@lavalogic/scoria 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +58 -58
  2. package/dist/Components/AccordionGroup.svelte +42 -42
  3. package/dist/Components/AccordionGroupButton.svelte +99 -99
  4. package/dist/Components/Action.svelte +138 -138
  5. package/dist/Components/AlertModal.svelte +125 -125
  6. package/dist/Components/Base/ContextWrapper.svelte +50 -50
  7. package/dist/Components/Base/Pagination.svelte +190 -190
  8. package/dist/Components/Base/Snippets.svelte +29 -29
  9. package/dist/Components/BigRadioSet.svelte +29 -29
  10. package/dist/Components/Bubble.svelte +77 -77
  11. package/dist/Components/Button.svelte +197 -197
  12. package/dist/Components/Checkbox.svelte +68 -68
  13. package/dist/Components/CollapsibleCard.svelte +70 -70
  14. package/dist/Components/Contexts/ContextMenu.svelte +157 -157
  15. package/dist/Components/Contexts/ContextMenuDivider.svelte +11 -11
  16. package/dist/Components/Contexts/ContextMenuOption.svelte +52 -52
  17. package/dist/Components/Contexts/Toast.svelte +61 -61
  18. package/dist/Components/Contexts/ToastContainer.svelte +46 -46
  19. package/dist/Components/Contexts/Tooltip.svelte +26 -26
  20. package/dist/Components/DataMatrixIcon.svelte +41 -41
  21. package/dist/Components/DateInput.svelte +109 -109
  22. package/dist/Components/DatePicker.svelte +312 -312
  23. package/dist/Components/DesktopModal.svelte +106 -106
  24. package/dist/Components/Dial.svelte +151 -151
  25. package/dist/Components/DimensionInput.svelte +184 -184
  26. package/dist/Components/DragMenu/DragMenuHolder.svelte +106 -106
  27. package/dist/Components/DragMenu/DraggableCard.svelte +41 -41
  28. package/dist/Components/FileCard.svelte +20 -20
  29. package/dist/Components/FileUpload.svelte +196 -196
  30. package/dist/Components/GridInput.svelte +190 -190
  31. package/dist/Components/GridInputSet.svelte +22 -22
  32. package/dist/Components/HorizontalTabGroup.svelte +78 -78
  33. package/dist/Components/HorizontalTabGroupButton.svelte +96 -96
  34. package/dist/Components/Icon.svelte +97 -97
  35. package/dist/Components/Icons.js +357 -357
  36. package/dist/Components/LoadingAnimation.svelte +32 -32
  37. package/dist/Components/LoadingModal.svelte +47 -47
  38. package/dist/Components/LoadingOverlay.svelte +27 -27
  39. package/dist/Components/Modal.svelte +251 -251
  40. package/dist/Components/MultiSelect.svelte +197 -197
  41. package/dist/Components/Nav/Nav.svelte +97 -97
  42. package/dist/Components/Nav/NavServiceMenuList.svelte +34 -34
  43. package/dist/Components/Nav/NavTab.svelte +91 -91
  44. package/dist/Components/Nav/ServiceMenuItem.svelte +91 -91
  45. package/dist/Components/Nav/ServicesNav.svelte +53 -53
  46. package/dist/Components/NumberInput.svelte +144 -144
  47. package/dist/Components/PageHeading.svelte +36 -36
  48. package/dist/Components/PasswordInput.svelte +73 -73
  49. package/dist/Components/Portal.svelte +25 -25
  50. package/dist/Components/ProgressBubble.svelte +80 -80
  51. package/dist/Components/QuerySelect.svelte +411 -411
  52. package/dist/Components/SingleSelect.svelte +261 -261
  53. package/dist/Components/StepButton.svelte +120 -120
  54. package/dist/Components/StepForm.svelte +108 -108
  55. package/dist/Components/Switch.svelte +36 -36
  56. package/dist/Components/Table/Body/QuickSearchCell.svelte +632 -632
  57. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +30 -30
  58. package/dist/Components/Table/Body/Rows/ColumnList.svelte +56 -56
  59. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +179 -179
  60. package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +49 -49
  61. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +180 -180
  62. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +102 -102
  63. package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +119 -119
  64. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +114 -114
  65. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +96 -96
  66. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +72 -72
  67. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +131 -131
  68. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +144 -144
  69. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +238 -238
  70. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +358 -358
  71. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +338 -338
  72. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +183 -183
  73. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +309 -309
  74. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +369 -369
  75. package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +170 -170
  76. package/dist/Components/Table/Body/Rows/TableRow.svelte +259 -259
  77. package/dist/Components/Table/Body/TableBody.svelte +67 -67
  78. package/dist/Components/Table/Headers/HighlightAllHeader.svelte +39 -39
  79. package/dist/Components/Table/Headers/SelectAllHeader.svelte +28 -28
  80. package/dist/Components/Table/Headers/TableHead.svelte +260 -260
  81. package/dist/Components/Table/Misc/ColumnPanel.svelte +78 -78
  82. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +99 -99
  83. package/dist/Components/Table/Misc/FilterInput.svelte +556 -556
  84. package/dist/Components/Table/Misc/FilterPanel.svelte +46 -46
  85. package/dist/Components/Table/Misc/FilterPanelModal.svelte +67 -67
  86. package/dist/Components/Table/Misc/SidePanel.svelte +32 -32
  87. package/dist/Components/Table/Misc/TableFooter.svelte +304 -304
  88. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +156 -156
  89. package/dist/Components/Table/Misc/TableSidebar.svelte +176 -176
  90. package/dist/Components/Table/Misc/ToolboxPanel.svelte +77 -77
  91. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +91 -91
  92. package/dist/Components/Table/NestedTable.svelte +84 -84
  93. package/dist/Components/Table/Table.svelte +492 -492
  94. package/dist/Components/Test/TestContextConsumer.svelte +13 -13
  95. package/dist/Components/TextArea.svelte +108 -108
  96. package/dist/Components/TextInput.svelte +147 -147
  97. package/dist/Components/ThreeStateRadio.svelte +124 -124
  98. package/dist/Components/Touch/BoolCard.svelte +52 -52
  99. package/dist/Components/Touch/DateModal.svelte +217 -217
  100. package/dist/Components/Touch/EditCard.svelte +52 -52
  101. package/dist/Components/Touch/InfoCard.svelte +24 -24
  102. package/dist/Components/Touch/InfoTextCard.svelte +21 -21
  103. package/dist/Components/Touch/ModalExplanation.svelte +20 -20
  104. package/dist/Components/Touch/NumberKeyboard.svelte +268 -268
  105. package/dist/Components/Touch/NumberModal.svelte +69 -69
  106. package/dist/Components/Touch/PlusMinusCard.svelte +50 -50
  107. package/dist/Components/Touch/SelectModal.svelte +105 -105
  108. package/dist/Components/Touch/SummaryCard.svelte +65 -65
  109. package/dist/Components/Touch/TextAreaModal.svelte +86 -86
  110. package/dist/Components/Touch/TextModal.svelte +123 -123
  111. package/dist/Components/Touch/TouchCard.svelte +28 -28
  112. package/dist/Components/Touch/TouchModal.svelte +259 -259
  113. package/dist/Components/Touch/UtilityButton.svelte +91 -91
  114. package/dist/Components/VerticalTabGroup.svelte +66 -66
  115. package/dist/Components/VerticalTabGroupButton.svelte +97 -97
  116. package/dist/Helpers/Datamatrix.d.ts +10 -10
  117. package/dist/Helpers/Datamatrix.js +1 -1
  118. package/dist/Helpers/Helpers.svelte.js +6 -4
  119. package/dist/Types/Examples/ExampleModal.svelte +34 -34
  120. package/dist/scss/_basics.scss +12 -12
  121. package/dist/scss/_colours.scss +134 -134
  122. package/dist/scss/_mixins.scss +3016 -3016
  123. package/dist/scss/_sizing.scss +69 -69
  124. package/dist/scss/_transitions.scss +8 -8
  125. package/package.json +2 -2
@@ -1,494 +1,494 @@
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 { devCatch, refWrapper } from '../../Helpers/Helpers.svelte';
12
- import type { RefWrapper } from '../../Types/Internal/RefWrapper.js';
13
- import { getContext, onMount, setContext, tick, untrack } from 'svelte';
14
- import LoadingOverlay from '../LoadingOverlay.svelte';
15
- import type { HeightData } from './Body/HeightData.js';
16
- import { shownPopup } from './Body/Rows/Columns/ActionsCell.svelte';
17
- import TableBody from './Body/TableBody.svelte';
18
- import TableHead from './Headers/TableHead.svelte';
19
- import TableFooter from './Misc/TableFooter.svelte';
20
- import TableHorizontalBar from './Misc/TableHorizontalBar.svelte';
21
- import TableSidebar from './Misc/TableSidebar.svelte';
22
- import { TableContext } from './Types/Context/TableContext.svelte';
23
- import type { Primitive } from './Types/Context/TableInitOptions.js';
24
- import { PaginationType } from './Types/Pagination/PaginationType.js';
25
- import { ToolbarPosition } from './Types/Toolbar/ToolbarPosition.js';
26
-
27
- declare global {
28
- interface HTMLElementEventMap {
29
- edit: CustomEvent<{
30
- selectAll: boolean;
31
- }>;
32
- }
33
- }
34
-
35
- export interface TableProps<T extends object, IdType extends Primitive> {
36
- readonly tableContext: TableContext<T, IdType>;
37
- nested?: boolean;
38
- }
39
-
40
- export const quickFilterContextIdentifier: symbol = Symbol();
41
-
42
- const openedQuickFilterSelect = refWrapper(null);
43
- </script>
44
-
45
- <script
46
- lang="ts"
47
- generics="T extends object,IdType extends Primitive"
48
- >
49
- const { nested = false, tableContext }: TableProps<T, IdType> = $props();
50
-
51
- // NOTE: known limitation. This is passed as a wrapper, and they should never require reassignment. It is deeply reactive
52
- // svelte-ignore state_referenced_locally
53
- setContext(TableContext.identifier, tableContext);
54
-
55
- const { openContextMenu: _openContextMenu } = getContext<ContextMenuContext>(
56
- ContextMenuContext.identifier
57
- );
58
-
59
- let ref: HTMLDivElement | undefined = $state();
60
- let unfocusRef: HTMLDivElement | undefined = $state();
61
-
62
- setContext(quickFilterContextIdentifier, openedQuickFilterSelect);
63
-
64
- $effect(() => {
65
- void tableContext.focusStart;
66
- void ref;
67
- untrack(() => {
68
- if (tableContext.focusStart && ref) {
69
- const { row, column: index } = tableContext.focusStart;
70
- let element = ref.querySelector(
71
- `#${tableContext.tableName}-cell-${row}-${tableContext.columnDefs[index].id}`
72
- ) as HTMLElement | undefined;
73
-
74
- if (element?.tabIndex == -1) {
75
- const td = element;
76
- element = td.querySelector('button') as HTMLElement | undefined;
77
- if (!element) {
78
- element = td.querySelector('input') as HTMLElement | undefined;
79
- }
80
- }
81
-
82
- element?.focus({ preventScroll: true });
83
- element?.scrollIntoView({
84
- behavior: 'instant',
85
- block: 'nearest',
86
- inline: 'nearest',
87
- });
88
- }
89
- });
90
- });
91
-
92
- $effect(() => {
93
- if (tableContext.focusEnd && ref) {
94
- const { row, column: index } = tableContext.focusEnd;
95
- const cell = ref.querySelector(
96
- `#${tableContext.tableName}-cell-${row}-${tableContext.columnDefs[index].id}`
97
- ) as HTMLElement | undefined;
98
-
99
- cell?.scrollIntoView({
100
- behavior: 'instant',
101
- block: 'nearest',
102
- inline: 'nearest',
103
- });
104
- }
105
- });
106
-
107
- function openContextMenu(e: MouseEvent) {
108
- e.stopPropagation();
109
-
110
- if (
111
- (e.target as HTMLElement).tagName === 'INPUT' ||
112
- (e.target as HTMLElement).closest('.select-wrapper') != null
113
- ) {
114
- return;
115
- }
116
-
117
- const clickedElement = document.activeElement?.id.split('-cell-')[1]?.split('-');
118
-
119
- if (clickedElement) {
120
- const [rowString, columnId] = clickedElement;
121
- const row = parseInt(rowString);
122
-
123
- if (
124
- !tableContext.focusedColumnIds.has(columnId) ||
125
- !tableContext.focusedRowIndices.has(row)
126
- ) {
127
- const columnIndex = tableContext.columnDefs.find((it) => it.id === columnId)?.index;
128
-
129
- if (columnIndex) {
130
- tableContext.moveFocus({
131
- column: columnIndex,
132
- row,
133
- id: columnId,
134
- });
135
- }
136
- }
137
- }
138
-
139
- // this may not exist in http contexts
140
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
141
- if (tableContext.allowCopy && window.Clipboard) {
142
- e.preventDefault();
143
-
144
- const options: Array<IContextMenuOption> = [
145
- {
146
- callback: () => {
147
- tableContext.copyDataToClipboard().catch(devCatch);
148
- },
149
- text: 'Copy Selection',
150
- },
151
- ];
152
-
153
- if (tableContext.hasHighlightedItems) {
154
- options.push({
155
- callback: () => {
156
- tableContext.copyHighlightedRowsToClipboard().catch(devCatch);
157
- },
158
- text: 'Copy Highlighted Rows',
159
- });
160
- }
161
-
162
- _openContextMenu(e, options).catch(devCatch);
163
- }
164
- }
165
-
166
- function handleClickOutside(e: MouseEvent) {
167
- const target = e.target as HTMLElement;
168
- if (target.isConnected) {
169
- if (target.closest('div.floating') == null && target.closest('div.stick-right') == null) {
170
- openedQuickFilterSelect.value = null;
171
- }
172
-
173
- if (target.closest('div.slot-wrapper') == null) {
174
- // svelte type gen/eslint miscommunication
175
- (shownPopup as RefWrapper<symbol | undefined>).value = undefined;
176
- }
177
- }
178
- }
179
-
180
- // nested table resizing
181
- let isResizing = $state(false);
182
- let remPxRatio = $state(0);
183
- const minNestedHeight = $derived(20 * remPxRatio);
184
-
185
- // HACK please figure this one out
186
- let nestedHeight = $derived(minNestedHeight);
187
-
188
- let tableRef: HTMLDivElement | undefined = undefined;
189
- let mousedDownEmptySpace = $state(false);
190
-
191
- onMount(() => {
192
- remPxRatio = parseFloat(getComputedStyle(document.documentElement).fontSize);
193
- });
194
-
195
- function addOverrideResizeHandler() {
196
- isResizing = true;
197
- window.addEventListener('mousemove', onResize);
198
- }
199
-
200
- function onResize(e: MouseEvent) {
201
- const sliderHeight = tableRef ? e.clientY - tableRef.getBoundingClientRect().top : 0;
202
- nestedHeight = Math.max(sliderHeight, minNestedHeight);
203
- }
204
-
205
- function removeOverrideResizeHandler() {
206
- isResizing = false;
207
- window.removeEventListener('mousemove', onResize);
208
- }
209
-
210
- const usesLoading = $derived(
211
- tableContext.paginationRepo &&
212
- tableContext.paginationRepo.paginationType === PaginationType.Remote
213
- );
214
-
215
- const shouldUnfocusRemote = $derived(
216
- usesLoading &&
217
- tableContext.paginationRepo?.isLoading === false &&
218
- tableContext.paginationRepo.paginationState.pageIndex !==
219
- tableContext.paginationRepo.previouslyViewedPage
220
- );
221
-
222
- const shouldUnfocusLocal = $derived(
223
- !usesLoading && tableContext.paginationRepo?.paginationState.pageIndex
224
- );
225
-
226
- $effect(() => {
227
- if ((shouldUnfocusRemote || shouldUnfocusLocal) && unfocusRef) {
228
- untrack(() => {
229
- tableContext.unfocus();
230
- });
231
- unfocusRef.scrollTop = 0;
232
- } else if (usesLoading && tableContext.paginationRepo?.isLoading === false) {
233
- // refocus same element after a reload
234
- // this does not break mutifocus
235
- untrack(() => {
236
- const fs = tableContext.focusStart;
237
- if (!fs) {
238
- return;
239
- }
240
- const idToFocusTo = `#${tableContext.tableName}-cell-${fs.row}-${fs.id}`;
241
-
242
- tick()
243
- .then(() => {
244
- if (idToFocusTo) {
245
- (document.querySelector(idToFocusTo) as HTMLTableCellElement | undefined)?.focus();
246
- }
247
- })
248
- .catch(devCatch);
249
- });
250
- }
251
- });
252
-
253
- const leftTotalSize = $derived(tableContext.table.getLeftTotalSize());
254
- const centreTotalSize = $derived(tableContext.table.getCenterTotalSize());
255
- const rightTotalSize = $derived(tableContext.table.getRightTotalSize());
256
-
257
- const leftColumns = $derived(tableContext.table.getLeftVisibleLeafColumns());
258
- const centreColumns = $derived(tableContext.table.getCenterVisibleLeafColumns());
259
- const rightColumns = $derived(tableContext.table.getRightVisibleLeafColumns());
260
-
261
- const appropriateSide = $derived.by(() => {
262
- if (leftColumns.length > 0) {
263
- return 'left';
264
- } else if (rightColumns.length != tableContext.columnDefs.length) {
265
- return 'centre';
266
- } else {
267
- return 'right';
268
- }
269
- });
270
-
271
- type SvelteTableElement = {
272
- rowHeights: RefWrapper<Array<number>>;
273
- };
274
-
275
- let leftTable: SvelteTableElement | undefined = $state();
276
- let centreTable: SvelteTableElement | undefined = $state();
277
- let rightTable: SvelteTableElement | undefined = $state();
278
-
279
- const totalRowSizes: Array<HeightData<T>> = $derived.by(() => {
280
- if (!leftTable || !centreTable || !rightTable) {
281
- return [];
282
- }
283
-
284
- // console.debug(`got refs for all tables`);
285
- const centre = centreTable.rowHeights.value;
286
- const right = rightTable.rowHeights.value;
287
-
288
- return leftTable.rowHeights.value.map((_leftHeight, index) => {
289
- const leftHeight = _leftHeight || 0;
290
- const centreHeight = centre[index] || 0;
291
- const rightHeight = right[index] || 0;
292
-
293
- const maxHeight = Math.max(leftHeight, centreHeight, rightHeight);
294
- const applyTo: HeightData<T>['applyTo'] = [];
295
-
296
- if (leftHeight < maxHeight) {
297
- // console.debug(`left (${leftHeight}) was lower than max (${maxHeight})`);
298
- applyTo.push('left');
299
- }
300
- if (centreHeight < maxHeight) {
301
- // console.debug(`centre (${centreHeight}) was lower than max (${maxHeight})`);
302
- applyTo.push('centre');
303
- }
304
- if (rightHeight < maxHeight) {
305
- // console.debug(`right (${rightHeight}) was lower than max (${maxHeight})`);
306
- applyTo.push('right');
307
- }
308
-
309
- return {
310
- index,
311
- applyTo,
312
- maxHeight,
313
- };
314
- });
315
- });
316
- </script>
317
-
318
- <svelte:window
319
- onmouseup={(e: MouseEvent) => {
320
- if (
321
- // if it's not a click on an item in the context menu
322
- !(e.target as HTMLElement | undefined)?.classList.contains('context-menu-item') &&
323
- // if it's outside the table or inside the empty space of the table
324
- (e.target === unfocusRef ||
325
- ((e.target as HTMLElement | undefined)?.closest('.table-wrapper') == null &&
326
- mousedDownEmptySpace))
327
- ) {
328
- tableContext.unfocus();
329
- }
330
- mousedDownEmptySpace = false;
331
-
332
- if (tableContext.isMousingDown) {
333
- tableContext.isMousingDown = false;
334
- }
335
-
336
- removeOverrideResizeHandler();
337
- }}
338
- onclick={(e: MouseEvent) => {
339
- handleClickOutside(e);
340
- }}
341
- onmousedown={(e: MouseEvent) => {
342
- mousedDownEmptySpace =
343
- e.target === unfocusRef || (e.target as HTMLElement).closest('.table-wrapper') == null;
344
- }}
345
- />
346
-
347
- <div
348
- class="grid"
349
- class:nested
350
- >
351
- {#if tableContext.dataRepo.isLoading}
352
- <LoadingOverlay
353
- absolute
354
- borderRadius="4px"
355
- message={tableContext.dataRepo.loadingMessage}
356
- />
357
- {/if}
358
-
359
- <div
360
- class="horizontal-bar"
361
- class:border={tableContext.toolbarPosition === ToolbarPosition.Top}
362
- >
363
- {#if tableContext.toolbarPosition === ToolbarPosition.Top}
364
- <TableHorizontalBar />
365
- {/if}
366
- </div>
367
-
368
- <div
369
- class="sidebar-wrapper sidebar-wrapper-left"
370
- class:nested
371
- style="--nested-height: {nestedHeight}px;"
372
- >
373
- {#if tableContext.toolbarPosition === ToolbarPosition.Left}
374
- <div class="sidebar toolbar-left">
375
- <TableSidebar />
376
- </div>
377
- {/if}
378
- </div>
379
- <div
380
- class="sidebar-wrapper sidebar-wrapper-right"
381
- class:nested
382
- style="--nested-height: {nestedHeight}px;"
383
- >
384
- {#if tableContext.toolbarPosition === ToolbarPosition.Right}
385
- <div class="sidebar toolbar-right">
386
- <TableSidebar />
387
- </div>
388
- {/if}
389
- </div>
390
-
391
- <div class="footer">
392
- {#if tableContext.displayFooter}
393
- <TableFooter />
394
- {/if}
395
- </div>
396
-
397
- <!-- svelte-ignore a11y_no_static_element_interactions -->
398
- <div
399
- bind:this={tableRef}
400
- class="datatable"
401
- class:nested
402
- class:compact={tableContext.isCompact}
403
- class:quick-filtering={tableContext.showQuickFiltering}
404
- onkeydown={tableContext.handleTdKeydown}
405
- >
406
- <div
407
- class="flex_table"
408
- bind:this={ref}
409
- class:nested
410
- >
411
- <div
412
- bind:this={unfocusRef}
413
- class="table-wrapper"
414
- oncontextmenu={openContextMenu}
415
- >
416
- <!-- {#if leftTotalSize > 0} -->
417
- <table
418
- class="datatable__table left"
419
- style="min-width: {leftTotalSize}px"
420
- >
421
- <TableHead
422
- showHighlight={leftTotalSize > 0}
423
- side="left"
424
- />
425
- <TableBody
426
- includeHighlight={appropriateSide == 'left'}
427
- columns={leftColumns}
428
- side="left"
429
- heights={totalRowSizes}
430
- bind:this={leftTable}
431
- />
432
- </table>
433
- <!-- {/if} -->
434
-
435
- <!-- {#if centreTotalSize > 0} -->
436
-
437
- <table
438
- class="datatable__table centre"
439
- style="min-width: {centreTotalSize}px"
440
- >
441
- <TableHead
442
- showHighlight={leftTotalSize == 0}
443
- side="centre"
444
- />
445
- <TableBody
446
- includeHighlight={appropriateSide == 'centre'}
447
- columns={centreColumns}
448
- side="centre"
449
- heights={totalRowSizes}
450
- bind:this={centreTable}
451
- />
452
- </table>
453
- <!-- {/if} -->
454
-
455
- <!-- {#if rightTotalSize > 0} -->
456
- <table
457
- class="datatable__table right"
458
- style="min-width: {rightTotalSize}px"
459
- >
460
- <TableHead
461
- showHighlight={false}
462
- side="right"
463
- />
464
- <TableBody
465
- includeHighlight={appropriateSide == 'right'}
466
- columns={rightColumns}
467
- side="right"
468
- heights={totalRowSizes}
469
- bind:this={rightTable}
470
- />
471
- </table>
472
- <!-- {/if} -->
473
- </div>
474
-
475
- <!-- TODO add filter callbacks -->
476
- </div>
477
-
478
- {#if nested}
479
- <!-- svelte-ignore a11y_no_static_element_interactions -->
480
- <div
481
- class:isResizing
482
- class="resizer"
483
- onmousedown={(e) => {
484
- e.preventDefault();
485
- addOverrideResizeHandler();
486
- }}
487
- ></div>
488
- {/if}
489
- </div>
490
- </div>
491
-
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 { devCatch, refWrapper } from '../../Helpers/Helpers.svelte';
12
+ import type { RefWrapper } from '../../Types/Internal/RefWrapper.js';
13
+ import { getContext, onMount, setContext, tick, untrack } from 'svelte';
14
+ import LoadingOverlay from '../LoadingOverlay.svelte';
15
+ import type { HeightData } from './Body/HeightData.js';
16
+ import { shownPopup } from './Body/Rows/Columns/ActionsCell.svelte';
17
+ import TableBody from './Body/TableBody.svelte';
18
+ import TableHead from './Headers/TableHead.svelte';
19
+ import TableFooter from './Misc/TableFooter.svelte';
20
+ import TableHorizontalBar from './Misc/TableHorizontalBar.svelte';
21
+ import TableSidebar from './Misc/TableSidebar.svelte';
22
+ import { TableContext } from './Types/Context/TableContext.svelte';
23
+ import type { Primitive } from './Types/Context/TableInitOptions.js';
24
+ import { PaginationType } from './Types/Pagination/PaginationType.js';
25
+ import { ToolbarPosition } from './Types/Toolbar/ToolbarPosition.js';
26
+
27
+ declare global {
28
+ interface HTMLElementEventMap {
29
+ edit: CustomEvent<{
30
+ selectAll: boolean;
31
+ }>;
32
+ }
33
+ }
34
+
35
+ export interface TableProps<T extends object, IdType extends Primitive> {
36
+ readonly tableContext: TableContext<T, IdType>;
37
+ nested?: boolean;
38
+ }
39
+
40
+ export const quickFilterContextIdentifier: symbol = Symbol();
41
+
42
+ const openedQuickFilterSelect = refWrapper(null);
43
+ </script>
44
+
45
+ <script
46
+ lang="ts"
47
+ generics="T extends object,IdType extends Primitive"
48
+ >
49
+ const { nested = false, tableContext }: TableProps<T, IdType> = $props();
50
+
51
+ // NOTE: known limitation. This is passed as a wrapper, and they should never require reassignment. It is deeply reactive
52
+ // svelte-ignore state_referenced_locally
53
+ setContext(TableContext.identifier, tableContext);
54
+
55
+ const { openContextMenu: _openContextMenu } = getContext<ContextMenuContext>(
56
+ ContextMenuContext.identifier
57
+ );
58
+
59
+ let ref: HTMLDivElement | undefined = $state();
60
+ let unfocusRef: HTMLDivElement | undefined = $state();
61
+
62
+ setContext(quickFilterContextIdentifier, openedQuickFilterSelect);
63
+
64
+ $effect(() => {
65
+ void tableContext.focusStart;
66
+ void ref;
67
+ untrack(() => {
68
+ if (tableContext.focusStart && ref) {
69
+ const { row, column: index } = tableContext.focusStart;
70
+ let element = ref.querySelector(
71
+ `#${tableContext.tableName}-cell-${row}-${tableContext.columnDefs[index].id}`
72
+ ) as HTMLElement | undefined;
73
+
74
+ if (element?.tabIndex == -1) {
75
+ const td = element;
76
+ element = td.querySelector('button') as HTMLElement | undefined;
77
+ if (!element) {
78
+ element = td.querySelector('input') as HTMLElement | undefined;
79
+ }
80
+ }
81
+
82
+ element?.focus({ preventScroll: true });
83
+ element?.scrollIntoView({
84
+ behavior: 'instant',
85
+ block: 'nearest',
86
+ inline: 'nearest',
87
+ });
88
+ }
89
+ });
90
+ });
91
+
92
+ $effect(() => {
93
+ if (tableContext.focusEnd && ref) {
94
+ const { row, column: index } = tableContext.focusEnd;
95
+ const cell = ref.querySelector(
96
+ `#${tableContext.tableName}-cell-${row}-${tableContext.columnDefs[index].id}`
97
+ ) as HTMLElement | undefined;
98
+
99
+ cell?.scrollIntoView({
100
+ behavior: 'instant',
101
+ block: 'nearest',
102
+ inline: 'nearest',
103
+ });
104
+ }
105
+ });
106
+
107
+ function openContextMenu(e: MouseEvent) {
108
+ e.stopPropagation();
109
+
110
+ if (
111
+ (e.target as HTMLElement).tagName === 'INPUT' ||
112
+ (e.target as HTMLElement).closest('.select-wrapper') != null
113
+ ) {
114
+ return;
115
+ }
116
+
117
+ const clickedElement = document.activeElement?.id.split('-cell-')[1]?.split('-');
118
+
119
+ if (clickedElement) {
120
+ const [rowString, columnId] = clickedElement;
121
+ const row = parseInt(rowString);
122
+
123
+ if (
124
+ !tableContext.focusedColumnIds.has(columnId) ||
125
+ !tableContext.focusedRowIndices.has(row)
126
+ ) {
127
+ const columnIndex = tableContext.columnDefs.find((it) => it.id === columnId)?.index;
128
+
129
+ if (columnIndex) {
130
+ tableContext.moveFocus({
131
+ column: columnIndex,
132
+ row,
133
+ id: columnId,
134
+ });
135
+ }
136
+ }
137
+ }
138
+
139
+ // this may not exist in http contexts
140
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
141
+ if (tableContext.allowCopy && window.Clipboard) {
142
+ e.preventDefault();
143
+
144
+ const options: Array<IContextMenuOption> = [
145
+ {
146
+ callback: () => {
147
+ tableContext.copyDataToClipboard().catch(devCatch);
148
+ },
149
+ text: 'Copy Selection',
150
+ },
151
+ ];
152
+
153
+ if (tableContext.hasHighlightedItems) {
154
+ options.push({
155
+ callback: () => {
156
+ tableContext.copyHighlightedRowsToClipboard().catch(devCatch);
157
+ },
158
+ text: 'Copy Highlighted Rows',
159
+ });
160
+ }
161
+
162
+ _openContextMenu(e, options).catch(devCatch);
163
+ }
164
+ }
165
+
166
+ function handleClickOutside(e: MouseEvent) {
167
+ const target = e.target as HTMLElement;
168
+ if (target.isConnected) {
169
+ if (target.closest('div.floating') == null && target.closest('div.stick-right') == null) {
170
+ openedQuickFilterSelect.value = null;
171
+ }
172
+
173
+ if (target.closest('div.slot-wrapper') == null) {
174
+ // svelte type gen/eslint miscommunication
175
+ (shownPopup as RefWrapper<symbol | undefined>).value = undefined;
176
+ }
177
+ }
178
+ }
179
+
180
+ // nested table resizing
181
+ let isResizing = $state(false);
182
+ let remPxRatio = $state(0);
183
+ const minNestedHeight = $derived(20 * remPxRatio);
184
+
185
+ // HACK please figure this one out
186
+ let nestedHeight = $derived(minNestedHeight);
187
+
188
+ let tableRef: HTMLDivElement | undefined = undefined;
189
+ let mousedDownEmptySpace = $state(false);
190
+
191
+ onMount(() => {
192
+ remPxRatio = parseFloat(getComputedStyle(document.documentElement).fontSize);
193
+ });
194
+
195
+ function addOverrideResizeHandler() {
196
+ isResizing = true;
197
+ window.addEventListener('mousemove', onResize);
198
+ }
199
+
200
+ function onResize(e: MouseEvent) {
201
+ const sliderHeight = tableRef ? e.clientY - tableRef.getBoundingClientRect().top : 0;
202
+ nestedHeight = Math.max(sliderHeight, minNestedHeight);
203
+ }
204
+
205
+ function removeOverrideResizeHandler() {
206
+ isResizing = false;
207
+ window.removeEventListener('mousemove', onResize);
208
+ }
209
+
210
+ const usesLoading = $derived(
211
+ tableContext.paginationRepo &&
212
+ tableContext.paginationRepo.paginationType === PaginationType.Remote
213
+ );
214
+
215
+ const shouldUnfocusRemote = $derived(
216
+ usesLoading &&
217
+ tableContext.paginationRepo?.isLoading === false &&
218
+ tableContext.paginationRepo.paginationState.pageIndex !==
219
+ tableContext.paginationRepo.previouslyViewedPage
220
+ );
221
+
222
+ const shouldUnfocusLocal = $derived(
223
+ !usesLoading && tableContext.paginationRepo?.paginationState.pageIndex
224
+ );
225
+
226
+ $effect(() => {
227
+ if ((shouldUnfocusRemote || shouldUnfocusLocal) && unfocusRef) {
228
+ untrack(() => {
229
+ tableContext.unfocus();
230
+ });
231
+ unfocusRef.scrollTop = 0;
232
+ } else if (usesLoading && tableContext.paginationRepo?.isLoading === false) {
233
+ // refocus same element after a reload
234
+ // this does not break mutifocus
235
+ untrack(() => {
236
+ const fs = tableContext.focusStart;
237
+ if (!fs) {
238
+ return;
239
+ }
240
+ const idToFocusTo = `#${tableContext.tableName}-cell-${fs.row}-${fs.id}`;
241
+
242
+ tick()
243
+ .then(() => {
244
+ if (idToFocusTo) {
245
+ (document.querySelector(idToFocusTo) as HTMLTableCellElement | undefined)?.focus();
246
+ }
247
+ })
248
+ .catch(devCatch);
249
+ });
250
+ }
251
+ });
252
+
253
+ const leftTotalSize = $derived(tableContext.table.getLeftTotalSize());
254
+ const centreTotalSize = $derived(tableContext.table.getCenterTotalSize());
255
+ const rightTotalSize = $derived(tableContext.table.getRightTotalSize());
256
+
257
+ const leftColumns = $derived(tableContext.table.getLeftVisibleLeafColumns());
258
+ const centreColumns = $derived(tableContext.table.getCenterVisibleLeafColumns());
259
+ const rightColumns = $derived(tableContext.table.getRightVisibleLeafColumns());
260
+
261
+ const appropriateSide = $derived.by(() => {
262
+ if (leftColumns.length > 0) {
263
+ return 'left';
264
+ } else if (rightColumns.length != tableContext.columnDefs.length) {
265
+ return 'centre';
266
+ } else {
267
+ return 'right';
268
+ }
269
+ });
270
+
271
+ type SvelteTableElement = {
272
+ rowHeights: RefWrapper<Array<number>>;
273
+ };
274
+
275
+ let leftTable: SvelteTableElement | undefined = $state();
276
+ let centreTable: SvelteTableElement | undefined = $state();
277
+ let rightTable: SvelteTableElement | undefined = $state();
278
+
279
+ const totalRowSizes: Array<HeightData<T>> = $derived.by(() => {
280
+ if (!leftTable || !centreTable || !rightTable) {
281
+ return [];
282
+ }
283
+
284
+ // console.debug(`got refs for all tables`);
285
+ const centre = centreTable.rowHeights.value;
286
+ const right = rightTable.rowHeights.value;
287
+
288
+ return leftTable.rowHeights.value.map((_leftHeight, index) => {
289
+ const leftHeight = _leftHeight || 0;
290
+ const centreHeight = centre[index] || 0;
291
+ const rightHeight = right[index] || 0;
292
+
293
+ const maxHeight = Math.max(leftHeight, centreHeight, rightHeight);
294
+ const applyTo: HeightData<T>['applyTo'] = [];
295
+
296
+ if (leftHeight < maxHeight) {
297
+ // console.debug(`left (${leftHeight}) was lower than max (${maxHeight})`);
298
+ applyTo.push('left');
299
+ }
300
+ if (centreHeight < maxHeight) {
301
+ // console.debug(`centre (${centreHeight}) was lower than max (${maxHeight})`);
302
+ applyTo.push('centre');
303
+ }
304
+ if (rightHeight < maxHeight) {
305
+ // console.debug(`right (${rightHeight}) was lower than max (${maxHeight})`);
306
+ applyTo.push('right');
307
+ }
308
+
309
+ return {
310
+ index,
311
+ applyTo,
312
+ maxHeight,
313
+ };
314
+ });
315
+ });
316
+ </script>
317
+
318
+ <svelte:window
319
+ onmouseup={(e: MouseEvent) => {
320
+ if (
321
+ // if it's not a click on an item in the context menu
322
+ !(e.target as HTMLElement | undefined)?.classList.contains('context-menu-item') &&
323
+ // if it's outside the table or inside the empty space of the table
324
+ (e.target === unfocusRef ||
325
+ ((e.target as HTMLElement | undefined)?.closest('.table-wrapper') == null &&
326
+ mousedDownEmptySpace))
327
+ ) {
328
+ tableContext.unfocus();
329
+ }
330
+ mousedDownEmptySpace = false;
331
+
332
+ if (tableContext.isMousingDown) {
333
+ tableContext.isMousingDown = false;
334
+ }
335
+
336
+ removeOverrideResizeHandler();
337
+ }}
338
+ onclick={(e: MouseEvent) => {
339
+ handleClickOutside(e);
340
+ }}
341
+ onmousedown={(e: MouseEvent) => {
342
+ mousedDownEmptySpace =
343
+ e.target === unfocusRef || (e.target as HTMLElement).closest('.table-wrapper') == null;
344
+ }}
345
+ />
346
+
347
+ <div
348
+ class="grid"
349
+ class:nested
350
+ >
351
+ {#if tableContext.dataRepo.isLoading}
352
+ <LoadingOverlay
353
+ absolute
354
+ borderRadius="4px"
355
+ message={tableContext.dataRepo.loadingMessage}
356
+ />
357
+ {/if}
358
+
359
+ <div
360
+ class="horizontal-bar"
361
+ class:border={tableContext.toolbarPosition === ToolbarPosition.Top}
362
+ >
363
+ {#if tableContext.toolbarPosition === ToolbarPosition.Top}
364
+ <TableHorizontalBar />
365
+ {/if}
366
+ </div>
367
+
368
+ <div
369
+ class="sidebar-wrapper sidebar-wrapper-left"
370
+ class:nested
371
+ style="--nested-height: {nestedHeight}px;"
372
+ >
373
+ {#if tableContext.toolbarPosition === ToolbarPosition.Left}
374
+ <div class="sidebar toolbar-left">
375
+ <TableSidebar />
376
+ </div>
377
+ {/if}
378
+ </div>
379
+ <div
380
+ class="sidebar-wrapper sidebar-wrapper-right"
381
+ class:nested
382
+ style="--nested-height: {nestedHeight}px;"
383
+ >
384
+ {#if tableContext.toolbarPosition === ToolbarPosition.Right}
385
+ <div class="sidebar toolbar-right">
386
+ <TableSidebar />
387
+ </div>
388
+ {/if}
389
+ </div>
390
+
391
+ <div class="footer">
392
+ {#if tableContext.displayFooter}
393
+ <TableFooter />
394
+ {/if}
395
+ </div>
396
+
397
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
398
+ <div
399
+ bind:this={tableRef}
400
+ class="datatable"
401
+ class:nested
402
+ class:compact={tableContext.isCompact}
403
+ class:quick-filtering={tableContext.showQuickFiltering}
404
+ onkeydown={tableContext.handleTdKeydown}
405
+ >
406
+ <div
407
+ class="flex_table"
408
+ bind:this={ref}
409
+ class:nested
410
+ >
411
+ <div
412
+ bind:this={unfocusRef}
413
+ class="table-wrapper"
414
+ oncontextmenu={openContextMenu}
415
+ >
416
+ <!-- {#if leftTotalSize > 0} -->
417
+ <table
418
+ class="datatable__table left"
419
+ style="min-width: {leftTotalSize}px"
420
+ >
421
+ <TableHead
422
+ showHighlight={leftTotalSize > 0}
423
+ side="left"
424
+ />
425
+ <TableBody
426
+ includeHighlight={appropriateSide == 'left'}
427
+ columns={leftColumns}
428
+ side="left"
429
+ heights={totalRowSizes}
430
+ bind:this={leftTable}
431
+ />
432
+ </table>
433
+ <!-- {/if} -->
434
+
435
+ <!-- {#if centreTotalSize > 0} -->
436
+
437
+ <table
438
+ class="datatable__table centre"
439
+ style="min-width: {centreTotalSize}px"
440
+ >
441
+ <TableHead
442
+ showHighlight={leftTotalSize == 0}
443
+ side="centre"
444
+ />
445
+ <TableBody
446
+ includeHighlight={appropriateSide == 'centre'}
447
+ columns={centreColumns}
448
+ side="centre"
449
+ heights={totalRowSizes}
450
+ bind:this={centreTable}
451
+ />
452
+ </table>
453
+ <!-- {/if} -->
454
+
455
+ <!-- {#if rightTotalSize > 0} -->
456
+ <table
457
+ class="datatable__table right"
458
+ style="min-width: {rightTotalSize}px"
459
+ >
460
+ <TableHead
461
+ showHighlight={false}
462
+ side="right"
463
+ />
464
+ <TableBody
465
+ includeHighlight={appropriateSide == 'right'}
466
+ columns={rightColumns}
467
+ side="right"
468
+ heights={totalRowSizes}
469
+ bind:this={rightTable}
470
+ />
471
+ </table>
472
+ <!-- {/if} -->
473
+ </div>
474
+
475
+ <!-- TODO add filter callbacks -->
476
+ </div>
477
+
478
+ {#if nested}
479
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
480
+ <div
481
+ class:isResizing
482
+ class="resizer"
483
+ onmousedown={(e) => {
484
+ e.preventDefault();
485
+ addOverrideResizeHandler();
486
+ }}
487
+ ></div>
488
+ {/if}
489
+ </div>
490
+ </div>
491
+
492
492
  <style>div.grid {
493
493
  border-radius: 4px;
494
494
  display: grid;
@@ -657,4 +657,4 @@ div.sidebar-wrapper .sidebar.toolbar-right {
657
657
  position: sticky;
658
658
  z-index: 2;
659
659
  right: 0;
660
- }</style>
660
+ }</style>