@human-kit/svelte-components 1.0.0-alpha.4 → 1.0.0-alpha.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.
Files changed (82) hide show
  1. package/dist/checkbox/README.md +53 -0
  2. package/dist/checkbox/TODO.md +16 -0
  3. package/dist/checkbox/index.d.ts +6 -0
  4. package/dist/checkbox/index.js +6 -0
  5. package/dist/checkbox/index.parts.d.ts +2 -0
  6. package/dist/checkbox/index.parts.js +2 -0
  7. package/dist/checkbox/indicator/README.md +23 -0
  8. package/dist/checkbox/indicator/checkbox-indicator.svelte +43 -0
  9. package/dist/checkbox/indicator/checkbox-indicator.svelte.d.ts +10 -0
  10. package/dist/checkbox/root/README.md +47 -0
  11. package/dist/checkbox/root/checkbox-label-test.svelte +10 -0
  12. package/dist/checkbox/root/checkbox-label-test.svelte.d.ts +18 -0
  13. package/dist/checkbox/root/checkbox-root.svelte +361 -0
  14. package/dist/checkbox/root/checkbox-root.svelte.d.ts +23 -0
  15. package/dist/checkbox/root/checkbox-test.svelte +59 -0
  16. package/dist/checkbox/root/checkbox-test.svelte.d.ts +18 -0
  17. package/dist/checkbox/root/context.d.ts +21 -0
  18. package/dist/checkbox/root/context.js +15 -0
  19. package/dist/combobox/list/combobox-listbox.svelte.d.ts +1 -1
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.js +4 -0
  22. package/dist/table/IMPLEMENTATION_NOTES.md +8 -0
  23. package/dist/table/PLAN-HIDDEN-COLUMNS.md +152 -0
  24. package/dist/table/PLAN.md +924 -0
  25. package/dist/table/README.md +116 -0
  26. package/dist/table/SELECTION_CHECKBOX_PLAN.md +234 -0
  27. package/dist/table/TODO.md +100 -0
  28. package/dist/table/body/README.md +24 -0
  29. package/dist/table/body/table-body.svelte +25 -0
  30. package/dist/table/body/table-body.svelte.d.ts +9 -0
  31. package/dist/table/cell/README.md +25 -0
  32. package/dist/table/cell/table-cell.svelte +247 -0
  33. package/dist/table/cell/table-cell.svelte.d.ts +9 -0
  34. package/dist/table/checkbox/README.md +38 -0
  35. package/dist/table/checkbox/table-checkbox-test.svelte +121 -0
  36. package/dist/table/checkbox/table-checkbox-test.svelte.d.ts +16 -0
  37. package/dist/table/checkbox/table-checkbox.svelte +274 -0
  38. package/dist/table/checkbox/table-checkbox.svelte.d.ts +13 -0
  39. package/dist/table/checkbox-indicator/README.md +29 -0
  40. package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte +22 -0
  41. package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte.d.ts +10 -0
  42. package/dist/table/column/README.md +32 -0
  43. package/dist/table/column/table-column.svelte +108 -0
  44. package/dist/table/column/table-column.svelte.d.ts +18 -0
  45. package/dist/table/column-header-cell/README.md +28 -0
  46. package/dist/table/column-header-cell/table-column-header-cell.svelte +281 -0
  47. package/dist/table/column-header-cell/table-column-header-cell.svelte.d.ts +9 -0
  48. package/dist/table/column-resizer/README.md +32 -0
  49. package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte +51 -0
  50. package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte.d.ts +3 -0
  51. package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte +83 -0
  52. package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte.d.ts +3 -0
  53. package/dist/table/column-resizer/table-column-resizer-test.svelte +75 -0
  54. package/dist/table/column-resizer/table-column-resizer-test.svelte.d.ts +3 -0
  55. package/dist/table/column-resizer/table-column-resizer.svelte +616 -0
  56. package/dist/table/column-resizer/table-column-resizer.svelte.d.ts +11 -0
  57. package/dist/table/empty-state/README.md +25 -0
  58. package/dist/table/empty-state/table-empty-state.svelte +38 -0
  59. package/dist/table/empty-state/table-empty-state.svelte.d.ts +8 -0
  60. package/dist/table/footer/README.md +24 -0
  61. package/dist/table/footer/table-footer.svelte +19 -0
  62. package/dist/table/footer/table-footer.svelte.d.ts +9 -0
  63. package/dist/table/header/README.md +24 -0
  64. package/dist/table/header/table-header.svelte +19 -0
  65. package/dist/table/header/table-header.svelte.d.ts +9 -0
  66. package/dist/table/index.d.ts +16 -0
  67. package/dist/table/index.js +16 -0
  68. package/dist/table/index.parts.d.ts +12 -0
  69. package/dist/table/index.parts.js +12 -0
  70. package/dist/table/root/README.md +56 -0
  71. package/dist/table/root/context.d.ts +198 -0
  72. package/dist/table/root/context.js +1426 -0
  73. package/dist/table/root/table-reorder-test.svelte +64 -0
  74. package/dist/table/root/table-reorder-test.svelte.d.ts +3 -0
  75. package/dist/table/root/table-root.svelte +410 -0
  76. package/dist/table/root/table-root.svelte.d.ts +29 -0
  77. package/dist/table/root/table-test.svelte +165 -0
  78. package/dist/table/root/table-test.svelte.d.ts +25 -0
  79. package/dist/table/row/README.md +27 -0
  80. package/dist/table/row/table-row.svelte +321 -0
  81. package/dist/table/row/table-row.svelte.d.ts +13 -0
  82. package/package.json +11 -1
@@ -0,0 +1,53 @@
1
+ # Checkbox
2
+
3
+ ## Description
4
+
5
+ `Checkbox` is a composable tri-state checkbox with separate checked and indeterminate bindings, hidden input form support, and headless indicator rendering.
6
+
7
+ ## Anatomy
8
+
9
+ - `Checkbox.Root`
10
+ - `Checkbox.Indicator`
11
+
12
+ ```svelte
13
+ <Checkbox.Root aria-label="Accept terms">
14
+ <Checkbox.Indicator>
15
+ <CheckIcon />
16
+ </Checkbox.Indicator>
17
+ </Checkbox.Root>
18
+ ```
19
+
20
+ ## Usage guidelines
21
+
22
+ - Use `isChecked` / `defaultChecked` for the checked state and `isIndeterminate` / `defaultIndeterminate` for the mixed state.
23
+ - `isIndeterminate` takes precedence over `isChecked`. When both are `true`, the checkbox is exposed as indeterminate.
24
+ - Use `value` only for form submission through the hidden native input; it does not represent the visual state.
25
+ - Wrap the checkbox in a native `<label>` for the simplest accessible labeling pattern.
26
+
27
+ ## API reference
28
+
29
+ - `Checkbox.Root`
30
+ - `isChecked?: boolean`
31
+ - `defaultChecked?: boolean`
32
+ - `isIndeterminate?: boolean`
33
+ - `defaultIndeterminate?: boolean`
34
+ - `onCheckedChange?: (checked: boolean) => void`
35
+ - `onIndeterminateChange?: (indeterminate: boolean) => void`
36
+ - `isDisabled?: boolean`
37
+ - `isReadOnly?: boolean`
38
+ - `name?: string`
39
+ - `value?: string`
40
+ - `required?: boolean`
41
+ - `Checkbox.Indicator`
42
+ - `keepMounted?: boolean`
43
+
44
+ ## Accessibility
45
+
46
+ - `Checkbox.Root` exposes `role="checkbox"` with `aria-checked="true" | "false" | "mixed"`.
47
+ - Press `Space` to toggle the checkbox.
48
+ - `isReadOnly` keeps the checkbox focusable while preventing state changes.
49
+
50
+ ## Notes
51
+
52
+ - The first user toggle from the indeterminate state resolves to checked.
53
+ - A hidden checkbox input is kept in sync for form integration and `label[for]` support.
@@ -0,0 +1,16 @@
1
+ # Checkbox TODO
2
+
3
+ ## Goal
4
+
5
+ Track Checkbox work with a single mandatory TODO format.
6
+
7
+ ## Backlog
8
+
9
+ - [x] [S][P0][Area: Architecture][Owner: Unassigned][Target: Done] Create base `root` and `indicator` parts with namespace exports.
10
+ - [x] [S][P0][Area: State][Owner: Unassigned][Target: Done] Implement checked, unchecked, and indeterminate state transitions.
11
+ - [x] [S][P0][Area: Accessibility][Owner: Unassigned][Target: Done] Add checkbox semantics, keyboard toggle, and readonly behavior.
12
+ - [x] [S][P0][Area: Forms][Owner: Unassigned][Target: Done] Sync a hidden native input for form submission and label targeting.
13
+ - [x] [S][P0][Area: Testing][Owner: Unassigned][Target: Done] Add baseline tests for root and indicator behavior.
14
+ - [ ] [S][P1][Area: API][Owner: Unassigned][Target: TBD] Add optional `nativeButton` rendering mode for sibling-label patterns.
15
+ - [ ] [S][P1][Area: Forms][Owner: Unassigned][Target: TBD] Support unchecked submission values and form reset synchronization.
16
+ - [ ] [C][P2][Area: Animation][Owner: Unassigned][Target: TBD] Add indicator presence data for enter and exit animations.
@@ -0,0 +1,6 @@
1
+ export * as Checkbox from './index.parts.ts';
2
+ export { default as CheckboxRoot } from './root/checkbox-root.svelte';
3
+ export { default as CheckboxIndicator } from './indicator/checkbox-indicator.svelte';
4
+ export { getCheckboxContext, setCheckboxContext, useCheckboxContext, type CheckboxContext, type CheckboxState } from './root/context.ts';
5
+ import * as CheckboxParts from './index.parts.ts';
6
+ export default CheckboxParts;
@@ -0,0 +1,6 @@
1
+ export * as Checkbox from './index.parts.ts';
2
+ export { default as CheckboxRoot } from './root/checkbox-root.svelte';
3
+ export { default as CheckboxIndicator } from './indicator/checkbox-indicator.svelte';
4
+ export { getCheckboxContext, setCheckboxContext, useCheckboxContext } from './root/context.ts';
5
+ import * as CheckboxParts from './index.parts.ts';
6
+ export default CheckboxParts;
@@ -0,0 +1,2 @@
1
+ export { default as Root } from './root/checkbox-root.svelte';
2
+ export { default as Indicator } from './indicator/checkbox-indicator.svelte';
@@ -0,0 +1,2 @@
1
+ export { default as Root } from './root/checkbox-root.svelte';
2
+ export { default as Indicator } from './indicator/checkbox-indicator.svelte';
@@ -0,0 +1,23 @@
1
+ # Checkbox Indicator
2
+
3
+ ## API reference
4
+
5
+ ### Checkbox.Indicator
6
+
7
+ Name: `Checkbox.Indicator`
8
+ Description: Headless presence wrapper for checkbox indicator content. It renders when the checkbox is checked or indeterminate.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | -------------- | --------------------------------- | ----------- | ------------------------------------------------------------------------ |
12
+ | `keepMounted` | `boolean` | `false` | Keeps the indicator mounted while hidden when the checkbox is unchecked. |
13
+ | `children` | `Snippet` | `undefined` | Rendered indicator content, such as a check or dash icon. |
14
+ | `class` | `string` | `''` | CSS class names for the indicator wrapper. |
15
+ | `...restProps` | `HTMLAttributes<HTMLSpanElement>` | `-` | Additional attributes forwarded to the indicator span. |
16
+
17
+ ```svelte
18
+ <Checkbox.Root aria-label="Select row">
19
+ <Checkbox.Indicator>
20
+ <CheckIcon />
21
+ </Checkbox.Indicator>
22
+ </Checkbox.Root>
23
+ ```
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { Snippet } from 'svelte';
4
+ import { cn } from '../../utils/cn';
5
+ import { useCheckboxContext } from '../root/context';
6
+
7
+ type CheckboxIndicatorProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class'> & {
8
+ keepMounted?: boolean;
9
+ children?: Snippet;
10
+ class?: string;
11
+ };
12
+
13
+ let {
14
+ keepMounted = false,
15
+ children,
16
+ class: className = '',
17
+ ...restProps
18
+ }: CheckboxIndicatorProps = $props();
19
+
20
+ const checkbox = useCheckboxContext();
21
+ const visible = $derived(checkbox.state !== 'unchecked');
22
+ </script>
23
+
24
+ {#if keepMounted || visible}
25
+ <span
26
+ {...restProps}
27
+ data-checkbox-indicator="true"
28
+ data-checked={checkbox.isChecked || undefined}
29
+ data-unchecked={checkbox.state === 'unchecked' || undefined}
30
+ data-indeterminate={checkbox.isIndeterminate || undefined}
31
+ data-pressed={checkbox.pressed || undefined}
32
+ data-disabled={checkbox.isDisabled || undefined}
33
+ data-readonly={checkbox.isReadOnly || undefined}
34
+ data-required={checkbox.required || undefined}
35
+ data-focused={checkbox.focused || undefined}
36
+ data-focus-visible={checkbox.focusVisible || undefined}
37
+ hidden={keepMounted && !visible}
38
+ aria-hidden={keepMounted && !visible ? 'true' : undefined}
39
+ class={cn('contents', className)}
40
+ >
41
+ {@render children?.()}
42
+ </span>
43
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ type CheckboxIndicatorProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class'> & {
4
+ keepMounted?: boolean;
5
+ children?: Snippet;
6
+ class?: string;
7
+ };
8
+ declare const CheckboxIndicator: import("svelte").Component<CheckboxIndicatorProps, {}, "">;
9
+ type CheckboxIndicator = ReturnType<typeof CheckboxIndicator>;
10
+ export default CheckboxIndicator;
@@ -0,0 +1,47 @@
1
+ # Checkbox Root
2
+
3
+ ## API reference
4
+
5
+ ### Checkbox.Root
6
+
7
+ Name: `Checkbox.Root`
8
+ Description: Interactive tri-state checkbox root that owns checked, indeterminate, focus, and hidden input synchronization.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | ----------------------- | ---------------------------------- | ------------- | ---------------------------------------------------------------- |
12
+ | `id` | `string` | `$props.id()` | Stable id used for the hidden input and derived root id. |
13
+ | `name` | `string` | `undefined` | Form field name forwarded to the hidden native input. |
14
+ | `value` | `string` | `'on'` | Submitted value when the checkbox is checked. |
15
+ | `isChecked` | `boolean` | `undefined` | Controlled checked state. Supports `bind:isChecked`. |
16
+ | `defaultChecked` | `boolean` | `false` | Initial checked state in uncontrolled mode. |
17
+ | `isIndeterminate` | `boolean` | `undefined` | Controlled indeterminate state. Supports `bind:isIndeterminate`. |
18
+ | `defaultIndeterminate` | `boolean` | `false` | Initial indeterminate state in uncontrolled mode. |
19
+ | `onCheckedChange` | `(checked: boolean) => void` | `undefined` | Called when the effective checked state changes. |
20
+ | `onIndeterminateChange` | `(indeterminate: boolean) => void` | `undefined` | Called when the effective indeterminate state changes. |
21
+ | `isDisabled` | `boolean` | `false` | Prevents focus and state changes. |
22
+ | `isReadOnly` | `boolean` | `false` | Allows focus but blocks user-driven state changes. |
23
+ | `required` | `boolean` | `false` | Marks the hidden input as required and exposes `data-required`. |
24
+ | `children` | `Snippet` | `undefined` | Composed checkbox parts such as `Checkbox.Indicator`. |
25
+ | `class` | `string` | `''` | CSS class names for the root element. |
26
+ | `...restProps` | `HTMLAttributes<HTMLSpanElement>` | `-` | Additional attributes forwarded to the checkbox root span. |
27
+
28
+ ### Context utilities
29
+
30
+ Name: `context.ts` helpers
31
+ Description: Internal APIs for publishing and consuming checkbox state.
32
+
33
+ | Prop | Type | Default | Description |
34
+ | -------------------- | ------------------------------------ | ------- | -------------------------------------------------- |
35
+ | `setCheckboxContext` | `(ctx: CheckboxContext) => void` | `-` | Registers the checkbox context in root. |
36
+ | `getCheckboxContext` | `() => CheckboxContext \| undefined` | `-` | Returns the context when available. |
37
+ | `useCheckboxContext` | `() => CheckboxContext` | `-` | Returns the context and throws outside root usage. |
38
+
39
+ `CheckboxState` is the internal state union used by the root context: `'checked' | 'unchecked' | 'indeterminate'`.
40
+
41
+ ```svelte
42
+ <Checkbox.Root bind:isChecked bind:isIndeterminate aria-label="Notifications">
43
+ <Checkbox.Indicator>
44
+ <CheckIcon />
45
+ </Checkbox.Indicator>
46
+ </Checkbox.Root>
47
+ ```
@@ -0,0 +1,10 @@
1
+ <script lang="ts">
2
+ import { Checkbox } from '../index.ts';
3
+ </script>
4
+
5
+ <label for="notifications">Enable notifications</label>
6
+ <Checkbox.Root id="notifications" aria-label="Notifications">
7
+ <Checkbox.Indicator>
8
+ <span data-checkbox-icon="true">icon</span>
9
+ </Checkbox.Indicator>
10
+ </Checkbox.Root>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const CheckboxLabelTest: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type CheckboxLabelTest = InstanceType<typeof CheckboxLabelTest>;
18
+ export default CheckboxLabelTest;
@@ -0,0 +1,361 @@
1
+ <script lang="ts">
2
+ import { untrack, type Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import {
5
+ shouldShowFocusVisible,
6
+ trackInteractionModality
7
+ } from '../../primitives/input-modality';
8
+ import { cn } from '../../utils/cn';
9
+ import { setCheckboxContext, type CheckboxContext, type CheckboxState } from './context';
10
+
11
+ type CheckboxRootProps = Omit<
12
+ HTMLAttributes<HTMLSpanElement>,
13
+ | 'children'
14
+ | 'class'
15
+ | 'id'
16
+ | 'role'
17
+ | 'tabindex'
18
+ | 'aria-checked'
19
+ | 'aria-disabled'
20
+ | 'aria-readonly'
21
+ | 'aria-required'
22
+ | 'onclick'
23
+ | 'onkeydown'
24
+ | 'value'
25
+ > & {
26
+ id?: string;
27
+ name?: string;
28
+ value?: string;
29
+ isChecked?: boolean;
30
+ defaultChecked?: boolean;
31
+ isIndeterminate?: boolean;
32
+ defaultIndeterminate?: boolean;
33
+ onCheckedChange?: (checked: boolean) => void;
34
+ onIndeterminateChange?: (indeterminate: boolean) => void;
35
+ isDisabled?: boolean;
36
+ isReadOnly?: boolean;
37
+ required?: boolean;
38
+ children?: Snippet;
39
+ class?: string;
40
+ 'aria-label'?: string;
41
+ 'aria-labelledby'?: string;
42
+ };
43
+
44
+ function resolveState(isChecked: boolean, isIndeterminate: boolean): CheckboxState {
45
+ if (isIndeterminate) return 'indeterminate';
46
+ return isChecked ? 'checked' : 'unchecked';
47
+ }
48
+
49
+ function getNextState(currentState: CheckboxState): CheckboxState {
50
+ if (currentState === 'indeterminate') return 'checked';
51
+ if (currentState === 'checked') return 'unchecked';
52
+ return 'checked';
53
+ }
54
+
55
+ const generatedId = $props.id();
56
+
57
+ let {
58
+ id,
59
+ name,
60
+ value = 'on',
61
+ isChecked = $bindable(),
62
+ defaultChecked = false,
63
+ isIndeterminate = $bindable(),
64
+ defaultIndeterminate = false,
65
+ onCheckedChange,
66
+ onIndeterminateChange,
67
+ isDisabled = false,
68
+ isReadOnly = false,
69
+ required = false,
70
+ children,
71
+ class: className = '',
72
+ 'aria-label': ariaLabel,
73
+ 'aria-labelledby': ariaLabelledby,
74
+ ...restProps
75
+ }: CheckboxRootProps = $props();
76
+
77
+ const instanceId = untrack(() => id) ?? generatedId;
78
+ const inputId = instanceId;
79
+ const rootId = `${instanceId}-root`;
80
+
81
+ const initialChecked = untrack(() => isChecked ?? defaultChecked);
82
+ const initialIndeterminate = untrack(() => isIndeterminate ?? defaultIndeterminate);
83
+ const initialState = resolveState(initialChecked, initialIndeterminate);
84
+
85
+ let checkedInternal = $state(initialState === 'checked');
86
+ let indeterminateInternal = $state(initialState === 'indeterminate');
87
+ let pressed = $state(false);
88
+ let pressedKey: 'Enter' | 'Space' | null = $state(null);
89
+ let focused = $state(false);
90
+ let focusVisible = $state(false);
91
+ let rootRef: HTMLSpanElement | null = $state(null);
92
+ let inputRef: HTMLInputElement | null = $state(null);
93
+
94
+ if (untrack(() => isChecked) === undefined) {
95
+ isChecked = initialState === 'checked';
96
+ }
97
+
98
+ if (untrack(() => isIndeterminate) === undefined) {
99
+ isIndeterminate = initialState === 'indeterminate';
100
+ }
101
+
102
+ const isCheckedControlled = $derived(isChecked !== undefined);
103
+ const isIndeterminateControlled = $derived(isIndeterminate !== undefined);
104
+
105
+ const currentState = $derived.by(() =>
106
+ resolveState(
107
+ isCheckedControlled ? Boolean(isChecked) : checkedInternal,
108
+ isIndeterminateControlled ? Boolean(isIndeterminate) : indeterminateInternal
109
+ )
110
+ );
111
+
112
+ const currentChecked = $derived(currentState === 'checked');
113
+ const currentIndeterminate = $derived(currentState === 'indeterminate');
114
+ const currentUnchecked = $derived(currentState === 'unchecked');
115
+
116
+ function publishState(nextState: CheckboxState, event?: Event) {
117
+ const nextChecked = nextState === 'checked';
118
+ const nextIndeterminate = nextState === 'indeterminate';
119
+ const previousChecked = currentChecked;
120
+ const previousIndeterminate = currentIndeterminate;
121
+
122
+ if (!isCheckedControlled) {
123
+ checkedInternal = nextChecked;
124
+ }
125
+
126
+ if (!isIndeterminateControlled) {
127
+ indeterminateInternal = nextIndeterminate;
128
+ }
129
+
130
+ isChecked = nextChecked;
131
+ isIndeterminate = nextIndeterminate;
132
+
133
+ if (nextChecked !== previousChecked) {
134
+ onCheckedChange?.(nextChecked);
135
+ }
136
+
137
+ if (nextIndeterminate !== previousIndeterminate) {
138
+ onIndeterminateChange?.(nextIndeterminate);
139
+ }
140
+
141
+ if (event && rootRef && document.activeElement !== rootRef) {
142
+ rootRef.focus();
143
+ }
144
+ }
145
+
146
+ function setState(nextState: CheckboxState, event?: Event) {
147
+ if (isDisabled || isReadOnly) return;
148
+ publishState(nextState, event);
149
+ }
150
+
151
+ function toggle(event?: Event) {
152
+ setState(getNextState(currentState), event);
153
+ }
154
+
155
+ function requestNativeToggle(event?: Event) {
156
+ if (isDisabled || isReadOnly) return;
157
+ if (!inputRef) {
158
+ toggle(event);
159
+ return;
160
+ }
161
+
162
+ inputRef.click();
163
+ }
164
+
165
+ function handleClick(event: MouseEvent) {
166
+ trackInteractionModality(event, rootRef);
167
+
168
+ if (event.defaultPrevented) return;
169
+ if (isDisabled || isReadOnly) {
170
+ event.preventDefault();
171
+ return;
172
+ }
173
+
174
+ event.preventDefault();
175
+ requestNativeToggle(event);
176
+ }
177
+
178
+ function handleKeyDown(event: KeyboardEvent) {
179
+ if (event.defaultPrevented) return;
180
+ if (event.key !== ' ' && event.key !== 'Spacebar' && event.key !== 'Enter') return;
181
+
182
+ trackInteractionModality(event, rootRef);
183
+ if (focused) {
184
+ focusVisible = true;
185
+ } else {
186
+ focusVisible = shouldShowFocusVisible(rootRef);
187
+ }
188
+ event.preventDefault();
189
+
190
+ if (event.repeat && pressed) return;
191
+
192
+ pressed = true;
193
+ pressedKey = event.key === 'Enter' ? 'Enter' : 'Space';
194
+ }
195
+
196
+ function handleKeyUp(event: KeyboardEvent) {
197
+ if (event.defaultPrevented) return;
198
+
199
+ const releasedKey =
200
+ event.key === 'Enter'
201
+ ? 'Enter'
202
+ : event.key === ' ' || event.key === 'Spacebar'
203
+ ? 'Space'
204
+ : null;
205
+ if (!releasedKey) return;
206
+
207
+ trackInteractionModality(event, rootRef);
208
+ if (focused) {
209
+ focusVisible = true;
210
+ } else {
211
+ focusVisible = shouldShowFocusVisible(rootRef);
212
+ }
213
+ event.preventDefault();
214
+
215
+ const shouldToggle = pressed && pressedKey === releasedKey;
216
+ pressed = false;
217
+ pressedKey = null;
218
+
219
+ if (!shouldToggle) return;
220
+ requestNativeToggle(event);
221
+ }
222
+
223
+ function handlePointerDown(event: PointerEvent | MouseEvent) {
224
+ trackInteractionModality(event, rootRef);
225
+ focusVisible = false;
226
+ }
227
+
228
+ function handleFocus() {
229
+ focused = true;
230
+ focusVisible = shouldShowFocusVisible(rootRef);
231
+ }
232
+
233
+ function handleBlur() {
234
+ focused = false;
235
+ focusVisible = false;
236
+ pressed = false;
237
+ pressedKey = null;
238
+ }
239
+
240
+ function handleInputChange(event: Event) {
241
+ const target = event.currentTarget;
242
+ if (!(target instanceof HTMLInputElement)) return;
243
+
244
+ const nextState = resolveState(target.checked, target.indeterminate);
245
+ publishState(nextState, event);
246
+ }
247
+
248
+ function handleInputFocus() {
249
+ rootRef?.focus();
250
+ }
251
+
252
+ function handleInputClick(event: MouseEvent) {
253
+ event.stopPropagation();
254
+ }
255
+
256
+ $effect(() => {
257
+ if (!inputRef) return;
258
+ inputRef.checked = currentChecked;
259
+ inputRef.indeterminate = currentIndeterminate;
260
+ });
261
+
262
+ setCheckboxContext({
263
+ get id() {
264
+ return rootId;
265
+ },
266
+ get inputId() {
267
+ return inputId;
268
+ },
269
+ get inputRef() {
270
+ return inputRef;
271
+ },
272
+ setInputRef(element) {
273
+ inputRef = element;
274
+ },
275
+ get state() {
276
+ return currentState;
277
+ },
278
+ get pressed() {
279
+ return pressed;
280
+ },
281
+ get isChecked() {
282
+ return currentChecked;
283
+ },
284
+ get isIndeterminate() {
285
+ return currentIndeterminate;
286
+ },
287
+ get isDisabled() {
288
+ return isDisabled;
289
+ },
290
+ get isReadOnly() {
291
+ return isReadOnly;
292
+ },
293
+ get required() {
294
+ return required;
295
+ },
296
+ get focused() {
297
+ return focused;
298
+ },
299
+ get focusVisible() {
300
+ return focusVisible;
301
+ },
302
+ toggle,
303
+ setState
304
+ } satisfies CheckboxContext);
305
+ </script>
306
+
307
+ <span
308
+ {...restProps}
309
+ bind:this={rootRef}
310
+ id={rootId}
311
+ role="checkbox"
312
+ tabindex={isDisabled ? undefined : 0}
313
+ aria-checked={currentIndeterminate ? 'mixed' : currentChecked ? 'true' : 'false'}
314
+ aria-disabled={isDisabled || undefined}
315
+ aria-readonly={isReadOnly || undefined}
316
+ aria-required={required || undefined}
317
+ aria-label={ariaLabel}
318
+ aria-labelledby={ariaLabelledby}
319
+ data-checkbox-root="true"
320
+ data-checked={currentChecked || undefined}
321
+ data-unchecked={currentUnchecked || undefined}
322
+ data-indeterminate={currentIndeterminate || undefined}
323
+ data-pressed={pressed || undefined}
324
+ data-disabled={isDisabled || undefined}
325
+ data-readonly={isReadOnly || undefined}
326
+ data-required={required || undefined}
327
+ data-focused={focused || undefined}
328
+ data-focus-visible={focusVisible || undefined}
329
+ onclick={handleClick}
330
+ onkeydown={handleKeyDown}
331
+ onkeyup={handleKeyUp}
332
+ onpointerdown={handlePointerDown}
333
+ onmousedown={handlePointerDown}
334
+ onfocus={handleFocus}
335
+ onblur={handleBlur}
336
+ class={cn(
337
+ 'relative inline-flex shrink-0 items-center justify-center align-middle outline-none',
338
+ className
339
+ )}
340
+ >
341
+ <input
342
+ bind:this={inputRef}
343
+ id={inputId}
344
+ tabindex={-1}
345
+ type="checkbox"
346
+ {name}
347
+ {value}
348
+ checked={currentChecked}
349
+ disabled={isDisabled}
350
+ {required}
351
+ readonly={isReadOnly}
352
+ aria-hidden="true"
353
+ data-checkbox-input="true"
354
+ onclick={handleInputClick}
355
+ onchange={handleInputChange}
356
+ onfocus={handleInputFocus}
357
+ style="position:absolute;inset:0;width:100%;height:100%;margin:0;padding:0;border:0;opacity:0;cursor:inherit;pointer-events:none;"
358
+ />
359
+
360
+ {@render children?.()}
361
+ </span>
@@ -0,0 +1,23 @@
1
+ import { type Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type CheckboxRootProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class' | 'id' | 'role' | 'tabindex' | 'aria-checked' | 'aria-disabled' | 'aria-readonly' | 'aria-required' | 'onclick' | 'onkeydown' | 'value'> & {
4
+ id?: string;
5
+ name?: string;
6
+ value?: string;
7
+ isChecked?: boolean;
8
+ defaultChecked?: boolean;
9
+ isIndeterminate?: boolean;
10
+ defaultIndeterminate?: boolean;
11
+ onCheckedChange?: (checked: boolean) => void;
12
+ onIndeterminateChange?: (indeterminate: boolean) => void;
13
+ isDisabled?: boolean;
14
+ isReadOnly?: boolean;
15
+ required?: boolean;
16
+ children?: Snippet;
17
+ class?: string;
18
+ 'aria-label'?: string;
19
+ 'aria-labelledby'?: string;
20
+ };
21
+ declare const CheckboxRoot: import("svelte").Component<CheckboxRootProps, {}, "isChecked" | "isIndeterminate">;
22
+ type CheckboxRoot = ReturnType<typeof CheckboxRoot>;
23
+ export default CheckboxRoot;