@toolbox-web/grid 3.9.2 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/all.js +1 -1
  2. package/all.js.map +1 -1
  3. package/index.js +1 -1
  4. package/index.js.map +1 -1
  5. package/lib/core/types.d.ts +6 -3
  6. package/lib/features/clipboard.js.map +1 -1
  7. package/lib/features/column-virtualization.js.map +1 -1
  8. package/lib/features/context-menu.js.map +1 -1
  9. package/lib/features/editing.js.map +1 -1
  10. package/lib/features/export.js.map +1 -1
  11. package/lib/features/filtering.js.map +1 -1
  12. package/lib/features/grouping-columns.js.map +1 -1
  13. package/lib/features/grouping-rows.js.map +1 -1
  14. package/lib/features/master-detail.js.map +1 -1
  15. package/lib/features/multi-sort.js.map +1 -1
  16. package/lib/features/pinned-columns.js.map +1 -1
  17. package/lib/features/pinned-rows.js.map +1 -1
  18. package/lib/features/pivot.js.map +1 -1
  19. package/lib/features/print.js.map +1 -1
  20. package/lib/features/registry.js +1 -1
  21. package/lib/features/registry.js.map +1 -1
  22. package/lib/features/reorder-columns.js.map +1 -1
  23. package/lib/features/responsive.js.map +1 -1
  24. package/lib/features/row-drag-drop.js.map +1 -1
  25. package/lib/features/selection.js.map +1 -1
  26. package/lib/features/server-side.js.map +1 -1
  27. package/lib/features/shell.js.map +1 -1
  28. package/lib/features/sticky-rows.js.map +1 -1
  29. package/lib/features/tooltip.js.map +1 -1
  30. package/lib/features/tree.js.map +1 -1
  31. package/lib/features/undo-redo.js.map +1 -1
  32. package/lib/features/visibility.js.map +1 -1
  33. package/lib/plugins/clipboard/index.js.map +1 -1
  34. package/lib/plugins/column-virtualization/index.js.map +1 -1
  35. package/lib/plugins/context-menu/index.js.map +1 -1
  36. package/lib/plugins/editing/editors.d.ts +15 -0
  37. package/lib/plugins/editing/index.js +1 -1
  38. package/lib/plugins/editing/index.js.map +1 -1
  39. package/lib/plugins/editing/types.d.ts +5 -1
  40. package/lib/plugins/export/index.js +1 -1
  41. package/lib/plugins/export/index.js.map +1 -1
  42. package/lib/plugins/filtering/filter-model.d.ts +20 -1
  43. package/lib/plugins/filtering/filter-panel-default.d.ts +15 -1
  44. package/lib/plugins/filtering/index.d.ts +1 -1
  45. package/lib/plugins/filtering/index.js +1 -1
  46. package/lib/plugins/filtering/index.js.map +1 -1
  47. package/lib/plugins/filtering/types.d.ts +58 -3
  48. package/lib/plugins/grouping-columns/index.js.map +1 -1
  49. package/lib/plugins/grouping-rows/index.js +1 -1
  50. package/lib/plugins/grouping-rows/index.js.map +1 -1
  51. package/lib/plugins/hierarchy/index.js.map +1 -1
  52. package/lib/plugins/master-detail/index.js +1 -1
  53. package/lib/plugins/master-detail/index.js.map +1 -1
  54. package/lib/plugins/multi-sort/index.js.map +1 -1
  55. package/lib/plugins/pinned-columns/index.js.map +1 -1
  56. package/lib/plugins/pinned-rows/index.js.map +1 -1
  57. package/lib/plugins/pivot/index.js +1 -1
  58. package/lib/plugins/pivot/index.js.map +1 -1
  59. package/lib/plugins/pivot/pivot-plugin.d.ts +2 -0
  60. package/lib/plugins/pivot/types.d.ts +2 -0
  61. package/lib/plugins/print/index.js.map +1 -1
  62. package/lib/plugins/reorder-columns/index.js.map +1 -1
  63. package/lib/plugins/responsive/index.js.map +1 -1
  64. package/lib/plugins/row-drag-drop/index.js.map +1 -1
  65. package/lib/plugins/selection/index.js +1 -1
  66. package/lib/plugins/selection/index.js.map +1 -1
  67. package/lib/plugins/server-side/index.js.map +1 -1
  68. package/lib/plugins/shared/form-field-target.d.ts +10 -0
  69. package/lib/plugins/shared/format-value.d.ts +12 -0
  70. package/lib/plugins/shell/index.js.map +1 -1
  71. package/lib/plugins/sticky-rows/index.js.map +1 -1
  72. package/lib/plugins/tooltip/index.js.map +1 -1
  73. package/lib/plugins/tree/index.js +1 -1
  74. package/lib/plugins/tree/index.js.map +1 -1
  75. package/lib/plugins/undo-redo/index.js.map +1 -1
  76. package/lib/plugins/visibility/index.js.map +1 -1
  77. package/package.json +1 -1
  78. package/umd/grid.all.umd.js +1 -1
  79. package/umd/grid.all.umd.js.map +1 -1
  80. package/umd/grid.umd.js +1 -1
  81. package/umd/grid.umd.js.map +1 -1
  82. package/umd/plugins/clipboard.umd.js.map +1 -1
  83. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  84. package/umd/plugins/context-menu.umd.js.map +1 -1
  85. package/umd/plugins/editing.umd.js +1 -1
  86. package/umd/plugins/editing.umd.js.map +1 -1
  87. package/umd/plugins/export.umd.js +1 -1
  88. package/umd/plugins/export.umd.js.map +1 -1
  89. package/umd/plugins/filtering.umd.js +1 -1
  90. package/umd/plugins/filtering.umd.js.map +1 -1
  91. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  92. package/umd/plugins/grouping-rows.umd.js +1 -1
  93. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  94. package/umd/plugins/hierarchy.umd.js.map +1 -1
  95. package/umd/plugins/master-detail.umd.js +1 -1
  96. package/umd/plugins/master-detail.umd.js.map +1 -1
  97. package/umd/plugins/multi-sort.umd.js.map +1 -1
  98. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  99. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  100. package/umd/plugins/pivot.umd.js +1 -1
  101. package/umd/plugins/pivot.umd.js.map +1 -1
  102. package/umd/plugins/print.umd.js.map +1 -1
  103. package/umd/plugins/reorder-columns.umd.js.map +1 -1
  104. package/umd/plugins/responsive.umd.js.map +1 -1
  105. package/umd/plugins/row-drag-drop.umd.js.map +1 -1
  106. package/umd/plugins/selection.umd.js +1 -1
  107. package/umd/plugins/selection.umd.js.map +1 -1
  108. package/umd/plugins/server-side.umd.js.map +1 -1
  109. package/umd/plugins/shell.umd.js.map +1 -1
  110. package/umd/plugins/sticky-rows.umd.js.map +1 -1
  111. package/umd/plugins/tooltip.umd.js.map +1 -1
  112. package/umd/plugins/tree.umd.js +1 -1
  113. package/umd/plugins/tree.umd.js.map +1 -1
  114. package/umd/plugins/undo-redo.umd.js.map +1 -1
  115. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../../../libs/grid/src/lib/core/constants.ts","../../../../../../libs/grid/src/lib/core/internal/drag-drop-registry.ts","../../../../../../libs/grid/src/lib/core/internal/utils.ts","../../../../../../libs/grid/src/lib/core/internal/keyboard.ts","../../../../../../libs/grid/src/lib/plugins/shared/drag-alternative-menu.ts","../../../../../../libs/grid/src/lib/plugins/shared/drag-drop-protocol.ts","../../../../../../libs/grid/src/lib/plugins/row-drag-drop/row-drag-drop.css?inline","../../../../../../libs/grid/src/lib/plugins/row-drag-drop/row-drag-drop-plugin.ts"],"sourcesContent":["/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin, UndoRedoPlugin, and keyboard navigation. Defined here\n * (a leaf module) rather than in `rows.ts` so `keyboard.ts` can consume it\n * without creating a rows ↔ keyboard import cycle.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-grid-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n FIELD: 'data-field',\n\n /**\n * Marks a `title` the grid itself put on a cell to reveal ellipsised text\n * (WCAG SC 1.4.12). Anything that sets its own `title` on a cell must clear\n * this, or the grid will later mistake that title for its own and replace it.\n *\n * @since 3.6.0\n */\n TRUNCATED: 'data-tbw-truncated',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\n\n/**\n * Union of all CSS class names used in the grid's DOM.\n * Use `GridClasses` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\n\n/**\n * Union of all `data-*` attribute names used on grid elements.\n * Use `GridDataAttrs` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\n\n/**\n * Union of all CSS custom property names (`--tbw-*`) available for theming.\n * Use `GridCSSVars` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","/**\n * Drag-Drop Registry\n *\n * Module-level singleton that holds live (WeakRef) references to row objects\n * being dragged across grids in the same JS heap. Used by `RowDragDropPlugin`\n * to recover live references on drop, avoiding any serialization cost when\n * source and target grids share a heap.\n *\n * Lives in `core/internal/` (not in the plugin bundle) so that two copies of\n * `@toolbox-web/grid` loaded via different entry points (e.g. `/all` vs\n * per-plugin imports) cannot end up with two separate registries that silently\n * fall back to JSON.\n *\n * Cross-window / cross-origin-iframe drags do NOT go through this registry —\n * they fall back to `dataTransfer` JSON.\n *\n * @internal\n */\n\ninterface RegistryEntry {\n readonly refs: ReadonlyArray<WeakRef<object>>;\n /** True when one or more registered rows were primitives (not objects). */\n readonly hasPrimitives: boolean;\n}\n\nconst registry = new Map<string, RegistryEntry>();\n\n/**\n * Register a drag session's live row references.\n *\n * Object rows are stored as `WeakRef<object>`; primitive rows cannot be held\n * weakly and would either need a sentinel (which corrupts the row value on\n * recovery) or be omitted (which misaligns indices). We instead flag the\n * session as containing primitives — `lookupDragSession()` then returns\n * `undefined` so callers fall back to the JSON payload, which round-trips\n * primitives losslessly.\n *\n * @param sessionId - Unique drag session identifier (typically a UUID).\n * @param rows - Rows being dragged.\n */\nexport function registerDragSession(sessionId: string, rows: readonly unknown[]): void {\n const refs: WeakRef<object>[] = [];\n let hasPrimitives = false;\n for (const row of rows) {\n if (row !== null && typeof row === 'object') {\n refs.push(new WeakRef(row as object));\n } else {\n hasPrimitives = true;\n }\n }\n registry.set(sessionId, { refs, hasPrimitives });\n}\n\n/**\n * Look up the live row references for a drag session.\n *\n * Returns `undefined` when the caller MUST fall back to the JSON payload:\n * - the session is unknown,\n * - any registered reference has been garbage-collected (partial recovery\n * would silently corrupt cross-grid moves), or\n * - the session contains primitive rows (which cannot be held weakly).\n */\nexport function lookupDragSession<T = unknown>(sessionId: string): T[] | undefined {\n const entry = registry.get(sessionId);\n if (!entry) return undefined;\n if (entry.hasPrimitives) return undefined; // primitives — caller falls back to JSON\n const result: unknown[] = [];\n for (const ref of entry.refs) {\n const obj = ref.deref();\n if (obj === undefined) return undefined; // GC'd — caller falls back to JSON\n result.push(obj);\n }\n return result as T[];\n}\n\n/**\n * Clear a drag session from the registry.\n * Should be called from `dragend` on the source grid.\n */\nexport function clearDragSession(sessionId: string): void {\n registry.delete(sessionId);\n}\n\n/**\n * @internal Test-only — clear all sessions.\n */\nexport function _clearAllDragSessions(): void {\n registry.clear();\n}\n\n/**\n * Generate a unique drag session identifier.\n *\n * Used as a Map key to recover live row references for in-process drags; this\n * is **not** a security context. Uses `crypto.randomUUID()` when available\n * (modern browsers in secure contexts, Node 19+, Bun) and falls back to\n * `crypto.getRandomValues()` for older non-secure contexts. `Math.random()`\n * is intentionally avoided to keep CodeQL clean.\n */\nexport function newDragSessionId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const buf = new Uint32Array(2);\n crypto.getRandomValues(buf);\n return `drag-${Date.now().toString(36)}-${buf[0].toString(36)}${buf[1].toString(36)}`;\n }\n // Final fallback: timestamp + monotonically incrementing counter (no PRNG).\n // Sufficient because the registry is in-process and lives only for the\n // duration of a single drag.\n return `drag-${Date.now().toString(36)}-${(++fallbackCounter).toString(36)}`;\n}\n\nlet fallbackCounter = 0;\n","// #region Environment Helpers\n\n/**\n * Check if we're running in a development environment.\n * Returns true for localhost or when NODE_ENV !== 'production'.\n * Used to show warnings only in development.\n */\nexport function isDevelopment(): boolean {\n // Check for localhost (browser environment)\n if (typeof window !== 'undefined' && window.location) {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {\n return true;\n }\n }\n // Check for NODE_ENV (build-time or SSR)\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n return true;\n }\n return false;\n}\n\n// #endregion\n\n// #region Cell Rendering Helpers\n\n/**\n * Generate accessible HTML for a boolean cell.\n * Uses role=\"checkbox\" with proper aria attributes.\n */\nexport function booleanCellHTML(value: boolean): string {\n return `<span role=\"checkbox\" aria-checked=\"${value}\" aria-label=\"${value}\">${value ? '&#x1F5F9;' : '&#9744;'}</span>`;\n}\n\n/**\n * Format a date value for display.\n * Handles Date objects, timestamps, and date strings.\n * Returns empty string for invalid dates.\n */\nexport function formatDateValue(value: unknown): string {\n if (value == null || value === '') return '';\n if (value instanceof Date) {\n return isNaN(value.getTime()) ? '' : value.toLocaleDateString();\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return isNaN(d.getTime()) ? '' : d.toLocaleDateString();\n }\n return '';\n}\n\n/**\n * Get the row index from a cell element's data-row attribute.\n * Falls back to calculating from parent row's DOM position if data-row is missing.\n * Returns -1 if no valid row index is found.\n */\nexport function getRowIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-row');\n if (attr) return parseInt(attr, 10);\n\n // Fallback: find the parent .data-grid-row and calculate index from siblings\n const rowEl = cell.closest('.data-grid-row');\n if (!rowEl) return -1;\n\n const parent = rowEl.parentElement;\n if (!parent) return -1;\n\n // Get all data-grid-row siblings and find this row's index\n const rows = parent.querySelectorAll(':scope > .data-grid-row');\n for (let i = 0; i < rows.length; i++) {\n if (rows[i] === rowEl) return i;\n }\n return -1;\n}\n\n/**\n * Get the column index from a cell element's data-col attribute.\n * Returns -1 if no valid column index is found.\n */\nexport function getColIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-col');\n return attr ? parseInt(attr, 10) : -1;\n}\n\n/**\n * Clear all cell-focus styling from a root element.\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | null): void {\n if (!root) return;\n root.querySelectorAll('.cell-focus').forEach((el) => el.classList.remove('cell-focus'));\n}\n// #endregion\n\n// #region RTL Helpers\n\n/** Text direction */\nexport type TextDirection = 'ltr' | 'rtl';\n\n/**\n * Get the text direction for an element.\n * Reads from the computed style, which respects the `dir` attribute on the element\n * or any ancestor, as well as CSS `direction` property.\n *\n * @param element - The element to check direction for\n * @returns 'ltr' or 'rtl'\n *\n * @example\n * ```typescript\n * // Detect grid's direction\n * const dir = getDirection(gridElement);\n * if (dir === 'rtl') {\n * // Handle RTL layout\n * }\n * ```\n */\nexport function getDirection(element: Element): TextDirection {\n // Try computed style first (works in real browsers)\n try {\n const computedDir = getComputedStyle(element).direction;\n if (computedDir === 'rtl') return 'rtl';\n } catch {\n // getComputedStyle may fail in some test environments\n }\n\n // Fallback: check dir attribute on element or ancestors\n // This handles test environments where getComputedStyle may not reflect dir attribute\n try {\n const dirAttr = element.closest?.('[dir]')?.getAttribute('dir');\n if (dirAttr === 'rtl') return 'rtl';\n } catch {\n // closest may not be available on mock elements\n }\n\n return 'ltr';\n}\n\n/**\n * Check if an element is in RTL mode.\n *\n * @param element - The element to check\n * @returns true if the element's text direction is right-to-left\n */\nexport function isRTL(element: Element): boolean {\n return getDirection(element) === 'rtl';\n}\n\n/**\n * Resolve a logical inline position to a physical position based on text direction.\n *\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n * - `'left'` / `'right'` → unchanged (physical values)\n *\n * @param position - Logical or physical position\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical position ('left' or 'right')\n *\n * @example\n * ```typescript\n * resolveInlinePosition('start', 'ltr'); // 'left'\n * resolveInlinePosition('start', 'rtl'); // 'right'\n * resolveInlinePosition('left', 'rtl'); // 'left' (unchanged)\n * ```\n */\nexport function resolveInlinePosition(\n position: 'left' | 'right' | 'start' | 'end',\n direction: TextDirection,\n): 'left' | 'right' {\n if (position === 'left' || position === 'right') {\n return position;\n }\n if (direction === 'rtl') {\n return position === 'start' ? 'right' : 'left';\n }\n return position === 'start' ? 'left' : 'right';\n}\n// #endregion\n","/**\n * Central keyboard handler attached to the host element. Manages navigation, paging,\n * and edit lifecycle triggers while respecting active form field interactions.\n */\nimport { FOCUSABLE_EDITOR_SELECTOR, GridClasses } from '../constants';\nimport type { GridHost } from '../types';\nimport { clearCellFocus, isRTL } from './utils';\nimport { readCellField } from './value-accessor';\nimport { fromVirtualScrollTop, getRowIndexAtOffset, toVirtualScrollTop } from './virtualization';\n\n/** Commit active row edit if the editing plugin provides this method. */\nfunction tryCommitEdit(grid: GridHost): void {\n if (typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n}\n\nfunction isFormField(el: HTMLElement | null): boolean {\n if (!el) return false;\n const tag = el.tagName;\n if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return true;\n if (el.isContentEditable) return true;\n return false;\n}\n\n/** True while a row-scoped edit session is active. */\nfunction isRowEditing(grid: GridHost): boolean {\n return grid._activeEditRows !== undefined && grid._activeEditRows !== -1;\n}\n\n// #region Keyboard Handler\n/**\n * Guard chain deciding whether this keydown belongs to something other than\n * cell navigation and must be left alone.\n */\nfunction shouldIgnoreKeyDown(grid: GridHost, e: KeyboardEvent, target: HTMLElement | null): boolean {\n // Keyboard events whose target lives inside the grid host but OUTSIDE the\n // rows body (toolpanel inputs/buttons, shell-header controls, light-DOM\n // children of <tbw-grid>) own their own keyboard interactions. Treating\n // ArrowUp/ArrowDown/Enter on such a target as cell navigation steals the\n // user's actual interaction target on the very next render cycle.\n // Cells and the grid host itself remain valid navigation targets.\n if (target && target !== grid && !target.closest?.('.rows-body')) return true;\n\n if (isFormField(target)) {\n switch (e.key) {\n // Home/End and horizontal arrows navigate within the text input; Enter\n // and Escape are handled by the input's own handlers first.\n case 'Home':\n case 'End':\n case 'ArrowLeft':\n case 'ArrowRight':\n case 'Enter':\n case 'Escape':\n return true;\n case 'ArrowUp':\n case 'ArrowDown':\n // Number spinners own the vertical arrows; other fields fall through.\n if ((target as HTMLInputElement).tagName === 'INPUT' && (target as HTMLInputElement).type === 'number') {\n return true;\n }\n break;\n }\n }\n\n const colType = grid._visibleColumns[grid._focusCol]?.type;\n return isRowEditing(grid) && colType === 'select' && (e.key === 'ArrowDown' || e.key === 'ArrowUp');\n}\n\n/** Tab / Shift+Tab: advance one cell, wrapping to the next/previous row. */\nfunction navigateTab(grid: GridHost, e: KeyboardEvent, maxRow: number, maxCol: number): void {\n e.preventDefault();\n if (!e.shiftKey) {\n if (grid._focusCol < maxCol) {\n grid._focusCol += 1;\n return;\n }\n tryCommitEdit(grid);\n if (grid._focusRow < maxRow) {\n grid._focusRow += 1;\n grid._focusCol = 0;\n }\n return;\n }\n if (grid._focusCol > 0) {\n grid._focusCol -= 1;\n } else if (grid._focusRow > 0) {\n if (grid._activeEditRows === grid._focusRow) tryCommitEdit(grid);\n grid._focusRow -= 1;\n grid._focusCol = maxCol;\n }\n}\n\n/**\n * ArrowLeft/ArrowRight. `towardEnd` is the LOGICAL direction (ArrowRight); in\n * RTL the physical arrow maps to the opposite column index.\n */\nfunction navigateHorizontal(grid: GridHost, e: KeyboardEvent, maxCol: number, towardEnd: boolean): void {\n const forward = isRTL(grid) ? !towardEnd : towardEnd;\n grid._focusCol = forward ? Math.min(maxCol, grid._focusCol + 1) : Math.max(0, grid._focusCol - 1);\n e.preventDefault();\n}\n\n/** Commit any active row edit, then focus the first/last row (column unchanged). */\nfunction jumpToRowEdge(grid: GridHost, maxRow: number, toEnd: boolean): void {\n if (isRowEditing(grid)) tryCommitEdit(grid);\n grid._focusRow = toEnd ? maxRow : 0;\n}\n\n/** ArrowUp/ArrowDown: one row, or the first/last row with Ctrl/Cmd. */\nfunction navigateVertical(grid: GridHost, e: KeyboardEvent, maxRow: number, down: boolean): void {\n if (e.ctrlKey || e.metaKey) jumpToRowEdge(grid, maxRow, down);\n else {\n if (isRowEditing(grid)) tryCommitEdit(grid);\n grid._focusRow = down ? Math.min(maxRow, grid._focusRow + 1) : Math.max(0, grid._focusRow - 1);\n }\n e.preventDefault();\n}\n\n/**\n * PageUp/PageDown target: one viewport of rows (less any overlay band), so an\n * unconstrained grid pages straight to its edge. 20 rows until layout exists.\n */\nfunction pageTargetRow(grid: GridHost, maxRow: number, down: boolean): number {\n const from = grid._focusRow;\n const virt = grid._virtualization;\n const viewportH = virt?.viewportEl?.clientHeight ?? virt?.container?.clientHeight ?? 0;\n const offsets = grid._getVerticalScrollOffsets?.(from);\n const usable = viewportH - Math.max(0, offsets?.top ?? 0) - Math.max(0, offsets?.bottom ?? 0);\n const pc = virt?.variableHeights ? virt.positionCache : null;\n let target: number;\n if (usable > 0 && pc?.[from]) {\n target = getRowIndexAtOffset(pc, pc[from].offset + (down ? usable : -usable));\n // A row taller than the viewport would otherwise pin focus in place.\n if (target === from) target = from + (down ? 1 : -1);\n } else {\n const rowH = virt?.rowHeight ?? 0;\n const step = usable > 0 && rowH > 0 ? Math.max(1, Math.floor(usable / rowH)) : 20;\n target = from + (down ? step : -step);\n }\n return Math.max(0, Math.min(maxRow, target));\n}\n\n/** Home/End (plus Ctrl/Cmd variants which also jump to the first/last row). */\nfunction navigateRowEdge(grid: GridHost, e: KeyboardEvent, maxRow: number, maxCol: number, toEnd: boolean): void {\n if (e.ctrlKey || e.metaKey) jumpToRowEdge(grid, maxRow, toEnd);\n grid._focusCol = toEnd ? maxCol : 0;\n e.preventDefault();\n ensureCellVisible(grid, toEnd ? { forceScrollRight: true } : { forceScrollLeft: true });\n}\n\n/** Dispatch the unified `cell-activate` event. Returns true when cancelled. */\nfunction emitCellActivate(grid: GridHost, e: KeyboardEvent): boolean {\n const rowIndex = grid._focusRow;\n const colIndex = grid._focusCol;\n const column = grid._visibleColumns[colIndex];\n const row = grid._rows[rowIndex];\n const field = column?.field ?? '';\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n detail: {\n rowIndex,\n colIndex,\n column,\n field,\n value: field && row ? readCellField(row, field) : undefined,\n row,\n cellEl: grid.querySelector(`[data-row=\"${rowIndex}\"][data-col=\"${colIndex}\"]`) as HTMLElement | undefined,\n trigger: 'keyboard' as const,\n originalEvent: e,\n },\n });\n grid.dispatchEvent(activateEvent);\n return activateEvent.defaultPrevented;\n}\n\nexport function handleGridKeyDown(grid: GridHost, e: KeyboardEvent): void {\n // Dispatch to plugin system first - if any plugin handles it, stop here\n if (grid._dispatchKeyDown?.(e)) return;\n\n const path = e.composedPath?.() ?? [];\n const target = (path.length ? path[0] : e.target) as HTMLElement | null;\n if (shouldIgnoreKeyDown(grid, e, target)) return;\n\n const maxRow = grid._rows.length - 1;\n const maxCol = grid._visibleColumns.length - 1;\n\n switch (e.key) {\n case 'Tab':\n navigateTab(grid, e, maxRow, maxCol);\n break;\n case 'ArrowDown':\n navigateVertical(grid, e, maxRow, true);\n break;\n case 'ArrowUp':\n navigateVertical(grid, e, maxRow, false);\n break;\n case 'ArrowRight':\n navigateHorizontal(grid, e, maxCol, true);\n break;\n case 'ArrowLeft':\n navigateHorizontal(grid, e, maxCol, false);\n break;\n case 'Home':\n navigateRowEdge(grid, e, maxRow, maxCol, false);\n return;\n case 'End':\n navigateRowEdge(grid, e, maxRow, maxCol, true);\n return;\n case 'PageDown':\n grid._focusRow = pageTargetRow(grid, maxRow, true);\n e.preventDefault();\n break;\n case 'PageUp':\n grid._focusRow = pageTargetRow(grid, maxRow, false);\n e.preventDefault();\n break;\n // NOTE: Enter is normally handled by EditingPlugin. If no plugin handled\n // it, dispatch the unified cell-activate event for custom handling.\n case 'Enter':\n if (emitCellActivate(grid, e)) {\n e.preventDefault();\n return;\n }\n break;\n default:\n return;\n }\n ensureCellVisible(grid);\n}\n// #endregion\n\n// #region Cell Visibility\n/**\n * Options for ensureCellVisible to control scroll behavior.\n */\ninterface EnsureCellVisibleOptions {\n /** Force scroll to the leftmost position (for Home key) */\n forceScrollLeft?: boolean;\n /** Force scroll to the rightmost position (for End key) */\n forceScrollRight?: boolean;\n /** Force horizontal scroll even in edit mode (for Tab navigation) */\n forceHorizontalScroll?: boolean;\n}\n\n/**\n * Vertical: scroll the virtualized container so the focused row is in view.\n *\n * `y` is in raw row-content space (rowHeight × index). Above the browser height\n * cap (#326), `scrollEl.scrollTop` is in clamped spacer space — the two\n * coordinate systems are not interchangeable, so translate scrollTop into\n * virtual space for the comparison and back into native space when writing.\n * For sub-cap datasets the mapping is identity, so this is a no-op.\n *\n * Plugins that paint over the viewport (sticky rows) report the space they\n * obscure via `_getVerticalScrollOffsets`, so the row lands clear of the\n * overlay instead of underneath it — WCAG 2.2 SC 2.4.11 Focus Not Obscured.\n */\nfunction scrollFocusedRowIntoView(grid: GridHost): void {\n if (!grid._virtualization?.enabled) return;\n const { rowHeight, container, viewportEl, scrollMapping } = grid._virtualization;\n // container is the faux scrollbar element that handles actual scrolling;\n // viewportEl is the visible area element that has the correct height.\n const scrollEl = container as HTMLElement | undefined;\n const visibleHeight = viewportEl?.clientHeight ?? scrollEl?.clientHeight ?? 0;\n if (!scrollEl || visibleHeight <= 0) return;\n\n const offsets = grid._getVerticalScrollOffsets?.(grid._focusRow) ?? { top: 0, bottom: 0 };\n if (offsets.skipScroll) return;\n // A malformed or oversized report must never invert the visible band.\n const top = Math.max(0, offsets.top);\n const bottom = Math.max(0, offsets.bottom);\n const usableHeight = visibleHeight - top - bottom;\n if (usableHeight <= 0) return;\n\n const y = grid._focusRow * rowHeight;\n const virtualScrollTop = scrollMapping ? toVirtualScrollTop(scrollEl.scrollTop, scrollMapping) : scrollEl.scrollTop;\n if (y < virtualScrollTop + top) {\n const target = Math.max(0, y - top);\n scrollEl.scrollTop = scrollMapping ? fromVirtualScrollTop(target, scrollMapping) : target;\n } else if (y + rowHeight > virtualScrollTop + top + usableHeight) {\n const target = Math.max(0, y - usableHeight + rowHeight - top);\n scrollEl.scrollTop = scrollMapping ? fromVirtualScrollTop(target, scrollMapping) : target;\n }\n}\n\n/**\n * Resolve the rendered cell for the focused row/col, or `undefined` when the\n * focused row is outside the current virtual window.\n */\nfunction findFocusedCell(grid: GridHost): { rowEl: HTMLElement | null; cell: HTMLElement } | undefined {\n const rowIndex = grid._focusRow;\n const vStart = grid._virtualization.start ?? 0;\n const vEnd = grid._virtualization.end ?? grid._rows.length;\n if (rowIndex < vStart || rowIndex >= vEnd) return undefined;\n\n const rowEl = grid._bodyEl.querySelectorAll('.data-grid-row')[rowIndex - vStart] as HTMLElement | null;\n // Try exact column match first, then query by data-col, then fall back to the\n // first cell (full-width group rows).\n let cell = rowEl?.children[grid._focusCol] as HTMLElement | undefined;\n if (!cell || !cell.classList?.contains('cell')) {\n cell = (rowEl?.querySelector(`.cell[data-col=\"${grid._focusCol}\"]`) ?? rowEl?.querySelector('.cell[data-col]')) as\n HTMLElement | undefined;\n }\n return cell ? { rowEl: rowEl ?? null, cell } : undefined;\n}\n\n/** Horizontal: bring the focused cell into the `.tbw-scroll-area` viewport. */\nfunction scrollCellIntoViewHorizontally(\n grid: GridHost,\n rowEl: HTMLElement | null,\n cell: HTMLElement,\n options?: EnsureCellVisibleOptions,\n): void {\n const scrollArea = grid.querySelector('.tbw-scroll-area') as HTMLElement | null;\n if (!scrollArea) return;\n\n // Home/End always scroll to the edge.\n if (options?.forceScrollLeft) {\n scrollArea.scrollLeft = 0;\n return;\n }\n if (options?.forceScrollRight) {\n scrollArea.scrollLeft = scrollArea.scrollWidth - scrollArea.clientWidth;\n return;\n }\n\n // Plugins (e.g. pinned columns) report how much of the scroll area they\n // obscure, and whether the focused cell should skip scrolling entirely\n // (pinned cells are always visible).\n const offsets = grid._getHorizontalScrollOffsets?.(rowEl ?? undefined, cell) ?? { left: 0, right: 0 };\n if (offsets.skipScroll) return;\n\n const cellRect = cell.getBoundingClientRect();\n const scrollAreaRect = scrollArea.getBoundingClientRect();\n const cellLeft = cellRect.left - scrollAreaRect.left + scrollArea.scrollLeft;\n const cellRight = cellLeft + cellRect.width;\n const visibleLeft = scrollArea.scrollLeft + offsets.left;\n const visibleRight = scrollArea.scrollLeft + scrollArea.clientWidth - offsets.right;\n if (cellLeft < visibleLeft) {\n scrollArea.scrollLeft = cellLeft - offsets.left;\n } else if (cellRight > visibleRight) {\n scrollArea.scrollLeft = cellRight - scrollArea.clientWidth + offsets.right;\n }\n}\n\n/** Move real DOM focus to the editor, the cell, or the grid host. */\nfunction applyFocusTarget(grid: GridHost, cell: HTMLElement, isEditing: boolean): void {\n if (isEditing && cell.classList.contains(GridClasses.EDITING)) {\n // Editing cell: focus the editor input inside it\n const focusTarget = cell.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (focusTarget && document.activeElement !== focusTarget) {\n try {\n focusTarget.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n return;\n }\n\n if (isEditing) {\n // Active edit row but this cell isn't the editing cell — focus it so Tab\n // navigation within the row can attach editors.\n if (cell.contains(document.activeElement)) return;\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n try {\n cell.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n return;\n }\n\n // NOT editing: keep focus on the grid element (tabindex=0) rather than\n // individual cells. In a virtualized grid, cells can be detached by\n // subsequent render cycles (e.g., SelectionPlugin's requestAfterRender\n // → RAF → row recycling). A detached focused cell causes activeElement\n // to revert to <body>, breaking keyboard navigation.\n // Visual focus is managed by the .cell-focus CSS class + data-has-focus.\n //\n // BUT: don't steal focus from a real focused descendant (toolpanel\n // input, shell-header button, registered overlay control). Cell focus\n // is virtual; if the user is actively typing in a non-cell input, we\n // must leave that input alone. We only reclaim focus when nothing\n // meaningful inside the grid currently holds it (active element is\n // outside grid, on the grid host itself, or on a bare cell).\n const active = document.activeElement;\n const meaningful =\n active instanceof HTMLElement &&\n active !== grid &&\n typeof grid.contains === 'function' &&\n grid.contains(active) &&\n !active.classList.contains('cell') &&\n !active.closest('.cell');\n if (!meaningful && active !== grid) {\n grid.focus({ preventScroll: true });\n }\n}\n\n/**\n * Scroll the viewport (virtualized or static) so the focused cell's row is visible\n * and apply visual focus styling / tabindex management.\n */\nexport function ensureCellVisible(grid: GridHost, options?: EnsureCellVisibleOptions): void {\n scrollFocusedRowIntoView(grid);\n\n const isEditing = isRowEditing(grid) || !!grid._isGridEditMode;\n // Skip refreshVirtualWindow when in edit mode to avoid wiping editors\n if (!isEditing) grid.refreshVirtualWindow(false);\n\n clearCellFocus(grid._bodyEl);\n // Clear previous aria-selected markers\n Array.from(grid._bodyEl.querySelectorAll('[aria-selected=\"true\"]')).forEach((el) => {\n el.setAttribute('aria-selected', 'false');\n });\n\n const focused = findFocusedCell(grid);\n if (!focused) return;\n const { rowEl, cell } = focused;\n\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n\n // Skip horizontal scrolling in edit mode to prevent scroll jumps when editors\n // are created — unless forceHorizontalScroll is set (Tab while editing).\n if (!isEditing || options?.forceHorizontalScroll) {\n scrollCellIntoViewHorizontally(grid, rowEl, cell, options);\n }\n\n applyFocusTarget(grid, cell, isEditing);\n}\n// #endregion\n","/**\n * Generic click-only action menu — the shared building block for WCAG 2.2\n * SC 2.5.7 \"Dragging Movements\" alternatives in plugins.\n *\n * Every drag affordance in the grid needs a path that works with plain clicks\n * or taps. A keyboard equivalent is **not** enough: SC 2.5.7 is specifically\n * about pointer input, and the W3C Understanding text calls out \"a pop-up menu\n * after tap\" as a conforming alternative. This module is that pop-up.\n *\n * Lives under `plugins/shared/` on purpose: each plugin bundle is built\n * self-contained, so this code is duplicated into the plugins that use it and\n * never lands in the core `index.js` budget.\n *\n * @since 3.6.0\n * @internal\n */\n\n/** Gap between the anchor element and the menu, in px. */\nconst ANCHOR_GAP_PX = 6;\n\n/** Runtime check — happy-dom and older browsers may lack the Popover API. */\nfunction supportsPopover(): boolean {\n return typeof HTMLElement.prototype?.showPopover === 'function';\n}\n\nlet hoverlessQuery: MediaQueryList | null | undefined;\n\n/** Whether the primary pointer cannot hover (touch, most switch devices). */\nfunction isHoverless(): boolean {\n if (hoverlessQuery === undefined) hoverlessQuery = globalThis.matchMedia?.('(hover: none)') ?? null;\n return hoverlessQuery?.matches ?? false;\n}\n\n/**\n * Whether a plugin should also expose its drag alternative as a dedicated\n * inline control, on top of the menu that is always available.\n *\n * Hover-less pointers get one unconditionally — a control revealed on hover is\n * unreachable when nothing can hover, and long-press is the wrong fallback for\n * the tremor and low-dexterity users SC 2.5.7 exists for.\n *\n * Driven by `a11y.dragAlternatives` on the grid config.\n */\nexport function prefersInlineDragAlternative(grid: {\n effectiveConfig?: { a11y?: { dragAlternatives?: 'menu' | 'inline' } };\n}): boolean {\n return grid.effectiveConfig?.a11y?.dragAlternatives === 'inline' || isHoverless();\n}\n\n/** One clickable entry in the menu. */\nexport interface DragAlternativeAction {\n /** Visible text, also the accessible name. */\n label: string;\n /** Render the entry but refuse the click (e.g. \"Move up\" on the first row). */\n disabled?: boolean;\n /** Invoked on click; the menu closes first so focus handling is predictable. */\n run(): void;\n}\n\nexport interface DragAlternativeMenu {\n /**\n * Show `actions` anchored to `anchor`, with `label` as the group's accessible\n * name. Mounted inside the anchor's grid host so it inherits theme tokens.\n */\n open(anchor: HTMLElement, label: string, actions: readonly DragAlternativeAction[]): void;\n /** Hide the menu. Safe to call when already closed. */\n close(): void;\n /** Remove the element and every document-level listener. */\n dispose(): void;\n /** The menu element, or `null` before the first {@link DragAlternativeMenu.open}. */\n readonly element: HTMLElement | null;\n}\n\n/**\n * Create a menu instance.\n *\n * @param id - DOM id for the menu element, unique per owning plugin.\n * @param className - Root class name, so each plugin can style its own menu.\n */\nexport function createDragAlternativeMenu(id: string, className: string): DragAlternativeMenu {\n let root: HTMLDivElement | null = null;\n let opened = false;\n let disposed = false;\n const abort = new AbortController();\n\n function close(): void {\n if (!opened || !root) return;\n opened = false;\n if (supportsPopover()) root.hidePopover();\n root.hidden = true;\n }\n\n function build(): HTMLDivElement {\n const el = document.createElement('div');\n el.id = id;\n el.className = className;\n el.setAttribute('popover', 'manual');\n el.setAttribute('role', 'group');\n // Without the Popover API there is no UA `display: none`, so hide it here.\n el.hidden = true;\n // Tabbing past either end dismisses instead of stranding the user — the\n // menu has no visible trigger to tab back to. A null `relatedTarget` (click\n // focus in Firefox/Safari, focus leaving the document) is ignored.\n el.addEventListener(\n 'focusout',\n (e) => {\n const next = (e as FocusEvent).relatedTarget;\n if (next instanceof Node && !el.contains(next)) close();\n },\n { signal: abort.signal },\n );\n return el;\n }\n\n /**\n * Position against the anchor in viewport coordinates. The menu lives in the\n * top layer, so its containing block is the viewport regardless of where the\n * anchor sits in the DOM.\n */\n function place(anchor: HTMLElement): void {\n if (!root) return;\n const a = anchor.getBoundingClientRect();\n const box = root.getBoundingClientRect();\n const left = Math.max(4, Math.min(a.left, window.innerWidth - box.width - 4));\n const below = a.bottom + ANCHOR_GAP_PX;\n const top = below + box.height <= window.innerHeight ? below : Math.max(4, a.top - ANCHOR_GAP_PX - box.height);\n root.style.left = `${Math.round(left)}px`;\n root.style.top = `${Math.round(top)}px`;\n }\n\n // Any pointer press outside the menu dismisses it. Capture phase so the press\n // that lands on a grid cell still reaches its own handler afterwards.\n document.addEventListener(\n 'pointerdown',\n (e) => {\n if (opened && root && !e.composedPath().includes(root)) close();\n },\n { capture: true, signal: abort.signal },\n );\n document.addEventListener(\n 'keydown',\n (e) => {\n if ((e as KeyboardEvent).key === 'Escape') close();\n },\n { capture: true, signal: abort.signal },\n );\n\n return {\n get element() {\n return root;\n },\n open(anchor, label, actions) {\n if (disposed || actions.length === 0) return;\n if (!root) root = build();\n // Mount inside the grid host (light DOM) so theme custom properties and\n // `color-scheme` cascade in; the top layer keeps it out of overflow.\n const host = anchor.closest<HTMLElement>('tbw-grid, [data-tbw-grid]') ?? document.body;\n if (root.parentNode !== host) host.appendChild(root);\n root.setAttribute('aria-label', label);\n root.replaceChildren();\n for (const action of actions) {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `${className}-item`;\n btn.textContent = action.label;\n btn.disabled = action.disabled === true;\n btn.addEventListener('click', () => {\n close();\n action.run();\n });\n root.appendChild(btn);\n }\n root.hidden = false;\n if (!opened && supportsPopover()) root.showPopover();\n opened = true;\n place(anchor);\n root.querySelector<HTMLButtonElement>('button:not([disabled])')?.focus();\n },\n close,\n dispose() {\n disposed = true;\n close();\n abort.abort();\n root?.remove();\n root = null;\n },\n };\n}\n","/**\n * Drag-Drop Protocol — shared utilities for cross-grid row drag-and-drop.\n *\n * Used by `RowDragDropPlugin` (and any future plugin that participates in the\n * tbw row-drag protocol). Centralises:\n *\n * - MIME type constants and zone tagging (visible in `dataTransfer.types`)\n * - Payload codec for cross-window / cross-iframe transfers\n * - Drop-position math (midpoint algorithm)\n * - Auto-scroll engine (rAF loop, edge detection, boundary clamp)\n * - Plain-text TSV fallback for drags out to Notepad / Excel / Slack\n *\n * @internal Plugin shared utility (not part of the public API).\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n// ---------------------------------------------------------------------------\n// Same-window current-session tracker\n// ---------------------------------------------------------------------------\n\n/**\n * Module-level \"active drag session\" — set on dragstart by the source grid,\n * cleared on dragend. Lets target grids read the full payload synchronously\n * during `dragover` (browsers hide `dataTransfer.getData()` during dragover\n * for security). Cross-window drags don't have access to this module and\n * fall back to a generic accept-check followed by a drop-time canDrop call.\n *\n * @internal\n */\nlet currentSession: { sessionId: string; payload: RowDragPayload<unknown> } | null = null;\n\n/** @internal */\nexport function setCurrentDragSession<T>(sessionId: string, payload: RowDragPayload<T>): void {\n currentSession = { sessionId, payload: payload as RowDragPayload<unknown> };\n}\n\n/** @internal */\nexport function getCurrentDragSession<T = unknown>(): { sessionId: string; payload: RowDragPayload<T> } | null {\n return currentSession as { sessionId: string; payload: RowDragPayload<T> } | null;\n}\n\n/** @internal */\nexport function clearCurrentDragSession(): void {\n currentSession = null;\n}\n\n// ---------------------------------------------------------------------------\n// MIME types\n// ---------------------------------------------------------------------------\n\n/** Base MIME type carried on `dataTransfer` for tbw row drags. */\nexport const TBW_ROW_DRAG_MIME = 'application/x-tbw-grid-rows+json';\n\n/** Build a zone-tagged MIME type, visible in `dataTransfer.types` during dragover. */\nexport function mimeForZone(zone: string): string {\n return `${TBW_ROW_DRAG_MIME};zone=${encodeURIComponent(zone)}`;\n}\n\n/** Extract the zone from a tagged MIME, or `null` if untagged. */\nexport function parseZoneFromMime(mime: string): string | null {\n const match = mime.match(/^application\\/x-tbw-grid-rows\\+json;zone=(.*)$/);\n if (!match) return null;\n try {\n return decodeURIComponent(match[1]);\n } catch {\n return match[1];\n }\n}\n\n/**\n * Find the first MIME entry in `types` whose zone matches `zone`.\n * Used during `dragover` for the accept-check (browsers hide `getData()`\n * during dragover, but `types` is always visible).\n */\nexport function findMatchingZoneMime(types: readonly string[], zone: string): string | null {\n for (const t of types) {\n if (parseZoneFromMime(t) === zone) return t;\n }\n return null;\n}\n\n/** Returns true if any tbw row-drag MIME is present in `types`. */\nexport function hasAnyRowDragMime(types: readonly string[]): boolean {\n for (const t of types) {\n if (t === TBW_ROW_DRAG_MIME || t.startsWith(`${TBW_ROW_DRAG_MIME};`)) return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Payload\n// ---------------------------------------------------------------------------\n\n/**\n * Cross-grid drag payload, carried on `dataTransfer` and (for same-window\n * recovery) keyed in the WeakRef registry by `sessionId`.\n *\n * Re-exported as public API from `plugins/row-drag-drop`.\n * @since 2.4.0\n */\nexport interface RowDragPayload<T = unknown> {\n /** Drag session id (matches the WeakRef registry key). */\n sessionId: string;\n /** Source grid id (`grid.id` or auto-generated UUID). */\n sourceGridId: string;\n /** Drop zone the source grid is participating in. */\n dropZone: string;\n /** Serialized row payload (JSON-safe). For same-window drops, recovered live via the registry. */\n rows: T[];\n /** Original indices in the source grid's `_rows` array. */\n rowIndices: number[];\n /** Move (default) removes from source; copy leaves source intact. */\n operation: 'move' | 'copy';\n}\n\n/** Encode a payload to the JSON string written to `dataTransfer`. */\nexport function encodePayload<T>(payload: RowDragPayload<T>): string {\n return JSON.stringify(payload);\n}\n\n/** Decode a payload from the JSON string read off `dataTransfer`. */\nexport function decodePayload<T = unknown>(raw: string): RowDragPayload<T> | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as RowDragPayload<T>;\n if (\n typeof parsed?.sessionId !== 'string' ||\n typeof parsed?.sourceGridId !== 'string' ||\n typeof parsed?.dropZone !== 'string' ||\n !Array.isArray(parsed?.rows) ||\n !Array.isArray(parsed?.rowIndices) ||\n (parsed.operation !== 'move' && parsed.operation !== 'copy')\n ) {\n return null;\n }\n // `rowIndices` is later used for sorting and splicing — reject anything\n // that isn't a finite, non-negative integer to avoid JS coercion bugs\n // (NaN ordering, float splice indices, string concatenation, etc.).\n for (const idx of parsed.rowIndices) {\n if (typeof idx !== 'number' || !Number.isInteger(idx) || idx < 0) return null;\n }\n return parsed;\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Drop position math\n// ---------------------------------------------------------------------------\n\nexport interface DropPosition {\n /** Target visual row index that the cursor is over, or `null` for empty area. */\n overIndex: number | null;\n /** Final insertion index in the target grid's `_rows`. */\n insertIndex: number;\n /** True when the cursor is above the row midpoint. */\n isBefore: boolean;\n}\n\n/**\n * Compute the insertion index for a drop, given the row element under the\n * cursor (if any), the cursor's clientY, and the total row count when the\n * cursor is in empty space below the last row.\n *\n * - Cursor above row midpoint → insert at `targetIndex`\n * - Cursor below row midpoint → insert at `targetIndex + 1`\n * - Cursor over empty area below the last row → append (`insertIndex = totalRows`)\n */\nexport function computeDropPosition(\n rowEl: HTMLElement | null,\n clientY: number,\n rowIndexResolver: (el: HTMLElement) => number,\n totalRows: number,\n): DropPosition {\n if (!rowEl) {\n return { overIndex: null, insertIndex: totalRows, isBefore: false };\n }\n const targetIndex = rowIndexResolver(rowEl);\n if (targetIndex < 0) {\n return { overIndex: null, insertIndex: totalRows, isBefore: false };\n }\n const rect = rowEl.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n const isBefore = clientY < midY;\n return {\n overIndex: targetIndex,\n insertIndex: isBefore ? targetIndex : targetIndex + 1,\n isBefore,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Auto-scroll engine\n// ---------------------------------------------------------------------------\n\nexport interface AutoScrollOptions {\n /** Pixels from the top/bottom edge that activate auto-scroll. */\n edgeSize?: number;\n /** Base scroll speed in pixels-per-frame near the edge. */\n speed?: number;\n /** Max speed at the very edge (linear ramp from `speed` → `maxSpeed`). */\n maxSpeed?: number;\n}\n\nexport interface AutoScroller {\n /** Update pointer position; starts the rAF loop if needed. */\n onPointerMove(clientY: number): void;\n /** Stop the rAF loop and reset state. */\n stop(): void;\n /** True when actively auto-scrolling. */\n readonly isScrolling: boolean;\n}\n\n/**\n * Create an auto-scroller for a viewport element.\n *\n * The scroller runs a `requestAnimationFrame` loop only while the pointer is\n * within `edgeSize` of the viewport's top or bottom and there is room to\n * scroll. Speed ramps linearly from `speed` (at the edge boundary) to\n * `maxSpeed` (at the very edge).\n */\nexport function createAutoScroller(\n viewport: HTMLElement,\n options: AutoScrollOptions = {},\n onScrollChange?: (active: boolean) => void,\n): AutoScroller {\n const edgeSize = options.edgeSize ?? 60;\n const baseSpeed = options.speed ?? 8;\n const maxSpeed = options.maxSpeed ?? 24;\n\n let rafId: number | null = null;\n let pointerY: number | null = null;\n let active = false;\n\n const setActive = (next: boolean): void => {\n if (next === active) return;\n active = next;\n onScrollChange?.(next);\n };\n\n const tick = (): void => {\n rafId = null;\n if (pointerY === null) {\n setActive(false);\n return;\n }\n const rect = viewport.getBoundingClientRect();\n let delta = 0;\n\n if (pointerY < rect.top + edgeSize) {\n const distance = Math.max(0, pointerY - rect.top);\n const ratio = 1 - distance / edgeSize;\n delta = -Math.round(baseSpeed + (maxSpeed - baseSpeed) * ratio);\n } else if (pointerY > rect.bottom - edgeSize) {\n const distance = Math.max(0, rect.bottom - pointerY);\n const ratio = 1 - distance / edgeSize;\n delta = Math.round(baseSpeed + (maxSpeed - baseSpeed) * ratio);\n }\n\n if (delta === 0) {\n setActive(false);\n return;\n }\n\n const before = viewport.scrollTop;\n viewport.scrollTop = before + delta;\n\n // Stop if we hit a boundary (no movement happened)\n if (viewport.scrollTop === before) {\n setActive(false);\n return;\n }\n\n setActive(true);\n rafId = requestAnimationFrame(tick);\n };\n\n return {\n onPointerMove(clientY: number): void {\n pointerY = clientY;\n if (rafId === null) {\n rafId = requestAnimationFrame(tick);\n }\n },\n stop(): void {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n pointerY = null;\n setActive(false);\n },\n get isScrolling(): boolean {\n return active;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// TSV fallback\n// ---------------------------------------------------------------------------\n\n/**\n * Format rows as a TSV string for the `text/plain` drag fallback.\n * Used when the drop target is an external app (Excel, Notepad, Slack).\n *\n * - One row per line, fields separated by `\\t`.\n * - Tabs and newlines in cell values are replaced with spaces.\n * - Skips columns flagged as `utility` (drag handle, checkbox, etc.).\n */\nexport function formatRowsAsTSV<T extends Record<string, unknown>>(\n rows: readonly T[],\n columns: readonly ColumnConfig[],\n): string {\n const dataColumns = columns.filter(\n (c) => !(c as { utility?: boolean }).utility && typeof c.field === 'string' && c.field !== '',\n );\n const lines: string[] = [];\n for (const row of rows) {\n const cells = dataColumns.map((col) => {\n const value = row[col.field];\n if (value === null || value === undefined) return '';\n const str = String(value);\n return str.replace(/[\\t\\r\\n]+/g, ' ');\n });\n lines.push(cells.join('\\t'));\n }\n return lines.join('\\n');\n}\n","/**\n * Row Drag-Drop Plugin CSS\n *\n * Styles for drag handle, drop indicators (intra- and cross-grid), grid-level\n * drag/drop state classes, and FLIP animation on landing rows.\n */\n\n@layer tbw-plugins {\n /* ------------------------------------------------------------------ */\n /* Drag handle column */\n /* ------------------------------------------------------------------ */\n [data-field='__tbw_row_drag'] {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .dg-row-drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 1em;\n min-height: 1em;\n cursor: grab;\n user-select: none;\n color: var(--tbw-row-reorder-handle-color, var(--tbw-color-fg-muted));\n transition: color var(--tbw-transition-duration, 120ms) var(--tbw-transition-ease, ease);\n font-size: var(--tbw-font-size, 1em);\n letter-spacing: -2px;\n\n /* SC 2.5.8: grow the target, not the box, so the row keeps its height. */\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset-block-start: 50%;\n inset-inline-start: 50%;\n translate: -50% -50%;\n inline-size: var(--tbw-touch-target-min, 24px);\n block-size: var(--tbw-touch-target-min, 24px);\n }\n\n &:hover {\n color: var(--tbw-row-reorder-handle-hover, var(--tbw-color-fg));\n }\n\n &:active {\n cursor: grabbing;\n }\n }\n\n /* Coarse pointer (touch): handle always in active colour and large enough to tap */\n @media (hover: none) {\n .dg-row-drag-handle {\n color: var(--tbw-row-reorder-handle-hover, var(--tbw-color-fg));\n }\n }\n\n @media (pointer: coarse) {\n .dg-row-drag-handle {\n min-width: var(--tbw-touch-target-min, 24px);\n min-height: var(--tbw-touch-target-min, 24px);\n }\n }\n\n /* ------------------------------------------------------------------ */\n /* Source row state */\n /* ------------------------------------------------------------------ */\n .data-grid-row.dragging {\n opacity: 0.6;\n }\n\n /* When the row itself is the drag handle, hint at the affordance. */\n .data-grid-row[draggable='true'] {\n cursor: grab;\n }\n\n .data-grid-row[draggable='true'].dragging {\n cursor: grabbing;\n }\n\n /* Off-screen full-row clone used as the HTML5 drag image. */\n /* The browser already applies its own translucency to the snapshot, */\n /* so this clone keeps full opacity and skips the box-shadow — both */\n /* would otherwise composite on top of the browser's effect and make */\n /* the drag image look washed-out / fade at the edges. */\n .tbw-row-drag-clone {\n position: fixed;\n top: -10000px;\n left: -10000px;\n pointer-events: none;\n }\n\n /* Multi-row drag count badge */\n .tbw-row-drag-count {\n position: fixed;\n top: -1000px; /* off-screen until the browser snapshots it */\n left: -1000px;\n pointer-events: none;\n background: var(--tbw-row-reorder-indicator, var(--tbw-color-accent));\n color: var(--tbw-color-accent-fg);\n font:\n 600 12px/1.4 system-ui,\n sans-serif;\n border-radius: 999px;\n padding: 2px 8px;\n box-shadow: 0 2px 6px var(--tbw-color-shadow);\n z-index: 9999;\n }\n\n /* ------------------------------------------------------------------ */\n /* Drop position indicator (rows under cursor) */\n /* ------------------------------------------------------------------ */\n .data-grid-row.drop-target {\n position: relative;\n\n &.drop-before::before,\n &.drop-after::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n height: var(--tbw-drop-indicator-width, 2px);\n background-color: var(--tbw-drop-indicator-color, var(--tbw-row-reorder-indicator, var(--tbw-color-accent)));\n z-index: 10;\n pointer-events: none;\n }\n\n &.drop-before::before {\n top: 0;\n }\n\n &.drop-after::after {\n bottom: 0;\n }\n }\n\n /* ------------------------------------------------------------------ */\n /* Grid-level state classes */\n /* ------------------------------------------------------------------ */\n\n /* Source grid while a drag originating here is in progress */\n .tbw-grid--drag-source {\n /* Reserved hook for consumer styling. */\n }\n\n /* Target grid while a valid drag hovers (matching dropZone, canDrop ok) */\n .tbw-grid--drop-target-active {\n outline: 2px dashed var(--tbw-drop-indicator-color, var(--tbw-color-accent));\n outline-offset: -2px;\n }\n\n /* Briefly applied when a drop attempt is rejected */\n .tbw-grid--drop-target-rejected {\n outline: 2px dashed var(--tbw-color-danger);\n outline-offset: -2px;\n }\n\n /* Applied while the auto-scroll engine is actively scrolling this grid */\n .tbw-grid--auto-scrolling {\n cursor: ns-resize;\n }\n\n /* ------------------------------------------------------------------ */\n /* FLIP animation on landing rows */\n /* ------------------------------------------------------------------ */\n .data-grid-row.flip-animating {\n transition: transform var(--tbw-animation-duration, 200ms) ease-out;\n will-change: transform;\n z-index: 1;\n }\n\n /* ------------------------------------------------------------------ */\n /* Click-only move menu (WCAG 2.2 SC 2.5.7) */\n /* ------------------------------------------------------------------ */\n /* Rendered into `document.body` and promoted to the top layer, so it sits\n outside the grid's custom-property scope — every var needs a literal\n fallback here, the same way the tooltip popover does it. */\n .tbw-row-move-menu {\n position: fixed;\n margin: 0;\n display: flex;\n flex-direction: column;\n min-width: 10rem;\n padding: var(--tbw-spacing-xs, 0.25rem);\n background: var(--tbw-color-panel-bg, light-dark(#fff, #1f1f1f));\n color: var(--tbw-color-fg, light-dark(#1a1a1a, #f0f0f0));\n border: 1px solid var(--tbw-color-border, light-dark(#d0d0d0, #3a3a3a));\n border-radius: var(--tbw-border-radius, 0.25rem);\n box-shadow: var(--tbw-shadow-md, 0 4px 12px rgb(0 0 0 / 25%));\n font: inherit;\n z-index: 10001;\n\n &[hidden] {\n display: none;\n }\n }\n\n .tbw-row-move-menu-item {\n min-height: var(--tbw-touch-target-min, 24px);\n padding: var(--tbw-spacing-xs, 0.25rem) var(--tbw-spacing-sm, 0.5rem);\n text-align: start;\n background: none;\n border: none;\n /* Themes set radii for much larger surfaces (Material uses 12px), which\n turns a 24px row into a lozenge. */\n border-radius: min(var(--tbw-border-radius, 0.25rem), 6px);\n color: inherit;\n font: inherit;\n cursor: pointer;\n\n &:hover:not(:disabled) {\n background: var(--tbw-color-row-hover, light-dark(#f0f0f0, #2a2a2a));\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: default;\n }\n }\n}\n","/**\n * Row Drag-Drop Plugin\n *\n * Drag rows within a single grid **and** between grids that share a\n * `dropZone`.\n *\n * Architecture overview is documented in the issue body and in\n * `.github/knowledge/grid-plugins.md`. Key invariants:\n *\n * - Mutations write to `grid._rows`; the user's input `sourceRows` array is\n * never mutated on either side. Persistence is consumer-driven via the\n * `row-move` (intra-grid) and `row-transfer` (cross-grid) events.\n * - Same-window cross-grid drops use the WeakRef registry in\n * `core/internal/drag-drop-registry` to recover live row references.\n * Cross-window drops fall back to `JSON.parse(JSON.stringify(row))` (or the\n * `serializeRow`/`deserializeRow` hooks for non-JSON values).\n * - Duplicate instances of this plugin collapse via the PluginManager\n * alias-collapse pre-pass, which merges their configs.\n *\n * @see {@link RowDragDropConfig} for all configuration options.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport {\n clearDragSession,\n lookupDragSession,\n newDragSessionId,\n registerDragSession,\n} from '../../core/internal/drag-drop-registry';\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin, type GridElement, type PluginManifest } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridHost } from '../../core/types';\nimport {\n type DragAlternativeAction,\n type DragAlternativeMenu,\n createDragAlternativeMenu,\n} from '../shared/drag-alternative-menu';\nimport {\n type AutoScroller,\n type RowDragPayload,\n TBW_ROW_DRAG_MIME,\n clearCurrentDragSession,\n computeDropPosition,\n createAutoScroller,\n decodePayload,\n encodePayload,\n findMatchingZoneMime,\n formatRowsAsTSV,\n getCurrentDragSession,\n hasAnyRowDragMime,\n mimeForZone,\n setCurrentDragSession,\n} from '../shared/drag-drop-protocol';\nimport styles from './row-drag-drop.css?inline';\nimport type {\n PendingMove,\n RowDragDropConfig,\n RowDragEndDetail,\n RowDragStartDetail,\n RowDropDetail,\n RowMoveDetail,\n RowTransferDetail,\n} from './types';\n\n/**\n * Field name for the drag handle column.\n *\n * @since 2.4.0\n */\nexport const ROW_DRAG_HANDLE_FIELD = '__tbw_row_drag';\n\n// ---------------------------------------------------------------------------\n// Cross-window coordination via BroadcastChannel\n// ---------------------------------------------------------------------------\n//\n// Same-window cross-grid drops can locate the source grid and its plugin via\n// `document.getElementById(sourceGridId)` — the target plugin handles both\n// sides of the transfer in `onDrop` (insert into target + remove from source\n// + emit `row-transfer` on both grids).\n//\n// Cross-window drops can't reach the source DOM that way. The target window\n// broadcasts a `tbw-row-drag-drop:transfer` message; each window has a single\n// channel and fans the message out to every attached plugin instance, which\n// matches on `sourceGridId === this.gridId` and runs the source-side path\n// (row removal on `move`, `row-transfer` emit, `dragAccepted` flip).\n//\n// The channel is created lazily on the first plugin that needs it and shared\n// across all instances in the window. It's torn down when the last instance\n// detaches so we don't leak a handle in environments where the plugin is\n// repeatedly attached/detached.\n\n/** @internal */\ninterface RemoteTransferMessage {\n type: 'tbw-row-drag-drop:transfer';\n sessionId: string;\n sourceGridId: string;\n toGridId: string;\n dropZone: string;\n rowIndices: number[];\n toIndex: number;\n operation: 'move' | 'copy';\n /** Pre-serialized rows (the same shape `serializeRow` produced on dragstart). */\n serializedRows: unknown[];\n}\n\ntype RemoteTransferListener = (msg: RemoteTransferMessage) => void;\n\nconst CROSS_WINDOW_CHANNEL_NAME = 'tbw-row-drag-drop';\nlet sharedChannel: BroadcastChannel | null = null;\nconst remoteListeners = new Set<RemoteTransferListener>();\n\nfunction ensureCrossWindowChannel(): BroadcastChannel | null {\n if (typeof BroadcastChannel === 'undefined') return null;\n if (sharedChannel) return sharedChannel;\n try {\n sharedChannel = new BroadcastChannel(CROSS_WINDOW_CHANNEL_NAME);\n sharedChannel.addEventListener('message', (event: MessageEvent) => {\n const data = event.data as RemoteTransferMessage | undefined;\n if (!data || data.type !== 'tbw-row-drag-drop:transfer') return;\n // Snapshot to tolerate listeners that detach during dispatch.\n for (const listener of Array.from(remoteListeners)) {\n try {\n listener(data);\n } catch {\n /* listener errors must not break sibling listeners */\n }\n }\n });\n } catch {\n sharedChannel = null;\n }\n return sharedChannel;\n}\n\nfunction registerRemoteTransferListener(listener: RemoteTransferListener): void {\n remoteListeners.add(listener);\n ensureCrossWindowChannel();\n}\n\n/**\n * Every plugin instance attached in this window.\n *\n * Drag-and-drop discovers its target from the pointer position, so it never\n * needed a registry. The click-only transfer control (WCAG 2.2 SC 2.5.7) does:\n * it has to *list* the grids a row can be sent to before the user picks one.\n *\n * Typed as `unknown` because the class is generic over the row type and every\n * instance in the window may have a different one; `peersInDropZone()` narrows.\n */\nconst attachedPlugins = new Set<unknown>();\n\nfunction unregisterRemoteTransferListener(listener: RemoteTransferListener): void {\n remoteListeners.delete(listener);\n if (remoteListeners.size === 0 && sharedChannel) {\n try {\n sharedChannel.close();\n } catch {\n /* noop */\n }\n sharedChannel = null;\n }\n}\n\nfunction broadcastRemoteTransfer(msg: RemoteTransferMessage): void {\n const channel = ensureCrossWindowChannel();\n if (!channel) return;\n try {\n channel.postMessage(msg);\n } catch {\n /* postMessage may throw on uncloneable payloads — acceptable; user can\n * supply `serializeRow` to provide a structured-cloneable shape. */\n }\n}\n\n/**\n * Row Drag-Drop Plugin for `<tbw-grid>`.\n *\n * @example Intra-grid reordering\n * ```ts\n * import { RowDragDropPlugin } from '@toolbox-web/grid/plugins/row-drag-drop';\n *\n * grid.gridConfig = {\n * plugins: [new RowDragDropPlugin()],\n * };\n * ```\n *\n * @example Cross-grid transfer list\n * ```ts\n * gridA.gridConfig = { plugins: [new RowDragDropPlugin({ dropZone: 'tasks' })] };\n * gridB.gridConfig = { plugins: [new RowDragDropPlugin({ dropZone: 'tasks' })] };\n *\n * gridA.addEventListener('row-transfer', (e) => persist(e.detail));\n * gridB.addEventListener('row-transfer', (e) => persist(e.detail));\n * ```\n *\n * @category Plugin\n */\nexport class RowDragDropPlugin<T = unknown> extends BaseGridPlugin<RowDragDropConfig<T>> {\n /** @internal */\n readonly name = 'rowDragDrop';\n\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n static override readonly manifest: PluginManifest<RowDragDropConfig> = {\n events: [\n { type: 'row-move', description: 'Intra-grid row reorder.', cancelable: true },\n { type: 'row-drag-start', description: 'Cross-grid drag started on this grid.', cancelable: true },\n { type: 'row-drag-end', description: 'Drag finished on this grid (regardless of outcome).' },\n { type: 'row-drop', description: 'Cross-grid drop landing on this grid.', cancelable: true },\n { type: 'row-transfer', description: 'Cross-grid transfer completed (fires on both grids).' },\n ],\n };\n\n /** @internal */\n protected override get defaultConfig(): Partial<RowDragDropConfig<T>> {\n return {\n enableKeyboard: true,\n // `showDragHandle` default depends on `dragFrom` — resolved by\n // `shouldRenderDragHandle()` rather than here so a single `dragFrom`\n // change reshapes both at once.\n dragHandlePosition: 'left',\n dragHandleWidth: 40,\n debounceMs: 150,\n animation: 'flip',\n operation: 'move',\n autoScroll: true,\n dragFrom: 'handle',\n };\n }\n\n /**\n * Resolve whether the grip column should be rendered. The handle is shown\n * unless the user explicitly set `showDragHandle: false`, OR `dragFrom`\n * is `'row'` and `showDragHandle` was not explicitly set to `true`.\n */\n private shouldRenderDragHandle(): boolean {\n const explicit = this.config.showDragHandle;\n if (explicit === false) return false;\n if (explicit === true) return true;\n return this.config.dragFrom !== 'row';\n }\n\n /** Whether the row element itself should accept native HTML5 drag. */\n private get rowIsDraggable(): boolean {\n return this.config.dragFrom === 'row' || this.config.dragFrom === 'both';\n }\n\n /** Resolve animation type from plugin config (respects grid-level reduced-motion). */\n private get animationType(): false | 'flip' {\n if (!this.isAnimationEnabled) return false;\n if (this.config.animation !== undefined) return this.config.animation;\n return 'flip';\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedRowIndex: number | null = null;\n private draggedRows: T[] = [];\n private draggedIndices: number[] = [];\n private dragSessionId: string | null = null;\n private dragAccepted = false;\n private dropRowIndex: number | null = null;\n private pendingMove: PendingMove | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private lastFocusCol = 0;\n private autoScroller: AutoScroller | null = null;\n /** Stable id for this grid instance (used as `sourceGridId` in payloads). */\n private gridId = '';\n\n /** Bound listener so we can register/unregister the same reference. */\n private readonly remoteTransferListener: RemoteTransferListener = (msg) => this.onRemoteTransfer(msg);\n\n /** Click-only move menu (WCAG 2.2 SC 2.5.7), created lazily on first tap. */\n private moveMenu: DragAlternativeMenu | null = null;\n\n /** Typed internal grid accessor. */\n private get internalGrid(): GridHost {\n return this.grid;\n }\n // #endregion\n\n // #region Lifecycle\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n const host = this.gridElement;\n if (host) {\n this.gridId = host.id || `tbw-grid-${newDragSessionId().slice(0, 8)}`;\n if (!host.id) host.id = this.gridId;\n this.setupDelegatedDragListeners();\n registerRemoteTransferListener(this.remoteTransferListener);\n attachedPlugins.add(this);\n }\n }\n\n /** @internal */\n override detach(): void {\n this.clearDebounceTimer();\n this.autoScroller?.stop();\n this.autoScroller = null;\n unregisterRemoteTransferListener(this.remoteTransferListener);\n attachedPlugins.delete(this);\n this.moveMenu?.dispose();\n this.moveMenu = null;\n if (this.dragSessionId) clearDragSession(this.dragSessionId);\n clearCurrentDragSession();\n this.resetDragState();\n super.detach();\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.shouldRenderDragHandle()) return [...columns];\n\n const dragHandleColumn: ColumnConfig = {\n field: ROW_DRAG_HANDLE_FIELD,\n header: '',\n width: this.config.dragHandleWidth ?? 40,\n resizable: false,\n sortable: false,\n filterable: false,\n lockPosition: true,\n utility: true,\n viewRenderer: () => {\n const container = document.createElement('div');\n container.className = 'dg-row-drag-handle';\n container.setAttribute('aria-label', 'Drag to reorder, or activate for move options');\n container.setAttribute('role', 'button');\n container.setAttribute('tabindex', '-1');\n container.draggable = true;\n // Press-and-release without moving is a tap, not a drag — HTML5 DnD\n // only fires `dragstart` on a real drag, so a plain `click` here means\n // the pointer user could not (or chose not to) drag. Offer the\n // click-only alternative instead (WCAG 2.2 SC 2.5.7).\n container.addEventListener('click', (e) => {\n e.stopPropagation();\n this.openMoveMenu(container);\n });\n this.setIcon(container, 'dragHandle');\n return container;\n },\n };\n\n return this.config.dragHandlePosition === 'right' ? [...columns, dragHandleColumn] : [dragHandleColumn, ...columns];\n }\n\n /** @internal */\n override afterRender(): void {\n this.applyRowDraggable();\n }\n\n /** @internal */\n override onScrollRender(): void {\n // Virtualization recycles row DOM elements during scroll; re-apply the\n // `draggable` attribute so newly-shown rows still accept HTML5 drag.\n this.applyRowDraggable();\n }\n\n /**\n * Set or clear the `draggable` attribute on every visible row, depending on\n * `config.dragFrom`. Idempotent and cheap (one attribute write per row).\n */\n private applyRowDraggable(): void {\n const body = this.internalGrid._bodyEl;\n if (!body) return;\n const wantDraggable = this.rowIsDraggable;\n const rows = body.querySelectorAll<HTMLElement>('.data-grid-row');\n for (const row of rows) {\n if (wantDraggable) {\n if (row.getAttribute('draggable') !== 'true') row.setAttribute('draggable', 'true');\n } else if (row.hasAttribute('draggable')) {\n row.removeAttribute('draggable');\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!this.config.enableKeyboard) return;\n // Ctrl is deprecated (macOS reserves Ctrl+↑/↓); remove in the next major.\n if (!(event.altKey || event.ctrlKey) || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')) return;\n if (this.#isKeyboardMoveBlocked(event)) return;\n\n const grid = this.internalGrid;\n const focusRow = grid._focusRow;\n const rows = grid._rows ?? this.sourceRows;\n if (focusRow < 0 || focusRow >= rows.length) return;\n\n const direction = event.key === 'ArrowUp' ? 'up' : 'down';\n const toIndex = direction === 'up' ? focusRow - 1 : focusRow + 1;\n if (toIndex < 0 || toIndex >= rows.length) return;\n\n const row = rows[focusRow];\n if (!this.canMoveRow(focusRow, toIndex)) return;\n\n this.handleKeyboardMove(row, focusRow, toIndex, grid._focusCol);\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n\n /** Editors, form controls and non-body chrome own the chord (e.g. `<select>` Alt+↓ opens it). */\n #isKeyboardMoveBlocked(event: KeyboardEvent): boolean {\n if (this.queryBoolean('isEditing')) return true;\n const target = (event.composedPath?.()[0] ?? event.target) as HTMLElement | null;\n if (!target?.closest || target === this.gridElement) return false;\n if (!target.closest('.rows-body')) return true;\n const tag = target.tagName;\n return tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA' || target.isContentEditable;\n }\n\n /** @internal */\n override onCellClick(): void {\n this.flushPendingMove();\n }\n // #endregion\n\n // #region Public API\n /** Move a row to a new position programmatically (intra-grid). */\n moveRow(fromIndex: number, toIndex: number): void {\n const rows = [...this.sourceRows];\n if (fromIndex < 0 || fromIndex >= rows.length) return;\n if (toIndex < 0 || toIndex >= rows.length) return;\n if (fromIndex === toIndex) return;\n if (!this.canMoveRow(fromIndex, toIndex)) return;\n this.executeIntraGridMove(rows[fromIndex], fromIndex, toIndex, 'keyboard');\n }\n\n /**\n * Check if a row can be moved within this grid.\n * Consults the `canDrag` veto for the source row, the plugin query system\n * (`canMoveRow`), and `canDrop` for the target.\n */\n canMoveRow(fromIndex: number, toIndex: number): boolean {\n // During debounced keyboard moves, `grid._rows` diverges from\n // `sourceRows` (the user-facing snapshot) because the plugin mutates\n // resolves the focused row from `_rows ?? sourceRows`, so validation must\n // read from the same array — otherwise we'd run `canDrag`/queries against\n // the wrong row.\n const rows = this.internalGrid._rows ?? this.sourceRows;\n if (fromIndex < 0 || fromIndex >= rows.length) return false;\n if (toIndex < 0 || toIndex >= rows.length) return false;\n if (fromIndex === toIndex) return false;\n\n // Plugin query veto (Tree, GroupingRows)\n const row = rows[fromIndex] as T;\n const queryResults = this.grid?.query?.<boolean>('canMoveRow', row);\n if (Array.isArray(queryResults) && queryResults.includes(false)) return false;\n\n // canDrag veto (dragstart side)\n if (this.config.canDrag && !this.config.canDrag(row, fromIndex)) return false;\n\n // canDrop callback (intra-grid synthesised payload)\n if (this.config.canDrop) {\n const payload: RowDragPayload<T> = {\n sessionId: 'intra',\n sourceGridId: this.gridId,\n dropZone: this.config.dropZone ?? '',\n rows: [row],\n rowIndices: [fromIndex],\n operation: 'move',\n };\n if (!this.config.canDrop(payload, toIndex)) return false;\n }\n\n return true;\n }\n // #endregion\n\n // #region Drag Setup\n\n private setupDelegatedDragListeners(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n const signal = this.disconnectSignal;\n\n gridEl.addEventListener('dragstart', (e) => this.onDragStart(e as DragEvent), { signal });\n gridEl.addEventListener('dragend', () => this.onDragEnd(), { signal });\n gridEl.addEventListener('dragover', (e) => this.onDragOver(e as DragEvent), { signal });\n gridEl.addEventListener('dragleave', (e) => this.onDragLeave(e as DragEvent), { signal });\n gridEl.addEventListener('drop', (e) => this.onDrop(e as DragEvent), { signal });\n }\n\n private onDragStart(de: DragEvent): void {\n const target = de.target as HTMLElement | null;\n if (!target) return;\n\n // Resolve the row element being picked up. Order matters: a click on the\n // grip column inside a row-draggable grid should still go through the\n // handle path so the cursor offset feels right.\n const handle = target.closest('.dg-row-drag-handle') as HTMLElement | null;\n let rowEl: HTMLElement | null = null;\n let initiatedFromHandle = false;\n if (handle) {\n rowEl = handle.closest('.data-grid-row') as HTMLElement | null;\n initiatedFromHandle = true;\n } else if (this.rowIsDraggable) {\n // Row-as-handle: any cell may start the drag, but interactive\n // descendants (inputs, buttons, anchors, contenteditable, open\n // editors, selection checkboxes) keep their native behaviour.\n if (this.isInteractiveDragOrigin(target)) return;\n rowEl = target.closest('.data-grid-row') as HTMLElement | null;\n }\n if (!rowEl) return;\n\n const rowIndex = this.getRowIndex(rowEl);\n if (rowIndex < 0) return;\n\n // Resolve the rows being dragged: whole selection if dragged row is selected.\n const { rows, indices } = this.resolveDraggedRows(rowIndex);\n if (rows.length === 0) return;\n\n // canDrag veto on the originating row\n if (this.config.canDrag && !this.config.canDrag(rows[0], rowIndex)) {\n de.preventDefault();\n return;\n }\n\n const operation = this.config.operation ?? 'move';\n const dropZone = this.config.dropZone ?? '';\n const sessionId = newDragSessionId();\n\n // Emit cancelable row-drag-start (source-side veto)\n const startDetail: RowDragStartDetail<T> = { rows, indices, operation, dropZone };\n if (this.emitCancelable('row-drag-start', startDetail)) {\n de.preventDefault();\n return;\n }\n\n this.isDragging = true;\n this.draggedRowIndex = rowIndex;\n this.draggedRows = rows;\n this.draggedIndices = indices;\n this.dragSessionId = sessionId;\n this.dragAccepted = false;\n\n // Build the cross-grid payload (always built, even when dropZone is empty —\n // intra-grid drops still benefit from the registry round-trip)\n const serialize = this.config.serializeRow ?? ((r: T) => r);\n const payload: RowDragPayload<T> = {\n sessionId,\n sourceGridId: this.gridId,\n dropZone,\n rows: rows.map(serialize) as T[],\n rowIndices: indices,\n operation,\n };\n\n if (de.dataTransfer) {\n de.dataTransfer.effectAllowed = operation === 'copy' ? 'copyMove' : 'move';\n try {\n de.dataTransfer.setData(TBW_ROW_DRAG_MIME, encodePayload(payload));\n if (dropZone) de.dataTransfer.setData(mimeForZone(dropZone), encodePayload(payload));\n // Plain-text TSV fallback for external drop targets\n de.dataTransfer.setData('text/plain', formatRowsAsTSV(rows as Record<string, unknown>[], this.columns));\n } catch {\n /* JSDOM/happy-dom may throw on setData; harmless */\n }\n\n // Drag image: full-row clone for single-row drags (more legible than\n // the handle alone); count badge for multi-row drags.\n if (rows.length > 1) {\n const badge = document.createElement('div');\n badge.className = 'tbw-row-drag-count';\n badge.textContent = `${rows.length} rows`;\n document.body.appendChild(badge);\n try {\n de.dataTransfer.setDragImage(badge, 10, 10);\n } catch {\n /* ignore */\n }\n setTimeout(() => badge.remove(), 0);\n } else {\n this.attachRowCloneDragImage(de, rowEl, initiatedFromHandle ? handle : null);\n }\n }\n\n // Same-window registry + current-session marker\n registerDragSession(sessionId, rows as unknown[]);\n setCurrentDragSession(sessionId, payload);\n\n rowEl.classList.add(GridClasses.DRAGGING);\n this.gridElement.classList.add('tbw-grid--drag-source');\n }\n\n /**\n * Selectors whose dragstart should NOT initiate a row drag in `dragFrom: 'row'`\n * mode. Keeps native interactions (text input, button clicks, link drag,\n * cell editing, selection checkboxes) working unchanged.\n */\n private static readonly INTERACTIVE_DRAG_SELECTORS =\n 'input,textarea,select,button,a,[contenteditable=\"\"],[contenteditable=\"true\"],.dg-cell-editor,.tbw-checkbox-cell';\n\n /** @internal */\n private isInteractiveDragOrigin(target: HTMLElement): boolean {\n return target.closest(RowDragDropPlugin.INTERACTIVE_DRAG_SELECTORS) !== null;\n }\n\n /**\n * Build a full-row drag image by cloning `rowEl` so the user sees the\n * actual row — not just the grip icon — while dragging.\n *\n * The clone is appended off-screen, snapshotted by the browser via\n * `setDragImage`, then removed on the next tick (after the snapshot).\n * The cursor offset is preserved relative to where the user pressed.\n */\n private attachRowCloneDragImage(de: DragEvent, rowEl: HTMLElement, handle: HTMLElement | null): void {\n if (!de.dataTransfer) return;\n const rect = rowEl.getBoundingClientRect();\n const clone = rowEl.cloneNode(true) as HTMLElement;\n clone.classList.add('tbw-row-drag-clone');\n // Strip transient classes that would look wrong in the drag image.\n clone.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after', 'flip-animating', 'row-focus');\n clone.removeAttribute('aria-selected');\n // Preserve the actual rendered width so cells don't reflow in the snapshot.\n clone.style.width = `${rect.width}px`;\n clone.style.height = `${rect.height}px`;\n // The clone MUST stay inside the grid host: every core row/cell rule is\n // scoped under `tbw-grid …` (see core/styles/*.css), and the\n // `--tbw-column-template` custom property is set on the host. If the\n // clone is moved to `document.body`, none of those rules match and the\n // drag image collapses to an empty box. Off-screen positioning via\n // `position: fixed` works the same regardless of the DOM parent.\n this.gridElement.appendChild(clone);\n // Cursor offset: where the user pressed inside the source row. Falls\n // back to the centre of the handle when initiated from the grip.\n let offsetX = de.clientX - rect.left;\n let offsetY = de.clientY - rect.top;\n if (handle) {\n const handleRect = handle.getBoundingClientRect();\n offsetX = handleRect.left - rect.left + handleRect.width / 2;\n offsetY = handleRect.top - rect.top + handleRect.height / 2;\n }\n // Clamp into the row bounds so the cursor stays inside the drag image.\n offsetX = Math.max(0, Math.min(rect.width, offsetX));\n offsetY = Math.max(0, Math.min(rect.height, offsetY));\n try {\n de.dataTransfer.setDragImage(clone, offsetX, offsetY);\n } catch {\n /* JSDOM/happy-dom: harmless */\n }\n setTimeout(() => clone.remove(), 0);\n }\n\n private onDragOver(de: DragEvent): void {\n const dt = de.dataTransfer;\n if (!dt) return;\n\n // Identify whether a tbw row drag is in progress and whether it matches our zone.\n const types = dt.types ? Array.from(dt.types) : [];\n if (!hasAnyRowDragMime(types) && !this.isDragging) return;\n\n const dropZone = this.config.dropZone ?? '';\n const session = getCurrentDragSession<T>();\n\n // For cross-grid drags we require a matching zone-tagged MIME OR the\n // session payload's dropZone must match ours.\n const isIntra = this.isDragging && session?.payload.sourceGridId === this.gridId;\n if (!isIntra) {\n if (!dropZone) return; // intra-grid only — ignore external drags\n const matchingMime = findMatchingZoneMime(types, dropZone);\n if (!matchingMime && !(session && session.payload.dropZone === dropZone)) return;\n }\n\n de.preventDefault();\n if (dt) dt.dropEffect = (session?.payload.operation ?? this.config.operation ?? 'move') as 'copy' | 'move';\n\n // Compute drop position\n const rowEl = (de.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n const rows = this.internalGrid._rows ?? [];\n const pos = computeDropPosition(rowEl, de.clientY, (el) => this.getRowIndex(el), rows.length);\n\n // Same-row no-op for intra-grid\n if (isIntra && pos.overIndex !== null && pos.overIndex === this.draggedRowIndex) {\n this.clearDropTargetClasses();\n return;\n }\n\n // canDrop check (same-window only — payload visible)\n if (session && this.config.canDrop) {\n const accepted = this.config.canDrop(session.payload, pos.insertIndex);\n this.gridElement.classList.toggle('tbw-grid--drop-target-active', accepted);\n this.gridElement.classList.toggle('tbw-grid--drop-target-rejected', !accepted);\n if (!accepted) {\n this.clearDropTargetClasses();\n return;\n }\n } else {\n this.gridElement.classList.add('tbw-grid--drop-target-active');\n }\n\n this.dropRowIndex = pos.insertIndex;\n this.applyDropPositionClasses(rowEl, pos.isBefore);\n\n // Auto-scroll the target viewport\n if (this.config.autoScroll !== false) {\n this.ensureAutoScroller();\n this.autoScroller?.onPointerMove(de.clientY);\n }\n }\n\n private onDragLeave(de: DragEvent): void {\n const rowEl = (de.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n if (rowEl) rowEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n // Tear down grid-level state when the cursor leaves the grid entirely\n if (de.currentTarget && !this.gridElement.contains(de.relatedTarget as Node)) {\n this.gridElement.classList.remove('tbw-grid--drop-target-active', 'tbw-grid--drop-target-rejected');\n this.autoScroller?.stop();\n }\n }\n\n private onDrop(de: DragEvent): void {\n de.preventDefault();\n this.autoScroller?.stop();\n this.gridElement.classList.remove('tbw-grid--drop-target-active', 'tbw-grid--drop-target-rejected');\n this.clearDropTargetClasses();\n\n const dt = de.dataTransfer;\n if (!dt) return;\n\n // Resolve payload — prefer same-window session, fall back to dataTransfer JSON\n const session = getCurrentDragSession<T>();\n let payload: RowDragPayload<T> | null = session?.payload ?? null;\n let liveRows: T[] | null = null;\n\n if (payload) {\n const lookup = lookupDragSession<T>(payload.sessionId);\n if (lookup) liveRows = lookup;\n } else {\n const raw = dt.getData(TBW_ROW_DRAG_MIME);\n payload = decodePayload<T>(raw);\n if (payload) {\n const lookup = lookupDragSession<T>(payload.sessionId);\n if (lookup) liveRows = lookup;\n }\n }\n if (!payload) return;\n\n // Drop position (recompute in case dragover wasn't called for a few frames)\n const rowEl = (de.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n const rows = this.internalGrid._rows ?? [];\n const pos = computeDropPosition(rowEl, de.clientY, (el) => this.getRowIndex(el), rows.length);\n let targetIndex = this.dropRowIndex ?? pos.insertIndex;\n\n const isIntra = payload.sourceGridId === this.gridId;\n const dropZone = this.config.dropZone ?? '';\n\n if (isIntra) {\n // Intra-grid path — emit `row-move`.\n const fromIndex = payload.rowIndices[0];\n // Adjust toIndex when dropping after the dragged row (single-row only)\n if (payload.rowIndices.length === 1 && targetIndex > fromIndex) targetIndex--;\n if (fromIndex === targetIndex) return;\n const row = (liveRows ?? payload.rows)[0];\n if (!this.canMoveRow(fromIndex, targetIndex)) return;\n this.executeIntraGridMove(row, fromIndex, targetIndex, 'drag');\n return;\n }\n\n // Cross-grid path\n if (!dropZone || dropZone !== payload.dropZone) return;\n\n // canDrop final check\n if (this.config.canDrop && !this.config.canDrop(payload, targetIndex)) {\n this.gridElement.classList.add('tbw-grid--drop-target-rejected');\n setTimeout(() => this.gridElement.classList.remove('tbw-grid--drop-target-rejected'), 200);\n return;\n }\n\n // Resolve final row references — live (same-window) or deserialized JSON\n const deserialize = this.config.deserializeRow ?? ((r: unknown) => r as T);\n const incomingRows: T[] = liveRows ?? payload.rows.map((r) => deserialize(r as unknown));\n\n const dropDetail: RowDropDetail<T> = {\n payload,\n sourceGridId: payload.sourceGridId,\n targetIndex,\n operation: payload.operation,\n };\n if (this.emitCancelable('row-drop', dropDetail)) return;\n\n // Insert into target grid's _rows\n const targetRows = [...rows];\n targetRows.splice(targetIndex, 0, ...(incomingRows as unknown[]));\n this.grid.rows = targetRows;\n\n // Locate the source plugin in THIS window. If it's here we handle the\n // source-side mutation directly; if it isn't, we broadcast a message and\n // let the source-window plugin (if any) handle removal + transfer emit.\n const sourcePlugin = this.findPeerOnGrid(payload.sourceGridId);\n\n // Remove from source grid's _rows when operation === 'move' (same-window)\n if (payload.operation === 'move' && sourcePlugin) {\n const sourceGrid = document.getElementById(payload.sourceGridId) as\n (HTMLElement & { rows?: unknown[]; _rows?: unknown[] }) | null;\n if (sourceGrid) {\n const srcRows = (sourceGrid._rows ?? sourceGrid.rows ?? []).slice();\n // Remove from highest index down so earlier indices stay stable\n const sortedIndices = [...payload.rowIndices].sort((a, b) => b - a);\n for (const idx of sortedIndices) {\n if (idx >= 0 && idx < srcRows.length) srcRows.splice(idx, 1);\n }\n sourceGrid.rows = srcRows;\n }\n }\n\n // Mark accepted on the source plugin so dragend knows (same-window only —\n // cross-window source flips its own flag in `onRemoteTransfer`).\n if (sourcePlugin) sourcePlugin.dragAccepted = true;\n\n // Emit row-transfer on the target. The source's `row-transfer` is fired\n // either here (same-window) or by the source plugin in `onRemoteTransfer`\n // (cross-window).\n const transferDetail: RowTransferDetail<T> = {\n rows: incomingRows,\n fromGridId: payload.sourceGridId,\n toGridId: this.gridId,\n fromIndices: payload.rowIndices,\n toIndex: targetIndex,\n operation: payload.operation,\n };\n this.emit('row-transfer', transferDetail);\n\n if (sourcePlugin) {\n sourcePlugin.emitTransfer(transferDetail);\n } else {\n // Cross-window: broadcast so the source window can finish its half of\n // the transfer (row removal on `move`, `row-transfer` emit, accepted flip).\n broadcastRemoteTransfer({\n type: 'tbw-row-drag-drop:transfer',\n sessionId: payload.sessionId,\n sourceGridId: payload.sourceGridId,\n toGridId: this.gridId,\n dropZone,\n rowIndices: payload.rowIndices,\n toIndex: targetIndex,\n operation: payload.operation,\n // Re-use the payload's already-serialized rows so the source side can\n // round-trip them through `deserializeRow` for its `row-transfer` event.\n serializedRows: payload.rows as unknown[],\n });\n }\n }\n\n /**\n * Source-window handler for a remote `row-transfer` broadcast from a target\n * window. Only runs on the plugin instance whose grid id matches the message.\n * @internal\n */\n private onRemoteTransfer(msg: RemoteTransferMessage): void {\n if (msg.sourceGridId !== this.gridId) return;\n // Defensive: only act when the message zone matches our zone. This prevents\n // a stray message from a grid sharing the same id but a different zone\n // (e.g. independent demos in different tabs) from mutating our rows.\n const dropZone = this.config.dropZone ?? '';\n if (!dropZone || msg.dropZone !== dropZone) return;\n\n // Resolve incoming rows (for the `row-transfer` event payload).\n const deserialize = this.config.deserializeRow ?? ((r: unknown) => r as T);\n const incomingRows: T[] = msg.serializedRows.map((r) => deserialize(r));\n\n if (msg.operation === 'move') {\n const srcRows = (this.internalGrid._rows ?? this.sourceRows).slice();\n const sortedIndices = [...msg.rowIndices].sort((a, b) => b - a);\n for (const idx of sortedIndices) {\n if (idx >= 0 && idx < srcRows.length) srcRows.splice(idx, 1);\n }\n this.grid.rows = srcRows as unknown[];\n }\n\n // Flip accepted before dragend (best-effort — the message may arrive after\n // dragend has already fired with `accepted: false`; `row-transfer` is the\n // authoritative success signal).\n this.dragAccepted = true;\n\n this.emit('row-transfer', {\n rows: incomingRows,\n fromGridId: msg.sourceGridId,\n toGridId: msg.toGridId,\n fromIndices: msg.rowIndices,\n toIndex: msg.toIndex,\n operation: msg.operation,\n } as RowTransferDetail<T>);\n }\n\n private onDragEnd(): void {\n if (this.dragSessionId) clearDragSession(this.dragSessionId);\n clearCurrentDragSession();\n this.autoScroller?.stop();\n this.gridElement.classList.remove('tbw-grid--drag-source');\n\n if (this.isDragging) {\n const endDetail: RowDragEndDetail<T> = {\n rows: this.draggedRows,\n indices: this.draggedIndices,\n accepted: this.dragAccepted,\n };\n this.emit('row-drag-end', endDetail);\n }\n this.clearDragClasses();\n this.resetDragState();\n }\n // #endregion\n\n // #region Helpers\n\n /** Public wrapper so a peer plugin can dispatch `row-transfer` on this grid. @internal */\n emitTransfer(detail: RowTransferDetail<T>): void {\n this.emit('row-transfer', detail);\n }\n\n /** Find the peer `RowDragDropPlugin` instance on another grid by id. */\n private findPeerOnGrid(gridId: string): RowDragDropPlugin<T> | null {\n const peerEl = document.getElementById(gridId) as\n (HTMLElement & { getPluginByName?: (name: string) => RowDragDropPlugin<T> | undefined }) | null;\n if (!peerEl?.getPluginByName) return null;\n return peerEl.getPluginByName('rowDragDrop') ?? null;\n }\n\n // #region Click-only move menu (WCAG 2.2 SC 2.5.7)\n\n /** Grids in this window sharing our (non-empty) `dropZone`. */\n private peersInDropZone(): RowDragDropPlugin<T>[] {\n const dropZone = this.config.dropZone ?? '';\n if (!dropZone) return [];\n const peers: RowDragDropPlugin<T>[] = [];\n for (const candidate of attachedPlugins) {\n const peer = candidate as RowDragDropPlugin<T>;\n if (peer === this) continue;\n if ((peer.config.dropZone ?? '') === dropZone) peers.push(peer);\n }\n return peers;\n }\n\n /** Human-readable name for this grid, used in the transfer menu. */\n private get gridLabel(): string {\n const host = this.gridElement;\n return host?.getAttribute('aria-label') || host?.id || 'grid';\n }\n\n /**\n * Open the click-only alternative to dragging a row: reorder within this grid\n * and, when a `dropZone` is configured, send the row to a peer grid — all\n * with single clicks or taps, no press-hold-move gesture.\n */\n private openMoveMenu(handle: HTMLElement): void {\n const rowEl = handle.closest('.data-grid-row') as HTMLElement | null;\n if (!rowEl) return;\n const rowIndex = this.getRowIndex(rowEl);\n if (rowIndex < 0) return;\n\n const rows = this.internalGrid._rows ?? this.sourceRows;\n const actions: DragAlternativeAction[] = [\n {\n label: 'Move up',\n disabled: rowIndex === 0 || !this.canMoveRow(rowIndex, rowIndex - 1),\n run: () => this.moveRow(rowIndex, rowIndex - 1),\n },\n {\n label: 'Move down',\n disabled: rowIndex >= rows.length - 1 || !this.canMoveRow(rowIndex, rowIndex + 1),\n run: () => this.moveRow(rowIndex, rowIndex + 1),\n },\n {\n label: 'Move to top',\n disabled: rowIndex === 0 || !this.canMoveRow(rowIndex, 0),\n run: () => this.moveRow(rowIndex, 0),\n },\n {\n label: 'Move to bottom',\n disabled: rowIndex >= rows.length - 1 || !this.canMoveRow(rowIndex, rows.length - 1),\n run: () => this.moveRow(rowIndex, rows.length - 1),\n },\n ];\n\n const verb = (this.config.operation ?? 'move') === 'copy' ? 'Copy to' : 'Send to';\n for (const peer of this.peersInDropZone()) {\n actions.push({\n label: `${verb} ${peer.gridLabel}`,\n run: () => this.transferToPeer(peer, rowIndex),\n });\n }\n\n this.moveMenu ??= createDragAlternativeMenu('tbw-row-move-menu', 'tbw-row-move-menu');\n this.moveMenu.open(handle, `Move row ${rowIndex + 1}`, actions);\n }\n\n /**\n * Click-only equivalent of dropping the row onto `peer`. Runs the same vetoes\n * and emits the same events as the drag path (`row-drag-start`, `row-drop`,\n * `row-transfer`) so consumers cannot tell the two apart.\n */\n private transferToPeer(peer: RowDragDropPlugin<T>, rowIndex: number): void {\n const dropZone = this.config.dropZone ?? '';\n if (!dropZone) return;\n\n const { rows, indices } = this.resolveDraggedRows(rowIndex);\n if (rows.length === 0) return;\n if (this.config.canDrag && !this.config.canDrag(rows[0], rowIndex)) return;\n\n const operation = this.config.operation ?? 'move';\n if (this.emitCancelable('row-drag-start', { rows, indices, operation, dropZone } as RowDragStartDetail<T>)) return;\n\n const serialize = this.config.serializeRow ?? ((r: T) => r);\n const payload: RowDragPayload<T> = {\n sessionId: newDragSessionId(),\n sourceGridId: this.gridId,\n dropZone,\n rows: rows.map(serialize) as T[],\n rowIndices: indices,\n operation,\n };\n\n const toIndex = peer.acceptTransfer(payload, rows);\n if (toIndex < 0) return;\n\n if (operation === 'move') {\n const srcRows = [...(this.internalGrid._rows ?? this.sourceRows)];\n for (const idx of [...indices].sort((a, b) => b - a)) {\n if (idx >= 0 && idx < srcRows.length) srcRows.splice(idx, 1);\n }\n this.grid.rows = srcRows as unknown[];\n }\n\n const detail: RowTransferDetail<T> = {\n rows,\n fromGridId: this.gridId,\n toGridId: peer.gridId,\n fromIndices: indices,\n toIndex,\n operation,\n };\n peer.emitTransfer(detail);\n this.emit('row-transfer', detail);\n }\n\n /**\n * Target half of {@link RowDragDropPlugin.transferToPeer}. Returns the insert\n * index, or `-1` when the transfer was rejected (zone mismatch, `canDrop`\n * veto, or a cancelled `row-drop`).\n *\n * @internal\n */\n acceptTransfer(payload: RowDragPayload<T>, liveRows: readonly T[]): number {\n const dropZone = this.config.dropZone ?? '';\n if (!dropZone || dropZone !== payload.dropZone) return -1;\n\n const rows = this.internalGrid._rows ?? this.sourceRows;\n const targetIndex = rows.length;\n if (this.config.canDrop && !this.config.canDrop(payload, targetIndex)) return -1;\n\n const dropDetail: RowDropDetail<T> = {\n payload,\n sourceGridId: payload.sourceGridId,\n targetIndex,\n operation: payload.operation,\n };\n if (this.emitCancelable('row-drop', dropDetail)) return -1;\n\n this.grid.rows = [...(rows as unknown[]), ...(liveRows as unknown[])];\n return targetIndex;\n }\n // #endregion\n\n private resolveDraggedRows(originIndex: number): { rows: T[]; indices: number[] } {\n const rows = this.internalGrid._rows ?? this.sourceRows;\n const originRow = rows[originIndex] as T;\n\n // If a selection plugin is loaded and the dragged row is selected, drag the whole selection.\n const selection = this.grid?.getPluginByName?.('selection') as\n { getSelectedRowIndices?: () => number[]; getSelectedRows?: <U>() => U[] } | undefined;\n if (selection?.getSelectedRowIndices) {\n const selectedIndices = selection.getSelectedRowIndices();\n if (selectedIndices.includes(originIndex) && selectedIndices.length > 1) {\n const sorted = [...selectedIndices].sort((a, b) => a - b);\n return {\n rows: sorted.map((i) => rows[i] as T),\n indices: sorted,\n };\n }\n }\n return { rows: [originRow], indices: [originIndex] };\n }\n\n private ensureAutoScroller(): void {\n if (this.autoScroller) return;\n const viewport = this.gridElement.querySelector<HTMLElement>('.rows-viewport');\n if (!viewport) return;\n const opts = typeof this.config.autoScroll === 'object' ? this.config.autoScroll : undefined;\n this.autoScroller = createAutoScroller(viewport, opts, (active) => {\n this.gridElement.classList.toggle('tbw-grid--auto-scrolling', active);\n });\n }\n\n private applyDropPositionClasses(rowEl: HTMLElement | null, isBefore: boolean): void {\n this.clearDropTargetClasses();\n if (!rowEl) return;\n rowEl.classList.add('drop-target');\n rowEl.classList.toggle('drop-before', isBefore);\n rowEl.classList.toggle('drop-after', !isBefore);\n }\n\n private clearDropTargetClasses(): void {\n this.gridElement?.querySelectorAll('.data-grid-row.drop-target').forEach((row) => {\n row.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n }\n\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.data-grid-row').forEach((row) => {\n row.classList.remove(GridClasses.DRAGGING, 'drop-target', 'drop-before', 'drop-after');\n });\n }\n\n private resetDragState(): void {\n this.isDragging = false;\n this.draggedRowIndex = null;\n this.draggedRows = [];\n this.draggedIndices = [];\n this.dragSessionId = null;\n this.dragAccepted = false;\n this.dropRowIndex = null;\n this.pendingMove = null;\n }\n\n private getRowIndex(rowEl: HTMLElement): number {\n const cell = rowEl.querySelector('.cell[data-row]');\n return cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n }\n\n private clearDebounceTimer(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n\n // #endregion\n\n // #region Intra-Grid Move\n\n private handleKeyboardMove(row: T, fromIndex: number, toIndex: number, focusCol: number): void {\n if (!this.pendingMove) {\n this.pendingMove = { originalIndex: fromIndex, currentIndex: toIndex, row };\n } else {\n this.pendingMove.currentIndex = toIndex;\n }\n this.lastFocusCol = focusCol;\n\n const grid = this.internalGrid;\n const rows = [...(grid._rows ?? this.sourceRows)];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n\n grid._rows = rows;\n grid._focusRow = toIndex;\n grid._focusCol = focusCol;\n grid.refreshVirtualWindow(true);\n ensureCellVisible(grid);\n\n this.clearDebounceTimer();\n this.debounceTimer = setTimeout(() => this.flushPendingMove(), this.config.debounceMs ?? 300);\n }\n\n private flushPendingMove(): void {\n this.clearDebounceTimer();\n if (!this.pendingMove) return;\n const { originalIndex, currentIndex, row: movedRow } = this.pendingMove;\n this.pendingMove = null;\n if (originalIndex === currentIndex) return;\n\n const grid = this.internalGrid;\n // `grid._rows` already reflects the post-move order (mutated incrementally\n // by `handleKeyboardMove`); report it as `detail.rows` so consumers see\n // the actual reordered array, not the original `sourceRows` snapshot.\n const postMoveRows = (grid._rows ?? this.sourceRows) as T[];\n const detail: RowMoveDetail<T> = {\n row: movedRow as T,\n fromIndex: originalIndex,\n toIndex: currentIndex,\n rows: [...postMoveRows],\n source: 'keyboard',\n };\n const cancelled = this.emitCancelable('row-move', detail);\n if (cancelled) {\n // Revert: restore the original snapshot. `sourceRows` was never mutated\n // during the pending move (only `grid._rows` was), so resetting from it\n // is the correct rollback regardless of how many incremental keyboard\n // moves accumulated.\n grid._rows = [...this.sourceRows];\n grid._focusRow = originalIndex;\n grid._focusCol = this.lastFocusCol;\n grid.refreshVirtualWindow(true);\n ensureCellVisible(grid);\n }\n }\n\n private executeIntraGridMove(row: unknown, fromIndex: number, toIndex: number, source: 'keyboard' | 'drag'): void {\n const rows = [...this.sourceRows];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n const detail: RowMoveDetail<T> = {\n row: row as T,\n fromIndex,\n toIndex,\n rows: rows as T[],\n source,\n };\n const cancelled = this.emitCancelable('row-move', detail);\n if (cancelled) return;\n if (this.animationType === 'flip' && this.gridElement) {\n const oldPositions = this.captureRowPositions();\n this.grid.rows = rows;\n requestAnimationFrame(() => {\n void this.gridElement.offsetHeight;\n this.animateFLIP(oldPositions, fromIndex, toIndex);\n });\n } else {\n this.grid.rows = rows;\n }\n }\n\n private captureRowPositions(): Map<number, number> {\n const positions = new Map<number, number>();\n this.gridElement?.querySelectorAll('.data-grid-row').forEach((row) => {\n const rowIndex = this.getRowIndex(row as HTMLElement);\n if (rowIndex >= 0) positions.set(rowIndex, row.getBoundingClientRect().top);\n });\n return positions;\n }\n\n private animateFLIP(oldPositions: Map<number, number>, fromIndex: number, toIndex: number): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n const minIndex = Math.min(fromIndex, toIndex);\n const maxIndex = Math.max(fromIndex, toIndex);\n const rowsToAnimate: { el: HTMLElement; deltaY: number }[] = [];\n\n gridEl.querySelectorAll('.data-grid-row').forEach((row) => {\n const rowEl = row as HTMLElement;\n const newRowIndex = this.getRowIndex(rowEl);\n if (newRowIndex < 0 || newRowIndex < minIndex || newRowIndex > maxIndex) return;\n let oldIndex: number;\n if (newRowIndex === toIndex) oldIndex = fromIndex;\n else if (fromIndex < toIndex) oldIndex = newRowIndex + 1;\n else oldIndex = newRowIndex - 1;\n const oldTop = oldPositions.get(oldIndex);\n if (oldTop === undefined) return;\n const newTop = rowEl.getBoundingClientRect().top;\n const deltaY = oldTop - newTop;\n if (Math.abs(deltaY) > 1) rowsToAnimate.push({ el: rowEl, deltaY });\n });\n\n if (rowsToAnimate.length === 0) return;\n\n rowsToAnimate.forEach(({ el, deltaY }) => {\n el.style.transform = `translateY(${deltaY}px)`;\n });\n void gridEl.offsetHeight;\n\n const duration = this.animationDuration;\n requestAnimationFrame(() => {\n rowsToAnimate.forEach(({ el }) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n setTimeout(() => {\n rowsToAnimate.forEach(({ el }) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n // #endregion\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["styles","BaseGridPlugin","fromVirtualScrollTop","toVirtualScrollTop","GridClasses","registry","Map","lookupDragSession","sessionId","entry","get","hasPrimitives","result","ref","refs","obj","deref","push","clearDragSession","delete","newDragSessionId","crypto","randomUUID","getRandomValues","buf","Uint32Array","Date","now","toString","fallbackCounter","ensureCellVisible","grid","options","_virtualization","enabled","rowHeight","container","viewportEl","scrollMapping","scrollEl","visibleHeight","clientHeight","offsets","_getVerticalScrollOffsets","_focusRow","top","bottom","skipScroll","Math","max","usableHeight","y","virtualScrollTop","scrollTop","target","scrollFocusedRowIntoView","isEditing","_activeEditRows","isRowEditing","_isGridEditMode","root","refreshVirtualWindow","_bodyEl","querySelectorAll","forEach","el","classList","remove","Array","from","setAttribute","focused","rowIndex","vStart","start","vEnd","end","_rows","length","rowEl","cell","children","_focusCol","contains","querySelector","findFocusedCell","add","forceHorizontalScroll","scrollArea","forceScrollLeft","scrollLeft","forceScrollRight","scrollWidth","clientWidth","_getHorizontalScrollOffsets","left","right","cellRect","getBoundingClientRect","scrollAreaRect","cellLeft","cellRight","width","visibleLeft","visibleRight","scrollCellIntoViewHorizontally","focusTarget","document","activeElement","focus","preventScroll","hasAttribute","active","HTMLElement","closest","applyFocusTarget","supportsPopover","prototype","showPopover","createDragAlternativeMenu","id","className","opened","disposed","abort","AbortController","close","hidePopover","hidden","addEventListener","e","composedPath","includes","capture","signal","key","element","open","anchor","label","actions","createElement","next","relatedTarget","Node","build","host","body","parentNode","appendChild","replaceChildren","action","btn","type","textContent","disabled","run","a","box","min","window","innerWidth","below","height","innerHeight","style","round","place","dispose","currentSession","getCurrentDragSession","clearCurrentDragSession","TBW_ROW_DRAG_MIME","parseZoneFromMime","mime","match","decodeURIComponent","encodePayload","payload","JSON","stringify","computeDropPosition","clientY","rowIndexResolver","totalRows","overIndex","insertIndex","isBefore","targetIndex","rect","ROW_DRAG_HANDLE_FIELD","sharedChannel","remoteListeners","Set","ensureCrossWindowChannel","BroadcastChannel","event","data","listener","attachedPlugins","RowDragDropPlugin","name","styles","static","events","description","cancelable","defaultConfig","enableKeyboard","dragHandlePosition","dragHandleWidth","debounceMs","animation","operation","autoScroll","dragFrom","shouldRenderDragHandle","explicit","this","config","showDragHandle","rowIsDraggable","animationType","isAnimationEnabled","isDragging","draggedRowIndex","draggedRows","draggedIndices","dragSessionId","dragAccepted","dropRowIndex","pendingMove","debounceTimer","lastFocusCol","autoScroller","gridId","remoteTransferListener","msg","onRemoteTransfer","moveMenu","internalGrid","attach","super","gridElement","slice","setupDelegatedDragListeners","detach","clearDebounceTimer","stop","size","unregisterRemoteTransferListener","resetDragState","processColumns","columns","dragHandleColumn","field","header","resizable","sortable","filterable","lockPosition","utility","viewRenderer","draggable","stopPropagation","openMoveMenu","setIcon","afterRender","applyRowDraggable","onScrollRender","wantDraggable","rows","row","getAttribute","removeAttribute","onKeyDown","altKey","ctrlKey","isKeyboardMoveBlocked","focusRow","sourceRows","toIndex","canMoveRow","handleKeyboardMove","preventDefault","queryBoolean","tag","tagName","isContentEditable","onCellClick","flushPendingMove","moveRow","fromIndex","executeIntraGridMove","queryResults","query","isArray","canDrag","canDrop","sourceGridId","dropZone","rowIndices","gridEl","disconnectSignal","onDragStart","onDragEnd","onDragOver","onDragLeave","onDrop","de","handle","initiatedFromHandle","isInteractiveDragOrigin","getRowIndex","indices","resolveDraggedRows","startDetail","emitCancelable","serialize","serializeRow","r","map","dataTransfer","effectAllowed","setData","encodeURIComponent","dataColumns","filter","c","lines","cells","col","value","String","replace","join","formatRowsAsTSV","badge","setDragImage","setTimeout","attachRowCloneDragImage","WeakRef","set","registerDragSession","setCurrentDragSession","INTERACTIVE_DRAG_SELECTORS","clone","cloneNode","offsetX","clientX","offsetY","handleRect","dt","types","t","startsWith","hasAnyRowDragMime","session","isIntra","zone","findMatchingZoneMime","dropEffect","pos","clearDropTargetClasses","accepted","toggle","applyDropPositionClasses","ensureAutoScroller","onPointerMove","currentTarget","liveRows","lookup","raw","parsed","parse","idx","Number","isInteger","decodePayload","getData","deserialize","deserializeRow","incomingRows","dropDetail","targetRows","splice","sourcePlugin","findPeerOnGrid","sourceGrid","getElementById","srcRows","sortedIndices","sort","b","transferDetail","fromGridId","toGridId","fromIndices","emit","emitTransfer","channel","postMessage","broadcastRemoteTransfer","serializedRows","endDetail","clearDragClasses","detail","peerEl","getPluginByName","peersInDropZone","peers","candidate","peer","gridLabel","verb","transferToPeer","acceptTransfer","originIndex","originRow","selection","getSelectedRowIndices","selectedIndices","sorted","i","viewport","opts","onScrollChange","edgeSize","baseSpeed","speed","maxSpeed","rafId","pointerY","setActive","tick","delta","ratio","before","requestAnimationFrame","cancelAnimationFrame","isScrolling","createAutoScroller","parseInt","clearTimeout","focusCol","currentIndex","originalIndex","movedRow","source","oldPositions","captureRowPositions","offsetHeight","animateFLIP","positions","minIndex","maxIndex","rowsToAnimate","newRowIndex","oldIndex","oldTop","deltaY","abs","transform","duration","animationDuration"],"sources":["../../../../../../libs/grid/src/lib/core/constants.ts","../../../../../../libs/grid/src/lib/core/internal/drag-drop-registry.ts","../../../../../../libs/grid/src/lib/core/internal/utils.ts","../../../../../../libs/grid/src/lib/core/internal/keyboard.ts","../../../../../../libs/grid/src/lib/plugins/shared/drag-alternative-menu.ts","../../../../../../libs/grid/src/lib/plugins/shared/drag-drop-protocol.ts","../../../../../../libs/grid/src/lib/plugins/row-drag-drop/row-drag-drop.css?inline","../../../../../../libs/grid/src/lib/plugins/row-drag-drop/row-drag-drop-plugin.ts"],"sourcesContent":["/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin, UndoRedoPlugin, and keyboard navigation. Defined here\n * (a leaf module) rather than in `rows.ts` so `keyboard.ts` can consume it\n * without creating a rows ↔ keyboard import cycle.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-grid-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n FIELD: 'data-field',\n\n /**\n * Marks a `title` the grid itself put on a cell to reveal ellipsised text\n * (WCAG SC 1.4.12). Anything that sets its own `title` on a cell must clear\n * this, or the grid will later mistake that title for its own and replace it.\n *\n * @since 3.6.0\n */\n TRUNCATED: 'data-tbw-truncated',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\n\n/**\n * Union of all CSS class names used in the grid's DOM.\n * Use `GridClasses` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\n\n/**\n * Union of all `data-*` attribute names used on grid elements.\n * Use `GridDataAttrs` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\n\n/**\n * Union of all CSS custom property names (`--tbw-*`) available for theming.\n * Use `GridCSSVars` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","/**\n * Drag-Drop Registry\n *\n * Module-level singleton that holds live (WeakRef) references to row objects\n * being dragged across grids in the same JS heap. Used by `RowDragDropPlugin`\n * to recover live references on drop, avoiding any serialization cost when\n * source and target grids share a heap.\n *\n * Lives in `core/internal/` (not in the plugin bundle) so that two copies of\n * `@toolbox-web/grid` loaded via different entry points (e.g. `/all` vs\n * per-plugin imports) cannot end up with two separate registries that silently\n * fall back to JSON.\n *\n * Cross-window / cross-origin-iframe drags do NOT go through this registry —\n * they fall back to `dataTransfer` JSON.\n *\n * @internal\n */\n\ninterface RegistryEntry {\n readonly refs: ReadonlyArray<WeakRef<object>>;\n /** True when one or more registered rows were primitives (not objects). */\n readonly hasPrimitives: boolean;\n}\n\nconst registry = new Map<string, RegistryEntry>();\n\n/**\n * Register a drag session's live row references.\n *\n * Object rows are stored as `WeakRef<object>`; primitive rows cannot be held\n * weakly and would either need a sentinel (which corrupts the row value on\n * recovery) or be omitted (which misaligns indices). We instead flag the\n * session as containing primitives — `lookupDragSession()` then returns\n * `undefined` so callers fall back to the JSON payload, which round-trips\n * primitives losslessly.\n *\n * @param sessionId - Unique drag session identifier (typically a UUID).\n * @param rows - Rows being dragged.\n */\nexport function registerDragSession(sessionId: string, rows: readonly unknown[]): void {\n const refs: WeakRef<object>[] = [];\n let hasPrimitives = false;\n for (const row of rows) {\n if (row !== null && typeof row === 'object') {\n refs.push(new WeakRef(row as object));\n } else {\n hasPrimitives = true;\n }\n }\n registry.set(sessionId, { refs, hasPrimitives });\n}\n\n/**\n * Look up the live row references for a drag session.\n *\n * Returns `undefined` when the caller MUST fall back to the JSON payload:\n * - the session is unknown,\n * - any registered reference has been garbage-collected (partial recovery\n * would silently corrupt cross-grid moves), or\n * - the session contains primitive rows (which cannot be held weakly).\n */\nexport function lookupDragSession<T = unknown>(sessionId: string): T[] | undefined {\n const entry = registry.get(sessionId);\n if (!entry) return undefined;\n if (entry.hasPrimitives) return undefined; // primitives — caller falls back to JSON\n const result: unknown[] = [];\n for (const ref of entry.refs) {\n const obj = ref.deref();\n if (obj === undefined) return undefined; // GC'd — caller falls back to JSON\n result.push(obj);\n }\n return result as T[];\n}\n\n/**\n * Clear a drag session from the registry.\n * Should be called from `dragend` on the source grid.\n */\nexport function clearDragSession(sessionId: string): void {\n registry.delete(sessionId);\n}\n\n/**\n * @internal Test-only — clear all sessions.\n */\nexport function _clearAllDragSessions(): void {\n registry.clear();\n}\n\n/**\n * Generate a unique drag session identifier.\n *\n * Used as a Map key to recover live row references for in-process drags; this\n * is **not** a security context. Uses `crypto.randomUUID()` when available\n * (modern browsers in secure contexts, Node 19+, Bun) and falls back to\n * `crypto.getRandomValues()` for older non-secure contexts. `Math.random()`\n * is intentionally avoided to keep CodeQL clean.\n */\nexport function newDragSessionId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const buf = new Uint32Array(2);\n crypto.getRandomValues(buf);\n return `drag-${Date.now().toString(36)}-${buf[0].toString(36)}${buf[1].toString(36)}`;\n }\n // Final fallback: timestamp + monotonically incrementing counter (no PRNG).\n // Sufficient because the registry is in-process and lives only for the\n // duration of a single drag.\n return `drag-${Date.now().toString(36)}-${(++fallbackCounter).toString(36)}`;\n}\n\nlet fallbackCounter = 0;\n","// #region Environment Helpers\n\n/**\n * Check if we're running in a development environment.\n * Returns true for localhost or when NODE_ENV !== 'production'.\n * Used to show warnings only in development.\n */\nexport function isDevelopment(): boolean {\n // Check for localhost (browser environment)\n if (typeof window !== 'undefined' && window.location) {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {\n return true;\n }\n }\n // Check for NODE_ENV (build-time or SSR)\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n return true;\n }\n return false;\n}\n\n// #endregion\n\n// #region Cell Rendering Helpers\n\n/**\n * Generate accessible HTML for a boolean cell.\n * Uses role=\"checkbox\" with proper aria attributes.\n */\nexport function booleanCellHTML(value: boolean): string {\n return `<span role=\"checkbox\" aria-checked=\"${value}\" aria-label=\"${value}\">${value ? '&#x1F5F9;' : '&#9744;'}</span>`;\n}\n\n/**\n * Format a date value for display.\n * Handles Date objects, timestamps, and date strings.\n * Returns empty string for invalid dates.\n */\nexport function formatDateValue(value: unknown): string {\n if (value == null || value === '') return '';\n if (value instanceof Date) {\n return isNaN(value.getTime()) ? '' : value.toLocaleDateString();\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return isNaN(d.getTime()) ? '' : d.toLocaleDateString();\n }\n return '';\n}\n\n/**\n * Get the row index from a cell element's data-row attribute.\n * Falls back to calculating from parent row's DOM position if data-row is missing.\n * Returns -1 if no valid row index is found.\n */\nexport function getRowIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-row');\n if (attr) return parseInt(attr, 10);\n\n // Fallback: find the parent .data-grid-row and calculate index from siblings\n const rowEl = cell.closest('.data-grid-row');\n if (!rowEl) return -1;\n\n const parent = rowEl.parentElement;\n if (!parent) return -1;\n\n // Get all data-grid-row siblings and find this row's index\n const rows = parent.querySelectorAll(':scope > .data-grid-row');\n for (let i = 0; i < rows.length; i++) {\n if (rows[i] === rowEl) return i;\n }\n return -1;\n}\n\n/**\n * Get the column index from a cell element's data-col attribute.\n * Returns -1 if no valid column index is found.\n */\nexport function getColIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-col');\n return attr ? parseInt(attr, 10) : -1;\n}\n\n/**\n * Clear all cell-focus styling from a root element.\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | null): void {\n if (!root) return;\n root.querySelectorAll('.cell-focus').forEach((el) => el.classList.remove('cell-focus'));\n}\n// #endregion\n\n// #region RTL Helpers\n\n/** Text direction */\nexport type TextDirection = 'ltr' | 'rtl';\n\n/**\n * Get the text direction for an element.\n * Reads from the computed style, which respects the `dir` attribute on the element\n * or any ancestor, as well as CSS `direction` property.\n *\n * @param element - The element to check direction for\n * @returns 'ltr' or 'rtl'\n *\n * @example\n * ```typescript\n * // Detect grid's direction\n * const dir = getDirection(gridElement);\n * if (dir === 'rtl') {\n * // Handle RTL layout\n * }\n * ```\n */\nexport function getDirection(element: Element): TextDirection {\n // Try computed style first (works in real browsers)\n try {\n const computedDir = getComputedStyle(element).direction;\n if (computedDir === 'rtl') return 'rtl';\n } catch {\n // getComputedStyle may fail in some test environments\n }\n\n // Fallback: check dir attribute on element or ancestors\n // This handles test environments where getComputedStyle may not reflect dir attribute\n try {\n const dirAttr = element.closest?.('[dir]')?.getAttribute('dir');\n if (dirAttr === 'rtl') return 'rtl';\n } catch {\n // closest may not be available on mock elements\n }\n\n return 'ltr';\n}\n\n/**\n * Check if an element is in RTL mode.\n *\n * @param element - The element to check\n * @returns true if the element's text direction is right-to-left\n */\nexport function isRTL(element: Element): boolean {\n return getDirection(element) === 'rtl';\n}\n\n/**\n * Resolve a logical inline position to a physical position based on text direction.\n *\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n * - `'left'` / `'right'` → unchanged (physical values)\n *\n * @param position - Logical or physical position\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical position ('left' or 'right')\n *\n * @example\n * ```typescript\n * resolveInlinePosition('start', 'ltr'); // 'left'\n * resolveInlinePosition('start', 'rtl'); // 'right'\n * resolveInlinePosition('left', 'rtl'); // 'left' (unchanged)\n * ```\n */\nexport function resolveInlinePosition(\n position: 'left' | 'right' | 'start' | 'end',\n direction: TextDirection,\n): 'left' | 'right' {\n if (position === 'left' || position === 'right') {\n return position;\n }\n if (direction === 'rtl') {\n return position === 'start' ? 'right' : 'left';\n }\n return position === 'start' ? 'left' : 'right';\n}\n// #endregion\n","/**\n * Central keyboard handler attached to the host element. Manages navigation, paging,\n * and edit lifecycle triggers while respecting active form field interactions.\n */\nimport { FOCUSABLE_EDITOR_SELECTOR, GridClasses } from '../constants';\nimport type { GridHost } from '../types';\nimport { clearCellFocus, isRTL } from './utils';\nimport { readCellField } from './value-accessor';\nimport { fromVirtualScrollTop, getRowIndexAtOffset, toVirtualScrollTop } from './virtualization';\n\n/** Commit active row edit if the editing plugin provides this method. */\nfunction tryCommitEdit(grid: GridHost): void {\n if (typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n}\n\nfunction isFormField(el: HTMLElement | null): boolean {\n if (!el) return false;\n const tag = el.tagName;\n if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return true;\n if (el.isContentEditable) return true;\n return false;\n}\n\n/** True while a row-scoped edit session is active. */\nfunction isRowEditing(grid: GridHost): boolean {\n return grid._activeEditRows !== undefined && grid._activeEditRows !== -1;\n}\n\n// #region Keyboard Handler\n/**\n * Guard chain deciding whether this keydown belongs to something other than\n * cell navigation and must be left alone.\n */\nfunction shouldIgnoreKeyDown(grid: GridHost, e: KeyboardEvent, target: HTMLElement | null): boolean {\n // Keyboard events whose target lives inside the grid host but OUTSIDE the\n // rows body (toolpanel inputs/buttons, shell-header controls, light-DOM\n // children of <tbw-grid>) own their own keyboard interactions. Treating\n // ArrowUp/ArrowDown/Enter on such a target as cell navigation steals the\n // user's actual interaction target on the very next render cycle.\n // Cells and the grid host itself remain valid navigation targets.\n if (target && target !== grid && !target.closest?.('.rows-body')) return true;\n\n if (isFormField(target)) {\n switch (e.key) {\n // Home/End and horizontal arrows navigate within the text input; Enter\n // and Escape are handled by the input's own handlers first.\n case 'Home':\n case 'End':\n case 'ArrowLeft':\n case 'ArrowRight':\n case 'Enter':\n case 'Escape':\n return true;\n case 'ArrowUp':\n case 'ArrowDown':\n // Number spinners own the vertical arrows; other fields fall through.\n if ((target as HTMLInputElement).tagName === 'INPUT' && (target as HTMLInputElement).type === 'number') {\n return true;\n }\n break;\n }\n }\n\n const colType = grid._visibleColumns[grid._focusCol]?.type;\n return isRowEditing(grid) && colType === 'select' && (e.key === 'ArrowDown' || e.key === 'ArrowUp');\n}\n\n/** Tab / Shift+Tab: advance one cell, wrapping to the next/previous row. */\nfunction navigateTab(grid: GridHost, e: KeyboardEvent, maxRow: number, maxCol: number): void {\n e.preventDefault();\n if (!e.shiftKey) {\n if (grid._focusCol < maxCol) {\n grid._focusCol += 1;\n return;\n }\n tryCommitEdit(grid);\n if (grid._focusRow < maxRow) {\n grid._focusRow += 1;\n grid._focusCol = 0;\n }\n return;\n }\n if (grid._focusCol > 0) {\n grid._focusCol -= 1;\n } else if (grid._focusRow > 0) {\n if (grid._activeEditRows === grid._focusRow) tryCommitEdit(grid);\n grid._focusRow -= 1;\n grid._focusCol = maxCol;\n }\n}\n\n/**\n * ArrowLeft/ArrowRight. `towardEnd` is the LOGICAL direction (ArrowRight); in\n * RTL the physical arrow maps to the opposite column index.\n */\nfunction navigateHorizontal(grid: GridHost, e: KeyboardEvent, maxCol: number, towardEnd: boolean): void {\n const forward = isRTL(grid) ? !towardEnd : towardEnd;\n grid._focusCol = forward ? Math.min(maxCol, grid._focusCol + 1) : Math.max(0, grid._focusCol - 1);\n e.preventDefault();\n}\n\n/** Commit any active row edit, then focus the first/last row (column unchanged). */\nfunction jumpToRowEdge(grid: GridHost, maxRow: number, toEnd: boolean): void {\n if (isRowEditing(grid)) tryCommitEdit(grid);\n grid._focusRow = toEnd ? maxRow : 0;\n}\n\n/** ArrowUp/ArrowDown: one row, or the first/last row with Ctrl/Cmd. */\nfunction navigateVertical(grid: GridHost, e: KeyboardEvent, maxRow: number, down: boolean): void {\n if (e.ctrlKey || e.metaKey) jumpToRowEdge(grid, maxRow, down);\n else {\n if (isRowEditing(grid)) tryCommitEdit(grid);\n grid._focusRow = down ? Math.min(maxRow, grid._focusRow + 1) : Math.max(0, grid._focusRow - 1);\n }\n e.preventDefault();\n}\n\n/**\n * PageUp/PageDown target: one viewport of rows (less any overlay band), so an\n * unconstrained grid pages straight to its edge. 20 rows until layout exists.\n */\nfunction pageTargetRow(grid: GridHost, maxRow: number, down: boolean): number {\n const from = grid._focusRow;\n const virt = grid._virtualization;\n const viewportH = virt?.viewportEl?.clientHeight ?? virt?.container?.clientHeight ?? 0;\n const offsets = grid._getVerticalScrollOffsets?.(from);\n const usable = viewportH - Math.max(0, offsets?.top ?? 0) - Math.max(0, offsets?.bottom ?? 0);\n const pc = virt?.variableHeights ? virt.positionCache : null;\n let target: number;\n if (usable > 0 && pc?.[from]) {\n target = getRowIndexAtOffset(pc, pc[from].offset + (down ? usable : -usable));\n // A row taller than the viewport would otherwise pin focus in place.\n if (target === from) target = from + (down ? 1 : -1);\n } else {\n const rowH = virt?.rowHeight ?? 0;\n const step = usable > 0 && rowH > 0 ? Math.max(1, Math.floor(usable / rowH)) : 20;\n target = from + (down ? step : -step);\n }\n return Math.max(0, Math.min(maxRow, target));\n}\n\n/** Home/End (plus Ctrl/Cmd variants which also jump to the first/last row). */\nfunction navigateRowEdge(grid: GridHost, e: KeyboardEvent, maxRow: number, maxCol: number, toEnd: boolean): void {\n if (e.ctrlKey || e.metaKey) jumpToRowEdge(grid, maxRow, toEnd);\n grid._focusCol = toEnd ? maxCol : 0;\n e.preventDefault();\n ensureCellVisible(grid, toEnd ? { forceScrollRight: true } : { forceScrollLeft: true });\n}\n\n/** Dispatch the unified `cell-activate` event. Returns true when cancelled. */\nfunction emitCellActivate(grid: GridHost, e: KeyboardEvent): boolean {\n const rowIndex = grid._focusRow;\n const colIndex = grid._focusCol;\n const column = grid._visibleColumns[colIndex];\n const row = grid._rows[rowIndex];\n const field = column?.field ?? '';\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n detail: {\n rowIndex,\n colIndex,\n column,\n field,\n value: field && row ? readCellField(row, field) : undefined,\n row,\n cellEl: grid.querySelector(`[data-row=\"${rowIndex}\"][data-col=\"${colIndex}\"]`) as HTMLElement | undefined,\n trigger: 'keyboard' as const,\n originalEvent: e,\n },\n });\n grid.dispatchEvent(activateEvent);\n return activateEvent.defaultPrevented;\n}\n\nexport function handleGridKeyDown(grid: GridHost, e: KeyboardEvent): void {\n // Dispatch to plugin system first - if any plugin handles it, stop here\n if (grid._dispatchKeyDown?.(e)) return;\n\n const path = e.composedPath?.() ?? [];\n const target = (path.length ? path[0] : e.target) as HTMLElement | null;\n if (shouldIgnoreKeyDown(grid, e, target)) return;\n\n const maxRow = grid._rows.length - 1;\n const maxCol = grid._visibleColumns.length - 1;\n\n switch (e.key) {\n case 'Tab':\n navigateTab(grid, e, maxRow, maxCol);\n break;\n case 'ArrowDown':\n navigateVertical(grid, e, maxRow, true);\n break;\n case 'ArrowUp':\n navigateVertical(grid, e, maxRow, false);\n break;\n case 'ArrowRight':\n navigateHorizontal(grid, e, maxCol, true);\n break;\n case 'ArrowLeft':\n navigateHorizontal(grid, e, maxCol, false);\n break;\n case 'Home':\n navigateRowEdge(grid, e, maxRow, maxCol, false);\n return;\n case 'End':\n navigateRowEdge(grid, e, maxRow, maxCol, true);\n return;\n case 'PageDown':\n grid._focusRow = pageTargetRow(grid, maxRow, true);\n e.preventDefault();\n break;\n case 'PageUp':\n grid._focusRow = pageTargetRow(grid, maxRow, false);\n e.preventDefault();\n break;\n // NOTE: Enter is normally handled by EditingPlugin. If no plugin handled\n // it, dispatch the unified cell-activate event for custom handling.\n case 'Enter':\n if (emitCellActivate(grid, e)) {\n e.preventDefault();\n return;\n }\n break;\n default:\n return;\n }\n ensureCellVisible(grid);\n}\n// #endregion\n\n// #region Cell Visibility\n/**\n * Options for ensureCellVisible to control scroll behavior.\n */\ninterface EnsureCellVisibleOptions {\n /** Force scroll to the leftmost position (for Home key) */\n forceScrollLeft?: boolean;\n /** Force scroll to the rightmost position (for End key) */\n forceScrollRight?: boolean;\n /** Force horizontal scroll even in edit mode (for Tab navigation) */\n forceHorizontalScroll?: boolean;\n}\n\n/**\n * Vertical: scroll the virtualized container so the focused row is in view.\n *\n * `y` is in raw row-content space (rowHeight × index). Above the browser height\n * cap (#326), `scrollEl.scrollTop` is in clamped spacer space — the two\n * coordinate systems are not interchangeable, so translate scrollTop into\n * virtual space for the comparison and back into native space when writing.\n * For sub-cap datasets the mapping is identity, so this is a no-op.\n *\n * Plugins that paint over the viewport (sticky rows) report the space they\n * obscure via `_getVerticalScrollOffsets`, so the row lands clear of the\n * overlay instead of underneath it — WCAG 2.2 SC 2.4.11 Focus Not Obscured.\n */\nfunction scrollFocusedRowIntoView(grid: GridHost): void {\n if (!grid._virtualization?.enabled) return;\n const { rowHeight, container, viewportEl, scrollMapping } = grid._virtualization;\n // container is the faux scrollbar element that handles actual scrolling;\n // viewportEl is the visible area element that has the correct height.\n const scrollEl = container as HTMLElement | undefined;\n const visibleHeight = viewportEl?.clientHeight ?? scrollEl?.clientHeight ?? 0;\n if (!scrollEl || visibleHeight <= 0) return;\n\n const offsets = grid._getVerticalScrollOffsets?.(grid._focusRow) ?? { top: 0, bottom: 0 };\n if (offsets.skipScroll) return;\n // A malformed or oversized report must never invert the visible band.\n const top = Math.max(0, offsets.top);\n const bottom = Math.max(0, offsets.bottom);\n const usableHeight = visibleHeight - top - bottom;\n if (usableHeight <= 0) return;\n\n const y = grid._focusRow * rowHeight;\n const virtualScrollTop = scrollMapping ? toVirtualScrollTop(scrollEl.scrollTop, scrollMapping) : scrollEl.scrollTop;\n if (y < virtualScrollTop + top) {\n const target = Math.max(0, y - top);\n scrollEl.scrollTop = scrollMapping ? fromVirtualScrollTop(target, scrollMapping) : target;\n } else if (y + rowHeight > virtualScrollTop + top + usableHeight) {\n const target = Math.max(0, y - usableHeight + rowHeight - top);\n scrollEl.scrollTop = scrollMapping ? fromVirtualScrollTop(target, scrollMapping) : target;\n }\n}\n\n/**\n * Resolve the rendered cell for the focused row/col, or `undefined` when the\n * focused row is outside the current virtual window.\n */\nfunction findFocusedCell(grid: GridHost): { rowEl: HTMLElement | null; cell: HTMLElement } | undefined {\n const rowIndex = grid._focusRow;\n const vStart = grid._virtualization.start ?? 0;\n const vEnd = grid._virtualization.end ?? grid._rows.length;\n if (rowIndex < vStart || rowIndex >= vEnd) return undefined;\n\n const rowEl = grid._bodyEl.querySelectorAll('.data-grid-row')[rowIndex - vStart] as HTMLElement | null;\n // Try exact column match first, then query by data-col, then fall back to the\n // first cell (full-width group rows).\n let cell = rowEl?.children[grid._focusCol] as HTMLElement | undefined;\n if (!cell || !cell.classList?.contains('cell')) {\n cell = (rowEl?.querySelector(`.cell[data-col=\"${grid._focusCol}\"]`) ?? rowEl?.querySelector('.cell[data-col]')) as\n HTMLElement | undefined;\n }\n return cell ? { rowEl: rowEl ?? null, cell } : undefined;\n}\n\n/** Horizontal: bring the focused cell into the `.tbw-scroll-area` viewport. */\nfunction scrollCellIntoViewHorizontally(\n grid: GridHost,\n rowEl: HTMLElement | null,\n cell: HTMLElement,\n options?: EnsureCellVisibleOptions,\n): void {\n const scrollArea = grid.querySelector('.tbw-scroll-area') as HTMLElement | null;\n if (!scrollArea) return;\n\n // Home/End always scroll to the edge.\n if (options?.forceScrollLeft) {\n scrollArea.scrollLeft = 0;\n return;\n }\n if (options?.forceScrollRight) {\n scrollArea.scrollLeft = scrollArea.scrollWidth - scrollArea.clientWidth;\n return;\n }\n\n // Plugins (e.g. pinned columns) report how much of the scroll area they\n // obscure, and whether the focused cell should skip scrolling entirely\n // (pinned cells are always visible).\n const offsets = grid._getHorizontalScrollOffsets?.(rowEl ?? undefined, cell) ?? { left: 0, right: 0 };\n if (offsets.skipScroll) return;\n\n const cellRect = cell.getBoundingClientRect();\n const scrollAreaRect = scrollArea.getBoundingClientRect();\n const cellLeft = cellRect.left - scrollAreaRect.left + scrollArea.scrollLeft;\n const cellRight = cellLeft + cellRect.width;\n const visibleLeft = scrollArea.scrollLeft + offsets.left;\n const visibleRight = scrollArea.scrollLeft + scrollArea.clientWidth - offsets.right;\n if (cellLeft < visibleLeft) {\n scrollArea.scrollLeft = cellLeft - offsets.left;\n } else if (cellRight > visibleRight) {\n scrollArea.scrollLeft = cellRight - scrollArea.clientWidth + offsets.right;\n }\n}\n\n/** Move real DOM focus to the editor, the cell, or the grid host. */\nfunction applyFocusTarget(grid: GridHost, cell: HTMLElement, isEditing: boolean): void {\n if (isEditing && cell.classList.contains(GridClasses.EDITING)) {\n // Editing cell: focus the editor input inside it\n const focusTarget = cell.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (focusTarget && document.activeElement !== focusTarget) {\n try {\n focusTarget.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n return;\n }\n\n if (isEditing) {\n // Active edit row but this cell isn't the editing cell — focus it so Tab\n // navigation within the row can attach editors.\n if (cell.contains(document.activeElement)) return;\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n try {\n cell.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n return;\n }\n\n // NOT editing: keep focus on the grid element (tabindex=0) rather than\n // individual cells. In a virtualized grid, cells can be detached by\n // subsequent render cycles (e.g., SelectionPlugin's requestAfterRender\n // → RAF → row recycling). A detached focused cell causes activeElement\n // to revert to <body>, breaking keyboard navigation.\n // Visual focus is managed by the .cell-focus CSS class + data-has-focus.\n //\n // BUT: don't steal focus from a real focused descendant (toolpanel\n // input, shell-header button, registered overlay control). Cell focus\n // is virtual; if the user is actively typing in a non-cell input, we\n // must leave that input alone. We only reclaim focus when nothing\n // meaningful inside the grid currently holds it (active element is\n // outside grid, on the grid host itself, or on a bare cell).\n const active = document.activeElement;\n const meaningful =\n active instanceof HTMLElement &&\n active !== grid &&\n typeof grid.contains === 'function' &&\n grid.contains(active) &&\n !active.classList.contains('cell') &&\n !active.closest('.cell');\n if (!meaningful && active !== grid) {\n grid.focus({ preventScroll: true });\n }\n}\n\n/**\n * Scroll the viewport (virtualized or static) so the focused cell's row is visible\n * and apply visual focus styling / tabindex management.\n */\nexport function ensureCellVisible(grid: GridHost, options?: EnsureCellVisibleOptions): void {\n scrollFocusedRowIntoView(grid);\n\n const isEditing = isRowEditing(grid) || !!grid._isGridEditMode;\n // Skip refreshVirtualWindow when in edit mode to avoid wiping editors\n if (!isEditing) grid.refreshVirtualWindow(false);\n\n clearCellFocus(grid._bodyEl);\n // Clear previous aria-selected markers\n Array.from(grid._bodyEl.querySelectorAll('[aria-selected=\"true\"]')).forEach((el) => {\n el.setAttribute('aria-selected', 'false');\n });\n\n const focused = findFocusedCell(grid);\n if (!focused) return;\n const { rowEl, cell } = focused;\n\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n\n // Skip horizontal scrolling in edit mode to prevent scroll jumps when editors\n // are created — unless forceHorizontalScroll is set (Tab while editing).\n if (!isEditing || options?.forceHorizontalScroll) {\n scrollCellIntoViewHorizontally(grid, rowEl, cell, options);\n }\n\n applyFocusTarget(grid, cell, isEditing);\n}\n// #endregion\n","/**\n * Generic click-only action menu — the shared building block for WCAG 2.2\n * SC 2.5.7 \"Dragging Movements\" alternatives in plugins.\n *\n * Every drag affordance in the grid needs a path that works with plain clicks\n * or taps. A keyboard equivalent is **not** enough: SC 2.5.7 is specifically\n * about pointer input, and the W3C Understanding text calls out \"a pop-up menu\n * after tap\" as a conforming alternative. This module is that pop-up.\n *\n * Lives under `plugins/shared/` on purpose: each plugin bundle is built\n * self-contained, so this code is duplicated into the plugins that use it and\n * never lands in the core `index.js` budget.\n *\n * @since 3.6.0\n * @internal\n */\n\n/** Gap between the anchor element and the menu, in px. */\nconst ANCHOR_GAP_PX = 6;\n\n/** Runtime check — happy-dom and older browsers may lack the Popover API. */\nfunction supportsPopover(): boolean {\n return typeof HTMLElement.prototype?.showPopover === 'function';\n}\n\nlet hoverlessQuery: MediaQueryList | null | undefined;\n\n/** Whether the primary pointer cannot hover (touch, most switch devices). */\nfunction isHoverless(): boolean {\n if (hoverlessQuery === undefined) hoverlessQuery = globalThis.matchMedia?.('(hover: none)') ?? null;\n return hoverlessQuery?.matches ?? false;\n}\n\n/**\n * Whether a plugin should also expose its drag alternative as a dedicated\n * inline control, on top of the menu that is always available.\n *\n * Hover-less pointers get one unconditionally — a control revealed on hover is\n * unreachable when nothing can hover, and long-press is the wrong fallback for\n * the tremor and low-dexterity users SC 2.5.7 exists for.\n *\n * Driven by `a11y.dragAlternatives` on the grid config.\n */\nexport function prefersInlineDragAlternative(grid: {\n effectiveConfig?: { a11y?: { dragAlternatives?: 'menu' | 'inline' } };\n}): boolean {\n return grid.effectiveConfig?.a11y?.dragAlternatives === 'inline' || isHoverless();\n}\n\n/** One clickable entry in the menu. */\nexport interface DragAlternativeAction {\n /** Visible text, also the accessible name. */\n label: string;\n /** Render the entry but refuse the click (e.g. \"Move up\" on the first row). */\n disabled?: boolean;\n /** Invoked on click; the menu closes first so focus handling is predictable. */\n run(): void;\n}\n\nexport interface DragAlternativeMenu {\n /**\n * Show `actions` anchored to `anchor`, with `label` as the group's accessible\n * name. Mounted inside the anchor's grid host so it inherits theme tokens.\n */\n open(anchor: HTMLElement, label: string, actions: readonly DragAlternativeAction[]): void;\n /** Hide the menu. Safe to call when already closed. */\n close(): void;\n /** Remove the element and every document-level listener. */\n dispose(): void;\n /** The menu element, or `null` before the first {@link DragAlternativeMenu.open}. */\n readonly element: HTMLElement | null;\n}\n\n/**\n * Create a menu instance.\n *\n * @param id - DOM id for the menu element, unique per owning plugin.\n * @param className - Root class name, so each plugin can style its own menu.\n */\nexport function createDragAlternativeMenu(id: string, className: string): DragAlternativeMenu {\n let root: HTMLDivElement | null = null;\n let opened = false;\n let disposed = false;\n const abort = new AbortController();\n\n function close(): void {\n if (!opened || !root) return;\n opened = false;\n if (supportsPopover()) root.hidePopover();\n root.hidden = true;\n }\n\n function build(): HTMLDivElement {\n const el = document.createElement('div');\n el.id = id;\n el.className = className;\n el.setAttribute('popover', 'manual');\n el.setAttribute('role', 'group');\n // Without the Popover API there is no UA `display: none`, so hide it here.\n el.hidden = true;\n // Tabbing past either end dismisses instead of stranding the user — the\n // menu has no visible trigger to tab back to. A null `relatedTarget` (click\n // focus in Firefox/Safari, focus leaving the document) is ignored.\n el.addEventListener(\n 'focusout',\n (e) => {\n const next = (e as FocusEvent).relatedTarget;\n if (next instanceof Node && !el.contains(next)) close();\n },\n { signal: abort.signal },\n );\n return el;\n }\n\n /**\n * Position against the anchor in viewport coordinates. The menu lives in the\n * top layer, so its containing block is the viewport regardless of where the\n * anchor sits in the DOM.\n */\n function place(anchor: HTMLElement): void {\n if (!root) return;\n const a = anchor.getBoundingClientRect();\n const box = root.getBoundingClientRect();\n const left = Math.max(4, Math.min(a.left, window.innerWidth - box.width - 4));\n const below = a.bottom + ANCHOR_GAP_PX;\n const top = below + box.height <= window.innerHeight ? below : Math.max(4, a.top - ANCHOR_GAP_PX - box.height);\n root.style.left = `${Math.round(left)}px`;\n root.style.top = `${Math.round(top)}px`;\n }\n\n // Any pointer press outside the menu dismisses it. Capture phase so the press\n // that lands on a grid cell still reaches its own handler afterwards.\n document.addEventListener(\n 'pointerdown',\n (e) => {\n if (opened && root && !e.composedPath().includes(root)) close();\n },\n { capture: true, signal: abort.signal },\n );\n document.addEventListener(\n 'keydown',\n (e) => {\n if ((e as KeyboardEvent).key === 'Escape') close();\n },\n { capture: true, signal: abort.signal },\n );\n\n return {\n get element() {\n return root;\n },\n open(anchor, label, actions) {\n if (disposed || actions.length === 0) return;\n if (!root) root = build();\n // Mount inside the grid host (light DOM) so theme custom properties and\n // `color-scheme` cascade in; the top layer keeps it out of overflow.\n const host = anchor.closest<HTMLElement>('tbw-grid, [data-tbw-grid]') ?? document.body;\n if (root.parentNode !== host) host.appendChild(root);\n root.setAttribute('aria-label', label);\n root.replaceChildren();\n for (const action of actions) {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `${className}-item`;\n btn.textContent = action.label;\n btn.disabled = action.disabled === true;\n btn.addEventListener('click', () => {\n close();\n action.run();\n });\n root.appendChild(btn);\n }\n root.hidden = false;\n if (!opened && supportsPopover()) root.showPopover();\n opened = true;\n place(anchor);\n root.querySelector<HTMLButtonElement>('button:not([disabled])')?.focus();\n },\n close,\n dispose() {\n disposed = true;\n close();\n abort.abort();\n root?.remove();\n root = null;\n },\n };\n}\n","/**\n * Drag-Drop Protocol — shared utilities for cross-grid row drag-and-drop.\n *\n * Used by `RowDragDropPlugin` (and any future plugin that participates in the\n * tbw row-drag protocol). Centralises:\n *\n * - MIME type constants and zone tagging (visible in `dataTransfer.types`)\n * - Payload codec for cross-window / cross-iframe transfers\n * - Drop-position math (midpoint algorithm)\n * - Auto-scroll engine (rAF loop, edge detection, boundary clamp)\n * - Plain-text TSV fallback for drags out to Notepad / Excel / Slack\n *\n * @internal Plugin shared utility (not part of the public API).\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n// ---------------------------------------------------------------------------\n// Same-window current-session tracker\n// ---------------------------------------------------------------------------\n\n/**\n * Module-level \"active drag session\" — set on dragstart by the source grid,\n * cleared on dragend. Lets target grids read the full payload synchronously\n * during `dragover` (browsers hide `dataTransfer.getData()` during dragover\n * for security). Cross-window drags don't have access to this module and\n * fall back to a generic accept-check followed by a drop-time canDrop call.\n *\n * @internal\n */\nlet currentSession: { sessionId: string; payload: RowDragPayload<unknown> } | null = null;\n\n/** @internal */\nexport function setCurrentDragSession<T>(sessionId: string, payload: RowDragPayload<T>): void {\n currentSession = { sessionId, payload: payload as RowDragPayload<unknown> };\n}\n\n/** @internal */\nexport function getCurrentDragSession<T = unknown>(): { sessionId: string; payload: RowDragPayload<T> } | null {\n return currentSession as { sessionId: string; payload: RowDragPayload<T> } | null;\n}\n\n/** @internal */\nexport function clearCurrentDragSession(): void {\n currentSession = null;\n}\n\n// ---------------------------------------------------------------------------\n// MIME types\n// ---------------------------------------------------------------------------\n\n/** Base MIME type carried on `dataTransfer` for tbw row drags. */\nexport const TBW_ROW_DRAG_MIME = 'application/x-tbw-grid-rows+json';\n\n/** Build a zone-tagged MIME type, visible in `dataTransfer.types` during dragover. */\nexport function mimeForZone(zone: string): string {\n return `${TBW_ROW_DRAG_MIME};zone=${encodeURIComponent(zone)}`;\n}\n\n/** Extract the zone from a tagged MIME, or `null` if untagged. */\nexport function parseZoneFromMime(mime: string): string | null {\n const match = mime.match(/^application\\/x-tbw-grid-rows\\+json;zone=(.*)$/);\n if (!match) return null;\n try {\n return decodeURIComponent(match[1]);\n } catch {\n return match[1];\n }\n}\n\n/**\n * Find the first MIME entry in `types` whose zone matches `zone`.\n * Used during `dragover` for the accept-check (browsers hide `getData()`\n * during dragover, but `types` is always visible).\n */\nexport function findMatchingZoneMime(types: readonly string[], zone: string): string | null {\n for (const t of types) {\n if (parseZoneFromMime(t) === zone) return t;\n }\n return null;\n}\n\n/** Returns true if any tbw row-drag MIME is present in `types`. */\nexport function hasAnyRowDragMime(types: readonly string[]): boolean {\n for (const t of types) {\n if (t === TBW_ROW_DRAG_MIME || t.startsWith(`${TBW_ROW_DRAG_MIME};`)) return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Payload\n// ---------------------------------------------------------------------------\n\n/**\n * Cross-grid drag payload, carried on `dataTransfer` and (for same-window\n * recovery) keyed in the WeakRef registry by `sessionId`.\n *\n * Re-exported as public API from `plugins/row-drag-drop`.\n * @since 2.4.0\n */\nexport interface RowDragPayload<T = unknown> {\n /** Drag session id (matches the WeakRef registry key). */\n sessionId: string;\n /** Source grid id (`grid.id` or auto-generated UUID). */\n sourceGridId: string;\n /** Drop zone the source grid is participating in. */\n dropZone: string;\n /** Serialized row payload (JSON-safe). For same-window drops, recovered live via the registry. */\n rows: T[];\n /** Original indices in the source grid's `_rows` array. */\n rowIndices: number[];\n /** Move (default) removes from source; copy leaves source intact. */\n operation: 'move' | 'copy';\n}\n\n/** Encode a payload to the JSON string written to `dataTransfer`. */\nexport function encodePayload<T>(payload: RowDragPayload<T>): string {\n return JSON.stringify(payload);\n}\n\n/** Decode a payload from the JSON string read off `dataTransfer`. */\nexport function decodePayload<T = unknown>(raw: string): RowDragPayload<T> | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as RowDragPayload<T>;\n if (\n typeof parsed?.sessionId !== 'string' ||\n typeof parsed?.sourceGridId !== 'string' ||\n typeof parsed?.dropZone !== 'string' ||\n !Array.isArray(parsed?.rows) ||\n !Array.isArray(parsed?.rowIndices) ||\n (parsed.operation !== 'move' && parsed.operation !== 'copy')\n ) {\n return null;\n }\n // `rowIndices` is later used for sorting and splicing — reject anything\n // that isn't a finite, non-negative integer to avoid JS coercion bugs\n // (NaN ordering, float splice indices, string concatenation, etc.).\n for (const idx of parsed.rowIndices) {\n if (typeof idx !== 'number' || !Number.isInteger(idx) || idx < 0) return null;\n }\n return parsed;\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Drop position math\n// ---------------------------------------------------------------------------\n\nexport interface DropPosition {\n /** Target visual row index that the cursor is over, or `null` for empty area. */\n overIndex: number | null;\n /** Final insertion index in the target grid's `_rows`. */\n insertIndex: number;\n /** True when the cursor is above the row midpoint. */\n isBefore: boolean;\n}\n\n/**\n * Compute the insertion index for a drop, given the row element under the\n * cursor (if any), the cursor's clientY, and the total row count when the\n * cursor is in empty space below the last row.\n *\n * - Cursor above row midpoint → insert at `targetIndex`\n * - Cursor below row midpoint → insert at `targetIndex + 1`\n * - Cursor over empty area below the last row → append (`insertIndex = totalRows`)\n */\nexport function computeDropPosition(\n rowEl: HTMLElement | null,\n clientY: number,\n rowIndexResolver: (el: HTMLElement) => number,\n totalRows: number,\n): DropPosition {\n if (!rowEl) {\n return { overIndex: null, insertIndex: totalRows, isBefore: false };\n }\n const targetIndex = rowIndexResolver(rowEl);\n if (targetIndex < 0) {\n return { overIndex: null, insertIndex: totalRows, isBefore: false };\n }\n const rect = rowEl.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n const isBefore = clientY < midY;\n return {\n overIndex: targetIndex,\n insertIndex: isBefore ? targetIndex : targetIndex + 1,\n isBefore,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Auto-scroll engine\n// ---------------------------------------------------------------------------\n\nexport interface AutoScrollOptions {\n /** Pixels from the top/bottom edge that activate auto-scroll. */\n edgeSize?: number;\n /** Base scroll speed in pixels-per-frame near the edge. */\n speed?: number;\n /** Max speed at the very edge (linear ramp from `speed` → `maxSpeed`). */\n maxSpeed?: number;\n}\n\nexport interface AutoScroller {\n /** Update pointer position; starts the rAF loop if needed. */\n onPointerMove(clientY: number): void;\n /** Stop the rAF loop and reset state. */\n stop(): void;\n /** True when actively auto-scrolling. */\n readonly isScrolling: boolean;\n}\n\n/**\n * Create an auto-scroller for a viewport element.\n *\n * The scroller runs a `requestAnimationFrame` loop only while the pointer is\n * within `edgeSize` of the viewport's top or bottom and there is room to\n * scroll. Speed ramps linearly from `speed` (at the edge boundary) to\n * `maxSpeed` (at the very edge).\n */\nexport function createAutoScroller(\n viewport: HTMLElement,\n options: AutoScrollOptions = {},\n onScrollChange?: (active: boolean) => void,\n): AutoScroller {\n const edgeSize = options.edgeSize ?? 60;\n const baseSpeed = options.speed ?? 8;\n const maxSpeed = options.maxSpeed ?? 24;\n\n let rafId: number | null = null;\n let pointerY: number | null = null;\n let active = false;\n\n const setActive = (next: boolean): void => {\n if (next === active) return;\n active = next;\n onScrollChange?.(next);\n };\n\n const tick = (): void => {\n rafId = null;\n if (pointerY === null) {\n setActive(false);\n return;\n }\n const rect = viewport.getBoundingClientRect();\n let delta = 0;\n\n if (pointerY < rect.top + edgeSize) {\n const distance = Math.max(0, pointerY - rect.top);\n const ratio = 1 - distance / edgeSize;\n delta = -Math.round(baseSpeed + (maxSpeed - baseSpeed) * ratio);\n } else if (pointerY > rect.bottom - edgeSize) {\n const distance = Math.max(0, rect.bottom - pointerY);\n const ratio = 1 - distance / edgeSize;\n delta = Math.round(baseSpeed + (maxSpeed - baseSpeed) * ratio);\n }\n\n if (delta === 0) {\n setActive(false);\n return;\n }\n\n const before = viewport.scrollTop;\n viewport.scrollTop = before + delta;\n\n // Stop if we hit a boundary (no movement happened)\n if (viewport.scrollTop === before) {\n setActive(false);\n return;\n }\n\n setActive(true);\n rafId = requestAnimationFrame(tick);\n };\n\n return {\n onPointerMove(clientY: number): void {\n pointerY = clientY;\n if (rafId === null) {\n rafId = requestAnimationFrame(tick);\n }\n },\n stop(): void {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n pointerY = null;\n setActive(false);\n },\n get isScrolling(): boolean {\n return active;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// TSV fallback\n// ---------------------------------------------------------------------------\n\n/**\n * Format rows as a TSV string for the `text/plain` drag fallback.\n * Used when the drop target is an external app (Excel, Notepad, Slack).\n *\n * - One row per line, fields separated by `\\t`.\n * - Tabs and newlines in cell values are replaced with spaces.\n * - Skips columns flagged as `utility` (drag handle, checkbox, etc.).\n */\nexport function formatRowsAsTSV<T extends Record<string, unknown>>(\n rows: readonly T[],\n columns: readonly ColumnConfig[],\n): string {\n const dataColumns = columns.filter(\n (c) => !(c as { utility?: boolean }).utility && typeof c.field === 'string' && c.field !== '',\n );\n const lines: string[] = [];\n for (const row of rows) {\n const cells = dataColumns.map((col) => {\n const value = row[col.field];\n if (value === null || value === undefined) return '';\n const str = String(value);\n return str.replace(/[\\t\\r\\n]+/g, ' ');\n });\n lines.push(cells.join('\\t'));\n }\n return lines.join('\\n');\n}\n","/**\n * Row Drag-Drop Plugin CSS\n *\n * Styles for drag handle, drop indicators (intra- and cross-grid), grid-level\n * drag/drop state classes, and FLIP animation on landing rows.\n */\n\n@layer tbw-plugins {\n /* ------------------------------------------------------------------ */\n /* Drag handle column */\n /* ------------------------------------------------------------------ */\n [data-field='__tbw_row_drag'] {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .dg-row-drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 1em;\n min-height: 1em;\n cursor: grab;\n user-select: none;\n color: var(--tbw-row-reorder-handle-color, var(--tbw-color-fg-muted));\n transition: color var(--tbw-transition-duration, 120ms) var(--tbw-transition-ease, ease);\n font-size: var(--tbw-font-size, 1em);\n letter-spacing: -2px;\n\n /* SC 2.5.8: grow the target, not the box, so the row keeps its height. */\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset-block-start: 50%;\n inset-inline-start: 50%;\n translate: -50% -50%;\n inline-size: var(--tbw-touch-target-min, 24px);\n block-size: var(--tbw-touch-target-min, 24px);\n }\n\n &:hover {\n color: var(--tbw-row-reorder-handle-hover, var(--tbw-color-fg));\n }\n\n &:active {\n cursor: grabbing;\n }\n }\n\n /* Coarse pointer (touch): handle always in active colour and large enough to tap */\n @media (hover: none) {\n .dg-row-drag-handle {\n color: var(--tbw-row-reorder-handle-hover, var(--tbw-color-fg));\n }\n }\n\n @media (pointer: coarse) {\n .dg-row-drag-handle {\n min-width: var(--tbw-touch-target-min, 24px);\n min-height: var(--tbw-touch-target-min, 24px);\n }\n }\n\n /* ------------------------------------------------------------------ */\n /* Source row state */\n /* ------------------------------------------------------------------ */\n .data-grid-row.dragging {\n opacity: 0.6;\n }\n\n /* When the row itself is the drag handle, hint at the affordance. */\n .data-grid-row[draggable='true'] {\n cursor: grab;\n }\n\n .data-grid-row[draggable='true'].dragging {\n cursor: grabbing;\n }\n\n /* Off-screen full-row clone used as the HTML5 drag image. */\n /* The browser already applies its own translucency to the snapshot, */\n /* so this clone keeps full opacity and skips the box-shadow — both */\n /* would otherwise composite on top of the browser's effect and make */\n /* the drag image look washed-out / fade at the edges. */\n .tbw-row-drag-clone {\n position: fixed;\n top: -10000px;\n left: -10000px;\n pointer-events: none;\n }\n\n /* Multi-row drag count badge */\n .tbw-row-drag-count {\n position: fixed;\n top: -1000px; /* off-screen until the browser snapshots it */\n left: -1000px;\n pointer-events: none;\n background: var(--tbw-row-reorder-indicator, var(--tbw-color-accent));\n color: var(--tbw-color-accent-fg);\n font:\n 600 12px/1.4 system-ui,\n sans-serif;\n border-radius: 999px;\n padding: 2px 8px;\n box-shadow: 0 2px 6px var(--tbw-color-shadow);\n z-index: 9999;\n }\n\n /* ------------------------------------------------------------------ */\n /* Drop position indicator (rows under cursor) */\n /* ------------------------------------------------------------------ */\n .data-grid-row.drop-target {\n position: relative;\n\n &.drop-before::before,\n &.drop-after::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n height: var(--tbw-drop-indicator-width, 2px);\n background-color: var(--tbw-drop-indicator-color, var(--tbw-row-reorder-indicator, var(--tbw-color-accent)));\n z-index: 10;\n pointer-events: none;\n }\n\n &.drop-before::before {\n top: 0;\n }\n\n &.drop-after::after {\n bottom: 0;\n }\n }\n\n /* ------------------------------------------------------------------ */\n /* Grid-level state classes */\n /* ------------------------------------------------------------------ */\n\n /* Source grid while a drag originating here is in progress */\n .tbw-grid--drag-source {\n /* Reserved hook for consumer styling. */\n }\n\n /* Target grid while a valid drag hovers (matching dropZone, canDrop ok) */\n .tbw-grid--drop-target-active {\n outline: 2px dashed var(--tbw-drop-indicator-color, var(--tbw-color-accent));\n outline-offset: -2px;\n }\n\n /* Briefly applied when a drop attempt is rejected */\n .tbw-grid--drop-target-rejected {\n outline: 2px dashed var(--tbw-color-danger);\n outline-offset: -2px;\n }\n\n /* Applied while the auto-scroll engine is actively scrolling this grid */\n .tbw-grid--auto-scrolling {\n cursor: ns-resize;\n }\n\n /* ------------------------------------------------------------------ */\n /* FLIP animation on landing rows */\n /* ------------------------------------------------------------------ */\n .data-grid-row.flip-animating {\n transition: transform var(--tbw-animation-duration, 200ms) ease-out;\n will-change: transform;\n z-index: 1;\n }\n\n /* ------------------------------------------------------------------ */\n /* Click-only move menu (WCAG 2.2 SC 2.5.7) */\n /* ------------------------------------------------------------------ */\n /* Rendered into `document.body` and promoted to the top layer, so it sits\n outside the grid's custom-property scope — every var needs a literal\n fallback here, the same way the tooltip popover does it. */\n .tbw-row-move-menu {\n position: fixed;\n margin: 0;\n display: flex;\n flex-direction: column;\n min-width: 10rem;\n padding: var(--tbw-spacing-xs, 0.25rem);\n background: var(--tbw-color-panel-bg, light-dark(#fff, #1f1f1f));\n color: var(--tbw-color-fg, light-dark(#1a1a1a, #f0f0f0));\n border: 1px solid var(--tbw-color-border, light-dark(#d0d0d0, #3a3a3a));\n border-radius: var(--tbw-border-radius, 0.25rem);\n box-shadow: var(--tbw-shadow-md, 0 4px 12px rgb(0 0 0 / 25%));\n font: inherit;\n z-index: 10001;\n\n &[hidden] {\n display: none;\n }\n }\n\n .tbw-row-move-menu-item {\n min-height: var(--tbw-touch-target-min, 24px);\n padding: var(--tbw-spacing-xs, 0.25rem) var(--tbw-spacing-sm, 0.5rem);\n text-align: start;\n background: none;\n border: none;\n /* Themes set radii for much larger surfaces (Material uses 12px), which\n turns a 24px row into a lozenge. */\n border-radius: min(var(--tbw-border-radius, 0.25rem), 6px);\n color: inherit;\n font: inherit;\n cursor: pointer;\n\n &:hover:not(:disabled) {\n background: var(--tbw-color-row-hover, light-dark(#f0f0f0, #2a2a2a));\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: default;\n }\n }\n}\n","/**\n * Row Drag-Drop Plugin\n *\n * Drag rows within a single grid **and** between grids that share a\n * `dropZone`.\n *\n * Architecture overview is documented in the issue body and in\n * `.github/knowledge/grid-plugins.md`. Key invariants:\n *\n * - Mutations write to `grid._rows`; the user's input `sourceRows` array is\n * never mutated on either side. Persistence is consumer-driven via the\n * `row-move` (intra-grid) and `row-transfer` (cross-grid) events.\n * - Same-window cross-grid drops use the WeakRef registry in\n * `core/internal/drag-drop-registry` to recover live row references.\n * Cross-window drops fall back to `JSON.parse(JSON.stringify(row))` (or the\n * `serializeRow`/`deserializeRow` hooks for non-JSON values).\n * - Duplicate instances of this plugin collapse via the PluginManager\n * alias-collapse pre-pass, which merges their configs.\n *\n * @see {@link RowDragDropConfig} for all configuration options.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport {\n clearDragSession,\n lookupDragSession,\n newDragSessionId,\n registerDragSession,\n} from '../../core/internal/drag-drop-registry';\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin, type GridElement, type PluginManifest } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridHost } from '../../core/types';\nimport {\n type DragAlternativeAction,\n type DragAlternativeMenu,\n createDragAlternativeMenu,\n} from '../shared/drag-alternative-menu';\nimport {\n type AutoScroller,\n type RowDragPayload,\n TBW_ROW_DRAG_MIME,\n clearCurrentDragSession,\n computeDropPosition,\n createAutoScroller,\n decodePayload,\n encodePayload,\n findMatchingZoneMime,\n formatRowsAsTSV,\n getCurrentDragSession,\n hasAnyRowDragMime,\n mimeForZone,\n setCurrentDragSession,\n} from '../shared/drag-drop-protocol';\nimport styles from './row-drag-drop.css?inline';\nimport type {\n PendingMove,\n RowDragDropConfig,\n RowDragEndDetail,\n RowDragStartDetail,\n RowDropDetail,\n RowMoveDetail,\n RowTransferDetail,\n} from './types';\n\n/**\n * Field name for the drag handle column.\n *\n * @since 2.4.0\n */\nexport const ROW_DRAG_HANDLE_FIELD = '__tbw_row_drag';\n\n// ---------------------------------------------------------------------------\n// Cross-window coordination via BroadcastChannel\n// ---------------------------------------------------------------------------\n//\n// Same-window cross-grid drops can locate the source grid and its plugin via\n// `document.getElementById(sourceGridId)` — the target plugin handles both\n// sides of the transfer in `onDrop` (insert into target + remove from source\n// + emit `row-transfer` on both grids).\n//\n// Cross-window drops can't reach the source DOM that way. The target window\n// broadcasts a `tbw-row-drag-drop:transfer` message; each window has a single\n// channel and fans the message out to every attached plugin instance, which\n// matches on `sourceGridId === this.gridId` and runs the source-side path\n// (row removal on `move`, `row-transfer` emit, `dragAccepted` flip).\n//\n// The channel is created lazily on the first plugin that needs it and shared\n// across all instances in the window. It's torn down when the last instance\n// detaches so we don't leak a handle in environments where the plugin is\n// repeatedly attached/detached.\n\n/** @internal */\ninterface RemoteTransferMessage {\n type: 'tbw-row-drag-drop:transfer';\n sessionId: string;\n sourceGridId: string;\n toGridId: string;\n dropZone: string;\n rowIndices: number[];\n toIndex: number;\n operation: 'move' | 'copy';\n /** Pre-serialized rows (the same shape `serializeRow` produced on dragstart). */\n serializedRows: unknown[];\n}\n\ntype RemoteTransferListener = (msg: RemoteTransferMessage) => void;\n\nconst CROSS_WINDOW_CHANNEL_NAME = 'tbw-row-drag-drop';\nlet sharedChannel: BroadcastChannel | null = null;\nconst remoteListeners = new Set<RemoteTransferListener>();\n\nfunction ensureCrossWindowChannel(): BroadcastChannel | null {\n if (typeof BroadcastChannel === 'undefined') return null;\n if (sharedChannel) return sharedChannel;\n try {\n sharedChannel = new BroadcastChannel(CROSS_WINDOW_CHANNEL_NAME);\n sharedChannel.addEventListener('message', (event: MessageEvent) => {\n const data = event.data as RemoteTransferMessage | undefined;\n if (!data || data.type !== 'tbw-row-drag-drop:transfer') return;\n // Snapshot to tolerate listeners that detach during dispatch.\n for (const listener of Array.from(remoteListeners)) {\n try {\n listener(data);\n } catch {\n /* listener errors must not break sibling listeners */\n }\n }\n });\n } catch {\n sharedChannel = null;\n }\n return sharedChannel;\n}\n\nfunction registerRemoteTransferListener(listener: RemoteTransferListener): void {\n remoteListeners.add(listener);\n ensureCrossWindowChannel();\n}\n\n/**\n * Every plugin instance attached in this window.\n *\n * Drag-and-drop discovers its target from the pointer position, so it never\n * needed a registry. The click-only transfer control (WCAG 2.2 SC 2.5.7) does:\n * it has to *list* the grids a row can be sent to before the user picks one.\n *\n * Typed as `unknown` because the class is generic over the row type and every\n * instance in the window may have a different one; `peersInDropZone()` narrows.\n */\nconst attachedPlugins = new Set<unknown>();\n\nfunction unregisterRemoteTransferListener(listener: RemoteTransferListener): void {\n remoteListeners.delete(listener);\n if (remoteListeners.size === 0 && sharedChannel) {\n try {\n sharedChannel.close();\n } catch {\n /* noop */\n }\n sharedChannel = null;\n }\n}\n\nfunction broadcastRemoteTransfer(msg: RemoteTransferMessage): void {\n const channel = ensureCrossWindowChannel();\n if (!channel) return;\n try {\n channel.postMessage(msg);\n } catch {\n /* postMessage may throw on uncloneable payloads — acceptable; user can\n * supply `serializeRow` to provide a structured-cloneable shape. */\n }\n}\n\n/**\n * Row Drag-Drop Plugin for `<tbw-grid>`.\n *\n * @example Intra-grid reordering\n * ```ts\n * import { RowDragDropPlugin } from '@toolbox-web/grid/plugins/row-drag-drop';\n *\n * grid.gridConfig = {\n * plugins: [new RowDragDropPlugin()],\n * };\n * ```\n *\n * @example Cross-grid transfer list\n * ```ts\n * gridA.gridConfig = { plugins: [new RowDragDropPlugin({ dropZone: 'tasks' })] };\n * gridB.gridConfig = { plugins: [new RowDragDropPlugin({ dropZone: 'tasks' })] };\n *\n * gridA.addEventListener('row-transfer', (e) => persist(e.detail));\n * gridB.addEventListener('row-transfer', (e) => persist(e.detail));\n * ```\n *\n * @category Plugin\n */\nexport class RowDragDropPlugin<T = unknown> extends BaseGridPlugin<RowDragDropConfig<T>> {\n /** @internal */\n readonly name = 'rowDragDrop';\n\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n static override readonly manifest: PluginManifest<RowDragDropConfig> = {\n events: [\n { type: 'row-move', description: 'Intra-grid row reorder.', cancelable: true },\n { type: 'row-drag-start', description: 'Cross-grid drag started on this grid.', cancelable: true },\n { type: 'row-drag-end', description: 'Drag finished on this grid (regardless of outcome).' },\n { type: 'row-drop', description: 'Cross-grid drop landing on this grid.', cancelable: true },\n { type: 'row-transfer', description: 'Cross-grid transfer completed (fires on both grids).' },\n ],\n };\n\n /** @internal */\n protected override get defaultConfig(): Partial<RowDragDropConfig<T>> {\n return {\n enableKeyboard: true,\n // `showDragHandle` default depends on `dragFrom` — resolved by\n // `shouldRenderDragHandle()` rather than here so a single `dragFrom`\n // change reshapes both at once.\n dragHandlePosition: 'left',\n dragHandleWidth: 40,\n debounceMs: 150,\n animation: 'flip',\n operation: 'move',\n autoScroll: true,\n dragFrom: 'handle',\n };\n }\n\n /**\n * Resolve whether the grip column should be rendered. The handle is shown\n * unless the user explicitly set `showDragHandle: false`, OR `dragFrom`\n * is `'row'` and `showDragHandle` was not explicitly set to `true`.\n */\n private shouldRenderDragHandle(): boolean {\n const explicit = this.config.showDragHandle;\n if (explicit === false) return false;\n if (explicit === true) return true;\n return this.config.dragFrom !== 'row';\n }\n\n /** Whether the row element itself should accept native HTML5 drag. */\n private get rowIsDraggable(): boolean {\n return this.config.dragFrom === 'row' || this.config.dragFrom === 'both';\n }\n\n /** Resolve animation type from plugin config (respects grid-level reduced-motion). */\n private get animationType(): false | 'flip' {\n if (!this.isAnimationEnabled) return false;\n if (this.config.animation !== undefined) return this.config.animation;\n return 'flip';\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedRowIndex: number | null = null;\n private draggedRows: T[] = [];\n private draggedIndices: number[] = [];\n private dragSessionId: string | null = null;\n private dragAccepted = false;\n private dropRowIndex: number | null = null;\n private pendingMove: PendingMove | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private lastFocusCol = 0;\n private autoScroller: AutoScroller | null = null;\n /** Stable id for this grid instance (used as `sourceGridId` in payloads). */\n private gridId = '';\n\n /** Bound listener so we can register/unregister the same reference. */\n private readonly remoteTransferListener: RemoteTransferListener = (msg) => this.onRemoteTransfer(msg);\n\n /** Click-only move menu (WCAG 2.2 SC 2.5.7), created lazily on first tap. */\n private moveMenu: DragAlternativeMenu | null = null;\n\n /** Typed internal grid accessor. */\n private get internalGrid(): GridHost {\n return this.grid;\n }\n // #endregion\n\n // #region Lifecycle\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n const host = this.gridElement;\n if (host) {\n this.gridId = host.id || `tbw-grid-${newDragSessionId().slice(0, 8)}`;\n if (!host.id) host.id = this.gridId;\n this.setupDelegatedDragListeners();\n registerRemoteTransferListener(this.remoteTransferListener);\n attachedPlugins.add(this);\n }\n }\n\n /** @internal */\n override detach(): void {\n this.clearDebounceTimer();\n this.autoScroller?.stop();\n this.autoScroller = null;\n unregisterRemoteTransferListener(this.remoteTransferListener);\n attachedPlugins.delete(this);\n this.moveMenu?.dispose();\n this.moveMenu = null;\n if (this.dragSessionId) clearDragSession(this.dragSessionId);\n clearCurrentDragSession();\n this.resetDragState();\n super.detach();\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.shouldRenderDragHandle()) return [...columns];\n\n const dragHandleColumn: ColumnConfig = {\n field: ROW_DRAG_HANDLE_FIELD,\n header: '',\n width: this.config.dragHandleWidth ?? 40,\n resizable: false,\n sortable: false,\n filterable: false,\n lockPosition: true,\n utility: true,\n viewRenderer: () => {\n const container = document.createElement('div');\n container.className = 'dg-row-drag-handle';\n container.setAttribute('aria-label', 'Drag to reorder, or activate for move options');\n container.setAttribute('role', 'button');\n container.setAttribute('tabindex', '-1');\n container.draggable = true;\n // Press-and-release without moving is a tap, not a drag — HTML5 DnD\n // only fires `dragstart` on a real drag, so a plain `click` here means\n // the pointer user could not (or chose not to) drag. Offer the\n // click-only alternative instead (WCAG 2.2 SC 2.5.7).\n container.addEventListener('click', (e) => {\n e.stopPropagation();\n this.openMoveMenu(container);\n });\n this.setIcon(container, 'dragHandle');\n return container;\n },\n };\n\n return this.config.dragHandlePosition === 'right' ? [...columns, dragHandleColumn] : [dragHandleColumn, ...columns];\n }\n\n /** @internal */\n override afterRender(): void {\n this.applyRowDraggable();\n }\n\n /** @internal */\n override onScrollRender(): void {\n // Virtualization recycles row DOM elements during scroll; re-apply the\n // `draggable` attribute so newly-shown rows still accept HTML5 drag.\n this.applyRowDraggable();\n }\n\n /**\n * Set or clear the `draggable` attribute on every visible row, depending on\n * `config.dragFrom`. Idempotent and cheap (one attribute write per row).\n */\n private applyRowDraggable(): void {\n const body = this.internalGrid._bodyEl;\n if (!body) return;\n const wantDraggable = this.rowIsDraggable;\n const rows = body.querySelectorAll<HTMLElement>('.data-grid-row');\n for (const row of rows) {\n if (wantDraggable) {\n if (row.getAttribute('draggable') !== 'true') row.setAttribute('draggable', 'true');\n } else if (row.hasAttribute('draggable')) {\n row.removeAttribute('draggable');\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!this.config.enableKeyboard) return;\n // Ctrl is deprecated (macOS reserves Ctrl+↑/↓); remove in the next major.\n if (!(event.altKey || event.ctrlKey) || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')) return;\n if (this.#isKeyboardMoveBlocked(event)) return;\n\n const grid = this.internalGrid;\n const focusRow = grid._focusRow;\n const rows = grid._rows ?? this.sourceRows;\n if (focusRow < 0 || focusRow >= rows.length) return;\n\n const direction = event.key === 'ArrowUp' ? 'up' : 'down';\n const toIndex = direction === 'up' ? focusRow - 1 : focusRow + 1;\n if (toIndex < 0 || toIndex >= rows.length) return;\n\n const row = rows[focusRow];\n if (!this.canMoveRow(focusRow, toIndex)) return;\n\n this.handleKeyboardMove(row, focusRow, toIndex, grid._focusCol);\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n\n /** Editors, form controls and non-body chrome own the chord (e.g. `<select>` Alt+↓ opens it). */\n #isKeyboardMoveBlocked(event: KeyboardEvent): boolean {\n if (this.queryBoolean('isEditing')) return true;\n const target = (event.composedPath?.()[0] ?? event.target) as HTMLElement | null;\n if (!target?.closest || target === this.gridElement) return false;\n if (!target.closest('.rows-body')) return true;\n const tag = target.tagName;\n return tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA' || target.isContentEditable;\n }\n\n /** @internal */\n override onCellClick(): void {\n this.flushPendingMove();\n }\n // #endregion\n\n // #region Public API\n /** Move a row to a new position programmatically (intra-grid). */\n moveRow(fromIndex: number, toIndex: number): void {\n const rows = [...this.sourceRows];\n if (fromIndex < 0 || fromIndex >= rows.length) return;\n if (toIndex < 0 || toIndex >= rows.length) return;\n if (fromIndex === toIndex) return;\n if (!this.canMoveRow(fromIndex, toIndex)) return;\n this.executeIntraGridMove(rows[fromIndex], fromIndex, toIndex, 'keyboard');\n }\n\n /**\n * Check if a row can be moved within this grid.\n * Consults the `canDrag` veto for the source row, the plugin query system\n * (`canMoveRow`), and `canDrop` for the target.\n */\n canMoveRow(fromIndex: number, toIndex: number): boolean {\n // During debounced keyboard moves, `grid._rows` diverges from\n // `sourceRows` (the user-facing snapshot) because the plugin mutates\n // resolves the focused row from `_rows ?? sourceRows`, so validation must\n // read from the same array — otherwise we'd run `canDrag`/queries against\n // the wrong row.\n const rows = this.internalGrid._rows ?? this.sourceRows;\n if (fromIndex < 0 || fromIndex >= rows.length) return false;\n if (toIndex < 0 || toIndex >= rows.length) return false;\n if (fromIndex === toIndex) return false;\n\n // Plugin query veto (Tree, GroupingRows)\n const row = rows[fromIndex] as T;\n const queryResults = this.grid?.query?.<boolean>('canMoveRow', row);\n if (Array.isArray(queryResults) && queryResults.includes(false)) return false;\n\n // canDrag veto (dragstart side)\n if (this.config.canDrag && !this.config.canDrag(row, fromIndex)) return false;\n\n // canDrop callback (intra-grid synthesised payload)\n if (this.config.canDrop) {\n const payload: RowDragPayload<T> = {\n sessionId: 'intra',\n sourceGridId: this.gridId,\n dropZone: this.config.dropZone ?? '',\n rows: [row],\n rowIndices: [fromIndex],\n operation: 'move',\n };\n if (!this.config.canDrop(payload, toIndex)) return false;\n }\n\n return true;\n }\n // #endregion\n\n // #region Drag Setup\n\n private setupDelegatedDragListeners(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n const signal = this.disconnectSignal;\n\n gridEl.addEventListener('dragstart', (e) => this.onDragStart(e as DragEvent), { signal });\n gridEl.addEventListener('dragend', () => this.onDragEnd(), { signal });\n gridEl.addEventListener('dragover', (e) => this.onDragOver(e as DragEvent), { signal });\n gridEl.addEventListener('dragleave', (e) => this.onDragLeave(e as DragEvent), { signal });\n gridEl.addEventListener('drop', (e) => this.onDrop(e as DragEvent), { signal });\n }\n\n private onDragStart(de: DragEvent): void {\n const target = de.target as HTMLElement | null;\n if (!target) return;\n\n // Resolve the row element being picked up. Order matters: a click on the\n // grip column inside a row-draggable grid should still go through the\n // handle path so the cursor offset feels right.\n const handle = target.closest('.dg-row-drag-handle') as HTMLElement | null;\n let rowEl: HTMLElement | null = null;\n let initiatedFromHandle = false;\n if (handle) {\n rowEl = handle.closest('.data-grid-row') as HTMLElement | null;\n initiatedFromHandle = true;\n } else if (this.rowIsDraggable) {\n // Row-as-handle: any cell may start the drag, but interactive\n // descendants (inputs, buttons, anchors, contenteditable, open\n // editors, selection checkboxes) keep their native behaviour.\n if (this.isInteractiveDragOrigin(target)) return;\n rowEl = target.closest('.data-grid-row') as HTMLElement | null;\n }\n if (!rowEl) return;\n\n const rowIndex = this.getRowIndex(rowEl);\n if (rowIndex < 0) return;\n\n // Resolve the rows being dragged: whole selection if dragged row is selected.\n const { rows, indices } = this.resolveDraggedRows(rowIndex);\n if (rows.length === 0) return;\n\n // canDrag veto on the originating row\n if (this.config.canDrag && !this.config.canDrag(rows[0], rowIndex)) {\n de.preventDefault();\n return;\n }\n\n const operation = this.config.operation ?? 'move';\n const dropZone = this.config.dropZone ?? '';\n const sessionId = newDragSessionId();\n\n // Emit cancelable row-drag-start (source-side veto)\n const startDetail: RowDragStartDetail<T> = { rows, indices, operation, dropZone };\n if (this.emitCancelable('row-drag-start', startDetail)) {\n de.preventDefault();\n return;\n }\n\n this.isDragging = true;\n this.draggedRowIndex = rowIndex;\n this.draggedRows = rows;\n this.draggedIndices = indices;\n this.dragSessionId = sessionId;\n this.dragAccepted = false;\n\n // Build the cross-grid payload (always built, even when dropZone is empty —\n // intra-grid drops still benefit from the registry round-trip)\n const serialize = this.config.serializeRow ?? ((r: T) => r);\n const payload: RowDragPayload<T> = {\n sessionId,\n sourceGridId: this.gridId,\n dropZone,\n rows: rows.map(serialize) as T[],\n rowIndices: indices,\n operation,\n };\n\n if (de.dataTransfer) {\n de.dataTransfer.effectAllowed = operation === 'copy' ? 'copyMove' : 'move';\n try {\n de.dataTransfer.setData(TBW_ROW_DRAG_MIME, encodePayload(payload));\n if (dropZone) de.dataTransfer.setData(mimeForZone(dropZone), encodePayload(payload));\n // Plain-text TSV fallback for external drop targets\n de.dataTransfer.setData('text/plain', formatRowsAsTSV(rows as Record<string, unknown>[], this.columns));\n } catch {\n /* JSDOM/happy-dom may throw on setData; harmless */\n }\n\n // Drag image: full-row clone for single-row drags (more legible than\n // the handle alone); count badge for multi-row drags.\n if (rows.length > 1) {\n const badge = document.createElement('div');\n badge.className = 'tbw-row-drag-count';\n badge.textContent = `${rows.length} rows`;\n document.body.appendChild(badge);\n try {\n de.dataTransfer.setDragImage(badge, 10, 10);\n } catch {\n /* ignore */\n }\n setTimeout(() => badge.remove(), 0);\n } else {\n this.attachRowCloneDragImage(de, rowEl, initiatedFromHandle ? handle : null);\n }\n }\n\n // Same-window registry + current-session marker\n registerDragSession(sessionId, rows as unknown[]);\n setCurrentDragSession(sessionId, payload);\n\n rowEl.classList.add(GridClasses.DRAGGING);\n this.gridElement.classList.add('tbw-grid--drag-source');\n }\n\n /**\n * Selectors whose dragstart should NOT initiate a row drag in `dragFrom: 'row'`\n * mode. Keeps native interactions (text input, button clicks, link drag,\n * cell editing, selection checkboxes) working unchanged.\n */\n private static readonly INTERACTIVE_DRAG_SELECTORS =\n 'input,textarea,select,button,a,[contenteditable=\"\"],[contenteditable=\"true\"],.dg-cell-editor,.tbw-checkbox-cell';\n\n /** @internal */\n private isInteractiveDragOrigin(target: HTMLElement): boolean {\n return target.closest(RowDragDropPlugin.INTERACTIVE_DRAG_SELECTORS) !== null;\n }\n\n /**\n * Build a full-row drag image by cloning `rowEl` so the user sees the\n * actual row — not just the grip icon — while dragging.\n *\n * The clone is appended off-screen, snapshotted by the browser via\n * `setDragImage`, then removed on the next tick (after the snapshot).\n * The cursor offset is preserved relative to where the user pressed.\n */\n private attachRowCloneDragImage(de: DragEvent, rowEl: HTMLElement, handle: HTMLElement | null): void {\n if (!de.dataTransfer) return;\n const rect = rowEl.getBoundingClientRect();\n const clone = rowEl.cloneNode(true) as HTMLElement;\n clone.classList.add('tbw-row-drag-clone');\n // Strip transient classes that would look wrong in the drag image.\n clone.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after', 'flip-animating', 'row-focus');\n clone.removeAttribute('aria-selected');\n // Preserve the actual rendered width so cells don't reflow in the snapshot.\n clone.style.width = `${rect.width}px`;\n clone.style.height = `${rect.height}px`;\n // The clone MUST stay inside the grid host: every core row/cell rule is\n // scoped under `tbw-grid …` (see core/styles/*.css), and the\n // `--tbw-column-template` custom property is set on the host. If the\n // clone is moved to `document.body`, none of those rules match and the\n // drag image collapses to an empty box. Off-screen positioning via\n // `position: fixed` works the same regardless of the DOM parent.\n this.gridElement.appendChild(clone);\n // Cursor offset: where the user pressed inside the source row. Falls\n // back to the centre of the handle when initiated from the grip.\n let offsetX = de.clientX - rect.left;\n let offsetY = de.clientY - rect.top;\n if (handle) {\n const handleRect = handle.getBoundingClientRect();\n offsetX = handleRect.left - rect.left + handleRect.width / 2;\n offsetY = handleRect.top - rect.top + handleRect.height / 2;\n }\n // Clamp into the row bounds so the cursor stays inside the drag image.\n offsetX = Math.max(0, Math.min(rect.width, offsetX));\n offsetY = Math.max(0, Math.min(rect.height, offsetY));\n try {\n de.dataTransfer.setDragImage(clone, offsetX, offsetY);\n } catch {\n /* JSDOM/happy-dom: harmless */\n }\n setTimeout(() => clone.remove(), 0);\n }\n\n private onDragOver(de: DragEvent): void {\n const dt = de.dataTransfer;\n if (!dt) return;\n\n // Identify whether a tbw row drag is in progress and whether it matches our zone.\n const types = dt.types ? Array.from(dt.types) : [];\n if (!hasAnyRowDragMime(types) && !this.isDragging) return;\n\n const dropZone = this.config.dropZone ?? '';\n const session = getCurrentDragSession<T>();\n\n // For cross-grid drags we require a matching zone-tagged MIME OR the\n // session payload's dropZone must match ours.\n const isIntra = this.isDragging && session?.payload.sourceGridId === this.gridId;\n if (!isIntra) {\n if (!dropZone) return; // intra-grid only — ignore external drags\n const matchingMime = findMatchingZoneMime(types, dropZone);\n if (!matchingMime && !(session && session.payload.dropZone === dropZone)) return;\n }\n\n de.preventDefault();\n if (dt) dt.dropEffect = (session?.payload.operation ?? this.config.operation ?? 'move') as 'copy' | 'move';\n\n // Compute drop position\n const rowEl = (de.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n const rows = this.internalGrid._rows ?? [];\n const pos = computeDropPosition(rowEl, de.clientY, (el) => this.getRowIndex(el), rows.length);\n\n // Same-row no-op for intra-grid\n if (isIntra && pos.overIndex !== null && pos.overIndex === this.draggedRowIndex) {\n this.clearDropTargetClasses();\n return;\n }\n\n // canDrop check (same-window only — payload visible)\n if (session && this.config.canDrop) {\n const accepted = this.config.canDrop(session.payload, pos.insertIndex);\n this.gridElement.classList.toggle('tbw-grid--drop-target-active', accepted);\n this.gridElement.classList.toggle('tbw-grid--drop-target-rejected', !accepted);\n if (!accepted) {\n this.clearDropTargetClasses();\n return;\n }\n } else {\n this.gridElement.classList.add('tbw-grid--drop-target-active');\n }\n\n this.dropRowIndex = pos.insertIndex;\n this.applyDropPositionClasses(rowEl, pos.isBefore);\n\n // Auto-scroll the target viewport\n if (this.config.autoScroll !== false) {\n this.ensureAutoScroller();\n this.autoScroller?.onPointerMove(de.clientY);\n }\n }\n\n private onDragLeave(de: DragEvent): void {\n const rowEl = (de.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n if (rowEl) rowEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n // Tear down grid-level state when the cursor leaves the grid entirely\n if (de.currentTarget && !this.gridElement.contains(de.relatedTarget as Node)) {\n this.gridElement.classList.remove('tbw-grid--drop-target-active', 'tbw-grid--drop-target-rejected');\n this.autoScroller?.stop();\n }\n }\n\n private onDrop(de: DragEvent): void {\n de.preventDefault();\n this.autoScroller?.stop();\n this.gridElement.classList.remove('tbw-grid--drop-target-active', 'tbw-grid--drop-target-rejected');\n this.clearDropTargetClasses();\n\n const dt = de.dataTransfer;\n if (!dt) return;\n\n // Resolve payload — prefer same-window session, fall back to dataTransfer JSON\n const session = getCurrentDragSession<T>();\n let payload: RowDragPayload<T> | null = session?.payload ?? null;\n let liveRows: T[] | null = null;\n\n if (payload) {\n const lookup = lookupDragSession<T>(payload.sessionId);\n if (lookup) liveRows = lookup;\n } else {\n const raw = dt.getData(TBW_ROW_DRAG_MIME);\n payload = decodePayload<T>(raw);\n if (payload) {\n const lookup = lookupDragSession<T>(payload.sessionId);\n if (lookup) liveRows = lookup;\n }\n }\n if (!payload) return;\n\n // Drop position (recompute in case dragover wasn't called for a few frames)\n const rowEl = (de.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n const rows = this.internalGrid._rows ?? [];\n const pos = computeDropPosition(rowEl, de.clientY, (el) => this.getRowIndex(el), rows.length);\n let targetIndex = this.dropRowIndex ?? pos.insertIndex;\n\n const isIntra = payload.sourceGridId === this.gridId;\n const dropZone = this.config.dropZone ?? '';\n\n if (isIntra) {\n // Intra-grid path — emit `row-move`.\n const fromIndex = payload.rowIndices[0];\n // Adjust toIndex when dropping after the dragged row (single-row only)\n if (payload.rowIndices.length === 1 && targetIndex > fromIndex) targetIndex--;\n if (fromIndex === targetIndex) return;\n const row = (liveRows ?? payload.rows)[0];\n if (!this.canMoveRow(fromIndex, targetIndex)) return;\n this.executeIntraGridMove(row, fromIndex, targetIndex, 'drag');\n return;\n }\n\n // Cross-grid path\n if (!dropZone || dropZone !== payload.dropZone) return;\n\n // canDrop final check\n if (this.config.canDrop && !this.config.canDrop(payload, targetIndex)) {\n this.gridElement.classList.add('tbw-grid--drop-target-rejected');\n setTimeout(() => this.gridElement.classList.remove('tbw-grid--drop-target-rejected'), 200);\n return;\n }\n\n // Resolve final row references — live (same-window) or deserialized JSON\n const deserialize = this.config.deserializeRow ?? ((r: unknown) => r as T);\n const incomingRows: T[] = liveRows ?? payload.rows.map((r) => deserialize(r as unknown));\n\n const dropDetail: RowDropDetail<T> = {\n payload,\n sourceGridId: payload.sourceGridId,\n targetIndex,\n operation: payload.operation,\n };\n if (this.emitCancelable('row-drop', dropDetail)) return;\n\n // Insert into target grid's _rows\n const targetRows = [...rows];\n targetRows.splice(targetIndex, 0, ...(incomingRows as unknown[]));\n this.grid.rows = targetRows;\n\n // Locate the source plugin in THIS window. If it's here we handle the\n // source-side mutation directly; if it isn't, we broadcast a message and\n // let the source-window plugin (if any) handle removal + transfer emit.\n const sourcePlugin = this.findPeerOnGrid(payload.sourceGridId);\n\n // Remove from source grid's _rows when operation === 'move' (same-window)\n if (payload.operation === 'move' && sourcePlugin) {\n const sourceGrid = document.getElementById(payload.sourceGridId) as\n (HTMLElement & { rows?: unknown[]; _rows?: unknown[] }) | null;\n if (sourceGrid) {\n const srcRows = (sourceGrid._rows ?? sourceGrid.rows ?? []).slice();\n // Remove from highest index down so earlier indices stay stable\n const sortedIndices = [...payload.rowIndices].sort((a, b) => b - a);\n for (const idx of sortedIndices) {\n if (idx >= 0 && idx < srcRows.length) srcRows.splice(idx, 1);\n }\n sourceGrid.rows = srcRows;\n }\n }\n\n // Mark accepted on the source plugin so dragend knows (same-window only —\n // cross-window source flips its own flag in `onRemoteTransfer`).\n if (sourcePlugin) sourcePlugin.dragAccepted = true;\n\n // Emit row-transfer on the target. The source's `row-transfer` is fired\n // either here (same-window) or by the source plugin in `onRemoteTransfer`\n // (cross-window).\n const transferDetail: RowTransferDetail<T> = {\n rows: incomingRows,\n fromGridId: payload.sourceGridId,\n toGridId: this.gridId,\n fromIndices: payload.rowIndices,\n toIndex: targetIndex,\n operation: payload.operation,\n };\n this.emit('row-transfer', transferDetail);\n\n if (sourcePlugin) {\n sourcePlugin.emitTransfer(transferDetail);\n } else {\n // Cross-window: broadcast so the source window can finish its half of\n // the transfer (row removal on `move`, `row-transfer` emit, accepted flip).\n broadcastRemoteTransfer({\n type: 'tbw-row-drag-drop:transfer',\n sessionId: payload.sessionId,\n sourceGridId: payload.sourceGridId,\n toGridId: this.gridId,\n dropZone,\n rowIndices: payload.rowIndices,\n toIndex: targetIndex,\n operation: payload.operation,\n // Re-use the payload's already-serialized rows so the source side can\n // round-trip them through `deserializeRow` for its `row-transfer` event.\n serializedRows: payload.rows as unknown[],\n });\n }\n }\n\n /**\n * Source-window handler for a remote `row-transfer` broadcast from a target\n * window. Only runs on the plugin instance whose grid id matches the message.\n * @internal\n */\n private onRemoteTransfer(msg: RemoteTransferMessage): void {\n if (msg.sourceGridId !== this.gridId) return;\n // Defensive: only act when the message zone matches our zone. This prevents\n // a stray message from a grid sharing the same id but a different zone\n // (e.g. independent demos in different tabs) from mutating our rows.\n const dropZone = this.config.dropZone ?? '';\n if (!dropZone || msg.dropZone !== dropZone) return;\n\n // Resolve incoming rows (for the `row-transfer` event payload).\n const deserialize = this.config.deserializeRow ?? ((r: unknown) => r as T);\n const incomingRows: T[] = msg.serializedRows.map((r) => deserialize(r));\n\n if (msg.operation === 'move') {\n const srcRows = (this.internalGrid._rows ?? this.sourceRows).slice();\n const sortedIndices = [...msg.rowIndices].sort((a, b) => b - a);\n for (const idx of sortedIndices) {\n if (idx >= 0 && idx < srcRows.length) srcRows.splice(idx, 1);\n }\n this.grid.rows = srcRows as unknown[];\n }\n\n // Flip accepted before dragend (best-effort — the message may arrive after\n // dragend has already fired with `accepted: false`; `row-transfer` is the\n // authoritative success signal).\n this.dragAccepted = true;\n\n this.emit('row-transfer', {\n rows: incomingRows,\n fromGridId: msg.sourceGridId,\n toGridId: msg.toGridId,\n fromIndices: msg.rowIndices,\n toIndex: msg.toIndex,\n operation: msg.operation,\n } as RowTransferDetail<T>);\n }\n\n private onDragEnd(): void {\n if (this.dragSessionId) clearDragSession(this.dragSessionId);\n clearCurrentDragSession();\n this.autoScroller?.stop();\n this.gridElement.classList.remove('tbw-grid--drag-source');\n\n if (this.isDragging) {\n const endDetail: RowDragEndDetail<T> = {\n rows: this.draggedRows,\n indices: this.draggedIndices,\n accepted: this.dragAccepted,\n };\n this.emit('row-drag-end', endDetail);\n }\n this.clearDragClasses();\n this.resetDragState();\n }\n // #endregion\n\n // #region Helpers\n\n /** Public wrapper so a peer plugin can dispatch `row-transfer` on this grid. @internal */\n emitTransfer(detail: RowTransferDetail<T>): void {\n this.emit('row-transfer', detail);\n }\n\n /** Find the peer `RowDragDropPlugin` instance on another grid by id. */\n private findPeerOnGrid(gridId: string): RowDragDropPlugin<T> | null {\n const peerEl = document.getElementById(gridId) as\n (HTMLElement & { getPluginByName?: (name: string) => RowDragDropPlugin<T> | undefined }) | null;\n if (!peerEl?.getPluginByName) return null;\n return peerEl.getPluginByName('rowDragDrop') ?? null;\n }\n\n // #region Click-only move menu (WCAG 2.2 SC 2.5.7)\n\n /** Grids in this window sharing our (non-empty) `dropZone`. */\n private peersInDropZone(): RowDragDropPlugin<T>[] {\n const dropZone = this.config.dropZone ?? '';\n if (!dropZone) return [];\n const peers: RowDragDropPlugin<T>[] = [];\n for (const candidate of attachedPlugins) {\n const peer = candidate as RowDragDropPlugin<T>;\n if (peer === this) continue;\n if ((peer.config.dropZone ?? '') === dropZone) peers.push(peer);\n }\n return peers;\n }\n\n /** Human-readable name for this grid, used in the transfer menu. */\n private get gridLabel(): string {\n const host = this.gridElement;\n return host?.getAttribute('aria-label') || host?.id || 'grid';\n }\n\n /**\n * Open the click-only alternative to dragging a row: reorder within this grid\n * and, when a `dropZone` is configured, send the row to a peer grid — all\n * with single clicks or taps, no press-hold-move gesture.\n */\n private openMoveMenu(handle: HTMLElement): void {\n const rowEl = handle.closest('.data-grid-row') as HTMLElement | null;\n if (!rowEl) return;\n const rowIndex = this.getRowIndex(rowEl);\n if (rowIndex < 0) return;\n\n const rows = this.internalGrid._rows ?? this.sourceRows;\n const actions: DragAlternativeAction[] = [\n {\n label: 'Move up',\n disabled: rowIndex === 0 || !this.canMoveRow(rowIndex, rowIndex - 1),\n run: () => this.moveRow(rowIndex, rowIndex - 1),\n },\n {\n label: 'Move down',\n disabled: rowIndex >= rows.length - 1 || !this.canMoveRow(rowIndex, rowIndex + 1),\n run: () => this.moveRow(rowIndex, rowIndex + 1),\n },\n {\n label: 'Move to top',\n disabled: rowIndex === 0 || !this.canMoveRow(rowIndex, 0),\n run: () => this.moveRow(rowIndex, 0),\n },\n {\n label: 'Move to bottom',\n disabled: rowIndex >= rows.length - 1 || !this.canMoveRow(rowIndex, rows.length - 1),\n run: () => this.moveRow(rowIndex, rows.length - 1),\n },\n ];\n\n const verb = (this.config.operation ?? 'move') === 'copy' ? 'Copy to' : 'Send to';\n for (const peer of this.peersInDropZone()) {\n actions.push({\n label: `${verb} ${peer.gridLabel}`,\n run: () => this.transferToPeer(peer, rowIndex),\n });\n }\n\n this.moveMenu ??= createDragAlternativeMenu('tbw-row-move-menu', 'tbw-row-move-menu');\n this.moveMenu.open(handle, `Move row ${rowIndex + 1}`, actions);\n }\n\n /**\n * Click-only equivalent of dropping the row onto `peer`. Runs the same vetoes\n * and emits the same events as the drag path (`row-drag-start`, `row-drop`,\n * `row-transfer`) so consumers cannot tell the two apart.\n */\n private transferToPeer(peer: RowDragDropPlugin<T>, rowIndex: number): void {\n const dropZone = this.config.dropZone ?? '';\n if (!dropZone) return;\n\n const { rows, indices } = this.resolveDraggedRows(rowIndex);\n if (rows.length === 0) return;\n if (this.config.canDrag && !this.config.canDrag(rows[0], rowIndex)) return;\n\n const operation = this.config.operation ?? 'move';\n if (this.emitCancelable('row-drag-start', { rows, indices, operation, dropZone } as RowDragStartDetail<T>)) return;\n\n const serialize = this.config.serializeRow ?? ((r: T) => r);\n const payload: RowDragPayload<T> = {\n sessionId: newDragSessionId(),\n sourceGridId: this.gridId,\n dropZone,\n rows: rows.map(serialize) as T[],\n rowIndices: indices,\n operation,\n };\n\n const toIndex = peer.acceptTransfer(payload, rows);\n if (toIndex < 0) return;\n\n if (operation === 'move') {\n const srcRows = [...(this.internalGrid._rows ?? this.sourceRows)];\n for (const idx of [...indices].sort((a, b) => b - a)) {\n if (idx >= 0 && idx < srcRows.length) srcRows.splice(idx, 1);\n }\n this.grid.rows = srcRows as unknown[];\n }\n\n const detail: RowTransferDetail<T> = {\n rows,\n fromGridId: this.gridId,\n toGridId: peer.gridId,\n fromIndices: indices,\n toIndex,\n operation,\n };\n peer.emitTransfer(detail);\n this.emit('row-transfer', detail);\n }\n\n /**\n * Target half of {@link RowDragDropPlugin.transferToPeer}. Returns the insert\n * index, or `-1` when the transfer was rejected (zone mismatch, `canDrop`\n * veto, or a cancelled `row-drop`).\n *\n * @internal\n */\n acceptTransfer(payload: RowDragPayload<T>, liveRows: readonly T[]): number {\n const dropZone = this.config.dropZone ?? '';\n if (!dropZone || dropZone !== payload.dropZone) return -1;\n\n const rows = this.internalGrid._rows ?? this.sourceRows;\n const targetIndex = rows.length;\n if (this.config.canDrop && !this.config.canDrop(payload, targetIndex)) return -1;\n\n const dropDetail: RowDropDetail<T> = {\n payload,\n sourceGridId: payload.sourceGridId,\n targetIndex,\n operation: payload.operation,\n };\n if (this.emitCancelable('row-drop', dropDetail)) return -1;\n\n this.grid.rows = [...(rows as unknown[]), ...(liveRows as unknown[])];\n return targetIndex;\n }\n // #endregion\n\n private resolveDraggedRows(originIndex: number): { rows: T[]; indices: number[] } {\n const rows = this.internalGrid._rows ?? this.sourceRows;\n const originRow = rows[originIndex] as T;\n\n // If a selection plugin is loaded and the dragged row is selected, drag the whole selection.\n const selection = this.grid?.getPluginByName?.('selection') as\n { getSelectedRowIndices?: () => number[]; getSelectedRows?: <U>() => U[] } | undefined;\n if (selection?.getSelectedRowIndices) {\n const selectedIndices = selection.getSelectedRowIndices();\n if (selectedIndices.includes(originIndex) && selectedIndices.length > 1) {\n const sorted = [...selectedIndices].sort((a, b) => a - b);\n return {\n rows: sorted.map((i) => rows[i] as T),\n indices: sorted,\n };\n }\n }\n return { rows: [originRow], indices: [originIndex] };\n }\n\n private ensureAutoScroller(): void {\n if (this.autoScroller) return;\n const viewport = this.gridElement.querySelector<HTMLElement>('.rows-viewport');\n if (!viewport) return;\n const opts = typeof this.config.autoScroll === 'object' ? this.config.autoScroll : undefined;\n this.autoScroller = createAutoScroller(viewport, opts, (active) => {\n this.gridElement.classList.toggle('tbw-grid--auto-scrolling', active);\n });\n }\n\n private applyDropPositionClasses(rowEl: HTMLElement | null, isBefore: boolean): void {\n this.clearDropTargetClasses();\n if (!rowEl) return;\n rowEl.classList.add('drop-target');\n rowEl.classList.toggle('drop-before', isBefore);\n rowEl.classList.toggle('drop-after', !isBefore);\n }\n\n private clearDropTargetClasses(): void {\n this.gridElement?.querySelectorAll('.data-grid-row.drop-target').forEach((row) => {\n row.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n }\n\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.data-grid-row').forEach((row) => {\n row.classList.remove(GridClasses.DRAGGING, 'drop-target', 'drop-before', 'drop-after');\n });\n }\n\n private resetDragState(): void {\n this.isDragging = false;\n this.draggedRowIndex = null;\n this.draggedRows = [];\n this.draggedIndices = [];\n this.dragSessionId = null;\n this.dragAccepted = false;\n this.dropRowIndex = null;\n this.pendingMove = null;\n }\n\n private getRowIndex(rowEl: HTMLElement): number {\n const cell = rowEl.querySelector('.cell[data-row]');\n return cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n }\n\n private clearDebounceTimer(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n\n // #endregion\n\n // #region Intra-Grid Move\n\n private handleKeyboardMove(row: T, fromIndex: number, toIndex: number, focusCol: number): void {\n if (!this.pendingMove) {\n this.pendingMove = { originalIndex: fromIndex, currentIndex: toIndex, row };\n } else {\n this.pendingMove.currentIndex = toIndex;\n }\n this.lastFocusCol = focusCol;\n\n const grid = this.internalGrid;\n const rows = [...(grid._rows ?? this.sourceRows)];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n\n grid._rows = rows;\n grid._focusRow = toIndex;\n grid._focusCol = focusCol;\n grid.refreshVirtualWindow(true);\n ensureCellVisible(grid);\n\n this.clearDebounceTimer();\n this.debounceTimer = setTimeout(() => this.flushPendingMove(), this.config.debounceMs ?? 300);\n }\n\n private flushPendingMove(): void {\n this.clearDebounceTimer();\n if (!this.pendingMove) return;\n const { originalIndex, currentIndex, row: movedRow } = this.pendingMove;\n this.pendingMove = null;\n if (originalIndex === currentIndex) return;\n\n const grid = this.internalGrid;\n // `grid._rows` already reflects the post-move order (mutated incrementally\n // by `handleKeyboardMove`); report it as `detail.rows` so consumers see\n // the actual reordered array, not the original `sourceRows` snapshot.\n const postMoveRows = (grid._rows ?? this.sourceRows) as T[];\n const detail: RowMoveDetail<T> = {\n row: movedRow as T,\n fromIndex: originalIndex,\n toIndex: currentIndex,\n rows: [...postMoveRows],\n source: 'keyboard',\n };\n const cancelled = this.emitCancelable('row-move', detail);\n if (cancelled) {\n // Revert: restore the original snapshot. `sourceRows` was never mutated\n // during the pending move (only `grid._rows` was), so resetting from it\n // is the correct rollback regardless of how many incremental keyboard\n // moves accumulated.\n grid._rows = [...this.sourceRows];\n grid._focusRow = originalIndex;\n grid._focusCol = this.lastFocusCol;\n grid.refreshVirtualWindow(true);\n ensureCellVisible(grid);\n }\n }\n\n private executeIntraGridMove(row: unknown, fromIndex: number, toIndex: number, source: 'keyboard' | 'drag'): void {\n const rows = [...this.sourceRows];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n const detail: RowMoveDetail<T> = {\n row: row as T,\n fromIndex,\n toIndex,\n rows: rows as T[],\n source,\n };\n const cancelled = this.emitCancelable('row-move', detail);\n if (cancelled) return;\n if (this.animationType === 'flip' && this.gridElement) {\n const oldPositions = this.captureRowPositions();\n this.grid.rows = rows;\n requestAnimationFrame(() => {\n void this.gridElement.offsetHeight;\n this.animateFLIP(oldPositions, fromIndex, toIndex);\n });\n } else {\n this.grid.rows = rows;\n }\n }\n\n private captureRowPositions(): Map<number, number> {\n const positions = new Map<number, number>();\n this.gridElement?.querySelectorAll('.data-grid-row').forEach((row) => {\n const rowIndex = this.getRowIndex(row as HTMLElement);\n if (rowIndex >= 0) positions.set(rowIndex, row.getBoundingClientRect().top);\n });\n return positions;\n }\n\n private animateFLIP(oldPositions: Map<number, number>, fromIndex: number, toIndex: number): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n const minIndex = Math.min(fromIndex, toIndex);\n const maxIndex = Math.max(fromIndex, toIndex);\n const rowsToAnimate: { el: HTMLElement; deltaY: number }[] = [];\n\n gridEl.querySelectorAll('.data-grid-row').forEach((row) => {\n const rowEl = row as HTMLElement;\n const newRowIndex = this.getRowIndex(rowEl);\n if (newRowIndex < 0 || newRowIndex < minIndex || newRowIndex > maxIndex) return;\n let oldIndex: number;\n if (newRowIndex === toIndex) oldIndex = fromIndex;\n else if (fromIndex < toIndex) oldIndex = newRowIndex + 1;\n else oldIndex = newRowIndex - 1;\n const oldTop = oldPositions.get(oldIndex);\n if (oldTop === undefined) return;\n const newTop = rowEl.getBoundingClientRect().top;\n const deltaY = oldTop - newTop;\n if (Math.abs(deltaY) > 1) rowsToAnimate.push({ el: rowEl, deltaY });\n });\n\n if (rowsToAnimate.length === 0) return;\n\n rowsToAnimate.forEach(({ el, deltaY }) => {\n el.style.transform = `translateY(${deltaY}px)`;\n });\n void gridEl.offsetHeight;\n\n const duration = this.animationDuration;\n requestAnimationFrame(() => {\n rowsToAnimate.forEach(({ el }) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n setTimeout(() => {\n rowsToAnimate.forEach(({ el }) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n // #endregion\n}\n"],"mappings":"yB,4EAoBA,IAgBaI,EAsBF,UAtBEA,EAyBD,WCpCNC,iBAAW,IAAIC,IAqCrB,SAAgBC,EAA+BC,GAC7C,MAAMC,EAAQJ,EAASK,IAAIF,GAC3B,IAAKC,EAAO,OACZ,GAAIA,EAAME,cAAe,OACzB,MAAMC,EAAoB,GAC1B,IAAK,MAAMC,KAAOJ,EAAMK,KAAM,CAC5B,MAAMC,EAAMF,EAAIG,QAChB,QAAY,IAARD,EAAmB,OACvBH,EAAOK,KAAKF,EACd,CACA,OAAOH,CACT,CAMA,SAAgBM,EAAiBV,GAC/BH,EAASc,OAAOX,EAClB,CAkBA,SAAgBY,IACd,GAAsB,oBAAXC,QAAuD,mBAAtBA,OAAOC,WACjD,OAAOD,OAAOC,aAEhB,GAAsB,oBAAXD,QAA4D,mBAA3BA,OAAOE,gBAAgC,CACjF,MAAMC,iBAAM,IAAIC,YAAY,GAE5B,OADAJ,OAAOE,gBAAgBC,GAChB,QAAQE,KAAKC,MAAMC,SAAS,OAAOJ,EAAI,GAAGI,SAAS,MAAMJ,EAAI,GAAGI,SAAS,KAClF,CAIA,MAAO,QAAQF,KAAKC,MAAMC,SAAS,UAAUC,GAAiBD,SAAS,KACzE,CAEA,IAAIC,EAAkB,EEgStB,SAAgBC,EAAkBC,EAAgBC,IAlJlD,SAAkCD,GAChC,IAAKA,EAAKE,iBAAiBC,QAAS,OACpC,MAAMC,UAAEA,EAAAC,UAAWA,EAAAC,WAAWA,EAAAC,cAAYA,GAAkBP,EAAKE,gBAG3DM,EAAWH,EACXI,EAAgBH,GAAYI,cAAgBF,GAAUE,cAAgB,EAC5E,IAAKF,GAAYC,GAAiB,EAAG,OAErC,MAAME,EAAUX,EAAKY,4BAA4BZ,EAAKa,YAAc,CAAEC,IAAK,EAAGC,OAAQ,GACtF,GAAIJ,EAAQK,WAAY,OAExB,MAAMF,EAAMG,KAAKC,IAAI,EAAGP,EAAQG,KAE1BK,EAAeV,EAAgBK,EADtBG,KAAKC,IAAI,EAAGP,EAAQI,QAEnC,GAAII,GAAgB,EAAG,OAEvB,MAAMC,EAAIpB,EAAKa,UAAYT,EACrBiB,EAAmBd,EAAgBnC,EAAmBoC,EAASc,UAAWf,GAAiBC,EAASc,UAC1G,GAAIF,EAAIC,EAAmBP,EAAK,CAC9B,MAAMS,EAASN,KAAKC,IAAI,EAAGE,EAAIN,GAC/BN,EAASc,UAAYf,EAAgBpC,EAAqBoD,EAAQhB,GAAiBgB,CACrF,MAAO,GAAIH,EAAIhB,EAAYiB,EAAmBP,EAAMK,EAAc,CAChE,MAAMI,EAASN,KAAKC,IAAI,EAAGE,EAAID,EAAef,EAAYU,GAC1DN,EAASc,UAAYf,EAAgBpC,EAAqBoD,EAAQhB,GAAiBgB,CACrF,CACF,CAyHEC,CAAyBxB,GAEzB,MAAMyB,EA7XR,SAAsBzB,GACpB,YAAgC,IAAzBA,EAAK0B,kBAA0D,IAAzB1B,EAAK0B,eACpD,CA2XoBC,CAAa3B,MAAWA,EAAK4B,gBD3TjD,IAA+BC,EC6TxBJ,GAAWzB,EAAK8B,sBAAqB,ID7TbD,EC+Td7B,EAAK+B,UD7TpBF,EAAKG,iBAAiB,eAAeC,QAASC,GAAOA,EAAGC,UAAUC,OAAO,eC+TzEC,MAAMC,KAAKtC,EAAK+B,QAAQC,iBAAiB,2BAA2BC,QAASC,IAC3EA,EAAGK,aAAa,gBAAiB,WAGnC,MAAMC,EA/HR,SAAyBxC,GACvB,MAAMyC,EAAWzC,EAAKa,UAChB6B,EAAS1C,EAAKE,gBAAgByC,OAAS,EACvCC,EAAO5C,EAAKE,gBAAgB2C,KAAO7C,EAAK8C,MAAMC,OACpD,GAAIN,EAAWC,GAAUD,GAAYG,EAAM,OAE3C,MAAMI,EAAQhD,EAAK+B,QAAQC,iBAAiB,kBAAkBS,EAAWC,GAGzE,IAAIO,EAAOD,GAAOE,SAASlD,EAAKmD,WAKhC,OAJKF,GAASA,EAAKd,WAAWiB,SAAS,UACrCH,EAAQD,GAAOK,cAAc,mBAAmBrD,EAAKmD,gBAAkBH,GAAOK,cAAc,oBAGvFJ,EAAO,CAAED,MAAOA,GAAS,KAAMC,aAAS,CACjD,CAgHkBK,CAAgBtD,GAChC,IAAKwC,EAAS,OACd,MAAMQ,MAAEA,EAAAC,KAAOA,GAAST,EAExBS,EAAKd,UAAUoB,IAAI,cACnBN,EAAKV,aAAa,gBAAiB,QAI9Bd,IAAaxB,GAASuD,uBAtH7B,SACExD,EACAgD,EACAC,EACAhD,GAEA,MAAMwD,EAAazD,EAAKqD,cAAc,oBACtC,IAAKI,EAAY,OAGjB,GAAIxD,GAASyD,gBAEX,YADAD,EAAWE,WAAa,GAG1B,GAAI1D,GAAS2D,iBAEX,YADAH,EAAWE,WAAaF,EAAWI,YAAcJ,EAAWK,aAO9D,MAAMnD,EAAUX,EAAK+D,8BAA8Bf,QAAS,EAAWC,IAAS,CAAEe,KAAM,EAAGC,MAAO,GAClG,GAAItD,EAAQK,WAAY,OAExB,MAAMkD,EAAWjB,EAAKkB,wBAChBC,EAAiBX,EAAWU,wBAC5BE,EAAWH,EAASF,KAAOI,EAAeJ,KAAOP,EAAWE,WAC5DW,EAAYD,EAAWH,EAASK,MAChCC,EAAcf,EAAWE,WAAahD,EAAQqD,KAC9CS,EAAehB,EAAWE,WAAaF,EAAWK,YAAcnD,EAAQsD,MAC1EI,EAAWG,EACbf,EAAWE,WAAaU,EAAW1D,EAAQqD,KAClCM,EAAYG,IACrBhB,EAAWE,WAAaW,EAAYb,EAAWK,YAAcnD,EAAQsD,MAEzE,CAmFIS,CAA+B1E,EAAMgD,EAAOC,EAAMhD,GAhFtD,SAA0BD,EAAgBiD,EAAmBxB,GAC3D,GAAIA,GAAawB,EAAKd,UAAUiB,SAAS/E,GAAsB,CAE7D,MAAMsG,EAAc1B,EAAKI,cHvU3B,uGGwUE,GAAIsB,GAAeC,SAASC,gBAAkBF,EAC5C,IACEA,EAAYG,MAAM,CAAEC,eAAe,GACrC,CAAA,MAEA,CAEF,MACF,CAEA,GAAItD,EAAW,CAGb,GAAIwB,EAAKG,SAASwB,SAASC,eAAgB,OACtC5B,EAAK+B,aAAa,aAAa/B,EAAKV,aAAa,WAAY,MAClE,IACEU,EAAK6B,MAAM,CAAEC,eAAe,GAC9B,CAAA,MAEA,CACA,MACF,CAeA,MAAME,EAASL,SAASC,cAEtBI,aAAkBC,aAClBD,IAAWjF,GACc,mBAAlBA,EAAKoD,UACZpD,EAAKoD,SAAS6B,KACbA,EAAO9C,UAAUiB,SAAS,UAC1B6B,EAAOE,QAAQ,UACCF,IAAWjF,GAC5BA,EAAK8E,MAAM,CAAEC,eAAe,GAEhC,CAgCEK,CAAiBpF,EAAMiD,EAAMxB,EAC/B,CCxZA,SAAS4D,IACP,MAAqD,mBAAvCH,YAAYI,WAAWC,WACvC,CAwDA,SAAgBC,EAA0BC,EAAYC,GACpD,IAAI7D,EAA8B,KAC9B8D,GAAS,EACTC,GAAW,EACf,MAAMC,EAAQ,IAAIC,gBAElB,SAASC,IACFJ,GAAW9D,IAChB8D,GAAS,EACLN,KAAmBxD,EAAKmE,cAC5BnE,EAAKoE,QAAS,EAChB,CAyDA,OAfArB,SAASsB,iBACP,cACCC,IACKR,GAAU9D,IAASsE,EAAEC,eAAeC,SAASxE,IAAOkE,KAE1D,CAAEO,SAAS,EAAMC,OAAQV,EAAMU,SAEjC3B,SAASsB,iBACP,UACCC,IACkC,WAA5BA,EAAoBK,KAAkBT,KAE7C,CAAEO,SAAS,EAAMC,OAAQV,EAAMU,SAG1B,CACL,WAAIE,GACF,OAAO5E,CACT,EACA,IAAA6E,CAAKC,EAAQC,EAAOC,GAClB,GAAIjB,GAA+B,IAAnBiB,EAAQ9D,OAAc,OACjClB,IAAMA,EA7Df,WACE,MAAMK,EAAK0C,SAASkC,cAAc,OAkBlC,OAjBA5E,EAAGuD,GAAKA,EACRvD,EAAGwD,UAAYA,EACfxD,EAAGK,aAAa,UAAW,UAC3BL,EAAGK,aAAa,OAAQ,SAExBL,EAAG+D,QAAS,EAIZ/D,EAAGgE,iBACD,WACCC,IACC,MAAMY,EAAQZ,EAAiBa,cAC3BD,aAAgBE,OAAS/E,EAAGkB,SAAS2D,IAAOhB,KAElD,CAAEQ,OAAQV,EAAMU,SAEXrE,CACT,CAyCsBgF,IAGlB,MAAMC,EAAOR,EAAOxB,QAAqB,8BAAgCP,SAASwC,KAC9EvF,EAAKwF,aAAeF,GAAMA,EAAKG,YAAYzF,GAC/CA,EAAKU,aAAa,aAAcqE,GAChC/E,EAAK0F,kBACL,IAAK,MAAMC,KAAUX,EAAS,CAC5B,MAAMY,EAAM7C,SAASkC,cAAc,UACnCW,EAAIC,KAAO,SACXD,EAAI/B,UAAY,GAAGA,SACnB+B,EAAIE,YAAcH,EAAOZ,MACzBa,EAAIG,UAA+B,IAApBJ,EAAOI,SACtBH,EAAIvB,iBAAiB,QAAA,KACnBH,IACAyB,EAAOK,QAEThG,EAAKyF,YAAYG,EACnB,CACA5F,EAAKoE,QAAS,GACTN,GAAUN,KAAmBxD,EAAK0D,cACvCI,GAAS,EAvDb,SAAegB,GACb,IAAK9E,EAAM,OACX,MAAMiG,EAAInB,EAAOxC,wBACX4D,EAAMlG,EAAKsC,wBACXH,EAAO/C,KAAKC,IAAI,EAAGD,KAAK+G,IAAIF,EAAE9D,KAAMiE,OAAOC,WAAaH,EAAIxD,MAAQ,IACpE4D,EAAQL,EAAE/G,OA1GE,EA2GZD,EAAMqH,EAAQJ,EAAIK,QAAUH,OAAOI,YAAcF,EAAQlH,KAAKC,IAAI,EAAG4G,EAAEhH,IA3G3D,EA2GiFiH,EAAIK,QACvGvG,EAAKyG,MAAMtE,KAAO,GAAG/C,KAAKsH,MAAMvE,OAChCnC,EAAKyG,MAAMxH,IAAM,GAAGG,KAAKsH,MAAMzH,MACjC,CA+CI0H,CAAM7B,GACN9E,EAAKwB,cAAiC,2BAA2ByB,OACnE,EACAiB,QACA,OAAA0C,GACE7C,GAAW,EACXG,IACAF,EAAMA,QACNhE,GAAMO,SACNP,EAAO,IACT,EAEJ,CC7JA,IAAI6G,EAAiF,KAQrF,SAAgBC,IACd,OAAOD,CACT,CAGA,SAAgBE,IACdF,EAAiB,IACnB,CAOA,IAAaG,EAAoB,mCAQjC,SAAgBC,EAAkBC,GAChC,MAAMC,EAAQD,EAAKC,MAAM,kDACzB,IAAKA,EAAO,OAAO,KACnB,IACE,OAAOC,mBAAmBD,EAAM,GAClC,CAAA,MACE,OAAOA,EAAM,EACf,CACF,CAiDA,SAAgBE,EAAiBC,GAC/B,OAAOC,KAAKC,UAAUF,EACxB,CAmDA,SAAgBG,EACdtG,EACAuG,EACAC,EACAC,GAEA,IAAKzG,EACH,MAAO,CAAE0G,UAAW,KAAMC,YAAaF,EAAWG,UAAU,GAE9D,MAAMC,EAAcL,EAAiBxG,GACrC,GAAI6G,EAAc,EAChB,MAAO,CAAEH,UAAW,KAAMC,YAAaF,EAAWG,UAAU,GAE9D,MAAME,EAAO9G,EAAMmB,wBAEbyF,EAAWL,EADJO,EAAKhJ,IAAMgJ,EAAK1B,OAAS,EAEtC,MAAO,CACLsB,UAAWG,EACXF,YAAaC,EAAWC,EAAcA,EAAc,EACpDD,WAEJ,C,IE1HaG,EAAwB,iBAuCjCC,EAAyC,KACvCC,iBAAkB,IAAIC,IAE5B,SAASC,IACP,GAAgC,oBAArBC,iBAAkC,OAAO,KACpD,GAAIJ,EAAe,OAAOA,EAC1B,KACEA,EAAgB,IAAII,iBARU,sBAShBlE,iBAAiB,UAAYmE,IACzC,MAAMC,EAAOD,EAAMC,KACnB,GAAKA,GAAsB,+BAAdA,EAAK5C,KAElB,IAAK,MAAM6C,KAAYlI,MAAMC,KAAK2H,GAChC,IACEM,EAASD,EACX,CAAA,MAEA,GAGN,CAAA,MACEN,EAAgB,IAClB,CACA,OAAOA,CACT,CAiBA,IAAMQ,iBAAkB,IAAIN,IAgD5B,IAAaO,EAAb,MAAaA,UAAuCvM,EAElDwM,KAAgB,cAGhBC,O,+yGAGAC,gBAAuE,CACrEC,OAAQ,CACN,CAAEnD,KAAM,WAAYoD,YAAa,0BAA2BC,YAAY,GACxE,CAAErD,KAAM,iBAAkBoD,YAAa,wCAAyCC,YAAY,GAC5F,CAAErD,KAAM,eAAgBoD,YAAa,uDACrC,CAAEpD,KAAM,WAAYoD,YAAa,wCAAyCC,YAAY,GACtF,CAAErD,KAAM,eAAgBoD,YAAa,0DAKzC,iBAAuBE,GACrB,MAAO,CACLC,gBAAgB,EAIhBC,mBAAoB,OACpBC,gBAAiB,GACjBC,WAAY,IACZC,UAAW,OACXC,UAAW,OACXC,YAAY,EACZC,SAAU,SAEd,CAOA,sBAAAC,GACE,MAAMC,EAAWC,KAAKC,OAAOC,eAC7B,OAAiB,IAAbH,KACa,IAAbA,GAC4B,QAAzBC,KAAKC,OAAOJ,SACrB,CAGA,kBAAYM,GACV,MAAgC,QAAzBH,KAAKC,OAAOJ,UAA+C,SAAzBG,KAAKC,OAAOJ,QACvD,CAGA,iBAAYO,GACV,QAAKJ,KAAKK,0BACoB,IAA1BL,KAAKC,OAAOP,UAAgCM,KAAKC,OAAOP,UACrD,OACT,CAGAY,YAAqB,EACrBC,gBAAyC,KACzCC,YAA2B,GAC3BC,eAAmC,GACnCC,cAAuC,KACvCC,cAAuB,EACvBC,aAAsC,KACtCC,YAA0C,KAC1CC,cAA8D,KAC9DC,aAAuB,EACvBC,aAA4C,KAE5CC,OAAiB,GAGjBC,uBAAmEC,GAAQnB,KAAKoB,iBAAiBD,GAGjGE,SAA+C,KAG/C,gBAAYC,GACV,OAAOtB,KAAK3L,IACd,CAKA,MAAAkN,CAAgBlN,GACdmN,MAAMD,OAAOlN,GACb,MAAMmH,EAAOwE,KAAKyB,YAzJtB,IAAwC7C,EA0JhCpD,IACFwE,KAAKiB,OAASzF,EAAK1B,IAAM,YAAYpG,IAAmBgO,MAAM,EAAG,KAC5DlG,EAAK1B,KAAI0B,EAAK1B,GAAKkG,KAAKiB,QAC7BjB,KAAK2B,8BA7J6B/C,EA8JHoB,KAAKkB,uBA7JxC5C,EAAgB1G,IAAIgH,GACpBJ,IA6JIK,EAAgBjH,IAAIoI,MAExB,CAGA,MAAA4B,GACE5B,KAAK6B,qBACL7B,KAAKgB,cAAcc,OACnB9B,KAAKgB,aAAe,KAtJxB,SAA0CpC,GAExC,GADAN,EAAgB7K,OAAOmL,GACM,IAAzBN,EAAgByD,MAAc1D,EAAe,CAC/C,IACEA,EAAcjE,OAChB,CAAA,MAEA,CACAiE,EAAgB,IAClB,CACF,CA6II2D,CAAiChC,KAAKkB,wBACtCrC,EAAgBpL,OAAOuM,MACvBA,KAAKqB,UAAUvE,UACfkD,KAAKqB,SAAW,KACZrB,KAAKU,eAAelN,EAAiBwM,KAAKU,eAC9CzD,IACA+C,KAAKiC,iBACLT,MAAMI,QACR,CAMA,cAAAM,CAAwBC,GACtB,IAAKnC,KAAKF,yBAA0B,MAAO,IAAIqC,GAE/C,MAAMC,EAAiC,CACrCC,MAAOjE,EACPkE,OAAQ,GACR1J,MAAOoH,KAAKC,OAAOT,iBAAmB,GACtC+C,WAAW,EACXC,UAAU,EACVC,YAAY,EACZC,cAAc,EACdC,SAAS,EACTC,aAAA,KACE,MAAMlO,EAAYuE,SAASkC,cAAc,OAezC,OAdAzG,EAAUqF,UAAY,qBACtBrF,EAAUkC,aAAa,aAAc,iDACrClC,EAAUkC,aAAa,OAAQ,UAC/BlC,EAAUkC,aAAa,WAAY,MACnClC,EAAUmO,WAAY,EAKtBnO,EAAU6F,iBAAiB,QAAUC,IACnCA,EAAEsI,kBACF9C,KAAK+C,aAAarO,KAEpBsL,KAAKgD,QAAQtO,EAAW,cACjBA,IAIX,MAA0C,UAAnCsL,KAAKC,OAAOV,mBAAiC,IAAI4C,EAASC,GAAoB,CAACA,KAAqBD,EAC7G,CAGA,WAAAc,GACEjD,KAAKkD,mBACP,CAGA,cAAAC,GAGEnD,KAAKkD,mBACP,CAMA,iBAAAA,GACE,MAAMzH,EAAOuE,KAAKsB,aAAalL,QAC/B,IAAKqF,EAAM,OACX,MAAM2H,EAAgBpD,KAAKG,eACrBkD,EAAO5H,EAAKpF,iBAA8B,kBAChD,IAAK,MAAMiN,KAAOD,EACZD,EACoC,SAAlCE,EAAIC,aAAa,cAAyBD,EAAI1M,aAAa,YAAa,QACnE0M,EAAIjK,aAAa,cAC1BiK,EAAIE,gBAAgB,YAG1B,CAGA,SAAAC,CAAmB/E,GACjB,IAAKsB,KAAKC,OAAOX,eAAgB,OAEjC,IAAMZ,EAAMgF,SAAUhF,EAAMiF,SAA2B,YAAdjF,EAAM7D,KAAmC,cAAd6D,EAAM7D,IAAsB,OAChG,GAAImF,MAAK4D,EAAuBlF,GAAQ,OAExC,MAAMrK,EAAO2L,KAAKsB,aACZuC,EAAWxP,EAAKa,UAChBmO,EAAOhP,EAAK8C,OAAS6I,KAAK8D,WAChC,GAAID,EAAW,GAAKA,GAAYR,EAAKjM,OAAQ,OAG7C,MAAM2M,EAAwB,OADE,YAAdrF,EAAM7D,IAAoB,KAAO,QACdgJ,EAAW,EAAIA,EAAW,EAC/D,GAAIE,EAAU,GAAKA,GAAWV,EAAKjM,OAAQ,OAE3C,MAAMkM,EAAMD,EAAKQ,GACjB,OAAK7D,KAAKgE,WAAWH,EAAUE,IAE/B/D,KAAKiE,mBAAmBX,EAAKO,EAAUE,EAAS1P,EAAKmD,WACrDkH,EAAMwF,iBACNxF,EAAMoE,mBACC,QALP,CAMF,CAGA,EAAAc,CAAuBlF,GACrB,GAAIsB,KAAKmE,aAAa,aAAc,OAAO,EAC3C,MAAMvO,EAAU8I,EAAMjE,iBAAiB,IAAMiE,EAAM9I,OACnD,IAAKA,GAAQ4D,SAAW5D,IAAWoK,KAAKyB,YAAa,OAAO,EAC5D,IAAK7L,EAAO4D,QAAQ,cAAe,OAAO,EAC1C,MAAM4K,EAAMxO,EAAOyO,QACnB,MAAe,UAARD,GAA2B,WAARA,GAA4B,aAARA,GAAsBxO,EAAO0O,iBAC7E,CAGA,WAAAC,GACEvE,KAAKwE,kBACP,CAKA,OAAAC,CAAQC,EAAmBX,GACzB,MAAMV,EAAO,IAAIrD,KAAK8D,YAClBY,EAAY,GAAKA,GAAarB,EAAKjM,QACnC2M,EAAU,GAAKA,GAAWV,EAAKjM,QAC/BsN,IAAcX,GACb/D,KAAKgE,WAAWU,EAAWX,IAChC/D,KAAK2E,qBAAqBtB,EAAKqB,GAAYA,EAAWX,EAAS,WACjE,CAOA,UAAAC,CAAWU,EAAmBX,GAM5B,MAAMV,EAAOrD,KAAKsB,aAAanK,OAAS6I,KAAK8D,WAC7C,GAAIY,EAAY,GAAKA,GAAarB,EAAKjM,OAAQ,OAAO,EACtD,GAAI2M,EAAU,GAAKA,GAAWV,EAAKjM,OAAQ,OAAO,EAClD,GAAIsN,IAAcX,EAAS,OAAO,EAGlC,MAAMT,EAAMD,EAAKqB,GACXE,EAAe5E,KAAK3L,MAAMwQ,QAAiB,aAAcvB,GAC/D,GAAI5M,MAAMoO,QAAQF,IAAiBA,EAAalK,UAAS,GAAQ,OAAO,EAGxE,GAAIsF,KAAKC,OAAO8E,UAAY/E,KAAKC,OAAO8E,QAAQzB,EAAKoB,GAAY,OAAO,EAGxE,GAAI1E,KAAKC,OAAO+E,QAAS,CACvB,MAAMxH,EAA6B,CACjC1K,UAAW,QACXmS,aAAcjF,KAAKiB,OACnBiE,SAAUlF,KAAKC,OAAOiF,UAAY,GAClC7B,KAAM,CAACC,GACP6B,WAAY,CAACT,GACb/E,UAAW,QAEb,IAAKK,KAAKC,OAAO+E,QAAQxH,EAASuG,GAAU,OAAO,CACrD,CAEA,OAAO,CACT,CAKA,2BAAApC,GACE,MAAMyD,EAASpF,KAAKyB,YACpB,IAAK2D,EAAQ,OACb,MAAMxK,EAASoF,KAAKqF,iBAEpBD,EAAO7K,iBAAiB,YAAcC,GAAMwF,KAAKsF,YAAY9K,GAAiB,CAAEI,WAChFwK,EAAO7K,iBAAiB,UAAA,IAAiByF,KAAKuF,YAAa,CAAE3K,WAC7DwK,EAAO7K,iBAAiB,WAAaC,GAAMwF,KAAKwF,WAAWhL,GAAiB,CAAEI,WAC9EwK,EAAO7K,iBAAiB,YAAcC,GAAMwF,KAAKyF,YAAYjL,GAAiB,CAAEI,WAChFwK,EAAO7K,iBAAiB,OAASC,GAAMwF,KAAK0F,OAAOlL,GAAiB,CAAEI,UACxE,CAEA,WAAA0K,CAAoBK,GAClB,MAAM/P,EAAS+P,EAAG/P,OAClB,IAAKA,EAAQ,OAKb,MAAMgQ,EAAShQ,EAAO4D,QAAQ,uBAC9B,IAAInC,EAA4B,KAC5BwO,GAAsB,EAC1B,GAAID,EACFvO,EAAQuO,EAAOpM,QAAQ,kBACvBqM,GAAsB,OACjB,GAAI7F,KAAKG,eAAgB,CAI9B,GAAIH,KAAK8F,wBAAwBlQ,GAAS,OAC1CyB,EAAQzB,EAAO4D,QAAQ,iBACzB,CACA,IAAKnC,EAAO,OAEZ,MAAMP,EAAWkJ,KAAK+F,YAAY1O,GAClC,GAAIP,EAAW,EAAG,OAGlB,MAAMuM,KAAEA,EAAA2C,QAAMA,GAAYhG,KAAKiG,mBAAmBnP,GAClD,GAAoB,IAAhBuM,EAAKjM,OAAc,OAGvB,GAAI4I,KAAKC,OAAO8E,UAAY/E,KAAKC,OAAO8E,QAAQ1B,EAAK,GAAIvM,GAEvD,YADA6O,EAAGzB,iBAIL,MAAMvE,EAAYK,KAAKC,OAAON,WAAa,OACrCuF,EAAWlF,KAAKC,OAAOiF,UAAY,GACnCpS,EAAYY,IAGZwS,EAAqC,CAAE7C,OAAM2C,UAASrG,YAAWuF,YACvE,GAAIlF,KAAKmG,eAAe,iBAAkBD,GAExC,YADAP,EAAGzB,iBAILlE,KAAKM,YAAa,EAClBN,KAAKO,gBAAkBzJ,EACvBkJ,KAAKQ,YAAc6C,EACnBrD,KAAKS,eAAiBuF,EACtBhG,KAAKU,cAAgB5N,EACrBkN,KAAKW,cAAe,EAIpB,MAAMyF,EAAYpG,KAAKC,OAAOoG,cAAA,CAAkBC,GAASA,GACnD9I,EAA6B,CACjC1K,YACAmS,aAAcjF,KAAKiB,OACnBiE,WACA7B,KAAMA,EAAKkD,IAAIH,GACfjB,WAAYa,EACZrG,aAGF,GAAIgG,EAAGa,aAAc,CACnBb,EAAGa,aAAaC,cAA8B,SAAd9G,EAAuB,WAAa,OACpE,IACEgG,EAAGa,aAAaE,QAAQxJ,EAAmBK,EAAcC,IACrD0H,GAAUS,EAAGa,aAAaE,QFrf7B,GAAGxJ,UAA0ByJ,mBEqfoBzB,KAAW3H,EAAcC,IAE3EmI,EAAGa,aAAaE,QAAQ,aFvPhC,SACErD,EACAlB,GAEA,MAAMyE,EAAczE,EAAQ0E,OACzBC,IAAQA,EAA4BnE,SAA8B,iBAAZmE,EAAEzE,OAAkC,KAAZyE,EAAEzE,OAE7E0E,EAAkB,GACxB,IAAK,MAAMzD,KAAOD,EAAM,CACtB,MAAM2D,EAAQJ,EAAYL,IAAKU,IAC7B,MAAMC,EAAQ5D,EAAI2D,EAAI5E,OACtB,OAAI6E,QAA8C,GACtCC,OAAOD,GACRE,QAAQ,aAAc,OAEnCL,EAAMxT,KAAKyT,EAAMK,KAAK,MACxB,CACA,OAAON,EAAMM,KAAK,KACpB,CEqO8CC,CAAgBjE,EAAmCrD,KAAKmC,SAChG,CAAA,MAEA,CAIA,GAAIkB,EAAKjM,OAAS,EAAG,CACnB,MAAMmQ,EAAQtO,SAASkC,cAAc,OACrCoM,EAAMxN,UAAY,qBAClBwN,EAAMvL,YAAc,GAAGqH,EAAKjM,cAC5B6B,SAASwC,KAAKE,YAAY4L,GAC1B,IACE5B,EAAGa,aAAagB,aAAaD,EAAO,GAAI,GAC1C,CAAA,MAEA,CACAE,WAAA,IAAiBF,EAAM9Q,SAAU,EACnC,MACEuJ,KAAK0H,wBAAwB/B,EAAItO,EAAOwO,EAAsBD,EAAS,KAE3E,EN5hBJ,SAAoC9S,EAAmBuQ,GACrD,MAAMjQ,EAA0B,GAChC,IAAIH,GAAgB,EACpB,IAAK,MAAMqQ,KAAOD,EACJ,OAARC,GAA+B,iBAARA,EACzBlQ,EAAKG,KAAK,IAAIoU,QAAQrE,IAEtBrQ,GAAgB,EAGpBN,EAASiV,IAAI9U,EAAW,CAAEM,OAAMH,iBAClC,CMohBI4U,CAAoB/U,EAAWuQ,GFtiBnC,SAAyCvQ,EAAmB0K,GAC1DT,EAAiB,CAAEjK,YAAoB0K,UACzC,CEqiBIsK,CAAsBhV,EAAW0K,GAEjCnG,EAAMb,UAAUoB,IAAIlF,GACpBsN,KAAKyB,YAAYjL,UAAUoB,IAAI,wBACjC,CAOAqH,kCACE,kHAGF,uBAAA6G,CAAgClQ,GAC9B,OAAwE,OAAjEA,EAAO4D,QAAQsF,EAAkBiJ,2BAC1C,CAUA,uBAAAL,CAAgC/B,EAAetO,EAAoBuO,GACjE,IAAKD,EAAGa,aAAc,OACtB,MAAMrI,EAAO9G,EAAMmB,wBACbwP,EAAQ3Q,EAAM4Q,WAAU,GAC9BD,EAAMxR,UAAUoB,IAAI,sBAEpBoQ,EAAMxR,UAAUC,OAAO,WAAY,cAAe,cAAe,aAAc,iBAAkB,aACjGuR,EAAMxE,gBAAgB,iBAEtBwE,EAAMrL,MAAM/D,MAAQ,GAAGuF,EAAKvF,UAC5BoP,EAAMrL,MAAMF,OAAS,GAAG0B,EAAK1B,WAO7BuD,KAAKyB,YAAY9F,YAAYqM,GAG7B,IAAIE,EAAUvC,EAAGwC,QAAUhK,EAAK9F,KAC5B+P,EAAUzC,EAAG/H,QAAUO,EAAKhJ,IAChC,GAAIyQ,EAAQ,CACV,MAAMyC,EAAazC,EAAOpN,wBAC1B0P,EAAUG,EAAWhQ,KAAO8F,EAAK9F,KAAOgQ,EAAWzP,MAAQ,EAC3DwP,EAAUC,EAAWlT,IAAMgJ,EAAKhJ,IAAMkT,EAAW5L,OAAS,CAC5D,CAEAyL,EAAU5S,KAAKC,IAAI,EAAGD,KAAK+G,IAAI8B,EAAKvF,MAAOsP,IAC3CE,EAAU9S,KAAKC,IAAI,EAAGD,KAAK+G,IAAI8B,EAAK1B,OAAQ2L,IAC5C,IACEzC,EAAGa,aAAagB,aAAaQ,EAAOE,EAASE,EAC/C,CAAA,MAEA,CACAX,WAAA,IAAiBO,EAAMvR,SAAU,EACnC,CAEA,UAAA+O,CAAmBG,GACjB,MAAM2C,EAAK3C,EAAGa,aACd,IAAK8B,EAAI,OAGT,MAAMC,EAAQD,EAAGC,MAAQ7R,MAAMC,KAAK2R,EAAGC,OAAS,GAChD,IF5jBJ,SAAkCA,GAChC,IAAK,MAAMC,KAAKD,EACd,GAAI,qCAAAC,GAA2BA,EAAEC,WAAW,qCAA0B,OAAO,EAE/E,OAAO,CACT,CEujBSC,CAAkBH,KAAWvI,KAAKM,WAAY,OAEnD,MAAM4E,EAAWlF,KAAKC,OAAOiF,UAAY,GACnCyD,EAAU3L,IAIV4L,EAAU5I,KAAKM,YAAcqI,GAASnL,QAAQyH,eAAiBjF,KAAKiB,OAC1E,IAAK2H,EAAS,CACZ,IAAK1D,EAAU,OAEf,KF/kBN,SAAqCqD,EAA0BM,GAC7D,IAAK,MAAML,KAAKD,EACd,GAAIpL,EAAkBqL,KAAOK,EAAM,OAAOL,EAE5C,OAAO,IACT,CEykB2BM,CAAqBP,EAAOrD,IAC1ByD,GAAWA,EAAQnL,QAAQ0H,WAAaA,GAAW,MAC5E,CAEAS,EAAGzB,iBACCoE,IAAIA,EAAGS,WAAcJ,GAASnL,QAAQmC,WAAaK,KAAKC,OAAON,WAAa,QAGhF,MAAMtI,EAASsO,EAAG/P,OAAuB4D,QAAQ,kBAC3C6J,EAAOrD,KAAKsB,aAAanK,OAAS,GAClC6R,EAAMrL,EAAoBtG,EAAOsO,EAAG/H,QAAUrH,GAAOyJ,KAAK+F,YAAYxP,GAAK8M,EAAKjM,QAGtF,GAAIwR,GAA6B,OAAlBI,EAAIjL,WAAsBiL,EAAIjL,YAAciC,KAAKO,gBAC9DP,KAAKiJ,6BADP,CAMA,GAAIN,GAAW3I,KAAKC,OAAO+E,QAAS,CAClC,MAAMkE,EAAWlJ,KAAKC,OAAO+E,QAAQ2D,EAAQnL,QAASwL,EAAIhL,aAG1D,GAFAgC,KAAKyB,YAAYjL,UAAU2S,OAAO,+BAAgCD,GAClElJ,KAAKyB,YAAYjL,UAAU2S,OAAO,kCAAmCD,IAChEA,EAEH,YADAlJ,KAAKiJ,wBAGT,MACEjJ,KAAKyB,YAAYjL,UAAUoB,IAAI,gCAGjCoI,KAAKY,aAAeoI,EAAIhL,YACxBgC,KAAKoJ,yBAAyB/R,EAAO2R,EAAI/K,WAGV,IAA3B+B,KAAKC,OAAOL,aACdI,KAAKqJ,qBACLrJ,KAAKgB,cAAcsI,cAAc3D,EAAG/H,SArBtC,CAuBF,CAEA,WAAA6H,CAAoBE,GAClB,MAAMtO,EAASsO,EAAG/P,OAAuB4D,QAAQ,kBAC7CnC,GAAOA,EAAMb,UAAUC,OAAO,cAAe,cAAe,cAE5DkP,EAAG4D,gBAAkBvJ,KAAKyB,YAAYhK,SAASkO,EAAGtK,iBACpD2E,KAAKyB,YAAYjL,UAAUC,OAAO,+BAAgC,kCAClEuJ,KAAKgB,cAAcc,OAEvB,CAEA,MAAA4D,CAAeC,GACbA,EAAGzB,iBACHlE,KAAKgB,cAAcc,OACnB9B,KAAKyB,YAAYjL,UAAUC,OAAO,+BAAgC,kCAClEuJ,KAAKiJ,yBAEL,MAAMX,EAAK3C,EAAGa,aACd,IAAK8B,EAAI,OAIT,IAAI9K,EADYR,KACiCQ,SAAW,KACxDgM,EAAuB,KAE3B,GAAIhM,EAAS,CACX,MAAMiM,EAAS5W,EAAqB2K,EAAQ1K,WACxC2W,IAAQD,EAAWC,EACzB,KAAO,CAGL,GADAjM,EFrmBN,SAA2CkM,GACzC,IAAKA,EAAK,OAAO,KACjB,IACE,MAAMC,EAASlM,KAAKmM,MAAMF,GAC1B,GAC+B,iBAAtBC,GAAQ7W,WACiB,iBAAzB6W,GAAQ1E,cACa,iBAArB0E,GAAQzE,WACdxO,MAAMoO,QAAQ6E,GAAQtG,QACtB3M,MAAMoO,QAAQ6E,GAAQxE,aACD,SAArBwE,EAAOhK,WAA6C,SAArBgK,EAAOhK,UAEvC,OAAO,KAKT,IAAK,MAAMkK,KAAOF,EAAOxE,WACvB,GAAmB,iBAAR0E,IAAqBC,OAAOC,UAAUF,IAAQA,EAAM,EAAG,OAAO,KAE3E,OAAOF,CACT,CAAA,MACE,OAAO,IACT,CACF,CE6kBgBK,CADE1B,EAAG2B,QAAQ/M,IAEnBM,EAAS,CACX,MAAMiM,EAAS5W,EAAqB2K,EAAQ1K,WACxC2W,IAAQD,EAAWC,EACzB,CACF,CACA,IAAKjM,EAAS,OAGd,MAAMnG,EAASsO,EAAG/P,OAAuB4D,QAAQ,kBAC3C6J,EAAOrD,KAAKsB,aAAanK,OAAS,GAClC6R,EAAMrL,EAAoBtG,EAAOsO,EAAG/H,QAAUrH,GAAOyJ,KAAK+F,YAAYxP,GAAK8M,EAAKjM,QACtF,IAAI8G,EAAc8B,KAAKY,cAAgBoI,EAAIhL,YAE3C,MAAM4K,EAAUpL,EAAQyH,eAAiBjF,KAAKiB,OACxCiE,EAAWlF,KAAKC,OAAOiF,UAAY,GAEzC,GAAI0D,EAAS,CAEX,MAAMlE,EAAYlH,EAAQ2H,WAAW,GAGrC,GADkC,IAA9B3H,EAAQ2H,WAAW/N,QAAgB8G,EAAcwG,GAAWxG,IAC5DwG,IAAcxG,EAAa,OAC/B,MAAMoF,GAAOkG,GAAYhM,EAAQ6F,MAAM,GACvC,IAAKrD,KAAKgE,WAAWU,EAAWxG,GAAc,OAE9C,YADA8B,KAAK2E,qBAAqBrB,EAAKoB,EAAWxG,EAAa,OAEzD,CAGA,IAAKgH,GAAYA,IAAa1H,EAAQ0H,SAAU,OAGhD,GAAIlF,KAAKC,OAAO+E,UAAYhF,KAAKC,OAAO+E,QAAQxH,EAASU,GAGvD,OAFA8B,KAAKyB,YAAYjL,UAAUoB,IAAI,uCAC/B6P,WAAA,IAAiBzH,KAAKyB,YAAYjL,UAAUC,OAAO,kCAAmC,KAKxF,MAAMyT,EAAclK,KAAKC,OAAOkK,gBAAA,CAAoB7D,GAAeA,GAC7D8D,EAAoBZ,GAAYhM,EAAQ6F,KAAKkD,IAAKD,GAAM4D,EAAY5D,IAEpE+D,EAA+B,CACnC7M,UACAyH,aAAczH,EAAQyH,aACtB/G,cACAyB,UAAWnC,EAAQmC,WAErB,GAAIK,KAAKmG,eAAe,WAAYkE,GAAa,OAGjD,MAAMC,EAAa,IAAIjH,GACvBiH,EAAWC,OAAOrM,EAAa,KAAOkM,GACtCpK,KAAK3L,KAAKgP,KAAOiH,EAKjB,MAAME,EAAexK,KAAKyK,eAAejN,EAAQyH,cAGjD,GAA0B,SAAtBzH,EAAQmC,WAAwB6K,EAAc,CAChD,MAAME,EAAazR,SAAS0R,eAAenN,EAAQyH,cAEnD,GAAIyF,EAAY,CACd,MAAME,GAAWF,EAAWvT,OAASuT,EAAWrH,MAAQ,IAAI3B,QAEtDmJ,EAAgB,IAAIrN,EAAQ2H,YAAY2F,KAAA,CAAM3O,EAAG4O,IAAMA,EAAI5O,GACjE,IAAK,MAAM0N,KAAOgB,EACZhB,GAAO,GAAKA,EAAMe,EAAQxT,QAAQwT,EAAQL,OAAOV,EAAK,GAE5Da,EAAWrH,KAAOuH,CACpB,CACF,CAIIJ,IAAcA,EAAa7J,cAAe,GAK9C,MAAMqK,EAAuC,CAC3C3H,KAAM+G,EACNa,WAAYzN,EAAQyH,aACpBiG,SAAUlL,KAAKiB,OACfkK,YAAa3N,EAAQ2H,WACrBpB,QAAS7F,EACTyB,UAAWnC,EAAQmC,WAErBK,KAAKoL,KAAK,eAAgBJ,GAEtBR,EACFA,EAAaa,aAAaL,GA1pBhC,SAAiC7J,GAC/B,MAAMmK,EAAU9M,IAChB,GAAK8M,EACL,IACEA,EAAQC,YAAYpK,EACtB,CAAA,MAGA,CACF,CAqpBMqK,CAAwB,CACtBzP,KAAM,6BACNjJ,UAAW0K,EAAQ1K,UACnBmS,aAAczH,EAAQyH,aACtBiG,SAAUlL,KAAKiB,OACfiE,WACAC,WAAY3H,EAAQ2H,WACpBpB,QAAS7F,EACTyB,UAAWnC,EAAQmC,UAGnB8L,eAAgBjO,EAAQ6F,MAG9B,CAOA,gBAAAjC,CAAyBD,GACvB,GAAIA,EAAI8D,eAAiBjF,KAAKiB,OAAQ,OAItC,MAAMiE,EAAWlF,KAAKC,OAAOiF,UAAY,GACzC,IAAKA,GAAY/D,EAAI+D,WAAaA,EAAU,OAG5C,MAAMgF,EAAclK,KAAKC,OAAOkK,gBAAA,CAAoB7D,GAAeA,GAC7D8D,EAAoBjJ,EAAIsK,eAAelF,IAAKD,GAAM4D,EAAY5D,IAEpE,GAAsB,SAAlBnF,EAAIxB,UAAsB,CAC5B,MAAMiL,GAAW5K,KAAKsB,aAAanK,OAAS6I,KAAK8D,YAAYpC,QACvDmJ,EAAgB,IAAI1J,EAAIgE,YAAY2F,KAAA,CAAM3O,EAAG4O,IAAMA,EAAI5O,GAC7D,IAAK,MAAM0N,KAAOgB,EACZhB,GAAO,GAAKA,EAAMe,EAAQxT,QAAQwT,EAAQL,OAAOV,EAAK,GAE5D7J,KAAK3L,KAAKgP,KAAOuH,CACnB,CAKA5K,KAAKW,cAAe,EAEpBX,KAAKoL,KAAK,eAAgB,CACxB/H,KAAM+G,EACNa,WAAY9J,EAAI8D,aAChBiG,SAAU/J,EAAI+J,SACdC,YAAahK,EAAIgE,WACjBpB,QAAS5C,EAAI4C,QACbpE,UAAWwB,EAAIxB,WAEnB,CAEA,SAAA4F,GAME,GALIvF,KAAKU,eAAelN,EAAiBwM,KAAKU,eAC9CzD,IACA+C,KAAKgB,cAAcc,OACnB9B,KAAKyB,YAAYjL,UAAUC,OAAO,yBAE9BuJ,KAAKM,WAAY,CACnB,MAAMoL,EAAiC,CACrCrI,KAAMrD,KAAKQ,YACXwF,QAAShG,KAAKS,eACdyI,SAAUlJ,KAAKW,cAEjBX,KAAKoL,KAAK,eAAgBM,EAC5B,CACA1L,KAAK2L,mBACL3L,KAAKiC,gBACP,CAMA,YAAAoJ,CAAaO,GACX5L,KAAKoL,KAAK,eAAgBQ,EAC5B,CAGA,cAAAnB,CAAuBxJ,GACrB,MAAM4K,EAAS5S,SAAS0R,eAAe1J,GAEvC,OAAK4K,GAAQC,gBACND,EAAOC,gBAAgB,gBAAkB,KADX,IAEvC,CAKA,eAAAC,GACE,MAAM7G,EAAWlF,KAAKC,OAAOiF,UAAY,GACzC,IAAKA,EAAU,MAAO,GACtB,MAAM8G,EAAgC,GACtC,IAAK,MAAMC,KAAapN,EAAiB,CACvC,MAAMqN,EAAOD,EACTC,IAASlM,QACRkM,EAAKjM,OAAOiF,UAAY,MAAQA,GAAU8G,EAAMzY,KAAK2Y,GAC5D,CACA,OAAOF,CACT,CAGA,aAAYG,GACV,MAAM3Q,EAAOwE,KAAKyB,YAClB,OAAOjG,GAAM+H,aAAa,eAAiB/H,GAAM1B,IAAM,MACzD,CAOA,YAAAiJ,CAAqB6C,GACnB,MAAMvO,EAAQuO,EAAOpM,QAAQ,kBAC7B,IAAKnC,EAAO,OACZ,MAAMP,EAAWkJ,KAAK+F,YAAY1O,GAClC,GAAIP,EAAW,EAAG,OAElB,MAAMuM,EAAOrD,KAAKsB,aAAanK,OAAS6I,KAAK8D,WACvC5I,EAAmC,CACvC,CACED,MAAO,UACPgB,SAAuB,IAAbnF,IAAmBkJ,KAAKgE,WAAWlN,EAAUA,EAAW,GAClEoF,IAAA,IAAW8D,KAAKyE,QAAQ3N,EAAUA,EAAW,IAE/C,CACEmE,MAAO,YACPgB,SAAUnF,GAAYuM,EAAKjM,OAAS,IAAM4I,KAAKgE,WAAWlN,EAAUA,EAAW,GAC/EoF,IAAA,IAAW8D,KAAKyE,QAAQ3N,EAAUA,EAAW,IAE/C,CACEmE,MAAO,cACPgB,SAAuB,IAAbnF,IAAmBkJ,KAAKgE,WAAWlN,EAAU,GACvDoF,IAAA,IAAW8D,KAAKyE,QAAQ3N,EAAU,IAEpC,CACEmE,MAAO,iBACPgB,SAAUnF,GAAYuM,EAAKjM,OAAS,IAAM4I,KAAKgE,WAAWlN,EAAUuM,EAAKjM,OAAS,GAClF8E,IAAA,IAAW8D,KAAKyE,QAAQ3N,EAAUuM,EAAKjM,OAAS,KAI9CgV,EAA6C,UAArCpM,KAAKC,OAAON,WAAa,QAAqB,UAAY,UACxE,IAAK,MAAMuM,KAAQlM,KAAK+L,kBACtB7Q,EAAQ3H,KAAK,CACX0H,MAAO,GAAGmR,KAAQF,EAAKC,YACvBjQ,IAAA,IAAW8D,KAAKqM,eAAeH,EAAMpV,KAIzCkJ,KAAKqB,WAAaxH,EAA0B,oBAAqB,qBACjEmG,KAAKqB,SAAStG,KAAK6K,EAAQ,YAAY9O,EAAW,IAAKoE,EACzD,CAOA,cAAAmR,CAAuBH,EAA4BpV,GACjD,MAAMoO,EAAWlF,KAAKC,OAAOiF,UAAY,GACzC,IAAKA,EAAU,OAEf,MAAM7B,KAAEA,EAAA2C,QAAMA,GAAYhG,KAAKiG,mBAAmBnP,GAClD,GAAoB,IAAhBuM,EAAKjM,OAAc,OACvB,GAAI4I,KAAKC,OAAO8E,UAAY/E,KAAKC,OAAO8E,QAAQ1B,EAAK,GAAIvM,GAAW,OAEpE,MAAM6I,EAAYK,KAAKC,OAAON,WAAa,OAC3C,GAAIK,KAAKmG,eAAe,iBAAkB,CAAE9C,OAAM2C,UAASrG,YAAWuF,aAAsC,OAE5G,MAAMkB,EAAYpG,KAAKC,OAAOoG,cAAA,CAAkBC,GAASA,GACnD9I,EAA6B,CACjC1K,UAAWY,IACXuR,aAAcjF,KAAKiB,OACnBiE,WACA7B,KAAMA,EAAKkD,IAAIH,GACfjB,WAAYa,EACZrG,aAGIoE,EAAUmI,EAAKI,eAAe9O,EAAS6F,GAC7C,GAAIU,EAAU,EAAG,OAEjB,GAAkB,SAAdpE,EAAsB,CACxB,MAAMiL,EAAU,IAAK5K,KAAKsB,aAAanK,OAAS6I,KAAK8D,YACrD,IAAK,MAAM+F,IAAO,IAAI7D,GAAS8E,KAAA,CAAM3O,EAAG4O,IAAMA,EAAI5O,GAC5C0N,GAAO,GAAKA,EAAMe,EAAQxT,QAAQwT,EAAQL,OAAOV,EAAK,GAE5D7J,KAAK3L,KAAKgP,KAAOuH,CACnB,CAEA,MAAMgB,EAA+B,CACnCvI,OACA4H,WAAYjL,KAAKiB,OACjBiK,SAAUgB,EAAKjL,OACfkK,YAAanF,EACbjC,UACApE,aAEFuM,EAAKb,aAAaO,GAClB5L,KAAKoL,KAAK,eAAgBQ,EAC5B,CASA,cAAAU,CAAe9O,EAA4BgM,GACzC,MAAMtE,EAAWlF,KAAKC,OAAOiF,UAAY,GACzC,IAAKA,GAAYA,IAAa1H,EAAQ0H,SAAU,OAAO,EAEvD,MAAM7B,EAAOrD,KAAKsB,aAAanK,OAAS6I,KAAK8D,WACvC5F,EAAcmF,EAAKjM,OACzB,GAAI4I,KAAKC,OAAO+E,UAAYhF,KAAKC,OAAO+E,QAAQxH,EAASU,GAAc,OAAO,EAE9E,MAAMmM,EAA+B,CACnC7M,UACAyH,aAAczH,EAAQyH,aACtB/G,cACAyB,UAAWnC,EAAQmC,WAErB,OAAIK,KAAKmG,eAAe,WAAYkE,IAAoB,GAExDrK,KAAK3L,KAAKgP,KAAO,IAAKA,KAAwBmG,GACvCtL,EACT,CAGA,kBAAA+H,CAA2BsG,GACzB,MAAMlJ,EAAOrD,KAAKsB,aAAanK,OAAS6I,KAAK8D,WACvC0I,EAAYnJ,EAAKkJ,GAGjBE,EAAYzM,KAAK3L,MAAMyX,kBAAkB,aAE/C,GAAIW,GAAWC,sBAAuB,CACpC,MAAMC,EAAkBF,EAAUC,wBAClC,GAAIC,EAAgBjS,SAAS6R,IAAgBI,EAAgBvV,OAAS,EAAG,CACvE,MAAMwV,EAAS,IAAID,GAAiB7B,KAAA,CAAM3O,EAAG4O,IAAM5O,EAAI4O,GACvD,MAAO,CACL1H,KAAMuJ,EAAOrG,IAAKsG,GAAMxJ,EAAKwJ,IAC7B7G,QAAS4G,EAEb,CACF,CACA,MAAO,CAAEvJ,KAAM,CAACmJ,GAAYxG,QAAS,CAACuG,GACxC,CAEA,kBAAAlD,GACE,GAAIrJ,KAAKgB,aAAc,OACvB,MAAM8L,EAAW9M,KAAKyB,YAAY/J,cAA2B,kBAC7D,IAAKoV,EAAU,OACf,MAAMC,EAAyC,iBAA3B/M,KAAKC,OAAOL,WAA0BI,KAAKC,OAAOL,gBAAa,EACnFI,KAAKgB,aFv2BT,SACE8L,EACAxY,EAA6B,CAAC,EAC9B0Y,GAEA,MAAMC,EAAW3Y,EAAQ2Y,UAAY,GAC/BC,EAAY5Y,EAAQ6Y,OAAS,EAC7BC,EAAW9Y,EAAQ8Y,UAAY,GAErC,IAAIC,EAAuB,KACvBC,EAA0B,KAC1BhU,GAAS,EAEb,MAAMiU,EAAanS,IACbA,IAAS9B,IACbA,EAAS8B,EACT4R,IAAiB5R,KAGboS,EAAA,KAEJ,GADAH,EAAQ,KACS,OAAbC,EAEF,YADAC,GAAU,GAGZ,MAAMpP,EAAO2O,EAAStU,wBACtB,IAAIiV,EAAQ,EAEZ,GAAIH,EAAWnP,EAAKhJ,IAAM8X,EAAU,CAElC,MAAMS,EAAQ,EADGpY,KAAKC,IAAI,EAAG+X,EAAWnP,EAAKhJ,KAChB8X,EAC7BQ,GAASnY,KAAKsH,MAAMsQ,GAAaE,EAAWF,GAAaQ,EAC3D,MAAO,GAAIJ,EAAWnP,EAAK/I,OAAS6X,EAAU,CAE5C,MAAMS,EAAQ,EADGpY,KAAKC,IAAI,EAAG4I,EAAK/I,OAASkY,GACdL,EAC7BQ,EAAQnY,KAAKsH,MAAMsQ,GAAaE,EAAWF,GAAaQ,EAC1D,CAEA,GAAc,IAAVD,EAEF,YADAF,GAAU,GAIZ,MAAMI,EAASb,EAASnX,UACxBmX,EAASnX,UAAYgY,EAASF,EAG1BX,EAASnX,YAAcgY,GAK3BJ,GAAU,GACVF,EAAQO,sBAAsBJ,IAL5BD,GAAU,IAQd,MAAO,CACL,aAAAjE,CAAc1L,GACZ0P,EAAW1P,EACG,OAAVyP,IACFA,EAAQO,sBAAsBJ,GAElC,EACA,IAAA1L,GACgB,OAAVuL,IACFQ,qBAAqBR,GACrBA,EAAQ,MAEVC,EAAW,KACXC,GAAU,EACZ,EACA,eAAIO,GACF,OAAOxU,CACT,EAEJ,CE4xBwByU,CAAmBjB,EAAUC,EAAOzT,IACtD0G,KAAKyB,YAAYjL,UAAU2S,OAAO,2BAA4B7P,IAElE,CAEA,wBAAA8P,CAAiC/R,EAA2B4G,GAC1D+B,KAAKiJ,yBACA5R,IACLA,EAAMb,UAAUoB,IAAI,eACpBP,EAAMb,UAAU2S,OAAO,cAAelL,GACtC5G,EAAMb,UAAU2S,OAAO,cAAelL,GACxC,CAEA,sBAAAgL,GACEjJ,KAAKyB,aAAapL,iBAAiB,8BAA8BC,QAASgN,IACxEA,EAAI9M,UAAUC,OAAO,cAAe,cAAe,eAEvD,CAEA,gBAAAkV,GACE3L,KAAKyB,aAAapL,iBAAiB,kBAAkBC,QAASgN,IAC5DA,EAAI9M,UAAUC,OAAO/D,EAAsB,cAAe,cAAe,eAE7E,CAEA,cAAAuP,GACEjC,KAAKM,YAAa,EAClBN,KAAKO,gBAAkB,KACvBP,KAAKQ,YAAc,GACnBR,KAAKS,eAAiB,GACtBT,KAAKU,cAAgB,KACrBV,KAAKW,cAAe,EACpBX,KAAKY,aAAe,KACpBZ,KAAKa,YAAc,IACrB,CAEA,WAAAkF,CAAoB1O,GAClB,MAAMC,EAAOD,EAAMK,cAAc,mBACjC,OAAOJ,EAAO0W,SAAS1W,EAAKiM,aAAa,aAAe,KAAM,KAAM,CACtE,CAEA,kBAAA1B,GACM7B,KAAKc,gBACPmN,aAAajO,KAAKc,eAClBd,KAAKc,cAAgB,KAEzB,CAMA,kBAAAmD,CAA2BX,EAAQoB,EAAmBX,EAAiBmK,GAChElO,KAAKa,YAGRb,KAAKa,YAAYsN,aAAepK,EAFhC/D,KAAKa,YAAc,CAAEuN,cAAe1J,EAAWyJ,aAAcpK,EAAST,OAIxEtD,KAAKe,aAAemN,EAEpB,MAAM7Z,EAAO2L,KAAKsB,aACZ+B,EAAO,IAAKhP,EAAK8C,OAAS6I,KAAK8D,aAC9BuK,GAAYhL,EAAKkH,OAAO7F,EAAW,GAC1CrB,EAAKkH,OAAOxG,EAAS,EAAGsK,GAExBha,EAAK8C,MAAQkM,EACbhP,EAAKa,UAAY6O,EACjB1P,EAAKmD,UAAY0W,EACjB7Z,EAAK8B,sBAAqB,GAC1B/B,EAAkBC,GAElB2L,KAAK6B,qBACL7B,KAAKc,cAAgB2G,WAAA,IAAiBzH,KAAKwE,mBAAoBxE,KAAKC,OAAOR,YAAc,IAC3F,CAEA,gBAAA+E,GAEE,GADAxE,KAAK6B,sBACA7B,KAAKa,YAAa,OACvB,MAAMuN,cAAEA,EAAAD,aAAeA,EAAc7K,IAAK+K,GAAarO,KAAKa,YAE5D,GADAb,KAAKa,YAAc,KACfuN,IAAkBD,EAAc,OAEpC,MAAM9Z,EAAO2L,KAAKsB,aAKZsK,EAA2B,CAC/BtI,IAAK+K,EACL3J,UAAW0J,EACXrK,QAASoK,EACT9K,KAAM,IALchP,EAAK8C,OAAS6I,KAAK8D,YAMvCwK,OAAQ,YAEQtO,KAAKmG,eAAe,WAAYyF,KAMhDvX,EAAK8C,MAAQ,IAAI6I,KAAK8D,YACtBzP,EAAKa,UAAYkZ,EACjB/Z,EAAKmD,UAAYwI,KAAKe,aACtB1M,EAAK8B,sBAAqB,GAC1B/B,EAAkBC,GAEtB,CAEA,oBAAAsQ,CAA6BrB,EAAcoB,EAAmBX,EAAiBuK,GAC7E,MAAMjL,EAAO,IAAIrD,KAAK8D,aACfuK,GAAYhL,EAAKkH,OAAO7F,EAAW,GAC1CrB,EAAKkH,OAAOxG,EAAS,EAAGsK,GACxB,MAAMzC,EAA2B,CAC1BtI,MACLoB,YACAX,UACMV,OACNiL,UAGF,IADkBtO,KAAKmG,eAAe,WAAYyF,GAElD,GAA2B,SAAvB5L,KAAKI,eAA4BJ,KAAKyB,YAAa,CACrD,MAAM8M,EAAevO,KAAKwO,sBAC1BxO,KAAK3L,KAAKgP,KAAOA,EACjBuK,sBAAA,KACE5N,KAAUyB,YAAYgN,aACtBzO,KAAK0O,YAAYH,EAAc7J,EAAWX,IAE9C,MACE/D,KAAK3L,KAAKgP,KAAOA,CAErB,CAEA,mBAAAmL,GACE,MAAMG,iBAAY,IAAI/b,IAKtB,OAJAoN,KAAKyB,aAAapL,iBAAiB,kBAAkBC,QAASgN,IAC5D,MAAMxM,EAAWkJ,KAAK+F,YAAYzC,GAC9BxM,GAAY,GAAG6X,EAAU/G,IAAI9Q,EAAUwM,EAAI9K,wBAAwBrD,OAElEwZ,CACT,CAEA,WAAAD,CAAoBH,EAAmC7J,EAAmBX,GACxE,MAAMqB,EAASpF,KAAKyB,YACpB,IAAK2D,GAAgC,IAAtBmJ,EAAaxM,KAAY,OAExC,MAAM6M,EAAWtZ,KAAK+G,IAAIqI,EAAWX,GAC/B8K,EAAWvZ,KAAKC,IAAImP,EAAWX,GAC/B+K,EAAuD,GAiB7D,GAfA1J,EAAO/O,iBAAiB,kBAAkBC,QAASgN,IACjD,MAAMjM,EAAQiM,EACRyL,EAAc/O,KAAK+F,YAAY1O,GACrC,GAAI0X,EAAc,GAAKA,EAAcH,GAAYG,EAAcF,EAAU,OACzE,IAAIG,EACyBA,EAAzBD,IAAgBhL,EAAoBW,EAC/BA,EAAYX,EAAoBgL,EAAc,EACvCA,EAAc,EAC9B,MAAME,EAASV,EAAavb,IAAIgc,GAChC,QAAe,IAAXC,EAAsB,OAE1B,MAAMC,EAASD,EADA5X,EAAMmB,wBAAwBrD,IAEzCG,KAAK6Z,IAAID,GAAU,GAAGJ,EAAcvb,KAAK,CAAEgD,GAAIc,EAAO6X,aAG/B,IAAzBJ,EAAc1X,OAAc,OAEhC0X,EAAcxY,QAAA,EAAWC,KAAI2Y,aAC3B3Y,EAAGoG,MAAMyS,UAAY,cAAcF,SAErC9J,EAAYqJ,aAEZ,MAAMY,EAAWrP,KAAKsP,kBACtB1B,sBAAA,KACEkB,EAAcxY,QAAA,EAAWC,SACvBA,EAAGC,UAAUoB,IAAI,kBACjBrB,EAAGoG,MAAMyS,UAAY,KAEvB3H,WAAA,KACEqH,EAAcxY,QAAA,EAAWC,SACvBA,EAAGoG,MAAMyS,UAAY,GACrB7Y,EAAGC,UAAUC,OAAO,qBAErB4Y,EAAW,KAElB,U"}