@atlaskit/top-layer 0.8.0 → 0.10.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 +41 -0
- package/README.md +5 -1
- package/dialog/create-close-event/package.json +17 -0
- package/dist/cjs/animations/presets.js +11 -4
- package/dist/cjs/arrow/index.js +131 -0
- package/dist/cjs/arrow/types.js +5 -0
- package/dist/cjs/dialog/dialog-content.js +23 -6
- package/dist/cjs/entry-points/dialog/create-close-event.js +12 -0
- package/dist/cjs/entry-points/placement-map.js +6 -5
- package/dist/cjs/entry-points/popover/create-close-event.js +12 -0
- package/dist/cjs/focus/focus.js +73 -19
- package/dist/cjs/index.js +3 -4
- package/dist/cjs/internal/resolve-css-length-to-pixels.js +61 -10
- package/dist/cjs/internal/role-types.js +40 -9
- package/dist/cjs/internal/set-style.js +23 -25
- package/dist/cjs/internal/use-anchor-position.js +109 -68
- package/dist/cjs/internal/use-focus-wrap.js +13 -2
- package/dist/cjs/internal/use-initial-focus.js +31 -10
- package/dist/cjs/placement-map/index.js +9 -110
- package/dist/cjs/placement-map/legacy-placements.js +113 -0
- package/dist/cjs/popover/popover.js +44 -11
- package/dist/cjs/popup/popup-content.js +19 -13
- package/dist/cjs/popup/popup-context.js +2 -0
- package/dist/cjs/popup/popup-surface.js +6 -0
- package/dist/cjs/popup/popup-trigger-function.js +1 -1
- package/dist/cjs/popup/popup-trigger.js +17 -37
- package/dist/cjs/popup/popup.js +21 -10
- package/dist/cjs/use-arrow-navigation/use-arrow-navigation.js +27 -10
- package/dist/es2019/animations/presets.js +10 -3
- package/dist/es2019/arrow/index.js +269 -0
- package/dist/es2019/arrow/types.js +1 -0
- package/dist/es2019/dialog/dialog-content.js +23 -6
- package/dist/es2019/entry-points/create-close-event.js +7 -0
- package/dist/es2019/entry-points/dialog/create-close-event.js +1 -0
- package/dist/es2019/entry-points/placement-map.js +2 -1
- package/dist/es2019/entry-points/popover/create-close-event.js +1 -0
- package/dist/es2019/focus/focus.js +58 -7
- package/dist/es2019/index.js +3 -4
- package/dist/es2019/internal/resolve-css-length-to-pixels.js +60 -7
- package/dist/es2019/internal/role-types.js +40 -9
- package/dist/es2019/internal/set-style.js +19 -22
- package/dist/es2019/internal/use-anchor-position.js +97 -54
- package/dist/es2019/internal/use-focus-wrap.js +14 -3
- package/dist/es2019/internal/use-initial-focus.js +32 -11
- package/dist/es2019/placement-map/index.js +8 -108
- package/dist/es2019/placement-map/legacy-placements.js +107 -0
- package/dist/es2019/popover/popover.js +44 -10
- package/dist/es2019/popup/popup-content.js +20 -14
- package/dist/es2019/popup/popup-context.js +2 -0
- package/dist/es2019/popup/popup-surface.js +6 -0
- package/dist/es2019/popup/popup-trigger-function.js +1 -1
- package/dist/es2019/popup/popup-trigger.js +17 -37
- package/dist/es2019/popup/popup.js +20 -9
- package/dist/es2019/use-arrow-navigation/use-arrow-navigation.js +27 -10
- package/dist/esm/animations/presets.js +10 -3
- package/dist/esm/arrow/index.js +124 -0
- package/dist/esm/arrow/types.js +1 -0
- package/dist/esm/dialog/dialog-content.js +23 -6
- package/dist/esm/entry-points/create-close-event.js +7 -0
- package/dist/esm/entry-points/dialog/create-close-event.js +1 -0
- package/dist/esm/entry-points/placement-map.js +2 -1
- package/dist/esm/entry-points/popover/create-close-event.js +1 -0
- package/dist/esm/focus/focus.js +72 -19
- package/dist/esm/index.js +3 -4
- package/dist/esm/internal/resolve-css-length-to-pixels.js +61 -10
- package/dist/esm/internal/role-types.js +39 -9
- package/dist/esm/internal/set-style.js +23 -25
- package/dist/esm/internal/use-anchor-position.js +110 -64
- package/dist/esm/internal/use-focus-wrap.js +14 -3
- package/dist/esm/internal/use-initial-focus.js +32 -11
- package/dist/esm/placement-map/index.js +8 -108
- package/dist/esm/placement-map/legacy-placements.js +107 -0
- package/dist/esm/popover/popover.js +44 -11
- package/dist/esm/popup/popup-content.js +20 -14
- package/dist/esm/popup/popup-context.js +2 -0
- package/dist/esm/popup/popup-surface.js +6 -0
- package/dist/esm/popup/popup-trigger-function.js +1 -1
- package/dist/esm/popup/popup-trigger.js +17 -37
- package/dist/esm/popup/popup.js +21 -10
- package/dist/esm/use-arrow-navigation/use-arrow-navigation.js +27 -10
- package/dist/types/arrow/index.d.ts +24 -0
- package/dist/types/arrow/types.d.ts +29 -0
- package/dist/types/dialog/dialog-content.d.ts +5 -1
- package/dist/types/dialog/types.d.ts +50 -24
- package/dist/types/entry-points/create-close-event.d.ts +7 -0
- package/dist/types/entry-points/dialog/create-close-event.d.ts +2 -0
- package/dist/types/entry-points/placement-map.d.ts +2 -1
- package/dist/types/entry-points/popover/create-close-event.d.ts +2 -0
- package/dist/types/focus/focus.d.ts +8 -0
- package/dist/types/internal/resolve-css-length-to-pixels.d.ts +4 -6
- package/dist/types/internal/role-types.d.ts +25 -4
- package/dist/types/internal/set-style.d.ts +2 -19
- package/dist/types/internal/use-anchor-position.d.ts +2 -3
- package/dist/types/internal/use-initial-focus.d.ts +5 -4
- package/dist/types/placement-map/index.d.ts +8 -21
- package/dist/types/placement-map/legacy-placements.d.ts +21 -0
- package/dist/types/popover/popover.d.ts +1 -1
- package/dist/types/popover/types.d.ts +20 -19
- package/dist/types/popup/popup-surface.d.ts +6 -0
- package/dist/types/popup/popup-trigger-function.d.ts +10 -6
- package/dist/types/popup/popup-trigger.d.ts +8 -17
- package/dist/types/popup/popup.d.ts +2 -2
- package/dist/types/popup/types.d.ts +68 -22
- package/dist/types/use-arrow-navigation/use-arrow-navigation.d.ts +19 -5
- package/dist/types-ts4.5/arrow/index.d.ts +24 -0
- package/dist/types-ts4.5/arrow/types.d.ts +29 -0
- package/dist/types-ts4.5/dialog/dialog-content.d.ts +5 -1
- package/dist/types-ts4.5/dialog/types.d.ts +50 -24
- package/dist/types-ts4.5/entry-points/create-close-event.d.ts +7 -0
- package/dist/types-ts4.5/entry-points/dialog/create-close-event.d.ts +2 -0
- package/dist/types-ts4.5/entry-points/placement-map.d.ts +2 -1
- package/dist/types-ts4.5/entry-points/popover/create-close-event.d.ts +2 -0
- package/dist/types-ts4.5/focus/focus.d.ts +8 -0
- package/dist/types-ts4.5/internal/resolve-css-length-to-pixels.d.ts +4 -6
- package/dist/types-ts4.5/internal/role-types.d.ts +25 -4
- package/dist/types-ts4.5/internal/set-style.d.ts +2 -19
- package/dist/types-ts4.5/internal/use-anchor-position.d.ts +2 -3
- package/dist/types-ts4.5/internal/use-initial-focus.d.ts +5 -4
- package/dist/types-ts4.5/placement-map/index.d.ts +8 -21
- package/dist/types-ts4.5/placement-map/legacy-placements.d.ts +39 -0
- package/dist/types-ts4.5/popover/popover.d.ts +1 -1
- package/dist/types-ts4.5/popover/types.d.ts +20 -19
- package/dist/types-ts4.5/popup/popup-surface.d.ts +6 -0
- package/dist/types-ts4.5/popup/popup-trigger-function.d.ts +10 -6
- package/dist/types-ts4.5/popup/popup-trigger.d.ts +8 -17
- package/dist/types-ts4.5/popup/popup.d.ts +2 -2
- package/dist/types-ts4.5/popup/types.d.ts +68 -22
- package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +19 -5
- package/package.json +3 -1
- package/popover/create-close-event/package.json +17 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy Popper.js placement values (used by `@atlaskit/popper`,
|
|
3
|
+
* `@atlaskit/popup`, `@atlaskit/tooltip`, etc.). Re-exported from
|
|
4
|
+
* `@atlaskit/top-layer/placement-map` for migration consumers that need
|
|
5
|
+
* to enumerate every placement (e.g. for a settings dropdown). New code
|
|
6
|
+
* should use the object-based `TPlacementOptions` directly.
|
|
7
|
+
*/
|
|
8
|
+
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
9
|
+
export var LEGACY_PLACEMENTS = ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-center', 'top-end', 'bottom', 'bottom-start', 'bottom-center', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'];
|
|
10
|
+
/**
|
|
11
|
+
* The full mapping record. Internal - use `fromLegacyPlacement` from the
|
|
12
|
+
* public entry-point so the offset-tuple translation is applied consistently.
|
|
13
|
+
*
|
|
14
|
+
* Note: `auto`, `auto-start`, `auto-end` map to `block-end` placements. This
|
|
15
|
+
* is a reduction in expressiveness vs Popper.js (which picked the best
|
|
16
|
+
* placement at runtime). CSS `position-try-fallbacks` flips at the edge but
|
|
17
|
+
* the initial placement is fixed. Migration consumers who relied on Popper's
|
|
18
|
+
* dynamic auto-flip should pick an explicit placement instead.
|
|
19
|
+
*/
|
|
20
|
+
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
21
|
+
export var placementMapping = {
|
|
22
|
+
top: {
|
|
23
|
+
axis: 'block',
|
|
24
|
+
edge: 'start',
|
|
25
|
+
align: 'center'
|
|
26
|
+
},
|
|
27
|
+
'top-start': {
|
|
28
|
+
axis: 'block',
|
|
29
|
+
edge: 'start',
|
|
30
|
+
align: 'start'
|
|
31
|
+
},
|
|
32
|
+
'top-center': {
|
|
33
|
+
axis: 'block',
|
|
34
|
+
edge: 'start',
|
|
35
|
+
align: 'center'
|
|
36
|
+
},
|
|
37
|
+
'top-end': {
|
|
38
|
+
axis: 'block',
|
|
39
|
+
edge: 'start',
|
|
40
|
+
align: 'end'
|
|
41
|
+
},
|
|
42
|
+
bottom: {
|
|
43
|
+
axis: 'block',
|
|
44
|
+
edge: 'end',
|
|
45
|
+
align: 'center'
|
|
46
|
+
},
|
|
47
|
+
'bottom-start': {
|
|
48
|
+
axis: 'block',
|
|
49
|
+
edge: 'end',
|
|
50
|
+
align: 'start'
|
|
51
|
+
},
|
|
52
|
+
'bottom-center': {
|
|
53
|
+
axis: 'block',
|
|
54
|
+
edge: 'end',
|
|
55
|
+
align: 'center'
|
|
56
|
+
},
|
|
57
|
+
'bottom-end': {
|
|
58
|
+
axis: 'block',
|
|
59
|
+
edge: 'end',
|
|
60
|
+
align: 'end'
|
|
61
|
+
},
|
|
62
|
+
right: {
|
|
63
|
+
axis: 'inline',
|
|
64
|
+
edge: 'end',
|
|
65
|
+
align: 'center'
|
|
66
|
+
},
|
|
67
|
+
'right-start': {
|
|
68
|
+
axis: 'inline',
|
|
69
|
+
edge: 'end',
|
|
70
|
+
align: 'start'
|
|
71
|
+
},
|
|
72
|
+
'right-end': {
|
|
73
|
+
axis: 'inline',
|
|
74
|
+
edge: 'end',
|
|
75
|
+
align: 'end'
|
|
76
|
+
},
|
|
77
|
+
left: {
|
|
78
|
+
axis: 'inline',
|
|
79
|
+
edge: 'start',
|
|
80
|
+
align: 'center'
|
|
81
|
+
},
|
|
82
|
+
'left-start': {
|
|
83
|
+
axis: 'inline',
|
|
84
|
+
edge: 'start',
|
|
85
|
+
align: 'start'
|
|
86
|
+
},
|
|
87
|
+
'left-end': {
|
|
88
|
+
axis: 'inline',
|
|
89
|
+
edge: 'start',
|
|
90
|
+
align: 'end'
|
|
91
|
+
},
|
|
92
|
+
auto: {
|
|
93
|
+
axis: 'block',
|
|
94
|
+
edge: 'end',
|
|
95
|
+
align: 'center'
|
|
96
|
+
},
|
|
97
|
+
'auto-start': {
|
|
98
|
+
axis: 'block',
|
|
99
|
+
edge: 'end',
|
|
100
|
+
align: 'start'
|
|
101
|
+
},
|
|
102
|
+
'auto-end': {
|
|
103
|
+
axis: 'block',
|
|
104
|
+
edge: 'end',
|
|
105
|
+
align: 'end'
|
|
106
|
+
}
|
|
107
|
+
};
|
|
@@ -7,8 +7,8 @@ import { ax, ix } from "@compiled/react/runtime";
|
|
|
7
7
|
import React, { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useRef } from 'react';
|
|
8
8
|
import { bind } from 'bind-event-listener';
|
|
9
9
|
import mergeRefs from '@atlaskit/ds-lib/merge-refs';
|
|
10
|
-
import noop from '@atlaskit/ds-lib/noop';
|
|
11
10
|
import once from '@atlaskit/ds-lib/once';
|
|
11
|
+
import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
|
|
12
12
|
import { useAnimatedVisibility } from '../internal/use-animated-visibility';
|
|
13
13
|
import { useFocusWrap } from '../internal/use-focus-wrap';
|
|
14
14
|
import { useInitialFocus } from '../internal/use-initial-focus';
|
|
@@ -31,6 +31,18 @@ var supportsPopoverHint = once(function () {
|
|
|
31
31
|
var styles = {
|
|
32
32
|
root: "_19itglyw _ca0qidpf _u5f3idpf _n3tdidpf _19bvidpf _19pkidpf _2hwxidpf _otyridpf _18u0idpf _1r041wug _1reoewfl _18m9ewfl _bfhk1j28"
|
|
33
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* ARIA roles on the Popover primitive that register with the open layer observer
|
|
36
|
+
* as LayerType 'popup'. These are interactive overlays that should be tracked
|
|
37
|
+
* and dismissed when closeLayers() is called (e.g. when the side nav resizes).
|
|
38
|
+
*
|
|
39
|
+
* Note on open layer observer types not included here:
|
|
40
|
+
* - 'modal' only ever comes from the Dialog primitive, not Popover.
|
|
41
|
+
* - Other layer types (e.g. 'tooltip') do not yet exist in LayerType because
|
|
42
|
+
* there is no current use case for distinguishing them in the observer.
|
|
43
|
+
*/
|
|
44
|
+
var POPUP_ROLES = new Set(['menu', 'listbox', 'dialog', 'alertdialog', 'tree', 'grid']);
|
|
45
|
+
|
|
34
46
|
/**
|
|
35
47
|
* Unopinionated top-layer primitive.
|
|
36
48
|
*
|
|
@@ -45,15 +57,14 @@ var styles = {
|
|
|
45
57
|
*
|
|
46
58
|
* For `mode="auto"`, the browser can dismiss the popover via light dismiss
|
|
47
59
|
* (Escape, click outside). When that happens, `onClose` is called and the
|
|
48
|
-
* consumer must set `isOpen` to `false`. The DOM owns the dismiss
|
|
60
|
+
* consumer must set `isOpen` to `false`. The DOM owns the dismiss; React
|
|
49
61
|
* must follow.
|
|
50
62
|
*/
|
|
51
63
|
export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
|
|
52
64
|
var children = _ref.children,
|
|
53
65
|
_ref$mode = _ref.mode,
|
|
54
66
|
modeProp = _ref$mode === void 0 ? 'auto' : _ref$mode,
|
|
55
|
-
|
|
56
|
-
onClose = _ref$onClose === void 0 ? noop : _ref$onClose,
|
|
67
|
+
onClose = _ref.onClose,
|
|
57
68
|
onOpenChange = _ref.onOpenChange,
|
|
58
69
|
onExitFinish = _ref.onExitFinish,
|
|
59
70
|
animate = _ref.animate,
|
|
@@ -68,7 +79,7 @@ export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
|
|
|
68
79
|
var autoId = useId();
|
|
69
80
|
var ownRef = useRef(null);
|
|
70
81
|
var combinedRef = mergeRefs([ownRef, ref].filter(Boolean));
|
|
71
|
-
// Regex is intentional
|
|
82
|
+
// Regex is intentional: `replaceAll` is not available in our supported
|
|
72
83
|
// browser matrix. In React 18, `useId()` returns IDs containing colons
|
|
73
84
|
// (e.g. `:r1:`) which are invalid in CSS selectors and `popover` target
|
|
74
85
|
// attributes. React 19.2.0 replaced colons with underscores, making this
|
|
@@ -86,7 +97,7 @@ export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
|
|
|
86
97
|
// ── Focus management ──
|
|
87
98
|
// Initial focus: on open, moves focus into the popover (role-dependent).
|
|
88
99
|
// Focus wrap: Tab/Shift+Tab cycle within the popover for dialog roles.
|
|
89
|
-
// Focus restoration: handled natively by the Popover API
|
|
100
|
+
// Focus restoration: handled natively by the Popover API. The browser
|
|
90
101
|
// tracks `previouslyFocusedElement` on show and restores it on hide.
|
|
91
102
|
// No custom restoration hook is needed. See: notes/architecture/focus-restoration.md
|
|
92
103
|
useFocusWrap({
|
|
@@ -99,6 +110,24 @@ export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
|
|
|
99
110
|
role: role
|
|
100
111
|
});
|
|
101
112
|
|
|
113
|
+
// ── Open layer observer registration ──
|
|
114
|
+
// Notifies the open layer observer so app-coordination features
|
|
115
|
+
// (closeLayers(), open-count subscriptions) work with top-layer popovers.
|
|
116
|
+
//
|
|
117
|
+
// Only set type: 'popup' for roles that are interactive overlay popups.
|
|
118
|
+
// Passive/informational roles (tooltip, note, status, alert, log) and
|
|
119
|
+
// no-role cases do not map to LayerType 'popup', so type is left undefined.
|
|
120
|
+
var handleObserverClose = useCallback(function () {
|
|
121
|
+
onClose === null || onClose === void 0 || onClose({
|
|
122
|
+
reason: 'programmatic'
|
|
123
|
+
});
|
|
124
|
+
}, [onClose]);
|
|
125
|
+
useNotifyOpenLayerObserver({
|
|
126
|
+
type: role && POPUP_ROLES.has(role) ? 'popup' : undefined,
|
|
127
|
+
isOpen: isOpen,
|
|
128
|
+
onClose: handleObserverClose
|
|
129
|
+
});
|
|
130
|
+
|
|
102
131
|
// Hint mode fallback: "hint" downgrades to "auto" if unsupported.
|
|
103
132
|
//
|
|
104
133
|
// Decision (2026-03-17 audit): We keep "auto" as the fallback (not "manual")
|
|
@@ -111,8 +140,8 @@ export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
|
|
|
111
140
|
var mode = modeProp === 'hint' && !supportsPopoverHint() ? 'auto' : modeProp;
|
|
112
141
|
|
|
113
142
|
// Distinguishes programmatic hidePopover() from browser-initiated dismiss.
|
|
114
|
-
// When we call hidePopover() ourselves the beforetoggle event fires synchronously
|
|
115
|
-
// this ref prevents the toggle handler from calling onClose redundantly.
|
|
143
|
+
// When we call hidePopover() ourselves the beforetoggle event fires synchronously,
|
|
144
|
+
// so this ref prevents the toggle handler from calling onClose redundantly.
|
|
116
145
|
var programmaticCloseRef = useRef(false);
|
|
117
146
|
|
|
118
147
|
// Escape key tracking: set in a capture-phase keydown listener (fires before
|
|
@@ -145,7 +174,11 @@ export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
|
|
|
145
174
|
// Browser dismiss (Escape or click-outside). Notify consumer.
|
|
146
175
|
var reason = closeReasonRef.current;
|
|
147
176
|
closeReasonRef.current = 'light-dismiss'; // reset for next dismiss
|
|
148
|
-
onClose
|
|
177
|
+
// `onClose` is required for `auto`/`hint` and forbidden for
|
|
178
|
+
// `manual`, but `TPopoverForwardedProps` is a flat type used
|
|
179
|
+
// internally by `PopupContent` - the optional check guards
|
|
180
|
+
// the manual-mode forwarded path where the field is undefined.
|
|
181
|
+
onClose === null || onClose === void 0 || onClose({
|
|
149
182
|
reason: reason
|
|
150
183
|
});
|
|
151
184
|
}
|
|
@@ -180,7 +213,7 @@ export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
|
|
|
180
213
|
|
|
181
214
|
// Placement-dependent animation CSS vars (e.g. slide direction).
|
|
182
215
|
// Separate from show/hide so that preset reference changes
|
|
183
|
-
// (e.g. inline `animate={slideAndFade()}`)
|
|
216
|
+
// (e.g. inline `animate={slideAndFade()}`) do not re-trigger visibility.
|
|
184
217
|
useLayoutEffect(function () {
|
|
185
218
|
var el = ownRef.current;
|
|
186
219
|
if (!el || !(preset !== null && preset !== void 0 && preset.getProperties) || !placement) {
|
|
@@ -202,7 +235,7 @@ export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
|
|
|
202
235
|
// in the target state (e.g. browser dismissed via light-dismiss before our
|
|
203
236
|
// effect ran).
|
|
204
237
|
// Defensive try/catch: these APIs throw InvalidStateError if the element
|
|
205
|
-
// is disconnected or missing the popover attribute. While we
|
|
238
|
+
// is disconnected or missing the popover attribute. While we do not expect
|
|
206
239
|
// that to happen in normal usage, it can occur in edge cases (e.g. React
|
|
207
240
|
// concurrent mode, StrictMode double-effects, or unmount races).
|
|
208
241
|
useLayoutEffect(function () {
|
|
@@ -3,7 +3,7 @@ import _extends from "@babel/runtime/helpers/extends";
|
|
|
3
3
|
import { ax, ix } from "@compiled/react/runtime";
|
|
4
4
|
import React, { forwardRef, useCallback, useLayoutEffect, useRef } from 'react';
|
|
5
5
|
import mergeRefs from '@atlaskit/ds-lib/merge-refs';
|
|
6
|
-
import { roleToAriaHasPopup } from '../internal/role-types';
|
|
6
|
+
import { roleToAriaHasPopup, shouldFocusIntoPopover } from '../internal/role-types';
|
|
7
7
|
import { useAnchorPosition } from '../internal/use-anchor-position';
|
|
8
8
|
import { useWidthFromAnchor } from '../internal/use-width-from-anchor';
|
|
9
9
|
import { Popover } from '../popover/popover';
|
|
@@ -31,7 +31,8 @@ export var PopupContent = /*#__PURE__*/forwardRef(function PopupContent(_ref, re
|
|
|
31
31
|
placementProp = _ref.placement,
|
|
32
32
|
onCloseProp = _ref.onClose,
|
|
33
33
|
testIdProp = _ref.testId,
|
|
34
|
-
forceFallbackPositioningProp = _ref.forceFallbackPositioning
|
|
34
|
+
forceFallbackPositioningProp = _ref.forceFallbackPositioning,
|
|
35
|
+
idProp = _ref.id;
|
|
35
36
|
// When inside <Popup>, context provides defaults. Props always win.
|
|
36
37
|
var ctx = useMaybePopupContext();
|
|
37
38
|
var triggerRef = triggerRefProp !== null && triggerRefProp !== void 0 ? triggerRefProp : ctx === null || ctx === void 0 ? void 0 : ctx.triggerRef;
|
|
@@ -74,7 +75,7 @@ export var PopupContent = /*#__PURE__*/forwardRef(function PopupContent(_ref, re
|
|
|
74
75
|
// does not restore focus when they close.
|
|
75
76
|
//
|
|
76
77
|
// To handle this, we listen for the close transition via `onOpenChange` and
|
|
77
|
-
// restore focus to the trigger if the browser
|
|
78
|
+
// restore focus to the trigger if the browser did not. This runs after the
|
|
78
79
|
// browser's native restoration (which happens synchronously during hidePopover),
|
|
79
80
|
// so it only takes effect when the browser skipped restoration (nested case).
|
|
80
81
|
//
|
|
@@ -91,7 +92,7 @@ export var PopupContent = /*#__PURE__*/forwardRef(function PopupContent(_ref, re
|
|
|
91
92
|
// focus when they close.
|
|
92
93
|
//
|
|
93
94
|
// We detect this case by checking if focus is still inside the
|
|
94
|
-
// closing popover element. If it is, the browser
|
|
95
|
+
// closing popover element. If it is, the browser did not restore,
|
|
95
96
|
// and we need to move focus to the trigger ourselves.
|
|
96
97
|
//
|
|
97
98
|
// This does NOT fire for:
|
|
@@ -100,13 +101,15 @@ export var PopupContent = /*#__PURE__*/forwardRef(function PopupContent(_ref, re
|
|
|
100
101
|
if (!args.isOpen) {
|
|
101
102
|
var trigger = triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current;
|
|
102
103
|
var focusStillInPopover = trigger && args.element.contains(document.activeElement);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
// Single source of truth for "this role moves focus on open
|
|
105
|
+
// and therefore needs to be restored on close" - see
|
|
106
|
+
// `shouldFocusIntoPopover` in `internal/role-types.tsx`.
|
|
107
|
+
if (focusStillInPopover && shouldFocusIntoPopover({
|
|
108
|
+
role: role
|
|
109
|
+
})) {
|
|
110
|
+
trigger.focus({
|
|
111
|
+
preventScroll: true
|
|
112
|
+
});
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
}, [setIsOpen, consumerOnOpenChange, triggerRef, role]);
|
|
@@ -136,8 +139,8 @@ export var PopupContent = /*#__PURE__*/forwardRef(function PopupContent(_ref, re
|
|
|
136
139
|
var combinedRef = mergeRefs([popoverRef, ref]);
|
|
137
140
|
|
|
138
141
|
// TPopupContentProps already validates the discriminated union (role + label,
|
|
139
|
-
// mode + onClose) at its API boundary. Popover accepts
|
|
140
|
-
// internally so individual prop forwarding
|
|
142
|
+
// mode + onClose) at its API boundary. Popover accepts TPopoverForwardedProps
|
|
143
|
+
// internally so individual prop forwarding does not need to re-prove the union.
|
|
141
144
|
var popoverProps = {
|
|
142
145
|
isOpen: isOpen,
|
|
143
146
|
animate: animate,
|
|
@@ -150,7 +153,10 @@ export var PopupContent = /*#__PURE__*/forwardRef(function PopupContent(_ref, re
|
|
|
150
153
|
onOpenChange: handleOpenChange,
|
|
151
154
|
testId: testId,
|
|
152
155
|
children: children,
|
|
153
|
-
|
|
156
|
+
// Standalone consumers can pass an explicit `id` - used by their own
|
|
157
|
+
// trigger to wire `aria-controls`. Inside the `<Popup>` compound, the
|
|
158
|
+
// id flows from context (`ctx.popoverId`).
|
|
159
|
+
id: idProp !== null && idProp !== void 0 ? idProp : ctx === null || ctx === void 0 ? void 0 : ctx.popoverId,
|
|
154
160
|
xcss: xcss
|
|
155
161
|
};
|
|
156
162
|
return /*#__PURE__*/React.createElement(Popover, _extends({
|
|
@@ -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
|
}
|
|
@@ -14,6 +14,12 @@ var styles = {
|
|
|
14
14
|
* should use `Popup.Content` without wrapping in `PopupSurface`.
|
|
15
15
|
*
|
|
16
16
|
* Exposed as `Popup.Surface` on the compound component.
|
|
17
|
+
*
|
|
18
|
+
* **Presentational primitive - `children`-only by design.** This component
|
|
19
|
+
* intentionally exposes no `style`, `className`, `xcss`, `id`, or `ref`
|
|
20
|
+
* props. If you need any of those, render your own wrapper element inside
|
|
21
|
+
* `Popup.Content` instead - `PopupSurface` is the no-config "give me the
|
|
22
|
+
* default ADS overlay look" surface, nothing more.
|
|
17
23
|
*/
|
|
18
24
|
export function PopupSurface(_ref) {
|
|
19
25
|
var children = _ref.children;
|
|
@@ -8,7 +8,7 @@ import { usePopupContext } from './popup-context';
|
|
|
8
8
|
/**
|
|
9
9
|
* Render-prop alternative to `Popup.Trigger`.
|
|
10
10
|
*
|
|
11
|
-
* Use when you need full control over the trigger element
|
|
11
|
+
* Use when you need full control over the trigger element. For example,
|
|
12
12
|
* to compose your own trigger with custom event handlers, wrap a third-party
|
|
13
13
|
* component, or build a trigger from multiple elements.
|
|
14
14
|
*
|
|
@@ -2,24 +2,15 @@ import React, { isValidElement, useCallback } from 'react';
|
|
|
2
2
|
import { Slot } from '../internal/slot';
|
|
3
3
|
import { usePopupContext } from './popup-context';
|
|
4
4
|
/**
|
|
5
|
-
* Wraps
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Wraps a single trigger element via `cloneElement`. Attaches aria
|
|
6
|
+
* attributes, the toggle handler, and the anchor-name. `aria-haspopup`
|
|
7
|
+
* is derived from `Popup.Content`'s role via context.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* (
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* **Limitation:** Uses `cloneElement` internally (via `Slot`), which is
|
|
15
|
-
* incompatible with Compiled's `css` prop on the direct child element.
|
|
16
|
-
* The `@jsx jsx` pragma transforms the child before `cloneElement` can
|
|
17
|
-
* inject `onClick` and `ref` onto the DOM node, preventing the popover
|
|
18
|
-
* from opening.
|
|
19
|
-
*
|
|
20
|
-
* If you need Compiled `css` on the trigger, use `Popup.TriggerFunction`
|
|
21
|
-
* instead - it hands you raw props via a render-prop and avoids
|
|
22
|
-
* `cloneElement` entirely.
|
|
9
|
+
* Child constraints: must be a single ref-forwarding element. The child's
|
|
10
|
+
* `onClick` is composed (runs after); other event handlers are NOT.
|
|
11
|
+
* Children using Compiled's `css` prop swallow injected props - use
|
|
12
|
+
* `Popup.TriggerFunction` for these cases or any time you need handler
|
|
13
|
+
* composition.
|
|
23
14
|
*/
|
|
24
15
|
export function PopupTrigger(_ref) {
|
|
25
16
|
var children = _ref.children;
|
|
@@ -31,35 +22,24 @@ export function PopupTrigger(_ref) {
|
|
|
31
22
|
ariaHasPopup = _usePopupContext.ariaHasPopup;
|
|
32
23
|
var childOnClick = /*#__PURE__*/isValidElement(children) ? children.props.onClick : undefined;
|
|
33
24
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// the handler on every open/close, breaking cloneElement identity).
|
|
25
|
+
// Track latest isOpen via ref so the click handler keeps a stable
|
|
26
|
+
// identity (cloneElement relies on it).
|
|
37
27
|
var isOpenRef = React.useRef(isOpen);
|
|
38
28
|
isOpenRef.current = isOpen;
|
|
39
29
|
var handleClick = useCallback(function (event) {
|
|
40
30
|
var popoverEl = popoverRef.current;
|
|
41
31
|
if (popoverEl) {
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// We use React's `isOpen` state (via ref) rather than the DOM
|
|
48
|
-
// state to determine intent:
|
|
49
|
-
// - If React thinks it was open → the user wants to close.
|
|
50
|
-
// The browser already did this via light-dismiss, so do nothing.
|
|
51
|
-
// - If React thinks it was closed → the user wants to open.
|
|
52
|
-
// Call showPopover().
|
|
32
|
+
// `popover="auto"` light-dismiss can close the popover BEFORE
|
|
33
|
+
// our click handler runs, so trust React's isOpen rather than
|
|
34
|
+
// DOM state. If React still thinks it is closed, open it; if
|
|
35
|
+
// it was open, the browser already closed it.
|
|
53
36
|
if (!isOpenRef.current) {
|
|
54
|
-
// Defensive: element may be disconnected or in an unexpected state.
|
|
55
37
|
try {
|
|
56
38
|
popoverEl.showPopover();
|
|
57
|
-
} catch (_unused) {
|
|
39
|
+
} catch (_unused) {
|
|
40
|
+
// Element may be disconnected; ignore.
|
|
41
|
+
}
|
|
58
42
|
}
|
|
59
|
-
// When isOpenRef.current is true, the browser's light-dismiss
|
|
60
|
-
// already closed the popover and fired the toggle event, which
|
|
61
|
-
// will update React state via handleToggle → setIsOpen(false).
|
|
62
|
-
// No action needed here.
|
|
63
43
|
}
|
|
64
44
|
childOnClick === null || childOnClick === void 0 || childOnClick(event);
|
|
65
45
|
}, [popoverRef, childOnClick]);
|
package/dist/esm/popup/popup.js
CHANGED
|
@@ -2,10 +2,10 @@ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
|
2
2
|
import React, { useCallback, useId, useRef, useState } from 'react';
|
|
3
3
|
import { PopupProvider } from './popup-context';
|
|
4
4
|
/**
|
|
5
|
-
* Popup compound
|
|
5
|
+
* Popup compound: trigger + popover content.
|
|
6
6
|
*
|
|
7
7
|
* Use when a button opens anchored content (dropdown, menu, dialog).
|
|
8
|
-
* The browser manages visibility via `togglePopover()
|
|
8
|
+
* The browser manages visibility via `togglePopover()`; the consumer
|
|
9
9
|
* never manages `isOpen`. For custom trigger lifecycles (hover, timers,
|
|
10
10
|
* external state), use `Popover` directly instead.
|
|
11
11
|
*
|
|
@@ -13,18 +13,24 @@ import { PopupProvider } from './popup-context';
|
|
|
13
13
|
* `Popup.Content` reads and forwards to the underlying `Popover`.
|
|
14
14
|
*/
|
|
15
15
|
export function PopupRoot(props) {
|
|
16
|
-
var _props$mode;
|
|
17
|
-
var
|
|
18
|
-
children = props.children,
|
|
16
|
+
var _props$placement, _props$mode;
|
|
17
|
+
var children = props.children,
|
|
19
18
|
testId = props.testId,
|
|
20
19
|
forceFallbackPositioning = props.forceFallbackPositioning,
|
|
21
20
|
onOpenChange = props.onOpenChange;
|
|
21
|
+
// `{}` resolves to "below the trigger, centered, with `space.100` gap"
|
|
22
|
+
// inside `useAnchorPosition`, which is the common case.
|
|
23
|
+
var placement = (_props$placement = props.placement) !== null && _props$placement !== void 0 ? _props$placement : {};
|
|
22
24
|
var mode = (_props$mode = props.mode) !== null && _props$mode !== void 0 ? _props$mode : 'auto';
|
|
23
|
-
|
|
25
|
+
|
|
26
|
+
// `TPopupProps` discriminates on `mode`: `manual` types `onClose` as
|
|
27
|
+
// `never`, so for manual consumers `props.onClose` is always `undefined`
|
|
28
|
+
// here. No runtime guard needed; the type contract carries the rule.
|
|
29
|
+
var onClose = props.onClose;
|
|
24
30
|
var id = useId();
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
31
|
+
// `useId()` returns IDs containing colons (`:r1:`), which are invalid
|
|
32
|
+
// in CSS selectors and `popover` target attributes. `replaceAll` is
|
|
33
|
+
// not in our browser matrix.
|
|
28
34
|
var sanitizedId = id.replace(/:/g, '');
|
|
29
35
|
var popoverId = "popover-".concat(sanitizedId);
|
|
30
36
|
var triggerRef = useRef(null);
|
|
@@ -33,6 +39,8 @@ export function PopupRoot(props) {
|
|
|
33
39
|
_useState2 = _slicedToArray(_useState, 2),
|
|
34
40
|
isOpen = _useState2[0],
|
|
35
41
|
setIsOpen = _useState2[1];
|
|
42
|
+
// Initial seed before `Popup.Content`'s layout effect resolves the
|
|
43
|
+
// real role on first paint.
|
|
36
44
|
var _useState3 = useState('dialog'),
|
|
37
45
|
_useState4 = _slicedToArray(_useState3, 2),
|
|
38
46
|
ariaHasPopup = _useState4[0],
|
|
@@ -43,7 +51,10 @@ export function PopupRoot(props) {
|
|
|
43
51
|
var contextValue = {
|
|
44
52
|
popoverId: popoverId,
|
|
45
53
|
placement: placement,
|
|
46
|
-
|
|
54
|
+
// `null` when the consumer did not supply a handler (manual mode, or
|
|
55
|
+
// auto/hint without a light-dismiss handler). `Popup.Content` reads
|
|
56
|
+
// this to decide whether to forward an `onClose` to `Popover`.
|
|
57
|
+
onClose: onClose ? handleClose : null,
|
|
47
58
|
triggerRef: triggerRef,
|
|
48
59
|
popoverRef: popoverRef,
|
|
49
60
|
isOpen: isOpen,
|
|
@@ -7,16 +7,16 @@ var menuScope = '[role="menu"]';
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Returns true if the element belongs to the current menu level of the
|
|
10
|
-
* container
|
|
10
|
+
* container, i.e. it is NOT inside a nested `role="menu"` sub-container.
|
|
11
11
|
*
|
|
12
12
|
* Uses `element.closest('[role="menu"]')` to determine the nearest menu
|
|
13
13
|
* scope. If the nearest scope is the container's own `role="menu"` element,
|
|
14
|
-
* the element belongs to this level. If it
|
|
14
|
+
* the element belongs to this level. If it is a different (nested) menu,
|
|
15
15
|
* the element belongs to a child level and should be excluded.
|
|
16
16
|
*
|
|
17
17
|
* Supports two container patterns:
|
|
18
18
|
*
|
|
19
|
-
* **1. Container has `role="menu"` directly** (preferred
|
|
19
|
+
* **1. Container has `role="menu"` directly** (preferred, used by avatar-group):
|
|
20
20
|
* ```
|
|
21
21
|
* <div ref={containerRef} role="menu">
|
|
22
22
|
* <button role="menuitem" /> ← isAtCurrentMenuLevel → true
|
|
@@ -28,7 +28,7 @@ var menuScope = '[role="menu"]';
|
|
|
28
28
|
* Uses the simple check: `element.closest('[role="menu"]') === container`
|
|
29
29
|
*
|
|
30
30
|
* **2. Container is a wrapper without `role="menu"`** (used by dropdown-menu,
|
|
31
|
-
* where `MenuGroup` owns `role="menu"` but
|
|
31
|
+
* where `MenuGroup` owns `role="menu"` but does not accept a ref):
|
|
32
32
|
* ```
|
|
33
33
|
* <div ref={containerRef}> ← wrapper (no role="menu")
|
|
34
34
|
* <MenuGroup role="menu">
|
|
@@ -55,7 +55,7 @@ var menuScope = '[role="menu"]';
|
|
|
55
55
|
export function isAtCurrentMenuLevel(element, container) {
|
|
56
56
|
var closestMenu = element.closest(menuScope);
|
|
57
57
|
|
|
58
|
-
// Container itself has role="menu"
|
|
58
|
+
// Container itself has role="menu". Element belongs to this level
|
|
59
59
|
// if its closest menu is the container.
|
|
60
60
|
if (container.getAttribute('role') === 'menu') {
|
|
61
61
|
return closestMenu === container;
|
|
@@ -64,18 +64,18 @@ export function isAtCurrentMenuLevel(element, container) {
|
|
|
64
64
|
// Container is a wrapper without role="menu" (e.g. dropdown-menu
|
|
65
65
|
// wraps a MenuGroup). The element belongs to the top level if its
|
|
66
66
|
// closest role="menu" ancestor is a direct child of the container,
|
|
67
|
-
// i.e. there
|
|
67
|
+
// i.e. there is no nested role="menu" between them.
|
|
68
68
|
if (closestMenu === null) {
|
|
69
69
|
return true;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// Walk up from the closest menu to see if it
|
|
72
|
+
// Walk up from the closest menu to see if it is "owned" by this
|
|
73
73
|
// container without passing through another role="menu" first.
|
|
74
74
|
var parent = closestMenu.parentElement;
|
|
75
75
|
while (parent && parent !== container) {
|
|
76
76
|
if (parent.getAttribute('role') === 'menu') {
|
|
77
|
-
// There
|
|
78
|
-
// container
|
|
77
|
+
// There is a parent menu between the closest menu and the
|
|
78
|
+
// container, so the element is in a nested sub-menu.
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
81
|
parent = parent.parentElement;
|
|
@@ -84,6 +84,22 @@ export function isAtCurrentMenuLevel(element, container) {
|
|
|
84
84
|
// The closest menu is directly inside the container (no nesting).
|
|
85
85
|
return parent === container;
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Args for `useArrowNavigation`.
|
|
90
|
+
*
|
|
91
|
+
* Keyboard contract:
|
|
92
|
+
* - `ArrowDown` / `ArrowUp` move within the current menu level.
|
|
93
|
+
* - `Home` / `End` jump to first / last.
|
|
94
|
+
* - `ArrowRight` / `ArrowLeft` opens / closes nested submenus.
|
|
95
|
+
* - `Tab` / `Shift+Tab` dismiss the menu (matching the ARIA APG menu pattern,
|
|
96
|
+
* plus Radix UI / Headless UI / React Aria / Floating UI conventions).
|
|
97
|
+
* Symmetric in both directions: there is no special-case for the last
|
|
98
|
+
* item that lets Tab leave the menu without dismissing it.
|
|
99
|
+
* - `Page Up` / `Page Down`: NOT handled - extend via your own listener if
|
|
100
|
+
* your menu needs page-style scrolling.
|
|
101
|
+
*/
|
|
102
|
+
|
|
87
103
|
/**
|
|
88
104
|
* Provides arrow key navigation for menu containers.
|
|
89
105
|
*
|
|
@@ -119,6 +135,7 @@ export function isAtCurrentMenuLevel(element, container) {
|
|
|
119
135
|
* });
|
|
120
136
|
* ```
|
|
121
137
|
*/
|
|
138
|
+
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
122
139
|
export function useArrowNavigation(_ref) {
|
|
123
140
|
var containerRef = _ref.containerRef,
|
|
124
141
|
onClose = _ref.onClose,
|
|
@@ -214,7 +231,7 @@ export function useArrowNavigation(_ref) {
|
|
|
214
231
|
if (onNestedClose) {
|
|
215
232
|
var _container$parentElem;
|
|
216
233
|
// Only close if this container is inside another role="menu"
|
|
217
|
-
// (i.e. it
|
|
234
|
+
// (i.e. it is a submenu, not the top-level menu).
|
|
218
235
|
// Walk up from the parent to avoid matching the container itself.
|
|
219
236
|
var isNested = ((_container$parentElem = container.parentElement) === null || _container$parentElem === void 0 ? void 0 : _container$parentElem.closest(menuScope)) !== null;
|
|
220
237
|
if (isNested) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type TArrowPreset } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an arrow preset for popover content.
|
|
4
|
+
*
|
|
5
|
+
* The returned object is passed to `PopoverContent`'s `arrow` prop.
|
|
6
|
+
* It carries the CSS string and fallback logic so that consumers who
|
|
7
|
+
* do not use arrows pay no bundle cost.
|
|
8
|
+
*
|
|
9
|
+
* The arrows inherit their background from the popover element.
|
|
10
|
+
* When `arrow` is active, `PopoverContent` does not reset `background`
|
|
11
|
+
* to `transparent`, allowing the UA default (`canvas`) or a consumer-set
|
|
12
|
+
* background to show through.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* import { arrow } from '@atlaskit/top-layer/arrow';
|
|
17
|
+
*
|
|
18
|
+
* const myArrow = arrow();
|
|
19
|
+
*
|
|
20
|
+
* <Popup.Content arrow={myArrow} />
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare const arrow: () => TArrowPreset;
|
|
24
|
+
export type { TArrowPreset } from './types';
|