@lavalogic/scoria 0.40.1 → 0.40.2
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.
- package/README.md +58 -58
- package/dist/Components/AccordionGroup.svelte +46 -46
- package/dist/Components/AccordionGroupButton.svelte +76 -76
- package/dist/Components/Action.svelte +136 -136
- package/dist/Components/AlertModal.svelte +195 -195
- package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte +729 -729
- package/dist/Components/Base/BaseModal.svelte +125 -125
- package/dist/Components/Base/ContextWrapper.svelte +72 -72
- package/dist/Components/Base/Pagination.svelte +198 -198
- package/dist/Components/Base/Snippets.svelte +36 -36
- package/dist/Components/BigRadioSet.svelte +38 -38
- package/dist/Components/Bubble.svelte +101 -101
- package/dist/Components/Button.svelte +209 -209
- package/dist/Components/Checkbox.svelte +86 -86
- package/dist/Components/CollapsibleCard.svelte +78 -78
- package/dist/Components/Contexts/ContextMenu.svelte +164 -164
- package/dist/Components/Contexts/ContextMenuDivider.svelte +15 -15
- package/dist/Components/Contexts/ContextMenuOption.svelte +60 -60
- package/dist/Components/Contexts/Toast.svelte +86 -86
- package/dist/Components/Contexts/ToastContainer.svelte +65 -65
- package/dist/Components/Contexts/Tooltip.svelte +37 -37
- package/dist/Components/DataMatrixIcon.svelte +112 -112
- package/dist/Components/DateInput.svelte +148 -148
- package/dist/Components/DatePicker.svelte +300 -300
- package/dist/Components/DesktopModal.svelte +90 -90
- package/dist/Components/Dial.svelte +204 -204
- package/dist/Components/DimensionInput.svelte +170 -170
- package/dist/Components/DragMenu/DragMenuHolder.svelte +119 -119
- package/dist/Components/DragMenu/DraggableCard.svelte +77 -77
- package/dist/Components/FileCard.svelte +24 -24
- package/dist/Components/FileUpload.svelte +236 -236
- package/dist/Components/GridInput.svelte +196 -196
- package/dist/Components/GridInputSet.svelte +29 -29
- package/dist/Components/HorizontalTabGroup.svelte +82 -82
- package/dist/Components/HorizontalTabGroupButton.svelte +44 -44
- package/dist/Components/Icon.svelte +128 -128
- package/dist/Components/Icons.js +357 -357
- package/dist/Components/InfoAlert.svelte +35 -35
- package/dist/Components/LoadingAnimation.svelte +29 -29
- package/dist/Components/LoadingModal.svelte +45 -45
- package/dist/Components/LoadingOverlay.svelte +25 -25
- package/dist/Components/Modal.svelte +258 -258
- package/dist/Components/MultiSelect.svelte +220 -220
- package/dist/Components/Nav/Nav.svelte +110 -110
- package/dist/Components/Nav/NavServiceMenuList.svelte +43 -43
- package/dist/Components/Nav/NavTab.svelte +62 -62
- package/dist/Components/Nav/ServiceMenuItem.svelte +112 -112
- package/dist/Components/Nav/ServicesNav.svelte +80 -80
- package/dist/Components/NumberInput.svelte +157 -157
- package/dist/Components/PageHeading.svelte +25 -25
- package/dist/Components/PasswordInput.svelte +88 -88
- package/dist/Components/Portal.svelte +36 -36
- package/dist/Components/ProgressBubble.svelte +107 -107
- package/dist/Components/QuerySelect.svelte +366 -366
- package/dist/Components/SingleSelect.svelte +250 -250
- package/dist/Components/StepButton.svelte +70 -70
- package/dist/Components/StepForm.svelte +140 -140
- package/dist/Components/Switch.svelte +69 -69
- package/dist/Components/Table/ARCHITECTURE.md +298 -298
- package/dist/Components/Table/Body/QuickSearchCell.svelte +679 -679
- package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +51 -51
- package/dist/Components/Table/Body/Rows/ColumnList.svelte +59 -59
- package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +454 -454
- package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +79 -79
- package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +185 -185
- package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +124 -124
- package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +131 -120
- package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +234 -234
- package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +105 -105
- package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +87 -87
- package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +126 -126
- package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +159 -159
- package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +246 -246
- package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +216 -216
- package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +252 -252
- package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +179 -179
- package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +221 -221
- package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +197 -197
- package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +190 -190
- package/dist/Components/Table/Body/Rows/TableRow.svelte +290 -290
- package/dist/Components/Table/Body/TableBody.svelte +155 -155
- package/dist/Components/Table/ColumnFactory.svelte.js +1 -0
- package/dist/Components/Table/Headers/HighlightAllHeader.svelte +41 -41
- package/dist/Components/Table/Headers/SelectAllHeader.svelte +30 -30
- package/dist/Components/Table/Headers/TableHead.svelte +415 -415
- package/dist/Components/Table/Misc/ColumnPanel.svelte +39 -39
- package/dist/Components/Table/Misc/ColumnPanelModal.svelte +39 -39
- package/dist/Components/Table/Misc/FilterInput.svelte +641 -641
- package/dist/Components/Table/Misc/FilterPanel.svelte +190 -190
- package/dist/Components/Table/Misc/FilterPanelModal.svelte +31 -31
- package/dist/Components/Table/Misc/ShareViewModal.svelte +291 -291
- package/dist/Components/Table/Misc/SidePanel.svelte +35 -35
- package/dist/Components/Table/Misc/TableConfigurationModal.svelte +644 -644
- package/dist/Components/Table/Misc/TableFooter.svelte +340 -340
- package/dist/Components/Table/Misc/TableHorizontalBar.svelte +140 -140
- package/dist/Components/Table/Misc/TableSidebar.svelte +200 -200
- package/dist/Components/Table/Misc/TableViewDropdown.svelte +105 -105
- package/dist/Components/Table/Misc/ToolboxPanel.svelte +100 -100
- package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +107 -107
- package/dist/Components/Table/Misc/UpdateViewOptionsModal.svelte +192 -192
- package/dist/Components/Table/NestedTable.svelte +130 -130
- package/dist/Components/Table/Table.svelte +692 -692
- package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.d.ts +5 -0
- package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.js +3 -0
- package/dist/Components/Table/Types/Public/ColumnOptions.d.ts +10 -0
- package/dist/Components/Test/TestContextConsumer.svelte +13 -13
- package/dist/Components/TextArea.svelte +130 -130
- package/dist/Components/TextInput.svelte +176 -176
- package/dist/Components/ThreeStateRadio.svelte +131 -131
- package/dist/Components/Touch/BoolCard.svelte +45 -45
- package/dist/Components/Touch/DateModal.svelte +209 -209
- package/dist/Components/Touch/EditCard.svelte +59 -59
- package/dist/Components/Touch/InfoCard.svelte +31 -31
- package/dist/Components/Touch/InfoTextCard.svelte +28 -28
- package/dist/Components/Touch/ModalExplanation.svelte +17 -17
- package/dist/Components/Touch/NumberKeyboard.svelte +225 -225
- package/dist/Components/Touch/NumberModal.svelte +79 -79
- package/dist/Components/Touch/PlusMinusCard.svelte +46 -46
- package/dist/Components/Touch/SelectModal.svelte +127 -127
- package/dist/Components/Touch/SummaryCard.svelte +93 -93
- package/dist/Components/Touch/TextAreaModal.svelte +95 -95
- package/dist/Components/Touch/TextModal.svelte +106 -106
- package/dist/Components/Touch/TouchCard.svelte +33 -33
- package/dist/Components/Touch/TouchModal.svelte +274 -274
- package/dist/Components/Touch/UtilityButton.svelte +109 -109
- package/dist/Components/VerticalTabGroup.svelte +79 -79
- package/dist/Components/VerticalTabGroupButton.svelte +58 -58
- package/dist/Helpers/Datamatrix.d.ts +19 -19
- package/dist/Helpers/Datamatrix.js +291 -291
- package/dist/Types/Examples/ExampleModal.svelte +35 -35
- package/dist/scss/_basics.scss +12 -12
- package/dist/scss/_colours.scss +134 -134
- package/dist/scss/_mixins.scss +3392 -3392
- package/dist/scss/_motion.scss +57 -57
- package/dist/scss/_sizing.scss +85 -85
- package/dist/scss/_transitions.scss +8 -8
- package/package.json +1 -1
|
@@ -1,236 +1,236 @@
|
|
|
1
|
-
<svelte:options runes />
|
|
2
|
-
|
|
3
|
-
<script
|
|
4
|
-
lang="ts"
|
|
5
|
-
generics="T extends object, FilterValueType, RowIdType extends Primitive"
|
|
6
|
-
>
|
|
7
|
-
import type { AbstractDisplayDef } from '../../../Types/Columns/Definitions/Display/AbstractDisplayDef.svelte.js';
|
|
8
|
-
import { TableContext } from '../../../Types/Context/TableContext.svelte.js';
|
|
9
|
-
import { type Primitive } from '../../../Types/Context/TableInitOptions.js';
|
|
10
|
-
import type { TableFocusDetails } from '../../../Types/Focus/TableFocusDetails.svelte.js';
|
|
11
|
-
import {
|
|
12
|
-
devCatch,
|
|
13
|
-
formatDateTime,
|
|
14
|
-
isValidityTuple,
|
|
15
|
-
loadingText,
|
|
16
|
-
} from '../../../../../Helpers/Helpers.svelte.js';
|
|
17
|
-
import type { SelectOption } from '../../../../../Types/Internal/SelectOption.js';
|
|
18
|
-
import { Validity } from '../../../../../Types/Internal/Validity.js';
|
|
19
|
-
import { getContext, tick } from 'svelte';
|
|
20
|
-
import type { FocusableImmutableCellProps } from './FocusableImmutableCellProps.js';
|
|
21
|
-
|
|
22
|
-
const { cell, children }: FocusableImmutableCellProps<T, FilterValueType> = $props();
|
|
23
|
-
|
|
24
|
-
const value = $derived(cell.getValue());
|
|
25
|
-
|
|
26
|
-
/** Optional override-display value from the column def; falls back to the raw `value`. */
|
|
27
|
-
const visibleValue = $derived(
|
|
28
|
-
cell.column.columnDef.getDisplayValue?.(cell.row.original, cell.row.originalIndex) ?? value
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
/** For Date-typed display columns, expose both the local-time and UTC
|
|
32
|
-
* representations of the raw accessor value as a multi-line tooltip.
|
|
33
|
-
* The cell's body shows the locale-formatted value (via
|
|
34
|
-
* `getDisplayValue`); the title attribute below lets users hover to
|
|
35
|
-
* see Local + UTC side-by-side without expanding the column. */
|
|
36
|
-
const dateTooltip: string | undefined = $derived.by(() => {
|
|
37
|
-
if (cell.column.columnDef.filterValueType !== 'Date') {
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
if (value == null || value === '' || value instanceof Promise) {
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
let d: Date | undefined;
|
|
44
|
-
if (value instanceof Date) {
|
|
45
|
-
d = value;
|
|
46
|
-
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
47
|
-
const parsed = new Date(value);
|
|
48
|
-
if (!Number.isNaN(parsed.getTime())) {
|
|
49
|
-
d = parsed;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (d == null) {
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
// Both lines go through `formatDateTime` so the tooltip matches
|
|
56
|
-
// every other date surface in the app; only the timezone differs —
|
|
57
|
-
// the second call overrides it to UTC.
|
|
58
|
-
return `Local: ${formatDateTime(d)}\nUTC: ${formatDateTime(d, undefined, 'UTC')}`;
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const tableContext = getContext<TableContext<T, RowIdType>>(TableContext.identifier);
|
|
62
|
-
|
|
63
|
-
const options: Array<SelectOption<unknown>> = $derived(
|
|
64
|
-
('options' in cell.column.columnDef ? cell.column.columnDef.options : []) as Array<
|
|
65
|
-
SelectOption<unknown>
|
|
66
|
-
>
|
|
67
|
-
);
|
|
68
|
-
const hasValue: boolean = $derived(value !== undefined);
|
|
69
|
-
|
|
70
|
-
const focusDetails: TableFocusDetails<T, RowIdType> = $derived(
|
|
71
|
-
tableContext._focus.getFocusDetailsFor(cell)
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
// `modal` lives on every `AbstractDisplayDef` subclass (Display,
|
|
75
|
-
// Bubble, Progress, Validity), so a duck-typed read against the base
|
|
76
|
-
// `ColumnDef` is sufficient and avoids importing the entire
|
|
77
|
-
// AbstractDisplayDef class chain just to satisfy the type checker.
|
|
78
|
-
const modalComponent = $derived(
|
|
79
|
-
(cell.column.columnDef as AbstractDisplayDef<T, FilterValueType>).modal
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
let previousFocusElement: HTMLElement | undefined = $state();
|
|
83
|
-
let showModal = $state(false);
|
|
84
|
-
|
|
85
|
-
const thisId = $derived(
|
|
86
|
-
`${tableContext.tableName}-cell-${cell.row.visibleIndex}-${cell.column.id}`
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
// Match the open-modal state by row-object identity and column id so
|
|
90
|
-
// pagination / sort / filter does not silently re-target the modal at
|
|
91
|
-
// a different row that happens to occupy the same visible index.
|
|
92
|
-
$effect(() => {
|
|
93
|
-
const target = tableContext.openModalCell;
|
|
94
|
-
if (target !== null && target.row === cell.row.original && target.columnId === cell.column.id) {
|
|
95
|
-
previousFocusElement = document.activeElement as HTMLElement | undefined;
|
|
96
|
-
showModal = true;
|
|
97
|
-
} else {
|
|
98
|
-
showModal = false;
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
function onclose() {
|
|
103
|
-
tableContext.setOpenModalCell(null);
|
|
104
|
-
|
|
105
|
-
if (previousFocusElement?.id != thisId) {
|
|
106
|
-
tick()
|
|
107
|
-
.then(() => {
|
|
108
|
-
previousFocusElement?.focus();
|
|
109
|
-
})
|
|
110
|
-
.catch(devCatch);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let validity: Validity = $state(Validity.Valid);
|
|
115
|
-
$effect(() => {
|
|
116
|
-
const validityPromise = focusDetails.validity;
|
|
117
|
-
|
|
118
|
-
validityPromise
|
|
119
|
-
.then((v) => {
|
|
120
|
-
if (!v) {
|
|
121
|
-
validity = Validity.Neutral;
|
|
122
|
-
} else if (isValidityTuple(v)) {
|
|
123
|
-
validity = v.value;
|
|
124
|
-
} else {
|
|
125
|
-
validity = v;
|
|
126
|
-
}
|
|
127
|
-
})
|
|
128
|
-
.catch(devCatch);
|
|
129
|
-
});
|
|
130
|
-
</script>
|
|
131
|
-
|
|
132
|
-
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
|
|
133
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
134
|
-
<div
|
|
135
|
-
id={thisId}
|
|
136
|
-
class:info={validity == Validity.Info}
|
|
137
|
-
class:success={validity == Validity.Valid}
|
|
138
|
-
class:warning={validity == Validity.Warning}
|
|
139
|
-
class:error={validity == Validity.Invalid}
|
|
140
|
-
class:has-modal={!!modalComponent}
|
|
141
|
-
tabIndex={hasValue ? 0 : undefined}
|
|
142
|
-
class="focusable"
|
|
143
|
-
title={dateTooltip}
|
|
144
|
-
style={focusDetails.outline}
|
|
145
|
-
class:multifocus={hasValue && focusDetails.isMultiFocused}
|
|
146
|
-
class:multifocus-start={hasValue && focusDetails.isMultiFocusStart}
|
|
147
|
-
onmousedown={(e) => {
|
|
148
|
-
// start focus only on left click
|
|
149
|
-
if (e.button === 0) {
|
|
150
|
-
if (e.shiftKey) {
|
|
151
|
-
e.preventDefault();
|
|
152
|
-
tableContext.setFocusEnd(focusDetails.coordinates);
|
|
153
|
-
} else {
|
|
154
|
-
tableContext.startFocus(focusDetails.coordinates);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}}
|
|
158
|
-
onmouseover={() => {
|
|
159
|
-
if (tableContext.isMousingDown) {
|
|
160
|
-
tableContext.setFocusEnd(focusDetails.coordinates);
|
|
161
|
-
}
|
|
162
|
-
}}
|
|
163
|
-
onmouseup={() => {
|
|
164
|
-
tableContext.endFocus(focusDetails.coordinates);
|
|
165
|
-
}}
|
|
166
|
-
onclick={() => {
|
|
167
|
-
// Open the per-cell modal if the column declares one. Click is
|
|
168
|
-
// the right trigger here (focus already started on mousedown);
|
|
169
|
-
// modal-less display cells fall through to the default focus
|
|
170
|
-
// behaviour with no extra handler.
|
|
171
|
-
if (modalComponent) {
|
|
172
|
-
tableContext.setOpenModalCell({
|
|
173
|
-
row: cell.row.original,
|
|
174
|
-
columnId: cell.column.id,
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}}
|
|
178
|
-
onkeydown={(e) => {
|
|
179
|
-
// Keyboard parity with `onclick` above: when the cell has DOM
|
|
180
|
-
// focus (via `tabIndex`), Enter / Space opens the modal. Only
|
|
181
|
-
// fires for columns that declare a `modal`; non-modal display
|
|
182
|
-
// cells defer to the table's global keyboard navigation.
|
|
183
|
-
if (modalComponent && (e.key === 'Enter' || e.key === ' ')) {
|
|
184
|
-
e.preventDefault();
|
|
185
|
-
tableContext.setOpenModalCell({
|
|
186
|
-
row: cell.row.original,
|
|
187
|
-
columnId: cell.column.id,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}}
|
|
191
|
-
>
|
|
192
|
-
{#if hasValue}
|
|
193
|
-
{#await visibleValue}
|
|
194
|
-
{loadingText}
|
|
195
|
-
{:then v}
|
|
196
|
-
{(v instanceof Date
|
|
197
|
-
? v.toLocaleDateString()
|
|
198
|
-
: options.length
|
|
199
|
-
? options.find((it) => it.value === v)?.label
|
|
200
|
-
: v) ??
|
|
201
|
-
v ??
|
|
202
|
-
''}
|
|
203
|
-
{/await}
|
|
204
|
-
{:else if children}
|
|
205
|
-
<div
|
|
206
|
-
class="slot-wrapper"
|
|
207
|
-
class:multifocus={focusDetails.isMultiFocused}
|
|
208
|
-
class:multifocus-start={focusDetails.isMultiFocusStart}
|
|
209
|
-
>
|
|
210
|
-
{@render children()}
|
|
211
|
-
</div>
|
|
212
|
-
{/if}
|
|
213
|
-
</div>
|
|
214
|
-
{#if showModal && modalComponent}
|
|
215
|
-
<!--
|
|
216
|
-
The component receives the public `DisplayModalProps<T>` shape
|
|
217
|
-
`{ row, close }`. The dialog is NOT wrapped in
|
|
218
|
-
`<Portal target={document.body}>` any more. `<dialog>` opened
|
|
219
|
-
via `showModal()` enters the browser's top layer, so it already
|
|
220
|
-
escapes overflow:hidden / sticky / transform ancestors without
|
|
221
|
-
needing to be re-parented. Moving the `<div>` ancestor mid-mount
|
|
222
|
-
(Portal's $effect runs during the same microtask round as
|
|
223
|
-
BaseModal's `tick().then` `dialog.showModal()`) was popping the
|
|
224
|
-
dialog out of the top layer and leaving it to render inline at
|
|
225
|
-
the bottom of the page.
|
|
226
|
-
-->
|
|
227
|
-
{@const ModalComponent = modalComponent}
|
|
228
|
-
<ModalComponent
|
|
229
|
-
row={cell.row.original}
|
|
230
|
-
close={onclose}
|
|
231
|
-
/>
|
|
232
|
-
{/if}
|
|
233
|
-
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script
|
|
4
|
+
lang="ts"
|
|
5
|
+
generics="T extends object, FilterValueType, RowIdType extends Primitive"
|
|
6
|
+
>
|
|
7
|
+
import type { AbstractDisplayDef } from '../../../Types/Columns/Definitions/Display/AbstractDisplayDef.svelte.js';
|
|
8
|
+
import { TableContext } from '../../../Types/Context/TableContext.svelte.js';
|
|
9
|
+
import { type Primitive } from '../../../Types/Context/TableInitOptions.js';
|
|
10
|
+
import type { TableFocusDetails } from '../../../Types/Focus/TableFocusDetails.svelte.js';
|
|
11
|
+
import {
|
|
12
|
+
devCatch,
|
|
13
|
+
formatDateTime,
|
|
14
|
+
isValidityTuple,
|
|
15
|
+
loadingText,
|
|
16
|
+
} from '../../../../../Helpers/Helpers.svelte.js';
|
|
17
|
+
import type { SelectOption } from '../../../../../Types/Internal/SelectOption.js';
|
|
18
|
+
import { Validity } from '../../../../../Types/Internal/Validity.js';
|
|
19
|
+
import { getContext, tick } from 'svelte';
|
|
20
|
+
import type { FocusableImmutableCellProps } from './FocusableImmutableCellProps.js';
|
|
21
|
+
|
|
22
|
+
const { cell, children }: FocusableImmutableCellProps<T, FilterValueType> = $props();
|
|
23
|
+
|
|
24
|
+
const value = $derived(cell.getValue());
|
|
25
|
+
|
|
26
|
+
/** Optional override-display value from the column def; falls back to the raw `value`. */
|
|
27
|
+
const visibleValue = $derived(
|
|
28
|
+
cell.column.columnDef.getDisplayValue?.(cell.row.original, cell.row.originalIndex) ?? value
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
/** For Date-typed display columns, expose both the local-time and UTC
|
|
32
|
+
* representations of the raw accessor value as a multi-line tooltip.
|
|
33
|
+
* The cell's body shows the locale-formatted value (via
|
|
34
|
+
* `getDisplayValue`); the title attribute below lets users hover to
|
|
35
|
+
* see Local + UTC side-by-side without expanding the column. */
|
|
36
|
+
const dateTooltip: string | undefined = $derived.by(() => {
|
|
37
|
+
if (cell.column.columnDef.filterValueType !== 'Date') {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
if (value == null || value === '' || value instanceof Promise) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
let d: Date | undefined;
|
|
44
|
+
if (value instanceof Date) {
|
|
45
|
+
d = value;
|
|
46
|
+
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
47
|
+
const parsed = new Date(value);
|
|
48
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
49
|
+
d = parsed;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (d == null) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
// Both lines go through `formatDateTime` so the tooltip matches
|
|
56
|
+
// every other date surface in the app; only the timezone differs —
|
|
57
|
+
// the second call overrides it to UTC.
|
|
58
|
+
return `Local: ${formatDateTime(d)}\nUTC: ${formatDateTime(d, undefined, 'UTC')}`;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const tableContext = getContext<TableContext<T, RowIdType>>(TableContext.identifier);
|
|
62
|
+
|
|
63
|
+
const options: Array<SelectOption<unknown>> = $derived(
|
|
64
|
+
('options' in cell.column.columnDef ? cell.column.columnDef.options : []) as Array<
|
|
65
|
+
SelectOption<unknown>
|
|
66
|
+
>
|
|
67
|
+
);
|
|
68
|
+
const hasValue: boolean = $derived(value !== undefined);
|
|
69
|
+
|
|
70
|
+
const focusDetails: TableFocusDetails<T, RowIdType> = $derived(
|
|
71
|
+
tableContext._focus.getFocusDetailsFor(cell)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// `modal` lives on every `AbstractDisplayDef` subclass (Display,
|
|
75
|
+
// Bubble, Progress, Validity), so a duck-typed read against the base
|
|
76
|
+
// `ColumnDef` is sufficient and avoids importing the entire
|
|
77
|
+
// AbstractDisplayDef class chain just to satisfy the type checker.
|
|
78
|
+
const modalComponent = $derived(
|
|
79
|
+
(cell.column.columnDef as AbstractDisplayDef<T, FilterValueType>).modal
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
let previousFocusElement: HTMLElement | undefined = $state();
|
|
83
|
+
let showModal = $state(false);
|
|
84
|
+
|
|
85
|
+
const thisId = $derived(
|
|
86
|
+
`${tableContext.tableName}-cell-${cell.row.visibleIndex}-${cell.column.id}`
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Match the open-modal state by row-object identity and column id so
|
|
90
|
+
// pagination / sort / filter does not silently re-target the modal at
|
|
91
|
+
// a different row that happens to occupy the same visible index.
|
|
92
|
+
$effect(() => {
|
|
93
|
+
const target = tableContext.openModalCell;
|
|
94
|
+
if (target !== null && target.row === cell.row.original && target.columnId === cell.column.id) {
|
|
95
|
+
previousFocusElement = document.activeElement as HTMLElement | undefined;
|
|
96
|
+
showModal = true;
|
|
97
|
+
} else {
|
|
98
|
+
showModal = false;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
function onclose() {
|
|
103
|
+
tableContext.setOpenModalCell(null);
|
|
104
|
+
|
|
105
|
+
if (previousFocusElement?.id != thisId) {
|
|
106
|
+
tick()
|
|
107
|
+
.then(() => {
|
|
108
|
+
previousFocusElement?.focus();
|
|
109
|
+
})
|
|
110
|
+
.catch(devCatch);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let validity: Validity = $state(Validity.Valid);
|
|
115
|
+
$effect(() => {
|
|
116
|
+
const validityPromise = focusDetails.validity;
|
|
117
|
+
|
|
118
|
+
validityPromise
|
|
119
|
+
.then((v) => {
|
|
120
|
+
if (!v) {
|
|
121
|
+
validity = Validity.Neutral;
|
|
122
|
+
} else if (isValidityTuple(v)) {
|
|
123
|
+
validity = v.value;
|
|
124
|
+
} else {
|
|
125
|
+
validity = v;
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
.catch(devCatch);
|
|
129
|
+
});
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
|
|
133
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
134
|
+
<div
|
|
135
|
+
id={thisId}
|
|
136
|
+
class:info={validity == Validity.Info}
|
|
137
|
+
class:success={validity == Validity.Valid}
|
|
138
|
+
class:warning={validity == Validity.Warning}
|
|
139
|
+
class:error={validity == Validity.Invalid}
|
|
140
|
+
class:has-modal={!!modalComponent}
|
|
141
|
+
tabIndex={hasValue ? 0 : undefined}
|
|
142
|
+
class="focusable"
|
|
143
|
+
title={dateTooltip}
|
|
144
|
+
style={focusDetails.outline}
|
|
145
|
+
class:multifocus={hasValue && focusDetails.isMultiFocused}
|
|
146
|
+
class:multifocus-start={hasValue && focusDetails.isMultiFocusStart}
|
|
147
|
+
onmousedown={(e) => {
|
|
148
|
+
// start focus only on left click
|
|
149
|
+
if (e.button === 0) {
|
|
150
|
+
if (e.shiftKey) {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
tableContext.setFocusEnd(focusDetails.coordinates);
|
|
153
|
+
} else {
|
|
154
|
+
tableContext.startFocus(focusDetails.coordinates);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}}
|
|
158
|
+
onmouseover={() => {
|
|
159
|
+
if (tableContext.isMousingDown) {
|
|
160
|
+
tableContext.setFocusEnd(focusDetails.coordinates);
|
|
161
|
+
}
|
|
162
|
+
}}
|
|
163
|
+
onmouseup={() => {
|
|
164
|
+
tableContext.endFocus(focusDetails.coordinates);
|
|
165
|
+
}}
|
|
166
|
+
onclick={() => {
|
|
167
|
+
// Open the per-cell modal if the column declares one. Click is
|
|
168
|
+
// the right trigger here (focus already started on mousedown);
|
|
169
|
+
// modal-less display cells fall through to the default focus
|
|
170
|
+
// behaviour with no extra handler.
|
|
171
|
+
if (modalComponent) {
|
|
172
|
+
tableContext.setOpenModalCell({
|
|
173
|
+
row: cell.row.original,
|
|
174
|
+
columnId: cell.column.id,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}}
|
|
178
|
+
onkeydown={(e) => {
|
|
179
|
+
// Keyboard parity with `onclick` above: when the cell has DOM
|
|
180
|
+
// focus (via `tabIndex`), Enter / Space opens the modal. Only
|
|
181
|
+
// fires for columns that declare a `modal`; non-modal display
|
|
182
|
+
// cells defer to the table's global keyboard navigation.
|
|
183
|
+
if (modalComponent && (e.key === 'Enter' || e.key === ' ')) {
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
tableContext.setOpenModalCell({
|
|
186
|
+
row: cell.row.original,
|
|
187
|
+
columnId: cell.column.id,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}}
|
|
191
|
+
>
|
|
192
|
+
{#if hasValue}
|
|
193
|
+
{#await visibleValue}
|
|
194
|
+
{loadingText}
|
|
195
|
+
{:then v}
|
|
196
|
+
{(v instanceof Date
|
|
197
|
+
? v.toLocaleDateString()
|
|
198
|
+
: options.length
|
|
199
|
+
? options.find((it) => it.value === v)?.label
|
|
200
|
+
: v) ??
|
|
201
|
+
v ??
|
|
202
|
+
''}
|
|
203
|
+
{/await}
|
|
204
|
+
{:else if children}
|
|
205
|
+
<div
|
|
206
|
+
class="slot-wrapper"
|
|
207
|
+
class:multifocus={focusDetails.isMultiFocused}
|
|
208
|
+
class:multifocus-start={focusDetails.isMultiFocusStart}
|
|
209
|
+
>
|
|
210
|
+
{@render children()}
|
|
211
|
+
</div>
|
|
212
|
+
{/if}
|
|
213
|
+
</div>
|
|
214
|
+
{#if showModal && modalComponent}
|
|
215
|
+
<!--
|
|
216
|
+
The component receives the public `DisplayModalProps<T>` shape
|
|
217
|
+
`{ row, close }`. The dialog is NOT wrapped in
|
|
218
|
+
`<Portal target={document.body}>` any more. `<dialog>` opened
|
|
219
|
+
via `showModal()` enters the browser's top layer, so it already
|
|
220
|
+
escapes overflow:hidden / sticky / transform ancestors without
|
|
221
|
+
needing to be re-parented. Moving the `<div>` ancestor mid-mount
|
|
222
|
+
(Portal's $effect runs during the same microtask round as
|
|
223
|
+
BaseModal's `tick().then` `dialog.showModal()`) was popping the
|
|
224
|
+
dialog out of the top layer and leaving it to render inline at
|
|
225
|
+
the bottom of the page.
|
|
226
|
+
-->
|
|
227
|
+
{@const ModalComponent = modalComponent}
|
|
228
|
+
<ModalComponent
|
|
229
|
+
row={cell.row.original}
|
|
230
|
+
close={onclose}
|
|
231
|
+
/>
|
|
232
|
+
{/if}
|
|
233
|
+
|
|
234
234
|
<style>.focusable,
|
|
235
235
|
.checkbox-cell,
|
|
236
236
|
.input-cell,
|
|
@@ -321,4 +321,4 @@ div.slot-wrapper {
|
|
|
321
321
|
.input-cell.error,
|
|
322
322
|
.select-cell.error {
|
|
323
323
|
background-color: #f9e9e9;
|
|
324
|
-
}</style>
|
|
324
|
+
}</style>
|