UNPKG

@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
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;