@elliemae/ds-floating-context 3.62.1 → 3.70.0-alpha.10

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 (74) hide show
  1. package/dist/cjs/DSFloatingContext.js +93 -36
  2. package/dist/cjs/DSFloatingContext.js.map +2 -2
  3. package/dist/cjs/hooks/useFloatingClickOutside.js +8 -8
  4. package/dist/cjs/hooks/useFloatingClickOutside.js.map +2 -2
  5. package/dist/cjs/hooks/useFloatingResizeObserver.js +11 -5
  6. package/dist/cjs/hooks/useFloatingResizeObserver.js.map +2 -2
  7. package/dist/cjs/hooks/{useFloatingEscape.js → useReferenceHiddenByBoundary.js} +24 -39
  8. package/dist/cjs/hooks/useReferenceHiddenByBoundary.js.map +7 -0
  9. package/dist/cjs/hooks/useResolvedReference.js +2 -2
  10. package/dist/cjs/hooks/useResolvedReference.js.map +2 -2
  11. package/dist/cjs/hooks/useSyntheticEventFromControlledState.js +53 -0
  12. package/dist/cjs/hooks/useSyntheticEventFromControlledState.js.map +7 -0
  13. package/dist/cjs/parts/FloatingWrapper/react-desc-prop-types.js +1 -1
  14. package/dist/cjs/parts/FloatingWrapper/react-desc-prop-types.js.map +2 -2
  15. package/dist/cjs/parts/PopoverArrow.js.map +2 -2
  16. package/dist/cjs/react-desc-prop-types.js +11 -4
  17. package/dist/cjs/react-desc-prop-types.js.map +2 -2
  18. package/dist/cjs/typescript-testing/typescript-floating-context-valid.js +3 -1
  19. package/dist/cjs/typescript-testing/typescript-floating-context-valid.js.map +2 -2
  20. package/dist/cjs/useComputedPositionStyles.js +29 -9
  21. package/dist/cjs/useComputedPositionStyles.js.map +2 -2
  22. package/dist/cjs/utils/computePosition.js +26 -10
  23. package/dist/cjs/utils/computePosition.js.map +2 -2
  24. package/dist/cjs/utils/floatingPositioning.js +11 -0
  25. package/dist/cjs/utils/floatingPositioning.js.map +2 -2
  26. package/dist/esm/DSFloatingContext.js +94 -37
  27. package/dist/esm/DSFloatingContext.js.map +2 -2
  28. package/dist/esm/hooks/useFloatingClickOutside.js +8 -8
  29. package/dist/esm/hooks/useFloatingClickOutside.js.map +2 -2
  30. package/dist/esm/hooks/useFloatingResizeObserver.js +12 -6
  31. package/dist/esm/hooks/useFloatingResizeObserver.js.map +2 -2
  32. package/dist/esm/hooks/useReferenceHiddenByBoundary.js +29 -0
  33. package/dist/esm/hooks/useReferenceHiddenByBoundary.js.map +7 -0
  34. package/dist/esm/hooks/useResolvedReference.js +2 -2
  35. package/dist/esm/hooks/useResolvedReference.js.map +2 -2
  36. package/dist/esm/hooks/useSyntheticEventFromControlledState.js +23 -0
  37. package/dist/esm/hooks/useSyntheticEventFromControlledState.js.map +7 -0
  38. package/dist/esm/parts/FloatingWrapper/react-desc-prop-types.js +1 -1
  39. package/dist/esm/parts/FloatingWrapper/react-desc-prop-types.js.map +2 -2
  40. package/dist/esm/parts/PopoverArrow.js.map +2 -2
  41. package/dist/esm/react-desc-prop-types.js +11 -4
  42. package/dist/esm/react-desc-prop-types.js.map +2 -2
  43. package/dist/esm/typescript-testing/typescript-floating-context-valid.js +3 -1
  44. package/dist/esm/typescript-testing/typescript-floating-context-valid.js.map +2 -2
  45. package/dist/esm/useComputedPositionStyles.js +29 -9
  46. package/dist/esm/useComputedPositionStyles.js.map +2 -2
  47. package/dist/esm/utils/computePosition.js +27 -10
  48. package/dist/esm/utils/computePosition.js.map +2 -2
  49. package/dist/esm/utils/floatingPositioning.js +11 -0
  50. package/dist/esm/utils/floatingPositioning.js.map +2 -2
  51. package/dist/types/DSFloatingContext.d.ts +46 -6
  52. package/dist/types/hooks/useFloatingClickOutside.d.ts +5 -5
  53. package/dist/types/hooks/useFloatingResizeObserver.d.ts +3 -3
  54. package/dist/types/hooks/useReferenceHiddenByBoundary.d.ts +17 -0
  55. package/dist/types/hooks/useResolvedReference.d.ts +1 -1
  56. package/dist/types/hooks/useSyntheticEventFromControlledState.d.ts +32 -0
  57. package/dist/types/react-desc-prop-types.d.ts +59 -4
  58. package/dist/types/useComputedPositionStyles.d.ts +6 -3
  59. package/dist/types/utils/computePosition.d.ts +25 -2
  60. package/dist/types/utils/floatingPositioning.d.ts +13 -1
  61. package/package.json +9 -9
  62. package/dist/cjs/hooks/useFloatingEscape.js.map +0 -7
  63. package/dist/esm/hooks/useFloatingEscape.js +0 -44
  64. package/dist/esm/hooks/useFloatingEscape.js.map +0 -7
  65. package/dist/types/hooks/useFloatingEscape.d.ts +0 -19
  66. package/dist/types/tests/ControlledTestRenderer.d.ts +0 -1
  67. package/dist/types/tests/FloatingContext.a11y.test.d.ts +0 -1
  68. package/dist/types/tests/FloatingContext.data-testid.test.d.ts +0 -1
  69. package/dist/types/tests/FloatingContext.event.test.d.ts +0 -1
  70. package/dist/types/tests/FloatingContext.exports.test.d.ts +0 -1
  71. package/dist/types/tests/FloatingContext.get-owner-props.test.d.ts +0 -1
  72. package/dist/types/tests/FloatingContext.utils.test.d.ts +0 -1
  73. package/dist/types/tests/playwright/FloatingContext.test.playwright.d.ts +0 -1
  74. package/dist/types/typescript-testing/typescript-floating-context-valid.d.ts +0 -1
@@ -0,0 +1,32 @@
1
+ interface UseSyntheticEventFromControlledStateParams {
2
+ /**
3
+ * The externally-controlled value whose transitions are turned into events. `undefined` means
4
+ * the value is not being controlled (uncontrolled usage) — no events are synthesized in that case.
5
+ */
6
+ controlledValue: boolean | undefined;
7
+ /** Fired when `controlledValue` transitions to `true`. Passed inline is fine (latest-ref'd). */
8
+ onEnter?: () => void;
9
+ /** Fired when `controlledValue` transitions to `false`. Passed inline is fine (latest-ref'd). */
10
+ onExit?: () => void;
11
+ }
12
+ /**
13
+ * Synthesizes semantic enter/exit events from transitions of an **externally-controlled** value.
14
+ *
15
+ * Why this is a hook (and an effect), not something a caller should hand-roll: when a value is
16
+ * controlled by the consumer, its changes originate *outside* this component — there is no
17
+ * interaction handler in here to hang a notification on. Reacting to that change and emitting an
18
+ * event is the *sanctioned* `useEffect` use: **synchronizing with an external system**, where the
19
+ * external system is the consumer that owns the controlled state. This is categorically different
20
+ * from the `useEffect` misuse (deriving/coordinating internal state), and it is the reason the
21
+ * emitted callback is a genuine "for whatever reason the owner changed it" event rather than a
22
+ * per-interaction one.
23
+ *
24
+ * Guarantees:
25
+ * - Never fires on mount — only on real transitions after the initial value.
26
+ * - Never fires while uncontrolled (`undefined`); only on explicit `false ↔ true` edges.
27
+ * - The prev-value bookkeeping is a ref (not state) *on purpose*: it is read/written only inside
28
+ * the effect (post-commit), never during render, so it carries none of the render-purity hazards
29
+ * that would make a ref wrong for an in-render transition guard.
30
+ */
31
+ export declare const useSyntheticEventFromControlledState: ({ controlledValue, onEnter, onExit, }: UseSyntheticEventFromControlledStateParams) => void;
32
+ export {};
@@ -1,3 +1,4 @@
1
+ import type React from 'react';
1
2
  import type { DSPropTypesSchema } from '@elliemae/ds-props-helpers';
2
3
  export declare namespace DSHookFloatingContextT {
3
4
  interface DefaultProps {
@@ -10,10 +11,42 @@ export declare namespace DSHookFloatingContextT {
10
11
  closeOnEscape: boolean;
11
12
  returnFocusToReference: boolean;
12
13
  }
14
+ /**
15
+ * Describes why onOpen/onClose fired.
16
+ * - `interaction`: driven by this hook's own interaction handling (focus/hover/escape, via
17
+ * ds-hooks-headless-tooltip). Fires synchronously in the event catch phase and carries the
18
+ * originating DOM event when there is one (absent for imperative show/hide). Fires for
19
+ * controlled contexts too — it is the pre-flip, event-carrying signal a controlled consumer can
20
+ * choose to act on.
21
+ * - `controlled-flip`: driven by the consumer flipping `externallyControlledIsOpen`. Fires from a
22
+ * post-commit effect — the change originates outside this hook, so there is no event to carry.
23
+ *
24
+ * A controlled consumer may see BOTH for one logical transition (the pre-flip interaction, then
25
+ * the post-flip controlled-flip when they echo it into the prop); discriminate on `reason` (and
26
+ * `isControlled`) to pick which one your logic cares about. That choice is the consumer's — this
27
+ * hook does not decide it for them.
28
+ */
29
+ type OpenChange = {
30
+ reason: 'interaction';
31
+ event?: React.FocusEvent | React.MouseEvent | React.KeyboardEvent | KeyboardEvent;
32
+ isControlled: boolean;
33
+ } | {
34
+ reason: 'controlled-flip';
35
+ isControlled: true;
36
+ };
13
37
  interface OptionalProps {
14
38
  placementOrderPreference?: PopperPlacementsT[];
15
- onOpen?: () => void;
16
- onClose?: () => void;
39
+ /**
40
+ * Called on every open transition — both interaction-driven (pre-flip, with `event`) and
41
+ * controlled-flip-driven (post-flip). Discriminate on the `OpenChange` argument's `reason`.
42
+ */
43
+ onOpen?: (info: OpenChange) => void;
44
+ /**
45
+ * Called on every close transition — both interaction-driven and controlled-flip-driven.
46
+ * Discriminate on `reason`. Note a scoped Escape close routes to `onEscape` instead of `onClose`
47
+ * (see `onEscape`).
48
+ */
49
+ onClose?: (info: OpenChange) => void;
17
50
  externallyControlledIsOpen?: boolean;
18
51
  /**
19
52
  * Pre-resolved reference element. When provided, the hook uses this as the
@@ -22,14 +55,36 @@ export declare namespace DSHookFloatingContextT {
22
55
  * removes the visibility:hidden race that breaks programmatic focus on open.
23
56
  */
24
57
  externalReferenceElement?: Element | null;
58
+ /**
59
+ * Opt-in extra boundary — e.g. a scrollable ancestor of the reference — the floating
60
+ * element should stay visually contained within, in addition to the viewport. Only
61
+ * applied when portaled (withoutPortal: false, the default).
62
+ *
63
+ * Not the same as `withoutPortal: true`, and not a substitute for it: withoutPortal already
64
+ * derives a clipping ancestor automatically, but does so by rendering the floating element
65
+ * as a literal DOM descendant of it — so the browser's own CSS overflow clipping applies
66
+ * unconditionally, cutting off any floating content taller/wider than that ancestor, with no
67
+ * way for position math to prevent it. boundaryElement keeps the portal, so oversized
68
+ * content (e.g. a full calendar popover inside a short container) still escapes fully into
69
+ * the page, unclipped — it only changes the clipping *math* used for shift/flip, ignored
70
+ * per-axis when the floating content doesn't fit that axis of the boundary, falling back to
71
+ * the plain viewport for that axis instead of forcing an impossible position.
72
+ *
73
+ * Also hides the floating element (opacity/pointer-events only — isOpen state is untouched)
74
+ * whenever the reference scrolls fully out of this boundary's visible area, and shows it
75
+ * again automatically once the reference is back in view.
76
+ */
77
+ boundaryElement?: Element | null;
25
78
  /**
26
79
  * Called when the user clicks/taps outside both the floating element and
27
80
  * the reference element while the floating is open.
28
81
  */
29
82
  onClickOutside?: (event: MouseEvent | TouchEvent) => void;
30
83
  /**
31
- * Called when Escape is pressed while focus is within the floating element.
32
- * Only fires when `closeOnEscape` is true.
84
+ * Called when Escape is pressed while the floating element is open and focus is within the
85
+ * floating element or the reference element. Only fires when `closeOnEscape` is true.
86
+ * Receives the native KeyboardEvent — this is delivered via a document-level listener, not a
87
+ * JSX handler, so there is no React SyntheticEvent to hand back.
33
88
  */
34
89
  onEscape?: (event: KeyboardEvent) => void;
35
90
  }
@@ -3,13 +3,16 @@ import type { DSHookFloatingContextT } from './react-desc-prop-types.js';
3
3
  import type { PopoverArrowT } from './parts/PopoverArrow.js';
4
4
  type UseComputedPositionStylesT = {
5
5
  /** Prevent computing when closed (optimization + avoids unnecessary frames) */
6
- preventComputing?: boolean;
7
- reference: Element | null;
8
- floating: HTMLElement | null;
6
+ isClose?: boolean;
7
+ triggerElementReference: Element | null;
8
+ floatingWrapperNode: HTMLElement | null;
9
9
  placement: DSHookFloatingContextT.PopperPlacementsT;
10
10
  placementOrderPreference?: DSHookFloatingContextT.PopperPlacementsT[];
11
11
  customOffset: [number, number];
12
12
  withoutPortal: boolean;
13
+ /** Opt-in extra boundary the floating element should stay visually contained within, in
14
+ * addition to the viewport, when portaled. See computePosition's boundaryElement doc. */
15
+ boundaryElement?: Element | null;
13
16
  /** Debounce ms for scroll/resize/observer events */
14
17
  debounceMs?: number;
15
18
  };
@@ -1,11 +1,34 @@
1
1
  import type { DSHookFloatingContextT } from '../react-desc-prop-types.js';
2
2
  interface ComputePositionProps {
3
- reference: Element;
4
- floating: HTMLElement;
3
+ triggerElementReference: Element;
4
+ floatingWrapperNode: HTMLElement;
5
5
  placement: DSHookFloatingContextT.PopperPlacementsT;
6
6
  placementOrderPreference?: DSHookFloatingContextT.PopperPlacementsT[];
7
7
  customOffset: [number, number];
8
8
  withoutPortal: boolean;
9
+ /**
10
+ * Opt-in extra boundary (e.g. a scrollable ancestor of the reference) the floating element
11
+ * should stay visually contained within, in addition to the viewport. Only applied when
12
+ * portaled — the non-portal path already derives its own clipping ancestor automatically via
13
+ * getClippingParent, by walking up from the reference.
14
+ *
15
+ * Why this exists instead of just setting `withoutPortal: true` to get that same automatic
16
+ * clipping-ancestor derivation: withoutPortal renders the floating element as a literal DOM
17
+ * descendant of the clipping ancestor, so the browser's own CSS overflow clipping applies
18
+ * unconditionally — any floating content taller/wider than that ancestor gets visually cut
19
+ * off, with no way for position math to prevent it. That is fine for content that always fits
20
+ * (e.g. a small filter's low/high inputs), but breaks content that can legitimately be larger
21
+ * than its reference's scroll container (e.g. a date-range calendar popover inside a short
22
+ * table row) — see PUI-17187 in ds-data-table's FilterPopover for the exact regression this
23
+ * caused when withoutPortal was tried first.
24
+ *
25
+ * boundaryElement keeps the portal (so oversized content still escapes fully into the page,
26
+ * unclipped, exactly like the no-boundaryElement case) and only changes the clipping *rect*
27
+ * used for shift/flip math — ignored per-axis when the floating content doesn't fit that axis
28
+ * of the boundary (see getBoundaryConstrainedRect), falling back to the plain viewport for
29
+ * that axis so oversized content is never forced into an impossible position.
30
+ */
31
+ boundaryElement?: Element | null;
9
32
  }
10
33
  export declare const computePosition: (props: ComputePositionProps) => {
11
34
  coordsStyle: {
@@ -21,6 +21,18 @@ declare const adjustForFixedParent: (child: Element) => {
21
21
  */
22
22
  declare const getClippingParent: (el: Element | null) => Element | null;
23
23
  declare const getViewportRect: () => RectLike;
24
+ /**
25
+ * Intersects the viewport with an explicit boundary (e.g. a scrollable ancestor the consumer
26
+ * wants the floating element visually contained within), one axis at a time. An axis is only
27
+ * constrained to the boundary when the floating element's own size actually fits within it —
28
+ * otherwise that axis falls back to the plain viewport. Without this fallback, content larger
29
+ * than the boundary (e.g. a full calendar inside a short table row) gets forced into an
30
+ * impossible position instead of being allowed to escape it, as it does today.
31
+ */
32
+ declare const getBoundaryConstrainedRect: (viewportRect: RectLike, boundaryRect: RectLike, floatingSize: {
33
+ width: number;
34
+ height: number;
35
+ }) => RectLike;
24
36
  /**
25
37
  * Shift behavior: if any overflow side is positive, push the popper back inside.
26
38
  * Similar to Floating UI "shift" middleware.
@@ -50,4 +62,4 @@ declare const getOffsetParentData: (floating: HTMLElement) => {
50
62
  scrollLeft: number;
51
63
  scrollTop: number;
52
64
  };
53
- export { applyShift, adjustForFixedParent, expandWithVariations, fits, getClippingParent, getOverflowScore, getOffsetParentData, getViewportRect, type RectLike, type OverflowOffsets, };
65
+ export { applyShift, adjustForFixedParent, expandWithVariations, fits, getBoundaryConstrainedRect, getClippingParent, getOverflowScore, getOffsetParentData, getViewportRect, type RectLike, type OverflowOffsets, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-floating-context",
3
- "version": "3.62.1",
3
+ "version": "3.70.0-alpha.10",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Popper Hook",
6
6
  "files": [
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "https://git.elliemae.io/platform-ui/dimsum.git"
25
+ "url": "https://github.com/intcx/PLATFORM-UI.dimsum.git"
26
26
  },
27
27
  "engines": {
28
28
  "pnpm": ">=9",
@@ -36,20 +36,20 @@
36
36
  "indent": 4
37
37
  },
38
38
  "dependencies": {
39
- "@elliemae/ds-hooks-headless-tooltip": "3.62.1",
40
- "@elliemae/ds-props-helpers": "3.62.1",
41
- "@elliemae/ds-system": "3.62.1",
42
- "@elliemae/ds-typescript-helpers": "3.62.1"
39
+ "@elliemae/ds-hooks-headless-tooltip": "3.70.0-alpha.10",
40
+ "@elliemae/ds-system": "3.70.0-alpha.10",
41
+ "@elliemae/ds-props-helpers": "3.70.0-alpha.10",
42
+ "@elliemae/ds-typescript-helpers": "3.70.0-alpha.10"
43
43
  },
44
44
  "devDependencies": {
45
45
  "jest": "^30.0.0",
46
- "@elliemae/ds-monorepo-devops": "3.62.1"
46
+ "@elliemae/ds-monorepo-devops": "3.70.0-alpha.10"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "lodash-es": "^4.18.1",
50
50
  "react": "^18.3.1",
51
51
  "react-dom": "^18.3.1",
52
- "styled-components": "~5.3.9",
52
+ "styled-components": "~5.3.11",
53
53
  "styled-system": "^5.1.5"
54
54
  },
55
55
  "publishConfig": {
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "scripts": {
60
60
  "dev": "cross-env NODE_ENV=development node ../../../scripts/build/build.mjs --watch",
61
- "test": "playwright test -c ./playwright.config.mjs --pass-with-no-tests && ds-monorepo-devops test --passWithNoTests --coverage=\"false\"",
61
+ "test": "playwright test -c ./playwright.config.mjs && ds-monorepo-devops test --passWithNoTests --coverage=\"false\"",
62
62
  "lint": "node ../../../scripts/lint.mjs --fix",
63
63
  "lint:strict": "node ../../../scripts/lint-strict.mjs",
64
64
  "dts": "node ../../../scripts/dts.mjs",
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/hooks/useFloatingEscape.ts", "../../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["import { useCallback, useEffect, type MutableRefObject } from 'react';\n\ninterface UseFloatingEscapeParams {\n enabled: boolean;\n floating: HTMLElement | null;\n reference: Element | null;\n /** Latest-ref of the consumer's onEscape callback. Construct with `useLatestRef(onEscape)`. */\n onEscapeRef: MutableRefObject<((event: KeyboardEvent) => void) | undefined>;\n /** Fallback close handler invoked when no `onEscape` is provided. */\n handleClose: () => void;\n /** When true, calls `reference.focus()` after the escape close. */\n returnFocusToReference: boolean;\n}\n\n/**\n * Listens for `Escape` on the document and dismisses the floating element when focus is\n * within the floating element or on the reference element. Outside that scope the event is\n * ignored \u2014 we don't hijack Escape for unrelated UI on the page.\n */\nexport const useFloatingEscape = ({\n enabled,\n floating,\n reference,\n onEscapeRef,\n handleClose,\n returnFocusToReference,\n}: UseFloatingEscapeParams) => {\n const isFocusWithinFloatingScope = useCallback(\n (active: Element | null): boolean => {\n if (!active) return false;\n if (floating && floating.contains(active)) return true;\n if (!reference) return false;\n return reference === active || reference.contains(active);\n },\n [floating, reference],\n );\n\n useEffect(() => {\n if (!enabled) return undefined;\n\n const listener = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n if (!isFocusWithinFloatingScope(document.activeElement)) return;\n\n const cb = onEscapeRef.current;\n if (cb) {\n cb(event);\n } else {\n handleClose();\n }\n if (returnFocusToReference && reference instanceof HTMLElement) {\n reference.focus();\n }\n };\n\n document.addEventListener('keydown', listener, true);\n return () => {\n document.removeEventListener('keydown', listener, true);\n };\n }, [enabled, handleClose, isFocusWithinFloatingScope, onEscapeRef, reference, returnFocusToReference]);\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,mBAA8D;AAmBvD,MAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAA+B;AAC7B,QAAM,iCAA6B;AAAA,IACjC,CAAC,WAAoC;AACnC,UAAI,CAAC,OAAQ,QAAO;AACpB,UAAI,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AAClD,UAAI,CAAC,UAAW,QAAO;AACvB,aAAO,cAAc,UAAU,UAAU,SAAS,MAAM;AAAA,IAC1D;AAAA,IACA,CAAC,UAAU,SAAS;AAAA,EACtB;AAEA,8BAAU,MAAM;AACd,QAAI,CAAC,QAAS,QAAO;AAErB,UAAM,WAAW,CAAC,UAAyB;AACzC,UAAI,MAAM,QAAQ,SAAU;AAC5B,UAAI,CAAC,2BAA2B,SAAS,aAAa,EAAG;AAEzD,YAAM,KAAK,YAAY;AACvB,UAAI,IAAI;AACN,WAAG,KAAK;AAAA,MACV,OAAO;AACL,oBAAY;AAAA,MACd;AACA,UAAI,0BAA0B,qBAAqB,aAAa;AAC9D,kBAAU,MAAM;AAAA,MAClB;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,UAAU,IAAI;AACnD,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,UAAU,IAAI;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,SAAS,aAAa,4BAA4B,aAAa,WAAW,sBAAsB,CAAC;AACvG;",
6
- "names": []
7
- }
@@ -1,44 +0,0 @@
1
- import * as React from "react";
2
- import { useCallback, useEffect } from "react";
3
- const useFloatingEscape = ({
4
- enabled,
5
- floating,
6
- reference,
7
- onEscapeRef,
8
- handleClose,
9
- returnFocusToReference
10
- }) => {
11
- const isFocusWithinFloatingScope = useCallback(
12
- (active) => {
13
- if (!active) return false;
14
- if (floating && floating.contains(active)) return true;
15
- if (!reference) return false;
16
- return reference === active || reference.contains(active);
17
- },
18
- [floating, reference]
19
- );
20
- useEffect(() => {
21
- if (!enabled) return void 0;
22
- const listener = (event) => {
23
- if (event.key !== "Escape") return;
24
- if (!isFocusWithinFloatingScope(document.activeElement)) return;
25
- const cb = onEscapeRef.current;
26
- if (cb) {
27
- cb(event);
28
- } else {
29
- handleClose();
30
- }
31
- if (returnFocusToReference && reference instanceof HTMLElement) {
32
- reference.focus();
33
- }
34
- };
35
- document.addEventListener("keydown", listener, true);
36
- return () => {
37
- document.removeEventListener("keydown", listener, true);
38
- };
39
- }, [enabled, handleClose, isFocusWithinFloatingScope, onEscapeRef, reference, returnFocusToReference]);
40
- };
41
- export {
42
- useFloatingEscape
43
- };
44
- //# sourceMappingURL=useFloatingEscape.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../../../scripts/build/transpile/react-shim.js", "../../../src/hooks/useFloatingEscape.ts"],
4
- "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { useCallback, useEffect, type MutableRefObject } from 'react';\n\ninterface UseFloatingEscapeParams {\n enabled: boolean;\n floating: HTMLElement | null;\n reference: Element | null;\n /** Latest-ref of the consumer's onEscape callback. Construct with `useLatestRef(onEscape)`. */\n onEscapeRef: MutableRefObject<((event: KeyboardEvent) => void) | undefined>;\n /** Fallback close handler invoked when no `onEscape` is provided. */\n handleClose: () => void;\n /** When true, calls `reference.focus()` after the escape close. */\n returnFocusToReference: boolean;\n}\n\n/**\n * Listens for `Escape` on the document and dismisses the floating element when focus is\n * within the floating element or on the reference element. Outside that scope the event is\n * ignored \u2014 we don't hijack Escape for unrelated UI on the page.\n */\nexport const useFloatingEscape = ({\n enabled,\n floating,\n reference,\n onEscapeRef,\n handleClose,\n returnFocusToReference,\n}: UseFloatingEscapeParams) => {\n const isFocusWithinFloatingScope = useCallback(\n (active: Element | null): boolean => {\n if (!active) return false;\n if (floating && floating.contains(active)) return true;\n if (!reference) return false;\n return reference === active || reference.contains(active);\n },\n [floating, reference],\n );\n\n useEffect(() => {\n if (!enabled) return undefined;\n\n const listener = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n if (!isFocusWithinFloatingScope(document.activeElement)) return;\n\n const cb = onEscapeRef.current;\n if (cb) {\n cb(event);\n } else {\n handleClose();\n }\n if (returnFocusToReference && reference instanceof HTMLElement) {\n reference.focus();\n }\n };\n\n document.addEventListener('keydown', listener, true);\n return () => {\n document.removeEventListener('keydown', listener, true);\n };\n }, [enabled, handleClose, isFocusWithinFloatingScope, onEscapeRef, reference, returnFocusToReference]);\n};\n"],
5
- "mappings": "AAAA,YAAY,WAAW;ACAvB,SAAS,aAAa,iBAAwC;AAmBvD,MAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAA+B;AAC7B,QAAM,6BAA6B;AAAA,IACjC,CAAC,WAAoC;AACnC,UAAI,CAAC,OAAQ,QAAO;AACpB,UAAI,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AAClD,UAAI,CAAC,UAAW,QAAO;AACvB,aAAO,cAAc,UAAU,UAAU,SAAS,MAAM;AAAA,IAC1D;AAAA,IACA,CAAC,UAAU,SAAS;AAAA,EACtB;AAEA,YAAU,MAAM;AACd,QAAI,CAAC,QAAS,QAAO;AAErB,UAAM,WAAW,CAAC,UAAyB;AACzC,UAAI,MAAM,QAAQ,SAAU;AAC5B,UAAI,CAAC,2BAA2B,SAAS,aAAa,EAAG;AAEzD,YAAM,KAAK,YAAY;AACvB,UAAI,IAAI;AACN,WAAG,KAAK;AAAA,MACV,OAAO;AACL,oBAAY;AAAA,MACd;AACA,UAAI,0BAA0B,qBAAqB,aAAa;AAC9D,kBAAU,MAAM;AAAA,MAClB;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,UAAU,IAAI;AACnD,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,UAAU,IAAI;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,SAAS,aAAa,4BAA4B,aAAa,WAAW,sBAAsB,CAAC;AACvG;",
6
- "names": []
7
- }
@@ -1,19 +0,0 @@
1
- import { type MutableRefObject } from 'react';
2
- interface UseFloatingEscapeParams {
3
- enabled: boolean;
4
- floating: HTMLElement | null;
5
- reference: Element | null;
6
- /** Latest-ref of the consumer's onEscape callback. Construct with `useLatestRef(onEscape)`. */
7
- onEscapeRef: MutableRefObject<((event: KeyboardEvent) => void) | undefined>;
8
- /** Fallback close handler invoked when no `onEscape` is provided. */
9
- handleClose: () => void;
10
- /** When true, calls `reference.focus()` after the escape close. */
11
- returnFocusToReference: boolean;
12
- }
13
- /**
14
- * Listens for `Escape` on the document and dismisses the floating element when focus is
15
- * within the floating element or on the reference element. Outside that scope the event is
16
- * ignored — we don't hijack Escape for unrelated UI on the page.
17
- */
18
- export declare const useFloatingEscape: ({ enabled, floating, reference, onEscapeRef, handleClose, returnFocusToReference, }: UseFloatingEscapeParams) => void;
19
- export {};
@@ -1 +0,0 @@
1
- export const ControlledTestRenderer: import("react").FunctionComponent<import("react").JSX.IntrinsicAttributes>;
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};