import { TemplateResult as TemplateResult$1 } from "../node_modules/lit-html/development/lit-html.js"; import { TemporalMixinInterface } from "../elements/EFTemporal.js"; import { CanvasElementData } from "./api/types.js"; import { SelectionContext } from "./selection/selectionContext.js"; import { PanZoomTransform } from "../elements/EFPanZoom.js"; import * as _$lit from "lit"; import { LitElement } from "lit"; //#region src/canvas/EFCanvas.d.ts declare const EFCanvas_base: typeof LitElement; /** * ============================================================================= * COORDINATE SYSTEM AND DIMENSION CALCULATION PRINCIPLES * ============================================================================= * * This canvas system uses a unified approach for calculating element positions * and dimensions that works correctly regardless of CSS transforms (rotation, * scale, etc.), zoom level, or nesting depth. * * TWO KEY DOM APIS WITH DIFFERENT BEHAVIORS: * * 1. offsetWidth / offsetHeight * - Returns the element's LAYOUT dimensions (CSS box model) * - These are the dimensions BEFORE any CSS transforms are applied * - UNAFFECTED by: rotation, scale, skew, or any other transform * - UNAFFECTED by: parent transforms (including zoom scale) * - AFFECTED by: CSS width/height properties, padding, border * - Units: CSS pixels in the element's own coordinate space * * Example: A 200x100px element rotated 45° still has offsetWidth=200, offsetHeight=100 * Example: A 200x100px element in a 2x zoomed canvas still has offsetWidth=200, offsetHeight=100 * * USE FOR: Getting the actual dimensions of an element in canvas coordinates * * 2. getBoundingClientRect() * - Returns the element's visual BOUNDING BOX on screen * - This is the axis-aligned rectangle that fully contains the transformed element * - AFFECTED by: rotation (bounding box grows), scale, all transforms * - AFFECTED by: parent transforms (including zoom scale) * - Units: Screen pixels (viewport coordinates) * * Example: A 200x100px element rotated 45° has bounding box ~212x212px * Example: A 200x100px element in a 2x zoomed canvas has bounding rect 400x200px * * USE FOR: Getting the visual center position (center of bounding box = element center) * NOT FOR: Getting actual dimensions (bounding box ≠ actual size when rotated) * * THE UNIFIED CALCULATION METHOD: * * For ANY element (rotated, scaled, nested, etc.): * * 1. DIMENSIONS: Use offsetWidth/offsetHeight * - These give us the true dimensions in canvas coordinates * - No division by scale needed - they're already in canvas space * * 2. CENTER POSITION: Use getBoundingClientRect() center * - screenCenterX = rect.left + rect.width / 2 * - screenCenterY = rect.top + rect.height / 2 * - The center of the bounding box IS the element's center * - This is true for ANY rotation (rotation around center keeps center fixed) * * 3. TOP-LEFT POSITION: Calculate from center and dimensions * - canvasX = canvasCenter.x - width / 2 * - canvasY = canvasCenter.y - height / 2 * * WHY THIS WORKS UNIVERSALLY: * * - offsetWidth/Height are defined by CSS spec to be unaffected by transforms * - The center of any shape is preserved under rotation around that center * - This mathematical relationship holds for ALL transform combinations * - No special cases needed for rotation, scale, or nesting * * COMMON MISTAKES TO AVOID: * * ❌ Using getBoundingClientRect().width for dimensions (wrong when rotated) * ❌ Dividing offsetWidth by scale (offsetWidth is already in canvas coords) * ❌ Using getBoundingClientRect().left/top for position (wrong when rotated) * ❌ Having different code paths for rotated vs non-rotated elements * * ============================================================================= */ /** * Main canvas container element. * Manages existing elements (EF* elements, divs, etc.) and provides selection functionality. */ declare class EFCanvas extends EFCanvas_base { static styles: _$lit.CSSResult[]; panZoomTransform?: PanZoomTransform; elementIdAttribute: string; enableTransformHandles: boolean; /** * When true, the overlay RAF loop skips all layout-thrashing work * (transform handles, metadata updates). Used during playback to * avoid competing with the canvas render pipeline for CPU time. */ paused: boolean; private elementRegistry; private elementMetadata; private selectionController; private overlayLayer; private selectionOverlay; private transformHandlesMap; private overlayRafId; private isDragging; private dragStarted; private dragStartPos; private dragStartCanvasPos; private dragStartElementPositions; private draggedElementId; private capturedPointerId; private readonly DRAG_THRESHOLD; private isBoxSelecting; private boxSelectStart; private boxSelectModifierKeys; private emptySpaceClickPos; private selectionChangeHandler?; private elementHoverHandlers; private _activeRootTemporal; selectionContext: SelectionContext; /** * The currently highlighted (hovered) element. * This is the source of truth for highlight state across all panels * (canvas, hierarchy, timeline). */ highlightedElement: HTMLElement | null; constructor(); connectedCallback(): void; disconnectedCallback(): void; /** * Setup mutation observer to track element additions/removals. */ private setupElementTracking; /** * Try to register an element, auto-generating an ID if needed. * Public method for external use (e.g., hierarchy selection). */ tryRegisterElement(element: HTMLElement): void; /** * Register an element for canvas management. * @throws Error if element does not have an ID */ registerElement(element: HTMLElement, id?: string): string; /** * Unregister an element. */ unregisterElement(element: HTMLElement | string): void; /** * Setup hover event listeners for an element to update highlightedElement. * The canvas is the source of truth for highlight state. */ private setupElementHoverListeners; /** * Set the highlighted element. Called by canvas hover handlers * and by external panels (hierarchy, timeline) when user hovers items. */ setHighlightedElement(element: HTMLElement | null): void; /** * Remove hover event listeners for an element. */ private removeElementHoverListeners; /** * Update element metadata from DOM. * * UNIFIED APPROACH - works for ALL elements regardless of rotation, scale, or nesting: * * 1. DIMENSIONS: Always use offsetWidth/offsetHeight * - These are layout dimensions in the element's coordinate space * - Unaffected by CSS transforms (rotation, scale, etc.) * - Already in canvas coordinates (no scale division needed) * * 2. CENTER POSITION: Always use getBoundingClientRect() center * - The center of the bounding box IS the element's center (transform-origin: center) * - Works correctly for rotated elements (center is rotation-invariant) * - Convert to canvas coordinates using screenToCanvas() * * 3. TOP-LEFT POSITION: Calculate from center and dimensions * - x = centerX - width/2 * - y = centerY - height/2 */ private updateElementMetadata; /** * Hit test - find elements intersecting with bounds. * @param bounds - DOMRect in canvas coordinate space (from SelectionModel.boxSelectBounds) */ private hitTest; /** * Handle pointer down events. * Only handles events when clicking on elements. Otherwise, lets panzoom handle it. */ private handlePointerDown; /** * Handle pointer move events. */ private handlePointerMove; /** * Handle pointer up events. */ private handlePointerUp; /** * Setup listener for selection changes to update data-selected attributes. */ private setupSelectionListener; /** * Remove selection change listener. */ private removeSelectionListener; /** * Get the root temporal element containing the current selection. * Returns null if no element is selected or the selected element has no root temporal. */ get activeRootTemporal(): (TemporalMixinInterface & HTMLElement) | null; /** * Update data-selected attributes on elements based on current selection. * This enables pure CSS styling of selected elements. */ private updateSelectionAttributes; /** * Update element position in canvas coordinates. * Unified approach: Always calculate relative to parent (or .canvas-content for direct children). * For direct children, parent position is (0, 0), so relative = absolute (no-op). * * For nested elements, we read parent's current position from DOM (not metadata) to ensure * we're always calculating relative to the actual current position. */ updateElementPosition(elementId: string, x: number, y: number): void; /** * Get element metadata. */ getElementData(elementId: string): CanvasElementData | null; /** * Get all element data. */ getAllElementsData(): CanvasElementData[]; /** * Convert screen coordinates to canvas coordinates (for API). */ screenToCanvasCoords(screenX: number, screenY: number): { x: number; y: number; }; /** * Convert canvas coordinates to screen coordinates (for API). */ canvasToScreenCoords(canvasX: number, canvasY: number): { x: number; y: number; }; /** * Setup overlay layer as sibling of panzoom (outside panzoom, same level as panzoom). */ private setupOverlayLayer; /** * Cleanup overlay layer if we created it. */ private cleanupOverlayLayer; /** * Setup selection overlay as sibling of panzoom (outside panzoom, same level as panzoom). * This ensures the overlay maintains 1:1 pixel ratio regardless of zoom level. */ private setupSelectionOverlay; /** * Cleanup selection overlay if we created it. */ private cleanupSelectionOverlay; /** * Start RAF loop for overlay layer sync and transform handles updates. */ private wasPaused; private startOverlayRafLoop; /** * Stop RAF loop. */ private stopOverlayRafLoop; /** * Update transform handles for selected elements. * For multiple selections, shows a single set of handles for the bounding box. */ private updateTransformHandles; /** * Update transform handles bounds for an element. */ private updateTransformHandlesBounds; /** * Handle transform handles bounds-change event for multi-selection. * Updates all selected elements proportionally. */ private handleMultiSelectionTransformHandlesBoundsChange; private lastMultiSelectionRotation; private multiSelectionRotationCenter; private multiSelectionResizeInitial; /** * Handle transform handles rotation-change event for multiple selected elements. * Rotates all elements around the center of the bounding box. */ private handleMultiSelectionTransformHandlesRotationChange; /** * Handle transform handles bounds-change event for single element. * Converts bounds from overlay-relative screen coordinates to canvas coordinates. */ private handleTransformHandlesBoundsChange; /** * Handle transform handles rotation-change event for single element. */ private handleTransformHandlesRotationChange; /** * Cleanup transform handles. */ private cleanupTransformHandles; render(): TemplateResult$1<1>; } declare global { interface HTMLElementTagNameMap { "ef-canvas": EFCanvas; } } //#endregion export { EFCanvas }; //# sourceMappingURL=EFCanvas.d.ts.map