@atlaskit/top-layer 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,115 @@
1
1
  # @atlaskit/top-layer
2
2
 
3
+ ## 0.6.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`9d77f6602f870`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9d77f6602f870) -
8
+ Fix two CSS anchor positioning bugs for aligned placements (align-start/align-end):
9
+ 1. Popups at viewport corners now correctly fall back to the diagonally opposite corner when their
10
+ preferred placement has no space, rather than remaining partially off-screen.
11
+
12
+ 2. Popups near a single viewport edge now correctly trigger position fallbacks when their content
13
+ is wider than the available space, rather than wrapping content into a narrow column.
14
+
15
+ ## 0.6.1
16
+
17
+ ### Patch Changes
18
+
19
+ - [`2bed6255731de`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2bed6255731de) -
20
+ Top-layer adoption work behind the `platform-dst-top-layer` feature flag. Public adopter APIs are
21
+ intentionally kept narrow while the top-layer API surface settles, with one exception called out
22
+ below.
23
+
24
+ Highlights:
25
+ - Pass the full `[along, away]` legacy popper offset through to the new top-layer
26
+ `placement.offset` API (via `fromLegacyPlacement`). Previously only the `away` axis was
27
+ forwarded, which dropped the `along` offset for consumers of `Popup`, `PopupSelect`,
28
+ `Spotlight`, and `Tooltip` when `platform-dst-top-layer` is enabled.
29
+ - Fix broken import of `dialogHeight` and `dialogWidth` from the removed utils module in
30
+ `@atlaskit/modal-dialog`.
31
+
32
+ Public API:
33
+ - **`@atlaskit/tooltip`** (`minor`): add an optional `testId?: string` field to `TriggerProps`.
34
+ This is additive (no existing prop changes shape). Required because `@atlaskit/button/new` (and
35
+ other `Pressable`-backed primitives) overwrite `data-testid` from spread, so the legacy
36
+ `(triggerProps as any)['data-testid']` workaround is silently absorbed by those consumers. A
37
+ typed `testId` field flows through their own `testId` destructure instead, restoring
38
+ `data-testid` propagation onto the rendered trigger element.
39
+ - **`@atlaskit/popup`**, **`@atlaskit/dropdown-menu`** (`patch`): no public type changes. Wider
40
+ `aria-haspopup` unions that the FF-on path produces are bridged at the package boundary into
41
+ `@atlaskit/top-layer` with localised `FUDGE(top-layer-api)` casts, documented in
42
+ `packages/design-system/top-layer/notes/decisions/migration-roadmap.md` ("Open API decisions
43
+ deferred to a follow-up PR"). They will be widened in a follow-up `minor` PR once the top-layer
44
+ API is committed.
45
+ - **`@atlaskit/modal-dialog`**, **`@atlaskit/select`**, **`@atlaskit/spotlight`**
46
+ (`patch`/`minor`): no public type changes; bug fixes only.
47
+
48
+ Merge-readiness fixes (FF-on test wiring + adopter behavior):
49
+ - **`@atlaskit/popup`** (`minor`): wire the compositional `PopupContent` to delegate to
50
+ `PopupContentTopLayer` when `platform-dst-top-layer` is enabled. Previously only the legacy
51
+ `Popup` component had the FF branch, leaving consumers of the compositional API on the legacy
52
+ popper path.
53
+ - **`@atlaskit/select`** (`minor`): add an `onClick` handler to the `PopupSelect` top-layer
54
+ trigger so clicks open/close the menu (mirrors the legacy global click handler in
55
+ `popup-select.tsx`). Add explicit Escape handling on the menu's `onKeyDown` so the menu closes
56
+ and focus returns to the trigger.
57
+ - **`@atlaskit/top-layer`** (`patch`): the `<dialog>` rendered by the Dialog primitive now sets
58
+ `aria-modal="true"` explicitly. Modern browsers infer modal semantics from `.showModal()` but
59
+ some assistive tech still keys off the explicit attribute.
60
+ - **`@atlaskit/top-layer`** (`patch`): guard `use-anchor-positioning` against environments where
61
+ `ResizeObserver` is not defined (e.g. jest's `node` environment, used by the post-office test
62
+ suite). The observer is used to wait for the popover's first valid layout before measuring;
63
+ consumers in non-DOM jest environments now get a no-op observer and the scroll/resize listeners
64
+ still apply if the host environment polyfills `showPopover`. Real browsers always have
65
+ `ResizeObserver`.
66
+ - **`@atlaskit/modal-dialog`** (`patch`): on the FF-on path, drop the `tabIndex={-1}` (and unused
67
+ `:focus-visible` outline) from the modal content wrapper. The native `<dialog>.showModal()`
68
+ focus-delegate algorithm picks the first focusable descendant (including `tabindex=-1`), and the
69
+ wrapper was hijacking initial focus from the close button. Also honor `shouldReturnFocus={ref}`
70
+ on the FF-on path (an unmount-cleanup focuses the ref after `dialog.close()` so it overrides the
71
+ browser's automatic return-to-trigger). Boolean `shouldReturnFocus={false}` is not yet honored
72
+ on the FF-on path — see `top-layer/notes/merge-blockers.md`.
73
+ - **`@atlaskit/datetime-picker`** (`patch`): on the FF-on path, set `mode="manual"` on the
74
+ `Popup.Content` rendered by both `internal/menu-top-layer.tsx` (date-picker calendar) and
75
+ `internal/fixed-layer-menu-top-layer.tsx` (time-picker menu). With the default `mode="auto"`,
76
+ the same click event that opens the menu (which targets the react-select combobox input —
77
+ outside the popover element) bubbles to the browser's native popover light-dismiss handler and
78
+ immediately closes the menu. react-select / DateTimePicker already own outside-click and Esc
79
+ dismissal via their own state, so opting out of the native auto-dismiss is the correct
80
+ integration. Also extend the existing Esc → trigger-focus restoration in
81
+ `components/date-picker.tsx` to the FF-on path (manual mode disables the browser's built-in
82
+ focus return, and the legacy code path was already handling this for itself behind an FF
83
+ negation).
84
+ - **`@atlaskit/popup`** (no public API change): no source changes — only FF-on Playwright
85
+ spec/example fixes drove the suite from 21/3/2 to 27/0/0. Notable: the two `test.fixme`'d
86
+ nested-popover cases were not browser limitations; `popover="auto"` chains correctly via DOM
87
+ ancestry (the original fixmes had the wrong testId selector). Added `testId` props to two
88
+ examples (`16-popup-with-a11y-props`, `18-should-fit-container`) so default-shape tests can
89
+ reach the trigger.
90
+ - Test alignment for FF-on Playwright suites across `popup`, `select`, `datetime-picker`,
91
+ `inline-dialog`, `inline-message`, and `modal-dialog`: selector updates to match the new
92
+ top-layer testId convention (`${testId}--content`, `[role="dialog"][aria-label="calendar"]`),
93
+ per-spec `skipAxeCheck()` for example-level color-contrast violations unrelated to the
94
+ migration, and focus assertions adjusted to match native `<dialog>` / `Popup.Content` auto-focus
95
+ semantics (focus lands on the first focusable child, not the dialog container itself).
96
+ - **`@atlassian/capacity-planning-capacity-graph`**, **`@atlaskit/color-picker`**,
97
+ **`@atlassian/timeline-table`**, **`@atlassian/global-side-navigation`** (`patch`): scope `fg`
98
+ mocks in unit tests so `platform-dst-top-layer` returns `false`. JSDOM does not implement the
99
+ native Popover API (`showPopover`/`hidePopover`/`toggle` events), so leaving the gate ON in unit
100
+ tests caused popover content to remain in the DOM after close and broke close-behaviour
101
+ assertions. Browser coverage for the FF-on path is provided by the Playwright suites listed
102
+ above.
103
+ - **`@atlaskit/dropdown-menu`** (no public API change): test/example-only fixes for the FF-on
104
+ Playwright suite. Added `role="menuitem"` to the nested-trigger `ButtonItem` in
105
+ `examples/93-testing-nested-keyboard-navigation-top-layer.tsx` to satisfy axe's
106
+ `aria-required-children` rule on the parent menu. Added a `test.beforeEach(skipAxeCheck)` to
107
+ `dropdown-menu.spec.tsx` (FF-on suite) for example-level `color-contrast` violations on the
108
+ pre-existing `color.text.selected`/`color.background.selected` token pair (3.91:1). Replaced a
109
+ deadlocking `await expect(moveItem).not.toBeFocused()` pre-open assertion (Playwright's
110
+ auto-wait blocks 5s on the absent element) with `await expect(moveItem).not.toBeVisible()`.
111
+ Suite result: 22/22 passing.
112
+
3
113
  ## 0.6.0
4
114
 
5
115
  ### Minor Changes
@@ -91,6 +91,20 @@ function placementToPositionArea(_ref) {
91
91
  * For aligned placements (start/end), includes the same-edge centered
92
92
  * position as fallbacks so the browser can try centering when the aligned
93
93
  * position would overflow, before flipping to the opposite edge.
94
+ *
95
+ * **Why `flip-inline flip-block` (or `flip-block flip-inline`) is included:**
96
+ *
97
+ * When a popup is in a viewport corner (e.g. inline-end align-end at the
98
+ * top-right), flipping only the primary axis (`flip-inline`) moves the popup
99
+ * to the left but keeps the same cross-axis span direction (e.g. upward),
100
+ * which is still off-screen. The combined keyword `flip-inline flip-block`
101
+ * mirrors the popup diagonally — it flips BOTH the position-area AND both
102
+ * inline and block margins simultaneously, placing the popup at the
103
+ * diagonally opposite corner where there is always space.
104
+ *
105
+ * Named `position-area` entries (e.g. `inline-start span-block-end`) in
106
+ * `position-try-fallbacks` do NOT update margins, so only the `flip-*`
107
+ * tactic keywords correctly flip the gap margin along with the position.
94
108
  */
95
109
  function placementToTryFallbacks(_ref2) {
96
110
  var placement = _ref2.placement;
@@ -106,9 +120,10 @@ function placementToTryFallbacks(_ref2) {
106
120
  var _sameEdge = "".concat(axis, "-").concat(edge);
107
121
  var _oppositeEdge = "".concat(axis, "-").concat(flippedEdge);
108
122
  var _flipKeyword = axis === 'block' ? 'flip-block' : 'flip-inline';
123
+ var flipCrossKeyword = axis === 'block' ? 'flip-inline' : 'flip-block';
124
+ // Try same-edge shifts first; two flip entries cover single-edge then corner overflow.
109
125
  // Legacy Popper fallbacks for e.g. bottom-start: [bottom, bottom-end, top-start, top, top-end, auto].
110
- // Include same-edge centered so the browser can try center when start/end would overflow.
111
- return ["".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _sameEdge, "".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end'), _flipKeyword, "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _oppositeEdge, "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end')].join(', ');
126
+ return ["".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _sameEdge, "".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end'), _flipKeyword, "".concat(_flipKeyword, " ").concat(flipCrossKeyword), "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _oppositeEdge, "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end')].join(', ');
112
127
  }
113
128
  var sameEdge = "".concat(axis, "-").concat(edge);
114
129
  var oppositeEdge = "".concat(axis, "-").concat(flippedEdge);
@@ -313,6 +328,12 @@ function useAnchorPosition(_ref5) {
313
328
  property: 'inset',
314
329
  value: 'auto'
315
330
  },
331
+ // Ensures the popup's margin box overflows the viewport when its span region is too
332
+ // narrow, triggering position-try-fallbacks correctly rather than wrapping content.
333
+ {
334
+ property: 'min-inline-size',
335
+ value: 'max-content'
336
+ },
316
337
  // Main-axis gap between the popover and its trigger
317
338
  {
318
339
  property: gap.property,
@@ -79,6 +79,20 @@ export function placementToPositionArea({
79
79
  * For aligned placements (start/end), includes the same-edge centered
80
80
  * position as fallbacks so the browser can try centering when the aligned
81
81
  * position would overflow, before flipping to the opposite edge.
82
+ *
83
+ * **Why `flip-inline flip-block` (or `flip-block flip-inline`) is included:**
84
+ *
85
+ * When a popup is in a viewport corner (e.g. inline-end align-end at the
86
+ * top-right), flipping only the primary axis (`flip-inline`) moves the popup
87
+ * to the left but keeps the same cross-axis span direction (e.g. upward),
88
+ * which is still off-screen. The combined keyword `flip-inline flip-block`
89
+ * mirrors the popup diagonally — it flips BOTH the position-area AND both
90
+ * inline and block margins simultaneously, placing the popup at the
91
+ * diagonally opposite corner where there is always space.
92
+ *
93
+ * Named `position-area` entries (e.g. `inline-start span-block-end`) in
94
+ * `position-try-fallbacks` do NOT update margins, so only the `flip-*`
95
+ * tactic keywords correctly flip the gap margin along with the position.
82
96
  */
83
97
  export function placementToTryFallbacks({
84
98
  placement
@@ -96,9 +110,10 @@ export function placementToTryFallbacks({
96
110
  const sameEdge = `${axis}-${edge}`;
97
111
  const oppositeEdge = `${axis}-${flippedEdge}`;
98
112
  const flipKeyword = axis === 'block' ? 'flip-block' : 'flip-inline';
113
+ const flipCrossKeyword = axis === 'block' ? 'flip-inline' : 'flip-block';
114
+ // Try same-edge shifts first; two flip entries cover single-edge then corner overflow.
99
115
  // Legacy Popper fallbacks for e.g. bottom-start: [bottom, bottom-end, top-start, top, top-end, auto].
100
- // Include same-edge centered so the browser can try center when start/end would overflow.
101
- return [`${sameEdge} span-${crossAxis}-${align === 'start' ? 'end' : 'start'}`, sameEdge, `${sameEdge} span-${crossAxis}-${align === 'start' ? 'start' : 'end'}`, flipKeyword, `${oppositeEdge} span-${crossAxis}-${align === 'start' ? 'end' : 'start'}`, oppositeEdge, `${oppositeEdge} span-${crossAxis}-${align === 'start' ? 'start' : 'end'}`].join(', ');
116
+ return [`${sameEdge} span-${crossAxis}-${align === 'start' ? 'end' : 'start'}`, sameEdge, `${sameEdge} span-${crossAxis}-${align === 'start' ? 'start' : 'end'}`, flipKeyword, `${flipKeyword} ${flipCrossKeyword}`, `${oppositeEdge} span-${crossAxis}-${align === 'start' ? 'end' : 'start'}`, oppositeEdge, `${oppositeEdge} span-${crossAxis}-${align === 'start' ? 'start' : 'end'}`].join(', ');
102
117
  }
103
118
  const sameEdge = `${axis}-${edge}`;
104
119
  const oppositeEdge = `${axis}-${flippedEdge}`;
@@ -308,6 +323,12 @@ export function useAnchorPosition({
308
323
  property: 'inset',
309
324
  value: 'auto'
310
325
  },
326
+ // Ensures the popup's margin box overflows the viewport when its span region is too
327
+ // narrow, triggering position-try-fallbacks correctly rather than wrapping content.
328
+ {
329
+ property: 'min-inline-size',
330
+ value: 'max-content'
331
+ },
311
332
  // Main-axis gap between the popover and its trigger
312
333
  {
313
334
  property: gap.property,
@@ -77,6 +77,20 @@ export function placementToPositionArea(_ref) {
77
77
  * For aligned placements (start/end), includes the same-edge centered
78
78
  * position as fallbacks so the browser can try centering when the aligned
79
79
  * position would overflow, before flipping to the opposite edge.
80
+ *
81
+ * **Why `flip-inline flip-block` (or `flip-block flip-inline`) is included:**
82
+ *
83
+ * When a popup is in a viewport corner (e.g. inline-end align-end at the
84
+ * top-right), flipping only the primary axis (`flip-inline`) moves the popup
85
+ * to the left but keeps the same cross-axis span direction (e.g. upward),
86
+ * which is still off-screen. The combined keyword `flip-inline flip-block`
87
+ * mirrors the popup diagonally — it flips BOTH the position-area AND both
88
+ * inline and block margins simultaneously, placing the popup at the
89
+ * diagonally opposite corner where there is always space.
90
+ *
91
+ * Named `position-area` entries (e.g. `inline-start span-block-end`) in
92
+ * `position-try-fallbacks` do NOT update margins, so only the `flip-*`
93
+ * tactic keywords correctly flip the gap margin along with the position.
80
94
  */
81
95
  export function placementToTryFallbacks(_ref2) {
82
96
  var placement = _ref2.placement;
@@ -92,9 +106,10 @@ export function placementToTryFallbacks(_ref2) {
92
106
  var _sameEdge = "".concat(axis, "-").concat(edge);
93
107
  var _oppositeEdge = "".concat(axis, "-").concat(flippedEdge);
94
108
  var _flipKeyword = axis === 'block' ? 'flip-block' : 'flip-inline';
109
+ var flipCrossKeyword = axis === 'block' ? 'flip-inline' : 'flip-block';
110
+ // Try same-edge shifts first; two flip entries cover single-edge then corner overflow.
95
111
  // Legacy Popper fallbacks for e.g. bottom-start: [bottom, bottom-end, top-start, top, top-end, auto].
96
- // Include same-edge centered so the browser can try center when start/end would overflow.
97
- return ["".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _sameEdge, "".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end'), _flipKeyword, "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _oppositeEdge, "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end')].join(', ');
112
+ return ["".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _sameEdge, "".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end'), _flipKeyword, "".concat(_flipKeyword, " ").concat(flipCrossKeyword), "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _oppositeEdge, "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end')].join(', ');
98
113
  }
99
114
  var sameEdge = "".concat(axis, "-").concat(edge);
100
115
  var oppositeEdge = "".concat(axis, "-").concat(flippedEdge);
@@ -299,6 +314,12 @@ export function useAnchorPosition(_ref5) {
299
314
  property: 'inset',
300
315
  value: 'auto'
301
316
  },
317
+ // Ensures the popup's margin box overflows the viewport when its span region is too
318
+ // narrow, triggering position-try-fallbacks correctly rather than wrapping content.
319
+ {
320
+ property: 'min-inline-size',
321
+ value: 'max-content'
322
+ },
302
323
  // Main-axis gap between the popover and its trigger
303
324
  {
304
325
  property: gap.property,
@@ -53,6 +53,20 @@ export declare function placementToPositionArea({ placement }: {
53
53
  * For aligned placements (start/end), includes the same-edge centered
54
54
  * position as fallbacks so the browser can try centering when the aligned
55
55
  * position would overflow, before flipping to the opposite edge.
56
+ *
57
+ * **Why `flip-inline flip-block` (or `flip-block flip-inline`) is included:**
58
+ *
59
+ * When a popup is in a viewport corner (e.g. inline-end align-end at the
60
+ * top-right), flipping only the primary axis (`flip-inline`) moves the popup
61
+ * to the left but keeps the same cross-axis span direction (e.g. upward),
62
+ * which is still off-screen. The combined keyword `flip-inline flip-block`
63
+ * mirrors the popup diagonally — it flips BOTH the position-area AND both
64
+ * inline and block margins simultaneously, placing the popup at the
65
+ * diagonally opposite corner where there is always space.
66
+ *
67
+ * Named `position-area` entries (e.g. `inline-start span-block-end`) in
68
+ * `position-try-fallbacks` do NOT update margins, so only the `flip-*`
69
+ * tactic keywords correctly flip the gap margin along with the position.
56
70
  */
57
71
  export declare function placementToTryFallbacks({ placement }: {
58
72
  placement: TPlacementOptions;
@@ -53,6 +53,20 @@ export declare function placementToPositionArea({ placement }: {
53
53
  * For aligned placements (start/end), includes the same-edge centered
54
54
  * position as fallbacks so the browser can try centering when the aligned
55
55
  * position would overflow, before flipping to the opposite edge.
56
+ *
57
+ * **Why `flip-inline flip-block` (or `flip-block flip-inline`) is included:**
58
+ *
59
+ * When a popup is in a viewport corner (e.g. inline-end align-end at the
60
+ * top-right), flipping only the primary axis (`flip-inline`) moves the popup
61
+ * to the left but keeps the same cross-axis span direction (e.g. upward),
62
+ * which is still off-screen. The combined keyword `flip-inline flip-block`
63
+ * mirrors the popup diagonally — it flips BOTH the position-area AND both
64
+ * inline and block margins simultaneously, placing the popup at the
65
+ * diagonally opposite corner where there is always space.
66
+ *
67
+ * Named `position-area` entries (e.g. `inline-start span-block-end`) in
68
+ * `position-try-fallbacks` do NOT update margins, so only the `flip-*`
69
+ * tactic keywords correctly flip the gap margin along with the position.
56
70
  */
57
71
  export declare function placementToTryFallbacks({ placement }: {
58
72
  placement: TPlacementOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/top-layer",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Low-level layering primitives based on popover and dialog",
5
5
  "homepage": "https://atlassian.design/components/top-layer",
6
6
  "repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",