@lavalogic/scoria 0.36.0 → 0.37.1

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 (552) hide show
  1. package/dist/Components/AccordionGroup.svelte +6 -2
  2. package/dist/Components/AccordionGroupButton.svelte +78 -78
  3. package/dist/Components/AccordionGroupButtonProps.d.ts +27 -8
  4. package/dist/Components/AccordionGroupProps.d.ts +10 -2
  5. package/dist/Components/Action.svelte +31 -33
  6. package/dist/Components/ActionProps.d.ts +23 -3
  7. package/dist/Components/AlertModal.svelte +90 -39
  8. package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte +1126 -0
  9. package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte.d.ts +4 -0
  10. package/dist/Components/AppointmentCalendar/AppointmentCalendarProps.d.ts +92 -0
  11. package/dist/Components/AppointmentCalendar/AppointmentCalendarProps.js +12 -0
  12. package/dist/Components/Base/BaseModal.svelte +172 -0
  13. package/dist/Components/Base/BaseModal.svelte.d.ts +4 -0
  14. package/dist/Components/Base/BaseModalProps.d.ts +73 -0
  15. package/dist/Components/Base/BaseModalProps.js +1 -0
  16. package/dist/Components/Base/ContextWrapper.svelte +37 -15
  17. package/dist/Components/Base/ContextWrapper.svelte.d.ts +24 -5
  18. package/dist/Components/Base/ContextWrapperProps.d.ts +22 -0
  19. package/dist/Components/Base/Pagination.svelte +70 -62
  20. package/dist/Components/Base/Pagination.svelte.d.ts +12 -2
  21. package/dist/Components/Base/PaginationProps.d.ts +60 -3
  22. package/dist/Components/Base/Snippets.svelte +7 -0
  23. package/dist/Components/Base/Snippets.svelte.d.ts +5 -1
  24. package/dist/Components/BigRadioSet.svelte +18 -9
  25. package/dist/Components/BigRadioSetProps.d.ts +17 -1
  26. package/dist/Components/Bubble.svelte +67 -34
  27. package/dist/Components/Bubble.svelte.d.ts +23 -2
  28. package/dist/Components/BubbleProps.d.ts +55 -4
  29. package/dist/Components/Button.svelte +50 -38
  30. package/dist/Components/ButtonProps.d.ts +66 -1
  31. package/dist/Components/Checkbox.svelte +57 -29
  32. package/dist/Components/Checkbox.svelte.d.ts +1 -12
  33. package/dist/Components/CheckboxProps.d.ts +27 -0
  34. package/dist/Components/CheckboxProps.js +1 -0
  35. package/dist/Components/CollapsibleCard.svelte +53 -22
  36. package/dist/Components/CollapsibleCardProps.d.ts +22 -1
  37. package/dist/Components/Contexts/ContextMenu.svelte +53 -32
  38. package/dist/Components/Contexts/ContextMenu.svelte.d.ts +26 -0
  39. package/dist/Components/Contexts/ContextMenuDivider.svelte +15 -7
  40. package/dist/Components/Contexts/ContextMenuDivider.svelte.d.ts +9 -0
  41. package/dist/Components/Contexts/ContextMenuOption.svelte +27 -18
  42. package/dist/Components/Contexts/ContextMenuOption.svelte.d.ts +12 -1
  43. package/dist/Components/Contexts/ContextMenuOptionProps.d.ts +34 -2
  44. package/dist/Components/Contexts/Toast.svelte +28 -4
  45. package/dist/Components/Contexts/Toast.svelte.d.ts +14 -0
  46. package/dist/Components/Contexts/ToastContainer.svelte +30 -11
  47. package/dist/Components/Contexts/ToastContainer.svelte.d.ts +14 -0
  48. package/dist/Components/Contexts/ToastProps.d.ts +14 -1
  49. package/dist/Components/Contexts/Tooltip.svelte +21 -10
  50. package/dist/Components/Contexts/Tooltip.svelte.d.ts +10 -0
  51. package/dist/Components/DataMatrixIcon.svelte +82 -11
  52. package/dist/Components/DataMatrixIconProps.d.ts +73 -1
  53. package/dist/Components/DateInput.svelte +64 -29
  54. package/dist/Components/DateInput.svelte.d.ts +1 -1
  55. package/dist/Components/DateOnlyInputProps.d.ts +31 -5
  56. package/dist/Components/DatePicker.svelte +96 -108
  57. package/dist/Components/DatePickerProps.d.ts +18 -1
  58. package/dist/Components/DateTimeInputProps.d.ts +30 -5
  59. package/dist/Components/DesktopModal.svelte +14 -43
  60. package/dist/Components/DesktopModalProps.d.ts +5 -4
  61. package/dist/Components/Dial.svelte +103 -41
  62. package/dist/Components/DialProps.d.ts +74 -2
  63. package/dist/Components/DimensionInput.svelte +50 -64
  64. package/dist/Components/DimensionInput.svelte.d.ts +1 -1
  65. package/dist/Components/DimensionInputProps.d.ts +39 -4
  66. package/dist/Components/DragMenu/DragMenuHolder.svelte +41 -29
  67. package/dist/Components/DragMenu/DragMenuHolderProps.d.ts +30 -1
  68. package/dist/Components/DragMenu/DraggableCard.svelte +44 -8
  69. package/dist/Components/DragMenu/DraggableCardProps.d.ts +27 -1
  70. package/dist/Components/FileCard.svelte +6 -2
  71. package/dist/Components/FileCardProps.d.ts +17 -1
  72. package/dist/Components/FileUpload.svelte +82 -42
  73. package/dist/Components/FileUploadProps.d.ts +34 -4
  74. package/dist/Components/GridInput.svelte +27 -17
  75. package/dist/Components/GridInput.svelte.d.ts +5 -5
  76. package/dist/Components/GridInputProps.d.ts +4 -1
  77. package/dist/Components/GridInputSet.svelte +12 -5
  78. package/dist/Components/GridInputSet.svelte.d.ts +5 -5
  79. package/dist/Components/GridInputSetProps.d.ts +4 -2
  80. package/dist/Components/HorizontalTabGroup.svelte +13 -11
  81. package/dist/Components/HorizontalTabGroupButton.svelte +16 -68
  82. package/dist/Components/HorizontalTabGroupProps.d.ts +15 -2
  83. package/dist/Components/Icon.svelte +62 -21
  84. package/dist/Components/IconProps.d.ts +54 -6
  85. package/dist/Components/Icons.d.ts +8 -0
  86. package/dist/Components/Icons.js +234 -215
  87. package/dist/Components/LoadingAnimation.svelte +18 -18
  88. package/dist/Components/LoadingAnimation.svelte.d.ts +1 -3
  89. package/dist/Components/LoadingAnimationProps.d.ts +16 -0
  90. package/dist/Components/LoadingAnimationProps.js +1 -0
  91. package/dist/Components/LoadingModal.svelte +18 -20
  92. package/dist/Components/LoadingModal.svelte.d.ts +1 -4
  93. package/dist/Components/LoadingModalProps.d.ts +17 -0
  94. package/dist/Components/LoadingModalProps.js +1 -0
  95. package/dist/Components/LoadingOverlay.svelte +11 -13
  96. package/dist/Components/LoadingOverlay.svelte.d.ts +1 -5
  97. package/dist/Components/LoadingOverlayProps.d.ts +28 -0
  98. package/dist/Components/LoadingOverlayProps.js +1 -0
  99. package/dist/Components/Modal.svelte +77 -82
  100. package/dist/Components/Modal.svelte.d.ts +1 -25
  101. package/dist/Components/ModalProps.d.ts +65 -0
  102. package/dist/Components/ModalProps.js +1 -0
  103. package/dist/Components/MultiSelect.svelte +88 -76
  104. package/dist/Components/MultiSelectCommonProps.d.ts +33 -4
  105. package/dist/Components/MultiSelectPropsAsObject.d.ts +5 -0
  106. package/dist/Components/MultiSelectPropsAsPrimitive.d.ts +6 -0
  107. package/dist/Components/Nav/CallbackButtonWithIcon.d.ts +22 -1
  108. package/dist/Components/Nav/Nav.svelte +35 -30
  109. package/dist/Components/Nav/Nav.svelte.d.ts +23 -2
  110. package/dist/Components/Nav/NavProps.d.ts +50 -3
  111. package/dist/Components/Nav/NavServiceMenuList.svelte +12 -3
  112. package/dist/Components/Nav/NavServiceMenuListProps.d.ts +25 -1
  113. package/dist/Components/Nav/NavTab.svelte +19 -48
  114. package/dist/Components/Nav/NavTab.svelte.d.ts +23 -2
  115. package/dist/Components/Nav/NavTabProps.d.ts +16 -2
  116. package/dist/Components/Nav/ServiceMenuItem.svelte +29 -20
  117. package/dist/Components/Nav/ServicesNav.svelte +60 -34
  118. package/dist/Components/Nav/ServicesNav.svelte.d.ts +23 -2
  119. package/dist/Components/Nav/ServicesNavProps.d.ts +20 -4
  120. package/dist/Components/Nav/SideMenuItemProps.d.ts +16 -0
  121. package/dist/Components/NumberInput.svelte +37 -27
  122. package/dist/Components/NumberInputProps.d.ts +37 -0
  123. package/dist/Components/PageHeading.svelte +2 -13
  124. package/dist/Components/PageHeading.svelte.d.ts +1 -6
  125. package/dist/Components/PageHeadingProps.d.ts +23 -0
  126. package/dist/Components/PageHeadingProps.js +1 -0
  127. package/dist/Components/PasswordInput.svelte +27 -15
  128. package/dist/Components/PasswordInput.svelte.d.ts +2 -2
  129. package/dist/Components/PasswordInputProps.d.ts +21 -0
  130. package/dist/Components/PasswordInputProps.js +1 -0
  131. package/dist/Components/Portal.svelte +14 -3
  132. package/dist/Components/PortalProps.d.ts +20 -1
  133. package/dist/Components/ProgressBubble.svelte +98 -53
  134. package/dist/Components/ProgressBubbleProps.d.ts +23 -2
  135. package/dist/Components/QuerySelect.svelte +103 -159
  136. package/dist/Components/QuerySelect.svelte.d.ts +2 -29
  137. package/dist/Components/QuerySelectProps.d.ts +66 -0
  138. package/dist/Components/QuerySelectProps.js +1 -0
  139. package/dist/Components/SingleSelect.svelte +86 -108
  140. package/dist/Components/SingleSelectCommonProps.d.ts +43 -5
  141. package/dist/Components/SingleSelectPropsAsObject.d.ts +7 -0
  142. package/dist/Components/SingleSelectPropsAsPrimitive.d.ts +8 -0
  143. package/dist/Components/StepButton.svelte +19 -69
  144. package/dist/Components/StepForm.svelte +52 -52
  145. package/dist/Components/StepFormProps.d.ts +16 -2
  146. package/dist/Components/Switch.svelte +68 -31
  147. package/dist/Components/Switch.svelte.d.ts +1 -1
  148. package/dist/Components/SwitchProps.d.ts +46 -0
  149. package/dist/Components/Table/Body/HeightData.d.ts +1 -6
  150. package/dist/Components/Table/Body/HeightData.js +8 -0
  151. package/dist/Components/Table/Body/QuickSearchCell.svelte +106 -50
  152. package/dist/Components/Table/Body/QuickSearchCell.svelte.d.ts +12 -11
  153. package/dist/Components/Table/Body/QuickSearchCellProps.d.ts +6 -0
  154. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +43 -9
  155. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte.d.ts +24 -2
  156. package/dist/Components/Table/Body/Rows/ColumnHighlightProps.d.ts +3 -0
  157. package/dist/Components/Table/Body/Rows/ColumnList.svelte +15 -5
  158. package/dist/Components/Table/Body/Rows/ColumnList.svelte.d.ts +12 -11
  159. package/dist/Components/Table/Body/Rows/ColumnListProps.d.ts +1 -0
  160. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +437 -91
  161. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte.d.ts +12 -11
  162. package/dist/Components/Table/Body/Rows/ColumnListRowProps.d.ts +5 -0
  163. package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +43 -13
  164. package/dist/Components/Table/Body/Rows/Columns/AccessorComponentProps.d.ts +12 -0
  165. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +50 -38
  166. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte.d.ts +12 -12
  167. package/dist/Components/Table/Body/Rows/Columns/ActionsColumn.d.ts +9 -0
  168. package/dist/Components/Table/Body/Rows/Columns/ActionsTableCell.d.ts +5 -0
  169. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +61 -26
  170. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte.d.ts +14 -13
  171. package/dist/Components/Table/Body/Rows/Columns/BubbleCellProps.d.ts +12 -0
  172. package/dist/Components/Table/Body/Rows/Columns/BubbleCellProps.js +1 -0
  173. package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +36 -16
  174. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +133 -26
  175. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte.d.ts +12 -11
  176. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCellProps.d.ts +7 -0
  177. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +54 -20
  178. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte.d.ts +17 -11
  179. package/dist/Components/Table/Body/Rows/Columns/RowSelectionColumn.d.ts +8 -0
  180. package/dist/Components/Table/Body/Rows/Columns/RowSelectionTableCell.d.ts +5 -0
  181. package/dist/Components/Table/Body/Rows/Columns/SelectColumn.d.ts +11 -0
  182. package/dist/Components/Table/Body/Rows/Columns/SelectTableCell.d.ts +11 -0
  183. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +55 -40
  184. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte.d.ts +11 -0
  185. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +38 -27
  186. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte.d.ts +13 -18
  187. package/dist/Components/Table/Body/Rows/Columns/TableCheckboxProps.d.ts +17 -0
  188. package/dist/Components/Table/Body/Rows/Columns/TableCheckboxProps.js +1 -0
  189. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +22 -7
  190. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte.d.ts +1 -7
  191. package/dist/Components/Table/Body/Rows/Columns/TableColumnProps.d.ts +16 -0
  192. package/dist/Components/Table/Body/Rows/Columns/TableColumnProps.js +1 -0
  193. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +61 -31
  194. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte.d.ts +12 -11
  195. package/dist/Components/Table/Body/Rows/Columns/TableDatePickerProps.d.ts +9 -0
  196. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +123 -244
  197. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte.d.ts +20 -12
  198. package/dist/Components/Table/Body/Rows/Columns/TableQueryCell.d.ts +8 -0
  199. package/dist/Components/Table/Body/Rows/Columns/TableQueryColumn.d.ts +9 -0
  200. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +126 -189
  201. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte.d.ts +12 -11
  202. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelectProps.d.ts +8 -0
  203. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +31 -19
  204. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte.d.ts +2 -5
  205. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckboxProps.d.ts +14 -0
  206. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckboxProps.js +1 -0
  207. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +125 -187
  208. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte.d.ts +13 -12
  209. package/dist/Components/Table/Body/Rows/Columns/TableSelectProps.d.ts +6 -0
  210. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +119 -270
  211. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte.d.ts +12 -11
  212. package/dist/Components/Table/Body/Rows/Columns/TableTextInputProps.d.ts +6 -0
  213. package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +46 -23
  214. package/dist/Components/Table/Body/Rows/Columns/_helpers/useAsyncEditable.svelte.d.ts +104 -0
  215. package/dist/Components/Table/Body/Rows/Columns/_helpers/useAsyncEditable.svelte.js +215 -0
  216. package/dist/Components/Table/Body/Rows/TableRow.svelte +200 -132
  217. package/dist/Components/Table/Body/Rows/TableRow.svelte.d.ts +2 -10
  218. package/dist/Components/Table/Body/Rows/TableRowProps.d.ts +11 -2
  219. package/dist/Components/Table/Body/TableBody.svelte +170 -53
  220. package/dist/Components/Table/Body/TableBody.svelte.d.ts +15 -22
  221. package/dist/Components/Table/Body/TableBodyProps.d.ts +9 -7
  222. package/dist/Components/Table/ColumnDefinitions/ExampleItemColumnDefRepository.svelte.d.ts +2 -2
  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 +5 -3
  227. package/dist/Components/Table/Headers/HighlightAllHeader.svelte.d.ts +12 -11
  228. package/dist/Components/Table/Headers/SelectAllHeader.svelte +5 -3
  229. package/dist/Components/Table/Headers/SelectAllHeader.svelte.d.ts +12 -11
  230. package/dist/Components/Table/Headers/TableHead.svelte +340 -200
  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 +108 -50
  235. package/dist/Components/Table/Misc/ColumnPanel.svelte.d.ts +12 -11
  236. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +20 -7
  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 +30 -41
  240. package/dist/Components/Table/Misc/FilterInput.svelte.d.ts +12 -11
  241. package/dist/Components/Table/Misc/FilterPanel.svelte +387 -29
  242. package/dist/Components/Table/Misc/FilterPanel.svelte.d.ts +12 -11
  243. package/dist/Components/Table/Misc/FilterPanelModal.svelte +4 -51
  244. package/dist/Components/Table/Misc/FilterPanelModal.svelte.d.ts +2 -23
  245. package/dist/Components/Table/Misc/SidePanel.svelte +12 -9
  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 +44 -35
  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 +17 -48
  254. package/dist/Components/Table/Misc/TableHorizontalBar.svelte.d.ts +12 -11
  255. package/dist/Components/Table/Misc/TableSidebar.svelte +28 -47
  256. package/dist/Components/Table/Misc/TableSidebar.svelte.d.ts +12 -11
  257. package/dist/Components/Table/Misc/ToolboxPanel.svelte +121 -55
  258. package/dist/Components/Table/Misc/ToolboxPanel.svelte.d.ts +12 -11
  259. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +31 -24
  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 +85 -41
  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 +24 -0
  271. package/dist/Components/Table/SubApis.svelte.js +380 -0
  272. package/dist/Components/Table/Table.svelte +345 -172
  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 +30 -9
  306. package/dist/Components/Table/Types/Columns/Definitions/Display/AbstractDisplayDef.svelte.js +14 -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 +580 -58
  351. package/dist/Components/Table/Types/Context/TableContext.svelte.js +984 -1278
  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 +244 -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 +40 -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 +190 -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 +270 -0
  431. package/dist/Components/TextArea.svelte +37 -18
  432. package/dist/Components/TextAreaProps.d.ts +37 -0
  433. package/dist/Components/TextInput.svelte +41 -22
  434. package/dist/Components/TextInputProps.d.ts +58 -1
  435. package/dist/Components/ThreeStateRadio.svelte +61 -64
  436. package/dist/Components/ThreeStateRadioProps.d.ts +29 -1
  437. package/dist/Components/Touch/BoolCard.svelte +8 -15
  438. package/dist/Components/Touch/BoolCard.svelte.d.ts +1 -10
  439. package/dist/Components/Touch/BoolCardProps.d.ts +21 -0
  440. package/dist/Components/Touch/BoolCardProps.js +1 -0
  441. package/dist/Components/Touch/DateModal.svelte +17 -25
  442. package/dist/Components/Touch/DateModal.svelte.d.ts +1 -17
  443. package/dist/Components/Touch/DateModalProps.d.ts +36 -0
  444. package/dist/Components/Touch/DateModalProps.js +1 -0
  445. package/dist/Components/Touch/EditCard.svelte +10 -3
  446. package/dist/Components/Touch/EditCardProps.d.ts +21 -0
  447. package/dist/Components/Touch/InfoCard.svelte +14 -7
  448. package/dist/Components/Touch/InfoCardProps.d.ts +9 -0
  449. package/dist/Components/Touch/InfoTextCard.svelte +9 -2
  450. package/dist/Components/Touch/InfoTextCardProps.d.ts +8 -0
  451. package/dist/Components/Touch/ModalExplanation.svelte +9 -12
  452. package/dist/Components/Touch/ModalExplanation.svelte.d.ts +1 -6
  453. package/dist/Components/Touch/ModalExplanationProps.d.ts +22 -0
  454. package/dist/Components/Touch/ModalExplanationProps.js +1 -0
  455. package/dist/Components/Touch/NumberKeyboard.svelte +62 -105
  456. package/dist/Components/Touch/NumberKeyboardProps.d.ts +19 -0
  457. package/dist/Components/Touch/NumberModal.svelte +16 -6
  458. package/dist/Components/Touch/NumberModalProps.d.ts +23 -1
  459. package/dist/Components/Touch/PlusMinusCard.svelte +12 -16
  460. package/dist/Components/Touch/PlusMinusCard.svelte.d.ts +1 -6
  461. package/dist/Components/Touch/PlusMinusCardProps.d.ts +18 -0
  462. package/dist/Components/Touch/PlusMinusCardProps.js +1 -0
  463. package/dist/Components/Touch/SelectModal.svelte +33 -15
  464. package/dist/Components/Touch/SelectModalProps.d.ts +22 -1
  465. package/dist/Components/Touch/SummaryCard.svelte +56 -14
  466. package/dist/Components/Touch/SummaryCardProps.d.ts +16 -3
  467. package/dist/Components/Touch/TextAreaModal.svelte +18 -9
  468. package/dist/Components/Touch/TextAreaModalProps.d.ts +21 -0
  469. package/dist/Components/Touch/TextModal.svelte +15 -32
  470. package/dist/Components/Touch/TextModalProps.d.ts +28 -1
  471. package/dist/Components/Touch/TouchCard.svelte +9 -47
  472. package/dist/Components/Touch/TouchCardProps.d.ts +21 -0
  473. package/dist/Components/Touch/TouchModal.svelte +105 -99
  474. package/dist/Components/Touch/TouchModalProps.d.ts +35 -1
  475. package/dist/Components/Touch/UtilityButton.svelte +36 -18
  476. package/dist/Components/Touch/UtilityButtonProps.d.ts +16 -0
  477. package/dist/Components/VerticalTabGroup.svelte +18 -40
  478. package/dist/Components/VerticalTabGroupButton.svelte +11 -50
  479. package/dist/Components/VerticalTabGroupProps.d.ts +15 -2
  480. package/dist/Contexts/AlertContext.svelte.d.ts +92 -8
  481. package/dist/Contexts/AlertContext.svelte.js +69 -6
  482. package/dist/Contexts/ContextMenuContext.svelte.d.ts +54 -8
  483. package/dist/Contexts/ContextMenuContext.svelte.js +48 -2
  484. package/dist/Contexts/TestContext.svelte.d.ts +27 -1
  485. package/dist/Contexts/TestContext.svelte.js +29 -1
  486. package/dist/Contexts/ToastContext.svelte.d.ts +29 -1
  487. package/dist/Contexts/ToastContext.svelte.js +93 -24
  488. package/dist/Contexts/TooltipContext.svelte.d.ts +68 -1
  489. package/dist/Contexts/TooltipContext.svelte.js +70 -7
  490. package/dist/Contexts/UntypedInputContext.svelte.d.ts +54 -5
  491. package/dist/Contexts/UntypedInputContext.svelte.js +87 -34
  492. package/dist/Delay/Delay.js +1 -1
  493. package/dist/Helpers/AsyncFlag.svelte.d.ts +39 -0
  494. package/dist/Helpers/AsyncFlag.svelte.js +72 -0
  495. package/dist/Helpers/Datamatrix.d.ts +10 -1
  496. package/dist/Helpers/Datamatrix.js +290 -1
  497. package/dist/Helpers/Helpers.svelte.d.ts +252 -18
  498. package/dist/Helpers/Helpers.svelte.js +271 -75
  499. package/dist/Helpers/INamed.d.ts +6 -0
  500. package/dist/Helpers/Storage.d.ts +67 -0
  501. package/dist/Helpers/Storage.js +138 -0
  502. package/dist/Helpers/dropdownPortal.d.ts +52 -0
  503. package/dist/Helpers/dropdownPortal.js +232 -0
  504. package/dist/Helpers/dropdownPortal.svelte.d.ts +2 -0
  505. package/dist/Helpers/dropdownPortal.svelte.js +5 -0
  506. package/dist/Helpers/memoiseByRow.d.ts +45 -0
  507. package/dist/Helpers/memoiseByRow.js +78 -0
  508. package/dist/Helpers/prefersReducedMotion.svelte.d.ts +27 -0
  509. package/dist/Helpers/prefersReducedMotion.svelte.js +18 -0
  510. package/dist/Helpers/useTooltip.svelte.d.ts +53 -0
  511. package/dist/Helpers/useTooltip.svelte.js +62 -0
  512. package/dist/Types/Examples/ExampleModal.svelte +12 -11
  513. package/dist/Types/Examples/ExampleModal.svelte.d.ts +2 -2
  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/_mixins.scss +451 -167
  547. package/dist/scss/_motion.scss +57 -0
  548. package/dist/scss/_sizing.scss +16 -0
  549. package/dist/scss/colours.js +2 -2
  550. package/dist/scss/sizing.d.ts +9 -0
  551. package/dist/scss/sizing.js +10 -1
  552. package/package.json +35 -31
@@ -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;
460
- }
461
- set columnPresets(v) {
462
- this._columnPresets = v;
520
+ return this._preferences.columnPresets;
463
521
  }
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;
496
- }
497
- set focusStart(v) {
498
- this._focusStart = v;
575
+ return this._focus.focusStart;
499
576
  }
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,159 +637,113 @@ 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
- _openModalCoordinates = $state(null);
578
- get openModalCoordinates() {
579
- return this._openModalCoordinates;
676
+ /**
677
+ * Identifies the cell whose `col.validity({ modal })` /
678
+ * `col.display({ modal })` modal is currently open, or `null` when no
679
+ * modal is open.
680
+ *
681
+ * Keyed on the **row object reference**, not on visible index, so the
682
+ * open-modal state is stable across pagination, sort, and filter
683
+ * changes - the cell renderer matches via `cell.row.original ===
684
+ * openModalCell.row`. Visible-index-based keying (the old
685
+ * `CellCoordinatesWithoutColumn` shape) silently re-targeted the modal
686
+ * to whichever row happened to occupy the same index after a page
687
+ * change.
688
+ */
689
+ _openModalCell = $state(null);
690
+ get openModalCell() {
691
+ return this._openModalCell;
580
692
  }
581
- set openModalCoordinates(v) {
582
- this._openModalCoordinates = v;
693
+ set openModalCell(v) {
694
+ this._openModalCell = v;
583
695
  }
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;
696
+ // Per-table identifier of the ActionsCell that currently has its popup open.
697
+ // Lives on the context rather than at module scope so multiple tables on the
698
+ // same page do not share open-popup state (security: cross-table state
699
+ // poisoning).
700
+ _shownActionsPopup = $state();
701
+ get shownActionsPopup() {
702
+ return this._shownActionsPopup;
609
703
  }
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
- }
704
+ set shownActionsPopup(v) {
705
+ this._shownActionsPopup = v;
663
706
  }
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;
707
+ /**
708
+ * Delegating getter preserved for backwards compatibility. The
709
+ * actual `$derived` chain (and the async-result cache) live on
710
+ * `_selection.allSelectedInCurrentPage` (Phase 6.5).
711
+ */
712
+ get allSelectedInCurrentPage() {
713
+ return this._selection.allSelectedInCurrentPage;
689
714
  }
690
- _allHighlighted = $state(false);
715
+ /**
716
+ * Delegating getter preserved for backwards compatibility. Reads
717
+ * through to `_selection.allHighlighted`; the value is published by
718
+ * the `_allHighlightedEffect` below which lives on this class
719
+ * because it reads `paginationRepo.pageStartIndex` / `pageEndIndexExclusive`
720
+ * directly.
721
+ */
691
722
  get allHighlighted() {
692
- return this._allHighlighted;
723
+ return this._selection.allHighlighted;
693
724
  }
694
- _hasHighlightedItems = $derived(this.highlightedItems.size > 0);
725
+ /**
726
+ * Delegating getter preserved for backwards compatibility. The
727
+ * `$derived` lives on `_selection.hasHighlightedItems` (Phase 6.5).
728
+ */
695
729
  get hasHighlightedItems() {
696
- return this._hasHighlightedItems;
730
+ return this._selection.hasHighlightedItems;
697
731
  }
698
- //type of entries depends on the value extractor in the column def
699
- _selectedItems = $state(new SvelteMap());
732
+ /**
733
+ * Delegating accessors preserved for backwards compatibility. The
734
+ * `$state` slots live on `_selection` (Phase 6.5). `selectedItems`
735
+ * is read-only at this surface; the consumer-facing writers are
736
+ * `toggleSelected` / `setSelected` / `resetSelectedItems` /
737
+ * `setSelectionMap` below.
738
+ */
700
739
  get selectedItems() {
701
- return this._selectedItems;
740
+ return this._selection.selectedItems;
702
741
  }
703
- _invalidValues = $state(new SvelteMap());
704
742
  get invalidValues() {
705
- return this._invalidValues;
743
+ return this._selection.invalidValues;
706
744
  }
707
745
  set invalidValues(v) {
708
- this._invalidValues = v;
746
+ this._selection.invalidValues = v;
709
747
  }
710
748
  _hasInvalidSelectedItems = $state(false);
711
749
  get hasInvalidSelectedItems() {
@@ -714,116 +752,292 @@ export class TableContext {
714
752
  set hasInvalidSelectedItems(v) {
715
753
  this._hasInvalidSelectedItems = v;
716
754
  }
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([]);
755
+ /**
756
+ * Delegating accessor preserved for backwards compatibility. The
757
+ * id->def lookup map is owned by `_columnLayout.columnDefsById`
758
+ * (Phase 6.3); previously this was a `$derived` private field on
759
+ * `TableContext`. Made `public get` (instead of the previous
760
+ * `private` field) so the dependent sub-contexts (`SortingState`)
761
+ * and helpers (`generateTableOptions`) can read it through the
762
+ * legacy surface without piercing the sub-context boundary.
763
+ */
764
+ get columnDefsById() {
765
+ return this._columnLayout.columnDefsById;
766
+ }
767
+ /**
768
+ * Phase 6.3 sub-context that owns the column-layout state:
769
+ * `columnDefs`, `columnSizing`, `columnPinning`, `columnVisibility`,
770
+ * the `columnDefsById` reverse-lookup map, the `visualColumns`
771
+ * cached array (and the paired `visualColumnIndexById` O(1) lookup),
772
+ * the gated `setColumnPinning` updater, the `updateDefsBasedOnPinning`
773
+ * helper, and the `setNewPinningState` helper.
774
+ *
775
+ * Dependencies are passed as thunks so the sub-context never holds
776
+ * a reference to the parent. The `allowColumnReordering` thunk
777
+ * gates the public `setColumnPinning` updater; the `debug` thunk
778
+ * mirrors the parent's logger gate.
779
+ */
780
+ _columnLayout = new ColumnLayoutState({
781
+ getAllowColumnReordering: () => this.allowColumnReordering,
782
+ getDebug: () => this.debug,
783
+ });
784
+ /**
785
+ * Phase 6.2 sub-context that owns the filtering-related state: the
786
+ * per-row filter result (`filteredIndices`), the `filteredData`
787
+ * derived chain, the user-facing toggles (`showQuickFiltering`,
788
+ * `showSelectedOnly`), the per-type `FilterFn` relays, the
789
+ * remote-filter slot, and the `updateFilters` `OnChangeFn` slot.
790
+ *
791
+ * Dependencies are passed as thunks so the sub-context never holds
792
+ * a reference to the parent; the population effect for
793
+ * `_filteredIndices` stays in `TableContext` because it calls
794
+ * `this.createRow` (parent concern) - the effect publishes its
795
+ * result via `this._filtering.setFilteredIndices`.
796
+ */
797
+ _filtering = new FilteringState({
798
+ getRepoData: () => this.dataRepo.data,
799
+ getPaginationType: () => this.paginationRepo?.paginationType,
800
+ getDateFilterFn: () => this.paginationRepo?.dateFilter,
801
+ getNumberFilterFn: () => this.paginationRepo?.numberFilter,
802
+ getSelectFilterFn: () => this.paginationRepo?.selectFilter,
803
+ getBoolFilterFn: () => this.paginationRepo?.boolFilter,
804
+ getStringFilterFn: () => this.paginationRepo?.stringFilter,
805
+ getSelectedItems: () => this.selectedItems,
806
+ getAllowQuickFiltering: () => this.allowQuickFiltering,
807
+ getDebug: () => this.debug,
808
+ });
809
+ /**
810
+ * Delegating getter preserved for backwards compatibility. Reads
811
+ * through to `_filtering.filteredIndices`, which is populated by
812
+ * the per-row filter `$effect` in this class (the effect calls
813
+ * `this.createRow` and must stay where `createRow` lives).
814
+ */
752
815
  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;
816
+ return this._filtering.filteredIndices;
817
+ }
818
+ /**
819
+ * Delegating getter preserved for backwards compatibility. Reads
820
+ * through to `_filtering.filteredData`, which owns the local /
821
+ * remote / show-selected-only branching.
822
+ */
823
+ get filteredData() {
824
+ return this._filtering.filteredData;
825
+ }
826
+ /**
827
+ * Phase 6.1 sub-context that owns the sort-related derived chain and
828
+ * the per-`ColumnDef` comparator dispatch. The dependency thunks
829
+ * close over `this.columnDefsById` / `this.filteredData` /
830
+ * `this.dataRepo` / `this.paginationRepo` / `this.debug`, so the
831
+ * sub-context never holds a direct reference to the parent and the
832
+ * reactivity graph remains threaded through `$derived` only.
833
+ *
834
+ * Exposed as a public field so future consumers can read sorting
835
+ * state directly; the legacy `sortedData` getter below preserves the
836
+ * pre-decomposition surface for backwards compatibility.
837
+ */
838
+ _sorting = new SortingState({
839
+ getColumnDefsById: () => this.columnDefsById,
840
+ getFilteredData: () => this._filtering.filteredData,
841
+ getSortingArray: () => this.dataRepo.sorting,
842
+ getPaginationType: () => this.paginationRepo?.paginationType,
843
+ getDataLength: () => this.dataRepo.data.length,
844
+ getDebug: () => this.debug,
776
845
  });
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;
846
+ /**
847
+ * Phase 6.4 sub-context that owns every persistence / preferences
848
+ * concern previously held directly on `TableContext`:
849
+ *
850
+ * - the saved column-presets list and currently-selected preset
851
+ * (`columnPresets`, `selectedPreset`);
852
+ * - the `userIdSegment` `$derived` (used to namespace every
853
+ * `scoria-table:<user>:<tableName>:<suffix>` storage key);
854
+ * - the `saveColumnPreset` / `setSelectedPreset` writers (which
855
+ * apply the layout AND persist);
856
+ * - the `loadPresets` / `loadSelectedPresetName` /
857
+ * `loadSavedSettings` boot-time hydration helpers;
858
+ * - the `persistSettings` writer used by the in-app settings
859
+ * toggles (`setIsCompact`, `setAlternateRowColours`, etc.);
860
+ * - the schema-validation typeguards (exported alongside the
861
+ * sub-context for unit testing).
862
+ *
863
+ * The settings fields themselves (`isCompact`, `singleClickEditing`,
864
+ * `alternateRowColours`, etc.) stay on `TableContext` because each
865
+ * one drives reactive UI; only the localStorage round-trip moved.
866
+ * `TableContext` retains delegating getters/setters/methods for
867
+ * every legacy surface (`columnPresets`, `selectedPreset`,
868
+ * `saveColumnPreset`, `setSelectedPreset`, `setIsCompact`, etc.).
869
+ */
870
+ _preferences = new PreferencesState({
871
+ getTableName: () => this.tableName,
872
+ getUserId: () => this.getUserId(),
873
+ getColumnDefs: () => this.columnDefs,
874
+ getColumnVisibility: () => this.columnVisibility,
875
+ getColumnPinning: () => this.columnPinning,
876
+ getSortingArray: () => this.paginationRepo?.sorting,
877
+ setColumnDefs: (defs) => {
878
+ this.columnDefs = defs;
879
+ },
880
+ setColumnVisibility: (state) => {
881
+ this.columnVisibility = state;
882
+ },
883
+ setColumnPinning: (state) => {
884
+ this.columnPinning = state;
885
+ },
886
+ applySortingState: (state) => {
887
+ this.paginationRepo?.updateSorting?.(state);
888
+ },
889
+ getDebug: () => this.debug,
890
+ });
891
+ /**
892
+ * Phase 6.5 sub-context that owns every selection + highlighting +
893
+ * key-cache concern previously held directly on `TableContext`:
894
+ *
895
+ * - `selectedItems` / `invalidValues` / `highlightedItems`
896
+ * (`SvelteMap` slots, keyed by `selectionExtractor(item)`);
897
+ * - the `WeakMap<T, RowIdType>` key-cache that memoises the
898
+ * extractor result for each row reference;
899
+ * - the pending-flush buffer and its debounced microtask
900
+ * (`_pendingSelectionChanges` -> `tick().then(flush)`);
901
+ * - the `allSelectedInCurrentPage` `$derived` (with the async-
902
+ * result cache for promise-returning `selectionExtractor` /
903
+ * `RowSelectionDef.isSelectable`);
904
+ * - the `selectAllInCurrentPage` / `updateSelectedItems` /
905
+ * `onHighlightAll` flows;
906
+ * - the `handleItemMutation` split that powers the parent's
907
+ * `onEditCell` - the sub-context owns the key-cache delete +
908
+ * selection rebind; the user-supplied callback stays on this
909
+ * class.
910
+ *
911
+ * Dependencies are passed as thunks so the sub-context never holds
912
+ * a direct reference to `TableContext`. The two `$effect`s that
913
+ * historically lived on this class continue to live here (they
914
+ * read `paginationRepo` directly) but publish their results
915
+ * through `_selection.setAllHighlighted(...)` and
916
+ * `_selection.updateSelectedItems()`.
917
+ */
918
+ _selection = new SelectionState({
919
+ getSortedData: () => this.sortedData,
920
+ getCurrentPageData: () => this.currentPageData,
921
+ getColumnDefs: () => this.columnDefs,
922
+ getPageStartIndex: () => this.paginationRepo?.pageStartIndex,
923
+ getPageEndIndexExclusive: () => this.paginationRepo?.pageEndIndexExclusive,
924
+ getSelectionExtractor: () => this.selectionExtractor,
925
+ getIsSelectable: () => this.isSelectable,
926
+ getDebug: () => this.debug,
927
+ });
928
+ /**
929
+ * Phase 6.6 sub-context that owns every focus + keyboard-navigation
930
+ * concern previously held directly on `TableContext`:
931
+ *
932
+ * - `focusStart` / `focusEnd` / `focusStartIsBeingEdited` /
933
+ * `isMousingDown` (the `$state` slots that define the focused
934
+ * cell-range);
935
+ * - the `focusedRowIndices` / `focusedColumnIds` sets (populated
936
+ * by the parent's `$effect` through `setFocusedDerived(...)`);
937
+ * - every focus writer (`startFocus`, `moveFocus`, `unfocus`,
938
+ * `endFocus`, `setFocusEnd`, `focusToTop`, `focusToBottom`,
939
+ * `focusToStart`, `focusToEnd`, `moveToTop`, `moveToBottom`,
940
+ * `moveToStart`, `moveToEnd`, `setFocusUp` / `setFocusDown` /
941
+ * `setFocusLeft` / `setFocusRight`, and the
942
+ * `moveFocusUp` / `moveFocusDown` / `moveFocusLeft` /
943
+ * `moveFocusRight` range-extension variants);
944
+ * - `handleTdKeydown` (the root `onkeydown` router) and the
945
+ * private `handleKeystroke` dispatch.
946
+ *
947
+ * Dependencies are passed as thunks so the sub-context never holds
948
+ * a direct reference to `TableContext`. The Ctrl+F / Tab / Ctrl+C
949
+ * paths in `handleTdKeydown` and `handleKeystroke` call back into
950
+ * the parent through the `toggleQuickFiltering` / `tabForward` /
951
+ * `tabBackward` / `copyDataToClipboard` thunks because those
952
+ * methods read fields owned by other sub-contexts (or the parent
953
+ * directly) and would otherwise need to import them.
954
+ *
955
+ * The focus-derived `$effect` that publishes
956
+ * `focusedRowIndices` / `focusedColumnIds` lives on `TableContext`
957
+ * because it reads `paginationRowModel` + `_visualColumns` directly;
958
+ * it calls `_focus.setFocusedDerived(rows, cols)` to publish.
959
+ *
960
+ * Group 2 Item C (per-cell `TableFocusDetails` reuse cache) is
961
+ * deferred for Phase 6.6 - see the class-level JSDoc on
962
+ * `FocusState` for the deferral rationale.
963
+ */
964
+ _focus = new FocusState({
965
+ getAllowCopy: () => this.allowCopy,
966
+ getAllowQuickFiltering: () => this.allowQuickFiltering,
967
+ getColumnDefs: () => this.columnDefs,
968
+ getColumnVisibility: () => this.columnVisibility,
969
+ getVisualColumns: () => this._visualColumns,
970
+ getVisualColumnIndexById: (id) => this.columnVisualIndex(id),
971
+ getSortedDataLength: () => this.sortedData.length,
972
+ getPaginationRowModel: () => this.paginationRowModel,
973
+ copyDataToClipboard: () => this.copyDataToClipboard(),
974
+ toggleQuickFiltering: () => {
975
+ this.toggleQuickFiltering();
976
+ },
977
+ tabForward: () => {
978
+ this.tabForward();
979
+ },
980
+ tabBackward: () => {
981
+ this.tabBackward();
982
+ },
983
+ getDebug: () => this.debug,
984
+ // Phase 7.1 Item C - `getFocusDetailsFor` needs the parent
985
+ // `TableContext` to construct `TableFocusDetails` on cache-miss.
986
+ // Pass via a thunk so the sub-context never closes over `this`
987
+ // at construction time.
988
+ getTableContextRef: () => this,
826
989
  });
990
+ /**
991
+ * Phase 7.4 row-virtualisation sub-context. Owns the visible-row
992
+ * window plus the top / bottom spacer-height derived chain that
993
+ * `TableBody.svelte` renders. Threshold defaults to 100 rows
994
+ * (below that the legacy code path renders every row directly);
995
+ * overscan defaults to 10 rows above and below the viewport so a
996
+ * fast flick of the wheel does not surface a blank band before
997
+ * the next paint.
998
+ *
999
+ * Dependencies are thunks - the sub-context reads
1000
+ * `paginationRowModel.rows` through `getRows`, the max-side
1001
+ * observed height from the `_columnLayout` row-height pool
1002
+ * through `getRowHeight`, and the per-table-density estimate
1003
+ * through `getEstimatedRowHeight`.
1004
+ */
1005
+ /**
1006
+ * Rendered widths of header cells, keyed by columnId. TableHead
1007
+ * observes each `<div role="columnheader">` with a `ResizeObserver`
1008
+ * and writes the actual rendered width here; TableRow / TableBody
1009
+ * read these widths when computing sticky pinning offsets so that
1010
+ * pinned columns can flex with `minmax(W, 1fr)` like centre columns
1011
+ * (matching width-preservation expectation on pinning) while the
1012
+ * sticky-cell offsets stay accurate.
1013
+ *
1014
+ * Fallback when a column has not yet been measured: 0. The first
1015
+ * frame after pinning toggles may render with stale offsets but the
1016
+ * resize observer fires immediately afterwards.
1017
+ */
1018
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
1019
+ _columnRenderedWidths = new SvelteMap();
1020
+ _virtualisation = new VirtualisationState({
1021
+ getRows: () => this.paginationRowModel.rows,
1022
+ // Row alignment is browser-handled in the single-grid layout, so
1023
+ // the per-side height pool was deleted along with its observers.
1024
+ // VirtualisationState falls back to `getEstimatedRowHeight` when
1025
+ // no measured value is available, which is sufficient for
1026
+ // determining the spacer heights to within one overscan band.
1027
+ getRowHeight: () => 0,
1028
+ getEstimatedRowHeight: () => (this.isCompact ? 28 : 48),
1029
+ });
1030
+ /**
1031
+ * Delegating getter preserved for backwards compatibility. Reads
1032
+ * through to `_sorting.sortedData`, which owns the comparator-hoist
1033
+ * optimisation and the `AbortController` short-circuit. Consumers
1034
+ * inside `src/lib` already point at `tableContext.sortedData`; the
1035
+ * delegation will be removed once Phase 6 v-next migrates every
1036
+ * reader to `tableContext._sorting.sortedData` directly.
1037
+ */
1038
+ get sortedData() {
1039
+ return this._sorting.sortedData;
1040
+ }
827
1041
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
828
1042
  _sortedRowModel = $state({ rows: [], rowsById: new Map() });
829
1043
  get sortedRowModel() {
@@ -965,490 +1179,139 @@ export class TableContext {
965
1179
  },
966
1180
  };
967
1181
  };
968
- const makeCellFromId = (columnId) => {
969
- const def = this.columnDefsById.get(columnId);
970
- if (!def) {
971
- throw new Error(`Could not find column with id ${columnId}`);
972
- }
973
- return makeRow(def);
974
- };
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
1182
+ // Iterating `this.columnDefs.filter(...)` instead of
1183
+ // `this.columnPinning.left?.keys()` puts pinned-left body
1184
+ // cells in declaration order (matching the header rendering
1185
+ // and the `getVisualColumns` tab-order). Iterating the
1186
+ // pinning Map's keys produced *insertion* order so that
1187
+ // pinning Name then ID gave a body row of [Name, ID] while
1188
+ // the header above it stayed [ID, Name]. See the duplicate
1189
+ // factory in `createRow` below for the same fix.
1190
+ const leftVisibleCells = $derived(this.columnDefs
978
1191
  .filter((it) => {
979
- return !(this.columnPinning.left?.has(it.id) || this.columnPinning.right?.has(it.id));
1192
+ return (this.columnVisibility[it.id] !== false && this.columnPinning.left?.has(it.id) === true);
980
1193
  })
981
1194
  .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
- }
1195
+ const rightVisibleCells = $derived(this.columnDefs
1196
+ .filter((it) => {
1197
+ return (this.columnVisibility[it.id] !== false &&
1198
+ this.columnPinning.right?.has(it.id) === true);
1199
+ })
1200
+ .map(makeRow));
1201
+ const centerVisibleCells = $derived(this.columnDefs
1202
+ .filter((it) => {
1203
+ return (this.columnVisibility[it.id] !== false &&
1204
+ !(this.columnPinning.left?.has(it.id) || this.columnPinning.right?.has(it.id)));
1205
+ })
1206
+ .map(makeRow));
1207
+ return row;
1208
+ });
1209
+ });
1210
+ _paginationRowModel = $state({
1211
+ rows: [],
1212
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
1213
+ rowsById: new Map(),
1214
+ });
1215
+ get paginationRowModel() {
1216
+ return this._paginationRowModel;
1217
+ }
1218
+ // #endregion
1219
+ // #region public methods
1220
+ /**
1221
+ * Delegating wrapper preserved for backwards compatibility. The
1222
+ * key-extraction cache lives on `_selection` (Phase 6.5); this
1223
+ * method forwards to `_selection.cachedKey(item)` so legacy call
1224
+ * sites in `getHighlightedRowData` / `getFocusElementData` /
1225
+ * `originalRowIndices` / `sortedRowModel` / `onHighlightAll` see
1226
+ * no behavioural change.
1227
+ */
1228
+ _cachedKey(item) {
1229
+ return this._selection.cachedKey(item);
1451
1230
  }
1231
+ /** Delegating accessor preserved for backwards compatibility. The
1232
+ * implementation lives on `_selection.selectionContains` (Phase 6.5). */
1233
+ selectionContains = (item) => {
1234
+ return this._selection.selectionContains(item);
1235
+ };
1236
+ /** Delegating accessor preserved for backwards compatibility. */
1237
+ addInvalidValue = (item) => {
1238
+ return this._selection.addInvalidValue(item);
1239
+ };
1240
+ /** Delegating accessor preserved for backwards compatibility. */
1241
+ removeInvalidValue = (item) => {
1242
+ return this._selection.removeInvalidValue(item);
1243
+ };
1244
+ /**
1245
+ * Stores or clears the cell whose per-cell modal is currently open.
1246
+ *
1247
+ * Pass `{ row, columnId }` to open; pass `null` to close. The `row`
1248
+ * value must be the same object reference the table holds for that row
1249
+ * (`cell.row.original`) - identity comparison is what the cell
1250
+ * renderers use to decide which one of them should render the modal.
1251
+ */
1252
+ setOpenModalCell = (cell) => {
1253
+ this.openModalCell = cell ? { row: cell.row, columnId: cell.columnId } : null;
1254
+ };
1255
+ /** Delegating accessor preserved for backwards compatibility. */
1256
+ toggleHighlightedRow = (item) => {
1257
+ return this._selection.toggleHighlightedRow(item);
1258
+ };
1259
+ /** Delegating accessor preserved for backwards compatibility. */
1260
+ resetHighlightedRows = () => {
1261
+ this._selection.resetHighlightedRows();
1262
+ };
1263
+ /** Delegating accessor preserved for backwards compatibility. */
1264
+ toggleSelected = (item) => {
1265
+ return this._selection.toggleSelected(item);
1266
+ };
1267
+ /** Delegating accessor preserved for backwards compatibility. */
1268
+ setSelected = (item, selected) => {
1269
+ return this._selection.setSelected(item, selected);
1270
+ };
1271
+ /** Delegating accessor preserved for backwards compatibility. */
1272
+ setSelectionMap = (set) => {
1273
+ this._selection.setSelectionMap(set);
1274
+ };
1275
+ /** Delegating accessor preserved for backwards compatibility. */
1276
+ updateSelectedItems = async () => {
1277
+ await this._selection.updateSelectedItems();
1278
+ };
1279
+ /** Delegating accessor preserved for backwards compatibility. */
1280
+ resetSelectedItems = () => {
1281
+ this._selection.resetSelectedItems();
1282
+ };
1283
+ /** Delegating accessor preserved for backwards compatibility. */
1284
+ selectAllInCurrentPage = async () => {
1285
+ await this._selection.selectAllInCurrentPage();
1286
+ };
1287
+ /** Delegating accessor preserved for backwards compatibility. */
1288
+ onHighlightAll = () => {
1289
+ return this._selection.onHighlightAll();
1290
+ };
1291
+ /**
1292
+ * Invoked by every editable cell on commit. Split between this
1293
+ * class (the user-supplied edit callback) and `_selection`
1294
+ * (the key-cache invalidation + selection-rebind half). The
1295
+ * sub-context's `handleItemMutation` accepts a thunk that runs
1296
+ * the consumer callback between the pre- and post-edit selection
1297
+ * bookkeeping so the boundary stays explicit.
1298
+ */
1299
+ onEditCell = async (item, coords, value) => {
1300
+ await this._selection.handleItemMutation(item, async () => {
1301
+ await this._onEditCell?.(item, coords, value);
1302
+ });
1303
+ };
1304
+ /**
1305
+ * Delegating accessor preserved for backwards compatibility. The
1306
+ * implementation lives on `_focus.handleTdKeydown` (Phase 6.6).
1307
+ * Bound to the root `<table>` container's `onkeydown` listener;
1308
+ * routes Ctrl+F / Escape / Tab synchronously and dispatches the
1309
+ * arrow-key / Home / End / Ctrl+A / Ctrl+C path asynchronously.
1310
+ */
1311
+ handleTdKeydown = (e) => {
1312
+ this._focus.handleTdKeydown(e);
1313
+ };
1314
+ /** Toggles quick-filtering visibility and refocuses the matching cell. */
1452
1315
  toggleQuickFiltering = () => {
1453
1316
  if (this.allowQuickFiltering) {
1454
1317
  const fs = this.focusStart;
@@ -1466,13 +1329,26 @@ export class TableContext {
1466
1329
  .catch(devCatch);
1467
1330
  }
1468
1331
  };
1332
+ /**
1333
+ * Applies an updater to the column-pinning state (left / right
1334
+ * pins). Delegates to `_columnLayout.setColumnPinning`, which
1335
+ * enforces the `allowColumnReordering` master gate.
1336
+ */
1469
1337
  setColumnPinning = (updater) => {
1470
- if (!this.allowColumnReordering) {
1471
- return;
1472
- }
1473
- this.columnPinning = updater(this.columnPinning);
1338
+ this._columnLayout.setColumnPinning(updater);
1474
1339
  };
1475
- updateFilters = $state();
1340
+ /**
1341
+ * Delegating accessor preserved for backwards compatibility. The
1342
+ * actual `$state` slot lives on `_filtering`; this getter / setter
1343
+ * pair forwards reads and writes so existing consumers (notably
1344
+ * `generateTableOptions`) need no migration.
1345
+ */
1346
+ get updateFilters() {
1347
+ return this._filtering.updateFilters;
1348
+ }
1349
+ set updateFilters(v) {
1350
+ this._filtering.updateFilters = v;
1351
+ }
1476
1352
  updateVisibility = (stateOrCallback) => {
1477
1353
  if (typeof stateOrCallback == 'function') {
1478
1354
  this.columnVisibility = stateOrCallback(this.columnVisibility);
@@ -1495,6 +1371,7 @@ export class TableContext {
1495
1371
  // return this._rowModel;
1496
1372
  // }
1497
1373
  lastPage = $derived(this.dataRepo.lastPage ?? 0);
1374
+ /** Canvas-measured width of a column header label, plus padding. */
1498
1375
  measureHeaderWidth = (columnDef) => {
1499
1376
  const label = columnDef.header;
1500
1377
  if (typeof label === 'string' && browser) {
@@ -1516,6 +1393,7 @@ export class TableContext {
1516
1393
  }
1517
1394
  return 0;
1518
1395
  };
1396
+ /** Sizes a column to fit its widest current-page cell label. */
1519
1397
  autoResize = async (columnDef) => {
1520
1398
  if (!browser) {
1521
1399
  return;
@@ -1541,6 +1419,11 @@ export class TableContext {
1541
1419
  }
1542
1420
  this.columnSizing[colId] = maxWidth + 32; //standard extra cell padding
1543
1421
  };
1422
+ /**
1423
+ * Drops the in-progress column drag onto the supplied target,
1424
+ * updating `columnDefs`, pinning state, and focus. No-op when
1425
+ * column reordering is disabled.
1426
+ */
1544
1427
  reorderColumn = (dropTarget) => {
1545
1428
  if (!this.allowColumnReordering) {
1546
1429
  return;
@@ -1602,6 +1485,41 @@ export class TableContext {
1602
1485
  this.columnDefs = newDefs;
1603
1486
  // this.forceVisibilityUpdate();
1604
1487
  };
1488
+ /**
1489
+ * Programmatic column-reorder primitive used by keyboard accessibility
1490
+ * shortcuts in the column-management panel. Moves the column at
1491
+ * {@link currentIndex} to {@link newIndex} (both expressed in the
1492
+ * sorted-by-`index` order of {@link columnDefs}). No-op when column
1493
+ * reordering is disabled or indices are out of range / unchanged.
1494
+ */
1495
+ moveColumnTo = (currentIndex, newIndex) => {
1496
+ if (!this.allowColumnReordering) {
1497
+ return;
1498
+ }
1499
+ const newDefs = this.columnDefs.concat();
1500
+ newDefs
1501
+ .sort((a, b) => a.index - b.index)
1502
+ .forEach((element, index) => {
1503
+ element.index = index;
1504
+ });
1505
+ if (currentIndex < 0 ||
1506
+ currentIndex >= newDefs.length ||
1507
+ newIndex < 0 ||
1508
+ newIndex >= newDefs.length ||
1509
+ currentIndex === newIndex) {
1510
+ return;
1511
+ }
1512
+ const [moved] = newDefs.splice(currentIndex, 1);
1513
+ newDefs.splice(newIndex, 0, moved);
1514
+ newDefs.forEach((element, index) => {
1515
+ element.index = index;
1516
+ });
1517
+ this._setNewPinningState();
1518
+ this.updateDefsBasedOnPinning();
1519
+ this.unfocus();
1520
+ this.columnDefs = newDefs;
1521
+ };
1522
+ /** Copies the highlighted rows to the OS clipboard as TSV / CSV. */
1605
1523
  copyHighlightedRowsToClipboard = async () => {
1606
1524
  // NOTE: False Positive. Clipboard does not exist in http environments
1607
1525
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -1611,6 +1529,7 @@ export class TableContext {
1611
1529
  }
1612
1530
  return this._performRowCopy(await this.getHighlightedRowData());
1613
1531
  };
1532
+ /** Copies the currently focused cell range to the OS clipboard. */
1614
1533
  copyDataToClipboard = async () => {
1615
1534
  // NOTE: False Positive. Clipboard does not exist in http environments
1616
1535
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -1645,11 +1564,13 @@ export class TableContext {
1645
1564
  const value = columnOptions?.find((it) => it.value === focusedElementData[0][0])?.label ??
1646
1565
  focusedElementData[0][0]?.toString() ??
1647
1566
  '';
1648
- return value;
1567
+ // Single-cell copy bypasses TSV/CSV wrapping but still lands
1568
+ // in a spreadsheet; neutralise formula-injection prefixes.
1569
+ return escapeForSpreadsheet(value);
1649
1570
  }
1650
1571
  else {
1651
1572
  const value = focusedElementData[0][0]?.toString().trim() ?? '';
1652
- return value;
1573
+ return escapeForSpreadsheet(value);
1653
1574
  }
1654
1575
  }
1655
1576
  return `${shouldCopyHeaders ? `${focusedColumnIds.join(separator)}\n` : ''}${focusedElementData
@@ -1678,238 +1599,124 @@ export class TableContext {
1678
1599
  })();
1679
1600
  navigator.clipboard.writeText(value).catch(devCatch);
1680
1601
  };
1602
+ /** Delegating accessor preserved for backwards compatibility. The
1603
+ * implementation lives on `_focus.startFocus` (Phase 6.6). */
1681
1604
  startFocus = (coords) => {
1682
- this.isMousingDown = true;
1683
- this.moveFocus(coords);
1605
+ this._focus.startFocus(coords);
1684
1606
  };
1607
+ /** Delegating accessor preserved for backwards compatibility. The
1608
+ * implementation lives on `_focus.moveFocus` (Phase 6.6). */
1685
1609
  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
- }
1610
+ this._focus.moveFocus(coords, startEditing);
1695
1611
  };
1612
+ /** Delegating accessor preserved for backwards compatibility. */
1696
1613
  unfocus = () => {
1697
- this.focusStartIsBeingEdited = false;
1698
- this.focusStart = null;
1699
- this.focusEnd = null;
1614
+ this._focus.unfocus();
1700
1615
  };
1616
+ /** Delegating accessor preserved for backwards compatibility. */
1701
1617
  focusToTop = () => {
1702
- if (!this.allowCopy) {
1703
- return;
1704
- }
1705
- this._moveFocusEndRow(-Infinity);
1618
+ this._focus.focusToTop();
1706
1619
  };
1620
+ /** Delegating accessor preserved for backwards compatibility. */
1707
1621
  focusToBottom = () => {
1708
- if (!this.allowCopy) {
1709
- return;
1710
- }
1711
- this._moveFocusEndRow(Infinity);
1622
+ this._focus.focusToBottom();
1712
1623
  };
1624
+ /** Delegating accessor preserved for backwards compatibility. */
1713
1625
  moveToBottom = () => {
1714
- this._moveFocusRow(Infinity);
1626
+ this._focus.moveToBottom();
1715
1627
  };
1628
+ /** Delegating accessor preserved for backwards compatibility. */
1716
1629
  moveToTop = () => {
1717
- this._moveFocusRow(-Infinity);
1630
+ this._focus.moveToTop();
1718
1631
  };
1632
+ /** Delegating accessor preserved for backwards compatibility. */
1719
1633
  moveFocusUp = () => {
1720
- if (!this.allowCopy) {
1721
- return;
1722
- }
1723
- this._moveFocusEndRow(-1);
1634
+ this._focus.moveFocusUp();
1724
1635
  };
1636
+ /** Delegating accessor preserved for backwards compatibility. */
1725
1637
  setFocusUp = () => {
1726
- this._moveFocusRow(-1);
1638
+ this._focus.setFocusUp();
1727
1639
  };
1640
+ /** Delegating accessor preserved for backwards compatibility. */
1728
1641
  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
- }
1642
+ this._focus.tabForward();
1753
1643
  };
1644
+ /** Delegating accessor preserved for backwards compatibility. */
1754
1645
  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
- }
1646
+ this._focus.tabBackward();
1778
1647
  };
1648
+ /** Delegating accessor preserved for backwards compatibility. */
1779
1649
  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
- }
1650
+ this._focus.focusToStart();
1791
1651
  };
1652
+ /** Delegating accessor preserved for backwards compatibility. */
1792
1653
  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
- }
1654
+ this._focus.moveToStart();
1801
1655
  };
1656
+ /** Delegating accessor preserved for backwards compatibility. */
1802
1657
  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
- }
1658
+ this._focus.focusToEnd();
1814
1659
  };
1660
+ /** Delegating accessor preserved for backwards compatibility. */
1815
1661
  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
- }
1662
+ this._focus.moveToEnd();
1824
1663
  };
1664
+ /** Delegating accessor preserved for backwards compatibility. */
1825
1665
  moveFocusDown = () => {
1826
- if (!this.allowCopy) {
1827
- return;
1828
- }
1829
- this._moveFocusEndRow(1);
1666
+ this._focus.moveFocusDown();
1830
1667
  };
1668
+ /** Delegating accessor preserved for backwards compatibility. */
1831
1669
  setFocusDown = () => {
1832
- this._moveFocusRow(1);
1670
+ this._focus.setFocusDown();
1833
1671
  };
1672
+ /** Delegating accessor preserved for backwards compatibility. */
1834
1673
  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
- }
1674
+ this._focus.moveFocusLeft();
1846
1675
  };
1676
+ /** Delegating accessor preserved for backwards compatibility. */
1847
1677
  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
- }
1678
+ this._focus.setFocusLeft();
1857
1679
  };
1680
+ /** Delegating accessor preserved for backwards compatibility. */
1858
1681
  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
- }
1682
+ this._focus.moveFocusRight();
1870
1683
  };
1684
+ /** Delegating accessor preserved for backwards compatibility. */
1871
1685
  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
- }
1686
+ this._focus.setFocusRight();
1881
1687
  };
1688
+ /** Delegating accessor preserved for backwards compatibility. */
1882
1689
  endFocus = (coords) => {
1883
- if (this.isMousingDown) {
1884
- this.isMousingDown = false;
1885
- if (!this.allowCopy) {
1886
- return;
1887
- }
1888
- this.focusEnd = { ...coords };
1889
- }
1690
+ this._focus.endFocus(coords);
1890
1691
  };
1692
+ /** Toggles compact row sizing and persists to localStorage. */
1891
1693
  setIsCompact = (value) => {
1892
1694
  this.isCompact = value;
1893
1695
  this._updateLocalStorageSettings();
1894
1696
  };
1697
+ /** Toggles alternating row colours and persists to localStorage. */
1895
1698
  setAlternateRowColours = (value) => {
1896
1699
  this.alternateRowColours = value;
1897
1700
  this._updateLocalStorageSettings();
1898
1701
  };
1702
+ /** Toggles single-click editing and persists to localStorage. */
1899
1703
  setSingleClickEditing = (value) => {
1900
1704
  this.singleClickEditing = value;
1901
1705
  this._updateLocalStorageSettings();
1902
1706
  };
1707
+ /** Toggles row-hover colour change and persists to localStorage. */
1903
1708
  setChangeColourOnHover = (value) => {
1904
1709
  this.changeColourOnHover = value;
1905
1710
  this._updateLocalStorageSettings();
1906
1711
  };
1712
+ /** Toggles whether copy-to-clipboard prepends a header row and persists. */
1907
1713
  setCopyWithHeaders = (value) => {
1908
1714
  if (this.allowCopy) {
1909
1715
  this.copyWithHeaders = value;
1910
1716
  this._updateLocalStorageSettings();
1911
1717
  }
1912
1718
  };
1719
+ /** Toggles CSV vs TSV copy-to-clipboard separator and persists. */
1913
1720
  setCommaSeparatedCopy = (value) => {
1914
1721
  if (this.allowCopy) {
1915
1722
  this.commaSeparatedCopy = value;
@@ -1929,63 +1736,41 @@ export class TableContext {
1929
1736
  // });
1930
1737
  // }
1931
1738
  // };
1739
+ /**
1740
+ * Delegating method preserved for backwards compatibility. Applies
1741
+ * the preset to the live column layout, forwards the preset's
1742
+ * `sortingState` to the data-repository updater, and persists the
1743
+ * selected name. The underlying implementation lives on
1744
+ * `_preferences.setSelectedPreset` (Phase 6.4).
1745
+ *
1746
+ * The pre-extraction debug warning ("no sorting state found") was
1747
+ * emitted unconditionally when `paginationRepo.updateSorting` was
1748
+ * absent; the sub-context skips that warning because the
1749
+ * `applySortingState` thunk already no-ops in that case. The
1750
+ * warning is reinstated below so the surface is byte-identical.
1751
+ */
1932
1752
  setSelectedPreset = (preset) => {
1933
- if (this.debug) {
1934
- console.log('setting selected preset:', preset);
1935
- }
1936
- this.columnDefs = preset.columnDefs;
1937
1753
  if (!this.paginationRepo?.updateSorting) {
1938
1754
  console.warn('no sorting state found');
1939
1755
  }
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);
1756
+ this._preferences.setSelectedPreset(preset);
1945
1757
  };
1758
+ /**
1759
+ * Delegating method preserved for backwards compatibility. Adds or
1760
+ * replaces a saved preset for the current column layout and
1761
+ * persists the full preset list to localStorage. Delegates to
1762
+ * `_preferences.saveColumnPreset` (Phase 6.4).
1763
+ */
1946
1764
  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);
1765
+ this._preferences.saveColumnPreset(name);
1980
1766
  };
1981
1767
  /**
1982
1768
  * This listener should be added and removed based on isMousingDown
1983
1769
  */
1770
+ /** Delegating accessor preserved for backwards compatibility. The
1771
+ * implementation lives on `_focus.setFocusEnd` (Phase 6.6). */
1984
1772
  setFocusEnd = (coords) => {
1985
- if (!this.allowCopy) {
1986
- return;
1987
- }
1988
- this.focusEnd = coords;
1773
+ this._focus.setFocusEnd(coords);
1989
1774
  };
1990
1775
  // #region private methods
1991
1776
  createRow(item, index, unsortedIndex) {
@@ -2041,18 +1826,36 @@ export class TableContext {
2041
1826
  },
2042
1827
  };
2043
1828
  };
2044
- const makeCellFromId = (columnId) => {
2045
- const def = this.columnDefsById.get(columnId);
2046
- if (!def) {
2047
- throw new Error(`Could not make cell from id ${columnId}: Coloumn Def was not found`);
2048
- }
2049
- return makeRow(def);
2050
- };
2051
- const leftVisibleCells = $derived([...(this.columnPinning.left?.keys() ?? [])].map(makeCellFromId));
2052
- const rightVisibleCells = $derived([...(this.columnPinning.right?.keys() ?? [])].map(makeCellFromId));
1829
+ // Visibility filter applied to every cell collection so the cell
1830
+ // count in TableRow stays in sync with the visible-column count
1831
+ // that drives `--cols` in Table.svelte. Without this, a hidden
1832
+ // non-pinned column would still produce an empty cell wrapper in
1833
+ // the row, which would overflow the grid template and force the
1834
+ // extra cell onto an implicit grid row (visually splitting each
1835
+ // data row into two or three lines).
1836
+ //
1837
+ // Iterating `this.columnDefs.filter(...)` instead of
1838
+ // `this.columnPinning.left?.keys()` puts pinned-left body cells
1839
+ // in declaration order (matching the header rendering and the
1840
+ // `getVisualColumns` tab-order). Iterating the pinning Map's
1841
+ // keys produced *insertion* order: pinning Name then ID gave a
1842
+ // body row of [Name, ID] while the header row above it was
1843
+ // [ID, Name], and tab navigation walked [Name, ID] instead of
1844
+ // the visually-left-to-right [ID, Name].
1845
+ const leftVisibleCells = $derived(this.columnDefs
1846
+ .filter((it) => {
1847
+ return (this.columnVisibility[it.id] !== false && this.columnPinning.left?.has(it.id) === true);
1848
+ })
1849
+ .map(makeRow));
1850
+ const rightVisibleCells = $derived(this.columnDefs
1851
+ .filter((it) => {
1852
+ return (this.columnVisibility[it.id] !== false && this.columnPinning.right?.has(it.id) === true);
1853
+ })
1854
+ .map(makeRow));
2053
1855
  const centerVisibleCells = $derived(this.columnDefs
2054
1856
  .filter((it) => {
2055
- return !(this.columnPinning.left?.has(it.id) || this.columnPinning.right?.has(it.id));
1857
+ return (this.columnVisibility[it.id] !== false &&
1858
+ !(this.columnPinning.left?.has(it.id) || this.columnPinning.right?.has(it.id)));
2056
1859
  })
2057
1860
  .map(makeRow));
2058
1861
  return row;
@@ -2062,87 +1865,57 @@ export class TableContext {
2062
1865
  this.columnVisibility[it.id] !== false &&
2063
1866
  !uncopyableColumns.has(it.columnType));
2064
1867
  };
1868
+ /**
1869
+ * Builds the user-controlled settings blob from the current
1870
+ * `$state` slots and delegates the actual `localStorage.setItem`
1871
+ * call to `_preferences.persistSettings` (Phase 6.4). The settings
1872
+ * field set (`isCompact`, `singleClickEditing`, etc.) still lives
1873
+ * on `TableContext` because each one drives reactive UI; only the
1874
+ * persistence side of the boundary moved.
1875
+ */
2065
1876
  _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
1877
  const settingsObject = {
2073
- isCompact,
2074
- singleClickEditing,
2075
- alternateRowColours,
2076
- changeColourOnHover,
2077
- copyWithHeaders,
2078
- commaSeparatedCopy,
1878
+ isCompact: this.isCompact,
1879
+ singleClickEditing: this.singleClickEditing,
1880
+ alternateRowColours: this.alternateRowColours,
1881
+ changeColourOnHover: this.changeColourOnHover,
1882
+ copyWithHeaders: this.copyWithHeaders,
1883
+ commaSeparatedCopy: this.commaSeparatedCopy,
2079
1884
  };
2080
- localStorage.setItem(`${this.userId}-${this.tableName}-settings`, JSON.stringify(settingsObject));
1885
+ this._preferences.persistSettings(settingsObject);
2081
1886
  }
2082
1887
  /**
2083
- * Columns in visual display order: pinned-left → center → pinned-right,
2084
- * preserving memory order within each section. Only includes visible columns.
1888
+ * Delegating accessor preserved for backwards compatibility. The
1889
+ * cached visual-column array and its paired O(1) id->index map now
1890
+ * live on `_columnLayout` (Phase 6.3). The underscore prefix is
1891
+ * preserved on `TableContext` to keep the existing read sites in
1892
+ * `tabForward` / `focusToStart` / `moveFocusLeft` / etc. untouched.
2085
1893
  */
2086
1894
  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
- ];
1895
+ return this._columnLayout.visualColumns;
2096
1896
  }
2097
- /** Returns the visual index of the column with the given id, or -1 if not found. */
1897
+ /** Returns the visual index of the column with the given id, or
1898
+ * -1 if not found. Delegates to `_columnLayout.columnVisualIndex`
1899
+ * which provides the O(1) lookup. */
2098
1900
  columnVisualIndex(id) {
2099
- return this._visualColumns.findIndex((it) => it.id === id);
1901
+ return this._columnLayout.columnVisualIndex(id);
2100
1902
  }
1903
+ /**
1904
+ * Delegating getter preserved for backwards compatibility. The
1905
+ * implementation lives on `_focus.focusStartVisualColumn`
1906
+ * (Phase 6.6); the visual-index lookup itself is owned by
1907
+ * `_columnLayout.columnVisualIndex` (Phase 6.3).
1908
+ */
2101
1909
  get focusStartVisualColumn() {
2102
- if (!this.focusStart) {
2103
- return -1;
2104
- }
2105
- return this.columnVisualIndex(this.focusStart.id);
1910
+ return this._focus.focusStartVisualColumn;
2106
1911
  }
1912
+ /**
1913
+ * Delegating getter preserved for backwards compatibility. The
1914
+ * implementation lives on `_focus.focusEndVisualColumn`
1915
+ * (Phase 6.6).
1916
+ */
2107
1917
  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 });
1918
+ return this._focus.focusEndVisualColumn;
2146
1919
  }
2147
1920
  async getHighlightedRowData() {
2148
1921
  const affectedColumns = this.columnDefs.filter(this.columnDefReturnsTextValue);
@@ -2289,28 +2062,7 @@ export class TableContext {
2289
2062
  };
2290
2063
  }
2291
2064
  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;
2065
+ return this._columnLayout.updateDefsBasedOnPinning();
2314
2066
  };
2315
2067
  assertNoDuplicateIds() {
2316
2068
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
@@ -2322,10 +2074,8 @@ export class TableContext {
2322
2074
  foundIds.add(def.id);
2323
2075
  });
2324
2076
  }
2325
- // FIXME
2326
2077
  // eslint-disable-next-line @typescript-eslint/require-await
2327
2078
  async setupDefaultColumnDefs() {
2328
- // const helper = createColumnHelper<T>();
2329
2079
  this.assertNoDuplicateIds();
2330
2080
  const adjustedDefsPromises = this.defaultColumnDefs.defs.map((def) => {
2331
2081
  if (def instanceof AccessorDef) {
@@ -2460,47 +2210,44 @@ export class TableContext {
2460
2210
  this.columnDefs = adjustedDefs;
2461
2211
  return adjustedDefs;
2462
2212
  }
2213
+ /**
2214
+ * Reads the persisted per-table user-settings blob through
2215
+ * `_preferences.loadSavedSettings` (Phase 6.4) and applies each
2216
+ * field to the live `$state` slot. Field defaults mirror the
2217
+ * pre-extraction behaviour:
2218
+ *
2219
+ * - missing slot: `singleClickEditing` defaults to `true`; every
2220
+ * other field keeps its constructor default.
2221
+ * - present slot: each field is applied if it would deviate from
2222
+ * the constructor default (the "only alter when the user
2223
+ * explicitly disabled" semantics are preserved verbatim).
2224
+ */
2463
2225
  _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 {
2226
+ const saved = this._preferences.loadSavedSettings();
2227
+ if (saved === null) {
2486
2228
  this.singleClickEditing = true;
2229
+ return;
2230
+ }
2231
+ this.singleClickEditing = saved.singleClickEditing ?? true;
2232
+ if (saved.alternateRowColours) {
2233
+ this.alternateRowColours = true;
2234
+ }
2235
+ if (saved.commaSeparatedCopy) {
2236
+ this.commaSeparatedCopy = true;
2237
+ }
2238
+ // these are true by default so we only want to alter them if the user wants them disabled
2239
+ if (saved.isCompact === false) {
2240
+ this.isCompact = false;
2241
+ }
2242
+ if (saved.copyWithHeaders === false) {
2243
+ this.copyWithHeaders = false;
2244
+ }
2245
+ if (saved.changeColourOnHover === false) {
2246
+ this.changeColourOnHover = false;
2487
2247
  }
2488
2248
  }
2489
2249
  _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
2250
+ this._columnLayout.setNewPinningState();
2504
2251
  }
2505
2252
  getToggleSortingHandler = (def) => () => {
2506
2253
  this.paginationRepo?.updateSorting?.((state) => {
@@ -2524,7 +2271,6 @@ export class TableContext {
2524
2271
  return newState;
2525
2272
  });
2526
2273
  };
2527
- // FIXME
2528
2274
  // eslint-disable-next-line @typescript-eslint/require-await
2529
2275
  async reassignDefsToPresets(presets) {
2530
2276
  const adjustedDefs = this.columnDefs;
@@ -2591,9 +2337,51 @@ export function isSearchable(def) {
2591
2337
  function hasPagination(repository) {
2592
2338
  return 'setPage' in repository;
2593
2339
  }
2340
+ /**
2341
+ * Neutralises CSV / TSV formula-injection prefixes recognised by Excel,
2342
+ * LibreOffice Calc, Google Sheets, and Apple Numbers.
2343
+ *
2344
+ * When a clipboard payload reaches a spreadsheet, any cell whose first
2345
+ * character is one of `=`, `+`, `-`, `@`, `\t`, `\r` is treated as a
2346
+ * formula and evaluated in the user's context. Untrusted row data
2347
+ * containing `=cmd|'/c calc'!A1` (or DDE / web-query equivalents) is the
2348
+ * canonical attack vector. Prefixing such a value with a single quote
2349
+ * (`'`) makes the spreadsheet interpret the cell as a literal string
2350
+ * instead.
2351
+ *
2352
+ * Trade-off: the leading apostrophe is invisible in the rendered cell of
2353
+ * every major spreadsheet, but the underlying cell value differs from the
2354
+ * source when read back by another formula referencing the cell. This is
2355
+ * the accepted industry mitigation (OWASP CSV Injection cheat sheet).
2356
+ *
2357
+ * Private helper - keep this colocated with `encapsulate` so every
2358
+ * clipboard-bound string flows through both functions before being
2359
+ * written to the OS clipboard.
2360
+ */
2361
+ function escapeForSpreadsheet(value) {
2362
+ if (value.length === 0) {
2363
+ return value;
2364
+ }
2365
+ const first = value.charCodeAt(0);
2366
+ // '=' 61, '+' 43, '-' 45, '@' 64, '\t' 9, '\r' 13
2367
+ if (first === 61 || first === 43 || first === 45 || first === 64 || first === 9 || first === 13) {
2368
+ return `'${value}`;
2369
+ }
2370
+ return value;
2371
+ }
2594
2372
  function encapsulate(value) {
2595
- return `"${value.replace(/[\n]/gm, ' ').replace(/["]/g, '""')}"`;
2373
+ const safe = escapeForSpreadsheet(value);
2374
+ return `"${safe.replace(/[\n]/gm, ' ').replace(/["]/g, '""')}"`;
2596
2375
  }
2376
+ /**
2377
+ * Internal exports for tests. Not part of the public API; do not import
2378
+ * from consumer code. Allows the clipboard formula-escape regression test
2379
+ * to call the module-private helpers directly.
2380
+ */
2381
+ export const _internal = {
2382
+ escapeForSpreadsheet,
2383
+ encapsulate,
2384
+ };
2597
2385
  function generateTableOptions(contextObj) {
2598
2386
  const _pagination = $derived.by(() => {
2599
2387
  void contextObj.paginationRepo?.paginationState.pageSize;
@@ -2616,6 +2404,22 @@ function generateTableOptions(contextObj) {
2616
2404
  let originalWidth = 0;
2617
2405
  let mouseStartingPoint = 0;
2618
2406
  function addOverrideResizeHandler(e) {
2407
+ // Capture the column's currently rendered width as the
2408
+ // drag's starting point, then mark this column as
2409
+ // user-resized. Going forward the grid template will use a
2410
+ // fixed `${width}px` track for this column (see
2411
+ // `Table.svelte` colsTemplate) so dragging the handle only
2412
+ // changes this column. Other columns that have not yet
2413
+ // been user-resized continue to flex via `minmax(W, 1fr)`,
2414
+ // which means they automatically take up any horizontal
2415
+ // space the resized column gives back when shrunk - the
2416
+ // table never ends up narrower than its container while
2417
+ // any column still flexes.
2418
+ const rendered = contextObj._columnRenderedWidths.get(def.id);
2419
+ if (rendered != null && rendered > 0) {
2420
+ def.width = rendered;
2421
+ }
2422
+ def.userResized = true;
2619
2423
  originalWidth = def.width;
2620
2424
  mouseStartingPoint = e.clientX;
2621
2425
  def.isResizing = true;
@@ -2623,7 +2427,14 @@ function generateTableOptions(contextObj) {
2623
2427
  window.addEventListener('mouseup', removeOverrideResizeHandler);
2624
2428
  }
2625
2429
  function onResize(e) {
2626
- def.width = originalWidth - (mouseStartingPoint - e.clientX);
2430
+ // Clamp the resize floor so the column can never reach a
2431
+ // negative or zero width. With the div+grid layout a
2432
+ // non-positive `width` produces an invalid track size
2433
+ // in `grid-template-columns`, which makes the browser
2434
+ // fall back to single-column block layout and visually
2435
+ // collapses the row.
2436
+ const next = originalWidth - (mouseStartingPoint - e.clientX);
2437
+ def.width = Math.max(next, 20);
2627
2438
  }
2628
2439
  function removeOverrideResizeHandler() {
2629
2440
  def.isResizing = false;
@@ -2753,111 +2564,6 @@ function generateTableOptions(contextObj) {
2753
2564
  // }
2754
2565
  };
2755
2566
  }
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
2567
  function adaptJSONPinningState(state) {
2862
2568
  //HACK ignore previously broken pinning states
2863
2569
  return {