@atlaskit/top-layer 4.4.5 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +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/{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/popover/popover.compiled.css +4 -0
- package/dist/cjs/popover/popover.js +11 -4
- 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/{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/popover/popover.compiled.css +4 -0
- package/dist/es2019/popover/popover.js +11 -4
- 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/{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/popover/popover.compiled.css +4 -0
- package/dist/esm/popover/popover.js +11 -4
- 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/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
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The placement axis joined to its edge, e.g. `block-end`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The cells to slide to along one edge, on the cross axis.
|
|
7
|
+
*
|
|
8
|
+
* A try option is only worth listing if its margin box can fit where an earlier
|
|
9
|
+
* one did not, and two options over the SAME cell have the same margin box and
|
|
10
|
+
* the same fit result. That rules out three cells for an aligned placement: the
|
|
11
|
+
* near span on the requested edge IS the base position-area, and on the opposite
|
|
12
|
+
* edge the near and far spans are where `flip-{A}` and the diagonal flip land.
|
|
13
|
+
*
|
|
14
|
+
* A centred placement has no near or far, so it slides to both spans on both
|
|
15
|
+
* edges.
|
|
16
|
+
*/
|
|
17
|
+
function slideOnEdge({
|
|
18
|
+
align,
|
|
19
|
+
edgeKey,
|
|
20
|
+
crossAxis,
|
|
21
|
+
isOppositeEdge
|
|
22
|
+
}) {
|
|
23
|
+
if (align === 'center') {
|
|
24
|
+
return [`${edgeKey} span-${crossAxis}-end`, `${edgeKey} span-${crossAxis}-start`];
|
|
25
|
+
}
|
|
26
|
+
if (isOppositeEdge) {
|
|
27
|
+
return [edgeKey];
|
|
28
|
+
}
|
|
29
|
+
// For `align: 'start'` the popover expands toward `end`, so the far span is
|
|
30
|
+
// `start`; `align: 'end'` mirrors this.
|
|
31
|
+
const far = align === 'start' ? 'start' : 'end';
|
|
32
|
+
return [edgeKey, `${edgeKey} span-${crossAxis}-${far}`];
|
|
33
|
+
}
|
|
34
|
+
function flipDiagonally({
|
|
35
|
+
axis
|
|
36
|
+
}) {
|
|
37
|
+
const flipOnMainAxis = axis === 'block' ? 'flip-block' : 'flip-inline';
|
|
38
|
+
const flipToCrossAxis = axis === 'block' ? 'flip-inline' : 'flip-block';
|
|
39
|
+
return `${flipOnMainAxis} ${flipToCrossAxis}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns the `position-try-fallbacks` value for a RESOLVED placement. The
|
|
44
|
+
* browser tries the entries in order and uses the first one whose margin box
|
|
45
|
+
* fits, so an entry over a cell already tried can never be chosen; `slideOnEdge`
|
|
46
|
+
* explains which ones those are.
|
|
47
|
+
*
|
|
48
|
+
* Centred: slide along the requested edge to either span, flip to the opposite
|
|
49
|
+
* edge, then slide along that edge to either span.
|
|
50
|
+
*
|
|
51
|
+
* Start / end aligned: slide along the requested edge (re-centred, then the far
|
|
52
|
+
* span), flip to the opposite edge keeping the alignment, flip on the diagonal
|
|
53
|
+
* into the opposite corner, then re-centre on the opposite edge. The diagonal is
|
|
54
|
+
* the clean escape when the popover would land off screen on both axes: a flip
|
|
55
|
+
* keyword carries the gap and shift margins across, where a position-area slide
|
|
56
|
+
* leaves them on the original sides.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* // Centred on the block-end edge (the default placement):
|
|
60
|
+
* placementToTryFallbacks({ placement: resolvePlacement({ placement: {} }) });
|
|
61
|
+
* // => 'block-end span-inline-end, block-end span-inline-start, flip-block,
|
|
62
|
+
* // block-start span-inline-end, block-start span-inline-start'
|
|
63
|
+
*
|
|
64
|
+
* // Start-aligned on the block-end edge:
|
|
65
|
+
* placementToTryFallbacks({ placement: resolvePlacement({ placement: { align: 'start' } }) });
|
|
66
|
+
* // => 'block-end, block-end span-inline-start, flip-block, flip-block flip-inline, block-start'
|
|
67
|
+
*/
|
|
68
|
+
export function placementToTryFallbacks({
|
|
69
|
+
placement
|
|
70
|
+
}) {
|
|
71
|
+
const {
|
|
72
|
+
axis,
|
|
73
|
+
edge,
|
|
74
|
+
align
|
|
75
|
+
} = placement;
|
|
76
|
+
const crossAxis = axis === 'block' ? 'inline' : 'block';
|
|
77
|
+
const flippedEdge = edge === 'start' ? 'end' : 'start';
|
|
78
|
+
const sameEdgeArea = `${axis}-${edge}`;
|
|
79
|
+
const oppositeEdgeArea = `${axis}-${flippedEdge}`;
|
|
80
|
+
const flipOnMainAxis = axis === 'block' ? 'flip-block' : 'flip-inline';
|
|
81
|
+
return [slideOnEdge({
|
|
82
|
+
align,
|
|
83
|
+
edgeKey: sameEdgeArea,
|
|
84
|
+
crossAxis,
|
|
85
|
+
isOppositeEdge: false
|
|
86
|
+
}), flipOnMainAxis, align !== 'center' ? flipDiagonally({
|
|
87
|
+
axis
|
|
88
|
+
}) : null, slideOnEdge({
|
|
89
|
+
align,
|
|
90
|
+
edgeKey: oppositeEdgeArea,
|
|
91
|
+
crossAxis,
|
|
92
|
+
isOppositeEdge: true
|
|
93
|
+
})].filter(Boolean).flat().join(', ');
|
|
94
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The space kept clear between a popover and the viewport edge. Matches legacy
|
|
3
|
+
* `@atlaskit/popper`'s `preventOverflow` padding of `5`.
|
|
4
|
+
*/
|
|
5
|
+
export const VIEWPORT_PADDING = '5px';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The upper bound for the default placement-axis floor while fitting. A capped
|
|
9
|
+
* popover never overflows, so it never flips; this floor makes the margin box
|
|
10
|
+
* overflow a too-small cell so `position-try-fallbacks` fires. A flip driver and
|
|
11
|
+
* nothing else, so the JavaScript fallback, which has no cell to overflow, never
|
|
12
|
+
* writes it. See `getPlacementAxisFloor`.
|
|
13
|
+
*
|
|
14
|
+
* 150px is the value the flip was measured with, not a design constant
|
|
15
|
+
* (`notes/plans/should-fit-viewport.md`). Deliberately not a `space.*` token:
|
|
16
|
+
* theming it would move the flip threshold, and so change which side a popover
|
|
17
|
+
* lands on.
|
|
18
|
+
*/
|
|
19
|
+
export const FALLBACK_MINIMUM_MAIN_AXIS_SIZE = '150px';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Which way the POPOVER's inline axis runs; `'vertical'` covers the `vertical-*`
|
|
23
|
+
* and `sideways-*` writing modes. Measured rather than assumed, because every
|
|
24
|
+
* logical property this module writes resolves against the popover's own writing
|
|
25
|
+
* mode, and so must the viewport unit paired with it.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The dynamic viewport unit for one of the popover's axes. Not `dvi` / `dvb`:
|
|
30
|
+
* css-values-4 defines those against the ROOT element's writing mode, while the
|
|
31
|
+
* `max-{axis}-size` they are written to follows the popover's, so a popover in a
|
|
32
|
+
* `vertical-rl` subtree of a horizontal document would be capped by the wrong
|
|
33
|
+
* dimension.
|
|
34
|
+
*/
|
|
35
|
+
function viewportUnit({
|
|
36
|
+
axis,
|
|
37
|
+
writingMode
|
|
38
|
+
}) {
|
|
39
|
+
const isHorizontalAxis = axis === 'inline' === (writingMode === 'horizontal');
|
|
40
|
+
return isHorizontalAxis ? 'dvw' : 'dvh';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* How one axis of an anchored popover is sized. Both axes take these four values
|
|
45
|
+
* under identical rules.
|
|
46
|
+
*
|
|
47
|
+
* - `'content'`: sized by its content. Named after `flex-basis: content`; NOT
|
|
48
|
+
* `'auto'`, whose CSS meaning for a positioned box is shrink-to-fit, the very
|
|
49
|
+
* behaviour rule 4 removes.
|
|
50
|
+
* - `'match-anchor'`: exactly the anchor's size on this axis.
|
|
51
|
+
* - `'min-anchor'`: at least the anchor's size on this axis, free to grow.
|
|
52
|
+
* - `'max-available'`: at most the space between the anchor and the viewport
|
|
53
|
+
* edge, content-sized below that. Capped by the `position-area` cell on the
|
|
54
|
+
* placement axis and by the viewport on the cross axis (a `span-*` cell is
|
|
55
|
+
* narrower than the viewport, so capping to it would wrap content that fits on
|
|
56
|
+
* screen).
|
|
57
|
+
*
|
|
58
|
+
* Four rules apply on top:
|
|
59
|
+
*
|
|
60
|
+
* 1. Fitting one axis fills a `'content'` other axis with `'max-available'` too.
|
|
61
|
+
* An explicit value is never overridden, so caps stay strictly per-axis.
|
|
62
|
+
* 2. Fitting either axis floors the placement axis, because flipping is a
|
|
63
|
+
* whole-popover outcome. See `getPlacementAxisFloor`.
|
|
64
|
+
* 3. The viewport cap is unconditional, so a non-fitting axis still cannot
|
|
65
|
+
* overhang the screen. `'max-available'` and `'content'` are therefore
|
|
66
|
+
* indistinguishable on the cross axis.
|
|
67
|
+
* 4. A non-anchor-relative inline axis gets `inline-size: max-content`. Under
|
|
68
|
+
* `position-area` the containing block is the cell, so a CSS `auto` inline
|
|
69
|
+
* size shrink-to-fits a too-narrow cell and WRAPS into it, never overflowing
|
|
70
|
+
* and so never sliding to the roomier side. The caps still beat it, because a
|
|
71
|
+
* max beats a definite size. Not needed on the block axis, which does not
|
|
72
|
+
* wrap, nor on the JavaScript fallback, whose containing block is the
|
|
73
|
+
* viewport. Consumers opt out with `shouldPreserveInlineSize`.
|
|
74
|
+
*
|
|
75
|
+
* See `notes/decisions/fit-available-space.md`.
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Narrowed rather than `string`, so a typo cannot reach `setStyle` as a
|
|
80
|
+
* silently-ignored property name.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The anchor's size along each axis, as a CSS length. `null` when nothing needs
|
|
85
|
+
* it, so the measurement is skipped.
|
|
86
|
+
*
|
|
87
|
+
* The CSS path uses the `self-` keywords, whose axes are the POPOVER's, so each
|
|
88
|
+
* value matches the axis of the property it is set on (`anchor-size(inline)` and
|
|
89
|
+
* `anchor-size(width)` are both silently wrong in a vertical writing mode). The
|
|
90
|
+
* JavaScript fallback uses a measured pixel length, because `anchor-size()` needs
|
|
91
|
+
* a `position-anchor` that path never sets. See
|
|
92
|
+
* `notes/decisions/width-from-anchor-floors.md`.
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
function getCrossAxis(axis) {
|
|
96
|
+
return axis === 'block' ? 'inline' : 'block';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The whole viewport, less the reserved padding on both sides. Rule 3's backstop.
|
|
101
|
+
*/
|
|
102
|
+
function viewportCap({
|
|
103
|
+
axis,
|
|
104
|
+
writingMode
|
|
105
|
+
}) {
|
|
106
|
+
return `calc(100${viewportUnit({
|
|
107
|
+
axis,
|
|
108
|
+
writingMode
|
|
109
|
+
})} - 2 * ${VIEWPORT_PADDING})`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The position-area cell cap, for the placement axis only. The host carries
|
|
114
|
+
* `position-area`, so per css-anchor-position-1 the cell is its containing block:
|
|
115
|
+
* `100%` is the anchor edge to the viewport edge, and it follows whichever side
|
|
116
|
+
* `position-try-fallbacks` settles on. Percentages do not subtract margins, so
|
|
117
|
+
* the gap is subtracted here.
|
|
118
|
+
*
|
|
119
|
+
* Meaningless on the JavaScript fallback, whose containing block is the viewport,
|
|
120
|
+
* so that path gets `viewportCap` on both axes.
|
|
121
|
+
*/
|
|
122
|
+
function cellCap({
|
|
123
|
+
gap
|
|
124
|
+
}) {
|
|
125
|
+
return `calc(100% - ${VIEWPORT_PADDING} - ${gap})`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The anchor term of the placement-axis floor, and the one place it is capped.
|
|
130
|
+
*
|
|
131
|
+
* `'match-anchor'` earns a floor only where it can flip: a definite size clamps to
|
|
132
|
+
* the cap and shrinks below the anchor instead of flipping.
|
|
133
|
+
*
|
|
134
|
+
* The `canFlip` clamp is deliberate both ways. With no flip in play an unclamped
|
|
135
|
+
* floor beats `max-{axis}-size` (CSS resolves `min` last), making the viewport
|
|
136
|
+
* backstop inert. With a flip in play, clamping would stop the floor exceeding
|
|
137
|
+
* the cell cap, which is what drives the flip.
|
|
138
|
+
*/
|
|
139
|
+
function getPlacementAxisAnchorFloor({
|
|
140
|
+
placementAxis,
|
|
141
|
+
placementAxisSize,
|
|
142
|
+
anchorValue,
|
|
143
|
+
canFlip,
|
|
144
|
+
writingMode
|
|
145
|
+
}) {
|
|
146
|
+
const hasAnchorFloor = placementAxisSize === 'min-anchor' || placementAxisSize === 'match-anchor' && canFlip;
|
|
147
|
+
if (!hasAnchorFloor || anchorValue === null) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
if (canFlip) {
|
|
151
|
+
return anchorValue;
|
|
152
|
+
}
|
|
153
|
+
return `min(${anchorValue}, ${viewportCap({
|
|
154
|
+
axis: placementAxis,
|
|
155
|
+
writingMode
|
|
156
|
+
})})`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The default flip floor, clamped so that SOME cell can always hold it.
|
|
161
|
+
*
|
|
162
|
+
* The two cells either side of the anchor add up to the viewport less the anchor,
|
|
163
|
+
* so the roomier one is at least half of that. A floor whose margin box fits
|
|
164
|
+
* inside that half is guaranteed a cell somewhere in the fallback chain.
|
|
165
|
+
* Unclamped, a short viewport with a centred anchor has NO cell that holds
|
|
166
|
+
* 150px: every try-fallback overflows, the browser reverts to the base position,
|
|
167
|
+
* and the floor pushes the popover past the viewport edge.
|
|
168
|
+
*
|
|
169
|
+
* This is NOT the `min(floor, cellCap)` that suppresses flipping: it is relative
|
|
170
|
+
* to the VIEWPORT, so a small base cell still overflows it. `max(0px, …)` states
|
|
171
|
+
* the lower bound for an anchor taller than the viewport.
|
|
172
|
+
*/
|
|
173
|
+
function getDefaultPlacementAxisFloor({
|
|
174
|
+
placementAxis,
|
|
175
|
+
anchorValue,
|
|
176
|
+
gap,
|
|
177
|
+
writingMode
|
|
178
|
+
}) {
|
|
179
|
+
if (anchorValue === null) {
|
|
180
|
+
return FALLBACK_MINIMUM_MAIN_AXIS_SIZE;
|
|
181
|
+
}
|
|
182
|
+
// The viewport term and the anchor term must measure the SAME axis; see
|
|
183
|
+
// `viewportUnit`.
|
|
184
|
+
const viewport = `100${viewportUnit({
|
|
185
|
+
axis: placementAxis,
|
|
186
|
+
writingMode
|
|
187
|
+
})}`;
|
|
188
|
+
const halfOfRemainingViewport = `calc((${viewport} - ${anchorValue}) / 2 - ${gap} - ${VIEWPORT_PADDING})`;
|
|
189
|
+
return `min(${FALLBACK_MINIMUM_MAIN_AXIS_SIZE}, max(0px, ${halfOfRemainingViewport}))`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The placement-axis floor: explicit minimums COMPOSE, the default does not.
|
|
194
|
+
*
|
|
195
|
+
* `placement.minSize` and the anchor term are both explicit requests, so they
|
|
196
|
+
* compose as `max()`. `FALLBACK_MINIMUM_MAIN_AXIS_SIZE` never composes: it only
|
|
197
|
+
* ever won by overriding a size the consumer asked for, and the anchor term drives
|
|
198
|
+
* the flip on its own. Knock-on, intended: `minSize: 0` no longer removes an
|
|
199
|
+
* anchor floor, because `max(0px, anchor)` IS the anchor.
|
|
200
|
+
*/
|
|
201
|
+
function getPlacementAxisFloor({
|
|
202
|
+
minSize,
|
|
203
|
+
placementAxis,
|
|
204
|
+
placementAxisSize,
|
|
205
|
+
anchorValue,
|
|
206
|
+
gap,
|
|
207
|
+
canFlip,
|
|
208
|
+
writingMode
|
|
209
|
+
}) {
|
|
210
|
+
const anchorFloor = getPlacementAxisAnchorFloor({
|
|
211
|
+
placementAxis,
|
|
212
|
+
placementAxisSize,
|
|
213
|
+
anchorValue,
|
|
214
|
+
canFlip,
|
|
215
|
+
writingMode
|
|
216
|
+
});
|
|
217
|
+
if (minSize !== undefined) {
|
|
218
|
+
return anchorFloor === undefined ? minSize : `max(${minSize}, ${anchorFloor})`;
|
|
219
|
+
}
|
|
220
|
+
if (anchorFloor !== undefined) {
|
|
221
|
+
return anchorFloor;
|
|
222
|
+
}
|
|
223
|
+
if (!canFlip) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
return getDefaultPlacementAxisFloor({
|
|
227
|
+
placementAxis,
|
|
228
|
+
anchorValue,
|
|
229
|
+
gap,
|
|
230
|
+
writingMode
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The whole size recipe for an anchored popover, in one place. Pure, so the truth
|
|
236
|
+
* table is unit-testable without a browser, and every size property on the
|
|
237
|
+
* popover host has exactly one writer.
|
|
238
|
+
*
|
|
239
|
+
* While anything is fitting on the CSS path, the placement-axis floor is never
|
|
240
|
+
* clamped to the CELL cap. Its whole job is to exceed it: overflow detection
|
|
241
|
+
* reads the margin box AFTER the clamp (css-anchor-position-1 §6.5), so
|
|
242
|
+
* `min(floor, cap)` suppresses `position-try-fallbacks` entirely. The cross-axis
|
|
243
|
+
* floor is always clamped, because there it is a size contract rather than a flip
|
|
244
|
+
* driver.
|
|
245
|
+
*
|
|
246
|
+
* See `notes/decisions/fit-available-space.md` for the full recipe and the
|
|
247
|
+
* measured evidence.
|
|
248
|
+
*/
|
|
249
|
+
export function getAnchoredPopoverSizeDeclarations({
|
|
250
|
+
placement,
|
|
251
|
+
inlineSize,
|
|
252
|
+
blockSize,
|
|
253
|
+
isUsingCssAnchorPositioning,
|
|
254
|
+
anchorSize,
|
|
255
|
+
writingMode,
|
|
256
|
+
shouldPreserveInlineSize = false
|
|
257
|
+
}) {
|
|
258
|
+
const placementAxis = placement.axis;
|
|
259
|
+
const crossAxis = getCrossAxis(placementAxis);
|
|
260
|
+
|
|
261
|
+
// Rule 1. Feeds the per-axis CAPS only.
|
|
262
|
+
const size = {
|
|
263
|
+
inline: inlineSize === 'content' && blockSize === 'max-available' ? 'max-available' : inlineSize,
|
|
264
|
+
block: blockSize === 'content' && inlineSize === 'max-available' ? 'max-available' : blockSize
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Rule 2 reads the requested values instead, un-mirrored: flipping is a
|
|
268
|
+
// whole-popover outcome, so a fit request on either axis turns the floor on.
|
|
269
|
+
const isFitting = inlineSize === 'max-available' || blockSize === 'max-available';
|
|
270
|
+
|
|
271
|
+
// ...but only where the floor is what causes the flip. On the JavaScript
|
|
272
|
+
// fallback a floor only inflates the measured size the side is picked from, so
|
|
273
|
+
// it would flip a popover that fits.
|
|
274
|
+
const canFlip = isFitting && isUsingCssAnchorPositioning;
|
|
275
|
+
const declarations = [
|
|
276
|
+
// Rule 3: both caps are always written. Only the placement axis can take the
|
|
277
|
+
// cell cap.
|
|
278
|
+
{
|
|
279
|
+
property: `max-${placementAxis}-size`,
|
|
280
|
+
value: size[placementAxis] === 'max-available' && isUsingCssAnchorPositioning ? cellCap({
|
|
281
|
+
gap: placement.offset.gap
|
|
282
|
+
}) : viewportCap({
|
|
283
|
+
axis: placementAxis,
|
|
284
|
+
writingMode
|
|
285
|
+
})
|
|
286
|
+
}, {
|
|
287
|
+
property: `max-${crossAxis}-size`,
|
|
288
|
+
value: viewportCap({
|
|
289
|
+
axis: crossAxis,
|
|
290
|
+
writingMode
|
|
291
|
+
})
|
|
292
|
+
}];
|
|
293
|
+
|
|
294
|
+
// Rule 4. Only for a non-anchor-relative inline axis, so `'match-anchor'` below
|
|
295
|
+
// stays the one writer of `inline-size` when it applies. CSS path only: the
|
|
296
|
+
// fallback's containing block is the viewport, where shrink-to-fit already IS
|
|
297
|
+
// the natural width for anything that fits on screen.
|
|
298
|
+
if (isUsingCssAnchorPositioning && !shouldPreserveInlineSize && (size.inline === 'content' || size.inline === 'max-available')) {
|
|
299
|
+
declarations.push({
|
|
300
|
+
property: 'inline-size',
|
|
301
|
+
value: 'max-content'
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// A max beats a definite size, so the caps above still win over this.
|
|
306
|
+
for (const axis of [placementAxis, crossAxis]) {
|
|
307
|
+
const anchorValue = anchorSize[axis];
|
|
308
|
+
if (size[axis] === 'match-anchor' && anchorValue !== null) {
|
|
309
|
+
declarations.push({
|
|
310
|
+
property: `${axis}-size`,
|
|
311
|
+
value: anchorValue
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const placementAxisFloor = getPlacementAxisFloor({
|
|
316
|
+
minSize: placement.minSize,
|
|
317
|
+
placementAxis,
|
|
318
|
+
placementAxisSize: size[placementAxis],
|
|
319
|
+
anchorValue: anchorSize[placementAxis],
|
|
320
|
+
gap: placement.offset.gap,
|
|
321
|
+
canFlip,
|
|
322
|
+
writingMode
|
|
323
|
+
});
|
|
324
|
+
if (placementAxisFloor !== undefined) {
|
|
325
|
+
declarations.push({
|
|
326
|
+
property: `min-${placementAxis}-size`,
|
|
327
|
+
value: placementAxisFloor
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// A size contract, not a flip driver, so it is clamped to the cap.
|
|
332
|
+
const crossAxisAnchorValue = anchorSize[crossAxis];
|
|
333
|
+
if (size[crossAxis] === 'min-anchor' && crossAxisAnchorValue !== null) {
|
|
334
|
+
declarations.push({
|
|
335
|
+
property: `min-${crossAxis}-size`,
|
|
336
|
+
value: `min(${crossAxisAnchorValue}, ${viewportCap({
|
|
337
|
+
axis: crossAxis,
|
|
338
|
+
writingMode
|
|
339
|
+
})})`
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return declarations;
|
|
343
|
+
}
|
|
@@ -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',
|
|
@@ -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,
|