@elliemae/ds-floating-context 3.70.0-next.54 → 3.70.0-next.59

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 (36) hide show
  1. package/dist/cjs/DSFloatingContext.js +10 -3
  2. package/dist/cjs/DSFloatingContext.js.map +2 -2
  3. package/dist/cjs/hooks/useReferenceHiddenByBoundary.js +59 -0
  4. package/dist/cjs/hooks/useReferenceHiddenByBoundary.js.map +7 -0
  5. package/dist/cjs/package.json +1 -4
  6. package/dist/cjs/react-desc-prop-types.js +3 -0
  7. package/dist/cjs/react-desc-prop-types.js.map +2 -2
  8. package/dist/cjs/typescript-testing/typescript-floating-context-valid.js +3 -1
  9. package/dist/cjs/typescript-testing/typescript-floating-context-valid.js.map +2 -2
  10. package/dist/cjs/useComputedPositionStyles.js +7 -3
  11. package/dist/cjs/useComputedPositionStyles.js.map +2 -2
  12. package/dist/cjs/utils/computePosition.js +13 -4
  13. package/dist/cjs/utils/computePosition.js.map +2 -2
  14. package/dist/cjs/utils/floatingPositioning.js +11 -0
  15. package/dist/cjs/utils/floatingPositioning.js.map +2 -2
  16. package/dist/esm/DSFloatingContext.js +10 -3
  17. package/dist/esm/DSFloatingContext.js.map +2 -2
  18. package/dist/esm/hooks/useReferenceHiddenByBoundary.js +29 -0
  19. package/dist/esm/hooks/useReferenceHiddenByBoundary.js.map +7 -0
  20. package/dist/esm/package.json +1 -4
  21. package/dist/esm/react-desc-prop-types.js +3 -0
  22. package/dist/esm/react-desc-prop-types.js.map +2 -2
  23. package/dist/esm/typescript-testing/typescript-floating-context-valid.js +3 -1
  24. package/dist/esm/typescript-testing/typescript-floating-context-valid.js.map +2 -2
  25. package/dist/esm/useComputedPositionStyles.js +7 -3
  26. package/dist/esm/useComputedPositionStyles.js.map +2 -2
  27. package/dist/esm/utils/computePosition.js +14 -4
  28. package/dist/esm/utils/computePosition.js.map +2 -2
  29. package/dist/esm/utils/floatingPositioning.js +11 -0
  30. package/dist/esm/utils/floatingPositioning.js.map +2 -2
  31. package/dist/types/hooks/useReferenceHiddenByBoundary.d.ts +17 -0
  32. package/dist/types/react-desc-prop-types.d.ts +20 -0
  33. package/dist/types/useComputedPositionStyles.d.ts +3 -0
  34. package/dist/types/utils/computePosition.d.ts +23 -0
  35. package/dist/types/utils/floatingPositioning.d.ts +13 -1
  36. package/package.json +8 -8
@@ -43,10 +43,11 @@ var import_useLatestRef = require("./hooks/useLatestRef.js");
43
43
  var import_useResolvedReference = require("./hooks/useResolvedReference.js");
44
44
  var import_useFloatingClickOutside = require("./hooks/useFloatingClickOutside.js");
45
45
  var import_useFloatingResizeObserver = require("./hooks/useFloatingResizeObserver.js");
46
+ var import_useReferenceHiddenByBoundary = require("./hooks/useReferenceHiddenByBoundary.js");
46
47
  var import_useSyntheticEventFromControlledState = require("./hooks/useSyntheticEventFromControlledState.js");
47
48
  const isKeyboardEvent = (event) => !!event?.key;
48
49
  const useFloatingContext = (props = {}) => {
49
- const { onClickOutside, onEscape, onOpen, onClose, externalReferenceElement, ...stableProps } = props;
50
+ const { onClickOutside, onEscape, onOpen, onClose, externalReferenceElement, boundaryElement, ...stableProps } = props;
50
51
  const propsWithDefault = (0, import_ds_props_helpers.useMemoMergePropsWithDefault)(
51
52
  stableProps,
52
53
  import_react_desc_prop_types.defaultProps
@@ -147,6 +148,7 @@ const useFloatingContext = (props = {}) => {
147
148
  placementOrderPreference,
148
149
  customOffset,
149
150
  withoutPortal,
151
+ boundaryElement,
150
152
  isClose: !isOpen,
151
153
  debounceMs: 150
152
154
  });
@@ -160,6 +162,11 @@ const useFloatingContext = (props = {}) => {
160
162
  debouncedUpdateStyles();
161
163
  }, [isOpen, debouncedUpdateStyles]);
162
164
  (0, import_positionObserver.usePositionObserver)(triggerElementReference, onObservedMovement);
165
+ const isReferenceHiddenByBoundary = (0, import_useReferenceHiddenByBoundary.useReferenceHiddenByBoundary)({
166
+ reference: triggerElementReference,
167
+ boundaryElement,
168
+ enabled: isOpen && !!boundaryElement
169
+ });
163
170
  (0, import_useFloatingClickOutside.useFloatingClickOutside)({
164
171
  isOpen,
165
172
  floatingWrapperNode,
@@ -178,13 +185,13 @@ const useFloatingContext = (props = {}) => {
178
185
  onExit: handleControlledClose
179
186
  });
180
187
  const computedFloatingStyles = (0, import_react.useMemo)(() => {
181
- const isInteractive = isOpen && hasComputedOnce;
188
+ const isInteractive = isOpen && hasComputedOnce && !isReferenceHiddenByBoundary;
182
189
  return {
183
190
  ...floatingStyles,
184
191
  opacity: isInteractive ? 1 : 0,
185
192
  pointerEvents: isInteractive ? "auto" : "none"
186
193
  };
187
- }, [floatingStyles, hasComputedOnce, isOpen]);
194
+ }, [floatingStyles, hasComputedOnce, isOpen, isReferenceHiddenByBoundary]);
188
195
  const refs = (0, import_react.useMemo)(
189
196
  () => ({
190
197
  setReference: setReferenceElement,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/DSFloatingContext.tsx", "../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["/* 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 { 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, ...stableProps } = 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 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 // 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;\n return {\n ...floatingStyles,\n opacity: isInteractive ? 1 : 0,\n pointerEvents: isInteractive ? 'auto' : 'none',\n };\n }, [floatingStyles, hasComputedOnce, isOpen]);\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", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* 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", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": "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",
6
6
  "names": ["React"]
7
7
  }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var useReferenceHiddenByBoundary_exports = {};
30
+ __export(useReferenceHiddenByBoundary_exports, {
31
+ useReferenceHiddenByBoundary: () => useReferenceHiddenByBoundary
32
+ });
33
+ module.exports = __toCommonJS(useReferenceHiddenByBoundary_exports);
34
+ var React = __toESM(require("react"));
35
+ var import_react = require("react");
36
+ const useReferenceHiddenByBoundary = ({
37
+ reference,
38
+ boundaryElement,
39
+ enabled
40
+ }) => {
41
+ const [isHidden, setIsHidden] = (0, import_react.useState)(false);
42
+ (0, import_react.useEffect)(() => {
43
+ if (!enabled || !reference || !boundaryElement || !window.IntersectionObserver) {
44
+ setIsHidden(false);
45
+ return () => {
46
+ };
47
+ }
48
+ const observer = new IntersectionObserver(
49
+ ([entry]) => {
50
+ setIsHidden(!entry.isIntersecting);
51
+ },
52
+ { root: boundaryElement, threshold: 0 }
53
+ );
54
+ observer.observe(reference);
55
+ return () => observer.disconnect();
56
+ }, [enabled, reference, boundaryElement]);
57
+ return isHidden;
58
+ };
59
+ //# sourceMappingURL=useReferenceHiddenByBoundary.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/hooks/useReferenceHiddenByBoundary.ts", "../../../../../../scripts/build/transpile/react-shim.js"],
4
+ "sourcesContent": ["import { useEffect, useState } from 'react';\n\n/**\n * Tracks whether `reference` is currently visible at all within `boundaryElement` \u2014 e.g. a\n * scrollable ancestor the reference lives inside. Returns true the moment the reference scrolls\n * fully out of that boundary's visible area (intersection ratio 0), even though it may still be\n * within the browser viewport; flips back to false as soon as it's visible again. This is what\n * boundaryElement/clamping cannot solve on its own: a clamped floating element just sticks at\n * the boundary's edge, orphaned, once its reference is no longer visible to anchor it to \u2014 the\n * consumer still considers it \"open,\" it should just not be shown while its anchor isn't there.\n *\n * Always false (inert) when boundaryElement is not provided \u2014 this is opt-in, paired with\n * useFloatingContext's boundaryElement prop.\n */\nexport const useReferenceHiddenByBoundary = ({\n reference,\n boundaryElement,\n enabled,\n}: {\n reference: Element | null;\n boundaryElement?: Element | null;\n enabled: boolean;\n}): boolean => {\n const [isHidden, setIsHidden] = useState(false);\n\n // Justified per antipattern_useeffect_misuse.md: this subscribes to a browser observer API\n // (IntersectionObserver), the same external-subscription shape as usePositionObserver.ts in\n // this package \u2014 not derivable via useMemo, since intersection with a scrollable ancestor is\n // asynchronous, browser-computed layout state, not something React can compute from props.\n useEffect(() => {\n if (!enabled || !reference || !boundaryElement || !window.IntersectionObserver) {\n setIsHidden(false);\n return () => {};\n }\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n setIsHidden(!entry.isIntersecting);\n },\n { root: boundaryElement, threshold: 0 },\n );\n observer.observe(reference);\n\n return () => observer.disconnect();\n }, [enabled, reference, boundaryElement]);\n\n return isHidden;\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,mBAAoC;AAc7B,MAAM,+BAA+B,CAAC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AACF,MAIe;AACb,QAAM,CAAC,UAAU,WAAW,QAAI,uBAAS,KAAK;AAM9C,8BAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,aAAa,CAAC,mBAAmB,CAAC,OAAO,sBAAsB;AAC9E,kBAAY,KAAK;AACjB,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AAEA,UAAM,WAAW,IAAI;AAAA,MACnB,CAAC,CAAC,KAAK,MAAM;AACX,oBAAY,CAAC,MAAM,cAAc;AAAA,MACnC;AAAA,MACA,EAAE,MAAM,iBAAiB,WAAW,EAAE;AAAA,IACxC;AACA,aAAS,QAAQ,SAAS;AAE1B,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,SAAS,WAAW,eAAe,CAAC;AAExC,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -3,8 +3,5 @@
3
3
  "sideEffects": [
4
4
  "*.css",
5
5
  "*.scss"
6
- ],
7
- "publishConfig": {
8
- "access": "public"
9
- }
6
+ ]
10
7
  }
@@ -89,6 +89,9 @@ const DSFloatingContextPropTypes = {
89
89
  externalReferenceElement: import_ds_props_helpers.PropTypes.object.description(
90
90
  "Pre-resolved reference element. When provided, used as the source of truth for positioning, avoiding the need for a follow-up refs.setReference() effect and the visibility:hidden race on open."
91
91
  ),
92
+ boundaryElement: import_ds_props_helpers.PropTypes.object.description(
93
+ "Opt-in extra boundary the floating element should stay visually contained within, in addition to the viewport, when portaled. Not the same as withoutPortal: that clips unconditionally via native CSS overflow (cutting off oversized content); this only changes the clipping math and is ignored per-axis when the floating content does not fit that axis of the boundary, so oversized content still escapes fully into the page. Also auto-hides/shows the floating element (visually only) as the reference scrolls in and out of this boundary."
94
+ ),
92
95
  onClickOutside: import_ds_props_helpers.PropTypes.func.description(
93
96
  "Called on mousedown/touchstart outside both the floating element and the reference element while open."
94
97
  ),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/react-desc-prop-types.ts", "../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["/* eslint-disable @typescript-eslint/no-empty-interface */\nimport type React from 'react';\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 /**\n * Describes why onOpen/onClose fired.\n * - `interaction`: driven by this hook's own interaction handling (focus/hover/escape, via\n * ds-hooks-headless-tooltip). Fires synchronously in the event catch phase and carries the\n * originating DOM event when there is one (absent for imperative show/hide). Fires for\n * controlled contexts too \u2014 it is the pre-flip, event-carrying signal a controlled consumer can\n * choose to act on.\n * - `controlled-flip`: driven by the consumer flipping `externallyControlledIsOpen`. Fires from a\n * post-commit effect \u2014 the change originates outside this hook, so there is no event to carry.\n *\n * A controlled consumer may see BOTH for one logical transition (the pre-flip interaction, then\n * the post-flip controlled-flip when they echo it into the prop); discriminate on `reason` (and\n * `isControlled`) to pick which one your logic cares about. That choice is the consumer's \u2014 this\n * hook does not decide it for them.\n */\n export type OpenChange =\n | {\n reason: 'interaction';\n event?: React.FocusEvent | React.MouseEvent | React.KeyboardEvent | KeyboardEvent;\n isControlled: boolean;\n }\n | {\n reason: 'controlled-flip';\n isControlled: true;\n };\n\n export interface OptionalProps {\n placementOrderPreference?: PopperPlacementsT[];\n /**\n * Called on every open transition \u2014 both interaction-driven (pre-flip, with `event`) and\n * controlled-flip-driven (post-flip). Discriminate on the `OpenChange` argument's `reason`.\n */\n onOpen?: (info: OpenChange) => void;\n /**\n * Called on every close transition \u2014 both interaction-driven and controlled-flip-driven.\n * Discriminate on `reason`. Note a scoped Escape close routes to `onEscape` instead of `onClose`\n * (see `onEscape`).\n */\n onClose?: (info: OpenChange) => 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 the floating element is open and focus is within the\n * floating element or the reference element. Only fires when `closeOnEscape` is true.\n * Receives the native KeyboardEvent \u2014 this is delivered via a document-level listener, not a\n * JSX handler, so there is no React SyntheticEvent to hand back.\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.object\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(\n 'Called on every open transition (interaction or controlled-flip); receives arguments with a `reason`.',\n ),\n onClose: PropTypes.func.description(\n 'Called on every close transition (interaction or controlled-flip); receives arguments with a `reason`.',\n ),\n externallyControlledIsOpen: PropTypes.bool.description(\n 'If true, the context open/close state will be controlled externally.',\n ),\n externalReferenceElement: PropTypes.object.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", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADGvB,8BAA0B;AA4FnB,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,kCAAU,KACtB,YAAY,4DAA4D,EACxE,aAAa,KAAK;AAAA,EACrB,kBAAkB,kCAAU,KAAK,YAAY,kDAAkD,EAAE,aAAa,KAAK;AAAA,EACnH,oBAAoB,kCAAU,OAC3B,YAAY,qDAAqD,EACjE,aAAa,uFAAuF;AAAA,EACvG,mBAAmB,kCAAU,OAAO,YAAY,gDAAgD,EAAE,aAAa,GAAG;AAAA,EAClH,WAAW,kCAAU,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,kCAAU,QAAQ,kCAAU,MAAM,EAC7C,YAAY,mCAAmC,EAC/C,aAAa,CAAC,IAAI,EAAE,CAAC;AAAA,EACxB,0BAA0B,kCAAU,UAAU;AAAA,IAC5C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAAA,IAChD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAAA,IAC1C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;AAAA,IAC9C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;AAAA,IAClD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,IAC5C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAAA,IAChD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;AAAA,IACjD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,IAC7C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;AAAA,IACnD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;AAAA,IAC/C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAAA,IAC3C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;AAAA,EACnD,CAAC,EAAE,YAAY,wCAAwC;AAAA,EACvD,QAAQ,kCAAU,KAAK;AAAA,IACrB;AAAA,EACF;AAAA,EACA,SAAS,kCAAU,KAAK;AAAA,IACtB;AAAA,EACF;AAAA,EACA,4BAA4B,kCAAU,KAAK;AAAA,IACzC;AAAA,EACF;AAAA,EACA,0BAA0B,kCAAU,OAAO;AAAA,IACzC;AAAA,EAEF;AAAA,EACA,gBAAgB,kCAAU,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EACA,eAAe,kCAAU,KACtB,YAAY,sFAAsF,EAClG,aAAa,KAAK;AAAA,EACrB,UAAU,kCAAU,KAAK,YAAY,2EAA2E;AAAA,EAChH,wBAAwB,kCAAU,KAC/B,YAAY,qEAAqE,EACjF,aAAa,KAAK;AACvB;",
4
+ "sourcesContent": ["/* eslint-disable @typescript-eslint/no-empty-interface */\nimport type React from 'react';\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 /**\n * Describes why onOpen/onClose fired.\n * - `interaction`: driven by this hook's own interaction handling (focus/hover/escape, via\n * ds-hooks-headless-tooltip). Fires synchronously in the event catch phase and carries the\n * originating DOM event when there is one (absent for imperative show/hide). Fires for\n * controlled contexts too \u2014 it is the pre-flip, event-carrying signal a controlled consumer can\n * choose to act on.\n * - `controlled-flip`: driven by the consumer flipping `externallyControlledIsOpen`. Fires from a\n * post-commit effect \u2014 the change originates outside this hook, so there is no event to carry.\n *\n * A controlled consumer may see BOTH for one logical transition (the pre-flip interaction, then\n * the post-flip controlled-flip when they echo it into the prop); discriminate on `reason` (and\n * `isControlled`) to pick which one your logic cares about. That choice is the consumer's \u2014 this\n * hook does not decide it for them.\n */\n export type OpenChange =\n | {\n reason: 'interaction';\n event?: React.FocusEvent | React.MouseEvent | React.KeyboardEvent | KeyboardEvent;\n isControlled: boolean;\n }\n | {\n reason: 'controlled-flip';\n isControlled: true;\n };\n\n export interface OptionalProps {\n placementOrderPreference?: PopperPlacementsT[];\n /**\n * Called on every open transition \u2014 both interaction-driven (pre-flip, with `event`) and\n * controlled-flip-driven (post-flip). Discriminate on the `OpenChange` argument's `reason`.\n */\n onOpen?: (info: OpenChange) => void;\n /**\n * Called on every close transition \u2014 both interaction-driven and controlled-flip-driven.\n * Discriminate on `reason`. Note a scoped Escape close routes to `onEscape` instead of `onClose`\n * (see `onEscape`).\n */\n onClose?: (info: OpenChange) => 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 * Opt-in extra boundary \u2014 e.g. a scrollable ancestor of the reference \u2014 the floating\n * element should stay visually contained within, in addition to the viewport. Only\n * applied when portaled (withoutPortal: false, the default).\n *\n * Not the same as `withoutPortal: true`, and not a substitute for it: withoutPortal already\n * derives a clipping ancestor automatically, but does so by rendering the floating element\n * as a literal DOM descendant of it \u2014 so the browser's own CSS overflow clipping applies\n * unconditionally, cutting off any floating content taller/wider than that ancestor, with no\n * way for position math to prevent it. boundaryElement keeps the portal, so oversized\n * content (e.g. a full calendar popover inside a short container) still escapes fully into\n * the page, unclipped \u2014 it only changes the clipping *math* used for shift/flip, ignored\n * per-axis when the floating content doesn't fit that axis of the boundary, falling back to\n * the plain viewport for that axis instead of forcing an impossible position.\n *\n * Also hides the floating element (opacity/pointer-events only \u2014 isOpen state is untouched)\n * whenever the reference scrolls fully out of this boundary's visible area, and shows it\n * again automatically once the reference is back in view.\n */\n boundaryElement?: 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 the floating element is open and focus is within the\n * floating element or the reference element. Only fires when `closeOnEscape` is true.\n * Receives the native KeyboardEvent \u2014 this is delivered via a document-level listener, not a\n * JSX handler, so there is no React SyntheticEvent to hand back.\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.object\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(\n 'Called on every open transition (interaction or controlled-flip); receives arguments with a `reason`.',\n ),\n onClose: PropTypes.func.description(\n 'Called on every close transition (interaction or controlled-flip); receives arguments with a `reason`.',\n ),\n externallyControlledIsOpen: PropTypes.bool.description(\n 'If true, the context open/close state will be controlled externally.',\n ),\n externalReferenceElement: PropTypes.object.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 boundaryElement: PropTypes.object.description(\n 'Opt-in extra boundary the floating element should stay visually contained within, in addition to the ' +\n 'viewport, when portaled. Not the same as withoutPortal: that clips unconditionally via native CSS ' +\n 'overflow (cutting off oversized content); this only changes the clipping math and is ignored per-axis ' +\n 'when the floating content does not fit that axis of the boundary, so oversized content still escapes ' +\n 'fully into the page. Also auto-hides/shows the floating element (visually only) as the reference ' +\n 'scrolls in and out of this boundary.',\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", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADGvB,8BAA0B;AAgHnB,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,kCAAU,KACtB,YAAY,4DAA4D,EACxE,aAAa,KAAK;AAAA,EACrB,kBAAkB,kCAAU,KAAK,YAAY,kDAAkD,EAAE,aAAa,KAAK;AAAA,EACnH,oBAAoB,kCAAU,OAC3B,YAAY,qDAAqD,EACjE,aAAa,uFAAuF;AAAA,EACvG,mBAAmB,kCAAU,OAAO,YAAY,gDAAgD,EAAE,aAAa,GAAG;AAAA,EAClH,WAAW,kCAAU,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,kCAAU,QAAQ,kCAAU,MAAM,EAC7C,YAAY,mCAAmC,EAC/C,aAAa,CAAC,IAAI,EAAE,CAAC;AAAA,EACxB,0BAA0B,kCAAU,UAAU;AAAA,IAC5C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAAA,IAChD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAAA,IAC1C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;AAAA,IAC9C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;AAAA,IAClD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,IAC5C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAAA,IAChD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;AAAA,IACjD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,IAC7C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;AAAA,IACnD,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;AAAA,IAC/C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAAA,IAC3C,kCAAU,MAAM,CAAC,kCAAU,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;AAAA,EACnD,CAAC,EAAE,YAAY,wCAAwC;AAAA,EACvD,QAAQ,kCAAU,KAAK;AAAA,IACrB;AAAA,EACF;AAAA,EACA,SAAS,kCAAU,KAAK;AAAA,IACtB;AAAA,EACF;AAAA,EACA,4BAA4B,kCAAU,KAAK;AAAA,IACzC;AAAA,EACF;AAAA,EACA,0BAA0B,kCAAU,OAAO;AAAA,IACzC;AAAA,EAEF;AAAA,EACA,iBAAiB,kCAAU,OAAO;AAAA,IAChC;AAAA,EAMF;AAAA,EACA,gBAAgB,kCAAU,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EACA,eAAe,kCAAU,KACtB,YAAY,sFAAsF,EAClG,aAAa,KAAK;AAAA,EACrB,UAAU,kCAAU,KAAK,YAAY,2EAA2E;AAAA,EAChH,wBAAwB,kCAAU,KAC/B,YAAY,qEAAqE,EACjF,aAAa,KAAK;AACvB;",
6
6
  "names": []
7
7
  }
@@ -25,7 +25,9 @@ var React = __toESM(require("react"));
25
25
  var import_jsx_runtime = require("react/jsx-runtime");
26
26
  var import_react = __toESM(require("react"));
27
27
  var import__ = require("../index.js");
28
- const testOptionalProps = {};
28
+ const testOptionalProps = {
29
+ boundaryElement: document.body
30
+ };
29
31
  const testPartialDefaults = {
30
32
  withoutAnimation: false,
31
33
  withoutPortal: false,
@@ -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 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,YAAuB;ACwErB;AAvEF,mBAAkB;AAClB,eAAgC;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,4EAEE;AAAA;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAU,aAAAA,QAAM,UAA0B;AAAA,MAC1C,QAAM;AAAA,MACN,gBAAgB,CAAC;AAAA,MAEjB,sDAAC,SAAI,qBAAO;AAAA;AAAA,EACd;AAAA,EACA;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAU,aAAAA,QAAM,UAA0B;AAAA,MAC1C,QAAM;AAAA,MACN,gBAAgB,CAAC;AAAA,MAEjB,sDAAC,SAAI,qBAAO;AAAA;AAAA,EACd;AAAA,EACA,4CAAC,4BAAiB,GAAG,uBAAuB,UAAU,aAAAA,QAAM,UAA0B,GAAG,QAAM,MAAC,gBAAgB,CAAC,GAC/G,sDAAC,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\n// boundaryElement (PUI-17187): exercises the optional prop through the same buckets/spreads\n// below, so a broken or missing type on DSHookFloatingContextT.OptionalProps.boundaryElement\n// would fail compilation here.\nconst testOptionalProps: ComponentPropsOptionalProps = {\n boundaryElement: document.body,\n};\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,YAAuB;AC6ErB;AA5EF,mBAAkB;AAClB,eAAgC;AAYhC,MAAM,oBAAiD;AAAA,EACrD,iBAAiB,SAAS;AAC5B;AAIA,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,4EAEE;AAAA;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAU,aAAAA,QAAM,UAA0B;AAAA,MAC1C,QAAM;AAAA,MACN,gBAAgB,CAAC;AAAA,MAEjB,sDAAC,SAAI,qBAAO;AAAA;AAAA,EACd;AAAA,EACA;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAU,aAAAA,QAAM,UAA0B;AAAA,MAC1C,QAAM;AAAA,MACN,gBAAgB,CAAC;AAAA,MAEjB,sDAAC,SAAI,qBAAO;AAAA;AAAA,EACd;AAAA,EACA,4CAAC,4BAAiB,GAAG,uBAAuB,UAAU,aAAAA,QAAM,UAA0B,GAAG,QAAM,MAAC,gBAAgB,CAAC,GAC/G,sDAAC,SAAI,qBAAO,GACd;AAAA,GAEF;",
6
6
  "names": ["React"]
7
7
  }
@@ -43,6 +43,7 @@ const useComputedPositionStyles = (config) => {
43
43
  placementOrderPreference,
44
44
  customOffset,
45
45
  withoutPortal,
46
+ boundaryElement,
46
47
  isClose = false,
47
48
  debounceMs = 150
48
49
  } = config;
@@ -64,7 +65,8 @@ const useComputedPositionStyles = (config) => {
64
65
  placement,
65
66
  placementOrderPreference,
66
67
  customOffset,
67
- withoutPortal
68
+ withoutPortal,
69
+ boundaryElement
68
70
  });
69
71
  const { transform, top, left } = coordsStyle;
70
72
  setFloatingStyles((prev) => {
@@ -80,7 +82,8 @@ const useComputedPositionStyles = (config) => {
80
82
  placement,
81
83
  placementOrderPreference,
82
84
  customOffset,
83
- withoutPortal
85
+ withoutPortal,
86
+ boundaryElement
84
87
  ]);
85
88
  const mutableUpdateStyles = (0, import_react.useRef)(updateStyles);
86
89
  mutableUpdateStyles.current = updateStyles;
@@ -109,7 +112,8 @@ const useComputedPositionStyles = (config) => {
109
112
  placement,
110
113
  placementOrderPreference,
111
114
  customOffset,
112
- withoutPortal
115
+ withoutPortal,
116
+ boundaryElement
113
117
  ]);
114
118
  const forceUpdatePosition = (0, import_react.useCallback)(() => {
115
119
  mutableUpdateStyles.current();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/useComputedPositionStyles.tsx", "../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["/* 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 isClose?: boolean;\n triggerElementReference: Element | null;\n floatingWrapperNode: 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 triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n isClose = 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 = triggerElementReference !== null && floatingWrapperNode !== null && !isClose;\n\n const updateStyles = useCallback(() => {\n if (!canCompute) return;\n\n const { coordsStyle, finalPlacement, coordsArrow } = computePosition({\n triggerElementReference,\n floatingWrapperNode,\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 }, [\n canCompute,\n triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n ]);\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 }, [\n canCompute,\n triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n ]);\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", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADCvB,mBAAwE;AACxE,uBAAyB;AAEzB,6BAAgC;AAiBzB,MAAM,4BAA4B,CAAC,WAAuC;AAC/E,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,aAAa;AAAA,EACf,IAAI;AAEJ,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,MAAM,CAAC;AAStG,QAAM,CAAC,gBAAgB,iBAAiB,QAAI,uBAAwB;AAAA,IAClE,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,eAAe;AAAA,IACf,YAAY;AAAA,EACd,CAAC;AAED,QAAM,CAAC,iBAAiB,kBAAkB,QAAI,uBAAS,KAAK;AAE5D,QAAM,aAAa,4BAA4B,QAAQ,wBAAwB,QAAQ,CAAC;AAExF,QAAM,mBAAe,0BAAY,MAAM;AACrC,QAAI,CAAC,WAAY;AAEjB,UAAM,EAAE,aAAa,gBAAgB,YAAY,QAAI,wCAAgB;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;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,QAAM,0BAAsB,qBAAO,YAAY;AAC/C,sBAAoB,UAAU;AAE9B,QAAM,4BAAwB,sBAAQ,MAAM;AAC1C,UAAM,QAAI,2BAAS,MAAM;AACvB,0BAAoB,QAAQ;AAAA,IAC9B,GAAG,UAAU;AACb,WAAO;AAAA,EAET,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,6BAAyB,qBAAO,qBAAqB;AAC3D,yBAAuB,UAAU;AAGjC;AAAA,IACE,MAAM,MAAM;AACV,4BAAsB,OAAO;AAAA,IAC/B;AAAA,IACA,CAAC,qBAAqB;AAAA,EACxB;AAGA,oCAAgB,MAAM;AACpB,QAAI,YAAY;AACd,0BAAoB,QAAQ;AAAA,IAC9B;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,0BAAsB,0BAAY,MAAM;AAC5C,wBAAoB,QAAQ;AAAA,EAC9B,GAAG,CAAC,CAAC;AAIL,QAAM,0BAAsB,0BAAY,MAAM;AAC5C,sBAAkB,CAAC,UAAU;AAAA,MAC3B,GAAG;AAAA,MACH,SAAS;AAAA,MACT,eAAe;AAAA,IACjB,EAAE;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,aAAO;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;",
4
+ "sourcesContent": ["/* 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 isClose?: boolean;\n triggerElementReference: Element | null;\n floatingWrapperNode: HTMLElement | null;\n placement: DSHookFloatingContextT.PopperPlacementsT;\n placementOrderPreference?: DSHookFloatingContextT.PopperPlacementsT[];\n customOffset: [number, number];\n withoutPortal: boolean;\n /** Opt-in extra boundary the floating element should stay visually contained within, in\n * addition to the viewport, when portaled. See computePosition's boundaryElement doc. */\n boundaryElement?: Element | null;\n /** Debounce ms for scroll/resize/observer events */\n debounceMs?: number;\n};\n\nexport const useComputedPositionStyles = (config: UseComputedPositionStylesT) => {\n const {\n triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n boundaryElement,\n isClose = 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 = triggerElementReference !== null && floatingWrapperNode !== null && !isClose;\n\n const updateStyles = useCallback(() => {\n if (!canCompute) return;\n\n const { coordsStyle, finalPlacement, coordsArrow } = computePosition({\n triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n boundaryElement,\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 }, [\n canCompute,\n triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n boundaryElement,\n ]);\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 }, [\n canCompute,\n triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\n boundaryElement,\n ]);\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", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADCvB,mBAAwE;AACxE,uBAAyB;AAEzB,6BAAgC;AAoBzB,MAAM,4BAA4B,CAAC,WAAuC;AAC/E,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,aAAa;AAAA,EACf,IAAI;AAEJ,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,MAAM,CAAC;AAStG,QAAM,CAAC,gBAAgB,iBAAiB,QAAI,uBAAwB;AAAA,IAClE,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,eAAe;AAAA,IACf,YAAY;AAAA,EACd,CAAC;AAED,QAAM,CAAC,iBAAiB,kBAAkB,QAAI,uBAAS,KAAK;AAE5D,QAAM,aAAa,4BAA4B,QAAQ,wBAAwB,QAAQ,CAAC;AAExF,QAAM,mBAAe,0BAAY,MAAM;AACrC,QAAI,CAAC,WAAY;AAEjB,UAAM,EAAE,aAAa,gBAAgB,YAAY,QAAI,wCAAgB;AAAA,MACnE;AAAA,MACA;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;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,QAAM,0BAAsB,qBAAO,YAAY;AAC/C,sBAAoB,UAAU;AAE9B,QAAM,4BAAwB,sBAAQ,MAAM;AAC1C,UAAM,QAAI,2BAAS,MAAM;AACvB,0BAAoB,QAAQ;AAAA,IAC9B,GAAG,UAAU;AACb,WAAO;AAAA,EAET,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,6BAAyB,qBAAO,qBAAqB;AAC3D,yBAAuB,UAAU;AAGjC;AAAA,IACE,MAAM,MAAM;AACV,4BAAsB,OAAO;AAAA,IAC/B;AAAA,IACA,CAAC,qBAAqB;AAAA,EACxB;AAGA,oCAAgB,MAAM;AACpB,QAAI,YAAY;AACd,0BAAoB,QAAQ;AAAA,IAC9B;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,0BAAsB,0BAAY,MAAM;AAC5C,wBAAoB,QAAQ;AAAA,EAC9B,GAAG,CAAC,CAAC;AAIL,QAAM,0BAAsB,0BAAY,MAAM;AAC5C,sBAAkB,CAAC,UAAU;AAAA,MAC3B,GAAG;AAAA,MACH,SAAS;AAAA,MACT,eAAe;AAAA,IACjB,EAAE;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,aAAO;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
  }
@@ -43,7 +43,8 @@ const computePosition = (props) => {
43
43
  placement,
44
44
  placementOrderPreference,
45
45
  customOffset,
46
- withoutPortal
46
+ withoutPortal,
47
+ boundaryElement
47
48
  } = props;
48
49
  const parentOffsets = withoutPortal ? (0, import_floatingPositioning.adjustForFixedParent)(triggerElementReference) : { top: 0, left: 0 };
49
50
  const referenceRect = triggerElementReference.getBoundingClientRect();
@@ -53,7 +54,15 @@ const computePosition = (props) => {
53
54
  [placement].concat(fallbackPlacements)
54
55
  );
55
56
  const clippingParent = withoutPortal ? (0, import_floatingPositioning.getClippingParent)(triggerElementReference) : null;
56
- const clippingRect = clippingParent ? clippingParent.getBoundingClientRect() : (0, import_floatingPositioning.getViewportRect)();
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
+ }
57
66
  let bestPlacement = placement;
58
67
  let bestOverflows = null;
59
68
  let bestScore = { total: Number.POSITIVE_INFINITY, maxSide: Number.POSITIVE_INFINITY };
@@ -78,8 +87,8 @@ const computePosition = (props) => {
78
87
  let x = -overflows.left - parentOffsets.left;
79
88
  let y = -overflows.top - parentOffsets.top;
80
89
  if (!withoutPortal) {
81
- x += window.scrollX;
82
- y += window.scrollY;
90
+ x += window.scrollX + clippingRect.left;
91
+ y += window.scrollY + clippingRect.top;
83
92
  } else {
84
93
  const op = (0, import_floatingPositioning.getOffsetParentData)(floatingWrapperNode);
85
94
  x = x + clippingRect.left - op.left + op.scrollLeft;
@@ -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 triggerElementReference: Element;\n floatingWrapperNode: 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 {\n triggerElementReference,\n floatingWrapperNode,\n placement,\n placementOrderPreference,\n customOffset,\n withoutPortal,\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 // - Portal => viewport (inset by body padding for Storybook)\n // - No portal => nearest clipping/scroll container (fallback viewport rect)\n const clippingParent = withoutPortal ? getClippingParent(triggerElementReference) : 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(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,iCAWO;AAWA,MAAM,kBAAkB,CAAC,UAAgC;AAC9D,QAAM;AAAA,IACJ;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;AAKA,QAAM,iBAAiB,oBAAgB,8CAAkB,uBAAuB,IAAI;AACpF,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,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;",
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;