@atlaskit/top-layer 0.12.2 → 0.14.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 +31 -0
- package/README.md +61 -34
- package/dist/cjs/animations/presets.js +4 -4
- package/dist/cjs/entry-points/focus.js +6 -4
- package/dist/cjs/entry-points/get-aria-for-trigger.js +12 -0
- package/dist/cjs/entry-points/popover-surface.js +12 -0
- package/dist/cjs/entry-points/use-anchor-position-at-point.js +12 -0
- package/dist/cjs/entry-points/use-popover-id.js +21 -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/get-aria-for-trigger.js +88 -0
- package/dist/cjs/internal/role-to-aria-has-popup.js +37 -0
- package/dist/cjs/internal/role-types.js +2 -35
- package/dist/cjs/internal/should-focus-into-popover.js +19 -0
- package/dist/cjs/internal/slot.js +1 -1
- package/dist/cjs/internal/use-anchor-position-at-point.js +127 -0
- package/dist/cjs/internal/use-anchor-position.js +36 -16
- 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/popover/popover.js +79 -80
- package/dist/cjs/popover-surface/index.js +12 -0
- package/dist/cjs/{popup/popup-surface.js → popover-surface/popover-surface.js} +9 -12
- package/dist/cjs/testing/polyfill.js +9 -6
- package/dist/cjs/use-arrow-navigation/use-arrow-navigation.js +7 -5
- package/dist/es2019/animations/presets.js +4 -4
- package/dist/es2019/entry-points/focus.js +3 -1
- package/dist/es2019/entry-points/get-aria-for-trigger.js +1 -0
- package/dist/es2019/entry-points/popover-surface.js +1 -0
- package/dist/es2019/entry-points/use-anchor-position-at-point.js +1 -0
- package/dist/es2019/entry-points/use-popover-id.js +15 -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/get-aria-for-trigger.js +84 -0
- package/dist/es2019/internal/role-to-aria-has-popup.js +32 -0
- package/dist/es2019/internal/role-types.js +0 -33
- package/dist/es2019/internal/should-focus-into-popover.js +14 -0
- package/dist/es2019/internal/slot.js +1 -1
- package/dist/es2019/internal/use-anchor-position-at-point.js +122 -0
- package/dist/es2019/internal/use-anchor-position.js +21 -14
- 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/popover/popover.js +79 -80
- package/dist/es2019/popover-surface/index.js +1 -0
- package/dist/es2019/popover-surface/popover-surface.js +27 -0
- package/dist/es2019/testing/polyfill.js +9 -6
- package/dist/es2019/use-arrow-navigation/use-arrow-navigation.js +3 -1
- package/dist/esm/animations/presets.js +4 -4
- package/dist/esm/entry-points/focus.js +3 -1
- package/dist/esm/entry-points/get-aria-for-trigger.js +1 -0
- package/dist/esm/entry-points/popover-surface.js +1 -0
- package/dist/esm/entry-points/use-anchor-position-at-point.js +1 -0
- package/dist/esm/entry-points/use-popover-id.js +15 -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/get-aria-for-trigger.js +83 -0
- package/dist/esm/internal/role-to-aria-has-popup.js +31 -0
- package/dist/esm/internal/role-types.js +2 -34
- package/dist/esm/internal/should-focus-into-popover.js +13 -0
- package/dist/esm/internal/slot.js +1 -1
- package/dist/esm/internal/use-anchor-position-at-point.js +122 -0
- package/dist/esm/internal/use-anchor-position.js +36 -16
- 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/popover/popover.js +79 -80
- package/dist/esm/popover-surface/index.js +1 -0
- package/dist/esm/popover-surface/popover-surface.js +26 -0
- package/dist/esm/testing/polyfill.js +9 -6
- package/dist/esm/use-arrow-navigation/use-arrow-navigation.js +3 -1
- package/dist/types/animations/presets.d.ts +4 -4
- package/dist/types/animations/types.d.ts +1 -1
- package/dist/types/dialog/types.d.ts +0 -8
- package/dist/types/entry-points/focus.d.ts +4 -2
- package/dist/types/entry-points/get-aria-for-trigger.d.ts +1 -0
- package/dist/types/entry-points/popover-surface.d.ts +2 -0
- package/dist/types/entry-points/popover.d.ts +1 -1
- package/dist/types/entry-points/use-anchor-position-at-point.d.ts +1 -0
- package/dist/types/entry-points/use-popover-id.d.ts +11 -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/get-aria-for-trigger.d.ts +86 -0
- package/dist/types/internal/role-to-aria-has-popup.d.ts +13 -0
- package/dist/types/internal/role-types.d.ts +0 -12
- package/dist/types/internal/should-focus-into-popover.d.ts +8 -0
- package/dist/types/internal/slot.d.ts +2 -2
- 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/popover/index.d.ts +1 -1
- package/dist/types/popover/popover.d.ts +6 -13
- package/dist/types/popover/types.d.ts +6 -5
- package/dist/types/popover-surface/index.d.ts +2 -0
- package/dist/types/popover-surface/popover-surface.d.ts +21 -0
- package/dist/types/use-arrow-navigation/use-arrow-navigation.d.ts +1 -1
- package/dist/types-ts4.5/animations/presets.d.ts +4 -4
- package/dist/types-ts4.5/animations/types.d.ts +1 -1
- package/dist/types-ts4.5/dialog/types.d.ts +0 -8
- package/dist/types-ts4.5/entry-points/focus.d.ts +4 -2
- package/dist/types-ts4.5/entry-points/get-aria-for-trigger.d.ts +1 -0
- package/dist/types-ts4.5/entry-points/popover-surface.d.ts +2 -0
- package/dist/types-ts4.5/entry-points/popover.d.ts +1 -1
- package/dist/types-ts4.5/entry-points/use-anchor-position-at-point.d.ts +1 -0
- package/dist/types-ts4.5/entry-points/use-popover-id.d.ts +11 -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/get-aria-for-trigger.d.ts +86 -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 -12
- package/dist/types-ts4.5/internal/should-focus-into-popover.d.ts +8 -0
- package/dist/types-ts4.5/internal/slot.d.ts +2 -2
- 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/popover/index.d.ts +1 -1
- package/dist/types-ts4.5/popover/popover.d.ts +6 -13
- package/dist/types-ts4.5/popover/types.d.ts +6 -5
- package/dist/types-ts4.5/popover-surface/index.d.ts +2 -0
- package/dist/types-ts4.5/popover-surface/popover-surface.d.ts +21 -0
- package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +1 -1
- package/get-aria-for-trigger/package.json +17 -0
- package/package.json +3 -3
- package/popover-surface/package.json +17 -0
- package/use-anchor-position-at-point/package.json +17 -0
- package/use-popover-id/package.json +17 -0
- package/dist/cjs/entry-points/popup-surface.js +0 -12
- package/dist/cjs/entry-points/popup.js +0 -12
- package/dist/cjs/focus/focus.js +0 -169
- package/dist/cjs/focus/index.js +0 -24
- package/dist/cjs/popup/index.js +0 -37
- package/dist/cjs/popup/popup-content.js +0 -214
- package/dist/cjs/popup/popup-context.js +0 -25
- package/dist/cjs/popup/popup-provider.js +0 -15
- package/dist/cjs/popup/popup-trigger-function.js +0 -80
- package/dist/cjs/popup/popup-trigger.js +0 -66
- package/dist/cjs/popup/popup.js +0 -81
- package/dist/cjs/popup/types.js +0 -5
- package/dist/cjs/popup/use-maybe-popup-context.js +0 -17
- package/dist/cjs/popup/use-popup-context.js +0 -20
- package/dist/es2019/entry-points/popup-surface.js +0 -1
- package/dist/es2019/entry-points/popup.js +0 -1
- package/dist/es2019/focus/focus.js +0 -158
- package/dist/es2019/focus/index.js +0 -1
- package/dist/es2019/popup/index.js +0 -33
- package/dist/es2019/popup/popup-content.js +0 -206
- package/dist/es2019/popup/popup-context.js +0 -20
- package/dist/es2019/popup/popup-provider.js +0 -10
- package/dist/es2019/popup/popup-surface.js +0 -30
- package/dist/es2019/popup/popup-trigger-function.js +0 -75
- package/dist/es2019/popup/popup-trigger.js +0 -61
- package/dist/es2019/popup/popup.js +0 -67
- package/dist/es2019/popup/types.js +0 -1
- package/dist/es2019/popup/use-maybe-popup-context.js +0 -12
- package/dist/es2019/popup/use-popup-context.js +0 -15
- package/dist/esm/entry-points/popup-surface.js +0 -1
- package/dist/esm/entry-points/popup.js +0 -1
- package/dist/esm/focus/focus.js +0 -160
- package/dist/esm/focus/index.js +0 -1
- package/dist/esm/popup/index.js +0 -33
- package/dist/esm/popup/popup-content.js +0 -206
- package/dist/esm/popup/popup-context.js +0 -20
- package/dist/esm/popup/popup-provider.js +0 -10
- package/dist/esm/popup/popup-surface.js +0 -29
- package/dist/esm/popup/popup-trigger-function.js +0 -75
- package/dist/esm/popup/popup-trigger.js +0 -59
- package/dist/esm/popup/popup.js +0 -72
- package/dist/esm/popup/types.js +0 -1
- package/dist/esm/popup/use-maybe-popup-context.js +0 -12
- package/dist/esm/popup/use-popup-context.js +0 -15
- package/dist/types/entry-points/popup-surface.d.ts +0 -2
- package/dist/types/entry-points/popup.d.ts +0 -2
- package/dist/types/focus/focus.d.ts +0 -50
- package/dist/types/focus/index.d.ts +0 -2
- package/dist/types/popup/index.d.ts +0 -34
- package/dist/types/popup/popup-content.d.ts +0 -14
- package/dist/types/popup/popup-context.d.ts +0 -96
- package/dist/types/popup/popup-provider.d.ts +0 -10
- package/dist/types/popup/popup-surface.d.ts +0 -22
- package/dist/types/popup/popup-trigger-function.d.ts +0 -112
- package/dist/types/popup/popup-trigger.d.ts +0 -14
- package/dist/types/popup/popup.d.ts +0 -14
- package/dist/types/popup/types.d.ts +0 -321
- package/dist/types/popup/use-maybe-popup-context.d.ts +0 -8
- package/dist/types/popup/use-popup-context.d.ts +0 -7
- package/dist/types-ts4.5/entry-points/popup-surface.d.ts +0 -2
- package/dist/types-ts4.5/entry-points/popup.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
- package/dist/types-ts4.5/popup/index.d.ts +0 -34
- package/dist/types-ts4.5/popup/popup-content.d.ts +0 -14
- package/dist/types-ts4.5/popup/popup-context.d.ts +0 -96
- package/dist/types-ts4.5/popup/popup-provider.d.ts +0 -10
- package/dist/types-ts4.5/popup/popup-surface.d.ts +0 -22
- package/dist/types-ts4.5/popup/popup-trigger-function.d.ts +0 -112
- package/dist/types-ts4.5/popup/popup-trigger.d.ts +0 -14
- package/dist/types-ts4.5/popup/popup.d.ts +0 -14
- package/dist/types-ts4.5/popup/types.d.ts +0 -321
- package/dist/types-ts4.5/popup/use-maybe-popup-context.d.ts +0 -8
- package/dist/types-ts4.5/popup/use-popup-context.d.ts +0 -7
- package/popup/package.json +0 -17
- package/popup-surface/package.json +0 -17
- /package/dist/cjs/{popup/popup-surface.compiled.css → popover-surface/popover-surface.compiled.css} +0 -0
- /package/dist/es2019/{popup/popup-surface.compiled.css → popover-surface/popover-surface.compiled.css} +0 -0
- /package/dist/esm/{popup/popup-surface.compiled.css → popover-surface/popover-surface.compiled.css} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const selectors = (() => {
|
|
2
|
+
// Common exclusion filter applied to every selector.
|
|
3
|
+
const not = ':not([tabindex="-1"]):not([aria-disabled="true"]):not([aria-hidden="true"])';
|
|
4
|
+
const notDisabled = `:not([disabled])${not}`;
|
|
5
|
+
const focusable = [`a[href]${not}`, `area[href]${not}`, `input${notDisabled}:not([type="hidden"])`, `select${notDisabled}`, `textarea${notDisabled}`, `button${notDisabled}`, `iframe${not}`, `audio[controls]${not}`, `video[controls]${not}`, `[contenteditable]:not([contenteditable="false"])${not}`, `[tabindex]${notDisabled}${not}`];
|
|
6
|
+
return {
|
|
7
|
+
focusable: focusable.join(','),
|
|
8
|
+
// Build the `:focus` selector
|
|
9
|
+
focused: focusable.map(selector => `${selector}:focus`).join(',')
|
|
10
|
+
};
|
|
11
|
+
})();
|
|
File without changes
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { roleToAriaHasPopup } from './role-to-aria-has-popup';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Roles accepted by `getAriaForTrigger`.
|
|
5
|
+
*
|
|
6
|
+
* Excludes tooltip-family roles (`tooltip`, `note`, `status`, `alert`, `log`)
|
|
7
|
+
* because the correct trigger wiring for those is `aria-describedby`, not
|
|
8
|
+
* `aria-haspopup` / `aria-expanded` / `aria-controls`. Use `aria-describedby`
|
|
9
|
+
* manually for tooltip triggers.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Non-undefined subset of `TAriaHasPopupValue`. Every role accepted by
|
|
14
|
+
* `getAriaForTrigger` maps to a concrete `aria-haspopup` string, so the
|
|
15
|
+
* trigger always receives a defined attribute value.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns the ARIA attributes to spread onto a click-activated popover trigger.
|
|
20
|
+
*
|
|
21
|
+
* Centralises three attributes that are easy to forget or get wrong individually:
|
|
22
|
+
*
|
|
23
|
+
* - `aria-haspopup` - derived from the popover's role (e.g. `dialog → "dialog"`,
|
|
24
|
+
* `menu → "menu"`, `tooltip → undefined`). React omits the attribute when
|
|
25
|
+
* the value is `undefined`.
|
|
26
|
+
* - `aria-expanded` - reflects the current open state.
|
|
27
|
+
* - `aria-controls` - references the popover element by its id, even
|
|
28
|
+
* while the popover is closed. The relationship is stable; `aria-expanded`
|
|
29
|
+
* carries the current visibility state.
|
|
30
|
+
*
|
|
31
|
+
* **This function is for click/keyboard-activated popovers only.** For
|
|
32
|
+
* hover-driven tooltips, the trigger should use `aria-describedby` instead.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* import { getAriaForTrigger } from '@atlaskit/top-layer/get-aria-for-trigger';
|
|
37
|
+
* import { usePopoverId } from '@atlaskit/top-layer/use-popover-id';
|
|
38
|
+
*
|
|
39
|
+
* const popoverId = usePopoverId();
|
|
40
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
41
|
+
*
|
|
42
|
+
* return (
|
|
43
|
+
* <>
|
|
44
|
+
* <button
|
|
45
|
+
* ref={triggerRef}
|
|
46
|
+
* onClick={() => setIsOpen((previous) => !previous)}
|
|
47
|
+
* {...getAriaForTrigger({ role: 'dialog', isOpen, popoverId })}
|
|
48
|
+
* >
|
|
49
|
+
* Open
|
|
50
|
+
* </button>
|
|
51
|
+
* <Popover
|
|
52
|
+
* id={popoverId}
|
|
53
|
+
* role="dialog"
|
|
54
|
+
* label="My dialog"
|
|
55
|
+
* isOpen={isOpen}
|
|
56
|
+
* triggerRef={triggerRef}
|
|
57
|
+
* onClose={() => setIsOpen(false)}
|
|
58
|
+
* >
|
|
59
|
+
* …
|
|
60
|
+
* </Popover>
|
|
61
|
+
* </>
|
|
62
|
+
* );
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function getAriaForTrigger({
|
|
66
|
+
role,
|
|
67
|
+
isOpen,
|
|
68
|
+
popoverId
|
|
69
|
+
}) {
|
|
70
|
+
// `roleToAriaHasPopup` accepts a wider role union (including tooltip-family
|
|
71
|
+
// roles that map to `undefined`). `TAriaForTriggerRole` excludes those, so
|
|
72
|
+
// the returned value is guaranteed to be a defined string. The cast narrows
|
|
73
|
+
// the wider helper signature to match.
|
|
74
|
+
const ariaHasPopup = roleToAriaHasPopup({
|
|
75
|
+
role
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
'aria-haspopup': ariaHasPopup,
|
|
79
|
+
'aria-expanded': isOpen,
|
|
80
|
+
// Keep the trigger-to-popover relationship stable. `aria-expanded`
|
|
81
|
+
// tells assistive technology whether the controlled popover is visible.
|
|
82
|
+
'aria-controls': popoverId
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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({
|
|
11
|
+
role
|
|
12
|
+
}) {
|
|
13
|
+
if (role === 'menu') {
|
|
14
|
+
return 'menu';
|
|
15
|
+
}
|
|
16
|
+
if (role === 'listbox') {
|
|
17
|
+
return 'listbox';
|
|
18
|
+
}
|
|
19
|
+
if (role === 'tree') {
|
|
20
|
+
return 'tree';
|
|
21
|
+
}
|
|
22
|
+
if (role === 'grid') {
|
|
23
|
+
return 'grid';
|
|
24
|
+
}
|
|
25
|
+
if (role === 'dialog' || role === 'alertdialog') {
|
|
26
|
+
return 'dialog';
|
|
27
|
+
}
|
|
28
|
+
// Non-popup roles (tooltip, note, status, alert, log) and the
|
|
29
|
+
// no-role case: omit `aria-haspopup` entirely. React drops the
|
|
30
|
+
// attribute when the value is `undefined`.
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
@@ -42,39 +42,6 @@
|
|
|
42
42
|
* the role for cases where it is set externally or not needed.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
* Maps a popover content role to the correct `aria-haspopup` value
|
|
47
|
-
* for its trigger element.
|
|
48
|
-
*
|
|
49
|
-
* Returns `undefined` for roles that are not popups in the ARIA sense
|
|
50
|
-
* (`tooltip`, `note`, `status`, `alert`, `log`). Triggers for those roles
|
|
51
|
-
* should not announce a popup at all - emitting `aria-haspopup="dialog"`
|
|
52
|
-
* on a tooltip trigger would mislead assistive technology.
|
|
53
|
-
*/
|
|
54
|
-
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
55
|
-
export function roleToAriaHasPopup({
|
|
56
|
-
role
|
|
57
|
-
}) {
|
|
58
|
-
if (role === 'menu') {
|
|
59
|
-
return 'menu';
|
|
60
|
-
}
|
|
61
|
-
if (role === 'listbox') {
|
|
62
|
-
return 'listbox';
|
|
63
|
-
}
|
|
64
|
-
if (role === 'tree') {
|
|
65
|
-
return 'tree';
|
|
66
|
-
}
|
|
67
|
-
if (role === 'grid') {
|
|
68
|
-
return 'grid';
|
|
69
|
-
}
|
|
70
|
-
if (role === 'dialog' || role === 'alertdialog') {
|
|
71
|
-
return 'dialog';
|
|
72
|
-
}
|
|
73
|
-
// Non-popup roles (tooltip, note, status, alert, log) and the
|
|
74
|
-
// no-role case: omit `aria-haspopup` entirely. React drops the
|
|
75
|
-
// attribute when the value is `undefined`.
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
45
|
const rolesThatMoveFocus = new Set(['dialog', 'alertdialog', 'menu', 'listbox', 'tree', 'grid']);
|
|
79
46
|
|
|
80
47
|
/**
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const rolesThatMoveFocus = new Set(['dialog', 'alertdialog', 'menu', 'listbox', 'tree', 'grid']);
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns `true` when the given popover role moves focus into the popover
|
|
5
|
+
* on open, and `false` otherwise (including when `role` is `undefined`).
|
|
6
|
+
*/
|
|
7
|
+
export function shouldFocusIntoPopover({
|
|
8
|
+
role
|
|
9
|
+
}) {
|
|
10
|
+
if (!role) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return rolesThatMoveFocus.has(role);
|
|
14
|
+
}
|
|
@@ -43,7 +43,7 @@ export const Slot = /*#__PURE__*/forwardRef(function Slot(slotProps, ref) {
|
|
|
43
43
|
children: rawChildren,
|
|
44
44
|
...props
|
|
45
45
|
} = slotProps;
|
|
46
|
-
// `
|
|
46
|
+
// `SlotProps` intersects `Record<string, unknown>`, so `children` is typed as `unknown`.
|
|
47
47
|
const children = rawChildren;
|
|
48
48
|
if (!isSlotChild(children)) {
|
|
49
49
|
return children;
|
|
@@ -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
|
+
}
|
|
@@ -28,13 +28,13 @@ import { setStyle } from './set-style';
|
|
|
28
28
|
// from `./resolve-placement` so search-and-jump lands at the source of truth.
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* Module-scope no-op `ResizeObserver`
|
|
31
|
+
* Module-scope no-op `ResizeObserver` used in non-DOM jest environments where
|
|
32
32
|
* `ResizeObserver` is `undefined`. Hoisted out of the effect to avoid
|
|
33
|
-
*
|
|
33
|
+
* allocating a new class declaration on every run.
|
|
34
34
|
*/
|
|
35
|
-
const
|
|
36
|
-
observe() {}
|
|
37
|
-
unobserve() {}
|
|
35
|
+
const NoopResizeObserver = class {
|
|
36
|
+
observe() {}
|
|
37
|
+
unobserve() {}
|
|
38
38
|
disconnect() {}
|
|
39
39
|
};
|
|
40
40
|
|
|
@@ -202,13 +202,17 @@ export function useAnchorPosition({
|
|
|
202
202
|
anchorRef,
|
|
203
203
|
popoverRef,
|
|
204
204
|
placement = {},
|
|
205
|
-
forceFallbackPositioning = false
|
|
205
|
+
forceFallbackPositioning = false,
|
|
206
|
+
isEnabled = true
|
|
206
207
|
}) {
|
|
207
208
|
const id = useId();
|
|
208
209
|
// Stabilize `placement` object so the same object literal
|
|
209
210
|
// value returns an object with the same reference
|
|
210
211
|
const stablePlacement = useStablePlacement(placement);
|
|
211
212
|
useLayoutEffect(() => {
|
|
213
|
+
if (!isEnabled) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
212
216
|
const trigger = anchorRef.current;
|
|
213
217
|
const popover = popoverRef.current;
|
|
214
218
|
if (!trigger || !popover) {
|
|
@@ -399,17 +403,20 @@ export function useAnchorPosition({
|
|
|
399
403
|
// Self-disconnects after one valid measurement; ongoing
|
|
400
404
|
// scroll/resize is handled by the window listeners below.
|
|
401
405
|
// `ResizeObserver` is missing in some non-DOM jest environments
|
|
402
|
-
// (e.g. post-office's `node` environment). Fall back to
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
|
|
406
|
+
// (e.g. post-office's `node` environment). Fall back to a no-op
|
|
407
|
+
// observer there. The scroll/resize listeners below still keep
|
|
408
|
+
// the popover positioned in the rare case the consumer also
|
|
409
|
+
// polyfilled `showPopover` but not `ResizeObserver`. Real
|
|
410
|
+
// browsers always have it. The `NoopResizeObserver` class is
|
|
411
|
+
// hoisted to module scope (see top of file) so we do not allocate
|
|
412
|
+
// a new constructor on every effect run.
|
|
413
|
+
const ResizeObserverImpl = typeof ResizeObserver !== 'undefined' ? ResizeObserver : NoopResizeObserver;
|
|
414
|
+
const resizeObserver = new ResizeObserverImpl(() => {
|
|
408
415
|
if (popover.offsetWidth > 0 && popover.offsetHeight > 0) {
|
|
409
416
|
update();
|
|
410
417
|
resizeObserver.disconnect();
|
|
411
418
|
}
|
|
412
|
-
})
|
|
419
|
+
});
|
|
413
420
|
|
|
414
421
|
// If the popover is already open by the time this effect runs (for
|
|
415
422
|
// example, the parent `useAnchorPosition` effect runs after the child
|
|
@@ -456,5 +463,5 @@ export function useAnchorPosition({
|
|
|
456
463
|
popover.style.removeProperty('opacity');
|
|
457
464
|
});
|
|
458
465
|
return undoPositioning;
|
|
459
|
-
}, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, id]);
|
|
466
|
+
}, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, isEnabled, id]);
|
|
460
467
|
}
|
|
@@ -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
|
+
};
|