@charcuterie/ui 4.18.0 → 4.18.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.
Files changed (33) hide show
  1. package/dist/Overlay/AnchoredOverlayScope.d.ts +10 -0
  2. package/dist/Overlay/AnchoredOverlayScope.d.ts.map +1 -0
  3. package/dist/Overlay/AnchoredOverlayScope.js +15 -0
  4. package/dist/Overlay/AnchoredOverlayScope.js.map +1 -0
  5. package/dist/Overlay/OverlayPanel.d.ts.map +1 -1
  6. package/dist/Overlay/OverlayPanel.js +6 -3
  7. package/dist/Overlay/OverlayPanel.js.map +1 -1
  8. package/dist/Overlay/useAnchoredOverlay.d.ts.map +1 -1
  9. package/dist/Overlay/useAnchoredOverlay.js +15 -3
  10. package/dist/Overlay/useAnchoredOverlay.js.map +1 -1
  11. package/dist/Tabs/PanelTabs.d.ts.map +1 -1
  12. package/dist/Tabs/PanelTabs.js +6 -1
  13. package/dist/Tabs/PanelTabs.js.map +1 -1
  14. package/dist/Tabs/RoutedTabs.d.ts.map +1 -1
  15. package/dist/Tabs/RoutedTabs.js +3 -1
  16. package/dist/Tabs/RoutedTabs.js.map +1 -1
  17. package/dist/Tabs/useRevealCurrentTab.d.ts +4 -0
  18. package/dist/Tabs/useRevealCurrentTab.d.ts.map +1 -0
  19. package/dist/Tabs/useRevealCurrentTab.js +85 -0
  20. package/dist/Tabs/useRevealCurrentTab.js.map +1 -0
  21. package/package.json +1 -1
  22. package/src/Dialog/Dialog.mdx +9 -0
  23. package/src/Dialog/Dialog.stories.tsx +51 -2
  24. package/src/Dialog/Dialog.test.tsx +59 -0
  25. package/src/Overlay/AnchoredOverlayScope.ts +42 -0
  26. package/src/Overlay/OverlayPanel.tsx +14 -3
  27. package/src/Overlay/useAnchoredOverlay.ts +21 -3
  28. package/src/Tabs/PanelTabs.tsx +9 -2
  29. package/src/Tabs/RoutedTabs.tsx +6 -0
  30. package/src/Tabs/Tabs.mdx +8 -0
  31. package/src/Tabs/Tabs.stories.tsx +73 -0
  32. package/src/Tabs/Tabs.test.tsx +85 -0
  33. package/src/Tabs/useRevealCurrentTab.ts +93 -0
@@ -0,0 +1,10 @@
1
+ /** Anchored children share their enclosing modal's dismissal boundary. */
2
+ type AnchoredOverlayScope = {
3
+ topId: string | null;
4
+ register: (id: string) => void;
5
+ unregister: (id: string) => void;
6
+ };
7
+ export declare const AnchoredOverlayScopeContext: import("react").Context<AnchoredOverlayScope | null>;
8
+ export declare function useAnchoredOverlayScope(): AnchoredOverlayScope;
9
+ export {};
10
+ //# sourceMappingURL=AnchoredOverlayScope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnchoredOverlayScope.d.ts","sourceRoot":"","sources":["../../src/Overlay/AnchoredOverlayScope.ts"],"names":[],"mappings":"AAOA,0EAA0E;AAC1E,KAAK,oBAAoB,GAAG;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC,CAAA;AACD,eAAO,MAAM,2BAA2B,sDACU,CAAA;AAClD,wBAAgB,uBAAuB,IAAI,oBAAoB,CA0B9D"}
@@ -0,0 +1,15 @@
1
+ import { createContext, useCallback, useMemo, useState, } from "react";
2
+ export const AnchoredOverlayScopeContext = createContext(null);
3
+ export function useAnchoredOverlayScope() {
4
+ const [ids, setIds] = useState([]);
5
+ const register = useCallback((id) => setIds((previous) => previous.includes(id)
6
+ ? previous
7
+ : [...previous, id]), []);
8
+ const unregister = useCallback((id) => setIds((previous) => previous.filter((value) => value !== id)), []);
9
+ return useMemo(() => ({
10
+ topId: ids.at(-1) ?? null,
11
+ register,
12
+ unregister,
13
+ }), [ids, register, unregister]);
14
+ }
15
+ //# sourceMappingURL=AnchoredOverlayScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnchoredOverlayScope.js","sourceRoot":"","sources":["../../src/Overlay/AnchoredOverlayScope.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,EACP,QAAQ,GACT,MAAM,OAAO,CAAA;AAQd,MAAM,CAAC,MAAM,2BAA2B,GACtC,aAAa,CAA8B,IAAI,CAAC,CAAA;AAClD,MAAM,UAAU,uBAAuB;IACrC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAW,EAAE,CAAC,CAAA;IAC5C,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,EAAU,EAAE,EAAE,CACb,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAClB,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnB,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,EAAE,CAAC,CACtB,EACH,EAAE,CACH,CAAA;IACD,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,EAAU,EAAE,EAAE,CACb,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAClB,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CACzC,EACH,EAAE,CACH,CAAA;IACD,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC;QACL,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI;QACzB,QAAQ;QACR,UAAU;KACX,CAAC,EACF,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,CAAC,CAC5B,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"OverlayPanel.d.ts","sourceRoot":"","sources":["../../src/Overlay/OverlayPanel.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAOjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC5C,8CAA8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAA;CAChC,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,6IAU1B,iBAAiB,KAAG,SAuLtB,CAAA"}
1
+ {"version":3,"file":"OverlayPanel.d.ts","sourceRoot":"","sources":["../../src/Overlay/OverlayPanel.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAWjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC5C,8CAA8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAA;CAChC,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,6IAU1B,iBAAiB,KAAG,SA8LtB,CAAA"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { useLatestRef, useUniqueId, } from "@charcuterie/logic";
3
3
  import { FloatingFocusManager, FloatingPortal, useDismiss, useFloating, useInteractions, useRole, } from "@floating-ui/react";
4
4
  import { useCallback, useEffect } from "react";
5
+ import { AnchoredOverlayScopeContext, useAnchoredOverlayScope, } from "./AnchoredOverlayScope.js";
5
6
  import { lockScrollBehind } from "./lockScrollBehind.js";
6
7
  import { useOverlayStack } from "./OverlayStack.js";
7
8
  import { SharedBackdrop } from "./SharedBackdrop.js";
@@ -15,6 +16,8 @@ export const OverlayPanel = ({ "aria-label": ariaLabel, "aria-labelledby": ariaL
15
16
  onCloseRef.current();
16
17
  }, [onCloseRef]);
17
18
  const isTop = !stack.isProvided || stack.topId === id;
19
+ const anchors = useAnchoredOverlayScope();
20
+ const hasOpenAnchor = anchors.topId !== null;
18
21
  const { context, refs } = useFloating({
19
22
  open: isVisible,
20
23
  onOpenChange: (isNextVisible) => {
@@ -25,13 +28,13 @@ export const OverlayPanel = ({ "aria-label": ariaLabel, "aria-labelledby": ariaL
25
28
  });
26
29
  const { getFloatingProps } = useInteractions([
27
30
  useDismiss(context, {
28
- escapeKey: isDismissable && isTop,
31
+ escapeKey: isDismissable && isTop && !hasOpenAnchor,
29
32
  // The top's outside press is the backdrop click. A per-panel
30
33
  // `outsidePress` on a *lower* modal would close it too, so it is
31
34
  // scoped to the top — which is also why the scrim needs no click
32
35
  // handler of its own.
33
36
  outsidePress: (event) => {
34
- if (!isDismissable || !isTop)
37
+ if (!isDismissable || !isTop || hasOpenAnchor)
35
38
  return false;
36
39
  /*
37
40
  * ⚠️ A press on a toast is NOT an outside press.
@@ -123,6 +126,6 @@ export const OverlayPanel = ({ "aria-label": ariaLabel, "aria-labelledby": ariaL
123
126
  // but the top, so a lower modal is unreachable across the
124
127
  // portal boundary even where `FloatingFocusManager`'s
125
128
  // outside-hiding does not reach a sibling portal.
126
- inert: isTop ? undefined : true, ref: refs.setFloating, role: role, children: children }) }) }) })] }));
129
+ inert: isTop ? undefined : true, ref: refs.setFloating, role: role, children: _jsx(AnchoredOverlayScopeContext.Provider, { value: anchors, children: children }) }) }) }) })] }));
127
130
  };
128
131
  //# sourceMappingURL=OverlayPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"OverlayPanel.js","sourceRoot":"","sources":["../../src/Overlay/OverlayPanel.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OverlayPanel.js","sourceRoot":"","sources":["../../src/Overlay/OverlayPanel.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useAnchoredOverlay.d.ts","sourceRoot":"","sources":["../../src/Overlay/useAnchoredOverlay.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,cAAc,EACf,MAAM,oBAAoB,CAAA;AAY3B,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,MAAM,mBAAmB,GAC3B,QAAQ,GACR,SAAS,GACT,MAAM,CAAA;AAEV;;;;;;;;;;GAUG;AACH,MAAM,MAAM,qBAAqB,GAC7B,WAAW,GACX,cAAc,CAAA;AAElB,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAA;IACnD;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,+DAA+D;IAC/D,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,IAAI,EAAE,mBAAmB,CAAA;IACzB;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB,CAAA;AAQD,eAAO,MAAM,kBAAkB,GAAI,8MAehC,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;CA4L3B,CAAA"}
1
+ {"version":3,"file":"useAnchoredOverlay.d.ts","sourceRoot":"","sources":["../../src/Overlay/useAnchoredOverlay.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,cAAc,EACf,MAAM,oBAAoB,CAAA;AAY3B,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,MAAM,mBAAmB,GAC3B,QAAQ,GACR,SAAS,GACT,MAAM,CAAA;AAEV;;;;;;;;;;GAUG;AACH,MAAM,MAAM,qBAAqB,GAC7B,WAAW,GACX,cAAc,CAAA;AAElB,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAA;IACnD;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,+DAA+D;IAC/D,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,IAAI,EAAE,mBAAmB,CAAA;IACzB;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB,CAAA;AAQD,eAAO,MAAM,kBAAkB,GAAI,8MAehC,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;CAyM3B,CAAA"}
@@ -1,12 +1,24 @@
1
1
  import { useClonedChild, useUniqueId, } from "@charcuterie/logic";
2
2
  import { autoUpdate, flip, offset, shift, size, useDismiss, useFloating, useInteractions, useRole, } from "@floating-ui/react";
3
- import { createElement, useLayoutEffect } from "react";
3
+ import { createElement, useContext, useLayoutEffect, } from "react";
4
+ import { AnchoredOverlayScopeContext } from "./AnchoredOverlayScope.js";
4
5
  // `useClonedChild` insists on exactly one element, and hooks cannot be
5
6
  // called conditionally — so in `anchorRef` mode we still call it, on this
6
7
  // throwaway, and discard the result. It is never rendered and never
7
8
  // receives the floating reference (that goes to `anchorRef`).
8
9
  const PLACEHOLDER_TRIGGER = createElement("span");
9
10
  export const useAnchoredOverlay = ({ anchorRef, isEscapeDismissable = true, isHeightClamped = false, isOutsidePressDismissable = true, isTriggerWidthMatched = false, isVisible, isWidthClamped = false, maxHeightPx, maxWidthPx, offsetValue = 8, onDismiss, placement = "bottom-start", role, trigger, }) => {
11
+ const scope = useContext(AnchoredOverlayScopeContext);
12
+ const anchorId = useUniqueId();
13
+ const register = scope?.register;
14
+ const unregister = scope?.unregister;
15
+ useLayoutEffect(() => {
16
+ if (!isVisible || !register || !unregister)
17
+ return;
18
+ register(anchorId);
19
+ return () => unregister(anchorId);
20
+ }, [isVisible, anchorId, register, unregister]);
21
+ const isTopAnchor = scope === null || scope.topId === anchorId;
10
22
  const middleware = [
11
23
  offset(offsetValue),
12
24
  flip(),
@@ -58,8 +70,8 @@ export const useAnchoredOverlay = ({ anchorRef, isEscapeDismissable = true, isHe
58
70
  // decision raised and this answers.
59
71
  const { getFloatingProps, getReferenceProps } = useInteractions([
60
72
  useDismiss(context, {
61
- escapeKey: isEscapeDismissable,
62
- outsidePress: isOutsidePressDismissable,
73
+ escapeKey: isEscapeDismissable && isTopAnchor,
74
+ outsidePress: isOutsidePressDismissable && isTopAnchor,
63
75
  }),
64
76
  useRole(context, { role }),
65
77
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"useAnchoredOverlay.js","sourceRoot":"","sources":["../../src/Overlay/useAnchoredOverlay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useAnchoredOverlay.js","sourceRoot":"","sources":["../../src/Overlay/useAnchoredOverlay.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PanelTabs.d.ts","sourceRoot":"","sources":["../../src/Tabs/PanelTabs.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAgBhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,eAAO,MAAM,SAAS,GAAI,2EAQvB,cAAc,KAAG,SA6KnB,CAAA"}
1
+ {"version":3,"file":"PanelTabs.d.ts","sourceRoot":"","sources":["../../src/Tabs/PanelTabs.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAiBhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,eAAO,MAAM,SAAS,GAAI,2EAQvB,cAAc,KAAG,SAoLnB,CAAA"}
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef } from "react";
4
4
  import { toClassName } from "../toClassName.js";
5
5
  import { TabTrigger } from "./TabTrigger.js";
6
6
  import { toTabListClass } from "./tabStyles.js";
7
+ import { useRevealCurrentTab } from "./useRevealCurrentTab.js";
7
8
  const ORIENTATION_KEYS = {
8
9
  horizontal: {
9
10
  next: "ArrowRight",
@@ -95,6 +96,7 @@ export const PanelTabs = ({ activation = "automatic", activeKey, className, labe
95
96
  * `selectTabIndex` itself and got fixed there.
96
97
  */
97
98
  const shownKey = selection.selectedValue ?? selection.pendingValue;
99
+ const revealRef = useRevealCurrentTab(shownKey, orientation);
98
100
  const selectTab = useCallback((key) => {
99
101
  // Both, because a pointer press moves focus *and* chooses.
100
102
  // Only selecting would leave `automatic`'s effect below to
@@ -146,7 +148,10 @@ export const PanelTabs = ({ activation = "automatic", activeKey, className, labe
146
148
  keyEvent.preventDefault();
147
149
  select(activeValue);
148
150
  }
149
- }, ref: tablistRef, role: "tablist", children: tabs.map((tab, index) => (_jsx(TabTrigger, { id: `${baseId}-tab-${index}`, isSelected: shownKey === tab.key, onSelect: selectTab, panelId: `${baseId}-panel-${index}`, registerFocus: focus.register, orientation: orientation, registerSelection: selection.register, tab: tab,
151
+ }, ref: (element) => {
152
+ tablistRef.current = element;
153
+ revealRef.current = element;
154
+ }, role: "tablist", children: tabs.map((tab, index) => (_jsx(TabTrigger, { id: `${baseId}-tab-${index}`, isSelected: shownKey === tab.key, onSelect: selectTab, panelId: `${baseId}-panel-${index}`, registerFocus: focus.register, orientation: orientation, registerSelection: selection.register, tab: tab,
150
155
  // The roving-tabindex rule read from the core rather
151
156
  // than restated here: exactly one member is in the tab
152
157
  // order, so Tab enters and leaves the bar once while
@@ -1 +1 @@
1
- {"version":3,"file":"PanelTabs.js","sourceRoot":"","sources":["../../src/Tabs/PanelTabs.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,EACf,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAkB,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,gBAAgB,GAGlB;IACF,UAAU,EAAE;QACV,IAAI,EAAE,YAAY;QAClB,QAAQ,EAAE,WAAW;KACtB;IACD,QAAQ,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE;CACrD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,EACxB,UAAU,GAAG,WAAW,EACxB,SAAS,EACT,SAAS,EACT,KAAK,EACL,QAAQ,EACR,WAAW,GAAG,YAAY,EAC1B,IAAI,GACW,EAAa,EAAE;IAC9B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAE/C,MAAM,WAAW,GAAG,MAAM,CACxB,IAAI,GAAG,EAA6B,CACrC,CAAA;IAED,MAAM,UAAU,GACd,SAAS;QACT,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,GAAG;QACxC,IAAI,CAAA;IAEN,MAAM,SAAS,GAAG,eAAe,CAAS;QACxC,QAAQ;QACR,aAAa,EAAE,UAAU;KAC1B,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,cAAc,CAAS;QACnC,WAAW,EAAE,UAAU;KACxB,CAAC,CAAA;IAEF,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAE5B,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,KAAK,CAAA;IAE7C;;;;;;;;;;;;;;OAcG;IACH,MAAM,QAAQ,GACZ,SAAS,CAAC,aAAa,IAAI,SAAS,CAAC,YAAY,CAAA;IAEnD,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,GAAW,EAAE,EAAE;QACd,2DAA2D;QAC3D,2DAA2D;QAC3D,mCAAmC;QACnC,cAAc,CAAC,GAAG,CAAC,CAAA;QAEnB,MAAM,CAAC,GAAG,CAAC,CAAA;IACb,CAAC,EACD,CAAC,MAAM,EAAE,cAAc,CAAC,CACzB,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,4DAA4D;QAC5D,8DAA8D;QAC9D,2DAA2D;QAC3D,IACE,UAAU,KAAK,WAAW;YAC1B,WAAW,KAAK,IAAI,EACpB,CAAC;YACD,MAAM,CAAC,WAAW,CAAC,CAAA;QACrB,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,CAAA;IAErC,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAA;QAElC,IACE,CAAC,OAAO;YACR,WAAW,KAAK,IAAI;YACpB,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EACzC,CAAC;YACD,OAAM;QACR,CAAC;QAED,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,CAAA;IAC/C,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAA;IAEjB,MAAM,IAAI,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAA;IAE1C,OAAO,CACL,eACE,SAAS,EAAE,WAAW,CACpB,qBAAqB,EACrB,SAAS,CACV,aAED,4BACc,KAAK,sBACC,WAAW,EAC7B,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,EACtC,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE;oBACtB,MAAM,QAAQ,GAA+B;wBAC3C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI;wBACvB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,QAAQ;wBAC/B,GAAG,EAAE,KAAK,CAAC,IAAI;wBACf,IAAI,EAAE,KAAK,CAAC,KAAK;qBAClB,CAAA;oBAED,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;oBAEtC,IAAI,OAAO,EAAE,CAAC;wBACZ,uDAAuD;wBACvD,sDAAsD;wBACtD,yCAAyC;wBACzC,QAAQ,CAAC,cAAc,EAAE,CAAA;wBAEzB,OAAO,EAAE,CAAA;wBAET,OAAM;oBACR,CAAC;oBAED,yDAAyD;oBACzD,wDAAwD;oBACxD,6BAA6B;oBAC7B,IACE,CAAC,QAAQ,CAAC,GAAG,KAAK,OAAO;wBACvB,QAAQ,CAAC,GAAG,KAAK,GAAG,CAAC;wBACvB,WAAW,KAAK,IAAI,EACpB,CAAC;wBACD,QAAQ,CAAC,cAAc,EAAE,CAAA;wBAEzB,MAAM,CAAC,WAAW,CAAC,CAAA;oBACrB,CAAC;gBACH,CAAC,EACD,GAAG,EAAE,UAAU,EACf,IAAI,EAAC,SAAS,YAEb,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CACxB,KAAC,UAAU,IACT,EAAE,EAAE,GAAG,MAAM,QAAQ,KAAK,EAAE,EAC5B,UAAU,EAAE,QAAQ,KAAK,GAAG,CAAC,GAAG,EAEhC,QAAQ,EAAE,SAAS,EACnB,OAAO,EAAE,GAAG,MAAM,UAAU,KAAK,EAAE,EACnC,aAAa,EAAE,KAAK,CAAC,QAAQ,EAC7B,WAAW,EAAE,WAAW,EACxB,iBAAiB,EAAE,SAAS,CAAC,QAAQ,EACrC,GAAG,EAAE,GAAG;oBACR,qDAAqD;oBACrD,uDAAuD;oBACvD,qDAAqD;oBACrD,iCAAiC;oBACjC,QAAQ,EAAE,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,EACxC,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE;wBAC7B,IAAI,OAAO,EAAE,CAAC;4BACZ,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;wBACvC,CAAC;6BAAM,CAAC;4BACN,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;wBACjC,CAAC;oBACH,CAAC,IAlBI,GAAG,CAAC,GAAG,CAmBZ,CACH,CAAC,GACE,EAEL,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CACxB,iCACmB,GAAG,MAAM,QAAQ,KAAK,EAAE,EACzC,MAAM,EAAE,QAAQ,KAAK,GAAG,CAAC,GAAG,EAC5B,EAAE,EAAE,GAAG,MAAM,UAAU,KAAK,EAAE,EAE9B,IAAI,EAAC,UAAU,YAEd,GAAG,CAAC,OAAO,IAHP,GAAG,CAAC,GAAG,CAIR,CACP,CAAC,IACE,CACP,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"PanelTabs.js","sourceRoot":"","sources":["../../src/Tabs/PanelTabs.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RoutedTabs.d.ts","sourceRoot":"","sources":["../../src/Tabs/RoutedTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG/C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,GAAI,sDAMxB,aAAa,KAAG,SAyBlB,CAAA"}
1
+ {"version":3,"file":"RoutedTabs.d.ts","sourceRoot":"","sources":["../../src/Tabs/RoutedTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAI/C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,GAAI,sDAMxB,aAAa,KAAG,SA8BlB,CAAA"}
@@ -3,6 +3,7 @@ import { getNavItemKey, resolveActiveKey, } from "../Nav/navItems.js";
3
3
  import { toClassName } from "../toClassName.js";
4
4
  import { TabLink } from "./TabLink.js";
5
5
  import { toTabListClass } from "./tabStyles.js";
6
+ import { useRevealCurrentTab } from "./useRevealCurrentTab.js";
6
7
  /**
7
8
  * The routed bar: a named `<nav>` of real links wearing the tab
8
9
  * paint.
@@ -20,7 +21,8 @@ import { toTabListClass } from "./tabStyles.js";
20
21
  */
21
22
  export const RoutedTabs = ({ activeHref, className, label, orientation = "horizontal", tabs, }) => {
22
23
  const activeKey = resolveActiveKey(tabs, activeHref);
23
- return (_jsx("nav", { "aria-label": label, className: toClassName(toTabListClass(orientation), className), children: tabs.map((tab) => {
24
+ const containerRef = useRevealCurrentTab(activeKey, orientation);
25
+ return (_jsx("nav", { ref: containerRef, "aria-label": label, className: toClassName(toTabListClass(orientation), className), children: tabs.map((tab) => {
24
26
  const key = getNavItemKey(tab);
25
27
  return (_jsx(TabLink, { isCurrent: key === activeKey, item: tab, orientation: orientation }, key));
26
28
  }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"RoutedTabs.js","sourceRoot":"","sources":["../../src/Tabs/RoutedTabs.tsx"],"names":[],"mappings":";AAEA,OAAO,EACL,aAAa,EACb,gBAAgB,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAe,CAAA;AAEvC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,UAAU,EACV,SAAS,EACT,KAAK,EACL,WAAW,GAAG,YAAY,EAC1B,IAAI,GACU,EAAa,EAAE;IAC7B,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IAEpD,OAAO,CACL,4BACc,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,cAAc,CAAC,WAAW,CAAC,EAC3B,SAAS,CACV,YAEA,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAChB,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;YAE9B,OAAO,CACL,KAAC,OAAO,IACN,SAAS,EAAE,GAAG,KAAK,SAAS,EAC5B,IAAI,EAAE,GAAG,EAET,WAAW,EAAE,WAAW,IADnB,GAAG,CAER,CACH,CAAA;QACH,CAAC,CAAC,GACE,CACP,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"RoutedTabs.js","sourceRoot":"","sources":["../../src/Tabs/RoutedTabs.tsx"],"names":[],"mappings":";AAEA,OAAO,EACL,aAAa,EACb,gBAAgB,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAe,CAAA;AAEvC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE9D;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,UAAU,EACV,SAAS,EACT,KAAK,EACL,WAAW,GAAG,YAAY,EAC1B,IAAI,GACU,EAAa,EAAE;IAC7B,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACpD,MAAM,YAAY,GAAG,mBAAmB,CACtC,SAAS,EACT,WAAW,CACZ,CAAA;IAED,OAAO,CACL,cACE,GAAG,EAAE,YAAY,gBACL,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,cAAc,CAAC,WAAW,CAAC,EAC3B,SAAS,CACV,YAEA,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAChB,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;YAE9B,OAAO,CACL,KAAC,OAAO,IACN,SAAS,EAAE,GAAG,KAAK,SAAS,EAC5B,IAAI,EAAE,GAAG,EAET,WAAW,EAAE,WAAW,IADnB,GAAG,CAER,CACH,CAAA;QACH,CAAC,CAAC,GACE,CACP,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { TabsOrientation } from "./tabItems.ts";
2
+ /** Reveal within the tab bar only; never scroll an ancestor or move focus. */
3
+ export declare function useRevealCurrentTab(currentKey: string | null, orientation: TabsOrientation): import("react").RefObject<HTMLElement | null>;
4
+ //# sourceMappingURL=useRevealCurrentTab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRevealCurrentTab.d.ts","sourceRoot":"","sources":["../../src/Tabs/useRevealCurrentTab.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,8EAA8E;AAC9E,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,WAAW,EAAE,eAAe,iDAsF7B"}
@@ -0,0 +1,85 @@
1
+ import { useLayoutEffect, useRef } from "react";
2
+ /** Reveal within the tab bar only; never scroll an ancestor or move focus. */
3
+ export function useRevealCurrentTab(currentKey, orientation) {
4
+ const containerRef = useRef(null);
5
+ useLayoutEffect(() => {
6
+ const container = containerRef.current;
7
+ if (!container || currentKey === null)
8
+ return;
9
+ let isActive = true;
10
+ const reveal = () => {
11
+ if (!isActive)
12
+ return;
13
+ // In manual panel mode, the focused tab can differ from the selected tab.
14
+ const focused = container.ownerDocument.activeElement;
15
+ const current = focused instanceof HTMLElement &&
16
+ container.contains(focused) &&
17
+ focused.matches('a, [role="tab"]')
18
+ ? focused
19
+ : container.querySelector('[aria-current="page"], [aria-selected="true"]');
20
+ if (!current)
21
+ return;
22
+ const viewport = container.getBoundingClientRect();
23
+ const target = current.getBoundingClientRect();
24
+ const start = orientation === "horizontal"
25
+ ? target.left
26
+ : target.top;
27
+ const end = orientation === "horizontal"
28
+ ? target.right
29
+ : target.bottom;
30
+ const viewStart = orientation === "horizontal"
31
+ ? viewport.left
32
+ : viewport.top;
33
+ const viewEnd = orientation === "horizontal"
34
+ ? viewport.right
35
+ : viewport.bottom;
36
+ const hasOversize = end - start > viewEnd - viewStart;
37
+ const isRtl = orientation === "horizontal" &&
38
+ getComputedStyle(container).direction === "rtl";
39
+ const delta = hasOversize
40
+ ? isRtl
41
+ ? end - viewEnd
42
+ : start - viewStart
43
+ : start < viewStart
44
+ ? start - viewStart
45
+ : end > viewEnd
46
+ ? end - viewEnd
47
+ : 0;
48
+ if (orientation === "horizontal")
49
+ container.scrollLeft += delta;
50
+ else
51
+ container.scrollTop += delta;
52
+ };
53
+ const frame = { id: 0 };
54
+ const scheduleReveal = () => {
55
+ cancelAnimationFrame(frame.id);
56
+ frame.id = requestAnimationFrame(reveal);
57
+ };
58
+ const sizes = new ResizeObserver(scheduleReveal);
59
+ const observeItems = () => {
60
+ sizes.disconnect();
61
+ sizes.observe(container);
62
+ for (const child of container.children)
63
+ sizes.observe(child);
64
+ reveal();
65
+ };
66
+ const contents = new MutationObserver(observeItems);
67
+ contents.observe(container, {
68
+ childList: true,
69
+ subtree: true,
70
+ characterData: true,
71
+ });
72
+ container.addEventListener("focusin", reveal);
73
+ observeItems();
74
+ void container.ownerDocument.fonts.ready.then(reveal);
75
+ return () => {
76
+ isActive = false;
77
+ cancelAnimationFrame(frame.id);
78
+ sizes.disconnect();
79
+ contents.disconnect();
80
+ container.removeEventListener("focusin", reveal);
81
+ };
82
+ }, [currentKey, orientation]);
83
+ return containerRef;
84
+ }
85
+ //# sourceMappingURL=useRevealCurrentTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRevealCurrentTab.js","sourceRoot":"","sources":["../../src/Tabs/useRevealCurrentTab.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcuterie/ui",
3
- "version": "4.18.0",
3
+ "version": "4.18.2",
4
4
  "description": "The Charcuterie components. Tokens for look, @charcuterie/logic for state, ARIA that agents can actually drive.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -54,6 +54,15 @@ differential — the dialog that ignores Escape is then closed by its own button
54
54
 
55
55
  <Canvas of={DialogStories.AllStates} />
56
56
 
57
+ ## Pickers inside a draft
58
+
59
+ An open picker, menu, or popover owns the first Escape or outside press inside a
60
+ modal. Dismissing that child keeps the enclosing dialog and its unsaved fields open.
61
+ After the child closes, Escape dismisses the dialog normally. Selecting a picker
62
+ option also keeps the draft open and returns focus to its trigger.
63
+
64
+ <Canvas of={DialogStories.PickerDraft} />
65
+
57
66
  ## Stacking, and the one shared scrim
58
67
 
59
68
  A lone dialog needs no setup. Stack one over another and an `OverlayStackProvider` at the
@@ -2,12 +2,13 @@ import { useVisibility } from "@charcuterie/logic"
2
2
  import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
3
3
  import type { Meta, StoryObj } from "@storybook/react"
4
4
  import type { ReactNode } from "react"
5
- import { useRef } from "react"
6
-
5
+ import { useRef, useState } from "react"
7
6
  import { toStoryChoice } from "../argTypes.storyHelpers.ts"
8
7
  import { Button } from "../Button/Button.tsx"
9
8
  import { StoryRow } from "../board.storyHelpers.tsx"
9
+ import { Field } from "../Field/Field.tsx"
10
10
  import { OverlayStackProvider } from "../Overlay/OverlayStack.tsx"
11
+ import { Picker } from "../Picker/Picker.tsx"
11
12
  import type { DialogSize } from "./Dialog.tsx"
12
13
  import { Dialog } from "./Dialog.tsx"
13
14
 
@@ -445,3 +446,51 @@ export const Interactive: Story = {
445
446
  />
446
447
  ),
447
448
  }
449
+
450
+ const PickerDraftDemo = () => {
451
+ const [isVisible, setIsVisible] = useState(true)
452
+ const [draft, setDraft] = useState(
453
+ "Keep the unsaved draft",
454
+ )
455
+ const [value, setValue] = useState("")
456
+ return (
457
+ <div className="min-h-screen">
458
+ <Button onClick={() => setIsVisible(true)}>
459
+ Open draft
460
+ </Button>
461
+ <Dialog
462
+ heading="Edit a draft"
463
+ isVisible={isVisible}
464
+ onClose={() => setIsVisible(false)}
465
+ >
466
+ <Field label="Draft name">
467
+ <input
468
+ value={draft}
469
+ onChange={(event) =>
470
+ setDraft(event.currentTarget.value)
471
+ }
472
+ />
473
+ </Field>
474
+ <Picker
475
+ label="Draft parent"
476
+ value={value}
477
+ options={[
478
+ { value: "", label: "No parent" },
479
+ { value: "other", label: "Another draft" },
480
+ ]}
481
+ onChange={setValue}
482
+ />
483
+ </Dialog>
484
+ </div>
485
+ )
486
+ }
487
+
488
+ /** Escape dismisses the picker's portal before its enclosing dialog. */
489
+ export const PickerDraft: Story = {
490
+ args: {
491
+ heading: "Edit a draft",
492
+ isVisible: true,
493
+ onClose: () => {},
494
+ },
495
+ render: () => <PickerDraftDemo />,
496
+ }
@@ -11,6 +11,7 @@ import {
11
11
  import * as stories from "./Dialog.stories.tsx"
12
12
 
13
13
  const {
14
+ PickerDraft,
14
15
  AllStates,
15
16
  AllVariants,
16
17
  Playground,
@@ -368,3 +369,61 @@ test("initialFocus puts the caret in the field, not on Close", async () => {
368
369
 
369
370
  await expect(field).toHaveValue("Sharpen the chisels")
370
371
  })
372
+
373
+ test("a picker cancels its selection without closing the enclosing draft", async () => {
374
+ const { body } = await mountStory(PickerDraft)
375
+ const input = expectAgentDrivable(body, {
376
+ role: "textbox",
377
+ name: "Draft name",
378
+ })
379
+ await userEvent.clear(input)
380
+ await userEvent.type(input, "An unsaved change")
381
+ const trigger = body.getByRole("button", {
382
+ name: "Draft parent: No parent",
383
+ })
384
+ await userEvent.click(trigger)
385
+ const option = body.getByRole("option", {
386
+ name: "No parent",
387
+ })
388
+ await expect(option).toHaveFocus()
389
+ await userEvent.keyboard("{Escape}")
390
+ await expect(
391
+ body.getByRole("dialog", { name: "Edit a draft" }),
392
+ ).toBeVisible()
393
+ await expect(input).toHaveValue("An unsaved change")
394
+ await expect(trigger).toHaveFocus()
395
+ await expect(
396
+ body.queryByRole("listbox"),
397
+ ).not.toBeInTheDocument()
398
+ // A press elsewhere in the draft also dismisses only the picker.
399
+ await userEvent.click(trigger)
400
+ await expect(
401
+ body.getByRole("option", { name: "No parent" }),
402
+ ).toHaveFocus()
403
+ await userEvent.click(input)
404
+ await expect(
405
+ body.queryByRole("listbox"),
406
+ ).not.toBeInTheDocument()
407
+ await expect(
408
+ body.getByRole("dialog", { name: "Edit a draft" }),
409
+ ).toBeVisible()
410
+ await expect(input).toHaveValue("An unsaved change")
411
+ await userEvent.click(trigger)
412
+ await userEvent.click(
413
+ body.getByRole("option", { name: "Another draft" }),
414
+ )
415
+ await expect(
416
+ body.getByRole("dialog", { name: "Edit a draft" }),
417
+ ).toBeVisible()
418
+ await expect(
419
+ body.getByRole("button", {
420
+ name: "Draft parent: Another draft",
421
+ }),
422
+ ).toHaveFocus()
423
+ await userEvent.keyboard("{Escape}")
424
+ await waitFor(() =>
425
+ expect(
426
+ body.queryByRole("dialog", { name: "Edit a draft" }),
427
+ ).not.toBeInTheDocument(),
428
+ )
429
+ })
@@ -0,0 +1,42 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useMemo,
5
+ useState,
6
+ } from "react"
7
+
8
+ /** Anchored children share their enclosing modal's dismissal boundary. */
9
+ type AnchoredOverlayScope = {
10
+ topId: string | null
11
+ register: (id: string) => void
12
+ unregister: (id: string) => void
13
+ }
14
+ export const AnchoredOverlayScopeContext =
15
+ createContext<AnchoredOverlayScope | null>(null)
16
+ export function useAnchoredOverlayScope(): AnchoredOverlayScope {
17
+ const [ids, setIds] = useState<string[]>([])
18
+ const register = useCallback(
19
+ (id: string) =>
20
+ setIds((previous) =>
21
+ previous.includes(id)
22
+ ? previous
23
+ : [...previous, id],
24
+ ),
25
+ [],
26
+ )
27
+ const unregister = useCallback(
28
+ (id: string) =>
29
+ setIds((previous) =>
30
+ previous.filter((value) => value !== id),
31
+ ),
32
+ [],
33
+ )
34
+ return useMemo(
35
+ () => ({
36
+ topId: ids.at(-1) ?? null,
37
+ register,
38
+ unregister,
39
+ }),
40
+ [ids, register, unregister],
41
+ )
42
+ }
@@ -13,6 +13,10 @@ import {
13
13
  import type { ReactNode, RefObject } from "react"
14
14
  import { useCallback, useEffect } from "react"
15
15
 
16
+ import {
17
+ AnchoredOverlayScopeContext,
18
+ useAnchoredOverlayScope,
19
+ } from "./AnchoredOverlayScope.ts"
16
20
  import { lockScrollBehind } from "./lockScrollBehind.ts"
17
21
  import { useOverlayStack } from "./OverlayStack.tsx"
18
22
  import { SharedBackdrop } from "./SharedBackdrop.tsx"
@@ -100,6 +104,8 @@ export const OverlayPanel = ({
100
104
  }, [onCloseRef])
101
105
 
102
106
  const isTop = !stack.isProvided || stack.topId === id
107
+ const anchors = useAnchoredOverlayScope()
108
+ const hasOpenAnchor = anchors.topId !== null
103
109
 
104
110
  const { context, refs } = useFloating({
105
111
  open: isVisible,
@@ -112,13 +118,14 @@ export const OverlayPanel = ({
112
118
 
113
119
  const { getFloatingProps } = useInteractions([
114
120
  useDismiss(context, {
115
- escapeKey: isDismissable && isTop,
121
+ escapeKey: isDismissable && isTop && !hasOpenAnchor,
116
122
  // The top's outside press is the backdrop click. A per-panel
117
123
  // `outsidePress` on a *lower* modal would close it too, so it is
118
124
  // scoped to the top — which is also why the scrim needs no click
119
125
  // handler of its own.
120
126
  outsidePress: (event) => {
121
- if (!isDismissable || !isTop) return false
127
+ if (!isDismissable || !isTop || hasOpenAnchor)
128
+ return false
122
129
  /*
123
130
  * ⚠️ A press on a toast is NOT an outside press.
124
131
  *
@@ -262,7 +269,11 @@ export const OverlayPanel = ({
262
269
  ref={refs.setFloating}
263
270
  role={role}
264
271
  >
265
- {children}
272
+ <AnchoredOverlayScopeContext.Provider
273
+ value={anchors}
274
+ >
275
+ {children}
276
+ </AnchoredOverlayScopeContext.Provider>
266
277
  </div>
267
278
  </FloatingFocusManager>
268
279
  </div>
@@ -18,7 +18,12 @@ import {
18
18
  useRole,
19
19
  } from "@floating-ui/react"
20
20
  import type { ReactElement, RefObject } from "react"
21
- import { createElement, useLayoutEffect } from "react"
21
+ import {
22
+ createElement,
23
+ useContext,
24
+ useLayoutEffect,
25
+ } from "react"
26
+ import { AnchoredOverlayScopeContext } from "./AnchoredOverlayScope.ts"
22
27
 
23
28
  /**
24
29
  * The floating-ui block every anchored overlay — `Popover`, `Menu`,
@@ -178,6 +183,18 @@ export const useAnchoredOverlay = ({
178
183
  role,
179
184
  trigger,
180
185
  }: UseAnchoredOverlayOptions) => {
186
+ const scope = useContext(AnchoredOverlayScopeContext)
187
+ const anchorId = useUniqueId()
188
+ const register = scope?.register
189
+ const unregister = scope?.unregister
190
+ useLayoutEffect(() => {
191
+ if (!isVisible || !register || !unregister) return
192
+ register(anchorId)
193
+ return () => unregister(anchorId)
194
+ }, [isVisible, anchorId, register, unregister])
195
+ const isTopAnchor =
196
+ scope === null || scope.topId === anchorId
197
+
181
198
  const middleware = [
182
199
  offset(offsetValue),
183
200
  flip(),
@@ -249,8 +266,9 @@ export const useAnchoredOverlay = ({
249
266
  const { getFloatingProps, getReferenceProps } =
250
267
  useInteractions([
251
268
  useDismiss(context, {
252
- escapeKey: isEscapeDismissable,
253
- outsidePress: isOutsidePressDismissable,
269
+ escapeKey: isEscapeDismissable && isTopAnchor,
270
+ outsidePress:
271
+ isOutsidePressDismissable && isTopAnchor,
254
272
  }),
255
273
  useRole(context, { role }),
256
274
  ])
@@ -6,12 +6,12 @@ import {
6
6
  } from "@charcuterie/logic"
7
7
  import type { ReactNode } from "react"
8
8
  import { useCallback, useEffect, useRef } from "react"
9
-
10
9
  import { toClassName } from "../toClassName.ts"
11
10
  import type { TabsPanelProps } from "./Tabs.tsx"
12
11
  import { TabTrigger } from "./TabTrigger.tsx"
13
12
  import type { TabsOrientation } from "./tabItems.ts"
14
13
  import { toTabListClass } from "./tabStyles.ts"
14
+ import { useRevealCurrentTab } from "./useRevealCurrentTab.ts"
15
15
 
16
16
  const ORIENTATION_KEYS: Record<
17
17
  TabsOrientation,
@@ -128,6 +128,10 @@ export const PanelTabs = ({
128
128
  */
129
129
  const shownKey =
130
130
  selection.selectedValue ?? selection.pendingValue
131
+ const revealRef = useRevealCurrentTab(
132
+ shownKey,
133
+ orientation,
134
+ )
131
135
 
132
136
  const selectTab = useCallback(
133
137
  (key: string) => {
@@ -214,7 +218,10 @@ export const PanelTabs = ({
214
218
  select(activeValue)
215
219
  }
216
220
  }}
217
- ref={tablistRef}
221
+ ref={(element) => {
222
+ tablistRef.current = element
223
+ revealRef.current = element
224
+ }}
218
225
  role="tablist"
219
226
  >
220
227
  {tabs.map((tab, index) => (
@@ -8,6 +8,7 @@ import { toClassName } from "../toClassName.ts"
8
8
  import { TabLink } from "./TabLink.tsx"
9
9
  import type { TabsLinkProps } from "./Tabs.tsx"
10
10
  import { toTabListClass } from "./tabStyles.ts"
11
+ import { useRevealCurrentTab } from "./useRevealCurrentTab.ts"
11
12
 
12
13
  /**
13
14
  * The routed bar: a named `<nav>` of real links wearing the tab
@@ -32,9 +33,14 @@ export const RoutedTabs = ({
32
33
  tabs,
33
34
  }: TabsLinkProps): ReactNode => {
34
35
  const activeKey = resolveActiveKey(tabs, activeHref)
36
+ const containerRef = useRevealCurrentTab(
37
+ activeKey,
38
+ orientation,
39
+ )
35
40
 
36
41
  return (
37
42
  <nav
43
+ ref={containerRef}
38
44
  aria-label={label}
39
45
  className={toClassName(
40
46
  toTabListClass(orientation),
package/src/Tabs/Tabs.mdx CHANGED
@@ -194,3 +194,11 @@ bar below and the underline stays put until Enter: focus has moved to a tab that
194
194
  selected, which is a state a three-kind model cannot represent at all.
195
195
 
196
196
  <Canvas of={TabsStories.Manual} />
197
+
198
+
199
+ ## Current tabs in a narrow bar
200
+
201
+ An overflowing bar keeps its current tab in view on first render, route changes,
202
+ and reflow. It scrolls only itself, without moving focus or the surrounding page.
203
+ Manual panel activation keeps the focused tab visible even while another panel
204
+ remains selected. A reader can still scroll to other tabs.
@@ -1,13 +1,16 @@
1
1
  import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
2
2
  import type { Meta, StoryObj } from "@storybook/react"
3
3
 
4
+ import { useState } from "react"
4
5
  import { toStoryChoice } from "../argTypes.storyHelpers.ts"
5
6
  import { Badge } from "../Badge/Badge.tsx"
7
+ import { Button } from "../Button/Button.tsx"
6
8
  import {
7
9
  ContainerBoard,
8
10
  StoryCell,
9
11
  StoryGrid,
10
12
  } from "../board.storyHelpers.tsx"
13
+ import { Field } from "../Field/Field.tsx"
11
14
  import { ProgressBar } from "../ProgressBar/ProgressBar.tsx"
12
15
  import { Tabs } from "./Tabs.tsx"
13
16
  import type { TabItem, TabLinkItem } from "./tabItems.ts"
@@ -319,3 +322,73 @@ export const RoutedAllStates: Story = {
319
322
  />
320
323
  ),
321
324
  }
325
+
326
+ const OVERFLOW_ROUTES: TabLinkItem[] = [
327
+ { href: "/sections/activity", label: "Recent activity" },
328
+ { href: "/sections/files", label: "Saved files" },
329
+ { href: "/sections/builds", label: "Prepared builds" },
330
+ { href: "/sections/reports", label: "Reports" },
331
+ ]
332
+ export const CurrentTabVisible: Story = {
333
+ args: { label: "Sections", tabs: BAY_TABS },
334
+ render: () => {
335
+ const [href, setHref] = useState("/sections/reports")
336
+ const [inlineSize, setInlineSize] = useState(240)
337
+ return (
338
+ <div className="flex flex-col gap-4">
339
+ <Field label="Available width">
340
+ <input
341
+ type="number"
342
+ min={100}
343
+ max={500}
344
+ value={inlineSize}
345
+ onChange={(event) =>
346
+ setInlineSize(
347
+ Number(event.currentTarget.value),
348
+ )
349
+ }
350
+ />
351
+ </Field>
352
+ <div className="flex flex-wrap gap-2">
353
+ <Button
354
+ onClick={() => setHref("/sections/activity")}
355
+ >
356
+ Show activity
357
+ </Button>
358
+ <Button
359
+ onClick={() => setHref("/sections/reports")}
360
+ >
361
+ Show reports
362
+ </Button>
363
+ </div>
364
+ <div style={{ inlineSize, maxInlineSize: "100%" }}>
365
+ <Tabs
366
+ label="Current routed section"
367
+ activeHref={href}
368
+ tabs={OVERFLOW_ROUTES}
369
+ />
370
+ </div>
371
+ <div
372
+ dir="rtl"
373
+ style={{ inlineSize: 240, maxInlineSize: "100%" }}
374
+ >
375
+ <Tabs
376
+ label="Current RTL section"
377
+ activeHref="/sections/reports"
378
+ tabs={OVERFLOW_ROUTES}
379
+ />
380
+ </div>
381
+ <div
382
+ style={{ inlineSize: 150, maxInlineSize: "100%" }}
383
+ >
384
+ <Tabs
385
+ label="Current manual panel"
386
+ activation="manual"
387
+ activeKey="disc"
388
+ tabs={BAY_TABS}
389
+ />
390
+ </div>
391
+ </div>
392
+ )
393
+ },
394
+ }
@@ -16,6 +16,7 @@ const {
16
16
  Routed,
17
17
  RoutedAllStates,
18
18
  RoutedAllVariants,
19
+ CurrentTabVisible,
19
20
  } = composeStories(stories)
20
21
 
21
22
  test("selecting a tab wires the panel back to it", async () => {
@@ -450,3 +451,87 @@ test("a routed tab and a panel tab are painted identically", async () => {
450
451
  )
451
452
  }
452
453
  })
454
+
455
+ test("current routed and panel tabs remain visible without moving focus", async () => {
456
+ const { canvas } = await mountStory(CurrentTabVisible)
457
+ const assertVisible = async (
458
+ bar: HTMLElement,
459
+ selector: string,
460
+ ) => {
461
+ await waitFor(() => {
462
+ const selected =
463
+ bar.querySelector<HTMLElement>(selector)
464
+ expect(selected).not.toBeNull()
465
+ const item = selected?.getBoundingClientRect()
466
+ const bounds = bar.getBoundingClientRect()
467
+ expect(
468
+ item?.left ?? -Infinity,
469
+ ).toBeGreaterThanOrEqual(bounds.left - 1)
470
+ expect(item?.right ?? Infinity).toBeLessThanOrEqual(
471
+ bounds.right + 1,
472
+ )
473
+ })
474
+ }
475
+ const routed = expectAgentDrivable(canvas, {
476
+ role: "navigation",
477
+ name: "Current routed section",
478
+ })
479
+ const rtl = expectAgentDrivable(canvas, {
480
+ role: "navigation",
481
+ name: "Current RTL section",
482
+ })
483
+ const panel = expectAgentDrivable(canvas, {
484
+ role: "tablist",
485
+ name: "Current manual panel",
486
+ })
487
+ await assertVisible(routed, '[aria-current="page"]')
488
+ await assertVisible(rtl, '[aria-current="page"]')
489
+ await assertVisible(panel, '[aria-selected="true"]')
490
+ const width = expectAgentDrivable(canvas, {
491
+ role: "spinbutton",
492
+ name: "Available width",
493
+ })
494
+ await userEvent.clear(width)
495
+ await userEvent.type(width, "160")
496
+ await assertVisible(routed, '[aria-current="page"]')
497
+ await expect(width).toHaveFocus()
498
+ const activity = expectAgentDrivable(canvas, {
499
+ role: "button",
500
+ name: "Show activity",
501
+ })
502
+ await userEvent.click(activity)
503
+ await assertVisible(routed, '[aria-current="page"]')
504
+ await expect(activity).toHaveFocus()
505
+ const reports = expectAgentDrivable(canvas, {
506
+ role: "button",
507
+ name: "Show reports",
508
+ })
509
+ await userEvent.click(reports)
510
+ await assertVisible(routed, '[aria-current="page"]')
511
+ await expect(reports).toHaveFocus()
512
+ await userEvent.clear(width)
513
+ await userEvent.type(width, "80")
514
+ await userEvent.click(activity)
515
+ await waitFor(() => {
516
+ const bounds = routed.getBoundingClientRect()
517
+ const selected = routed
518
+ .querySelector<HTMLElement>('[aria-current="page"]')
519
+ ?.getBoundingClientRect()
520
+ expect(
521
+ selected?.left ?? -Infinity,
522
+ ).toBeGreaterThanOrEqual(bounds.left - 1)
523
+ expect(Math.abs(routed.scrollLeft)).toBeLessThanOrEqual(
524
+ 1,
525
+ )
526
+ })
527
+ const disc = panel.querySelector<HTMLElement>(
528
+ '[aria-selected="true"]',
529
+ )
530
+ disc?.focus()
531
+ await userEvent.keyboard("{ArrowLeft}")
532
+ await assertVisible(panel, ":focus")
533
+ await expect(disc).toHaveAttribute(
534
+ "aria-selected",
535
+ "true",
536
+ )
537
+ })
@@ -0,0 +1,93 @@
1
+ import { useLayoutEffect, useRef } from "react"
2
+ import type { TabsOrientation } from "./tabItems.ts"
3
+
4
+ /** Reveal within the tab bar only; never scroll an ancestor or move focus. */
5
+ export function useRevealCurrentTab(
6
+ currentKey: string | null,
7
+ orientation: TabsOrientation,
8
+ ) {
9
+ const containerRef = useRef<HTMLElement>(null)
10
+ useLayoutEffect(() => {
11
+ const container = containerRef.current
12
+ if (!container || currentKey === null) return
13
+ let isActive = true
14
+ const reveal = () => {
15
+ if (!isActive) return
16
+ // In manual panel mode, the focused tab can differ from the selected tab.
17
+ const focused = container.ownerDocument.activeElement
18
+ const current =
19
+ focused instanceof HTMLElement &&
20
+ container.contains(focused) &&
21
+ focused.matches('a, [role="tab"]')
22
+ ? focused
23
+ : container.querySelector<HTMLElement>(
24
+ '[aria-current="page"], [aria-selected="true"]',
25
+ )
26
+ if (!current) return
27
+ const viewport = container.getBoundingClientRect()
28
+ const target = current.getBoundingClientRect()
29
+ const start =
30
+ orientation === "horizontal"
31
+ ? target.left
32
+ : target.top
33
+ const end =
34
+ orientation === "horizontal"
35
+ ? target.right
36
+ : target.bottom
37
+ const viewStart =
38
+ orientation === "horizontal"
39
+ ? viewport.left
40
+ : viewport.top
41
+ const viewEnd =
42
+ orientation === "horizontal"
43
+ ? viewport.right
44
+ : viewport.bottom
45
+ const hasOversize = end - start > viewEnd - viewStart
46
+ const isRtl =
47
+ orientation === "horizontal" &&
48
+ getComputedStyle(container).direction === "rtl"
49
+ const delta = hasOversize
50
+ ? isRtl
51
+ ? end - viewEnd
52
+ : start - viewStart
53
+ : start < viewStart
54
+ ? start - viewStart
55
+ : end > viewEnd
56
+ ? end - viewEnd
57
+ : 0
58
+ if (orientation === "horizontal")
59
+ container.scrollLeft += delta
60
+ else container.scrollTop += delta
61
+ }
62
+ const frame = { id: 0 }
63
+ const scheduleReveal = () => {
64
+ cancelAnimationFrame(frame.id)
65
+ frame.id = requestAnimationFrame(reveal)
66
+ }
67
+ const sizes = new ResizeObserver(scheduleReveal)
68
+ const observeItems = () => {
69
+ sizes.disconnect()
70
+ sizes.observe(container)
71
+ for (const child of container.children)
72
+ sizes.observe(child)
73
+ reveal()
74
+ }
75
+ const contents = new MutationObserver(observeItems)
76
+ contents.observe(container, {
77
+ childList: true,
78
+ subtree: true,
79
+ characterData: true,
80
+ })
81
+ container.addEventListener("focusin", reveal)
82
+ observeItems()
83
+ void container.ownerDocument.fonts.ready.then(reveal)
84
+ return () => {
85
+ isActive = false
86
+ cancelAnimationFrame(frame.id)
87
+ sizes.disconnect()
88
+ contents.disconnect()
89
+ container.removeEventListener("focusin", reveal)
90
+ }
91
+ }, [currentKey, orientation])
92
+ return containerRef
93
+ }