@lilydesignsystem/svelte-headless 0.4.0 → 0.5.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/CHANGELOG.md +15 -0
- package/dist/components/ContentsLink/ContentsLink.svelte +36 -2
- package/dist/components/ContentsLink/ContentsLink.svelte.d.ts +3 -0
- package/dist/components/ContentsNav/ContentsNav.svelte +193 -1
- package/dist/components/ContentsNav/ContentsNav.svelte.d.ts +18 -1
- package/dist/components/DataTableTD/DataTableTD.svelte +5 -7
- package/dist/components/DataTableTD/DataTableTD.svelte.d.ts +1 -1
- package/dist/components/FiveFaceRatingPicker/FiveFaceRatingPicker.svelte +32 -2
- package/dist/components/FiveFaceRatingView/FiveFaceRatingView.svelte +3 -2
- package/dist/components/FiveStarRatingPicker/FiveStarRatingPicker.svelte +34 -2
- package/dist/components/FiveStarRatingPicker/FiveStarRatingPicker.svelte.d.ts +2 -0
- package/dist/components/GanttTableTD/GanttTableTD.svelte +3 -4
- package/dist/components/KanbanTableTD/KanbanTableTD.svelte +3 -4
- package/dist/components/KanbanTableTD/KanbanTableTD.svelte.d.ts +1 -1
- package/dist/components/NetPromoterScorePicker/NetPromoterScorePicker.svelte +29 -0
- package/dist/components/PinInputDiv/PinInputDiv.svelte +42 -2
- package/dist/components/PinInputDiv/PinInputDiv.svelte.d.ts +5 -0
- package/dist/components/Tag/Tag.svelte +20 -8
- package/dist/components/Tag/Tag.svelte.d.ts +6 -0
- package/index.md +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/)
|
|
4
4
|
and the package follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## 0.5.0 — 2026-10-14 (scrollspy, link counts)
|
|
7
|
+
|
|
8
|
+
- `ContentsNav` gains scrollspy (`spy`): the link of the section in view gets `aria-current` and `data-active`. Props `spy`, `offset`, `root`, `current`, bindable `active`, `onActiveChange`.
|
|
9
|
+
- `ContentsLink` gains `count` and `countLabel`: a visible, localised count inside the link, with `data-count` (and `data-complete` at 0). The Svelte `ContentsLink` is now a real `<a>` (it was a placeholder `<div>`).
|
|
10
|
+
|
|
11
|
+
## Unreleased — 2026-10-11 (labels and contracts)
|
|
12
|
+
|
|
13
|
+
`PinInputDiv` takes `digitLabel` and no longer names its boxes in English; `FiveStarRatingPicker` takes `starLabel`; `FiveFaceRatingPicker` / `FiveFaceRatingView` default their labels to the numbers. `Tag` is not a live region unless `live` is set. The form-reset fixes listed above now also reach `components/`, which the build publishes. See the repository CHANGELOG (2026-10-11).
|
|
14
|
+
|
|
15
|
+
## Unreleased — 2026-10-11
|
|
16
|
+
|
|
17
|
+
Fixed: `FiveStarRatingPicker`, `FiveFaceRatingPicker`, `NetPromoterScorePicker` and `PinInputDiv` restore their initial value when their `<form>` is reset (one tick after `reset`, unless it is
|
|
18
|
+
cancelled); before, a reset changed the inputs without telling the component, so its value disagreed with what the
|
|
19
|
+
form showed or submitted. `PinInputDiv` also now shows its initial `value` instead of overwriting it with an empty string on mount.
|
|
20
|
+
|
|
6
21
|
## 0.4.0 — 2026-10-06
|
|
7
22
|
|
|
8
23
|
**16 new components (catalog 555 → 571); no breaking changes.**
|
|
@@ -1,18 +1,52 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
// ContentsLink component
|
|
3
|
+
//
|
|
4
|
+
// One table of contents link: an <a href> to a page section, with an optional count (for example
|
|
5
|
+
// of tasks to do in that section).
|
|
6
|
+
//
|
|
7
|
+
// Props:
|
|
8
|
+
// className — string, optional. CSS class name.
|
|
9
|
+
// href — string, default "#". The link target.
|
|
10
|
+
// label — string, optional. aria-label override. It replaces the link's whole accessible name, so
|
|
11
|
+
// if you also show a count, include the count in it.
|
|
12
|
+
// count — number, optional. Sets data-count on the link (and data-complete when 0), as CSS hooks.
|
|
13
|
+
// countLabel — string, optional. Visible, localised count text (e.g. "3 to do"), rendered inside
|
|
14
|
+
// the link after the text so the link's accessible name includes it. No default, no English.
|
|
15
|
+
// children — Snippet, required. The link text.
|
|
16
|
+
// ...restProps — additional HTML attributes spread onto the <a>.
|
|
17
|
+
//
|
|
18
|
+
// Accessibility:
|
|
19
|
+
// - The count is text inside the link, never colour alone (WCAG 1.4.1).
|
|
20
|
+
|
|
2
21
|
import type { Snippet } from "svelte";
|
|
22
|
+
|
|
3
23
|
let {
|
|
4
24
|
class: className = "",
|
|
5
25
|
label = undefined,
|
|
26
|
+
href = "#",
|
|
27
|
+
count = undefined,
|
|
28
|
+
countLabel = undefined,
|
|
6
29
|
children,
|
|
7
30
|
...restProps
|
|
8
31
|
}: {
|
|
9
32
|
label?: string;
|
|
33
|
+
href?: string;
|
|
34
|
+
count?: number;
|
|
35
|
+
countLabel?: string;
|
|
10
36
|
children: Snippet;
|
|
11
37
|
[key: string]: unknown;
|
|
12
38
|
} = $props();
|
|
13
39
|
</script>
|
|
14
40
|
|
|
15
41
|
<!-- ContentsLink.svelte -->
|
|
16
|
-
<
|
|
42
|
+
<a
|
|
43
|
+
class={`contents-link ${className}`}
|
|
44
|
+
{href}
|
|
45
|
+
aria-label={label}
|
|
46
|
+
data-count={count}
|
|
47
|
+
data-complete={count === 0 ? "" : undefined}
|
|
48
|
+
{...restProps}
|
|
49
|
+
>
|
|
17
50
|
{@render children?.()}
|
|
18
|
-
</
|
|
51
|
+
{#if countLabel}<span class="contents-link-count">{countLabel}</span>{/if}
|
|
52
|
+
</a>
|
|
@@ -10,8 +10,21 @@
|
|
|
10
10
|
// className — string, optional. CSS class name.
|
|
11
11
|
// label — string, required. Accessible name for the navigation landmark (e.g., "Contents").
|
|
12
12
|
// children — Snippet, required. Typically a ContentsList component.
|
|
13
|
+
// spy — boolean, default false. Scrollspy: mark the link of the section currently in view.
|
|
14
|
+
// offset — number, default 0. Scrollspy activation line, in px below the top of `root`
|
|
15
|
+
// (set it to a fixed header's height, matching the targets' scroll-margin-top).
|
|
16
|
+
// root — HTMLElement | null, default null (the viewport). A scrolling element other than the viewport.
|
|
17
|
+
// current — string, default "true". The aria-current token set on the active link.
|
|
18
|
+
// active — string, bindable. The id of the section currently in view (spy only).
|
|
19
|
+
// onActiveChange — (id: string) => void, optional. Called when the active section changes.
|
|
13
20
|
// ...restProps — additional HTML attributes spread onto the <nav>.
|
|
14
21
|
//
|
|
22
|
+
// Scrollspy (spy): the links are the consumer's own `<a href="#id">` elements inside the nav. The
|
|
23
|
+
// active section is the last one whose top has passed the activation line; at the bottom of the
|
|
24
|
+
// scroll range the last section is active; before the first section, the first. The active link gets
|
|
25
|
+
// aria-current={current} and data-active; its ancestor <li>s get data-descendant-active. A click makes
|
|
26
|
+
// its link active at once and holds it until the scroll settles. Lily never animates or moves focus.
|
|
27
|
+
//
|
|
15
28
|
// Syntax:
|
|
16
29
|
// <ContentsNav label="Contents">
|
|
17
30
|
// <ContentsList>
|
|
@@ -53,12 +66,191 @@
|
|
|
53
66
|
class: className = "",
|
|
54
67
|
label,
|
|
55
68
|
children,
|
|
69
|
+
spy = false,
|
|
70
|
+
offset = 0,
|
|
71
|
+
root = null,
|
|
72
|
+
current = "true",
|
|
73
|
+
active = $bindable(""),
|
|
74
|
+
onActiveChange,
|
|
56
75
|
...restProps
|
|
57
|
-
}: {
|
|
76
|
+
}: {
|
|
77
|
+
label: string;
|
|
78
|
+
children: Snippet;
|
|
79
|
+
spy?: boolean;
|
|
80
|
+
offset?: number;
|
|
81
|
+
root?: HTMLElement | null;
|
|
82
|
+
current?: string;
|
|
83
|
+
active?: string;
|
|
84
|
+
onActiveChange?: (id: string) => void;
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
} = $props();
|
|
87
|
+
|
|
88
|
+
let navEl: HTMLElement | undefined = $state();
|
|
89
|
+
|
|
90
|
+
$effect(() => {
|
|
91
|
+
const nav = navEl;
|
|
92
|
+
if (!spy || !nav || typeof window === "undefined") return;
|
|
93
|
+
const spyOffset = offset;
|
|
94
|
+
const spyRoot = root;
|
|
95
|
+
const token = current;
|
|
96
|
+
let locked = false;
|
|
97
|
+
let lockTimer: ReturnType<typeof setTimeout> | undefined;
|
|
98
|
+
let frame = 0;
|
|
99
|
+
let last = "";
|
|
100
|
+
// Until the reader first scrolls, the URL hash decides (links may render after mount).
|
|
101
|
+
let hashPending = false;
|
|
102
|
+
|
|
103
|
+
const scroller: Window | HTMLElement = spyRoot ?? window;
|
|
104
|
+
|
|
105
|
+
function mark(link: HTMLAnchorElement | undefined): void {
|
|
106
|
+
for (const a of contentsLinks(nav!)) {
|
|
107
|
+
if (a === link) {
|
|
108
|
+
a.setAttribute("aria-current", token);
|
|
109
|
+
a.setAttribute("data-active", "");
|
|
110
|
+
} else {
|
|
111
|
+
a.removeAttribute("aria-current");
|
|
112
|
+
a.removeAttribute("data-active");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const li of nav!.querySelectorAll("li")) {
|
|
116
|
+
if (link && li.contains(link) && li.querySelector(":scope > a") !== link) {
|
|
117
|
+
li.setAttribute("data-descendant-active", "");
|
|
118
|
+
} else {
|
|
119
|
+
li.removeAttribute("data-descendant-active");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const id = link ? contentsLinkId(link) : "";
|
|
123
|
+
if (id !== last) {
|
|
124
|
+
last = id;
|
|
125
|
+
active = id;
|
|
126
|
+
if (id) onActiveChange?.(id);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function update(): void {
|
|
131
|
+
if (locked) return;
|
|
132
|
+
if (hashPending) {
|
|
133
|
+
const id = decodeURIComponent(window.location.hash.slice(1));
|
|
134
|
+
const hashed = id ? contentsLinks(nav!).find((a) => contentsLinkId(a) === id) : undefined;
|
|
135
|
+
if (hashed) {
|
|
136
|
+
mark(hashed);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const links = contentsLinks(nav!);
|
|
141
|
+
const pairs = links
|
|
142
|
+
.map((a) => ({ a, el: contentsTarget(a, nav!.ownerDocument) }))
|
|
143
|
+
.filter((p): p is { a: HTMLAnchorElement; el: HTMLElement } => p.el !== null);
|
|
144
|
+
if (pairs.length === 0) {
|
|
145
|
+
mark(undefined);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const base = spyRoot ? spyRoot.getBoundingClientRect().top : 0;
|
|
149
|
+
const tops = pairs.map((p) => p.el.getBoundingClientRect().top - base);
|
|
150
|
+
const atBottom = spyRoot
|
|
151
|
+
? spyRoot.scrollTop + spyRoot.clientHeight >= spyRoot.scrollHeight - 1
|
|
152
|
+
: window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 1;
|
|
153
|
+
mark(pairs[activeSectionIndex(tops, spyOffset, atBottom)].a);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function schedule(): void {
|
|
157
|
+
if (frame) return;
|
|
158
|
+
frame = requestAnimationFrame(() => {
|
|
159
|
+
frame = 0;
|
|
160
|
+
update();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function onScroll(): void {
|
|
165
|
+
hashPending = false;
|
|
166
|
+
schedule();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function release(): void {
|
|
170
|
+
locked = false;
|
|
171
|
+
if (lockTimer) clearTimeout(lockTimer);
|
|
172
|
+
update();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function onClick(event: Event): void {
|
|
176
|
+
const link = (event.target as Element | null)?.closest?.("a");
|
|
177
|
+
if (!link || !nav!.contains(link) || !contentsTarget(link as HTMLAnchorElement, nav!.ownerDocument)) return;
|
|
178
|
+
// The link is current at once; observer-style updates wait until the scroll settles.
|
|
179
|
+
mark(link as HTMLAnchorElement);
|
|
180
|
+
locked = true;
|
|
181
|
+
if (lockTimer) clearTimeout(lockTimer);
|
|
182
|
+
lockTimer = setTimeout(release, 1000);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function onHashChange(): void {
|
|
186
|
+
const id = decodeURIComponent(window.location.hash.slice(1));
|
|
187
|
+
const link = contentsLinks(nav!).find((a) => contentsLinkId(a) === id);
|
|
188
|
+
if (link) mark(link);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
scroller.addEventListener("scroll", onScroll, { passive: true });
|
|
192
|
+
scroller.addEventListener("scrollend", release);
|
|
193
|
+
window.addEventListener("resize", schedule);
|
|
194
|
+
window.addEventListener("hashchange", onHashChange);
|
|
195
|
+
nav.addEventListener("click", onClick);
|
|
196
|
+
// Links rendered or re-pointed after mount (a list built from data) are picked up too.
|
|
197
|
+
const mutations = typeof MutationObserver === "undefined" ? null : new MutationObserver(schedule);
|
|
198
|
+
mutations?.observe(nav, { childList: true, subtree: true, attributes: true, attributeFilter: ["href"] });
|
|
199
|
+
|
|
200
|
+
// The URL hash wins on load; otherwise the position decides.
|
|
201
|
+
const initial = decodeURIComponent(window.location.hash.slice(1));
|
|
202
|
+
hashPending = initial !== "";
|
|
203
|
+
const hashLink = initial ? contentsLinks(nav).find((a) => contentsLinkId(a) === initial) : undefined;
|
|
204
|
+
if (hashLink) mark(hashLink);
|
|
205
|
+
else update();
|
|
206
|
+
|
|
207
|
+
return () => {
|
|
208
|
+
scroller.removeEventListener("scroll", onScroll);
|
|
209
|
+
scroller.removeEventListener("scrollend", release);
|
|
210
|
+
window.removeEventListener("resize", schedule);
|
|
211
|
+
window.removeEventListener("hashchange", onHashChange);
|
|
212
|
+
nav.removeEventListener("click", onClick);
|
|
213
|
+
mutations?.disconnect();
|
|
214
|
+
if (frame) cancelAnimationFrame(frame);
|
|
215
|
+
if (lockTimer) clearTimeout(lockTimer);
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
</script>
|
|
219
|
+
|
|
220
|
+
<script lang="ts" module>
|
|
221
|
+
/**
|
|
222
|
+
* Which section is current: the last whose top has passed the activation line
|
|
223
|
+
* (`tops` are each section's top relative to the scroller; `offset` is the line).
|
|
224
|
+
* At the bottom of the scroll range the last section wins (a short last section
|
|
225
|
+
* never reaches the line); before the first section, the first.
|
|
226
|
+
*/
|
|
227
|
+
export function activeSectionIndex(tops: number[], offset: number, atBottom: boolean): number {
|
|
228
|
+
if (tops.length === 0) return -1;
|
|
229
|
+
if (atBottom) return tops.length - 1;
|
|
230
|
+
let index = 0;
|
|
231
|
+
for (let i = 0; i < tops.length; i++) {
|
|
232
|
+
if (tops[i] <= offset + 1) index = i;
|
|
233
|
+
}
|
|
234
|
+
return index;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** The in-page `<a href="#id">` links inside `nav`. */
|
|
238
|
+
export function contentsLinks(nav: Element): HTMLAnchorElement[] {
|
|
239
|
+
return Array.from(nav.querySelectorAll<HTMLAnchorElement>('a[href^="#"]')).filter((a) => a.getAttribute("href")!.length > 1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function contentsLinkId(link: Element): string {
|
|
243
|
+
return decodeURIComponent((link.getAttribute("href") ?? "").slice(1));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function contentsTarget(link: Element, doc: Document): HTMLElement | null {
|
|
247
|
+
return doc.getElementById(contentsLinkId(link));
|
|
248
|
+
}
|
|
58
249
|
</script>
|
|
59
250
|
|
|
60
251
|
<!-- ContentsNav.svelte -->
|
|
61
252
|
<nav
|
|
253
|
+
bind:this={navEl}
|
|
62
254
|
class={`contents-nav ${className}`}
|
|
63
255
|
aria-label={label}
|
|
64
256
|
{...restProps}
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which section is current: the last whose top has passed the activation line
|
|
3
|
+
* (`tops` are each section's top relative to the scroller; `offset` is the line).
|
|
4
|
+
* At the bottom of the scroll range the last section wins (a short last section
|
|
5
|
+
* never reaches the line); before the first section, the first.
|
|
6
|
+
*/
|
|
7
|
+
export declare function activeSectionIndex(tops: number[], offset: number, atBottom: boolean): number;
|
|
8
|
+
/** The in-page `<a href="#id">` links inside `nav`. */
|
|
9
|
+
export declare function contentsLinks(nav: Element): HTMLAnchorElement[];
|
|
10
|
+
export declare function contentsLinkId(link: Element): string;
|
|
11
|
+
export declare function contentsTarget(link: Element, doc: Document): HTMLElement | null;
|
|
1
12
|
import type { Snippet } from "svelte";
|
|
2
13
|
type $$ComponentProps = {
|
|
3
14
|
label: string;
|
|
4
15
|
children: Snippet;
|
|
16
|
+
spy?: boolean;
|
|
17
|
+
offset?: number;
|
|
18
|
+
root?: HTMLElement | null;
|
|
19
|
+
current?: string;
|
|
20
|
+
active?: string;
|
|
21
|
+
onActiveChange?: (id: string) => void;
|
|
5
22
|
[key: string]: unknown;
|
|
6
23
|
};
|
|
7
|
-
declare const ContentsNav: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
24
|
+
declare const ContentsNav: import("svelte").Component<$$ComponentProps, {}, "active">;
|
|
8
25
|
type ContentsNav = ReturnType<typeof ContentsNav>;
|
|
9
26
|
export default ContentsNav;
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
// DataTableTD component
|
|
3
3
|
//
|
|
4
4
|
// A single data cell within a DataTableRow. Renders as a <td> with
|
|
5
|
-
// role="gridcell". Supports an active state
|
|
6
|
-
//
|
|
7
|
-
// readers. Uses a roving tabindex pattern.
|
|
5
|
+
// role="gridcell". Supports an active state marking the grid's single roving tab
|
|
6
|
+
// stop (tabindex="0"); it does not set aria-selected.
|
|
8
7
|
//
|
|
9
8
|
// Props:
|
|
10
9
|
// className — string, optional. CSS class name.
|
|
11
|
-
// active — boolean, default false. Whether this cell is
|
|
10
|
+
// active — boolean, default false. Whether this cell is the roving tab stop.
|
|
12
11
|
// children — Snippet, required. Cell content.
|
|
13
12
|
// ...restProps — additional HTML attributes spread onto the <td>.
|
|
14
13
|
//
|
|
@@ -21,7 +20,7 @@
|
|
|
21
20
|
//
|
|
22
21
|
// Accessibility:
|
|
23
22
|
// - role="gridcell" identifies the cell as part of an interactive grid
|
|
24
|
-
// - aria-selected
|
|
23
|
+
// - `active` is the roving tab stop only: it never sets aria-selected, because focus is not selection (a grid with real selection passes aria-selected itself)
|
|
25
24
|
// - Roving tabindex: tabindex="0" when active, "-1" otherwise
|
|
26
25
|
//
|
|
27
26
|
// Claude rules:
|
|
@@ -39,7 +38,7 @@
|
|
|
39
38
|
children,
|
|
40
39
|
...restProps
|
|
41
40
|
}: {
|
|
42
|
-
/** Whether this cell is
|
|
41
|
+
/** Whether this cell is the roving tab stop (tabindex="0"). Never sets aria-selected. */
|
|
43
42
|
active?: boolean;
|
|
44
43
|
/** Cell content. */
|
|
45
44
|
children: Snippet;
|
|
@@ -51,7 +50,6 @@
|
|
|
51
50
|
<td
|
|
52
51
|
class={`data-table-td ${className}`}
|
|
53
52
|
role="gridcell"
|
|
54
|
-
aria-selected={active || undefined}
|
|
55
53
|
tabindex={active ? 0 : -1}
|
|
56
54
|
{...restProps}
|
|
57
55
|
>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
-
/** Whether this cell is
|
|
3
|
+
/** Whether this cell is the roving tab stop (tabindex="0"). Never sets aria-selected. */
|
|
4
4
|
active?: boolean;
|
|
5
5
|
/** Cell content. */
|
|
6
6
|
children: Snippet;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from "svelte";
|
|
2
3
|
// FiveFaceRatingPicker component
|
|
3
4
|
//
|
|
4
5
|
// A five-face rating input that lets users select a satisfaction level from 1 to 5
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
// label — string, required. Accessible name for the rating group via aria-label.
|
|
12
13
|
// value — number, default 0. Current rating (1-5, 0 = none); bindable via bind:value.
|
|
13
14
|
// name — string, default "face-rating". Radio group name for form submission.
|
|
14
|
-
// labels — string[], default ["
|
|
15
|
+
// labels — string[], default ["1", "2", "3", "4", "5"] (no English default; pass words, e.g. ["Very bad", …, "Very good"]). Accessible labels for each face.
|
|
15
16
|
// disabled — boolean, default false. Whether the entire group is disabled.
|
|
16
17
|
// ...restProps — additional HTML attributes spread onto the fieldset.
|
|
17
18
|
//
|
|
@@ -53,7 +54,8 @@
|
|
|
53
54
|
label,
|
|
54
55
|
value = $bindable(0),
|
|
55
56
|
name = "face-rating",
|
|
56
|
-
|
|
57
|
+
// The numbers, not English words: the words are the consumer's to supply.
|
|
58
|
+
labels = ["1", "2", "3", "4", "5"],
|
|
57
59
|
disabled = false,
|
|
58
60
|
...restProps
|
|
59
61
|
}: {
|
|
@@ -71,10 +73,38 @@
|
|
|
71
73
|
} = $props();
|
|
72
74
|
|
|
73
75
|
const faces = [1, 2, 3, 4, 5];
|
|
76
|
+
|
|
77
|
+
// Form reset: the radios use a one-way `checked`, which a reset clears
|
|
78
|
+
// without telling this component, so `value` would keep a rating the form no
|
|
79
|
+
// longer submits. Restore the initial value instead — one tick later, after
|
|
80
|
+
// the browser has reset the DOM, and not when the reset was cancelled.
|
|
81
|
+
let root: HTMLFieldSetElement | undefined = $state();
|
|
82
|
+
const initialValue = untrack(() => value);
|
|
83
|
+
$effect(() => {
|
|
84
|
+
const form = root?.form ?? root?.closest("form");
|
|
85
|
+
if (!form) return;
|
|
86
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
87
|
+
const onReset = (event: Event) => {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
timer = setTimeout(() => {
|
|
90
|
+
if (event.defaultPrevented || !root) return;
|
|
91
|
+
value = initialValue;
|
|
92
|
+
for (const input of root.querySelectorAll<HTMLInputElement>('input[type="radio"]')) {
|
|
93
|
+
input.checked = input.value === String(initialValue);
|
|
94
|
+
}
|
|
95
|
+
}, 0);
|
|
96
|
+
};
|
|
97
|
+
form.addEventListener("reset", onReset);
|
|
98
|
+
return () => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
form.removeEventListener("reset", onReset);
|
|
101
|
+
};
|
|
102
|
+
});
|
|
74
103
|
</script>
|
|
75
104
|
|
|
76
105
|
<!-- FiveFaceRatingPicker.svelte -->
|
|
77
106
|
<fieldset
|
|
107
|
+
bind:this={root}
|
|
78
108
|
class={`five-face-rating-picker ${className}`}
|
|
79
109
|
role="radiogroup"
|
|
80
110
|
aria-label={label}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// className — string, optional. CSS class name.
|
|
11
11
|
// value — number, required. Rating value (1-5) to display.
|
|
12
12
|
// label — string, required. Accessible description via aria-label.
|
|
13
|
-
// labels — string[], default ["
|
|
13
|
+
// labels — string[], default ["1", "2", "3", "4", "5"] (no English default; pass words, e.g. ["Very bad", …, "Very good"]). Text labels for each face.
|
|
14
14
|
// ...restProps — additional HTML attributes spread onto the span.
|
|
15
15
|
//
|
|
16
16
|
// Syntax:
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
class: className = "",
|
|
47
47
|
value,
|
|
48
48
|
label,
|
|
49
|
-
|
|
49
|
+
// The numbers, not English words: the words are the consumer's to supply.
|
|
50
|
+
labels = ["1", "2", "3", "4", "5"],
|
|
50
51
|
...restProps
|
|
51
52
|
}: {
|
|
52
53
|
/** Rating value (1-5). */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from "svelte";
|
|
2
3
|
// FiveStarRatingPicker component
|
|
3
4
|
//
|
|
4
5
|
// A five-star rating input that lets users select a rating from 1 to 5 stars using
|
|
@@ -9,6 +10,7 @@
|
|
|
9
10
|
// Props:
|
|
10
11
|
// className — string, optional. CSS class name.
|
|
11
12
|
// label — string, required. Accessible name for the rating group via aria-label.
|
|
13
|
+
// starLabel — (star: number) => string, optional. Each option's text; defaults to the number (no English).
|
|
12
14
|
// value — number, default 0. Current rating (1-5, 0 = none); bindable via bind:value.
|
|
13
15
|
// name — string, default "rating". Radio group name for form submission.
|
|
14
16
|
// disabled — boolean, default false. Whether the entire group is disabled.
|
|
@@ -52,6 +54,7 @@
|
|
|
52
54
|
label,
|
|
53
55
|
value = $bindable(0),
|
|
54
56
|
name = "rating",
|
|
57
|
+
starLabel,
|
|
55
58
|
disabled = false,
|
|
56
59
|
...restProps
|
|
57
60
|
}: {
|
|
@@ -61,16 +64,46 @@
|
|
|
61
64
|
value?: number;
|
|
62
65
|
/** Radio group name for form submission. */
|
|
63
66
|
name?: string;
|
|
67
|
+
/** Each option's text, e.g. (n) => `${n} stars`. Defaults to the number alone: no English default. */
|
|
68
|
+
starLabel?: (star: number) => string;
|
|
64
69
|
/** Whether the entire group is disabled. */
|
|
65
70
|
disabled?: boolean;
|
|
66
71
|
[key: string]: unknown;
|
|
67
72
|
} = $props();
|
|
68
73
|
|
|
69
74
|
const stars = [1, 2, 3, 4, 5];
|
|
75
|
+
|
|
76
|
+
// Form reset: the radios use a one-way `checked`, which a reset clears
|
|
77
|
+
// without telling this component, so `value` would keep a rating the form no
|
|
78
|
+
// longer submits. Restore the initial value instead — one tick later, after
|
|
79
|
+
// the browser has reset the DOM, and not when the reset was cancelled.
|
|
80
|
+
let root: HTMLFieldSetElement | undefined = $state();
|
|
81
|
+
const initialValue = untrack(() => value);
|
|
82
|
+
$effect(() => {
|
|
83
|
+
const form = root?.form ?? root?.closest("form");
|
|
84
|
+
if (!form) return;
|
|
85
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
86
|
+
const onReset = (event: Event) => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
timer = setTimeout(() => {
|
|
89
|
+
if (event.defaultPrevented || !root) return;
|
|
90
|
+
value = initialValue;
|
|
91
|
+
for (const input of root.querySelectorAll<HTMLInputElement>('input[type="radio"]')) {
|
|
92
|
+
input.checked = input.value === String(initialValue);
|
|
93
|
+
}
|
|
94
|
+
}, 0);
|
|
95
|
+
};
|
|
96
|
+
form.addEventListener("reset", onReset);
|
|
97
|
+
return () => {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
form.removeEventListener("reset", onReset);
|
|
100
|
+
};
|
|
101
|
+
});
|
|
70
102
|
</script>
|
|
71
103
|
|
|
72
104
|
<!-- FiveStarRatingPicker.svelte -->
|
|
73
105
|
<fieldset
|
|
106
|
+
bind:this={root}
|
|
74
107
|
class={`five-star-rating-picker ${className}`}
|
|
75
108
|
role="radiogroup"
|
|
76
109
|
aria-label={label}
|
|
@@ -86,8 +119,7 @@
|
|
|
86
119
|
checked={value === star}
|
|
87
120
|
onchange={() => (value = star)}
|
|
88
121
|
/>
|
|
89
|
-
{star}
|
|
90
|
-
{star === 1 ? "star" : "stars"}
|
|
122
|
+
{starLabel ? starLabel(star) : star}
|
|
91
123
|
</label>
|
|
92
124
|
{/each}
|
|
93
125
|
</fieldset>
|
|
@@ -5,6 +5,8 @@ type $$ComponentProps = {
|
|
|
5
5
|
value?: number;
|
|
6
6
|
/** Radio group name for form submission. */
|
|
7
7
|
name?: string;
|
|
8
|
+
/** Each option's text, e.g. (n) => `${n} stars`. Defaults to the number alone: no English default. */
|
|
9
|
+
starLabel?: (star: number) => string;
|
|
8
10
|
/** Whether the entire group is disabled. */
|
|
9
11
|
disabled?: boolean;
|
|
10
12
|
[key: string]: unknown;
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
// A single cell within a GanttTableTr representing a time period in the
|
|
5
5
|
// Gantt table grid. Renders as a <td> with role="gridcell". Supports an
|
|
6
|
-
// active state
|
|
7
|
-
//
|
|
6
|
+
// active state that makes the cell a tab stop (tabindex="0"); it does
|
|
7
|
+
// not set aria-selected, which would announce focus as selection.
|
|
8
8
|
//
|
|
9
9
|
// Props:
|
|
10
10
|
// className — string, optional. CSS class name.
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
//
|
|
32
32
|
// Accessibility:
|
|
33
33
|
// - role="gridcell" identifies the cell as part of a grid
|
|
34
|
-
// - aria-selected
|
|
34
|
+
// - `active` is the roving tab stop only: it never sets aria-selected, because focus is not selection (a grid with real selection passes aria-selected itself)
|
|
35
35
|
// - Roving tabindex: tabindex="0" when active, "-1" otherwise
|
|
36
36
|
//
|
|
37
37
|
// Internationalization:
|
|
@@ -65,7 +65,6 @@
|
|
|
65
65
|
<td
|
|
66
66
|
class={`gantt-table-td ${className}`}
|
|
67
67
|
role="gridcell"
|
|
68
|
-
aria-selected={active || undefined}
|
|
69
68
|
tabindex={active ? 0 : -1}
|
|
70
69
|
{...restProps}
|
|
71
70
|
>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Props:
|
|
9
9
|
// className — string, optional. CSS class name.
|
|
10
|
-
// active — boolean, optional. Marks the cell as
|
|
10
|
+
// active — boolean, optional. Marks the cell as the roving tab stop.
|
|
11
11
|
// label — string, optional. Accessible name for the cell via aria-label.
|
|
12
12
|
// children — Snippet, optional. Cell content such as task cards or text.
|
|
13
13
|
// ...restProps — additional HTML attributes spread onto the <td>.
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
//
|
|
29
29
|
// Accessibility:
|
|
30
30
|
// - role="gridcell" identifies the cell as part of a grid
|
|
31
|
-
// - aria-selected
|
|
31
|
+
// - `active` is the roving tab stop only: it never sets aria-selected, because focus is not selection (a grid with real selection passes aria-selected itself)
|
|
32
32
|
// - tabindex enables roving tabindex (0 when active, -1 otherwise)
|
|
33
33
|
// - aria-label optionally provides an accessible name for the cell
|
|
34
34
|
//
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
children,
|
|
53
53
|
...restProps
|
|
54
54
|
}: {
|
|
55
|
-
/** Marks the cell as
|
|
55
|
+
/** Marks the cell as the roving tab stop (tabindex="0"). Never sets aria-selected. */
|
|
56
56
|
active?: boolean;
|
|
57
57
|
/** Accessible name for the cell. */
|
|
58
58
|
label?: string;
|
|
@@ -66,7 +66,6 @@
|
|
|
66
66
|
<td
|
|
67
67
|
class={`kanban-table-td ${className}`}
|
|
68
68
|
role="gridcell"
|
|
69
|
-
aria-selected={active || undefined}
|
|
70
69
|
tabindex={active ? 0 : -1}
|
|
71
70
|
aria-label={label}
|
|
72
71
|
{...restProps}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
-
/** Marks the cell as
|
|
3
|
+
/** Marks the cell as the roving tab stop (tabindex="0"). Never sets aria-selected. */
|
|
4
4
|
active?: boolean;
|
|
5
5
|
/** Accessible name for the cell. */
|
|
6
6
|
label?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from "svelte";
|
|
2
3
|
// NetPromoterScorePicker component
|
|
3
4
|
//
|
|
4
5
|
// A headless Net Promoter Score (NPS) rating input that renders a fieldset
|
|
@@ -69,10 +70,38 @@
|
|
|
69
70
|
name?: string;
|
|
70
71
|
[key: string]: unknown;
|
|
71
72
|
} = $props();
|
|
73
|
+
|
|
74
|
+
// Form reset: the radios use a one-way `checked`, which a reset clears
|
|
75
|
+
// without telling this component, so `value` would keep a rating the form no
|
|
76
|
+
// longer submits. Restore the initial value instead — one tick later, after
|
|
77
|
+
// the browser has reset the DOM, and not when the reset was cancelled.
|
|
78
|
+
let root: HTMLFieldSetElement | undefined = $state();
|
|
79
|
+
const initialValue = untrack(() => value);
|
|
80
|
+
$effect(() => {
|
|
81
|
+
const form = root?.form ?? root?.closest("form");
|
|
82
|
+
if (!form) return;
|
|
83
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
84
|
+
const onReset = (event: Event) => {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
timer = setTimeout(() => {
|
|
87
|
+
if (event.defaultPrevented || !root) return;
|
|
88
|
+
value = initialValue;
|
|
89
|
+
for (const input of root.querySelectorAll<HTMLInputElement>('input[type="radio"]')) {
|
|
90
|
+
input.checked = input.value === String(initialValue);
|
|
91
|
+
}
|
|
92
|
+
}, 0);
|
|
93
|
+
};
|
|
94
|
+
form.addEventListener("reset", onReset);
|
|
95
|
+
return () => {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
form.removeEventListener("reset", onReset);
|
|
98
|
+
};
|
|
99
|
+
});
|
|
72
100
|
</script>
|
|
73
101
|
|
|
74
102
|
<!-- NetPromoterScorePicker: a fieldset radiogroup with 11 radio buttons (0-10) -->
|
|
75
103
|
<fieldset
|
|
104
|
+
bind:this={root}
|
|
76
105
|
class={`net-promoter-score-picker ${className}`}
|
|
77
106
|
role="radiogroup"
|
|
78
107
|
aria-label={label}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from "svelte";
|
|
2
3
|
// PinInputDiv component
|
|
3
4
|
//
|
|
4
5
|
// A headless PIN input that renders a group of single-character numeric inputs
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
// className — string, optional. CSS class name.
|
|
13
14
|
// label — string, required. Accessible label for the group of inputs via aria-label.
|
|
14
15
|
// length — number, default 4. Number of digit inputs to render.
|
|
16
|
+
// digitLabel — (position, length) => string, optional. Each input's accessible name; defaults to `label`.
|
|
15
17
|
// value — string, default "". Bindable combined string of all entered digits.
|
|
16
18
|
// disabled — boolean, default false. Whether all inputs are disabled.
|
|
17
19
|
// ...restProps — additional HTML attributes spread onto the group <div>.
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
class: className = "",
|
|
61
63
|
label,
|
|
62
64
|
length = 4,
|
|
65
|
+
digitLabel,
|
|
63
66
|
value = $bindable(""),
|
|
64
67
|
disabled = false,
|
|
65
68
|
...restProps
|
|
@@ -68,6 +71,11 @@
|
|
|
68
71
|
label: string;
|
|
69
72
|
/** Number of digit inputs to render */
|
|
70
73
|
length?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Each digit input's accessible name, e.g. (n, of) => `Digit ${n} of ${of}`.
|
|
76
|
+
* Without it each input is named by `label`: no English default.
|
|
77
|
+
*/
|
|
78
|
+
digitLabel?: (position: number, length: number) => string;
|
|
71
79
|
/** Combined string of all entered digits, bindable */
|
|
72
80
|
value?: string;
|
|
73
81
|
/** Whether all inputs are disabled */
|
|
@@ -79,7 +87,12 @@
|
|
|
79
87
|
// Internal state: array of individual digit characters.
|
|
80
88
|
// Note: the length prop is captured at initialization time for the initial
|
|
81
89
|
// array size. This is intentional -- the pin length is expected to be static.
|
|
82
|
-
|
|
90
|
+
// Seeded from the initial `value`: starting empty meant the effect below
|
|
91
|
+
// overwrote a bound starting value with "" on mount.
|
|
92
|
+
const initialDigits = untrack(() =>
|
|
93
|
+
Array.from({ length }, (_, i) => (/^[0-9]$/.test(value[i] ?? "") ? value[i] : "")),
|
|
94
|
+
);
|
|
95
|
+
let digits: string[] = $state([...initialDigits]);
|
|
83
96
|
|
|
84
97
|
// References to each input element for focus management
|
|
85
98
|
let inputRefs: HTMLInputElement[] = $state([]);
|
|
@@ -93,6 +106,32 @@
|
|
|
93
106
|
});
|
|
94
107
|
|
|
95
108
|
// Handle input events on individual digit fields
|
|
109
|
+
// Form reset: the digit inputs use a one-way `value`, which a reset clears
|
|
110
|
+
// without telling this component, so `value` would keep digits the fields no
|
|
111
|
+
// longer show. Restore the initial digits instead — one tick later, after the
|
|
112
|
+
// browser has reset the DOM, and not when the reset was cancelled.
|
|
113
|
+
let root: HTMLDivElement | undefined = $state();
|
|
114
|
+
$effect(() => {
|
|
115
|
+
const form = root?.closest("form");
|
|
116
|
+
if (!form) return;
|
|
117
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
118
|
+
const onReset = (event: Event) => {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
timer = setTimeout(() => {
|
|
121
|
+
if (event.defaultPrevented) return;
|
|
122
|
+
digits = [...initialDigits];
|
|
123
|
+
inputRefs.forEach((input, i) => {
|
|
124
|
+
if (input) input.value = initialDigits[i];
|
|
125
|
+
});
|
|
126
|
+
}, 0);
|
|
127
|
+
};
|
|
128
|
+
form.addEventListener("reset", onReset);
|
|
129
|
+
return () => {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
form.removeEventListener("reset", onReset);
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
|
|
96
135
|
function handleInput(index: number, event: Event): void {
|
|
97
136
|
const target = event.target as HTMLInputElement;
|
|
98
137
|
const inputValue = target.value;
|
|
@@ -137,6 +176,7 @@
|
|
|
137
176
|
|
|
138
177
|
<!-- PinInputDiv.svelte -->
|
|
139
178
|
<div
|
|
179
|
+
bind:this={root}
|
|
140
180
|
class={`pin-input-div ${className}`}
|
|
141
181
|
role="group"
|
|
142
182
|
aria-label={label}
|
|
@@ -148,7 +188,7 @@
|
|
|
148
188
|
type="text"
|
|
149
189
|
inputmode="numeric"
|
|
150
190
|
maxlength="1"
|
|
151
|
-
aria-label={
|
|
191
|
+
aria-label={digitLabel ? digitLabel(index + 1, length) : label}
|
|
152
192
|
value={digits[index]}
|
|
153
193
|
{disabled}
|
|
154
194
|
oninput={(e) => handleInput(index, e)}
|
|
@@ -3,6 +3,11 @@ type $$ComponentProps = {
|
|
|
3
3
|
label: string;
|
|
4
4
|
/** Number of digit inputs to render */
|
|
5
5
|
length?: number;
|
|
6
|
+
/**
|
|
7
|
+
* Each digit input's accessible name, e.g. (n, of) => `Digit ${n} of ${of}`.
|
|
8
|
+
* Without it each input is named by `label`: no English default.
|
|
9
|
+
*/
|
|
10
|
+
digitLabel?: (position: number, length: number) => string;
|
|
6
11
|
/** Combined string of all entered digits, bindable */
|
|
7
12
|
value?: string;
|
|
8
13
|
/** Whether all inputs are disabled */
|
|
@@ -47,11 +47,18 @@
|
|
|
47
47
|
let {
|
|
48
48
|
class: className = "",
|
|
49
49
|
label,
|
|
50
|
+
live = false,
|
|
50
51
|
children,
|
|
51
52
|
...restProps
|
|
52
53
|
}: {
|
|
53
54
|
/** Accessible label for screen readers */
|
|
54
55
|
label: string;
|
|
56
|
+
/**
|
|
57
|
+
* Announce changes: a `role="status"` live region, for a tag whose text
|
|
58
|
+
* changes in place (a job's status). Off by default — a page of static
|
|
59
|
+
* tags should not be a page of live regions.
|
|
60
|
+
*/
|
|
61
|
+
live?: boolean;
|
|
55
62
|
/** The tag content (text, icon, etc.) */
|
|
56
63
|
children: Snippet;
|
|
57
64
|
/** Additional HTML attributes */
|
|
@@ -60,11 +67,16 @@
|
|
|
60
67
|
</script>
|
|
61
68
|
|
|
62
69
|
<!-- Tag.svelte -->
|
|
63
|
-
|
|
64
|
-
class={`tag ${className}`}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
{#if live}
|
|
71
|
+
<span class={`tag ${className}`} role="status" aria-label={label} {...restProps}>
|
|
72
|
+
{@render children?.()}
|
|
73
|
+
</span>
|
|
74
|
+
{:else}
|
|
75
|
+
<!-- Not a live region: aria-label is not allowed on a plain <span>, so the
|
|
76
|
+
label is screen-reader text appended after the content (the themes hide
|
|
77
|
+
.screen-reader-span). The content stays exposed: it may hold a focusable
|
|
78
|
+
control, such as a remove button. -->
|
|
79
|
+
<span class={`tag ${className}`} {...restProps}>
|
|
80
|
+
{@render children?.()}{#if label}<span class="screen-reader-span">{label}</span>{/if}
|
|
81
|
+
</span>
|
|
82
|
+
{/if}
|
|
@@ -2,6 +2,12 @@ import type { Snippet } from "svelte";
|
|
|
2
2
|
type $$ComponentProps = {
|
|
3
3
|
/** Accessible label for screen readers */
|
|
4
4
|
label: string;
|
|
5
|
+
/**
|
|
6
|
+
* Announce changes: a `role="status"` live region, for a tag whose text
|
|
7
|
+
* changes in place (a job's status). Off by default — a page of static
|
|
8
|
+
* tags should not be a page of live regions.
|
|
9
|
+
*/
|
|
10
|
+
live?: boolean;
|
|
5
11
|
/** The tag content (text, icon, etc.) */
|
|
6
12
|
children: Snippet;
|
|
7
13
|
/** Additional HTML attributes */
|
package/index.md
CHANGED
|
@@ -85,7 +85,7 @@ A headless Svelte 5 component library with 490 components. All components are he
|
|
|
85
85
|
- **data-table-td**: a data table interactive grid data cell for displaying and sorting tabular data
|
|
86
86
|
- **date-field**: a structured field for entering date components
|
|
87
87
|
- **date-input**: an input for entering a date value
|
|
88
|
-
- **date-range**:
|
|
88
|
+
- **date-range**: paired start and end date inputs
|
|
89
89
|
- **date-time-now-input**: an input for entering a date and time and "now" button
|
|
90
90
|
- **date-time-local-input**: an input for entering a date and time without time zone
|
|
91
91
|
- **details**: a disclosure widget that shows and hides content
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lilydesignsystem/svelte-headless",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": "=26"
|
|
6
6
|
},
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/LilyDesignSystem/lily-design-system
|
|
17
|
+
"url": "git+https://github.com/LilyDesignSystem/lily-design-system.git",
|
|
18
|
+
"directory": "lily-design-system-svelte-headless"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|
|
20
21
|
"lily",
|