cursedbelt 5.15.0 → 5.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/components/DropdownMenu.d.ts +1 -1
- package/dist/react/components/DropdownMenu.d.ts.map +1 -1
- package/dist/react/components/DropdownMenu.js +25 -1
- package/dist/react/components/DropdownMenu.js.map +1 -1
- package/dist/react/components/Popover.d.ts.map +1 -1
- package/dist/react/components/Popover.js +4 -1
- package/dist/react/components/Popover.js.map +1 -1
- package/dist/react/hooks/useOutsidePress.d.ts +13 -1
- package/dist/react/hooks/useOutsidePress.d.ts.map +1 -1
- package/dist/react/hooks/useOutsidePress.js +41 -15
- package/dist/react/hooks/useOutsidePress.js.map +1 -1
- package/dist/react/icons.d.ts +13 -3
- package/dist/react/icons.d.ts.map +1 -1
- package/dist/react/icons.js +9 -3
- package/dist/react/icons.js.map +1 -1
- package/package.json +2 -1
- package/src/react/components/DropdownMenu.spec.tsx +39 -0
- package/src/react/components/DropdownMenu.tsx +32 -3
- package/src/react/components/Popover.spec.tsx +38 -0
- package/src/react/components/Popover.tsx +4 -0
- package/src/react/hooks/useOutsidePress.ts +52 -15
- package/src/react/icons.spec.ts +19 -0
- package/src/react/icons.tsx +14 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../../src/react/components/DropdownMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../../src/react/components/DropdownMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,OAAO,CAAC;AAWtE,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,gEAAgE;AAChE,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,IAAI,CAAC;IACd;qFACiF;IACjF,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;AAExE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,iFAAiF;IACjF,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,8EAA8E;IAC9E,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,KAAK,EACL,KAAe,EACf,IAAe,EACf,SAAS,EACT,IAAI,EACJ,YAAY,GACb,EAAE,iBAAiB,+BAiDnB"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as RDrop from '@radix-ui/react-dropdown-menu';
|
|
3
3
|
import { Check, ChevronRight } from 'lucide-react';
|
|
4
|
+
import { useCallback, useRef, useState } from 'react';
|
|
5
|
+
import { useOutsidePress } from '../hooks/useOutsidePress.js';
|
|
4
6
|
import { cn } from '../lib/cn.js';
|
|
5
7
|
import { useFloatingContainer } from '../lib/floatingContainer.js';
|
|
6
8
|
import { FLOATING_ANIMATION, FLOATING_COLLISION_PADDING, FLOATING_CONTENT_BASE, FLOATING_ITEM_BASE, } from '../lib/floatingStyles.js';
|
|
@@ -15,7 +17,29 @@ import { FLOATING_ANIMATION, FLOATING_COLLISION_PADDING, FLOATING_CONTENT_BASE,
|
|
|
15
17
|
*/
|
|
16
18
|
export function DropdownMenu({ trigger, items, align = 'start', side = 'bottom', className, open, onOpenChange, }) {
|
|
17
19
|
const container = useFloatingContainer();
|
|
18
|
-
|
|
20
|
+
// Always controlled underneath, so the outside-press fallback can close it too.
|
|
21
|
+
const [ownOpen, setOwnOpen] = useState(false);
|
|
22
|
+
const isOpen = open ?? ownOpen;
|
|
23
|
+
const setOpen = useCallback((next) => {
|
|
24
|
+
if (open === undefined)
|
|
25
|
+
setOwnOpen(next);
|
|
26
|
+
onOpenChange?.(next);
|
|
27
|
+
}, [open, onOpenChange]);
|
|
28
|
+
/*
|
|
29
|
+
* Radix's menu listens for the outside `pointerdown` in the BUBBLE phase, so a surface that
|
|
30
|
+
* stops its own pointerdown (a drag handle, a canvas) kept the menu open. The belt's one
|
|
31
|
+
* tap-away listener runs in the capture phase; see `useOutsidePress`. The menu is modal, so
|
|
32
|
+
* it decides on the pointerdown (an inert page sends an iPhone no click).
|
|
33
|
+
*/
|
|
34
|
+
const triggerRef = useRef(null);
|
|
35
|
+
const pressInside = useOutsidePress({
|
|
36
|
+
open: isOpen,
|
|
37
|
+
onDismiss: () => setOpen(false),
|
|
38
|
+
inside: [triggerRef],
|
|
39
|
+
escape: false,
|
|
40
|
+
decideOn: 'pointerdown',
|
|
41
|
+
});
|
|
42
|
+
return (_jsxs(RDrop.Root, { open: isOpen, onOpenChange: setOpen, children: [_jsx(RDrop.Trigger, { asChild: true, ref: triggerRef, children: trigger }), _jsx(RDrop.Portal, { container: container, children: _jsxs(RDrop.Content, { align: align, side: side, sideOffset: 4, collisionPadding: FLOATING_COLLISION_PADDING, onClickCapture: pressInside.onClickCapture, onPointerDownCapture: pressInside.onPointerDownCapture, className: cn(FLOATING_CONTENT_BASE, FLOATING_ANIMATION, 'min-w-[8rem] p-1', className), children: [items.map((item) => (_jsx(DropdownItem, { item: item }, item.key))), _jsx(RDrop.Arrow, { className: 'fill-border' })] }) })] }));
|
|
19
43
|
}
|
|
20
44
|
function DropdownItem({ item }) {
|
|
21
45
|
const container = useFloatingContainer();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.js","sourceRoot":"","sources":["../../../src/react/components/DropdownMenu.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"DropdownMenu.js","sourceRoot":"","sources":["../../../src/react/components/DropdownMenu.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../../src/react/components/Popover.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAA0C,MAAM,OAAO,CAAC;AAU/E;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,uFAAuF;IACvF,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAAC;IACvC,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;CACtC;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,YAAY,EACZ,KAAe,EACf,IAAe,EACf,UAAc,EACd,KAAK,EACL,KAAK,EACL,SAAS,EACT,QAAgB,EAChB,KAAa,EACb,MAAa,EACb,SAAgB,EAChB,KAAK,EACL,IAAI,GACL,EAAE,YAAY,+
|
|
1
|
+
{"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../../src/react/components/Popover.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAA0C,MAAM,OAAO,CAAC;AAU/E;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,uFAAuF;IACvF,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAAC;IACvC,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;CACtC;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,YAAY,EACZ,KAAe,EACf,IAAe,EACf,UAAc,EACd,KAAK,EACL,KAAK,EACL,SAAS,EACT,QAAgB,EAChB,KAAa,EACb,MAAa,EACb,SAAgB,EAChB,KAAK,EACL,IAAI,GACL,EAAE,YAAY,+BA2Fd;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEjE"}
|
|
@@ -60,6 +60,9 @@ export function Popover({ trigger, anchor, children, open, onOpenChange, align =
|
|
|
60
60
|
onDismiss: () => setOpen(false),
|
|
61
61
|
inside: [triggerRef, anchorTrigger],
|
|
62
62
|
escape: false,
|
|
63
|
+
// Under `modal` the page is inert and an iPhone sends no click for a tap on it — see
|
|
64
|
+
// `decideOn` in useOutsidePress.
|
|
65
|
+
decideOn: modal ? 'pointerdown' : 'click',
|
|
63
66
|
});
|
|
64
67
|
const content = (_jsxs(RPopover.Content, { align: align, side: side, sideOffset: sideOffset, collisionPadding: FLOATING_COLLISION_PADDING, style: width != null ? { width } : undefined, "aria-label": label, ...(role ? { role } : {}), onOpenAutoFocus: autoFocus ? undefined : (event) => event.preventDefault(), onInteractOutside: (event) => {
|
|
65
68
|
// The anchor's own control decides open/shut with its click — see PopoverPoint.
|
|
@@ -68,7 +71,7 @@ export function Popover({ trigger, anchor, children, open, onOpenChange, align =
|
|
|
68
71
|
},
|
|
69
72
|
// React-tree capture: a press on a nested layer this panel portalled (a Select's
|
|
70
73
|
// list, a sub-menu) is INSIDE even though the DOM puts it elsewhere.
|
|
71
|
-
onClickCapture: pressInside.onClickCapture, className: cn(unstyled
|
|
74
|
+
onClickCapture: pressInside.onClickCapture, onPointerDownCapture: pressInside.onPointerDownCapture, className: cn(unstyled
|
|
72
75
|
? 'z-[var(--z-popover)] outline-none'
|
|
73
76
|
: cn(FLOATING_CONTENT_BASE, 'p-3'), FLOATING_ANIMATION, className), children: [children, showArrow && _jsx(RPopover.Arrow, { className: 'fill-border' })] }));
|
|
74
77
|
return (_jsxs(RPopover.Root, { open: isOpen, onOpenChange: setOpen, modal: modal, children: [virtualRef ? _jsx(RPopover.Anchor, { virtualRef: virtualRef }) : null, trigger !== undefined ? (_jsx(RPopover.Trigger, { asChild: true, ref: triggerRef, children: trigger })) : null, portal ? _jsx(RPopover.Portal, { container: container, children: content }) : content] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.js","sourceRoot":"","sources":["../../../src/react/components/Popover.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Popover.js","sourceRoot":"","sources":["../../../src/react/components/Popover.tsx"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,17 @@ export interface UseOutsidePressOptions {
|
|
|
19
19
|
* whose rows play on tap). Default false.
|
|
20
20
|
*/
|
|
21
21
|
modal?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Decide on the `pointerdown` rather than the `click`. Only for a panel that has made the
|
|
24
|
+
* page under it inert (`Popover`'s `modal`, which sets `pointer-events: none` on body).
|
|
25
|
+
*
|
|
26
|
+
* 🔴 Measured 2026-09-27 in Playwright's iPhone 15 WebKit: with the page inert, a tap
|
|
27
|
+
* outside hits `<html>`, which iOS does not consider clickable, so NO `click` is ever
|
|
28
|
+
* sent — the menu stayed open under the owner's finger, exactly as reported. Deciding on
|
|
29
|
+
* the pointerdown closes it; the `click` that same press may still produce is swallowed,
|
|
30
|
+
* or it would land on whatever the closed menu uncovered (the picture, which pauses).
|
|
31
|
+
*/
|
|
32
|
+
decideOn?: 'click' | 'pointerdown';
|
|
22
33
|
}
|
|
23
34
|
/**
|
|
24
35
|
* Close a panel when the reader presses anywhere else — the ONE implementation of it in the
|
|
@@ -41,8 +52,9 @@ export interface UseOutsidePressOptions {
|
|
|
41
52
|
* · **the toggle is inside**, or pressing an open menu's own button shuts it and reopens
|
|
42
53
|
* it a frame later — the "menu that ignores the second press" desk hit on 2026-09-07.
|
|
43
54
|
*/
|
|
44
|
-
export declare function useOutsidePress({ open, onDismiss, inside, escape: closeOnEscape, modal, }: UseOutsidePressOptions): {
|
|
55
|
+
export declare function useOutsidePress({ open, onDismiss, inside, escape: closeOnEscape, modal, decideOn, }: UseOutsidePressOptions): {
|
|
45
56
|
onClickCapture: () => void;
|
|
57
|
+
onPointerDownCapture: () => void;
|
|
46
58
|
};
|
|
47
59
|
export {};
|
|
48
60
|
//# sourceMappingURL=useOutsidePress.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOutsidePress.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useOutsidePress.ts"],"names":[],"mappings":"AAEA,KAAK,YAAY,GAAG;IAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,sBAAsB;IACrC,+BAA+B;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,+EAA+E;IAC/E,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useOutsidePress.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useOutsidePress.ts"],"names":[],"mappings":"AAEA,KAAK,YAAY,GAAG;IAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,sBAAsB;IACrC,+BAA+B;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,+EAA+E;IAC/E,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;CACpC;AAUD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,SAAS,EACT,MAAW,EACX,MAAM,EAAE,aAAoB,EAC5B,KAAa,EACb,QAAkB,GACnB,EAAE,sBAAsB,GAAG;IAAE,cAAc,EAAE,MAAM,IAAI,CAAC;IAAC,oBAAoB,EAAE,MAAM,IAAI,CAAA;CAAE,CAuF3F"}
|
|
@@ -25,7 +25,7 @@ function resolve(target) {
|
|
|
25
25
|
* · **the toggle is inside**, or pressing an open menu's own button shuts it and reopens
|
|
26
26
|
* it a frame later — the "menu that ignores the second press" desk hit on 2026-09-07.
|
|
27
27
|
*/
|
|
28
|
-
export function useOutsidePress({ open, onDismiss, inside = [], escape: closeOnEscape = true, modal = false, }) {
|
|
28
|
+
export function useOutsidePress({ open, onDismiss, inside = [], escape: closeOnEscape = true, modal = false, decideOn = 'click', }) {
|
|
29
29
|
const pressInsideRef = useRef(false);
|
|
30
30
|
// Latest values without re-subscribing on every render — a listener torn down mid-gesture
|
|
31
31
|
// would miss the very click it exists for.
|
|
@@ -50,15 +50,8 @@ export function useOutsidePress({ open, onDismiss, inside = [], escape: closeOnE
|
|
|
50
50
|
return element ? layer.contains(element) : false;
|
|
51
51
|
});
|
|
52
52
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const target = event.target instanceof Node ? event.target : null;
|
|
56
|
-
if (target && latest.current.modal && !isInside(target) && !onAnotherLayer(target)) {
|
|
57
|
-
// Decidable now for a DOM-inside press; a React-tree-inside press would have been
|
|
58
|
-
// swallowed too, so a modal panel's portalled layers must live inside `inside`.
|
|
59
|
-
event.preventDefault();
|
|
60
|
-
event.stopPropagation();
|
|
61
|
-
}
|
|
53
|
+
/** Decide once the event has finished dispatching — see the header. */
|
|
54
|
+
const decideLater = (target) => {
|
|
62
55
|
const timer = setTimeout(() => {
|
|
63
56
|
timers.delete(timer);
|
|
64
57
|
if (pressInsideRef.current || !target)
|
|
@@ -71,24 +64,57 @@ export function useOutsidePress({ open, onDismiss, inside = [], escape: closeOnE
|
|
|
71
64
|
}, 0);
|
|
72
65
|
timers.add(timer);
|
|
73
66
|
};
|
|
67
|
+
const outside = (target) => Boolean(target) && !isInside(target) && !onAnotherLayer(target);
|
|
68
|
+
// `pointerdown` mode: the click the same press produces is not a second decision — it is
|
|
69
|
+
// swallowed, until the next press or a second later, whichever comes first.
|
|
70
|
+
let swallowUntil = 0;
|
|
71
|
+
const onPointerDown = (event) => {
|
|
72
|
+
pressInsideRef.current = false;
|
|
73
|
+
swallowUntil = 0;
|
|
74
|
+
const target = event.target instanceof Node ? event.target : null;
|
|
75
|
+
if (outside(target))
|
|
76
|
+
swallowUntil = Date.now() + 1000;
|
|
77
|
+
decideLater(target);
|
|
78
|
+
};
|
|
79
|
+
const onClick = (event) => {
|
|
80
|
+
const target = event.target instanceof Node ? event.target : null;
|
|
81
|
+
if (decideOn === 'pointerdown') {
|
|
82
|
+
if (Date.now() < swallowUntil) {
|
|
83
|
+
swallowUntil = 0;
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
pressInsideRef.current = false;
|
|
90
|
+
if (latest.current.modal && outside(target)) {
|
|
91
|
+
// Decidable now for a DOM-inside press; a React-tree-inside press would have been
|
|
92
|
+
// swallowed too, so a modal panel's portalled layers must live inside `inside`.
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
event.stopPropagation();
|
|
95
|
+
}
|
|
96
|
+
decideLater(target);
|
|
97
|
+
};
|
|
74
98
|
const onKeyDown = (event) => {
|
|
75
99
|
if (event.key === 'Escape')
|
|
76
100
|
latest.current.onDismiss();
|
|
77
101
|
};
|
|
78
102
|
document.addEventListener('click', onClick, true);
|
|
103
|
+
if (decideOn === 'pointerdown')
|
|
104
|
+
document.addEventListener('pointerdown', onPointerDown, true);
|
|
79
105
|
if (closeOnEscape)
|
|
80
106
|
document.addEventListener('keydown', onKeyDown);
|
|
81
107
|
return () => {
|
|
82
108
|
document.removeEventListener('click', onClick, true);
|
|
109
|
+
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
83
110
|
document.removeEventListener('keydown', onKeyDown);
|
|
84
111
|
for (const timer of timers)
|
|
85
112
|
clearTimeout(timer);
|
|
86
113
|
};
|
|
87
|
-
}, [open, closeOnEscape]);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
pressInsideRef.current = true;
|
|
91
|
-
},
|
|
114
|
+
}, [open, closeOnEscape, decideOn]);
|
|
115
|
+
const markInside = () => {
|
|
116
|
+
pressInsideRef.current = true;
|
|
92
117
|
};
|
|
118
|
+
return { onClickCapture: markInside, onPointerDownCapture: markInside };
|
|
93
119
|
}
|
|
94
120
|
//# sourceMappingURL=useOutsidePress.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOutsidePress.js","sourceRoot":"","sources":["../../../src/react/hooks/useOutsidePress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useOutsidePress.js","sourceRoot":"","sources":["../../../src/react/hooks/useOutsidePress.ts"],"names":[],"mappings":"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"}
|
package/dist/react/icons.d.ts
CHANGED
|
@@ -75,7 +75,11 @@ export declare const defaultIconMap: Record<IconToken, IconComponent>;
|
|
|
75
75
|
export declare function createIconMapRegistry(): IconMapRegistry;
|
|
76
76
|
export interface DynamicIconProps {
|
|
77
77
|
token?: IconToken;
|
|
78
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* The icon map to resolve `token` against — typically `runtime.icons`. Omitted, the token
|
|
80
|
+
* resolves against `defaultIconMap`, so a plain `<DynamicIcon token="settings" />` draws the
|
|
81
|
+
* settings icon.
|
|
82
|
+
*/
|
|
79
83
|
iconMap?: IconMapRegistry | null;
|
|
80
84
|
className?: string;
|
|
81
85
|
}
|
|
@@ -91,8 +95,14 @@ export interface DynamicIconProps {
|
|
|
91
95
|
* element and visually overflows into neighboring content instead of
|
|
92
96
|
* respecting the icon's slot.
|
|
93
97
|
*
|
|
94
|
-
* The `iconMap` is typically the `runtime.icons` registry from the host shell
|
|
95
|
-
*
|
|
98
|
+
* The `iconMap` is typically the `runtime.icons` registry from the host shell.
|
|
99
|
+
*
|
|
100
|
+
* 🔴 Omitted, it is `defaultIconMap` — NOT "nothing registered". Until 5.15.1 a missing
|
|
101
|
+
* `iconMap` resolved every token to the letter fallback, so vault's header, which renders
|
|
102
|
+
* `<DynamicIcon token="unlock" />` and its neighbours with no map, painted the letters
|
|
103
|
+
* "U W S" where its lock, generator and settings icons belonged, on every screen, from the
|
|
104
|
+
* day it adopted the component. A registry that was never passed is not a registry that was
|
|
105
|
+
* cleared; only an explicit map may leave a token unresolved.
|
|
96
106
|
*/
|
|
97
107
|
export declare function DynamicIcon({ token, iconMap, className }: DynamicIconProps): React.JSX.Element | null;
|
|
98
108
|
//# sourceMappingURL=icons.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/react/icons.tsx"],"names":[],"mappings":"AAyDA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,8DAA8D;AAC9D,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Ef,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,YAAY,CAAC;AAElD,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/D,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;IACjD,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;0EAE0E;AAC1E,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,SAAS,EAAE,aAAa,CAwD3D,CAAC;AAEF,wBAAgB,qBAAqB,IAAI,eAAe,CAavD;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/react/icons.tsx"],"names":[],"mappings":"AAyDA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,8DAA8D;AAC9D,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Ef,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,YAAY,CAAC;AAElD,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/D,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;IACjD,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;0EAE0E;AAC1E,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,SAAS,EAAE,aAAa,CAwD3D,CAAC;AAEF,wBAAgB,qBAAqB,IAAI,eAAe,CAavD;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,gBAAgB,4BAoB1E"}
|
package/dist/react/icons.js
CHANGED
|
@@ -168,13 +168,19 @@ export function createIconMapRegistry() {
|
|
|
168
168
|
* element and visually overflows into neighboring content instead of
|
|
169
169
|
* respecting the icon's slot.
|
|
170
170
|
*
|
|
171
|
-
* The `iconMap` is typically the `runtime.icons` registry from the host shell
|
|
172
|
-
*
|
|
171
|
+
* The `iconMap` is typically the `runtime.icons` registry from the host shell.
|
|
172
|
+
*
|
|
173
|
+
* 🔴 Omitted, it is `defaultIconMap` — NOT "nothing registered". Until 5.15.1 a missing
|
|
174
|
+
* `iconMap` resolved every token to the letter fallback, so vault's header, which renders
|
|
175
|
+
* `<DynamicIcon token="unlock" />` and its neighbours with no map, painted the letters
|
|
176
|
+
* "U W S" where its lock, generator and settings icons belonged, on every screen, from the
|
|
177
|
+
* day it adopted the component. A registry that was never passed is not a registry that was
|
|
178
|
+
* cleared; only an explicit map may leave a token unresolved.
|
|
173
179
|
*/
|
|
174
180
|
export function DynamicIcon({ token, iconMap, className }) {
|
|
175
181
|
if (!token)
|
|
176
182
|
return null;
|
|
177
|
-
const Icon = iconMap
|
|
183
|
+
const Icon = iconMap ? iconMap.get(token) : defaultIconMap[token];
|
|
178
184
|
if (!Icon)
|
|
179
185
|
return (_jsx(Tooltip, { content: token, children: _jsx("span", { className: cn(
|
|
180
186
|
// guardrails-ignore micro-typography-floor: 0.6em is RELATIVE to the avatar box —
|
package/dist/react/icons.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icons.js","sourceRoot":"","sources":["../../src/react/icons.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"icons.js","sourceRoot":"","sources":["../../src/react/icons.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,SAAS,EACT,SAAS,EACT,IAAI,EACJ,MAAM,EACN,GAAG,EACH,YAAY,EACZ,QAAQ,EACR,MAAM,EACN,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,MAAM,EACN,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,KAAK,EACL,SAAS,EACT,IAAI,EACJ,KAAK,IAAI,SAAS,EAClB,KAAK,EACL,eAAe,EACf,QAAQ,EACR,IAAI,IAAI,QAAQ,EAChB,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,aAAa,EACb,GAAG,EACH,iBAAiB,EACjB,WAAW,EACX,OAAO,EACP,OAAO,EACP,IAAI,EACJ,MAAM,EACN,KAAK,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,UAAU,EACV,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,EACL,MAAM,GACP,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAElD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,4EAA4E;IAC5E,6EAA6E;IAC7E,0EAA0E;IAC1E,gFAAgF;IAChF,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC;IACT,SAAS,EAAE,CAAC;IACZ,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;IACX,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,CAAC;IACT,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;IACP,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,CAAC;IACN,2EAA2E;IAC3E,yEAAyE;IACzE,4CAA4C;IAC5C,GAAG,EAAE,CAAC;IACN,KAAK,EAAE,CAAC;IACR,6EAA6E;IAC7E,8EAA8E;IAC9E,uDAAuD;IACvD,IAAI,EAAE,CAAC;IACP,4EAA4E;IAC5E,yEAAyE;IACzE,KAAK,EAAE,CAAC;IACR,yEAAyE;IACzE,2EAA2E;IAC3E,QAAQ,EAAE,CAAC;IACX,4EAA4E;IAC5E,6EAA6E;IAC7E,2EAA2E;IAC3E,OAAO,EAAE,CAAC;IACV,0EAA0E;IAC1E,yEAAyE;IACzE,2DAA2D;IAC3D,YAAY,EAAE,CAAC;IACf,8EAA8E;IAC9E,sEAAsE;IACtE,UAAU,EAAE,CAAC;CACL,CAAC;AAgBX;;0EAE0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAqC;IAC9D,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,KAAK;IACZ,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,MAAM;IACd,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,eAAe;IACtB,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,aAAa;IACnB,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,MAAM,EAAE,YAAY;IACpB,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,SAAS;IACpB,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,WAAW;IACrB,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,WAAW;IAClB,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;IACd,GAAG,EAAE,GAAG;IACR,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,GAAG;IACR,KAAK,EAAE,UAAU;IACjB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,YAAY;IACnB,QAAQ,EAAE,QAAQ;IAClB,OAAO,EAAE,iBAAiB;IAC1B,YAAY,EAAE,YAAY;IAC1B,UAAU,EAAE,UAAU;CACvB,CAAC;AAEF,MAAM,UAAU,qBAAqB;IACnC,IAAI,KAAK,GAA8C,EAAE,GAAG,cAAc,EAAE,CAAC;IAC7E,OAAO;QACL,QAAQ,CAAC,GAAG;YACV,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,GAAG,EAAE,CAAC;QAC/B,CAAC;QACD,GAAG,CAAC,KAAK;YACP,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,KAAK;YACH,KAAK,GAAG,EAAE,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC;AAaD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAoB;IACzE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAClE,IAAI,CAAC,IAAI;QACP,OAAO,CACL,KAAC,OAAO,IAAC,OAAO,EAAE,KAAK,YACrB,eACE,SAAS,EAAE,EAAE;gBACX,kFAAkF;gBAC1F,2EAA2E;gBAC3E,oHAAoH,EAC5G,SAAS,CACV,eACU,KAAK,YAEf,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GACb,GACC,CACX,CAAC;IACJ,OAAO,KAAC,IAAI,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,GAAI,CAAC;AAClD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt",
|
|
3
|
-
"version": "5.15.
|
|
3
|
+
"version": "5.15.2",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"e2e:playback-degraded": "playwright test --config e2e/playwright.config.ts playback-degraded.spec.ts",
|
|
87
87
|
"e2e:resize-grip": "playwright test --config e2e/playwright.config.ts resize-grip-rail.spec.ts",
|
|
88
88
|
"e2e:touch": "playwright test --config e2e/playwright.config.ts --project=touch",
|
|
89
|
+
"e2e:tap-away": "playwright test --config e2e/playwright.config.ts tap-away.spec.ts",
|
|
89
90
|
"done:serial": "bun run typecheck && bun run lint && bun run test && bun run build",
|
|
90
91
|
"paths": "bun run scripts/paths.ts",
|
|
91
92
|
"surface": "public-surface",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import type { ButtonHTMLAttributes } from 'react';
|
|
4
|
+
import { DropdownMenu } from './DropdownMenu.js';
|
|
5
|
+
|
|
6
|
+
afterEach(cleanup);
|
|
7
|
+
|
|
8
|
+
function B(props: ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
9
|
+
return (
|
|
10
|
+
<button
|
|
11
|
+
// guardrails-ignore no-raw-action-button: a spec fixture — any trigger element must work.
|
|
12
|
+
type='button'
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const menu = () => screen.queryByRole('menu', { hidden: true });
|
|
19
|
+
|
|
20
|
+
describe('DropdownMenu closes on a press anywhere else', () => {
|
|
21
|
+
test('even on a surface that stops its own pointerdown — a drag handle, a canvas', async () => {
|
|
22
|
+
render(
|
|
23
|
+
<div>
|
|
24
|
+
<B onPointerDown={(e) => e.stopPropagation()}>drag handle</B>
|
|
25
|
+
<DropdownMenu items={[{ key: 'a', label: 'Rename' }]} trigger={<B>actions</B>} />
|
|
26
|
+
</div>,
|
|
27
|
+
);
|
|
28
|
+
fireEvent.pointerDown(screen.getByText('actions'), { button: 0, ctrlKey: false });
|
|
29
|
+
expect(menu()).toBeTruthy();
|
|
30
|
+
await act(() => new Promise((r) => setTimeout(r, 0)));
|
|
31
|
+
act(() => {
|
|
32
|
+
screen.getByText('drag handle').dispatchEvent(
|
|
33
|
+
new PointerEvent('pointerdown', { bubbles: true, button: 0, pointerType: 'touch' }),
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
await act(() => new Promise((r) => setTimeout(r, 5)));
|
|
37
|
+
expect(menu()).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as RDrop from '@radix-ui/react-dropdown-menu';
|
|
2
2
|
import { Check, ChevronRight } from 'lucide-react';
|
|
3
|
-
import type
|
|
3
|
+
import { type ReactNode, useCallback, useRef, useState } from 'react';
|
|
4
|
+
import { useOutsidePress } from '../hooks/useOutsidePress.js';
|
|
4
5
|
import { cn } from '../lib/cn.js';
|
|
5
6
|
import { useFloatingContainer } from '../lib/floatingContainer.js';
|
|
6
7
|
import {
|
|
@@ -87,15 +88,43 @@ export function DropdownMenu({
|
|
|
87
88
|
onOpenChange,
|
|
88
89
|
}: DropdownMenuProps) {
|
|
89
90
|
const container = useFloatingContainer();
|
|
91
|
+
// Always controlled underneath, so the outside-press fallback can close it too.
|
|
92
|
+
const [ownOpen, setOwnOpen] = useState(false);
|
|
93
|
+
const isOpen = open ?? ownOpen;
|
|
94
|
+
const setOpen = useCallback(
|
|
95
|
+
(next: boolean) => {
|
|
96
|
+
if (open === undefined) setOwnOpen(next);
|
|
97
|
+
onOpenChange?.(next);
|
|
98
|
+
},
|
|
99
|
+
[open, onOpenChange],
|
|
100
|
+
);
|
|
101
|
+
/*
|
|
102
|
+
* Radix's menu listens for the outside `pointerdown` in the BUBBLE phase, so a surface that
|
|
103
|
+
* stops its own pointerdown (a drag handle, a canvas) kept the menu open. The belt's one
|
|
104
|
+
* tap-away listener runs in the capture phase; see `useOutsidePress`. The menu is modal, so
|
|
105
|
+
* it decides on the pointerdown (an inert page sends an iPhone no click).
|
|
106
|
+
*/
|
|
107
|
+
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
108
|
+
const pressInside = useOutsidePress({
|
|
109
|
+
open: isOpen,
|
|
110
|
+
onDismiss: () => setOpen(false),
|
|
111
|
+
inside: [triggerRef],
|
|
112
|
+
escape: false,
|
|
113
|
+
decideOn: 'pointerdown',
|
|
114
|
+
});
|
|
90
115
|
return (
|
|
91
|
-
<RDrop.Root open={
|
|
92
|
-
<RDrop.Trigger asChild
|
|
116
|
+
<RDrop.Root open={isOpen} onOpenChange={setOpen}>
|
|
117
|
+
<RDrop.Trigger asChild ref={triggerRef}>
|
|
118
|
+
{trigger}
|
|
119
|
+
</RDrop.Trigger>
|
|
93
120
|
<RDrop.Portal container={container}>
|
|
94
121
|
<RDrop.Content
|
|
95
122
|
align={align}
|
|
96
123
|
side={side}
|
|
97
124
|
sideOffset={4}
|
|
98
125
|
collisionPadding={FLOATING_COLLISION_PADDING}
|
|
126
|
+
onClickCapture={pressInside.onClickCapture}
|
|
127
|
+
onPointerDownCapture={pressInside.onPointerDownCapture}
|
|
99
128
|
className={cn(FLOATING_CONTENT_BASE, FLOATING_ANIMATION, 'min-w-[8rem] p-1', className)}
|
|
100
129
|
>
|
|
101
130
|
{items.map((item) => (
|
|
@@ -130,6 +130,44 @@ describe('Popover closes when you press anywhere else', () => {
|
|
|
130
130
|
expect(panel()).toBeNull();
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
test('🔴 modal closes on a tap that sends NO click — an iPhone tapping the inert page', async () => {
|
|
134
|
+
// Measured in Playwright's iPhone 15 WebKit, 2026-09-27: under `modal` the tap lands on
|
|
135
|
+
// <html>, iOS sends no click for it, and a click-deciding popover never closed.
|
|
136
|
+
render(<Harness modal />);
|
|
137
|
+
openIt();
|
|
138
|
+
await act(() => new Promise((r) => setTimeout(r, 0)));
|
|
139
|
+
act(() => {
|
|
140
|
+
document.documentElement.dispatchEvent(
|
|
141
|
+
new PointerEvent('pointerdown', { bubbles: true, button: 0, pointerType: 'touch' }),
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
await act(() => new Promise((r) => setTimeout(r, 5)));
|
|
145
|
+
expect(panel()).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('modal: the click the closing tap produces does not reach what the menu uncovered', async () => {
|
|
149
|
+
let played = 0;
|
|
150
|
+
function Player() {
|
|
151
|
+
const [open, setOpen] = useState(false);
|
|
152
|
+
return (
|
|
153
|
+
<div>
|
|
154
|
+
<B onClick={() => played++}>picture</B>
|
|
155
|
+
<Popover open={open} onOpenChange={setOpen} modal label='Subtitles' trigger={<B>subtitles</B>}>
|
|
156
|
+
<B>English</B>
|
|
157
|
+
</Popover>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
render(<Player />);
|
|
162
|
+
openIt();
|
|
163
|
+
await act(() => new Promise((r) => setTimeout(r, 0)));
|
|
164
|
+
await tapOutside(screen.getByText('picture'), 'touch');
|
|
165
|
+
expect(panel()).toBeNull();
|
|
166
|
+
expect(played).toBe(0);
|
|
167
|
+
await tapOutside(screen.getByText('picture'), 'touch');
|
|
168
|
+
expect(played).toBe(1); // closed, the picture works again
|
|
169
|
+
});
|
|
170
|
+
|
|
133
171
|
test('modal still closes on an outside press', async () => {
|
|
134
172
|
render(<Harness modal />);
|
|
135
173
|
openIt();
|
|
@@ -150,6 +150,9 @@ export function Popover({
|
|
|
150
150
|
onDismiss: () => setOpen(false),
|
|
151
151
|
inside: [triggerRef, anchorTrigger],
|
|
152
152
|
escape: false,
|
|
153
|
+
// Under `modal` the page is inert and an iPhone sends no click for a tap on it — see
|
|
154
|
+
// `decideOn` in useOutsidePress.
|
|
155
|
+
decideOn: modal ? 'pointerdown' : 'click',
|
|
153
156
|
});
|
|
154
157
|
|
|
155
158
|
const content = (
|
|
@@ -171,6 +174,7 @@ export function Popover({
|
|
|
171
174
|
// React-tree capture: a press on a nested layer this panel portalled (a Select's
|
|
172
175
|
// list, a sub-menu) is INSIDE even though the DOM puts it elsewhere.
|
|
173
176
|
onClickCapture={pressInside.onClickCapture}
|
|
177
|
+
onPointerDownCapture={pressInside.onPointerDownCapture}
|
|
174
178
|
className={cn(
|
|
175
179
|
unstyled
|
|
176
180
|
? 'z-[var(--z-popover)] outline-none'
|
|
@@ -20,6 +20,17 @@ export interface UseOutsidePressOptions {
|
|
|
20
20
|
* whose rows play on tap). Default false.
|
|
21
21
|
*/
|
|
22
22
|
modal?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Decide on the `pointerdown` rather than the `click`. Only for a panel that has made the
|
|
25
|
+
* page under it inert (`Popover`'s `modal`, which sets `pointer-events: none` on body).
|
|
26
|
+
*
|
|
27
|
+
* 🔴 Measured 2026-09-27 in Playwright's iPhone 15 WebKit: with the page inert, a tap
|
|
28
|
+
* outside hits `<html>`, which iOS does not consider clickable, so NO `click` is ever
|
|
29
|
+
* sent — the menu stayed open under the owner's finger, exactly as reported. Deciding on
|
|
30
|
+
* the pointerdown closes it; the `click` that same press may still produce is swallowed,
|
|
31
|
+
* or it would land on whatever the closed menu uncovered (the picture, which pauses).
|
|
32
|
+
*/
|
|
33
|
+
decideOn?: 'click' | 'pointerdown';
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
/** A floating layer another component portalled — a press there belongs to that layer. */
|
|
@@ -57,7 +68,8 @@ export function useOutsidePress({
|
|
|
57
68
|
inside = [],
|
|
58
69
|
escape: closeOnEscape = true,
|
|
59
70
|
modal = false,
|
|
60
|
-
|
|
71
|
+
decideOn = 'click',
|
|
72
|
+
}: UseOutsidePressOptions): { onClickCapture: () => void; onPointerDownCapture: () => void } {
|
|
61
73
|
const pressInsideRef = useRef(false);
|
|
62
74
|
// Latest values without re-subscribing on every render — a listener torn down mid-gesture
|
|
63
75
|
// would miss the very click it exists for.
|
|
@@ -83,15 +95,8 @@ export function useOutsidePress({
|
|
|
83
95
|
});
|
|
84
96
|
};
|
|
85
97
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const target = event.target instanceof Node ? event.target : null;
|
|
89
|
-
if (target && latest.current.modal && !isInside(target) && !onAnotherLayer(target)) {
|
|
90
|
-
// Decidable now for a DOM-inside press; a React-tree-inside press would have been
|
|
91
|
-
// swallowed too, so a modal panel's portalled layers must live inside `inside`.
|
|
92
|
-
event.preventDefault();
|
|
93
|
-
event.stopPropagation();
|
|
94
|
-
}
|
|
98
|
+
/** Decide once the event has finished dispatching — see the header. */
|
|
99
|
+
const decideLater = (target: Node | null) => {
|
|
95
100
|
const timer = setTimeout(() => {
|
|
96
101
|
timers.delete(timer);
|
|
97
102
|
if (pressInsideRef.current || !target) return;
|
|
@@ -101,22 +106,54 @@ export function useOutsidePress({
|
|
|
101
106
|
}, 0);
|
|
102
107
|
timers.add(timer);
|
|
103
108
|
};
|
|
109
|
+
const outside = (target: Node | null) => Boolean(target) && !isInside(target as Node) && !onAnotherLayer(target as Node);
|
|
110
|
+
|
|
111
|
+
// `pointerdown` mode: the click the same press produces is not a second decision — it is
|
|
112
|
+
// swallowed, until the next press or a second later, whichever comes first.
|
|
113
|
+
let swallowUntil = 0;
|
|
114
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
115
|
+
pressInsideRef.current = false;
|
|
116
|
+
swallowUntil = 0;
|
|
117
|
+
const target = event.target instanceof Node ? event.target : null;
|
|
118
|
+
if (outside(target)) swallowUntil = Date.now() + 1000;
|
|
119
|
+
decideLater(target);
|
|
120
|
+
};
|
|
121
|
+
const onClick = (event: MouseEvent) => {
|
|
122
|
+
const target = event.target instanceof Node ? event.target : null;
|
|
123
|
+
if (decideOn === 'pointerdown') {
|
|
124
|
+
if (Date.now() < swallowUntil) {
|
|
125
|
+
swallowUntil = 0;
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
event.stopPropagation();
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
pressInsideRef.current = false;
|
|
132
|
+
if (latest.current.modal && outside(target)) {
|
|
133
|
+
// Decidable now for a DOM-inside press; a React-tree-inside press would have been
|
|
134
|
+
// swallowed too, so a modal panel's portalled layers must live inside `inside`.
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
event.stopPropagation();
|
|
137
|
+
}
|
|
138
|
+
decideLater(target);
|
|
139
|
+
};
|
|
104
140
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
105
141
|
if (event.key === 'Escape') latest.current.onDismiss();
|
|
106
142
|
};
|
|
107
143
|
|
|
108
144
|
document.addEventListener('click', onClick, true);
|
|
145
|
+
if (decideOn === 'pointerdown') document.addEventListener('pointerdown', onPointerDown, true);
|
|
109
146
|
if (closeOnEscape) document.addEventListener('keydown', onKeyDown);
|
|
110
147
|
return () => {
|
|
111
148
|
document.removeEventListener('click', onClick, true);
|
|
149
|
+
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
112
150
|
document.removeEventListener('keydown', onKeyDown);
|
|
113
151
|
for (const timer of timers) clearTimeout(timer);
|
|
114
152
|
};
|
|
115
|
-
}, [open, closeOnEscape]);
|
|
153
|
+
}, [open, closeOnEscape, decideOn]);
|
|
116
154
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
pressInsideRef.current = true;
|
|
120
|
-
},
|
|
155
|
+
const markInside = () => {
|
|
156
|
+
pressInsideRef.current = true;
|
|
121
157
|
};
|
|
158
|
+
return { onClickCapture: markInside, onPointerDownCapture: markInside };
|
|
122
159
|
}
|
package/src/react/icons.spec.ts
CHANGED
|
@@ -42,3 +42,22 @@ describe('DynamicIcon fallback', () => {
|
|
|
42
42
|
expect(container.innerHTML).toBe('');
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
describe('DynamicIcon with no iconMap', () => {
|
|
47
|
+
// 🔴 vault rendered `<DynamicIcon token="unlock" />` with no map, and every token fell to the
|
|
48
|
+
// letter fallback — "U W S" in its header where three icons belonged (2026-09-28).
|
|
49
|
+
test('draws the default icon, never the letter fallback', () => {
|
|
50
|
+
for (const token of ['unlock', 'wand', 'settings', 'search'] as const) {
|
|
51
|
+
const { container } = render(DynamicIcon({ token }));
|
|
52
|
+
expect(container.querySelector('[data-icon]'), `${token} fell back to a letter`).toBeNull();
|
|
53
|
+
expect(container.querySelector('svg'), `${token} drew no icon`).not.toBeNull();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('an explicit map that lacks the token still falls back to its letter', () => {
|
|
58
|
+
const icons = createIconMapRegistry();
|
|
59
|
+
icons.clear();
|
|
60
|
+
const { container } = render(DynamicIcon({ token: 'settings', iconMap: icons }));
|
|
61
|
+
expect(container.querySelector('[data-icon="settings"]')?.textContent).toBe('s');
|
|
62
|
+
});
|
|
63
|
+
});
|
package/src/react/icons.tsx
CHANGED
|
@@ -232,7 +232,11 @@ export function createIconMapRegistry(): IconMapRegistry {
|
|
|
232
232
|
|
|
233
233
|
export interface DynamicIconProps {
|
|
234
234
|
token?: IconToken;
|
|
235
|
-
/**
|
|
235
|
+
/**
|
|
236
|
+
* The icon map to resolve `token` against — typically `runtime.icons`. Omitted, the token
|
|
237
|
+
* resolves against `defaultIconMap`, so a plain `<DynamicIcon token="settings" />` draws the
|
|
238
|
+
* settings icon.
|
|
239
|
+
*/
|
|
236
240
|
iconMap?: IconMapRegistry | null;
|
|
237
241
|
className?: string;
|
|
238
242
|
}
|
|
@@ -249,12 +253,18 @@ export interface DynamicIconProps {
|
|
|
249
253
|
* element and visually overflows into neighboring content instead of
|
|
250
254
|
* respecting the icon's slot.
|
|
251
255
|
*
|
|
252
|
-
* The `iconMap` is typically the `runtime.icons` registry from the host shell
|
|
253
|
-
*
|
|
256
|
+
* The `iconMap` is typically the `runtime.icons` registry from the host shell.
|
|
257
|
+
*
|
|
258
|
+
* 🔴 Omitted, it is `defaultIconMap` — NOT "nothing registered". Until 5.15.1 a missing
|
|
259
|
+
* `iconMap` resolved every token to the letter fallback, so vault's header, which renders
|
|
260
|
+
* `<DynamicIcon token="unlock" />` and its neighbours with no map, painted the letters
|
|
261
|
+
* "U W S" where its lock, generator and settings icons belonged, on every screen, from the
|
|
262
|
+
* day it adopted the component. A registry that was never passed is not a registry that was
|
|
263
|
+
* cleared; only an explicit map may leave a token unresolved.
|
|
254
264
|
*/
|
|
255
265
|
export function DynamicIcon({ token, iconMap, className }: DynamicIconProps) {
|
|
256
266
|
if (!token) return null;
|
|
257
|
-
const Icon = iconMap
|
|
267
|
+
const Icon = iconMap ? iconMap.get(token) : defaultIconMap[token];
|
|
258
268
|
if (!Icon)
|
|
259
269
|
return (
|
|
260
270
|
<Tooltip content={token}>
|