@elliemae/ds-floating-context 3.70.0-next.8 → 3.70.0-rc.0

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 (82) hide show
  1. package/dist/cjs/DSFloatingContext.js +129 -41
  2. package/dist/cjs/DSFloatingContext.js.map +2 -2
  3. package/dist/cjs/hooks/useFloatingClickOutside.js +60 -0
  4. package/dist/cjs/hooks/useFloatingClickOutside.js.map +7 -0
  5. package/dist/cjs/hooks/useFloatingEscape.js +74 -0
  6. package/dist/cjs/hooks/useFloatingEscape.js.map +7 -0
  7. package/dist/cjs/hooks/useFloatingResizeObserver.js +52 -0
  8. package/dist/cjs/hooks/useFloatingResizeObserver.js.map +7 -0
  9. package/dist/cjs/hooks/useLatestRef.js +41 -0
  10. package/dist/cjs/hooks/useLatestRef.js.map +7 -0
  11. package/dist/cjs/hooks/useReferenceHiddenByBoundary.js +59 -0
  12. package/dist/cjs/hooks/useReferenceHiddenByBoundary.js.map +7 -0
  13. package/dist/cjs/hooks/useResolvedReference.js +52 -0
  14. package/dist/cjs/hooks/useResolvedReference.js.map +7 -0
  15. package/dist/cjs/hooks/useSyntheticEventFromControlledState.js +53 -0
  16. package/dist/cjs/hooks/useSyntheticEventFromControlledState.js.map +7 -0
  17. package/dist/cjs/package.json +1 -4
  18. package/dist/cjs/parts/FloatingWrapper/react-desc-prop-types.js +1 -1
  19. package/dist/cjs/parts/FloatingWrapper/react-desc-prop-types.js.map +2 -2
  20. package/dist/cjs/parts/PopoverArrow.js.map +2 -2
  21. package/dist/cjs/react-desc-prop-types.js +23 -5
  22. package/dist/cjs/react-desc-prop-types.js.map +2 -2
  23. package/dist/cjs/typescript-testing/typescript-floating-context-valid.js +8 -2
  24. package/dist/cjs/typescript-testing/typescript-floating-context-valid.js.map +2 -2
  25. package/dist/cjs/useComputedPositionStyles.js +41 -18
  26. package/dist/cjs/useComputedPositionStyles.js.map +2 -2
  27. package/dist/cjs/utils/computePosition.js +26 -10
  28. package/dist/cjs/utils/computePosition.js.map +2 -2
  29. package/dist/cjs/utils/floatingPositioning.js +11 -0
  30. package/dist/cjs/utils/floatingPositioning.js.map +2 -2
  31. package/dist/esm/DSFloatingContext.js +130 -42
  32. package/dist/esm/DSFloatingContext.js.map +2 -2
  33. package/dist/esm/hooks/useFloatingClickOutside.js +30 -0
  34. package/dist/esm/hooks/useFloatingClickOutside.js.map +7 -0
  35. package/dist/esm/hooks/useFloatingEscape.js +44 -0
  36. package/dist/esm/hooks/useFloatingEscape.js.map +7 -0
  37. package/dist/esm/hooks/useFloatingResizeObserver.js +22 -0
  38. package/dist/esm/hooks/useFloatingResizeObserver.js.map +7 -0
  39. package/dist/esm/hooks/useLatestRef.js +11 -0
  40. package/dist/esm/hooks/useLatestRef.js.map +7 -0
  41. package/dist/esm/hooks/useReferenceHiddenByBoundary.js +29 -0
  42. package/dist/esm/hooks/useReferenceHiddenByBoundary.js.map +7 -0
  43. package/dist/esm/hooks/useResolvedReference.js +22 -0
  44. package/dist/esm/hooks/useResolvedReference.js.map +7 -0
  45. package/dist/esm/hooks/useSyntheticEventFromControlledState.js +23 -0
  46. package/dist/esm/hooks/useSyntheticEventFromControlledState.js.map +7 -0
  47. package/dist/esm/package.json +1 -4
  48. package/dist/esm/parts/FloatingWrapper/react-desc-prop-types.js +1 -1
  49. package/dist/esm/parts/FloatingWrapper/react-desc-prop-types.js.map +2 -2
  50. package/dist/esm/parts/PopoverArrow.js.map +2 -2
  51. package/dist/esm/react-desc-prop-types.js +23 -5
  52. package/dist/esm/react-desc-prop-types.js.map +2 -2
  53. package/dist/esm/typescript-testing/typescript-floating-context-valid.js +8 -2
  54. package/dist/esm/typescript-testing/typescript-floating-context-valid.js.map +2 -2
  55. package/dist/esm/useComputedPositionStyles.js +41 -18
  56. package/dist/esm/useComputedPositionStyles.js.map +2 -2
  57. package/dist/esm/utils/computePosition.js +27 -10
  58. package/dist/esm/utils/computePosition.js.map +2 -2
  59. package/dist/esm/utils/floatingPositioning.js +11 -0
  60. package/dist/esm/utils/floatingPositioning.js.map +2 -2
  61. package/dist/types/DSFloatingContext.d.ts +48 -8
  62. package/dist/types/hooks/useFloatingClickOutside.d.ts +20 -0
  63. package/dist/types/hooks/useFloatingEscape.d.ts +19 -0
  64. package/dist/types/hooks/useFloatingResizeObserver.d.ts +11 -0
  65. package/dist/types/hooks/useLatestRef.d.ts +7 -0
  66. package/dist/types/hooks/useReferenceHiddenByBoundary.d.ts +17 -0
  67. package/dist/types/hooks/useResolvedReference.d.ts +19 -0
  68. package/dist/types/hooks/useSyntheticEventFromControlledState.d.ts +32 -0
  69. package/dist/types/react-desc-prop-types.d.ts +76 -2
  70. package/dist/types/useComputedPositionStyles.d.ts +7 -4
  71. package/dist/types/utils/computePosition.d.ts +25 -2
  72. package/dist/types/utils/floatingPositioning.d.ts +13 -1
  73. package/package.json +10 -10
  74. package/dist/types/tests/ControlledTestRenderer.d.ts +0 -1
  75. package/dist/types/tests/FloatingContext.a11y.test.d.ts +0 -1
  76. package/dist/types/tests/FloatingContext.data-testid.test.d.ts +0 -1
  77. package/dist/types/tests/FloatingContext.event.test.d.ts +0 -1
  78. package/dist/types/tests/FloatingContext.exports.test.d.ts +0 -1
  79. package/dist/types/tests/FloatingContext.get-owner-props.test.d.ts +0 -1
  80. package/dist/types/tests/FloatingContext.utils.test.d.ts +0 -1
  81. package/dist/types/tests/playwright/FloatingContext.test.playwright.d.ts +0 -1
  82. package/dist/types/typescript-testing/typescript-floating-context-valid.d.ts +0 -1
@@ -37,16 +37,32 @@ var import_getArrowOffset = require("./getArrowOffset.js");
37
37
  var import_detectOverflow = require("./detectOverflow.js");
38
38
  var import_floatingPositioning = require("./floatingPositioning.js");
39
39
  const computePosition = (props) => {
40
- const { reference, floating, placement, placementOrderPreference, customOffset, withoutPortal } = props;
41
- const parentOffsets = withoutPortal ? (0, import_floatingPositioning.adjustForFixedParent)(reference) : { top: 0, left: 0 };
42
- const referenceRect = reference.getBoundingClientRect();
43
- const floatingRect = floating.getBoundingClientRect();
40
+ const {
41
+ triggerElementReference,
42
+ floatingWrapperNode,
43
+ placement,
44
+ placementOrderPreference,
45
+ customOffset,
46
+ withoutPortal,
47
+ boundaryElement
48
+ } = props;
49
+ const parentOffsets = withoutPortal ? (0, import_floatingPositioning.adjustForFixedParent)(triggerElementReference) : { top: 0, left: 0 };
50
+ const referenceRect = triggerElementReference.getBoundingClientRect();
51
+ const floatingRect = floatingWrapperNode.getBoundingClientRect();
44
52
  const fallbackPlacements = placementOrderPreference || (0, import_getExpandedFallbackPlacements.getExpandedFallbackPlacements)(placement);
45
53
  const placements = (0, import_floatingPositioning.expandWithVariations)(
46
54
  [placement].concat(fallbackPlacements)
47
55
  );
48
- const clippingParent = withoutPortal ? (0, import_floatingPositioning.getClippingParent)(reference) : null;
49
- const clippingRect = clippingParent ? clippingParent.getBoundingClientRect() : (0, import_floatingPositioning.getViewportRect)();
56
+ const clippingParent = withoutPortal ? (0, import_floatingPositioning.getClippingParent)(triggerElementReference) : null;
57
+ const viewportRect = (0, import_floatingPositioning.getViewportRect)();
58
+ let clippingRect;
59
+ if (clippingParent) {
60
+ clippingRect = clippingParent.getBoundingClientRect();
61
+ } else if (boundaryElement) {
62
+ clippingRect = (0, import_floatingPositioning.getBoundaryConstrainedRect)(viewportRect, boundaryElement.getBoundingClientRect(), floatingRect);
63
+ } else {
64
+ clippingRect = viewportRect;
65
+ }
50
66
  let bestPlacement = placement;
51
67
  let bestOverflows = null;
52
68
  let bestScore = { total: Number.POSITIVE_INFINITY, maxSide: Number.POSITIVE_INFINITY };
@@ -71,10 +87,10 @@ const computePosition = (props) => {
71
87
  let x = -overflows.left - parentOffsets.left;
72
88
  let y = -overflows.top - parentOffsets.top;
73
89
  if (!withoutPortal) {
74
- x += window.scrollX;
75
- y += window.scrollY;
90
+ x += window.scrollX + clippingRect.left;
91
+ y += window.scrollY + clippingRect.top;
76
92
  } else {
77
- const op = (0, import_floatingPositioning.getOffsetParentData)(floating);
93
+ const op = (0, import_floatingPositioning.getOffsetParentData)(floatingWrapperNode);
78
94
  x = x + clippingRect.left - op.left + op.scrollLeft;
79
95
  y = y + clippingRect.top - op.top + op.scrollTop;
80
96
  }
@@ -87,7 +103,7 @@ const computePosition = (props) => {
87
103
  y,
88
104
  withoutPortal,
89
105
  parentOffsets,
90
- floatingEl: floating,
106
+ floatingEl: floatingWrapperNode,
91
107
  arrowPadding: 12
92
108
  });
93
109
  return {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/utils/computePosition.ts", "../../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["/* eslint-disable max-lines */\n/* eslint-disable no-nested-ternary */\n/* eslint-disable complexity */\n/* eslint-disable max-statements */\n/* eslint-disable @typescript-eslint/no-use-before-define */\n/* eslint-disable max-params */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable arrow-body-style */\nimport type { DSHookFloatingContextT } from '../react-desc-prop-types.js';\nimport { getExpandedFallbackPlacements } from './getExpandedFallbackPlacements.js';\nimport { getArrowOffsetDynamic } from './getArrowOffset.js';\nimport { detectOverflow } from './detectOverflow.js';\nimport {\n applyShift,\n adjustForFixedParent,\n expandWithVariations,\n fits,\n getClippingParent,\n getOverflowScore,\n getOffsetParentData,\n getViewportRect,\n type RectLike,\n type OverflowOffsets,\n} from './floatingPositioning.js';\n\ninterface ComputePositionProps {\n reference: Element;\n floating: HTMLElement;\n placement: DSHookFloatingContextT.PopperPlacementsT;\n placementOrderPreference?: DSHookFloatingContextT.PopperPlacementsT[];\n customOffset: [number, number];\n withoutPortal: boolean;\n}\n\nexport const computePosition = (props: ComputePositionProps) => {\n const { reference, floating, placement, placementOrderPreference, customOffset, withoutPortal } = props;\n\n // When WITHOUT portal: only apply fixed-parent offsets (absolute parents scroll and must NOT be treated as fixed)\n const parentOffsets = withoutPortal ? adjustForFixedParent(reference) : { top: 0, left: 0 };\n\n const referenceRect = reference.getBoundingClientRect();\n const floatingRect = floating.getBoundingClientRect();\n\n const fallbackPlacements = placementOrderPreference || getExpandedFallbackPlacements(placement);\n\n const placements = expandWithVariations(\n [placement].concat(fallbackPlacements as DSHookFloatingContextT.PopperPlacementsT[]),\n );\n\n // Boundary selection:\n // - Portal => viewport (inset by body padding for Storybook)\n // - No portal => nearest clipping/scroll container (fallback viewport rect)\n const clippingParent = withoutPortal ? getClippingParent(reference) : null;\n const clippingRect: RectLike = clippingParent ? clippingParent.getBoundingClientRect() : getViewportRect();\n\n // Best-fit selection:\n // 1) choose first placement that fully fits\n // 2) otherwise choose placement with smallest max overflow, tie-break by total overflow\n let bestPlacement = placement;\n let bestOverflows: OverflowOffsets | null = null;\n let bestScore = { total: Number.POSITIVE_INFINITY, maxSide: Number.POSITIVE_INFINITY };\n\n for (let i = 0; i < placements.length; i += 1) {\n const currentPlacement = placements[i];\n\n const overflows = detectOverflow(referenceRect, floatingRect, currentPlacement, customOffset, clippingRect);\n\n if (fits(overflows)) {\n bestPlacement = currentPlacement;\n bestOverflows = overflows;\n break;\n }\n\n const score = getOverflowScore(overflows);\n\n const isBetter =\n score.maxSide < bestScore.maxSide || (score.maxSide === bestScore.maxSide && score.total < bestScore.total);\n\n if (isBetter) {\n bestPlacement = currentPlacement;\n bestOverflows = overflows;\n bestScore = score;\n }\n }\n\n const finalPlacement = bestPlacement;\n\n const overflows =\n bestOverflows ?? detectOverflow(referenceRect, floatingRect, finalPlacement, customOffset, clippingRect);\n\n // Convert overflow -> coordinates.\n // detectOverflow uses viewport/clipping-rect coordinates.\n //\n // - If tooltip is rendered IN A PORTAL (withoutPortal === false) and is positioned with `position: absolute`,\n // convert viewport coords to page coords by adding window.scrollX/Y.\n //\n // - If tooltip is rendered WITHOUT portal, convert viewport coords to offsetParent coords\n // (subtract offsetParent rect, add its scroll).\n let x = -overflows.left - parentOffsets.left;\n let y = -overflows.top - parentOffsets.top;\n\n if (!withoutPortal) {\n x += window.scrollX;\n y += window.scrollY;\n } else {\n // clippingRect.top/left may be non-zero (e.g. a scroll container below the viewport top).\n // The overflow values already subtracted it; add it back so we convert pure viewport coords\n // to offset-parent-relative coords: viewportCoord - offsetParent.top + offsetParent.scrollTop\n const op = getOffsetParentData(floating);\n x = x + clippingRect.left - op.left + op.scrollLeft;\n y = y + clippingRect.top - op.top + op.scrollTop;\n }\n\n // Always shift back inside boundary\n ({ x, y } = applyShift(x, y, overflows));\n\n const coordsArrow = getArrowOffsetDynamic({\n placement: finalPlacement,\n referenceRect,\n floatingRect,\n x,\n y,\n withoutPortal,\n parentOffsets,\n floatingEl: floating,\n arrowPadding: 12,\n });\n\n return {\n coordsStyle: {\n transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,\n top: 0,\n left: 0,\n },\n finalPlacement,\n coordsArrow,\n };\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADSvB,2CAA8C;AAC9C,4BAAsC;AACtC,4BAA+B;AAC/B,iCAWO;AAWA,MAAM,kBAAkB,CAAC,UAAgC;AAC9D,QAAM,EAAE,WAAW,UAAU,WAAW,0BAA0B,cAAc,cAAc,IAAI;AAGlG,QAAM,gBAAgB,oBAAgB,iDAAqB,SAAS,IAAI,EAAE,KAAK,GAAG,MAAM,EAAE;AAE1F,QAAM,gBAAgB,UAAU,sBAAsB;AACtD,QAAM,eAAe,SAAS,sBAAsB;AAEpD,QAAM,qBAAqB,gCAA4B,oEAA8B,SAAS;AAE9F,QAAM,iBAAa;AAAA,IACjB,CAAC,SAAS,EAAE,OAAO,kBAAgE;AAAA,EACrF;AAKA,QAAM,iBAAiB,oBAAgB,8CAAkB,SAAS,IAAI;AACtE,QAAM,eAAyB,iBAAiB,eAAe,sBAAsB,QAAI,4CAAgB;AAKzG,MAAI,gBAAgB;AACpB,MAAI,gBAAwC;AAC5C,MAAI,YAAY,EAAE,OAAO,OAAO,mBAAmB,SAAS,OAAO,kBAAkB;AAErF,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK,GAAG;AAC7C,UAAM,mBAAmB,WAAW,CAAC;AAErC,UAAMA,iBAAY,sCAAe,eAAe,cAAc,kBAAkB,cAAc,YAAY;AAE1G,YAAI,iCAAKA,UAAS,GAAG;AACnB,sBAAgB;AAChB,sBAAgBA;AAChB;AAAA,IACF;AAEA,UAAM,YAAQ,6CAAiBA,UAAS;AAExC,UAAM,WACJ,MAAM,UAAU,UAAU,WAAY,MAAM,YAAY,UAAU,WAAW,MAAM,QAAQ,UAAU;AAEvG,QAAI,UAAU;AACZ,sBAAgB;AAChB,sBAAgBA;AAChB,kBAAY;AAAA,IACd;AAAA,EACF;AAEA,QAAM,iBAAiB;AAEvB,QAAM,YACJ,qBAAiB,sCAAe,eAAe,cAAc,gBAAgB,cAAc,YAAY;AAUzG,MAAI,IAAI,CAAC,UAAU,OAAO,cAAc;AACxC,MAAI,IAAI,CAAC,UAAU,MAAM,cAAc;AAEvC,MAAI,CAAC,eAAe;AAClB,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd,OAAO;AAIL,UAAM,SAAK,gDAAoB,QAAQ;AACvC,QAAI,IAAI,aAAa,OAAO,GAAG,OAAO,GAAG;AACzC,QAAI,IAAI,aAAa,MAAM,GAAG,MAAM,GAAG;AAAA,EACzC;AAGA,GAAC,EAAE,GAAG,EAAE,QAAI,uCAAW,GAAG,GAAG,SAAS;AAEtC,QAAM,kBAAc,6CAAsB;AAAA,IACxC,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AAED,SAAO;AAAA,IACL,aAAa;AAAA,MACX,WAAW,eAAe,KAAK,MAAM,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;AAAA,MAC3D,KAAK;AAAA,MACL,MAAM;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["/* eslint-disable max-lines */\n/* eslint-disable no-nested-ternary */\n/* eslint-disable complexity */\n/* eslint-disable max-statements */\n/* eslint-disable @typescript-eslint/no-use-before-define */\n/* eslint-disable max-params */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable arrow-body-style */\nimport type { DSHookFloatingContextT } from '../react-desc-prop-types.js';\nimport { getExpandedFallbackPlacements } from './getExpandedFallbackPlacements.js';\nimport { getArrowOffsetDynamic } from './getArrowOffset.js';\nimport { detectOverflow } from './detectOverflow.js';\nimport {\n applyShift,\n adjustForFixedParent,\n expandWithVariations,\n fits,\n getBoundaryConstrainedRect,\n getClippingParent,\n getOverflowScore,\n getOffsetParentData,\n getViewportRect,\n type RectLike,\n type OverflowOffsets,\n} from './floatingPositioning.js';\n\ninterface ComputePositionProps {\n triggerElementReference: Element;\n floatingWrapperNode: HTMLElement;\n placement: DSHookFloatingContextT.PopperPlacementsT;\n placementOrderPreference?: DSHookFloatingContextT.PopperPlacementsT[];\n customOffset: [number, number];\n withoutPortal: boolean;\n /**\n * Opt-in extra boundary (e.g. a scrollable ancestor of the reference) the floating element\n * should stay visually contained within, in addition to the viewport. Only applied when\n * portaled \u2014 the non-portal path already derives its own clipping ancestor automatically via\n * getClippingParent, by walking up from the reference.\n *\n * Why this exists instead of just setting `withoutPortal: true` to get that same automatic\n * clipping-ancestor derivation: withoutPortal renders the floating element as a literal DOM\n * descendant of the clipping ancestor, so the browser's own CSS overflow clipping applies\n * unconditionally \u2014 any floating content taller/wider than that ancestor gets visually cut\n * off, with no way for position math to prevent it. That is fine for content that always fits\n * (e.g. a small filter's low/high inputs), but breaks content that can legitimately be larger\n * than its reference's scroll container (e.g. a date-range calendar popover inside a short\n * table row) \u2014 see PUI-17187 in ds-data-table's FilterPopover for the exact regression this\n * caused when withoutPortal was tried first.\n *\n * boundaryElement keeps the portal (so oversized content still escapes fully into the page,\n * unclipped, exactly like the no-boundaryElement case) and only changes the clipping *rect*\n * used for shift/flip math \u2014 ignored per-axis when the floating content doesn't fit that axis\n * of the boundary (see getBoundaryConstrainedRect), falling back to the plain viewport for\n * that axis so oversized content is never forced into an impossible position.\n */\n boundaryElement?: Element | null;\n}\n\nexport const computePosition = (props: ComputePositionProps) => {\n const {\n triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n boundaryElement,\n } = props;\n\n // When WITHOUT portal: only apply fixed-parent offsets (absolute parents scroll and must NOT be treated as fixed)\n const parentOffsets = withoutPortal ? adjustForFixedParent(triggerElementReference) : { top: 0, left: 0 };\n\n const referenceRect = triggerElementReference.getBoundingClientRect();\n const floatingRect = floatingWrapperNode.getBoundingClientRect();\n\n const fallbackPlacements = placementOrderPreference || getExpandedFallbackPlacements(placement);\n\n const placements = expandWithVariations(\n [placement].concat(fallbackPlacements as DSHookFloatingContextT.PopperPlacementsT[]),\n );\n\n // Boundary selection:\n // - No portal => nearest clipping/scroll container (fallback viewport rect)\n // - Portal + boundaryElement => viewport intersected with that element, per-axis (see\n // getBoundaryConstrainedRect)\n // - Portal, no boundaryElement => viewport (inset by body padding for Storybook)\n const clippingParent = withoutPortal ? getClippingParent(triggerElementReference) : null;\n const viewportRect = getViewportRect();\n let clippingRect: RectLike;\n if (clippingParent) {\n clippingRect = clippingParent.getBoundingClientRect();\n } else if (boundaryElement) {\n clippingRect = getBoundaryConstrainedRect(viewportRect, boundaryElement.getBoundingClientRect(), floatingRect);\n } else {\n clippingRect = viewportRect;\n }\n\n // Best-fit selection:\n // 1) choose first placement that fully fits\n // 2) otherwise choose placement with smallest max overflow, tie-break by total overflow\n let bestPlacement = placement;\n let bestOverflows: OverflowOffsets | null = null;\n let bestScore = { total: Number.POSITIVE_INFINITY, maxSide: Number.POSITIVE_INFINITY };\n\n for (let i = 0; i < placements.length; i += 1) {\n const currentPlacement = placements[i];\n\n const overflows = detectOverflow(referenceRect, floatingRect, currentPlacement, customOffset, clippingRect);\n\n if (fits(overflows)) {\n bestPlacement = currentPlacement;\n bestOverflows = overflows;\n break;\n }\n\n const score = getOverflowScore(overflows);\n\n const isBetter =\n score.maxSide < bestScore.maxSide || (score.maxSide === bestScore.maxSide && score.total < bestScore.total);\n\n if (isBetter) {\n bestPlacement = currentPlacement;\n bestOverflows = overflows;\n bestScore = score;\n }\n }\n\n const finalPlacement = bestPlacement;\n\n const overflows =\n bestOverflows ?? detectOverflow(referenceRect, floatingRect, finalPlacement, customOffset, clippingRect);\n\n // Convert overflow -> coordinates.\n // detectOverflow uses viewport/clipping-rect coordinates.\n //\n // - If tooltip is rendered IN A PORTAL (withoutPortal === false) and is positioned with `position: absolute`,\n // convert viewport coords to page coords by adding window.scrollX/Y, plus clippingRect.left/top \u2014\n // normally 0 (plain viewport), but non-zero when boundaryElement constrains clippingRect to a\n // region that doesn't start at the viewport's own origin (e.g. below a sticky header).\n //\n // - If tooltip is rendered WITHOUT portal, convert viewport coords to offsetParent coords\n // (subtract offsetParent rect, add its scroll).\n let x = -overflows.left - parentOffsets.left;\n let y = -overflows.top - parentOffsets.top;\n\n if (!withoutPortal) {\n x += window.scrollX + clippingRect.left;\n y += window.scrollY + clippingRect.top;\n } else {\n // clippingRect.top/left may be non-zero (e.g. a scroll container below the viewport top).\n // The overflow values already subtracted it; add it back so we convert pure viewport coords\n // to offset-parent-relative coords: viewportCoord - offsetParent.top + offsetParent.scrollTop\n const op = getOffsetParentData(floatingWrapperNode);\n x = x + clippingRect.left - op.left + op.scrollLeft;\n y = y + clippingRect.top - op.top + op.scrollTop;\n }\n\n // Always shift back inside boundary\n ({ x, y } = applyShift(x, y, overflows));\n\n const coordsArrow = getArrowOffsetDynamic({\n placement: finalPlacement,\n referenceRect,\n floatingRect,\n x,\n y,\n withoutPortal,\n parentOffsets,\n floatingEl: floatingWrapperNode,\n arrowPadding: 12,\n });\n\n return {\n coordsStyle: {\n transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,\n top: 0,\n left: 0,\n },\n finalPlacement,\n coordsArrow,\n };\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADSvB,2CAA8C;AAC9C,4BAAsC;AACtC,4BAA+B;AAC/B,iCAYO;AAkCA,MAAM,kBAAkB,CAAC,UAAgC;AAC9D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,QAAM,gBAAgB,oBAAgB,iDAAqB,uBAAuB,IAAI,EAAE,KAAK,GAAG,MAAM,EAAE;AAExG,QAAM,gBAAgB,wBAAwB,sBAAsB;AACpE,QAAM,eAAe,oBAAoB,sBAAsB;AAE/D,QAAM,qBAAqB,gCAA4B,oEAA8B,SAAS;AAE9F,QAAM,iBAAa;AAAA,IACjB,CAAC,SAAS,EAAE,OAAO,kBAAgE;AAAA,EACrF;AAOA,QAAM,iBAAiB,oBAAgB,8CAAkB,uBAAuB,IAAI;AACpF,QAAM,mBAAe,4CAAgB;AACrC,MAAI;AACJ,MAAI,gBAAgB;AAClB,mBAAe,eAAe,sBAAsB;AAAA,EACtD,WAAW,iBAAiB;AAC1B,uBAAe,uDAA2B,cAAc,gBAAgB,sBAAsB,GAAG,YAAY;AAAA,EAC/G,OAAO;AACL,mBAAe;AAAA,EACjB;AAKA,MAAI,gBAAgB;AACpB,MAAI,gBAAwC;AAC5C,MAAI,YAAY,EAAE,OAAO,OAAO,mBAAmB,SAAS,OAAO,kBAAkB;AAErF,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK,GAAG;AAC7C,UAAM,mBAAmB,WAAW,CAAC;AAErC,UAAMA,iBAAY,sCAAe,eAAe,cAAc,kBAAkB,cAAc,YAAY;AAE1G,YAAI,iCAAKA,UAAS,GAAG;AACnB,sBAAgB;AAChB,sBAAgBA;AAChB;AAAA,IACF;AAEA,UAAM,YAAQ,6CAAiBA,UAAS;AAExC,UAAM,WACJ,MAAM,UAAU,UAAU,WAAY,MAAM,YAAY,UAAU,WAAW,MAAM,QAAQ,UAAU;AAEvG,QAAI,UAAU;AACZ,sBAAgB;AAChB,sBAAgBA;AAChB,kBAAY;AAAA,IACd;AAAA,EACF;AAEA,QAAM,iBAAiB;AAEvB,QAAM,YACJ,qBAAiB,sCAAe,eAAe,cAAc,gBAAgB,cAAc,YAAY;AAYzG,MAAI,IAAI,CAAC,UAAU,OAAO,cAAc;AACxC,MAAI,IAAI,CAAC,UAAU,MAAM,cAAc;AAEvC,MAAI,CAAC,eAAe;AAClB,SAAK,OAAO,UAAU,aAAa;AACnC,SAAK,OAAO,UAAU,aAAa;AAAA,EACrC,OAAO;AAIL,UAAM,SAAK,gDAAoB,mBAAmB;AAClD,QAAI,IAAI,aAAa,OAAO,GAAG,OAAO,GAAG;AACzC,QAAI,IAAI,aAAa,MAAM,GAAG,MAAM,GAAG;AAAA,EACzC;AAGA,GAAC,EAAE,GAAG,EAAE,QAAI,uCAAW,GAAG,GAAG,SAAS;AAEtC,QAAM,kBAAc,6CAAsB;AAAA,IACxC,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AAED,SAAO;AAAA,IACL,aAAa;AAAA,MACX,WAAW,eAAe,KAAK,MAAM,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;AAAA,MAC3D,KAAK;AAAA,MACL,MAAM;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
6
6
  "names": ["overflows"]
7
7
  }
@@ -32,6 +32,7 @@ __export(floatingPositioning_exports, {
32
32
  applyShift: () => applyShift,
33
33
  expandWithVariations: () => expandWithVariations,
34
34
  fits: () => fits,
35
+ getBoundaryConstrainedRect: () => getBoundaryConstrainedRect,
35
36
  getClippingParent: () => getClippingParent,
36
37
  getOffsetParentData: () => getOffsetParentData,
37
38
  getOverflowScore: () => getOverflowScore,
@@ -77,6 +78,16 @@ const getViewportRect = () => ({
77
78
  right: window.innerWidth,
78
79
  bottom: window.innerHeight
79
80
  });
81
+ const getBoundaryConstrainedRect = (viewportRect, boundaryRect, floatingSize) => {
82
+ const fitsWidth = floatingSize.width <= boundaryRect.right - boundaryRect.left;
83
+ const fitsHeight = floatingSize.height <= boundaryRect.bottom - boundaryRect.top;
84
+ return {
85
+ top: fitsHeight ? Math.max(viewportRect.top, boundaryRect.top) : viewportRect.top,
86
+ bottom: fitsHeight ? Math.min(viewportRect.bottom, boundaryRect.bottom) : viewportRect.bottom,
87
+ left: fitsWidth ? Math.max(viewportRect.left, boundaryRect.left) : viewportRect.left,
88
+ right: fitsWidth ? Math.min(viewportRect.right, boundaryRect.right) : viewportRect.right
89
+ };
90
+ };
80
91
  const applyShift = (x, y, overflow) => {
81
92
  let nextX = x;
82
93
  let nextY = y;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/utils/floatingPositioning.ts", "../../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["import type { DSHookFloatingContextT } from '../react-desc-prop-types.js';\n\ntype RectLike = { top: number; left: number; right: number; bottom: number };\ntype OverflowOffsets = { top: number; bottom: number; left: number; right: number };\n\nconst findFixedParent = (el: Element | null) => {\n let element = el;\n while (element && element !== document.body) {\n const style = getComputedStyle(element);\n // IMPORTANT: only treat FIXED as special. Absolute parents are common and scroll,\n // and using their rect as an offset breaks positioning on scroll.\n if (style.position === 'fixed') {\n return element;\n }\n element = element.parentElement;\n }\n return null;\n};\n\nconst adjustForFixedParent = (child: Element) => {\n const fixedParent = findFixedParent(child);\n if (fixedParent) {\n const rect = fixedParent.getBoundingClientRect();\n return { top: rect.top, left: rect.left };\n }\n return { top: 0, left: 0 };\n};\n\n/**\n * Finds nearest ancestor that can CLIP the floating element.\n * This is typically the correct boundary when rendering without portal.\n */\nconst getClippingParent = (el: Element | null): Element | null => {\n let node = el?.parentElement ?? null;\n\n while (node && node !== document.body) {\n const style = getComputedStyle(node);\n\n const overflowX = style.overflowX || style.overflow;\n const overflowY = style.overflowY || style.overflow;\n\n const clipsX = /(auto|scroll|hidden|clip)/.test(overflowX);\n const clipsY = /(auto|scroll|hidden|clip)/.test(overflowY);\n\n if (clipsX || clipsY) return node;\n\n node = node.parentElement;\n }\n\n return null;\n};\n\nconst getViewportRect = (): RectLike => ({\n top: 0,\n left: 0,\n right: window.innerWidth,\n bottom: window.innerHeight,\n});\n\n/**\n * Shift behavior: if any overflow side is positive, push the popper back inside.\n * Similar to Floating UI \"shift\" middleware.\n */\nconst applyShift = (x: number, y: number, overflow: OverflowOffsets) => {\n let nextX = x;\n let nextY = y;\n\n if (overflow.left > 0) nextX += overflow.left;\n if (overflow.right > 0) nextX -= overflow.right;\n\n if (overflow.top > 0) nextY += overflow.top;\n if (overflow.bottom > 0) nextY -= overflow.bottom;\n\n return { x: nextX, y: nextY };\n};\n\nconst fits = (o: OverflowOffsets) => o.top <= 0 && o.bottom <= 0 && o.left <= 0 && o.right <= 0;\n\nconst getOverflowScore = (o: OverflowOffsets) => {\n const top = Math.max(0, o.top);\n const bottom = Math.max(0, o.bottom);\n const left = Math.max(0, o.left);\n const right = Math.max(0, o.right);\n\n const total = top + bottom + left + right;\n const maxSide = Math.max(top, bottom, left, right);\n\n return { total, maxSide };\n};\n\n/**\n * Ensure we actually TRY variations (start/end) for each base placement.\n * Fixes cases where only e.g. \"bottom-end\" fits but would never be tried.\n */\nconst expandWithVariations = (pls: DSHookFloatingContextT.PopperPlacementsT[]) => {\n const out: DSHookFloatingContextT.PopperPlacementsT[] = [];\n const seen = new Set<string>();\n\n const add = (p: string) => {\n if (!seen.has(p)) {\n seen.add(p);\n out.push(p as DSHookFloatingContextT.PopperPlacementsT);\n }\n };\n\n pls.forEach((p) => {\n add(p);\n\n const [base, variation] = p.split('-');\n if (!variation) {\n add(`${base}-start`);\n add(`${base}-end`);\n }\n });\n\n return out;\n};\n\n/**\n * When rendering WITHOUT portal, the floating element is positioned relative to its offsetParent.\n * But referenceRect/floatingRect are in viewport coordinates, so we must convert x/y from viewport\n * space into the offsetParent's coordinate space (including its scroll offsets).\n */\nconst getOffsetParentData = (floating: HTMLElement) => {\n const op = floating.offsetParent as HTMLElement | null;\n\n // If there's no offsetParent (rare), assume viewport.\n if (!op) {\n return { left: 0, top: 0, scrollLeft: 0, scrollTop: 0 };\n }\n\n const opRect = op.getBoundingClientRect();\n\n return {\n left: opRect.left,\n top: opRect.top,\n scrollLeft: op.scrollLeft ?? 0,\n scrollTop: op.scrollTop ?? 0,\n };\n};\n\nexport {\n applyShift,\n adjustForFixedParent,\n expandWithVariations,\n fits,\n getClippingParent,\n getOverflowScore,\n getOffsetParentData,\n getViewportRect,\n type RectLike,\n type OverflowOffsets,\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { DSHookFloatingContextT } from '../react-desc-prop-types.js';\n\ntype RectLike = { top: number; left: number; right: number; bottom: number };\ntype OverflowOffsets = { top: number; bottom: number; left: number; right: number };\n\nconst findFixedParent = (el: Element | null) => {\n let element = el;\n while (element && element !== document.body) {\n const style = getComputedStyle(element);\n // IMPORTANT: only treat FIXED as special. Absolute parents are common and scroll,\n // and using their rect as an offset breaks positioning on scroll.\n if (style.position === 'fixed') {\n return element;\n }\n element = element.parentElement;\n }\n return null;\n};\n\nconst adjustForFixedParent = (child: Element) => {\n const fixedParent = findFixedParent(child);\n if (fixedParent) {\n const rect = fixedParent.getBoundingClientRect();\n return { top: rect.top, left: rect.left };\n }\n return { top: 0, left: 0 };\n};\n\n/**\n * Finds nearest ancestor that can CLIP the floating element.\n * This is typically the correct boundary when rendering without portal.\n */\nconst getClippingParent = (el: Element | null): Element | null => {\n let node = el?.parentElement ?? null;\n\n while (node && node !== document.body) {\n const style = getComputedStyle(node);\n\n const overflowX = style.overflowX || style.overflow;\n const overflowY = style.overflowY || style.overflow;\n\n const clipsX = /(auto|scroll|hidden|clip)/.test(overflowX);\n const clipsY = /(auto|scroll|hidden|clip)/.test(overflowY);\n\n if (clipsX || clipsY) return node;\n\n node = node.parentElement;\n }\n\n return null;\n};\n\nconst getViewportRect = (): RectLike => ({\n top: 0,\n left: 0,\n right: window.innerWidth,\n bottom: window.innerHeight,\n});\n\n/**\n * Intersects the viewport with an explicit boundary (e.g. a scrollable ancestor the consumer\n * wants the floating element visually contained within), one axis at a time. An axis is only\n * constrained to the boundary when the floating element's own size actually fits within it \u2014\n * otherwise that axis falls back to the plain viewport. Without this fallback, content larger\n * than the boundary (e.g. a full calendar inside a short table row) gets forced into an\n * impossible position instead of being allowed to escape it, as it does today.\n */\nconst getBoundaryConstrainedRect = (\n viewportRect: RectLike,\n boundaryRect: RectLike,\n floatingSize: { width: number; height: number },\n): RectLike => {\n const fitsWidth = floatingSize.width <= boundaryRect.right - boundaryRect.left;\n const fitsHeight = floatingSize.height <= boundaryRect.bottom - boundaryRect.top;\n\n return {\n top: fitsHeight ? Math.max(viewportRect.top, boundaryRect.top) : viewportRect.top,\n bottom: fitsHeight ? Math.min(viewportRect.bottom, boundaryRect.bottom) : viewportRect.bottom,\n left: fitsWidth ? Math.max(viewportRect.left, boundaryRect.left) : viewportRect.left,\n right: fitsWidth ? Math.min(viewportRect.right, boundaryRect.right) : viewportRect.right,\n };\n};\n\n/**\n * Shift behavior: if any overflow side is positive, push the popper back inside.\n * Similar to Floating UI \"shift\" middleware.\n */\nconst applyShift = (x: number, y: number, overflow: OverflowOffsets) => {\n let nextX = x;\n let nextY = y;\n\n if (overflow.left > 0) nextX += overflow.left;\n if (overflow.right > 0) nextX -= overflow.right;\n\n if (overflow.top > 0) nextY += overflow.top;\n if (overflow.bottom > 0) nextY -= overflow.bottom;\n\n return { x: nextX, y: nextY };\n};\n\nconst fits = (o: OverflowOffsets) => o.top <= 0 && o.bottom <= 0 && o.left <= 0 && o.right <= 0;\n\nconst getOverflowScore = (o: OverflowOffsets) => {\n const top = Math.max(0, o.top);\n const bottom = Math.max(0, o.bottom);\n const left = Math.max(0, o.left);\n const right = Math.max(0, o.right);\n\n const total = top + bottom + left + right;\n const maxSide = Math.max(top, bottom, left, right);\n\n return { total, maxSide };\n};\n\n/**\n * Ensure we actually TRY variations (start/end) for each base placement.\n * Fixes cases where only e.g. \"bottom-end\" fits but would never be tried.\n */\nconst expandWithVariations = (pls: DSHookFloatingContextT.PopperPlacementsT[]) => {\n const out: DSHookFloatingContextT.PopperPlacementsT[] = [];\n const seen = new Set<string>();\n\n const add = (p: string) => {\n if (!seen.has(p)) {\n seen.add(p);\n out.push(p as DSHookFloatingContextT.PopperPlacementsT);\n }\n };\n\n pls.forEach((p) => {\n add(p);\n\n const [base, variation] = p.split('-');\n if (!variation) {\n add(`${base}-start`);\n add(`${base}-end`);\n }\n });\n\n return out;\n};\n\n/**\n * When rendering WITHOUT portal, the floating element is positioned relative to its offsetParent.\n * But referenceRect/floatingRect are in viewport coordinates, so we must convert x/y from viewport\n * space into the offsetParent's coordinate space (including its scroll offsets).\n */\nconst getOffsetParentData = (floating: HTMLElement) => {\n const op = floating.offsetParent as HTMLElement | null;\n\n // If there's no offsetParent (rare), assume viewport.\n if (!op) {\n return { left: 0, top: 0, scrollLeft: 0, scrollTop: 0 };\n }\n\n const opRect = op.getBoundingClientRect();\n\n return {\n left: opRect.left,\n top: opRect.top,\n scrollLeft: op.scrollLeft ?? 0,\n scrollTop: op.scrollTop ?? 0,\n };\n};\n\nexport {\n applyShift,\n adjustForFixedParent,\n expandWithVariations,\n fits,\n getBoundaryConstrainedRect,\n getClippingParent,\n getOverflowScore,\n getOffsetParentData,\n getViewportRect,\n type RectLike,\n type OverflowOffsets,\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import React2, { useMemo, useEffect } from "react";
2
+ import React2, { useCallback, useMemo } from "react";
3
3
  import {
4
4
  useMemoMergePropsWithDefault,
5
5
  useValidateTypescriptPropTypes,
@@ -9,8 +9,19 @@ import { useHeadlessTooltip } from "@elliemae/ds-hooks-headless-tooltip";
9
9
  import { defaultProps, DSFloatingContextPropTypes } from "./react-desc-prop-types.js";
10
10
  import { useComputedPositionStyles } from "./useComputedPositionStyles.js";
11
11
  import { usePositionObserver } from "./utils/positionObserver.js";
12
+ import { useLatestRef } from "./hooks/useLatestRef.js";
13
+ import { useResolvedReference } from "./hooks/useResolvedReference.js";
14
+ import { useFloatingClickOutside } from "./hooks/useFloatingClickOutside.js";
15
+ import { useFloatingResizeObserver } from "./hooks/useFloatingResizeObserver.js";
16
+ import { useReferenceHiddenByBoundary } from "./hooks/useReferenceHiddenByBoundary.js";
17
+ import { useSyntheticEventFromControlledState } from "./hooks/useSyntheticEventFromControlledState.js";
18
+ const isKeyboardEvent = (event) => !!event?.key;
12
19
  const useFloatingContext = (props = {}) => {
13
- const propsWithDefault = useMemoMergePropsWithDefault(props, defaultProps);
20
+ const { onClickOutside, onEscape, onOpen, onClose, externalReferenceElement, boundaryElement, ...stableProps } = props;
21
+ const propsWithDefault = useMemoMergePropsWithDefault(
22
+ stableProps,
23
+ defaultProps
24
+ );
14
25
  useValidateTypescriptPropTypes(propsWithDefault, DSFloatingContextPropTypes, "FloatingContext");
15
26
  const {
16
27
  withoutPortal,
@@ -20,68 +31,145 @@ const useFloatingContext = (props = {}) => {
20
31
  placement,
21
32
  customOffset,
22
33
  placementOrderPreference,
23
- onOpen,
24
- onClose,
25
- externallyControlledIsOpen
34
+ externallyControlledIsOpen,
35
+ closeOnEscape,
36
+ returnFocusToReference
26
37
  } = propsWithDefault;
38
+ const isControlled = externallyControlledIsOpen !== void 0;
39
+ const [floatingWrapperNode, setFloatingWrapperNode] = React2.useState(null);
40
+ const triggerElementReferenceRef = React2.useRef(null);
41
+ const onClickOutsideRef = useLatestRef(onClickOutside);
42
+ const onEscapeRef = useLatestRef(onEscape);
43
+ const onOpenRef = useLatestRef(onOpen);
44
+ const onCloseRef = useLatestRef(onClose);
27
45
  const [internalIsOpen, setInternalIsOpen] = React2.useState(false);
28
46
  const isOpen = useMemo(
29
47
  () => externallyControlledIsOpen !== void 0 ? externallyControlledIsOpen : internalIsOpen,
30
48
  [externallyControlledIsOpen, internalIsOpen]
31
49
  );
32
- const handleOpen = React2.useCallback(() => {
33
- setInternalIsOpen(true);
34
- onOpen?.();
35
- }, [onOpen]);
36
- const handleClose = React2.useCallback(() => {
37
- setInternalIsOpen(false);
38
- onClose?.();
39
- }, [onClose]);
40
- const tooltipHelpers = useHeadlessTooltip({ onOpen: handleOpen, onClose: handleClose });
41
- const { setReferenceElement, referenceElement, hideTooltip, showTooltip } = tooltipHelpers;
42
- const [floating, setFloating] = React2.useState(null);
50
+ const isControlledRef = useLatestRef(isControlled);
51
+ const closeOnEscapeRef = useLatestRef(closeOnEscape);
52
+ const returnFocusToReferenceRef = useLatestRef(returnFocusToReference);
53
+ const floatingWrapperNodeRef = useLatestRef(floatingWrapperNode);
54
+ const isOpenRef = useLatestRef(isOpen);
55
+ const handleOpen = React2.useCallback(
56
+ (event) => {
57
+ setInternalIsOpen(true);
58
+ onOpenRef.current?.({ reason: "interaction", event, isControlled: isControlledRef.current });
59
+ },
60
+ [onOpenRef, isControlledRef]
61
+ );
62
+ const isFocusWithinFloatingScope = useCallback(() => {
63
+ const active = document.activeElement;
64
+ if (!active) return false;
65
+ if (floatingWrapperNodeRef.current?.contains(active)) return true;
66
+ const reference = triggerElementReferenceRef.current;
67
+ return reference === active || !!reference?.contains(active);
68
+ }, [floatingWrapperNodeRef]);
69
+ const handleClose = React2.useCallback(
70
+ (event) => {
71
+ const wasOpen = isOpenRef.current;
72
+ setInternalIsOpen(false);
73
+ const isScopedEscapeClose = wasOpen && closeOnEscapeRef.current && isKeyboardEvent(event) && event.key === "Escape" && isFocusWithinFloatingScope();
74
+ if (isScopedEscapeClose) {
75
+ onEscapeRef.current?.(event);
76
+ if (returnFocusToReferenceRef.current && triggerElementReferenceRef.current instanceof HTMLElement) {
77
+ triggerElementReferenceRef.current.focus();
78
+ }
79
+ } else {
80
+ onCloseRef.current?.({ reason: "interaction", event, isControlled: isControlledRef.current });
81
+ }
82
+ },
83
+ [
84
+ isOpenRef,
85
+ closeOnEscapeRef,
86
+ isFocusWithinFloatingScope,
87
+ returnFocusToReferenceRef,
88
+ onCloseRef,
89
+ onEscapeRef,
90
+ isControlledRef
91
+ ]
92
+ );
93
+ const shouldFocusReferenceOnEscape = useCallback(
94
+ () => closeOnEscapeRef.current && returnFocusToReferenceRef.current && isFocusWithinFloatingScope(),
95
+ [closeOnEscapeRef, returnFocusToReferenceRef, isFocusWithinFloatingScope]
96
+ );
97
+ const tooltipHelpersConfig = useMemo(
98
+ () => ({ onOpen: handleOpen, onClose: handleClose, focusReferenceOnEscape: shouldFocusReferenceOnEscape }),
99
+ [handleOpen, handleClose, shouldFocusReferenceOnEscape]
100
+ );
101
+ const tooltipHelpers = useHeadlessTooltip(tooltipHelpersConfig);
102
+ const {
103
+ setReferenceElement: setInternalReferenceElement,
104
+ referenceElement: internalReferenceElement,
105
+ hideTooltip,
106
+ showTooltip
107
+ } = tooltipHelpers;
108
+ const { triggerElementReference, setReferenceElement } = useResolvedReference({
109
+ externalReferenceElement,
110
+ internalReferenceElement,
111
+ setInternalReferenceElement
112
+ });
113
+ triggerElementReferenceRef.current = triggerElementReference;
43
114
  const { arrowStyles, floatingStyles, hasComputedOnce, updateStyles, debouncedUpdateStyles, mutableUpdateStyles } = useComputedPositionStyles({
44
- reference: referenceElement,
45
- floating,
115
+ triggerElementReference,
116
+ floatingWrapperNode,
46
117
  placement,
47
118
  placementOrderPreference,
48
119
  customOffset,
49
120
  withoutPortal,
50
- preventComputing: !isOpen,
121
+ boundaryElement,
122
+ isClose: !isOpen,
51
123
  debounceMs: 150
52
124
  });
53
- useEffect(() => {
54
- if (!isOpen) return;
55
- const observers = [];
56
- if (floating) {
57
- const roFloating = new ResizeObserver(() => {
58
- mutableUpdateStyles.current();
59
- });
60
- roFloating.observe(floating);
61
- observers.push(roFloating);
62
- }
63
- return () => observers.forEach((o) => o.disconnect());
64
- }, [isOpen, floating, updateStyles, mutableUpdateStyles]);
125
+ const floatingResizeOberverConfig = useMemo(
126
+ () => ({ isOpen, floatingWrapperNode, onResize: () => mutableUpdateStyles.current() }),
127
+ [isOpen, floatingWrapperNode, mutableUpdateStyles]
128
+ );
129
+ useFloatingResizeObserver(floatingResizeOberverConfig);
65
130
  const onObservedMovement = React2.useCallback(() => {
66
131
  if (!isOpen) return;
67
132
  debouncedUpdateStyles();
68
133
  }, [isOpen, debouncedUpdateStyles]);
69
- usePositionObserver(referenceElement, onObservedMovement);
70
- const computedFloatingStyles = useMemo(
71
- () => ({
134
+ usePositionObserver(triggerElementReference, onObservedMovement);
135
+ const isReferenceHiddenByBoundary = useReferenceHiddenByBoundary({
136
+ reference: triggerElementReference,
137
+ boundaryElement,
138
+ enabled: isOpen && !!boundaryElement
139
+ });
140
+ useFloatingClickOutside({
141
+ isOpen,
142
+ floatingWrapperNode,
143
+ triggerElementReference,
144
+ onClickOutsideRef
145
+ });
146
+ const handleControlledOpen = useCallback(() => {
147
+ onOpenRef.current?.({ reason: "controlled-flip", isControlled: true });
148
+ }, [onOpenRef]);
149
+ const handleControlledClose = useCallback(() => {
150
+ onCloseRef.current?.({ reason: "controlled-flip", isControlled: true });
151
+ }, [onCloseRef]);
152
+ useSyntheticEventFromControlledState({
153
+ controlledValue: externallyControlledIsOpen,
154
+ onEnter: handleControlledOpen,
155
+ onExit: handleControlledClose
156
+ });
157
+ const computedFloatingStyles = useMemo(() => {
158
+ const isInteractive = isOpen && hasComputedOnce && !isReferenceHiddenByBoundary;
159
+ return {
72
160
  ...floatingStyles,
73
- visibility: isOpen && hasComputedOnce ? "visible" : "hidden"
74
- }),
75
- [floatingStyles, hasComputedOnce, isOpen]
76
- );
161
+ opacity: isInteractive ? 1 : 0,
162
+ pointerEvents: isInteractive ? "auto" : "none"
163
+ };
164
+ }, [floatingStyles, hasComputedOnce, isOpen, isReferenceHiddenByBoundary]);
77
165
  const refs = useMemo(
78
166
  () => ({
79
167
  setReference: setReferenceElement,
80
- setFloating,
81
- floating,
82
- reference: referenceElement
168
+ setFloating: setFloatingWrapperNode,
169
+ floating: floatingWrapperNode,
170
+ reference: triggerElementReference
83
171
  }),
84
- [setReferenceElement, floating, referenceElement]
172
+ [setReferenceElement, floatingWrapperNode, triggerElementReference]
85
173
  );
86
174
  const handlers = useMemo(
87
175
  () => ({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../scripts/build/transpile/react-shim.js", "../../src/DSFloatingContext.tsx"],
4
- "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable max-lines */\n/* eslint-disable consistent-return */\n/* eslint-disable max-statements */\nimport React, { useMemo, useEffect } from 'react';\nimport {\n useMemoMergePropsWithDefault,\n useValidateTypescriptPropTypes,\n describe,\n type ValidationMap,\n} from '@elliemae/ds-props-helpers';\nimport { useHeadlessTooltip } from '@elliemae/ds-hooks-headless-tooltip';\nimport type { DSHookFloatingContextT } from './react-desc-prop-types.js';\nimport { defaultProps, DSFloatingContextPropTypes } from './react-desc-prop-types.js';\nimport { useComputedPositionStyles } from './useComputedPositionStyles.js';\nimport { usePositionObserver } from './utils/positionObserver.js';\n\nconst useFloatingContext = (props: DSHookFloatingContextT.Props = {}) => {\n const propsWithDefault = useMemoMergePropsWithDefault<DSHookFloatingContextT.InternalProps>(props, defaultProps);\n useValidateTypescriptPropTypes(propsWithDefault, DSFloatingContextPropTypes, 'FloatingContext');\n\n const {\n withoutPortal,\n withoutAnimation,\n portalDOMContainer,\n animationDuration,\n placement,\n customOffset,\n placementOrderPreference,\n onOpen,\n onClose,\n externallyControlledIsOpen,\n } = propsWithDefault;\n\n const [internalIsOpen, setInternalIsOpen] = React.useState<boolean>(false);\n const isOpen = useMemo(\n () => (externallyControlledIsOpen !== undefined ? externallyControlledIsOpen : internalIsOpen),\n [externallyControlledIsOpen, internalIsOpen],\n );\n\n const handleOpen = React.useCallback(() => {\n setInternalIsOpen(true);\n onOpen?.();\n }, [onOpen]);\n\n const handleClose = React.useCallback(() => {\n setInternalIsOpen(false);\n onClose?.();\n }, [onClose]);\n\n const tooltipHelpers = useHeadlessTooltip({ onOpen: handleOpen, onClose: handleClose });\n const { setReferenceElement, referenceElement, hideTooltip, showTooltip } = tooltipHelpers;\n\n // Anchor and floating refs\n const [floating, setFloating] = React.useState<HTMLElement | null>(null);\n\n // Compute position (skip when closed)\n const { arrowStyles, floatingStyles, hasComputedOnce, updateStyles, debouncedUpdateStyles, mutableUpdateStyles } =\n useComputedPositionStyles({\n reference: referenceElement,\n floating,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n preventComputing: !isOpen,\n debounceMs: 150,\n });\n\n useEffect(() => {\n if (!isOpen) return;\n\n const observers: ResizeObserver[] = [];\n\n if (floating) {\n const roFloating = new ResizeObserver(() => {\n mutableUpdateStyles.current();\n });\n roFloating.observe(floating);\n observers.push(roFloating);\n }\n\n return () => observers.forEach((o) => o.disconnect());\n }, [isOpen, floating, updateStyles, mutableUpdateStyles]);\n\n const onObservedMovement = React.useCallback(() => {\n if (!isOpen) return;\n debouncedUpdateStyles();\n }, [isOpen, debouncedUpdateStyles]);\n usePositionObserver(referenceElement, onObservedMovement);\n\n // Expose visibility: only visible if open and already computed at least once\n const computedFloatingStyles = useMemo<React.CSSProperties>(\n () => ({\n ...floatingStyles,\n visibility: isOpen && hasComputedOnce ? 'visible' : 'hidden',\n }),\n [floatingStyles, hasComputedOnce, isOpen],\n );\n\n const refs = useMemo(\n () => ({\n setReference: setReferenceElement,\n setFloating,\n floating,\n reference: referenceElement,\n }),\n [setReferenceElement, floating, referenceElement],\n );\n\n const handlers = useMemo(\n () => ({\n onMouseEnter: tooltipHelpers.onMouseEnter,\n onMouseLeave: tooltipHelpers.onMouseLeave,\n onFocus: tooltipHelpers.onFocus,\n onBlur: tooltipHelpers.onBlur,\n }),\n [tooltipHelpers.onBlur, tooltipHelpers.onFocus, tooltipHelpers.onMouseEnter, tooltipHelpers.onMouseLeave],\n );\n\n return useMemo(\n () => ({\n refs,\n floatingStyles: computedFloatingStyles,\n handlers,\n isOpen,\n arrowStyles,\n hideTooltip,\n showTooltip,\n context: {\n withoutPortal,\n withoutAnimation,\n portalDOMContainer,\n animationDuration,\n },\n mutableUpdateStyles,\n forceUpdatePosition: updateStyles,\n }),\n [\n refs,\n computedFloatingStyles,\n handlers,\n isOpen,\n arrowStyles,\n hideTooltip,\n showTooltip,\n withoutPortal,\n withoutAnimation,\n portalDOMContainer,\n animationDuration,\n mutableUpdateStyles,\n updateStyles,\n ],\n );\n};\n\nuseFloatingContext.displayName = 'FloatingContext';\nconst UseFloatingContextWithSchema = describe(useFloatingContext);\nUseFloatingContextWithSchema.propTypes =\n DSFloatingContextPropTypes as unknown as ValidationMap<DSHookFloatingContextT.Props>;\n\nexport { useFloatingContext, UseFloatingContextWithSchema };\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable max-lines */\n/* eslint-disable max-statements */\nimport React, { useCallback, useMemo } from 'react';\nimport {\n useMemoMergePropsWithDefault,\n useValidateTypescriptPropTypes,\n describe,\n type ValidationMap,\n} from '@elliemae/ds-props-helpers';\nimport { useHeadlessTooltip, type UseHeadlessTooltipConfig } from '@elliemae/ds-hooks-headless-tooltip';\nimport type { DSHookFloatingContextT } from './react-desc-prop-types.js';\nimport { defaultProps, DSFloatingContextPropTypes } from './react-desc-prop-types.js';\nimport { useComputedPositionStyles } from './useComputedPositionStyles.js';\nimport { usePositionObserver } from './utils/positionObserver.js';\nimport { useLatestRef } from './hooks/useLatestRef.js';\nimport { useResolvedReference } from './hooks/useResolvedReference.js';\nimport { useFloatingClickOutside } from './hooks/useFloatingClickOutside.js';\nimport { useFloatingResizeObserver } from './hooks/useFloatingResizeObserver.js';\nimport { useReferenceHiddenByBoundary } from './hooks/useReferenceHiddenByBoundary.js';\nimport { useSyntheticEventFromControlledState } from './hooks/useSyntheticEventFromControlledState.js';\n\nconst isKeyboardEvent = (\n event: React.FocusEvent | React.MouseEvent | KeyboardEvent | undefined,\n): event is KeyboardEvent => !!(event as KeyboardEvent)?.key;\n\n/**\n * useFloatingContext \u2014 headless positioning + open/close lifecycle for a floating surface\n * (popover / tooltip / dialog-like panel). Returns positioning styles + refs, imperative show/hide\n * helpers, interaction handlers to spread on a trigger, and a discriminated open/close notification.\n *\n * COUPLING: this hook is the shared substrate under ds-app-picker, ds-tooltip and others; its\n * open/close/escape/reason contract is relied on across packages. The consumer that leans hardest on\n * the exact semantics below is ds-app-picker\n * (layout/ds-app-picker/src/parts/AppPickerFloatingContext/useAppPickerFloatingContext.ts). Treat\n * changes to these semantics as cross-package.\n *\n * OPEN STATE \u2014 controlled vs uncontrolled\n * - Uncontrolled: `externallyControlledIsOpen` undefined \u2192 open state lives here (in the underlying\n * ds-hooks-headless-tooltip) and is driven by the focus/hover handlers + the show/hide helpers.\n * - Controlled: `externallyControlledIsOpen` is a boolean \u2192 the consumer owns visibility and the\n * internal open state is inert. `isControlled` is surfaced on every OpenChange payload.\n *\n * NOTIFICATION \u2014 one onOpen/onClose pair, two reasons (see OpenChange in react-desc-prop-types)\n * - 'interaction': fired synchronously in the event catch phase by this hook's own interaction\n * handling \u2014 focus/hover/blur/mouseleave, an imperative show/hide, or a NON-scoped Escape falling\n * through to onClose \u2014 carrying the DOM event when there is one. Fires for controlled contexts too\n * (the pre-flip signal a controlled consumer may act on).\n * - 'controlled-flip': fired from a post-commit effect (useSyntheticEventFromControlledState) when\n * `externallyControlledIsOpen` transitions. No event. NEVER fires on mount \u2014 a surface rendered\n * already-open gets no onOpen and must seed any initial focus itself.\n *\n * ESCAPE \u2014 one listener, not two\n * Escape dismissal rides ds-hooks-headless-tooltip's single always-on document keydown listener (it\n * calls hideTooltip on ANY Escape, even when this context never opened). handleClose is the one place\n * that interprets it:\n * - SCOPED Escape (was open + closeOnEscape + key 'Escape' + focus within the panel/reference)\n * \u2192 routes to `onEscape` (NOT onClose); if returnFocusToReference, focuses the reference.\n * - anything else, including a non-scoped Escape \u2192 `onClose` with reason 'interaction'.\n * returnFocusToReference is a SCOPED-ESCAPE-ONLY affordance; no other close path moves focus.\n *\n * FRAGILITY: closeOnEscape is honored independently of who owns open state. A CONTROLLED consumer that\n * leaves closeOnEscape on but does not itself close on Escape will have focus returned to the reference\n * on a scoped Escape while the consumer-owned open state has not changed \u2014 focus can leave a panel that\n * stays open. Controlled consumers should own Escape (ds-app-picker uses `closeOnEscape: !onKeyDown`).\n */\nconst useFloatingContext = (props: DSHookFloatingContextT.Props = {}) => {\n // Extract unstable callback refs BEFORE useMemoMergePropsWithDefault.\n // useMemoMergePropsWithDefault compares with fast-deep-equal which compares functions by\n // reference; an inline callback from the consumer would invalidate propsWithDefault on every\n // render, which in turn would invalidate the destructured `customOffset` array and re-trigger\n // the position-computation useLayoutEffect on every render \u2192 infinite loop.\n const { onClickOutside, onEscape, onOpen, onClose, externalReferenceElement, boundaryElement, ...stableProps } =\n props;\n\n const propsWithDefault = useMemoMergePropsWithDefault<DSHookFloatingContextT.InternalProps>(\n stableProps,\n defaultProps,\n );\n useValidateTypescriptPropTypes(propsWithDefault, DSFloatingContextPropTypes, 'FloatingContext');\n\n const {\n withoutPortal,\n withoutAnimation,\n portalDOMContainer,\n animationDuration,\n placement,\n customOffset,\n placementOrderPreference,\n externallyControlledIsOpen,\n closeOnEscape,\n returnFocusToReference,\n } = propsWithDefault;\n // Whether the consumer owns the open state. Surfaced on the OpenChange payload so a handler can\n // branch without re-reading props.\n const isControlled = externallyControlledIsOpen !== undefined;\n // Anchor and floating refs\n const [floatingWrapperNode, setFloatingWrapperNode] = React.useState<HTMLElement | null>(null);\n\n // Resolved below (after useResolvedReference) into a stable ref rather than referenced directly,\n // because isFocusWithinFloatingScope / the escape-handling below need to read it from a config\n // object built BEFORE useResolvedReference (and the useHeadlessTooltip call it depends on) runs.\n // A ref sidesteps that ordering constraint; the value it holds is always current by the time\n // anything actually reads it (Escape handling only ever happens after mount, never during this\n // synchronous render pass).\n const triggerElementReferenceRef = React.useRef<Element | null>(null);\n\n // Latest-refs for consumer callbacks (see useLatestRef docs).\n const onClickOutsideRef = useLatestRef(onClickOutside);\n const onEscapeRef = useLatestRef(onEscape);\n const onOpenRef = useLatestRef(onOpen);\n const onCloseRef = useLatestRef(onClose);\n\n // Open/close state \u2014 externally controlled when `externallyControlledIsOpen` is provided.\n const [internalIsOpen, setInternalIsOpen] = React.useState(false);\n const isOpen = useMemo(\n () => (externallyControlledIsOpen !== undefined ? externallyControlledIsOpen : internalIsOpen),\n [externallyControlledIsOpen, internalIsOpen],\n );\n\n // Latest-refs for the reactive VALUES that handleOpen/handleClose read. These handlers are wired\n // into ds-hooks-headless-tooltip's always-on document keydown listener; if a handler's identity\n // changed whenever one of these values changed (isOpen flips on every open/close,\n // floatingWrapperNode on every mount/unmount), that listener would detach + re-attach on every\n // transition. Reading through refs keeps the handlers identity-stable so the listener attaches\n // once. Handlers only ever run post-commit, so the ref always holds the current value when read \u2014\n // same rationale as the consumer-callback refs above.\n const isControlledRef = useLatestRef(isControlled);\n const closeOnEscapeRef = useLatestRef(closeOnEscape);\n const returnFocusToReferenceRef = useLatestRef(returnFocusToReference);\n const floatingWrapperNodeRef = useLatestRef(floatingWrapperNode);\n const isOpenRef = useLatestRef(isOpen);\n\n const handleOpen = React.useCallback<Required<UseHeadlessTooltipConfig>['onOpen']>(\n (event) => {\n setInternalIsOpen(true);\n onOpenRef.current?.({ reason: 'interaction', event, isControlled: isControlledRef.current });\n },\n [onOpenRef, isControlledRef],\n );\n\n // \"In scope\" = the active element is inside the floating (popover) content, or is/inside the\n // reference (trigger) element. Used to decide whether an Escape is this floating context's concern.\n const isFocusWithinFloatingScope = useCallback((): boolean => {\n const active = document.activeElement;\n if (!active) return false;\n if (floatingWrapperNodeRef.current?.contains(active)) return true;\n const reference = triggerElementReferenceRef.current;\n return reference === active || !!reference?.contains(active);\n }, [floatingWrapperNodeRef]);\n\n const handleClose = React.useCallback<Required<UseHeadlessTooltipConfig>['onClose']>(\n (event) => {\n // Read before flipping state: a global Escape press can arrive while this context is\n // already closed (the underlying listener is always-on, see ds-hooks-headless-tooltip), and\n // onEscape must not fire for that \u2014 only a close that was actually transitioning from open.\n const wasOpen = isOpenRef.current;\n setInternalIsOpen(false);\n\n // Escape routing: when the context was open, `closeOnEscape` is set, the event is a keyboard\n // Escape, AND focus was within scope, we route to the dedicated `onEscape` callback (instead\n // of `onClose`) and, if asked, return focus to the reference. Every other close \u2014 including a\n // non-opted-in Escape \u2014 goes through `onClose` with `reason: 'interaction'`. Whether that\n // interaction is meaningful to a controlled consumer is THEIR call: they discriminate on the\n // OpenChange `reason` (interaction vs controlled-flip). We do not decide that for them here.\n // All reactive values are read through refs (see the latest-ref block above) so this handler's\n // identity stays stable and the always-on keydown listener is not re-attached per transition.\n const isScopedEscapeClose =\n wasOpen &&\n closeOnEscapeRef.current &&\n isKeyboardEvent(event) &&\n event.key === 'Escape' &&\n isFocusWithinFloatingScope();\n\n if (isScopedEscapeClose) {\n onEscapeRef.current?.(event);\n // Focus the resolved reference directly rather than relying solely on\n // ds-hooks-headless-tooltip's own focusReferenceOnEscape: that mechanism focuses the\n // hook's *internal* reference element, which stays null whenever this context is\n // configured with `externalReferenceElement` (useResolvedReference makes\n // refs.setReference a no-op in that mode \u2014 see its docstring). triggerElementReferenceRef\n // is always the correctly-resolved element (internal or external), so focusing it here\n // works for both.\n if (returnFocusToReferenceRef.current && triggerElementReferenceRef.current instanceof HTMLElement) {\n triggerElementReferenceRef.current.focus();\n }\n } else {\n onCloseRef.current?.({ reason: 'interaction', event, isControlled: isControlledRef.current });\n }\n },\n [\n isOpenRef,\n closeOnEscapeRef,\n isFocusWithinFloatingScope,\n returnFocusToReferenceRef,\n onCloseRef,\n onEscapeRef,\n isControlledRef,\n ],\n );\n\n // Passed as a thunk (not a pre-computed boolean) into useHeadlessTooltip's config: this config\n // object is built before useResolvedReference runs, so a boolean computed here would be evaluated\n // one render behind the reference element it depends on. Deferring the check to actual\n // Escape-handling time (inside ds-hooks-headless-tooltip) avoids that staleness.\n // Only drives ds-hooks-headless-tooltip's *internal*-reference focus path (see handleClose above\n // for why the externally-resolved reference is focused directly instead).\n const shouldFocusReferenceOnEscape = useCallback(\n () => closeOnEscapeRef.current && returnFocusToReferenceRef.current && isFocusWithinFloatingScope(),\n [closeOnEscapeRef, returnFocusToReferenceRef, isFocusWithinFloatingScope],\n );\n const tooltipHelpersConfig: UseHeadlessTooltipConfig = useMemo(\n () => ({ onOpen: handleOpen, onClose: handleClose, focusReferenceOnEscape: shouldFocusReferenceOnEscape }),\n [handleOpen, handleClose, shouldFocusReferenceOnEscape],\n );\n // Headless tooltip exposes show/hide helpers + manages its own internal state.\n const tooltipHelpers = useHeadlessTooltip(tooltipHelpersConfig);\n const {\n setReferenceElement: setInternalReferenceElement,\n referenceElement: internalReferenceElement,\n hideTooltip,\n showTooltip,\n } = tooltipHelpers;\n\n // Reference element resolution: external takes precedence over internal.\n const { triggerElementReference, setReferenceElement } = useResolvedReference({\n externalReferenceElement,\n internalReferenceElement,\n setInternalReferenceElement,\n });\n // Keep the ref declared above in sync \u2014 see its declaration comment for why this can't just be\n // read directly from isFocusWithinFloatingScope/handleClose without going through a ref first.\n triggerElementReferenceRef.current = triggerElementReference;\n\n // Compute position (skip when closed)\n const { arrowStyles, floatingStyles, hasComputedOnce, updateStyles, debouncedUpdateStyles, mutableUpdateStyles } =\n useComputedPositionStyles({\n triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n boundaryElement,\n isClose: !isOpen,\n debounceMs: 150,\n });\n\n const floatingResizeOberverConfig = useMemo(\n () => ({ isOpen, floatingWrapperNode, onResize: () => mutableUpdateStyles.current() }),\n [isOpen, floatingWrapperNode, mutableUpdateStyles],\n );\n\n // Re-compute position when the floating element's size changes (e.g. async content).\n useFloatingResizeObserver(floatingResizeOberverConfig);\n\n // Re-compute position when the reference element moves in the viewport.\n const onObservedMovement = React.useCallback(() => {\n if (!isOpen) return;\n debouncedUpdateStyles();\n }, [isOpen, debouncedUpdateStyles]);\n usePositionObserver(triggerElementReference, onObservedMovement);\n\n // Reference fully scrolled out of boundaryElement's visible area (opt-in, paired with\n // boundaryElement): a clamped position alone would leave the floating element stuck at the\n // boundary's edge with nothing left to visually anchor it to. This does NOT close the\n // floating element (isOpen/consumer state is untouched) \u2014 it's purely visual, symmetric with\n // the reappear case, so scrolling the reference back into view shows the same still-open\n // floating element again with no re-click needed.\n const isReferenceHiddenByBoundary = useReferenceHiddenByBoundary({\n reference: triggerElementReference,\n boundaryElement,\n enabled: isOpen && !!boundaryElement,\n });\n\n // Click-outside dismissal (opt-in via `onClickOutside`).\n useFloatingClickOutside({\n isOpen,\n floatingWrapperNode,\n triggerElementReference,\n onClickOutsideRef,\n });\n\n // Route controlled-flip transitions through the SAME onOpen/onClose callbacks, tagged\n // `reason: 'controlled-flip'`. The interaction handlers above emit `reason: 'interaction'` in the\n // event catch phase; these cover the transitions driven by the consumer flipping\n // `externallyControlledIsOpen`, which have no interaction to hang off \u2014 the sanctioned \"sync with\n // external system\" effect (see the hook). One callback, one contract; `reason` discriminates.\n const handleControlledOpen = useCallback(() => {\n onOpenRef.current?.({ reason: 'controlled-flip', isControlled: true });\n }, [onOpenRef]);\n const handleControlledClose = useCallback(() => {\n onCloseRef.current?.({ reason: 'controlled-flip', isControlled: true });\n }, [onCloseRef]);\n useSyntheticEventFromControlledState({\n controlledValue: externallyControlledIsOpen,\n onEnter: handleControlledOpen,\n onExit: handleControlledClose,\n });\n\n // Expose visibility via opacity (NOT visibility:hidden \u2014 that blocks programmatic focus,\n // breaking autoFocus on first-open before the position-computation effect can run).\n // When `externalReferenceElement` is provided, computation runs on the first useLayoutEffect\n // (reference is known synchronously), so the popper becomes interactive in the same commit.\n const computedFloatingStyles = useMemo<React.CSSProperties>(() => {\n const isInteractive = isOpen && hasComputedOnce && !isReferenceHiddenByBoundary;\n return {\n ...floatingStyles,\n opacity: isInteractive ? 1 : 0,\n pointerEvents: isInteractive ? 'auto' : 'none',\n };\n }, [floatingStyles, hasComputedOnce, isOpen, isReferenceHiddenByBoundary]);\n\n const refs = useMemo(\n () => ({\n setReference: setReferenceElement,\n setFloating: setFloatingWrapperNode,\n floating: floatingWrapperNode,\n reference: triggerElementReference,\n }),\n [setReferenceElement, floatingWrapperNode, triggerElementReference],\n );\n\n const handlers = useMemo(\n () => ({\n onMouseEnter: tooltipHelpers.onMouseEnter,\n onMouseLeave: tooltipHelpers.onMouseLeave,\n onFocus: tooltipHelpers.onFocus,\n onBlur: tooltipHelpers.onBlur,\n }),\n [tooltipHelpers.onBlur, tooltipHelpers.onFocus, tooltipHelpers.onMouseEnter, tooltipHelpers.onMouseLeave],\n );\n\n return useMemo(\n () => ({\n refs,\n floatingStyles: computedFloatingStyles,\n handlers,\n isOpen,\n arrowStyles,\n hideTooltip,\n showTooltip,\n context: {\n withoutPortal,\n withoutAnimation,\n portalDOMContainer,\n animationDuration,\n },\n mutableUpdateStyles,\n forceUpdatePosition: updateStyles,\n }),\n [\n refs,\n computedFloatingStyles,\n handlers,\n isOpen,\n arrowStyles,\n hideTooltip,\n showTooltip,\n withoutPortal,\n withoutAnimation,\n portalDOMContainer,\n animationDuration,\n mutableUpdateStyles,\n updateStyles,\n ],\n );\n};\n\nuseFloatingContext.displayName = 'FloatingContext';\nconst UseFloatingContextWithSchema = describe(useFloatingContext);\nUseFloatingContextWithSchema.propTypes =\n DSFloatingContextPropTypes as unknown as ValidationMap<DSHookFloatingContextT.Props>;\n\nexport { useFloatingContext, UseFloatingContextWithSchema };\n"],
5
+ "mappings": "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",
6
6
  "names": ["React"]
7
7
  }
@@ -0,0 +1,30 @@
1
+ import * as React from "react";
2
+ import { useEffect } from "react";
3
+ const useFloatingClickOutside = ({
4
+ isOpen,
5
+ floatingWrapperNode,
6
+ triggerElementReference,
7
+ onClickOutsideRef
8
+ }) => {
9
+ useEffect(() => {
10
+ if (!isOpen || !floatingWrapperNode || !triggerElementReference) return void 0;
11
+ const listener = (event) => {
12
+ const cb = onClickOutsideRef.current;
13
+ if (!cb) return;
14
+ const target = event.target;
15
+ if (!target) return;
16
+ if (floatingWrapperNode.contains(target) || triggerElementReference.contains(target)) return;
17
+ cb(event);
18
+ };
19
+ document.addEventListener("mousedown", listener, true);
20
+ document.addEventListener("touchstart", listener, true);
21
+ return () => {
22
+ document.removeEventListener("mousedown", listener, true);
23
+ document.removeEventListener("touchstart", listener, true);
24
+ };
25
+ }, [floatingWrapperNode, triggerElementReference, onClickOutsideRef, isOpen]);
26
+ };
27
+ export {
28
+ useFloatingClickOutside
29
+ };
30
+ //# sourceMappingURL=useFloatingClickOutside.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../scripts/build/transpile/react-shim.js", "../../../src/hooks/useFloatingClickOutside.ts"],
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { useEffect, type MutableRefObject } from 'react';\n\ninterface UseFloatingClickOutsideParams {\n isOpen: boolean;\n floatingWrapperNode: HTMLElement | null;\n triggerElementReference: Element | null;\n /**\n * Latest-ref of the click-outside callback. Passed as a ref (rather than a value)\n * so consumers can pass inline functions without re-attaching listeners on every render.\n * Use `useLatestRef(onClickOutside)` to construct it.\n */\n onClickOutsideRef: MutableRefObject<((event: MouseEvent | TouchEvent) => void) | undefined>;\n}\n\n/**\n * Attaches mousedown/touchstart listeners on the document that fire when the click target\n * is outside BOTH the floating element and the reference element.\n *\n * Only active when `enabled === true` and both elements exist.\n */\nexport const useFloatingClickOutside = ({\n isOpen,\n floatingWrapperNode,\n triggerElementReference,\n onClickOutsideRef,\n}: UseFloatingClickOutsideParams) => {\n useEffect(() => {\n if (!isOpen || !floatingWrapperNode || !triggerElementReference) return undefined;\n\n const listener = (event: MouseEvent | TouchEvent) => {\n const cb = onClickOutsideRef.current;\n if (!cb) return;\n const target = event.target as Node | null;\n if (!target) return;\n if (floatingWrapperNode.contains(target) || triggerElementReference.contains(target)) return;\n cb(event);\n };\n\n document.addEventListener('mousedown', listener, true);\n document.addEventListener('touchstart', listener, true);\n return () => {\n document.removeEventListener('mousedown', listener, true);\n document.removeEventListener('touchstart', listener, true);\n };\n }, [floatingWrapperNode, triggerElementReference, onClickOutsideRef, isOpen]);\n};\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;ACAvB,SAAS,iBAAwC;AAoB1C,MAAM,0BAA0B,CAAC;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAqC;AACnC,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,CAAC,uBAAuB,CAAC,wBAAyB,QAAO;AAExE,UAAM,WAAW,CAAC,UAAmC;AACnD,YAAM,KAAK,kBAAkB;AAC7B,UAAI,CAAC,GAAI;AACT,YAAM,SAAS,MAAM;AACrB,UAAI,CAAC,OAAQ;AACb,UAAI,oBAAoB,SAAS,MAAM,KAAK,wBAAwB,SAAS,MAAM,EAAG;AACtF,SAAG,KAAK;AAAA,IACV;AAEA,aAAS,iBAAiB,aAAa,UAAU,IAAI;AACrD,aAAS,iBAAiB,cAAc,UAAU,IAAI;AACtD,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,UAAU,IAAI;AACxD,eAAS,oBAAoB,cAAc,UAAU,IAAI;AAAA,IAC3D;AAAA,EACF,GAAG,CAAC,qBAAqB,yBAAyB,mBAAmB,MAAM,CAAC;AAC9E;",
6
+ "names": []
7
+ }
@@ -0,0 +1,44 @@
1
+ import * as React from "react";
2
+ import { useCallback, useEffect } from "react";
3
+ const useFloatingEscape = ({
4
+ enabled,
5
+ floating,
6
+ reference,
7
+ onEscapeRef,
8
+ handleClose,
9
+ returnFocusToReference
10
+ }) => {
11
+ const isFocusWithinFloatingScope = useCallback(
12
+ (active) => {
13
+ if (!active) return false;
14
+ if (floating && floating.contains(active)) return true;
15
+ if (!reference) return false;
16
+ return reference === active || reference.contains(active);
17
+ },
18
+ [floating, reference]
19
+ );
20
+ useEffect(() => {
21
+ if (!enabled) return void 0;
22
+ const listener = (event) => {
23
+ if (event.key !== "Escape") return;
24
+ if (!isFocusWithinFloatingScope(document.activeElement)) return;
25
+ const cb = onEscapeRef.current;
26
+ if (cb) {
27
+ cb(event);
28
+ } else {
29
+ handleClose();
30
+ }
31
+ if (returnFocusToReference && reference instanceof HTMLElement) {
32
+ reference.focus();
33
+ }
34
+ };
35
+ document.addEventListener("keydown", listener, true);
36
+ return () => {
37
+ document.removeEventListener("keydown", listener, true);
38
+ };
39
+ }, [enabled, handleClose, isFocusWithinFloatingScope, onEscapeRef, reference, returnFocusToReference]);
40
+ };
41
+ export {
42
+ useFloatingEscape
43
+ };
44
+ //# sourceMappingURL=useFloatingEscape.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../scripts/build/transpile/react-shim.js", "../../../src/hooks/useFloatingEscape.ts"],
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { useCallback, useEffect, type MutableRefObject } from 'react';\n\ninterface UseFloatingEscapeParams {\n enabled: boolean;\n floating: HTMLElement | null;\n reference: Element | null;\n /** Latest-ref of the consumer's onEscape callback. Construct with `useLatestRef(onEscape)`. */\n onEscapeRef: MutableRefObject<((event: KeyboardEvent) => void) | undefined>;\n /** Fallback close handler invoked when no `onEscape` is provided. */\n handleClose: () => void;\n /** When true, calls `reference.focus()` after the escape close. */\n returnFocusToReference: boolean;\n}\n\n/**\n * Listens for `Escape` on the document and dismisses the floating element when focus is\n * within the floating element or on the reference element. Outside that scope the event is\n * ignored \u2014 we don't hijack Escape for unrelated UI on the page.\n */\nexport const useFloatingEscape = ({\n enabled,\n floating,\n reference,\n onEscapeRef,\n handleClose,\n returnFocusToReference,\n}: UseFloatingEscapeParams) => {\n const isFocusWithinFloatingScope = useCallback(\n (active: Element | null): boolean => {\n if (!active) return false;\n if (floating && floating.contains(active)) return true;\n if (!reference) return false;\n return reference === active || reference.contains(active);\n },\n [floating, reference],\n );\n\n useEffect(() => {\n if (!enabled) return undefined;\n\n const listener = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n if (!isFocusWithinFloatingScope(document.activeElement)) return;\n\n const cb = onEscapeRef.current;\n if (cb) {\n cb(event);\n } else {\n handleClose();\n }\n if (returnFocusToReference && reference instanceof HTMLElement) {\n reference.focus();\n }\n };\n\n document.addEventListener('keydown', listener, true);\n return () => {\n document.removeEventListener('keydown', listener, true);\n };\n }, [enabled, handleClose, isFocusWithinFloatingScope, onEscapeRef, reference, returnFocusToReference]);\n};\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;ACAvB,SAAS,aAAa,iBAAwC;AAmBvD,MAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAA+B;AAC7B,QAAM,6BAA6B;AAAA,IACjC,CAAC,WAAoC;AACnC,UAAI,CAAC,OAAQ,QAAO;AACpB,UAAI,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AAClD,UAAI,CAAC,UAAW,QAAO;AACvB,aAAO,cAAc,UAAU,UAAU,SAAS,MAAM;AAAA,IAC1D;AAAA,IACA,CAAC,UAAU,SAAS;AAAA,EACtB;AAEA,YAAU,MAAM;AACd,QAAI,CAAC,QAAS,QAAO;AAErB,UAAM,WAAW,CAAC,UAAyB;AACzC,UAAI,MAAM,QAAQ,SAAU;AAC5B,UAAI,CAAC,2BAA2B,SAAS,aAAa,EAAG;AAEzD,YAAM,KAAK,YAAY;AACvB,UAAI,IAAI;AACN,WAAG,KAAK;AAAA,MACV,OAAO;AACL,oBAAY;AAAA,MACd;AACA,UAAI,0BAA0B,qBAAqB,aAAa;AAC9D,kBAAU,MAAM;AAAA,MAClB;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,UAAU,IAAI;AACnD,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,UAAU,IAAI;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,SAAS,aAAa,4BAA4B,aAAa,WAAW,sBAAsB,CAAC;AACvG;",
6
+ "names": []
7
+ }
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+ import { useEffect, useRef } from "react";
3
+ const useFloatingResizeObserver = ({
4
+ isOpen,
5
+ floatingWrapperNode,
6
+ onResize
7
+ }) => {
8
+ const onResizeRef = useRef(onResize);
9
+ onResizeRef.current = onResize;
10
+ useEffect(() => {
11
+ if (!isOpen || !floatingWrapperNode) return void 0;
12
+ const observer = new ResizeObserver(() => {
13
+ onResizeRef.current();
14
+ });
15
+ observer.observe(floatingWrapperNode);
16
+ return () => observer.disconnect();
17
+ }, [isOpen, floatingWrapperNode]);
18
+ };
19
+ export {
20
+ useFloatingResizeObserver
21
+ };
22
+ //# sourceMappingURL=useFloatingResizeObserver.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../scripts/build/transpile/react-shim.js", "../../../src/hooks/useFloatingResizeObserver.ts"],
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { useEffect, useRef } from 'react';\n\ninterface UseFloatingResizeObserverParams {\n isOpen: boolean;\n floatingWrapperNode: HTMLElement | null;\n onResize: () => void;\n}\n\n/**\n * Observes the floating element's size and calls `onResize` when its bounding box changes.\n * Used to re-run position computation when the floating content reflows (e.g. async-loaded data).\n */\nexport const useFloatingResizeObserver = ({\n isOpen,\n floatingWrapperNode,\n onResize,\n}: UseFloatingResizeObserverParams) => {\n // Latest-ref so an inline `onResize` does not invalidate the effect on every render.\n // ResizeObserver fires an initial callback on `observe()`, so re-subscribing every render\n // would call `onResize` \u2192 setState \u2192 re-render \u2192 re-subscribe \u2192 infinite loop.\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n\n useEffect(() => {\n if (!isOpen || !floatingWrapperNode) return undefined;\n\n const observer = new ResizeObserver(() => {\n onResizeRef.current();\n });\n observer.observe(floatingWrapperNode);\n\n return () => observer.disconnect();\n }, [isOpen, floatingWrapperNode]);\n};\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;ACAvB,SAAS,WAAW,cAAc;AAY3B,MAAM,4BAA4B,CAAC;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AACF,MAAuC;AAIrC,QAAM,cAAc,OAAO,QAAQ;AACnC,cAAY,UAAU;AAEtB,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,CAAC,oBAAqB,QAAO;AAE5C,UAAM,WAAW,IAAI,eAAe,MAAM;AACxC,kBAAY,QAAQ;AAAA,IACtB,CAAC;AACD,aAAS,QAAQ,mBAAmB;AAEpC,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,QAAQ,mBAAmB,CAAC;AAClC;",
6
+ "names": []
7
+ }