@atlaskit/top-layer 0.1.4 → 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 +32 -0
- package/dist/cjs/arrow/index.js +2 -2
- 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 +181 -37
- package/dist/cjs/placement-map/index.js +31 -3
- package/dist/cjs/popup/popup-content.js +0 -2
- package/dist/es2019/arrow/index.js +24 -5
- 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 +174 -29
- package/dist/es2019/placement-map/index.js +25 -3
- package/dist/es2019/popup/popup-content.js +0 -2
- package/dist/esm/arrow/index.js +2 -2
- 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 +180 -37
- 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
|
@@ -1,9 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anchor positioning for top-layer popovers. See
|
|
3
|
+
* `notes/architecture/positioning.md` for the full model.
|
|
4
|
+
*
|
|
5
|
+
* Two runtime paths: CSS Anchor Positioning (modern browsers) and a JS
|
|
6
|
+
* fallback that measures the trigger and writes `top`/`left`.
|
|
7
|
+
*
|
|
8
|
+
* Both paths honour `offset.gap` and `offset.crossAxisShift`. The
|
|
9
|
+
* CSS path passes the values through as strings; the JS fallback resolves
|
|
10
|
+
* them to pixels via a hidden DOM probe (see `resolveCssLengthToPixels`)
|
|
11
|
+
* so tokens, `calc()`, `var()`, etc all work. The JS fallback hides the
|
|
12
|
+
* popover with `opacity: 0` until the first measurement completes, so it
|
|
13
|
+
* is never painted at the wrong location.
|
|
14
|
+
*/
|
|
1
15
|
import { useId, useLayoutEffect } from 'react';
|
|
2
16
|
import { bind } from 'bind-event-listener';
|
|
3
17
|
import rafSchedule from 'raf-schd';
|
|
4
18
|
import once from '@atlaskit/ds-lib/once';
|
|
5
19
|
import { computeFallbackPosition } from './anchor-positioning-fallback';
|
|
6
20
|
import { combine } from './combine';
|
|
21
|
+
import { resolveCssLengthToPixels } from './resolve-css-length-to-pixels';
|
|
7
22
|
import { getPlacement } from './resolve-placement';
|
|
8
23
|
import { setStyle } from './set-style';
|
|
9
24
|
export { getPlacement } from './resolve-placement';
|
|
@@ -87,6 +102,19 @@ export function placementToTryFallbacks(_ref2) {
|
|
|
87
102
|
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(', ');
|
|
88
103
|
}
|
|
89
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Returns true when the popover is currently open. Wrapped because the
|
|
107
|
+
* `:popover-open` pseudo-class is not implemented in some test environments
|
|
108
|
+
* (e.g. jsdom), where `matches` throws on unknown selectors.
|
|
109
|
+
*/
|
|
110
|
+
function isPopoverOpen(popover) {
|
|
111
|
+
try {
|
|
112
|
+
return popover.matches(':popover-open');
|
|
113
|
+
} catch (_unused) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
90
118
|
/**
|
|
91
119
|
* Detects whether the browser supports CSS Anchor Positioning.
|
|
92
120
|
* Uses `once()` for lazy evaluation — safe for SSR and avoids hydration mismatches.
|
|
@@ -98,12 +126,6 @@ var supportsAnchorPositioning = once(function () {
|
|
|
98
126
|
return CSS.supports('anchor-name', '--a');
|
|
99
127
|
});
|
|
100
128
|
|
|
101
|
-
/**
|
|
102
|
-
* Default gap between the popover and its trigger (in pixels).
|
|
103
|
-
* Maps to `space.100` (8px) in the design token scale.
|
|
104
|
-
*/
|
|
105
|
-
var DEFAULT_OFFSET = 8;
|
|
106
|
-
|
|
107
129
|
/**
|
|
108
130
|
* Returns the CSS margin declaration that creates a gap between the
|
|
109
131
|
* popover and its trigger on the side facing the anchor.
|
|
@@ -116,7 +138,7 @@ function edgeMargin(_ref3) {
|
|
|
116
138
|
offset = _ref3.offset;
|
|
117
139
|
var axis = placement.axis,
|
|
118
140
|
edge = placement.edge;
|
|
119
|
-
var value =
|
|
141
|
+
var value = offset;
|
|
120
142
|
if (axis === 'block' && edge === 'end') {
|
|
121
143
|
return {
|
|
122
144
|
property: 'margin-block-start',
|
|
@@ -147,6 +169,33 @@ function edgeMargin(_ref3) {
|
|
|
147
169
|
};
|
|
148
170
|
}
|
|
149
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Cross-axis shift margin. Margin side matches the popover's anchored
|
|
174
|
+
* cross-axis edge: `align: 'start'` uses the START margin, `align: 'end'`
|
|
175
|
+
* uses the END margin with the sign inverted (so `forwards` is always
|
|
176
|
+
* positive), `align: 'center'` uses START. Margin on the un-anchored side
|
|
177
|
+
* has no effect under CSS Anchor Positioning.
|
|
178
|
+
*/
|
|
179
|
+
function crossAxisShiftMargin(_ref4) {
|
|
180
|
+
var placement = _ref4.placement,
|
|
181
|
+
crossAxisShiftCssValue = _ref4.crossAxisShiftCssValue,
|
|
182
|
+
direction = _ref4.direction;
|
|
183
|
+
var axis = placement.axis,
|
|
184
|
+
align = placement.align;
|
|
185
|
+
var directionSign = direction === 'forwards' ? 1 : -1;
|
|
186
|
+
var crossAxis = axis === 'block' ? 'inline' : 'block';
|
|
187
|
+
var useEndSide = align === 'end';
|
|
188
|
+
var side = useEndSide ? 'end' : 'start';
|
|
189
|
+
var sideSign = useEndSide ? -1 : 1;
|
|
190
|
+
var finalSign = directionSign * sideSign;
|
|
191
|
+
// Wrap any string in calc() with the sign factor. CSS handles the math.
|
|
192
|
+
var value = finalSign === 1 ? crossAxisShiftCssValue : "calc(-1 * ".concat(crossAxisShiftCssValue, ")");
|
|
193
|
+
return {
|
|
194
|
+
property: "margin-".concat(crossAxis, "-").concat(side),
|
|
195
|
+
value: value
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
150
199
|
/**
|
|
151
200
|
* Hook that positions an element relative to an anchor element using
|
|
152
201
|
* CSS Anchor Positioning (with a JS fallback for older browsers).
|
|
@@ -163,15 +212,13 @@ function edgeMargin(_ref3) {
|
|
|
163
212
|
* `position: fixed` with measured coordinates, re-running on scroll (capture)
|
|
164
213
|
* and resize events.
|
|
165
214
|
*/
|
|
166
|
-
export function useAnchorPosition(
|
|
167
|
-
var anchorRef =
|
|
168
|
-
popoverRef =
|
|
169
|
-
placement =
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
forceFallbackPositioning = _ref4$forceFallbackPo === void 0 ? false : _ref4$forceFallbackPo,
|
|
174
|
-
arrow = _ref4.arrow;
|
|
215
|
+
export function useAnchorPosition(_ref5) {
|
|
216
|
+
var anchorRef = _ref5.anchorRef,
|
|
217
|
+
popoverRef = _ref5.popoverRef,
|
|
218
|
+
placement = _ref5.placement,
|
|
219
|
+
_ref5$forceFallbackPo = _ref5.forceFallbackPositioning,
|
|
220
|
+
forceFallbackPositioning = _ref5$forceFallbackPo === void 0 ? false : _ref5$forceFallbackPo,
|
|
221
|
+
arrow = _ref5.arrow;
|
|
175
222
|
var id = useId();
|
|
176
223
|
var anchorName = "--anchor-".concat(id.replace(/:/g, ''));
|
|
177
224
|
useLayoutEffect(function () {
|
|
@@ -183,11 +230,18 @@ export function useAnchorPosition(_ref4) {
|
|
|
183
230
|
var resolvedPlacement = getPlacement({
|
|
184
231
|
placement: placement
|
|
185
232
|
});
|
|
233
|
+
var gapCssValue = resolvedPlacement.offset.gap;
|
|
234
|
+
var crossAxisShiftCssValue = resolvedPlacement.offset.crossAxisShift.value;
|
|
186
235
|
var shouldForceFallback = Boolean(forceFallbackPositioning);
|
|
187
236
|
if (supportsAnchorPositioning() && !shouldForceFallback) {
|
|
188
237
|
var gap = edgeMargin({
|
|
189
238
|
placement: resolvedPlacement,
|
|
190
|
-
offset:
|
|
239
|
+
offset: gapCssValue
|
|
240
|
+
});
|
|
241
|
+
var crossAxisShift = crossAxisShiftMargin({
|
|
242
|
+
placement: resolvedPlacement,
|
|
243
|
+
crossAxisShiftCssValue: crossAxisShiftCssValue,
|
|
244
|
+
direction: resolvedPlacement.offset.crossAxisShift.direction
|
|
191
245
|
});
|
|
192
246
|
var popoverStyles = [{
|
|
193
247
|
property: 'position-anchor',
|
|
@@ -214,20 +268,65 @@ export function useAnchorPosition(_ref4) {
|
|
|
214
268
|
property: 'inset',
|
|
215
269
|
value: 'auto'
|
|
216
270
|
},
|
|
217
|
-
//
|
|
271
|
+
// Main-axis gap between the popover and its trigger
|
|
218
272
|
{
|
|
219
273
|
property: gap.property,
|
|
220
274
|
value: gap.value
|
|
275
|
+
},
|
|
276
|
+
// Cross-axis shift
|
|
277
|
+
{
|
|
278
|
+
property: crossAxisShift.property,
|
|
279
|
+
value: crossAxisShift.value
|
|
221
280
|
}];
|
|
281
|
+
|
|
282
|
+
// Surface the active cross-axis shift value as a custom property so
|
|
283
|
+
// each named arrow @position-try rule can re-apply it after a flip.
|
|
284
|
+
// The four properties cover (cross-axis, side); only one is non-zero
|
|
285
|
+
// at a time.
|
|
286
|
+
popoverStyles.push({
|
|
287
|
+
property: '--ds-cross-axis-shift-margin-start',
|
|
288
|
+
value: '0px'
|
|
289
|
+
});
|
|
290
|
+
popoverStyles.push({
|
|
291
|
+
property: '--ds-cross-axis-shift-margin-end',
|
|
292
|
+
value: '0px'
|
|
293
|
+
});
|
|
294
|
+
popoverStyles.push({
|
|
295
|
+
property: '--ds-cross-axis-shift-margin-block-start',
|
|
296
|
+
value: '0px'
|
|
297
|
+
});
|
|
298
|
+
popoverStyles.push({
|
|
299
|
+
property: '--ds-cross-axis-shift-margin-block-end',
|
|
300
|
+
value: '0px'
|
|
301
|
+
});
|
|
302
|
+
var crossAxisShiftActive = crossAxisShiftMargin({
|
|
303
|
+
placement: resolvedPlacement,
|
|
304
|
+
crossAxisShiftCssValue: crossAxisShiftCssValue,
|
|
305
|
+
direction: resolvedPlacement.offset.crossAxisShift.direction
|
|
306
|
+
});
|
|
307
|
+
// Map margin property to its corresponding custom property name.
|
|
308
|
+
var customPropertyByMarginProperty = {
|
|
309
|
+
'margin-inline-start': '--ds-cross-axis-shift-margin-start',
|
|
310
|
+
'margin-inline-end': '--ds-cross-axis-shift-margin-end',
|
|
311
|
+
'margin-block-start': '--ds-cross-axis-shift-margin-block-start',
|
|
312
|
+
'margin-block-end': '--ds-cross-axis-shift-margin-block-end'
|
|
313
|
+
};
|
|
314
|
+
var crossAxisShiftCustomProperty = customPropertyByMarginProperty[crossAxisShiftActive.property];
|
|
315
|
+
if (crossAxisShiftCustomProperty) {
|
|
316
|
+
popoverStyles.push({
|
|
317
|
+
property: crossAxisShiftCustomProperty,
|
|
318
|
+
value: crossAxisShiftActive.value
|
|
319
|
+
});
|
|
320
|
+
}
|
|
222
321
|
if (arrow) {
|
|
223
322
|
// Set the arrow size variable used by the injected arrow CSS
|
|
224
323
|
// and the @position-try rules for margin values.
|
|
225
324
|
popoverStyles.push({
|
|
226
325
|
property: '--ds-arrow-size',
|
|
227
|
-
value:
|
|
326
|
+
value: gapCssValue
|
|
228
327
|
});
|
|
229
328
|
// Mark the popover root as the arrow host. The ARROW_CSS targets
|
|
230
|
-
// [data-ds-popover-arrow] to apply clip-path: inset(
|
|
329
|
+
// [data-ds-popover-arrow] to apply clip-path: inset(0) margin-box
|
|
231
330
|
// and box-shadow: none. Must be on the popover root (not a child)
|
|
232
331
|
// because that's where useAnchorPosition sets the margin gap.
|
|
233
332
|
popover.setAttribute('data-ds-popover-arrow', '');
|
|
@@ -262,6 +361,7 @@ export function useAnchorPosition(_ref4) {
|
|
|
262
361
|
}]
|
|
263
362
|
});
|
|
264
363
|
function update() {
|
|
364
|
+
var _popover$parentElemen;
|
|
265
365
|
if (!trigger || !popover) {
|
|
266
366
|
return;
|
|
267
367
|
}
|
|
@@ -270,41 +370,86 @@ export function useAnchorPosition(_ref4) {
|
|
|
270
370
|
width: window.innerWidth,
|
|
271
371
|
height: window.innerHeight
|
|
272
372
|
};
|
|
373
|
+
|
|
374
|
+
// Resolve consumer-supplied CSS length strings (tokens, calc, var,
|
|
375
|
+
// px, rem, etc) to pixels. The probe is mounted next to the popover
|
|
376
|
+
// so it inherits the same custom-property scope and font size.
|
|
377
|
+
// `popover.parentElement` is guaranteed to exist for any rendered
|
|
378
|
+
// popover (the popover itself is appended somewhere in the tree),
|
|
379
|
+
// but fall back to the popover element if not for safety.
|
|
380
|
+
var probeContainer = (_popover$parentElemen = popover.parentElement) !== null && _popover$parentElemen !== void 0 ? _popover$parentElemen : popover;
|
|
381
|
+
var gapPx = resolveCssLengthToPixels({
|
|
382
|
+
value: gapCssValue,
|
|
383
|
+
container: probeContainer
|
|
384
|
+
});
|
|
385
|
+
var crossAxisShiftPx = resolveCssLengthToPixels({
|
|
386
|
+
value: crossAxisShiftCssValue,
|
|
387
|
+
container: probeContainer
|
|
388
|
+
});
|
|
273
389
|
var _computeFallbackPosit = computeFallbackPosition({
|
|
274
390
|
triggerRect: triggerRect,
|
|
275
391
|
popoverEl: popover,
|
|
276
392
|
placement: resolvedPlacement,
|
|
277
393
|
viewport: viewport,
|
|
278
|
-
|
|
394
|
+
gap: gapPx,
|
|
395
|
+
crossAxisShift: {
|
|
396
|
+
value: crossAxisShiftPx,
|
|
397
|
+
direction: resolvedPlacement.offset.crossAxisShift.direction
|
|
398
|
+
}
|
|
279
399
|
}),
|
|
280
400
|
top = _computeFallbackPosit.top,
|
|
281
401
|
left = _computeFallbackPosit.left;
|
|
282
402
|
popover.style.setProperty('top', "".concat(top, "px"));
|
|
283
403
|
popover.style.setProperty('left', "".concat(left, "px"));
|
|
404
|
+
// Reveal the popover only after it has been positioned.
|
|
405
|
+
// See the toggle listener below for why we hide on open.
|
|
406
|
+
popover.style.removeProperty('opacity');
|
|
284
407
|
}
|
|
285
408
|
|
|
286
409
|
// Throttle scroll/resize updates to one per animation frame
|
|
287
410
|
var scheduledUpdate = rafSchedule(update);
|
|
288
|
-
|
|
411
|
+
|
|
412
|
+
// Wait for the FIRST valid layout before measuring: the popover
|
|
413
|
+
// is `display: none` until `showPopover()`, and some browsers
|
|
414
|
+
// fire `toggle` before layout, so RAF-after-toggle is unreliable.
|
|
415
|
+
// ResizeObserver fires once the browser has real dimensions.
|
|
416
|
+
// Self-disconnects after one valid measurement; ongoing
|
|
417
|
+
// scroll/resize is handled by the window listeners below.
|
|
418
|
+
var resizeObserver = new ResizeObserver(function () {
|
|
419
|
+
if (popover.offsetWidth > 0 && popover.offsetHeight > 0) {
|
|
420
|
+
update();
|
|
421
|
+
resizeObserver.disconnect();
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// If the popover is already open by the time this effect runs (for
|
|
426
|
+
// example, the parent `useAnchorPosition` effect runs after the child
|
|
427
|
+
// `Popover` effect that called `showPopover()`), the initial `toggle`
|
|
428
|
+
// event has already fired and our listener below would miss it. Start
|
|
429
|
+
// observing immediately so the first measurement still happens.
|
|
430
|
+
if (isPopoverOpen(popover)) {
|
|
431
|
+
popover.style.setProperty('opacity', '0');
|
|
432
|
+
resizeObserver.observe(popover);
|
|
433
|
+
}
|
|
289
434
|
var undoPositioning = combine(cleanupBaseStyles,
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
// `
|
|
294
|
-
//
|
|
295
|
-
// `
|
|
296
|
-
// the element has been laid out — so dimensions are still 0.
|
|
297
|
-
// Waiting one frame guarantees layout has run and measurements
|
|
298
|
-
// are accurate.
|
|
435
|
+
// On every open: hide synchronously so the user never sees
|
|
436
|
+
// the UA-default position, then re-observe for a fresh
|
|
437
|
+
// measurement. We use `opacity: 0` rather than
|
|
438
|
+
// `visibility: hidden` because Firefox skips visibility-hidden
|
|
439
|
+
// elements during `<dialog>` initial-focus traversal — see
|
|
440
|
+
// `form-in-popup.spec.tsx` on `desktop-firefox`.
|
|
299
441
|
bind(popover, {
|
|
300
442
|
type: 'toggle',
|
|
301
443
|
listener: function listener(event) {
|
|
302
444
|
var toggleEvent = event;
|
|
303
445
|
if (toggleEvent.newState === 'open') {
|
|
304
|
-
|
|
446
|
+
popover.style.setProperty('opacity', '0');
|
|
447
|
+
resizeObserver.observe(popover);
|
|
305
448
|
}
|
|
306
449
|
}
|
|
307
|
-
}),
|
|
450
|
+
}), function () {
|
|
451
|
+
return resizeObserver.disconnect();
|
|
452
|
+
}, bind(window, {
|
|
308
453
|
type: 'scroll',
|
|
309
454
|
listener: scheduledUpdate,
|
|
310
455
|
options: {
|
|
@@ -319,12 +464,10 @@ export function useAnchorPosition(_ref4) {
|
|
|
319
464
|
}
|
|
320
465
|
}), function () {
|
|
321
466
|
scheduledUpdate.cancel();
|
|
322
|
-
if (pendingFrame !== null) {
|
|
323
|
-
cancelAnimationFrame(pendingFrame);
|
|
324
|
-
}
|
|
325
467
|
popover.style.removeProperty('top');
|
|
326
468
|
popover.style.removeProperty('left');
|
|
469
|
+
popover.style.removeProperty('opacity');
|
|
327
470
|
});
|
|
328
471
|
return undoPositioning;
|
|
329
|
-
}, [anchorRef, popoverRef, placement, anchorName,
|
|
472
|
+
}, [anchorRef, popoverRef, placement, anchorName, forceFallbackPositioning, arrow]);
|
|
330
473
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
|
+
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; }
|
|
4
|
+
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) { _defineProperty(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; }
|
|
1
5
|
/**
|
|
2
6
|
* Legacy Popper.js placement values (used by `@atlaskit/popper`, `@atlaskit/popup`,
|
|
3
7
|
* `@atlaskit/tooltip`, etc.).
|
|
@@ -128,11 +132,34 @@ var placementMapping = {
|
|
|
128
132
|
};
|
|
129
133
|
|
|
130
134
|
/**
|
|
131
|
-
* Convert a legacy Popper.js placement to the new
|
|
135
|
+
* Convert a legacy Popper.js placement to the new `position-area`-based Placement.
|
|
136
|
+
* Optional `offset` is the legacy Popper `[along, away]` tuple (also known as
|
|
137
|
+
* `[skidding, distance]`); negative `along` becomes `direction: 'backwards'`.
|
|
138
|
+
*
|
|
139
|
+
* The tuple keeps the historical popper names so call sites that already pass
|
|
140
|
+
* `[along, away]` migrate without thinking; the new internal field names
|
|
141
|
+
* (`gap` / `crossAxisShift`) are produced inside this adapter.
|
|
132
142
|
*/
|
|
133
143
|
export function fromLegacyPlacement(_ref) {
|
|
134
|
-
var legacy = _ref.legacy
|
|
135
|
-
|
|
144
|
+
var legacy = _ref.legacy,
|
|
145
|
+
offset = _ref.offset;
|
|
146
|
+
var basePlacement = placementMapping[legacy];
|
|
147
|
+
if (!offset) {
|
|
148
|
+
return basePlacement;
|
|
149
|
+
}
|
|
150
|
+
var _offset = _slicedToArray(offset, 2),
|
|
151
|
+
along = _offset[0],
|
|
152
|
+
away = _offset[1];
|
|
153
|
+
return _objectSpread(_objectSpread({}, basePlacement), {}, {
|
|
154
|
+
offset: {
|
|
155
|
+
gap: away,
|
|
156
|
+
crossAxisShift: {
|
|
157
|
+
// Sign is captured in `direction`; `value` is the magnitude.
|
|
158
|
+
value: Math.abs(along),
|
|
159
|
+
direction: along >= 0 ? 'forwards' : 'backwards'
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
136
163
|
}
|
|
137
164
|
|
|
138
165
|
/**
|
|
@@ -32,7 +32,6 @@ export var PopupContent = /*#__PURE__*/forwardRef(function PopupContent(_ref, re
|
|
|
32
32
|
placementProp = _ref.placement,
|
|
33
33
|
onCloseProp = _ref.onClose,
|
|
34
34
|
testIdProp = _ref.testId,
|
|
35
|
-
offsetProp = _ref.offset,
|
|
36
35
|
forceFallbackPositioningProp = _ref.forceFallbackPositioning;
|
|
37
36
|
// When inside <Popup>, context provides defaults. Props always win.
|
|
38
37
|
var ctx = useMaybePopupContext();
|
|
@@ -129,7 +128,6 @@ export var PopupContent = /*#__PURE__*/forwardRef(function PopupContent(_ref, re
|
|
|
129
128
|
},
|
|
130
129
|
popoverRef: popoverRef,
|
|
131
130
|
placement: placement,
|
|
132
|
-
offset: offsetProp,
|
|
133
131
|
forceFallbackPositioning: forceFallbackPositioning,
|
|
134
132
|
arrow: arrowPreset !== null && arrowPreset !== void 0 ? arrowPreset : undefined
|
|
135
133
|
});
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { type TPlacement } from './resolve-placement';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Computes popover position from trigger rect and viewport, flipping to
|
|
4
|
+
* the side with more space when needed. Re-runs on scroll/resize.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - "auto" placement: show on the edge with the most available space
|
|
9
|
-
*
|
|
10
|
-
* Re-runs on scroll (capture on window) and resize events.
|
|
6
|
+
* `gap` and `crossAxisShift` are pre-resolved to pixels by the
|
|
7
|
+
* caller (see `resolveCssLengthToPixels`), so this function stays purely
|
|
8
|
+
* numeric.
|
|
11
9
|
*/
|
|
12
|
-
export declare function computeFallbackPosition({ triggerRect, popoverEl, placement, viewport,
|
|
10
|
+
export declare function computeFallbackPosition({ triggerRect, popoverEl, placement, viewport, gap, crossAxisShift, }: {
|
|
13
11
|
triggerRect: DOMRect;
|
|
14
12
|
popoverEl: HTMLElement;
|
|
15
13
|
placement: TPlacement;
|
|
@@ -17,7 +15,11 @@ export declare function computeFallbackPosition({ triggerRect, popoverEl, placem
|
|
|
17
15
|
width: number;
|
|
18
16
|
height: number;
|
|
19
17
|
};
|
|
20
|
-
|
|
18
|
+
gap: number;
|
|
19
|
+
crossAxisShift: {
|
|
20
|
+
value: number;
|
|
21
|
+
direction: 'forwards' | 'backwards';
|
|
22
|
+
};
|
|
21
23
|
}): {
|
|
22
24
|
top: number;
|
|
23
25
|
left: number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a CSS length value to a pixel number using a hidden DOM probe.
|
|
3
|
+
*
|
|
4
|
+
* Used by the JS positioning fallback: consumer offsets can be numbers,
|
|
5
|
+
* `${n}px` strings, tokenised `var(--ds-space-100, 8px)` strings, `calc()`
|
|
6
|
+
* expressions, viewport units, etc. Parsing arbitrary CSS in JS is fragile
|
|
7
|
+
* (and impossible for `calc(var(...))` without a layout context), so we let
|
|
8
|
+
* the browser do the math.
|
|
9
|
+
*
|
|
10
|
+
* The probe is appended to `container` so it inherits the same containing
|
|
11
|
+
* block, font size, and custom-property scope as the popover. We use `width`
|
|
12
|
+
* because it accepts any CSS length, including negative `calc()` results
|
|
13
|
+
* (negative widths are clamped to 0 — see note below).
|
|
14
|
+
*
|
|
15
|
+
* Cost: one synchronous reflow per call. The fallback only resolves on
|
|
16
|
+
* placement/offset changes, not on every scroll/resize update, so this is
|
|
17
|
+
* not a hot path.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveCssLengthToPixels({ value, container, }: {
|
|
20
|
+
value: number | string;
|
|
21
|
+
container: HTMLElement;
|
|
22
|
+
}): number;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes an offset to a CSS length string. Numbers become `${n}px`;
|
|
3
|
+
* strings pass through. Apply at the API boundary so internal code only
|
|
4
|
+
* deals with strings.
|
|
5
|
+
*/
|
|
6
|
+
export declare function toCssLengthString({ value }: {
|
|
7
|
+
value: number | string;
|
|
8
|
+
}): string;
|
|
@@ -1,16 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Cross-axis shift. `value` is a CSS length string (consumer numbers are
|
|
3
|
+
* normalized to `${n}px`). `direction` is `'forwards'` (toward end) or
|
|
4
|
+
* `'backwards'` (toward start).
|
|
5
|
+
*/
|
|
6
|
+
export type TCrossAxisShiftOffset = {
|
|
7
|
+
value: string;
|
|
8
|
+
direction: 'forwards' | 'backwards';
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Fully-resolved placement (internal). Use `getPlacement` for partial input.
|
|
12
|
+
* `offset.gap` and `offset.crossAxisShift.value` are always CSS
|
|
13
|
+
* length strings.
|
|
4
14
|
*/
|
|
5
15
|
export type TPlacement = {
|
|
6
16
|
axis: 'block' | 'inline';
|
|
7
17
|
edge: 'start' | 'end';
|
|
8
18
|
align: 'start' | 'center' | 'end';
|
|
19
|
+
offset: {
|
|
20
|
+
gap: string;
|
|
21
|
+
crossAxisShift: {
|
|
22
|
+
value: string;
|
|
23
|
+
direction: 'forwards' | 'backwards';
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Partial placement options. `offset.gap` and
|
|
29
|
+
* `offset.crossAxisShift.value` accept either a number (pixels) or a
|
|
30
|
+
* CSS length string (e.g. `token('space.100')`).
|
|
31
|
+
*
|
|
32
|
+
* The two offset axes are named for clarity at the call site:
|
|
33
|
+
* - `gap` — distance away from the trigger along the placement axis
|
|
34
|
+
* - `crossAxisShift` — nudge along the trigger edge (perpendicular to the placement axis)
|
|
35
|
+
*/
|
|
36
|
+
export type TPlacementOptions = {
|
|
37
|
+
axis?: 'block' | 'inline';
|
|
38
|
+
edge?: 'start' | 'end';
|
|
39
|
+
align?: 'start' | 'center' | 'end';
|
|
40
|
+
offset?: {
|
|
41
|
+
gap?: number | string;
|
|
42
|
+
crossAxisShift?: {
|
|
43
|
+
value?: number | string;
|
|
44
|
+
direction?: 'forwards' | 'backwards';
|
|
45
|
+
};
|
|
46
|
+
};
|
|
9
47
|
};
|
|
10
48
|
/**
|
|
11
|
-
* Resolves a partial placement to its fully-specified form.
|
|
12
|
-
*
|
|
49
|
+
* Resolves a partial placement to its fully-specified form. Number offsets
|
|
50
|
+
* are normalized to `${n}px` strings.
|
|
51
|
+
*
|
|
52
|
+
* Defaults: `axis: 'block'`, `edge: 'end'`, `align: 'center'`,
|
|
53
|
+
* `offset.gap: token('space.100', '8px')`,
|
|
54
|
+
* `offset.crossAxisShift: { value: '0px', direction: 'forwards' }`.
|
|
13
55
|
*/
|
|
14
56
|
export declare function getPlacement({ placement }: {
|
|
15
|
-
placement:
|
|
57
|
+
placement: TPlacementOptions;
|
|
16
58
|
}): TPlacement;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anchor positioning for top-layer popovers. See
|
|
3
|
+
* `notes/architecture/positioning.md` for the full model.
|
|
4
|
+
*
|
|
5
|
+
* Two runtime paths: CSS Anchor Positioning (modern browsers) and a JS
|
|
6
|
+
* fallback that measures the trigger and writes `top`/`left`.
|
|
7
|
+
*
|
|
8
|
+
* Both paths honour `offset.gap` and `offset.crossAxisShift`. The
|
|
9
|
+
* CSS path passes the values through as strings; the JS fallback resolves
|
|
10
|
+
* them to pixels via a hidden DOM probe (see `resolveCssLengthToPixels`)
|
|
11
|
+
* so tokens, `calc()`, `var()`, etc all work. The JS fallback hides the
|
|
12
|
+
* popover with `opacity: 0` until the first measurement completes, so it
|
|
13
|
+
* is never painted at the wrong location.
|
|
14
|
+
*/
|
|
1
15
|
import { type RefObject } from 'react';
|
|
2
16
|
import { type TArrowPreset } from '../arrow/types';
|
|
3
|
-
import { type TPlacementOptions } from '../
|
|
17
|
+
import { type TPlacementOptions } from '../internal/resolve-placement';
|
|
4
18
|
export { getPlacement } from './resolve-placement';
|
|
5
19
|
/**
|
|
6
20
|
* Resolves placement options to CSS `position-area` syntax.
|
|
@@ -59,7 +73,7 @@ export declare function placementToTryFallbacks({ placement }: {
|
|
|
59
73
|
* `position: fixed` with measured coordinates, re-running on scroll (capture)
|
|
60
74
|
* and resize events.
|
|
61
75
|
*/
|
|
62
|
-
export declare function useAnchorPosition({ anchorRef, popoverRef, placement,
|
|
76
|
+
export declare function useAnchorPosition({ anchorRef, popoverRef, placement, forceFallbackPositioning, arrow, }: {
|
|
63
77
|
/**
|
|
64
78
|
* Element to position relative to.
|
|
65
79
|
*/
|
|
@@ -70,13 +84,10 @@ export declare function useAnchorPosition({ anchorRef, popoverRef, placement, of
|
|
|
70
84
|
popoverRef: RefObject<HTMLElement | null>;
|
|
71
85
|
/**
|
|
72
86
|
* Where to place the element relative to the anchor.
|
|
87
|
+
* `offset.gap` and `offset.crossAxisShift` are part of the
|
|
88
|
+
* placement object.
|
|
73
89
|
*/
|
|
74
90
|
placement: TPlacementOptions;
|
|
75
|
-
/**
|
|
76
|
-
* Gap between the positioned element and the anchor, in pixels.
|
|
77
|
-
* Defaults to 8 (`space.100`). Tooltip uses 4.
|
|
78
|
-
*/
|
|
79
|
-
offset?: number;
|
|
80
91
|
/**
|
|
81
92
|
* Forces the JavaScript positioning fallback even when the browser
|
|
82
93
|
* supports CSS Anchor Positioning. Useful for testing fallback
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type TPlacementOptions } from '../popup/types';
|
|
1
|
+
import { type TPlacementOptions } from '../internal/resolve-placement';
|
|
3
2
|
/**
|
|
4
3
|
* Legacy Popper.js placement values (used by `@atlaskit/popper`, `@atlaskit/popup`,
|
|
5
4
|
* `@atlaskit/tooltip`, etc.).
|
|
@@ -41,12 +40,19 @@ export type TLegacyPlacement = 'auto' | 'auto-start' | 'auto-end' | 'top' | 'top
|
|
|
41
40
|
* // newPlacement === { axis: 'block', edge: 'end', align: 'start' }
|
|
42
41
|
* ```
|
|
43
42
|
*/
|
|
44
|
-
declare const placementMapping: Record<TLegacyPlacement,
|
|
43
|
+
declare const placementMapping: Record<TLegacyPlacement, TPlacementOptions>;
|
|
45
44
|
/**
|
|
46
|
-
* Convert a legacy Popper.js placement to the new
|
|
45
|
+
* Convert a legacy Popper.js placement to the new `position-area`-based Placement.
|
|
46
|
+
* Optional `offset` is the legacy Popper `[along, away]` tuple (also known as
|
|
47
|
+
* `[skidding, distance]`); negative `along` becomes `direction: 'backwards'`.
|
|
48
|
+
*
|
|
49
|
+
* The tuple keeps the historical popper names so call sites that already pass
|
|
50
|
+
* `[along, away]` migrate without thinking; the new internal field names
|
|
51
|
+
* (`gap` / `crossAxisShift`) are produced inside this adapter.
|
|
47
52
|
*/
|
|
48
|
-
export declare function fromLegacyPlacement({ legacy }: {
|
|
53
|
+
export declare function fromLegacyPlacement({ legacy, offset, }: {
|
|
49
54
|
legacy: TLegacyPlacement;
|
|
55
|
+
offset?: [along: number, away: number];
|
|
50
56
|
}): TPlacementOptions;
|
|
51
57
|
/**
|
|
52
58
|
* The full mapping record, exposed for consumers that need to iterate or introspect.
|