@chromvoid/headless-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/a11y-contracts/index.d.ts +23 -0
- package/dist/a11y-contracts/index.js +1 -0
- package/dist/accordion/index.d.ts +78 -0
- package/dist/accordion/index.js +264 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +1 -0
- package/dist/alert/index.d.ts +33 -0
- package/dist/alert/index.js +54 -0
- package/dist/alert-dialog/index.d.ts +69 -0
- package/dist/alert-dialog/index.js +94 -0
- package/dist/badge/index.d.ts +48 -0
- package/dist/badge/index.js +89 -0
- package/dist/breadcrumb/index.d.ts +55 -0
- package/dist/breadcrumb/index.js +77 -0
- package/dist/button/index.d.ts +46 -0
- package/dist/button/index.js +86 -0
- package/dist/callout/index.d.ts +41 -0
- package/dist/callout/index.js +63 -0
- package/dist/card/index.d.ts +54 -0
- package/dist/card/index.js +103 -0
- package/dist/carousel/index.d.ts +98 -0
- package/dist/carousel/index.js +243 -0
- package/dist/checkbox/index.d.ts +50 -0
- package/dist/checkbox/index.js +87 -0
- package/dist/combobox/index.d.ts +114 -0
- package/dist/combobox/index.js +431 -0
- package/dist/command-palette/index.d.ts +73 -0
- package/dist/command-palette/index.js +147 -0
- package/dist/context-menu/index.d.ts +111 -0
- package/dist/context-menu/index.js +372 -0
- package/dist/copy-button/index.d.ts +62 -0
- package/dist/copy-button/index.js +183 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.js +2 -0
- package/dist/core/selection.d.ts +5 -0
- package/dist/core/selection.js +39 -0
- package/dist/core/value-range.d.ts +49 -0
- package/dist/core/value-range.js +134 -0
- package/dist/date-picker/index.d.ts +210 -0
- package/dist/date-picker/index.js +895 -0
- package/dist/dialog/index.d.ts +95 -0
- package/dist/dialog/index.js +153 -0
- package/dist/disclosure/index.d.ts +52 -0
- package/dist/disclosure/index.js +159 -0
- package/dist/drawer/index.d.ts +30 -0
- package/dist/drawer/index.js +39 -0
- package/dist/feed/index.d.ts +77 -0
- package/dist/feed/index.js +260 -0
- package/dist/grid/index.d.ts +103 -0
- package/dist/grid/index.js +415 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +51 -0
- package/dist/input/index.d.ts +86 -0
- package/dist/input/index.js +156 -0
- package/dist/interactions/composite-navigation.d.ts +69 -0
- package/dist/interactions/composite-navigation.js +169 -0
- package/dist/interactions/index.d.ts +15 -0
- package/dist/interactions/index.js +4 -0
- package/dist/interactions/keyboard-intents.d.ts +16 -0
- package/dist/interactions/keyboard-intents.js +33 -0
- package/dist/interactions/overlay-focus.d.ts +40 -0
- package/dist/interactions/overlay-focus.js +93 -0
- package/dist/interactions/typeahead.d.ts +20 -0
- package/dist/interactions/typeahead.js +41 -0
- package/dist/landmarks/index.d.ts +39 -0
- package/dist/landmarks/index.js +58 -0
- package/dist/link/index.d.ts +34 -0
- package/dist/link/index.js +39 -0
- package/dist/listbox/index.d.ts +92 -0
- package/dist/listbox/index.js +337 -0
- package/dist/menu/index.d.ts +132 -0
- package/dist/menu/index.js +541 -0
- package/dist/menu-button/index.d.ts +71 -0
- package/dist/menu-button/index.js +121 -0
- package/dist/meter/index.d.ts +45 -0
- package/dist/meter/index.js +106 -0
- package/dist/number/index.d.ts +113 -0
- package/dist/number/index.js +252 -0
- package/dist/popover/index.d.ts +70 -0
- package/dist/popover/index.js +126 -0
- package/dist/progress/index.d.ts +49 -0
- package/dist/progress/index.js +79 -0
- package/dist/radio-group/index.d.ts +61 -0
- package/dist/radio-group/index.js +150 -0
- package/dist/select/index.d.ts +92 -0
- package/dist/select/index.js +239 -0
- package/dist/sidebar/index.d.ts +74 -0
- package/dist/sidebar/index.js +186 -0
- package/dist/slider/index.d.ts +61 -0
- package/dist/slider/index.js +150 -0
- package/dist/slider-multi-thumb/index.d.ts +70 -0
- package/dist/slider-multi-thumb/index.js +222 -0
- package/dist/spinbutton/index.d.ts +75 -0
- package/dist/spinbutton/index.js +214 -0
- package/dist/spinner/index.d.ts +1 -0
- package/dist/spinner/index.js +1 -0
- package/dist/spinner/spinner.d.ts +23 -0
- package/dist/spinner/spinner.js +25 -0
- package/dist/switch/index.d.ts +40 -0
- package/dist/switch/index.js +61 -0
- package/dist/table/index.d.ts +117 -0
- package/dist/table/index.js +377 -0
- package/dist/tabs/index.d.ts +63 -0
- package/dist/tabs/index.js +174 -0
- package/dist/textarea/index.d.ts +68 -0
- package/dist/textarea/index.js +137 -0
- package/dist/toast/index.d.ts +67 -0
- package/dist/toast/index.js +145 -0
- package/dist/toolbar/index.d.ts +59 -0
- package/dist/toolbar/index.js +139 -0
- package/dist/tooltip/index.d.ts +52 -0
- package/dist/tooltip/index.js +169 -0
- package/dist/treegrid/index.d.ts +101 -0
- package/dist/treegrid/index.js +463 -0
- package/dist/treeview/index.d.ts +68 -0
- package/dist/treeview/index.js +370 -0
- package/dist/window-splitter/index.d.ts +65 -0
- package/dist/window-splitter/index.js +204 -0
- package/package.json +92 -0
- package/specs/ADR-001-headless-architecture.md +461 -0
- package/specs/ADR-002-repo-release-model.md +108 -0
- package/specs/ADR-003-public-api-versioning.md +136 -0
- package/specs/ADR-004-focus-selection-policy.md +117 -0
- package/specs/IMPLEMENTATION-ROADMAP.md +237 -0
- package/specs/ISSUE-BACKLOG.md +681 -0
- package/specs/RELEASE-CANDIDATE.md +30 -0
- package/specs/components/accordion.md +130 -0
- package/specs/components/alert-dialog.md +72 -0
- package/specs/components/alert.md +65 -0
- package/specs/components/badge.md +220 -0
- package/specs/components/breadcrumb.md +74 -0
- package/specs/components/button.md +115 -0
- package/specs/components/callout.md +195 -0
- package/specs/components/card.md +280 -0
- package/specs/components/carousel.md +140 -0
- package/specs/components/checkbox.md +172 -0
- package/specs/components/combobox.md +423 -0
- package/specs/components/command-palette.md +92 -0
- package/specs/components/context-menu.md +556 -0
- package/specs/components/copy-button.md +293 -0
- package/specs/components/date-picker.md +400 -0
- package/specs/components/dialog.md +298 -0
- package/specs/components/disclosure.md +257 -0
- package/specs/components/drawer.md +353 -0
- package/specs/components/feed.md +265 -0
- package/specs/components/grid.md +186 -0
- package/specs/components/input.md +254 -0
- package/specs/components/landmarks.md +136 -0
- package/specs/components/link.md +134 -0
- package/specs/components/listbox.md +351 -0
- package/specs/components/menu-button.md +76 -0
- package/specs/components/menu.md +623 -0
- package/specs/components/meter.md +149 -0
- package/specs/components/number.md +393 -0
- package/specs/components/popover.md +252 -0
- package/specs/components/progress.md +188 -0
- package/specs/components/radio-group.md +151 -0
- package/specs/components/select.md +144 -0
- package/specs/components/sidebar.md +321 -0
- package/specs/components/slider-multi-thumb.md +78 -0
- package/specs/components/slider.md +84 -0
- package/specs/components/spinbutton.md +140 -0
- package/specs/components/spinner.md +132 -0
- package/specs/components/switch.md +175 -0
- package/specs/components/table.md +403 -0
- package/specs/components/tabs.md +265 -0
- package/specs/components/textarea.md +185 -0
- package/specs/components/toast.md +198 -0
- package/specs/components/toolbar.md +278 -0
- package/specs/components/tooltip.md +252 -0
- package/specs/components/treegrid.md +281 -0
- package/specs/components/treeview.md +91 -0
- package/specs/components/window-splitter.md +297 -0
- package/specs/ops/git-shard-sync.md +107 -0
- package/specs/ops/release-checklist.md +76 -0
- package/specs/release/GAP-TO-GREEN-ISSUES.md +88 -0
- package/specs/release/api-freeze-candidate.md +54 -0
- package/specs/release/changelog-automation.md +76 -0
- package/specs/release/changelog.generated.md +53 -0
- package/specs/release/changelog.patch.generated.md +46 -0
- package/specs/release/consumer-integration.md +53 -0
- package/specs/release/migration-notes-pre-v1.md +40 -0
- package/specs/release/mvp-changelog.md +57 -0
- package/specs/release/release-notes-template.md +61 -0
- package/specs/release/release-rehearsal.md +113 -0
- package/specs/release/semver-deprecation-dry-run.md +89 -0
- package/specs/release/shard-release-drill-report.md +50 -0
- package/specs/release/shard-release-follow-ups.md +31 -0
- package/specs/signals.md +208 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type Atom, type Computed } from '@reatom/core';
|
|
2
|
+
export type SpinbuttonKeyboardEventLike = Pick<KeyboardEvent, 'key'> & {
|
|
3
|
+
preventDefault?: () => void;
|
|
4
|
+
};
|
|
5
|
+
export interface CreateSpinbuttonOptions {
|
|
6
|
+
idBase?: string;
|
|
7
|
+
value?: number;
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
step?: number;
|
|
11
|
+
largeStep?: number;
|
|
12
|
+
isDisabled?: boolean;
|
|
13
|
+
isReadOnly?: boolean;
|
|
14
|
+
ariaLabel?: string;
|
|
15
|
+
ariaLabelledBy?: string;
|
|
16
|
+
ariaDescribedBy?: string;
|
|
17
|
+
formatValueText?: (value: number) => string;
|
|
18
|
+
onValueChange?: (value: number) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface SpinbuttonState {
|
|
21
|
+
value: Atom<number>;
|
|
22
|
+
min: Atom<number | undefined>;
|
|
23
|
+
max: Atom<number | undefined>;
|
|
24
|
+
step: Atom<number>;
|
|
25
|
+
largeStep: Atom<number>;
|
|
26
|
+
isDisabled: Atom<boolean>;
|
|
27
|
+
isReadOnly: Atom<boolean>;
|
|
28
|
+
hasMin: Computed<boolean>;
|
|
29
|
+
hasMax: Computed<boolean>;
|
|
30
|
+
}
|
|
31
|
+
export interface SpinbuttonActions {
|
|
32
|
+
setValue(value: number): void;
|
|
33
|
+
increment(): void;
|
|
34
|
+
decrement(): void;
|
|
35
|
+
incrementLarge(): void;
|
|
36
|
+
decrementLarge(): void;
|
|
37
|
+
setFirst(): void;
|
|
38
|
+
setLast(): void;
|
|
39
|
+
setDisabled(value: boolean): void;
|
|
40
|
+
setReadOnly(value: boolean): void;
|
|
41
|
+
handleKeyDown(event: SpinbuttonKeyboardEventLike): void;
|
|
42
|
+
}
|
|
43
|
+
export interface SpinbuttonProps {
|
|
44
|
+
id: string;
|
|
45
|
+
role: 'spinbutton';
|
|
46
|
+
tabindex: '0' | '-1';
|
|
47
|
+
'aria-valuenow': string;
|
|
48
|
+
'aria-valuemin'?: string;
|
|
49
|
+
'aria-valuemax'?: string;
|
|
50
|
+
'aria-valuetext'?: string;
|
|
51
|
+
'aria-disabled'?: 'true';
|
|
52
|
+
'aria-readonly'?: 'true';
|
|
53
|
+
'aria-label'?: string;
|
|
54
|
+
'aria-labelledby'?: string;
|
|
55
|
+
'aria-describedby'?: string;
|
|
56
|
+
onKeyDown: (event: SpinbuttonKeyboardEventLike) => void;
|
|
57
|
+
}
|
|
58
|
+
export interface SpinbuttonButtonProps {
|
|
59
|
+
id: string;
|
|
60
|
+
tabindex: '-1';
|
|
61
|
+
'aria-label': string;
|
|
62
|
+
'aria-disabled'?: 'true';
|
|
63
|
+
onClick: () => void;
|
|
64
|
+
}
|
|
65
|
+
export interface SpinbuttonContracts {
|
|
66
|
+
getSpinbuttonProps(): SpinbuttonProps;
|
|
67
|
+
getIncrementButtonProps(): SpinbuttonButtonProps;
|
|
68
|
+
getDecrementButtonProps(): SpinbuttonButtonProps;
|
|
69
|
+
}
|
|
70
|
+
export interface SpinbuttonModel {
|
|
71
|
+
readonly state: SpinbuttonState;
|
|
72
|
+
readonly actions: SpinbuttonActions;
|
|
73
|
+
readonly contracts: SpinbuttonContracts;
|
|
74
|
+
}
|
|
75
|
+
export declare function createSpinbutton(options?: CreateSpinbuttonOptions): SpinbuttonModel;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { action, atom, computed } from '@reatom/core';
|
|
2
|
+
const EPSILON = 1e-9;
|
|
3
|
+
const toNumericString = (value) => (value == null ? undefined : String(value));
|
|
4
|
+
const sanitizePositive = (value, fallback) => {
|
|
5
|
+
if (value == null || !Number.isFinite(value) || value <= 0) {
|
|
6
|
+
return fallback;
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
const decimalPlaces = (value) => {
|
|
11
|
+
const text = value.toString().toLowerCase();
|
|
12
|
+
const scientific = text.match(/e-(\d+)$/);
|
|
13
|
+
if (scientific?.[1]) {
|
|
14
|
+
return Number.parseInt(scientific[1], 10);
|
|
15
|
+
}
|
|
16
|
+
const decimalIndex = text.indexOf('.');
|
|
17
|
+
if (decimalIndex < 0)
|
|
18
|
+
return 0;
|
|
19
|
+
return text.length - decimalIndex - 1;
|
|
20
|
+
};
|
|
21
|
+
const normalizePrecision = (value, precision) => Number(value.toFixed(Math.min(Math.max(precision, 0), 12)));
|
|
22
|
+
const normalizeBounds = (min, max) => {
|
|
23
|
+
if (min != null && max != null && min > max) {
|
|
24
|
+
return { min: max, max: min };
|
|
25
|
+
}
|
|
26
|
+
return { min, max };
|
|
27
|
+
};
|
|
28
|
+
const clampOptional = (value, min, max) => {
|
|
29
|
+
if (min != null && value < min)
|
|
30
|
+
return min;
|
|
31
|
+
if (max != null && value > max)
|
|
32
|
+
return max;
|
|
33
|
+
return value;
|
|
34
|
+
};
|
|
35
|
+
const snapToStep = (value, step, anchor) => {
|
|
36
|
+
const offset = (value - anchor) / step;
|
|
37
|
+
const snapped = anchor + Math.round(offset) * step;
|
|
38
|
+
const precision = Math.max(decimalPlaces(anchor), decimalPlaces(step));
|
|
39
|
+
return normalizePrecision(snapped, precision + 2);
|
|
40
|
+
};
|
|
41
|
+
const isAtOrPastMax = (value, max) => max != null && value >= max - EPSILON;
|
|
42
|
+
const isAtOrPastMin = (value, min) => min != null && value <= min + EPSILON;
|
|
43
|
+
export function createSpinbutton(options = {}) {
|
|
44
|
+
const idBase = options.idBase ?? 'spinbutton';
|
|
45
|
+
const initialStep = sanitizePositive(options.step, 1);
|
|
46
|
+
const initialLargeStep = sanitizePositive(options.largeStep, initialStep * 10);
|
|
47
|
+
const normalizedBounds = normalizeBounds(options.min, options.max);
|
|
48
|
+
const minAtom = atom(normalizedBounds.min, `${idBase}.min`);
|
|
49
|
+
const maxAtom = atom(normalizedBounds.max, `${idBase}.max`);
|
|
50
|
+
const stepAtom = atom(initialStep, `${idBase}.step`);
|
|
51
|
+
const largeStepAtom = atom(initialLargeStep, `${idBase}.largeStep`);
|
|
52
|
+
const normalizeValue = (value) => {
|
|
53
|
+
const snapped = snapToStep(value, stepAtom(), minAtom() ?? 0);
|
|
54
|
+
return clampOptional(snapped, minAtom(), maxAtom());
|
|
55
|
+
};
|
|
56
|
+
const initialValueSeed = Number.isFinite(options.value) ? options.value : (minAtom() ?? 0);
|
|
57
|
+
const initialValue = normalizeValue(initialValueSeed);
|
|
58
|
+
const valueAtom = atom(initialValue, `${idBase}.value`);
|
|
59
|
+
const isDisabledAtom = atom(options.isDisabled ?? false, `${idBase}.isDisabled`);
|
|
60
|
+
const isReadOnlyAtom = atom(options.isReadOnly ?? false, `${idBase}.isReadOnly`);
|
|
61
|
+
const hasMinComputed = computed(() => minAtom() != null, `${idBase}.hasMinComputed`);
|
|
62
|
+
const hasMaxComputed = computed(() => maxAtom() != null, `${idBase}.hasMaxComputed`);
|
|
63
|
+
const canMutate = () => !isDisabledAtom() && !isReadOnlyAtom();
|
|
64
|
+
const commitValue = (next) => {
|
|
65
|
+
if (!Number.isFinite(next))
|
|
66
|
+
return;
|
|
67
|
+
const previous = valueAtom();
|
|
68
|
+
const normalizedValue = normalizeValue(next);
|
|
69
|
+
valueAtom.set(normalizedValue);
|
|
70
|
+
if (previous !== normalizedValue) {
|
|
71
|
+
options.onValueChange?.(normalizedValue);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const setValue = action((value) => {
|
|
75
|
+
if (!canMutate())
|
|
76
|
+
return;
|
|
77
|
+
commitValue(value);
|
|
78
|
+
}, `${idBase}.setValue`);
|
|
79
|
+
const increment = action(() => {
|
|
80
|
+
if (!canMutate())
|
|
81
|
+
return;
|
|
82
|
+
commitValue(valueAtom() + stepAtom());
|
|
83
|
+
}, `${idBase}.increment`);
|
|
84
|
+
const decrement = action(() => {
|
|
85
|
+
if (!canMutate())
|
|
86
|
+
return;
|
|
87
|
+
commitValue(valueAtom() - stepAtom());
|
|
88
|
+
}, `${idBase}.decrement`);
|
|
89
|
+
const incrementLarge = action(() => {
|
|
90
|
+
if (!canMutate())
|
|
91
|
+
return;
|
|
92
|
+
commitValue(valueAtom() + largeStepAtom());
|
|
93
|
+
}, `${idBase}.incrementLarge`);
|
|
94
|
+
const decrementLarge = action(() => {
|
|
95
|
+
if (!canMutate())
|
|
96
|
+
return;
|
|
97
|
+
commitValue(valueAtom() - largeStepAtom());
|
|
98
|
+
}, `${idBase}.decrementLarge`);
|
|
99
|
+
const setFirst = action(() => {
|
|
100
|
+
if (!canMutate() || minAtom() == null)
|
|
101
|
+
return;
|
|
102
|
+
commitValue(minAtom());
|
|
103
|
+
}, `${idBase}.setFirst`);
|
|
104
|
+
const setLast = action(() => {
|
|
105
|
+
if (!canMutate() || maxAtom() == null)
|
|
106
|
+
return;
|
|
107
|
+
commitValue(maxAtom());
|
|
108
|
+
}, `${idBase}.setLast`);
|
|
109
|
+
const setDisabled = action((value) => {
|
|
110
|
+
isDisabledAtom.set(value);
|
|
111
|
+
}, `${idBase}.setDisabled`);
|
|
112
|
+
const setReadOnly = action((value) => {
|
|
113
|
+
isReadOnlyAtom.set(value);
|
|
114
|
+
}, `${idBase}.setReadOnly`);
|
|
115
|
+
const handleKeyDown = action((event) => {
|
|
116
|
+
switch (event.key) {
|
|
117
|
+
case 'ArrowUp':
|
|
118
|
+
event.preventDefault?.();
|
|
119
|
+
increment();
|
|
120
|
+
return;
|
|
121
|
+
case 'ArrowDown':
|
|
122
|
+
event.preventDefault?.();
|
|
123
|
+
decrement();
|
|
124
|
+
return;
|
|
125
|
+
case 'PageUp':
|
|
126
|
+
event.preventDefault?.();
|
|
127
|
+
incrementLarge();
|
|
128
|
+
return;
|
|
129
|
+
case 'PageDown':
|
|
130
|
+
event.preventDefault?.();
|
|
131
|
+
decrementLarge();
|
|
132
|
+
return;
|
|
133
|
+
case 'Home':
|
|
134
|
+
event.preventDefault?.();
|
|
135
|
+
setFirst();
|
|
136
|
+
return;
|
|
137
|
+
case 'End':
|
|
138
|
+
event.preventDefault?.();
|
|
139
|
+
setLast();
|
|
140
|
+
return;
|
|
141
|
+
default:
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}, `${idBase}.handleKeyDown`);
|
|
145
|
+
const actions = {
|
|
146
|
+
setValue,
|
|
147
|
+
increment,
|
|
148
|
+
decrement,
|
|
149
|
+
incrementLarge,
|
|
150
|
+
decrementLarge,
|
|
151
|
+
setFirst,
|
|
152
|
+
setLast,
|
|
153
|
+
setDisabled,
|
|
154
|
+
setReadOnly,
|
|
155
|
+
handleKeyDown,
|
|
156
|
+
};
|
|
157
|
+
const isControlDisabled = () => isDisabledAtom() || isReadOnlyAtom();
|
|
158
|
+
const isIncrementDisabled = () => isControlDisabled() || isAtOrPastMax(valueAtom(), maxAtom());
|
|
159
|
+
const isDecrementDisabled = () => isControlDisabled() || isAtOrPastMin(valueAtom(), minAtom());
|
|
160
|
+
const contracts = {
|
|
161
|
+
getSpinbuttonProps() {
|
|
162
|
+
const value = valueAtom();
|
|
163
|
+
return {
|
|
164
|
+
id: `${idBase}-root`,
|
|
165
|
+
role: 'spinbutton',
|
|
166
|
+
tabindex: isDisabledAtom() ? '-1' : '0',
|
|
167
|
+
'aria-valuenow': String(value),
|
|
168
|
+
'aria-valuemin': toNumericString(minAtom()),
|
|
169
|
+
'aria-valuemax': toNumericString(maxAtom()),
|
|
170
|
+
'aria-valuetext': options.formatValueText?.(value),
|
|
171
|
+
'aria-disabled': isDisabledAtom() ? 'true' : undefined,
|
|
172
|
+
'aria-readonly': isReadOnlyAtom() ? 'true' : undefined,
|
|
173
|
+
'aria-label': options.ariaLabel,
|
|
174
|
+
'aria-labelledby': options.ariaLabelledBy,
|
|
175
|
+
'aria-describedby': options.ariaDescribedBy,
|
|
176
|
+
onKeyDown: handleKeyDown,
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
getIncrementButtonProps() {
|
|
180
|
+
return {
|
|
181
|
+
id: `${idBase}-increment`,
|
|
182
|
+
tabindex: '-1',
|
|
183
|
+
'aria-label': 'Increment value',
|
|
184
|
+
'aria-disabled': isIncrementDisabled() ? 'true' : undefined,
|
|
185
|
+
onClick: increment,
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
getDecrementButtonProps() {
|
|
189
|
+
return {
|
|
190
|
+
id: `${idBase}-decrement`,
|
|
191
|
+
tabindex: '-1',
|
|
192
|
+
'aria-label': 'Decrement value',
|
|
193
|
+
'aria-disabled': isDecrementDisabled() ? 'true' : undefined,
|
|
194
|
+
onClick: decrement,
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
const state = {
|
|
199
|
+
value: valueAtom,
|
|
200
|
+
min: minAtom,
|
|
201
|
+
max: maxAtom,
|
|
202
|
+
step: stepAtom,
|
|
203
|
+
largeStep: largeStepAtom,
|
|
204
|
+
isDisabled: isDisabledAtom,
|
|
205
|
+
isReadOnly: isReadOnlyAtom,
|
|
206
|
+
hasMin: hasMinComputed,
|
|
207
|
+
hasMax: hasMaxComputed,
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
state,
|
|
211
|
+
actions,
|
|
212
|
+
contracts,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSpinner, type CreateSpinnerOptions, type SpinnerProps, type SpinnerState, type SpinnerActions, type SpinnerContracts, type SpinnerModel, } from './spinner.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSpinner, } from './spinner.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Atom } from '@reatom/core';
|
|
2
|
+
export interface CreateSpinnerOptions {
|
|
3
|
+
label?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SpinnerProps {
|
|
6
|
+
role: 'progressbar';
|
|
7
|
+
'aria-label': string;
|
|
8
|
+
}
|
|
9
|
+
export interface SpinnerState {
|
|
10
|
+
label: Atom<string>;
|
|
11
|
+
}
|
|
12
|
+
export interface SpinnerActions {
|
|
13
|
+
setLabel(value: string): void;
|
|
14
|
+
}
|
|
15
|
+
export interface SpinnerContracts {
|
|
16
|
+
getSpinnerProps(): SpinnerProps;
|
|
17
|
+
}
|
|
18
|
+
export interface SpinnerModel {
|
|
19
|
+
readonly state: SpinnerState;
|
|
20
|
+
readonly actions: SpinnerActions;
|
|
21
|
+
readonly contracts: SpinnerContracts;
|
|
22
|
+
}
|
|
23
|
+
export declare function createSpinner(options?: CreateSpinnerOptions): SpinnerModel;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { action, atom } from '@reatom/core';
|
|
2
|
+
export function createSpinner(options = {}) {
|
|
3
|
+
const labelAtom = atom(options.label ?? 'Loading', 'spinner.label');
|
|
4
|
+
const actions = {
|
|
5
|
+
setLabel: action((value) => {
|
|
6
|
+
labelAtom.set(value);
|
|
7
|
+
}, 'spinner.setLabel'),
|
|
8
|
+
};
|
|
9
|
+
const contracts = {
|
|
10
|
+
getSpinnerProps() {
|
|
11
|
+
return {
|
|
12
|
+
role: 'progressbar',
|
|
13
|
+
'aria-label': labelAtom(),
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
const state = {
|
|
18
|
+
label: labelAtom,
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
state,
|
|
22
|
+
actions,
|
|
23
|
+
contracts,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Atom } from '@reatom/core';
|
|
2
|
+
export interface CreateSwitchOptions {
|
|
3
|
+
idBase?: string;
|
|
4
|
+
isOn?: boolean;
|
|
5
|
+
isDisabled?: boolean;
|
|
6
|
+
ariaLabelledBy?: string;
|
|
7
|
+
ariaDescribedBy?: string;
|
|
8
|
+
onCheckedChange?: (value: boolean) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface SwitchState {
|
|
11
|
+
isOn: Atom<boolean>;
|
|
12
|
+
isDisabled: Atom<boolean>;
|
|
13
|
+
}
|
|
14
|
+
export interface SwitchActions {
|
|
15
|
+
setOn(value: boolean): void;
|
|
16
|
+
setDisabled(value: boolean): void;
|
|
17
|
+
toggle(): void;
|
|
18
|
+
handleClick(): void;
|
|
19
|
+
handleKeyDown(event: Pick<KeyboardEvent, 'key' | 'preventDefault'>): void;
|
|
20
|
+
}
|
|
21
|
+
export interface SwitchProps {
|
|
22
|
+
id: string;
|
|
23
|
+
role: 'switch';
|
|
24
|
+
tabindex: '0' | '-1';
|
|
25
|
+
'aria-checked': 'true' | 'false';
|
|
26
|
+
'aria-disabled': 'true' | 'false';
|
|
27
|
+
'aria-labelledby'?: string;
|
|
28
|
+
'aria-describedby'?: string;
|
|
29
|
+
onClick: () => void;
|
|
30
|
+
onKeyDown: (event: Pick<KeyboardEvent, 'key' | 'preventDefault'>) => void;
|
|
31
|
+
}
|
|
32
|
+
export interface SwitchContracts {
|
|
33
|
+
getSwitchProps(): SwitchProps;
|
|
34
|
+
}
|
|
35
|
+
export interface SwitchModel {
|
|
36
|
+
readonly state: SwitchState;
|
|
37
|
+
readonly actions: SwitchActions;
|
|
38
|
+
readonly contracts: SwitchContracts;
|
|
39
|
+
}
|
|
40
|
+
export declare function createSwitch(options?: CreateSwitchOptions): SwitchModel;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { action, atom } from '@reatom/core';
|
|
2
|
+
const isSpaceKey = (key) => key === ' ' || key === 'Spacebar';
|
|
3
|
+
export function createSwitch(options = {}) {
|
|
4
|
+
const idBase = options.idBase ?? 'switch';
|
|
5
|
+
const isOnAtom = atom(options.isOn ?? false, `${idBase}.isOn`);
|
|
6
|
+
const isDisabledAtom = atom(options.isDisabled ?? false, `${idBase}.isDisabled`);
|
|
7
|
+
const setOn = action((value) => {
|
|
8
|
+
isOnAtom.set(value);
|
|
9
|
+
options.onCheckedChange?.(value);
|
|
10
|
+
}, `${idBase}.setOn`);
|
|
11
|
+
const setDisabled = action((value) => {
|
|
12
|
+
isDisabledAtom.set(value);
|
|
13
|
+
}, `${idBase}.setDisabled`);
|
|
14
|
+
const toggle = action(() => {
|
|
15
|
+
if (isDisabledAtom())
|
|
16
|
+
return;
|
|
17
|
+
setOn(!isOnAtom());
|
|
18
|
+
}, `${idBase}.toggle`);
|
|
19
|
+
const handleClick = action(() => {
|
|
20
|
+
toggle();
|
|
21
|
+
}, `${idBase}.handleClick`);
|
|
22
|
+
const handleKeyDown = action((event) => {
|
|
23
|
+
if (isDisabledAtom())
|
|
24
|
+
return;
|
|
25
|
+
if (event.key === 'Enter' || isSpaceKey(event.key)) {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
toggle();
|
|
28
|
+
}
|
|
29
|
+
}, `${idBase}.handleKeyDown`);
|
|
30
|
+
const actions = {
|
|
31
|
+
setOn,
|
|
32
|
+
setDisabled,
|
|
33
|
+
toggle,
|
|
34
|
+
handleClick,
|
|
35
|
+
handleKeyDown,
|
|
36
|
+
};
|
|
37
|
+
const contracts = {
|
|
38
|
+
getSwitchProps() {
|
|
39
|
+
return {
|
|
40
|
+
id: `${idBase}-root`,
|
|
41
|
+
role: 'switch',
|
|
42
|
+
tabindex: isDisabledAtom() ? '-1' : '0',
|
|
43
|
+
'aria-checked': isOnAtom() ? 'true' : 'false',
|
|
44
|
+
'aria-disabled': isDisabledAtom() ? 'true' : 'false',
|
|
45
|
+
'aria-labelledby': options.ariaLabelledBy,
|
|
46
|
+
'aria-describedby': options.ariaDescribedBy,
|
|
47
|
+
onClick: handleClick,
|
|
48
|
+
onKeyDown: handleKeyDown,
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const state = {
|
|
53
|
+
isOn: isOnAtom,
|
|
54
|
+
isDisabled: isDisabledAtom,
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
state,
|
|
58
|
+
actions,
|
|
59
|
+
contracts,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type Atom, type Computed } from '@reatom/core';
|
|
2
|
+
export type TableSortDirection = 'ascending' | 'descending' | 'none';
|
|
3
|
+
export interface TableColumn {
|
|
4
|
+
id: string;
|
|
5
|
+
index?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface TableRow {
|
|
8
|
+
id: string;
|
|
9
|
+
index?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface TableKeyboardEventLike {
|
|
12
|
+
key: string;
|
|
13
|
+
shiftKey?: boolean;
|
|
14
|
+
ctrlKey?: boolean;
|
|
15
|
+
metaKey?: boolean;
|
|
16
|
+
altKey?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface CreateTableOptions {
|
|
19
|
+
columns: readonly TableColumn[];
|
|
20
|
+
rows: readonly TableRow[];
|
|
21
|
+
totalColumnCount?: number;
|
|
22
|
+
totalRowCount?: number;
|
|
23
|
+
initialSortColumnId?: string | null;
|
|
24
|
+
initialSortDirection?: TableSortDirection;
|
|
25
|
+
ariaLabel?: string;
|
|
26
|
+
ariaLabelledBy?: string;
|
|
27
|
+
idBase?: string;
|
|
28
|
+
selectable?: 'single' | 'multi' | false;
|
|
29
|
+
initialSelectedRowIds?: readonly string[];
|
|
30
|
+
interactive?: boolean;
|
|
31
|
+
initialFocusedRowIndex?: number | null;
|
|
32
|
+
initialFocusedColumnIndex?: number | null;
|
|
33
|
+
pageSize?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface TableState {
|
|
36
|
+
rowCount: Computed<number>;
|
|
37
|
+
columnCount: Computed<number>;
|
|
38
|
+
sortColumnId: Atom<string | null>;
|
|
39
|
+
sortDirection: Atom<TableSortDirection>;
|
|
40
|
+
selectedRowIds: Atom<Set<string>>;
|
|
41
|
+
focusedRowIndex: Atom<number | null>;
|
|
42
|
+
focusedColumnIndex: Atom<number | null>;
|
|
43
|
+
selectable: 'single' | 'multi' | false;
|
|
44
|
+
interactive: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface TableActions {
|
|
47
|
+
sortBy(columnId: string, direction: TableSortDirection): void;
|
|
48
|
+
clearSort(): void;
|
|
49
|
+
selectRow(rowId: string): void;
|
|
50
|
+
deselectRow(rowId: string): void;
|
|
51
|
+
toggleRowSelection(rowId: string): void;
|
|
52
|
+
selectAllRows(): void;
|
|
53
|
+
clearSelection(): void;
|
|
54
|
+
moveFocus(direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
55
|
+
moveFocusToStart(): void;
|
|
56
|
+
moveFocusToEnd(): void;
|
|
57
|
+
moveFocusToRowStart(): void;
|
|
58
|
+
moveFocusToRowEnd(): void;
|
|
59
|
+
setFocusedCell(rowIndex: number, columnIndex: number): void;
|
|
60
|
+
pageUp(): void;
|
|
61
|
+
pageDown(): void;
|
|
62
|
+
handleKeyDown(event: TableKeyboardEventLike): void;
|
|
63
|
+
}
|
|
64
|
+
export interface TableProps {
|
|
65
|
+
id: string;
|
|
66
|
+
role: 'table' | 'grid';
|
|
67
|
+
'aria-label'?: string;
|
|
68
|
+
'aria-labelledby'?: string;
|
|
69
|
+
'aria-rowcount': number;
|
|
70
|
+
'aria-colcount': number;
|
|
71
|
+
'aria-multiselectable'?: 'true';
|
|
72
|
+
tabindex?: '0';
|
|
73
|
+
}
|
|
74
|
+
export interface TableRowProps {
|
|
75
|
+
id: string;
|
|
76
|
+
role: 'row';
|
|
77
|
+
'aria-rowindex': number;
|
|
78
|
+
'aria-selected'?: 'true' | 'false';
|
|
79
|
+
}
|
|
80
|
+
export interface TableCellProps {
|
|
81
|
+
id: string;
|
|
82
|
+
role: 'cell' | 'gridcell';
|
|
83
|
+
'aria-colindex': number;
|
|
84
|
+
'aria-colspan'?: number;
|
|
85
|
+
'aria-rowspan'?: number;
|
|
86
|
+
tabindex?: '0' | '-1';
|
|
87
|
+
'data-active'?: 'true' | 'false';
|
|
88
|
+
}
|
|
89
|
+
export interface TableColumnHeaderProps {
|
|
90
|
+
id: string;
|
|
91
|
+
role: 'columnheader';
|
|
92
|
+
'aria-colindex': number;
|
|
93
|
+
'aria-sort'?: TableSortDirection;
|
|
94
|
+
tabindex?: '0' | '-1';
|
|
95
|
+
}
|
|
96
|
+
export interface TableRowHeaderProps {
|
|
97
|
+
id: string;
|
|
98
|
+
role: 'rowheader';
|
|
99
|
+
'aria-rowindex': number;
|
|
100
|
+
'aria-colindex': number;
|
|
101
|
+
}
|
|
102
|
+
export interface TableContracts {
|
|
103
|
+
getTableProps(): TableProps;
|
|
104
|
+
getRowProps(rowId: string): TableRowProps;
|
|
105
|
+
getCellProps(rowId: string, columnId: string, span?: {
|
|
106
|
+
colspan?: number;
|
|
107
|
+
rowspan?: number;
|
|
108
|
+
}): TableCellProps;
|
|
109
|
+
getColumnHeaderProps(columnId: string): TableColumnHeaderProps;
|
|
110
|
+
getRowHeaderProps(rowId: string, columnId: string): TableRowHeaderProps;
|
|
111
|
+
}
|
|
112
|
+
export interface TableModel {
|
|
113
|
+
readonly state: TableState;
|
|
114
|
+
readonly actions: TableActions;
|
|
115
|
+
readonly contracts: TableContracts;
|
|
116
|
+
}
|
|
117
|
+
export declare function createTable(options: CreateTableOptions): TableModel;
|