@atlaskit/popup 6.3.3 → 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 (36) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/__tests__/playwright/fit-viewport.spec.tsx +225 -0
  3. package/dist/cjs/compositional/popup-content-top-layer.js +30 -19
  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/popper-wrapper.js +1 -1
  8. package/dist/cjs/popup-top-layer.js +28 -20
  9. package/dist/cjs/popup.js +1 -1
  10. package/dist/cjs/use-appearance.js +11 -3
  11. package/dist/es2019/compositional/popup-content-top-layer.js +27 -18
  12. package/dist/es2019/compositional/popup.js +1 -1
  13. package/dist/es2019/compositional/trigger-ref-object-context.js +2 -2
  14. package/dist/es2019/internal/get-popup-axis-sizes.js +37 -0
  15. package/dist/es2019/popper-wrapper.js +1 -1
  16. package/dist/es2019/popup-top-layer.js +32 -21
  17. package/dist/es2019/popup.js +1 -1
  18. package/dist/es2019/use-appearance.js +11 -3
  19. package/dist/esm/compositional/popup-content-top-layer.js +30 -18
  20. package/dist/esm/compositional/popup.js +1 -1
  21. package/dist/esm/compositional/trigger-ref-object-context.js +2 -2
  22. package/dist/esm/internal/get-popup-axis-sizes.js +36 -0
  23. package/dist/esm/popper-wrapper.js +1 -1
  24. package/dist/esm/popup-top-layer.js +28 -20
  25. package/dist/esm/popup.js +1 -1
  26. package/dist/esm/use-appearance.js +11 -3
  27. package/dist/types/compositional/trigger-ref-object-context.d.ts +2 -2
  28. package/dist/types/internal/get-popup-axis-sizes.d.ts +25 -0
  29. package/dist/types/types.d.ts +30 -0
  30. package/package.json +9 -4
  31. package/dist/cjs/compositional/popup-content-top-layer.compiled.css +0 -2
  32. package/dist/cjs/popup-top-layer.compiled.css +0 -2
  33. package/dist/es2019/compositional/popup-content-top-layer.compiled.css +0 -2
  34. package/dist/es2019/popup-top-layer.compiled.css +0 -2
  35. package/dist/esm/compositional/popup-content-top-layer.compiled.css +0 -2
  36. package/dist/esm/popup-top-layer.compiled.css +0 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
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
+
14
+ ## 6.3.4
15
+
16
+ ### Patch Changes
17
+
18
+ - [`345efa8128cb2`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/345efa8128cb2) -
19
+ Behind platform_dst_popup_media_query_perf_fix Popups with the default appearance no longer cause
20
+ state changes across the below.sm viewport media query Popups with the UNSAFE_modal-below-sm
21
+ appearance (modal) still cause the state change This was done to fix popup heavy pages stuttering
22
+ on resizing, as the state change was causing unnecessary re-renders
23
+ - Updated dependencies
24
+
3
25
  ## 6.3.3
4
26
 
5
27
  ### 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
+ });
@@ -1,4 +1,4 @@
1
- /* popup-content-top-layer.tsx generated by @compiled/babel-plugin v2.0.0 */
1
+ /* popup-content-top-layer.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  "use strict";
3
3
 
4
4
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- /* popper-wrapper.tsx generated by @compiled/babel-plugin v2.0.0 */
1
+ /* popper-wrapper.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  "use strict";
3
3
 
4
4
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
@@ -1,4 +1,4 @@
1
- /* popup-top-layer.tsx generated by @compiled/babel-plugin v2.0.0 */
1
+ /* popup-top-layer.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  "use strict";
3
3
 
4
4
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
@@ -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
  });
package/dist/cjs/popup.js CHANGED
@@ -1,4 +1,4 @@
1
- /* popup.tsx generated by @compiled/babel-plugin v2.0.0 */
1
+ /* popup.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  "use strict";
3
3
 
4
4
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.usePopupAppearance = usePopupAppearance;
8
8
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
9
  var _react = require("react");
10
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
10
11
  var _compiled = require("@atlaskit/primitives/compiled");
11
12
  /**
12
13
  * **usePopupAppearance()**
@@ -19,14 +20,21 @@ var _compiled = require("@atlaskit/primitives/compiled");
19
20
  function usePopupAppearance(_ref) {
20
21
  var _appearance = _ref.appearance,
21
22
  _shouldRenderToParent = _ref.shouldRenderToParent;
22
- var mq = (0, _compiled.UNSAFE_useMediaQuery)('below.sm', function (e) {
23
+ var canBecomeModal = _appearance === 'UNSAFE_modal-below-sm';
24
+
25
+ /**
26
+ * This gate removes unnecessary state changes across the below-sm boundary
27
+ * The state change is only needed if the popup can become a modal - and this is maintained
28
+ */
29
+ var shouldUseMediaQuery = !(0, _fg.fg)('platform_dst_popup_media_query_perf_fix') || canBecomeModal;
30
+ var mq = (0, _compiled.UNSAFE_useMediaQuery)('below.sm', shouldUseMediaQuery ? function (e) {
23
31
  setIsSmallViewport(!!e.matches);
24
- });
32
+ } : undefined);
25
33
  var _useState = (0, _react.useState)(!!(mq !== null && mq !== void 0 && mq.matches)),
26
34
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
27
35
  isSmallViewport = _useState2[0],
28
36
  setIsSmallViewport = _useState2[1];
29
- var appearance = _appearance === 'UNSAFE_modal-below-sm' && isSmallViewport ? 'UNSAFE_modal-below-sm' : 'default';
37
+ var appearance = canBecomeModal && isSmallViewport ? 'UNSAFE_modal-below-sm' : 'default';
30
38
  var shouldRenderToParent = _shouldRenderToParent && appearance === 'default';
31
39
  return {
32
40
  appearance: appearance,
@@ -1,19 +1,17 @@
1
- /* popup-content-top-layer.tsx generated by @compiled/babel-plugin v2.0.0 */
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.