@human-kit/svelte-components 1.0.0-alpha.3 → 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.
- package/dist/FOCUS_STATE_CONTRACT.md +12 -0
- package/dist/calendar/body-cell/README.md +15 -0
- package/dist/calendar/grid/README.md +13 -0
- package/dist/calendar/grid-body/README.md +13 -0
- package/dist/calendar/grid-header/README.md +13 -0
- package/dist/calendar/header-cell/README.md +14 -0
- package/dist/calendar/heading/README.md +13 -0
- package/dist/calendar/root/README.md +24 -0
- package/dist/calendar/trigger-next/README.md +14 -0
- package/dist/calendar/trigger-previous/README.md +14 -0
- package/dist/checkbox/README.md +53 -0
- package/dist/checkbox/TODO.md +16 -0
- package/dist/checkbox/index.d.ts +6 -0
- package/dist/checkbox/index.js +6 -0
- package/dist/checkbox/index.parts.d.ts +2 -0
- package/dist/checkbox/index.parts.js +2 -0
- package/dist/checkbox/indicator/README.md +23 -0
- package/dist/checkbox/indicator/checkbox-indicator.svelte +43 -0
- package/dist/checkbox/indicator/checkbox-indicator.svelte.d.ts +10 -0
- package/dist/checkbox/root/README.md +47 -0
- package/dist/checkbox/root/checkbox-label-test.svelte +10 -0
- package/dist/checkbox/root/checkbox-label-test.svelte.d.ts +18 -0
- package/dist/checkbox/root/checkbox-root.svelte +361 -0
- package/dist/checkbox/root/checkbox-root.svelte.d.ts +23 -0
- package/dist/checkbox/root/checkbox-test.svelte +59 -0
- package/dist/checkbox/root/checkbox-test.svelte.d.ts +18 -0
- package/dist/checkbox/root/context.d.ts +21 -0
- package/dist/checkbox/root/context.js +15 -0
- package/dist/clock/README.md +75 -0
- package/dist/clock/axis/README.md +24 -0
- package/dist/clock/axis/clock-axis.svelte +37 -0
- package/dist/clock/axis/clock-axis.svelte.d.ts +8 -0
- package/dist/clock/hooks/use-wheel-scroll.svelte.d.ts +16 -0
- package/dist/clock/hooks/use-wheel-scroll.svelte.js +336 -0
- package/dist/clock/index.d.ts +10 -0
- package/dist/clock/index.js +10 -0
- package/dist/clock/index.parts.d.ts +4 -0
- package/dist/clock/index.parts.js +4 -0
- package/dist/clock/root/README.md +38 -0
- package/dist/clock/root/clock-root-test.svelte +62 -0
- package/dist/clock/root/clock-root-test.svelte.d.ts +14 -0
- package/dist/clock/root/clock-root.svelte +329 -0
- package/dist/clock/root/clock-root.svelte.d.ts +25 -0
- package/dist/clock/root/context.d.ts +22 -0
- package/dist/clock/root/context.js +15 -0
- package/dist/clock/root/resolve-visible-columns.d.ts +7 -0
- package/dist/clock/root/resolve-visible-columns.js +16 -0
- package/dist/clock/root/time-utils.d.ts +48 -0
- package/dist/clock/root/time-utils.js +314 -0
- package/dist/clock/root/wheel-options.d.ts +17 -0
- package/dist/clock/root/wheel-options.js +63 -0
- package/dist/clock/wheel-column/README.md +25 -0
- package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte +16 -0
- package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte.d.ts +3 -0
- package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte +29 -0
- package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte.d.ts +6 -0
- package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte +11 -0
- package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte.d.ts +3 -0
- package/dist/clock/wheel-column/clock-wheel-column-test.svelte +38 -0
- package/dist/clock/wheel-column/clock-wheel-column-test.svelte.d.ts +12 -0
- package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte +38 -0
- package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte.d.ts +12 -0
- package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte +29 -0
- package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte.d.ts +6 -0
- package/dist/clock/wheel-column/clock-wheel-column.svelte +499 -0
- package/dist/clock/wheel-column/clock-wheel-column.svelte.d.ts +17 -0
- package/dist/clock/wheel-item/README.md +17 -0
- package/dist/clock/wheel-item/clock-wheel-item.svelte +49 -0
- package/dist/clock/wheel-item/clock-wheel-item.svelte.d.ts +17 -0
- package/dist/combobox/list/combobox-listbox.svelte.d.ts +1 -1
- package/dist/datepicker/TODO.md +2 -2
- package/dist/datepicker/calendar/README.md +19 -0
- package/dist/datepicker/input/README.md +15 -0
- package/dist/datepicker/popover/README.md +20 -0
- package/dist/datepicker/root/README.md +38 -0
- package/dist/datepicker/segment/README.md +14 -0
- package/dist/datepicker/trigger/README.md +14 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/primitives/focus-trap.js +11 -12
- package/dist/primitives/input-modality.js +10 -1
- package/dist/table/IMPLEMENTATION_NOTES.md +8 -0
- package/dist/table/PLAN-HIDDEN-COLUMNS.md +152 -0
- package/dist/table/PLAN.md +924 -0
- package/dist/table/README.md +116 -0
- package/dist/table/SELECTION_CHECKBOX_PLAN.md +234 -0
- package/dist/table/TODO.md +100 -0
- package/dist/table/body/README.md +24 -0
- package/dist/table/body/table-body.svelte +25 -0
- package/dist/table/body/table-body.svelte.d.ts +9 -0
- package/dist/table/cell/README.md +25 -0
- package/dist/table/cell/table-cell.svelte +247 -0
- package/dist/table/cell/table-cell.svelte.d.ts +9 -0
- package/dist/table/checkbox/README.md +38 -0
- package/dist/table/checkbox/table-checkbox-test.svelte +121 -0
- package/dist/table/checkbox/table-checkbox-test.svelte.d.ts +16 -0
- package/dist/table/checkbox/table-checkbox.svelte +274 -0
- package/dist/table/checkbox/table-checkbox.svelte.d.ts +13 -0
- package/dist/table/checkbox-indicator/README.md +29 -0
- package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte +22 -0
- package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte.d.ts +10 -0
- package/dist/table/column/README.md +32 -0
- package/dist/table/column/table-column.svelte +108 -0
- package/dist/table/column/table-column.svelte.d.ts +18 -0
- package/dist/table/column-header-cell/README.md +28 -0
- package/dist/table/column-header-cell/table-column-header-cell.svelte +281 -0
- package/dist/table/column-header-cell/table-column-header-cell.svelte.d.ts +9 -0
- package/dist/table/column-resizer/README.md +32 -0
- package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte +51 -0
- package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte +83 -0
- package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-test.svelte +75 -0
- package/dist/table/column-resizer/table-column-resizer-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer.svelte +616 -0
- package/dist/table/column-resizer/table-column-resizer.svelte.d.ts +11 -0
- package/dist/table/empty-state/README.md +25 -0
- package/dist/table/empty-state/table-empty-state.svelte +38 -0
- package/dist/table/empty-state/table-empty-state.svelte.d.ts +8 -0
- package/dist/table/footer/README.md +24 -0
- package/dist/table/footer/table-footer.svelte +19 -0
- package/dist/table/footer/table-footer.svelte.d.ts +9 -0
- package/dist/table/header/README.md +24 -0
- package/dist/table/header/table-header.svelte +19 -0
- package/dist/table/header/table-header.svelte.d.ts +9 -0
- package/dist/table/index.d.ts +16 -0
- package/dist/table/index.js +16 -0
- package/dist/table/index.parts.d.ts +12 -0
- package/dist/table/index.parts.js +12 -0
- package/dist/table/root/README.md +56 -0
- package/dist/table/root/context.d.ts +198 -0
- package/dist/table/root/context.js +1426 -0
- package/dist/table/root/table-reorder-test.svelte +64 -0
- package/dist/table/root/table-reorder-test.svelte.d.ts +3 -0
- package/dist/table/root/table-root.svelte +410 -0
- package/dist/table/root/table-root.svelte.d.ts +29 -0
- package/dist/table/root/table-test.svelte +165 -0
- package/dist/table/root/table-test.svelte.d.ts +25 -0
- package/dist/table/row/README.md +27 -0
- package/dist/table/row/table-row.svelte +321 -0
- package/dist/table/row/table-row.svelte.d.ts +13 -0
- package/dist/timepicker/IMPLEMENTATION_PLAN.md +254 -0
- package/dist/timepicker/README.md +97 -0
- package/dist/timepicker/TODO.md +86 -0
- package/dist/timepicker/clock/README.md +14 -0
- package/dist/timepicker/clock/time-picker-clock-test.svelte +45 -0
- package/dist/timepicker/clock/time-picker-clock-test.svelte.d.ts +11 -0
- package/dist/timepicker/clock/time-picker-clock.svelte +65 -0
- package/dist/timepicker/clock/time-picker-clock.svelte.d.ts +10 -0
- package/dist/timepicker/index.d.ts +14 -0
- package/dist/timepicker/index.js +14 -0
- package/dist/timepicker/index.parts.d.ts +8 -0
- package/dist/timepicker/index.parts.js +8 -0
- package/dist/timepicker/input/README.md +15 -0
- package/dist/timepicker/input/time-picker-input-forwarding-test.svelte +40 -0
- package/dist/timepicker/input/time-picker-input-forwarding-test.svelte.d.ts +3 -0
- package/dist/timepicker/input/time-picker-input.svelte +109 -0
- package/dist/timepicker/input/time-picker-input.svelte.d.ts +11 -0
- package/dist/timepicker/internal/strict-props.d.ts +4 -0
- package/dist/timepicker/internal/strict-props.js +51 -0
- package/dist/timepicker/popover/README.md +20 -0
- package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte +22 -0
- package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte.d.ts +3 -0
- package/dist/timepicker/popover/time-picker-popover.svelte +89 -0
- package/dist/timepicker/popover/time-picker-popover.svelte.d.ts +7 -0
- package/dist/timepicker/root/README.md +42 -0
- package/dist/timepicker/root/context.d.ts +51 -0
- package/dist/timepicker/root/context.js +15 -0
- package/dist/timepicker/root/time-picker-12h-test.svelte +22 -0
- package/dist/timepicker/root/time-picker-12h-test.svelte.d.ts +3 -0
- package/dist/timepicker/root/time-picker-bindable-test.svelte +25 -0
- package/dist/timepicker/root/time-picker-bindable-test.svelte.d.ts +3 -0
- package/dist/timepicker/root/time-picker-empty-test.svelte +20 -0
- package/dist/timepicker/root/time-picker-empty-test.svelte.d.ts +3 -0
- package/dist/timepicker/root/time-picker-root.svelte +625 -0
- package/dist/timepicker/root/time-picker-root.svelte.d.ts +28 -0
- package/dist/timepicker/root/time-picker-test.svelte +72 -0
- package/dist/timepicker/root/time-picker-test.svelte.d.ts +15 -0
- package/dist/timepicker/root/time-utils.d.ts +1 -0
- package/dist/timepicker/root/time-utils.js +3 -0
- package/dist/timepicker/segment/README.md +14 -0
- package/dist/timepicker/segment/time-picker-segment.svelte +365 -0
- package/dist/timepicker/segment/time-picker-segment.svelte.d.ts +9 -0
- package/dist/timepicker/trigger/README.md +14 -0
- package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte +35 -0
- package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte.d.ts +3 -0
- package/dist/timepicker/trigger/time-picker-trigger.svelte +122 -0
- package/dist/timepicker/trigger/time-picker-trigger.svelte.d.ts +9 -0
- package/package.json +21 -1
|
@@ -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;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Checkbox } from '../index';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
id?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
value?: string;
|
|
8
|
+
isChecked?: boolean;
|
|
9
|
+
defaultChecked?: boolean;
|
|
10
|
+
isIndeterminate?: boolean;
|
|
11
|
+
defaultIndeterminate?: boolean;
|
|
12
|
+
isDisabled?: boolean;
|
|
13
|
+
isReadOnly?: boolean;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
keepMounted?: boolean;
|
|
16
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
17
|
+
onIndeterminateChange?: (indeterminate: boolean) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
id,
|
|
22
|
+
name,
|
|
23
|
+
value = 'newsletter',
|
|
24
|
+
isChecked = $bindable(),
|
|
25
|
+
defaultChecked = false,
|
|
26
|
+
isIndeterminate = $bindable(),
|
|
27
|
+
defaultIndeterminate = false,
|
|
28
|
+
isDisabled = false,
|
|
29
|
+
isReadOnly = false,
|
|
30
|
+
required = false,
|
|
31
|
+
keepMounted = false,
|
|
32
|
+
onCheckedChange,
|
|
33
|
+
onIndeterminateChange
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<Checkbox.Root
|
|
38
|
+
{id}
|
|
39
|
+
{name}
|
|
40
|
+
{value}
|
|
41
|
+
bind:isChecked
|
|
42
|
+
{defaultChecked}
|
|
43
|
+
bind:isIndeterminate
|
|
44
|
+
{defaultIndeterminate}
|
|
45
|
+
{isDisabled}
|
|
46
|
+
{isReadOnly}
|
|
47
|
+
{required}
|
|
48
|
+
{onCheckedChange}
|
|
49
|
+
{onIndeterminateChange}
|
|
50
|
+
class="inline-flex h-5 w-5 items-center justify-center"
|
|
51
|
+
aria-label="Accept terms"
|
|
52
|
+
>
|
|
53
|
+
<Checkbox.Indicator {keepMounted}>
|
|
54
|
+
<span data-checkbox-icon="true">icon</span>
|
|
55
|
+
</Checkbox.Indicator>
|
|
56
|
+
</Checkbox.Root>
|
|
57
|
+
|
|
58
|
+
<output data-checked-state>{String(isChecked ?? false)}</output>
|
|
59
|
+
<output data-indeterminate-state>{String(isIndeterminate ?? false)}</output>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
id?: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
isChecked?: boolean;
|
|
6
|
+
defaultChecked?: boolean;
|
|
7
|
+
isIndeterminate?: boolean;
|
|
8
|
+
defaultIndeterminate?: boolean;
|
|
9
|
+
isDisabled?: boolean;
|
|
10
|
+
isReadOnly?: boolean;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
keepMounted?: boolean;
|
|
13
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
14
|
+
onIndeterminateChange?: (indeterminate: boolean) => void;
|
|
15
|
+
};
|
|
16
|
+
declare const CheckboxTest: import("svelte").Component<Props, {}, "isChecked" | "isIndeterminate">;
|
|
17
|
+
type CheckboxTest = ReturnType<typeof CheckboxTest>;
|
|
18
|
+
export default CheckboxTest;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type CheckboxState = 'checked' | 'unchecked' | 'indeterminate';
|
|
2
|
+
export type CheckboxContext = {
|
|
3
|
+
id: string;
|
|
4
|
+
inputId: string;
|
|
5
|
+
inputRef: HTMLInputElement | null;
|
|
6
|
+
setInputRef: (element: HTMLInputElement | null) => void;
|
|
7
|
+
state: CheckboxState;
|
|
8
|
+
pressed: boolean;
|
|
9
|
+
isChecked: boolean;
|
|
10
|
+
isIndeterminate: boolean;
|
|
11
|
+
isDisabled: boolean;
|
|
12
|
+
isReadOnly: boolean;
|
|
13
|
+
required: boolean;
|
|
14
|
+
focused: boolean;
|
|
15
|
+
focusVisible: boolean;
|
|
16
|
+
toggle: (event?: Event) => void;
|
|
17
|
+
setState: (state: CheckboxState, event?: Event) => void;
|
|
18
|
+
};
|
|
19
|
+
export declare function setCheckboxContext(context: CheckboxContext): void;
|
|
20
|
+
export declare function getCheckboxContext(): CheckboxContext | undefined;
|
|
21
|
+
export declare function useCheckboxContext(): CheckboxContext;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const CHECKBOX_CONTEXT_KEY = Symbol('checkbox');
|
|
3
|
+
export function setCheckboxContext(context) {
|
|
4
|
+
setContext(CHECKBOX_CONTEXT_KEY, context);
|
|
5
|
+
}
|
|
6
|
+
export function getCheckboxContext() {
|
|
7
|
+
return getContext(CHECKBOX_CONTEXT_KEY);
|
|
8
|
+
}
|
|
9
|
+
export function useCheckboxContext() {
|
|
10
|
+
const context = getCheckboxContext();
|
|
11
|
+
if (!context) {
|
|
12
|
+
throw new Error('Checkbox components must be used within Checkbox.Root.');
|
|
13
|
+
}
|
|
14
|
+
return context;
|
|
15
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Clock
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
`Clock` provides a standalone wheel-based time picker with spinbutton columns for hour, minute, second, and day-period selection. It can be used independently or composed inside `TimePicker` via `TimePicker.Clock`.
|
|
6
|
+
|
|
7
|
+
## Anatomy
|
|
8
|
+
|
|
9
|
+
- `Clock.Root`
|
|
10
|
+
- `Clock.Axis`
|
|
11
|
+
- `Clock.WheelColumn`
|
|
12
|
+
- `Clock.WheelItem`
|
|
13
|
+
|
|
14
|
+
```svelte
|
|
15
|
+
<Clock.Root value="14:30" granularity="minute" hourCycle={24} class="flex gap-2">
|
|
16
|
+
{#snippet column(col)}
|
|
17
|
+
<Clock.WheelColumn type={col.type} class="h-44 w-16">
|
|
18
|
+
{#snippet children(option)}
|
|
19
|
+
<Clock.WheelItem type={col.type} {option} class="..." />
|
|
20
|
+
{/snippet}
|
|
21
|
+
</Clock.WheelColumn>
|
|
22
|
+
{/snippet}
|
|
23
|
+
<Clock.Axis class="rounded-md ring-1 ring-inset" />
|
|
24
|
+
</Clock.Root>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Default columns are rendered automatically when no `column` snippet is provided:
|
|
28
|
+
|
|
29
|
+
```svelte
|
|
30
|
+
<Clock.Root value="09:00" granularity="minute" hourCycle={24} />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Root API
|
|
34
|
+
|
|
35
|
+
- `value?: string | null` (`HH:mm` or `HH:mm:ss`)
|
|
36
|
+
- `defaultValue?: string | null` (`HH:mm` or `HH:mm:ss`)
|
|
37
|
+
- `onChange?: (value: string | null) => void`
|
|
38
|
+
- `minValue?: string`
|
|
39
|
+
- `maxValue?: string`
|
|
40
|
+
- `hourCycle?: 12 | 24` (defaults to locale)
|
|
41
|
+
- `granularity?: 'hour' | 'minute' | 'second'` (defaults to `'minute'`)
|
|
42
|
+
- `hourStep?: number`
|
|
43
|
+
- `minuteStep?: number`
|
|
44
|
+
- `secondStep?: number`
|
|
45
|
+
- `isDisabled?: boolean`
|
|
46
|
+
- `column?: Snippet<[ClockColumnInfo]>` — custom per-column rendering
|
|
47
|
+
- `children?: Snippet` — arbitrary children. When `column` is used, children render after columns (useful for overlays like `Clock.Axis`).
|
|
48
|
+
- `class?: string`
|
|
49
|
+
- `aria-label?: string`
|
|
50
|
+
|
|
51
|
+
Visible columns are resolved automatically in stable order: `hour → minute? → second? → dayPeriod?`.
|
|
52
|
+
|
|
53
|
+
## Wheel API
|
|
54
|
+
|
|
55
|
+
- `Clock.Axis` renders a root-level visual overlay (for example, a central selection band) across all columns.
|
|
56
|
+
- `Clock.WheelColumn` renders one wheel (`role="spinbutton"`) for one editable segment (`hour`, `minute`, `second`, or `dayPeriod`).
|
|
57
|
+
- `Clock.WheelItem` is headless: it renders one item (`data-wheel-item`) with state attributes (`data-selected`, `data-disabled`, `data-centered`) and leaves all visual styling to consumers.
|
|
58
|
+
- `ClockColumnInfo` shape:
|
|
59
|
+
- `type: 'hour' | 'minute' | 'second' | 'dayPeriod'`
|
|
60
|
+
- `label?: string`
|
|
61
|
+
|
|
62
|
+
## Accessibility
|
|
63
|
+
|
|
64
|
+
- Each wheel column exposes `role="spinbutton"` with `aria-valuenow`, `aria-valuetext`, `aria-valuemin`, and `aria-valuemax`.
|
|
65
|
+
- `ArrowUp/ArrowDown`: change value by one step.
|
|
66
|
+
- `ArrowLeft/ArrowRight`: move focus between columns.
|
|
67
|
+
- `Home/End`: jump to first/last value in the column.
|
|
68
|
+
|
|
69
|
+
## Notes
|
|
70
|
+
|
|
71
|
+
- Locale is read from `LocaleProvider` when available.
|
|
72
|
+
- Internally, values are normalized to 24-hour representation; 12-hour rendering only affects UI segments.
|
|
73
|
+
- `granularity='hour'` emits `HH:00` values.
|
|
74
|
+
- Min/max comparisons do not support midnight-wrapping ranges.
|
|
75
|
+
- Wheel selection commits immediately on snap.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Clock Axis
|
|
2
|
+
|
|
3
|
+
## API reference
|
|
4
|
+
|
|
5
|
+
### Clock.Axis
|
|
6
|
+
|
|
7
|
+
Name: `Clock.Axis`
|
|
8
|
+
Description: Visual overlay band for `Clock.Root`; it is presentational (`aria-hidden`) and does not manage selection.
|
|
9
|
+
|
|
10
|
+
| Prop | Type | Default | Description |
|
|
11
|
+
| -------------- | -------------------------------- | ----------- | ---------------------------------------------------- |
|
|
12
|
+
| `height` | `number` | `undefined` | Optional overlay height in pixels. |
|
|
13
|
+
| `class` | `string` | `''` | CSS class names for the overlay element. |
|
|
14
|
+
| `style` | `string` | `''` | Inline styles merged with the optional height style. |
|
|
15
|
+
| `...restProps` | `HTMLAttributes<HTMLDivElement>` | `-` | Additional attributes forwarded to the overlay div. |
|
|
16
|
+
|
|
17
|
+
### Context utilities
|
|
18
|
+
|
|
19
|
+
Name: `useClockContext`
|
|
20
|
+
Description: Ensures `Clock.Axis` is used within `Clock.Root`.
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
| ----------------- | -------------------- | ------- | ------------------------------------------------ |
|
|
24
|
+
| `useClockContext` | `() => ClockContext` | `-` | Returns context and throws outside `Clock.Root`. |
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { useClockContext } from '../root/context';
|
|
4
|
+
|
|
5
|
+
type ClockAxisProps = Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'> & {
|
|
6
|
+
class?: string;
|
|
7
|
+
height?: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
class: className = '',
|
|
12
|
+
height,
|
|
13
|
+
style: styleProp = '',
|
|
14
|
+
...restProps
|
|
15
|
+
}: ClockAxisProps = $props();
|
|
16
|
+
|
|
17
|
+
const resolvedStyle = $derived.by(() => {
|
|
18
|
+
const base = styleProp?.trim() ?? '';
|
|
19
|
+
if (height === undefined || !Number.isFinite(height) || height <= 0) {
|
|
20
|
+
return base.length > 0 ? base : undefined;
|
|
21
|
+
}
|
|
22
|
+
const axisHeight = `height:${height}px`;
|
|
23
|
+
if (base.length === 0) return axisHeight;
|
|
24
|
+
const withSemicolon = /;\s*$/.test(base) ? base : `${base};`;
|
|
25
|
+
return `${withSemicolon}${axisHeight}`;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
useClockContext();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<div
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
data-clock-axis
|
|
34
|
+
class={`pointer-events-none absolute top-1/2 left-0 w-full -translate-y-1/2 ${className}`}
|
|
35
|
+
style={resolvedStyle}
|
|
36
|
+
{...restProps}
|
|
37
|
+
></div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type ClockAxisProps = Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'> & {
|
|
3
|
+
class?: string;
|
|
4
|
+
height?: number;
|
|
5
|
+
};
|
|
6
|
+
declare const ClockAxis: import("svelte").Component<ClockAxisProps, {}, "">;
|
|
7
|
+
type ClockAxis = ReturnType<typeof ClockAxis>;
|
|
8
|
+
export default ClockAxis;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type WheelScrollBehavior = 'smooth' | 'instant';
|
|
2
|
+
export type WheelScrollApi = {
|
|
3
|
+
scrollToIndex: (index: number, behavior?: WheelScrollBehavior, options?: {
|
|
4
|
+
silent?: boolean;
|
|
5
|
+
}) => void;
|
|
6
|
+
destroy: () => void;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Manages scroll-based item selection for a wheel column.
|
|
10
|
+
*
|
|
11
|
+
* All snapping is handled in JavaScript (no CSS `scroll-snap-type`).
|
|
12
|
+
* After scrolling settles (either short inactivity in `scroll` events
|
|
13
|
+
* or browser `scrollend`), we find the nearest centered item and run
|
|
14
|
+
* a fast 120 ms ease-out animation to align it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useWheelScroll(container: HTMLElement, onSnap: (centeredIndex: number) => number | null | void): WheelScrollApi;
|