@astryxdesign/core 0.7.7-canary.4a8a76e → 0.7.7-canary.4e495ec
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/useCloseMobileDrawerOnActivate.d.ts +13 -0
- package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts.map +1 -0
- package/dist/AppShell/useCloseMobileDrawerOnActivate.js +45 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +79 -9
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +1 -1
- package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
- package/dist/BottomSheet/bottomSheetStack.js +5 -4
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +2 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +7 -3
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +7 -1
- package/dist/CheckboxList/CheckboxListContext.d.ts +6 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +10 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +5 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +5 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +16 -14
- package/dist/Drawer/Drawer.d.ts.map +1 -1
- package/dist/Drawer/Drawer.js +15 -11
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
- package/dist/Drawer/useDrawerDialogPresence.js +5 -1
- package/dist/Field/PanelSearchInput.d.ts +9 -1
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +3 -1
- package/dist/InfoTip/InfoTip.d.ts +2 -2
- package/dist/InfoTip/InfoTip.js +4 -4
- package/dist/Layer/layerAnimations.stylex.d.ts +7 -8
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +25 -20
- package/dist/Layer/useBackdropPress.d.ts +27 -0
- package/dist/Layer/useBackdropPress.d.ts.map +1 -0
- package/dist/Layer/useBackdropPress.js +53 -0
- package/dist/Layer/usePressPin.d.ts +3 -5
- package/dist/Layer/usePressPin.d.ts.map +1 -1
- package/dist/Layer/usePressPin.js +4 -6
- package/dist/MobileNav/MobileNav.js +0 -1
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +2 -4
- package/dist/Outline/Outline.d.ts +2 -4
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +4 -52
- package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/useOutlineFromMarkdown.js +1 -0
- package/dist/Outline/useScrollSpy.js +3 -3
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +134 -179
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +5 -0
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +118 -260
- package/dist/Resizable/resizeGesture.d.ts +91 -0
- package/dist/Resizable/resizeGesture.d.ts.map +1 -0
- package/dist/Resizable/resizeGesture.js +251 -0
- package/dist/SegmentedControl/SegmentedControl.d.ts +3 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +115 -8
- package/dist/SegmentedControl/SegmentedControlContext.d.ts +6 -0
- package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -0
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +8 -1
- package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts +357 -0
- package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts.map +1 -0
- package/dist/SegmentedControl/useSegmentedSelectionMotion.js +1349 -0
- package/dist/SegmentedControl/useSegmentedTouchTracking.d.ts +70 -0
- package/dist/SegmentedControl/useSegmentedTouchTracking.d.ts.map +1 -0
- package/dist/SegmentedControl/useSegmentedTouchTracking.js +264 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +96 -67
- package/dist/Selector/optionShape.d.ts +24 -0
- package/dist/Selector/optionShape.d.ts.map +1 -0
- package/dist/Selector/optionShape.js +48 -0
- package/dist/Selector/selectableOptionEntries.d.ts +20 -0
- package/dist/Selector/selectableOptionEntries.d.ts.map +1 -0
- package/dist/Selector/selectableOptionEntries.js +38 -0
- package/dist/Selector/utils.d.ts +2 -17
- package/dist/Selector/utils.d.ts.map +1 -1
- package/dist/Selector/utils.js +3 -51
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +3 -9
- package/dist/Switch/switchTouch.d.ts +0 -7
- package/dist/Switch/switchTouch.d.ts.map +1 -1
- package/dist/Switch/switchTouch.js +2 -10
- package/dist/Switch/useSwitchTouch.d.ts +2 -11
- package/dist/Switch/useSwitchTouch.d.ts.map +1 -1
- package/dist/Switch/useSwitchTouch.js +26 -85
- package/dist/Token/Token.d.ts +2 -2
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +12 -4
- package/dist/Token/TokenNamingContext.d.ts +14 -0
- package/dist/Token/TokenNamingContext.d.ts.map +1 -0
- package/dist/Token/TokenNamingContext.js +23 -0
- package/dist/Tokenizer/Tokenizer.d.ts +46 -59
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +35 -58
- package/dist/Tokenizer/TokenizerBottomSheet.d.ts +25 -28
- package/dist/Tokenizer/TokenizerBottomSheet.d.ts.map +1 -1
- package/dist/Tokenizer/TokenizerBottomSheet.js +157 -54
- package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -1
- package/dist/Tokenizer/TokenizerItemRemovableContext.js +5 -3
- package/dist/Tooltip/Tooltip.d.ts +25 -30
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +24 -5
- package/dist/Tooltip/index.d.ts +1 -1
- package/dist/Tooltip/index.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +28 -29
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +20 -8
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +4 -7
- package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuItem.js +4 -7
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +4 -7
- package/dist/astryx.css +76 -5
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +53 -5
- package/dist/hooks/actionGroupTracker.d.ts +3 -3
- package/dist/hooks/actionGroupTracker.d.ts.map +1 -1
- package/dist/hooks/actionGroupTracker.js +6 -53
- package/dist/hooks/useHighlightedOptionScroll.d.ts +11 -1
- package/dist/hooks/useHighlightedOptionScroll.d.ts.map +1 -1
- package/dist/hooks/useHighlightedOptionScroll.js +112 -11
- package/dist/hooks/useMediaQuery.d.ts +2 -1
- package/dist/hooks/useMediaQuery.d.ts.map +1 -1
- package/dist/hooks/useMediaQuery.js +7 -4
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +4 -2
- package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.js +1 -0
- package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.js +7 -1
- 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/internal/SheetConfirmation.d.ts +11 -0
- package/dist/internal/SheetConfirmation.d.ts.map +1 -0
- package/dist/internal/SheetConfirmation.js +92 -0
- package/dist/internal/sheetDraft.d.ts +35 -0
- package/dist/internal/sheetDraft.d.ts.map +1 -0
- package/dist/internal/sheetDraft.js +65 -0
- package/dist/internal/touch-sheet.d.ts +21 -0
- package/dist/internal/touch-sheet.d.ts.map +1 -0
- package/dist/internal/touch-sheet.js +19 -0
- package/dist/internal/useDismissSearchOnTouchDrag.d.ts +24 -0
- package/dist/internal/useDismissSearchOnTouchDrag.d.ts.map +1 -0
- package/dist/internal/useDismissSearchOnTouchDrag.js +56 -0
- package/dist/motion/roles.d.ts +7 -1
- package/dist/motion/roles.d.ts.map +1 -1
- package/dist/motion/roles.js +20 -1
- package/dist/motion/uikit/index.d.ts +6 -5
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +6 -5
- package/dist/motion/uikit/segmented.d.ts +55 -0
- package/dist/motion/uikit/segmented.d.ts.map +1 -0
- package/dist/motion/uikit/segmented.js +77 -0
- package/dist/motion/uikit/springs.d.ts +0 -18
- package/dist/motion/uikit/springs.d.ts.map +1 -1
- package/dist/motion/uikit/springs.js +5 -21
- package/dist/privateAttributes/segmentTitle.stylex.d.ts +5 -0
- package/dist/privateAttributes/segmentTitle.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/segmentTitle.stylex.js +22 -0
- package/dist/utils/browserClickWait.d.ts +35 -0
- package/dist/utils/browserClickWait.d.ts.map +1 -0
- package/dist/utils/browserClickWait.js +123 -0
- package/dist/utils/clickGuard.d.ts +8 -1
- package/dist/utils/clickGuard.d.ts.map +1 -1
- package/dist/utils/clickGuard.js +24 -1
- package/locales/en.json +4 -0
- package/locales/pseudo.json +3 -0
- package/package.json +8 -8
- package/src/AppShell/useCloseMobileDrawerOnActivate.ts +52 -0
- package/src/BottomSheet/BottomSheet.tsx +95 -10
- package/src/BottomSheet/BottomSheetPanel.tsx +7 -2
- package/src/BottomSheet/bottomSheetStack.ts +8 -4
- package/src/Breadcrumbs/BreadcrumbItem.tsx +13 -3
- package/src/CheckboxList/CheckboxList.doc.mjs +4 -2
- package/src/CheckboxList/CheckboxList.tsx +6 -0
- package/src/CheckboxList/CheckboxListContext.tsx +6 -0
- package/src/CheckboxList/CheckboxListItem.tsx +11 -2
- package/src/DateInput/DateInput.tsx +8 -2
- package/src/DateTimeInput/DateTimeInput.tsx +8 -2
- package/src/Dialog/Dialog.tsx +44 -14
- package/src/Drawer/Drawer.tsx +28 -11
- package/src/Drawer/useDrawerDialogPresence.ts +5 -1
- package/src/Field/PanelSearchInput.tsx +11 -0
- package/src/InfoTip/InfoTip.doc.mjs +3 -3
- package/src/InfoTip/InfoTip.tsx +5 -5
- package/src/Layer/layerAnimations.stylex.ts +24 -16
- package/src/Layer/useBackdropPress.ts +81 -0
- package/src/Layer/usePressPin.ts +5 -11
- package/src/MobileNav/MobileNav.tsx +1 -1
- package/src/MultiSelector/hooks.ts +2 -4
- package/src/Outline/Outline.doc.mjs +177 -51
- package/src/Outline/Outline.tsx +5 -57
- package/src/Outline/useOutlineFromMarkdown.doc.mjs +157 -0
- package/src/Outline/useOutlineFromMarkdown.ts +1 -0
- package/src/Outline/useScrollSpy.ts +3 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +3 -3
- package/src/PowerSearch/PowerSearch.tsx +196 -183
- package/src/RadioList/RadioList.doc.mjs +4 -2
- package/src/RadioList/RadioList.tsx +4 -0
- package/src/Resizable/ResizeHandle.tsx +133 -328
- package/src/Resizable/resizeGesture.ts +368 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +3 -3
- package/src/SegmentedControl/SegmentedControl.tsx +179 -5
- package/src/SegmentedControl/SegmentedControlContext.ts +6 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +12 -1
- package/src/SegmentedControl/useSegmentedSelectionMotion.ts +1637 -0
- package/src/SegmentedControl/useSegmentedTouchTracking.ts +308 -0
- package/src/Selector/Selector.tsx +136 -89
- package/src/Selector/optionShape.ts +64 -0
- package/src/Selector/selectableOptionEntries.ts +39 -0
- package/src/Selector/utils.ts +9 -68
- package/src/SideNav/SideNavItem.tsx +3 -7
- package/src/Switch/switchTouch.ts +2 -10
- package/src/Switch/useSwitchTouch.ts +26 -96
- package/src/Token/Token.tsx +13 -4
- package/src/Token/TokenNamingContext.ts +28 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +16 -0
- package/src/Tokenizer/Tokenizer.tsx +81 -86
- package/src/Tokenizer/TokenizerBottomSheet.tsx +219 -93
- package/src/Tokenizer/TokenizerItemRemovableContext.ts +5 -3
- package/src/Tooltip/Tooltip.doc.mjs +123 -54
- package/src/Tooltip/Tooltip.tsx +36 -39
- package/src/Tooltip/index.ts +0 -1
- package/src/Tooltip/useTooltip.doc.mjs +147 -33
- package/src/Tooltip/useTooltip.tsx +39 -32
- package/src/TopNav/TopNavItem.tsx +6 -5
- package/src/TopNav/TopNavMegaMenuItem.tsx +6 -5
- package/src/TopNav/TopNavMenu.tsx +6 -5
- package/src/gestures/GestureHost.ts +73 -4
- package/src/hooks/actionGroupTracker.ts +12 -56
- package/src/hooks/useHighlightedOptionScroll.ts +123 -15
- package/src/hooks/useMediaQuery.doc.mjs +2 -2
- package/src/hooks/useMediaQuery.ts +13 -4
- package/src/hooks/useMenuPress.ts +8 -2
- package/src/i18n/generated-english/PowerSearch/PowerSearch.generated.ts +1 -0
- package/src/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.ts +7 -1
- package/src/i18n/generated-locales/en.generated.ts +1 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
- package/src/internal/SheetConfirmation.tsx +101 -0
- package/src/internal/sheetDraft.ts +80 -0
- package/src/internal/touch-sheet.ts +21 -0
- package/src/internal/useDismissSearchOnTouchDrag.ts +69 -0
- package/src/motion/MEASUREMENTS.md +7 -0
- package/src/motion/README.md +1 -0
- package/src/motion/roles.ts +20 -1
- package/src/motion/uikit/index.ts +5 -5
- package/src/motion/uikit/segmented.ts +74 -0
- package/src/motion/uikit/springs.ts +5 -21
- package/src/privateAttributes/segmentTitle.stylex.ts +22 -0
- package/src/utils/browserClickWait.ts +133 -0
- package/src/utils/clickGuard.ts +37 -1
- package/dist/internal/tokenizer-presentation.d.ts +0 -21
- package/dist/internal/tokenizer-presentation.d.ts.map +0 -1
- package/dist/internal/tokenizer-presentation.js +0 -21
- package/src/internal/tokenizer-presentation.ts +0 -25
|
@@ -24,6 +24,7 @@ import {colorVars} from '../theme/tokens.stylex';
|
|
|
24
24
|
import type {BaseProps} from '../BaseProps';
|
|
25
25
|
import {CheckboxInput} from '../CheckboxInput/CheckboxInput';
|
|
26
26
|
import type {CheckboxInputProps} from '../CheckboxInput/CheckboxInput';
|
|
27
|
+
import type {InputStatus} from '../Field/types';
|
|
27
28
|
import {ListItem} from '../List/ListItem';
|
|
28
29
|
import {ListContext} from '../List/ListContext';
|
|
29
30
|
import {CheckboxListContext} from './CheckboxListContext';
|
|
@@ -149,6 +150,8 @@ export interface CheckboxListItemProps extends BaseProps<HTMLLIElement> {
|
|
|
149
150
|
// Component
|
|
150
151
|
// =============================================================================
|
|
151
152
|
|
|
153
|
+
const ERROR_STATUS: InputStatus = {type: 'error'};
|
|
154
|
+
|
|
152
155
|
/**
|
|
153
156
|
* The row's visible description is the checkbox's accessible description. Item
|
|
154
157
|
* renders that element and owns its id, and publishes the id through
|
|
@@ -167,11 +170,17 @@ function DescribedCheckboxInput(
|
|
|
167
170
|
props: Omit<CheckboxInputProps, 'aria-describedby'>,
|
|
168
171
|
) {
|
|
169
172
|
const describedBy = use(ItemDescriptionContext);
|
|
170
|
-
const
|
|
173
|
+
const listContext = use(CheckboxListContext);
|
|
171
174
|
return (
|
|
172
175
|
<CheckboxInput
|
|
173
176
|
{...props}
|
|
174
|
-
aria-describedby={joinAriaIDs(
|
|
177
|
+
aria-describedby={joinAriaIDs(
|
|
178
|
+
describedBy ?? undefined,
|
|
179
|
+
listContext?.disabledMessageID,
|
|
180
|
+
)}
|
|
181
|
+
// The status has no message, so CheckboxInput renders nothing. The list
|
|
182
|
+
// renders the message.
|
|
183
|
+
status={listContext?.isInvalid ? ERROR_STATUS : undefined}
|
|
175
184
|
/>
|
|
176
185
|
);
|
|
177
186
|
}
|
|
@@ -884,12 +884,18 @@ function PointerDateField({
|
|
|
884
884
|
if (!isEffectivelyDisabled) {
|
|
885
885
|
popover.show({skipAutoFocus: true});
|
|
886
886
|
}
|
|
887
|
-
} else if (
|
|
887
|
+
} else if (
|
|
888
|
+
e.key === 'Enter' &&
|
|
889
|
+
(pendingInput !== null || isEffectivelyDisabled)
|
|
890
|
+
) {
|
|
891
|
+
// Typed text holds Enter back, or a form would submit the value from
|
|
892
|
+
// before the commit. A disabled or busy field must not submit its
|
|
893
|
+
// form either. Otherwise Enter keeps its default, as in any text field.
|
|
888
894
|
e.preventDefault();
|
|
889
895
|
commitPendingInput();
|
|
890
896
|
}
|
|
891
897
|
},
|
|
892
|
-
[popover, commitPendingInput, isEffectivelyDisabled],
|
|
898
|
+
[popover, commitPendingInput, isEffectivelyDisabled, pendingInput],
|
|
893
899
|
);
|
|
894
900
|
|
|
895
901
|
const inputWrapper = (
|
|
@@ -1143,12 +1143,18 @@ function PointerDateTimeField({
|
|
|
1143
1143
|
if (!isEffectivelyDisabled) {
|
|
1144
1144
|
popover.show({skipAutoFocus: true});
|
|
1145
1145
|
}
|
|
1146
|
-
} else if (
|
|
1146
|
+
} else if (
|
|
1147
|
+
e.key === 'Enter' &&
|
|
1148
|
+
(datePendingInput !== null || isEffectivelyDisabled)
|
|
1149
|
+
) {
|
|
1150
|
+
// Typed text holds Enter back, or a form would submit the value from
|
|
1151
|
+
// before the commit. A disabled or busy field must not submit its
|
|
1152
|
+
// form either. Otherwise Enter keeps its default, as in any text field.
|
|
1147
1153
|
e.preventDefault();
|
|
1148
1154
|
commitDatePendingInput();
|
|
1149
1155
|
}
|
|
1150
1156
|
},
|
|
1151
|
-
[popover, commitDatePendingInput, isEffectivelyDisabled],
|
|
1157
|
+
[popover, commitDatePendingInput, isEffectivelyDisabled, datePendingInput],
|
|
1152
1158
|
);
|
|
1153
1159
|
|
|
1154
1160
|
// --- Time-option popover (#2727) ---
|
package/src/Dialog/Dialog.tsx
CHANGED
|
@@ -52,6 +52,7 @@ import {LayerContentBoundary} from '../Layer/layerScopedContext';
|
|
|
52
52
|
import {layerClickBoundary} from '../Layer/layerClickBoundary';
|
|
53
53
|
import {LayerDepthProvider} from '../Layer/LayerDepthContext';
|
|
54
54
|
import {useLayerDismissal} from '../Layer/useLayerDismissal';
|
|
55
|
+
import {useBackdropPress} from '../Layer/useBackdropPress';
|
|
55
56
|
import {useModalOutlet} from '../Layer/modalOutlet';
|
|
56
57
|
import {
|
|
57
58
|
colorVars,
|
|
@@ -84,6 +85,8 @@ import {focusForLastInput, isFocusLost} from '../utils/focusReturn';
|
|
|
84
85
|
import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
|
|
85
86
|
|
|
86
87
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
88
|
+
|
|
89
|
+
type PointerHandler = (event: React.PointerEvent<HTMLDialogElement>) => void;
|
|
87
90
|
/**
|
|
88
91
|
* Calculate a directional translate offset for dialog entry animation.
|
|
89
92
|
* Returns a normalized vector from the trigger element toward the viewport
|
|
@@ -186,8 +189,22 @@ const styles = stylex.create({
|
|
|
186
189
|
position: 'fixed',
|
|
187
190
|
margin: 'auto',
|
|
188
191
|
padding: 0,
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
// The backdrop does not blur the page. Forced colors override the
|
|
193
|
+
// backdrop's colour, so a 1px border keeps the modal separate from the
|
|
194
|
+
// page. It is a border, not an outline: the focus ring
|
|
195
|
+
// (focusOutlineProps) owns the outline longhands on this element.
|
|
196
|
+
borderWidth: {
|
|
197
|
+
default: 0,
|
|
198
|
+
'@media (forced-colors: active)': 1,
|
|
199
|
+
},
|
|
200
|
+
borderStyle: {
|
|
201
|
+
default: 'none',
|
|
202
|
+
'@media (forced-colors: active)': 'solid',
|
|
203
|
+
},
|
|
204
|
+
borderColor: {
|
|
205
|
+
default: null,
|
|
206
|
+
'@media (forced-colors: active)': 'CanvasText',
|
|
207
|
+
},
|
|
191
208
|
backgroundColor: colorVars['--color-background-surface'],
|
|
192
209
|
'--_dialog-radius': radiusVars['--radius-container'],
|
|
193
210
|
borderRadius: 'var(--_dialog-radius)',
|
|
@@ -233,11 +250,10 @@ const styles = stylex.create({
|
|
|
233
250
|
openSettled: {
|
|
234
251
|
animationName: 'none',
|
|
235
252
|
},
|
|
236
|
-
//
|
|
253
|
+
// No backdrop-filter: a full-viewport blur is costly to repaint.
|
|
237
254
|
backdrop: {
|
|
238
255
|
'::backdrop': {
|
|
239
256
|
backgroundColor: colorVars['--color-overlay'],
|
|
240
|
-
backdropFilter: 'blur(2px)',
|
|
241
257
|
},
|
|
242
258
|
},
|
|
243
259
|
fullscreen: {
|
|
@@ -246,6 +262,8 @@ const styles = stylex.create({
|
|
|
246
262
|
maxWidth: '100dvw',
|
|
247
263
|
maxHeight: '100dvh',
|
|
248
264
|
borderRadius: 0,
|
|
265
|
+
// Nothing of the page shows around a full-screen dialog: no edge.
|
|
266
|
+
borderWidth: 0,
|
|
249
267
|
margin: 0,
|
|
250
268
|
inset: 0,
|
|
251
269
|
},
|
|
@@ -723,16 +741,16 @@ export function Dialog({
|
|
|
723
741
|
}
|
|
724
742
|
}, [isOpen, isInline, hasConsumerName, titleId]);
|
|
725
743
|
|
|
726
|
-
//
|
|
727
|
-
// the event target is the <dialog> element itself; clicks on child content
|
|
728
|
-
// always target the child. This avoids false positives from native browser
|
|
744
|
+
// A press on the ::backdrop targets the <dialog> itself, so native browser
|
|
729
745
|
// popups (date pickers, selects, color pickers) that render outside the
|
|
730
|
-
// dialog's
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
746
|
+
// dialog's rect never count. The press must start and end there.
|
|
747
|
+
const handleBackdropPress = useCallback(() => {
|
|
748
|
+
onOpenChange(false);
|
|
749
|
+
}, [onOpenChange]);
|
|
750
|
+
const backdropPress = useBackdropPress(
|
|
751
|
+
handleBackdropPress,
|
|
752
|
+
allowBackdropClick,
|
|
753
|
+
);
|
|
736
754
|
|
|
737
755
|
// The native `cancel` event is the browser's own close-watcher firing: an
|
|
738
756
|
// Android back gesture, or a close request the stack never saw a press for.
|
|
@@ -890,8 +908,20 @@ export function Dialog({
|
|
|
890
908
|
// written.
|
|
891
909
|
{...layerClickBoundary({
|
|
892
910
|
...safeProps,
|
|
893
|
-
onClick: chainEventHandlers(
|
|
911
|
+
onClick: chainEventHandlers(backdropPress.onClick, onClickProp),
|
|
894
912
|
})}
|
|
913
|
+
onPointerDownCapture={chainEventHandlers(
|
|
914
|
+
backdropPress.onPointerDownCapture,
|
|
915
|
+
safeProps.onPointerDownCapture as PointerHandler | undefined,
|
|
916
|
+
)}
|
|
917
|
+
onPointerUpCapture={chainEventHandlers(
|
|
918
|
+
backdropPress.onPointerUpCapture,
|
|
919
|
+
safeProps.onPointerUpCapture as PointerHandler | undefined,
|
|
920
|
+
)}
|
|
921
|
+
onPointerCancelCapture={chainEventHandlers(
|
|
922
|
+
backdropPress.onPointerCancelCapture,
|
|
923
|
+
safeProps.onPointerCancelCapture as PointerHandler | undefined,
|
|
924
|
+
)}
|
|
895
925
|
onCancel={handleCancel}
|
|
896
926
|
aria-modal={isHydrating ? undefined : 'true'}
|
|
897
927
|
// The default aria-labelledby (from a DialogHeader title) is set
|
package/src/Drawer/Drawer.tsx
CHANGED
|
@@ -78,9 +78,11 @@ import {layerTextReset} from '../Layer/layerTextReset.stylex';
|
|
|
78
78
|
import {layerStructureReset} from '../Layer/layerStructureReset.stylex';
|
|
79
79
|
import {LayerContentBoundary} from '../Layer/layerScopedContext';
|
|
80
80
|
import {useModalOutlet} from '../Layer/modalOutlet';
|
|
81
|
+
import {useBackdropPress} from '../Layer/useBackdropPress';
|
|
81
82
|
import {layerClickBoundary} from '../Layer/layerClickBoundary';
|
|
82
83
|
import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
|
|
83
84
|
import {composeEventHandlers, mergeProps, themeProps} from '../utils';
|
|
85
|
+
import {chainEventHandlers} from '../utils/chainEventHandlers';
|
|
84
86
|
import {container, overlayPaddingReset} from '../Layout';
|
|
85
87
|
import {
|
|
86
88
|
containerPaddingBlockEndVarStyles,
|
|
@@ -92,6 +94,8 @@ import {
|
|
|
92
94
|
import type {SpacingStep, SpacingToken} from '../Layout';
|
|
93
95
|
import {useDrawerDialogPresence} from './useDrawerDialogPresence';
|
|
94
96
|
|
|
97
|
+
type PointerHandler = (event: React.PointerEvent<HTMLDialogElement>) => void;
|
|
98
|
+
|
|
95
99
|
// =============================================================================
|
|
96
100
|
// Styles
|
|
97
101
|
// =============================================================================
|
|
@@ -218,8 +222,8 @@ const styles = stylex.create({
|
|
|
218
222
|
// Scrim via the browser's ::backdrop pseudo-element (top layer).
|
|
219
223
|
scrim: {
|
|
220
224
|
'::backdrop': {
|
|
225
|
+
// No backdrop-filter: a full-viewport blur is costly to repaint.
|
|
221
226
|
backgroundColor: colorVars['--color-overlay'],
|
|
222
|
-
backdropFilter: 'blur(2px)',
|
|
223
227
|
opacity: 0,
|
|
224
228
|
transitionProperty: 'opacity',
|
|
225
229
|
transitionDuration: durationVars['--duration-medium'],
|
|
@@ -543,15 +547,14 @@ export function Drawer({
|
|
|
543
547
|
[allowEscape, handleDismiss, shouldDismissOnCloseRequest],
|
|
544
548
|
);
|
|
545
549
|
|
|
546
|
-
//
|
|
547
|
-
//
|
|
548
|
-
const
|
|
549
|
-
(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
[allowScrimClick, hasScrim, onOpenChange],
|
|
550
|
+
// A press on the ::backdrop targets the <dialog> itself (the content area
|
|
551
|
+
// fills the panel). The press must start and end there.
|
|
552
|
+
const handleScrimPress = useCallback(() => {
|
|
553
|
+
onOpenChange(false);
|
|
554
|
+
}, [onOpenChange]);
|
|
555
|
+
const scrimPress = useBackdropPress(
|
|
556
|
+
handleScrimPress,
|
|
557
|
+
hasScrim && allowScrimClick,
|
|
555
558
|
);
|
|
556
559
|
|
|
557
560
|
const widthValue = typeof width === 'number' ? `${width}px` : width;
|
|
@@ -612,8 +615,22 @@ export function Drawer({
|
|
|
612
615
|
// written.
|
|
613
616
|
{...layerClickBoundary({
|
|
614
617
|
...safeProps,
|
|
615
|
-
onClick: composeEventHandlers(onClickProp,
|
|
618
|
+
onClick: composeEventHandlers(onClickProp, scrimPress.onClick),
|
|
616
619
|
})}
|
|
620
|
+
// The hook records where the press started first. The caller's capture
|
|
621
|
+
// handler then runs, also when a child prevented default.
|
|
622
|
+
onPointerDownCapture={chainEventHandlers(
|
|
623
|
+
scrimPress.onPointerDownCapture,
|
|
624
|
+
safeProps.onPointerDownCapture as PointerHandler | undefined,
|
|
625
|
+
)}
|
|
626
|
+
onPointerUpCapture={chainEventHandlers(
|
|
627
|
+
scrimPress.onPointerUpCapture,
|
|
628
|
+
safeProps.onPointerUpCapture as PointerHandler | undefined,
|
|
629
|
+
)}
|
|
630
|
+
onPointerCancelCapture={chainEventHandlers(
|
|
631
|
+
scrimPress.onPointerCancelCapture,
|
|
632
|
+
safeProps.onPointerCancelCapture as PointerHandler | undefined,
|
|
633
|
+
)}
|
|
617
634
|
onKeyDown={onKeyDownProp}
|
|
618
635
|
onCancel={handleCancel}>
|
|
619
636
|
<ModalAnnounceProvider dialogRef={dialogRef} isOpen={isOpen && hasScrim}>
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from 'react';
|
|
34
34
|
import {flushSync} from 'react-dom';
|
|
35
35
|
import {notifyTopLayerEntry} from '../Layer/modalOutlet';
|
|
36
|
+
import {runPopoverOperation} from '../Layer/popoverOperations';
|
|
36
37
|
|
|
37
38
|
/** Slack past the computed transition before the backstop gives up waiting. */
|
|
38
39
|
const EXIT_BACKSTOP_BUFFER_MS = 50;
|
|
@@ -71,7 +72,10 @@ function showDrawerHost(dialog: HTMLDialogElement, isModal: boolean): void {
|
|
|
71
72
|
if (isModal) {
|
|
72
73
|
dialog.showModal();
|
|
73
74
|
} else if (typeof dialog.showPopover === 'function') {
|
|
74
|
-
dialog.
|
|
75
|
+
// The dialog's show focuses its first control. A tooltip on that control
|
|
76
|
+
// then asks to show. The browser refuses a show inside another show.
|
|
77
|
+
// Counting this show makes the tooltip wait until it ends.
|
|
78
|
+
runPopoverOperation(dialog.ownerDocument, () => dialog.showPopover());
|
|
75
79
|
// The drawer is now the newest top-layer entry: global hosts shown before
|
|
76
80
|
// it (the toast viewport) re-enter above it, as they do above a modal.
|
|
77
81
|
notifyTopLayerEntry();
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
} from '../theme/tokens.stylex';
|
|
59
59
|
import type {BaseProps} from '../BaseProps';
|
|
60
60
|
import {textEntryTraitAttributes} from './textEntryTraits';
|
|
61
|
+
import {inputWrapperStyles} from './inputStyles.stylex';
|
|
61
62
|
|
|
62
63
|
const styles = stylex.create({
|
|
63
64
|
// Gutter only. Matches the option list's own padding so the field below
|
|
@@ -185,6 +186,14 @@ export interface PanelSearchInputProps extends Omit<
|
|
|
185
186
|
* the field to the same height as its rows.
|
|
186
187
|
*/
|
|
187
188
|
fieldXstyle?: StyleXStyles;
|
|
189
|
+
/**
|
|
190
|
+
* Draw the field as the shared input box — the input border, accent while
|
|
191
|
+
* focused, surface and radius — instead of the borderless panel row. A
|
|
192
|
+
* touch sheet is a full-width surface, not a bordered panel, so its field
|
|
193
|
+
* reads as a form input.
|
|
194
|
+
* @default false
|
|
195
|
+
*/
|
|
196
|
+
hasInputBox?: boolean;
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
/**
|
|
@@ -208,6 +217,7 @@ export function PanelSearchInput({
|
|
|
208
217
|
onBlur,
|
|
209
218
|
onContainerKeyDown,
|
|
210
219
|
fieldXstyle,
|
|
220
|
+
hasInputBox = false,
|
|
211
221
|
xstyle,
|
|
212
222
|
className,
|
|
213
223
|
style,
|
|
@@ -268,6 +278,7 @@ export function PanelSearchInput({
|
|
|
268
278
|
{...stylex.props(
|
|
269
279
|
styles.field,
|
|
270
280
|
isKeyboardFocus && styles.fieldKeyboardFocus,
|
|
281
|
+
hasInputBox && inputWrapperStyles.base,
|
|
271
282
|
fieldXstyle,
|
|
272
283
|
)}>
|
|
273
284
|
<Icon icon="search" size="sm" color="secondary" xstyle={styles.icon} />
|
|
@@ -66,7 +66,7 @@ export const docs = {
|
|
|
66
66
|
{
|
|
67
67
|
guidance: true,
|
|
68
68
|
description:
|
|
69
|
-
'Rely on InfoTip rather than a hand-rolled info button on touch: it
|
|
69
|
+
'Rely on InfoTip rather than a hand-rolled info button on touch: it pins its help on a press, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
guidance: false,
|
|
@@ -131,7 +131,7 @@ export const docsZh = {
|
|
|
131
131
|
{
|
|
132
132
|
guidance: true,
|
|
133
133
|
description:
|
|
134
|
-
'Rely on InfoTip rather than a hand-rolled info button on touch: it
|
|
134
|
+
'Rely on InfoTip rather than a hand-rolled info button on touch: it pins its help on a press, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
guidance: false,
|
|
@@ -168,7 +168,7 @@ export const docsDense = {
|
|
|
168
168
|
{
|
|
169
169
|
guidance: true,
|
|
170
170
|
description:
|
|
171
|
-
'Rely on InfoTip rather than a hand-rolled info button on touch: it
|
|
171
|
+
'Rely on InfoTip rather than a hand-rolled info button on touch: it pins its help on a press, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
guidance: false,
|
package/src/InfoTip/InfoTip.tsx
CHANGED
|
@@ -23,7 +23,7 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
23
23
|
import type {BaseProps} from '../BaseProps';
|
|
24
24
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
25
25
|
import {Icon, type IconSize} from '../Icon/Icon';
|
|
26
|
-
import {
|
|
26
|
+
import {TooltipWithTriggerBehavior} from '../Tooltip/Tooltip';
|
|
27
27
|
import {
|
|
28
28
|
registerEnglishFallback,
|
|
29
29
|
useComponentTranslator,
|
|
@@ -122,8 +122,8 @@ const styles = stylex.create({
|
|
|
122
122
|
* labels, values, and metrics for permission notes, metric definitions, and
|
|
123
123
|
* field help.
|
|
124
124
|
*
|
|
125
|
-
* InfoTip is
|
|
126
|
-
*
|
|
125
|
+
* InfoTip is a press-to-pin Tooltip on a named button: Tooltip opens, pins,
|
|
126
|
+
* announces, and closes the help. The info icon resolves from
|
|
127
127
|
* the icon registry, so themes can replace it.
|
|
128
128
|
*
|
|
129
129
|
* @example
|
|
@@ -157,7 +157,7 @@ export function InfoTip({
|
|
|
157
157
|
// stack takes Escape, so one press never closes both the help and an
|
|
158
158
|
// enclosing Dialog.
|
|
159
159
|
return (
|
|
160
|
-
<
|
|
160
|
+
<TooltipWithTriggerBehavior
|
|
161
161
|
content={content}
|
|
162
162
|
// The tooltip is the button's only purpose. A click, a tap, Enter, or
|
|
163
163
|
// Space pins it open, and the press stops at the button. So an InfoTip's
|
|
@@ -179,7 +179,7 @@ export function InfoTip({
|
|
|
179
179
|
)}>
|
|
180
180
|
<Icon icon="info" size={size} />
|
|
181
181
|
</button>
|
|
182
|
-
</
|
|
182
|
+
</TooltipWithTriggerBehavior>
|
|
183
183
|
);
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file layerAnimations.stylex.ts
|
|
5
|
-
* @input Uses StyleX, theme duration/easing tokens
|
|
5
|
+
* @input Uses StyleX, theme duration/easing tokens, the Reduce Motion fade
|
|
6
|
+
* constants
|
|
6
7
|
* @output Exports shared entry animation styles for layer-based components
|
|
7
8
|
* @position Shared animation primitives; consumed by DropdownMenu, Popover,
|
|
8
9
|
* HoverCard, Tooltip, Selector, and any component using useLayer
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
*/
|
|
28
29
|
|
|
29
30
|
import * as stylex from '@stylexjs/stylex';
|
|
31
|
+
import {reducedMotionFadeConsts} from '../motion/reducedMotion.stylex';
|
|
30
32
|
import {durationVars, easeVars, spacingVars} from '../theme/tokens.stylex';
|
|
31
33
|
|
|
32
34
|
const enterBelow = stylex.keyframes({
|
|
@@ -84,17 +86,21 @@ const enterStartRtl = stylex.keyframes({
|
|
|
84
86
|
|
|
85
87
|
// Reduce Motion under a finger: the same entrance without its travel. A touch
|
|
86
88
|
// presentation substitutes a cross-fade for motion, never a jump.
|
|
89
|
+
// The fade's curve rides in its first keyframe, so the published class type
|
|
90
|
+
// of `animationTimingFunction` stays the theme ease's.
|
|
87
91
|
const fadeIn = stylex.keyframes({
|
|
88
|
-
from: {opacity: 0},
|
|
92
|
+
from: {opacity: 0, animationTimingFunction: reducedMotionFadeConsts.easing},
|
|
89
93
|
to: {opacity: 1},
|
|
90
94
|
});
|
|
91
95
|
|
|
96
|
+
// No theme value lengthens the Reduce Motion fade, so its values are constants.
|
|
97
|
+
// The media keys stay literal: StyleX gives an imported at-rule key a later
|
|
98
|
+
// priority layer (see reducedMotionKeys).
|
|
92
99
|
const animationBase = {
|
|
93
100
|
animationDuration: {
|
|
94
101
|
default: durationVars['--duration-fast-max'],
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
'@media (prefers-reduced-motion: reduce) and (pointer: coarse)': `min(${durationVars['--duration-fast-max']}, 200ms)`,
|
|
102
|
+
'@media (prefers-reduced-motion: reduce) and (pointer: coarse)':
|
|
103
|
+
reducedMotionFadeConsts.duration,
|
|
98
104
|
},
|
|
99
105
|
animationTimingFunction: easeVars['--ease-standard'],
|
|
100
106
|
animationFillMode: 'backwards' as const,
|
|
@@ -106,19 +112,18 @@ const animationBase = {
|
|
|
106
112
|
* Keyed by LayerPlacement ('above' | 'below' | 'start' | 'end')
|
|
107
113
|
* for easy lookup: `layerAnimations[placement]`.
|
|
108
114
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* (design:reduced-motion DR2, DR3), as iOS does for its menus, popovers and
|
|
115
|
-
* tips.
|
|
115
|
+
* Under `prefers-reduced-motion: reduce` each entry drops its travel and
|
|
116
|
+
* scale. Under a finger (the primary pointer is coarse) the layer fades in,
|
|
117
|
+
* opacity only, in 200 ms on easeInOut: on a touch screen Reduce Motion
|
|
118
|
+
* replaces motion with a cross-fade rather than a jump; under any other
|
|
119
|
+
* pointer it appears at once.
|
|
116
120
|
*/
|
|
117
121
|
export const layerAnimations = stylex.create({
|
|
118
122
|
below: {
|
|
119
123
|
animationName: {
|
|
120
124
|
default: enterBelow,
|
|
121
|
-
'@media (prefers-reduced-motion: reduce)
|
|
125
|
+
'@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
|
|
126
|
+
'none',
|
|
122
127
|
'@media (prefers-reduced-motion: reduce) and (pointer: coarse)': fadeIn,
|
|
123
128
|
},
|
|
124
129
|
...animationBase,
|
|
@@ -126,7 +131,8 @@ export const layerAnimations = stylex.create({
|
|
|
126
131
|
above: {
|
|
127
132
|
animationName: {
|
|
128
133
|
default: enterAbove,
|
|
129
|
-
'@media (prefers-reduced-motion: reduce)
|
|
134
|
+
'@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
|
|
135
|
+
'none',
|
|
130
136
|
'@media (prefers-reduced-motion: reduce) and (pointer: coarse)': fadeIn,
|
|
131
137
|
},
|
|
132
138
|
...animationBase,
|
|
@@ -135,7 +141,8 @@ export const layerAnimations = stylex.create({
|
|
|
135
141
|
animationName: {
|
|
136
142
|
default: enterEnd,
|
|
137
143
|
':is([dir="rtl"] *)': enterEndRtl,
|
|
138
|
-
'@media (prefers-reduced-motion: reduce)
|
|
144
|
+
'@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
|
|
145
|
+
'none',
|
|
139
146
|
'@media (prefers-reduced-motion: reduce) and (pointer: coarse)': fadeIn,
|
|
140
147
|
},
|
|
141
148
|
...animationBase,
|
|
@@ -144,7 +151,8 @@ export const layerAnimations = stylex.create({
|
|
|
144
151
|
animationName: {
|
|
145
152
|
default: enterStart,
|
|
146
153
|
':is([dir="rtl"] *)': enterStartRtl,
|
|
147
|
-
'@media (prefers-reduced-motion: reduce)
|
|
154
|
+
'@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
|
|
155
|
+
'none',
|
|
148
156
|
'@media (prefers-reduced-motion: reduce) and (pointer: coarse)': fadeIn,
|
|
149
157
|
},
|
|
150
158
|
...animationBase,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useBackdropPress.ts
|
|
7
|
+
* @input A modal layer's backdrop dismissal and whether it is allowed now
|
|
8
|
+
* @output Capture-phase pointer handlers and a click handler for the layer's
|
|
9
|
+
* <dialog> root
|
|
10
|
+
* @position Shared backdrop-press rule for Dialog and Drawer
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {useCallback, useRef, type PointerEvent, type MouseEvent} from 'react';
|
|
14
|
+
|
|
15
|
+
export interface BackdropPressHandlers {
|
|
16
|
+
onPointerDownCapture: (event: PointerEvent<HTMLElement>) => void;
|
|
17
|
+
onPointerUpCapture: (event: PointerEvent<HTMLElement>) => void;
|
|
18
|
+
onPointerCancelCapture: (event: PointerEvent<HTMLElement>) => void;
|
|
19
|
+
onClick: (event: MouseEvent<HTMLElement>) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A press on a modal layer's backdrop targets the <dialog> root itself. A
|
|
24
|
+
* click there is also what the browser sends when a press starts on one side
|
|
25
|
+
* of the layer's edge and ends on the other (it goes to the nearest common
|
|
26
|
+
* ancestor), so a backdrop press dismisses only when its pointer-down and its
|
|
27
|
+
* pointer-up both landed on the backdrop. A cancelled pointer ends the press.
|
|
28
|
+
*
|
|
29
|
+
* The pointer handlers run in the capture phase. A nested layer, such as a
|
|
30
|
+
* popover open in the dialog, stops its presses at its own root, so a
|
|
31
|
+
* bubble-phase handler here does not see them.
|
|
32
|
+
*/
|
|
33
|
+
export function useBackdropPress(
|
|
34
|
+
onBackdropPress: () => void,
|
|
35
|
+
isEnabled: boolean,
|
|
36
|
+
): BackdropPressHandlers {
|
|
37
|
+
const startedOnBackdropRef = useRef(false);
|
|
38
|
+
const endedOnBackdropRef = useRef(false);
|
|
39
|
+
|
|
40
|
+
const onPointerDownCapture = useCallback(
|
|
41
|
+
(event: PointerEvent<HTMLElement>) => {
|
|
42
|
+
startedOnBackdropRef.current = event.target === event.currentTarget;
|
|
43
|
+
endedOnBackdropRef.current = false;
|
|
44
|
+
},
|
|
45
|
+
[],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const onPointerUpCapture = useCallback((event: PointerEvent<HTMLElement>) => {
|
|
49
|
+
endedOnBackdropRef.current =
|
|
50
|
+
startedOnBackdropRef.current && event.target === event.currentTarget;
|
|
51
|
+
startedOnBackdropRef.current = false;
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
const onPointerCancelCapture = useCallback(() => {
|
|
55
|
+
startedOnBackdropRef.current = false;
|
|
56
|
+
endedOnBackdropRef.current = false;
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const onClick = useCallback(
|
|
60
|
+
(event: MouseEvent<HTMLElement>) => {
|
|
61
|
+
const pressWasOnBackdrop = endedOnBackdropRef.current;
|
|
62
|
+
startedOnBackdropRef.current = false;
|
|
63
|
+
endedOnBackdropRef.current = false;
|
|
64
|
+
if (
|
|
65
|
+
isEnabled &&
|
|
66
|
+
pressWasOnBackdrop &&
|
|
67
|
+
event.target === event.currentTarget
|
|
68
|
+
) {
|
|
69
|
+
onBackdropPress();
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
[isEnabled, onBackdropPress],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
onPointerDownCapture,
|
|
77
|
+
onPointerUpCapture,
|
|
78
|
+
onPointerCancelCapture,
|
|
79
|
+
onClick,
|
|
80
|
+
};
|
|
81
|
+
}
|
package/src/Layer/usePressPin.ts
CHANGED
|
@@ -35,12 +35,10 @@ import {
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* How the trigger opens the layer. `adaptive`, the default, previews it on
|
|
38
|
-
* hover and focus, and
|
|
39
|
-
*
|
|
40
|
-
* and focus, and no touch or pen tap opens it. `toggle` pins it on every
|
|
41
|
-
* press of a button trigger.
|
|
38
|
+
* hover and focus, and `touchTrigger` decides taps. `toggle` pins it on every
|
|
39
|
+
* press of a button trigger. Internal: InfoTip's press-to-pin uses `toggle`.
|
|
42
40
|
*/
|
|
43
|
-
export type LayerTriggerBehavior = 'adaptive' | '
|
|
41
|
+
export type LayerTriggerBehavior = 'adaptive' | 'toggle';
|
|
44
42
|
|
|
45
43
|
/** The clicks a press pin answered, so the wrapper can stop them (`stopPressPinClick`). */
|
|
46
44
|
const pressPinClicks = new WeakSet<Event>();
|
|
@@ -214,11 +212,7 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
|
|
|
214
212
|
clearTapOpen,
|
|
215
213
|
armOutsideDismiss,
|
|
216
214
|
} = useTouchTriggerInternal({
|
|
217
|
-
touchTrigger: isToggleMode
|
|
218
|
-
? 'tap'
|
|
219
|
-
: triggerBehavior === 'hover'
|
|
220
|
-
? 'none'
|
|
221
|
-
: options.touchTrigger,
|
|
215
|
+
touchTrigger: isToggleMode ? 'tap' : options.touchTrigger,
|
|
222
216
|
isEnabled: options.isEnabled,
|
|
223
217
|
isControlled: isOpen !== undefined,
|
|
224
218
|
isOpen: isLayerOpen,
|
|
@@ -466,7 +460,7 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
|
|
|
466
460
|
const isWired = isToggleMode && offeredKind === 'button';
|
|
467
461
|
useDevWarning(
|
|
468
462
|
options.component,
|
|
469
|
-
'
|
|
463
|
+
'A press-to-pin tooltip pins only a button trigger (a <button> or role="button"); this trigger keeps tap-only opening.',
|
|
470
464
|
isToggleMode && offeredKind === 'other',
|
|
471
465
|
);
|
|
472
466
|
|
|
@@ -143,8 +143,8 @@ const styles = stylex.create({
|
|
|
143
143
|
// ::backdrop is provided by the browser's top layer
|
|
144
144
|
backdrop: {
|
|
145
145
|
'::backdrop': {
|
|
146
|
+
// No backdrop-filter: a full-viewport blur is costly to repaint.
|
|
146
147
|
backgroundColor: colorVars['--color-overlay'],
|
|
147
|
-
backdropFilter: 'blur(2px)',
|
|
148
148
|
opacity: 0,
|
|
149
149
|
transitionProperty: 'opacity',
|
|
150
150
|
transitionDuration: durationVars['--duration-medium'],
|
|
@@ -174,13 +174,11 @@ export function useMultiCombobox({
|
|
|
174
174
|
// The action cell is the caller's control, usable whether or not the
|
|
175
175
|
// option itself is disabled (spec:AST-058 FR4).
|
|
176
176
|
if (cell === 'action') {
|
|
177
|
-
highlightOnHover(index);
|
|
178
|
-
setHighlightedCell('action');
|
|
177
|
+
highlightOnHover(index, () => setHighlightedCell('action'));
|
|
179
178
|
return;
|
|
180
179
|
}
|
|
181
180
|
if (!item.disabled) {
|
|
182
|
-
highlightOnHover(index);
|
|
183
|
-
setHighlightedCell('option');
|
|
181
|
+
highlightOnHover(index, () => setHighlightedCell('option'));
|
|
184
182
|
}
|
|
185
183
|
},
|
|
186
184
|
[highlightOnHover],
|