@atlaskit/top-layer 4.4.5 → 5.0.1
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 +50 -0
- package/README.md +13 -4
- package/dist/cjs/dialog/dialog-content.js +2 -0
- package/dist/cjs/entry-points/use-popover-id.js +5 -10
- package/dist/cjs/internal/anchor-positioning/apply-anchor-positioning.js +86 -0
- package/dist/cjs/internal/anchor-positioning/cross-axis-shift-margins.js +34 -0
- package/dist/cjs/internal/anchor-positioning/edge-margin.js +21 -0
- package/dist/cjs/internal/anchor-positioning/fit-margins.js +77 -0
- package/dist/cjs/internal/anchor-positioning/get-existing-anchor-name.js +18 -0
- package/dist/cjs/internal/{placement-to-position-area.js → anchor-positioning/placement-to-position-area.js} +11 -11
- package/dist/cjs/internal/anchor-positioning/placement-to-try-fallbacks.js +95 -0
- package/dist/cjs/internal/anchored-popover-size.js +345 -0
- package/dist/cjs/internal/get-aria-for-trigger.js +7 -13
- package/dist/cjs/internal/is-native-element-open.js +19 -0
- package/dist/cjs/internal/{anchor-positioning-fallback.js → javascript-fallback/anchor-positioning-fallback.js} +12 -33
- package/dist/cjs/internal/javascript-fallback/apply-javascript-fallback-positioning.js +297 -0
- package/dist/cjs/internal/javascript-fallback/resolve-css-length-to-pixels.js +90 -0
- package/dist/cjs/internal/javascript-fallback/warn-fallback-failure.js +19 -0
- package/dist/cjs/internal/resolve-placement.js +14 -6
- package/dist/cjs/internal/supports-anchor-positioning.js +17 -0
- package/dist/cjs/internal/supports-anchor-size.js +17 -0
- package/dist/cjs/internal/supports-css-declaration.js +25 -0
- package/dist/cjs/internal/surface-reset.js +5 -0
- package/dist/cjs/internal/use-anchored-popover-at-point.js +124 -0
- package/dist/cjs/internal/use-anchored-popover.js +277 -0
- package/dist/cjs/internal/use-animated-visibility.js +11 -23
- package/dist/cjs/popover/popover.compiled.css +4 -0
- package/dist/cjs/popover/popover.js +63 -61
- package/dist/cjs/testing/polyfill.js +6 -7
- package/dist/es2019/dialog/dialog-content.js +2 -0
- package/dist/es2019/entry-points/use-popover-id.js +6 -10
- package/dist/es2019/internal/anchor-positioning/apply-anchor-positioning.js +83 -0
- package/dist/es2019/internal/anchor-positioning/cross-axis-shift-margins.js +29 -0
- package/dist/es2019/internal/anchor-positioning/edge-margin.js +18 -0
- package/dist/es2019/internal/anchor-positioning/fit-margins.js +69 -0
- package/dist/es2019/internal/anchor-positioning/get-existing-anchor-name.js +12 -0
- package/dist/es2019/internal/{placement-to-position-area.js → anchor-positioning/placement-to-position-area.js} +9 -9
- package/dist/es2019/internal/anchor-positioning/placement-to-try-fallbacks.js +94 -0
- package/dist/es2019/internal/anchored-popover-size.js +343 -0
- package/dist/es2019/internal/get-aria-for-trigger.js +7 -13
- package/dist/es2019/internal/is-native-element-open.js +14 -0
- package/dist/es2019/internal/{anchor-positioning-fallback.js → javascript-fallback/anchor-positioning-fallback.js} +12 -33
- package/dist/es2019/internal/javascript-fallback/apply-javascript-fallback-positioning.js +291 -0
- package/dist/es2019/internal/javascript-fallback/resolve-css-length-to-pixels.js +87 -0
- package/dist/es2019/internal/javascript-fallback/warn-fallback-failure.js +14 -0
- package/dist/es2019/internal/resolve-placement.js +14 -6
- package/dist/es2019/internal/supports-anchor-positioning.js +12 -0
- package/dist/es2019/internal/supports-anchor-size.js +12 -0
- package/dist/es2019/internal/supports-css-declaration.js +20 -0
- package/dist/es2019/internal/surface-reset.js +1 -0
- package/dist/es2019/internal/use-anchored-popover-at-point.js +116 -0
- package/dist/es2019/internal/use-anchored-popover.js +265 -0
- package/dist/es2019/internal/use-animated-visibility.js +1 -14
- package/dist/es2019/popover/popover.compiled.css +4 -0
- package/dist/es2019/popover/popover.js +69 -61
- package/dist/es2019/testing/polyfill.js +6 -7
- package/dist/esm/dialog/dialog-content.js +2 -0
- package/dist/esm/entry-points/use-popover-id.js +6 -10
- package/dist/esm/internal/anchor-positioning/apply-anchor-positioning.js +80 -0
- package/dist/esm/internal/anchor-positioning/cross-axis-shift-margins.js +28 -0
- package/dist/esm/internal/anchor-positioning/edge-margin.js +15 -0
- package/dist/esm/internal/anchor-positioning/fit-margins.js +71 -0
- package/dist/esm/internal/anchor-positioning/get-existing-anchor-name.js +12 -0
- package/dist/esm/internal/{placement-to-position-area.js → anchor-positioning/placement-to-position-area.js} +11 -12
- package/dist/esm/internal/anchor-positioning/placement-to-try-fallbacks.js +89 -0
- package/dist/esm/internal/anchored-popover-size.js +338 -0
- package/dist/esm/internal/get-aria-for-trigger.js +7 -13
- package/dist/esm/internal/is-native-element-open.js +13 -0
- package/dist/esm/internal/{anchor-positioning-fallback.js → javascript-fallback/anchor-positioning-fallback.js} +12 -33
- package/dist/esm/internal/javascript-fallback/apply-javascript-fallback-positioning.js +290 -0
- package/dist/esm/internal/javascript-fallback/resolve-css-length-to-pixels.js +85 -0
- package/dist/esm/internal/javascript-fallback/warn-fallback-failure.js +13 -0
- package/dist/esm/internal/resolve-placement.js +14 -6
- package/dist/esm/internal/supports-anchor-positioning.js +12 -0
- package/dist/esm/internal/supports-anchor-size.js +12 -0
- package/dist/esm/internal/supports-css-declaration.js +19 -0
- package/dist/esm/internal/surface-reset.js +1 -0
- package/dist/esm/internal/use-anchored-popover-at-point.js +118 -0
- package/dist/esm/internal/use-anchored-popover.js +271 -0
- package/dist/esm/internal/use-animated-visibility.js +10 -22
- package/dist/esm/popover/popover.compiled.css +4 -0
- package/dist/esm/popover/popover.js +63 -61
- package/dist/esm/testing/polyfill.js +6 -7
- package/dist/types/entry-points/placement-map.d.ts +2 -2
- package/dist/types/entry-points/use-popover-id.d.ts +3 -7
- package/dist/types/internal/anchor-positioning/apply-anchor-positioning.d.ts +26 -0
- package/dist/types/internal/anchor-positioning/cross-axis-shift-margins.d.ts +19 -0
- package/dist/types/internal/anchor-positioning/edge-margin.d.ts +10 -0
- package/dist/types/internal/anchor-positioning/fit-margins.d.ts +30 -0
- package/dist/types/internal/anchor-positioning/get-existing-anchor-name.d.ts +8 -0
- package/dist/types/internal/{placement-to-position-area.d.ts → anchor-positioning/placement-to-position-area.d.ts} +10 -6
- package/dist/types/internal/anchor-positioning/placement-to-try-fallbacks.d.ts +30 -0
- package/dist/types/internal/anchored-popover-size.d.ts +126 -0
- package/dist/types/internal/get-aria-for-trigger.d.ts +13 -25
- package/dist/types/internal/is-native-element-open.d.ts +3 -0
- package/dist/types/internal/javascript-fallback/anchor-positioning-fallback.d.ts +36 -0
- package/dist/types/internal/javascript-fallback/apply-javascript-fallback-positioning.d.ts +15 -0
- package/dist/types/internal/javascript-fallback/resolve-css-length-to-pixels.d.ts +20 -0
- package/dist/types/internal/javascript-fallback/warn-fallback-failure.d.ts +7 -0
- package/dist/types/internal/resolve-placement.d.ts +18 -2
- package/dist/types/internal/supports-anchor-positioning.d.ts +5 -0
- package/dist/types/internal/supports-anchor-size.d.ts +5 -0
- package/dist/types/internal/supports-css-declaration.d.ts +11 -0
- package/dist/types/internal/surface-reset.d.ts +25 -0
- package/dist/types/internal/use-anchored-popover-at-point.d.ts +48 -0
- package/dist/types/internal/use-anchored-popover.d.ts +143 -0
- package/dist/types/popover/index.d.ts +2 -1
- package/dist/types/popover/popover.d.ts +1 -1
- package/dist/types/popover/types.d.ts +15 -17
- package/dist/types/testing/polyfill.d.ts +3 -3
- package/package.json +2 -2
- package/use-anchored-popover/package.json +10 -0
- package/use-anchored-popover-at-point/package.json +10 -0
- package/dist/cjs/internal/placement-to-try-fallbacks.js +0 -108
- package/dist/cjs/internal/resolve-css-length-to-pixels.js +0 -104
- package/dist/cjs/internal/use-anchor-position-at-point.js +0 -129
- package/dist/cjs/internal/use-anchor-position.js +0 -501
- package/dist/cjs/internal/use-width-from-anchor.js +0 -124
- package/dist/es2019/internal/placement-to-try-fallbacks.js +0 -108
- package/dist/es2019/internal/resolve-css-length-to-pixels.js +0 -101
- package/dist/es2019/internal/use-anchor-position-at-point.js +0 -124
- package/dist/es2019/internal/use-anchor-position.js +0 -480
- package/dist/es2019/internal/use-width-from-anchor.js +0 -120
- package/dist/esm/internal/placement-to-try-fallbacks.js +0 -103
- package/dist/esm/internal/resolve-css-length-to-pixels.js +0 -98
- package/dist/esm/internal/use-anchor-position-at-point.js +0 -124
- package/dist/esm/internal/use-anchor-position.js +0 -494
- package/dist/esm/internal/use-width-from-anchor.js +0 -118
- package/dist/types/internal/anchor-positioning-fallback.d.ts +0 -51
- package/dist/types/internal/placement-to-try-fallbacks.d.ts +0 -13
- package/dist/types/internal/resolve-css-length-to-pixels.d.ts +0 -20
- package/dist/types/internal/use-anchor-position-at-point.d.ts +0 -67
- package/dist/types/internal/use-anchor-position.d.ts +0 -87
- package/dist/types/internal/use-width-from-anchor.d.ts +0 -19
- package/use-anchor-position/package.json +0 -10
- package/use-anchor-position-at-point/package.json +0 -10
- package/use-width-from-anchor/package.json +0 -10
|
@@ -8,25 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Maps each supported popover role to its `aria-haspopup` value.
|
|
12
12
|
*
|
|
13
13
|
* Derived from the HTML spec - maps popover roles to what the trigger
|
|
14
|
-
* announces.
|
|
14
|
+
* announces. Roles that do not produce a popup use `undefined` instead (for
|
|
15
15
|
* non-popup roles like `tooltip`/`status`/`alert`/`note`/`log` where
|
|
16
|
-
* `aria-haspopup` would be misleading)
|
|
16
|
+
* `aria-haspopup` would be misleading), so they are intentionally excluded
|
|
17
|
+
* from this mapping and from `getAriaForTrigger`.
|
|
17
18
|
*
|
|
18
|
-
* `true` is intentionally
|
|
19
|
-
* the explicit string forms, so
|
|
20
|
-
* pass `true` and get an `aria-haspopup="true"` serialisation that the
|
|
19
|
+
* `true` is intentionally not a value in this mapping. The runtime only ever
|
|
20
|
+
* produces the explicit string forms, so allowing it would invite consumers
|
|
21
|
+
* to pass `true` and get an `aria-haspopup="true"` serialisation that the
|
|
21
22
|
* runtime no longer emits.
|
|
22
23
|
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Non-undefined subset of `TAriaHasPopupValue`. Every role accepted by
|
|
26
|
-
* `getAriaForTrigger` maps to a concrete `aria-haspopup` string, so the
|
|
27
|
-
* trigger always receives a defined attribute value.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
24
|
const roleToAriaHasPopup = {
|
|
31
25
|
dialog: 'dialog',
|
|
32
26
|
alertdialog: 'dialog',
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function isNativeElementOpen({
|
|
2
|
+
element
|
|
3
|
+
}) {
|
|
4
|
+
if (element instanceof HTMLDialogElement) {
|
|
5
|
+
return element.open;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Some DOM implementations throw for unsupported pseudo-classes.
|
|
9
|
+
try {
|
|
10
|
+
return element.matches(':popover-open');
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,26 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JS fallback positioning math. Used only when the browser does not
|
|
3
|
-
* support CSS Anchor Positioning (less than 6% of users).
|
|
4
|
-
*
|
|
5
|
-
* Consumer-supplied `offset.gap` and `offset.crossAxisShift` are
|
|
6
|
-
* honored: the caller (`useAnchorPosition`) resolves CSS length strings
|
|
7
|
-
* (tokens, calc, var, etc) to pixels via a DOM probe before passing them
|
|
8
|
-
* in. The caller also keeps the popover hidden (`opacity: 0`) until the
|
|
9
|
-
* first measurement completes, so the popover never paints at the wrong
|
|
10
|
-
* position.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
1
|
/**
|
|
14
2
|
* A viewport-relative position, in pixels. Physical rather than logical
|
|
15
3
|
* because it is written straight to `top` / `left`.
|
|
16
4
|
*/
|
|
17
5
|
|
|
18
6
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* Mirrors `TCrossAxisShiftOffset`, whose `value` is an unresolved CSS length
|
|
22
|
-
* string. Only this module needs the pixel form, because only this module does
|
|
23
|
-
* the arithmetic itself.
|
|
7
|
+
* `TCrossAxisShiftOffset` with its CSS length already resolved to pixels. Only
|
|
8
|
+
* this module needs the pixel form, because only this module does arithmetic.
|
|
24
9
|
*/
|
|
25
10
|
|
|
26
11
|
/**
|
|
@@ -137,18 +122,13 @@ function applyAlignment({
|
|
|
137
122
|
}
|
|
138
123
|
|
|
139
124
|
/**
|
|
140
|
-
* Applies the cross-axis shift offset.
|
|
141
|
-
*
|
|
142
|
-
* `forwards` always moves the popover toward the cross-axis END and
|
|
143
|
-
* `backwards` toward the START, for every `align` value. See
|
|
144
|
-
* `notes/decisions/placement-offset.md`.
|
|
125
|
+
* Applies the cross-axis shift offset. `forwards` always moves the popover
|
|
126
|
+
* toward the cross-axis END, for every `align` value.
|
|
145
127
|
*
|
|
146
|
-
*
|
|
147
|
-
* is
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* the shift. Applying that inversion to a coordinate moved `align: 'end'`
|
|
151
|
-
* popovers the wrong way.
|
|
128
|
+
* Do not add a per-`align` sign inversion here. This is a coordinate, so the
|
|
129
|
+
* direction is the sign and nothing else. The CSS path inverts only because an
|
|
130
|
+
* END-side margin pushes a box the opposite way to a START-side one, which is a
|
|
131
|
+
* property of margins.
|
|
152
132
|
*/
|
|
153
133
|
function applyCrossAxisShift({
|
|
154
134
|
position,
|
|
@@ -175,12 +155,11 @@ function applyCrossAxisShift({
|
|
|
175
155
|
}
|
|
176
156
|
|
|
177
157
|
/**
|
|
178
|
-
* Computes popover position from trigger rect and viewport, flipping to
|
|
179
|
-
* the side with more space when needed.
|
|
158
|
+
* Computes the popover position from the trigger rect and viewport, flipping to
|
|
159
|
+
* the side with more space when needed.
|
|
180
160
|
*
|
|
181
|
-
* `gap` and `crossAxisShift`
|
|
182
|
-
*
|
|
183
|
-
* numeric.
|
|
161
|
+
* `gap` and `crossAxisShift` arrive already resolved to pixels (see
|
|
162
|
+
* `resolveCssLengthToPixels`), so this stays purely numeric.
|
|
184
163
|
*/
|
|
185
164
|
export function computeFallbackPosition({
|
|
186
165
|
triggerRect,
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JavaScript positioning fallback: measured `position: fixed` coordinates for
|
|
3
|
+
* browsers without CSS Anchor Positioning.
|
|
4
|
+
*
|
|
5
|
+
* Best effort, degrading in that order: measured anchored position, then anchored
|
|
6
|
+
* with unresolvable offsets treated as `0`, then centred in the viewport, and
|
|
7
|
+
* always visible. The two hazards differ in kind: `new ResizeObserver` runs
|
|
8
|
+
* synchronously in the layout effect, where a throw unmounts the React tree,
|
|
9
|
+
* while `update` runs asynchronously, where the danger is an uncleared
|
|
10
|
+
* `opacity: 0`.
|
|
11
|
+
*/
|
|
12
|
+
import { bind } from 'bind-event-listener';
|
|
13
|
+
import rafSchedule from 'raf-schd';
|
|
14
|
+
import { combine } from '../combine';
|
|
15
|
+
import { setStyle } from '../set-style';
|
|
16
|
+
import { computeFallbackPosition } from './anchor-positioning-fallback';
|
|
17
|
+
import { resolveCssLengthToPixels } from './resolve-css-length-to-pixels';
|
|
18
|
+
import { warnFallbackFailure } from './warn-fallback-failure';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Wrapped because jsdom throws on the unknown `:popover-open` selector.
|
|
22
|
+
*/
|
|
23
|
+
function isPopoverOpen(popover) {
|
|
24
|
+
try {
|
|
25
|
+
return popover.matches(':popover-open');
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Observes the popover until its first non-zero layout, then self-disconnects;
|
|
33
|
+
* ongoing movement is handled by the scroll and resize listeners. The popover is
|
|
34
|
+
* `display: none` until `showPopover()`, and some browsers fire `toggle` before
|
|
35
|
+
* layout, so RAF-after-toggle is unreliable.
|
|
36
|
+
*
|
|
37
|
+
* `null` where `ResizeObserver` is missing (non-DOM jest environments). There is
|
|
38
|
+
* then no measurement to wait for, so the caller positions immediately and must
|
|
39
|
+
* not hide the popover, because no reveal would follow.
|
|
40
|
+
*/
|
|
41
|
+
function createFirstLayoutObserver({
|
|
42
|
+
popover,
|
|
43
|
+
onFirstLayout
|
|
44
|
+
}) {
|
|
45
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const observer = new ResizeObserver(() => {
|
|
50
|
+
if (popover.offsetWidth > 0 && popover.offsetHeight > 0) {
|
|
51
|
+
onFirstLayout();
|
|
52
|
+
observer.disconnect();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return observer;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
warnFallbackFailure({
|
|
58
|
+
error
|
|
59
|
+
});
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The consumer's offsets in pixels. See `resolvedOffsets` below.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Positions `popover` against `anchor` from measurements, and keeps it there
|
|
70
|
+
* across opens, scroll and resize. Returns the cleanup.
|
|
71
|
+
*
|
|
72
|
+
* No fit margins on this path: the gap and the cross-axis shift are coordinate
|
|
73
|
+
* deltas here, so writing them as margins too would double-apply them.
|
|
74
|
+
*/
|
|
75
|
+
export function applyJavaScriptFallbackPositioning({
|
|
76
|
+
anchor,
|
|
77
|
+
popover,
|
|
78
|
+
placement,
|
|
79
|
+
sizeStyles
|
|
80
|
+
}) {
|
|
81
|
+
// Already in the top layer via the `popover` attribute, so this only resets the
|
|
82
|
+
// UA defaults and writes measured `top` / `left`.
|
|
83
|
+
const cleanupBaseStyles = setStyle({
|
|
84
|
+
element: popover,
|
|
85
|
+
styles: [{
|
|
86
|
+
property: 'margin',
|
|
87
|
+
value: '0'
|
|
88
|
+
}, {
|
|
89
|
+
property: 'inset',
|
|
90
|
+
value: 'auto'
|
|
91
|
+
}, ...sizeStyles]
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// The popover can be consumer-owned (`@atlaskit/popper`'s `createPopper`),
|
|
95
|
+
// so restore prior inline values rather than removing ours.
|
|
96
|
+
let undoPosition;
|
|
97
|
+
let undoHide;
|
|
98
|
+
|
|
99
|
+
// Resolved once per open and per resize, reused across scrolls: resolving is a
|
|
100
|
+
// full style and layout flush, and a scroll cannot change what a CSS length
|
|
101
|
+
// resolves to. A resize can (viewport units), and so can a re-open (a token
|
|
102
|
+
// retargeted while closed).
|
|
103
|
+
let resolvedOffsets = null;
|
|
104
|
+
function invalidateResolvedOffsets() {
|
|
105
|
+
resolvedOffsets = null;
|
|
106
|
+
}
|
|
107
|
+
function getResolvedOffsets() {
|
|
108
|
+
if (resolvedOffsets) {
|
|
109
|
+
return resolvedOffsets;
|
|
110
|
+
}
|
|
111
|
+
// Probed INSIDE the popover: it is in the top layer, not the anchor's DOM
|
|
112
|
+
// tree, so anywhere else resolves a token against a different scope.
|
|
113
|
+
resolvedOffsets = {
|
|
114
|
+
gap: resolveCssLengthToPixels({
|
|
115
|
+
value: placement.offset.gap,
|
|
116
|
+
container: popover
|
|
117
|
+
}),
|
|
118
|
+
crossAxisShift: resolveCssLengthToPixels({
|
|
119
|
+
value: placement.offset.crossAxisShift.value,
|
|
120
|
+
container: popover
|
|
121
|
+
})
|
|
122
|
+
};
|
|
123
|
+
return resolvedOffsets;
|
|
124
|
+
}
|
|
125
|
+
function reveal() {
|
|
126
|
+
var _undoHide;
|
|
127
|
+
(_undoHide = undoHide) === null || _undoHide === void 0 ? void 0 : _undoHide();
|
|
128
|
+
undoHide = undefined;
|
|
129
|
+
}
|
|
130
|
+
function restorePosition() {
|
|
131
|
+
var _undoPosition;
|
|
132
|
+
(_undoPosition = undoPosition) === null || _undoPosition === void 0 ? void 0 : _undoPosition();
|
|
133
|
+
undoPosition = undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// `opacity: 0` not `visibility: hidden`: Firefox skips visibility-hidden
|
|
137
|
+
// elements during `<dialog>` initial-focus traversal.
|
|
138
|
+
function hideUntilPositioned() {
|
|
139
|
+
// Keep the first snapshot, else we capture our own `opacity: 0`.
|
|
140
|
+
if (undoHide) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
undoHide = setStyle({
|
|
144
|
+
element: popover,
|
|
145
|
+
styles: [{
|
|
146
|
+
property: 'opacity',
|
|
147
|
+
value: '0'
|
|
148
|
+
}]
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Last resort. The base styles above reset the UA
|
|
154
|
+
* `[popover] { inset: 0; margin: auto }`, so a popover with no `top` / `left`
|
|
155
|
+
* sits in the viewport corner rather than centred.
|
|
156
|
+
*/
|
|
157
|
+
function centerInViewport() {
|
|
158
|
+
restorePosition();
|
|
159
|
+
undoPosition = setStyle({
|
|
160
|
+
element: popover,
|
|
161
|
+
styles: [{
|
|
162
|
+
property: 'inset',
|
|
163
|
+
value: '0'
|
|
164
|
+
}, {
|
|
165
|
+
property: 'margin',
|
|
166
|
+
value: 'auto'
|
|
167
|
+
}]
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function update() {
|
|
171
|
+
try {
|
|
172
|
+
const triggerRect = anchor.getBoundingClientRect();
|
|
173
|
+
const viewport = {
|
|
174
|
+
width: window.innerWidth,
|
|
175
|
+
height: window.innerHeight
|
|
176
|
+
};
|
|
177
|
+
const {
|
|
178
|
+
gap,
|
|
179
|
+
crossAxisShift
|
|
180
|
+
} = getResolvedOffsets();
|
|
181
|
+
const {
|
|
182
|
+
top,
|
|
183
|
+
left
|
|
184
|
+
} = computeFallbackPosition({
|
|
185
|
+
triggerRect,
|
|
186
|
+
popoverEl: popover,
|
|
187
|
+
placement,
|
|
188
|
+
viewport,
|
|
189
|
+
gap,
|
|
190
|
+
crossAxisShift: {
|
|
191
|
+
value: crossAxisShift,
|
|
192
|
+
direction: placement.offset.crossAxisShift.direction
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// A non-finite coordinate serialises to a declaration the browser drops,
|
|
197
|
+
// landing the popover in the corner just as silently as a throw would.
|
|
198
|
+
if (!Number.isFinite(top) || !Number.isFinite(left)) {
|
|
199
|
+
centerInViewport();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Restore first so the snapshot stays the consumer's value.
|
|
204
|
+
restorePosition();
|
|
205
|
+
undoPosition = setStyle({
|
|
206
|
+
element: popover,
|
|
207
|
+
styles: [{
|
|
208
|
+
property: 'top',
|
|
209
|
+
value: `${top}px`
|
|
210
|
+
}, {
|
|
211
|
+
property: 'left',
|
|
212
|
+
value: `${left}px`
|
|
213
|
+
}]
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
warnFallbackFailure({
|
|
217
|
+
error
|
|
218
|
+
});
|
|
219
|
+
// Anchoring needs the trigger geometry we just failed to get, so the
|
|
220
|
+
// centred position is the only one left to degrade to.
|
|
221
|
+
try {
|
|
222
|
+
centerInViewport();
|
|
223
|
+
} catch {}
|
|
224
|
+
} finally {
|
|
225
|
+
// Unconditional. A popover in the wrong place is recoverable on the
|
|
226
|
+
// next scroll or resize; one left at `opacity: 0` is invisible forever.
|
|
227
|
+
reveal();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function invalidateAndUpdate() {
|
|
231
|
+
invalidateResolvedOffsets();
|
|
232
|
+
update();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// One update per animation frame. Scroll reuses the resolved offsets; resize
|
|
236
|
+
// re-resolves them.
|
|
237
|
+
const scheduledScrollUpdate = rafSchedule(update);
|
|
238
|
+
const scheduledResizeUpdate = rafSchedule(invalidateAndUpdate);
|
|
239
|
+
const firstLayoutObserver = createFirstLayoutObserver({
|
|
240
|
+
popover,
|
|
241
|
+
onFirstLayout: update
|
|
242
|
+
});
|
|
243
|
+
function positionOnNextLayout() {
|
|
244
|
+
// A token can have been retargeted while the popover was closed, so each
|
|
245
|
+
// open re-resolves the offsets.
|
|
246
|
+
invalidateResolvedOffsets();
|
|
247
|
+
// No observer means no measurement is coming: position now, and do not
|
|
248
|
+
// hide, because the reveal would never arrive.
|
|
249
|
+
if (!firstLayoutObserver) {
|
|
250
|
+
update();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
hideUntilPositioned();
|
|
254
|
+
firstLayoutObserver.observe(popover);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// The popover can already be open when this runs, in which case the initial
|
|
258
|
+
// `toggle` has fired and the listener below would miss it.
|
|
259
|
+
if (isPopoverOpen(popover)) {
|
|
260
|
+
positionOnNextLayout();
|
|
261
|
+
}
|
|
262
|
+
return combine(cleanupBaseStyles,
|
|
263
|
+
// Each open needs a fresh measurement, so hide and re-observe.
|
|
264
|
+
bind(popover, {
|
|
265
|
+
type: 'toggle',
|
|
266
|
+
listener: event => {
|
|
267
|
+
const toggleEvent = event;
|
|
268
|
+
if (toggleEvent.newState === 'open') {
|
|
269
|
+
positionOnNextLayout();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}), () => firstLayoutObserver === null || firstLayoutObserver === void 0 ? void 0 : firstLayoutObserver.disconnect(), bind(window, {
|
|
273
|
+
type: 'scroll',
|
|
274
|
+
listener: scheduledScrollUpdate,
|
|
275
|
+
options: {
|
|
276
|
+
capture: true,
|
|
277
|
+
passive: true
|
|
278
|
+
}
|
|
279
|
+
}), bind(window, {
|
|
280
|
+
type: 'resize',
|
|
281
|
+
listener: scheduledResizeUpdate,
|
|
282
|
+
options: {
|
|
283
|
+
passive: true
|
|
284
|
+
}
|
|
285
|
+
}), () => {
|
|
286
|
+
scheduledScrollUpdate.cancel();
|
|
287
|
+
scheduledResizeUpdate.cancel();
|
|
288
|
+
restorePosition();
|
|
289
|
+
reveal();
|
|
290
|
+
});
|
|
291
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { warnFallbackFailure } from './warn-fallback-failure';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `null` when the environment cannot measure at all.
|
|
5
|
+
*/
|
|
6
|
+
function measureWithProbe({
|
|
7
|
+
value,
|
|
8
|
+
container
|
|
9
|
+
}) {
|
|
10
|
+
// Reached through the container rather than the global, so a popover in
|
|
11
|
+
// another document still measures and one with no view degrades gracefully.
|
|
12
|
+
const view = container.ownerDocument.defaultView;
|
|
13
|
+
if (!view) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const probe = container.ownerDocument.createElement('div');
|
|
17
|
+
probe.style.position = 'absolute';
|
|
18
|
+
probe.style.visibility = 'hidden';
|
|
19
|
+
probe.style.pointerEvents = 'none';
|
|
20
|
+
probe.style.height = '0';
|
|
21
|
+
probe.style.padding = '0';
|
|
22
|
+
probe.style.border = '0';
|
|
23
|
+
// `width` clamps negatives to 0; use `margin-left` instead so signs are
|
|
24
|
+
// preserved. `getComputedStyle` returns the resolved pixel value for
|
|
25
|
+
// margin-* properties even when the source was a token / calc / var.
|
|
26
|
+
probe.style.marginLeft = value;
|
|
27
|
+
// Hidden from assistive tech and event targeting; defensive against
|
|
28
|
+
// `getElementsByTagName('div')` walks in consumer code.
|
|
29
|
+
probe.setAttribute('aria-hidden', 'true');
|
|
30
|
+
probe.setAttribute('inert', '');
|
|
31
|
+
try {
|
|
32
|
+
container.appendChild(probe);
|
|
33
|
+
const resolved = parseFloat(view.getComputedStyle(probe).marginLeft);
|
|
34
|
+
return Number.isFinite(resolved) ? resolved : 0;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
warnFallbackFailure({
|
|
37
|
+
error
|
|
38
|
+
});
|
|
39
|
+
return null;
|
|
40
|
+
} finally {
|
|
41
|
+
// `remove()` rather than `removeChild()`: consumer code may already have
|
|
42
|
+
// detached the probe, which must not throw out of a layout effect.
|
|
43
|
+
probe.remove();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolves a CSS length value to a pixel number using a hidden DOM probe.
|
|
49
|
+
*
|
|
50
|
+
* Used by the JS positioning fallback, where a consumer offset can be a number, a
|
|
51
|
+
* `${n}px` string, a token, a `calc()` expression, a viewport unit, and so on.
|
|
52
|
+
* Parsing arbitrary CSS in JS is fragile, and impossible for `calc(var(...))`
|
|
53
|
+
* without a layout context, so the browser does the math instead. The probe is
|
|
54
|
+
* appended to `container` so it inherits the popover's containing block, font
|
|
55
|
+
* size and custom-property scope. Anything unmeasurable resolves to `0`.
|
|
56
|
+
*
|
|
57
|
+
* Costs one synchronous style and layout flush per call that reaches the probe.
|
|
58
|
+
* Deliberately NOT memoised: a token can be retargeted by an override on the
|
|
59
|
+
* consumer's wrapper, a viewport unit changes on resize and an `em` on a font
|
|
60
|
+
* change, so no (container, value) cache can be proven fresh. The caller decides
|
|
61
|
+
* when to re-resolve; see `applyJavaScriptFallbackPositioning`.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveCssLengthToPixels({
|
|
64
|
+
value,
|
|
65
|
+
container
|
|
66
|
+
}) {
|
|
67
|
+
var _measureWithProbe;
|
|
68
|
+
if (typeof value === 'number') {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Fast path: consumers occasionally pass `'0'` instead of the number `0`.
|
|
73
|
+
if (value === '0' || value === '-0') {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Fast path: bare `${n}px` strings come from `resolvePlacement`, so this avoids
|
|
78
|
+
// a reflow for the common case.
|
|
79
|
+
const pxMatch = value.match(/^(-?\d+(?:\.\d+)?)px$/);
|
|
80
|
+
if (pxMatch) {
|
|
81
|
+
return Number(pxMatch[1]);
|
|
82
|
+
}
|
|
83
|
+
return (_measureWithProbe = measureWithProbe({
|
|
84
|
+
value,
|
|
85
|
+
container
|
|
86
|
+
})) !== null && _measureWithProbe !== void 0 ? _measureWithProbe : 0;
|
|
87
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import warnOnce from '@atlaskit/ds-lib/warn-once';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Callers on the JavaScript fallback path degrade rather than throw, so this is
|
|
5
|
+
* the only signal that a popover is not where it should be.
|
|
6
|
+
*/
|
|
7
|
+
export function warnFallbackFailure({
|
|
8
|
+
error
|
|
9
|
+
}) {
|
|
10
|
+
if (process.env.NODE_ENV === 'production') {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
warnOnce(`@atlaskit/top-layer: the JavaScript positioning fallback hit an error, so this popover may be positioned incorrectly. ${String(error)}`);
|
|
14
|
+
}
|
|
@@ -20,8 +20,9 @@ const DEFAULT_GAP = "var(--ds-space-100, 8px)";
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Fully-resolved placement (internal). Use `resolvePlacement` for partial input.
|
|
23
|
-
* `offset.gap
|
|
24
|
-
*
|
|
23
|
+
* `offset.gap`, `offset.crossAxisShift.value` and `minSize` are always CSS length
|
|
24
|
+
* strings. `minSize` stays `undefined` when unset, so "not specified" and an
|
|
25
|
+
* explicit `0` remain distinguishable.
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -41,19 +42,26 @@ const DEFAULT_GAP = "var(--ds-space-100, 8px)";
|
|
|
41
42
|
* Defaults: `axis: 'block'`, `edge: 'end'`, `align: 'center'`,
|
|
42
43
|
* `offset.gap: token('space.100', '8px')`,
|
|
43
44
|
* `offset.crossAxisShift: { value: '0px', direction: 'forwards' }`.
|
|
45
|
+
*
|
|
46
|
+
* `minSize` has no default here: whether it needs one depends on how the popover
|
|
47
|
+
* is sized, which only `useAnchoredPopover` knows.
|
|
44
48
|
*/
|
|
45
49
|
export function resolvePlacement({
|
|
46
50
|
placement
|
|
47
51
|
}) {
|
|
48
|
-
var _placement$
|
|
49
|
-
const consumerGap = (_placement$offset = placement.offset) === null || _placement$offset === void 0 ? void 0 : _placement$offset.gap;
|
|
52
|
+
var _placement$axis, _placement$edge, _placement$align, _placement$offset$gap, _placement$offset, _placement$offset$cro, _placement$offset2, _placement$offset2$cr, _placement$offset$cro2, _placement$offset3, _placement$offset3$cr;
|
|
50
53
|
return {
|
|
51
54
|
axis: (_placement$axis = placement.axis) !== null && _placement$axis !== void 0 ? _placement$axis : 'block',
|
|
52
55
|
edge: (_placement$edge = placement.edge) !== null && _placement$edge !== void 0 ? _placement$edge : 'end',
|
|
53
56
|
align: (_placement$align = placement.align) !== null && _placement$align !== void 0 ? _placement$align : 'center',
|
|
57
|
+
// Not `??`: `undefined` must survive, so "not specified" stays
|
|
58
|
+
// distinguishable from an explicit `0`.
|
|
59
|
+
minSize: placement.minSize === undefined ? undefined : toCssLengthString({
|
|
60
|
+
value: placement.minSize
|
|
61
|
+
}),
|
|
54
62
|
offset: {
|
|
55
|
-
gap:
|
|
56
|
-
value:
|
|
63
|
+
gap: toCssLengthString({
|
|
64
|
+
value: (_placement$offset$gap = (_placement$offset = placement.offset) === null || _placement$offset === void 0 ? void 0 : _placement$offset.gap) !== null && _placement$offset$gap !== void 0 ? _placement$offset$gap : DEFAULT_GAP
|
|
57
65
|
}),
|
|
58
66
|
crossAxisShift: {
|
|
59
67
|
value: toCssLengthString({
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createCssDeclarationProbe } from './supports-css-declaration';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Probes `anchor-name` and NOT `anchor-size()`: some browsers have positioning
|
|
5
|
+
* without sizing, which is what `supportsAnchorSize` is for.
|
|
6
|
+
*/
|
|
7
|
+
// Annotated rather than inferred: `--isolatedDeclarations` cannot emit a type
|
|
8
|
+
// for a `const` initialised from a call.
|
|
9
|
+
export const supportsAnchorPositioning = createCssDeclarationProbe({
|
|
10
|
+
property: 'anchor-name',
|
|
11
|
+
value: '--a'
|
|
12
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createCssDeclarationProbe } from './supports-css-declaration';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Separate from `supportsAnchorPositioning`: some browsers have anchor
|
|
5
|
+
* positioning without anchor sizing.
|
|
6
|
+
*/
|
|
7
|
+
// Annotated rather than inferred: `--isolatedDeclarations` cannot emit a type
|
|
8
|
+
// for a `const` initialised from a call.
|
|
9
|
+
export const supportsAnchorSize = createCssDeclarationProbe({
|
|
10
|
+
property: 'min-inline-size',
|
|
11
|
+
value: 'anchor-size(self-inline)'
|
|
12
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import once from '@atlaskit/ds-lib/once';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A lazy, cached `CSS.supports(property, value)` probe. Lazy so it is SSR safe
|
|
5
|
+
* and cannot cause a hydration mismatch: nothing is read until the first call,
|
|
6
|
+
* which happens in an effect. Cached because the answer cannot change within a
|
|
7
|
+
* page. `false` wherever `CSS.supports` is missing (SSR, jsdom), so a caller
|
|
8
|
+
* falls back rather than throws.
|
|
9
|
+
*/
|
|
10
|
+
export function createCssDeclarationProbe({
|
|
11
|
+
property,
|
|
12
|
+
value
|
|
13
|
+
}) {
|
|
14
|
+
return once(function supportsCssDeclaration() {
|
|
15
|
+
if (typeof window === 'undefined' || typeof CSS === 'undefined' || typeof CSS.supports !== 'function') {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return CSS.supports(property, value);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|