@lavalogic/scoria 0.35.0 → 0.37.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 (555) hide show
  1. package/README.md +58 -58
  2. package/dist/Components/AccordionGroup.svelte +46 -42
  3. package/dist/Components/AccordionGroupButton.svelte +106 -106
  4. package/dist/Components/AccordionGroupButtonProps.d.ts +27 -8
  5. package/dist/Components/AccordionGroupProps.d.ts +10 -2
  6. package/dist/Components/Action.svelte +137 -139
  7. package/dist/Components/ActionProps.d.ts +23 -3
  8. package/dist/Components/AlertModal.svelte +176 -125
  9. package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte +1126 -0
  10. package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte.d.ts +4 -0
  11. package/dist/Components/AppointmentCalendar/AppointmentCalendarProps.d.ts +92 -0
  12. package/dist/Components/AppointmentCalendar/AppointmentCalendarProps.js +12 -0
  13. package/dist/Components/Base/BaseModal.svelte +172 -0
  14. package/dist/Components/Base/BaseModal.svelte.d.ts +4 -0
  15. package/dist/Components/Base/BaseModalProps.d.ts +73 -0
  16. package/dist/Components/Base/BaseModalProps.js +1 -0
  17. package/dist/Components/Base/ContextWrapper.svelte +72 -50
  18. package/dist/Components/Base/ContextWrapper.svelte.d.ts +24 -5
  19. package/dist/Components/Base/ContextWrapperProps.d.ts +22 -0
  20. package/dist/Components/Base/Pagination.svelte +198 -190
  21. package/dist/Components/Base/Pagination.svelte.d.ts +12 -2
  22. package/dist/Components/Base/PaginationProps.d.ts +60 -3
  23. package/dist/Components/Base/Snippets.svelte +36 -29
  24. package/dist/Components/Base/Snippets.svelte.d.ts +5 -1
  25. package/dist/Components/BigRadioSet.svelte +39 -30
  26. package/dist/Components/BigRadioSetProps.d.ts +17 -1
  27. package/dist/Components/Bubble.svelte +110 -77
  28. package/dist/Components/Bubble.svelte.d.ts +23 -2
  29. package/dist/Components/BubbleProps.d.ts +55 -4
  30. package/dist/Components/Button.svelte +209 -197
  31. package/dist/Components/ButtonProps.d.ts +66 -1
  32. package/dist/Components/Checkbox.svelte +100 -72
  33. package/dist/Components/Checkbox.svelte.d.ts +1 -12
  34. package/dist/Components/CheckboxProps.d.ts +27 -0
  35. package/dist/Components/CheckboxProps.js +1 -0
  36. package/dist/Components/CollapsibleCard.svelte +114 -83
  37. package/dist/Components/CollapsibleCardProps.d.ts +22 -1
  38. package/dist/Components/Contexts/ContextMenu.svelte +178 -157
  39. package/dist/Components/Contexts/ContextMenu.svelte.d.ts +26 -0
  40. package/dist/Components/Contexts/ContextMenuDivider.svelte +19 -11
  41. package/dist/Components/Contexts/ContextMenuDivider.svelte.d.ts +9 -0
  42. package/dist/Components/Contexts/ContextMenuOption.svelte +66 -57
  43. package/dist/Components/Contexts/ContextMenuOption.svelte.d.ts +12 -1
  44. package/dist/Components/Contexts/ContextMenuOptionProps.d.ts +34 -2
  45. package/dist/Components/Contexts/Toast.svelte +88 -64
  46. package/dist/Components/Contexts/Toast.svelte.d.ts +14 -0
  47. package/dist/Components/Contexts/ToastContainer.svelte +68 -49
  48. package/dist/Components/Contexts/ToastContainer.svelte.d.ts +14 -0
  49. package/dist/Components/Contexts/ToastProps.d.ts +14 -1
  50. package/dist/Components/Contexts/Tooltip.svelte +47 -36
  51. package/dist/Components/Contexts/Tooltip.svelte.d.ts +10 -0
  52. package/dist/Components/DataMatrixIcon.svelte +113 -42
  53. package/dist/Components/DataMatrixIconProps.d.ts +73 -1
  54. package/dist/Components/DateInput.svelte +144 -109
  55. package/dist/Components/DateInput.svelte.d.ts +1 -1
  56. package/dist/Components/DateOnlyInputProps.d.ts +31 -5
  57. package/dist/Components/DatePicker.svelte +301 -313
  58. package/dist/Components/DatePickerProps.d.ts +18 -1
  59. package/dist/Components/DateTimeInputProps.d.ts +30 -5
  60. package/dist/Components/DesktopModal.svelte +90 -119
  61. package/dist/Components/DesktopModalProps.d.ts +5 -4
  62. package/dist/Components/Dial.svelte +213 -151
  63. package/dist/Components/DialProps.d.ts +74 -2
  64. package/dist/Components/DimensionInput.svelte +171 -185
  65. package/dist/Components/DimensionInput.svelte.d.ts +1 -1
  66. package/dist/Components/DimensionInputProps.d.ts +39 -4
  67. package/dist/Components/DragMenu/DragMenuHolder.svelte +119 -107
  68. package/dist/Components/DragMenu/DragMenuHolderProps.d.ts +30 -1
  69. package/dist/Components/DragMenu/DraggableCard.svelte +77 -41
  70. package/dist/Components/DragMenu/DraggableCardProps.d.ts +27 -1
  71. package/dist/Components/FileCard.svelte +24 -20
  72. package/dist/Components/FileCardProps.d.ts +17 -1
  73. package/dist/Components/FileUpload.svelte +236 -196
  74. package/dist/Components/FileUploadProps.d.ts +34 -4
  75. package/dist/Components/GridInput.svelte +201 -191
  76. package/dist/Components/GridInput.svelte.d.ts +5 -5
  77. package/dist/Components/GridInputProps.d.ts +4 -1
  78. package/dist/Components/GridInputSet.svelte +31 -24
  79. package/dist/Components/GridInputSet.svelte.d.ts +5 -5
  80. package/dist/Components/GridInputSetProps.d.ts +4 -2
  81. package/dist/Components/HorizontalTabGroup.svelte +81 -79
  82. package/dist/Components/HorizontalTabGroupButton.svelte +51 -103
  83. package/dist/Components/HorizontalTabGroupProps.d.ts +15 -2
  84. package/dist/Components/Icon.svelte +144 -103
  85. package/dist/Components/IconProps.d.ts +54 -6
  86. package/dist/Components/Icons.d.ts +8 -0
  87. package/dist/Components/Icons.js +591 -572
  88. package/dist/Components/LoadingAnimation.svelte +39 -39
  89. package/dist/Components/LoadingAnimation.svelte.d.ts +1 -3
  90. package/dist/Components/LoadingAnimationProps.d.ts +16 -0
  91. package/dist/Components/LoadingAnimationProps.js +1 -0
  92. package/dist/Components/LoadingModal.svelte +48 -50
  93. package/dist/Components/LoadingModal.svelte.d.ts +1 -4
  94. package/dist/Components/LoadingModalProps.d.ts +17 -0
  95. package/dist/Components/LoadingModalProps.js +1 -0
  96. package/dist/Components/LoadingOverlay.svelte +28 -30
  97. package/dist/Components/LoadingOverlay.svelte.d.ts +1 -5
  98. package/dist/Components/LoadingOverlayProps.d.ts +28 -0
  99. package/dist/Components/LoadingOverlayProps.js +1 -0
  100. package/dist/Components/Modal.svelte +250 -255
  101. package/dist/Components/Modal.svelte.d.ts +1 -25
  102. package/dist/Components/ModalProps.d.ts +65 -0
  103. package/dist/Components/ModalProps.js +1 -0
  104. package/dist/Components/MultiSelect.svelte +238 -226
  105. package/dist/Components/MultiSelectCommonProps.d.ts +33 -4
  106. package/dist/Components/MultiSelectPropsAsObject.d.ts +5 -0
  107. package/dist/Components/MultiSelectPropsAsPrimitive.d.ts +6 -0
  108. package/dist/Components/Nav/CallbackButtonWithIcon.d.ts +22 -1
  109. package/dist/Components/Nav/Nav.svelte +115 -110
  110. package/dist/Components/Nav/Nav.svelte.d.ts +23 -2
  111. package/dist/Components/Nav/NavProps.d.ts +50 -3
  112. package/dist/Components/Nav/NavServiceMenuList.svelte +44 -35
  113. package/dist/Components/Nav/NavServiceMenuListProps.d.ts +25 -1
  114. package/dist/Components/Nav/NavTab.svelte +62 -91
  115. package/dist/Components/Nav/NavTab.svelte.d.ts +23 -2
  116. package/dist/Components/Nav/NavTabProps.d.ts +16 -2
  117. package/dist/Components/Nav/ServiceMenuItem.svelte +101 -92
  118. package/dist/Components/Nav/ServicesNav.svelte +96 -70
  119. package/dist/Components/Nav/ServicesNav.svelte.d.ts +23 -2
  120. package/dist/Components/Nav/ServicesNavProps.d.ts +20 -4
  121. package/dist/Components/Nav/SideMenuItemProps.d.ts +16 -0
  122. package/dist/Components/NumberInput.svelte +155 -145
  123. package/dist/Components/NumberInputProps.d.ts +37 -0
  124. package/dist/Components/PageHeading.svelte +25 -36
  125. package/dist/Components/PageHeading.svelte.d.ts +1 -6
  126. package/dist/Components/PageHeadingProps.d.ts +23 -0
  127. package/dist/Components/PageHeadingProps.js +1 -0
  128. package/dist/Components/PasswordInput.svelte +85 -73
  129. package/dist/Components/PasswordInput.svelte.d.ts +2 -2
  130. package/dist/Components/PasswordInputProps.d.ts +21 -0
  131. package/dist/Components/PasswordInputProps.js +1 -0
  132. package/dist/Components/Portal.svelte +36 -25
  133. package/dist/Components/PortalProps.d.ts +20 -1
  134. package/dist/Components/ProgressBubble.svelte +129 -84
  135. package/dist/Components/ProgressBubbleProps.d.ts +23 -2
  136. package/dist/Components/QuerySelect.svelte +384 -440
  137. package/dist/Components/QuerySelect.svelte.d.ts +2 -29
  138. package/dist/Components/QuerySelectProps.d.ts +66 -0
  139. package/dist/Components/QuerySelectProps.js +1 -0
  140. package/dist/Components/SingleSelect.svelte +268 -290
  141. package/dist/Components/SingleSelectCommonProps.d.ts +43 -5
  142. package/dist/Components/SingleSelectPropsAsObject.d.ts +7 -0
  143. package/dist/Components/SingleSelectPropsAsPrimitive.d.ts +8 -0
  144. package/dist/Components/StepButton.svelte +70 -120
  145. package/dist/Components/StepForm.svelte +147 -147
  146. package/dist/Components/StepFormProps.d.ts +16 -2
  147. package/dist/Components/Switch.svelte +84 -47
  148. package/dist/Components/Switch.svelte.d.ts +1 -1
  149. package/dist/Components/SwitchProps.d.ts +46 -0
  150. package/dist/Components/Table/Body/HeightData.d.ts +1 -6
  151. package/dist/Components/Table/Body/HeightData.js +8 -0
  152. package/dist/Components/Table/Body/QuickSearchCell.svelte +697 -641
  153. package/dist/Components/Table/Body/QuickSearchCell.svelte.d.ts +12 -11
  154. package/dist/Components/Table/Body/QuickSearchCellProps.d.ts +6 -0
  155. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +68 -34
  156. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte.d.ts +24 -2
  157. package/dist/Components/Table/Body/Rows/ColumnHighlightProps.d.ts +3 -0
  158. package/dist/Components/Table/Body/Rows/ColumnList.svelte +66 -56
  159. package/dist/Components/Table/Body/Rows/ColumnList.svelte.d.ts +12 -11
  160. package/dist/Components/Table/Body/Rows/ColumnListProps.d.ts +1 -0
  161. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +515 -182
  162. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte.d.ts +12 -11
  163. package/dist/Components/Table/Body/Rows/ColumnListRowProps.d.ts +5 -0
  164. package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +79 -49
  165. package/dist/Components/Table/Body/Rows/Columns/AccessorComponentProps.d.ts +12 -0
  166. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +203 -191
  167. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte.d.ts +12 -12
  168. package/dist/Components/Table/Body/Rows/Columns/ActionsColumn.d.ts +9 -0
  169. package/dist/Components/Table/Body/Rows/Columns/ActionsTableCell.d.ts +5 -0
  170. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +150 -115
  171. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte.d.ts +14 -13
  172. package/dist/Components/Table/Body/Rows/Columns/BubbleCellProps.d.ts +12 -0
  173. package/dist/Components/Table/Body/Rows/Columns/BubbleCellProps.js +1 -0
  174. package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +149 -129
  175. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +149 -126
  176. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte.d.ts +12 -11
  177. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCellProps.d.ts +7 -0
  178. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +142 -108
  179. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte.d.ts +17 -11
  180. package/dist/Components/Table/Body/Rows/Columns/RowSelectionColumn.d.ts +8 -0
  181. package/dist/Components/Table/Body/Rows/Columns/RowSelectionTableCell.d.ts +5 -0
  182. package/dist/Components/Table/Body/Rows/Columns/SelectColumn.d.ts +11 -0
  183. package/dist/Components/Table/Body/Rows/Columns/SelectTableCell.d.ts +11 -0
  184. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +87 -72
  185. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte.d.ts +11 -0
  186. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +148 -137
  187. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte.d.ts +13 -18
  188. package/dist/Components/Table/Body/Rows/Columns/TableCheckboxProps.d.ts +17 -0
  189. package/dist/Components/Table/Body/Rows/Columns/TableCheckboxProps.js +1 -0
  190. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +159 -144
  191. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte.d.ts +1 -7
  192. package/dist/Components/Table/Body/Rows/Columns/TableColumnProps.d.ts +16 -0
  193. package/dist/Components/Table/Body/Rows/Columns/TableColumnProps.js +1 -0
  194. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +279 -249
  195. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte.d.ts +12 -11
  196. package/dist/Components/Table/Body/Rows/Columns/TableDatePickerProps.d.ts +9 -0
  197. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +249 -370
  198. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte.d.ts +20 -12
  199. package/dist/Components/Table/Body/Rows/Columns/TableQueryCell.d.ts +8 -0
  200. package/dist/Components/Table/Body/Rows/Columns/TableQueryColumn.d.ts +9 -0
  201. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +291 -354
  202. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte.d.ts +12 -11
  203. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelectProps.d.ts +8 -0
  204. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +201 -189
  205. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte.d.ts +2 -5
  206. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckboxProps.d.ts +14 -0
  207. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckboxProps.js +1 -0
  208. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +263 -325
  209. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte.d.ts +13 -12
  210. package/dist/Components/Table/Body/Rows/Columns/TableSelectProps.d.ts +6 -0
  211. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +230 -381
  212. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte.d.ts +12 -11
  213. package/dist/Components/Table/Body/Rows/Columns/TableTextInputProps.d.ts +6 -0
  214. package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +187 -177
  215. package/dist/Components/Table/Body/Rows/Columns/_helpers/useAsyncEditable.svelte.d.ts +104 -0
  216. package/dist/Components/Table/Body/Rows/Columns/_helpers/useAsyncEditable.svelte.js +215 -0
  217. package/dist/Components/Table/Body/Rows/TableRow.svelte +331 -263
  218. package/dist/Components/Table/Body/Rows/TableRow.svelte.d.ts +2 -10
  219. package/dist/Components/Table/Body/Rows/TableRowProps.d.ts +11 -2
  220. package/dist/Components/Table/Body/TableBody.svelte +198 -81
  221. package/dist/Components/Table/Body/TableBody.svelte.d.ts +15 -22
  222. package/dist/Components/Table/Body/TableBodyProps.d.ts +9 -7
  223. package/dist/Components/Table/ColumnDefinitions/ExampleItemColumnDefRepository.svelte.js +18 -12
  224. package/dist/Components/Table/ColumnFactory.svelte.d.ts +43 -0
  225. package/dist/Components/Table/ColumnFactory.svelte.js +339 -0
  226. package/dist/Components/Table/Headers/HighlightAllHeader.svelte +41 -39
  227. package/dist/Components/Table/Headers/HighlightAllHeader.svelte.d.ts +12 -11
  228. package/dist/Components/Table/Headers/SelectAllHeader.svelte +30 -28
  229. package/dist/Components/Table/Headers/SelectAllHeader.svelte.d.ts +12 -11
  230. package/dist/Components/Table/Headers/TableHead.svelte +434 -294
  231. package/dist/Components/Table/Headers/TableHead.svelte.d.ts +13 -16
  232. package/dist/Components/Table/Headers/TableHeadProps.d.ts +10 -0
  233. package/dist/Components/Table/Headers/TableHeadProps.js +1 -0
  234. package/dist/Components/Table/Misc/ColumnPanel.svelte +154 -96
  235. package/dist/Components/Table/Misc/ColumnPanel.svelte.d.ts +12 -11
  236. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +112 -99
  237. package/dist/Components/Table/Misc/ColumnPanelModal.svelte.d.ts +12 -11
  238. package/dist/Components/Table/Misc/ColumnPanelModalProps.d.ts +5 -0
  239. package/dist/Components/Table/Misc/FilterInput.svelte +545 -556
  240. package/dist/Components/Table/Misc/FilterInput.svelte.d.ts +12 -11
  241. package/dist/Components/Table/Misc/FilterPanel.svelte +409 -51
  242. package/dist/Components/Table/Misc/FilterPanel.svelte.d.ts +12 -11
  243. package/dist/Components/Table/Misc/FilterPanelModal.svelte +30 -77
  244. package/dist/Components/Table/Misc/FilterPanelModal.svelte.d.ts +2 -23
  245. package/dist/Components/Table/Misc/SidePanel.svelte +35 -32
  246. package/dist/Components/Table/Misc/SidePanel.svelte.d.ts +1 -7
  247. package/dist/Components/Table/Misc/SidePanelProps.d.ts +17 -0
  248. package/dist/Components/Table/Misc/SidePanelProps.js +1 -0
  249. package/dist/Components/Table/Misc/TableFooter.svelte +315 -306
  250. package/dist/Components/Table/Misc/TableFooter.svelte.d.ts +13 -14
  251. package/dist/Components/Table/Misc/TableFooterProps.d.ts +8 -0
  252. package/dist/Components/Table/Misc/TableFooterProps.js +1 -0
  253. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +126 -157
  254. package/dist/Components/Table/Misc/TableHorizontalBar.svelte.d.ts +12 -11
  255. package/dist/Components/Table/Misc/TableSidebar.svelte +158 -177
  256. package/dist/Components/Table/Misc/TableSidebar.svelte.d.ts +12 -11
  257. package/dist/Components/Table/Misc/ToolboxPanel.svelte +147 -81
  258. package/dist/Components/Table/Misc/ToolboxPanel.svelte.d.ts +12 -11
  259. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +107 -100
  260. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte.d.ts +12 -11
  261. package/dist/Components/Table/Misc/ToolboxPanelModalProps.d.ts +5 -0
  262. package/dist/Components/Table/Misc/numberDateFilterTypes.d.ts +7 -4
  263. package/dist/Components/Table/Misc/numberDateFilterTypes.js +8 -3
  264. package/dist/Components/Table/Misc/stringFilterTypes.d.ts +6 -4
  265. package/dist/Components/Table/Misc/stringFilterTypes.js +7 -3
  266. package/dist/Components/Table/NestedTable.svelte +132 -88
  267. package/dist/Components/Table/NestedTable.svelte.d.ts +2 -10
  268. package/dist/Components/Table/NestedTableProps.d.ts +25 -0
  269. package/dist/Components/Table/NestedTableProps.js +1 -0
  270. package/dist/Components/Table/SubApis.svelte.d.ts +23 -0
  271. package/dist/Components/Table/SubApis.svelte.js +359 -0
  272. package/dist/Components/Table/Table.svelte +694 -519
  273. package/dist/Components/Table/Table.svelte.d.ts +24 -1
  274. package/dist/Components/Table/TableHelpers.svelte.d.ts +58 -0
  275. package/dist/Components/Table/TableHelpers.svelte.js +88 -0
  276. package/dist/Components/Table/Types/Columns/Column.d.ts +10 -0
  277. package/dist/Components/Table/Types/Columns/ColumnPinningState.d.ts +12 -0
  278. package/dist/Components/Table/Types/Columns/ColumnSizingState.d.ts +8 -0
  279. package/dist/Components/Table/Types/Columns/ColumnType.d.ts +17 -0
  280. package/dist/Components/Table/Types/Columns/ColumnType.js +18 -1
  281. package/dist/Components/Table/Types/Columns/DateColumn.d.ts +6 -0
  282. package/dist/Components/Table/Types/Columns/DateTableCell.d.ts +7 -0
  283. package/dist/Components/Table/Types/Columns/Definitions/AccessorFn.d.ts +13 -0
  284. package/dist/Components/Table/Types/Columns/Definitions/Accessors/AccessorDef.svelte.d.ts +28 -3
  285. package/dist/Components/Table/Types/Columns/Definitions/Accessors/AccessorDef.svelte.js +15 -0
  286. package/dist/Components/Table/Types/Columns/Definitions/Accessors/AccessorType.d.ts +10 -0
  287. package/dist/Components/Table/Types/Columns/Definitions/Accessors/AccessorType.js +11 -1
  288. package/dist/Components/Table/Types/Columns/Definitions/Accessors/CheckboxDef.svelte.d.ts +23 -2
  289. package/dist/Components/Table/Types/Columns/Definitions/Accessors/CheckboxDef.svelte.js +9 -2
  290. package/dist/Components/Table/Types/Columns/Definitions/Accessors/DateInputDef.svelte.d.ts +21 -2
  291. package/dist/Components/Table/Types/Columns/Definitions/Accessors/DateInputDef.svelte.js +8 -0
  292. package/dist/Components/Table/Types/Columns/Definitions/Accessors/NumberInputDef.svelte.d.ts +29 -3
  293. package/dist/Components/Table/Types/Columns/Definitions/Accessors/NumberInputDef.svelte.js +11 -0
  294. package/dist/Components/Table/Types/Columns/Definitions/Accessors/QueryDef.svelte.d.ts +23 -0
  295. package/dist/Components/Table/Types/Columns/Definitions/Accessors/QueryDef.svelte.js +8 -0
  296. package/dist/Components/Table/Types/Columns/Definitions/Accessors/SelectDef.svelte.d.ts +31 -0
  297. package/dist/Components/Table/Types/Columns/Definitions/Accessors/SelectDef.svelte.js +17 -3
  298. package/dist/Components/Table/Types/Columns/Definitions/Accessors/TextInputDef.svelte.d.ts +21 -2
  299. package/dist/Components/Table/Types/Columns/Definitions/Accessors/TextInputDef.svelte.js +7 -0
  300. package/dist/Components/Table/Types/Columns/Definitions/Actions/ActionsDef.svelte.d.ts +29 -1
  301. package/dist/Components/Table/Types/Columns/Definitions/Actions/ActionsDef.svelte.js +17 -3
  302. package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.d.ts +122 -11
  303. package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.js +77 -11
  304. package/dist/Components/Table/Types/Columns/Definitions/ColumnDefSet.d.ts +9 -0
  305. package/dist/Components/Table/Types/Columns/Definitions/Display/AbstractDisplayDef.svelte.d.ts +41 -1
  306. package/dist/Components/Table/Types/Columns/Definitions/Display/AbstractDisplayDef.svelte.js +16 -0
  307. package/dist/Components/Table/Types/Columns/Definitions/Display/BubbleDef.svelte.d.ts +19 -1
  308. package/dist/Components/Table/Types/Columns/Definitions/Display/BubbleDef.svelte.js +12 -1
  309. package/dist/Components/Table/Types/Columns/Definitions/Display/DisplayDef.svelte.d.ts +11 -0
  310. package/dist/Components/Table/Types/Columns/Definitions/Display/DisplayDef.svelte.js +6 -3
  311. package/dist/Components/Table/Types/Columns/Definitions/Display/ProgressBarDef.svelte.d.ts +19 -0
  312. package/dist/Components/Table/Types/Columns/Definitions/Display/ProgressBarDef.svelte.js +25 -1
  313. package/dist/Components/Table/Types/Columns/Definitions/Display/ValidityDef.svelte.d.ts +26 -0
  314. package/dist/Components/Table/Types/Columns/Definitions/Display/ValidityDef.svelte.js +20 -5
  315. package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.d.ts +43 -7
  316. package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.js +18 -3
  317. package/dist/Components/Table/Types/Columns/Definitions/FilterOnly/FilterOnlyDef.svelte.d.ts +19 -5
  318. package/dist/Components/Table/Types/Columns/Definitions/FilterOnly/FilterOnlyDef.svelte.js +16 -5
  319. package/dist/Components/Table/Types/Columns/Definitions/JSONColumnDefSet.d.ts +14 -0
  320. package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.d.ts +34 -2
  321. package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.js +30 -7
  322. package/dist/Components/Table/Types/Columns/Definitions/ValidationFn.d.ts +17 -0
  323. package/dist/Components/Table/Types/Columns/DefsWrapper.d.ts +10 -0
  324. package/dist/Components/Table/Types/Columns/DisplayType.d.ts +13 -0
  325. package/dist/Components/Table/Types/Columns/DisplayType.js +14 -1
  326. package/dist/Components/Table/Types/Columns/ExpandColumn.d.ts +5 -0
  327. package/dist/Components/Table/Types/Columns/ExpandTableCell.d.ts +7 -0
  328. package/dist/Components/Table/Types/Columns/JSONColumnPinningState.d.ts +6 -0
  329. package/dist/Components/Table/Types/Columns/ProgressBarColumn.d.ts +5 -0
  330. package/dist/Components/Table/Types/Columns/ProgressBarTableCell.d.ts +9 -0
  331. package/dist/Components/Table/Types/Columns/TableActionButton.d.ts +12 -0
  332. package/dist/Components/Table/Types/Columns/TableCell.d.ts +16 -2
  333. package/dist/Components/Table/Types/Columns/VisibilityState.d.ts +12 -0
  334. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.d.ts +173 -0
  335. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.js +241 -0
  336. package/dist/Components/Table/Types/Context/FilteringState.svelte.d.ts +166 -0
  337. package/dist/Components/Table/Types/Context/FilteringState.svelte.js +180 -0
  338. package/dist/Components/Table/Types/Context/FocusState.svelte.d.ts +316 -0
  339. package/dist/Components/Table/Types/Context/FocusState.svelte.js +751 -0
  340. package/dist/Components/Table/Types/Context/ITable.d.ts +10 -0
  341. package/dist/Components/Table/Types/Context/PreferencesState.svelte.d.ts +242 -0
  342. package/dist/Components/Table/Types/Context/PreferencesState.svelte.js +317 -0
  343. package/dist/Components/Table/Types/Context/ReadonlyRowModel.d.ts +14 -3
  344. package/dist/Components/Table/Types/Context/Row.d.ts +18 -0
  345. package/dist/Components/Table/Types/Context/RowModel.d.ts +11 -0
  346. package/dist/Components/Table/Types/Context/SelectionState.svelte.d.ts +260 -0
  347. package/dist/Components/Table/Types/Context/SelectionState.svelte.js +613 -0
  348. package/dist/Components/Table/Types/Context/SortingState.svelte.d.ts +112 -0
  349. package/dist/Components/Table/Types/Context/SortingState.svelte.js +272 -0
  350. package/dist/Components/Table/Types/Context/TableContext.svelte.d.ts +547 -53
  351. package/dist/Components/Table/Types/Context/TableContext.svelte.js +959 -1259
  352. package/dist/Components/Table/Types/Context/TableInitOptions.d.ts +16 -0
  353. package/dist/Components/Table/Types/Context/TableSettings.d.ts +20 -0
  354. package/dist/Components/Table/Types/Context/VirtualisationState.svelte.d.ts +167 -0
  355. package/dist/Components/Table/Types/Context/VirtualisationState.svelte.js +226 -0
  356. package/dist/Components/Table/Types/Coordinates/CellCoordinates.d.ts +12 -0
  357. package/dist/Components/Table/Types/Coordinates/CellCoordinatesWithoutColumn.d.ts +5 -0
  358. package/dist/Components/Table/Types/Coordinates/ColumnIndices.d.ts +16 -2
  359. package/dist/Components/Table/Types/DataRepository/BuiltInRemoteRepository.svelte.d.ts +143 -0
  360. package/dist/Components/Table/Types/DataRepository/BuiltInRemoteRepository.svelte.js +454 -0
  361. package/dist/Components/Table/Types/DataRepository/IDataRepository.d.ts +21 -0
  362. package/dist/Components/Table/Types/DataRepository/ILocalDataRepository.d.ts +10 -0
  363. package/dist/Components/Table/Types/DataRepository/IRemoteDataRepository.d.ts +16 -0
  364. package/dist/Components/Table/Types/DataRepository/LocalTableDataRepository.svelte.d.ts +87 -3
  365. package/dist/Components/Table/Types/DataRepository/LocalTableDataRepository.svelte.js +112 -7
  366. package/dist/Components/Table/Types/DataRepository/SortingState.d.ts +12 -0
  367. package/dist/Components/Table/Types/DataRepository/TableDataRepository.svelte.d.ts +87 -2
  368. package/dist/Components/Table/Types/DataRepository/TableDataRepository.svelte.js +74 -1
  369. package/dist/Components/Table/Types/ExpandedPanel.d.ts +9 -0
  370. package/dist/Components/Table/Types/ExpandedPanel.js +10 -1
  371. package/dist/Components/Table/Types/Filtering/ColumnFiltersState.d.ts +17 -0
  372. package/dist/Components/Table/Types/Filtering/CustomFilter.d.ts +21 -0
  373. package/dist/Components/Table/Types/Filtering/CustomFilterType.d.ts +5 -0
  374. package/dist/Components/Table/Types/Filtering/CustomFilterType.js +6 -1
  375. package/dist/Components/Table/Types/Filtering/CustomRemoteFilters.d.ts +6 -0
  376. package/dist/Components/Table/Types/Filtering/FilterFn.d.ts +28 -0
  377. package/dist/Components/Table/Types/Filtering/FilterMode.d.ts +7 -0
  378. package/dist/Components/Table/Types/Filtering/FilterType.d.ts +8 -0
  379. package/dist/Components/Table/Types/Filtering/FilterType.js +9 -1
  380. package/dist/Components/Table/Types/Filtering/FilterValueType.d.ts +11 -0
  381. package/dist/Components/Table/Types/Filtering/FilterValueType.js +12 -1
  382. package/dist/Components/Table/Types/Filtering/HasCustomFilterFunction.d.ts +23 -2
  383. package/dist/Components/Table/Types/Filtering/NumberDateFilterMode.d.ts +19 -12
  384. package/dist/Components/Table/Types/Filtering/NumberDateFilterMode.js +18 -14
  385. package/dist/Components/Table/Types/Filtering/StringFilterMode.d.ts +22 -15
  386. package/dist/Components/Table/Types/Filtering/StringFilterMode.js +22 -16
  387. package/dist/Components/Table/Types/Focus/MultiFocusEdge.d.ts +8 -0
  388. package/dist/Components/Table/Types/Focus/MultiFocusEdge.js +9 -1
  389. package/dist/Components/Table/Types/Focus/TableFocusDetails.svelte.d.ts +22 -0
  390. package/dist/Components/Table/Types/Focus/TableFocusDetails.svelte.js +22 -0
  391. package/dist/Components/Table/Types/Header.d.ts +8 -0
  392. package/dist/Components/Table/Types/HeaderGroup.d.ts +7 -0
  393. package/dist/Components/Table/Types/Layout/CellPinning.d.ts +1 -0
  394. package/dist/Components/Table/Types/Layout/CellPinning.js +8 -0
  395. package/dist/Components/Table/Types/OnChangeFn.d.ts +11 -0
  396. package/dist/Components/Table/Types/Pagination/PaginationState.d.ts +9 -0
  397. package/dist/Components/Table/Types/Pagination/PaginationType.d.ts +10 -0
  398. package/dist/Components/Table/Types/Pagination/PaginationType.js +11 -1
  399. package/dist/Components/Table/Types/Public/AnyEditEvent.d.ts +30 -0
  400. package/dist/Components/Table/Types/Public/AnyEditEvent.js +1 -0
  401. package/dist/Components/Table/Types/Public/BaseColumnOptions.d.ts +44 -0
  402. package/dist/Components/Table/Types/Public/BaseColumnOptions.js +1 -0
  403. package/dist/Components/Table/Types/Public/ColumnFactory.d.ts +35 -0
  404. package/dist/Components/Table/Types/Public/ColumnFactory.js +1 -0
  405. package/dist/Components/Table/Types/Public/ColumnOptions.d.ts +238 -0
  406. package/dist/Components/Table/Types/Public/ColumnOptions.js +1 -0
  407. package/dist/Components/Table/Types/Public/ColumnSpec.d.ts +39 -0
  408. package/dist/Components/Table/Types/Public/ColumnSpec.js +1 -0
  409. package/dist/Components/Table/Types/Public/CreateTableOptions.d.ts +87 -0
  410. package/dist/Components/Table/Types/Public/CreateTableOptions.js +1 -0
  411. package/dist/Components/Table/Types/Public/KeyConstraints.d.ts +42 -0
  412. package/dist/Components/Table/Types/Public/KeyConstraints.js +22 -0
  413. package/dist/Components/Table/Types/Public/RowSource.d.ts +87 -0
  414. package/dist/Components/Table/Types/Public/RowSource.js +1 -0
  415. package/dist/Components/Table/Types/Public/SelectionOptions.d.ts +26 -0
  416. package/dist/Components/Table/Types/Public/SelectionOptions.js +1 -0
  417. package/dist/Components/Table/Types/Public/Table.d.ts +39 -0
  418. package/dist/Components/Table/Types/Public/Table.js +1 -0
  419. package/dist/Components/Table/Types/Public/TableInitialState.d.ts +26 -0
  420. package/dist/Components/Table/Types/Public/TableInitialState.js +1 -0
  421. package/dist/Components/Table/Types/Public/TablePersistence.d.ts +38 -0
  422. package/dist/Components/Table/Types/Public/TablePersistence.js +1 -0
  423. package/dist/Components/Table/Types/Public/TableSubApis.d.ts +157 -0
  424. package/dist/Components/Table/Types/Public/TableSubApis.js +1 -0
  425. package/dist/Components/Table/Types/Public/index.d.ts +20 -0
  426. package/dist/Components/Table/Types/Public/index.js +8 -0
  427. package/dist/Components/Table/Types/Toolbar/ToolbarPosition.d.ts +4 -0
  428. package/dist/Components/Table/Types/Toolbar/ToolbarPosition.js +5 -1
  429. package/dist/Components/Table/createTable.svelte.d.ts +53 -0
  430. package/dist/Components/Table/createTable.svelte.js +269 -0
  431. package/dist/Components/Test/TestContextConsumer.svelte +13 -13
  432. package/dist/Components/TextArea.svelte +127 -108
  433. package/dist/Components/TextAreaProps.d.ts +37 -0
  434. package/dist/Components/TextInput.svelte +166 -147
  435. package/dist/Components/TextInputProps.d.ts +58 -1
  436. package/dist/Components/ThreeStateRadio.svelte +140 -143
  437. package/dist/Components/ThreeStateRadioProps.d.ts +29 -1
  438. package/dist/Components/Touch/BoolCard.svelte +45 -52
  439. package/dist/Components/Touch/BoolCard.svelte.d.ts +1 -10
  440. package/dist/Components/Touch/BoolCardProps.d.ts +21 -0
  441. package/dist/Components/Touch/BoolCardProps.js +1 -0
  442. package/dist/Components/Touch/DateModal.svelte +209 -217
  443. package/dist/Components/Touch/DateModal.svelte.d.ts +1 -17
  444. package/dist/Components/Touch/DateModalProps.d.ts +36 -0
  445. package/dist/Components/Touch/DateModalProps.js +1 -0
  446. package/dist/Components/Touch/EditCard.svelte +59 -52
  447. package/dist/Components/Touch/EditCardProps.d.ts +21 -0
  448. package/dist/Components/Touch/InfoCard.svelte +35 -28
  449. package/dist/Components/Touch/InfoCardProps.d.ts +9 -0
  450. package/dist/Components/Touch/InfoTextCard.svelte +28 -21
  451. package/dist/Components/Touch/InfoTextCardProps.d.ts +8 -0
  452. package/dist/Components/Touch/ModalExplanation.svelte +17 -20
  453. package/dist/Components/Touch/ModalExplanation.svelte.d.ts +1 -6
  454. package/dist/Components/Touch/ModalExplanationProps.d.ts +22 -0
  455. package/dist/Components/Touch/ModalExplanationProps.js +1 -0
  456. package/dist/Components/Touch/NumberKeyboard.svelte +225 -268
  457. package/dist/Components/Touch/NumberKeyboardProps.d.ts +19 -0
  458. package/dist/Components/Touch/NumberModal.svelte +79 -69
  459. package/dist/Components/Touch/NumberModalProps.d.ts +23 -1
  460. package/dist/Components/Touch/PlusMinusCard.svelte +48 -52
  461. package/dist/Components/Touch/PlusMinusCard.svelte.d.ts +1 -6
  462. package/dist/Components/Touch/PlusMinusCardProps.d.ts +18 -0
  463. package/dist/Components/Touch/PlusMinusCardProps.js +1 -0
  464. package/dist/Components/Touch/SelectModal.svelte +123 -105
  465. package/dist/Components/Touch/SelectModalProps.d.ts +22 -1
  466. package/dist/Components/Touch/SummaryCard.svelte +107 -65
  467. package/dist/Components/Touch/SummaryCardProps.d.ts +16 -3
  468. package/dist/Components/Touch/TextAreaModal.svelte +95 -86
  469. package/dist/Components/Touch/TextAreaModalProps.d.ts +21 -0
  470. package/dist/Components/Touch/TextModal.svelte +106 -123
  471. package/dist/Components/Touch/TextModalProps.d.ts +28 -1
  472. package/dist/Components/Touch/TouchCard.svelte +34 -72
  473. package/dist/Components/Touch/TouchCardProps.d.ts +21 -0
  474. package/dist/Components/Touch/TouchModal.svelte +265 -259
  475. package/dist/Components/Touch/TouchModalProps.d.ts +35 -1
  476. package/dist/Components/Touch/UtilityButton.svelte +116 -98
  477. package/dist/Components/Touch/UtilityButtonProps.d.ts +16 -0
  478. package/dist/Components/VerticalTabGroup.svelte +80 -102
  479. package/dist/Components/VerticalTabGroupButton.svelte +59 -98
  480. package/dist/Components/VerticalTabGroupProps.d.ts +15 -2
  481. package/dist/Contexts/AlertContext.svelte.d.ts +92 -8
  482. package/dist/Contexts/AlertContext.svelte.js +69 -6
  483. package/dist/Contexts/ContextMenuContext.svelte.d.ts +54 -8
  484. package/dist/Contexts/ContextMenuContext.svelte.js +48 -2
  485. package/dist/Contexts/TestContext.svelte.d.ts +27 -1
  486. package/dist/Contexts/TestContext.svelte.js +29 -1
  487. package/dist/Contexts/ToastContext.svelte.d.ts +29 -1
  488. package/dist/Contexts/ToastContext.svelte.js +93 -24
  489. package/dist/Contexts/TooltipContext.svelte.d.ts +68 -1
  490. package/dist/Contexts/TooltipContext.svelte.js +70 -7
  491. package/dist/Contexts/UntypedInputContext.svelte.d.ts +54 -5
  492. package/dist/Contexts/UntypedInputContext.svelte.js +87 -34
  493. package/dist/Delay/Delay.js +1 -1
  494. package/dist/Helpers/AsyncFlag.svelte.d.ts +39 -0
  495. package/dist/Helpers/AsyncFlag.svelte.js +72 -0
  496. package/dist/Helpers/Datamatrix.d.ts +19 -10
  497. package/dist/Helpers/Datamatrix.js +291 -2
  498. package/dist/Helpers/Helpers.svelte.d.ts +252 -17
  499. package/dist/Helpers/Helpers.svelte.js +271 -60
  500. package/dist/Helpers/INamed.d.ts +6 -0
  501. package/dist/Helpers/Storage.d.ts +67 -0
  502. package/dist/Helpers/Storage.js +138 -0
  503. package/dist/Helpers/dropdownPortal.d.ts +52 -0
  504. package/dist/Helpers/dropdownPortal.js +232 -0
  505. package/dist/Helpers/dropdownPortal.svelte.d.ts +2 -0
  506. package/dist/Helpers/dropdownPortal.svelte.js +5 -0
  507. package/dist/Helpers/memoiseByRow.d.ts +45 -0
  508. package/dist/Helpers/memoiseByRow.js +78 -0
  509. package/dist/Helpers/prefersReducedMotion.svelte.d.ts +27 -0
  510. package/dist/Helpers/prefersReducedMotion.svelte.js +18 -0
  511. package/dist/Helpers/useTooltip.svelte.d.ts +53 -0
  512. package/dist/Helpers/useTooltip.svelte.js +62 -0
  513. package/dist/Types/Examples/ExampleModal.svelte +35 -34
  514. package/dist/Types/Examples/ExampleSelectEnum.js +1 -1
  515. package/dist/Types/Internal/AsyncFlag.d.ts +7 -0
  516. package/dist/Types/Internal/AsyncFlag.js +1 -0
  517. package/dist/Types/Internal/BigRadioOption.d.ts +8 -0
  518. package/dist/Types/Internal/ButtonProp.d.ts +24 -2
  519. package/dist/Types/Internal/CallbackButton.d.ts +12 -0
  520. package/dist/Types/Internal/DateChange.d.ts +8 -0
  521. package/dist/Types/Internal/DatePickerType.d.ts +5 -0
  522. package/dist/Types/Internal/DatePickerType.js +6 -1
  523. package/dist/Types/Internal/IdentifiableToastMessage.d.ts +8 -0
  524. package/dist/Types/Internal/InputVariant.d.ts +5 -0
  525. package/dist/Types/Internal/InputVariant.js +6 -1
  526. package/dist/Types/Internal/Misc.d.ts +91 -0
  527. package/dist/Types/Internal/RefWrapper.d.ts +6 -0
  528. package/dist/Types/Internal/SelectOption.d.ts +8 -0
  529. package/dist/Types/Internal/Shape.d.ts +1 -0
  530. package/dist/Types/Internal/Shape.js +2 -1
  531. package/dist/Types/Internal/Side.d.ts +1 -0
  532. package/dist/Types/Internal/Side.js +2 -1
  533. package/dist/Types/Internal/Size.d.ts +5 -0
  534. package/dist/Types/Internal/Size.js +6 -1
  535. package/dist/Types/Internal/TabOption.d.ts +15 -5
  536. package/dist/Types/Internal/Ternary.d.ts +6 -0
  537. package/dist/Types/Internal/Ternary.js +7 -1
  538. package/dist/Types/Internal/ToastMessage.d.ts +28 -0
  539. package/dist/Types/Internal/Validity.d.ts +6 -0
  540. package/dist/Types/Internal/Validity.js +7 -1
  541. package/dist/Types/Internal/ValidityWrapper.d.ts +8 -0
  542. package/dist/Types/Internal/Variant.d.ts +5 -0
  543. package/dist/Types/Internal/Variant.js +6 -1
  544. package/dist/index.d.ts +27 -82
  545. package/dist/index.js +24 -62
  546. package/dist/scss/_basics.scss +12 -12
  547. package/dist/scss/_colours.scss +134 -134
  548. package/dist/scss/_mixins.scss +3300 -3016
  549. package/dist/scss/_motion.scss +57 -0
  550. package/dist/scss/_sizing.scss +85 -69
  551. package/dist/scss/_transitions.scss +8 -8
  552. package/dist/scss/colours.js +2 -2
  553. package/dist/scss/sizing.d.ts +9 -0
  554. package/dist/scss/sizing.js +10 -1
  555. package/package.json +6 -2
@@ -1,5 +1,7 @@
1
1
  import { browser, dev } from '$app/environment';
2
- import { asyncAll, asyncForEach, asyncMap, devCatch, getCanvasContext, isKeyOf, } from '../../../../Helpers/Helpers.svelte.js';
2
+ import { asyncAll, asyncMap, devCatch, getCanvasContext, isKeyOf, } from '../../../../Helpers/Helpers.svelte.js';
3
+ import { PreferencesState } from './PreferencesState.svelte.js';
4
+ import { SelectionState } from './SelectionState.svelte.js';
3
5
  import { tick, untrack } from 'svelte';
4
6
  import { SvelteMap } from 'svelte/reactivity';
5
7
  import { ColumnType } from '../Columns/ColumnType.js';
@@ -13,17 +15,41 @@ import { BubbleDef } from '../Columns/Definitions/Display/BubbleDef.svelte.js';
13
15
  import { DisplayDef } from '../Columns/Definitions/Display/DisplayDef.svelte.js';
14
16
  import { ProgressBarDef } from '../Columns/Definitions/Display/ProgressBarDef.svelte.js';
15
17
  import { RowSelectionDef } from '../Columns/Definitions/RowSelection/RowSelectionDef.svelte.js';
18
+ import { SortingState } from './SortingState.svelte.js';
19
+ import { FilteringState } from './FilteringState.svelte.js';
20
+ import { ColumnLayoutState } from './ColumnLayoutState.svelte.js';
21
+ import { FocusState } from './FocusState.svelte.js';
22
+ import { VirtualisationState } from './VirtualisationState.svelte.js';
16
23
  import { PaginationType } from '../Pagination/PaginationType.js';
17
24
  import { ToolbarPosition } from '../Toolbar/ToolbarPosition.js';
18
25
  const SORTING_ICON_WIDTH = 24;
19
26
  const InternalSymbol = Symbol();
20
27
  const uncopyableColumns = new Set([
21
- // ColumnType.RowSelect, //row select is a special case and is handled by tableContext
28
+ // RowSelect column is a special case and is handled by tableContext directly
22
29
  ColumnType.Expand,
23
30
  ColumnType.Actions,
24
31
  ColumnType.FilterOnly,
25
32
  ColumnType.Custom,
26
33
  ]);
34
+ /**
35
+ * Default values applied by `TableContext.init` for any `TableInitOptions`
36
+ * field the consumer omits. Frozen so callers (notably `NestedTable`)
37
+ * can spread it without risking accidental shared-object mutation.
38
+ *
39
+ * Mirrors the default literals consumed inside `init`; both share this
40
+ * constant so the two cannot drift.
41
+ */
42
+ export const DEFAULT_TABLE_INIT_OPTIONS = Object.freeze({
43
+ allowCopy: true,
44
+ enableResize: true,
45
+ allowColumnReordering: true,
46
+ allowQuickFiltering: true,
47
+ showQuickFilterByDefault: true,
48
+ allowAdvancedFiltering: true,
49
+ toolbar: ToolbarPosition.Right,
50
+ displayFooter: true,
51
+ debug: false,
52
+ });
27
53
  export class TableContext {
28
54
  defaultColumnDefs;
29
55
  tableName;
@@ -40,8 +66,27 @@ export class TableContext {
40
66
  _onEditCell;
41
67
  debug;
42
68
  static identifier = Symbol();
69
+ /**
70
+ * Factory for `TableContext`. Coalesces every optional flag from the
71
+ * caller-supplied `options` against `DEFAULT_TABLE_INIT_OPTIONS` so
72
+ * the constructor receives a complete set of values. The constructor
73
+ * itself is `protected` and gated by an internal symbol to make it
74
+ * impossible to construct a `TableContext` without going through
75
+ * `init`.
76
+ */
43
77
  static init(defaultColumnDefs, tableName, dataRepo, options) {
44
- const context = new this(InternalSymbol, defaultColumnDefs, tableName, dataRepo, options.allowCopy ?? true, options.enableResize ?? true, options.allowColumnReordering ?? true, options.allowQuickFiltering ?? true, options.showQuickFilterByDefault ?? true, options.allowAdvancedFiltering ?? true, options.toolbar ?? ToolbarPosition.Right, options.displayFooter ?? true, options.selectionExtractor, options.getUserId, options.onEditCell, options.debug ?? false);
78
+ 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.
89
+ options.selectionExtractor ?? ((row) => row), options.getUserId, options.onEditCell, options.debug ?? DEFAULT_TABLE_INIT_OPTIONS.debug);
45
90
  return context;
46
91
  }
47
92
  // #region constructor
@@ -66,7 +111,6 @@ export class TableContext {
66
111
  throw new Error('This class may only be instantiated through the static init() method');
67
112
  }
68
113
  this._dataRepo = $state(dataRepo);
69
- // this.dataRepo.forceRefresh = this.forceVisibilityUpdate;
70
114
  $effect(() => {
71
115
  void this.currentPageData;
72
116
  // void this.selectedItems;
@@ -77,7 +121,6 @@ export class TableContext {
77
121
  this.resetHighlightedRows();
78
122
  })
79
123
  .catch(devCatch);
80
- // this.recalculateAllSelectedInCurrentPage();
81
124
  }
82
125
  });
83
126
  });
@@ -96,12 +139,11 @@ export class TableContext {
96
139
  // throw new Error(`filtered indices are not in a tracking context`);
97
140
  // }
98
141
  if (!this.dataRepo.filtering.length) {
99
- this._filteredIndices = data.map((_item, index) => {
142
+ this._filtering.setFilteredIndices(data.map((_item, index) => {
100
143
  return index;
101
- });
144
+ }));
102
145
  }
103
146
  else {
104
- // await asyncSleep(300);
105
147
  const filtered = [];
106
148
  for (let index = 0; index < data.length; ++index) {
107
149
  if (currentController.signal.aborted) {
@@ -133,7 +175,7 @@ export class TableContext {
133
175
  filtered.push(index);
134
176
  }
135
177
  }
136
- this._filteredIndices = filtered;
178
+ this._filtering.setFilteredIndices(filtered);
137
179
  }
138
180
  })().catch((e) => {
139
181
  devCatch(e);
@@ -144,7 +186,7 @@ export class TableContext {
144
186
  void this.dataRepo.data.length;
145
187
  void this.selectedItems;
146
188
  void this.paginationRepo?.pageStartIndex;
147
- const invalidValues = this._invalidValues;
189
+ const invalidValues = this.invalidValues;
148
190
  const allSelectedInCurrentPage = [...this.selectedItems.keys()];
149
191
  const currentIncrement = ++lastIncrement;
150
192
  void this.allSelectedInCurrentPage;
@@ -165,9 +207,7 @@ export class TableContext {
165
207
  const current = (latest_pageColData = latest_pageColData + 1);
166
208
  if (!this.focusStart || !this.focusEnd) {
167
209
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
168
- this._focusedRowIndices = new Set();
169
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
170
- this._focusedColumnIds = new Set();
210
+ this._focus.setFocusedDerived(new Set(), new Set());
171
211
  return;
172
212
  }
173
213
  const { row: startRowIndexId } = this.focusStart;
@@ -178,9 +218,7 @@ export class TableContext {
178
218
  const endRowIndex = model.rows.findIndex((row) => row.visibleIndex === endRowIndexId);
179
219
  if (startRowIndex == -1 || endRowIndex == -1) {
180
220
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
181
- this._focusedRowIndices = new Set();
182
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
183
- this._focusedColumnIds = new Set();
221
+ this._focus.setFocusedDerived(new Set(), new Set());
184
222
  return;
185
223
  }
186
224
  const minRowIndex = Math.min(startRowIndex, endRowIndex);
@@ -199,8 +237,7 @@ export class TableContext {
199
237
  for (let i = minRowIndex; i <= maxRowIndex; i++) {
200
238
  indices.add(parseInt(model.rows[i].id));
201
239
  }
202
- this._focusedRowIndices = indices;
203
- this._focusedColumnIds = affectedColumns;
240
+ this._focus.setFocusedDerived(indices, affectedColumns);
204
241
  }
205
242
  });
206
243
  let latest_paginationRowModel = 0;
@@ -282,40 +319,36 @@ export class TableContext {
282
319
  .catch(devCatch);
283
320
  });
284
321
  $effect(() => {
285
- if (this.highlightedItems.size === 0) {
286
- this._allHighlighted = false;
322
+ if (this._selection.highlightedItems.size === 0) {
323
+ this._selection.setAllHighlighted(false);
287
324
  return;
288
325
  }
289
326
  const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
290
327
  const pageEndIndexExclusive = this.paginationRepo
291
328
  ? Math.min(this.paginationRepo.pageEndIndexExclusive, this.sortedData.length)
292
329
  : this.sortedData.length;
293
- const allHighlightedResult = this._areAllHighlighted(pageStartIndex, pageEndIndexExclusive);
330
+ const allHighlightedResult = this._selection.computeAllHighlightedInRange(pageStartIndex, pageEndIndexExclusive);
294
331
  if (allHighlightedResult instanceof Promise) {
295
332
  allHighlightedResult
296
333
  .then((flag) => {
297
- this._allHighlighted = flag;
334
+ this._selection.setAllHighlighted(flag);
298
335
  })
299
336
  .catch(devCatch);
300
337
  }
301
338
  else {
302
- this._allHighlighted = allHighlightedResult;
339
+ this._selection.setAllHighlighted(allHighlightedResult);
303
340
  }
304
341
  });
305
342
  if (defaultColumnDefs.remoteFilters) {
306
- this.remoteFilters = defaultColumnDefs.remoteFilters;
343
+ this._filtering.setRemoteFilters(defaultColumnDefs.remoteFilters);
307
344
  }
308
345
  if (hasPagination(dataRepo)) {
309
346
  this.paginationRepo = dataRepo;
310
- // this.tableOptions.onSortingChange = dataRepo.updateSorting;
311
- // this.tableOptions.manualSorting = dataRepo.manualSorting;
312
347
  }
313
- this.userId = $derived(getUserId());
314
348
  if (browser) {
315
349
  this._getUserDefaults();
316
350
  const removeInvalidPresets = () => {
317
- localStorage.removeItem(`${this.userId}-${tableName}-presets`);
318
- localStorage.removeItem(`${this.userId}-${tableName}-selected-preset-name`);
351
+ this._preferences.clearPresetsAndSelection();
319
352
  const visibilityState = {};
320
353
  this.columnDefs.forEach((def) => {
321
354
  if (def.defaultHidden || def.columnType === ColumnType.FilterOnly) {
@@ -323,7 +356,7 @@ export class TableContext {
323
356
  }
324
357
  });
325
358
  const defaultPreset = {
326
- name: 'default',
359
+ name: 'Default',
327
360
  columnDefs: this.columnDefs,
328
361
  sortingState: [],
329
362
  visibilityState,
@@ -331,16 +364,15 @@ export class TableContext {
331
364
  };
332
365
  this.selectedPreset = defaultPreset;
333
366
  this.columnVisibility = visibilityState;
334
- this.columnPresets = [...this.columnPresets, defaultPreset];
367
+ this._preferences.setPresets([...this.columnPresets, defaultPreset]);
335
368
  };
336
369
  this.setupDefaultColumnDefs()
337
370
  .then((defs) => {
338
371
  this.showQuickFiltering = this.showQuickFilterByDefault;
339
372
  this.isSelectable = defs.some((it) => it.id.startsWith('is-selectable-'));
340
373
  if (this.allowColumnReordering) {
341
- const savedPresets = localStorage.getItem(`${this.userId}-${tableName}-presets`);
342
- if (savedPresets) {
343
- const presets = JSON.parse(savedPresets);
374
+ const presets = this._preferences.loadPresets();
375
+ if (presets.length > 0) {
344
376
  if (presets[0] && areDefsEquivalent(defs, presets[0].columnDefs)) {
345
377
  return this.reassignDefsToPresets(presets).then((p) => p);
346
378
  }
@@ -356,7 +388,7 @@ export class TableContext {
356
388
  removeInvalidPresets();
357
389
  return;
358
390
  }
359
- const savedPresetName = localStorage.getItem(`${this.userId}-${tableName}-selected-preset-name`);
391
+ const savedPresetName = this._preferences.loadSelectedPresetName();
360
392
  if (savedPresetName) {
361
393
  const preset = presets.find((it) => it.name === savedPresetName);
362
394
  if (preset && areDefsEquivalent(this.columnDefs, preset.columnDefs)) {
@@ -366,13 +398,13 @@ export class TableContext {
366
398
  this.selectedPreset = preset;
367
399
  }
368
400
  else {
369
- localStorage.removeItem(`${this.userId}-${tableName}-selected-preset-name`);
401
+ localStorage.removeItem(this._preferences.storageKey('selected-preset-name'));
370
402
  this.selectedPreset = presets[0];
371
403
  if (presets.at(0)?.visibilityState) {
372
404
  this.columnVisibility = presets[0].visibilityState;
373
405
  }
374
406
  }
375
- this.columnPresets = presets;
407
+ this._preferences.setPresets(presets);
376
408
  }
377
409
  })
378
410
  .catch(devCatch);
@@ -380,7 +412,6 @@ export class TableContext {
380
412
  }
381
413
  // #endregion
382
414
  // #region stores
383
- userId;
384
415
  isSelectable = $state(false);
385
416
  _paginationRepo = $state();
386
417
  get paginationRepo() {
@@ -389,20 +420,17 @@ export class TableContext {
389
420
  set paginationRepo(v) {
390
421
  this._paginationRepo = v;
391
422
  }
392
- _showQuickFiltering = $state(false);
423
+ /**
424
+ * Delegating accessor preserved for backwards compatibility; reads /
425
+ * writes through to `_filtering.showQuickFiltering`. The sub-context
426
+ * owns the `allowQuickFiltering` gate so the setter logic stays
427
+ * identical to the pre-extraction implementation.
428
+ */
393
429
  get showQuickFiltering() {
394
- return this._showQuickFiltering;
430
+ return this._filtering.showQuickFiltering;
395
431
  }
396
432
  set showQuickFiltering(v) {
397
- if (this.debug) {
398
- if (!this.allowQuickFiltering) {
399
- console.log(`attempted to toggle quick filtering when allowQuickFiltering = false`);
400
- }
401
- else {
402
- console.log(`setting quick filtering = ${v}`);
403
- }
404
- }
405
- this._showQuickFiltering = this.allowQuickFiltering && v;
433
+ this._filtering.showQuickFiltering = v;
406
434
  }
407
435
  _dataRepo;
408
436
  get dataRepo() {
@@ -411,28 +439,49 @@ export class TableContext {
411
439
  set dataRepo(v) {
412
440
  this._dataRepo = v;
413
441
  }
414
- _remoteFilters = $state();
442
+ /**
443
+ * Delegating getter preserved for backwards compatibility. Reads
444
+ * through to `_filtering.remoteFilters`; the parent uses
445
+ * `_filtering.setRemoteFilters` during column-def setup to write the
446
+ * caller-supplied value.
447
+ */
415
448
  get remoteFilters() {
416
- return this._remoteFilters;
417
- }
418
- set remoteFilters(v) {
419
- this._remoteFilters = v;
449
+ return this._filtering.remoteFilters;
420
450
  }
421
- _showSelectedOnly = $state(false);
451
+ /**
452
+ * Delegating accessor preserved for backwards compatibility; reads /
453
+ * writes through to `_filtering.showSelectedOnly`. The sub-context
454
+ * preserves the idempotency short-circuit (re-assigning the same
455
+ * value is a no-op).
456
+ */
422
457
  get showSelectedOnly() {
423
- return this._showSelectedOnly;
458
+ return this._filtering.showSelectedOnly;
424
459
  }
425
460
  set showSelectedOnly(v) {
426
- if (v === this._showSelectedOnly) {
427
- return;
428
- }
429
- this._showSelectedOnly = v;
461
+ this._filtering.showSelectedOnly = v;
462
+ }
463
+ /**
464
+ * Delegating getters that relay the per-type `FilterFn` from
465
+ * `_filtering`. The sub-context's `$derived` chain reads through
466
+ * `paginationRepo` via the thunk so the source of truth stays
467
+ * with the data repository; consumers that bind these getters
468
+ * (notably `setupDefaultColumnDefs`) need no migration.
469
+ */
470
+ get dateFilter() {
471
+ return this._filtering.dateFilter;
472
+ }
473
+ get numberFilter() {
474
+ return this._filtering.numberFilter;
475
+ }
476
+ get selectFilter() {
477
+ return this._filtering.selectFilter;
478
+ }
479
+ get boolFilter() {
480
+ return this._filtering.boolFilter;
481
+ }
482
+ get stringFilter() {
483
+ return this._filtering.stringFilter;
430
484
  }
431
- dateFilter = $derived(this.paginationRepo?.dateFilter);
432
- numberFilter = $derived(this.paginationRepo?.numberFilter);
433
- selectFilter = $derived(this.paginationRepo?.selectFilter);
434
- boolFilter = $derived(this.paginationRepo?.boolFilter);
435
- stringFilter = $derived(this.paginationRepo?.stringFilter);
436
485
  _limitFooter = $state(false);
437
486
  get limitFooter() {
438
487
  return this._limitFooter;
@@ -447,69 +496,104 @@ export class TableContext {
447
496
  set dragTarget(v) {
448
497
  this._dragTarget = v;
449
498
  }
450
- _columnDefs = $state([]);
499
+ /**
500
+ * Delegating accessor preserved for backwards compatibility. The
501
+ * actual `$state` slot lives on `_columnLayout`; this getter / setter
502
+ * pair forwards reads and writes so existing consumers need no
503
+ * migration. See `_columnLayout` below for the sub-context.
504
+ */
451
505
  get columnDefs() {
452
- return this._columnDefs;
506
+ return this._columnLayout.columnDefs;
453
507
  }
454
508
  set columnDefs(v) {
455
- this._columnDefs = v;
509
+ this._columnLayout.columnDefs = v;
456
510
  }
457
- _columnPresets = $state([]);
511
+ /**
512
+ * Delegating accessor preserved for backwards compatibility. The
513
+ * actual `$state` slot lives on `_preferences._columnPresets`
514
+ * (Phase 6.4). The setter is private because the consumer-facing
515
+ * write path goes through `saveColumnPreset` (which also persists
516
+ * the list to localStorage); internal call sites that need to
517
+ * mutate the list directly call `_preferences.setPresets(...)`.
518
+ */
458
519
  get columnPresets() {
459
- return this._columnPresets;
520
+ return this._preferences.columnPresets;
460
521
  }
461
- set columnPresets(v) {
462
- this._columnPresets = v;
463
- }
464
- //FIXME why was this allowed to be undefined?
465
- _selectedPreset = $state();
522
+ /**
523
+ * Delegating accessor preserved for backwards compatibility. The
524
+ * actual `$state` slot lives on `_preferences._selectedPreset`
525
+ * (Phase 6.4). Reads / writes through to the sub-context.
526
+ */
466
527
  get selectedPreset() {
467
- return this._selectedPreset;
528
+ return this._preferences.selectedPreset;
468
529
  }
469
530
  set selectedPreset(v) {
470
- this._selectedPreset = v;
531
+ this._preferences.selectedPreset = v;
471
532
  }
472
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
473
- _focusedRowIndices = $state(new Set());
533
+ /**
534
+ * Delegating accessor preserved for backwards compatibility. The
535
+ * `$state` slot lives on `_focus._focusedRowIndices` (Phase 6.6).
536
+ * Populated by the parent's focus-derived `$effect` through
537
+ * `_focus.setFocusedDerived(rows, cols)`.
538
+ */
474
539
  get focusedRowIndices() {
475
- return this._focusedRowIndices;
540
+ return this._focus.focusedRowIndices;
476
541
  }
477
- _highlightedItems = $state(new SvelteMap());
542
+ /**
543
+ * Delegating accessor preserved for backwards compatibility. The
544
+ * actual `$state` slot lives on `_selection._highlightedItems`
545
+ * (Phase 6.5).
546
+ */
478
547
  get highlightedItems() {
479
- return this._highlightedItems;
548
+ return this._selection.highlightedItems;
480
549
  }
481
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
482
- _focusedColumnIds = $state(new Set());
550
+ /**
551
+ * Delegating accessor preserved for backwards compatibility. The
552
+ * `$state` slot lives on `_focus._focusedColumnIds` (Phase 6.6).
553
+ */
483
554
  get focusedColumnIds() {
484
- return this._focusedColumnIds;
555
+ return this._focus.focusedColumnIds;
485
556
  }
486
- _isMousingDown = $state(false);
557
+ /**
558
+ * Delegating accessor preserved for backwards compatibility. The
559
+ * `$state` slot lives on `_focus._isMousingDown` (Phase 6.6).
560
+ */
487
561
  get isMousingDown() {
488
- return this._isMousingDown;
562
+ return this._focus.isMousingDown;
489
563
  }
490
564
  set isMousingDown(v) {
491
- this._isMousingDown = v;
565
+ this._focus.isMousingDown = v;
492
566
  }
493
- _focusStart = $state(null);
567
+ /**
568
+ * Delegating accessor preserved for backwards compatibility. The
569
+ * `$state` slot lives on `_focus._focusStart` (Phase 6.6). The
570
+ * setter remains read-only at this surface; the write path is
571
+ * `moveFocus` / `unfocus` / `tabForward` / `tabBackward` /
572
+ * `endFocus` / `setFocusEnd` / `startFocus` (all delegated below).
573
+ */
494
574
  get focusStart() {
495
- return this._focusStart;
575
+ return this._focus.focusStart;
496
576
  }
497
- set focusStart(v) {
498
- this._focusStart = v;
499
- }
500
- _focusStartIsBeingEdited = $state(false);
577
+ /**
578
+ * Delegating accessor preserved for backwards compatibility. The
579
+ * `$state` slot lives on `_focus._focusStartIsBeingEdited`
580
+ * (Phase 6.6).
581
+ */
501
582
  get focusStartIsBeingEdited() {
502
- return this._focusStartIsBeingEdited;
583
+ return this._focus.focusStartIsBeingEdited;
503
584
  }
504
585
  set focusStartIsBeingEdited(v) {
505
- this._focusStartIsBeingEdited = v;
586
+ this._focus.focusStartIsBeingEdited = v;
506
587
  }
507
- _focusEnd = $state(null);
588
+ /**
589
+ * Delegating accessor preserved for backwards compatibility. The
590
+ * `$state` slot lives on `_focus._focusEnd` (Phase 6.6).
591
+ */
508
592
  get focusEnd() {
509
- return this._focusEnd;
593
+ return this._focus.focusEnd;
510
594
  }
511
595
  set focusEnd(v) {
512
- this._focusEnd = v;
596
+ this._focus.focusEnd = v;
513
597
  }
514
598
  _singleClickEditing = $state(false);
515
599
  get singleClickEditing() {
@@ -553,26 +637,41 @@ export class TableContext {
553
637
  set commaSeparatedCopy(v) {
554
638
  this._commaSeparatedCopy = v;
555
639
  }
556
- _columnSizing = $state({});
640
+ /**
641
+ * Delegating accessor preserved for backwards compatibility. Reads
642
+ * through to `_columnLayout.columnSizing`. Per-key mutations (e.g.
643
+ * `this.columnSizing[id] = w` in `autoResize`) work because the
644
+ * underlying `$state` slot is a Svelte proxy.
645
+ */
557
646
  get columnSizing() {
558
- return this._columnSizing;
647
+ return this._columnLayout.columnSizing;
559
648
  }
560
649
  set columnSizing(v) {
561
- this._columnSizing = v;
650
+ this._columnLayout.columnSizing = v;
562
651
  }
563
- _columnPinning = $state({});
652
+ /**
653
+ * Delegating accessor preserved for backwards compatibility. Reads
654
+ * through to `_columnLayout.columnPinning`. The `setColumnPinning`
655
+ * updater below honours the `allowColumnReordering` master gate; the
656
+ * direct setter does not, by design (used for preset switches and
657
+ * the init flow where the gate has already been checked).
658
+ */
564
659
  get columnPinning() {
565
- return this._columnPinning;
660
+ return this._columnLayout.columnPinning;
566
661
  }
567
662
  set columnPinning(v) {
568
- this._columnPinning = v;
663
+ this._columnLayout.columnPinning = v;
569
664
  }
570
- _columnVisibility = $state({});
665
+ /**
666
+ * Delegating accessor preserved for backwards compatibility. Reads
667
+ * through to `_columnLayout.columnVisibility`. Tri-state semantics
668
+ * (`true` / `false` / `undefined`) are preserved.
669
+ */
571
670
  get columnVisibility() {
572
- return this._columnVisibility;
671
+ return this._columnLayout.columnVisibility;
573
672
  }
574
673
  set columnVisibility(v) {
575
- this._columnVisibility = v;
674
+ this._columnLayout.columnVisibility = v;
576
675
  }
577
676
  _openModalCoordinates = $state(null);
578
677
  get openModalCoordinates() {
@@ -581,131 +680,57 @@ export class TableContext {
581
680
  set openModalCoordinates(v) {
582
681
  this._openModalCoordinates = v;
583
682
  }
584
- _allSelectedAsync = $state(false);
585
- _allSelectedInCurrentPage = $derived.by(() => {
586
- void this.columnDefs;
587
- if (this.selectedItems.size === 0) {
588
- return false;
589
- }
590
- const sortedData = this.sortedData;
591
- const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
592
- const pageEndIndexExclusive = this.paginationRepo
593
- ? Math.min(this.paginationRepo.pageEndIndexExclusive, sortedData.length)
594
- : sortedData.length;
595
- const selectableMethod = this.columnDefs.find((it) => it instanceof RowSelectionDef)?.isSelectable;
596
- const result = this.allSelectedBySelectableMethod(pageStartIndex, pageEndIndexExclusive, selectableMethod);
597
- if (result instanceof Promise) {
598
- result
599
- .then((flag) => {
600
- this._allSelectedAsync = flag;
601
- })
602
- .catch(devCatch);
603
- return this._allSelectedAsync;
604
- }
605
- return result;
606
- });
607
- get allSelectedInCurrentPage() {
608
- return this._allSelectedInCurrentPage;
683
+ // Per-table identifier of the ActionsCell that currently has its popup open.
684
+ // Lives on the context rather than at module scope so multiple tables on the
685
+ // same page do not share open-popup state (security: cross-table state
686
+ // poisoning).
687
+ _shownActionsPopup = $state();
688
+ get shownActionsPopup() {
689
+ return this._shownActionsPopup;
609
690
  }
610
- allSelectedBySelectableMethod(pageStartIndex, pageEndIndexExclusive, selectableMethod) {
611
- const sortedData = this.sortedData;
612
- const emptyPromise = new Promise(() => { });
613
- if (selectableMethod) {
614
- const pending = [];
615
- for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
616
- const item = sortedData[i];
617
- const relIdx = i - pageStartIndex;
618
- const raw = this._cachedKey(item);
619
- const checkUnselected = (key) => {
620
- const selectable = selectableMethod(item, relIdx);
621
- if (selectable instanceof Promise) {
622
- return selectable.then((s) => s && !this.selectedItems.has(key));
623
- }
624
- return selectable && !this.selectedItems.has(key);
625
- };
626
- const r = raw instanceof Promise ? raw.then(checkUnselected) : checkUnselected(raw);
627
- if (r === true) {
628
- return false;
629
- }
630
- if (r instanceof Promise) {
631
- pending.push(r);
632
- }
633
- }
634
- if (pending.length === 0) {
635
- return true;
636
- }
637
- return Promise.race([
638
- Promise.race(pending.map(async (p) => ((await p) ? false : emptyPromise))),
639
- Promise.all(pending).then((results) => {
640
- return !results.some(Boolean);
641
- }),
642
- ]);
643
- }
644
- else {
645
- const pending = [];
646
- for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
647
- const raw = this._cachedKey(sortedData[i]);
648
- if (raw instanceof Promise) {
649
- pending.push(raw.then((key) => !this.selectedItems.has(key)));
650
- }
651
- else if (!this.selectedItems.has(raw)) {
652
- return false;
653
- }
654
- }
655
- if (pending.length === 0) {
656
- return true;
657
- }
658
- return Promise.race([
659
- Promise.race(pending.map(async (p) => ((await p) ? false : emptyPromise))),
660
- Promise.all(pending).then((results) => !results.some(Boolean)),
661
- ]);
662
- }
691
+ set shownActionsPopup(v) {
692
+ this._shownActionsPopup = v;
663
693
  }
664
- _areAllHighlighted(pageStartIndex, pageEndIndexExclusive) {
665
- const sortedData = this.sortedData;
666
- for (let index = pageStartIndex; index < pageEndIndexExclusive; index++) {
667
- const item = sortedData[index];
668
- const raw = this._cachedKey(item);
669
- if (raw instanceof Promise) {
670
- return raw.then(async (key) => {
671
- if (!this.highlightedItems.has(key)) {
672
- return false;
673
- }
674
- for (let j = index + 1; j < pageEndIndexExclusive; j++) {
675
- const r = this._cachedKey(sortedData[j]);
676
- const k = r instanceof Promise ? await r : r;
677
- if (!this.highlightedItems.has(k)) {
678
- return false;
679
- }
680
- }
681
- return true;
682
- });
683
- }
684
- if (!this.highlightedItems.has(raw)) {
685
- return false;
686
- }
687
- }
688
- return true;
694
+ /**
695
+ * Delegating getter preserved for backwards compatibility. The
696
+ * actual `$derived` chain (and the async-result cache) live on
697
+ * `_selection.allSelectedInCurrentPage` (Phase 6.5).
698
+ */
699
+ get allSelectedInCurrentPage() {
700
+ return this._selection.allSelectedInCurrentPage;
689
701
  }
690
- _allHighlighted = $state(false);
702
+ /**
703
+ * Delegating getter preserved for backwards compatibility. Reads
704
+ * through to `_selection.allHighlighted`; the value is published by
705
+ * the `_allHighlightedEffect` below which lives on this class
706
+ * because it reads `paginationRepo.pageStartIndex` / `pageEndIndexExclusive`
707
+ * directly.
708
+ */
691
709
  get allHighlighted() {
692
- return this._allHighlighted;
710
+ return this._selection.allHighlighted;
693
711
  }
694
- _hasHighlightedItems = $derived(this.highlightedItems.size > 0);
712
+ /**
713
+ * Delegating getter preserved for backwards compatibility. The
714
+ * `$derived` lives on `_selection.hasHighlightedItems` (Phase 6.5).
715
+ */
695
716
  get hasHighlightedItems() {
696
- return this._hasHighlightedItems;
717
+ return this._selection.hasHighlightedItems;
697
718
  }
698
- //type of entries depends on the value extractor in the column def
699
- _selectedItems = $state(new SvelteMap());
719
+ /**
720
+ * Delegating accessors preserved for backwards compatibility. The
721
+ * `$state` slots live on `_selection` (Phase 6.5). `selectedItems`
722
+ * is read-only at this surface; the consumer-facing writers are
723
+ * `toggleSelected` / `setSelected` / `resetSelectedItems` /
724
+ * `setSelectionMap` below.
725
+ */
700
726
  get selectedItems() {
701
- return this._selectedItems;
727
+ return this._selection.selectedItems;
702
728
  }
703
- _invalidValues = $state(new SvelteMap());
704
729
  get invalidValues() {
705
- return this._invalidValues;
730
+ return this._selection.invalidValues;
706
731
  }
707
732
  set invalidValues(v) {
708
- this._invalidValues = v;
733
+ this._selection.invalidValues = v;
709
734
  }
710
735
  _hasInvalidSelectedItems = $state(false);
711
736
  get hasInvalidSelectedItems() {
@@ -714,116 +739,292 @@ export class TableContext {
714
739
  set hasInvalidSelectedItems(v) {
715
740
  this._hasInvalidSelectedItems = v;
716
741
  }
717
- // FIXME decide
718
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
719
- columnDefsById = $derived(new Map(this.columnDefs.map((it) => [it.id, it])));
720
- getSortingFn = (id, desc) => {
721
- if (this.dataRepo.data.length == 0) {
722
- return undefined;
723
- }
724
- const def = this.columnDefsById.get(id);
725
- if (!def) {
726
- return undefined;
727
- }
728
- if (def.getSortingFn != undefined) {
729
- return def.getSortingFn(desc);
730
- }
731
- if (def instanceof DisplayDef ||
732
- def instanceof TextInputDef ||
733
- def instanceof NumberInputDef ||
734
- def instanceof DateInputDef) {
735
- return comparableSortingFn(id, desc);
736
- }
737
- if (def instanceof CheckboxDef) {
738
- return checkboxSortingFn(id, desc);
739
- }
740
- if (def instanceof SelectDef) {
741
- return comparableSortingFn(id, desc);
742
- }
743
- if (def instanceof ProgressBarDef) {
744
- return progressSortingFn(id, desc);
745
- }
746
- // if (dev) {
747
- // console.warn('Unhandled def type ' + id, def);
748
- // }
749
- return comparableSortingFn(id, desc);
750
- };
751
- _filteredIndices = $state([]);
742
+ /**
743
+ * Delegating accessor preserved for backwards compatibility. The
744
+ * id->def lookup map is owned by `_columnLayout.columnDefsById`
745
+ * (Phase 6.3); previously this was a `$derived` private field on
746
+ * `TableContext`. Made `public get` (instead of the previous
747
+ * `private` field) so the dependent sub-contexts (`SortingState`)
748
+ * and helpers (`generateTableOptions`) can read it through the
749
+ * legacy surface without piercing the sub-context boundary.
750
+ */
751
+ get columnDefsById() {
752
+ return this._columnLayout.columnDefsById;
753
+ }
754
+ /**
755
+ * Phase 6.3 sub-context that owns the column-layout state:
756
+ * `columnDefs`, `columnSizing`, `columnPinning`, `columnVisibility`,
757
+ * the `columnDefsById` reverse-lookup map, the `visualColumns`
758
+ * cached array (and the paired `visualColumnIndexById` O(1) lookup),
759
+ * the gated `setColumnPinning` updater, the `updateDefsBasedOnPinning`
760
+ * helper, and the `setNewPinningState` helper.
761
+ *
762
+ * Dependencies are passed as thunks so the sub-context never holds
763
+ * a reference to the parent. The `allowColumnReordering` thunk
764
+ * gates the public `setColumnPinning` updater; the `debug` thunk
765
+ * mirrors the parent's logger gate.
766
+ */
767
+ _columnLayout = new ColumnLayoutState({
768
+ getAllowColumnReordering: () => this.allowColumnReordering,
769
+ getDebug: () => this.debug,
770
+ });
771
+ /**
772
+ * Phase 6.2 sub-context that owns the filtering-related state: the
773
+ * per-row filter result (`filteredIndices`), the `filteredData`
774
+ * derived chain, the user-facing toggles (`showQuickFiltering`,
775
+ * `showSelectedOnly`), the per-type `FilterFn` relays, the
776
+ * remote-filter slot, and the `updateFilters` `OnChangeFn` slot.
777
+ *
778
+ * Dependencies are passed as thunks so the sub-context never holds
779
+ * a reference to the parent; the population effect for
780
+ * `_filteredIndices` stays in `TableContext` because it calls
781
+ * `this.createRow` (parent concern) - the effect publishes its
782
+ * result via `this._filtering.setFilteredIndices`.
783
+ */
784
+ _filtering = new FilteringState({
785
+ getRepoData: () => this.dataRepo.data,
786
+ getPaginationType: () => this.paginationRepo?.paginationType,
787
+ getDateFilterFn: () => this.paginationRepo?.dateFilter,
788
+ getNumberFilterFn: () => this.paginationRepo?.numberFilter,
789
+ getSelectFilterFn: () => this.paginationRepo?.selectFilter,
790
+ getBoolFilterFn: () => this.paginationRepo?.boolFilter,
791
+ getStringFilterFn: () => this.paginationRepo?.stringFilter,
792
+ getSelectedItems: () => this.selectedItems,
793
+ getAllowQuickFiltering: () => this.allowQuickFiltering,
794
+ getDebug: () => this.debug,
795
+ });
796
+ /**
797
+ * Delegating getter preserved for backwards compatibility. Reads
798
+ * through to `_filtering.filteredIndices`, which is populated by
799
+ * the per-row filter `$effect` in this class (the effect calls
800
+ * `this.createRow` and must stay where `createRow` lives).
801
+ */
752
802
  get filteredIndices() {
753
- return this._filteredIndices;
754
- }
755
- filteredData = $derived.by(() => {
756
- void this.dataRepo.data;
757
- void this.filteredIndices;
758
- void this.showSelectedOnly;
759
- // selectedItems is intentionally NOT read here unconditionally — doing so would
760
- // invalidate filteredData → sortedData → currentPageData → entire row model on
761
- // every single toggle. The selectedItems.values() reads inside the showSelectedOnly
762
- // branches below are sufficient to track reactivity when it actually matters.
763
- if (this.paginationRepo?.paginationType == PaginationType.Local) {
764
- const data = this.showSelectedOnly
765
- ? // FIXME this will show the selected items by order of insertion to the selectedItems map.
766
- [...this.selectedItems.values()]
767
- : this.filteredIndices.map((index) => {
768
- return this.dataRepo.data[index];
769
- });
770
- return data;
771
- }
772
- if (this.debug) {
773
- console.log(`updating filtered data. Selected only: ${this.showSelectedOnly}`);
774
- }
775
- return this.showSelectedOnly ? [...this.selectedItems.values()] : this.dataRepo.data;
803
+ return this._filtering.filteredIndices;
804
+ }
805
+ /**
806
+ * Delegating getter preserved for backwards compatibility. Reads
807
+ * through to `_filtering.filteredData`, which owns the local /
808
+ * remote / show-selected-only branching.
809
+ */
810
+ get filteredData() {
811
+ return this._filtering.filteredData;
812
+ }
813
+ /**
814
+ * Phase 6.1 sub-context that owns the sort-related derived chain and
815
+ * the per-`ColumnDef` comparator dispatch. The dependency thunks
816
+ * close over `this.columnDefsById` / `this.filteredData` /
817
+ * `this.dataRepo` / `this.paginationRepo` / `this.debug`, so the
818
+ * sub-context never holds a direct reference to the parent and the
819
+ * reactivity graph remains threaded through `$derived` only.
820
+ *
821
+ * Exposed as a public field so future consumers can read sorting
822
+ * state directly; the legacy `sortedData` getter below preserves the
823
+ * pre-decomposition surface for backwards compatibility.
824
+ */
825
+ _sorting = new SortingState({
826
+ getColumnDefsById: () => this.columnDefsById,
827
+ getFilteredData: () => this._filtering.filteredData,
828
+ getSortingArray: () => this.dataRepo.sorting,
829
+ getPaginationType: () => this.paginationRepo?.paginationType,
830
+ getDataLength: () => this.dataRepo.data.length,
831
+ getDebug: () => this.debug,
776
832
  });
777
- sortedDataAbortController = null;
778
- sortedData = $derived.by(() => {
779
- // if (!$effect.tracking()) {
780
- // throw new Error(`sorted data is not in a tracking context`);
781
- // }
782
- void this.filteredData;
783
- void this.dataRepo.sorting;
784
- this.sortedDataAbortController?.abort();
785
- const currentController = (this.sortedDataAbortController = new AbortController());
786
- if (this.debug) {
787
- console.log('sorted data');
788
- }
789
- if (this.paginationRepo?.paginationType == PaginationType.Local) {
790
- const data = this.filteredData;
791
- if (currentController.signal.aborted) {
792
- return [];
793
- }
794
- const dataToSort = data.concat();
795
- dataToSort.sort((a, b) => {
796
- if (currentController.signal.aborted) {
797
- return 0;
798
- }
799
- const end = this.dataRepo.sorting.length;
800
- for (let i = 0; i < end; i++) {
801
- const { id, desc } = this.dataRepo.sorting[i];
802
- const sortingFn = this.getSortingFn(id, desc);
803
- if (sortingFn) {
804
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
805
- if (currentController.signal.aborted) {
806
- return 0;
807
- }
808
- const result = sortingFn(a, b);
809
- if (result != 0) {
810
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
811
- if (currentController.signal.aborted) {
812
- return 0;
813
- }
814
- return result;
815
- }
816
- }
817
- else if (dev) {
818
- console.warn('no sorting fn found for ' + id);
819
- }
820
- }
821
- return 0;
822
- });
823
- return dataToSort;
824
- }
825
- return this.filteredData;
833
+ /**
834
+ * Phase 6.4 sub-context that owns every persistence / preferences
835
+ * concern previously held directly on `TableContext`:
836
+ *
837
+ * - the saved column-presets list and currently-selected preset
838
+ * (`columnPresets`, `selectedPreset`);
839
+ * - the `userIdSegment` `$derived` (used to namespace every
840
+ * `scoria-table:<user>:<tableName>:<suffix>` storage key);
841
+ * - the `saveColumnPreset` / `setSelectedPreset` writers (which
842
+ * apply the layout AND persist);
843
+ * - the `loadPresets` / `loadSelectedPresetName` /
844
+ * `loadSavedSettings` boot-time hydration helpers;
845
+ * - the `persistSettings` writer used by the in-app settings
846
+ * toggles (`setIsCompact`, `setAlternateRowColours`, etc.);
847
+ * - the schema-validation typeguards (exported alongside the
848
+ * sub-context for unit testing).
849
+ *
850
+ * The settings fields themselves (`isCompact`, `singleClickEditing`,
851
+ * `alternateRowColours`, etc.) stay on `TableContext` because each
852
+ * one drives reactive UI; only the localStorage round-trip moved.
853
+ * `TableContext` retains delegating getters/setters/methods for
854
+ * every legacy surface (`columnPresets`, `selectedPreset`,
855
+ * `saveColumnPreset`, `setSelectedPreset`, `setIsCompact`, etc.).
856
+ */
857
+ _preferences = new PreferencesState({
858
+ getTableName: () => this.tableName,
859
+ getUserId: () => this.getUserId(),
860
+ getColumnDefs: () => this.columnDefs,
861
+ getColumnVisibility: () => this.columnVisibility,
862
+ getColumnPinning: () => this.columnPinning,
863
+ getSortingArray: () => this.paginationRepo?.sorting,
864
+ setColumnDefs: (defs) => {
865
+ this.columnDefs = defs;
866
+ },
867
+ setColumnVisibility: (state) => {
868
+ this.columnVisibility = state;
869
+ },
870
+ setColumnPinning: (state) => {
871
+ this.columnPinning = state;
872
+ },
873
+ applySortingState: (state) => {
874
+ this.paginationRepo?.updateSorting?.(state);
875
+ },
876
+ getDebug: () => this.debug,
877
+ });
878
+ /**
879
+ * Phase 6.5 sub-context that owns every selection + highlighting +
880
+ * key-cache concern previously held directly on `TableContext`:
881
+ *
882
+ * - `selectedItems` / `invalidValues` / `highlightedItems`
883
+ * (`SvelteMap` slots, keyed by `selectionExtractor(item)`);
884
+ * - the `WeakMap<T, RowIdType>` key-cache that memoises the
885
+ * extractor result for each row reference;
886
+ * - the pending-flush buffer and its debounced microtask
887
+ * (`_pendingSelectionChanges` -> `tick().then(flush)`);
888
+ * - the `allSelectedInCurrentPage` `$derived` (with the async-
889
+ * result cache for promise-returning `selectionExtractor` /
890
+ * `RowSelectionDef.isSelectable`);
891
+ * - the `selectAllInCurrentPage` / `updateSelectedItems` /
892
+ * `onHighlightAll` flows;
893
+ * - the `handleItemMutation` split that powers the parent's
894
+ * `onEditCell` - the sub-context owns the key-cache delete +
895
+ * selection rebind; the user-supplied callback stays on this
896
+ * class.
897
+ *
898
+ * Dependencies are passed as thunks so the sub-context never holds
899
+ * a direct reference to `TableContext`. The two `$effect`s that
900
+ * historically lived on this class continue to live here (they
901
+ * read `paginationRepo` directly) but publish their results
902
+ * through `_selection.setAllHighlighted(...)` and
903
+ * `_selection.updateSelectedItems()`.
904
+ */
905
+ _selection = new SelectionState({
906
+ getSortedData: () => this.sortedData,
907
+ getCurrentPageData: () => this.currentPageData,
908
+ getColumnDefs: () => this.columnDefs,
909
+ getPageStartIndex: () => this.paginationRepo?.pageStartIndex,
910
+ getPageEndIndexExclusive: () => this.paginationRepo?.pageEndIndexExclusive,
911
+ getSelectionExtractor: () => this.selectionExtractor,
912
+ getIsSelectable: () => this.isSelectable,
913
+ getDebug: () => this.debug,
914
+ });
915
+ /**
916
+ * Phase 6.6 sub-context that owns every focus + keyboard-navigation
917
+ * concern previously held directly on `TableContext`:
918
+ *
919
+ * - `focusStart` / `focusEnd` / `focusStartIsBeingEdited` /
920
+ * `isMousingDown` (the `$state` slots that define the focused
921
+ * cell-range);
922
+ * - the `focusedRowIndices` / `focusedColumnIds` sets (populated
923
+ * by the parent's `$effect` through `setFocusedDerived(...)`);
924
+ * - every focus writer (`startFocus`, `moveFocus`, `unfocus`,
925
+ * `endFocus`, `setFocusEnd`, `focusToTop`, `focusToBottom`,
926
+ * `focusToStart`, `focusToEnd`, `moveToTop`, `moveToBottom`,
927
+ * `moveToStart`, `moveToEnd`, `setFocusUp` / `setFocusDown` /
928
+ * `setFocusLeft` / `setFocusRight`, and the
929
+ * `moveFocusUp` / `moveFocusDown` / `moveFocusLeft` /
930
+ * `moveFocusRight` range-extension variants);
931
+ * - `handleTdKeydown` (the root `onkeydown` router) and the
932
+ * private `handleKeystroke` dispatch.
933
+ *
934
+ * Dependencies are passed as thunks so the sub-context never holds
935
+ * a direct reference to `TableContext`. The Ctrl+F / Tab / Ctrl+C
936
+ * paths in `handleTdKeydown` and `handleKeystroke` call back into
937
+ * the parent through the `toggleQuickFiltering` / `tabForward` /
938
+ * `tabBackward` / `copyDataToClipboard` thunks because those
939
+ * methods read fields owned by other sub-contexts (or the parent
940
+ * directly) and would otherwise need to import them.
941
+ *
942
+ * The focus-derived `$effect` that publishes
943
+ * `focusedRowIndices` / `focusedColumnIds` lives on `TableContext`
944
+ * because it reads `paginationRowModel` + `_visualColumns` directly;
945
+ * it calls `_focus.setFocusedDerived(rows, cols)` to publish.
946
+ *
947
+ * Group 2 Item C (per-cell `TableFocusDetails` reuse cache) is
948
+ * deferred for Phase 6.6 - see the class-level JSDoc on
949
+ * `FocusState` for the deferral rationale.
950
+ */
951
+ _focus = new FocusState({
952
+ getAllowCopy: () => this.allowCopy,
953
+ getAllowQuickFiltering: () => this.allowQuickFiltering,
954
+ getColumnDefs: () => this.columnDefs,
955
+ getColumnVisibility: () => this.columnVisibility,
956
+ getVisualColumns: () => this._visualColumns,
957
+ getVisualColumnIndexById: (id) => this.columnVisualIndex(id),
958
+ getSortedDataLength: () => this.sortedData.length,
959
+ getPaginationRowModel: () => this.paginationRowModel,
960
+ copyDataToClipboard: () => this.copyDataToClipboard(),
961
+ toggleQuickFiltering: () => {
962
+ this.toggleQuickFiltering();
963
+ },
964
+ tabForward: () => {
965
+ this.tabForward();
966
+ },
967
+ tabBackward: () => {
968
+ this.tabBackward();
969
+ },
970
+ getDebug: () => this.debug,
971
+ // Phase 7.1 Item C - `getFocusDetailsFor` needs the parent
972
+ // `TableContext` to construct `TableFocusDetails` on cache-miss.
973
+ // Pass via a thunk so the sub-context never closes over `this`
974
+ // at construction time.
975
+ getTableContextRef: () => this,
826
976
  });
977
+ /**
978
+ * Phase 7.4 row-virtualisation sub-context. Owns the visible-row
979
+ * window plus the top / bottom spacer-height derived chain that
980
+ * `TableBody.svelte` renders. Threshold defaults to 100 rows
981
+ * (below that the legacy code path renders every row directly);
982
+ * overscan defaults to 10 rows above and below the viewport so a
983
+ * fast flick of the wheel does not surface a blank band before
984
+ * the next paint.
985
+ *
986
+ * Dependencies are thunks - the sub-context reads
987
+ * `paginationRowModel.rows` through `getRows`, the max-side
988
+ * observed height from the `_columnLayout` row-height pool
989
+ * through `getRowHeight`, and the per-table-density estimate
990
+ * through `getEstimatedRowHeight`.
991
+ */
992
+ /**
993
+ * Rendered widths of header cells, keyed by columnId. TableHead
994
+ * observes each `<div role="columnheader">` with a `ResizeObserver`
995
+ * and writes the actual rendered width here; TableRow / TableBody
996
+ * read these widths when computing sticky pinning offsets so that
997
+ * pinned columns can flex with `minmax(W, 1fr)` like centre columns
998
+ * (matching width-preservation expectation on pinning) while the
999
+ * sticky-cell offsets stay accurate.
1000
+ *
1001
+ * Fallback when a column has not yet been measured: 0. The first
1002
+ * frame after pinning toggles may render with stale offsets but the
1003
+ * resize observer fires immediately afterwards.
1004
+ */
1005
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
1006
+ _columnRenderedWidths = new SvelteMap();
1007
+ _virtualisation = new VirtualisationState({
1008
+ getRows: () => this.paginationRowModel.rows,
1009
+ // Row alignment is browser-handled in the single-grid layout, so
1010
+ // the per-side height pool was deleted along with its observers.
1011
+ // VirtualisationState falls back to `getEstimatedRowHeight` when
1012
+ // no measured value is available, which is sufficient for
1013
+ // determining the spacer heights to within one overscan band.
1014
+ getRowHeight: () => 0,
1015
+ getEstimatedRowHeight: () => (this.isCompact ? 28 : 48),
1016
+ });
1017
+ /**
1018
+ * Delegating getter preserved for backwards compatibility. Reads
1019
+ * through to `_sorting.sortedData`, which owns the comparator-hoist
1020
+ * optimisation and the `AbortController` short-circuit. Consumers
1021
+ * inside `src/lib` already point at `tableContext.sortedData`; the
1022
+ * delegation will be removed once Phase 6 v-next migrates every
1023
+ * reader to `tableContext._sorting.sortedData` directly.
1024
+ */
1025
+ get sortedData() {
1026
+ return this._sorting.sortedData;
1027
+ }
827
1028
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
828
1029
  _sortedRowModel = $state({ rows: [], rowsById: new Map() });
829
1030
  get sortedRowModel() {
@@ -972,483 +1173,132 @@ export class TableContext {
972
1173
  }
973
1174
  return makeRow(def);
974
1175
  };
975
- const leftVisibleCells = $derived([...(this.columnPinning.left?.keys() ?? [])].map(makeCellFromId));
976
- const rightVisibleCells = $derived([...(this.columnPinning.right?.keys() ?? [])].map(makeCellFromId));
977
- const centerVisibleCells = $derived(this.columnDefs
1176
+ // Iterating `this.columnDefs.filter(...)` instead of
1177
+ // `this.columnPinning.left?.keys()` puts pinned-left body
1178
+ // cells in declaration order (matching the header rendering
1179
+ // and the `getVisualColumns` tab-order). Iterating the
1180
+ // pinning Map's keys produced *insertion* order so that
1181
+ // pinning Name then ID gave a body row of [Name, ID] while
1182
+ // the header above it stayed [ID, Name]. See the duplicate
1183
+ // factory in `createRow` below for the same fix.
1184
+ const leftVisibleCells = $derived(this.columnDefs
978
1185
  .filter((it) => {
979
- return !(this.columnPinning.left?.has(it.id) || this.columnPinning.right?.has(it.id));
1186
+ return (this.columnVisibility[it.id] !== false && this.columnPinning.left?.has(it.id) === true);
980
1187
  })
981
1188
  .map(makeRow));
982
- return row;
983
- });
984
- });
985
- _paginationRowModel = $state({
986
- rows: [],
987
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
988
- rowsById: new Map(),
989
- });
990
- get paginationRowModel() {
991
- return this._paginationRowModel;
992
- }
993
- // #endregion
994
- // #region public methods
995
- // Key extraction cache — avoids re-calling selectionExtractor for the same item object.
996
- // Only onEditCell explicitly invalidates the cache (when the item is mutated in-place).
997
- _keyCache = new WeakMap();
998
- _cachedKey(item) {
999
- const cached = this._keyCache.get(item);
1000
- if (cached !== undefined) {
1001
- return cached;
1002
- }
1003
- const raw = this.selectionExtractor(item);
1004
- if (raw instanceof Promise) {
1005
- return raw.then((key) => {
1006
- this._keyCache.set(item, key);
1007
- return key;
1008
- });
1009
- }
1010
- this._keyCache.set(item, raw);
1011
- return raw;
1012
- }
1013
- // Pending selection changes — buffered and flushed as a single SvelteMap reassignment
1014
- // to avoid per-toggle reactive cascades.
1015
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
1016
- _pendingSelectionChanges = new Map();
1017
- _flushScheduled = false;
1018
- _flushPendingSelectionChanges() {
1019
- if (this._pendingSelectionChanges.size === 0) {
1020
- return;
1021
- }
1022
- // Mutate in place to preserve SvelteMap's per-key reactive tracking.
1023
- // Replacing _selectedItems with a cloned map would invalidate every .has()/.get()
1024
- // subscriber across all visible rows, not just the changed keys.
1025
- for (const [key, item] of this._pendingSelectionChanges) {
1026
- if (item === null) {
1027
- this.selectedItems.delete(key);
1028
- }
1029
- else {
1030
- this.selectedItems.set(key, item);
1031
- }
1032
- }
1033
- this._pendingSelectionChanges.clear();
1034
- this._flushScheduled = false;
1035
- }
1036
- _schedulePendingFlush() {
1037
- if (this._flushScheduled) {
1038
- return;
1039
- }
1040
- this._flushScheduled = true;
1041
- tick()
1042
- .then(() => {
1043
- this._flushPendingSelectionChanges();
1044
- })
1045
- .catch(devCatch);
1046
- }
1047
- selectionContains = (item) => {
1048
- void item;
1049
- void this.isSelectable;
1050
- void this.selectedItems.size;
1051
- // if (!$effect.tracking()) {
1052
- // throw new Error(`selectionContains is not ran in a tracking context`);
1053
- // }
1054
- if (!this.isSelectable) {
1055
- return true;
1056
- }
1057
- const rawIdentity = this._cachedKey(item);
1058
- return rawIdentity instanceof Promise
1059
- ? rawIdentity.then((identity) => this.selectedItems.has(identity))
1060
- : this.selectedItems.has(rawIdentity);
1061
- };
1062
- addInvalidValue = (item) => {
1063
- const raw = this._cachedKey(item);
1064
- if (raw instanceof Promise) {
1065
- return raw.then((key) => {
1066
- this.invalidValues.set(key, item);
1067
- });
1068
- }
1069
- this.invalidValues.set(raw, item);
1070
- };
1071
- removeInvalidValue = (item) => {
1072
- const raw = this._cachedKey(item);
1073
- if (raw instanceof Promise) {
1074
- return raw.then((key) => {
1075
- this.invalidValues.delete(key);
1076
- });
1077
- }
1078
- this.invalidValues.delete(raw);
1079
- };
1080
- setOpenModalCoordinates = (coordinates) => {
1081
- this.openModalCoordinates = coordinates ? { ...coordinates } : null;
1082
- };
1083
- toggleHighlightedRow = (item) => {
1084
- const raw = this._cachedKey(item);
1085
- if (raw instanceof Promise) {
1086
- return raw.then((key) => {
1087
- if (this.highlightedItems.has(key)) {
1088
- this.highlightedItems.delete(key);
1089
- }
1090
- else {
1091
- this.highlightedItems.set(key, item);
1092
- }
1093
- });
1094
- }
1095
- if (this.highlightedItems.has(raw)) {
1096
- this.highlightedItems.delete(raw);
1097
- }
1098
- else {
1099
- this.highlightedItems.set(raw, item);
1100
- }
1101
- };
1102
- resetHighlightedRows = () => {
1103
- this.highlightedItems.clear();
1104
- };
1105
- toggleSelected = (item) => {
1106
- const raw = this._cachedKey(item);
1107
- if (raw instanceof Promise) {
1108
- return raw.then((key) => {
1109
- const pendingValue = this._pendingSelectionChanges.get(key);
1110
- const isSelected = pendingValue !== undefined ? pendingValue !== null : this.selectedItems.has(key);
1111
- this._pendingSelectionChanges.set(key, isSelected ? null : item);
1112
- this._schedulePendingFlush();
1113
- });
1114
- }
1115
- const pendingValue = this._pendingSelectionChanges.get(raw);
1116
- const isSelected = pendingValue !== undefined ? pendingValue !== null : this.selectedItems.has(raw);
1117
- this._pendingSelectionChanges.set(raw, isSelected ? null : item);
1118
- this._schedulePendingFlush();
1119
- };
1120
- setSelected = (item, selected) => {
1121
- const raw = this._cachedKey(item);
1122
- if (raw instanceof Promise) {
1123
- return raw.then((key) => {
1124
- this._pendingSelectionChanges.set(key, selected ? item : null);
1125
- this._schedulePendingFlush();
1126
- });
1127
- }
1128
- this._pendingSelectionChanges.set(raw, selected ? item : null);
1129
- this._schedulePendingFlush();
1130
- };
1131
- setSelectionMap = (set) => {
1132
- this._selectedItems = set;
1133
- };
1134
- updateSelectedItems = async () => {
1135
- if (this.selectedItems.size === 0) {
1136
- return;
1137
- }
1138
- const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
1139
- const sortedData = this.sortedData;
1140
- const pageEndIndexExclusive = this.paginationRepo
1141
- ? Math.min(this.paginationRepo.pageEndIndexExclusive, sortedData.length)
1142
- : sortedData.length;
1143
- // Build key → item map for the current page (O(page_size) extractions)
1144
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
1145
- const pageKeyMap = new Map();
1146
- const pending = [];
1147
- for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
1148
- const item = sortedData[i];
1149
- const raw = this._cachedKey(item);
1150
- if (raw instanceof Promise) {
1151
- pending.push(raw.then((key) => {
1152
- pageKeyMap.set(key, item);
1153
- }));
1154
- }
1155
- else {
1156
- pageKeyMap.set(raw, item);
1157
- }
1158
- }
1159
- if (pending.length > 0) {
1160
- await Promise.all(pending);
1161
- }
1162
- // Update only selected items that appear on this page (O(selected_size))
1163
- for (const key of this.selectedItems.keys()) {
1164
- const newItem = pageKeyMap.get(key);
1165
- if (newItem !== undefined) {
1166
- this.selectedItems.set(key, newItem);
1167
- }
1168
- }
1169
- };
1170
- resetSelectedItems = () => {
1171
- this._selectedItems.clear();
1172
- };
1173
- selectAllInCurrentPage = async () => {
1174
- void this.paginationRepo?.pageStartIndex;
1175
- const sortedData = this.sortedData;
1176
- const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
1177
- const pageEndIndexExclusive = this.paginationRepo
1178
- ? Math.min(this.paginationRepo.pageEndIndexExclusive, sortedData.length)
1179
- : sortedData.length;
1180
- // Will be doing one full reassignment to this.selectedItems to avoid svelteMap redraws
1181
- // const cloned = new SvelteMap(this.selectedItems);
1182
- const sel = this.selectedItems; // HACK: keep the same svelteMap going so that we can support setSelectionMap()
1183
- // if this is deemed problematic, we'll need to find another way to ask for a new map from the context consumer
1184
- const pending = [];
1185
- if (this.allSelectedInCurrentPage) {
1186
- for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
1187
- const raw = this._cachedKey(sortedData[i]);
1188
- if (raw instanceof Promise) {
1189
- pending.push(raw.then((key) => {
1190
- sel.delete(key);
1191
- }));
1192
- }
1193
- else {
1194
- sel.delete(raw);
1195
- }
1196
- }
1197
- }
1198
- else {
1199
- const selectableMethod = this.columnDefs.find((it) => it instanceof RowSelectionDef)?.isSelectable;
1200
- if (selectableMethod) {
1201
- for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
1202
- const item = sortedData[i];
1203
- const relIdx = i - pageStartIndex;
1204
- const s = selectableMethod(item, relIdx);
1205
- const doSet = (ok) => {
1206
- if (!ok) {
1207
- return;
1208
- }
1209
- const raw = this._cachedKey(item);
1210
- if (raw instanceof Promise) {
1211
- return raw.then((key) => {
1212
- sel.set(key, item);
1213
- });
1214
- }
1215
- sel.set(raw, item);
1216
- };
1217
- const r = s instanceof Promise ? s.then(doSet) : doSet(s);
1218
- if (r instanceof Promise) {
1219
- pending.push(r);
1220
- }
1221
- }
1222
- }
1223
- else {
1224
- for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
1225
- const item = sortedData[i];
1226
- const raw = this._cachedKey(item);
1227
- if (raw instanceof Promise) {
1228
- pending.push(raw.then((key) => {
1229
- sel.set(key, item);
1230
- }));
1231
- }
1232
- else {
1233
- sel.set(raw, item);
1234
- }
1235
- }
1236
- }
1237
- }
1238
- if (pending.length > 0) {
1239
- await Promise.all(pending);
1240
- }
1241
- // HACK
1242
- // this._selectedItems = cloned;
1243
- };
1244
- onHighlightAll = () => {
1245
- if (this.allHighlighted) {
1246
- // console.debug('All were highlighted. Clearing');
1247
- this.highlightedItems.clear();
1248
- return Promise.resolve();
1249
- }
1250
- else {
1251
- const items = this.currentPageData;
1252
- return asyncForEach(items, (item) => {
1253
- const raw = this._cachedKey(item);
1254
- if (raw instanceof Promise) {
1255
- return raw.then((key) => {
1256
- this.highlightedItems.set(key, item);
1257
- });
1258
- }
1259
- this.highlightedItems.set(raw, item);
1260
- return;
1261
- });
1262
- }
1263
- };
1264
- onEditCell = async (item, coords, value) => {
1265
- const rawOldKey = this._cachedKey(item);
1266
- const oldKey = rawOldKey instanceof Promise ? await rawOldKey : rawOldKey;
1267
- const wasSelected = this.selectedItems.has(oldKey);
1268
- if (wasSelected) {
1269
- this.selectedItems.delete(oldKey);
1270
- }
1271
- // Invalidate cache — _onEditCell may mutate the item's key field in-place
1272
- this._keyCache.delete(item);
1273
- await this._onEditCell?.(item, coords, value);
1274
- if (wasSelected && this.isSelectable) {
1275
- const rawNewKey = this._cachedKey(item);
1276
- const newKey = rawNewKey instanceof Promise ? await rawNewKey : rawNewKey;
1277
- if (newKey) {
1278
- this.selectedItems.set(newKey, item);
1279
- }
1280
- // this.recalculateAllSelectedInCurrentPage();
1281
- }
1282
- };
1283
- handleTdKeydown = (e) => {
1284
- e.stopPropagation();
1285
- if (e.key.toLocaleUpperCase() === 'F' && e.ctrlKey && this.allowQuickFiltering) {
1286
- e.preventDefault();
1287
- this.toggleQuickFiltering();
1288
- }
1289
- else if (e.key === 'Escape') {
1290
- e.preventDefault();
1291
- this.focusStartIsBeingEdited = false;
1292
- }
1293
- else if (e.key === 'Tab') {
1294
- //allow regular operation in side panels
1295
- const target = e.target;
1296
- if (target?.closest('.side-panel') != null ||
1297
- target?.attributes.getNamedItem('data-type')?.value === 'quicksearch') {
1298
- return;
1299
- }
1300
- e.preventDefault();
1301
- if (e.getModifierState('Shift')) {
1302
- this.tabBackward();
1303
- }
1304
- else {
1305
- this.tabForward();
1306
- }
1307
- }
1308
- if (e.target.tagName === 'INPUT' &&
1309
- e.target.attributes.getNamedItem('type')?.value !== 'checkbox') {
1310
- return;
1311
- }
1312
- this.handleKeystroke(e).catch(devCatch);
1313
- };
1314
- async handleKeystroke(e) {
1315
- switch (e.key) {
1316
- case 'Enter':
1317
- case 'F2':
1318
- if (!e.target?.classList.contains('editable')) {
1319
- break;
1320
- }
1321
- e.preventDefault();
1322
- e.target?.dispatchEvent(new CustomEvent('edit', { detail: { selectAll: e.key === 'Enter' } }));
1323
- // focusStartIsBeingEdited.set(true);
1324
- break;
1325
- case 'ArrowUp': {
1326
- e.preventDefault();
1327
- if (e.getModifierState('Control')) {
1328
- if (e.getModifierState('Shift')) {
1329
- if (!this.allowCopy) {
1330
- return;
1331
- }
1332
- this.focusToTop();
1333
- }
1334
- else {
1335
- this.moveToTop();
1336
- }
1337
- }
1338
- else if (e.getModifierState('Shift')) {
1339
- if (!this.allowCopy) {
1340
- return;
1341
- }
1342
- this.moveFocusUp();
1343
- }
1344
- else {
1345
- this.setFocusUp();
1346
- }
1347
- return;
1348
- }
1349
- case 'ArrowDown': {
1350
- e.preventDefault();
1351
- if (e.getModifierState('Control')) {
1352
- if (e.getModifierState('Shift')) {
1353
- if (!this.allowCopy) {
1354
- return;
1355
- }
1356
- this.focusToBottom();
1357
- }
1358
- else {
1359
- this.moveToBottom();
1360
- }
1361
- }
1362
- else if (e.getModifierState('Shift')) {
1363
- if (!this.allowCopy) {
1364
- return;
1365
- }
1366
- this.moveFocusDown();
1367
- }
1368
- else {
1369
- this.setFocusDown();
1370
- }
1371
- return;
1372
- }
1373
- // TODO add ctrl-home and ctrl-end as ways to get to top-left/bottom-right
1374
- case 'ArrowLeft':
1375
- case 'Home': {
1376
- e.preventDefault();
1377
- if (e.getModifierState('Control') || e.key === 'Home') {
1378
- if (e.getModifierState('Shift')) {
1379
- if (!this.allowCopy) {
1380
- return;
1381
- }
1382
- this.focusToStart();
1383
- }
1384
- else {
1385
- this.moveToStart();
1386
- }
1387
- //these two at the bottom shouldn't happen on a home press
1388
- }
1389
- else if (e.getModifierState('Shift') && this.allowCopy) {
1390
- this.moveFocusLeft();
1391
- }
1392
- else {
1393
- this.setFocusLeft();
1394
- }
1395
- return;
1396
- }
1397
- case 'ArrowRight':
1398
- case 'End': {
1399
- e.preventDefault();
1400
- if (e.getModifierState('Control') || e.key === 'End') {
1401
- if (e.getModifierState('Shift')) {
1402
- if (!this.allowCopy) {
1403
- return;
1404
- }
1405
- this.focusToEnd();
1406
- }
1407
- else {
1408
- this.moveToEnd();
1409
- }
1410
- }
1411
- else if (e.getModifierState('Shift') && this.allowCopy) {
1412
- this.moveFocusRight();
1413
- }
1414
- else {
1415
- this.setFocusRight();
1416
- }
1417
- return;
1418
- }
1419
- default: {
1420
- if (this.debug) {
1421
- console.log(`handleKeystroke: ignoring key ${e.key}`);
1422
- }
1423
- break;
1424
- }
1425
- }
1426
- if (this.allowCopy) {
1427
- switch (e.key) {
1428
- case 'a': {
1429
- if (e.ctrlKey) {
1430
- e.preventDefault();
1431
- this.moveToTop();
1432
- this.moveToStart();
1433
- this.focusToBottom();
1434
- this.focusToEnd();
1435
- }
1436
- break;
1437
- }
1438
- case 'c':
1439
- case 'C':
1440
- if (e.getModifierState('Control')) {
1441
- e.preventDefault();
1442
- await this.copyDataToClipboard();
1443
- }
1444
- break;
1445
- default: {
1446
- //nothing
1447
- break;
1448
- }
1449
- }
1450
- }
1189
+ const rightVisibleCells = $derived(this.columnDefs
1190
+ .filter((it) => {
1191
+ return (this.columnVisibility[it.id] !== false &&
1192
+ this.columnPinning.right?.has(it.id) === true);
1193
+ })
1194
+ .map(makeRow));
1195
+ const centerVisibleCells = $derived(this.columnDefs
1196
+ .filter((it) => {
1197
+ return (this.columnVisibility[it.id] !== false &&
1198
+ !(this.columnPinning.left?.has(it.id) || this.columnPinning.right?.has(it.id)));
1199
+ })
1200
+ .map(makeRow));
1201
+ return row;
1202
+ });
1203
+ });
1204
+ _paginationRowModel = $state({
1205
+ rows: [],
1206
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
1207
+ rowsById: new Map(),
1208
+ });
1209
+ get paginationRowModel() {
1210
+ return this._paginationRowModel;
1211
+ }
1212
+ // #endregion
1213
+ // #region public methods
1214
+ /**
1215
+ * Delegating wrapper preserved for backwards compatibility. The
1216
+ * key-extraction cache lives on `_selection` (Phase 6.5); this
1217
+ * method forwards to `_selection.cachedKey(item)` so legacy call
1218
+ * sites in `getHighlightedRowData` / `getFocusElementData` /
1219
+ * `originalRowIndices` / `sortedRowModel` / `onHighlightAll` see
1220
+ * no behavioural change.
1221
+ */
1222
+ _cachedKey(item) {
1223
+ return this._selection.cachedKey(item);
1451
1224
  }
1225
+ /** Delegating accessor preserved for backwards compatibility. The
1226
+ * implementation lives on `_selection.selectionContains` (Phase 6.5). */
1227
+ selectionContains = (item) => {
1228
+ return this._selection.selectionContains(item);
1229
+ };
1230
+ /** Delegating accessor preserved for backwards compatibility. */
1231
+ addInvalidValue = (item) => {
1232
+ return this._selection.addInvalidValue(item);
1233
+ };
1234
+ /** Delegating accessor preserved for backwards compatibility. */
1235
+ removeInvalidValue = (item) => {
1236
+ return this._selection.removeInvalidValue(item);
1237
+ };
1238
+ /** Stores or clears the coordinates of the open per-cell modal. */
1239
+ setOpenModalCoordinates = (coordinates) => {
1240
+ this.openModalCoordinates = coordinates ? { ...coordinates } : null;
1241
+ };
1242
+ /** Delegating accessor preserved for backwards compatibility. */
1243
+ toggleHighlightedRow = (item) => {
1244
+ return this._selection.toggleHighlightedRow(item);
1245
+ };
1246
+ /** Delegating accessor preserved for backwards compatibility. */
1247
+ resetHighlightedRows = () => {
1248
+ this._selection.resetHighlightedRows();
1249
+ };
1250
+ /** Delegating accessor preserved for backwards compatibility. */
1251
+ toggleSelected = (item) => {
1252
+ return this._selection.toggleSelected(item);
1253
+ };
1254
+ /** Delegating accessor preserved for backwards compatibility. */
1255
+ setSelected = (item, selected) => {
1256
+ return this._selection.setSelected(item, selected);
1257
+ };
1258
+ /** Delegating accessor preserved for backwards compatibility. */
1259
+ setSelectionMap = (set) => {
1260
+ this._selection.setSelectionMap(set);
1261
+ };
1262
+ /** Delegating accessor preserved for backwards compatibility. */
1263
+ updateSelectedItems = async () => {
1264
+ await this._selection.updateSelectedItems();
1265
+ };
1266
+ /** Delegating accessor preserved for backwards compatibility. */
1267
+ resetSelectedItems = () => {
1268
+ this._selection.resetSelectedItems();
1269
+ };
1270
+ /** Delegating accessor preserved for backwards compatibility. */
1271
+ selectAllInCurrentPage = async () => {
1272
+ await this._selection.selectAllInCurrentPage();
1273
+ };
1274
+ /** Delegating accessor preserved for backwards compatibility. */
1275
+ onHighlightAll = () => {
1276
+ return this._selection.onHighlightAll();
1277
+ };
1278
+ /**
1279
+ * Invoked by every editable cell on commit. Split between this
1280
+ * class (the user-supplied edit callback) and `_selection`
1281
+ * (the key-cache invalidation + selection-rebind half). The
1282
+ * sub-context's `handleItemMutation` accepts a thunk that runs
1283
+ * the consumer callback between the pre- and post-edit selection
1284
+ * bookkeeping so the boundary stays explicit.
1285
+ */
1286
+ onEditCell = async (item, coords, value) => {
1287
+ await this._selection.handleItemMutation(item, async () => {
1288
+ await this._onEditCell?.(item, coords, value);
1289
+ });
1290
+ };
1291
+ /**
1292
+ * Delegating accessor preserved for backwards compatibility. The
1293
+ * implementation lives on `_focus.handleTdKeydown` (Phase 6.6).
1294
+ * Bound to the root `<table>` container's `onkeydown` listener;
1295
+ * routes Ctrl+F / Escape / Tab synchronously and dispatches the
1296
+ * arrow-key / Home / End / Ctrl+A / Ctrl+C path asynchronously.
1297
+ */
1298
+ handleTdKeydown = (e) => {
1299
+ this._focus.handleTdKeydown(e);
1300
+ };
1301
+ /** Toggles quick-filtering visibility and refocuses the matching cell. */
1452
1302
  toggleQuickFiltering = () => {
1453
1303
  if (this.allowQuickFiltering) {
1454
1304
  const fs = this.focusStart;
@@ -1466,13 +1316,26 @@ export class TableContext {
1466
1316
  .catch(devCatch);
1467
1317
  }
1468
1318
  };
1319
+ /**
1320
+ * Applies an updater to the column-pinning state (left / right
1321
+ * pins). Delegates to `_columnLayout.setColumnPinning`, which
1322
+ * enforces the `allowColumnReordering` master gate.
1323
+ */
1469
1324
  setColumnPinning = (updater) => {
1470
- if (!this.allowColumnReordering) {
1471
- return;
1472
- }
1473
- this.columnPinning = updater(this.columnPinning);
1325
+ this._columnLayout.setColumnPinning(updater);
1474
1326
  };
1475
- updateFilters = $state();
1327
+ /**
1328
+ * Delegating accessor preserved for backwards compatibility. The
1329
+ * actual `$state` slot lives on `_filtering`; this getter / setter
1330
+ * pair forwards reads and writes so existing consumers (notably
1331
+ * `generateTableOptions`) need no migration.
1332
+ */
1333
+ get updateFilters() {
1334
+ return this._filtering.updateFilters;
1335
+ }
1336
+ set updateFilters(v) {
1337
+ this._filtering.updateFilters = v;
1338
+ }
1476
1339
  updateVisibility = (stateOrCallback) => {
1477
1340
  if (typeof stateOrCallback == 'function') {
1478
1341
  this.columnVisibility = stateOrCallback(this.columnVisibility);
@@ -1495,6 +1358,7 @@ export class TableContext {
1495
1358
  // return this._rowModel;
1496
1359
  // }
1497
1360
  lastPage = $derived(this.dataRepo.lastPage ?? 0);
1361
+ /** Canvas-measured width of a column header label, plus padding. */
1498
1362
  measureHeaderWidth = (columnDef) => {
1499
1363
  const label = columnDef.header;
1500
1364
  if (typeof label === 'string' && browser) {
@@ -1516,6 +1380,7 @@ export class TableContext {
1516
1380
  }
1517
1381
  return 0;
1518
1382
  };
1383
+ /** Sizes a column to fit its widest current-page cell label. */
1519
1384
  autoResize = async (columnDef) => {
1520
1385
  if (!browser) {
1521
1386
  return;
@@ -1541,6 +1406,11 @@ export class TableContext {
1541
1406
  }
1542
1407
  this.columnSizing[colId] = maxWidth + 32; //standard extra cell padding
1543
1408
  };
1409
+ /**
1410
+ * Drops the in-progress column drag onto the supplied target,
1411
+ * updating `columnDefs`, pinning state, and focus. No-op when
1412
+ * column reordering is disabled.
1413
+ */
1544
1414
  reorderColumn = (dropTarget) => {
1545
1415
  if (!this.allowColumnReordering) {
1546
1416
  return;
@@ -1602,6 +1472,41 @@ export class TableContext {
1602
1472
  this.columnDefs = newDefs;
1603
1473
  // this.forceVisibilityUpdate();
1604
1474
  };
1475
+ /**
1476
+ * Programmatic column-reorder primitive used by keyboard accessibility
1477
+ * shortcuts in the column-management panel. Moves the column at
1478
+ * {@link currentIndex} to {@link newIndex} (both expressed in the
1479
+ * sorted-by-`index` order of {@link columnDefs}). No-op when column
1480
+ * reordering is disabled or indices are out of range / unchanged.
1481
+ */
1482
+ moveColumnTo = (currentIndex, newIndex) => {
1483
+ if (!this.allowColumnReordering) {
1484
+ return;
1485
+ }
1486
+ const newDefs = this.columnDefs.concat();
1487
+ newDefs
1488
+ .sort((a, b) => a.index - b.index)
1489
+ .forEach((element, index) => {
1490
+ element.index = index;
1491
+ });
1492
+ if (currentIndex < 0 ||
1493
+ currentIndex >= newDefs.length ||
1494
+ newIndex < 0 ||
1495
+ newIndex >= newDefs.length ||
1496
+ currentIndex === newIndex) {
1497
+ return;
1498
+ }
1499
+ const [moved] = newDefs.splice(currentIndex, 1);
1500
+ newDefs.splice(newIndex, 0, moved);
1501
+ newDefs.forEach((element, index) => {
1502
+ element.index = index;
1503
+ });
1504
+ this._setNewPinningState();
1505
+ this.updateDefsBasedOnPinning();
1506
+ this.unfocus();
1507
+ this.columnDefs = newDefs;
1508
+ };
1509
+ /** Copies the highlighted rows to the OS clipboard as TSV / CSV. */
1605
1510
  copyHighlightedRowsToClipboard = async () => {
1606
1511
  // NOTE: False Positive. Clipboard does not exist in http environments
1607
1512
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -1611,6 +1516,7 @@ export class TableContext {
1611
1516
  }
1612
1517
  return this._performRowCopy(await this.getHighlightedRowData());
1613
1518
  };
1519
+ /** Copies the currently focused cell range to the OS clipboard. */
1614
1520
  copyDataToClipboard = async () => {
1615
1521
  // NOTE: False Positive. Clipboard does not exist in http environments
1616
1522
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -1645,11 +1551,13 @@ export class TableContext {
1645
1551
  const value = columnOptions?.find((it) => it.value === focusedElementData[0][0])?.label ??
1646
1552
  focusedElementData[0][0]?.toString() ??
1647
1553
  '';
1648
- return value;
1554
+ // Single-cell copy bypasses TSV/CSV wrapping but still lands
1555
+ // in a spreadsheet; neutralise formula-injection prefixes.
1556
+ return escapeForSpreadsheet(value);
1649
1557
  }
1650
1558
  else {
1651
1559
  const value = focusedElementData[0][0]?.toString().trim() ?? '';
1652
- return value;
1560
+ return escapeForSpreadsheet(value);
1653
1561
  }
1654
1562
  }
1655
1563
  return `${shouldCopyHeaders ? `${focusedColumnIds.join(separator)}\n` : ''}${focusedElementData
@@ -1678,238 +1586,124 @@ export class TableContext {
1678
1586
  })();
1679
1587
  navigator.clipboard.writeText(value).catch(devCatch);
1680
1588
  };
1589
+ /** Delegating accessor preserved for backwards compatibility. The
1590
+ * implementation lives on `_focus.startFocus` (Phase 6.6). */
1681
1591
  startFocus = (coords) => {
1682
- this.isMousingDown = true;
1683
- this.moveFocus(coords);
1592
+ this._focus.startFocus(coords);
1684
1593
  };
1594
+ /** Delegating accessor preserved for backwards compatibility. The
1595
+ * implementation lives on `_focus.moveFocus` (Phase 6.6). */
1685
1596
  moveFocus = (coords, startEditing = false) => {
1686
- this.focusStart = { ...coords };
1687
- this.focusEnd = { ...coords };
1688
- if (startEditing) {
1689
- tick()
1690
- .then(() => {
1691
- this.focusStartIsBeingEdited = true;
1692
- })
1693
- .catch(devCatch);
1694
- }
1597
+ this._focus.moveFocus(coords, startEditing);
1695
1598
  };
1599
+ /** Delegating accessor preserved for backwards compatibility. */
1696
1600
  unfocus = () => {
1697
- this.focusStartIsBeingEdited = false;
1698
- this.focusStart = null;
1699
- this.focusEnd = null;
1601
+ this._focus.unfocus();
1700
1602
  };
1603
+ /** Delegating accessor preserved for backwards compatibility. */
1701
1604
  focusToTop = () => {
1702
- if (!this.allowCopy) {
1703
- return;
1704
- }
1705
- this._moveFocusEndRow(-Infinity);
1605
+ this._focus.focusToTop();
1706
1606
  };
1607
+ /** Delegating accessor preserved for backwards compatibility. */
1707
1608
  focusToBottom = () => {
1708
- if (!this.allowCopy) {
1709
- return;
1710
- }
1711
- this._moveFocusEndRow(Infinity);
1609
+ this._focus.focusToBottom();
1712
1610
  };
1611
+ /** Delegating accessor preserved for backwards compatibility. */
1713
1612
  moveToBottom = () => {
1714
- this._moveFocusRow(Infinity);
1613
+ this._focus.moveToBottom();
1715
1614
  };
1615
+ /** Delegating accessor preserved for backwards compatibility. */
1716
1616
  moveToTop = () => {
1717
- this._moveFocusRow(-Infinity);
1617
+ this._focus.moveToTop();
1718
1618
  };
1619
+ /** Delegating accessor preserved for backwards compatibility. */
1719
1620
  moveFocusUp = () => {
1720
- if (!this.allowCopy) {
1721
- return;
1722
- }
1723
- this._moveFocusEndRow(-1);
1621
+ this._focus.moveFocusUp();
1724
1622
  };
1623
+ /** Delegating accessor preserved for backwards compatibility. */
1725
1624
  setFocusUp = () => {
1726
- this._moveFocusRow(-1);
1625
+ this._focus.setFocusUp();
1727
1626
  };
1627
+ /** Delegating accessor preserved for backwards compatibility. */
1728
1628
  tabForward = () => {
1729
- if (!this.allowCopy) {
1730
- return;
1731
- }
1732
- const defs = this.columnDefs;
1733
- const visibility = this.columnVisibility;
1734
- const indexBounds = defs.length;
1735
- const rowBounds = this.sortedData.length;
1736
- if (!this.focusStart || !rowBounds) {
1737
- return;
1738
- }
1739
- let { column: index, row } = this.focusStart;
1740
- ++index;
1741
- while (row < rowBounds) {
1742
- while (index < indexBounds) {
1743
- const newId = defs[index].id;
1744
- if (newId && visibility[newId] !== false) {
1745
- this.moveFocus({ id: newId, row, column: index }, this.focusStartIsBeingEdited);
1746
- return;
1747
- }
1748
- ++index;
1749
- }
1750
- index = 0;
1751
- ++row;
1752
- }
1629
+ this._focus.tabForward();
1753
1630
  };
1631
+ /** Delegating accessor preserved for backwards compatibility. */
1754
1632
  tabBackward = () => {
1755
- if (!this.allowCopy) {
1756
- return;
1757
- }
1758
- const maxIndexRight = this.columnDefs.length - 1;
1759
- const defs = this.columnDefs;
1760
- const visibility = this.columnVisibility;
1761
- if (!this.focusStart) {
1762
- return;
1763
- }
1764
- let { column: index, row } = this.focusStart;
1765
- --index;
1766
- while (row > -1) {
1767
- while (index > -1) {
1768
- const newId = defs[index].id;
1769
- if (newId && visibility[newId] !== false) {
1770
- this.moveFocus({ id: newId, row, column: index }, this.focusStartIsBeingEdited);
1771
- return;
1772
- }
1773
- --index;
1774
- }
1775
- index = maxIndexRight;
1776
- --row;
1777
- }
1633
+ this._focus.tabBackward();
1778
1634
  };
1635
+ /** Delegating accessor preserved for backwards compatibility. */
1779
1636
  focusToStart = () => {
1780
- if (!this.allowCopy) {
1781
- return;
1782
- }
1783
- if (!this.focusEnd) {
1784
- return;
1785
- }
1786
- const visualCols = this._visualColumns;
1787
- if (visualCols.length > 0) {
1788
- const first = visualCols[0];
1789
- this.focusEnd = { ...this.focusEnd, id: first.id, column: first.index };
1790
- }
1637
+ this._focus.focusToStart();
1791
1638
  };
1639
+ /** Delegating accessor preserved for backwards compatibility. */
1792
1640
  moveToStart = () => {
1793
- if (!this.focusStart) {
1794
- return;
1795
- }
1796
- const visualCols = this._visualColumns;
1797
- if (visualCols.length > 0) {
1798
- const first = visualCols[0];
1799
- this.moveFocus({ ...this.focusStart, id: first.id, column: first.index });
1800
- }
1641
+ this._focus.moveToStart();
1801
1642
  };
1643
+ /** Delegating accessor preserved for backwards compatibility. */
1802
1644
  focusToEnd = () => {
1803
- if (!this.allowCopy) {
1804
- return;
1805
- }
1806
- if (!this.focusEnd) {
1807
- return;
1808
- }
1809
- const visualCols = this._visualColumns;
1810
- if (visualCols.length > 0) {
1811
- const last = visualCols[visualCols.length - 1];
1812
- this.focusEnd = { ...this.focusEnd, id: last.id, column: last.index };
1813
- }
1645
+ this._focus.focusToEnd();
1814
1646
  };
1647
+ /** Delegating accessor preserved for backwards compatibility. */
1815
1648
  moveToEnd = () => {
1816
- if (!this.focusStart) {
1817
- return;
1818
- }
1819
- const visualCols = this._visualColumns;
1820
- if (visualCols.length > 0) {
1821
- const last = visualCols[visualCols.length - 1];
1822
- this.moveFocus({ ...this.focusStart, id: last.id, column: last.index });
1823
- }
1649
+ this._focus.moveToEnd();
1824
1650
  };
1651
+ /** Delegating accessor preserved for backwards compatibility. */
1825
1652
  moveFocusDown = () => {
1826
- if (!this.allowCopy) {
1827
- return;
1828
- }
1829
- this._moveFocusEndRow(1);
1653
+ this._focus.moveFocusDown();
1830
1654
  };
1655
+ /** Delegating accessor preserved for backwards compatibility. */
1831
1656
  setFocusDown = () => {
1832
- this._moveFocusRow(1);
1657
+ this._focus.setFocusDown();
1833
1658
  };
1659
+ /** Delegating accessor preserved for backwards compatibility. */
1834
1660
  moveFocusLeft = () => {
1835
- if (!this.allowCopy) {
1836
- return;
1837
- }
1838
- if (this.focusEnd != null) {
1839
- const visualCols = this._visualColumns;
1840
- const visualIndex = this.columnVisualIndex(this.focusEnd.id);
1841
- if (visualIndex > 0) {
1842
- const prev = visualCols[visualIndex - 1];
1843
- this.focusEnd = { ...this.focusEnd, id: prev.id, column: prev.index };
1844
- }
1845
- }
1661
+ this._focus.moveFocusLeft();
1846
1662
  };
1663
+ /** Delegating accessor preserved for backwards compatibility. */
1847
1664
  setFocusLeft = () => {
1848
- if (!this.focusStart) {
1849
- return;
1850
- }
1851
- const visualCols = this._visualColumns;
1852
- const visualIndex = this.columnVisualIndex(this.focusStart.id);
1853
- if (visualIndex > 0) {
1854
- const prev = visualCols[visualIndex - 1];
1855
- this.moveFocus({ ...this.focusStart, id: prev.id, column: prev.index });
1856
- }
1665
+ this._focus.setFocusLeft();
1857
1666
  };
1667
+ /** Delegating accessor preserved for backwards compatibility. */
1858
1668
  moveFocusRight = () => {
1859
- if (!this.allowCopy) {
1860
- return;
1861
- }
1862
- if (this.focusEnd != null) {
1863
- const visualCols = this._visualColumns;
1864
- const visualIndex = this.columnVisualIndex(this.focusEnd.id);
1865
- if (visualIndex < visualCols.length - 1) {
1866
- const next = visualCols[visualIndex + 1];
1867
- this.focusEnd = { ...this.focusEnd, id: next.id, column: next.index };
1868
- }
1869
- }
1669
+ this._focus.moveFocusRight();
1870
1670
  };
1671
+ /** Delegating accessor preserved for backwards compatibility. */
1871
1672
  setFocusRight = () => {
1872
- if (!this.focusStart) {
1873
- return;
1874
- }
1875
- const visualCols = this._visualColumns;
1876
- const visualIndex = this.columnVisualIndex(this.focusStart.id);
1877
- if (visualIndex < visualCols.length - 1) {
1878
- const next = visualCols[visualIndex + 1];
1879
- this.moveFocus({ ...this.focusStart, id: next.id, column: next.index });
1880
- }
1673
+ this._focus.setFocusRight();
1881
1674
  };
1675
+ /** Delegating accessor preserved for backwards compatibility. */
1882
1676
  endFocus = (coords) => {
1883
- if (this.isMousingDown) {
1884
- this.isMousingDown = false;
1885
- if (!this.allowCopy) {
1886
- return;
1887
- }
1888
- this.focusEnd = { ...coords };
1889
- }
1677
+ this._focus.endFocus(coords);
1890
1678
  };
1679
+ /** Toggles compact row sizing and persists to localStorage. */
1891
1680
  setIsCompact = (value) => {
1892
1681
  this.isCompact = value;
1893
1682
  this._updateLocalStorageSettings();
1894
1683
  };
1684
+ /** Toggles alternating row colours and persists to localStorage. */
1895
1685
  setAlternateRowColours = (value) => {
1896
1686
  this.alternateRowColours = value;
1897
1687
  this._updateLocalStorageSettings();
1898
1688
  };
1689
+ /** Toggles single-click editing and persists to localStorage. */
1899
1690
  setSingleClickEditing = (value) => {
1900
1691
  this.singleClickEditing = value;
1901
1692
  this._updateLocalStorageSettings();
1902
1693
  };
1694
+ /** Toggles row-hover colour change and persists to localStorage. */
1903
1695
  setChangeColourOnHover = (value) => {
1904
1696
  this.changeColourOnHover = value;
1905
1697
  this._updateLocalStorageSettings();
1906
1698
  };
1699
+ /** Toggles whether copy-to-clipboard prepends a header row and persists. */
1907
1700
  setCopyWithHeaders = (value) => {
1908
1701
  if (this.allowCopy) {
1909
1702
  this.copyWithHeaders = value;
1910
1703
  this._updateLocalStorageSettings();
1911
1704
  }
1912
1705
  };
1706
+ /** Toggles CSV vs TSV copy-to-clipboard separator and persists. */
1913
1707
  setCommaSeparatedCopy = (value) => {
1914
1708
  if (this.allowCopy) {
1915
1709
  this.commaSeparatedCopy = value;
@@ -1929,63 +1723,41 @@ export class TableContext {
1929
1723
  // });
1930
1724
  // }
1931
1725
  // };
1726
+ /**
1727
+ * Delegating method preserved for backwards compatibility. Applies
1728
+ * the preset to the live column layout, forwards the preset's
1729
+ * `sortingState` to the data-repository updater, and persists the
1730
+ * selected name. The underlying implementation lives on
1731
+ * `_preferences.setSelectedPreset` (Phase 6.4).
1732
+ *
1733
+ * The pre-extraction debug warning ("no sorting state found") was
1734
+ * emitted unconditionally when `paginationRepo.updateSorting` was
1735
+ * absent; the sub-context skips that warning because the
1736
+ * `applySortingState` thunk already no-ops in that case. The
1737
+ * warning is reinstated below so the surface is byte-identical.
1738
+ */
1932
1739
  setSelectedPreset = (preset) => {
1933
- if (this.debug) {
1934
- console.log('setting selected preset:', preset);
1935
- }
1936
- this.columnDefs = preset.columnDefs;
1937
1740
  if (!this.paginationRepo?.updateSorting) {
1938
1741
  console.warn('no sorting state found');
1939
1742
  }
1940
- this.paginationRepo?.updateSorting?.(preset.sortingState);
1941
- this.columnVisibility = preset.visibilityState;
1942
- this.columnPinning = preset.columnPinningState;
1943
- this.selectedPreset = preset;
1944
- localStorage.setItem(`${this.userId}-${this.tableName}-selected-preset-name`, preset.name);
1743
+ this._preferences.setSelectedPreset(preset);
1945
1744
  };
1745
+ /**
1746
+ * Delegating method preserved for backwards compatibility. Adds or
1747
+ * replaces a saved preset for the current column layout and
1748
+ * persists the full preset list to localStorage. Delegates to
1749
+ * `_preferences.saveColumnPreset` (Phase 6.4).
1750
+ */
1946
1751
  saveColumnPreset = (name) => {
1947
- if (!name) {
1948
- return;
1949
- }
1950
- const visibilityState = this.columnVisibility;
1951
- const columnPinningState = this.columnPinning;
1952
- const columnDefs = this.columnDefs;
1953
- const sortingState = this.paginationRepo?.sorting || [];
1954
- const newPreset = { columnDefs, name, visibilityState, columnPinningState, sortingState };
1955
- const lowercasePresetname = name.toLocaleLowerCase();
1956
- const indexToReplace = this.columnPresets.findIndex((oldPreset) => oldPreset.name.toLocaleLowerCase() === lowercasePresetname);
1957
- const newPresets = this.columnPresets.concat();
1958
- if (indexToReplace > -1) {
1959
- newPresets[indexToReplace] = newPreset;
1960
- }
1961
- else {
1962
- newPresets.push(newPreset);
1963
- }
1964
- this.columnPresets = newPresets;
1965
- const jsonFriendly = newPresets.map((preset) => {
1966
- return {
1967
- ...preset,
1968
- columnDefs: preset.columnDefs.map((it) => {
1969
- return { ...it.toJSON(), header: it.header.toString() };
1970
- }),
1971
- columnPinningState: {
1972
- left: [...(preset.columnPinningState.left?.entries() ?? [])],
1973
- right: [...(preset.columnPinningState.right?.entries() ?? [])],
1974
- },
1975
- };
1976
- });
1977
- const stringified = JSON.stringify(jsonFriendly);
1978
- localStorage.setItem(`${this.userId}-${this.tableName}-presets`, stringified);
1979
- this.setSelectedPreset(newPreset);
1752
+ this._preferences.saveColumnPreset(name);
1980
1753
  };
1981
1754
  /**
1982
1755
  * This listener should be added and removed based on isMousingDown
1983
1756
  */
1757
+ /** Delegating accessor preserved for backwards compatibility. The
1758
+ * implementation lives on `_focus.setFocusEnd` (Phase 6.6). */
1984
1759
  setFocusEnd = (coords) => {
1985
- if (!this.allowCopy) {
1986
- return;
1987
- }
1988
- this.focusEnd = coords;
1760
+ this._focus.setFocusEnd(coords);
1989
1761
  };
1990
1762
  // #region private methods
1991
1763
  createRow(item, index, unsortedIndex) {
@@ -2048,11 +1820,36 @@ export class TableContext {
2048
1820
  }
2049
1821
  return makeRow(def);
2050
1822
  };
2051
- const leftVisibleCells = $derived([...(this.columnPinning.left?.keys() ?? [])].map(makeCellFromId));
2052
- const rightVisibleCells = $derived([...(this.columnPinning.right?.keys() ?? [])].map(makeCellFromId));
1823
+ // Visibility filter applied to every cell collection so the cell
1824
+ // count in TableRow stays in sync with the visible-column count
1825
+ // that drives `--cols` in Table.svelte. Without this, a hidden
1826
+ // non-pinned column would still produce an empty cell wrapper in
1827
+ // the row, which would overflow the grid template and force the
1828
+ // extra cell onto an implicit grid row (visually splitting each
1829
+ // data row into two or three lines).
1830
+ //
1831
+ // Iterating `this.columnDefs.filter(...)` instead of
1832
+ // `this.columnPinning.left?.keys()` puts pinned-left body cells
1833
+ // in declaration order (matching the header rendering and the
1834
+ // `getVisualColumns` tab-order). Iterating the pinning Map's
1835
+ // keys produced *insertion* order: pinning Name then ID gave a
1836
+ // body row of [Name, ID] while the header row above it was
1837
+ // [ID, Name], and tab navigation walked [Name, ID] instead of
1838
+ // the visually-left-to-right [ID, Name].
1839
+ const leftVisibleCells = $derived(this.columnDefs
1840
+ .filter((it) => {
1841
+ return (this.columnVisibility[it.id] !== false && this.columnPinning.left?.has(it.id) === true);
1842
+ })
1843
+ .map(makeRow));
1844
+ const rightVisibleCells = $derived(this.columnDefs
1845
+ .filter((it) => {
1846
+ return (this.columnVisibility[it.id] !== false && this.columnPinning.right?.has(it.id) === true);
1847
+ })
1848
+ .map(makeRow));
2053
1849
  const centerVisibleCells = $derived(this.columnDefs
2054
1850
  .filter((it) => {
2055
- return !(this.columnPinning.left?.has(it.id) || this.columnPinning.right?.has(it.id));
1851
+ return (this.columnVisibility[it.id] !== false &&
1852
+ !(this.columnPinning.left?.has(it.id) || this.columnPinning.right?.has(it.id)));
2056
1853
  })
2057
1854
  .map(makeRow));
2058
1855
  return row;
@@ -2062,87 +1859,57 @@ export class TableContext {
2062
1859
  this.columnVisibility[it.id] !== false &&
2063
1860
  !uncopyableColumns.has(it.columnType));
2064
1861
  };
1862
+ /**
1863
+ * Builds the user-controlled settings blob from the current
1864
+ * `$state` slots and delegates the actual `localStorage.setItem`
1865
+ * call to `_preferences.persistSettings` (Phase 6.4). The settings
1866
+ * field set (`isCompact`, `singleClickEditing`, etc.) still lives
1867
+ * on `TableContext` because each one drives reactive UI; only the
1868
+ * persistence side of the boundary moved.
1869
+ */
2065
1870
  _updateLocalStorageSettings() {
2066
- const isCompact = this.isCompact;
2067
- const singleClickEditing = this.singleClickEditing;
2068
- const alternateRowColours = this.alternateRowColours;
2069
- const copyWithHeaders = this.copyWithHeaders;
2070
- const commaSeparatedCopy = this.commaSeparatedCopy;
2071
- const changeColourOnHover = this.changeColourOnHover;
2072
1871
  const settingsObject = {
2073
- isCompact,
2074
- singleClickEditing,
2075
- alternateRowColours,
2076
- changeColourOnHover,
2077
- copyWithHeaders,
2078
- commaSeparatedCopy,
1872
+ isCompact: this.isCompact,
1873
+ singleClickEditing: this.singleClickEditing,
1874
+ alternateRowColours: this.alternateRowColours,
1875
+ changeColourOnHover: this.changeColourOnHover,
1876
+ copyWithHeaders: this.copyWithHeaders,
1877
+ commaSeparatedCopy: this.commaSeparatedCopy,
2079
1878
  };
2080
- localStorage.setItem(`${this.userId}-${this.tableName}-settings`, JSON.stringify(settingsObject));
1879
+ this._preferences.persistSettings(settingsObject);
2081
1880
  }
2082
1881
  /**
2083
- * Columns in visual display order: pinned-left → center → pinned-right,
2084
- * preserving memory order within each section. Only includes visible columns.
1882
+ * Delegating accessor preserved for backwards compatibility. The
1883
+ * cached visual-column array and its paired O(1) id->index map now
1884
+ * live on `_columnLayout` (Phase 6.3). The underscore prefix is
1885
+ * preserved on `TableContext` to keep the existing read sites in
1886
+ * `tabForward` / `focusToStart` / `moveFocusLeft` / etc. untouched.
2085
1887
  */
2086
1888
  get _visualColumns() {
2087
- const visibility = this.columnVisibility;
2088
- const pinLeft = this.columnPinning.left;
2089
- const pinRight = this.columnPinning.right;
2090
- const visible = this.columnDefs.filter((it) => visibility[it.id] !== false);
2091
- return [
2092
- ...visible.filter((it) => pinLeft?.has(it.id)),
2093
- ...visible.filter((it) => !pinLeft?.has(it.id) && !pinRight?.has(it.id)),
2094
- ...visible.filter((it) => pinRight?.has(it.id)),
2095
- ];
1889
+ return this._columnLayout.visualColumns;
2096
1890
  }
2097
- /** Returns the visual index of the column with the given id, or -1 if not found. */
1891
+ /** Returns the visual index of the column with the given id, or
1892
+ * -1 if not found. Delegates to `_columnLayout.columnVisualIndex`
1893
+ * which provides the O(1) lookup. */
2098
1894
  columnVisualIndex(id) {
2099
- return this._visualColumns.findIndex((it) => it.id === id);
1895
+ return this._columnLayout.columnVisualIndex(id);
2100
1896
  }
1897
+ /**
1898
+ * Delegating getter preserved for backwards compatibility. The
1899
+ * implementation lives on `_focus.focusStartVisualColumn`
1900
+ * (Phase 6.6); the visual-index lookup itself is owned by
1901
+ * `_columnLayout.columnVisualIndex` (Phase 6.3).
1902
+ */
2101
1903
  get focusStartVisualColumn() {
2102
- if (!this.focusStart) {
2103
- return -1;
2104
- }
2105
- return this.columnVisualIndex(this.focusStart.id);
1904
+ return this._focus.focusStartVisualColumn;
2106
1905
  }
1906
+ /**
1907
+ * Delegating getter preserved for backwards compatibility. The
1908
+ * implementation lives on `_focus.focusEndVisualColumn`
1909
+ * (Phase 6.6).
1910
+ */
2107
1911
  get focusEndVisualColumn() {
2108
- if (!this.focusEnd) {
2109
- return -1;
2110
- }
2111
- return this.columnVisualIndex(this.focusEnd.id);
2112
- }
2113
- _moveFocusEndRow(offset) {
2114
- if (!this.allowCopy) {
2115
- return;
2116
- }
2117
- if (!this.focusEnd) {
2118
- console.warn('no focusEnd found');
2119
- return;
2120
- }
2121
- // if (!$effect.tracking()) {
2122
- // throw new Error(`_moveFocusEndRow is not in a tracking context.`);
2123
- // }
2124
- const focusEnd = this.focusEnd;
2125
- const prm = this.paginationRowModel;
2126
- const rows = prm.rows;
2127
- const endRowIndex = rows.findIndex((row) => row.visibleIndex === focusEnd.row);
2128
- this.focusEnd = {
2129
- ...this.focusEnd,
2130
- row: rows[Math.max(Math.min(endRowIndex + offset, rows.length - 1), 0)].visibleIndex,
2131
- };
2132
- }
2133
- _moveFocusRow(offset) {
2134
- if (!this.focusStart) {
2135
- console.warn('no focusStart found');
2136
- return;
2137
- }
2138
- const focusStart = this.focusStart;
2139
- const newEndRowIndex = (() => {
2140
- const prm = this.paginationRowModel;
2141
- const rows = prm.rows;
2142
- const endRowIndex = rows.findIndex((row) => row.visibleIndex === focusStart.row);
2143
- return rows[Math.max(Math.min(endRowIndex + offset, rows.length - 1), 0)].visibleIndex;
2144
- })();
2145
- this.moveFocus({ ...this.focusStart, row: newEndRowIndex });
1912
+ return this._focus.focusEndVisualColumn;
2146
1913
  }
2147
1914
  async getHighlightedRowData() {
2148
1915
  const affectedColumns = this.columnDefs.filter(this.columnDefReturnsTextValue);
@@ -2289,28 +2056,7 @@ export class TableContext {
2289
2056
  };
2290
2057
  }
2291
2058
  updateDefsBasedOnPinning = () => {
2292
- const leftDefs = [];
2293
- const midDefs = [];
2294
- const rightDefs = [];
2295
- this.columnDefs.forEach((def) => {
2296
- if (this.columnPinning.left?.has(def.id)) {
2297
- leftDefs.push(def);
2298
- }
2299
- else if (this.columnPinning.right?.has(def.id)) {
2300
- rightDefs.push(def);
2301
- }
2302
- else {
2303
- midDefs.push(def);
2304
- }
2305
- });
2306
- leftDefs.sort((a, b) => a.index - b.index);
2307
- midDefs.sort((a, b) => a.index - b.index);
2308
- rightDefs.sort((a, b) => a.index - b.index);
2309
- const allDefs = leftDefs.concat(midDefs, rightDefs);
2310
- allDefs.forEach((def, index) => {
2311
- def.index = index;
2312
- });
2313
- return allDefs;
2059
+ return this._columnLayout.updateDefsBasedOnPinning();
2314
2060
  };
2315
2061
  assertNoDuplicateIds() {
2316
2062
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
@@ -2322,10 +2068,8 @@ export class TableContext {
2322
2068
  foundIds.add(def.id);
2323
2069
  });
2324
2070
  }
2325
- // FIXME
2326
2071
  // eslint-disable-next-line @typescript-eslint/require-await
2327
2072
  async setupDefaultColumnDefs() {
2328
- // const helper = createColumnHelper<T>();
2329
2073
  this.assertNoDuplicateIds();
2330
2074
  const adjustedDefsPromises = this.defaultColumnDefs.defs.map((def) => {
2331
2075
  if (def instanceof AccessorDef) {
@@ -2460,47 +2204,44 @@ export class TableContext {
2460
2204
  this.columnDefs = adjustedDefs;
2461
2205
  return adjustedDefs;
2462
2206
  }
2207
+ /**
2208
+ * Reads the persisted per-table user-settings blob through
2209
+ * `_preferences.loadSavedSettings` (Phase 6.4) and applies each
2210
+ * field to the live `$state` slot. Field defaults mirror the
2211
+ * pre-extraction behaviour:
2212
+ *
2213
+ * - missing slot: `singleClickEditing` defaults to `true`; every
2214
+ * other field keeps its constructor default.
2215
+ * - present slot: each field is applied if it would deviate from
2216
+ * the constructor default (the "only alter when the user
2217
+ * explicitly disabled" semantics are preserved verbatim).
2218
+ */
2463
2219
  _getUserDefaults() {
2464
- const savedSettings = localStorage.getItem(`${this.userId}-${this.tableName}-settings`);
2465
- if (savedSettings) {
2466
- const saved = JSON.parse(savedSettings);
2467
- this.singleClickEditing = saved.singleClickEditing ?? true;
2468
- if (saved.alternateRowColours) {
2469
- this.alternateRowColours = true;
2470
- }
2471
- if (saved.commaSeparatedCopy) {
2472
- this.commaSeparatedCopy = true;
2473
- }
2474
- // these are true by default so we only want to alter them if the user wants them disabled
2475
- if (saved.isCompact === false) {
2476
- this.isCompact = false;
2477
- }
2478
- if (saved.copyWithHeaders === false) {
2479
- this.copyWithHeaders = false;
2480
- }
2481
- if (saved.changeColourOnHover === false) {
2482
- this.changeColourOnHover = false;
2483
- }
2484
- }
2485
- else {
2220
+ const saved = this._preferences.loadSavedSettings();
2221
+ if (saved === null) {
2486
2222
  this.singleClickEditing = true;
2223
+ return;
2224
+ }
2225
+ this.singleClickEditing = saved.singleClickEditing ?? true;
2226
+ if (saved.alternateRowColours) {
2227
+ this.alternateRowColours = true;
2228
+ }
2229
+ if (saved.commaSeparatedCopy) {
2230
+ this.commaSeparatedCopy = true;
2231
+ }
2232
+ // these are true by default so we only want to alter them if the user wants them disabled
2233
+ if (saved.isCompact === false) {
2234
+ this.isCompact = false;
2235
+ }
2236
+ if (saved.copyWithHeaders === false) {
2237
+ this.copyWithHeaders = false;
2238
+ }
2239
+ if (saved.changeColourOnHover === false) {
2240
+ this.changeColourOnHover = false;
2487
2241
  }
2488
2242
  }
2489
2243
  _setNewPinningState() {
2490
- this.columnPinning = {
2491
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
2492
- left: new Map(this.columnPinning.left &&
2493
- this.columnDefs
2494
- .filter((col) => {
2495
- return this.columnPinning.left?.has(col.id);
2496
- })
2497
- .map((col, index) => [col.id, index])),
2498
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
2499
- right: new Map(this.columnPinning.right &&
2500
- this.columnDefs
2501
- .filter((col) => this.columnPinning.right?.has(col.id))
2502
- .map((col, index) => [col.id, index])),
2503
- }; // TODO
2244
+ this._columnLayout.setNewPinningState();
2504
2245
  }
2505
2246
  getToggleSortingHandler = (def) => () => {
2506
2247
  this.paginationRepo?.updateSorting?.((state) => {
@@ -2524,7 +2265,6 @@ export class TableContext {
2524
2265
  return newState;
2525
2266
  });
2526
2267
  };
2527
- // FIXME
2528
2268
  // eslint-disable-next-line @typescript-eslint/require-await
2529
2269
  async reassignDefsToPresets(presets) {
2530
2270
  const adjustedDefs = this.columnDefs;
@@ -2591,9 +2331,51 @@ export function isSearchable(def) {
2591
2331
  function hasPagination(repository) {
2592
2332
  return 'setPage' in repository;
2593
2333
  }
2334
+ /**
2335
+ * Neutralises CSV / TSV formula-injection prefixes recognised by Excel,
2336
+ * LibreOffice Calc, Google Sheets, and Apple Numbers.
2337
+ *
2338
+ * When a clipboard payload reaches a spreadsheet, any cell whose first
2339
+ * character is one of `=`, `+`, `-`, `@`, `\t`, `\r` is treated as a
2340
+ * formula and evaluated in the user's context. Untrusted row data
2341
+ * containing `=cmd|'/c calc'!A1` (or DDE / web-query equivalents) is the
2342
+ * canonical attack vector. Prefixing such a value with a single quote
2343
+ * (`'`) makes the spreadsheet interpret the cell as a literal string
2344
+ * instead.
2345
+ *
2346
+ * Trade-off: the leading apostrophe is invisible in the rendered cell of
2347
+ * every major spreadsheet, but the underlying cell value differs from the
2348
+ * source when read back by another formula referencing the cell. This is
2349
+ * the accepted industry mitigation (OWASP CSV Injection cheat sheet).
2350
+ *
2351
+ * Private helper - keep this colocated with `encapsulate` so every
2352
+ * clipboard-bound string flows through both functions before being
2353
+ * written to the OS clipboard.
2354
+ */
2355
+ function escapeForSpreadsheet(value) {
2356
+ if (value.length === 0) {
2357
+ return value;
2358
+ }
2359
+ const first = value.charCodeAt(0);
2360
+ // '=' 61, '+' 43, '-' 45, '@' 64, '\t' 9, '\r' 13
2361
+ if (first === 61 || first === 43 || first === 45 || first === 64 || first === 9 || first === 13) {
2362
+ return `'${value}`;
2363
+ }
2364
+ return value;
2365
+ }
2594
2366
  function encapsulate(value) {
2595
- return `"${value.replace(/[\n]/gm, ' ').replace(/["]/g, '""')}"`;
2367
+ const safe = escapeForSpreadsheet(value);
2368
+ return `"${safe.replace(/[\n]/gm, ' ').replace(/["]/g, '""')}"`;
2596
2369
  }
2370
+ /**
2371
+ * Internal exports for tests. Not part of the public API; do not import
2372
+ * from consumer code. Allows the clipboard formula-escape regression test
2373
+ * to call the module-private helpers directly.
2374
+ */
2375
+ export const _internal = {
2376
+ escapeForSpreadsheet,
2377
+ encapsulate,
2378
+ };
2597
2379
  function generateTableOptions(contextObj) {
2598
2380
  const _pagination = $derived.by(() => {
2599
2381
  void contextObj.paginationRepo?.paginationState.pageSize;
@@ -2616,6 +2398,22 @@ function generateTableOptions(contextObj) {
2616
2398
  let originalWidth = 0;
2617
2399
  let mouseStartingPoint = 0;
2618
2400
  function addOverrideResizeHandler(e) {
2401
+ // Capture the column's currently rendered width as the
2402
+ // drag's starting point, then mark this column as
2403
+ // user-resized. Going forward the grid template will use a
2404
+ // fixed `${width}px` track for this column (see
2405
+ // `Table.svelte` colsTemplate) so dragging the handle only
2406
+ // changes this column. Other columns that have not yet
2407
+ // been user-resized continue to flex via `minmax(W, 1fr)`,
2408
+ // which means they automatically take up any horizontal
2409
+ // space the resized column gives back when shrunk - the
2410
+ // table never ends up narrower than its container while
2411
+ // any column still flexes.
2412
+ const rendered = contextObj._columnRenderedWidths.get(def.id);
2413
+ if (rendered != null && rendered > 0) {
2414
+ def.width = rendered;
2415
+ }
2416
+ def.userResized = true;
2619
2417
  originalWidth = def.width;
2620
2418
  mouseStartingPoint = e.clientX;
2621
2419
  def.isResizing = true;
@@ -2623,7 +2421,14 @@ function generateTableOptions(contextObj) {
2623
2421
  window.addEventListener('mouseup', removeOverrideResizeHandler);
2624
2422
  }
2625
2423
  function onResize(e) {
2626
- def.width = originalWidth - (mouseStartingPoint - e.clientX);
2424
+ // Clamp the resize floor so the column can never reach a
2425
+ // negative or zero width. With the div+grid layout a
2426
+ // non-positive `width` produces an invalid track size
2427
+ // in `grid-template-columns`, which makes the browser
2428
+ // fall back to single-column block layout and visually
2429
+ // collapses the row.
2430
+ const next = originalWidth - (mouseStartingPoint - e.clientX);
2431
+ def.width = Math.max(next, 20);
2627
2432
  }
2628
2433
  function removeOverrideResizeHandler() {
2629
2434
  def.isResizing = false;
@@ -2753,111 +2558,6 @@ function generateTableOptions(contextObj) {
2753
2558
  // }
2754
2559
  };
2755
2560
  }
2756
- function checkboxSortingFn(id, desc) {
2757
- return desc
2758
- ? (a, b) => {
2759
- if (a[id] && !b[id]) {
2760
- return 1;
2761
- }
2762
- if (!a[id] && b[id]) {
2763
- return -1;
2764
- }
2765
- return 0;
2766
- }
2767
- : (a, b) => {
2768
- if (a[id] && !b[id]) {
2769
- return -1;
2770
- }
2771
- if (!a[id] && b[id]) {
2772
- return 1;
2773
- }
2774
- return 0;
2775
- };
2776
- }
2777
- function comparableSortingFn(id, desc) {
2778
- return desc
2779
- ? (a, b) => {
2780
- if (a[id] == undefined && b[id] != undefined) {
2781
- return 1;
2782
- }
2783
- if (b[id] == undefined && a[id] != undefined) {
2784
- return -1;
2785
- }
2786
- if (a[id] < b[id]) {
2787
- return 1;
2788
- }
2789
- if (a[id] > b[id]) {
2790
- return -1;
2791
- }
2792
- return 0;
2793
- }
2794
- : (a, b) => {
2795
- if (a[id] == undefined && b[id] != undefined) {
2796
- return 1;
2797
- }
2798
- if (b[id] == undefined && a[id] != undefined) {
2799
- return -1;
2800
- }
2801
- if (a[id] < b[id]) {
2802
- return -1;
2803
- }
2804
- if (a[id] > b[id]) {
2805
- return 1;
2806
- }
2807
- return 0;
2808
- };
2809
- }
2810
- function progressSortingFn(id, desc) {
2811
- return desc
2812
- ? (a, b) => {
2813
- const [minA, maxA] = a[id];
2814
- const [minB, maxB] = b[id];
2815
- if (minA == undefined || maxA == undefined || maxA == 0) {
2816
- if (maxB != undefined && maxB != 0) {
2817
- return 1;
2818
- }
2819
- else {
2820
- return 0;
2821
- }
2822
- }
2823
- if (minB == undefined || maxB == undefined || maxB == 0) {
2824
- return -1;
2825
- }
2826
- const valueA = minA / maxA;
2827
- const valueB = minB / maxB;
2828
- if (valueA < valueB) {
2829
- return 1;
2830
- }
2831
- if (valueB < valueA) {
2832
- return -1;
2833
- }
2834
- return 0;
2835
- }
2836
- : (a, b) => {
2837
- const [minA, maxA] = a[id];
2838
- const [minB, maxB] = b[id];
2839
- if (minA == undefined || maxA == undefined || maxA == 0) {
2840
- if (maxB != undefined && maxB != 0) {
2841
- return -1;
2842
- }
2843
- else {
2844
- return 0;
2845
- }
2846
- }
2847
- if (minB == undefined || maxB == undefined || maxB == 0) {
2848
- return 1;
2849
- }
2850
- const valueA = minA / maxA;
2851
- const valueB = minB / maxB;
2852
- if (valueA < valueB) {
2853
- return -1;
2854
- }
2855
- if (valueB < valueA) {
2856
- return 1;
2857
- }
2858
- return 0;
2859
- };
2860
- }
2861
2561
  function adaptJSONPinningState(state) {
2862
2562
  //HACK ignore previously broken pinning states
2863
2563
  return {