@atlaskit/top-layer 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +4 -2
  3. package/dist/cjs/arrow/index.js +2 -2
  4. package/dist/cjs/dialog/dialog-content.js +6 -1
  5. package/dist/cjs/entry-points/use-width-from-anchor.js +12 -0
  6. package/dist/cjs/focus/focus.js +4 -4
  7. package/dist/cjs/internal/combine.js +2 -2
  8. package/dist/cjs/internal/use-anchor-position.js +25 -7
  9. package/dist/cjs/internal/use-animated-visibility.js +3 -3
  10. package/dist/cjs/internal/use-focus-wrap.js +7 -7
  11. package/dist/cjs/internal/use-initial-focus.js +1 -1
  12. package/dist/cjs/internal/use-width-from-anchor.js +107 -0
  13. package/dist/cjs/popover/popover.js +12 -10
  14. package/dist/cjs/popup/popup-content.js +10 -37
  15. package/dist/cjs/popup/popup-trigger.js +2 -2
  16. package/dist/cjs/use-simple-light-dismiss/index.js +1 -1
  17. package/dist/es2019/arrow/index.js +2 -2
  18. package/dist/es2019/dialog/dialog-content.js +6 -1
  19. package/dist/es2019/entry-points/use-width-from-anchor.js +1 -0
  20. package/dist/es2019/focus/focus.js +1 -1
  21. package/dist/es2019/internal/combine.js +1 -1
  22. package/dist/es2019/internal/use-anchor-position.js +13 -7
  23. package/dist/es2019/internal/use-animated-visibility.js +3 -3
  24. package/dist/es2019/internal/use-focus-wrap.js +7 -7
  25. package/dist/es2019/internal/use-initial-focus.js +1 -1
  26. package/dist/es2019/internal/use-width-from-anchor.js +103 -0
  27. package/dist/es2019/popover/popover.js +12 -10
  28. package/dist/es2019/popup/popup-content.js +9 -36
  29. package/dist/es2019/popup/popup-trigger.js +2 -2
  30. package/dist/es2019/use-simple-light-dismiss/index.js +1 -1
  31. package/dist/esm/arrow/index.js +2 -2
  32. package/dist/esm/dialog/dialog-content.js +6 -1
  33. package/dist/esm/entry-points/use-width-from-anchor.js +1 -0
  34. package/dist/esm/focus/focus.js +4 -4
  35. package/dist/esm/internal/combine.js +2 -2
  36. package/dist/esm/internal/use-anchor-position.js +25 -7
  37. package/dist/esm/internal/use-animated-visibility.js +3 -3
  38. package/dist/esm/internal/use-focus-wrap.js +7 -7
  39. package/dist/esm/internal/use-initial-focus.js +1 -1
  40. package/dist/esm/internal/use-width-from-anchor.js +102 -0
  41. package/dist/esm/popover/popover.js +12 -10
  42. package/dist/esm/popup/popup-content.js +10 -37
  43. package/dist/esm/popup/popup-trigger.js +2 -2
  44. package/dist/esm/use-simple-light-dismiss/index.js +1 -1
  45. package/dist/types/entry-points/use-width-from-anchor.d.ts +1 -0
  46. package/dist/types/internal/use-anchor-position.d.ts +1 -1
  47. package/dist/types/internal/use-focus-wrap.d.ts +1 -1
  48. package/dist/types/internal/use-width-from-anchor.d.ts +20 -0
  49. package/dist/types/popover/popover.d.ts +3 -1
  50. package/dist/types/popover/types.d.ts +13 -0
  51. package/dist/types/popup/popup-content.d.ts +1 -1
  52. package/dist/types/popup/popup-trigger.d.ts +1 -1
  53. package/dist/types/popup/types.d.ts +8 -8
  54. package/dist/types/use-simple-light-dismiss/index.d.ts +3 -3
  55. package/dist/types-ts4.5/entry-points/use-width-from-anchor.d.ts +1 -0
  56. package/dist/types-ts4.5/internal/use-anchor-position.d.ts +1 -1
  57. package/dist/types-ts4.5/internal/use-focus-wrap.d.ts +1 -1
  58. package/dist/types-ts4.5/internal/use-width-from-anchor.d.ts +20 -0
  59. package/dist/types-ts4.5/popover/popover.d.ts +3 -1
  60. package/dist/types-ts4.5/popover/types.d.ts +13 -0
  61. package/dist/types-ts4.5/popup/popup-content.d.ts +1 -1
  62. package/dist/types-ts4.5/popup/popup-trigger.d.ts +1 -1
  63. package/dist/types-ts4.5/popup/types.d.ts +8 -8
  64. package/dist/types-ts4.5/use-simple-light-dismiss/index.d.ts +3 -3
  65. package/package.json +2 -2
  66. package/use-width-from-anchor/package.json +17 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,117 @@
1
1
  # @atlaskit/top-layer
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`43e486948865a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/43e486948865a) -
8
+ Added a new `useWidthFromAnchor` hook exported from `@atlaskit/top-layer/use-width-from-anchor`,
9
+ for setting the width of a popover element relative to its anchor trigger. Supports three modes:
10
+ `'none'` (no width constraint), `'match-anchor'` (popover matches anchor width exactly), and
11
+ `'min-anchor'` (popover is at least as wide as the anchor).
12
+
13
+ Renamed the `width` prop on `Popup.Content` to `widthFromAnchor`, and updated its type from a
14
+ string enum `'content' | 'trigger' | 'min-trigger'` to `'none' | 'match-anchor' | 'min-anchor'` to
15
+ match the new hook.
16
+
17
+ ## 0.6.3
18
+
19
+ ### Patch Changes
20
+
21
+ - [`7250582895c0b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/7250582895c0b) -
22
+ Top-layer adoption work behind the `platform-dst-top-layer` feature flag. Public adopter APIs are
23
+ intentionally kept narrow while the top-layer API surface settles, with one exception called out
24
+ below.
25
+
26
+ Highlights:
27
+ - Pass the full `[along, away]` legacy popper offset through to the new top-layer
28
+ `placement.offset` API (via `fromLegacyPlacement`). Previously only the `away` axis was
29
+ forwarded, which dropped the `along` offset for consumers of `Popup`, `PopupSelect`,
30
+ `Spotlight`, and `Tooltip` when `platform-dst-top-layer` is enabled.
31
+ - Fix broken import of `dialogHeight` and `dialogWidth` from the removed utils module in
32
+ `@atlaskit/modal-dialog`.
33
+
34
+ Public API:
35
+ - **`@atlaskit/tooltip`** (`minor`): add an optional `testId?: string` field to `TriggerProps`.
36
+ This is additive (no existing prop changes shape). Required because `@atlaskit/button/new` (and
37
+ other `Pressable`-backed primitives) overwrite `data-testid` from spread, so the legacy
38
+ `(triggerProps as any)['data-testid']` workaround is silently absorbed by those consumers. A
39
+ typed `testId` field flows through their own `testId` destructure instead, restoring
40
+ `data-testid` propagation onto the rendered trigger element.
41
+ - **`@atlaskit/popup`**, **`@atlaskit/dropdown-menu`** (`patch`): no public type changes. Wider
42
+ `aria-haspopup` unions that the FF-on path produces are bridged at the package boundary into
43
+ `@atlaskit/top-layer` with localised `FUDGE(top-layer-api)` casts, documented in
44
+ `packages/design-system/top-layer/notes/decisions/migration-roadmap.md` ("Open API decisions
45
+ deferred to a follow-up PR"). They will be widened in a follow-up `minor` PR once the top-layer
46
+ API is committed.
47
+ - **`@atlaskit/modal-dialog`**, **`@atlaskit/select`**, **`@atlaskit/spotlight`**
48
+ (`patch`/`minor`): no public type changes; bug fixes only.
49
+
50
+ Merge-readiness fixes (FF-on test wiring + adopter behavior):
51
+ - **`@atlaskit/popup`** (`minor`): wire the compositional `PopupContent` to delegate to
52
+ `PopupContentTopLayer` when `platform-dst-top-layer` is enabled. Previously only the legacy
53
+ `Popup` component had the FF branch, leaving consumers of the compositional API on the legacy
54
+ popper path.
55
+ - **`@atlaskit/select`** (`minor`): add an `onClick` handler to the `PopupSelect` top-layer
56
+ trigger so clicks open/close the menu (mirrors the legacy global click handler in
57
+ `popup-select.tsx`). Add explicit Escape handling on the menu's `onKeyDown` so the menu closes
58
+ and focus returns to the trigger.
59
+ - **`@atlaskit/top-layer`** (`patch`): the `<dialog>` rendered by the Dialog primitive now sets
60
+ `aria-modal="true"` explicitly. Modern browsers infer modal semantics from `.showModal()` but
61
+ some assistive tech still keys off the explicit attribute.
62
+ - **`@atlaskit/top-layer`** (`patch`): guard `use-anchor-positioning` against environments where
63
+ `ResizeObserver` is not defined (e.g. jest's `node` environment, used by the post-office test
64
+ suite). The observer is used to wait for the popover's first valid layout before measuring;
65
+ consumers in non-DOM jest environments now get a no-op observer and the scroll/resize listeners
66
+ still apply if the host environment polyfills `showPopover`. Real browsers always have
67
+ `ResizeObserver`.
68
+ - **`@atlaskit/modal-dialog`** (`patch`): on the FF-on path, drop the `tabIndex={-1}` (and unused
69
+ `:focus-visible` outline) from the modal content wrapper. The native `<dialog>.showModal()`
70
+ focus-delegate algorithm picks the first focusable descendant (including `tabindex=-1`), and the
71
+ wrapper was hijacking initial focus from the close button. Also honor `shouldReturnFocus={ref}`
72
+ on the FF-on path (an unmount-cleanup focuses the ref after `dialog.close()` so it overrides the
73
+ browser's automatic return-to-trigger). Boolean `shouldReturnFocus={false}` is not yet honored
74
+ on the FF-on path — see `top-layer/notes/merge-blockers.md`.
75
+ - **`@atlaskit/datetime-picker`** (`patch`): on the FF-on path, set `mode="manual"` on the
76
+ `Popup.Content` rendered by both `internal/menu-top-layer.tsx` (date-picker calendar) and
77
+ `internal/fixed-layer-menu-top-layer.tsx` (time-picker menu). With the default `mode="auto"`,
78
+ the same click event that opens the menu (which targets the react-select combobox input —
79
+ outside the popover element) bubbles to the browser's native popover light-dismiss handler and
80
+ immediately closes the menu. react-select / DateTimePicker already own outside-click and Esc
81
+ dismissal via their own state, so opting out of the native auto-dismiss is the correct
82
+ integration. Also extend the existing Esc → trigger-focus restoration in
83
+ `components/date-picker.tsx` to the FF-on path (manual mode disables the browser's built-in
84
+ focus return, and the legacy code path was already handling this for itself behind an FF
85
+ negation).
86
+ - **`@atlaskit/popup`** (no public API change): no source changes — only FF-on Playwright
87
+ spec/example fixes drove the suite from 21/3/2 to 27/0/0. Notable: the two `test.fixme`'d
88
+ nested-popover cases were not browser limitations; `popover="auto"` chains correctly via DOM
89
+ ancestry (the original fixmes had the wrong testId selector). Added `testId` props to two
90
+ examples (`16-popup-with-a11y-props`, `18-should-fit-container`) so default-shape tests can
91
+ reach the trigger.
92
+ - Test alignment for FF-on Playwright suites across `popup`, `select`, `datetime-picker`,
93
+ `inline-dialog`, `inline-message`, and `modal-dialog`: selector updates to match the new
94
+ top-layer testId convention (`${testId}--content`, `[role="dialog"][aria-label="calendar"]`),
95
+ per-spec `skipAxeCheck()` for example-level color-contrast violations unrelated to the
96
+ migration, and focus assertions adjusted to match native `<dialog>` / `Popup.Content` auto-focus
97
+ semantics (focus lands on the first focusable child, not the dialog container itself).
98
+ - **`@atlassian/capacity-planning-capacity-graph`**, **`@atlaskit/color-picker`**,
99
+ **`@atlassian/timeline-table`**, **`@atlassian/global-side-navigation`** (`patch`): scope `fg`
100
+ mocks in unit tests so `platform-dst-top-layer` returns `false`. JSDOM does not implement the
101
+ native Popover API (`showPopover`/`hidePopover`/`toggle` events), so leaving the gate ON in unit
102
+ tests caused popover content to remain in the DOM after close and broke close-behaviour
103
+ assertions. Browser coverage for the FF-on path is provided by the Playwright suites listed
104
+ above.
105
+ - **`@atlaskit/dropdown-menu`** (no public API change): test/example-only fixes for the FF-on
106
+ Playwright suite. Added `role="menuitem"` to the nested-trigger `ButtonItem` in
107
+ `examples/93-testing-nested-keyboard-navigation-top-layer.tsx` to satisfy axe's
108
+ `aria-required-children` rule on the parent menu. Added a `test.beforeEach(skipAxeCheck)` to
109
+ `dropdown-menu.spec.tsx` (FF-on suite) for example-level `color-contrast` violations on the
110
+ pre-existing `color.text.selected`/`color.background.selected` token pair (3.91:1). Replaced a
111
+ deadlocking `await expect(moveItem).not.toBeFocused()` pre-open assertion (Playwright's
112
+ auto-wait blocks 5s on the absent element) with `await expect(moveItem).not.toBeVisible()`.
113
+ Suite result: 22/22 passing.
114
+
3
115
  ## 0.6.2
4
116
 
5
117
  ### Patch Changes
package/README.md CHANGED
@@ -129,7 +129,7 @@ Browsers without `@starting-style` support will show/hide elements instantly —
129
129
 
130
130
  Browser tests in `__tests__/playwright/` verify the following WCAG 2.2 success criteria. See
131
131
  [`notes/goals/accessibility-criteria.md`](notes/goals/accessibility-criteria.md) and
132
- [`notes/audits/accessibility-audit-report.md`](notes/audits/accessibility-audit-report.md) for
132
+ [`notes/decisions/accessibility-audit-report.md`](notes/decisions/accessibility-audit-report.md) for
133
133
  details.
134
134
 
135
135
  | A11y criterion | Browser test |
@@ -150,4 +150,6 @@ details.
150
150
  ## Documentation
151
151
 
152
152
  See [`notes/`](notes/) for project documentation, architecture decisions, migration records, and
153
- audit reports. The [`notes/README.md`](notes/README.md) provides a full index.
153
+ audit reports. The [`notes/README.md`](notes/README.md) provides a full index, including **current
154
+ migration status** and links to
155
+ [`notes/decisions/migration-roadmap.md`](notes/decisions/migration-roadmap.md).
@@ -21,7 +21,7 @@ var _useAnchorPosition = require("../internal/use-anchor-position");
21
21
  *
22
22
  * Constraints:
23
23
  * - `box-shadow` must be `none` (interferes with `clip-path: inset() margin-box`)
24
- * - `::before` and `::after` are consumed — consumers cannot use them
24
+ * - `::before` and `::after` are consumed - consumers cannot use them
25
25
  * - Arrow pseudo-elements use `background: inherit` from the popover element
26
26
  *
27
27
  * @see https://goulet.dev/posts/tooltip-with-popover-and-anchor-positioning/
@@ -46,7 +46,7 @@ function positionAreaToRuleName(_ref) {
46
46
  * that change both `position-area` and `margin` when flipping.
47
47
  *
48
48
  * This is required for the arrow technique because built-in keywords
49
- * (`flip-block`, `flip-inline`) only flip `position-area` — they do
49
+ * (`flip-block`, `flip-inline`) only flip `position-area` - they do
50
50
  * not update the margin direction needed to reveal the correct arrow.
51
51
  */
52
52
  function getTryFallbacks(_ref2) {
@@ -128,7 +128,12 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
128
128
  var escapedId = CSS.escape(dialogId);
129
129
  return /*#__PURE__*/_react.default.createElement("dialog", (0, _extends2.default)({
130
130
  ref: combinedRef,
131
- id: dialogId,
131
+ id: dialogId
132
+ // Modern browsers infer modal semantics from `.showModal()` but
133
+ // some assistive tech still keys off `aria-modal="true"`. Setting
134
+ // it explicitly is recommended by WAI-ARIA APG dialog pattern.
135
+ ,
136
+ "aria-modal": "true",
132
137
  "aria-label": label,
133
138
  "aria-labelledby": label ? undefined : labelledBy,
134
139
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "useWidthFromAnchor", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _useWidthFromAnchor.useWidthFromAnchor;
10
+ }
11
+ });
12
+ var _useWidthFromAnchor = require("../internal/use-width-from-anchor");
@@ -46,10 +46,10 @@ var selectors = function () {
46
46
  function getFocusables(_ref) {
47
47
  var container = _ref.container,
48
48
  filter = _ref.filter;
49
- return Array.from(container.querySelectorAll(selectors.focusable)).filter(function (el) {
50
- return el instanceof HTMLElement;
51
- }).filter(function (el) {
52
- return !filter || filter(el, container);
49
+ return Array.from(container.querySelectorAll(selectors.focusable)).filter(function (element) {
50
+ return element instanceof HTMLElement;
51
+ }).filter(function (element) {
52
+ return !filter || filter(element, container);
53
53
  });
54
54
  }
55
55
 
@@ -22,8 +22,8 @@ function combine() {
22
22
  cleanupFns[_key] = arguments[_key];
23
23
  }
24
24
  return function cleanup() {
25
- cleanupFns.forEach(function (fn) {
26
- return fn === null || fn === void 0 ? void 0 : fn();
25
+ cleanupFns.forEach(function (callback) {
26
+ return callback === null || callback === void 0 ? void 0 : callback();
27
27
  });
28
28
  };
29
29
  }
@@ -13,6 +13,8 @@ Object.defineProperty(exports, "getPlacement", {
13
13
  exports.placementToPositionArea = placementToPositionArea;
14
14
  exports.placementToTryFallbacks = placementToTryFallbacks;
15
15
  exports.useAnchorPosition = useAnchorPosition;
16
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
17
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
16
18
  var _react = require("react");
17
19
  var _bindEventListener = require("bind-event-listener");
18
20
  var _rafSchd = _interopRequireDefault(require("raf-schd"));
@@ -328,12 +330,6 @@ function useAnchorPosition(_ref5) {
328
330
  property: 'inset',
329
331
  value: 'auto'
330
332
  },
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
- },
337
333
  // Main-axis gap between the popover and its trigger
338
334
  {
339
335
  property: gap.property,
@@ -494,7 +490,29 @@ function useAnchorPosition(_ref5) {
494
490
  // ResizeObserver fires once the browser has real dimensions.
495
491
  // Self-disconnects after one valid measurement; ongoing
496
492
  // scroll/resize is handled by the window listeners below.
497
- var resizeObserver = new ResizeObserver(function () {
493
+ // `ResizeObserver` is missing in some non-DOM jest environments
494
+ // (e.g. post-office's `node` environment). Fall back to a no-op
495
+ // observer there — the scroll/resize listeners below still keep
496
+ // the popover positioned in the rare case the consumer also
497
+ // polyfilled `showPopover` but not `ResizeObserver`. Real
498
+ // browsers always have it.
499
+ var NoopResizeObserver = /*#__PURE__*/function () {
500
+ function NoopResizeObserver() {
501
+ (0, _classCallCheck2.default)(this, NoopResizeObserver);
502
+ }
503
+ return (0, _createClass2.default)(NoopResizeObserver, [{
504
+ key: "observe",
505
+ value: function observe() {}
506
+ }, {
507
+ key: "unobserve",
508
+ value: function unobserve() {}
509
+ }, {
510
+ key: "disconnect",
511
+ value: function disconnect() {}
512
+ }]);
513
+ }();
514
+ var ResizeObserverImpl = typeof ResizeObserver !== 'undefined' ? ResizeObserver : NoopResizeObserver;
515
+ var resizeObserver = new ResizeObserverImpl(function () {
498
516
  if (popover.offsetWidth > 0 && popover.offsetHeight > 0) {
499
517
  update();
500
518
  resizeObserver.disconnect();
@@ -114,8 +114,8 @@ function useAnimatedVisibility(_ref) {
114
114
  if (isOpen || !showChildren || !willAnimate) {
115
115
  return;
116
116
  }
117
- var el = elementRef.current;
118
- if (!el) {
117
+ var element = elementRef.current;
118
+ if (!element) {
119
119
  return;
120
120
  }
121
121
  function finish() {
@@ -129,7 +129,7 @@ function useAnimatedVisibility(_ref) {
129
129
  var fallbackId = setTimeout(finish, ((_preset$exitDurationM = preset === null || preset === void 0 ? void 0 : preset.exitDurationMs) !== null && _preset$exitDurationM !== void 0 ? _preset$exitDurationM : 0) + 50);
130
130
 
131
131
  // Primary: listen for the CSS transition to complete
132
- var unbind = (0, _bindEventListener.bind)(el, {
132
+ var unbind = (0, _bindEventListener.bind)(element, {
133
133
  type: 'transitionend',
134
134
  listener: finish,
135
135
  options: {
@@ -39,7 +39,7 @@ function roleRequiresFocusWrap(role) {
39
39
  * elements within the container using wrapping navigation.
40
40
  *
41
41
  * Light dismiss (Escape, click outside) continues to work natively
42
- * via `popover="auto"` — this hook only intercepts Tab.
42
+ * via `popover="auto"` - this hook only intercepts Tab.
43
43
  *
44
44
  * Always call this hook unconditionally. The listener is only
45
45
  * attached when the role requires focus wrapping, and is cleaned
@@ -49,11 +49,11 @@ function useFocusWrap(_ref) {
49
49
  var elementRef = _ref.elementRef,
50
50
  role = _ref.role;
51
51
  (0, _react.useEffect)(function () {
52
- var el = elementRef.current;
53
- if (!el || !roleRequiresFocusWrap(role)) {
52
+ var element = elementRef.current;
53
+ if (!element || !roleRequiresFocusWrap(role)) {
54
54
  return;
55
55
  }
56
- var unbind = (0, _bindEventListener.bind)(el, {
56
+ var unbind = (0, _bindEventListener.bind)(element, {
57
57
  type: 'keydown',
58
58
  listener: function listener(event) {
59
59
  if (event.key !== 'Tab') {
@@ -65,7 +65,7 @@ function useFocusWrap(_ref) {
65
65
  // Try to move to the next/previous focusable element relative
66
66
  // to the currently focused element within the container.
67
67
  var next = (0, _focus.getNextFocusable)({
68
- container: el,
68
+ container: element,
69
69
  direction: direction
70
70
  });
71
71
  if (next) {
@@ -77,9 +77,9 @@ function useFocusWrap(_ref) {
77
77
  // focused element isn't in the focusable list). Fall back to
78
78
  // the first or last focusable element depending on direction.
79
79
  var fallback = direction === 'forwards' ? (0, _focus.getFirstFocusable)({
80
- container: el
80
+ container: element
81
81
  }) : (0, _focus.getLastFocusable)({
82
- container: el
82
+ container: element
83
83
  });
84
84
  if (fallback) {
85
85
  fallback.focus();
@@ -18,7 +18,7 @@ var _focus = require("../focus/focus");
18
18
  * - no role / other roles: no focus movement
19
19
  *
20
20
  * Note: React 18's `autoFocus` JSX prop does NOT set the HTML `autofocus`
21
- * attribute — it only calls `.focus()` imperatively during commit, which
21
+ * attribute - it only calls `.focus()` imperatively during commit, which
22
22
  * has no effect on hidden popover content. Consumers must set the native
23
23
  * attribute via a ref callback (e.g. `node.setAttribute('autofocus', '')`).
24
24
  * React 19+ reflects the JSX prop as the HTML attribute automatically.
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useWidthFromAnchor = useWidthFromAnchor;
7
+ var _react = require("react");
8
+ var _setStyle = require("./set-style");
9
+ /**
10
+ * Sets the width of a popover element relative to its anchor element.
11
+ *
12
+ * - `'match-anchor'` → `width: anchor-size(width)` — popover matches anchor width exactly.
13
+ * - `'min-anchor'` → `min-width: anchor-size(width)` — popover is at least as wide as anchor,
14
+ * but can grow wider.
15
+ * - `'none'` → no explicit width is set (default behaviour)
16
+ *
17
+ * Uses CSS `anchor-size(width)` when the browser supports it, falling back to a
18
+ * one-off measurement of `anchor.offsetWidth`.
19
+ *
20
+ * Designed to be composed alongside `useAnchorPosition` for top-layer popovers.
21
+ */
22
+ function useWidthFromAnchor(_ref) {
23
+ var mode = _ref.mode,
24
+ popoverRef = _ref.popoverRef,
25
+ anchorRef = _ref.anchorRef;
26
+ (0, _react.useLayoutEffect)(function () {
27
+ var node = popoverRef.current;
28
+ if (!node) {
29
+ return;
30
+ }
31
+
32
+ // Feature-detect `anchor-size()` specifically (not just `anchor-name`)
33
+ // so the JS fallback fires correctly in browsers that support anchor
34
+ // positioning but not anchor sizing.
35
+ var supportsAnchorSize = typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('width', 'anchor-size(width)');
36
+ if (mode === 'match-anchor') {
37
+ // Set an exact width matching the anchor.
38
+ // `min-inline-size` is intentionally NOT set here — it would override the
39
+ // explicit width constraint, allowing the popover to grow past the anchor's width.
40
+ if (supportsAnchorSize) {
41
+ return (0, _setStyle.setStyle)({
42
+ el: node,
43
+ styles: [{
44
+ property: 'width',
45
+ value: 'anchor-size(width)'
46
+ }]
47
+ });
48
+ }
49
+
50
+ // JS fallback: one-off read of the anchor's width.
51
+ var anchor = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current;
52
+ if (anchor) {
53
+ return (0, _setStyle.setStyle)({
54
+ el: node,
55
+ styles: [{
56
+ property: 'width',
57
+ value: "".concat(anchor.offsetWidth, "px")
58
+ }]
59
+ });
60
+ }
61
+ return;
62
+ }
63
+
64
+ // For 'min-anchor' and 'none', apply `min-inline-size: max-content` to ensure
65
+ // correct position-try-fallbacks behaviour (popover margin box overflows the
66
+ // viewport when its span region is too narrow, rather than wrapping content).
67
+ var minInlineSizeStyle = {
68
+ property: 'min-inline-size',
69
+ value: 'max-content'
70
+ };
71
+ if (mode === 'min-anchor') {
72
+ // Set a minimum width matching the anchor. The popover can grow wider
73
+ // if its content requires it.
74
+ if (supportsAnchorSize) {
75
+ return (0, _setStyle.setStyle)({
76
+ el: node,
77
+ styles: [minInlineSizeStyle, {
78
+ property: 'min-width',
79
+ value: 'anchor-size(width)'
80
+ }]
81
+ });
82
+ }
83
+ var _anchor = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current;
84
+ if (_anchor) {
85
+ return (0, _setStyle.setStyle)({
86
+ el: node,
87
+ styles: [minInlineSizeStyle, {
88
+ property: 'min-width',
89
+ value: "".concat(_anchor.offsetWidth, "px")
90
+ }]
91
+ });
92
+ }
93
+
94
+ // No anchor available in JS fallback — still apply min-inline-size.
95
+ return (0, _setStyle.setStyle)({
96
+ el: node,
97
+ styles: [minInlineSizeStyle]
98
+ });
99
+ }
100
+
101
+ // 'none' mode: no explicit width, but still apply `min-inline-size: max-content`.
102
+ return (0, _setStyle.setStyle)({
103
+ el: node,
104
+ styles: [minInlineSizeStyle]
105
+ });
106
+ }, [mode, popoverRef, anchorRef]);
107
+ }
@@ -44,7 +44,9 @@ var styles = {
44
44
  * Unopinionated top-layer primitive.
45
45
  *
46
46
  * Manages only visibility (isOpen) and animation (animate). Has no knowledge
47
- * of positioning — compose with `useAnchorPosition` for anchor positioning.
47
+ * of positioning — compose with `useAnchorPosition` for anchor positioning
48
+ * and optionally `useWidthFromAnchor` for setting the width of a popover element
49
+ * relative to its anchor trigger.
48
50
  *
49
51
  * Visibility is driven by the `isOpen` prop:
50
52
  * - `isOpen={true}` → `showPopover()`, entry animation via `@starting-style`
@@ -126,21 +128,21 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
126
128
  // the browser processes light dismiss), read in the toggle handler.
127
129
  var closeReasonRef = (0, _react.useRef)('light-dismiss');
128
130
  var handleToggle = (0, _react.useCallback)(function (event) {
129
- var el = ownRef.current;
131
+ var element = ownRef.current;
130
132
  if (event.newState === 'open') {
131
- if (el) {
133
+ if (element) {
132
134
  onOpenChange === null || onOpenChange === void 0 || onOpenChange({
133
135
  isOpen: true,
134
- element: el
136
+ element: element
135
137
  });
136
138
  }
137
139
  return;
138
140
  }
139
141
  if (event.newState === 'closed') {
140
- if (el) {
142
+ if (element) {
141
143
  onOpenChange === null || onOpenChange === void 0 || onOpenChange({
142
144
  isOpen: false,
143
- element: el
145
+ element: element
144
146
  });
145
147
  }
146
148
 
@@ -158,13 +160,13 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
158
160
  }
159
161
  }, [onClose, onOpenChange]);
160
162
  (0, _react.useEffect)(function () {
161
- var el = ownRef.current;
162
- if (!el) {
163
+ var element = ownRef.current;
164
+ if (!element) {
163
165
  return;
164
166
  }
165
167
 
166
168
  // Capture-phase: tag Escape before the browser processes light dismiss.
167
- var unbindEscape = (0, _bindEventListener.bind)(el, {
169
+ var unbindEscape = (0, _bindEventListener.bind)(element, {
168
170
  type: 'keydown',
169
171
  listener: function listener(event) {
170
172
  if (event.key === 'Escape') {
@@ -175,7 +177,7 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
175
177
  capture: true
176
178
  }
177
179
  });
178
- var unbindToggle = (0, _bindEventListener.bind)(el, {
180
+ var unbindToggle = (0, _bindEventListener.bind)(element, {
179
181
  type: 'toggle',
180
182
  listener: handleToggle
181
183
  });
@@ -14,13 +14,14 @@ var _mergeRefs = _interopRequireDefault(require("@atlaskit/ds-lib/merge-refs"));
14
14
  var _roleTypes = require("../internal/role-types");
15
15
  var _useAnchorPosition = require("../internal/use-anchor-position");
16
16
  var _usePresetStyles = require("../internal/use-preset-styles");
17
+ var _useWidthFromAnchor = require("../internal/use-width-from-anchor");
17
18
  var _popover = require("../popover/popover");
18
19
  var _popupContext = require("./popup-context");
19
20
  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); }
20
21
  /**
21
22
  * Thin context wrapper that composes `Popover` + `useAnchorPosition`.
22
23
  *
23
- * Used inside the `<Popup>` compound — reads triggerRef, placement, isOpen,
24
+ * Used inside the `<Popup>` compound - reads triggerRef, placement, isOpen,
24
25
  * and onClose from context. For standalone usage (tooltip, spotlight, flag),
25
26
  * use `Popover` + `useAnchorPosition` directly.
26
27
  */
@@ -33,8 +34,8 @@ var PopupContent = exports.PopupContent = /*#__PURE__*/(0, _react.forwardRef)(fu
33
34
  animate = _ref.animate,
34
35
  arrow = _ref.arrow,
35
36
  mode = _ref.mode,
36
- _ref$width = _ref.width,
37
- width = _ref$width === void 0 ? 'content' : _ref$width,
37
+ _ref$widthFromAnchor = _ref.widthFromAnchor,
38
+ widthFromAnchor = _ref$widthFromAnchor === void 0 ? 'none' : _ref$widthFromAnchor,
38
39
  xcss = _ref.xcss,
39
40
  isOpenProp = _ref.isOpen,
40
41
  triggerRefProp = _ref.triggerRef,
@@ -97,7 +98,7 @@ var PopupContent = exports.PopupContent = /*#__PURE__*/(0, _react.forwardRef)(fu
97
98
  // ── Nested popover focus restoration fallback ──
98
99
  // The browser only captures `previouslyFocusedElement` for the
99
100
  // outermost popover="auto" in the stack. Inner (nested) popovers
100
- // have shouldRestoreFocus=false, so the browser won't restore
101
+ // have shouldRestoreFocus=false, so the browser will not restore
101
102
  // focus when they close.
102
103
  //
103
104
  // We detect this case by checking if focus is still inside the
@@ -140,39 +141,11 @@ var PopupContent = exports.PopupContent = /*#__PURE__*/(0, _react.forwardRef)(fu
140
141
  forceFallbackPositioning: forceFallbackPositioning,
141
142
  arrow: arrowPreset !== null && arrowPreset !== void 0 ? arrowPreset : undefined
142
143
  });
143
-
144
- // ── Width: match trigger ──
145
- // When width="trigger" or width="min-trigger", size the popover relative
146
- // to the trigger element. Uses CSS `anchor-size(width)` when supported,
147
- // falling back to a one-off measurement of the trigger's offsetWidth.
148
- (0, _react.useLayoutEffect)(function () {
149
- if (width === 'content') {
150
- return;
151
- }
152
- var node = popoverRef.current;
153
- if (!node) {
154
- return;
155
- }
156
- var cssProperty = width === 'trigger' ? 'width' : 'min-width';
157
- var anchorValue = 'anchor-size(width)';
158
-
159
- // Feature-detect `anchor-size()` specifically (not just `anchor-name`)
160
- // so the JS fallback fires correctly in browsers that support anchor
161
- // positioning but not anchor sizing.
162
- var supportsAnchorSize = typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('width', 'anchor-size(width)');
163
- if (supportsAnchorSize) {
164
- node.style.setProperty(cssProperty, anchorValue);
165
- } else {
166
- // JS fallback: one-off read of the trigger's width.
167
- var trigger = triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current;
168
- if (trigger) {
169
- node.style.setProperty(cssProperty, "".concat(trigger.offsetWidth, "px"));
170
- }
171
- }
172
- return function () {
173
- node.style.removeProperty(cssProperty);
174
- };
175
- }, [width, popoverRef, triggerRef]);
144
+ (0, _useWidthFromAnchor.useWidthFromAnchor)({
145
+ mode: widthFromAnchor,
146
+ popoverRef: popoverRef,
147
+ anchorRef: triggerRef
148
+ });
176
149
 
177
150
  // Merge: the popoverRef (shared with trigger + positioning) and the forwarded ref.
178
151
  var combinedRef = (0, _mergeRefs.default)([popoverRef, ref]);
@@ -26,7 +26,7 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
26
26
  * from opening.
27
27
  *
28
28
  * If you need Compiled `css` on the trigger, use `Popup.TriggerFunction`
29
- * instead — it hands you raw props via a render-prop and avoids
29
+ * instead - it hands you raw props via a render-prop and avoids
30
30
  * `cloneElement` entirely.
31
31
  */
32
32
  function PopupTrigger(_ref) {
@@ -40,7 +40,7 @@ function PopupTrigger(_ref) {
40
40
  var childOnClick = /*#__PURE__*/(0, _react.isValidElement)(children) ? children.props.onClick : undefined;
41
41
 
42
42
  // Use a ref to track the latest isOpen value so the click handler
43
- // doesn't need isOpen in its dependency array (which would recreate
43
+ // does not need isOpen in its dependency array (which would recreate
44
44
  // the handler on every open/close, breaking cloneElement identity).
45
45
  var isOpenRef = _react.default.useRef(isOpen);
46
46
  isOpenRef.current = isOpen;
@@ -26,7 +26,7 @@ function useSimpleLightDismiss(_ref) {
26
26
  var popoverRef = _ref.popoverRef,
27
27
  isOpen = _ref.isOpen,
28
28
  onClose = _ref.onClose;
29
- // Keep a stable ref to onClose so the effect doesn't re-bind on every render.
29
+ // Keep a stable ref to onClose so the effect does not re-bind on every render.
30
30
  var onCloseRef = (0, _react.useRef)(onClose);
31
31
  onCloseRef.current = onClose;
32
32
  (0, _react.useEffect)(function () {
@@ -14,7 +14,7 @@ import { getPlacement, placementToPositionArea } from '../internal/use-anchor-po
14
14
  *
15
15
  * Constraints:
16
16
  * - `box-shadow` must be `none` (interferes with `clip-path: inset() margin-box`)
17
- * - `::before` and `::after` are consumed — consumers cannot use them
17
+ * - `::before` and `::after` are consumed - consumers cannot use them
18
18
  * - Arrow pseudo-elements use `background: inherit` from the popover element
19
19
  *
20
20
  * @see https://goulet.dev/posts/tooltip-with-popover-and-anchor-positioning/
@@ -184,7 +184,7 @@ function positionAreaToRuleName({
184
184
  * that change both `position-area` and `margin` when flipping.
185
185
  *
186
186
  * This is required for the arrow technique because built-in keywords
187
- * (`flip-block`, `flip-inline`) only flip `position-area` — they do
187
+ * (`flip-block`, `flip-inline`) only flip `position-area` - they do
188
188
  * not update the margin direction needed to reveal the correct arrow.
189
189
  */
190
190
  function getTryFallbacks({
@@ -120,7 +120,12 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
120
120
  const escapedId = CSS.escape(dialogId);
121
121
  return /*#__PURE__*/React.createElement("dialog", _extends({
122
122
  ref: combinedRef,
123
- id: dialogId,
123
+ id: dialogId
124
+ // Modern browsers infer modal semantics from `.showModal()` but
125
+ // some assistive tech still keys off `aria-modal="true"`. Setting
126
+ // it explicitly is recommended by WAI-ARIA APG dialog pattern.
127
+ ,
128
+ "aria-modal": "true",
124
129
  "aria-label": label,
125
130
  "aria-labelledby": label ? undefined : labelledBy,
126
131
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop