@atlaskit/top-layer 0.8.0 → 0.9.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @atlaskit/top-layer
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`8e9ab10567283`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8e9ab10567283) -
8
+ When `platform-dst-top-layer` is enabled, the open layer observer registration now happens inside
9
+ the `Dialog` and `Popover` primitives directly, so `popup`, `modal-dialog`, and `tooltip` no
10
+ longer register with the open layer observer separately (avoiding double-counting).
11
+ - `Dialog` registers as `type: 'modal'`, with a no-op `onClose`, as there is no current use case
12
+ for programmatic close of modals via the open layer observer.
13
+ - `Popover` registers as `type: 'popup'` for interactive overlay roles (`dialog`, `alertdialog`,
14
+ `menu`, `listbox`, `tree`, `grid`) and omits `type` for other roles (e.g. `tooltip`).
15
+ - A new `programmatic` value has been added to `TPopoverCloseReason` to distinguish programmatic
16
+ closes, such as those from the open layer observer's `closeLayers()` calls.
17
+
3
18
  ## 0.8.0
4
19
 
5
20
  ### Minor Changes
@@ -104,6 +104,7 @@ var FADE_CSS = "\n[data-ds-popover-fade] {\n opacity: 0;\n transition:\n op
104
104
  * <Popup.Content animate={fade()} />
105
105
  * ```
106
106
  */
107
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
107
108
  function fade() {
108
109
  return {
109
110
  name: 'fade',
@@ -128,6 +129,7 @@ var SCALE_AND_FADE_CSS = "\n[data-ds-popover-scale-and-fade] {\n opacity: 0;\n
128
129
  * <Popup.Content animate={scaleAndFade()} />
129
130
  * ```
130
131
  */
132
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
131
133
  function scaleAndFade() {
132
134
  return {
133
135
  name: 'scale-and-fade',
@@ -162,6 +164,7 @@ var DIALOG_SLIDE_UP_AND_FADE_CSS = "\n[data-ds-dialog-slide-up-and-fade] {\n op
162
164
  * </Dialog>
163
165
  * ```
164
166
  */
167
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
165
168
  function dialogSlideUpAndFade(options) {
166
169
  var _options$distance2;
167
170
  var distance = (_options$distance2 = options === null || options === void 0 ? void 0 : options.distance) !== null && _options$distance2 !== void 0 ? _options$distance2 : 12;
@@ -190,6 +193,7 @@ var DIALOG_FADE_CSS = "\n[data-ds-dialog-fade] {\n opacity: 0;\n transition:\n
190
193
  * </Dialog>
191
194
  * ```
192
195
  */
196
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
193
197
  function dialogFade() {
194
198
  return {
195
199
  name: 'dialog-fade',
@@ -14,6 +14,8 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
14
14
  var _react = _interopRequireWildcard(require("react"));
15
15
  var _bindEventListener = require("bind-event-listener");
16
16
  var _mergeRefs = _interopRequireDefault(require("@atlaskit/ds-lib/merge-refs"));
17
+ var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
18
+ var _openLayerObserver = require("@atlaskit/layering/experimental/open-layer-observer");
17
19
  var _useAnimatedVisibility = require("../internal/use-animated-visibility");
18
20
  var _useFocusWrap = require("../internal/use-focus-wrap");
19
21
  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); }
@@ -71,6 +73,16 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
71
73
  role: 'dialog'
72
74
  });
73
75
 
76
+ // ── Open layer observer registration ──
77
+ // Notifies the open layer observer so app-coordination features
78
+ // (open-count subscriptions) work with top-layer dialogs.
79
+ (0, _openLayerObserver.useNotifyOpenLayerObserver)({
80
+ type: 'modal',
81
+ isOpen: isOpen,
82
+ // No-op: no current use case for programmatic close via OpenLayerObserver.
83
+ onClose: _noop.default
84
+ });
85
+
74
86
  // Show/hide the dialog in response to isOpen changes.
75
87
  (0, _react.useLayoutEffect)(function () {
76
88
  var dialog = ownRef.current;
@@ -69,6 +69,7 @@ function getFirstFocusable(_ref2) {
69
69
  /**
70
70
  * Returns the last focusable element within the container.
71
71
  */
72
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
72
73
  function getLastFocusable(_ref3) {
73
74
  var container = _ref3.container,
74
75
  filter = _ref3.filter;
@@ -86,6 +87,7 @@ function getLastFocusable(_ref3) {
86
87
  * Returns `null` if there is no focused element within the container or if
87
88
  * the focused element is not in the list of focusable elements.
88
89
  */
90
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
89
91
  function getNextFocusable(_ref4) {
90
92
  var _all$nextIndex;
91
93
  var container = _ref4.container,
@@ -108,6 +108,7 @@ function placementToPositionArea(_ref) {
108
108
  * `position-try-fallbacks` do NOT update margins, so only the `flip-*`
109
109
  * tactic keywords correctly flip the gap margin along with the position.
110
110
  */
111
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
111
112
  function placementToTryFallbacks(_ref2) {
112
113
  var placement = _ref2.placement;
113
114
  var _getPlacement2 = (0, _resolvePlacement.getPlacement)({
@@ -271,6 +272,7 @@ function isResolvedPlacementEqual(a, b) {
271
272
  * comparison is used internally so re-runs are skipped when the resolved
272
273
  * placement shape is unchanged.
273
274
  */
275
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
274
276
  function useAnchorPosition(_ref5) {
275
277
  var anchorRef = _ref5.anchorRef,
276
278
  popoverRef = _ref5.popoverRef,
@@ -172,4 +172,5 @@ function fromLegacyPlacement(_ref) {
172
172
 
173
173
  /**
174
174
  * The full mapping record, exposed for consumers that need to iterate or introspect.
175
- */
175
+ */
176
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
@@ -17,6 +17,7 @@ var _bindEventListener = require("bind-event-listener");
17
17
  var _mergeRefs = _interopRequireDefault(require("@atlaskit/ds-lib/merge-refs"));
18
18
  var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
19
19
  var _once = _interopRequireDefault(require("@atlaskit/ds-lib/once"));
20
+ var _openLayerObserver = require("@atlaskit/layering/experimental/open-layer-observer");
20
21
  var _useAnimatedVisibility = require("../internal/use-animated-visibility");
21
22
  var _useFocusWrap = require("../internal/use-focus-wrap");
22
23
  var _useInitialFocus = require("../internal/use-initial-focus");
@@ -40,6 +41,18 @@ var supportsPopoverHint = (0, _once.default)(function () {
40
41
  var styles = {
41
42
  root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28"
42
43
  };
44
+ /**
45
+ * ARIA roles on the Popover primitive that register with the open layer observer
46
+ * as LayerType 'popup'. These are interactive overlays that should be tracked
47
+ * and dismissed when closeLayers() is called (e.g. when the side nav resizes).
48
+ *
49
+ * Note on open layer observer types not included here:
50
+ * - 'modal' only ever comes from the Dialog primitive, not Popover.
51
+ * - Other layer types (e.g. 'tooltip') do not yet exist in LayerType because
52
+ * there is no current use case for distinguishing them in the observer.
53
+ */
54
+ var POPUP_ROLES = new Set(['menu', 'listbox', 'dialog', 'alertdialog', 'tree', 'grid']);
55
+
43
56
  /**
44
57
  * Unopinionated top-layer primitive.
45
58
  *
@@ -108,6 +121,24 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
108
121
  role: role
109
122
  });
110
123
 
124
+ // ── Open layer observer registration ──
125
+ // Notifies the open layer observer so app-coordination features
126
+ // (closeLayers(), open-count subscriptions) work with top-layer popovers.
127
+ //
128
+ // Only set type: 'popup' for roles that are interactive overlay popups.
129
+ // Passive/informational roles (tooltip, note, status, alert, log) and
130
+ // no-role cases do not map to LayerType 'popup', so type is left undefined.
131
+ var handleObserverClose = (0, _react.useCallback)(function () {
132
+ onClose({
133
+ reason: 'programmatic'
134
+ });
135
+ }, [onClose]);
136
+ (0, _openLayerObserver.useNotifyOpenLayerObserver)({
137
+ type: role && POPUP_ROLES.has(role) ? 'popup' : undefined,
138
+ isOpen: isOpen,
139
+ onClose: handleObserverClose
140
+ });
141
+
111
142
  // Hint mode fallback: "hint" downgrades to "auto" if unsupported.
112
143
  //
113
144
  // Decision (2026-03-17 audit): We keep "auto" as the fallback (not "manual")
@@ -23,6 +23,7 @@ var PopupProvider = exports.PopupProvider = PopupContext.Provider;
23
23
  *
24
24
  * Throws if called outside of a `<Popup>` compound component.
25
25
  */
26
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
26
27
  function usePopupContext() {
27
28
  var ctx = (0, _react.useContext)(PopupContext);
28
29
  if (ctx === null) {
@@ -37,6 +38,7 @@ function usePopupContext() {
37
38
  * Used by `PopupContent` to support standalone usage (e.g. tooltip)
38
39
  * where values are passed as props instead of coming from a compound component.
39
40
  */
41
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
40
42
  function useMaybePopupContext() {
41
43
  return (0, _react.useContext)(PopupContext);
42
44
  }
@@ -126,6 +126,7 @@ function isAtCurrentMenuLevel(element, container) {
126
126
  * });
127
127
  * ```
128
128
  */
129
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
129
130
  function useArrowNavigation(_ref) {
130
131
  var containerRef = _ref.containerRef,
131
132
  onClose = _ref.onClose,
@@ -152,6 +152,7 @@ const FADE_CSS = `
152
152
  * <Popup.Content animate={fade()} />
153
153
  * ```
154
154
  */
155
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
155
156
  export function fade() {
156
157
  return {
157
158
  name: 'fade',
@@ -206,6 +207,7 @@ const SCALE_AND_FADE_CSS = `
206
207
  * <Popup.Content animate={scaleAndFade()} />
207
208
  * ```
208
209
  */
210
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
209
211
  export function scaleAndFade() {
210
212
  return {
211
213
  name: 'scale-and-fade',
@@ -291,6 +293,7 @@ const DIALOG_SLIDE_UP_AND_FADE_CSS = `
291
293
  * </Dialog>
292
294
  * ```
293
295
  */
296
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
294
297
  export function dialogSlideUpAndFade(options) {
295
298
  var _options$distance2;
296
299
  const distance = (_options$distance2 = options === null || options === void 0 ? void 0 : options.distance) !== null && _options$distance2 !== void 0 ? _options$distance2 : 12;
@@ -366,6 +369,7 @@ const DIALOG_FADE_CSS = `
366
369
  * </Dialog>
367
370
  * ```
368
371
  */
372
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
369
373
  export function dialogFade() {
370
374
  return {
371
375
  name: 'dialog-fade',
@@ -5,6 +5,8 @@ import { ax, ix } from "@compiled/react/runtime";
5
5
  import React, { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useRef } from 'react';
6
6
  import { bind } from 'bind-event-listener';
7
7
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
8
+ import noop from '@atlaskit/ds-lib/noop';
9
+ import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
8
10
  import { useAnimatedVisibility } from '../internal/use-animated-visibility';
9
11
  import { useFocusWrap } from '../internal/use-focus-wrap';
10
12
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- dialog reset requires values not in cssMap's type union
@@ -63,6 +65,16 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
63
65
  role: 'dialog'
64
66
  });
65
67
 
68
+ // ── Open layer observer registration ──
69
+ // Notifies the open layer observer so app-coordination features
70
+ // (open-count subscriptions) work with top-layer dialogs.
71
+ useNotifyOpenLayerObserver({
72
+ type: 'modal',
73
+ isOpen,
74
+ // No-op: no current use case for programmatic close via OpenLayerObserver.
75
+ onClose: noop
76
+ });
77
+
66
78
  // Show/hide the dialog in response to isOpen changes.
67
79
  useLayoutEffect(() => {
68
80
  const dialog = ownRef.current;
@@ -59,6 +59,7 @@ export function getFirstFocusable({
59
59
  /**
60
60
  * Returns the last focusable element within the container.
61
61
  */
62
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
62
63
  export function getLastFocusable({
63
64
  container,
64
65
  filter
@@ -77,6 +78,7 @@ export function getLastFocusable({
77
78
  * Returns `null` if there is no focused element within the container or if
78
79
  * the focused element is not in the list of focusable elements.
79
80
  */
81
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
80
82
  export function getNextFocusable({
81
83
  container,
82
84
  direction,
@@ -94,6 +94,7 @@ export function placementToPositionArea({
94
94
  * `position-try-fallbacks` do NOT update margins, so only the `flip-*`
95
95
  * tactic keywords correctly flip the gap margin along with the position.
96
96
  */
97
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
97
98
  export function placementToTryFallbacks({
98
99
  placement
99
100
  }) {
@@ -265,6 +266,7 @@ function isResolvedPlacementEqual(a, b) {
265
266
  * comparison is used internally so re-runs are skipped when the resolved
266
267
  * placement shape is unchanged.
267
268
  */
269
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
268
270
  export function useAnchorPosition({
269
271
  anchorRef,
270
272
  popoverRef,
@@ -161,4 +161,5 @@ export function fromLegacyPlacement({
161
161
  /**
162
162
  * The full mapping record, exposed for consumers that need to iterate or introspect.
163
163
  */
164
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
164
165
  export { placementMapping };
@@ -7,6 +7,7 @@ import { bind } from 'bind-event-listener';
7
7
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
8
8
  import noop from '@atlaskit/ds-lib/noop';
9
9
  import once from '@atlaskit/ds-lib/once';
10
+ import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
10
11
  import { useAnimatedVisibility } from '../internal/use-animated-visibility';
11
12
  import { useFocusWrap } from '../internal/use-focus-wrap';
12
13
  import { useInitialFocus } from '../internal/use-initial-focus';
@@ -29,6 +30,18 @@ const supportsPopoverHint = once(() => {
29
30
  const styles = {
30
31
  root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28"
31
32
  };
33
+ /**
34
+ * ARIA roles on the Popover primitive that register with the open layer observer
35
+ * as LayerType 'popup'. These are interactive overlays that should be tracked
36
+ * and dismissed when closeLayers() is called (e.g. when the side nav resizes).
37
+ *
38
+ * Note on open layer observer types not included here:
39
+ * - 'modal' only ever comes from the Dialog primitive, not Popover.
40
+ * - Other layer types (e.g. 'tooltip') do not yet exist in LayerType because
41
+ * there is no current use case for distinguishing them in the observer.
42
+ */
43
+ const POPUP_ROLES = new Set(['menu', 'listbox', 'dialog', 'alertdialog', 'tree', 'grid']);
44
+
32
45
  /**
33
46
  * Unopinionated top-layer primitive.
34
47
  *
@@ -98,6 +111,24 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
98
111
  role
99
112
  });
100
113
 
114
+ // ── Open layer observer registration ──
115
+ // Notifies the open layer observer so app-coordination features
116
+ // (closeLayers(), open-count subscriptions) work with top-layer popovers.
117
+ //
118
+ // Only set type: 'popup' for roles that are interactive overlay popups.
119
+ // Passive/informational roles (tooltip, note, status, alert, log) and
120
+ // no-role cases do not map to LayerType 'popup', so type is left undefined.
121
+ const handleObserverClose = useCallback(() => {
122
+ onClose({
123
+ reason: 'programmatic'
124
+ });
125
+ }, [onClose]);
126
+ useNotifyOpenLayerObserver({
127
+ type: role && POPUP_ROLES.has(role) ? 'popup' : undefined,
128
+ isOpen,
129
+ onClose: handleObserverClose
130
+ });
131
+
101
132
  // Hint mode fallback: "hint" downgrades to "auto" if unsupported.
102
133
  //
103
134
  // Decision (2026-03-17 audit): We keep "auto" as the fallback (not "manual")
@@ -15,6 +15,7 @@ export const PopupProvider = PopupContext.Provider;
15
15
  *
16
16
  * Throws if called outside of a `<Popup>` compound component.
17
17
  */
18
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
18
19
  export function usePopupContext() {
19
20
  const ctx = useContext(PopupContext);
20
21
  if (ctx === null) {
@@ -29,6 +30,7 @@ export function usePopupContext() {
29
30
  * Used by `PopupContent` to support standalone usage (e.g. tooltip)
30
31
  * where values are passed as props instead of coming from a compound component.
31
32
  */
33
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
32
34
  export function useMaybePopupContext() {
33
35
  return useContext(PopupContext);
34
36
  }
@@ -119,6 +119,7 @@ export function isAtCurrentMenuLevel(element, container) {
119
119
  * });
120
120
  * ```
121
121
  */
122
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
122
123
  export function useArrowNavigation({
123
124
  containerRef,
124
125
  onClose,
@@ -95,6 +95,7 @@ var FADE_CSS = "\n[data-ds-popover-fade] {\n opacity: 0;\n transition:\n op
95
95
  * <Popup.Content animate={fade()} />
96
96
  * ```
97
97
  */
98
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
98
99
  export function fade() {
99
100
  return {
100
101
  name: 'fade',
@@ -119,6 +120,7 @@ var SCALE_AND_FADE_CSS = "\n[data-ds-popover-scale-and-fade] {\n opacity: 0;\n
119
120
  * <Popup.Content animate={scaleAndFade()} />
120
121
  * ```
121
122
  */
123
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
122
124
  export function scaleAndFade() {
123
125
  return {
124
126
  name: 'scale-and-fade',
@@ -153,6 +155,7 @@ var DIALOG_SLIDE_UP_AND_FADE_CSS = "\n[data-ds-dialog-slide-up-and-fade] {\n op
153
155
  * </Dialog>
154
156
  * ```
155
157
  */
158
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
156
159
  export function dialogSlideUpAndFade(options) {
157
160
  var _options$distance2;
158
161
  var distance = (_options$distance2 = options === null || options === void 0 ? void 0 : options.distance) !== null && _options$distance2 !== void 0 ? _options$distance2 : 12;
@@ -181,6 +184,7 @@ var DIALOG_FADE_CSS = "\n[data-ds-dialog-fade] {\n opacity: 0;\n transition:\n
181
184
  * </Dialog>
182
185
  * ```
183
186
  */
187
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
184
188
  export function dialogFade() {
185
189
  return {
186
190
  name: 'dialog-fade',
@@ -6,6 +6,8 @@ import { ax, ix } from "@compiled/react/runtime";
6
6
  import React, { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useRef } from 'react';
7
7
  import { bind } from 'bind-event-listener';
8
8
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
9
+ import noop from '@atlaskit/ds-lib/noop';
10
+ import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
9
11
  import { useAnimatedVisibility } from '../internal/use-animated-visibility';
10
12
  import { useFocusWrap } from '../internal/use-focus-wrap';
11
13
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- dialog reset requires values not in cssMap's type union
@@ -62,6 +64,16 @@ export var Dialog = /*#__PURE__*/forwardRef(function Dialog(_ref, ref) {
62
64
  role: 'dialog'
63
65
  });
64
66
 
67
+ // ── Open layer observer registration ──
68
+ // Notifies the open layer observer so app-coordination features
69
+ // (open-count subscriptions) work with top-layer dialogs.
70
+ useNotifyOpenLayerObserver({
71
+ type: 'modal',
72
+ isOpen: isOpen,
73
+ // No-op: no current use case for programmatic close via OpenLayerObserver.
74
+ onClose: noop
75
+ });
76
+
65
77
  // Show/hide the dialog in response to isOpen changes.
66
78
  useLayoutEffect(function () {
67
79
  var dialog = ownRef.current;
@@ -61,6 +61,7 @@ export function getFirstFocusable(_ref2) {
61
61
  /**
62
62
  * Returns the last focusable element within the container.
63
63
  */
64
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
64
65
  export function getLastFocusable(_ref3) {
65
66
  var container = _ref3.container,
66
67
  filter = _ref3.filter;
@@ -78,6 +79,7 @@ export function getLastFocusable(_ref3) {
78
79
  * Returns `null` if there is no focused element within the container or if
79
80
  * the focused element is not in the list of focusable elements.
80
81
  */
82
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
81
83
  export function getNextFocusable(_ref4) {
82
84
  var _all$nextIndex;
83
85
  var container = _ref4.container,
@@ -94,6 +94,7 @@ export function placementToPositionArea(_ref) {
94
94
  * `position-try-fallbacks` do NOT update margins, so only the `flip-*`
95
95
  * tactic keywords correctly flip the gap margin along with the position.
96
96
  */
97
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
97
98
  export function placementToTryFallbacks(_ref2) {
98
99
  var placement = _ref2.placement;
99
100
  var _getPlacement2 = getPlacement({
@@ -257,6 +258,7 @@ function isResolvedPlacementEqual(a, b) {
257
258
  * comparison is used internally so re-runs are skipped when the resolved
258
259
  * placement shape is unchanged.
259
260
  */
261
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
260
262
  export function useAnchorPosition(_ref5) {
261
263
  var anchorRef = _ref5.anchorRef,
262
264
  popoverRef = _ref5.popoverRef,
@@ -165,4 +165,5 @@ export function fromLegacyPlacement(_ref) {
165
165
  /**
166
166
  * The full mapping record, exposed for consumers that need to iterate or introspect.
167
167
  */
168
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
168
169
  export { placementMapping };
@@ -9,6 +9,7 @@ import { bind } from 'bind-event-listener';
9
9
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
10
10
  import noop from '@atlaskit/ds-lib/noop';
11
11
  import once from '@atlaskit/ds-lib/once';
12
+ import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
12
13
  import { useAnimatedVisibility } from '../internal/use-animated-visibility';
13
14
  import { useFocusWrap } from '../internal/use-focus-wrap';
14
15
  import { useInitialFocus } from '../internal/use-initial-focus';
@@ -31,6 +32,18 @@ var supportsPopoverHint = once(function () {
31
32
  var styles = {
32
33
  root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28"
33
34
  };
35
+ /**
36
+ * ARIA roles on the Popover primitive that register with the open layer observer
37
+ * as LayerType 'popup'. These are interactive overlays that should be tracked
38
+ * and dismissed when closeLayers() is called (e.g. when the side nav resizes).
39
+ *
40
+ * Note on open layer observer types not included here:
41
+ * - 'modal' only ever comes from the Dialog primitive, not Popover.
42
+ * - Other layer types (e.g. 'tooltip') do not yet exist in LayerType because
43
+ * there is no current use case for distinguishing them in the observer.
44
+ */
45
+ var POPUP_ROLES = new Set(['menu', 'listbox', 'dialog', 'alertdialog', 'tree', 'grid']);
46
+
34
47
  /**
35
48
  * Unopinionated top-layer primitive.
36
49
  *
@@ -99,6 +112,24 @@ export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
99
112
  role: role
100
113
  });
101
114
 
115
+ // ── Open layer observer registration ──
116
+ // Notifies the open layer observer so app-coordination features
117
+ // (closeLayers(), open-count subscriptions) work with top-layer popovers.
118
+ //
119
+ // Only set type: 'popup' for roles that are interactive overlay popups.
120
+ // Passive/informational roles (tooltip, note, status, alert, log) and
121
+ // no-role cases do not map to LayerType 'popup', so type is left undefined.
122
+ var handleObserverClose = useCallback(function () {
123
+ onClose({
124
+ reason: 'programmatic'
125
+ });
126
+ }, [onClose]);
127
+ useNotifyOpenLayerObserver({
128
+ type: role && POPUP_ROLES.has(role) ? 'popup' : undefined,
129
+ isOpen: isOpen,
130
+ onClose: handleObserverClose
131
+ });
132
+
102
133
  // Hint mode fallback: "hint" downgrades to "auto" if unsupported.
103
134
  //
104
135
  // Decision (2026-03-17 audit): We keep "auto" as the fallback (not "manual")
@@ -15,6 +15,7 @@ export var PopupProvider = PopupContext.Provider;
15
15
  *
16
16
  * Throws if called outside of a `<Popup>` compound component.
17
17
  */
18
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
18
19
  export function usePopupContext() {
19
20
  var ctx = useContext(PopupContext);
20
21
  if (ctx === null) {
@@ -29,6 +30,7 @@ export function usePopupContext() {
29
30
  * Used by `PopupContent` to support standalone usage (e.g. tooltip)
30
31
  * where values are passed as props instead of coming from a compound component.
31
32
  */
33
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
32
34
  export function useMaybePopupContext() {
33
35
  return useContext(PopupContext);
34
36
  }
@@ -119,6 +119,7 @@ export function isAtCurrentMenuLevel(element, container) {
119
119
  * });
120
120
  * ```
121
121
  */
122
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
122
123
  export function useArrowNavigation(_ref) {
123
124
  var containerRef = _ref.containerRef,
124
125
  onClose = _ref.onClose,
@@ -4,13 +4,14 @@ import { type TAnimationPreset } from '../animations/types';
4
4
  import { type TAriaRoleOptional, type TRoleRequiringAccessibleName, type TRoleWithImplicitName } from '../internal/role-types';
5
5
  import { type TPlacementOptions } from '../popup/types';
6
6
  /**
7
- * The reason a popover was dismissed via light dismiss.
7
+ * The reason a popover was closed.
8
8
  *
9
9
  * - `'escape'`: The user pressed the Escape key.
10
10
  * - `'light-dismiss'`: The user clicked outside the popover (or the browser
11
11
  * dismissed it for another auto-mode reason).
12
+ * - `'programmatic'`: The popover was closed programmatically.
12
13
  */
13
- export type TPopoverCloseReason = 'escape' | 'light-dismiss';
14
+ export type TPopoverCloseReason = 'escape' | 'light-dismiss' | 'programmatic';
14
15
  /**
15
16
  * Flat (non-discriminated) props accepted by the Popover component internally.
16
17
  *
@@ -4,13 +4,14 @@ import { type TAnimationPreset } from '../animations/types';
4
4
  import { type TAriaRoleOptional, type TRoleRequiringAccessibleName, type TRoleWithImplicitName } from '../internal/role-types';
5
5
  import { type TPlacementOptions } from '../popup/types';
6
6
  /**
7
- * The reason a popover was dismissed via light dismiss.
7
+ * The reason a popover was closed.
8
8
  *
9
9
  * - `'escape'`: The user pressed the Escape key.
10
10
  * - `'light-dismiss'`: The user clicked outside the popover (or the browser
11
11
  * dismissed it for another auto-mode reason).
12
+ * - `'programmatic'`: The popover was closed programmatically.
12
13
  */
13
- export type TPopoverCloseReason = 'escape' | 'light-dismiss';
14
+ export type TPopoverCloseReason = 'escape' | 'light-dismiss' | 'programmatic';
14
15
  /**
15
16
  * Flat (non-discriminated) props accepted by the Popover component internally.
16
17
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/top-layer",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
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",
@@ -63,6 +63,8 @@
63
63
  "@testing-library/jest-dom": "^6.4.5",
64
64
  "@testing-library/react": "^16.3.0",
65
65
  "@types/raf-schd": "^4.0.1",
66
+ "react": "^19.0.0",
67
+ "react-dom": "^19.0.0",
66
68
  "tiny-invariant": "^1.2.0"
67
69
  },
68
70
  "techstack": {