@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 +6 -0
- package/dist/index.cjs +326 -109
- package/dist/index.d.cts +20 -12
- package/dist/index.d.mts +20 -12
- package/dist/index.mjs +326 -109
- package/dist/style.css +93 -67
- package/package.json +8 -7
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-
|
|
2917
|
-
"description": "capra-Drawer-module-
|
|
2918
|
-
"drawerInFromLeft": "capra-Drawer-module-
|
|
2919
|
-
"drawerInFromRight": "capra-Drawer-module-
|
|
2920
|
-
"
|
|
2921
|
-
"
|
|
2922
|
-
"
|
|
2923
|
-
"
|
|
2924
|
-
"
|
|
2925
|
-
"
|
|
2926
|
-
"
|
|
2927
|
-
"
|
|
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 (!
|
|
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
|
-
|
|
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)
|
|
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:
|
|
3371
|
-
dark:
|
|
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:
|
|
3375
|
-
dark:
|
|
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:
|
|
3379
|
-
dark:
|
|
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:
|
|
3383
|
-
dark:
|
|
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:
|
|
3387
|
-
dark:
|
|
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:
|
|
3391
|
-
dark:
|
|
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:
|
|
3395
|
-
dark:
|
|
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:
|
|
3399
|
-
dark:
|
|
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:
|
|
3403
|
-
dark:
|
|
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
|
-
|
|
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
|
-
},
|
|
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-
|
|
4317
|
-
"extraContent": "capra-TabNav-module-
|
|
4318
|
-
"indentSpacer": "capra-TabNav-module-
|
|
4319
|
-
"panel": "capra-TabNav-module-
|
|
4320
|
-
"root": "capra-TabNav-module-
|
|
4321
|
-
"subMenu": "capra-TabNav-module-
|
|
4322
|
-
"subMenuItem": "capra-TabNav-module-
|
|
4323
|
-
"subMenuItemActive": "capra-TabNav-module-
|
|
4324
|
-
"subTab": "capra-TabNav-module-
|
|
4325
|
-
"subTabList": "capra-TabNav-module-
|
|
4326
|
-
"subTabListItem": "capra-TabNav-module-
|
|
4327
|
-
"tab": "capra-TabNav-module-
|
|
4328
|
-
"tabBar": "capra-TabNav-module-
|
|
4329
|
-
"tabGroup": "capra-TabNav-module-
|
|
4330
|
-
"tabIcon": "capra-TabNav-module-
|
|
4331
|
-
"tabLabel": "capra-TabNav-module-
|
|
4332
|
-
"tabList": "capra-TabNav-module-
|
|
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
|
|
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
|
-
}, [
|
|
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) =>
|
|
4617
|
-
|
|
4618
|
-
|
|
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 = () =>
|
|
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, {
|