@astryxdesign/core 0.7.7-canary.f9ac85f → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- 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/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +30 -2
- 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/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/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 +4 -3
- package/dist/Drawer/Drawer.js +0 -1
- 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/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/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +3 -9
- 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 +5 -5
- 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/hooks/useHighlightedOptionScroll.d.ts +11 -1
- package/dist/hooks/useHighlightedOptionScroll.d.ts.map +1 -1
- package/dist/hooks/useHighlightedOptionScroll.js +103 -5
- 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/reducedMotion.d.ts +24 -0
- package/dist/motion/reducedMotion.d.ts.map +1 -1
- package/dist/motion/reducedMotion.js +78 -1
- 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 +9 -9
- 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/Button/Button.tsx +37 -1
- 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/DateInput/MonthScroller.tsx +18 -22
- package/src/DateInput/Wheel.tsx +30 -32
- package/src/DateTimeInput/DateTimeInput.tsx +8 -2
- package/src/Dialog/Dialog.tsx +19 -4
- package/src/Drawer/Drawer.tsx +1 -1
- 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/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/SideNav/SideNavItem.tsx +3 -7
- 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 +105 -10
- package/src/hooks/useHighlightedOptionScroll.ts +113 -9
- 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/reducedMotion.ts +79 -1
- 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
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useTooltip.tsx
|
|
7
7
|
* @input Uses useLayer, usePressPin, React hooks
|
|
8
|
-
* @output Exports useTooltip hook with stable trigger refs for hover/focus/tap tooltips
|
|
8
|
+
* @output Exports useTooltip hook with stable trigger refs for hover/focus/tap tooltips,
|
|
9
|
+
* and the module-only useTooltipWithTriggerBehavior that InfoTip's press-to-pin uses
|
|
9
10
|
* @position Layer hook; builds on useLayer for tooltip behavior
|
|
10
11
|
*
|
|
11
12
|
* SYNC: When modified, update:
|
|
@@ -31,7 +32,7 @@ import {useAnnounceRenderedText} from '../hooks/useAnnounceRenderedText';
|
|
|
31
32
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
32
33
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
33
34
|
import {registerLayerSurface} from '../Layer/layerSurfacePress';
|
|
34
|
-
import {usePressPin} from '../Layer/usePressPin';
|
|
35
|
+
import {usePressPin, type LayerTriggerBehavior} from '../Layer/usePressPin';
|
|
35
36
|
import {useLayerDismissal} from '../Layer/useLayerDismissal';
|
|
36
37
|
import {themeProps} from '../utils/themeProps';
|
|
37
38
|
import {
|
|
@@ -83,14 +84,6 @@ export type TooltipFocusTrigger = 'auto' | 'always' | 'never';
|
|
|
83
84
|
*/
|
|
84
85
|
export type TooltipTouchTrigger = LayerTouchTrigger;
|
|
85
86
|
|
|
86
|
-
/**
|
|
87
|
-
* How the trigger opens the tooltip. `adaptive`, the default, previews it on
|
|
88
|
-
* hover and focus, and a tap opens it unless the trigger acts. `hover`
|
|
89
|
-
* previews it on hover and focus, and no touch or pen tap opens it. `toggle`
|
|
90
|
-
* pins it on a click, tap, Enter, or Space on a button trigger.
|
|
91
|
-
*/
|
|
92
|
-
export type TooltipTriggerBehavior = 'adaptive' | 'hover' | 'toggle';
|
|
93
|
-
|
|
94
87
|
export interface TooltipOptions {
|
|
95
88
|
/**
|
|
96
89
|
* Position placement relative to anchor
|
|
@@ -135,32 +128,10 @@ export interface TooltipOptions {
|
|
|
135
128
|
* - `tap`: tap always opens the tooltip, even on a trigger that acts
|
|
136
129
|
* - `none`: touch never opens the tooltip
|
|
137
130
|
*
|
|
138
|
-
* It decides only under `triggerBehavior="adaptive"`, the default.
|
|
139
|
-
*
|
|
140
131
|
* @default 'auto'
|
|
141
132
|
*/
|
|
142
133
|
touchTrigger?: TooltipTouchTrigger;
|
|
143
134
|
|
|
144
|
-
/**
|
|
145
|
-
* How the trigger opens the tooltip:
|
|
146
|
-
* - `adaptive` (the default): hover and focus preview it. A tap opens it
|
|
147
|
-
* unless the trigger performs an action of its own. While `touchTrigger`
|
|
148
|
-
* remains, it decides those taps.
|
|
149
|
-
* - `hover`: hover and focus preview it; no touch or pen tap opens it.
|
|
150
|
-
* - `toggle`: on a button trigger, a click, a tap, Enter, or Space pins the
|
|
151
|
-
* tooltip open, and the same press closes it. Hover and keyboard focus
|
|
152
|
-
* still preview it. A pinned tooltip stays until Escape, a second press,
|
|
153
|
-
* a press outside, or focus leaving. Astryx clickable rows and cards
|
|
154
|
-
* ignore the pinning press, except an Item row on a trigger that is not
|
|
155
|
-
* a native button. The `Tooltip` component also stops it at a child
|
|
156
|
-
* trigger. A hook caller stops a plain `onClick` ancestor itself. The
|
|
157
|
-
* trigger reports `aria-expanded` and `aria-controls`, and the tooltip's
|
|
158
|
-
* text is announced once when it pins. `touchTrigger` is ignored.
|
|
159
|
-
*
|
|
160
|
-
* @default 'adaptive'
|
|
161
|
-
*/
|
|
162
|
-
triggerBehavior?: TooltipTriggerBehavior;
|
|
163
|
-
|
|
164
135
|
/**
|
|
165
136
|
* Whether the tooltip is enabled.
|
|
166
137
|
* When false, hover/focus triggers are disabled.
|
|
@@ -296,6 +267,42 @@ function isFocusable(element: HTMLElement): boolean {
|
|
|
296
267
|
*/
|
|
297
268
|
|
|
298
269
|
export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
270
|
+
// The option is fixed to `adaptive`, so an options object that carries it at
|
|
271
|
+
// runtime cannot reach the press-to-pin path.
|
|
272
|
+
return useTooltipWithTriggerBehavior({
|
|
273
|
+
...options,
|
|
274
|
+
triggerBehavior: 'adaptive',
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Module-only options for `useTooltipWithTriggerBehavior`. Not exported from
|
|
280
|
+
* the package.
|
|
281
|
+
*/
|
|
282
|
+
export interface TooltipWithTriggerBehaviorOptions extends TooltipOptions {
|
|
283
|
+
/**
|
|
284
|
+
* How the trigger opens the tooltip:
|
|
285
|
+
* - `adaptive` (the default): hover and focus preview it. `touchTrigger`
|
|
286
|
+
* decides taps.
|
|
287
|
+
* - `toggle`: on a button trigger, a click, a tap, Enter, or Space pins the
|
|
288
|
+
* tooltip open, and the same press closes it. Hover and keyboard focus
|
|
289
|
+
* still preview it. A pinned tooltip stays until Escape, a second press,
|
|
290
|
+
* a press outside, or focus leaving. The trigger reports `aria-expanded`
|
|
291
|
+
* and `aria-controls`, and the tooltip's text is announced once when it
|
|
292
|
+
* pins. `touchTrigger` is ignored. InfoTip uses it.
|
|
293
|
+
*
|
|
294
|
+
* @default 'adaptive'
|
|
295
|
+
*/
|
|
296
|
+
triggerBehavior?: LayerTriggerBehavior;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* `useTooltip` with a trigger behavior. Module-only: InfoTip's press-to-pin
|
|
301
|
+
* goes through it, and the package does not export it.
|
|
302
|
+
*/
|
|
303
|
+
export function useTooltipWithTriggerBehavior(
|
|
304
|
+
options: TooltipWithTriggerBehaviorOptions = {},
|
|
305
|
+
): TooltipReturn {
|
|
299
306
|
const {
|
|
300
307
|
placement = 'above',
|
|
301
308
|
alignment = 'center',
|
|
@@ -33,7 +33,7 @@ import type {LinkComponentType} from '../Link/types';
|
|
|
33
33
|
import {useTopNavRenderMode} from './TopNavRenderContext';
|
|
34
34
|
import {navItemStyles, type NavItemSize} from '../NavItem/navItemStyles.stylex';
|
|
35
35
|
import {mergeProps} from '../utils';
|
|
36
|
-
import {
|
|
36
|
+
import {useCloseMobileDrawerOnActivate} from '../AppShell/useCloseMobileDrawerOnActivate';
|
|
37
37
|
import {themeProps} from '../utils/themeProps';
|
|
38
38
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
39
39
|
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
@@ -199,7 +199,9 @@ export function TopNavItem({
|
|
|
199
199
|
const pressable = usePressFeedback();
|
|
200
200
|
const LinkComponent = useLinkComponent(as);
|
|
201
201
|
const renderMode = useTopNavRenderMode();
|
|
202
|
-
const
|
|
202
|
+
const closeDrawerAfter = useCloseMobileDrawerOnActivate(
|
|
203
|
+
renderMode === 'drawer',
|
|
204
|
+
);
|
|
203
205
|
|
|
204
206
|
// A disabled item renders as a plain <a> with no href: an href-less anchor
|
|
205
207
|
// is not focusable and exposes no link affordance, so programmatic focus +
|
|
@@ -214,14 +216,13 @@ export function TopNavItem({
|
|
|
214
216
|
// Drawer mode — render as a SideNavItem-style vertical list element
|
|
215
217
|
// =========================================================================
|
|
216
218
|
if (renderMode === 'drawer') {
|
|
219
|
+
const activate = closeDrawerAfter(onClick);
|
|
217
220
|
const handleDrawerClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
218
221
|
if (isDisabled) {
|
|
219
222
|
preventDefaultClick(e);
|
|
220
223
|
return;
|
|
221
224
|
}
|
|
222
|
-
|
|
223
|
-
onClick?.(e);
|
|
224
|
-
closeMobileNav();
|
|
225
|
+
activate?.(e);
|
|
225
226
|
};
|
|
226
227
|
|
|
227
228
|
return (
|
|
@@ -32,7 +32,7 @@ import type {LinkComponentType} from '../Link/types';
|
|
|
32
32
|
import {useTopNavRenderMode} from './TopNavRenderContext';
|
|
33
33
|
import {mergeProps} from '../utils';
|
|
34
34
|
import type {BaseProps} from '../BaseProps';
|
|
35
|
-
import {
|
|
35
|
+
import {useCloseMobileDrawerOnActivate} from '../AppShell/useCloseMobileDrawerOnActivate';
|
|
36
36
|
import {themeProps} from '../utils/themeProps';
|
|
37
37
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
38
38
|
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
@@ -200,7 +200,9 @@ export function TopNavMegaMenuItem({
|
|
|
200
200
|
const pressable = usePressFeedback();
|
|
201
201
|
const renderMode = useTopNavRenderMode();
|
|
202
202
|
const LinkComponent = useLinkComponent(as);
|
|
203
|
-
const
|
|
203
|
+
const closeDrawerAfter = useCloseMobileDrawerOnActivate(
|
|
204
|
+
renderMode === 'drawer',
|
|
205
|
+
);
|
|
204
206
|
|
|
205
207
|
// =========================================================================
|
|
206
208
|
// Drawer mode — matches SideNavItem / TopNavMenu drawer appearance
|
|
@@ -208,10 +210,9 @@ export function TopNavMegaMenuItem({
|
|
|
208
210
|
if (renderMode === 'drawer') {
|
|
209
211
|
const Element = href ? LinkComponent : 'button';
|
|
210
212
|
const elementProps = Element === 'button' ? {type: 'button' as const} : {};
|
|
211
|
-
const handleDrawerClick = () => {
|
|
213
|
+
const handleDrawerClick = closeDrawerAfter(() => {
|
|
212
214
|
onClick?.();
|
|
213
|
-
|
|
214
|
-
};
|
|
215
|
+
});
|
|
215
216
|
return (
|
|
216
217
|
<Element
|
|
217
218
|
{...restProps}
|
|
@@ -39,7 +39,7 @@ import type {BaseProps} from '../BaseProps';
|
|
|
39
39
|
import {navItemStyles} from '../NavItem/navItemStyles.stylex';
|
|
40
40
|
import {useTopNavSlot} from './TopNavContext';
|
|
41
41
|
import {useTopNavRenderMode} from './TopNavRenderContext';
|
|
42
|
-
import {
|
|
42
|
+
import {useCloseMobileDrawerOnActivate} from '../AppShell/useCloseMobileDrawerOnActivate';
|
|
43
43
|
import {useLinkComponent} from '../Link/useLinkComponent';
|
|
44
44
|
import {isSafeUrl} from '../utils/safeUrl';
|
|
45
45
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -363,7 +363,9 @@ export function TopNavMenu({
|
|
|
363
363
|
}: TopNavMenuProps) {
|
|
364
364
|
const pressable = usePressFeedback();
|
|
365
365
|
const renderMode = useTopNavRenderMode();
|
|
366
|
-
const
|
|
366
|
+
const closeDrawerAfter = useCloseMobileDrawerOnActivate(
|
|
367
|
+
renderMode === 'drawer',
|
|
368
|
+
);
|
|
367
369
|
const LinkComponent = useLinkComponent();
|
|
368
370
|
const [drawerExpanded, setDrawerExpanded] = useState(false);
|
|
369
371
|
const menuId = useId();
|
|
@@ -524,10 +526,9 @@ export function TopNavMenu({
|
|
|
524
526
|
key={getMenuItemKey(item)}
|
|
525
527
|
href={item.href}
|
|
526
528
|
{...pressable}
|
|
527
|
-
onClick={(_e: React.MouseEvent) => {
|
|
529
|
+
onClick={closeDrawerAfter((_e: React.MouseEvent) => {
|
|
528
530
|
item.onClick?.();
|
|
529
|
-
|
|
530
|
-
}}
|
|
531
|
+
})}
|
|
531
532
|
{...focusOutlineProps.focusVisible(
|
|
532
533
|
navItemStyles.item,
|
|
533
534
|
interactionOverlayStyles.backgroundColor,
|
|
@@ -731,6 +731,12 @@ export class GestureHost {
|
|
|
731
731
|
* lift click (`spec:AST-094` FR28: nothing acts).
|
|
732
732
|
*/
|
|
733
733
|
private readonly cancelledFingers = new Set<number>();
|
|
734
|
+
/**
|
|
735
|
+
* The pens and pointer-read fingers that the app's lost focus cancelled,
|
|
736
|
+
* by pointer id. Each is kept until its lift, which must not click either,
|
|
737
|
+
* or until a new press.
|
|
738
|
+
*/
|
|
739
|
+
private readonly cancelledPointers = new Map<number, PointerKind>();
|
|
734
740
|
/** Touch.identifier → the element the finger landed on. */
|
|
735
741
|
private readonly touchTargets = new Map<number, EventTarget>();
|
|
736
742
|
/**
|
|
@@ -755,6 +761,8 @@ export class GestureHost {
|
|
|
755
761
|
private readonly processed = new WeakSet<Event>();
|
|
756
762
|
/** pointerId → arena id, for live mouse and pen contacts. */
|
|
757
763
|
private readonly pointerIds = new Map<number, number>();
|
|
764
|
+
/** Each contact's kind: a blur refuses a pen's or a pointer-read finger's lift, never a mouse's. */
|
|
765
|
+
private readonly pointerKinds = new Map<number, PointerKind>();
|
|
758
766
|
/**
|
|
759
767
|
* The boxes the host watches for screen-edge pans (syncLayoutWatch): each
|
|
760
768
|
* element with an enabled one, and the root for one whose band moves with
|
|
@@ -1820,13 +1828,36 @@ export class GestureHost {
|
|
|
1820
1828
|
}
|
|
1821
1829
|
}
|
|
1822
1830
|
|
|
1823
|
-
/**
|
|
1831
|
+
/**
|
|
1832
|
+
* A new press is a new gesture, as the click guard reads one, so the lifts
|
|
1833
|
+
* the blur cancelled owe nothing after it. A second finger keeps the other
|
|
1834
|
+
* fingers' refusals, since those fingers may still be down.
|
|
1835
|
+
*/
|
|
1836
|
+
private dropCancelledLifts(event: PointerEvent): void {
|
|
1837
|
+
const isSecond = event.pointerType === 'touch' && event.isPrimary === false;
|
|
1838
|
+
for (const [pointerId, kind] of [...this.cancelledPointers]) {
|
|
1839
|
+
if (pointerId === event.pointerId || !(isSecond && kind === 'touch')) {
|
|
1840
|
+
this.cancelledPointers.delete(pointerId);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
/**
|
|
1846
|
+
* After a contact ends: an armed press that is over stops listening. A
|
|
1847
|
+
* contact the lost focus cancelled still owes its lift, so the press is
|
|
1848
|
+
* not over until that lift comes. A lift the page never hears ends it at
|
|
1849
|
+
* the next press, or at a cancel.
|
|
1850
|
+
*/
|
|
1824
1851
|
private releaseArmedPress(): void {
|
|
1825
1852
|
if (!this.isArmedPress) {
|
|
1826
1853
|
return;
|
|
1827
1854
|
}
|
|
1828
1855
|
queueMicrotask(() => {
|
|
1829
|
-
if (
|
|
1856
|
+
if (
|
|
1857
|
+
this.isArmedPress &&
|
|
1858
|
+
this.arena.touches.length === 0 &&
|
|
1859
|
+
this.cancelledPointers.size === 0
|
|
1860
|
+
) {
|
|
1830
1861
|
this.isArmedPress = false;
|
|
1831
1862
|
this.syncKeptPresses();
|
|
1832
1863
|
this.syncListeners();
|
|
@@ -1857,6 +1888,7 @@ export class GestureHost {
|
|
|
1857
1888
|
return;
|
|
1858
1889
|
}
|
|
1859
1890
|
this.cancelledFingers.clear();
|
|
1891
|
+
this.cancelledPointers.clear();
|
|
1860
1892
|
this.clearClaimedLifts();
|
|
1861
1893
|
this.cancelCallerRead?.();
|
|
1862
1894
|
this.cancelCallerRead = null;
|
|
@@ -2934,13 +2966,19 @@ export class GestureHost {
|
|
|
2934
2966
|
if (locksSelectionFor('touch')) {
|
|
2935
2967
|
toLock.push({id, target, consumerElement});
|
|
2936
2968
|
}
|
|
2937
|
-
// The finger's events keep going to what the engine hit.
|
|
2969
|
+
// The finger's events keep going to what the engine hit. A disabled
|
|
2970
|
+
// participant can never claim, so a scroll never waits on it.
|
|
2938
2971
|
this.watchTarget(
|
|
2939
2972
|
touch.identifier,
|
|
2940
2973
|
native,
|
|
2941
|
-
recognizers.some(
|
|
2942
|
-
|
|
2943
|
-
|
|
2974
|
+
recognizers.some(
|
|
2975
|
+
recognizer => recognizer.isEnabled && claimsWhileMoving(recognizer),
|
|
2976
|
+
) ||
|
|
2977
|
+
(consumer?.keepsTouch != null && consumer.isEnabled?.() !== false),
|
|
2978
|
+
(consumer != null && consumer.isEnabled?.() !== false) ||
|
|
2979
|
+
recognizers.some(
|
|
2980
|
+
recognizer => recognizer.isEnabled && recognizer.claimsTouches,
|
|
2981
|
+
),
|
|
2944
2982
|
);
|
|
2945
2983
|
this.lastFed.set(id, {x: touch.clientX, y: touch.clientY, time});
|
|
2946
2984
|
this.pairTouch(id, {x: touch.clientX, y: touch.clientY, target: native});
|
|
@@ -2975,6 +3013,11 @@ export class GestureHost {
|
|
|
2975
3013
|
for (const input of untracked) {
|
|
2976
3014
|
this.arena.untrackedBegan(input);
|
|
2977
3015
|
}
|
|
3016
|
+
// Again once the new fingers are delivered: a pinch's second finger may
|
|
3017
|
+
// have landed on another one-finger pan, the next row.
|
|
3018
|
+
if (this.fingerTargets.length > 1 && !this.isFingerClaimed()) {
|
|
3019
|
+
this.failUnclaimedLoneFingerPans();
|
|
3020
|
+
}
|
|
2978
3021
|
// Stamped after the consumers judged the touch: the brake asks whether
|
|
2979
3022
|
// an EARLIER finger is still driving a scroll.
|
|
2980
3023
|
const landed = landedOn(event);
|
|
@@ -3570,6 +3613,7 @@ export class GestureHost {
|
|
|
3570
3613
|
if (!isPointerKind(event.pointerType)) {
|
|
3571
3614
|
return;
|
|
3572
3615
|
}
|
|
3616
|
+
this.dropCancelledLifts(event);
|
|
3573
3617
|
if (event.pointerType === 'touch' && this.usesTouchEvents) {
|
|
3574
3618
|
this.pairPointer(event.pointerId, {
|
|
3575
3619
|
x: event.clientX,
|
|
@@ -3578,6 +3622,13 @@ export class GestureHost {
|
|
|
3578
3622
|
});
|
|
3579
3623
|
return;
|
|
3580
3624
|
}
|
|
3625
|
+
if (event.pointerType === 'touch' && event.isPrimary === false) {
|
|
3626
|
+
// Fingers from pointer events: a second one, wherever either lands,
|
|
3627
|
+
// leaves the two to the page's pinch, as onTouchStart does. A finger
|
|
3628
|
+
// that is not the primary pointer is a second finger, even where the
|
|
3629
|
+
// host never heard the first.
|
|
3630
|
+
this.failUnclaimedLoneFingerPans();
|
|
3631
|
+
}
|
|
3581
3632
|
// A right click, a pen's barrel button, or a ctrl-click on macOS (its
|
|
3582
3633
|
// context click) starts no press (FR43).
|
|
3583
3634
|
if (event.pointerType !== 'touch' && !isPrimaryClick(event)) {
|
|
@@ -3599,6 +3650,7 @@ export class GestureHost {
|
|
|
3599
3650
|
}
|
|
3600
3651
|
const id = this.nextId++;
|
|
3601
3652
|
this.pointerIds.set(event.pointerId, id);
|
|
3653
|
+
this.pointerKinds.set(event.pointerId, event.pointerType);
|
|
3602
3654
|
if (target !== landed && target != null) {
|
|
3603
3655
|
this.retargeted.set(id, {x: event.clientX, y: event.clientY, target});
|
|
3604
3656
|
}
|
|
@@ -3620,6 +3672,15 @@ export class GestureHost {
|
|
|
3620
3672
|
consumer,
|
|
3621
3673
|
},
|
|
3622
3674
|
]);
|
|
3675
|
+
if (
|
|
3676
|
+
event.pointerType === 'touch' &&
|
|
3677
|
+
event.isPrimary === false &&
|
|
3678
|
+
!this.isFingerClaimed()
|
|
3679
|
+
) {
|
|
3680
|
+
// Again once this finger is delivered: it may have landed on another
|
|
3681
|
+
// one-finger pan.
|
|
3682
|
+
this.failUnclaimedLoneFingerPans();
|
|
3683
|
+
}
|
|
3623
3684
|
if (locksSelectionFor(event.pointerType)) {
|
|
3624
3685
|
// The presented target: what the pen is on, where the engine's own
|
|
3625
3686
|
// hit test may still see the motion's start.
|
|
@@ -3678,9 +3739,14 @@ export class GestureHost {
|
|
|
3678
3739
|
}
|
|
3679
3740
|
const id = this.pointerIds.get(event.pointerId);
|
|
3680
3741
|
if (id == null) {
|
|
3742
|
+
// A contact the lost focus cancelled: its lift clicks nothing.
|
|
3743
|
+
if (this.cancelledPointers.delete(event.pointerId)) {
|
|
3744
|
+
this.refuseCancelledLift(event);
|
|
3745
|
+
}
|
|
3681
3746
|
return;
|
|
3682
3747
|
}
|
|
3683
3748
|
this.pointerIds.delete(event.pointerId);
|
|
3749
|
+
this.pointerKinds.delete(event.pointerId);
|
|
3684
3750
|
this.unlockSelection(id);
|
|
3685
3751
|
const dispositions = this.arena.ended([
|
|
3686
3752
|
{id, x: event.clientX, y: event.clientY, time: eventTime(event)},
|
|
@@ -3784,9 +3850,12 @@ export class GestureHost {
|
|
|
3784
3850
|
}
|
|
3785
3851
|
return;
|
|
3786
3852
|
}
|
|
3853
|
+
// A contact that ends in a cancel owes no refused lift.
|
|
3854
|
+
this.cancelledPointers.delete(event.pointerId);
|
|
3787
3855
|
const id = this.pointerIds.get(event.pointerId);
|
|
3788
3856
|
if (id != null) {
|
|
3789
3857
|
this.pointerIds.delete(event.pointerId);
|
|
3858
|
+
this.pointerKinds.delete(event.pointerId);
|
|
3790
3859
|
this.unlockSelection(id, true);
|
|
3791
3860
|
this.retargeted.delete(id);
|
|
3792
3861
|
this.arena.cancelled([id], eventTime(event));
|
|
@@ -3991,20 +4060,26 @@ export class GestureHost {
|
|
|
3991
4060
|
}
|
|
3992
4061
|
}
|
|
3993
4062
|
}
|
|
4063
|
+
/** Whether a finger's drag is claimed: then two fingers make no pinch. */
|
|
4064
|
+
private isFingerClaimed(): boolean {
|
|
4065
|
+
return this.arena.touches.some(
|
|
4066
|
+
touch => touch.pointerType === 'touch' && this.arena.isClaimed(touch.id),
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
3994
4069
|
|
|
3995
4070
|
private readonly onVisibilityChange = (): void => {
|
|
3996
4071
|
if (this.doc.visibilityState === 'hidden') {
|
|
3997
4072
|
this.cancelEverything(this.arena.now());
|
|
3998
|
-
// As at a blur:
|
|
3999
|
-
//
|
|
4073
|
+
// As at a blur: an armed press's page listeners go once nothing is left
|
|
4074
|
+
// to hear.
|
|
4000
4075
|
this.releaseArmedPress();
|
|
4001
4076
|
}
|
|
4002
4077
|
};
|
|
4003
4078
|
|
|
4004
4079
|
private readonly onBlur = (): void => {
|
|
4005
4080
|
this.cancelEverything(this.arena.now());
|
|
4006
|
-
//
|
|
4007
|
-
//
|
|
4081
|
+
// An armed press's page listeners go once nothing is left to hear. A pen
|
|
4082
|
+
// or a pointer-read finger the blur cancelled still owes its lift.
|
|
4008
4083
|
this.releaseArmedPress();
|
|
4009
4084
|
};
|
|
4010
4085
|
|
|
@@ -4027,8 +4102,27 @@ export class GestureHost {
|
|
|
4027
4102
|
);
|
|
4028
4103
|
}
|
|
4029
4104
|
|
|
4105
|
+
/**
|
|
4106
|
+
* The lift of a contact the lost focus cancelled: its click is refused,
|
|
4107
|
+
* and a finger's touchend after it is cancelled.
|
|
4108
|
+
*/
|
|
4109
|
+
private refuseCancelledLift(event: PointerEvent): void {
|
|
4110
|
+
armClickGuard(this.doc, {takesLateMouse: event.pointerType === 'touch'});
|
|
4111
|
+
if (event.pointerType === 'touch') {
|
|
4112
|
+
// Noted as a claimed lift is: capped, and gone after its time.
|
|
4113
|
+
this.noteClaimedLift(event);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4030
4117
|
/** The app lost focus: UIKit cancels every touch in flight. */
|
|
4031
4118
|
private cancelEverything(time: number): void {
|
|
4119
|
+
// The browser may keep a pen or a pointer-read finger down: its lift must
|
|
4120
|
+
// not click. A mouse button held through the blur keeps its click.
|
|
4121
|
+
for (const [pointerId, kind] of this.pointerKinds) {
|
|
4122
|
+
if (kind !== 'mouse') {
|
|
4123
|
+
this.cancelledPointers.set(pointerId, kind);
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4032
4126
|
const ids = [...this.pointerIds.values()];
|
|
4033
4127
|
for (const id of ids) {
|
|
4034
4128
|
this.unlockSelection(id, true);
|
|
@@ -4042,6 +4136,7 @@ export class GestureHost {
|
|
|
4042
4136
|
}
|
|
4043
4137
|
}
|
|
4044
4138
|
this.pointerIds.clear();
|
|
4139
|
+
this.pointerKinds.clear();
|
|
4045
4140
|
this.retargeted.clear();
|
|
4046
4141
|
this.fingerPointers.clear();
|
|
4047
4142
|
this.pendingPointers.clear();
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* option-id resolver for the owning listbox
|
|
9
9
|
* @output Exports useHighlightedOptionScroll: keeps the highlighted option
|
|
10
10
|
* scrolled into view for keyboard navigation while hover-driven highlights
|
|
11
|
-
* never scroll
|
|
11
|
+
* never scroll, and a hover after a keyboard move waits for the pointer to
|
|
12
|
+
* move or press; the hover callback is stable while `setHighlightedIndex` is
|
|
12
13
|
* @position Internal hook; used by useCombobox, useMultiCombobox,
|
|
13
14
|
* BaseTypeahead, and DateTimeInput — the owners of combobox highlight state
|
|
14
15
|
*/
|
|
@@ -27,6 +28,21 @@ interface UseHighlightedOptionScrollOptions {
|
|
|
27
28
|
itemCount?: number;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Highlights the hovered option at `index`. `onHighlight` runs when the hover
|
|
33
|
+
* takes effect: at once, or after the pointer moves or presses when a
|
|
34
|
+
* keyboard move holds the highlight.
|
|
35
|
+
*/
|
|
36
|
+
export type HighlightFromHover = (
|
|
37
|
+
index: number,
|
|
38
|
+
onHighlight?: () => void,
|
|
39
|
+
) => void;
|
|
40
|
+
|
|
41
|
+
interface WaitingHover {
|
|
42
|
+
index: number;
|
|
43
|
+
onHighlight?: () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
/**
|
|
31
47
|
* Keep the highlighted option visible during keyboard navigation — but not for
|
|
32
48
|
* hover highlights. Hover must highlight only: scrollIntoView moves the next
|
|
@@ -34,6 +50,10 @@ interface UseHighlightedOptionScrollOptions {
|
|
|
34
50
|
* scrolls again — a runaway auto-scroll loop with no user input.
|
|
35
51
|
* Keyboard paths call the raw setter directly and keep their scrolling; hover
|
|
36
52
|
* paths call the returned callback.
|
|
53
|
+
*
|
|
54
|
+
* A keyboard move holds the highlight. A key that scrolls the list moves rows
|
|
55
|
+
* under a still mouse, and the browser reports the row under it as entered;
|
|
56
|
+
* that row takes the highlight only once the pointer moves or presses.
|
|
37
57
|
*/
|
|
38
58
|
export function useHighlightedOptionScroll({
|
|
39
59
|
isOpen,
|
|
@@ -41,19 +61,93 @@ export function useHighlightedOptionScroll({
|
|
|
41
61
|
setHighlightedIndex,
|
|
42
62
|
getOptionId,
|
|
43
63
|
itemCount,
|
|
44
|
-
}: UseHighlightedOptionScrollOptions):
|
|
64
|
+
}: UseHighlightedOptionScrollOptions): HighlightFromHover {
|
|
45
65
|
// The callback reads no render state, so memoized rows keep one function.
|
|
46
66
|
const hoveredIndexRef = useRef<number | null>(null);
|
|
47
67
|
const shownIndexRef = useRef(highlightedIndex);
|
|
68
|
+
// The hover that came while a keyboard move held the highlight.
|
|
69
|
+
const waitingHoverRef = useRef<WaitingHover | null>(null);
|
|
70
|
+
// Set only while a keyboard move holds the highlight.
|
|
71
|
+
const removeHoldListenersRef = useRef<(() => void) | null>(null);
|
|
72
|
+
const setHighlightedIndexRef = useRef(setHighlightedIndex);
|
|
73
|
+
// The pointer's last screen position a hold saw, kept across holds.
|
|
74
|
+
const lastPointerRef = useRef<{x: number; y: number} | null>(null);
|
|
75
|
+
|
|
76
|
+
const applyHover = useCallback((hover: WaitingHover) => {
|
|
77
|
+
hoveredIndexRef.current = hover.index;
|
|
78
|
+
setHighlightedIndexRef.current(hover.index);
|
|
79
|
+
hover.onHighlight?.();
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
const endHold = useCallback(() => {
|
|
83
|
+
removeHoldListenersRef.current?.();
|
|
84
|
+
removeHoldListenersRef.current = null;
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
const releaseHold = useCallback(() => {
|
|
88
|
+
endHold();
|
|
89
|
+
const waiting = waitingHoverRef.current;
|
|
90
|
+
waitingHoverRef.current = null;
|
|
91
|
+
if (waiting != null) {
|
|
92
|
+
applyHover(waiting);
|
|
93
|
+
}
|
|
94
|
+
}, [applyHover, endHold]);
|
|
48
95
|
|
|
49
|
-
|
|
50
|
-
|
|
96
|
+
// Holds the highlight until the pointer moves or presses. The listeners
|
|
97
|
+
// live only while the hold does. Movement is read by screen coordinates: a
|
|
98
|
+
// scroll moves the rows, not the pointer. The browser reports a row as
|
|
99
|
+
// entered before the move that reaches it, so the first real move gives
|
|
100
|
+
// that row the highlight. A press releases at once, so a held press lights
|
|
101
|
+
// the pressed row.
|
|
102
|
+
const startHold = useCallback(() => {
|
|
103
|
+
waitingHoverRef.current = null;
|
|
104
|
+
if (removeHoldListenersRef.current != null) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const onPointerMove = (event: PointerEvent) => {
|
|
108
|
+
const last = lastPointerRef.current;
|
|
109
|
+
lastPointerRef.current = {x: event.screenX, y: event.screenY};
|
|
110
|
+
if (
|
|
111
|
+
last != null &&
|
|
112
|
+
last.x === event.screenX &&
|
|
113
|
+
last.y === event.screenY
|
|
114
|
+
) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
releaseHold();
|
|
118
|
+
};
|
|
119
|
+
const options = {capture: true, passive: true} as const;
|
|
120
|
+
document.addEventListener('pointermove', onPointerMove, options);
|
|
121
|
+
document.addEventListener('pointerdown', releaseHold, options);
|
|
122
|
+
removeHoldListenersRef.current = () => {
|
|
123
|
+
document.removeEventListener('pointermove', onPointerMove, options);
|
|
124
|
+
document.removeEventListener('pointerdown', releaseHold, options);
|
|
125
|
+
};
|
|
126
|
+
}, [releaseHold]);
|
|
127
|
+
|
|
128
|
+
const highlightFromHover = useCallback<HighlightFromHover>(
|
|
129
|
+
(index, onHighlight) => {
|
|
130
|
+
if (removeHoldListenersRef.current != null) {
|
|
131
|
+
waitingHoverRef.current = {index, onHighlight};
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
51
134
|
hoveredIndexRef.current = index;
|
|
52
135
|
setHighlightedIndex(index);
|
|
136
|
+
onHighlight?.();
|
|
53
137
|
},
|
|
54
138
|
[setHighlightedIndex],
|
|
55
139
|
);
|
|
56
140
|
|
|
141
|
+
// Effect: keeps the hold's document listeners calling the current setter.
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
setHighlightedIndexRef.current = setHighlightedIndex;
|
|
144
|
+
}, [setHighlightedIndex]);
|
|
145
|
+
|
|
146
|
+
// Effect: removes the hold's document listeners on unmount.
|
|
147
|
+
useEffect(() => endHold, [endHold]);
|
|
148
|
+
|
|
149
|
+
// Effect: scrolls the highlighted option into view in the document, and
|
|
150
|
+
// starts or ends the hold on its document listeners.
|
|
57
151
|
useEffect(() => {
|
|
58
152
|
// Only a move to the hovered row came from the hover. A hover on the row
|
|
59
153
|
// already highlighted changes nothing, so a later run for the same row
|
|
@@ -63,13 +157,23 @@ export function useHighlightedOptionScroll({
|
|
|
63
157
|
const isFromHover =
|
|
64
158
|
hasMoved && hoveredIndexRef.current === highlightedIndex;
|
|
65
159
|
hoveredIndexRef.current = null;
|
|
66
|
-
if (isFromHover
|
|
160
|
+
if (isFromHover) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// A cleared highlight, a closed list, or a list that is not shown (a
|
|
164
|
+
// closed CommandPalette reports itself as open) has nothing to hold.
|
|
165
|
+
const option =
|
|
166
|
+
isOpen && highlightedIndex >= 0
|
|
167
|
+
? document.getElementById(getOptionId(highlightedIndex))
|
|
168
|
+
: null;
|
|
169
|
+
if (option == null) {
|
|
170
|
+
waitingHoverRef.current = null;
|
|
171
|
+
endHold();
|
|
67
172
|
return;
|
|
68
173
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}, [isOpen, highlightedIndex, getOptionId, itemCount]);
|
|
174
|
+
startHold();
|
|
175
|
+
option.scrollIntoView?.({block: 'nearest'});
|
|
176
|
+
}, [isOpen, highlightedIndex, getOptionId, itemCount, endHold, startHold]);
|
|
73
177
|
|
|
74
178
|
return highlightFromHover;
|
|
75
179
|
}
|
|
@@ -483,7 +483,10 @@ export function useMenuPress(options: UseMenuPressOptions): UseMenuPressReturn {
|
|
|
483
483
|
// Detach before acting: the activation usually closes the menu.
|
|
484
484
|
endGesture();
|
|
485
485
|
// Once armed, the one swallower stays installed.
|
|
486
|
-
armClickGuard(doc, {
|
|
486
|
+
armClickGuard(doc, {
|
|
487
|
+
staysInstalled: true,
|
|
488
|
+
cancelsTouchEnd: native?.pointerType === 'touch',
|
|
489
|
+
});
|
|
487
490
|
if (native != null) {
|
|
488
491
|
activate(effect.row, native);
|
|
489
492
|
}
|
|
@@ -492,7 +495,10 @@ export function useMenuPress(options: UseMenuPressOptions): UseMenuPressReturn {
|
|
|
492
495
|
case 'settle': {
|
|
493
496
|
endGesture();
|
|
494
497
|
if (effect.stray) {
|
|
495
|
-
armClickGuard(doc, {
|
|
498
|
+
armClickGuard(doc, {
|
|
499
|
+
staysInstalled: true,
|
|
500
|
+
cancelsTouchEnd: native?.pointerType === 'touch',
|
|
501
|
+
});
|
|
496
502
|
}
|
|
497
503
|
if (effect.dismiss) {
|
|
498
504
|
optionsRef.current.onDismiss?.();
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const english: Readonly<Record<string, string>> = {
|
|
10
10
|
"@astryx.powersearch.allFiltersRemoved": "All filters removed",
|
|
11
|
+
"@astryx.powersearch.editableFiltersRemoved": "Editable filters removed",
|
|
11
12
|
"@astryx.powersearch.editor.apply": "Apply",
|
|
12
13
|
"@astryx.powersearch.label": "Search",
|
|
13
14
|
"@astryx.powersearch.placeholder": "Search…",
|