@streamscloud/kit 0.89.1 → 0.90.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 (68) hide show
  1. package/README.md +2 -1
  2. package/dist/core/validation/form-validation-handler/form-validation-handler.svelte.d.ts +4 -2
  3. package/dist/core/validation/form-validation-handler/form-validation-handler.svelte.js +1 -1
  4. package/dist/ui/_internal/form-field-label-context/form-field-label-context.svelte.d.ts +17 -0
  5. package/dist/ui/_internal/form-field-label-context/form-field-label-context.svelte.js +22 -0
  6. package/dist/ui/_internal/form-field-label-context/index.d.ts +1 -0
  7. package/dist/ui/_internal/form-field-label-context/index.js +1 -0
  8. package/dist/ui/checkbox/cmp.checkbox.svelte +11 -2
  9. package/dist/ui/chip-group/cmp.chip-group.svelte +10 -3
  10. package/dist/ui/chip-group/cmp.chip-group.svelte.d.ts +4 -2
  11. package/dist/ui/color-picker/cmp.color-picker.svelte +15 -2
  12. package/dist/ui/color-picker/cmp.color-picker.svelte.d.ts +1 -3
  13. package/dist/ui/date-picker/cmp.date-picker.svelte +11 -6
  14. package/dist/ui/date-picker/cmp.date-picker.svelte.d.ts +2 -9
  15. package/dist/ui/date-picker/cmp.date-time-picker.svelte +19 -3
  16. package/dist/ui/date-picker/cmp.date-time-picker.svelte.d.ts +3 -5
  17. package/dist/ui/date-picker/cmp.time-picker.svelte +3 -1
  18. package/dist/ui/date-picker/cmp.time-picker.svelte.d.ts +1 -4
  19. package/dist/ui/document-tabs/types.d.ts +0 -1
  20. package/dist/ui/form-field/cmp.form-field-validatable.svelte +4 -4
  21. package/dist/ui/form-field/cmp.form-field-validatable.svelte.d.ts +4 -7
  22. package/dist/ui/form-field/cmp.form-field.svelte +31 -79
  23. package/dist/ui/form-field/cmp.form-field.svelte.d.ts +11 -10
  24. package/dist/ui/form-field/form-field-label.svelte.d.ts +6 -0
  25. package/dist/ui/form-field/form-field-label.svelte.js +19 -0
  26. package/dist/ui/form-field/index.d.ts +1 -1
  27. package/dist/ui/icon-picker/cmp.icon-picker.svelte +3 -1
  28. package/dist/ui/icon-picker/cmp.icon-picker.svelte.d.ts +3 -1
  29. package/dist/ui/input/cmp.input.svelte +5 -1
  30. package/dist/ui/input/types.d.ts +0 -3
  31. package/dist/ui/numeral-input/cmp.numeral-input.svelte +6 -2
  32. package/dist/ui/numeral-input/cmp.numeral-input.svelte.d.ts +0 -4
  33. package/dist/ui/option-pill/cmp.option-pill.svelte.d.ts +0 -2
  34. package/dist/ui/page-layout/cmp.page-layout.svelte.d.ts +1 -2
  35. package/dist/ui/pin-input/cmp.pin-input.svelte +11 -3
  36. package/dist/ui/pin-input/cmp.pin-input.svelte.d.ts +1 -1
  37. package/dist/ui/popover/cmp.popover.svelte +6 -3
  38. package/dist/ui/popover/cmp.popover.svelte.d.ts +3 -6
  39. package/dist/ui/radio/cmp.radio.svelte +12 -3
  40. package/dist/ui/rich-text-input/cmp.rich-text-input.svelte +19 -7
  41. package/dist/ui/rich-text-input/cmp.rich-text-input.svelte.d.ts +1 -2
  42. package/dist/ui/segmented-control/cmp.segmented-control.svelte +5 -3
  43. package/dist/ui/segmented-control/cmp.segmented-control.svelte.d.ts +1 -2
  44. package/dist/ui/select/cmp.input-suggest.svelte +5 -1
  45. package/dist/ui/select/cmp.input-suggest.svelte.d.ts +0 -3
  46. package/dist/ui/select/cmp.multiselect-tree.svelte +2 -1
  47. package/dist/ui/select/cmp.multiselect-tree.svelte.d.ts +1 -3
  48. package/dist/ui/select/cmp.singleselect-async.svelte +9 -3
  49. package/dist/ui/select/cmp.singleselect-async.svelte.d.ts +0 -2
  50. package/dist/ui/select/cmp.singleselect.svelte +3 -1
  51. package/dist/ui/select/cmp.singleselect.svelte.d.ts +0 -2
  52. package/dist/ui/select/multiselect-base.svelte +10 -3
  53. package/dist/ui/select/select-core.svelte.js +0 -2
  54. package/dist/ui/select/select-listbox.svelte +4 -7
  55. package/dist/ui/select/types.d.ts +2 -6
  56. package/dist/ui/slider/cmp.slider.svelte +6 -2
  57. package/dist/ui/slider/cmp.slider.svelte.d.ts +1 -0
  58. package/dist/ui/tabs/types.d.ts +0 -1
  59. package/dist/ui/textarea/cmp.textarea.svelte +5 -1
  60. package/dist/ui/textarea/cmp.textarea.svelte.d.ts +0 -2
  61. package/dist/ui/toggle/cmp.toggle.svelte +4 -2
  62. package/dist/ui/tree/cmp.tree-node-view.svelte +13 -15
  63. package/dist/ui/validatable/cmp.validatable.svelte +1 -2
  64. package/dist/ui/validatable/cmp.validatable.svelte.d.ts +1 -2
  65. package/dist/ui/video/cmp.video.svelte.d.ts +0 -1
  66. package/dist/ui/visual-select/cmp.visual-select.svelte +11 -3
  67. package/dist/ui/visual-select/cmp.visual-select.svelte.d.ts +2 -1
  68. package/package.json +3 -2
package/README.md CHANGED
@@ -15,7 +15,8 @@ without it agent sessions run with no hooks and no shared skills. Re-run after c
15
15
  ## Checks
16
16
 
17
17
  ```sh
18
- npm run check # the gate: strict svelte-check + tsc for node files + eslint + prettier --check
18
+ npm run check:fix # after edits: lint:fix, then check
19
+ npm run check # the gate, read-only: strict svelte-check + tsc for node files + eslint + prettier --check
19
20
  npm run lint:fix # eslint --fix + prettier --write
20
21
  ```
21
22
 
@@ -25,8 +25,10 @@ export declare class FormValidationHandler<T extends TypeToValidate<T> = TypeToV
25
25
  onReset?: () => void;
26
26
  });
27
27
  get form(): T;
28
- get errors(): { [P in keyof Partial<T>]: string; };
29
- get touched(): Record<keyof T, boolean>;
28
+ get errors(): Readonly<{
29
+ [P in keyof Partial<T>]: string;
30
+ }>;
31
+ get touched(): Readonly<Record<keyof T, boolean>>;
30
32
  get isValidating(): boolean;
31
33
  get isSubmitting(): boolean;
32
34
  updateField: <K extends keyof T>(field: K, value: T[K]) => void;
@@ -167,7 +167,7 @@ export class FormValidationHandler {
167
167
  this.updateTouched(field, true);
168
168
  this._isValidating = true;
169
169
  const error = await this._validator.validateAt(this._form, field);
170
- ValidationUtils.update(this.errors, field, error || '');
170
+ ValidationUtils.update(this._errors, field, error || '');
171
171
  this._isValidating = false;
172
172
  };
173
173
  stringify = (value) => {
@@ -0,0 +1,17 @@
1
+ export type FormFieldLabelContext = {
2
+ readonly labelId: string | undefined;
3
+ /** The first mounted claimant gets the label; the returned release hands it to the next one still mounted. */
4
+ claimControl: (id: () => string) => () => void;
5
+ };
6
+ /** Offers a visible label to every kit control rendered inside the calling component. */
7
+ export declare const provideFormFieldLabel: (formFieldLabel: FormFieldLabelContext) => void;
8
+ /** The label of the surrounding FormField, or `undefined` outside one. */
9
+ export declare const getFormFieldLabel: () => FormFieldLabelContext | undefined;
10
+ /** For a text input: the surrounding FormField label points its `for` at this id while the input is mounted. */
11
+ export declare const claimFormFieldLabel: (controlId: () => string) => void;
12
+ /** The `aria-labelledby` of a control that is not a text input: the explicit one, else the FormField label's id — unless an explicit `aria-label` names it. */
13
+ export declare const resolveAriaLabelledby: (formFieldLabel: FormFieldLabelContext | undefined, ariaLabel: string | undefined, ariaLabelledby: string | undefined) => string | undefined;
14
+ /** Whether `target` sits in a `<label>` whose `for` control lies inside `controlRoot` — a press there belongs to that control, not outside it. */
15
+ export declare const isInsideLabelOf: (target: Node, controlRoot: HTMLElement | undefined) => boolean;
16
+ /** Below the calling component no control is named by the surrounding FormField label — for a field that renders other fields in its own panel. */
17
+ export declare const stopFormFieldLabelHere: () => void;
@@ -0,0 +1,22 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ // Svelte context despite sc-frontend-svelte's "no context API": the least invasive way for FormField to reach a kit control at any depth (see the ADR).
3
+ const KEY = Symbol('form-field-label');
4
+ /** Offers a visible label to every kit control rendered inside the calling component. */
5
+ export const provideFormFieldLabel = (formFieldLabel) => {
6
+ setContext(KEY, formFieldLabel);
7
+ };
8
+ /** The label of the surrounding FormField, or `undefined` outside one. */
9
+ export const getFormFieldLabel = () => getContext(KEY);
10
+ /** For a text input: the surrounding FormField label points its `for` at this id while the input is mounted. */
11
+ export const claimFormFieldLabel = (controlId) => {
12
+ const formFieldLabel = getFormFieldLabel();
13
+ $effect(() => formFieldLabel?.claimControl(controlId));
14
+ };
15
+ /** The `aria-labelledby` of a control that is not a text input: the explicit one, else the FormField label's id — unless an explicit `aria-label` names it. */
16
+ export const resolveAriaLabelledby = (formFieldLabel, ariaLabel, ariaLabelledby) => ariaLabelledby ?? (ariaLabel ? undefined : formFieldLabel?.labelId);
17
+ /** Whether `target` sits in a `<label>` whose `for` control lies inside `controlRoot` — a press there belongs to that control, not outside it. */
18
+ export const isInsideLabelOf = (target, controlRoot) => target instanceof Element && !!controlRoot?.contains(target.closest('label')?.control ?? null);
19
+ /** Below the calling component no control is named by the surrounding FormField label — for a field that renders other fields in its own panel. */
20
+ export const stopFormFieldLabelHere = () => {
21
+ setContext(KEY, undefined);
22
+ };
@@ -0,0 +1 @@
1
+ export { claimFormFieldLabel, getFormFieldLabel, isInsideLabelOf, provideFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere, type FormFieldLabelContext } from './form-field-label-context.svelte';
@@ -0,0 +1 @@
1
+ export { claimFormFieldLabel, getFormFieldLabel, isInsideLabelOf, provideFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere } from './form-field-label-context.svelte';
@@ -1,10 +1,12 @@
1
- <script lang="ts">import { Icon } from '../icon';
1
+ <script lang="ts">import { getFormFieldLabel } from '../_internal/form-field-label-context';
2
+ import { Icon } from '../icon';
2
3
  import IconCheckmark from '@fluentui/svg-icons/icons/checkmark_12_regular.svg?raw';
3
4
  import IconSubtract from '@fluentui/svg-icons/icons/subtract_16_regular.svg?raw';
4
5
  const { checked, variant = 'default', size = 'md', disabled = false, label, title, on } = $props();
5
6
  const isOn = $derived(checked === true);
6
7
  const isIndeterminate = $derived(checked === 'indeterminate');
7
8
  const ariaChecked = $derived(isIndeterminate ? 'mixed' : isOn ? 'true' : 'false');
9
+ const formFieldLabel = getFormFieldLabel();
8
10
  const handleClick = (event) => {
9
11
  event.stopPropagation();
10
12
  if (disabled) {
@@ -21,7 +23,14 @@ const handleClick = (event) => {
21
23
  class:check--indeterminate={isIndeterminate}
22
24
  class:check--disabled={disabled}
23
25
  title={title}>
24
- <button type="button" class="check__box" role="checkbox" aria-checked={ariaChecked} disabled={disabled} onclick={handleClick}>
26
+ <button
27
+ type="button"
28
+ class="check__box"
29
+ role="checkbox"
30
+ aria-checked={ariaChecked}
31
+ aria-labelledby={label ? undefined : formFieldLabel?.labelId}
32
+ disabled={disabled}
33
+ onclick={handleClick}>
25
34
  <span class="check__mark" class:check__mark--visible={isOn || isIndeterminate} aria-hidden="true">
26
35
  <Icon src={isIndeterminate ? IconSubtract : IconCheckmark} />
27
36
  </span>
@@ -1,6 +1,8 @@
1
- <script lang="ts" generics="T">import { Icon } from '../icon';
1
+ <script lang="ts" generics="T">import { getFormFieldLabel, resolveAriaLabelledby } from '../_internal/form-field-label-context';
2
+ import { Icon } from '../icon';
2
3
  import IconCheckmark from '@fluentui/svg-icons/icons/checkmark_12_regular.svg?raw';
3
- const { chips, variant = 'soft', size = 'md', disabled = false, compare = (a, b) => a === b, ...mode } = $props();
4
+ const { chips, variant = 'soft', size = 'md', disabled = false, compare = (a, b) => a === b, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, ...mode } = $props();
5
+ const formFieldLabel = getFormFieldLabel();
4
6
  const isSelected = (chip) => {
5
7
  if (mode.type === 'multi') {
6
8
  return mode.value.some((v) => compare(v, chip.value));
@@ -22,7 +24,12 @@ const handleClick = (chip) => {
22
24
  };
23
25
  </script>
24
26
 
25
- <div class="chip-group chip-group--{size} chip-group--{variant}" class:chip-group--disabled={disabled} role={mode.type === 'multi' ? 'group' : 'radiogroup'}>
27
+ <div
28
+ class="chip-group chip-group--{size} chip-group--{variant}"
29
+ class:chip-group--disabled={disabled}
30
+ role={mode.type === 'multi' ? 'group' : 'radiogroup'}
31
+ aria-label={ariaLabel}
32
+ aria-labelledby={resolveAriaLabelledby(formFieldLabel, ariaLabel, ariaLabelledby)}>
26
33
  {#each chips as chip (chip.value)}
27
34
  {@const selected = isSelected(chip)}
28
35
  <button
@@ -6,10 +6,11 @@ declare function $$render<T>(): {
6
6
  variant?: "soft" | "solid";
7
7
  /** @default 'md' */
8
8
  size?: "sm" | "md";
9
- /** Disable the whole group. */
10
9
  disabled?: boolean;
11
10
  /** Custom equality comparator for non-primitive values (objects with `id`, etc.). @default (a, b) => a === b */
12
11
  compare?: (a: T, b: T) => boolean;
12
+ 'aria-label'?: string;
13
+ 'aria-labelledby'?: string;
13
14
  } & {
14
15
  /** Single-select mode. */
15
16
  type: "single";
@@ -23,10 +24,11 @@ declare function $$render<T>(): {
23
24
  variant?: "soft" | "solid";
24
25
  /** @default 'md' */
25
26
  size?: "sm" | "md";
26
- /** Disable the whole group. */
27
27
  disabled?: boolean;
28
28
  /** Custom equality comparator for non-primitive values (objects with `id`, etc.). @default (a, b) => a === b */
29
29
  compare?: (a: T, b: T) => boolean;
30
+ 'aria-label'?: string;
31
+ 'aria-labelledby'?: string;
30
32
  } & {
31
33
  /** Multi-select mode. */
32
34
  type: "multi";
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">import { ColorHelper } from '../../core/utils';
2
+ import { claimFormFieldLabel, getFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere } from '../_internal/form-field-label-context';
2
3
  import { Icon } from '../icon';
3
4
  import { Input } from '../input';
4
5
  import { Popover } from '../popover';
@@ -8,7 +9,11 @@ import IconColorF from '@fluentui/svg-icons/icons/color_20_regular.svg?raw';
8
9
  import IconDismiss from '@fluentui/svg-icons/icons/dismiss_20_regular.svg?raw';
9
10
  import { colord } from 'colord';
10
11
  import AwesomeColorPicker from 'svelte-awesome-color-picker';
11
- let { value, variant = 'fill', label, size = 'md', defaultColor, clearable = false, disabled = false, placeholder, on, children, previewIcon, 'aria-label': ariaLabel } = $props();
12
+ let { value, variant = 'fill', label, size = 'md', defaultColor, clearable = false, disabled = false, placeholder, on, children, previewIcon, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby } = $props();
13
+ const formFieldLabel = getFormFieldLabel();
14
+ const triggerId = $props.id();
15
+ claimFormFieldLabel(() => triggerId);
16
+ stopFormFieldLabelHere();
12
17
  const loc = new ColorPickerLocalization();
13
18
  const displayColor = $derived(value || defaultColor || '');
14
19
  const textColor = $derived.by(() => {
@@ -45,7 +50,15 @@ const onColorPickerInput = ({ hex }) => {
45
50
  </script>
46
51
 
47
52
  <div class="color-picker">
48
- <Popover position="bottom-start" keepOpen panel={false} fixedPosition disabled={disabled} aria-label={ariaLabel}>
53
+ <Popover
54
+ position="bottom-start"
55
+ keepOpen
56
+ panel={false}
57
+ fixedPosition
58
+ disabled={disabled}
59
+ id={triggerId}
60
+ aria-label={ariaLabel}
61
+ aria-labelledby={resolveAriaLabelledby(formFieldLabel, ariaLabel, ariaLabelledby)}>
49
62
  {#snippet trigger()}
50
63
  <div class="color-picker__trigger">
51
64
  {#if children}
@@ -9,9 +9,7 @@ type Props = {
9
9
  size?: 'sm' | 'md' | 'lg';
10
10
  /** Default color shown in the picker UI when value is empty (does not set the value itself). */
11
11
  defaultColor?: string;
12
- /** Show a × clear button on the trigger to wipe the value. */
13
12
  clearable?: boolean;
14
- /** Visual disabled state — popover won't open. */
15
13
  disabled?: boolean;
16
14
  /** Placeholder shown on the swatch trigger when no value is selected. @default localized "Not set" */
17
15
  placeholder?: string;
@@ -25,8 +23,8 @@ type Props = {
25
23
  * droplet glyph. Same shape as Input's `icon` prop (Rule 9).
26
24
  */
27
25
  previewIcon?: string | Snippet;
28
- /** Accessible name of the trigger — otherwise it is announced by its color value only. */
29
26
  'aria-label'?: string;
27
+ 'aria-labelledby'?: string;
30
28
  };
31
29
  /**
32
30
  * A color picker with an Input-shaped swatch trigger that opens a popover hosting a full hex/rgb picker
@@ -1,10 +1,15 @@
1
- <script lang="ts">import { InputClearButton } from '../_internal/input-clear-button';
1
+ <script lang="ts">import { claimFormFieldLabel, getFormFieldLabel, isInsideLabelOf, resolveAriaLabelledby } from '../_internal/form-field-label-context';
2
+ import { InputClearButton } from '../_internal/input-clear-button';
2
3
  import { Icon, IconSlot } from '../icon';
3
4
  import DatePickerCalendar from './cmp.date-picker-calendar.svelte';
4
5
  import { DatePickerLocalization } from './date-picker-localization';
5
6
  import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
6
7
  import IconCalendar from '@fluentui/svg-icons/icons/calendar_16_regular.svg?raw';
7
- const { size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, min, max, locale, name, id = null, autofocus = false, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, icon, ...mode } = $props();
8
+ const { size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, min, max, locale, name, id = null, autofocus = false, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, icon, ...mode } = $props();
9
+ const formFieldLabel = getFormFieldLabel();
10
+ const fallbackId = $props.id();
11
+ const controlId = $derived(id ?? fallbackId);
12
+ claimFormFieldLabel(() => controlId);
8
13
  const localization = new DatePickerLocalization();
9
14
  /** Resolved locale: explicit prop or `'en-US'` default. */
10
15
  const resolvedLocale = $derived(locale ?? 'en-US');
@@ -93,7 +98,7 @@ $effect(() => {
93
98
  if (!target) {
94
99
  return;
95
100
  }
96
- if (triggerEl?.contains(target) || panelEl?.contains(target)) {
101
+ if (triggerEl?.contains(target) || panelEl?.contains(target) || isInsideLabelOf(target, triggerEl)) {
97
102
  return;
98
103
  }
99
104
  open = false;
@@ -127,13 +132,14 @@ $effect(() => {
127
132
  bind:this={triggerEl}
128
133
  type="button"
129
134
  class="date-picker__trigger"
130
- id={id}
135
+ id={controlId}
131
136
  disabled={disabled}
132
137
  inert={inert}
133
138
  autofocus={autofocus}
134
139
  aria-haspopup="dialog"
135
140
  aria-expanded={open}
136
141
  aria-label={ariaLabel ?? name}
142
+ aria-labelledby={resolveAriaLabelledby(formFieldLabel, ariaLabel, ariaLabelledby)}
137
143
  aria-describedby={ariaDescribedby}
138
144
  onclick={readonly ? undefined : toggleOpen}>
139
145
  {#if icon}
@@ -156,8 +162,7 @@ $effect(() => {
156
162
  {/if}
157
163
 
158
164
  {#if open}
159
- <!-- preventDefault cancels an ancestor <label>'s activation — a dead-space click inside the panel would forward to the trigger and close the calendar. FormField guards its own; a consumer's plain <label> does not. -->
160
- <div bind:this={panelEl} class="date-picker__panel" role="presentation" onclick={(e) => e.preventDefault()} onkeydown={() => undefined}>
165
+ <div bind:this={panelEl} class="date-picker__panel">
161
166
  <DatePickerCalendar
162
167
  selectedDate={selectedDate}
163
168
  minDate={minDate}
@@ -5,9 +5,7 @@ type BaseProps = {
5
5
  size?: 'sm' | 'md' | 'lg';
6
6
  placeholder?: string;
7
7
  disabled?: boolean;
8
- /** Non-clickable but still visible. Trigger button is disabled (like `disabled`) but cursor stays default. */
9
8
  readonly?: boolean;
10
- /** Visual display mode — non-focusable, no hover/focus animations, hidden clear button. */
11
9
  inert?: boolean;
12
10
  error?: boolean;
13
11
  /** Strip border + background + focus underline. */
@@ -16,17 +14,14 @@ type BaseProps = {
16
14
  min?: LikeDate;
17
15
  /** Latest selectable date (inclusive). */
18
16
  max?: LikeDate;
19
- /**
20
- * BCP-47 locale tag. Drives month/weekday labels, trigger date formatting AND first day of week.
21
- * When omitted, uses an opinionated default: `en-US` formatting + Monday-first calendar.
22
- * When passed (even `'en-US'`), first day of week is derived from the locale via `Intl.Locale.getWeekInfo()`.
23
- */
17
+ /** Omitted: `en-US` formatting with Monday first. Passed (even `'en-US'`): first day of week comes from the locale. */
24
18
  locale?: string;
25
19
  /** Native form name. When set, a hidden input with the ISO value is rendered for form submission. */
26
20
  name?: string;
27
21
  id?: string | null;
28
22
  autofocus?: boolean;
29
23
  'aria-label'?: string;
24
+ 'aria-labelledby'?: string;
30
25
  'aria-describedby'?: string;
31
26
  'aria-required'?: boolean;
32
27
  /**
@@ -36,7 +31,6 @@ type BaseProps = {
36
31
  icon?: IconProp;
37
32
  };
38
33
  type ClearableProps = BaseProps & {
39
- /** Value can be cleared to `null`. Trigger shows a clear (X) button when set. */
40
34
  clearable: true;
41
35
  value: LikeDate | null | undefined;
42
36
  on?: {
@@ -44,7 +38,6 @@ type ClearableProps = BaseProps & {
44
38
  };
45
39
  };
46
40
  type RequiredProps = BaseProps & {
47
- /** Value is required. No clear button. */
48
41
  clearable: false;
49
42
  value: LikeDate;
50
43
  on?: {
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">import DatePicker from './cmp.date-picker.svelte';
2
2
  import TimePicker from './cmp.time-picker.svelte';
3
- const { value, size = 'md', datePlaceholder = '', min, step = 30, on } = $props();
3
+ const { value, size = 'md', datePlaceholder = '', min, step = 30, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, on } = $props();
4
4
  const DEFAULT_TIME = '12:00';
5
5
  const MINUTES_PER_DAY = 24 * 60;
6
6
  const AFTER_LAST_SLOT = '24:00';
@@ -72,9 +72,25 @@ const onTimeChange = (next) => {
72
72
  </script>
73
73
 
74
74
  <div class="date-time date-time--{size}" class:date-time--date-only={!currentDate}>
75
- <DatePicker clearable size={size} value={value} placeholder={datePlaceholder} min={firstAllowedDate ?? undefined} on={{ change: onDateChange }} />
75
+ <DatePicker
76
+ clearable
77
+ size={size}
78
+ value={value}
79
+ placeholder={datePlaceholder}
80
+ min={firstAllowedDate ?? undefined}
81
+ aria-label={ariaLabel}
82
+ aria-labelledby={ariaLabelledby}
83
+ on={{ change: onDateChange }} />
76
84
  {#if currentDate}
77
- <TimePicker clearable={false} size={size} step={step} min={timeMin} value={timeValue} on={{ change: onTimeChange }} />
85
+ <TimePicker
86
+ clearable={false}
87
+ size={size}
88
+ step={step}
89
+ min={timeMin}
90
+ value={timeValue}
91
+ aria-label={ariaLabel}
92
+ aria-labelledby={ariaLabelledby}
93
+ on={{ change: onTimeChange }} />
78
94
  {/if}
79
95
  </div>
80
96
 
@@ -4,14 +4,12 @@ type Props = {
4
4
  /** @default 'md' */
5
5
  size?: 'sm' | 'md' | 'lg';
6
6
  datePlaceholder?: string;
7
- /**
8
- * Earliest selectable date-time (inclusive). On its day only the `step` slots at or after it are offered and an
9
- * earlier time is raised to the first of them; a day with no slot left is not selectable. A `value` before the first
10
- * allowed day still renders, but its time list is empty — only picking an allowed date changes it.
11
- */
7
+ /** Earliest selectable date-time, inclusive: on its day only the slots from it on are offered, and a day with no slot left is disabled. */
12
8
  min?: LikeDate;
13
9
  /** Minute interval for the time list. @default 30 */
14
10
  step?: number;
11
+ 'aria-label'?: string;
12
+ 'aria-labelledby'?: string;
15
13
  on?: {
16
14
  change?: (value: string | null) => void;
17
15
  };
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">import { Singleselect } from '../select';
2
- const { size = 'md', step = 30, min, placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, id, name, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
2
+ const { size = 'md', step = 30, min, placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, id, name, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
3
3
  const MINUTES_PER_DAY = 24 * 60;
4
4
  const pad = (n) => n.toString().padStart(2, '0');
5
5
  const stepOptions = $derived.by(() => {
@@ -44,6 +44,7 @@ const emit = (next) => {
44
44
  id={id}
45
45
  name={name}
46
46
  aria-label={ariaLabel}
47
+ aria-labelledby={ariaLabelledby}
47
48
  aria-describedby={ariaDescribedby}
48
49
  aria-required={ariaRequired}
49
50
  on={{ change: emit }} />
@@ -62,6 +63,7 @@ const emit = (next) => {
62
63
  id={id}
63
64
  name={name}
64
65
  aria-label={ariaLabel}
66
+ aria-labelledby={ariaLabelledby}
65
67
  aria-describedby={ariaDescribedby}
66
68
  aria-required={ariaRequired}
67
69
  on={{ change: emit }} />
@@ -7,20 +7,18 @@ type BaseProps = {
7
7
  min?: string;
8
8
  placeholder?: string;
9
9
  disabled?: boolean;
10
- /** Focusable, value visible, but selection cannot change. */
11
10
  readonly?: boolean;
12
- /** Non-interactive, unstyled view-only mode. */
13
11
  inert?: boolean;
14
12
  error?: boolean;
15
13
  borderless?: boolean;
16
14
  id?: string;
17
15
  name?: string;
18
16
  'aria-label'?: string;
17
+ 'aria-labelledby'?: string;
19
18
  'aria-describedby'?: string;
20
19
  'aria-required'?: boolean;
21
20
  };
22
21
  type ClearableProps = BaseProps & {
23
- /** Value can be cleared to `null`; shows a clear (X) button when set. */
24
22
  clearable: true;
25
23
  value: string | null | undefined;
26
24
  on?: {
@@ -28,7 +26,6 @@ type ClearableProps = BaseProps & {
28
26
  };
29
27
  };
30
28
  type RequiredProps = BaseProps & {
31
- /** Value is required. No clear button. */
32
29
  clearable: false;
33
30
  value: string;
34
31
  on?: {
@@ -15,6 +15,5 @@ export type DocumentTab<TValue = unknown> = {
15
15
  tone?: DocumentTabTone;
16
16
  /** Full text for a label that truncates — the complete file path, typically. */
17
17
  tooltip?: string;
18
- /** Non-interactive — click does nothing. */
19
18
  disabled?: boolean;
20
19
  };
@@ -13,16 +13,16 @@ let { handler, name, label, hint, required = false, validateOn, debounceMs, disa
13
13
  on={on}>
14
14
  {#snippet children(binding, validating)}
15
15
  <FormField label={label} hint={hint} required={required}>
16
- <span class="form-field-validatable__control">{@render field(binding, validating)}</span>
16
+ {#snippet children(ids)}
17
+ <span class="form-field-validatable__control">{@render field(binding, validating, ids)}</span>
18
+ {/snippet}
17
19
  </FormField>
18
20
  {/snippet}
19
21
  </Validatable>
20
22
 
21
23
  <!--
22
24
  @component
23
- FormFieldValidatable — `FormField` and `Validatable` in one component: a label plus a validated field, with the error message outside the `<label>`, so
24
- screen readers announce it as the field's description rather than as part of the field's name. Composing them by hand keeps that only with `Validatable` on
25
- the outside and `FormField` inside its snippet.
25
+ FormFieldValidatable — `FormField` and `Validatable` in one component: a label plus a validated field, with the error message below the field.
26
26
  -->
27
27
 
28
28
  <style>.form-field-validatable__control {
@@ -1,5 +1,6 @@
1
1
  import type { FormValidationHandler } from '../../core/validation';
2
2
  import { type ErrorPlacement, type FieldBinding, type ValidateOnEvent } from '../validatable';
3
+ import { type FormFieldIds } from './cmp.form-field.svelte';
3
4
  import type { Snippet } from 'svelte';
4
5
  declare function $$render<T extends Record<string, unknown>, K extends keyof T & string>(): {
5
6
  props: {
@@ -27,8 +28,8 @@ declare function $$render<T extends Record<string, unknown>, K extends keyof T &
27
28
  change?: (value: T[K]) => void;
28
29
  blur?: () => void;
29
30
  };
30
- /** Receives the wired `FieldBinding<T[K]>` — spread or wire onto the kit input — plus the `validating` flag. */
31
- children: Snippet<[FieldBinding<T[K]>, boolean]>;
31
+ /** Receives the wired `FieldBinding<T[K]>` — spread or wire onto the kit input — the `validating` flag, and `FormField`'s ids. */
32
+ children: Snippet<[FieldBinding<T[K]>, boolean, FormFieldIds]>;
32
33
  };
33
34
  exports: {};
34
35
  bindings: "";
@@ -49,11 +50,7 @@ interface $$IsomorphicComponent {
49
50
  <T extends Record<string, unknown>, K extends keyof T & string>(internal: unknown, props: ReturnType<__sveltets_Render<T, K>['props']> & {}): ReturnType<__sveltets_Render<T, K>['exports']>;
50
51
  z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
51
52
  }
52
- /**
53
- * FormFieldValidatable — `FormField` and `Validatable` in one component: a label plus a validated field, with the error message outside the `<label>`, so
54
- * screen readers announce it as the field's description rather than as part of the field's name. Composing them by hand keeps that only with `Validatable` on
55
- * the outside and `FormField` inside its snippet.
56
- */
53
+ /** FormFieldValidatable — `FormField` and `Validatable` in one component: a label plus a validated field, with the error message below the field. */
57
54
  declare const Cmp: $$IsomorphicComponent;
58
55
  type Cmp<T extends Record<string, unknown>, K extends keyof T & string> = InstanceType<typeof Cmp<T, K>>;
59
56
  export default Cmp;