@elliemae/ds-floating-context 3.70.0-next.7 → 3.70.0-next.71
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.
- package/dist/cjs/DSFloatingContext.js +129 -41
- package/dist/cjs/DSFloatingContext.js.map +2 -2
- package/dist/cjs/hooks/useFloatingClickOutside.js +60 -0
- package/dist/cjs/hooks/useFloatingClickOutside.js.map +7 -0
- package/dist/cjs/hooks/useFloatingResizeObserver.js +52 -0
- package/dist/cjs/hooks/useFloatingResizeObserver.js.map +7 -0
- package/dist/cjs/hooks/useLatestRef.js +41 -0
- package/dist/cjs/hooks/useLatestRef.js.map +7 -0
- package/dist/cjs/hooks/useReferenceHiddenByBoundary.js +59 -0
- package/dist/cjs/hooks/useReferenceHiddenByBoundary.js.map +7 -0
- package/dist/cjs/hooks/useResolvedReference.js +52 -0
- package/dist/cjs/hooks/useResolvedReference.js.map +7 -0
- package/dist/cjs/hooks/useSyntheticEventFromControlledState.js +53 -0
- package/dist/cjs/hooks/useSyntheticEventFromControlledState.js.map +7 -0
- package/dist/cjs/package.json +1 -4
- package/dist/cjs/parts/FloatingWrapper/react-desc-prop-types.js +1 -1
- package/dist/cjs/parts/FloatingWrapper/react-desc-prop-types.js.map +2 -2
- package/dist/cjs/parts/PopoverArrow.js.map +2 -2
- package/dist/cjs/react-desc-prop-types.js +23 -5
- package/dist/cjs/react-desc-prop-types.js.map +2 -2
- package/dist/cjs/typescript-testing/typescript-floating-context-valid.js +8 -2
- package/dist/cjs/typescript-testing/typescript-floating-context-valid.js.map +2 -2
- package/dist/cjs/useComputedPositionStyles.js +41 -18
- package/dist/cjs/useComputedPositionStyles.js.map +2 -2
- package/dist/cjs/utils/computePosition.js +26 -10
- package/dist/cjs/utils/computePosition.js.map +2 -2
- package/dist/cjs/utils/floatingPositioning.js +11 -0
- package/dist/cjs/utils/floatingPositioning.js.map +2 -2
- package/dist/esm/DSFloatingContext.js +130 -42
- package/dist/esm/DSFloatingContext.js.map +2 -2
- package/dist/esm/hooks/useFloatingClickOutside.js +30 -0
- package/dist/esm/hooks/useFloatingClickOutside.js.map +7 -0
- package/dist/esm/hooks/useFloatingResizeObserver.js +22 -0
- package/dist/esm/hooks/useFloatingResizeObserver.js.map +7 -0
- package/dist/esm/hooks/useLatestRef.js +11 -0
- package/dist/esm/hooks/useLatestRef.js.map +7 -0
- package/dist/esm/hooks/useReferenceHiddenByBoundary.js +29 -0
- package/dist/esm/hooks/useReferenceHiddenByBoundary.js.map +7 -0
- package/dist/esm/hooks/useResolvedReference.js +22 -0
- package/dist/esm/hooks/useResolvedReference.js.map +7 -0
- package/dist/esm/hooks/useSyntheticEventFromControlledState.js +23 -0
- package/dist/esm/hooks/useSyntheticEventFromControlledState.js.map +7 -0
- package/dist/esm/package.json +1 -4
- package/dist/esm/parts/FloatingWrapper/react-desc-prop-types.js +1 -1
- package/dist/esm/parts/FloatingWrapper/react-desc-prop-types.js.map +2 -2
- package/dist/esm/parts/PopoverArrow.js.map +2 -2
- package/dist/esm/react-desc-prop-types.js +23 -5
- package/dist/esm/react-desc-prop-types.js.map +2 -2
- package/dist/esm/typescript-testing/typescript-floating-context-valid.js +8 -2
- package/dist/esm/typescript-testing/typescript-floating-context-valid.js.map +2 -2
- package/dist/esm/useComputedPositionStyles.js +41 -18
- package/dist/esm/useComputedPositionStyles.js.map +2 -2
- package/dist/esm/utils/computePosition.js +27 -10
- package/dist/esm/utils/computePosition.js.map +2 -2
- package/dist/esm/utils/floatingPositioning.js +11 -0
- package/dist/esm/utils/floatingPositioning.js.map +2 -2
- package/dist/types/DSFloatingContext.d.ts +48 -8
- package/dist/types/hooks/useFloatingClickOutside.d.ts +20 -0
- package/dist/types/hooks/useFloatingResizeObserver.d.ts +11 -0
- package/dist/types/hooks/useLatestRef.d.ts +7 -0
- package/dist/types/hooks/useReferenceHiddenByBoundary.d.ts +17 -0
- package/dist/types/hooks/useResolvedReference.d.ts +19 -0
- package/dist/types/hooks/useSyntheticEventFromControlledState.d.ts +32 -0
- package/dist/types/react-desc-prop-types.d.ts +76 -2
- package/dist/types/useComputedPositionStyles.d.ts +7 -4
- package/dist/types/utils/computePosition.d.ts +25 -2
- package/dist/types/utils/floatingPositioning.d.ts +13 -1
- package/package.json +10 -10
- package/dist/types/tests/ControlledTestRenderer.d.ts +0 -1
- package/dist/types/tests/FloatingContext.a11y.test.d.ts +0 -1
- package/dist/types/tests/FloatingContext.data-testid.test.d.ts +0 -1
- package/dist/types/tests/FloatingContext.event.test.d.ts +0 -1
- package/dist/types/tests/FloatingContext.exports.test.d.ts +0 -1
- package/dist/types/tests/FloatingContext.get-owner-props.test.d.ts +0 -1
- package/dist/types/tests/FloatingContext.utils.test.d.ts +0 -1
- package/dist/types/tests/playwright/FloatingContext.test.playwright.d.ts +0 -1
- package/dist/types/typescript-testing/typescript-floating-context-valid.d.ts +0 -1
|
@@ -1,31 +1,71 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { DSHookFloatingContextT } from './react-desc-prop-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* useFloatingContext — headless positioning + open/close lifecycle for a floating surface
|
|
5
|
+
* (popover / tooltip / dialog-like panel). Returns positioning styles + refs, imperative show/hide
|
|
6
|
+
* helpers, interaction handlers to spread on a trigger, and a discriminated open/close notification.
|
|
7
|
+
*
|
|
8
|
+
* COUPLING: this hook is the shared substrate under ds-app-picker, ds-tooltip and others; its
|
|
9
|
+
* open/close/escape/reason contract is relied on across packages. The consumer that leans hardest on
|
|
10
|
+
* the exact semantics below is ds-app-picker
|
|
11
|
+
* (layout/ds-app-picker/src/parts/AppPickerFloatingContext/useAppPickerFloatingContext.ts). Treat
|
|
12
|
+
* changes to these semantics as cross-package.
|
|
13
|
+
*
|
|
14
|
+
* OPEN STATE — controlled vs uncontrolled
|
|
15
|
+
* - Uncontrolled: `externallyControlledIsOpen` undefined → open state lives here (in the underlying
|
|
16
|
+
* ds-hooks-headless-tooltip) and is driven by the focus/hover handlers + the show/hide helpers.
|
|
17
|
+
* - Controlled: `externallyControlledIsOpen` is a boolean → the consumer owns visibility and the
|
|
18
|
+
* internal open state is inert. `isControlled` is surfaced on every OpenChange payload.
|
|
19
|
+
*
|
|
20
|
+
* NOTIFICATION — one onOpen/onClose pair, two reasons (see OpenChange in react-desc-prop-types)
|
|
21
|
+
* - 'interaction': fired synchronously in the event catch phase by this hook's own interaction
|
|
22
|
+
* handling — focus/hover/blur/mouseleave, an imperative show/hide, or a NON-scoped Escape falling
|
|
23
|
+
* through to onClose — carrying the DOM event when there is one. Fires for controlled contexts too
|
|
24
|
+
* (the pre-flip signal a controlled consumer may act on).
|
|
25
|
+
* - 'controlled-flip': fired from a post-commit effect (useSyntheticEventFromControlledState) when
|
|
26
|
+
* `externallyControlledIsOpen` transitions. No event. NEVER fires on mount — a surface rendered
|
|
27
|
+
* already-open gets no onOpen and must seed any initial focus itself.
|
|
28
|
+
*
|
|
29
|
+
* ESCAPE — one listener, not two
|
|
30
|
+
* Escape dismissal rides ds-hooks-headless-tooltip's single always-on document keydown listener (it
|
|
31
|
+
* calls hideTooltip on ANY Escape, even when this context never opened). handleClose is the one place
|
|
32
|
+
* that interprets it:
|
|
33
|
+
* - SCOPED Escape (was open + closeOnEscape + key 'Escape' + focus within the panel/reference)
|
|
34
|
+
* → routes to `onEscape` (NOT onClose); if returnFocusToReference, focuses the reference.
|
|
35
|
+
* - anything else, including a non-scoped Escape → `onClose` with reason 'interaction'.
|
|
36
|
+
* returnFocusToReference is a SCOPED-ESCAPE-ONLY affordance; no other close path moves focus.
|
|
37
|
+
*
|
|
38
|
+
* FRAGILITY: closeOnEscape is honored independently of who owns open state. A CONTROLLED consumer that
|
|
39
|
+
* leaves closeOnEscape on but does not itself close on Escape will have focus returned to the reference
|
|
40
|
+
* on a scoped Escape while the consumer-owned open state has not changed — focus can leave a panel that
|
|
41
|
+
* stays open. Controlled consumers should own Escape (ds-app-picker uses `closeOnEscape: !onKeyDown`).
|
|
42
|
+
*/
|
|
3
43
|
declare const useFloatingContext: {
|
|
4
44
|
(props?: DSHookFloatingContextT.Props): {
|
|
5
45
|
refs: {
|
|
6
|
-
setReference:
|
|
46
|
+
setReference: (el: Element | null) => void;
|
|
7
47
|
setFloating: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
|
|
8
48
|
floating: HTMLElement | null;
|
|
9
49
|
reference: Element | null;
|
|
10
50
|
};
|
|
11
51
|
floatingStyles: React.CSSProperties;
|
|
12
52
|
handlers: {
|
|
13
|
-
onMouseEnter:
|
|
14
|
-
onMouseLeave:
|
|
15
|
-
onFocus:
|
|
16
|
-
onBlur:
|
|
53
|
+
onMouseEnter: React.MouseEventHandler<Element>;
|
|
54
|
+
onMouseLeave: React.MouseEventHandler<Element>;
|
|
55
|
+
onFocus: React.FocusEventHandler<Element>;
|
|
56
|
+
onBlur: React.FocusEventHandler<Element>;
|
|
17
57
|
};
|
|
18
58
|
isOpen: boolean;
|
|
19
59
|
arrowStyles: import("./parts/PopoverArrow.js").PopoverArrowT;
|
|
20
|
-
hideTooltip: () => void;
|
|
21
|
-
showTooltip: () => void;
|
|
60
|
+
hideTooltip: (event?: React.FocusEvent | React.MouseEvent | KeyboardEvent) => void;
|
|
61
|
+
showTooltip: (event?: React.FocusEvent | React.MouseEvent | React.KeyboardEvent) => void;
|
|
22
62
|
context: {
|
|
23
63
|
withoutPortal: boolean;
|
|
24
64
|
withoutAnimation: boolean;
|
|
25
65
|
portalDOMContainer: HTMLElement | undefined;
|
|
26
66
|
animationDuration: number;
|
|
27
67
|
};
|
|
28
|
-
mutableUpdateStyles: React.MutableRefObject<() => void
|
|
68
|
+
mutableUpdateStyles: React.MutableRefObject<import("lodash").DebouncedFunc<() => void>>;
|
|
29
69
|
forceUpdatePosition: () => void;
|
|
30
70
|
};
|
|
31
71
|
displayName: string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type MutableRefObject } from 'react';
|
|
2
|
+
interface UseFloatingClickOutsideParams {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
floatingWrapperNode: HTMLElement | null;
|
|
5
|
+
triggerElementReference: Element | null;
|
|
6
|
+
/**
|
|
7
|
+
* Latest-ref of the click-outside callback. Passed as a ref (rather than a value)
|
|
8
|
+
* so consumers can pass inline functions without re-attaching listeners on every render.
|
|
9
|
+
* Use `useLatestRef(onClickOutside)` to construct it.
|
|
10
|
+
*/
|
|
11
|
+
onClickOutsideRef: MutableRefObject<((event: MouseEvent | TouchEvent) => void) | undefined>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Attaches mousedown/touchstart listeners on the document that fire when the click target
|
|
15
|
+
* is outside BOTH the floating element and the reference element.
|
|
16
|
+
*
|
|
17
|
+
* Only active when `enabled === true` and both elements exist.
|
|
18
|
+
*/
|
|
19
|
+
export declare const useFloatingClickOutside: ({ isOpen, floatingWrapperNode, triggerElementReference, onClickOutsideRef, }: UseFloatingClickOutsideParams) => void;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface UseFloatingResizeObserverParams {
|
|
2
|
+
isOpen: boolean;
|
|
3
|
+
floatingWrapperNode: HTMLElement | null;
|
|
4
|
+
onResize: () => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Observes the floating element's size and calls `onResize` when its bounding box changes.
|
|
8
|
+
* Used to re-run position computation when the floating content reflows (e.g. async-loaded data).
|
|
9
|
+
*/
|
|
10
|
+
export declare const useFloatingResizeObserver: ({ isOpen, floatingWrapperNode, onResize, }: UseFloatingResizeObserverParams) => void;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Latest-ref pattern: keeps the value in a ref that's updated on every render.
|
|
3
|
+
* Use this when an effect/listener needs to read the latest value of a prop or callback
|
|
4
|
+
* without including it in the effect's dependency array — avoids re-attaching listeners
|
|
5
|
+
* (and infinite loops via deep-equal comparators) when consumers pass inline callbacks.
|
|
6
|
+
*/
|
|
7
|
+
export declare const useLatestRef: <T>(value: T) => import("react").MutableRefObject<T>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks whether `reference` is currently visible at all within `boundaryElement` — e.g. a
|
|
3
|
+
* scrollable ancestor the reference lives inside. Returns true the moment the reference scrolls
|
|
4
|
+
* fully out of that boundary's visible area (intersection ratio 0), even though it may still be
|
|
5
|
+
* within the browser viewport; flips back to false as soon as it's visible again. This is what
|
|
6
|
+
* boundaryElement/clamping cannot solve on its own: a clamped floating element just sticks at
|
|
7
|
+
* the boundary's edge, orphaned, once its reference is no longer visible to anchor it to — the
|
|
8
|
+
* consumer still considers it "open," it should just not be shown while its anchor isn't there.
|
|
9
|
+
*
|
|
10
|
+
* Always false (inert) when boundaryElement is not provided — this is opt-in, paired with
|
|
11
|
+
* useFloatingContext's boundaryElement prop.
|
|
12
|
+
*/
|
|
13
|
+
export declare const useReferenceHiddenByBoundary: ({ reference, boundaryElement, enabled, }: {
|
|
14
|
+
reference: Element | null;
|
|
15
|
+
boundaryElement?: Element | null;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
}) => boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface UseResolvedReferenceParams {
|
|
2
|
+
externalReferenceElement: Element | null | undefined;
|
|
3
|
+
internalReferenceElement: Element | null;
|
|
4
|
+
setInternalReferenceElement: (el: Element | null) => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the active reference element for the floating context.
|
|
8
|
+
*
|
|
9
|
+
* - When `externalReferenceElement` is provided (anything other than `undefined`, including `null`),
|
|
10
|
+
* it is used as the source of truth. `setReference` becomes a no-op so consumers that mistakenly
|
|
11
|
+
* call `refs.setReference()` don't desynchronize the two sources.
|
|
12
|
+
* - Otherwise the internally-managed state is used, populated by the consumer via `refs.setReference()`
|
|
13
|
+
* (typically as a callback ref: `innerRef={refs.setReference}`).
|
|
14
|
+
*/
|
|
15
|
+
export declare const useResolvedReference: ({ externalReferenceElement, internalReferenceElement, setInternalReferenceElement, }: UseResolvedReferenceParams) => {
|
|
16
|
+
triggerElementReference: Element | null;
|
|
17
|
+
setReferenceElement: (el: Element | null) => void;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -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 {
|
|
@@ -7,12 +8,85 @@ export declare namespace DSHookFloatingContextT {
|
|
|
7
8
|
animationDuration: number;
|
|
8
9
|
placement: PopperPlacementsT;
|
|
9
10
|
customOffset: [number, number];
|
|
11
|
+
closeOnEscape: boolean;
|
|
12
|
+
returnFocusToReference: boolean;
|
|
10
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
|
+
};
|
|
11
37
|
interface OptionalProps {
|
|
12
38
|
placementOrderPreference?: PopperPlacementsT[];
|
|
13
|
-
|
|
14
|
-
|
|
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;
|
|
15
50
|
externallyControlledIsOpen?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Pre-resolved reference element. When provided, the hook uses this as the
|
|
53
|
+
* source of truth for positioning and ignores its internal reference state.
|
|
54
|
+
* Eliminates the need for a follow-up `refs.setReference(...)` effect and
|
|
55
|
+
* removes the visibility:hidden race that breaks programmatic focus on open.
|
|
56
|
+
*/
|
|
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;
|
|
78
|
+
/**
|
|
79
|
+
* Called when the user clicks/taps outside both the floating element and
|
|
80
|
+
* the reference element while the floating is open.
|
|
81
|
+
*/
|
|
82
|
+
onClickOutside?: (event: MouseEvent | TouchEvent) => void;
|
|
83
|
+
/**
|
|
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.
|
|
88
|
+
*/
|
|
89
|
+
onEscape?: (event: KeyboardEvent) => void;
|
|
16
90
|
}
|
|
17
91
|
interface Props extends Partial<DefaultProps>, OptionalProps {
|
|
18
92
|
}
|
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
};
|
|
@@ -19,7 +22,7 @@ export declare const useComputedPositionStyles: (config: UseComputedPositionStyl
|
|
|
19
22
|
hasComputedOnce: boolean;
|
|
20
23
|
updateStyles: () => void;
|
|
21
24
|
debouncedUpdateStyles: import("lodash").DebouncedFunc<() => void>;
|
|
22
|
-
mutableUpdateStyles: import("react").MutableRefObject<() => void
|
|
25
|
+
mutableUpdateStyles: import("react").MutableRefObject<import("lodash").DebouncedFunc<() => void>>;
|
|
23
26
|
resetVisibilityOnly: () => void;
|
|
24
27
|
};
|
|
25
28
|
export {};
|
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
import type { DSHookFloatingContextT } from '../react-desc-prop-types.js';
|
|
2
2
|
interface ComputePositionProps {
|
|
3
|
-
|
|
4
|
-
|
|
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.70.0-next.
|
|
3
|
+
"version": "3.70.0-next.71",
|
|
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://
|
|
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.70.0-next.
|
|
40
|
-
"@elliemae/ds-
|
|
41
|
-
"@elliemae/ds-
|
|
42
|
-
"@elliemae/ds-typescript-helpers": "3.70.0-next.
|
|
39
|
+
"@elliemae/ds-hooks-headless-tooltip": "3.70.0-next.71",
|
|
40
|
+
"@elliemae/ds-system": "3.70.0-next.71",
|
|
41
|
+
"@elliemae/ds-props-helpers": "3.70.0-next.71",
|
|
42
|
+
"@elliemae/ds-typescript-helpers": "3.70.0-next.71"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"jest": "^30.0.0",
|
|
46
|
-
"@elliemae/ds-monorepo-devops": "3.70.0-next.
|
|
46
|
+
"@elliemae/ds-monorepo-devops": "3.70.0-next.71"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"lodash-es": "^4.
|
|
49
|
+
"lodash-es": "^4.18.1",
|
|
50
50
|
"react": "^18.3.1",
|
|
51
51
|
"react-dom": "^18.3.1",
|
|
52
|
-
"styled-components": "~5.3.
|
|
52
|
+
"styled-components": "~5.3.11",
|
|
53
53
|
"styled-system": "^5.1.5"
|
|
54
54
|
},
|
|
55
55
|
"publishConfig": {
|
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
"build": "cross-env NODE_ENV=production node ../../../scripts/build/build.mjs",
|
|
66
66
|
"checkDeps": "npx -yes ../../util/ds-codemods check-missing-packages --projectFolderPath=\"./\" --ignorePackagesGlobPattern=\"\" --ignoreFilesGlobPattern=\"**/test-ables/*,**/tests/*\""
|
|
67
67
|
}
|
|
68
|
-
}
|
|
68
|
+
}
|
|
@@ -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 {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|