@juspay/svelte-ui-components 4.27.6 → 4.28.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.
- package/README.md +140 -36
- package/dist/Animations/ModalAnimation.svelte +15 -1
- package/dist/Animations/OverlayAnimation.svelte +7 -1
- package/dist/AreaChart/AreaChart.svelte +366 -85
- package/dist/AspectRatio/AspectRatio.svelte +51 -0
- package/dist/AspectRatio/AspectRatio.svelte.d.ts +3 -0
- package/dist/AspectRatio/properties.d.ts +27 -0
- package/dist/AspectRatio/properties.js +1 -0
- package/dist/Banner/Banner.svelte +10 -4
- package/dist/Banner/properties.d.ts +8 -0
- package/dist/BarChart/BarChart.svelte +156 -49
- package/dist/Book/Book.svelte +14 -1
- package/dist/BrandLoader/BrandLoader.svelte +76 -12
- package/dist/Button/Button.svelte +56 -11
- package/dist/Calendar/Calendar.svelte +88 -61
- package/dist/Card/Card.svelte +1 -1
- package/dist/Carousel/Carousel.svelte +6 -3
- package/dist/ChatComposer/ChatComposer.svelte +54 -4
- package/dist/ChatComposer/dictationState.d.ts +15 -0
- package/dist/ChatComposer/dictationState.js +9 -0
- package/dist/ChatComposer/properties.d.ts +28 -1
- package/dist/ChatMessage/ChatMessage.svelte +27 -1
- package/dist/CheckListItem/CheckListItem.svelte +3 -3
- package/dist/Checkbox/Checkbox.svelte +84 -3
- package/dist/Checkbox/properties.d.ts +45 -13
- package/dist/ChipInput/ChipInput.svelte +70 -3
- package/dist/ChipInput/properties.d.ts +16 -0
- package/dist/Choicebox/Choicebox.svelte +156 -1
- package/dist/Choicebox/group.d.ts +63 -0
- package/dist/Choicebox/group.js +127 -0
- package/dist/Choicebox/properties.d.ts +46 -0
- package/dist/ColorPicker/ColorPicker.svelte +169 -20
- package/dist/ColorPicker/properties.d.ts +16 -0
- package/dist/Combobox/Combobox.svelte +87 -25
- package/dist/Combobox/properties.d.ts +23 -0
- package/dist/CommandMenu/CommandMenu.svelte +27 -11
- package/dist/ContextMenu/ContextMenu.svelte +31 -37
- package/dist/DateRangePicker/DateRangePicker.svelte +171 -73
- package/dist/DeltaIndicator/DeltaIndicator.svelte +3 -3
- package/dist/Draggable/Draggable.svelte +12 -7
- package/dist/DualAxisBarChart/DualAxisBarChart.svelte +11 -4
- package/dist/EmptyState/EmptyState.svelte +1 -1
- package/dist/FileDropzoneTrigger/FileDropzoneTrigger.svelte +1 -1
- package/dist/FileInput/FileInput.svelte +50 -1
- package/dist/FileInput/properties.d.ts +7 -0
- package/dist/FunnelChart/FunnelChart.svelte +40 -12
- package/dist/FunnelChart/properties.d.ts +7 -1
- package/dist/Gallery/Gallery.svelte +20 -13
- package/dist/Gallery/properties.d.ts +8 -0
- package/dist/GridItem/GridItem.svelte +31 -1
- package/dist/HITL/HITL.svelte +35 -9
- package/dist/Icon/Icon.svelte +19 -3
- package/dist/Input/Input.svelte +24 -10
- package/dist/Input/properties.d.ts +37 -1
- package/dist/InputButton/InputButton.svelte +86 -11
- package/dist/InputButton/properties.d.ts +10 -1
- package/dist/Label/Label.svelte +54 -0
- package/dist/Label/Label.svelte.d.ts +3 -0
- package/dist/Label/properties.d.ts +26 -0
- package/dist/Label/properties.js +1 -0
- package/dist/LineChart/LineChart.svelte +385 -94
- package/dist/ListItem/ListItem.svelte +121 -15
- package/dist/Loader/Loader.svelte +22 -2
- package/dist/LoadingDots/LoadingDots.svelte +21 -0
- package/dist/MediaUpload/MediaUpload.svelte +13 -3
- package/dist/Menu/Menu.svelte +56 -28
- package/dist/Modal/Modal.svelte +77 -42
- package/dist/Pagination/Pagination.svelte +9 -2
- package/dist/Phone/Phone.svelte +4 -0
- package/dist/PieChart/PieChart.svelte +282 -38
- package/dist/PieChart/properties.d.ts +9 -0
- package/dist/Pill/Pill.svelte +14 -5
- package/dist/Progress/Progress.svelte +20 -0
- package/dist/Radio/Radio.svelte +58 -1
- package/dist/Radio/properties.d.ts +23 -0
- package/dist/RatingGroup/RatingGroup.svelte +283 -0
- package/dist/RatingGroup/RatingGroup.svelte.d.ts +4 -0
- package/dist/RatingGroup/properties.d.ts +73 -0
- package/dist/RatingGroup/properties.js +1 -0
- package/dist/RelativeTime/RelativeTime.svelte +1 -1
- package/dist/SankeyChart/SankeyChart.svelte +272 -50
- package/dist/SankeyChart/properties.d.ts +9 -0
- package/dist/Scroller/Scroller.svelte +37 -2
- package/dist/Select/Select.svelte +166 -46
- package/dist/Select/properties.d.ts +7 -0
- package/dist/Separator/Separator.svelte +48 -0
- package/dist/Separator/Separator.svelte.d.ts +3 -0
- package/dist/Separator/properties.d.ts +22 -0
- package/dist/Separator/properties.js +1 -0
- package/dist/Sheet/Sheet.svelte +60 -21
- package/dist/Shimmer/Shimmer.svelte +15 -0
- package/dist/Slider/Slider.svelte +87 -6
- package/dist/Slider/properties.d.ts +26 -0
- package/dist/Snippet/Snippet.svelte +4 -0
- package/dist/Snippet/copyState.svelte.d.ts +13 -1
- package/dist/Snippet/copyState.svelte.js +27 -1
- package/dist/Snippet/properties.d.ts +7 -0
- package/dist/SoundKit/SoundKit.js +9 -8
- package/dist/SplitInput/SplitInput.svelte +68 -2
- package/dist/SplitInput/properties.d.ts +23 -0
- package/dist/StatCard/StatCard.svelte +6 -6
- package/dist/Status/Status.svelte +2 -2
- package/dist/Stepper/Step.svelte +19 -6
- package/dist/Stepper/Stepper.svelte +108 -35
- package/dist/Table/BuiltinCell.svelte +2 -2
- package/dist/Table/Table.svelte +373 -55
- package/dist/Table/properties.d.ts +134 -3
- package/dist/Tabs/Tabs.svelte +263 -65
- package/dist/Tabs/properties.d.ts +23 -0
- package/dist/TaskList/TaskList.svelte +14 -5
- package/dist/ThemeSwitcher/ThemeSwitcher.svelte +23 -0
- package/dist/ThinkingIndicator/ThinkingIndicator.svelte +13 -5
- package/dist/Toast/Toast.svelte +27 -2
- package/dist/Toggle/Toggle.svelte +79 -7
- package/dist/Toggle/Toggle.svelte.d.ts +1 -1
- package/dist/Toggle/properties.d.ts +30 -7
- package/dist/ToolCallLog/ToolCallLog.svelte +59 -21
- package/dist/ToolCallLog/properties.d.ts +10 -0
- package/dist/Toolbar/Toolbar.svelte +1 -1
- package/dist/Tooltip/Tooltip.svelte +20 -2
- package/dist/Tooltip/tooltip-action.js +39 -4
- package/dist/TypewriterText/TypewriterText.svelte +34 -2
- package/dist/TypewriterText/properties.d.ts +18 -5
- package/dist/_chart/ChartContainer.svelte +12 -0
- package/dist/_chart/ChartTooltip.svelte +18 -4
- package/dist/_chart/Legend.svelte +21 -0
- package/dist/_chart/Legend.svelte.d.ts +15 -0
- package/dist/_chart/geometry.d.ts +54 -1
- package/dist/_chart/geometry.js +122 -14
- package/dist/_chart/interactions.d.ts +5 -1
- package/dist/_chart/interactions.js +9 -5
- package/dist/_chart/scales.js +16 -0
- package/dist/_chart/temporal.d.ts +45 -0
- package/dist/_chart/temporal.js +116 -0
- package/dist/_field/description.d.ts +50 -0
- package/dist/_field/description.js +51 -0
- package/dist/_interaction/dismissal.d.ts +67 -0
- package/dist/_interaction/dismissal.js +137 -0
- package/dist/_interaction/focus.d.ts +73 -0
- package/dist/_interaction/focus.js +143 -0
- package/dist/assets/star.svg +5 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +21 -1
- package/dist/reduced-motion.svelte.d.ts +37 -0
- package/dist/reduced-motion.svelte.js +44 -0
- package/dist/styles/theme-dark.css +778 -0
- package/dist/types.d.ts +4 -2
- package/dist/utils.d.ts +62 -1
- package/dist/utils.js +80 -21
- package/dist-codemod/{bin.js → codemod/bin.js} +13 -2
- package/dist-codemod/{cli.js → codemod/cli.js} +48 -3
- package/dist-codemod/codemod/migrate-audit.js +395 -0
- package/dist-codemod/codemod/migration-assets/legacy-display.css +120 -0
- package/dist-codemod/codemod/migration-assets/legacy-palette.css +106 -0
- package/dist-codemod/codemod/migration-assets/wc-display.json +492 -0
- package/dist-codemod/migrate/analyze.js +804 -0
- package/dist-codemod/migrate/chart-tooltip-selector.js +301 -0
- package/dist-wc/index.js +13992 -4323
- package/package.json +13 -6
- package/dist/Modal/focus-trap.d.ts +0 -42
- package/dist/Modal/focus-trap.js +0 -57
- /package/dist-codemod/{legacy-pairs.js → codemod/legacy-pairs.js} +0 -0
- /package/dist-codemod/{transform.js → codemod/transform.js} +0 -0
- /package/dist-codemod/{wc-children.js → codemod/wc-children.js} +0 -0
package/dist/Table/Table.svelte
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
TableProperties,
|
|
4
|
+
TableCheckboxSelectionConfig,
|
|
5
|
+
TableRow,
|
|
6
|
+
SortDirection
|
|
7
|
+
} from './properties';
|
|
3
8
|
import { normalizeColumns } from './normalizeColumns';
|
|
4
9
|
import BuiltinCell from './BuiltinCell.svelte';
|
|
5
10
|
import type { JSONValue } from 'type-decoder';
|
|
@@ -25,6 +30,7 @@
|
|
|
25
30
|
sortable = true,
|
|
26
31
|
sortableColumns,
|
|
27
32
|
sortMode = 'client',
|
|
33
|
+
sortState,
|
|
28
34
|
stickyHeader = false,
|
|
29
35
|
isTableScrollable = false,
|
|
30
36
|
isContentScrollable = false,
|
|
@@ -37,6 +43,7 @@
|
|
|
37
43
|
empty,
|
|
38
44
|
onrowclick,
|
|
39
45
|
onsort,
|
|
46
|
+
onsortchange,
|
|
40
47
|
classes,
|
|
41
48
|
paginatorSlot,
|
|
42
49
|
getRowTestId,
|
|
@@ -52,6 +59,7 @@
|
|
|
52
59
|
headerTooltipIcon,
|
|
53
60
|
headerTooltipPosition,
|
|
54
61
|
usePortal = false,
|
|
62
|
+
mobileCardLayout = false,
|
|
55
63
|
labels
|
|
56
64
|
}: TableProperties = $props();
|
|
57
65
|
|
|
@@ -86,8 +94,36 @@
|
|
|
86
94
|
});
|
|
87
95
|
|
|
88
96
|
// ─── Sort state ──────────────────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
// Held as a column ID, never as a column index. The index of a field changes
|
|
98
|
+
// when columns are reordered or hidden, so index-keyed state follows the
|
|
99
|
+
// POSITION and silently starts sorting whichever field moved into it.
|
|
100
|
+
let internalSortColumnId = $state<string | null>(null);
|
|
101
|
+
let internalSortDirection = $state<SortDirection>('asc');
|
|
102
|
+
|
|
103
|
+
/** Positional tables declare no identity, so the index string is the ID. */
|
|
104
|
+
const columnIdAt = (colIndex: number): string => columns?.[colIndex]?.id ?? String(colIndex);
|
|
105
|
+
|
|
106
|
+
// Controlled-sort overlay, mirroring the controlled-selection overlay below:
|
|
107
|
+
// when the consumer supplies `sortState` (including `null`, meaning "sorted
|
|
108
|
+
// by nothing"), Table renders FROM it and never mutates it. Absent — every
|
|
109
|
+
// pre-existing consumer — the internal state behaves exactly as before.
|
|
110
|
+
let isControlledSort = $derived(typeof sortState !== 'undefined');
|
|
111
|
+
let activeSortColumnId = $derived(
|
|
112
|
+
isControlledSort ? (sortState?.columnId ?? null) : internalSortColumnId
|
|
113
|
+
);
|
|
114
|
+
let sortDirection = $derived(
|
|
115
|
+
isControlledSort ? (sortState?.direction ?? 'asc') : internalSortDirection
|
|
116
|
+
);
|
|
117
|
+
/** The sorted column's CURRENT index, or null when its ID matches none. */
|
|
118
|
+
let sortColumn = $derived.by((): number | null => {
|
|
119
|
+
if (activeSortColumnId === null) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const index = effectiveHeaders.findIndex(
|
|
123
|
+
(_header, colIndex) => columnIdAt(colIndex) === activeSortColumnId
|
|
124
|
+
);
|
|
125
|
+
return index === -1 ? null : index;
|
|
126
|
+
});
|
|
91
127
|
|
|
92
128
|
const isColumnSortable = (colIndex: number): boolean => {
|
|
93
129
|
if (!sortable) {
|
|
@@ -104,26 +140,52 @@
|
|
|
104
140
|
return;
|
|
105
141
|
}
|
|
106
142
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
143
|
+
const columnId = columnIdAt(colIndex);
|
|
144
|
+
const nextDirection: SortDirection =
|
|
145
|
+
activeSortColumnId === columnId && sortDirection === 'asc' ? 'desc' : 'asc';
|
|
146
|
+
|
|
147
|
+
if (!isControlledSort) {
|
|
148
|
+
internalSortColumnId = columnId;
|
|
149
|
+
internalSortDirection = nextDirection;
|
|
112
150
|
}
|
|
113
|
-
onsort?.(colIndex,
|
|
151
|
+
onsort?.(colIndex, nextDirection);
|
|
152
|
+
onsortchange?.({ columnId, direction: nextDirection });
|
|
114
153
|
};
|
|
115
154
|
|
|
116
155
|
// ─── Row click ───────────────────────────────────────────────────────────────
|
|
117
|
-
const handleRowClick = (
|
|
156
|
+
const handleRowClick = (
|
|
157
|
+
event: MouseEvent,
|
|
158
|
+
rowIndex: number,
|
|
159
|
+
rowData: JSONValue[],
|
|
160
|
+
originalIndex: number
|
|
161
|
+
): void => {
|
|
162
|
+
if (isRowActivationIgnored(event.target)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
118
165
|
onrowclick?.(rowIndex, rowData, originalIndex);
|
|
119
166
|
};
|
|
120
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Enter/Space activates the row only when the ROW ITSELF holds focus.
|
|
170
|
+
*
|
|
171
|
+
* Built-in cells stop propagation, but a caller's `cell` snippet deliberately
|
|
172
|
+
* does not, so every key pressed inside a custom control used to bubble here:
|
|
173
|
+
* typing a space in an editable cell opened the record AND lost the space to
|
|
174
|
+
* `preventDefault()`, and Enter on a cell button fired the button and the row.
|
|
175
|
+
* Asking whether the row is the event's target answers that exactly — a key
|
|
176
|
+
* event targets the focused element — without an allowlist of "interactive"
|
|
177
|
+
* tags, which cannot be complete for inputs, editable regions, custom
|
|
178
|
+
* elements or portaled controls.
|
|
179
|
+
*/
|
|
121
180
|
const handleRowKeydown = (
|
|
122
181
|
event: KeyboardEvent,
|
|
123
182
|
rowIndex: number,
|
|
124
183
|
rowData: JSONValue[],
|
|
125
184
|
originalIndex: number
|
|
126
185
|
): void => {
|
|
186
|
+
if (event.target !== event.currentTarget) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
127
189
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
128
190
|
event.preventDefault();
|
|
129
191
|
onrowclick?.(rowIndex, rowData, originalIndex);
|
|
@@ -133,6 +195,27 @@
|
|
|
133
195
|
let isRowClickable = $derived(typeof onrowclick === 'function');
|
|
134
196
|
let isStickyHeader = $derived(stickyHeader || isTableScrollable);
|
|
135
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Click is NOT guarded the way keydown is, because "click anywhere on the row
|
|
200
|
+
* to open it" is the feature — a guard by event target would break clicking
|
|
201
|
+
* the row's own text, which is most of the row. Interactive custom-cell
|
|
202
|
+
* content opts out instead, by marking itself:
|
|
203
|
+
*
|
|
204
|
+
* <button data-row-activation="ignore">Retry</button>
|
|
205
|
+
*
|
|
206
|
+
* An attribute rather than an exported action, so it needs no import and
|
|
207
|
+
* survives being written by a consumer who only has markup. Built-in cells
|
|
208
|
+
* already stop propagation themselves and need nothing. Portaled controls
|
|
209
|
+
* (in-cell Menu/Select with `usePortal`) render outside the row entirely, so
|
|
210
|
+
* their clicks never reach it — they are safe without the marking, which is
|
|
211
|
+
* why this is deliberately not sold as a complete "is interactive" test.
|
|
212
|
+
*/
|
|
213
|
+
const IGNORE_SELECTOR = '[data-row-activation="ignore"]';
|
|
214
|
+
|
|
215
|
+
const isRowActivationIgnored = (target: EventTarget | null): boolean => {
|
|
216
|
+
return target instanceof Element && target.closest(IGNORE_SELECTOR) !== null;
|
|
217
|
+
};
|
|
218
|
+
|
|
136
219
|
// ─── Horizontal-scroll affordance ────────────────────────────────────────
|
|
137
220
|
// The table clips columns behind an internal horizontal scroll on narrow
|
|
138
221
|
// viewports, but a bare scroll container gives no visual hint that more
|
|
@@ -165,7 +248,15 @@
|
|
|
165
248
|
};
|
|
166
249
|
|
|
167
250
|
// ─── C2-3: Search ─────────────────────────────────────────────────────────
|
|
168
|
-
|
|
251
|
+
// Same overlay shape as sort and selection: `searchConfig.searchTerm` takes
|
|
252
|
+
// ownership of the visible term, `onSearchTermChange` reports the would-be
|
|
253
|
+
// next one. Note this is orthogonal to `onsearchchange`, which delegates
|
|
254
|
+
// FILTERING — a controlled term still filters client-side on its own.
|
|
255
|
+
let internalSearchTerm = $state('');
|
|
256
|
+
let isControlledSearch = $derived(typeof searchConfig?.searchTerm === 'string');
|
|
257
|
+
let searchTerm = $derived(
|
|
258
|
+
isControlledSearch ? (searchConfig?.searchTerm ?? '') : internalSearchTerm
|
|
259
|
+
);
|
|
169
260
|
let hasSearchConfig = $derived(!!searchConfig);
|
|
170
261
|
let isServerSearch = $derived(typeof onsearchchange === 'function');
|
|
171
262
|
let searchInputRef = $state<HTMLInputElement | null>(null);
|
|
@@ -175,9 +266,12 @@
|
|
|
175
266
|
let isInlineSearchExpanded = $state(false);
|
|
176
267
|
|
|
177
268
|
const updateSearch = (term: string): void => {
|
|
178
|
-
|
|
269
|
+
if (!isControlledSearch) {
|
|
270
|
+
internalSearchTerm = term;
|
|
271
|
+
}
|
|
179
272
|
pageOverride = 1;
|
|
180
273
|
pagination?.onPageChange?.(1);
|
|
274
|
+
searchConfig?.onSearchTermChange?.(term);
|
|
181
275
|
if (isServerSearch) {
|
|
182
276
|
onsearchchange?.(term);
|
|
183
277
|
}
|
|
@@ -332,16 +426,20 @@
|
|
|
332
426
|
let pageOverride = $state<number | null>(null);
|
|
333
427
|
let pageSizeOverride = $state<number | null>(null);
|
|
334
428
|
let paginationMode = $derived(pagination?.mode ?? 'client');
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
429
|
+
// Server mode with a declared `pageSize` hands page-size ownership to the
|
|
430
|
+
// consumer, exactly as `selectedIds` does for selection: the selector emits
|
|
431
|
+
// `onPageSizeChange` as a REQUEST, and until the consumer answers by changing
|
|
432
|
+
// the prop, the chrome keeps describing the page the server actually served.
|
|
433
|
+
// Without a declared `pageSize` (and in client mode) the selector owns it, as
|
|
434
|
+
// before.
|
|
435
|
+
let isPageSizeControlled = $derived(
|
|
436
|
+
paginationMode === 'server' && typeof pagination?.pageSize === 'number'
|
|
437
|
+
);
|
|
438
|
+
let effectivePageSize = $derived(
|
|
439
|
+
isPageSizeControlled
|
|
440
|
+
? (pagination?.pageSize ?? 10)
|
|
441
|
+
: (pageSizeOverride ?? pagination?.pageSize ?? 10)
|
|
442
|
+
);
|
|
345
443
|
let paginationTotalItems = $derived.by(() => {
|
|
346
444
|
if (!pagination) {
|
|
347
445
|
return 0;
|
|
@@ -354,6 +452,29 @@
|
|
|
354
452
|
let paginationTotalPages = $derived(
|
|
355
453
|
Math.max(1, Math.ceil(paginationTotalItems / Math.max(1, effectivePageSize)))
|
|
356
454
|
);
|
|
455
|
+
let requestedPage = $derived.by(() => {
|
|
456
|
+
if (!pagination) {
|
|
457
|
+
return 1;
|
|
458
|
+
}
|
|
459
|
+
if (paginationMode === 'server') {
|
|
460
|
+
return pagination.page ?? 1;
|
|
461
|
+
}
|
|
462
|
+
return pageOverride ?? pagination.page ?? 1;
|
|
463
|
+
});
|
|
464
|
+
// In client mode the row set can shrink from outside — rows deleted, replaced,
|
|
465
|
+
// or filtered away — while the reader sits on a later page, leaving the slice
|
|
466
|
+
// empty and the range text describing rows that no longer exist ("21-5 of 5").
|
|
467
|
+
// Table owns the page in this mode, so it resolves to the last page that has
|
|
468
|
+
// rows. Display-side only: no onPageChange is emitted, since no one asked for
|
|
469
|
+
// a page change, and the reader's chosen page is kept rather than overwritten,
|
|
470
|
+
// so a set that shrinks and comes back does not relocate them to page 1.
|
|
471
|
+
// Server mode is left alone — there the page is the consumer's and an
|
|
472
|
+
// unexpected page number is theirs to reconcile.
|
|
473
|
+
let effectivePage = $derived(
|
|
474
|
+
paginationMode === 'server'
|
|
475
|
+
? requestedPage
|
|
476
|
+
: Math.min(Math.max(1, requestedPage), paginationTotalPages)
|
|
477
|
+
);
|
|
357
478
|
let paginatedTableData = $derived.by(() => {
|
|
358
479
|
if (!pagination || paginationMode === 'server') {
|
|
359
480
|
return filteredTableData;
|
|
@@ -614,8 +735,29 @@
|
|
|
614
735
|
return { ...own, ...consumer };
|
|
615
736
|
};
|
|
616
737
|
|
|
738
|
+
// ─── Empty-state reason ───────────────────────────────────────────────────
|
|
739
|
+
// "No records yet" and "nothing matched" need different messages and offer
|
|
740
|
+
// different remedies, and a consumer cannot tell them apart from `rows` alone
|
|
741
|
+
// once the built-in search owns the term. A filter being ACTIVE is the
|
|
742
|
+
// signal, not whether rows survived it: that stays true in server mode, where
|
|
743
|
+
// the consumer hands back an already-filtered (and empty) page.
|
|
744
|
+
let hasActiveColumnFilter = $derived(
|
|
745
|
+
(columns ?? []).some((column) => typeof column.filter?.selectedValue === 'string')
|
|
746
|
+
);
|
|
747
|
+
let emptyReason = $derived<'no-rows' | 'no-matches'>(
|
|
748
|
+
searchTerm.trim() !== '' || hasActiveColumnFilter ? 'no-matches' : 'no-rows'
|
|
749
|
+
);
|
|
750
|
+
|
|
617
751
|
let isCheckboxMode = $derived(!!checkboxSelection && checkboxSelection.enabled !== false);
|
|
618
752
|
let isSingleSelect = $derived(checkboxSelection?.selectionMode === 'single');
|
|
753
|
+
|
|
754
|
+
// ─── Mobile record cards ─────────────────────────────────────────────────────
|
|
755
|
+
// Restating a role that is already implicit at desktop width (table stays
|
|
756
|
+
// `display: table`) is a no-op there and is what keeps the AX tree a real
|
|
757
|
+
// table once `.table-mobile-cards` switches `display` to `block` below the
|
|
758
|
+
// breakpoint — see the accessibility comment on `mobileCardLayout` in
|
|
759
|
+
// properties.ts and the `.table-mobile-cards` rules below.
|
|
760
|
+
const mobileRole = (role: string): string | null => (mobileCardLayout ? role : null);
|
|
619
761
|
</script>
|
|
620
762
|
|
|
621
763
|
{#if typeof tableTitle === 'string' && tableTitle.length > 0}
|
|
@@ -665,6 +807,7 @@
|
|
|
665
807
|
{#if effectiveHeaders.length !== 0 || effectiveData.length !== 0}
|
|
666
808
|
<div
|
|
667
809
|
class="table-container {isTableScrollable ? 'scrollable-table' : ''} {classes ?? ''}"
|
|
810
|
+
class:table-mobile-cards={mobileCardLayout}
|
|
668
811
|
data-pw={testId}
|
|
669
812
|
testID={testId}
|
|
670
813
|
>
|
|
@@ -674,16 +817,17 @@
|
|
|
674
817
|
class:scrollable-right={canScrollRight}
|
|
675
818
|
>
|
|
676
819
|
<div class="table-scroll" use:trackHorizontalScroll>
|
|
677
|
-
<table>
|
|
820
|
+
<table role={mobileRole('table')}>
|
|
678
821
|
{#if caption}
|
|
679
822
|
<caption class="sr-only">{caption}</caption>
|
|
680
823
|
{/if}
|
|
681
|
-
<thead>
|
|
682
|
-
<tr>
|
|
824
|
+
<thead role={mobileRole('rowgroup')}>
|
|
825
|
+
<tr role={mobileRole('row')}>
|
|
683
826
|
{#if isCheckboxMode && !isSingleSelect}
|
|
684
827
|
<th
|
|
685
828
|
class="table-header table-checkbox-col"
|
|
686
829
|
class:table-header-sticky={isStickyHeader}
|
|
830
|
+
role={mobileRole('columnheader')}
|
|
687
831
|
>
|
|
688
832
|
<!-- Header tri-state checkbox: the library Checkbox in controlled mode,
|
|
689
833
|
so the tri-state is computed here and never flipped locally. -->
|
|
@@ -705,13 +849,15 @@
|
|
|
705
849
|
<th
|
|
706
850
|
class="table-header table-checkbox-col"
|
|
707
851
|
class:table-header-sticky={isStickyHeader}
|
|
852
|
+
role={mobileRole('columnheader')}
|
|
708
853
|
>
|
|
709
854
|
</th>
|
|
710
855
|
{/if}
|
|
711
856
|
{#if rowNumberColumn}
|
|
712
857
|
<th
|
|
713
858
|
class="table-header table-row-number-col"
|
|
714
|
-
class:table-header-sticky={isStickyHeader}
|
|
859
|
+
class:table-header-sticky={isStickyHeader}
|
|
860
|
+
role={mobileRole('columnheader')}>{rowNumberLabel}</th
|
|
715
861
|
>
|
|
716
862
|
{/if}
|
|
717
863
|
{#each effectiveHeaders as header, colIndex (colIndex)}
|
|
@@ -720,6 +866,14 @@
|
|
|
720
866
|
class="table-header"
|
|
721
867
|
class:table-header-sticky={isStickyHeader}
|
|
722
868
|
class:table-col-highlighted={headerColumn?.highlighted === true}
|
|
869
|
+
role={mobileRole('columnheader')}
|
|
870
|
+
aria-sort={isColumnSortable(colIndex)
|
|
871
|
+
? sortColumn === colIndex
|
|
872
|
+
? sortDirection === 'asc'
|
|
873
|
+
? 'ascending'
|
|
874
|
+
: 'descending'
|
|
875
|
+
: 'none'
|
|
876
|
+
: null}
|
|
723
877
|
data-pw={headerColumn?.testId ?? null}
|
|
724
878
|
testID={headerColumn?.testId ?? null}
|
|
725
879
|
style:text-align={headerColumn?.align ?? null}
|
|
@@ -879,16 +1033,17 @@
|
|
|
879
1033
|
{/each}
|
|
880
1034
|
</tr>
|
|
881
1035
|
</thead>
|
|
882
|
-
<tbody>
|
|
1036
|
+
<tbody role={mobileRole('rowgroup')}>
|
|
883
1037
|
{#if filteredTableData.length === 0 && typeof empty === 'function'}
|
|
884
|
-
<tr>
|
|
1038
|
+
<tr role={mobileRole('row')}>
|
|
885
1039
|
<td
|
|
886
1040
|
class="table-empty"
|
|
1041
|
+
role={mobileRole('cell')}
|
|
887
1042
|
colspan={effectiveHeaders.length +
|
|
888
1043
|
(isCheckboxMode ? 1 : 0) +
|
|
889
1044
|
(rowNumberColumn ? 1 : 0)}
|
|
890
1045
|
>
|
|
891
|
-
{@render empty()}
|
|
1046
|
+
{@render empty({ reason: emptyReason, searchTerm })}
|
|
892
1047
|
</td>
|
|
893
1048
|
</tr>
|
|
894
1049
|
{:else}
|
|
@@ -904,10 +1059,11 @@
|
|
|
904
1059
|
class:table-row-selected={rowSelected}
|
|
905
1060
|
class:table-summary-row={summaryRowIndex !== null &&
|
|
906
1061
|
originalIndex === summaryRowIndex}
|
|
1062
|
+
role={mobileRole('row')}
|
|
907
1063
|
data-pw={typeof getRowTestId === 'function' ? getRowTestId(row, rowIndex) : null}
|
|
908
1064
|
testID={typeof getRowTestId === 'function' ? getRowTestId(row, rowIndex) : null}
|
|
909
1065
|
onclick={isRowClickable
|
|
910
|
-
? () => handleRowClick(rowIndex, row, originalIndex)
|
|
1066
|
+
? (mouseEvent) => handleRowClick(mouseEvent, rowIndex, row, originalIndex)
|
|
911
1067
|
: null}
|
|
912
1068
|
onkeydown={isRowClickable
|
|
913
1069
|
? (keyboardEvent) =>
|
|
@@ -920,10 +1076,19 @@
|
|
|
920
1076
|
also fire its own handler when the checkbox is toggled. -->
|
|
921
1077
|
<td
|
|
922
1078
|
class="table-content table-checkbox-col"
|
|
1079
|
+
role={mobileRole('cell')}
|
|
923
1080
|
onclick={(mouseEvent) => mouseEvent.stopPropagation()}
|
|
924
1081
|
onkeydown={(keyboardEvent) => keyboardEvent.stopPropagation()}
|
|
925
1082
|
>
|
|
926
|
-
|
|
1083
|
+
{#if mobileCardLayout}
|
|
1084
|
+
<!-- Real element, not `::before` — see the accessibility note
|
|
1085
|
+
on `mobileCardLayout` in properties.ts for why generated
|
|
1086
|
+
content cannot be the label. `aria-hidden` keeps a screen
|
|
1087
|
+
reader from announcing it a second time on top of the
|
|
1088
|
+
`columnheader`-derived name the checkbox's own
|
|
1089
|
+
`Select row …` aria-label already carries. -->
|
|
1090
|
+
<span class="table-mobile-label" aria-hidden="true">Select</span>
|
|
1091
|
+
{/if}<Checkbox
|
|
927
1092
|
text=""
|
|
928
1093
|
ariaLabel={labels?.selectRow?.(rowId) ??
|
|
929
1094
|
`Select row ${rowId || 'non-selectable'}`}
|
|
@@ -936,7 +1101,11 @@
|
|
|
936
1101
|
</td>
|
|
937
1102
|
{/if}
|
|
938
1103
|
{#if rowNumberColumn}
|
|
939
|
-
<td class="table-content table-row-number-col"
|
|
1104
|
+
<td class="table-content table-row-number-col" role={mobileRole('cell')}>
|
|
1105
|
+
{#if mobileCardLayout}
|
|
1106
|
+
<span class="table-mobile-label" aria-hidden="true">{rowNumberLabel}</span>
|
|
1107
|
+
{/if}<span class="table-mobile-value">{rowNumberFor(pageRowIndex)}</span>
|
|
1108
|
+
</td>
|
|
940
1109
|
{/if}
|
|
941
1110
|
{#each row as cellValue, colIndex (colIndex)}
|
|
942
1111
|
{@const keyedColumn = columns?.[colIndex]}
|
|
@@ -945,20 +1114,20 @@
|
|
|
945
1114
|
typeof cellValue === 'string' ||
|
|
946
1115
|
typeof cellValue === 'number' ||
|
|
947
1116
|
typeof cellValue === 'boolean'}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1117
|
+
{@const cellLabel = effectiveHeaders[colIndex] ?? ''}
|
|
1118
|
+
<!--
|
|
1119
|
+
The value markup is a snippet, called with `{@render}` rather than
|
|
1120
|
+
inlined as a `<div>` sibling of the `{#if mobileCardLayout}` label
|
|
1121
|
+
below, because a raw element there is whitespace-sensitive: Svelte
|
|
1122
|
+
bakes the newline prettier inserts between a control-block boundary
|
|
1123
|
+
and the following sibling into a real, always-present single-space
|
|
1124
|
+
text node (present even with `mobileCardLayout` off, since the `{#if}`
|
|
1125
|
+
still leaves its anchor comment behind) — verified with a throwaway
|
|
1126
|
+
component during development. `{@render ...}` is a mustache tag, not
|
|
1127
|
+
an element, so prettier never separates it from `{/if}` and the space
|
|
1128
|
+
never appears.
|
|
1129
|
+
-->
|
|
1130
|
+
{#snippet cellValueBody()}
|
|
962
1131
|
<div
|
|
963
1132
|
class={isContentScrollable ? 'scrollable-content' : ''}
|
|
964
1133
|
class:table-cell-clamp={keyedColumn?.maxWidth && isScalarCell}
|
|
@@ -985,6 +1154,25 @@
|
|
|
985
1154
|
{cellValue}
|
|
986
1155
|
{/if}
|
|
987
1156
|
</div>
|
|
1157
|
+
{/snippet}
|
|
1158
|
+
<td
|
|
1159
|
+
class="table-content"
|
|
1160
|
+
class:table-col-highlighted={keyedColumn?.highlighted === true}
|
|
1161
|
+
role={mobileRole('cell')}
|
|
1162
|
+
data-pw={typeof getCellTestId === 'function'
|
|
1163
|
+
? getCellTestId(row, cellValue, rowIndex)
|
|
1164
|
+
: null}
|
|
1165
|
+
testID={typeof getCellTestId === 'function'
|
|
1166
|
+
? getCellTestId(row, cellValue, rowIndex)
|
|
1167
|
+
: null}
|
|
1168
|
+
style:text-align={keyedColumn?.align ?? null}
|
|
1169
|
+
style:width={keyedColumn?.width ?? null}
|
|
1170
|
+
style:max-width={keyedColumn?.maxWidth ?? null}
|
|
1171
|
+
title={keyedColumn?.maxWidth && isScalarCell ? String(cellValue) : null}
|
|
1172
|
+
>
|
|
1173
|
+
{#if mobileCardLayout}
|
|
1174
|
+
<span class="table-mobile-label" aria-hidden="true">{cellLabel}</span>
|
|
1175
|
+
{/if}{@render cellValueBody()}
|
|
988
1176
|
</td>
|
|
989
1177
|
{/each}
|
|
990
1178
|
</tr>
|
|
@@ -1074,8 +1262,17 @@
|
|
|
1074
1262
|
margin: var(--table-title-margin, 0px 0px 12px 0px);
|
|
1075
1263
|
font-size: var(--table-title-font-size, var(--table-tile-font-size, 18px));
|
|
1076
1264
|
font-weight: var(--table-title-font-weight, 600);
|
|
1077
|
-
|
|
1078
|
-
|
|
1265
|
+
/* Unlike .table-content, a heading legitimately has no background of its
|
|
1266
|
+
own -- it sits directly on whatever surface the consumer places it on.
|
|
1267
|
+
The old #111827 literal ignored that surface entirely: on a dark page
|
|
1268
|
+
it measured a contrast of 1.03 against #1a1a26, i.e. invisible. `color`
|
|
1269
|
+
already inherits by default, so `inherit` states that explicitly and
|
|
1270
|
+
lets the title track its container's text colour; `currentColor` would
|
|
1271
|
+
resolve to the same value here (the spec special-cases it to mean
|
|
1272
|
+
exactly `inherit` for the `color` property) but reads as if it were
|
|
1273
|
+
referencing a colour set elsewhere on this rule, which it isn't. */
|
|
1274
|
+
color: var(--table-title-color, inherit);
|
|
1275
|
+
font-family: var(--table-title-font-family, inherit);
|
|
1079
1276
|
padding: var(--table-title-padding);
|
|
1080
1277
|
}
|
|
1081
1278
|
|
|
@@ -1120,7 +1317,7 @@
|
|
|
1120
1317
|
}
|
|
1121
1318
|
|
|
1122
1319
|
.table-search-input::placeholder {
|
|
1123
|
-
color: var(--table-search-placeholder-color, #
|
|
1320
|
+
color: var(--table-search-placeholder-color, #4b5563);
|
|
1124
1321
|
}
|
|
1125
1322
|
|
|
1126
1323
|
/* Hide browser-default clear button on search inputs */
|
|
@@ -1226,7 +1423,8 @@
|
|
|
1226
1423
|
width: var(--table-scroll-scrim-width, 32px);
|
|
1227
1424
|
opacity: 0;
|
|
1228
1425
|
pointer-events: none;
|
|
1229
|
-
transition: opacity 0.2s
|
|
1426
|
+
transition: opacity var(--table-scroll-scrim-transition-duration, var(--motion-duration, 0.2s))
|
|
1427
|
+
var(--table-scroll-scrim-transition-easing, var(--motion-easing, ease));
|
|
1230
1428
|
z-index: 2;
|
|
1231
1429
|
}
|
|
1232
1430
|
|
|
@@ -1286,10 +1484,10 @@
|
|
|
1286
1484
|
.table-header {
|
|
1287
1485
|
background-color: var(--table-header-background, var(--table-header-border-bgcolor, #f9fafb));
|
|
1288
1486
|
font-size: var(--table-header-font-size, 13px);
|
|
1289
|
-
font-family: var(--table-header-font-family);
|
|
1487
|
+
font-family: var(--table-header-font-family, inherit);
|
|
1290
1488
|
font-weight: var(--table-header-font-weight, 600);
|
|
1291
1489
|
letter-spacing: var(--table-header-letter-spacing, 0.02em);
|
|
1292
|
-
text-transform: var(--table-header-text-transform);
|
|
1490
|
+
text-transform: var(--table-header-text-transform, inherit);
|
|
1293
1491
|
color: var(--table-header-color, var(--table-header-font-color, #6b7280));
|
|
1294
1492
|
border-bottom: var(--table-header-border, var(--table-inner-border, none));
|
|
1295
1493
|
}
|
|
@@ -1359,9 +1557,16 @@
|
|
|
1359
1557
|
}
|
|
1360
1558
|
|
|
1361
1559
|
.table-content {
|
|
1362
|
-
|
|
1560
|
+
/* The terminal #ffffff matters as much as the colour beside it. Without it
|
|
1561
|
+
the whole declaration is invalid at computed-value time when neither
|
|
1562
|
+
variable is set, so the cell falls back to `transparent` and inherits
|
|
1563
|
+
whatever surface it lands on -- while `color` still resolves to a
|
|
1564
|
+
hardcoded near-black. On a dark page that is #111827 on #1a1a26: a
|
|
1565
|
+
measured contrast of 1.03, i.e. invisible. The header row never had the
|
|
1566
|
+
bug because it pairs its colour with a background of its own. */
|
|
1567
|
+
background-color: var(--table-content-background, var(--table-content-border-bgcolor, #ffffff));
|
|
1363
1568
|
font-size: var(--table-content-font-size, 14px);
|
|
1364
|
-
font-family: var(--table-content-font-family);
|
|
1569
|
+
font-family: var(--table-content-font-family, inherit);
|
|
1365
1570
|
color: var(--table-content-color, var(--table-content-font-color, #111827));
|
|
1366
1571
|
}
|
|
1367
1572
|
|
|
@@ -1461,7 +1666,12 @@
|
|
|
1461
1666
|
--checkbox-icon-size: var(--table-checkbox-icon-size, 12px);
|
|
1462
1667
|
--checkbox-checkmark-color: var(--table-checkbox-icon-color, #ffffff);
|
|
1463
1668
|
--checkbox-dash-color: var(--table-checkbox-icon-color, #ffffff);
|
|
1464
|
-
|
|
1669
|
+
/* Named so a consumer can reach it. Table sets the Checkbox child's token
|
|
1670
|
+
directly, and a rule on the element beats anything declared at :root, so
|
|
1671
|
+
through <sui-table> the selection checkboxes' transition could not be
|
|
1672
|
+
slowed or stopped by any means -- custom properties are the only route
|
|
1673
|
+
across a shadow boundary and this one was already spoken for. */
|
|
1674
|
+
--checkbox-transition: var(--table-checkbox-transition, 0.15s);
|
|
1465
1675
|
}
|
|
1466
1676
|
|
|
1467
1677
|
/* ── Sort button ────────────────────────────────────────────────────────── */
|
|
@@ -1569,6 +1779,11 @@
|
|
|
1569
1779
|
.table-paginator-size {
|
|
1570
1780
|
display: inline-flex;
|
|
1571
1781
|
min-width: var(--table-paginator-size-width, 72px);
|
|
1782
|
+
/* Select's standalone defaults (40px min-height, 8px 12px padding) render
|
|
1783
|
+
58px tall next to the 36px pagination buttons sharing this row. Match the
|
|
1784
|
+
steppers instead of towering over them. */
|
|
1785
|
+
--select-trigger-min-height: var(--table-paginator-size-height, 36px);
|
|
1786
|
+
--select-trigger-padding: var(--table-paginator-size-padding, 0 8px);
|
|
1572
1787
|
}
|
|
1573
1788
|
|
|
1574
1789
|
/* ── Toolbar (bulk actions) ─────────────────────────────────────────────── */
|
|
@@ -1603,4 +1818,107 @@
|
|
|
1603
1818
|
white-space: nowrap;
|
|
1604
1819
|
border-width: 0;
|
|
1605
1820
|
}
|
|
1821
|
+
|
|
1822
|
+
/* ── Mobile record cards (opt-in via `mobileCardLayout`) ──────────────────
|
|
1823
|
+
The label span is always in the DOM -- rendering it only under the media
|
|
1824
|
+
query below would mean an AT that queries computed style rather than
|
|
1825
|
+
layout (or a print stylesheet, or a future breakpoint change) sees no
|
|
1826
|
+
label at all until the exact 640px condition is re-evaluated. Keeping the
|
|
1827
|
+
element and toggling `display` is what lets `aria-hidden` on it (see the
|
|
1828
|
+
template) stay meaningful at every width: hidden today, but never a
|
|
1829
|
+
generated-content trick that could not carry that attribute in the first
|
|
1830
|
+
place. */
|
|
1831
|
+
.table-mobile-label {
|
|
1832
|
+
display: none;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
/* The breakpoint is a literal, not `var(--table-mobile-breakpoint)`: a
|
|
1836
|
+
custom property can't be read inside an `@media` condition (only inside
|
|
1837
|
+
a declaration value), so a themable breakpoint was never on the table --
|
|
1838
|
+
documented in docs/Table.md rather than silently unsupported. */
|
|
1839
|
+
@media (max-width: 640px) {
|
|
1840
|
+
/* `display: table` (and its `-row`/`-cell`/etc. companions) is what gives
|
|
1841
|
+
a table its IMPLICIT header/cell ARIA semantics -- switching every one
|
|
1842
|
+
of these to `block` for the stacked-card look throws that away, which
|
|
1843
|
+
is exactly why `mobileRole()` in the script restates it explicitly as
|
|
1844
|
+
`table`/`rowgroup`/`row`/`columnheader`/`cell` on the elements below.
|
|
1845
|
+
Without both halves this rule would silently turn every consumer's
|
|
1846
|
+
table into an AX-tree list the moment their viewport narrowed. */
|
|
1847
|
+
.table-mobile-cards table,
|
|
1848
|
+
.table-mobile-cards thead,
|
|
1849
|
+
.table-mobile-cards tbody,
|
|
1850
|
+
.table-mobile-cards tr,
|
|
1851
|
+
.table-mobile-cards th,
|
|
1852
|
+
.table-mobile-cards td {
|
|
1853
|
+
display: block;
|
|
1854
|
+
width: 100%;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
/* Headers are not shown as a row anymore -- their text now repeats per
|
|
1858
|
+
card as the `.table-mobile-label` beside each value -- but they stay
|
|
1859
|
+
in the DOM (not `display: none`) so `role="columnheader"` continues
|
|
1860
|
+
naming its `role="cell"` for any reading that still associates the
|
|
1861
|
+
two by DOM structure rather than only by the label span's text. */
|
|
1862
|
+
.table-mobile-cards thead {
|
|
1863
|
+
position: absolute;
|
|
1864
|
+
width: 1px;
|
|
1865
|
+
height: 1px;
|
|
1866
|
+
padding: 0;
|
|
1867
|
+
overflow: hidden;
|
|
1868
|
+
clip: rect(0, 0, 0, 0);
|
|
1869
|
+
clip-path: inset(50%);
|
|
1870
|
+
white-space: nowrap;
|
|
1871
|
+
border-width: 0;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/* The horizontal-scroll affordance this table uses at desktop width (see
|
|
1875
|
+
.table-scroll / .table-scroll-shell above) has nothing to scroll once
|
|
1876
|
+
every cell is a full-width block, and a lingering scrim would fade a
|
|
1877
|
+
card edge that no longer clips anything. */
|
|
1878
|
+
.table-mobile-cards .table-scroll {
|
|
1879
|
+
overflow-x: visible;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
.table-mobile-cards .table-scroll-shell::before,
|
|
1883
|
+
.table-mobile-cards .table-scroll-shell::after {
|
|
1884
|
+
display: none;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
.table-mobile-cards tbody tr.table-row {
|
|
1888
|
+
border: var(--table-mobile-card-border, 1px solid #e5e7eb);
|
|
1889
|
+
border-radius: var(
|
|
1890
|
+
--table-mobile-card-radius,
|
|
1891
|
+
var(--table-border-radius, var(--radius, 4px))
|
|
1892
|
+
);
|
|
1893
|
+
padding: var(--table-mobile-card-padding, 12px);
|
|
1894
|
+
margin-block-end: var(--table-mobile-card-gap, 12px);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
.table-mobile-cards tbody tr.table-row:last-child {
|
|
1898
|
+
margin-block-end: 0;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
/* Each cell becomes a label/value pair instead of a table column; the
|
|
1902
|
+
label span (hidden above) is the only thing `display: inline` turns
|
|
1903
|
+
back on inside this query. */
|
|
1904
|
+
.table-mobile-cards td.table-content {
|
|
1905
|
+
display: flex;
|
|
1906
|
+
align-items: baseline;
|
|
1907
|
+
gap: var(--table-mobile-label-gap, 8px);
|
|
1908
|
+
border: none;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
.table-mobile-cards .table-mobile-label {
|
|
1912
|
+
display: inline;
|
|
1913
|
+
flex: 0 0 var(--table-mobile-label-width, 40%);
|
|
1914
|
+
font-weight: var(--table-mobile-label-font-weight, 600);
|
|
1915
|
+
color: var(--table-mobile-label-color, #6b7280);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
.table-mobile-cards .table-mobile-value,
|
|
1919
|
+
.table-mobile-cards td.table-content > div {
|
|
1920
|
+
flex: 1 1 auto;
|
|
1921
|
+
min-width: 0;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1606
1924
|
</style>
|