@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/README.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  `@capra/core` publishes the foundational React components for the Capra design system, along with the package stylesheet entry point at `@capra/core/styles.css`.
6
6
 
7
+ ## Documentation
8
+
9
+ Human-readable documentation is available at https://capra.cribl.io.
10
+
11
+ LLM-readable documentation is available at https://capra.cribl.io/llms.txt.
12
+
7
13
  ## Install
8
14
 
9
15
  ```sh
package/dist/index.cjs CHANGED
@@ -2937,20 +2937,74 @@ function createConfirmationModalOpener(appearance) {
2937
2937
  //#endregion
2938
2938
  //#region src/components/Drawer/Drawer.module.css
2939
2939
  var Drawer_module_default = {
2940
- "body": "capra-Drawer-module-wiZVBa-body",
2941
- "description": "capra-Drawer-module-wiZVBa-description",
2942
- "drawerInFromLeft": "capra-Drawer-module-wiZVBa-drawerInFromLeft",
2943
- "drawerInFromRight": "capra-Drawer-module-wiZVBa-drawerInFromRight",
2944
- "expandedTitleLayout": "capra-Drawer-module-wiZVBa-expandedTitleLayout",
2945
- "footer": "capra-Drawer-module-wiZVBa-footer",
2946
- "header": "capra-Drawer-module-wiZVBa-header",
2947
- "headerMain": "capra-Drawer-module-wiZVBa-headerMain",
2948
- "headerTrailing": "capra-Drawer-module-wiZVBa-headerTrailing",
2949
- "overlay": "capra-Drawer-module-wiZVBa-overlay",
2950
- "root": "capra-Drawer-module-wiZVBa-root",
2951
- "sheet": "capra-Drawer-module-wiZVBa-sheet"
2940
+ "body": "capra-Drawer-module-H0eofa-body",
2941
+ "description": "capra-Drawer-module-H0eofa-description",
2942
+ "drawerInFromLeft": "capra-Drawer-module-H0eofa-drawerInFromLeft",
2943
+ "drawerInFromRight": "capra-Drawer-module-H0eofa-drawerInFromRight",
2944
+ "drawerOutToLeft": "capra-Drawer-module-H0eofa-drawerOutToLeft",
2945
+ "drawerOutToRight": "capra-Drawer-module-H0eofa-drawerOutToRight",
2946
+ "expandedTitleLayout": "capra-Drawer-module-H0eofa-expandedTitleLayout",
2947
+ "footer": "capra-Drawer-module-H0eofa-footer",
2948
+ "header": "capra-Drawer-module-H0eofa-header",
2949
+ "headerMain": "capra-Drawer-module-H0eofa-headerMain",
2950
+ "headerTrailing": "capra-Drawer-module-H0eofa-headerTrailing",
2951
+ "overlay": "capra-Drawer-module-H0eofa-overlay",
2952
+ "root": "capra-Drawer-module-H0eofa-root",
2953
+ "sheet": "capra-Drawer-module-H0eofa-sheet"
2952
2954
  };
2953
2955
  //#endregion
2956
+ //#region src/components/Drawer/useDrawerExitAnimation.ts
2957
+ /** Matches `motion.duration.medium.2` — keep in sync with Drawer.module.css sheet animations. */
2958
+ const drawerAnimationMs = 300;
2959
+ /**
2960
+ * Keeps the drawer portal mounted through the close animation and unmounts after it finishes
2961
+ * (via `animationend` or a duration fallback timeout).
2962
+ */
2963
+ function useDrawerExitAnimation(isOpen) {
2964
+ const [isRendered, setIsRendered] = react$1.useState(isOpen);
2965
+ const closeTimeoutRef = react$1.useRef(void 0);
2966
+ const finishCloseAnimation = react$1.useCallback(() => {
2967
+ if (isOpen) return;
2968
+ if (closeTimeoutRef.current !== void 0) {
2969
+ window.clearTimeout(closeTimeoutRef.current);
2970
+ closeTimeoutRef.current = void 0;
2971
+ }
2972
+ setIsRendered(false);
2973
+ }, [isOpen]);
2974
+ react$1.useEffect(() => {
2975
+ if (isOpen) {
2976
+ if (closeTimeoutRef.current !== void 0) {
2977
+ window.clearTimeout(closeTimeoutRef.current);
2978
+ closeTimeoutRef.current = void 0;
2979
+ }
2980
+ setIsRendered(true);
2981
+ return;
2982
+ }
2983
+ if (!isRendered) return;
2984
+ if (typeof window === "undefined") {
2985
+ setIsRendered(false);
2986
+ return;
2987
+ }
2988
+ closeTimeoutRef.current = window.setTimeout(() => {
2989
+ closeTimeoutRef.current = void 0;
2990
+ setIsRendered(false);
2991
+ }, drawerAnimationMs);
2992
+ return () => {
2993
+ if (closeTimeoutRef.current !== void 0) {
2994
+ window.clearTimeout(closeTimeoutRef.current);
2995
+ closeTimeoutRef.current = void 0;
2996
+ }
2997
+ };
2998
+ }, [isOpen, isRendered]);
2999
+ return {
3000
+ isRendered,
3001
+ onSheetAnimationEnd: react$1.useCallback((event) => {
3002
+ if (event.target !== event.currentTarget || isOpen) return;
3003
+ finishCloseAnimation();
3004
+ }, [finishCloseAnimation, isOpen])
3005
+ };
3006
+ }
3007
+ //#endregion
2954
3008
  //#region src/components/Drawer/Drawer.tsx
2955
3009
  const DrawerTitleContext = react.createContext(void 0);
2956
3010
  function cssSize(value) {
@@ -2958,11 +3012,6 @@ function cssSize(value) {
2958
3012
  return typeof value === "number" ? `${value}px` : value;
2959
3013
  }
2960
3014
  const defaultWidth = 480;
2961
- /** History state marker so we can sync `history.back()` when the drawer closes without a popstate. */
2962
- const DRAWER_HISTORY_STATE = { __capraUiDrawer: 1 };
2963
- function isDrawerHistoryState(state) {
2964
- return typeof state === "object" && state !== null && "__capraUiDrawer" in state && state.__capraUiDrawer === DRAWER_HISTORY_STATE.__capraUiDrawer;
2965
- }
2966
3015
  function resolvePortalContainer(getContainer) {
2967
3016
  if (typeof document === "undefined") return null;
2968
3017
  if (getContainer === false || getContainer === void 0) return document.body;
@@ -2988,6 +3037,7 @@ function DrawerDescription({ children }) {
2988
3037
  }
2989
3038
  function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right", width, title, footer, closable = true, modal = true, getContainer, FORCE__className, children, "aria-label": ariaLabel }) {
2990
3039
  const isModal = modal !== false;
3040
+ const { isRendered, onSheetAnimationEnd } = useDrawerExitAnimation(isOpen);
2991
3041
  react.useEffect(() => {
2992
3042
  onOpenChange?.(isOpen);
2993
3043
  }, [isOpen, onOpenChange]);
@@ -2999,20 +3049,6 @@ function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right
2999
3049
  const requestClose = react.useCallback(() => {
3000
3050
  onCloseRef.current?.();
3001
3051
  }, []);
3002
- react.useEffect(() => {
3003
- if (!isOpen || typeof window === "undefined") return;
3004
- window.history.pushState(DRAWER_HISTORY_STATE, "", window.location.href);
3005
- let historyPushed = true;
3006
- const onPopState = () => {
3007
- historyPushed = false;
3008
- requestClose();
3009
- };
3010
- window.addEventListener("popstate", onPopState);
3011
- return () => {
3012
- window.removeEventListener("popstate", onPopState);
3013
- if (historyPushed && isDrawerHistoryState(window.history.state)) window.history.back();
3014
- };
3015
- }, [isOpen, requestClose]);
3016
3052
  react.useEffect(() => {
3017
3053
  if (!isOpen) return;
3018
3054
  const onKeyDown = (e) => {
@@ -3035,24 +3071,11 @@ function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right
3035
3071
  const sheetVars = react.useMemo(() => ({ "--drawer-width": widthCss }), [widthCss]);
3036
3072
  const titleId = react.useId();
3037
3073
  const portalContainer = react.useMemo(() => resolvePortalContainer(getContainer), [getContainer]);
3038
- if (!isOpen) return null;
3074
+ if (!isRendered) return null;
3039
3075
  if (!portalContainer) return null;
3040
3076
  const labelledBy = title != null ? titleId : void 0;
3041
3077
  const dialogLabel = title != null ? void 0 : ariaLabel ?? "Drawer";
3042
- return (0, react_dom.createPortal)(/* @__PURE__ */ react.createElement("div", {
3043
- className: clsx(Drawer_module_default.root, FORCE__className),
3044
- "data-open": "true",
3045
- "data-modal": isModal ? "true" : "false"
3046
- }, isModal ? /* @__PURE__ */ react.createElement("div", {
3047
- "data-drawer-overlay": true,
3048
- className: Drawer_module_default.overlay,
3049
- "aria-hidden": isOpen ? void 0 : true,
3050
- onPointerDown: onOverlayPointerDown
3051
- }) : null, /* @__PURE__ */ react.createElement(react_aria.FocusScope, {
3052
- contain: isOpen && isModal,
3053
- autoFocus: isOpen && isModal,
3054
- restoreFocus: true
3055
- }, /* @__PURE__ */ react.createElement("div", {
3078
+ const sheet = /* @__PURE__ */ react.createElement("div", {
3056
3079
  className: Drawer_module_default.sheet,
3057
3080
  style: sheetVars,
3058
3081
  "data-placement": placement,
@@ -3061,15 +3084,30 @@ function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right
3061
3084
  "aria-hidden": isOpen ? void 0 : true,
3062
3085
  "aria-label": isOpen ? dialogLabel : void 0,
3063
3086
  "aria-labelledby": isOpen ? labelledBy : void 0,
3064
- tabIndex: isOpen && isModal ? -1 : void 0
3087
+ tabIndex: isOpen && isModal ? -1 : void 0,
3088
+ onAnimationEnd: onSheetAnimationEnd
3065
3089
  }, (title != null || closable) && /* @__PURE__ */ react.createElement("header", { className: Drawer_module_default.header }, /* @__PURE__ */ react.createElement("div", { className: Drawer_module_default.headerMain }, title != null ? /* @__PURE__ */ react.createElement(DrawerTitleContext.Provider, { value: titleId }, typeof title === "string" ? /* @__PURE__ */ react.createElement(DrawerHeading, null, title) : title) : null), /* @__PURE__ */ react.createElement("div", { className: Drawer_module_default.headerTrailing }, closable ? /* @__PURE__ */ react.createElement(IconButton, {
3066
3090
  variant: "tertiary",
3067
3091
  appearance: "neutral",
3068
3092
  "aria-label": "Close drawer",
3069
3093
  size: "sm",
3070
3094
  icon: _capra_icons.CloseOutlined,
3071
- onClick: requestClose
3072
- }) : null)), /* @__PURE__ */ react.createElement("div", { className: Drawer_module_default.body }, children), footer ? /* @__PURE__ */ react.createElement("footer", { className: Drawer_module_default.footer }, footer) : null))), portalContainer);
3095
+ onClick: isOpen ? requestClose : void 0
3096
+ }) : null)), /* @__PURE__ */ react.createElement("div", { className: Drawer_module_default.body }, children), footer ? /* @__PURE__ */ react.createElement("footer", { className: Drawer_module_default.footer }, footer) : null);
3097
+ return (0, react_dom.createPortal)(/* @__PURE__ */ react.createElement("div", {
3098
+ className: clsx(Drawer_module_default.root, FORCE__className),
3099
+ "data-open": isOpen ? "true" : "false",
3100
+ "data-modal": isModal ? "true" : "false"
3101
+ }, isModal ? /* @__PURE__ */ react.createElement(react.Fragment, null, /* @__PURE__ */ react.createElement("div", {
3102
+ "data-drawer-overlay": true,
3103
+ className: Drawer_module_default.overlay,
3104
+ "aria-hidden": isOpen ? void 0 : true,
3105
+ onPointerDown: isOpen ? onOverlayPointerDown : void 0
3106
+ }), /* @__PURE__ */ react.createElement(react_aria.FocusScope, {
3107
+ contain: true,
3108
+ autoFocus: isOpen,
3109
+ restoreFocus: true
3110
+ }, sheet)) : sheet), portalContainer);
3073
3111
  }
3074
3112
  /**
3075
3113
  * A side sheet aligned with Capra design: supports **modal** (scrim + scroll lock + focus trap) and **non-modal**
@@ -3389,42 +3427,47 @@ var EmptyState_module_default = {
3389
3427
  };
3390
3428
  //#endregion
3391
3429
  //#region src/components/EmptyState/EmptyState.tsx
3430
+ function lazyLoad(importFn) {
3431
+ return react.default.lazy(() => importFn().then((m) => {
3432
+ return { default: m[Object.getOwnPropertyNames(m)[0]] };
3433
+ }));
3434
+ }
3392
3435
  const ILLUSTRATIONS = {
3393
3436
  EmptyFolder: {
3394
- light: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptyFolder }))),
3395
- dark: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptyFolderDark })))
3437
+ light: lazyLoad(() => import("@capra/icons/esm/images/EmptyFolder")),
3438
+ dark: lazyLoad(() => import("@capra/icons/esm/images/EmptyFolderDark"))
3396
3439
  },
3397
3440
  ArtSupplies: {
3398
- light: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.ArtSupplies }))),
3399
- dark: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.ArtSuppliesDark })))
3441
+ light: lazyLoad(() => import("@capra/icons/esm/images/ArtSupplies")),
3442
+ dark: lazyLoad(() => import("@capra/icons/esm/images/ArtSuppliesDark"))
3400
3443
  },
3401
3444
  Sandcastle: {
3402
- light: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.Sandcastle }))),
3403
- dark: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.SandcastleDark })))
3445
+ light: lazyLoad(() => import("@capra/icons/esm/images/Sandcastle")),
3446
+ dark: lazyLoad(() => import("@capra/icons/esm/images/SandcastleDark"))
3404
3447
  },
3405
3448
  EmptyBowl: {
3406
- light: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptyBowl }))),
3407
- dark: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptyBowl })))
3449
+ light: lazyLoad(() => import("@capra/icons/esm/images/EmptyBowl")),
3450
+ dark: lazyLoad(() => import("@capra/icons/esm/images/EmptyBowl"))
3408
3451
  },
3409
3452
  EmptySuitcase: {
3410
- light: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptySuitcase }))),
3411
- dark: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.EmptySuitcaseDark })))
3453
+ light: lazyLoad(() => import("@capra/icons/esm/images/EmptySuitcase")),
3454
+ dark: lazyLoad(() => import("@capra/icons/esm/images/EmptySuitcaseDark"))
3412
3455
  },
3413
3456
  Envelope: {
3414
- light: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.Envelope }))),
3415
- dark: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.Envelope })))
3457
+ light: lazyLoad(() => import("@capra/icons/esm/images/Envelope")),
3458
+ dark: lazyLoad(() => import("@capra/icons/esm/images/Envelope"))
3416
3459
  },
3417
3460
  MissingSock: {
3418
- light: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.MissingSock }))),
3419
- dark: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.MissingSock })))
3461
+ light: lazyLoad(() => import("@capra/icons/esm/images/MissingSock")),
3462
+ dark: lazyLoad(() => import("@capra/icons/esm/images/MissingSock"))
3420
3463
  },
3421
3464
  PizzaBox: {
3422
- light: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.PizzaBox }))),
3423
- dark: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.PizzaBox })))
3465
+ light: lazyLoad(() => import("@capra/icons/esm/images/PizzaBox")),
3466
+ dark: lazyLoad(() => import("@capra/icons/esm/images/PizzaBox"))
3424
3467
  },
3425
3468
  PottedPlant: {
3426
- light: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.PottedPlant }))),
3427
- dark: react.default.lazy(() => import("@capra/icons/images").then((m) => ({ default: m.PottedPlant })))
3469
+ light: lazyLoad(() => import("@capra/icons/esm/images/PottedPlant")),
3470
+ dark: lazyLoad(() => import("@capra/icons/esm/images/PottedPlant"))
3428
3471
  }
3429
3472
  };
3430
3473
  /**
@@ -4295,7 +4338,39 @@ const MenuRootTriggerPressable = react.forwardRef(function MenuRootTriggerPressa
4295
4338
  }, /* @__PURE__ */ react.createElement(react_aria_components.Pressable, null, children));
4296
4339
  });
4297
4340
  MenuRootTriggerPressable.displayName = "MenuRootTriggerPressable";
4298
- const Menu = ({ trigger, children, contentProps, open, onOpenChange, itemHoverAppearance = "default", itemActiveAccentBar = true, itemActiveLabelSemibold = true, panelPadding = "default", popoverOffset = 0, FORCE__className: classNameOverride }) => {
4341
+ /**
4342
+ * Wraps the menu list in a focus scope while the overlay is open. Must render under {@link AriaMenuTrigger} so
4343
+ * {@link OverlayTriggerStateContext} resolves for uncontrolled menus.
4344
+ *
4345
+ * `contain` is enabled only after focus moves inside the panel so the trigger (outside this subtree) can still
4346
+ * receive Tab/Enter to open the menu and so programmatic focus from the trigger into the first item is not blocked.
4347
+ */
4348
+ function MenuPanelFocusScope({ controlledOpen, children }) {
4349
+ const overlayState = react.useContext(react_aria_components.OverlayTriggerStateContext);
4350
+ const isOpen = controlledOpen !== void 0 ? controlledOpen : overlayState?.isOpen ?? false;
4351
+ const rootRef = react.useRef(null);
4352
+ const [focusInsidePanel, setFocusInsidePanel] = react.useState(false);
4353
+ react.useLayoutEffect(() => {
4354
+ if (!isOpen) setFocusInsidePanel(false);
4355
+ }, [isOpen]);
4356
+ const onFocusCapture = react.useCallback(() => {
4357
+ if (isOpen) setFocusInsidePanel(true);
4358
+ }, [isOpen]);
4359
+ const onBlurCapture = react.useCallback((event) => {
4360
+ const next = event.relatedTarget;
4361
+ if (rootRef.current?.contains(next)) return;
4362
+ setFocusInsidePanel(false);
4363
+ }, []);
4364
+ return /* @__PURE__ */ react.createElement("div", {
4365
+ ref: rootRef,
4366
+ onFocusCapture,
4367
+ onBlurCapture
4368
+ }, /* @__PURE__ */ react.createElement(react_aria.FocusScope, {
4369
+ contain: isOpen && focusInsidePanel,
4370
+ restoreFocus: true
4371
+ }, children));
4372
+ }
4373
+ const Menu = ({ trigger, children, contentProps, open, onOpenChange, itemHoverAppearance = "default", itemActiveAccentBar = true, itemActiveLabelSemibold = true, panelPadding = "default", popoverOffset = 0, trapFocus = false, FORCE__className: classNameOverride }) => {
4299
4374
  const sessionId = react.useId();
4300
4375
  const menuPanel = /* @__PURE__ */ react.createElement(react_aria_components.Menu, {
4301
4376
  className: clsx(Menu_module_default.menu, classNameOverride),
@@ -4307,11 +4382,12 @@ const Menu = ({ trigger, children, contentProps, open, onOpenChange, itemHoverAp
4307
4382
  itemActiveLabelSemibold
4308
4383
  })
4309
4384
  }, /* @__PURE__ */ react.createElement(MenuSectionIndentContext.Provider, { value: false }, children));
4385
+ const panelBody = trapFocus ? /* @__PURE__ */ react.createElement(MenuPanelFocusScope, { controlledOpen: open }, menuPanel) : menuPanel;
4310
4386
  const wrappedPanel = contentProps ? /* @__PURE__ */ react.createElement("div", {
4311
4387
  ...contentProps,
4312
4388
  [MENU_SESSION_DATA_ATTR]: sessionId,
4313
4389
  className: contentProps.className
4314
- }, menuPanel) : /* @__PURE__ */ react.createElement("div", { [MENU_SESSION_DATA_ATTR]: sessionId }, menuPanel);
4390
+ }, panelBody) : /* @__PURE__ */ react.createElement("div", { [MENU_SESSION_DATA_ATTR]: sessionId }, panelBody);
4315
4391
  return /* @__PURE__ */ react.createElement(MenuSessionContext.Provider, { value: sessionId }, /* @__PURE__ */ react.createElement(react_aria_components.MenuTrigger, {
4316
4392
  isOpen: open,
4317
4393
  onOpenChange
@@ -4337,23 +4413,23 @@ const submenuPanelClassName = Menu_module_default.menu;
4337
4413
  //#endregion
4338
4414
  //#region src/components/TabNav/TabNav.module.css
4339
4415
  var TabNav_module_default = {
4340
- "dropdownIcon": "capra-TabNav-module-VtoGYa-dropdownIcon",
4341
- "extraContent": "capra-TabNav-module-VtoGYa-extraContent",
4342
- "indentSpacer": "capra-TabNav-module-VtoGYa-indentSpacer",
4343
- "panel": "capra-TabNav-module-VtoGYa-panel",
4344
- "root": "capra-TabNav-module-VtoGYa-root",
4345
- "subMenu": "capra-TabNav-module-VtoGYa-subMenu",
4346
- "subMenuItem": "capra-TabNav-module-VtoGYa-subMenuItem",
4347
- "subMenuItemActive": "capra-TabNav-module-VtoGYa-subMenuItemActive",
4348
- "subTab": "capra-TabNav-module-VtoGYa-subTab",
4349
- "subTabList": "capra-TabNav-module-VtoGYa-subTabList",
4350
- "subTabListItem": "capra-TabNav-module-VtoGYa-subTabListItem",
4351
- "tab": "capra-TabNav-module-VtoGYa-tab",
4352
- "tabBar": "capra-TabNav-module-VtoGYa-tabBar",
4353
- "tabGroup": "capra-TabNav-module-VtoGYa-tabGroup",
4354
- "tabIcon": "capra-TabNav-module-VtoGYa-tabIcon",
4355
- "tabLabel": "capra-TabNav-module-VtoGYa-tabLabel",
4356
- "tabList": "capra-TabNav-module-VtoGYa-tabList"
4416
+ "dropdownIcon": "capra-TabNav-module-e0IbmW-dropdownIcon",
4417
+ "extraContent": "capra-TabNav-module-e0IbmW-extraContent",
4418
+ "indentSpacer": "capra-TabNav-module-e0IbmW-indentSpacer",
4419
+ "panel": "capra-TabNav-module-e0IbmW-panel",
4420
+ "root": "capra-TabNav-module-e0IbmW-root",
4421
+ "subMenu": "capra-TabNav-module-e0IbmW-subMenu",
4422
+ "subMenuItem": "capra-TabNav-module-e0IbmW-subMenuItem",
4423
+ "subMenuItemActive": "capra-TabNav-module-e0IbmW-subMenuItemActive",
4424
+ "subTab": "capra-TabNav-module-e0IbmW-subTab",
4425
+ "subTabList": "capra-TabNav-module-e0IbmW-subTabList",
4426
+ "subTabListItem": "capra-TabNav-module-e0IbmW-subTabListItem",
4427
+ "tab": "capra-TabNav-module-e0IbmW-tab",
4428
+ "tabBar": "capra-TabNav-module-e0IbmW-tabBar",
4429
+ "tabGroup": "capra-TabNav-module-e0IbmW-tabGroup",
4430
+ "tabIcon": "capra-TabNav-module-e0IbmW-tabIcon",
4431
+ "tabLabel": "capra-TabNav-module-e0IbmW-tabLabel",
4432
+ "tabList": "capra-TabNav-module-e0IbmW-tabList"
4357
4433
  };
4358
4434
  //#endregion
4359
4435
  //#region src/components/TabNav/TabNav.tsx
@@ -4373,7 +4449,27 @@ function optionalAriaLabelProps(source) {
4373
4449
  const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", centered = false, tabBarExtraSlot, wrap = true, FORCE__className: classNameOverride, "aria-label": ariaLabel = "Navigation", ...props }) => {
4374
4450
  const [subMenuOpen, setSubMenuOpen] = react.useState({});
4375
4451
  const hoverCloseTimeoutRef = react.useRef(null);
4376
- const pointerDownOnTriggerRef = react.useRef(false);
4452
+ const pendingKeyboardFocusKeyRef = react.useRef(null);
4453
+ const menuContentRefs = react.useRef({});
4454
+ const getTriggerElement = react.useCallback((key) => {
4455
+ const trigger = document.getElementById(`tabnav-${key}`);
4456
+ return trigger instanceof HTMLElement ? trigger : null;
4457
+ }, []);
4458
+ const getEnabledMenuItems = react.useCallback((key) => {
4459
+ const menu = menuContentRefs.current[key]?.querySelector("[role=\"menu\"]");
4460
+ if (!(menu instanceof HTMLElement)) return [];
4461
+ return Array.from(menu.querySelectorAll("[role=\"menuitem\"]")).filter((element) => element.getAttribute("aria-disabled") !== "true");
4462
+ }, []);
4463
+ const focusMenuItem = react.useCallback((key, direction) => {
4464
+ const menuItems = getEnabledMenuItems(key);
4465
+ const target = direction === "first" ? menuItems[0] : menuItems[menuItems.length - 1];
4466
+ if (target) {
4467
+ target.focus();
4468
+ return true;
4469
+ }
4470
+ getTriggerElement(key)?.focus();
4471
+ return false;
4472
+ }, [getEnabledMenuItems, getTriggerElement]);
4377
4473
  const openSubMenu = react.useCallback((key) => {
4378
4474
  if (hoverCloseTimeoutRef.current) {
4379
4475
  clearTimeout(hoverCloseTimeoutRef.current);
@@ -4416,22 +4512,101 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4416
4512
  hoverCloseTimeoutRef.current = null;
4417
4513
  }
4418
4514
  }, []);
4515
+ const closeAllSubMenus = react.useCallback(() => {
4516
+ if (hoverCloseTimeoutRef.current) {
4517
+ clearTimeout(hoverCloseTimeoutRef.current);
4518
+ hoverCloseTimeoutRef.current = null;
4519
+ }
4520
+ pendingKeyboardFocusKeyRef.current = null;
4521
+ setSubMenuOpen({});
4522
+ }, []);
4419
4523
  const [activeMainKey, activeSubKey] = react.useMemo(() => {
4420
4524
  if (!activeKey) return ["", ""];
4421
4525
  const idx = activeKey.indexOf(SUB_KEY_SEP);
4422
4526
  if (idx >= 0) return [activeKey.slice(0, idx), activeKey.slice(idx + 2)];
4423
4527
  return [String(activeKey), ""];
4424
4528
  }, [activeKey]);
4529
+ /** While a horizontal flyout is open, sibling tab controls use roving `tabIndex` so Tab cannot reach them. */
4530
+ const horizontalFlyoutOpenKey = react.useMemo(() => {
4531
+ if (tabPlacement !== "horizontal") return;
4532
+ return items.find((i) => Boolean(i.subItems?.length && subMenuOpen[i.key]))?.key;
4533
+ }, [
4534
+ items,
4535
+ subMenuOpen,
4536
+ tabPlacement
4537
+ ]);
4425
4538
  /** Expand vertical groups when the active route targets a sub-item. */
4426
4539
  react.useEffect(() => {
4427
- if (!activeSubKey) return;
4540
+ if (tabPlacement !== "vertical" || !activeSubKey) return;
4428
4541
  items.forEach((item) => {
4429
4542
  if (item.subItems?.some((s) => s.key === activeSubKey)) setSubMenuOpen((prev) => ({
4430
4543
  ...prev,
4431
4544
  [item.key]: true
4432
4545
  }));
4433
4546
  });
4434
- }, [activeSubKey, items]);
4547
+ }, [
4548
+ activeSubKey,
4549
+ items,
4550
+ tabPlacement
4551
+ ]);
4552
+ react.useEffect(() => {
4553
+ const key = pendingKeyboardFocusKeyRef.current;
4554
+ if (!key || !subMenuOpen[key] || tabPlacement !== "horizontal") return;
4555
+ const menuContent = menuContentRefs.current[key];
4556
+ const focusFirstMenuItem = () => {
4557
+ const items = getEnabledMenuItems(key);
4558
+ const target = items.find((element) => element.getAttribute("aria-current") !== "page") ?? items[0];
4559
+ if (target) {
4560
+ target.focus();
4561
+ pendingKeyboardFocusKeyRef.current = null;
4562
+ return true;
4563
+ }
4564
+ return false;
4565
+ };
4566
+ if (focusFirstMenuItem()) return;
4567
+ if (!menuContent) return;
4568
+ const observer = new MutationObserver(() => {
4569
+ if (focusFirstMenuItem()) observer.disconnect();
4570
+ });
4571
+ observer.observe(menuContent, {
4572
+ childList: true,
4573
+ subtree: true
4574
+ });
4575
+ return () => {
4576
+ observer.disconnect();
4577
+ };
4578
+ }, [
4579
+ getEnabledMenuItems,
4580
+ subMenuOpen,
4581
+ tabPlacement
4582
+ ]);
4583
+ const closeHorizontalSubMenuFromKeyboard = react.useCallback((key) => {
4584
+ pendingKeyboardFocusKeyRef.current = null;
4585
+ closeAllSubMenus();
4586
+ getTriggerElement(key)?.focus();
4587
+ }, [closeAllSubMenus, getTriggerElement]);
4588
+ const handleOpenSubMenuTabKey = react.useCallback((event, key) => {
4589
+ const menuItems = getEnabledMenuItems(key);
4590
+ if (menuItems.length === 0) {
4591
+ event.preventDefault();
4592
+ event.stopPropagation();
4593
+ getTriggerElement(key)?.focus();
4594
+ return;
4595
+ }
4596
+ const activeElement = document.activeElement;
4597
+ const currentIndex = activeElement instanceof HTMLElement ? menuItems.indexOf(activeElement) : -1;
4598
+ event.preventDefault();
4599
+ event.stopPropagation();
4600
+ if (currentIndex === -1) {
4601
+ focusMenuItem(key, event.shiftKey ? "last" : "first");
4602
+ return;
4603
+ }
4604
+ menuItems[(currentIndex + (event.shiftKey ? -1 : 1) + menuItems.length) % menuItems.length]?.focus();
4605
+ }, [
4606
+ focusMenuItem,
4607
+ getEnabledMenuItems,
4608
+ getTriggerElement
4609
+ ]);
4435
4610
  const handleTabClick = (key, event) => {
4436
4611
  const item = items.find((i) => i.key === key);
4437
4612
  if (item?.disabled) {
@@ -4444,9 +4619,40 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4444
4619
  const handleKeyDown = (event, key) => {
4445
4620
  const itemIndex = items.findIndex((i) => i.key === key);
4446
4621
  if (itemIndex === -1) return;
4622
+ const item = items[itemIndex];
4623
+ const hasSubItems = Boolean(item?.subItems?.length);
4447
4624
  let nextIndex = itemIndex;
4448
4625
  const isHorizontal = tabPlacement === "horizontal";
4449
4626
  const isVertical = tabPlacement === "vertical";
4627
+ if (hasSubItems && isHorizontal && (event.key === "Enter" || event.key === " ")) {
4628
+ event.preventDefault();
4629
+ event.stopPropagation();
4630
+ if (subMenuOpen[key]) {
4631
+ pendingKeyboardFocusKeyRef.current = null;
4632
+ closeSubMenu(key);
4633
+ } else {
4634
+ pendingKeyboardFocusKeyRef.current = key;
4635
+ openSubMenu(key);
4636
+ }
4637
+ return;
4638
+ }
4639
+ if (hasSubItems && isHorizontal && event.key === "Escape") {
4640
+ event.preventDefault();
4641
+ event.stopPropagation();
4642
+ closeHorizontalSubMenuFromKeyboard(key);
4643
+ return;
4644
+ }
4645
+ if (hasSubItems && isHorizontal && subMenuOpen[key] && event.key === "Tab") {
4646
+ handleOpenSubMenuTabKey(event, key);
4647
+ return;
4648
+ }
4649
+ if (hasSubItems && isHorizontal && subMenuOpen[key]) {
4650
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "Home" || event.key === "End") {
4651
+ event.preventDefault();
4652
+ event.stopPropagation();
4653
+ }
4654
+ return;
4655
+ }
4450
4656
  if (isHorizontal) {
4451
4657
  if (event.key === "ArrowLeft") {
4452
4658
  event.preventDefault();
@@ -4510,6 +4716,7 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4510
4716
  const isSubMenuOpen = hasSubItems && Boolean(subMenuOpen[item.key]);
4511
4717
  /** Only one `aria-current="page"` in the nav: parent tab when no child route; sub-items set it when a child is current. */
4512
4718
  const ariaCurrentPage = item.key === activeMainKey && (!hasSubItems || !activeSubKey);
4719
+ const computedTabIndex = tabPlacement === "horizontal" && horizontalFlyoutOpenKey != null ? !item.disabled && item.key === horizontalFlyoutOpenKey && hasSubItems ? 0 : -1 : void 0;
4513
4720
  const commonTabProps = {
4514
4721
  "data-tab-key": item.key,
4515
4722
  "data-indent": item.indent || void 0,
@@ -4519,7 +4726,8 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4519
4726
  "data-disabled": item.disabled || void 0,
4520
4727
  id: `tabnav-${item.key}`,
4521
4728
  className: TabNav_module_default.tab,
4522
- ...optionalAriaLabelProps(item)
4729
+ ...optionalAriaLabelProps(item),
4730
+ ...computedTabIndex !== void 0 ? { tabIndex: computedTabIndex } : {}
4523
4731
  };
4524
4732
  if (hasSubItems && tabPlacement === "vertical") {
4525
4733
  const expanded = Boolean(subMenuOpen[item.key]);
@@ -4613,46 +4821,53 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4613
4821
  const trigger = /* @__PURE__ */ react.createElement("button", {
4614
4822
  type: "button",
4615
4823
  ...commonTabProps,
4824
+ "aria-expanded": isSubMenuOpen,
4825
+ "aria-haspopup": "menu",
4616
4826
  "aria-current": ariaCurrentPage ? "page" : void 0,
4617
4827
  disabled: item.disabled,
4618
4828
  onMouseEnter: () => openSubMenu(item.key),
4619
4829
  onMouseLeave: () => scheduleCloseSubMenu(item.key),
4620
- onPointerDown: () => {
4621
- pointerDownOnTriggerRef.current = true;
4622
- },
4623
- onPointerUp: () => {
4624
- pointerDownOnTriggerRef.current = false;
4625
- },
4626
- onFocus: () => {
4627
- if (!pointerDownOnTriggerRef.current) openSubMenu(item.key);
4628
- },
4629
4830
  onKeyDown: (e) => handleKeyDown(e, item.key)
4630
4831
  }, renderTabContent(item));
4631
4832
  return /* @__PURE__ */ react.createElement(Menu, {
4632
4833
  key: item.key,
4633
4834
  trigger,
4835
+ trapFocus: true,
4634
4836
  itemHoverAppearance: "accent",
4635
4837
  itemActiveAccentBar: false,
4636
4838
  itemActiveLabelSemibold: false,
4637
4839
  panelPadding: "none",
4638
4840
  popoverOffset: 1,
4639
4841
  open: subMenuOpen[item.key],
4640
- onOpenChange: (open) => setSubMenuOpen((prev) => ({
4641
- ...prev,
4642
- [item.key]: open
4643
- })),
4842
+ onOpenChange: (open) => {
4843
+ if (open) {
4844
+ openSubMenu(item.key);
4845
+ return;
4846
+ }
4847
+ pendingKeyboardFocusKeyRef.current = null;
4848
+ closeSubMenu(item.key);
4849
+ },
4644
4850
  contentProps: {
4851
+ ref: (node) => {
4852
+ menuContentRefs.current[item.key] = node;
4853
+ },
4645
4854
  onMouseEnter: () => {
4646
4855
  cancelScheduledClose();
4647
4856
  openSubMenu(item.key);
4648
4857
  },
4649
- onMouseLeave: () => closeSubMenu(item.key)
4858
+ onMouseLeave: () => closeSubMenu(item.key),
4859
+ onKeyDownCapture: (event) => {
4860
+ if (event.key === "Escape") {
4861
+ event.preventDefault();
4862
+ event.stopPropagation();
4863
+ closeHorizontalSubMenuFromKeyboard(item.key);
4864
+ return;
4865
+ }
4866
+ if (event.key === "Tab") handleOpenSubMenuTabKey(event, item.key);
4867
+ }
4650
4868
  }
4651
4869
  }, item.subItems.map((sub) => {
4652
- const closeMenu = () => setSubMenuOpen((prev) => ({
4653
- ...prev,
4654
- [item.key]: false
4655
- }));
4870
+ const closeMenu = () => closeAllSubMenus();
4656
4871
  return /* @__PURE__ */ react.createElement(Menu.Item, {
4657
4872
  key: sub.key,
4658
4873
  label: sub.name,
@@ -4681,7 +4896,7 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
4681
4896
  role: "link",
4682
4897
  "aria-disabled": item.disabled,
4683
4898
  "aria-current": ariaCurrentPage ? "page" : void 0,
4684
- tabIndex: 0,
4899
+ tabIndex: item.disabled ? -1 : computedTabIndex ?? 0,
4685
4900
  onKeyDown: (e) => {
4686
4901
  if (e.key === "Enter" || e.key === " ") {
4687
4902
  e.preventDefault();
@@ -4749,11 +4964,13 @@ var Tooltip_module_default = {
4749
4964
  };
4750
4965
  //#endregion
4751
4966
  //#region src/components/Tooltip/Tooltip.tsx
4967
+ const TOOLTIP_HOVER_SHOW_DELAY_MS = 500;
4752
4968
  /**
4753
4969
  * A component to display additional information when hovering or focusing on an interactive element.
4754
4970
  */
4755
4971
  function Tooltip({ children, title, shortcut, getContainer, placement = "bottom", isDisabled = false, FORCE__className: classNameOverride, ...props }) {
4756
4972
  return /* @__PURE__ */ react.createElement(react_aria_components.TooltipTrigger, {
4973
+ delay: TOOLTIP_HOVER_SHOW_DELAY_MS,
4757
4974
  isDisabled,
4758
4975
  shouldCloseOnPress: false
4759
4976
  }, children, /* @__PURE__ */ react.createElement(react_aria.UNSAFE_PortalProvider, { getContainer }, /* @__PURE__ */ react.createElement(react_aria_components.Tooltip, {