@happyvertical/smrt-ui 0.43.4 → 0.43.6

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 (108) hide show
  1. package/AGENTS.md +33 -0
  2. package/README.md +78 -4
  3. package/dist/components/display/CurrencyDisplay.contract.d.ts +7 -0
  4. package/dist/components/display/CurrencyDisplay.contract.d.ts.map +1 -0
  5. package/dist/components/display/CurrencyDisplay.contract.js +1 -0
  6. package/dist/components/display/CurrencyDisplay.svelte +148 -17
  7. package/dist/components/display/CurrencyDisplay.svelte.d.ts +8 -5
  8. package/dist/components/display/CurrencyDisplay.svelte.d.ts.map +1 -1
  9. package/dist/components/display/__tests__/CurrencyDisplay.test.js +193 -3
  10. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte +20 -0
  11. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts +19 -0
  12. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts.map +1 -0
  13. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte +7 -0
  14. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts +19 -0
  15. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts.map +1 -0
  16. package/dist/components/display/currency-metadata.d.ts +2 -0
  17. package/dist/components/display/currency-metadata.d.ts.map +1 -0
  18. package/dist/components/display/currency-metadata.js +14 -0
  19. package/dist/components/forms/Checkbox.svelte +15 -6
  20. package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
  21. package/dist/components/forms/Combobox.svelte +45 -10
  22. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  23. package/dist/components/forms/FilePicker.svelte +5 -2
  24. package/dist/components/forms/Form.svelte +57 -0
  25. package/dist/components/forms/Form.svelte.d.ts +2 -0
  26. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  27. package/dist/components/forms/Input.svelte +59 -13
  28. package/dist/components/forms/Input.svelte.d.ts.map +1 -1
  29. package/dist/components/forms/Listbox.svelte +33 -6
  30. package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
  31. package/dist/components/forms/MultiSelect.svelte +41 -19
  32. package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
  33. package/dist/components/forms/RadioGroup.svelte +14 -3
  34. package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
  35. package/dist/components/forms/RangeSlider.svelte +61 -6
  36. package/dist/components/forms/RangeSlider.svelte.d.ts.map +1 -1
  37. package/dist/components/forms/SegmentedControl.svelte +33 -6
  38. package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
  39. package/dist/components/forms/Select.svelte +32 -9
  40. package/dist/components/forms/Select.svelte.d.ts.map +1 -1
  41. package/dist/components/forms/Slider.svelte +26 -6
  42. package/dist/components/forms/Slider.svelte.d.ts.map +1 -1
  43. package/dist/components/forms/StagedControlReview.svelte +637 -0
  44. package/dist/components/forms/StagedControlReview.svelte.d.ts +13 -0
  45. package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -0
  46. package/dist/components/forms/Switch.svelte +16 -7
  47. package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
  48. package/dist/components/forms/TagsInput.svelte +36 -8
  49. package/dist/components/forms/TagsInput.svelte.d.ts.map +1 -1
  50. package/dist/components/forms/Textarea.svelte +20 -8
  51. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
  52. package/dist/components/forms/ToggleButton.svelte +20 -3
  53. package/dist/components/forms/ToggleButton.svelte.d.ts.map +1 -1
  54. package/dist/components/forms/__tests__/Form.test.js +106 -0
  55. package/dist/components/forms/__tests__/StagedControlReview.test.js +668 -0
  56. package/dist/components/forms/__tests__/composite-controls.fixture.svelte +22 -4
  57. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts +6 -2
  58. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts.map +1 -1
  59. package/dist/components/forms/__tests__/composite-controls.test.js +96 -1
  60. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte +41 -0
  61. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts +13 -0
  62. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts.map +1 -0
  63. package/dist/components/forms/__tests__/control-interaction.test.js +3323 -15
  64. package/dist/components/forms/__tests__/core-controls.fixture.svelte +16 -5
  65. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts +1 -0
  66. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts.map +1 -1
  67. package/dist/components/forms/__tests__/core-controls.test.js +223 -2
  68. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte +25 -0
  69. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts +8 -0
  70. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts.map +1 -0
  71. package/dist/components/forms/__tests__/select-interaction.fixture.svelte +25 -0
  72. package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts +9 -0
  73. package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts.map +1 -0
  74. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte +21 -0
  75. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts +8 -0
  76. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts.map +1 -0
  77. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte +23 -0
  78. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts +8 -0
  79. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts.map +1 -0
  80. package/dist/components/forms/__tests__/staged-review.fixture.svelte +56 -0
  81. package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts +12 -0
  82. package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts.map +1 -0
  83. package/dist/components/forms/control-interaction-context.d.ts +9 -1
  84. package/dist/components/forms/control-interaction-context.d.ts.map +1 -1
  85. package/dist/components/forms/control-interaction-context.js +16 -0
  86. package/dist/components/forms/control-interaction.d.ts +97 -7
  87. package/dist/components/forms/control-interaction.d.ts.map +1 -1
  88. package/dist/components/forms/control-interaction.js +1019 -92
  89. package/dist/components/forms/control-value-validation.d.ts +19 -0
  90. package/dist/components/forms/control-value-validation.d.ts.map +1 -0
  91. package/dist/components/forms/control-value-validation.js +152 -0
  92. package/dist/components/forms/index.d.ts +3 -1
  93. package/dist/components/forms/index.d.ts.map +1 -1
  94. package/dist/components/forms/index.js +2 -1
  95. package/dist/components/forms/staged-control-review.d.ts +21 -0
  96. package/dist/components/forms/staged-control-review.d.ts.map +1 -0
  97. package/dist/components/forms/staged-control-review.js +1 -0
  98. package/dist/components/forms/use-control-registration.svelte.d.ts.map +1 -1
  99. package/dist/components/forms/use-control-registration.svelte.js +23 -4
  100. package/dist/i18n/strings.d.ts +3 -0
  101. package/dist/i18n/strings.d.ts.map +1 -1
  102. package/dist/i18n/strings.js +3 -0
  103. package/dist/i18n/strings.ui.d.ts +18 -0
  104. package/dist/i18n/strings.ui.d.ts.map +1 -1
  105. package/dist/i18n/strings.ui.js +19 -0
  106. package/dist/svelte/playground/InteractiveControlsPreview.svelte +3 -4
  107. package/dist/svelte/playground/InteractiveControlsPreview.svelte.d.ts.map +1 -1
  108. package/package.json +2 -2
@@ -1,6 +1,9 @@
1
1
  <script lang="ts">
2
2
  import Combobox from '../Combobox.svelte';
3
- import type { ControlInteractionRegistry } from '../control-interaction.js';
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 { registry }: Props = $props();
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
- <Combobox name="country" label="Country" options={countries} bind:value={country} />
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,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAO5E,UAAU,KAAK;IACb,QAAQ,EAAE,0BAA0B,CAAC;CACtC;AA8BD,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
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"}