@octanejs/base-ui 0.1.15 → 0.1.17

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.
@@ -141,14 +141,9 @@ export function useCompositeRoot(...args: any[]): {
141
141
 
142
142
  if (activeIndex !== -1) {
143
143
  onHighlightedIndexChange(activeIndex);
144
- } else if (
145
- isListIndexDisabled({ current: sortedElements }, highlightedIndex, disabledIndices)
146
- ) {
147
- const firstEnabledIndex = findNonDisabledListIndex(
148
- { current: sortedElements },
149
- { disabledIndices },
150
- );
151
- if (!isIndexOutOfListBounds({ current: sortedElements }, firstEnabledIndex)) {
144
+ } else if (isListIndexDisabled(sortedElements, highlightedIndex, disabledIndices)) {
145
+ const firstEnabledIndex = findNonDisabledListIndex(sortedElements, { disabledIndices });
146
+ if (!isIndexOutOfListBounds(sortedElements, firstEnabledIndex)) {
152
147
  onHighlightedIndexChange(firstEnabledIndex);
153
148
  }
154
149
  }
@@ -168,9 +163,9 @@ export function useCompositeRoot(...args: any[]): {
168
163
  return;
169
164
  }
170
165
  const elements = elementsRef.current;
171
- if (isListIndexDisabled(elementsRef, highlightedIndex, disabledIndices)) {
172
- const firstEnabledIndex = findNonDisabledListIndex(elementsRef, { disabledIndices });
173
- if (!isIndexOutOfListBounds({ current: elements }, firstEnabledIndex)) {
166
+ if (isListIndexDisabled(elements, highlightedIndex, disabledIndices)) {
167
+ const firstEnabledIndex = findNonDisabledListIndex(elements, { disabledIndices });
168
+ if (!isIndexOutOfListBounds(elements, firstEnabledIndex)) {
174
169
  onHighlightedIndexChange(firstEnabledIndex);
175
170
  }
176
171
  }
@@ -294,7 +289,7 @@ export function useCompositeRoot(...args: any[]): {
294
289
  nextIndex = onLoop(event, highlightedIndex, nextIndex, elementsRef);
295
290
  }
296
291
  } else {
297
- nextIndex = findNonDisabledListIndex(elementsRef, {
292
+ nextIndex = findNonDisabledListIndex(elementsRef.current, {
298
293
  startingIndex: nextIndex,
299
294
  decrement: backwardKeys.includes(event.key),
300
295
  disabledIndices,
@@ -302,7 +297,10 @@ export function useCompositeRoot(...args: any[]): {
302
297
  }
303
298
  }
304
299
 
305
- if (nextIndex !== highlightedIndex && !isIndexOutOfListBounds(elementsRef, nextIndex)) {
300
+ if (
301
+ nextIndex !== highlightedIndex &&
302
+ !isIndexOutOfListBounds(elementsRef.current, nextIndex)
303
+ ) {
306
304
  if (stopEventPropagation) {
307
305
  event.stopPropagation();
308
306
  }
@@ -2,8 +2,30 @@
2
2
  // overlay/positioner layer needs. `POPUP_COLLISION_AVOIDANCE` is the default collision config
3
3
  // popups pass to `useAnchorPositioning`; `DISABLED_TRANSITIONS_STYLE` disables transitions while
4
4
  // a popup is in the `starting` transition phase (so the mount frame doesn't animate).
5
+
6
+ /**
7
+ * Used by regular popups that usually aren't scrollable and are allowed to
8
+ * freely flip to any axis of placement.
9
+ */
5
10
  export const POPUP_COLLISION_AVOIDANCE = {
6
11
  fallbackAxisSide: 'end',
7
12
  } as const;
8
13
 
14
+ /**
15
+ * Used for dropdowns that usually strictly prefer top/bottom placements and
16
+ * use `var(--available-height)` to limit their height.
17
+ */
18
+ export const DROPDOWN_COLLISION_AVOIDANCE = {
19
+ fallbackAxisSide: 'none',
20
+ } as const;
21
+
9
22
  export const DISABLED_TRANSITIONS_STYLE = { style: { transition: 'none' } } as const;
23
+
24
+ /** How long a typeahead buffer survives without a keystroke before it resets. */
25
+ export const TYPEAHEAD_RESET_MS = 500;
26
+
27
+ /**
28
+ * How long after a hover-open a click is still treated as "patient" (and therefore does not
29
+ * immediately close the popup it just opened).
30
+ */
31
+ export const PATIENT_CLICK_THRESHOLD = 500;
@@ -28,8 +28,11 @@ export const REASONS = {
28
28
  outsidePress: 'outside-press',
29
29
  closePress: 'close-press',
30
30
  focusOut: 'focus-out',
31
+ listNavigation: 'list-navigation',
31
32
  escapeKey: 'escape-key',
32
33
  imperativeAction: 'imperative-action',
34
+ cancelOpen: 'cancel-open',
35
+ siblingOpen: 'sibling-open',
33
36
  } as const;
34
37
 
35
38
  export type BaseUIEventReason = (typeof REASONS)[keyof typeof REASONS];
@@ -0,0 +1,19 @@
1
+ // Ported verbatim from .base-ui/packages/react/src/menu/utils/findRootOwnerId.ts (v1.6.0). Pure —
2
+ // no octane adaptations needed.
3
+ //
4
+ // Walks up from a node to the nearest element carrying `data-rootownerid` (stamped on
5
+ // `Menu.Popup`), so a mouseup landing in ANOTHER popup of the same menu root can be told apart
6
+ // from a mouseup that landed outside the menu tree entirely.
7
+ import { getParentNode, isHTMLElement, isLastTraversableNode } from '@floating-ui/utils/dom';
8
+
9
+ export function findRootOwnerId(node: Node): string | undefined {
10
+ if (isHTMLElement(node) && node.hasAttribute('data-rootownerid')) {
11
+ return node.getAttribute('data-rootownerid') ?? undefined;
12
+ }
13
+
14
+ if (isLastTraversableNode(node)) {
15
+ return undefined;
16
+ }
17
+
18
+ return findRootOwnerId(getParentNode(node));
19
+ }
@@ -0,0 +1,54 @@
1
+ // Ported from .base-ui/packages/react/src/floating-ui-react/hooks/gridNavigation.ts (v1.6.0). The
2
+ // grid navigator `useListNavigation` injects for its own `grid` option — distinct from
3
+ // `../composite/gridNavigation`, which builds the navigator `useCompositeRoot` takes.
4
+ //
5
+ // octane adaptations: the event is the NATIVE `KeyboardEvent`, and `React.RefObject<T>` is a plain
6
+ // `{ current: T }`. Not a hook, so no slot. Upstream's `utils/composite` module is split in this
7
+ // binding: the list algebra lives in `../composite/list-utils`, the grid algorithm in
8
+ // `../composite/grid-utils`.
9
+ import { getGridNavigatedIndex } from '../composite/grid-utils';
10
+ import { isIndexOutOfListBounds, type DisabledIndices } from '../composite/list-utils';
11
+
12
+ /**
13
+ * Positional arguments are deliberate: property names of an options object
14
+ * don't minify, and the signature is locked to the caller via `typeof` on the
15
+ * `grid` option of `useListNavigation`.
16
+ *
17
+ * The injected grid navigator only ever operates on a uniform 1x1 grid (sizes are
18
+ * always `1x1` and packing is never dense), so the cell-map machinery that supports
19
+ * multi-cell items collapses to an identity transform over the item list. Calling
20
+ * `getGridNavigatedIndex` directly keeps the cell-map helpers
21
+ * (`createGridCellMap`/`getGridCellIndexOfCorner`/`getGridCellIndices`) out of
22
+ * grid-combobox bundles.
23
+ */
24
+ export function gridNavigation(
25
+ event: KeyboardEvent,
26
+ prevIndex: number,
27
+ listRef: { current: Array<HTMLElement | null> },
28
+ orientation: 'horizontal' | 'vertical' | 'both',
29
+ loopFocus: boolean,
30
+ rtl: boolean,
31
+ disabledIndices: DisabledIndices | undefined,
32
+ minIndex: number,
33
+ maxIndex: number,
34
+ cols = 2,
35
+ ): number | undefined {
36
+ const nextIndex = getGridNavigatedIndex(listRef.current, {
37
+ event,
38
+ orientation,
39
+ loopFocus,
40
+ rtl,
41
+ cols,
42
+ disabledIndices,
43
+ minIndex,
44
+ maxIndex,
45
+ // An out-of-range previous index falls back to the first enabled item.
46
+ prevIndex: prevIndex > maxIndex ? minIndex : prevIndex,
47
+ stopEvent: true,
48
+ });
49
+
50
+ // `getGridNavigatedIndex` can return an out-of-bounds sentinel (e.g. `-1` when there is no
51
+ // previous item to move from); surface that as `undefined` so the caller treats it as
52
+ // "no navigation" rather than highlighting index `-1`.
53
+ return isIndexOutOfListBounds(listRef.current, nextIndex) ? undefined : nextIndex;
54
+ }