@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
|
@@ -8,7 +8,7 @@ export const docs = {
|
|
|
8
8
|
displayName: 'Radio List Item',
|
|
9
9
|
isHiddenFromOverview: true,
|
|
10
10
|
description:
|
|
11
|
-
'Individual radio item with label, description, and content slots.',
|
|
11
|
+
'Individual radio item with label, description, and content slots. Under a coarse pointer an enabled option’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; the row keeps its size. That hit area is the row’s `::after`, so decorate a row with a child.',
|
|
12
12
|
// RadioListItem requires RadioList context; wrap it so the preview doesn't throw.
|
|
13
13
|
playground: {
|
|
14
14
|
defaults: {value: 'option-1', label: 'Option'},
|
|
@@ -97,7 +97,8 @@ export const docsZh = {
|
|
|
97
97
|
name: 'RadioListItem',
|
|
98
98
|
isHiddenFromOverview: true,
|
|
99
99
|
displayName: 'Radio List Item',
|
|
100
|
-
description:
|
|
100
|
+
description:
|
|
101
|
+
'单个单选选项,包含标签、描述和内容插槽。在粗指针(触摸)下,已启用选项的目标通过不可见的点击区域向 44 px 扩展。它不小于所在行,与相邻控件较近时止于间距的一半;行尺寸不变。该点击区域是行的 `::after`,因此请用子元素装饰行。',
|
|
101
102
|
props: [
|
|
102
103
|
{
|
|
103
104
|
name: 'label',
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* - /packages/cli/assets/templates/blocks/components/RadioList/ (showcase blocks)
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import React, {use, useId, useRef, type ReactNode} from 'react';
|
|
21
|
+
import React, {use, useCallback, useId, useRef, type ReactNode} from 'react';
|
|
22
22
|
import * as stylex from '@stylexjs/stylex';
|
|
23
23
|
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
24
24
|
import type {BaseProps} from '../BaseProps';
|
|
@@ -32,9 +32,13 @@ import {rtlStyles} from '../utils/rtlStyles';
|
|
|
32
32
|
import {indicatorScope} from '../Indicator/indicator.markers.stylex';
|
|
33
33
|
import {useIndicatorFocusRing} from '../hooks/useIndicatorFocusRing';
|
|
34
34
|
import {usePressFeedback} from '../hooks/usePressFeedback';
|
|
35
|
+
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
36
|
+
import {watchHitArea, writeHitInsets} from '../utils/hitArea';
|
|
37
|
+
import {touchHitAreaStyles} from '../utils/touchHitArea.stylex';
|
|
35
38
|
import {useIndicator} from '../Indicator/useIndicator';
|
|
36
39
|
import {Item, type ItemAlign} from '../Item/Item';
|
|
37
40
|
import {themeProps} from '../utils/themeProps';
|
|
41
|
+
import {rowPressLayerStyles} from '../privateAttributes/press.stylex';
|
|
38
42
|
|
|
39
43
|
// The touch press's paint, declared by the shared overlay styles on the
|
|
40
44
|
// element the controller writes to (`pressedAlpha`) at the press's strength,
|
|
@@ -147,11 +151,6 @@ const rowStyles = stylex.create({
|
|
|
147
151
|
// Suppress Item's interactive hover/press background so the resting and
|
|
148
152
|
// hovered row look identical by default (a theme can restyle either).
|
|
149
153
|
backgroundColor: 'transparent',
|
|
150
|
-
// ...and the touch press's overlay image, for the same reason: under a
|
|
151
|
-
// finger only the radio circle paints, as under a mouse. The press's
|
|
152
|
-
// strength still lands on the row, which is where the layer over the
|
|
153
|
-
// circle reads it.
|
|
154
|
-
backgroundImage: 'none',
|
|
155
154
|
},
|
|
156
155
|
// The mouse press (a finger's lives on `data-astryx-press`): the row is
|
|
157
156
|
// pressed, but not through a nested link or button, which keep their own
|
|
@@ -309,6 +308,21 @@ export function RadioListItem({
|
|
|
309
308
|
// `<label htmlFor>` would double-fire under delegation.
|
|
310
309
|
const radioRef = useRef<HTMLInputElement>(null);
|
|
311
310
|
|
|
311
|
+
// Under a coarse pointer an option's row grows toward 44 px through an
|
|
312
|
+
// invisible band, and the row keeps its size. A tap on the band lands on
|
|
313
|
+
// the row, which hands it to the radio. The watch lives as long as the row
|
|
314
|
+
// is attached: the ref's cleanup stops it.
|
|
315
|
+
const watchRowHitArea = useCallback(
|
|
316
|
+
(row: HTMLDivElement | null) => {
|
|
317
|
+
if (row == null || isDisabled) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
return watchHitArea(row, insets => writeHitInsets(row, insets));
|
|
321
|
+
},
|
|
322
|
+
[isDisabled],
|
|
323
|
+
);
|
|
324
|
+
const mergedRowRef = useMergedRefs(ref, watchRowHitArea);
|
|
325
|
+
|
|
312
326
|
const radioCircle = (
|
|
313
327
|
<div
|
|
314
328
|
{...stylex.props(
|
|
@@ -391,7 +405,7 @@ export function RadioListItem({
|
|
|
391
405
|
|
|
392
406
|
return (
|
|
393
407
|
<Item
|
|
394
|
-
ref={
|
|
408
|
+
ref={mergedRowRef}
|
|
395
409
|
{...(isDisabled ? undefined : pressable)}
|
|
396
410
|
startContent={mediaContent}
|
|
397
411
|
// Delegate row-surface clicks (label text, description, and the empty
|
|
@@ -414,12 +428,16 @@ export function RadioListItem({
|
|
|
414
428
|
// the painting row element (Item), the same element that carries the
|
|
415
429
|
// theme target, so a theme's hover styling stays in step with the tint.
|
|
416
430
|
!isDisabled && indicatorScope,
|
|
431
|
+
// The row keeps no press paint of its own: the circle paints, and
|
|
432
|
+
// reads the strength whose release the row keeps.
|
|
433
|
+
rowPressLayerStyles.cellWithoutLayer,
|
|
417
434
|
// Restore the bare default look: zero Item's padding/radius and its
|
|
418
435
|
// interactive hover/press background. Applied after Item's own base
|
|
419
436
|
// styles so it wins within the base layer; a `radio-list-item` theme
|
|
420
437
|
// still overrides it from the higher `astryx-theme` layer.
|
|
421
438
|
rowStyles.root,
|
|
422
439
|
!isDisabled && rowStyles.press,
|
|
440
|
+
!isDisabled && touchHitAreaStyles.layer,
|
|
423
441
|
xstyle,
|
|
424
442
|
] as StyleXStyles
|
|
425
443
|
}
|
|
@@ -44,6 +44,7 @@ import type {StyleXStyles} from '@stylexjs/stylex';
|
|
|
44
44
|
import {colorVars, durationVars, easeVars} from '../theme/tokens.stylex';
|
|
45
45
|
import type {Elevation, SizeValue, SpacingStep} from '../utils/types';
|
|
46
46
|
import {isImeKeyEvent, mergeProps} from '../utils';
|
|
47
|
+
import {chainEventHandlers} from '../utils/chainEventHandlers';
|
|
47
48
|
import {Card} from '../Card/Card';
|
|
48
49
|
import type {CardVariant} from '../Card/Card';
|
|
49
50
|
import {useClickableContainer} from '../hooks/useClickableContainer';
|
|
@@ -398,6 +399,7 @@ export function SelectableCard({
|
|
|
398
399
|
onChange,
|
|
399
400
|
onClick: onClickProp,
|
|
400
401
|
onMouseUp: onMouseUpProp,
|
|
402
|
+
onPointerDown: onPointerDownProp,
|
|
401
403
|
isDisabled = false,
|
|
402
404
|
children,
|
|
403
405
|
padding,
|
|
@@ -458,7 +460,7 @@ export function SelectableCard({
|
|
|
458
460
|
[isDisabled, requestChange],
|
|
459
461
|
);
|
|
460
462
|
|
|
461
|
-
const {onClick, onMouseUp} = useClickableContainer({
|
|
463
|
+
const {onClick, onMouseUp, onPointerDown} = useClickableContainer({
|
|
462
464
|
containerRef,
|
|
463
465
|
interactiveRef,
|
|
464
466
|
onClick: handleClick,
|
|
@@ -530,6 +532,11 @@ export function SelectableCard({
|
|
|
530
532
|
}
|
|
531
533
|
onClick={!isDisabled ? composedOnClick : undefined}
|
|
532
534
|
onMouseUp={!isDisabled ? composedOnMouseUp : undefined}
|
|
535
|
+
onPointerDown={
|
|
536
|
+
!isDisabled
|
|
537
|
+
? chainEventHandlers(onPointerDown, onPointerDownProp)
|
|
538
|
+
: onPointerDownProp
|
|
539
|
+
}
|
|
533
540
|
{...props}
|
|
534
541
|
onClickCapture={
|
|
535
542
|
!isDisabled && handleLayerClickCapture
|
package/src/Spinner/Spinner.tsx
CHANGED
|
@@ -211,6 +211,16 @@ function syncRotationPhase(
|
|
|
211
211
|
// Animation
|
|
212
212
|
// =============================================================================
|
|
213
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Full turns in one iteration of the rotation.
|
|
216
|
+
*
|
|
217
|
+
* Chromium wakes the main thread at each iteration boundary while the
|
|
218
|
+
* document listens for `animationiteration`. React listens for it on every
|
|
219
|
+
* root. One iteration holds 100 turns, so the wake comes once in 100 turns.
|
|
220
|
+
* Each turn is 360 degrees, so the ring looks the same.
|
|
221
|
+
*/
|
|
222
|
+
const TURNS_PER_ITERATION = 100;
|
|
223
|
+
|
|
214
224
|
/**
|
|
215
225
|
* A `transform` animation runs on the compositor, so the ring keeps turning
|
|
216
226
|
* while a script runs. A `stroke-dashoffset` animation would repaint the arc
|
|
@@ -218,7 +228,7 @@ function syncRotationPhase(
|
|
|
218
228
|
*/
|
|
219
229
|
const rotation = stylex.keyframes({
|
|
220
230
|
'0%': {transform: 'rotate(0deg)'},
|
|
221
|
-
'100%': {transform:
|
|
231
|
+
'100%': {transform: `rotate(${360 * TURNS_PER_ITERATION}deg)`},
|
|
222
232
|
});
|
|
223
233
|
|
|
224
234
|
// =============================================================================
|
|
@@ -307,8 +317,8 @@ const styles = stylex.create({
|
|
|
307
317
|
// broken. The status role and its name still say that work is busy.
|
|
308
318
|
animationDuration: {
|
|
309
319
|
// The loop reads the duration before a theme's tempo scales it.
|
|
310
|
-
default: `var(--_astryx-pre-tempo-duration-slow-min, ${durationVars['--duration-slow-min']})`,
|
|
311
|
-
'@media (prefers-reduced-motion: reduce)':
|
|
320
|
+
default: `calc(var(--_astryx-pre-tempo-duration-slow-min, ${durationVars['--duration-slow-min']}) * ${TURNS_PER_ITERATION})`,
|
|
321
|
+
'@media (prefers-reduced-motion: reduce)': `calc(3s * ${TURNS_PER_ITERATION})`,
|
|
312
322
|
},
|
|
313
323
|
animationIterationCount: 'infinite',
|
|
314
324
|
animationName: rotation,
|
package/src/Stack/Stack.doc.mjs
CHANGED
|
@@ -524,7 +524,7 @@ export const docs = {
|
|
|
524
524
|
name: 'isScrollable',
|
|
525
525
|
type: 'boolean',
|
|
526
526
|
description:
|
|
527
|
-
'Enables scrollable overflow (overflow: auto). StackItem already applies the flex min-height/min-width reset, so <StackItem size="fill" isScrollable> is a complete scroll region. Matches isScrollable on LayoutContent and LayoutPanel.',
|
|
527
|
+
'Enables scrollable overflow (overflow: auto). StackItem already applies the flex min-height/min-width reset, so <StackItem size="fill" isScrollable> is a complete scroll region. Matches isScrollable on LayoutContent and LayoutPanel. While its content overflows, a div or span item is a group named by aria-label, or by a translated "Content"; another element keeps its own role.',
|
|
528
528
|
default: 'false',
|
|
529
529
|
},
|
|
530
530
|
{
|
package/src/Stack/StackItem.tsx
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file StackItem.tsx
|
|
5
|
-
* @input Uses React, ElementType, stackItem utility
|
|
5
|
+
* @input Uses React, ElementType, stackItem utility, StackItemScrollRegion
|
|
6
6
|
* @output Exports StackItem polymorphic component and StackItemProps
|
|
7
|
-
* @position Layout/Stack component; uses stackItem.stylex.ts
|
|
7
|
+
* @position Layout/Stack component; uses stackItem.stylex.ts. A scrollable
|
|
8
|
+
* `div` or `span` item renders through StackItemScrollRegion, which names
|
|
9
|
+
* it while it scrolls.
|
|
8
10
|
*
|
|
9
11
|
* SYNC: When modified, update these files to stay in sync:
|
|
10
12
|
* - /packages/core/src/Stack/Stack.doc.mjs
|
|
@@ -21,6 +23,7 @@ import {
|
|
|
21
23
|
} from './stackItem.stylex';
|
|
22
24
|
import {mergeProps} from '../utils/mergeProps';
|
|
23
25
|
import {themeProps} from '../utils/themeProps';
|
|
26
|
+
import {StackItemScrollRegion} from '../hooks/StackItemScrollRegion';
|
|
24
27
|
|
|
25
28
|
const overflowStyles = stylex.create({
|
|
26
29
|
scrollable: {
|
|
@@ -53,7 +56,9 @@ export interface StackItemProps extends BaseProps<HTMLElement> {
|
|
|
53
56
|
* reset, so `<StackItem size="fill" isScrollable>` is a complete scroll
|
|
54
57
|
* region — it grows to fill the stack and scrolls its own overflow with
|
|
55
58
|
* no extra style plumbing. Matches `isScrollable` on `LayoutContent`
|
|
56
|
-
* and `LayoutPanel`.
|
|
59
|
+
* and `LayoutPanel`. While its content overflows, a `div` or `span` item
|
|
60
|
+
* is a group named by the caller's `aria-label`, or by a translated
|
|
61
|
+
* "Content". Another element keeps its own role.
|
|
57
62
|
* @default false
|
|
58
63
|
*/
|
|
59
64
|
isScrollable?: boolean;
|
|
@@ -102,18 +107,27 @@ export function StackItem({
|
|
|
102
107
|
xstyle,
|
|
103
108
|
);
|
|
104
109
|
|
|
110
|
+
const elementProps = {
|
|
111
|
+
...mergeProps(
|
|
112
|
+
themeProps('stack-item', {size}),
|
|
113
|
+
stylexProps,
|
|
114
|
+
className,
|
|
115
|
+
style,
|
|
116
|
+
),
|
|
117
|
+
...props,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (isScrollable && (element === 'div' || element === 'span')) {
|
|
121
|
+
return createElement(
|
|
122
|
+
StackItemScrollRegion,
|
|
123
|
+
{as: element, ref, elementProps},
|
|
124
|
+
children,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
105
128
|
return createElement(
|
|
106
129
|
element,
|
|
107
|
-
{
|
|
108
|
-
ref: ref as Ref<Element>,
|
|
109
|
-
...mergeProps(
|
|
110
|
-
themeProps('stack-item', {size}),
|
|
111
|
-
stylexProps,
|
|
112
|
-
className,
|
|
113
|
-
style,
|
|
114
|
-
),
|
|
115
|
-
...props,
|
|
116
|
-
},
|
|
130
|
+
{ref: ref as Ref<Element>, ...elementProps},
|
|
117
131
|
children,
|
|
118
132
|
);
|
|
119
133
|
}
|
|
@@ -168,7 +168,7 @@ const sortStyles = stylex.create({
|
|
|
168
168
|
fontSize: 10,
|
|
169
169
|
// eslint-disable-next-line @astryx/no-hardcoded-styles -- no token for lineHeight:1 (tight badge)
|
|
170
170
|
lineHeight: '1',
|
|
171
|
-
color: colorVars['--color-accent'],
|
|
171
|
+
color: colorVars['--color-text-accent'],
|
|
172
172
|
},
|
|
173
173
|
});
|
|
174
174
|
|
package/src/Token/Token.tsx
CHANGED
|
@@ -39,6 +39,7 @@ import {themeProps} from '../utils/themeProps';
|
|
|
39
39
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
40
40
|
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
41
41
|
import {usePressFeedback} from '../hooks/usePressFeedback';
|
|
42
|
+
import {cancelNestedTouchPress} from '../hooks/useClickableContainer';
|
|
42
43
|
import type {TokenColorMap} from './index';
|
|
43
44
|
import {disabledInteractionProps} from '../utils/disabledInteraction';
|
|
44
45
|
import {TokenNamingContext} from './TokenNamingContext';
|
|
@@ -510,6 +511,19 @@ export function Token({
|
|
|
510
511
|
}
|
|
511
512
|
effectiveOnClick(e);
|
|
512
513
|
};
|
|
514
|
+
// A finger press on Remove or a control in end content does not run the
|
|
515
|
+
// token's action, so the token shows no press for it. The inner button,
|
|
516
|
+
// the first button child, is the token's own action.
|
|
517
|
+
const handleContainerPointerDown = (
|
|
518
|
+
e: React.PointerEvent<HTMLSpanElement>,
|
|
519
|
+
) => {
|
|
520
|
+
rest.onPointerDown?.(e);
|
|
521
|
+
cancelNestedTouchPress(
|
|
522
|
+
e,
|
|
523
|
+
e.currentTarget,
|
|
524
|
+
e.currentTarget.querySelector(':scope > button'),
|
|
525
|
+
);
|
|
526
|
+
};
|
|
513
527
|
|
|
514
528
|
return (
|
|
515
529
|
<span
|
|
@@ -535,7 +549,10 @@ export function Token({
|
|
|
535
549
|
style,
|
|
536
550
|
)}
|
|
537
551
|
{...rest}
|
|
538
|
-
{...sharedProps}
|
|
552
|
+
{...sharedProps}
|
|
553
|
+
onPointerDown={
|
|
554
|
+
isDisabled ? rest.onPointerDown : handleContainerPointerDown
|
|
555
|
+
}>
|
|
539
556
|
{icon}
|
|
540
557
|
<button
|
|
541
558
|
type="button"
|
package/src/Token/TokenLink.tsx
CHANGED
|
@@ -90,12 +90,13 @@ export function TokenLink({
|
|
|
90
90
|
removeButton,
|
|
91
91
|
onClick: onClickProp,
|
|
92
92
|
onMouseUp: onMouseUpProp,
|
|
93
|
+
onPointerDown: onPointerDownProp,
|
|
93
94
|
...containerProps
|
|
94
95
|
}: TokenLinkProps) {
|
|
95
96
|
const containerRef = useRef<HTMLElement | null>(null);
|
|
96
97
|
const linkRef = useRef<HTMLElement | null>(null);
|
|
97
98
|
|
|
98
|
-
const {onClick, onMouseUp} = useClickableContainer({
|
|
99
|
+
const {onClick, onMouseUp, onPointerDown} = useClickableContainer({
|
|
99
100
|
containerRef,
|
|
100
101
|
interactiveRef: linkRef,
|
|
101
102
|
href,
|
|
@@ -113,6 +114,11 @@ export function TokenLink({
|
|
|
113
114
|
isDisabled
|
|
114
115
|
? onMouseUpProp
|
|
115
116
|
: composeEventHandlers(onMouseUpProp, onMouseUp)
|
|
117
|
+
}
|
|
118
|
+
onPointerDown={
|
|
119
|
+
isDisabled
|
|
120
|
+
? onPointerDownProp
|
|
121
|
+
: composeEventHandlers(onPointerDownProp, onPointerDown)
|
|
116
122
|
}>
|
|
117
123
|
{icon}
|
|
118
124
|
{isDisabled ? (
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
type ReactNode,
|
|
22
22
|
type RefCallback,
|
|
23
23
|
} from 'react';
|
|
24
|
-
import {createTriggerFocusGate} from '../utils/triggerFocus';
|
|
24
|
+
import {createTriggerFocusGate, isFocusOnTrigger} from '../utils/triggerFocus';
|
|
25
25
|
import * as stylex from '@stylexjs/stylex';
|
|
26
26
|
import {
|
|
27
27
|
useLayerInternal,
|
|
@@ -308,6 +308,17 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
308
308
|
const showTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
309
309
|
const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
310
310
|
const triggerRef = useRef<HTMLElement | null>(null);
|
|
311
|
+
// Whether the tooltip is enabled now, for a show that runs after a render
|
|
312
|
+
// (a pending hover show) and for the trigger ref callback.
|
|
313
|
+
const isEnabledRef = useRef(isEnabled);
|
|
314
|
+
isEnabledRef.current = isEnabled;
|
|
315
|
+
// Counts the renders in which the tooltip is disabled. A pending show
|
|
316
|
+
// records it, so a show scheduled before a disable is dropped even when
|
|
317
|
+
// the tooltip is enabled again before the timer fires.
|
|
318
|
+
const disabledRendersRef = useRef(0);
|
|
319
|
+
if (!isEnabled) {
|
|
320
|
+
disabledRendersRef.current += 1;
|
|
321
|
+
}
|
|
311
322
|
|
|
312
323
|
// Clear all timeouts
|
|
313
324
|
const clearTimeouts = useCallback(() => {
|
|
@@ -460,22 +471,16 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
460
471
|
return;
|
|
461
472
|
}
|
|
462
473
|
clearTimeouts();
|
|
474
|
+
const disabledRenders = disabledRendersRef.current;
|
|
463
475
|
showTimeoutRef.current = setTimeout(() => {
|
|
464
|
-
|
|
476
|
+
// A show runs only if the tooltip stayed enabled since it was
|
|
477
|
+
// scheduled: nothing would close a tooltip that opens after a disable.
|
|
478
|
+
if (disabledRendersRef.current === disabledRenders) {
|
|
479
|
+
showLayer();
|
|
480
|
+
}
|
|
465
481
|
}, delay);
|
|
466
482
|
}, [isEnabled, isOpen, clearTimeouts, showLayer, delay]);
|
|
467
483
|
|
|
468
|
-
// A show scheduled while the triggers were enabled must not outlive them:
|
|
469
|
-
// disabling also detaches the leave listener that would have cancelled it,
|
|
470
|
-
// so the timer would open a tooltip that nothing ever closes. Only the
|
|
471
|
-
// pending show is dropped; a pending hide still runs.
|
|
472
|
-
useEffect(() => {
|
|
473
|
-
if (!isEnabled && showTimeoutRef.current) {
|
|
474
|
-
clearTimeout(showTimeoutRef.current);
|
|
475
|
-
showTimeoutRef.current = null;
|
|
476
|
-
}
|
|
477
|
-
}, [isEnabled]);
|
|
478
|
-
|
|
479
484
|
// Schedule hide with delay (suppressed when isOpen is true).
|
|
480
485
|
// A small hover bridge (when hideDelay is 0) lets the pointer travel from the
|
|
481
486
|
// trigger onto the tooltip surface without the tooltip vanishing — required
|
|
@@ -537,23 +542,25 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
537
542
|
// focus that counted still hides on blur (utils/triggerFocus.ts).
|
|
538
543
|
const [focusGate] = useState(createTriggerFocusGate);
|
|
539
544
|
|
|
545
|
+
// Whether focus on `target` opens the tooltip. With a `trigger`, its
|
|
546
|
+
// `focusTrigger` rule applies too. Only `:focus-visible` focus opens it, and
|
|
547
|
+
// a tap never does: a tapped `<input>` also matches `:focus-visible`.
|
|
548
|
+
const opensOnFocus = useCallback(
|
|
549
|
+
(target: HTMLElement, trigger: HTMLElement | null): boolean => {
|
|
550
|
+
if (trigger != null && !isFocusOnTrigger(target, trigger, focusTrigger)) {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
return !isTouchInteraction() && target.matches(':focus-visible');
|
|
554
|
+
},
|
|
555
|
+
[focusTrigger, isTouchInteraction],
|
|
556
|
+
);
|
|
557
|
+
|
|
540
558
|
const handleFocusIn = useCallback(
|
|
541
559
|
(e: Event) => {
|
|
542
560
|
if (!focusGate.focusIn(e, focusTrigger) || !isEnabled) {
|
|
543
561
|
return;
|
|
544
562
|
}
|
|
545
|
-
|
|
546
|
-
// filter that out on every element: a tapped `<input>` or contenteditable
|
|
547
|
-
// matches it. Those are exactly the action triggers `auto` just decided
|
|
548
|
-
// to keep shut, so without this the focus reopens what the tap
|
|
549
|
-
// suppressed, over the field the user is trying to type into.
|
|
550
|
-
if (isTouchInteraction()) {
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
// Only show tooltip for keyboard focus (:focus-visible),
|
|
554
|
-
// not programmatic focus (e.g. dialog auto-focus, touch tap)
|
|
555
|
-
const target = e.target as HTMLElement;
|
|
556
|
-
if (!target.matches(':focus-visible')) {
|
|
563
|
+
if (!opensOnFocus(e.target as HTMLElement, null)) {
|
|
557
564
|
return;
|
|
558
565
|
}
|
|
559
566
|
clearTimeouts();
|
|
@@ -563,7 +570,7 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
563
570
|
focusGate,
|
|
564
571
|
focusTrigger,
|
|
565
572
|
isEnabled,
|
|
566
|
-
|
|
573
|
+
opensOnFocus,
|
|
567
574
|
clearTimeouts,
|
|
568
575
|
showLayer,
|
|
569
576
|
],
|
|
@@ -656,66 +663,155 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
656
663
|
],
|
|
657
664
|
);
|
|
658
665
|
|
|
659
|
-
//
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
666
|
+
// The trigger's listeners call the latest handlers through this ref, so the
|
|
667
|
+
// trigger ref callback keeps one identity.
|
|
668
|
+
const handlersRef = useRef({
|
|
669
|
+
handleMouseEnter,
|
|
670
|
+
handleMouseLeave,
|
|
671
|
+
handleFocusIn,
|
|
672
|
+
handleFocusOut,
|
|
673
|
+
handleHeldFocusOut,
|
|
674
|
+
handlePointerEnter,
|
|
675
|
+
handlePointerDown,
|
|
676
|
+
focusTrigger,
|
|
677
|
+
isOpen,
|
|
678
|
+
dropHold,
|
|
679
|
+
hideNow,
|
|
680
|
+
showNow,
|
|
681
|
+
opensOnFocus,
|
|
682
|
+
});
|
|
683
|
+
handlersRef.current = {
|
|
684
|
+
handleMouseEnter,
|
|
685
|
+
handleMouseLeave,
|
|
686
|
+
handleFocusIn,
|
|
687
|
+
handleFocusOut,
|
|
688
|
+
handleHeldFocusOut,
|
|
689
|
+
handlePointerEnter,
|
|
690
|
+
handlePointerDown,
|
|
691
|
+
focusTrigger,
|
|
692
|
+
isOpen,
|
|
693
|
+
dropHold,
|
|
694
|
+
hideNow,
|
|
695
|
+
showNow,
|
|
696
|
+
opensOnFocus,
|
|
697
|
+
};
|
|
679
698
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
699
|
+
// Removes the attached trigger's listeners (the trigger ref callback's
|
|
700
|
+
// cleanup).
|
|
701
|
+
const detachTriggerRef = useRef<(() => void) | null>(null);
|
|
702
|
+
// Set when the trigger detached while the tooltip was disabled: the
|
|
703
|
+
// caller removed the wiring (DropdownMenu's Button while its menu is
|
|
704
|
+
// open), so the next enabled attach reads the focus that arrived meanwhile.
|
|
705
|
+
const wasSuppressedRef = useRef(false);
|
|
706
|
+
|
|
707
|
+
// The trigger's ref callback owns every trigger listener, and reads the
|
|
708
|
+
// trigger's state when it attaches and detaches.
|
|
709
|
+
const interactionRef = useCallback((el: HTMLElement | null) => {
|
|
710
|
+
// A caller that reports `null` by hand detaches the trigger, as the
|
|
711
|
+
// returned cleanup does.
|
|
712
|
+
if (el == null) {
|
|
713
|
+
detachTriggerRef.current?.();
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (el === triggerRef.current) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
detachTriggerRef.current?.();
|
|
720
|
+
const latest = () => handlersRef.current;
|
|
721
|
+
// `pointerenter` runs before the synthesized `mouseenter` a tap produces,
|
|
722
|
+
// which is what lets the hover path know it is looking at a finger.
|
|
723
|
+
const onPointerEnter = (e: PointerEvent) => latest().handlePointerEnter(e);
|
|
724
|
+
const onMouseEnter = () => latest().handleMouseEnter();
|
|
725
|
+
const onMouseLeave = (e: Event) => latest().handleMouseLeave(e);
|
|
726
|
+
// Press-to-dismiss on a mouse; tap-to-open on a touch pointer.
|
|
727
|
+
const onPointerDown = (e: PointerEvent) => latest().handlePointerDown(e);
|
|
728
|
+
// Focus counts unless focus never opens the tooltip; `auto` decides per
|
|
729
|
+
// event (isFocusForTrigger).
|
|
730
|
+
const onFocusIn = (e: Event) => {
|
|
731
|
+
if (latest().focusTrigger !== 'never') {
|
|
732
|
+
latest().handleFocusIn(e);
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
const onFocusOut = (e: Event) => {
|
|
736
|
+
if (latest().focusTrigger !== 'never') {
|
|
737
|
+
latest().handleFocusOut(e);
|
|
696
738
|
}
|
|
739
|
+
};
|
|
740
|
+
// A held tooltip's own focus-leave close, whatever `focusTrigger` is.
|
|
741
|
+
const onHeldFocusOut = (e: FocusEvent) => latest().handleHeldFocusOut(e);
|
|
742
|
+
el.addEventListener('pointerenter', onPointerEnter);
|
|
743
|
+
el.addEventListener('mouseenter', onMouseEnter);
|
|
744
|
+
el.addEventListener('mouseleave', onMouseLeave);
|
|
745
|
+
el.addEventListener('pointerdown', onPointerDown);
|
|
746
|
+
el.addEventListener('focusin', onFocusIn);
|
|
747
|
+
el.addEventListener('focusout', onFocusOut);
|
|
748
|
+
el.addEventListener('focusout', onHeldFocusOut);
|
|
749
|
+
triggerRef.current = el;
|
|
750
|
+
|
|
751
|
+
// A trigger that attaches again after its wiring was removed
|
|
752
|
+
// (DropdownMenu's Button as its menu closes) missed the focus that
|
|
753
|
+
// arrived meanwhile. Show for it only if it would open the tooltip:
|
|
754
|
+
// returned keyboard focus does, focus returned after a pointer pick does
|
|
755
|
+
// not. Any other attach leaves the tooltip as it is, so a tooltip
|
|
756
|
+
// dismissed with Escape stays closed.
|
|
757
|
+
const focused = el.ownerDocument.activeElement;
|
|
758
|
+
const wasSuppressed = wasSuppressedRef.current;
|
|
759
|
+
if (isEnabledRef.current) {
|
|
760
|
+
wasSuppressedRef.current = false;
|
|
761
|
+
}
|
|
762
|
+
if (
|
|
763
|
+
wasSuppressed &&
|
|
764
|
+
isEnabledRef.current &&
|
|
765
|
+
latest().isOpen === undefined &&
|
|
766
|
+
latest().focusTrigger !== 'never' &&
|
|
767
|
+
focused instanceof HTMLElement &&
|
|
768
|
+
el.contains(focused) &&
|
|
769
|
+
latest().opensOnFocus(focused, el)
|
|
770
|
+
) {
|
|
771
|
+
latest().showNow();
|
|
772
|
+
}
|
|
697
773
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
774
|
+
const detach = () => {
|
|
775
|
+
if (detachTriggerRef.current === detach) {
|
|
776
|
+
detachTriggerRef.current = null;
|
|
777
|
+
}
|
|
778
|
+
el.removeEventListener('pointerenter', onPointerEnter);
|
|
779
|
+
el.removeEventListener('mouseenter', onMouseEnter);
|
|
780
|
+
el.removeEventListener('mouseleave', onMouseLeave);
|
|
781
|
+
el.removeEventListener('pointerdown', onPointerDown);
|
|
782
|
+
el.removeEventListener('focusin', onFocusIn);
|
|
783
|
+
el.removeEventListener('focusout', onFocusOut);
|
|
784
|
+
el.removeEventListener('focusout', onHeldFocusOut);
|
|
785
|
+
stopPressTrackingRef.current?.();
|
|
786
|
+
if (triggerRef.current === el) {
|
|
787
|
+
triggerRef.current = null;
|
|
788
|
+
}
|
|
789
|
+
// A caller removes the wiring as it disables the tooltip. Close an
|
|
790
|
+
// uncontrolled tooltip then, or the next attach shows a stale tooltip.
|
|
791
|
+
// A detach while enabled, such as a merged ref that changes identity
|
|
792
|
+
// on a re-render, keeps a hovered or held tooltip open.
|
|
793
|
+
if (!isEnabledRef.current && latest().isOpen === undefined) {
|
|
794
|
+
wasSuppressedRef.current = true;
|
|
795
|
+
latest().dropHold();
|
|
796
|
+
latest().hideNow();
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
detachTriggerRef.current = detach;
|
|
800
|
+
return detach;
|
|
801
|
+
}, []);
|
|
713
802
|
|
|
714
803
|
// Combined ref - shorthand for calling both positionRef and interactionRef
|
|
715
|
-
const ref
|
|
804
|
+
const ref = useCallback(
|
|
716
805
|
(el: HTMLElement | null) => {
|
|
717
806
|
layerRef(el);
|
|
718
|
-
interactionRef(el);
|
|
807
|
+
const detachInteraction = interactionRef(el);
|
|
808
|
+
if (el == null) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
return () => {
|
|
812
|
+
detachInteraction?.();
|
|
813
|
+
layerRef(null);
|
|
814
|
+
};
|
|
719
815
|
},
|
|
720
816
|
[layerRef, interactionRef],
|
|
721
817
|
);
|