@atlaskit/popper 8.0.1 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/dist/cjs/index.js +4 -3
  3. package/dist/cjs/internal/anchor-context.js +17 -0
  4. package/dist/cjs/internal/anchor-setter-context.js +17 -0
  5. package/dist/cjs/internal/read-viewport.js +31 -0
  6. package/dist/cjs/internal/rect-point-for-placement.js +93 -0
  7. package/dist/cjs/internal/set-style.js +43 -0
  8. package/dist/cjs/internal/use-anchor-state.js +23 -0
  9. package/dist/cjs/internal/use-fit-viewport-max-size.js +155 -0
  10. package/dist/cjs/internal/use-manager-anchor-setter.js +16 -0
  11. package/dist/cjs/internal/use-manager-anchor.js +16 -0
  12. package/dist/cjs/internal/use-reference-visibility.js +164 -0
  13. package/dist/cjs/manager.js +37 -0
  14. package/dist/cjs/popper-top-layer.js +286 -0
  15. package/dist/cjs/popper.js +48 -12
  16. package/dist/cjs/reference.js +44 -0
  17. package/dist/es2019/index.js +6 -1
  18. package/dist/es2019/internal/anchor-context.js +12 -0
  19. package/dist/es2019/internal/anchor-setter-context.js +9 -0
  20. package/dist/es2019/internal/read-viewport.js +25 -0
  21. package/dist/es2019/internal/rect-point-for-placement.js +91 -0
  22. package/dist/es2019/internal/set-style.js +39 -0
  23. package/dist/es2019/internal/use-anchor-state.js +12 -0
  24. package/dist/es2019/internal/use-fit-viewport-max-size.js +151 -0
  25. package/dist/es2019/internal/use-manager-anchor-setter.js +11 -0
  26. package/dist/es2019/internal/use-manager-anchor.js +11 -0
  27. package/dist/es2019/internal/use-reference-visibility.js +146 -0
  28. package/dist/es2019/manager.js +33 -0
  29. package/dist/es2019/popper-top-layer.js +267 -0
  30. package/dist/es2019/popper.js +37 -0
  31. package/dist/es2019/reference.js +35 -0
  32. package/dist/esm/index.js +6 -1
  33. package/dist/esm/internal/anchor-context.js +12 -0
  34. package/dist/esm/internal/anchor-setter-context.js +11 -0
  35. package/dist/esm/internal/read-viewport.js +25 -0
  36. package/dist/esm/internal/rect-point-for-placement.js +87 -0
  37. package/dist/esm/internal/set-style.js +37 -0
  38. package/dist/esm/internal/use-anchor-state.js +16 -0
  39. package/dist/esm/internal/use-fit-viewport-max-size.js +149 -0
  40. package/dist/esm/internal/use-manager-anchor-setter.js +11 -0
  41. package/dist/esm/internal/use-manager-anchor.js +11 -0
  42. package/dist/esm/internal/use-reference-visibility.js +157 -0
  43. package/dist/esm/manager.js +31 -0
  44. package/dist/esm/popper-top-layer.js +277 -0
  45. package/dist/esm/popper.js +48 -12
  46. package/dist/esm/reference.js +36 -0
  47. package/dist/types/index.d.ts +2 -1
  48. package/dist/types/internal/anchor-context.d.ts +11 -0
  49. package/dist/types/internal/anchor-setter-context.d.ts +11 -0
  50. package/dist/types/internal/read-viewport.d.ts +12 -0
  51. package/dist/types/internal/rect-point-for-placement.d.ts +19 -0
  52. package/dist/types/internal/set-style.d.ts +14 -0
  53. package/dist/types/internal/use-anchor-state.d.ts +10 -0
  54. package/dist/types/internal/use-fit-viewport-max-size.d.ts +53 -0
  55. package/dist/types/internal/use-manager-anchor-setter.d.ts +7 -0
  56. package/dist/types/internal/use-manager-anchor.d.ts +6 -0
  57. package/dist/types/internal/use-reference-visibility.d.ts +32 -0
  58. package/dist/types/manager.d.ts +13 -0
  59. package/dist/types/popper-top-layer.d.ts +20 -0
  60. package/dist/types/reference.d.ts +25 -0
  61. package/package.json +20 -9
  62. package/popper.docs.tsx +1 -0
@@ -0,0 +1,151 @@
1
+ import { useLayoutEffect } from 'react';
2
+ import { setStyle } from './set-style';
3
+
4
+ /**
5
+ * Applies the `shouldFitViewport` size caps directly to the
6
+ * `position-area`-anchored popover host. Pure CSS — the browser owns every
7
+ * value, so there is no measurement, no scroll/resize listeners, and no
8
+ * `--ds-popper-anchor-*` custom properties.
9
+ *
10
+ * **Primary (placement) axis** is capped to `calc(100% - 5px - gap)`. Because
11
+ * the host carries `position-area`, its containing block *is* the position-area
12
+ * cell — the region between the anchor edge and the viewport edge — so `100%`
13
+ * resolves to that distance. The popover is pushed `gap` into the cell by its
14
+ * offset margin (`margin-*`, set by `useAnchorPosition`), so the gap is
15
+ * subtracted as well to keep the legacy `viewportPadding = 5` on the far edge.
16
+ * The cap is also more correct than measuring the requested placement: the cell
17
+ * follows whichever side `position-try-fallbacks` actually flips to, so it
18
+ * tracks the flip automatically.
19
+ *
20
+ * **Cross axis** is capped to `calc(100dvw|dvh - 10px)` (viewport, legacy
21
+ * `2 * viewportPadding`). `display: flex` (plus a `min-*-size: 0` reset on the
22
+ * child) lets an oversized child shrink and reflow to the cap. The host stays
23
+ * `overflow: visible` so it never clips the consumer surface's `box-shadow`;
24
+ * scrolling content that cannot reflow is the consumer surface's own
25
+ * responsibility (it owns `overflow`), matching legacy `react-popper`, which
26
+ * applied the cap to the consumer's own element.
27
+ *
28
+ * `min-inline-size` is reset to `0`. `useWidthFromAnchor({ mode: 'none' })`
29
+ * floors the host at `max-content` so a too-narrow span overflows and drives
30
+ * `position-try-fallbacks` rather than wrapping. That floor is the opposite of
31
+ * fitting: when it exceeds a cap, CSS min/max resolution lets the min win and
32
+ * the host overflows the viewport. In fit mode the caps must win, so the floor
33
+ * is neutralised here; `setStyle` restores the prior inline value on cleanup.
34
+ *
35
+ * Verified in Chromium: `max-block-size: calc(100% - …)` on a `position: fixed`
36
+ * + `position-area` element resolves `100%` to the cell, not the viewport.
37
+ */
38
+
39
+ const CROSS_INLINE_CAP = 'calc(100dvw - 10px)';
40
+ const CROSS_BLOCK_CAP = 'calc(100dvh - 10px)';
41
+
42
+ /**
43
+ * Legacy `viewportPadding` (`preventOverflow` padding) the popover kept from
44
+ * every viewport edge under `react-popper`. The caps above already *reserve*
45
+ * this space (`- 5px` on the primary far edge, `- 10px` = `2 * 5px` on the
46
+ * cross axis), but nothing makes the reservation land on the viewport side:
47
+ * the anchor-side gap is a real margin (`useAnchorPosition`'s `edgeMargin`),
48
+ * the viewport side has none, so a capped popover packs flush against the
49
+ * viewport edge it slid/flipped to. Re-applying the padding as a margin on the
50
+ * three non-anchor sides puts the reserved space back on the viewport side.
51
+ * `position-try` flips mirror margins and slides keep them per-side, so the
52
+ * gap tracks whichever edge the browser settles on.
53
+ */
54
+ const VIEWPORT_PADDING = '5px';
55
+
56
+ /**
57
+ * The popover's three non-anchor margin sides for a given placement axis — the
58
+ * primary *far* side plus both cross sides. The remaining (anchor-facing) side
59
+ * already carries the offset gap from `useAnchorPosition` and is left alone.
60
+ */
61
+ const NON_ANCHOR_MARGIN_SIDES = {
62
+ top: ['margin-block-start', 'margin-inline-start', 'margin-inline-end'],
63
+ bottom: ['margin-block-end', 'margin-inline-start', 'margin-inline-end'],
64
+ left: ['margin-inline-start', 'margin-block-start', 'margin-block-end'],
65
+ right: ['margin-inline-end', 'margin-block-start', 'margin-block-end']
66
+ };
67
+
68
+ /**
69
+ * @param target the `position-area`-anchored popover host
70
+ * @param placementAxis the primary axis derived from the popper placement
71
+ * @param gap the offset distance the popover is pushed from the
72
+ * anchor (CSS length), subtracted from the primary cap
73
+ * @param isEnabled mirrors the consumer's `shouldFitViewport` prop
74
+ * @param isOpen re-applies the caps after the host unmounts/remounts
75
+ * across open cycles (the host is torn down on exit)
76
+ */
77
+ export function useFitViewportMaxSize({
78
+ target,
79
+ placementAxis,
80
+ gap,
81
+ isEnabled,
82
+ isOpen
83
+ }) {
84
+ useLayoutEffect(() => {
85
+ const element = target.current;
86
+ if (!element || !isEnabled) {
87
+ return;
88
+ }
89
+ const isBlockAxis = placementAxis === 'top' || placementAxis === 'bottom';
90
+ const primaryAxisCap = `calc(100% - 5px - ${gap})`;
91
+ const cleanupHost = setStyle({
92
+ element,
93
+ styles: [{
94
+ property: 'display',
95
+ value: 'flex'
96
+ },
97
+ // Neutralise `useWidthFromAnchor`'s `min-inline-size: max-content`
98
+ // floor so the caps below win and content reflows. `setStyle`
99
+ // restores the floor on cleanup.
100
+ {
101
+ property: 'min-inline-size',
102
+ value: '0'
103
+ }, isBlockAxis ? {
104
+ property: 'max-block-size',
105
+ value: primaryAxisCap
106
+ } : {
107
+ property: 'max-inline-size',
108
+ value: primaryAxisCap
109
+ }, isBlockAxis ? {
110
+ property: 'max-inline-size',
111
+ value: CROSS_INLINE_CAP
112
+ } : {
113
+ property: 'max-block-size',
114
+ value: CROSS_BLOCK_CAP
115
+ },
116
+ // Park the reserved viewport padding on the non-anchor sides so a
117
+ // capped popover keeps the legacy `viewportPadding` gap from the
118
+ // viewport edge instead of sitting flush against it.
119
+ ...NON_ANCHOR_MARGIN_SIDES[placementAxis].map(property => ({
120
+ property,
121
+ value: VIEWPORT_PADDING
122
+ }))]
123
+ });
124
+
125
+ // The host deliberately stays `overflow: visible`. An ancestor with
126
+ // `overflow: auto` clips a descendant's `box-shadow`, so capping and
127
+ // scrolling on the host would strip the elevation shadow from the
128
+ // consumer surface. Instead the host only constrains size; scrolling
129
+ // oversized content is the consumer surface's own responsibility,
130
+ // matching the contract `@atlaskit/top-layer`'s `PopoverSurface` follows
131
+ // (shadow and `overflow` on one element). For that surface (the host's
132
+ // flex child) to be clamped to the cap and scroll its own content, it
133
+ // must be allowed to shrink below its intrinsic size, so reset its
134
+ // min-size floor.
135
+ const child = element.firstElementChild;
136
+ const cleanupChild = child instanceof HTMLElement ? setStyle({
137
+ element: child,
138
+ styles: [{
139
+ property: 'min-block-size',
140
+ value: '0'
141
+ }, {
142
+ property: 'min-inline-size',
143
+ value: '0'
144
+ }]
145
+ }) : undefined;
146
+ return () => {
147
+ cleanupHost();
148
+ cleanupChild === null || cleanupChild === void 0 ? void 0 : cleanupChild();
149
+ };
150
+ }, [target, placementAxis, gap, isEnabled, isOpen]);
151
+ }
@@ -0,0 +1,11 @@
1
+ import { useContext } from 'react';
2
+ import { AnchorSetterContext } from './anchor-setter-context';
3
+
4
+ /**
5
+ * Returns the setter that an ancestor `<Manager>` exposes to publish
6
+ * the anchor element from `<Reference>`. Returns a no-op when there is
7
+ * no surrounding `<Manager>`.
8
+ */
9
+ export function useManagerAnchorSetter() {
10
+ return useContext(AnchorSetterContext);
11
+ }
@@ -0,0 +1,11 @@
1
+ import { useContext } from 'react';
2
+ import { AnchorContext } from './anchor-context';
3
+
4
+ /**
5
+ * Returns the current anchor element published by an ancestor
6
+ * `<Reference>` via the shared `<Manager>` provider. Returns `null`
7
+ * when there is no surrounding `<Manager>` / `<Reference>` pair.
8
+ */
9
+ export function useManagerAnchor() {
10
+ return useContext(AnchorContext);
11
+ }
@@ -0,0 +1,146 @@
1
+ import { useCallback, useLayoutEffect, useState } from 'react';
2
+ import { bind, bindAll } from 'bind-event-listener';
3
+ import { readViewport } from './read-viewport';
4
+ const INITIAL = {
5
+ isReferenceHidden: false,
6
+ hasPopperEscaped: false
7
+ };
8
+ function isFullyOutsideViewport(rect, viewport) {
9
+ if (rect.width === 0 && rect.height === 0) {
10
+ return true;
11
+ }
12
+ if (rect.bottom <= 0) {
13
+ return true;
14
+ }
15
+ if (rect.right <= 0) {
16
+ return true;
17
+ }
18
+ if (rect.top >= viewport.height) {
19
+ return true;
20
+ }
21
+ if (rect.left >= viewport.width) {
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+ function isClippingOverflow(value) {
27
+ return value === 'auto' || value === 'scroll' || value === 'hidden' || value === 'clip';
28
+ }
29
+ function isClippedByAncestors(element) {
30
+ const rect = element.getBoundingClientRect();
31
+ let parent = element.parentElement;
32
+ while (parent) {
33
+ // Read the longhand axis properties directly. The `overflow` shorthand
34
+ // is only populated when both axes share the same value, so reading it
35
+ // alone misses ancestors that set only `overflow-x` or `overflow-y`.
36
+ const style = window.getComputedStyle(parent);
37
+ const clipsHorizontally = isClippingOverflow(style.overflowX);
38
+ const clipsVertically = isClippingOverflow(style.overflowY);
39
+ if (clipsHorizontally || clipsVertically) {
40
+ const parentRect = parent.getBoundingClientRect();
41
+ const horizontallyOutside = rect.right <= parentRect.left || rect.left >= parentRect.right;
42
+ const verticallyOutside = rect.bottom <= parentRect.top || rect.top >= parentRect.bottom;
43
+ if (clipsHorizontally && horizontallyOutside) {
44
+ return true;
45
+ }
46
+ if (clipsVertically && verticallyOutside) {
47
+ return true;
48
+ }
49
+ }
50
+ parent = parent.parentElement;
51
+ }
52
+ return false;
53
+ }
54
+
55
+ /**
56
+ * Exists **only** to preserve API parity with the legacy popper.js
57
+ * implementation. The top-layer popper does not natively expose
58
+ * popper.js's `isReferenceHidden` / `hasPopperEscaped` render-prop
59
+ * modifiers, but existing consumers still branch on those values, so
60
+ * this hook reconstructs them from live DOM measurement.
61
+ *
62
+ * Do not reach for this hook in new code. If you find yourself wanting
63
+ * these signals for a new feature, prefer a first-class top-layer or
64
+ * anchor-positioning primitive instead.
65
+ *
66
+ * - `isReferenceHidden`: the anchor's bounding rect is fully outside the
67
+ * visual viewport OR is clipped to zero area by any scrollable
68
+ * ancestor.
69
+ * - `hasPopperEscaped`: the popover surface's rect is fully outside the
70
+ * visual viewport.
71
+ *
72
+ * Measurement runs in a `useLayoutEffect` so the first paint already
73
+ * reflects the correct values, preventing consumers from animating
74
+ * from `opacity: 1` to `opacity: 0` on mount.
75
+ */
76
+ export function useReferenceVisibility({
77
+ anchor,
78
+ popoverRef
79
+ }) {
80
+ const [visibility, setVisibility] = useState(INITIAL);
81
+ const measure = useCallback(() => {
82
+ if (!anchor) {
83
+ setVisibility(previous => previous.isReferenceHidden === false && previous.hasPopperEscaped === false ? previous : INITIAL);
84
+ return;
85
+ }
86
+ const viewport = readViewport();
87
+ const anchorRect = anchor.getBoundingClientRect();
88
+ const isReferenceHidden = isFullyOutsideViewport(anchorRect, viewport) || isClippedByAncestors(anchor);
89
+ const popoverElement = popoverRef.current;
90
+ const hasPopperEscaped = popoverElement ? isFullyOutsideViewport(popoverElement.getBoundingClientRect(), viewport) : false;
91
+ setVisibility(previous => {
92
+ if (previous.isReferenceHidden === isReferenceHidden && previous.hasPopperEscaped === hasPopperEscaped) {
93
+ return previous;
94
+ }
95
+ return {
96
+ isReferenceHidden,
97
+ hasPopperEscaped
98
+ };
99
+ });
100
+ }, [anchor, popoverRef]);
101
+ useLayoutEffect(() => {
102
+ measure();
103
+ if (typeof window === 'undefined') {
104
+ return undefined;
105
+ }
106
+ const cleanups = [];
107
+
108
+ // Resize observer on the anchor so layout changes update visibility.
109
+ if (anchor && typeof ResizeObserver !== 'undefined') {
110
+ const observer = new ResizeObserver(() => measure());
111
+ observer.observe(anchor);
112
+ cleanups.push(() => observer.disconnect());
113
+ }
114
+
115
+ // Scroll + resize: any scroll event in the document can move the
116
+ // anchor in/out of any scrollable ancestor's viewport.
117
+ cleanups.push(bindAll(window, [{
118
+ type: 'scroll',
119
+ listener: measure,
120
+ options: {
121
+ capture: true,
122
+ passive: true
123
+ }
124
+ }, {
125
+ type: 'resize',
126
+ listener: measure,
127
+ options: {
128
+ passive: true
129
+ }
130
+ }]));
131
+ const visual = window.visualViewport;
132
+ if (visual) {
133
+ cleanups.push(bind(visual, {
134
+ type: 'resize',
135
+ listener: measure,
136
+ options: {
137
+ passive: true
138
+ }
139
+ }));
140
+ }
141
+ return () => {
142
+ cleanups.forEach(cleanup => cleanup());
143
+ };
144
+ }, [anchor, measure]);
145
+ return visibility;
146
+ }
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { Manager as ReactPopperManager } from 'react-popper';
3
+ import { AnchorContext } from './internal/anchor-context';
4
+ import { AnchorSetterContext } from './internal/anchor-setter-context';
5
+ import { useAnchorState } from './internal/use-anchor-state';
6
+
7
+ // Derive the wrapper's props from `react-popper`'s own `ManagerProps` so the
8
+ // public surface provably accepts exactly what the underlying `<Manager>`
9
+ // accepts (today just `children`) and stays in sync if that contract changes.
10
+
11
+ /**
12
+ * Wraps `react-popper`'s `<Manager>` so the anchor that `<Reference>`
13
+ * captures is published through `@atlaskit/popper`'s own bridge context.
14
+ * Doing so insulates `<Popper>` consumers from `react-popper`'s dual
15
+ * CJS / ESM builds, which otherwise create two unrelated context
16
+ * instances that prevent the FF-on top-layer adapter from discovering
17
+ * the anchor.
18
+ */
19
+ export function Manager({
20
+ children
21
+ }) {
22
+ // `setAnchor` is the `useState` setter, which React guarantees is stable
23
+ // across renders, so it can be passed straight to the context provider.
24
+ const {
25
+ anchor,
26
+ setAnchor
27
+ } = useAnchorState();
28
+ return /*#__PURE__*/React.createElement(ReactPopperManager, null, /*#__PURE__*/React.createElement(AnchorSetterContext.Provider, {
29
+ value: setAnchor
30
+ }, /*#__PURE__*/React.createElement(AnchorContext.Provider, {
31
+ value: anchor
32
+ }, children)));
33
+ }
@@ -0,0 +1,267 @@
1
+ /* popper-top-layer.tsx generated by @compiled/babel-plugin v0.39.1 */
2
+ import { ax, ix } from "@compiled/react/runtime";
3
+ import React, { useMemo, useRef, useState } from 'react';
4
+ import { getDocument } from '@atlaskit/browser-apis';
5
+ import noop from '@atlaskit/ds-lib/noop';
6
+ import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map';
7
+ import { Popover } from '@atlaskit/top-layer/popover';
8
+ import { useAnchorPosition } from '@atlaskit/top-layer/use-anchor-position';
9
+ import { useAnchorPositionAtPoint } from '@atlaskit/top-layer/use-anchor-position-at-point';
10
+ import { usePopoverId } from '@atlaskit/top-layer/use-popover-id';
11
+ import { useWidthFromAnchor } from '@atlaskit/top-layer/use-width-from-anchor';
12
+ import { rectPointForPlacement } from './internal/rect-point-for-placement';
13
+ import { useFitViewportMaxSize } from './internal/use-fit-viewport-max-size';
14
+ import { useManagerAnchor } from './internal/use-manager-anchor';
15
+ import { useReferenceVisibility } from './internal/use-reference-visibility';
16
+ /**
17
+ * Inert render-prop values. The browser owns positioning under CSS Anchor
18
+ * Positioning, so `style` / `ref` / `arrowProps` / `update` / `forceUpdate`
19
+ * are no-ops that consumers can safely spread. Module scope keeps identities
20
+ * stable for effect dep arrays.
21
+ */
22
+ const noopStyle = {};
23
+ const noopSetRef = noop;
24
+ const noopUpdate = () => Promise.resolve(null);
25
+ const noopForceUpdate = () => ({});
26
+
27
+ /**
28
+ * `react-popper` stamps `data-popper-arrow` on the arrow element at runtime
29
+ * but does not declare it on `arrowProps`. Widen the type so consumers that
30
+ * rely on the attribute (CSS selectors, snapshots, tests) keep working.
31
+ */
32
+
33
+ const noopArrowProps = {
34
+ ref: noopSetRef,
35
+ style: noopStyle,
36
+ 'data-popper-arrow': true
37
+ };
38
+
39
+ /**
40
+ * Returns whether the current page is laid out right-to-left.
41
+ */
42
+ function isPageRtl() {
43
+ const document = getDocument();
44
+ if (!document) {
45
+ return false;
46
+ }
47
+ return document.dir === 'rtl' || document.body.dir === 'rtl' || document.documentElement.dir === 'rtl';
48
+ }
49
+
50
+ /**
51
+ * `@popperjs/core`'s `Placement` union is a strict subset of
52
+ * `TLegacyPlacement` (the placement-map adds `top-center` /
53
+ * `bottom-center` on top of popper's enum), so every value popper hands
54
+ * us is a valid legacy placement. The cast keeps the runtime path free
55
+ * of an extra module-level lookup that bundlers can occasionally fail
56
+ * to wire up (observed as `Cannot read properties of undefined (reading
57
+ * 'includes')` in component-test bundles).
58
+ */
59
+ function toLegacyPlacement(placement) {
60
+ return placement;
61
+ }
62
+
63
+ /**
64
+ * Returns the primary axis (`top` / `bottom` / `left` / `right`) of a popper
65
+ * placement, used to pick which axis `useFitViewportMaxSize` caps to the
66
+ * anchor edge.
67
+ */
68
+ function getPlacementAxis(placement) {
69
+ if (placement.startsWith('top')) {
70
+ return 'top';
71
+ }
72
+ if (placement.startsWith('bottom')) {
73
+ return 'bottom';
74
+ }
75
+ if (placement.startsWith('left')) {
76
+ return 'left';
77
+ }
78
+ if (placement.startsWith('right')) {
79
+ return 'right';
80
+ }
81
+ // `auto*` placements have no fixed axis. Default to `bottom` to match the
82
+ // `auto -> block-end` mapping in `fromLegacyPlacement`.
83
+ return 'bottom';
84
+ }
85
+
86
+ /**
87
+ * Normalises popper's `[along, away]` offset (which may include `null` or
88
+ * `undefined` entries) into the `[along, away]` number tuple
89
+ * `fromLegacyPlacement` expects.
90
+ */
91
+ function popperToTopLayerOffset(offset) {
92
+ if (!offset) {
93
+ return undefined;
94
+ }
95
+ const [along, away] = offset;
96
+ if (along == null && away == null) {
97
+ return undefined;
98
+ }
99
+ return [along !== null && along !== void 0 ? along : 0, away !== null && away !== void 0 ? away : 0];
100
+ }
101
+
102
+ /**
103
+ * FF-on implementation of `@atlaskit/popper`'s `<Popper>` primitive.
104
+ *
105
+ * Renders the consumer's render-prop output into a `<Popover>` from
106
+ * `@atlaskit/top-layer`, which lifts the element into the browser top
107
+ * layer and positions it via CSS Anchor Positioning. The render-prop
108
+ * contract (`PopperChildrenProps`) is preserved at the type level;
109
+ * `style` and `arrowProps.style` are inert at runtime because the
110
+ * browser owns positioning.
111
+ *
112
+ * Gated behind the `platform-dst-top-layer` feature flag from
113
+ * `popper.tsx`.
114
+ */
115
+ export function PopperTopLayer({
116
+ children,
117
+ offset,
118
+ placement = 'bottom-start',
119
+ referenceElement,
120
+ shouldFitViewport = false
121
+ }) {
122
+ var _ref;
123
+ // `modifiers` and `strategy` are accepted for source compatibility but have
124
+ // no runtime effect; CSS Anchor Positioning + top-layer rendering replaces
125
+ // them. See `top-layer/notes/migrations/popper-migration.md`.
126
+
127
+ // Anchor resolution: `referenceElement` prop, then `<Manager>` context.
128
+ const managerAnchor = useManagerAnchor();
129
+ const effectiveReference = (_ref = referenceElement !== null && referenceElement !== void 0 ? referenceElement : managerAnchor) !== null && _ref !== void 0 ? _ref : undefined;
130
+
131
+ // Real DOM nodes go to `useAnchorPosition`; popper `VirtualElement`s are
132
+ // bridged through `useAnchorPositionAtPoint`, which owns its own synthetic
133
+ // anchor.
134
+ const htmlAnchor = effectiveReference instanceof HTMLElement ? effectiveReference : null;
135
+ const virtualReference = effectiveReference != null && !(effectiveReference instanceof HTMLElement) ? effectiveReference : null;
136
+ const htmlAnchorRef = useRef(htmlAnchor);
137
+ htmlAnchorRef.current = htmlAnchor;
138
+ const popoverRef = useRef(null);
139
+ const popoverId = usePopoverId();
140
+
141
+ // Track the resolved DOM anchor in state so visibility / max-size hooks
142
+ // re-run when its identity changes. Virtual anchors do not feed these
143
+ // hooks because their probe is outside the consumer's DOM.
144
+ //
145
+ // Adjust state during render by comparing against the state itself: the
146
+ // conditional guard means `setResolvedAnchor` is skipped once they match,
147
+ // so it converges in one extra render. This is the React-documented pattern
148
+ // and is side-effect-free — no ref mutation during render.
149
+ const [resolvedAnchor, setResolvedAnchor] = useState(htmlAnchor);
150
+ if (resolvedAnchor !== htmlAnchor) {
151
+ setResolvedAnchor(htmlAnchor);
152
+ }
153
+ const topLayerPlacement = useMemo(() => {
154
+ return fromLegacyPlacement({
155
+ legacy: toLegacyPlacement(placement),
156
+ offset: popperToTopLayerOffset(offset)
157
+ });
158
+ }, [placement, offset]);
159
+ const isOpen = effectiveReference != null;
160
+
161
+ // HTML-element path. No-op when the reference is virtual or absent.
162
+ useAnchorPosition({
163
+ anchorRef: htmlAnchorRef,
164
+ popoverRef,
165
+ placement: topLayerPlacement,
166
+ isEnabled: htmlAnchor != null,
167
+ isOpen
168
+ });
169
+
170
+ // Virtual-element path. `useAnchorPositionAtPoint` owns a synthetic
171
+ // anchor in `document.body` and latches `getPoint` once per
172
+ // `isEnabled` activation. Reading the latest `virtualReference` and
173
+ // `topLayerPlacement` via refs ensures the latched closure always
174
+ // sees the current values rather than the ones captured at first
175
+ // activation, which would otherwise go stale if either prop changes
176
+ // while the popper stays open.
177
+ const virtualReferenceRef = useRef(virtualReference);
178
+ virtualReferenceRef.current = virtualReference;
179
+ const topLayerPlacementRef = useRef(topLayerPlacement);
180
+ topLayerPlacementRef.current = topLayerPlacement;
181
+ const isVirtualEnabled = virtualReference != null;
182
+ useAnchorPositionAtPoint({
183
+ popoverRef,
184
+ placement: topLayerPlacement,
185
+ isEnabled: isVirtualEnabled,
186
+ isOpen,
187
+ getPoint: () => {
188
+ const current = virtualReferenceRef.current;
189
+ if (!current) {
190
+ return null;
191
+ }
192
+ return rectPointForPlacement({
193
+ rect: current.getBoundingClientRect(),
194
+ placement: topLayerPlacementRef.current,
195
+ isRtl: isPageRtl()
196
+ });
197
+ }
198
+ });
199
+ const {
200
+ isReferenceHidden,
201
+ hasPopperEscaped
202
+ } = useReferenceVisibility({
203
+ anchor: resolvedAnchor,
204
+ popoverRef
205
+ });
206
+
207
+ // Restore legacy `react-popper`'s natural-width behaviour. Under CSS
208
+ // Anchor Positioning the `position-area` grid cell becomes the popover
209
+ // host's containing block, so an auto-width host shrinks to that cell.
210
+ // When the anchor sits near a viewport edge the cell is narrow, so the
211
+ // content wraps far more than it did under `react-popper` (which kept the
212
+ // content's natural width and shifted/flipped to stay on screen).
213
+ // `min-inline-size: max-content` (mode `'none'`) floors the host at its
214
+ // content's intrinsic width, so a too-narrow span overflows the viewport
215
+ // (driving `position-try-fallbacks`) instead of wrapping. This is safe
216
+ // alongside `shouldFitViewport`: the fit caps clamp the host on both axes,
217
+ // so the host never exceeds the viewport / anchor-edge cap.
218
+ useWidthFromAnchor({
219
+ mode: 'none',
220
+ popoverRef,
221
+ anchorRef: htmlAnchorRef,
222
+ isOpen
223
+ });
224
+
225
+ // `shouldFitViewport` caps are applied directly to the `position-area`
226
+ // host, whose containing block is the cell between the anchor edge and the
227
+ // viewport edge — so a pure-CSS `calc(100% - 5px - gap)` reproduces the
228
+ // legacy per-placement anchor-edge cap with no measurement. The gap mirrors
229
+ // `getPlacement`: an omitted offset resolves to `space.100`, otherwise the
230
+ // consumer's `away` value.
231
+ const resolvedOffset = popperToTopLayerOffset(offset);
232
+ const fitGap = resolvedOffset ? `${resolvedOffset[1]}px` : "var(--ds-space-100, 8px)";
233
+ const placementAxis = getPlacementAxis(placement);
234
+ useFitViewportMaxSize({
235
+ target: popoverRef,
236
+ placementAxis,
237
+ gap: fitGap,
238
+ isEnabled: shouldFitViewport,
239
+ isOpen
240
+ });
241
+ const renderChildren = children;
242
+ if (typeof renderChildren !== 'function') {
243
+ return null;
244
+ }
245
+ const renderPropArg = {
246
+ ref: noopSetRef,
247
+ style: noopStyle,
248
+ placement,
249
+ isReferenceHidden,
250
+ hasPopperEscaped,
251
+ update: noopUpdate,
252
+ forceUpdate: noopForceUpdate,
253
+ arrowProps: noopArrowProps
254
+ };
255
+ const content = renderChildren(renderPropArg);
256
+
257
+ // The `shouldFitViewport` size caps live on the `<Popover>` host itself
258
+ // (applied by `useFitViewportMaxSize` above), so the consumer's content is
259
+ // rendered directly with no intermediate wrapper.
260
+ return /*#__PURE__*/React.createElement(Popover, {
261
+ ref: popoverRef,
262
+ id: popoverId,
263
+ isOpen: isOpen,
264
+ mode: "manual",
265
+ animate: false
266
+ }, content);
267
+ }
@@ -1,6 +1,8 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { Popper as ReactPopper } from 'react-popper';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { getMaxSizeModifiers } from './max-size';
5
+ import { PopperTopLayer } from './popper-top-layer';
4
6
  export { placements } from '@popperjs/core';
5
7
  // Export types from PopperJS / React Popper
6
8
 
@@ -19,6 +21,41 @@ function defaultChildrenFn() {
19
21
  }
20
22
  const defaultOffset = [0, 8];
21
23
  export function Popper({
24
+ children,
25
+ offset,
26
+ placement,
27
+ referenceElement,
28
+ modifiers,
29
+ strategy,
30
+ shouldFitViewport
31
+ }) {
32
+ // The FF check sits at the very top of the public Popper so the
33
+ // rest of the function (which has its own hooks) does not violate
34
+ // the rules of hooks. Each branch is its own component with its
35
+ // own complete hook order. Props are forwarded explicitly to
36
+ // satisfy `no-unsafe-spread-props`.
37
+ if (fg('platform-dst-top-layer')) {
38
+ return /*#__PURE__*/React.createElement(PopperTopLayer, {
39
+ children: children,
40
+ offset: offset,
41
+ placement: placement,
42
+ referenceElement: referenceElement,
43
+ modifiers: modifiers,
44
+ strategy: strategy,
45
+ shouldFitViewport: shouldFitViewport
46
+ });
47
+ }
48
+ return /*#__PURE__*/React.createElement(LegacyPopper, {
49
+ children: children,
50
+ offset: offset,
51
+ placement: placement,
52
+ referenceElement: referenceElement,
53
+ modifiers: modifiers,
54
+ strategy: strategy,
55
+ shouldFitViewport: shouldFitViewport
56
+ });
57
+ }
58
+ function LegacyPopper({
22
59
  children = defaultChildrenFn,
23
60
  offset = defaultOffset,
24
61
  placement = 'bottom-start',