@astryxdesign/core 0.7.8 → 0.7.9-canary.0a64fdb
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/dist/AppShell/observeMainOverflow.d.ts.map +1 -1
- package/dist/AppShell/observeMainOverflow.js +9 -6
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +5 -0
- package/dist/Calendar/styles.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +54 -11
- package/dist/ClickableCard/ClickableCard.d.ts +1 -1
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +5 -1
- package/dist/DateInput/PickerWheel.d.ts.map +1 -1
- package/dist/DateInput/PickerWheel.js +7 -1
- package/dist/DateRangeInput/DateRangeInput.js +1 -1
- package/dist/Dialog/Dialog.d.ts +2 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +12 -1
- package/dist/Drawer/useDrawerDialogPresence.d.ts +2 -1
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
- package/dist/Drawer/useDrawerDialogPresence.js +11 -2
- package/dist/DropdownMenu/DropdownMenu.d.ts +9 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +9 -4
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +3 -2
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +3 -2
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +3 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +3 -2
- package/dist/Field/inputDisabledFocus.d.ts +4 -4
- package/dist/Field/inputStyles.stylex.d.ts +1 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +16 -2
- package/dist/Icon/Icon.d.ts +1 -1
- package/dist/InfoTip/InfoTip.d.ts +1 -0
- package/dist/InfoTip/InfoTip.d.ts.map +1 -1
- package/dist/InfoTip/InfoTip.js +1 -0
- package/dist/InputGroup/InputGroup.d.ts +4 -3
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +2 -2
- package/dist/InputGroup/groupStyles.d.ts +1 -1
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +1 -1
- package/dist/Item/Item.d.ts +2 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +29 -6
- package/dist/Layer/holdEntranceInput.d.ts +24 -0
- package/dist/Layer/holdEntranceInput.d.ts.map +1 -0
- package/dist/Layer/holdEntranceInput.js +65 -0
- package/dist/Layer/inputHold.d.ts +49 -0
- package/dist/Layer/inputHold.d.ts.map +1 -0
- package/dist/Layer/inputHold.js +636 -0
- package/dist/Layer/preserveAnimations.d.ts +1 -5
- package/dist/Layer/preserveAnimations.d.ts.map +1 -1
- package/dist/Layer/preserveAnimations.js +12 -214
- package/dist/Layout/LayoutContent.d.ts +3 -1
- package/dist/Layout/LayoutContent.d.ts.map +1 -1
- package/dist/Layout/LayoutContent.js +12 -4
- package/dist/Layout/LayoutPanel.d.ts +4 -1
- package/dist/Layout/LayoutPanel.d.ts.map +1 -1
- package/dist/Layout/LayoutPanel.js +13 -4
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +4 -0
- package/dist/MetadataList/MetadataList.js +1 -1
- package/dist/MobileNav/MobileNav.d.ts +11 -1
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +39 -1
- package/dist/MoreMenu/MoreMenu.d.ts +5 -3
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +22 -4
- package/dist/SelectableCard/SelectableCard.d.ts +1 -1
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +5 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +12 -2
- package/dist/Stack/StackItem.d.ts +8 -4
- package/dist/Stack/StackItem.d.ts.map +1 -1
- package/dist/Stack/StackItem.js +17 -4
- package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +9 -0
- package/dist/Token/TokenLink.d.ts +1 -1
- package/dist/Token/TokenLink.d.ts.map +1 -1
- package/dist/Token/TokenLink.js +4 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +162 -59
- package/dist/TreeList/TreeListItem.d.ts +2 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +27 -3
- package/dist/astryx.css +35 -8
- package/dist/gestures/GestureHost.d.ts +2 -2
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +21 -6
- package/dist/gestures/PanGestureRecognizer.d.ts +11 -1
- package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/PanGestureRecognizer.js +15 -1
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +6 -1
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/nativeScroll.d.ts +3 -6
- package/dist/gestures/nativeScroll.d.ts.map +1 -1
- package/dist/gestures/nativeScroll.js +7 -52
- package/dist/gestures/wheelSegmenter.d.ts +68 -1
- package/dist/gestures/wheelSegmenter.d.ts.map +1 -1
- package/dist/gestures/wheelSegmenter.js +199 -10
- package/dist/hooks/StackItemScrollRegion.d.ts +31 -0
- package/dist/hooks/StackItemScrollRegion.d.ts.map +1 -0
- package/dist/hooks/StackItemScrollRegion.js +40 -0
- package/dist/hooks/menuPressGesture.d.ts +133 -21
- package/dist/hooks/menuPressGesture.d.ts.map +1 -1
- package/dist/hooks/menuPressGesture.js +155 -30
- package/dist/hooks/menuPressValues.d.ts +16 -0
- package/dist/hooks/menuPressValues.d.ts.map +1 -0
- package/dist/hooks/menuPressValues.js +18 -0
- package/dist/hooks/useClickableContainer.d.ts +27 -5
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +41 -5
- package/dist/hooks/useFocusTrap.d.ts +10 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +225 -132
- package/dist/hooks/useMenuPress.d.ts +34 -1
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +93 -5
- package/dist/hooks/usePressFeedback.d.ts +6 -6
- package/dist/hooks/usePressFeedback.js +6 -6
- package/dist/hooks/useScrollRegionProps.d.ts +35 -0
- package/dist/hooks/useScrollRegionProps.d.ts.map +1 -0
- package/dist/hooks/useScrollRegionProps.js +51 -0
- package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.d.ts +8 -0
- package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.js +12 -0
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +1 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
- package/dist/motion/carryAnimations.d.ts +58 -0
- package/dist/motion/carryAnimations.d.ts.map +1 -0
- package/dist/motion/carryAnimations.js +305 -0
- package/dist/motion/transientCopy.d.ts +4 -2
- package/dist/motion/transientCopy.d.ts.map +1 -1
- package/dist/motion/transientCopy.js +34 -5
- package/dist/motion/uikit/gestures.d.ts +41 -0
- package/dist/motion/uikit/gestures.d.ts.map +1 -1
- package/dist/motion/uikit/gestures.js +25 -0
- package/dist/motion/uikit/index.d.ts +1 -1
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +1 -1
- package/dist/privateAttributes/menuPressLit.stylex.d.ts +52 -0
- package/dist/privateAttributes/menuPressLit.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/menuPressLit.stylex.js +99 -0
- package/dist/privateAttributes/press.stylex.d.ts +139 -0
- package/dist/privateAttributes/press.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/press.stylex.js +98 -0
- package/dist/theme/tokens.stylex.d.ts +2 -2
- package/dist/theme/tokens.stylex.js +1 -1
- package/dist/utils/flatTree.d.ts +6 -3
- package/dist/utils/flatTree.d.ts.map +1 -1
- package/dist/utils/flatTree.js +13 -3
- package/dist/utils/hitArea.d.ts.map +1 -1
- package/dist/utils/hitArea.js +82 -5
- package/dist/utils/inputStream.d.ts +14 -1
- package/dist/utils/inputStream.d.ts.map +1 -1
- package/dist/utils/inputStream.js +40 -9
- package/dist/utils/interactionOverlay.stylex.d.ts +2 -1
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +7 -2
- package/dist/utils/pressFeedback.d.ts +7 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +32 -7
- package/dist/utils/pressGesture.d.ts +6 -0
- package/dist/utils/pressGesture.d.ts.map +1 -1
- package/dist/utils/pressGesture.js +17 -0
- package/dist/utils/rowPressLayer.stylex.d.ts +14 -0
- package/dist/utils/rowPressLayer.stylex.d.ts.map +1 -0
- package/dist/utils/rowPressLayer.stylex.js +25 -0
- package/dist/utils/scrollView.d.ts +25 -0
- package/dist/utils/scrollView.d.ts.map +1 -0
- package/dist/utils/scrollView.js +108 -0
- package/dist/utils/selectionLock.d.ts.map +1 -1
- package/dist/utils/selectionLock.js +1 -10
- package/dist/utils/touchHitArea.stylex.d.ts +35 -0
- package/dist/utils/touchHitArea.stylex.d.ts.map +1 -0
- package/dist/utils/touchHitArea.stylex.js +32 -0
- package/dist/utils/triggerFocus.d.ts +5 -0
- package/dist/utils/triggerFocus.d.ts.map +1 -1
- package/dist/utils/triggerFocus.js +9 -2
- package/locales/en.json +4 -0
- package/locales/pseudo.json +3 -0
- package/package.json +5 -5
- package/src/AppShell/observeMainOverflow.ts +9 -6
- package/src/BottomSheet/BottomSheet.tsx +5 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -2
- package/src/CheckboxInput/CheckboxInput.tsx +71 -9
- package/src/ClickableCard/ClickableCard.tsx +8 -1
- package/src/DateInput/PickerWheel.tsx +7 -2
- package/src/DateRangeInput/DateRangeInput.tsx +1 -1
- package/src/Dialog/Dialog.doc.mjs +1 -1
- package/src/Dialog/Dialog.tsx +12 -1
- package/src/Drawer/Drawer.doc.mjs +1 -1
- package/src/Drawer/useDrawerDialogPresence.ts +9 -2
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -2
- package/src/DropdownMenu/DropdownMenu.tsx +29 -5
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -1
- package/src/DropdownMenu/DropdownMenuItem.tsx +8 -4
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -1
- package/src/FileInput/FileInput.tsx +16 -2
- package/src/InfoTip/InfoTip.doc.mjs +26 -2
- package/src/InfoTip/InfoTip.tsx +1 -0
- package/src/InputGroup/InputGroup.doc.mjs +4 -3
- package/src/InputGroup/InputGroup.tsx +4 -3
- package/src/InputGroup/groupStyles.ts +3 -1
- package/src/Item/Item.tsx +51 -9
- package/src/Layer/holdEntranceInput.ts +84 -0
- package/src/Layer/inputHold.ts +734 -0
- package/src/Layer/preserveAnimations.ts +12 -275
- package/src/Layout/LayoutContent.doc.mjs +6 -3
- package/src/Layout/LayoutContent.tsx +10 -4
- package/src/Layout/LayoutPanel.doc.mjs +6 -3
- package/src/Layout/LayoutPanel.tsx +11 -4
- package/src/Lightbox/Lightbox.tsx +4 -0
- package/src/MetadataList/MetadataList.tsx +1 -1
- package/src/MobileNav/MobileNav.doc.mjs +3 -3
- package/src/MobileNav/MobileNav.tsx +39 -1
- package/src/MoreMenu/MoreMenu.doc.mjs +2 -4
- package/src/MoreMenu/MoreMenu.tsx +5 -3
- package/src/RadioList/RadioListItem.doc.mjs +3 -2
- package/src/RadioList/RadioListItem.tsx +25 -7
- package/src/SelectableCard/SelectableCard.tsx +8 -1
- package/src/Spinner/Spinner.tsx +13 -3
- package/src/Stack/Stack.doc.mjs +1 -1
- package/src/Stack/StackItem.tsx +27 -13
- package/src/Table/plugins/sortable/useTableSortable.tsx +1 -1
- package/src/Token/Token.tsx +18 -1
- package/src/Token/TokenLink.tsx +7 -1
- package/src/Tooltip/useTooltip.tsx +175 -79
- package/src/TreeList/TreeListItem.tsx +36 -2
- package/src/gestures/GestureHost.ts +21 -5
- package/src/gestures/PanGestureRecognizer.ts +19 -2
- package/src/gestures/README.md +14 -5
- package/src/gestures/ScreenEdgePanGestureRecognizer.ts +6 -1
- package/src/gestures/nativeScroll.ts +12 -68
- package/src/gestures/wheelSegmenter.ts +243 -10
- package/src/hooks/StackItemScrollRegion.tsx +57 -0
- package/src/hooks/menuPressGesture.ts +244 -38
- package/src/hooks/menuPressValues.ts +18 -0
- package/src/hooks/useClickableContainer.doc.mjs +8 -2
- package/src/hooks/useClickableContainer.ts +72 -6
- package/src/hooks/useFocusTrap.ts +268 -151
- package/src/hooks/useMenuPress.ts +118 -11
- package/src/hooks/usePressFeedback.doc.mjs +3 -3
- package/src/hooks/usePressFeedback.ts +6 -6
- package/src/hooks/useScrollRegionProps.ts +76 -0
- package/src/i18n/generated-english/hooks/useScrollRegionProps.generated.ts +13 -0
- package/src/i18n/generated-locales/en.generated.ts +1 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
- package/src/motion/MEASUREMENTS.md +11 -0
- package/src/motion/carryAnimations.ts +389 -0
- package/src/motion/transientCopy.ts +45 -5
- package/src/motion/uikit/gestures.ts +22 -0
- package/src/motion/uikit/index.ts +1 -0
- package/src/privateAttributes/menuPressLit.stylex.ts +104 -0
- package/src/privateAttributes/press.stylex.ts +244 -0
- package/src/theme/tokens.stylex.ts +1 -1
- package/src/utils/flatTree.ts +15 -3
- package/src/utils/hitArea.ts +107 -5
- package/src/utils/inputStream.ts +47 -10
- package/src/utils/interactionOverlay.stylex.ts +7 -2
- package/src/utils/pressFeedback.ts +31 -6
- package/src/utils/pressGesture.ts +24 -1
- package/src/utils/rowPressLayer.stylex.ts +27 -0
- package/src/utils/scrollView.ts +135 -0
- package/src/utils/selectionLock.ts +5 -10
- package/src/utils/touchHitArea.stylex.ts +41 -0
- package/src/utils/triggerFocus.ts +17 -4
package/locales/pseudo.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.9-canary.0a64fdb",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -870,6 +870,9 @@
|
|
|
870
870
|
"react-dom": ">=19.0.0"
|
|
871
871
|
},
|
|
872
872
|
"devDependencies": {
|
|
873
|
+
"@astryxdesign/a11y-spec": "0.7.9-canary.0a64fdb",
|
|
874
|
+
"@astryxdesign/cli": "0.7.9-canary.0a64fdb",
|
|
875
|
+
"@astryxdesign/interaction-harness": "0.7.9-canary.0a64fdb",
|
|
873
876
|
"@babel/cli": "^7.29.7",
|
|
874
877
|
"@babel/core": "^7.29.7",
|
|
875
878
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -880,10 +883,7 @@
|
|
|
880
883
|
"@testing-library/jest-dom": "^6.6.0",
|
|
881
884
|
"@testing-library/react": "^16.3.2",
|
|
882
885
|
"@types/babel__core": "^7.20.5",
|
|
883
|
-
"tailwindcss": "^4.3.2"
|
|
884
|
-
"@astryxdesign/cli": "0.7.8",
|
|
885
|
-
"@astryxdesign/interaction-harness": "0.0.0",
|
|
886
|
-
"@astryxdesign/a11y-spec": "0.0.0"
|
|
886
|
+
"tailwindcss": "^4.3.2"
|
|
887
887
|
},
|
|
888
888
|
"dependencies": {
|
|
889
889
|
"intl-messageformat": "^11.2.9"
|
|
@@ -4,20 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file observeMainOverflow.ts
|
|
7
|
-
* @input
|
|
8
|
-
*
|
|
7
|
+
* @input A scroll container that holds its content as direct children with
|
|
8
|
+
* no wrapper: AppShell's main region under `height="fill"`, or a scrolling
|
|
9
|
+
* LayoutContent, LayoutPanel or StackItem
|
|
9
10
|
* @output observeMainOverflow(main, onChange): reports whether main's content
|
|
10
11
|
* overflows it on either axis, once at the start and on every change, and
|
|
11
12
|
* returns the function that stops observing
|
|
12
|
-
* @position Internal
|
|
13
|
+
* @position Internal and never exported. AppShell makes main a
|
|
13
14
|
* keyboard stop only while it overflows (`spec:AST-025` FR12,
|
|
14
|
-
* `component:AppShell` FR4 and PR1).
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* `component:AppShell` FR4 and PR1). useScrollRegionProps names a
|
|
16
|
+
* scrolling region only while it overflows. Main has no inner content
|
|
17
|
+
* box, so its content grows inside it without resizing it: its direct
|
|
18
|
+
* children's sizes are watched instead.
|
|
17
19
|
*
|
|
18
20
|
* SYNC: When modified, update:
|
|
19
21
|
* - /packages/core/src/AppShell/AppShell.tsx
|
|
20
22
|
* - /packages/core/src/AppShell/observeMainOverflow.test.ts
|
|
23
|
+
* - /packages/core/src/hooks/useScrollRegionProps.ts
|
|
21
24
|
*/
|
|
22
25
|
|
|
23
26
|
import {
|
|
@@ -81,6 +81,7 @@ import {LayerDepthProvider} from '../Layer/LayerDepthContext';
|
|
|
81
81
|
import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
|
|
82
82
|
import {useModalOutlet} from '../Layer/modalOutlet';
|
|
83
83
|
import {useLayerDismissal} from '../Layer/useLayerDismissal';
|
|
84
|
+
import {useFocusRegionContainment} from '../hooks/useFocusTrap';
|
|
84
85
|
import {focusForLastInput} from '../utils/focusReturn';
|
|
85
86
|
import {useInteractionModalityTracking} from '../utils/interactionModality';
|
|
86
87
|
import {
|
|
@@ -444,6 +445,10 @@ function StandaloneBottomSheet({
|
|
|
444
445
|
false,
|
|
445
446
|
);
|
|
446
447
|
const isEntrySettled = settledOpen && isOpen;
|
|
448
|
+
// Only a modal sheet contains Tab; a non-modal one leaves the page's focus
|
|
449
|
+
// order alone. A closing sheet stops containing as soon as it starts to
|
|
450
|
+
// leave.
|
|
451
|
+
useFocusRegionContainment(dialogRef, isOpen && hasScrim);
|
|
447
452
|
// Presentation outlives the controlled prop: a closing sheet stays mounted
|
|
448
453
|
// and in its native host through its exit, and only its motion completion
|
|
449
454
|
// (or a replace finishing) clears it. The open flip is derived during
|
|
@@ -109,7 +109,7 @@ export const docs = {
|
|
|
109
109
|
name: 'size',
|
|
110
110
|
type: "'sm' | 'md'",
|
|
111
111
|
description:
|
|
112
|
-
'The size of the checkbox. sm for compact layouts, md for default.',
|
|
112
|
+
'The size of the checkbox. sm for compact layouts, md for default. Under a coarse pointer a labelled checkbox’s target grows toward 44 px through an invisible hit area. It never shrinks below the row and stops at half the gap to a closer neighbour; nothing changes size.',
|
|
113
113
|
default: "'md'",
|
|
114
114
|
},
|
|
115
115
|
{
|
|
@@ -361,7 +361,8 @@ export const docsZh = {
|
|
|
361
361
|
{
|
|
362
362
|
name: 'size',
|
|
363
363
|
type: "'sm' | 'md'",
|
|
364
|
-
description:
|
|
364
|
+
description:
|
|
365
|
+
'复选框尺寸。sm 用于紧凑布局,md 为默认。在粗指针(触摸)下,带可见标签的复选框的目标通过不可见的点击区域向 44 px 扩展。它不小于所在行,与相邻控件较近时止于间距的一半;尺寸不变。',
|
|
365
366
|
default: "'md'",
|
|
366
367
|
},
|
|
367
368
|
{
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
useTransition,
|
|
25
25
|
type ChangeEvent,
|
|
26
26
|
type FocusEvent,
|
|
27
|
+
type MouseEvent as ReactMouseEvent,
|
|
28
|
+
type PointerEvent as ReactPointerEvent,
|
|
27
29
|
type ReactNode,
|
|
28
30
|
} from 'react';
|
|
29
31
|
import * as stylex from '@stylexjs/stylex';
|
|
@@ -55,6 +57,8 @@ import {useIndicator} from '../Indicator/useIndicator';
|
|
|
55
57
|
import {themeProps} from '../utils/themeProps';
|
|
56
58
|
|
|
57
59
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
60
|
+
import {watchHitArea, writeHitInsets} from '../utils/hitArea';
|
|
61
|
+
import {touchHitAreaStyles} from '../utils/touchHitArea.stylex';
|
|
58
62
|
import {
|
|
59
63
|
registerEnglishFallback,
|
|
60
64
|
useComponentTranslator,
|
|
@@ -83,6 +87,10 @@ const styles = stylex.create({
|
|
|
83
87
|
containerLabelHidden: {
|
|
84
88
|
gap: 0,
|
|
85
89
|
},
|
|
90
|
+
// Under a coarse pointer the row contains its hit band.
|
|
91
|
+
hitAreaHost: {
|
|
92
|
+
position: {default: null, '@media (pointer: coarse)': 'relative'},
|
|
93
|
+
},
|
|
86
94
|
// An indicator in another package may not name this row's private press
|
|
87
95
|
// attribute. So the row hands its touch press image down to an indicator
|
|
88
96
|
// that paints its own press, but only in its own press states. A pressed
|
|
@@ -407,6 +415,63 @@ export function CheckboxInput({
|
|
|
407
415
|
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), indicatorRef);
|
|
408
416
|
const {focusProps} = useIndicatorFocusRing(indicatorRef);
|
|
409
417
|
|
|
418
|
+
// Under a coarse pointer a labelled checkbox's row grows toward 44 px
|
|
419
|
+
// through an invisible band, and nothing changes size. A label-hidden
|
|
420
|
+
// checkbox is a marker in another component's row, which keeps its target.
|
|
421
|
+
const hasHitArea = !isLabelHidden && !isDisabled && !isReadOnly;
|
|
422
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
423
|
+
// The watch lives as long as the row is attached: the ref's cleanup stops
|
|
424
|
+
// it, and a replaced row starts its own.
|
|
425
|
+
const watchRowHitArea = useCallback(
|
|
426
|
+
(row: HTMLDivElement | null) => {
|
|
427
|
+
if (row == null || !hasHitArea) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
return watchHitArea(row, insets => writeHitInsets(row, insets));
|
|
431
|
+
},
|
|
432
|
+
[hasHitArea],
|
|
433
|
+
);
|
|
434
|
+
// The disabled-message tooltip listens for hover and focus on the whole
|
|
435
|
+
// row, a larger trigger. It anchors on the checkbox itself (below), so it
|
|
436
|
+
// shows next to the control. Its handlers check isEnabled, so the row
|
|
437
|
+
// always attaches them.
|
|
438
|
+
const rowRef = useMergedRefs(
|
|
439
|
+
disabledMessageTooltip.interactionRef,
|
|
440
|
+
watchRowHitArea,
|
|
441
|
+
);
|
|
442
|
+
// Where a press on the row itself began. A browser can move a tap's click
|
|
443
|
+
// onto the row's edge, so the band is judged from the press.
|
|
444
|
+
const pressPointRef = useRef<{x: number; y: number} | null>(null);
|
|
445
|
+
const handleRowPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
446
|
+
pressPointRef.current =
|
|
447
|
+
event.target === event.currentTarget
|
|
448
|
+
? {x: event.clientX, y: event.clientY}
|
|
449
|
+
: null;
|
|
450
|
+
};
|
|
451
|
+
// A press that began on the band, outside the row's box, toggles the
|
|
452
|
+
// checkbox, as a tap on the box does. A click from code has no press count
|
|
453
|
+
// (`detail` is 0), so it never toggles from here.
|
|
454
|
+
const handleRowClick = (event: ReactMouseEvent<HTMLDivElement>) => {
|
|
455
|
+
const press = pressPointRef.current;
|
|
456
|
+
pressPointRef.current = null;
|
|
457
|
+
if (
|
|
458
|
+
press == null ||
|
|
459
|
+
event.detail === 0 ||
|
|
460
|
+
event.target !== event.currentTarget
|
|
461
|
+
) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
465
|
+
const isOnBand =
|
|
466
|
+
press.x < box.left ||
|
|
467
|
+
press.x > box.right ||
|
|
468
|
+
press.y < box.top ||
|
|
469
|
+
press.y > box.bottom;
|
|
470
|
+
if (isOnBand) {
|
|
471
|
+
inputRef.current?.click();
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
410
475
|
const isIndeterminate = optimisticValue === 'indeterminate';
|
|
411
476
|
const isChecked = optimisticValue === true;
|
|
412
477
|
|
|
@@ -455,19 +520,15 @@ export function CheckboxInput({
|
|
|
455
520
|
style,
|
|
456
521
|
)}>
|
|
457
522
|
<div
|
|
458
|
-
ref={
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
// positioning anchors on the checkbox itself (below) so the tooltip
|
|
462
|
-
// appears next to the control, not the far edge of the row.
|
|
463
|
-
// Handlers are gated internally by isEnabled, so attaching
|
|
464
|
-
// unconditionally is safe.
|
|
465
|
-
disabledMessageTooltip.interactionRef(el);
|
|
466
|
-
}}
|
|
523
|
+
ref={rowRef}
|
|
524
|
+
onPointerDown={hasHitArea ? handleRowPointerDown : undefined}
|
|
525
|
+
onClick={hasHitArea ? handleRowClick : undefined}
|
|
467
526
|
{...(isDisabled ? undefined : pressable)}
|
|
468
527
|
{...stylex.props(
|
|
469
528
|
styles.container,
|
|
470
529
|
isLabelHidden && styles.containerLabelHidden,
|
|
530
|
+
hasHitArea && styles.hitAreaHost,
|
|
531
|
+
hasHitArea && touchHitAreaStyles.layer,
|
|
471
532
|
// Hover and focus reach the checkbox visual through this ancestor
|
|
472
533
|
// marker rather than props, so the whole row drives it.
|
|
473
534
|
!isDisabled && indicatorScope,
|
|
@@ -490,6 +551,7 @@ export function CheckboxInput({
|
|
|
490
551
|
ref,
|
|
491
552
|
indeterminateRef,
|
|
492
553
|
disabledMessageTooltip.positionRef,
|
|
554
|
+
inputRef,
|
|
493
555
|
)}
|
|
494
556
|
id={id}
|
|
495
557
|
type="checkbox"
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
} from '../theme/tokens.stylex';
|
|
47
47
|
import type {SizeValue, SpacingStep, Elevation} from '../utils/types';
|
|
48
48
|
import {mergeProps} from '../utils';
|
|
49
|
+
import {chainEventHandlers} from '../utils/chainEventHandlers';
|
|
49
50
|
import {Card} from '../Card/Card';
|
|
50
51
|
import type {CardVariant} from '../Card/Card';
|
|
51
52
|
import {useClickableContainer} from '../hooks/useClickableContainer';
|
|
@@ -314,6 +315,7 @@ export function ClickableCard({
|
|
|
314
315
|
label,
|
|
315
316
|
onClick: onClickProp,
|
|
316
317
|
onMouseUp: onMouseUpProp,
|
|
318
|
+
onPointerDown: onPointerDownProp,
|
|
317
319
|
href,
|
|
318
320
|
target,
|
|
319
321
|
isDisabled = false,
|
|
@@ -370,7 +372,7 @@ export function ClickableCard({
|
|
|
370
372
|
|
|
371
373
|
const LinkComponent = useLinkComponent();
|
|
372
374
|
|
|
373
|
-
const {onClick, onMouseUp} = useClickableContainer({
|
|
375
|
+
const {onClick, onMouseUp, onPointerDown} = useClickableContainer({
|
|
374
376
|
containerRef,
|
|
375
377
|
interactiveRef,
|
|
376
378
|
onClick: onClickProp,
|
|
@@ -427,6 +429,11 @@ export function ClickableCard({
|
|
|
427
429
|
}
|
|
428
430
|
onClick={!isDisabled ? onClick : undefined}
|
|
429
431
|
onMouseUp={!isDisabled ? handleMouseUp : undefined}
|
|
432
|
+
onPointerDown={
|
|
433
|
+
!isDisabled
|
|
434
|
+
? chainEventHandlers(onPointerDown, onPointerDownProp)
|
|
435
|
+
: onPointerDownProp
|
|
436
|
+
}
|
|
430
437
|
{...props}>
|
|
431
438
|
{isLink ? (
|
|
432
439
|
<LinkRoot
|
|
@@ -73,6 +73,12 @@ const UIKIT_SCALE = ROW_HEIGHT / picker.geometry.rowHeight;
|
|
|
73
73
|
/** UIKit's rubber band dimension for the picker's table, at that pitch. */
|
|
74
74
|
const RUBBER_BAND_DIMENSION = picker.geometry.rubberBandDimension * UIKIT_SCALE;
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* The rows' opacity at the column's edges. At 0.7 the row four from the band
|
|
78
|
+
* keeps 5:1 or more in every shipped theme, a margin over WCAG AA's 4.5:1.
|
|
79
|
+
*/
|
|
80
|
+
const FADE_FLOOR = 0.7;
|
|
81
|
+
|
|
76
82
|
const styles = stylex.create({
|
|
77
83
|
// The scrolling column's box, band and rows (Wheel.tsx), moved by the
|
|
78
84
|
// gesture system and the compositor instead of a native scroller.
|
|
@@ -92,8 +98,7 @@ const styles = stylex.create({
|
|
|
92
98
|
position: 'absolute',
|
|
93
99
|
inset: 0,
|
|
94
100
|
pointerEvents: 'none',
|
|
95
|
-
maskImage:
|
|
96
|
-
'linear-gradient(to bottom, rgb(0 0 0 / 0.3), rgb(0 0 0) 50%, rgb(0 0 0 / 0.3))',
|
|
101
|
+
maskImage: `linear-gradient(to bottom, rgb(0 0 0 / ${FADE_FLOOR}), rgb(0 0 0) 50%, rgb(0 0 0 / ${FADE_FLOOR}))`,
|
|
97
102
|
},
|
|
98
103
|
/**
|
|
99
104
|
* The selection band: a centred row-height plate behind the rows.
|
|
@@ -238,7 +238,7 @@ const styles = stylex.create({
|
|
|
238
238
|
},
|
|
239
239
|
presetButtonActive: {
|
|
240
240
|
backgroundColor: colorVars['--color-accent-muted'],
|
|
241
|
-
color: colorVars['--color-accent'],
|
|
241
|
+
color: colorVars['--color-text-accent'],
|
|
242
242
|
},
|
|
243
243
|
presetButtonDisabled: {
|
|
244
244
|
color: colorVars['--color-text-disabled'],
|
|
@@ -69,7 +69,7 @@ export const docs = {
|
|
|
69
69
|
],
|
|
70
70
|
},
|
|
71
71
|
description:
|
|
72
|
-
'Modal dialog using the native <dialog> element. Modal and inline content starts with theme body text defaults. Ancestor surface/group membership ends as a whole, including group-owned state. Explicit props and unrelated contexts remain unchanged. Place intentional groups and complete required providers inside the dialog. With reduced motion the dialog never slides or scales in: on a touch screen it fades in, in 200 ms or less, and with a mouse it appears at once.',
|
|
72
|
+
'Modal dialog using the native <dialog> element. Modal and inline content starts with theme body text defaults. Ancestor surface/group membership ends as a whole, including group-owned state. Explicit props and unrelated contexts remain unchanged. Place intentional groups and complete required providers inside the dialog. With reduced motion the dialog never slides or scales in: on a touch screen it fades in, in 200 ms or less, and with a mouse it appears at once. On a phone or tablet (a coarse primary pointer), a tap or a drag that lands while the dialog is still entering reaches nothing, and the dialog finishes entering; keys and assistive technology stay live, and a mouse or a trackpad still clicks: at once when it was the last pointer used, otherwise once it moves.',
|
|
73
73
|
props: [
|
|
74
74
|
{
|
|
75
75
|
name: 'isOpen',
|
package/src/Dialog/Dialog.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Dialog.tsx
|
|
7
7
|
* @input Uses React, DialogHTMLAttributes, ReactNode, container (Layout), DialogContext, layerTextReset,
|
|
8
|
-
* layerStructureReset, modalOutlet, interactionModality, chainEventHandlers
|
|
8
|
+
* layerStructureReset, modalOutlet, interactionModality, chainEventHandlers, holdEntranceInput
|
|
9
9
|
* @output Exports Dialog component, DialogProps, DialogVariant, DialogPurpose types
|
|
10
10
|
* @position Core implementation; consumed by index.ts, tested by Dialog.test.tsx
|
|
11
11
|
*
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* SYNC: When modified, update these files to stay in sync:
|
|
28
28
|
* - /packages/core/src/Dialog/Dialog.doc.mjs (props table, features, implementation notes)
|
|
29
29
|
* - /packages/core/src/Dialog/Dialog.test.tsx (tests for new/changed behavior)
|
|
30
|
+
* - /packages/core/src/Dialog/__tests__/DialogTouchEntrance.interaction.browser.spec.ts (the touch entrance hold)
|
|
30
31
|
* - /packages/core/src/Dialog/index.ts (exports if types change)
|
|
31
32
|
* - /apps/storybook/stories/Dialog.stories.tsx (storybook stories)
|
|
32
33
|
* - /packages/cli/assets/templates/blocks/components/Dialog/ (showcase blocks)
|
|
@@ -51,8 +52,10 @@ import {layerStructureReset} from '../Layer/layerStructureReset.stylex';
|
|
|
51
52
|
import {LayerContentBoundary} from '../Layer/layerScopedContext';
|
|
52
53
|
import {layerClickBoundary} from '../Layer/layerClickBoundary';
|
|
53
54
|
import {LayerDepthProvider} from '../Layer/LayerDepthContext';
|
|
55
|
+
import {holdEntranceInput} from '../Layer/holdEntranceInput';
|
|
54
56
|
import {useLayerDismissal} from '../Layer/useLayerDismissal';
|
|
55
57
|
import {useBackdropPress} from '../Layer/useBackdropPress';
|
|
58
|
+
import {useFocusRegionContainment} from '../hooks/useFocusTrap';
|
|
56
59
|
import {useModalOutlet} from '../Layer/modalOutlet';
|
|
57
60
|
import {
|
|
58
61
|
colorVars,
|
|
@@ -547,6 +550,9 @@ export function Dialog({
|
|
|
547
550
|
props['aria-label'] != null || props['aria-labelledby'] != null;
|
|
548
551
|
|
|
549
552
|
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
553
|
+
// Native showModal() lets Tab leave for the browser's own UI, so the shared
|
|
554
|
+
// module wraps it. An inline preview is not modal and contains nothing.
|
|
555
|
+
useFocusRegionContainment(dialogRef, isOpen && !isInline);
|
|
550
556
|
|
|
551
557
|
// Callback ref on the <dialog>: sync the default aria-labelledby from the
|
|
552
558
|
// DialogHeader title (if one rendered) in the same commit, with no second
|
|
@@ -642,6 +648,11 @@ export function Dialog({
|
|
|
642
648
|
focusForLastInput(autofocusTarget);
|
|
643
649
|
}
|
|
644
650
|
}
|
|
651
|
+
// Under a coarse primary pointer, a tap or a drag during the entrance
|
|
652
|
+
// reaches nothing. Keys and assistive technology stay live. The hold
|
|
653
|
+
// ends with the entrance, or with this effect's cleanup on a close or
|
|
654
|
+
// unmount.
|
|
655
|
+
return holdEntranceInput(dialog) ?? undefined;
|
|
645
656
|
} else {
|
|
646
657
|
if (dialog.open) {
|
|
647
658
|
dialog.close();
|
|
@@ -172,7 +172,7 @@ export const docs = {
|
|
|
172
172
|
usage: {
|
|
173
173
|
anatomy,
|
|
174
174
|
description:
|
|
175
|
-
'A side panel that floats above page content for inspectors and detail views: the "click a table row, see its details" pattern. Unlike a docked panel it overlays the layout instead of reflowing it. Works on desktop and touch: the width budget applies on desktop and the panel preserves a 56px page reveal below 640px without exceeding the width budget. Escape closes the drawer (unless purpose is required) and focus returns to the element that opened it. Entry/exit slide animation respects prefers-reduced-motion. Stacking contract: sibling drawers stack last-opened on top, Escape closes only the topmost, and closing peels innermost-first; render them as siblings, never nested.\n\nLike Dialog, the drawer is a container: its content area is inset by --spacing-4 (16px) by default, by the padding prop, or by a theme\'s padding on drawer. Compose the header, body, and footer the way Dialog does, with a Layout: DrawerHeader in the header slot (it shows a close button when you pass it onOpenChange, like DialogHeader), the body in LayoutContent, and actions in LayoutFooter. The Layout redistributes the inset to those regions so they line up on one content line, and the header is not inset twice. A lone Section child also escapes the inset. Drawer renders no close button of its own, so give every drawer a visible way to close, especially a non-modal one, which has no scrim to click. Put data-autofocus on the control that should receive focus on open.\n\nChoosing a surface: use Dialog for a centered decision or short form, Drawer for full-height side detail that keeps the page in sight, BottomSheet for block-axis sheets on touch, and a docked panel (a layout column) when content should reflow the page instead of floating over it.\n\nTheming: the panel is the single stable target (astryx-drawer, with data-side reflecting side), and the scrim is the panel\'s native ::backdrop. DrawerHeader exposes astryx-drawer-header targets for its row, slots, and close icon.',
|
|
175
|
+
'A side panel that floats above page content for inspectors and detail views: the "click a table row, see its details" pattern. Unlike a docked panel it overlays the layout instead of reflowing it. Works on desktop and touch: the width budget applies on desktop and the panel preserves a 56px page reveal below 640px without exceeding the width budget. Escape closes the drawer (unless purpose is required) and focus returns to the element that opened it. Entry/exit slide animation respects prefers-reduced-motion. On a phone or tablet (a coarse primary pointer), a tap or a drag that lands while the drawer is still sliding in reaches nothing (for a drawer without a scrim, only one on the panel: the page stays live), and the drawer finishes sliding in; keys and assistive technology stay live, and a mouse or a trackpad still clicks: at once when it was the last pointer used, otherwise once it moves. Stacking contract: sibling drawers stack last-opened on top, Escape closes only the topmost, and closing peels innermost-first; render them as siblings, never nested.\n\nLike Dialog, the drawer is a container: its content area is inset by --spacing-4 (16px) by default, by the padding prop, or by a theme\'s padding on drawer. Compose the header, body, and footer the way Dialog does, with a Layout: DrawerHeader in the header slot (it shows a close button when you pass it onOpenChange, like DialogHeader), the body in LayoutContent, and actions in LayoutFooter. The Layout redistributes the inset to those regions so they line up on one content line, and the header is not inset twice. A lone Section child also escapes the inset. Drawer renders no close button of its own, so give every drawer a visible way to close, especially a non-modal one, which has no scrim to click. Put data-autofocus on the control that should receive focus on open.\n\nChoosing a surface: use Dialog for a centered decision or short form, Drawer for full-height side detail that keeps the page in sight, BottomSheet for block-axis sheets on touch, and a docked panel (a layout column) when content should reflow the page instead of floating over it.\n\nTheming: the panel is the single stable target (astryx-drawer, with data-side reflecting side), and the scrim is the panel\'s native ::backdrop. DrawerHeader exposes astryx-drawer-header targets for its row, slots, and close icon.',
|
|
176
176
|
bestPractices: [
|
|
177
177
|
{
|
|
178
178
|
guidance: true,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useDrawerDialogPresence.ts
|
|
7
7
|
* @input Controlled open state, modal mode, dialog ref, and rendered-state setter
|
|
8
|
-
* @output Coordinates native top-layer presence, exit timing, focus restoration, and unmount cleanup
|
|
8
|
+
* @output Coordinates native top-layer presence, the touch entrance's input hold, exit timing, focus restoration, and unmount cleanup
|
|
9
9
|
* @position Drawer-internal hook; consumed only by Drawer.tsx
|
|
10
10
|
*
|
|
11
11
|
* The drawer has two independent notions of presence:
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*
|
|
22
22
|
* SYNC: When modified, update:
|
|
23
23
|
* - /packages/core/src/Drawer/Drawer.test.tsx
|
|
24
|
+
* - /packages/core/src/Drawer/__tests__/DrawerTouchEntrance.interaction.browser.spec.ts
|
|
24
25
|
* - /.github/scripts/modal-close-visibility.js
|
|
25
26
|
*/
|
|
26
27
|
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
type SetStateAction,
|
|
33
34
|
} from 'react';
|
|
34
35
|
import {flushSync} from 'react-dom';
|
|
36
|
+
import {holdEntranceInput} from '../Layer/holdEntranceInput';
|
|
35
37
|
import {notifyTopLayerEntry} from '../Layer/modalOutlet';
|
|
36
38
|
import {runPopoverOperation} from '../Layer/popoverOperations';
|
|
37
39
|
|
|
@@ -149,7 +151,12 @@ export function useDrawerDialogPresence({
|
|
|
149
151
|
// contract as Dialog).
|
|
150
152
|
dialog.querySelector<HTMLElement>('[data-autofocus]')?.focus();
|
|
151
153
|
}
|
|
152
|
-
|
|
154
|
+
// Under a coarse primary pointer, a tap or a drag during the slide-in
|
|
155
|
+
// reaches nothing. A non-modal drawer holds only its panel, so its page
|
|
156
|
+
// stays live. Keys and assistive technology stay live. The hold ends
|
|
157
|
+
// with the slide-in, or with this effect's cleanup on a close or
|
|
158
|
+
// unmount.
|
|
159
|
+
return holdEntranceInput(dialog, {isModal}) ?? undefined;
|
|
153
160
|
}
|
|
154
161
|
|
|
155
162
|
if (!isDrawerHostOpen(dialog, isModal)) {
|
|
@@ -201,8 +201,7 @@ export const docs = {
|
|
|
201
201
|
name: 'presentation',
|
|
202
202
|
type: "'popover' | 'bottom-sheet' | 'adaptive'",
|
|
203
203
|
description:
|
|
204
|
-
"Presentation surface for data-driven items. 'popover' stays anchored, 'bottom-sheet' always renders the actions in a modal BottomSheet, and 'adaptive' uses a BottomSheet on compact coarse-pointer layouts while remaining anchored elsewhere. Compound children currently support popover only.",
|
|
205
|
-
default: "'popover'",
|
|
204
|
+
"Presentation surface for data-driven items. 'popover' stays anchored, 'bottom-sheet' always renders the actions in a modal BottomSheet, and 'adaptive' uses a BottomSheet on compact coarse-pointer layouts while remaining anchored elsewhere. Compound children currently support popover only. Omitted, the menu is anchored, and under a finger, on every device, the finger that opened it picks a row only after moving 10 px. Pass 'popover' to keep the pointer behavior on every device, the settle rule included.",
|
|
206
205
|
},
|
|
207
206
|
{
|
|
208
207
|
name: 'isMenuOpen',
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
|
|
71
71
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
72
72
|
import {useListFocus} from '../hooks/useListFocus';
|
|
73
|
-
import {
|
|
73
|
+
import {useDropdownMenuPress} from '../hooks/useMenuPress';
|
|
74
74
|
import type {MenuPressPointerType} from '../hooks/menuPressGesture';
|
|
75
75
|
import {useTypeahead} from '../hooks/useTypeahead';
|
|
76
76
|
import {useFocusReturnVisibility} from '../hooks/useFocusReturnVisibility';
|
|
@@ -460,7 +460,10 @@ interface DropdownMenuDataProps extends DropdownMenuBaseProps {
|
|
|
460
460
|
* Surface used to present the actions. Pass a value selected by product
|
|
461
461
|
* policy (for example, a compact-touch media query) when the presentation
|
|
462
462
|
* should adapt. Bottom-sheet presentation is intended for short action sets.
|
|
463
|
-
*
|
|
463
|
+
* Omitted, the menu is the anchored popover. Under a finger, on every
|
|
464
|
+
* device, the finger that opened it picks as UIMenu's does, once it has
|
|
465
|
+
* moved. `'popover'` pins the anchored popover with the pointer behavior on
|
|
466
|
+
* every device (the opening press follows the settle rule).
|
|
464
467
|
*/
|
|
465
468
|
presentation?: DropdownMenuPresentation;
|
|
466
469
|
items: DropdownMenuOption[];
|
|
@@ -468,6 +471,11 @@ interface DropdownMenuDataProps extends DropdownMenuBaseProps {
|
|
|
468
471
|
}
|
|
469
472
|
|
|
470
473
|
interface DropdownMenuCompoundProps extends DropdownMenuBaseProps {
|
|
474
|
+
/**
|
|
475
|
+
* `'popover'` pins the pointer behavior on every device (the opening press
|
|
476
|
+
* follows the settle rule). Omitted, on every device, the finger
|
|
477
|
+
* that opened the menu picks as UIMenu's does, once it has moved.
|
|
478
|
+
*/
|
|
471
479
|
presentation?: 'popover';
|
|
472
480
|
items?: undefined;
|
|
473
481
|
children: ReactNode;
|
|
@@ -881,6 +889,7 @@ function DropdownMenuPopover({
|
|
|
881
889
|
placement = 'below',
|
|
882
890
|
alignment = 'start',
|
|
883
891
|
presentation: _presentation,
|
|
892
|
+
pressTouchModel = 'uikit',
|
|
884
893
|
header,
|
|
885
894
|
footer,
|
|
886
895
|
className,
|
|
@@ -888,7 +897,13 @@ function DropdownMenuPopover({
|
|
|
888
897
|
xstyle,
|
|
889
898
|
'data-testid': testId,
|
|
890
899
|
...props
|
|
891
|
-
}: DropdownMenuProps
|
|
900
|
+
}: DropdownMenuProps & {
|
|
901
|
+
/**
|
|
902
|
+
* How a finger's press runs, on every device: UIMenu's, unless the caller
|
|
903
|
+
* pins `presentation="popover"`.
|
|
904
|
+
*/
|
|
905
|
+
pressTouchModel?: 'pointer' | 'uikit';
|
|
906
|
+
}) {
|
|
892
907
|
const t = useComponentTranslator();
|
|
893
908
|
const button = buttonFromProps ?? {label: t(DEFAULT_BUTTON_I18N_KEY)};
|
|
894
909
|
|
|
@@ -1359,12 +1374,15 @@ function DropdownMenuPopover({
|
|
|
1359
1374
|
// The press model: the row under a release acts, the highlight follows a
|
|
1360
1375
|
// held pointer. A mouse released outside the menu dismisses it; a finger
|
|
1361
1376
|
// released outside leaves it open, so the hook never calls onDismiss then.
|
|
1362
|
-
const menuPress =
|
|
1377
|
+
const menuPress = useDropdownMenuPress({
|
|
1363
1378
|
menuRef: listRef,
|
|
1364
1379
|
triggerRef: buttonRef,
|
|
1365
1380
|
itemSelector: MENU_ITEM_SELECTOR,
|
|
1366
1381
|
onTriggerPress: handleTriggerPress,
|
|
1367
1382
|
onDismiss: closeMenu,
|
|
1383
|
+
// Under a finger, on every device, the opening finger picks as UIMenu's
|
|
1384
|
+
// does. `presentation="popover"` pins the pointer model.
|
|
1385
|
+
touchModel: pressTouchModel,
|
|
1368
1386
|
});
|
|
1369
1387
|
cancelMenuPressRef.current = menuPress.cancel;
|
|
1370
1388
|
|
|
@@ -1786,7 +1804,13 @@ export function DropdownMenu(props: DropdownMenuProps) {
|
|
|
1786
1804
|
);
|
|
1787
1805
|
}
|
|
1788
1806
|
|
|
1789
|
-
return
|
|
1807
|
+
return (
|
|
1808
|
+
<DropdownMenuPopover
|
|
1809
|
+
{...sharedProps}
|
|
1810
|
+
presentation="popover"
|
|
1811
|
+
pressTouchModel={props.presentation === 'popover' ? 'pointer' : 'uikit'}
|
|
1812
|
+
/>
|
|
1813
|
+
);
|
|
1790
1814
|
}
|
|
1791
1815
|
|
|
1792
1816
|
DropdownMenu.displayName = 'DropdownMenu';
|
|
@@ -38,6 +38,7 @@ import {useIndicator} from '../Indicator';
|
|
|
38
38
|
import {Item} from '../Item';
|
|
39
39
|
import {useDropdownMenuContext} from './DropdownMenuContext';
|
|
40
40
|
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
41
|
+
import {menuPressLitRowStyle} from '../privateAttributes/menuPressLit.stylex';
|
|
41
42
|
import {focusMenuItemOnHover} from './menuItemHover';
|
|
42
43
|
import {colorVars, focusVars, spacingVars} from '../theme/tokens.stylex';
|
|
43
44
|
import {composeEventHandlers, mergeProps, themeProps} from '../utils';
|
|
@@ -50,7 +51,9 @@ const styles = stylex.create({
|
|
|
50
51
|
color: colorVars['--color-text-primary'],
|
|
51
52
|
backgroundColor: {
|
|
52
53
|
default: 'transparent',
|
|
53
|
-
|
|
54
|
+
// menuPressLitRowStyle sets the variable on the row a held finger or pen
|
|
55
|
+
// lights.
|
|
56
|
+
':focus': `var(--_menu-row-focus-paint, ${colorVars['--color-overlay-hover']})`,
|
|
54
57
|
},
|
|
55
58
|
cursor: {
|
|
56
59
|
default: 'pointer',
|
|
@@ -231,6 +234,7 @@ export function DropdownMenuCheckboxItem({
|
|
|
231
234
|
xstyle={[
|
|
232
235
|
focusOutlineStyles.focusVisible,
|
|
233
236
|
styles.root,
|
|
237
|
+
menuPressLitRowStyle,
|
|
234
238
|
isDisabled && styles.disabled,
|
|
235
239
|
xstyle,
|
|
236
240
|
]}
|
|
@@ -49,6 +49,7 @@ import {composeEventHandlers, mergeProps} from '../utils';
|
|
|
49
49
|
import type {BaseProps} from '../BaseProps';
|
|
50
50
|
import {useDropdownMenuContext} from './DropdownMenuContext';
|
|
51
51
|
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
52
|
+
import {menuPressLitRowStyle} from '../privateAttributes/menuPressLit.stylex';
|
|
52
53
|
import {focusMenuItemOnHover} from './menuItemHover';
|
|
53
54
|
import {isModifiedClick} from './menuItemRoles';
|
|
54
55
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -66,14 +67,16 @@ const menuItemStyles = stylex.create({
|
|
|
66
67
|
color: colorVars['--color-text-primary'],
|
|
67
68
|
backgroundColor: {
|
|
68
69
|
default: 'transparent',
|
|
69
|
-
|
|
70
|
+
// menuPressLitRowStyle sets the variable on the row a held finger or pen
|
|
71
|
+
// lights.
|
|
72
|
+
':focus': `var(--_menu-row-focus-paint, ${colorVars['--color-overlay-hover']})`,
|
|
70
73
|
// The pressed look only where hover exists. Under a finger the row a
|
|
71
74
|
// press BEGAN on would otherwise stay painted while the highlight (focus)
|
|
72
|
-
// moves to the row under the finger — two rows lit.
|
|
75
|
+
// moves to the row under the finger — two rows lit. A held finger or pen
|
|
76
|
+
// sets the variable on the menu. Then only the lit row keeps the look.
|
|
73
77
|
'@media (hover: hover)': {
|
|
74
78
|
default: null,
|
|
75
|
-
':active:where(:not(:disabled,[aria-disabled="true"]))':
|
|
76
|
-
colorVars['--color-overlay-pressed'],
|
|
79
|
+
':active:where(:not(:disabled,[aria-disabled="true"]))': `var(--_menu-row-active-paint, ${colorVars['--color-overlay-pressed']})`,
|
|
77
80
|
},
|
|
78
81
|
},
|
|
79
82
|
borderWidth: 0,
|
|
@@ -303,6 +306,7 @@ export function DropdownMenuItem({
|
|
|
303
306
|
xstyle={[
|
|
304
307
|
focusOutlineStyles.focusVisible,
|
|
305
308
|
menuItemStyles.root,
|
|
309
|
+
menuPressLitRowStyle,
|
|
306
310
|
itemSizeStyles[menuSize],
|
|
307
311
|
isDestructive && menuItemStyles.destructive,
|
|
308
312
|
isDisabled && menuItemStyles.disabled,
|
|
@@ -34,6 +34,7 @@ import {useIndicator} from '../Indicator';
|
|
|
34
34
|
import {Item} from '../Item';
|
|
35
35
|
import {useDropdownMenuContext} from './DropdownMenuContext';
|
|
36
36
|
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
37
|
+
import {menuPressLitRowStyle} from '../privateAttributes/menuPressLit.stylex';
|
|
37
38
|
import {focusMenuItemOnHover} from './menuItemHover';
|
|
38
39
|
import {colorVars, focusVars, spacingVars} from '../theme/tokens.stylex';
|
|
39
40
|
import {composeEventHandlers, mergeProps, themeProps} from '../utils';
|
|
@@ -47,7 +48,9 @@ const styles = stylex.create({
|
|
|
47
48
|
color: colorVars['--color-text-primary'],
|
|
48
49
|
backgroundColor: {
|
|
49
50
|
default: 'transparent',
|
|
50
|
-
|
|
51
|
+
// menuPressLitRowStyle sets the variable on the row a held finger or pen
|
|
52
|
+
// lights.
|
|
53
|
+
':focus': `var(--_menu-row-focus-paint, ${colorVars['--color-overlay-hover']})`,
|
|
51
54
|
},
|
|
52
55
|
cursor: {
|
|
53
56
|
default: 'pointer',
|
|
@@ -218,6 +221,7 @@ export function DropdownMenuRadioItem({
|
|
|
218
221
|
xstyle={[
|
|
219
222
|
focusOutlineStyles.focusVisible,
|
|
220
223
|
styles.root,
|
|
224
|
+
menuPressLitRowStyle,
|
|
221
225
|
isDisabled && styles.disabled,
|
|
222
226
|
xstyle,
|
|
223
227
|
]}
|
|
@@ -99,6 +99,7 @@ import {
|
|
|
99
99
|
rtlStyles,
|
|
100
100
|
} from '../utils';
|
|
101
101
|
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
102
|
+
import {menuPressLitRowStyle} from '../privateAttributes/menuPressLit.stylex';
|
|
102
103
|
import {getInteractionModality} from '../utils/interactionModality';
|
|
103
104
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
104
105
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
@@ -146,7 +147,9 @@ const triggerStyles = stylex.create({
|
|
|
146
147
|
color: colorVars['--color-text-primary'],
|
|
147
148
|
backgroundColor: {
|
|
148
149
|
default: 'transparent',
|
|
149
|
-
|
|
150
|
+
// menuPressLitRowStyle sets the variable on the row a held finger or pen
|
|
151
|
+
// lights.
|
|
152
|
+
':focus': `var(--_menu-row-focus-paint, ${colorVars['--color-overlay-hover']})`,
|
|
150
153
|
},
|
|
151
154
|
borderWidth: 0,
|
|
152
155
|
borderStyle: 'none',
|
|
@@ -1243,6 +1246,7 @@ export function DropdownMenuSubMenu(
|
|
|
1243
1246
|
xstyle={[
|
|
1244
1247
|
focusOutlineStyles.focusVisible,
|
|
1245
1248
|
triggerStyles.root,
|
|
1249
|
+
menuPressLitRowStyle,
|
|
1246
1250
|
triggerSizeStyles[menuSize],
|
|
1247
1251
|
isOpen && triggerStyles.open,
|
|
1248
1252
|
isDisabled && triggerStyles.disabled,
|