@spacing-ui/core 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 +64 -0
- package/dist/index.cjs +258 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +256 -0
- package/dist/index.js.map +1 -0
- package/package.json +103 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nhan Nguyen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @spacing-ui/core
|
|
2
|
+
|
|
3
|
+
Headless, accessible React UI primitives.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @spacing-ui/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`react` and `react-dom` 18 or 19 are peer dependencies.
|
|
12
|
+
|
|
13
|
+
## Components
|
|
14
|
+
|
|
15
|
+
### `Select`
|
|
16
|
+
|
|
17
|
+
A fully accessible, unstyled listbox. Bring your own styles.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { Select } from "@spacing-ui/core";
|
|
21
|
+
|
|
22
|
+
function Example() {
|
|
23
|
+
const [value, setValue] = useState("apple");
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Select value={value} onValueChange={setValue}>
|
|
27
|
+
<Select.Trigger>
|
|
28
|
+
{({ open }) => (
|
|
29
|
+
<>
|
|
30
|
+
<span>{value}</span>
|
|
31
|
+
<ChevronIcon className={open ? "rotate-180" : ""} />
|
|
32
|
+
</>
|
|
33
|
+
)}
|
|
34
|
+
</Select.Trigger>
|
|
35
|
+
<Select.Content>
|
|
36
|
+
<Select.Option value="apple" textValue="Apple">
|
|
37
|
+
{({ selected, active }) => (
|
|
38
|
+
<div data-active={active} data-selected={selected}>
|
|
39
|
+
Apple
|
|
40
|
+
</div>
|
|
41
|
+
)}
|
|
42
|
+
</Select.Option>
|
|
43
|
+
<Select.Option value="banana" textValue="Banana">
|
|
44
|
+
Banana
|
|
45
|
+
</Select.Option>
|
|
46
|
+
</Select.Content>
|
|
47
|
+
</Select>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Keyboard:
|
|
53
|
+
|
|
54
|
+
- `Space` / `Enter` / `↓` / `↑` on trigger: open
|
|
55
|
+
- `↑` / `↓`: move highlight
|
|
56
|
+
- `Home` / `End`: jump to first / last
|
|
57
|
+
- typing letters: jump to matching option
|
|
58
|
+
- `Enter` / `Space`: select highlighted
|
|
59
|
+
- `Esc`: close, return focus to trigger
|
|
60
|
+
- `Tab`: close, move focus on
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/select/select.tsx
|
|
7
|
+
function render(children, state) {
|
|
8
|
+
return typeof children === "function" ? children(state) : children;
|
|
9
|
+
}
|
|
10
|
+
var SelectContext = react.createContext(null);
|
|
11
|
+
function useSelectContext(component) {
|
|
12
|
+
const ctx = react.useContext(SelectContext);
|
|
13
|
+
if (!ctx) {
|
|
14
|
+
throw new Error(`<Select.${component}> must be rendered inside <Select>`);
|
|
15
|
+
}
|
|
16
|
+
return ctx;
|
|
17
|
+
}
|
|
18
|
+
var Root = ({ value, onValueChange, children }) => {
|
|
19
|
+
const [open, setOpenState] = react.useState(false);
|
|
20
|
+
const [activeValue, setActiveValue] = react.useState(null);
|
|
21
|
+
const triggerRef = react.useRef(null);
|
|
22
|
+
const listboxRef = react.useRef(null);
|
|
23
|
+
const reactId = react.useId();
|
|
24
|
+
const triggerId = `${reactId}trigger`;
|
|
25
|
+
const listboxId = `${reactId}listbox`;
|
|
26
|
+
const optionIdPrefix = `${reactId}option-`;
|
|
27
|
+
const getOptionId = react.useCallback(
|
|
28
|
+
(v) => optionIdPrefix + v.replace(/[^a-zA-Z0-9_-]/g, "_"),
|
|
29
|
+
[optionIdPrefix]
|
|
30
|
+
);
|
|
31
|
+
const getOrderedOptions = react.useCallback(() => {
|
|
32
|
+
const root = listboxRef.current;
|
|
33
|
+
if (!root) return [];
|
|
34
|
+
const nodes = root.querySelectorAll('[role="option"]');
|
|
35
|
+
return Array.from(nodes).map((node) => ({
|
|
36
|
+
value: node.dataset.value ?? "",
|
|
37
|
+
textValue: node.dataset.textValue ?? node.textContent ?? ""
|
|
38
|
+
}));
|
|
39
|
+
}, []);
|
|
40
|
+
const setOpen = react.useCallback((next) => {
|
|
41
|
+
setOpenState(next);
|
|
42
|
+
if (!next) setActiveValue(null);
|
|
43
|
+
}, []);
|
|
44
|
+
react.useEffect(() => {
|
|
45
|
+
if (!open) return;
|
|
46
|
+
const onPointerDown = (e) => {
|
|
47
|
+
const target = e.target;
|
|
48
|
+
if (!triggerRef.current?.contains(target) && !listboxRef.current?.contains(target)) {
|
|
49
|
+
setOpen(false);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
53
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
54
|
+
}, [open, setOpen]);
|
|
55
|
+
const ctxValue = react.useMemo(
|
|
56
|
+
() => ({
|
|
57
|
+
open,
|
|
58
|
+
setOpen,
|
|
59
|
+
value,
|
|
60
|
+
onValueChange,
|
|
61
|
+
activeValue,
|
|
62
|
+
setActiveValue,
|
|
63
|
+
triggerId,
|
|
64
|
+
listboxId,
|
|
65
|
+
triggerRef,
|
|
66
|
+
listboxRef,
|
|
67
|
+
getOptionId,
|
|
68
|
+
getOrderedOptions
|
|
69
|
+
}),
|
|
70
|
+
[
|
|
71
|
+
open,
|
|
72
|
+
setOpen,
|
|
73
|
+
value,
|
|
74
|
+
onValueChange,
|
|
75
|
+
activeValue,
|
|
76
|
+
triggerId,
|
|
77
|
+
listboxId,
|
|
78
|
+
getOptionId,
|
|
79
|
+
getOrderedOptions
|
|
80
|
+
]
|
|
81
|
+
);
|
|
82
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SelectContext.Provider, { value: ctxValue, children });
|
|
83
|
+
};
|
|
84
|
+
var Trigger = react.forwardRef(function Trigger2({ children, onClick, onKeyDown, ...rest }, forwardedRef) {
|
|
85
|
+
const ctx = useSelectContext("Trigger");
|
|
86
|
+
const setRefs = (node) => {
|
|
87
|
+
ctx.triggerRef.current = node;
|
|
88
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
89
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
90
|
+
};
|
|
91
|
+
const handleClick = (e) => {
|
|
92
|
+
onClick?.(e);
|
|
93
|
+
if (e.defaultPrevented) return;
|
|
94
|
+
ctx.setOpen(!ctx.open);
|
|
95
|
+
};
|
|
96
|
+
const handleKeyDown = (e) => {
|
|
97
|
+
onKeyDown?.(e);
|
|
98
|
+
if (e.defaultPrevented) return;
|
|
99
|
+
if (e.key === " " || e.key === "Enter" || e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
ctx.setOpen(true);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
105
|
+
"button",
|
|
106
|
+
{
|
|
107
|
+
ref: setRefs,
|
|
108
|
+
type: "button",
|
|
109
|
+
id: ctx.triggerId,
|
|
110
|
+
role: "combobox",
|
|
111
|
+
"aria-haspopup": "listbox",
|
|
112
|
+
"aria-expanded": ctx.open,
|
|
113
|
+
"aria-controls": ctx.listboxId,
|
|
114
|
+
onClick: handleClick,
|
|
115
|
+
onKeyDown: handleKeyDown,
|
|
116
|
+
...rest,
|
|
117
|
+
children: render(children, { open: ctx.open, value: ctx.value })
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
var TYPEAHEAD_TIMEOUT_MS = 500;
|
|
122
|
+
var Content = ({ children, onKeyDown, ...rest }) => {
|
|
123
|
+
const ctx = useSelectContext("Content");
|
|
124
|
+
const typeaheadRef = react.useRef({ buffer: "", lastTime: 0 });
|
|
125
|
+
react.useEffect(() => {
|
|
126
|
+
if (!ctx.open) return;
|
|
127
|
+
const options = ctx.getOrderedOptions();
|
|
128
|
+
if (options.length === 0) {
|
|
129
|
+
ctx.listboxRef.current?.focus();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const initial = options.find((o) => o.value === ctx.value)?.value ?? options[0].value;
|
|
133
|
+
ctx.setActiveValue(initial);
|
|
134
|
+
ctx.listboxRef.current?.focus();
|
|
135
|
+
}, [ctx.open]);
|
|
136
|
+
if (!ctx.open) return null;
|
|
137
|
+
const moveActive = (delta) => {
|
|
138
|
+
const options = ctx.getOrderedOptions();
|
|
139
|
+
if (options.length === 0) return;
|
|
140
|
+
const currentIdx = options.findIndex((o) => o.value === ctx.activeValue);
|
|
141
|
+
let nextIdx;
|
|
142
|
+
if (delta === "first") nextIdx = 0;
|
|
143
|
+
else if (delta === "last") nextIdx = options.length - 1;
|
|
144
|
+
else if (currentIdx === -1) nextIdx = delta === 1 ? 0 : options.length - 1;
|
|
145
|
+
else nextIdx = Math.min(options.length - 1, Math.max(0, currentIdx + delta));
|
|
146
|
+
ctx.setActiveValue(options[nextIdx].value);
|
|
147
|
+
};
|
|
148
|
+
const typeahead = (char) => {
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const reset = now - typeaheadRef.current.lastTime > TYPEAHEAD_TIMEOUT_MS;
|
|
151
|
+
const buffer = (reset ? "" : typeaheadRef.current.buffer) + char.toLowerCase();
|
|
152
|
+
typeaheadRef.current = { buffer, lastTime: now };
|
|
153
|
+
const options = ctx.getOrderedOptions();
|
|
154
|
+
const match = options.find((o) => o.textValue.toLowerCase().startsWith(buffer));
|
|
155
|
+
if (match) ctx.setActiveValue(match.value);
|
|
156
|
+
};
|
|
157
|
+
const handleKeyDown = (e) => {
|
|
158
|
+
onKeyDown?.(e);
|
|
159
|
+
if (e.defaultPrevented) return;
|
|
160
|
+
switch (e.key) {
|
|
161
|
+
case "ArrowDown":
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
moveActive(1);
|
|
164
|
+
return;
|
|
165
|
+
case "ArrowUp":
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
moveActive(-1);
|
|
168
|
+
return;
|
|
169
|
+
case "Home":
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
moveActive("first");
|
|
172
|
+
return;
|
|
173
|
+
case "End":
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
moveActive("last");
|
|
176
|
+
return;
|
|
177
|
+
case "Enter":
|
|
178
|
+
case " ":
|
|
179
|
+
e.preventDefault();
|
|
180
|
+
if (ctx.activeValue !== null) {
|
|
181
|
+
ctx.onValueChange(ctx.activeValue);
|
|
182
|
+
ctx.setOpen(false);
|
|
183
|
+
ctx.triggerRef.current?.focus();
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
case "Escape":
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
ctx.setOpen(false);
|
|
189
|
+
ctx.triggerRef.current?.focus();
|
|
190
|
+
return;
|
|
191
|
+
case "Tab":
|
|
192
|
+
ctx.setOpen(false);
|
|
193
|
+
return;
|
|
194
|
+
default:
|
|
195
|
+
if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
196
|
+
typeahead(e.key);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
201
|
+
"div",
|
|
202
|
+
{
|
|
203
|
+
ref: (node) => {
|
|
204
|
+
ctx.listboxRef.current = node;
|
|
205
|
+
},
|
|
206
|
+
id: ctx.listboxId,
|
|
207
|
+
role: "listbox",
|
|
208
|
+
tabIndex: -1,
|
|
209
|
+
"aria-labelledby": ctx.triggerId,
|
|
210
|
+
"aria-activedescendant": ctx.activeValue !== null ? ctx.getOptionId(ctx.activeValue) : void 0,
|
|
211
|
+
onKeyDown: handleKeyDown,
|
|
212
|
+
...rest,
|
|
213
|
+
children
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
var Option = ({ value, textValue, children, onMouseEnter, onClick, ...rest }) => {
|
|
218
|
+
const ctx = useSelectContext("Option");
|
|
219
|
+
const selected = ctx.value === value;
|
|
220
|
+
const active = ctx.activeValue === value;
|
|
221
|
+
const handleClick = (e) => {
|
|
222
|
+
onClick?.(e);
|
|
223
|
+
if (e.defaultPrevented) return;
|
|
224
|
+
ctx.onValueChange(value);
|
|
225
|
+
ctx.setOpen(false);
|
|
226
|
+
ctx.triggerRef.current?.focus();
|
|
227
|
+
};
|
|
228
|
+
const handleMouseEnter = (e) => {
|
|
229
|
+
onMouseEnter?.(e);
|
|
230
|
+
if (e.defaultPrevented) return;
|
|
231
|
+
ctx.setActiveValue(value);
|
|
232
|
+
};
|
|
233
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
234
|
+
"div",
|
|
235
|
+
{
|
|
236
|
+
id: ctx.getOptionId(value),
|
|
237
|
+
role: "option",
|
|
238
|
+
"aria-selected": selected,
|
|
239
|
+
"data-value": value,
|
|
240
|
+
"data-text-value": textValue,
|
|
241
|
+
"data-active": active || void 0,
|
|
242
|
+
"data-selected": selected || void 0,
|
|
243
|
+
onClick: handleClick,
|
|
244
|
+
onMouseEnter: handleMouseEnter,
|
|
245
|
+
...rest,
|
|
246
|
+
children: render(children, { selected, active })
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
var Select = Object.assign(Root, {
|
|
251
|
+
Trigger,
|
|
252
|
+
Content,
|
|
253
|
+
Option
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
exports.Select = Select;
|
|
257
|
+
//# sourceMappingURL=index.cjs.map
|
|
258
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/select/select.tsx"],"names":["createContext","useContext","useState","useRef","useId","useCallback","useEffect","useMemo","forwardRef","Trigger","jsx"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n createContext,\n useContext,\n useState,\n useRef,\n useEffect,\n useId,\n useMemo,\n useCallback,\n forwardRef,\n type ReactNode,\n type ButtonHTMLAttributes,\n type HTMLAttributes,\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type RefObject,\n} from \"react\";\n\ntype Renderable<S> = ReactNode | ((state: S) => ReactNode);\n\nfunction render<S>(children: Renderable<S> | undefined, state: S): ReactNode {\n return typeof children === \"function\" ? children(state) : children;\n}\n\ninterface OptionData {\n value: string;\n textValue: string;\n}\n\ninterface SelectContextValue {\n open: boolean;\n setOpen: (open: boolean) => void;\n value: string;\n onValueChange: (value: string) => void;\n activeValue: string | null;\n setActiveValue: (value: string | null) => void;\n triggerId: string;\n listboxId: string;\n triggerRef: RefObject<HTMLButtonElement | null>;\n listboxRef: RefObject<HTMLDivElement | null>;\n getOptionId: (value: string) => string;\n getOrderedOptions: () => OptionData[];\n}\n\nconst SelectContext = createContext<SelectContextValue | null>(null);\n\nfunction useSelectContext(component: string): SelectContextValue {\n const ctx = useContext(SelectContext);\n if (!ctx) {\n throw new Error(`<Select.${component}> must be rendered inside <Select>`);\n }\n return ctx;\n}\n\ninterface SelectProps {\n value: string;\n onValueChange: (value: string) => void;\n children: ReactNode;\n}\n\nconst Root = ({ value, onValueChange, children }: SelectProps) => {\n const [open, setOpenState] = useState(false);\n const [activeValue, setActiveValue] = useState<string | null>(null);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const listboxRef = useRef<HTMLDivElement | null>(null);\n\n const reactId = useId();\n const triggerId = `${reactId}trigger`;\n const listboxId = `${reactId}listbox`;\n const optionIdPrefix = `${reactId}option-`;\n\n const getOptionId = useCallback(\n (v: string) => optionIdPrefix + v.replace(/[^a-zA-Z0-9_-]/g, \"_\"),\n [optionIdPrefix]\n );\n\n const getOrderedOptions = useCallback((): OptionData[] => {\n const root = listboxRef.current;\n if (!root) return [];\n const nodes = root.querySelectorAll<HTMLElement>('[role=\"option\"]');\n return Array.from(nodes).map((node) => ({\n value: node.dataset.value ?? \"\",\n textValue: node.dataset.textValue ?? node.textContent ?? \"\",\n }));\n }, []);\n\n const setOpen = useCallback((next: boolean) => {\n setOpenState(next);\n if (!next) setActiveValue(null);\n }, []);\n\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: PointerEvent) => {\n const target = e.target as Node;\n if (!triggerRef.current?.contains(target) && !listboxRef.current?.contains(target)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n }, [open, setOpen]);\n\n const ctxValue = useMemo<SelectContextValue>(\n () => ({\n open,\n setOpen,\n value,\n onValueChange,\n activeValue,\n setActiveValue,\n triggerId,\n listboxId,\n triggerRef,\n listboxRef,\n getOptionId,\n getOrderedOptions,\n }),\n [\n open,\n setOpen,\n value,\n onValueChange,\n activeValue,\n triggerId,\n listboxId,\n getOptionId,\n getOrderedOptions,\n ]\n );\n\n return <SelectContext.Provider value={ctxValue}>{children}</SelectContext.Provider>;\n};\n\ninterface TriggerProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"children\"> {\n children?: Renderable<{ open: boolean; value: string }>;\n}\n\nconst Trigger = forwardRef<HTMLButtonElement, TriggerProps>(function Trigger(\n { children, onClick, onKeyDown, ...rest },\n forwardedRef\n) {\n const ctx = useSelectContext(\"Trigger\");\n\n const setRefs = (node: HTMLButtonElement | null) => {\n ctx.triggerRef.current = node;\n if (typeof forwardedRef === \"function\") forwardedRef(node);\n else if (forwardedRef) forwardedRef.current = node;\n };\n\n const handleClick = (e: ReactMouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n ctx.setOpen(!ctx.open);\n };\n\n const handleKeyDown = (e: ReactKeyboardEvent<HTMLButtonElement>) => {\n onKeyDown?.(e);\n if (e.defaultPrevented) return;\n if (e.key === \" \" || e.key === \"Enter\" || e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n ctx.setOpen(true);\n }\n };\n\n return (\n <button\n ref={setRefs}\n type=\"button\"\n id={ctx.triggerId}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={ctx.open}\n aria-controls={ctx.listboxId}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n {...rest}\n >\n {render(children, { open: ctx.open, value: ctx.value })}\n </button>\n );\n});\n\ninterface ContentProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n}\n\nconst TYPEAHEAD_TIMEOUT_MS = 500;\n\nconst Content = ({ children, onKeyDown, ...rest }: ContentProps) => {\n const ctx = useSelectContext(\"Content\");\n const typeaheadRef = useRef({ buffer: \"\", lastTime: 0 });\n\n useEffect(() => {\n if (!ctx.open) return;\n const options = ctx.getOrderedOptions();\n if (options.length === 0) {\n ctx.listboxRef.current?.focus();\n return;\n }\n const initial = options.find((o) => o.value === ctx.value)?.value ?? options[0]!.value;\n ctx.setActiveValue(initial);\n ctx.listboxRef.current?.focus();\n // only re-run on open transition\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ctx.open]);\n\n if (!ctx.open) return null;\n\n const moveActive = (delta: 1 | -1 | \"first\" | \"last\") => {\n const options = ctx.getOrderedOptions();\n if (options.length === 0) return;\n const currentIdx = options.findIndex((o) => o.value === ctx.activeValue);\n let nextIdx: number;\n if (delta === \"first\") nextIdx = 0;\n else if (delta === \"last\") nextIdx = options.length - 1;\n else if (currentIdx === -1) nextIdx = delta === 1 ? 0 : options.length - 1;\n else nextIdx = Math.min(options.length - 1, Math.max(0, currentIdx + delta));\n ctx.setActiveValue(options[nextIdx]!.value);\n };\n\n const typeahead = (char: string) => {\n const now = Date.now();\n const reset = now - typeaheadRef.current.lastTime > TYPEAHEAD_TIMEOUT_MS;\n const buffer = (reset ? \"\" : typeaheadRef.current.buffer) + char.toLowerCase();\n typeaheadRef.current = { buffer, lastTime: now };\n const options = ctx.getOrderedOptions();\n const match = options.find((o) => o.textValue.toLowerCase().startsWith(buffer));\n if (match) ctx.setActiveValue(match.value);\n };\n\n const handleKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(e);\n if (e.defaultPrevented) return;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n moveActive(1);\n return;\n case \"ArrowUp\":\n e.preventDefault();\n moveActive(-1);\n return;\n case \"Home\":\n e.preventDefault();\n moveActive(\"first\");\n return;\n case \"End\":\n e.preventDefault();\n moveActive(\"last\");\n return;\n case \"Enter\":\n case \" \":\n e.preventDefault();\n if (ctx.activeValue !== null) {\n ctx.onValueChange(ctx.activeValue);\n ctx.setOpen(false);\n ctx.triggerRef.current?.focus();\n }\n return;\n case \"Escape\":\n e.preventDefault();\n ctx.setOpen(false);\n ctx.triggerRef.current?.focus();\n return;\n case \"Tab\":\n ctx.setOpen(false);\n return;\n default:\n if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {\n typeahead(e.key);\n }\n }\n };\n\n return (\n <div\n ref={(node) => {\n ctx.listboxRef.current = node;\n }}\n id={ctx.listboxId}\n role=\"listbox\"\n tabIndex={-1}\n aria-labelledby={ctx.triggerId}\n aria-activedescendant={\n ctx.activeValue !== null ? ctx.getOptionId(ctx.activeValue) : undefined\n }\n onKeyDown={handleKeyDown}\n {...rest}\n >\n {children}\n </div>\n );\n};\n\ninterface OptionProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n value: string;\n textValue?: string;\n children?: Renderable<{ selected: boolean; active: boolean }>;\n}\n\nconst Option = ({ value, textValue, children, onMouseEnter, onClick, ...rest }: OptionProps) => {\n const ctx = useSelectContext(\"Option\");\n const selected = ctx.value === value;\n const active = ctx.activeValue === value;\n\n const handleClick = (e: ReactMouseEvent<HTMLDivElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n ctx.onValueChange(value);\n ctx.setOpen(false);\n ctx.triggerRef.current?.focus();\n };\n\n const handleMouseEnter = (e: ReactMouseEvent<HTMLDivElement>) => {\n onMouseEnter?.(e);\n if (e.defaultPrevented) return;\n ctx.setActiveValue(value);\n };\n\n return (\n <div\n id={ctx.getOptionId(value)}\n role=\"option\"\n aria-selected={selected}\n data-value={value}\n data-text-value={textValue}\n data-active={active || undefined}\n data-selected={selected || undefined}\n onClick={handleClick}\n onMouseEnter={handleMouseEnter}\n {...rest}\n >\n {render(children, { selected, active })}\n </div>\n );\n};\n\nexport const Select = Object.assign(Root, {\n Trigger,\n Content,\n Option,\n});\n\nexport type { SelectProps, TriggerProps, ContentProps, OptionProps };\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode, ButtonHTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
type Renderable<S> = ReactNode | ((state: S) => ReactNode);
|
|
5
|
+
interface SelectProps {
|
|
6
|
+
value: string;
|
|
7
|
+
onValueChange: (value: string) => void;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
interface TriggerProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
|
11
|
+
children?: Renderable<{
|
|
12
|
+
open: boolean;
|
|
13
|
+
value: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
interface ContentProps extends HTMLAttributes<HTMLDivElement> {
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
interface OptionProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
20
|
+
value: string;
|
|
21
|
+
textValue?: string;
|
|
22
|
+
children?: Renderable<{
|
|
23
|
+
selected: boolean;
|
|
24
|
+
active: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
declare const Select: (({ value, onValueChange, children }: SelectProps) => react.JSX.Element) & {
|
|
28
|
+
Trigger: react.ForwardRefExoticComponent<TriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
29
|
+
Content: ({ children, onKeyDown, ...rest }: ContentProps) => react.JSX.Element | null;
|
|
30
|
+
Option: ({ value, textValue, children, onMouseEnter, onClick, ...rest }: OptionProps) => react.JSX.Element;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { type ContentProps, type OptionProps, Select, type SelectProps, type TriggerProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode, ButtonHTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
type Renderable<S> = ReactNode | ((state: S) => ReactNode);
|
|
5
|
+
interface SelectProps {
|
|
6
|
+
value: string;
|
|
7
|
+
onValueChange: (value: string) => void;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
interface TriggerProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
|
11
|
+
children?: Renderable<{
|
|
12
|
+
open: boolean;
|
|
13
|
+
value: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
interface ContentProps extends HTMLAttributes<HTMLDivElement> {
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
interface OptionProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
20
|
+
value: string;
|
|
21
|
+
textValue?: string;
|
|
22
|
+
children?: Renderable<{
|
|
23
|
+
selected: boolean;
|
|
24
|
+
active: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
declare const Select: (({ value, onValueChange, children }: SelectProps) => react.JSX.Element) & {
|
|
28
|
+
Trigger: react.ForwardRefExoticComponent<TriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
29
|
+
Content: ({ children, onKeyDown, ...rest }: ContentProps) => react.JSX.Element | null;
|
|
30
|
+
Option: ({ value, textValue, children, onMouseEnter, onClick, ...rest }: OptionProps) => react.JSX.Element;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { type ContentProps, type OptionProps, Select, type SelectProps, type TriggerProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { createContext, forwardRef, useContext, useState, useRef, useId, useCallback, useEffect, useMemo } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/select/select.tsx
|
|
5
|
+
function render(children, state) {
|
|
6
|
+
return typeof children === "function" ? children(state) : children;
|
|
7
|
+
}
|
|
8
|
+
var SelectContext = createContext(null);
|
|
9
|
+
function useSelectContext(component) {
|
|
10
|
+
const ctx = useContext(SelectContext);
|
|
11
|
+
if (!ctx) {
|
|
12
|
+
throw new Error(`<Select.${component}> must be rendered inside <Select>`);
|
|
13
|
+
}
|
|
14
|
+
return ctx;
|
|
15
|
+
}
|
|
16
|
+
var Root = ({ value, onValueChange, children }) => {
|
|
17
|
+
const [open, setOpenState] = useState(false);
|
|
18
|
+
const [activeValue, setActiveValue] = useState(null);
|
|
19
|
+
const triggerRef = useRef(null);
|
|
20
|
+
const listboxRef = useRef(null);
|
|
21
|
+
const reactId = useId();
|
|
22
|
+
const triggerId = `${reactId}trigger`;
|
|
23
|
+
const listboxId = `${reactId}listbox`;
|
|
24
|
+
const optionIdPrefix = `${reactId}option-`;
|
|
25
|
+
const getOptionId = useCallback(
|
|
26
|
+
(v) => optionIdPrefix + v.replace(/[^a-zA-Z0-9_-]/g, "_"),
|
|
27
|
+
[optionIdPrefix]
|
|
28
|
+
);
|
|
29
|
+
const getOrderedOptions = useCallback(() => {
|
|
30
|
+
const root = listboxRef.current;
|
|
31
|
+
if (!root) return [];
|
|
32
|
+
const nodes = root.querySelectorAll('[role="option"]');
|
|
33
|
+
return Array.from(nodes).map((node) => ({
|
|
34
|
+
value: node.dataset.value ?? "",
|
|
35
|
+
textValue: node.dataset.textValue ?? node.textContent ?? ""
|
|
36
|
+
}));
|
|
37
|
+
}, []);
|
|
38
|
+
const setOpen = useCallback((next) => {
|
|
39
|
+
setOpenState(next);
|
|
40
|
+
if (!next) setActiveValue(null);
|
|
41
|
+
}, []);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!open) return;
|
|
44
|
+
const onPointerDown = (e) => {
|
|
45
|
+
const target = e.target;
|
|
46
|
+
if (!triggerRef.current?.contains(target) && !listboxRef.current?.contains(target)) {
|
|
47
|
+
setOpen(false);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
51
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
52
|
+
}, [open, setOpen]);
|
|
53
|
+
const ctxValue = useMemo(
|
|
54
|
+
() => ({
|
|
55
|
+
open,
|
|
56
|
+
setOpen,
|
|
57
|
+
value,
|
|
58
|
+
onValueChange,
|
|
59
|
+
activeValue,
|
|
60
|
+
setActiveValue,
|
|
61
|
+
triggerId,
|
|
62
|
+
listboxId,
|
|
63
|
+
triggerRef,
|
|
64
|
+
listboxRef,
|
|
65
|
+
getOptionId,
|
|
66
|
+
getOrderedOptions
|
|
67
|
+
}),
|
|
68
|
+
[
|
|
69
|
+
open,
|
|
70
|
+
setOpen,
|
|
71
|
+
value,
|
|
72
|
+
onValueChange,
|
|
73
|
+
activeValue,
|
|
74
|
+
triggerId,
|
|
75
|
+
listboxId,
|
|
76
|
+
getOptionId,
|
|
77
|
+
getOrderedOptions
|
|
78
|
+
]
|
|
79
|
+
);
|
|
80
|
+
return /* @__PURE__ */ jsx(SelectContext.Provider, { value: ctxValue, children });
|
|
81
|
+
};
|
|
82
|
+
var Trigger = forwardRef(function Trigger2({ children, onClick, onKeyDown, ...rest }, forwardedRef) {
|
|
83
|
+
const ctx = useSelectContext("Trigger");
|
|
84
|
+
const setRefs = (node) => {
|
|
85
|
+
ctx.triggerRef.current = node;
|
|
86
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
87
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
88
|
+
};
|
|
89
|
+
const handleClick = (e) => {
|
|
90
|
+
onClick?.(e);
|
|
91
|
+
if (e.defaultPrevented) return;
|
|
92
|
+
ctx.setOpen(!ctx.open);
|
|
93
|
+
};
|
|
94
|
+
const handleKeyDown = (e) => {
|
|
95
|
+
onKeyDown?.(e);
|
|
96
|
+
if (e.defaultPrevented) return;
|
|
97
|
+
if (e.key === " " || e.key === "Enter" || e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
ctx.setOpen(true);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
return /* @__PURE__ */ jsx(
|
|
103
|
+
"button",
|
|
104
|
+
{
|
|
105
|
+
ref: setRefs,
|
|
106
|
+
type: "button",
|
|
107
|
+
id: ctx.triggerId,
|
|
108
|
+
role: "combobox",
|
|
109
|
+
"aria-haspopup": "listbox",
|
|
110
|
+
"aria-expanded": ctx.open,
|
|
111
|
+
"aria-controls": ctx.listboxId,
|
|
112
|
+
onClick: handleClick,
|
|
113
|
+
onKeyDown: handleKeyDown,
|
|
114
|
+
...rest,
|
|
115
|
+
children: render(children, { open: ctx.open, value: ctx.value })
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
var TYPEAHEAD_TIMEOUT_MS = 500;
|
|
120
|
+
var Content = ({ children, onKeyDown, ...rest }) => {
|
|
121
|
+
const ctx = useSelectContext("Content");
|
|
122
|
+
const typeaheadRef = useRef({ buffer: "", lastTime: 0 });
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!ctx.open) return;
|
|
125
|
+
const options = ctx.getOrderedOptions();
|
|
126
|
+
if (options.length === 0) {
|
|
127
|
+
ctx.listboxRef.current?.focus();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const initial = options.find((o) => o.value === ctx.value)?.value ?? options[0].value;
|
|
131
|
+
ctx.setActiveValue(initial);
|
|
132
|
+
ctx.listboxRef.current?.focus();
|
|
133
|
+
}, [ctx.open]);
|
|
134
|
+
if (!ctx.open) return null;
|
|
135
|
+
const moveActive = (delta) => {
|
|
136
|
+
const options = ctx.getOrderedOptions();
|
|
137
|
+
if (options.length === 0) return;
|
|
138
|
+
const currentIdx = options.findIndex((o) => o.value === ctx.activeValue);
|
|
139
|
+
let nextIdx;
|
|
140
|
+
if (delta === "first") nextIdx = 0;
|
|
141
|
+
else if (delta === "last") nextIdx = options.length - 1;
|
|
142
|
+
else if (currentIdx === -1) nextIdx = delta === 1 ? 0 : options.length - 1;
|
|
143
|
+
else nextIdx = Math.min(options.length - 1, Math.max(0, currentIdx + delta));
|
|
144
|
+
ctx.setActiveValue(options[nextIdx].value);
|
|
145
|
+
};
|
|
146
|
+
const typeahead = (char) => {
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
const reset = now - typeaheadRef.current.lastTime > TYPEAHEAD_TIMEOUT_MS;
|
|
149
|
+
const buffer = (reset ? "" : typeaheadRef.current.buffer) + char.toLowerCase();
|
|
150
|
+
typeaheadRef.current = { buffer, lastTime: now };
|
|
151
|
+
const options = ctx.getOrderedOptions();
|
|
152
|
+
const match = options.find((o) => o.textValue.toLowerCase().startsWith(buffer));
|
|
153
|
+
if (match) ctx.setActiveValue(match.value);
|
|
154
|
+
};
|
|
155
|
+
const handleKeyDown = (e) => {
|
|
156
|
+
onKeyDown?.(e);
|
|
157
|
+
if (e.defaultPrevented) return;
|
|
158
|
+
switch (e.key) {
|
|
159
|
+
case "ArrowDown":
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
moveActive(1);
|
|
162
|
+
return;
|
|
163
|
+
case "ArrowUp":
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
moveActive(-1);
|
|
166
|
+
return;
|
|
167
|
+
case "Home":
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
moveActive("first");
|
|
170
|
+
return;
|
|
171
|
+
case "End":
|
|
172
|
+
e.preventDefault();
|
|
173
|
+
moveActive("last");
|
|
174
|
+
return;
|
|
175
|
+
case "Enter":
|
|
176
|
+
case " ":
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
if (ctx.activeValue !== null) {
|
|
179
|
+
ctx.onValueChange(ctx.activeValue);
|
|
180
|
+
ctx.setOpen(false);
|
|
181
|
+
ctx.triggerRef.current?.focus();
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
case "Escape":
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
ctx.setOpen(false);
|
|
187
|
+
ctx.triggerRef.current?.focus();
|
|
188
|
+
return;
|
|
189
|
+
case "Tab":
|
|
190
|
+
ctx.setOpen(false);
|
|
191
|
+
return;
|
|
192
|
+
default:
|
|
193
|
+
if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
194
|
+
typeahead(e.key);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
return /* @__PURE__ */ jsx(
|
|
199
|
+
"div",
|
|
200
|
+
{
|
|
201
|
+
ref: (node) => {
|
|
202
|
+
ctx.listboxRef.current = node;
|
|
203
|
+
},
|
|
204
|
+
id: ctx.listboxId,
|
|
205
|
+
role: "listbox",
|
|
206
|
+
tabIndex: -1,
|
|
207
|
+
"aria-labelledby": ctx.triggerId,
|
|
208
|
+
"aria-activedescendant": ctx.activeValue !== null ? ctx.getOptionId(ctx.activeValue) : void 0,
|
|
209
|
+
onKeyDown: handleKeyDown,
|
|
210
|
+
...rest,
|
|
211
|
+
children
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
var Option = ({ value, textValue, children, onMouseEnter, onClick, ...rest }) => {
|
|
216
|
+
const ctx = useSelectContext("Option");
|
|
217
|
+
const selected = ctx.value === value;
|
|
218
|
+
const active = ctx.activeValue === value;
|
|
219
|
+
const handleClick = (e) => {
|
|
220
|
+
onClick?.(e);
|
|
221
|
+
if (e.defaultPrevented) return;
|
|
222
|
+
ctx.onValueChange(value);
|
|
223
|
+
ctx.setOpen(false);
|
|
224
|
+
ctx.triggerRef.current?.focus();
|
|
225
|
+
};
|
|
226
|
+
const handleMouseEnter = (e) => {
|
|
227
|
+
onMouseEnter?.(e);
|
|
228
|
+
if (e.defaultPrevented) return;
|
|
229
|
+
ctx.setActiveValue(value);
|
|
230
|
+
};
|
|
231
|
+
return /* @__PURE__ */ jsx(
|
|
232
|
+
"div",
|
|
233
|
+
{
|
|
234
|
+
id: ctx.getOptionId(value),
|
|
235
|
+
role: "option",
|
|
236
|
+
"aria-selected": selected,
|
|
237
|
+
"data-value": value,
|
|
238
|
+
"data-text-value": textValue,
|
|
239
|
+
"data-active": active || void 0,
|
|
240
|
+
"data-selected": selected || void 0,
|
|
241
|
+
onClick: handleClick,
|
|
242
|
+
onMouseEnter: handleMouseEnter,
|
|
243
|
+
...rest,
|
|
244
|
+
children: render(children, { selected, active })
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
var Select = Object.assign(Root, {
|
|
249
|
+
Trigger,
|
|
250
|
+
Content,
|
|
251
|
+
Option
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
export { Select };
|
|
255
|
+
//# sourceMappingURL=index.js.map
|
|
256
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/select/select.tsx"],"names":["Trigger"],"mappings":"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","file":"index.js","sourcesContent":["import {\n createContext,\n useContext,\n useState,\n useRef,\n useEffect,\n useId,\n useMemo,\n useCallback,\n forwardRef,\n type ReactNode,\n type ButtonHTMLAttributes,\n type HTMLAttributes,\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type RefObject,\n} from \"react\";\n\ntype Renderable<S> = ReactNode | ((state: S) => ReactNode);\n\nfunction render<S>(children: Renderable<S> | undefined, state: S): ReactNode {\n return typeof children === \"function\" ? children(state) : children;\n}\n\ninterface OptionData {\n value: string;\n textValue: string;\n}\n\ninterface SelectContextValue {\n open: boolean;\n setOpen: (open: boolean) => void;\n value: string;\n onValueChange: (value: string) => void;\n activeValue: string | null;\n setActiveValue: (value: string | null) => void;\n triggerId: string;\n listboxId: string;\n triggerRef: RefObject<HTMLButtonElement | null>;\n listboxRef: RefObject<HTMLDivElement | null>;\n getOptionId: (value: string) => string;\n getOrderedOptions: () => OptionData[];\n}\n\nconst SelectContext = createContext<SelectContextValue | null>(null);\n\nfunction useSelectContext(component: string): SelectContextValue {\n const ctx = useContext(SelectContext);\n if (!ctx) {\n throw new Error(`<Select.${component}> must be rendered inside <Select>`);\n }\n return ctx;\n}\n\ninterface SelectProps {\n value: string;\n onValueChange: (value: string) => void;\n children: ReactNode;\n}\n\nconst Root = ({ value, onValueChange, children }: SelectProps) => {\n const [open, setOpenState] = useState(false);\n const [activeValue, setActiveValue] = useState<string | null>(null);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const listboxRef = useRef<HTMLDivElement | null>(null);\n\n const reactId = useId();\n const triggerId = `${reactId}trigger`;\n const listboxId = `${reactId}listbox`;\n const optionIdPrefix = `${reactId}option-`;\n\n const getOptionId = useCallback(\n (v: string) => optionIdPrefix + v.replace(/[^a-zA-Z0-9_-]/g, \"_\"),\n [optionIdPrefix]\n );\n\n const getOrderedOptions = useCallback((): OptionData[] => {\n const root = listboxRef.current;\n if (!root) return [];\n const nodes = root.querySelectorAll<HTMLElement>('[role=\"option\"]');\n return Array.from(nodes).map((node) => ({\n value: node.dataset.value ?? \"\",\n textValue: node.dataset.textValue ?? node.textContent ?? \"\",\n }));\n }, []);\n\n const setOpen = useCallback((next: boolean) => {\n setOpenState(next);\n if (!next) setActiveValue(null);\n }, []);\n\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: PointerEvent) => {\n const target = e.target as Node;\n if (!triggerRef.current?.contains(target) && !listboxRef.current?.contains(target)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n }, [open, setOpen]);\n\n const ctxValue = useMemo<SelectContextValue>(\n () => ({\n open,\n setOpen,\n value,\n onValueChange,\n activeValue,\n setActiveValue,\n triggerId,\n listboxId,\n triggerRef,\n listboxRef,\n getOptionId,\n getOrderedOptions,\n }),\n [\n open,\n setOpen,\n value,\n onValueChange,\n activeValue,\n triggerId,\n listboxId,\n getOptionId,\n getOrderedOptions,\n ]\n );\n\n return <SelectContext.Provider value={ctxValue}>{children}</SelectContext.Provider>;\n};\n\ninterface TriggerProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"children\"> {\n children?: Renderable<{ open: boolean; value: string }>;\n}\n\nconst Trigger = forwardRef<HTMLButtonElement, TriggerProps>(function Trigger(\n { children, onClick, onKeyDown, ...rest },\n forwardedRef\n) {\n const ctx = useSelectContext(\"Trigger\");\n\n const setRefs = (node: HTMLButtonElement | null) => {\n ctx.triggerRef.current = node;\n if (typeof forwardedRef === \"function\") forwardedRef(node);\n else if (forwardedRef) forwardedRef.current = node;\n };\n\n const handleClick = (e: ReactMouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n ctx.setOpen(!ctx.open);\n };\n\n const handleKeyDown = (e: ReactKeyboardEvent<HTMLButtonElement>) => {\n onKeyDown?.(e);\n if (e.defaultPrevented) return;\n if (e.key === \" \" || e.key === \"Enter\" || e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n ctx.setOpen(true);\n }\n };\n\n return (\n <button\n ref={setRefs}\n type=\"button\"\n id={ctx.triggerId}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={ctx.open}\n aria-controls={ctx.listboxId}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n {...rest}\n >\n {render(children, { open: ctx.open, value: ctx.value })}\n </button>\n );\n});\n\ninterface ContentProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n}\n\nconst TYPEAHEAD_TIMEOUT_MS = 500;\n\nconst Content = ({ children, onKeyDown, ...rest }: ContentProps) => {\n const ctx = useSelectContext(\"Content\");\n const typeaheadRef = useRef({ buffer: \"\", lastTime: 0 });\n\n useEffect(() => {\n if (!ctx.open) return;\n const options = ctx.getOrderedOptions();\n if (options.length === 0) {\n ctx.listboxRef.current?.focus();\n return;\n }\n const initial = options.find((o) => o.value === ctx.value)?.value ?? options[0]!.value;\n ctx.setActiveValue(initial);\n ctx.listboxRef.current?.focus();\n // only re-run on open transition\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ctx.open]);\n\n if (!ctx.open) return null;\n\n const moveActive = (delta: 1 | -1 | \"first\" | \"last\") => {\n const options = ctx.getOrderedOptions();\n if (options.length === 0) return;\n const currentIdx = options.findIndex((o) => o.value === ctx.activeValue);\n let nextIdx: number;\n if (delta === \"first\") nextIdx = 0;\n else if (delta === \"last\") nextIdx = options.length - 1;\n else if (currentIdx === -1) nextIdx = delta === 1 ? 0 : options.length - 1;\n else nextIdx = Math.min(options.length - 1, Math.max(0, currentIdx + delta));\n ctx.setActiveValue(options[nextIdx]!.value);\n };\n\n const typeahead = (char: string) => {\n const now = Date.now();\n const reset = now - typeaheadRef.current.lastTime > TYPEAHEAD_TIMEOUT_MS;\n const buffer = (reset ? \"\" : typeaheadRef.current.buffer) + char.toLowerCase();\n typeaheadRef.current = { buffer, lastTime: now };\n const options = ctx.getOrderedOptions();\n const match = options.find((o) => o.textValue.toLowerCase().startsWith(buffer));\n if (match) ctx.setActiveValue(match.value);\n };\n\n const handleKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(e);\n if (e.defaultPrevented) return;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n moveActive(1);\n return;\n case \"ArrowUp\":\n e.preventDefault();\n moveActive(-1);\n return;\n case \"Home\":\n e.preventDefault();\n moveActive(\"first\");\n return;\n case \"End\":\n e.preventDefault();\n moveActive(\"last\");\n return;\n case \"Enter\":\n case \" \":\n e.preventDefault();\n if (ctx.activeValue !== null) {\n ctx.onValueChange(ctx.activeValue);\n ctx.setOpen(false);\n ctx.triggerRef.current?.focus();\n }\n return;\n case \"Escape\":\n e.preventDefault();\n ctx.setOpen(false);\n ctx.triggerRef.current?.focus();\n return;\n case \"Tab\":\n ctx.setOpen(false);\n return;\n default:\n if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {\n typeahead(e.key);\n }\n }\n };\n\n return (\n <div\n ref={(node) => {\n ctx.listboxRef.current = node;\n }}\n id={ctx.listboxId}\n role=\"listbox\"\n tabIndex={-1}\n aria-labelledby={ctx.triggerId}\n aria-activedescendant={\n ctx.activeValue !== null ? ctx.getOptionId(ctx.activeValue) : undefined\n }\n onKeyDown={handleKeyDown}\n {...rest}\n >\n {children}\n </div>\n );\n};\n\ninterface OptionProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n value: string;\n textValue?: string;\n children?: Renderable<{ selected: boolean; active: boolean }>;\n}\n\nconst Option = ({ value, textValue, children, onMouseEnter, onClick, ...rest }: OptionProps) => {\n const ctx = useSelectContext(\"Option\");\n const selected = ctx.value === value;\n const active = ctx.activeValue === value;\n\n const handleClick = (e: ReactMouseEvent<HTMLDivElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n ctx.onValueChange(value);\n ctx.setOpen(false);\n ctx.triggerRef.current?.focus();\n };\n\n const handleMouseEnter = (e: ReactMouseEvent<HTMLDivElement>) => {\n onMouseEnter?.(e);\n if (e.defaultPrevented) return;\n ctx.setActiveValue(value);\n };\n\n return (\n <div\n id={ctx.getOptionId(value)}\n role=\"option\"\n aria-selected={selected}\n data-value={value}\n data-text-value={textValue}\n data-active={active || undefined}\n data-selected={selected || undefined}\n onClick={handleClick}\n onMouseEnter={handleMouseEnter}\n {...rest}\n >\n {render(children, { selected, active })}\n </div>\n );\n};\n\nexport const Select = Object.assign(Root, {\n Trigger,\n Content,\n Option,\n});\n\nexport type { SelectProps, TriggerProps, ContentProps, OptionProps };\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spacing-ui/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless, accessible React UI primitives.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Nhan Nguyen <nhan13574@gmail.com>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/nathannewyen/space-ui.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/nathannewyen/space-ui#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/nathannewyen/space-ui/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"require": "./dist/index.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"dev": "tsup --watch",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"lint": "eslint .",
|
|
39
|
+
"lint:fix": "eslint . --fix",
|
|
40
|
+
"format": "prettier --write \"**/*.{ts,tsx,js,mjs,json,md,yml,yaml}\"",
|
|
41
|
+
"format:check": "prettier --check \"**/*.{ts,tsx,js,mjs,json,md,yml,yaml}\"",
|
|
42
|
+
"clean": "rm -rf dist",
|
|
43
|
+
"prepare": "husky",
|
|
44
|
+
"prepublishOnly": "pnpm clean && pnpm lint && pnpm typecheck && pnpm test && pnpm build"
|
|
45
|
+
},
|
|
46
|
+
"lint-staged": {
|
|
47
|
+
"*.{ts,tsx}": [
|
|
48
|
+
"eslint --fix",
|
|
49
|
+
"prettier --write"
|
|
50
|
+
],
|
|
51
|
+
"*.{js,mjs,json,md,yml,yaml}": [
|
|
52
|
+
"prettier --write"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
57
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^10.0.1",
|
|
61
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
62
|
+
"@testing-library/react": "^16.1.0",
|
|
63
|
+
"@testing-library/user-event": "^14.5.2",
|
|
64
|
+
"@types/node": "^22.10.5",
|
|
65
|
+
"@types/react": "^19.0.7",
|
|
66
|
+
"@types/react-dom": "^19.0.3",
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
|
68
|
+
"@typescript-eslint/parser": "^8.20.0",
|
|
69
|
+
"eslint": "^9.18.0",
|
|
70
|
+
"eslint-config-prettier": "^10.1.5",
|
|
71
|
+
"eslint-plugin-react": "^7.37.4",
|
|
72
|
+
"eslint-plugin-react-hooks": "^5.1.0",
|
|
73
|
+
"husky": "^9.1.7",
|
|
74
|
+
"jsdom": "^26.0.0",
|
|
75
|
+
"lint-staged": "^16.2.0",
|
|
76
|
+
"prettier": "^3.4.2",
|
|
77
|
+
"react": "^19.0.0",
|
|
78
|
+
"react-dom": "^19.0.0",
|
|
79
|
+
"tsup": "^8.3.5",
|
|
80
|
+
"typescript": "^5.7.3",
|
|
81
|
+
"vitest": "^2.1.8"
|
|
82
|
+
},
|
|
83
|
+
"keywords": [
|
|
84
|
+
"react",
|
|
85
|
+
"ui",
|
|
86
|
+
"headless",
|
|
87
|
+
"accessible",
|
|
88
|
+
"a11y",
|
|
89
|
+
"components",
|
|
90
|
+
"primitives"
|
|
91
|
+
],
|
|
92
|
+
"engines": {
|
|
93
|
+
"node": ">=18"
|
|
94
|
+
},
|
|
95
|
+
"publishConfig": {
|
|
96
|
+
"access": "public"
|
|
97
|
+
},
|
|
98
|
+
"pnpm": {
|
|
99
|
+
"onlyBuiltDependencies": [
|
|
100
|
+
"esbuild"
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|