@capra/core 1.5.0 → 1.5.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.
package/dist/index.mjs CHANGED
@@ -2913,20 +2913,74 @@ function createConfirmationModalOpener(appearance) {
2913
2913
  //#endregion
2914
2914
  //#region src/components/Drawer/Drawer.module.css
2915
2915
  var Drawer_module_default = {
2916
- "body": "capra-Drawer-module-wiZVBa-body",
2917
- "description": "capra-Drawer-module-wiZVBa-description",
2918
- "drawerInFromLeft": "capra-Drawer-module-wiZVBa-drawerInFromLeft",
2919
- "drawerInFromRight": "capra-Drawer-module-wiZVBa-drawerInFromRight",
2920
- "expandedTitleLayout": "capra-Drawer-module-wiZVBa-expandedTitleLayout",
2921
- "footer": "capra-Drawer-module-wiZVBa-footer",
2922
- "header": "capra-Drawer-module-wiZVBa-header",
2923
- "headerMain": "capra-Drawer-module-wiZVBa-headerMain",
2924
- "headerTrailing": "capra-Drawer-module-wiZVBa-headerTrailing",
2925
- "overlay": "capra-Drawer-module-wiZVBa-overlay",
2926
- "root": "capra-Drawer-module-wiZVBa-root",
2927
- "sheet": "capra-Drawer-module-wiZVBa-sheet"
2916
+ "body": "capra-Drawer-module-H0eofa-body",
2917
+ "description": "capra-Drawer-module-H0eofa-description",
2918
+ "drawerInFromLeft": "capra-Drawer-module-H0eofa-drawerInFromLeft",
2919
+ "drawerInFromRight": "capra-Drawer-module-H0eofa-drawerInFromRight",
2920
+ "drawerOutToLeft": "capra-Drawer-module-H0eofa-drawerOutToLeft",
2921
+ "drawerOutToRight": "capra-Drawer-module-H0eofa-drawerOutToRight",
2922
+ "expandedTitleLayout": "capra-Drawer-module-H0eofa-expandedTitleLayout",
2923
+ "footer": "capra-Drawer-module-H0eofa-footer",
2924
+ "header": "capra-Drawer-module-H0eofa-header",
2925
+ "headerMain": "capra-Drawer-module-H0eofa-headerMain",
2926
+ "headerTrailing": "capra-Drawer-module-H0eofa-headerTrailing",
2927
+ "overlay": "capra-Drawer-module-H0eofa-overlay",
2928
+ "root": "capra-Drawer-module-H0eofa-root",
2929
+ "sheet": "capra-Drawer-module-H0eofa-sheet"
2928
2930
  };
2929
2931
  //#endregion
2932
+ //#region src/components/Drawer/useDrawerExitAnimation.ts
2933
+ /** Matches `motion.duration.medium.2` — keep in sync with Drawer.module.css sheet animations. */
2934
+ const drawerAnimationMs = 300;
2935
+ /**
2936
+ * Keeps the drawer portal mounted through the close animation and unmounts after it finishes
2937
+ * (via `animationend` or a duration fallback timeout).
2938
+ */
2939
+ function useDrawerExitAnimation(isOpen) {
2940
+ const [isRendered, setIsRendered] = React$1.useState(isOpen);
2941
+ const closeTimeoutRef = React$1.useRef(void 0);
2942
+ const finishCloseAnimation = React$1.useCallback(() => {
2943
+ if (isOpen) return;
2944
+ if (closeTimeoutRef.current !== void 0) {
2945
+ window.clearTimeout(closeTimeoutRef.current);
2946
+ closeTimeoutRef.current = void 0;
2947
+ }
2948
+ setIsRendered(false);
2949
+ }, [isOpen]);
2950
+ React$1.useEffect(() => {
2951
+ if (isOpen) {
2952
+ if (closeTimeoutRef.current !== void 0) {
2953
+ window.clearTimeout(closeTimeoutRef.current);
2954
+ closeTimeoutRef.current = void 0;
2955
+ }
2956
+ setIsRendered(true);
2957
+ return;
2958
+ }
2959
+ if (!isRendered) return;
2960
+ if (typeof window === "undefined") {
2961
+ setIsRendered(false);
2962
+ return;
2963
+ }
2964
+ closeTimeoutRef.current = window.setTimeout(() => {
2965
+ closeTimeoutRef.current = void 0;
2966
+ setIsRendered(false);
2967
+ }, drawerAnimationMs);
2968
+ return () => {
2969
+ if (closeTimeoutRef.current !== void 0) {
2970
+ window.clearTimeout(closeTimeoutRef.current);
2971
+ closeTimeoutRef.current = void 0;
2972
+ }
2973
+ };
2974
+ }, [isOpen, isRendered]);
2975
+ return {
2976
+ isRendered,
2977
+ onSheetAnimationEnd: React$1.useCallback((event) => {
2978
+ if (event.target !== event.currentTarget || isOpen) return;
2979
+ finishCloseAnimation();
2980
+ }, [finishCloseAnimation, isOpen])
2981
+ };
2982
+ }
2983
+ //#endregion
2930
2984
  //#region src/components/Drawer/Drawer.tsx
2931
2985
  const DrawerTitleContext = React$1.createContext(void 0);
2932
2986
  function cssSize(value) {
@@ -2934,11 +2988,6 @@ function cssSize(value) {
2934
2988
  return typeof value === "number" ? `${value}px` : value;
2935
2989
  }
2936
2990
  const defaultWidth = 480;
2937
- /** History state marker so we can sync `history.back()` when the drawer closes without a popstate. */
2938
- const DRAWER_HISTORY_STATE = { __capraUiDrawer: 1 };
2939
- function isDrawerHistoryState(state) {
2940
- return typeof state === "object" && state !== null && "__capraUiDrawer" in state && state.__capraUiDrawer === DRAWER_HISTORY_STATE.__capraUiDrawer;
2941
- }
2942
2991
  function resolvePortalContainer(getContainer) {
2943
2992
  if (typeof document === "undefined") return null;
2944
2993
  if (getContainer === false || getContainer === void 0) return document.body;
@@ -2964,6 +3013,7 @@ function DrawerDescription({ children }) {
2964
3013
  }
2965
3014
  function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right", width, title, footer, closable = true, modal = true, getContainer, FORCE__className, children, "aria-label": ariaLabel }) {
2966
3015
  const isModal = modal !== false;
3016
+ const { isRendered, onSheetAnimationEnd } = useDrawerExitAnimation(isOpen);
2967
3017
  React$1.useEffect(() => {
2968
3018
  onOpenChange?.(isOpen);
2969
3019
  }, [isOpen, onOpenChange]);
@@ -2975,20 +3025,6 @@ function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right
2975
3025
  const requestClose = React$1.useCallback(() => {
2976
3026
  onCloseRef.current?.();
2977
3027
  }, []);
2978
- React$1.useEffect(() => {
2979
- if (!isOpen || typeof window === "undefined") return;
2980
- window.history.pushState(DRAWER_HISTORY_STATE, "", window.location.href);
2981
- let historyPushed = true;
2982
- const onPopState = () => {
2983
- historyPushed = false;
2984
- requestClose();
2985
- };
2986
- window.addEventListener("popstate", onPopState);
2987
- return () => {
2988
- window.removeEventListener("popstate", onPopState);
2989
- if (historyPushed && isDrawerHistoryState(window.history.state)) window.history.back();
2990
- };
2991
- }, [isOpen, requestClose]);
2992
3028
  React$1.useEffect(() => {
2993
3029
  if (!isOpen) return;
2994
3030
  const onKeyDown = (e) => {
@@ -3011,24 +3047,11 @@ function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right
3011
3047
  const sheetVars = React$1.useMemo(() => ({ "--drawer-width": widthCss }), [widthCss]);
3012
3048
  const titleId = React$1.useId();
3013
3049
  const portalContainer = React$1.useMemo(() => resolvePortalContainer(getContainer), [getContainer]);
3014
- if (!isOpen) return null;
3050
+ if (!isRendered) return null;
3015
3051
  if (!portalContainer) return null;
3016
3052
  const labelledBy = title != null ? titleId : void 0;
3017
3053
  const dialogLabel = title != null ? void 0 : ariaLabel ?? "Drawer";
3018
- return createPortal(/* @__PURE__ */ React$1.createElement("div", {
3019
- className: clsx(Drawer_module_default.root, FORCE__className),
3020
- "data-open": "true",
3021
- "data-modal": isModal ? "true" : "false"
3022
- }, isModal ? /* @__PURE__ */ React$1.createElement("div", {
3023
- "data-drawer-overlay": true,
3024
- className: Drawer_module_default.overlay,
3025
- "aria-hidden": isOpen ? void 0 : true,
3026
- onPointerDown: onOverlayPointerDown
3027
- }) : null, /* @__PURE__ */ React$1.createElement(FocusScope, {
3028
- contain: isOpen && isModal,
3029
- autoFocus: isOpen && isModal,
3030
- restoreFocus: true
3031
- }, /* @__PURE__ */ React$1.createElement("div", {
3054
+ const sheet = /* @__PURE__ */ React$1.createElement("div", {
3032
3055
  className: Drawer_module_default.sheet,
3033
3056
  style: sheetVars,
3034
3057
  "data-placement": placement,
@@ -3037,15 +3060,30 @@ function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right
3037
3060
  "aria-hidden": isOpen ? void 0 : true,
3038
3061
  "aria-label": isOpen ? dialogLabel : void 0,
3039
3062
  "aria-labelledby": isOpen ? labelledBy : void 0,
3040
- tabIndex: isOpen && isModal ? -1 : void 0
3063
+ tabIndex: isOpen && isModal ? -1 : void 0,
3064
+ onAnimationEnd: onSheetAnimationEnd
3041
3065
  }, (title != null || closable) && /* @__PURE__ */ React$1.createElement("header", { className: Drawer_module_default.header }, /* @__PURE__ */ React$1.createElement("div", { className: Drawer_module_default.headerMain }, title != null ? /* @__PURE__ */ React$1.createElement(DrawerTitleContext.Provider, { value: titleId }, typeof title === "string" ? /* @__PURE__ */ React$1.createElement(DrawerHeading, null, title) : title) : null), /* @__PURE__ */ React$1.createElement("div", { className: Drawer_module_default.headerTrailing }, closable ? /* @__PURE__ */ React$1.createElement(IconButton, {
3042
3066
  variant: "tertiary",
3043
3067
  appearance: "neutral",
3044
3068
  "aria-label": "Close drawer",
3045
3069
  size: "sm",
3046
3070
  icon: CloseOutlined,
3047
- onClick: requestClose
3048
- }) : null)), /* @__PURE__ */ React$1.createElement("div", { className: Drawer_module_default.body }, children), footer ? /* @__PURE__ */ React$1.createElement("footer", { className: Drawer_module_default.footer }, footer) : null))), portalContainer);
3071
+ onClick: isOpen ? requestClose : void 0
3072
+ }) : null)), /* @__PURE__ */ React$1.createElement("div", { className: Drawer_module_default.body }, children), footer ? /* @__PURE__ */ React$1.createElement("footer", { className: Drawer_module_default.footer }, footer) : null);
3073
+ return createPortal(/* @__PURE__ */ React$1.createElement("div", {
3074
+ className: clsx(Drawer_module_default.root, FORCE__className),
3075
+ "data-open": isOpen ? "true" : "false",
3076
+ "data-modal": isModal ? "true" : "false"
3077
+ }, isModal ? /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement("div", {
3078
+ "data-drawer-overlay": true,
3079
+ className: Drawer_module_default.overlay,
3080
+ "aria-hidden": isOpen ? void 0 : true,
3081
+ onPointerDown: isOpen ? onOverlayPointerDown : void 0
3082
+ }), /* @__PURE__ */ React$1.createElement(FocusScope, {
3083
+ contain: true,
3084
+ autoFocus: isOpen,
3085
+ restoreFocus: true
3086
+ }, sheet)) : sheet), portalContainer);
3049
3087
  }
3050
3088
  /**
3051
3089
  * A side sheet aligned with Capra design: supports **modal** (scrim + scroll lock + focus trap) and **non-modal**
@@ -3365,42 +3403,47 @@ var EmptyState_module_default = {
3365
3403
  };
3366
3404
  //#endregion
3367
3405
  //#region src/components/EmptyState/EmptyState.tsx
3406
+ function lazyLoad(importFn) {
3407
+ return React.lazy(() => importFn().then((m) => {
3408
+ return { default: m[Object.getOwnPropertyNames(m)[0]] };
3409
+ }));
3410
+ }
3368
3411
  const ILLUSTRATIONS = {
3369
3412
  EmptyFolder: {
3370
- light: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptyFolder }))),
3371
- dark: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptyFolderDark })))
3413
+ light: lazyLoad(() => import("@capra/icons/esm/images/EmptyFolder")),
3414
+ dark: lazyLoad(() => import("@capra/icons/esm/images/EmptyFolderDark"))
3372
3415
  },
3373
3416
  ArtSupplies: {
3374
- light: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.ArtSupplies }))),
3375
- dark: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.ArtSuppliesDark })))
3417
+ light: lazyLoad(() => import("@capra/icons/esm/images/ArtSupplies")),
3418
+ dark: lazyLoad(() => import("@capra/icons/esm/images/ArtSuppliesDark"))
3376
3419
  },
3377
3420
  Sandcastle: {
3378
- light: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.Sandcastle }))),
3379
- dark: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.SandcastleDark })))
3421
+ light: lazyLoad(() => import("@capra/icons/esm/images/Sandcastle")),
3422
+ dark: lazyLoad(() => import("@capra/icons/esm/images/SandcastleDark"))
3380
3423
  },
3381
3424
  EmptyBowl: {
3382
- light: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptyBowl }))),
3383
- dark: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptyBowl })))
3425
+ light: lazyLoad(() => import("@capra/icons/esm/images/EmptyBowl")),
3426
+ dark: lazyLoad(() => import("@capra/icons/esm/images/EmptyBowl"))
3384
3427
  },
3385
3428
  EmptySuitcase: {
3386
- light: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptySuitcase }))),
3387
- dark: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptySuitcaseDark })))
3429
+ light: lazyLoad(() => import("@capra/icons/esm/images/EmptySuitcase")),
3430
+ dark: lazyLoad(() => import("@capra/icons/esm/images/EmptySuitcaseDark"))
3388
3431
  },
3389
3432
  Envelope: {
3390
- light: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.Envelope }))),
3391
- dark: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.Envelope })))
3433
+ light: lazyLoad(() => import("@capra/icons/esm/images/Envelope")),
3434
+ dark: lazyLoad(() => import("@capra/icons/esm/images/Envelope"))
3392
3435
  },
3393
3436
  MissingSock: {
3394
- light: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.MissingSock }))),
3395
- dark: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.MissingSock })))
3437
+ light: lazyLoad(() => import("@capra/icons/esm/images/MissingSock")),
3438
+ dark: lazyLoad(() => import("@capra/icons/esm/images/MissingSock"))
3396
3439
  },
3397
3440
  PizzaBox: {
3398
- light: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.PizzaBox }))),
3399
- dark: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.PizzaBox })))
3441
+ light: lazyLoad(() => import("@capra/icons/esm/images/PizzaBox")),
3442
+ dark: lazyLoad(() => import("@capra/icons/esm/images/PizzaBox"))
3400
3443
  },
3401
3444
  PottedPlant: {
3402
- light: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.PottedPlant }))),
3403
- dark: React.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.PottedPlant })))
3445
+ light: lazyLoad(() => import("@capra/icons/esm/images/PottedPlant")),
3446
+ dark: lazyLoad(() => import("@capra/icons/esm/images/PottedPlant"))
3404
3447
  }
3405
3448
  };
3406
3449
  /**
@@ -4271,7 +4314,39 @@ const MenuRootTriggerPressable = React$1.forwardRef(function MenuRootTriggerPres
4271
4314
  }, /* @__PURE__ */ React$1.createElement(Pressable, null, children));
4272
4315
  });
4273
4316
  MenuRootTriggerPressable.displayName = "MenuRootTriggerPressable";
4274
- const Menu = ({ trigger, children, contentProps, open, onOpenChange, itemHoverAppearance = "default", itemActiveAccentBar = true, itemActiveLabelSemibold = true, panelPadding = "default", popoverOffset = 0, FORCE__className: classNameOverride }) => {
4317
+ /**
4318
+ * Wraps the menu list in a focus scope while the overlay is open. Must render under {@link AriaMenuTrigger} so
4319
+ * {@link OverlayTriggerStateContext} resolves for uncontrolled menus.
4320
+ *
4321
+ * `contain` is enabled only after focus moves inside the panel so the trigger (outside this subtree) can still
4322
+ * receive Tab/Enter to open the menu and so programmatic focus from the trigger into the first item is not blocked.
4323
+ */
4324
+ function MenuPanelFocusScope({ controlledOpen, children }) {
4325
+ const overlayState = React$1.useContext(OverlayTriggerStateContext);
4326
+ const isOpen = controlledOpen !== void 0 ? controlledOpen : overlayState?.isOpen ?? false;
4327
+ const rootRef = React$1.useRef(null);
4328
+ const [focusInsidePanel, setFocusInsidePanel] = React$1.useState(false);
4329
+ React$1.useLayoutEffect(() => {
4330
+ if (!isOpen) setFocusInsidePanel(false);
4331
+ }, [isOpen]);
4332
+ const onFocusCapture = React$1.useCallback(() => {
4333
+ if (isOpen) setFocusInsidePanel(true);
4334
+ }, [isOpen]);
4335
+ const onBlurCapture = React$1.useCallback((event) => {
4336
+ const next = event.relatedTarget;
4337
+ if (rootRef.current?.contains(next)) return;
4338
+ setFocusInsidePanel(false);
4339
+ }, []);
4340
+ return /* @__PURE__ */ React$1.createElement("div", {
4341
+ ref: rootRef,
4342
+ onFocusCapture,
4343
+ onBlurCapture
4344
+ }, /* @__PURE__ */ React$1.createElement(FocusScope, {
4345
+ contain: isOpen && focusInsidePanel,
4346
+ restoreFocus: true
4347
+ }, children));
4348
+ }
4349
+ const Menu = ({ trigger, children, contentProps, open, onOpenChange, itemHoverAppearance = "default", itemActiveAccentBar = true, itemActiveLabelSemibold = true, panelPadding = "default", popoverOffset = 0, trapFocus = false, FORCE__className: classNameOverride }) => {
4275
4350
  const sessionId = React$1.useId();
4276
4351
  const menuPanel = /* @__PURE__ */ React$1.createElement(Menu$1, {
4277
4352
  className: clsx(Menu_module_default.menu, classNameOverride),
@@ -4283,11 +4358,12 @@ const Menu = ({ trigger, children, contentProps, open, onOpenChange, itemHoverAp
4283
4358
  itemActiveLabelSemibold
4284
4359
  })
4285
4360
  }, /* @__PURE__ */ React$1.createElement(MenuSectionIndentContext.Provider, { value: false }, children));
4361
+ const panelBody = trapFocus ? /* @__PURE__ */ React$1.createElement(MenuPanelFocusScope, { controlledOpen: open }, menuPanel) : menuPanel;
4286
4362
  const wrappedPanel = contentProps ? /* @__PURE__ */ React$1.createElement("div", {
4287
4363
  ...contentProps,
4288
4364
  [MENU_SESSION_DATA_ATTR]: sessionId,
4289
4365
  className: contentProps.className
4290
- }, menuPanel) : /* @__PURE__ */ React$1.createElement("div", { [MENU_SESSION_DATA_ATTR]: sessionId }, menuPanel);
4366
+ }, panelBody) : /* @__PURE__ */ React$1.createElement("div", { [MENU_SESSION_DATA_ATTR]: sessionId }, panelBody);
4291
4367
  return /* @__PURE__ */ React$1.createElement(MenuSessionContext.Provider, { value: sessionId }, /* @__PURE__ */ React$1.createElement(MenuTrigger, {
4292
4368
  isOpen: open,
4293
4369
  onOpenChange
@@ -4313,23 +4389,23 @@ const submenuPanelClassName = Menu_module_default.menu;
4313
4389
  //#endregion
4314
4390
  //#region src/components/TabNav/TabNav.module.css
4315
4391
  var TabNav_module_default = {
4316
- "dropdownIcon": "capra-TabNav-module-VtoGYa-dropdownIcon",
4317
- "extraContent": "capra-TabNav-module-VtoGYa-extraContent",
4318
- "indentSpacer": "capra-TabNav-module-VtoGYa-indentSpacer",
4319
- "panel": "capra-TabNav-module-VtoGYa-panel",
4320
- "root": "capra-TabNav-module-VtoGYa-root",
4321
- "subMenu": "capra-TabNav-module-VtoGYa-subMenu",
4322
- "subMenuItem": "capra-TabNav-module-VtoGYa-subMenuItem",
4323
- "subMenuItemActive": "capra-TabNav-module-VtoGYa-subMenuItemActive",
4324
- "subTab": "capra-TabNav-module-VtoGYa-subTab",
4325
- "subTabList": "capra-TabNav-module-VtoGYa-subTabList",
4326
- "subTabListItem": "capra-TabNav-module-VtoGYa-subTabListItem",
4327
- "tab": "capra-TabNav-module-VtoGYa-tab",
4328
- "tabBar": "capra-TabNav-module-VtoGYa-tabBar",
4329
- "tabGroup": "capra-TabNav-module-VtoGYa-tabGroup",
4330
- "tabIcon": "capra-TabNav-module-VtoGYa-tabIcon",
4331
- "tabLabel": "capra-TabNav-module-VtoGYa-tabLabel",
4332
- "tabList": "capra-TabNav-module-VtoGYa-tabList"
4392
+ "dropdownIcon": "capra-TabNav-module-e0IbmW-dropdownIcon",
4393
+ "extraContent": "capra-TabNav-module-e0IbmW-extraContent",
4394
+ "indentSpacer": "capra-TabNav-module-e0IbmW-indentSpacer",
4395
+ "panel": "capra-TabNav-module-e0IbmW-panel",
4396
+ "root": "capra-TabNav-module-e0IbmW-root",
4397
+ "subMenu": "capra-TabNav-module-e0IbmW-subMenu",
4398
+ "subMenuItem": "capra-TabNav-module-e0IbmW-subMenuItem",
4399
+ "subMenuItemActive": "capra-TabNav-module-e0IbmW-subMenuItemActive",
4400
+ "subTab": "capra-TabNav-module-e0IbmW-subTab",
4401
+ "subTabList": "capra-TabNav-module-e0IbmW-subTabList",
4402
+ "subTabListItem": "capra-TabNav-module-e0IbmW-subTabListItem",
4403
+ "tab": "capra-TabNav-module-e0IbmW-tab",
4404
+ "tabBar": "capra-TabNav-module-e0IbmW-tabBar",
4405
+ "tabGroup": "capra-TabNav-module-e0IbmW-tabGroup",
4406
+ "tabIcon": "capra-TabNav-module-e0IbmW-tabIcon",
4407
+ "tabLabel": "capra-TabNav-module-e0IbmW-tabLabel",
4408
+ "tabList": "capra-TabNav-module-e0IbmW-tabList"
4333
4409
  };
4334
4410
  //#endregion
4335
4411
  //#region src/components/TabNav/TabNav.tsx
@@ -4349,7 +4425,27 @@ function optionalAriaLabelProps(source) {
4349
4425
  const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", centered = false, tabBarExtraSlot, wrap = true, FORCE__className: classNameOverride, "aria-label": ariaLabel = "Navigation", ...props }) => {
4350
4426
  const [subMenuOpen, setSubMenuOpen] = React$1.useState({});
4351
4427
  const hoverCloseTimeoutRef = React$1.useRef(null);
4352
- const pointerDownOnTriggerRef = React$1.useRef(false);
4428
+ const pendingKeyboardFocusKeyRef = React$1.useRef(null);
4429
+ const menuContentRefs = React$1.useRef({});
4430
+ const getTriggerElement = React$1.useCallback((key) => {
4431
+ const trigger = document.getElementById(`tabnav-${key}`);
4432
+ return trigger instanceof HTMLElement ? trigger : null;
4433
+ }, []);
4434
+ const getEnabledMenuItems = React$1.useCallback((key) => {
4435
+ const menu = menuContentRefs.current[key]?.querySelector("[role=\"menu\"]");
4436
+ if (!(menu instanceof HTMLElement)) return [];
4437
+ return Array.from(menu.querySelectorAll("[role=\"menuitem\"]")).filter((element) => element.getAttribute("aria-disabled") !== "true");
4438
+ }, []);
4439
+ const focusMenuItem = React$1.useCallback((key, direction) => {
4440
+ const menuItems = getEnabledMenuItems(key);
4441
+ const target = direction === "first" ? menuItems[0] : menuItems[menuItems.length - 1];
4442
+ if (target) {
4443
+ target.focus();
4444
+ return true;
4445
+ }
4446
+ getTriggerElement(key)?.focus();
4447
+ return false;
4448
+ }, [getEnabledMenuItems, getTriggerElement]);
4353
4449
  const openSubMenu = React$1.useCallback((key) => {
4354
4450
  if (hoverCloseTimeoutRef.current) {
4355
4451
  clearTimeout(hoverCloseTimeoutRef.current);
@@ -4392,22 +4488,101 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4392
4488
  hoverCloseTimeoutRef.current = null;
4393
4489
  }
4394
4490
  }, []);
4491
+ const closeAllSubMenus = React$1.useCallback(() => {
4492
+ if (hoverCloseTimeoutRef.current) {
4493
+ clearTimeout(hoverCloseTimeoutRef.current);
4494
+ hoverCloseTimeoutRef.current = null;
4495
+ }
4496
+ pendingKeyboardFocusKeyRef.current = null;
4497
+ setSubMenuOpen({});
4498
+ }, []);
4395
4499
  const [activeMainKey, activeSubKey] = React$1.useMemo(() => {
4396
4500
  if (!activeKey) return ["", ""];
4397
4501
  const idx = activeKey.indexOf(SUB_KEY_SEP);
4398
4502
  if (idx >= 0) return [activeKey.slice(0, idx), activeKey.slice(idx + 2)];
4399
4503
  return [String(activeKey), ""];
4400
4504
  }, [activeKey]);
4505
+ /** While a horizontal flyout is open, sibling tab controls use roving `tabIndex` so Tab cannot reach them. */
4506
+ const horizontalFlyoutOpenKey = React$1.useMemo(() => {
4507
+ if (tabPlacement !== "horizontal") return;
4508
+ return items.find((i) => Boolean(i.subItems?.length && subMenuOpen[i.key]))?.key;
4509
+ }, [
4510
+ items,
4511
+ subMenuOpen,
4512
+ tabPlacement
4513
+ ]);
4401
4514
  /** Expand vertical groups when the active route targets a sub-item. */
4402
4515
  React$1.useEffect(() => {
4403
- if (!activeSubKey) return;
4516
+ if (tabPlacement !== "vertical" || !activeSubKey) return;
4404
4517
  items.forEach((item) => {
4405
4518
  if (item.subItems?.some((s) => s.key === activeSubKey)) setSubMenuOpen((prev) => ({
4406
4519
  ...prev,
4407
4520
  [item.key]: true
4408
4521
  }));
4409
4522
  });
4410
- }, [activeSubKey, items]);
4523
+ }, [
4524
+ activeSubKey,
4525
+ items,
4526
+ tabPlacement
4527
+ ]);
4528
+ React$1.useEffect(() => {
4529
+ const key = pendingKeyboardFocusKeyRef.current;
4530
+ if (!key || !subMenuOpen[key] || tabPlacement !== "horizontal") return;
4531
+ const menuContent = menuContentRefs.current[key];
4532
+ const focusFirstMenuItem = () => {
4533
+ const items = getEnabledMenuItems(key);
4534
+ const target = items.find((element) => element.getAttribute("aria-current") !== "page") ?? items[0];
4535
+ if (target) {
4536
+ target.focus();
4537
+ pendingKeyboardFocusKeyRef.current = null;
4538
+ return true;
4539
+ }
4540
+ return false;
4541
+ };
4542
+ if (focusFirstMenuItem()) return;
4543
+ if (!menuContent) return;
4544
+ const observer = new MutationObserver(() => {
4545
+ if (focusFirstMenuItem()) observer.disconnect();
4546
+ });
4547
+ observer.observe(menuContent, {
4548
+ childList: true,
4549
+ subtree: true
4550
+ });
4551
+ return () => {
4552
+ observer.disconnect();
4553
+ };
4554
+ }, [
4555
+ getEnabledMenuItems,
4556
+ subMenuOpen,
4557
+ tabPlacement
4558
+ ]);
4559
+ const closeHorizontalSubMenuFromKeyboard = React$1.useCallback((key) => {
4560
+ pendingKeyboardFocusKeyRef.current = null;
4561
+ closeAllSubMenus();
4562
+ getTriggerElement(key)?.focus();
4563
+ }, [closeAllSubMenus, getTriggerElement]);
4564
+ const handleOpenSubMenuTabKey = React$1.useCallback((event, key) => {
4565
+ const menuItems = getEnabledMenuItems(key);
4566
+ if (menuItems.length === 0) {
4567
+ event.preventDefault();
4568
+ event.stopPropagation();
4569
+ getTriggerElement(key)?.focus();
4570
+ return;
4571
+ }
4572
+ const activeElement = document.activeElement;
4573
+ const currentIndex = activeElement instanceof HTMLElement ? menuItems.indexOf(activeElement) : -1;
4574
+ event.preventDefault();
4575
+ event.stopPropagation();
4576
+ if (currentIndex === -1) {
4577
+ focusMenuItem(key, event.shiftKey ? "last" : "first");
4578
+ return;
4579
+ }
4580
+ menuItems[(currentIndex + (event.shiftKey ? -1 : 1) + menuItems.length) % menuItems.length]?.focus();
4581
+ }, [
4582
+ focusMenuItem,
4583
+ getEnabledMenuItems,
4584
+ getTriggerElement
4585
+ ]);
4411
4586
  const handleTabClick = (key, event) => {
4412
4587
  const item = items.find((i) => i.key === key);
4413
4588
  if (item?.disabled) {
@@ -4420,9 +4595,40 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4420
4595
  const handleKeyDown = (event, key) => {
4421
4596
  const itemIndex = items.findIndex((i) => i.key === key);
4422
4597
  if (itemIndex === -1) return;
4598
+ const item = items[itemIndex];
4599
+ const hasSubItems = Boolean(item?.subItems?.length);
4423
4600
  let nextIndex = itemIndex;
4424
4601
  const isHorizontal = tabPlacement === "horizontal";
4425
4602
  const isVertical = tabPlacement === "vertical";
4603
+ if (hasSubItems && isHorizontal && (event.key === "Enter" || event.key === " ")) {
4604
+ event.preventDefault();
4605
+ event.stopPropagation();
4606
+ if (subMenuOpen[key]) {
4607
+ pendingKeyboardFocusKeyRef.current = null;
4608
+ closeSubMenu(key);
4609
+ } else {
4610
+ pendingKeyboardFocusKeyRef.current = key;
4611
+ openSubMenu(key);
4612
+ }
4613
+ return;
4614
+ }
4615
+ if (hasSubItems && isHorizontal && event.key === "Escape") {
4616
+ event.preventDefault();
4617
+ event.stopPropagation();
4618
+ closeHorizontalSubMenuFromKeyboard(key);
4619
+ return;
4620
+ }
4621
+ if (hasSubItems && isHorizontal && subMenuOpen[key] && event.key === "Tab") {
4622
+ handleOpenSubMenuTabKey(event, key);
4623
+ return;
4624
+ }
4625
+ if (hasSubItems && isHorizontal && subMenuOpen[key]) {
4626
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "Home" || event.key === "End") {
4627
+ event.preventDefault();
4628
+ event.stopPropagation();
4629
+ }
4630
+ return;
4631
+ }
4426
4632
  if (isHorizontal) {
4427
4633
  if (event.key === "ArrowLeft") {
4428
4634
  event.preventDefault();
@@ -4486,6 +4692,7 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4486
4692
  const isSubMenuOpen = hasSubItems && Boolean(subMenuOpen[item.key]);
4487
4693
  /** Only one `aria-current="page"` in the nav: parent tab when no child route; sub-items set it when a child is current. */
4488
4694
  const ariaCurrentPage = item.key === activeMainKey && (!hasSubItems || !activeSubKey);
4695
+ const computedTabIndex = tabPlacement === "horizontal" && horizontalFlyoutOpenKey != null ? !item.disabled && item.key === horizontalFlyoutOpenKey && hasSubItems ? 0 : -1 : void 0;
4489
4696
  const commonTabProps = {
4490
4697
  "data-tab-key": item.key,
4491
4698
  "data-indent": item.indent || void 0,
@@ -4495,7 +4702,8 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4495
4702
  "data-disabled": item.disabled || void 0,
4496
4703
  id: `tabnav-${item.key}`,
4497
4704
  className: TabNav_module_default.tab,
4498
- ...optionalAriaLabelProps(item)
4705
+ ...optionalAriaLabelProps(item),
4706
+ ...computedTabIndex !== void 0 ? { tabIndex: computedTabIndex } : {}
4499
4707
  };
4500
4708
  if (hasSubItems && tabPlacement === "vertical") {
4501
4709
  const expanded = Boolean(subMenuOpen[item.key]);
@@ -4589,46 +4797,53 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4589
4797
  const trigger = /* @__PURE__ */ React$1.createElement("button", {
4590
4798
  type: "button",
4591
4799
  ...commonTabProps,
4800
+ "aria-expanded": isSubMenuOpen,
4801
+ "aria-haspopup": "menu",
4592
4802
  "aria-current": ariaCurrentPage ? "page" : void 0,
4593
4803
  disabled: item.disabled,
4594
4804
  onMouseEnter: () => openSubMenu(item.key),
4595
4805
  onMouseLeave: () => scheduleCloseSubMenu(item.key),
4596
- onPointerDown: () => {
4597
- pointerDownOnTriggerRef.current = true;
4598
- },
4599
- onPointerUp: () => {
4600
- pointerDownOnTriggerRef.current = false;
4601
- },
4602
- onFocus: () => {
4603
- if (!pointerDownOnTriggerRef.current) openSubMenu(item.key);
4604
- },
4605
4806
  onKeyDown: (e) => handleKeyDown(e, item.key)
4606
4807
  }, renderTabContent(item));
4607
4808
  return /* @__PURE__ */ React$1.createElement(Menu, {
4608
4809
  key: item.key,
4609
4810
  trigger,
4811
+ trapFocus: true,
4610
4812
  itemHoverAppearance: "accent",
4611
4813
  itemActiveAccentBar: false,
4612
4814
  itemActiveLabelSemibold: false,
4613
4815
  panelPadding: "none",
4614
4816
  popoverOffset: 1,
4615
4817
  open: subMenuOpen[item.key],
4616
- onOpenChange: (open) => setSubMenuOpen((prev) => ({
4617
- ...prev,
4618
- [item.key]: open
4619
- })),
4818
+ onOpenChange: (open) => {
4819
+ if (open) {
4820
+ openSubMenu(item.key);
4821
+ return;
4822
+ }
4823
+ pendingKeyboardFocusKeyRef.current = null;
4824
+ closeSubMenu(item.key);
4825
+ },
4620
4826
  contentProps: {
4827
+ ref: (node) => {
4828
+ menuContentRefs.current[item.key] = node;
4829
+ },
4621
4830
  onMouseEnter: () => {
4622
4831
  cancelScheduledClose();
4623
4832
  openSubMenu(item.key);
4624
4833
  },
4625
- onMouseLeave: () => closeSubMenu(item.key)
4834
+ onMouseLeave: () => closeSubMenu(item.key),
4835
+ onKeyDownCapture: (event) => {
4836
+ if (event.key === "Escape") {
4837
+ event.preventDefault();
4838
+ event.stopPropagation();
4839
+ closeHorizontalSubMenuFromKeyboard(item.key);
4840
+ return;
4841
+ }
4842
+ if (event.key === "Tab") handleOpenSubMenuTabKey(event, item.key);
4843
+ }
4626
4844
  }
4627
4845
  }, item.subItems.map((sub) => {
4628
- const closeMenu = () => setSubMenuOpen((prev) => ({
4629
- ...prev,
4630
- [item.key]: false
4631
- }));
4846
+ const closeMenu = () => closeAllSubMenus();
4632
4847
  return /* @__PURE__ */ React$1.createElement(Menu.Item, {
4633
4848
  key: sub.key,
4634
4849
  label: sub.name,
@@ -4657,7 +4872,7 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4657
4872
  role: "link",
4658
4873
  "aria-disabled": item.disabled,
4659
4874
  "aria-current": ariaCurrentPage ? "page" : void 0,
4660
- tabIndex: 0,
4875
+ tabIndex: item.disabled ? -1 : computedTabIndex ?? 0,
4661
4876
  onKeyDown: (e) => {
4662
4877
  if (e.key === "Enter" || e.key === " ") {
4663
4878
  e.preventDefault();
@@ -4725,11 +4940,13 @@ var Tooltip_module_default = {
4725
4940
  };
4726
4941
  //#endregion
4727
4942
  //#region src/components/Tooltip/Tooltip.tsx
4943
+ const TOOLTIP_HOVER_SHOW_DELAY_MS = 500;
4728
4944
  /**
4729
4945
  * A component to display additional information when hovering or focusing on an interactive element.
4730
4946
  */
4731
4947
  function Tooltip({ children, title, shortcut, getContainer, placement = "bottom", isDisabled = false, FORCE__className: classNameOverride, ...props }) {
4732
4948
  return /* @__PURE__ */ React$1.createElement(TooltipTrigger, {
4949
+ delay: TOOLTIP_HOVER_SHOW_DELAY_MS,
4733
4950
  isDisabled,
4734
4951
  shouldCloseOnPress: false
4735
4952
  }, children, /* @__PURE__ */ React$1.createElement(UNSAFE_PortalProvider, { getContainer }, /* @__PURE__ */ React$1.createElement(Tooltip$1, {