@lumx/react 4.9.0-alpha.1 → 4.9.0-alpha.2

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.
@@ -48,7 +48,7 @@ const CLICK_AWAY_EVENT_TYPES = ['mousedown', 'touchstart'];
48
48
  * @returns `true` if the click is outside all elements (i.e. a click away).
49
49
  */
50
50
  function isClickAway(targets, elements) {
51
- return !elements.some(element => targets.some(target => element?.contains(target)));
51
+ return !elements.some(element => element instanceof Node && targets.some(target => element.contains(target)));
52
52
  }
53
53
 
54
54
  /**
@@ -68,7 +68,7 @@ function setupClickAway(getElements, callback) {
68
68
  return undefined;
69
69
  }
70
70
  const listener = evt => {
71
- const targets = [evt.composedPath?.()[0], evt.target];
71
+ const targets = [evt.composedPath?.()[0], evt.target].filter(t => t instanceof Node);
72
72
  const elements = getElements();
73
73
  if (isClickAway(targets, elements)) {
74
74
  callback(evt);
@@ -192,4 +192,4 @@ const Portal = ({
192
192
  };
193
193
 
194
194
  export { ClickAwayProvider as C, DisabledStateProvider as D, Portal as P, PortalProvider as a, useDisabledStateContext as u };
195
- //# sourceMappingURL=C9YCH96e.js.map
195
+ //# sourceMappingURL=Dpulze-1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dpulze-1.js","sources":["../../src/utils/disabled/DisabledStateContext.tsx","../../../lumx-core/src/js/utils/ClickAway/index.ts","../../src/hooks/useClickAway.tsx","../../src/utils/ClickAwayProvider/ClickAwayProvider.tsx","../../src/utils/Portal/PortalProvider.tsx","../../src/utils/Portal/Portal.tsx"],"sourcesContent":["import React, { useContext } from 'react';\n\nimport { DisabledStateContextValue } from '@lumx/core/js/utils/disabledState';\n\nexport const DisabledStateContext = React.createContext<DisabledStateContextValue>({ state: null });\n\nexport type DisabledStateProviderProps = DisabledStateContextValue & {\n children: React.ReactNode;\n};\n\n/**\n * Disabled state provider.\n * All nested LumX Design System components inherit this disabled state.\n */\nexport function DisabledStateProvider({ children, ...value }: DisabledStateProviderProps) {\n return <DisabledStateContext.Provider value={value}>{children}</DisabledStateContext.Provider>;\n}\n\n/**\n * Get DisabledState context value\n */\nexport function useDisabledStateContext(): DisabledStateContextValue {\n return useContext(DisabledStateContext);\n}\n","/**\n * Shared types and logic for ClickAway detection.\n *\n * ClickAway detects clicks outside a set of elements and triggers a callback.\n * The core logic (event listening + target checking) is framework-agnostic.\n * Framework-specific wrappers (React hook, Vue composable) and context providers\n * (React context, Vue provide/inject) are implemented in each framework package.\n */\n\nimport type { Falsy } from '@lumx/core/js/types';\n\n/** Event types that trigger click away detection. */\nexport const CLICK_AWAY_EVENT_TYPES = ['mousedown', 'touchstart'] as const;\n\n/** Callback triggered when a click away is detected. */\nexport type ClickAwayCallback = EventListener | Falsy;\n\n/**\n * Check if the click event targets are outside all the given elements.\n *\n * @param targets - The event target elements (from `event.target` and `event.composedPath()`).\n * @param elements - The elements considered \"inside\" the click away context.\n * @returns `true` if the click is outside all elements (i.e. a click away).\n */\nexport function isClickAway(targets: HTMLElement[], elements: HTMLElement[]): boolean {\n return !elements.some((element) => element instanceof Node && targets.some((target) => element.contains(target)));\n}\n\n/**\n * Imperative setup for click away detection.\n * Adds mousedown/touchstart listeners on `document` and calls the callback when a click\n * occurs outside the elements returned by `getElements`.\n *\n * Note: when `getElements` returns an empty array, any click is considered a click away.\n * Callers should guard against calling `setupClickAway` when no refs are registered.\n *\n * @param getElements - Getter returning the current list of elements considered \"inside\".\n * @param callback - Callback to invoke on click away.\n * @returns A teardown function that removes the event listeners.\n */\nexport function setupClickAway(\n getElements: () => HTMLElement[],\n callback: ClickAwayCallback,\n): (() => void) | undefined {\n if (!callback) {\n return undefined;\n }\n\n const listener: EventListener = (evt) => {\n const targets = [evt.composedPath?.()[0], evt.target].filter((t): t is HTMLElement => t instanceof Node);\n const elements = getElements();\n if (isClickAway(targets, elements)) {\n callback(evt);\n }\n };\n\n CLICK_AWAY_EVENT_TYPES.forEach((evtType) => document.addEventListener(evtType, listener));\n return () => {\n CLICK_AWAY_EVENT_TYPES.forEach((evtType) => document.removeEventListener(evtType, listener));\n };\n}\n","import { RefObject, useEffect } from 'react';\n\nimport { Falsy } from '@lumx/react/utils/type';\nimport { setupClickAway } from '@lumx/core/js/utils/ClickAway';\n\nexport interface ClickAwayParameters {\n /**\n * A callback function to call when the user clicks away from the elements.\n */\n callback: EventListener | Falsy;\n /**\n * Elements considered within the click away context (clicking outside them will trigger the click away callback).\n */\n childrenRefs: RefObject<Array<RefObject<HTMLElement>>>;\n}\n\n/**\n * Listen to clicks away from the given elements and callback the passed in function.\n *\n * Warning: If you need to detect click away on nested React portals, please use the `ClickAwayProvider` component.\n */\nexport function useClickAway({ callback, childrenRefs }: ClickAwayParameters): void {\n useEffect(() => {\n const getElements = () => {\n const refs = childrenRefs.current;\n if (!refs) return [];\n return refs.map((ref) => ref?.current).filter(Boolean) as HTMLElement[];\n };\n return setupClickAway(getElements, callback);\n }, [callback, childrenRefs]);\n}\n","import { createContext, RefObject, useContext, useEffect, useMemo, useRef } from 'react';\nimport { ClickAwayParameters, useClickAway } from '@lumx/react/hooks/useClickAway';\n\ninterface ContextValue {\n childrenRefs: Array<RefObject<HTMLElement>>;\n addRefs(...newChildrenRefs: Array<RefObject<HTMLElement>>): void;\n}\n\nconst ClickAwayAncestorContext = createContext<ContextValue | null>(null);\n\ninterface ClickAwayProviderProps extends ClickAwayParameters {\n /**\n * (Optional) Element that should be considered as part of the parent\n */\n parentRef?: RefObject<HTMLElement>;\n /**\n * Children\n */\n children?: React.ReactNode;\n}\n\n/**\n * Component combining the `useClickAway` hook with a React context to hook into the React component tree and make sure\n * we take into account both the DOM tree and the React tree to detect click away.\n *\n * @return the react component.\n */\nexport const ClickAwayProvider: React.FC<ClickAwayProviderProps> = ({\n children,\n callback,\n childrenRefs,\n parentRef,\n}) => {\n const parentContext = useContext(ClickAwayAncestorContext);\n const currentContext = useMemo(() => {\n const context: ContextValue = {\n childrenRefs: [],\n /**\n * Add element refs to the current context and propagate to the parent context.\n */\n addRefs(...newChildrenRefs) {\n // Add element refs that should be considered as inside the click away context.\n context.childrenRefs.push(...newChildrenRefs);\n\n if (parentContext) {\n // Also add then to the parent context\n parentContext.addRefs(...newChildrenRefs);\n if (parentRef) {\n // The parent element is also considered as inside the parent click away context but not inside the current context\n parentContext.addRefs(parentRef);\n }\n }\n },\n };\n return context;\n }, [parentContext, parentRef]);\n\n useEffect(() => {\n const { current: currentRefs } = childrenRefs;\n if (!currentRefs) {\n return;\n }\n currentContext.addRefs(...currentRefs);\n }, [currentContext, childrenRefs]);\n\n useClickAway({ callback, childrenRefs: useRef(currentContext.childrenRefs) });\n return <ClickAwayAncestorContext.Provider value={currentContext}>{children}</ClickAwayAncestorContext.Provider>;\n};\nClickAwayProvider.displayName = 'ClickAwayProvider';\n","import React from 'react';\nimport type { PortalInit } from '@lumx/core/js/utils/Portal';\n\nexport type { PortalInit, PortalProviderProps } from '@lumx/core/js/utils/Portal';\n\nexport const PortalContext = React.createContext<PortalInit>(() => ({ container: document.body }));\n\nexport interface ReactPortalProviderProps {\n children?: React.ReactNode;\n value: PortalInit;\n}\n\n/**\n * Customize where <Portal> wrapped elements render (tooltip, popover, dialog, etc.)\n */\nexport const PortalProvider: React.FC<ReactPortalProviderProps> = PortalContext.Provider;\n","import React from 'react';\nimport { createPortal } from 'react-dom';\nimport { PortalContext } from './PortalProvider';\n\nexport type { PortalProps } from '@lumx/core/js/utils/Portal';\n\nexport interface ReactPortalProps {\n enabled?: boolean;\n children: React.ReactNode;\n}\n\n/**\n * Render children in a portal outside the current DOM position\n * (defaults to `document.body` but can be customized with the PortalContextProvider)\n */\nexport const Portal: React.FC<ReactPortalProps> = ({ children, enabled = true }) => {\n const init = React.useContext(PortalContext);\n const context = React.useMemo(\n () => {\n return enabled ? init() : null;\n },\n // Only update on 'enabled'\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [enabled],\n );\n\n React.useLayoutEffect(() => {\n return context?.teardown;\n }, [context?.teardown, enabled]);\n\n const { container } = context ?? {};\n if (!container || typeof container === 'string') {\n return <>{children}</>;\n }\n return createPortal(children, container);\n};\n"],"names":["DisabledStateContext","React","createContext","state","DisabledStateProvider","children","value","_jsx","Provider","useDisabledStateContext","useContext","CLICK_AWAY_EVENT_TYPES","isClickAway","targets","elements","some","element","Node","target","contains","setupClickAway","getElements","callback","undefined","listener","evt","composedPath","filter","t","forEach","evtType","document","addEventListener","removeEventListener","useClickAway","childrenRefs","useEffect","refs","current","map","ref","Boolean","ClickAwayAncestorContext","ClickAwayProvider","parentRef","parentContext","currentContext","useMemo","context","addRefs","newChildrenRefs","push","currentRefs","useRef","displayName","PortalContext","container","body","PortalProvider","Portal","enabled","init","useLayoutEffect","teardown","_Fragment","createPortal"],"mappings":"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"}
package/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import * as _lumx_core_js_types from '@lumx/core/js/types';
4
4
  import { GenericProps as GenericProps$1, HasTheme as HasTheme$1, ValueOf as ValueOf$1, PropsToOverride, HasAriaDisabled as HasAriaDisabled$1, HasRequiredLinkHref as HasRequiredLinkHref$1, HasClassName as HasClassName$1, HasCloseMode as HasCloseMode$1, JSXElement as JSXElement$1, CommonRef as CommonRef$1, Falsy, HeadingElement as HeadingElement$1, HasAriaLabelOrLabelledBy as HasAriaLabelOrLabelledBy$1 } from '@lumx/core/js/types';
5
5
  export * from '@lumx/core/js/types';
6
6
  import * as React$1 from 'react';
7
- import React__default, { Ref, ReactElement, ReactNode, SyntheticEvent, MouseEventHandler, KeyboardEventHandler, RefObject, SetStateAction, Key, CSSProperties, HTMLInputTypeAttribute, ComponentProps, ElementType as ElementType$1, ImgHTMLAttributes, AriaAttributes as AriaAttributes$1 } from 'react';
7
+ import React__default, { Ref, ReactElement, ReactNode, SyntheticEvent, MouseEventHandler, KeyboardEventHandler, RefObject, SetStateAction, Key, CSSProperties, ElementType as ElementType$1, HTMLInputTypeAttribute, ComponentProps, ImgHTMLAttributes, AriaAttributes as AriaAttributes$1 } from 'react';
8
8
  import * as react_jsx_runtime from 'react/jsx-runtime';
9
9
 
10
10
  /** LumX Component Type. */
@@ -1614,6 +1614,8 @@ interface ComboboxOptionMoreInfoProps$1 extends HasClassName {
1614
1614
  onMouseEnter?(): void;
1615
1615
  /** Mouse leave callback. */
1616
1616
  onMouseLeave?(): void;
1617
+ /** Props forwarded to the IconButton. */
1618
+ buttonProps?: Record<string, any>;
1617
1619
  }
1618
1620
  /**
1619
1621
  * Props that React/Vue wrappers need to re-declare with framework-specific types.
@@ -1628,6 +1630,8 @@ interface ComboboxOptionMoreInfoProps extends ReactToJSX<ComboboxOptionMoreInfoP
1628
1630
  children?: ReactNode;
1629
1631
  /** Callback when the popover opens or closes. */
1630
1632
  onToggle?(isOpen: boolean): void;
1633
+ /** Props forwarded to the IconButton. */
1634
+ buttonProps?: Partial<IconButtonProps>;
1631
1635
  }
1632
1636
 
1633
1637
  /**
@@ -1665,6 +1669,8 @@ interface ComboboxOptionProps$1 extends HasClassName {
1665
1669
  hidden?: boolean;
1666
1670
  /** On click callback. */
1667
1671
  handleClick?(): void;
1672
+ /** Extra props forwarded to the inner action element (e.g. link props when as="a"). */
1673
+ actionProps?: Record<string, any>;
1668
1674
  /** ref to the root <li> element. */
1669
1675
  ref?: CommonRef;
1670
1676
  /** The value for this option (used for selection). */
@@ -1674,7 +1680,7 @@ interface ComboboxOptionProps$1 extends HasClassName {
1674
1680
  * Props that React/Vue wrappers need to re-declare with framework-specific types.
1675
1681
  * Used by `ReactToJSX<ComboboxOptionProps, ComboboxOptionPropsToOverride>`.
1676
1682
  */
1677
- type ComboboxOptionPropsToOverride = 'before' | 'after' | 'children' | 'tooltipProps';
1683
+ type ComboboxOptionPropsToOverride = 'before' | 'after' | 'children' | 'tooltipProps' | 'actionProps';
1678
1684
 
1679
1685
  declare const ARIA_LINK_MODES: readonly ["aria-describedby", "aria-labelledby"];
1680
1686
 
@@ -1714,6 +1720,10 @@ interface TooltipProps extends GenericProps$1, TooltipProps$1 {
1714
1720
  */
1715
1721
  declare const Tooltip: Comp<TooltipProps, HTMLDivElement>;
1716
1722
 
1723
+ /**
1724
+ * Props forwarded to the inner action element (button or link).
1725
+ */
1726
+ type ComboboxOptionActionProps$1<E extends ElementType$1 = 'button'> = HasPolymorphicAs$1<E> & HasRequiredLinkHref$1<E>;
1717
1727
  /**
1718
1728
  * Props for Combobox.Option component.
1719
1729
  */
@@ -1732,6 +1742,8 @@ interface ComboboxOptionProps extends GenericProps$1, ReactToJSX<ComboboxOptionP
1732
1742
  after?: ReactNode;
1733
1743
  /** Props forwarded to a Tooltip wrapping the role="option" / role="gridcell" trigger element. */
1734
1744
  tooltipProps?: Partial<TooltipProps>;
1745
+ /** Props forwarded to the inner action element (e.g. `{ as: 'a', href: '/foo' }`). */
1746
+ actionProps?: ComboboxOptionActionProps$1<any>;
1735
1747
  }
1736
1748
 
1737
1749
  /**
@@ -1985,6 +1997,38 @@ declare namespace ComboboxProvider {
1985
1997
  var displayName: string;
1986
1998
  }
1987
1999
 
2000
+ /** Map of combobox event names to their payload types. */
2001
+ interface ComboboxEventMap {
2002
+ /** Fired when the combobox open state changes. Payload: whether the combobox is open. */
2003
+ open: boolean;
2004
+ /** Fired when the active descendant changes (visual focus). Payload: the option id or null. */
2005
+ activeDescendantChange: string | null;
2006
+ /**
2007
+ * Fired when the visible option count transitions between empty and non-empty.
2008
+ * Payload: whether the list is empty plus the current input value.
2009
+ */
2010
+ emptyChange: {
2011
+ isEmpty?: boolean;
2012
+ inputValue?: string;
2013
+ } | undefined;
2014
+ /**
2015
+ * Fired immediately when the aggregate loading state changes (skeleton count transitions
2016
+ * between 0 and >0). Used for empty suppression in ComboboxState and for aria-busy on the listbox.
2017
+ */
2018
+ loadingChange: boolean;
2019
+ /**
2020
+ * Fired after a 500ms debounce when loading persists, or immediately when loading ends.
2021
+ * Used to control the loading message text in the live region (ComboboxState).
2022
+ */
2023
+ loadingAnnouncement: boolean;
2024
+ }
2025
+
2026
+ /**
2027
+ * Hook to subscribe to a combobox event via the handle's subscriber system.
2028
+ * Re-subscribes when the handle changes (e.g. trigger mount/unmount).
2029
+ */
2030
+ declare function useComboboxEvent<K extends keyof ComboboxEventMap>(event: K, initialValue: ComboboxEventMap[K]): ComboboxEventMap[K];
2031
+
1988
2032
  /**
1989
2033
  * Props for Combobox.Button component.
1990
2034
  *
@@ -2066,6 +2110,8 @@ declare const Combobox: {
2066
2110
  };
2067
2111
  /** Visual separator between option groups (alias for ListDivider). Purely decorative — invisible to screen readers. */
2068
2112
  Divider: Comp<ListDividerProps, HTMLLIElement>;
2113
+ /** Hook to subscribe to combobox events. Must be used within a Combobox.Provider. */
2114
+ useComboboxEvent: typeof useComboboxEvent;
2069
2115
  };
2070
2116
 
2071
2117
  /**
package/index.js CHANGED
@@ -9,7 +9,7 @@ import ReactDOM__default from 'react-dom';
9
9
  import { classNames, onEscapePressed, onEnterPressed as onEnterPressed$1, detectHorizontalSwipe } from '@lumx/core/js/utils';
10
10
  import last from 'lodash/last.js';
11
11
  import pull from 'lodash/pull.js';
12
- import { u as useDisabledStateContext, P as Portal, C as ClickAwayProvider } from './_internal/C9YCH96e.js';
12
+ import { u as useDisabledStateContext, P as Portal, C as ClickAwayProvider } from './_internal/Dpulze-1.js';
13
13
  import isEmpty from 'lodash/isEmpty.js';
14
14
  import get from 'lodash/get.js';
15
15
  import { getDisabledState } from '@lumx/core/js/utils/disabledState';
@@ -1637,6 +1637,7 @@ const IconButton = forwardRef((props, ref) => {
1637
1637
  tooltipProps,
1638
1638
  hideTooltip,
1639
1639
  label,
1640
+ theme = defaultTheme,
1640
1641
  ...forwardedProps
1641
1642
  } = props;
1642
1643
  const {
@@ -1654,7 +1655,7 @@ const IconButton = forwardRef((props, ref) => {
1654
1655
  ...tooltipProps,
1655
1656
  children: IconButton$1({
1656
1657
  ref,
1657
- theme: defaultTheme,
1658
+ theme,
1658
1659
  ...disabledStateProps,
1659
1660
  ...restOfOtherProps,
1660
1661
  handleClick: onClick,
@@ -3207,7 +3208,7 @@ function setupListbox(handle, signal, notify) {
3207
3208
  *
3208
3209
  * @see https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
3209
3210
  *
3210
- * @param callbacks Callbacks for select and open/close events.
3211
+ * @param callbacks Callbacks invoked on combobox events (e.g. option selection).
3211
3212
  * @param options Options for configuring the shared combobox behavior.
3212
3213
  * @param onTriggerAttach Optional callback invoked when the trigger is registered and the signal is ready.
3213
3214
  * Used by mode-specific wrappers (setupComboboxInput/Button) to automatically
@@ -3343,17 +3344,19 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3343
3344
  case 'Enter':
3344
3345
  if (handle.isOpen && nav?.hasActiveItem && nav.activeItem) {
3345
3346
  if (!isOptionDisabled(nav.activeItem)) {
3346
- if (nav.type === 'grid' && isActionCell(nav.activeItem)) {
3347
- // Action cell: programmatically click it.
3348
- nav.activeItem.click();
3349
- } else {
3350
- // Option cell: select the option.
3351
- handle.select(nav.activeItem);
3352
- }
3347
+ // Click the active item. For option cells, the delegated click handler
3348
+ // on the listbox will call handle.select() and handle closing.
3349
+ // For action cells and link options, the native click fires too.
3350
+ nav.activeItem.click();
3353
3351
  }
3354
- }
3355
- // In multi-select mode, keep open after selection; otherwise toggle.
3356
- if (!handle.isMultiSelect) {
3352
+ // Close for single-select. For option cells the delegated handler
3353
+ // already closed, but setIsOpen(false) is idempotent. For action cells
3354
+ // and disabled options, the delegated handler did NOT close, so this is needed.
3355
+ if (!handle.isMultiSelect) {
3356
+ handle.setIsOpen(false);
3357
+ }
3358
+ } else if (!handle.isMultiSelect) {
3359
+ // No active item — toggle open/close.
3357
3360
  handle.setIsOpen(!handle.isOpen);
3358
3361
  }
3359
3362
  flag = true;
@@ -3406,10 +3409,12 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3406
3409
  flag = true;
3407
3410
  break;
3408
3411
  case 'Tab':
3409
- // Select the active option (if any) and close. Let Tab propagate.
3412
+ // Click the active option (if any) and close. Let Tab propagate.
3410
3413
  if (nav?.hasActiveItem && nav.activeItem && !isOptionDisabled(nav.activeItem)) {
3411
- handle.select(nav.activeItem);
3414
+ nav.activeItem.click();
3412
3415
  }
3416
+ // The delegated click handler closes for single-select, but for multi-select
3417
+ // or when no item is active, we still need to explicitly close.
3413
3418
  handle.setIsOpen(false);
3414
3419
  break;
3415
3420
  case 'PageUp':
@@ -3493,7 +3498,7 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
3493
3498
  select(option) {
3494
3499
  callbacks.onSelect({
3495
3500
  value: option ? getOptionValue(option) : ''
3496
- });
3501
+ }, handle);
3497
3502
  },
3498
3503
  registerOption(element, callback) {
3499
3504
  const filterLower = filterValue.toLowerCase();
@@ -3775,13 +3780,8 @@ function setupComboboxButton(button, callbacks) {
3775
3780
  case ' ':
3776
3781
  // Space acts like Enter in button mode.
3777
3782
  if (combobox.isOpen && nav?.hasActiveItem && nav.activeItem) {
3778
- if (nav.type === 'grid' && isActionCell(nav.activeItem)) {
3779
- // Action cell: programmatically click it.
3780
- nav.activeItem.click();
3781
- } else {
3782
- combobox.select(nav.activeItem);
3783
- combobox.setIsOpen(false);
3784
- }
3783
+ // Click the active item — delegated handler handles select + close.
3784
+ nav.activeItem.click();
3785
3785
  } else {
3786
3786
  combobox.setIsOpen(true);
3787
3787
  }
@@ -6563,44 +6563,58 @@ const ComboboxButton = Object.assign(forwardRefPolymorphic((props, ref) => {
6563
6563
  * Handles: Home/End (text cursor), ArrowLeft/Right (clear active descendant),
6564
6564
  * filtering (on input and on open), and focus behavior.
6565
6565
  *
6566
- * @param input The input element to use as the combobox trigger.
6567
- * @param callbacks Callbacks for select and open/close events.
6568
- * @param options Options for configuring the input-mode controller.
6566
+ * @param input The input element to use as the combobox trigger.
6567
+ * @param options Options and callbacks for configuring the input-mode controller.
6569
6568
  * @returns A ComboboxHandle for interacting with the combobox.
6570
6569
  */
6571
- function setupComboboxInput(input, callbacks, options = {}) {
6570
+ function setupComboboxInput(input, options) {
6572
6571
  const {
6573
- autoFilter = true
6572
+ autoFilter = true,
6573
+ onSelect: optionOnSelect
6574
6574
  } = options;
6575
- const handle = setupCombobox(callbacks, {
6575
+
6576
+ /**
6577
+ * True when the current input value came from user typing (real InputEvent).
6578
+ * False when the value was set programmatically (select, clear, etc.).
6579
+ * Used to decide whether to re-apply the filter when the combobox opens.
6580
+ */
6581
+ let userHasTyped = false;
6582
+
6583
+ /**
6584
+ * Wraps the consumer's onSelect to perform input-mode side effects after selection:
6585
+ * clears the active descendant, resets the filter, and re-opens the popup.
6586
+ */
6587
+ const onSelect = (option, combobox) => {
6588
+ optionOnSelect(option, combobox);
6589
+
6590
+ // Clear the active item. In multi-select, keep visual focus so the
6591
+ // user can continue navigating after selection.
6592
+ if (!combobox.isMultiSelect) {
6593
+ combobox.focusNav?.clear();
6594
+ }
6595
+ userHasTyped = false;
6596
+ combobox.setIsOpen(true);
6597
+ if (autoFilter) {
6598
+ combobox.setFilter('');
6599
+ }
6600
+ };
6601
+ const handle = setupCombobox({
6602
+ onSelect
6603
+ }, {
6576
6604
  wrapNavigation: true
6577
6605
  }, (combobox, signal) => {
6578
- /**
6579
- * True when the current input value came from user typing (real InputEvent).
6580
- * False when the value was set programmatically (select, clear, etc.).
6581
- * Used to decide whether to re-apply the filter when the combobox opens.
6582
- */
6583
- let userHasTyped = false;
6584
6606
  signal.addEventListener('abort', () => {
6585
6607
  userHasTyped = false;
6586
6608
  });
6587
6609
 
6588
- // Filter on user typing; reset filter on programmatic input changes.
6589
- // Real user input fires an `InputEvent` (with `inputType`), while
6590
- // programmatic changes (selection bridge, clear, etc.) dispatch a
6591
- // plain `Event('input')` — we use this to distinguish the two.
6610
+ // Filter on real user typing (InputEvent with `inputType`).
6592
6611
  input.addEventListener('input', event => {
6593
- const isUserTyping = event instanceof InputEvent;
6594
-
6595
- // Clear the active item. In multi-select with a programmatic change, keep
6596
- // visual focus so the user can continue navigating after selection.
6597
- if (isUserTyping || !combobox.isMultiSelect) {
6598
- combobox.focusNav?.clear();
6599
- }
6600
- userHasTyped = isUserTyping;
6612
+ if (!(event instanceof InputEvent)) return;
6613
+ combobox.focusNav?.clear();
6614
+ userHasTyped = true;
6601
6615
  combobox.setIsOpen(true);
6602
6616
  if (autoFilter) {
6603
- combobox.setFilter(isUserTyping ? input.value : '');
6617
+ combobox.setFilter(input.value);
6604
6618
  }
6605
6619
  }, {
6606
6620
  signal
@@ -6694,6 +6708,7 @@ const ComboboxInput$1 = (props, {
6694
6708
  textFieldRef,
6695
6709
  toggleButtonProps,
6696
6710
  handleToggle,
6711
+ theme,
6697
6712
  ...forwardedProps
6698
6713
  } = props;
6699
6714
  return /*#__PURE__*/jsx(TextField, {
@@ -6706,8 +6721,10 @@ const ComboboxInput$1 = (props, {
6706
6721
  inputRef: inputRef,
6707
6722
  textFieldRef: textFieldRef,
6708
6723
  autoComplete: "off",
6724
+ theme: theme,
6709
6725
  afterElement: toggleButtonProps ? /*#__PURE__*/jsx(IconButton, {
6710
6726
  ...toggleButtonProps,
6727
+ theme: theme,
6711
6728
  emphasis: "low",
6712
6729
  size: "s",
6713
6730
  icon: isOpen ? mdiChevronUp : mdiChevronDown,
@@ -7229,32 +7246,6 @@ TextField.displayName = COMPONENT_NAME$1d;
7229
7246
  TextField.className = CLASSNAME$1c;
7230
7247
  TextField.defaultProps = DEFAULT_PROPS$Z;
7231
7248
 
7232
- /**
7233
- * Set an input's value using the native HTMLInputElement.prototype.value setter,
7234
- * bypassing React's controlled input value tracker, then dispatch an `input` event
7235
- * so React's onChange fires.
7236
- *
7237
- * React wraps the `value` property setter on controlled inputs to track the "last
7238
- * known value." If you set `input.value` through React's wrapper with the same value
7239
- * React last rendered, the subsequent `input` event is suppressed. Using the native
7240
- * setter bypasses this tracker, ensuring React detects a value change and fires onChange.
7241
- */
7242
- function setNativeInputValue(input, value) {
7243
- const nativeSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
7244
- if (nativeSetter) {
7245
- nativeSetter.call(input, value);
7246
- } else {
7247
- // Fallback: set directly (may not trigger React onChange on controlled inputs)
7248
- // eslint-disable-next-line no-param-reassign
7249
- input.value = value;
7250
- }
7251
-
7252
- // Dispatch input event so React's onChange fires
7253
- input.dispatchEvent(new Event('input', {
7254
- bubbles: true
7255
- }));
7256
- }
7257
-
7258
7249
  /**
7259
7250
  * Props for Combobox.Input component.
7260
7251
  * Note: role, aria-autocomplete, aria-controls, aria-expanded are set internally and cannot be overridden.
@@ -7285,9 +7276,11 @@ const ComboboxInput = forwardRef((props, ref) => {
7285
7276
  const internalInputRef = useRef(null);
7286
7277
  const mergedInputRef = useMergeRefs(externalInputRef, internalInputRef);
7287
7278
 
7288
- // Keep onSelect in a ref to avoid re-creating the handle on every render
7279
+ // Keep callbacks in refs to avoid re-creating the handle on every render
7289
7280
  const onSelectRef = useRef(onSelect);
7290
7281
  onSelectRef.current = onSelect;
7282
+ const onChangeRef = useRef(otherProps.onChange);
7283
+ onChangeRef.current = otherProps.onChange;
7291
7284
 
7292
7285
  // Create the combobox handle with input-mode controller on mount
7293
7286
  useEffect(() => {
@@ -7295,11 +7288,10 @@ const ComboboxInput = forwardRef((props, ref) => {
7295
7288
  if (!input) return undefined;
7296
7289
  const handle = setupComboboxInput(input, {
7297
7290
  onSelect(option) {
7298
- // Bridge to React's controlled input flow.
7299
- setNativeInputValue(input, option.value);
7291
+ // Update controlled value through React's normal onChange flow.
7292
+ onChangeRef.current?.(option.value);
7300
7293
  onSelectRef.current?.(option);
7301
- }
7302
- }, {
7294
+ },
7303
7295
  autoFilter
7304
7296
  });
7305
7297
  setHandle(handle);
@@ -7737,6 +7729,7 @@ const ComboboxOption$1 = (props, {
7737
7729
  isGrid,
7738
7730
  isSelected,
7739
7731
  handleClick,
7732
+ actionProps,
7740
7733
  ref,
7741
7734
  tooltipProps,
7742
7735
  value,
@@ -7749,7 +7742,8 @@ const ComboboxOption$1 = (props, {
7749
7742
  itemRole = isGrid ? 'row' : 'none';
7750
7743
  }
7751
7744
  const actionElement = ListItemAction$1({
7752
- as: 'p',
7745
+ as: 'button',
7746
+ ...actionProps,
7753
7747
  id,
7754
7748
  className: element$H('trigger'),
7755
7749
  handleClick,
@@ -7832,6 +7826,7 @@ const ComboboxOption = forwardRef((props, ref) => {
7832
7826
  before,
7833
7827
  after,
7834
7828
  tooltipProps,
7829
+ actionProps,
7835
7830
  onClick,
7836
7831
  ...forwardedProps
7837
7832
  } = props;
@@ -7865,6 +7860,7 @@ const ComboboxOption = forwardRef((props, ref) => {
7865
7860
  return ComboboxOption$1({
7866
7861
  ...forwardedProps,
7867
7862
  ref: mergedRef,
7863
+ actionProps,
7868
7864
  hidden: isFiltered,
7869
7865
  value,
7870
7866
  description,
@@ -8011,7 +8007,8 @@ const ComboboxOptionMoreInfo$1 = (props, {
8011
8007
  popoverId,
8012
8008
  ref,
8013
8009
  onMouseEnter,
8014
- onMouseLeave
8010
+ onMouseLeave,
8011
+ buttonProps
8015
8012
  } = props;
8016
8013
  return /*#__PURE__*/jsxs(Fragment, {
8017
8014
  children: [/*#__PURE__*/jsx(IconButton, {
@@ -8026,7 +8023,8 @@ const ComboboxOptionMoreInfo$1 = (props, {
8026
8023
  // Keyboard accessibility is handled via combobox keyboard highlighting.
8027
8024
  ,
8028
8025
  "aria-hidden": true,
8029
- label: ""
8026
+ label: "",
8027
+ ...buttonProps
8030
8028
  }), /*#__PURE__*/jsx(Popover, {
8031
8029
  id: popoverId,
8032
8030
  className: element$G('popover'),
@@ -8720,6 +8718,7 @@ const ComboboxOptionMoreInfo = props => {
8720
8718
  const {
8721
8719
  children,
8722
8720
  onToggle,
8721
+ buttonProps,
8723
8722
  ...forwardedProps
8724
8723
  } = props;
8725
8724
  const ref = useRef(null);
@@ -8750,6 +8749,7 @@ const ComboboxOptionMoreInfo = props => {
8750
8749
  isOpen,
8751
8750
  popoverId,
8752
8751
  children,
8752
+ buttonProps,
8753
8753
  onMouseEnter: () => setIsHovered(true),
8754
8754
  onMouseLeave: () => setIsHovered(false)
8755
8755
  }, {
@@ -10018,7 +10018,9 @@ const Combobox = {
10018
10018
  Section: ComboboxSection,
10019
10019
  State: ComboboxState,
10020
10020
  /** Visual separator between option groups (alias for ListDivider). Purely decorative — invisible to screen readers. */
10021
- Divider: ListDivider
10021
+ Divider: ListDivider,
10022
+ /** Hook to subscribe to combobox events. Must be used within a Combobox.Provider. */
10023
+ useComboboxEvent
10022
10024
  };
10023
10025
 
10024
10026
  /**