@streamscloud/kit 0.76.5 → 0.78.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/dist/core/utils/color-helper.d.ts +10 -0
- package/dist/core/utils/color-helper.js +25 -1
- package/dist/ui/form-field/cmp.form-field-validatable.svelte +3 -3
- package/dist/ui/form-field/cmp.form-field-validatable.svelte.d.ts +4 -4
- package/dist/ui/table/cmp.table-draggable.svelte +56 -304
- package/dist/ui/table/cmp.table-draggable.svelte.d.ts +30 -19
- package/dist/ui/table/cmp.table.svelte +23 -264
- package/dist/ui/table/cmp.table.svelte.d.ts +32 -18
- package/dist/ui/table/table-cells/table-checkbox-cell.svelte +0 -1
- package/dist/ui/table/table-cells/table-move-row-cell.svelte +0 -2
- package/dist/ui/table/table-cells/table-text-cell.svelte +0 -1
- package/dist/ui/table/table-column-models.svelte.d.ts +5 -0
- package/dist/ui/table/table-column-models.svelte.js +6 -0
- package/dist/ui/table/table-core.svelte +407 -0
- package/dist/ui/table/table-core.svelte.d.ts +88 -0
- package/dist/ui/table/table-headers/table-header-sortable.svelte +1 -4
- package/dist/ui/table/table-headers/table-header.svelte +1 -1
- package/dist/ui/table/table-localization.d.ts +1 -0
- package/dist/ui/table/table-localization.js +7 -0
- package/dist/ui/table/table-model.svelte.js +9 -1
- package/dist/ui/table/types.d.ts +22 -1
- package/dist/ui/validatable/cmp.validatable.svelte +3 -4
- package/dist/ui/validatable/cmp.validatable.svelte.d.ts +6 -6
- package/dist/ui/validatable/cmp.validation-error.svelte +4 -4
- package/dist/ui/validatable/cmp.validation-error.svelte.d.ts +7 -6
- package/dist/ui/validatable/field-frame.svelte +25 -15
- package/dist/ui/validatable/field-frame.svelte.d.ts +8 -8
- package/dist/ui/validatable/index.d.ts +1 -0
- package/dist/ui/validatable/types.d.ts +7 -0
- package/dist/ui/validatable/types.js +1 -0
- package/package.json +1 -1
- package/dist/ui/table/_table-styles.scss +0 -158
- package/dist/ui/table/table-view.svelte.d.ts +0 -15
- package/dist/ui/table/table-view.svelte.js +0 -47
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
<script lang="ts" module>export const TABLE_ROW_FLIP_DURATION_MS = 200;
|
|
2
|
+
/**
|
|
3
|
+
* Restores the look of a dragged row's clone, which the drag library reparents to `<body>` — out of reach of the table's private vars and layout.
|
|
4
|
+
* `rowIndex` is the dragged row's position among the `<tbody>` rows.
|
|
5
|
+
*/
|
|
6
|
+
export const styleTableRowClone = (clone, tbody, rowIndex) => {
|
|
7
|
+
const table = tbody.closest('table');
|
|
8
|
+
const sourceRow = tbody.children[rowIndex];
|
|
9
|
+
if (!table || !sourceRow) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const sourceCells = sourceRow.querySelectorAll('td');
|
|
13
|
+
const cloneCells = clone.querySelectorAll('td');
|
|
14
|
+
if (sourceCells.length !== cloneCells.length) {
|
|
15
|
+
console.error('DraggableTable: dragged row column count does not match its source row');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
for (let i = 0, length = sourceCells.length; i < length; i++) {
|
|
19
|
+
cloneCells[i].style.width = `${sourceCells[i].clientWidth}px`;
|
|
20
|
+
cloneCells[i].style.height = `${sourceCells[i].clientHeight}px`;
|
|
21
|
+
}
|
|
22
|
+
const tableStyle = window.getComputedStyle(table);
|
|
23
|
+
for (const name of ['--_table--cell--font-size', '--_table--cell--padding-inline', '--_table--cell--padding-block', '--_table--cell--vertical-align']) {
|
|
24
|
+
clone.style.setProperty(name, tableStyle.getPropertyValue(name));
|
|
25
|
+
}
|
|
26
|
+
clone.style.setProperty('--_table--border-color', 'transparent');
|
|
27
|
+
clone.style.backgroundColor = tableStyle.getPropertyValue('--_table--row--alternative-color');
|
|
28
|
+
clone.style.outline = 'none';
|
|
29
|
+
};
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<script lang="ts" generics="T extends {id: string}">import { DynamicComponent } from '../dynamic-component';
|
|
33
|
+
import { EmptyState } from '../empty-state';
|
|
34
|
+
import { Icon } from '../icon';
|
|
35
|
+
import { TableCell, TableHideableCell } from './table-cells';
|
|
36
|
+
import { TableLocalization } from './table-localization';
|
|
37
|
+
import IconReorderDotsHorizontal from '@fluentui/svg-icons/icons/re_order_dots_horizontal_20_regular.svg?raw';
|
|
38
|
+
import { untrack } from 'svelte';
|
|
39
|
+
import { flip } from 'svelte/animate';
|
|
40
|
+
let { model, rows, showAlternativeView = false, noItemsPlaceholderDelay, reorder, on, alternativeView, noItemsPlaceholder, toolbar, groupHeader } = $props();
|
|
41
|
+
const localization = new TableLocalization();
|
|
42
|
+
$effect(() => {
|
|
43
|
+
const selection = model.selection;
|
|
44
|
+
return untrack(() => {
|
|
45
|
+
const subscriptions = [
|
|
46
|
+
selection.subscribeOnChange((values) => on?.selectionChanged?.(values)),
|
|
47
|
+
selection.subscribeOnAdd((values) => on?.itemsSelected?.(values)),
|
|
48
|
+
selection.subscribeOnRemove((values) => on?.itemsDeselected?.(values))
|
|
49
|
+
];
|
|
50
|
+
return () => subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
let showNoItemsPlaceholder = $state(false);
|
|
54
|
+
let placeholderTimeout = 0;
|
|
55
|
+
$effect(() => {
|
|
56
|
+
if (model.items.length || noItemsPlaceholder === false) {
|
|
57
|
+
clearTimeout(placeholderTimeout);
|
|
58
|
+
showNoItemsPlaceholder = false;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const delay = noItemsPlaceholderDelay ?? 0;
|
|
62
|
+
if (delay <= 0) {
|
|
63
|
+
clearTimeout(placeholderTimeout);
|
|
64
|
+
showNoItemsPlaceholder = true;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
placeholderTimeout = window.setTimeout(() => {
|
|
68
|
+
showNoItemsPlaceholder = !model.items.length;
|
|
69
|
+
}, delay);
|
|
70
|
+
return () => clearTimeout(placeholderTimeout);
|
|
71
|
+
});
|
|
72
|
+
const visibleColspan = $derived(model.columns.filter((column) => !column.hidden).length + (reorder ? 1 : 0));
|
|
73
|
+
const itemCount = $derived(rows.filter((row) => row.kind === 'item').length);
|
|
74
|
+
const showColumnHeader = $derived(!toolbar || model.items.length > 0);
|
|
75
|
+
const columnCellStyle = (column) => {
|
|
76
|
+
const { inline, block } = column.cellPadding;
|
|
77
|
+
return [
|
|
78
|
+
column.cssWidth ? `width: ${column.cssWidth};` : 'width: auto;',
|
|
79
|
+
column.minCssWidth ? `min-width: ${column.minCssWidth};` : '',
|
|
80
|
+
inline ? `--_table--cell--padding-inline: ${inline};` : '',
|
|
81
|
+
block ? `--_table--cell--padding-block: ${block};` : ''
|
|
82
|
+
]
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join(' ');
|
|
85
|
+
};
|
|
86
|
+
const rowState = (row) => {
|
|
87
|
+
const selected = model.selection.has(row.item.id);
|
|
88
|
+
const highlighted = model.highlightedItemIds.includes(row.item.id);
|
|
89
|
+
return {
|
|
90
|
+
selected,
|
|
91
|
+
highlighted,
|
|
92
|
+
zebra: model.styles?.zebra === true && row.index % 2 === 1 && !selected && !highlighted,
|
|
93
|
+
flushBottom: model.styles?.variant === 'card' && row.index === itemCount - 1
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
{#snippet noItemsContent()}
|
|
99
|
+
{#if noItemsPlaceholder}
|
|
100
|
+
{@render noItemsPlaceholder()}
|
|
101
|
+
{:else}
|
|
102
|
+
<EmptyState graphic="zero-content" title={localization.noItemsFoundTitle} description={localization.noItemsFoundBody} />
|
|
103
|
+
{/if}
|
|
104
|
+
{/snippet}
|
|
105
|
+
|
|
106
|
+
<div
|
|
107
|
+
class="table-container table-container--{model.styles?.size ?? 'md'} table-container--{model.styles?.variant ?? 'plain'}"
|
|
108
|
+
class:table-container--with-toolbar={!!toolbar}>
|
|
109
|
+
{#if toolbar}
|
|
110
|
+
<div class="table-container__toolbar">{@render toolbar()}</div>
|
|
111
|
+
{/if}
|
|
112
|
+
<div class="table-container__scroll">
|
|
113
|
+
{#if alternativeView && showAlternativeView}
|
|
114
|
+
{#if showNoItemsPlaceholder}
|
|
115
|
+
<div class="table-container__placeholder">{@render noItemsContent()}</div>
|
|
116
|
+
{:else}
|
|
117
|
+
{@render alternativeView()}
|
|
118
|
+
{/if}
|
|
119
|
+
{:else}
|
|
120
|
+
<table class="table" class:table--sticky-header={model.styles?.stickyHeader === true || !!toolbar} class:table--fixed={model.styles?.layout === 'fixed'}>
|
|
121
|
+
{#if showColumnHeader}
|
|
122
|
+
<thead class="table__head">
|
|
123
|
+
<tr>
|
|
124
|
+
{#if reorder}
|
|
125
|
+
<th class="table__th table__drag-handle"> </th>
|
|
126
|
+
{/if}
|
|
127
|
+
{#each model.columns as column (column)}
|
|
128
|
+
<TableHideableCell column={column}>
|
|
129
|
+
<th class="table__th" class:table__th--centered={column.centered} style={columnCellStyle(column)} title={column.title}>
|
|
130
|
+
<DynamicComponent model={column.headerView} />
|
|
131
|
+
</th>
|
|
132
|
+
</TableHideableCell>
|
|
133
|
+
{/each}
|
|
134
|
+
</tr>
|
|
135
|
+
</thead>
|
|
136
|
+
{/if}
|
|
137
|
+
<tbody {@attach reorder?.attachment}>
|
|
138
|
+
{#if showNoItemsPlaceholder}
|
|
139
|
+
<tr>
|
|
140
|
+
<td colspan={visibleColspan}>
|
|
141
|
+
<div class="table-container__placeholder">{@render noItemsContent()}</div>
|
|
142
|
+
</td>
|
|
143
|
+
</tr>
|
|
144
|
+
{:else}
|
|
145
|
+
{#each rows as row (row.kind === 'group' ? 'g:' + row.key : row.key)}
|
|
146
|
+
{@const state = row.kind === 'item' ? rowState(row) : null}
|
|
147
|
+
<tr
|
|
148
|
+
animate:flip={{ duration: TABLE_ROW_FLIP_DURATION_MS }}
|
|
149
|
+
id={row.kind === 'item' ? row.item.id : undefined}
|
|
150
|
+
class:table__tr={row.kind === 'item'}
|
|
151
|
+
class:table__tr--selected={state?.selected}
|
|
152
|
+
class:table__tr--highlighted={state?.highlighted}
|
|
153
|
+
class:table__tr--zebra={state?.zebra}>
|
|
154
|
+
{#if row.kind === 'group'}
|
|
155
|
+
<td class="table__group-header" colspan={visibleColspan}>
|
|
156
|
+
{#if groupHeader}
|
|
157
|
+
{@render groupHeader({ key: row.key, label: row.label, count: row.count })}
|
|
158
|
+
{:else}
|
|
159
|
+
{row.label}
|
|
160
|
+
{/if}
|
|
161
|
+
</td>
|
|
162
|
+
{:else}
|
|
163
|
+
{#if reorder}
|
|
164
|
+
<td class="table__td table__drag-handle" class:table__td--flush={state?.flushBottom}>
|
|
165
|
+
<div class="table__drag-handle-grip" {@attach reorder.handleAttachment}>
|
|
166
|
+
<Icon src={IconReorderDotsHorizontal} />
|
|
167
|
+
</div>
|
|
168
|
+
</td>
|
|
169
|
+
{/if}
|
|
170
|
+
{#each model.columns as column (column)}
|
|
171
|
+
<TableHideableCell column={column}>
|
|
172
|
+
<td
|
|
173
|
+
class="table__td"
|
|
174
|
+
class:table__td--centered={column.centered}
|
|
175
|
+
class:table__td--clickable={!!column.onClick}
|
|
176
|
+
class:table__td--flush={state?.flushBottom}
|
|
177
|
+
style={columnCellStyle(column)}
|
|
178
|
+
onclick={() => column.onClick && column.onClick(row.item)}>
|
|
179
|
+
<div class="table__cell" class:table__cell--reveal-on-row-hover={column.revealOnRowHover}>
|
|
180
|
+
<TableCell model={model} item={row.item} column={column} itemIndex={row.index} on={{ itemsReordered: on?.itemsReordered }} />
|
|
181
|
+
</div>
|
|
182
|
+
</td>
|
|
183
|
+
</TableHideableCell>
|
|
184
|
+
{/each}
|
|
185
|
+
{/if}
|
|
186
|
+
</tr>
|
|
187
|
+
{/each}
|
|
188
|
+
{/if}
|
|
189
|
+
</tbody>
|
|
190
|
+
</table>
|
|
191
|
+
{/if}
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<!--
|
|
196
|
+
@component
|
|
197
|
+
Internal rendering core shared by `Table` and `DraggableTable`: container, header, rows and cells. Each public wrapper owns its
|
|
198
|
+
props and hands the core finished display rows, plus a group-header snippet (`Table`) or the row reorder wiring (`DraggableTable`).
|
|
199
|
+
|
|
200
|
+
Not exported from `index.ts` — for kit-internal use only.
|
|
201
|
+
-->
|
|
202
|
+
|
|
203
|
+
<style>.table-container {
|
|
204
|
+
--_table--row--height: var(--sc-kit--table--row--height, var(--_table--size-row-height));
|
|
205
|
+
--_table--cell--font-size: var(--sc-kit--table--cell--font-size, var(--_table--size-cell-font-size));
|
|
206
|
+
--_table--cell--padding-inline: var(--sc-kit--table--cell--padding-inline, var(--_table--size-cell-padding));
|
|
207
|
+
--_table--cell--padding-block: var(--sc-kit--table--cell--padding-block, 0);
|
|
208
|
+
--_table--cell--vertical-align: var(--sc-kit--table--cell--vertical-align, middle);
|
|
209
|
+
--_table--head--height: var(--sc-kit--table--head--height, var(--_table--size-head-height, var(--_table--row--height)));
|
|
210
|
+
--_table--head--background-default: var(--sc-kit--color--bg--field-alt);
|
|
211
|
+
--_table--head--background-color: var(--sc-kit--table--head--background-color, var(--_table--head--background-default));
|
|
212
|
+
--_table--toolbar--background-color: var(--sc-kit--table--toolbar--background-color, var(--sc-kit--color--bg--field-alt));
|
|
213
|
+
--_table--toolbar--padding-block: var(--sc-kit--table--toolbar--padding-block, var(--sc-kit--space--3));
|
|
214
|
+
--_table--toolbar--padding-inline: var(--sc-kit--table--toolbar--padding-inline, var(--_table--cell--padding-inline));
|
|
215
|
+
--_table--head--color: var(--sc-kit--table--head--color, var(--sc-kit--color--text--secondary));
|
|
216
|
+
--_table--head--font-weight: var(--sc-kit--table--head--font-weight, var(--sc-kit--font-weight--semibold));
|
|
217
|
+
--_table--head--letter-spacing: var(--sc-kit--table--head--letter-spacing, normal);
|
|
218
|
+
--_table--head--text-transform: var(--sc-kit--table--head--text-transform, none);
|
|
219
|
+
--_table--border-color: var(--sc-kit--table--border-color, var(--sc-kit--color--border));
|
|
220
|
+
--_table--row--color: var(--sc-kit--table--row--color, transparent);
|
|
221
|
+
--_table--row--alternative-color: var(--sc-kit--table--row--alternative-color, var(--sc-kit--color--bg--hover));
|
|
222
|
+
--_table--zebra--color: var(--sc-kit--table--zebra--color, light-dark(#fcfcfd, #232323));
|
|
223
|
+
--_table--selected--color: var(--sc-kit--table--selected--color, var(--sc-kit--color--accent--softer));
|
|
224
|
+
--_table--selected--color-hover: var(--sc-kit--table--selected--color--hover, var(--sc-kit--color--accent--soft));
|
|
225
|
+
--_table--card--background: var(--sc-kit--table--card--background, var(--sc-kit--color--bg--panel));
|
|
226
|
+
--_table--card--border-color: var(--sc-kit--table--card--border-color, var(--sc-kit--color--border));
|
|
227
|
+
--_table--card--border-radius: var(--sc-kit--table--card--border-radius, var(--sc-kit--radius--lg));
|
|
228
|
+
--_table--card--padding-bottom: var(--sc-kit--table--card--padding-bottom, var(--sc-kit--space--4));
|
|
229
|
+
--_table--min-width: var(--sc-kit--table--min-width, auto);
|
|
230
|
+
--_table--body--background-color: var(--sc-kit--color--bg--panel);
|
|
231
|
+
--_table--scroll--padding-bottom: 0;
|
|
232
|
+
display: flex;
|
|
233
|
+
flex-direction: column;
|
|
234
|
+
height: 100%;
|
|
235
|
+
}
|
|
236
|
+
.table-container--md {
|
|
237
|
+
--_table--size-row-height: 2.75rem;
|
|
238
|
+
--_table--size-head-height: 2.5rem;
|
|
239
|
+
--_table--size-cell-font-size: var(--sc-kit--font-size--md);
|
|
240
|
+
--_table--size-cell-padding: var(--sc-kit--space--4);
|
|
241
|
+
}
|
|
242
|
+
.table-container--sm {
|
|
243
|
+
--_table--size-row-height: 2.25rem;
|
|
244
|
+
--_table--size-head-height: 2rem;
|
|
245
|
+
--_table--size-cell-font-size: var(--sc-kit--font-size--sm);
|
|
246
|
+
--_table--size-cell-padding: var(--sc-kit--space--3);
|
|
247
|
+
}
|
|
248
|
+
.table-container--card {
|
|
249
|
+
--_table--body--background-color: var(--_table--card--background);
|
|
250
|
+
--_table--scroll--padding-bottom: var(--_table--card--padding-bottom);
|
|
251
|
+
background: var(--_table--card--background);
|
|
252
|
+
border: 1px solid var(--_table--card--border-color);
|
|
253
|
+
border-radius: var(--_table--card--border-radius);
|
|
254
|
+
overflow: hidden;
|
|
255
|
+
}
|
|
256
|
+
.table-container--with-toolbar {
|
|
257
|
+
--_table--head--background-default: var(--_table--body--background-color);
|
|
258
|
+
}
|
|
259
|
+
.table-container__toolbar {
|
|
260
|
+
flex: none;
|
|
261
|
+
padding: var(--_table--toolbar--padding-block) var(--_table--toolbar--padding-inline);
|
|
262
|
+
background: var(--_table--toolbar--background-color);
|
|
263
|
+
border-bottom: 1px solid var(--_table--border-color);
|
|
264
|
+
}
|
|
265
|
+
.table-container__scroll {
|
|
266
|
+
display: flex;
|
|
267
|
+
flex: 1 1 auto;
|
|
268
|
+
min-height: 0;
|
|
269
|
+
padding-bottom: var(--_table--scroll--padding-bottom);
|
|
270
|
+
overflow: auto;
|
|
271
|
+
--_cross-browser-scrollbar--thumb-color: var(--scrollbar--thumb-color, light-dark(#d1d5db, #4b5563));
|
|
272
|
+
--_cross-browser-scrollbar--track-color: var(--scrollbar--track-color, transparent);
|
|
273
|
+
}
|
|
274
|
+
.table-container__scroll::-webkit-scrollbar {
|
|
275
|
+
width: 6px;
|
|
276
|
+
height: 6px;
|
|
277
|
+
}
|
|
278
|
+
.table-container__scroll::-webkit-scrollbar-track {
|
|
279
|
+
background: var(--_cross-browser-scrollbar--track-color);
|
|
280
|
+
border-radius: 100vw;
|
|
281
|
+
}
|
|
282
|
+
.table-container__scroll::-webkit-scrollbar-thumb {
|
|
283
|
+
background: var(--_cross-browser-scrollbar--thumb-color);
|
|
284
|
+
border-radius: 100vw;
|
|
285
|
+
}
|
|
286
|
+
@supports (scrollbar-color: transparent transparent) {
|
|
287
|
+
.table-container__scroll {
|
|
288
|
+
scrollbar-color: var(--_cross-browser-scrollbar--thumb-color) var(--_cross-browser-scrollbar--track-color);
|
|
289
|
+
scrollbar-width: thin;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
.table-container__placeholder {
|
|
293
|
+
width: 100%;
|
|
294
|
+
display: flex;
|
|
295
|
+
align-items: center;
|
|
296
|
+
justify-content: center;
|
|
297
|
+
font-size: 80%;
|
|
298
|
+
user-select: none;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.table {
|
|
302
|
+
width: 100%;
|
|
303
|
+
min-width: var(--_table--min-width);
|
|
304
|
+
table-layout: auto;
|
|
305
|
+
height: fit-content;
|
|
306
|
+
border-collapse: separate;
|
|
307
|
+
border-spacing: 0;
|
|
308
|
+
}
|
|
309
|
+
.table--fixed {
|
|
310
|
+
table-layout: fixed;
|
|
311
|
+
}
|
|
312
|
+
.table__head {
|
|
313
|
+
height: var(--_table--head--height);
|
|
314
|
+
max-height: var(--_table--head--height);
|
|
315
|
+
background: var(--_table--head--background-color);
|
|
316
|
+
color: var(--_table--head--color);
|
|
317
|
+
font-size: var(--sc-kit--table--head--font-size, var(--sc-kit--font-size--sm));
|
|
318
|
+
letter-spacing: var(--_table--head--letter-spacing);
|
|
319
|
+
text-transform: var(--_table--head--text-transform);
|
|
320
|
+
}
|
|
321
|
+
.table__th {
|
|
322
|
+
padding: 0 var(--_table--cell--padding-inline);
|
|
323
|
+
font-weight: var(--_table--head--font-weight);
|
|
324
|
+
text-align: left;
|
|
325
|
+
vertical-align: middle;
|
|
326
|
+
border-bottom: 1px solid var(--_table--border-color);
|
|
327
|
+
-webkit-user-drag: none;
|
|
328
|
+
user-select: none;
|
|
329
|
+
}
|
|
330
|
+
.table__th :global([contenteditable]) {
|
|
331
|
+
user-select: text;
|
|
332
|
+
}
|
|
333
|
+
.table__th--centered {
|
|
334
|
+
--_--table-header--justify-content: center;
|
|
335
|
+
}
|
|
336
|
+
.table__tr {
|
|
337
|
+
height: var(--_table--row--height);
|
|
338
|
+
max-height: var(--_table--row--height);
|
|
339
|
+
background: var(--_table--row--color);
|
|
340
|
+
}
|
|
341
|
+
.table__tr--zebra {
|
|
342
|
+
background: var(--_table--zebra--color);
|
|
343
|
+
}
|
|
344
|
+
.table__tr:hover, .table__tr--highlighted {
|
|
345
|
+
background: var(--_table--row--alternative-color);
|
|
346
|
+
}
|
|
347
|
+
.table__tr:hover, .table__tr:focus-within {
|
|
348
|
+
--_table--row-reveal--opacity: 1;
|
|
349
|
+
}
|
|
350
|
+
@media (any-hover: none) {
|
|
351
|
+
.table__tr {
|
|
352
|
+
--_table--row-reveal--opacity: 1;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
.table__tr--selected {
|
|
356
|
+
background: var(--_table--selected--color);
|
|
357
|
+
}
|
|
358
|
+
.table__tr--selected:hover {
|
|
359
|
+
background: var(--_table--selected--color-hover);
|
|
360
|
+
}
|
|
361
|
+
.table__td {
|
|
362
|
+
padding: 0;
|
|
363
|
+
border-bottom: 1px solid var(--_table--border-color);
|
|
364
|
+
font-size: var(--_table--cell--font-size);
|
|
365
|
+
color: var(--sc-kit--color--text--primary);
|
|
366
|
+
vertical-align: var(--_table--cell--vertical-align);
|
|
367
|
+
}
|
|
368
|
+
.table__td--centered {
|
|
369
|
+
text-align: center;
|
|
370
|
+
}
|
|
371
|
+
.table__td--clickable {
|
|
372
|
+
cursor: pointer;
|
|
373
|
+
}
|
|
374
|
+
.table__td--flush {
|
|
375
|
+
border-bottom: none;
|
|
376
|
+
}
|
|
377
|
+
.table__cell {
|
|
378
|
+
padding: var(--_table--cell--padding-block) var(--_table--cell--padding-inline);
|
|
379
|
+
}
|
|
380
|
+
.table__cell--reveal-on-row-hover {
|
|
381
|
+
opacity: var(--_table--row-reveal--opacity, 0);
|
|
382
|
+
transition: opacity var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
383
|
+
}
|
|
384
|
+
.table__group-header {
|
|
385
|
+
padding-block: var(--sc-kit--space--6) var(--sc-kit--space--2);
|
|
386
|
+
padding-inline: var(--_table--cell--padding-inline);
|
|
387
|
+
font-size: var(--sc-kit--font-size--sm);
|
|
388
|
+
font-weight: var(--sc-kit--font-weight--semibold);
|
|
389
|
+
color: var(--sc-kit--color--text--secondary);
|
|
390
|
+
}
|
|
391
|
+
.table__drag-handle {
|
|
392
|
+
width: 3.25rem;
|
|
393
|
+
padding: 0;
|
|
394
|
+
--sc-kit--icon--size: 1.25rem;
|
|
395
|
+
--sc-kit--icon--color: var(--sc-kit--color--text--muted);
|
|
396
|
+
}
|
|
397
|
+
.table__drag-handle-grip {
|
|
398
|
+
display: flex;
|
|
399
|
+
align-items: center;
|
|
400
|
+
justify-content: center;
|
|
401
|
+
min-height: var(--_table--row--height);
|
|
402
|
+
}
|
|
403
|
+
.table--sticky-header thead {
|
|
404
|
+
position: sticky;
|
|
405
|
+
top: 0;
|
|
406
|
+
z-index: 1;
|
|
407
|
+
}</style>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Attachment } from 'svelte/attachments';
|
|
2
|
+
export type TableDisplayRow<T> = {
|
|
3
|
+
kind: 'group';
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
count: number;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'item';
|
|
9
|
+
/** Keyed-each identity of the row. */
|
|
10
|
+
key: unknown;
|
|
11
|
+
item: T;
|
|
12
|
+
/** Position among the item rows — drives zebra striping, the flush last row and move-row bounds. */
|
|
13
|
+
index: number;
|
|
14
|
+
};
|
|
15
|
+
/** Rows reordered by dragging their handle: the core renders the handle column, the caller wires the reordering. */
|
|
16
|
+
export type TableRowReorder = {
|
|
17
|
+
attachment: Attachment<HTMLElement>;
|
|
18
|
+
handleAttachment: Attachment<HTMLElement>;
|
|
19
|
+
};
|
|
20
|
+
export declare const TABLE_ROW_FLIP_DURATION_MS = 200;
|
|
21
|
+
/**
|
|
22
|
+
* Restores the look of a dragged row's clone, which the drag library reparents to `<body>` — out of reach of the table's private vars and layout.
|
|
23
|
+
* `rowIndex` is the dragged row's position among the `<tbody>` rows.
|
|
24
|
+
*/
|
|
25
|
+
export declare const styleTableRowClone: (clone: HTMLElement, tbody: HTMLElement, rowIndex: number) => void;
|
|
26
|
+
import type { TableModel } from './table-model.svelte';
|
|
27
|
+
import { type Snippet } from 'svelte';
|
|
28
|
+
declare function $$render<T extends {
|
|
29
|
+
id: string;
|
|
30
|
+
}>(): {
|
|
31
|
+
props: {
|
|
32
|
+
model: TableModel<T>;
|
|
33
|
+
rows: TableDisplayRow<T>[];
|
|
34
|
+
showAlternativeView?: boolean;
|
|
35
|
+
noItemsPlaceholderDelay?: number;
|
|
36
|
+
reorder?: TableRowReorder;
|
|
37
|
+
on?: {
|
|
38
|
+
selectionChanged?: (e: T[]) => void;
|
|
39
|
+
itemsSelected?: (e: T[]) => void;
|
|
40
|
+
itemsDeselected?: (e: T[]) => void;
|
|
41
|
+
itemsReordered?: (e: T[]) => void;
|
|
42
|
+
};
|
|
43
|
+
alternativeView?: Snippet;
|
|
44
|
+
noItemsPlaceholder?: Snippet | false;
|
|
45
|
+
toolbar?: Snippet;
|
|
46
|
+
/** Custom rendering of a group row; the label is rendered as text without it. */
|
|
47
|
+
groupHeader?: Snippet<[{
|
|
48
|
+
key: string;
|
|
49
|
+
label: string;
|
|
50
|
+
count: number;
|
|
51
|
+
}]>;
|
|
52
|
+
};
|
|
53
|
+
exports: {};
|
|
54
|
+
bindings: "";
|
|
55
|
+
slots: {};
|
|
56
|
+
events: {};
|
|
57
|
+
};
|
|
58
|
+
declare class __sveltets_Render<T extends {
|
|
59
|
+
id: string;
|
|
60
|
+
}> {
|
|
61
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
62
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
63
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
64
|
+
bindings(): "";
|
|
65
|
+
exports(): {};
|
|
66
|
+
}
|
|
67
|
+
interface $$IsomorphicComponent {
|
|
68
|
+
new <T extends {
|
|
69
|
+
id: string;
|
|
70
|
+
}>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
71
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
72
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
73
|
+
<T extends {
|
|
74
|
+
id: string;
|
|
75
|
+
}>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
76
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Internal rendering core shared by `Table` and `DraggableTable`: container, header, rows and cells. Each public wrapper owns its
|
|
80
|
+
* props and hands the core finished display rows, plus a group-header snippet (`Table`) or the row reorder wiring (`DraggableTable`).
|
|
81
|
+
*
|
|
82
|
+
* Not exported from `index.ts` — for kit-internal use only.
|
|
83
|
+
*/
|
|
84
|
+
declare const TableCore: $$IsomorphicComponent;
|
|
85
|
+
type TableCore<T extends {
|
|
86
|
+
id: string;
|
|
87
|
+
}> = InstanceType<typeof TableCore<T>>;
|
|
88
|
+
export default TableCore;
|
|
@@ -18,7 +18,7 @@ const changeSort = (direction) => {
|
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<TableHeader column={column}>
|
|
21
|
-
<div class="th-sortable"
|
|
21
|
+
<div class="th-sortable">
|
|
22
22
|
{@render children?.()}
|
|
23
23
|
<span> </span>
|
|
24
24
|
<Popover position="bottom-start">
|
|
@@ -72,9 +72,6 @@ const changeSort = (direction) => {
|
|
|
72
72
|
align-items: center;
|
|
73
73
|
justify-content: flex-start;
|
|
74
74
|
}
|
|
75
|
-
.th-sortable--centered {
|
|
76
|
-
justify-content: center;
|
|
77
|
-
}
|
|
78
75
|
.th-sortable__title, .th-sortable__marker {
|
|
79
76
|
color: inherit;
|
|
80
77
|
display: inline-block;
|
|
@@ -6,6 +6,9 @@ export class TableLocalization {
|
|
|
6
6
|
get noItemsFoundBody() {
|
|
7
7
|
return loc.noItemsFoundBody[AppLocale.current];
|
|
8
8
|
}
|
|
9
|
+
get actionsColumnTitle() {
|
|
10
|
+
return loc.actionsColumnTitle[AppLocale.current];
|
|
11
|
+
}
|
|
9
12
|
}
|
|
10
13
|
const loc = {
|
|
11
14
|
noItemsFoundTitle: {
|
|
@@ -15,5 +18,9 @@ const loc = {
|
|
|
15
18
|
noItemsFoundBody: {
|
|
16
19
|
en: 'No data found. Create one to get started',
|
|
17
20
|
no: 'Ingen data funnet. Opprett en for å komme i gang'
|
|
21
|
+
},
|
|
22
|
+
actionsColumnTitle: {
|
|
23
|
+
en: 'Actions',
|
|
24
|
+
no: 'Handlinger'
|
|
18
25
|
}
|
|
19
26
|
};
|
|
@@ -3,6 +3,7 @@ import { createRepository } from '../../core/repository';
|
|
|
3
3
|
import { DynamicComponentModel } from '../dynamic-component';
|
|
4
4
|
import { ResolvedColumn } from './table-column-models.svelte';
|
|
5
5
|
import { TableCheckboxHeader, TableHeader, TableHeaderSortable } from './table-headers';
|
|
6
|
+
import { TableLocalization } from './table-localization';
|
|
6
7
|
import { untrack } from 'svelte';
|
|
7
8
|
export class TableModel {
|
|
8
9
|
_init;
|
|
@@ -121,9 +122,16 @@ export class TableModel {
|
|
|
121
122
|
// Append actions column if needed
|
|
122
123
|
if (this._itemActions.length) {
|
|
123
124
|
const actionsColumn = new ResolvedColumn({ type: 'actions', centered: true, cssWidth: Css.em(75) });
|
|
125
|
+
const localization = new TableLocalization();
|
|
124
126
|
actionsColumn.headerView = new DynamicComponentModel({
|
|
125
127
|
component: TableHeader,
|
|
126
|
-
props: {
|
|
128
|
+
props: {
|
|
129
|
+
column: {
|
|
130
|
+
get title() {
|
|
131
|
+
return localization.actionsColumnTitle;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
127
135
|
});
|
|
128
136
|
result.push(actionsColumn);
|
|
129
137
|
}
|
package/dist/ui/table/types.d.ts
CHANGED
|
@@ -19,6 +19,16 @@ interface ITableColumnBase {
|
|
|
19
19
|
/** Floor the column will not shrink below, taken out of columns whose `cssWidth` is merely a suggestion. Use it on the column that must keep space. */
|
|
20
20
|
minCssWidth?: string;
|
|
21
21
|
centered?: boolean;
|
|
22
|
+
/** Hide the cell until its row is hovered or holds focus. Always visible on touch-only devices. @default true for `move-row`, false otherwise */
|
|
23
|
+
revealOnRowHover?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Overrides the table-wide cell padding for this column. `inline` applies to the header and body cells, `block` to body cells. Under the kit's
|
|
26
|
+
* border-box sizing `cssWidth` includes this padding.
|
|
27
|
+
*/
|
|
28
|
+
cellPadding?: {
|
|
29
|
+
inline?: string;
|
|
30
|
+
block?: string;
|
|
31
|
+
};
|
|
22
32
|
}
|
|
23
33
|
export interface ITableColumn<T> extends ITableColumnBase {
|
|
24
34
|
id: keyof T;
|
|
@@ -112,7 +122,11 @@ export type TableItemAction<T> = {
|
|
|
112
122
|
visibilityFactory?: (item: T) => boolean;
|
|
113
123
|
};
|
|
114
124
|
export type TableStyles = {
|
|
115
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* Stick the column header to the top of the table's own scroll area, so it only takes effect when the table is height-constrained. Off when `styles`
|
|
127
|
+
* is passed without it; a model created without `styles` at all gets `{ stickyHeader: true }`. Always on with a `toolbar`.
|
|
128
|
+
* @default false
|
|
129
|
+
*/
|
|
116
130
|
stickyHeader?: boolean;
|
|
117
131
|
/** Show the per-row actions popover above the row instead of below. @default false */
|
|
118
132
|
actionsPopoverTopDirection?: boolean;
|
|
@@ -122,6 +136,13 @@ export type TableStyles = {
|
|
|
122
136
|
zebra?: boolean;
|
|
123
137
|
/** Visual chrome around the table. `'plain'` blends with surrounding layout; `'card'` adds a bordered, rounded panel. @default 'plain' */
|
|
124
138
|
variant?: 'plain' | 'card';
|
|
139
|
+
/**
|
|
140
|
+
* Column width algorithm. `'auto'` treats `cssWidth` as a hint and lets content redistribute space. `'fixed'` honours `cssWidth` exactly and wraps or
|
|
141
|
+
* clips content instead of widening the column. Under `'fixed'`, percent columns summing to 100% share what the fixed columns leave in that ratio;
|
|
142
|
+
* `minCssWidth` does not apply — floor the whole table with `--sc-kit--table--min-width` instead.
|
|
143
|
+
* @default 'auto'
|
|
144
|
+
*/
|
|
145
|
+
layout?: 'auto' | 'fixed';
|
|
125
146
|
};
|
|
126
147
|
export type TableOrderByState<TOrder> = {
|
|
127
148
|
orderBy: TOrder;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
<script lang="ts" generics="T extends Record<string, unknown>, K extends keyof T & string">import { Utils } from '../../core/utils';
|
|
5
5
|
import { default as FieldFrame } from './field-frame.svelte';
|
|
6
|
-
let { handler, name, validateOn = ['input', 'change', 'blur'], debounceMs = 0, disableTouchedTracking = false,
|
|
6
|
+
let { handler, name, validateOn = ['input', 'change', 'blur'], debounceMs = 0, disableTouchedTracking = false, errorPlacement = 'overlay', on, children } = $props();
|
|
7
7
|
// True between a value change and the next completed validation. Hides the stale error in the
|
|
8
8
|
// UI without mutating handler.errors — the model keeps the last known result, the view just
|
|
9
9
|
// doesn't trust it until the validator has caught up.
|
|
@@ -64,7 +64,7 @@ const field = $derived({
|
|
|
64
64
|
});
|
|
65
65
|
</script>
|
|
66
66
|
|
|
67
|
-
<FieldFrame error={showErrors ? handler.errors[name] : null}
|
|
67
|
+
<FieldFrame error={showErrors ? handler.errors[name] : null} errorPlacement={errorPlacement}>
|
|
68
68
|
{@render children(field, pendingValidation)}
|
|
69
69
|
</FieldFrame>
|
|
70
70
|
|
|
@@ -72,8 +72,7 @@ const field = $derived({
|
|
|
72
72
|
@component
|
|
73
73
|
Binds a form field to any kit input via a render-prop snippet. The base inputs stay unchanged;
|
|
74
74
|
`Validatable` is the only place that knows about `FormValidationHandler`. Wraps children in an
|
|
75
|
-
internal `FieldFrame` that
|
|
76
|
-
layout; `reserveErrorSpace={false}` drops the reserve — the message overlays content below.
|
|
75
|
+
internal `FieldFrame` that renders the error message below the field, placed per `errorPlacement`.
|
|
77
76
|
|
|
78
77
|
### Usage
|
|
79
78
|
```svelte
|