@lavalogic/scoria 0.37.44 → 0.37.45

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 (237) hide show
  1. package/README.md +58 -58
  2. package/dist/Components/AccordionGroup.svelte +46 -46
  3. package/dist/Components/AccordionGroupButton.svelte +76 -76
  4. package/dist/Components/Action.svelte +136 -136
  5. package/dist/Components/ActionProps.d.ts +2 -6
  6. package/dist/Components/AlertModal.svelte +195 -183
  7. package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte +702 -702
  8. package/dist/Components/Base/BaseModal.svelte +125 -125
  9. package/dist/Components/Base/ContextWrapper.svelte +72 -72
  10. package/dist/Components/Base/Pagination.svelte +198 -198
  11. package/dist/Components/Base/Snippets.svelte +36 -36
  12. package/dist/Components/BigRadioSet.svelte +38 -38
  13. package/dist/Components/Bubble.svelte +101 -101
  14. package/dist/Components/Button.svelte +209 -209
  15. package/dist/Components/ButtonProps.d.ts +2 -6
  16. package/dist/Components/Checkbox.svelte +86 -86
  17. package/dist/Components/CollapsibleCard.svelte +78 -78
  18. package/dist/Components/Contexts/ContextMenu.svelte +164 -164
  19. package/dist/Components/Contexts/ContextMenuDivider.svelte +15 -15
  20. package/dist/Components/Contexts/ContextMenuOption.svelte +60 -60
  21. package/dist/Components/Contexts/Toast.svelte +83 -83
  22. package/dist/Components/Contexts/ToastContainer.svelte +65 -65
  23. package/dist/Components/Contexts/Tooltip.svelte +37 -37
  24. package/dist/Components/DataMatrixIcon.svelte +112 -112
  25. package/dist/Components/DataMatrixIconProps.d.ts +3 -13
  26. package/dist/Components/DateInput.svelte +148 -148
  27. package/dist/Components/DatePicker.svelte +300 -300
  28. package/dist/Components/DesktopModal.svelte +90 -90
  29. package/dist/Components/Dial.svelte +204 -204
  30. package/dist/Components/DialProps.d.ts +1 -18
  31. package/dist/Components/DimensionInput.svelte +170 -170
  32. package/dist/Components/DragMenu/DragMenuHolder.svelte +119 -119
  33. package/dist/Components/DragMenu/DraggableCard.svelte +77 -77
  34. package/dist/Components/FileCard.svelte +24 -24
  35. package/dist/Components/FileUpload.svelte +236 -236
  36. package/dist/Components/GridInput.svelte +196 -200
  37. package/dist/Components/GridInputSet.svelte +29 -29
  38. package/dist/Components/HorizontalTabGroup.svelte +82 -82
  39. package/dist/Components/HorizontalTabGroupButton.svelte +44 -44
  40. package/dist/Components/Icon.svelte +128 -128
  41. package/dist/Components/IconProps.d.ts +2 -7
  42. package/dist/Components/Icons.js +357 -357
  43. package/dist/Components/LoadingAnimation.svelte +29 -29
  44. package/dist/Components/LoadingModal.svelte +45 -45
  45. package/dist/Components/LoadingOverlay.svelte +25 -25
  46. package/dist/Components/LoadingOverlayProps.d.ts +3 -7
  47. package/dist/Components/Modal.svelte +258 -258
  48. package/dist/Components/ModalProps.d.ts +0 -3
  49. package/dist/Components/MultiSelect.svelte +220 -220
  50. package/dist/Components/MultiSelectCommonProps.d.ts +6 -17
  51. package/dist/Components/Nav/Nav.svelte +99 -99
  52. package/dist/Components/Nav/NavProps.d.ts +4 -11
  53. package/dist/Components/Nav/NavServiceMenuList.svelte +43 -43
  54. package/dist/Components/Nav/NavTab.svelte +62 -62
  55. package/dist/Components/Nav/ServiceMenuItem.svelte +100 -100
  56. package/dist/Components/Nav/ServicesNav.svelte +80 -80
  57. package/dist/Components/NumberInput.svelte +157 -157
  58. package/dist/Components/PageHeading.svelte +25 -25
  59. package/dist/Components/PasswordInput.svelte +88 -88
  60. package/dist/Components/Portal.svelte +36 -36
  61. package/dist/Components/ProgressBubble.svelte +107 -108
  62. package/dist/Components/ProgressBubbleProps.d.ts +3 -3
  63. package/dist/Components/QuerySelect.svelte +366 -366
  64. package/dist/Components/QuerySelectProps.d.ts +4 -14
  65. package/dist/Components/SingleSelect.svelte +250 -250
  66. package/dist/Components/SingleSelectCommonProps.d.ts +3 -19
  67. package/dist/Components/StepButton.svelte +70 -70
  68. package/dist/Components/StepForm.svelte +140 -140
  69. package/dist/Components/Switch.svelte +69 -69
  70. package/dist/Components/Table/ARCHITECTURE.md +298 -0
  71. package/dist/Components/Table/Body/QuickSearchCell.svelte +679 -694
  72. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +51 -51
  73. package/dist/Components/Table/Body/Rows/ColumnList.svelte +59 -59
  74. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +456 -478
  75. package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +79 -79
  76. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +185 -188
  77. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +124 -124
  78. package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +120 -120
  79. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +230 -230
  80. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +105 -105
  81. package/dist/Components/Table/Body/Rows/Columns/SelectColumn.d.ts +12 -9
  82. package/dist/Components/Table/Body/Rows/Columns/SelectTableCell.d.ts +5 -5
  83. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +87 -87
  84. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +126 -126
  85. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +159 -159
  86. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +246 -246
  87. package/dist/Components/Table/Body/Rows/Columns/TableDatePickerProps.d.ts +4 -4
  88. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +216 -216
  89. package/dist/Components/Table/Body/Rows/Columns/TableQueryCell.d.ts +6 -5
  90. package/dist/Components/Table/Body/Rows/Columns/TableQueryColumn.d.ts +7 -6
  91. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +252 -257
  92. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +179 -179
  93. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +221 -229
  94. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte.d.ts +11 -11
  95. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +197 -197
  96. package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +190 -190
  97. package/dist/Components/Table/Body/Rows/TableRow.svelte +290 -290
  98. package/dist/Components/Table/Body/TableBody.svelte +155 -155
  99. package/dist/Components/Table/ColumnDefinitions/ExampleItemColumnDefRepository.svelte.js +2 -3
  100. package/dist/Components/Table/ColumnFactory.svelte.d.ts +9 -11
  101. package/dist/Components/Table/ColumnFactory.svelte.js +101 -118
  102. package/dist/Components/Table/ColumnKindRegistry.d.ts +112 -0
  103. package/dist/Components/Table/ColumnKindRegistry.js +63 -0
  104. package/dist/Components/Table/Headers/HighlightAllHeader.svelte +41 -41
  105. package/dist/Components/Table/Headers/SelectAllHeader.svelte +30 -30
  106. package/dist/Components/Table/Headers/TableHead.svelte +415 -413
  107. package/dist/Components/Table/Misc/ColumnPanel.svelte +94 -94
  108. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +112 -112
  109. package/dist/Components/Table/Misc/FilterInput.svelte +620 -644
  110. package/dist/Components/Table/Misc/FilterInput.svelte.d.ts +12 -12
  111. package/dist/Components/Table/Misc/FilterInputProps.d.ts +2 -2
  112. package/dist/Components/Table/Misc/FilterPanel.svelte +257 -257
  113. package/dist/Components/Table/Misc/FilterPanelModal.svelte +30 -30
  114. package/dist/Components/Table/Misc/SidePanel.svelte +35 -35
  115. package/dist/Components/Table/Misc/TableFooter.svelte +311 -311
  116. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +125 -125
  117. package/dist/Components/Table/Misc/TableSidebar.svelte +157 -157
  118. package/dist/Components/Table/Misc/ToolboxPanel.svelte +100 -100
  119. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +107 -107
  120. package/dist/Components/Table/NestedTable.svelte +130 -133
  121. package/dist/Components/Table/SubApis.svelte.d.ts +1 -5
  122. package/dist/Components/Table/SubApis.svelte.js +33 -32
  123. package/dist/Components/Table/Table.svelte +692 -745
  124. package/dist/Components/Table/TableHelpers.svelte.d.ts +6 -19
  125. package/dist/Components/Table/TableHelpers.svelte.js +6 -14
  126. package/dist/Components/Table/Types/Columns/Column.d.ts +2 -2
  127. package/dist/Components/Table/Types/Columns/DateColumn.d.ts +3 -3
  128. package/dist/Components/Table/Types/Columns/DateTableCell.d.ts +7 -7
  129. package/dist/Components/Table/Types/Columns/Definitions/Accessors/AccessorDef.svelte.d.ts +7 -8
  130. package/dist/Components/Table/Types/Columns/Definitions/Accessors/AccessorDef.svelte.js +4 -5
  131. package/dist/Components/Table/Types/Columns/Definitions/Accessors/CheckboxDef.svelte.d.ts +11 -14
  132. package/dist/Components/Table/Types/Columns/Definitions/Accessors/CheckboxDef.svelte.js +3 -4
  133. package/dist/Components/Table/Types/Columns/Definitions/Accessors/DateInputDef.svelte.d.ts +7 -11
  134. package/dist/Components/Table/Types/Columns/Definitions/Accessors/DateInputDef.svelte.js +3 -3
  135. package/dist/Components/Table/Types/Columns/Definitions/Accessors/NumberInputDef.svelte.d.ts +13 -18
  136. package/dist/Components/Table/Types/Columns/Definitions/Accessors/NumberInputDef.svelte.js +4 -5
  137. package/dist/Components/Table/Types/Columns/Definitions/Accessors/QueryDef.svelte.d.ts +5 -5
  138. package/dist/Components/Table/Types/Columns/Definitions/Accessors/QueryDef.svelte.js +2 -2
  139. package/dist/Components/Table/Types/Columns/Definitions/Accessors/SelectDef.svelte.d.ts +15 -12
  140. package/dist/Components/Table/Types/Columns/Definitions/Accessors/SelectDef.svelte.js +4 -5
  141. package/dist/Components/Table/Types/Columns/Definitions/Accessors/TextInputDef.svelte.d.ts +8 -12
  142. package/dist/Components/Table/Types/Columns/Definitions/Accessors/TextInputDef.svelte.js +3 -3
  143. package/dist/Components/Table/Types/Columns/Definitions/Actions/ActionsDef.svelte.d.ts +6 -6
  144. package/dist/Components/Table/Types/Columns/Definitions/Actions/ActionsDef.svelte.js +3 -3
  145. package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.d.ts +49 -16
  146. package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.js +27 -5
  147. package/dist/Components/Table/Types/Columns/Definitions/Display/AbstractDisplayDef.svelte.d.ts +7 -7
  148. package/dist/Components/Table/Types/Columns/Definitions/Display/AbstractDisplayDef.svelte.js +4 -4
  149. package/dist/Components/Table/Types/Columns/Definitions/Display/BubbleDef.svelte.d.ts +5 -5
  150. package/dist/Components/Table/Types/Columns/Definitions/Display/BubbleDef.svelte.js +2 -2
  151. package/dist/Components/Table/Types/Columns/Definitions/Display/DisplayDef.svelte.d.ts +5 -5
  152. package/dist/Components/Table/Types/Columns/Definitions/Display/DisplayDef.svelte.js +2 -2
  153. package/dist/Components/Table/Types/Columns/Definitions/Display/ProgressBarDef.svelte.d.ts +5 -5
  154. package/dist/Components/Table/Types/Columns/Definitions/Display/ProgressBarDef.svelte.js +2 -2
  155. package/dist/Components/Table/Types/Columns/Definitions/Display/ValidityDef.svelte.d.ts +5 -5
  156. package/dist/Components/Table/Types/Columns/Definitions/Display/ValidityDef.svelte.js +2 -2
  157. package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.d.ts +11 -19
  158. package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.js +4 -8
  159. package/dist/Components/Table/Types/Columns/Definitions/FilterOnly/FilterOnlyDef.svelte.d.ts +6 -7
  160. package/dist/Components/Table/Types/Columns/Definitions/FilterOnly/FilterOnlyDef.svelte.js +3 -4
  161. package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.d.ts +9 -9
  162. package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.js +4 -4
  163. package/dist/Components/Table/Types/Columns/ExpandTableCell.d.ts +5 -5
  164. package/dist/Components/Table/Types/Columns/ProgressBarTableCell.d.ts +6 -6
  165. package/dist/Components/Table/Types/Columns/TableCell.d.ts +7 -6
  166. package/dist/Components/Table/Types/Context/ClipboardState.svelte.d.ts +98 -0
  167. package/dist/Components/Table/Types/Context/ClipboardState.svelte.js +290 -0
  168. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.d.ts +17 -45
  169. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.js +17 -45
  170. package/dist/Components/Table/Types/Context/FilteringState.svelte.d.ts +9 -20
  171. package/dist/Components/Table/Types/Context/FilteringState.svelte.js +9 -20
  172. package/dist/Components/Table/Types/Context/FocusState.svelte.d.ts +23 -42
  173. package/dist/Components/Table/Types/Context/FocusState.svelte.js +32 -59
  174. package/dist/Components/Table/Types/Context/ITable.d.ts +5 -8
  175. package/dist/Components/Table/Types/Context/PreferencesState.svelte.d.ts +29 -56
  176. package/dist/Components/Table/Types/Context/PreferencesState.svelte.js +28 -55
  177. package/dist/Components/Table/Types/Context/QueryValuesState.svelte.d.ts +50 -0
  178. package/dist/Components/Table/Types/Context/QueryValuesState.svelte.js +62 -0
  179. package/dist/Components/Table/Types/Context/SelectionState.svelte.d.ts +17 -28
  180. package/dist/Components/Table/Types/Context/SelectionState.svelte.js +17 -28
  181. package/dist/Components/Table/Types/Context/SortingState.svelte.d.ts +15 -29
  182. package/dist/Components/Table/Types/Context/SortingState.svelte.js +17 -31
  183. package/dist/Components/Table/Types/Context/TableContext.svelte.d.ts +212 -325
  184. package/dist/Components/Table/Types/Context/TableContext.svelte.js +292 -706
  185. package/dist/Components/Table/Types/Context/VirtualisationState.svelte.d.ts +62 -79
  186. package/dist/Components/Table/Types/Context/VirtualisationState.svelte.js +54 -73
  187. package/dist/Components/Table/Types/DataRepository/BuiltInRemoteRepository.svelte.d.ts +16 -27
  188. package/dist/Components/Table/Types/DataRepository/BuiltInRemoteRepository.svelte.js +16 -27
  189. package/dist/Components/Table/Types/DataRepository/IDataRepository.d.ts +12 -15
  190. package/dist/Components/Table/Types/DataRepository/TableDataRepository.svelte.d.ts +15 -30
  191. package/dist/Components/Table/Types/DataRepository/TableDataRepository.svelte.js +16 -28
  192. package/dist/Components/Table/Types/Header.d.ts +4 -4
  193. package/dist/Components/Table/Types/HeaderGroup.d.ts +5 -5
  194. package/dist/Components/Table/Types/Public/index.d.ts +2 -4
  195. package/dist/Components/Table/Types/Public/index.js +2 -4
  196. package/dist/Components/Table/clipboardEscape.d.ts +32 -0
  197. package/dist/Components/Table/clipboardEscape.js +45 -0
  198. package/dist/Components/Table/createTable.svelte.d.ts +8 -18
  199. package/dist/Components/Table/createTable.svelte.js +19 -47
  200. package/dist/Components/Test/TestContextConsumer.svelte +13 -13
  201. package/dist/Components/TextArea.svelte +130 -130
  202. package/dist/Components/TextInput.svelte +176 -176
  203. package/dist/Components/ThreeStateRadio.svelte +131 -131
  204. package/dist/Components/Touch/BoolCard.svelte +45 -45
  205. package/dist/Components/Touch/DateModal.svelte +209 -209
  206. package/dist/Components/Touch/EditCard.svelte +59 -59
  207. package/dist/Components/Touch/InfoCard.svelte +31 -31
  208. package/dist/Components/Touch/InfoTextCard.svelte +28 -28
  209. package/dist/Components/Touch/ModalExplanation.svelte +17 -17
  210. package/dist/Components/Touch/NumberKeyboard.svelte +225 -225
  211. package/dist/Components/Touch/NumberModal.svelte +79 -79
  212. package/dist/Components/Touch/PlusMinusCard.svelte +46 -46
  213. package/dist/Components/Touch/SelectModal.svelte +123 -123
  214. package/dist/Components/Touch/SummaryCard.svelte +93 -93
  215. package/dist/Components/Touch/TextAreaModal.svelte +95 -95
  216. package/dist/Components/Touch/TextModal.svelte +106 -106
  217. package/dist/Components/Touch/TouchCard.svelte +33 -33
  218. package/dist/Components/Touch/TouchModal.svelte +274 -274
  219. package/dist/Components/Touch/UtilityButton.svelte +109 -109
  220. package/dist/Components/VerticalTabGroup.svelte +79 -79
  221. package/dist/Components/VerticalTabGroupButton.svelte +58 -58
  222. package/dist/Contexts/ToastContext.svelte.js +3 -4
  223. package/dist/Helpers/Datamatrix.d.ts +19 -19
  224. package/dist/Helpers/Datamatrix.js +291 -291
  225. package/dist/Helpers/Helpers.svelte.d.ts +2 -4
  226. package/dist/Helpers/Helpers.svelte.js +2 -4
  227. package/dist/Helpers/dropdownPortal.d.ts +26 -41
  228. package/dist/Helpers/dropdownPortal.js +62 -116
  229. package/dist/Types/Examples/ExampleModal.svelte +35 -35
  230. package/dist/index.d.ts +0 -1
  231. package/dist/scss/_basics.scss +12 -12
  232. package/dist/scss/_colours.scss +134 -134
  233. package/dist/scss/_mixins.scss +3348 -3360
  234. package/dist/scss/_motion.scss +57 -57
  235. package/dist/scss/_sizing.scss +85 -85
  236. package/dist/scss/_transitions.scss +8 -8
  237. package/package.json +2 -1
@@ -1,20 +1,15 @@
1
1
  import { browser, dev } from '$app/environment';
2
2
  import { asyncAll, asyncMap, devCatch, getCanvasContext, isKeyOf, } from '../../../../Helpers/Helpers.svelte.js';
3
+ import { COLUMN_KIND_REGISTRY, resolveDefaultFilter, } from '../../ColumnKindRegistry.js';
4
+ import { ClipboardState } from './ClipboardState.svelte.js';
3
5
  import { PreferencesState } from './PreferencesState.svelte.js';
6
+ import { QueryValuesState } from './QueryValuesState.svelte.js';
4
7
  import { SelectionState } from './SelectionState.svelte.js';
5
8
  import { tick, untrack } from 'svelte';
6
9
  import { SvelteMap } from 'svelte/reactivity';
7
10
  import { ColumnType } from '../Columns/ColumnType.js';
8
11
  import { AccessorDef } from '../Columns/Definitions/Accessors/AccessorDef.svelte.js';
9
- import { CheckboxDef } from '../Columns/Definitions/Accessors/CheckboxDef.svelte.js';
10
- import { DateInputDef } from '../Columns/Definitions/Accessors/DateInputDef.svelte.js';
11
12
  import { NumberInputDef } from '../Columns/Definitions/Accessors/NumberInputDef.svelte.js';
12
- import { SelectDef } from '../Columns/Definitions/Accessors/SelectDef.svelte.js';
13
- import { TextInputDef } from '../Columns/Definitions/Accessors/TextInputDef.svelte.js';
14
- import { BubbleDef } from '../Columns/Definitions/Display/BubbleDef.svelte.js';
15
- import { DisplayDef } from '../Columns/Definitions/Display/DisplayDef.svelte.js';
16
- import { ProgressBarDef } from '../Columns/Definitions/Display/ProgressBarDef.svelte.js';
17
- import { RowSelectionDef } from '../Columns/Definitions/RowSelection/RowSelectionDef.svelte.js';
18
13
  import { SortingState } from './SortingState.svelte.js';
19
14
  import { FilteringState } from './FilteringState.svelte.js';
20
15
  import { ColumnLayoutState } from './ColumnLayoutState.svelte.js';
@@ -24,13 +19,6 @@ import { PaginationType } from '../Pagination/PaginationType.js';
24
19
  import { ToolbarPosition } from '../Toolbar/ToolbarPosition.js';
25
20
  const SORTING_ICON_WIDTH = 24;
26
21
  const InternalSymbol = Symbol();
27
- const uncopyableColumns = new Set([
28
- // RowSelect column is a special case and is handled by tableContext directly
29
- ColumnType.Expand,
30
- ColumnType.Actions,
31
- ColumnType.FilterOnly,
32
- ColumnType.Custom,
33
- ]);
34
22
  /**
35
23
  * Default values applied by `TableContext.init` for any `TableInitOptions`
36
24
  * field the consumer omits. Frozen so callers (notably `NestedTable`)
@@ -76,16 +64,12 @@ export class TableContext {
76
64
  */
77
65
  static init(defaultColumnDefs, tableName, dataRepo, options) {
78
66
  const context = new this(InternalSymbol, defaultColumnDefs, tableName, dataRepo, options.allowCopy ?? DEFAULT_TABLE_INIT_OPTIONS.allowCopy, options.enableResize ?? DEFAULT_TABLE_INIT_OPTIONS.enableResize, options.allowColumnReordering ?? DEFAULT_TABLE_INIT_OPTIONS.allowColumnReordering, options.allowQuickFiltering ?? DEFAULT_TABLE_INIT_OPTIONS.allowQuickFiltering, options.showQuickFilterByDefault ?? DEFAULT_TABLE_INIT_OPTIONS.showQuickFilterByDefault, options.allowAdvancedFiltering ?? DEFAULT_TABLE_INIT_OPTIONS.allowAdvancedFiltering, options.toolbar ?? DEFAULT_TABLE_INIT_OPTIONS.toolbar, options.displayFooter ?? DEFAULT_TABLE_INIT_OPTIONS.displayFooter,
79
- // Fallback identity extractor when no `selectionExtractor`
80
- // is supplied. The nested-table call path (see
81
- // `ColumnFactory.expand`) always passes one through, but
82
- // other consumers may construct an options object directly
83
- // without it. Returning the row object itself preserves
84
- // row identity (the cell wrappers use reference equality
85
- // via the keying cache) and avoids the
86
- // `tableContext.selectionExtractor is not a function`
87
- // crash inside `SelectionState.cachedKey` /
88
- // `TableRow.svelte`'s id-resolution effect.
67
+ // Fallback identity extractor when no `selectionExtractor` is
68
+ // supplied (some consumers build an options object directly
69
+ // without one). Returning the row object preserves identity
70
+ // (cell wrappers compare by reference via the keying cache)
71
+ // and avoids the `selectionExtractor is not a function` crash
72
+ // inside `SelectionState.cachedKey` and `TableRow.svelte`.
89
73
  options.selectionExtractor ?? ((row) => row), options.getUserId, options.onEditCell, options.debug ?? DEFAULT_TABLE_INIT_OPTIONS.debug);
90
74
  return context;
91
75
  }
@@ -276,22 +260,17 @@ export class TableContext {
276
260
  if (current !== latest_sortedRowModel) {
277
261
  throw new Error(`stale data`);
278
262
  }
279
- // Do NOT throw when `originalIndex` is missing. The
280
- // `originalRowIndices` effect and this one both
281
- // re-derive asynchronously on `dataRepo.data`
282
- // changes; when a row is added (e.g. the
283
- // auto-pushed empty pod line that fires after a
284
- // user picks an SKU), the sortedRowModel rebuild
285
- // can win the race and run against a still-stale
286
- // `ori` map that does not yet contain the new
287
- // row's key. The previous `throw` rejected the
288
- // `Promise.all` for the entire rebuild, which
289
- // left `_sortedRowModel` pinned to the pre-edit
290
- // snapshot and stopped editable cells from
291
- // reflecting their post-edit state - the visible
292
- // symptom on Receipt's SKU cell. Falling back to
293
- // `unsortedIndex = index` is a fine approximation
294
- // for the missing entry; the next reactive pass
263
+ // Do NOT throw when `originalIndex` is missing.
264
+ // `originalRowIndices` and the sortedRowModel
265
+ // rebuild re-derive asynchronously on
266
+ // `dataRepo.data` changes; on a row addition the
267
+ // sortedRowModel can win the race and run against
268
+ // a still-stale `ori` map. Throwing would reject
269
+ // the whole `Promise.all` and pin
270
+ // `_sortedRowModel` to the pre-edit snapshot, so
271
+ // editable cells would not reflect post-edit
272
+ // state. Falling back to `unsortedIndex = index`
273
+ // is a fine approximation; the next reactive pass
295
274
  // (after `originalRowIndices` catches up) refines
296
275
  // it.
297
276
  return this.createRow(item, index, unsortedIndex);
@@ -436,10 +415,8 @@ export class TableContext {
436
415
  this._paginationRepo = v;
437
416
  }
438
417
  /**
439
- * Delegating accessor preserved for backwards compatibility; reads /
440
- * writes through to `_filtering.showQuickFiltering`. The sub-context
441
- * owns the `allowQuickFiltering` gate so the setter logic stays
442
- * identical to the pre-extraction implementation.
418
+ * Delegates to `_filtering.showQuickFiltering`. The sub-context owns
419
+ * the `allowQuickFiltering` gate inside the setter.
443
420
  */
444
421
  get showQuickFiltering() {
445
422
  return this._filtering.showQuickFiltering;
@@ -455,19 +432,15 @@ export class TableContext {
455
432
  this._dataRepo = v;
456
433
  }
457
434
  /**
458
- * Delegating getter preserved for backwards compatibility. Reads
459
- * through to `_filtering.remoteFilters`; the parent uses
460
- * `_filtering.setRemoteFilters` during column-def setup to write the
461
- * caller-supplied value.
435
+ * Delegates to `_filtering.remoteFilters`. The write path is
436
+ * `_filtering.setRemoteFilters`, called during column-def setup.
462
437
  */
463
438
  get remoteFilters() {
464
439
  return this._filtering.remoteFilters;
465
440
  }
466
441
  /**
467
- * Delegating accessor preserved for backwards compatibility; reads /
468
- * writes through to `_filtering.showSelectedOnly`. The sub-context
469
- * preserves the idempotency short-circuit (re-assigning the same
470
- * value is a no-op).
442
+ * Delegates to `_filtering.showSelectedOnly`. Re-assigning the same
443
+ * value is a no-op (idempotency short-circuit in the sub-context).
471
444
  */
472
445
  get showSelectedOnly() {
473
446
  return this._filtering.showSelectedOnly;
@@ -476,11 +449,8 @@ export class TableContext {
476
449
  this._filtering.showSelectedOnly = v;
477
450
  }
478
451
  /**
479
- * Delegating getters that relay the per-type `FilterFn` from
480
- * `_filtering`. The sub-context's `$derived` chain reads through
481
- * `paginationRepo` via the thunk so the source of truth stays
482
- * with the data repository; consumers that bind these getters
483
- * (notably `setupDefaultColumnDefs`) need no migration.
452
+ * Per-type `FilterFn` getters. Delegate to `_filtering`, which reads
453
+ * the source-of-truth from `paginationRepo` via thunk.
484
454
  */
485
455
  get dateFilter() {
486
456
  return this._filtering.dateFilter;
@@ -511,12 +481,7 @@ export class TableContext {
511
481
  set dragTarget(v) {
512
482
  this._dragTarget = v;
513
483
  }
514
- /**
515
- * Delegating accessor preserved for backwards compatibility. The
516
- * actual `$state` slot lives on `_columnLayout`; this getter / setter
517
- * pair forwards reads and writes so existing consumers need no
518
- * migration. See `_columnLayout` below for the sub-context.
519
- */
484
+ /** Delegates to `_columnLayout.columnDefs`. */
520
485
  get columnDefs() {
521
486
  return this._columnLayout.columnDefs;
522
487
  }
@@ -524,21 +489,15 @@ export class TableContext {
524
489
  this._columnLayout.columnDefs = v;
525
490
  }
526
491
  /**
527
- * Delegating accessor preserved for backwards compatibility. The
528
- * actual `$state` slot lives on `_preferences._columnPresets`
529
- * (Phase 6.4). The setter is private because the consumer-facing
530
- * write path goes through `saveColumnPreset` (which also persists
531
- * the list to localStorage); internal call sites that need to
492
+ * Delegates to `_preferences._columnPresets`. The setter is private
493
+ * because the public write path is `saveColumnPreset` (which also
494
+ * persists to localStorage); internal call sites that need to
532
495
  * mutate the list directly call `_preferences.setPresets(...)`.
533
496
  */
534
497
  get columnPresets() {
535
498
  return this._preferences.columnPresets;
536
499
  }
537
- /**
538
- * Delegating accessor preserved for backwards compatibility. The
539
- * actual `$state` slot lives on `_preferences._selectedPreset`
540
- * (Phase 6.4). Reads / writes through to the sub-context.
541
- */
500
+ /** Delegates to `_preferences._selectedPreset`. */
542
501
  get selectedPreset() {
543
502
  return this._preferences.selectedPreset;
544
503
  }
@@ -546,33 +505,22 @@ export class TableContext {
546
505
  this._preferences.selectedPreset = v;
547
506
  }
548
507
  /**
549
- * Delegating accessor preserved for backwards compatibility. The
550
- * `$state` slot lives on `_focus._focusedRowIndices` (Phase 6.6).
551
- * Populated by the parent's focus-derived `$effect` through
508
+ * Delegates to `_focus._focusedRowIndices`. Populated by the
509
+ * focus-derived `$effect` in the constructor through
552
510
  * `_focus.setFocusedDerived(rows, cols)`.
553
511
  */
554
512
  get focusedRowIndices() {
555
513
  return this._focus.focusedRowIndices;
556
514
  }
557
- /**
558
- * Delegating accessor preserved for backwards compatibility. The
559
- * actual `$state` slot lives on `_selection._highlightedItems`
560
- * (Phase 6.5).
561
- */
515
+ /** Delegates to `_selection._highlightedItems`. */
562
516
  get highlightedItems() {
563
517
  return this._selection.highlightedItems;
564
518
  }
565
- /**
566
- * Delegating accessor preserved for backwards compatibility. The
567
- * `$state` slot lives on `_focus._focusedColumnIds` (Phase 6.6).
568
- */
519
+ /** Delegates to `_focus._focusedColumnIds`. */
569
520
  get focusedColumnIds() {
570
521
  return this._focus.focusedColumnIds;
571
522
  }
572
- /**
573
- * Delegating accessor preserved for backwards compatibility. The
574
- * `$state` slot lives on `_focus._isMousingDown` (Phase 6.6).
575
- */
523
+ /** Delegates to `_focus._isMousingDown`. */
576
524
  get isMousingDown() {
577
525
  return this._focus.isMousingDown;
578
526
  }
@@ -580,30 +528,22 @@ export class TableContext {
580
528
  this._focus.isMousingDown = v;
581
529
  }
582
530
  /**
583
- * Delegating accessor preserved for backwards compatibility. The
584
- * `$state` slot lives on `_focus._focusStart` (Phase 6.6). The
585
- * setter remains read-only at this surface; the write path is
586
- * `moveFocus` / `unfocus` / `tabForward` / `tabBackward` /
587
- * `endFocus` / `setFocusEnd` / `startFocus` (all delegated below).
531
+ * Delegates to `_focus._focusStart`. This surface is read-only; the
532
+ * write path is `moveFocus` / `unfocus` / `tabForward` /
533
+ * `tabBackward` / `endFocus` / `setFocusEnd` / `startFocus` (all
534
+ * delegated below).
588
535
  */
589
536
  get focusStart() {
590
537
  return this._focus.focusStart;
591
538
  }
592
- /**
593
- * Delegating accessor preserved for backwards compatibility. The
594
- * `$state` slot lives on `_focus._focusStartIsBeingEdited`
595
- * (Phase 6.6).
596
- */
539
+ /** Delegates to `_focus._focusStartIsBeingEdited`. */
597
540
  get focusStartIsBeingEdited() {
598
541
  return this._focus.focusStartIsBeingEdited;
599
542
  }
600
543
  set focusStartIsBeingEdited(v) {
601
544
  this._focus.focusStartIsBeingEdited = v;
602
545
  }
603
- /**
604
- * Delegating accessor preserved for backwards compatibility. The
605
- * `$state` slot lives on `_focus._focusEnd` (Phase 6.6).
606
- */
546
+ /** Delegates to `_focus._focusEnd`. */
607
547
  get focusEnd() {
608
548
  return this._focus.focusEnd;
609
549
  }
@@ -638,23 +578,48 @@ export class TableContext {
638
578
  set alternateRowColours(v) {
639
579
  this._alternateRowColours = v;
640
580
  }
641
- _copyWithHeaders = $state(true);
581
+ /**
582
+ * Clipboard sub-context owning the two user-tunable preference flags
583
+ * (`copyWithHeaders`, `commaSeparatedCopy`) plus the
584
+ * `copyDataToClipboard` / `copyHighlightedRowsToClipboard` writers and
585
+ * their cell-serialisation internals. Dependency thunks below close
586
+ * over the parent reads it needs (column defs, sorted data, focus
587
+ * coordinates, etc.); the parent never sees the clipboard internals.
588
+ */
589
+ _clipboard = new ClipboardState({
590
+ getColumnDefs: () => this.columnDefs,
591
+ getColumnVisibility: () => this.columnVisibility,
592
+ getHighlightedItems: () => this.highlightedItems,
593
+ getSelectedItems: () => this.selectedItems,
594
+ getSortedData: () => this.sortedData,
595
+ getCurrentPageData: () => this.currentPageData,
596
+ getPaginationRowModel: () => this.paginationRowModel,
597
+ getVisualColumns: () => this._visualColumns,
598
+ getFocusStart: () => this.focusStart,
599
+ getFocusEnd: () => this.focusEnd,
600
+ cachedKey: (item) => this._cachedKey(item),
601
+ columnVisualIndex: (id) => this.columnVisualIndex(id),
602
+ getAllowCopy: () => this.allowCopy,
603
+ persistSettings: () => {
604
+ this._updateLocalStorageSettings();
605
+ },
606
+ });
607
+ /** Delegates to `_clipboard.copyWithHeaders`. */
642
608
  get copyWithHeaders() {
643
- return this._copyWithHeaders;
609
+ return this._clipboard.copyWithHeaders;
644
610
  }
645
611
  set copyWithHeaders(v) {
646
- this._copyWithHeaders = v;
612
+ this._clipboard.copyWithHeaders = v;
647
613
  }
648
- _commaSeparatedCopy = $state(false);
614
+ /** Delegates to `_clipboard.commaSeparatedCopy`. */
649
615
  get commaSeparatedCopy() {
650
- return this._commaSeparatedCopy;
616
+ return this._clipboard.commaSeparatedCopy;
651
617
  }
652
618
  set commaSeparatedCopy(v) {
653
- this._commaSeparatedCopy = v;
619
+ this._clipboard.commaSeparatedCopy = v;
654
620
  }
655
621
  /**
656
- * Delegating accessor preserved for backwards compatibility. Reads
657
- * through to `_columnLayout.columnSizing`. Per-key mutations (e.g.
622
+ * Delegates to `_columnLayout.columnSizing`. Per-key mutations (e.g.
658
623
  * `this.columnSizing[id] = w` in `autoResize`) work because the
659
624
  * underlying `$state` slot is a Svelte proxy.
660
625
  */
@@ -665,8 +630,7 @@ export class TableContext {
665
630
  this._columnLayout.columnSizing = v;
666
631
  }
667
632
  /**
668
- * Delegating accessor preserved for backwards compatibility. Reads
669
- * through to `_columnLayout.columnPinning`. The `setColumnPinning`
633
+ * Delegates to `_columnLayout.columnPinning`. The `setColumnPinning`
670
634
  * updater below honours the `allowColumnReordering` master gate; the
671
635
  * direct setter does not, by design (used for preset switches and
672
636
  * the init flow where the gate has already been checked).
@@ -678,8 +642,7 @@ export class TableContext {
678
642
  this._columnLayout.columnPinning = v;
679
643
  }
680
644
  /**
681
- * Delegating accessor preserved for backwards compatibility. Reads
682
- * through to `_columnLayout.columnVisibility`. Tri-state semantics
645
+ * Delegates to `_columnLayout.columnVisibility`. Tri-state semantics
683
646
  * (`true` / `false` / `undefined`) are preserved.
684
647
  */
685
648
  get columnVisibility() {
@@ -693,13 +656,12 @@ export class TableContext {
693
656
  * `col.display({ modal })` modal is currently open, or `null` when no
694
657
  * modal is open.
695
658
  *
696
- * Keyed on the **row object reference**, not on visible index, so the
697
- * open-modal state is stable across pagination, sort, and filter
698
- * changes - the cell renderer matches via `cell.row.original ===
699
- * openModalCell.row`. Visible-index-based keying (the old
700
- * `CellCoordinatesWithoutColumn` shape) silently re-targeted the modal
701
- * to whichever row happened to occupy the same index after a page
702
- * change.
659
+ * Keyed on the **row object reference** (NOT on visible index) so
660
+ * the open-modal state is stable across pagination, sort, and filter
661
+ * changes; the cell renderer matches via
662
+ * `cell.row.original === openModalCell.row`. Visible-index keying
663
+ * would silently re-target the modal to whichever row happened to
664
+ * occupy the same index after a page change.
703
665
  */
704
666
  _openModalCell = $state(null);
705
667
  get openModalCell() {
@@ -708,10 +670,9 @@ export class TableContext {
708
670
  set openModalCell(v) {
709
671
  this._openModalCell = v;
710
672
  }
711
- // Per-table identifier of the ActionsCell that currently has its popup open.
712
- // Lives on the context rather than at module scope so multiple tables on the
713
- // same page do not share open-popup state (security: cross-table state
714
- // poisoning).
673
+ // Per-table identifier of the ActionsCell that currently has its popup
674
+ // open. Lives on the context (NOT at module scope) so multiple tables
675
+ // on the same page do not share open-popup state.
715
676
  _shownActionsPopup = $state();
716
677
  get shownActionsPopup() {
717
678
  return this._shownActionsPopup;
@@ -720,36 +681,29 @@ export class TableContext {
720
681
  this._shownActionsPopup = v;
721
682
  }
722
683
  /**
723
- * Delegating getter preserved for backwards compatibility. The
724
- * actual `$derived` chain (and the async-result cache) live on
725
- * `_selection.allSelectedInCurrentPage` (Phase 6.5).
684
+ * Delegates to `_selection.allSelectedInCurrentPage` (the `$derived`
685
+ * chain and the async-result cache live there).
726
686
  */
727
687
  get allSelectedInCurrentPage() {
728
688
  return this._selection.allSelectedInCurrentPage;
729
689
  }
730
690
  /**
731
- * Delegating getter preserved for backwards compatibility. Reads
732
- * through to `_selection.allHighlighted`; the value is published by
733
- * the `_allHighlightedEffect` below which lives on this class
734
- * because it reads `paginationRepo.pageStartIndex` / `pageEndIndexExclusive`
735
- * directly.
691
+ * Delegates to `_selection.allHighlighted`. The value is published
692
+ * by the `_allHighlightedEffect` below (which lives on this class
693
+ * because it reads `paginationRepo.pageStartIndex` /
694
+ * `pageEndIndexExclusive` directly).
736
695
  */
737
696
  get allHighlighted() {
738
697
  return this._selection.allHighlighted;
739
698
  }
740
- /**
741
- * Delegating getter preserved for backwards compatibility. The
742
- * `$derived` lives on `_selection.hasHighlightedItems` (Phase 6.5).
743
- */
699
+ /** Delegates to `_selection.hasHighlightedItems`. */
744
700
  get hasHighlightedItems() {
745
701
  return this._selection.hasHighlightedItems;
746
702
  }
747
703
  /**
748
- * Delegating accessors preserved for backwards compatibility. The
749
- * `$state` slots live on `_selection` (Phase 6.5). `selectedItems`
750
- * is read-only at this surface; the consumer-facing writers are
751
- * `toggleSelected` / `setSelected` / `resetSelectedItems` /
752
- * `setSelectionMap` below.
704
+ * Delegates to `_selection.selectedItems`. Read-only at this surface;
705
+ * the public writers are `toggleSelected` / `setSelected` /
706
+ * `resetSelectedItems` / `setSelectionMap` below.
753
707
  */
754
708
  get selectedItems() {
755
709
  return this._selection.selectedItems;
@@ -768,24 +722,20 @@ export class TableContext {
768
722
  this._hasInvalidSelectedItems = v;
769
723
  }
770
724
  /**
771
- * Delegating accessor preserved for backwards compatibility. The
772
- * id->def lookup map is owned by `_columnLayout.columnDefsById`
773
- * (Phase 6.3); previously this was a `$derived` private field on
774
- * `TableContext`. Made `public get` (instead of the previous
775
- * `private` field) so the dependent sub-contexts (`SortingState`)
776
- * and helpers (`generateTableOptions`) can read it through the
777
- * legacy surface without piercing the sub-context boundary.
725
+ * Delegates to `_columnLayout.columnDefsById`. `public get` (rather
726
+ * than a private field) so dependent sub-contexts (`SortingState`)
727
+ * and helpers (`generateTableOptions`) can read it without piercing
728
+ * the sub-context boundary.
778
729
  */
779
730
  get columnDefsById() {
780
731
  return this._columnLayout.columnDefsById;
781
732
  }
782
733
  /**
783
- * Phase 6.3 sub-context that owns the column-layout state:
784
- * `columnDefs`, `columnSizing`, `columnPinning`, `columnVisibility`,
785
- * the `columnDefsById` reverse-lookup map, the `visualColumns`
786
- * cached array (and the paired `visualColumnIndexById` O(1) lookup),
787
- * the gated `setColumnPinning` updater, the `updateDefsBasedOnPinning`
788
- * helper, and the `setNewPinningState` helper.
734
+ * Column-layout sub-context: `columnDefs`, `columnSizing`,
735
+ * `columnPinning`, `columnVisibility`, the `columnDefsById`
736
+ * reverse-lookup map, the `visualColumns` cached array (and paired
737
+ * `visualColumnIndexById` O(1) lookup), the gated `setColumnPinning`
738
+ * updater, `updateDefsBasedOnPinning`, and `setNewPinningState`.
789
739
  *
790
740
  * Dependencies are passed as thunks so the sub-context never holds
791
741
  * a reference to the parent. The `allowColumnReordering` thunk
@@ -797,16 +747,15 @@ export class TableContext {
797
747
  getDebug: () => this.debug,
798
748
  });
799
749
  /**
800
- * Phase 6.2 sub-context that owns the filtering-related state: the
801
- * per-row filter result (`filteredIndices`), the `filteredData`
802
- * derived chain, the user-facing toggles (`showQuickFiltering`,
803
- * `showSelectedOnly`), the per-type `FilterFn` relays, the
804
- * remote-filter slot, and the `updateFilters` `OnChangeFn` slot.
750
+ * Filtering sub-context: per-row filter result
751
+ * (`filteredIndices`), the `filteredData` derived chain, user-facing
752
+ * toggles (`showQuickFiltering`, `showSelectedOnly`), the per-type
753
+ * `FilterFn` relays, the remote-filter slot, and the `updateFilters`
754
+ * `OnChangeFn` slot.
805
755
  *
806
- * Dependencies are passed as thunks so the sub-context never holds
807
- * a reference to the parent; the population effect for
808
- * `_filteredIndices` stays in `TableContext` because it calls
809
- * `this.createRow` (parent concern) - the effect publishes its
756
+ * Dependencies are thunks so the sub-context never holds a parent
757
+ * reference. The population effect for `_filteredIndices` lives on
758
+ * `TableContext` because it calls `this.createRow`; it publishes its
810
759
  * result via `this._filtering.setFilteredIndices`.
811
760
  */
812
761
  _filtering = new FilteringState({
@@ -822,33 +771,40 @@ export class TableContext {
822
771
  getDebug: () => this.debug,
823
772
  });
824
773
  /**
825
- * Delegating getter preserved for backwards compatibility. Reads
826
- * through to `_filtering.filteredIndices`, which is populated by
827
- * the per-row filter `$effect` in this class (the effect calls
828
- * `this.createRow` and must stay where `createRow` lives).
774
+ * Delegates to `_filtering.filteredIndices`. Populated by the per-row
775
+ * filter `$effect` in this class (which calls `this.createRow` and
776
+ * must stay where `createRow` lives).
829
777
  */
830
778
  get filteredIndices() {
831
779
  return this._filtering.filteredIndices;
832
780
  }
833
781
  /**
834
- * Delegating getter preserved for backwards compatibility. Reads
835
- * through to `_filtering.filteredData`, which owns the local /
782
+ * Delegates to `_filtering.filteredData`, which owns the local /
836
783
  * remote / show-selected-only branching.
837
784
  */
838
785
  get filteredData() {
839
786
  return this._filtering.filteredData;
840
787
  }
841
788
  /**
842
- * Phase 6.1 sub-context that owns the sort-related derived chain and
843
- * the per-`ColumnDef` comparator dispatch. The dependency thunks
844
- * close over `this.columnDefsById` / `this.filteredData` /
845
- * `this.dataRepo` / `this.paginationRepo` / `this.debug`, so the
846
- * sub-context never holds a direct reference to the parent and the
847
- * reactivity graph remains threaded through `$derived` only.
789
+ * Typeahead-echo cache for QuerySelect-backed filter inputs. Keyed by
790
+ * `ColumnDef` so reads and writes cannot diverge on the key (a recurring
791
+ * regression from the legacy `Map<string, unknown>` shape). See
792
+ * `QueryValuesState` for the rationale.
793
+ */
794
+ _queryValues = new QueryValuesState();
795
+ /** Public alias for `_queryValues`, used by filter input components. */
796
+ get queryValues() {
797
+ return this._queryValues;
798
+ }
799
+ /**
800
+ * Sort sub-context: owns the sort-related derived chain and the
801
+ * per-`ColumnDef` comparator dispatch. Dependency thunks close over
802
+ * `this.columnDefsById` / `this.filteredData` / `this.dataRepo` /
803
+ * `this.paginationRepo` / `this.debug`, so the sub-context never
804
+ * holds a parent reference and reactivity stays in `$derived`.
848
805
  *
849
- * Exposed as a public field so future consumers can read sorting
850
- * state directly; the legacy `sortedData` getter below preserves the
851
- * pre-decomposition surface for backwards compatibility.
806
+ * Public field so consumers can read sorting state directly; the
807
+ * `sortedData` getter below preserves the legacy surface.
852
808
  */
853
809
  _sorting = new SortingState({
854
810
  getColumnDefsById: () => this.columnDefsById,
@@ -859,28 +815,27 @@ export class TableContext {
859
815
  getDebug: () => this.debug,
860
816
  });
861
817
  /**
862
- * Phase 6.4 sub-context that owns every persistence / preferences
863
- * concern previously held directly on `TableContext`:
818
+ * Persistence / preferences sub-context:
864
819
  *
865
- * - the saved column-presets list and currently-selected preset
820
+ * - saved column-presets list and currently-selected preset
866
821
  * (`columnPresets`, `selectedPreset`);
867
- * - the `userIdSegment` `$derived` (used to namespace every
822
+ * - `userIdSegment` `$derived` (namespaces every
868
823
  * `scoria-table:<user>:<tableName>:<suffix>` storage key);
869
- * - the `saveColumnPreset` / `setSelectedPreset` writers (which
870
- * apply the layout AND persist);
871
- * - the `loadPresets` / `loadSelectedPresetName` /
872
- * `loadSavedSettings` boot-time hydration helpers;
873
- * - the `persistSettings` writer used by the in-app settings
874
- * toggles (`setIsCompact`, `setAlternateRowColours`, etc.);
875
- * - the schema-validation typeguards (exported alongside the
876
- * sub-context for unit testing).
824
+ * - `saveColumnPreset` / `setSelectedPreset` writers (apply layout
825
+ * AND persist);
826
+ * - `loadPresets` / `loadSelectedPresetName` / `loadSavedSettings`
827
+ * boot-time hydration helpers;
828
+ * - `persistSettings` writer used by the in-app settings toggles
829
+ * (`setIsCompact`, `setAlternateRowColours`, etc.);
830
+ * - schema-validation typeguards (exported alongside the sub-context
831
+ * for unit testing).
877
832
  *
878
833
  * The settings fields themselves (`isCompact`, `singleClickEditing`,
879
834
  * `alternateRowColours`, etc.) stay on `TableContext` because each
880
- * one drives reactive UI; only the localStorage round-trip moved.
881
- * `TableContext` retains delegating getters/setters/methods for
882
- * every legacy surface (`columnPresets`, `selectedPreset`,
883
- * `saveColumnPreset`, `setSelectedPreset`, `setIsCompact`, etc.).
835
+ * drives reactive UI; only the localStorage round-trip lives here.
836
+ * Delegating getters / setters / methods on `TableContext` cover the
837
+ * full surface (`columnPresets`, `selectedPreset`, `saveColumnPreset`,
838
+ * `setSelectedPreset`, `setIsCompact`, etc.).
884
839
  */
885
840
  _preferences = new PreferencesState({
886
841
  getTableName: () => this.tableName,
@@ -904,31 +859,27 @@ export class TableContext {
904
859
  getDebug: () => this.debug,
905
860
  });
906
861
  /**
907
- * Phase 6.5 sub-context that owns every selection + highlighting +
908
- * key-cache concern previously held directly on `TableContext`:
862
+ * Selection + highlighting + key-cache sub-context:
909
863
  *
910
864
  * - `selectedItems` / `invalidValues` / `highlightedItems`
911
865
  * (`SvelteMap` slots, keyed by `selectionExtractor(item)`);
912
- * - the `WeakMap<T, RowIdType>` key-cache that memoises the
913
- * extractor result for each row reference;
914
- * - the pending-flush buffer and its debounced microtask
866
+ * - `WeakMap<T, RowIdType>` key-cache memoising the extractor result
867
+ * per row reference;
868
+ * - pending-flush buffer and its debounced microtask
915
869
  * (`_pendingSelectionChanges` -> `tick().then(flush)`);
916
- * - the `allSelectedInCurrentPage` `$derived` (with the async-
917
- * result cache for promise-returning `selectionExtractor` /
870
+ * - `allSelectedInCurrentPage` `$derived` (with the async-result
871
+ * cache for promise-returning `selectionExtractor` /
918
872
  * `RowSelectionDef.isSelectable`);
919
- * - the `selectAllInCurrentPage` / `updateSelectedItems` /
873
+ * - `selectAllInCurrentPage` / `updateSelectedItems` /
920
874
  * `onHighlightAll` flows;
921
- * - the `handleItemMutation` split that powers the parent's
922
- * `onEditCell` - the sub-context owns the key-cache delete +
923
- * selection rebind; the user-supplied callback stays on this
924
- * class.
875
+ * - `handleItemMutation` split that powers the parent's `onEditCell`:
876
+ * the sub-context owns the key-cache delete + selection rebind; the
877
+ * user-supplied callback stays on this class.
925
878
  *
926
- * Dependencies are passed as thunks so the sub-context never holds
927
- * a direct reference to `TableContext`. The two `$effect`s that
928
- * historically lived on this class continue to live here (they
929
- * read `paginationRepo` directly) but publish their results
930
- * through `_selection.setAllHighlighted(...)` and
931
- * `_selection.updateSelectedItems()`.
879
+ * Dependencies are thunks so the sub-context never holds a parent
880
+ * reference. The two `$effect`s that read `paginationRepo` directly
881
+ * stay on `TableContext` and publish via `_selection.setAllHighlighted`
882
+ * and `_selection.updateSelectedItems`.
932
883
  */
933
884
  _selection = new SelectionState({
934
885
  getSortedData: () => this.sortedData,
@@ -941,8 +892,7 @@ export class TableContext {
941
892
  getDebug: () => this.debug,
942
893
  });
943
894
  /**
944
- * Phase 6.6 sub-context that owns every focus + keyboard-navigation
945
- * concern previously held directly on `TableContext`:
895
+ * Focus + keyboard-navigation sub-context:
946
896
  *
947
897
  * - `focusStart` / `focusEnd` / `focusStartIsBeingEdited` /
948
898
  * `isMousingDown` (the `$state` slots that define the focused
@@ -972,9 +922,8 @@ export class TableContext {
972
922
  * because it reads `paginationRowModel` + `_visualColumns` directly;
973
923
  * it calls `_focus.setFocusedDerived(rows, cols)` to publish.
974
924
  *
975
- * Group 2 Item C (per-cell `TableFocusDetails` reuse cache) is
976
- * deferred for Phase 6.6 - see the class-level JSDoc on
977
- * `FocusState` for the deferral rationale.
925
+ * Per-cell `TableFocusDetails` reuse cache is deferred; see the
926
+ * class-level JSDoc on `FocusState` for the rationale.
978
927
  */
979
928
  _focus = new FocusState({
980
929
  getAllowCopy: () => this.allowCopy,
@@ -996,26 +945,24 @@ export class TableContext {
996
945
  this.tabBackward();
997
946
  },
998
947
  getDebug: () => this.debug,
999
- // Phase 7.1 Item C - `getFocusDetailsFor` needs the parent
1000
- // `TableContext` to construct `TableFocusDetails` on cache-miss.
1001
- // Pass via a thunk so the sub-context never closes over `this`
1002
- // at construction time.
948
+ // `getFocusDetailsFor` needs the parent `TableContext` to
949
+ // construct `TableFocusDetails` on cache miss. Pass via a thunk
950
+ // so the sub-context never closes over `this` at construction.
1003
951
  getTableContextRef: () => this,
1004
952
  });
1005
953
  /**
1006
- * Phase 7.4 row-virtualisation sub-context. Owns the visible-row
1007
- * window plus the top / bottom spacer-height derived chain that
1008
- * `TableBody.svelte` renders. Threshold defaults to 100 rows
1009
- * (below that the legacy code path renders every row directly);
1010
- * overscan defaults to 10 rows above and below the viewport so a
1011
- * fast flick of the wheel does not surface a blank band before
1012
- * the next paint.
954
+ * Row-virtualisation sub-context. Owns the visible-row window plus
955
+ * the top / bottom spacer-height derived chain that
956
+ * `TableBody.svelte` renders. Below the 100-row threshold every row
957
+ * is rendered directly; above it, overscan rows above and below the
958
+ * viewport stay rendered so a fast wheel flick does not surface a
959
+ * blank band.
1013
960
  *
1014
- * Dependencies are thunks - the sub-context reads
1015
- * `paginationRowModel.rows` through `getRows`, the max-side
1016
- * observed height from the `_columnLayout` row-height pool
1017
- * through `getRowHeight`, and the per-table-density estimate
1018
- * through `getEstimatedRowHeight`.
961
+ * Dependencies are thunks: `getRows` reads
962
+ * `paginationRowModel.rows`, `getRowHeight` returns the max-side
963
+ * observed height from the row-height pool (currently a no-op
964
+ * pending re-introduction), and `getEstimatedRowHeight` returns the
965
+ * per-table-density estimate.
1019
966
  */
1020
967
  /**
1021
968
  * Rendered widths of header cells, keyed by columnId. TableHead
@@ -1043,12 +990,8 @@ export class TableContext {
1043
990
  getEstimatedRowHeight: () => (this.isCompact ? 28 : 48),
1044
991
  });
1045
992
  /**
1046
- * Delegating getter preserved for backwards compatibility. Reads
1047
- * through to `_sorting.sortedData`, which owns the comparator-hoist
1048
- * optimisation and the `AbortController` short-circuit. Consumers
1049
- * inside `src/lib` already point at `tableContext.sortedData`; the
1050
- * delegation will be removed once Phase 6 v-next migrates every
1051
- * reader to `tableContext._sorting.sortedData` directly.
993
+ * Delegates to `_sorting.sortedData`, which owns the comparator-hoist
994
+ * optimisation and the `AbortController` short-circuit.
1052
995
  */
1053
996
  get sortedData() {
1054
997
  return this._sorting.sortedData;
@@ -1233,26 +1176,23 @@ export class TableContext {
1233
1176
  // #endregion
1234
1177
  // #region public methods
1235
1178
  /**
1236
- * Delegating wrapper preserved for backwards compatibility. The
1237
- * key-extraction cache lives on `_selection` (Phase 6.5); this
1238
- * method forwards to `_selection.cachedKey(item)` so legacy call
1239
- * sites in `getHighlightedRowData` / `getFocusElementData` /
1240
- * `originalRowIndices` / `sortedRowModel` / `onHighlightAll` see
1241
- * no behavioural change.
1179
+ * Forwards to `_selection.cachedKey(item)` (the key-extraction cache
1180
+ * lives there). Used by `getHighlightedRowData` /
1181
+ * `getFocusElementData` / `originalRowIndices` / `sortedRowModel` /
1182
+ * `onHighlightAll`.
1242
1183
  */
1243
1184
  _cachedKey(item) {
1244
1185
  return this._selection.cachedKey(item);
1245
1186
  }
1246
- /** Delegating accessor preserved for backwards compatibility. The
1247
- * implementation lives on `_selection.selectionContains` (Phase 6.5). */
1187
+ /** Delegates to `_selection.selectionContains`. */
1248
1188
  selectionContains = (item) => {
1249
1189
  return this._selection.selectionContains(item);
1250
1190
  };
1251
- /** Delegating accessor preserved for backwards compatibility. */
1191
+ /** Delegates to `_selection.addInvalidValue`. */
1252
1192
  addInvalidValue = (item) => {
1253
1193
  return this._selection.addInvalidValue(item);
1254
1194
  };
1255
- /** Delegating accessor preserved for backwards compatibility. */
1195
+ /** Delegates to `_selection.removeInvalidValue`. */
1256
1196
  removeInvalidValue = (item) => {
1257
1197
  return this._selection.removeInvalidValue(item);
1258
1198
  };
@@ -1267,39 +1207,39 @@ export class TableContext {
1267
1207
  setOpenModalCell = (cell) => {
1268
1208
  this.openModalCell = cell ? { row: cell.row, columnId: cell.columnId } : null;
1269
1209
  };
1270
- /** Delegating accessor preserved for backwards compatibility. */
1210
+ /** Delegates to `_focus`. */
1271
1211
  toggleHighlightedRow = (item) => {
1272
1212
  return this._selection.toggleHighlightedRow(item);
1273
1213
  };
1274
- /** Delegating accessor preserved for backwards compatibility. */
1214
+ /** Delegates to `_focus`. */
1275
1215
  resetHighlightedRows = () => {
1276
1216
  this._selection.resetHighlightedRows();
1277
1217
  };
1278
- /** Delegating accessor preserved for backwards compatibility. */
1218
+ /** Delegates to `_focus`. */
1279
1219
  toggleSelected = (item) => {
1280
1220
  return this._selection.toggleSelected(item);
1281
1221
  };
1282
- /** Delegating accessor preserved for backwards compatibility. */
1222
+ /** Delegates to `_focus`. */
1283
1223
  setSelected = (item, selected) => {
1284
1224
  return this._selection.setSelected(item, selected);
1285
1225
  };
1286
- /** Delegating accessor preserved for backwards compatibility. */
1226
+ /** Delegates to `_focus`. */
1287
1227
  setSelectionMap = (set) => {
1288
1228
  this._selection.setSelectionMap(set);
1289
1229
  };
1290
- /** Delegating accessor preserved for backwards compatibility. */
1230
+ /** Delegates to `_focus`. */
1291
1231
  updateSelectedItems = async () => {
1292
1232
  await this._selection.updateSelectedItems();
1293
1233
  };
1294
- /** Delegating accessor preserved for backwards compatibility. */
1234
+ /** Delegates to `_focus`. */
1295
1235
  resetSelectedItems = () => {
1296
1236
  this._selection.resetSelectedItems();
1297
1237
  };
1298
- /** Delegating accessor preserved for backwards compatibility. */
1238
+ /** Delegates to `_focus`. */
1299
1239
  selectAllInCurrentPage = async () => {
1300
1240
  await this._selection.selectAllInCurrentPage();
1301
1241
  };
1302
- /** Delegating accessor preserved for backwards compatibility. */
1242
+ /** Delegates to `_focus`. */
1303
1243
  onHighlightAll = () => {
1304
1244
  return this._selection.onHighlightAll();
1305
1245
  };
@@ -1317,11 +1257,10 @@ export class TableContext {
1317
1257
  });
1318
1258
  };
1319
1259
  /**
1320
- * Delegating accessor preserved for backwards compatibility. The
1321
- * implementation lives on `_focus.handleTdKeydown` (Phase 6.6).
1322
- * Bound to the root `<table>` container's `onkeydown` listener;
1323
- * routes Ctrl+F / Escape / Tab synchronously and dispatches the
1324
- * arrow-key / Home / End / Ctrl+A / Ctrl+C path asynchronously.
1260
+ * Delegates to `_focus.handleTdKeydown`. Bound to the root
1261
+ * `<table>` container's `onkeydown` listener; routes Ctrl+F /
1262
+ * Escape / Tab synchronously and dispatches the arrow-key / Home /
1263
+ * End / Ctrl+A / Ctrl+C path asynchronously.
1325
1264
  */
1326
1265
  handleTdKeydown = (e) => {
1327
1266
  this._focus.handleTdKeydown(e);
@@ -1352,12 +1291,7 @@ export class TableContext {
1352
1291
  setColumnPinning = (updater) => {
1353
1292
  this._columnLayout.setColumnPinning(updater);
1354
1293
  };
1355
- /**
1356
- * Delegating accessor preserved for backwards compatibility. The
1357
- * actual `$state` slot lives on `_filtering`; this getter / setter
1358
- * pair forwards reads and writes so existing consumers (notably
1359
- * `generateTableOptions`) need no migration.
1360
- */
1294
+ /** Delegates to `_filtering.updateFilters`. */
1361
1295
  get updateFilters() {
1362
1296
  return this._filtering.updateFilters;
1363
1297
  }
@@ -1534,173 +1468,99 @@ export class TableContext {
1534
1468
  this.unfocus();
1535
1469
  this.columnDefs = newDefs;
1536
1470
  };
1537
- /** Copies the highlighted rows to the OS clipboard as TSV / CSV. */
1538
- copyHighlightedRowsToClipboard = async () => {
1539
- // NOTE: False Positive. Clipboard does not exist in http environments
1540
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1541
- if (!navigator.clipboard) {
1542
- alert('cannot copy in an insecure connection');
1543
- return;
1544
- }
1545
- return this._performRowCopy(await this.getHighlightedRowData());
1471
+ /** Delegates to `_clipboard.copyHighlightedRowsToClipboard`. */
1472
+ copyHighlightedRowsToClipboard = () => {
1473
+ return this._clipboard.copyHighlightedRowsToClipboard();
1546
1474
  };
1547
- /** Copies the currently focused cell range to the OS clipboard. */
1548
- copyDataToClipboard = async () => {
1549
- // NOTE: False Positive. Clipboard does not exist in http environments
1550
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1551
- if (!navigator.clipboard) {
1552
- alert('cannot copy in an insecure connection');
1553
- return;
1554
- }
1555
- return this._performRowCopy(await this.getFocusElementData());
1475
+ /** Delegates to `_clipboard.copyDataToClipboard`. */
1476
+ copyDataToClipboard = () => {
1477
+ return this._clipboard.copyDataToClipboard();
1556
1478
  };
1557
- _performRowCopy = async ({ items, indicesByItem, focusedElementData, affectedColumns, }) => {
1558
- const focusedColumnIds = affectedColumns.map((it) => encapsulate(typeof it.header === 'string' ? it.header : it.id));
1559
- const value = await (async () => {
1560
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
1561
- const selectColumns = new Map();
1562
- for (let i = affectedColumns.length - 1; i > -1; i--) {
1563
- const columnDef = affectedColumns[i];
1564
- if (columnDef instanceof SelectDef) {
1565
- const item = items[i];
1566
- const defOptions = indicesByItem.get(item);
1567
- selectColumns.set(i, await (defOptions
1568
- ? (columnDef.getSpecificOptions?.(item, defOptions) ?? columnDef.getOptions())
1569
- : columnDef.getOptions()));
1570
- }
1571
- }
1572
- const separator = this.commaSeparatedCopy ? ',' : '\t';
1573
- const shouldCopyHeaders = this.copyWithHeaders &&
1574
- (focusedElementData.length !== 1 || focusedElementData[0].length !== 1);
1575
- // transform to TSV
1576
- if (focusedElementData.length === 1 && focusedElementData[0].length === 1) {
1577
- if (selectColumns.has(0)) {
1578
- const columnOptions = selectColumns.get(0);
1579
- const value = columnOptions?.find((it) => it.value === focusedElementData[0][0])?.label ??
1580
- focusedElementData[0][0]?.toString() ??
1581
- '';
1582
- // Single-cell copy bypasses TSV/CSV wrapping but still lands
1583
- // in a spreadsheet; neutralise formula-injection prefixes.
1584
- return escapeForSpreadsheet(value);
1585
- }
1586
- else {
1587
- const value = focusedElementData[0][0]?.toString().trim() ?? '';
1588
- return escapeForSpreadsheet(value);
1589
- }
1590
- }
1591
- return `${shouldCopyHeaders ? `${focusedColumnIds.join(separator)}\n` : ''}${focusedElementData
1592
- .map((it) => it
1593
- .map((toCopy, i) => {
1594
- if (toCopy instanceof Date) {
1595
- return toCopy.toLocaleDateString();
1596
- }
1597
- if (selectColumns.has(i)) {
1598
- const columnOptions = selectColumns.get(i);
1599
- if (dev && !columnOptions) {
1600
- console.warn(`could not find colum with index ${i}`);
1601
- }
1602
- const value = columnOptions?.find((it) => it.value === toCopy)?.label ??
1603
- toCopy?.toString() ??
1604
- '';
1605
- return encapsulate(value);
1606
- }
1607
- else {
1608
- const value = toCopy?.toString().trim() ?? '';
1609
- return encapsulate(value);
1610
- }
1611
- })
1612
- .join(separator))
1613
- .join('\n')}`;
1614
- })();
1615
- navigator.clipboard.writeText(value).catch(devCatch);
1616
- };
1617
- /** Delegating accessor preserved for backwards compatibility. The
1618
- * implementation lives on `_focus.startFocus` (Phase 6.6). */
1479
+ /** Delegates to `_focus.startFocus`. */
1619
1480
  startFocus = (coords) => {
1620
1481
  this._focus.startFocus(coords);
1621
1482
  };
1622
- /** Delegating accessor preserved for backwards compatibility. The
1623
- * implementation lives on `_focus.moveFocus` (Phase 6.6). */
1483
+ /** Delegates to `_focus.moveFocus`. */
1624
1484
  moveFocus = (coords, startEditing = false) => {
1625
1485
  this._focus.moveFocus(coords, startEditing);
1626
1486
  };
1627
- /** Delegating accessor preserved for backwards compatibility. */
1487
+ /** Delegates to `_focus`. */
1628
1488
  unfocus = () => {
1629
1489
  this._focus.unfocus();
1630
1490
  };
1631
- /** Delegating accessor preserved for backwards compatibility. */
1491
+ /** Delegates to `_focus`. */
1632
1492
  focusToTop = () => {
1633
1493
  this._focus.focusToTop();
1634
1494
  };
1635
- /** Delegating accessor preserved for backwards compatibility. */
1495
+ /** Delegates to `_focus`. */
1636
1496
  focusToBottom = () => {
1637
1497
  this._focus.focusToBottom();
1638
1498
  };
1639
- /** Delegating accessor preserved for backwards compatibility. */
1499
+ /** Delegates to `_focus`. */
1640
1500
  moveToBottom = () => {
1641
1501
  this._focus.moveToBottom();
1642
1502
  };
1643
- /** Delegating accessor preserved for backwards compatibility. */
1503
+ /** Delegates to `_focus`. */
1644
1504
  moveToTop = () => {
1645
1505
  this._focus.moveToTop();
1646
1506
  };
1647
- /** Delegating accessor preserved for backwards compatibility. */
1507
+ /** Delegates to `_focus`. */
1648
1508
  moveFocusUp = () => {
1649
1509
  this._focus.moveFocusUp();
1650
1510
  };
1651
- /** Delegating accessor preserved for backwards compatibility. */
1511
+ /** Delegates to `_focus`. */
1652
1512
  setFocusUp = () => {
1653
1513
  this._focus.setFocusUp();
1654
1514
  };
1655
- /** Delegating accessor preserved for backwards compatibility. */
1515
+ /** Delegates to `_focus`. */
1656
1516
  tabForward = () => {
1657
1517
  this._focus.tabForward();
1658
1518
  };
1659
- /** Delegating accessor preserved for backwards compatibility. */
1519
+ /** Delegates to `_focus`. */
1660
1520
  tabBackward = () => {
1661
1521
  this._focus.tabBackward();
1662
1522
  };
1663
- /** Delegating accessor preserved for backwards compatibility. */
1523
+ /** Delegates to `_focus`. */
1664
1524
  focusToStart = () => {
1665
1525
  this._focus.focusToStart();
1666
1526
  };
1667
- /** Delegating accessor preserved for backwards compatibility. */
1527
+ /** Delegates to `_focus`. */
1668
1528
  moveToStart = () => {
1669
1529
  this._focus.moveToStart();
1670
1530
  };
1671
- /** Delegating accessor preserved for backwards compatibility. */
1531
+ /** Delegates to `_focus`. */
1672
1532
  focusToEnd = () => {
1673
1533
  this._focus.focusToEnd();
1674
1534
  };
1675
- /** Delegating accessor preserved for backwards compatibility. */
1535
+ /** Delegates to `_focus`. */
1676
1536
  moveToEnd = () => {
1677
1537
  this._focus.moveToEnd();
1678
1538
  };
1679
- /** Delegating accessor preserved for backwards compatibility. */
1539
+ /** Delegates to `_focus`. */
1680
1540
  moveFocusDown = () => {
1681
1541
  this._focus.moveFocusDown();
1682
1542
  };
1683
- /** Delegating accessor preserved for backwards compatibility. */
1543
+ /** Delegates to `_focus`. */
1684
1544
  setFocusDown = () => {
1685
1545
  this._focus.setFocusDown();
1686
1546
  };
1687
- /** Delegating accessor preserved for backwards compatibility. */
1547
+ /** Delegates to `_focus`. */
1688
1548
  moveFocusLeft = () => {
1689
1549
  this._focus.moveFocusLeft();
1690
1550
  };
1691
- /** Delegating accessor preserved for backwards compatibility. */
1551
+ /** Delegates to `_focus`. */
1692
1552
  setFocusLeft = () => {
1693
1553
  this._focus.setFocusLeft();
1694
1554
  };
1695
- /** Delegating accessor preserved for backwards compatibility. */
1555
+ /** Delegates to `_focus`. */
1696
1556
  moveFocusRight = () => {
1697
1557
  this._focus.moveFocusRight();
1698
1558
  };
1699
- /** Delegating accessor preserved for backwards compatibility. */
1559
+ /** Delegates to `_focus`. */
1700
1560
  setFocusRight = () => {
1701
1561
  this._focus.setFocusRight();
1702
1562
  };
1703
- /** Delegating accessor preserved for backwards compatibility. */
1563
+ /** Delegates to `_focus`. */
1704
1564
  endFocus = (coords) => {
1705
1565
  this._focus.endFocus(coords);
1706
1566
  };
@@ -1752,17 +1612,15 @@ export class TableContext {
1752
1612
  // }
1753
1613
  // };
1754
1614
  /**
1755
- * Delegating method preserved for backwards compatibility. Applies
1756
- * the preset to the live column layout, forwards the preset's
1757
- * `sortingState` to the data-repository updater, and persists the
1758
- * selected name. The underlying implementation lives on
1759
- * `_preferences.setSelectedPreset` (Phase 6.4).
1615
+ * Applies the preset to the live column layout, forwards the
1616
+ * preset's `sortingState` to the data-repository updater, and
1617
+ * persists the selected name. Delegates to
1618
+ * `_preferences.setSelectedPreset`.
1760
1619
  *
1761
- * The pre-extraction debug warning ("no sorting state found") was
1762
- * emitted unconditionally when `paginationRepo.updateSorting` was
1763
- * absent; the sub-context skips that warning because the
1764
- * `applySortingState` thunk already no-ops in that case. The
1765
- * warning is reinstated below so the surface is byte-identical.
1620
+ * The "no sorting state found" debug warning is emitted here (not
1621
+ * inside the sub-context) so the public surface stays byte-identical
1622
+ * - `_preferences.applySortingState` already no-ops when
1623
+ * `paginationRepo.updateSorting` is absent.
1766
1624
  */
1767
1625
  setSelectedPreset = (preset) => {
1768
1626
  if (!this.paginationRepo?.updateSorting) {
@@ -1771,19 +1629,17 @@ export class TableContext {
1771
1629
  this._preferences.setSelectedPreset(preset);
1772
1630
  };
1773
1631
  /**
1774
- * Delegating method preserved for backwards compatibility. Adds or
1775
- * replaces a saved preset for the current column layout and
1632
+ * Adds or replaces a saved preset for the current column layout and
1776
1633
  * persists the full preset list to localStorage. Delegates to
1777
- * `_preferences.saveColumnPreset` (Phase 6.4).
1634
+ * `_preferences.saveColumnPreset`.
1778
1635
  */
1779
1636
  saveColumnPreset = (name) => {
1780
1637
  this._preferences.saveColumnPreset(name);
1781
1638
  };
1782
1639
  /**
1783
- * This listener should be added and removed based on isMousingDown
1640
+ * Delegates to `_focus.setFocusEnd`. This listener should be added
1641
+ * and removed based on `isMousingDown`.
1784
1642
  */
1785
- /** Delegating accessor preserved for backwards compatibility. The
1786
- * implementation lives on `_focus.setFocusEnd` (Phase 6.6). */
1787
1643
  setFocusEnd = (coords) => {
1788
1644
  this._focus.setFocusEnd(coords);
1789
1645
  };
@@ -1888,18 +1744,12 @@ export class TableContext {
1888
1744
  .map(makeRow));
1889
1745
  return row;
1890
1746
  }
1891
- columnDefReturnsTextValue = (it) => {
1892
- return ('index' in it &&
1893
- this.columnVisibility[it.id] !== false &&
1894
- !uncopyableColumns.has(it.columnType));
1895
- };
1896
1747
  /**
1897
- * Builds the user-controlled settings blob from the current
1898
- * `$state` slots and delegates the actual `localStorage.setItem`
1899
- * call to `_preferences.persistSettings` (Phase 6.4). The settings
1900
- * field set (`isCompact`, `singleClickEditing`, etc.) still lives
1901
- * on `TableContext` because each one drives reactive UI; only the
1902
- * persistence side of the boundary moved.
1748
+ * Build the user-controlled settings blob from the current `$state`
1749
+ * slots and delegate the `localStorage.setItem` call to
1750
+ * `_preferences.persistSettings`. The settings fields themselves
1751
+ * live on `TableContext` because each drives reactive UI; only the
1752
+ * persistence boundary lives in the sub-context.
1903
1753
  */
1904
1754
  _updateLocalStorageSettings() {
1905
1755
  const settingsObject = {
@@ -1913,11 +1763,9 @@ export class TableContext {
1913
1763
  this._preferences.persistSettings(settingsObject);
1914
1764
  }
1915
1765
  /**
1916
- * Delegating accessor preserved for backwards compatibility. The
1917
- * cached visual-column array and its paired O(1) id->index map now
1918
- * live on `_columnLayout` (Phase 6.3). The underscore prefix is
1919
- * preserved on `TableContext` to keep the existing read sites in
1920
- * `tabForward` / `focusToStart` / `moveFocusLeft` / etc. untouched.
1766
+ * Delegates to `_columnLayout.visualColumns`. The underscore prefix
1767
+ * is preserved so the existing read sites in `tabForward` /
1768
+ * `focusToStart` / `moveFocusLeft` / etc. stay untouched.
1921
1769
  */
1922
1770
  get _visualColumns() {
1923
1771
  return this._columnLayout.visualColumns;
@@ -1929,166 +1777,16 @@ export class TableContext {
1929
1777
  return this._columnLayout.columnVisualIndex(id);
1930
1778
  }
1931
1779
  /**
1932
- * Delegating getter preserved for backwards compatibility. The
1933
- * implementation lives on `_focus.focusStartVisualColumn`
1934
- * (Phase 6.6); the visual-index lookup itself is owned by
1935
- * `_columnLayout.columnVisualIndex` (Phase 6.3).
1780
+ * Delegates to `_focus.focusStartVisualColumn`; the visual-index
1781
+ * lookup is owned by `_columnLayout.columnVisualIndex`.
1936
1782
  */
1937
1783
  get focusStartVisualColumn() {
1938
1784
  return this._focus.focusStartVisualColumn;
1939
1785
  }
1940
- /**
1941
- * Delegating getter preserved for backwards compatibility. The
1942
- * implementation lives on `_focus.focusEndVisualColumn`
1943
- * (Phase 6.6).
1944
- */
1786
+ /** Delegates to `_focus.focusEndVisualColumn`. */
1945
1787
  get focusEndVisualColumn() {
1946
1788
  return this._focus.focusEndVisualColumn;
1947
1789
  }
1948
- async getHighlightedRowData() {
1949
- const affectedColumns = this.columnDefs.filter(this.columnDefReturnsTextValue);
1950
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
1951
- const indices = new Map();
1952
- const selectedRows = [...this.highlightedItems.values()];
1953
- const sortedData = this.sortedData;
1954
- for (const item of selectedRows) {
1955
- const rowIndex = sortedData.indexOf(item);
1956
- if (rowIndex == -1) {
1957
- console.warn(`row index for highlighted row data not found`);
1958
- continue;
1959
- }
1960
- indices.set(item, rowIndex);
1961
- }
1962
- selectedRows.sort((left, right) => {
1963
- return (indices.get(left) ?? -1) - (indices.get(right) ?? -1);
1964
- });
1965
- // TODO somewhere between here and the ending of the copy functionality, we end up with issues where instead of it using the accessor fn, it tries to pick up the select option, and ends up using the value ("1", etc)
1966
- const selectedCells = [];
1967
- const rowLength = selectedRows.length;
1968
- for (let rowIndex = 0; rowIndex < rowLength; rowIndex++) {
1969
- const item = selectedRows[rowIndex];
1970
- const rawKey = this._cachedKey(item);
1971
- const key = rawKey instanceof Promise ? await rawKey : rawKey;
1972
- selectedCells[rowIndex] = [];
1973
- const targetRow = selectedCells[rowIndex];
1974
- const colLength = affectedColumns.length;
1975
- for (let colIndex = 0; colIndex < colLength; colIndex++) {
1976
- const columnDef = affectedColumns[colIndex];
1977
- if (columnDef instanceof RowSelectionDef) {
1978
- targetRow.push(this.selectedItems.has(key) ? 'true' : 'false');
1979
- continue;
1980
- }
1981
- if (uncopyableColumns.has(columnDef.columnType)) {
1982
- continue;
1983
- }
1984
- const x = (await columnDef.getDisplayValue?.(item, sortedData.lastIndexOf(item))) ||
1985
- ('accessorFn' in columnDef &&
1986
- (await columnDef.accessorFn?.(item, sortedData.indexOf(item))));
1987
- if (x && typeof x === 'string') {
1988
- targetRow.push(x);
1989
- continue;
1990
- }
1991
- if (x && Array.isArray(x) && typeof x[0] === 'string') {
1992
- targetRow.push(x.join(', '));
1993
- continue;
1994
- }
1995
- const toCopy = columnDef.id && columnDef.id in item ? item[columnDef.id] : null;
1996
- const y = toCopy && typeof toCopy === 'object' && toCopy instanceof Date
1997
- ? toCopy.toLocaleDateString()
1998
- : (toCopy?.toString() ?? '');
1999
- targetRow.push(y);
2000
- }
2001
- }
2002
- return {
2003
- items: selectedRows,
2004
- indicesByItem: indices,
2005
- focusedElementData: selectedCells,
2006
- affectedColumns,
2007
- };
2008
- }
2009
- async getFocusElementData() {
2010
- if (this.focusStart && this.focusEnd) {
2011
- const { row: startRowIndexId } = this.focusStart;
2012
- const { row: endRowIndexId } = this.focusEnd;
2013
- // const model = this._currentPageData;
2014
- const model = this.paginationRowModel;
2015
- const startRowIndex = model.rows.findIndex((row) => row.visibleIndex === startRowIndexId);
2016
- const endRowIndex = model.rows.findIndex((row) => row.visibleIndex === endRowIndexId);
2017
- if (!this.currentPageData.length || startRowIndex == -1 || endRowIndex == -1) {
2018
- throw new Error(`copy was missing one of the two indices. First: ${startRowIndex}, second: ${endRowIndex}`);
2019
- }
2020
- const minRowIndex = Math.min(startRowIndex, endRowIndex);
2021
- const maxRowIndex = Math.max(startRowIndex, endRowIndex);
2022
- const startVisualIndex = this.columnVisualIndex(this.focusStart.id);
2023
- const endVisualIndex = this.columnVisualIndex(this.focusEnd.id);
2024
- const minVisualIndex = Math.min(startVisualIndex, endVisualIndex);
2025
- const maxVisualIndex = Math.max(startVisualIndex, endVisualIndex);
2026
- const affectedColumns = this._visualColumns
2027
- .slice(minVisualIndex, maxVisualIndex + 1)
2028
- .filter((it) => this.columnDefReturnsTextValue(it));
2029
- const selectedRows = [];
2030
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
2031
- const indices = new Map();
2032
- for (let i = minRowIndex; i <= maxRowIndex; i++) {
2033
- const row = model.rows[i];
2034
- const item = row.original;
2035
- const itemIndex = row.originalIndex;
2036
- selectedRows.push(item);
2037
- indices.set(item, itemIndex);
2038
- }
2039
- // TODO somewhere between here and the ending of the copy functionality, we end up with issues where instead of it using the accessor fn, it tries to pick up the select option, and ends up using the value ("1", etc)
2040
- const selectedCells = [];
2041
- const rowLength = selectedRows.length;
2042
- const sortedData = this.sortedData;
2043
- for (let rowIndex = 0; rowIndex < rowLength; rowIndex++) {
2044
- const item = selectedRows[rowIndex];
2045
- const rawKey = this._cachedKey(item);
2046
- const key = rawKey instanceof Promise ? await rawKey : rawKey;
2047
- selectedCells[rowIndex] = [];
2048
- const targetRow = selectedCells[rowIndex];
2049
- const colLength = affectedColumns.length;
2050
- for (let colIndex = 0; colIndex < colLength; colIndex++) {
2051
- const columnDef = affectedColumns[colIndex];
2052
- if (columnDef instanceof RowSelectionDef) {
2053
- targetRow.push(this.selectedItems.has(key) ? 'true' : 'false');
2054
- continue;
2055
- }
2056
- if (uncopyableColumns.has(columnDef.columnType)) {
2057
- continue;
2058
- }
2059
- const x = (await columnDef.getDisplayValue?.(item, sortedData.lastIndexOf(item))) ||
2060
- ('accessorFn' in columnDef &&
2061
- (await columnDef.accessorFn?.(item, sortedData.indexOf(item))));
2062
- if (x && typeof x === 'string') {
2063
- targetRow.push(x);
2064
- continue;
2065
- }
2066
- if (x && Array.isArray(x) && typeof x[0] === 'string') {
2067
- targetRow.push(x.join(', '));
2068
- continue;
2069
- }
2070
- const toCopy = columnDef.id && columnDef.id in item ? item[columnDef.id] : null;
2071
- const y = toCopy && typeof toCopy === 'object' && toCopy instanceof Date
2072
- ? toCopy.toLocaleDateString()
2073
- : (toCopy?.toString() ?? '');
2074
- targetRow.push(y);
2075
- }
2076
- }
2077
- return {
2078
- items: selectedRows,
2079
- indicesByItem: indices,
2080
- focusedElementData: selectedCells,
2081
- affectedColumns,
2082
- };
2083
- }
2084
- return {
2085
- items: [],
2086
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
2087
- indicesByItem: new Map(),
2088
- focusedElementData: [],
2089
- affectedColumns: [],
2090
- };
2091
- }
2092
1790
  updateDefsBasedOnPinning = () => {
2093
1791
  return this._columnLayout.updateDefsBasedOnPinning();
2094
1792
  };
@@ -2105,17 +1803,28 @@ export class TableContext {
2105
1803
  // eslint-disable-next-line @typescript-eslint/require-await
2106
1804
  async setupDefaultColumnDefs() {
2107
1805
  this.assertNoDuplicateIds();
1806
+ // Snapshot the locally-available filter functions once per pass so
1807
+ // `resolveDefaultFilter` can index into them by name.
1808
+ const localFilters = {
1809
+ stringFilter: this.stringFilter,
1810
+ numberFilter: this.numberFilter,
1811
+ selectFilter: this.selectFilter,
1812
+ boolFilter: this.boolFilter,
1813
+ dateFilter: this.dateFilter,
1814
+ };
2108
1815
  const adjustedDefsPromises = this.defaultColumnDefs.defs.map((def) => {
2109
- if (def instanceof AccessorDef) {
1816
+ const entry = def.kind != null ? COLUMN_KIND_REGISTRY[def.kind] : undefined;
1817
+ // Accessor columns get a header-width minSize floor so the
1818
+ // header text never wraps under its declared width. Falls
1819
+ // back to 120px when the header is a Snippet rather than a
1820
+ // plain string (cannot canvas-measure).
1821
+ if (entry?.category === 'accessor') {
2110
1822
  if (!def.allowFiltering) {
2111
1823
  if (def.debug) {
2112
1824
  console.log(`${def.id}: does not allow filtering`, def);
2113
1825
  }
2114
1826
  return def;
2115
1827
  }
2116
- if (def.debug) {
2117
- console.log(`${def.id}: is an accessor def`, def);
2118
- }
2119
1828
  if (!('minSize' in def) || !def.minSize) {
2120
1829
  if (typeof def.header === 'string') {
2121
1830
  const minWidth = this.measureHeaderWidth(def);
@@ -2126,103 +1835,26 @@ export class TableContext {
2126
1835
  def.minSize = 120;
2127
1836
  }
2128
1837
  }
2129
- // code smell...
2130
- if (def instanceof TextInputDef) {
2131
- def.filterFn = this.stringFilter;
2132
- if (def.debug) {
2133
- console.log(`${def.id}: given a string filter`, def);
2134
- }
2135
- }
2136
- else if (def instanceof NumberInputDef) {
2137
- def.filterFn = this.numberFilter;
2138
- if (def.debug) {
2139
- console.log(`${def.id}: given a number filter`, def);
2140
- }
2141
- }
2142
- else if (def instanceof SelectDef) {
2143
- def.filterFn = this.selectFilter;
2144
- if (def.debug) {
2145
- console.log(`${def.id}: given a select filter`, def);
2146
- }
2147
- }
2148
- else if (def instanceof CheckboxDef) {
2149
- def.filterFn = this.boolFilter;
2150
- if (def.debug) {
2151
- console.log(`${def.id}: given a bool filter`, def);
2152
- }
2153
- }
2154
- else if (def instanceof DateInputDef) {
2155
- def.filterFn = this.dateFilter;
2156
- if (def.debug) {
2157
- console.log(`${def.id}: given a date filter`, def);
2158
- }
2159
- }
2160
- else if (def.debug) {
2161
- console.log(`${def.id}: was not a text,number,select,checkbox,or date accessor, and was not given a filter`, def);
2162
- }
2163
1838
  }
2164
- else if (
2165
- // this.paginationRepo?.paginationType == PaginationType.Local &&
2166
- !def.filterFn &&
2167
- def.allowFiltering) {
2168
- if (def instanceof DisplayDef) {
2169
- if (def.debug) {
2170
- console.log(`${def.id}: is a display def`, def);
2171
- }
2172
- switch (def.filterValueType) {
2173
- case undefined:
2174
- case 'String': {
2175
- def.filterFn = this.stringFilter;
2176
- if (def.debug) {
2177
- console.log(`${def.id}: given a string filter`, def);
2178
- }
2179
- break;
2180
- }
2181
- case 'Bool': {
2182
- def.filterFn = this.boolFilter;
2183
- if (def.debug) {
2184
- console.log(`${def.id}: given a bool filter`, def);
2185
- }
2186
- break;
2187
- }
2188
- case 'Date': {
2189
- def.filterFn = this.dateFilter;
2190
- if (def.debug) {
2191
- console.log(`${def.id}: given a date filter`, def);
2192
- }
2193
- break;
2194
- }
2195
- case 'Number': {
2196
- def.filterFn = this.numberFilter;
2197
- if (def.debug) {
2198
- console.log(`${def.id}: given a number filter`, def);
2199
- }
2200
- break;
2201
- }
2202
- case 'Select': {
2203
- def.filterFn = this.selectFilter;
2204
- if (def.debug) {
2205
- console.log(`${def.id}: given a select filter`, def);
2206
- }
2207
- break;
1839
+ // Wire the default filter function for kinds the registry knows
1840
+ // about. Accessor kinds replace any pre-existing filterFn;
1841
+ // display kinds (Display / Bubble / Progress) only fill in when
1842
+ // nothing was set. Structural kinds (rowSelection, actions,
1843
+ // expand, validity, filterOnly) skip entirely.
1844
+ if (def.allowFiltering) {
1845
+ const isAccessor = entry?.category === 'accessor';
1846
+ if (isAccessor || !def.filterFn) {
1847
+ const resolved = resolveDefaultFilter(def, localFilters);
1848
+ if (resolved !== undefined) {
1849
+ def.filterFn = resolved;
1850
+ if (def.debug) {
1851
+ console.log(`${def.id}: assigned default ${def.kind ?? '<unknown>'} filter`, def);
2208
1852
  }
2209
1853
  }
2210
- }
2211
- else if (def instanceof BubbleDef) {
2212
- def.filterFn = this.stringFilter;
2213
- if (def.debug) {
2214
- console.log(`${def.id}: is a bubble def. given a string filter`, def);
2215
- }
2216
- }
2217
- else if (def instanceof ProgressBarDef) {
2218
- def.filterFn = this.numberFilter;
2219
- if (def.debug) {
2220
- console.log(`${def.id}: is a progress def. given a number filter`, def);
1854
+ else if (def.debug && entry !== undefined && entry.category !== 'structural') {
1855
+ console.log(`${def.id}: kind has no default filter and none assigned`, def);
2221
1856
  }
2222
1857
  }
2223
- else if (def.debug) {
2224
- console.log(`${def.id}: is not a display, bubble, or progressbar def, and was not given a filter`, def);
2225
- }
2226
1858
  }
2227
1859
  else if (def.debug) {
2228
1860
  console.log(`${def.id}: no filtering matched`, def);
@@ -2239,16 +1871,15 @@ export class TableContext {
2239
1871
  return adjustedDefs;
2240
1872
  }
2241
1873
  /**
2242
- * Reads the persisted per-table user-settings blob through
2243
- * `_preferences.loadSavedSettings` (Phase 6.4) and applies each
2244
- * field to the live `$state` slot. Field defaults mirror the
2245
- * pre-extraction behaviour:
1874
+ * Read the persisted per-table user-settings blob through
1875
+ * `_preferences.loadSavedSettings` and apply each field to the live
1876
+ * `$state` slot. Field defaults:
2246
1877
  *
2247
1878
  * - missing slot: `singleClickEditing` defaults to `true`; every
2248
1879
  * other field keeps its constructor default.
2249
- * - present slot: each field is applied if it would deviate from
1880
+ * - present slot: a field is applied only if it would deviate from
2250
1881
  * the constructor default (the "only alter when the user
2251
- * explicitly disabled" semantics are preserved verbatim).
1882
+ * explicitly disabled it" semantic).
2252
1883
  */
2253
1884
  _getUserDefaults() {
2254
1885
  const saved = this._preferences.loadSavedSettings();
@@ -2365,51 +1996,6 @@ export function isSearchable(def) {
2365
1996
  function hasPagination(repository) {
2366
1997
  return 'setPage' in repository;
2367
1998
  }
2368
- /**
2369
- * Neutralises CSV / TSV formula-injection prefixes recognised by Excel,
2370
- * LibreOffice Calc, Google Sheets, and Apple Numbers.
2371
- *
2372
- * When a clipboard payload reaches a spreadsheet, any cell whose first
2373
- * character is one of `=`, `+`, `-`, `@`, `\t`, `\r` is treated as a
2374
- * formula and evaluated in the user's context. Untrusted row data
2375
- * containing `=cmd|'/c calc'!A1` (or DDE / web-query equivalents) is the
2376
- * canonical attack vector. Prefixing such a value with a single quote
2377
- * (`'`) makes the spreadsheet interpret the cell as a literal string
2378
- * instead.
2379
- *
2380
- * Trade-off: the leading apostrophe is invisible in the rendered cell of
2381
- * every major spreadsheet, but the underlying cell value differs from the
2382
- * source when read back by another formula referencing the cell. This is
2383
- * the accepted industry mitigation (OWASP CSV Injection cheat sheet).
2384
- *
2385
- * Private helper - keep this colocated with `encapsulate` so every
2386
- * clipboard-bound string flows through both functions before being
2387
- * written to the OS clipboard.
2388
- */
2389
- function escapeForSpreadsheet(value) {
2390
- if (value.length === 0) {
2391
- return value;
2392
- }
2393
- const first = value.charCodeAt(0);
2394
- // '=' 61, '+' 43, '-' 45, '@' 64, '\t' 9, '\r' 13
2395
- if (first === 61 || first === 43 || first === 45 || first === 64 || first === 9 || first === 13) {
2396
- return `'${value}`;
2397
- }
2398
- return value;
2399
- }
2400
- function encapsulate(value) {
2401
- const safe = escapeForSpreadsheet(value);
2402
- return `"${safe.replace(/[\n]/gm, ' ').replace(/["]/g, '""')}"`;
2403
- }
2404
- /**
2405
- * Internal exports for tests. Not part of the public API; do not import
2406
- * from consumer code. Allows the clipboard formula-escape regression test
2407
- * to call the module-private helpers directly.
2408
- */
2409
- export const _internal = {
2410
- escapeForSpreadsheet,
2411
- encapsulate,
2412
- };
2413
1999
  function generateTableOptions(contextObj) {
2414
2000
  const _pagination = $derived.by(() => {
2415
2001
  void contextObj.paginationRepo?.paginationState.pageSize;