Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fling-over-page-keeps-layer-open.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] A fling over the page no longer closes an open popover or selector list, and no longer taps the page element under the finger.
Comment thread
astracat-bot[bot] marked this conversation as resolved.
Resolved

@cixzhang
11 changes: 9 additions & 2 deletions docs/architecture/layer-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ verified_by:
packages/core/src/Toast/ToastViewport.test.tsx,
packages/core/src/Toast/ToastViewport.modalHost.test.tsx,
packages/core/src/Tooltip/__tests__/TooltipClosedTriggerAnchor.interaction.browser.spec.ts,
packages/core/src/Layer/gestureCounter.test.ts,
packages/core/src/Layer/outsidePress.test.ts,
packages/core/src/Layer/__tests__/LayerPanLightDismiss.interaction.browser.spec.ts,
]
deciding_specs: [spec:AST-038, spec:AST-059]
---
Expand Down Expand Up @@ -525,7 +528,11 @@ layers use Layer rendering without joining Escape/platform dismissal.
browser close do not double-fire state or `onHide` updates.
- **INV7 — Existing native-light-dismiss guards identify gestures.** The same
physical press that closes a Layer cannot immediately reopen it; menu-hover also
suppresses exposure-induced hover.
suppresses exposure-induced hover. A one-finger touch that starts outside an
open layer and travels the shared pan distance is a gesture, not an outside
press, even when the browser reads it as a tap; a tap outside still
dismisses. An anchored menu instead closes as that pan begins
(`spec:AST-094` FR32).
Comment thread
astracat-bot[bot] marked this conversation as resolved.
Resolved
- **INV8 — Current Escape delivery follows registered layers.** The shared stack
uses present `close` and `block` entries in its current depth, containment, and
stable-registration order.
Expand Down Expand Up @@ -849,7 +856,7 @@ this current architecture record.
| INV12 | The `Core/Layer` viewport-inset stories' first-frame comparison, and `useLayer.test.tsx`'s synchronous anchor read; for the check after a scroll stops, `useLayer.test.tsx`'s `scrollend` and 150 ms fallback cases (one check, in the first painted frame after the stop, with no read in the scroll handler) and the Tooltip suites' scroll-edge cases; a Chromium trace at 4× CPU of a wheel scroll that carries a Tooltip's trigger to the scroll edge counts the forced layouts per scroll before and after DEC-8: page scroll 27–42 → 5, a scrolling panel 7 → 5, the Slider value bubble 12–22 → 5 | A layer whose first painted rectangle differs from its settled one; a box check, a re-pick or a forced layout for a layer in a scroll frame or a scroll handler; a re-pick before the first painted frame after a scroll stops, or more than one per stop; a painted frame that shows a layer that keeps its anchor clear over its anchor |
| INV11, INV12 | The software keyboard's cap: `useLayerKeyboardClearance.test.tsx` for its rule, its timing, its read before first paint, and the inset's hold; the `PopoverKeyboard`, `keyboardCappedLists` and `SelectorKeyboardInset` browser specs for its rendered geometry | A layer that runs under the keyboard, or is capped though the keyboard does not reach it; a cap that changes more than once per keyboard movement or under a press; a layer opened with the keyboard up whose first frame is uncapped or paints its content past its box; a layer that reads the inset and moves down when the keyboard report ends |
| INV13 | `layerExitCopy.test.ts` and `layerExitCopy.interaction.browser.spec.ts` | A copy with an id, a form name, a form owner, or pointer reach; a copy off the layer's drawn box; a copy outside the top layer; a second copy for one layer |
| INV6, INV7 | `useLayer.test.tsx`, `Popover.test.tsx`, `DropdownMenu.test.tsx`, and `useMenuHover.test.tsx` | Duplicate close callback or the same press/re-hover reopens a surface |
| INV6, INV7 | `useLayer.test.tsx`, `Popover.test.tsx`, `DropdownMenu.test.tsx`, `useMenuHover.test.tsx`, `gestureCounter.test.ts` and `outsidePress.test.ts`; `LayerPanLightDismiss.interaction.browser.spec.ts` in Chromium for a touch that pans outside an open Popover, Selector list, DropdownMenu or ContextMenu, with and without `pointercancel`, including a pan that the browser reads as a tap | Duplicate close callback or the same press/re-hover reopens a surface; a pan outside that light-dismisses an open non-menu layer, or a tap outside that leaves it open |
| INV8 | `useLayerDismissal.test.tsx`, `layerDismissalInvariants.test.tsx`, and `useFocusTrap.test.tsx` | Current top registered layer is skipped, two layers close, or a blocker leaks through |
| INV9 | Representative Dialog, ContextMenu, Tooltip/HoverCard, and BottomSheet source/tests; `useBackdropPress.test.tsx`; `outsidePress.test.ts` | A current local channel silently changes ownership or policy |
| INV10 | `LayerProvider.tsx`, `toastHost.tsx`, `LayerProvider.test.tsx`, `toastHost.test.tsx`, `toastHost.lateRegistration.test.tsx`, `useToast.tsx`, `ToastViewport.test.tsx`, `ToastViewport.modalHost.test.tsx`, and `scripts/layer-toast-bundle.test.mjs` | Provider begins relocating ordinary layers, Toast fallback loses its current lifecycle, a default provider renders differently from no provider, a Layer import loads a Toast module, or a toast added before Toast loads is lost |
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/Breadcrumbs/BreadcrumbItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import type {BaseProps} from '../BaseProps';
import {themeProps} from '../utils/themeProps';
import {focusOutlineProps} from '../utils/focusOutline.stylex';
import {Icon} from '../Icon';
import {usePopover} from '../Popover/usePopover';
import {usePopoverInternal} from '../Popover/usePopover';
import {useListFocus} from '../hooks/useListFocus';
import {useMenuPress} from '../hooks/useMenuPress';
import {pressOnItemsOnly} from '../hooks/menuPressOnItems';
Expand Down Expand Up @@ -552,7 +552,9 @@ function BreadcrumbMenuTrigger({
// to activate the tracking they read, so it activates it itself.
const {ref: modalityRef} = useInteractionModalityTracking();

const popover = usePopover({
const popover = usePopoverInternal({
// The menu, not the layer, owns what a touch outside that pans does.
ownsOutsidePan: true,
hasLightDismiss: true,
hasCloseButton: false,
hasAutoFocus: false,
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/DropdownMenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import React, {
} from 'react';
import {readAccessibleName} from '../utils/accessibleName';
import * as stylex from '@stylexjs/stylex';
import {usePopover} from '../Popover/usePopover';
import {usePopoverInternal} from '../Popover/usePopover';
import {holdEntranceInput} from '../Layer/holdEntranceInput';
import {
claimPress,
Expand Down Expand Up @@ -1108,10 +1108,12 @@ function DropdownMenuPopover({
? button.label
: undefined;

const popover = usePopover({
const popover = usePopoverInternal({
onHide: handleLayerHide,
onShow: handleLayerShow,
hasLightDismiss: true,
// The menu, not the layer, owns what a touch outside that pans does.
ownsOutsidePan: true,
hasCloseButton: false,
hasAutoFocus: false,
// Without a header or footer the popup's own role="menu" is the exposed
Expand Down
Loading
Loading