@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.
- package/CHANGELOG.md +112 -0
- package/README.md +4 -2
- package/dist/cjs/arrow/index.js +2 -2
- package/dist/cjs/dialog/dialog-content.js +6 -1
- package/dist/cjs/entry-points/use-width-from-anchor.js +12 -0
- package/dist/cjs/focus/focus.js +4 -4
- package/dist/cjs/internal/combine.js +2 -2
- package/dist/cjs/internal/use-anchor-position.js +25 -7
- package/dist/cjs/internal/use-animated-visibility.js +3 -3
- package/dist/cjs/internal/use-focus-wrap.js +7 -7
- package/dist/cjs/internal/use-initial-focus.js +1 -1
- package/dist/cjs/internal/use-width-from-anchor.js +107 -0
- package/dist/cjs/popover/popover.js +12 -10
- package/dist/cjs/popup/popup-content.js +10 -37
- package/dist/cjs/popup/popup-trigger.js +2 -2
- package/dist/cjs/use-simple-light-dismiss/index.js +1 -1
- package/dist/es2019/arrow/index.js +2 -2
- package/dist/es2019/dialog/dialog-content.js +6 -1
- package/dist/es2019/entry-points/use-width-from-anchor.js +1 -0
- package/dist/es2019/focus/focus.js +1 -1
- package/dist/es2019/internal/combine.js +1 -1
- package/dist/es2019/internal/use-anchor-position.js +13 -7
- package/dist/es2019/internal/use-animated-visibility.js +3 -3
- package/dist/es2019/internal/use-focus-wrap.js +7 -7
- package/dist/es2019/internal/use-initial-focus.js +1 -1
- package/dist/es2019/internal/use-width-from-anchor.js +103 -0
- package/dist/es2019/popover/popover.js +12 -10
- package/dist/es2019/popup/popup-content.js +9 -36
- package/dist/es2019/popup/popup-trigger.js +2 -2
- package/dist/es2019/use-simple-light-dismiss/index.js +1 -1
- package/dist/esm/arrow/index.js +2 -2
- package/dist/esm/dialog/dialog-content.js +6 -1
- package/dist/esm/entry-points/use-width-from-anchor.js +1 -0
- package/dist/esm/focus/focus.js +4 -4
- package/dist/esm/internal/combine.js +2 -2
- package/dist/esm/internal/use-anchor-position.js +25 -7
- package/dist/esm/internal/use-animated-visibility.js +3 -3
- package/dist/esm/internal/use-focus-wrap.js +7 -7
- package/dist/esm/internal/use-initial-focus.js +1 -1
- package/dist/esm/internal/use-width-from-anchor.js +102 -0
- package/dist/esm/popover/popover.js +12 -10
- package/dist/esm/popup/popup-content.js +10 -37
- package/dist/esm/popup/popup-trigger.js +2 -2
- package/dist/esm/use-simple-light-dismiss/index.js +1 -1
- package/dist/types/entry-points/use-width-from-anchor.d.ts +1 -0
- package/dist/types/internal/use-anchor-position.d.ts +1 -1
- package/dist/types/internal/use-focus-wrap.d.ts +1 -1
- package/dist/types/internal/use-width-from-anchor.d.ts +20 -0
- package/dist/types/popover/popover.d.ts +3 -1
- package/dist/types/popover/types.d.ts +13 -0
- package/dist/types/popup/popup-content.d.ts +1 -1
- package/dist/types/popup/popup-trigger.d.ts +1 -1
- package/dist/types/popup/types.d.ts +8 -8
- package/dist/types/use-simple-light-dismiss/index.d.ts +3 -3
- package/dist/types-ts4.5/entry-points/use-width-from-anchor.d.ts +1 -0
- package/dist/types-ts4.5/internal/use-anchor-position.d.ts +1 -1
- package/dist/types-ts4.5/internal/use-focus-wrap.d.ts +1 -1
- package/dist/types-ts4.5/internal/use-width-from-anchor.d.ts +20 -0
- package/dist/types-ts4.5/popover/popover.d.ts +3 -1
- package/dist/types-ts4.5/popover/types.d.ts +13 -0
- package/dist/types-ts4.5/popup/popup-content.d.ts +1 -1
- package/dist/types-ts4.5/popup/popup-trigger.d.ts +1 -1
- package/dist/types-ts4.5/popup/types.d.ts +8 -8
- package/dist/types-ts4.5/use-simple-light-dismiss/index.d.ts +3 -3
- package/package.json +2 -2
- package/use-width-from-anchor/package.json +17 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useWidthFromAnchor } from '../internal/use-width-from-anchor';
|
|
@@ -39,7 +39,7 @@ function getFocusables({
|
|
|
39
39
|
container,
|
|
40
40
|
filter
|
|
41
41
|
}) {
|
|
42
|
-
return Array.from(container.querySelectorAll(selectors.focusable)).filter(
|
|
42
|
+
return Array.from(container.querySelectorAll(selectors.focusable)).filter(element => element instanceof HTMLElement).filter(element => !filter || filter(element, container));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -13,6 +13,6 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export function combine(...cleanupFns) {
|
|
15
15
|
return function cleanup() {
|
|
16
|
-
cleanupFns.forEach(
|
|
16
|
+
cleanupFns.forEach(callback => callback === null || callback === void 0 ? void 0 : callback());
|
|
17
17
|
};
|
|
18
18
|
}
|
|
@@ -323,12 +323,6 @@ export function useAnchorPosition({
|
|
|
323
323
|
property: 'inset',
|
|
324
324
|
value: 'auto'
|
|
325
325
|
},
|
|
326
|
-
// Ensures the popup's margin box overflows the viewport when its span region is too
|
|
327
|
-
// narrow, triggering position-try-fallbacks correctly rather than wrapping content.
|
|
328
|
-
{
|
|
329
|
-
property: 'min-inline-size',
|
|
330
|
-
value: 'max-content'
|
|
331
|
-
},
|
|
332
326
|
// Main-axis gap between the popover and its trigger
|
|
333
327
|
{
|
|
334
328
|
property: gap.property,
|
|
@@ -488,7 +482,19 @@ export function useAnchorPosition({
|
|
|
488
482
|
// ResizeObserver fires once the browser has real dimensions.
|
|
489
483
|
// Self-disconnects after one valid measurement; ongoing
|
|
490
484
|
// scroll/resize is handled by the window listeners below.
|
|
491
|
-
|
|
485
|
+
// `ResizeObserver` is missing in some non-DOM jest environments
|
|
486
|
+
// (e.g. post-office's `node` environment). Fall back to a no-op
|
|
487
|
+
// observer there — the scroll/resize listeners below still keep
|
|
488
|
+
// the popover positioned in the rare case the consumer also
|
|
489
|
+
// polyfilled `showPopover` but not `ResizeObserver`. Real
|
|
490
|
+
// browsers always have it.
|
|
491
|
+
const NoopResizeObserver = class {
|
|
492
|
+
observe() {}
|
|
493
|
+
unobserve() {}
|
|
494
|
+
disconnect() {}
|
|
495
|
+
};
|
|
496
|
+
const ResizeObserverImpl = typeof ResizeObserver !== 'undefined' ? ResizeObserver : NoopResizeObserver;
|
|
497
|
+
const resizeObserver = new ResizeObserverImpl(() => {
|
|
492
498
|
if (popover.offsetWidth > 0 && popover.offsetHeight > 0) {
|
|
493
499
|
update();
|
|
494
500
|
resizeObserver.disconnect();
|
|
@@ -104,8 +104,8 @@ export function useAnimatedVisibility({
|
|
|
104
104
|
if (isOpen || !showChildren || !willAnimate) {
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
|
-
const
|
|
108
|
-
if (!
|
|
107
|
+
const element = elementRef.current;
|
|
108
|
+
if (!element) {
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
111
|
function finish() {
|
|
@@ -119,7 +119,7 @@ export function useAnimatedVisibility({
|
|
|
119
119
|
const fallbackId = setTimeout(finish, ((_preset$exitDurationM = preset === null || preset === void 0 ? void 0 : preset.exitDurationMs) !== null && _preset$exitDurationM !== void 0 ? _preset$exitDurationM : 0) + 50);
|
|
120
120
|
|
|
121
121
|
// Primary: listen for the CSS transition to complete
|
|
122
|
-
const unbind = bind(
|
|
122
|
+
const unbind = bind(element, {
|
|
123
123
|
type: 'transitionend',
|
|
124
124
|
listener: finish,
|
|
125
125
|
options: {
|
|
@@ -34,7 +34,7 @@ function roleRequiresFocusWrap(role) {
|
|
|
34
34
|
* elements within the container using wrapping navigation.
|
|
35
35
|
*
|
|
36
36
|
* Light dismiss (Escape, click outside) continues to work natively
|
|
37
|
-
* via `popover="auto"`
|
|
37
|
+
* via `popover="auto"` - this hook only intercepts Tab.
|
|
38
38
|
*
|
|
39
39
|
* Always call this hook unconditionally. The listener is only
|
|
40
40
|
* attached when the role requires focus wrapping, and is cleaned
|
|
@@ -45,11 +45,11 @@ export function useFocusWrap({
|
|
|
45
45
|
role
|
|
46
46
|
}) {
|
|
47
47
|
useEffect(() => {
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
48
|
+
const element = elementRef.current;
|
|
49
|
+
if (!element || !roleRequiresFocusWrap(role)) {
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
|
-
const unbind = bind(
|
|
52
|
+
const unbind = bind(element, {
|
|
53
53
|
type: 'keydown',
|
|
54
54
|
listener: event => {
|
|
55
55
|
if (event.key !== 'Tab') {
|
|
@@ -61,7 +61,7 @@ export function useFocusWrap({
|
|
|
61
61
|
// Try to move to the next/previous focusable element relative
|
|
62
62
|
// to the currently focused element within the container.
|
|
63
63
|
const next = getNextFocusable({
|
|
64
|
-
container:
|
|
64
|
+
container: element,
|
|
65
65
|
direction
|
|
66
66
|
});
|
|
67
67
|
if (next) {
|
|
@@ -73,9 +73,9 @@ export function useFocusWrap({
|
|
|
73
73
|
// focused element isn't in the focusable list). Fall back to
|
|
74
74
|
// the first or last focusable element depending on direction.
|
|
75
75
|
const fallback = direction === 'forwards' ? getFirstFocusable({
|
|
76
|
-
container:
|
|
76
|
+
container: element
|
|
77
77
|
}) : getLastFocusable({
|
|
78
|
-
container:
|
|
78
|
+
container: element
|
|
79
79
|
});
|
|
80
80
|
if (fallback) {
|
|
81
81
|
fallback.focus();
|
|
@@ -13,7 +13,7 @@ import { getFirstFocusable } from '../focus/focus';
|
|
|
13
13
|
* - no role / other roles: no focus movement
|
|
14
14
|
*
|
|
15
15
|
* Note: React 18's `autoFocus` JSX prop does NOT set the HTML `autofocus`
|
|
16
|
-
* attribute
|
|
16
|
+
* attribute - it only calls `.focus()` imperatively during commit, which
|
|
17
17
|
* has no effect on hidden popover content. Consumers must set the native
|
|
18
18
|
* attribute via a ref callback (e.g. `node.setAttribute('autofocus', '')`).
|
|
19
19
|
* React 19+ reflects the JSX prop as the HTML attribute automatically.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { setStyle } from './set-style';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sets the width of a popover element relative to its anchor element.
|
|
6
|
+
*
|
|
7
|
+
* - `'match-anchor'` → `width: anchor-size(width)` — popover matches anchor width exactly.
|
|
8
|
+
* - `'min-anchor'` → `min-width: anchor-size(width)` — popover is at least as wide as anchor,
|
|
9
|
+
* but can grow wider.
|
|
10
|
+
* - `'none'` → no explicit width is set (default behaviour)
|
|
11
|
+
*
|
|
12
|
+
* Uses CSS `anchor-size(width)` when the browser supports it, falling back to a
|
|
13
|
+
* one-off measurement of `anchor.offsetWidth`.
|
|
14
|
+
*
|
|
15
|
+
* Designed to be composed alongside `useAnchorPosition` for top-layer popovers.
|
|
16
|
+
*/
|
|
17
|
+
export function useWidthFromAnchor({
|
|
18
|
+
mode,
|
|
19
|
+
popoverRef,
|
|
20
|
+
anchorRef
|
|
21
|
+
}) {
|
|
22
|
+
useLayoutEffect(() => {
|
|
23
|
+
const node = popoverRef.current;
|
|
24
|
+
if (!node) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Feature-detect `anchor-size()` specifically (not just `anchor-name`)
|
|
29
|
+
// so the JS fallback fires correctly in browsers that support anchor
|
|
30
|
+
// positioning but not anchor sizing.
|
|
31
|
+
const supportsAnchorSize = typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('width', 'anchor-size(width)');
|
|
32
|
+
if (mode === 'match-anchor') {
|
|
33
|
+
// Set an exact width matching the anchor.
|
|
34
|
+
// `min-inline-size` is intentionally NOT set here — it would override the
|
|
35
|
+
// explicit width constraint, allowing the popover to grow past the anchor's width.
|
|
36
|
+
if (supportsAnchorSize) {
|
|
37
|
+
return setStyle({
|
|
38
|
+
el: node,
|
|
39
|
+
styles: [{
|
|
40
|
+
property: 'width',
|
|
41
|
+
value: 'anchor-size(width)'
|
|
42
|
+
}]
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// JS fallback: one-off read of the anchor's width.
|
|
47
|
+
const anchor = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current;
|
|
48
|
+
if (anchor) {
|
|
49
|
+
return setStyle({
|
|
50
|
+
el: node,
|
|
51
|
+
styles: [{
|
|
52
|
+
property: 'width',
|
|
53
|
+
value: `${anchor.offsetWidth}px`
|
|
54
|
+
}]
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// For 'min-anchor' and 'none', apply `min-inline-size: max-content` to ensure
|
|
61
|
+
// correct position-try-fallbacks behaviour (popover margin box overflows the
|
|
62
|
+
// viewport when its span region is too narrow, rather than wrapping content).
|
|
63
|
+
const minInlineSizeStyle = {
|
|
64
|
+
property: 'min-inline-size',
|
|
65
|
+
value: 'max-content'
|
|
66
|
+
};
|
|
67
|
+
if (mode === 'min-anchor') {
|
|
68
|
+
// Set a minimum width matching the anchor. The popover can grow wider
|
|
69
|
+
// if its content requires it.
|
|
70
|
+
if (supportsAnchorSize) {
|
|
71
|
+
return setStyle({
|
|
72
|
+
el: node,
|
|
73
|
+
styles: [minInlineSizeStyle, {
|
|
74
|
+
property: 'min-width',
|
|
75
|
+
value: 'anchor-size(width)'
|
|
76
|
+
}]
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const anchor = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current;
|
|
80
|
+
if (anchor) {
|
|
81
|
+
return setStyle({
|
|
82
|
+
el: node,
|
|
83
|
+
styles: [minInlineSizeStyle, {
|
|
84
|
+
property: 'min-width',
|
|
85
|
+
value: `${anchor.offsetWidth}px`
|
|
86
|
+
}]
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// No anchor available in JS fallback — still apply min-inline-size.
|
|
91
|
+
return setStyle({
|
|
92
|
+
el: node,
|
|
93
|
+
styles: [minInlineSizeStyle]
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 'none' mode: no explicit width, but still apply `min-inline-size: max-content`.
|
|
98
|
+
return setStyle({
|
|
99
|
+
el: node,
|
|
100
|
+
styles: [minInlineSizeStyle]
|
|
101
|
+
});
|
|
102
|
+
}, [mode, popoverRef, anchorRef]);
|
|
103
|
+
}
|
|
@@ -33,7 +33,9 @@ const styles = {
|
|
|
33
33
|
* Unopinionated top-layer primitive.
|
|
34
34
|
*
|
|
35
35
|
* Manages only visibility (isOpen) and animation (animate). Has no knowledge
|
|
36
|
-
* of positioning — compose with `useAnchorPosition` for anchor positioning
|
|
36
|
+
* of positioning — compose with `useAnchorPosition` for anchor positioning
|
|
37
|
+
* and optionally `useWidthFromAnchor` for setting the width of a popover element
|
|
38
|
+
* relative to its anchor trigger.
|
|
37
39
|
*
|
|
38
40
|
* Visibility is driven by the `isOpen` prop:
|
|
39
41
|
* - `isOpen={true}` → `showPopover()`, entry animation via `@starting-style`
|
|
@@ -116,21 +118,21 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
|
|
|
116
118
|
// the browser processes light dismiss), read in the toggle handler.
|
|
117
119
|
const closeReasonRef = useRef('light-dismiss');
|
|
118
120
|
const handleToggle = useCallback(event => {
|
|
119
|
-
const
|
|
121
|
+
const element = ownRef.current;
|
|
120
122
|
if (event.newState === 'open') {
|
|
121
|
-
if (
|
|
123
|
+
if (element) {
|
|
122
124
|
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange({
|
|
123
125
|
isOpen: true,
|
|
124
|
-
element:
|
|
126
|
+
element: element
|
|
125
127
|
});
|
|
126
128
|
}
|
|
127
129
|
return;
|
|
128
130
|
}
|
|
129
131
|
if (event.newState === 'closed') {
|
|
130
|
-
if (
|
|
132
|
+
if (element) {
|
|
131
133
|
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange({
|
|
132
134
|
isOpen: false,
|
|
133
|
-
element:
|
|
135
|
+
element: element
|
|
134
136
|
});
|
|
135
137
|
}
|
|
136
138
|
|
|
@@ -148,13 +150,13 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
|
|
|
148
150
|
}
|
|
149
151
|
}, [onClose, onOpenChange]);
|
|
150
152
|
useEffect(() => {
|
|
151
|
-
const
|
|
152
|
-
if (!
|
|
153
|
+
const element = ownRef.current;
|
|
154
|
+
if (!element) {
|
|
153
155
|
return;
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
// Capture-phase: tag Escape before the browser processes light dismiss.
|
|
157
|
-
const unbindEscape = bind(
|
|
159
|
+
const unbindEscape = bind(element, {
|
|
158
160
|
type: 'keydown',
|
|
159
161
|
listener: event => {
|
|
160
162
|
if (event.key === 'Escape') {
|
|
@@ -165,7 +167,7 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
|
|
|
165
167
|
capture: true
|
|
166
168
|
}
|
|
167
169
|
});
|
|
168
|
-
const unbindToggle = bind(
|
|
170
|
+
const unbindToggle = bind(element, {
|
|
169
171
|
type: 'toggle',
|
|
170
172
|
listener: handleToggle
|
|
171
173
|
});
|
|
@@ -6,12 +6,13 @@ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
|
|
|
6
6
|
import { roleToAriaHasPopup } from '../internal/role-types';
|
|
7
7
|
import { useAnchorPosition } from '../internal/use-anchor-position';
|
|
8
8
|
import { usePresetStyles } from '../internal/use-preset-styles';
|
|
9
|
+
import { useWidthFromAnchor } from '../internal/use-width-from-anchor';
|
|
9
10
|
import { Popover } from '../popover/popover';
|
|
10
11
|
import { useMaybePopupContext } from './popup-context';
|
|
11
12
|
/**
|
|
12
13
|
* Thin context wrapper that composes `Popover` + `useAnchorPosition`.
|
|
13
14
|
*
|
|
14
|
-
* Used inside the `<Popup>` compound
|
|
15
|
+
* Used inside the `<Popup>` compound - reads triggerRef, placement, isOpen,
|
|
15
16
|
* and onClose from context. For standalone usage (tooltip, spotlight, flag),
|
|
16
17
|
* use `Popover` + `useAnchorPosition` directly.
|
|
17
18
|
*/
|
|
@@ -23,7 +24,7 @@ export const PopupContent = /*#__PURE__*/forwardRef(function PopupContent({
|
|
|
23
24
|
animate,
|
|
24
25
|
arrow,
|
|
25
26
|
mode,
|
|
26
|
-
|
|
27
|
+
widthFromAnchor = 'none',
|
|
27
28
|
xcss,
|
|
28
29
|
isOpen: isOpenProp,
|
|
29
30
|
triggerRef: triggerRefProp,
|
|
@@ -88,7 +89,7 @@ export const PopupContent = /*#__PURE__*/forwardRef(function PopupContent({
|
|
|
88
89
|
// ── Nested popover focus restoration fallback ──
|
|
89
90
|
// The browser only captures `previouslyFocusedElement` for the
|
|
90
91
|
// outermost popover="auto" in the stack. Inner (nested) popovers
|
|
91
|
-
// have shouldRestoreFocus=false, so the browser
|
|
92
|
+
// have shouldRestoreFocus=false, so the browser will not restore
|
|
92
93
|
// focus when they close.
|
|
93
94
|
//
|
|
94
95
|
// We detect this case by checking if focus is still inside the
|
|
@@ -131,39 +132,11 @@ export const PopupContent = /*#__PURE__*/forwardRef(function PopupContent({
|
|
|
131
132
|
forceFallbackPositioning,
|
|
132
133
|
arrow: arrowPreset !== null && arrowPreset !== void 0 ? arrowPreset : undefined
|
|
133
134
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
useLayoutEffect(() => {
|
|
140
|
-
if (width === 'content') {
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
const node = popoverRef.current;
|
|
144
|
-
if (!node) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const cssProperty = width === 'trigger' ? 'width' : 'min-width';
|
|
148
|
-
const anchorValue = 'anchor-size(width)';
|
|
149
|
-
|
|
150
|
-
// Feature-detect `anchor-size()` specifically (not just `anchor-name`)
|
|
151
|
-
// so the JS fallback fires correctly in browsers that support anchor
|
|
152
|
-
// positioning but not anchor sizing.
|
|
153
|
-
const supportsAnchorSize = typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('width', 'anchor-size(width)');
|
|
154
|
-
if (supportsAnchorSize) {
|
|
155
|
-
node.style.setProperty(cssProperty, anchorValue);
|
|
156
|
-
} else {
|
|
157
|
-
// JS fallback: one-off read of the trigger's width.
|
|
158
|
-
const trigger = triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current;
|
|
159
|
-
if (trigger) {
|
|
160
|
-
node.style.setProperty(cssProperty, `${trigger.offsetWidth}px`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return () => {
|
|
164
|
-
node.style.removeProperty(cssProperty);
|
|
165
|
-
};
|
|
166
|
-
}, [width, popoverRef, triggerRef]);
|
|
135
|
+
useWidthFromAnchor({
|
|
136
|
+
mode: widthFromAnchor,
|
|
137
|
+
popoverRef,
|
|
138
|
+
anchorRef: triggerRef
|
|
139
|
+
});
|
|
167
140
|
|
|
168
141
|
// Merge: the popoverRef (shared with trigger + positioning) and the forwarded ref.
|
|
169
142
|
const combinedRef = mergeRefs([popoverRef, ref]);
|
|
@@ -18,7 +18,7 @@ import { usePopupContext } from './popup-context';
|
|
|
18
18
|
* from opening.
|
|
19
19
|
*
|
|
20
20
|
* If you need Compiled `css` on the trigger, use `Popup.TriggerFunction`
|
|
21
|
-
* instead
|
|
21
|
+
* instead - it hands you raw props via a render-prop and avoids
|
|
22
22
|
* `cloneElement` entirely.
|
|
23
23
|
*/
|
|
24
24
|
export function PopupTrigger({
|
|
@@ -34,7 +34,7 @@ export function PopupTrigger({
|
|
|
34
34
|
const childOnClick = /*#__PURE__*/isValidElement(children) ? children.props.onClick : undefined;
|
|
35
35
|
|
|
36
36
|
// Use a ref to track the latest isOpen value so the click handler
|
|
37
|
-
//
|
|
37
|
+
// does not need isOpen in its dependency array (which would recreate
|
|
38
38
|
// the handler on every open/close, breaking cloneElement identity).
|
|
39
39
|
const isOpenRef = React.useRef(isOpen);
|
|
40
40
|
isOpenRef.current = isOpen;
|
|
@@ -21,7 +21,7 @@ export function useSimpleLightDismiss({
|
|
|
21
21
|
isOpen,
|
|
22
22
|
onClose
|
|
23
23
|
}) {
|
|
24
|
-
// Keep a stable ref to onClose so the effect
|
|
24
|
+
// Keep a stable ref to onClose so the effect does not re-bind on every render.
|
|
25
25
|
const onCloseRef = useRef(onClose);
|
|
26
26
|
onCloseRef.current = onClose;
|
|
27
27
|
useEffect(() => {
|
package/dist/esm/arrow/index.js
CHANGED
|
@@ -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
|
|
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/
|
|
@@ -39,7 +39,7 @@ function positionAreaToRuleName(_ref) {
|
|
|
39
39
|
* that change both `position-area` and `margin` when flipping.
|
|
40
40
|
*
|
|
41
41
|
* This is required for the arrow technique because built-in keywords
|
|
42
|
-
* (`flip-block`, `flip-inline`) only flip `position-area`
|
|
42
|
+
* (`flip-block`, `flip-inline`) only flip `position-area` - they do
|
|
43
43
|
* not update the margin direction needed to reveal the correct arrow.
|
|
44
44
|
*/
|
|
45
45
|
function getTryFallbacks(_ref2) {
|
|
@@ -119,7 +119,12 @@ export var Dialog = /*#__PURE__*/forwardRef(function Dialog(_ref, ref) {
|
|
|
119
119
|
var escapedId = CSS.escape(dialogId);
|
|
120
120
|
return /*#__PURE__*/React.createElement("dialog", _extends({
|
|
121
121
|
ref: combinedRef,
|
|
122
|
-
id: dialogId
|
|
122
|
+
id: dialogId
|
|
123
|
+
// Modern browsers infer modal semantics from `.showModal()` but
|
|
124
|
+
// some assistive tech still keys off `aria-modal="true"`. Setting
|
|
125
|
+
// it explicitly is recommended by WAI-ARIA APG dialog pattern.
|
|
126
|
+
,
|
|
127
|
+
"aria-modal": "true",
|
|
123
128
|
"aria-label": label,
|
|
124
129
|
"aria-labelledby": label ? undefined : labelledBy,
|
|
125
130
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useWidthFromAnchor } from '../internal/use-width-from-anchor';
|
package/dist/esm/focus/focus.js
CHANGED
|
@@ -38,10 +38,10 @@ var selectors = function () {
|
|
|
38
38
|
function getFocusables(_ref) {
|
|
39
39
|
var container = _ref.container,
|
|
40
40
|
filter = _ref.filter;
|
|
41
|
-
return Array.from(container.querySelectorAll(selectors.focusable)).filter(function (
|
|
42
|
-
return
|
|
43
|
-
}).filter(function (
|
|
44
|
-
return !filter || filter(
|
|
41
|
+
return Array.from(container.querySelectorAll(selectors.focusable)).filter(function (element) {
|
|
42
|
+
return element instanceof HTMLElement;
|
|
43
|
+
}).filter(function (element) {
|
|
44
|
+
return !filter || filter(element, container);
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -16,8 +16,8 @@ export function combine() {
|
|
|
16
16
|
cleanupFns[_key] = arguments[_key];
|
|
17
17
|
}
|
|
18
18
|
return function cleanup() {
|
|
19
|
-
cleanupFns.forEach(function (
|
|
20
|
-
return
|
|
19
|
+
cleanupFns.forEach(function (callback) {
|
|
20
|
+
return callback === null || callback === void 0 ? void 0 : callback();
|
|
21
21
|
});
|
|
22
22
|
};
|
|
23
23
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
2
|
+
import _createClass from "@babel/runtime/helpers/createClass";
|
|
1
3
|
/**
|
|
2
4
|
* Anchor positioning for top-layer popovers. See
|
|
3
5
|
* `notes/architecture/positioning.md` for the full model.
|
|
@@ -314,12 +316,6 @@ export function useAnchorPosition(_ref5) {
|
|
|
314
316
|
property: 'inset',
|
|
315
317
|
value: 'auto'
|
|
316
318
|
},
|
|
317
|
-
// Ensures the popup's margin box overflows the viewport when its span region is too
|
|
318
|
-
// narrow, triggering position-try-fallbacks correctly rather than wrapping content.
|
|
319
|
-
{
|
|
320
|
-
property: 'min-inline-size',
|
|
321
|
-
value: 'max-content'
|
|
322
|
-
},
|
|
323
319
|
// Main-axis gap between the popover and its trigger
|
|
324
320
|
{
|
|
325
321
|
property: gap.property,
|
|
@@ -480,7 +476,29 @@ export function useAnchorPosition(_ref5) {
|
|
|
480
476
|
// ResizeObserver fires once the browser has real dimensions.
|
|
481
477
|
// Self-disconnects after one valid measurement; ongoing
|
|
482
478
|
// scroll/resize is handled by the window listeners below.
|
|
483
|
-
|
|
479
|
+
// `ResizeObserver` is missing in some non-DOM jest environments
|
|
480
|
+
// (e.g. post-office's `node` environment). Fall back to a no-op
|
|
481
|
+
// observer there — the scroll/resize listeners below still keep
|
|
482
|
+
// the popover positioned in the rare case the consumer also
|
|
483
|
+
// polyfilled `showPopover` but not `ResizeObserver`. Real
|
|
484
|
+
// browsers always have it.
|
|
485
|
+
var NoopResizeObserver = /*#__PURE__*/function () {
|
|
486
|
+
function NoopResizeObserver() {
|
|
487
|
+
_classCallCheck(this, NoopResizeObserver);
|
|
488
|
+
}
|
|
489
|
+
return _createClass(NoopResizeObserver, [{
|
|
490
|
+
key: "observe",
|
|
491
|
+
value: function observe() {}
|
|
492
|
+
}, {
|
|
493
|
+
key: "unobserve",
|
|
494
|
+
value: function unobserve() {}
|
|
495
|
+
}, {
|
|
496
|
+
key: "disconnect",
|
|
497
|
+
value: function disconnect() {}
|
|
498
|
+
}]);
|
|
499
|
+
}();
|
|
500
|
+
var ResizeObserverImpl = typeof ResizeObserver !== 'undefined' ? ResizeObserver : NoopResizeObserver;
|
|
501
|
+
var resizeObserver = new ResizeObserverImpl(function () {
|
|
484
502
|
if (popover.offsetWidth > 0 && popover.offsetHeight > 0) {
|
|
485
503
|
update();
|
|
486
504
|
resizeObserver.disconnect();
|
|
@@ -107,8 +107,8 @@ export function useAnimatedVisibility(_ref) {
|
|
|
107
107
|
if (isOpen || !showChildren || !willAnimate) {
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
var
|
|
111
|
-
if (!
|
|
110
|
+
var element = elementRef.current;
|
|
111
|
+
if (!element) {
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
function finish() {
|
|
@@ -122,7 +122,7 @@ export function useAnimatedVisibility(_ref) {
|
|
|
122
122
|
var fallbackId = setTimeout(finish, ((_preset$exitDurationM = preset === null || preset === void 0 ? void 0 : preset.exitDurationMs) !== null && _preset$exitDurationM !== void 0 ? _preset$exitDurationM : 0) + 50);
|
|
123
123
|
|
|
124
124
|
// Primary: listen for the CSS transition to complete
|
|
125
|
-
var unbind = bind(
|
|
125
|
+
var unbind = bind(element, {
|
|
126
126
|
type: 'transitionend',
|
|
127
127
|
listener: finish,
|
|
128
128
|
options: {
|
|
@@ -34,7 +34,7 @@ function roleRequiresFocusWrap(role) {
|
|
|
34
34
|
* elements within the container using wrapping navigation.
|
|
35
35
|
*
|
|
36
36
|
* Light dismiss (Escape, click outside) continues to work natively
|
|
37
|
-
* via `popover="auto"`
|
|
37
|
+
* via `popover="auto"` - this hook only intercepts Tab.
|
|
38
38
|
*
|
|
39
39
|
* Always call this hook unconditionally. The listener is only
|
|
40
40
|
* attached when the role requires focus wrapping, and is cleaned
|
|
@@ -44,11 +44,11 @@ export function useFocusWrap(_ref) {
|
|
|
44
44
|
var elementRef = _ref.elementRef,
|
|
45
45
|
role = _ref.role;
|
|
46
46
|
useEffect(function () {
|
|
47
|
-
var
|
|
48
|
-
if (!
|
|
47
|
+
var element = elementRef.current;
|
|
48
|
+
if (!element || !roleRequiresFocusWrap(role)) {
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
-
var unbind = bind(
|
|
51
|
+
var unbind = bind(element, {
|
|
52
52
|
type: 'keydown',
|
|
53
53
|
listener: function listener(event) {
|
|
54
54
|
if (event.key !== 'Tab') {
|
|
@@ -60,7 +60,7 @@ export function useFocusWrap(_ref) {
|
|
|
60
60
|
// Try to move to the next/previous focusable element relative
|
|
61
61
|
// to the currently focused element within the container.
|
|
62
62
|
var next = getNextFocusable({
|
|
63
|
-
container:
|
|
63
|
+
container: element,
|
|
64
64
|
direction: direction
|
|
65
65
|
});
|
|
66
66
|
if (next) {
|
|
@@ -72,9 +72,9 @@ export function useFocusWrap(_ref) {
|
|
|
72
72
|
// focused element isn't in the focusable list). Fall back to
|
|
73
73
|
// the first or last focusable element depending on direction.
|
|
74
74
|
var fallback = direction === 'forwards' ? getFirstFocusable({
|
|
75
|
-
container:
|
|
75
|
+
container: element
|
|
76
76
|
}) : getLastFocusable({
|
|
77
|
-
container:
|
|
77
|
+
container: element
|
|
78
78
|
});
|
|
79
79
|
if (fallback) {
|
|
80
80
|
fallback.focus();
|
|
@@ -13,7 +13,7 @@ import { getFirstFocusable } from '../focus/focus';
|
|
|
13
13
|
* - no role / other roles: no focus movement
|
|
14
14
|
*
|
|
15
15
|
* Note: React 18's `autoFocus` JSX prop does NOT set the HTML `autofocus`
|
|
16
|
-
* attribute
|
|
16
|
+
* attribute - it only calls `.focus()` imperatively during commit, which
|
|
17
17
|
* has no effect on hidden popover content. Consumers must set the native
|
|
18
18
|
* attribute via a ref callback (e.g. `node.setAttribute('autofocus', '')`).
|
|
19
19
|
* React 19+ reflects the JSX prop as the HTML attribute automatically.
|