@happyvertical/smrt-ui 0.43.3 → 0.43.5
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/AGENTS.md +33 -0
- package/README.md +50 -4
- package/dist/components/forms/Checkbox.svelte +15 -6
- package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
- package/dist/components/forms/Combobox.svelte +45 -10
- package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
- package/dist/components/forms/FilePicker.svelte +5 -2
- package/dist/components/forms/Form.svelte +57 -0
- package/dist/components/forms/Form.svelte.d.ts +2 -0
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/Input.svelte +59 -13
- package/dist/components/forms/Input.svelte.d.ts.map +1 -1
- package/dist/components/forms/Listbox.svelte +33 -6
- package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
- package/dist/components/forms/MultiSelect.svelte +41 -19
- package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
- package/dist/components/forms/RadioGroup.svelte +14 -3
- package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
- package/dist/components/forms/RangeSlider.svelte +61 -6
- package/dist/components/forms/RangeSlider.svelte.d.ts.map +1 -1
- package/dist/components/forms/SegmentedControl.svelte +33 -6
- package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
- package/dist/components/forms/Select.svelte +32 -9
- package/dist/components/forms/Select.svelte.d.ts.map +1 -1
- package/dist/components/forms/Slider.svelte +26 -6
- package/dist/components/forms/Slider.svelte.d.ts.map +1 -1
- package/dist/components/forms/StagedControlReview.svelte +637 -0
- package/dist/components/forms/StagedControlReview.svelte.d.ts +13 -0
- package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -0
- package/dist/components/forms/Switch.svelte +16 -7
- package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
- package/dist/components/forms/TagsInput.svelte +36 -8
- package/dist/components/forms/TagsInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/Textarea.svelte +20 -8
- package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
- package/dist/components/forms/ToggleButton.svelte +20 -3
- package/dist/components/forms/ToggleButton.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/Form.test.js +106 -0
- package/dist/components/forms/__tests__/StagedControlReview.test.js +668 -0
- package/dist/components/forms/__tests__/composite-controls.fixture.svelte +22 -4
- package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts +6 -2
- package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/composite-controls.test.js +96 -1
- package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte +41 -0
- package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/control-interaction.test.js +3323 -15
- package/dist/components/forms/__tests__/core-controls.fixture.svelte +16 -5
- package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts +1 -0
- package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/core-controls.test.js +223 -2
- package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte +25 -0
- package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/select-interaction.fixture.svelte +25 -0
- package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte +21 -0
- package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte +23 -0
- package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/staged-review.fixture.svelte +56 -0
- package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts +12 -0
- package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/control-interaction-context.d.ts +9 -1
- package/dist/components/forms/control-interaction-context.d.ts.map +1 -1
- package/dist/components/forms/control-interaction-context.js +16 -0
- package/dist/components/forms/control-interaction.d.ts +97 -7
- package/dist/components/forms/control-interaction.d.ts.map +1 -1
- package/dist/components/forms/control-interaction.js +1019 -92
- package/dist/components/forms/control-value-validation.d.ts +19 -0
- package/dist/components/forms/control-value-validation.d.ts.map +1 -0
- package/dist/components/forms/control-value-validation.js +152 -0
- package/dist/components/forms/index.d.ts +3 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +2 -1
- package/dist/components/forms/staged-control-review.d.ts +21 -0
- package/dist/components/forms/staged-control-review.d.ts.map +1 -0
- package/dist/components/forms/staged-control-review.js +1 -0
- package/dist/components/forms/use-control-registration.svelte.d.ts.map +1 -1
- package/dist/components/forms/use-control-registration.svelte.js +23 -4
- package/dist/i18n/strings.ui.d.ts +18 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -1
- package/dist/i18n/strings.ui.js +19 -0
- package/dist/svelte/playground/InteractiveControlsPreview.svelte +3 -4
- package/dist/svelte/playground/InteractiveControlsPreview.svelte.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Combobox from '../Combobox.svelte';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ControlInteractionRegistry,
|
|
5
|
+
ControlOption,
|
|
6
|
+
} from '../control-interaction.js';
|
|
4
7
|
import Form from '../Form.svelte';
|
|
5
8
|
import Listbox from '../Listbox.svelte';
|
|
6
9
|
import MultiSelect from '../MultiSelect.svelte';
|
|
@@ -8,8 +11,18 @@ import TagsInput from '../TagsInput.svelte';
|
|
|
8
11
|
|
|
9
12
|
interface Props {
|
|
10
13
|
registry: ControlInteractionRegistry;
|
|
14
|
+
fieldsetDisabled?: boolean;
|
|
15
|
+
collisionOptions?: ControlOption[];
|
|
16
|
+
collisionValues?: Array<string | number>;
|
|
17
|
+
onCollisionValuesChange?: (values: Array<string | number>) => void;
|
|
11
18
|
}
|
|
12
|
-
let {
|
|
19
|
+
let {
|
|
20
|
+
registry,
|
|
21
|
+
fieldsetDisabled = false,
|
|
22
|
+
collisionOptions,
|
|
23
|
+
collisionValues = $bindable([]),
|
|
24
|
+
onCollisionValuesChange,
|
|
25
|
+
}: Props = $props();
|
|
13
26
|
const countries = [
|
|
14
27
|
{ value: 'ca', label: 'Canada' },
|
|
15
28
|
{ value: 'us', label: 'United States' },
|
|
@@ -22,8 +35,13 @@ let region = $state<string | number>('west');
|
|
|
22
35
|
</script>
|
|
23
36
|
|
|
24
37
|
<Form formId="profile" interactionRegistry={registry} aria-label="Profile">
|
|
25
|
-
<
|
|
38
|
+
<fieldset disabled={fieldsetDisabled}>
|
|
39
|
+
<Combobox name="country" label="Country" options={countries} required bind:value={country} />
|
|
26
40
|
<MultiSelect name="channels" label="Channels" options={[{ value: 'email', label: 'Email' }, { value: 'sms', label: 'SMS' }]} bind:values={channels} />
|
|
27
|
-
<TagsInput name="topics" label="Topics" bind:values={tags} />
|
|
41
|
+
<TagsInput name="topics" label="Topics" maxTags={2} bind:values={tags} />
|
|
28
42
|
<Listbox name="region" label="Region" options={[{ value: 'west', label: 'West' }, { value: 'east', label: 'East' }]} bind:value={region} />
|
|
43
|
+
{#if collisionOptions}
|
|
44
|
+
<MultiSelect name="collision" label="Collision" options={collisionOptions} bind:values={collisionValues} onvalueschange={onCollisionValuesChange} />
|
|
45
|
+
{/if}
|
|
46
|
+
</fieldset>
|
|
29
47
|
</Form>
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type { ControlInteractionRegistry } from '../control-interaction.js';
|
|
1
|
+
import type { ControlInteractionRegistry, ControlOption } from '../control-interaction.js';
|
|
2
2
|
interface Props {
|
|
3
3
|
registry: ControlInteractionRegistry;
|
|
4
|
+
fieldsetDisabled?: boolean;
|
|
5
|
+
collisionOptions?: ControlOption[];
|
|
6
|
+
collisionValues?: Array<string | number>;
|
|
7
|
+
onCollisionValuesChange?: (values: Array<string | number>) => void;
|
|
4
8
|
}
|
|
5
|
-
declare const CompositeControls: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
declare const CompositeControls: import("svelte").Component<Props, {}, "collisionValues">;
|
|
6
10
|
type CompositeControls = ReturnType<typeof CompositeControls>;
|
|
7
11
|
export default CompositeControls;
|
|
8
12
|
//# sourceMappingURL=composite-controls.fixture.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composite-controls.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/composite-controls.fixture.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"composite-controls.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/composite-controls.fixture.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,0BAA0B,EAC1B,aAAa,EACd,MAAM,2BAA2B,CAAC;AAOnC,UAAU,KAAK;IACb,QAAQ,EAAE,0BAA0B,CAAC;IACrC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;IACnC,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACzC,uBAAuB,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;CACpE;AAyCD,QAAA,MAAM,iBAAiB,0DAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/svelte';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
5
5
|
import { createControlInteractionRegistry } from '../control-interaction.js';
|
|
6
6
|
import Fixture from './composite-controls.fixture.svelte';
|
|
@@ -25,6 +25,101 @@ describe('composite controls', () => {
|
|
|
25
25
|
render(Fixture, { props: { registry } });
|
|
26
26
|
expect(registry.list('profile').map((item) => item.identity.controlId)).toEqual(['country', 'channels', 'topics', 'region']);
|
|
27
27
|
});
|
|
28
|
+
it('rejects invalid choice and normalized-shape proposals at staging time', async () => {
|
|
29
|
+
const registry = createControlInteractionRegistry();
|
|
30
|
+
render(Fixture, { props: { registry } });
|
|
31
|
+
const invalid = new Map([
|
|
32
|
+
['country', 'unknown'],
|
|
33
|
+
['channels', ['fax']],
|
|
34
|
+
['topics', ['duplicate', 'duplicate']],
|
|
35
|
+
['region', 'north'],
|
|
36
|
+
]);
|
|
37
|
+
for (const [controlId, value] of invalid) {
|
|
38
|
+
await registry.execute({
|
|
39
|
+
action: 'stage',
|
|
40
|
+
identity: { formId: 'profile', controlId },
|
|
41
|
+
value,
|
|
42
|
+
}, { source: 'agent' });
|
|
43
|
+
expect(registry.get({ formId: 'profile', controlId })?.state.staged?.valid).toBe(false);
|
|
44
|
+
}
|
|
45
|
+
expect(await screen.findAllByRole('alert')).toHaveLength(invalid.size);
|
|
46
|
+
});
|
|
47
|
+
it('applies only the deterministic MultiSelect match for colliding values', async () => {
|
|
48
|
+
const registry = createControlInteractionRegistry({
|
|
49
|
+
isLocalGesture: () => true,
|
|
50
|
+
});
|
|
51
|
+
const onValuesChange = vi.fn();
|
|
52
|
+
render(Fixture, {
|
|
53
|
+
props: {
|
|
54
|
+
registry,
|
|
55
|
+
collisionOptions: [
|
|
56
|
+
{ value: 1, label: 'Number one' },
|
|
57
|
+
{ value: '1', label: 'String one' },
|
|
58
|
+
{ value: 2, label: 'Disabled two', disabled: true },
|
|
59
|
+
],
|
|
60
|
+
onCollisionValuesChange: onValuesChange,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
await registry.execute({
|
|
64
|
+
action: 'stage',
|
|
65
|
+
identity: { formId: 'profile', controlId: 'collision' },
|
|
66
|
+
value: [1],
|
|
67
|
+
}, { source: 'agent' });
|
|
68
|
+
expect(registry.get({ formId: 'profile', controlId: 'collision' })?.state.staged
|
|
69
|
+
?.valid).toBe(true);
|
|
70
|
+
await userEvent.click(screen.getByRole('button', { name: 'Apply valid changes' }));
|
|
71
|
+
expect(onValuesChange).toHaveBeenLastCalledWith([1]);
|
|
72
|
+
await userEvent.click(screen.getByRole('button', { name: /Collision.*Number one/ }));
|
|
73
|
+
expect(screen.getByRole('option', { name: 'Number one' })).toHaveAttribute('aria-selected', 'true');
|
|
74
|
+
expect(screen.getByRole('option', { name: 'String one' })).toHaveAttribute('aria-selected', 'false');
|
|
75
|
+
for (const value of [[1, 1], [2], ['unknown']]) {
|
|
76
|
+
await registry.execute({
|
|
77
|
+
action: 'stage',
|
|
78
|
+
identity: { formId: 'profile', controlId: 'collision' },
|
|
79
|
+
value,
|
|
80
|
+
}, { source: 'agent' });
|
|
81
|
+
expect(registry.get({ formId: 'profile', controlId: 'collision' })?.state
|
|
82
|
+
.staged?.valid).toBe(false);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
it('stages the typed option value selected by a transport string', async () => {
|
|
86
|
+
const registry = createControlInteractionRegistry();
|
|
87
|
+
render(Fixture, {
|
|
88
|
+
props: {
|
|
89
|
+
registry,
|
|
90
|
+
collisionOptions: [{ value: 1, label: 'Number one' }],
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
await registry.execute({
|
|
94
|
+
action: 'stage',
|
|
95
|
+
identity: { formId: 'profile', controlId: 'collision' },
|
|
96
|
+
value: ['1'],
|
|
97
|
+
}, { source: 'agent' });
|
|
98
|
+
expect(registry.get({ formId: 'profile', controlId: 'collision' })?.state.staged
|
|
99
|
+
?.value).toEqual([1]);
|
|
100
|
+
});
|
|
101
|
+
it('normalizes legacy string-bound selections for numeric options', async () => {
|
|
102
|
+
const registry = createControlInteractionRegistry();
|
|
103
|
+
const onValuesChange = vi.fn();
|
|
104
|
+
render(Fixture, {
|
|
105
|
+
props: {
|
|
106
|
+
registry,
|
|
107
|
+
collisionOptions: [{ value: 1, label: 'Number one' }],
|
|
108
|
+
collisionValues: ['1'],
|
|
109
|
+
onCollisionValuesChange: onValuesChange,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
await userEvent.click(screen.getByRole('button', { name: /Collision.*Number one/ }));
|
|
113
|
+
const option = screen.getByRole('option', { name: 'Number one' });
|
|
114
|
+
expect(option).toHaveAttribute('aria-selected', 'true');
|
|
115
|
+
await userEvent.click(option);
|
|
116
|
+
expect(onValuesChange).toHaveBeenLastCalledWith([]);
|
|
117
|
+
});
|
|
118
|
+
it('reports effective disabled state from an ancestor fieldset', () => {
|
|
119
|
+
const registry = createControlInteractionRegistry();
|
|
120
|
+
render(Fixture, { props: { registry, fieldsetDisabled: true } });
|
|
121
|
+
expect(registry.list('profile').map((item) => item.state.disabled)).toEqual([true, true, true, true]);
|
|
122
|
+
});
|
|
28
123
|
it('is axe-clean', async () => {
|
|
29
124
|
const registry = createControlInteractionRegistry();
|
|
30
125
|
const { container } = render(Fixture, { props: { registry } });
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ControlInteractionRegistry } from '../control-interaction.js';
|
|
3
|
+
import Form from '../Form.svelte';
|
|
4
|
+
import SegmentedControl from '../SegmentedControl.svelte';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
registry,
|
|
8
|
+
onchange,
|
|
9
|
+
modeState,
|
|
10
|
+
}: {
|
|
11
|
+
registry: ControlInteractionRegistry;
|
|
12
|
+
onchange?: (event: Event) => void;
|
|
13
|
+
modeState?: {
|
|
14
|
+
get?: () => string;
|
|
15
|
+
set?: (next: string) => void;
|
|
16
|
+
};
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
let mode = $state<'draft' | 'review' | 'published'>('draft');
|
|
20
|
+
|
|
21
|
+
$effect(() => {
|
|
22
|
+
if (!modeState) return;
|
|
23
|
+
modeState.get = () => mode;
|
|
24
|
+
modeState.set = (next) => {
|
|
25
|
+
mode = next as typeof mode;
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<Form formId="profile" interactionRegistry={registry} {onchange}>
|
|
31
|
+
<SegmentedControl
|
|
32
|
+
name="mode"
|
|
33
|
+
label="Mode"
|
|
34
|
+
bind:value={mode}
|
|
35
|
+
options={[
|
|
36
|
+
{ value: 'draft', label: 'Draft' },
|
|
37
|
+
{ value: 'review', label: 'Review' },
|
|
38
|
+
{ value: 'published', label: 'Published' },
|
|
39
|
+
]}
|
|
40
|
+
/>
|
|
41
|
+
</Form>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ControlInteractionRegistry } from '../control-interaction.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
registry: ControlInteractionRegistry;
|
|
4
|
+
onchange?: (event: Event) => void;
|
|
5
|
+
modeState?: {
|
|
6
|
+
get?: () => string;
|
|
7
|
+
set?: (next: string) => void;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
declare const CompositeUserEdit: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
|
+
type CompositeUserEdit = ReturnType<typeof CompositeUserEdit>;
|
|
12
|
+
export default CompositeUserEdit;
|
|
13
|
+
//# sourceMappingURL=composite-user-edit.fixture.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composite-user-edit.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/composite-user-edit.fixture.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAI3E,KAAK,gBAAgB,GAAI;IACxB,QAAQ,EAAE,0BAA0B,CAAC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE;QACV,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;KAC9B,CAAC;CACH,CAAC;AAiCF,QAAA,MAAM,iBAAiB,sDAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|