apex-grid 3.2.0 → 3.4.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 (205) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/README.md +229 -52
  3. package/components/cell.d.ts +16 -3
  4. package/components/cell.js +78 -8
  5. package/components/cell.js.map +1 -1
  6. package/components/filter-row.d.ts +3 -6
  7. package/components/filter-row.js +144 -115
  8. package/components/filter-row.js.map +1 -1
  9. package/components/grid.d.ts +421 -77
  10. package/components/grid.js +432 -13
  11. package/components/grid.js.map +1 -1
  12. package/components/group-header-row.d.ts +34 -0
  13. package/components/group-header-row.js +125 -0
  14. package/components/group-header-row.js.map +1 -0
  15. package/components/header-row.d.ts +13 -2
  16. package/components/header-row.js +33 -18
  17. package/components/header-row.js.map +1 -1
  18. package/components/header.d.ts +11 -0
  19. package/components/header.js +209 -8
  20. package/components/header.js.map +1 -1
  21. package/components/paginator.d.ts +6 -1
  22. package/components/paginator.js +17 -11
  23. package/components/paginator.js.map +1 -1
  24. package/components/row.d.ts +28 -0
  25. package/components/row.js +100 -3
  26. package/components/row.js.map +1 -1
  27. package/components/toolbar.d.ts +8 -1
  28. package/components/toolbar.js +25 -7
  29. package/components/toolbar.js.map +1 -1
  30. package/controllers/data-operation.js +1 -0
  31. package/controllers/data-operation.js.map +1 -1
  32. package/controllers/dom.d.ts +24 -6
  33. package/controllers/dom.js +62 -5
  34. package/controllers/dom.js.map +1 -1
  35. package/controllers/editing.d.ts +68 -1
  36. package/controllers/editing.js +129 -44
  37. package/controllers/editing.js.map +1 -1
  38. package/controllers/expansion.js +4 -4
  39. package/controllers/expansion.js.map +1 -1
  40. package/controllers/filter.d.ts +1 -1
  41. package/controllers/filter.js +27 -5
  42. package/controllers/filter.js.map +1 -1
  43. package/controllers/history.d.ts +86 -0
  44. package/controllers/history.js +125 -0
  45. package/controllers/history.js.map +1 -0
  46. package/controllers/navigation.d.ts +49 -2
  47. package/controllers/navigation.js +145 -3
  48. package/controllers/navigation.js.map +1 -1
  49. package/controllers/pagination.js +1 -1
  50. package/controllers/pagination.js.map +1 -1
  51. package/controllers/reorder.js +3 -1
  52. package/controllers/reorder.js.map +1 -1
  53. package/controllers/row-pin.d.ts +59 -0
  54. package/controllers/row-pin.js +75 -0
  55. package/controllers/row-pin.js.map +1 -0
  56. package/controllers/row-reorder.d.ts +161 -0
  57. package/controllers/row-reorder.js +332 -0
  58. package/controllers/row-reorder.js.map +1 -0
  59. package/controllers/selection.js +3 -3
  60. package/controllers/selection.js.map +1 -1
  61. package/controllers/sort.js +13 -3
  62. package/controllers/sort.js.map +1 -1
  63. package/controllers/state.d.ts +33 -0
  64. package/controllers/state.js +37 -2
  65. package/controllers/state.js.map +1 -1
  66. package/controllers/tree.d.ts +8 -0
  67. package/controllers/tree.js +0 -0
  68. package/controllers/tree.js.map +1 -1
  69. package/custom-elements.json +4667 -926
  70. package/docs/assets/hierarchy.js +1 -1
  71. package/docs/assets/highlight.css +28 -0
  72. package/docs/assets/navigation.js +1 -1
  73. package/docs/assets/search.js +1 -1
  74. package/docs/functions/custom.html +3 -0
  75. package/docs/functions/interpolate.html +4 -0
  76. package/docs/functions/localize.html +8 -0
  77. package/docs/functions/max.html +3 -0
  78. package/docs/functions/min.html +3 -0
  79. package/docs/functions/pattern.html +3 -0
  80. package/docs/functions/required.html +3 -0
  81. package/docs/hierarchy.html +1 -1
  82. package/docs/index.html +245 -54
  83. package/docs/interfaces/ApexCellValidationFailedEvent.html +13 -0
  84. package/docs/interfaces/ApexCellValueChangedEvent.html +1 -1
  85. package/docs/interfaces/ApexCellValueChangingEvent.html +2 -2
  86. package/docs/interfaces/ApexColumnGroupContext.html +6 -0
  87. package/docs/interfaces/ApexColumnMovedEvent.html +2 -2
  88. package/docs/interfaces/ApexColumnMovingEvent.html +1 -1
  89. package/docs/interfaces/ApexDetailContext.html +2 -2
  90. package/docs/interfaces/ApexGridEventMap.html +37 -9
  91. package/docs/interfaces/ApexHistoryChangedEvent.html +6 -0
  92. package/docs/interfaces/ApexPageChangedEvent.html +1 -1
  93. package/docs/interfaces/ApexRowMovingEvent.html +9 -0
  94. package/docs/interfaces/ApexRowPinningEvent.html +8 -0
  95. package/docs/interfaces/ApexStateChangedEvent.html +4 -0
  96. package/docs/interfaces/BaseApexCellContext.html +1 -1
  97. package/docs/interfaces/BaseApexEditorContext.html +1 -1
  98. package/docs/interfaces/BaseColumnConfiguration.html +42 -17
  99. package/docs/interfaces/CSVExportOptions.html +2 -2
  100. package/docs/interfaces/ColumnGroupConfiguration.html +12 -0
  101. package/docs/interfaces/ColumnLayoutState.html +2 -0
  102. package/docs/interfaces/ColumnSchema.html +22 -0
  103. package/docs/interfaces/DataPipelineConfiguration.html +2 -2
  104. package/docs/interfaces/EditingHistoryConfiguration.html +10 -0
  105. package/docs/interfaces/ExportFormat.html +2 -2
  106. package/docs/interfaces/ExportOptions.html +1 -1
  107. package/docs/interfaces/FilterStateSnapshot.html +4 -0
  108. package/docs/interfaces/GetStateOptions.html +4 -0
  109. package/docs/interfaces/GridCapabilities.html +8 -0
  110. package/docs/interfaces/GridEditingConfiguration.html +4 -2
  111. package/docs/interfaces/GridRowPinningConfiguration.html +5 -0
  112. package/docs/interfaces/GridRowReorderingConfiguration.html +22 -0
  113. package/docs/interfaces/GridSchema.html +19 -0
  114. package/docs/interfaces/GridState.html +41 -0
  115. package/docs/interfaces/GridTreeConfiguration.html +2 -2
  116. package/docs/interfaces/PaginationConfiguration.html +1 -1
  117. package/docs/interfaces/PaginationState.html +1 -1
  118. package/docs/interfaces/SetStateOptions.html +9 -0
  119. package/docs/interfaces/SetStateResult.html +11 -0
  120. package/docs/interfaces/SortStateSnapshot.html +2 -0
  121. package/docs/interfaces/ValidatorContext.html +8 -0
  122. package/docs/media/_tokens.scss +70 -81
  123. package/docs/media/package.json +2 -2
  124. package/docs/media/state-persistence.html +189 -0
  125. package/docs/modules.html +1 -1
  126. package/docs/types/DataPipelineParams.html +2 -2
  127. package/docs/types/EditMode.html +2 -2
  128. package/docs/types/GridLocaleKey.html +3 -0
  129. package/docs/types/GridLocaleText.html +5 -0
  130. package/docs/types/LocaleParams.html +2 -0
  131. package/docs/types/PaginationMode.html +1 -1
  132. package/docs/types/RowDropPosition.html +2 -0
  133. package/docs/types/RowPinPosition.html +2 -0
  134. package/docs/types/RowRef.html +3 -0
  135. package/docs/types/Validator.html +4 -0
  136. package/docs/variables/EN_LOCALE.html +9 -0
  137. package/docs/variables/esLocale.html +8 -0
  138. package/i18n/en.d.ts +245 -0
  139. package/i18n/en.js +219 -0
  140. package/i18n/en.js.map +1 -0
  141. package/i18n/es.d.ts +18 -0
  142. package/i18n/es.js +219 -0
  143. package/i18n/es.js.map +1 -0
  144. package/i18n/index.d.ts +3 -0
  145. package/i18n/index.js +4 -0
  146. package/i18n/index.js.map +1 -0
  147. package/i18n/localize.d.ts +23 -0
  148. package/i18n/localize.js +15 -0
  149. package/i18n/localize.js.map +1 -0
  150. package/index.d.ts +8 -2
  151. package/index.js +2 -0
  152. package/index.js.map +1 -1
  153. package/internal/column-types.js +1 -1
  154. package/internal/column-types.js.map +1 -1
  155. package/internal/feature-module.d.ts +46 -0
  156. package/internal/feature-module.js +11 -0
  157. package/internal/feature-module.js.map +1 -1
  158. package/internal/flip.js +1 -1
  159. package/internal/flip.js.map +1 -1
  160. package/internal/icons.d.ts +9 -7
  161. package/internal/icons.js +75 -35
  162. package/internal/icons.js.map +1 -1
  163. package/internal/index.d.ts +5 -3
  164. package/internal/index.js +2 -1
  165. package/internal/index.js.map +1 -1
  166. package/internal/state-schema.d.ts +85 -0
  167. package/internal/state-schema.js +27 -0
  168. package/internal/state-schema.js.map +1 -0
  169. package/internal/state-snapshot.d.ts +152 -0
  170. package/internal/state-snapshot.js +115 -0
  171. package/internal/state-snapshot.js.map +1 -0
  172. package/internal/tags.d.ts +1 -0
  173. package/internal/tags.js +1 -0
  174. package/internal/tags.js.map +1 -1
  175. package/internal/types.d.ts +162 -2
  176. package/internal/types.js.map +1 -1
  177. package/internal/utils.d.ts +12 -0
  178. package/internal/utils.js +17 -0
  179. package/internal/utils.js.map +1 -1
  180. package/internal/validators.d.ts +31 -0
  181. package/internal/validators.js +44 -0
  182. package/internal/validators.js.map +1 -0
  183. package/package.json +1 -1
  184. package/styles/_tokens.css.js +1 -1
  185. package/styles/_tokens.css.js.map +1 -1
  186. package/styles/body-cell/body-cell.css.js +1 -1
  187. package/styles/body-cell/body-cell.css.js.map +1 -1
  188. package/styles/body-row/body-row.css.js +1 -1
  189. package/styles/body-row/body-row.css.js.map +1 -1
  190. package/styles/filter-row/filter-row.css.js +1 -1
  191. package/styles/filter-row/filter-row.css.js.map +1 -1
  192. package/styles/grid/_grid.base.css.js +1 -1
  193. package/styles/grid/_grid.base.css.js.map +1 -1
  194. package/styles/grid/grid.css.js +1 -1
  195. package/styles/grid/grid.css.js.map +1 -1
  196. package/styles/group-header-row/group-header-row.css.d.ts +1 -0
  197. package/styles/group-header-row/group-header-row.css.js +3 -0
  198. package/styles/group-header-row/group-header-row.css.js.map +1 -0
  199. package/styles/header-cell/header-cell.css.js +1 -1
  200. package/styles/header-cell/header-cell.css.js.map +1 -1
  201. package/styles/header-row/header-row.base.css.js +1 -1
  202. package/styles/header-row/header-row.base.css.js.map +1 -1
  203. package/styles/toolbar/toolbar.css.js +1 -1
  204. package/styles/toolbar/toolbar.css.js.map +1 -1
  205. package/docs/classes/ApexGrid.html +0 -399
@@ -0,0 +1,161 @@
1
+ import type { ReactiveController } from 'lit';
2
+ import type { GridHost } from '../internal/types.js';
3
+ /** Where a moved row lands relative to its target. */
4
+ export type RowDropPosition = 'before' | 'after';
5
+ /** A single cell snapshot used to paint the floating row ghost. */
6
+ export interface RowGhostCell {
7
+ /** The cell's rendered text at grab time. */
8
+ text: string;
9
+ /** The cell's rendered width (px), so the ghost matches the column layout. */
10
+ width: number;
11
+ /** The cell's computed `text-align`, so numbers stay right-aligned in the ghost. */
12
+ align: string;
13
+ }
14
+ /** Everything the row hands the controller to seed the floating ghost. */
15
+ export interface RowGhostInit {
16
+ /** The source row's bounding rect at grab time. */
17
+ rect: DOMRect;
18
+ /** Pointer position at grab time (to keep the grab point under the cursor). */
19
+ clientX: number;
20
+ clientY: number;
21
+ /** Per-cell snapshot of the row's visible content. */
22
+ cells: RowGhostCell[];
23
+ }
24
+ /**
25
+ * Live state for the floating "ghost" row that trails the cursor during a
26
+ * pointer drag. Mirrors {@link ReorderController}'s column ghost: the source
27
+ * row stays in the grid (and live-swaps), the ghost is a purely visual proxy.
28
+ */
29
+ export interface RowGhostState {
30
+ /** Ghost position (viewport-relative top-left, in CSS pixels). */
31
+ x: number;
32
+ y: number;
33
+ /** Ghost size, fixed at the source row's initial bounding rect. */
34
+ width: number;
35
+ height: number;
36
+ /** Cursor offset within the ghost so the grab point stays under the cursor. */
37
+ pointerOffsetX: number;
38
+ pointerOffsetY: number;
39
+ /** Snapshot of the row's visible cells. */
40
+ cells: ReadonlyArray<RowGhostCell>;
41
+ }
42
+ /**
43
+ * Reactive controller backing row drag-reorder and a manual row order.
44
+ *
45
+ * @remarks
46
+ * Row order is normally derived (filter → sort). This controller adds a
47
+ * **manual order**: an explicit sequence of row references applied as a
48
+ * pipeline step after sort. While a manual order is active, sorting is mutually
49
+ * exclusive (applying a sort clears the manual order). State is reference-based,
50
+ * so it survives the in-place pipeline.
51
+ *
52
+ * Reordering happens through {@link moveRow} (pointer live-swap during a drag,
53
+ * the keyboard grab/move flow, or the public API). Each move goes through the
54
+ * cancellable `rowMoving` event and emits a follow-up `rowMoved`, then animates
55
+ * with a Y-axis FLIP. Pinned rows (F4) are never the drag source and reorder
56
+ * only within the unpinned body set.
57
+ *
58
+ * Persistence: by default the order lives in this controller and the app
59
+ * persists via `rowMoved`. With `rowReordering.applyToData` the move also
60
+ * splices {@link ApexGrid.data} in place (and clears the manual order, since the
61
+ * data array then carries the order).
62
+ */
63
+ export declare class RowReorderController<T extends object> implements ReactiveController {
64
+ #private;
65
+ protected host: GridHost<T>;
66
+ /** The row currently being pointer-dragged, or `null`. */
67
+ dragging: T | null;
68
+ /**
69
+ * Live floating-ghost state during a pointer drag, or `null`. Surfaced to the
70
+ * grid, which renders the ghost at the top of its shadow tree (outside the
71
+ * virtualizer) so `position: fixed` tracks the viewport cleanly.
72
+ */
73
+ ghost: RowGhostState | null;
74
+ /** The row currently grabbed for keyboard reorder, or `null`. */
75
+ grabbed: T | null;
76
+ constructor(host: GridHost<T>);
77
+ hostConnected(): void;
78
+ /** Whether row reordering is enabled at the grid level. */
79
+ get enabled(): boolean;
80
+ /**
81
+ * Whether a pointer drag must start from the dedicated grip handle (the
82
+ * default), rather than from anywhere on the row. Driven by
83
+ * `rowReordering.handle` (defaults to `true`).
84
+ */
85
+ get handleMode(): boolean;
86
+ /**
87
+ * Whether the leading grip-handle column is rendered. Only in handle mode, and
88
+ * only while reordering is enabled.
89
+ */
90
+ get showHandleColumn(): boolean;
91
+ /** Whether moves also splice {@link ApexGrid.data} in place. */
92
+ get applyToData(): boolean;
93
+ /** Whether a manual order is currently active. */
94
+ get hasManualOrder(): boolean;
95
+ /** Whether a row is currently grabbed for keyboard reorder. */
96
+ get isGrabbing(): boolean;
97
+ /**
98
+ * Pipeline step: reorders the (post-filter, post-sort) rows by the manual
99
+ * order. Rows absent from the manual order (e.g. newly added) keep their
100
+ * incoming relative order at the end. Identity pass-through when inactive.
101
+ */
102
+ apply(rows: T[]): T[];
103
+ /**
104
+ * The current manual order as a copy of its row references, or `null` for the
105
+ * derived order. Consumed by `ApexGrid.getState`.
106
+ */
107
+ getManualOrder(): T[] | null;
108
+ /**
109
+ * Restores a manual order from a list of row references (state restore). Pass
110
+ * `null` (or an empty list) to clear it. Silent: emits no `rowMoving` /
111
+ * `rowMoved` and does not animate; re-runs the pipeline.
112
+ */
113
+ restoreManualOrder(order: T[] | null): void;
114
+ /**
115
+ * Clears the manual order (e.g. when a sort is applied). Re-runs the pipeline.
116
+ */
117
+ clearManualOrder(announce?: boolean): void;
118
+ /**
119
+ * Moves the row at view index `from` relative to the row at view index `to`.
120
+ * Indices are into {@link ApexGrid.pageItems}.
121
+ *
122
+ * @returns `true` when applied, `false` when disabled, out of range, or
123
+ * cancelled via `rowMoving`.
124
+ */
125
+ moveRow(from: number, to: number, position?: RowDropPosition): boolean;
126
+ /**
127
+ * Begins a pointer drag from `source`. When `ghost` is supplied (the pointer
128
+ * path always does), seeds the floating ghost from the source row's rect and
129
+ * the grab point so it stays anchored under the cursor.
130
+ */
131
+ startDrag(source: T, ghost?: RowGhostInit): void;
132
+ /** Moves the floating ghost to track the cursor. No-op without an active ghost. */
133
+ moveGhost(clientX: number, clientY: number): void;
134
+ /**
135
+ * Called on pointer-move during a drag: live-swaps the dragged row past the
136
+ * body row currently under `clientY` once the cursor crosses its midpoint.
137
+ */
138
+ dragOver(clientY: number): void;
139
+ /** Ends the pointer drag. The order is already final (live-swapped). */
140
+ endDrag(): void;
141
+ /**
142
+ * Arms a pointer drag from a row press. Called by the row on `pointerdown`
143
+ * (from the grip in handle mode, or anywhere in whole-row mode). The move /
144
+ * up / cancel listeners are bound to the **grid host**, so the gesture is
145
+ * immune to the source row's DOM being recycled by a live-swap. `getGhost` is
146
+ * a thunk the controller invokes once, the instant the drag engages, to
147
+ * snapshot the row's cells (the row is still in place then).
148
+ */
149
+ armPointerDrag(data: T, getGhost: () => RowGhostInit, event: PointerEvent): void;
150
+ /** Grabs the row at view index `rowIndex` for keyboard reorder. */
151
+ grab(rowIndex: number): boolean;
152
+ /**
153
+ * Moves the grabbed row one position in `direction`. Returns the grabbed
154
+ * row's new view index, or `-1` when it can't move (no grab / at the edge).
155
+ */
156
+ moveGrabbed(direction: -1 | 1): number;
157
+ /** Drops the grabbed row, keeping its moves. */
158
+ drop(): void;
159
+ /** Cancels the grab, reverting to the order captured when it was grabbed. */
160
+ cancelGrab(): void;
161
+ }
@@ -0,0 +1,332 @@
1
+ import { PIPELINE } from '../internal/constants.js';
2
+ import { awaitChildUpdates, playKeyedFlip } from '../internal/flip.js';
3
+ const DRAG_THRESHOLD_PX = 4;
4
+ export class RowReorderController {
5
+ #order;
6
+ #preGrab;
7
+ #swapping;
8
+ #pending;
9
+ constructor(host) {
10
+ this.host = host;
11
+ this.#order = null;
12
+ this.#preGrab = null;
13
+ this.dragging = null;
14
+ this.ghost = null;
15
+ this.grabbed = null;
16
+ this.#swapping = false;
17
+ this.#pending = null;
18
+ this.#onHostPointerMove = (event) => {
19
+ const pending = this.#pending;
20
+ if (!pending || event.pointerId !== pending.pointerId)
21
+ return;
22
+ if (!this.dragging) {
23
+ const dx = event.clientX - pending.startX;
24
+ const dy = event.clientY - pending.startY;
25
+ if (Math.hypot(dx, dy) < DRAG_THRESHOLD_PX)
26
+ return;
27
+ try {
28
+ this.#hostElement.setPointerCapture(pending.pointerId);
29
+ }
30
+ catch {
31
+ }
32
+ this.startDrag(pending.data, pending.getGhost());
33
+ }
34
+ this.moveGhost(event.clientX, event.clientY);
35
+ this.dragOver(event.clientY);
36
+ };
37
+ this.#onHostPointerUp = (event) => {
38
+ const pending = this.#pending;
39
+ if (!pending || event.pointerId !== pending.pointerId)
40
+ return;
41
+ const host = this.#hostElement;
42
+ host.removeEventListener('pointermove', this.#onHostPointerMove);
43
+ host.removeEventListener('pointerup', this.#onHostPointerUp);
44
+ host.removeEventListener('pointercancel', this.#onHostPointerUp);
45
+ try {
46
+ if (host.hasPointerCapture(pending.pointerId))
47
+ host.releasePointerCapture(pending.pointerId);
48
+ }
49
+ catch {
50
+ }
51
+ this.#pending = null;
52
+ this.endDrag();
53
+ };
54
+ this.host.addController(this);
55
+ }
56
+ get #hostElement() {
57
+ return this.host;
58
+ }
59
+ hostConnected() { }
60
+ get enabled() {
61
+ return Boolean(this.host.rowReordering?.enabled);
62
+ }
63
+ get handleMode() {
64
+ return this.enabled && this.host.rowReordering?.handle !== false;
65
+ }
66
+ get showHandleColumn() {
67
+ return this.handleMode;
68
+ }
69
+ get applyToData() {
70
+ return Boolean(this.host.rowReordering?.applyToData);
71
+ }
72
+ get hasManualOrder() {
73
+ return this.#order !== null;
74
+ }
75
+ get isGrabbing() {
76
+ return this.grabbed !== null;
77
+ }
78
+ apply(rows) {
79
+ if (!this.#order)
80
+ return rows;
81
+ const rank = new Map();
82
+ this.#order.forEach((row, i) => {
83
+ rank.set(row, i);
84
+ });
85
+ return [...rows].sort((a, b) => {
86
+ const ra = rank.has(a) ? rank.get(a) : Number.POSITIVE_INFINITY;
87
+ const rb = rank.has(b) ? rank.get(b) : Number.POSITIVE_INFINITY;
88
+ return ra - rb;
89
+ });
90
+ }
91
+ getManualOrder() {
92
+ return this.#order ? [...this.#order] : null;
93
+ }
94
+ restoreManualOrder(order) {
95
+ this.#order = order?.length ? [...order] : null;
96
+ this.host.requestUpdate(PIPELINE);
97
+ }
98
+ clearManualOrder(announce = false) {
99
+ if (!this.#order)
100
+ return;
101
+ this.#order = null;
102
+ this.host.requestUpdate(PIPELINE);
103
+ if (announce)
104
+ this.host.announce(this.host.localize('announce.manualOrderCleared'));
105
+ }
106
+ moveRow(from, to, position = 'before') {
107
+ const view = this.host.pageItems;
108
+ const before = this.#captureRowRects();
109
+ const ok = this.#commit(view[from], view[to], position);
110
+ if (ok)
111
+ void this.#flipAfterUpdate(before);
112
+ return ok;
113
+ }
114
+ startDrag(source, ghost) {
115
+ if (!this.enabled)
116
+ return;
117
+ this.dragging = source;
118
+ this.ghost = ghost
119
+ ? {
120
+ x: ghost.rect.left,
121
+ y: ghost.rect.top,
122
+ width: ghost.rect.width,
123
+ height: ghost.rect.height,
124
+ pointerOffsetX: ghost.clientX - ghost.rect.left,
125
+ pointerOffsetY: ghost.clientY - ghost.rect.top,
126
+ cells: ghost.cells,
127
+ }
128
+ : null;
129
+ this.host.requestUpdate();
130
+ }
131
+ moveGhost(clientX, clientY) {
132
+ if (!this.ghost)
133
+ return;
134
+ this.ghost = {
135
+ ...this.ghost,
136
+ x: clientX - this.ghost.pointerOffsetX,
137
+ y: clientY - this.ghost.pointerOffsetY,
138
+ };
139
+ this.host.requestUpdate();
140
+ }
141
+ dragOver(clientY) {
142
+ if (!this.dragging || this.#swapping)
143
+ return;
144
+ const target = this.#bodyRowAt(clientY);
145
+ if (!target || target.data === this.dragging)
146
+ return;
147
+ const view = this.host.pageItems;
148
+ const sourceIdx = view.indexOf(this.dragging);
149
+ const targetIdx = view.indexOf(target.data);
150
+ if (sourceIdx === -1 || targetIdx === -1)
151
+ return;
152
+ const rect = target.element.getBoundingClientRect();
153
+ const midpoint = rect.top + rect.height / 2;
154
+ let position;
155
+ if (sourceIdx < targetIdx) {
156
+ if (clientY < midpoint)
157
+ return;
158
+ position = 'after';
159
+ }
160
+ else {
161
+ if (clientY > midpoint)
162
+ return;
163
+ position = 'before';
164
+ }
165
+ void this.#dragSwap(target.data, position);
166
+ }
167
+ endDrag() {
168
+ if (!this.dragging)
169
+ return;
170
+ this.dragging = null;
171
+ this.ghost = null;
172
+ this.host.requestUpdate();
173
+ }
174
+ armPointerDrag(data, getGhost, event) {
175
+ const pinned = this.host.pinnedRows;
176
+ if (!this.enabled || pinned.top.includes(data) || pinned.bottom.includes(data))
177
+ return;
178
+ this.#pending = {
179
+ data,
180
+ getGhost,
181
+ startX: event.clientX,
182
+ startY: event.clientY,
183
+ pointerId: event.pointerId,
184
+ };
185
+ const host = this.#hostElement;
186
+ host.addEventListener('pointermove', this.#onHostPointerMove);
187
+ host.addEventListener('pointerup', this.#onHostPointerUp);
188
+ host.addEventListener('pointercancel', this.#onHostPointerUp);
189
+ }
190
+ #onHostPointerMove;
191
+ #onHostPointerUp;
192
+ async #dragSwap(target, position) {
193
+ if (this.#swapping)
194
+ return;
195
+ this.#swapping = true;
196
+ try {
197
+ const before = this.#captureRowRects();
198
+ if (this.#commit(this.dragging, target, position)) {
199
+ await this.host.updateComplete;
200
+ await awaitChildUpdates(this.host.rows);
201
+ this.#playRowFlip(before);
202
+ }
203
+ }
204
+ finally {
205
+ this.#swapping = false;
206
+ }
207
+ }
208
+ grab(rowIndex) {
209
+ if (!this.enabled)
210
+ return false;
211
+ const row = this.host.pageItems[rowIndex];
212
+ if (!row)
213
+ return false;
214
+ this.grabbed = row;
215
+ this.#preGrab = this.#order ? [...this.#order] : null;
216
+ this.host.requestUpdate();
217
+ this.host.announce(this.host.localize('announce.rowGrabbed'));
218
+ return true;
219
+ }
220
+ moveGrabbed(direction) {
221
+ if (!this.grabbed)
222
+ return -1;
223
+ const view = this.host.pageItems;
224
+ const current = view.indexOf(this.grabbed);
225
+ const target = current + direction;
226
+ if (current === -1 || target < 0 || target >= view.length)
227
+ return -1;
228
+ const before = this.#captureRowRects();
229
+ const ok = this.#commit(this.grabbed, view[target], direction > 0 ? 'after' : 'before', {
230
+ silent: true,
231
+ });
232
+ if (!ok)
233
+ return -1;
234
+ void this.#flipAfterUpdate(before);
235
+ this.host.announce(this.host.localize('announce.rowMovedTo', { position: target + 1 }));
236
+ return target;
237
+ }
238
+ drop() {
239
+ if (!this.grabbed)
240
+ return;
241
+ this.grabbed = null;
242
+ this.#preGrab = null;
243
+ this.host.requestUpdate();
244
+ this.host.announce(this.host.localize('announce.rowDropped'));
245
+ }
246
+ cancelGrab() {
247
+ if (!this.grabbed)
248
+ return;
249
+ this.grabbed = null;
250
+ this.#order = this.#preGrab;
251
+ this.#preGrab = null;
252
+ this.host.requestUpdate(PIPELINE);
253
+ this.host.announce(this.host.localize('announce.reorderCancelled'));
254
+ }
255
+ #commit(source, target, position, options = {}) {
256
+ if (!this.enabled || !source || !target || source === target)
257
+ return false;
258
+ const view = this.host.pageItems;
259
+ const from = view.indexOf(source);
260
+ const to = view.indexOf(target);
261
+ const proceed = this.host.emitEvent('rowMoving', {
262
+ detail: { from, to, data: source },
263
+ cancelable: true,
264
+ });
265
+ if (!proceed)
266
+ return false;
267
+ const baseline = this.#order ?? [...this.host.dataView];
268
+ const next = baseline.filter((row) => row !== source);
269
+ let index = next.indexOf(target);
270
+ if (index === -1)
271
+ return false;
272
+ if (position === 'after')
273
+ index += 1;
274
+ next.splice(index, 0, source);
275
+ if (this.applyToData) {
276
+ this.#spliceData(next);
277
+ this.#order = null;
278
+ }
279
+ else {
280
+ this.#order = next;
281
+ }
282
+ this.host.requestUpdate(PIPELINE);
283
+ if (!options.silent)
284
+ this.host.announce(this.host.localize('announce.rowMoved'));
285
+ this.host.emitEvent('rowMoved', { detail: { from, to, data: source } });
286
+ return true;
287
+ }
288
+ #spliceData(order) {
289
+ const rank = new Map();
290
+ order.forEach((row, i) => {
291
+ rank.set(row, i);
292
+ });
293
+ this.host.data.sort((a, b) => {
294
+ const ra = rank.has(a) ? rank.get(a) : Number.POSITIVE_INFINITY;
295
+ const rb = rank.has(b) ? rank.get(b) : Number.POSITIVE_INFINITY;
296
+ return ra - rb;
297
+ });
298
+ }
299
+ #bodyRowAt(clientY) {
300
+ const pinned = this.host.pinnedRows;
301
+ for (const row of this.host.rows) {
302
+ if (pinned.top.includes(row.data) || pinned.bottom.includes(row.data))
303
+ continue;
304
+ const element = row;
305
+ const rect = element.getBoundingClientRect();
306
+ if (clientY >= rect.top && clientY < rect.bottom)
307
+ return { data: row.data, element };
308
+ }
309
+ return null;
310
+ }
311
+ #captureRowRects() {
312
+ const entries = [];
313
+ for (const row of this.host.rows) {
314
+ const el = row;
315
+ entries.push({ key: row.data, rect: el.getBoundingClientRect() });
316
+ }
317
+ return entries;
318
+ }
319
+ async #flipAfterUpdate(before) {
320
+ await this.host.updateComplete;
321
+ await awaitChildUpdates(this.host.rows);
322
+ this.#playRowFlip(before);
323
+ }
324
+ #playRowFlip(before) {
325
+ const byData = new Map();
326
+ for (const row of this.host.rows) {
327
+ byData.set(row.data, row);
328
+ }
329
+ playKeyedFlip(before, (data) => byData.get(data) ?? null, 'y');
330
+ }
331
+ }
332
+ //# sourceMappingURL=row-reorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-reorder.js","sourceRoot":"","sources":["../../src/controllers/row-reorder.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport { PIPELINE } from '../internal/constants.js';\nimport { awaitChildUpdates, type KeyedFlipEntry, playKeyedFlip } from '../internal/flip.js';\nimport type { GridHost } from '../internal/types.js';\n\n/** Where a moved row lands relative to its target. */\nexport type RowDropPosition = 'before' | 'after';\n\n/** Pixels the pointer must travel before a row drag engages. */\nconst DRAG_THRESHOLD_PX = 4;\n\n/** A single cell snapshot used to paint the floating row ghost. */\nexport interface RowGhostCell {\n /** The cell's rendered text at grab time. */\n text: string;\n /** The cell's rendered width (px), so the ghost matches the column layout. */\n width: number;\n /** The cell's computed `text-align`, so numbers stay right-aligned in the ghost. */\n align: string;\n}\n\n/** Everything the row hands the controller to seed the floating ghost. */\nexport interface RowGhostInit {\n /** The source row's bounding rect at grab time. */\n rect: DOMRect;\n /** Pointer position at grab time (to keep the grab point under the cursor). */\n clientX: number;\n clientY: number;\n /** Per-cell snapshot of the row's visible content. */\n cells: RowGhostCell[];\n}\n\n/**\n * Live state for the floating \"ghost\" row that trails the cursor during a\n * pointer drag. Mirrors {@link ReorderController}'s column ghost: the source\n * row stays in the grid (and live-swaps), the ghost is a purely visual proxy.\n */\nexport interface RowGhostState {\n /** Ghost position (viewport-relative top-left, in CSS pixels). */\n x: number;\n y: number;\n /** Ghost size, fixed at the source row's initial bounding rect. */\n width: number;\n height: number;\n /** Cursor offset within the ghost so the grab point stays under the cursor. */\n pointerOffsetX: number;\n pointerOffsetY: number;\n /** Snapshot of the row's visible cells. */\n cells: ReadonlyArray<RowGhostCell>;\n}\n\n/**\n * Reactive controller backing row drag-reorder and a manual row order.\n *\n * @remarks\n * Row order is normally derived (filter → sort). This controller adds a\n * **manual order**: an explicit sequence of row references applied as a\n * pipeline step after sort. While a manual order is active, sorting is mutually\n * exclusive (applying a sort clears the manual order). State is reference-based,\n * so it survives the in-place pipeline.\n *\n * Reordering happens through {@link moveRow} (pointer live-swap during a drag,\n * the keyboard grab/move flow, or the public API). Each move goes through the\n * cancellable `rowMoving` event and emits a follow-up `rowMoved`, then animates\n * with a Y-axis FLIP. Pinned rows (F4) are never the drag source and reorder\n * only within the unpinned body set.\n *\n * Persistence: by default the order lives in this controller and the app\n * persists via `rowMoved`. With `rowReordering.applyToData` the move also\n * splices {@link ApexGrid.data} in place (and clears the manual order, since the\n * data array then carries the order).\n */\nexport class RowReorderController<T extends object> implements ReactiveController {\n /** Manual order of row references, or `null` for the natural (sorted) order. */\n #order: T[] | null = null;\n\n /** Order snapshot captured at keyboard-grab time, for Escape-to-cancel. */\n #preGrab: T[] | null = null;\n\n /** The row currently being pointer-dragged, or `null`. */\n public dragging: T | null = null;\n\n /**\n * Live floating-ghost state during a pointer drag, or `null`. Surfaced to the\n * grid, which renders the ghost at the top of its shadow tree (outside the\n * virtualizer) so `position: fixed` tracks the viewport cleanly.\n */\n public ghost: RowGhostState | null = null;\n\n /** The row currently grabbed for keyboard reorder, or `null`. */\n public grabbed: T | null = null;\n\n /** Re-entrancy guard so a slow swap can't trigger a second mid-drag. */\n #swapping = false;\n\n /**\n * In-flight pointer-drag gesture. The move/up listeners live on the **grid\n * host** (a stable element), not the row, so a live-swap that recycles the\n * source row's DOM can't tear the drag down mid-gesture.\n */\n #pending: {\n data: T;\n getGhost: () => RowGhostInit;\n startX: number;\n startY: number;\n pointerId: number;\n } | null = null;\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n /** The grid host as a DOM event target (it is always an `HTMLElement`). */\n get #hostElement(): HTMLElement {\n return this.host as unknown as HTMLElement;\n }\n\n public hostConnected() {}\n\n /** Whether row reordering is enabled at the grid level. */\n public get enabled(): boolean {\n return Boolean(this.host.rowReordering?.enabled);\n }\n\n /**\n * Whether a pointer drag must start from the dedicated grip handle (the\n * default), rather than from anywhere on the row. Driven by\n * `rowReordering.handle` (defaults to `true`).\n */\n public get handleMode(): boolean {\n return this.enabled && this.host.rowReordering?.handle !== false;\n }\n\n /**\n * Whether the leading grip-handle column is rendered. Only in handle mode, and\n * only while reordering is enabled.\n */\n public get showHandleColumn(): boolean {\n return this.handleMode;\n }\n\n /** Whether moves also splice {@link ApexGrid.data} in place. */\n public get applyToData(): boolean {\n return Boolean(this.host.rowReordering?.applyToData);\n }\n\n /** Whether a manual order is currently active. */\n public get hasManualOrder(): boolean {\n return this.#order !== null;\n }\n\n /** Whether a row is currently grabbed for keyboard reorder. */\n public get isGrabbing(): boolean {\n return this.grabbed !== null;\n }\n\n /**\n * Pipeline step: reorders the (post-filter, post-sort) rows by the manual\n * order. Rows absent from the manual order (e.g. newly added) keep their\n * incoming relative order at the end. Identity pass-through when inactive.\n */\n public apply(rows: T[]): T[] {\n if (!this.#order) return rows;\n const rank = new Map<T, number>();\n this.#order.forEach((row, i) => {\n rank.set(row, i);\n });\n return [...rows].sort((a, b) => {\n const ra = rank.has(a) ? (rank.get(a) as number) : Number.POSITIVE_INFINITY;\n const rb = rank.has(b) ? (rank.get(b) as number) : Number.POSITIVE_INFINITY;\n return ra - rb;\n });\n }\n\n /**\n * The current manual order as a copy of its row references, or `null` for the\n * derived order. Consumed by `ApexGrid.getState`.\n */\n public getManualOrder(): T[] | null {\n return this.#order ? [...this.#order] : null;\n }\n\n /**\n * Restores a manual order from a list of row references (state restore). Pass\n * `null` (or an empty list) to clear it. Silent: emits no `rowMoving` /\n * `rowMoved` and does not animate; re-runs the pipeline.\n */\n public restoreManualOrder(order: T[] | null): void {\n this.#order = order?.length ? [...order] : null;\n this.host.requestUpdate(PIPELINE);\n }\n\n /**\n * Clears the manual order (e.g. when a sort is applied). Re-runs the pipeline.\n */\n public clearManualOrder(announce = false): void {\n if (!this.#order) return;\n this.#order = null;\n this.host.requestUpdate(PIPELINE);\n if (announce) this.host.announce(this.host.localize('announce.manualOrderCleared'));\n }\n\n /**\n * Moves the row at view index `from` relative to the row at view index `to`.\n * Indices are into {@link ApexGrid.pageItems}.\n *\n * @returns `true` when applied, `false` when disabled, out of range, or\n * cancelled via `rowMoving`.\n */\n public moveRow(from: number, to: number, position: RowDropPosition = 'before'): boolean {\n const view = this.host.pageItems;\n const before = this.#captureRowRects();\n const ok = this.#commit(view[from] as T, view[to] as T, position);\n if (ok) void this.#flipAfterUpdate(before);\n return ok;\n }\n\n // --- pointer drag -------------------------------------------------------\n\n /**\n * Begins a pointer drag from `source`. When `ghost` is supplied (the pointer\n * path always does), seeds the floating ghost from the source row's rect and\n * the grab point so it stays anchored under the cursor.\n */\n public startDrag(source: T, ghost?: RowGhostInit): void {\n if (!this.enabled) return;\n this.dragging = source;\n this.ghost = ghost\n ? {\n x: ghost.rect.left,\n y: ghost.rect.top,\n width: ghost.rect.width,\n height: ghost.rect.height,\n pointerOffsetX: ghost.clientX - ghost.rect.left,\n pointerOffsetY: ghost.clientY - ghost.rect.top,\n cells: ghost.cells,\n }\n : null;\n this.host.requestUpdate();\n }\n\n /** Moves the floating ghost to track the cursor. No-op without an active ghost. */\n public moveGhost(clientX: number, clientY: number): void {\n if (!this.ghost) return;\n this.ghost = {\n ...this.ghost,\n x: clientX - this.ghost.pointerOffsetX,\n y: clientY - this.ghost.pointerOffsetY,\n };\n this.host.requestUpdate();\n }\n\n /**\n * Called on pointer-move during a drag: live-swaps the dragged row past the\n * body row currently under `clientY` once the cursor crosses its midpoint.\n */\n public dragOver(clientY: number): void {\n if (!this.dragging || this.#swapping) return;\n const target = this.#bodyRowAt(clientY);\n if (!target || target.data === this.dragging) return;\n\n const view = this.host.pageItems;\n const sourceIdx = view.indexOf(this.dragging);\n const targetIdx = view.indexOf(target.data);\n if (sourceIdx === -1 || targetIdx === -1) return;\n\n const rect = target.element.getBoundingClientRect();\n const midpoint = rect.top + rect.height / 2;\n let position: RowDropPosition;\n if (sourceIdx < targetIdx) {\n if (clientY < midpoint) return; // dragging down — wait for the midpoint\n position = 'after';\n } else {\n if (clientY > midpoint) return; // dragging up\n position = 'before';\n }\n void this.#dragSwap(target.data, position);\n }\n\n /** Ends the pointer drag. The order is already final (live-swapped). */\n public endDrag(): void {\n if (!this.dragging) return;\n this.dragging = null;\n this.ghost = null;\n this.host.requestUpdate();\n }\n\n /**\n * Arms a pointer drag from a row press. Called by the row on `pointerdown`\n * (from the grip in handle mode, or anywhere in whole-row mode). The move /\n * up / cancel listeners are bound to the **grid host**, so the gesture is\n * immune to the source row's DOM being recycled by a live-swap. `getGhost` is\n * a thunk the controller invokes once, the instant the drag engages, to\n * snapshot the row's cells (the row is still in place then).\n */\n public armPointerDrag(data: T, getGhost: () => RowGhostInit, event: PointerEvent): void {\n const pinned = this.host.pinnedRows;\n if (!this.enabled || pinned.top.includes(data) || pinned.bottom.includes(data)) return;\n this.#pending = {\n data,\n getGhost,\n startX: event.clientX,\n startY: event.clientY,\n pointerId: event.pointerId,\n };\n const host = this.#hostElement;\n host.addEventListener('pointermove', this.#onHostPointerMove);\n host.addEventListener('pointerup', this.#onHostPointerUp);\n host.addEventListener('pointercancel', this.#onHostPointerUp);\n }\n\n #onHostPointerMove = (event: PointerEvent): void => {\n const pending = this.#pending;\n if (!pending || event.pointerId !== pending.pointerId) return;\n if (!this.dragging) {\n const dx = event.clientX - pending.startX;\n const dy = event.clientY - pending.startY;\n if (Math.hypot(dx, dy) < DRAG_THRESHOLD_PX) return;\n // Capture on the stable host so moves keep arriving even when the cursor\n // leaves the grid, and so the source row reflowing can't drop the pointer.\n try {\n this.#hostElement.setPointerCapture(pending.pointerId);\n } catch {\n /* a synthetic pointer id may not be capturable; hit-testing still works */\n }\n this.startDrag(pending.data, pending.getGhost());\n }\n this.moveGhost(event.clientX, event.clientY);\n this.dragOver(event.clientY);\n };\n\n #onHostPointerUp = (event: PointerEvent): void => {\n const pending = this.#pending;\n if (!pending || event.pointerId !== pending.pointerId) return;\n const host = this.#hostElement;\n host.removeEventListener('pointermove', this.#onHostPointerMove);\n host.removeEventListener('pointerup', this.#onHostPointerUp);\n host.removeEventListener('pointercancel', this.#onHostPointerUp);\n try {\n if (host.hasPointerCapture(pending.pointerId)) host.releasePointerCapture(pending.pointerId);\n } catch {\n /* capture was never taken or already released */\n }\n this.#pending = null;\n this.endDrag();\n };\n\n async #dragSwap(target: T, position: RowDropPosition): Promise<void> {\n if (this.#swapping) return;\n this.#swapping = true;\n try {\n const before = this.#captureRowRects();\n if (this.#commit(this.dragging as T, target, position)) {\n await this.host.updateComplete;\n await awaitChildUpdates(this.host.rows);\n this.#playRowFlip(before);\n }\n } finally {\n this.#swapping = false;\n }\n }\n\n // --- keyboard grab ------------------------------------------------------\n\n /** Grabs the row at view index `rowIndex` for keyboard reorder. */\n public grab(rowIndex: number): boolean {\n if (!this.enabled) return false;\n const row = this.host.pageItems[rowIndex] as T | undefined;\n if (!row) return false;\n this.grabbed = row;\n this.#preGrab = this.#order ? [...this.#order] : null;\n this.host.requestUpdate();\n this.host.announce(this.host.localize('announce.rowGrabbed'));\n return true;\n }\n\n /**\n * Moves the grabbed row one position in `direction`. Returns the grabbed\n * row's new view index, or `-1` when it can't move (no grab / at the edge).\n */\n public moveGrabbed(direction: -1 | 1): number {\n if (!this.grabbed) return -1;\n const view = this.host.pageItems;\n const current = view.indexOf(this.grabbed);\n const target = current + direction;\n if (current === -1 || target < 0 || target >= view.length) return -1;\n const before = this.#captureRowRects();\n const ok = this.#commit(this.grabbed, view[target] as T, direction > 0 ? 'after' : 'before', {\n silent: true,\n });\n if (!ok) return -1;\n void this.#flipAfterUpdate(before);\n this.host.announce(this.host.localize('announce.rowMovedTo', { position: target + 1 }));\n return target;\n }\n\n /** Drops the grabbed row, keeping its moves. */\n public drop(): void {\n if (!this.grabbed) return;\n this.grabbed = null;\n this.#preGrab = null;\n this.host.requestUpdate();\n this.host.announce(this.host.localize('announce.rowDropped'));\n }\n\n /** Cancels the grab, reverting to the order captured when it was grabbed. */\n public cancelGrab(): void {\n if (!this.grabbed) return;\n this.grabbed = null;\n this.#order = this.#preGrab;\n this.#preGrab = null;\n this.host.requestUpdate(PIPELINE);\n this.host.announce(this.host.localize('announce.reorderCancelled'));\n }\n\n // --- internals ----------------------------------------------------------\n\n /**\n * The core order mutation: validates, emits `rowMoving` (cancellable),\n * updates the manual order (or splices data when `applyToData`), re-runs the\n * pipeline, and emits `rowMoved`. Does not animate.\n */\n #commit(\n source: T | undefined,\n target: T | undefined,\n position: RowDropPosition,\n options: { silent?: boolean } = {}\n ): boolean {\n if (!this.enabled || !source || !target || source === target) return false;\n const view = this.host.pageItems;\n const from = view.indexOf(source);\n const to = view.indexOf(target);\n\n const proceed = this.host.emitEvent('rowMoving', {\n detail: { from, to, data: source },\n cancelable: true,\n });\n if (!proceed) return false;\n\n const baseline = this.#order ?? [...(this.host.dataView as ReadonlyArray<T>)];\n const next = baseline.filter((row) => row !== source);\n let index = next.indexOf(target);\n if (index === -1) return false;\n if (position === 'after') index += 1;\n next.splice(index, 0, source);\n\n if (this.applyToData) {\n this.#spliceData(next);\n this.#order = null; // the data array now carries the order\n } else {\n this.#order = next;\n }\n\n this.host.requestUpdate(PIPELINE);\n if (!options.silent) this.host.announce(this.host.localize('announce.rowMoved'));\n this.host.emitEvent('rowMoved', { detail: { from, to, data: source } });\n return true;\n }\n\n /** Reorders {@link ApexGrid.data} in place to match `order`. */\n #spliceData(order: ReadonlyArray<T>): void {\n const rank = new Map<T, number>();\n order.forEach((row, i) => {\n rank.set(row, i);\n });\n (this.host.data as T[]).sort((a, b) => {\n const ra = rank.has(a) ? (rank.get(a) as number) : Number.POSITIVE_INFINITY;\n const rb = rank.has(b) ? (rank.get(b) as number) : Number.POSITIVE_INFINITY;\n return ra - rb;\n });\n }\n\n /** The rendered body row (non-pinned) whose box contains `clientY`. */\n #bodyRowAt(clientY: number): { data: T; element: HTMLElement } | null {\n const pinned = this.host.pinnedRows;\n for (const row of this.host.rows) {\n if (pinned.top.includes(row.data) || pinned.bottom.includes(row.data)) continue;\n const element = row as unknown as HTMLElement;\n const rect = element.getBoundingClientRect();\n if (clientY >= rect.top && clientY < rect.bottom) return { data: row.data, element };\n }\n return null;\n }\n\n #captureRowRects(): KeyedFlipEntry<T>[] {\n const entries: KeyedFlipEntry<T>[] = [];\n for (const row of this.host.rows) {\n const el = row as unknown as HTMLElement;\n entries.push({ key: row.data, rect: el.getBoundingClientRect() });\n }\n return entries;\n }\n\n async #flipAfterUpdate(before: ReadonlyArray<KeyedFlipEntry<T>>): Promise<void> {\n await this.host.updateComplete;\n await awaitChildUpdates(this.host.rows);\n this.#playRowFlip(before);\n }\n\n #playRowFlip(before: ReadonlyArray<KeyedFlipEntry<T>>): void {\n const byData = new Map<T, HTMLElement>();\n for (const row of this.host.rows) {\n byData.set(row.data, row as unknown as HTMLElement);\n }\n playKeyedFlip(before, (data) => byData.get(data) ?? null, 'y');\n }\n}\n"]}
@@ -151,13 +151,13 @@ export class SelectionController {
151
151
  this.host.requestUpdate();
152
152
  const total = this.selected.size;
153
153
  if (total === 0) {
154
- this.host.announce('Selection cleared');
154
+ this.host.announce(this.host.localize('announce.selectionCleared'));
155
155
  }
156
156
  else if (added.length === 1 && removed.length === 0 && total === 1) {
157
- this.host.announce('1 row selected');
157
+ this.host.announce(this.host.localize('announce.rowSelected'));
158
158
  }
159
159
  else {
160
- this.host.announce(`${total} rows selected`);
160
+ this.host.announce(this.host.localize('announce.rowsSelected', { count: total }));
161
161
  }
162
162
  this.host.emitEvent('rowSelected', {
163
163
  detail: { added, removed, selected: Array.from(this.selected) },
@@ -1 +1 @@
1
- {"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/controllers/selection.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type { GridHost, SelectionMode } from '../internal/types.js';\n\n/**\n * The default selection configuration used when the grid has none set.\n */\nexport const DEFAULT_SELECTION_CONFIG = Object.freeze({\n enabled: false,\n mode: 'multiple' as SelectionMode,\n showCheckboxColumn: false,\n});\n\n/**\n * Reactive controller backing row selection.\n *\n * @remarks\n * Selection state is reference-based — the controller holds a `Set` of row\n * data references so the selection survives sort / filter / pagination as\n * long as those operations preserve identities (the default in-place\n * pipeline does). Replacing {@link ApexGrid.data} wholesale will invalidate\n * any selected rows that are no longer present in the new array; consumers\n * should call {@link clear} or re-apply selection after such a change.\n *\n * Mutations go through the cancellable `rowSelecting` event and emit a\n * follow-up `rowSelected` event, mirroring the pattern used by editing.\n */\nexport class SelectionController<T extends object> implements ReactiveController {\n /** Set of currently selected row data references. */\n public selected: Set<T> = new Set();\n\n /**\n * Anchor row for Shift+click range selection. Set whenever a single row\n * is toggled / picked so a follow-up Shift+click knows where to start.\n */\n public anchor: T | null = null;\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /**\n * Whether selection is enabled at the grid level.\n */\n public get enabled(): boolean {\n return Boolean(this.host.selection?.enabled);\n }\n\n /**\n * The resolved selection mode (`'multiple'` if unset).\n */\n public get mode(): SelectionMode {\n return this.host.selection?.mode ?? DEFAULT_SELECTION_CONFIG.mode;\n }\n\n /**\n * Whether to render the built-in checkbox column.\n */\n public get showCheckboxColumn(): boolean {\n return (\n this.enabled &&\n (this.host.selection?.showCheckboxColumn ?? DEFAULT_SELECTION_CONFIG.showCheckboxColumn)\n );\n }\n\n /**\n * Whether the given row is currently selected.\n */\n public isSelected(row: T): boolean {\n return this.selected.has(row);\n }\n\n /**\n * Returns the currently selected rows in insertion order.\n */\n public selectedRows(): T[] {\n return Array.from(this.selected);\n }\n\n /**\n * Whether every row in the current view ({@link ApexGrid.dataView}) is\n * selected. Used by the header checkbox to render its \"all\" state.\n */\n public allSelected(): boolean {\n const view = this.host.dataView;\n if (view.length === 0) return false;\n return view.every((row) => this.selected.has(row as T));\n }\n\n /**\n * Whether some — but not all — rows in the current view are selected.\n * Used to render the header checkbox in its indeterminate state.\n */\n public someSelected(): boolean {\n if (this.selected.size === 0) return false;\n if (this.allSelected()) return false;\n return true;\n }\n\n /**\n * Toggles selection of `row`. In `'single'` mode this either selects the\n * row (replacing any previous selection) or deselects it if it was the\n * only selected row.\n */\n public async toggleRow(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n const next = new Set(this.selected);\n if (next.has(row)) {\n next.delete(row);\n } else {\n if (this.mode === 'single') next.clear();\n next.add(row);\n }\n this.anchor = next.has(row) ? row : null;\n return this.#commit(next);\n }\n\n /**\n * Adds `row` to the selection. In `'single'` mode the existing selection\n * is cleared first.\n */\n public async selectRow(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n const next = new Set(this.mode === 'single' ? [] : this.selected);\n next.add(row);\n this.anchor = row;\n return this.#commit(next);\n }\n\n /**\n * Removes `row` from the selection.\n */\n public async deselectRow(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n if (!this.selected.has(row)) return true;\n const next = new Set(this.selected);\n next.delete(row);\n return this.#commit(next);\n }\n\n /**\n * Adds `row` to the selection without affecting other selected rows\n * (`'multiple'` mode only). In `'single'` mode this behaves like\n * {@link selectRow}.\n */\n public async additiveToggle(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n if (this.mode === 'single') return this.toggleRow(row);\n const next = new Set(this.selected);\n if (next.has(row)) {\n next.delete(row);\n } else {\n next.add(row);\n }\n this.anchor = next.has(row) ? row : this.anchor;\n return this.#commit(next);\n }\n\n /**\n * Selects every row between {@link anchor} and `row` (inclusive) in the\n * current page view. No-op in `'single'` mode or when no anchor is set —\n * in those cases the call falls back to a plain {@link selectRow}.\n */\n public async rangeToggle(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n if (this.mode === 'single' || !this.anchor) {\n return this.selectRow(row);\n }\n const pageItems = this.host.pageItems as ReadonlyArray<T>;\n const startIdx = pageItems.indexOf(this.anchor);\n const endIdx = pageItems.indexOf(row);\n if (startIdx === -1 || endIdx === -1) {\n return this.selectRow(row);\n }\n const [lo, hi] = startIdx < endIdx ? [startIdx, endIdx] : [endIdx, startIdx];\n const next = new Set(this.selected);\n for (let i = lo; i <= hi; i++) {\n next.add(pageItems[i] as T);\n }\n return this.#commit(next);\n }\n\n /**\n * Selects every row currently in view ({@link ApexGrid.dataView}). No-op\n * in `'single'` mode.\n */\n public async selectAll(): Promise<boolean> {\n if (!this.enabled || this.mode === 'single') return false;\n const next = new Set(this.host.dataView as ReadonlyArray<T>);\n return this.#commit(next);\n }\n\n /**\n * Clears the selection.\n */\n public async clear(): Promise<boolean> {\n if (this.selected.size === 0) return true;\n return this.#commit(new Set());\n }\n\n /**\n * Replaces the current selection with `rows`. Used by the public\n * {@link ApexGrid.selectedRows} setter so callers can drive selection\n * programmatically. Single-mode keeps at most one row.\n */\n public async replaceSelection(rows: ReadonlyArray<T>): Promise<boolean> {\n if (!this.enabled) return false;\n const next = new Set(this.mode === 'single' ? rows.slice(0, 1) : rows);\n return this.#commit(next);\n }\n\n async #commit(next: Set<T>): Promise<boolean> {\n const added: T[] = [];\n const removed: T[] = [];\n for (const row of next) {\n if (!this.selected.has(row)) added.push(row);\n }\n for (const row of this.selected) {\n if (!next.has(row)) removed.push(row);\n }\n if (added.length === 0 && removed.length === 0) return true;\n\n const proceed = this.host.emitEvent('rowSelecting', {\n detail: {\n added,\n removed,\n current: Array.from(this.selected),\n next: Array.from(next),\n },\n cancelable: true,\n });\n if (!proceed) return false;\n\n this.selected = next;\n this.host.requestUpdate();\n\n const total = this.selected.size;\n if (total === 0) {\n this.host.announce('Selection cleared');\n } else if (added.length === 1 && removed.length === 0 && total === 1) {\n this.host.announce('1 row selected');\n } else {\n this.host.announce(`${total} rows selected`);\n }\n\n this.host.emitEvent('rowSelected', {\n detail: { added, removed, selected: Array.from(this.selected) },\n });\n return true;\n }\n}\n"]}
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/controllers/selection.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type { GridHost, SelectionMode } from '../internal/types.js';\n\n/**\n * The default selection configuration used when the grid has none set.\n */\nexport const DEFAULT_SELECTION_CONFIG = Object.freeze({\n enabled: false,\n mode: 'multiple' as SelectionMode,\n showCheckboxColumn: false,\n});\n\n/**\n * Reactive controller backing row selection.\n *\n * @remarks\n * Selection state is reference-based — the controller holds a `Set` of row\n * data references so the selection survives sort / filter / pagination as\n * long as those operations preserve identities (the default in-place\n * pipeline does). Replacing {@link ApexGrid.data} wholesale will invalidate\n * any selected rows that are no longer present in the new array; consumers\n * should call {@link clear} or re-apply selection after such a change.\n *\n * Mutations go through the cancellable `rowSelecting` event and emit a\n * follow-up `rowSelected` event, mirroring the pattern used by editing.\n */\nexport class SelectionController<T extends object> implements ReactiveController {\n /** Set of currently selected row data references. */\n public selected: Set<T> = new Set();\n\n /**\n * Anchor row for Shift+click range selection. Set whenever a single row\n * is toggled / picked so a follow-up Shift+click knows where to start.\n */\n public anchor: T | null = null;\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /**\n * Whether selection is enabled at the grid level.\n */\n public get enabled(): boolean {\n return Boolean(this.host.selection?.enabled);\n }\n\n /**\n * The resolved selection mode (`'multiple'` if unset).\n */\n public get mode(): SelectionMode {\n return this.host.selection?.mode ?? DEFAULT_SELECTION_CONFIG.mode;\n }\n\n /**\n * Whether to render the built-in checkbox column.\n */\n public get showCheckboxColumn(): boolean {\n return (\n this.enabled &&\n (this.host.selection?.showCheckboxColumn ?? DEFAULT_SELECTION_CONFIG.showCheckboxColumn)\n );\n }\n\n /**\n * Whether the given row is currently selected.\n */\n public isSelected(row: T): boolean {\n return this.selected.has(row);\n }\n\n /**\n * Returns the currently selected rows in insertion order.\n */\n public selectedRows(): T[] {\n return Array.from(this.selected);\n }\n\n /**\n * Whether every row in the current view ({@link ApexGrid.dataView}) is\n * selected. Used by the header checkbox to render its \"all\" state.\n */\n public allSelected(): boolean {\n const view = this.host.dataView;\n if (view.length === 0) return false;\n return view.every((row) => this.selected.has(row as T));\n }\n\n /**\n * Whether some — but not all — rows in the current view are selected.\n * Used to render the header checkbox in its indeterminate state.\n */\n public someSelected(): boolean {\n if (this.selected.size === 0) return false;\n if (this.allSelected()) return false;\n return true;\n }\n\n /**\n * Toggles selection of `row`. In `'single'` mode this either selects the\n * row (replacing any previous selection) or deselects it if it was the\n * only selected row.\n */\n public async toggleRow(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n const next = new Set(this.selected);\n if (next.has(row)) {\n next.delete(row);\n } else {\n if (this.mode === 'single') next.clear();\n next.add(row);\n }\n this.anchor = next.has(row) ? row : null;\n return this.#commit(next);\n }\n\n /**\n * Adds `row` to the selection. In `'single'` mode the existing selection\n * is cleared first.\n */\n public async selectRow(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n const next = new Set(this.mode === 'single' ? [] : this.selected);\n next.add(row);\n this.anchor = row;\n return this.#commit(next);\n }\n\n /**\n * Removes `row` from the selection.\n */\n public async deselectRow(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n if (!this.selected.has(row)) return true;\n const next = new Set(this.selected);\n next.delete(row);\n return this.#commit(next);\n }\n\n /**\n * Adds `row` to the selection without affecting other selected rows\n * (`'multiple'` mode only). In `'single'` mode this behaves like\n * {@link selectRow}.\n */\n public async additiveToggle(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n if (this.mode === 'single') return this.toggleRow(row);\n const next = new Set(this.selected);\n if (next.has(row)) {\n next.delete(row);\n } else {\n next.add(row);\n }\n this.anchor = next.has(row) ? row : this.anchor;\n return this.#commit(next);\n }\n\n /**\n * Selects every row between {@link anchor} and `row` (inclusive) in the\n * current page view. No-op in `'single'` mode or when no anchor is set —\n * in those cases the call falls back to a plain {@link selectRow}.\n */\n public async rangeToggle(row: T): Promise<boolean> {\n if (!this.enabled) return false;\n if (this.mode === 'single' || !this.anchor) {\n return this.selectRow(row);\n }\n const pageItems = this.host.pageItems as ReadonlyArray<T>;\n const startIdx = pageItems.indexOf(this.anchor);\n const endIdx = pageItems.indexOf(row);\n if (startIdx === -1 || endIdx === -1) {\n return this.selectRow(row);\n }\n const [lo, hi] = startIdx < endIdx ? [startIdx, endIdx] : [endIdx, startIdx];\n const next = new Set(this.selected);\n for (let i = lo; i <= hi; i++) {\n next.add(pageItems[i] as T);\n }\n return this.#commit(next);\n }\n\n /**\n * Selects every row currently in view ({@link ApexGrid.dataView}). No-op\n * in `'single'` mode.\n */\n public async selectAll(): Promise<boolean> {\n if (!this.enabled || this.mode === 'single') return false;\n const next = new Set(this.host.dataView as ReadonlyArray<T>);\n return this.#commit(next);\n }\n\n /**\n * Clears the selection.\n */\n public async clear(): Promise<boolean> {\n if (this.selected.size === 0) return true;\n return this.#commit(new Set());\n }\n\n /**\n * Replaces the current selection with `rows`. Used by the public\n * {@link ApexGrid.selectedRows} setter so callers can drive selection\n * programmatically. Single-mode keeps at most one row.\n */\n public async replaceSelection(rows: ReadonlyArray<T>): Promise<boolean> {\n if (!this.enabled) return false;\n const next = new Set(this.mode === 'single' ? rows.slice(0, 1) : rows);\n return this.#commit(next);\n }\n\n async #commit(next: Set<T>): Promise<boolean> {\n const added: T[] = [];\n const removed: T[] = [];\n for (const row of next) {\n if (!this.selected.has(row)) added.push(row);\n }\n for (const row of this.selected) {\n if (!next.has(row)) removed.push(row);\n }\n if (added.length === 0 && removed.length === 0) return true;\n\n const proceed = this.host.emitEvent('rowSelecting', {\n detail: {\n added,\n removed,\n current: Array.from(this.selected),\n next: Array.from(next),\n },\n cancelable: true,\n });\n if (!proceed) return false;\n\n this.selected = next;\n this.host.requestUpdate();\n\n const total = this.selected.size;\n if (total === 0) {\n this.host.announce(this.host.localize('announce.selectionCleared'));\n } else if (added.length === 1 && removed.length === 0 && total === 1) {\n this.host.announce(this.host.localize('announce.rowSelected'));\n } else {\n this.host.announce(this.host.localize('announce.rowsSelected', { count: total }));\n }\n\n this.host.emitEvent('rowSelected', {\n detail: { added, removed, selected: Array.from(this.selected) },\n });\n return true;\n }\n}\n"]}