@streamscloud/kit 0.89.1 → 0.90.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/README.md +2 -1
- package/dist/core/validation/form-validation-handler/form-validation-handler.svelte.d.ts +4 -2
- package/dist/core/validation/form-validation-handler/form-validation-handler.svelte.js +1 -1
- package/dist/ui/_internal/form-field-label-context/form-field-label-context.svelte.d.ts +15 -0
- package/dist/ui/_internal/form-field-label-context/form-field-label-context.svelte.js +20 -0
- package/dist/ui/_internal/form-field-label-context/index.d.ts +1 -0
- package/dist/ui/_internal/form-field-label-context/index.js +1 -0
- package/dist/ui/checkbox/cmp.checkbox.svelte +11 -2
- package/dist/ui/chip-group/cmp.chip-group.svelte +10 -3
- package/dist/ui/chip-group/cmp.chip-group.svelte.d.ts +4 -2
- package/dist/ui/color-picker/cmp.color-picker.svelte +12 -2
- package/dist/ui/color-picker/cmp.color-picker.svelte.d.ts +1 -3
- package/dist/ui/date-picker/cmp.date-picker.svelte +6 -4
- package/dist/ui/date-picker/cmp.date-picker.svelte.d.ts +2 -9
- package/dist/ui/date-picker/cmp.date-time-picker.svelte +19 -3
- package/dist/ui/date-picker/cmp.date-time-picker.svelte.d.ts +3 -5
- package/dist/ui/date-picker/cmp.time-picker.svelte +3 -1
- package/dist/ui/date-picker/cmp.time-picker.svelte.d.ts +1 -4
- package/dist/ui/document-tabs/types.d.ts +0 -1
- package/dist/ui/form-field/cmp.form-field-validatable.svelte +4 -4
- package/dist/ui/form-field/cmp.form-field-validatable.svelte.d.ts +4 -7
- package/dist/ui/form-field/cmp.form-field.svelte +31 -79
- package/dist/ui/form-field/cmp.form-field.svelte.d.ts +11 -10
- package/dist/ui/form-field/form-field-label.svelte.d.ts +6 -0
- package/dist/ui/form-field/form-field-label.svelte.js +19 -0
- package/dist/ui/form-field/index.d.ts +1 -1
- package/dist/ui/icon-picker/cmp.icon-picker.svelte +3 -1
- package/dist/ui/icon-picker/cmp.icon-picker.svelte.d.ts +3 -1
- package/dist/ui/input/cmp.input.svelte +5 -1
- package/dist/ui/input/types.d.ts +0 -3
- package/dist/ui/numeral-input/cmp.numeral-input.svelte +6 -2
- package/dist/ui/numeral-input/cmp.numeral-input.svelte.d.ts +0 -4
- package/dist/ui/option-pill/cmp.option-pill.svelte.d.ts +0 -2
- package/dist/ui/page-layout/cmp.page-layout.svelte.d.ts +1 -2
- package/dist/ui/pin-input/cmp.pin-input.svelte +11 -3
- package/dist/ui/pin-input/cmp.pin-input.svelte.d.ts +1 -1
- package/dist/ui/popover/cmp.popover.svelte +2 -1
- package/dist/ui/popover/cmp.popover.svelte.d.ts +2 -6
- package/dist/ui/radio/cmp.radio.svelte +12 -3
- package/dist/ui/rich-text-input/cmp.rich-text-input.svelte +19 -7
- package/dist/ui/rich-text-input/cmp.rich-text-input.svelte.d.ts +1 -2
- package/dist/ui/segmented-control/cmp.segmented-control.svelte +5 -3
- package/dist/ui/segmented-control/cmp.segmented-control.svelte.d.ts +1 -2
- package/dist/ui/select/cmp.input-suggest.svelte +5 -1
- package/dist/ui/select/cmp.input-suggest.svelte.d.ts +0 -3
- package/dist/ui/select/cmp.multiselect-tree.svelte +2 -1
- package/dist/ui/select/cmp.multiselect-tree.svelte.d.ts +1 -3
- package/dist/ui/select/cmp.singleselect-async.svelte +5 -2
- package/dist/ui/select/cmp.singleselect-async.svelte.d.ts +0 -2
- package/dist/ui/select/cmp.singleselect.svelte +3 -1
- package/dist/ui/select/cmp.singleselect.svelte.d.ts +0 -2
- package/dist/ui/select/multiselect-base.svelte +6 -2
- package/dist/ui/select/select-core.svelte.js +0 -2
- package/dist/ui/select/select-listbox.svelte +1 -5
- package/dist/ui/select/types.d.ts +2 -6
- package/dist/ui/slider/cmp.slider.svelte +6 -2
- package/dist/ui/slider/cmp.slider.svelte.d.ts +1 -0
- package/dist/ui/tabs/types.d.ts +0 -1
- package/dist/ui/textarea/cmp.textarea.svelte +5 -1
- package/dist/ui/textarea/cmp.textarea.svelte.d.ts +0 -2
- package/dist/ui/toggle/cmp.toggle.svelte +4 -2
- package/dist/ui/validatable/cmp.validatable.svelte +1 -2
- package/dist/ui/validatable/cmp.validatable.svelte.d.ts +1 -2
- package/dist/ui/video/cmp.video.svelte.d.ts +0 -1
- package/dist/ui/visual-select/cmp.visual-select.svelte +11 -3
- package/dist/ui/visual-select/cmp.visual-select.svelte.d.ts +2 -1
- package/package.json +3 -2
|
@@ -1,25 +1,26 @@
|
|
|
1
|
+
export type FormFieldIds = {
|
|
2
|
+
/** For a control outside the kit — kit controls pick the label up by themselves. */
|
|
3
|
+
labelId: string | undefined;
|
|
4
|
+
};
|
|
1
5
|
import type { Snippet } from 'svelte';
|
|
2
6
|
type Props = {
|
|
3
|
-
/** Label rendered above the
|
|
7
|
+
/** Label rendered above the field. String renders as text; Snippet allows extra content (e.g. tooltip icon) next to the text. */
|
|
4
8
|
label?: string | Snippet;
|
|
5
|
-
/** Affordance pinned to the trailing edge of the label row — rendered only alongside a `label`. String renders the standard info icon — keyboard-focusable, named by the text — with that text in a tooltip; Snippet renders anything (button, link, badge).
|
|
9
|
+
/** Affordance pinned to the trailing edge of the label row — rendered only alongside a `label`. String renders the standard info icon — keyboard-focusable, named by the text — with that text in a tooltip; Snippet renders anything (button, link, badge). */
|
|
6
10
|
hint?: string | Snippet;
|
|
7
11
|
/** Appends a danger-colored `*` to the label. Purely visual — does not enforce anything. */
|
|
8
12
|
required?: boolean;
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
* A control made of several fields (`SegmentedControl`, `PinInput`) is not named by it — give it its own `aria-label`.
|
|
12
|
-
*/
|
|
13
|
-
children: Snippet;
|
|
13
|
+
/** Field slot. A kit control inside is named by the label on its own: a text input through `<label for>`, anything else through `aria-labelledby`. */
|
|
14
|
+
children: Snippet<[FormFieldIds]>;
|
|
14
15
|
};
|
|
15
16
|
/**
|
|
16
|
-
* FormField — a label above
|
|
17
|
-
*
|
|
17
|
+
* FormField — a label above a field slot; a kit control inside is named by the label on its own, a control outside the kit takes the slot's `labelId` as its
|
|
18
|
+
* `aria-labelledby`. Errors are `FormFieldValidatable`'s job.
|
|
18
19
|
*
|
|
19
20
|
* ### CSS Custom Properties
|
|
20
21
|
* | Property | Description | Default |
|
|
21
22
|
* |---|---|---|
|
|
22
|
-
* | `--sc-kit--form-field--gap` | Vertical gap between label and the
|
|
23
|
+
* | `--sc-kit--form-field--gap` | Vertical gap between label and the field slot | `0.375rem` (6px) |
|
|
23
24
|
* | `--sc-kit--form-field--hint--color` | Hint color (inherited by the default info icon) | `--sc-kit--color--text--muted` |
|
|
24
25
|
* | `--sc-kit--form-field--label--color` | Label text color | `--sc-kit--color--text--primary` |
|
|
25
26
|
* | `--sc-kit--form-field--label--font-size` | Label font size | `--sc-kit--font-size--sm` |
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FormFieldLabelContext } from '../_internal/form-field-label-context';
|
|
2
|
+
export type FormFieldLabel = FormFieldLabelContext & {
|
|
3
|
+
/** The id the label's `for` points at — the first mounted text input that claimed the label, if any. */
|
|
4
|
+
readonly controlId: string | undefined;
|
|
5
|
+
};
|
|
6
|
+
export declare const createFormFieldLabel: (id: string, hasLabel: () => boolean) => FormFieldLabel;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { untrack } from 'svelte';
|
|
2
|
+
export const createFormFieldLabel = (id, hasLabel) => {
|
|
3
|
+
const labelId = $derived(hasLabel() ? id : undefined);
|
|
4
|
+
let claimants = $state.raw([]);
|
|
5
|
+
return {
|
|
6
|
+
get labelId() {
|
|
7
|
+
return labelId;
|
|
8
|
+
},
|
|
9
|
+
get controlId() {
|
|
10
|
+
return claimants[0]?.();
|
|
11
|
+
},
|
|
12
|
+
claimControl: (controlId) => {
|
|
13
|
+
claimants = [...untrack(() => claimants), controlId];
|
|
14
|
+
return () => {
|
|
15
|
+
claimants = untrack(() => claimants).filter((claimant) => claimant !== controlId);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default as FormField } from './cmp.form-field.svelte';
|
|
1
|
+
export { default as FormField, type FormFieldIds } from './cmp.form-field.svelte';
|
|
2
2
|
export { default as FormFieldValidatable } from './cmp.form-field-validatable.svelte';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">import { Icon } from '../icon';
|
|
2
2
|
import { Singleselect } from '../select';
|
|
3
3
|
import { isIconPickerGroup } from './types';
|
|
4
|
-
let { value, icons, placeholder = '', disabled = false, error = false, borderless = false, cols = 7, on } = $props();
|
|
4
|
+
let { value, icons, placeholder = '', disabled = false, error = false, borderless = false, cols = 7, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, on } = $props();
|
|
5
5
|
const toOption = (i) => ({ value: i.value, label: i.label, disabled: i.disabled });
|
|
6
6
|
const selectOptions = $derived(icons.map((item) => (isIconPickerGroup(item) ? { label: item.label, options: item.options.map(toOption) } : toOption(item))));
|
|
7
7
|
const iconBySrc = $derived(new Map(icons.flatMap((item) => isIconPickerGroup(item) ? item.options.map((o) => [o.value, o.src]) : [[item.value, item.src]])));
|
|
@@ -15,6 +15,8 @@ const iconBySrc = $derived(new Map(icons.flatMap((item) => isIconPickerGroup(ite
|
|
|
15
15
|
disabled={disabled}
|
|
16
16
|
error={error}
|
|
17
17
|
borderless={borderless}
|
|
18
|
+
aria-label={ariaLabel}
|
|
19
|
+
aria-labelledby={ariaLabelledby}
|
|
18
20
|
on={{ change: (v) => on?.change?.(v) }}>
|
|
19
21
|
{#snippet selectionSnippet({ option })}
|
|
20
22
|
{@const src = iconBySrc.get(option.value)}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type IconPickerItem } from './types';
|
|
2
2
|
type Props = {
|
|
3
3
|
value: string | null;
|
|
4
|
-
/** Flat list of icons or a list of
|
|
4
|
+
/** Flat list of icons or a list of labeled groups. */
|
|
5
5
|
icons: IconPickerItem[];
|
|
6
6
|
placeholder?: string;
|
|
7
7
|
disabled?: boolean;
|
|
@@ -10,6 +10,8 @@ type Props = {
|
|
|
10
10
|
borderless?: boolean;
|
|
11
11
|
/** Tiles per row in the popover grid. @default 7 */
|
|
12
12
|
cols?: number;
|
|
13
|
+
'aria-label'?: string;
|
|
14
|
+
'aria-labelledby'?: string;
|
|
13
15
|
on?: {
|
|
14
16
|
change?: (value: string | null) => void;
|
|
15
17
|
};
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<script lang="ts">import { HtmlHelper, StringHelper } from '../../core/utils';
|
|
2
|
+
import { claimFormFieldLabel } from '../_internal/form-field-label-context';
|
|
2
3
|
import { InputClearButton } from '../_internal/input-clear-button';
|
|
3
4
|
import { InputEmojiPicker } from '../_internal/input-emoji-picker';
|
|
4
5
|
import { InputPasswordToggle } from '../_internal/input-password-toggle';
|
|
5
6
|
import { IconSlot } from '../icon';
|
|
6
7
|
const { value, type = 'text', size = 'md', disabled = false, readonly = false, inert = false, error = false, borderless = false, clearable = false, emoji = false, trim = true, placeholder = '', title = '', name = '', id = null, autofocus = false, maxLength = null, autocomplete, autocapitalize, inputmode, enterkeyhint, spellcheck, 'aria-describedby': ariaDescribedby, 'aria-label': ariaLabel, 'aria-required': ariaRequired, on, icon, iconPosition = 'leading', secondaryIcon, prefix, suffix } = $props();
|
|
8
|
+
const fallbackId = $props.id();
|
|
9
|
+
const controlId = $derived(id ?? fallbackId);
|
|
10
|
+
claimFormFieldLabel(() => controlId);
|
|
7
11
|
let inputEl = $state.raw(undefined);
|
|
8
12
|
let passwordRevealed = $state(false);
|
|
9
13
|
const valueParsed = $derived(value ?? '');
|
|
@@ -121,7 +125,7 @@ const focusInput = () => inputEl?.focus();
|
|
|
121
125
|
class="input__el"
|
|
122
126
|
type={effectiveType}
|
|
123
127
|
name={name}
|
|
124
|
-
id={
|
|
128
|
+
id={controlId}
|
|
125
129
|
placeholder={placeholder}
|
|
126
130
|
disabled={disabled}
|
|
127
131
|
readonly={readonly}
|
package/dist/ui/input/types.d.ts
CHANGED
|
@@ -10,15 +10,12 @@ export type InputProps = {
|
|
|
10
10
|
/** @default 'md' */
|
|
11
11
|
size?: InputSize;
|
|
12
12
|
disabled?: boolean;
|
|
13
|
-
/** Non-editable but selectable. The value is still submitted with a form. */
|
|
14
13
|
readonly?: boolean;
|
|
15
|
-
/** Visual display mode — non-focusable, no hover/focus animations, hidden clear button. */
|
|
16
14
|
inert?: boolean;
|
|
17
15
|
/** Visual error state — also sets `aria-invalid="true"`. */
|
|
18
16
|
error?: boolean;
|
|
19
17
|
/** Strip border + background + focus underline. */
|
|
20
18
|
borderless?: boolean;
|
|
21
|
-
/** Show the default × clear button when value is non-empty. */
|
|
22
19
|
clearable?: boolean;
|
|
23
20
|
/** Show the emoji-picker trigger inside the input. */
|
|
24
21
|
emoji?: boolean;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
<script lang="ts">import {
|
|
1
|
+
<script lang="ts">import { claimFormFieldLabel } from '../_internal/form-field-label-context';
|
|
2
|
+
import { InputClearButton } from '../_internal/input-clear-button';
|
|
2
3
|
import { IconSlot } from '../icon';
|
|
3
4
|
import { formatNumeral, NumeralThousandGroupStyles, registerCursorTracker } from 'cleave-zen';
|
|
4
5
|
import { untrack } from 'svelte';
|
|
5
6
|
const { size = 'md', disabled = false, readonly = false, inert = false, error = false, borderless = false, placeholder = '', title = '', name = '', id = null, autofocus = false, min = null, max = null, minExclusive = false, maxExclusive = false, decimals = 2, suffix, hideResolvedValue = false, 'aria-describedby': ariaDescribedby, 'aria-label': ariaLabel, 'aria-required': ariaRequired, icon, iconPosition = 'leading', secondaryIcon, ...mode } = $props();
|
|
7
|
+
const fallbackId = $props.id();
|
|
8
|
+
const controlId = $derived(id ?? fallbackId);
|
|
9
|
+
claimFormFieldLabel(() => controlId);
|
|
6
10
|
let inputEl = $state.raw(undefined);
|
|
7
11
|
let focused = $state(false);
|
|
8
12
|
let typedValue = $state(undefined);
|
|
@@ -136,7 +140,7 @@ const initInput = (node) => {
|
|
|
136
140
|
type="text"
|
|
137
141
|
inputmode="decimal"
|
|
138
142
|
name={name}
|
|
139
|
-
id={
|
|
143
|
+
id={controlId}
|
|
140
144
|
placeholder={placeholder}
|
|
141
145
|
disabled={disabled}
|
|
142
146
|
readonly={readonly}
|
|
@@ -3,9 +3,7 @@ type BaseProps = {
|
|
|
3
3
|
/** @default 'md' */
|
|
4
4
|
size?: 'sm' | 'md' | 'lg';
|
|
5
5
|
disabled?: boolean;
|
|
6
|
-
/** Non-editable but selectable; value still submitted with a form. */
|
|
7
6
|
readonly?: boolean;
|
|
8
|
-
/** Visual display mode — non-focusable, no hover/focus animations, no clear button. */
|
|
9
7
|
inert?: boolean;
|
|
10
8
|
/** Visual error state — also sets `aria-invalid="true"`. */
|
|
11
9
|
error?: boolean;
|
|
@@ -41,7 +39,6 @@ type BaseProps = {
|
|
|
41
39
|
secondaryIcon?: IconProp;
|
|
42
40
|
};
|
|
43
41
|
type ClearableProps = BaseProps & {
|
|
44
|
-
/** Value can be cleared to `null`. Trigger shows an × button when set. */
|
|
45
42
|
clearable: true;
|
|
46
43
|
value: number | null | undefined;
|
|
47
44
|
on?: {
|
|
@@ -55,7 +52,6 @@ type ClearableProps = BaseProps & {
|
|
|
55
52
|
};
|
|
56
53
|
};
|
|
57
54
|
type RequiredProps = BaseProps & {
|
|
58
|
-
/** Value is required; no clear button. */
|
|
59
55
|
clearable: false;
|
|
60
56
|
value: number;
|
|
61
57
|
on?: {
|
|
@@ -2,7 +2,6 @@ import type { OptionPillOption } from './types';
|
|
|
2
2
|
declare function $$render<T>(): {
|
|
3
3
|
props: ({
|
|
4
4
|
options: OptionPillOption<T>[];
|
|
5
|
-
/** Disable the whole group. */
|
|
6
5
|
disabled?: boolean;
|
|
7
6
|
/** Custom equality comparator for non-primitive values (objects with `id`, etc.). @default (a, b) => a === b */
|
|
8
7
|
compare?: (a: T, b: T) => boolean;
|
|
@@ -15,7 +14,6 @@ declare function $$render<T>(): {
|
|
|
15
14
|
};
|
|
16
15
|
}) | ({
|
|
17
16
|
options: OptionPillOption<T>[];
|
|
18
|
-
/** Disable the whole group. */
|
|
19
17
|
disabled?: boolean;
|
|
20
18
|
/** Custom equality comparator for non-primitive values (objects with `id`, etc.). @default (a, b) => a === b */
|
|
21
19
|
compare?: (a: T, b: T) => boolean;
|
|
@@ -9,9 +9,8 @@ type Props = {
|
|
|
9
9
|
/** Editor chrome — the fixed panels-toggle + close pair. Set it to render both buttons; omit it entirely for a non-editor page. */
|
|
10
10
|
editorControls?: EditorControls;
|
|
11
11
|
scrollable?: boolean;
|
|
12
|
-
/** Sets `inert` on the whole layout — header and body stop taking focus, clicks and assistive-tech interaction. */
|
|
13
12
|
inert?: boolean;
|
|
14
|
-
/** Shows a `Spinner` centered over the layout; `blocking` also makes the whole layout `inert` and dims it.
|
|
13
|
+
/** Shows a `Spinner` centered over the layout; `blocking` also makes the whole layout `inert` and dims it. */
|
|
15
14
|
loading?: PageLoading;
|
|
16
15
|
};
|
|
17
16
|
/**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
<script lang="ts">import {
|
|
2
|
-
|
|
1
|
+
<script lang="ts">import { getFormFieldLabel, resolveAriaLabelledby } from '../_internal/form-field-label-context';
|
|
2
|
+
import { PinInputLocalization } from './pin-input-localization';
|
|
3
|
+
const { value, length = 6, mask = false, disabled = false, error = false, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, on } = $props();
|
|
4
|
+
const formFieldLabel = getFormFieldLabel();
|
|
3
5
|
const localization = new PinInputLocalization();
|
|
4
6
|
let cells = $state([]);
|
|
5
7
|
const cellValue = (i) => value[i] ?? '';
|
|
@@ -59,7 +61,13 @@ const handleFocus = (e) => {
|
|
|
59
61
|
};
|
|
60
62
|
</script>
|
|
61
63
|
|
|
62
|
-
<div
|
|
64
|
+
<div
|
|
65
|
+
class="pin-input"
|
|
66
|
+
class:pin-input--disabled={disabled}
|
|
67
|
+
class:pin-input--error={error}
|
|
68
|
+
role="group"
|
|
69
|
+
aria-label={ariaLabel}
|
|
70
|
+
aria-labelledby={resolveAriaLabelledby(formFieldLabel, ariaLabel, ariaLabelledby)}>
|
|
63
71
|
{#each Array(length) as _, i (i)}
|
|
64
72
|
<input
|
|
65
73
|
aria-label={localization.cellLabel(i + 1, length)}
|
|
@@ -7,8 +7,8 @@ type Props = {
|
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
/** Visual error state — also sets `aria-invalid="true"` on each cell. */
|
|
9
9
|
error?: boolean;
|
|
10
|
-
/** Accessible label for the cell group. Consumer-provided (no kit default). */
|
|
11
10
|
'aria-label'?: string;
|
|
11
|
+
'aria-labelledby'?: string;
|
|
12
12
|
'aria-describedby'?: string;
|
|
13
13
|
on?: {
|
|
14
14
|
input?: (value: string) => void;
|
|
@@ -4,7 +4,7 @@ import { validateTrigger } from './validate-trigger';
|
|
|
4
4
|
import { autoUpdate, computePosition, flip, offset as offsetMiddleware, shift, size } from '@floating-ui/dom';
|
|
5
5
|
import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_20_regular.svg?raw';
|
|
6
6
|
import { tick } from 'svelte';
|
|
7
|
-
let { position = 'bottom-start', disabled = false, fixedPosition = false, offset = 4, boundaryMargin = 8, backdrop = false, matchWidth = false, panel = true, keepOpen = false, fillContainer = false, tabbable = true, on, children, trigger, 'aria-label': ariaLabel } = $props();
|
|
7
|
+
let { position = 'bottom-start', disabled = false, fixedPosition = false, offset = 4, boundaryMargin = 8, backdrop = false, matchWidth = false, panel = true, keepOpen = false, fillContainer = false, tabbable = true, on, children, trigger, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby } = $props();
|
|
8
8
|
let opened = $state(false);
|
|
9
9
|
let triggerEl = $state.raw(undefined);
|
|
10
10
|
let contentEl = $state.raw(undefined);
|
|
@@ -182,6 +182,7 @@ const handleBackdropClick = (e) => {
|
|
|
182
182
|
disabled={disabled}
|
|
183
183
|
tabindex={tabbable ? undefined : -1}
|
|
184
184
|
aria-label={ariaLabel}
|
|
185
|
+
aria-labelledby={ariaLabelledby}
|
|
185
186
|
aria-haspopup="true"
|
|
186
187
|
aria-expanded={opened}
|
|
187
188
|
onclick={handleTriggerClick}
|
|
@@ -27,14 +27,10 @@ type Props = {
|
|
|
27
27
|
closed?: () => void;
|
|
28
28
|
};
|
|
29
29
|
children: Snippet;
|
|
30
|
-
/**
|
|
31
|
-
* Custom trigger content; defaults to a chevron-down icon. Renders inside the trigger `<button>`,
|
|
32
|
-
* so content MUST be non-interactive — for a button-looking trigger use `Button` with
|
|
33
|
-
* `type="presentational"`. Nesting an interactive element triggers a dev-only warning.
|
|
34
|
-
*/
|
|
30
|
+
/** Content of the trigger `<button>`, so non-interactive only — `Button type="presentational"` for a button look. @default chevron-down icon */
|
|
35
31
|
trigger?: Snippet;
|
|
36
|
-
/** Accessible name for the trigger button — required when the trigger content carries no text (an icon-only trigger). */
|
|
37
32
|
'aria-label'?: string;
|
|
33
|
+
'aria-labelledby'?: string;
|
|
38
34
|
};
|
|
39
35
|
/**
|
|
40
36
|
* Popover — floating content anchored to a trigger element. Click trigger to open;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts">import { getFormFieldLabel } from '../_internal/form-field-label-context';
|
|
2
|
+
const { selected, label, disabled = false, title, name, on } = $props();
|
|
2
3
|
const ariaLabel = $derived(typeof label === 'string' ? label : name);
|
|
4
|
+
const formFieldLabel = getFormFieldLabel();
|
|
3
5
|
const handleClick = (event) => {
|
|
4
6
|
event.stopPropagation();
|
|
5
7
|
if (disabled || selected) {
|
|
@@ -7,11 +9,18 @@ const handleClick = (event) => {
|
|
|
7
9
|
}
|
|
8
10
|
on?.change?.();
|
|
9
11
|
};
|
|
10
|
-
export {};
|
|
11
12
|
</script>
|
|
12
13
|
|
|
13
14
|
<label class="radio" class:radio--on={selected} class:radio--disabled={disabled} title={title}>
|
|
14
|
-
<button
|
|
15
|
+
<button
|
|
16
|
+
type="button"
|
|
17
|
+
class="radio__dot"
|
|
18
|
+
role="radio"
|
|
19
|
+
aria-checked={selected}
|
|
20
|
+
aria-label={ariaLabel}
|
|
21
|
+
aria-labelledby={label || ariaLabel ? undefined : formFieldLabel?.labelId}
|
|
22
|
+
disabled={disabled}
|
|
23
|
+
onclick={handleClick}>
|
|
15
24
|
<span class="radio__inner" class:radio__inner--visible={selected} aria-hidden="true"></span>
|
|
16
25
|
</button>
|
|
17
26
|
{#if typeof label === 'string'}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<script lang="ts">import { AppTheme } from '../../core/theme';
|
|
2
2
|
import { HtmlHelper } from '../../core/utils';
|
|
3
|
+
import { getFormFieldLabel, resolveAriaLabelledby } from '../_internal/form-field-label-context';
|
|
3
4
|
import { InputEmojiPicker } from '../_internal/input-emoji-picker';
|
|
4
5
|
import { HtmlBlock } from '../html-block';
|
|
5
6
|
import { RichTextInputLocalization } from './rich-text-input-localization';
|
|
6
7
|
import { addValidatedLinkButton } from './validated-link-button';
|
|
7
8
|
import { onDestroy, onMount } from 'svelte';
|
|
8
|
-
const { value, size = 'md', disabled = false, readonly = false, inert = false, error = false, borderless = false, emoji = false, autofocus = false, rows = 0, placeholder = '', options, conf, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
|
|
9
|
-
const id =
|
|
9
|
+
const { value, size = 'md', disabled = false, readonly = false, inert = false, error = false, borderless = false, emoji = false, autofocus = false, rows = 0, placeholder = '', options, conf, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
|
|
10
|
+
const id = $props.id();
|
|
11
|
+
const formFieldLabel = getFormFieldLabel();
|
|
10
12
|
let elementRef = $state.raw(undefined);
|
|
11
13
|
let toolbarRef = $state.raw(undefined);
|
|
12
14
|
let editorRef = $state.raw(null);
|
|
@@ -142,13 +144,23 @@ const onEmojiSelected = (emojiChar) => insertEmoji(emojiChar);
|
|
|
142
144
|
class:rich-text-input--readonly={readonly}
|
|
143
145
|
class:rich-text-input--inert={inert}
|
|
144
146
|
class:rich-text-input--error={error}
|
|
145
|
-
class:rich-text-input--borderless={borderless}
|
|
146
|
-
aria-label={ariaLabel}
|
|
147
|
-
aria-describedby={ariaDescribedby}
|
|
148
|
-
aria-required={ariaRequired ? 'true' : undefined}>
|
|
147
|
+
class:rich-text-input--borderless={borderless}>
|
|
149
148
|
<div class="rich-text-input__toolbar" bind:this={toolbarRef}></div>
|
|
150
149
|
<HtmlBlock>
|
|
151
|
-
<div
|
|
150
|
+
<div
|
|
151
|
+
bind:this={elementRef}
|
|
152
|
+
class="rich-text-input__input"
|
|
153
|
+
id={id}
|
|
154
|
+
role="textbox"
|
|
155
|
+
tabindex={disabled || inert ? undefined : 0}
|
|
156
|
+
aria-multiline="true"
|
|
157
|
+
aria-label={ariaLabel}
|
|
158
|
+
aria-labelledby={resolveAriaLabelledby(formFieldLabel, ariaLabel, ariaLabelledby)}
|
|
159
|
+
aria-describedby={ariaDescribedby}
|
|
160
|
+
aria-required={ariaRequired ? 'true' : undefined}
|
|
161
|
+
aria-invalid={error ? 'true' : undefined}
|
|
162
|
+
aria-readonly={readonly ? 'true' : undefined}>
|
|
163
|
+
</div>
|
|
152
164
|
</HtmlBlock>
|
|
153
165
|
{#if emoji}
|
|
154
166
|
<span class="rich-text-input__emoji">
|
|
@@ -6,9 +6,7 @@ type Props = {
|
|
|
6
6
|
/** @default 'md' — controls the editor root font size (and downstream rem-based sizing). */
|
|
7
7
|
size?: 'sm' | 'md' | 'lg';
|
|
8
8
|
disabled?: boolean;
|
|
9
|
-
/** Non-editable but still visible (mirrors `disabled` in editor mode). */
|
|
10
9
|
readonly?: boolean;
|
|
11
|
-
/** Visual display mode — no focus, no hover, hidden affordances. */
|
|
12
10
|
inert?: boolean;
|
|
13
11
|
/** Visual error state. */
|
|
14
12
|
error?: boolean;
|
|
@@ -24,6 +22,7 @@ type Props = {
|
|
|
24
22
|
/** Escape-hatch for extra HugeRTE init options, merged AFTER kit defaults. */
|
|
25
23
|
conf?: Partial<RawEditorOptions>;
|
|
26
24
|
'aria-label'?: string;
|
|
25
|
+
'aria-labelledby'?: string;
|
|
27
26
|
'aria-describedby'?: string;
|
|
28
27
|
'aria-required'?: boolean;
|
|
29
28
|
on?: {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script lang="ts" module>export {};
|
|
2
2
|
</script>
|
|
3
3
|
|
|
4
|
-
<script lang="ts" generics="T">
|
|
5
|
-
const { value, segments, variant = 'tray', size = 'md', fullWidth = false, compare, 'aria-label': ariaLabel, on } = $props();
|
|
4
|
+
<script lang="ts" generics="T">import { getFormFieldLabel, resolveAriaLabelledby } from '../_internal/form-field-label-context';
|
|
5
|
+
const { value, segments, variant = 'tray', size = 'md', fullWidth = false, compare, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, on } = $props();
|
|
6
|
+
const formFieldLabel = getFormFieldLabel();
|
|
6
7
|
const segmentIdPrefix = $props.id();
|
|
7
8
|
const segmentEls = [];
|
|
8
9
|
const isActive = (segment) => (compare ? compare(value, segment.value) : value === segment.value);
|
|
@@ -51,7 +52,8 @@ const handleKeydown = (event, from) => {
|
|
|
51
52
|
class="segmented-control segmented-control--{variant} segmented-control--{size}"
|
|
52
53
|
class:segmented-control--full={fullWidth}
|
|
53
54
|
role="radiogroup"
|
|
54
|
-
aria-label={ariaLabel}
|
|
55
|
+
aria-label={ariaLabel}
|
|
56
|
+
aria-labelledby={resolveAriaLabelledby(formFieldLabel, ariaLabel, ariaLabelledby)}>
|
|
55
57
|
{#each segments as segment, i (i)}
|
|
56
58
|
{@const segmentLabelId = `${segmentIdPrefix}-${i}`}
|
|
57
59
|
<button
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export type Segment<TValue = unknown> = {
|
|
2
2
|
value: TValue;
|
|
3
3
|
label: string;
|
|
4
|
-
/** Non-interactive — click does nothing. */
|
|
5
4
|
disabled?: boolean;
|
|
6
5
|
/** Visual flag — colors the segment in the danger tone (validation hint). */
|
|
7
6
|
error?: boolean;
|
|
@@ -23,8 +22,8 @@ declare function $$render<T>(): {
|
|
|
23
22
|
fullWidth?: boolean;
|
|
24
23
|
/** Equality for object T values. @default reference equality (`===`) */
|
|
25
24
|
compare?: (a: T, b: T) => boolean;
|
|
26
|
-
/** Accessible name of the group. */
|
|
27
25
|
'aria-label'?: string;
|
|
26
|
+
'aria-labelledby'?: string;
|
|
28
27
|
on?: {
|
|
29
28
|
change?: (value: T) => void;
|
|
30
29
|
};
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
</script>
|
|
3
3
|
|
|
4
4
|
<script lang="ts">import { HtmlHelper } from '../../core/utils';
|
|
5
|
+
import { claimFormFieldLabel } from '../_internal/form-field-label-context';
|
|
5
6
|
import { InputClearButton } from '../_internal/input-clear-button';
|
|
6
7
|
import { IconSlot } from '../icon';
|
|
7
8
|
import { default as SelectListbox } from './select-listbox.svelte';
|
|
8
9
|
const { value, suggestions, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, clearable = false, trim = true, icon, name, id, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
|
|
10
|
+
const fallbackId = $props.id();
|
|
11
|
+
const controlId = $derived(id ?? fallbackId);
|
|
12
|
+
claimFormFieldLabel(() => controlId);
|
|
9
13
|
let inputEl = $state.raw(undefined);
|
|
10
14
|
let rootEl = $state.raw(undefined);
|
|
11
15
|
let isOpen = $state(false);
|
|
@@ -241,7 +245,7 @@ const handlePickRow = (row) => {
|
|
|
241
245
|
aria-describedby={ariaDescribedby}
|
|
242
246
|
aria-required={ariaRequired ? 'true' : undefined}
|
|
243
247
|
aria-invalid={error ? 'true' : undefined}
|
|
244
|
-
id={
|
|
248
|
+
id={controlId}
|
|
245
249
|
name={name}
|
|
246
250
|
autocomplete={autocomplete}
|
|
247
251
|
placeholder={placeholder}
|
|
@@ -17,15 +17,12 @@ type Props = {
|
|
|
17
17
|
size?: 'sm' | 'md' | 'lg';
|
|
18
18
|
placeholder?: string;
|
|
19
19
|
disabled?: boolean;
|
|
20
|
-
/** Non-editable but selectable; the popover never opens. */
|
|
21
20
|
readonly?: boolean;
|
|
22
|
-
/** Visual display mode — non-focusable, no hover/focus animations. */
|
|
23
21
|
inert?: boolean;
|
|
24
22
|
/** Visual error state — also sets `aria-invalid="true"`. */
|
|
25
23
|
error?: boolean;
|
|
26
24
|
/** Strip border + background + focus underline. */
|
|
27
25
|
borderless?: boolean;
|
|
28
|
-
/** Show the × clear button when the value is non-empty. */
|
|
29
26
|
clearable?: boolean;
|
|
30
27
|
/**
|
|
31
28
|
* Strip leading / trailing whitespace when the value is committed — focus leaving the control or
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<script lang="ts" generics="T">import { default as MultiselectBase } from './multiselect-base.svelte';
|
|
5
5
|
import { buildFuseIndex, defaultCompare, runFuseSearch } from './select-internals';
|
|
6
6
|
import { isSelectGroup } from './types';
|
|
7
|
-
const { options, value, compare = defaultCompare, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = false, groupHeader = 'toggle-all', selectionMode = 'children-only', selectedDisplay = 'checkbox', createOptions, icon, chevronIcon, optionSnippet, selectionSnippet, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
|
|
7
|
+
const { options, value, compare = defaultCompare, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = false, groupHeader = 'toggle-all', selectionMode = 'children-only', selectedDisplay = 'checkbox', createOptions, icon, chevronIcon, optionSnippet, selectionSnippet, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
|
|
8
8
|
const fuse = $derived(buildFuseIndex(options));
|
|
9
9
|
const loadOptionsShim = (q) => Promise.resolve(runFuseSearch(fuse, q, options));
|
|
10
10
|
// Include flat roots, not only groups — else a childless root can't be picked as a create-parent.
|
|
@@ -70,6 +70,7 @@ $effect(() => {
|
|
|
70
70
|
name={name}
|
|
71
71
|
autocomplete={autocomplete}
|
|
72
72
|
aria-label={ariaLabel}
|
|
73
|
+
aria-labelledby={ariaLabelledby}
|
|
73
74
|
aria-describedby={ariaDescribedby}
|
|
74
75
|
aria-required={ariaRequired}
|
|
75
76
|
on={{
|
|
@@ -21,11 +21,8 @@ declare function $$render<T>(): {
|
|
|
21
21
|
size?: "sm" | "md" | "lg";
|
|
22
22
|
/** Placeholder shown only when nothing is selected and the user is not typing. @default '' */
|
|
23
23
|
placeholder?: string;
|
|
24
|
-
/** Fully disabled — no focus, no interaction, dimmed styling. @default false */
|
|
25
24
|
disabled?: boolean;
|
|
26
|
-
/** Read-only — value visible, but selection cannot change. @default false */
|
|
27
25
|
readonly?: boolean;
|
|
28
|
-
/** Inert subtree — non-interactive but unstyled. @default false */
|
|
29
26
|
inert?: boolean;
|
|
30
27
|
/** Visual error state (accent border + aria-invalid). @default false */
|
|
31
28
|
error?: boolean;
|
|
@@ -81,6 +78,7 @@ declare function $$render<T>(): {
|
|
|
81
78
|
name?: string;
|
|
82
79
|
autocomplete?: HTMLInputAttributes["autocomplete"];
|
|
83
80
|
'aria-label'?: string;
|
|
81
|
+
'aria-labelledby'?: string;
|
|
84
82
|
'aria-describedby'?: string;
|
|
85
83
|
'aria-required'?: boolean;
|
|
86
84
|
on?: {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts" module>export {};
|
|
2
2
|
</script>
|
|
3
3
|
|
|
4
|
-
<script lang="ts" generics="T">import {
|
|
4
|
+
<script lang="ts" generics="T">import { getFormFieldLabel, resolveAriaLabelledby } from '../_internal/form-field-label-context';
|
|
5
|
+
import { InputClearButton } from '../_internal/input-clear-button';
|
|
5
6
|
import { Spinner } from '../_internal/spinner';
|
|
6
7
|
import { Icon, IconSlot } from '../icon';
|
|
7
8
|
import { createSelectCore } from './select-core.svelte';
|
|
@@ -9,7 +10,8 @@ import { defaultCompare } from './select-internals';
|
|
|
9
10
|
import { default as SelectListbox } from './select-listbox.svelte';
|
|
10
11
|
import { SingleselectLocalization } from './singleselect-localization';
|
|
11
12
|
import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_16_regular.svg?raw';
|
|
12
|
-
const { loadOptions, debounceMs = 400, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = true, groupHeader = 'static', compare = defaultCompare, createOptions, icon, chevronIcon, optionSnippet, selectionSnippet, listbox, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
|
|
13
|
+
const { loadOptions, debounceMs = 400, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = true, groupHeader = 'static', compare = defaultCompare, createOptions, icon, chevronIcon, optionSnippet, selectionSnippet, listbox, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
|
|
14
|
+
const formFieldLabel = getFormFieldLabel();
|
|
13
15
|
const localization = new SingleselectLocalization();
|
|
14
16
|
const selectedOption = $derived(mode.value ?? null);
|
|
15
17
|
const selectionLabel = $derived(selectedOption?.label ?? '');
|
|
@@ -101,6 +103,7 @@ const showClear = $derived(mode.clearable && hasValue && isInteractive && !core.
|
|
|
101
103
|
aria-activedescendant={core.isOpen ? `${listboxId}-row-${core.highlight}` : undefined}
|
|
102
104
|
aria-autocomplete="list"
|
|
103
105
|
aria-label={ariaLabel ?? name}
|
|
106
|
+
aria-labelledby={resolveAriaLabelledby(formFieldLabel, ariaLabel, ariaLabelledby)}
|
|
104
107
|
aria-describedby={ariaDescribedby}
|
|
105
108
|
aria-required={ariaRequired}
|
|
106
109
|
aria-invalid={error ? 'true' : undefined}
|
|
@@ -15,7 +15,6 @@ declare function $$render<T>(): {
|
|
|
15
15
|
/** Internal — set by the sync `Singleselect` wrapper. Async typeahead is always searchable. @default true */
|
|
16
16
|
searchable?: boolean;
|
|
17
17
|
} & {
|
|
18
|
-
/** Discriminator — `true` enables the X clear button and allows `value` to be `null`. */
|
|
19
18
|
clearable: true;
|
|
20
19
|
/** Currently selected option (pre-resolved — labels render before the first fetch lands). `null` when nothing is picked. */
|
|
21
20
|
value: SelectOption<T> | null | undefined;
|
|
@@ -33,7 +32,6 @@ declare function $$render<T>(): {
|
|
|
33
32
|
/** Internal — set by the sync `Singleselect` wrapper. Async typeahead is always searchable. @default true */
|
|
34
33
|
searchable?: boolean;
|
|
35
34
|
} & {
|
|
36
|
-
/** Discriminator — `false` removes the X clear button; `value` must always be a non-null `SelectOption<T>`. */
|
|
37
35
|
clearable: false;
|
|
38
36
|
/** Currently selected option — non-null. */
|
|
39
37
|
value: SelectOption<T>;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<script lang="ts" generics="T">import { default as SingleselectAsync } from './cmp.singleselect-async.svelte';
|
|
5
5
|
import { buildFuseIndex, defaultCompare, runFuseSearch } from './select-internals';
|
|
6
6
|
import { isSelectGroup } from './types';
|
|
7
|
-
const { options, compare = defaultCompare, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = true, groupHeader = 'static', createOptions, icon, chevronIcon, optionSnippet, selectionSnippet, listbox, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
|
|
7
|
+
const { options, compare = defaultCompare, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = true, groupHeader = 'static', createOptions, icon, chevronIcon, optionSnippet, selectionSnippet, listbox, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
|
|
8
8
|
const fuse = $derived(buildFuseIndex(options));
|
|
9
9
|
const loadOptionsShim = (q) => Promise.resolve(runFuseSearch(fuse, q, options));
|
|
10
10
|
const resolvedValue = $derived.by(() => {
|
|
@@ -64,6 +64,7 @@ $effect(() => {
|
|
|
64
64
|
name={name}
|
|
65
65
|
autocomplete={autocomplete}
|
|
66
66
|
aria-label={ariaLabel}
|
|
67
|
+
aria-labelledby={ariaLabelledby}
|
|
67
68
|
aria-describedby={ariaDescribedby}
|
|
68
69
|
aria-required={ariaRequired}
|
|
69
70
|
on={{
|
|
@@ -97,6 +98,7 @@ $effect(() => {
|
|
|
97
98
|
name={name}
|
|
98
99
|
autocomplete={autocomplete}
|
|
99
100
|
aria-label={ariaLabel}
|
|
101
|
+
aria-labelledby={ariaLabelledby}
|
|
100
102
|
aria-describedby={ariaDescribedby}
|
|
101
103
|
aria-required={ariaRequired}
|
|
102
104
|
on={{
|
|
@@ -11,7 +11,6 @@ declare function $$render<T>(): {
|
|
|
11
11
|
/** When true, typing filters the options; when false, it is a click/keyboard-only picker. @default true */
|
|
12
12
|
searchable?: boolean;
|
|
13
13
|
} & {
|
|
14
|
-
/** Discriminator — `true` enables the X clear button and allows `value` to be `null`. */
|
|
15
14
|
clearable: true;
|
|
16
15
|
/** Current selection by value. `null` when nothing is picked. */
|
|
17
16
|
value: T | null | undefined;
|
|
@@ -27,7 +26,6 @@ declare function $$render<T>(): {
|
|
|
27
26
|
/** When true, typing filters the options; when false, it is a click/keyboard-only picker. @default true */
|
|
28
27
|
searchable?: boolean;
|
|
29
28
|
} & {
|
|
30
|
-
/** Discriminator — `false` hides the X button; `value` must always be a non-null `T`. */
|
|
31
29
|
clearable: false;
|
|
32
30
|
/** Current selection by value — non-null. */
|
|
33
31
|
value: T;
|