@lab206/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 +114 -0
- package/README.md +6 -0
- package/dist/components/Accordion.d.ts +25 -0
- package/dist/components/Accordion.d.ts.map +1 -0
- package/dist/components/Accordion.js +123 -0
- package/dist/components/Accordion.js.map +1 -0
- package/dist/components/Alert.d.ts +17 -0
- package/dist/components/Alert.d.ts.map +1 -0
- package/dist/components/Alert.js +59 -0
- package/dist/components/Alert.js.map +1 -0
- package/dist/components/AspectRatio.d.ts +13 -0
- package/dist/components/AspectRatio.d.ts.map +1 -0
- package/dist/components/AspectRatio.js +38 -0
- package/dist/components/AspectRatio.js.map +1 -0
- package/dist/components/Avatar.d.ts +17 -0
- package/dist/components/Avatar.d.ts.map +1 -0
- package/dist/components/Avatar.js +57 -0
- package/dist/components/Avatar.js.map +1 -0
- package/dist/components/Badge.d.ts +12 -0
- package/dist/components/Badge.d.ts.map +1 -0
- package/dist/components/Badge.js +62 -0
- package/dist/components/Badge.js.map +1 -0
- package/dist/components/Breadcrumb.d.ts +16 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -0
- package/dist/components/Breadcrumb.js +76 -0
- package/dist/components/Breadcrumb.js.map +1 -0
- package/dist/components/Button.d.ts +28 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +110 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Card.d.ts +18 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +57 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/Checkbox.d.ts +23 -0
- package/dist/components/Checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox.js +107 -0
- package/dist/components/Checkbox.js.map +1 -0
- package/dist/components/Chip.d.ts +18 -0
- package/dist/components/Chip.d.ts.map +1 -0
- package/dist/components/Chip.js +103 -0
- package/dist/components/Chip.js.map +1 -0
- package/dist/components/Code.d.ts +12 -0
- package/dist/components/Code.d.ts.map +1 -0
- package/dist/components/Code.js +57 -0
- package/dist/components/Code.js.map +1 -0
- package/dist/components/Collapse.d.ts +15 -0
- package/dist/components/Collapse.d.ts.map +1 -0
- package/dist/components/Collapse.js +49 -0
- package/dist/components/Collapse.js.map +1 -0
- package/dist/components/Combobox.d.ts +47 -0
- package/dist/components/Combobox.d.ts.map +1 -0
- package/dist/components/Combobox.js +325 -0
- package/dist/components/Combobox.js.map +1 -0
- package/dist/components/Command.d.ts +42 -0
- package/dist/components/Command.d.ts.map +1 -0
- package/dist/components/Command.js +263 -0
- package/dist/components/Command.js.map +1 -0
- package/dist/components/Container.d.ts +11 -0
- package/dist/components/Container.d.ts.map +1 -0
- package/dist/components/Container.js +38 -0
- package/dist/components/Container.js.map +1 -0
- package/dist/components/Dialog.d.ts +26 -0
- package/dist/components/Dialog.d.ts.map +1 -0
- package/dist/components/Dialog.js +159 -0
- package/dist/components/Dialog.js.map +1 -0
- package/dist/components/Divider.d.ts +10 -0
- package/dist/components/Divider.d.ts.map +1 -0
- package/dist/components/Divider.js +52 -0
- package/dist/components/Divider.js.map +1 -0
- package/dist/components/Drawer.d.ts +19 -0
- package/dist/components/Drawer.d.ts.map +1 -0
- package/dist/components/Drawer.js +131 -0
- package/dist/components/Drawer.js.map +1 -0
- package/dist/components/Empty.d.ts +17 -0
- package/dist/components/Empty.d.ts.map +1 -0
- package/dist/components/Empty.js +51 -0
- package/dist/components/Empty.js.map +1 -0
- package/dist/components/Field.d.ts +27 -0
- package/dist/components/Field.d.ts.map +1 -0
- package/dist/components/Field.js +153 -0
- package/dist/components/Field.js.map +1 -0
- package/dist/components/Grid.d.ts +13 -0
- package/dist/components/Grid.d.ts.map +1 -0
- package/dist/components/Grid.js +44 -0
- package/dist/components/Grid.js.map +1 -0
- package/dist/components/Input.d.ts +55 -0
- package/dist/components/Input.d.ts.map +1 -0
- package/dist/components/Input.js +113 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/Kbd.d.ts +10 -0
- package/dist/components/Kbd.d.ts.map +1 -0
- package/dist/components/Kbd.js +41 -0
- package/dist/components/Kbd.js.map +1 -0
- package/dist/components/Label.d.ts +13 -0
- package/dist/components/Label.d.ts.map +1 -0
- package/dist/components/Label.js +34 -0
- package/dist/components/Label.js.map +1 -0
- package/dist/components/Link.d.ts +18 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +41 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/components/List.d.ts +22 -0
- package/dist/components/List.d.ts.map +1 -0
- package/dist/components/List.js +157 -0
- package/dist/components/List.js.map +1 -0
- package/dist/components/Menu.d.ts +28 -0
- package/dist/components/Menu.d.ts.map +1 -0
- package/dist/components/Menu.js +126 -0
- package/dist/components/Menu.js.map +1 -0
- package/dist/components/NumberInput.d.ts +27 -0
- package/dist/components/NumberInput.d.ts.map +1 -0
- package/dist/components/NumberInput.js +92 -0
- package/dist/components/NumberInput.js.map +1 -0
- package/dist/components/Pagination.d.ts +13 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +104 -0
- package/dist/components/Pagination.js.map +1 -0
- package/dist/components/Popover.d.ts +26 -0
- package/dist/components/Popover.d.ts.map +1 -0
- package/dist/components/Popover.js +165 -0
- package/dist/components/Popover.js.map +1 -0
- package/dist/components/Progress.d.ts +15 -0
- package/dist/components/Progress.d.ts.map +1 -0
- package/dist/components/Progress.js +63 -0
- package/dist/components/Progress.js.map +1 -0
- package/dist/components/Radio.d.ts +25 -0
- package/dist/components/Radio.d.ts.map +1 -0
- package/dist/components/Radio.js +95 -0
- package/dist/components/Radio.js.map +1 -0
- package/dist/components/ScrollArea.d.ts +13 -0
- package/dist/components/ScrollArea.d.ts.map +1 -0
- package/dist/components/ScrollArea.js +33 -0
- package/dist/components/ScrollArea.js.map +1 -0
- package/dist/components/Select.d.ts +34 -0
- package/dist/components/Select.d.ts.map +1 -0
- package/dist/components/Select.js +108 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/Skeleton.d.ts +18 -0
- package/dist/components/Skeleton.d.ts.map +1 -0
- package/dist/components/Skeleton.js +86 -0
- package/dist/components/Skeleton.js.map +1 -0
- package/dist/components/Slider.d.ts +26 -0
- package/dist/components/Slider.d.ts.map +1 -0
- package/dist/components/Slider.js +113 -0
- package/dist/components/Slider.js.map +1 -0
- package/dist/components/Spinner.d.ts +12 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +44 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/Stack.d.ts +21 -0
- package/dist/components/Stack.d.ts.map +1 -0
- package/dist/components/Stack.js +48 -0
- package/dist/components/Stack.js.map +1 -0
- package/dist/components/Stat.d.ts +23 -0
- package/dist/components/Stat.d.ts.map +1 -0
- package/dist/components/Stat.js +115 -0
- package/dist/components/Stat.js.map +1 -0
- package/dist/components/Steps.d.ts +20 -0
- package/dist/components/Steps.d.ts.map +1 -0
- package/dist/components/Steps.js +126 -0
- package/dist/components/Steps.js.map +1 -0
- package/dist/components/Switch.d.ts +23 -0
- package/dist/components/Switch.d.ts.map +1 -0
- package/dist/components/Switch.js +108 -0
- package/dist/components/Switch.js.map +1 -0
- package/dist/components/Table.d.ts +27 -0
- package/dist/components/Table.d.ts.map +1 -0
- package/dist/components/Table.js +112 -0
- package/dist/components/Table.js.map +1 -0
- package/dist/components/Tabs.d.ts +27 -0
- package/dist/components/Tabs.d.ts.map +1 -0
- package/dist/components/Tabs.js +171 -0
- package/dist/components/Tabs.js.map +1 -0
- package/dist/components/Text.d.ts +17 -0
- package/dist/components/Text.d.ts.map +1 -0
- package/dist/components/Text.js +48 -0
- package/dist/components/Text.js.map +1 -0
- package/dist/components/Textarea.d.ts +32 -0
- package/dist/components/Textarea.d.ts.map +1 -0
- package/dist/components/Textarea.js +91 -0
- package/dist/components/Textarea.js.map +1 -0
- package/dist/components/Timeline.d.ts +17 -0
- package/dist/components/Timeline.d.ts.map +1 -0
- package/dist/components/Timeline.js +95 -0
- package/dist/components/Timeline.js.map +1 -0
- package/dist/components/Toast.d.ts +36 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +143 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/ToggleGroup.d.ts +25 -0
- package/dist/components/ToggleGroup.d.ts.map +1 -0
- package/dist/components/ToggleGroup.js +103 -0
- package/dist/components/ToggleGroup.js.map +1 -0
- package/dist/components/Tooltip.d.ts +19 -0
- package/dist/components/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip.js +141 -0
- package/dist/components/Tooltip.js.map +1 -0
- package/dist/components/Transition.d.ts +22 -0
- package/dist/components/Transition.d.ts.map +1 -0
- package/dist/components/Transition.js +139 -0
- package/dist/components/Transition.js.map +1 -0
- package/dist/density.d.ts +21 -0
- package/dist/density.d.ts.map +1 -0
- package/dist/density.js +31 -0
- package/dist/density.js.map +1 -0
- package/dist/dev.d.ts +27 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +77 -0
- package/dist/dev.js.map +1 -0
- package/dist/focusTrap.d.ts +6 -0
- package/dist/focusTrap.d.ts.map +1 -0
- package/dist/focusTrap.js +55 -0
- package/dist/focusTrap.js.map +1 -0
- package/dist/form.d.ts +125 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +192 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/motion.d.ts +29 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +43 -0
- package/dist/motion.js.map +1 -0
- package/dist/overlay.d.ts +63 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +172 -0
- package/dist/overlay.js.map +1 -0
- package/dist/palette.d.ts +20 -0
- package/dist/palette.d.ts.map +1 -0
- package/dist/palette.js +52 -0
- package/dist/palette.js.map +1 -0
- package/dist/reactive.d.ts +25 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +43 -0
- package/dist/reactive.js.map +1 -0
- package/dist/rovingFocus.d.ts +36 -0
- package/dist/rovingFocus.d.ts.map +1 -0
- package/dist/rovingFocus.js +127 -0
- package/dist/rovingFocus.js.map +1 -0
- package/dist/size-entries/form-kit.d.ts +12 -0
- package/dist/size-entries/form-kit.d.ts.map +1 -0
- package/dist/size-entries/form-kit.js +12 -0
- package/dist/size-entries/form-kit.js.map +1 -0
- package/dist/styles.d.ts +29 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +53 -0
- package/dist/styles.js.map +1 -0
- package/dist/theme.d.ts +22 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +32 -0
- package/dist/theme.js.map +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +24 -0
- package/dist/utils.js.map +1 -0
- package/package.json +71 -0
- package/src/components/Accordion.tsx +194 -0
- package/src/components/Alert.tsx +88 -0
- package/src/components/AspectRatio.tsx +64 -0
- package/src/components/Avatar.tsx +89 -0
- package/src/components/Badge.tsx +84 -0
- package/src/components/Breadcrumb.tsx +116 -0
- package/src/components/Button.tsx +148 -0
- package/src/components/Card.tsx +92 -0
- package/src/components/Checkbox.tsx +143 -0
- package/src/components/Chip.tsx +153 -0
- package/src/components/Code.tsx +71 -0
- package/src/components/Collapse.tsx +76 -0
- package/src/components/Combobox.tsx +438 -0
- package/src/components/Command.tsx +380 -0
- package/src/components/Container.tsx +58 -0
- package/src/components/Dialog.tsx +226 -0
- package/src/components/Divider.tsx +72 -0
- package/src/components/Drawer.tsx +191 -0
- package/src/components/Empty.tsx +86 -0
- package/src/components/Field.tsx +184 -0
- package/src/components/Grid.tsx +67 -0
- package/src/components/Input.tsx +169 -0
- package/src/components/Kbd.tsx +57 -0
- package/src/components/Label.tsx +54 -0
- package/src/components/Link.tsx +87 -0
- package/src/components/List.tsx +236 -0
- package/src/components/Menu.tsx +195 -0
- package/src/components/NumberInput.tsx +158 -0
- package/src/components/Pagination.tsx +153 -0
- package/src/components/Popover.tsx +219 -0
- package/src/components/Progress.tsx +108 -0
- package/src/components/Radio.tsx +161 -0
- package/src/components/ScrollArea.tsx +58 -0
- package/src/components/Select.tsx +128 -0
- package/src/components/Skeleton.tsx +117 -0
- package/src/components/Slider.tsx +175 -0
- package/src/components/Spinner.tsx +70 -0
- package/src/components/Stack.tsx +79 -0
- package/src/components/Stat.tsx +169 -0
- package/src/components/Steps.tsx +191 -0
- package/src/components/Switch.tsx +146 -0
- package/src/components/Table.tsx +168 -0
- package/src/components/Tabs.tsx +246 -0
- package/src/components/Text.tsx +69 -0
- package/src/components/Textarea.tsx +128 -0
- package/src/components/Timeline.tsx +150 -0
- package/src/components/Toast.tsx +221 -0
- package/src/components/ToggleGroup.tsx +161 -0
- package/src/components/Tooltip.tsx +187 -0
- package/src/components/Transition.tsx +155 -0
- package/src/density.ts +46 -0
- package/src/dev.ts +87 -0
- package/src/focusTrap.ts +61 -0
- package/src/form.ts +269 -0
- package/src/index.ts +243 -0
- package/src/motion.ts +62 -0
- package/src/overlay.ts +232 -0
- package/src/palette.ts +70 -0
- package/src/reactive.ts +54 -0
- package/src/rovingFocus.ts +171 -0
- package/src/size-entries/form-kit.ts +11 -0
- package/src/styles/base.css +70 -0
- package/src/styles/theme.css +14 -0
- package/src/styles/tokens.css +377 -0
- package/src/styles/utilities.css +216 -0
- package/src/styles.ts +52 -0
- package/src/theme.ts +48 -0
- package/src/utils.ts +25 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @lab206/ui — integrated design system (styling + components)
|
|
3
|
+
*
|
|
4
|
+
* Layers: tokens → primitives → optional utilities (see docs/STYLING.md)
|
|
5
|
+
*
|
|
6
|
+
* 1. import "@lab206/ui/theme.css" // tokens + base + utilities
|
|
7
|
+
* 2. createTheme().bind(); createDensity().bind();
|
|
8
|
+
* 3. Use Button, Field, Stack… (prefer primitives over raw utilities)
|
|
9
|
+
* 4. Forms: <Input bind={signal} /> + Field error={…} — see docs/USABILITY.md
|
|
10
|
+
*
|
|
11
|
+
* Edit brand in: packages/ui/src/styles/tokens.css
|
|
12
|
+
* Author new primitives: createStyleSheet + component() — see docs/COMPONENTS.md
|
|
13
|
+
* Public docs hub: docs/README.md
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export { createTheme } from "./theme.js";
|
|
17
|
+
export type { ThemeMode, ThemeController } from "./theme.js";
|
|
18
|
+
|
|
19
|
+
export { createDensity } from "./density.js";
|
|
20
|
+
export type { Density, DensityController } from "./density.js";
|
|
21
|
+
|
|
22
|
+
export { createPalette } from "./palette.js";
|
|
23
|
+
export type { PaletteId, PaletteController } from "./palette.js";
|
|
24
|
+
|
|
25
|
+
export { cx, puId } from "./utils.js";
|
|
26
|
+
|
|
27
|
+
export { createStyleSheet, styleVars } from "./styles.js";
|
|
28
|
+
export {
|
|
29
|
+
readProp,
|
|
30
|
+
readBool,
|
|
31
|
+
readStr,
|
|
32
|
+
readNum,
|
|
33
|
+
type MaybeReactive,
|
|
34
|
+
} from "./reactive.js";
|
|
35
|
+
export { trapFocus } from "./focusTrap.js";
|
|
36
|
+
export { attachOverlay } from "./overlay.js";
|
|
37
|
+
export type { OverlayAttachOptions, OverlayContext } from "./overlay.js";
|
|
38
|
+
export {
|
|
39
|
+
listRovingItems,
|
|
40
|
+
applyRovingTabIndex,
|
|
41
|
+
focusRovingItem,
|
|
42
|
+
handleRovingKeydown,
|
|
43
|
+
initRovingFocus,
|
|
44
|
+
} from "./rovingFocus.js";
|
|
45
|
+
export type { RovingKeyOptions } from "./rovingFocus.js";
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
firstError,
|
|
49
|
+
required,
|
|
50
|
+
minLength,
|
|
51
|
+
maxLength,
|
|
52
|
+
emailFormat,
|
|
53
|
+
matches,
|
|
54
|
+
validateForm,
|
|
55
|
+
eventValue,
|
|
56
|
+
eventChecked,
|
|
57
|
+
bindInput,
|
|
58
|
+
bindString,
|
|
59
|
+
bindSelect,
|
|
60
|
+
bindChecked,
|
|
61
|
+
asSelectBind,
|
|
62
|
+
createField,
|
|
63
|
+
} from "./form.js";
|
|
64
|
+
export type {
|
|
65
|
+
FieldError,
|
|
66
|
+
Bindable,
|
|
67
|
+
FieldHandle,
|
|
68
|
+
CreateFieldOptions,
|
|
69
|
+
} from "./form.js";
|
|
70
|
+
|
|
71
|
+
export { MOTION_PRESETS, motionVars } from "./motion.js";
|
|
72
|
+
export type { MotionPreset, MotionPresetName } from "./motion.js";
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
devWarnOnce,
|
|
76
|
+
warnIfThemeMissing,
|
|
77
|
+
warnPossibleSnapshotValue,
|
|
78
|
+
setDevWarnings,
|
|
79
|
+
installDevWarnings,
|
|
80
|
+
} from "./dev.js";
|
|
81
|
+
|
|
82
|
+
// —— Layout ——
|
|
83
|
+
export { Stack } from "./components/Stack.js";
|
|
84
|
+
export type { StackProps } from "./components/Stack.js";
|
|
85
|
+
|
|
86
|
+
export { Grid } from "./components/Grid.js";
|
|
87
|
+
export type { GridProps } from "./components/Grid.js";
|
|
88
|
+
|
|
89
|
+
export { Container } from "./components/Container.js";
|
|
90
|
+
export type { ContainerProps } from "./components/Container.js";
|
|
91
|
+
|
|
92
|
+
export { Divider } from "./components/Divider.js";
|
|
93
|
+
export type { DividerProps } from "./components/Divider.js";
|
|
94
|
+
|
|
95
|
+
export { AspectRatio } from "./components/AspectRatio.js";
|
|
96
|
+
export type { AspectRatioProps } from "./components/AspectRatio.js";
|
|
97
|
+
|
|
98
|
+
export { ScrollArea } from "./components/ScrollArea.js";
|
|
99
|
+
export type { ScrollAreaProps } from "./components/ScrollArea.js";
|
|
100
|
+
|
|
101
|
+
export { Collapse } from "./components/Collapse.js";
|
|
102
|
+
export type { CollapseProps } from "./components/Collapse.js";
|
|
103
|
+
|
|
104
|
+
// —— Type ——
|
|
105
|
+
export { Text } from "./components/Text.js";
|
|
106
|
+
export type { TextProps } from "./components/Text.js";
|
|
107
|
+
|
|
108
|
+
export { Code } from "./components/Code.js";
|
|
109
|
+
export type { CodeProps } from "./components/Code.js";
|
|
110
|
+
|
|
111
|
+
export { Kbd } from "./components/Kbd.js";
|
|
112
|
+
export type { KbdProps } from "./components/Kbd.js";
|
|
113
|
+
|
|
114
|
+
export { Link } from "./components/Link.js";
|
|
115
|
+
export type { LinkProps } from "./components/Link.js";
|
|
116
|
+
|
|
117
|
+
// —— Forms ——
|
|
118
|
+
export { Button } from "./components/Button.js";
|
|
119
|
+
export type { ButtonProps, ButtonVariant, ButtonSize } from "./components/Button.js";
|
|
120
|
+
|
|
121
|
+
export { Input } from "./components/Input.js";
|
|
122
|
+
export type { InputProps } from "./components/Input.js";
|
|
123
|
+
|
|
124
|
+
export { Textarea } from "./components/Textarea.js";
|
|
125
|
+
export type { TextareaProps } from "./components/Textarea.js";
|
|
126
|
+
|
|
127
|
+
export { Select } from "./components/Select.js";
|
|
128
|
+
export type { SelectProps, SelectOption } from "./components/Select.js";
|
|
129
|
+
|
|
130
|
+
export { Label } from "./components/Label.js";
|
|
131
|
+
export type { LabelProps } from "./components/Label.js";
|
|
132
|
+
|
|
133
|
+
export { Field } from "./components/Field.js";
|
|
134
|
+
export type { FieldProps } from "./components/Field.js";
|
|
135
|
+
|
|
136
|
+
export { Switch } from "./components/Switch.js";
|
|
137
|
+
export type { SwitchProps } from "./components/Switch.js";
|
|
138
|
+
|
|
139
|
+
export { Checkbox } from "./components/Checkbox.js";
|
|
140
|
+
export type { CheckboxProps } from "./components/Checkbox.js";
|
|
141
|
+
|
|
142
|
+
export { RadioGroup } from "./components/Radio.js";
|
|
143
|
+
export type { RadioGroupProps, RadioOption } from "./components/Radio.js";
|
|
144
|
+
|
|
145
|
+
export { Slider } from "./components/Slider.js";
|
|
146
|
+
export type { SliderProps } from "./components/Slider.js";
|
|
147
|
+
|
|
148
|
+
export { NumberInput } from "./components/NumberInput.js";
|
|
149
|
+
export type { NumberInputProps } from "./components/NumberInput.js";
|
|
150
|
+
|
|
151
|
+
export { ToggleGroup } from "./components/ToggleGroup.js";
|
|
152
|
+
export type { ToggleGroupProps, ToggleOption } from "./components/ToggleGroup.js";
|
|
153
|
+
|
|
154
|
+
export { Combobox } from "./components/Combobox.js";
|
|
155
|
+
export type { ComboboxProps, ComboboxOption } from "./components/Combobox.js";
|
|
156
|
+
|
|
157
|
+
// —— Surfaces & chrome ——
|
|
158
|
+
export { Card } from "./components/Card.js";
|
|
159
|
+
export type { CardProps } from "./components/Card.js";
|
|
160
|
+
|
|
161
|
+
export { Badge } from "./components/Badge.js";
|
|
162
|
+
export type { BadgeProps } from "./components/Badge.js";
|
|
163
|
+
|
|
164
|
+
export { Chip } from "./components/Chip.js";
|
|
165
|
+
export type { ChipProps, ChipTone } from "./components/Chip.js";
|
|
166
|
+
|
|
167
|
+
export { Avatar } from "./components/Avatar.js";
|
|
168
|
+
export type { AvatarProps, AvatarSize } from "./components/Avatar.js";
|
|
169
|
+
|
|
170
|
+
// —— Feedback ——
|
|
171
|
+
export { Alert } from "./components/Alert.js";
|
|
172
|
+
export type { AlertProps, AlertTone } from "./components/Alert.js";
|
|
173
|
+
|
|
174
|
+
export { Spinner } from "./components/Spinner.js";
|
|
175
|
+
export type { SpinnerProps, SpinnerSize } from "./components/Spinner.js";
|
|
176
|
+
|
|
177
|
+
export { Progress } from "./components/Progress.js";
|
|
178
|
+
export type { ProgressProps } from "./components/Progress.js";
|
|
179
|
+
|
|
180
|
+
export { Skeleton } from "./components/Skeleton.js";
|
|
181
|
+
export type { SkeletonProps } from "./components/Skeleton.js";
|
|
182
|
+
|
|
183
|
+
export { Empty } from "./components/Empty.js";
|
|
184
|
+
export type { EmptyProps } from "./components/Empty.js";
|
|
185
|
+
|
|
186
|
+
export { Stat } from "./components/Stat.js";
|
|
187
|
+
export type { StatProps } from "./components/Stat.js";
|
|
188
|
+
|
|
189
|
+
export { Toaster, createToaster } from "./components/Toast.js";
|
|
190
|
+
export type {
|
|
191
|
+
ToasterProps,
|
|
192
|
+
ToastController,
|
|
193
|
+
ToastItem,
|
|
194
|
+
ToastTone,
|
|
195
|
+
} from "./components/Toast.js";
|
|
196
|
+
|
|
197
|
+
// —— Navigation & structure ——
|
|
198
|
+
export { Tabs } from "./components/Tabs.js";
|
|
199
|
+
export type { TabsProps, TabItem } from "./components/Tabs.js";
|
|
200
|
+
|
|
201
|
+
export { Accordion } from "./components/Accordion.js";
|
|
202
|
+
export type { AccordionProps, AccordionItem } from "./components/Accordion.js";
|
|
203
|
+
|
|
204
|
+
export { Breadcrumb } from "./components/Breadcrumb.js";
|
|
205
|
+
export type { BreadcrumbProps, BreadcrumbItem } from "./components/Breadcrumb.js";
|
|
206
|
+
|
|
207
|
+
export { Pagination } from "./components/Pagination.js";
|
|
208
|
+
export type { PaginationProps } from "./components/Pagination.js";
|
|
209
|
+
|
|
210
|
+
export { Steps } from "./components/Steps.js";
|
|
211
|
+
export type { StepsProps, StepItem } from "./components/Steps.js";
|
|
212
|
+
|
|
213
|
+
export { Timeline } from "./components/Timeline.js";
|
|
214
|
+
export type { TimelineProps, TimelineItem } from "./components/Timeline.js";
|
|
215
|
+
|
|
216
|
+
export { List } from "./components/List.js";
|
|
217
|
+
export type { ListProps, ListItemData } from "./components/List.js";
|
|
218
|
+
|
|
219
|
+
export { Table } from "./components/Table.js";
|
|
220
|
+
export type { TableProps, TableColumn } from "./components/Table.js";
|
|
221
|
+
|
|
222
|
+
// —— Overlays ——
|
|
223
|
+
export { Dialog } from "./components/Dialog.js";
|
|
224
|
+
export type { DialogProps } from "./components/Dialog.js";
|
|
225
|
+
|
|
226
|
+
export { Drawer } from "./components/Drawer.js";
|
|
227
|
+
export type { DrawerProps } from "./components/Drawer.js";
|
|
228
|
+
|
|
229
|
+
export { Tooltip } from "./components/Tooltip.js";
|
|
230
|
+
export type { TooltipProps } from "./components/Tooltip.js";
|
|
231
|
+
|
|
232
|
+
export { Popover } from "./components/Popover.js";
|
|
233
|
+
export type { PopoverProps } from "./components/Popover.js";
|
|
234
|
+
|
|
235
|
+
export { Menu } from "./components/Menu.js";
|
|
236
|
+
export type { MenuProps, MenuItem } from "./components/Menu.js";
|
|
237
|
+
|
|
238
|
+
export { Command } from "./components/Command.js";
|
|
239
|
+
export type { CommandProps, CommandItem } from "./components/Command.js";
|
|
240
|
+
|
|
241
|
+
// —— Motion ——
|
|
242
|
+
export { Transition } from "./components/Transition.js";
|
|
243
|
+
export type { TransitionProps } from "./components/Transition.js";
|
package/src/motion.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Motion language — named enter/exit class presets for Transition / CSS.
|
|
3
|
+
*
|
|
4
|
+
* Use with `<Transition name="pu-fade" show={…}>` or apply classes yourself.
|
|
5
|
+
* All presets respect `prefers-reduced-motion` via Transition's built-in CSS
|
|
6
|
+
* and global base.css rules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type MotionPreset = {
|
|
10
|
+
/** Class prefix for Transition `name` prop */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Human label */
|
|
13
|
+
label: string;
|
|
14
|
+
/** When to use */
|
|
15
|
+
use: string;
|
|
16
|
+
/** Suggested duration ms */
|
|
17
|
+
duration: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Built-in Transition names shipped in Transition.tsx styles. */
|
|
21
|
+
export const MOTION_PRESETS: readonly MotionPreset[] = [
|
|
22
|
+
{
|
|
23
|
+
name: "pu-fade",
|
|
24
|
+
label: "Fade",
|
|
25
|
+
use: "Default enter/exit for overlays, panels, soft content swaps",
|
|
26
|
+
duration: 220,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "pu-collapse",
|
|
30
|
+
label: "Collapse",
|
|
31
|
+
use: "Accordion-like height expand/collapse (max-height)",
|
|
32
|
+
duration: 360,
|
|
33
|
+
},
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
export type MotionPresetName = (typeof MOTION_PRESETS)[number]["name"];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* CSS custom-property recipe for one-off motion on a node
|
|
40
|
+
* (pair with your own transition: rules).
|
|
41
|
+
*/
|
|
42
|
+
export function motionVars(opts: {
|
|
43
|
+
duration?: string | number;
|
|
44
|
+
ease?: "out" | "spring" | "default";
|
|
45
|
+
}): Record<string, string> {
|
|
46
|
+
const duration =
|
|
47
|
+
opts.duration == null
|
|
48
|
+
? "var(--pu-duration)"
|
|
49
|
+
: typeof opts.duration === "number"
|
|
50
|
+
? `${opts.duration}ms`
|
|
51
|
+
: opts.duration;
|
|
52
|
+
const ease =
|
|
53
|
+
opts.ease === "spring"
|
|
54
|
+
? "var(--pu-ease-spring)"
|
|
55
|
+
: opts.ease === "out"
|
|
56
|
+
? "var(--pu-ease-out)"
|
|
57
|
+
: "var(--pu-ease)";
|
|
58
|
+
return {
|
|
59
|
+
"--pu-motion-duration": duration,
|
|
60
|
+
"--pu-motion-ease": ease,
|
|
61
|
+
};
|
|
62
|
+
}
|
package/src/overlay.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { trapFocus } from "./focusTrap.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared overlay runtime for Dialog · Drawer · Popover · Command · Menu.
|
|
5
|
+
*
|
|
6
|
+
* Goals (FOUNDATION week 2):
|
|
7
|
+
* - One place for Escape, body scroll lock, focus trap, outside dismiss
|
|
8
|
+
* - Stacked layers: only the topmost Escape / outside-dismiss handler fires
|
|
9
|
+
* - Scroll lock is ref-counted (two dialogs open → one unlock when both close)
|
|
10
|
+
* - ownerDocument-aware (Lab iframes)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type OverlayContext = {
|
|
14
|
+
doc: Document;
|
|
15
|
+
win: Window & typeof globalThis;
|
|
16
|
+
root: HTMLElement | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type OverlayAttachOptions = {
|
|
20
|
+
/** Overlay root (for document + outside-click containment). */
|
|
21
|
+
getRoot: () => HTMLElement | null;
|
|
22
|
+
/** Panel to focus-trap; omit for non-modal popovers/menus. */
|
|
23
|
+
getFocusRoot?: () => HTMLElement | null;
|
|
24
|
+
/** Close / dismiss callback. */
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
/** Lock document.body scroll while open. Default false. */
|
|
27
|
+
scrollLock?: boolean;
|
|
28
|
+
/** Escape closes (topmost only). Default true. */
|
|
29
|
+
escape?: boolean;
|
|
30
|
+
/** Pointer down outside `getRoot()` closes (topmost only). Default false. */
|
|
31
|
+
dismissOutside?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Optional override for “inside” detection (portaled lists, multi-node roots).
|
|
34
|
+
* When omitted, uses `getRoot()?.contains(node)`.
|
|
35
|
+
*/
|
|
36
|
+
isInside?: (node: Node) => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Run after deferred attach (refs ready). Return optional extra cleanup
|
|
39
|
+
* (e.g. Popover reposition on scroll/resize).
|
|
40
|
+
*/
|
|
41
|
+
onAttach?: (ctx: OverlayContext) => (() => void) | void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type Layer = {
|
|
45
|
+
id: number;
|
|
46
|
+
doc: Document;
|
|
47
|
+
onClose: () => void;
|
|
48
|
+
escape: boolean;
|
|
49
|
+
dismissOutside: boolean;
|
|
50
|
+
/** Root for outside-click tests */
|
|
51
|
+
getRoot: () => HTMLElement | null;
|
|
52
|
+
isInside?: (node: Node) => boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
let nextLayerId = 1;
|
|
56
|
+
const layers: Layer[] = [];
|
|
57
|
+
|
|
58
|
+
function topLayer(
|
|
59
|
+
doc: Document,
|
|
60
|
+
pred: (l: Layer) => boolean,
|
|
61
|
+
): Layer | undefined {
|
|
62
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
63
|
+
const l = layers[i]!;
|
|
64
|
+
if (l.doc === doc && pred(l)) return l;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function pushLayer(layer: Layer): () => void {
|
|
70
|
+
layers.push(layer);
|
|
71
|
+
return () => {
|
|
72
|
+
const i = layers.findIndex((l) => l.id === layer.id);
|
|
73
|
+
if (i >= 0) layers.splice(i, 1);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Per-document scroll lock refcount. */
|
|
78
|
+
const scrollLocks = new WeakMap<Document, { count: number; prev: string }>();
|
|
79
|
+
|
|
80
|
+
function lockBodyScroll(doc: Document): () => void {
|
|
81
|
+
let entry = scrollLocks.get(doc);
|
|
82
|
+
if (!entry) {
|
|
83
|
+
entry = { count: 0, prev: doc.body.style.overflow };
|
|
84
|
+
scrollLocks.set(doc, entry);
|
|
85
|
+
}
|
|
86
|
+
if (entry.count === 0) {
|
|
87
|
+
entry.prev = doc.body.style.overflow;
|
|
88
|
+
doc.body.style.overflow = "hidden";
|
|
89
|
+
}
|
|
90
|
+
entry.count++;
|
|
91
|
+
return () => {
|
|
92
|
+
const e = scrollLocks.get(doc);
|
|
93
|
+
if (!e) return;
|
|
94
|
+
e.count--;
|
|
95
|
+
if (e.count <= 0) {
|
|
96
|
+
e.count = 0;
|
|
97
|
+
doc.body.style.overflow = e.prev;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Attach overlay behaviors while open. Call from an `effect` when open is true;
|
|
104
|
+
* return the dispose as the effect cleanup.
|
|
105
|
+
*
|
|
106
|
+
* Attach is deferred one macrotask so `ref` callbacks have run.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* effect(() => {
|
|
111
|
+
* if (!isOpen()) return;
|
|
112
|
+
* return attachOverlay({
|
|
113
|
+
* getRoot: () => rootEl,
|
|
114
|
+
* getFocusRoot: () => panelEl,
|
|
115
|
+
* onClose: () => props.onClose?.(),
|
|
116
|
+
* scrollLock: true,
|
|
117
|
+
* });
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export function attachOverlay(options: OverlayAttachOptions): () => void {
|
|
122
|
+
const escape = options.escape !== false;
|
|
123
|
+
const dismissOutside = !!options.dismissOutside;
|
|
124
|
+
const scrollLock = !!options.scrollLock;
|
|
125
|
+
|
|
126
|
+
let disposed = false;
|
|
127
|
+
const cleanups: Array<() => void> = [];
|
|
128
|
+
|
|
129
|
+
const attach = () => {
|
|
130
|
+
if (disposed) return;
|
|
131
|
+
|
|
132
|
+
const root = options.getRoot();
|
|
133
|
+
const doc = root?.ownerDocument ?? document;
|
|
134
|
+
const win = (doc.defaultView ?? window) as Window & typeof globalThis;
|
|
135
|
+
const focusRoot = options.getFocusRoot?.() ?? null;
|
|
136
|
+
|
|
137
|
+
const layer: Layer = {
|
|
138
|
+
id: nextLayerId++,
|
|
139
|
+
doc,
|
|
140
|
+
onClose: options.onClose,
|
|
141
|
+
escape,
|
|
142
|
+
dismissOutside,
|
|
143
|
+
getRoot: options.getRoot,
|
|
144
|
+
...(options.isInside ? { isInside: options.isInside } : {}),
|
|
145
|
+
};
|
|
146
|
+
cleanups.push(pushLayer(layer));
|
|
147
|
+
|
|
148
|
+
if (scrollLock) {
|
|
149
|
+
cleanups.push(lockBodyScroll(doc));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (escape) {
|
|
153
|
+
const onKey = (e: KeyboardEvent) => {
|
|
154
|
+
if (e.key !== "Escape") return;
|
|
155
|
+
const top = topLayer(doc, (l) => l.escape);
|
|
156
|
+
if (!top || top.id !== layer.id) return;
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
e.stopPropagation();
|
|
159
|
+
options.onClose();
|
|
160
|
+
};
|
|
161
|
+
win.addEventListener("keydown", onKey, true);
|
|
162
|
+
cleanups.push(() => win.removeEventListener("keydown", onKey, true));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (dismissOutside) {
|
|
166
|
+
const contains = (node: Node) => {
|
|
167
|
+
if (options.isInside) return options.isInside(node);
|
|
168
|
+
const r = options.getRoot();
|
|
169
|
+
return !!(r && r.contains(node));
|
|
170
|
+
};
|
|
171
|
+
const onPointer = (e: Event) => {
|
|
172
|
+
const top = topLayer(doc, (l) => l.dismissOutside);
|
|
173
|
+
if (!top || top.id !== layer.id) return;
|
|
174
|
+
const t = e.target as Node | null;
|
|
175
|
+
if (!t) return;
|
|
176
|
+
if (contains(t)) return;
|
|
177
|
+
options.onClose();
|
|
178
|
+
};
|
|
179
|
+
// Skip the opening interaction
|
|
180
|
+
const skipTimer = win.setTimeout(() => {
|
|
181
|
+
if (disposed) return;
|
|
182
|
+
doc.addEventListener("pointerdown", onPointer, true);
|
|
183
|
+
}, 0);
|
|
184
|
+
cleanups.push(() => {
|
|
185
|
+
win.clearTimeout(skipTimer);
|
|
186
|
+
doc.removeEventListener("pointerdown", onPointer, true);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (focusRoot) {
|
|
191
|
+
cleanups.push(trapFocus(focusRoot));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const extra = options.onAttach?.({ doc, win, root });
|
|
195
|
+
if (typeof extra === "function") cleanups.push(extra);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const win =
|
|
199
|
+
typeof window !== "undefined"
|
|
200
|
+
? window
|
|
201
|
+
: (undefined as unknown as Window | undefined);
|
|
202
|
+
const timer = win?.setTimeout(attach, 0);
|
|
203
|
+
if (timer != null && win) {
|
|
204
|
+
cleanups.push(() => win.clearTimeout(timer));
|
|
205
|
+
} else {
|
|
206
|
+
attach();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return () => {
|
|
210
|
+
disposed = true;
|
|
211
|
+
// LIFO cleanup for stable lock / stack order
|
|
212
|
+
for (let i = cleanups.length - 1; i >= 0; i--) {
|
|
213
|
+
try {
|
|
214
|
+
cleanups[i]!();
|
|
215
|
+
} catch {
|
|
216
|
+
/* ignore */
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
cleanups.length = 0;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Test helper — current stack depth (all documents). */
|
|
224
|
+
export function __overlayStackSize(): number {
|
|
225
|
+
return layers.length;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Test helper — clear stack (does not restore scroll). */
|
|
229
|
+
export function __resetOverlayStack(): void {
|
|
230
|
+
layers.length = 0;
|
|
231
|
+
nextLayerId = 1;
|
|
232
|
+
}
|
package/src/palette.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { signal, effect, type Signal } from "@lab206/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Preview palettes for the design system.
|
|
5
|
+
* - `instrument` — current default (cool navy + green signal)
|
|
6
|
+
* - `dual` — dual electric (navy primary + cyan secondary; green = success only)
|
|
7
|
+
*/
|
|
8
|
+
export type PaletteId = "instrument" | "dual";
|
|
9
|
+
|
|
10
|
+
export interface PaletteController {
|
|
11
|
+
id: Signal<PaletteId>;
|
|
12
|
+
set(id: PaletteId): void;
|
|
13
|
+
toggle(): void;
|
|
14
|
+
/** Apply `data-pu-palette` on documentElement (or target). */
|
|
15
|
+
bind(target?: Element): () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const STORAGE_KEY = "pu-palette";
|
|
19
|
+
|
|
20
|
+
function readStored(): PaletteId | null {
|
|
21
|
+
if (typeof localStorage === "undefined") return null;
|
|
22
|
+
try {
|
|
23
|
+
const v = localStorage.getItem(STORAGE_KEY);
|
|
24
|
+
if (v === "instrument" || v === "dual") return v;
|
|
25
|
+
} catch {
|
|
26
|
+
/* private mode */
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function writeStored(id: PaletteId) {
|
|
32
|
+
if (typeof localStorage === "undefined") return;
|
|
33
|
+
try {
|
|
34
|
+
localStorage.setItem(STORAGE_KEY, id);
|
|
35
|
+
} catch {
|
|
36
|
+
/* private mode */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Switchable token palette via `data-pu-palette` on `<html>`.
|
|
42
|
+
* Default: `dual` for preview (option 3); flip to `instrument` to restore the prior look.
|
|
43
|
+
*/
|
|
44
|
+
export function createPalette(initial: PaletteId = "dual"): PaletteController {
|
|
45
|
+
// Prefer last choice so “Instrument” sticks after you flip back
|
|
46
|
+
const start = readStored() ?? initial;
|
|
47
|
+
const id = signal<PaletteId>(start);
|
|
48
|
+
|
|
49
|
+
function set(next: PaletteId) {
|
|
50
|
+
id.set(next);
|
|
51
|
+
writeStored(next);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function toggle() {
|
|
55
|
+
set(id() === "dual" ? "instrument" : "dual");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function bind(target?: Element) {
|
|
59
|
+
const el =
|
|
60
|
+
target ??
|
|
61
|
+
(typeof document !== "undefined" ? document.documentElement : null);
|
|
62
|
+
if (!el) return () => {};
|
|
63
|
+
|
|
64
|
+
return effect(() => {
|
|
65
|
+
el.setAttribute("data-pu-palette", id());
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { id, set, toggle, bind };
|
|
70
|
+
}
|
package/src/reactive.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared prop reading for form / control primitives.
|
|
3
|
+
*
|
|
4
|
+
* Prefer `mergeProps` / `createProps` so accessors and signals unwrap on
|
|
5
|
+
* property access. These helpers still work on *raw* values when needed
|
|
6
|
+
* (e.g. reading before merge, or defensive double-unwrap).
|
|
7
|
+
*
|
|
8
|
+
* Call inside an `effect` (or JSX reactive binding) so dependencies track.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Plain value or zero-arg accessor / signal-like function. */
|
|
12
|
+
export type MaybeReactive<T> = T | (() => T);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve `T | (() => T)`.
|
|
16
|
+
* - functions with arity 0 (including signals) are called
|
|
17
|
+
* - multi-arg functions are returned as-is (not used as accessors)
|
|
18
|
+
* - everything else is returned as-is
|
|
19
|
+
*/
|
|
20
|
+
export function readProp<T>(value: MaybeReactive<T> | null | undefined): T | undefined {
|
|
21
|
+
if (value == null) return undefined;
|
|
22
|
+
if (typeof value === "function") {
|
|
23
|
+
// Signals and zero-arg accessors only
|
|
24
|
+
if ((value as (...args: unknown[]) => unknown).length === 0) {
|
|
25
|
+
return (value as () => T)();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return value as T;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Boolean controls (`disabled`, `checked`, …). */
|
|
32
|
+
export function readBool(
|
|
33
|
+
value: MaybeReactive<boolean> | null | undefined,
|
|
34
|
+
): boolean {
|
|
35
|
+
return !!readProp(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** String controls (`value`, `placeholder`, …). Nullish → `""`. */
|
|
39
|
+
export function readStr(
|
|
40
|
+
value: MaybeReactive<string> | null | undefined,
|
|
41
|
+
): string {
|
|
42
|
+
const v = readProp(value);
|
|
43
|
+
return v == null ? "" : String(v);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Numeric controls. Nullish → `fallback`. */
|
|
47
|
+
export function readNum(
|
|
48
|
+
value: MaybeReactive<number> | null | undefined,
|
|
49
|
+
fallback = 0,
|
|
50
|
+
): number {
|
|
51
|
+
const v = readProp(value);
|
|
52
|
+
if (v == null || Number.isNaN(Number(v))) return fallback;
|
|
53
|
+
return Number(v);
|
|
54
|
+
}
|