@atlaskit/popup 6.3.4 → 6.3.5

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 (27) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/__tests__/playwright/fit-viewport.spec.tsx +225 -0
  3. package/dist/cjs/compositional/popup-content-top-layer.js +29 -18
  4. package/dist/cjs/compositional/popup.js +1 -1
  5. package/dist/cjs/compositional/trigger-ref-object-context.js +2 -2
  6. package/dist/cjs/internal/get-popup-axis-sizes.js +42 -0
  7. package/dist/cjs/popup-top-layer.js +27 -19
  8. package/dist/es2019/compositional/popup-content-top-layer.js +26 -17
  9. package/dist/es2019/compositional/popup.js +1 -1
  10. package/dist/es2019/compositional/trigger-ref-object-context.js +2 -2
  11. package/dist/es2019/internal/get-popup-axis-sizes.js +37 -0
  12. package/dist/es2019/popup-top-layer.js +31 -20
  13. package/dist/esm/compositional/popup-content-top-layer.js +29 -17
  14. package/dist/esm/compositional/popup.js +1 -1
  15. package/dist/esm/compositional/trigger-ref-object-context.js +2 -2
  16. package/dist/esm/internal/get-popup-axis-sizes.js +36 -0
  17. package/dist/esm/popup-top-layer.js +27 -19
  18. package/dist/types/compositional/trigger-ref-object-context.d.ts +2 -2
  19. package/dist/types/internal/get-popup-axis-sizes.d.ts +25 -0
  20. package/dist/types/types.d.ts +30 -0
  21. package/package.json +4 -3
  22. package/dist/cjs/compositional/popup-content-top-layer.compiled.css +0 -2
  23. package/dist/cjs/popup-top-layer.compiled.css +0 -2
  24. package/dist/es2019/compositional/popup-content-top-layer.compiled.css +0 -2
  25. package/dist/es2019/popup-top-layer.compiled.css +0 -2
  26. package/dist/esm/compositional/popup-content-top-layer.compiled.css +0 -2
  27. package/dist/esm/popup-top-layer.compiled.css +0 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @atlaskit/popup
2
2
 
3
+ ## 6.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [`a7ec100f7d560`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a7ec100f7d560) -
8
+ Fixed `shouldFitViewport` doing nothing on the top-layer code path, and forwarded it to a custom
9
+ `popupComponent` as on the legacy path. `xcss` on `<Popup>` is now applied on the top-layer path
10
+ without a custom `popupComponent`; it was silently dropped there. Behind the
11
+ `platform-dst-top-layer` gate; legacy (flag-off) behaviour is unchanged.
12
+ - Updated dependencies
13
+
3
14
  ## 6.3.4
4
15
 
5
16
  ### Patch Changes
@@ -0,0 +1,225 @@
1
+ /* eslint-disable testing-library/prefer-screen-queries */
2
+ import invariant from 'tiny-invariant';
3
+
4
+ import { expect, type Page, test } from '@af/integration-testing';
5
+
6
+ /**
7
+ * `Popup`'s `shouldFitViewport`, asserted as geometry on both code paths. On the
8
+ * top-layer path the prop did nothing: its whole implementation was
9
+ * `overflow: auto` on a wrapper inside a `PopoverSurface` that already set it,
10
+ * and `overflow: auto` with no cap is inert.
11
+ *
12
+ * The assertions are path-agnostic, so they double as the legacy/top-layer parity
13
+ * check: a scroll container exists, is within the viewport, actually scrolls, and
14
+ * can bring the control at the bottom of the content on screen. The first three
15
+ * are what make this more than a reachability test, which a popover clamped to a
16
+ * 53px letterbox also passes.
17
+ *
18
+ * The flag is toggled with a bare `featureFlag` key and omitted entirely for
19
+ * flag-off, which is the only form the examples page understands.
20
+ */
21
+
22
+ const TOP_LAYER_FLAG = 'platform-dst-top-layer';
23
+
24
+ /**
25
+ * Sub-pixel layout differences.
26
+ */
27
+ const TOLERANCE = 1;
28
+
29
+ const VIEWPORT = { width: 400, height: 400 };
30
+
31
+ /**
32
+ * The nearest ancestor of the content whose `overflow` is scrollable. Found by
33
+ * walking up rather than by `testId`, because the two code paths put it on
34
+ * different elements and the point is that SOMETHING scrolls.
35
+ */
36
+ async function readScrollContainer({ page }: { page: Page }): Promise<{
37
+ top: number;
38
+ bottom: number;
39
+ clientHeight: number;
40
+ scrollHeight: number;
41
+ viewportHeight: number;
42
+ } | null> {
43
+ return page.evaluate(() => {
44
+ const content = document.querySelector('[data-testid="fit-popup-body"]');
45
+ let element = content?.parentElement ?? null;
46
+
47
+ while (element) {
48
+ const overflowY = window.getComputedStyle(element).overflowY;
49
+ if (overflowY === 'auto' || overflowY === 'scroll') {
50
+ break;
51
+ }
52
+ element = element.parentElement;
53
+ }
54
+
55
+ if (!element) {
56
+ return null;
57
+ }
58
+
59
+ const rect = element.getBoundingClientRect();
60
+ return {
61
+ top: rect.top,
62
+ bottom: rect.bottom,
63
+ clientHeight: element.clientHeight,
64
+ scrollHeight: element.scrollHeight,
65
+ viewportHeight: window.innerHeight,
66
+ };
67
+ });
68
+ }
69
+
70
+ async function openFixture({
71
+ page,
72
+ params,
73
+ isTopLayer,
74
+ }: {
75
+ page: Page;
76
+ params: { [key: string]: string };
77
+ isTopLayer: boolean;
78
+ }): Promise<void> {
79
+ await page.setViewportSize(VIEWPORT);
80
+ await page.visitExample<typeof import('../../examples/98-testing-fit-viewport.tsx')>(
81
+ 'design-system',
82
+ 'popup',
83
+ 'testing-fit-viewport',
84
+ isTopLayer ? { ...params, featureFlag: TOP_LAYER_FLAG } : params,
85
+ );
86
+
87
+ await page.getByTestId('fit-popup-trigger').click();
88
+ await expect(page.getByTestId('fit-popup-body')).toBeVisible();
89
+ }
90
+
91
+ /**
92
+ * The trigger sits 300px down a 400px viewport, so there are only ~80px below it
93
+ * and the 400px-tall content cannot possibly fit on either side unclamped.
94
+ */
95
+ const CRAMPED = { triggerBlockStart: '300', contentBlockSize: '400' };
96
+
97
+ [
98
+ { name: 'top-layer', isTopLayer: true },
99
+ { name: 'legacy', isTopLayer: false },
100
+ ].forEach(({ name, isTopLayer }) => {
101
+ test(`${name}: a fitting popup is capped, on screen, and scrolls`, async ({ page }) => {
102
+ await openFixture({ page, params: { ...CRAMPED, fit: 'true' }, isTopLayer });
103
+
104
+ await expect(async () => {
105
+ const scroller = await readScrollContainer({ page });
106
+ invariant(scroller, 'the popup should have a scroll container');
107
+
108
+ expect(scroller.top).toBeGreaterThanOrEqual(-TOLERANCE);
109
+ expect(scroller.bottom).toBeLessThanOrEqual(scroller.viewportHeight + TOLERANCE);
110
+ expect(scroller.scrollHeight).toBeGreaterThan(scroller.clientHeight);
111
+ }).toPass({ timeout: 10_000, intervals: [50, 100, 200, 500] });
112
+
113
+ // The reported symptom: the control at the bottom of the content has to be
114
+ // reachable. Scrolling the popup's own container must bring it on screen.
115
+ await page.getByTestId('fit-popup-footer-action').scrollIntoViewIfNeeded();
116
+ await expect(page.getByTestId('fit-popup-footer-action')).toBeInViewport();
117
+ });
118
+ });
119
+
120
+ test('top-layer: the cap reaches a custom popupComponent', async ({ page }) => {
121
+ // The consumer's element is the direct child of the popover host, so the cap
122
+ // has to reach it through the flex context. The container also keys its own
123
+ // `overflow: auto` off `shouldFitViewport`, which the top-layer path did not
124
+ // forward, so this covers both halves.
125
+ await openFixture({
126
+ page,
127
+ params: { ...CRAMPED, fit: 'true', container: 'custom' },
128
+ isTopLayer: true,
129
+ });
130
+
131
+ await expect(async () => {
132
+ const container = await page.evaluate(() => {
133
+ const element = document.querySelector('[data-testid="fit-popup-container"]');
134
+ if (!element) {
135
+ return null;
136
+ }
137
+ const rect = element.getBoundingClientRect();
138
+ return {
139
+ top: rect.top,
140
+ bottom: rect.bottom,
141
+ clientHeight: element.clientHeight,
142
+ scrollHeight: element.scrollHeight,
143
+ overflowY: window.getComputedStyle(element).overflowY,
144
+ viewportHeight: window.innerHeight,
145
+ };
146
+ });
147
+
148
+ invariant(container, 'the custom popup container should be rendered');
149
+
150
+ // The prop reached the container.
151
+ expect(container.overflowY).toBe('auto');
152
+ // The host cap reached the container.
153
+ expect(container.bottom).toBeLessThanOrEqual(container.viewportHeight + TOLERANCE);
154
+ expect(container.top).toBeGreaterThanOrEqual(-TOLERANCE);
155
+ expect(container.scrollHeight).toBeGreaterThan(container.clientHeight);
156
+ }).toPass({ timeout: 10_000, intervals: [50, 100, 200, 500] });
157
+ });
158
+
159
+ /**
160
+ * `xcss` on `<Popup>` has to reach the DOM on both paths. Legacy applies it to the
161
+ * container element; the top-layer path, with no custom `popupComponent`, applies it
162
+ * to a wrapper inside `PopoverSurface`. Either way it is the content's PARENT, so
163
+ * the assertion is path-agnostic. The content is 160px wide by its own styles, so
164
+ * only the consumer's `width: 280px` can make that parent 280px wide. Before the
165
+ * wrapper the top-layer path dropped the prop, and four call sites lost their width.
166
+ */
167
+ [
168
+ { name: 'top-layer', isTopLayer: true },
169
+ { name: 'legacy', isTopLayer: false },
170
+ ].forEach(({ name, isTopLayer }) => {
171
+ test(`${name}: an xcss width on <Popup> reaches the rendered DOM`, async ({ page }) => {
172
+ await openFixture({
173
+ page,
174
+ params: { triggerBlockStart: '100', contentBlockSize: '100', width: 'fixed' },
175
+ isTopLayer,
176
+ });
177
+
178
+ await expect(async () => {
179
+ const parentWidth = await page.evaluate(() => {
180
+ const content = document.querySelector('[data-testid="fit-popup-body"]');
181
+ return content?.parentElement?.getBoundingClientRect().width ?? null;
182
+ });
183
+ invariant(parentWidth !== null, 'the popup content should have a parent element');
184
+
185
+ expect(Math.abs(parentWidth - 280)).toBeLessThanOrEqual(TOLERANCE);
186
+ }).toPass({ timeout: 10_000, intervals: [50, 100, 200, 500] });
187
+ });
188
+ });
189
+
190
+ /**
191
+ * The control: fitting stays OPT-IN, since fitting by default would be a
192
+ * placement change for ~53 call sites. Two ways to be opted out, failing to two
193
+ * different regressions: an explicit `false` fails if the adapter stops reading
194
+ * the prop, and the prop left OFF additionally fails if the
195
+ * `shouldFitViewport = false` default in `popup-top-layer.tsx` is flipped.
196
+ *
197
+ * Measured on the SCROLL CONTAINER, because `fit-popup-body` carries a hard 400px
198
+ * `blockSize` and `getBoundingClientRect` ignores an ancestor's `overflow`. The
199
+ * container is capped and scrolls in BOTH states, the viewport backstop being
200
+ * unconditional; what fitting adds is the cell cap and the flip floor.
201
+ */
202
+ const OPT_OUT_CASES = [
203
+ { name: 'an explicit shouldFitViewport={false}', fit: 'false' },
204
+ { name: 'the prop left off, so the adapter default decides', fit: undefined },
205
+ ] as const;
206
+
207
+ OPT_OUT_CASES.forEach(({ name, fit }) => {
208
+ test(`top-layer: not fitted to the space beside the trigger, with ${name}`, async ({ page }) => {
209
+ await openFixture({
210
+ page,
211
+ params: fit === undefined ? { ...CRAMPED } : { ...CRAMPED, fit },
212
+ isTopLayer: true,
213
+ });
214
+
215
+ await expect(async () => {
216
+ const scroller = await readScrollContainer({ page });
217
+ invariant(scroller, 'the popup should have a scroll container');
218
+
219
+ // It starts on screen, next to the trigger, and runs off the bottom: a
220
+ // popup left where it was anchored rather than one moved or clamped.
221
+ expect(scroller.top).toBeLessThan(scroller.viewportHeight);
222
+ expect(scroller.bottom).toBeGreaterThan(scroller.viewportHeight + TOLERANCE);
223
+ }).toPass({ timeout: 10_000, intervals: [50, 100, 200, 500] });
224
+ });
225
+ });
@@ -7,23 +7,23 @@ Object.defineProperty(exports, "__esModule", {
7
7
  value: true
8
8
  });
9
9
  exports.PopupContentTopLayer = PopupContentTopLayer;
10
- require("./popup-content-top-layer.compiled.css");
11
10
  var _react = _interopRequireWildcard(require("react"));
12
11
  var React = _react;
13
- var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
14
12
  var _runtime = require("@compiled/react/runtime");
13
+ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
14
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
15
15
  var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
16
16
  var _index = require("@atlaskit/top-layer/placement-map/index");
17
17
  var _createCloseEvent = require("@atlaskit/top-layer/popover/create-close-event");
18
18
  var _popover = require("@atlaskit/top-layer/popover/popover");
19
19
  var _popoverSurface = require("@atlaskit/top-layer/popover-surface");
20
- var _useAnchorPosition = require("@atlaskit/top-layer/use-anchor-position");
21
- var _useWidthFromAnchor = require("@atlaskit/top-layer/use-width-from-anchor");
20
+ var _useAnchoredPopover = require("@atlaskit/top-layer/use-anchored-popover");
21
+ var _getPopupAxisSizes = require("../internal/get-popup-axis-sizes");
22
22
  var _topLayerBridge = require("../internal/top-layer-bridge");
23
23
  var _triggerRefObjectContext = require("./trigger-ref-object-context");
24
24
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
25
- var overflowAutoStyles = null;
26
-
25
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
26
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
27
27
  // Top-layer positioning is handled by CSS Anchor Positioning, not inline styles.
28
28
  var EMPTY_STYLE = {};
29
29
 
@@ -47,8 +47,10 @@ function PopupContentTopLayer(_ref) {
47
47
  PopupContainer = _ref.popupComponent,
48
48
  _ref$autoFocus = _ref.autoFocus,
49
49
  autoFocus = _ref$autoFocus === void 0 ? true : _ref$autoFocus,
50
- shouldFitContainer = _ref.shouldFitContainer,
51
- shouldFitViewport = _ref.shouldFitViewport,
50
+ _ref$shouldFitContain = _ref.shouldFitContainer,
51
+ shouldFitContainer = _ref$shouldFitContain === void 0 ? false : _ref$shouldFitContain,
52
+ _ref$shouldFitViewpor = _ref.shouldFitViewport,
53
+ shouldFitViewport = _ref$shouldFitViewpor === void 0 ? false : _ref$shouldFitViewpor,
52
54
  role = _ref.role,
53
55
  label = _ref.label,
54
56
  titleId = _ref.titleId,
@@ -168,18 +170,15 @@ function PopupContentTopLayer(_ref) {
168
170
 
169
171
  // `isOpen` is included so the anchor positioning effect re-runs when
170
172
  // the Popover host element is unmounted/remounted across open cycles.
171
- (0, _useAnchorPosition.useAnchorPosition)({
173
+ (0, _useAnchoredPopover.useAnchoredPopover)(_objectSpread({
172
174
  anchorRef: anchorRef,
173
175
  popoverRef: popoverRef,
174
176
  placement: topLayerPlacement,
175
177
  isOpen: isOpen
176
- });
177
- (0, _useWidthFromAnchor.useWidthFromAnchor)({
178
- mode: shouldFitContainer ? 'match-anchor' : 'none',
179
- popoverRef: popoverRef,
180
- anchorRef: anchorRef,
181
- isOpen: isOpen
182
- });
178
+ }, (0, _getPopupAxisSizes.getPopupAxisSizes)({
179
+ shouldFitContainer: shouldFitContainer,
180
+ shouldFitViewport: shouldFitViewport
181
+ })));
183
182
 
184
183
  // Narrow to ForwardRefExoticComponent so JSX accepts the ref prop.
185
184
  // All popupComponent implementations use forwardRef per the PopupComponentProps contract.
@@ -199,8 +198,20 @@ function PopupContentTopLayer(_ref) {
199
198
  "data-testid": testId,
200
199
  tabIndex: autoFocus ? -1 : undefined,
201
200
  xcss: xcss
202
- }, children(contentProps)) : /*#__PURE__*/React.createElement(_popoverSurface.PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
201
+ // Forwarded because several in-tree containers key their own
202
+ // `overflow: auto` branch off it.
203
+ ,
204
+ shouldFitViewport: shouldFitViewport
205
+ }, children(contentProps)) :
206
+ /*#__PURE__*/
207
+ // `PopoverSurface` owns `overflow: auto` and the `box-shadow` on the
208
+ // same element, so it is the scroll container. The wrapper exists only
209
+ // to carry the consumer's `xcss`: `PopoverSurface` exposes no
210
+ // `className` or `xcss` by design, so `xcss` lands on a CHILD of the
211
+ // surface. `width` works there; surface-level declarations apply one
212
+ // level in. Same shape as `../popup-top-layer.tsx`.
213
+ React.createElement(_popoverSurface.PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
203
214
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
204
- className: (0, _runtime.ax)([shouldFitViewport && "_1reo1wug _18m91wug", (0, _runtime.ax)([xcss])])
215
+ className: xcss
205
216
  }, children(contentProps))));
206
217
  }
@@ -52,7 +52,7 @@ var Popup = exports.Popup = function Popup(_ref) {
52
52
  // When the top-layer flag is on, we maintain a stable RefObject for the
53
53
  // trigger element, provided through TriggerRefObjectContext. Unlike the
54
54
  // state-based triggerRef (which causes a re-render when the trigger mounts),
55
- // this ref has a stable identity so useAnchorPosition's useLayoutEffect dep
55
+ // this ref has a stable identity so useAnchoredPopover's useLayoutEffect dep
56
56
  // array never changes — the effect runs once on mount when the trigger element
57
57
  // is already populated. PopupTrigger sets this ref directly instead of calling
58
58
  // the state setter, avoiding an unnecessary re-render.
@@ -12,8 +12,8 @@ var _react = require("react");
12
12
  * for the top-layer path. Unlike `TriggerRefContext` (which provides
13
13
  * `HTMLElement | null` as React state and causes a re-render when the trigger
14
14
  * mounts), this context provides a ref object whose identity is stable across
15
- * renders — allowing `useAnchorPosition` to receive a stable `anchorRef`
16
- * without needing to create a new ref wrapper on every `triggerRef` state change.
15
+ * renders — allowing `useAnchoredPopover` to receive a stable anchor ref without
16
+ * needing to create a new ref wrapper on every `triggerRef` state change.
17
17
  */
18
18
  var TriggerRefObjectContext = exports.TriggerRefObjectContext = /*#__PURE__*/(0, _react.createContext)({
19
19
  current: null
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getPopupAxisSizes = getPopupAxisSizes;
7
+ /**
8
+ * Maps the legacy `shouldFitContainer` / `shouldFitViewport` pair onto
9
+ * `useAnchoredPopover`'s per-axis sizes. Shared by the standard and the
10
+ * compositional adapter so the two cannot drift.
11
+ *
12
+ * Both props can be set together while `inlineSize` takes one value, so
13
+ * `shouldFitContainer` wins the inline axis, being the more specific request.
14
+ * Fitting then applies to the block axis only, and still turns the
15
+ * placement-axis floor on, so a cramped popup flips rather than letterboxing.
16
+ *
17
+ * Known cost on a `left-*` / `right-*` placement: the anchor floor is unclamped
18
+ * so it can drive the flip, and a min beats a max, so a trigger wider than the
19
+ * viewport is floored past the viewport cap. Not new, and not reconciled - see
20
+ * `@atlaskit/top-layer`'s `notes/decisions/width-from-anchor-floors.md`.
21
+ */
22
+ function getPopupAxisSizes(_ref) {
23
+ var shouldFitContainer = _ref.shouldFitContainer,
24
+ shouldFitViewport = _ref.shouldFitViewport;
25
+ var blockSize = shouldFitViewport ? 'max-available' : 'content';
26
+ if (shouldFitContainer) {
27
+ return {
28
+ inlineSize: 'match-anchor',
29
+ blockSize: blockSize
30
+ };
31
+ }
32
+ if (shouldFitViewport) {
33
+ return {
34
+ inlineSize: 'max-available',
35
+ blockSize: blockSize
36
+ };
37
+ }
38
+ return {
39
+ inlineSize: 'content',
40
+ blockSize: blockSize
41
+ };
42
+ }
@@ -7,27 +7,24 @@ Object.defineProperty(exports, "__esModule", {
7
7
  value: true
8
8
  });
9
9
  exports.PopupTopLayer = void 0;
10
- require("./popup-top-layer.compiled.css");
11
10
  var _react = _interopRequireWildcard(require("react"));
12
11
  var React = _react;
13
12
  var _runtime = require("@compiled/react/runtime");
14
13
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
14
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
15
15
  var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
16
16
  var _getAriaForTrigger = require("@atlaskit/top-layer/get-aria-for-trigger");
17
17
  var _index = require("@atlaskit/top-layer/placement-map/index");
18
18
  var _createCloseEvent = require("@atlaskit/top-layer/popover/create-close-event");
19
19
  var _popover = require("@atlaskit/top-layer/popover/popover");
20
20
  var _popoverSurface = require("@atlaskit/top-layer/popover-surface");
21
- var _useAnchorPosition = require("@atlaskit/top-layer/use-anchor-position");
21
+ var _useAnchoredPopover = require("@atlaskit/top-layer/use-anchored-popover");
22
22
  var _usePopoverId = require("@atlaskit/top-layer/use-popover-id");
23
- var _useWidthFromAnchor = require("@atlaskit/top-layer/use-width-from-anchor");
23
+ var _getPopupAxisSizes = require("./internal/get-popup-axis-sizes");
24
24
  var _topLayerBridge = require("./internal/top-layer-bridge");
25
25
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
26
- var contentOverflowStyles = {
27
- fitViewport: "_1reo1wug _18m91wug",
28
- default: ""
29
- };
30
-
26
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
27
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
31
28
  // Top-layer positioning is handled by CSS Anchor Positioning, not inline styles.
32
29
  var EMPTY_STYLE = {};
33
30
 
@@ -74,7 +71,8 @@ var PopupTopLayer = exports.PopupTopLayer = /*#__PURE__*/(0, _react.memo)(functi
74
71
  _rootBoundary = _ref.rootBoundary,
75
72
  _shouldUseCaptureOnOutsideClick = _ref.shouldUseCaptureOnOutsideClick,
76
73
  _shouldDisableFocusLock = _ref.shouldDisableFocusLock,
77
- shouldFitViewport = _ref.shouldFitViewport,
74
+ _ref$shouldFitViewpor = _ref.shouldFitViewport,
75
+ shouldFitViewport = _ref$shouldFitViewpor === void 0 ? false : _ref$shouldFitViewpor,
78
76
  _appearance = _ref.appearance;
79
77
  var triggerRef = (0, _react.useRef)(null);
80
78
  var popoverRef = (0, _react.useRef)(null);
@@ -149,18 +147,15 @@ var PopupTopLayer = exports.PopupTopLayer = /*#__PURE__*/(0, _react.memo)(functi
149
147
  offset: offsetProp
150
148
  });
151
149
  }, [placement, offsetProp]);
152
- (0, _useAnchorPosition.useAnchorPosition)({
150
+ (0, _useAnchoredPopover.useAnchoredPopover)(_objectSpread({
153
151
  anchorRef: triggerRef,
154
152
  popoverRef: popoverRef,
155
153
  placement: topLayerPlacement,
156
154
  isOpen: isOpen
157
- });
158
- (0, _useWidthFromAnchor.useWidthFromAnchor)({
159
- mode: shouldFitContainer ? 'match-anchor' : 'none',
160
- popoverRef: popoverRef,
161
- anchorRef: triggerRef,
162
- isOpen: isOpen
163
- });
155
+ }, (0, _getPopupAxisSizes.getPopupAxisSizes)({
156
+ shouldFitContainer: shouldFitContainer,
157
+ shouldFitViewport: shouldFitViewport
158
+ })));
164
159
 
165
160
  // onClose bridge.
166
161
  // Translates top-layer's { reason: TPopoverCloseReason } into legacy
@@ -241,7 +236,20 @@ var PopupTopLayer = exports.PopupTopLayer = /*#__PURE__*/(0, _react.memo)(functi
241
236
  "data-testid": testId,
242
237
  tabIndex: autoFocus ? -1 : undefined,
243
238
  xcss: xcss
244
- }, content(contentProps)) : /*#__PURE__*/React.createElement(_popoverSurface.PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
245
- className: (0, _runtime.ax)([contentOverflowStyles[shouldFitViewport ? 'fitViewport' : 'default']])
239
+ // Forwarded because several in-tree containers key their own
240
+ // `overflow: auto` branch off it.
241
+ ,
242
+ shouldFitViewport: shouldFitViewport
243
+ }, content(contentProps)) :
244
+ /*#__PURE__*/
245
+ // `PopoverSurface` owns `overflow: auto` and the `box-shadow` on the
246
+ // same element, so it is the scroll container. The wrapper exists only
247
+ // to carry the consumer's `xcss`: `PopoverSurface` exposes no
248
+ // `className` or `xcss` by design, so `xcss` lands on a CHILD of the
249
+ // surface. `width` works there; surface-level declarations apply one
250
+ // level in. Same shape as `compositional/popup-content-top-layer.tsx`.
251
+ React.createElement(_popoverSurface.PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
252
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
253
+ className: xcss
246
254
  }, content(contentProps)))));
247
255
  });
@@ -1,19 +1,17 @@
1
1
  /* popup-content-top-layer.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  import _extends from "@babel/runtime/helpers/extends";
3
- import "./popup-content-top-layer.compiled.css";
4
3
  import * as React from 'react';
4
+ import { ax, ix } from "@compiled/react/runtime";
5
5
  import { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
6
- import { ax, ix } from '@compiled/react/runtime';
7
6
  import noop from '@atlaskit/ds-lib/noop';
8
7
  import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map/index';
9
8
  import { createPopoverCloseEvent } from '@atlaskit/top-layer/popover/create-close-event';
10
9
  import { Popover } from '@atlaskit/top-layer/popover/popover';
11
10
  import { PopoverSurface } from '@atlaskit/top-layer/popover-surface';
12
- import { useAnchorPosition } from '@atlaskit/top-layer/use-anchor-position';
13
- import { useWidthFromAnchor } from '@atlaskit/top-layer/use-width-from-anchor';
11
+ import { useAnchoredPopover } from '@atlaskit/top-layer/use-anchored-popover';
12
+ import { getPopupAxisSizes } from '../internal/get-popup-axis-sizes';
14
13
  import { useRoleProps } from '../internal/top-layer-bridge';
15
14
  import { TriggerRefObjectContext } from './trigger-ref-object-context';
16
- const overflowAutoStyles = null;
17
15
 
18
16
  // Top-layer positioning is handled by CSS Anchor Positioning, not inline styles.
19
17
  const EMPTY_STYLE = {};
@@ -36,8 +34,9 @@ export function PopupContentTopLayer({
36
34
  fallbackPlacements: _fallbackPlacements,
37
35
  popupComponent: PopupContainer,
38
36
  autoFocus = true,
39
- shouldFitContainer,
40
- shouldFitViewport,
37
+ shouldFitContainer = false,
38
+ // Keep the explicit `false`. See the note in `popup-top-layer.tsx`.
39
+ shouldFitViewport = false,
41
40
  role,
42
41
  label,
43
42
  titleId,
@@ -176,17 +175,15 @@ export function PopupContentTopLayer({
176
175
 
177
176
  // `isOpen` is included so the anchor positioning effect re-runs when
178
177
  // the Popover host element is unmounted/remounted across open cycles.
179
- useAnchorPosition({
178
+ useAnchoredPopover({
180
179
  anchorRef,
181
180
  popoverRef,
182
181
  placement: topLayerPlacement,
183
- isOpen
184
- });
185
- useWidthFromAnchor({
186
- mode: shouldFitContainer ? 'match-anchor' : 'none',
187
- popoverRef,
188
- anchorRef,
189
- isOpen
182
+ isOpen,
183
+ ...getPopupAxisSizes({
184
+ shouldFitContainer,
185
+ shouldFitViewport
186
+ })
190
187
  });
191
188
 
192
189
  // Narrow to ForwardRefExoticComponent so JSX accepts the ref prop.
@@ -207,8 +204,20 @@ export function PopupContentTopLayer({
207
204
  "data-testid": testId,
208
205
  tabIndex: autoFocus ? -1 : undefined,
209
206
  xcss: xcss
210
- }, children(contentProps)) : /*#__PURE__*/React.createElement(PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
207
+ // Forwarded because several in-tree containers key their own
208
+ // `overflow: auto` branch off it.
209
+ ,
210
+ shouldFitViewport: shouldFitViewport
211
+ }, children(contentProps)) :
212
+ /*#__PURE__*/
213
+ // `PopoverSurface` owns `overflow: auto` and the `box-shadow` on the
214
+ // same element, so it is the scroll container. The wrapper exists only
215
+ // to carry the consumer's `xcss`: `PopoverSurface` exposes no
216
+ // `className` or `xcss` by design, so `xcss` lands on a CHILD of the
217
+ // surface. `width` works there; surface-level declarations apply one
218
+ // level in. Same shape as `../popup-top-layer.tsx`.
219
+ React.createElement(PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
211
220
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
212
- className: ax([shouldFitViewport && "_1reo1wug _18m91wug", ax([xcss])])
221
+ className: xcss
213
222
  }, children(contentProps))));
214
223
  }
@@ -39,7 +39,7 @@ export const Popup = ({
39
39
  // When the top-layer flag is on, we maintain a stable RefObject for the
40
40
  // trigger element, provided through TriggerRefObjectContext. Unlike the
41
41
  // state-based triggerRef (which causes a re-render when the trigger mounts),
42
- // this ref has a stable identity so useAnchorPosition's useLayoutEffect dep
42
+ // this ref has a stable identity so useAnchoredPopover's useLayoutEffect dep
43
43
  // array never changes — the effect runs once on mount when the trigger element
44
44
  // is already populated. PopupTrigger sets this ref directly instead of calling
45
45
  // the state setter, avoiding an unnecessary re-render.
@@ -6,8 +6,8 @@ import { createContext } from 'react';
6
6
  * for the top-layer path. Unlike `TriggerRefContext` (which provides
7
7
  * `HTMLElement | null` as React state and causes a re-render when the trigger
8
8
  * mounts), this context provides a ref object whose identity is stable across
9
- * renders — allowing `useAnchorPosition` to receive a stable `anchorRef`
10
- * without needing to create a new ref wrapper on every `triggerRef` state change.
9
+ * renders — allowing `useAnchoredPopover` to receive a stable anchor ref without
10
+ * needing to create a new ref wrapper on every `triggerRef` state change.
11
11
  */
12
12
  export const TriggerRefObjectContext = /*#__PURE__*/createContext({
13
13
  current: null
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Maps the legacy `shouldFitContainer` / `shouldFitViewport` pair onto
3
+ * `useAnchoredPopover`'s per-axis sizes. Shared by the standard and the
4
+ * compositional adapter so the two cannot drift.
5
+ *
6
+ * Both props can be set together while `inlineSize` takes one value, so
7
+ * `shouldFitContainer` wins the inline axis, being the more specific request.
8
+ * Fitting then applies to the block axis only, and still turns the
9
+ * placement-axis floor on, so a cramped popup flips rather than letterboxing.
10
+ *
11
+ * Known cost on a `left-*` / `right-*` placement: the anchor floor is unclamped
12
+ * so it can drive the flip, and a min beats a max, so a trigger wider than the
13
+ * viewport is floored past the viewport cap. Not new, and not reconciled - see
14
+ * `@atlaskit/top-layer`'s `notes/decisions/width-from-anchor-floors.md`.
15
+ */
16
+ export function getPopupAxisSizes({
17
+ shouldFitContainer,
18
+ shouldFitViewport
19
+ }) {
20
+ const blockSize = shouldFitViewport ? 'max-available' : 'content';
21
+ if (shouldFitContainer) {
22
+ return {
23
+ inlineSize: 'match-anchor',
24
+ blockSize
25
+ };
26
+ }
27
+ if (shouldFitViewport) {
28
+ return {
29
+ inlineSize: 'max-available',
30
+ blockSize
31
+ };
32
+ }
33
+ return {
34
+ inlineSize: 'content',
35
+ blockSize
36
+ };
37
+ }
@@ -1,6 +1,5 @@
1
1
  /* popup-top-layer.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  import _extends from "@babel/runtime/helpers/extends";
3
- import "./popup-top-layer.compiled.css";
4
3
  import * as React from 'react';
5
4
  import { ax, ix } from "@compiled/react/runtime";
6
5
  import { Fragment, memo, useCallback, useEffect, useMemo, useRef } from 'react';
@@ -10,15 +9,10 @@ import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map/index';
10
9
  import { createPopoverCloseEvent } from '@atlaskit/top-layer/popover/create-close-event';
11
10
  import { Popover } from '@atlaskit/top-layer/popover/popover';
12
11
  import { PopoverSurface } from '@atlaskit/top-layer/popover-surface';
13
- import { useAnchorPosition } from '@atlaskit/top-layer/use-anchor-position';
12
+ import { useAnchoredPopover } from '@atlaskit/top-layer/use-anchored-popover';
14
13
  import { usePopoverId } from '@atlaskit/top-layer/use-popover-id';
15
- import { useWidthFromAnchor } from '@atlaskit/top-layer/use-width-from-anchor';
14
+ import { getPopupAxisSizes } from './internal/get-popup-axis-sizes';
16
15
  import { useRoleProps } from './internal/top-layer-bridge';
17
- const contentOverflowStyles = {
18
- fitViewport: "_1reo1wug _18m91wug",
19
- default: ""
20
- };
21
-
22
16
  // Top-layer positioning is handled by CSS Anchor Positioning, not inline styles.
23
17
  const EMPTY_STYLE = {};
24
18
 
@@ -81,8 +75,14 @@ export const PopupTopLayer = /*#__PURE__*/memo(function PopupTopLayer({
81
75
  // top-layer: focus trapping is role-based. shouldDisableFocusLock is a no-op.
82
76
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
83
77
  shouldDisableFocusLock: _shouldDisableFocusLock,
84
- // top-layer: shouldFitViewport is handled via overflow on content wrapper.
85
- shouldFitViewport,
78
+ // top-layer: maps onto `'max-available'`, capping the popup to the space
79
+ // between the trigger and the viewport edge. `PopoverSurface` already owns
80
+ // `overflow: auto`, so the capped surface scrolls.
81
+ //
82
+ // Keep the explicit `false`: `undefined` previously reached an
83
+ // `isEnabled = true` default and silently enabled fitting for every popup
84
+ // that omitted the prop.
85
+ shouldFitViewport = false,
86
86
  // top-layer: appearance is accepted but UNSAFE_modal-below-sm is not yet implemented.
87
87
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
88
88
  appearance: _appearance
@@ -159,17 +159,15 @@ export const PopupTopLayer = /*#__PURE__*/memo(function PopupTopLayer({
159
159
  legacy: placement,
160
160
  offset: offsetProp
161
161
  }), [placement, offsetProp]);
162
- useAnchorPosition({
162
+ useAnchoredPopover({
163
163
  anchorRef: triggerRef,
164
164
  popoverRef,
165
165
  placement: topLayerPlacement,
166
- isOpen
167
- });
168
- useWidthFromAnchor({
169
- mode: shouldFitContainer ? 'match-anchor' : 'none',
170
- popoverRef,
171
- anchorRef: triggerRef,
172
- isOpen
166
+ isOpen,
167
+ ...getPopupAxisSizes({
168
+ shouldFitContainer,
169
+ shouldFitViewport
170
+ })
173
171
  });
174
172
 
175
173
  // onClose bridge.
@@ -250,7 +248,20 @@ export const PopupTopLayer = /*#__PURE__*/memo(function PopupTopLayer({
250
248
  "data-testid": testId,
251
249
  tabIndex: autoFocus ? -1 : undefined,
252
250
  xcss: xcss
253
- }, content(contentProps)) : /*#__PURE__*/React.createElement(PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
254
- className: ax([contentOverflowStyles[shouldFitViewport ? 'fitViewport' : 'default']])
251
+ // Forwarded because several in-tree containers key their own
252
+ // `overflow: auto` branch off it.
253
+ ,
254
+ shouldFitViewport: shouldFitViewport
255
+ }, content(contentProps)) :
256
+ /*#__PURE__*/
257
+ // `PopoverSurface` owns `overflow: auto` and the `box-shadow` on the
258
+ // same element, so it is the scroll container. The wrapper exists only
259
+ // to carry the consumer's `xcss`: `PopoverSurface` exposes no
260
+ // `className` or `xcss` by design, so `xcss` lands on a CHILD of the
261
+ // surface. `width` works there; surface-level declarations apply one
262
+ // level in. Same shape as `compositional/popup-content-top-layer.tsx`.
263
+ React.createElement(PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
264
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
265
+ className: xcss
255
266
  }, content(contentProps)))));
256
267
  });
@@ -1,19 +1,20 @@
1
1
  /* popup-content-top-layer.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  import _extends from "@babel/runtime/helpers/extends";
3
- import "./popup-content-top-layer.compiled.css";
3
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
4
  import * as React from 'react';
5
+ import { ax, ix } from "@compiled/react/runtime";
6
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
7
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
8
  import { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
6
- import { ax, ix } from '@compiled/react/runtime';
7
9
  import noop from '@atlaskit/ds-lib/noop';
8
10
  import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map/index';
9
11
  import { createPopoverCloseEvent } from '@atlaskit/top-layer/popover/create-close-event';
10
12
  import { Popover } from '@atlaskit/top-layer/popover/popover';
11
13
  import { PopoverSurface } from '@atlaskit/top-layer/popover-surface';
12
- import { useAnchorPosition } from '@atlaskit/top-layer/use-anchor-position';
13
- import { useWidthFromAnchor } from '@atlaskit/top-layer/use-width-from-anchor';
14
+ import { useAnchoredPopover } from '@atlaskit/top-layer/use-anchored-popover';
15
+ import { getPopupAxisSizes } from '../internal/get-popup-axis-sizes';
14
16
  import { useRoleProps } from '../internal/top-layer-bridge';
15
17
  import { TriggerRefObjectContext } from './trigger-ref-object-context';
16
- var overflowAutoStyles = null;
17
18
 
18
19
  // Top-layer positioning is handled by CSS Anchor Positioning, not inline styles.
19
20
  var EMPTY_STYLE = {};
@@ -38,8 +39,10 @@ export function PopupContentTopLayer(_ref) {
38
39
  PopupContainer = _ref.popupComponent,
39
40
  _ref$autoFocus = _ref.autoFocus,
40
41
  autoFocus = _ref$autoFocus === void 0 ? true : _ref$autoFocus,
41
- shouldFitContainer = _ref.shouldFitContainer,
42
- shouldFitViewport = _ref.shouldFitViewport,
42
+ _ref$shouldFitContain = _ref.shouldFitContainer,
43
+ shouldFitContainer = _ref$shouldFitContain === void 0 ? false : _ref$shouldFitContain,
44
+ _ref$shouldFitViewpor = _ref.shouldFitViewport,
45
+ shouldFitViewport = _ref$shouldFitViewpor === void 0 ? false : _ref$shouldFitViewpor,
43
46
  role = _ref.role,
44
47
  label = _ref.label,
45
48
  titleId = _ref.titleId,
@@ -159,18 +162,15 @@ export function PopupContentTopLayer(_ref) {
159
162
 
160
163
  // `isOpen` is included so the anchor positioning effect re-runs when
161
164
  // the Popover host element is unmounted/remounted across open cycles.
162
- useAnchorPosition({
165
+ useAnchoredPopover(_objectSpread({
163
166
  anchorRef: anchorRef,
164
167
  popoverRef: popoverRef,
165
168
  placement: topLayerPlacement,
166
169
  isOpen: isOpen
167
- });
168
- useWidthFromAnchor({
169
- mode: shouldFitContainer ? 'match-anchor' : 'none',
170
- popoverRef: popoverRef,
171
- anchorRef: anchorRef,
172
- isOpen: isOpen
173
- });
170
+ }, getPopupAxisSizes({
171
+ shouldFitContainer: shouldFitContainer,
172
+ shouldFitViewport: shouldFitViewport
173
+ })));
174
174
 
175
175
  // Narrow to ForwardRefExoticComponent so JSX accepts the ref prop.
176
176
  // All popupComponent implementations use forwardRef per the PopupComponentProps contract.
@@ -190,8 +190,20 @@ export function PopupContentTopLayer(_ref) {
190
190
  "data-testid": testId,
191
191
  tabIndex: autoFocus ? -1 : undefined,
192
192
  xcss: xcss
193
- }, children(contentProps)) : /*#__PURE__*/React.createElement(PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
193
+ // Forwarded because several in-tree containers key their own
194
+ // `overflow: auto` branch off it.
195
+ ,
196
+ shouldFitViewport: shouldFitViewport
197
+ }, children(contentProps)) :
198
+ /*#__PURE__*/
199
+ // `PopoverSurface` owns `overflow: auto` and the `box-shadow` on the
200
+ // same element, so it is the scroll container. The wrapper exists only
201
+ // to carry the consumer's `xcss`: `PopoverSurface` exposes no
202
+ // `className` or `xcss` by design, so `xcss` lands on a CHILD of the
203
+ // surface. `width` works there; surface-level declarations apply one
204
+ // level in. Same shape as `../popup-top-layer.tsx`.
205
+ React.createElement(PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
194
206
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
195
- className: ax([shouldFitViewport && "_1reo1wug _18m91wug", ax([xcss])])
207
+ className: xcss
196
208
  }, children(contentProps))));
197
209
  }
@@ -43,7 +43,7 @@ export var Popup = function Popup(_ref) {
43
43
  // When the top-layer flag is on, we maintain a stable RefObject for the
44
44
  // trigger element, provided through TriggerRefObjectContext. Unlike the
45
45
  // state-based triggerRef (which causes a re-render when the trigger mounts),
46
- // this ref has a stable identity so useAnchorPosition's useLayoutEffect dep
46
+ // this ref has a stable identity so useAnchoredPopover's useLayoutEffect dep
47
47
  // array never changes — the effect runs once on mount when the trigger element
48
48
  // is already populated. PopupTrigger sets this ref directly instead of calling
49
49
  // the state setter, avoiding an unnecessary re-render.
@@ -6,8 +6,8 @@ import { createContext } from 'react';
6
6
  * for the top-layer path. Unlike `TriggerRefContext` (which provides
7
7
  * `HTMLElement | null` as React state and causes a re-render when the trigger
8
8
  * mounts), this context provides a ref object whose identity is stable across
9
- * renders — allowing `useAnchorPosition` to receive a stable `anchorRef`
10
- * without needing to create a new ref wrapper on every `triggerRef` state change.
9
+ * renders — allowing `useAnchoredPopover` to receive a stable anchor ref without
10
+ * needing to create a new ref wrapper on every `triggerRef` state change.
11
11
  */
12
12
  export var TriggerRefObjectContext = /*#__PURE__*/createContext({
13
13
  current: null
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Maps the legacy `shouldFitContainer` / `shouldFitViewport` pair onto
3
+ * `useAnchoredPopover`'s per-axis sizes. Shared by the standard and the
4
+ * compositional adapter so the two cannot drift.
5
+ *
6
+ * Both props can be set together while `inlineSize` takes one value, so
7
+ * `shouldFitContainer` wins the inline axis, being the more specific request.
8
+ * Fitting then applies to the block axis only, and still turns the
9
+ * placement-axis floor on, so a cramped popup flips rather than letterboxing.
10
+ *
11
+ * Known cost on a `left-*` / `right-*` placement: the anchor floor is unclamped
12
+ * so it can drive the flip, and a min beats a max, so a trigger wider than the
13
+ * viewport is floored past the viewport cap. Not new, and not reconciled - see
14
+ * `@atlaskit/top-layer`'s `notes/decisions/width-from-anchor-floors.md`.
15
+ */
16
+ export function getPopupAxisSizes(_ref) {
17
+ var shouldFitContainer = _ref.shouldFitContainer,
18
+ shouldFitViewport = _ref.shouldFitViewport;
19
+ var blockSize = shouldFitViewport ? 'max-available' : 'content';
20
+ if (shouldFitContainer) {
21
+ return {
22
+ inlineSize: 'match-anchor',
23
+ blockSize: blockSize
24
+ };
25
+ }
26
+ if (shouldFitViewport) {
27
+ return {
28
+ inlineSize: 'max-available',
29
+ blockSize: blockSize
30
+ };
31
+ }
32
+ return {
33
+ inlineSize: 'content',
34
+ blockSize: blockSize
35
+ };
36
+ }
@@ -1,8 +1,10 @@
1
1
  /* popup-top-layer.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  import _extends from "@babel/runtime/helpers/extends";
3
- import "./popup-top-layer.compiled.css";
3
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
4
  import * as React from 'react';
5
5
  import { ax, ix } from "@compiled/react/runtime";
6
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
7
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
6
8
  import { Fragment, memo, useCallback, useEffect, useMemo, useRef } from 'react';
7
9
  import noop from '@atlaskit/ds-lib/noop';
8
10
  import { getAriaForTrigger } from '@atlaskit/top-layer/get-aria-for-trigger';
@@ -10,15 +12,10 @@ import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map/index';
10
12
  import { createPopoverCloseEvent } from '@atlaskit/top-layer/popover/create-close-event';
11
13
  import { Popover } from '@atlaskit/top-layer/popover/popover';
12
14
  import { PopoverSurface } from '@atlaskit/top-layer/popover-surface';
13
- import { useAnchorPosition } from '@atlaskit/top-layer/use-anchor-position';
15
+ import { useAnchoredPopover } from '@atlaskit/top-layer/use-anchored-popover';
14
16
  import { usePopoverId } from '@atlaskit/top-layer/use-popover-id';
15
- import { useWidthFromAnchor } from '@atlaskit/top-layer/use-width-from-anchor';
17
+ import { getPopupAxisSizes } from './internal/get-popup-axis-sizes';
16
18
  import { useRoleProps } from './internal/top-layer-bridge';
17
- var contentOverflowStyles = {
18
- fitViewport: "_1reo1wug _18m91wug",
19
- default: ""
20
- };
21
-
22
19
  // Top-layer positioning is handled by CSS Anchor Positioning, not inline styles.
23
20
  var EMPTY_STYLE = {};
24
21
 
@@ -65,7 +62,8 @@ export var PopupTopLayer = /*#__PURE__*/memo(function PopupTopLayer(_ref) {
65
62
  _rootBoundary = _ref.rootBoundary,
66
63
  _shouldUseCaptureOnOutsideClick = _ref.shouldUseCaptureOnOutsideClick,
67
64
  _shouldDisableFocusLock = _ref.shouldDisableFocusLock,
68
- shouldFitViewport = _ref.shouldFitViewport,
65
+ _ref$shouldFitViewpor = _ref.shouldFitViewport,
66
+ shouldFitViewport = _ref$shouldFitViewpor === void 0 ? false : _ref$shouldFitViewpor,
69
67
  _appearance = _ref.appearance;
70
68
  var triggerRef = useRef(null);
71
69
  var popoverRef = useRef(null);
@@ -140,18 +138,15 @@ export var PopupTopLayer = /*#__PURE__*/memo(function PopupTopLayer(_ref) {
140
138
  offset: offsetProp
141
139
  });
142
140
  }, [placement, offsetProp]);
143
- useAnchorPosition({
141
+ useAnchoredPopover(_objectSpread({
144
142
  anchorRef: triggerRef,
145
143
  popoverRef: popoverRef,
146
144
  placement: topLayerPlacement,
147
145
  isOpen: isOpen
148
- });
149
- useWidthFromAnchor({
150
- mode: shouldFitContainer ? 'match-anchor' : 'none',
151
- popoverRef: popoverRef,
152
- anchorRef: triggerRef,
153
- isOpen: isOpen
154
- });
146
+ }, getPopupAxisSizes({
147
+ shouldFitContainer: shouldFitContainer,
148
+ shouldFitViewport: shouldFitViewport
149
+ })));
155
150
 
156
151
  // onClose bridge.
157
152
  // Translates top-layer's { reason: TPopoverCloseReason } into legacy
@@ -232,7 +227,20 @@ export var PopupTopLayer = /*#__PURE__*/memo(function PopupTopLayer(_ref) {
232
227
  "data-testid": testId,
233
228
  tabIndex: autoFocus ? -1 : undefined,
234
229
  xcss: xcss
235
- }, content(contentProps)) : /*#__PURE__*/React.createElement(PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
236
- className: ax([contentOverflowStyles[shouldFitViewport ? 'fitViewport' : 'default']])
230
+ // Forwarded because several in-tree containers key their own
231
+ // `overflow: auto` branch off it.
232
+ ,
233
+ shouldFitViewport: shouldFitViewport
234
+ }, content(contentProps)) :
235
+ /*#__PURE__*/
236
+ // `PopoverSurface` owns `overflow: auto` and the `box-shadow` on the
237
+ // same element, so it is the scroll container. The wrapper exists only
238
+ // to carry the consumer's `xcss`: `PopoverSurface` exposes no
239
+ // `className` or `xcss` by design, so `xcss` lands on a CHILD of the
240
+ // surface. `width` works there; surface-level declarations apply one
241
+ // level in. Same shape as `compositional/popup-content-top-layer.tsx`.
242
+ React.createElement(PopoverSurface, null, /*#__PURE__*/React.createElement("div", {
243
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
244
+ className: xcss
237
245
  }, content(contentProps)))));
238
246
  });
@@ -4,7 +4,7 @@ import { type Context, type MutableRefObject } from 'react';
4
4
  * for the top-layer path. Unlike `TriggerRefContext` (which provides
5
5
  * `HTMLElement | null` as React state and causes a re-render when the trigger
6
6
  * mounts), this context provides a ref object whose identity is stable across
7
- * renders — allowing `useAnchorPosition` to receive a stable `anchorRef`
8
- * without needing to create a new ref wrapper on every `triggerRef` state change.
7
+ * renders — allowing `useAnchoredPopover` to receive a stable anchor ref without
8
+ * needing to create a new ref wrapper on every `triggerRef` state change.
9
9
  */
10
10
  export declare const TriggerRefObjectContext: Context<MutableRefObject<HTMLElement | null>>;
@@ -0,0 +1,25 @@
1
+ import type { TAnchoredPopoverOptions } from '@atlaskit/top-layer/use-anchored-popover';
2
+ type TPopupAxisSize = NonNullable<TAnchoredPopoverOptions['inlineSize']>;
3
+ /**
4
+ * Maps the legacy `shouldFitContainer` / `shouldFitViewport` pair onto
5
+ * `useAnchoredPopover`'s per-axis sizes. Shared by the standard and the
6
+ * compositional adapter so the two cannot drift.
7
+ *
8
+ * Both props can be set together while `inlineSize` takes one value, so
9
+ * `shouldFitContainer` wins the inline axis, being the more specific request.
10
+ * Fitting then applies to the block axis only, and still turns the
11
+ * placement-axis floor on, so a cramped popup flips rather than letterboxing.
12
+ *
13
+ * Known cost on a `left-*` / `right-*` placement: the anchor floor is unclamped
14
+ * so it can drive the flip, and a min beats a max, so a trigger wider than the
15
+ * viewport is floored past the viewport cap. Not new, and not reconciled - see
16
+ * `@atlaskit/top-layer`'s `notes/decisions/width-from-anchor-floors.md`.
17
+ */
18
+ export declare function getPopupAxisSizes({ shouldFitContainer, shouldFitViewport, }: {
19
+ shouldFitContainer: boolean;
20
+ shouldFitViewport: boolean;
21
+ }): {
22
+ inlineSize: TPopupAxisSize;
23
+ blockSize: TPopupAxisSize;
24
+ };
25
+ export {};
@@ -46,6 +46,36 @@ export interface ContentProps {
46
46
  */
47
47
  setInitialFocusRef: Dispatch<SetStateAction<HTMLElement | null>>;
48
48
  }
49
+ /**
50
+ * Props passed to a custom `popupComponent`, which renders as the popup container in
51
+ * place of the default surface.
52
+ *
53
+ * **Contract on the top-layer path (`platform-dst-top-layer`).** The container is
54
+ * the first child of a `display: flex` popover host whose `& > *` rule sets
55
+ * `flex-grow: 1`, `min-inline-size: 0` and `min-block-size: 0`. A size cap
56
+ * (`shouldFitViewport`, or the always-on viewport backstop) reaches the content only
57
+ * through that first child, so a container has to:
58
+ *
59
+ * 1. Render exactly one in-flow root. A second in-flow root becomes a second flex
60
+ * item, laid out in a row beside the first.
61
+ * 2. Keep that root in flow, not `position: absolute` or `fixed`. An out-of-flow root
62
+ * is not a flex item, so `flex-grow` and cross-axis stretch never reach it.
63
+ * 3. Give the root ITSELF a non-`visible` computed `overflow`, so it is the scroll
64
+ * container. The `min-*-size: 0` reset caps the root's box whatever its
65
+ * `overflow` is; a `visible` root's content spills out of a correctly-sized box
66
+ * instead of scrolling.
67
+ * 4. Leave the block size `auto`, so cross-axis stretch can size it.
68
+ * 5. Not pin the inline size above the cap, and not set `flex-shrink: 0`.
69
+ * 6. Put the `overflow` on the root, not on a descendant. `& > *` reaches one level.
70
+ *
71
+ * `shouldFitViewport` is forwarded on both paths, so the container can own its
72
+ * `overflow` (several in-tree containers key an `overflow: auto` branch off it).
73
+ *
74
+ * `shouldRenderToParent` is NOT forwarded on the top-layer path, by decision:
75
+ * everything renders in the top layer, so the container receives `undefined`, and a
76
+ * `(!shouldRenderToParent || shouldFitViewport)` branch reads as "fitting applies",
77
+ * which is the intended reading there.
78
+ */
49
79
  export interface PopupComponentProps {
50
80
  /**
51
81
  * Children passed through by the parent popup.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/popup",
3
- "version": "6.3.4",
3
+ "version": "6.3.5",
4
4
  "description": "A popup displays brief content in an overlay.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -41,7 +41,7 @@
41
41
  "@atlaskit/primitives": "^22.5.0",
42
42
  "@atlaskit/react-compiler-gating": "^0.2.0",
43
43
  "@atlaskit/tokens": "^16.12.0",
44
- "@atlaskit/top-layer": "^4.4.0",
44
+ "@atlaskit/top-layer": "^5.0.0",
45
45
  "@babel/runtime": "^7.0.0",
46
46
  "@compiled/react": "^1.0.2",
47
47
  "bind-event-listener": "^3.0.0",
@@ -60,11 +60,12 @@
60
60
  "@atlaskit/banner": "^15.3.0",
61
61
  "@atlaskit/button": "^25.3.0",
62
62
  "@atlaskit/code": "^19.2.0",
63
+ "@atlaskit/datetime-picker": "^18.7.0",
63
64
  "@atlaskit/docs": "^12.2.0",
64
65
  "@atlaskit/dropdown-menu": "^18.3.0",
65
66
  "@atlaskit/form": "^17.2.0",
66
67
  "@atlaskit/heading": "^7.2.0",
67
- "@atlaskit/icon": "^37.6.0",
68
+ "@atlaskit/icon": "^37.7.0",
68
69
  "@atlaskit/link": "^5.1.0",
69
70
  "@atlaskit/menu": "^10.2.0",
70
71
  "@atlaskit/modal-dialog": "^16.6.0",
@@ -1,2 +0,0 @@
1
- ._18m91wug{overflow-y:auto}
2
- ._1reo1wug{overflow-x:auto}
@@ -1,2 +0,0 @@
1
- ._18m91wug{overflow-y:auto}
2
- ._1reo1wug{overflow-x:auto}
@@ -1,2 +0,0 @@
1
- ._18m91wug{overflow-y:auto}
2
- ._1reo1wug{overflow-x:auto}
@@ -1,2 +0,0 @@
1
- ._18m91wug{overflow-y:auto}
2
- ._1reo1wug{overflow-x:auto}
@@ -1,2 +0,0 @@
1
- ._18m91wug{overflow-y:auto}
2
- ._1reo1wug{overflow-x:auto}
@@ -1,2 +0,0 @@
1
- ._18m91wug{overflow-y:auto}
2
- ._1reo1wug{overflow-x:auto}