svseeds 0.6.0 → 0.7.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.
Files changed (130) hide show
  1. package/README.md +1 -1
  2. package/_svseeds/Accordion.svelte +119 -83
  3. package/_svseeds/Accordion.svelte.d.ts +56 -27
  4. package/_svseeds/Button.svelte +116 -0
  5. package/_svseeds/Button.svelte.d.ts +55 -0
  6. package/_svseeds/Calendar.svelte +349 -0
  7. package/_svseeds/Calendar.svelte.d.ts +113 -0
  8. package/_svseeds/CheckField.svelte +228 -0
  9. package/_svseeds/CheckField.svelte.d.ts +80 -0
  10. package/_svseeds/ColorPicker.svelte +123 -0
  11. package/_svseeds/ColorPicker.svelte.d.ts +56 -0
  12. package/_svseeds/ComboBox.svelte +278 -0
  13. package/_svseeds/ComboBox.svelte.d.ts +72 -0
  14. package/_svseeds/ContextMenu.svelte +129 -0
  15. package/_svseeds/ContextMenu.svelte.d.ts +53 -0
  16. package/_svseeds/DarkToggle.svelte +125 -52
  17. package/_svseeds/DarkToggle.svelte.d.ts +38 -18
  18. package/_svseeds/DateField.svelte +278 -0
  19. package/_svseeds/DateField.svelte.d.ts +93 -0
  20. package/_svseeds/DateInput.svelte +347 -0
  21. package/_svseeds/DateInput.svelte.d.ts +105 -0
  22. package/_svseeds/Disclosure.svelte +233 -0
  23. package/_svseeds/Disclosure.svelte.d.ts +76 -0
  24. package/_svseeds/Drawer.svelte +179 -0
  25. package/_svseeds/Drawer.svelte.d.ts +62 -0
  26. package/_svseeds/FileField.svelte +276 -0
  27. package/_svseeds/FileField.svelte.d.ts +95 -0
  28. package/_svseeds/FileInput.svelte +279 -0
  29. package/_svseeds/FileInput.svelte.d.ts +103 -0
  30. package/_svseeds/HotkeyCapture.svelte +189 -0
  31. package/_svseeds/HotkeyCapture.svelte.d.ts +71 -0
  32. package/_svseeds/Image.svelte +211 -0
  33. package/_svseeds/Image.svelte.d.ts +83 -0
  34. package/_svseeds/MenuGroup.svelte +80 -0
  35. package/_svseeds/MenuGroup.svelte.d.ts +52 -0
  36. package/_svseeds/MenuItem.svelte +120 -0
  37. package/_svseeds/MenuItem.svelte.d.ts +63 -0
  38. package/_svseeds/MenuList.svelte +227 -0
  39. package/_svseeds/MenuList.svelte.d.ts +92 -0
  40. package/_svseeds/MenuSeparator.svelte +70 -0
  41. package/_svseeds/MenuSeparator.svelte.d.ts +44 -0
  42. package/_svseeds/Modal.svelte +121 -0
  43. package/_svseeds/Modal.svelte.d.ts +54 -0
  44. package/_svseeds/MonthPicker.svelte +172 -0
  45. package/_svseeds/MonthPicker.svelte.d.ts +66 -0
  46. package/_svseeds/NumberField.svelte +213 -0
  47. package/_svseeds/NumberField.svelte.d.ts +79 -0
  48. package/_svseeds/NumberInput.svelte +395 -0
  49. package/_svseeds/NumberInput.svelte.d.ts +95 -0
  50. package/_svseeds/Pagination.svelte +271 -0
  51. package/_svseeds/Pagination.svelte.d.ts +78 -0
  52. package/_svseeds/Popover.svelte +215 -0
  53. package/_svseeds/Popover.svelte.d.ts +79 -0
  54. package/_svseeds/ProgressTracker.svelte +124 -0
  55. package/_svseeds/{_ProgressTracker.svelte.d.ts → ProgressTracker.svelte.d.ts} +23 -12
  56. package/_svseeds/SelectField.svelte +213 -0
  57. package/_svseeds/{_SelectField.svelte.d.ts → SelectField.svelte.d.ts} +31 -20
  58. package/_svseeds/Slider.svelte +139 -0
  59. package/_svseeds/Slider.svelte.d.ts +75 -0
  60. package/_svseeds/Sortable.svelte +801 -0
  61. package/_svseeds/Sortable.svelte.d.ts +164 -0
  62. package/_svseeds/SortableGroup.svelte +87 -0
  63. package/_svseeds/SortableGroup.svelte.d.ts +52 -0
  64. package/_svseeds/Splitter.svelte +194 -0
  65. package/_svseeds/Splitter.svelte.d.ts +71 -0
  66. package/_svseeds/Tabs.svelte +230 -0
  67. package/_svseeds/Tabs.svelte.d.ts +72 -0
  68. package/_svseeds/TagsInput.svelte +260 -0
  69. package/_svseeds/TagsInput.svelte.d.ts +103 -0
  70. package/_svseeds/TagsInputField.svelte +118 -84
  71. package/_svseeds/TagsInputField.svelte.d.ts +31 -35
  72. package/_svseeds/TextField.svelte +238 -0
  73. package/_svseeds/TextField.svelte.d.ts +93 -0
  74. package/_svseeds/Toast.svelte +290 -0
  75. package/_svseeds/Toast.svelte.d.ts +87 -0
  76. package/_svseeds/Toggle.svelte +131 -0
  77. package/_svseeds/Toggle.svelte.d.ts +57 -0
  78. package/_svseeds/ToggleGroup.svelte +231 -0
  79. package/_svseeds/ToggleGroup.svelte.d.ts +76 -0
  80. package/_svseeds/ToggleGroupField.svelte +138 -66
  81. package/_svseeds/ToggleGroupField.svelte.d.ts +51 -29
  82. package/_svseeds/Tooltip.svelte +342 -0
  83. package/_svseeds/Tooltip.svelte.d.ts +74 -0
  84. package/_svseeds/WheelPicker.svelte +326 -0
  85. package/_svseeds/WheelPicker.svelte.d.ts +84 -0
  86. package/_svseeds/_core.d.ts +239 -0
  87. package/_svseeds/_core.js +290 -0
  88. package/index.d.ts +43 -25
  89. package/index.js +40 -22
  90. package/package.json +3 -3
  91. package/_svseeds/_Button.svelte +0 -92
  92. package/_svseeds/_Button.svelte.d.ts +0 -49
  93. package/_svseeds/_CheckField.svelte +0 -182
  94. package/_svseeds/_CheckField.svelte.d.ts +0 -71
  95. package/_svseeds/_ColorPicker.svelte +0 -114
  96. package/_svseeds/_ColorPicker.svelte.d.ts +0 -17
  97. package/_svseeds/_ComboBox.svelte +0 -163
  98. package/_svseeds/_ComboBox.svelte.d.ts +0 -52
  99. package/_svseeds/_ContextMenu.svelte +0 -82
  100. package/_svseeds/_ContextMenu.svelte.d.ts +0 -37
  101. package/_svseeds/_Disclosure.svelte +0 -152
  102. package/_svseeds/_Disclosure.svelte.d.ts +0 -48
  103. package/_svseeds/_Drawer.svelte +0 -136
  104. package/_svseeds/_Drawer.svelte.d.ts +0 -48
  105. package/_svseeds/_HotkeyCapture.svelte +0 -113
  106. package/_svseeds/_HotkeyCapture.svelte.d.ts +0 -34
  107. package/_svseeds/_Modal.svelte +0 -107
  108. package/_svseeds/_Modal.svelte.d.ts +0 -44
  109. package/_svseeds/_ProgressTracker.svelte +0 -101
  110. package/_svseeds/_SelectField.svelte +0 -190
  111. package/_svseeds/_Slider.svelte +0 -104
  112. package/_svseeds/_Slider.svelte.d.ts +0 -62
  113. package/_svseeds/_Sortable.svelte +0 -795
  114. package/_svseeds/_Sortable.svelte.d.ts +0 -111
  115. package/_svseeds/_Tabs.svelte +0 -118
  116. package/_svseeds/_Tabs.svelte.d.ts +0 -42
  117. package/_svseeds/_TagsInput.svelte +0 -155
  118. package/_svseeds/_TagsInput.svelte.d.ts +0 -81
  119. package/_svseeds/_TextField.svelte +0 -206
  120. package/_svseeds/_TextField.svelte.d.ts +0 -81
  121. package/_svseeds/_Toast.svelte +0 -258
  122. package/_svseeds/_Toast.svelte.d.ts +0 -14
  123. package/_svseeds/_Toggle.svelte +0 -112
  124. package/_svseeds/_Toggle.svelte.d.ts +0 -51
  125. package/_svseeds/_ToggleGroup.svelte +0 -102
  126. package/_svseeds/_ToggleGroup.svelte.d.ts +0 -49
  127. package/_svseeds/_Tooltip.svelte +0 -312
  128. package/_svseeds/_Tooltip.svelte.d.ts +0 -28
  129. package/_svseeds/core.d.ts +0 -229
  130. package/_svseeds/core.js +0 -1
@@ -0,0 +1,349 @@
1
+ <!--
2
+ @component
3
+ ### Usage
4
+ Use standalone.
5
+ ```svelte
6
+ <Calendar {...props} />
7
+ ```
8
+ ### Types
9
+ default value: *`(value)`*
10
+ ```ts
11
+ type DayCtx = {
12
+ date: Temporal.PlainDate;
13
+ variant: string;
14
+ weekday: number;
15
+ today: boolean;
16
+ selected: boolean;
17
+ outside: boolean;
18
+ disabled: boolean;
19
+ };
20
+ type TransitionProp = {
21
+ fn?: (node: HTMLElement, params: any, options: { direction: "in" | "out" | "both" }) => import("svelte/transition").TransitionConfig;
22
+ params?: unknown;
23
+ };
24
+ interface CalendarProps {
25
+ value?: Temporal.PlainDate; // bindable; selected date
26
+ display?: Temporal.PlainYearMonth; // bindable; shown month
27
+ picking?: boolean; // bindable (false)
28
+ min?: Temporal.PlainDate;
29
+ max?: Temporal.PlainDate;
30
+ isDisabled?: (d: Temporal.PlainDate) => boolean;
31
+ outsideDays?: boolean; // (false)
32
+ fixedWeeks?: boolean; // (false)
33
+ firstDayOfWeek?: number; // (0=Sun)
34
+ locale?: string;
35
+ label?: Snippet<[Temporal.PlainYearMonth, string, boolean]>;
36
+ prev?: Snippet<[string]>;
37
+ next?: Snippet<[string]>;
38
+ weekday?: Snippet<[number, string]>;
39
+ day?: Snippet<[DayCtx]>;
40
+ bottom?: Snippet<[string, () => void]>;
41
+ monthPicker?: Omit<MonthPickerProps, "value" | "min" | "max" | "variant">;
42
+ transition?: TransitionProp;
43
+ styling?: SVSClass;
44
+ variant?: SVSVariant; // (VARIANT.NEUTRAL)
45
+ }
46
+ ```
47
+ ### Anatomy
48
+ ```svelte
49
+ <div class="whole" role="application">
50
+ <div class="top">
51
+ <button class="left" />
52
+ <button class="label" />
53
+ <button class="right" />
54
+ </div>
55
+ <div class="middle">
56
+ <div class="aux" role="row">weekday headers</div>
57
+ <div class="main" role="grid">day cells</div>
58
+ MonthPicker while picking
59
+ </div>
60
+ <div class="bottom" conditional>{bottom}</div>
61
+ </div>
62
+ ```
63
+ ### Behavior
64
+ - Prev/next page `display`; the label toggles an embedded `_MonthPicker`.
65
+ - Enabled day clicks and Enter/Space select a single `Temporal.PlainDate`.
66
+ - Day cells expose `data-today`, `data-selected`, `data-outside`, `data-disabled`, and `data-weekday`.
67
+ - Arrow, Home/End, PageUp/PageDown, and Shift+PageUp/PageDown update roving focus.
68
+ -->
69
+ <script module lang="ts">
70
+ export type DayCtx = {
71
+ date: Temporal.PlainDate;
72
+ variant: string;
73
+ weekday: number;
74
+ today: boolean;
75
+ selected: boolean;
76
+ outside: boolean;
77
+ disabled: boolean;
78
+ };
79
+ export type TransitionProp = {
80
+ fn?: (node: HTMLElement, params: any, options: { direction: "in" | "out" | "both" }) => import("svelte/transition").TransitionConfig;
81
+ params?: unknown;
82
+ };
83
+ export interface CalendarProps {
84
+ value?: Temporal.PlainDate;
85
+ display?: Temporal.PlainYearMonth;
86
+ picking?: boolean;
87
+ min?: Temporal.PlainDate;
88
+ max?: Temporal.PlainDate;
89
+ isDisabled?: (d: Temporal.PlainDate) => boolean;
90
+ outsideDays?: boolean;
91
+ fixedWeeks?: boolean;
92
+ firstDayOfWeek?: number;
93
+ locale?: string;
94
+ label?: Snippet<[Temporal.PlainYearMonth, string, boolean]>;
95
+ prev?: Snippet<[string]>;
96
+ next?: Snippet<[string]>;
97
+ weekday?: Snippet<[number, string]>;
98
+ day?: Snippet<[DayCtx]>;
99
+ bottom?: Snippet<[string, () => void]>;
100
+ monthPicker?: Omit<MonthPickerProps, "value" | "min" | "max" | "variant">;
101
+ transition?: TransitionProp;
102
+ styling?: SVSClass;
103
+ variant?: SVSVariant;
104
+ }
105
+ export type CalendarReqdProps = never;
106
+ export type CalendarBindProps = "value" | "display" | "picking";
107
+
108
+ export const _CALENDAR_PRESET = "svs-calendar";
109
+
110
+ import { tick, untrack } from "svelte";
111
+ import { VARIANT, PARTS, _fnClass, shouldReduceMotion } from "./_core";
112
+ import MonthPicker from "./MonthPicker.svelte";
113
+ import type { Snippet } from "svelte";
114
+ import type { KeyboardEventHandler, MouseEventHandler } from "svelte/elements";
115
+ import type { SVSClass, SVSVariant } from "./_core";
116
+ import type { MonthPickerProps } from "./MonthPicker.svelte";
117
+ </script>
118
+
119
+ <script lang="ts">
120
+ // prettier-ignore
121
+ let { value = $bindable(), display = $bindable(), picking = $bindable(false), min, max, isDisabled, outsideDays = false, fixedWeeks = false, firstDayOfWeek = 0, locale, label, prev, next, weekday, day, bottom, monthPicker, transition, styling, variant = VARIANT.NEUTRAL }: CalendarProps = $props();
122
+
123
+ // *** Initialize *** //
124
+ const cls = $derived(_fnClass(_CALENDAR_PRESET, styling));
125
+ const uid = $props.id();
126
+ const idCaption = `${uid}-caption`;
127
+ // svelte-ignore state_referenced_locally
128
+ if (display === undefined) display = (value ?? Temporal.Now.plainDateISO()).toPlainYearMonth();
129
+ const today = $derived(Temporal.Now.plainDateISO());
130
+
131
+ // *** States *** //
132
+ const initialDisplay = display as Temporal.PlainYearMonth;
133
+ let focused = $state(value && sameMonth(value, initialDisplay) ? value : firstOf(initialDisplay));
134
+ const firstWeekday = $derived(normWeekday(firstDayOfWeek));
135
+ const monthStart = $derived(firstOf(currentDisplay()));
136
+ const captionText = $derived(monthStart.toLocaleString(locale, { year: "numeric", month: "long" }));
137
+ const appLabel = $derived(`Calendar, ${captionText}`);
138
+ const weekdayLabels = $derived.by(() => {
139
+ const short = new Intl.DateTimeFormat(locale, { weekday: "narrow" });
140
+ const long = new Intl.DateTimeFormat(locale, { weekday: "long" });
141
+ return Array.from({ length: 7 }, (_, i) => {
142
+ const weekday = (firstWeekday + i) % 7;
143
+ const d = dateForWeekday(weekday);
144
+ return { weekday, text: short.format(d), full: long.format(d) };
145
+ });
146
+ });
147
+ const weeks = $derived.by(() => {
148
+ const first = monthStart;
149
+ const lead = (weekdayOf(first) - firstWeekday + 7) % 7;
150
+ const rows = fixedWeeks ? 6 : Math.ceil((lead + first.daysInMonth) / 7);
151
+ const start = first.subtract({ days: lead });
152
+ return Array.from({ length: rows }, (_, r) => Array.from({ length: 7 }, (_, c) => start.add({ days: r * 7 + c })));
153
+ });
154
+ const reduced = $derived(typeof window !== "undefined" && shouldReduceMotion());
155
+ const tfn = $derived(!reduced && transition?.fn ? transition.fn : noop);
156
+ const tparams = $derived(transition?.params as any);
157
+
158
+ // *** Reactive Handlers *** //
159
+ $effect.pre(() => {
160
+ display;
161
+ value;
162
+ untrack(() => syncFocus());
163
+ });
164
+
165
+ function syncFocus() {
166
+ const shown = currentDisplay();
167
+ if (sameMonth(focused, shown)) return;
168
+ focused = value && sameMonth(value, shown) ? value : firstOf(shown);
169
+ }
170
+ function currentDisplay(): Temporal.PlainYearMonth {
171
+ return display ?? initialDisplay;
172
+ }
173
+ function noop() {
174
+ return {};
175
+ }
176
+ function firstOf(ym: Temporal.PlainYearMonth): Temporal.PlainDate {
177
+ return Temporal.PlainDate.from({ year: ym.year, month: ym.month, day: 1 });
178
+ }
179
+ function normWeekday(n: number): number {
180
+ return ((Math.trunc(n) % 7) + 7) % 7;
181
+ }
182
+ function weekdayOf(d: Temporal.PlainDate): number {
183
+ return d.dayOfWeek % 7;
184
+ }
185
+ function sameMonth(d: Temporal.PlainDate, ym: Temporal.PlainYearMonth): boolean {
186
+ return d.year === ym.year && d.month === ym.month;
187
+ }
188
+ function dateForWeekday(weekday: number): Date {
189
+ return new Date(Date.UTC(2023, 0, 1 + weekday));
190
+ }
191
+ function inRange(d: Temporal.PlainDate): boolean {
192
+ if (min && Temporal.PlainDate.compare(d, min) < 0) return false;
193
+ if (max && Temporal.PlainDate.compare(d, max) > 0) return false;
194
+ return true;
195
+ }
196
+ function disabled(d: Temporal.PlainDate): boolean {
197
+ return !inRange(d) || !!isDisabled?.(d);
198
+ }
199
+ function selected(d: Temporal.PlainDate): boolean {
200
+ return !!value?.equals(d);
201
+ }
202
+ function variantOf(d: Temporal.PlainDate): string {
203
+ if (disabled(d)) return VARIANT.INACTIVE;
204
+ if (selected(d)) return VARIANT.ACTIVE;
205
+ return VARIANT.NEUTRAL;
206
+ }
207
+ function ctx(d: Temporal.PlainDate): DayCtx {
208
+ return {
209
+ date: d,
210
+ variant: variantOf(d),
211
+ weekday: weekdayOf(d),
212
+ today: today.equals(d),
213
+ selected: selected(d),
214
+ outside: !sameMonth(d, currentDisplay()),
215
+ disabled: disabled(d),
216
+ };
217
+ }
218
+ function labelOf(d: Temporal.PlainDate): string {
219
+ return d.toLocaleString(locale, { dateStyle: "long" });
220
+ }
221
+ function displayOf(d: Temporal.PlainDate) {
222
+ display = d.toPlainYearMonth();
223
+ }
224
+ function prevMonth() {
225
+ display = currentDisplay().subtract({ months: 1 });
226
+ }
227
+ function nextMonth() {
228
+ display = currentDisplay().add({ months: 1 });
229
+ }
230
+ function select(d: Temporal.PlainDate) {
231
+ if (disabled(d)) return;
232
+ value = d;
233
+ focused = d;
234
+ if (!sameMonth(d, currentDisplay())) displayOf(d);
235
+ }
236
+ function setToday() {
237
+ displayOf(today);
238
+ select(today);
239
+ }
240
+ function move(d: Temporal.PlainDate) {
241
+ focused = d;
242
+ if (!sameMonth(d, currentDisplay())) displayOf(d);
243
+ tick().then(() => focusDate(d));
244
+ }
245
+ function focusDate(d: Temporal.PlainDate) {
246
+ const el = document.querySelector(`[data-svs-calendar="${uid}"] [data-date="${d.toString()}"]`) as HTMLElement | null;
247
+ el?.focus();
248
+ }
249
+ function weekEdge(toEnd: boolean): Temporal.PlainDate {
250
+ const offset = (weekdayOf(focused) - firstWeekday + 7) % 7;
251
+ return focused.add({ days: toEnd ? 6 - offset : -offset });
252
+ }
253
+
254
+ // *** Event Handlers *** //
255
+ const hprev: MouseEventHandler<HTMLButtonElement> = () => prevMonth();
256
+ const hnext: MouseEventHandler<HTMLButtonElement> = () => nextMonth();
257
+ const hlabel: MouseEventHandler<HTMLButtonElement> = () => (picking = !picking);
258
+ const hkeydown: KeyboardEventHandler<HTMLDivElement> = (ev) => {
259
+ let next: Temporal.PlainDate | undefined;
260
+ if (ev.key === "ArrowLeft") next = focused.subtract({ days: 1 });
261
+ else if (ev.key === "ArrowRight") next = focused.add({ days: 1 });
262
+ else if (ev.key === "ArrowUp") next = focused.subtract({ days: 7 });
263
+ else if (ev.key === "ArrowDown") next = focused.add({ days: 7 });
264
+ else if (ev.key === "Home") next = weekEdge(false);
265
+ else if (ev.key === "End") next = weekEdge(true);
266
+ else if (ev.key === "PageUp") next = focused.subtract(ev.shiftKey ? { years: 1 } : { months: 1 });
267
+ else if (ev.key === "PageDown") next = focused.add(ev.shiftKey ? { years: 1 } : { months: 1 });
268
+ else if (ev.key === "Enter" || ev.key === " ") select(focused);
269
+ else return;
270
+ ev.preventDefault();
271
+ if (next) move(next);
272
+ };
273
+ </script>
274
+
275
+ <!---------------------------------------->
276
+
277
+ <div class={cls(PARTS.WHOLE, variant)} role="application" aria-label={appLabel} data-svs-calendar={uid}>
278
+ <div class={cls(PARTS.TOP, variant)}>
279
+ <button class={cls(PARTS.LEFT, variant)} type="button" onclick={hprev}
280
+ >{#if prev}{@render prev(variant)}{:else}Previous{/if}</button
281
+ >
282
+ <button id={idCaption} class={cls(PARTS.LABEL, variant)} type="button" aria-expanded={picking} onclick={hlabel}>
283
+ {#if label}{@render label(currentDisplay(), variant, picking)}{:else}{captionText}{/if}
284
+ </button>
285
+ <button class={cls(PARTS.RIGHT, variant)} type="button" onclick={hnext}
286
+ >{#if next}{@render next(variant)}{:else}Next{/if}</button
287
+ >
288
+ </div>
289
+
290
+ {#if picking}
291
+ <div class={cls(PARTS.MIDDLE, variant)} transition:tfn|local={tparams}>
292
+ <MonthPicker
293
+ {...monthPicker}
294
+ bind:value={() => currentDisplay(), (v) => (display = v)}
295
+ min={min?.toPlainYearMonth()}
296
+ max={max?.toPlainYearMonth()}
297
+ {variant}
298
+ />
299
+ </div>
300
+ {:else}
301
+ <div class={cls(PARTS.MIDDLE, variant)} transition:tfn|local={tparams}>
302
+ <div class={cls(PARTS.AUX, variant)} role="row">
303
+ {#each weekdayLabels as wl}
304
+ <span class={cls(PARTS.EXTRA, VARIANT.NEUTRAL)} role="columnheader" data-weekday={wl.weekday} aria-label={wl.full}>
305
+ {#if weekday}{@render weekday(wl.weekday, variant)}{:else}{wl.text}{/if}
306
+ </span>
307
+ {/each}
308
+ </div>
309
+ <div class={cls(PARTS.MAIN, variant)} role="grid" tabindex="-1" aria-labelledby={idCaption} onkeydown={hkeydown}>
310
+ {#each weeks as week}
311
+ <div role="row">
312
+ {#each week as cell}
313
+ {@const outside = !sameMonth(cell, currentDisplay())}
314
+ {#if outside && !outsideDays}
315
+ <span class={cls(PARTS.EXTRA, VARIANT.NEUTRAL)} role="gridcell" aria-hidden="true"></span>
316
+ {:else}
317
+ {@const c = ctx(cell)}
318
+ <button
319
+ class={cls(PARTS.EXTRA, c.variant)}
320
+ type="button"
321
+ role="gridcell"
322
+ tabindex={cell.equals(focused) ? 0 : -1}
323
+ aria-selected={c.selected}
324
+ aria-disabled={c.disabled || undefined}
325
+ aria-current={c.today ? "date" : undefined}
326
+ aria-label={labelOf(cell)}
327
+ data-date={cell.toString()}
328
+ data-today={c.today || undefined}
329
+ data-selected={c.selected || undefined}
330
+ data-outside={c.outside || undefined}
331
+ data-disabled={c.disabled || undefined}
332
+ data-weekday={c.weekday}
333
+ onfocus={() => (focused = cell)}
334
+ onclick={() => select(cell)}
335
+ >
336
+ {#if day}{@render day(c)}{:else}{cell.day}{/if}
337
+ </button>
338
+ {/if}
339
+ {/each}
340
+ </div>
341
+ {/each}
342
+ </div>
343
+ </div>
344
+ {/if}
345
+
346
+ {#if bottom}
347
+ <div class={cls(PARTS.BOTTOM, variant)}>{@render bottom(variant, setToday)}</div>
348
+ {/if}
349
+ </div>
@@ -0,0 +1,113 @@
1
+ export type DayCtx = {
2
+ date: Temporal.PlainDate;
3
+ variant: string;
4
+ weekday: number;
5
+ today: boolean;
6
+ selected: boolean;
7
+ outside: boolean;
8
+ disabled: boolean;
9
+ };
10
+ export type TransitionProp = {
11
+ fn?: (node: HTMLElement, params: any, options: {
12
+ direction: "in" | "out" | "both";
13
+ }) => import("svelte/transition").TransitionConfig;
14
+ params?: unknown;
15
+ };
16
+ export interface CalendarProps {
17
+ value?: Temporal.PlainDate;
18
+ display?: Temporal.PlainYearMonth;
19
+ picking?: boolean;
20
+ min?: Temporal.PlainDate;
21
+ max?: Temporal.PlainDate;
22
+ isDisabled?: (d: Temporal.PlainDate) => boolean;
23
+ outsideDays?: boolean;
24
+ fixedWeeks?: boolean;
25
+ firstDayOfWeek?: number;
26
+ locale?: string;
27
+ label?: Snippet<[Temporal.PlainYearMonth, string, boolean]>;
28
+ prev?: Snippet<[string]>;
29
+ next?: Snippet<[string]>;
30
+ weekday?: Snippet<[number, string]>;
31
+ day?: Snippet<[DayCtx]>;
32
+ bottom?: Snippet<[string, () => void]>;
33
+ monthPicker?: Omit<MonthPickerProps, "value" | "min" | "max" | "variant">;
34
+ transition?: TransitionProp;
35
+ styling?: SVSClass;
36
+ variant?: SVSVariant;
37
+ }
38
+ export type CalendarReqdProps = never;
39
+ export type CalendarBindProps = "value" | "display" | "picking";
40
+ export declare const _CALENDAR_PRESET = "svs-calendar";
41
+ import type { Snippet } from "svelte";
42
+ import type { SVSClass, SVSVariant } from "./_core";
43
+ import type { MonthPickerProps } from "./MonthPicker.svelte";
44
+ /**
45
+ * ### Usage
46
+ * Use standalone.
47
+ * ```svelte
48
+ * <Calendar {...props} />
49
+ * ```
50
+ * ### Types
51
+ * default value: *`(value)`*
52
+ * ```ts
53
+ * type DayCtx = {
54
+ * date: Temporal.PlainDate;
55
+ * variant: string;
56
+ * weekday: number;
57
+ * today: boolean;
58
+ * selected: boolean;
59
+ * outside: boolean;
60
+ * disabled: boolean;
61
+ * };
62
+ * type TransitionProp = {
63
+ * fn?: (node: HTMLElement, params: any, options: { direction: "in" | "out" | "both" }) => import("svelte/transition").TransitionConfig;
64
+ * params?: unknown;
65
+ * };
66
+ * interface CalendarProps {
67
+ * value?: Temporal.PlainDate; // bindable; selected date
68
+ * display?: Temporal.PlainYearMonth; // bindable; shown month
69
+ * picking?: boolean; // bindable (false)
70
+ * min?: Temporal.PlainDate;
71
+ * max?: Temporal.PlainDate;
72
+ * isDisabled?: (d: Temporal.PlainDate) => boolean;
73
+ * outsideDays?: boolean; // (false)
74
+ * fixedWeeks?: boolean; // (false)
75
+ * firstDayOfWeek?: number; // (0=Sun)
76
+ * locale?: string;
77
+ * label?: Snippet<[Temporal.PlainYearMonth, string, boolean]>;
78
+ * prev?: Snippet<[string]>;
79
+ * next?: Snippet<[string]>;
80
+ * weekday?: Snippet<[number, string]>;
81
+ * day?: Snippet<[DayCtx]>;
82
+ * bottom?: Snippet<[string, () => void]>;
83
+ * monthPicker?: Omit<MonthPickerProps, "value" | "min" | "max" | "variant">;
84
+ * transition?: TransitionProp;
85
+ * styling?: SVSClass;
86
+ * variant?: SVSVariant; // (VARIANT.NEUTRAL)
87
+ * }
88
+ * ```
89
+ * ### Anatomy
90
+ * ```svelte
91
+ * <div class="whole" role="application">
92
+ * <div class="top">
93
+ * <button class="left" />
94
+ * <button class="label" />
95
+ * <button class="right" />
96
+ * </div>
97
+ * <div class="middle">
98
+ * <div class="aux" role="row">weekday headers</div>
99
+ * <div class="main" role="grid">day cells</div>
100
+ * MonthPicker while picking
101
+ * </div>
102
+ * <div class="bottom" conditional>{bottom}</div>
103
+ * </div>
104
+ * ```
105
+ * ### Behavior
106
+ * - Prev/next page `display`; the label toggles an embedded `_MonthPicker`.
107
+ * - Enabled day clicks and Enter/Space select a single `Temporal.PlainDate`.
108
+ * - Day cells expose `data-today`, `data-selected`, `data-outside`, `data-disabled`, and `data-weekday`.
109
+ * - Arrow, Home/End, PageUp/PageDown, and Shift+PageUp/PageDown update roving focus.
110
+ */
111
+ declare const Calendar: import("svelte").Component<CalendarProps, {}, "display" | "value" | "picking">;
112
+ type Calendar = ReturnType<typeof Calendar>;
113
+ export default Calendar;
@@ -0,0 +1,228 @@
1
+ <!--
2
+ @component
3
+ ### Usage
4
+ Use standalone.
5
+ ```svelte
6
+ <CheckField {...props} />
7
+ ```
8
+ ### Types
9
+ default value: *`(value)`*
10
+ ```ts
11
+ interface CheckFieldProps extends Omit<HTMLInputAttributes, "type" | "value" | "id"> {
12
+ options: SvelteMap<string, string> | Map<string, string>;
13
+ label?: string;
14
+ extra?: string;
15
+ aux?: Snippet<[string[], string, HTMLInputElement[]]>; // Snippet<[values,variant,elements]>
16
+ bottom?: string;
17
+ reserve?: boolean; // (false)
18
+ values?: string[]; // bindable
19
+ multiple?: boolean; // (true)
20
+ validations?: CheckFieldValidation[];
21
+ constraints?: CheckFieldConstraint[];
22
+ attach?: Attachment<HTMLInputElement>;
23
+ elements?: HTMLInputElement[]; // bindable
24
+ styling?: SVSClass;
25
+ variant?: SVSVariant; // bindable (VARIANT.NEUTRAL)
26
+ // class & other HTMLInputAttributes are passed to each <input> via ...rest (class is merged onto each input; required is applied dynamically)
27
+ }
28
+ type CheckFieldValidation = SVSFieldValidation<string[]>;
29
+ type CheckFieldConstraint = SVSFieldConstraint;
30
+ ```
31
+ ### Anatomy
32
+ ```svelte
33
+ <div class="whole">
34
+ <div class="top" conditional: label or aux>
35
+ <span class="label" conditional>
36
+ {label}
37
+ <span class="extra" conditional>{extra}</span>
38
+ </span>
39
+ <span class="aux" conditional>{aux}</span>
40
+ </div>
41
+ <div class="middle">
42
+ {#each options as { value, text }, i}
43
+ <label class="main">
44
+ <input class="left" {...rest} />
45
+ <span class="right">{text}</span>
46
+ </label>
47
+ {/each}
48
+ </div>
49
+ <div class="bottom" conditional: has text, or always when reserve>{bottom}</div>
50
+ </div>
51
+ ```
52
+ -->
53
+ <script module lang="ts">
54
+ export interface CheckFieldProps extends Omit<HTMLInputAttributes, "type" | "value" | "id"> {
55
+ options: SvelteMap<string, string> | Map<string, string>;
56
+ label?: string;
57
+ extra?: string;
58
+ aux?: Snippet<[string[], string, HTMLInputElement[]]>; // Snippet<[values,variant,elements]>
59
+ bottom?: string;
60
+ reserve?: boolean; // (false)
61
+ values?: string[]; // bindable
62
+ multiple?: boolean; // (true)
63
+ validations?: CheckFieldValidation[];
64
+ constraints?: CheckFieldConstraint[];
65
+ attach?: Attachment<HTMLInputElement>;
66
+ elements?: HTMLInputElement[]; // bindable
67
+ styling?: SVSClass;
68
+ variant?: SVSVariant; // bindable (VARIANT.NEUTRAL)
69
+ }
70
+ export type CheckFieldReqdProps = "options";
71
+ export type CheckFieldBindProps = "values" | "variant" | "elements";
72
+ export type CheckFieldValidation = SVSFieldValidation<string[]>;
73
+ export type CheckFieldConstraint = SVSFieldConstraint;
74
+
75
+ export const _CHECK_FIELD_PRESET = "svs-check-field";
76
+
77
+ import { untrack, onMount } from "svelte";
78
+ import { VARIANT, PARTS, _fnClass, _isNeutral } from "./_core";
79
+ import type { Snippet } from "svelte";
80
+ import type { Attachment } from "svelte/attachments";
81
+ import type { SvelteMap } from "svelte/reactivity";
82
+ import type { HTMLInputAttributes, ChangeEventHandler, EventHandler } from "svelte/elements";
83
+ import type { SVSClass, SVSVariant, SVSFieldValidation, SVSFieldConstraint } from "./_core";
84
+ </script>
85
+
86
+ <script lang="ts">
87
+ // prettier-ignore
88
+ let { options, label, extra, aux, bottom, reserve = false, values = $bindable([]), multiple = true, validations = [], constraints = [], name, onchange, oninvalid, attach, elements = $bindable([]), styling, variant = $bindable(VARIANT.NEUTRAL), class: c, required = false, ...rest }: CheckFieldProps = $props();
89
+
90
+ // *** Initialize *** //
91
+ const cls = $derived(_fnClass(_CHECK_FIELD_PRESET, styling));
92
+ const type = $derived(multiple ? "checkbox" : "radio");
93
+ const uid = $props.id();
94
+ const nm = $derived(name?.trim() ? name : `${uid}-name`);
95
+ const idLabel = $derived(label?.trim() ? `${uid}-label` : undefined);
96
+ const idDesc = $derived(bottom?.trim() ? `${uid}-desc` : undefined);
97
+ const idErr = $derived(idDesc ?? `${uid}-err`);
98
+ const roleGroup = $derived(multiple ? "group" : "radiogroup");
99
+ let errmsg = $state("");
100
+ const message = $derived(variant === VARIANT.INACTIVE ? errmsg || bottom : bottom);
101
+
102
+ // *** States *** //
103
+ let neutral = $state(_isNeutral(variant) ? variant : VARIANT.NEUTRAL);
104
+ $effect(() => {
105
+ neutral = _isNeutral(variant) ? variant : neutral;
106
+ });
107
+ const live = $derived(variant === VARIANT.INACTIVE ? "alert" : undefined);
108
+ const invalid = $derived(variant === VARIANT.INACTIVE ? true : undefined);
109
+ const idMsg = $derived(variant === VARIANT.INACTIVE && message?.trim() ? idErr : undefined);
110
+ const reqd = $derived(required && (!multiple || !values.length) ? true : undefined);
111
+ function shift(oninvalid: boolean = false, msg?: string) {
112
+ const vmsg = elements[0]?.validationMessage ?? "";
113
+ variant = msg ? VARIANT.INACTIVE : !values.length && !oninvalid ? neutral : vmsg ? VARIANT.INACTIVE : VARIANT.ACTIVE;
114
+ errmsg = msg ? msg : vmsg;
115
+ }
116
+ function verify() {
117
+ if (!elements[0]) return;
118
+ for (const v of validations) {
119
+ const msg = v({ value: values, validity: elements[0].validity, element: elements[0] });
120
+ if (msg) return elements[0].setCustomValidity(msg);
121
+ }
122
+ elements[0].setCustomValidity("");
123
+ }
124
+ function check(value: string): string {
125
+ if (!elements[0]) return "";
126
+ for (const c of constraints) {
127
+ const msg = c({ value, values, validity: elements[0].validity, element: elements[0] });
128
+ if (msg) return msg;
129
+ }
130
+ return "";
131
+ }
132
+
133
+ // *** Reactive Handlers *** //
134
+ const opts = $derived(Array.from(options, ([value, text]) => ({ value, text, checked: values.includes(value) })));
135
+ $effect.pre(() => {
136
+ values;
137
+ untrack(() => validate(true));
138
+ });
139
+ function validate(effect?: boolean) {
140
+ if (effect && _isNeutral(variant)) return;
141
+ verify();
142
+ shift();
143
+ }
144
+
145
+ // *** Event Handlers *** //
146
+ const hchange: ChangeEventHandler<HTMLInputElement> = (ev) => {
147
+ onchange?.(ev);
148
+ const input = ev.currentTarget;
149
+ if (input.checked) {
150
+ const msg = check(input.value);
151
+ if (msg) {
152
+ input.checked = false;
153
+ shift(false, msg);
154
+ return;
155
+ }
156
+ }
157
+ values = elements.filter((el) => el.checked).map((el) => el.value);
158
+ validate();
159
+ };
160
+ const hinvalid: EventHandler<Event, HTMLInputElement> = (ev) => {
161
+ oninvalid?.(ev);
162
+ ev.preventDefault();
163
+ shift(true);
164
+ };
165
+ onMount(() => verify());
166
+ </script>
167
+
168
+ <!---------------------------------------->
169
+
170
+ {#if opts.length}
171
+ <div class={cls(PARTS.WHOLE, variant)} role="group" aria-labelledby={idLabel}>
172
+ {#if label?.trim() || aux}
173
+ <div class={cls(PARTS.TOP, variant)}>
174
+ {@render lbl()}
175
+ {#if aux}
176
+ <span class={cls(PARTS.AUX, variant)}>{@render aux(values, variant, elements)}</span>
177
+ {/if}
178
+ </div>
179
+ {/if}
180
+ {@render main()}
181
+ {#if reserve || message?.trim()}
182
+ <div class={cls(PARTS.BOTTOM, variant)} id={idDesc ?? idErr} role={live}>{message}</div>
183
+ {/if}
184
+ </div>
185
+ {/if}
186
+
187
+ {#snippet lbl()}
188
+ {#if label?.trim()}
189
+ <span class={cls(PARTS.LABEL, variant)} id={idLabel}>
190
+ {label}
191
+ {#if extra?.trim()}
192
+ <span class={cls(PARTS.EXTRA, variant)}>{extra}</span>
193
+ {/if}
194
+ </span>
195
+ {/if}
196
+ {/snippet}
197
+ {#snippet main()}
198
+ <div
199
+ class={cls(PARTS.MIDDLE, variant)}
200
+ role={roleGroup}
201
+ aria-labelledby={idLabel}
202
+ aria-describedby={idDesc}
203
+ aria-invalid={!multiple ? invalid : undefined}
204
+ aria-errormessage={!multiple ? idMsg : undefined}
205
+ >
206
+ {#each opts as { value, text, checked }, i (value)}
207
+ {@const stat = checked ? VARIANT.ACTIVE : neutral}
208
+ <label class={cls(PARTS.MAIN, stat)}>
209
+ <input
210
+ bind:this={elements[i]}
211
+ class={[cls(PARTS.LEFT, stat), c]}
212
+ {...rest}
213
+ required={reqd}
214
+ aria-invalid={multiple ? invalid : undefined}
215
+ aria-errormessage={multiple ? idMsg : undefined}
216
+ {value}
217
+ name={nm}
218
+ {type}
219
+ {checked}
220
+ onchange={hchange}
221
+ oninvalid={hinvalid}
222
+ {@attach attach}
223
+ />
224
+ <span class={cls(PARTS.RIGHT, stat)}>{text}</span>
225
+ </label>
226
+ {/each}
227
+ </div>
228
+ {/snippet}