@ue-too/board
Version:
<h1 align="center"> uē-tôo </h1> <p align="center"> pan, zoom, rotate, and more with your html canvas. </p>
304 lines (303 loc) • 11.7 kB
TypeScript
import { Observer, SubscriptionOptions } from "../utils/observable";
/**
* Monitors and publishes position and dimension changes for SVG elements.
*
* @remarks
* This class tracks SVG element position and dimensions using multiple browser APIs
* to ensure comprehensive detection of all changes:
* - ResizeObserver: Detects size changes
* - IntersectionObserver: Detects visibility and position changes
* - MutationObserver: Detects attribute changes (width, height, style)
* - Window scroll/resize events: Detects changes from page layout
*
* The reported DOMRect excludes padding and borders to provide the actual
* content dimensions using {@link getTrueRect}.
*
* Position and dimension changes are published synchronously to all subscribers,
* ensuring immediate updates for coordinate transformations and rendering logic.
*
* @example
* ```typescript
* const svg = document.querySelector('svg');
* const publisher = new SvgPositionDimensionPublisher(svg);
*
* // Subscribe to position/dimension updates
* publisher.onPositionUpdate((rect) => {
* console.log(`SVG at (${rect.x}, ${rect.y}) with size ${rect.width}x${rect.height}`);
* });
*
* // Clean up when done
* publisher.dispose();
* ```
*
* @category Canvas Position
*/
export declare class SvgPositionDimensionPublisher {
private lastRect?;
private resizeObserver;
private intersectionObserver;
private mutationObserver;
private scrollHandler?;
private resizeHandler?;
private _observers;
/**
* Creates a new SVG position/dimension publisher.
*
* @param canvas - Optional SVG element to immediately attach to
*
* @remarks
* If a canvas is provided, observers are immediately attached and monitoring begins.
* Otherwise, call {@link attach} later to begin monitoring.
*/
constructor(canvas?: SVGSVGElement);
/**
* Cleans up all observers and event listeners.
*
* @remarks
* Disconnects all observers (ResizeObserver, IntersectionObserver, MutationObserver)
* and removes window event listeners (scroll, resize). Always call this method
* when the publisher is no longer needed to prevent memory leaks.
*/
dispose(): void;
/**
* Attaches observers to an SVG element and begins monitoring.
*
* @param canvas - The SVG element to monitor
*
* @remarks
* Automatically calls {@link dispose} first to clean up any previous attachments.
* Sets up all observers and records the initial position/dimensions.
*
* The initial rect is calculated immediately and stored, but no notification
* is sent to observers for this initial state.
*/
attach(canvas: SVGSVGElement): void;
private publishPositionUpdate;
/**
* Subscribes to position and dimension updates.
*
* @param observer - Callback function that receives the updated DOMRect
* @param options - Optional subscription options (e.g., AbortSignal for cleanup)
* @returns Unsubscribe function to remove this observer
*
* @remarks
* The observer is called synchronously whenever the SVG's position or dimensions change.
* The DOMRect parameter represents the actual content area (excluding padding and borders).
*
* @example
* ```typescript
* const unsubscribe = publisher.onPositionUpdate((rect) => {
* console.log(`Position: ${rect.x}, ${rect.y}`);
* console.log(`Size: ${rect.width}x${rect.height}`);
* });
*
* // Later, when done:
* unsubscribe();
* ```
*/
onPositionUpdate(observer: Observer<[DOMRect]>, options?: SubscriptionOptions): () => void;
private attributeCallBack;
}
/**
* Monitors and publishes position and dimension changes for HTML Canvas elements.
*
* @remarks
* Similar to {@link SvgPositionDimensionPublisher} but specifically for HTMLCanvasElement.
* Automatically handles device pixel ratio adjustments to maintain crisp rendering
* at different screen densities.
*
* Key differences from SVG version:
* - Automatically adjusts canvas.width/height attributes based on devicePixelRatio
* - Synchronizes CSS dimensions (style.width/height) with canvas buffer size
* - Ensures canvas maintains proper resolution on high-DPI displays
*
* The class uses multiple browser APIs for comprehensive change detection:
* - ResizeObserver: Detects size changes
* - IntersectionObserver: Detects visibility and position changes
* - MutationObserver: Detects attribute changes and synchronizes dimensions
* - Window scroll/resize events: Detects changes from page layout
*
* @example
* ```typescript
* const canvas = document.querySelector('canvas');
* const publisher = new CanvasPositionDimensionPublisher(canvas);
*
* // Subscribe to updates
* publisher.onPositionUpdate((rect) => {
* // Canvas dimensions automatically adjusted for devicePixelRatio
* console.log(`Canvas at (${rect.x}, ${rect.y})`);
* console.log(`Display size: ${rect.width}x${rect.height}`);
* });
*
* publisher.dispose();
* ```
*
* @category Canvas Position
* @see {@link SvgPositionDimensionPublisher} for SVG elements
*/
export declare class CanvasPositionDimensionPublisher {
private lastRect?;
private resizeObserver;
private intersectionObserver;
private mutationObserver;
private scrollHandler?;
private resizeHandler?;
private _observers;
private _abortController;
private _pixelRatioAbortController;
/**
* Creates a new Canvas position/dimension publisher.
*
* @param canvas - Optional canvas element to immediately attach to
*
* @remarks
* If a canvas is provided, observers are immediately attached and monitoring begins.
* The canvas dimensions are automatically adjusted for devicePixelRatio.
*/
constructor(canvas?: HTMLCanvasElement);
/**
* Cleans up all observers and event listeners.
*
* @remarks
* Disconnects all observers and removes window event listeners.
* Always call this method when the publisher is no longer needed to prevent memory leaks.
*/
dispose(): void;
/**
* Attaches observers to a canvas element and begins monitoring.
*
* @param canvas - The canvas element to monitor
*
* @remarks
* Automatically calls {@link dispose} first to clean up any previous attachments.
* Sets up all observers, adjusts canvas dimensions for devicePixelRatio,
* and records the initial position/dimensions.
*/
attach(canvas: HTMLCanvasElement): void;
private publishPositionUpdate;
/**
* Subscribes to position and dimension updates.
*
* @param observer - Callback function that receives the updated DOMRect
* @param options - Optional subscription options (e.g., AbortSignal for cleanup)
* @returns Unsubscribe function to remove this observer
*
* @remarks
* The observer is called synchronously whenever the canvas position or dimensions change.
* The DOMRect represents the actual content area (excluding padding and borders).
* Canvas buffer dimensions are automatically adjusted for devicePixelRatio.
*/
onPositionUpdate(observer: Observer<[DOMRect]>, options?: SubscriptionOptions): () => void;
/**
* Handles attribute mutations on the canvas element.
*
* @param mutationsList - List of mutations detected
* @param observer - The MutationObserver instance
*
* @remarks
* This callback synchronizes canvas buffer size with CSS dimensions:
* - When width/height attributes change: Updates CSS dimensions based on devicePixelRatio
* - When style changes: Updates buffer size to match CSS dimensions
*
* This ensures the canvas maintains proper resolution on all displays.
*/
private attributeCallBack;
}
/**
* Calculates the actual content rectangle excluding padding and borders.
*
* @param rect - The element's bounding client rectangle
* @param computedStyle - The computed CSS styles for the element
* @returns DOMRect representing the content area only
*
* @remarks
* Browser's getBoundingClientRect() includes padding and borders, but for
* coordinate transformations we need the actual drawable content area.
*
* This function subtracts padding and border from all four sides to get
* the "true" content rectangle. This is essential for accurate coordinate
* conversions between window and canvas space.
*
* @example
* ```typescript
* const canvas = document.querySelector('canvas');
* const rect = canvas.getBoundingClientRect();
* const style = window.getComputedStyle(canvas);
* const contentRect = getTrueRect(rect, style);
*
* // contentRect.width is less than rect.width if padding/borders exist
* console.log(`Full size: ${rect.width}x${rect.height}`);
* console.log(`Content size: ${contentRect.width}x${contentRect.height}`);
* ```
*
* @category Canvas Position
*/
export declare function getTrueRect(rect: DOMRect, computedStyle: CSSStyleDeclaration): DOMRect;
/**
* Creates a proxy that automatically flips y-coordinates for canvas context methods.
*
* @param context - The canvas 2D rendering context to wrap
* @returns Proxied context that handles y-axis reversal automatically
*
* @remarks
* Standard HTML canvas uses a top-left origin with y-axis pointing down.
* This proxy inverts the y-axis to create a mathematical coordinate system
* with y-axis pointing up.
*
* The proxy intercepts drawing methods (fillRect, strokeRect, moveTo, lineTo, etc.)
* and automatically negates y-coordinates and height values. This allows you to
* work in mathematical coordinates while still rendering correctly.
*
* Special handling for complex methods:
* - drawImage with 9 args: Properly inverts source and destination rectangles
* - drawImage with 5 args: Adjusts for image height
* - All methods in {@link methodsToFlip}: Y-coordinates negated automatically
*
* @example
* ```typescript
* const canvas = document.querySelector('canvas');
* const ctx = canvas.getContext('2d');
* const flippedCtx = reverseYAxis(ctx);
*
* // Draw with mathematical coordinates (y-up)
* flippedCtx.fillRect(0, 0, 100, 100); // Square in first quadrant
* flippedCtx.moveTo(0, 0);
* flippedCtx.lineTo(50, 100); // Line going upward
* ```
*
* @category Canvas Position
* @see {@link methodsToFlip} for list of intercepted methods
* @see {@link invertYAxisForDrawImageWith9Args} for drawImage special handling
*/
export declare function reverseYAxis(context: CanvasRenderingContext2D): CanvasRenderingContext2D;
/**
* Inverts y-coordinates for the 9-argument variant of drawImage.
*
* @param args - The arguments array for drawImage
* @returns Modified arguments with inverted y-coordinates
*
* @remarks
* The 9-argument drawImage signature is:
* drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
*
* When inverting y-axis, we need to adjust:
* - sy (source y): Flip relative to image height
* - sHeight: Negate (height becomes negative in flipped space)
* - dy (destination y): Negate
* - dy offset: Subtract destination height
*
* This ensures images render correctly when the canvas y-axis is flipped.
*
* @example
* ```typescript
* // Original call (top-left origin):
* ctx.drawImage(img, 0, 0, 100, 100, 50, 50, 200, 200);
*
* // With flipped y-axis, this becomes:
* // sy = imageHeight - 0, sHeight = -100, dy = -50 - 200, dHeight = -200
* ```
*
* @category Canvas Position
* @see {@link reverseYAxis} for the main y-axis flipping proxy
*/
export declare function invertYAxisForDrawImageWith9Args(args: any[]): typeof args;