@atlaskit/top-layer 0.1.5 → 0.2.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 +24 -0
- package/dist/cjs/arrow/index.js +1 -1
- package/dist/cjs/internal/anchor-positioning-fallback.js +82 -22
- package/dist/cjs/internal/resolve-css-length-to-pixels.js +53 -0
- package/dist/cjs/internal/resolve-css-length.js +18 -0
- package/dist/cjs/internal/resolve-placement.js +47 -6
- package/dist/cjs/internal/use-anchor-positioning.js +180 -36
- package/dist/cjs/placement-map/index.js +31 -3
- package/dist/cjs/popup/popup-content.js +0 -2
- package/dist/es2019/arrow/index.js +22 -3
- package/dist/es2019/internal/anchor-positioning-fallback.js +80 -17
- package/dist/es2019/internal/resolve-css-length-to-pixels.js +48 -0
- package/dist/es2019/internal/resolve-css-length.js +13 -0
- package/dist/es2019/internal/resolve-placement.js +48 -6
- package/dist/es2019/internal/use-anchor-positioning.js +173 -28
- package/dist/es2019/placement-map/index.js +25 -3
- package/dist/es2019/popup/popup-content.js +0 -2
- package/dist/esm/arrow/index.js +1 -1
- package/dist/esm/internal/anchor-positioning-fallback.js +82 -22
- package/dist/esm/internal/resolve-css-length-to-pixels.js +47 -0
- package/dist/esm/internal/resolve-css-length.js +12 -0
- package/dist/esm/internal/resolve-placement.js +48 -6
- package/dist/esm/internal/use-anchor-positioning.js +179 -36
- package/dist/esm/placement-map/index.js +30 -3
- package/dist/esm/popup/popup-content.js +0 -2
- package/dist/types/internal/anchor-positioning-fallback.d.ts +11 -9
- package/dist/types/internal/resolve-css-length-to-pixels.d.ts +22 -0
- package/dist/types/internal/resolve-css-length.d.ts +8 -0
- package/dist/types/internal/resolve-placement.d.ts +47 -5
- package/dist/types/internal/use-anchor-positioning.d.ts +18 -7
- package/dist/types/placement-map/index.d.ts +11 -5
- package/dist/types/popup/types.d.ts +15 -30
- package/dist/types-ts4.5/internal/anchor-positioning-fallback.d.ts +11 -9
- package/dist/types-ts4.5/internal/resolve-css-length-to-pixels.d.ts +22 -0
- package/dist/types-ts4.5/internal/resolve-css-length.d.ts +8 -0
- package/dist/types-ts4.5/internal/resolve-placement.d.ts +47 -5
- package/dist/types-ts4.5/internal/use-anchor-positioning.d.ts +18 -7
- package/dist/types-ts4.5/placement-map/index.d.ts +14 -5
- package/dist/types-ts4.5/popup/types.d.ts +15 -30
- package/package.json +2 -2
|
@@ -19,8 +19,24 @@ var _rafSchd = _interopRequireDefault(require("raf-schd"));
|
|
|
19
19
|
var _once = _interopRequireDefault(require("@atlaskit/ds-lib/once"));
|
|
20
20
|
var _anchorPositioningFallback = require("./anchor-positioning-fallback");
|
|
21
21
|
var _combine = require("./combine");
|
|
22
|
+
var _resolveCssLengthToPixels = require("./resolve-css-length-to-pixels");
|
|
22
23
|
var _resolvePlacement = require("./resolve-placement");
|
|
23
24
|
var _setStyle = require("./set-style");
|
|
25
|
+
/**
|
|
26
|
+
* Anchor positioning for top-layer popovers. See
|
|
27
|
+
* `notes/architecture/positioning.md` for the full model.
|
|
28
|
+
*
|
|
29
|
+
* Two runtime paths: CSS Anchor Positioning (modern browsers) and a JS
|
|
30
|
+
* fallback that measures the trigger and writes `top`/`left`.
|
|
31
|
+
*
|
|
32
|
+
* Both paths honour `offset.gap` and `offset.crossAxisShift`. The
|
|
33
|
+
* CSS path passes the values through as strings; the JS fallback resolves
|
|
34
|
+
* them to pixels via a hidden DOM probe (see `resolveCssLengthToPixels`)
|
|
35
|
+
* so tokens, `calc()`, `var()`, etc all work. The JS fallback hides the
|
|
36
|
+
* popover with `opacity: 0` until the first measurement completes, so it
|
|
37
|
+
* is never painted at the wrong location.
|
|
38
|
+
*/
|
|
39
|
+
|
|
24
40
|
/**
|
|
25
41
|
* Resolves placement options to CSS `position-area` syntax.
|
|
26
42
|
*
|
|
@@ -100,6 +116,19 @@ function placementToTryFallbacks(_ref2) {
|
|
|
100
116
|
return ["".concat(sameEdge, " span-").concat(crossAxis, "-end"), "".concat(sameEdge, " span-").concat(crossAxis, "-start"), flipKeyword, "".concat(oppositeEdge, " span-").concat(crossAxis, "-end"), "".concat(oppositeEdge, " span-").concat(crossAxis, "-start")].join(', ');
|
|
101
117
|
}
|
|
102
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Returns true when the popover is currently open. Wrapped because the
|
|
121
|
+
* `:popover-open` pseudo-class is not implemented in some test environments
|
|
122
|
+
* (e.g. jsdom), where `matches` throws on unknown selectors.
|
|
123
|
+
*/
|
|
124
|
+
function isPopoverOpen(popover) {
|
|
125
|
+
try {
|
|
126
|
+
return popover.matches(':popover-open');
|
|
127
|
+
} catch (_unused) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
103
132
|
/**
|
|
104
133
|
* Detects whether the browser supports CSS Anchor Positioning.
|
|
105
134
|
* Uses `once()` for lazy evaluation — safe for SSR and avoids hydration mismatches.
|
|
@@ -111,12 +140,6 @@ var supportsAnchorPositioning = (0, _once.default)(function () {
|
|
|
111
140
|
return CSS.supports('anchor-name', '--a');
|
|
112
141
|
});
|
|
113
142
|
|
|
114
|
-
/**
|
|
115
|
-
* Default gap between the popover and its trigger (in pixels).
|
|
116
|
-
* Maps to `space.100` (8px) in the design token scale.
|
|
117
|
-
*/
|
|
118
|
-
var DEFAULT_OFFSET = 8;
|
|
119
|
-
|
|
120
143
|
/**
|
|
121
144
|
* Returns the CSS margin declaration that creates a gap between the
|
|
122
145
|
* popover and its trigger on the side facing the anchor.
|
|
@@ -129,7 +152,7 @@ function edgeMargin(_ref3) {
|
|
|
129
152
|
offset = _ref3.offset;
|
|
130
153
|
var axis = placement.axis,
|
|
131
154
|
edge = placement.edge;
|
|
132
|
-
var value =
|
|
155
|
+
var value = offset;
|
|
133
156
|
if (axis === 'block' && edge === 'end') {
|
|
134
157
|
return {
|
|
135
158
|
property: 'margin-block-start',
|
|
@@ -160,6 +183,33 @@ function edgeMargin(_ref3) {
|
|
|
160
183
|
};
|
|
161
184
|
}
|
|
162
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Cross-axis shift margin. Margin side matches the popover's anchored
|
|
188
|
+
* cross-axis edge: `align: 'start'` uses the START margin, `align: 'end'`
|
|
189
|
+
* uses the END margin with the sign inverted (so `forwards` is always
|
|
190
|
+
* positive), `align: 'center'` uses START. Margin on the un-anchored side
|
|
191
|
+
* has no effect under CSS Anchor Positioning.
|
|
192
|
+
*/
|
|
193
|
+
function crossAxisShiftMargin(_ref4) {
|
|
194
|
+
var placement = _ref4.placement,
|
|
195
|
+
crossAxisShiftCssValue = _ref4.crossAxisShiftCssValue,
|
|
196
|
+
direction = _ref4.direction;
|
|
197
|
+
var axis = placement.axis,
|
|
198
|
+
align = placement.align;
|
|
199
|
+
var directionSign = direction === 'forwards' ? 1 : -1;
|
|
200
|
+
var crossAxis = axis === 'block' ? 'inline' : 'block';
|
|
201
|
+
var useEndSide = align === 'end';
|
|
202
|
+
var side = useEndSide ? 'end' : 'start';
|
|
203
|
+
var sideSign = useEndSide ? -1 : 1;
|
|
204
|
+
var finalSign = directionSign * sideSign;
|
|
205
|
+
// Wrap any string in calc() with the sign factor. CSS handles the math.
|
|
206
|
+
var value = finalSign === 1 ? crossAxisShiftCssValue : "calc(-1 * ".concat(crossAxisShiftCssValue, ")");
|
|
207
|
+
return {
|
|
208
|
+
property: "margin-".concat(crossAxis, "-").concat(side),
|
|
209
|
+
value: value
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
163
213
|
/**
|
|
164
214
|
* Hook that positions an element relative to an anchor element using
|
|
165
215
|
* CSS Anchor Positioning (with a JS fallback for older browsers).
|
|
@@ -176,15 +226,13 @@ function edgeMargin(_ref3) {
|
|
|
176
226
|
* `position: fixed` with measured coordinates, re-running on scroll (capture)
|
|
177
227
|
* and resize events.
|
|
178
228
|
*/
|
|
179
|
-
function useAnchorPosition(
|
|
180
|
-
var anchorRef =
|
|
181
|
-
popoverRef =
|
|
182
|
-
placement =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
forceFallbackPositioning = _ref4$forceFallbackPo === void 0 ? false : _ref4$forceFallbackPo,
|
|
187
|
-
arrow = _ref4.arrow;
|
|
229
|
+
function useAnchorPosition(_ref5) {
|
|
230
|
+
var anchorRef = _ref5.anchorRef,
|
|
231
|
+
popoverRef = _ref5.popoverRef,
|
|
232
|
+
placement = _ref5.placement,
|
|
233
|
+
_ref5$forceFallbackPo = _ref5.forceFallbackPositioning,
|
|
234
|
+
forceFallbackPositioning = _ref5$forceFallbackPo === void 0 ? false : _ref5$forceFallbackPo,
|
|
235
|
+
arrow = _ref5.arrow;
|
|
188
236
|
var id = (0, _react.useId)();
|
|
189
237
|
var anchorName = "--anchor-".concat(id.replace(/:/g, ''));
|
|
190
238
|
(0, _react.useLayoutEffect)(function () {
|
|
@@ -196,11 +244,18 @@ function useAnchorPosition(_ref4) {
|
|
|
196
244
|
var resolvedPlacement = (0, _resolvePlacement.getPlacement)({
|
|
197
245
|
placement: placement
|
|
198
246
|
});
|
|
247
|
+
var gapCssValue = resolvedPlacement.offset.gap;
|
|
248
|
+
var crossAxisShiftCssValue = resolvedPlacement.offset.crossAxisShift.value;
|
|
199
249
|
var shouldForceFallback = Boolean(forceFallbackPositioning);
|
|
200
250
|
if (supportsAnchorPositioning() && !shouldForceFallback) {
|
|
201
251
|
var gap = edgeMargin({
|
|
202
252
|
placement: resolvedPlacement,
|
|
203
|
-
offset:
|
|
253
|
+
offset: gapCssValue
|
|
254
|
+
});
|
|
255
|
+
var crossAxisShift = crossAxisShiftMargin({
|
|
256
|
+
placement: resolvedPlacement,
|
|
257
|
+
crossAxisShiftCssValue: crossAxisShiftCssValue,
|
|
258
|
+
direction: resolvedPlacement.offset.crossAxisShift.direction
|
|
204
259
|
});
|
|
205
260
|
var popoverStyles = [{
|
|
206
261
|
property: 'position-anchor',
|
|
@@ -227,17 +282,62 @@ function useAnchorPosition(_ref4) {
|
|
|
227
282
|
property: 'inset',
|
|
228
283
|
value: 'auto'
|
|
229
284
|
},
|
|
230
|
-
//
|
|
285
|
+
// Main-axis gap between the popover and its trigger
|
|
231
286
|
{
|
|
232
287
|
property: gap.property,
|
|
233
288
|
value: gap.value
|
|
289
|
+
},
|
|
290
|
+
// Cross-axis shift
|
|
291
|
+
{
|
|
292
|
+
property: crossAxisShift.property,
|
|
293
|
+
value: crossAxisShift.value
|
|
234
294
|
}];
|
|
295
|
+
|
|
296
|
+
// Surface the active cross-axis shift value as a custom property so
|
|
297
|
+
// each named arrow @position-try rule can re-apply it after a flip.
|
|
298
|
+
// The four properties cover (cross-axis, side); only one is non-zero
|
|
299
|
+
// at a time.
|
|
300
|
+
popoverStyles.push({
|
|
301
|
+
property: '--ds-cross-axis-shift-margin-start',
|
|
302
|
+
value: '0px'
|
|
303
|
+
});
|
|
304
|
+
popoverStyles.push({
|
|
305
|
+
property: '--ds-cross-axis-shift-margin-end',
|
|
306
|
+
value: '0px'
|
|
307
|
+
});
|
|
308
|
+
popoverStyles.push({
|
|
309
|
+
property: '--ds-cross-axis-shift-margin-block-start',
|
|
310
|
+
value: '0px'
|
|
311
|
+
});
|
|
312
|
+
popoverStyles.push({
|
|
313
|
+
property: '--ds-cross-axis-shift-margin-block-end',
|
|
314
|
+
value: '0px'
|
|
315
|
+
});
|
|
316
|
+
var crossAxisShiftActive = crossAxisShiftMargin({
|
|
317
|
+
placement: resolvedPlacement,
|
|
318
|
+
crossAxisShiftCssValue: crossAxisShiftCssValue,
|
|
319
|
+
direction: resolvedPlacement.offset.crossAxisShift.direction
|
|
320
|
+
});
|
|
321
|
+
// Map margin property to its corresponding custom property name.
|
|
322
|
+
var customPropertyByMarginProperty = {
|
|
323
|
+
'margin-inline-start': '--ds-cross-axis-shift-margin-start',
|
|
324
|
+
'margin-inline-end': '--ds-cross-axis-shift-margin-end',
|
|
325
|
+
'margin-block-start': '--ds-cross-axis-shift-margin-block-start',
|
|
326
|
+
'margin-block-end': '--ds-cross-axis-shift-margin-block-end'
|
|
327
|
+
};
|
|
328
|
+
var crossAxisShiftCustomProperty = customPropertyByMarginProperty[crossAxisShiftActive.property];
|
|
329
|
+
if (crossAxisShiftCustomProperty) {
|
|
330
|
+
popoverStyles.push({
|
|
331
|
+
property: crossAxisShiftCustomProperty,
|
|
332
|
+
value: crossAxisShiftActive.value
|
|
333
|
+
});
|
|
334
|
+
}
|
|
235
335
|
if (arrow) {
|
|
236
336
|
// Set the arrow size variable used by the injected arrow CSS
|
|
237
337
|
// and the @position-try rules for margin values.
|
|
238
338
|
popoverStyles.push({
|
|
239
339
|
property: '--ds-arrow-size',
|
|
240
|
-
value:
|
|
340
|
+
value: gapCssValue
|
|
241
341
|
});
|
|
242
342
|
// Mark the popover root as the arrow host. The ARROW_CSS targets
|
|
243
343
|
// [data-ds-popover-arrow] to apply clip-path: inset(0) margin-box
|
|
@@ -275,6 +375,7 @@ function useAnchorPosition(_ref4) {
|
|
|
275
375
|
}]
|
|
276
376
|
});
|
|
277
377
|
function update() {
|
|
378
|
+
var _popover$parentElemen;
|
|
278
379
|
if (!trigger || !popover) {
|
|
279
380
|
return;
|
|
280
381
|
}
|
|
@@ -283,41 +384,86 @@ function useAnchorPosition(_ref4) {
|
|
|
283
384
|
width: window.innerWidth,
|
|
284
385
|
height: window.innerHeight
|
|
285
386
|
};
|
|
387
|
+
|
|
388
|
+
// Resolve consumer-supplied CSS length strings (tokens, calc, var,
|
|
389
|
+
// px, rem, etc) to pixels. The probe is mounted next to the popover
|
|
390
|
+
// so it inherits the same custom-property scope and font size.
|
|
391
|
+
// `popover.parentElement` is guaranteed to exist for any rendered
|
|
392
|
+
// popover (the popover itself is appended somewhere in the tree),
|
|
393
|
+
// but fall back to the popover element if not for safety.
|
|
394
|
+
var probeContainer = (_popover$parentElemen = popover.parentElement) !== null && _popover$parentElemen !== void 0 ? _popover$parentElemen : popover;
|
|
395
|
+
var gapPx = (0, _resolveCssLengthToPixels.resolveCssLengthToPixels)({
|
|
396
|
+
value: gapCssValue,
|
|
397
|
+
container: probeContainer
|
|
398
|
+
});
|
|
399
|
+
var crossAxisShiftPx = (0, _resolveCssLengthToPixels.resolveCssLengthToPixels)({
|
|
400
|
+
value: crossAxisShiftCssValue,
|
|
401
|
+
container: probeContainer
|
|
402
|
+
});
|
|
286
403
|
var _computeFallbackPosit = (0, _anchorPositioningFallback.computeFallbackPosition)({
|
|
287
404
|
triggerRect: triggerRect,
|
|
288
405
|
popoverEl: popover,
|
|
289
406
|
placement: resolvedPlacement,
|
|
290
407
|
viewport: viewport,
|
|
291
|
-
|
|
408
|
+
gap: gapPx,
|
|
409
|
+
crossAxisShift: {
|
|
410
|
+
value: crossAxisShiftPx,
|
|
411
|
+
direction: resolvedPlacement.offset.crossAxisShift.direction
|
|
412
|
+
}
|
|
292
413
|
}),
|
|
293
414
|
top = _computeFallbackPosit.top,
|
|
294
415
|
left = _computeFallbackPosit.left;
|
|
295
416
|
popover.style.setProperty('top', "".concat(top, "px"));
|
|
296
417
|
popover.style.setProperty('left', "".concat(left, "px"));
|
|
418
|
+
// Reveal the popover only after it has been positioned.
|
|
419
|
+
// See the toggle listener below for why we hide on open.
|
|
420
|
+
popover.style.removeProperty('opacity');
|
|
297
421
|
}
|
|
298
422
|
|
|
299
423
|
// Throttle scroll/resize updates to one per animation frame
|
|
300
424
|
var scheduledUpdate = (0, _rafSchd.default)(update);
|
|
301
|
-
|
|
425
|
+
|
|
426
|
+
// Wait for the FIRST valid layout before measuring: the popover
|
|
427
|
+
// is `display: none` until `showPopover()`, and some browsers
|
|
428
|
+
// fire `toggle` before layout, so RAF-after-toggle is unreliable.
|
|
429
|
+
// ResizeObserver fires once the browser has real dimensions.
|
|
430
|
+
// Self-disconnects after one valid measurement; ongoing
|
|
431
|
+
// scroll/resize is handled by the window listeners below.
|
|
432
|
+
var resizeObserver = new ResizeObserver(function () {
|
|
433
|
+
if (popover.offsetWidth > 0 && popover.offsetHeight > 0) {
|
|
434
|
+
update();
|
|
435
|
+
resizeObserver.disconnect();
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// If the popover is already open by the time this effect runs (for
|
|
440
|
+
// example, the parent `useAnchorPosition` effect runs after the child
|
|
441
|
+
// `Popover` effect that called `showPopover()`), the initial `toggle`
|
|
442
|
+
// event has already fired and our listener below would miss it. Start
|
|
443
|
+
// observing immediately so the first measurement still happens.
|
|
444
|
+
if (isPopoverOpen(popover)) {
|
|
445
|
+
popover.style.setProperty('opacity', '0');
|
|
446
|
+
resizeObserver.observe(popover);
|
|
447
|
+
}
|
|
302
448
|
var undoPositioning = (0, _combine.combine)(cleanupBaseStyles,
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
// `
|
|
307
|
-
//
|
|
308
|
-
// `
|
|
309
|
-
// the element has been laid out — so dimensions are still 0.
|
|
310
|
-
// Waiting one frame guarantees layout has run and measurements
|
|
311
|
-
// are accurate.
|
|
449
|
+
// On every open: hide synchronously so the user never sees
|
|
450
|
+
// the UA-default position, then re-observe for a fresh
|
|
451
|
+
// measurement. We use `opacity: 0` rather than
|
|
452
|
+
// `visibility: hidden` because Firefox skips visibility-hidden
|
|
453
|
+
// elements during `<dialog>` initial-focus traversal — see
|
|
454
|
+
// `form-in-popup.spec.tsx` on `desktop-firefox`.
|
|
312
455
|
(0, _bindEventListener.bind)(popover, {
|
|
313
456
|
type: 'toggle',
|
|
314
457
|
listener: function listener(event) {
|
|
315
458
|
var toggleEvent = event;
|
|
316
459
|
if (toggleEvent.newState === 'open') {
|
|
317
|
-
|
|
460
|
+
popover.style.setProperty('opacity', '0');
|
|
461
|
+
resizeObserver.observe(popover);
|
|
318
462
|
}
|
|
319
463
|
}
|
|
320
|
-
}), (
|
|
464
|
+
}), function () {
|
|
465
|
+
return resizeObserver.disconnect();
|
|
466
|
+
}, (0, _bindEventListener.bind)(window, {
|
|
321
467
|
type: 'scroll',
|
|
322
468
|
listener: scheduledUpdate,
|
|
323
469
|
options: {
|
|
@@ -332,12 +478,10 @@ function useAnchorPosition(_ref4) {
|
|
|
332
478
|
}
|
|
333
479
|
}), function () {
|
|
334
480
|
scheduledUpdate.cancel();
|
|
335
|
-
if (pendingFrame !== null) {
|
|
336
|
-
cancelAnimationFrame(pendingFrame);
|
|
337
|
-
}
|
|
338
481
|
popover.style.removeProperty('top');
|
|
339
482
|
popover.style.removeProperty('left');
|
|
483
|
+
popover.style.removeProperty('opacity');
|
|
340
484
|
});
|
|
341
485
|
return undoPositioning;
|
|
342
|
-
}, [anchorRef, popoverRef, placement, anchorName,
|
|
486
|
+
}, [anchorRef, popoverRef, placement, anchorName, forceFallbackPositioning, arrow]);
|
|
343
487
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
3
4
|
Object.defineProperty(exports, "__esModule", {
|
|
4
5
|
value: true
|
|
5
6
|
});
|
|
6
7
|
exports.fromLegacyPlacement = fromLegacyPlacement;
|
|
7
8
|
exports.placementMapping = void 0;
|
|
9
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
10
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
11
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
12
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
8
13
|
/**
|
|
9
14
|
* Legacy Popper.js placement values (used by `@atlaskit/popper`, `@atlaskit/popup`,
|
|
10
15
|
* `@atlaskit/tooltip`, etc.).
|
|
@@ -135,11 +140,34 @@ var placementMapping = exports.placementMapping = {
|
|
|
135
140
|
};
|
|
136
141
|
|
|
137
142
|
/**
|
|
138
|
-
* Convert a legacy Popper.js placement to the new
|
|
143
|
+
* Convert a legacy Popper.js placement to the new `position-area`-based Placement.
|
|
144
|
+
* Optional `offset` is the legacy Popper `[along, away]` tuple (also known as
|
|
145
|
+
* `[skidding, distance]`); negative `along` becomes `direction: 'backwards'`.
|
|
146
|
+
*
|
|
147
|
+
* The tuple keeps the historical popper names so call sites that already pass
|
|
148
|
+
* `[along, away]` migrate without thinking; the new internal field names
|
|
149
|
+
* (`gap` / `crossAxisShift`) are produced inside this adapter.
|
|
139
150
|
*/
|
|
140
151
|
function fromLegacyPlacement(_ref) {
|
|
141
|
-
var legacy = _ref.legacy
|
|
142
|
-
|
|
152
|
+
var legacy = _ref.legacy,
|
|
153
|
+
offset = _ref.offset;
|
|
154
|
+
var basePlacement = placementMapping[legacy];
|
|
155
|
+
if (!offset) {
|
|
156
|
+
return basePlacement;
|
|
157
|
+
}
|
|
158
|
+
var _offset = (0, _slicedToArray2.default)(offset, 2),
|
|
159
|
+
along = _offset[0],
|
|
160
|
+
away = _offset[1];
|
|
161
|
+
return _objectSpread(_objectSpread({}, basePlacement), {}, {
|
|
162
|
+
offset: {
|
|
163
|
+
gap: away,
|
|
164
|
+
crossAxisShift: {
|
|
165
|
+
// Sign is captured in `direction`; `value` is the magnitude.
|
|
166
|
+
value: Math.abs(along),
|
|
167
|
+
direction: along >= 0 ? 'forwards' : 'backwards'
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
143
171
|
}
|
|
144
172
|
|
|
145
173
|
/**
|
|
@@ -41,7 +41,6 @@ var PopupContent = exports.PopupContent = /*#__PURE__*/(0, _react.forwardRef)(fu
|
|
|
41
41
|
placementProp = _ref.placement,
|
|
42
42
|
onCloseProp = _ref.onClose,
|
|
43
43
|
testIdProp = _ref.testId,
|
|
44
|
-
offsetProp = _ref.offset,
|
|
45
44
|
forceFallbackPositioningProp = _ref.forceFallbackPositioning;
|
|
46
45
|
// When inside <Popup>, context provides defaults. Props always win.
|
|
47
46
|
var ctx = (0, _popupContext.useMaybePopupContext)();
|
|
@@ -138,7 +137,6 @@ var PopupContent = exports.PopupContent = /*#__PURE__*/(0, _react.forwardRef)(fu
|
|
|
138
137
|
},
|
|
139
138
|
popoverRef: popoverRef,
|
|
140
139
|
placement: placement,
|
|
141
|
-
offset: offsetProp,
|
|
142
140
|
forceFallbackPositioning: forceFallbackPositioning,
|
|
143
141
|
arrow: arrowPreset !== null && arrowPreset !== void 0 ? arrowPreset : undefined
|
|
144
142
|
});
|
|
@@ -79,70 +79,89 @@ const ARROW_CSS = `
|
|
|
79
79
|
* inline-end (right of anchor) → margin-inline-start (left, facing anchor)
|
|
80
80
|
*/
|
|
81
81
|
|
|
82
|
-
/*
|
|
82
|
+
/* Each rule sets two margins: the anchor-facing margin for the gap
|
|
83
|
+
(via --ds-arrow-size) and one cross-axis margin for the shift
|
|
84
|
+
(via one of --ds-cross-axis-shift-margin-start, --ds-cross-axis-shift-margin-end,
|
|
85
|
+
--ds-cross-axis-shift-margin-block-start, or --ds-cross-axis-shift-margin-block-end). The
|
|
86
|
+
side matches the popover's anchored cross-axis edge per align. Only
|
|
87
|
+
one of the four properties is non-zero at a time; useAnchorPosition
|
|
88
|
+
picks it. See notes/architecture/positioning.md for details. */
|
|
89
|
+
/* Single-axis centered (align: center) */
|
|
83
90
|
@position-try --ds-arrow-block-start {
|
|
84
91
|
position-area: block-start;
|
|
85
92
|
margin: 0;
|
|
86
93
|
margin-block-end: var(--ds-arrow-size, 8px);
|
|
94
|
+
margin-inline-start: var(--ds-cross-axis-shift-margin-start, 0px);
|
|
87
95
|
}
|
|
88
96
|
@position-try --ds-arrow-block-end {
|
|
89
97
|
position-area: block-end;
|
|
90
98
|
margin: 0;
|
|
91
99
|
margin-block-start: var(--ds-arrow-size, 8px);
|
|
100
|
+
margin-inline-start: var(--ds-cross-axis-shift-margin-start, 0px);
|
|
92
101
|
}
|
|
93
102
|
@position-try --ds-arrow-inline-start {
|
|
94
103
|
position-area: inline-start;
|
|
95
104
|
margin: 0;
|
|
96
105
|
margin-inline-end: var(--ds-arrow-size, 8px);
|
|
106
|
+
margin-block-start: var(--ds-cross-axis-shift-margin-block-start, 0px);
|
|
97
107
|
}
|
|
98
108
|
@position-try --ds-arrow-inline-end {
|
|
99
109
|
position-area: inline-end;
|
|
100
110
|
margin: 0;
|
|
101
111
|
margin-inline-start: var(--ds-arrow-size, 8px);
|
|
112
|
+
margin-block-start: var(--ds-cross-axis-shift-margin-block-start, 0px);
|
|
102
113
|
}
|
|
103
114
|
|
|
104
|
-
/* Block edge + inline span */
|
|
115
|
+
/* Block edge + inline span (align: start uses span-inline-end; align: end uses span-inline-start) */
|
|
105
116
|
@position-try --ds-arrow-block-start-span-inline-start {
|
|
106
117
|
position-area: block-start span-inline-start;
|
|
107
118
|
margin: 0;
|
|
108
119
|
margin-block-end: var(--ds-arrow-size, 8px);
|
|
120
|
+
margin-inline-end: var(--ds-cross-axis-shift-margin-end, 0px);
|
|
109
121
|
}
|
|
110
122
|
@position-try --ds-arrow-block-start-span-inline-end {
|
|
111
123
|
position-area: block-start span-inline-end;
|
|
112
124
|
margin: 0;
|
|
113
125
|
margin-block-end: var(--ds-arrow-size, 8px);
|
|
126
|
+
margin-inline-start: var(--ds-cross-axis-shift-margin-start, 0px);
|
|
114
127
|
}
|
|
115
128
|
@position-try --ds-arrow-block-end-span-inline-start {
|
|
116
129
|
position-area: block-end span-inline-start;
|
|
117
130
|
margin: 0;
|
|
118
131
|
margin-block-start: var(--ds-arrow-size, 8px);
|
|
132
|
+
margin-inline-end: var(--ds-cross-axis-shift-margin-end, 0px);
|
|
119
133
|
}
|
|
120
134
|
@position-try --ds-arrow-block-end-span-inline-end {
|
|
121
135
|
position-area: block-end span-inline-end;
|
|
122
136
|
margin: 0;
|
|
123
137
|
margin-block-start: var(--ds-arrow-size, 8px);
|
|
138
|
+
margin-inline-start: var(--ds-cross-axis-shift-margin-start, 0px);
|
|
124
139
|
}
|
|
125
140
|
|
|
126
|
-
/* Inline edge + block span */
|
|
141
|
+
/* Inline edge + block span (align: start uses span-block-end; align: end uses span-block-start) */
|
|
127
142
|
@position-try --ds-arrow-inline-start-span-block-start {
|
|
128
143
|
position-area: inline-start span-block-start;
|
|
129
144
|
margin: 0;
|
|
130
145
|
margin-inline-end: var(--ds-arrow-size, 8px);
|
|
146
|
+
margin-block-end: var(--ds-cross-axis-shift-margin-block-end, 0px);
|
|
131
147
|
}
|
|
132
148
|
@position-try --ds-arrow-inline-start-span-block-end {
|
|
133
149
|
position-area: inline-start span-block-end;
|
|
134
150
|
margin: 0;
|
|
135
151
|
margin-inline-end: var(--ds-arrow-size, 8px);
|
|
152
|
+
margin-block-start: var(--ds-cross-axis-shift-margin-block-start, 0px);
|
|
136
153
|
}
|
|
137
154
|
@position-try --ds-arrow-inline-end-span-block-start {
|
|
138
155
|
position-area: inline-end span-block-start;
|
|
139
156
|
margin: 0;
|
|
140
157
|
margin-inline-start: var(--ds-arrow-size, 8px);
|
|
158
|
+
margin-block-end: var(--ds-cross-axis-shift-margin-block-end, 0px);
|
|
141
159
|
}
|
|
142
160
|
@position-try --ds-arrow-inline-end-span-block-end {
|
|
143
161
|
position-area: inline-end span-block-end;
|
|
144
162
|
margin: 0;
|
|
145
163
|
margin-inline-start: var(--ds-arrow-size, 8px);
|
|
164
|
+
margin-block-start: var(--ds-cross-axis-shift-margin-block-start, 0px);
|
|
146
165
|
}
|
|
147
166
|
`;
|
|
148
167
|
|
|
@@ -1,6 +1,20 @@
|
|
|
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
|
+
|
|
1
13
|
/**
|
|
2
14
|
* Computes the base {top, left} position based on the primary edge,
|
|
3
15
|
* flipping to the opposite side when there is not enough space.
|
|
16
|
+
*
|
|
17
|
+
* `gap` is applied along the main axis between trigger and popover.
|
|
4
18
|
*/
|
|
5
19
|
function computeEdgePosition({
|
|
6
20
|
axis,
|
|
@@ -9,14 +23,16 @@ function computeEdgePosition({
|
|
|
9
23
|
popoverWidth,
|
|
10
24
|
popoverHeight,
|
|
11
25
|
viewport,
|
|
12
|
-
|
|
26
|
+
gap
|
|
13
27
|
}) {
|
|
14
28
|
if (axis === 'block' && edge === 'end') {
|
|
15
29
|
// Below trigger
|
|
16
30
|
const spaceBelow = viewport.height - triggerRect.bottom;
|
|
17
31
|
const spaceAbove = triggerRect.top;
|
|
32
|
+
const fitsBelow = spaceBelow >= popoverHeight + gap;
|
|
33
|
+
const useBelow = fitsBelow || spaceBelow >= spaceAbove;
|
|
18
34
|
return {
|
|
19
|
-
top:
|
|
35
|
+
top: useBelow ? triggerRect.bottom + gap : triggerRect.top - popoverHeight - gap,
|
|
20
36
|
left: triggerRect.left + triggerRect.width / 2 - popoverWidth / 2
|
|
21
37
|
};
|
|
22
38
|
}
|
|
@@ -24,8 +40,10 @@ function computeEdgePosition({
|
|
|
24
40
|
// Above trigger
|
|
25
41
|
const spaceAbove = triggerRect.top;
|
|
26
42
|
const spaceBelow = viewport.height - triggerRect.bottom;
|
|
43
|
+
const fitsAbove = spaceAbove >= popoverHeight + gap;
|
|
44
|
+
const useAbove = fitsAbove || spaceAbove >= spaceBelow;
|
|
27
45
|
return {
|
|
28
|
-
top:
|
|
46
|
+
top: useAbove ? triggerRect.top - popoverHeight - gap : triggerRect.bottom + gap,
|
|
29
47
|
left: triggerRect.left + triggerRect.width / 2 - popoverWidth / 2
|
|
30
48
|
};
|
|
31
49
|
}
|
|
@@ -33,24 +51,28 @@ function computeEdgePosition({
|
|
|
33
51
|
// Right of trigger
|
|
34
52
|
const spaceRight = viewport.width - triggerRect.right;
|
|
35
53
|
const spaceLeft = triggerRect.left;
|
|
54
|
+
const fitsRight = spaceRight >= popoverWidth + gap;
|
|
55
|
+
const useRight = fitsRight || spaceRight >= spaceLeft;
|
|
36
56
|
return {
|
|
37
57
|
top: triggerRect.top + triggerRect.height / 2 - popoverHeight / 2,
|
|
38
|
-
left:
|
|
58
|
+
left: useRight ? triggerRect.right + gap : triggerRect.left - popoverWidth - gap
|
|
39
59
|
};
|
|
40
60
|
}
|
|
41
61
|
if (axis === 'inline' && edge === 'start') {
|
|
42
62
|
// Left of trigger
|
|
43
63
|
const spaceLeft = triggerRect.left;
|
|
44
64
|
const spaceRight = viewport.width - triggerRect.right;
|
|
65
|
+
const fitsLeft = spaceLeft >= popoverWidth + gap;
|
|
66
|
+
const useLeft = fitsLeft || spaceLeft >= spaceRight;
|
|
45
67
|
return {
|
|
46
68
|
top: triggerRect.top + triggerRect.height / 2 - popoverHeight / 2,
|
|
47
|
-
left:
|
|
69
|
+
left: useLeft ? triggerRect.left - popoverWidth - gap : triggerRect.right + gap
|
|
48
70
|
};
|
|
49
71
|
}
|
|
50
72
|
|
|
51
73
|
// Unreachable for valid placements; defaults to below trigger
|
|
52
74
|
return {
|
|
53
|
-
top: triggerRect.bottom +
|
|
75
|
+
top: triggerRect.bottom + gap,
|
|
54
76
|
left: triggerRect.left + triggerRect.width / 2 - popoverWidth / 2
|
|
55
77
|
};
|
|
56
78
|
}
|
|
@@ -102,21 +124,55 @@ function applyAlignment({
|
|
|
102
124
|
}
|
|
103
125
|
|
|
104
126
|
/**
|
|
105
|
-
*
|
|
127
|
+
* Applies the cross-axis shift offset.
|
|
106
128
|
*
|
|
107
|
-
*
|
|
108
|
-
* -
|
|
109
|
-
*
|
|
110
|
-
*
|
|
129
|
+
* The sign mirrors the CSS path (see `crossAxisShiftMargin` in
|
|
130
|
+
* `use-anchor-positioning.tsx`):
|
|
131
|
+
* - `align: 'start' | 'center'` + `forwards` → push toward end (positive)
|
|
132
|
+
* - `align: 'end'` + `forwards` → push toward start (negative)
|
|
133
|
+
* - `backwards` flips the sign in both cases.
|
|
134
|
+
*/
|
|
135
|
+
function applyCrossAxisShift({
|
|
136
|
+
position,
|
|
137
|
+
axis,
|
|
138
|
+
align,
|
|
139
|
+
crossAxisShift
|
|
140
|
+
}) {
|
|
141
|
+
if (crossAxisShift.value === 0) {
|
|
142
|
+
return position;
|
|
143
|
+
}
|
|
144
|
+
const directionSign = crossAxisShift.direction === 'forwards' ? 1 : -1;
|
|
145
|
+
const sideSign = align === 'end' ? -1 : 1;
|
|
146
|
+
const delta = crossAxisShift.value * directionSign * sideSign;
|
|
147
|
+
|
|
148
|
+
// Cross-axis is the OPPOSITE of the placement axis.
|
|
149
|
+
if (axis === 'block') {
|
|
150
|
+
return {
|
|
151
|
+
...position,
|
|
152
|
+
left: position.left + delta
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
...position,
|
|
157
|
+
top: position.top + delta
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Computes popover position from trigger rect and viewport, flipping to
|
|
163
|
+
* the side with more space when needed. Re-runs on scroll/resize.
|
|
111
164
|
*
|
|
112
|
-
*
|
|
165
|
+
* `gap` and `crossAxisShift` are pre-resolved to pixels by the
|
|
166
|
+
* caller (see `resolveCssLengthToPixels`), so this function stays purely
|
|
167
|
+
* numeric.
|
|
113
168
|
*/
|
|
114
169
|
export function computeFallbackPosition({
|
|
115
170
|
triggerRect,
|
|
116
171
|
popoverEl,
|
|
117
172
|
placement,
|
|
118
173
|
viewport,
|
|
119
|
-
|
|
174
|
+
gap,
|
|
175
|
+
crossAxisShift
|
|
120
176
|
}) {
|
|
121
177
|
const {
|
|
122
178
|
axis,
|
|
@@ -124,7 +180,8 @@ export function computeFallbackPosition({
|
|
|
124
180
|
align
|
|
125
181
|
} = placement;
|
|
126
182
|
|
|
127
|
-
// Measure popover dimensions
|
|
183
|
+
// Measure popover dimensions. Callers must ensure the popover has
|
|
184
|
+
// laid out before calling this (it should not be `display: none`).
|
|
128
185
|
const popoverWidth = popoverEl.offsetWidth;
|
|
129
186
|
const popoverHeight = popoverEl.offsetHeight;
|
|
130
187
|
const basePosition = computeEdgePosition({
|
|
@@ -134,7 +191,7 @@ export function computeFallbackPosition({
|
|
|
134
191
|
popoverWidth,
|
|
135
192
|
popoverHeight,
|
|
136
193
|
viewport,
|
|
137
|
-
|
|
194
|
+
gap
|
|
138
195
|
});
|
|
139
196
|
const alignedPosition = applyAlignment({
|
|
140
197
|
position: basePosition,
|
|
@@ -144,10 +201,16 @@ export function computeFallbackPosition({
|
|
|
144
201
|
popoverWidth,
|
|
145
202
|
popoverHeight
|
|
146
203
|
});
|
|
204
|
+
const shiftedPosition = applyCrossAxisShift({
|
|
205
|
+
position: alignedPosition,
|
|
206
|
+
axis,
|
|
207
|
+
align,
|
|
208
|
+
crossAxisShift
|
|
209
|
+
});
|
|
147
210
|
|
|
148
211
|
// Clamp to viewport so the popover is never offscreen
|
|
149
212
|
return {
|
|
150
|
-
top: Math.max(0, Math.min(
|
|
151
|
-
left: Math.max(0, Math.min(
|
|
213
|
+
top: Math.max(0, Math.min(shiftedPosition.top, viewport.height - popoverHeight)),
|
|
214
|
+
left: Math.max(0, Math.min(shiftedPosition.left, viewport.width - popoverWidth))
|
|
152
215
|
};
|
|
153
216
|
}
|