@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 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 '@kenos-ui/react-select';
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 | 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 |
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 type = child.type;
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 = child.props.children;
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 (child.props?.children != null) {
178
- label = extractItemTextLabel(child.props.children);
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
- if (isSelectItemElement(child)) {
193
- const value = child.props.value;
194
- const label = extractItemTextLabel(child.props.children) ?? value;
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 (child.props?.children != null) {
200
- walk(child.props.children);
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
- [disabled, required, readOnly, modal, name, multiple, mergedItems, isItemEqualToValue, openOnFocus]
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 ?? (el?.textContent ?? value);
913
+ const label = textValue ?? el?.textContent ?? value;
905
914
  store.registerItem({
906
915
  value,
907
916
  label,