cursedbelt 5.15.1 → 5.15.3
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/Button.d.ts +8 -1
- package/dist/react/components/Button.d.ts.map +1 -1
- package/dist/react/components/Button.js.map +1 -1
- 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/package.json +2 -1
- package/src/react/components/Button.spec.tsx +10 -0
- package/src/react/components/Button.tsx +8 -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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type VariantProps } from 'class-variance-authority';
|
|
2
|
-
import { type ButtonHTMLAttributes, type MouseEvent, type ReactNode } from 'react';
|
|
2
|
+
import { type ButtonHTMLAttributes, type MouseEvent, type ReactNode, type Ref } from 'react';
|
|
3
3
|
import { type TooltipMobile, type TooltipPlacement } from './Tooltip.js';
|
|
4
4
|
/**
|
|
5
5
|
* The single source of truth for every interactive element that triggers an action.
|
|
@@ -69,6 +69,13 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, Va
|
|
|
69
69
|
/** How long the post-completion checkmark flash shows (ms). Default 1200. */
|
|
70
70
|
resetMs?: number;
|
|
71
71
|
children?: ReactNode;
|
|
72
|
+
/**
|
|
73
|
+
* The rendered `<button>` (or the `asChild` element). React 19 passes `ref` as an ordinary
|
|
74
|
+
* prop and this component already spreads it onto the element — the type is what was
|
|
75
|
+
* missing, so a caller that needed the node (a `useOutsidePress` toggle, a focus return)
|
|
76
|
+
* could not ask for it without a cast.
|
|
77
|
+
*/
|
|
78
|
+
ref?: Ref<HTMLButtonElement>;
|
|
72
79
|
}
|
|
73
80
|
export declare function Button({ className, variant, size, asChild, loading, leftIcon, tooltip, tooltipSide, tooltipMobile, onAsyncClick, successMessage, errorMessage, resetMs, type, children, disabled, onClick, ...props }: ButtonProps): import("react").JSX.Element;
|
|
74
81
|
//# sourceMappingURL=Button.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../../src/react/components/Button.tsx"],"names":[],"mappings":"AACA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,
|
|
1
|
+
{"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../../src/react/components/Button.tsx"],"names":[],"mappings":"AACA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,GAAG,EAAoB,MAAM,OAAO,CAAC;AAG/G,OAAO,EAAW,KAAK,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGlF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc;;;8EAkF1B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AACxF,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAElF,MAAM,WAAW,WACf,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC,EAC7C,YAAY,CAAC,OAAO,cAAc,CAAC;IACrC;;;8CAG0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;6EACyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;;;eAQW;IACX,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,IAAI,GAAG,SAAS,CAAC,CAAC;IAC9C,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B;;;6DAGyD;IACzD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE;yFACqF;IACrF,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAClC;gFAC4E;IAC5E,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAChC,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;CAC9B;AAED,wBAAgB,MAAM,CAAC,EACrB,SAAS,EACT,OAAO,EACP,IAAI,EACJ,OAAe,EACf,OAAe,EACf,QAAQ,EACR,OAAO,EACP,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAsB,EACtB,YAAmB,EACnB,OAAc,EACd,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,GAAG,KAAK,EACT,EAAE,WAAW,+BAyEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","sourceRoot":"","sources":["../../../src/react/components/Button.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAqB,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,
|
|
1
|
+
{"version":3,"file":"Button.js","sourceRoot":"","sources":["../../../src/react/components/Button.tsx"],"names":[],"mappings":"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"}
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt",
|
|
3
|
-
"version": "5.15.
|
|
3
|
+
"version": "5.15.3",
|
|
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",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from 'bun:test';
|
|
2
2
|
import { cleanup, render } from '@testing-library/react';
|
|
3
|
+
import { createRef } from 'react';
|
|
3
4
|
import { Button } from './Button.js';
|
|
4
5
|
import { ButtonGroup } from './ButtonGroup.js';
|
|
5
6
|
import { ButtonLink } from './ButtonLink.js';
|
|
@@ -214,3 +215,12 @@ describe('SplitButton', () => {
|
|
|
214
215
|
expect(getByRole('group').querySelectorAll('button').length).toBe(2);
|
|
215
216
|
});
|
|
216
217
|
});
|
|
218
|
+
|
|
219
|
+
describe('ref', () => {
|
|
220
|
+
test('an IconButton hands its caller the <button> — a useOutsidePress toggle needs the node', () => {
|
|
221
|
+
const ref = createRef<HTMLButtonElement>();
|
|
222
|
+
render(<IconButton ref={ref} label='Folders'>F</IconButton>);
|
|
223
|
+
expect(ref.current?.tagName).toBe('BUTTON');
|
|
224
|
+
expect(ref.current?.getAttribute('aria-label')).toBe('Folders');
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Slot } from '@radix-ui/react-slot';
|
|
2
2
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
3
|
import { Check, Loader2 } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { type ButtonHTMLAttributes, type MouseEvent, type ReactNode, type Ref, useRef, useState } from 'react';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
6
|
import { cn } from '../lib/cn.js';
|
|
7
7
|
import { Tooltip, type TooltipMobile, type TooltipPlacement } from './Tooltip.js';
|
|
@@ -158,6 +158,13 @@ export interface ButtonProps
|
|
|
158
158
|
/** How long the post-completion checkmark flash shows (ms). Default 1200. */
|
|
159
159
|
resetMs?: number;
|
|
160
160
|
children?: ReactNode;
|
|
161
|
+
/**
|
|
162
|
+
* The rendered `<button>` (or the `asChild` element). React 19 passes `ref` as an ordinary
|
|
163
|
+
* prop and this component already spreads it onto the element — the type is what was
|
|
164
|
+
* missing, so a caller that needed the node (a `useOutsidePress` toggle, a focus return)
|
|
165
|
+
* could not ask for it without a cast.
|
|
166
|
+
*/
|
|
167
|
+
ref?: Ref<HTMLButtonElement>;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
export function Button({
|
|
@@ -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
|
}
|