Comprehensive event management system providing type-safe event binding, custom event objects, specialized mouse/keyboard handlers, and async event utilities.
Bind and unbind event listeners with full type safety and consistent event object structure.
/**
* Bind event listener to element
* @param element - Element to attach listener to
* @param event - Event type to listen for
* @param handler - Event handler function
* @returns Unbinder object to remove the listener
*/
function bind<K extends keyof HTMLElementEventMap, T extends Node | Window = Node>(
element: SugarElement<EventTarget>,
event: K,
handler: EventHandler<HTMLElementEventMap[K], T>
): EventUnbinder;
/**
* Bind capturing event listener to element
* @param element - Element to attach listener to
* @param event - Event type to listen for
* @param handler - Event handler function
* @returns Unbinder object to remove the listener
*/
function capture<K extends keyof HTMLElementEventMap, T extends Node | Window = Node>(
element: SugarElement<EventTarget>,
event: K,
handler: EventHandler<HTMLElementEventMap[K], T>
): EventUnbinder;
/**
* Convert raw DOM event to Sugar event format
* @param rawEvent - Native DOM event
* @returns Sugar-formatted event arguments
*/
function fromRawEvent<E extends Event>(rawEvent: E): EventArgs<E>;Usage Examples:
import { SugarElement, DomEvent } from "@ephox/sugar";
const button = SugarElement.fromTag('button');
// Basic click handler
const unbindClick = DomEvent.bind(button, 'click', (evt) => {
console.log('Clicked at:', evt.x, evt.y);
evt.prevent(); // Prevent default action
evt.stop(); // Stop propagation
});
// Capturing listener for early event interception
const unbindCapture = DomEvent.capture(document.body, 'keydown', (evt) => {
if (evt.raw.key === 'Escape') {
evt.kill(); // Stop and prevent
}
});
// Clean up when done
unbindClick.unbind();
unbindCapture.unbind();Pre-configured mouse event handlers for common interaction patterns.
/**
* Real (non-synthetic) click event handler
*/
interface RealClickHandler {
bind(element: SugarElement<EventTarget>, handler: EventHandler<MouseEvent>): EventUnbinder;
}
/**
* Left mouse button down event handler
*/
interface LeftDownHandler {
bind(element: SugarElement<EventTarget>, handler: EventHandler<MouseEvent>): EventUnbinder;
}
/**
* Mouse over event with left button pressed
*/
interface LeftPressedOverHandler {
bind(element: SugarElement<EventTarget>, handler: EventHandler<MouseEvent>): EventUnbinder;
}
/**
* Left mouse button up event handler
*/
interface LeftUpHandler {
bind(element: SugarElement<EventTarget>, handler: EventHandler<MouseEvent>): EventUnbinder;
}
// Pre-configured mouse event objects
const realClick: RealClickHandler;
const leftDown: LeftDownHandler;
const leftPressedOver: LeftPressedOverHandler;
const leftUp: LeftUpHandler;Usage Examples:
import { MouseEvents, SugarElement } from "@ephox/sugar";
const canvas = SugarElement.fromTag('canvas');
// Handle mouse dragging
let isDragging = false;
MouseEvents.leftDown.bind(canvas, (evt) => {
isDragging = true;
console.log('Drag started at:', evt.x, evt.y);
});
MouseEvents.leftPressedOver.bind(canvas, (evt) => {
if (isDragging) {
console.log('Dragging to:', evt.x, evt.y);
}
});
MouseEvents.leftUp.bind(document.body, (evt) => {
if (isDragging) {
isDragging = false;
console.log('Drag ended at:', evt.x, evt.y);
}
});Handle document and resource loading states with promises and callbacks.
/**
* Execute callback when document is ready
* @param f - Function to execute when document ready
*/
function document(f: () => void): void;
/**
* Wait for image to load completely
* @param image - Image element to monitor
* @returns Promise that resolves when image loads
*/
function image(image: SugarElement<HTMLImageElement>): Promise<SugarElement<HTMLImageElement>>;Usage Examples:
import { Ready, SugarElement } from "@ephox/sugar";
// Wait for document ready
Ready.document(() => {
console.log('Document is ready for manipulation');
// Initialize application
});
// Wait for specific image
const img = SugarElement.fromTag('img');
img.dom.src = 'large-image.jpg';
Ready.image(img).then((loadedImg) => {
console.log('Image loaded successfully');
// Proceed with image-dependent operations
}).catch((error) => {
console.error('Image failed to load');
});Monitor element size changes with ResizeObserver-based detection.
/**
* Start monitoring element for resize changes
* @param element - Element to monitor
* @param handler - Callback function for resize events
*/
function bind(element: SugarElement<HTMLElement>, handler: () => void): void;
/**
* Stop monitoring element for resize changes
* @param element - Element to stop monitoring
* @param handler - Handler function to remove
*/
function unbind(element: SugarElement<Node>, handler: () => void): void;Monitor scroll position changes across the document.
/**
* Monitor scroll position changes on document
* @param doc - Document to monitor
* @param handler - Callback receiving new scroll position
* @returns Unbinder to stop monitoring
*/
function bind(doc: SugarElement<Document>, handler: (pos: SugarPosition) => void): EventUnbinder;Execute callbacks when elements become visible or hidden.
/**
* Execute callback when element becomes visible in viewport
* @param element - Element to monitor for visibility
* @param f - Callback to execute when visible
* @returns Function to stop monitoring
*/
function onShow(element: SugarElement<HTMLElement>, f: () => void): () => void;Wait for specific events to occur with timeout support.
/**
* Wait for event to occur with timeout (lazy evaluation)
* @param element - Element to listen on
* @param eventType - Type of event to wait for
* @param timeout - Timeout in milliseconds
* @returns Lazy value that resolves to event result
*/
function cWaitFor(
element: SugarElement<EventTarget>,
eventType: string,
timeout: number
): LazyValue<Result<EventArgs, string>>;
/**
* Wait for event to occur with timeout (immediate future)
* @param element - Element to listen on
* @param eventType - Type of event to wait for
* @param timeout - Timeout in milliseconds
* @returns Future that resolves to event result
*/
function waitFor(
element: SugarElement<EventTarget>,
eventType: string,
timeout: number
): Future<Result<EventArgs, string>>;Usage Examples:
import { DomFuture, SugarElement } from "@ephox/sugar";
const button = SugarElement.fromTag('button');
// Wait for click with timeout
DomFuture.waitFor(button, 'click', 5000).get((result) => {
result.fold(
(eventArgs) => {
console.log('Button was clicked!', eventArgs);
},
(error) => {
console.log('Timeout waiting for click:', error);
}
);
});Future-based event utilities for waiting on specific events with timeouts.
/**
* Wait for an event with timeout, returns a Future
* @param element - Element to listen on
* @param eventType - Event type to wait for
* @param timeout - Timeout in milliseconds
* @returns Future that resolves with event or error on timeout
*/
function waitFor(
element: SugarElement<EventTarget>,
eventType: string,
timeout: number
): Future<Result<EventArgs, string>>;
/**
* Create a lazy evaluator that waits for an event with timeout
* @param element - Element to listen on
* @param eventType - Event type to wait for
* @param timeout - Timeout in milliseconds
* @returns LazyValue that can be evaluated to wait for the event
*/
function cWaitFor(
element: SugarElement<EventTarget>,
eventType: string,
timeout: number
): LazyValue<Result<EventArgs, string>>;Usage Examples:
import { DomFuture, SugarElement } from "@ephox/sugar";
const button = SugarElement.fromTag('button');
// Wait for click with 5 second timeout
DomFuture.waitFor(button, 'click', 5000).get((result) => {
result.fold(
(eventArgs) => console.log('Button clicked!', eventArgs),
(error) => console.log('Timeout:', error)
);
});
// Lazy evaluation approach
const clickWaiter = DomFuture.cWaitFor(button, 'click', 3000);
// Later when needed:
clickWaiter.get().fold(
(eventArgs) => console.log('Got click'),
(error) => console.log('Timed out')
);Enhanced scroll event handling that only fires when scroll position actually changes.
/**
* Bind scroll event listener that only fires on actual position changes
* @param doc - Document element to monitor for scroll
* @param handler - Callback function receiving scroll position
* @returns Event unbinder
*/
function bind(doc: SugarElement<Document>, handler: (pos: SugarPosition) => void): EventUnbinder;Usage Examples:
import { ScrollChange, SugarDocument } from "@ephox/sugar";
const doc = SugarDocument.getDocument();
// Monitor scroll changes
const unbindScroll = ScrollChange.bind(doc, (pos) => {
console.log('Scroll position changed:', pos.left, pos.top);
// Update floating header position
if (pos.top > 100) {
floatingHeader.classList.add('fixed');
} else {
floatingHeader.classList.remove('fixed');
}
});
// Clean up when done
unbindScroll.unbind();Monitor elements for visibility changes using MutationObserver or polling fallback.
/**
* Execute callback when element becomes visible
* @param element - Element to monitor for visibility
* @param callback - Function to execute when element becomes visible
* @returns Function to stop monitoring
*/
function onShow(element: SugarElement<HTMLElement>, callback: () => void): () => void;Usage Examples:
import { Viewable, SugarElement } from "@ephox/sugar";
const modal = SugarElement.fromTag('div');
modal.dom.style.display = 'none';
// Setup visibility monitoring
const stopMonitoring = Viewable.onShow(modal, () => {
console.log('Modal is now visible!');
// Initialize modal content, start animations, etc.
});
// Later, show the modal
modal.dom.style.display = 'block'; // Callback will fire
// Stop monitoring when done
stopMonitoring();/**
* Normalized event object with consistent structure across browsers
*/
interface EventArgs<E = Event, T extends Node | Window = Node> {
/** Element that received the event */
readonly target: SugarElement<T>;
/** X coordinate for mouse events, undefined for others */
readonly x: E extends { clientX: number } ? number : undefined;
/** Y coordinate for mouse events, undefined for others */
readonly y: E extends { clientY: number } ? number : undefined;
/** Stop event propagation */
readonly stop: () => void;
/** Prevent default action */
readonly prevent: () => void;
/** Stop propagation and prevent default */
readonly kill: () => void;
/** Original native event object */
readonly raw: E;
}
/**
* Event handler function type
*/
type EventHandler<E = Event, T extends Node | Window = Node> = (evt: EventArgs<E, T>) => void;
/**
* Event filter predicate function
*/
type EventFilter<E = Event> = (evt: E) => boolean;
/**
* Object returned by event binding functions for cleanup
*/
interface EventUnbinder {
unbind: () => void;
}
// Position type from view package
interface SugarPosition {
readonly left: number;
readonly top: number;
readonly translate: (x: number, y: number) => SugarPosition;
}
// Async utility types from @ephox/katamari
interface LazyValue<T> {
get(): T;
}
interface Future<T> {
get(callback: (value: T) => void): void;
}
interface Result<T, E> {
fold<U>(success: (value: T) => U, failure: (error: E) => U): U;
}