@lyeve-labs/ui-kit 0.12.1 → 0.13.1
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/components/Autocomplete.svelte +191 -125
- package/dist/components/Autocomplete.svelte.d.ts +29 -8
- package/dist/components/Card.svelte +43 -2
- package/dist/components/Card.svelte.d.ts +24 -2
- package/dist/components/Checkbox.svelte +174 -63
- package/dist/components/Checkbox.svelte.d.ts +20 -3
- package/dist/components/CheckboxGroup.svelte +162 -0
- package/dist/components/CheckboxGroup.svelte.d.ts +51 -0
- package/dist/components/Collapsible.svelte +142 -0
- package/dist/components/Collapsible.svelte.d.ts +32 -0
- package/dist/components/CopyButton.svelte +126 -0
- package/dist/components/CopyButton.svelte.d.ts +14 -0
- package/dist/components/DatePicker.svelte +48 -6
- package/dist/components/DateTimePicker.svelte +337 -0
- package/dist/components/DateTimePicker.svelte.d.ts +26 -0
- package/dist/components/DescriptionList.svelte +78 -0
- package/dist/components/DescriptionList.svelte.d.ts +34 -0
- package/dist/components/Field.svelte +104 -0
- package/dist/components/Field.svelte.d.ts +46 -0
- package/dist/components/FileInput.svelte +5 -2
- package/dist/components/FormMessage.svelte +85 -0
- package/dist/components/FormMessage.svelte.d.ts +11 -0
- package/dist/components/Input.svelte +1 -1
- package/dist/components/Label.svelte +7 -1
- package/dist/components/Label.svelte.d.ts +6 -0
- package/dist/components/MultiSelect.svelte +199 -109
- package/dist/components/MultiSelect.svelte.d.ts +22 -9
- package/dist/components/NumberInput.svelte +8 -4
- package/dist/components/PageHeader.svelte +37 -4
- package/dist/components/PageHeader.svelte.d.ts +15 -0
- package/dist/components/PageShell.svelte +85 -0
- package/dist/components/PageShell.svelte.d.ts +38 -0
- package/dist/components/Pagination.svelte +43 -7
- package/dist/components/Panel.svelte +101 -0
- package/dist/components/Panel.svelte.d.ts +39 -0
- package/dist/components/PasswordInput.svelte +139 -0
- package/dist/components/PasswordInput.svelte.d.ts +29 -0
- package/dist/components/Radio.svelte +152 -35
- package/dist/components/Radio.svelte.d.ts +16 -1
- package/dist/components/RadioGroup.svelte +118 -71
- package/dist/components/RadioGroup.svelte.d.ts +39 -9
- package/dist/components/SectionHeading.svelte +39 -0
- package/dist/components/SectionHeading.svelte.d.ts +21 -0
- package/dist/components/SegmentedControl.svelte +194 -0
- package/dist/components/SegmentedControl.svelte.d.ts +55 -0
- package/dist/components/Select.svelte +471 -46
- package/dist/components/Select.svelte.d.ts +95 -6
- package/dist/components/SidebarNav.svelte +264 -0
- package/dist/components/SidebarNav.svelte.d.ts +17 -0
- package/dist/components/Stat.svelte +53 -2
- package/dist/components/Stat.svelte.d.ts +31 -0
- package/dist/components/Textarea.svelte +1 -1
- package/dist/components/TimePicker.svelte +480 -0
- package/dist/components/TimePicker.svelte.d.ts +23 -0
- package/dist/components/Toolbar.svelte +39 -0
- package/dist/components/Toolbar.svelte.d.ts +26 -0
- package/dist/components/TreeView.svelte +339 -0
- package/dist/components/TreeView.svelte.d.ts +37 -0
- package/dist/index.d.ts +25 -1
- package/dist/index.js +20 -1
- package/dist/internal/calendar.d.ts +119 -0
- package/dist/internal/calendar.js +225 -0
- package/dist/internal/choice.d.ts +136 -0
- package/dist/internal/choice.js +179 -0
- package/dist/internal/field.d.ts +39 -0
- package/dist/internal/field.js +48 -0
- package/dist/internal/filter.d.ts +80 -0
- package/dist/internal/filter.js +80 -0
- package/dist/internal/layout.d.ts +119 -0
- package/dist/internal/layout.js +132 -0
- package/dist/internal/listbox.svelte.d.ts +77 -0
- package/dist/internal/listbox.svelte.js +438 -0
- package/dist/internal/nav-expansion.svelte.d.ts +36 -0
- package/dist/internal/nav-expansion.svelte.js +144 -0
- package/dist/internal/nav-tree.d.ts +68 -0
- package/dist/internal/nav-tree.js +102 -0
- package/dist/internal/panel.d.ts +100 -0
- package/dist/internal/panel.js +109 -0
- package/dist/internal/rollup.d.ts +52 -0
- package/dist/internal/rollup.js +67 -0
- package/dist/internal/time.d.ts +103 -0
- package/dist/internal/time.js +166 -0
- package/dist/internal/tree.d.ts +86 -0
- package/dist/internal/tree.js +111 -0
- package/dist/styles/theme.css +18 -10
- package/package.json +4 -2
- package/src/lib/styles/theme.css +18 -10
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
<script lang="ts" generics="T = unknown">
|
|
2
|
+
import { ChevronRight } from '@lucide/svelte';
|
|
3
|
+
import { CHOICE_MARK, choiceBox } from '../internal/choice.js';
|
|
4
|
+
import {
|
|
5
|
+
branchState,
|
|
6
|
+
flattenTree,
|
|
7
|
+
isBranch,
|
|
8
|
+
toggleSubtree,
|
|
9
|
+
type TreeNode,
|
|
10
|
+
type TriState,
|
|
11
|
+
} from '../internal/tree.js';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
nodes: TreeNode<T>[];
|
|
15
|
+
/** Required. An unnamed tree announces as just "tree". */
|
|
16
|
+
label: string;
|
|
17
|
+
expanded?: string[];
|
|
18
|
+
selected?: string;
|
|
19
|
+
/** Checked LEAF ids only. A branch's state is always derived, never stored, so the two cannot disagree. */
|
|
20
|
+
checked?: string[];
|
|
21
|
+
checkable?: boolean;
|
|
22
|
+
class?: string;
|
|
23
|
+
onactivate?: (node: TreeNode<T>) => void;
|
|
24
|
+
oncheck?: (checked: string[]) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
nodes,
|
|
29
|
+
label,
|
|
30
|
+
expanded = $bindable([]),
|
|
31
|
+
selected = $bindable(undefined),
|
|
32
|
+
checked = $bindable([]),
|
|
33
|
+
checkable = false,
|
|
34
|
+
class: klass = '',
|
|
35
|
+
onactivate,
|
|
36
|
+
oncheck,
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
let rootEl: HTMLUListElement | undefined = $state();
|
|
40
|
+
|
|
41
|
+
/** The row the roving tabindex sits on. Undefined until the user lands somewhere. */
|
|
42
|
+
let activeId: string | undefined = $state(undefined);
|
|
43
|
+
|
|
44
|
+
const expandedSet = $derived(new Set(expanded));
|
|
45
|
+
const checkedSet = $derived(new Set(checked));
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The visible rows, which is both the paint order and the arrow-key order.
|
|
49
|
+
* A collapsed branch contributes no children here, so ArrowDown cannot land
|
|
50
|
+
* on a row the user cannot see.
|
|
51
|
+
*/
|
|
52
|
+
const rows = $derived(flattenTree(nodes, expandedSet));
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Exactly one row is in the tab sequence, and the arrows move it.
|
|
56
|
+
*
|
|
57
|
+
* A tree is one stop, not one stop per row: a hundred-node tree with a
|
|
58
|
+
* tabbable row each is a hundred presses to get past. The fallback chain
|
|
59
|
+
* matters as much as the rule. The active row can be gone after a collapse
|
|
60
|
+
* or a data change, so the tabbable row falls back to the selected row and
|
|
61
|
+
* then to the first, and a tree with no row the keyboard can reach is a tree
|
|
62
|
+
* nobody can enter.
|
|
63
|
+
*/
|
|
64
|
+
const tabbableId = $derived(
|
|
65
|
+
rows.find((r) => r.node.id === activeId)?.node.id ??
|
|
66
|
+
rows.find((r) => r.node.id === selected)?.node.id ??
|
|
67
|
+
rows[0]?.node.id,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
/** Typeahead buffer. Not state: nothing renders it, and it clears on a timer. */
|
|
71
|
+
let typed = '';
|
|
72
|
+
let typedTimer: ReturnType<typeof setTimeout> | undefined;
|
|
73
|
+
|
|
74
|
+
$effect(() => () => clearTimeout(typedTimer));
|
|
75
|
+
|
|
76
|
+
function ariaChecked(state: TriState): 'true' | 'false' | 'mixed' {
|
|
77
|
+
if (state === 'all') return 'true';
|
|
78
|
+
return state === 'some' ? 'mixed' : 'false';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function rowPaint(isSelected: boolean, disabled: boolean): string {
|
|
82
|
+
// One utility per property in each branch. A resting `hover:bg-surface-2`
|
|
83
|
+
// that a selected row overrides with `bg-surface-2` resolves by the order
|
|
84
|
+
// Tailwind emits the two, not by the order they are written here.
|
|
85
|
+
if (disabled) return 'cursor-not-allowed opacity-50';
|
|
86
|
+
return isSelected
|
|
87
|
+
? 'cursor-pointer bg-surface-2 font-medium text-brand'
|
|
88
|
+
: 'cursor-pointer hover:bg-surface-2';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function rowElement(id: string): HTMLElement | undefined {
|
|
92
|
+
if (!rootEl) return undefined;
|
|
93
|
+
return [...rootEl.querySelectorAll<HTMLElement>('[role="treeitem"]')].find(
|
|
94
|
+
(el) => el.dataset.treeId === id,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function focusRow(id: string | undefined): void {
|
|
99
|
+
if (id === undefined) return;
|
|
100
|
+
activeId = id;
|
|
101
|
+
rowElement(id)?.focus();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function setOpen(node: TreeNode<T>, open: boolean): void {
|
|
105
|
+
if (!isBranch(node)) return;
|
|
106
|
+
const next = new Set(expanded);
|
|
107
|
+
if (open) next.add(node.id);
|
|
108
|
+
else next.delete(node.id);
|
|
109
|
+
expanded = [...next];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function toggleOpen(node: TreeNode<T>): void {
|
|
113
|
+
setOpen(node, !expandedSet.has(node.id));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function activate(node: TreeNode<T>): void {
|
|
117
|
+
if (node.disabled) return;
|
|
118
|
+
selected = node.id;
|
|
119
|
+
onactivate?.(node);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function toggleCheck(node: TreeNode<T>): void {
|
|
123
|
+
if (!checkable || node.disabled) return;
|
|
124
|
+
const next = new Set(checked);
|
|
125
|
+
const { add, remove } = toggleSubtree(node, next);
|
|
126
|
+
// A branch with no enabled leaf has nothing to write, and reporting a
|
|
127
|
+
// change that did not happen is how a consumer ends up saving a list it was
|
|
128
|
+
// never given.
|
|
129
|
+
if (add.length === 0 && remove.length === 0) return;
|
|
130
|
+
for (const id of add) next.add(id);
|
|
131
|
+
for (const id of remove) next.delete(id);
|
|
132
|
+
checked = [...next];
|
|
133
|
+
oncheck?.(checked);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function typeahead(key: string, fromId: string): void {
|
|
137
|
+
clearTimeout(typedTimer);
|
|
138
|
+
typedTimer = setTimeout(() => {
|
|
139
|
+
typed = '';
|
|
140
|
+
}, 500);
|
|
141
|
+
typed += key.toLowerCase();
|
|
142
|
+
|
|
143
|
+
const start = rows.findIndex((r) => r.node.id === fromId);
|
|
144
|
+
// A single character looks past the current row, so pressing it again walks
|
|
145
|
+
// the matches. A longer buffer includes the current row, or refining a
|
|
146
|
+
// search on the row it already found would jump off it.
|
|
147
|
+
const from = typed.length === 1 ? start + 1 : start;
|
|
148
|
+
for (let step = 0; step < rows.length; step++) {
|
|
149
|
+
const candidate = rows[(from + step + rows.length) % rows.length];
|
|
150
|
+
if (candidate.node.label.toLowerCase().startsWith(typed)) {
|
|
151
|
+
focusRow(candidate.node.id);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function onRowClick(e: MouseEvent, node: TreeNode<T>): void {
|
|
158
|
+
const target = e.target as Element | null;
|
|
159
|
+
// Every row is nested inside its ancestors, so a click on a child bubbles
|
|
160
|
+
// through all of them. A row answers only for a target that is its own.
|
|
161
|
+
if (!target || target.closest('[role="treeitem"]') !== e.currentTarget) return;
|
|
162
|
+
|
|
163
|
+
activeId = node.id;
|
|
164
|
+
if (target.closest('[data-tree-toggle]')) {
|
|
165
|
+
toggleOpen(node);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (checkable && target.closest('[data-tree-check]')) {
|
|
169
|
+
toggleCheck(node);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// A branch that does not open when its row is clicked reads as broken, so
|
|
173
|
+
// the row body opens it as well as activating it.
|
|
174
|
+
if (isBranch(node)) toggleOpen(node);
|
|
175
|
+
activate(node);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function onRowKeydown(e: KeyboardEvent, node: TreeNode<T>, parentId: string | undefined): void {
|
|
179
|
+
// Keydown bubbles the same way a click does, and only the focused row is
|
|
180
|
+
// the target.
|
|
181
|
+
if (e.target !== e.currentTarget) return;
|
|
182
|
+
|
|
183
|
+
const index = rows.findIndex((r) => r.node.id === node.id);
|
|
184
|
+
const open = expandedSet.has(node.id);
|
|
185
|
+
|
|
186
|
+
switch (e.key) {
|
|
187
|
+
case 'ArrowDown':
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
focusRow(rows[index + 1]?.node.id);
|
|
190
|
+
return;
|
|
191
|
+
case 'ArrowUp':
|
|
192
|
+
e.preventDefault();
|
|
193
|
+
focusRow(rows[index - 1]?.node.id);
|
|
194
|
+
return;
|
|
195
|
+
case 'Home':
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
focusRow(rows[0]?.node.id);
|
|
198
|
+
return;
|
|
199
|
+
case 'End':
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
focusRow(rows[rows.length - 1]?.node.id);
|
|
202
|
+
return;
|
|
203
|
+
case 'ArrowRight':
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
// Opening and descending are two presses. The first press has to leave
|
|
206
|
+
// focus where it is, or a screen reader announces the child before its
|
|
207
|
+
// parent has said it is now open.
|
|
208
|
+
if (!isBranch(node)) return;
|
|
209
|
+
if (!open) setOpen(node, true);
|
|
210
|
+
else focusRow(node.children?.[0]?.id);
|
|
211
|
+
return;
|
|
212
|
+
case 'ArrowLeft':
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
if (isBranch(node) && open) setOpen(node, false);
|
|
215
|
+
else focusRow(parentId);
|
|
216
|
+
return;
|
|
217
|
+
case 'Enter':
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
activate(node);
|
|
220
|
+
return;
|
|
221
|
+
case ' ':
|
|
222
|
+
// Space never reaches the typeahead below. One key that both toggles a
|
|
223
|
+
// check and activates a row leaves the user unable to do either on
|
|
224
|
+
// purpose, so a checkable tree gives Space to the check and keeps
|
|
225
|
+
// Enter for activation.
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
if (checkable) toggleCheck(node);
|
|
228
|
+
else activate(node);
|
|
229
|
+
return;
|
|
230
|
+
default:
|
|
231
|
+
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
typeahead(e.key, node.id);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
</script>
|
|
238
|
+
|
|
239
|
+
{#snippet branch(list: TreeNode<T>[], depth: number, parentId: string | undefined)}
|
|
240
|
+
{#each list as node, i (node.id)}
|
|
241
|
+
{@const open = expandedSet.has(node.id)}
|
|
242
|
+
{@const state = branchState(node, checkedSet)}
|
|
243
|
+
<!--
|
|
244
|
+
The focus ring is stated once, on the element that holds the role and the
|
|
245
|
+
focus, and outside every selected and checked branch. A ring inside the
|
|
246
|
+
selected ternary disappears in the state it was meant to mark, and a
|
|
247
|
+
selected row already carries a brand text colour that a border-only focus
|
|
248
|
+
state would have nothing to add to. Inset, because a tree of any size
|
|
249
|
+
lives in a scrolling container that would crop an outset ring.
|
|
250
|
+
-->
|
|
251
|
+
<li
|
|
252
|
+
role="treeitem"
|
|
253
|
+
data-tree-id={node.id}
|
|
254
|
+
tabindex={node.id === tabbableId ? 0 : -1}
|
|
255
|
+
aria-level={depth + 1}
|
|
256
|
+
aria-setsize={list.length}
|
|
257
|
+
aria-posinset={i + 1}
|
|
258
|
+
aria-expanded={isBranch(node) ? open : undefined}
|
|
259
|
+
aria-selected={selected === node.id}
|
|
260
|
+
aria-checked={checkable ? ariaChecked(state) : undefined}
|
|
261
|
+
aria-disabled={node.disabled ? 'true' : undefined}
|
|
262
|
+
class="rounded-md outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand"
|
|
263
|
+
onclick={(e) => onRowClick(e, node)}
|
|
264
|
+
onkeydown={(e) => onRowKeydown(e, node, parentId)}
|
|
265
|
+
onfocus={() => (activeId = node.id)}
|
|
266
|
+
>
|
|
267
|
+
<!--
|
|
268
|
+
Indentation is a style attribute reading the spacing tokens. A class
|
|
269
|
+
built by interpolating the depth matches no candidate in Tailwind's
|
|
270
|
+
scan, so no rule is generated and every row draws flush left.
|
|
271
|
+
-->
|
|
272
|
+
<div
|
|
273
|
+
class="flex w-full items-center gap-2 rounded-md py-1.5 pr-2 text-sm transition-colors duration-150 {rowPaint(
|
|
274
|
+
selected === node.id,
|
|
275
|
+
!!node.disabled,
|
|
276
|
+
)}"
|
|
277
|
+
style="padding-left: calc(var(--spacing-inline) + {depth} * var(--spacing-stack))"
|
|
278
|
+
>
|
|
279
|
+
{#if isBranch(node)}
|
|
280
|
+
<span
|
|
281
|
+
data-tree-toggle
|
|
282
|
+
class="flex h-4 w-4 shrink-0 items-center justify-center text-faint"
|
|
283
|
+
>
|
|
284
|
+
<ChevronRight
|
|
285
|
+
size={14}
|
|
286
|
+
class="transition-transform duration-150 {open ? 'rotate-90' : ''}"
|
|
287
|
+
/>
|
|
288
|
+
</span>
|
|
289
|
+
{:else}
|
|
290
|
+
<span class="h-4 w-4 shrink-0"></span>
|
|
291
|
+
{/if}
|
|
292
|
+
|
|
293
|
+
{#if checkable}
|
|
294
|
+
<!--
|
|
295
|
+
A painted box rather than an input: the treeitem itself carries
|
|
296
|
+
aria-checked, and a focusable control inside the row would put a
|
|
297
|
+
second tab stop in a widget whose whole point is that it has one.
|
|
298
|
+
-->
|
|
299
|
+
<span data-tree-check class="flex shrink-0 items-center">
|
|
300
|
+
<span class={choiceBox('checkbox', 'sm', state === 'all', state === 'some')}>
|
|
301
|
+
{#if state !== 'none'}
|
|
302
|
+
<svg width="10" height="8" viewBox="0 0 10 8" fill="none" aria-hidden="true">
|
|
303
|
+
<path
|
|
304
|
+
d={state === 'all' ? CHOICE_MARK.check : CHOICE_MARK.mixed}
|
|
305
|
+
stroke="currentColor"
|
|
306
|
+
stroke-width="1.5"
|
|
307
|
+
stroke-linecap="round"
|
|
308
|
+
stroke-linejoin="round"
|
|
309
|
+
/>
|
|
310
|
+
</svg>
|
|
311
|
+
{/if}
|
|
312
|
+
</span>
|
|
313
|
+
</span>
|
|
314
|
+
{/if}
|
|
315
|
+
|
|
316
|
+
{#if node.icon}
|
|
317
|
+
{@const Icon = node.icon}
|
|
318
|
+
<Icon size={14} class="shrink-0 text-faint" />
|
|
319
|
+
{/if}
|
|
320
|
+
|
|
321
|
+
<span class="truncate">{node.label}</span>
|
|
322
|
+
|
|
323
|
+
{#if node.badge !== undefined}
|
|
324
|
+
<span class="ml-auto shrink-0 pl-2 text-xs text-faint">{node.badge}</span>
|
|
325
|
+
{/if}
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
{#if isBranch(node) && open}
|
|
329
|
+
<ul role="group">
|
|
330
|
+
{@render branch(node.children ?? [], depth + 1, node.id)}
|
|
331
|
+
</ul>
|
|
332
|
+
{/if}
|
|
333
|
+
</li>
|
|
334
|
+
{/each}
|
|
335
|
+
{/snippet}
|
|
336
|
+
|
|
337
|
+
<ul bind:this={rootEl} role="tree" aria-label={label} class="text-fg select-none {klass}">
|
|
338
|
+
{@render branch(nodes, 0, undefined)}
|
|
339
|
+
</ul>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type TreeNode } from '../internal/tree.js';
|
|
2
|
+
declare function $$render<T = unknown>(): {
|
|
3
|
+
props: {
|
|
4
|
+
nodes: TreeNode<T>[];
|
|
5
|
+
/** Required. An unnamed tree announces as just "tree". */
|
|
6
|
+
label: string;
|
|
7
|
+
expanded?: string[];
|
|
8
|
+
selected?: string;
|
|
9
|
+
/** Checked LEAF ids only. A branch's state is always derived, never stored, so the two cannot disagree. */
|
|
10
|
+
checked?: string[];
|
|
11
|
+
checkable?: boolean;
|
|
12
|
+
class?: string;
|
|
13
|
+
onactivate?: (node: TreeNode<T>) => void;
|
|
14
|
+
oncheck?: (checked: string[]) => void;
|
|
15
|
+
};
|
|
16
|
+
exports: {};
|
|
17
|
+
bindings: "expanded" | "selected" | "checked";
|
|
18
|
+
slots: {};
|
|
19
|
+
events: {};
|
|
20
|
+
};
|
|
21
|
+
declare class __sveltets_Render<T = unknown> {
|
|
22
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
23
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
24
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
25
|
+
bindings(): "expanded" | "selected" | "checked";
|
|
26
|
+
exports(): {};
|
|
27
|
+
}
|
|
28
|
+
interface $$IsomorphicComponent {
|
|
29
|
+
new <T = unknown>(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']>> & {
|
|
30
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
31
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
32
|
+
<T = unknown>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
33
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
34
|
+
}
|
|
35
|
+
declare const TreeView: $$IsomorphicComponent;
|
|
36
|
+
type TreeView<T = unknown> = InstanceType<typeof TreeView<T>>;
|
|
37
|
+
export default TreeView;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,32 +7,55 @@
|
|
|
7
7
|
* is an implementation detail.
|
|
8
8
|
*/
|
|
9
9
|
export { default as Card } from './components/Card.svelte';
|
|
10
|
+
export { default as Panel } from './components/Panel.svelte';
|
|
11
|
+
export { default as PageShell } from './components/PageShell.svelte';
|
|
10
12
|
export { default as PageHeader } from './components/PageHeader.svelte';
|
|
13
|
+
export { default as SectionHeading } from './components/SectionHeading.svelte';
|
|
11
14
|
export { default as Divider } from './components/Divider.svelte';
|
|
12
15
|
export { default as Accordion } from './components/Accordion.svelte';
|
|
13
16
|
export { default as AccordionItem } from './components/AccordionItem.svelte';
|
|
17
|
+
export { default as Collapsible } from './components/Collapsible.svelte';
|
|
14
18
|
export { default as Table } from './components/Table.svelte';
|
|
19
|
+
export { default as DescriptionList } from './components/DescriptionList.svelte';
|
|
20
|
+
export { default as Toolbar } from './components/Toolbar.svelte';
|
|
21
|
+
export { default as TreeView } from './components/TreeView.svelte';
|
|
22
|
+
export type { PageWidth } from './internal/layout.js';
|
|
23
|
+
export type { TreeNode, TriState } from './internal/tree.js';
|
|
15
24
|
export { default as Button } from './components/Button.svelte';
|
|
16
25
|
export { default as ButtonGroup } from './components/ButtonGroup.svelte';
|
|
17
26
|
export { default as Input } from './components/Input.svelte';
|
|
27
|
+
export { default as PasswordInput } from './components/PasswordInput.svelte';
|
|
18
28
|
export { default as Textarea } from './components/Textarea.svelte';
|
|
19
29
|
export { default as NumberInput } from './components/NumberInput.svelte';
|
|
20
30
|
export { default as SearchInput } from './components/SearchInput.svelte';
|
|
21
31
|
export { default as FileInput } from './components/FileInput.svelte';
|
|
22
32
|
export { default as Label } from './components/Label.svelte';
|
|
33
|
+
export { default as Field } from './components/Field.svelte';
|
|
34
|
+
export { default as FormMessage } from './components/FormMessage.svelte';
|
|
35
|
+
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
23
36
|
export { default as Select } from './components/Select.svelte';
|
|
24
37
|
export { default as MultiSelect } from './components/MultiSelect.svelte';
|
|
25
38
|
export { default as Autocomplete } from './components/Autocomplete.svelte';
|
|
26
39
|
export { default as DatePicker } from './components/DatePicker.svelte';
|
|
40
|
+
export { default as TimePicker } from './components/TimePicker.svelte';
|
|
41
|
+
export { default as DateTimePicker } from './components/DateTimePicker.svelte';
|
|
27
42
|
export { default as Checkbox } from './components/Checkbox.svelte';
|
|
43
|
+
export { default as CheckboxGroup } from './components/CheckboxGroup.svelte';
|
|
28
44
|
export { default as Radio } from './components/Radio.svelte';
|
|
29
45
|
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
30
46
|
export { default as Toggle } from './components/Toggle.svelte';
|
|
47
|
+
export type { ChoiceSize, ChoiceVariant, ChoiceOrientation } from './internal/choice.js';
|
|
48
|
+
export type { ChoiceOption } from './components/CheckboxGroup.svelte';
|
|
49
|
+
export type { SelectOption, SelectChangeEvent } from './components/Select.svelte';
|
|
50
|
+
export type { ListOption } from './components/Autocomplete.svelte';
|
|
51
|
+
export type { FilterFn, FilterInput, FilterContext } from './internal/filter.js';
|
|
31
52
|
export { default as Breadcrumb } from './components/Breadcrumb.svelte';
|
|
32
53
|
export { default as Tabs } from './components/Tabs.svelte';
|
|
33
54
|
export { default as Pagination } from './components/Pagination.svelte';
|
|
34
55
|
export { default as StepIndicator } from './components/StepIndicator.svelte';
|
|
35
56
|
export { default as Dropdown } from './components/Dropdown.svelte';
|
|
57
|
+
export { default as SidebarNav } from './components/SidebarNav.svelte';
|
|
58
|
+
export type { NavNode, NavTree } from './internal/nav-tree.js';
|
|
36
59
|
export { default as Modal } from './components/Modal.svelte';
|
|
37
60
|
export { default as Drawer } from './components/Drawer.svelte';
|
|
38
61
|
export { default as Tooltip } from './components/Tooltip.svelte';
|
|
@@ -47,6 +70,7 @@ export { default as Skeleton } from './components/Skeleton.svelte';
|
|
|
47
70
|
export { default as EmptyState } from './components/EmptyState.svelte';
|
|
48
71
|
export { default as Stat } from './components/Stat.svelte';
|
|
49
72
|
export { default as Kbd } from './components/Kbd.svelte';
|
|
73
|
+
export { default as CopyButton } from './components/CopyButton.svelte';
|
|
50
74
|
export { default as Avatar } from './components/Avatar.svelte';
|
|
51
75
|
export { default as AvatarGroup } from './components/AvatarGroup.svelte';
|
|
52
76
|
export { default as ThemeToggle } from './components/ThemeToggle.svelte';
|
|
@@ -60,4 +84,4 @@ export { openDialog, closeDialog, dismissDialog, dismissAllDialogs, confirm, set
|
|
|
60
84
|
export type { DialogOptions, DialogEntry, DialogSize } from './components/dialog/types.js';
|
|
61
85
|
export { cn, type ClassValue } from './utils/cn.js';
|
|
62
86
|
export { getTheme, setTheme, toggleTheme, themeBootScript, type Theme } from './utils/theme.js';
|
|
63
|
-
export declare const VERSION = "0.
|
|
87
|
+
export declare const VERSION = "0.13.1";
|
package/dist/index.js
CHANGED
|
@@ -8,25 +8,42 @@
|
|
|
8
8
|
*/
|
|
9
9
|
// ── Layout & structure ─────────────────────────────────────────────────────
|
|
10
10
|
export { default as Card } from './components/Card.svelte';
|
|
11
|
+
export { default as Panel } from './components/Panel.svelte';
|
|
12
|
+
export { default as PageShell } from './components/PageShell.svelte';
|
|
11
13
|
export { default as PageHeader } from './components/PageHeader.svelte';
|
|
14
|
+
export { default as SectionHeading } from './components/SectionHeading.svelte';
|
|
12
15
|
export { default as Divider } from './components/Divider.svelte';
|
|
13
16
|
export { default as Accordion } from './components/Accordion.svelte';
|
|
14
17
|
export { default as AccordionItem } from './components/AccordionItem.svelte';
|
|
18
|
+
export { default as Collapsible } from './components/Collapsible.svelte';
|
|
15
19
|
export { default as Table } from './components/Table.svelte';
|
|
20
|
+
export { default as DescriptionList } from './components/DescriptionList.svelte';
|
|
21
|
+
export { default as Toolbar } from './components/Toolbar.svelte';
|
|
22
|
+
export { default as TreeView } from './components/TreeView.svelte';
|
|
16
23
|
// ── Forms & inputs ─────────────────────────────────────────────────────────
|
|
17
24
|
export { default as Button } from './components/Button.svelte';
|
|
18
25
|
export { default as ButtonGroup } from './components/ButtonGroup.svelte';
|
|
19
26
|
export { default as Input } from './components/Input.svelte';
|
|
27
|
+
export { default as PasswordInput } from './components/PasswordInput.svelte';
|
|
20
28
|
export { default as Textarea } from './components/Textarea.svelte';
|
|
21
29
|
export { default as NumberInput } from './components/NumberInput.svelte';
|
|
22
30
|
export { default as SearchInput } from './components/SearchInput.svelte';
|
|
23
31
|
export { default as FileInput } from './components/FileInput.svelte';
|
|
24
32
|
export { default as Label } from './components/Label.svelte';
|
|
33
|
+
// The field furniture as a component, for a consumer composing a control the
|
|
34
|
+
// kit does not ship. internal/field.ts stays private, and a page that could
|
|
35
|
+
// not reach it hand-copied the class strings instead.
|
|
36
|
+
export { default as Field } from './components/Field.svelte';
|
|
37
|
+
export { default as FormMessage } from './components/FormMessage.svelte';
|
|
38
|
+
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
25
39
|
export { default as Select } from './components/Select.svelte';
|
|
26
40
|
export { default as MultiSelect } from './components/MultiSelect.svelte';
|
|
27
41
|
export { default as Autocomplete } from './components/Autocomplete.svelte';
|
|
28
42
|
export { default as DatePicker } from './components/DatePicker.svelte';
|
|
43
|
+
export { default as TimePicker } from './components/TimePicker.svelte';
|
|
44
|
+
export { default as DateTimePicker } from './components/DateTimePicker.svelte';
|
|
29
45
|
export { default as Checkbox } from './components/Checkbox.svelte';
|
|
46
|
+
export { default as CheckboxGroup } from './components/CheckboxGroup.svelte';
|
|
30
47
|
export { default as Radio } from './components/Radio.svelte';
|
|
31
48
|
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
32
49
|
export { default as Toggle } from './components/Toggle.svelte';
|
|
@@ -36,6 +53,7 @@ export { default as Tabs } from './components/Tabs.svelte';
|
|
|
36
53
|
export { default as Pagination } from './components/Pagination.svelte';
|
|
37
54
|
export { default as StepIndicator } from './components/StepIndicator.svelte';
|
|
38
55
|
export { default as Dropdown } from './components/Dropdown.svelte';
|
|
56
|
+
export { default as SidebarNav } from './components/SidebarNav.svelte';
|
|
39
57
|
// ── Overlays ───────────────────────────────────────────────────────────────
|
|
40
58
|
export { default as Modal } from './components/Modal.svelte';
|
|
41
59
|
export { default as Drawer } from './components/Drawer.svelte';
|
|
@@ -52,6 +70,7 @@ export { default as Skeleton } from './components/Skeleton.svelte';
|
|
|
52
70
|
export { default as EmptyState } from './components/EmptyState.svelte';
|
|
53
71
|
export { default as Stat } from './components/Stat.svelte';
|
|
54
72
|
export { default as Kbd } from './components/Kbd.svelte';
|
|
73
|
+
export { default as CopyButton } from './components/CopyButton.svelte';
|
|
55
74
|
// ── Media ──────────────────────────────────────────────────────────────────
|
|
56
75
|
export { default as Avatar } from './components/Avatar.svelte';
|
|
57
76
|
export { default as AvatarGroup } from './components/AvatarGroup.svelte';
|
|
@@ -71,4 +90,4 @@ export { getTheme, setTheme, toggleTheme, themeBootScript } from './utils/theme.
|
|
|
71
90
|
// ── Version ────────────────────────────────────────────────────────────────
|
|
72
91
|
// Generated from package.json by `pnpm version:sync`. Bump package.json, never
|
|
73
92
|
// this line; the build and the test suite fail when the two disagree.
|
|
74
|
-
export const VERSION = '0.
|
|
93
|
+
export const VERSION = '0.13.1';
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Month arithmetic for the date controls, done without the Date parser.
|
|
3
|
+
*
|
|
4
|
+
* The rule this module exists to enforce: no string is ever handed to the Date
|
|
5
|
+
* parser. `new Date('2026-01-02')` is UTC midnight by specification and
|
|
6
|
+
* `new Date('2026-01-02T00:00')` is local midnight, so one helper applied to a
|
|
7
|
+
* date field and to a datetime field lands in two different zones. Read the
|
|
8
|
+
* first form back through the local getters west of UTC and it is 1 January.
|
|
9
|
+
* That is the off by one day, and it appears only for the part of the world
|
|
10
|
+
* that is not on UTC, which is why it survives a developer's own testing.
|
|
11
|
+
*
|
|
12
|
+
* DatePicker binds `value` to an ISO calendar date spelled `YYYY-MM-DD`: four
|
|
13
|
+
* digit year, two digit zero padded month, two digit zero padded day, with the
|
|
14
|
+
* empty string standing for no date. Its `min` and `max` props take the same
|
|
15
|
+
* spelling and are compared as plain strings, which works only because that
|
|
16
|
+
* spelling sorts lexicographically. DateRangePicker and DateTimePicker will
|
|
17
|
+
* carry the same value and need the same arithmetic, and each of the three is a
|
|
18
|
+
* place the zone bug enters, so the arithmetic is stated once here.
|
|
19
|
+
*
|
|
20
|
+
* The numeric Date constructor is avoided as well. `new Date(24, 0, 1)` is
|
|
21
|
+
* 1924, because the constructor remaps years 0 through 99 into the twentieth
|
|
22
|
+
* century, and the year in a date field comes from a user. Everything below is
|
|
23
|
+
* integer arithmetic on the proleptic Gregorian calendar. The one Date in the
|
|
24
|
+
* file is the no argument one inside todayLocal, which is the only place a real
|
|
25
|
+
* clock is needed.
|
|
26
|
+
*
|
|
27
|
+
* Not exported from the package entry point - this is an implementation detail.
|
|
28
|
+
*/
|
|
29
|
+
/** A calendar date with no zone and no time. m is 1-12, the month a human says, not the 0-11 the Date constructor takes. */
|
|
30
|
+
export interface CalendarDate {
|
|
31
|
+
y: number;
|
|
32
|
+
m: number;
|
|
33
|
+
d: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Reads the `YYYY-MM-DD` DatePicker binds, or null when the string is not one.
|
|
37
|
+
*
|
|
38
|
+
* Shape checks and round trips. `2024-13-45` returns null rather than silently
|
|
39
|
+
* becoming the 2025-02-14 those three numbers normalize to in a Date: a picker
|
|
40
|
+
* that accepted it would open on a month the user never typed and report a
|
|
41
|
+
* value they never chose. A day is rejected when its month does not have it, so
|
|
42
|
+
* `2024-02-30` and `2023-02-29` are both null while `2024-02-29` is a date.
|
|
43
|
+
* Undefined and the empty string are null too, because an unset field is the
|
|
44
|
+
* ordinary state of a date input and not an error.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseISODate(s: string | undefined | null): CalendarDate | null;
|
|
47
|
+
/**
|
|
48
|
+
* Writes the `YYYY-MM-DD` DatePicker binds.
|
|
49
|
+
*
|
|
50
|
+
* Every part is zero padded, including the year, because the min and max
|
|
51
|
+
* comparison in the control is a string comparison: an unpadded `2024-1-5`
|
|
52
|
+
* sorts after `2024-12-01` and the bound then rejects dates inside it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function toISODate(date: CalendarDate): string;
|
|
55
|
+
/**
|
|
56
|
+
* Today in the viewer's own zone, read from the Date's local getters and never
|
|
57
|
+
* from its ISO string.
|
|
58
|
+
*
|
|
59
|
+
* `new Date().toISOString().slice(0, 10)` is the tempting one line version and
|
|
60
|
+
* it is wrong: it converts to UTC first, so a viewer in Jakarta gets yesterday
|
|
61
|
+
* until 7am and a viewer in New York gets tomorrow from the evening on. The
|
|
62
|
+
* date the calendar marks as today has to be the date on the wall behind the
|
|
63
|
+
* viewer.
|
|
64
|
+
*/
|
|
65
|
+
export declare function todayLocal(): CalendarDate;
|
|
66
|
+
/**
|
|
67
|
+
* Days in a month, leap years included.
|
|
68
|
+
*
|
|
69
|
+
* A month outside 1 to 12 has no days and returns 0, so a caller bounding a day
|
|
70
|
+
* against it rejects the month rather than accepting a day in a month that does
|
|
71
|
+
* not exist.
|
|
72
|
+
*/
|
|
73
|
+
export declare function daysInMonth(y: number, m: number): number;
|
|
74
|
+
/** Weekday of the first of the month, 0 for Sunday. */
|
|
75
|
+
export declare function firstWeekday(y: number, m: number): number;
|
|
76
|
+
/**
|
|
77
|
+
* Adds months, clamping the day rather than rolling into the next month.
|
|
78
|
+
*
|
|
79
|
+
* 31 January plus one month is 28 or 29 February, never 2 or 3 March. A Date
|
|
80
|
+
* rolls, so paging a picker forward from the 31st and back again lands the user
|
|
81
|
+
* on a different day than the one they were looking at, and a range whose start
|
|
82
|
+
* was the 31st can end up after its own end.
|
|
83
|
+
*/
|
|
84
|
+
export declare function addMonths(date: CalendarDate, delta: number): CalendarDate;
|
|
85
|
+
/** Adds days across month and year boundaries, leap days included. */
|
|
86
|
+
export declare function addDays(date: CalendarDate, delta: number): CalendarDate;
|
|
87
|
+
/**
|
|
88
|
+
* Orders two dates as -1, 0 or 1.
|
|
89
|
+
*
|
|
90
|
+
* A total order, so a caller can sort and bound without constructing a Date.
|
|
91
|
+
* Returning the field difference would sort just as well and would report 2 for
|
|
92
|
+
* two years apart, which quietly breaks the caller that tests for 1.
|
|
93
|
+
*/
|
|
94
|
+
export declare function compareDates(a: CalendarDate, b: CalendarDate): number;
|
|
95
|
+
/**
|
|
96
|
+
* Inclusive on both ends. Either bound may be null, meaning unbounded.
|
|
97
|
+
*
|
|
98
|
+
* Inclusive because a picker given min and max for a single allowed day has to
|
|
99
|
+
* offer that day. DatePicker compares its bounds as strings today, which agrees
|
|
100
|
+
* with this only while every bound is zero padded.
|
|
101
|
+
*/
|
|
102
|
+
export declare function withinRange(date: CalendarDate, min: CalendarDate | null, max: CalendarDate | null): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* The six week grid a month renders, always 42 cells so the calendar does not
|
|
105
|
+
* change height between months. Cells outside the viewed month carry
|
|
106
|
+
* `outside: true`.
|
|
107
|
+
*
|
|
108
|
+
* DatePicker pads its lead with empty spans and stops at the last day, so
|
|
109
|
+
* February in a year where it starts on a Sunday draws four rows and a 31 day
|
|
110
|
+
* month starting on a Saturday draws six. The popover resizes as the user pages
|
|
111
|
+
* through the year and the day under the pointer moves out from under it. Fixed
|
|
112
|
+
* at 42 because six weeks is the most any month can touch.
|
|
113
|
+
*
|
|
114
|
+
* `weekStartsOn` is 0 for Sunday and 1 for Monday.
|
|
115
|
+
*/
|
|
116
|
+
export declare function monthGrid(y: number, m: number, weekStartsOn?: 0 | 1): {
|
|
117
|
+
date: CalendarDate;
|
|
118
|
+
outside: boolean;
|
|
119
|
+
}[];
|