svseeds 0.7.1 → 0.8.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.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/_svseeds/Accordion.svelte +10 -5
  3. package/_svseeds/Accordion.svelte.d.ts +3 -3
  4. package/_svseeds/Calendar.svelte +69 -47
  5. package/_svseeds/Calendar.svelte.d.ts +37 -35
  6. package/_svseeds/CheckField.svelte +19 -15
  7. package/_svseeds/ColorPicker.svelte +8 -4
  8. package/_svseeds/ColorPicker.svelte.d.ts +6 -3
  9. package/_svseeds/ComboBox.svelte +5 -7
  10. package/_svseeds/DarkToggle.svelte +2 -2
  11. package/_svseeds/DateField.svelte +11 -14
  12. package/_svseeds/DateInput.svelte +26 -23
  13. package/_svseeds/DateInput.svelte.d.ts +22 -16
  14. package/_svseeds/Disclosure.svelte +17 -7
  15. package/_svseeds/Drawer.svelte +1 -2
  16. package/_svseeds/Drawer.svelte.d.ts +1 -2
  17. package/_svseeds/FileField.svelte +19 -15
  18. package/_svseeds/FileInput.svelte +3 -11
  19. package/_svseeds/HotkeyCapture.svelte +3 -2
  20. package/_svseeds/HotkeyCapture.svelte.d.ts +3 -0
  21. package/_svseeds/Modal.svelte +5 -3
  22. package/_svseeds/NumberField.svelte +11 -14
  23. package/_svseeds/NumberInput.svelte +39 -53
  24. package/_svseeds/NumberInput.svelte.d.ts +11 -21
  25. package/_svseeds/Pagination.svelte +29 -31
  26. package/_svseeds/Pagination.svelte.d.ts +16 -13
  27. package/_svseeds/Popover.svelte +64 -18
  28. package/_svseeds/Popover.svelte.d.ts +16 -15
  29. package/_svseeds/SelectField.svelte +10 -13
  30. package/_svseeds/Sortable.svelte +43 -8
  31. package/_svseeds/Sortable.svelte.d.ts +8 -3
  32. package/_svseeds/SortableGroup.svelte +8 -5
  33. package/_svseeds/SortableGroup.svelte.d.ts +6 -3
  34. package/_svseeds/Tabs.svelte +22 -19
  35. package/_svseeds/Tabs.svelte.d.ts +8 -5
  36. package/_svseeds/TagsInput.svelte +3 -13
  37. package/_svseeds/TagsInputField.svelte +11 -14
  38. package/_svseeds/TextField.svelte +10 -13
  39. package/_svseeds/Toast.svelte +38 -6
  40. package/_svseeds/Toast.svelte.d.ts +24 -2
  41. package/_svseeds/Toggle.svelte +1 -1
  42. package/_svseeds/ToggleGroup.svelte +39 -41
  43. package/_svseeds/ToggleGroup.svelte.d.ts +13 -10
  44. package/_svseeds/ToggleGroupField.svelte +29 -29
  45. package/_svseeds/ToggleGroupField.svelte.d.ts +12 -12
  46. package/_svseeds/Tooltip.svelte +12 -7
  47. package/_svseeds/Tooltip.svelte.d.ts +7 -7
  48. package/_svseeds/WheelPicker.svelte +21 -13
  49. package/_svseeds/ZSortableA11y.svelte +1082 -0
  50. package/_svseeds/ZSortableA11y.svelte.d.ts +182 -0
  51. package/_svseeds/_core.d.ts +37 -1
  52. package/_svseeds/_core.js +70 -1
  53. package/index.d.ts +10 -9
  54. package/index.js +1 -0
  55. package/package.json +2 -2
@@ -93,7 +93,7 @@
93
93
  export const _DATE_FIELD_PRESET = "svs-date-field";
94
94
 
95
95
  import { onMount, untrack } from "svelte";
96
- import { PARTS, VARIANT, _fnClass, _isNeutral } from "./_core";
96
+ import { PARTS, VARIANT, _fieldAria, _fieldIds, _fieldMessage, _fnClass, _isNeutral, _verify } from "./_core";
97
97
  import DateInput, { _DATE_INPUT_PRESET, _setDateInputContext } from "./DateInput.svelte";
98
98
  import type { Snippet } from "svelte";
99
99
  import type { SVSClass, SVSFieldValidation, SVSVariant } from "./_core";
@@ -108,12 +108,14 @@
108
108
  const cls = $derived(_fnClass(_DATE_FIELD_PRESET, styling));
109
109
  const uid = $props.id();
110
110
  const id = $derived(label?.trim() ? `${uid}-ctrl` : undefined);
111
- const idLabel = $derived(label?.trim() ? `${uid}-label` : undefined);
112
- const idDesc = $derived(bottom?.trim() ? `${uid}-desc` : undefined);
113
- const idErr = $derived(idDesc ?? `${uid}-err`);
111
+ const ids = $derived(_fieldIds(uid, label, bottom));
112
+ const idLabel = $derived(ids.idLabel);
113
+ const idDesc = $derived(ids.idDesc);
114
+ const idErr = $derived(ids.idErr);
114
115
  let errmsg = $state("");
115
- const message = $derived(variant === VARIANT.INACTIVE ? errmsg || bottom : bottom);
116
- const idMsg = $derived(variant === VARIANT.INACTIVE && message?.trim() ? idErr : undefined);
116
+ const message = $derived(_fieldMessage(variant, errmsg, bottom));
117
+ const aria = $derived(_fieldAria(variant, message, idErr));
118
+ const idMsg = $derived(aria.idMsg);
117
119
  let proxyEl = $state<HTMLInputElement>();
118
120
 
119
121
  // *** Initialize Context *** //
@@ -152,7 +154,7 @@
152
154
  $effect(() => {
153
155
  neutral = _isNeutral(variant) ? variant : neutral;
154
156
  });
155
- const live = $derived(variant === VARIANT.INACTIVE ? "alert" : undefined);
157
+ const live = $derived(aria.live);
156
158
  const validEl = $derived(native ? element : proxyEl);
157
159
  function shift(oninvalid?: boolean) {
158
160
  const vmsg = validEl?.validationMessage ?? "";
@@ -160,12 +162,7 @@
160
162
  errmsg = vmsg;
161
163
  }
162
164
  function verify() {
163
- if (!validEl) return;
164
- for (const v of validations) {
165
- const msg = v({ value, validity: validEl.validity, element: validEl });
166
- if (msg) return validEl.setCustomValidity(msg);
167
- }
168
- validEl.setCustomValidity("");
165
+ _verify(validEl, validations, value);
169
166
  }
170
167
 
171
168
  // *** Reactive Handlers *** //
@@ -229,7 +226,7 @@
229
226
  {@render participant()}
230
227
  </div>
231
228
  {#if reserve || message?.trim()}
232
- <div class={cls(PARTS.BOTTOM, variant)} id={idDesc ?? idErr} role={live}>{message}</div>
229
+ <div class={cls(PARTS.BOTTOM, variant)} id={idErr} role={live}>{message}</div>
233
230
  {/if}
234
231
  </div>
235
232
 
@@ -7,10 +7,6 @@
7
7
  ```
8
8
  ### Types
9
9
  ```ts
10
- type TransitionProp = {
11
- fn?: (node: HTMLElement, params: any, options: { direction: "in" | "out" | "both" }) => import("svelte/transition").TransitionConfig;
12
- params?: unknown;
13
- };
14
10
  interface DateInputProps extends Omit<HTMLInputAttributes, "type" | "value" | "min" | "max" | "readonly" | "list" | "name"> {
15
11
  value?: Temporal.PlainDate; // bindable; undefined = empty
16
12
  open?: boolean; // bindable (false)
@@ -32,6 +28,16 @@
32
28
  styling?: SVSClass;
33
29
  variant?: SVSVariant; // (VARIANT.NEUTRAL)
34
30
  }
31
+ type DateInputCtl = {
32
+ toggle: () => void;
33
+ show: () => void;
34
+ hide: () => void;
35
+ clear: () => void;
36
+ };
37
+ type TransitionProp = {
38
+ fn?: (node: HTMLElement, params: any, options: { direction: "in" | "out" | "both" }) => import("svelte/transition").TransitionConfig;
39
+ params?: unknown;
40
+ };
35
41
  ```
36
42
  ### Anatomy
37
43
  ```svelte
@@ -50,16 +56,6 @@
50
56
  - `format` and `parse` are caller-coordinated; the default locale display is not necessarily parseable by a supplied parser.
51
57
  -->
52
58
  <script module lang="ts">
53
- type DateInputCtl = {
54
- toggle: () => void;
55
- show: () => void;
56
- hide: () => void;
57
- clear: () => void;
58
- };
59
- export type TransitionProp = {
60
- fn?: (node: HTMLElement, params: any, options: { direction: "in" | "out" | "both" }) => import("svelte/transition").TransitionConfig;
61
- params?: unknown;
62
- };
63
59
  export interface DateInputProps extends Omit<HTMLInputAttributes, "type" | "value" | "min" | "max" | "readonly" | "list" | "name"> {
64
60
  value?: Temporal.PlainDate;
65
61
  open?: boolean;
@@ -81,6 +77,16 @@
81
77
  styling?: SVSClass;
82
78
  variant?: SVSVariant;
83
79
  }
80
+ export type DateInputCtl = {
81
+ toggle: () => void;
82
+ show: () => void;
83
+ hide: () => void;
84
+ clear: () => void;
85
+ };
86
+ export type TransitionProp = {
87
+ fn?: (node: HTMLElement, params: any, options: { direction: "in" | "out" | "both" }) => import("svelte/transition").TransitionConfig;
88
+ params?: unknown;
89
+ };
84
90
  export type DateInputReqdProps = never;
85
91
  export type DateInputBindProps = "value" | "open" | "element";
86
92
 
@@ -98,7 +104,7 @@
98
104
  export const _DATE_INPUT_PRESET = "svs-date-input";
99
105
 
100
106
  import { onDestroy, tick, untrack } from "svelte";
101
- import { VARIANT, PARTS, _createContext, _fnClass, shouldReduceMotion } from "./_core";
107
+ import { VARIANT, PARTS, _createContext, _detectOverflow, _fnClass, shouldReduceMotion } from "./_core";
102
108
  import Calendar from "./Calendar.svelte";
103
109
  import type { Snippet } from "svelte";
104
110
  import type { Attachment } from "svelte/attachments";
@@ -122,7 +128,7 @@
122
128
  const effId = $derived(ctx ? ctx.id : idProp);
123
129
  const effDescribedby = $derived(ctx ? ctx.describedby : ariaDescribedbyProp);
124
130
  const fmt = $derived.by(() => formatProp ?? ((d: Temporal.PlainDate) => d.toLocaleString(locale, { dateStyle: "medium" })));
125
- const reduced = $derived(typeof window !== "undefined" && shouldReduceMotion());
131
+ const reduced = $derived(shouldReduceMotion());
126
132
  const tfn = $derived(!reduced && transition?.fn ? transition.fn : noop);
127
133
  const tparams = $derived(transition?.params as any);
128
134
 
@@ -152,6 +158,7 @@
152
158
  let last = $state(format());
153
159
  let focused = $state(false);
154
160
  let overflow = $state({ x: false, y: false });
161
+ let root = $state<HTMLElement>();
155
162
  let overlayElem = $state<HTMLDivElement>();
156
163
  let returning = false;
157
164
 
@@ -237,16 +244,13 @@
237
244
  });
238
245
  }
239
246
  function focusCalendar() {
240
- const el = document.querySelector(`[data-svs-dateinput="${uid}"] [data-svs-calendar] [tabindex="0"]`) as HTMLElement | null;
241
- el?.focus();
247
+ overlayElem?.querySelector<HTMLElement>('[tabindex="0"]')?.focus();
242
248
  }
243
249
  async function observeOverflow() {
244
250
  overflow = { x: false, y: false };
245
251
  await tick();
246
252
  if (!overlayElem || typeof window === "undefined") return;
247
- const rect = overlayElem.getBoundingClientRect();
248
- overflow.x = window.innerWidth < rect.right;
249
- overflow.y = window.innerHeight < rect.bottom;
253
+ overflow = _detectOverflow(overlayElem);
250
254
  }
251
255
  function stopListening() {
252
256
  if (typeof document === "undefined") return;
@@ -289,7 +293,6 @@
289
293
  returnFocus();
290
294
  };
291
295
  const houtside = (ev: PointerEvent) => {
292
- const root = document.querySelector(`[data-svs-dateinput="${uid}"]`);
293
296
  if (root?.contains(ev.target as Node)) return;
294
297
  hide();
295
298
  };
@@ -298,7 +301,7 @@
298
301
 
299
302
  <!---------------------------------------->
300
303
 
301
- <span class={cls(PARTS.WHOLE, effVariant)} data-svs-dateinput={uid} style="position:relative;">
304
+ <span bind:this={root} class={cls(PARTS.WHOLE, effVariant)} data-svs-dateinput={uid} style="position:relative;">
302
305
  {#if left}
303
306
  <span class={cls(PARTS.LEFT, effVariant)}>{@render left(ctl, open, effVariant)}</span>
304
307
  {/if}
@@ -1,15 +1,3 @@
1
- type DateInputCtl = {
2
- toggle: () => void;
3
- show: () => void;
4
- hide: () => void;
5
- clear: () => void;
6
- };
7
- export type TransitionProp = {
8
- fn?: (node: HTMLElement, params: any, options: {
9
- direction: "in" | "out" | "both";
10
- }) => import("svelte/transition").TransitionConfig;
11
- params?: unknown;
12
- };
13
1
  export interface DateInputProps extends Omit<HTMLInputAttributes, "type" | "value" | "min" | "max" | "readonly" | "list" | "name"> {
14
2
  value?: Temporal.PlainDate;
15
3
  open?: boolean;
@@ -31,6 +19,18 @@ export interface DateInputProps extends Omit<HTMLInputAttributes, "type" | "valu
31
19
  styling?: SVSClass;
32
20
  variant?: SVSVariant;
33
21
  }
22
+ export type DateInputCtl = {
23
+ toggle: () => void;
24
+ show: () => void;
25
+ hide: () => void;
26
+ clear: () => void;
27
+ };
28
+ export type TransitionProp = {
29
+ fn?: (node: HTMLElement, params: any, options: {
30
+ direction: "in" | "out" | "both";
31
+ }) => import("svelte/transition").TransitionConfig;
32
+ params?: unknown;
33
+ };
34
34
  export type DateInputReqdProps = never;
35
35
  export type DateInputBindProps = "value" | "open" | "element";
36
36
  export interface DateInputContext extends SVSContext {
@@ -58,10 +58,6 @@ import type { CalendarProps } from "./Calendar.svelte";
58
58
  * ```
59
59
  * ### Types
60
60
  * ```ts
61
- * type TransitionProp = {
62
- * fn?: (node: HTMLElement, params: any, options: { direction: "in" | "out" | "both" }) => import("svelte/transition").TransitionConfig;
63
- * params?: unknown;
64
- * };
65
61
  * interface DateInputProps extends Omit<HTMLInputAttributes, "type" | "value" | "min" | "max" | "readonly" | "list" | "name"> {
66
62
  * value?: Temporal.PlainDate; // bindable; undefined = empty
67
63
  * open?: boolean; // bindable (false)
@@ -83,6 +79,16 @@ import type { CalendarProps } from "./Calendar.svelte";
83
79
  * styling?: SVSClass;
84
80
  * variant?: SVSVariant; // (VARIANT.NEUTRAL)
85
81
  * }
82
+ * type DateInputCtl = {
83
+ * toggle: () => void;
84
+ * show: () => void;
85
+ * hide: () => void;
86
+ * clear: () => void;
87
+ * };
88
+ * type TransitionProp = {
89
+ * fn?: (node: HTMLElement, params: any, options: { direction: "in" | "out" | "both" }) => import("svelte/transition").TransitionConfig;
90
+ * params?: unknown;
91
+ * };
86
92
  * ```
87
93
  * ### Anatomy
88
94
  * ```svelte
@@ -95,7 +95,7 @@
95
95
 
96
96
  // *** Initialize *** //
97
97
  const cls = $derived(_fnClass(_DISCLOSURE_PRESET, styling ?? ctx?.styling));
98
- const reduced = $derived(typeof window !== "undefined" && shouldReduceMotion());
98
+ const reduced = $derived(shouldReduceMotion());
99
99
  const dur = $derived(_resolveDuration(duration));
100
100
  const tfn = $derived(reduced ? noop : (transition?.fn ?? slide));
101
101
  const tparams = $derived(transition?.params ?? { duration: dur });
@@ -122,16 +122,19 @@
122
122
  }
123
123
 
124
124
  // *** Reactive Handlers *** //
125
+ const neutralInput = $derived(ctx ? ctx.variant : variant);
126
+ const desiredBaseVariant = $derived(ctx && !effOpen && !isInactive && prevVariant === undefined ? base : undefined);
127
+ const shouldStoreInactive = $derived(isInactive && _isNeutral(variant));
128
+
125
129
  // External variant writes are styling-only; preserve the neutral fallback without changing open state.
126
130
  $effect(() => {
127
- neutral = ctx ? (_isNeutral(ctx.variant) ? ctx.variant : VARIANT.NEUTRAL) : _isNeutral(variant) ? variant : neutral;
128
- });
129
- $effect(() => {
130
- if (ctx && !effOpen && !isInactive && prevVariant === undefined) variant = base;
131
+ neutralInput;
132
+ untrack(() => syncNeutral());
131
133
  });
132
134
  $effect(() => {
133
- variant;
134
- untrack(() => (isInactive && _isNeutral(variant) ? storeVariant() : undefined));
135
+ desiredBaseVariant;
136
+ shouldStoreInactive;
137
+ untrack(() => syncVariant());
135
138
  });
136
139
  $effect.pre(() => {
137
140
  effOpen;
@@ -143,6 +146,13 @@
143
146
  restoreVariant();
144
147
  toggleOpen(effOpen);
145
148
  }
149
+ function syncNeutral() {
150
+ neutral = _isNeutral(neutralInput) ? neutralInput : neutral;
151
+ }
152
+ function syncVariant() {
153
+ if (shouldStoreInactive) storeVariant();
154
+ else if (desiredBaseVariant !== undefined) variant = desiredBaseVariant;
155
+ }
146
156
  function toggleOpen(isOpen: boolean) {
147
157
  guard.activate(dur);
148
158
  if (isOpen) {
@@ -51,7 +51,6 @@
51
51
  styling?: SVSClass;
52
52
  variant?: SVSVariant; // (VARIANT.NEUTRAL)
53
53
  }
54
- export type Position = "top" | "right" | "bottom" | "left";
55
54
  export type DrawerCssVar = "duration";
56
55
  export type DrawerReqdProps = "children";
57
56
  export type DrawerBindProps = "open" | "element";
@@ -80,7 +79,7 @@
80
79
  import { VARIANT, PARTS, _fnClass, _resolveDuration } from "./_core";
81
80
  import type { Snippet } from "svelte";
82
81
  import type { HTMLDialogAttributes, MouseEventHandler, KeyboardEventHandler, ToggleEventHandler } from "svelte/elements";
83
- import type { SVSClass, SVSVariant } from "./_core";
82
+ import type { SVSClass, SVSVariant, Position } from "./_core";
84
83
  </script>
85
84
 
86
85
  <script lang="ts">
@@ -11,14 +11,13 @@ export interface DrawerProps extends Omit<HTMLDialogAttributes, "children" | "st
11
11
  styling?: SVSClass;
12
12
  variant?: SVSVariant;
13
13
  }
14
- export type Position = "top" | "right" | "bottom" | "left";
15
14
  export type DrawerCssVar = "duration";
16
15
  export type DrawerReqdProps = "children";
17
16
  export type DrawerBindProps = "open" | "element";
18
17
  export declare const _DRAWER_PRESET = "svs-drawer";
19
18
  import type { Snippet } from "svelte";
20
19
  import type { HTMLDialogAttributes } from "svelte/elements";
21
- import type { SVSClass, SVSVariant } from "./_core";
20
+ import type { SVSClass, SVSVariant, Position } from "./_core";
22
21
  /**
23
22
  * ### Usage
24
23
  * Use standalone.
@@ -92,11 +92,18 @@
92
92
  export const _FILE_FIELD_PRESET = "svs-file-field";
93
93
 
94
94
  import { untrack } from "svelte";
95
- import { VARIANT, PARTS, _fnClass, _isNeutral } from "./_core";
95
+ import { VARIANT, PARTS, _fieldAria, _fieldIds, _fieldMessage, _fnClass, _isNeutral, _verify } from "./_core";
96
96
  import FileInput, { _FILE_INPUT_PRESET, _setFileInputContext } from "./FileInput.svelte";
97
97
  import type { Snippet } from "svelte";
98
98
  import type { SVSClass, SVSVariant, SVSFieldValidation } from "./_core";
99
- import type { FileInputContext, FileInputProps, FileInputReqdProps, FileInputBindProps, FileRejection, FileRejectReason } from "./FileInput.svelte";
99
+ import type {
100
+ FileInputContext,
101
+ FileInputProps,
102
+ FileInputReqdProps,
103
+ FileInputBindProps,
104
+ FileRejection,
105
+ FileRejectReason,
106
+ } from "./FileInput.svelte";
100
107
  </script>
101
108
 
102
109
  <script lang="ts">
@@ -107,12 +114,14 @@
107
114
  const cls = $derived(_fnClass(_FILE_FIELD_PRESET, styling));
108
115
  const uid = $props.id();
109
116
  const id = $derived(label?.trim() ? `${uid}-ctrl` : undefined);
110
- const idLabel = $derived(label?.trim() ? `${uid}-label` : undefined);
111
- const idDesc = $derived(bottom?.trim() ? `${uid}-desc` : undefined);
112
- const idErr = $derived(idDesc ?? `${uid}-err`);
117
+ const ids = $derived(_fieldIds(uid, label, bottom));
118
+ const idLabel = $derived(ids.idLabel);
119
+ const idDesc = $derived(ids.idDesc);
120
+ const idErr = $derived(ids.idErr);
113
121
  let errmsg = $state("");
114
- const message = $derived(variant === VARIANT.INACTIVE ? errmsg || bottom : bottom);
115
- const idMsg = $derived(variant === VARIANT.INACTIVE && message?.trim() ? idErr : undefined);
122
+ const message = $derived(_fieldMessage(variant, errmsg, bottom));
123
+ const aria = $derived(_fieldAria(variant, message, idErr));
124
+ const idMsg = $derived(aria.idMsg);
116
125
 
117
126
  // *** Initialize Context *** //
118
127
  const ctx: FileInputContext = {
@@ -154,7 +163,7 @@
154
163
  $effect(() => {
155
164
  neutral = _isNeutral(variant) ? variant : neutral;
156
165
  });
157
- const live = $derived(variant === VARIANT.INACTIVE ? "alert" : undefined);
166
+ const live = $derived(aria.live);
158
167
  function shift(oninvalid: boolean = false, msg?: string) {
159
168
  const vmsg = element?.validationMessage ?? "";
160
169
  if (files.length) touched = true;
@@ -167,12 +176,7 @@
167
176
  return vmsg ? VARIANT.INACTIVE : VARIANT.ACTIVE;
168
177
  }
169
178
  function verify() {
170
- if (!element) return;
171
- for (const v of validations) {
172
- const msg = v({ value: files, validity: element.validity, element });
173
- if (msg) return element.setCustomValidity(msg);
174
- }
175
- element.setCustomValidity("");
179
+ _verify(element, validations, files);
176
180
  }
177
181
 
178
182
  // *** Reactive Handlers *** //
@@ -255,7 +259,7 @@
255
259
  {@render side(PARTS.RIGHT, right)}
256
260
  </div>
257
261
  {#if reserve || message?.trim()}
258
- <div class={cls(PARTS.BOTTOM, variant)} id={idDesc ?? idErr} role={live}>{message}</div>
262
+ <div class={cls(PARTS.BOTTOM, variant)} id={idErr} role={live}>{message}</div>
259
263
  {/if}
260
264
  </div>
261
265
 
@@ -101,7 +101,7 @@
101
101
  export const [_getFileInputContext, _setFileInputContext] = _createContext<FileInputContext>();
102
102
 
103
103
  import { untrack } from "svelte";
104
- import { VARIANT, PARTS, SR_ONLY, _fnClass, _createContext } from "./_core";
104
+ import { VARIANT, PARTS, SR_ONLY, _commitSubset, _fnClass, _createContext } from "./_core";
105
105
  import type { Snippet } from "svelte";
106
106
  import type { Attachment } from "svelte/attachments";
107
107
  import type { HTMLInputAttributes, MouseEventHandler, ChangeEventHandler, DragEventHandler, EventHandler } from "svelte/elements";
@@ -183,11 +183,7 @@
183
183
  else accepted.push(file);
184
184
  }
185
185
  const detail = { values: effFiles, added: accepted, rejected };
186
- let committed = accepted;
187
- const a = events?.onadd?.(detail);
188
- if (a) committed = committed.filter((f) => a.includes(f));
189
- const b = ctx?.events?.onadd?.(detail);
190
- if (b) committed = committed.filter((f) => b.includes(f));
186
+ const committed = _commitSubset(accepted, detail, events?.onadd, ctx?.events?.onadd);
191
187
  const next = multiple ? [...current, ...committed] : committed.length ? committed : effFiles;
192
188
  if (!sameFileArray(effFiles, next)) setFiles(next);
193
189
  else syncInputFiles();
@@ -195,11 +191,7 @@
195
191
  }
196
192
  const remove = (file: File) => {
197
193
  const detail = { values: effFiles, removed: [file] };
198
- let keep = [file];
199
- const a = events?.onremove?.(detail);
200
- if (a) keep = keep.filter((f) => a.includes(f));
201
- const b = ctx?.events?.onremove?.(detail);
202
- if (b) keep = keep.filter((f) => b.includes(f));
194
+ const keep = _commitSubset([file], detail, events?.onremove, ctx?.events?.onremove);
203
195
  if (!keep.length) return;
204
196
  const next = effFiles.filter((f) => f !== file);
205
197
  if (!sameFileArray(effFiles, next)) setFiles(next);
@@ -39,6 +39,9 @@
39
39
  // Parses a space-separated hotkey string (e.g. "Ctrl Shift A") into its modifier flags and trailing key.
40
40
  function parseHotkey(value: string): Omit<HotkeyCaptureDetail, "event" | "kind">;
41
41
  ```
42
+ ### Behavior
43
+ Focus and blur events update `active`, and setting `active` focuses or blurs the input. Guards
44
+ keep focus in place when it is already correct, and `disabled` prevents programmatic focus moves.
42
45
  -->
43
46
  <script module lang="ts">
44
47
  export interface HotkeyCaptureProps extends Omit<
@@ -109,8 +112,6 @@
109
112
  });
110
113
  $effect(() => {
111
114
  active;
112
- disabled;
113
- element;
114
115
  untrack(() => toggleFocus());
115
116
  });
116
117
  function toggleFocus() {
@@ -65,6 +65,9 @@ import type { SVSClass, SVSVariant } from "./_core";
65
65
  * // Parses a space-separated hotkey string (e.g. "Ctrl Shift A") into its modifier flags and trailing key.
66
66
  * function parseHotkey(value: string): Omit<HotkeyCaptureDetail, "event" | "kind">;
67
67
  * ```
68
+ * ### Behavior
69
+ * Focus and blur events update `active`, and setting `active` focuses or blurs the input. Guards
70
+ * keep focus in place when it is already correct, and `disabled` prevents programmatic focus moves.
68
71
  */
69
72
  declare const HotkeyCapture: import("svelte").Component<HotkeyCaptureProps, {}, "variant" | "active" | "value" | "element">;
70
73
  type HotkeyCapture = ReturnType<typeof HotkeyCapture>;
@@ -113,9 +113,11 @@
113
113
 
114
114
  <style>
115
115
  :root:has(dialog[open]) {
116
- overflow: clip;
116
+ overflow: hidden;
117
117
  }
118
- dialog {
119
- margin: auto;
118
+ @layer components {
119
+ dialog {
120
+ margin: auto;
121
+ }
120
122
  }
121
123
  </style>
@@ -76,7 +76,7 @@
76
76
  export const _NUMBER_FIELD_PRESET = "svs-number-field";
77
77
 
78
78
  import { onMount, untrack } from "svelte";
79
- import { VARIANT, PARTS, _fnClass, _isNeutral } from "./_core";
79
+ import { VARIANT, PARTS, _fieldAria, _fieldIds, _fieldMessage, _fnClass, _isNeutral, _verify } from "./_core";
80
80
  import NumberInput, { _NUMBER_INPUT_PRESET, _setNumberInputContext } from "./NumberInput.svelte";
81
81
  import type { Snippet } from "svelte";
82
82
  import type { SVSClass, SVSVariant, SVSFieldValidation } from "./_core";
@@ -91,12 +91,14 @@
91
91
  const cls = $derived(_fnClass(_NUMBER_FIELD_PRESET, styling));
92
92
  const uid = $props.id();
93
93
  const id = $derived(label?.trim() ? `${uid}-ctrl` : undefined);
94
- const idLabel = $derived(label?.trim() ? `${uid}-label` : undefined);
95
- const idDesc = $derived(bottom?.trim() ? `${uid}-desc` : undefined);
96
- const idErr = $derived(idDesc ?? `${uid}-err`);
94
+ const ids = $derived(_fieldIds(uid, label, bottom));
95
+ const idLabel = $derived(ids.idLabel);
96
+ const idDesc = $derived(ids.idDesc);
97
+ const idErr = $derived(ids.idErr);
97
98
  let errmsg = $state("");
98
- const message = $derived(variant === VARIANT.INACTIVE ? errmsg || bottom : bottom);
99
- const idMsg = $derived(variant === VARIANT.INACTIVE && message?.trim() ? idErr : undefined);
99
+ const message = $derived(_fieldMessage(variant, errmsg, bottom));
100
+ const aria = $derived(_fieldAria(variant, message, idErr));
101
+ const idMsg = $derived(aria.idMsg);
100
102
 
101
103
  // *** Initialize Context *** //
102
104
  const ctx: NumberInputContext = {
@@ -134,19 +136,14 @@
134
136
  $effect(() => {
135
137
  neutral = _isNeutral(variant) ? variant : neutral;
136
138
  });
137
- const live = $derived(variant === VARIANT.INACTIVE ? "alert" : undefined);
139
+ const live = $derived(aria.live);
138
140
  function shift(oninvalid?: boolean) {
139
141
  const vmsg = element?.validationMessage ?? "";
140
142
  variant = value === undefined && !oninvalid ? neutral : vmsg ? VARIANT.INACTIVE : VARIANT.ACTIVE;
141
143
  errmsg = vmsg;
142
144
  }
143
145
  function verify() {
144
- if (!element) return;
145
- for (const v of validations) {
146
- const msg = v({ value, validity: element.validity, element });
147
- if (msg) return element.setCustomValidity(msg);
148
- }
149
- element.setCustomValidity("");
146
+ _verify(element, validations, value);
150
147
  }
151
148
 
152
149
  // *** Reactive Handlers *** //
@@ -192,7 +189,7 @@
192
189
  {@render side(PARTS.RIGHT, right)}
193
190
  </div>
194
191
  {#if reserve || message?.trim()}
195
- <div class={cls(PARTS.BOTTOM, variant)} id={idDesc ?? idErr} role={live}>{message}</div>
192
+ <div class={cls(PARTS.BOTTOM, variant)} id={idErr} role={live}>{message}</div>
196
193
  {/if}
197
194
  </div>
198
195