@kenos-ui/react-select 0.2.1 → 0.2.3
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/CHANGELOG.md +48 -0
- package/README.md +17 -17
- package/dist/index.cjs +24 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -20
- package/dist/index.d.ts +19 -20
- package/dist/index.js +24 -15
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @kenos-ui/react-select
|
|
2
2
|
|
|
3
|
+
## 0.2.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [9c9de3d]
|
|
8
|
+
- Updated dependencies [f2ea00f]
|
|
9
|
+
- @kenos-ui/utils@1.0.0
|
|
10
|
+
|
|
11
|
+
## 0.2.2
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- **Kenos UI Select — feature release** (`@kenos-ui/react-select@0.2.2`)
|
|
16
|
+
|
|
17
|
+
Headless, accessible, composable Select primitive for React 19+, fully unstyled.
|
|
18
|
+
|
|
19
|
+
**Compound API**
|
|
20
|
+
|
|
21
|
+
- Parts: `Root`, `Label`, `Trigger`, `Value`, `Icon`, `Content`, `List`, `Item`, `ItemText`, `ItemIndicator`
|
|
22
|
+
- `Group`, `GroupLabel`, `HiddenSelect`, `Portal`, `Positioner`, `Backdrop`, `ClearTrigger`
|
|
23
|
+
- `ScrollUpButton`, `ScrollDownButton` for long lists
|
|
24
|
+
|
|
25
|
+
**Selection & forms**
|
|
26
|
+
|
|
27
|
+
- Single and multiple selection (`multiple` on Root)
|
|
28
|
+
- `items` prop for value→label maps without mounting every `Item`
|
|
29
|
+
- `isItemEqualToValue` for custom equality
|
|
30
|
+
- `HiddenSelect` for native form submission (`name`, `required`, `disabled`, `readOnly`)
|
|
31
|
+
- `onValueChange`, `onOpenChange`, `onOpenChangeComplete`
|
|
32
|
+
|
|
33
|
+
**Popup policy (interop-first)**
|
|
34
|
+
|
|
35
|
+
- Defaults: `modal={false}`, `portal={false}` — safe inside Dialogs
|
|
36
|
+
- Opt-in `modal={true}` with `Backdrop` + focus trap
|
|
37
|
+
- `portal` + `container` on Content; Floating UI positioning (`side`, `align`, `sameWidth`)
|
|
38
|
+
- `lazyMount` / `unmountOnExit`; focus restore on close
|
|
39
|
+
- Escape `stopPropagation` — dismisses Select without closing parent Dialog
|
|
40
|
+
|
|
41
|
+
**Keyboard & a11y**
|
|
42
|
+
|
|
43
|
+
- List navigation (↑↓, Home, End), typeahead, Enter/Space select
|
|
44
|
+
- Store-based item registry; `role="combobox"` / `role="listbox"` / `role="option"`
|
|
45
|
+
- Test suite: ARIA, keyboard nav, forms, portal, presence, dialog interop, axe
|
|
46
|
+
|
|
47
|
+
**Packaging**
|
|
48
|
+
|
|
49
|
+
- Add `license: MIT` to `package.json` (fixes npm registry showing "no license")
|
|
50
|
+
|
|
3
51
|
## 0.2.1
|
|
4
52
|
|
|
5
53
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ pnpm add @kenos-ui/react-select
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
|
-
import { Select } from
|
|
14
|
+
import { Select } from "@kenos-ui/react-select";
|
|
15
15
|
|
|
16
16
|
<Select.Root name="framework" onValueChange={setValue}>
|
|
17
17
|
<Select.Label>Framework</Select.Label>
|
|
@@ -32,26 +32,26 @@ import { Select } from '@kenos-ui/react-select';
|
|
|
32
32
|
</Select.List>
|
|
33
33
|
</Select.Content>
|
|
34
34
|
<Select.HiddenSelect />
|
|
35
|
-
</Select.Root
|
|
35
|
+
</Select.Root>;
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## API
|
|
39
39
|
|
|
40
|
-
| Part
|
|
41
|
-
|
|
42
|
-
| `Root`
|
|
43
|
-
| `Label`
|
|
44
|
-
| `Trigger`
|
|
45
|
-
| `Value`
|
|
46
|
-
| `Icon`
|
|
47
|
-
| `Content`
|
|
48
|
-
| `List`
|
|
49
|
-
| `Item`
|
|
50
|
-
| `ItemText`
|
|
51
|
-
| `ItemIndicator` | Shown when the option is selected
|
|
52
|
-
| `Group`
|
|
53
|
-
| `GroupLabel`
|
|
54
|
-
| `HiddenSelect`
|
|
40
|
+
| Part | Description |
|
|
41
|
+
| --------------- | ------------------------------------------ |
|
|
42
|
+
| `Root` | Context + state provider |
|
|
43
|
+
| `Label` | Associated `<label>` |
|
|
44
|
+
| `Trigger` | Button that opens the listbox |
|
|
45
|
+
| `Value` | Displays the selected label or placeholder |
|
|
46
|
+
| `Icon` | Chevron slot |
|
|
47
|
+
| `Content` | Listbox container (floating, lazyMount) |
|
|
48
|
+
| `List` | `role="listbox"` wrapper |
|
|
49
|
+
| `Item` | `role="option"` — registers value/label |
|
|
50
|
+
| `ItemText` | Option label slot |
|
|
51
|
+
| `ItemIndicator` | Shown when the option is selected |
|
|
52
|
+
| `Group` | Groups options (`role="group"`) |
|
|
53
|
+
| `GroupLabel` | Label for a group |
|
|
54
|
+
| `HiddenSelect` | Native `<select>` for form submission |
|
|
55
55
|
|
|
56
56
|
## Popup defaults (interop-first)
|
|
57
57
|
|
package/dist/index.cjs
CHANGED
|
@@ -165,17 +165,18 @@ function extractItemTextLabel(children) {
|
|
|
165
165
|
let label = null;
|
|
166
166
|
react.Children.forEach(children, (child) => {
|
|
167
167
|
if (label != null || !react.isValidElement(child)) return;
|
|
168
|
-
const
|
|
168
|
+
const element = child;
|
|
169
|
+
const type = element.type;
|
|
169
170
|
const isItemText = type?.displayName === "Select.ItemText" || type?.name === "ItemText";
|
|
170
171
|
if (isItemText) {
|
|
171
|
-
const content =
|
|
172
|
+
const content = element.props.children;
|
|
172
173
|
if (typeof content === "string" || typeof content === "number") {
|
|
173
174
|
label = String(content);
|
|
174
175
|
}
|
|
175
176
|
return;
|
|
176
177
|
}
|
|
177
|
-
if (
|
|
178
|
-
label = extractItemTextLabel(
|
|
178
|
+
if (element.props.children != null) {
|
|
179
|
+
label = extractItemTextLabel(element.props.children);
|
|
179
180
|
}
|
|
180
181
|
});
|
|
181
182
|
return label;
|
|
@@ -189,15 +190,16 @@ function extractItemsFromChildren(children) {
|
|
|
189
190
|
const walk = (node) => {
|
|
190
191
|
react.Children.forEach(node, (child) => {
|
|
191
192
|
if (!react.isValidElement(child)) return;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
193
|
+
const element = child;
|
|
194
|
+
if (isSelectItemElement(element)) {
|
|
195
|
+
const value = element.props.value;
|
|
196
|
+
const label = extractItemTextLabel(element.props.children) ?? value;
|
|
195
197
|
if (value != null && label != null) {
|
|
196
198
|
items[value] = label;
|
|
197
199
|
}
|
|
198
200
|
}
|
|
199
|
-
if (
|
|
200
|
-
walk(
|
|
201
|
+
if (element.props.children != null) {
|
|
202
|
+
walk(element.props.children);
|
|
201
203
|
}
|
|
202
204
|
});
|
|
203
205
|
};
|
|
@@ -366,10 +368,7 @@ function Root(props) {
|
|
|
366
368
|
[store]
|
|
367
369
|
);
|
|
368
370
|
const discoveredItems = react.useMemo(() => extractItemsFromChildren(children), [children]);
|
|
369
|
-
const mergedItems = react.useMemo(
|
|
370
|
-
() => ({ ...discoveredItems, ...items }),
|
|
371
|
-
[discoveredItems, items]
|
|
372
|
-
);
|
|
371
|
+
const mergedItems = react.useMemo(() => ({ ...discoveredItems, ...items }), [discoveredItems, items]);
|
|
373
372
|
const config = react.useMemo(
|
|
374
373
|
() => ({
|
|
375
374
|
disabled,
|
|
@@ -382,7 +381,17 @@ function Root(props) {
|
|
|
382
381
|
isItemEqualToValue,
|
|
383
382
|
openOnFocus
|
|
384
383
|
}),
|
|
385
|
-
[
|
|
384
|
+
[
|
|
385
|
+
disabled,
|
|
386
|
+
required,
|
|
387
|
+
readOnly,
|
|
388
|
+
modal,
|
|
389
|
+
name,
|
|
390
|
+
multiple,
|
|
391
|
+
mergedItems,
|
|
392
|
+
isItemEqualToValue,
|
|
393
|
+
openOnFocus
|
|
394
|
+
]
|
|
386
395
|
);
|
|
387
396
|
const ctx = react.useMemo(
|
|
388
397
|
() => ({
|
|
@@ -901,7 +910,7 @@ function Item({
|
|
|
901
910
|
const isDisabled = disabled || config.disabled || config.readOnly;
|
|
902
911
|
react.useLayoutEffect(() => {
|
|
903
912
|
const el = liRef.current;
|
|
904
|
-
const label = textValue ??
|
|
913
|
+
const label = textValue ?? el?.textContent ?? value;
|
|
905
914
|
store.registerItem({
|
|
906
915
|
value,
|
|
907
916
|
label,
|