@astryxdesign/core 0.7.7-canary.bf14580 → 0.7.7-canary.c19ca12
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/AlertDialog/AlertDialog.d.ts +4 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +36 -6
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +76 -6
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +23 -4
- package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
- package/dist/BottomSheet/bottomSheetStack.js +5 -4
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +3 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +47 -3
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +13 -2
- package/dist/ComplexSelector/ComplexSelector.d.ts +4 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +14 -4
- package/dist/ContextMenu/ContextMenu.js +2 -2
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +13 -27
- package/dist/DateInput/Wheel.d.ts.map +1 -1
- package/dist/DateInput/Wheel.js +22 -41
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +7 -31
- package/dist/Field/Field.js +2 -2
- package/dist/HoverCard/HoverCard.d.ts +4 -3
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +27 -2
- package/dist/HoverCard/useHoverCard.d.ts +5 -4
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +17 -31
- package/dist/Icon/Icon.d.ts +5 -1
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +5 -2
- 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/usePressPin.d.ts +17 -5
- package/dist/Layer/usePressPin.d.ts.map +1 -1
- package/dist/Layer/usePressPin.js +62 -12
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +18 -0
- package/dist/PowerSearch/PowerSearch.d.ts +36 -3
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +522 -269
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +100 -0
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -0
- package/dist/PowerSearch/PowerSearchBottomSheet.js +1913 -0
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts +32 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +17 -6
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts +38 -3
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +170 -61
- package/dist/PowerSearch/formatFilterValue.d.ts +7 -0
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +11 -1
- package/dist/PowerSearch/formatTokenValue.d.ts +28 -0
- package/dist/PowerSearch/formatTokenValue.d.ts.map +1 -0
- package/dist/PowerSearch/formatTokenValue.js +160 -0
- package/dist/PowerSearch/powerSearchSheetUtils.d.ts +65 -0
- package/dist/PowerSearch/powerSearchSheetUtils.d.ts.map +1 -0
- package/dist/PowerSearch/powerSearchSheetUtils.js +168 -0
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +3 -2
- package/dist/RadioList/RadioListItem.d.ts +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +5 -3
- package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts.map +1 -1
- package/dist/SegmentedControl/useSegmentedSelectionMotion.js +3 -3
- package/dist/Slider/sliderTouchController.js +2 -2
- package/dist/Spinner/Spinner.d.ts +1 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +60 -58
- package/dist/Switch/useSwitchTouch.js +4 -4
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +29 -8
- package/dist/TextInput/TextInput.d.ts +2 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +10 -7
- package/dist/Toast/Toast.d.ts +11 -1
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +25 -15
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +94 -34
- package/dist/Toast/types.d.ts +9 -3
- package/dist/Toast/types.d.ts.map +1 -1
- 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/Tooltip/useTooltip.d.ts +19 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +34 -53
- package/dist/Typeahead/TypeaheadItem.d.ts +2 -1
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/animator/createAnimator.d.ts +98 -0
- package/dist/animator/createAnimator.d.ts.map +1 -0
- package/dist/animator/createAnimator.js +259 -0
- package/dist/astryx.css +22 -6
- package/dist/gestures/GestureHost.d.ts +27 -1
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +90 -8
- package/dist/gestures/PressTracker.d.ts.map +1 -1
- package/dist/gestures/PressTracker.js +4 -3
- package/dist/hooks/actionGroupTracker.d.ts +4 -1
- package/dist/hooks/actionGroupTracker.d.ts.map +1 -1
- package/dist/hooks/actionGroupTracker.js +13 -3
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/pressGroupContext.d.ts +6 -0
- package/dist/hooks/pressGroupContext.d.ts.map +1 -0
- package/dist/hooks/pressGroupContext.js +20 -0
- package/dist/hooks/useActionGroupTracking.d.ts +53 -0
- package/dist/hooks/useActionGroupTracking.d.ts.map +1 -0
- package/dist/hooks/useActionGroupTracking.js +174 -0
- package/dist/hooks/useAnimator.d.ts +41 -0
- package/dist/hooks/useAnimator.d.ts.map +1 -0
- package/dist/hooks/useAnimator.js +70 -0
- package/dist/hooks/useAttachment.d.ts +1 -1
- package/dist/hooks/useAttachment.js +1 -1
- package/dist/hooks/useInputStatusIcon.d.ts +1 -1
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +2 -2
- package/dist/hooks/useLongPress.d.ts +6 -2
- package/dist/hooks/useLongPress.d.ts.map +1 -1
- package/dist/hooks/useLongPress.js +95 -15
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +4 -2
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts +8 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +42 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.js +6 -0
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts +8 -0
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.js +14 -0
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +30 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +30 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/internal/indicatorPress.stylex.d.ts +20 -0
- package/dist/internal/indicatorPress.stylex.d.ts.map +1 -0
- package/dist/internal/indicatorPress.stylex.js +26 -0
- package/dist/motion/animator.d.ts +14 -14
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +41 -18
- package/dist/motion/reducedMotion.d.ts +24 -0
- package/dist/motion/reducedMotion.d.ts.map +1 -1
- package/dist/motion/reducedMotion.js +78 -1
- package/dist/motion/uikit/alert.d.ts +41 -0
- package/dist/motion/uikit/alert.d.ts.map +1 -0
- package/dist/motion/uikit/alert.js +43 -0
- package/dist/privateAttributes/pressGroupItem.stylex.d.ts +15 -0
- package/dist/privateAttributes/pressGroupItem.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/pressGroupItem.stylex.js +23 -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/dist/utils/dateParser.d.ts.map +1 -1
- package/dist/utils/dateParser.js +12 -0
- package/dist/utils/focusReturn.d.ts +15 -0
- package/dist/utils/focusReturn.d.ts.map +1 -1
- package/dist/utils/focusReturn.js +28 -0
- package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +3 -2
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +19 -6
- package/dist/utils/triggerFocus.d.ts +36 -0
- package/dist/utils/triggerFocus.d.ts.map +1 -0
- package/dist/utils/triggerFocus.js +65 -0
- package/locales/en.json +120 -0
- package/locales/pseudo.json +90 -0
- package/package.json +9 -4
- package/src/AlertDialog/AlertDialog.doc.mjs +2 -2
- package/src/AlertDialog/AlertDialog.tsx +37 -5
- package/src/BottomSheet/BottomSheet.tsx +85 -5
- package/src/BottomSheet/BottomSheetPanel.tsx +40 -5
- package/src/BottomSheet/bottomSheetStack.ts +8 -4
- package/src/BottomSheet/useSheetGestures.ts +3 -1
- package/src/Button/Button.tsx +63 -5
- package/src/CheckboxInput/CheckboxInput.doc.mjs +18 -0
- package/src/CheckboxInput/CheckboxInput.tsx +36 -9
- package/src/ComplexSelector/ComplexSelector.tsx +20 -5
- package/src/ContextMenu/ContextMenu.tsx +2 -2
- package/src/DateInput/MonthScroller.tsx +18 -22
- package/src/DateInput/Wheel.tsx +30 -32
- package/src/Dialog/Dialog.tsx +10 -31
- package/src/Field/Field.tsx +2 -2
- package/src/HoverCard/HoverCard.doc.mjs +4 -2
- package/src/HoverCard/HoverCard.tsx +35 -4
- package/src/HoverCard/useHoverCard.doc.mjs +178 -36
- package/src/HoverCard/useHoverCard.tsx +46 -52
- package/src/Icon/Icon.doc.mjs +49 -8
- package/src/Icon/Icon.tsx +15 -7
- package/src/Layer/layerAnimations.stylex.ts +24 -16
- package/src/Layer/usePressPin.ts +81 -15
- package/src/Lightbox/Lightbox.tsx +18 -0
- package/src/PowerSearch/PowerSearch.doc.mjs +54 -5
- package/src/PowerSearch/PowerSearch.tsx +734 -288
- package/src/PowerSearch/PowerSearchBottomSheet.tsx +2515 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +28 -6
- package/src/PowerSearch/PowerSearchValueEditor.tsx +203 -38
- package/src/PowerSearch/formatFilterValue.ts +18 -1
- package/src/PowerSearch/formatTokenValue.ts +215 -0
- package/src/PowerSearch/powerSearchSheetUtils.ts +224 -0
- package/src/RadioList/RadioList.tsx +3 -2
- package/src/RadioList/RadioListItem.tsx +5 -3
- package/src/SegmentedControl/useSegmentedSelectionMotion.ts +6 -3
- package/src/Slider/sliderTouchController.ts +2 -2
- package/src/Spinner/Spinner.tsx +77 -63
- package/src/Switch/useSwitchTouch.ts +4 -4
- package/src/Table/BaseTable.tsx +46 -12
- package/src/TextInput/TextInput.tsx +11 -10
- package/src/Toast/Toast.doc.mjs +4 -3
- package/src/Toast/Toast.tsx +52 -32
- package/src/Toast/ToastViewport.tsx +121 -35
- package/src/Toast/types.ts +9 -3
- package/src/Token/Token.tsx +13 -4
- package/src/Token/TokenNamingContext.ts +28 -0
- package/src/Tooltip/useTooltip.doc.mjs +24 -0
- package/src/Tooltip/useTooltip.tsx +61 -56
- package/src/Typeahead/TypeaheadItem.doc.mjs +6 -3
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/animator/createAnimator.doc.mjs +153 -0
- package/src/animator/createAnimator.ts +324 -0
- package/src/gestures/GestureHost.ts +105 -10
- package/src/gestures/PressTracker.ts +4 -3
- package/src/gestures/README.md +7 -1
- package/src/hooks/actionGroupTracker.ts +20 -5
- package/src/hooks/index.ts +2 -0
- package/src/hooks/pressGroupContext.ts +21 -0
- package/src/hooks/useActionGroupTracking.ts +224 -0
- package/src/hooks/useAnimator.doc.mjs +109 -0
- package/src/hooks/useAnimator.ts +67 -0
- package/src/hooks/useAttachment.ts +1 -1
- package/src/hooks/useInputStatusIcon.tsx +3 -2
- package/src/hooks/useLongPress.doc.mjs +2 -2
- package/src/hooks/useLongPress.ts +136 -15
- package/src/hooks/useMenuPress.ts +8 -2
- package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +43 -0
- package/src/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.ts +6 -0
- package/src/i18n/generated-english/PowerSearch/formatTokenValue.generated.ts +15 -0
- package/src/i18n/generated-locales/en.generated.ts +41 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +42 -0
- package/src/index.ts +13 -0
- package/src/internal/indicatorPress.stylex.ts +24 -0
- package/src/motion/README.md +8 -5
- package/src/motion/animator.ts +61 -29
- package/src/motion/reducedMotion.ts +79 -1
- package/src/motion/uikit/alert.ts +43 -0
- package/src/privateAttributes/pressGroupItem.stylex.ts +23 -0
- package/src/utils/clickGuard.ts +37 -1
- package/src/utils/dateParser.ts +16 -0
- package/src/utils/focusReturn.ts +30 -0
- package/src/utils/interactionOverlay.stylex.ts +3 -2
- package/src/utils/pressFeedback.ts +19 -6
- package/src/utils/triggerFocus.ts +80 -0
|
@@ -75,6 +75,7 @@ import {
|
|
|
75
75
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
76
76
|
import {useScrollLock} from '../hooks/useScrollLock';
|
|
77
77
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
78
|
+
import {observeMutations} from '../utils/sharedMutationObserver';
|
|
78
79
|
import {mergeProps} from '../utils/mergeProps';
|
|
79
80
|
import {LayerDepthProvider} from '../Layer/LayerDepthContext';
|
|
80
81
|
import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
|
|
@@ -212,6 +213,10 @@ const styles = stylex.create({
|
|
|
212
213
|
transitionTimingFunction: 'linear',
|
|
213
214
|
},
|
|
214
215
|
},
|
|
216
|
+
// A closing sheet that keeps focus takes no pointer input during its exit.
|
|
217
|
+
exitHoldingFocus: {
|
|
218
|
+
pointerEvents: 'none',
|
|
219
|
+
},
|
|
215
220
|
positioner: {
|
|
216
221
|
position: 'absolute',
|
|
217
222
|
insetInline: 0,
|
|
@@ -260,6 +265,24 @@ function stackPositionerStyles(view: BottomSheetStackView) {
|
|
|
260
265
|
}
|
|
261
266
|
|
|
262
267
|
/** The `data-sheet-stack` value for a host: only set while sheets stack. */
|
|
268
|
+
/** Stops a click, from any input, before it reaches its target. */
|
|
269
|
+
function ignoreActivation(event: React.MouseEvent): void {
|
|
270
|
+
event.preventDefault();
|
|
271
|
+
event.stopPropagation();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Stops every key except Tab before it reaches the focused control, so no
|
|
276
|
+
* key handler runs and no text changes (a search field's Enter, a typed
|
|
277
|
+
* letter, Backspace, an arrow that reopens a list). Tab still moves focus.
|
|
278
|
+
*/
|
|
279
|
+
function ignoreKeyDuringExit(event: React.KeyboardEvent): void {
|
|
280
|
+
if (event.key !== 'Tab') {
|
|
281
|
+
event.preventDefault();
|
|
282
|
+
event.stopPropagation();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
263
286
|
function stackStateAttribute(
|
|
264
287
|
view: BottomSheetStackView,
|
|
265
288
|
isOpen: boolean,
|
|
@@ -450,10 +473,14 @@ function StandaloneBottomSheet({
|
|
|
450
473
|
// `isOpen` restarts the flag during render, and the layout effect below
|
|
451
474
|
// settles a close.
|
|
452
475
|
const [isCloseSettled, setIsCloseSettled] = useState(false);
|
|
476
|
+
// Whether focus was inside the sheet when it closed. Such a sheet stays out
|
|
477
|
+
// of `inert` and `aria-hidden` until its exit ends and focus has left.
|
|
478
|
+
const [isExitFocusHeld, setIsExitFocusHeld] = useState(false);
|
|
453
479
|
const [settledIsOpen, setSettledIsOpen] = useState(isOpen);
|
|
454
480
|
if (settledIsOpen !== isOpen) {
|
|
455
481
|
setSettledIsOpen(isOpen);
|
|
456
482
|
setIsCloseSettled(false);
|
|
483
|
+
setIsExitFocusHeld(false);
|
|
457
484
|
}
|
|
458
485
|
const panelState: BottomSheetPanelState = isOpen
|
|
459
486
|
? {kind: 'open', entering: stackView.isReplacing}
|
|
@@ -581,9 +608,8 @@ function StandaloneBottomSheet({
|
|
|
581
608
|
}, [hasScrim, shouldPresent, writeScrim]);
|
|
582
609
|
useLayoutEffect(() => {
|
|
583
610
|
if (!isOpen) {
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
// focus was when the person closed the sheet.
|
|
611
|
+
// Where focus was when the person closed the sheet. The closing sheet
|
|
612
|
+
// is not inert yet, so focus has not moved.
|
|
587
613
|
const dialog = dialogRef.current;
|
|
588
614
|
hadFocusAtCloseRef.current =
|
|
589
615
|
dialog != null && dialog.contains(dialog.ownerDocument.activeElement);
|
|
@@ -596,6 +622,8 @@ function StandaloneBottomSheet({
|
|
|
596
622
|
// that was not paired as a replace is an exit from here on.
|
|
597
623
|
// eslint-disable-next-line @eslint-react/set-state-in-effect -- settles the close once the registry has answered
|
|
598
624
|
setIsCloseSettled(true);
|
|
625
|
+
// Read with the close, before the sheet can go inert.
|
|
626
|
+
setIsExitFocusHeld(hadFocusAtCloseRef.current);
|
|
599
627
|
}
|
|
600
628
|
}, [hasScrim, isOpen, shouldPresent]);
|
|
601
629
|
|
|
@@ -699,7 +727,44 @@ function StandaloneBottomSheet({
|
|
|
699
727
|
);
|
|
700
728
|
|
|
701
729
|
const {ref: modalityRef} = useInteractionModalityTracking();
|
|
702
|
-
|
|
730
|
+
// Ends the exit hold once focus has left the sheet: moved elsewhere, or
|
|
731
|
+
// dropped to the page because the focused control was removed or disabled.
|
|
732
|
+
// Native listeners, because React drops the `focusout` a browser sends while
|
|
733
|
+
// React removes the focused element, and Firefox and WebKit send no
|
|
734
|
+
// `focusout` for a removed element at all, so a subtree change is read too.
|
|
735
|
+
// Each reads the focus after the event. They attach only while a closing
|
|
736
|
+
// sheet holds focus, so a closed or open sheet observes nothing.
|
|
737
|
+
const isWatchingExitFocus =
|
|
738
|
+
!isOpen && isPresented && !isReplaced && isExitFocusHeld;
|
|
739
|
+
const exitFocusRef = useCallback(
|
|
740
|
+
(dialog: HTMLDialogElement | null) => {
|
|
741
|
+
if (dialog == null || !isWatchingExitFocus) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const releaseIfFocusLeft = () => {
|
|
745
|
+
if (!dialog.contains(dialog.ownerDocument.activeElement)) {
|
|
746
|
+
setIsExitFocusHeld(false);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
const onFocusOut = () => queueMicrotask(releaseIfFocusLeft);
|
|
750
|
+
dialog.addEventListener('focusout', onFocusOut);
|
|
751
|
+
const unobserve = observeMutations(
|
|
752
|
+
dialog,
|
|
753
|
+
{
|
|
754
|
+
childList: true,
|
|
755
|
+
subtree: true,
|
|
756
|
+
attributeFilter: ['disabled', 'hidden', 'inert'],
|
|
757
|
+
},
|
|
758
|
+
releaseIfFocusLeft,
|
|
759
|
+
);
|
|
760
|
+
return () => {
|
|
761
|
+
dialog.removeEventListener('focusout', onFocusOut);
|
|
762
|
+
unobserve();
|
|
763
|
+
};
|
|
764
|
+
},
|
|
765
|
+
[isWatchingExitFocus],
|
|
766
|
+
);
|
|
767
|
+
const dialogComposedRef = useMergedRefs(dialogRef, modalityRef, exitFocusRef);
|
|
703
768
|
useScrollLock(shouldPresent && hasScrim);
|
|
704
769
|
useDevWarning(
|
|
705
770
|
'BottomSheet',
|
|
@@ -769,7 +834,16 @@ function StandaloneBottomSheet({
|
|
|
769
834
|
// that contains no presented sheet is marked inert and hidden as well, so
|
|
770
835
|
// the state is explicit; one that does contain the top sheet is left alone,
|
|
771
836
|
// because inert on it would reach the top sheet too.
|
|
772
|
-
|
|
837
|
+
// A sheet that closes with focus inside it is neither inert nor hidden
|
|
838
|
+
// while that focus stays: `inert` would drop the focus to the page, and
|
|
839
|
+
// `aria-hidden` would hide the focused control. When the exit ends, the
|
|
840
|
+
// dialog closes and focus returns in the same task. When focus leaves the
|
|
841
|
+
// sheet first (into a sheet opened above it, for example), the sheet goes
|
|
842
|
+
// inert at once. Until the close settles, no closing sheet is inert, so the
|
|
843
|
+
// first commit of a close never hides focus.
|
|
844
|
+
const isExitHoldingFocus =
|
|
845
|
+
isExiting && !isReplaced && (!isCloseSettled || isExitFocusHeld);
|
|
846
|
+
const isInert = (isExiting && !isExitHoldingFocus) || stackView.isBlocked;
|
|
773
847
|
|
|
774
848
|
return (
|
|
775
849
|
<dialog
|
|
@@ -784,6 +858,7 @@ function StandaloneBottomSheet({
|
|
|
784
858
|
hasScrim && styles.scrim,
|
|
785
859
|
hasScrim && isEntrySettled && styles.scrimSettled,
|
|
786
860
|
hasScrim && isExiting && styles.scrimClosing,
|
|
861
|
+
isExitHoldingFocus && styles.exitHoldingFocus,
|
|
787
862
|
!hasScrim && styles.dialogNonModal,
|
|
788
863
|
)}
|
|
789
864
|
ref={dialogComposedRef}
|
|
@@ -805,6 +880,11 @@ function StandaloneBottomSheet({
|
|
|
805
880
|
hasScrim && stackView.depth > 0 ? stackView.depth : undefined
|
|
806
881
|
}
|
|
807
882
|
onCancel={handleCancel}
|
|
883
|
+
// A closing sheet that keeps focus takes no activation during its
|
|
884
|
+
// exit: no click and no key but Tab reaches the control that has focus.
|
|
885
|
+
onClickCapture={isExitHoldingFocus ? ignoreActivation : undefined}
|
|
886
|
+
onKeyDownCapture={isExitHoldingFocus ? ignoreKeyDuringExit : undefined}
|
|
887
|
+
onKeyUpCapture={isExitHoldingFocus ? ignoreKeyDuringExit : undefined}
|
|
808
888
|
// Presses inside the sheet do not reach handlers around where it is
|
|
809
889
|
// written. The dialog, not the panel, is the root, as in a switcher.
|
|
810
890
|
{...layerClickBoundary({onClick: handleClick})}>
|
|
@@ -296,14 +296,19 @@ const styles = stylex.create({
|
|
|
296
296
|
},
|
|
297
297
|
content: {
|
|
298
298
|
// Preserve the body's block formatting (including margin/float isolation)
|
|
299
|
-
// and its definite percentage-height basis.
|
|
299
|
+
// and its definite percentage-height basis. fit-content lets this real
|
|
300
300
|
// observed box grow with async content beyond a fixed-height viewport.
|
|
301
301
|
// This box is also the sheet's container: it carries the padding and
|
|
302
302
|
// publishes it, so a lone Section child escapes it exactly as in Dialog.
|
|
303
303
|
display: 'flow-root',
|
|
304
304
|
boxSizing: 'border-box',
|
|
305
305
|
height: '100%',
|
|
306
|
-
|
|
306
|
+
// fit-content, not min-content: they are one size in the block axis, but
|
|
307
|
+
// WebKit ignores min-content on a block's min-height. There the box keeps
|
|
308
|
+
// its body's height on a budget-height sheet. Content placed directly in
|
|
309
|
+
// it then overflows the box, and its block-end padding leaves the scroll
|
|
310
|
+
// range.
|
|
311
|
+
minHeight: 'fit-content',
|
|
307
312
|
},
|
|
308
313
|
tallKeyboardBody: {
|
|
309
314
|
scrollPaddingBlockEnd: MOBILE_KEYBOARD_BOTTOM_CLEARANCE,
|
|
@@ -413,6 +418,7 @@ function waitForTransition(
|
|
|
413
418
|
|
|
414
419
|
let done = false;
|
|
415
420
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
421
|
+
let backstopMs = 0;
|
|
416
422
|
const finish = () => {
|
|
417
423
|
if (done) {
|
|
418
424
|
return;
|
|
@@ -426,9 +432,24 @@ function waitForTransition(
|
|
|
426
432
|
complete();
|
|
427
433
|
};
|
|
428
434
|
const handleTransitionEnd = (event: TransitionEvent) => {
|
|
429
|
-
if (event.target
|
|
430
|
-
|
|
435
|
+
if (event.target !== element || event.propertyName !== propertyName) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
// The engine cancels a transition whose end value changes mid-flight. A
|
|
439
|
+
// percentage transform does when the closing frame moves the height by a
|
|
440
|
+
// fraction of a pixel. A replacement runs on from where it was. That one
|
|
441
|
+
// ends the motion, not the cancel.
|
|
442
|
+
if (
|
|
443
|
+
event.type === 'transitioncancel' &&
|
|
444
|
+
hasRunningTransition(element, propertyName)
|
|
445
|
+
) {
|
|
446
|
+
if (timer != null) {
|
|
447
|
+
clearTimeout(timer);
|
|
448
|
+
timer = setTimeout(finish, backstopMs);
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
431
451
|
}
|
|
452
|
+
finish();
|
|
432
453
|
};
|
|
433
454
|
element.addEventListener('transitionend', handleTransitionEnd);
|
|
434
455
|
element.addEventListener('transitioncancel', handleTransitionEnd);
|
|
@@ -479,7 +500,8 @@ function waitForTransition(
|
|
|
479
500
|
return () => {};
|
|
480
501
|
}
|
|
481
502
|
|
|
482
|
-
|
|
503
|
+
backstopMs = transitionMs + TRANSITION_BACKSTOP_BUFFER_MS;
|
|
504
|
+
timer = setTimeout(finish, backstopMs);
|
|
483
505
|
return () => {
|
|
484
506
|
if (timer != null) {
|
|
485
507
|
clearTimeout(timer);
|
|
@@ -489,6 +511,19 @@ function waitForTransition(
|
|
|
489
511
|
};
|
|
490
512
|
}
|
|
491
513
|
|
|
514
|
+
// Whether a CSS transition of `propertyName` still runs on `element`.
|
|
515
|
+
function hasRunningTransition(
|
|
516
|
+
element: HTMLElement,
|
|
517
|
+
propertyName: string,
|
|
518
|
+
): boolean {
|
|
519
|
+
return (element.getAnimations?.() ?? []).some(
|
|
520
|
+
animation =>
|
|
521
|
+
(animation as Animation & {transitionProperty?: string})
|
|
522
|
+
.transitionProperty === propertyName &&
|
|
523
|
+
(animation.playState === 'running' || animation.playState === 'paused'),
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
492
527
|
function parseTransitionTime(value: string): number | null {
|
|
493
528
|
const normalizedValue = value.trim();
|
|
494
529
|
if (!/^-?(?:\d+|\d*\.\d+)(?:ms|s)$/.test(normalizedValue)) {
|
|
@@ -866,12 +866,16 @@ function openModalsOutside(element: Element): HTMLDialogElement[] {
|
|
|
866
866
|
if (dialog === element || dialog.contains(element)) {
|
|
867
867
|
continue;
|
|
868
868
|
}
|
|
869
|
-
// A modal on its way out
|
|
870
|
-
//
|
|
871
|
-
//
|
|
869
|
+
// A modal on its way out blocks nothing any more: it is gone for this
|
|
870
|
+
// decision. A closing sheet keeps its dialog modal through the exit
|
|
871
|
+
// animation; it is inert and hidden then, unless focus was inside it at
|
|
872
|
+
// the close, so the registry's own record of the close counts too.
|
|
872
873
|
if (
|
|
873
874
|
dialog.hasAttribute('inert') ||
|
|
874
|
-
dialog.getAttribute('aria-hidden') === 'true'
|
|
875
|
+
dialog.getAttribute('aria-hidden') === 'true' ||
|
|
876
|
+
state.entries.some(
|
|
877
|
+
entry => !entry.isOpen && entry.getElement() === dialog,
|
|
878
|
+
)
|
|
875
879
|
) {
|
|
876
880
|
continue;
|
|
877
881
|
}
|
|
@@ -738,7 +738,9 @@ export function useSheetGestures({
|
|
|
738
738
|
}
|
|
739
739
|
return;
|
|
740
740
|
}
|
|
741
|
-
|
|
741
|
+
// No measure here: reading the height while the sheet mounts forces a
|
|
742
|
+
// layout in the middle of the open. The observer's first report comes
|
|
743
|
+
// after the browser's own layout, before the first paint.
|
|
742
744
|
const ro = new ResizeObserver(entries => {
|
|
743
745
|
const entry = entries[0];
|
|
744
746
|
if (entry) {
|
package/src/Button/Button.tsx
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
|
+
isValidElement,
|
|
22
23
|
use,
|
|
23
24
|
useEffect,
|
|
24
25
|
useRef,
|
|
@@ -44,6 +45,7 @@ import {
|
|
|
44
45
|
focusVars,
|
|
45
46
|
} from '../theme/tokens.stylex';
|
|
46
47
|
import {Spinner} from '../Spinner/Spinner';
|
|
48
|
+
import {Icon} from '../Icon/Icon';
|
|
47
49
|
import {VisuallyHidden} from '../VisuallyHidden/VisuallyHidden';
|
|
48
50
|
import {IconDefaultSizeProvider} from '../Icon/IconDefaultSizeContext';
|
|
49
51
|
import {iconBoxSizeStyles, type IconSize} from '../Icon/IconSize.stylex';
|
|
@@ -51,6 +53,7 @@ import {iconBoxSizeStyles, type IconSize} from '../Icon/IconSize.stylex';
|
|
|
51
53
|
import {EDGE_COMP_ATTR} from '../Layout/edgeCompensation.stylex';
|
|
52
54
|
import {useSize} from '../SizeContext/SizeContext';
|
|
53
55
|
import {useButtonGroup} from '../ButtonGroup/ButtonGroupContext';
|
|
56
|
+
import {PressGroupContext} from '../hooks/pressGroupContext';
|
|
54
57
|
import {useInputGroup} from '../InputGroup/InputGroupContext';
|
|
55
58
|
import {InputGroupInternalContext} from '../InputGroup/InputGroupInternalContext';
|
|
56
59
|
import {
|
|
@@ -75,6 +78,12 @@ import {isRenderable} from '../utils/isRenderable';
|
|
|
75
78
|
|
|
76
79
|
registerEnglishFallback(english);
|
|
77
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Written by the press controller or a group's tracker while a press is
|
|
83
|
+
* believed. Typed `string`, so StyleX reads it as a computed key.
|
|
84
|
+
*/
|
|
85
|
+
const PRESSED_ON: string = '[data-astryx-press="on"]';
|
|
86
|
+
|
|
78
87
|
/**
|
|
79
88
|
* Base button styles
|
|
80
89
|
* Pseudo-classes are nested within properties per StyleX recommendation:
|
|
@@ -136,7 +145,19 @@ const styles = stylex.create({
|
|
|
136
145
|
default: 'scale(0.98)',
|
|
137
146
|
'@media (pointer: coarse)': 'scale(1)',
|
|
138
147
|
},
|
|
139
|
-
|
|
148
|
+
[PRESSED_ON]: 'scale(0.98)',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
// A tracked action's own `:active` image paints nothing: the tracker paints
|
|
152
|
+
// the action under the pointer, which need not be the one pressed first.
|
|
153
|
+
trackedOverlay: {
|
|
154
|
+
backgroundSize: {default: null, [`:active:not(${PRESSED_ON})`]: '0 0'},
|
|
155
|
+
},
|
|
156
|
+
// A tracked action's press is painted by its group's tracker alone.
|
|
157
|
+
pressableTracked: {
|
|
158
|
+
transform: {
|
|
159
|
+
default: 'scale(1)',
|
|
160
|
+
[PRESSED_ON]: 'scale(0.98)',
|
|
140
161
|
},
|
|
141
162
|
},
|
|
142
163
|
inactive: {
|
|
@@ -145,7 +166,7 @@ const styles = stylex.create({
|
|
|
145
166
|
transform: {
|
|
146
167
|
default: 'none',
|
|
147
168
|
':active': 'none',
|
|
148
|
-
|
|
169
|
+
[PRESSED_ON]: 'none',
|
|
149
170
|
},
|
|
150
171
|
},
|
|
151
172
|
disabled: {
|
|
@@ -158,7 +179,7 @@ const styles = stylex.create({
|
|
|
158
179
|
backgroundImage: {
|
|
159
180
|
default: 'none',
|
|
160
181
|
':active': 'none',
|
|
161
|
-
|
|
182
|
+
[PRESSED_ON]: 'none',
|
|
162
183
|
},
|
|
163
184
|
},
|
|
164
185
|
iconOnly: {
|
|
@@ -559,6 +580,21 @@ const groupStyles = stylex.create({
|
|
|
559
580
|
},
|
|
560
581
|
});
|
|
561
582
|
|
|
583
|
+
/**
|
|
584
|
+
* Whether `node` adds nothing to a content name: nothing renders, or an Icon
|
|
585
|
+
* with no label. Any other node can add text.
|
|
586
|
+
*/
|
|
587
|
+
function addsNoName(node: ReactNode): boolean {
|
|
588
|
+
if (!isRenderable(node)) {
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
if (!isValidElement<{label?: unknown}>(node) || node.type !== Icon) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
const {label} = node.props;
|
|
595
|
+
return typeof label !== 'string' || label.trim() === '';
|
|
596
|
+
}
|
|
597
|
+
|
|
562
598
|
/**
|
|
563
599
|
* A versatile button component with multiple variants.
|
|
564
600
|
*
|
|
@@ -615,6 +651,7 @@ export function Button({
|
|
|
615
651
|
const t = useComponentTranslator();
|
|
616
652
|
const size = useSize(sizeProp, 'md');
|
|
617
653
|
const buttonGroup = useButtonGroup();
|
|
654
|
+
const isGroupTracked = use(PressGroupContext);
|
|
618
655
|
const inputGroup = useInputGroup();
|
|
619
656
|
|
|
620
657
|
const [isPending, startTransition] = useTransition();
|
|
@@ -743,11 +780,13 @@ export function Button({
|
|
|
743
780
|
sizeStyles[size],
|
|
744
781
|
isIconOnly && styles.iconOnly,
|
|
745
782
|
interactionOverlayStyles.backgroundImage,
|
|
783
|
+
isGroupTracked && styles.trackedOverlay,
|
|
746
784
|
buttonDisabled && styles.inactive,
|
|
747
785
|
isTrueDisabled && styles.disabled,
|
|
748
786
|
useAriaDisabled && styles.ariaDisabled,
|
|
749
787
|
renderAsLink && styles.link,
|
|
750
|
-
!buttonGroup &&
|
|
788
|
+
!buttonGroup &&
|
|
789
|
+
(isGroupTracked ? styles.pressableTracked : styles.pressable),
|
|
751
790
|
buttonGroup &&
|
|
752
791
|
(buttonGroup.orientation === 'horizontal'
|
|
753
792
|
? groupStyles.horizontal
|
|
@@ -836,10 +875,29 @@ export function Button({
|
|
|
836
875
|
(children != null && children !== label);
|
|
837
876
|
const ariaLabelProp = needsAriaLabel ? {'aria-label': label} : null;
|
|
838
877
|
|
|
878
|
+
// The Button's accessible name when props decide it: the derived
|
|
879
|
+
// aria-label, else a caller aria-label, else the label text alone. It is
|
|
880
|
+
// not known here when a caller aria-labelledby names the Button from other
|
|
881
|
+
// elements, or when `icon` or `endContent` can add text to its content
|
|
882
|
+
// name (a count, for example). A decorative Icon in either slot adds none.
|
|
883
|
+
const accessibleName =
|
|
884
|
+
props['aria-labelledby'] != null
|
|
885
|
+
? undefined
|
|
886
|
+
: needsAriaLabel
|
|
887
|
+
? label
|
|
888
|
+
: (props['aria-label'] ??
|
|
889
|
+
(addsNoName(icon) && addsNoName(endContent) ? label : undefined));
|
|
890
|
+
// A tooltip that repeats the name adds nothing to the description, so a
|
|
891
|
+
// screen reader does not read the same words twice.
|
|
892
|
+
const isTooltipName =
|
|
893
|
+
tooltip != null &&
|
|
894
|
+
accessibleName != null &&
|
|
895
|
+
tooltip.trim() === accessibleName.trim();
|
|
896
|
+
|
|
839
897
|
// When a tooltip is attached via the hook, point aria-describedby at the
|
|
840
898
|
// tooltip content (composing with any consumer-provided value).
|
|
841
899
|
const describedByProp =
|
|
842
|
-
tooltip != null
|
|
900
|
+
tooltip != null && !isTooltipName
|
|
843
901
|
? {
|
|
844
902
|
'aria-describedby':
|
|
845
903
|
[props['aria-describedby'], tooltipHook.describedBy]
|
|
@@ -151,6 +151,15 @@ export const docs = {
|
|
|
151
151
|
},
|
|
152
152
|
{className: 'astryx-checkbox-label'},
|
|
153
153
|
],
|
|
154
|
+
vars: [
|
|
155
|
+
{
|
|
156
|
+
name: '--_indicator-press-image',
|
|
157
|
+
description:
|
|
158
|
+
"The row's touch press image, handed down to a first-party indicator that paints its own press. Set from the press paint only while the row itself is pressed; only this row declares it.",
|
|
159
|
+
default: 'none',
|
|
160
|
+
private: true,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
154
163
|
},
|
|
155
164
|
usage: {
|
|
156
165
|
accessibility: [
|
|
@@ -390,6 +399,15 @@ export const docsZh = {
|
|
|
390
399
|
},
|
|
391
400
|
{className: 'astryx-checkbox-label'},
|
|
392
401
|
],
|
|
402
|
+
vars: [
|
|
403
|
+
{
|
|
404
|
+
name: '--_indicator-press-image',
|
|
405
|
+
description:
|
|
406
|
+
'复选框行的触摸按下图像,传递给自行绘制按下效果的第一方指示器。仅在该行自身被按下时根据按下涂层设置,只有该行声明它。',
|
|
407
|
+
default: 'none',
|
|
408
|
+
private: true,
|
|
409
|
+
},
|
|
410
|
+
],
|
|
393
411
|
},
|
|
394
412
|
};
|
|
395
413
|
|
|
@@ -46,6 +46,7 @@ import {useTooltip} from '../Tooltip/useTooltip';
|
|
|
46
46
|
import {mergeProps} from '../utils/mergeProps';
|
|
47
47
|
import {rtlStyles} from '../utils/rtlStyles';
|
|
48
48
|
import {indicatorScope} from '../Indicator/indicator.markers.stylex';
|
|
49
|
+
import {indicatorPressScope} from '../internal/indicatorPress.stylex';
|
|
49
50
|
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
50
51
|
import {useIndicatorFocusRing} from '../hooks/useIndicatorFocusRing';
|
|
51
52
|
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
@@ -63,6 +64,11 @@ import english from '../i18n/generated-english/CheckboxInput/CheckboxInput.gener
|
|
|
63
64
|
|
|
64
65
|
registerEnglishFallback(english);
|
|
65
66
|
|
|
67
|
+
/** Written by the touch press controller while the row is pressed. */
|
|
68
|
+
const PRESSED_ON = '[data-astryx-press="on"]';
|
|
69
|
+
/** Written by the touch press controller for the press's release. */
|
|
70
|
+
const PRESSED_FADING = '[data-astryx-press="fading"]';
|
|
71
|
+
|
|
66
72
|
// The touch press's paint, declared by the shared overlay styles on the
|
|
67
73
|
// element the controller writes to (`pressedAlpha`) at the press's strength,
|
|
68
74
|
// 1 while on and 1 → 0 over the release, and inherited resolved by the layer
|
|
@@ -78,6 +84,20 @@ const styles = stylex.create({
|
|
|
78
84
|
containerLabelHidden: {
|
|
79
85
|
gap: 0,
|
|
80
86
|
},
|
|
87
|
+
// An indicator in another package may not name this row's private press
|
|
88
|
+
// attribute. So the row hands its touch press image down to an indicator
|
|
89
|
+
// that paints its own press, but only in its own press states. A pressed
|
|
90
|
+
// ancestor row reaches it no more than it reaches the built-in box. The
|
|
91
|
+
// property has no default: a layered stylesheet ranks a default above the
|
|
92
|
+
// arms. Only this row declares it. A menu item paints its own press, so a
|
|
93
|
+
// mapped indicator there paints none.
|
|
94
|
+
indicatorPressImage: {
|
|
95
|
+
'--_indicator-press-image': {
|
|
96
|
+
default: null,
|
|
97
|
+
[PRESSED_ON]: 'var(--_press-paint-image)',
|
|
98
|
+
[PRESSED_FADING]: 'var(--_press-paint-image)',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
81
101
|
checkboxWrapper: {
|
|
82
102
|
position: 'relative',
|
|
83
103
|
display: 'flex',
|
|
@@ -86,15 +106,22 @@ const styles = stylex.create({
|
|
|
86
106
|
flexShrink: 0,
|
|
87
107
|
isolation: 'isolate',
|
|
88
108
|
},
|
|
89
|
-
// The owner paints this layer over the resolved indicator, so a theme
|
|
90
|
-
// replacement
|
|
109
|
+
// The owner paints this layer over the resolved indicator, so a plain theme
|
|
110
|
+
// replacement keeps the pressed feedback. An indicator with
|
|
111
|
+
// `indicatorPressScope` paints its own press from what the row hands down,
|
|
112
|
+
// so this layer stays off over it.
|
|
91
113
|
// Two pointers, two press models (see interactionOverlay.stylex.ts): a
|
|
92
114
|
// mouse keeps `:active`; under a coarse pointer that arm is dropped and the
|
|
93
115
|
// touch press controller writes `data-astryx-press` on the row, which this layer
|
|
94
116
|
// reads off the same scope marker as the hover tint.
|
|
95
117
|
indicatorPressOverlay: {
|
|
96
118
|
'::after': {
|
|
97
|
-
|
|
119
|
+
// `when.descendant` takes a pseudo-class; `:is(*)` matches any marked
|
|
120
|
+
// indicator.
|
|
121
|
+
content: {
|
|
122
|
+
default: '""',
|
|
123
|
+
[stylex.when.descendant(':is(*)', indicatorPressScope)]: 'none',
|
|
124
|
+
},
|
|
98
125
|
position: 'absolute',
|
|
99
126
|
inset: 0,
|
|
100
127
|
borderRadius: radiusVars['--radius-inner'],
|
|
@@ -112,15 +139,14 @@ const styles = stylex.create({
|
|
|
112
139
|
// what moves. Coarse pointers only, like the drop above.
|
|
113
140
|
backgroundImage: {
|
|
114
141
|
default: null,
|
|
115
|
-
[stylex.when.ancestor(
|
|
142
|
+
[stylex.when.ancestor(PRESSED_ON, indicatorScope)]: {
|
|
143
|
+
default: null,
|
|
144
|
+
'@media (pointer: coarse)': pressedOverlayImage,
|
|
145
|
+
},
|
|
146
|
+
[stylex.when.ancestor(PRESSED_FADING, indicatorScope)]: {
|
|
116
147
|
default: null,
|
|
117
148
|
'@media (pointer: coarse)': pressedOverlayImage,
|
|
118
149
|
},
|
|
119
|
-
[stylex.when.ancestor('[data-astryx-press="fading"]', indicatorScope)]:
|
|
120
|
-
{
|
|
121
|
-
default: null,
|
|
122
|
-
'@media (pointer: coarse)': pressedOverlayImage,
|
|
123
|
-
},
|
|
124
150
|
},
|
|
125
151
|
},
|
|
126
152
|
},
|
|
@@ -450,6 +476,7 @@ export function CheckboxInput({
|
|
|
450
476
|
// Hover and focus reach the checkbox visual through this ancestor
|
|
451
477
|
// marker rather than props, so the whole row drives it.
|
|
452
478
|
!isDisabled && indicatorScope,
|
|
479
|
+
!isDisabled && styles.indicatorPressImage,
|
|
453
480
|
// The touch press's strength and release live on the row the
|
|
454
481
|
// controller writes to; the layer over the checkbox box inherits
|
|
455
482
|
// and paints it.
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
} from '../Field/fieldParts';
|
|
36
36
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
37
37
|
import {mergeRefs} from '../utils/mergeRefs';
|
|
38
|
+
import {isFocusLost} from '../utils/focusReturn';
|
|
38
39
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
39
40
|
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
40
41
|
import {Field, inputWrapperStyles} from '../Field';
|
|
@@ -347,7 +348,10 @@ export interface ComplexSelectorRenderState {
|
|
|
347
348
|
export interface ComplexSelectorHandle {
|
|
348
349
|
/** Open the selector surface. No-op when disabled or already open. */
|
|
349
350
|
open(): void;
|
|
350
|
-
/**
|
|
351
|
+
/**
|
|
352
|
+
* Close the selector surface. Focus returns to the trigger only when it
|
|
353
|
+
* would otherwise be lost; a caller-rendered trigger always gets it back.
|
|
354
|
+
*/
|
|
351
355
|
close(): void;
|
|
352
356
|
/** Toggle the selector surface open or closed. */
|
|
353
357
|
toggle(): void;
|
|
@@ -630,12 +634,22 @@ export function ComplexSelector<Value>({
|
|
|
630
634
|
onOpenChange?.(true);
|
|
631
635
|
}, [onOpenChange]);
|
|
632
636
|
|
|
637
|
+
// The popup's content element, read when it closes.
|
|
638
|
+
const popupContentRef = useRef<HTMLElement | null>(null);
|
|
639
|
+
const hasCallerTrigger = renderTrigger != null;
|
|
633
640
|
const handlePopoverHide = useCallback(() => {
|
|
634
|
-
|
|
635
|
-
//
|
|
636
|
-
|
|
641
|
+
const trigger = triggerRef.current;
|
|
642
|
+
// A press that moved focus to another control keeps it there. A caller's
|
|
643
|
+
// control always gets focus back. Focus moves before the callback, so a
|
|
644
|
+
// consumer that moves it from the callback wins.
|
|
645
|
+
if (
|
|
646
|
+
hasCallerTrigger ||
|
|
647
|
+
isFocusLost(popupContentRef.current, trigger?.ownerDocument)
|
|
648
|
+
) {
|
|
649
|
+
trigger?.focus();
|
|
650
|
+
}
|
|
637
651
|
onOpenChange?.(false);
|
|
638
|
-
}, [onOpenChange]);
|
|
652
|
+
}, [hasCallerTrigger, onOpenChange]);
|
|
639
653
|
|
|
640
654
|
const popover = usePopover({
|
|
641
655
|
dialogLabel: label,
|
|
@@ -645,6 +659,7 @@ export function ComplexSelector<Value>({
|
|
|
645
659
|
onShow: handlePopoverShow,
|
|
646
660
|
onHide: handlePopoverHide,
|
|
647
661
|
});
|
|
662
|
+
popupContentRef.current = popover.contentRef.current;
|
|
648
663
|
|
|
649
664
|
const isOpen = popover.isOpen;
|
|
650
665
|
|
|
@@ -712,14 +712,14 @@ export function ContextMenu({
|
|
|
712
712
|
);
|
|
713
713
|
const handleTriggerTouchEnd = useCallback(
|
|
714
714
|
(event: React.TouchEvent<HTMLElement>) => {
|
|
715
|
-
handleLongPressTouchEnd();
|
|
715
|
+
handleLongPressTouchEnd(event);
|
|
716
716
|
onTouchEndProp?.(event);
|
|
717
717
|
},
|
|
718
718
|
[handleLongPressTouchEnd, onTouchEndProp],
|
|
719
719
|
);
|
|
720
720
|
const handleTriggerTouchCancel = useCallback(
|
|
721
721
|
(event: React.TouchEvent<HTMLElement>) => {
|
|
722
|
-
handleLongPressTouchCancel();
|
|
722
|
+
handleLongPressTouchCancel(event);
|
|
723
723
|
onTouchCancelProp?.(event);
|
|
724
724
|
},
|
|
725
725
|
[handleLongPressTouchCancel, onTouchCancelProp],
|