@atlaskit/top-layer 0.12.2 → 0.13.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 +16 -0
- package/dist/cjs/entry-points/focus.js +6 -4
- package/dist/cjs/entry-points/use-anchor-position-at-point.js +12 -0
- package/dist/cjs/focus/get-first-focusable.js +19 -0
- package/dist/cjs/focus/get-focusables.js +30 -0
- package/dist/cjs/focus/get-last-focusable.js +19 -0
- package/dist/cjs/focus/get-next-focusable.js +43 -0
- package/dist/cjs/focus/is-in-same-layer.js +23 -0
- package/dist/cjs/focus/is-nested-layer-focused.js +32 -0
- package/dist/cjs/focus/selectors.js +19 -0
- package/dist/cjs/focus/types.js +1 -0
- package/dist/cjs/internal/role-to-aria-has-popup.js +37 -0
- package/dist/cjs/internal/role-types.js +1 -97
- package/dist/cjs/internal/should-focus-into-popover.js +19 -0
- package/dist/cjs/internal/use-anchor-position-at-point.js +127 -0
- package/dist/cjs/internal/use-anchor-position.js +7 -3
- package/dist/cjs/internal/use-focus-wrap.js +8 -5
- package/dist/cjs/internal/use-initial-focus.js +4 -4
- package/dist/cjs/placement-map/index.js +2 -2
- package/dist/cjs/placement-map/legacy-placements.js +2 -101
- package/dist/cjs/placement-map/placement-mapping.js +103 -0
- package/dist/cjs/popup/popup-content.js +4 -3
- package/dist/cjs/use-arrow-navigation/use-arrow-navigation.js +7 -5
- package/dist/es2019/entry-points/focus.js +3 -1
- package/dist/es2019/entry-points/use-anchor-position-at-point.js +1 -0
- package/dist/es2019/focus/get-first-focusable.js +14 -0
- package/dist/es2019/focus/get-focusables.js +19 -0
- package/dist/es2019/focus/get-last-focusable.js +14 -0
- package/dist/es2019/focus/get-next-focusable.js +38 -0
- package/dist/es2019/focus/is-in-same-layer.js +18 -0
- package/dist/es2019/focus/is-nested-layer-focused.js +28 -0
- package/dist/es2019/focus/selectors.js +11 -0
- package/dist/es2019/focus/types.js +0 -0
- package/dist/es2019/internal/role-to-aria-has-popup.js +32 -0
- package/dist/es2019/internal/role-types.js +0 -92
- package/dist/es2019/internal/should-focus-into-popover.js +14 -0
- package/dist/es2019/internal/use-anchor-position-at-point.js +122 -0
- package/dist/es2019/internal/use-anchor-position.js +6 -3
- package/dist/es2019/internal/use-focus-wrap.js +4 -1
- package/dist/es2019/internal/use-initial-focus.js +1 -1
- package/dist/es2019/placement-map/index.js +1 -1
- package/dist/es2019/placement-map/legacy-placements.js +1 -100
- package/dist/es2019/placement-map/placement-mapping.js +97 -0
- package/dist/es2019/popup/popup-content.js +2 -1
- package/dist/es2019/use-arrow-navigation/use-arrow-navigation.js +3 -1
- package/dist/esm/entry-points/focus.js +3 -1
- package/dist/esm/entry-points/use-anchor-position-at-point.js +1 -0
- package/dist/esm/focus/get-first-focusable.js +13 -0
- package/dist/esm/focus/get-focusables.js +24 -0
- package/dist/esm/focus/get-last-focusable.js +13 -0
- package/dist/esm/focus/get-next-focusable.js +37 -0
- package/dist/esm/focus/is-in-same-layer.js +17 -0
- package/dist/esm/focus/is-nested-layer-focused.js +27 -0
- package/dist/esm/focus/selectors.js +13 -0
- package/dist/esm/focus/types.js +0 -0
- package/dist/esm/internal/role-to-aria-has-popup.js +31 -0
- package/dist/esm/internal/role-types.js +0 -90
- package/dist/esm/internal/should-focus-into-popover.js +13 -0
- package/dist/esm/internal/use-anchor-position-at-point.js +122 -0
- package/dist/esm/internal/use-anchor-position.js +7 -3
- package/dist/esm/internal/use-focus-wrap.js +4 -1
- package/dist/esm/internal/use-initial-focus.js +1 -1
- package/dist/esm/placement-map/index.js +1 -1
- package/dist/esm/placement-map/legacy-placements.js +1 -100
- package/dist/esm/placement-map/placement-mapping.js +97 -0
- package/dist/esm/popup/popup-content.js +2 -1
- package/dist/esm/use-arrow-navigation/use-arrow-navigation.js +3 -1
- package/dist/types/entry-points/focus.d.ts +4 -2
- package/dist/types/entry-points/use-anchor-position-at-point.d.ts +1 -0
- package/dist/types/focus/get-first-focusable.d.ts +8 -0
- package/dist/types/focus/get-focusables.d.ts +13 -0
- package/dist/types/focus/get-last-focusable.d.ts +8 -0
- package/dist/types/focus/get-next-focusable.d.ts +13 -0
- package/dist/types/focus/is-in-same-layer.d.ts +8 -0
- package/dist/types/focus/is-nested-layer-focused.d.ts +8 -0
- package/dist/types/focus/selectors.d.ts +4 -0
- package/dist/types/focus/types.d.ts +5 -0
- package/dist/types/internal/role-to-aria-has-popup.d.ts +13 -0
- package/dist/types/internal/role-types.d.ts +0 -19
- package/dist/types/internal/should-focus-into-popover.d.ts +8 -0
- package/dist/types/internal/use-anchor-position-at-point.d.ts +62 -0
- package/dist/types/internal/use-anchor-position.d.ts +6 -1
- package/dist/types/placement-map/index.d.ts +1 -1
- package/dist/types/placement-map/legacy-placements.d.ts +0 -12
- package/dist/types/placement-map/placement-mapping.d.ts +13 -0
- package/dist/types/use-arrow-navigation/use-arrow-navigation.d.ts +1 -1
- package/dist/types-ts4.5/entry-points/focus.d.ts +4 -2
- package/dist/types-ts4.5/entry-points/use-anchor-position-at-point.d.ts +1 -0
- package/dist/types-ts4.5/focus/get-first-focusable.d.ts +8 -0
- package/dist/types-ts4.5/focus/get-focusables.d.ts +13 -0
- package/dist/types-ts4.5/focus/get-last-focusable.d.ts +8 -0
- package/dist/types-ts4.5/focus/get-next-focusable.d.ts +13 -0
- package/dist/types-ts4.5/focus/is-in-same-layer.d.ts +8 -0
- package/dist/types-ts4.5/focus/is-nested-layer-focused.d.ts +8 -0
- package/dist/types-ts4.5/focus/selectors.d.ts +4 -0
- package/dist/types-ts4.5/focus/types.d.ts +5 -0
- package/dist/types-ts4.5/internal/role-to-aria-has-popup.d.ts +13 -0
- package/dist/types-ts4.5/internal/role-types.d.ts +0 -19
- package/dist/types-ts4.5/internal/should-focus-into-popover.d.ts +8 -0
- package/dist/types-ts4.5/internal/use-anchor-position-at-point.d.ts +62 -0
- package/dist/types-ts4.5/internal/use-anchor-position.d.ts +6 -1
- package/dist/types-ts4.5/placement-map/index.d.ts +1 -1
- package/dist/types-ts4.5/placement-map/legacy-placements.d.ts +0 -12
- package/dist/types-ts4.5/placement-map/placement-mapping.d.ts +13 -0
- package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +1 -1
- package/package.json +1 -1
- package/use-anchor-position-at-point/package.json +17 -0
- package/dist/cjs/focus/focus.js +0 -169
- package/dist/cjs/focus/index.js +0 -24
- package/dist/es2019/focus/focus.js +0 -158
- package/dist/es2019/focus/index.js +0 -1
- package/dist/esm/focus/focus.js +0 -160
- package/dist/esm/focus/index.js +0 -1
- package/dist/types/focus/focus.d.ts +0 -50
- package/dist/types/focus/index.d.ts +0 -2
- package/dist/types-ts4.5/focus/focus.d.ts +0 -50
- package/dist/types-ts4.5/focus/index.d.ts +0 -2
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
2
|
+
import { useAnchorPosition } from './use-anchor-position';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A point in viewport coordinates (CSS pixels) at which the synthetic
|
|
6
|
+
* anchor should be placed.
|
|
7
|
+
*
|
|
8
|
+
* Axis-named (`x`/`y`) rather than edge-named (`left`/`top`) so the
|
|
9
|
+
* shape is unambiguous in RTL writing modes — there is no ambiguity
|
|
10
|
+
* about whether a coordinate is logical (`inline-start`/`block-start`)
|
|
11
|
+
* or physical (`left`/`top`). Viewport coordinates are always
|
|
12
|
+
* physical, so we name them with the physical axis labels.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Styles the synthetic anchor as an invisible, zero-size point.
|
|
17
|
+
* Dynamic `inset-*` overrides are written by the positioning effect.
|
|
18
|
+
*
|
|
19
|
+
* Uses `Object.assign` rather than Compiled CSS (element is outside the React tree)
|
|
20
|
+
* or the internal `setStyle` helper (we own this element entirely — it is created
|
|
21
|
+
* and removed by this hook — so the snapshot/restore semantics `setStyle`
|
|
22
|
+
* provides for caller-owned elements would be dead work here).
|
|
23
|
+
*/
|
|
24
|
+
function applyStaticAnchorStyles(el) {
|
|
25
|
+
Object.assign(el.style, {
|
|
26
|
+
position: 'fixed',
|
|
27
|
+
// `top`/`left` rather than `inset-block-start`/`inset-inline-start`
|
|
28
|
+
// for consistency with the dynamic positioning effect below, which
|
|
29
|
+
// uses physical properties (see comment there for the rationale).
|
|
30
|
+
top: '0',
|
|
31
|
+
left: '0',
|
|
32
|
+
// Zero size — placement decides which side the popover sits on.
|
|
33
|
+
width: '0',
|
|
34
|
+
height: '0',
|
|
35
|
+
// Prevent the overlay from intercepting pointer events.
|
|
36
|
+
pointerEvents: 'none'
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// `useAnchorPosition` requires a real DOM element. When the caller only
|
|
41
|
+
// has a viewport coordinate (e.g. cursor at hover time), this hook
|
|
42
|
+
// manages a hidden `<div>` at that point and delegates the rest.
|
|
43
|
+
//
|
|
44
|
+
// The `<div>` is appended directly to `document.body` to escape any
|
|
45
|
+
// transformed ancestor, which would otherwise break `position: fixed`
|
|
46
|
+
// viewport-relative resolution.
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Positions a popover at a consumer-supplied viewport coordinate using a
|
|
50
|
+
* hidden synthetic anchor.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* function PointPositionedPopover({ popoverRef, placement, mousePos }) {
|
|
55
|
+
* useAnchorPositionAtPoint({
|
|
56
|
+
* popoverRef,
|
|
57
|
+
* placement,
|
|
58
|
+
* isEnabled: Boolean(mousePos),
|
|
59
|
+
* getPoint: () => {
|
|
60
|
+
* if (!mousePos) return null;
|
|
61
|
+
* return { x: mousePos.clientX, y: mousePos.clientY };
|
|
62
|
+
* },
|
|
63
|
+
* });
|
|
64
|
+
* return <Popover ref={popoverRef}>...</Popover>;
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function useAnchorPositionAtPoint({
|
|
69
|
+
popoverRef,
|
|
70
|
+
placement,
|
|
71
|
+
getPoint,
|
|
72
|
+
isEnabled = true
|
|
73
|
+
}) {
|
|
74
|
+
const syntheticAnchorRef = useRef(null);
|
|
75
|
+
|
|
76
|
+
// `getPoint` is kept in a ref so the layout effect can call the
|
|
77
|
+
// latest version without listing it as a dep — that would re-fire
|
|
78
|
+
// on every render and re-latch the point.
|
|
79
|
+
const getPointRef = useRef(getPoint);
|
|
80
|
+
getPointRef.current = getPoint;
|
|
81
|
+
|
|
82
|
+
// Lazy element creation: the `<div>` is only created when both
|
|
83
|
+
// `isEnabled` is true AND `getPoint()` returns a non-null point.
|
|
84
|
+
// `useLayoutEffect` ensures the element exists before the inner
|
|
85
|
+
// `useAnchorPosition` (also a layout effect) reads the ref.
|
|
86
|
+
useLayoutEffect(() => {
|
|
87
|
+
if (!isEnabled) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const point = getPointRef.current();
|
|
91
|
+
if (!point) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const anchor = document.createElement('div');
|
|
95
|
+
anchor.setAttribute('aria-hidden', 'true');
|
|
96
|
+
applyStaticAnchorStyles(anchor);
|
|
97
|
+
// `point.x`/`y` are physical viewport coordinates (from
|
|
98
|
+
// `MouseEvent.clientX/Y`), so map directly onto physical
|
|
99
|
+
// `left`/`top`. This is RTL-safe: `position: fixed` + physical
|
|
100
|
+
// insets is direction-independent. Using logical
|
|
101
|
+
// `inset-inline-start` would need `viewportWidth - point.x` in
|
|
102
|
+
// RTL to land at the same physical pixel.
|
|
103
|
+
anchor.style.top = `${point.y}px`;
|
|
104
|
+
anchor.style.left = `${point.x}px`;
|
|
105
|
+
document.body.appendChild(anchor);
|
|
106
|
+
syntheticAnchorRef.current = anchor;
|
|
107
|
+
return function cleanup() {
|
|
108
|
+
anchor.remove();
|
|
109
|
+
syntheticAnchorRef.current = null;
|
|
110
|
+
};
|
|
111
|
+
}, [isEnabled]);
|
|
112
|
+
|
|
113
|
+
// Mirror `isEnabled` so both hooks stay in sync — when disabled,
|
|
114
|
+
// neither this hook nor the inner one touches the popover, leaving
|
|
115
|
+
// any other positioning the consumer has set up free to take effect.
|
|
116
|
+
useAnchorPosition({
|
|
117
|
+
anchorRef: syntheticAnchorRef,
|
|
118
|
+
popoverRef,
|
|
119
|
+
placement,
|
|
120
|
+
isEnabled
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -197,18 +197,21 @@ function useStablePlacement(placement) {
|
|
|
197
197
|
* comparison is used internally so re-runs are skipped when the resolved
|
|
198
198
|
* placement shape is unchanged.
|
|
199
199
|
*/
|
|
200
|
-
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
201
200
|
export function useAnchorPosition({
|
|
202
201
|
anchorRef,
|
|
203
202
|
popoverRef,
|
|
204
203
|
placement = {},
|
|
205
|
-
forceFallbackPositioning = false
|
|
204
|
+
forceFallbackPositioning = false,
|
|
205
|
+
isEnabled = true
|
|
206
206
|
}) {
|
|
207
207
|
const id = useId();
|
|
208
208
|
// Stabilize `placement` object so the same object literal
|
|
209
209
|
// value returns an object with the same reference
|
|
210
210
|
const stablePlacement = useStablePlacement(placement);
|
|
211
211
|
useLayoutEffect(() => {
|
|
212
|
+
if (!isEnabled) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
212
215
|
const trigger = anchorRef.current;
|
|
213
216
|
const popover = popoverRef.current;
|
|
214
217
|
if (!trigger || !popover) {
|
|
@@ -456,5 +459,5 @@ export function useAnchorPosition({
|
|
|
456
459
|
popover.style.removeProperty('opacity');
|
|
457
460
|
});
|
|
458
461
|
return undoPositioning;
|
|
459
|
-
}, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, id]);
|
|
462
|
+
}, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, isEnabled, id]);
|
|
460
463
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { bind } from 'bind-event-listener';
|
|
3
|
-
import { getFirstFocusable
|
|
3
|
+
import { getFirstFocusable } from '../focus/get-first-focusable';
|
|
4
|
+
import { getLastFocusable } from '../focus/get-last-focusable';
|
|
5
|
+
import { getNextFocusable } from '../focus/get-next-focusable';
|
|
6
|
+
import { isNestedLayerFocused } from '../focus/is-nested-layer-focused';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Roles that require focus wrapping per WAI-ARIA APG.
|
|
@@ -5,103 +5,4 @@
|
|
|
5
5
|
* to enumerate every placement (e.g. for a settings dropdown). New code
|
|
6
6
|
* should use the object-based `TPlacementOptions` directly.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
export const LEGACY_PLACEMENTS = ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-center', 'top-end', 'bottom', 'bottom-start', 'bottom-center', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'];
|
|
10
|
-
/**
|
|
11
|
-
* The full mapping record. Internal - use `fromLegacyPlacement` from the
|
|
12
|
-
* public entry-point so the offset-tuple translation is applied consistently.
|
|
13
|
-
*
|
|
14
|
-
* Note: `auto`, `auto-start`, `auto-end` map to `block-end` placements. This
|
|
15
|
-
* is a reduction in expressiveness vs Popper.js (which picked the best
|
|
16
|
-
* placement at runtime). CSS `position-try-fallbacks` flips at the edge but
|
|
17
|
-
* the initial placement is fixed. Migration consumers who relied on Popper's
|
|
18
|
-
* dynamic auto-flip should pick an explicit placement instead.
|
|
19
|
-
*/
|
|
20
|
-
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
21
|
-
export const placementMapping = {
|
|
22
|
-
top: {
|
|
23
|
-
axis: 'block',
|
|
24
|
-
edge: 'start',
|
|
25
|
-
align: 'center'
|
|
26
|
-
},
|
|
27
|
-
'top-start': {
|
|
28
|
-
axis: 'block',
|
|
29
|
-
edge: 'start',
|
|
30
|
-
align: 'start'
|
|
31
|
-
},
|
|
32
|
-
'top-center': {
|
|
33
|
-
axis: 'block',
|
|
34
|
-
edge: 'start',
|
|
35
|
-
align: 'center'
|
|
36
|
-
},
|
|
37
|
-
'top-end': {
|
|
38
|
-
axis: 'block',
|
|
39
|
-
edge: 'start',
|
|
40
|
-
align: 'end'
|
|
41
|
-
},
|
|
42
|
-
bottom: {
|
|
43
|
-
axis: 'block',
|
|
44
|
-
edge: 'end',
|
|
45
|
-
align: 'center'
|
|
46
|
-
},
|
|
47
|
-
'bottom-start': {
|
|
48
|
-
axis: 'block',
|
|
49
|
-
edge: 'end',
|
|
50
|
-
align: 'start'
|
|
51
|
-
},
|
|
52
|
-
'bottom-center': {
|
|
53
|
-
axis: 'block',
|
|
54
|
-
edge: 'end',
|
|
55
|
-
align: 'center'
|
|
56
|
-
},
|
|
57
|
-
'bottom-end': {
|
|
58
|
-
axis: 'block',
|
|
59
|
-
edge: 'end',
|
|
60
|
-
align: 'end'
|
|
61
|
-
},
|
|
62
|
-
right: {
|
|
63
|
-
axis: 'inline',
|
|
64
|
-
edge: 'end',
|
|
65
|
-
align: 'center'
|
|
66
|
-
},
|
|
67
|
-
'right-start': {
|
|
68
|
-
axis: 'inline',
|
|
69
|
-
edge: 'end',
|
|
70
|
-
align: 'start'
|
|
71
|
-
},
|
|
72
|
-
'right-end': {
|
|
73
|
-
axis: 'inline',
|
|
74
|
-
edge: 'end',
|
|
75
|
-
align: 'end'
|
|
76
|
-
},
|
|
77
|
-
left: {
|
|
78
|
-
axis: 'inline',
|
|
79
|
-
edge: 'start',
|
|
80
|
-
align: 'center'
|
|
81
|
-
},
|
|
82
|
-
'left-start': {
|
|
83
|
-
axis: 'inline',
|
|
84
|
-
edge: 'start',
|
|
85
|
-
align: 'start'
|
|
86
|
-
},
|
|
87
|
-
'left-end': {
|
|
88
|
-
axis: 'inline',
|
|
89
|
-
edge: 'start',
|
|
90
|
-
align: 'end'
|
|
91
|
-
},
|
|
92
|
-
auto: {
|
|
93
|
-
axis: 'block',
|
|
94
|
-
edge: 'end',
|
|
95
|
-
align: 'center'
|
|
96
|
-
},
|
|
97
|
-
'auto-start': {
|
|
98
|
-
axis: 'block',
|
|
99
|
-
edge: 'end',
|
|
100
|
-
align: 'start'
|
|
101
|
-
},
|
|
102
|
-
'auto-end': {
|
|
103
|
-
axis: 'block',
|
|
104
|
-
edge: 'end',
|
|
105
|
-
align: 'end'
|
|
106
|
-
}
|
|
107
|
-
};
|
|
8
|
+
export const LEGACY_PLACEMENTS = ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-center', 'top-end', 'bottom', 'bottom-start', 'bottom-center', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'];
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The full mapping record. Internal - use `fromLegacyPlacement` from the
|
|
3
|
+
* public entry-point so the offset-tuple translation is applied consistently.
|
|
4
|
+
*
|
|
5
|
+
* Note: `auto`, `auto-start`, `auto-end` map to `block-end` placements. This
|
|
6
|
+
* is a reduction in expressiveness vs Popper.js (which picked the best
|
|
7
|
+
* placement at runtime). CSS `position-try-fallbacks` flips at the edge but
|
|
8
|
+
* the initial placement is fixed. Migration consumers who relied on Popper's
|
|
9
|
+
* dynamic auto-flip should pick an explicit placement instead.
|
|
10
|
+
*/
|
|
11
|
+
export const placementMapping = {
|
|
12
|
+
top: {
|
|
13
|
+
axis: 'block',
|
|
14
|
+
edge: 'start',
|
|
15
|
+
align: 'center'
|
|
16
|
+
},
|
|
17
|
+
'top-start': {
|
|
18
|
+
axis: 'block',
|
|
19
|
+
edge: 'start',
|
|
20
|
+
align: 'start'
|
|
21
|
+
},
|
|
22
|
+
'top-center': {
|
|
23
|
+
axis: 'block',
|
|
24
|
+
edge: 'start',
|
|
25
|
+
align: 'center'
|
|
26
|
+
},
|
|
27
|
+
'top-end': {
|
|
28
|
+
axis: 'block',
|
|
29
|
+
edge: 'start',
|
|
30
|
+
align: 'end'
|
|
31
|
+
},
|
|
32
|
+
bottom: {
|
|
33
|
+
axis: 'block',
|
|
34
|
+
edge: 'end',
|
|
35
|
+
align: 'center'
|
|
36
|
+
},
|
|
37
|
+
'bottom-start': {
|
|
38
|
+
axis: 'block',
|
|
39
|
+
edge: 'end',
|
|
40
|
+
align: 'start'
|
|
41
|
+
},
|
|
42
|
+
'bottom-center': {
|
|
43
|
+
axis: 'block',
|
|
44
|
+
edge: 'end',
|
|
45
|
+
align: 'center'
|
|
46
|
+
},
|
|
47
|
+
'bottom-end': {
|
|
48
|
+
axis: 'block',
|
|
49
|
+
edge: 'end',
|
|
50
|
+
align: 'end'
|
|
51
|
+
},
|
|
52
|
+
right: {
|
|
53
|
+
axis: 'inline',
|
|
54
|
+
edge: 'end',
|
|
55
|
+
align: 'center'
|
|
56
|
+
},
|
|
57
|
+
'right-start': {
|
|
58
|
+
axis: 'inline',
|
|
59
|
+
edge: 'end',
|
|
60
|
+
align: 'start'
|
|
61
|
+
},
|
|
62
|
+
'right-end': {
|
|
63
|
+
axis: 'inline',
|
|
64
|
+
edge: 'end',
|
|
65
|
+
align: 'end'
|
|
66
|
+
},
|
|
67
|
+
left: {
|
|
68
|
+
axis: 'inline',
|
|
69
|
+
edge: 'start',
|
|
70
|
+
align: 'center'
|
|
71
|
+
},
|
|
72
|
+
'left-start': {
|
|
73
|
+
axis: 'inline',
|
|
74
|
+
edge: 'start',
|
|
75
|
+
align: 'start'
|
|
76
|
+
},
|
|
77
|
+
'left-end': {
|
|
78
|
+
axis: 'inline',
|
|
79
|
+
edge: 'start',
|
|
80
|
+
align: 'end'
|
|
81
|
+
},
|
|
82
|
+
auto: {
|
|
83
|
+
axis: 'block',
|
|
84
|
+
edge: 'end',
|
|
85
|
+
align: 'center'
|
|
86
|
+
},
|
|
87
|
+
'auto-start': {
|
|
88
|
+
axis: 'block',
|
|
89
|
+
edge: 'end',
|
|
90
|
+
align: 'start'
|
|
91
|
+
},
|
|
92
|
+
'auto-end': {
|
|
93
|
+
axis: 'block',
|
|
94
|
+
edge: 'end',
|
|
95
|
+
align: 'end'
|
|
96
|
+
}
|
|
97
|
+
};
|
|
@@ -4,7 +4,8 @@ import { ax, ix } from "@compiled/react/runtime";
|
|
|
4
4
|
import React, { forwardRef, useCallback, useLayoutEffect, useRef } from 'react';
|
|
5
5
|
import mergeRefs from '@atlaskit/ds-lib/merge-refs';
|
|
6
6
|
import { prefersReducedMotion } from '../internal/reduced-motion';
|
|
7
|
-
import { roleToAriaHasPopup
|
|
7
|
+
import { roleToAriaHasPopup } from '../internal/role-to-aria-has-popup';
|
|
8
|
+
import { shouldFocusIntoPopover } from '../internal/should-focus-into-popover';
|
|
8
9
|
import { useAnchorPosition } from '../internal/use-anchor-position';
|
|
9
10
|
import { useWidthFromAnchor } from '../internal/use-width-from-anchor';
|
|
10
11
|
import { Popover } from '../popover/popover';
|
|
@@ -2,7 +2,9 @@ import { useCallback, useEffect } from 'react';
|
|
|
2
2
|
import { bind } from 'bind-event-listener';
|
|
3
3
|
import { getDocument } from '@atlaskit/browser-apis';
|
|
4
4
|
import { KEY_DOWN, KEY_END, KEY_HOME, KEY_TAB, KEY_UP } from '@atlaskit/ds-lib/keycodes';
|
|
5
|
-
import { getFirstFocusable
|
|
5
|
+
import { getFirstFocusable } from '../focus/get-first-focusable';
|
|
6
|
+
import { getLastFocusable } from '../focus/get-last-focusable';
|
|
7
|
+
import { getNextFocusable } from '../focus/get-next-focusable';
|
|
6
8
|
const menuScope = '[role="menu"]';
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
export { getFirstFocusable
|
|
1
|
+
export { getFirstFocusable } from '../focus/get-first-focusable';
|
|
2
|
+
export { getLastFocusable } from '../focus/get-last-focusable';
|
|
3
|
+
export { getNextFocusable } from '../focus/get-next-focusable';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useAnchorPositionAtPoint } from '../internal/use-anchor-position-at-point';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getFocusables } from './get-focusables';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the first focusable element within the container.
|
|
4
|
+
*/
|
|
5
|
+
export function getFirstFocusable(_ref) {
|
|
6
|
+
var container = _ref.container,
|
|
7
|
+
filter = _ref.filter;
|
|
8
|
+
var items = getFocusables({
|
|
9
|
+
container: container,
|
|
10
|
+
filter: filter
|
|
11
|
+
});
|
|
12
|
+
return items.length > 0 ? items[0] : null;
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isInSameLayer } from './is-in-same-layer';
|
|
2
|
+
import { selectors } from './selectors';
|
|
3
|
+
/**
|
|
4
|
+
* Returns all focusable HTMLElements within the container, optionally filtered.
|
|
5
|
+
*
|
|
6
|
+
* Focusables that belong to a nested top-layer scope (a `[popover]` or
|
|
7
|
+
* `<dialog>` descendant of the container) are excluded - those elements
|
|
8
|
+
* are owned by the inner layer's focus management. The container itself
|
|
9
|
+
* is allowed to be a popover/dialog; only nested ones are filtered.
|
|
10
|
+
*/
|
|
11
|
+
export function getFocusables(_ref) {
|
|
12
|
+
var container = _ref.container,
|
|
13
|
+
filter = _ref.filter;
|
|
14
|
+
return Array.from(container.querySelectorAll(selectors.focusable)).filter(function (element) {
|
|
15
|
+
return element instanceof HTMLElement;
|
|
16
|
+
}).filter(function (element) {
|
|
17
|
+
return isInSameLayer({
|
|
18
|
+
element: element,
|
|
19
|
+
container: container
|
|
20
|
+
});
|
|
21
|
+
}).filter(function (element) {
|
|
22
|
+
return !filter || filter(element, container);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getFocusables } from './get-focusables';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the last focusable element within the container.
|
|
4
|
+
*/
|
|
5
|
+
export function getLastFocusable(_ref) {
|
|
6
|
+
var container = _ref.container,
|
|
7
|
+
filter = _ref.filter;
|
|
8
|
+
var items = getFocusables({
|
|
9
|
+
container: container,
|
|
10
|
+
filter: filter
|
|
11
|
+
});
|
|
12
|
+
return items.length > 0 ? items[items.length - 1] : null;
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the next focusable element in the given direction relative to the
|
|
3
|
+
* currently focused element within the container. Wraps around at both ends.
|
|
4
|
+
*
|
|
5
|
+
* Returns `null` if there is no focused element within the container or if
|
|
6
|
+
* the focused element is not in the list of focusable elements.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getFocusables } from './get-focusables';
|
|
10
|
+
import { selectors } from './selectors';
|
|
11
|
+
export function getNextFocusable(_ref) {
|
|
12
|
+
var _all$nextIndex;
|
|
13
|
+
var container = _ref.container,
|
|
14
|
+
direction = _ref.direction,
|
|
15
|
+
filter = _ref.filter;
|
|
16
|
+
var all = getFocusables({
|
|
17
|
+
container: container,
|
|
18
|
+
filter: filter
|
|
19
|
+
});
|
|
20
|
+
var current = container.querySelector(selectors.focused);
|
|
21
|
+
if (!(current instanceof HTMLElement)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
var index = all.indexOf(current);
|
|
25
|
+
if (index === -1) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
var nextIndex = function () {
|
|
29
|
+
if (direction === 'forwards') {
|
|
30
|
+
return (index + 1) % all.length;
|
|
31
|
+
}
|
|
32
|
+
var proposed = index - 1;
|
|
33
|
+
// if going into negative numbers, loop back to the last item
|
|
34
|
+
return proposed < 0 ? all.length - 1 : proposed;
|
|
35
|
+
}();
|
|
36
|
+
return (_all$nextIndex = all[nextIndex]) !== null && _all$nextIndex !== void 0 ? _all$nextIndex : null;
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
var topLayerSelector = '[popover], dialog, [role="dialog"], [role="alertdialog"]';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns true when `element` and `container` resolve to the same nearest
|
|
5
|
+
* top-layer host (or both resolve to `null`, meaning the document layer).
|
|
6
|
+
*/
|
|
7
|
+
export function isInSameLayer(_ref) {
|
|
8
|
+
var element = _ref.element,
|
|
9
|
+
container = _ref.container;
|
|
10
|
+
var elementLayer = element.closest(topLayerSelector);
|
|
11
|
+
var containerLayer = container.closest(topLayerSelector);
|
|
12
|
+
// Both elements are in the "document" layer
|
|
13
|
+
if (!elementLayer && !containerLayer) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return elementLayer === containerLayer;
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getDocument } from '@atlaskit/browser-apis';
|
|
2
|
+
import { isInSameLayer } from './is-in-same-layer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns `true` when `document.activeElement` is inside a nested top-layer
|
|
6
|
+
* descendant of `container` (a `[popover]`, `<dialog>`, `[role="dialog"]` or
|
|
7
|
+
* `[role="alertdialog"]` that is itself a descendant of `container`).
|
|
8
|
+
*/
|
|
9
|
+
export function isNestedLayerFocused(_ref) {
|
|
10
|
+
var _getDocument;
|
|
11
|
+
var container = _ref.container;
|
|
12
|
+
var focused = (_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement;
|
|
13
|
+
if (!(focused instanceof HTMLElement)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Cannot be in a nested layer if it is not in the container
|
|
18
|
+
if (!container.contains(focused)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// If not in the same layer, must be in a nested layer
|
|
23
|
+
return !isInSameLayer({
|
|
24
|
+
element: focused,
|
|
25
|
+
container: container
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export var selectors = function () {
|
|
2
|
+
// Common exclusion filter applied to every selector.
|
|
3
|
+
var not = ':not([tabindex="-1"]):not([aria-disabled="true"]):not([aria-hidden="true"])';
|
|
4
|
+
var notDisabled = ":not([disabled])".concat(not);
|
|
5
|
+
var focusable = ["a[href]".concat(not), "area[href]".concat(not), "input".concat(notDisabled, ":not([type=\"hidden\"])"), "select".concat(notDisabled), "textarea".concat(notDisabled), "button".concat(notDisabled), "iframe".concat(not), "audio[controls]".concat(not), "video[controls]".concat(not), "[contenteditable]:not([contenteditable=\"false\"])".concat(not), "[tabindex]".concat(notDisabled).concat(not)];
|
|
6
|
+
return {
|
|
7
|
+
focusable: focusable.join(','),
|
|
8
|
+
// Build the `:focus` selector
|
|
9
|
+
focused: focusable.map(function (selector) {
|
|
10
|
+
return "".concat(selector, ":focus");
|
|
11
|
+
}).join(',')
|
|
12
|
+
};
|
|
13
|
+
}();
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps a popover content role to the correct `aria-haspopup` value
|
|
3
|
+
* for its trigger element.
|
|
4
|
+
*
|
|
5
|
+
* Returns `undefined` for roles that are not popups in the ARIA sense
|
|
6
|
+
* (`tooltip`, `note`, `status`, `alert`, `log`). Triggers for those roles
|
|
7
|
+
* should not announce a popup at all - emitting `aria-haspopup="dialog"`
|
|
8
|
+
* on a tooltip trigger would mislead assistive technology.
|
|
9
|
+
*/
|
|
10
|
+
export function roleToAriaHasPopup(_ref) {
|
|
11
|
+
var role = _ref.role;
|
|
12
|
+
if (role === 'menu') {
|
|
13
|
+
return 'menu';
|
|
14
|
+
}
|
|
15
|
+
if (role === 'listbox') {
|
|
16
|
+
return 'listbox';
|
|
17
|
+
}
|
|
18
|
+
if (role === 'tree') {
|
|
19
|
+
return 'tree';
|
|
20
|
+
}
|
|
21
|
+
if (role === 'grid') {
|
|
22
|
+
return 'grid';
|
|
23
|
+
}
|
|
24
|
+
if (role === 'dialog' || role === 'alertdialog') {
|
|
25
|
+
return 'dialog';
|
|
26
|
+
}
|
|
27
|
+
// Non-popup roles (tooltip, note, status, alert, log) and the
|
|
28
|
+
// no-role case: omit `aria-haspopup` entirely. React drops the
|
|
29
|
+
// attribute when the value is `undefined`.
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|