@lumx/react 4.19.1-next.0 → 4.20.0-next.1

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.
@@ -210,12 +210,15 @@ const InfiniteScroll = ({
210
210
  const {
211
211
  current: element
212
212
  } = elementRef;
213
- if (!element) {
213
+ if (!element || !callback) {
214
214
  return undefined;
215
215
  }
216
216
  return setupInfiniteScrollObserver(element, callback, options);
217
+ // `options?.root` starts as `null` (before the scrollable list's ref attaches) and is
218
+ // then set to the real element — must be a dep, or the observer gets stuck watching
219
+ // the wrong root (falling back to the viewport) for the component's whole lifetime.
217
220
  // eslint-disable-next-line react-hooks/exhaustive-deps
218
- }, [elementRef.current, callback]);
221
+ }, [elementRef.current, callback, options?.root]);
219
222
  return InfiniteScroll$1({
220
223
  ref: elementRef
221
224
  });
@@ -259,4 +262,4 @@ const Portal = ({
259
262
  };
260
263
 
261
264
  export { ClickAwayProvider as C, DisabledStateProvider as D, InfiniteScroll as I, Portal as P, PortalProvider as a, useDisabledStateContext as u };
262
- //# sourceMappingURL=BvaFEHZn.js.map
265
+ //# sourceMappingURL=CcLi-Wuu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CcLi-Wuu.js","sources":["../../src/utils/disabled/DisabledStateContext.tsx","../../../lumx-core/src/js/utils/ClickAway/index.ts","../../src/hooks/useClickAway.tsx","../../src/utils/ClickAwayProvider/ClickAwayProvider.tsx","../../../lumx-core/src/js/utils/InfiniteScroll/setupInfiniteScrollObserver.ts","../../../lumx-core/src/js/utils/InfiniteScroll/index.tsx","../../src/utils/InfiniteScroll/InfiniteScroll.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","type EventCallback = (evt?: Event) => void;\n\n/**\n * Sets up an IntersectionObserver on the given element.\n * Calls `callback` when at least one observed entry is intersecting.\n * Returns a cleanup function that unobserves the element.\n */\nexport function setupInfiniteScrollObserver(\n element: Element,\n callback: EventCallback,\n options?: IntersectionObserverInit,\n): () => void {\n const observer = new IntersectionObserver((entries = []) => {\n const hasIntersection = entries.some((entry) => entry.isIntersecting);\n\n if (!hasIntersection) {\n return;\n }\n\n callback();\n }, options);\n\n observer.observe(element);\n\n return () => {\n observer.unobserve(element);\n };\n}\n","import type { CommonRef } from '../../types';\n\nexport { setupInfiniteScrollObserver } from './setupInfiniteScrollObserver';\n\nexport const INFINITE_SCROLL_CLASSNAME = 'lumx-infinite-scroll-anchor';\n\nexport interface InfiniteScrollProps {\n /**\n * Callback when infinite scroll component is in view.\n * Omit (e.g. while loading) to temporarily disable without changing the callback's\n * identity when re-enabled — keeps the underlying observer stable.\n */\n // eslint-disable-next-line react/no-unused-prop-types\n callback?(evt?: Event): void;\n /** Customize intersection observer option */\n // eslint-disable-next-line react/no-unused-prop-types\n options?: IntersectionObserverInit;\n}\n\n/**\n * Framework-agnostic InfiniteScroll sentinel component.\n *\n * Renders a tiny invisible div that triggers a callback when it enters the viewport\n * (or intersects its root element) via IntersectionObserver.\n *\n * The div has a small height (4px) to avoid issues when a browser zoom is applied,\n * where a zero-height element might not trigger IntersectionObserver reliably.\n */\nexport const InfiniteScroll = ({ ref }: { ref?: CommonRef }) => (\n // In order to avoid issues when a zoom is added to the browser, we add a small height to the div so that\n // the intersection has a higher chance of working correctly.\n <div ref={ref} aria-hidden=\"true\" className={INFINITE_SCROLL_CLASSNAME} style={{ height: '4px' }} />\n);\n","import React, { useEffect } from 'react';\nimport {\n InfiniteScroll as UI,\n type InfiniteScrollProps,\n setupInfiniteScrollObserver,\n} from '@lumx/core/js/utils/InfiniteScroll';\n\nexport type { InfiniteScrollProps };\n\n/**\n * Handles basic callback pattern by using intersection observers.\n */\nexport const InfiniteScroll: React.FC<InfiniteScrollProps> = ({ callback, options }) => {\n const elementRef = React.useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n const { current: element } = elementRef;\n if (!element || !callback) {\n return undefined;\n }\n\n return setupInfiniteScrollObserver(element, callback, options);\n // `options?.root` starts as `null` (before the scrollable list's ref attaches) and is\n // then set to the real element — must be a dep, or the observer gets stuck watching\n // the wrong root (falling back to the viewport) for the component's whole lifetime.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [elementRef.current, callback, options?.root]);\n\n return UI({ ref: elementRef });\n};\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","setupInfiniteScrollObserver","options","observer","IntersectionObserver","entries","hasIntersection","entry","isIntersecting","observe","unobserve","INFINITE_SCROLL_CLASSNAME","InfiniteScroll","className","style","height","elementRef","root","UI","PortalContext","container","body","PortalProvider","Portal","enabled","init","useLayoutEffect","teardown","_Fragment","createPortal"],"mappings":"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"}
package/index.d.ts CHANGED
@@ -5271,14 +5271,17 @@ interface TabListProps$1 extends HasClassName, HasTheme {
5271
5271
  layout?: TabListLayout;
5272
5272
  /** Position of the tabs in the list (requires 'clustered' layout). */
5273
5273
  position?: Alignment;
5274
+ /** `role` applied to the inner `__links` container */
5275
+ role?: 'navigation' | 'tablist';
5274
5276
  /** ref to the wrapper element */
5275
5277
  ref?: CommonRef;
5276
5278
  }
5279
+ type TabListPropsToOverride = 'role';
5277
5280
 
5278
5281
  /**
5279
5282
  * Defines the props of the component.
5280
5283
  */
5281
- interface TabListProps extends GenericProps$1, ReactToJSX<TabListProps$1> {
5284
+ interface TabListProps extends GenericProps$1, ReactToJSX<TabListProps$1, TabListPropsToOverride> {
5282
5285
  }
5283
5286
 
5284
5287
  /**
@@ -5286,6 +5289,8 @@ interface TabListProps extends GenericProps$1, ReactToJSX<TabListProps$1> {
5286
5289
  *
5287
5290
  * Implements WAI-ARIA `tablist` role {@see https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html#rps_label}
5288
5291
  *
5292
+ * Renders a `role="navigation"` landmark instead when used outside a `TabProvider` (nav-link mode).
5293
+ *
5289
5294
  * @param props Component props.
5290
5295
  * @param ref Component ref.
5291
5296
  * @return React element.
@@ -5293,9 +5298,14 @@ interface TabListProps extends GenericProps$1, ReactToJSX<TabListProps$1> {
5293
5298
  declare const TabList: Comp<TabListProps, HTMLDivElement>;
5294
5299
 
5295
5300
  /**
5296
- * Defines the props of the component.
5301
+ * Element a `Tab` can render as: `'button'` (default, classic tab), `'a'` (nav-link, `href`
5302
+ * required via {@link HasRequiredLinkHref}), or any `ElementType` (nav-link, e.g. a router `Link`).
5297
5303
  */
5298
- interface TabProps$1 extends HasClassName {
5304
+ type TabElement = 'a' | 'button' | ElementType;
5305
+ /**
5306
+ * Defines the props of the component (independent of the `as` element).
5307
+ */
5308
+ interface CommonTabProps extends HasClassName {
5299
5309
  /** Children are not supported. */
5300
5310
  children?: never;
5301
5311
  /** Icon (SVG path). */
@@ -5306,6 +5316,11 @@ interface TabProps$1 extends HasClassName {
5306
5316
  id?: string;
5307
5317
  /** Whether the tab is active or not. */
5308
5318
  isActive?: boolean;
5319
+ /**
5320
+ * WAI-ARIA `tab` role, resolved by the framework wrapper from `TabProvider` presence:
5321
+ * `'tab'` inside a provider (classic tab mode), `undefined` outside (nav-link mode).
5322
+ */
5323
+ role?: 'tab';
5309
5324
  /** Whether the component is disabled or not. */
5310
5325
  isDisabled?: boolean;
5311
5326
  /** Label content. */
@@ -5326,23 +5341,38 @@ interface TabProps$1 extends HasClassName {
5326
5341
  tabIndexProp?: string;
5327
5342
  /** Name of the prop used to attach the keypress event (framework-dependent). */
5328
5343
  keyPressProp?: string;
5329
- /** ID applied to the tab button element (for aria-labelledby on the panel). */
5330
- tabId?: string;
5331
5344
  /** ID of the associated tab panel (for aria-controls). */
5332
5345
  tabPanelId?: string;
5333
5346
  /** Icon component injected by the framework wrapper. */
5334
5347
  Icon: any;
5335
5348
  /** Text component injected by the framework wrapper. */
5336
5349
  Text: any;
5337
- /** Forward ref to the underlying button element. */
5350
+ /** Forward ref to the underlying element. */
5338
5351
  ref?: CommonRef;
5339
5352
  }
5340
- type TabPropsToOverride = 'isAnyDisabled' | 'shouldActivateOnFocus' | 'changeToTab' | 'tabIndex' | 'tabIndexProp' | 'keyPressProp' | 'tabId' | 'tabPanelId' | 'Icon' | 'Text';
5353
+ /**
5354
+ * Polymorphic `as` surface for `Tab`. Not `HasPolymorphicAs<E>`: core JSX is type-checked under
5355
+ * both the React and Vue namespaces, so spreading React DOM prop types would break the Vue
5356
+ * cross-check. `href` is required at compile time when `as === 'a'` (via {@link HasRequiredLinkHref}).
5357
+ */
5358
+ type TabAsProps<E extends TabElement = 'button'> = {
5359
+ as?: E;
5360
+ } & (E extends 'a' ? HasRequiredLinkHref<'a'> : unknown);
5361
+ /**
5362
+ * Props of the `Tab` component. Polymorphic on `as` (element selection only): `as` picks the
5363
+ * rendered element, while **mode is driven by `TabProvider` presence** (via the wrapper-resolved
5364
+ * `role`), not by `as`. The `href` conditional's false branch is `unknown` (not a record) to avoid
5365
+ * widening `keyof TabProps`.
5366
+ */
5367
+ type TabProps$1<E extends TabElement = 'button'> = CommonTabProps & TabAsProps<E>;
5368
+ type TabPropsToOverride = 'as' | 'role' | 'isAnyDisabled' | 'shouldActivateOnFocus' | 'changeToTab' | 'tabIndex' | 'tabIndexProp' | 'keyPressProp' | 'tabPanelId' | 'Icon' | 'Text';
5341
5369
 
5342
5370
  /**
5343
- * Defines the props of the component.
5371
+ * Defines the props of the component. Polymorphic on `as` (element selection only): `as` picks the
5372
+ * rendered element (`as="a"` with required `href`, or `as={RouterLink}`), while the mode
5373
+ * (`role="tab"` vs nav-link) is driven by `TabProvider` presence. The forwarded ref type follows `as`.
5344
5374
  */
5345
- interface TabProps extends GenericProps$1, ReactToJSX<TabProps$1, TabPropsToOverride> {
5375
+ type TabProps<E extends ElementType$1 = 'button'> = GenericProps$1 & HasPolymorphicAs$1<E> & HasRequiredLinkHref$1<E> & ReactToJSX<TabProps$1, TabPropsToOverride> & {
5346
5376
  /** Children are not supported. */
5347
5377
  children?: never;
5348
5378
  /** Icon (SVG path). */
@@ -5357,17 +5387,44 @@ interface TabProps extends GenericProps$1, ReactToJSX<TabProps$1, TabPropsToOver
5357
5387
  isDisabled?: boolean;
5358
5388
  /** Label content. */
5359
5389
  label: string | ReactNode;
5360
- }
5390
+ };
5361
5391
  /**
5362
5392
  * Tab component.
5363
5393
  *
5364
5394
  * Implements WAI-ARIA `tab` role {@see https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html#rps_label}
5395
+ * when rendered inside a `TabProvider`.
5396
+ *
5397
+ * Outside a `TabProvider` (nav-link mode): plain link, no `role="tab"`/roving tabindex/`changeToTab`
5398
+ * dispatch — only `aria-current` marks the active item.
5365
5399
  *
5366
5400
  * @param props Component props.
5367
- * @param ref Component ref.
5401
+ * @param ref Component ref (type follows `as`).
5368
5402
  * @return React element.
5369
5403
  */
5370
- declare const Tab: Comp<TabProps, HTMLButtonElement>;
5404
+ declare const Tab: (<E extends ElementType$1 = "button">(props: GenericProps$1 & React$1.PropsWithoutRef<React$1.ComponentProps<E>> & {
5405
+ as?: E | undefined;
5406
+ } & HasRequiredLinkHref$1<E> & ReactToJSX<TabProps$1, TabPropsToOverride> & {
5407
+ /** Children are not supported. */
5408
+ children?: never;
5409
+ /** Icon (SVG path). */
5410
+ icon?: IconProps["icon"];
5411
+ /** Icon component properties. */
5412
+ iconProps?: Omit<IconProps, "icon">;
5413
+ /** Native id property. */
5414
+ id?: string;
5415
+ /** Whether the tab is active or not. */
5416
+ isActive?: boolean;
5417
+ /** Whether the component is disabled or not. */
5418
+ isDisabled?: boolean;
5419
+ /** Label content. */
5420
+ label: string | ReactNode;
5421
+ } & React$1.ComponentProps<E> & {
5422
+ ref?: ComponentRef<E> | undefined;
5423
+ }) => React.JSX.Element) & {
5424
+ displayName: string;
5425
+ className: string;
5426
+ defaultProps: Partial<TabProps$1<"button">>;
5427
+ };
5371
5428
 
5372
5429
  /**
5373
5430
  * Defines the props of the component.
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, I as InfiniteScroll } from './_internal/BvaFEHZn.js';
12
+ import { u as useDisabledStateContext, P as Portal, C as ClickAwayProvider, I as InfiniteScroll } from './_internal/CcLi-Wuu.js';
13
13
  import isEmpty from 'lodash/isEmpty.js';
14
14
  import get from 'lodash/get.js';
15
15
  import { getDisabledState as getDisabledState$1 } from '@lumx/core/js/utils/disabledState';
@@ -17991,6 +17991,8 @@ const SelectButton$2 = (props, {
17991
17991
  const isLoadingMore = listStatus === 'loadingMore';
17992
17992
  const isError = listStatus === 'error';
17993
17993
  const isMultiselectable = selectionType === 'multiple';
17994
+ // Prevent firing during loading or error states to avoid duplicate fetches.
17995
+ const isInfiniteScrollEnabled = !listStatus || listStatus === 'idle';
17994
17996
 
17995
17997
  /*
17996
17998
  * Display value: castArray normalizes single/multi value to an array, then resolve
@@ -18029,11 +18031,7 @@ const SelectButton$2 = (props, {
18029
18031
  }, {
18030
18032
  Combobox
18031
18033
  }), onLoadMore && InfiniteScroll && /*#__PURE__*/jsx(InfiniteScroll, {
18032
- callback: () => {
18033
- // Guard: prevent firing during loading or error states to avoid duplicate fetches.
18034
- if (listStatus && listStatus !== 'idle') return;
18035
- onLoadMore();
18036
- },
18034
+ callback: isInfiniteScrollEnabled ? onLoadMore : undefined,
18037
18035
  options: infiniteScrollOptions
18038
18036
  }), isLoadingMore && /*#__PURE__*/jsx(Combobox.OptionSkeleton, {
18039
18037
  count: 1
@@ -18177,7 +18175,13 @@ const SelectButton$1 = React__default.forwardRef((props, ref) => {
18177
18175
  as,
18178
18176
  ...buttonProps
18179
18177
  } = props;
18180
- const [listElement, setListElement] = useState(null);
18178
+ // The list itself doesn't scroll — its ancestor `__scroll` wrapper does. The
18179
+ // IntersectionObserver root must be the actual scrolling/clipping element, or the
18180
+ // sentinel is trivially always "intersecting" its non-clipping `<ul>` parent.
18181
+ const [scrollContainer, setScrollContainer] = useState(null);
18182
+ const setListElement = useCallback(listElement => {
18183
+ setScrollContainer(listElement?.closest(`.${CLASSNAME$17}__scroll`) ?? null);
18184
+ }, []);
18181
18185
  const isMultiple = selectionType === 'multiple';
18182
18186
 
18183
18187
  // Inject core-computed props into the consumer's renderOption result.
@@ -18218,7 +18222,7 @@ const SelectButton$1 = React__default.forwardRef((props, ref) => {
18218
18222
  translations,
18219
18223
  onLoadMore,
18220
18224
  infiniteScrollOptions: {
18221
- root: listElement,
18225
+ root: scrollContainer,
18222
18226
  rootMargin: '100px'
18223
18227
  }
18224
18228
  }, {
@@ -18291,6 +18295,8 @@ const SelectTextField$2 = (props, {
18291
18295
  const isFullLoading = listStatus === 'loading';
18292
18296
  const isLoadingMore = listStatus === 'loadingMore';
18293
18297
  const isError = listStatus === 'error';
18298
+ // Prevent firing during loading or error states to avoid duplicate fetches.
18299
+ const isInfiniteScrollEnabled = !listStatus || listStatus === 'idle';
18294
18300
  return /*#__PURE__*/jsxs(Combobox.Provider, {
18295
18301
  onOpen: onOpen,
18296
18302
  children: [/*#__PURE__*/jsx(Combobox.Input, {
@@ -18321,11 +18327,7 @@ const SelectTextField$2 = (props, {
18321
18327
  }, {
18322
18328
  Combobox
18323
18329
  }), afterOptions, onLoadMore && InfiniteScroll && /*#__PURE__*/jsx(InfiniteScroll, {
18324
- callback: () => {
18325
- // Guard: prevent firing during loading or error states to avoid duplicate fetches.
18326
- if (listStatus && listStatus !== 'idle') return;
18327
- onLoadMore();
18328
- },
18330
+ callback: isInfiniteScrollEnabled ? onLoadMore : undefined,
18329
18331
  options: infiniteScrollOptions
18330
18332
  }), isLoadingMore && /*#__PURE__*/jsx(Combobox.OptionSkeleton, {
18331
18333
  count: 1
@@ -18413,7 +18415,14 @@ const SelectTextField$1 = props => {
18413
18415
  // Wrap the consumer's renderOption to inject core-computed props
18414
18416
  // (`value`, `isSelected`, `description`, `key`) into the returned <Combobox.Option>.
18415
18417
  const wrappedRenderOption = wrapRenderOption(renderOption);
18416
- const [listElement, setListElement] = useState(null);
18418
+
18419
+ // The list itself doesn't scroll — its ancestor `__scroll` wrapper does. The
18420
+ // IntersectionObserver root must be the actual scrolling/clipping element, or the
18421
+ // sentinel is trivially always "intersecting" its non-clipping `<ul>` parent.
18422
+ const [scrollContainer, setScrollContainer] = useState(null);
18423
+ const setListElement = useCallback(listElement => {
18424
+ setScrollContainer(listElement?.closest(`.${CLASSNAME$17}__scroll`) ?? null);
18425
+ }, []);
18417
18426
  const localInputRef = useRef(null);
18418
18427
  const mergedInputRef = useMergeRefs(localInputRef, externalInputRef);
18419
18428
 
@@ -18563,7 +18572,7 @@ const SelectTextField$1 = props => {
18563
18572
  beforeOptions,
18564
18573
  onLoadMore,
18565
18574
  infiniteScrollOptions: {
18566
- root: listElement,
18575
+ root: scrollContainer,
18567
18576
  rootMargin: '100px'
18568
18577
  }
18569
18578
  }, {
@@ -20935,10 +20944,12 @@ const TabList$1 = props => {
20935
20944
  className,
20936
20945
  layout = DEFAULT_PROPS$7.layout,
20937
20946
  position = DEFAULT_PROPS$7.position,
20947
+ role = 'tablist',
20938
20948
  theme,
20939
20949
  ref,
20940
20950
  ...forwardedProps
20941
20951
  } = props;
20952
+ const LinkWrapper = role === 'navigation' ? 'nav' : 'div';
20942
20953
  return /*#__PURE__*/jsx("div", {
20943
20954
  ref: ref,
20944
20955
  ...forwardedProps,
@@ -20947,9 +20958,9 @@ const TabList$1 = props => {
20947
20958
  [`position-${position}`]: Boolean(position),
20948
20959
  [`theme-${theme}`]: Boolean(theme)
20949
20960
  })),
20950
- children: /*#__PURE__*/jsx("div", {
20961
+ children: /*#__PURE__*/jsx(LinkWrapper, {
20951
20962
  className: element$4('links'),
20952
- role: "tablist",
20963
+ role: role,
20953
20964
  "aria-label": ariaLabel,
20954
20965
  children: children
20955
20966
  })
@@ -20961,6 +20972,8 @@ const TabList$1 = props => {
20961
20972
  *
20962
20973
  * Implements WAI-ARIA `tablist` role {@see https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html#rps_label}
20963
20974
  *
20975
+ * Renders a `role="navigation"` landmark instead when used outside a `TabProvider` (nav-link mode).
20976
+ *
20964
20977
  * @param props Component props.
20965
20978
  * @param ref Component ref.
20966
20979
  * @return React element.
@@ -20972,14 +20985,19 @@ const TabList = forwardRef((props, ref) => {
20972
20985
  ...forwardedProps
20973
20986
  } = props;
20974
20987
  const tabListRef = React__default.useRef(null);
20988
+ const role = useTabProviderContextState() === undefined ? 'navigation' : 'tablist';
20989
+
20990
+ // Classic roving tabindex (role="tab"). Disabled in nav-link mode: nav links keep their
20991
+ // native per-link Tab stops, so this path is never engaged there.
20975
20992
  useRovingTabIndexContainer({
20976
- containerRef: tabListRef,
20993
+ containerRef: role === 'navigation' ? null : tabListRef,
20977
20994
  itemSelector: '[role="tab"]'
20978
20995
  });
20979
20996
  return TabList$1({
20997
+ ...forwardedProps,
20980
20998
  theme,
20981
- ref: mergeRefs(ref, tabListRef),
20982
- ...forwardedProps
20999
+ role,
21000
+ ref: mergeRefs(ref, tabListRef)
20983
21001
  });
20984
21002
  });
20985
21003
  TabList.displayName = COMPONENT_NAME$6;
@@ -21005,9 +21023,11 @@ const {
21005
21023
  } = bem(CLASSNAME$6);
21006
21024
 
21007
21025
  /**
21008
- * Tab component.
21009
- *
21010
- * Implements WAI-ARIA `tab` role {@see https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html#rps_label}
21026
+ * Tab component. Two modes keyed on `TabProvider` presence (via the wrapper-resolved `role`):
21027
+ * tab mode (`role === 'tab'`, inside a provider) implements the WAI-ARIA `tab` role
21028
+ * {@see https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html#rps_label};
21029
+ * nav-link mode (no provider) renders plain link semantics (`aria-current`, no tab role/aria).
21030
+ * `as` only selects the rendered element and applies in both modes.
21011
21031
  *
21012
21032
  * @param props Component props.
21013
21033
  * @param ref Component ref.
@@ -21015,6 +21035,7 @@ const {
21015
21035
  */
21016
21036
  const Tab$1 = props => {
21017
21037
  const {
21038
+ as = 'button',
21018
21039
  className,
21019
21040
  icon,
21020
21041
  iconProps = {},
@@ -21022,6 +21043,7 @@ const Tab$1 = props => {
21022
21043
  isDisabled,
21023
21044
  id,
21024
21045
  isActive,
21046
+ role,
21025
21047
  label,
21026
21048
  handleFocus,
21027
21049
  handleKeyPress,
@@ -21031,107 +21053,145 @@ const Tab$1 = props => {
21031
21053
  changeToTab,
21032
21054
  tabPanelId,
21033
21055
  shouldActivateOnFocus,
21034
- tabId,
21035
21056
  Icon,
21036
21057
  Text,
21037
21058
  ref,
21038
21059
  ...forwardedProps
21039
21060
  } = props;
21040
- const changeToCurrentTab = () => {
21041
- if (isAnyDisabled) {
21042
- return;
21043
- }
21044
- changeToTab?.();
21045
- };
21046
- const onFocus = event => {
21047
- handleFocus?.(event);
21048
- if (shouldActivateOnFocus) {
21061
+ let rawClickableProps;
21062
+ if (role === 'tab') {
21063
+ // Mode: WAI-ARIA `tab` role (inside a TabProvider)
21064
+ const changeToCurrentTab = () => {
21065
+ if (isAnyDisabled) {
21066
+ return;
21067
+ }
21068
+ changeToTab?.();
21069
+ };
21070
+ const onFocus = event => {
21071
+ handleFocus?.(event);
21072
+ if (shouldActivateOnFocus) {
21073
+ changeToCurrentTab();
21074
+ }
21075
+ };
21076
+ const onKeyPress = event => {
21077
+ handleKeyPress?.(event);
21078
+ if (event.key !== 'Enter' || isAnyDisabled) {
21079
+ return;
21080
+ }
21049
21081
  changeToCurrentTab();
21050
- }
21051
- };
21052
- const onKeyPress = event => {
21053
- handleKeyPress?.(event);
21054
- if (event.key !== 'Enter' || isAnyDisabled) {
21055
- return;
21056
- }
21057
- changeToCurrentTab();
21058
- };
21059
- return /*#__PURE__*/jsxs("button", {
21060
- ref: ref,
21061
- ...forwardedProps,
21062
- type: "button",
21063
- id: tabId,
21082
+ };
21083
+ rawClickableProps = {
21084
+ ...forwardedProps,
21085
+ 'aria-disabled': isAnyDisabled ? 'true' : 'false',
21086
+ handleClick: changeToCurrentTab,
21087
+ onFocus,
21088
+ role: 'tab',
21089
+ 'aria-selected': isActive,
21090
+ 'aria-controls': tabPanelId,
21091
+ [keyPressProp]: onKeyPress,
21092
+ [tabIndexProp]: isActive ? 0 : tabIndex
21093
+ };
21094
+ } else {
21095
+ // Mode: nav link
21096
+ const {
21097
+ onClick,
21098
+ ...rest
21099
+ } = forwardedProps;
21100
+ rawClickableProps = {
21101
+ ...rest,
21102
+ isDisabled,
21103
+ handleClick: onClick,
21104
+ 'aria-current': isActive ? 'page' : undefined
21105
+ };
21106
+ }
21107
+ return RawClickable({
21108
+ ...rawClickableProps,
21109
+ as,
21110
+ id,
21064
21111
  className: classnames(className, block$6({
21065
21112
  'is-active': isActive,
21066
21113
  'is-disabled': isAnyDisabled
21067
21114
  })),
21068
- onClick: changeToCurrentTab,
21069
- [keyPressProp]: onKeyPress,
21070
- onFocus: onFocus,
21071
- role: "tab",
21072
- [tabIndexProp]: isActive ? 0 : tabIndex,
21073
- "aria-disabled": isAnyDisabled,
21074
- "aria-selected": isActive,
21075
- "aria-controls": tabPanelId,
21076
- children: [icon && /*#__PURE__*/jsx(Icon, {
21077
- icon: icon,
21078
- size: Size.xs,
21079
- ...iconProps
21080
- }), label && /*#__PURE__*/jsx(Text, {
21081
- as: "span",
21082
- truncate: true,
21083
- children: label
21084
- })]
21115
+ ref: ref,
21116
+ children: /*#__PURE__*/jsxs(Fragment, {
21117
+ children: [icon && /*#__PURE__*/jsx(Icon, {
21118
+ icon: icon,
21119
+ size: Size.xs,
21120
+ ...iconProps
21121
+ }), label && /*#__PURE__*/jsx(Text, {
21122
+ as: "span",
21123
+ truncate: true,
21124
+ children: label
21125
+ })]
21126
+ })
21085
21127
  });
21086
21128
  };
21087
21129
 
21088
21130
  /**
21089
- * Defines the props of the component.
21131
+ * Defines the props of the component. Polymorphic on `as` (element selection only): `as` picks the
21132
+ * rendered element (`as="a"` with required `href`, or `as={RouterLink}`), while the mode
21133
+ * (`role="tab"` vs nav-link) is driven by `TabProvider` presence. The forwarded ref type follows `as`.
21090
21134
  */
21091
21135
 
21092
21136
  /**
21093
21137
  * Tab component.
21094
21138
  *
21095
21139
  * Implements WAI-ARIA `tab` role {@see https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html#rps_label}
21140
+ * when rendered inside a `TabProvider`.
21141
+ *
21142
+ * Outside a `TabProvider` (nav-link mode): plain link, no `role="tab"`/roving tabindex/`changeToTab`
21143
+ * dispatch — only `aria-current` marks the active item.
21096
21144
  *
21097
21145
  * @param props Component props.
21098
- * @param ref Component ref.
21146
+ * @param ref Component ref (type follows `as`).
21099
21147
  * @return React element.
21100
21148
  */
21101
- const Tab = forwardRef((props, ref) => {
21149
+ const Tab = Object.assign(forwardRefPolymorphic((props, ref) => {
21102
21150
  const {
21103
21151
  isAnyDisabled,
21104
21152
  otherProps
21105
21153
  } = useDisableStateProps(props);
21106
21154
  const {
21155
+ as = 'button',
21107
21156
  isActive: propIsActive,
21108
21157
  id,
21109
21158
  onFocus,
21110
21159
  onKeyPress,
21111
21160
  ...forwardedProps
21112
21161
  } = otherProps;
21162
+
21163
+ // Register into the provider (if any) so it can track this tab's index / active state.
21113
21164
  const tabState = useTabProviderContext('tab', id);
21165
+ // In a provider => tab mode (role="tab"); outside => nav-link mode. Mirrors TabList's role.
21166
+ const inProvider = tabState !== undefined;
21114
21167
  const {
21115
21168
  isLazy: _isLazy,
21169
+ tabId,
21116
21170
  ...state
21117
21171
  } = tabState ?? {};
21172
+ // Without a provider there's no active index, so this collapses to the caller-provided value.
21118
21173
  const isActive = propIsActive || state?.isActive;
21119
21174
  return Tab$1({
21120
- id,
21175
+ ...forwardedProps,
21121
21176
  ...state,
21177
+ as,
21178
+ role: inProvider ? 'tab' : undefined,
21122
21179
  Icon,
21123
21180
  Text,
21124
21181
  ref,
21182
+ // Falls back to the caller-provided `id` when there's no TabProvider to generate one.
21183
+ id: tabId || id,
21125
21184
  isActive,
21126
21185
  isAnyDisabled,
21186
+ isDisabled: isAnyDisabled,
21127
21187
  handleFocus: onFocus,
21128
- handleKeyPress: onKeyPress,
21129
- ...forwardedProps
21188
+ handleKeyPress: onKeyPress
21130
21189
  });
21190
+ }), {
21191
+ displayName: COMPONENT_NAME$5,
21192
+ className: CLASSNAME$6,
21193
+ defaultProps: DEFAULT_PROPS$6
21131
21194
  });
21132
- Tab.displayName = COMPONENT_NAME$5;
21133
- Tab.className = CLASSNAME$6;
21134
- Tab.defaultProps = DEFAULT_PROPS$6;
21135
21195
 
21136
21196
  /**
21137
21197
  * Component display name.