apex-grid 3.3.0 → 3.5.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 (132) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +74 -6
  3. package/components/cell.d.ts +2 -2
  4. package/components/cell.js +30 -2
  5. package/components/cell.js.map +1 -1
  6. package/components/filter-row.js +65 -6
  7. package/components/filter-row.js.map +1 -1
  8. package/components/grid.d.ts +132 -0
  9. package/components/grid.js +59 -2
  10. package/components/grid.js.map +1 -1
  11. package/components/header-row.js +8 -0
  12. package/components/header-row.js.map +1 -1
  13. package/components/header.js +85 -12
  14. package/components/header.js.map +1 -1
  15. package/components/row.js +32 -4
  16. package/components/row.js.map +1 -1
  17. package/components/virtualizer.d.ts +5 -1
  18. package/components/virtualizer.js +72 -2
  19. package/components/virtualizer.js.map +1 -1
  20. package/controllers/expansion.js +4 -4
  21. package/controllers/expansion.js.map +1 -1
  22. package/controllers/filter.js +11 -0
  23. package/controllers/filter.js.map +1 -1
  24. package/controllers/history.js +11 -4
  25. package/controllers/history.js.map +1 -1
  26. package/controllers/navigation.d.ts +35 -2
  27. package/controllers/navigation.js +102 -8
  28. package/controllers/navigation.js.map +1 -1
  29. package/controllers/pagination.js +1 -1
  30. package/controllers/pagination.js.map +1 -1
  31. package/controllers/reorder.js +4 -2
  32. package/controllers/reorder.js.map +1 -1
  33. package/controllers/row-pin.js +2 -2
  34. package/controllers/row-pin.js.map +1 -1
  35. package/controllers/row-reorder.js +6 -6
  36. package/controllers/row-reorder.js.map +1 -1
  37. package/controllers/selection.js +3 -3
  38. package/controllers/selection.js.map +1 -1
  39. package/controllers/sort.js +4 -2
  40. package/controllers/sort.js.map +1 -1
  41. package/controllers/state.d.ts +9 -3
  42. package/controllers/state.js +11 -1
  43. package/controllers/state.js.map +1 -1
  44. package/custom-elements.json +664 -190
  45. package/docs/assets/hierarchy.js +1 -1
  46. package/docs/assets/navigation.js +1 -1
  47. package/docs/assets/search.js +1 -1
  48. package/docs/classes/ApexGrid.html +586 -0
  49. package/docs/classes/ApexGridPaginator.html +36 -0
  50. package/docs/classes/ApexGridToolbar.html +67 -0
  51. package/docs/functions/localize.html +2 -2
  52. package/docs/functions/setup.html +1 -2
  53. package/docs/hierarchy.html +1 -1
  54. package/docs/index.html +94 -6
  55. package/docs/interfaces/ApexCellValidationFailedEvent.html +1 -1
  56. package/docs/interfaces/ApexCellValueChangedEvent.html +1 -1
  57. package/docs/interfaces/ApexCellValueChangingEvent.html +2 -2
  58. package/docs/interfaces/ApexColumnMovedEvent.html +2 -2
  59. package/docs/interfaces/ApexColumnMovingEvent.html +1 -1
  60. package/docs/interfaces/ApexDetailContext.html +2 -2
  61. package/docs/interfaces/ApexGridEventMap.html +5 -5
  62. package/docs/interfaces/ApexRowMovingEvent.html +1 -1
  63. package/docs/interfaces/ApexRowPinningEvent.html +1 -1
  64. package/docs/interfaces/BaseApexCellContext.html +1 -1
  65. package/docs/interfaces/BaseApexEditorContext.html +1 -1
  66. package/docs/interfaces/BaseColumnConfiguration.html +3 -3
  67. package/docs/interfaces/CSVExportOptions.html +2 -2
  68. package/docs/interfaces/DataPipelineConfiguration.html +2 -2
  69. package/docs/interfaces/EditingHistoryConfiguration.html +1 -1
  70. package/docs/interfaces/ExportFormat.html +2 -2
  71. package/docs/interfaces/ExportOptions.html +1 -1
  72. package/docs/interfaces/GridRowPinningConfiguration.html +1 -1
  73. package/docs/interfaces/GridRowReorderingConfiguration.html +2 -2
  74. package/docs/interfaces/PaginationConfiguration.html +1 -1
  75. package/docs/interfaces/PaginationState.html +1 -1
  76. package/docs/media/_tokens.scss +29 -8
  77. package/docs/media/package.json +4 -5
  78. package/docs/media/state-persistence.html +0 -10
  79. package/docs/modules.html +1 -1
  80. package/docs/types/DataPipelineParams.html +2 -2
  81. package/docs/types/EditMode.html +2 -2
  82. package/docs/types/GridLocaleText.html +1 -1
  83. package/docs/types/PaginationMode.html +1 -1
  84. package/docs/variables/EN_LOCALE.html +3 -3
  85. package/docs/variables/esLocale.html +1 -1
  86. package/i18n/en.d.ts +109 -1
  87. package/i18n/en.js +109 -1
  88. package/i18n/en.js.map +1 -1
  89. package/i18n/es.js +109 -1
  90. package/i18n/es.js.map +1 -1
  91. package/index.d.ts +2 -0
  92. package/index.js +2 -0
  93. package/index.js.map +1 -1
  94. package/internal/column-types.js +1 -1
  95. package/internal/column-types.js.map +1 -1
  96. package/internal/feature-module.d.ts +22 -0
  97. package/internal/feature-module.js +5 -0
  98. package/internal/feature-module.js.map +1 -1
  99. package/internal/index.d.ts +2 -2
  100. package/internal/index.js +1 -1
  101. package/internal/index.js.map +1 -1
  102. package/internal/utils.d.ts +14 -0
  103. package/internal/utils.js +5 -0
  104. package/internal/utils.js.map +1 -1
  105. package/package.json +1 -2
  106. package/setup.d.ts +12 -11
  107. package/setup.js +12 -4
  108. package/setup.js.map +1 -1
  109. package/styles/_common.css.js +1 -1
  110. package/styles/_common.css.js.map +1 -1
  111. package/styles/_tokens.css.js +1 -1
  112. package/styles/_tokens.css.js.map +1 -1
  113. package/styles/body-cell/body-cell.css.js +1 -1
  114. package/styles/body-cell/body-cell.css.js.map +1 -1
  115. package/styles/body-row/body-row.css.js +1 -1
  116. package/styles/body-row/body-row.css.js.map +1 -1
  117. package/styles/filter-row/filter-row.css.js +1 -1
  118. package/styles/filter-row/filter-row.css.js.map +1 -1
  119. package/styles/grid/_grid.base.css.js +1 -1
  120. package/styles/grid/_grid.base.css.js.map +1 -1
  121. package/styles/grid/grid.css.js +1 -1
  122. package/styles/grid/grid.css.js.map +1 -1
  123. package/styles/group-header-row/group-header-row.css.js +1 -1
  124. package/styles/group-header-row/group-header-row.css.js.map +1 -1
  125. package/styles/header-cell/header-cell.css.js +1 -1
  126. package/styles/header-cell/header-cell.css.js.map +1 -1
  127. package/styles/header-row/header-row.base.css.js +1 -1
  128. package/styles/header-row/header-row.base.css.js.map +1 -1
  129. package/styles/paginator/paginator.css.js +1 -1
  130. package/styles/paginator/paginator.css.js.map +1 -1
  131. package/styles/toolbar/toolbar.css.js +1 -1
  132. package/styles/toolbar/toolbar.css.js.map +1 -1
@@ -1,6 +1,6 @@
1
1
  import { NAVIGATION_STATE, SENTINEL_NODE } from '../internal/constants.js';
2
2
  import { GRID_ROW_TAG } from '../internal/tags.js';
3
- import { getDisplayColumns } from '../internal/utils.js';
3
+ import { getDisplayColumns, isRTL } from '../internal/utils.js';
4
4
  export class NavigationController {
5
5
  get virtualizer() {
6
6
  return this.host.scrollContainer;
@@ -39,6 +39,24 @@ export class NavigationController {
39
39
  ?.scrollIntoView({ block: 'nearest' });
40
40
  }
41
41
  }
42
+ findActiveCell() {
43
+ const row = Array.from(this.virtualizer.querySelectorAll(GRID_ROW_TAG)).find((el) => el.index === this.active.row);
44
+ return row?.cells.find((cell) => cell.column.key === this.active.column);
45
+ }
46
+ focusActiveCell() {
47
+ const node = this.state.get('current');
48
+ if (!node || node === SENTINEL_NODE)
49
+ return;
50
+ void (async () => {
51
+ await this.host.updateComplete;
52
+ let cell = this.findActiveCell();
53
+ if (!cell) {
54
+ await new Promise(requestAnimationFrame);
55
+ cell = this.findActiveCell();
56
+ }
57
+ cell?.focus({ preventScroll: true });
58
+ })();
59
+ }
42
60
  get active() {
43
61
  return this._active;
44
62
  }
@@ -57,6 +75,8 @@ export class NavigationController {
57
75
  ArrowRight: this.arrowRight,
58
76
  Home: this.home,
59
77
  End: this.end,
78
+ PageUp: this.pageUp,
79
+ PageDown: this.pageDown,
60
80
  ' ': this.toggleSelection,
61
81
  }));
62
82
  this.state = NAVIGATION_STATE;
@@ -64,11 +84,45 @@ export class NavigationController {
64
84
  this.host.addController(this);
65
85
  }
66
86
  home() {
67
- this.active = Object.assign(this.nextNode, { row: 0 });
68
- this.virtualizer.element(this.active.row)?.scrollIntoView({ block: 'nearest' });
87
+ this.active = Object.assign(this.nextNode, { column: this.firstColumn });
88
+ this.scrollToCell(this.active);
69
89
  }
70
90
  end() {
71
- this.active = Object.assign(this.nextNode, { row: this.host.pageItems.length - 1 });
91
+ const last = this.visibleColumns.at(-1)?.key ?? this.firstColumn;
92
+ this.active = Object.assign(this.nextNode, { column: last });
93
+ this.scrollToCell(this.active);
94
+ }
95
+ ctrlHome() {
96
+ this.active = { column: this.firstColumn, row: 0 };
97
+ this.virtualizer.element(0)?.scrollIntoView({ block: 'nearest' });
98
+ this.scrollToCell(this.active);
99
+ }
100
+ ctrlEnd() {
101
+ const row = Math.max(0, this.host.pageItems.length - 1);
102
+ const last = this.visibleColumns.at(-1)?.key ?? this.firstColumn;
103
+ this.active = { column: last, row };
104
+ this.virtualizer.element(row)?.scrollIntoView({ block: 'nearest' });
105
+ this.scrollToCell(this.active);
106
+ }
107
+ pageStep() {
108
+ const rowEl = this.virtualizer.element(this.nextNode.row);
109
+ const rowHeight = rowEl?.offsetHeight || 0;
110
+ const viewport = this.host.clientHeight || 0;
111
+ if (!rowHeight || !viewport)
112
+ return 10;
113
+ const rows = Math.floor(viewport / rowHeight) - 1;
114
+ return rows > 0 ? rows : 10;
115
+ }
116
+ pageUp() {
117
+ const next = this.nextNode;
118
+ this.active = Object.assign(next, { row: Math.max(0, next.row - this.pageStep()) });
119
+ this.virtualizer.element(this.active.row)?.scrollIntoView({ block: 'nearest' });
120
+ }
121
+ pageDown() {
122
+ const next = this.nextNode;
123
+ this.active = Object.assign(next, {
124
+ row: Math.min(this.host.pageItems.length - 1, next.row + this.pageStep()),
125
+ });
72
126
  this.virtualizer.element(this.active.row)?.scrollIntoView({ block: 'nearest' });
73
127
  }
74
128
  arrowDown() {
@@ -84,13 +138,18 @@ export class NavigationController {
84
138
  this.virtualizer.element(next.row)?.scrollIntoView({ block: 'nearest' });
85
139
  }
86
140
  arrowLeft() {
87
- const next = this.nextNode;
88
- this.active = Object.assign(next, { column: this.getPreviousColumn(next.column) });
89
- this.scrollToCell(this.active);
141
+ this.#moveInline('left');
90
142
  }
91
143
  arrowRight() {
144
+ this.#moveInline('right');
145
+ }
146
+ #moveInline(direction) {
147
+ const towardsEnd = isRTL(this.host) === (direction === 'left');
92
148
  const next = this.nextNode;
93
- this.active = Object.assign(next, { column: this.getNextColumn(next.column) });
149
+ const column = towardsEnd
150
+ ? this.getNextColumn(next.column)
151
+ : this.getPreviousColumn(next.column);
152
+ this.active = Object.assign(next, { column });
94
153
  this.scrollToCell(this.active);
95
154
  }
96
155
  toggleSelection() {
@@ -104,6 +163,27 @@ export class NavigationController {
104
163
  this.active = SENTINEL_NODE;
105
164
  this.state = NAVIGATION_STATE;
106
165
  }
166
+ get editing() {
167
+ return this.host.stateController.editing;
168
+ }
169
+ handleEditKey(event) {
170
+ if (event.key !== 'Enter' && event.key !== 'F2')
171
+ return false;
172
+ if (event.ctrlKey || event.metaKey || event.altKey)
173
+ return false;
174
+ const node = this.state.get('current');
175
+ if (!node || node === SENTINEL_NODE)
176
+ return false;
177
+ const editing = this.editing;
178
+ if (!editing?.enabled)
179
+ return false;
180
+ const column = this.host.getColumn(this.active.column);
181
+ if (!column || !editing.isEditable(column))
182
+ return false;
183
+ event.preventDefault();
184
+ void editing.editCell(this.active.row, this.active.column);
185
+ return true;
186
+ }
107
187
  get rowReorder() {
108
188
  return this.host.stateController.rowReorder;
109
189
  }
@@ -157,10 +237,24 @@ export class NavigationController {
157
237
  this.host.redo();
158
238
  return;
159
239
  }
240
+ if (event.key === 'Home' || event.key === 'End') {
241
+ event.preventDefault();
242
+ if (event.key === 'Home') {
243
+ this.ctrlHome();
244
+ }
245
+ else {
246
+ this.ctrlEnd();
247
+ }
248
+ this.focusActiveCell();
249
+ return;
250
+ }
160
251
  }
252
+ if (this.handleEditKey(event))
253
+ return;
161
254
  if (this.handlers.has(event.key)) {
162
255
  event.preventDefault();
163
256
  this.handlers.get(event.key).call(this);
257
+ this.focusActiveCell();
164
258
  }
165
259
  }
166
260
  }
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/controllers/navigation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type ApexGridRow from '../components/row.js';\nimport { NAVIGATION_STATE, SENTINEL_NODE } from '../internal/constants.js';\nimport { GRID_ROW_TAG } from '../internal/tags.js';\nimport type { ActiveNode, GridHost, Keys } from '../internal/types.js';\nimport { getDisplayColumns } from '../internal/utils.js';\n\nexport class NavigationController<T extends object> implements ReactiveController {\n protected handlers = new Map(\n Object.entries({\n ArrowDown: this.arrowDown,\n ArrowUp: this.arrowUp,\n ArrowLeft: this.arrowLeft,\n ArrowRight: this.arrowRight,\n Home: this.home,\n End: this.end,\n ' ': this.toggleSelection,\n })\n );\n\n protected get virtualizer() {\n // @ts-expect-error - Protected member access\n return this.host.scrollContainer;\n }\n\n protected state = NAVIGATION_STATE;\n protected _active = SENTINEL_NODE;\n\n protected get nextNode() {\n const node = this.state.get('current')!;\n return node === SENTINEL_NODE\n ? { column: this.firstColumn, row: 0 }\n : ({ ...node } as ActiveNode<T>);\n }\n\n protected get columns(): Array<{ key: Keys<T>; hidden?: boolean }> {\n return getDisplayColumns(this.host.columns) as unknown as Array<{\n key: Keys<T>;\n hidden?: boolean;\n }>;\n }\n\n protected get visibleColumns() {\n return this.columns.filter((column) => !column.hidden);\n }\n\n protected get firstColumn(): Keys<T> {\n const first = this.visibleColumns.at(0);\n return (first?.key ?? (this.host.columns[0]?.key as Keys<T>)) as Keys<T>;\n }\n\n protected getPreviousColumn(key: Keys<T>): Keys<T> {\n const columns = this.visibleColumns;\n const idx = columns.findIndex((column) => column.key === key);\n return columns[Math.max(idx - 1, 0)].key;\n }\n\n protected getNextColumn(key: Keys<T>): Keys<T> {\n const columns = this.visibleColumns;\n const idx = columns.findIndex((column) => column.key === key);\n return columns[Math.min(idx + 1, columns.length - 1)].key;\n }\n\n protected scrollToCell(node: ActiveNode<T>) {\n const row = Array.from(this.virtualizer.querySelectorAll(GRID_ROW_TAG)).find(\n (row) => (row as unknown as ApexGridRow<T>).index === node.row\n ) as unknown as ApexGridRow<T>;\n\n if (row) {\n row.cells\n .find((cell) => cell.column.key === node.column)\n ?.scrollIntoView({ block: 'nearest' });\n }\n }\n\n public get active() {\n return this._active as ActiveNode<T>;\n }\n\n public set active(node: ActiveNode<T>) {\n this._active = node;\n this.state.set('previous', this._active);\n this.state.set('current', node);\n this.host.requestUpdate();\n }\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n protected home() {\n this.active = Object.assign(this.nextNode, { row: 0 });\n this.virtualizer.element(this.active.row)?.scrollIntoView({ block: 'nearest' });\n }\n\n protected end() {\n this.active = Object.assign(this.nextNode, { row: this.host.pageItems.length - 1 });\n this.virtualizer.element(this.active.row)?.scrollIntoView({ block: 'nearest' });\n }\n\n protected arrowDown() {\n const next = this.nextNode;\n\n this.active = Object.assign(next, {\n row: Math.min(next.row + 1, this.host.pageItems.length - 1),\n });\n this.virtualizer.element(next.row)?.scrollIntoView({ block: 'nearest' });\n }\n\n protected arrowUp() {\n const next = this.nextNode;\n this.active = Object.assign(next, { row: Math.max(0, next.row - 1) });\n this.virtualizer.element(next.row)?.scrollIntoView({ block: 'nearest' });\n }\n\n protected arrowLeft() {\n const next = this.nextNode;\n this.active = Object.assign(next, { column: this.getPreviousColumn(next.column) });\n this.scrollToCell(this.active);\n }\n\n protected arrowRight() {\n const next = this.nextNode;\n this.active = Object.assign(next, { column: this.getNextColumn(next.column) });\n this.scrollToCell(this.active);\n }\n\n protected toggleSelection() {\n // Space on the focused grid toggles selection of the active row.\n // No-op when selection is disabled or there's no active row.\n const data = this.host.pageItems[this.active.row] as T | undefined;\n if (!data) return;\n void this.host.toggleRowSelection(data);\n }\n\n public hostConnected() {}\n\n public hostDisconnected() {\n this.active = SENTINEL_NODE as ActiveNode<T>;\n this.state = NAVIGATION_STATE;\n }\n\n protected get rowReorder() {\n // @ts-expect-error - protected member access\n return this.host.stateController.rowReorder as\n | {\n enabled: boolean;\n isGrabbing: boolean;\n grab(rowIndex: number): boolean;\n moveGrabbed(direction: -1 | 1): number;\n drop(): void;\n cancelGrab(): void;\n }\n | undefined;\n }\n\n /**\n * Keyboard row reorder: Space grabs the active row, arrows move it, Space /\n * Enter drops, Escape cancels. Returns `true` when the event was consumed.\n * Only active when `rowReordering.enabled` (so it never shadows Space-to-select\n * on grids without reordering).\n */\n protected handleReorderKey(event: KeyboardEvent): boolean {\n const reorder = this.rowReorder;\n if (!reorder?.enabled) return false;\n\n if (reorder.isGrabbing) {\n switch (event.key) {\n case 'ArrowUp':\n case 'ArrowDown': {\n event.preventDefault();\n const next = reorder.moveGrabbed(event.key === 'ArrowDown' ? 1 : -1);\n if (next >= 0) {\n this.active = Object.assign(this.nextNode, { row: next });\n this.virtualizer.element(next)?.scrollIntoView({ block: 'nearest' });\n }\n return true;\n }\n case ' ':\n case 'Enter':\n event.preventDefault();\n reorder.drop();\n return true;\n case 'Escape':\n event.preventDefault();\n reorder.cancelGrab();\n return true;\n default:\n return false;\n }\n }\n\n if (event.key === ' ' && this.active.row >= 0) {\n event.preventDefault();\n return reorder.grab(this.active.row);\n }\n return false;\n }\n\n public navigate(event: KeyboardEvent) {\n if (this.handleReorderKey(event)) return;\n // Undo / redo: Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z, Ctrl/Cmd+Y. Only fires when the\n // grid body (not an open editor) has focus, so a text editor's native undo\n // is never hijacked.\n if ((event.ctrlKey || event.metaKey) && !event.altKey) {\n const key = event.key.toLowerCase();\n if (key === 'z' && !event.shiftKey) {\n event.preventDefault();\n this.host.undo();\n return;\n }\n if ((key === 'z' && event.shiftKey) || key === 'y') {\n event.preventDefault();\n this.host.redo();\n return;\n }\n }\n if (this.handlers.has(event.key)) {\n event.preventDefault();\n this.handlers.get(event.key)!.call(this);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/controllers/navigation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type ApexGridRow from '../components/row.js';\nimport { NAVIGATION_STATE, SENTINEL_NODE } from '../internal/constants.js';\nimport { GRID_ROW_TAG } from '../internal/tags.js';\nimport type { ActiveNode, ColumnConfiguration, GridHost, Keys } from '../internal/types.js';\nimport { getDisplayColumns, isRTL } from '../internal/utils.js';\n\nexport class NavigationController<T extends object> implements ReactiveController {\n protected handlers = new Map(\n Object.entries({\n ArrowDown: this.arrowDown,\n ArrowUp: this.arrowUp,\n ArrowLeft: this.arrowLeft,\n ArrowRight: this.arrowRight,\n Home: this.home,\n End: this.end,\n PageUp: this.pageUp,\n PageDown: this.pageDown,\n ' ': this.toggleSelection,\n })\n );\n\n protected get virtualizer() {\n // @ts-expect-error - Protected member access\n return this.host.scrollContainer;\n }\n\n protected state = NAVIGATION_STATE;\n protected _active = SENTINEL_NODE;\n\n protected get nextNode() {\n const node = this.state.get('current')!;\n return node === SENTINEL_NODE\n ? { column: this.firstColumn, row: 0 }\n : ({ ...node } as ActiveNode<T>);\n }\n\n protected get columns(): Array<{ key: Keys<T>; hidden?: boolean }> {\n return getDisplayColumns(this.host.columns) as unknown as Array<{\n key: Keys<T>;\n hidden?: boolean;\n }>;\n }\n\n protected get visibleColumns() {\n return this.columns.filter((column) => !column.hidden);\n }\n\n protected get firstColumn(): Keys<T> {\n const first = this.visibleColumns.at(0);\n return (first?.key ?? (this.host.columns[0]?.key as Keys<T>)) as Keys<T>;\n }\n\n protected getPreviousColumn(key: Keys<T>): Keys<T> {\n const columns = this.visibleColumns;\n const idx = columns.findIndex((column) => column.key === key);\n return columns[Math.max(idx - 1, 0)].key;\n }\n\n protected getNextColumn(key: Keys<T>): Keys<T> {\n const columns = this.visibleColumns;\n const idx = columns.findIndex((column) => column.key === key);\n return columns[Math.min(idx + 1, columns.length - 1)].key;\n }\n\n protected scrollToCell(node: ActiveNode<T>) {\n const row = Array.from(this.virtualizer.querySelectorAll(GRID_ROW_TAG)).find(\n (row) => (row as unknown as ApexGridRow<T>).index === node.row\n ) as unknown as ApexGridRow<T>;\n\n if (row) {\n row.cells\n .find((cell) => cell.column.key === node.column)\n ?.scrollIntoView({ block: 'nearest' });\n }\n }\n\n protected findActiveCell() {\n const row = Array.from(this.virtualizer.querySelectorAll(GRID_ROW_TAG)).find(\n (el) => (el as unknown as ApexGridRow<T>).index === this.active.row\n ) as unknown as ApexGridRow<T> | undefined;\n return row?.cells.find((cell) => cell.column.key === this.active.column);\n }\n\n /**\n * Moves real DOM focus to the active cell (the grid body's roving tab stop),\n * so screen readers track keyboard navigation. Waits for the host render to\n * commit the cell's `active` state, then retries once on the next frame to\n * cover virtualizer re-materialization after a long jump. No-op while no\n * cell is active (the scroll container keeps focus as the entry point).\n */\n public focusActiveCell(): void {\n const node = this.state.get('current');\n if (!node || node === SENTINEL_NODE) return;\n void (async () => {\n await this.host.updateComplete;\n let cell = this.findActiveCell();\n if (!cell) {\n await new Promise(requestAnimationFrame);\n cell = this.findActiveCell();\n }\n cell?.focus({ preventScroll: true });\n })();\n }\n\n public get active() {\n return this._active as ActiveNode<T>;\n }\n\n public set active(node: ActiveNode<T>) {\n this._active = node;\n this.state.set('previous', this._active);\n this.state.set('current', node);\n this.host.requestUpdate();\n }\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n /** Home moves to the first cell in the row (ARIA grid pattern). */\n protected home() {\n this.active = Object.assign(this.nextNode, { column: this.firstColumn });\n this.scrollToCell(this.active);\n }\n\n /** End moves to the last cell in the row (ARIA grid pattern). */\n protected end() {\n const last = this.visibleColumns.at(-1)?.key ?? this.firstColumn;\n this.active = Object.assign(this.nextNode, { column: last });\n this.scrollToCell(this.active);\n }\n\n /** Ctrl/Cmd+Home moves to the first cell of the grid. */\n protected ctrlHome() {\n this.active = { column: this.firstColumn, row: 0 } as ActiveNode<T>;\n this.virtualizer.element(0)?.scrollIntoView({ block: 'nearest' });\n this.scrollToCell(this.active);\n }\n\n /** Ctrl/Cmd+End moves to the last cell of the grid. */\n protected ctrlEnd() {\n const row = Math.max(0, this.host.pageItems.length - 1);\n const last = this.visibleColumns.at(-1)?.key ?? this.firstColumn;\n this.active = { column: last, row } as ActiveNode<T>;\n this.virtualizer.element(row)?.scrollIntoView({ block: 'nearest' });\n this.scrollToCell(this.active);\n }\n\n /**\n * Rows per Page Up/Down step, derived from the host viewport and the height\n * of a rendered row (fallback: 10 rows when nothing is measurable yet).\n */\n protected pageStep(): number {\n const rowEl = this.virtualizer.element(this.nextNode.row) as HTMLElement | undefined;\n const rowHeight = rowEl?.offsetHeight || 0;\n const viewport = (this.host as unknown as HTMLElement).clientHeight || 0;\n if (!rowHeight || !viewport) return 10;\n const rows = Math.floor(viewport / rowHeight) - 1;\n return rows > 0 ? rows : 10;\n }\n\n protected pageUp() {\n const next = this.nextNode;\n this.active = Object.assign(next, { row: Math.max(0, next.row - this.pageStep()) });\n this.virtualizer.element(this.active.row)?.scrollIntoView({ block: 'nearest' });\n }\n\n protected pageDown() {\n const next = this.nextNode;\n this.active = Object.assign(next, {\n row: Math.min(this.host.pageItems.length - 1, next.row + this.pageStep()),\n });\n this.virtualizer.element(this.active.row)?.scrollIntoView({ block: 'nearest' });\n }\n\n protected arrowDown() {\n const next = this.nextNode;\n\n this.active = Object.assign(next, {\n row: Math.min(next.row + 1, this.host.pageItems.length - 1),\n });\n this.virtualizer.element(next.row)?.scrollIntoView({ block: 'nearest' });\n }\n\n protected arrowUp() {\n const next = this.nextNode;\n this.active = Object.assign(next, { row: Math.max(0, next.row - 1) });\n this.virtualizer.element(next.row)?.scrollIntoView({ block: 'nearest' });\n }\n\n protected arrowLeft() {\n this.#moveInline('left');\n }\n\n protected arrowRight() {\n this.#moveInline('right');\n }\n\n /**\n * Moves the active cell one column in a *physical* direction.\n *\n * ArrowLeft and ArrowRight name screen directions, not column order, so under\n * `dir=\"rtl\"` the column to the left is the *next* one and the mapping onto\n * previous/next inverts. Everything else about navigation is order-based and\n * needs no direction awareness.\n */\n #moveInline(direction: 'left' | 'right') {\n const towardsEnd = isRTL(this.host as unknown as Element) === (direction === 'left');\n const next = this.nextNode;\n const column = towardsEnd\n ? this.getNextColumn(next.column)\n : this.getPreviousColumn(next.column);\n this.active = Object.assign(next, { column });\n this.scrollToCell(this.active);\n }\n\n protected toggleSelection() {\n // Space on the focused grid toggles selection of the active row.\n // No-op when selection is disabled or there's no active row.\n const data = this.host.pageItems[this.active.row] as T | undefined;\n if (!data) return;\n void this.host.toggleRowSelection(data);\n }\n\n public hostConnected() {}\n\n public hostDisconnected() {\n this.active = SENTINEL_NODE as ActiveNode<T>;\n this.state = NAVIGATION_STATE;\n }\n\n protected get editing() {\n // @ts-expect-error - protected member access\n return this.host.stateController.editing as\n | {\n enabled: boolean;\n isEditable(column: ColumnConfiguration<T>): boolean;\n editCell(rowIndex: number, columnKey: Keys<T>): Promise<boolean>;\n }\n | undefined;\n }\n\n /**\n * Enter / F2 on the active cell opens its editor (the keyboard path into\n * edit mode). Returns `true` when the event was consumed.\n */\n protected handleEditKey(event: KeyboardEvent): boolean {\n if (event.key !== 'Enter' && event.key !== 'F2') return false;\n if (event.ctrlKey || event.metaKey || event.altKey) return false;\n const node = this.state.get('current');\n if (!node || node === SENTINEL_NODE) return false;\n const editing = this.editing;\n if (!editing?.enabled) return false;\n const column = this.host.getColumn(this.active.column);\n if (!column || !editing.isEditable(column)) return false;\n event.preventDefault();\n void editing.editCell(this.active.row, this.active.column);\n return true;\n }\n\n protected get rowReorder() {\n // @ts-expect-error - protected member access\n return this.host.stateController.rowReorder as\n | {\n enabled: boolean;\n isGrabbing: boolean;\n grab(rowIndex: number): boolean;\n moveGrabbed(direction: -1 | 1): number;\n drop(): void;\n cancelGrab(): void;\n }\n | undefined;\n }\n\n /**\n * Keyboard row reorder: Space grabs the active row, arrows move it, Space /\n * Enter drops, Escape cancels. Returns `true` when the event was consumed.\n * Only active when `rowReordering.enabled` (so it never shadows Space-to-select\n * on grids without reordering).\n */\n protected handleReorderKey(event: KeyboardEvent): boolean {\n const reorder = this.rowReorder;\n if (!reorder?.enabled) return false;\n\n if (reorder.isGrabbing) {\n switch (event.key) {\n case 'ArrowUp':\n case 'ArrowDown': {\n event.preventDefault();\n const next = reorder.moveGrabbed(event.key === 'ArrowDown' ? 1 : -1);\n if (next >= 0) {\n this.active = Object.assign(this.nextNode, { row: next });\n this.virtualizer.element(next)?.scrollIntoView({ block: 'nearest' });\n }\n return true;\n }\n case ' ':\n case 'Enter':\n event.preventDefault();\n reorder.drop();\n return true;\n case 'Escape':\n event.preventDefault();\n reorder.cancelGrab();\n return true;\n default:\n return false;\n }\n }\n\n if (event.key === ' ' && this.active.row >= 0) {\n event.preventDefault();\n return reorder.grab(this.active.row);\n }\n return false;\n }\n\n public navigate(event: KeyboardEvent) {\n if (this.handleReorderKey(event)) return;\n // Undo / redo: Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z, Ctrl/Cmd+Y. Only fires when the\n // grid body (not an open editor) has focus, so a text editor's native undo\n // is never hijacked.\n if ((event.ctrlKey || event.metaKey) && !event.altKey) {\n const key = event.key.toLowerCase();\n if (key === 'z' && !event.shiftKey) {\n event.preventDefault();\n this.host.undo();\n return;\n }\n if ((key === 'z' && event.shiftKey) || key === 'y') {\n event.preventDefault();\n this.host.redo();\n return;\n }\n // Ctrl/Cmd+Home / Ctrl/Cmd+End jump to the grid corners (ARIA grid pattern).\n if (event.key === 'Home' || event.key === 'End') {\n event.preventDefault();\n if (event.key === 'Home') {\n this.ctrlHome();\n } else {\n this.ctrlEnd();\n }\n this.focusActiveCell();\n return;\n }\n }\n if (this.handleEditKey(event)) return;\n if (this.handlers.has(event.key)) {\n event.preventDefault();\n this.handlers.get(event.key)!.call(this);\n // Real focus follows the active cell so AT tracks the movement.\n this.focusActiveCell();\n }\n }\n}\n"]}
@@ -60,7 +60,7 @@ export class PaginationController {
60
60
  });
61
61
  }
62
62
  #emitChanged() {
63
- this.host.announce(`Page ${this.page + 1} of ${this.pageCount}`);
63
+ this.host.announce(this.host.localize('announce.page', { page: this.page + 1, total: this.pageCount }));
64
64
  return this.host.emitEvent('pageChanged', { detail: this.state });
65
65
  }
66
66
  async gotoPage(page) {
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../src/controllers/pagination.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport { PIPELINE } from '../internal/constants.js';\nimport type { GridHost, PaginationState } from '../internal/types.js';\n\n/**\n * Default pagination state used when no `pagination` configuration is provided.\n */\nexport const DEFAULT_PAGINATION = Object.freeze({\n mode: 'local' as const,\n page: 0,\n pageSize: 25,\n pageSizeOptions: Object.freeze([10, 25, 50, 100]) as readonly number[],\n});\n\n/**\n * Reactive controller that owns the grid's pagination state.\n *\n * @remarks\n * Exposes a state machine for the current `page` and `pageSize`, derives `pageCount`\n * from the host's data, and emits the cancellable `pageChanging` + `pageChanged`\n * events on the grid host. Page slicing itself is performed by\n * {@link PaginationDataOperation} so the controller does not mutate `dataView`.\n */\nexport class PaginationController<T extends object> implements ReactiveController {\n /**\n * The current zero-based page index.\n */\n public page: number = DEFAULT_PAGINATION.page;\n\n /**\n * The current page size.\n */\n public pageSize: number = DEFAULT_PAGINATION.pageSize;\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /**\n * Whether pagination is enabled on the host grid.\n */\n public get enabled(): boolean {\n return Boolean(this.host.pagination?.enabled);\n }\n\n /**\n * The total number of records before pagination is applied.\n *\n * @remarks\n * For `'local'` mode this is the post-filter, post-sort dataset length. For `'remote'`\n * mode the consumer must supply the value via `pagination.totalItems`.\n */\n public get totalItems(): number {\n const cfg = this.host.pagination;\n if (cfg?.mode === 'remote') {\n return Math.max(0, cfg?.totalItems ?? 0);\n }\n // @ts-expect-error - protected member access\n return this.host.dataState?.length ?? 0;\n }\n\n /**\n * The total number of pages for the current data view and `pageSize`.\n *\n * @remarks\n * Always at least `1` so a paginator can render a sensible \"1 / 1\" label for empty data.\n */\n public get pageCount(): number {\n if (!this.pageSize) return 1;\n return Math.max(1, Math.ceil(this.totalItems / this.pageSize));\n }\n\n /**\n * Returns the resolved {@link PaginationState} for the current host.\n */\n public get state(): PaginationState {\n return {\n page: this.page,\n pageSize: this.pageSize,\n pageCount: this.pageCount,\n totalItems: this.totalItems,\n };\n }\n\n /**\n * Clamps a candidate `page` index into the valid `[0, pageCount - 1]` range.\n *\n * @param page - The candidate page index.\n */\n public clamp(page: number): number {\n if (!Number.isFinite(page)) return 0;\n return Math.min(Math.max(0, Math.trunc(page)), Math.max(0, this.pageCount - 1));\n }\n\n /**\n * Re-clamps the current page after the dataset size (or `pageSize`) changes.\n *\n * @remarks\n * Called from the grid pipeline so paging never points past the last valid page.\n * Mutates `page` in place and requests a re-render when the value changes.\n */\n public reclamp(): void {\n const next = this.clamp(this.page);\n if (next !== this.page) {\n this.page = next;\n this.host.requestUpdate();\n }\n }\n\n #emitChanging(nextPage: number, nextPageSize: number) {\n return this.host.emitEvent('pageChanging', {\n detail: {\n page: this.page,\n pageSize: this.pageSize,\n nextPage,\n nextPageSize,\n },\n cancelable: true,\n });\n }\n\n #emitChanged() {\n this.host.announce(`Page ${this.page + 1} of ${this.pageCount}`);\n return this.host.emitEvent('pageChanged', { detail: this.state });\n }\n\n /**\n * Navigates to `page`, emitting the cancellable `pageChanging` event first and\n * the `pageChanged` event after the pipeline has applied.\n *\n * @param page - The target zero-based page index. Out-of-range values are clamped.\n * @returns `true` if the change was applied, `false` if it was cancelled or a no-op.\n */\n public async gotoPage(page: number): Promise<boolean> {\n const next = this.clamp(page);\n if (next === this.page) return false;\n\n if (!this.#emitChanging(next, this.pageSize)) {\n return false;\n }\n\n this.page = next;\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n this.#emitChanged();\n return true;\n }\n\n /**\n * Sets the current page size and resets to the first page.\n *\n * @param size - The new page size. Must be a positive integer.\n * @returns `true` if the change was applied, `false` if it was cancelled or a no-op.\n */\n public async setPageSize(size: number): Promise<boolean> {\n const next = Math.max(1, Math.trunc(size));\n if (next === this.pageSize) return false;\n\n if (!this.#emitChanging(0, next)) {\n return false;\n }\n\n this.pageSize = next;\n this.page = 0;\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n this.#emitChanged();\n return true;\n }\n\n /**\n * Navigates to the next page if not already on the last one.\n */\n public nextPage() {\n return this.gotoPage(this.page + 1);\n }\n\n /**\n * Navigates to the previous page if not already on the first one.\n */\n public previousPage() {\n return this.gotoPage(this.page - 1);\n }\n\n /**\n * Navigates to the first page.\n */\n public firstPage() {\n return this.gotoPage(0);\n }\n\n /**\n * Navigates to the last page.\n */\n public lastPage() {\n return this.gotoPage(this.pageCount - 1);\n }\n}\n"]}
1
+ {"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../src/controllers/pagination.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport { PIPELINE } from '../internal/constants.js';\nimport type { GridHost, PaginationState } from '../internal/types.js';\n\n/**\n * Default pagination state used when no `pagination` configuration is provided.\n */\nexport const DEFAULT_PAGINATION = Object.freeze({\n mode: 'local' as const,\n page: 0,\n pageSize: 25,\n pageSizeOptions: Object.freeze([10, 25, 50, 100]) as readonly number[],\n});\n\n/**\n * Reactive controller that owns the grid's pagination state.\n *\n * @remarks\n * Exposes a state machine for the current `page` and `pageSize`, derives `pageCount`\n * from the host's data, and emits the cancellable `pageChanging` + `pageChanged`\n * events on the grid host. Page slicing itself is performed by\n * {@link PaginationDataOperation} so the controller does not mutate `dataView`.\n */\nexport class PaginationController<T extends object> implements ReactiveController {\n /**\n * The current zero-based page index.\n */\n public page: number = DEFAULT_PAGINATION.page;\n\n /**\n * The current page size.\n */\n public pageSize: number = DEFAULT_PAGINATION.pageSize;\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /**\n * Whether pagination is enabled on the host grid.\n */\n public get enabled(): boolean {\n return Boolean(this.host.pagination?.enabled);\n }\n\n /**\n * The total number of records before pagination is applied.\n *\n * @remarks\n * For `'local'` mode this is the post-filter, post-sort dataset length. For `'remote'`\n * mode the consumer must supply the value via `pagination.totalItems`.\n */\n public get totalItems(): number {\n const cfg = this.host.pagination;\n if (cfg?.mode === 'remote') {\n return Math.max(0, cfg?.totalItems ?? 0);\n }\n // @ts-expect-error - protected member access\n return this.host.dataState?.length ?? 0;\n }\n\n /**\n * The total number of pages for the current data view and `pageSize`.\n *\n * @remarks\n * Always at least `1` so a paginator can render a sensible \"1 / 1\" label for empty data.\n */\n public get pageCount(): number {\n if (!this.pageSize) return 1;\n return Math.max(1, Math.ceil(this.totalItems / this.pageSize));\n }\n\n /**\n * Returns the resolved {@link PaginationState} for the current host.\n */\n public get state(): PaginationState {\n return {\n page: this.page,\n pageSize: this.pageSize,\n pageCount: this.pageCount,\n totalItems: this.totalItems,\n };\n }\n\n /**\n * Clamps a candidate `page` index into the valid `[0, pageCount - 1]` range.\n *\n * @param page - The candidate page index.\n */\n public clamp(page: number): number {\n if (!Number.isFinite(page)) return 0;\n return Math.min(Math.max(0, Math.trunc(page)), Math.max(0, this.pageCount - 1));\n }\n\n /**\n * Re-clamps the current page after the dataset size (or `pageSize`) changes.\n *\n * @remarks\n * Called from the grid pipeline so paging never points past the last valid page.\n * Mutates `page` in place and requests a re-render when the value changes.\n */\n public reclamp(): void {\n const next = this.clamp(this.page);\n if (next !== this.page) {\n this.page = next;\n this.host.requestUpdate();\n }\n }\n\n #emitChanging(nextPage: number, nextPageSize: number) {\n return this.host.emitEvent('pageChanging', {\n detail: {\n page: this.page,\n pageSize: this.pageSize,\n nextPage,\n nextPageSize,\n },\n cancelable: true,\n });\n }\n\n #emitChanged() {\n this.host.announce(\n this.host.localize('announce.page', { page: this.page + 1, total: this.pageCount })\n );\n return this.host.emitEvent('pageChanged', { detail: this.state });\n }\n\n /**\n * Navigates to `page`, emitting the cancellable `pageChanging` event first and\n * the `pageChanged` event after the pipeline has applied.\n *\n * @param page - The target zero-based page index. Out-of-range values are clamped.\n * @returns `true` if the change was applied, `false` if it was cancelled or a no-op.\n */\n public async gotoPage(page: number): Promise<boolean> {\n const next = this.clamp(page);\n if (next === this.page) return false;\n\n if (!this.#emitChanging(next, this.pageSize)) {\n return false;\n }\n\n this.page = next;\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n this.#emitChanged();\n return true;\n }\n\n /**\n * Sets the current page size and resets to the first page.\n *\n * @param size - The new page size. Must be a positive integer.\n * @returns `true` if the change was applied, `false` if it was cancelled or a no-op.\n */\n public async setPageSize(size: number): Promise<boolean> {\n const next = Math.max(1, Math.trunc(size));\n if (next === this.pageSize) return false;\n\n if (!this.#emitChanging(0, next)) {\n return false;\n }\n\n this.pageSize = next;\n this.page = 0;\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n this.#emitChanged();\n return true;\n }\n\n /**\n * Navigates to the next page if not already on the last one.\n */\n public nextPage() {\n return this.gotoPage(this.page + 1);\n }\n\n /**\n * Navigates to the previous page if not already on the first one.\n */\n public previousPage() {\n return this.gotoPage(this.page - 1);\n }\n\n /**\n * Navigates to the first page.\n */\n public firstPage() {\n return this.gotoPage(0);\n }\n\n /**\n * Navigates to the last page.\n */\n public lastPage() {\n return this.gotoPage(this.pageCount - 1);\n }\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { awaitChildUpdates, captureRect, playFlip } from '../internal/flip.js';
2
+ import { isRTL } from '../internal/utils.js';
2
3
  export class ReorderController {
3
4
  #swapping;
4
5
  constructor(host) {
@@ -81,14 +82,15 @@ export class ReorderController {
81
82
  const targetIdx = visibleColumns.findIndex((c) => c.key === target.column.key);
82
83
  if (sourceIdx === -1 || targetIdx === -1)
83
84
  return;
85
+ const rtl = isRTL(this.host);
84
86
  let position;
85
87
  if (sourceIdx < targetIdx) {
86
- if (clientX < midpoint)
88
+ if (rtl ? clientX > midpoint : clientX < midpoint)
87
89
  return;
88
90
  position = 'after';
89
91
  }
90
92
  else {
91
- if (clientX > midpoint)
93
+ if (rtl ? clientX < midpoint : clientX > midpoint)
92
94
  return;
93
95
  position = 'before';
94
96
  }
@@ -1 +1 @@
1
- {"version":3,"file":"reorder.js","sourceRoot":"","sources":["../../src/controllers/reorder.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport { awaitChildUpdates, captureRect, type FlipEntry, playFlip } from '../internal/flip.js';\nimport type { ColumnConfiguration, GridHost, Keys } from '../internal/types.js';\n\n/**\n * Live drag state surfaced to the header row so it can render the floating\n * \"ghost\" element that follows the cursor.\n *\n * @remarks\n * The dragged column itself stays in the columns array — we live-swap it\n * with neighbours as the cursor crosses their midpoints, so the rendered\n * order updates while the user is still dragging. The ghost is purely a\n * visual indicator of what the user is grabbing.\n */\ninterface ReorderState<T> {\n /** The column currently being dragged. */\n sourceKey: Keys<T>;\n /** Ghost position (viewport-relative top-left, in CSS pixels). */\n ghostX: number;\n ghostY: number;\n /** Ghost size — fixed at the source header's initial bounding rect. */\n ghostWidth: number;\n ghostHeight: number;\n /** Cursor offset within the ghost so the grab point stays under the cursor. */\n pointerOffsetX: number;\n pointerOffsetY: number;\n /** Display label for the ghost (header text or column key). */\n label: string;\n}\n\n/**\n * Reactive controller backing pointer-driven column reordering with a\n * floating ghost and live mid-drag swaps.\n *\n * @remarks\n * Headers feed `pointerdown` / `pointermove` / `pointerup` events into the\n * controller. While dragging, the controller updates the ghost position and\n * decides whether the cursor has crossed an adjacent column's midpoint —\n * if so it calls {@link ApexGrid.moveColumn} immediately, so the swap\n * happens during the drag instead of waiting for drop. The same cancellable\n * `columnMoving` / follow-up `columnMoved` event pipeline applies for\n * every swap.\n */\nexport class ReorderController<T extends object> implements ReactiveController {\n public state: ReorderState<T> | null = null;\n\n /** Re-entrancy guard so a slow `moveColumn` can't trigger a second swap. */\n #swapping = false;\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n protected get headerRow() {\n // @ts-expect-error - protected member access\n return this.host.headerRow as\n | (HTMLElement & { headers?: Array<HTMLElement & { column: ColumnConfiguration<T> }> })\n | undefined;\n }\n\n /**\n * Whether reordering is enabled for the given column.\n */\n public isDraggable(column: ColumnConfiguration<T>): boolean {\n if (!this.host.columnReordering) return false;\n if (column.hidden) return false;\n return column.reorderable !== false;\n }\n\n /**\n * Whether `source` may be dropped onto `target`. Cross-pinning-group moves\n * are blocked so the drag can't escape its pin region.\n */\n public canDrop(source: ColumnConfiguration<T>, target: ColumnConfiguration<T>): boolean {\n if (source.key === target.key) return false;\n if (!this.isDraggable(source) || !this.isDraggable(target)) return false;\n if ((source.pinned ?? null) !== (target.pinned ?? null)) return false;\n // Grouped columns reorder only within their own group; ungrouped only among\n // ungrouped. Keeps a group's members contiguous (v1 has no group-level move).\n return (source.group ?? null) === (target.group ?? null);\n }\n\n /**\n * Begins a drag from `sourceKey`. Captures the source's bounding rect and\n * the cursor offset within it so the ghost stays anchored to the grab\n * point as the cursor moves.\n */\n public start(\n sourceKey: Keys<T>,\n sourceRect: DOMRect,\n initialClientX: number,\n initialClientY: number,\n label: string\n ): void {\n this.state = {\n sourceKey,\n ghostX: sourceRect.left,\n ghostY: sourceRect.top,\n ghostWidth: sourceRect.width,\n ghostHeight: sourceRect.height,\n pointerOffsetX: initialClientX - sourceRect.left,\n pointerOffsetY: initialClientY - sourceRect.top,\n label,\n };\n this.host.requestUpdate();\n }\n\n /**\n * Updates the ghost position and decides whether to swap with an adjacent\n * column.\n */\n public move(clientX: number, clientY: number): void {\n if (!this.state) return;\n this.state = {\n ...this.state,\n ghostX: clientX - this.state.pointerOffsetX,\n ghostY: clientY - this.state.pointerOffsetY,\n };\n this.host.requestUpdate();\n void this.#checkSwap(clientX);\n }\n\n /**\n * Ends the drag session. The columns are already in their final order\n * because we live-swapped during the drag, so there's nothing to commit.\n */\n public end(): void {\n if (!this.state) return;\n this.state = null;\n this.host.requestUpdate();\n }\n\n async #checkSwap(clientX: number): Promise<void> {\n if (!this.state || this.#swapping) return;\n const source = this.host.getColumn(this.state.sourceKey);\n if (!source) return;\n const headerRow = this.headerRow;\n if (!headerRow?.headers) return;\n\n // Find the visible header whose box currently contains the cursor X.\n const target = headerRow.headers.find((h) => {\n const rect = h.getBoundingClientRect();\n return clientX >= rect.left && clientX < rect.right;\n });\n if (!target || target.column.key === source.key) return;\n if (!this.canDrop(source, target.column)) return;\n\n // Only swap once the cursor crosses the target's midpoint, so we don't\n // bounce back and forth as soon as the ghost touches the edge.\n const rect = target.getBoundingClientRect();\n const midpoint = rect.left + rect.width / 2;\n const visibleColumns = this.host.columns.filter((c) => !c.hidden);\n const sourceIdx = visibleColumns.findIndex((c) => c.key === source.key);\n const targetIdx = visibleColumns.findIndex((c) => c.key === target.column.key);\n if (sourceIdx === -1 || targetIdx === -1) return;\n\n let position: 'before' | 'after';\n if (sourceIdx < targetIdx) {\n // Dragging right — swap when the cursor crosses past the target's centre.\n if (clientX < midpoint) return;\n position = 'after';\n } else {\n // Dragging left — swap when the cursor crosses before the target's centre.\n if (clientX > midpoint) return;\n position = 'before';\n }\n\n this.#swapping = true;\n try {\n const before = this.#captureCellRects();\n await this.host.moveColumn(source.key, target.column.key, position);\n await this.host.updateComplete;\n // Child rows + header row schedule their own updates one microtask\n // after the grid — wait for them so the cells have actually moved\n // before measuring the \"Last\" rect.\n await awaitChildUpdates([\n this.headerRow as unknown as { updateComplete: Promise<unknown> } | undefined,\n ...this.host.rows,\n ]);\n playFlip(before, 'x');\n } finally {\n this.#swapping = false;\n }\n }\n\n /**\n * Snapshots the bounding rect of every header + body cell before a\n * swap. Used as the \"First\" step of a FLIP — after `moveColumn` resolves,\n * `playFlip` computes the delta against the new rect and animates each\n * element from its old position back to identity.\n */\n #captureCellRects(): FlipEntry[] {\n const entries: FlipEntry[] = [];\n const headerRow = this.headerRow;\n if (headerRow?.headers) {\n for (const header of headerRow.headers) {\n entries.push(captureRect(header));\n }\n }\n for (const row of this.host.rows) {\n for (const cell of row.cells) {\n entries.push(captureRect(cell as unknown as HTMLElement));\n }\n }\n return entries;\n }\n}\n"]}
1
+ {"version":3,"file":"reorder.js","sourceRoot":"","sources":["../../src/controllers/reorder.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport { awaitChildUpdates, captureRect, type FlipEntry, playFlip } from '../internal/flip.js';\nimport type { ColumnConfiguration, GridHost, Keys } from '../internal/types.js';\nimport { isRTL } from '../internal/utils.js';\n\n/**\n * Live drag state surfaced to the header row so it can render the floating\n * \"ghost\" element that follows the cursor.\n *\n * @remarks\n * The dragged column itself stays in the columns array — we live-swap it\n * with neighbours as the cursor crosses their midpoints, so the rendered\n * order updates while the user is still dragging. The ghost is purely a\n * visual indicator of what the user is grabbing.\n */\ninterface ReorderState<T> {\n /** The column currently being dragged. */\n sourceKey: Keys<T>;\n /** Ghost position (viewport-relative top-left, in CSS pixels). */\n ghostX: number;\n ghostY: number;\n /** Ghost size — fixed at the source header's initial bounding rect. */\n ghostWidth: number;\n ghostHeight: number;\n /** Cursor offset within the ghost so the grab point stays under the cursor. */\n pointerOffsetX: number;\n pointerOffsetY: number;\n /** Display label for the ghost (header text or column key). */\n label: string;\n}\n\n/**\n * Reactive controller backing pointer-driven column reordering with a\n * floating ghost and live mid-drag swaps.\n *\n * @remarks\n * Headers feed `pointerdown` / `pointermove` / `pointerup` events into the\n * controller. While dragging, the controller updates the ghost position and\n * decides whether the cursor has crossed an adjacent column's midpoint —\n * if so it calls {@link ApexGrid.moveColumn} immediately, so the swap\n * happens during the drag instead of waiting for drop. The same cancellable\n * `columnMoving` / follow-up `columnMoved` event pipeline applies for\n * every swap.\n */\nexport class ReorderController<T extends object> implements ReactiveController {\n public state: ReorderState<T> | null = null;\n\n /** Re-entrancy guard so a slow `moveColumn` can't trigger a second swap. */\n #swapping = false;\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n protected get headerRow() {\n // @ts-expect-error - protected member access\n return this.host.headerRow as\n | (HTMLElement & { headers?: Array<HTMLElement & { column: ColumnConfiguration<T> }> })\n | undefined;\n }\n\n /**\n * Whether reordering is enabled for the given column.\n */\n public isDraggable(column: ColumnConfiguration<T>): boolean {\n if (!this.host.columnReordering) return false;\n if (column.hidden) return false;\n return column.reorderable !== false;\n }\n\n /**\n * Whether `source` may be dropped onto `target`. Cross-pinning-group moves\n * are blocked so the drag can't escape its pin region.\n */\n public canDrop(source: ColumnConfiguration<T>, target: ColumnConfiguration<T>): boolean {\n if (source.key === target.key) return false;\n if (!this.isDraggable(source) || !this.isDraggable(target)) return false;\n if ((source.pinned ?? null) !== (target.pinned ?? null)) return false;\n // Grouped columns reorder only within their own group; ungrouped only among\n // ungrouped. Keeps a group's members contiguous (v1 has no group-level move).\n return (source.group ?? null) === (target.group ?? null);\n }\n\n /**\n * Begins a drag from `sourceKey`. Captures the source's bounding rect and\n * the cursor offset within it so the ghost stays anchored to the grab\n * point as the cursor moves.\n */\n public start(\n sourceKey: Keys<T>,\n sourceRect: DOMRect,\n initialClientX: number,\n initialClientY: number,\n label: string\n ): void {\n this.state = {\n sourceKey,\n ghostX: sourceRect.left,\n ghostY: sourceRect.top,\n ghostWidth: sourceRect.width,\n ghostHeight: sourceRect.height,\n pointerOffsetX: initialClientX - sourceRect.left,\n pointerOffsetY: initialClientY - sourceRect.top,\n label,\n };\n this.host.requestUpdate();\n }\n\n /**\n * Updates the ghost position and decides whether to swap with an adjacent\n * column.\n */\n public move(clientX: number, clientY: number): void {\n if (!this.state) return;\n this.state = {\n ...this.state,\n ghostX: clientX - this.state.pointerOffsetX,\n ghostY: clientY - this.state.pointerOffsetY,\n };\n this.host.requestUpdate();\n void this.#checkSwap(clientX);\n }\n\n /**\n * Ends the drag session. The columns are already in their final order\n * because we live-swapped during the drag, so there's nothing to commit.\n */\n public end(): void {\n if (!this.state) return;\n this.state = null;\n this.host.requestUpdate();\n }\n\n async #checkSwap(clientX: number): Promise<void> {\n if (!this.state || this.#swapping) return;\n const source = this.host.getColumn(this.state.sourceKey);\n if (!source) return;\n const headerRow = this.headerRow;\n if (!headerRow?.headers) return;\n\n // Find the visible header whose box currently contains the cursor X.\n const target = headerRow.headers.find((h) => {\n const rect = h.getBoundingClientRect();\n return clientX >= rect.left && clientX < rect.right;\n });\n if (!target || target.column.key === source.key) return;\n if (!this.canDrop(source, target.column)) return;\n\n // Only swap once the cursor crosses the target's midpoint, so we don't\n // bounce back and forth as soon as the ghost touches the edge.\n const rect = target.getBoundingClientRect();\n const midpoint = rect.left + rect.width / 2;\n const visibleColumns = this.host.columns.filter((c) => !c.hidden);\n const sourceIdx = visibleColumns.findIndex((c) => c.key === source.key);\n const targetIdx = visibleColumns.findIndex((c) => c.key === target.column.key);\n if (sourceIdx === -1 || targetIdx === -1) return;\n\n // `position` is order-based, so it needs no direction awareness. The\n // midpoint test does: moving toward a *later* column means moving right in\n // LTR and left under `dir=\"rtl\"`, so the comparison flips.\n const rtl = isRTL(this.host as unknown as Element);\n let position: 'before' | 'after';\n if (sourceIdx < targetIdx) {\n // Toward a later column — swap once the cursor crosses the target's centre.\n if (rtl ? clientX > midpoint : clientX < midpoint) return;\n position = 'after';\n } else {\n // Toward an earlier column — swap once the cursor crosses back over it.\n if (rtl ? clientX < midpoint : clientX > midpoint) return;\n position = 'before';\n }\n\n this.#swapping = true;\n try {\n const before = this.#captureCellRects();\n await this.host.moveColumn(source.key, target.column.key, position);\n await this.host.updateComplete;\n // Child rows + header row schedule their own updates one microtask\n // after the grid — wait for them so the cells have actually moved\n // before measuring the \"Last\" rect.\n await awaitChildUpdates([\n this.headerRow as unknown as { updateComplete: Promise<unknown> } | undefined,\n ...this.host.rows,\n ]);\n playFlip(before, 'x');\n } finally {\n this.#swapping = false;\n }\n }\n\n /**\n * Snapshots the bounding rect of every header + body cell before a\n * swap. Used as the \"First\" step of a FLIP — after `moveColumn` resolves,\n * `playFlip` computes the delta against the new rect and animates each\n * element from its old position back to identity.\n */\n #captureCellRects(): FlipEntry[] {\n const entries: FlipEntry[] = [];\n const headerRow = this.headerRow;\n if (headerRow?.headers) {\n for (const header of headerRow.headers) {\n entries.push(captureRect(header));\n }\n }\n for (const row of this.host.rows) {\n for (const cell of row.cells) {\n entries.push(captureRect(cell as unknown as HTMLElement));\n }\n }\n return entries;\n }\n}\n"]}
@@ -35,7 +35,7 @@ export class RowPinController {
35
35
  this.#top.delete(row);
36
36
  this.#bottom.delete(row);
37
37
  (position === 'top' ? this.#top : this.#bottom).add(row);
38
- this.#commit(row, position, `Row pinned to ${position}`);
38
+ this.#commit(row, position, this.host.localize(position === 'top' ? 'announce.rowPinnedTop' : 'announce.rowPinnedBottom'));
39
39
  return true;
40
40
  }
41
41
  unpinRow(row) {
@@ -45,7 +45,7 @@ export class RowPinController {
45
45
  return false;
46
46
  this.#top.delete(row);
47
47
  this.#bottom.delete(row);
48
- this.#commit(row, null, 'Row unpinned');
48
+ this.#commit(row, null, this.host.localize('announce.rowUnpinned'));
49
49
  return true;
50
50
  }
51
51
  clear() {
@@ -1 +1 @@
1
- {"version":3,"file":"row-pin.js","sourceRoot":"","sources":["../../src/controllers/row-pin.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type { GridHost } from '../internal/types.js';\n\n/** Where a row is pinned, or `null` when it scrolls with the body. */\nexport type RowPinPosition = 'top' | 'bottom';\n\n/**\n * Reactive controller backing row pinning.\n *\n * @remarks\n * Pinned rows are lifted out of the scrollable body and rendered in sticky\n * bands above (`'top'`) or below (`'bottom'`) the virtualized rows. State is\n * reference-based: the controller holds a `Set` of row data references per band,\n * so pins survive sort / filter / pagination as long as those preserve row\n * identity (the default in-place pipeline does). Replacing {@link ApexGrid.data}\n * wholesale should be followed by re-pinning or {@link clear}.\n *\n * Mutations go through the cancellable `rowPinning` event and emit a follow-up\n * `rowPinned` event, mirroring selection / expansion.\n */\nexport class RowPinController<T extends object> implements ReactiveController {\n #top: Set<T> = new Set();\n #bottom: Set<T> = new Set();\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /** Whether row pinning is enabled at the grid level. */\n public get enabled(): boolean {\n return Boolean(this.host.rowPinning?.enabled);\n }\n\n /** Whether any row is currently pinned. */\n public get hasPinnedRows(): boolean {\n return this.#top.size > 0 || this.#bottom.size > 0;\n }\n\n /** The band `row` is pinned to, or `null` when it is not pinned. */\n public pinnedPosition(row: T): RowPinPosition | null {\n if (this.#top.has(row)) return 'top';\n if (this.#bottom.has(row)) return 'bottom';\n return null;\n }\n\n /** Whether `row` is pinned to either band. */\n public isPinned(row: T): boolean {\n return this.#top.has(row) || this.#bottom.has(row);\n }\n\n /** Pinned rows per band, in the order they were pinned. */\n public get pinnedRows(): { top: T[]; bottom: T[] } {\n return { top: [...this.#top], bottom: [...this.#bottom] };\n }\n\n /**\n * Pins `row` to the given band. Moves it between bands when already pinned\n * elsewhere. No-op (returns `true`) when it is already pinned there.\n *\n * @returns `true` when applied, `false` when disabled or cancelled.\n */\n public pinRow(row: T, position: RowPinPosition): boolean {\n if (!this.enabled) return false;\n if (this.pinnedPosition(row) === position) return true;\n if (!this.#emitPinning(row, position)) return false;\n\n this.#top.delete(row);\n this.#bottom.delete(row);\n (position === 'top' ? this.#top : this.#bottom).add(row);\n this.#commit(row, position, `Row pinned to ${position}`);\n return true;\n }\n\n /**\n * Unpins `row` from whichever band holds it. No-op (returns `true`) when it is\n * not pinned.\n *\n * @returns `true` when applied, `false` when cancelled.\n */\n public unpinRow(row: T): boolean {\n if (!this.isPinned(row)) return true;\n if (!this.#emitPinning(row, null)) return false;\n\n this.#top.delete(row);\n this.#bottom.delete(row);\n this.#commit(row, null, 'Row unpinned');\n return true;\n }\n\n /** Clears every pin in both bands. */\n public clear(): void {\n if (!this.hasPinnedRows) return;\n this.#top.clear();\n this.#bottom.clear();\n this.host.requestUpdate();\n }\n\n /**\n * Restores pinned rows from explicit per-band reference lists (state restore),\n * replacing any current pins. Silent: emits no `rowPinning` / `rowPinned` and\n * preserves the given order within each band.\n */\n public restore(top: ReadonlyArray<T>, bottom: ReadonlyArray<T>): void {\n this.#top = new Set(top);\n this.#bottom = new Set(bottom);\n this.host.requestUpdate();\n }\n\n #emitPinning(row: T, position: RowPinPosition | null): boolean {\n return this.host.emitEvent('rowPinning', {\n detail: { row, position },\n cancelable: true,\n });\n }\n\n #commit(row: T, position: RowPinPosition | null, message: string): void {\n this.host.requestUpdate();\n this.host.announce(message);\n this.host.emitEvent('rowPinned', { detail: { row, position } });\n }\n}\n"]}
1
+ {"version":3,"file":"row-pin.js","sourceRoot":"","sources":["../../src/controllers/row-pin.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport type { GridHost } from '../internal/types.js';\n\n/** Where a row is pinned, or `null` when it scrolls with the body. */\nexport type RowPinPosition = 'top' | 'bottom';\n\n/**\n * Reactive controller backing row pinning.\n *\n * @remarks\n * Pinned rows are lifted out of the scrollable body and rendered in sticky\n * bands above (`'top'`) or below (`'bottom'`) the virtualized rows. State is\n * reference-based: the controller holds a `Set` of row data references per band,\n * so pins survive sort / filter / pagination as long as those preserve row\n * identity (the default in-place pipeline does). Replacing {@link ApexGrid.data}\n * wholesale should be followed by re-pinning or {@link clear}.\n *\n * Mutations go through the cancellable `rowPinning` event and emit a follow-up\n * `rowPinned` event, mirroring selection / expansion.\n */\nexport class RowPinController<T extends object> implements ReactiveController {\n #top: Set<T> = new Set();\n #bottom: Set<T> = new Set();\n\n constructor(protected host: GridHost<T>) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /** Whether row pinning is enabled at the grid level. */\n public get enabled(): boolean {\n return Boolean(this.host.rowPinning?.enabled);\n }\n\n /** Whether any row is currently pinned. */\n public get hasPinnedRows(): boolean {\n return this.#top.size > 0 || this.#bottom.size > 0;\n }\n\n /** The band `row` is pinned to, or `null` when it is not pinned. */\n public pinnedPosition(row: T): RowPinPosition | null {\n if (this.#top.has(row)) return 'top';\n if (this.#bottom.has(row)) return 'bottom';\n return null;\n }\n\n /** Whether `row` is pinned to either band. */\n public isPinned(row: T): boolean {\n return this.#top.has(row) || this.#bottom.has(row);\n }\n\n /** Pinned rows per band, in the order they were pinned. */\n public get pinnedRows(): { top: T[]; bottom: T[] } {\n return { top: [...this.#top], bottom: [...this.#bottom] };\n }\n\n /**\n * Pins `row` to the given band. Moves it between bands when already pinned\n * elsewhere. No-op (returns `true`) when it is already pinned there.\n *\n * @returns `true` when applied, `false` when disabled or cancelled.\n */\n public pinRow(row: T, position: RowPinPosition): boolean {\n if (!this.enabled) return false;\n if (this.pinnedPosition(row) === position) return true;\n if (!this.#emitPinning(row, position)) return false;\n\n this.#top.delete(row);\n this.#bottom.delete(row);\n (position === 'top' ? this.#top : this.#bottom).add(row);\n this.#commit(\n row,\n position,\n this.host.localize(position === 'top' ? 'announce.rowPinnedTop' : 'announce.rowPinnedBottom')\n );\n return true;\n }\n\n /**\n * Unpins `row` from whichever band holds it. No-op (returns `true`) when it is\n * not pinned.\n *\n * @returns `true` when applied, `false` when cancelled.\n */\n public unpinRow(row: T): boolean {\n if (!this.isPinned(row)) return true;\n if (!this.#emitPinning(row, null)) return false;\n\n this.#top.delete(row);\n this.#bottom.delete(row);\n this.#commit(row, null, this.host.localize('announce.rowUnpinned'));\n return true;\n }\n\n /** Clears every pin in both bands. */\n public clear(): void {\n if (!this.hasPinnedRows) return;\n this.#top.clear();\n this.#bottom.clear();\n this.host.requestUpdate();\n }\n\n /**\n * Restores pinned rows from explicit per-band reference lists (state restore),\n * replacing any current pins. Silent: emits no `rowPinning` / `rowPinned` and\n * preserves the given order within each band.\n */\n public restore(top: ReadonlyArray<T>, bottom: ReadonlyArray<T>): void {\n this.#top = new Set(top);\n this.#bottom = new Set(bottom);\n this.host.requestUpdate();\n }\n\n #emitPinning(row: T, position: RowPinPosition | null): boolean {\n return this.host.emitEvent('rowPinning', {\n detail: { row, position },\n cancelable: true,\n });\n }\n\n #commit(row: T, position: RowPinPosition | null, message: string): void {\n this.host.requestUpdate();\n this.host.announce(message);\n this.host.emitEvent('rowPinned', { detail: { row, position } });\n }\n}\n"]}
@@ -101,7 +101,7 @@ export class RowReorderController {
101
101
  this.#order = null;
102
102
  this.host.requestUpdate(PIPELINE);
103
103
  if (announce)
104
- this.host.announce('Manual row order cleared');
104
+ this.host.announce(this.host.localize('announce.manualOrderCleared'));
105
105
  }
106
106
  moveRow(from, to, position = 'before') {
107
107
  const view = this.host.pageItems;
@@ -214,7 +214,7 @@ export class RowReorderController {
214
214
  this.grabbed = row;
215
215
  this.#preGrab = this.#order ? [...this.#order] : null;
216
216
  this.host.requestUpdate();
217
- this.host.announce('Row grabbed. Use arrow keys to move, Enter to drop, Escape to cancel.');
217
+ this.host.announce(this.host.localize('announce.rowGrabbed'));
218
218
  return true;
219
219
  }
220
220
  moveGrabbed(direction) {
@@ -232,7 +232,7 @@ export class RowReorderController {
232
232
  if (!ok)
233
233
  return -1;
234
234
  void this.#flipAfterUpdate(before);
235
- this.host.announce(`Row moved to position ${target + 1}`);
235
+ this.host.announce(this.host.localize('announce.rowMovedTo', { position: target + 1 }));
236
236
  return target;
237
237
  }
238
238
  drop() {
@@ -241,7 +241,7 @@ export class RowReorderController {
241
241
  this.grabbed = null;
242
242
  this.#preGrab = null;
243
243
  this.host.requestUpdate();
244
- this.host.announce('Row dropped');
244
+ this.host.announce(this.host.localize('announce.rowDropped'));
245
245
  }
246
246
  cancelGrab() {
247
247
  if (!this.grabbed)
@@ -250,7 +250,7 @@ export class RowReorderController {
250
250
  this.#order = this.#preGrab;
251
251
  this.#preGrab = null;
252
252
  this.host.requestUpdate(PIPELINE);
253
- this.host.announce('Reorder cancelled');
253
+ this.host.announce(this.host.localize('announce.reorderCancelled'));
254
254
  }
255
255
  #commit(source, target, position, options = {}) {
256
256
  if (!this.enabled || !source || !target || source === target)
@@ -281,7 +281,7 @@ export class RowReorderController {
281
281
  }
282
282
  this.host.requestUpdate(PIPELINE);
283
283
  if (!options.silent)
284
- this.host.announce('Row moved');
284
+ this.host.announce(this.host.localize('announce.rowMoved'));
285
285
  this.host.emitEvent('rowMoved', { detail: { from, to, data: source } });
286
286
  return true;
287
287
  }