@lavalogic/scoria 0.37.44 → 0.37.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +58 -58
  2. package/dist/Components/AccordionGroup.svelte +46 -46
  3. package/dist/Components/AccordionGroupButton.svelte +76 -76
  4. package/dist/Components/Action.svelte +136 -136
  5. package/dist/Components/ActionProps.d.ts +2 -6
  6. package/dist/Components/AlertModal.svelte +195 -183
  7. package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte +702 -702
  8. package/dist/Components/Base/BaseModal.svelte +125 -125
  9. package/dist/Components/Base/ContextWrapper.svelte +72 -72
  10. package/dist/Components/Base/Pagination.svelte +198 -198
  11. package/dist/Components/Base/Snippets.svelte +36 -36
  12. package/dist/Components/BigRadioSet.svelte +38 -38
  13. package/dist/Components/Bubble.svelte +101 -101
  14. package/dist/Components/Button.svelte +209 -209
  15. package/dist/Components/ButtonProps.d.ts +2 -6
  16. package/dist/Components/Checkbox.svelte +86 -86
  17. package/dist/Components/CollapsibleCard.svelte +78 -78
  18. package/dist/Components/Contexts/ContextMenu.svelte +164 -164
  19. package/dist/Components/Contexts/ContextMenuDivider.svelte +15 -15
  20. package/dist/Components/Contexts/ContextMenuOption.svelte +60 -60
  21. package/dist/Components/Contexts/Toast.svelte +83 -83
  22. package/dist/Components/Contexts/ToastContainer.svelte +65 -65
  23. package/dist/Components/Contexts/Tooltip.svelte +37 -37
  24. package/dist/Components/DataMatrixIcon.svelte +112 -112
  25. package/dist/Components/DataMatrixIconProps.d.ts +3 -13
  26. package/dist/Components/DateInput.svelte +148 -148
  27. package/dist/Components/DatePicker.svelte +300 -300
  28. package/dist/Components/DesktopModal.svelte +90 -90
  29. package/dist/Components/Dial.svelte +204 -204
  30. package/dist/Components/DialProps.d.ts +1 -18
  31. package/dist/Components/DimensionInput.svelte +170 -170
  32. package/dist/Components/DragMenu/DragMenuHolder.svelte +119 -119
  33. package/dist/Components/DragMenu/DraggableCard.svelte +77 -77
  34. package/dist/Components/FileCard.svelte +24 -24
  35. package/dist/Components/FileUpload.svelte +236 -236
  36. package/dist/Components/GridInput.svelte +196 -200
  37. package/dist/Components/GridInputSet.svelte +29 -29
  38. package/dist/Components/HorizontalTabGroup.svelte +82 -82
  39. package/dist/Components/HorizontalTabGroupButton.svelte +44 -44
  40. package/dist/Components/Icon.svelte +128 -128
  41. package/dist/Components/IconProps.d.ts +2 -7
  42. package/dist/Components/Icons.js +357 -357
  43. package/dist/Components/LoadingAnimation.svelte +29 -29
  44. package/dist/Components/LoadingModal.svelte +45 -45
  45. package/dist/Components/LoadingOverlay.svelte +25 -25
  46. package/dist/Components/LoadingOverlayProps.d.ts +3 -7
  47. package/dist/Components/Modal.svelte +258 -258
  48. package/dist/Components/ModalProps.d.ts +0 -3
  49. package/dist/Components/MultiSelect.svelte +220 -220
  50. package/dist/Components/MultiSelectCommonProps.d.ts +6 -17
  51. package/dist/Components/Nav/Nav.svelte +99 -99
  52. package/dist/Components/Nav/NavProps.d.ts +4 -11
  53. package/dist/Components/Nav/NavServiceMenuList.svelte +43 -43
  54. package/dist/Components/Nav/NavTab.svelte +62 -62
  55. package/dist/Components/Nav/ServiceMenuItem.svelte +100 -100
  56. package/dist/Components/Nav/ServicesNav.svelte +80 -80
  57. package/dist/Components/NumberInput.svelte +157 -157
  58. package/dist/Components/PageHeading.svelte +25 -25
  59. package/dist/Components/PasswordInput.svelte +88 -88
  60. package/dist/Components/Portal.svelte +36 -36
  61. package/dist/Components/ProgressBubble.svelte +107 -108
  62. package/dist/Components/ProgressBubbleProps.d.ts +3 -3
  63. package/dist/Components/QuerySelect.svelte +366 -366
  64. package/dist/Components/QuerySelectProps.d.ts +4 -14
  65. package/dist/Components/SingleSelect.svelte +250 -250
  66. package/dist/Components/SingleSelectCommonProps.d.ts +3 -19
  67. package/dist/Components/StepButton.svelte +70 -70
  68. package/dist/Components/StepForm.svelte +140 -140
  69. package/dist/Components/Switch.svelte +69 -69
  70. package/dist/Components/Table/ARCHITECTURE.md +298 -0
  71. package/dist/Components/Table/Body/QuickSearchCell.svelte +679 -694
  72. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +51 -51
  73. package/dist/Components/Table/Body/Rows/ColumnList.svelte +59 -59
  74. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +456 -478
  75. package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +79 -79
  76. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +185 -188
  77. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +124 -124
  78. package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +120 -120
  79. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +230 -230
  80. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +105 -105
  81. package/dist/Components/Table/Body/Rows/Columns/SelectColumn.d.ts +12 -9
  82. package/dist/Components/Table/Body/Rows/Columns/SelectTableCell.d.ts +5 -5
  83. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +87 -87
  84. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +126 -126
  85. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +159 -159
  86. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +246 -246
  87. package/dist/Components/Table/Body/Rows/Columns/TableDatePickerProps.d.ts +4 -4
  88. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +216 -216
  89. package/dist/Components/Table/Body/Rows/Columns/TableQueryCell.d.ts +6 -5
  90. package/dist/Components/Table/Body/Rows/Columns/TableQueryColumn.d.ts +7 -6
  91. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +252 -257
  92. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +179 -179
  93. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +221 -229
  94. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte.d.ts +11 -11
  95. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +197 -197
  96. package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +190 -190
  97. package/dist/Components/Table/Body/Rows/TableRow.svelte +290 -290
  98. package/dist/Components/Table/Body/TableBody.svelte +155 -155
  99. package/dist/Components/Table/ColumnDefinitions/ExampleItemColumnDefRepository.svelte.js +2 -3
  100. package/dist/Components/Table/ColumnFactory.svelte.d.ts +9 -11
  101. package/dist/Components/Table/ColumnFactory.svelte.js +101 -118
  102. package/dist/Components/Table/ColumnKindRegistry.d.ts +112 -0
  103. package/dist/Components/Table/ColumnKindRegistry.js +63 -0
  104. package/dist/Components/Table/Headers/HighlightAllHeader.svelte +41 -41
  105. package/dist/Components/Table/Headers/SelectAllHeader.svelte +30 -30
  106. package/dist/Components/Table/Headers/TableHead.svelte +415 -413
  107. package/dist/Components/Table/Misc/ColumnPanel.svelte +94 -94
  108. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +112 -112
  109. package/dist/Components/Table/Misc/FilterInput.svelte +620 -644
  110. package/dist/Components/Table/Misc/FilterInput.svelte.d.ts +12 -12
  111. package/dist/Components/Table/Misc/FilterInputProps.d.ts +2 -2
  112. package/dist/Components/Table/Misc/FilterPanel.svelte +257 -257
  113. package/dist/Components/Table/Misc/FilterPanelModal.svelte +30 -30
  114. package/dist/Components/Table/Misc/SidePanel.svelte +35 -35
  115. package/dist/Components/Table/Misc/TableFooter.svelte +311 -311
  116. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +125 -125
  117. package/dist/Components/Table/Misc/TableSidebar.svelte +157 -157
  118. package/dist/Components/Table/Misc/ToolboxPanel.svelte +100 -100
  119. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +107 -107
  120. package/dist/Components/Table/NestedTable.svelte +130 -133
  121. package/dist/Components/Table/SubApis.svelte.d.ts +1 -5
  122. package/dist/Components/Table/SubApis.svelte.js +33 -32
  123. package/dist/Components/Table/Table.svelte +692 -745
  124. package/dist/Components/Table/TableHelpers.svelte.d.ts +6 -19
  125. package/dist/Components/Table/TableHelpers.svelte.js +6 -14
  126. package/dist/Components/Table/Types/Columns/Column.d.ts +2 -2
  127. package/dist/Components/Table/Types/Columns/DateColumn.d.ts +3 -3
  128. package/dist/Components/Table/Types/Columns/DateTableCell.d.ts +7 -7
  129. package/dist/Components/Table/Types/Columns/Definitions/Accessors/AccessorDef.svelte.d.ts +7 -8
  130. package/dist/Components/Table/Types/Columns/Definitions/Accessors/AccessorDef.svelte.js +4 -5
  131. package/dist/Components/Table/Types/Columns/Definitions/Accessors/CheckboxDef.svelte.d.ts +11 -14
  132. package/dist/Components/Table/Types/Columns/Definitions/Accessors/CheckboxDef.svelte.js +3 -4
  133. package/dist/Components/Table/Types/Columns/Definitions/Accessors/DateInputDef.svelte.d.ts +7 -11
  134. package/dist/Components/Table/Types/Columns/Definitions/Accessors/DateInputDef.svelte.js +3 -3
  135. package/dist/Components/Table/Types/Columns/Definitions/Accessors/NumberInputDef.svelte.d.ts +13 -18
  136. package/dist/Components/Table/Types/Columns/Definitions/Accessors/NumberInputDef.svelte.js +4 -5
  137. package/dist/Components/Table/Types/Columns/Definitions/Accessors/QueryDef.svelte.d.ts +5 -5
  138. package/dist/Components/Table/Types/Columns/Definitions/Accessors/QueryDef.svelte.js +2 -2
  139. package/dist/Components/Table/Types/Columns/Definitions/Accessors/SelectDef.svelte.d.ts +15 -12
  140. package/dist/Components/Table/Types/Columns/Definitions/Accessors/SelectDef.svelte.js +4 -5
  141. package/dist/Components/Table/Types/Columns/Definitions/Accessors/TextInputDef.svelte.d.ts +8 -12
  142. package/dist/Components/Table/Types/Columns/Definitions/Accessors/TextInputDef.svelte.js +3 -3
  143. package/dist/Components/Table/Types/Columns/Definitions/Actions/ActionsDef.svelte.d.ts +6 -6
  144. package/dist/Components/Table/Types/Columns/Definitions/Actions/ActionsDef.svelte.js +3 -3
  145. package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.d.ts +49 -16
  146. package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.js +27 -5
  147. package/dist/Components/Table/Types/Columns/Definitions/Display/AbstractDisplayDef.svelte.d.ts +7 -7
  148. package/dist/Components/Table/Types/Columns/Definitions/Display/AbstractDisplayDef.svelte.js +4 -4
  149. package/dist/Components/Table/Types/Columns/Definitions/Display/BubbleDef.svelte.d.ts +5 -5
  150. package/dist/Components/Table/Types/Columns/Definitions/Display/BubbleDef.svelte.js +2 -2
  151. package/dist/Components/Table/Types/Columns/Definitions/Display/DisplayDef.svelte.d.ts +5 -5
  152. package/dist/Components/Table/Types/Columns/Definitions/Display/DisplayDef.svelte.js +2 -2
  153. package/dist/Components/Table/Types/Columns/Definitions/Display/ProgressBarDef.svelte.d.ts +5 -5
  154. package/dist/Components/Table/Types/Columns/Definitions/Display/ProgressBarDef.svelte.js +2 -2
  155. package/dist/Components/Table/Types/Columns/Definitions/Display/ValidityDef.svelte.d.ts +5 -5
  156. package/dist/Components/Table/Types/Columns/Definitions/Display/ValidityDef.svelte.js +2 -2
  157. package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.d.ts +11 -19
  158. package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.js +4 -8
  159. package/dist/Components/Table/Types/Columns/Definitions/FilterOnly/FilterOnlyDef.svelte.d.ts +6 -7
  160. package/dist/Components/Table/Types/Columns/Definitions/FilterOnly/FilterOnlyDef.svelte.js +3 -4
  161. package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.d.ts +9 -9
  162. package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.js +4 -4
  163. package/dist/Components/Table/Types/Columns/ExpandTableCell.d.ts +5 -5
  164. package/dist/Components/Table/Types/Columns/ProgressBarTableCell.d.ts +6 -6
  165. package/dist/Components/Table/Types/Columns/TableCell.d.ts +7 -6
  166. package/dist/Components/Table/Types/Context/ClipboardState.svelte.d.ts +98 -0
  167. package/dist/Components/Table/Types/Context/ClipboardState.svelte.js +290 -0
  168. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.d.ts +17 -45
  169. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.js +17 -45
  170. package/dist/Components/Table/Types/Context/FilteringState.svelte.d.ts +9 -20
  171. package/dist/Components/Table/Types/Context/FilteringState.svelte.js +9 -20
  172. package/dist/Components/Table/Types/Context/FocusState.svelte.d.ts +23 -42
  173. package/dist/Components/Table/Types/Context/FocusState.svelte.js +32 -59
  174. package/dist/Components/Table/Types/Context/ITable.d.ts +5 -8
  175. package/dist/Components/Table/Types/Context/PreferencesState.svelte.d.ts +29 -56
  176. package/dist/Components/Table/Types/Context/PreferencesState.svelte.js +28 -55
  177. package/dist/Components/Table/Types/Context/QueryValuesState.svelte.d.ts +50 -0
  178. package/dist/Components/Table/Types/Context/QueryValuesState.svelte.js +62 -0
  179. package/dist/Components/Table/Types/Context/SelectionState.svelte.d.ts +17 -28
  180. package/dist/Components/Table/Types/Context/SelectionState.svelte.js +17 -28
  181. package/dist/Components/Table/Types/Context/SortingState.svelte.d.ts +15 -29
  182. package/dist/Components/Table/Types/Context/SortingState.svelte.js +17 -31
  183. package/dist/Components/Table/Types/Context/TableContext.svelte.d.ts +212 -325
  184. package/dist/Components/Table/Types/Context/TableContext.svelte.js +292 -706
  185. package/dist/Components/Table/Types/Context/VirtualisationState.svelte.d.ts +62 -79
  186. package/dist/Components/Table/Types/Context/VirtualisationState.svelte.js +54 -73
  187. package/dist/Components/Table/Types/DataRepository/BuiltInRemoteRepository.svelte.d.ts +16 -27
  188. package/dist/Components/Table/Types/DataRepository/BuiltInRemoteRepository.svelte.js +16 -27
  189. package/dist/Components/Table/Types/DataRepository/IDataRepository.d.ts +12 -15
  190. package/dist/Components/Table/Types/DataRepository/TableDataRepository.svelte.d.ts +15 -30
  191. package/dist/Components/Table/Types/DataRepository/TableDataRepository.svelte.js +16 -28
  192. package/dist/Components/Table/Types/Header.d.ts +4 -4
  193. package/dist/Components/Table/Types/HeaderGroup.d.ts +5 -5
  194. package/dist/Components/Table/Types/Public/index.d.ts +2 -4
  195. package/dist/Components/Table/Types/Public/index.js +2 -4
  196. package/dist/Components/Table/clipboardEscape.d.ts +32 -0
  197. package/dist/Components/Table/clipboardEscape.js +45 -0
  198. package/dist/Components/Table/createTable.svelte.d.ts +8 -18
  199. package/dist/Components/Table/createTable.svelte.js +19 -47
  200. package/dist/Components/Test/TestContextConsumer.svelte +13 -13
  201. package/dist/Components/TextArea.svelte +130 -130
  202. package/dist/Components/TextInput.svelte +176 -176
  203. package/dist/Components/ThreeStateRadio.svelte +131 -131
  204. package/dist/Components/Touch/BoolCard.svelte +45 -45
  205. package/dist/Components/Touch/DateModal.svelte +209 -209
  206. package/dist/Components/Touch/EditCard.svelte +59 -59
  207. package/dist/Components/Touch/InfoCard.svelte +31 -31
  208. package/dist/Components/Touch/InfoTextCard.svelte +28 -28
  209. package/dist/Components/Touch/ModalExplanation.svelte +17 -17
  210. package/dist/Components/Touch/NumberKeyboard.svelte +225 -225
  211. package/dist/Components/Touch/NumberModal.svelte +79 -79
  212. package/dist/Components/Touch/PlusMinusCard.svelte +46 -46
  213. package/dist/Components/Touch/SelectModal.svelte +123 -123
  214. package/dist/Components/Touch/SummaryCard.svelte +93 -93
  215. package/dist/Components/Touch/TextAreaModal.svelte +95 -95
  216. package/dist/Components/Touch/TextModal.svelte +106 -106
  217. package/dist/Components/Touch/TouchCard.svelte +33 -33
  218. package/dist/Components/Touch/TouchModal.svelte +274 -274
  219. package/dist/Components/Touch/UtilityButton.svelte +109 -109
  220. package/dist/Components/VerticalTabGroup.svelte +79 -79
  221. package/dist/Components/VerticalTabGroupButton.svelte +58 -58
  222. package/dist/Contexts/ToastContext.svelte.js +3 -4
  223. package/dist/Helpers/Datamatrix.d.ts +19 -19
  224. package/dist/Helpers/Datamatrix.js +291 -291
  225. package/dist/Helpers/Helpers.svelte.d.ts +2 -4
  226. package/dist/Helpers/Helpers.svelte.js +2 -4
  227. package/dist/Helpers/dropdownPortal.d.ts +26 -41
  228. package/dist/Helpers/dropdownPortal.js +62 -116
  229. package/dist/Types/Examples/ExampleModal.svelte +35 -35
  230. package/dist/index.d.ts +0 -1
  231. package/dist/scss/_basics.scss +12 -12
  232. package/dist/scss/_colours.scss +134 -134
  233. package/dist/scss/_mixins.scss +3348 -3360
  234. package/dist/scss/_motion.scss +57 -57
  235. package/dist/scss/_sizing.scss +85 -85
  236. package/dist/scss/_transitions.scss +8 -8
  237. package/package.json +2 -1
@@ -1,747 +1,694 @@
1
- <svelte:options runes />
2
-
3
- <script
4
- lang="ts"
5
- module
6
- >
7
- import {
8
- ContextMenuContext,
9
- type IContextMenuOption,
10
- } from '../../Contexts/ContextMenuContext.svelte';
11
- import { cssLength, devCatch, refWrapper } from '../../Helpers/Helpers.svelte';
12
- import { getContext, onMount, setContext, tick, untrack } from 'svelte';
13
- import LoadingOverlay from '../LoadingOverlay.svelte';
14
- import TableBody from './Body/TableBody.svelte';
15
- import TableHead from './Headers/TableHead.svelte';
16
- import TableFooter from './Misc/TableFooter.svelte';
17
- import TableHorizontalBar from './Misc/TableHorizontalBar.svelte';
18
- import TableSidebar from './Misc/TableSidebar.svelte';
19
- import { TableContext } from './Types/Context/TableContext.svelte';
20
- import type { Primitive } from './Types/Context/TableInitOptions.js';
21
- import { PaginationType } from './Types/Pagination/PaginationType.js';
22
- import { ToolbarPosition } from './Types/Toolbar/ToolbarPosition.js';
23
-
24
- declare global {
25
- interface HTMLElementEventMap {
26
- edit: CustomEvent<{
27
- selectAll: boolean;
28
- }>;
29
- }
30
- }
31
-
32
- /**
33
- * Top-level orchestrator props. Accepts either:
34
- *
35
- * - `table`: the structured `Table` instance returned by `createTable(...)`.
36
- * This is the consumer-facing form.
37
- * - `tableContext`: the underlying `TableContext`. Used internally by
38
- * `NestedTable` which constructs a context directly for each
39
- * expansion. Not intended for consumer use.
40
- *
41
- * Exactly one of the two must be supplied; a runtime guard inside the
42
- * component throws if both are missing. The reference passed in is
43
- * captured at mount via `setContext` and should not be reassigned
44
- * afterwards.
45
- */
46
- export interface TableProps<T extends object, IdType extends Primitive> {
47
- readonly table?: { readonly _context: TableContext<T, IdType> };
48
- readonly tableContext?: TableContext<T, IdType>;
49
- nested?: boolean;
50
- }
51
-
52
- /**
53
- * Symbol used to share the currently-open quick-filter select across
54
- * every header in a single Table tree. Stored via `setContext` on
55
- * the same component that hosts the Table tree so multiple
56
- * independent tables on a page each own their own popup state.
57
- */
58
- export const quickFilterContextIdentifier: symbol = Symbol();
59
-
60
- const openedQuickFilterSelect = refWrapper<symbol | null>(null);
61
- </script>
62
-
63
- <script
64
- lang="ts"
65
- generics="T extends object,IdType extends Primitive"
66
- >
67
- const { nested = false, table, tableContext: tableContextProp }: TableProps<T, IdType> = $props();
68
-
69
- // Resolve the underlying TableContext from whichever prop the caller
70
- // passed. Consumers pass `table` (the structured instance); the
71
- // internal `NestedTable` passes `tableContext` directly. Exactly one
72
- // must be present; throw cleanly otherwise so the failure mode is
73
- // obvious rather than a downstream null-deref. Result is bound to a
74
- // `tableContext` const so the existing 30+ references in this file
75
- // keep resolving without per-site renames.
76
- const tableContext: TableContext<T, IdType> = (() => {
77
- const ctx = table?._context ?? tableContextProp;
78
- if (!ctx) {
79
- throw new Error(
80
- 'scoria: <Table /> requires either a `table` prop (the structured instance returned by createTable) or a `tableContext` prop (used internally by NestedTable).'
81
- );
82
- }
83
- return ctx;
84
- })();
85
-
86
- // NOTE: known limitation. This is passed as a wrapper, and they should never require reassignment. It is deeply reactive
87
- setContext(TableContext.identifier, tableContext);
88
-
89
- const { openContextMenu: _openContextMenu } = getContext<ContextMenuContext>(
90
- ContextMenuContext.identifier
91
- );
92
-
93
- let ref: HTMLDivElement | undefined = $state();
94
- let unfocusRef: HTMLDivElement | undefined = $state();
95
-
96
- setContext(quickFilterContextIdentifier, openedQuickFilterSelect);
97
-
98
- // Focus-restore on focusStart change. The query/focus DOM work runs
99
- // inside `untrack` so a future selector tweak that reads reactive
100
- // state (e.g. document.activeElement) does not retrigger this
101
- // effect on every focus change.
102
- $effect(() => {
103
- void tableContext.focusStart;
104
- void ref;
105
- untrack(() => {
106
- if (tableContext.focusStart && ref) {
107
- const { row, column: index } = tableContext.focusStart;
108
- // `columnDefs[index]` can be undefined when the focus
109
- // coordinate references a memory slot that no longer
110
- // exists (e.g. inner expand tables remount with a
111
- // different column count, or a preset hydration races
112
- // the column-layout init). Guard before destructuring
113
- // so an out-of-range index degrades to "no focus
114
- // restored" instead of a hard crash.
115
- const def = tableContext.columnDefs[index];
116
- if (!def) {
117
- return;
118
- }
119
- let element = ref.querySelector(
120
- `#${tableContext.tableName}-cell-${row}-${def.id}`
121
- ) as HTMLElement | undefined;
122
-
123
- if (element?.tabIndex == -1) {
124
- const td = element;
125
- element = td.querySelector('button') as HTMLElement | undefined;
126
- if (!element) {
127
- element = td.querySelector('input') as HTMLElement | undefined;
128
- }
129
- }
130
-
131
- element?.focus({ preventScroll: true });
132
- // Same pinned-cell horizontal-scroll skip as the focusEnd
133
- // effect below. Sticky-pinned cells are always visually
134
- // in view, but their underlying layout position is at
135
- // their real column track, so an unconditional
136
- // `inline: 'nearest'` here scrolls the table sideways
137
- // every time focus lands inside a pinned column (e.g.
138
- // every click on a right-pinned Actions button).
139
- const startCell = element?.closest('[role="cell"]');
140
- const isStartPinned =
141
- startCell?.classList.contains('pinned-left') ||
142
- startCell?.classList.contains('pinned-right');
143
- element?.scrollIntoView({
144
- behavior: 'instant',
145
- block: 'nearest',
146
- inline: isStartPinned ? undefined : 'nearest',
147
- });
148
- }
149
- });
150
- });
151
-
152
- // Scroll-into-view on focusEnd change. The DOM work is wrapped in
153
- // untrack so reading layout properties from the queried cell never
154
- // silently re-establishes a reactive dependency.
155
- $effect(() => {
156
- void tableContext.focusEnd;
157
- void ref;
158
- untrack(() => {
159
- if (tableContext.focusEnd && ref) {
160
- const { row, column: index } = tableContext.focusEnd;
161
- // Same out-of-range guard as the focusStart effect above.
162
- const def = tableContext.columnDefs[index];
163
- if (!def) {
164
- return;
165
- }
166
- const cell = ref.querySelector(
167
- `#${tableContext.tableName}-cell-${row}-${def.id}`
168
- ) as HTMLElement | undefined;
169
-
170
- // Skip horizontal scroll-into-view for pinned (sticky)
171
- // cells. Sticky positioning makes the cell visually
172
- // "always on screen", but its true layout position is at
173
- // its real column track - which for a right-pinned column
174
- // in a wider-than-viewport table is offscreen. Asking the
175
- // browser to `scrollIntoView({inline: 'nearest'})` then
176
- // scrolls the table horizontally to bring the cell's
177
- // underlying position into view, which is the user-facing
178
- // "table jumps right when I click the Actions button"
179
- // bug. The vertical block axis still scrolls because the
180
- // row's vertical position is the user's actual scroll
181
- // target.
182
- const isPinned =
183
- cell?.classList.contains('pinned-left') ||
184
- cell?.classList.contains('pinned-right');
185
- cell?.scrollIntoView({
186
- behavior: 'instant',
187
- block: 'nearest',
188
- inline: isPinned ? undefined : 'nearest',
189
- });
190
- }
191
- });
192
- });
193
-
194
- /**
195
- * Routes a right-click into either a "focus the cell that was
196
- * clicked" path (if the click landed on a cell other than the
197
- * current focus) or a "show copy options" context menu (when the
198
- * Table is configured with `allowCopy` and the platform exposes the
199
- * Clipboard API).
200
- */
201
- function openContextMenu(e: MouseEvent) {
202
- e.stopPropagation();
203
-
204
- if (
205
- (e.target as HTMLElement).tagName === 'INPUT' ||
206
- (e.target as HTMLElement).closest('.select-wrapper') != null
207
- ) {
208
- return;
209
- }
210
-
211
- const clickedElement = document.activeElement?.id.split('-cell-')[1]?.split('-');
212
-
213
- if (clickedElement) {
214
- const [rowString, columnId] = clickedElement;
215
- const row = parseInt(rowString);
216
-
217
- if (
218
- !tableContext.focusedColumnIds.has(columnId) ||
219
- !tableContext.focusedRowIndices.has(row)
220
- ) {
221
- const columnIndex = tableContext.columnDefs.find((it) => it.id === columnId)?.index;
222
-
223
- // Use `!= null` rather than a truthy check so a column at
224
- // memory index 0 (the leftmost column) is not silently
225
- // rejected. The previous truthy check broke right-click
226
- // copy on the first column.
227
- if (columnIndex != null) {
228
- tableContext.moveFocus({
229
- column: columnIndex,
230
- row,
231
- id: columnId,
232
- });
233
- }
234
- }
235
- }
236
-
237
- // this may not exist in http contexts
238
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
239
- if (tableContext.allowCopy && window.Clipboard) {
240
- e.preventDefault();
241
-
242
- const options: Array<IContextMenuOption> = [
243
- {
244
- callback: () => {
245
- tableContext.copyDataToClipboard().catch(devCatch);
246
- },
247
- text: 'Copy Selection',
248
- },
249
- ];
250
-
251
- if (tableContext.hasHighlightedItems) {
252
- options.push({
253
- callback: () => {
254
- tableContext.copyHighlightedRowsToClipboard().catch(devCatch);
255
- },
256
- text: 'Copy Highlighted Rows',
257
- });
258
- }
259
-
260
- _openContextMenu(e, options).catch(devCatch);
261
- }
262
- }
263
-
264
- /**
265
- * Closes the quick-filter popup and the actions-cell popup whenever
266
- * the user clicks somewhere that is not inside either floating UI.
267
- */
268
- function handleClickOutside(e: MouseEvent) {
269
- const target = e.target as HTMLElement;
270
- if (target.isConnected) {
271
- // A click in a teleported Svelecte dropdown (see
272
- // `Helpers/dropdownPortal.ts`) is a logical extension of
273
- // the trigger inside the quick-filter popup, so it must
274
- // not close either floating popover. The portal helper
275
- // sets `data-scoria-portal-dropdown` on the dropdown
276
- // while it is teleported to `document.body`.
277
- const insidePortalDropdown = target.closest('[data-scoria-portal-dropdown]') != null;
278
- if (
279
- !insidePortalDropdown &&
280
- target.closest('div.floating') == null &&
281
- target.closest('div.stick-right') == null
282
- ) {
283
- openedQuickFilterSelect.value = null;
284
- }
285
-
286
- if (!insidePortalDropdown && target.closest('div.slot-wrapper') == null) {
287
- tableContext.shownActionsPopup = undefined;
288
- }
289
- }
290
- }
291
-
292
- // nested table resizing
293
- let isResizing = $state(false);
294
- let remPxRatio = $state(0);
295
- const minNestedHeight = $derived(20 * remPxRatio);
296
-
297
- let nestedHeight = $state(0);
298
- $effect.pre(() => {
299
- if (!isResizing) {
300
- nestedHeight = minNestedHeight;
301
- }
302
- });
303
-
304
- let tableRef: HTMLDivElement | undefined = $state();
305
- let mousedDownEmptySpace = $state(false);
306
-
307
- onMount(() => {
308
- remPxRatio = parseFloat(getComputedStyle(document.documentElement).fontSize);
309
- });
310
-
311
- $effect(() => {
312
- if (!isResizing) {
313
- return;
314
- }
315
- const onResize = (e: MouseEvent) => {
316
- const sliderHeight = tableRef ? e.clientY - tableRef.getBoundingClientRect().top : 0;
317
- nestedHeight = Math.max(sliderHeight, minNestedHeight);
318
- };
319
- window.addEventListener('mousemove', onResize);
320
- return () => {
321
- window.removeEventListener('mousemove', onResize);
322
- };
323
- });
324
-
325
- function addOverrideResizeHandler() {
326
- isResizing = true;
327
- }
328
-
329
- function removeOverrideResizeHandler() {
330
- isResizing = false;
331
- }
332
-
333
- const usesLoading = $derived(
334
- tableContext.paginationRepo &&
335
- tableContext.paginationRepo.paginationType === PaginationType.Remote
336
- );
337
-
338
- const shouldUnfocusRemote = $derived(
339
- usesLoading &&
340
- tableContext.paginationRepo?.isLoading === false &&
341
- tableContext.paginationRepo.paginationState.pageIndex !==
342
- tableContext.paginationRepo.previouslyViewedPage
343
- );
344
-
345
- const shouldUnfocusLocal = $derived(
346
- !usesLoading && tableContext.paginationRepo?.paginationState.pageIndex
347
- );
348
-
349
- $effect(() => {
350
- if ((shouldUnfocusRemote || shouldUnfocusLocal) && unfocusRef) {
351
- const el = unfocusRef;
352
- untrack(() => {
353
- tableContext.unfocus();
354
- el.scrollTop = 0;
355
- });
356
- } else if (usesLoading && tableContext.paginationRepo?.isLoading === false) {
357
- // refocus same element after a reload
358
- // this does not break mutifocus
359
- untrack(() => {
360
- const fs = tableContext.focusStart;
361
- if (!fs) {
362
- return;
363
- }
364
- const idToFocusTo = `#${tableContext.tableName}-cell-${fs.row}-${fs.id}`;
365
-
366
- tick()
367
- .then(() => {
368
- (document.querySelector(idToFocusTo) as HTMLElement | undefined)?.focus();
369
- })
370
- .catch(devCatch);
371
- });
372
- }
373
- });
374
-
375
- const leftColumns = $derived(tableContext.table.getLeftVisibleLeafColumns());
376
- const centreColumns = $derived(tableContext.table.getCenterVisibleLeafColumns());
377
- const rightColumns = $derived(tableContext.table.getRightVisibleLeafColumns());
378
-
379
- // CSS Grid template for the table.
380
- //
381
- // - First track is the highlight column (always present, fixed at
382
- // 20px to match the existing visual).
383
- // - Each other track uses one of two strategies based on the
384
- // column's `userResized` flag:
385
- // - `userResized = false`: track depends on whether the consumer
386
- // supplied an explicit `width` and / or `maxWidth`:
387
- // - `widthIsFixed` (consumer-supplied `width`, no `maxWidth`):
388
- // fixed `${width}px` track. Stays at exactly that pixel value
389
- // regardless of leftover container width.
390
- // - `maxWidth` set (with or without explicit width): the track
391
- // becomes `minmax(${width}px, ${maxWidth}px)`, a bounded flex
392
- // that grows up to `maxWidth` and is then capped.
393
- // - Neither (the column is on its per-kind default width):
394
- // `minmax(${width}px, 1fr)`, the column flexes to absorb any
395
- // horizontal space the fixed-width tracks leave behind.
396
- // This produces the "specify widths for the columns you care
397
- // about; let the rest fill the gap" behaviour that mirrors the
398
- // legacy `<table>` element when only some columns set a width.
399
- // - `userResized = true`: `${width}px` (fixed). The column sits
400
- // at exactly the size the user dragged it to, and dragging the
401
- // handle again only changes this column.
402
- // - Pinned and centre columns use the same per-column rule so
403
- // toggling a column's pin state does not change its width.
404
- // - Sticky-cell offsets are read from `_columnRenderedWidths`
405
- // (populated by TableHead's ResizeObserver), which gives
406
- // accurate sticky positions in both states.
407
- const colsTemplate = $derived.by(() => {
408
- void tableContext.columnSizing;
409
- const track = (col: {
410
- columnDef: {
411
- width: number;
412
- maxWidth?: number;
413
- userResized: boolean;
414
- widthIsFixed: boolean;
415
- };
416
- }) => {
417
- if (col.columnDef.userResized) {
418
- return `${col.columnDef.width}px`;
419
- }
420
- if (col.columnDef.maxWidth != null) {
421
- return `minmax(${col.columnDef.width}px, ${col.columnDef.maxWidth}px)`;
422
- }
423
- if (col.columnDef.widthIsFixed) {
424
- return `${col.columnDef.width}px`;
425
- }
426
- return `minmax(${col.columnDef.width}px, 1fr)`;
427
- };
428
- const tracks: Array<string> = ['20px'];
429
- for (const col of leftColumns) {
430
- tracks.push(track(col));
431
- }
432
- for (const col of centreColumns) {
433
- tracks.push(track(col));
434
- }
435
- for (const col of rightColumns) {
436
- tracks.push(track(col));
437
- }
438
- return tracks.join(' ');
439
- });
440
-
441
- // Total rendered width of the pinned-left and pinned-right column
442
- // stacks. Driven by `_columnRenderedWidths` (populated by
443
- // TableHead's ResizeObserver) so the values track actual layout,
444
- // not the declared `columnDef.width`. Exposed to CSS via
445
- // `--pinned-left` / `--pinned-right` on `.datatable`; the SCSS
446
- // then uses them in `scroll-padding-left` / `scroll-padding-right`
447
- // so that `scrollIntoView` (called by keyboard arrow navigation
448
- // and tab focus restore) keeps the focused cell out from behind
449
- // the pinned columns. The highlight column is always 20px and
450
- // always pinned-left when present, so it is added to the left
451
- // total unconditionally.
452
- const totalPinnedLeftWidth = $derived.by(() => {
453
- void tableContext._columnRenderedWidths.size;
454
- let total = 20;
455
- for (const col of leftColumns) {
456
- total += tableContext._columnRenderedWidths.get(col.id) ?? col.columnDef.width;
457
- }
458
- return total;
459
- });
460
- const totalPinnedRightWidth = $derived.by(() => {
461
- void tableContext._columnRenderedWidths.size;
462
- let total = 0;
463
- for (const col of rightColumns) {
464
- total += tableContext._columnRenderedWidths.get(col.id) ?? col.columnDef.width;
465
- }
466
- return total;
467
- });
468
-
469
- // Phase 7.4 row virtualisation - wire the single scroll listener
470
- // for the shared scroll container. The single grid container is
471
- // the scroll host now (the previous three-table siblings have
472
- // collapsed into one).
473
- //
474
- // The same handler also drives `hasScrolledPastHeader` /
475
- // `hasContentBelowFooter`, which feed the CSS variables that
476
- // gate the sticky-row drop shadows. The shadows only appear when
477
- // there is actually content scrolled under the relevant sticky
478
- // row, so a freshly mounted (or fully-in-viewport) table shows no
479
- // shadows.
480
- let hasScrolledPastHeader = $state(false);
481
- let hasContentBelowFooter = $state(false);
482
-
483
- // Measured rendered height of the sticky strip at the top of
484
- // `.datatable` (header row + quick-filter row when active).
485
- // Drives `scroll-padding-top` via `--sticky-top-height` on
486
- // `.datatable`, so `scrollIntoView` (keyboard arrow navigation,
487
- // focus restore) parks the focused cell below the actual strip
488
- // even at sub-pixel rounding and any borders. Using `calc(var(--height) + 61px)`
489
- // statically left a 1-2px gap that hid the top edge of the
490
- // first visible body row when navigating to it.
491
- let stickyTopHeight: number = $state(0);
492
-
493
- function recomputeScrollShadows(): void {
494
- if (!tableRef) {
495
- hasScrolledPastHeader = false;
496
- hasContentBelowFooter = false;
497
- return;
498
- }
499
- // `.datatable` has `overflow-x: auto`. Per the CSS spec, when
500
- // one overflow axis is non-visible the other axis becomes
501
- // auto too, so `.datatable` is itself the vertical scroll
502
- // container for the sticky header / footer when the table
503
- // is constrained inside a fixed-height parent (the typical
504
- // app shell). Detect that internal scroll directly.
505
- const internalScrolledPast = tableRef.scrollTop > 0;
506
- const internalContentBelow =
507
- tableRef.scrollTop + tableRef.clientHeight < tableRef.scrollHeight - 1;
508
- // Fall back to window-relative checks for the case where the
509
- // table is not constrained and the page itself is the scroll
510
- // container (e.g. a story with no wrapper height).
511
- const rect = tableRef.getBoundingClientRect();
512
- const windowScrolledPast = rect.top < 0;
513
- const windowContentBelow = rect.bottom > window.innerHeight;
514
- hasScrolledPastHeader = internalScrolledPast || windowScrolledPast;
515
- hasContentBelowFooter = internalContentBelow || windowContentBelow;
516
- }
517
-
518
- $effect(() => {
519
- void tableRef;
520
- void unfocusRef;
521
- const handler = (e: Event): void => {
522
- const el = e.currentTarget as HTMLElement | null;
523
- if (!el) {
524
- return;
525
- }
526
- tableContext._virtualisation.registerScrollPosition(el.scrollTop, el.clientHeight);
527
- // Internal scroll inside `.datatable` is the sticky-context
528
- // for the header / footer in the constrained-height case,
529
- // so the shadow flags need to refresh on every internal
530
- // scroll, not just window scroll. Without this, scrolling
531
- // the table body never flips `hasScrolledPastHeader` /
532
- // `hasContentBelowFooter` and the shadows never appear.
533
- recomputeScrollShadows();
534
- };
535
- const elements: Array<HTMLElement> = [];
536
- if (tableRef) {
537
- elements.push(tableRef);
538
- }
539
- if (unfocusRef && unfocusRef !== tableRef) {
540
- elements.push(unfocusRef);
541
- }
542
- for (const el of elements) {
543
- el.addEventListener('scroll', handler, { passive: true });
544
- }
545
- const primary = tableRef ?? unfocusRef;
546
- if (primary) {
547
- tableContext._virtualisation.registerScrollPosition(primary.scrollTop, primary.clientHeight);
548
- }
549
-
550
- // Window-level scroll + resize cover the case where the table
551
- // is not constrained and the page itself scrolls instead.
552
- // `recomputeScrollShadows` checks both internal and window-level
553
- // scroll position so either path flips the shadow flags.
554
- const onWindow = (): void => {
555
- recomputeScrollShadows();
556
- };
557
- window.addEventListener('scroll', onWindow, { passive: true });
558
- window.addEventListener('resize', onWindow, { passive: true });
559
-
560
- // Content-height changes (rows added / removed, pagination
561
- // turning the page, expand toggled) flip whether there is
562
- // anything below the footer or above the header, so shadow
563
- // flags need to re-evaluate any time the table's intrinsic
564
- // height changes - not just on actual user scroll.
565
- const ro =
566
- typeof ResizeObserver === 'undefined'
567
- ? null
568
- : new ResizeObserver(() => {
569
- recomputeScrollShadows();
570
- });
571
- if (ro && tableRef) {
572
- ro.observe(tableRef);
573
- }
574
-
575
- // Measure the actual rendered height of the sticky strip
576
- // (header row + quick-filter row when active) so
577
- // `scroll-padding-top` matches it to the pixel. Static
578
- // `calc(var(--height) + 61px)` ignored the header's
579
- // border-bottom plus any sub-pixel rounding, leaving the
580
- // first visible body row 1-2px behind the strip when
581
- // `scrollIntoView` parked it at the top edge.
582
- const measureStickyTop = (): void => {
583
- if (!tableRef) {
584
- return;
585
- }
586
- const headerRow = tableRef.querySelector('.datatable-row.header-row') as HTMLElement | null;
587
- const filterRow = tableRef.querySelector(
588
- '.datatable-row.quick-filter-row'
589
- ) as HTMLElement | null;
590
- stickyTopHeight = (headerRow?.offsetHeight ?? 0) + (filterRow?.offsetHeight ?? 0);
591
- };
592
- const stickyRo =
593
- typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measureStickyTop);
594
- if (stickyRo && tableRef) {
595
- // Observe the scroll host so any change inside (mode
596
- // toggle, rows added, etc.) re-measures on the next frame.
597
- stickyRo.observe(tableRef);
598
- }
599
- // Initial measurement so the very first focus / arrow-key
600
- // navigation lands below the strip without waiting for the
601
- // observer's first frame.
602
- measureStickyTop();
603
-
604
- recomputeScrollShadows();
605
-
606
- return () => {
607
- for (const el of elements) {
608
- el.removeEventListener('scroll', handler);
609
- }
610
- window.removeEventListener('scroll', onWindow);
611
- window.removeEventListener('resize', onWindow);
612
- ro?.disconnect();
613
- stickyRo?.disconnect();
614
- };
615
- });
616
- </script>
617
-
618
- <svelte:window
619
- onmouseup={(e: MouseEvent) => {
620
- if (
621
- // if it's not a click on an item in the context menu
622
- !(e.target as HTMLElement | undefined)?.classList.contains('context-menu-item') &&
623
- // if it's outside the table or inside the empty space of the table
624
- (e.target === unfocusRef ||
625
- ((e.target as HTMLElement | undefined)?.closest('.table-wrapper') == null &&
626
- mousedDownEmptySpace))
627
- ) {
628
- tableContext.unfocus();
629
- }
630
- mousedDownEmptySpace = false;
631
-
632
- if (tableContext.isMousingDown) {
633
- tableContext.isMousingDown = false;
634
- }
635
-
636
- removeOverrideResizeHandler();
637
- }}
638
- onclick={(e: MouseEvent) => {
639
- handleClickOutside(e);
640
- }}
641
- onmousedown={(e: MouseEvent) => {
642
- mousedDownEmptySpace =
643
- e.target === unfocusRef || (e.target as HTMLElement).closest('.table-wrapper') == null;
644
- }}
645
- />
646
-
647
- <div
648
- class="grid"
649
- class:nested
650
- style="--show-top-shadow: {hasScrolledPastHeader
651
- ? 1
652
- : 0}; --show-bottom-shadow: {hasContentBelowFooter ? 1 : 0};"
653
- >
654
- {#if tableContext.dataRepo.isLoading}
655
- <LoadingOverlay
656
- absolute
657
- borderRadius={cssLength('4px')}
658
- message={tableContext.dataRepo.loadingMessage}
659
- />
660
- {/if}
661
-
662
- <div
663
- class="horizontal-bar"
664
- class:border={tableContext.toolbarPosition === ToolbarPosition.Top}
665
- >
666
- {#if tableContext.toolbarPosition === ToolbarPosition.Top}
667
- <TableHorizontalBar />
668
- {/if}
669
- </div>
670
-
671
- <div
672
- class="sidebar-wrapper sidebar-wrapper-left"
673
- class:nested
674
- style="--nested-height: {nestedHeight}px;"
675
- >
676
- {#if tableContext.toolbarPosition === ToolbarPosition.Left}
677
- <div class="sidebar toolbar-left">
678
- <TableSidebar />
679
- </div>
680
- {/if}
681
- </div>
682
- <div
683
- class="sidebar-wrapper sidebar-wrapper-right"
684
- class:nested
685
- style="--nested-height: {nestedHeight}px;"
686
- >
687
- {#if tableContext.toolbarPosition === ToolbarPosition.Right}
688
- <div class="sidebar toolbar-right">
689
- <TableSidebar />
690
- </div>
691
- {/if}
692
- </div>
693
-
694
- <div class="footer">
695
- {#if tableContext.displayFooter}
696
- <TableFooter />
697
- {/if}
698
- </div>
699
-
700
- <!-- svelte-ignore a11y_no_static_element_interactions -->
701
- <div
702
- bind:this={tableRef}
703
- class="datatable"
704
- class:nested
705
- class:compact={tableContext.isCompact}
706
- class:quick-filtering={tableContext.showQuickFiltering}
707
- onkeydown={tableContext.handleTdKeydown}
708
- style="--pinned-left: {totalPinnedLeftWidth}px; --pinned-right: {totalPinnedRightWidth}px; --sticky-top-height: {stickyTopHeight}px;"
709
- >
710
- <div
711
- class="flex_table"
712
- bind:this={ref}
713
- class:nested
714
- >
715
- <div
716
- bind:this={unfocusRef}
717
- class="table-wrapper"
718
- oncontextmenu={openContextMenu}
719
- >
720
- <div
721
- role="table"
722
- class="datatable-grid"
723
- style="--cols: {colsTemplate};"
724
- >
725
- <TableHead />
726
- <TableBody />
727
- </div>
728
- </div>
729
- </div>
730
-
731
- {#if nested}
732
- <!-- svelte-ignore a11y_no_static_element_interactions -->
733
- <div
734
- class:isResizing
735
- class="resizer"
736
- onmousedown={(e) => {
737
- e.preventDefault();
738
- addOverrideResizeHandler();
739
- }}
740
- ></div>
741
- {/if}
742
- </div>
743
- </div>
744
-
1
+ <svelte:options runes />
2
+
3
+ <script
4
+ lang="ts"
5
+ module
6
+ >
7
+ import {
8
+ ContextMenuContext,
9
+ type IContextMenuOption,
10
+ } from '../../Contexts/ContextMenuContext.svelte';
11
+ import { cssLength, devCatch, refWrapper } from '../../Helpers/Helpers.svelte';
12
+ import { getContext, onMount, setContext, tick, untrack } from 'svelte';
13
+ import LoadingOverlay from '../LoadingOverlay.svelte';
14
+ import TableBody from './Body/TableBody.svelte';
15
+ import TableHead from './Headers/TableHead.svelte';
16
+ import TableFooter from './Misc/TableFooter.svelte';
17
+ import TableHorizontalBar from './Misc/TableHorizontalBar.svelte';
18
+ import TableSidebar from './Misc/TableSidebar.svelte';
19
+ import { TableContext } from './Types/Context/TableContext.svelte';
20
+ import type { Primitive } from './Types/Context/TableInitOptions.js';
21
+ import { PaginationType } from './Types/Pagination/PaginationType.js';
22
+ import { ToolbarPosition } from './Types/Toolbar/ToolbarPosition.js';
23
+
24
+ declare global {
25
+ interface HTMLElementEventMap {
26
+ edit: CustomEvent<{
27
+ selectAll: boolean;
28
+ }>;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Top-level orchestrator props. Accepts either:
34
+ *
35
+ * - `table`: the structured `Table` instance returned by `createTable(...)`.
36
+ * This is the consumer-facing form.
37
+ * - `tableContext`: the underlying `TableContext`. Used internally by
38
+ * `NestedTable` which constructs a context directly for each
39
+ * expansion. Not intended for consumer use.
40
+ *
41
+ * Exactly one of the two must be supplied; a runtime guard inside the
42
+ * component throws if both are missing. The reference passed in is
43
+ * captured at mount via `setContext` and should not be reassigned
44
+ * afterwards.
45
+ */
46
+ export interface TableProps<T extends object, IdType extends Primitive> {
47
+ readonly table?: { readonly _context: TableContext<T, IdType> };
48
+ readonly tableContext?: TableContext<T, IdType>;
49
+ nested?: boolean;
50
+ }
51
+
52
+ /**
53
+ * Symbol used to share the currently-open quick-filter select across
54
+ * every header in a single Table tree. Stored via `setContext` on
55
+ * the same component that hosts the Table tree so multiple
56
+ * independent tables on a page each own their own popup state.
57
+ */
58
+ export const quickFilterContextIdentifier: symbol = Symbol();
59
+
60
+ const openedQuickFilterSelect = refWrapper<symbol | null>(null);
61
+ </script>
62
+
63
+ <script
64
+ lang="ts"
65
+ generics="T extends object,IdType extends Primitive"
66
+ >
67
+ const { nested = false, table, tableContext: tableContextProp }: TableProps<T, IdType> = $props();
68
+
69
+ // Resolve the underlying TableContext from whichever prop was passed:
70
+ // consumers pass `table`; internal `NestedTable` passes `tableContext`
71
+ // directly. Exactly one must be present — throw cleanly here rather
72
+ // than letting it surface as a downstream null-deref.
73
+ const tableContext: TableContext<T, IdType> = (() => {
74
+ const ctx = table?._context ?? tableContextProp;
75
+ if (!ctx) {
76
+ throw new Error(
77
+ 'scoria: <Table /> requires either a `table` prop (the structured instance returned by createTable) or a `tableContext` prop (used internally by NestedTable).'
78
+ );
79
+ }
80
+ return ctx;
81
+ })();
82
+
83
+ // NOTE: known limitation. This is passed as a wrapper, and they should never require reassignment. It is deeply reactive
84
+ setContext(TableContext.identifier, tableContext);
85
+
86
+ const { openContextMenu: _openContextMenu } = getContext<ContextMenuContext>(
87
+ ContextMenuContext.identifier
88
+ );
89
+
90
+ let ref: HTMLDivElement | undefined = $state();
91
+ let unfocusRef: HTMLDivElement | undefined = $state();
92
+
93
+ setContext(quickFilterContextIdentifier, openedQuickFilterSelect);
94
+
95
+ // Focus-restore on focusStart change. The DOM work runs inside `untrack`
96
+ // so a future selector tweak that reads reactive state (e.g.
97
+ // `document.activeElement`) does not retrigger on every focus change.
98
+ $effect(() => {
99
+ void tableContext.focusStart;
100
+ void ref;
101
+ untrack(() => {
102
+ if (tableContext.focusStart && ref) {
103
+ const { row, column: index } = tableContext.focusStart;
104
+ // `columnDefs[index]` can be undefined when the focus
105
+ // coordinate references a slot that no longer exists
106
+ // (e.g. an inner expand table remounted with a different
107
+ // column count, or a preset hydration raced column init).
108
+ // Guard so out-of-range degrades to "no focus restored".
109
+ const def = tableContext.columnDefs[index];
110
+ if (!def) {
111
+ return;
112
+ }
113
+ let element = ref.querySelector(
114
+ `#${tableContext.tableName}-cell-${row}-${def.id}`
115
+ ) as HTMLElement | undefined;
116
+
117
+ if (element?.tabIndex == -1) {
118
+ const td = element;
119
+ element = td.querySelector('button') as HTMLElement | undefined;
120
+ if (!element) {
121
+ element = td.querySelector('input') as HTMLElement | undefined;
122
+ }
123
+ }
124
+
125
+ element?.focus({ preventScroll: true });
126
+ // Skip horizontal scroll-into-view for pinned cells (see the
127
+ // focusEnd effect for the rationale).
128
+ const startCell = element?.closest('[role="cell"]');
129
+ const isStartPinned =
130
+ startCell?.classList.contains('pinned-left') ||
131
+ startCell?.classList.contains('pinned-right');
132
+ element?.scrollIntoView({
133
+ behavior: 'instant',
134
+ block: 'nearest',
135
+ inline: isStartPinned ? undefined : 'nearest',
136
+ });
137
+ }
138
+ });
139
+ });
140
+
141
+ // Scroll-into-view on focusEnd change. DOM work wrapped in `untrack` so
142
+ // reading layout properties never re-establishes a reactive dependency.
143
+ $effect(() => {
144
+ void tableContext.focusEnd;
145
+ void ref;
146
+ untrack(() => {
147
+ if (tableContext.focusEnd && ref) {
148
+ const { row, column: index } = tableContext.focusEnd;
149
+ const def = tableContext.columnDefs[index];
150
+ if (!def) {
151
+ return;
152
+ }
153
+ const cell = ref.querySelector(
154
+ `#${tableContext.tableName}-cell-${row}-${def.id}`
155
+ ) as HTMLElement | undefined;
156
+
157
+ // Pinned cells are visually always on screen, but their true
158
+ // layout position is at their real column track — which for
159
+ // a right-pinned column in a wider-than-viewport table is
160
+ // offscreen. Calling `scrollIntoView({inline:'nearest'})`
161
+ // would then scroll the table sideways to surface the cell's
162
+ // underlying position, which the user sees as the table
163
+ // jumping when they click a pinned Actions button. The
164
+ // vertical (block) axis still scrolls normally because the
165
+ // row's vertical position is the user's real scroll target.
166
+ const isPinned =
167
+ cell?.classList.contains('pinned-left') ||
168
+ cell?.classList.contains('pinned-right');
169
+ cell?.scrollIntoView({
170
+ behavior: 'instant',
171
+ block: 'nearest',
172
+ inline: isPinned ? undefined : 'nearest',
173
+ });
174
+ }
175
+ });
176
+ });
177
+
178
+ /**
179
+ * Routes a right-click into either a "focus the cell that was
180
+ * clicked" path (if the click landed on a cell other than the
181
+ * current focus) or a "show copy options" context menu (when the
182
+ * Table is configured with `allowCopy` and the platform exposes the
183
+ * Clipboard API).
184
+ */
185
+ function openContextMenu(e: MouseEvent) {
186
+ e.stopPropagation();
187
+
188
+ if (
189
+ (e.target as HTMLElement).tagName === 'INPUT' ||
190
+ (e.target as HTMLElement).closest('.select-wrapper') != null
191
+ ) {
192
+ return;
193
+ }
194
+
195
+ const clickedElement = document.activeElement?.id.split('-cell-')[1]?.split('-');
196
+
197
+ if (clickedElement) {
198
+ const [rowString, columnId] = clickedElement;
199
+ const row = parseInt(rowString);
200
+
201
+ if (
202
+ !tableContext.focusedColumnIds.has(columnId) ||
203
+ !tableContext.focusedRowIndices.has(row)
204
+ ) {
205
+ const columnIndex = tableContext.columnDefs.find((it) => it.id === columnId)?.index;
206
+
207
+ // Use `!= null` rather than a truthy check so a column at
208
+ // memory index 0 (the leftmost column) is not silently
209
+ // rejected. The previous truthy check broke right-click
210
+ // copy on the first column.
211
+ // `!= null` (not truthy) so an index of 0 (leftmost column) is
212
+ // not silently rejected.
213
+ if (columnIndex != null) {
214
+ tableContext.moveFocus({
215
+ column: columnIndex,
216
+ row,
217
+ id: columnId,
218
+ });
219
+ }
220
+ }
221
+ }
222
+
223
+ // `window.Clipboard` may be absent in non-HTTPS contexts.
224
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
225
+ if (tableContext.allowCopy && window.Clipboard) {
226
+ e.preventDefault();
227
+
228
+ const options: Array<IContextMenuOption> = [
229
+ {
230
+ callback: () => {
231
+ tableContext.copyDataToClipboard().catch(devCatch);
232
+ },
233
+ text: 'Copy Selection',
234
+ },
235
+ ];
236
+
237
+ if (tableContext.hasHighlightedItems) {
238
+ options.push({
239
+ callback: () => {
240
+ tableContext.copyHighlightedRowsToClipboard().catch(devCatch);
241
+ },
242
+ text: 'Copy Highlighted Rows',
243
+ });
244
+ }
245
+
246
+ _openContextMenu(e, options).catch(devCatch);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Closes the quick-filter popup and the actions-cell popup whenever
252
+ * the user clicks somewhere that is not inside either floating UI.
253
+ */
254
+ function handleClickOutside(e: MouseEvent) {
255
+ const target = e.target as HTMLElement;
256
+ if (target.isConnected) {
257
+ // A click inside an open Svelecte dropdown (marked with
258
+ // `data-scoria-portal-dropdown` by `dropdownPortal.ts`) is a
259
+ // logical extension of the trigger inside the quick-filter
260
+ // popup, so it must not close either floating popover.
261
+ const insidePortalDropdown = target.closest('[data-scoria-portal-dropdown]') != null;
262
+ if (
263
+ !insidePortalDropdown &&
264
+ target.closest('div.floating') == null &&
265
+ target.closest('div.stick-right') == null
266
+ ) {
267
+ openedQuickFilterSelect.value = null;
268
+ }
269
+
270
+ if (!insidePortalDropdown && target.closest('div.slot-wrapper') == null) {
271
+ tableContext.shownActionsPopup = undefined;
272
+ }
273
+ }
274
+ }
275
+
276
+ // nested table resizing
277
+ let isResizing = $state(false);
278
+ let remPxRatio = $state(0);
279
+ const minNestedHeight = $derived(20 * remPxRatio);
280
+
281
+ let nestedHeight = $state(0);
282
+ $effect.pre(() => {
283
+ if (!isResizing) {
284
+ nestedHeight = minNestedHeight;
285
+ }
286
+ });
287
+
288
+ let tableRef: HTMLDivElement | undefined = $state();
289
+ let mousedDownEmptySpace = $state(false);
290
+
291
+ onMount(() => {
292
+ remPxRatio = parseFloat(getComputedStyle(document.documentElement).fontSize);
293
+ });
294
+
295
+ $effect(() => {
296
+ if (!isResizing) {
297
+ return;
298
+ }
299
+ const onResize = (e: MouseEvent) => {
300
+ const sliderHeight = tableRef ? e.clientY - tableRef.getBoundingClientRect().top : 0;
301
+ nestedHeight = Math.max(sliderHeight, minNestedHeight);
302
+ };
303
+ window.addEventListener('mousemove', onResize);
304
+ return () => {
305
+ window.removeEventListener('mousemove', onResize);
306
+ };
307
+ });
308
+
309
+ function addOverrideResizeHandler() {
310
+ isResizing = true;
311
+ }
312
+
313
+ function removeOverrideResizeHandler() {
314
+ isResizing = false;
315
+ }
316
+
317
+ const usesLoading = $derived(
318
+ tableContext.paginationRepo &&
319
+ tableContext.paginationRepo.paginationType === PaginationType.Remote
320
+ );
321
+
322
+ const shouldUnfocusRemote = $derived(
323
+ usesLoading &&
324
+ tableContext.paginationRepo?.isLoading === false &&
325
+ tableContext.paginationRepo.paginationState.pageIndex !==
326
+ tableContext.paginationRepo.previouslyViewedPage
327
+ );
328
+
329
+ const shouldUnfocusLocal = $derived(
330
+ !usesLoading && tableContext.paginationRepo?.paginationState.pageIndex
331
+ );
332
+
333
+ $effect(() => {
334
+ if ((shouldUnfocusRemote || shouldUnfocusLocal) && unfocusRef) {
335
+ const el = unfocusRef;
336
+ untrack(() => {
337
+ tableContext.unfocus();
338
+ el.scrollTop = 0;
339
+ });
340
+ } else if (usesLoading && tableContext.paginationRepo?.isLoading === false) {
341
+ // refocus same element after a reload
342
+ // this does not break mutifocus
343
+ untrack(() => {
344
+ const fs = tableContext.focusStart;
345
+ if (!fs) {
346
+ return;
347
+ }
348
+ const idToFocusTo = `#${tableContext.tableName}-cell-${fs.row}-${fs.id}`;
349
+
350
+ tick()
351
+ .then(() => {
352
+ (document.querySelector(idToFocusTo) as HTMLElement | undefined)?.focus();
353
+ })
354
+ .catch(devCatch);
355
+ });
356
+ }
357
+ });
358
+
359
+ const leftColumns = $derived(tableContext.table.getLeftVisibleLeafColumns());
360
+ const centreColumns = $derived(tableContext.table.getCenterVisibleLeafColumns());
361
+ const rightColumns = $derived(tableContext.table.getRightVisibleLeafColumns());
362
+
363
+ // CSS Grid template for the table.
364
+ //
365
+ // - First track is the 20px highlight column (always present).
366
+ // - For each other column, the track depends on its `userResized`
367
+ // flag and its declared width/maxWidth:
368
+ //
369
+ // userResized = true → `${width}px`
370
+ // userResized = false, maxWidth set → `minmax(width, maxWidth)`
371
+ // userResized = false, widthIsFixed → `${width}px`
372
+ // userResized = false, on per-kind default → `minmax(width, 1fr)`
373
+ //
374
+ // This produces "specify widths for the columns you care about; let
375
+ // the rest fill the gap", matching the legacy `<table>` behaviour.
376
+ // Pinned and centre columns use the same rule so pinning a column
377
+ // does not change its width. Sticky-cell offsets read from
378
+ // `_columnRenderedWidths` (populated by TableHead's ResizeObserver).
379
+ const colsTemplate = $derived.by(() => {
380
+ void tableContext.columnSizing;
381
+ const track = (col: {
382
+ columnDef: {
383
+ width: number;
384
+ maxWidth?: number;
385
+ userResized: boolean;
386
+ widthIsFixed: boolean;
387
+ };
388
+ }) => {
389
+ if (col.columnDef.userResized) {
390
+ return `${col.columnDef.width}px`;
391
+ }
392
+ if (col.columnDef.maxWidth != null) {
393
+ return `minmax(${col.columnDef.width}px, ${col.columnDef.maxWidth}px)`;
394
+ }
395
+ if (col.columnDef.widthIsFixed) {
396
+ return `${col.columnDef.width}px`;
397
+ }
398
+ return `minmax(${col.columnDef.width}px, 1fr)`;
399
+ };
400
+ const tracks: Array<string> = ['20px'];
401
+ for (const col of leftColumns) {
402
+ tracks.push(track(col));
403
+ }
404
+ for (const col of centreColumns) {
405
+ tracks.push(track(col));
406
+ }
407
+ for (const col of rightColumns) {
408
+ tracks.push(track(col));
409
+ }
410
+ return tracks.join(' ');
411
+ });
412
+
413
+ // Total rendered width of the pinned-left and pinned-right column
414
+ // stacks. Driven by `_columnRenderedWidths` so the values track actual
415
+ // layout rather than the declared `columnDef.width`. Exposed to CSS
416
+ // as `--pinned-left` / `--pinned-right`; the SCSS uses these in
417
+ // `scroll-padding-{left,right}` so `scrollIntoView` (called on keyboard
418
+ // nav and focus restore) keeps the focused cell out from behind the
419
+ // pinned columns. The 20px highlight column is always pinned-left.
420
+ const totalPinnedLeftWidth = $derived.by(() => {
421
+ void tableContext._columnRenderedWidths.size;
422
+ let total = 20;
423
+ for (const col of leftColumns) {
424
+ total += tableContext._columnRenderedWidths.get(col.id) ?? col.columnDef.width;
425
+ }
426
+ return total;
427
+ });
428
+ const totalPinnedRightWidth = $derived.by(() => {
429
+ void tableContext._columnRenderedWidths.size;
430
+ let total = 0;
431
+ for (const col of rightColumns) {
432
+ total += tableContext._columnRenderedWidths.get(col.id) ?? col.columnDef.width;
433
+ }
434
+ return total;
435
+ });
436
+
437
+ // Single scroll listener for the shared scroll container, which is
438
+ // also the host the row virtualiser registers against. The same
439
+ // handler updates `hasScrolledPastHeader` / `hasContentBelowFooter`,
440
+ // which drive the sticky-row drop shadows — shadows appear only when
441
+ // there is actually content scrolled under the sticky strip.
442
+ let hasScrolledPastHeader = $state(false);
443
+ let hasContentBelowFooter = $state(false);
444
+
445
+ // Measured rendered height of the sticky strip at the top of
446
+ // `.datatable` (header row + quick-filter row when active). Drives
447
+ // `scroll-padding-top` via `--sticky-top-height`, so `scrollIntoView`
448
+ // (keyboard nav, focus restore) parks the focused cell below the
449
+ // actual strip a static `calc(var(--height) + Npx)` left a 1-2px
450
+ // gap from sub-pixel rounding and borders, clipping the body row.
451
+ let stickyTopHeight: number = $state(0);
452
+
453
+ function recomputeScrollShadows(): void {
454
+ if (!tableRef) {
455
+ hasScrolledPastHeader = false;
456
+ hasContentBelowFooter = false;
457
+ return;
458
+ }
459
+ // `.datatable` has `overflow-x: auto`. Per the CSS spec, when one
460
+ // overflow axis is non-visible the other axis becomes auto too,
461
+ // so `.datatable` is itself the vertical scroll container when
462
+ // the table is constrained inside a fixed-height parent (the
463
+ // typical app shell). Detect internal scroll directly.
464
+ const internalScrolledPast = tableRef.scrollTop > 0;
465
+ const internalContentBelow =
466
+ tableRef.scrollTop + tableRef.clientHeight < tableRef.scrollHeight - 1;
467
+ // Fall back to window-relative checks when the table is not
468
+ // constrained and the page itself is the scroll container.
469
+ const rect = tableRef.getBoundingClientRect();
470
+ const windowScrolledPast = rect.top < 0;
471
+ const windowContentBelow = rect.bottom > window.innerHeight;
472
+ hasScrolledPastHeader = internalScrolledPast || windowScrolledPast;
473
+ hasContentBelowFooter = internalContentBelow || windowContentBelow;
474
+ }
475
+
476
+ $effect(() => {
477
+ void tableRef;
478
+ void unfocusRef;
479
+ const handler = (e: Event): void => {
480
+ const el = e.currentTarget as HTMLElement | null;
481
+ if (!el) {
482
+ return;
483
+ }
484
+ tableContext._virtualisation.registerScrollPosition(el.scrollTop, el.clientHeight);
485
+ // Refresh shadow flags on every internal scroll: when the table
486
+ // is the scroll container, body scroll never reaches the window
487
+ // listener and the shadow flags would otherwise never flip.
488
+ recomputeScrollShadows();
489
+ };
490
+ const elements: Array<HTMLElement> = [];
491
+ if (tableRef) {
492
+ elements.push(tableRef);
493
+ }
494
+ if (unfocusRef && unfocusRef !== tableRef) {
495
+ elements.push(unfocusRef);
496
+ }
497
+ for (const el of elements) {
498
+ el.addEventListener('scroll', handler, { passive: true });
499
+ }
500
+ const primary = tableRef ?? unfocusRef;
501
+ if (primary) {
502
+ tableContext._virtualisation.registerScrollPosition(primary.scrollTop, primary.clientHeight);
503
+ }
504
+
505
+ // Window scroll + resize cover the unconstrained case where the
506
+ // page itself scrolls; `recomputeScrollShadows` handles both.
507
+ const onWindow = (): void => {
508
+ recomputeScrollShadows();
509
+ };
510
+ window.addEventListener('scroll', onWindow, { passive: true });
511
+ window.addEventListener('resize', onWindow, { passive: true });
512
+
513
+ // Content-height changes (row add/remove, pagination, expand
514
+ // toggled) can flip the shadow flags without any user scroll, so
515
+ // re-evaluate on any intrinsic-height change of the table.
516
+ const ro =
517
+ typeof ResizeObserver === 'undefined'
518
+ ? null
519
+ : new ResizeObserver(() => {
520
+ recomputeScrollShadows();
521
+ });
522
+ if (ro && tableRef) {
523
+ ro.observe(tableRef);
524
+ }
525
+
526
+ // Measure the actual rendered height of the sticky strip (header +
527
+ // quick-filter row when active) so `scroll-padding-top` matches
528
+ // it to the pixel.
529
+ const measureStickyTop = (): void => {
530
+ if (!tableRef) {
531
+ return;
532
+ }
533
+ const headerRow = tableRef.querySelector('.datatable-row.header-row') as HTMLElement | null;
534
+ const filterRow = tableRef.querySelector(
535
+ '.datatable-row.quick-filter-row'
536
+ ) as HTMLElement | null;
537
+ stickyTopHeight = (headerRow?.offsetHeight ?? 0) + (filterRow?.offsetHeight ?? 0);
538
+ };
539
+ const stickyRo =
540
+ typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measureStickyTop);
541
+ if (stickyRo && tableRef) {
542
+ // Observe the scroll host so any change inside (mode
543
+ // toggle, rows added, etc.) re-measures on the next frame.
544
+ stickyRo.observe(tableRef);
545
+ }
546
+ // Initial measurement so the very first focus / arrow-key
547
+ // navigation lands below the strip without waiting for the
548
+ // observer's first frame.
549
+ measureStickyTop();
550
+
551
+ recomputeScrollShadows();
552
+
553
+ return () => {
554
+ for (const el of elements) {
555
+ el.removeEventListener('scroll', handler);
556
+ }
557
+ window.removeEventListener('scroll', onWindow);
558
+ window.removeEventListener('resize', onWindow);
559
+ ro?.disconnect();
560
+ stickyRo?.disconnect();
561
+ };
562
+ });
563
+ </script>
564
+
565
+ <svelte:window
566
+ onmouseup={(e: MouseEvent) => {
567
+ if (
568
+ // if it's not a click on an item in the context menu
569
+ !(e.target as HTMLElement | undefined)?.classList.contains('context-menu-item') &&
570
+ // if it's outside the table or inside the empty space of the table
571
+ (e.target === unfocusRef ||
572
+ ((e.target as HTMLElement | undefined)?.closest('.table-wrapper') == null &&
573
+ mousedDownEmptySpace))
574
+ ) {
575
+ tableContext.unfocus();
576
+ }
577
+ mousedDownEmptySpace = false;
578
+
579
+ if (tableContext.isMousingDown) {
580
+ tableContext.isMousingDown = false;
581
+ }
582
+
583
+ removeOverrideResizeHandler();
584
+ }}
585
+ onclick={(e: MouseEvent) => {
586
+ handleClickOutside(e);
587
+ }}
588
+ onmousedown={(e: MouseEvent) => {
589
+ mousedDownEmptySpace =
590
+ e.target === unfocusRef || (e.target as HTMLElement).closest('.table-wrapper') == null;
591
+ }}
592
+ />
593
+
594
+ <div
595
+ class="grid"
596
+ class:nested
597
+ style="--show-top-shadow: {hasScrolledPastHeader
598
+ ? 1
599
+ : 0}; --show-bottom-shadow: {hasContentBelowFooter ? 1 : 0};"
600
+ >
601
+ {#if tableContext.dataRepo.isLoading}
602
+ <LoadingOverlay
603
+ absolute
604
+ borderRadius={cssLength('4px')}
605
+ message={tableContext.dataRepo.loadingMessage}
606
+ />
607
+ {/if}
608
+
609
+ <div
610
+ class="horizontal-bar"
611
+ class:border={tableContext.toolbarPosition === ToolbarPosition.Top}
612
+ >
613
+ {#if tableContext.toolbarPosition === ToolbarPosition.Top}
614
+ <TableHorizontalBar />
615
+ {/if}
616
+ </div>
617
+
618
+ <div
619
+ class="sidebar-wrapper sidebar-wrapper-left"
620
+ class:nested
621
+ style="--nested-height: {nestedHeight}px;"
622
+ >
623
+ {#if tableContext.toolbarPosition === ToolbarPosition.Left}
624
+ <div class="sidebar toolbar-left">
625
+ <TableSidebar />
626
+ </div>
627
+ {/if}
628
+ </div>
629
+ <div
630
+ class="sidebar-wrapper sidebar-wrapper-right"
631
+ class:nested
632
+ style="--nested-height: {nestedHeight}px;"
633
+ >
634
+ {#if tableContext.toolbarPosition === ToolbarPosition.Right}
635
+ <div class="sidebar toolbar-right">
636
+ <TableSidebar />
637
+ </div>
638
+ {/if}
639
+ </div>
640
+
641
+ <div class="footer">
642
+ {#if tableContext.displayFooter}
643
+ <TableFooter />
644
+ {/if}
645
+ </div>
646
+
647
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
648
+ <div
649
+ bind:this={tableRef}
650
+ class="datatable"
651
+ class:nested
652
+ class:compact={tableContext.isCompact}
653
+ class:quick-filtering={tableContext.showQuickFiltering}
654
+ onkeydown={tableContext.handleTdKeydown}
655
+ style="--pinned-left: {totalPinnedLeftWidth}px; --pinned-right: {totalPinnedRightWidth}px; --sticky-top-height: {stickyTopHeight}px;"
656
+ >
657
+ <div
658
+ class="flex_table"
659
+ bind:this={ref}
660
+ class:nested
661
+ >
662
+ <div
663
+ bind:this={unfocusRef}
664
+ class="table-wrapper"
665
+ oncontextmenu={openContextMenu}
666
+ >
667
+ <div
668
+ role="table"
669
+ class="datatable-grid"
670
+ style="--cols: {colsTemplate};"
671
+ >
672
+ <TableHead />
673
+ <TableBody />
674
+ </div>
675
+ </div>
676
+ </div>
677
+
678
+ {#if nested}
679
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
680
+ <div
681
+ class:isResizing
682
+ class="resizer"
683
+ onmousedown={(e) => {
684
+ e.preventDefault();
685
+ addOverrideResizeHandler();
686
+ }}
687
+ ></div>
688
+ {/if}
689
+ </div>
690
+ </div>
691
+
745
692
  <style>div.grid {
746
693
  border-radius: 4px;
747
694
  display: grid;
@@ -893,4 +840,4 @@ div.sidebar-wrapper .sidebar.toolbar-right {
893
840
  text-align: left;
894
841
  min-width: max-content;
895
842
  isolation: isolate;
896
- }</style>
843
+ }</style>