apex-grid 3.1.0 → 3.3.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 (200) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +201 -53
  3. package/components/cell.d.ts +14 -1
  4. package/components/cell.js +48 -6
  5. package/components/cell.js.map +1 -1
  6. package/components/filter-row.d.ts +3 -6
  7. package/components/filter-row.js +79 -109
  8. package/components/filter-row.js.map +1 -1
  9. package/components/grid.d.ts +416 -70
  10. package/components/grid.js +398 -11
  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 +136 -6
  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/filter.d.ts +1 -1
  39. package/controllers/filter.js +16 -5
  40. package/controllers/filter.js.map +1 -1
  41. package/controllers/history.d.ts +86 -0
  42. package/controllers/history.js +118 -0
  43. package/controllers/history.js.map +1 -0
  44. package/controllers/navigation.d.ts +15 -0
  45. package/controllers/navigation.js +53 -0
  46. package/controllers/navigation.js.map +1 -1
  47. package/controllers/reorder.js +3 -1
  48. package/controllers/reorder.js.map +1 -1
  49. package/controllers/row-pin.d.ts +59 -0
  50. package/controllers/row-pin.js +75 -0
  51. package/controllers/row-pin.js.map +1 -0
  52. package/controllers/row-reorder.d.ts +161 -0
  53. package/controllers/row-reorder.js +332 -0
  54. package/controllers/row-reorder.js.map +1 -0
  55. package/controllers/sort.d.ts +1 -1
  56. package/controllers/sort.js +11 -3
  57. package/controllers/sort.js.map +1 -1
  58. package/controllers/state.d.ts +33 -0
  59. package/controllers/state.js +37 -2
  60. package/controllers/state.js.map +1 -1
  61. package/controllers/tree.d.ts +8 -0
  62. package/controllers/tree.js +0 -0
  63. package/controllers/tree.js.map +1 -1
  64. package/custom-elements.json +4415 -875
  65. package/docs/assets/hierarchy.js +1 -1
  66. package/docs/assets/highlight.css +28 -0
  67. package/docs/assets/navigation.js +1 -1
  68. package/docs/assets/search.js +1 -1
  69. package/docs/functions/custom.html +3 -0
  70. package/docs/functions/interpolate.html +4 -0
  71. package/docs/functions/localize.html +8 -0
  72. package/docs/functions/max.html +3 -0
  73. package/docs/functions/min.html +3 -0
  74. package/docs/functions/pattern.html +3 -0
  75. package/docs/functions/required.html +3 -0
  76. package/docs/hierarchy.html +1 -1
  77. package/docs/index.html +185 -55
  78. package/docs/interfaces/ApexCellValidationFailedEvent.html +13 -0
  79. package/docs/interfaces/ApexCellValueChangedEvent.html +1 -1
  80. package/docs/interfaces/ApexCellValueChangingEvent.html +2 -2
  81. package/docs/interfaces/ApexColumnGroupContext.html +6 -0
  82. package/docs/interfaces/ApexColumnMovedEvent.html +2 -2
  83. package/docs/interfaces/ApexColumnMovingEvent.html +1 -1
  84. package/docs/interfaces/ApexDetailContext.html +2 -2
  85. package/docs/interfaces/ApexGridEventMap.html +37 -9
  86. package/docs/interfaces/ApexHistoryChangedEvent.html +6 -0
  87. package/docs/interfaces/ApexPageChangedEvent.html +1 -1
  88. package/docs/interfaces/ApexRowMovingEvent.html +9 -0
  89. package/docs/interfaces/ApexRowPinningEvent.html +8 -0
  90. package/docs/interfaces/ApexStateChangedEvent.html +4 -0
  91. package/docs/interfaces/BaseApexCellContext.html +1 -1
  92. package/docs/interfaces/BaseApexEditorContext.html +1 -1
  93. package/docs/interfaces/BaseColumnConfiguration.html +42 -17
  94. package/docs/interfaces/CSVExportOptions.html +2 -2
  95. package/docs/interfaces/ColumnGroupConfiguration.html +12 -0
  96. package/docs/interfaces/ColumnLayoutState.html +2 -0
  97. package/docs/interfaces/ColumnSchema.html +22 -0
  98. package/docs/interfaces/DataPipelineConfiguration.html +2 -2
  99. package/docs/interfaces/EditingHistoryConfiguration.html +10 -0
  100. package/docs/interfaces/ExportFormat.html +2 -2
  101. package/docs/interfaces/ExportOptions.html +1 -1
  102. package/docs/interfaces/FilterStateSnapshot.html +4 -0
  103. package/docs/interfaces/GetStateOptions.html +4 -0
  104. package/docs/interfaces/GridCapabilities.html +8 -0
  105. package/docs/interfaces/GridEditingConfiguration.html +4 -2
  106. package/docs/interfaces/GridRowPinningConfiguration.html +5 -0
  107. package/docs/interfaces/GridRowReorderingConfiguration.html +22 -0
  108. package/docs/interfaces/GridSchema.html +19 -0
  109. package/docs/interfaces/GridState.html +41 -0
  110. package/docs/interfaces/GridTreeConfiguration.html +2 -2
  111. package/docs/interfaces/PaginationConfiguration.html +1 -1
  112. package/docs/interfaces/PaginationState.html +1 -1
  113. package/docs/interfaces/SetStateOptions.html +9 -0
  114. package/docs/interfaces/SetStateResult.html +11 -0
  115. package/docs/interfaces/SortStateSnapshot.html +2 -0
  116. package/docs/interfaces/ValidatorContext.html +8 -0
  117. package/docs/media/_tokens.scss +131 -44
  118. package/docs/media/package.json +1 -1
  119. package/docs/media/state-persistence.html +189 -0
  120. package/docs/modules.html +1 -1
  121. package/docs/types/DataPipelineParams.html +2 -2
  122. package/docs/types/EditMode.html +2 -2
  123. package/docs/types/GridLocaleKey.html +3 -0
  124. package/docs/types/GridLocaleText.html +5 -0
  125. package/docs/types/LocaleParams.html +2 -0
  126. package/docs/types/PaginationMode.html +1 -1
  127. package/docs/types/RowDropPosition.html +2 -0
  128. package/docs/types/RowPinPosition.html +2 -0
  129. package/docs/types/RowRef.html +3 -0
  130. package/docs/types/Validator.html +4 -0
  131. package/docs/variables/EN_LOCALE.html +9 -0
  132. package/docs/variables/esLocale.html +8 -0
  133. package/i18n/en.d.ts +151 -0
  134. package/i18n/en.js +125 -0
  135. package/i18n/en.js.map +1 -0
  136. package/i18n/es.d.ts +18 -0
  137. package/i18n/es.js +125 -0
  138. package/i18n/es.js.map +1 -0
  139. package/i18n/index.d.ts +3 -0
  140. package/i18n/index.js +4 -0
  141. package/i18n/index.js.map +1 -0
  142. package/i18n/localize.d.ts +23 -0
  143. package/i18n/localize.js +15 -0
  144. package/i18n/localize.js.map +1 -0
  145. package/index.d.ts +8 -2
  146. package/index.js +2 -0
  147. package/index.js.map +1 -1
  148. package/internal/feature-module.d.ts +46 -0
  149. package/internal/feature-module.js +11 -0
  150. package/internal/feature-module.js.map +1 -1
  151. package/internal/flip.js +1 -1
  152. package/internal/flip.js.map +1 -1
  153. package/internal/icons.d.ts +9 -7
  154. package/internal/icons.js +75 -35
  155. package/internal/icons.js.map +1 -1
  156. package/internal/index.d.ts +5 -3
  157. package/internal/index.js +2 -1
  158. package/internal/index.js.map +1 -1
  159. package/internal/state-schema.d.ts +85 -0
  160. package/internal/state-schema.js +27 -0
  161. package/internal/state-schema.js.map +1 -0
  162. package/internal/state-snapshot.d.ts +152 -0
  163. package/internal/state-snapshot.js +115 -0
  164. package/internal/state-snapshot.js.map +1 -0
  165. package/internal/tags.d.ts +1 -0
  166. package/internal/tags.js +1 -0
  167. package/internal/tags.js.map +1 -1
  168. package/internal/types.d.ts +162 -2
  169. package/internal/types.js.map +1 -1
  170. package/internal/utils.d.ts +12 -0
  171. package/internal/utils.js +17 -0
  172. package/internal/utils.js.map +1 -1
  173. package/internal/validators.d.ts +31 -0
  174. package/internal/validators.js +44 -0
  175. package/internal/validators.js.map +1 -0
  176. package/package.json +1 -1
  177. package/styles/_tokens.css.js +1 -1
  178. package/styles/_tokens.css.js.map +1 -1
  179. package/styles/body-cell/body-cell.css.js +1 -1
  180. package/styles/body-cell/body-cell.css.js.map +1 -1
  181. package/styles/body-row/body-row.css.js +1 -1
  182. package/styles/body-row/body-row.css.js.map +1 -1
  183. package/styles/filter-row/filter-row.css.js +1 -1
  184. package/styles/filter-row/filter-row.css.js.map +1 -1
  185. package/styles/grid/_grid.base.css.js +1 -1
  186. package/styles/grid/_grid.base.css.js.map +1 -1
  187. package/styles/grid/grid.css.js +1 -1
  188. package/styles/grid/grid.css.js.map +1 -1
  189. package/styles/group-header-row/group-header-row.css.d.ts +1 -0
  190. package/styles/group-header-row/group-header-row.css.js +3 -0
  191. package/styles/group-header-row/group-header-row.css.js.map +1 -0
  192. package/styles/header-cell/header-cell.css.js +1 -1
  193. package/styles/header-cell/header-cell.css.js.map +1 -1
  194. package/styles/header-row/header-row.base.css.js +1 -1
  195. package/styles/header-row/header-row.base.css.js.map +1 -1
  196. package/styles/paginator/paginator.css.js +1 -1
  197. package/styles/paginator/paginator.css.js.map +1 -1
  198. package/styles/toolbar/toolbar.css.js +1 -1
  199. package/styles/toolbar/toolbar.css.js.map +1 -1
  200. package/docs/classes/ApexGrid.html +0 -393
@@ -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('Manual row order cleared');
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('Row grabbed. Use arrow keys to move, Enter to drop, Escape to cancel.');
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(`Row moved to 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('Row dropped');
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('Reorder cancelled');
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('Row moved');
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('Manual row order cleared');\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('Row grabbed. Use arrow keys to move, Enter to drop, Escape to cancel.');\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(`Row moved to 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('Row dropped');\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('Reorder cancelled');\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('Row moved');\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"]}
@@ -6,7 +6,7 @@ export declare class SortController<T extends object> implements ReactiveControl
6
6
  protected host: GridHost<T>;
7
7
  constructor(host: GridHost<T>);
8
8
  state: SortState<T>;
9
- sortFromHeaderClick(column: ColumnConfiguration<T>): Promise<void>;
9
+ sortFromHeaderClick(column: ColumnConfiguration<T>, additive?: boolean): Promise<void>;
10
10
  prepareExpression({ key, sort: options }: ColumnConfiguration<T>): SortExpression<T>;
11
11
  reset(key?: Keys<T>): void;
12
12
  protected _sort(expressions: SortExpression<T> | SortExpression<T>[]): void;
@@ -56,12 +56,12 @@ export class SortController {
56
56
  ? this.reset(expression.key)
57
57
  : this.state.set(expression.key, { ...expression });
58
58
  }
59
- async sortFromHeaderClick(column) {
59
+ async sortFromHeaderClick(column, additive = false) {
60
60
  const expression = this.prepareExpression(column);
61
61
  if (!this.#emitSortingEvent(expression)) {
62
62
  return;
63
63
  }
64
- if (!this.#isMultipleSort) {
64
+ if (!this.#isMultipleSort || !additive) {
65
65
  this.reset();
66
66
  }
67
67
  const beforeRects = this.#captureRowRects();
@@ -108,8 +108,16 @@ export class SortController {
108
108
  reset(key) {
109
109
  key ? this.state.delete(key) : this.state.clear();
110
110
  }
111
+ get #rowReorder() {
112
+ return this.host.stateController.rowReorder;
113
+ }
111
114
  _sort(expressions) {
112
- for (const expr of asArray(expressions)) {
115
+ const exprs = asArray(expressions);
116
+ const reorder = this.#rowReorder;
117
+ if (reorder?.hasManualOrder && exprs.some((expr) => expr.direction !== 'none')) {
118
+ reorder.clearManualOrder(true);
119
+ }
120
+ for (const expr of exprs) {
113
121
  this.#setExpression(expr);
114
122
  }
115
123
  this.host.requestUpdate(PIPELINE);
@@ -1 +1 @@
1
- {"version":3,"file":"sort.js","sourceRoot":"","sources":["../../src/controllers/sort.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 {\n ColumnConfiguration,\n ColumnSortConfiguration,\n GridHost,\n Keys,\n} from '../internal/types.js';\nimport { asArray } from '../internal/utils.js';\nimport type { SortExpression, SortingDirection, SortState } from '../operations/sort/types.js';\n\nexport class SortController<T extends object> implements ReactiveController {\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public state: SortState<T> = new Map();\n\n get #isMultipleSort() {\n return this.host.sortConfiguration.multiple;\n }\n\n get #isTriStateSort() {\n return this.host.sortConfiguration.triState;\n }\n\n #resolveSortOptions(options?: boolean | ColumnSortConfiguration<T>) {\n const expr: Pick<SortExpression<T>, 'caseSensitive' | 'comparer'> = {\n caseSensitive: false,\n comparer: undefined,\n };\n\n if (!options || typeof options === 'boolean') {\n return expr as Partial<SortExpression<T>>;\n }\n\n return Object.assign(expr, {\n caseSensitive: options.caseSensitive,\n comparer: options.comparer,\n }) as Partial<SortExpression<T>>;\n }\n\n #createDefaultExpression(key: Keys<T>) {\n const options = this.host.getColumn(key)?.sort;\n\n return {\n key,\n direction: 'ascending',\n ...this.#resolveSortOptions(options),\n } as SortExpression<T>;\n }\n\n #orderBy(dir?: SortingDirection): SortingDirection {\n return this.#isTriStateSort\n ? dir === 'ascending'\n ? 'descending'\n : dir === 'descending'\n ? 'none'\n : 'ascending'\n : dir === 'ascending'\n ? 'descending'\n : 'ascending';\n }\n\n #emitSortingEvent(detail: SortExpression<T>) {\n return this.host.emitEvent('sorting', { detail, cancelable: true });\n }\n\n #emitSortedEvent(detail: SortExpression<T>) {\n return this.host.emitEvent('sorted', { detail });\n }\n\n #setExpression(expression: SortExpression<T>) {\n expression.direction === 'none'\n ? this.reset(expression.key)\n : this.state.set(expression.key, { ...expression });\n }\n\n public async sortFromHeaderClick(column: ColumnConfiguration<T>) {\n const expression = this.prepareExpression(column);\n\n if (!this.#emitSortingEvent(expression)) {\n return;\n }\n\n if (!this.#isMultipleSort) {\n this.reset();\n }\n\n // Capture visible-row rects keyed by data identity BEFORE the pipeline\n // runs. After the new render the virtualizer will have repositioned\n // rows; we use those captured rects as the \"First\" of a FLIP and\n // animate each row from its old viewport position back to identity.\n const beforeRects = this.#captureRowRects();\n\n this._sort(expression);\n\n await this.host.updateComplete;\n // Wait for body row updates so the new data is committed to each row\n // element before measuring the \"Last\" rect — otherwise the captured-\n // before and measured-after rects match and the animation no-ops.\n await awaitChildUpdates(this.host.rows);\n this.#playRowFlip(beforeRects);\n this.#announce(expression, column);\n this.#emitSortedEvent(expression);\n }\n\n #announce(expression: SortExpression<T>, column: ColumnConfiguration<T>) {\n const label = column.headerText ?? String(column.key);\n const message =\n expression.direction === 'none'\n ? `Sort cleared on ${label}`\n : `Sorted by ${label} ${expression.direction}`;\n this.host.announce(message);\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 #playRowFlip(before: ReadonlyArray<KeyedFlipEntry<T>>) {\n // After sort, the same data may be rendered by a different `<apex-grid-row>`\n // DOM element (virtualizer recycles rows). Resolve by data identity.\n const rows = this.host.rows;\n const byData = new Map<T, HTMLElement>();\n for (const row of rows) {\n byData.set(row.data, row as unknown as HTMLElement);\n }\n playKeyedFlip(before, (data) => byData.get(data) ?? null, 'y');\n }\n\n public prepareExpression({ key, sort: options }: ColumnConfiguration<T>): SortExpression<T> {\n if (this.state.has(key)) {\n const expr = this.state.get(key)!;\n\n return Object.assign(expr, {\n direction: this.#orderBy(expr.direction),\n ...this.#resolveSortOptions(options),\n });\n }\n\n // Initial state\n return this.#createDefaultExpression(key);\n }\n\n public reset(key?: Keys<T>) {\n key ? this.state.delete(key) : this.state.clear();\n }\n\n protected _sort(expressions: SortExpression<T> | SortExpression<T>[]) {\n for (const expr of asArray(expressions)) {\n this.#setExpression(expr);\n }\n\n this.host.requestUpdate(PIPELINE);\n }\n\n public sort(expressions: SortExpression<T> | SortExpression<T>[]) {\n this._sort(\n asArray(expressions).map((expr) =>\n Object.assign(this.state.get(expr.key) ?? this.#createDefaultExpression(expr.key), expr)\n )\n );\n }\n\n public hostConnected() {}\n}\n"]}
1
+ {"version":3,"file":"sort.js","sourceRoot":"","sources":["../../src/controllers/sort.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAuB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAO5F,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C,MAAM,OAAO,cAAc;IACzB,YAAsB,IAAiB;QAAjB,SAAI,GAAJ,IAAI,CAAa;QAIhC,UAAK,GAAiB,IAAI,GAAG,EAAE,CAAC;QAHrC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAID,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC;IAC9C,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC;IAC9C,CAAC;IAED,mBAAmB,CAAC,OAA8C;QAChE,MAAM,IAAI,GAA0D;YAClE,aAAa,EAAE,KAAK;YACpB,QAAQ,EAAE,SAAS;SACpB,CAAC;QAEF,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,SAAS,EAAE,CAAC;YAC7C,OAAO,IAAkC,CAAC;QAC5C,CAAC;QAED,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;YACzB,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAA+B,CAAC;IACnC,CAAC;IAED,wBAAwB,CAAC,GAAY;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;QAE/C,OAAO;YACL,GAAG;YACH,SAAS,EAAE,WAAW;YACtB,GAAG,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC;SAChB,CAAC;IACzB,CAAC;IAED,QAAQ,CAAC,GAAsB;QAC7B,OAAO,IAAI,CAAC,eAAe;YACzB,CAAC,CAAC,GAAG,KAAK,WAAW;gBACnB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,GAAG,KAAK,YAAY;oBACpB,CAAC,CAAC,MAAM;oBACR,CAAC,CAAC,WAAW;YACjB,CAAC,CAAC,GAAG,KAAK,WAAW;gBACnB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,WAAW,CAAC;IACpB,CAAC;IAED,iBAAiB,CAAC,MAAyB;QACzC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,gBAAgB,CAAC,MAAyB;QACxC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,cAAc,CAAC,UAA6B;QAC1C,UAAU,CAAC,SAAS,KAAK,MAAM;YAC7B,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;YAC5B,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;IACxD,CAAC;IAEM,KAAK,CAAC,mBAAmB,CAAC,MAA8B,EAAE,QAAQ,GAAG,KAAK;QAC/E,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAElD,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,OAAO;QACT,CAAC;QAQD,IAAI,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvC,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;QAMD,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAE5C,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAEvB,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC;QAI/B,MAAM,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAC/B,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;IAED,SAAS,CAAC,UAA6B,EAAE,MAA8B;QACrE,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,OAAO,GACX,UAAU,CAAC,SAAS,KAAK,MAAM;YAC7B,CAAC,CAAC,mBAAmB,KAAK,EAAE;YAC5B,CAAC,CAAC,aAAa,KAAK,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;QACnD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAED,gBAAgB;QACd,MAAM,OAAO,GAAwB,EAAE,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACjC,MAAM,EAAE,GAAG,GAA6B,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,YAAY,CAAC,MAAwC;QAGnD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAA6B,CAAC,CAAC;QACtD,CAAC;QACD,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;IACjE,CAAC;IAEM,iBAAiB,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAA0B;QACrE,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;YAElC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;gBACzB,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC;gBACxC,GAAG,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC;aACrC,CAAC,CAAC;QACL,CAAC;QAGD,OAAO,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC;IAEM,KAAK,CAAC,GAAa;QACxB,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACpD,CAAC;IAED,IAAI,WAAW;QAEb,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,UAEpB,CAAC;IAChB,CAAC;IAES,KAAK,CAAC,WAAoD;QAClE,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QAGnC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC;QACjC,IAAI,OAAO,EAAE,cAAc,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC,EAAE,CAAC;YAC/E,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAEM,IAAI,CAAC,WAAoD;QAC9D,IAAI,CAAC,KAAK,CACR,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAChC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CACzF,CACF,CAAC;IACJ,CAAC;IAEM,aAAa,KAAI,CAAC;CAC1B","sourcesContent":["import type { ReactiveController } from 'lit';\nimport { PIPELINE } from '../internal/constants.js';\nimport { awaitChildUpdates, type KeyedFlipEntry, playKeyedFlip } from '../internal/flip.js';\nimport type {\n ColumnConfiguration,\n ColumnSortConfiguration,\n GridHost,\n Keys,\n} from '../internal/types.js';\nimport { asArray } from '../internal/utils.js';\nimport type { SortExpression, SortingDirection, SortState } from '../operations/sort/types.js';\n\nexport class SortController<T extends object> implements ReactiveController {\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public state: SortState<T> = new Map();\n\n get #isMultipleSort() {\n return this.host.sortConfiguration.multiple;\n }\n\n get #isTriStateSort() {\n return this.host.sortConfiguration.triState;\n }\n\n #resolveSortOptions(options?: boolean | ColumnSortConfiguration<T>) {\n const expr: Pick<SortExpression<T>, 'caseSensitive' | 'comparer'> = {\n caseSensitive: false,\n comparer: undefined,\n };\n\n if (!options || typeof options === 'boolean') {\n return expr as Partial<SortExpression<T>>;\n }\n\n return Object.assign(expr, {\n caseSensitive: options.caseSensitive,\n comparer: options.comparer,\n }) as Partial<SortExpression<T>>;\n }\n\n #createDefaultExpression(key: Keys<T>) {\n const options = this.host.getColumn(key)?.sort;\n\n return {\n key,\n direction: 'ascending',\n ...this.#resolveSortOptions(options),\n } as SortExpression<T>;\n }\n\n #orderBy(dir?: SortingDirection): SortingDirection {\n return this.#isTriStateSort\n ? dir === 'ascending'\n ? 'descending'\n : dir === 'descending'\n ? 'none'\n : 'ascending'\n : dir === 'ascending'\n ? 'descending'\n : 'ascending';\n }\n\n #emitSortingEvent(detail: SortExpression<T>) {\n return this.host.emitEvent('sorting', { detail, cancelable: true });\n }\n\n #emitSortedEvent(detail: SortExpression<T>) {\n return this.host.emitEvent('sorted', { detail });\n }\n\n #setExpression(expression: SortExpression<T>) {\n expression.direction === 'none'\n ? this.reset(expression.key)\n : this.state.set(expression.key, { ...expression });\n }\n\n public async sortFromHeaderClick(column: ColumnConfiguration<T>, additive = false) {\n const expression = this.prepareExpression(column);\n\n if (!this.#emitSortingEvent(expression)) {\n return;\n }\n\n // A plain click sorts by this column alone, clearing any other sorted\n // columns. Only a Ctrl/Cmd+click while multi-sort is enabled keeps the\n // existing sort keys and appends this column as an additional, lower-\n // priority sort. Without this gate every plain click would accumulate a\n // key, so a second column added behind a unique primary column would not\n // change the visible order at all.\n if (!this.#isMultipleSort || !additive) {\n this.reset();\n }\n\n // Capture visible-row rects keyed by data identity BEFORE the pipeline\n // runs. After the new render the virtualizer will have repositioned\n // rows; we use those captured rects as the \"First\" of a FLIP and\n // animate each row from its old viewport position back to identity.\n const beforeRects = this.#captureRowRects();\n\n this._sort(expression);\n\n await this.host.updateComplete;\n // Wait for body row updates so the new data is committed to each row\n // element before measuring the \"Last\" rect — otherwise the captured-\n // before and measured-after rects match and the animation no-ops.\n await awaitChildUpdates(this.host.rows);\n this.#playRowFlip(beforeRects);\n this.#announce(expression, column);\n this.#emitSortedEvent(expression);\n }\n\n #announce(expression: SortExpression<T>, column: ColumnConfiguration<T>) {\n const label = column.headerText ?? String(column.key);\n const message =\n expression.direction === 'none'\n ? `Sort cleared on ${label}`\n : `Sorted by ${label} ${expression.direction}`;\n this.host.announce(message);\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 #playRowFlip(before: ReadonlyArray<KeyedFlipEntry<T>>) {\n // After sort, the same data may be rendered by a different `<apex-grid-row>`\n // DOM element (virtualizer recycles rows). Resolve by data identity.\n const rows = this.host.rows;\n const byData = new Map<T, HTMLElement>();\n for (const row of rows) {\n byData.set(row.data, row as unknown as HTMLElement);\n }\n playKeyedFlip(before, (data) => byData.get(data) ?? null, 'y');\n }\n\n public prepareExpression({ key, sort: options }: ColumnConfiguration<T>): SortExpression<T> {\n if (this.state.has(key)) {\n const expr = this.state.get(key)!;\n\n return Object.assign(expr, {\n direction: this.#orderBy(expr.direction),\n ...this.#resolveSortOptions(options),\n });\n }\n\n // Initial state\n return this.#createDefaultExpression(key);\n }\n\n public reset(key?: Keys<T>) {\n key ? this.state.delete(key) : this.state.clear();\n }\n\n get #rowReorder() {\n // @ts-expect-error - protected member access\n return this.host.stateController.rowReorder as\n | { hasManualOrder: boolean; clearManualOrder(announce?: boolean): void }\n | undefined;\n }\n\n protected _sort(expressions: SortExpression<T> | SortExpression<T>[]) {\n const exprs = asArray(expressions);\n // Applying a sort is mutually exclusive with a manual row order (F5):\n // a real sort (any non-`none` direction) clears it, with an announcement.\n const reorder = this.#rowReorder;\n if (reorder?.hasManualOrder && exprs.some((expr) => expr.direction !== 'none')) {\n reorder.clearManualOrder(true);\n }\n\n for (const expr of exprs) {\n this.#setExpression(expr);\n }\n\n this.host.requestUpdate(PIPELINE);\n }\n\n public sort(expressions: SortExpression<T> | SortExpression<T>[]) {\n this._sort(\n asArray(expressions).map((expr) =>\n Object.assign(this.state.get(expr.key) ?? this.#createDefaultExpression(expr.key), expr)\n )\n );\n }\n\n public hostConnected() {}\n}\n"]}