@elliemae/ds-floating-context 3.61.5-rc.6 → 3.61.5-rc.7

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 (49) hide show
  1. package/dist/cjs/DSFloatingContext.js +58 -27
  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 +46 -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/useResolvedReference.js +52 -0
  12. package/dist/cjs/hooks/useResolvedReference.js.map +7 -0
  13. package/dist/cjs/react-desc-prop-types.js +13 -2
  14. package/dist/cjs/react-desc-prop-types.js.map +2 -2
  15. package/dist/cjs/typescript-testing/typescript-floating-context-valid.js +5 -1
  16. package/dist/cjs/typescript-testing/typescript-floating-context-valid.js.map +2 -2
  17. package/dist/cjs/useComputedPositionStyles.js +12 -9
  18. package/dist/cjs/useComputedPositionStyles.js.map +2 -2
  19. package/dist/cjs/utils/computePosition.js +2 -2
  20. package/dist/cjs/utils/computePosition.js.map +2 -2
  21. package/dist/esm/DSFloatingContext.js +59 -28
  22. package/dist/esm/DSFloatingContext.js.map +2 -2
  23. package/dist/esm/hooks/useFloatingClickOutside.js +30 -0
  24. package/dist/esm/hooks/useFloatingClickOutside.js.map +7 -0
  25. package/dist/esm/hooks/useFloatingEscape.js +44 -0
  26. package/dist/esm/hooks/useFloatingEscape.js.map +7 -0
  27. package/dist/esm/hooks/useFloatingResizeObserver.js +16 -0
  28. package/dist/esm/hooks/useFloatingResizeObserver.js.map +7 -0
  29. package/dist/esm/hooks/useLatestRef.js +11 -0
  30. package/dist/esm/hooks/useLatestRef.js.map +7 -0
  31. package/dist/esm/hooks/useResolvedReference.js +22 -0
  32. package/dist/esm/hooks/useResolvedReference.js.map +7 -0
  33. package/dist/esm/react-desc-prop-types.js +13 -2
  34. package/dist/esm/react-desc-prop-types.js.map +2 -2
  35. package/dist/esm/typescript-testing/typescript-floating-context-valid.js +5 -1
  36. package/dist/esm/typescript-testing/typescript-floating-context-valid.js.map +2 -2
  37. package/dist/esm/useComputedPositionStyles.js +12 -9
  38. package/dist/esm/useComputedPositionStyles.js.map +2 -2
  39. package/dist/esm/utils/computePosition.js +2 -2
  40. package/dist/esm/utils/computePosition.js.map +2 -2
  41. package/dist/types/DSFloatingContext.d.ts +2 -2
  42. package/dist/types/hooks/useFloatingClickOutside.d.ts +20 -0
  43. package/dist/types/hooks/useFloatingEscape.d.ts +19 -0
  44. package/dist/types/hooks/useFloatingResizeObserver.d.ts +11 -0
  45. package/dist/types/hooks/useLatestRef.d.ts +7 -0
  46. package/dist/types/hooks/useResolvedReference.d.ts +19 -0
  47. package/dist/types/react-desc-prop-types.d.ts +19 -0
  48. package/dist/types/useComputedPositionStyles.d.ts +1 -1
  49. package/package.json +6 -6
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../scripts/build/transpile/react-shim.js", "../../src/react-desc-prop-types.ts"],
4
- "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable @typescript-eslint/no-empty-interface */\nimport type { DSPropTypesSchema } from '@elliemae/ds-props-helpers';\nimport { PropTypes } from '@elliemae/ds-props-helpers';\nexport declare namespace DSHookFloatingContextT {\n export interface DefaultProps {\n withoutPortal: boolean;\n withoutAnimation: boolean;\n portalDOMContainer?: HTMLElement;\n animationDuration: number;\n placement: PopperPlacementsT;\n customOffset: [number, number];\n }\n\n export interface OptionalProps {\n placementOrderPreference?: PopperPlacementsT[];\n onOpen?: () => void;\n onClose?: () => void;\n externallyControlledIsOpen?: boolean;\n }\n export interface Props extends Partial<DefaultProps>, OptionalProps {}\n\n export interface InternalProps extends DefaultProps, OptionalProps {}\n\n export type PopperPlacementsT =\n | 'top-start'\n | 'top'\n | 'top-end'\n | 'right-start'\n | 'right'\n | 'right-end'\n | 'bottom-end'\n | 'bottom'\n | 'bottom-start'\n | 'left-end'\n | 'left'\n | 'left-start';\n}\n\nexport const defaultProps: DSHookFloatingContextT.DefaultProps = {\n withoutAnimation: false,\n animationDuration: 300,\n withoutPortal: false,\n placement: 'top',\n customOffset: [0, 12],\n};\n\nexport const DSFloatingContextPropTypes: DSPropTypesSchema<DSHookFloatingContextT.Props> = {\n withoutPortal: PropTypes.bool\n .description('If true, the tooltip will not be rendered inside a portal.')\n .defaultValue(false),\n withoutAnimation: PropTypes.bool.description('If true, the tooltip will not have an animation.').defaultValue(false),\n portalDOMContainer: PropTypes.instanceOf(HTMLElement)\n .description('The DOM element where the tooltip will be rendered.')\n .defaultValue('the first \"main\" landmark available in the document or the body if no \"main\" is found'),\n animationDuration: PropTypes.number.description('The duration of the animation in milliseconds.').defaultValue(300),\n placement: PropTypes.oneOf([\n 'top-start',\n 'top',\n 'top-end',\n 'right-start',\n 'right',\n 'right-end',\n 'bottom-end',\n 'bottom',\n 'bottom-start',\n 'left-end',\n 'left',\n 'left-start',\n ])\n .description('The placement of the tooltip.')\n .defaultValue('top'),\n customOffset: PropTypes.arrayOf(PropTypes.number)\n .description('The custom offset of the tooltip.')\n .defaultValue([12, 12]),\n placementOrderPreference: PropTypes.oneOfType([\n PropTypes.tuple([PropTypes.oneOf(['top-start'])]),\n PropTypes.tuple([PropTypes.oneOf(['top'])]),\n PropTypes.tuple([PropTypes.oneOf(['top-end'])]),\n PropTypes.tuple([PropTypes.oneOf(['right-start'])]),\n PropTypes.tuple([PropTypes.oneOf(['right'])]),\n PropTypes.tuple([PropTypes.oneOf(['right-end'])]),\n PropTypes.tuple([PropTypes.oneOf(['bottom-end'])]),\n PropTypes.tuple([PropTypes.oneOf(['bottom'])]),\n PropTypes.tuple([PropTypes.oneOf(['bottom-start'])]),\n PropTypes.tuple([PropTypes.oneOf(['left-end'])]),\n PropTypes.tuple([PropTypes.oneOf(['left'])]),\n PropTypes.tuple([PropTypes.oneOf(['left-start'])]),\n ]).description('The order of the placement preference.'),\n onOpen: PropTypes.func.description('Callback when the tooltip is opened.'),\n onClose: PropTypes.func.description('Callback when the tooltip is closed.'),\n externallyControlledIsOpen: PropTypes.bool.description(\n 'If true, the context open/close state will be controlled externally.',\n ),\n};\n"],
5
- "mappings": "AAAA,YAAY,WAAW;ACEvB,SAAS,iBAAiB;AAoCnB,MAAM,eAAoD;AAAA,EAC/D,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,cAAc,CAAC,GAAG,EAAE;AACtB;AAEO,MAAM,6BAA8E;AAAA,EACzF,eAAe,UAAU,KACtB,YAAY,4DAA4D,EACxE,aAAa,KAAK;AAAA,EACrB,kBAAkB,UAAU,KAAK,YAAY,kDAAkD,EAAE,aAAa,KAAK;AAAA,EACnH,oBAAoB,UAAU,WAAW,WAAW,EACjD,YAAY,qDAAqD,EACjE,aAAa,uFAAuF;AAAA,EACvG,mBAAmB,UAAU,OAAO,YAAY,gDAAgD,EAAE,aAAa,GAAG;AAAA,EAClH,WAAW,UAAU,MAAM;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACE,YAAY,+BAA+B,EAC3C,aAAa,KAAK;AAAA,EACrB,cAAc,UAAU,QAAQ,UAAU,MAAM,EAC7C,YAAY,mCAAmC,EAC/C,aAAa,CAAC,IAAI,EAAE,CAAC;AAAA,EACxB,0BAA0B,UAAU,UAAU;AAAA,IAC5C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAAA,IAChD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAAA,IAC1C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;AAAA,IAC9C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;AAAA,IAClD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,IAC5C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAAA,IAChD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;AAAA,IACjD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,IAC7C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;AAAA,IACnD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;AAAA,IAC/C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAAA,IAC3C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;AAAA,EACnD,CAAC,EAAE,YAAY,wCAAwC;AAAA,EACvD,QAAQ,UAAU,KAAK,YAAY,sCAAsC;AAAA,EACzE,SAAS,UAAU,KAAK,YAAY,sCAAsC;AAAA,EAC1E,4BAA4B,UAAU,KAAK;AAAA,IACzC;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable @typescript-eslint/no-empty-interface */\nimport type { DSPropTypesSchema } from '@elliemae/ds-props-helpers';\nimport { PropTypes } from '@elliemae/ds-props-helpers';\nexport declare namespace DSHookFloatingContextT {\n export interface DefaultProps {\n withoutPortal: boolean;\n withoutAnimation: boolean;\n portalDOMContainer?: HTMLElement;\n animationDuration: number;\n placement: PopperPlacementsT;\n customOffset: [number, number];\n closeOnEscape: boolean;\n returnFocusToReference: boolean;\n }\n\n export interface OptionalProps {\n placementOrderPreference?: PopperPlacementsT[];\n onOpen?: () => void;\n onClose?: () => void;\n externallyControlledIsOpen?: boolean;\n /**\n * Pre-resolved reference element. When provided, the hook uses this as the\n * source of truth for positioning and ignores its internal reference state.\n * Eliminates the need for a follow-up `refs.setReference(...)` effect and\n * removes the visibility:hidden race that breaks programmatic focus on open.\n */\n externalReferenceElement?: Element | null;\n /**\n * Called when the user clicks/taps outside both the floating element and\n * the reference element while the floating is open.\n */\n onClickOutside?: (event: MouseEvent | TouchEvent) => void;\n /**\n * Called when Escape is pressed while focus is within the floating element.\n * Only fires when `closeOnEscape` is true.\n */\n onEscape?: (event: KeyboardEvent) => void;\n }\n export interface Props extends Partial<DefaultProps>, OptionalProps {}\n\n export interface InternalProps extends DefaultProps, OptionalProps {}\n\n export type PopperPlacementsT =\n | 'top-start'\n | 'top'\n | 'top-end'\n | 'right-start'\n | 'right'\n | 'right-end'\n | 'bottom-end'\n | 'bottom'\n | 'bottom-start'\n | 'left-end'\n | 'left'\n | 'left-start';\n}\n\nexport const defaultProps: DSHookFloatingContextT.DefaultProps = {\n withoutAnimation: false,\n animationDuration: 300,\n withoutPortal: false,\n placement: 'top',\n customOffset: [0, 12],\n closeOnEscape: false,\n returnFocusToReference: false,\n};\n\nexport const DSFloatingContextPropTypes: DSPropTypesSchema<DSHookFloatingContextT.Props> = {\n withoutPortal: PropTypes.bool\n .description('If true, the tooltip will not be rendered inside a portal.')\n .defaultValue(false),\n withoutAnimation: PropTypes.bool.description('If true, the tooltip will not have an animation.').defaultValue(false),\n portalDOMContainer: PropTypes.instanceOf(HTMLElement)\n .description('The DOM element where the tooltip will be rendered.')\n .defaultValue('the first \"main\" landmark available in the document or the body if no \"main\" is found'),\n animationDuration: PropTypes.number.description('The duration of the animation in milliseconds.').defaultValue(300),\n placement: PropTypes.oneOf([\n 'top-start',\n 'top',\n 'top-end',\n 'right-start',\n 'right',\n 'right-end',\n 'bottom-end',\n 'bottom',\n 'bottom-start',\n 'left-end',\n 'left',\n 'left-start',\n ])\n .description('The placement of the tooltip.')\n .defaultValue('top'),\n customOffset: PropTypes.arrayOf(PropTypes.number)\n .description('The custom offset of the tooltip.')\n .defaultValue([12, 12]),\n placementOrderPreference: PropTypes.oneOfType([\n PropTypes.tuple([PropTypes.oneOf(['top-start'])]),\n PropTypes.tuple([PropTypes.oneOf(['top'])]),\n PropTypes.tuple([PropTypes.oneOf(['top-end'])]),\n PropTypes.tuple([PropTypes.oneOf(['right-start'])]),\n PropTypes.tuple([PropTypes.oneOf(['right'])]),\n PropTypes.tuple([PropTypes.oneOf(['right-end'])]),\n PropTypes.tuple([PropTypes.oneOf(['bottom-end'])]),\n PropTypes.tuple([PropTypes.oneOf(['bottom'])]),\n PropTypes.tuple([PropTypes.oneOf(['bottom-start'])]),\n PropTypes.tuple([PropTypes.oneOf(['left-end'])]),\n PropTypes.tuple([PropTypes.oneOf(['left'])]),\n PropTypes.tuple([PropTypes.oneOf(['left-start'])]),\n ]).description('The order of the placement preference.'),\n onOpen: PropTypes.func.description('Callback when the tooltip is opened.'),\n onClose: PropTypes.func.description('Callback when the tooltip is closed.'),\n externallyControlledIsOpen: PropTypes.bool.description(\n 'If true, the context open/close state will be controlled externally.',\n ),\n externalReferenceElement: PropTypes.instanceOf(Element).description(\n 'Pre-resolved reference element. When provided, used as the source of truth for positioning, ' +\n 'avoiding the need for a follow-up refs.setReference() effect and the visibility:hidden race on open.',\n ),\n onClickOutside: PropTypes.func.description(\n 'Called on mousedown/touchstart outside both the floating element and the reference element while open.',\n ),\n closeOnEscape: PropTypes.bool\n .description('If true, listens for Escape on the floating element and calls onEscape (or onClose).')\n .defaultValue(false),\n onEscape: PropTypes.func.description('Called when Escape is pressed while focus is within the floating element.'),\n returnFocusToReference: PropTypes.bool\n .description('If true, returns focus to the reference element after Escape-close.')\n .defaultValue(false),\n};\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;ACEvB,SAAS,iBAAiB;AAuDnB,MAAM,eAAoD;AAAA,EAC/D,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,cAAc,CAAC,GAAG,EAAE;AAAA,EACpB,eAAe;AAAA,EACf,wBAAwB;AAC1B;AAEO,MAAM,6BAA8E;AAAA,EACzF,eAAe,UAAU,KACtB,YAAY,4DAA4D,EACxE,aAAa,KAAK;AAAA,EACrB,kBAAkB,UAAU,KAAK,YAAY,kDAAkD,EAAE,aAAa,KAAK;AAAA,EACnH,oBAAoB,UAAU,WAAW,WAAW,EACjD,YAAY,qDAAqD,EACjE,aAAa,uFAAuF;AAAA,EACvG,mBAAmB,UAAU,OAAO,YAAY,gDAAgD,EAAE,aAAa,GAAG;AAAA,EAClH,WAAW,UAAU,MAAM;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACE,YAAY,+BAA+B,EAC3C,aAAa,KAAK;AAAA,EACrB,cAAc,UAAU,QAAQ,UAAU,MAAM,EAC7C,YAAY,mCAAmC,EAC/C,aAAa,CAAC,IAAI,EAAE,CAAC;AAAA,EACxB,0BAA0B,UAAU,UAAU;AAAA,IAC5C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAAA,IAChD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAAA,IAC1C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;AAAA,IAC9C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;AAAA,IAClD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,IAC5C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAAA,IAChD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;AAAA,IACjD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,IAC7C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;AAAA,IACnD,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;AAAA,IAC/C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAAA,IAC3C,UAAU,MAAM,CAAC,UAAU,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;AAAA,EACnD,CAAC,EAAE,YAAY,wCAAwC;AAAA,EACvD,QAAQ,UAAU,KAAK,YAAY,sCAAsC;AAAA,EACzE,SAAS,UAAU,KAAK,YAAY,sCAAsC;AAAA,EAC1E,4BAA4B,UAAU,KAAK;AAAA,IACzC;AAAA,EACF;AAAA,EACA,0BAA0B,UAAU,WAAW,OAAO,EAAE;AAAA,IACtD;AAAA,EAEF;AAAA,EACA,gBAAgB,UAAU,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EACA,eAAe,UAAU,KACtB,YAAY,sFAAsF,EAClG,aAAa,KAAK;AAAA,EACrB,UAAU,UAAU,KAAK,YAAY,2EAA2E;AAAA,EAChH,wBAAwB,UAAU,KAC/B,YAAY,qEAAqE,EACjF,aAAa,KAAK;AACvB;",
6
6
  "names": []
7
7
  }
@@ -14,7 +14,9 @@ const testCompleteDefaults = {
14
14
  animationDuration: 300,
15
15
  placement: "top",
16
16
  customOffset: [10, 10],
17
- portalDOMContainer: document.body
17
+ portalDOMContainer: document.body,
18
+ closeOnEscape: false,
19
+ returnFocusToReference: false
18
20
  };
19
21
  const testInternalProps = {
20
22
  ...testOptionalProps,
@@ -31,6 +33,8 @@ const testExplicitDefinition = {
31
33
  placement: "top",
32
34
  customOffset: [10, 10],
33
35
  portalDOMContainer: document.body,
36
+ closeOnEscape: false,
37
+ returnFocusToReference: false,
34
38
  ...testOptionalProps
35
39
  };
36
40
  const testInferedTypeCompatibility = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../scripts/build/transpile/react-shim.js", "../../../src/typescript-testing/typescript-floating-context-valid.tsx"],
4
- "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */\nimport React from 'react';\nimport { FloatingWrapper } from '../index.js';\nimport type { DSHookFloatingContextT } from '../index.js';\n\n// test we expose the namespace and the namespace follows our deliverable conventions\ntype ComponentPropsForApp = DSHookFloatingContextT.Props;\ntype ComponentPropsInternals = DSHookFloatingContextT.InternalProps;\ntype ComponentPropsDefaultProps = DSHookFloatingContextT.DefaultProps;\ntype ComponentPropsOptionalProps = DSHookFloatingContextT.OptionalProps;\n\nconst testOptionalProps: ComponentPropsOptionalProps = {};\n\n// difference Props and InternalProps is that InternalProps has all the default props filled in\n// Props allows for partial defaults\nconst testPartialDefaults: Partial<ComponentPropsDefaultProps> = {\n withoutAnimation: false,\n withoutPortal: false,\n animationDuration: 300,\n};\n\n// InternalProps requires all defaults to be filled in\nconst testCompleteDefaults: Required<ComponentPropsDefaultProps> = {\n withoutAnimation: false,\n withoutPortal: false,\n animationDuration: 300,\n placement: 'top',\n customOffset: [10, 10],\n portalDOMContainer: document.body,\n};\n\nconst testInternalProps: ComponentPropsInternals = {\n ...testOptionalProps,\n ...testCompleteDefaults,\n};\n\nconst testInternalPropsAsSyntax = {\n ...testOptionalProps,\n ...testCompleteDefaults,\n} as ComponentPropsInternals;\n\n// using the explicit type definition, if there is an error, it will be marked on the key that is wrong\nconst testExplicitDefinition: ComponentPropsForApp = {\n animationDuration: 300,\n withoutAnimation: false,\n withoutPortal: false,\n placement: 'top',\n customOffset: [10, 10],\n portalDOMContainer: document.body,\n ...testOptionalProps,\n};\n\n// using the \"as\" syntax, if there is an error, it will be marking the whole object as wrong because it is not compatible with the type\nconst testInferedTypeCompatibility = {\n ...testOptionalProps,\n ...testPartialDefaults,\n animationDuration: 300,\n} as ComponentPropsForApp;\n\nconst testDefinitionAsConst = {\n animationDuration: 300,\n withoutAnimation: false,\n withoutPortal: false,\n placement: 'top',\n portalDOMContainer: document.body,\n} as const;\n\nconst ExampleUsageComponent = () => (\n <>\n {/* works with explicitly casted props, all syntaxes */}\n <FloatingWrapper\n {...testExplicitDefinition}\n innerRef={React.createRef<HTMLDivElement>()}\n isOpen\n floatingStyles={{}}\n >\n <div>Content</div>\n </FloatingWrapper>\n <FloatingWrapper\n {...testInferedTypeCompatibility}\n innerRef={React.createRef<HTMLDivElement>()}\n isOpen\n floatingStyles={{}}\n >\n <div>Content</div>\n </FloatingWrapper>\n <FloatingWrapper {...testDefinitionAsConst} innerRef={React.createRef<HTMLDivElement>()} isOpen floatingStyles={{}}>\n <div>Content</div>\n </FloatingWrapper>\n {/* works with inline values */}\n </>\n);\n"],
5
- "mappings": "AAAA,YAAY,WAAW;ACoErB,mBAQI,KARJ;AAnEF,OAAOA,YAAW;AAClB,SAAS,uBAAuB;AAShC,MAAM,oBAAiD,CAAC;AAIxD,MAAM,sBAA2D;AAAA,EAC/D,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,mBAAmB;AACrB;AAGA,MAAM,uBAA6D;AAAA,EACjE,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,cAAc,CAAC,IAAI,EAAE;AAAA,EACrB,oBAAoB,SAAS;AAC/B;AAEA,MAAM,oBAA6C;AAAA,EACjD,GAAG;AAAA,EACH,GAAG;AACL;AAEA,MAAM,4BAA4B;AAAA,EAChC,GAAG;AAAA,EACH,GAAG;AACL;AAGA,MAAM,yBAA+C;AAAA,EACnD,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,cAAc,CAAC,IAAI,EAAE;AAAA,EACrB,oBAAoB,SAAS;AAAA,EAC7B,GAAG;AACL;AAGA,MAAM,+BAA+B;AAAA,EACnC,GAAG;AAAA,EACH,GAAG;AAAA,EACH,mBAAmB;AACrB;AAEA,MAAM,wBAAwB;AAAA,EAC5B,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,oBAAoB,SAAS;AAC/B;AAEA,MAAM,wBAAwB,MAC5B,iCAEE;AAAA;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAUA,OAAM,UAA0B;AAAA,MAC1C,QAAM;AAAA,MACN,gBAAgB,CAAC;AAAA,MAEjB,8BAAC,SAAI,qBAAO;AAAA;AAAA,EACd;AAAA,EACA;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAUA,OAAM,UAA0B;AAAA,MAC1C,QAAM;AAAA,MACN,gBAAgB,CAAC;AAAA,MAEjB,8BAAC,SAAI,qBAAO;AAAA;AAAA,EACd;AAAA,EACA,oBAAC,mBAAiB,GAAG,uBAAuB,UAAUA,OAAM,UAA0B,GAAG,QAAM,MAAC,gBAAgB,CAAC,GAC/G,8BAAC,SAAI,qBAAO,GACd;AAAA,GAEF;",
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */\nimport React from 'react';\nimport { FloatingWrapper } from '../index.js';\nimport type { DSHookFloatingContextT } from '../index.js';\n\n// test we expose the namespace and the namespace follows our deliverable conventions\ntype ComponentPropsForApp = DSHookFloatingContextT.Props;\ntype ComponentPropsInternals = DSHookFloatingContextT.InternalProps;\ntype ComponentPropsDefaultProps = DSHookFloatingContextT.DefaultProps;\ntype ComponentPropsOptionalProps = DSHookFloatingContextT.OptionalProps;\n\nconst testOptionalProps: ComponentPropsOptionalProps = {};\n\n// difference Props and InternalProps is that InternalProps has all the default props filled in\n// Props allows for partial defaults\nconst testPartialDefaults: Partial<ComponentPropsDefaultProps> = {\n withoutAnimation: false,\n withoutPortal: false,\n animationDuration: 300,\n};\n\n// InternalProps requires all defaults to be filled in\nconst testCompleteDefaults: Required<ComponentPropsDefaultProps> = {\n withoutAnimation: false,\n withoutPortal: false,\n animationDuration: 300,\n placement: 'top',\n customOffset: [10, 10],\n portalDOMContainer: document.body,\n closeOnEscape: false,\n returnFocusToReference: false,\n};\n\nconst testInternalProps: ComponentPropsInternals = {\n ...testOptionalProps,\n ...testCompleteDefaults,\n};\n\nconst testInternalPropsAsSyntax = {\n ...testOptionalProps,\n ...testCompleteDefaults,\n} as ComponentPropsInternals;\n\n// using the explicit type definition, if there is an error, it will be marked on the key that is wrong\nconst testExplicitDefinition: ComponentPropsForApp = {\n animationDuration: 300,\n withoutAnimation: false,\n withoutPortal: false,\n placement: 'top',\n customOffset: [10, 10],\n portalDOMContainer: document.body,\n closeOnEscape: false,\n returnFocusToReference: false,\n ...testOptionalProps,\n};\n\n// using the \"as\" syntax, if there is an error, it will be marking the whole object as wrong because it is not compatible with the type\nconst testInferedTypeCompatibility = {\n ...testOptionalProps,\n ...testPartialDefaults,\n animationDuration: 300,\n} as ComponentPropsForApp;\n\nconst testDefinitionAsConst = {\n animationDuration: 300,\n withoutAnimation: false,\n withoutPortal: false,\n placement: 'top',\n portalDOMContainer: document.body,\n} as const;\n\nconst ExampleUsageComponent = () => (\n <>\n {/* works with explicitly casted props, all syntaxes */}\n <FloatingWrapper\n {...testExplicitDefinition}\n innerRef={React.createRef<HTMLDivElement>()}\n isOpen\n floatingStyles={{}}\n >\n <div>Content</div>\n </FloatingWrapper>\n <FloatingWrapper\n {...testInferedTypeCompatibility}\n innerRef={React.createRef<HTMLDivElement>()}\n isOpen\n floatingStyles={{}}\n >\n <div>Content</div>\n </FloatingWrapper>\n <FloatingWrapper {...testDefinitionAsConst} innerRef={React.createRef<HTMLDivElement>()} isOpen floatingStyles={{}}>\n <div>Content</div>\n </FloatingWrapper>\n {/* works with inline values */}\n </>\n);\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;ACwErB,mBAQI,KARJ;AAvEF,OAAOA,YAAW;AAClB,SAAS,uBAAuB;AAShC,MAAM,oBAAiD,CAAC;AAIxD,MAAM,sBAA2D;AAAA,EAC/D,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,mBAAmB;AACrB;AAGA,MAAM,uBAA6D;AAAA,EACjE,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,cAAc,CAAC,IAAI,EAAE;AAAA,EACrB,oBAAoB,SAAS;AAAA,EAC7B,eAAe;AAAA,EACf,wBAAwB;AAC1B;AAEA,MAAM,oBAA6C;AAAA,EACjD,GAAG;AAAA,EACH,GAAG;AACL;AAEA,MAAM,4BAA4B;AAAA,EAChC,GAAG;AAAA,EACH,GAAG;AACL;AAGA,MAAM,yBAA+C;AAAA,EACnD,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,cAAc,CAAC,IAAI,EAAE;AAAA,EACrB,oBAAoB,SAAS;AAAA,EAC7B,eAAe;AAAA,EACf,wBAAwB;AAAA,EACxB,GAAG;AACL;AAGA,MAAM,+BAA+B;AAAA,EACnC,GAAG;AAAA,EACH,GAAG;AAAA,EACH,mBAAmB;AACrB;AAEA,MAAM,wBAAwB;AAAA,EAC5B,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,oBAAoB,SAAS;AAC/B;AAEA,MAAM,wBAAwB,MAC5B,iCAEE;AAAA;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAUA,OAAM,UAA0B;AAAA,MAC1C,QAAM;AAAA,MACN,gBAAgB,CAAC;AAAA,MAEjB,8BAAC,SAAI,qBAAO;AAAA;AAAA,EACd;AAAA,EACA;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAUA,OAAM,UAA0B;AAAA,MAC1C,QAAM;AAAA,MACN,gBAAgB,CAAC;AAAA,MAEjB,8BAAC,SAAI,qBAAO;AAAA;AAAA,EACd;AAAA,EACA,oBAAC,mBAAiB,GAAG,uBAAuB,UAAUA,OAAM,UAA0B,GAAG,QAAM,MAAC,gBAAgB,CAAC,GAC/G,8BAAC,SAAI,qBAAO,GACd;AAAA,GAEF;",
6
6
  "names": ["React"]
7
7
  }
@@ -17,7 +17,8 @@ const useComputedPositionStyles = (config) => {
17
17
  const [floatingStyles, setFloatingStyles] = useState({
18
18
  position: "absolute",
19
19
  zIndex: 3e3,
20
- visibility: "hidden",
20
+ opacity: 0,
21
+ pointerEvents: "none",
21
22
  willChange: "transform"
22
23
  });
23
24
  const [hasComputedOnce, setHasComputedOnce] = useState(false);
@@ -32,12 +33,11 @@ const useComputedPositionStyles = (config) => {
32
33
  customOffset,
33
34
  withoutPortal
34
35
  });
35
- setFloatingStyles((prev) => ({
36
- position: "absolute",
37
- zIndex: 3e3,
38
- ...prev,
39
- ...coordsStyle
40
- }));
36
+ const { transform, top, left } = coordsStyle;
37
+ setFloatingStyles((prev) => {
38
+ if (prev.transform === transform) return prev;
39
+ return { position: "absolute", zIndex: 3e3, ...prev, transform, top, left };
40
+ });
41
41
  setArrowStyles({ style: coordsArrow, placement: finalPlacement });
42
42
  setHasComputedOnce(true);
43
43
  }, [canCompute, reference, floating, placement, placementOrderPreference, customOffset, withoutPortal]);
@@ -49,6 +49,8 @@ const useComputedPositionStyles = (config) => {
49
49
  }, debounceMs);
50
50
  return d;
51
51
  }, [debounceMs]);
52
+ const mutableDebouncedStyles = useRef(debouncedUpdateStyles);
53
+ mutableDebouncedStyles.current = debouncedUpdateStyles;
52
54
  useLayoutEffect(
53
55
  () => () => {
54
56
  debouncedUpdateStyles.cancel();
@@ -66,7 +68,8 @@ const useComputedPositionStyles = (config) => {
66
68
  const resetVisibilityOnly = useCallback(() => {
67
69
  setFloatingStyles((prev) => ({
68
70
  ...prev,
69
- visibility: "hidden"
71
+ opacity: 0,
72
+ pointerEvents: "none"
70
73
  }));
71
74
  }, []);
72
75
  return useMemo(
@@ -76,7 +79,7 @@ const useComputedPositionStyles = (config) => {
76
79
  hasComputedOnce,
77
80
  updateStyles: forceUpdatePosition,
78
81
  debouncedUpdateStyles,
79
- mutableUpdateStyles,
82
+ mutableUpdateStyles: mutableDebouncedStyles,
80
83
  resetVisibilityOnly
81
84
  }),
82
85
  [arrowStyles, floatingStyles, hasComputedOnce, forceUpdatePosition, debouncedUpdateStyles, resetVisibilityOnly]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../scripts/build/transpile/react-shim.js", "../../src/useComputedPositionStyles.tsx"],
4
- "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable max-statements */\nimport { useLayoutEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport { debounce } from 'lodash-es';\nimport { type CSSProperties } from 'styled-components';\nimport { computePosition } from './utils/computePosition.js';\nimport type { DSHookFloatingContextT } from './react-desc-prop-types.js';\nimport type { PopoverArrowT } from './parts/PopoverArrow.js';\n\ntype UseComputedPositionStylesT = {\n /** Prevent computing when closed (optimization + avoids unnecessary frames) */\n preventComputing?: boolean;\n reference: Element | null;\n floating: HTMLElement | null;\n placement: DSHookFloatingContextT.PopperPlacementsT;\n placementOrderPreference?: DSHookFloatingContextT.PopperPlacementsT[];\n customOffset: [number, number];\n withoutPortal: boolean;\n /** Debounce ms for scroll/resize/observer events */\n debounceMs?: number;\n};\n\nexport const useComputedPositionStyles = (config: UseComputedPositionStylesT) => {\n const {\n reference,\n floating,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n preventComputing = false,\n debounceMs = 150,\n } = config;\n\n const [arrowStyles, setArrowStyles] = useState<PopoverArrowT>({ style: { left: 0 }, placement: 'top' });\n\n // Important: do not initialize top/left to (0,0); keep hidden until the first computation\n const [floatingStyles, setFloatingStyles] = useState<CSSProperties>({\n position: 'absolute',\n zIndex: 3000,\n visibility: 'hidden',\n willChange: 'transform',\n });\n\n const [hasComputedOnce, setHasComputedOnce] = useState(false);\n\n const canCompute = reference !== null && floating !== null && !preventComputing;\n\n const updateStyles = useCallback(() => {\n if (!canCompute) return;\n\n const { coordsStyle, finalPlacement, coordsArrow } = computePosition({\n reference,\n floating,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n });\n\n // Do not touch visibility here; it is managed outside depending on open/hasComputedOnce\n setFloatingStyles((prev) => ({\n position: 'absolute',\n zIndex: 3000,\n ...prev,\n ...coordsStyle,\n }));\n setArrowStyles({ style: coordsArrow, placement: finalPlacement });\n setHasComputedOnce(true);\n }, [canCompute, reference, floating, placement, placementOrderPreference, customOffset, withoutPortal]);\n\n // Store latest update function in a ref to keep debounced stable\n const mutableUpdateStyles = useRef(updateStyles);\n mutableUpdateStyles.current = updateStyles;\n\n const debouncedUpdateStyles = useMemo(() => {\n const d = debounce(() => {\n mutableUpdateStyles.current();\n }, debounceMs);\n return d;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debounceMs]);\n\n // Clean up debounce on unmount\n useLayoutEffect(\n () => () => {\n debouncedUpdateStyles.cancel();\n },\n [debouncedUpdateStyles],\n );\n\n // Recalculate BEFORE paint when dependencies change\n useLayoutEffect(() => {\n if (canCompute) {\n mutableUpdateStyles.current();\n }\n }, [canCompute, reference, floating, placement, placementOrderPreference, customOffset, withoutPortal]);\n\n const forceUpdatePosition = useCallback(() => {\n mutableUpdateStyles.current();\n }, []);\n\n // Do not reset coordinates when closing; just hide\n const resetVisibilityOnly = useCallback(() => {\n setFloatingStyles((prev) => ({\n ...prev,\n visibility: 'hidden',\n }));\n }, []);\n\n return useMemo(\n () => ({\n arrowStyles,\n floatingStyles,\n hasComputedOnce,\n updateStyles: forceUpdatePosition,\n debouncedUpdateStyles,\n mutableUpdateStyles,\n resetVisibilityOnly,\n }),\n [arrowStyles, floatingStyles, hasComputedOnce, forceUpdatePosition, debouncedUpdateStyles, resetVisibilityOnly],\n );\n};\n"],
5
- "mappings": "AAAA,YAAY,WAAW;ACCvB,SAAS,iBAAiB,SAAS,QAAQ,UAAU,mBAAmB;AACxE,SAAS,gBAAgB;AAEzB,SAAS,uBAAuB;AAiBzB,MAAM,4BAA4B,CAAC,WAAuC;AAC/E,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB,aAAa;AAAA,EACf,IAAI;AAEJ,QAAM,CAAC,aAAa,cAAc,IAAI,SAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,MAAM,CAAC;AAGtG,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAwB;AAAA,IAClE,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,EACd,CAAC;AAED,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAS,KAAK;AAE5D,QAAM,aAAa,cAAc,QAAQ,aAAa,QAAQ,CAAC;AAE/D,QAAM,eAAe,YAAY,MAAM;AACrC,QAAI,CAAC,WAAY;AAEjB,UAAM,EAAE,aAAa,gBAAgB,YAAY,IAAI,gBAAgB;AAAA,MACnE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAGD,sBAAkB,CAAC,UAAU;AAAA,MAC3B,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,GAAG;AAAA,MACH,GAAG;AAAA,IACL,EAAE;AACF,mBAAe,EAAE,OAAO,aAAa,WAAW,eAAe,CAAC;AAChE,uBAAmB,IAAI;AAAA,EACzB,GAAG,CAAC,YAAY,WAAW,UAAU,WAAW,0BAA0B,cAAc,aAAa,CAAC;AAGtG,QAAM,sBAAsB,OAAO,YAAY;AAC/C,sBAAoB,UAAU;AAE9B,QAAM,wBAAwB,QAAQ,MAAM;AAC1C,UAAM,IAAI,SAAS,MAAM;AACvB,0BAAoB,QAAQ;AAAA,IAC9B,GAAG,UAAU;AACb,WAAO;AAAA,EAET,GAAG,CAAC,UAAU,CAAC;AAGf;AAAA,IACE,MAAM,MAAM;AACV,4BAAsB,OAAO;AAAA,IAC/B;AAAA,IACA,CAAC,qBAAqB;AAAA,EACxB;AAGA,kBAAgB,MAAM;AACpB,QAAI,YAAY;AACd,0BAAoB,QAAQ;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,YAAY,WAAW,UAAU,WAAW,0BAA0B,cAAc,aAAa,CAAC;AAEtG,QAAM,sBAAsB,YAAY,MAAM;AAC5C,wBAAoB,QAAQ;AAAA,EAC9B,GAAG,CAAC,CAAC;AAGL,QAAM,sBAAsB,YAAY,MAAM;AAC5C,sBAAkB,CAAC,UAAU;AAAA,MAC3B,GAAG;AAAA,MACH,YAAY;AAAA,IACd,EAAE;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,aAAa,gBAAgB,iBAAiB,qBAAqB,uBAAuB,mBAAmB;AAAA,EAChH;AACF;",
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable max-statements */\nimport { useLayoutEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport { debounce } from 'lodash-es';\nimport { type CSSProperties } from 'styled-components';\nimport { computePosition } from './utils/computePosition.js';\nimport type { DSHookFloatingContextT } from './react-desc-prop-types.js';\nimport type { PopoverArrowT } from './parts/PopoverArrow.js';\n\ntype UseComputedPositionStylesT = {\n /** Prevent computing when closed (optimization + avoids unnecessary frames) */\n preventComputing?: boolean;\n reference: Element | null;\n floating: HTMLElement | null;\n placement: DSHookFloatingContextT.PopperPlacementsT;\n placementOrderPreference?: DSHookFloatingContextT.PopperPlacementsT[];\n customOffset: [number, number];\n withoutPortal: boolean;\n /** Debounce ms for scroll/resize/observer events */\n debounceMs?: number;\n};\n\nexport const useComputedPositionStyles = (config: UseComputedPositionStylesT) => {\n const {\n reference,\n floating,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n preventComputing = false,\n debounceMs = 150,\n } = config;\n\n const [arrowStyles, setArrowStyles] = useState<PopoverArrowT>({ style: { left: 0 }, placement: 'top' });\n\n // Initial state: invisible (opacity:0) but FOCUSABLE.\n // We intentionally use opacity instead of visibility:hidden \u2014 `visibility:hidden` blocks\n // programmatic focus on descendants (including React's `autoFocus` attribute on inputs),\n // which causes a race on first-open: the floating content's autoFocus fires before the\n // position-computation useLayoutEffect can flip visibility to `visible`, so the focus\n // silently no-ops. Opacity:0 keeps the element invisible while letting `.focus()` work.\n // pointer-events:none prevents accidental clicks on the still-unpositioned (0,0) area.\n const [floatingStyles, setFloatingStyles] = useState<CSSProperties>({\n position: 'absolute',\n zIndex: 3000,\n opacity: 0,\n pointerEvents: 'none',\n willChange: 'transform',\n });\n\n const [hasComputedOnce, setHasComputedOnce] = useState(false);\n\n const canCompute = reference !== null && floating !== null && !preventComputing;\n\n const updateStyles = useCallback(() => {\n if (!canCompute) return;\n\n const { coordsStyle, finalPlacement, coordsArrow } = computePosition({\n reference,\n floating,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n });\n // INTENTIONAL explicit destructure \u2014 do NOT replace with `...coordsStyle`.\n //\n // PUI-18470 is a ghost bug: an infinite ResizeObserver \u2192 setState \u2192 re-render loop\n // that only triggers at specific viewport pixel combinations (\"magic pixel\") and is\n // nearly impossible to reproduce consistently across machines. It took a synthetic\n // Playwright ResizeObserver-intercept test to surface it at all.\n //\n // Part of the fix is the bail-out below (`prev.transform === transform`): if the\n // computed position hasn't changed we return the same state reference, preventing a\n // re-render and breaking the loop. That bail-out only works if we know exactly which\n // properties coordsStyle contributes. Spreading `...coordsStyle` hides that contract:\n // if computePosition ever extends coordsStyle with a new property, the bail-out\n // silently becomes incomplete and the loop can re-emerge with no obvious cause.\n //\n // Keeping the destructure explicit forces any future change to computePosition's\n // coordsStyle contract to be a conscious, visible decision here \u2014 not a silent pass-through.\n const { transform, top, left } = coordsStyle;\n\n // Do not touch visibility here; it is managed outside depending on open/hasComputedOnce\n setFloatingStyles((prev) => {\n if (prev.transform === transform) return prev;\n return { position: 'absolute', zIndex: 3000, ...prev, transform, top, left };\n });\n setArrowStyles({ style: coordsArrow, placement: finalPlacement });\n setHasComputedOnce(true);\n }, [canCompute, reference, floating, placement, placementOrderPreference, customOffset, withoutPortal]);\n\n // Store latest update function in a ref to keep debounced stable\n const mutableUpdateStyles = useRef(updateStyles);\n mutableUpdateStyles.current = updateStyles;\n\n const debouncedUpdateStyles = useMemo(() => {\n const d = debounce(() => {\n mutableUpdateStyles.current();\n }, debounceMs);\n return d;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debounceMs]);\n\n const mutableDebouncedStyles = useRef(debouncedUpdateStyles);\n mutableDebouncedStyles.current = debouncedUpdateStyles;\n\n // Clean up debounce on unmount\n useLayoutEffect(\n () => () => {\n debouncedUpdateStyles.cancel();\n },\n [debouncedUpdateStyles],\n );\n\n // Recalculate BEFORE paint when dependencies change\n useLayoutEffect(() => {\n if (canCompute) {\n mutableUpdateStyles.current();\n }\n }, [canCompute, reference, floating, placement, placementOrderPreference, customOffset, withoutPortal]);\n\n const forceUpdatePosition = useCallback(() => {\n mutableUpdateStyles.current();\n }, []);\n\n // Do not reset coordinates when closing; just hide via opacity (keeps element focusable\n // if anything inside needs to remain focusable during animations).\n const resetVisibilityOnly = useCallback(() => {\n setFloatingStyles((prev) => ({\n ...prev,\n opacity: 0,\n pointerEvents: 'none',\n }));\n }, []);\n\n return useMemo(\n () => ({\n arrowStyles,\n floatingStyles,\n hasComputedOnce,\n updateStyles: forceUpdatePosition,\n debouncedUpdateStyles,\n mutableUpdateStyles: mutableDebouncedStyles,\n resetVisibilityOnly,\n }),\n [arrowStyles, floatingStyles, hasComputedOnce, forceUpdatePosition, debouncedUpdateStyles, resetVisibilityOnly],\n );\n};\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;ACCvB,SAAS,iBAAiB,SAAS,QAAQ,UAAU,mBAAmB;AACxE,SAAS,gBAAgB;AAEzB,SAAS,uBAAuB;AAiBzB,MAAM,4BAA4B,CAAC,WAAuC;AAC/E,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB,aAAa;AAAA,EACf,IAAI;AAEJ,QAAM,CAAC,aAAa,cAAc,IAAI,SAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,MAAM,CAAC;AAStG,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAwB;AAAA,IAClE,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,eAAe;AAAA,IACf,YAAY;AAAA,EACd,CAAC;AAED,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAS,KAAK;AAE5D,QAAM,aAAa,cAAc,QAAQ,aAAa,QAAQ,CAAC;AAE/D,QAAM,eAAe,YAAY,MAAM;AACrC,QAAI,CAAC,WAAY;AAEjB,UAAM,EAAE,aAAa,gBAAgB,YAAY,IAAI,gBAAgB;AAAA,MACnE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAiBD,UAAM,EAAE,WAAW,KAAK,KAAK,IAAI;AAGjC,sBAAkB,CAAC,SAAS;AAC1B,UAAI,KAAK,cAAc,UAAW,QAAO;AACzC,aAAO,EAAE,UAAU,YAAY,QAAQ,KAAM,GAAG,MAAM,WAAW,KAAK,KAAK;AAAA,IAC7E,CAAC;AACD,mBAAe,EAAE,OAAO,aAAa,WAAW,eAAe,CAAC;AAChE,uBAAmB,IAAI;AAAA,EACzB,GAAG,CAAC,YAAY,WAAW,UAAU,WAAW,0BAA0B,cAAc,aAAa,CAAC;AAGtG,QAAM,sBAAsB,OAAO,YAAY;AAC/C,sBAAoB,UAAU;AAE9B,QAAM,wBAAwB,QAAQ,MAAM;AAC1C,UAAM,IAAI,SAAS,MAAM;AACvB,0BAAoB,QAAQ;AAAA,IAC9B,GAAG,UAAU;AACb,WAAO;AAAA,EAET,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,yBAAyB,OAAO,qBAAqB;AAC3D,yBAAuB,UAAU;AAGjC;AAAA,IACE,MAAM,MAAM;AACV,4BAAsB,OAAO;AAAA,IAC/B;AAAA,IACA,CAAC,qBAAqB;AAAA,EACxB;AAGA,kBAAgB,MAAM;AACpB,QAAI,YAAY;AACd,0BAAoB,QAAQ;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,YAAY,WAAW,UAAU,WAAW,0BAA0B,cAAc,aAAa,CAAC;AAEtG,QAAM,sBAAsB,YAAY,MAAM;AAC5C,wBAAoB,QAAQ;AAAA,EAC9B,GAAG,CAAC,CAAC;AAIL,QAAM,sBAAsB,YAAY,MAAM;AAC5C,sBAAkB,CAAC,UAAU;AAAA,MAC3B,GAAG;AAAA,MACH,SAAS;AAAA,MACT,eAAe;AAAA,IACjB,EAAE;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA,qBAAqB;AAAA,MACrB;AAAA,IACF;AAAA,IACA,CAAC,aAAa,gBAAgB,iBAAiB,qBAAqB,uBAAuB,mBAAmB;AAAA,EAChH;AACF;",
6
6
  "names": []
7
7
  }
@@ -51,8 +51,8 @@ const computePosition = (props) => {
51
51
  y += window.scrollY;
52
52
  } else {
53
53
  const op = getOffsetParentData(floating);
54
- x = x - op.left + op.scrollLeft;
55
- y = y - op.top + op.scrollTop;
54
+ x = x + clippingRect.left - op.left + op.scrollLeft;
55
+ y = y + clippingRect.top - op.top + op.scrollTop;
56
56
  }
57
57
  ({ x, y } = applyShift(x, y, overflows));
58
58
  const coordsArrow = getArrowOffsetDynamic({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../scripts/build/transpile/react-shim.js", "../../../src/utils/computePosition.ts"],
4
- "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* 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 const op = getOffsetParentData(floating);\n x = x - op.left + op.scrollLeft;\n y = y - 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"],
5
- "mappings": "AAAA,YAAY,WAAW;ACSvB,SAAS,qCAAqC;AAC9C,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAWA,MAAM,kBAAkB,CAAC,UAAgC;AAC9D,QAAM,EAAE,WAAW,UAAU,WAAW,0BAA0B,cAAc,cAAc,IAAI;AAGlG,QAAM,gBAAgB,gBAAgB,qBAAqB,SAAS,IAAI,EAAE,KAAK,GAAG,MAAM,EAAE;AAE1F,QAAM,gBAAgB,UAAU,sBAAsB;AACtD,QAAM,eAAe,SAAS,sBAAsB;AAEpD,QAAM,qBAAqB,4BAA4B,8BAA8B,SAAS;AAE9F,QAAM,aAAa;AAAA,IACjB,CAAC,SAAS,EAAE,OAAO,kBAAgE;AAAA,EACrF;AAKA,QAAM,iBAAiB,gBAAgB,kBAAkB,SAAS,IAAI;AACtE,QAAM,eAAyB,iBAAiB,eAAe,sBAAsB,IAAI,gBAAgB;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,aAAY,eAAe,eAAe,cAAc,kBAAkB,cAAc,YAAY;AAE1G,QAAI,KAAKA,UAAS,GAAG;AACnB,sBAAgB;AAChB,sBAAgBA;AAChB;AAAA,IACF;AAEA,UAAM,QAAQ,iBAAiBA,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,iBAAiB,eAAe,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;AACL,UAAM,KAAK,oBAAoB,QAAQ;AACvC,QAAI,IAAI,GAAG,OAAO,GAAG;AACrB,QAAI,IAAI,GAAG,MAAM,GAAG;AAAA,EACtB;AAGA,GAAC,EAAE,GAAG,EAAE,IAAI,WAAW,GAAG,GAAG,SAAS;AAEtC,QAAM,cAAc,sBAAsB;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": ["import * as React from 'react';\nexport { React };\n", "/* 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"],
5
+ "mappings": "AAAA,YAAY,WAAW;ACSvB,SAAS,qCAAqC;AAC9C,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAWA,MAAM,kBAAkB,CAAC,UAAgC;AAC9D,QAAM,EAAE,WAAW,UAAU,WAAW,0BAA0B,cAAc,cAAc,IAAI;AAGlG,QAAM,gBAAgB,gBAAgB,qBAAqB,SAAS,IAAI,EAAE,KAAK,GAAG,MAAM,EAAE;AAE1F,QAAM,gBAAgB,UAAU,sBAAsB;AACtD,QAAM,eAAe,SAAS,sBAAsB;AAEpD,QAAM,qBAAqB,4BAA4B,8BAA8B,SAAS;AAE9F,QAAM,aAAa;AAAA,IACjB,CAAC,SAAS,EAAE,OAAO,kBAAgE;AAAA,EACrF;AAKA,QAAM,iBAAiB,gBAAgB,kBAAkB,SAAS,IAAI;AACtE,QAAM,eAAyB,iBAAiB,eAAe,sBAAsB,IAAI,gBAAgB;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,aAAY,eAAe,eAAe,cAAc,kBAAkB,cAAc,YAAY;AAE1G,QAAI,KAAKA,UAAS,GAAG;AACnB,sBAAgB;AAChB,sBAAgBA;AAChB;AAAA,IACF;AAEA,UAAM,QAAQ,iBAAiBA,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,iBAAiB,eAAe,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,KAAK,oBAAoB,QAAQ;AACvC,QAAI,IAAI,aAAa,OAAO,GAAG,OAAO,GAAG;AACzC,QAAI,IAAI,aAAa,MAAM,GAAG,MAAM,GAAG;AAAA,EACzC;AAGA,GAAC,EAAE,GAAG,EAAE,IAAI,WAAW,GAAG,GAAG,SAAS;AAEtC,QAAM,cAAc,sBAAsB;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
  }
@@ -3,7 +3,7 @@ import type { DSHookFloatingContextT } from './react-desc-prop-types.js';
3
3
  declare const useFloatingContext: {
4
4
  (props?: DSHookFloatingContextT.Props): {
5
5
  refs: {
6
- setReference: React.Dispatch<React.SetStateAction<Element | null>>;
6
+ setReference: (el: Element | null) => void;
7
7
  setFloating: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
8
8
  floating: HTMLElement | null;
9
9
  reference: Element | null;
@@ -25,7 +25,7 @@ declare const useFloatingContext: {
25
25
  portalDOMContainer: HTMLElement | undefined;
26
26
  animationDuration: number;
27
27
  };
28
- mutableUpdateStyles: React.MutableRefObject<() => void>;
28
+ mutableUpdateStyles: React.MutableRefObject<import("lodash").DebouncedFunc<() => void>>;
29
29
  forceUpdatePosition: () => void;
30
30
  };
31
31
  displayName: string;
@@ -0,0 +1,20 @@
1
+ import { type MutableRefObject } from 'react';
2
+ interface UseFloatingClickOutsideParams {
3
+ enabled: boolean;
4
+ floating: HTMLElement | null;
5
+ reference: Element | null;
6
+ /**
7
+ * Latest-ref of the click-outside callback. Passed as a ref (rather than a value)
8
+ * so consumers can pass inline functions without re-attaching listeners on every render.
9
+ * Use `useLatestRef(onClickOutside)` to construct it.
10
+ */
11
+ callbackRef: MutableRefObject<((event: MouseEvent | TouchEvent) => void) | undefined>;
12
+ }
13
+ /**
14
+ * Attaches mousedown/touchstart listeners on the document that fire when the click target
15
+ * is outside BOTH the floating element and the reference element.
16
+ *
17
+ * Only active when `enabled === true` and both elements exist.
18
+ */
19
+ export declare const useFloatingClickOutside: ({ enabled, floating, reference, callbackRef, }: UseFloatingClickOutsideParams) => void;
20
+ export {};
@@ -0,0 +1,19 @@
1
+ import { type MutableRefObject } from 'react';
2
+ interface UseFloatingEscapeParams {
3
+ enabled: boolean;
4
+ floating: HTMLElement | null;
5
+ reference: Element | null;
6
+ /** Latest-ref of the consumer's onEscape callback. Construct with `useLatestRef(onEscape)`. */
7
+ onEscapeRef: MutableRefObject<((event: KeyboardEvent) => void) | undefined>;
8
+ /** Fallback close handler invoked when no `onEscape` is provided. */
9
+ handleClose: () => void;
10
+ /** When true, calls `reference.focus()` after the escape close. */
11
+ returnFocusToReference: boolean;
12
+ }
13
+ /**
14
+ * Listens for `Escape` on the document and dismisses the floating element when focus is
15
+ * within the floating element or on the reference element. Outside that scope the event is
16
+ * ignored — we don't hijack Escape for unrelated UI on the page.
17
+ */
18
+ export declare const useFloatingEscape: ({ enabled, floating, reference, onEscapeRef, handleClose, returnFocusToReference, }: UseFloatingEscapeParams) => void;
19
+ export {};
@@ -0,0 +1,11 @@
1
+ interface UseFloatingResizeObserverParams {
2
+ enabled: boolean;
3
+ floating: HTMLElement | null;
4
+ onResize: () => void;
5
+ }
6
+ /**
7
+ * Observes the floating element's size and calls `onResize` when its bounding box changes.
8
+ * Used to re-run position computation when the floating content reflows (e.g. async-loaded data).
9
+ */
10
+ export declare const useFloatingResizeObserver: ({ enabled, floating, onResize }: UseFloatingResizeObserverParams) => void;
11
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Latest-ref pattern: keeps the value in a ref that's updated on every render.
3
+ * Use this when an effect/listener needs to read the latest value of a prop or callback
4
+ * without including it in the effect's dependency array — avoids re-attaching listeners
5
+ * (and infinite loops via deep-equal comparators) when consumers pass inline callbacks.
6
+ */
7
+ export declare const useLatestRef: <T>(value: T) => import("react").MutableRefObject<T>;
@@ -0,0 +1,19 @@
1
+ interface UseResolvedReferenceParams {
2
+ externalReferenceElement: Element | null | undefined;
3
+ internalReferenceElement: Element | null;
4
+ setInternalReferenceElement: (el: Element | null) => void;
5
+ }
6
+ /**
7
+ * Resolves the active reference element for the floating context.
8
+ *
9
+ * - When `externalReferenceElement` is provided (anything other than `undefined`, including `null`),
10
+ * it is used as the source of truth. `setReference` becomes a no-op so consumers that mistakenly
11
+ * call `refs.setReference()` don't desynchronize the two sources.
12
+ * - Otherwise the internally-managed state is used, populated by the consumer via `refs.setReference()`
13
+ * (typically as a callback ref: `innerRef={refs.setReference}`).
14
+ */
15
+ export declare const useResolvedReference: ({ externalReferenceElement, internalReferenceElement, setInternalReferenceElement, }: UseResolvedReferenceParams) => {
16
+ referenceElement: Element | null;
17
+ setReferenceElement: (el: Element | null) => void;
18
+ };
19
+ export {};
@@ -7,12 +7,31 @@ export declare namespace DSHookFloatingContextT {
7
7
  animationDuration: number;
8
8
  placement: PopperPlacementsT;
9
9
  customOffset: [number, number];
10
+ closeOnEscape: boolean;
11
+ returnFocusToReference: boolean;
10
12
  }
11
13
  interface OptionalProps {
12
14
  placementOrderPreference?: PopperPlacementsT[];
13
15
  onOpen?: () => void;
14
16
  onClose?: () => void;
15
17
  externallyControlledIsOpen?: boolean;
18
+ /**
19
+ * Pre-resolved reference element. When provided, the hook uses this as the
20
+ * source of truth for positioning and ignores its internal reference state.
21
+ * Eliminates the need for a follow-up `refs.setReference(...)` effect and
22
+ * removes the visibility:hidden race that breaks programmatic focus on open.
23
+ */
24
+ externalReferenceElement?: Element | null;
25
+ /**
26
+ * Called when the user clicks/taps outside both the floating element and
27
+ * the reference element while the floating is open.
28
+ */
29
+ onClickOutside?: (event: MouseEvent | TouchEvent) => void;
30
+ /**
31
+ * Called when Escape is pressed while focus is within the floating element.
32
+ * Only fires when `closeOnEscape` is true.
33
+ */
34
+ onEscape?: (event: KeyboardEvent) => void;
16
35
  }
17
36
  interface Props extends Partial<DefaultProps>, OptionalProps {
18
37
  }
@@ -19,7 +19,7 @@ export declare const useComputedPositionStyles: (config: UseComputedPositionStyl
19
19
  hasComputedOnce: boolean;
20
20
  updateStyles: () => void;
21
21
  debouncedUpdateStyles: import("lodash").DebouncedFunc<() => void>;
22
- mutableUpdateStyles: import("react").MutableRefObject<() => void>;
22
+ mutableUpdateStyles: import("react").MutableRefObject<import("lodash").DebouncedFunc<() => void>>;
23
23
  resetVisibilityOnly: () => void;
24
24
  };
25
25
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-floating-context",
3
- "version": "3.61.5-rc.6",
3
+ "version": "3.61.5-rc.7",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Popper Hook",
6
6
  "files": [
@@ -36,14 +36,14 @@
36
36
  "indent": 4
37
37
  },
38
38
  "dependencies": {
39
- "@elliemae/ds-hooks-headless-tooltip": "3.61.5-rc.6",
40
- "@elliemae/ds-typescript-helpers": "3.61.5-rc.6",
41
- "@elliemae/ds-props-helpers": "3.61.5-rc.6",
42
- "@elliemae/ds-system": "3.61.5-rc.6"
39
+ "@elliemae/ds-props-helpers": "3.61.5-rc.7",
40
+ "@elliemae/ds-typescript-helpers": "3.61.5-rc.7",
41
+ "@elliemae/ds-system": "3.61.5-rc.7",
42
+ "@elliemae/ds-hooks-headless-tooltip": "3.61.5-rc.7"
43
43
  },
44
44
  "devDependencies": {
45
45
  "jest": "^30.0.0",
46
- "@elliemae/ds-monorepo-devops": "3.61.5-rc.6"
46
+ "@elliemae/ds-monorepo-devops": "3.61.5-rc.7"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "lodash-es": "^4.18.1",