@12-apps/ui 6.45.0 → 6.45.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 (25) hide show
  1. package/dist/_internal/components/data-display/HoverCard/HoverCard.hooks.js +1 -1
  2. package/dist/_internal/components/data-display/HoverCard/HoverCard.js +2 -2
  3. package/dist/_internal/components/data-display/Lightbox/Lightbox.js +1 -1
  4. package/dist/_internal/components/data-display/index.js +3 -3
  5. package/dist/_internal/components/index.js +3 -3
  6. package/dist/{chunk-Q534UJ34.js → chunk-KN3WYEH2.js} +54 -35
  7. package/dist/chunk-KN3WYEH2.js.map +1 -0
  8. package/dist/{chunk-IIXMTC65.js → chunk-SPZAEMGW.js} +8 -2
  9. package/dist/chunk-SPZAEMGW.js.map +1 -0
  10. package/dist/{chunk-VNWZWQF3.js → chunk-Z3USLQQT.js} +69 -24
  11. package/dist/chunk-Z3USLQQT.js.map +1 -0
  12. package/dist/data-display/HoverCard.js +2 -2
  13. package/dist/data-display/Lightbox.js +1 -1
  14. package/dist/types/components/data-display/HoverCard/HoverCard.d.ts.map +1 -1
  15. package/dist/types/components/data-display/HoverCard/HoverCard.hooks.d.ts +2 -8
  16. package/dist/types/components/data-display/HoverCard/HoverCard.hooks.d.ts.map +1 -1
  17. package/dist/types/components/data-display/Lightbox/Lightbox.d.ts.map +1 -1
  18. package/package.json +1 -1
  19. package/src/components/data-display/HoverCard/HoverCard.hooks.ts +173 -33
  20. package/src/components/data-display/HoverCard/HoverCard.md +1 -0
  21. package/src/components/data-display/HoverCard/HoverCard.tsx +38 -16
  22. package/src/components/data-display/Lightbox/Lightbox.tsx +7 -1
  23. package/dist/chunk-IIXMTC65.js.map +0 -1
  24. package/dist/chunk-Q534UJ34.js.map +0 -1
  25. package/dist/chunk-VNWZWQF3.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useHoverCard
3
- } from "../../../../chunk-VNWZWQF3.js";
3
+ } from "../../../../chunk-Z3USLQQT.js";
4
4
  import "../../../../chunk-PZ5AY32C.js";
5
5
  export {
6
6
  useHoverCard
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  HoverCard
3
- } from "../../../../chunk-Q534UJ34.js";
3
+ } from "../../../../chunk-KN3WYEH2.js";
4
4
  import "../../../../chunk-NRIV2CQ2.js";
5
5
  import "../../../../chunk-BSYPPO2I.js";
6
6
  import "../../../../chunk-ZHV7TCAC.js";
7
- import "../../../../chunk-VNWZWQF3.js";
7
+ import "../../../../chunk-Z3USLQQT.js";
8
8
  import "../../../../chunk-HPK33ACY.js";
9
9
  import "../../../../chunk-FQ6UW7IY.js";
10
10
  import "../../../../chunk-6JAIFIPW.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Lightbox,
3
3
  Lightbox_default
4
- } from "../../../../chunk-IIXMTC65.js";
4
+ } from "../../../../chunk-SPZAEMGW.js";
5
5
  import "../../../../chunk-UOS7DEQZ.js";
6
6
  import "../../../../chunk-HJPPNVCF.js";
7
7
  import "../../../../chunk-M6PWPEAV.js";
@@ -77,11 +77,11 @@ import {
77
77
  import "../../../chunk-6OS4AKWT.js";
78
78
  import {
79
79
  HoverCard
80
- } from "../../../chunk-Q534UJ34.js";
80
+ } from "../../../chunk-KN3WYEH2.js";
81
81
  import "../../../chunk-NRIV2CQ2.js";
82
82
  import "../../../chunk-BSYPPO2I.js";
83
83
  import "../../../chunk-ZHV7TCAC.js";
84
- import "../../../chunk-VNWZWQF3.js";
84
+ import "../../../chunk-Z3USLQQT.js";
85
85
  import "../../../chunk-TAF3YYMK.js";
86
86
  import {
87
87
  InteractiveTooltip
@@ -97,7 +97,7 @@ import "../../../chunk-724ZONOL.js";
97
97
  import "../../../chunk-UM35GGCS.js";
98
98
  import {
99
99
  Lightbox_default
100
- } from "../../../chunk-IIXMTC65.js";
100
+ } from "../../../chunk-SPZAEMGW.js";
101
101
  import "../../../chunk-UOS7DEQZ.js";
102
102
  import "../../../chunk-HJPPNVCF.js";
103
103
  import "../../../chunk-M6PWPEAV.js";
@@ -505,11 +505,11 @@ import {
505
505
  import "../../chunk-6OS4AKWT.js";
506
506
  import {
507
507
  HoverCard
508
- } from "../../chunk-Q534UJ34.js";
508
+ } from "../../chunk-KN3WYEH2.js";
509
509
  import "../../chunk-NRIV2CQ2.js";
510
510
  import "../../chunk-BSYPPO2I.js";
511
511
  import "../../chunk-ZHV7TCAC.js";
512
- import "../../chunk-VNWZWQF3.js";
512
+ import "../../chunk-Z3USLQQT.js";
513
513
  import "../../chunk-TAF3YYMK.js";
514
514
  import {
515
515
  InteractiveTooltip
@@ -525,7 +525,7 @@ import "../../chunk-724ZONOL.js";
525
525
  import "../../chunk-UM35GGCS.js";
526
526
  import {
527
527
  Lightbox_default
528
- } from "../../chunk-IIXMTC65.js";
528
+ } from "../../chunk-SPZAEMGW.js";
529
529
  import "../../chunk-UOS7DEQZ.js";
530
530
  import "../../chunk-HJPPNVCF.js";
531
531
  import "../../chunk-M6PWPEAV.js";
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-BSYPPO2I.js";
11
11
  import {
12
12
  useHoverCard
13
- } from "./chunk-VNWZWQF3.js";
13
+ } from "./chunk-Z3USLQQT.js";
14
14
  import {
15
15
  withDefaults
16
16
  } from "./chunk-HPK33ACY.js";
@@ -75,6 +75,14 @@ var capOf = (theme, maxWidth) => {
75
75
  const cap = maxWidth ?? 400;
76
76
  return cap <= 1 && cap !== 0 ? `${cap * 100}%` : rem(theme, cap);
77
77
  };
78
+ var buildTriggerElement = (trigger, triggerHandlers, disabled, dataTestId, children) => {
79
+ const triggerProps = {
80
+ ...triggerHandlers,
81
+ style: { cursor: disabled ? "default" : "pointer" },
82
+ "data-testid": dataTestId ? `${dataTestId}-trigger` : "hover-card-trigger"
83
+ };
84
+ return trigger ? React.cloneElement(trigger, triggerProps) : /* @__PURE__ */ jsx("span", { ...triggerProps, style: { display: "inline-block", ...triggerProps.style }, children });
85
+ };
78
86
  var HoverCard = React.forwardRef((props, ref) => {
79
87
  const {
80
88
  variant,
@@ -102,7 +110,15 @@ var HoverCard = React.forwardRef((props, ref) => {
102
110
  dataTestId,
103
111
  ...rest
104
112
  } = withDefaults(props, DEFAULTS);
105
- const { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef } = useHoverCard({
113
+ const {
114
+ anchorEl,
115
+ isOpen,
116
+ handleClose,
117
+ triggerHandlers,
118
+ cardHandlers,
119
+ contentRef,
120
+ onContentPointerDownCapture
121
+ } = useHoverCard({
106
122
  disabled,
107
123
  touchEnabled,
108
124
  enterDelay,
@@ -110,12 +126,7 @@ var HoverCard = React.forwardRef((props, ref) => {
110
126
  onOpen,
111
127
  onClose
112
128
  });
113
- const triggerProps = {
114
- ...triggerHandlers,
115
- style: { cursor: disabled ? "default" : "pointer" },
116
- "data-testid": dataTestId ? `${dataTestId}-trigger` : "hover-card-trigger"
117
- };
118
- const triggerElement = trigger ? React.cloneElement(trigger, triggerProps) : /* @__PURE__ */ jsx("span", { ...triggerProps, style: { display: "inline-block", ...triggerProps.style }, children });
129
+ const triggerElement = buildTriggerElement(trigger, triggerHandlers, disabled, dataTestId, children);
119
130
  const padding = CONTENT_PADDING[variant] ?? 2;
120
131
  return /* @__PURE__ */ jsxs(Fragment, { children: [
121
132
  triggerElement,
@@ -135,34 +146,42 @@ var HoverCard = React.forwardRef((props, ref) => {
135
146
  customAnimation: animation,
136
147
  slotProps: { paper: { style: { pointerEvents: "auto" } } },
137
148
  ...rest,
138
- children: /* @__PURE__ */ jsxs(Box, { ref: contentRef, sx: { position: "relative" }, children: [
139
- showArrow && /* @__PURE__ */ jsx(ArrowContainer, { placement, offset }),
140
- /* @__PURE__ */ jsx(
141
- StyledCard,
142
- {
143
- customVariant: variant,
144
- glow,
145
- pulse,
146
- animation,
147
- "data-testid": dataTestId || "hover-card-content",
148
- sx: (theme) => ({ maxWidth: capOf(theme, maxWidth), ...offsetMargin(theme, placement, offset) }),
149
- children: /* @__PURE__ */ jsx(CardContent, { sx: { p: padding, "&:last-child": { pb: padding } }, children: /* @__PURE__ */ jsx(
150
- HoverCardContent,
149
+ children: /* @__PURE__ */ jsxs(
150
+ Box,
151
+ {
152
+ ref: contentRef,
153
+ onPointerDownCapture: onContentPointerDownCapture,
154
+ sx: { position: "relative" },
155
+ children: [
156
+ showArrow && /* @__PURE__ */ jsx(ArrowContainer, { placement, offset }),
157
+ /* @__PURE__ */ jsx(
158
+ StyledCard,
151
159
  {
152
- loadingText,
153
- variant,
154
- title,
155
- description,
156
- avatar,
157
- loading,
158
- loadingComponent,
159
- dataTestId,
160
- children
160
+ customVariant: variant,
161
+ glow,
162
+ pulse,
163
+ animation,
164
+ "data-testid": dataTestId || "hover-card-content",
165
+ sx: (theme) => ({ maxWidth: capOf(theme, maxWidth), ...offsetMargin(theme, placement, offset) }),
166
+ children: /* @__PURE__ */ jsx(CardContent, { sx: { p: padding, "&:last-child": { pb: padding } }, children: /* @__PURE__ */ jsx(
167
+ HoverCardContent,
168
+ {
169
+ loadingText,
170
+ variant,
171
+ title,
172
+ description,
173
+ avatar,
174
+ loading,
175
+ loadingComponent,
176
+ dataTestId,
177
+ children
178
+ }
179
+ ) })
161
180
  }
162
- ) })
163
- }
164
- )
165
- ] })
181
+ )
182
+ ]
183
+ }
184
+ )
166
185
  }
167
186
  )
168
187
  ] });
@@ -172,4 +191,4 @@ HoverCard.displayName = "HoverCard";
172
191
  export {
173
192
  HoverCard
174
193
  };
175
- //# sourceMappingURL=chunk-Q534UJ34.js.map
194
+ //# sourceMappingURL=chunk-KN3WYEH2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/data-display/HoverCard/HoverCard.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport Card from '@mui/material/Card/index.js';\nimport CardContent from '@mui/material/CardContent/index.js';\nimport Popover from '@mui/material/Popover/index.js';\nimport { styled, type Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { rem } from '../../../tokens/relative';\nimport { withDefaults } from '../../../utils/withDefaults';\n\nimport { HoverCardContent } from './HoverCard.content';\nimport { useHoverCard } from './HoverCard.hooks';\nimport { arrowSx, cardSx, getAnchorOrigin, getTransformOrigin, sideOf } from './HoverCard.styles';\nimport type { CardStyleFlags } from './HoverCard.styles';\nimport type { HoverCardAnimation, HoverCardPlacement, HoverCardProps } from './HoverCard.types';\n\nconst ArrowContainer = styled('div')<{ placement: HoverCardPlacement; offset?: number }>(\n ({ theme, placement }) => ({ ...arrowSx(theme, placement) }),\n);\n\nconst StyledCard = styled(Card, {\n shouldForwardProp: (prop) =>\n !['customVariant', 'glow', 'pulse', 'animation'].includes(prop as string),\n})<CardStyleFlags>(({ theme, ...flags }) => ({ ...cardSx(theme, flags) }));\n\nconst StyledPopover = styled(Popover, {\n shouldForwardProp: (prop) => prop !== 'customAnimation',\n})<{ customAnimation?: HoverCardAnimation }>(({ customAnimation }) => ({\n // Only the card takes the pointer (its paper sets `pointer-events: auto`).\n // MUI's popover root and its invisible backdrop cover the whole viewport, so\n // while a card was open the pointer was \"on the card\" wherever it went: the\n // backdrop's mouseover reached the card's onMouseEnter and cancelled the\n // close the trigger's mouseleave had just scheduled. A card left by moving\n // the mouse away stayed open, and the page's next click hit the backdrop\n // instead of its target (FUT-2619). The backdrop was also the only\n // click-away; `useClickAway` in the hooks replaces it.\n pointerEvents: 'none',\n // The popover only positions; StyledCard draws the surface.\n '& .MuiPopover-paper': {\n backgroundColor: 'transparent',\n boxShadow: 'none',\n overflow: 'visible',\n ...(customAnimation === 'scale' && { transformOrigin: 'center' }),\n },\n}));\n\nconst DEFAULTS = {\n variant: 'default',\n glow: false,\n pulse: false,\n placement: 'bottom',\n showArrow: false,\n animation: 'fade',\n enterDelay: 700,\n exitDelay: 0,\n loading: false,\n touchEnabled: true,\n disabled: false,\n} satisfies Partial<HoverCardProps>;\n\ntype ResolvedProps = HoverCardProps & Required<Pick<HoverCardProps, keyof typeof DEFAULTS>>;\n\n/** Padding is the variant's own: detailed roomier, minimal tighter. */\nconst CONTENT_PADDING: Record<string, number> = { detailed: 3, minimal: 1.5 };\n\n/**\n * The card is nudged off the anchor on whichever side it sits — `offset` design\n * px (8 unless the caller says otherwise), through the theme's type scale.\n */\nconst offsetMargin = (theme: Theme, placement: HoverCardPlacement, offset: number | undefined) => {\n const side = sideOf(placement);\n const key = ({ top: 'mt', bottom: 'mb', left: 'ml', right: 'mr' } as const)[side];\n return { [key]: rem(theme, offset ?? 8) };\n};\n\n/**\n * The card's cap: 400 design px unless the caller says otherwise. `sx` has always\n * read a number of 1 or less as a fraction of the parent, and that stays so.\n */\nconst capOf = (theme: Theme, maxWidth: number | undefined): string => {\n const cap = maxWidth ?? 400;\n return cap <= 1 && cap !== 0 ? `${cap * 100}%` : rem(theme, cap);\n};\n\n/** The clone-onto-`trigger` vs. wrap-in-a-`span` choice, pulled out of the component so its body stays readable. */\nconst buildTriggerElement = (\n trigger: HoverCardProps['trigger'],\n triggerHandlers: ReturnType<typeof useHoverCard>['triggerHandlers'],\n disabled: boolean,\n dataTestId: string | undefined,\n children: React.ReactNode,\n) => {\n const triggerProps = {\n ...triggerHandlers,\n style: { cursor: disabled ? 'default' : 'pointer' },\n 'data-testid': dataTestId ? `${dataTestId}-trigger` : 'hover-card-trigger',\n };\n\n return trigger ? (\n React.cloneElement(trigger, triggerProps)\n ) : (\n <span {...triggerProps} style={{ display: 'inline-block', ...triggerProps.style }}>\n {children}\n </span>\n );\n};\n\nexport const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props, ref) => {\n const {\n variant, glow, pulse, title, description, avatar, trigger, placement, showArrow,\n animation, enterDelay, exitDelay, maxWidth, loading, loadingComponent, loadingText, touchEnabled,\n offset, disabled, onOpen, onClose, children, dataTestId,\n ...rest\n } = withDefaults(props, DEFAULTS) as ResolvedProps;\n\n const {\n anchorEl,\n isOpen,\n handleClose,\n triggerHandlers,\n cardHandlers,\n contentRef,\n onContentPointerDownCapture,\n } = useHoverCard({\n disabled,\n touchEnabled,\n enterDelay,\n exitDelay,\n onOpen,\n onClose,\n });\n\n const triggerElement = buildTriggerElement(trigger, triggerHandlers, disabled, dataTestId, children);\n const padding = CONTENT_PADDING[variant] ?? 2;\n\n return (\n <>\n {triggerElement}\n <StyledPopover\n ref={ref}\n open={isOpen && !disabled}\n anchorEl={anchorEl}\n onClose={handleClose}\n anchorOrigin={getAnchorOrigin(placement)}\n transformOrigin={getTransformOrigin(placement)}\n {...cardHandlers}\n disableAutoFocus\n disableEnforceFocus\n disableRestoreFocus\n customAnimation={animation}\n slotProps={{ paper: { style: { pointerEvents: 'auto' } } }}\n {...rest}\n >\n <Box\n ref={contentRef}\n onPointerDownCapture={onContentPointerDownCapture}\n sx={{ position: 'relative' }}\n >\n {showArrow && <ArrowContainer placement={placement} offset={offset} />}\n <StyledCard\n customVariant={variant}\n glow={glow}\n pulse={pulse}\n animation={animation}\n data-testid={dataTestId || 'hover-card-content'}\n sx={(theme) => ({ maxWidth: capOf(theme, maxWidth), ...offsetMargin(theme, placement, offset) })}\n >\n <CardContent sx={{ p: padding, '&:last-child': { pb: padding } }}>\n <HoverCardContent\n loadingText={loadingText} variant={variant} title={title}\n description={description}\n avatar={avatar}\n loading={loading}\n loadingComponent={loadingComponent}\n dataTestId={dataTestId}\n >\n {children}\n </HoverCardContent>\n </CardContent>\n </StyledCard>\n </Box>\n </StyledPopover>\n </>\n );\n});\n\nHoverCard.displayName = 'HoverCard';\n"],"mappings":"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","names":[]}
@@ -49,8 +49,14 @@ var DIALOG_STATIC_PROPS = {
49
49
  // second, non-modal `role="dialog"` this component sets of its own accord
50
50
  // — duplicating (and, as a static English literal, contradicting) the name
51
51
  // the Paper already carries.
52
+ //
53
+ // For the same reason, `role` is deliberately NOT set here (FUT-2861):
54
+ // `Dialog` destructures `aria-labelledby` and `aria-modal` by name and
55
+ // applies them to the Paper alongside its own hard-coded `role="dialog"`,
56
+ // but `role` itself is not one of the props it destructures — a `role`
57
+ // passed here falls into `...other` and lands a second, unlabelled
58
+ // `role="dialog"` on the outer Modal root instead of the Paper.
52
59
  "aria-labelledby": "lightbox-title",
53
- role: "dialog",
54
60
  "aria-modal": "true"
55
61
  };
56
62
  var Lightbox = React.forwardRef((componentProps, ref) => {
@@ -142,4 +148,4 @@ export {
142
148
  Lightbox,
143
149
  Lightbox_default
144
150
  };
145
- //# sourceMappingURL=chunk-IIXMTC65.js.map
151
+ //# sourceMappingURL=chunk-SPZAEMGW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/data-display/Lightbox/Lightbox.tsx"],"sourcesContent":["import Dialog from '@mui/material/Dialog/index.js';\nimport Fade from '@mui/material/Fade/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport type { Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { makeTestId } from './Lightbox.constants';\nimport { resolveLightboxProps } from './Lightbox.helpers';\nimport { LightboxCopyProvider } from './lightbox-copy-context';\nimport type { LightboxProps, LightboxRef } from './Lightbox.types';\nimport { LightboxCurrentMedia } from './LightboxCurrentMedia';\nimport { LightboxOverlay } from './LightboxOverlay';\nimport { LightboxStage } from './LightboxStage';\nimport { LightboxThumbnails } from './LightboxThumbnails';\nimport { useLightbox } from './useLightbox';\nimport { scrim } from '../../../tokens/ink';\nimport { rem, sxRem } from '../../../tokens/relative';\n\nconst DIALOG_STATIC_PROPS = {\n maxWidth: false,\n fullScreen: true,\n TransitionComponent: Fade,\n TransitionProps: { timeout: 300 },\n PaperProps: { sx: { background: (theme: Theme) => scrim(theme, 0.9), backdropFilter: (theme: Theme) => `blur(${rem(theme, 2)})` } },\n // `aria-labelledby` is the one accessible name for the modal itself: MUI\n // forwards it (with `aria-modal` and its own `role=\"dialog\"`) onto the\n // Paper, the element that is actually the WAI-ARIA dialog. A plain\n // `aria-label` alongside it would land on the OUTER root instead — a\n // second, non-modal `role=\"dialog\"` this component sets of its own accord\n // — duplicating (and, as a static English literal, contradicting) the name\n // the Paper already carries.\n //\n // For the same reason, `role` is deliberately NOT set here (FUT-2861):\n // `Dialog` destructures `aria-labelledby` and `aria-modal` by name and\n // applies them to the Paper alongside its own hard-coded `role=\"dialog\"`,\n // but `role` itself is not one of the props it destructures — a `role`\n // passed here falls into `...other` and lands a second, unlabelled\n // `role=\"dialog\"` on the outer Modal root instead of the Paper.\n 'aria-labelledby': 'lightbox-title',\n 'aria-modal': 'true',\n} as const;\n\nexport const Lightbox = React.forwardRef<LightboxRef, LightboxProps>((componentProps, ref) => {\n const props = resolveLightboxProps(componentProps);\n const { isOpen, items, loop, autoplay, showControls, showCaptions, thumbnails, zoomable } = props;\n const { className, style, dataTestId } = props;\n\n const testId = makeTestId(dataTestId);\n const lightbox = useLightbox(props, ref);\n const { currentIndex, currentItem, zoom } = lightbox;\n\n return (\n <LightboxCopyProvider copy={props.copy}>\n <Dialog\n open={isOpen}\n onClose={lightbox.close}\n {...DIALOG_STATIC_PROPS}\n className={className}\n style={style}\n onTouchStart={lightbox.handleTouchStart}\n onTouchEnd={lightbox.handleTouchEnd}\n data-testid={dataTestId || 'lightbox'}\n >\n {/* Visually hidden title for screen readers */}\n <Typography id=\"lightbox-title\" variant=\"h6\" sx={{ position: 'absolute', left: sxRem(-10000) }}>\n {props.copy.dialogLabel} - {currentItem?.alt || props.copy.itemPosition(currentIndex + 1, items.length)}\n </Typography>\n\n <LightboxOverlay\n testId={testId}\n itemCount={items.length}\n currentIndex={currentIndex}\n showControls={showControls}\n loop={loop}\n autoplay={autoplay}\n canZoom={lightbox.canZoom}\n isAutoPlaying={lightbox.isAutoPlaying}\n onClose={lightbox.close}\n onPrev={lightbox.prev}\n onNext={lightbox.next}\n onZoomIn={zoom.zoomIn}\n onZoomOut={zoom.zoomOut}\n onResetZoom={zoom.resetZoom}\n onToggleAutoplay={() => lightbox.setIsAutoPlaying((playing) => !playing)}\n />\n\n <LightboxStage\n items={items}\n currentItem={currentItem}\n currentIndex={currentIndex}\n isLoading={lightbox.isLoading}\n showCaptions={showCaptions}\n thumbnails={thumbnails}\n testId={testId}\n media={\n <LightboxCurrentMedia\n item={currentItem}\n index={currentIndex}\n total={items.length}\n zoomable={zoomable}\n zoom={zoom}\n testId={testId}\n onLoadingChange={lightbox.setIsLoading}\n onWheel={lightbox.handleWheel}\n />\n }\n thumbnailStrip={\n thumbnails &&\n items.length > 1 && (\n <LightboxThumbnails\n items={items}\n currentIndex={currentIndex}\n testId={testId}\n onSelect={lightbox.goToIndex}\n />\n )\n }\n />\n </Dialog>\n </LightboxCopyProvider>\n );\n});\n\nLightbox.displayName = 'Lightbox';\n\nexport default Lightbox;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,OAAO,gBAAgB;AAEvB,OAAO,WAAW;AA4DZ,SAIA,KAJA;AA9CN,IAAM,sBAAsB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,qBAAqB;AAAA,EACrB,iBAAiB,EAAE,SAAS,IAAI;AAAA,EAChC,YAAY,EAAE,IAAI,EAAE,YAAY,CAAC,UAAiB,MAAM,OAAO,GAAG,GAAG,gBAAgB,CAAC,UAAiB,QAAQ,IAAI,OAAO,CAAC,CAAC,IAAI,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAelI,mBAAmB;AAAA,EACnB,cAAc;AAChB;AAEO,IAAM,WAAW,MAAM,WAAuC,CAAC,gBAAgB,QAAQ;AAC5F,QAAM,QAAQ,qBAAqB,cAAc;AACjD,QAAM,EAAE,QAAQ,OAAO,MAAM,UAAU,cAAc,cAAc,YAAY,SAAS,IAAI;AAC5F,QAAM,EAAE,WAAW,OAAO,WAAW,IAAI;AAEzC,QAAM,SAAS,WAAW,UAAU;AACpC,QAAM,WAAW,YAAY,OAAO,GAAG;AACvC,QAAM,EAAE,cAAc,aAAa,KAAK,IAAI;AAE5C,SACE,oBAAC,wBAAqB,MAAM,MAAM,MAClC;AAAA,IAAC;AAAA;AAAA,MACC,MAAM;AAAA,MACN,SAAS,SAAS;AAAA,MACjB,GAAG;AAAA,MACJ;AAAA,MACA;AAAA,MACA,cAAc,SAAS;AAAA,MACvB,YAAY,SAAS;AAAA,MACrB,eAAa,cAAc;AAAA,MAG3B;AAAA,6BAAC,cAAW,IAAG,kBAAiB,SAAQ,MAAK,IAAI,EAAE,UAAU,YAAY,MAAM,MAAM,IAAM,EAAE,GAC1F;AAAA,gBAAM,KAAK;AAAA,UAAY;AAAA,UAAI,aAAa,OAAO,MAAM,KAAK,aAAa,eAAe,GAAG,MAAM,MAAM;AAAA,WACxG;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,WAAW,MAAM;AAAA,YACjB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS,SAAS;AAAA,YAClB,eAAe,SAAS;AAAA,YACxB,SAAS,SAAS;AAAA,YAClB,QAAQ,SAAS;AAAA,YACjB,QAAQ,SAAS;AAAA,YACjB,UAAU,KAAK;AAAA,YACf,WAAW,KAAK;AAAA,YAChB,aAAa,KAAK;AAAA,YAClB,kBAAkB,MAAM,SAAS,iBAAiB,CAAC,YAAY,CAAC,OAAO;AAAA;AAAA,QACzE;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA,WAAW,SAAS;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,YACA,OACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,OAAO,MAAM;AAAA,gBACb;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,iBAAiB,SAAS;AAAA,gBAC1B,SAAS,SAAS;AAAA;AAAA,YACpB;AAAA,YAEF,gBACE,cACA,MAAM,SAAS,KACb;AAAA,cAAC;AAAA;AAAA,gBACC;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,UAAU,SAAS;AAAA;AAAA,YACrB;AAAA;AAAA,QAGN;AAAA;AAAA;AAAA,EACF,GACA;AAEJ,CAAC;AAED,SAAS,cAAc;AAEvB,IAAO,mBAAQ;","names":[]}
@@ -61,18 +61,49 @@ var useEscapeKey = (active, onEscape) => {
61
61
  return () => document.removeEventListener("keydown", handleEscape);
62
62
  }, [active, onEscape]);
63
63
  };
64
- var useClickAway = (active, inside, onAway) => {
64
+ var useClickAway = (active, inside, ownsEvent, onAway) => {
65
+ const sessionRef = React.useRef(null);
66
+ const pendingRef = React.useRef(/* @__PURE__ */ new Set());
65
67
  React.useEffect(() => {
66
- if (!active) return;
68
+ if (!active) return void 0;
69
+ const session = {};
70
+ sessionRef.current = session;
67
71
  const handlePointerDown = (event) => {
68
- const target = event.target;
69
- if (target && inside().some((el) => el?.contains(target))) return;
70
- onAway();
72
+ const timeoutId = window.setTimeout(() => {
73
+ pendingRef.current.delete(timeoutId);
74
+ if (sessionRef.current !== session) return;
75
+ const target = event.target;
76
+ const ownedByReactTree = ownsEvent(event);
77
+ const ownedByDom = Boolean(target) && inside().some((el) => el?.contains(target));
78
+ if (ownedByReactTree || ownedByDom) return;
79
+ onAway();
80
+ }, 0);
81
+ pendingRef.current.add(timeoutId);
71
82
  };
72
83
  document.addEventListener("pointerdown", handlePointerDown, true);
73
- return () => document.removeEventListener("pointerdown", handlePointerDown, true);
74
- }, [active, inside, onAway]);
84
+ return () => {
85
+ sessionRef.current = null;
86
+ pendingRef.current.forEach((id) => window.clearTimeout(id));
87
+ pendingRef.current.clear();
88
+ document.removeEventListener("pointerdown", handlePointerDown, true);
89
+ };
90
+ }, [active, inside, ownsEvent, onAway]);
75
91
  };
92
+ var useCardOwnership = () => {
93
+ const ownedEventsRef = React.useRef(/* @__PURE__ */ new WeakSet());
94
+ const markInside = React.useCallback((event) => {
95
+ ownedEventsRef.current.add(event.nativeEvent);
96
+ }, []);
97
+ const ownsEvent = React.useCallback(
98
+ (event) => ownedEventsRef.current.has(event),
99
+ []
100
+ );
101
+ return { markInside, ownsEvent };
102
+ };
103
+ var useInsideCard = (anchorEl, contentRef) => React.useCallback(
104
+ () => [anchorEl, contentRef.current],
105
+ [anchorEl, contentRef]
106
+ );
76
107
  var useClearOnUnmount = (...refs) => {
77
108
  React.useEffect(
78
109
  () => () => refs.forEach(clearPending),
@@ -80,23 +111,11 @@ var useClearOnUnmount = (...refs) => {
80
111
  []
81
112
  );
82
113
  };
83
- var useHoverCard = ({
84
- disabled,
85
- touchEnabled,
86
- enterDelay,
87
- exitDelay,
88
- onOpen,
89
- onClose
90
- }) => {
114
+ var useCardVisibility = ({ disabled, enterDelay, exitDelay, onOpen, onClose }) => {
91
115
  const [anchorEl, setAnchorEl] = React.useState(null);
92
116
  const [isOpen, setIsOpen] = React.useState(false);
93
- const [isTouchDevice, setIsTouchDevice] = React.useState(false);
94
117
  const enterTimeoutRef = React.useRef(void 0);
95
118
  const exitTimeoutRef = React.useRef(void 0);
96
- const contentRef = React.useRef(null);
97
- React.useEffect(() => {
98
- setIsTouchDevice("ontouchstart" in window);
99
- }, []);
100
119
  const openCard = React.useCallback(
101
120
  (trigger) => {
102
121
  if (disabled) return;
@@ -126,6 +145,22 @@ var useHoverCard = ({
126
145
  setAnchorEl(null);
127
146
  onClose?.();
128
147
  }, [onClose]);
148
+ return { anchorEl, isOpen, openCard, closeCard, handleClose, enterTimeoutRef, exitTimeoutRef };
149
+ };
150
+ var useHoverCard = ({
151
+ disabled,
152
+ touchEnabled,
153
+ enterDelay,
154
+ exitDelay,
155
+ onOpen,
156
+ onClose
157
+ }) => {
158
+ const [isTouchDevice, setIsTouchDevice] = React.useState(false);
159
+ const contentRef = React.useRef(null);
160
+ const { anchorEl, isOpen, openCard, closeCard, handleClose, enterTimeoutRef, exitTimeoutRef } = useCardVisibility({ disabled, enterDelay, exitDelay, onOpen, onClose });
161
+ React.useEffect(() => {
162
+ setIsTouchDevice("ontouchstart" in window);
163
+ }, []);
129
164
  const { triggerHandlers, cardHandlers } = useHoverHandlers({
130
165
  isTouchDevice,
131
166
  touchEnabled,
@@ -134,14 +169,24 @@ var useHoverCard = ({
134
169
  closeCard,
135
170
  exitTimeoutRef
136
171
  });
137
- const insideCard = React.useCallback(() => [anchorEl, contentRef.current], [anchorEl]);
172
+ const insideCard = useInsideCard(anchorEl, contentRef);
173
+ const { markInside, ownsEvent } = useCardOwnership();
138
174
  useEscapeKey(isOpen, handleClose);
139
- useClickAway(isOpen, insideCard, handleClose);
175
+ useClickAway(isOpen, insideCard, ownsEvent, handleClose);
140
176
  useClearOnUnmount(enterTimeoutRef, exitTimeoutRef);
141
- return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef };
177
+ return {
178
+ anchorEl,
179
+ isOpen,
180
+ handleClose,
181
+ triggerHandlers,
182
+ cardHandlers,
183
+ contentRef,
184
+ /** Wire onto the card's content root as `onPointerDownCapture` (FUT-2776). */
185
+ onContentPointerDownCapture: markInside
186
+ };
142
187
  };
143
188
 
144
189
  export {
145
190
  useHoverCard
146
191
  };
147
- //# sourceMappingURL=chunk-VNWZWQF3.js.map
192
+ //# sourceMappingURL=chunk-Z3USLQQT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/data-display/HoverCard/HoverCard.hooks.ts"],"sourcesContent":["import React from 'react';\n\ntype TimerRef = React.MutableRefObject<number | undefined>;\n\n/**\n * Cancel the timer a ref holds, and forget it. Every place that schedules\n * or dismisses goes through this, so no timer outlives the ref that could\n * cancel it (FUT-2619: an overwritten enter timer reopened a dismissed card).\n */\nconst clearPending = (ref: TimerRef) => {\n if (ref.current !== undefined) window.clearTimeout(ref.current);\n ref.current = undefined;\n};\n\ninterface HandlerInput {\n isTouchDevice: boolean;\n touchEnabled: boolean;\n enterDelay: number;\n openCard: (trigger: HTMLElement) => void;\n closeCard: () => void;\n exitTimeoutRef: TimerRef;\n}\n\n/**\n * The pointer and touch handlers, over the open/close pair. Touch gets a\n * long-press because there is no hover to respond to; the card's own handlers\n * keep it open while the pointer is travelling onto it.\n */\nconst useHoverHandlers = ({\n isTouchDevice,\n touchEnabled,\n enterDelay,\n openCard,\n closeCard,\n exitTimeoutRef,\n}: HandlerInput) => {\n const [touchTimeout, setTouchTimeout] = React.useState<number>();\n\n /** A touch device with touch disabled has no hover to respond to. */\n const hoverIgnored = isTouchDevice && !touchEnabled;\n\n const cancelTouch = React.useCallback(() => {\n if (touchTimeout) {\n window.clearTimeout(touchTimeout);\n setTouchTimeout(undefined);\n }\n }, [touchTimeout]);\n\n React.useEffect(\n () => () => {\n if (touchTimeout) window.clearTimeout(touchTimeout);\n },\n [touchTimeout],\n );\n\n const triggerHandlers = {\n onMouseEnter: (event: React.MouseEvent<HTMLElement>) => {\n if (hoverIgnored) return;\n openCard(event.currentTarget);\n },\n onMouseLeave: () => {\n if (hoverIgnored) return;\n closeCard();\n },\n onTouchStart: (event: React.TouchEvent<HTMLElement>) => {\n if (!touchEnabled) return;\n if (touchTimeout) window.clearTimeout(touchTimeout);\n // Read NOW: React nulls `currentTarget` once the handler returns, so\n // the long-press timer used to open the card with no anchor at all.\n const trigger = event.currentTarget;\n setTouchTimeout(window.setTimeout(() => openCard(trigger), enterDelay));\n },\n // End and cancel do the same thing: the press did not become a long press.\n onTouchEnd: cancelTouch,\n onTouchCancel: cancelTouch,\n };\n\n /** Moving onto the card keeps it open; leaving it starts the exit delay. */\n const cardHandlers = {\n onMouseEnter: () => clearPending(exitTimeoutRef),\n onMouseLeave: closeCard,\n };\n\n return { triggerHandlers, cardHandlers };\n};\n\n/** Escape dismisses the card while it is open. */\nconst useEscapeKey = (active: boolean, onEscape: () => void) => {\n React.useEffect(() => {\n if (!active) return;\n\n const handleEscape = (event: globalThis.KeyboardEvent) => {\n if (event.key === 'Escape') onEscape();\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [active, onEscape]);\n};\n\n/**\n * A press anywhere outside the trigger and the card dismisses it.\n *\n * Touch has no mouse-leave, and MUI's own click-away was the popover's\n * backdrop, which `pointer-events: none` on the popover root (so the page\n * under an open card stays usable) takes out of play. So this listens on the\n * document itself, in the CAPTURE phase, where a `stopPropagation` further in\n * — including one a nested control (or something entirely unrelated to the\n * card) calls on its OWN bubble — cannot swallow it (FUT-2776 adversarial\n * review: a bubble-phase listener here was tried and reverted; see below).\n * It only observes: no `preventDefault`, so the tap or click still reaches\n * whatever it landed on. Attached while open, removed on close and on\n * unmount.\n *\n * That capture placement is exactly what makes the decision impossible to\n * make SYNCHRONOUSLY: `document` is the outermost node on the capture path,\n * so this handler fires before React's own capture dispatch even starts —\n * before `useCardOwnership`'s marker on the card's content root (also\n * capture, but on a node further down the same tree) has had a chance to run\n * for this same event. So the decision is deferred — but NOT to a microtask:\n * a microtask here reads the marker before it is set for TRUSTED input (a\n * real press, or Playwright's `page.mouse`), which is the very bug this hook\n * exists to fix (FUT-2776, third-review adversarial probe, measured with\n * real Chromium and `page.mouse.down()`):\n *\n * TRUSTED: doc-capture-sync → doc-capture-microtask (marker unset) → inner-capture (sets marker)\n * SCRIPT: doc-capture-sync → inner-capture (sets marker) → doc-capture-microtask (marker set)\n *\n * Chromium runs any microtasks queued by a capture-phase listener BEFORE\n * dispatching to the next listener on the path when the event is trusted —\n * so a microtask queued here runs before `useCardOwnership`'s capture\n * listener further down the tree ever gets a turn, and the ownership check\n * always reads empty. A script-dispatched event (`fireEvent`, `userEvent` in\n * jsdom, or in a Storybook `play` function) instead completes its WHOLE\n * capture-then-bubble dispatch before any microtask runs — which is why\n * every test under the microtask version passed and the bug still shipped.\n * A macrotask (`setTimeout(0)`) is not part of the event dispatch under\n * EITHER kind of input, so it always runs after the whole dispatch — marker\n * included — regardless of trusted vs. script. That ordering is what this\n * hook now relies on.\n *\n * `sessionRef` replaces a plain boolean: it is a fresh object assigned each\n * time the card opens (this effect's mount), so a `setTimeout` callback\n * queued for a press during one open session can tell whether it is still\n * that SAME session by comparing against the ref, not just whether the card\n * happens to be open again — the card closing and reopening while a stale\n * timeout is still pending must not let that timeout act on the new session.\n * Every scheduled timeout is also tracked so a close or unmount can cancel\n * whatever is still pending, rather than letting it fire against a card that\n * is no longer there to close.\n */\nconst useClickAway = (\n active: boolean,\n inside: () => ReadonlyArray<Element | null>,\n ownsEvent: (event: Event) => boolean,\n onAway: () => void,\n) => {\n const sessionRef = React.useRef<object | null>(null);\n const pendingRef = React.useRef<Set<number>>(new Set());\n\n React.useEffect(() => {\n if (!active) return undefined;\n\n const session = {};\n sessionRef.current = session;\n\n const handlePointerDown = (event: globalThis.PointerEvent) => {\n const timeoutId = window.setTimeout(() => {\n pendingRef.current.delete(timeoutId);\n // Stale: this session closed (or a newer one opened) before this\n // press's decision could run.\n if (sessionRef.current !== session) return;\n\n const target = event.target as Node | null;\n const ownedByReactTree = ownsEvent(event);\n const ownedByDom = Boolean(target) && inside().some((el) => el?.contains(target));\n if (ownedByReactTree || ownedByDom) return;\n onAway();\n }, 0);\n pendingRef.current.add(timeoutId);\n };\n\n document.addEventListener('pointerdown', handlePointerDown, true);\n return () => {\n sessionRef.current = null;\n pendingRef.current.forEach((id) => window.clearTimeout(id));\n pendingRef.current.clear();\n document.removeEventListener('pointerdown', handlePointerDown, true);\n };\n }, [active, inside, ownsEvent, onAway]);\n};\n\n/**\n * Whether the pointerdown `useClickAway` is about to look at started inside\n * the card's OWN React tree — a MUI `Select`'s menu, a nested `Popover`, a\n * `DropdownMenu` the card's `content` renders — even when that control's DOM\n * node is not a descendant of the card's box at all, because it portalled\n * itself out near `document.body` (FUT-2776).\n *\n * React re-parents a portal's events to bubble AND capture through its OWNING\n * React tree, not the DOM tree it renders into (see the React docs on\n * `createPortal`), so an `onPointerDownCapture` on the card's own content root\n * still fires for a press inside a portal the card's content renders, and\n * NEVER fires for a press inside an unrelated one mounted by something that\n * is not a descendant of the card in the REACT tree — ownership, not timing.\n * That is the whole fix: the ORIGINAL approach (a `MutationObserver` on\n * `document.body`) treated ANY element appended to the body while the card\n * was open as inside, so an unrelated Snackbar, dev overlay or Popover that\n * happened to mount at the same time stopped closing the card too.\n *\n * Tracked per NATIVE EVENT OBJECT in a `WeakSet`, not a boolean flag: a\n * boolean stayed `true` forever once a press set it whose NATIVE propagation\n * then got stopped before reaching `useClickAway`'s own (bubble-phase, in\n * that earlier revision) listener — common for anything that treats the\n * press as its own gesture — so the marker never got consumed, and the\n * NEXT, genuinely outside press read a STALE `true` and stayed open\n * (FUT-2776, caught on adversarial review of the first fix here). Keying on\n * the event itself instead needs no consuming step and cannot go stale: a\n * `WeakSet` entry only ever answers for the ONE dispatch that created it,\n * and nothing outside this closure keeps a reference to a past pointerdown\n * once `useClickAway`'s deferred (`setTimeout(0)`, not a microtask — see that\n * hook for why) check for it has run, so the entry is simply garbage from\n * then on.\n */\nconst useCardOwnership = () => {\n const ownedEventsRef = React.useRef<WeakSet<Event>>(new WeakSet());\n\n const markInside = React.useCallback((event: React.PointerEvent<HTMLElement>) => {\n ownedEventsRef.current.add(event.nativeEvent);\n }, []);\n\n const ownsEvent = React.useCallback(\n (event: Event) => ownedEventsRef.current.has(event),\n [],\n );\n\n return { markInside, ownsEvent };\n};\n\n/** The trigger and the card's own box — a plain DOM containment check. */\nconst useInsideCard = (\n anchorEl: HTMLElement | null,\n contentRef: React.MutableRefObject<HTMLDivElement | null>,\n) =>\n React.useCallback(\n () => [anchorEl, contentRef.current],\n [anchorEl, contentRef],\n );\n\n/** Whatever is still pending when the card unmounts is cancelled with it. */\nconst useClearOnUnmount = (...refs: TimerRef[]) => {\n React.useEffect(\n () => () => refs.forEach(clearPending),\n // The refs are stable objects: a mount/unmount pair, as before.\n [],\n );\n};\n\ninterface HoverCardTimingInput {\n disabled: boolean;\n touchEnabled: boolean;\n enterDelay: number;\n exitDelay: number;\n onOpen?: () => void;\n onClose?: () => void;\n}\n\ntype VisibilityInput = Omit<HoverCardTimingInput, 'touchEnabled'>;\n\n/**\n * The open/close state and its two timers, split out of `useHoverCard` so\n * that composing it with click-away, escape and ownership reads as one flat\n * list rather than one long function.\n *\n * The enter delay before opening and the exit delay before closing (so the\n * pointer can travel from the trigger onto the card without dismissing it)\n * each cancel the other's pending work, which is why they live together.\n */\nconst useCardVisibility = ({ disabled, enterDelay, exitDelay, onOpen, onClose }: VisibilityInput) => {\n const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);\n const [isOpen, setIsOpen] = React.useState(false);\n const enterTimeoutRef = React.useRef<number | undefined>(undefined);\n const exitTimeoutRef = React.useRef<number | undefined>(undefined);\n\n const openCard = React.useCallback(\n (trigger: HTMLElement) => {\n if (disabled) return;\n\n clearPending(exitTimeoutRef);\n // A second enter before the delay ran out (a click and then a hover, or\n // the pointer jittering across the edge) must REPLACE the pending open,\n // not add one: an overwritten timer can no longer be cancelled, and it\n // reopened the card after Escape had closed it.\n clearPending(enterTimeoutRef);\n\n enterTimeoutRef.current = window.setTimeout(() => {\n setAnchorEl(trigger);\n setIsOpen(true);\n onOpen?.();\n }, enterDelay);\n },\n [disabled, enterDelay, onOpen],\n );\n\n const closeCard = React.useCallback(() => {\n clearPending(enterTimeoutRef);\n clearPending(exitTimeoutRef);\n\n exitTimeoutRef.current = window.setTimeout(() => {\n setIsOpen(false);\n setAnchorEl(null);\n onClose?.();\n }, exitDelay);\n }, [exitDelay, onClose]);\n\n // Escape and the popover's own close are FINAL: nothing still pending may\n // reopen the card behind them. It opens again on the next enter.\n const handleClose = React.useCallback(() => {\n clearPending(enterTimeoutRef);\n clearPending(exitTimeoutRef);\n setIsOpen(false);\n setAnchorEl(null);\n onClose?.();\n }, [onClose]);\n\n return { anchorEl, isOpen, openCard, closeCard, handleClose, enterTimeoutRef, exitTimeoutRef };\n};\n\nexport const useHoverCard = ({\n disabled,\n touchEnabled,\n enterDelay,\n exitDelay,\n onOpen,\n onClose,\n}: HoverCardTimingInput) => {\n const [isTouchDevice, setIsTouchDevice] = React.useState(false);\n /** The card's own box, inside the popover paper: a press here is not \"away\". */\n const contentRef = React.useRef<HTMLDivElement | null>(null);\n\n const { anchorEl, isOpen, openCard, closeCard, handleClose, enterTimeoutRef, exitTimeoutRef } =\n useCardVisibility({ disabled, enterDelay, exitDelay, onOpen, onClose });\n\n // Probed in an effect, not during render, so SSR and hydration stay intact.\n React.useEffect(() => {\n setIsTouchDevice('ontouchstart' in window);\n }, []);\n\n const { triggerHandlers, cardHandlers } = useHoverHandlers({\n isTouchDevice,\n touchEnabled,\n enterDelay,\n openCard,\n closeCard,\n exitTimeoutRef,\n });\n\n const insideCard = useInsideCard(anchorEl, contentRef);\n const { markInside, ownsEvent } = useCardOwnership();\n\n useEscapeKey(isOpen, handleClose);\n useClickAway(isOpen, insideCard, ownsEvent, handleClose);\n useClearOnUnmount(enterTimeoutRef, exitTimeoutRef);\n\n return {\n anchorEl,\n isOpen,\n handleClose,\n triggerHandlers,\n cardHandlers,\n contentRef,\n /** Wire onto the card's content root as `onPointerDownCapture` (FUT-2776). */\n onContentPointerDownCapture: markInside,\n };\n};\n"],"mappings":"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","names":[]}
@@ -1,11 +1,11 @@
1
1
  import "../chunk-6OS4AKWT.js";
2
2
  import {
3
3
  HoverCard
4
- } from "../chunk-Q534UJ34.js";
4
+ } from "../chunk-KN3WYEH2.js";
5
5
  import "../chunk-NRIV2CQ2.js";
6
6
  import "../chunk-BSYPPO2I.js";
7
7
  import "../chunk-ZHV7TCAC.js";
8
- import "../chunk-VNWZWQF3.js";
8
+ import "../chunk-Z3USLQQT.js";
9
9
  import "../chunk-HPK33ACY.js";
10
10
  import "../chunk-FQ6UW7IY.js";
11
11
  import "../chunk-6JAIFIPW.js";
@@ -1,7 +1,7 @@
1
1
  import "../chunk-UM35GGCS.js";
2
2
  import {
3
3
  Lightbox_default
4
- } from "../chunk-IIXMTC65.js";
4
+ } from "../chunk-SPZAEMGW.js";
5
5
  import "../chunk-UOS7DEQZ.js";
6
6
  import "../chunk-HJPPNVCF.js";
7
7
  import "../chunk-M6PWPEAV.js";
@@ -1 +1 @@
1
- {"version":3,"file":"HoverCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/HoverCard/HoverCard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAS1B,OAAO,KAAK,EAA0C,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAsEhG,eAAO,MAAM,SAAS,oGA8EpB,CAAC"}
1
+ {"version":3,"file":"HoverCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/HoverCard/HoverCard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAS1B,OAAO,KAAK,EAA0C,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA6FhG,eAAO,MAAM,SAAS,oGA6EpB,CAAC"}
@@ -7,14 +7,6 @@ interface HoverCardTimingInput {
7
7
  onOpen?: () => void;
8
8
  onClose?: () => void;
9
9
  }
10
- /**
11
- * When the card is open, and the delays that decide it.
12
- *
13
- * Three timers are in play: the enter delay before opening, the exit delay
14
- * before closing (so the pointer can travel from the trigger onto the card
15
- * without dismissing it), and the long-press timer on touch. Each cancels the
16
- * others' pending work, which is why they live together.
17
- */
18
10
  export declare const useHoverCard: ({ disabled, touchEnabled, enterDelay, exitDelay, onOpen, onClose, }: HoverCardTimingInput) => {
19
11
  anchorEl: HTMLElement | null;
20
12
  isOpen: boolean;
@@ -31,6 +23,8 @@ export declare const useHoverCard: ({ disabled, touchEnabled, enterDelay, exitDe
31
23
  onMouseLeave: () => void;
32
24
  };
33
25
  contentRef: React.RefObject<HTMLDivElement | null>;
26
+ /** Wire onto the card's content root as `onPointerDownCapture` (FUT-2776). */
27
+ onContentPointerDownCapture: (event: React.PointerEvent<HTMLElement>) => void;
34
28
  };
35
29
  export {};
36
30
  //# sourceMappingURL=HoverCard.hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HoverCard.hooks.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/HoverCard/HoverCard.hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AA2I1B,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,qEAO1B,oBAAoB;;;;;8BA3GG,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;;8BAQ7B,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;;;;;;4BA7CpC,IAAI;;;CAuNtB,CAAC"}
1
+ {"version":3,"file":"HoverCard.hooks.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/HoverCard/HoverCard.hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAkQ1B,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AA+DD,eAAO,MAAM,YAAY,GAAI,qEAO1B,oBAAoB;;;;;8BAvRG,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;;8BAQ7B,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;;;;;;4BA7CpC,IAAI;;;IAgWnB,8EAA8E;yCAhJnC,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC;CAmJ7E,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Lightbox.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/Lightbox/Lightbox.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA2BnE,eAAO,MAAM,QAAQ,mFA+EnB,CAAC;AAIH,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Lightbox.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/Lightbox/Lightbox.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAiCnE,eAAO,MAAM,QAAQ,mFA+EnB,CAAC;AAIH,eAAe,QAAQ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.45.0",
3
+ "version": "6.45.2",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@10.34.5",
6
6
  "type": "module",
@@ -105,29 +105,148 @@ const useEscapeKey = (active: boolean, onEscape: () => void) => {
105
105
  * backdrop, which `pointer-events: none` on the popover root (so the page
106
106
  * under an open card stays usable) takes out of play. So this listens on the
107
107
  * document itself, in the CAPTURE phase, where a `stopPropagation` further in
108
- * cannot swallow it. It only observes: no `preventDefault`, so the tap or
109
- * click still reaches whatever it landed on. Attached while open, removed on
110
- * close and on unmount.
108
+ * — including one a nested control (or something entirely unrelated to the
109
+ * card) calls on its OWN bubble — cannot swallow it (FUT-2776 adversarial
110
+ * review: a bubble-phase listener here was tried and reverted; see below).
111
+ * It only observes: no `preventDefault`, so the tap or click still reaches
112
+ * whatever it landed on. Attached while open, removed on close and on
113
+ * unmount.
114
+ *
115
+ * That capture placement is exactly what makes the decision impossible to
116
+ * make SYNCHRONOUSLY: `document` is the outermost node on the capture path,
117
+ * so this handler fires before React's own capture dispatch even starts —
118
+ * before `useCardOwnership`'s marker on the card's content root (also
119
+ * capture, but on a node further down the same tree) has had a chance to run
120
+ * for this same event. So the decision is deferred — but NOT to a microtask:
121
+ * a microtask here reads the marker before it is set for TRUSTED input (a
122
+ * real press, or Playwright's `page.mouse`), which is the very bug this hook
123
+ * exists to fix (FUT-2776, third-review adversarial probe, measured with
124
+ * real Chromium and `page.mouse.down()`):
125
+ *
126
+ * TRUSTED: doc-capture-sync → doc-capture-microtask (marker unset) → inner-capture (sets marker)
127
+ * SCRIPT: doc-capture-sync → inner-capture (sets marker) → doc-capture-microtask (marker set)
128
+ *
129
+ * Chromium runs any microtasks queued by a capture-phase listener BEFORE
130
+ * dispatching to the next listener on the path when the event is trusted —
131
+ * so a microtask queued here runs before `useCardOwnership`'s capture
132
+ * listener further down the tree ever gets a turn, and the ownership check
133
+ * always reads empty. A script-dispatched event (`fireEvent`, `userEvent` in
134
+ * jsdom, or in a Storybook `play` function) instead completes its WHOLE
135
+ * capture-then-bubble dispatch before any microtask runs — which is why
136
+ * every test under the microtask version passed and the bug still shipped.
137
+ * A macrotask (`setTimeout(0)`) is not part of the event dispatch under
138
+ * EITHER kind of input, so it always runs after the whole dispatch — marker
139
+ * included — regardless of trusted vs. script. That ordering is what this
140
+ * hook now relies on.
141
+ *
142
+ * `sessionRef` replaces a plain boolean: it is a fresh object assigned each
143
+ * time the card opens (this effect's mount), so a `setTimeout` callback
144
+ * queued for a press during one open session can tell whether it is still
145
+ * that SAME session by comparing against the ref, not just whether the card
146
+ * happens to be open again — the card closing and reopening while a stale
147
+ * timeout is still pending must not let that timeout act on the new session.
148
+ * Every scheduled timeout is also tracked so a close or unmount can cancel
149
+ * whatever is still pending, rather than letting it fire against a card that
150
+ * is no longer there to close.
111
151
  */
112
152
  const useClickAway = (
113
153
  active: boolean,
114
154
  inside: () => ReadonlyArray<Element | null>,
155
+ ownsEvent: (event: Event) => boolean,
115
156
  onAway: () => void,
116
157
  ) => {
158
+ const sessionRef = React.useRef<object | null>(null);
159
+ const pendingRef = React.useRef<Set<number>>(new Set());
160
+
117
161
  React.useEffect(() => {
118
- if (!active) return;
162
+ if (!active) return undefined;
163
+
164
+ const session = {};
165
+ sessionRef.current = session;
119
166
 
120
167
  const handlePointerDown = (event: globalThis.PointerEvent) => {
121
- const target = event.target as Node | null;
122
- if (target && inside().some((el) => el?.contains(target))) return;
123
- onAway();
168
+ const timeoutId = window.setTimeout(() => {
169
+ pendingRef.current.delete(timeoutId);
170
+ // Stale: this session closed (or a newer one opened) before this
171
+ // press's decision could run.
172
+ if (sessionRef.current !== session) return;
173
+
174
+ const target = event.target as Node | null;
175
+ const ownedByReactTree = ownsEvent(event);
176
+ const ownedByDom = Boolean(target) && inside().some((el) => el?.contains(target));
177
+ if (ownedByReactTree || ownedByDom) return;
178
+ onAway();
179
+ }, 0);
180
+ pendingRef.current.add(timeoutId);
124
181
  };
125
182
 
126
183
  document.addEventListener('pointerdown', handlePointerDown, true);
127
- return () => document.removeEventListener('pointerdown', handlePointerDown, true);
128
- }, [active, inside, onAway]);
184
+ return () => {
185
+ sessionRef.current = null;
186
+ pendingRef.current.forEach((id) => window.clearTimeout(id));
187
+ pendingRef.current.clear();
188
+ document.removeEventListener('pointerdown', handlePointerDown, true);
189
+ };
190
+ }, [active, inside, ownsEvent, onAway]);
129
191
  };
130
192
 
193
+ /**
194
+ * Whether the pointerdown `useClickAway` is about to look at started inside
195
+ * the card's OWN React tree — a MUI `Select`'s menu, a nested `Popover`, a
196
+ * `DropdownMenu` the card's `content` renders — even when that control's DOM
197
+ * node is not a descendant of the card's box at all, because it portalled
198
+ * itself out near `document.body` (FUT-2776).
199
+ *
200
+ * React re-parents a portal's events to bubble AND capture through its OWNING
201
+ * React tree, not the DOM tree it renders into (see the React docs on
202
+ * `createPortal`), so an `onPointerDownCapture` on the card's own content root
203
+ * still fires for a press inside a portal the card's content renders, and
204
+ * NEVER fires for a press inside an unrelated one mounted by something that
205
+ * is not a descendant of the card in the REACT tree — ownership, not timing.
206
+ * That is the whole fix: the ORIGINAL approach (a `MutationObserver` on
207
+ * `document.body`) treated ANY element appended to the body while the card
208
+ * was open as inside, so an unrelated Snackbar, dev overlay or Popover that
209
+ * happened to mount at the same time stopped closing the card too.
210
+ *
211
+ * Tracked per NATIVE EVENT OBJECT in a `WeakSet`, not a boolean flag: a
212
+ * boolean stayed `true` forever once a press set it whose NATIVE propagation
213
+ * then got stopped before reaching `useClickAway`'s own (bubble-phase, in
214
+ * that earlier revision) listener — common for anything that treats the
215
+ * press as its own gesture — so the marker never got consumed, and the
216
+ * NEXT, genuinely outside press read a STALE `true` and stayed open
217
+ * (FUT-2776, caught on adversarial review of the first fix here). Keying on
218
+ * the event itself instead needs no consuming step and cannot go stale: a
219
+ * `WeakSet` entry only ever answers for the ONE dispatch that created it,
220
+ * and nothing outside this closure keeps a reference to a past pointerdown
221
+ * once `useClickAway`'s deferred (`setTimeout(0)`, not a microtask — see that
222
+ * hook for why) check for it has run, so the entry is simply garbage from
223
+ * then on.
224
+ */
225
+ const useCardOwnership = () => {
226
+ const ownedEventsRef = React.useRef<WeakSet<Event>>(new WeakSet());
227
+
228
+ const markInside = React.useCallback((event: React.PointerEvent<HTMLElement>) => {
229
+ ownedEventsRef.current.add(event.nativeEvent);
230
+ }, []);
231
+
232
+ const ownsEvent = React.useCallback(
233
+ (event: Event) => ownedEventsRef.current.has(event),
234
+ [],
235
+ );
236
+
237
+ return { markInside, ownsEvent };
238
+ };
239
+
240
+ /** The trigger and the card's own box — a plain DOM containment check. */
241
+ const useInsideCard = (
242
+ anchorEl: HTMLElement | null,
243
+ contentRef: React.MutableRefObject<HTMLDivElement | null>,
244
+ ) =>
245
+ React.useCallback(
246
+ () => [anchorEl, contentRef.current],
247
+ [anchorEl, contentRef],
248
+ );
249
+
131
250
  /** Whatever is still pending when the card unmounts is cancelled with it. */
132
251
  const useClearOnUnmount = (...refs: TimerRef[]) => {
133
252
  React.useEffect(
@@ -146,34 +265,22 @@ interface HoverCardTimingInput {
146
265
  onClose?: () => void;
147
266
  }
148
267
 
268
+ type VisibilityInput = Omit<HoverCardTimingInput, 'touchEnabled'>;
269
+
149
270
  /**
150
- * When the card is open, and the delays that decide it.
271
+ * The open/close state and its two timers, split out of `useHoverCard` so
272
+ * that composing it with click-away, escape and ownership reads as one flat
273
+ * list rather than one long function.
151
274
  *
152
- * Three timers are in play: the enter delay before opening, the exit delay
153
- * before closing (so the pointer can travel from the trigger onto the card
154
- * without dismissing it), and the long-press timer on touch. Each cancels the
155
- * others' pending work, which is why they live together.
275
+ * The enter delay before opening and the exit delay before closing (so the
276
+ * pointer can travel from the trigger onto the card without dismissing it)
277
+ * each cancel the other's pending work, which is why they live together.
156
278
  */
157
- export const useHoverCard = ({
158
- disabled,
159
- touchEnabled,
160
- enterDelay,
161
- exitDelay,
162
- onOpen,
163
- onClose,
164
- }: HoverCardTimingInput) => {
279
+ const useCardVisibility = ({ disabled, enterDelay, exitDelay, onOpen, onClose }: VisibilityInput) => {
165
280
  const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);
166
281
  const [isOpen, setIsOpen] = React.useState(false);
167
- const [isTouchDevice, setIsTouchDevice] = React.useState(false);
168
282
  const enterTimeoutRef = React.useRef<number | undefined>(undefined);
169
283
  const exitTimeoutRef = React.useRef<number | undefined>(undefined);
170
- /** The card's own box, inside the popover paper: a press here is not "away". */
171
- const contentRef = React.useRef<HTMLDivElement | null>(null);
172
-
173
- // Probed in an effect, not during render, so SSR and hydration stay intact.
174
- React.useEffect(() => {
175
- setIsTouchDevice('ontouchstart' in window);
176
- }, []);
177
284
 
178
285
  const openCard = React.useCallback(
179
286
  (trigger: HTMLElement) => {
@@ -216,6 +323,29 @@ export const useHoverCard = ({
216
323
  onClose?.();
217
324
  }, [onClose]);
218
325
 
326
+ return { anchorEl, isOpen, openCard, closeCard, handleClose, enterTimeoutRef, exitTimeoutRef };
327
+ };
328
+
329
+ export const useHoverCard = ({
330
+ disabled,
331
+ touchEnabled,
332
+ enterDelay,
333
+ exitDelay,
334
+ onOpen,
335
+ onClose,
336
+ }: HoverCardTimingInput) => {
337
+ const [isTouchDevice, setIsTouchDevice] = React.useState(false);
338
+ /** The card's own box, inside the popover paper: a press here is not "away". */
339
+ const contentRef = React.useRef<HTMLDivElement | null>(null);
340
+
341
+ const { anchorEl, isOpen, openCard, closeCard, handleClose, enterTimeoutRef, exitTimeoutRef } =
342
+ useCardVisibility({ disabled, enterDelay, exitDelay, onOpen, onClose });
343
+
344
+ // Probed in an effect, not during render, so SSR and hydration stay intact.
345
+ React.useEffect(() => {
346
+ setIsTouchDevice('ontouchstart' in window);
347
+ }, []);
348
+
219
349
  const { triggerHandlers, cardHandlers } = useHoverHandlers({
220
350
  isTouchDevice,
221
351
  touchEnabled,
@@ -225,11 +355,21 @@ export const useHoverCard = ({
225
355
  exitTimeoutRef,
226
356
  });
227
357
 
228
- const insideCard = React.useCallback(() => [anchorEl, contentRef.current], [anchorEl]);
358
+ const insideCard = useInsideCard(anchorEl, contentRef);
359
+ const { markInside, ownsEvent } = useCardOwnership();
229
360
 
230
361
  useEscapeKey(isOpen, handleClose);
231
- useClickAway(isOpen, insideCard, handleClose);
362
+ useClickAway(isOpen, insideCard, ownsEvent, handleClose);
232
363
  useClearOnUnmount(enterTimeoutRef, exitTimeoutRef);
233
364
 
234
- return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef };
365
+ return {
366
+ anchorEl,
367
+ isOpen,
368
+ handleClose,
369
+ triggerHandlers,
370
+ cardHandlers,
371
+ contentRef,
372
+ /** Wire onto the card's content root as `onPointerDownCapture` (FUT-2776). */
373
+ onContentPointerDownCapture: markInside,
374
+ };
235
375
  };
@@ -108,6 +108,7 @@ The HoverCard component displays additional information in a card-style popup wh
108
108
 
109
109
  - The component supports keyboard navigation (Escape key to close)
110
110
  - The card is dismissed by moving the pointer off the trigger and the card (after `exitDelay`), by Escape, or by a tap or click anywhere outside them. The outside press still reaches what it landed on: the page under an open card stays usable
111
+ - Nested portal content is supported: if `children` opens something of its own in a portal — a MUI `Select`'s menu, a nested `Popover`, a `DropdownMenu` — a press inside it counts as inside the card too, so picking an option there does not dismiss the card. No extra wiring is needed for this; it works for anything the card's OWN content renders, portalled or not — ownership is by React tree membership, not by when something happened to mount to `document.body`, so an unrelated portal (a Snackbar, a dev overlay, another component's Popover) that mounts while the card is open is not treated as inside it, and a press there still closes the card like any other outside press
111
112
  - Proper ARIA attributes are applied to the popover
112
113
  - Focus management is handled automatically
113
114
  - Touch support for mobile devices can be enabled/disabled
@@ -82,6 +82,29 @@ const capOf = (theme: Theme, maxWidth: number | undefined): string => {
82
82
  return cap <= 1 && cap !== 0 ? `${cap * 100}%` : rem(theme, cap);
83
83
  };
84
84
 
85
+ /** The clone-onto-`trigger` vs. wrap-in-a-`span` choice, pulled out of the component so its body stays readable. */
86
+ const buildTriggerElement = (
87
+ trigger: HoverCardProps['trigger'],
88
+ triggerHandlers: ReturnType<typeof useHoverCard>['triggerHandlers'],
89
+ disabled: boolean,
90
+ dataTestId: string | undefined,
91
+ children: React.ReactNode,
92
+ ) => {
93
+ const triggerProps = {
94
+ ...triggerHandlers,
95
+ style: { cursor: disabled ? 'default' : 'pointer' },
96
+ 'data-testid': dataTestId ? `${dataTestId}-trigger` : 'hover-card-trigger',
97
+ };
98
+
99
+ return trigger ? (
100
+ React.cloneElement(trigger, triggerProps)
101
+ ) : (
102
+ <span {...triggerProps} style={{ display: 'inline-block', ...triggerProps.style }}>
103
+ {children}
104
+ </span>
105
+ );
106
+ };
107
+
85
108
  export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props, ref) => {
86
109
  const {
87
110
  variant, glow, pulse, title, description, avatar, trigger, placement, showArrow,
@@ -90,7 +113,15 @@ export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props
90
113
  ...rest
91
114
  } = withDefaults(props, DEFAULTS) as ResolvedProps;
92
115
 
93
- const { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef } = useHoverCard({
116
+ const {
117
+ anchorEl,
118
+ isOpen,
119
+ handleClose,
120
+ triggerHandlers,
121
+ cardHandlers,
122
+ contentRef,
123
+ onContentPointerDownCapture,
124
+ } = useHoverCard({
94
125
  disabled,
95
126
  touchEnabled,
96
127
  enterDelay,
@@ -99,20 +130,7 @@ export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props
99
130
  onClose,
100
131
  });
101
132
 
102
- const triggerProps = {
103
- ...triggerHandlers,
104
- style: { cursor: disabled ? 'default' : 'pointer' },
105
- 'data-testid': dataTestId ? `${dataTestId}-trigger` : 'hover-card-trigger',
106
- };
107
-
108
- const triggerElement = trigger ? (
109
- React.cloneElement(trigger, triggerProps)
110
- ) : (
111
- <span {...triggerProps} style={{ display: 'inline-block', ...triggerProps.style }}>
112
- {children}
113
- </span>
114
- );
115
-
133
+ const triggerElement = buildTriggerElement(trigger, triggerHandlers, disabled, dataTestId, children);
116
134
  const padding = CONTENT_PADDING[variant] ?? 2;
117
135
 
118
136
  return (
@@ -133,7 +151,11 @@ export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props
133
151
  slotProps={{ paper: { style: { pointerEvents: 'auto' } } }}
134
152
  {...rest}
135
153
  >
136
- <Box ref={contentRef} sx={{ position: 'relative' }}>
154
+ <Box
155
+ ref={contentRef}
156
+ onPointerDownCapture={onContentPointerDownCapture}
157
+ sx={{ position: 'relative' }}
158
+ >
137
159
  {showArrow && <ArrowContainer placement={placement} offset={offset} />}
138
160
  <StyledCard
139
161
  customVariant={variant}
@@ -29,8 +29,14 @@ const DIALOG_STATIC_PROPS = {
29
29
  // second, non-modal `role="dialog"` this component sets of its own accord
30
30
  // — duplicating (and, as a static English literal, contradicting) the name
31
31
  // the Paper already carries.
32
+ //
33
+ // For the same reason, `role` is deliberately NOT set here (FUT-2861):
34
+ // `Dialog` destructures `aria-labelledby` and `aria-modal` by name and
35
+ // applies them to the Paper alongside its own hard-coded `role="dialog"`,
36
+ // but `role` itself is not one of the props it destructures — a `role`
37
+ // passed here falls into `...other` and lands a second, unlabelled
38
+ // `role="dialog"` on the outer Modal root instead of the Paper.
32
39
  'aria-labelledby': 'lightbox-title',
33
- role: 'dialog',
34
40
  'aria-modal': 'true',
35
41
  } as const;
36
42
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/Lightbox/Lightbox.tsx"],"sourcesContent":["import Dialog from '@mui/material/Dialog/index.js';\nimport Fade from '@mui/material/Fade/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport type { Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { makeTestId } from './Lightbox.constants';\nimport { resolveLightboxProps } from './Lightbox.helpers';\nimport { LightboxCopyProvider } from './lightbox-copy-context';\nimport type { LightboxProps, LightboxRef } from './Lightbox.types';\nimport { LightboxCurrentMedia } from './LightboxCurrentMedia';\nimport { LightboxOverlay } from './LightboxOverlay';\nimport { LightboxStage } from './LightboxStage';\nimport { LightboxThumbnails } from './LightboxThumbnails';\nimport { useLightbox } from './useLightbox';\nimport { scrim } from '../../../tokens/ink';\nimport { rem, sxRem } from '../../../tokens/relative';\n\nconst DIALOG_STATIC_PROPS = {\n maxWidth: false,\n fullScreen: true,\n TransitionComponent: Fade,\n TransitionProps: { timeout: 300 },\n PaperProps: { sx: { background: (theme: Theme) => scrim(theme, 0.9), backdropFilter: (theme: Theme) => `blur(${rem(theme, 2)})` } },\n // `aria-labelledby` is the one accessible name for the modal itself: MUI\n // forwards it (with `aria-modal` and its own `role=\"dialog\"`) onto the\n // Paper, the element that is actually the WAI-ARIA dialog. A plain\n // `aria-label` alongside it would land on the OUTER root instead — a\n // second, non-modal `role=\"dialog\"` this component sets of its own accord\n // — duplicating (and, as a static English literal, contradicting) the name\n // the Paper already carries.\n 'aria-labelledby': 'lightbox-title',\n role: 'dialog',\n 'aria-modal': 'true',\n} as const;\n\nexport const Lightbox = React.forwardRef<LightboxRef, LightboxProps>((componentProps, ref) => {\n const props = resolveLightboxProps(componentProps);\n const { isOpen, items, loop, autoplay, showControls, showCaptions, thumbnails, zoomable } = props;\n const { className, style, dataTestId } = props;\n\n const testId = makeTestId(dataTestId);\n const lightbox = useLightbox(props, ref);\n const { currentIndex, currentItem, zoom } = lightbox;\n\n return (\n <LightboxCopyProvider copy={props.copy}>\n <Dialog\n open={isOpen}\n onClose={lightbox.close}\n {...DIALOG_STATIC_PROPS}\n className={className}\n style={style}\n onTouchStart={lightbox.handleTouchStart}\n onTouchEnd={lightbox.handleTouchEnd}\n data-testid={dataTestId || 'lightbox'}\n >\n {/* Visually hidden title for screen readers */}\n <Typography id=\"lightbox-title\" variant=\"h6\" sx={{ position: 'absolute', left: sxRem(-10000) }}>\n {props.copy.dialogLabel} - {currentItem?.alt || props.copy.itemPosition(currentIndex + 1, items.length)}\n </Typography>\n\n <LightboxOverlay\n testId={testId}\n itemCount={items.length}\n currentIndex={currentIndex}\n showControls={showControls}\n loop={loop}\n autoplay={autoplay}\n canZoom={lightbox.canZoom}\n isAutoPlaying={lightbox.isAutoPlaying}\n onClose={lightbox.close}\n onPrev={lightbox.prev}\n onNext={lightbox.next}\n onZoomIn={zoom.zoomIn}\n onZoomOut={zoom.zoomOut}\n onResetZoom={zoom.resetZoom}\n onToggleAutoplay={() => lightbox.setIsAutoPlaying((playing) => !playing)}\n />\n\n <LightboxStage\n items={items}\n currentItem={currentItem}\n currentIndex={currentIndex}\n isLoading={lightbox.isLoading}\n showCaptions={showCaptions}\n thumbnails={thumbnails}\n testId={testId}\n media={\n <LightboxCurrentMedia\n item={currentItem}\n index={currentIndex}\n total={items.length}\n zoomable={zoomable}\n zoom={zoom}\n testId={testId}\n onLoadingChange={lightbox.setIsLoading}\n onWheel={lightbox.handleWheel}\n />\n }\n thumbnailStrip={\n thumbnails &&\n items.length > 1 && (\n <LightboxThumbnails\n items={items}\n currentIndex={currentIndex}\n testId={testId}\n onSelect={lightbox.goToIndex}\n />\n )\n }\n />\n </Dialog>\n </LightboxCopyProvider>\n );\n});\n\nLightbox.displayName = 'Lightbox';\n\nexport default Lightbox;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,OAAO,gBAAgB;AAEvB,OAAO,WAAW;AAsDZ,SAIA,KAJA;AAxCN,IAAM,sBAAsB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,qBAAqB;AAAA,EACrB,iBAAiB,EAAE,SAAS,IAAI;AAAA,EAChC,YAAY,EAAE,IAAI,EAAE,YAAY,CAAC,UAAiB,MAAM,OAAO,GAAG,GAAG,gBAAgB,CAAC,UAAiB,QAAQ,IAAI,OAAO,CAAC,CAAC,IAAI,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlI,mBAAmB;AAAA,EACnB,MAAM;AAAA,EACN,cAAc;AAChB;AAEO,IAAM,WAAW,MAAM,WAAuC,CAAC,gBAAgB,QAAQ;AAC5F,QAAM,QAAQ,qBAAqB,cAAc;AACjD,QAAM,EAAE,QAAQ,OAAO,MAAM,UAAU,cAAc,cAAc,YAAY,SAAS,IAAI;AAC5F,QAAM,EAAE,WAAW,OAAO,WAAW,IAAI;AAEzC,QAAM,SAAS,WAAW,UAAU;AACpC,QAAM,WAAW,YAAY,OAAO,GAAG;AACvC,QAAM,EAAE,cAAc,aAAa,KAAK,IAAI;AAE5C,SACE,oBAAC,wBAAqB,MAAM,MAAM,MAClC;AAAA,IAAC;AAAA;AAAA,MACC,MAAM;AAAA,MACN,SAAS,SAAS;AAAA,MACjB,GAAG;AAAA,MACJ;AAAA,MACA;AAAA,MACA,cAAc,SAAS;AAAA,MACvB,YAAY,SAAS;AAAA,MACrB,eAAa,cAAc;AAAA,MAG3B;AAAA,6BAAC,cAAW,IAAG,kBAAiB,SAAQ,MAAK,IAAI,EAAE,UAAU,YAAY,MAAM,MAAM,IAAM,EAAE,GAC1F;AAAA,gBAAM,KAAK;AAAA,UAAY;AAAA,UAAI,aAAa,OAAO,MAAM,KAAK,aAAa,eAAe,GAAG,MAAM,MAAM;AAAA,WACxG;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,WAAW,MAAM;AAAA,YACjB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS,SAAS;AAAA,YAClB,eAAe,SAAS;AAAA,YACxB,SAAS,SAAS;AAAA,YAClB,QAAQ,SAAS;AAAA,YACjB,QAAQ,SAAS;AAAA,YACjB,UAAU,KAAK;AAAA,YACf,WAAW,KAAK;AAAA,YAChB,aAAa,KAAK;AAAA,YAClB,kBAAkB,MAAM,SAAS,iBAAiB,CAAC,YAAY,CAAC,OAAO;AAAA;AAAA,QACzE;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA,WAAW,SAAS;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,YACA,OACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,OAAO,MAAM;AAAA,gBACb;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,iBAAiB,SAAS;AAAA,gBAC1B,SAAS,SAAS;AAAA;AAAA,YACpB;AAAA,YAEF,gBACE,cACA,MAAM,SAAS,KACb;AAAA,cAAC;AAAA;AAAA,gBACC;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,UAAU,SAAS;AAAA;AAAA,YACrB;AAAA;AAAA,QAGN;AAAA;AAAA;AAAA,EACF,GACA;AAEJ,CAAC;AAED,SAAS,cAAc;AAEvB,IAAO,mBAAQ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/HoverCard/HoverCard.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport Card from '@mui/material/Card/index.js';\nimport CardContent from '@mui/material/CardContent/index.js';\nimport Popover from '@mui/material/Popover/index.js';\nimport { styled, type Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { rem } from '../../../tokens/relative';\nimport { withDefaults } from '../../../utils/withDefaults';\n\nimport { HoverCardContent } from './HoverCard.content';\nimport { useHoverCard } from './HoverCard.hooks';\nimport { arrowSx, cardSx, getAnchorOrigin, getTransformOrigin, sideOf } from './HoverCard.styles';\nimport type { CardStyleFlags } from './HoverCard.styles';\nimport type { HoverCardAnimation, HoverCardPlacement, HoverCardProps } from './HoverCard.types';\n\nconst ArrowContainer = styled('div')<{ placement: HoverCardPlacement; offset?: number }>(\n ({ theme, placement }) => ({ ...arrowSx(theme, placement) }),\n);\n\nconst StyledCard = styled(Card, {\n shouldForwardProp: (prop) =>\n !['customVariant', 'glow', 'pulse', 'animation'].includes(prop as string),\n})<CardStyleFlags>(({ theme, ...flags }) => ({ ...cardSx(theme, flags) }));\n\nconst StyledPopover = styled(Popover, {\n shouldForwardProp: (prop) => prop !== 'customAnimation',\n})<{ customAnimation?: HoverCardAnimation }>(({ customAnimation }) => ({\n // Only the card takes the pointer (its paper sets `pointer-events: auto`).\n // MUI's popover root and its invisible backdrop cover the whole viewport, so\n // while a card was open the pointer was \"on the card\" wherever it went: the\n // backdrop's mouseover reached the card's onMouseEnter and cancelled the\n // close the trigger's mouseleave had just scheduled. A card left by moving\n // the mouse away stayed open, and the page's next click hit the backdrop\n // instead of its target (FUT-2619). The backdrop was also the only\n // click-away; `useClickAway` in the hooks replaces it.\n pointerEvents: 'none',\n // The popover only positions; StyledCard draws the surface.\n '& .MuiPopover-paper': {\n backgroundColor: 'transparent',\n boxShadow: 'none',\n overflow: 'visible',\n ...(customAnimation === 'scale' && { transformOrigin: 'center' }),\n },\n}));\n\nconst DEFAULTS = {\n variant: 'default',\n glow: false,\n pulse: false,\n placement: 'bottom',\n showArrow: false,\n animation: 'fade',\n enterDelay: 700,\n exitDelay: 0,\n loading: false,\n touchEnabled: true,\n disabled: false,\n} satisfies Partial<HoverCardProps>;\n\ntype ResolvedProps = HoverCardProps & Required<Pick<HoverCardProps, keyof typeof DEFAULTS>>;\n\n/** Padding is the variant's own: detailed roomier, minimal tighter. */\nconst CONTENT_PADDING: Record<string, number> = { detailed: 3, minimal: 1.5 };\n\n/**\n * The card is nudged off the anchor on whichever side it sits — `offset` design\n * px (8 unless the caller says otherwise), through the theme's type scale.\n */\nconst offsetMargin = (theme: Theme, placement: HoverCardPlacement, offset: number | undefined) => {\n const side = sideOf(placement);\n const key = ({ top: 'mt', bottom: 'mb', left: 'ml', right: 'mr' } as const)[side];\n return { [key]: rem(theme, offset ?? 8) };\n};\n\n/**\n * The card's cap: 400 design px unless the caller says otherwise. `sx` has always\n * read a number of 1 or less as a fraction of the parent, and that stays so.\n */\nconst capOf = (theme: Theme, maxWidth: number | undefined): string => {\n const cap = maxWidth ?? 400;\n return cap <= 1 && cap !== 0 ? `${cap * 100}%` : rem(theme, cap);\n};\n\nexport const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props, ref) => {\n const {\n variant, glow, pulse, title, description, avatar, trigger, placement, showArrow,\n animation, enterDelay, exitDelay, maxWidth, loading, loadingComponent, loadingText, touchEnabled,\n offset, disabled, onOpen, onClose, children, dataTestId,\n ...rest\n } = withDefaults(props, DEFAULTS) as ResolvedProps;\n\n const { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef } = useHoverCard({\n disabled,\n touchEnabled,\n enterDelay,\n exitDelay,\n onOpen,\n onClose,\n });\n\n const triggerProps = {\n ...triggerHandlers,\n style: { cursor: disabled ? 'default' : 'pointer' },\n 'data-testid': dataTestId ? `${dataTestId}-trigger` : 'hover-card-trigger',\n };\n\n const triggerElement = trigger ? (\n React.cloneElement(trigger, triggerProps)\n ) : (\n <span {...triggerProps} style={{ display: 'inline-block', ...triggerProps.style }}>\n {children}\n </span>\n );\n\n const padding = CONTENT_PADDING[variant] ?? 2;\n\n return (\n <>\n {triggerElement}\n <StyledPopover\n ref={ref}\n open={isOpen && !disabled}\n anchorEl={anchorEl}\n onClose={handleClose}\n anchorOrigin={getAnchorOrigin(placement)}\n transformOrigin={getTransformOrigin(placement)}\n {...cardHandlers}\n disableAutoFocus\n disableEnforceFocus\n disableRestoreFocus\n customAnimation={animation}\n slotProps={{ paper: { style: { pointerEvents: 'auto' } } }}\n {...rest}\n >\n <Box ref={contentRef} sx={{ position: 'relative' }}>\n {showArrow && <ArrowContainer placement={placement} offset={offset} />}\n <StyledCard\n customVariant={variant}\n glow={glow}\n pulse={pulse}\n animation={animation}\n data-testid={dataTestId || 'hover-card-content'}\n sx={(theme) => ({ maxWidth: capOf(theme, maxWidth), ...offsetMargin(theme, placement, offset) })}\n >\n <CardContent sx={{ p: padding, '&:last-child': { pb: padding } }}>\n <HoverCardContent\n loadingText={loadingText} variant={variant} title={title}\n description={description}\n avatar={avatar}\n loading={loading}\n loadingComponent={loadingComponent}\n dataTestId={dataTestId}\n >\n {children}\n </HoverCardContent>\n </CardContent>\n </StyledCard>\n </Box>\n </StyledPopover>\n </>\n );\n});\n\nHoverCard.displayName = 'HoverCard';\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/HoverCard/HoverCard.hooks.ts"],"sourcesContent":["import React from 'react';\n\ntype TimerRef = React.MutableRefObject<number | undefined>;\n\n/**\n * Cancel the timer a ref holds, and forget it. Every place that schedules\n * or dismisses goes through this, so no timer outlives the ref that could\n * cancel it (FUT-2619: an overwritten enter timer reopened a dismissed card).\n */\nconst clearPending = (ref: TimerRef) => {\n if (ref.current !== undefined) window.clearTimeout(ref.current);\n ref.current = undefined;\n};\n\ninterface HandlerInput {\n isTouchDevice: boolean;\n touchEnabled: boolean;\n enterDelay: number;\n openCard: (trigger: HTMLElement) => void;\n closeCard: () => void;\n exitTimeoutRef: TimerRef;\n}\n\n/**\n * The pointer and touch handlers, over the open/close pair. Touch gets a\n * long-press because there is no hover to respond to; the card's own handlers\n * keep it open while the pointer is travelling onto it.\n */\nconst useHoverHandlers = ({\n isTouchDevice,\n touchEnabled,\n enterDelay,\n openCard,\n closeCard,\n exitTimeoutRef,\n}: HandlerInput) => {\n const [touchTimeout, setTouchTimeout] = React.useState<number>();\n\n /** A touch device with touch disabled has no hover to respond to. */\n const hoverIgnored = isTouchDevice && !touchEnabled;\n\n const cancelTouch = React.useCallback(() => {\n if (touchTimeout) {\n window.clearTimeout(touchTimeout);\n setTouchTimeout(undefined);\n }\n }, [touchTimeout]);\n\n React.useEffect(\n () => () => {\n if (touchTimeout) window.clearTimeout(touchTimeout);\n },\n [touchTimeout],\n );\n\n const triggerHandlers = {\n onMouseEnter: (event: React.MouseEvent<HTMLElement>) => {\n if (hoverIgnored) return;\n openCard(event.currentTarget);\n },\n onMouseLeave: () => {\n if (hoverIgnored) return;\n closeCard();\n },\n onTouchStart: (event: React.TouchEvent<HTMLElement>) => {\n if (!touchEnabled) return;\n if (touchTimeout) window.clearTimeout(touchTimeout);\n // Read NOW: React nulls `currentTarget` once the handler returns, so\n // the long-press timer used to open the card with no anchor at all.\n const trigger = event.currentTarget;\n setTouchTimeout(window.setTimeout(() => openCard(trigger), enterDelay));\n },\n // End and cancel do the same thing: the press did not become a long press.\n onTouchEnd: cancelTouch,\n onTouchCancel: cancelTouch,\n };\n\n /** Moving onto the card keeps it open; leaving it starts the exit delay. */\n const cardHandlers = {\n onMouseEnter: () => clearPending(exitTimeoutRef),\n onMouseLeave: closeCard,\n };\n\n return { triggerHandlers, cardHandlers };\n};\n\n/** Escape dismisses the card while it is open. */\nconst useEscapeKey = (active: boolean, onEscape: () => void) => {\n React.useEffect(() => {\n if (!active) return;\n\n const handleEscape = (event: globalThis.KeyboardEvent) => {\n if (event.key === 'Escape') onEscape();\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [active, onEscape]);\n};\n\n/**\n * A press anywhere outside the trigger and the card dismisses it.\n *\n * Touch has no mouse-leave, and MUI's own click-away was the popover's\n * backdrop, which `pointer-events: none` on the popover root (so the page\n * under an open card stays usable) takes out of play. So this listens on the\n * document itself, in the CAPTURE phase, where a `stopPropagation` further in\n * cannot swallow it. It only observes: no `preventDefault`, so the tap or\n * click still reaches whatever it landed on. Attached while open, removed on\n * close and on unmount.\n */\nconst useClickAway = (\n active: boolean,\n inside: () => ReadonlyArray<Element | null>,\n onAway: () => void,\n) => {\n React.useEffect(() => {\n if (!active) return;\n\n const handlePointerDown = (event: globalThis.PointerEvent) => {\n const target = event.target as Node | null;\n if (target && inside().some((el) => el?.contains(target))) return;\n onAway();\n };\n\n document.addEventListener('pointerdown', handlePointerDown, true);\n return () => document.removeEventListener('pointerdown', handlePointerDown, true);\n }, [active, inside, onAway]);\n};\n\n/** Whatever is still pending when the card unmounts is cancelled with it. */\nconst useClearOnUnmount = (...refs: TimerRef[]) => {\n React.useEffect(\n () => () => refs.forEach(clearPending),\n // The refs are stable objects: a mount/unmount pair, as before.\n [],\n );\n};\n\ninterface HoverCardTimingInput {\n disabled: boolean;\n touchEnabled: boolean;\n enterDelay: number;\n exitDelay: number;\n onOpen?: () => void;\n onClose?: () => void;\n}\n\n/**\n * When the card is open, and the delays that decide it.\n *\n * Three timers are in play: the enter delay before opening, the exit delay\n * before closing (so the pointer can travel from the trigger onto the card\n * without dismissing it), and the long-press timer on touch. Each cancels the\n * others' pending work, which is why they live together.\n */\nexport const useHoverCard = ({\n disabled,\n touchEnabled,\n enterDelay,\n exitDelay,\n onOpen,\n onClose,\n}: HoverCardTimingInput) => {\n const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);\n const [isOpen, setIsOpen] = React.useState(false);\n const [isTouchDevice, setIsTouchDevice] = React.useState(false);\n const enterTimeoutRef = React.useRef<number | undefined>(undefined);\n const exitTimeoutRef = React.useRef<number | undefined>(undefined);\n /** The card's own box, inside the popover paper: a press here is not \"away\". */\n const contentRef = React.useRef<HTMLDivElement | null>(null);\n\n // Probed in an effect, not during render, so SSR and hydration stay intact.\n React.useEffect(() => {\n setIsTouchDevice('ontouchstart' in window);\n }, []);\n\n const openCard = React.useCallback(\n (trigger: HTMLElement) => {\n if (disabled) return;\n\n clearPending(exitTimeoutRef);\n // A second enter before the delay ran out (a click and then a hover, or\n // the pointer jittering across the edge) must REPLACE the pending open,\n // not add one: an overwritten timer can no longer be cancelled, and it\n // reopened the card after Escape had closed it.\n clearPending(enterTimeoutRef);\n\n enterTimeoutRef.current = window.setTimeout(() => {\n setAnchorEl(trigger);\n setIsOpen(true);\n onOpen?.();\n }, enterDelay);\n },\n [disabled, enterDelay, onOpen],\n );\n\n const closeCard = React.useCallback(() => {\n clearPending(enterTimeoutRef);\n clearPending(exitTimeoutRef);\n\n exitTimeoutRef.current = window.setTimeout(() => {\n setIsOpen(false);\n setAnchorEl(null);\n onClose?.();\n }, exitDelay);\n }, [exitDelay, onClose]);\n\n // Escape and the popover's own close are FINAL: nothing still pending may\n // reopen the card behind them. It opens again on the next enter.\n const handleClose = React.useCallback(() => {\n clearPending(enterTimeoutRef);\n clearPending(exitTimeoutRef);\n setIsOpen(false);\n setAnchorEl(null);\n onClose?.();\n }, [onClose]);\n\n const { triggerHandlers, cardHandlers } = useHoverHandlers({\n isTouchDevice,\n touchEnabled,\n enterDelay,\n openCard,\n closeCard,\n exitTimeoutRef,\n });\n\n const insideCard = React.useCallback(() => [anchorEl, contentRef.current], [anchorEl]);\n\n useEscapeKey(isOpen, handleClose);\n useClickAway(isOpen, insideCard, handleClose);\n useClearOnUnmount(enterTimeoutRef, exitTimeoutRef);\n\n return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef };\n};\n"],"mappings":"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","names":[]}