@jobber/components 9.5.3 → 9.6.0
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/Combobox.d.ts +1 -0
- package/Combobox.js +17 -0
- package/dist/Autocomplete/index.cjs +0 -2
- package/dist/Autocomplete/index.mjs +0 -2
- package/dist/Autocomplete-cjs.js +4 -5
- package/dist/Autocomplete-es.js +1 -2
- package/dist/Card/index.cjs +2 -2
- package/dist/Card/index.mjs +2 -2
- package/dist/Chip/index.cjs +0 -2
- package/dist/Chip/index.mjs +0 -2
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +0 -2
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +0 -2
- package/dist/Chips/InternalChipDismissible/index.cjs +0 -2
- package/dist/Chips/InternalChipDismissible/index.mjs +0 -2
- package/dist/Chips/index.cjs +0 -2
- package/dist/Chips/index.mjs +0 -2
- package/dist/Combobox/Combobox.contentSlots.d.ts +5 -0
- package/dist/Combobox/Combobox.context.d.ts +34 -0
- package/dist/Combobox/Combobox.d.ts +69 -0
- package/dist/Combobox/Combobox.footer.d.ts +7 -0
- package/dist/Combobox/Combobox.items.d.ts +25 -0
- package/dist/Combobox/Combobox.responsive.d.ts +54 -0
- package/dist/Combobox/Combobox.selectAll.d.ts +4 -0
- package/dist/Combobox/Combobox.slotFactory.d.ts +7 -0
- package/dist/Combobox/Combobox.testUtils.d.ts +12 -0
- package/dist/Combobox/Combobox.types.d.ts +166 -0
- package/dist/Combobox/index.cjs +35 -0
- package/dist/Combobox/index.d.ts +2 -0
- package/dist/Combobox/index.mjs +29 -0
- package/dist/Combobox-cjs.js +675 -0
- package/dist/Combobox-es.js +673 -0
- package/dist/ComboboxPrimitive-cjs.js +88 -47
- package/dist/ComboboxPrimitive-es.js +39 -23
- package/dist/{ComboboxChipRemove-cjs.js → ComboboxSeparator-cjs.js} +501 -455
- package/dist/{ComboboxChipRemove-es.js → ComboboxSeparator-es.js} +500 -456
- package/dist/ConfirmationModal/index.cjs +0 -2
- package/dist/ConfirmationModal/index.mjs +0 -2
- package/dist/ContentBlock/index.cjs +1 -0
- package/dist/ContentBlock/index.mjs +1 -0
- package/dist/ContentBlock-cjs.js +2 -18
- package/dist/ContentBlock-es.js +1 -17
- package/dist/DataDump/index.cjs +2 -2
- package/dist/DataDump/index.mjs +2 -2
- package/dist/DataList/components/DataListActions/index.cjs +0 -2
- package/dist/DataList/components/DataListActions/index.mjs +0 -2
- package/dist/DataList/components/DataListBulkActions/index.cjs +0 -2
- package/dist/DataList/components/DataListBulkActions/index.mjs +0 -2
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +0 -2
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +0 -2
- package/dist/DataList/components/DataListFilters/index.cjs +0 -2
- package/dist/DataList/components/DataListFilters/index.mjs +0 -2
- package/dist/DataList/components/DataListHeader/index.cjs +0 -2
- package/dist/DataList/components/DataListHeader/index.mjs +0 -2
- package/dist/DataList/components/DataListItem/index.cjs +0 -2
- package/dist/DataList/components/DataListItem/index.mjs +0 -2
- package/dist/DataList/components/DataListItemActions/index.cjs +0 -2
- package/dist/DataList/components/DataListItemActions/index.mjs +0 -2
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +0 -2
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +0 -2
- package/dist/DataList/components/DataListItems/index.cjs +0 -2
- package/dist/DataList/components/DataListItems/index.mjs +0 -2
- package/dist/DataList/components/DataListLayout/index.cjs +0 -2
- package/dist/DataList/components/DataListLayout/index.mjs +0 -2
- package/dist/DataList/components/DataListLayoutActions/index.cjs +0 -2
- package/dist/DataList/components/DataListLayoutActions/index.mjs +0 -2
- package/dist/DataList/index.cjs +0 -2
- package/dist/DataList/index.mjs +0 -2
- package/dist/DatePicker/index.cjs +0 -2
- package/dist/DatePicker/index.mjs +0 -2
- package/dist/DatePicker-cjs.js +4 -5
- package/dist/DatePicker-es.js +1 -2
- package/dist/Dialog/index.cjs +2 -2
- package/dist/Dialog/index.mjs +2 -2
- package/dist/DrawerDescription-cjs.js +268 -99
- package/dist/DrawerDescription-es.js +270 -103
- package/dist/DrawerTitle-cjs.js +2 -6
- package/dist/DrawerTitle-es.js +3 -7
- package/dist/FieldDescription-cjs.js +184 -181
- package/dist/FieldDescription-es.js +187 -184
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +0 -2
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +0 -2
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +0 -2
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +0 -2
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +0 -2
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +0 -2
- package/dist/FilterPicker/index.cjs +0 -2
- package/dist/FilterPicker/index.mjs +0 -2
- package/dist/FilterPickerContent-cjs.js +4 -5
- package/dist/FilterPickerContent-es.js +1 -2
- package/dist/FormatFile/index.cjs +0 -2
- package/dist/FormatFile/index.mjs +0 -2
- package/dist/Gallery/index.cjs +0 -2
- package/dist/Gallery/index.mjs +0 -2
- package/dist/InputDate/index.cjs +0 -2
- package/dist/InputDate/index.mjs +0 -2
- package/dist/InputNumber/index.cjs +2 -2
- package/dist/InputNumber/index.mjs +2 -2
- package/dist/InputNumber/types.d.ts +5 -0
- package/dist/InputNumber-cjs.js +2 -2
- package/dist/InputNumber-es.js +2 -2
- package/dist/LightBox/index.cjs +0 -2
- package/dist/LightBox/index.mjs +0 -2
- package/dist/Menu/index.cjs +2 -2
- package/dist/Menu/index.mjs +2 -2
- package/dist/Menu-cjs.js +4 -5
- package/dist/Menu-es.js +1 -2
- package/dist/MenuSubmenuTrigger-cjs.js +206 -164
- package/dist/MenuSubmenuTrigger-es.js +194 -152
- package/dist/Modal/index.cjs +0 -2
- package/dist/Modal/index.mjs +1 -3
- package/dist/NumberFieldInput-cjs.js +187 -308
- package/dist/NumberFieldInput-es.js +187 -308
- package/dist/Page/index.cjs +2 -2
- package/dist/Page/index.mjs +2 -2
- package/dist/Popover/index.cjs +0 -2
- package/dist/Popover/index.mjs +0 -2
- package/dist/Popover-cjs.js +7 -8
- package/dist/Popover-es.js +1 -2
- package/dist/ScrollAreaViewport-cjs.js +747 -1093
- package/dist/ScrollAreaViewport-es.js +734 -1081
- package/dist/Select/index.cjs +2 -2
- package/dist/Select/index.mjs +2 -2
- package/dist/SelectGroupLabel-cjs.js +207 -306
- package/dist/SelectGroupLabel-es.js +203 -302
- package/dist/SelectPrimitive-cjs.js +11 -17
- package/dist/SelectPrimitive-es.js +1 -7
- package/dist/Tooltip/index.cjs +0 -2
- package/dist/Tooltip/index.mjs +0 -2
- package/dist/Tooltip-cjs.js +6 -7
- package/dist/Tooltip-es.js +1 -2
- package/dist/buttonRenderAdapter-cjs.js +2 -1
- package/dist/buttonRenderAdapter-es.js +2 -1
- package/dist/dialogReturnFocus-cjs.js +1 -1
- package/dist/dialogReturnFocus-es.js +2 -2
- package/dist/docs/InputNumber/InputNumber.md +2 -2
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +1 -1
- package/dist/floating-ui.react-cjs.js +2557 -185
- package/dist/floating-ui.react-es.js +2512 -149
- package/dist/getMappedBreakpointWidth-cjs.js +21 -0
- package/dist/getMappedBreakpointWidth-es.js +18 -0
- package/dist/index.cjs +15 -10
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +14 -10
- package/dist/mergeClassName-cjs.js +12 -0
- package/dist/mergeClassName-es.js +10 -0
- package/dist/primitives/BottomSheet/index.cjs +1 -1
- package/dist/primitives/BottomSheet/index.mjs +1 -1
- package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +2 -2
- package/dist/primitives/ComboboxPrimitive/index.cjs +4 -5
- package/dist/primitives/ComboboxPrimitive/index.mjs +4 -5
- package/dist/primitives/SelectPrimitive/index.cjs +1 -2
- package/dist/primitives/SelectPrimitive/index.mjs +1 -2
- package/dist/primitives/index.cjs +3 -3
- package/dist/primitives/index.mjs +3 -3
- package/dist/scrollEdges-cjs.js +1 -11
- package/dist/scrollEdges-es.js +1 -11
- package/dist/scrollable-cjs.js +58 -0
- package/dist/scrollable-es.js +54 -0
- package/dist/styles.css +759 -42
- package/dist/unstyledPrimitives/index.cjs +720 -478
- package/dist/unstyledPrimitives/index.mjs +663 -421
- package/dist/useCompositeListItem-cjs.js +2108 -361
- package/dist/useCompositeListItem-es.js +2076 -331
- package/dist/useLabel-cjs.js +2 -3
- package/dist/useLabel-es.js +2 -3
- package/dist/useRenderElement-cjs.js +9 -7
- package/dist/useRenderElement-es.js +9 -7
- package/dist/useScrollLock-cjs.js +819 -344
- package/dist/useScrollLock-es.js +732 -296
- package/dist/useScrollToActive-cjs.js +4 -5
- package/dist/useScrollToActive-es.js +1 -2
- package/dist/utils/meta/meta.json +43 -1
- package/package.json +8 -3
- package/dist/floating-ui.react-dom-cjs.js +0 -2103
- package/dist/floating-ui.react-dom-es.js +0 -2072
- package/dist/floating-ui.utils-cjs.js +0 -342
- package/dist/floating-ui.utils-es.js +0 -300
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { E as EMPTY_OBJECT, f as formatErrorMessage, u as useRenderElement, m as mergeProps } from './useRenderElement-es.js';
|
|
3
|
-
import {
|
|
2
|
+
import { E as EMPTY_OBJECT, N as NOOP, f as formatErrorMessage, u as useRenderElement, m as mergeProps } from './useRenderElement-es.js';
|
|
3
|
+
import { aK as FloatingRootStore, aL as PopupTriggerMap, b3 as shimExports, a6 as popupStateMapping, t as transitionStatusMapping, a2 as FloatingPortal, a3 as InternalBackdrop, a4 as inertValue, W as useDismiss, e as getTarget, a0 as contains, b2 as useScrollLock, c as useIsoLayoutEffect, b4 as usePopupInteractionProps, X as ReactStore, b5 as setPopupOpenState, b6 as usePopupRootStore, b7 as usePopupRootSync, b8 as useImplicitActiveTrigger, b9 as useOpenStateTransitions, h as createChangeEventDetails, k as imperativeAction, ba as PopupHandleAttachment, u as useBaseUiId, bb as useTriggerDataForwarding, f as useButton, V as useClick, bc as useOpenMethodTriggerProps, bd as CLICK_TRIGGER_IDENTIFIER, be as triggerOpenStateMapping, p as popupTransitionStateMapping, bf as CommonPopupDataAttributes, d as useStableCallback, o as ownerDocument, S as SafeReact, r as error, l as useOpenChangeComplete, F as FOCUSABLE_POPUP_PROPS, n as FloatingFocusManager, v as useControlled, z as none, bg as android, I as getWindow, y as addEventListener, bh as closeWatcher, a8 as useAnimationFrame, au as isElement, D as activeElement, bi as TransitionStatusDataAttributes, bj as swipe, bk as BASE_UI_SWIPE_IGNORE_SELECTOR, q as useTimeout, a7 as useDirection, a9 as webkit } from './useScrollLock-es.js';
|
|
4
4
|
import { c as clamp, C as COMPOSITE_KEYS, u as useCSPContext, s as styleDisableScrollbar, n as normalizeScrollOffset } from './scrollEdges-es.js';
|
|
5
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
-
import { K as getWindow, i as isHTMLElement, d as isLastTraversableNode, e as getParentNode, h as getComputedStyle$1, b as isElement } from './floating-ui.utils-es.js';
|
|
7
6
|
import * as ReactDOM from 'react-dom';
|
|
7
|
+
import { f as findScrollableTouchTarget, h as hasScrollableAncestor } from './scrollable-es.js';
|
|
8
8
|
|
|
9
9
|
function getEmptyRootContext() {
|
|
10
10
|
return new FloatingRootStore({
|
|
@@ -58,12 +58,12 @@ function createPopupFloatingRootContext(triggerElements, floatingId, nested = fa
|
|
|
58
58
|
onOpenChange: undefined
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
-
const activeTriggerIdSelector =
|
|
62
|
-
const openSelector =
|
|
63
|
-
const popupIdSelector =
|
|
61
|
+
const activeTriggerIdSelector = state => state.triggerIdProp ?? state.activeTriggerId;
|
|
62
|
+
const openSelector = state => state.openProp ?? state.open;
|
|
63
|
+
const popupIdSelector = state => {
|
|
64
64
|
const popupId = state.popupElement?.id ?? state.floatingId;
|
|
65
65
|
return popupId || undefined;
|
|
66
|
-
}
|
|
66
|
+
};
|
|
67
67
|
function triggerOwnsOpenPopup(state, triggerId) {
|
|
68
68
|
return triggerId !== undefined && openSelector(state) && activeTriggerIdSelector(state) === triggerId;
|
|
69
69
|
}
|
|
@@ -75,47 +75,81 @@ function triggerOwnsOpenPopupOrIsOnlyTrigger(state, triggerId) {
|
|
|
75
75
|
}
|
|
76
76
|
const popupStoreSelectors = {
|
|
77
77
|
open: openSelector,
|
|
78
|
-
mounted:
|
|
79
|
-
transitionStatus:
|
|
80
|
-
floatingRootContext:
|
|
81
|
-
triggerCount:
|
|
82
|
-
preventUnmountingOnClose:
|
|
83
|
-
payload:
|
|
78
|
+
mounted: state => state.mounted,
|
|
79
|
+
transitionStatus: state => state.transitionStatus,
|
|
80
|
+
floatingRootContext: state => state.floatingRootContext,
|
|
81
|
+
triggerCount: state => state.triggerCount,
|
|
82
|
+
preventUnmountingOnClose: state => state.preventUnmountingOnClose,
|
|
83
|
+
payload: state => state.payload,
|
|
84
84
|
activeTriggerId: activeTriggerIdSelector,
|
|
85
|
-
activeTriggerElement:
|
|
85
|
+
activeTriggerElement: state => state.mounted ? state.activeTriggerElement : null,
|
|
86
86
|
popupId: popupIdSelector,
|
|
87
87
|
/**
|
|
88
88
|
* Whether the trigger with the given ID was used to open the popup.
|
|
89
89
|
*/
|
|
90
|
-
isTriggerActive:
|
|
90
|
+
isTriggerActive: (state, triggerId) => triggerId !== undefined && activeTriggerIdSelector(state) === triggerId,
|
|
91
91
|
/**
|
|
92
92
|
* Whether the popup is open and was activated by a trigger with the given ID.
|
|
93
93
|
*/
|
|
94
|
-
isOpenedByTrigger:
|
|
94
|
+
isOpenedByTrigger: (state, triggerId) => triggerOwnsOpenPopup(state, triggerId),
|
|
95
95
|
/**
|
|
96
96
|
* Whether the popup is mounted and was activated by a trigger with the given ID.
|
|
97
97
|
*/
|
|
98
|
-
isMountedByTrigger:
|
|
99
|
-
triggerProps:
|
|
98
|
+
isMountedByTrigger: (state, triggerId) => triggerId !== undefined && activeTriggerIdSelector(state) === triggerId && state.mounted,
|
|
99
|
+
triggerProps: (state, isActive) => isActive ? state.activeTriggerProps : state.inactiveTriggerProps,
|
|
100
100
|
/**
|
|
101
101
|
* Popup id for the trigger that currently owns the open popup.
|
|
102
102
|
*/
|
|
103
|
-
triggerPopupId:
|
|
104
|
-
popupProps:
|
|
105
|
-
popupElement:
|
|
106
|
-
positionerElement:
|
|
103
|
+
triggerPopupId: (state, triggerId) => triggerOwnsOpenPopupOrIsOnlyTrigger(state, triggerId) ? popupIdSelector(state) : undefined,
|
|
104
|
+
popupProps: state => state.popupProps,
|
|
105
|
+
popupElement: state => state.popupElement,
|
|
106
|
+
positionerElement: state => state.positionerElement
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Store members a detached handle-backed trigger reads or invokes for trigger registration and data
|
|
111
|
+
* forwarding. `set`/`update` are included only for trigger-count and trigger-data bookkeeping; on a
|
|
112
|
+
* detached (inert) store they are intentionally no-ops, so a write through them is not guaranteed to
|
|
113
|
+
* be durable. Component handle-store views Pick these from their concrete store (preserving its
|
|
114
|
+
* context and selectors) and add any component-specific trigger-invoked members such as `setOpen`.
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The subset of a popup store that trigger registration and data forwarding rely on. Narrow enough
|
|
119
|
+
* that an inert store can be passed while detached.
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Reads the store currently exposed by a popup handle and subscribes to store-pointer changes.
|
|
124
|
+
* Detached triggers use this to follow a handle as a root attaches or detaches: while no root is
|
|
125
|
+
* attached, the handle exposes its fallback store; once a root attaches, subscribers re-render and
|
|
126
|
+
* read from the live root store.
|
|
127
|
+
*
|
|
128
|
+
* Returns `undefined` when no handle is provided so callers can fall back to their root context.
|
|
129
|
+
*
|
|
130
|
+
* @param handle The popup handle to read from, or `undefined` when the trigger is not handle-bound.
|
|
131
|
+
*/
|
|
132
|
+
function usePopupHandleStore(handle) {
|
|
133
|
+
const subscribe = React.useCallback(listener => {
|
|
134
|
+
if (handle === undefined) {
|
|
135
|
+
return NOOP;
|
|
136
|
+
}
|
|
137
|
+
return handle.subscribeStore(listener);
|
|
138
|
+
}, [handle]);
|
|
139
|
+
const getSnapshot = React.useCallback(() => {
|
|
140
|
+
return handle === undefined ? undefined : handle.store;
|
|
141
|
+
}, [handle]);
|
|
142
|
+
return shimExports.useSyncExternalStore(subscribe, getSnapshot, () => handle?.serverStore);
|
|
143
|
+
}
|
|
144
|
+
|
|
111
145
|
const DialogRootContext = /*#__PURE__*/React.createContext(undefined);
|
|
112
146
|
if (process.env.NODE_ENV !== "production") DialogRootContext.displayName = "DialogRootContext";
|
|
113
147
|
function useDialogRootContext(optional) {
|
|
114
|
-
const
|
|
115
|
-
if (optional
|
|
148
|
+
const store = React.useContext(DialogRootContext);
|
|
149
|
+
if (!optional && store === undefined) {
|
|
116
150
|
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DialogRootContext is missing. Dialog parts must be placed within <Dialog.Root>.' : formatErrorMessage(27));
|
|
117
151
|
}
|
|
118
|
-
return
|
|
152
|
+
return store;
|
|
119
153
|
}
|
|
120
154
|
|
|
121
155
|
const DialogPortalContext = /*#__PURE__*/React.createContext(undefined);
|
|
@@ -128,14 +162,26 @@ function useDialogPortalContext() {
|
|
|
128
162
|
return value;
|
|
129
163
|
}
|
|
130
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Shared by `Dialog.Popup` and `Dialog.Viewport`, whose states have the same shape.
|
|
167
|
+
* `nested` is not mapped: unmapped `true` booleans already render as `data-nested`.
|
|
168
|
+
*/
|
|
169
|
+
const dialogStateAttributesMapping = {
|
|
170
|
+
...popupStateMapping,
|
|
171
|
+
...transitionStatusMapping,
|
|
172
|
+
nestedDialogOpen(value) {
|
|
173
|
+
return value ? {
|
|
174
|
+
'data-nested-dialog-open': ''
|
|
175
|
+
} : null;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
131
179
|
const DialogPortal = /*#__PURE__*/React.forwardRef(function DialogPortal(props, forwardedRef) {
|
|
132
180
|
const {
|
|
133
181
|
keepMounted = false,
|
|
134
182
|
...portalProps
|
|
135
183
|
} = props;
|
|
136
|
-
const
|
|
137
|
-
store
|
|
138
|
-
} = useDialogRootContext();
|
|
184
|
+
const store = useDialogRootContext();
|
|
139
185
|
const mounted = store.useState('mounted');
|
|
140
186
|
const modal = store.useState('modal');
|
|
141
187
|
const open = store.useState('open');
|
|
@@ -157,25 +203,6 @@ const DialogPortal = /*#__PURE__*/React.forwardRef(function DialogPortal(props,
|
|
|
157
203
|
});
|
|
158
204
|
if (process.env.NODE_ENV !== "production") DialogPortal.displayName = "DialogPortal";
|
|
159
205
|
|
|
160
|
-
function useDialogRoot(params) {
|
|
161
|
-
const {
|
|
162
|
-
store,
|
|
163
|
-
actionsRef
|
|
164
|
-
} = params;
|
|
165
|
-
const open = store.useState('open');
|
|
166
|
-
usePopupRootSync(store, open);
|
|
167
|
-
useImplicitActiveTrigger(store);
|
|
168
|
-
const {
|
|
169
|
-
forceUnmount
|
|
170
|
-
} = useOpenStateTransitions(open, store);
|
|
171
|
-
const handleImperativeClose = React.useCallback(() => {
|
|
172
|
-
store.setOpen(false, createChangeEventDetails(imperativeAction));
|
|
173
|
-
}, [store]);
|
|
174
|
-
React.useImperativeHandle(actionsRef, () => ({
|
|
175
|
-
unmount: forceUnmount,
|
|
176
|
-
close: handleImperativeClose
|
|
177
|
-
}), [forceUnmount, handleImperativeClose]);
|
|
178
|
-
}
|
|
179
206
|
function DialogInteractions({
|
|
180
207
|
store,
|
|
181
208
|
parentContext,
|
|
@@ -211,8 +238,18 @@ function DialogInteractions({
|
|
|
211
238
|
if ('button' in event && event.button !== 0) {
|
|
212
239
|
return false;
|
|
213
240
|
}
|
|
214
|
-
if ('touches' in event
|
|
215
|
-
|
|
241
|
+
if ('touches' in event) {
|
|
242
|
+
// Outside press can be handled on `touchend`, where the lifted point is
|
|
243
|
+
// reported in `changedTouches` and `touches` contains any remaining
|
|
244
|
+
// active points. Treat it as a single-finger tap only when exactly one
|
|
245
|
+
// touch ended and no other fingers are still down.
|
|
246
|
+
if (event.type === 'touchend') {
|
|
247
|
+
if (event.changedTouches.length !== 1 || event.touches.length !== 0) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
} else if (event.touches.length !== 1) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
216
253
|
}
|
|
217
254
|
const target = getTarget(event);
|
|
218
255
|
if (isTopmost && !disablePointerDismissal) {
|
|
@@ -220,7 +257,9 @@ function DialogInteractions({
|
|
|
220
257
|
// This supports multiple modal dialogs that aren't nested in the React tree:
|
|
221
258
|
// https://github.com/mui/base-ui/issues/1320
|
|
222
259
|
if (modal) {
|
|
223
|
-
|
|
260
|
+
const internalBackdrop = store.context.internalBackdropRef.current;
|
|
261
|
+
const backdrop = store.context.backdropRef.current;
|
|
262
|
+
return internalBackdrop || backdrop ? internalBackdrop === target || backdrop === target || contains(target, popupElement) && !target?.hasAttribute('data-base-ui-portal') : true;
|
|
224
263
|
}
|
|
225
264
|
return true;
|
|
226
265
|
}
|
|
@@ -231,73 +270,82 @@ function DialogInteractions({
|
|
|
231
270
|
useScrollLock(open && modal === true, popupElement);
|
|
232
271
|
|
|
233
272
|
// Listen for nested open/close events on this store to maintain the counts.
|
|
273
|
+
// A close notification is an open notification with zeroed counts.
|
|
234
274
|
store.useContextCallback('onNestedDialogOpen', (dialogCount, drawerCount) => {
|
|
235
275
|
setOwnNestedOpenDialogs(dialogCount);
|
|
236
276
|
setOwnNestedOpenDrawers(drawerCount);
|
|
237
277
|
});
|
|
238
|
-
store.useContextCallback('onNestedDialogClose', () => {
|
|
239
|
-
setOwnNestedOpenDialogs(0);
|
|
240
|
-
setOwnNestedOpenDrawers(0);
|
|
241
|
-
});
|
|
242
278
|
|
|
243
279
|
// Notify parent of our open/close state using parent callbacks, if any
|
|
244
|
-
|
|
245
|
-
if (parentContext?.onNestedDialogOpen
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
280
|
+
useIsoLayoutEffect(() => {
|
|
281
|
+
if (parentContext?.onNestedDialogOpen) {
|
|
282
|
+
if (open) {
|
|
283
|
+
parentContext.onNestedDialogOpen(ownNestedOpenDialogs + 1, ownNestedOpenDrawers + (isDrawer ? 1 : 0));
|
|
284
|
+
} else {
|
|
285
|
+
parentContext.onNestedDialogOpen(0, 0);
|
|
286
|
+
}
|
|
250
287
|
}
|
|
251
288
|
return () => {
|
|
252
|
-
if (parentContext?.
|
|
253
|
-
parentContext.
|
|
289
|
+
if (parentContext?.onNestedDialogOpen && open) {
|
|
290
|
+
parentContext.onNestedDialogOpen(0, 0);
|
|
254
291
|
}
|
|
255
292
|
};
|
|
256
293
|
}, [isDrawer, open, ownNestedOpenDialogs, ownNestedOpenDrawers, parentContext]);
|
|
257
|
-
const activeTriggerProps = dismiss.reference ?? EMPTY_OBJECT;
|
|
258
|
-
const inactiveTriggerProps = dismiss.trigger ?? EMPTY_OBJECT;
|
|
259
|
-
// Consumers (DialogPopup/DrawerPopup) already spread `FOCUSABLE_POPUP_PROPS`
|
|
260
|
-
// directly, so the popup props only need to carry the dismiss handlers.
|
|
261
|
-
const popupProps = dismiss.floating ?? EMPTY_OBJECT;
|
|
262
294
|
usePopupInteractionProps(store, {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
295
|
+
// `enabled` is not passed to `useDismiss`, so its props are always defined,
|
|
296
|
+
// and `trigger` is the same object as `reference`.
|
|
297
|
+
activeTriggerProps: dismiss.reference,
|
|
298
|
+
inactiveTriggerProps: dismiss.trigger,
|
|
299
|
+
// DialogPopup and DrawerPopup spread `FOCUSABLE_POPUP_PROPS` directly, so
|
|
300
|
+
// this only needs to carry the dismiss handlers.
|
|
301
|
+
popupProps: dismiss.floating,
|
|
266
302
|
nestedOpenDialogCount: ownNestedOpenDialogs,
|
|
267
303
|
nestedOpenDrawerCount: ownNestedOpenDrawers
|
|
268
304
|
});
|
|
269
305
|
return null;
|
|
270
306
|
}
|
|
271
307
|
|
|
308
|
+
/**
|
|
309
|
+
* A `ReactStore` whose state never changes.
|
|
310
|
+
*
|
|
311
|
+
* Useful for fallback stores that need to support normal store reads while detached from the
|
|
312
|
+
* component that owns real state. Context values may still contain mutable refs or maps.
|
|
313
|
+
*/
|
|
314
|
+
class NullStore extends ReactStore {
|
|
315
|
+
// `update`/`set`/`notifyAll` funnel through `setState` in the base `Store`, so overriding
|
|
316
|
+
// `setState` alone would neutralize them today. They are overridden explicitly so the store stays
|
|
317
|
+
// inert even if a future base-class change stops routing a mutator through `setState`.
|
|
318
|
+
setState(_newState) {}
|
|
319
|
+
update(_changes) {}
|
|
320
|
+
set(_key, _value) {}
|
|
321
|
+
notifyAll() {}
|
|
322
|
+
}
|
|
323
|
+
|
|
272
324
|
const selectors = {
|
|
273
325
|
...popupStoreSelectors,
|
|
274
|
-
modal:
|
|
275
|
-
nested:
|
|
276
|
-
nestedOpenDialogCount:
|
|
277
|
-
nestedOpenDrawerCount:
|
|
278
|
-
disablePointerDismissal:
|
|
279
|
-
openMethod:
|
|
280
|
-
descriptionElementId:
|
|
281
|
-
titleElementId:
|
|
282
|
-
viewportElement:
|
|
283
|
-
role:
|
|
326
|
+
modal: state => state.modal,
|
|
327
|
+
nested: state => state.nested,
|
|
328
|
+
nestedOpenDialogCount: state => state.nestedOpenDialogCount,
|
|
329
|
+
nestedOpenDrawerCount: state => state.nestedOpenDrawerCount,
|
|
330
|
+
disablePointerDismissal: state => state.disablePointerDismissal,
|
|
331
|
+
openMethod: state => state.openMethod,
|
|
332
|
+
descriptionElementId: state => state.descriptionElementId,
|
|
333
|
+
titleElementId: state => state.titleElementId,
|
|
334
|
+
viewportElement: state => state.viewportElement,
|
|
335
|
+
role: state => state.role
|
|
284
336
|
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* The subset of `DialogStore` that detached handle-backed triggers rely on. Both the real
|
|
340
|
+
* `DialogStore` and the inert fallback store satisfy it, so a trigger can read from whichever
|
|
341
|
+
* store the handle currently exposes.
|
|
342
|
+
*/
|
|
343
|
+
|
|
285
344
|
class DialogStore extends ReactStore {
|
|
286
|
-
constructor(initialState, floatingId, nested
|
|
345
|
+
constructor(initialState, floatingId, nested) {
|
|
287
346
|
const triggerElements = new PopupTriggerMap();
|
|
288
|
-
const state = createInitialState(initialState);
|
|
289
|
-
state
|
|
290
|
-
super(state, {
|
|
291
|
-
popupRef: /*#__PURE__*/React.createRef(),
|
|
292
|
-
backdropRef: /*#__PURE__*/React.createRef(),
|
|
293
|
-
internalBackdropRef: /*#__PURE__*/React.createRef(),
|
|
294
|
-
outsidePressEnabledRef: {
|
|
295
|
-
current: true
|
|
296
|
-
},
|
|
297
|
-
triggerElements,
|
|
298
|
-
onOpenChange: undefined,
|
|
299
|
-
onOpenChangeComplete: undefined
|
|
300
|
-
}, selectors);
|
|
347
|
+
const state = createInitialState(initialState, triggerElements, floatingId, nested);
|
|
348
|
+
super(state, createInitialContext(triggerElements), selectors);
|
|
301
349
|
}
|
|
302
350
|
setOpen = (nextOpen, eventDetails) => {
|
|
303
351
|
eventDetails.preventUnmountOnClose = () => {
|
|
@@ -319,20 +367,22 @@ class DialogStore extends ReactStore {
|
|
|
319
367
|
setPopupOpenState(updatedState, nextOpen, eventDetails.trigger);
|
|
320
368
|
this.update(updatedState);
|
|
321
369
|
};
|
|
322
|
-
|
|
323
|
-
/* eslint-disable react-hooks/rules-of-hooks */
|
|
324
|
-
const store = usePopupStore(externalStore, (floatingId, nested) => new DialogStore(initialState, floatingId, nested), true).store;
|
|
325
|
-
/* eslint-enable react-hooks/rules-of-hooks */
|
|
370
|
+
}
|
|
326
371
|
|
|
327
|
-
|
|
328
|
-
|
|
372
|
+
/**
|
|
373
|
+
* Creates the inert fallback store used by detached handle-backed triggers while no
|
|
374
|
+
* `Dialog.Root` is attached. It preserves a dialog-specific trigger registry in context so
|
|
375
|
+
* detached triggers can register before migrating to the live root store.
|
|
376
|
+
*/
|
|
377
|
+
function createNullDialogStore() {
|
|
378
|
+
const triggerElements = new PopupTriggerMap();
|
|
379
|
+
return new NullStore(Object.freeze(createInitialState(undefined, triggerElements)), Object.freeze(createInitialContext(triggerElements)), selectors);
|
|
329
380
|
}
|
|
330
|
-
function createInitialState(initialState =
|
|
331
|
-
|
|
381
|
+
function createInitialState(initialState, triggerElements, floatingId, nested = false) {
|
|
382
|
+
const state = {
|
|
332
383
|
...createInitialPopupStoreState(),
|
|
333
384
|
modal: true,
|
|
334
385
|
disablePointerDismissal: false,
|
|
335
|
-
popupElement: null,
|
|
336
386
|
viewportElement: null,
|
|
337
387
|
descriptionElementId: undefined,
|
|
338
388
|
titleElementId: undefined,
|
|
@@ -343,9 +393,24 @@ function createInitialState(initialState = {}) {
|
|
|
343
393
|
role: 'dialog',
|
|
344
394
|
...initialState
|
|
345
395
|
};
|
|
396
|
+
state.floatingRootContext = createPopupFloatingRootContext(triggerElements, floatingId, nested);
|
|
397
|
+
return state;
|
|
398
|
+
}
|
|
399
|
+
function createInitialContext(triggerElements) {
|
|
400
|
+
return {
|
|
401
|
+
popupRef: /*#__PURE__*/React.createRef(),
|
|
402
|
+
backdropRef: /*#__PURE__*/React.createRef(),
|
|
403
|
+
internalBackdropRef: /*#__PURE__*/React.createRef(),
|
|
404
|
+
outsidePressEnabledRef: {
|
|
405
|
+
current: true
|
|
406
|
+
},
|
|
407
|
+
triggerElements,
|
|
408
|
+
onOpenChange: undefined,
|
|
409
|
+
onOpenChangeComplete: undefined
|
|
410
|
+
};
|
|
346
411
|
}
|
|
347
412
|
|
|
348
|
-
function useRenderDialogRoot(
|
|
413
|
+
function useRenderDialogRoot(mode, props) {
|
|
349
414
|
const {
|
|
350
415
|
children,
|
|
351
416
|
open: openProp,
|
|
@@ -364,38 +429,26 @@ function useRenderDialogRoot(props, mode = 'dialog') {
|
|
|
364
429
|
const modal = isAlertDialog ? true : modalProp;
|
|
365
430
|
const disablePointerDismissal = isAlertDialog || disablePointerDismissalProp;
|
|
366
431
|
const role = isAlertDialog ? 'alertdialog' : 'dialog';
|
|
367
|
-
const
|
|
368
|
-
const nested =
|
|
432
|
+
const parentStore = useDialogRootContext(true);
|
|
433
|
+
const nested = parentStore != null;
|
|
369
434
|
const rootState = {
|
|
370
435
|
modal,
|
|
371
436
|
disablePointerDismissal,
|
|
372
437
|
nested,
|
|
373
438
|
role
|
|
374
439
|
};
|
|
375
|
-
|
|
440
|
+
|
|
441
|
+
// The store is owned by this Root instance and created exactly once. It is not tied to the handle:
|
|
442
|
+
// the handle attaches to it, so swapping the handle re-attaches rather than recreating state.
|
|
443
|
+
// Default values are only initial values; controlled values and root state are synced after creation.
|
|
444
|
+
// Dialogs pass the popup element to Floating UI as the floating element (`treatPopupAsFloatingElement`).
|
|
445
|
+
const store = usePopupRootStore((floatingId, floatingNested) => new DialogStore({
|
|
376
446
|
open: defaultOpen,
|
|
377
447
|
openProp,
|
|
378
448
|
activeTriggerId: defaultTriggerIdProp,
|
|
379
449
|
triggerIdProp,
|
|
380
450
|
...rootState
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
// Support initially open state when uncontrolled
|
|
384
|
-
useOnFirstRender(() => {
|
|
385
|
-
const nextState = openProp === undefined && store.state.open === false && defaultOpen === true ? {
|
|
386
|
-
open: true,
|
|
387
|
-
activeTriggerId: defaultTriggerIdProp
|
|
388
|
-
} : null;
|
|
389
|
-
if (isAlertDialog) {
|
|
390
|
-
// Handles can reuse plain Dialog stores; alert dialog invariants must exist immediately.
|
|
391
|
-
store.update(nextState ? {
|
|
392
|
-
...rootState,
|
|
393
|
-
...nextState
|
|
394
|
-
} : rootState);
|
|
395
|
-
} else if (nextState) {
|
|
396
|
-
store.update(nextState);
|
|
397
|
-
}
|
|
398
|
-
});
|
|
451
|
+
}, floatingId, floatingNested), true);
|
|
399
452
|
store.useControlledProp('openProp', openProp);
|
|
400
453
|
store.useControlledProp('triggerIdProp', triggerIdProp);
|
|
401
454
|
store.useSyncedValues(rootState);
|
|
@@ -404,83 +457,31 @@ function useRenderDialogRoot(props, mode = 'dialog') {
|
|
|
404
457
|
const open = store.useState('open');
|
|
405
458
|
const mounted = store.useState('mounted');
|
|
406
459
|
const payload = store.useState('payload');
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
460
|
+
usePopupRootSync(store, open);
|
|
461
|
+
useImplicitActiveTrigger(store);
|
|
462
|
+
const {
|
|
463
|
+
forceUnmount
|
|
464
|
+
} = useOpenStateTransitions(open, store);
|
|
465
|
+
React.useImperativeHandle(actionsRef, () => ({
|
|
466
|
+
unmount: forceUnmount,
|
|
467
|
+
close: () => store.setOpen(false, createChangeEventDetails(imperativeAction))
|
|
468
|
+
}), [forceUnmount, store]);
|
|
411
469
|
const shouldRenderInteractions = open || mounted;
|
|
412
|
-
|
|
413
|
-
store
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
payload
|
|
425
|
-
}) : children]
|
|
426
|
-
})
|
|
470
|
+
return /*#__PURE__*/jsxs(DialogRootContext.Provider, {
|
|
471
|
+
value: store,
|
|
472
|
+
children: [handle && /*#__PURE__*/jsx(PopupHandleAttachment, {
|
|
473
|
+
handle: handle,
|
|
474
|
+
store: store
|
|
475
|
+
}), shouldRenderInteractions && /*#__PURE__*/jsx(DialogInteractions, {
|
|
476
|
+
store: store,
|
|
477
|
+
parentContext: parentStore?.context,
|
|
478
|
+
isDrawer: isDrawer
|
|
479
|
+
}), typeof children === 'function' ? children({
|
|
480
|
+
payload
|
|
481
|
+
}) : children]
|
|
427
482
|
});
|
|
428
483
|
}
|
|
429
484
|
|
|
430
|
-
/**
|
|
431
|
-
* Groups all parts of the dialog.
|
|
432
|
-
* Doesn't render its own HTML element.
|
|
433
|
-
*
|
|
434
|
-
* Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
|
|
435
|
-
*/
|
|
436
|
-
function DialogRoot(props) {
|
|
437
|
-
const mode = React.useContext(IsDrawerContext) ? 'drawer' : 'dialog';
|
|
438
|
-
return useRenderDialogRoot(props, mode);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
let DialogViewportDataAttributes = function (DialogViewportDataAttributes) {
|
|
442
|
-
/**
|
|
443
|
-
* Present when the dialog is open.
|
|
444
|
-
*/
|
|
445
|
-
DialogViewportDataAttributes[DialogViewportDataAttributes["open"] = CommonPopupDataAttributes.open] = "open";
|
|
446
|
-
/**
|
|
447
|
-
* Present when the dialog is closed.
|
|
448
|
-
*/
|
|
449
|
-
DialogViewportDataAttributes[DialogViewportDataAttributes["closed"] = CommonPopupDataAttributes.closed] = "closed";
|
|
450
|
-
/**
|
|
451
|
-
* Present when the dialog is animating in.
|
|
452
|
-
*/
|
|
453
|
-
DialogViewportDataAttributes[DialogViewportDataAttributes["startingStyle"] = CommonPopupDataAttributes.startingStyle] = "startingStyle";
|
|
454
|
-
/**
|
|
455
|
-
* Present when the dialog is animating out.
|
|
456
|
-
*/
|
|
457
|
-
DialogViewportDataAttributes[DialogViewportDataAttributes["endingStyle"] = CommonPopupDataAttributes.endingStyle] = "endingStyle";
|
|
458
|
-
/**
|
|
459
|
-
* Present when the dialog is nested within another dialog.
|
|
460
|
-
*/
|
|
461
|
-
DialogViewportDataAttributes["nested"] = "data-nested";
|
|
462
|
-
/**
|
|
463
|
-
* Present when the dialog has other open dialogs nested within it.
|
|
464
|
-
*/
|
|
465
|
-
DialogViewportDataAttributes["nestedDialogOpen"] = "data-nested-dialog-open";
|
|
466
|
-
return DialogViewportDataAttributes;
|
|
467
|
-
}({});
|
|
468
|
-
|
|
469
|
-
const stateAttributesMapping$2 = {
|
|
470
|
-
...popupStateMapping,
|
|
471
|
-
...transitionStatusMapping,
|
|
472
|
-
nested(value) {
|
|
473
|
-
return value ? {
|
|
474
|
-
[DialogViewportDataAttributes.nested]: ''
|
|
475
|
-
} : null;
|
|
476
|
-
},
|
|
477
|
-
nestedDialogOpen(value) {
|
|
478
|
-
return value ? {
|
|
479
|
-
[DialogViewportDataAttributes.nestedDialogOpen]: ''
|
|
480
|
-
} : null;
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
|
|
484
485
|
/**
|
|
485
486
|
* A positioning container for the dialog popup that can be made scrollable.
|
|
486
487
|
* Renders a `<div>` element.
|
|
@@ -496,9 +497,7 @@ const DialogViewport = /*#__PURE__*/React.forwardRef(function DialogViewport(com
|
|
|
496
497
|
...elementProps
|
|
497
498
|
} = componentProps;
|
|
498
499
|
const keepMounted = useDialogPortalContext();
|
|
499
|
-
const
|
|
500
|
-
store
|
|
501
|
-
} = useDialogRootContext();
|
|
500
|
+
const store = useDialogRootContext();
|
|
502
501
|
const open = store.useState('open');
|
|
503
502
|
const nested = store.useState('nested');
|
|
504
503
|
const transitionStatus = store.useState('transitionStatus');
|
|
@@ -517,7 +516,7 @@ const DialogViewport = /*#__PURE__*/React.forwardRef(function DialogViewport(com
|
|
|
517
516
|
enabled: shouldRender,
|
|
518
517
|
state,
|
|
519
518
|
ref: [forwardedRef, setViewportElement],
|
|
520
|
-
stateAttributesMapping:
|
|
519
|
+
stateAttributesMapping: dialogStateAttributesMapping,
|
|
521
520
|
props: [{
|
|
522
521
|
role: 'presentation',
|
|
523
522
|
hidden: !mounted,
|
|
@@ -548,8 +547,9 @@ const DialogTrigger = /*#__PURE__*/React.forwardRef(function DialogTrigger(compo
|
|
|
548
547
|
handle,
|
|
549
548
|
...elementProps
|
|
550
549
|
} = componentProps;
|
|
551
|
-
const
|
|
552
|
-
const
|
|
550
|
+
const dialogRootStore = useDialogRootContext(true);
|
|
551
|
+
const handleStore = usePopupHandleStore(handle);
|
|
552
|
+
const store = handleStore ?? dialogRootStore;
|
|
553
553
|
if (!store) {
|
|
554
554
|
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Dialog.Trigger> must be used within <Dialog.Root> or provided with a handle.' : formatErrorMessage(79));
|
|
555
555
|
}
|
|
@@ -571,9 +571,7 @@ const DialogTrigger = /*#__PURE__*/React.forwardRef(function DialogTrigger(compo
|
|
|
571
571
|
disabled,
|
|
572
572
|
native: nativeButton
|
|
573
573
|
});
|
|
574
|
-
const click = useClick(floatingContext
|
|
575
|
-
enabled: floatingContext != null
|
|
576
|
-
});
|
|
574
|
+
const click = useClick(floatingContext);
|
|
577
575
|
const interactionTypeProps = useOpenMethodTriggerProps(() => store.select('open'), interactionType => {
|
|
578
576
|
store.set('openMethod', interactionType);
|
|
579
577
|
});
|
|
@@ -645,11 +643,6 @@ let DrawerBackdropCssVars = /*#__PURE__*/function (DrawerBackdropCssVars) {
|
|
|
645
643
|
return DrawerBackdropCssVars;
|
|
646
644
|
}({});
|
|
647
645
|
|
|
648
|
-
const stateAttributesMapping$1 = {
|
|
649
|
-
...popupStateMapping,
|
|
650
|
-
...transitionStatusMapping
|
|
651
|
-
};
|
|
652
|
-
|
|
653
646
|
/**
|
|
654
647
|
* An overlay displayed beneath the popup.
|
|
655
648
|
* Renders a `<div>` element.
|
|
@@ -664,9 +657,7 @@ const DrawerBackdrop = /*#__PURE__*/React.forwardRef(function DrawerBackdrop(com
|
|
|
664
657
|
forceRender = false,
|
|
665
658
|
...elementProps
|
|
666
659
|
} = componentProps;
|
|
667
|
-
const
|
|
668
|
-
store
|
|
669
|
-
} = useDialogRootContext();
|
|
660
|
+
const store = useDialogRootContext();
|
|
670
661
|
const open = store.useState('open');
|
|
671
662
|
const nested = store.useState('nested');
|
|
672
663
|
const mounted = store.useState('mounted');
|
|
@@ -678,7 +669,7 @@ const DrawerBackdrop = /*#__PURE__*/React.forwardRef(function DrawerBackdrop(com
|
|
|
678
669
|
return useRenderElement('div', componentProps, {
|
|
679
670
|
state,
|
|
680
671
|
ref: [store.context.backdropRef, forwardedRef],
|
|
681
|
-
stateAttributesMapping:
|
|
672
|
+
stateAttributesMapping: popupTransitionStateMapping,
|
|
682
673
|
props: [{
|
|
683
674
|
role: 'presentation',
|
|
684
675
|
hidden: !mounted,
|
|
@@ -722,9 +713,8 @@ if (process.env.NODE_ENV !== "production") DrawerContent.displayName = "DrawerCo
|
|
|
722
713
|
|
|
723
714
|
const DrawerProviderContext = /*#__PURE__*/React.createContext(undefined);
|
|
724
715
|
if (process.env.NODE_ENV !== "production") DrawerProviderContext.displayName = "DrawerProviderContext";
|
|
725
|
-
function useDrawerProviderContext(
|
|
726
|
-
|
|
727
|
-
return context;
|
|
716
|
+
function useDrawerProviderContext() {
|
|
717
|
+
return React.useContext(DrawerProviderContext);
|
|
728
718
|
}
|
|
729
719
|
|
|
730
720
|
let DrawerPopupDataAttributes = function (DrawerPopupDataAttributes) {
|
|
@@ -737,7 +727,7 @@ let DrawerPopupDataAttributes = function (DrawerPopupDataAttributes) {
|
|
|
737
727
|
*/
|
|
738
728
|
DrawerPopupDataAttributes[DrawerPopupDataAttributes["closed"] = CommonPopupDataAttributes.closed] = "closed";
|
|
739
729
|
/**
|
|
740
|
-
* Present when the drawer
|
|
730
|
+
* Present when the drawer begins animating in.
|
|
741
731
|
*/
|
|
742
732
|
DrawerPopupDataAttributes[DrawerPopupDataAttributes["startingStyle"] = CommonPopupDataAttributes.startingStyle] = "startingStyle";
|
|
743
733
|
/**
|
|
@@ -776,7 +766,7 @@ const DrawerRootContext = /*#__PURE__*/React.createContext(undefined);
|
|
|
776
766
|
if (process.env.NODE_ENV !== "production") DrawerRootContext.displayName = "DrawerRootContext";
|
|
777
767
|
function useDrawerRootContext(optional) {
|
|
778
768
|
const drawerRootContext = React.useContext(DrawerRootContext);
|
|
779
|
-
if (optional
|
|
769
|
+
if (optional !== true && drawerRootContext === undefined) {
|
|
780
770
|
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DrawerRootContext is missing. Drawer parts must be placed within <Drawer.Root>.' : formatErrorMessage(90));
|
|
781
771
|
}
|
|
782
772
|
return drawerRootContext;
|
|
@@ -817,22 +807,24 @@ function resolveSnapPointValue(snapPoint, viewportHeight, rootFontSize) {
|
|
|
817
807
|
}
|
|
818
808
|
return null;
|
|
819
809
|
}
|
|
820
|
-
|
|
821
|
-
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Returns the index of the value closest to `target`, or `-1` if `values` is empty.
|
|
813
|
+
*/
|
|
814
|
+
function closestSnapPointIndex(values, target) {
|
|
815
|
+
let closestIndex = -1;
|
|
822
816
|
let closestDistance = Infinity;
|
|
823
|
-
for (
|
|
824
|
-
const distance = Math.abs(
|
|
817
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
818
|
+
const distance = Math.abs(values[index] - target);
|
|
825
819
|
if (distance < closestDistance) {
|
|
826
820
|
closestDistance = distance;
|
|
827
|
-
|
|
821
|
+
closestIndex = index;
|
|
828
822
|
}
|
|
829
823
|
}
|
|
830
|
-
return
|
|
824
|
+
return closestIndex;
|
|
831
825
|
}
|
|
832
826
|
function useDrawerSnapPoints() {
|
|
833
|
-
const
|
|
834
|
-
store
|
|
835
|
-
} = useDialogRootContext();
|
|
827
|
+
const store = useDialogRootContext();
|
|
836
828
|
const {
|
|
837
829
|
snapPoints,
|
|
838
830
|
activeSnapPoint,
|
|
@@ -845,12 +837,7 @@ function useDrawerSnapPoints() {
|
|
|
845
837
|
const measureViewportHeight = useStableCallback(() => {
|
|
846
838
|
const doc = ownerDocument(viewportElement);
|
|
847
839
|
const html = doc.documentElement;
|
|
848
|
-
|
|
849
|
-
setViewportHeight(viewportElement.offsetHeight);
|
|
850
|
-
}
|
|
851
|
-
if (!viewportElement) {
|
|
852
|
-
setViewportHeight(html.clientHeight);
|
|
853
|
-
}
|
|
840
|
+
setViewportHeight(viewportElement ? viewportElement.offsetHeight : html.clientHeight);
|
|
854
841
|
const fontSize = parseFloat(getComputedStyle(html).fontSize);
|
|
855
842
|
if (Number.isFinite(fontSize)) {
|
|
856
843
|
setRootFontSize(fontSize);
|
|
@@ -872,12 +859,9 @@ function useDrawerSnapPoints() {
|
|
|
872
859
|
return [];
|
|
873
860
|
}
|
|
874
861
|
const maxHeight = Math.min(popupHeight, viewportHeight);
|
|
875
|
-
if (!Number.isFinite(maxHeight) || maxHeight <= 0) {
|
|
876
|
-
return [];
|
|
877
|
-
}
|
|
878
862
|
const resolved = snapPoints.map(value => {
|
|
879
863
|
const resolvedHeight = resolveSnapPointValue(value, viewportHeight, rootFontSize);
|
|
880
|
-
if (resolvedHeight === null
|
|
864
|
+
if (resolvedHeight === null) {
|
|
881
865
|
return null;
|
|
882
866
|
}
|
|
883
867
|
const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
|
|
@@ -905,9 +889,6 @@ function useDrawerSnapPoints() {
|
|
|
905
889
|
return deduped;
|
|
906
890
|
}, [popupHeight, rootFontSize, snapPoints, viewportHeight]);
|
|
907
891
|
const resolvedActiveSnapPoint = React.useMemo(() => {
|
|
908
|
-
if (activeSnapPoint === undefined) {
|
|
909
|
-
return resolvedSnapPoints[0];
|
|
910
|
-
}
|
|
911
892
|
if (activeSnapPoint === null) {
|
|
912
893
|
return undefined;
|
|
913
894
|
}
|
|
@@ -917,11 +898,11 @@ function useDrawerSnapPoints() {
|
|
|
917
898
|
}
|
|
918
899
|
const maxHeight = Math.min(popupHeight, viewportHeight);
|
|
919
900
|
const resolvedHeight = resolveSnapPointValue(activeSnapPoint, viewportHeight, rootFontSize);
|
|
920
|
-
if (resolvedHeight === null
|
|
901
|
+
if (resolvedHeight === null) {
|
|
921
902
|
return undefined;
|
|
922
903
|
}
|
|
923
904
|
const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
|
|
924
|
-
return
|
|
905
|
+
return resolvedSnapPoints[closestSnapPointIndex(resolvedSnapPoints.map(point => point.height), clampedHeight)];
|
|
925
906
|
}, [activeSnapPoint, popupHeight, resolvedSnapPoints, rootFontSize, viewportHeight]);
|
|
926
907
|
return {
|
|
927
908
|
snapPoints,
|
|
@@ -936,9 +917,8 @@ function useDrawerSnapPoints() {
|
|
|
936
917
|
|
|
937
918
|
const DrawerViewportContext = /*#__PURE__*/React.createContext(null);
|
|
938
919
|
if (process.env.NODE_ENV !== "production") DrawerViewportContext.displayName = "DrawerViewportContext";
|
|
939
|
-
function useDrawerViewportContext(
|
|
940
|
-
|
|
941
|
-
return context;
|
|
920
|
+
function useDrawerViewportContext() {
|
|
921
|
+
return React.useContext(DrawerViewportContext);
|
|
942
922
|
}
|
|
943
923
|
|
|
944
924
|
let drawerSwipeVarsRegistered = false;
|
|
@@ -996,8 +976,7 @@ function removeCSSVariableInheritance$1() {
|
|
|
996
976
|
drawerSwipeVarsRegistered = true;
|
|
997
977
|
}
|
|
998
978
|
const stateAttributesMapping = {
|
|
999
|
-
...
|
|
1000
|
-
...transitionStatusMapping,
|
|
979
|
+
...popupTransitionStateMapping,
|
|
1001
980
|
expanded(value) {
|
|
1002
981
|
return value ? {
|
|
1003
982
|
[DrawerPopupDataAttributes.expanded]: ''
|
|
@@ -1014,9 +993,9 @@ const stateAttributesMapping = {
|
|
|
1014
993
|
} : null;
|
|
1015
994
|
},
|
|
1016
995
|
swipeDirection(value) {
|
|
1017
|
-
return
|
|
996
|
+
return {
|
|
1018
997
|
[DrawerPopupDataAttributes.swipeDirection]: value
|
|
1019
|
-
}
|
|
998
|
+
};
|
|
1020
999
|
},
|
|
1021
1000
|
swiping(value) {
|
|
1022
1001
|
return value ? {
|
|
@@ -1040,9 +1019,7 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1040
1019
|
initialFocus,
|
|
1041
1020
|
...elementProps
|
|
1042
1021
|
} = componentProps;
|
|
1043
|
-
const
|
|
1044
|
-
store
|
|
1045
|
-
} = useDialogRootContext();
|
|
1022
|
+
const store = useDialogRootContext();
|
|
1046
1023
|
const popupRef = store.context.popupRef;
|
|
1047
1024
|
const {
|
|
1048
1025
|
swipeDirection,
|
|
@@ -1081,6 +1058,8 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1081
1058
|
const swipeStrength = swipe?.swipeStrength ?? null;
|
|
1082
1059
|
const [popupHeight, setPopupHeight] = React.useState(0);
|
|
1083
1060
|
const popupHeightRef = React.useRef(0);
|
|
1061
|
+
|
|
1062
|
+
/* istanbul ignore else -- process.env.NODE_ENV is a build-time constant. */
|
|
1084
1063
|
if (process.env.NODE_ENV !== 'production') {
|
|
1085
1064
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
1086
1065
|
React.useEffect(() => {
|
|
@@ -1163,7 +1142,7 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1163
1142
|
}
|
|
1164
1143
|
};
|
|
1165
1144
|
}, [nestedSwipeProgressStore, popupRef]);
|
|
1166
|
-
|
|
1145
|
+
useIsoLayoutEffect(() => {
|
|
1167
1146
|
if (!open) {
|
|
1168
1147
|
return undefined;
|
|
1169
1148
|
}
|
|
@@ -1172,7 +1151,7 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1172
1151
|
notifyParentFrontmostHeight?.(0);
|
|
1173
1152
|
};
|
|
1174
1153
|
}, [frontmostHeight, open, notifyParentFrontmostHeight]);
|
|
1175
|
-
|
|
1154
|
+
useIsoLayoutEffect(() => {
|
|
1176
1155
|
if (!notifyParentHasNestedDrawer) {
|
|
1177
1156
|
return undefined;
|
|
1178
1157
|
}
|
|
@@ -1216,7 +1195,7 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1216
1195
|
let dragStyles = swipe ? swipe.getDragStyles() : EMPTY_OBJECT;
|
|
1217
1196
|
if (shouldApplySnapPoints && swipeDirection === 'down') {
|
|
1218
1197
|
const baseOffset = activeSnapPointOffset ?? 0;
|
|
1219
|
-
const movementValue = Number.parseFloat(String(dragStyles[DrawerPopupCssVars.swipeMovementY]
|
|
1198
|
+
const movementValue = Number.parseFloat(String(dragStyles[DrawerPopupCssVars.swipeMovementY]));
|
|
1220
1199
|
if (swiping && Number.isFinite(movementValue)) {
|
|
1221
1200
|
dragStyles = {
|
|
1222
1201
|
...dragStyles,
|
|
@@ -1326,6 +1305,7 @@ function DrawerRoot(props) {
|
|
|
1326
1305
|
state: 'snapPoint'
|
|
1327
1306
|
});
|
|
1328
1307
|
const isNestedDrawerOpenRef = React.useRef(false);
|
|
1308
|
+
const swipeAreaActiveRef = React.useRef(false);
|
|
1329
1309
|
const setActiveSnapPoint = useStableCallback((nextSnapPoint, eventDetails) => {
|
|
1330
1310
|
const resolvedEventDetails = eventDetails ?? createChangeEventDetails(none);
|
|
1331
1311
|
onSnapPointChange?.(nextSnapPoint, resolvedEventDetails);
|
|
@@ -1385,6 +1365,7 @@ function DrawerRoot(props) {
|
|
|
1385
1365
|
});
|
|
1386
1366
|
const contextValue = React.useMemo(() => ({
|
|
1387
1367
|
swipeDirection,
|
|
1368
|
+
swipeAreaActiveRef,
|
|
1388
1369
|
snapToSequentialPoints,
|
|
1389
1370
|
snapPoints,
|
|
1390
1371
|
activeSnapPoint: resolvedActiveSnapPoint,
|
|
@@ -1403,30 +1384,28 @@ function DrawerRoot(props) {
|
|
|
1403
1384
|
notifyParentSwipingChange,
|
|
1404
1385
|
notifyParentSwipeProgressChange,
|
|
1405
1386
|
notifyParentHasNestedDrawer
|
|
1406
|
-
}), [resolvedActiveSnapPoint, frontmostHeight, hasNestedDrawer, nestedSwiping, nestedSwipeProgressStore, notifyParentHasNestedDrawer, notifyParentSwipeProgressChange, notifyParentSwipingChange, notifyParentFrontmostHeight, onNestedDrawerPresenceChange, onNestedFrontmostHeightChange, onNestedSwipeProgressChange, onNestedSwipingChange, onPopupHeightChange, popupHeight, setActiveSnapPoint, snapPoints, snapToSequentialPoints, swipeDirection]);
|
|
1387
|
+
}), [resolvedActiveSnapPoint, frontmostHeight, hasNestedDrawer, nestedSwiping, nestedSwipeProgressStore, notifyParentHasNestedDrawer, notifyParentSwipeProgressChange, notifyParentSwipingChange, notifyParentFrontmostHeight, onNestedDrawerPresenceChange, onNestedFrontmostHeightChange, onNestedSwipeProgressChange, onNestedSwipingChange, onPopupHeightChange, popupHeight, setActiveSnapPoint, snapPoints, snapToSequentialPoints, swipeAreaActiveRef, swipeDirection]);
|
|
1407
1388
|
const resolvedChildren = typeof children === 'function' ? payload => /*#__PURE__*/jsxs(React.Fragment, {
|
|
1408
1389
|
children: [_DrawerProviderReport || (_DrawerProviderReport = /*#__PURE__*/jsx(DrawerProviderReporter, {})), children(payload)]
|
|
1409
1390
|
}) : /*#__PURE__*/jsxs(React.Fragment, {
|
|
1410
1391
|
children: [_DrawerProviderReport2 || (_DrawerProviderReport2 = /*#__PURE__*/jsx(DrawerProviderReporter, {})), children]
|
|
1411
1392
|
});
|
|
1393
|
+
const dialog = useRenderDialogRoot('drawer', {
|
|
1394
|
+
open: openProp,
|
|
1395
|
+
defaultOpen,
|
|
1396
|
+
onOpenChange: handleOpenChange,
|
|
1397
|
+
onOpenChangeComplete,
|
|
1398
|
+
disablePointerDismissal,
|
|
1399
|
+
modal,
|
|
1400
|
+
actionsRef,
|
|
1401
|
+
handle,
|
|
1402
|
+
triggerId: triggerIdProp,
|
|
1403
|
+
defaultTriggerId: defaultTriggerIdProp,
|
|
1404
|
+
children: resolvedChildren
|
|
1405
|
+
});
|
|
1412
1406
|
return /*#__PURE__*/jsx(DrawerRootContext.Provider, {
|
|
1413
1407
|
value: contextValue,
|
|
1414
|
-
children:
|
|
1415
|
-
value: true,
|
|
1416
|
-
children: /*#__PURE__*/jsx(DialogRoot, {
|
|
1417
|
-
open: openProp,
|
|
1418
|
-
defaultOpen: defaultOpen,
|
|
1419
|
-
onOpenChange: handleOpenChange,
|
|
1420
|
-
onOpenChangeComplete: onOpenChangeComplete,
|
|
1421
|
-
disablePointerDismissal: disablePointerDismissal,
|
|
1422
|
-
modal: modal,
|
|
1423
|
-
actionsRef: actionsRef,
|
|
1424
|
-
handle: handle,
|
|
1425
|
-
triggerId: triggerIdProp,
|
|
1426
|
-
defaultTriggerId: defaultTriggerIdProp,
|
|
1427
|
-
children: resolvedChildren
|
|
1428
|
-
})
|
|
1429
|
-
})
|
|
1408
|
+
children: dialog
|
|
1430
1409
|
});
|
|
1431
1410
|
}
|
|
1432
1411
|
function createNestedSwipeProgressStore() {
|
|
@@ -1453,29 +1432,25 @@ function createNestedSwipeProgressStore() {
|
|
|
1453
1432
|
};
|
|
1454
1433
|
}
|
|
1455
1434
|
function DrawerProviderReporter() {
|
|
1456
|
-
const drawerId = useId();
|
|
1457
1435
|
const providerContext = useDrawerProviderContext();
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1460
|
-
|
|
1436
|
+
const store = useDialogRootContext(false);
|
|
1437
|
+
const setDrawerOpen = providerContext?.setDrawerOpen;
|
|
1438
|
+
const removeDrawer = providerContext?.removeDrawer;
|
|
1461
1439
|
const open = store.useState('open');
|
|
1462
1440
|
const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
|
|
1463
1441
|
const popupElement = store.useState('popupElement');
|
|
1464
1442
|
const isTopmost = nestedOpenDialogCount === 0;
|
|
1465
|
-
|
|
1466
|
-
if (!
|
|
1443
|
+
useIsoLayoutEffect(() => {
|
|
1444
|
+
if (!removeDrawer) {
|
|
1467
1445
|
return undefined;
|
|
1468
1446
|
}
|
|
1469
1447
|
return () => {
|
|
1470
|
-
|
|
1448
|
+
removeDrawer(store);
|
|
1471
1449
|
};
|
|
1472
|
-
}, [
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
}
|
|
1477
|
-
providerContext?.setDrawerOpen(drawerId, open);
|
|
1478
|
-
}, [drawerId, open, providerContext]);
|
|
1450
|
+
}, [removeDrawer, store]);
|
|
1451
|
+
useIsoLayoutEffect(() => {
|
|
1452
|
+
setDrawerOpen?.(store, open);
|
|
1453
|
+
}, [open, setDrawerOpen, store]);
|
|
1479
1454
|
React.useEffect(() => {
|
|
1480
1455
|
// CloseWatcher enables the Android back gesture (Chromium-only).
|
|
1481
1456
|
// Keep this Android-only for now to avoid interfering with Escape/nesting semantics on desktop due to `useDismiss`.
|
|
@@ -1503,53 +1478,11 @@ function DrawerProviderReporter() {
|
|
|
1503
1478
|
return null;
|
|
1504
1479
|
}
|
|
1505
1480
|
|
|
1506
|
-
|
|
1507
|
-
//
|
|
1508
|
-
//
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
if (axis === 'vertical') {
|
|
1512
|
-
const overflowY = style.overflowY;
|
|
1513
|
-
if (overflowY !== 'auto' && overflowY !== 'scroll') {
|
|
1514
|
-
return false;
|
|
1515
|
-
}
|
|
1516
|
-
return allowOverflowIntent ? element.clientHeight > 0 : element.scrollHeight > element.clientHeight;
|
|
1517
|
-
}
|
|
1518
|
-
const overflowX = style.overflowX;
|
|
1519
|
-
if (overflowX !== 'auto' && overflowX !== 'scroll') {
|
|
1520
|
-
return false;
|
|
1521
|
-
}
|
|
1522
|
-
return allowOverflowIntent ? element.clientWidth > 0 : element.scrollWidth > element.clientWidth;
|
|
1523
|
-
}
|
|
1524
|
-
function hasScrollableAncestor(target, root, axes) {
|
|
1525
|
-
// `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
|
|
1526
|
-
// still walks up to scrollable ancestors in the light DOM.
|
|
1527
|
-
let node = target;
|
|
1528
|
-
while (isHTMLElement(node) && node !== root && !isLastTraversableNode(node)) {
|
|
1529
|
-
for (const axis of axes) {
|
|
1530
|
-
if (isScrollable(node, axis)) {
|
|
1531
|
-
return true;
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
node = getParentNode(node);
|
|
1535
|
-
}
|
|
1536
|
-
return false;
|
|
1537
|
-
}
|
|
1538
|
-
function findScrollableTouchTarget(target, root, axis = 'vertical', allowOverflowIntent = false) {
|
|
1539
|
-
// `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
|
|
1540
|
-
// still reaches a scrollable ancestor in the light DOM.
|
|
1541
|
-
let node = isHTMLElement(target) ? target : null;
|
|
1542
|
-
while (isHTMLElement(node) && node !== root && !isLastTraversableNode(node)) {
|
|
1543
|
-
if (isScrollable(node, axis, allowOverflowIntent)) {
|
|
1544
|
-
return node;
|
|
1545
|
-
}
|
|
1546
|
-
node = getParentNode(node);
|
|
1547
|
-
}
|
|
1548
|
-
return isScrollable(root, axis, allowOverflowIntent) ? root : null;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
function getElementAtPoint(doc, x, y) {
|
|
1552
|
-
return typeof doc?.elementFromPoint === 'function' ? doc.elementFromPoint(x, y) : null;
|
|
1481
|
+
// `Document.elementFromPoint` retargets shadow content to the shadow host, which then fails
|
|
1482
|
+
// `contains()` checks against a popup inside that shadow root, so callers pass `getRootNode()`
|
|
1483
|
+
// (a document or a shadow root) rather than `ownerDocument()`.
|
|
1484
|
+
function getElementAtPoint(root, x, y) {
|
|
1485
|
+
return typeof root?.elementFromPoint === 'function' ? root.elementFromPoint(x, y) : null;
|
|
1553
1486
|
}
|
|
1554
1487
|
|
|
1555
1488
|
const DEFAULT_SWIPE_THRESHOLD = 40;
|
|
@@ -1696,6 +1629,7 @@ function useSwipeDismiss(options) {
|
|
|
1696
1629
|
});
|
|
1697
1630
|
const swipeProgressRef = React.useRef(0);
|
|
1698
1631
|
const swipeThresholdRef = React.useRef(swipeThresholdDefault);
|
|
1632
|
+
const swipeThresholdFunctionRef = React.useRef(null);
|
|
1699
1633
|
const swipeStartTimeRef = React.useRef(null);
|
|
1700
1634
|
const lastDragSampleRef = React.useRef(null);
|
|
1701
1635
|
const lastDragVelocityRef = React.useRef({
|
|
@@ -1717,15 +1651,12 @@ function useSwipeDismiss(options) {
|
|
|
1717
1651
|
if (!direction) {
|
|
1718
1652
|
return;
|
|
1719
1653
|
}
|
|
1720
|
-
if (typeof swipeThresholdProp !== 'function') {
|
|
1721
|
-
swipeThresholdRef.current = swipeThresholdDefault;
|
|
1722
|
-
return;
|
|
1723
|
-
}
|
|
1724
1654
|
const element = elementRef.current;
|
|
1725
|
-
|
|
1655
|
+
const thresholdFunction = swipeThresholdFunctionRef.current;
|
|
1656
|
+
if (!element || !thresholdFunction) {
|
|
1726
1657
|
return;
|
|
1727
1658
|
}
|
|
1728
|
-
const value =
|
|
1659
|
+
const value = thresholdFunction({
|
|
1729
1660
|
element,
|
|
1730
1661
|
direction
|
|
1731
1662
|
});
|
|
@@ -1803,6 +1734,7 @@ function useSwipeDismiss(options) {
|
|
|
1803
1734
|
setDragDismissed(false);
|
|
1804
1735
|
updateSwipeProgress(0);
|
|
1805
1736
|
swipeThresholdRef.current = swipeThresholdDefault;
|
|
1737
|
+
swipeThresholdFunctionRef.current = null;
|
|
1806
1738
|
dragStartPosRef.current = {
|
|
1807
1739
|
x: 0,
|
|
1808
1740
|
y: 0
|
|
@@ -1843,11 +1775,6 @@ function useSwipeDismiss(options) {
|
|
|
1843
1775
|
lastProgressDetailsRef.current = null;
|
|
1844
1776
|
syncDragStyles(false);
|
|
1845
1777
|
}, [setSwiping, swipeThresholdDefault, syncDragStyles, updateSwipeProgress]);
|
|
1846
|
-
React.useEffect(() => {
|
|
1847
|
-
if (typeof swipeThresholdProp !== 'function') {
|
|
1848
|
-
swipeThresholdRef.current = swipeThresholdDefault;
|
|
1849
|
-
}
|
|
1850
|
-
}, [swipeThresholdDefault, swipeThresholdProp]);
|
|
1851
1778
|
function getPrimaryPointerPosition(event) {
|
|
1852
1779
|
if ('touches' in event) {
|
|
1853
1780
|
const touch = event.touches[0];
|
|
@@ -1868,8 +1795,8 @@ function useSwipeDismiss(options) {
|
|
|
1868
1795
|
return event.pointerType === 'touch';
|
|
1869
1796
|
}
|
|
1870
1797
|
function getTargetAtPoint(position, nativeEvent) {
|
|
1871
|
-
const
|
|
1872
|
-
const elementAtPoint = getElementAtPoint(
|
|
1798
|
+
const root = elementRef.current?.getRootNode();
|
|
1799
|
+
const elementAtPoint = getElementAtPoint(root, position.x, position.y);
|
|
1873
1800
|
const target = elementAtPoint ?? getTarget(nativeEvent);
|
|
1874
1801
|
return target;
|
|
1875
1802
|
}
|
|
@@ -1912,6 +1839,8 @@ function useSwipeDismiss(options) {
|
|
|
1912
1839
|
swipeStartTimeRef.current = getValidTimeStamp(event.timeStamp);
|
|
1913
1840
|
swipeCancelBaselineRef.current = position;
|
|
1914
1841
|
lastMovePosRef.current = position;
|
|
1842
|
+
swipeThresholdRef.current = swipeThresholdDefault;
|
|
1843
|
+
swipeThresholdFunctionRef.current = typeof swipeThresholdProp === 'function' ? swipeThresholdProp : null;
|
|
1915
1844
|
if (element) {
|
|
1916
1845
|
elementSizeRef.current = {
|
|
1917
1846
|
width: element.offsetWidth,
|
|
@@ -1976,12 +1905,9 @@ function useSwipeDismiss(options) {
|
|
|
1976
1905
|
onCancel?.(event.nativeEvent);
|
|
1977
1906
|
}
|
|
1978
1907
|
function applyDirectionalDamping(deltaX, deltaY) {
|
|
1979
|
-
const exponent = value => value
|
|
1908
|
+
const exponent = value => Math.sign(value) * Math.abs(value) ** 0.5;
|
|
1980
1909
|
const dampAxis = (delta, allowNegative, allowPositive) => {
|
|
1981
|
-
if (!allowNegative && delta < 0) {
|
|
1982
|
-
return exponent(delta);
|
|
1983
|
-
}
|
|
1984
|
-
if (!allowPositive && delta > 0) {
|
|
1910
|
+
if (!allowNegative && delta < 0 || !allowPositive && delta > 0) {
|
|
1985
1911
|
return exponent(delta);
|
|
1986
1912
|
}
|
|
1987
1913
|
return delta;
|
|
@@ -1994,29 +1920,16 @@ function useSwipeDismiss(options) {
|
|
|
1994
1920
|
};
|
|
1995
1921
|
}
|
|
1996
1922
|
function canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY) {
|
|
1923
|
+
// Swiping toward the axis start edge (down/right) is allowed when scrolled to the start;
|
|
1924
|
+
// toward the end edge (up/left) when scrolled to the end.
|
|
1925
|
+
const canSwipeOnAxis = (delta, scrollOffset, maxScrollOffset, allowTowardStart, allowTowardEnd) => delta > 0 && scrollOffset <= 0 && allowTowardStart || delta < 0 && scrollOffset >= Math.max(0, maxScrollOffset) && allowTowardEnd;
|
|
1997
1926
|
const absDeltaX = Math.abs(deltaX);
|
|
1998
1927
|
const absDeltaY = Math.abs(deltaY);
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
const movingDown = deltaY > 0;
|
|
2005
|
-
const movingUp = deltaY < 0;
|
|
2006
|
-
const canSwipeDown = movingDown && atTop && allowDown;
|
|
2007
|
-
const canSwipeUp = movingUp && atBottom && allowUp;
|
|
2008
|
-
return canSwipeDown || canSwipeUp;
|
|
2009
|
-
}
|
|
2010
|
-
const useHorizontalAxis = hasHorizontal && deltaX !== 0 && (!hasVertical || absDeltaX > absDeltaY);
|
|
2011
|
-
if (useHorizontalAxis) {
|
|
2012
|
-
const maxScrollLeft = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
|
|
2013
|
-
const atLeft = scrollTarget.scrollLeft <= 0;
|
|
2014
|
-
const atRight = scrollTarget.scrollLeft >= maxScrollLeft;
|
|
2015
|
-
const movingRight = deltaX > 0;
|
|
2016
|
-
const movingLeft = deltaX < 0;
|
|
2017
|
-
const canSwipeRight = movingRight && atLeft && allowRight;
|
|
2018
|
-
const canSwipeLeft = movingLeft && atRight && allowLeft;
|
|
2019
|
-
return canSwipeRight || canSwipeLeft;
|
|
1928
|
+
if (hasVertical && deltaY !== 0 && (!hasHorizontal || absDeltaY >= absDeltaX)) {
|
|
1929
|
+
return canSwipeOnAxis(deltaY, scrollTarget.scrollTop, scrollTarget.scrollHeight - scrollTarget.clientHeight, allowDown, allowUp);
|
|
1930
|
+
}
|
|
1931
|
+
if (hasHorizontal && deltaX !== 0 && (!hasVertical || absDeltaX > absDeltaY)) {
|
|
1932
|
+
return canSwipeOnAxis(deltaX, scrollTarget.scrollLeft, scrollTarget.scrollWidth - scrollTarget.clientWidth, allowRight, allowLeft);
|
|
2020
1933
|
}
|
|
2021
1934
|
return null;
|
|
2022
1935
|
}
|
|
@@ -2037,7 +1950,7 @@ function useSwipeDismiss(options) {
|
|
|
2037
1950
|
pendingSwipeRef.current = true;
|
|
2038
1951
|
pendingSwipeStartPosRef.current = startPos;
|
|
2039
1952
|
swipeFromScrollableRef.current = false;
|
|
2040
|
-
sawPrimaryButtonsOnMoveRef.current =
|
|
1953
|
+
sawPrimaryButtonsOnMoveRef.current = !('touches' in event);
|
|
2041
1954
|
const allowedToStart = canStart ? canStart(startPos, {
|
|
2042
1955
|
nativeEvent: event.nativeEvent,
|
|
2043
1956
|
direction: primaryDirection
|
|
@@ -2065,14 +1978,21 @@ function useSwipeDismiss(options) {
|
|
|
2065
1978
|
event.preventDefault();
|
|
2066
1979
|
}
|
|
2067
1980
|
if (isFirstPointerMoveRef.current) {
|
|
2068
|
-
// Adjust the starting position to the current position on the first move
|
|
2069
|
-
// to account for the delay between pointerdown and the first pointermove on iOS.
|
|
2070
|
-
dragStartPosRef.current = position;
|
|
2071
|
-
const moveTime = getValidTimeStamp(event.timeStamp);
|
|
2072
|
-
if (moveTime !== null) {
|
|
2073
|
-
swipeStartTimeRef.current = moveTime;
|
|
2074
|
-
}
|
|
2075
1981
|
isFirstPointerMoveRef.current = false;
|
|
1982
|
+
// Reset the drag origin to the first move's position to absorb the gap between the press and
|
|
1983
|
+
// the first move event — notably on iOS touch, where the first `touchmove` arrives already
|
|
1984
|
+
// offset from the `touchstart` — which would otherwise make the dragged element jump. This
|
|
1985
|
+
// only matters when an element follows the pointer; when `trackDrag` is false (e.g. the
|
|
1986
|
+
// swipe-area, which only opens the drawer) keep the original press position so a quick flick
|
|
1987
|
+
// still registers: on a low-refresh-rate display the whole travel can land in this single
|
|
1988
|
+
// first move, and discarding it would drop the gesture.
|
|
1989
|
+
if (trackDrag) {
|
|
1990
|
+
dragStartPosRef.current = position;
|
|
1991
|
+
const moveTime = getValidTimeStamp(event.timeStamp);
|
|
1992
|
+
if (moveTime !== null) {
|
|
1993
|
+
swipeStartTimeRef.current = moveTime;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
2076
1996
|
}
|
|
2077
1997
|
const clientX = position.x;
|
|
2078
1998
|
const clientY = position.y;
|
|
@@ -2160,51 +2080,45 @@ function useSwipeDismiss(options) {
|
|
|
2160
2080
|
newOffsetY += dampedDelta.y;
|
|
2161
2081
|
}
|
|
2162
2082
|
}
|
|
2083
|
+
|
|
2084
|
+
// Only rewrite drag styles when the drag offset actually changed. `syncDragStyles` writes the
|
|
2085
|
+
// raw (undamped) frozen transform and movement vars, relying on the consumer's `onProgress`
|
|
2086
|
+
// to overwrite them with damped styles — but `updateSwipeProgress` dedupes unchanged
|
|
2087
|
+
// deltas and skips `onProgress`. A move that doesn't change the offset (e.g. the cursor
|
|
2088
|
+
// pinned at a screen edge during an off-screen drag, jittering only on the ignored axis)
|
|
2089
|
+
// would otherwise reinstate the raw styles with no correction, jumping the element to the
|
|
2090
|
+
// undamped position.
|
|
2091
|
+
const previousOffset = dragOffsetRef.current;
|
|
2092
|
+
const offsetChanged = newOffsetX !== previousOffset.x || newOffsetY !== previousOffset.y;
|
|
2163
2093
|
dragOffsetRef.current = {
|
|
2164
2094
|
x: newOffsetX,
|
|
2165
2095
|
y: newOffsetY
|
|
2166
2096
|
};
|
|
2167
|
-
|
|
2097
|
+
if (offsetChanged) {
|
|
2098
|
+
syncDragStyles(true);
|
|
2099
|
+
}
|
|
2168
2100
|
recordDragSample({
|
|
2169
2101
|
x: newOffsetX,
|
|
2170
2102
|
y: newOffsetY
|
|
2171
2103
|
}, getValidTimeStamp(event.timeStamp));
|
|
2172
2104
|
const dragDeltaX = newOffsetX - initialTransformRef.current.x;
|
|
2173
2105
|
const dragDeltaY = newOffsetY - initialTransformRef.current.y;
|
|
2174
|
-
const
|
|
2175
|
-
const progressDirection = primaryDirection ?? intendedSwipeDirectionRef.current;
|
|
2176
|
-
if (!progressDirection) {
|
|
2177
|
-
updateSwipeProgress(0, {
|
|
2178
|
-
deltaX: dragDeltaX,
|
|
2179
|
-
deltaY: dragDeltaY,
|
|
2180
|
-
direction: swipeDirectionDetails
|
|
2181
|
-
});
|
|
2182
|
-
return;
|
|
2183
|
-
}
|
|
2184
|
-
const size = progressDirection === 'left' || progressDirection === 'right' ? elementSizeRef.current.width : elementSizeRef.current.height;
|
|
2185
|
-
const scale = initialTransformRef.current.scale || 1;
|
|
2186
|
-
if (size <= 0 || scale <= 0) {
|
|
2187
|
-
updateSwipeProgress(0, {
|
|
2188
|
-
deltaX: dragDeltaX,
|
|
2189
|
-
deltaY: dragDeltaY,
|
|
2190
|
-
direction: swipeDirectionDetails
|
|
2191
|
-
});
|
|
2192
|
-
return;
|
|
2193
|
-
}
|
|
2194
|
-
const progressDisplacement = getDisplacement(progressDirection, newOffsetX - initialTransformRef.current.x, newOffsetY - initialTransformRef.current.y);
|
|
2195
|
-
if (progressDisplacement <= 0) {
|
|
2196
|
-
updateSwipeProgress(0, {
|
|
2197
|
-
deltaX: dragDeltaX,
|
|
2198
|
-
deltaY: dragDeltaY,
|
|
2199
|
-
direction: swipeDirectionDetails
|
|
2200
|
-
});
|
|
2201
|
-
return;
|
|
2202
|
-
}
|
|
2203
|
-
updateSwipeProgress(progressDisplacement / (size * scale), {
|
|
2106
|
+
const progressDetails = {
|
|
2204
2107
|
deltaX: dragDeltaX,
|
|
2205
2108
|
deltaY: dragDeltaY,
|
|
2206
|
-
direction:
|
|
2207
|
-
}
|
|
2109
|
+
direction: intendedSwipeDirectionRef.current
|
|
2110
|
+
};
|
|
2111
|
+
let progress = 0;
|
|
2112
|
+
const progressDirection = primaryDirection ?? intendedSwipeDirectionRef.current;
|
|
2113
|
+
if (progressDirection) {
|
|
2114
|
+
const size = progressDirection === 'left' || progressDirection === 'right' ? elementSizeRef.current.width : elementSizeRef.current.height;
|
|
2115
|
+
const scale = initialTransformRef.current.scale || 1;
|
|
2116
|
+
const progressDisplacement = getDisplacement(progressDirection, dragDeltaX, dragDeltaY);
|
|
2117
|
+
if (size > 0 && scale > 0 && progressDisplacement > 0) {
|
|
2118
|
+
progress = progressDisplacement / (size * scale);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
updateSwipeProgress(progress, progressDetails);
|
|
2208
2122
|
}
|
|
2209
2123
|
const handleEnd = useStableCallback(event => {
|
|
2210
2124
|
if (!enabled) {
|
|
@@ -2292,33 +2206,9 @@ function useSwipeDismiss(options) {
|
|
|
2292
2206
|
dismissDirection = intendedSwipeDirectionRef.current ?? primaryDirection;
|
|
2293
2207
|
} else {
|
|
2294
2208
|
for (const direction of directions) {
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
shouldClose = true;
|
|
2299
|
-
dismissDirection = 'right';
|
|
2300
|
-
}
|
|
2301
|
-
break;
|
|
2302
|
-
case 'left':
|
|
2303
|
-
if (deltaX < -swipeThresholdRef.current) {
|
|
2304
|
-
shouldClose = true;
|
|
2305
|
-
dismissDirection = 'left';
|
|
2306
|
-
}
|
|
2307
|
-
break;
|
|
2308
|
-
case 'down':
|
|
2309
|
-
if (deltaY > swipeThresholdRef.current) {
|
|
2310
|
-
shouldClose = true;
|
|
2311
|
-
dismissDirection = 'down';
|
|
2312
|
-
}
|
|
2313
|
-
break;
|
|
2314
|
-
case 'up':
|
|
2315
|
-
if (deltaY < -swipeThresholdRef.current) {
|
|
2316
|
-
shouldClose = true;
|
|
2317
|
-
dismissDirection = 'up';
|
|
2318
|
-
}
|
|
2319
|
-
break;
|
|
2320
|
-
}
|
|
2321
|
-
if (shouldClose) {
|
|
2209
|
+
if (getDisplacement(direction, deltaX, deltaY) > swipeThresholdRef.current) {
|
|
2210
|
+
shouldClose = true;
|
|
2211
|
+
dismissDirection = direction;
|
|
2322
2212
|
break;
|
|
2323
2213
|
}
|
|
2324
2214
|
}
|
|
@@ -2460,25 +2350,29 @@ function useSwipeDismiss(options) {
|
|
|
2460
2350
|
});
|
|
2461
2351
|
});
|
|
2462
2352
|
const getDragStyles = React.useCallback(() => {
|
|
2353
|
+
// Read `isSwipingRef`, not the lagging `isSwiping` state, to match the imperative writer
|
|
2354
|
+
// `syncDragStyles`. Otherwise a render that commits before `setSwiping(true)` flushes strips the
|
|
2355
|
+
// transform it just wrote, flashing the popup to its resting position for a frame.
|
|
2356
|
+
const swiping = isSwipingRef.current;
|
|
2463
2357
|
const dragOffset = dragOffsetRef.current;
|
|
2464
2358
|
const initialTransform = initialTransformRef.current;
|
|
2465
2359
|
const deltaX = dragOffset.x - initialTransform.x;
|
|
2466
2360
|
const deltaY = dragOffset.y - initialTransform.y;
|
|
2467
|
-
if (!
|
|
2361
|
+
if (!swiping && deltaX === 0 && deltaY === 0 && !dragDismissed) {
|
|
2468
2362
|
return {
|
|
2469
2363
|
[movementCssVars.x]: '0px',
|
|
2470
2364
|
[movementCssVars.y]: '0px'
|
|
2471
2365
|
};
|
|
2472
2366
|
}
|
|
2473
2367
|
return {
|
|
2474
|
-
transition:
|
|
2368
|
+
transition: swiping ? 'none' : undefined,
|
|
2475
2369
|
// While swiping, freeze the element at its current visual transform so it doesn't snap to the
|
|
2476
2370
|
// end position.
|
|
2477
|
-
transform:
|
|
2371
|
+
transform: swiping ? getDragTransform(dragOffset, initialTransform.scale) : undefined,
|
|
2478
2372
|
[movementCssVars.x]: `${deltaX}px`,
|
|
2479
2373
|
[movementCssVars.y]: `${deltaY}px`
|
|
2480
2374
|
};
|
|
2481
|
-
}, [dragDismissed,
|
|
2375
|
+
}, [dragDismissed, movementCssVars]);
|
|
2482
2376
|
const getPointerProps = React.useCallback(() => {
|
|
2483
2377
|
if (!enabled) {
|
|
2484
2378
|
return {};
|
|
@@ -2538,6 +2432,8 @@ const MIN_SWIPE_RELEASE_DURATION_MS = 80;
|
|
|
2538
2432
|
const MAX_SWIPE_RELEASE_DURATION_MS = 360;
|
|
2539
2433
|
const MIN_SWIPE_RELEASE_SCALAR = 0.1;
|
|
2540
2434
|
const MAX_SWIPE_RELEASE_SCALAR = 1;
|
|
2435
|
+
const AXIS_LOCK_SLOP = 6;
|
|
2436
|
+
const AXIS_LOCK_BIAS = 2;
|
|
2541
2437
|
const DRAWER_CONTENT_SELECTOR = `[${DRAWER_CONTENT_ATTRIBUTE}]`;
|
|
2542
2438
|
/**
|
|
2543
2439
|
* A positioning container for the drawer popup that can be made scrollable.
|
|
@@ -2553,9 +2449,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2553
2449
|
children,
|
|
2554
2450
|
...elementProps
|
|
2555
2451
|
} = props;
|
|
2556
|
-
const
|
|
2557
|
-
store
|
|
2558
|
-
} = useDialogRootContext();
|
|
2452
|
+
const store = useDialogRootContext();
|
|
2559
2453
|
const popupRef = store.context.popupRef;
|
|
2560
2454
|
const backdropRef = store.context.backdropRef;
|
|
2561
2455
|
const {
|
|
@@ -2563,7 +2457,8 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2563
2457
|
notifyParentSwipingChange,
|
|
2564
2458
|
notifyParentSwipeProgressChange,
|
|
2565
2459
|
frontmostHeight,
|
|
2566
|
-
snapToSequentialPoints
|
|
2460
|
+
snapToSequentialPoints,
|
|
2461
|
+
swipeAreaActiveRef
|
|
2567
2462
|
} = useDrawerRootContext();
|
|
2568
2463
|
const providerContext = useDrawerProviderContext();
|
|
2569
2464
|
const {
|
|
@@ -2597,30 +2492,13 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2597
2492
|
const touchScrollStateRef = React.useRef(null);
|
|
2598
2493
|
const virtualKeyboard = useDrawerVirtualKeyboardContext();
|
|
2599
2494
|
const snapPointRange = React.useMemo(() => {
|
|
2600
|
-
if (!snapPoints || snapPoints.length < 2) {
|
|
2601
|
-
return null;
|
|
2602
|
-
}
|
|
2603
|
-
if (swipeDirection !== 'down' && swipeDirection !== 'up') {
|
|
2604
|
-
return null;
|
|
2605
|
-
}
|
|
2606
|
-
if (resolvedSnapPoints.length < 2) {
|
|
2607
|
-
return null;
|
|
2608
|
-
}
|
|
2609
|
-
const offsets = resolvedSnapPoints.map(point => point.offset).filter(offset => Number.isFinite(offset)).sort((a, b) => a - b);
|
|
2610
|
-
if (offsets.length < 2) {
|
|
2495
|
+
if (!snapPoints || snapPoints.length < 2 || resolvedSnapPoints.length < 2 || swipeDirection !== 'down' && swipeDirection !== 'up') {
|
|
2611
2496
|
return null;
|
|
2612
2497
|
}
|
|
2498
|
+
const offsets = resolvedSnapPoints.map(point => point.offset).sort((a, b) => a - b);
|
|
2613
2499
|
const minOffset = offsets[0];
|
|
2614
2500
|
const nextOffset = offsets[1];
|
|
2615
|
-
const
|
|
2616
|
-
let range = nextOffset - minOffset;
|
|
2617
|
-
if (!Number.isFinite(range) || range <= 0) {
|
|
2618
|
-
const fallbackRange = maxOffset - minOffset;
|
|
2619
|
-
if (!Number.isFinite(fallbackRange) || fallbackRange <= 0) {
|
|
2620
|
-
return null;
|
|
2621
|
-
}
|
|
2622
|
-
range = fallbackRange;
|
|
2623
|
-
}
|
|
2501
|
+
const range = nextOffset - minOffset;
|
|
2624
2502
|
return {
|
|
2625
2503
|
minOffset,
|
|
2626
2504
|
range
|
|
@@ -2639,7 +2517,8 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2639
2517
|
return [swipeDirection];
|
|
2640
2518
|
}, [snapPoints, swipeDirection]);
|
|
2641
2519
|
const setSwipeDismissed = useStableCallback(dismissed => {
|
|
2642
|
-
|
|
2520
|
+
popupRef.current?.toggleAttribute(DrawerPopupDataAttributes.swipeDismiss, dismissed);
|
|
2521
|
+
backdropRef.current?.toggleAttribute(DrawerPopupDataAttributes.swipeDismiss, dismissed);
|
|
2643
2522
|
});
|
|
2644
2523
|
const clearSwipeRelease = useStableCallback(() => {
|
|
2645
2524
|
setSwipeDismissed(false);
|
|
@@ -2653,11 +2532,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2653
2532
|
nestedSwipeActiveRef.current = false;
|
|
2654
2533
|
notifyParentSwipingChange?.(false);
|
|
2655
2534
|
});
|
|
2656
|
-
const applySwipeProgress = useStableCallback(({
|
|
2657
|
-
resolvedProgress,
|
|
2658
|
-
shouldTrackProgress,
|
|
2659
|
-
notifyParent
|
|
2660
|
-
}) => {
|
|
2535
|
+
const applySwipeProgress = useStableCallback((resolvedProgress, shouldTrackProgress, notifyParent) => {
|
|
2661
2536
|
const isActive = open && !nested && shouldTrackProgress;
|
|
2662
2537
|
const swipeProgress = isActive ? resolvedProgress : 0;
|
|
2663
2538
|
const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
|
|
@@ -2675,78 +2550,48 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2675
2550
|
if (!backdropElement) {
|
|
2676
2551
|
return;
|
|
2677
2552
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
return;
|
|
2682
|
-
}
|
|
2683
|
-
backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${swipeProgress}`);
|
|
2684
|
-
if (frontmostHeight > 0) {
|
|
2553
|
+
const showProgress = isActive && swipeProgress > 0;
|
|
2554
|
+
backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, showProgress ? `${swipeProgress}` : '0');
|
|
2555
|
+
if (showProgress && frontmostHeight > 0) {
|
|
2685
2556
|
backdropElement.style.setProperty(DrawerPopupCssVars.height, `${frontmostHeight}px`);
|
|
2686
2557
|
} else {
|
|
2687
2558
|
backdropElement.style.removeProperty(DrawerPopupCssVars.height);
|
|
2688
2559
|
}
|
|
2689
2560
|
});
|
|
2690
|
-
function resolveSwipeRelease({
|
|
2691
|
-
direction
|
|
2692
|
-
|
|
2693
|
-
deltaY,
|
|
2694
|
-
velocityX,
|
|
2695
|
-
velocityY,
|
|
2696
|
-
releaseVelocityX,
|
|
2697
|
-
releaseVelocityY
|
|
2698
|
-
}) {
|
|
2699
|
-
if (!direction) {
|
|
2700
|
-
return null;
|
|
2701
|
-
}
|
|
2702
|
-
const popupElement = store.context.popupRef.current;
|
|
2703
|
-
if (!popupElement) {
|
|
2704
|
-
return null;
|
|
2705
|
-
}
|
|
2706
|
-
const size = direction === 'left' || direction === 'right' ? popupElement.offsetWidth : popupElement.offsetHeight;
|
|
2707
|
-
if (!Number.isFinite(size) || size <= 0) {
|
|
2561
|
+
function resolveSwipeRelease(popupElement, direction, deltaX, deltaY, velocityX, velocityY, releaseVelocityX, releaseVelocityY) {
|
|
2562
|
+
const size = getBaseSwipeSize(popupElement, direction);
|
|
2563
|
+
if (size <= 0) {
|
|
2708
2564
|
return null;
|
|
2709
2565
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
} else if (direction === 'up') {
|
|
2716
|
-
baseOffset = -snapPointBaseOffset;
|
|
2717
|
-
}
|
|
2718
|
-
const translation = baseOffset + axisDelta;
|
|
2719
|
-
const translationAlongDirection = direction === 'left' || direction === 'up' ? -translation : translation;
|
|
2566
|
+
|
|
2567
|
+
// The snap point base offset shifts the popup along the dismiss direction for both
|
|
2568
|
+
// `down` (+offset) and `up` (-offset), so it always adds to the directional translation.
|
|
2569
|
+
const snapPointBaseOffset = (direction === 'down' || direction === 'up') && snapPoints && snapPoints.length > 0 ? activeSnapPointOffset ?? 0 : 0;
|
|
2570
|
+
const translationAlongDirection = snapPointBaseOffset + getDisplacement(direction, deltaX, deltaY);
|
|
2720
2571
|
const remainingDistance = Math.max(0, size - translationAlongDirection);
|
|
2721
|
-
if (
|
|
2572
|
+
if (remainingDistance <= 0) {
|
|
2722
2573
|
return null;
|
|
2723
2574
|
}
|
|
2724
|
-
const
|
|
2725
|
-
const
|
|
2726
|
-
|
|
2727
|
-
const directionalVelocity = direction === 'left' || direction === 'up' ? -resolvedVelocity : resolvedVelocity;
|
|
2728
|
-
if (!Number.isFinite(directionalVelocity) || directionalVelocity <= MIN_SWIPE_RELEASE_VELOCITY) {
|
|
2575
|
+
const releaseVelocity = getDisplacement(direction, releaseVelocityX, releaseVelocityY);
|
|
2576
|
+
const directionalVelocity = Math.abs(releaseVelocity) > 0 ? releaseVelocity : getDisplacement(direction, velocityX, velocityY);
|
|
2577
|
+
if (directionalVelocity <= MIN_SWIPE_RELEASE_VELOCITY) {
|
|
2729
2578
|
return null;
|
|
2730
2579
|
}
|
|
2731
2580
|
const clampedVelocity = clamp(directionalVelocity, MIN_SWIPE_RELEASE_VELOCITY, MAX_SWIPE_RELEASE_VELOCITY);
|
|
2581
|
+
// The gesture hook supplies finite deltas and velocities. The guards above keep the remaining
|
|
2582
|
+
// distance and divisor positive, so the duration stays within [MIN, MAX] and the resulting
|
|
2583
|
+
// scalar within (0, 1].
|
|
2732
2584
|
const durationMs = clamp(remainingDistance / clampedVelocity, MIN_SWIPE_RELEASE_DURATION_MS, MAX_SWIPE_RELEASE_DURATION_MS);
|
|
2733
|
-
if (!Number.isFinite(durationMs)) {
|
|
2734
|
-
return null;
|
|
2735
|
-
}
|
|
2736
2585
|
const normalizedDuration = (durationMs - MIN_SWIPE_RELEASE_DURATION_MS) / (MAX_SWIPE_RELEASE_DURATION_MS - MIN_SWIPE_RELEASE_DURATION_MS);
|
|
2737
|
-
|
|
2738
|
-
if (!Number.isFinite(durationScalar) || durationScalar <= 0) {
|
|
2739
|
-
return null;
|
|
2740
|
-
}
|
|
2741
|
-
return durationScalar;
|
|
2586
|
+
return MIN_SWIPE_RELEASE_SCALAR + normalizedDuration * (MAX_SWIPE_RELEASE_SCALAR - MIN_SWIPE_RELEASE_SCALAR);
|
|
2742
2587
|
}
|
|
2743
2588
|
function updateNestedSwipeActive(details) {
|
|
2744
2589
|
if (nestedSwipeActiveRef.current || !details) {
|
|
2745
2590
|
return;
|
|
2746
2591
|
}
|
|
2747
2592
|
const direction = details.direction ?? swipeDirection;
|
|
2748
|
-
const delta = direction
|
|
2749
|
-
if (
|
|
2593
|
+
const delta = getDisplacement(direction, details.deltaX, details.deltaY);
|
|
2594
|
+
if (Math.abs(delta) < MIN_SWIPE_THRESHOLD) {
|
|
2750
2595
|
return;
|
|
2751
2596
|
}
|
|
2752
2597
|
nestedSwipeActiveRef.current = true;
|
|
@@ -2763,13 +2608,10 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2763
2608
|
y: DrawerPopupCssVars.swipeMovementY
|
|
2764
2609
|
},
|
|
2765
2610
|
onSwipeStart(event) {
|
|
2766
|
-
if ('touches' in event ||
|
|
2611
|
+
if ('touches' in event || event.pointerType === 'touch') {
|
|
2767
2612
|
return;
|
|
2768
2613
|
}
|
|
2769
2614
|
const popupElement = popupRef.current;
|
|
2770
|
-
if (!popupElement) {
|
|
2771
|
-
return;
|
|
2772
|
-
}
|
|
2773
2615
|
const doc = ownerDocument(popupElement);
|
|
2774
2616
|
const selection = doc.getSelection?.();
|
|
2775
2617
|
if (!selection || selection.isCollapsed) {
|
|
@@ -2801,54 +2643,38 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2801
2643
|
return false;
|
|
2802
2644
|
}
|
|
2803
2645
|
const doc = popupElement.ownerDocument;
|
|
2804
|
-
const elementAtPoint = getElementAtPoint(
|
|
2646
|
+
const elementAtPoint = getElementAtPoint(popupElement.getRootNode(), position.x, position.y);
|
|
2805
2647
|
if (!elementAtPoint || !contains(popupElement, elementAtPoint)) {
|
|
2806
2648
|
return false;
|
|
2807
2649
|
}
|
|
2808
2650
|
const nativeEvent = details.nativeEvent;
|
|
2809
|
-
const touchLike = 'touches' in nativeEvent ||
|
|
2651
|
+
const touchLike = 'touches' in nativeEvent || nativeEvent.pointerType === 'touch';
|
|
2810
2652
|
if (touchLike && shouldIgnoreSwipeForTextSelection(doc, popupElement)) {
|
|
2811
2653
|
return false;
|
|
2812
2654
|
}
|
|
2813
|
-
if (nativeEvent.type === 'touchstart' && isSwipeIgnoredTarget(elementAtPoint)) {
|
|
2814
|
-
return false;
|
|
2815
|
-
}
|
|
2816
2655
|
return true;
|
|
2817
2656
|
},
|
|
2818
2657
|
onProgress(progress, details) {
|
|
2819
2658
|
updateNestedSwipeActive(details);
|
|
2820
2659
|
const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
|
|
2821
|
-
if (swipingRef.current && swipeDirection === 'down' && hasSnapPoints && details
|
|
2660
|
+
if (swipingRef.current && swipeDirection === 'down' && hasSnapPoints && details) {
|
|
2822
2661
|
const popupElement = store.context.popupRef.current;
|
|
2823
2662
|
if (popupElement) {
|
|
2824
2663
|
popupElement.style.removeProperty('transform');
|
|
2825
2664
|
popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementY, `${getSnapPointSwipeMovement(activeSnapPointOffset ?? 0, details.deltaY)}px`);
|
|
2826
2665
|
}
|
|
2827
2666
|
}
|
|
2828
|
-
const currentDirection = details?.direction ?? swipe$1.swipeDirection;
|
|
2829
|
-
const isDismissSwipe = currentDirection === undefined || currentDirection === swipeDirection;
|
|
2830
|
-
const isVerticalSwipe = swipeDirection === 'down' || swipeDirection === 'up';
|
|
2831
|
-
const shouldTrackProgress = hasSnapPoints && isVerticalSwipe || !hasSnapPoints || swipeDirection === 'left' || swipeDirection === 'right' || isDismissSwipe;
|
|
2832
2667
|
let resolvedProgress = progress;
|
|
2833
2668
|
if (snapPointRange && popupHeight > 0) {
|
|
2669
|
+
const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;
|
|
2670
|
+
const offsetToProgress = nextOffset => clamp((nextOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
|
|
2834
2671
|
if (details && Number.isFinite(details.deltaY)) {
|
|
2835
|
-
|
|
2836
|
-
const nextOffset = clamp(baseOffset + details.deltaY, 0, popupHeight);
|
|
2837
|
-
resolvedProgress = clamp((nextOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
|
|
2672
|
+
resolvedProgress = offsetToProgress(clamp(baseOffset + details.deltaY, 0, popupHeight));
|
|
2838
2673
|
} else if (snapPointProgress !== null) {
|
|
2839
2674
|
resolvedProgress = snapPointProgress;
|
|
2840
|
-
} else if (currentDirection === 'down' || currentDirection === 'up') {
|
|
2841
|
-
const displacement = progress * popupHeight;
|
|
2842
|
-
const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;
|
|
2843
|
-
const nextOffset = currentDirection === 'down' ? baseOffset + displacement : baseOffset - displacement;
|
|
2844
|
-
resolvedProgress = clamp((nextOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
|
|
2845
2675
|
}
|
|
2846
2676
|
}
|
|
2847
|
-
applySwipeProgress(
|
|
2848
|
-
resolvedProgress,
|
|
2849
|
-
shouldTrackProgress,
|
|
2850
|
-
notifyParent: true
|
|
2851
|
-
});
|
|
2677
|
+
applySwipeProgress(resolvedProgress, true, true);
|
|
2852
2678
|
},
|
|
2853
2679
|
onRelease({
|
|
2854
2680
|
event,
|
|
@@ -2860,30 +2686,21 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2860
2686
|
releaseVelocityX,
|
|
2861
2687
|
releaseVelocityY
|
|
2862
2688
|
}) {
|
|
2863
|
-
const
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
releaseVelocityY
|
|
2870
|
-
};
|
|
2689
|
+
const popupElement = store.context.popupRef.current;
|
|
2690
|
+
if (!popupElement) {
|
|
2691
|
+
clearSwipeRelease();
|
|
2692
|
+
return undefined;
|
|
2693
|
+
}
|
|
2694
|
+
const releasePopupElement = popupElement;
|
|
2871
2695
|
function startSwipeRelease(resolvedDirection) {
|
|
2872
2696
|
// Start ending transition styles earlier and synchronously to prevent a period where
|
|
2873
2697
|
// the popup appears stuck on release before the actual closing animation starts.
|
|
2874
|
-
const popupElement = store.context.popupRef.current;
|
|
2875
|
-
if (!popupElement) {
|
|
2876
|
-
return;
|
|
2877
|
-
}
|
|
2878
2698
|
finishNestedSwipe();
|
|
2879
2699
|
setSwipeDismissed(true);
|
|
2880
|
-
|
|
2881
|
-
|
|
2700
|
+
releasePopupElement.style.removeProperty('transition');
|
|
2701
|
+
releasePopupElement.setAttribute(TransitionStatusDataAttributes.endingStyle, '');
|
|
2882
2702
|
ReactDOM.flushSync(() => {
|
|
2883
|
-
setSwipeRelease(resolveSwipeRelease(
|
|
2884
|
-
direction: resolvedDirection,
|
|
2885
|
-
...swipeReleasePayload
|
|
2886
|
-
}));
|
|
2703
|
+
setSwipeRelease(resolveSwipeRelease(releasePopupElement, resolvedDirection, deltaX, deltaY, velocityX, velocityY, releaseVelocityX, releaseVelocityY));
|
|
2887
2704
|
});
|
|
2888
2705
|
}
|
|
2889
2706
|
if (!snapPoints || snapPoints.length === 0) {
|
|
@@ -2891,29 +2708,16 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2891
2708
|
clearSwipeRelease();
|
|
2892
2709
|
return undefined;
|
|
2893
2710
|
}
|
|
2894
|
-
const
|
|
2895
|
-
if (!element) {
|
|
2896
|
-
clearSwipeRelease();
|
|
2897
|
-
return undefined;
|
|
2898
|
-
}
|
|
2899
|
-
const baseThreshold = getBaseSwipeThreshold(element, direction);
|
|
2900
|
-
const delta = direction === 'left' || direction === 'right' ? deltaX : deltaY;
|
|
2901
|
-
if (!Number.isFinite(delta)) {
|
|
2902
|
-
clearSwipeRelease();
|
|
2903
|
-
return undefined;
|
|
2904
|
-
}
|
|
2905
|
-
const directionalDelta = direction === 'left' || direction === 'up' ? -delta : delta;
|
|
2711
|
+
const directionalDelta = getDisplacement(direction, deltaX, deltaY);
|
|
2906
2712
|
if (directionalDelta <= 0) {
|
|
2907
2713
|
clearSwipeRelease();
|
|
2908
2714
|
return false;
|
|
2909
2715
|
}
|
|
2910
|
-
|
|
2911
|
-
const directionalVelocity = direction === 'left' || direction === 'up' ? -velocity : velocity;
|
|
2912
|
-
if (directionalVelocity >= FAST_SWIPE_VELOCITY && directionalDelta > 0) {
|
|
2716
|
+
if (getDisplacement(direction, velocityX, velocityY) >= FAST_SWIPE_VELOCITY) {
|
|
2913
2717
|
startSwipeRelease(direction);
|
|
2914
2718
|
return true;
|
|
2915
2719
|
}
|
|
2916
|
-
const shouldClose = directionalDelta >
|
|
2720
|
+
const shouldClose = directionalDelta > getBaseSwipeThreshold(releasePopupElement, direction);
|
|
2917
2721
|
if (shouldClose) {
|
|
2918
2722
|
startSwipeRelease(direction);
|
|
2919
2723
|
} else {
|
|
@@ -2925,20 +2729,20 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2925
2729
|
clearSwipeRelease();
|
|
2926
2730
|
return undefined;
|
|
2927
2731
|
}
|
|
2928
|
-
if (!popupHeight
|
|
2732
|
+
if (!popupHeight) {
|
|
2929
2733
|
clearSwipeRelease();
|
|
2930
|
-
return
|
|
2734
|
+
return false;
|
|
2931
2735
|
}
|
|
2932
|
-
|
|
2933
|
-
if (!Number.isFinite(dragDelta)) {
|
|
2736
|
+
if (resolvedSnapPoints.length === 0) {
|
|
2934
2737
|
clearSwipeRelease();
|
|
2935
2738
|
return undefined;
|
|
2936
2739
|
}
|
|
2740
|
+
const dragDelta = swipeDirection === 'down' ? deltaY : -deltaY;
|
|
2937
2741
|
const dragDirection = Math.sign(dragDelta);
|
|
2938
2742
|
const releaseDirectionalVelocity = swipeDirection === 'down' ? releaseVelocityY : -releaseVelocityY;
|
|
2939
2743
|
const fallbackDirectionalVelocity = swipeDirection === 'down' ? velocityY : -velocityY;
|
|
2940
|
-
let resolvedDirectionalVelocity =
|
|
2941
|
-
if (dragDirection !== 0 && Math.abs(dragDelta) >= MIN_SWIPE_THRESHOLD
|
|
2744
|
+
let resolvedDirectionalVelocity = releaseDirectionalVelocity;
|
|
2745
|
+
if (dragDirection !== 0 && Math.abs(dragDelta) >= MIN_SWIPE_THRESHOLD) {
|
|
2942
2746
|
const velocityDirection = Math.sign(resolvedDirectionalVelocity);
|
|
2943
2747
|
if (velocityDirection !== 0 && velocityDirection !== dragDirection) {
|
|
2944
2748
|
// Ignore touch reversals that would otherwise flip the snap decision.
|
|
@@ -2947,39 +2751,20 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2947
2751
|
}
|
|
2948
2752
|
const currentOffset = activeSnapPointOffset ?? 0;
|
|
2949
2753
|
const dragTargetOffset = clamp(currentOffset + dragDelta, 0, popupHeight);
|
|
2950
|
-
const velocityOffset =
|
|
2754
|
+
const velocityOffset = Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD ? clamp(resolvedDirectionalVelocity, -MAX_SNAP_VELOCITY, MAX_SNAP_VELOCITY) * SNAP_VELOCITY_MULTIPLIER : 0;
|
|
2951
2755
|
const targetOffset = snapToSequentialPoints ? dragTargetOffset : clamp(dragTargetOffset + velocityOffset, 0, popupHeight);
|
|
2952
2756
|
const snapPointEventDetails = createChangeEventDetails(swipe, event);
|
|
2953
2757
|
const closeFromSnapPoints = () => {
|
|
2954
2758
|
pendingSwipeCloseSnapPointRef.current = activeSnapPoint;
|
|
2955
|
-
setActiveSnapPoint
|
|
2759
|
+
setActiveSnapPoint(null, snapPointEventDetails);
|
|
2956
2760
|
startSwipeRelease(swipeDirection);
|
|
2957
2761
|
return true;
|
|
2958
2762
|
};
|
|
2959
2763
|
if (snapToSequentialPoints) {
|
|
2960
2764
|
const orderedSnapPoints = [...resolvedSnapPoints].sort((first, second) => first.offset - second.offset);
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
}
|
|
2965
|
-
let currentIndex = 0;
|
|
2966
|
-
let closestDistance = Math.abs(currentOffset - orderedSnapPoints[0].offset);
|
|
2967
|
-
for (let index = 1; index < orderedSnapPoints.length; index += 1) {
|
|
2968
|
-
const distance = Math.abs(currentOffset - orderedSnapPoints[index].offset);
|
|
2969
|
-
if (distance < closestDistance) {
|
|
2970
|
-
closestDistance = distance;
|
|
2971
|
-
currentIndex = index;
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
2974
|
-
let targetSnapPoint = orderedSnapPoints[0];
|
|
2975
|
-
closestDistance = Math.abs(targetOffset - targetSnapPoint.offset);
|
|
2976
|
-
for (const snapPoint of orderedSnapPoints) {
|
|
2977
|
-
const distance = Math.abs(targetOffset - snapPoint.offset);
|
|
2978
|
-
if (distance < closestDistance) {
|
|
2979
|
-
closestDistance = distance;
|
|
2980
|
-
targetSnapPoint = snapPoint;
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2765
|
+
const orderedOffsets = orderedSnapPoints.map(point => point.offset);
|
|
2766
|
+
const currentIndex = closestSnapPointIndex(orderedOffsets, currentOffset);
|
|
2767
|
+
let targetSnapPoint = orderedSnapPoints[closestSnapPointIndex(orderedOffsets, targetOffset)];
|
|
2983
2768
|
const velocityDirection = Math.sign(resolvedDirectionalVelocity);
|
|
2984
2769
|
const shouldAdvance = dragDirection !== 0 && velocityDirection !== 0 && velocityDirection === dragDirection && Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD;
|
|
2985
2770
|
let effectiveTargetOffset = targetOffset;
|
|
@@ -2996,34 +2781,24 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2996
2781
|
return closeFromSnapPoints();
|
|
2997
2782
|
}
|
|
2998
2783
|
}
|
|
2999
|
-
const
|
|
3000
|
-
const closeDistance = Math.abs(effectiveTargetOffset - closeOffset);
|
|
2784
|
+
const closeDistance = Math.abs(effectiveTargetOffset - popupHeight);
|
|
3001
2785
|
const snapDistance = Math.abs(effectiveTargetOffset - targetSnapPoint.offset);
|
|
3002
2786
|
if (closeDistance < snapDistance) {
|
|
3003
2787
|
return closeFromSnapPoints();
|
|
3004
2788
|
}
|
|
3005
|
-
setActiveSnapPoint
|
|
2789
|
+
setActiveSnapPoint(targetSnapPoint.value, snapPointEventDetails);
|
|
3006
2790
|
clearSwipeRelease();
|
|
3007
2791
|
return false;
|
|
3008
2792
|
}
|
|
3009
2793
|
if (resolvedDirectionalVelocity >= FAST_SWIPE_VELOCITY && dragDelta > 0) {
|
|
3010
2794
|
return closeFromSnapPoints();
|
|
3011
2795
|
}
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
const distance = Math.abs(targetOffset - snapPoint.offset);
|
|
3016
|
-
if (distance < closestDistance) {
|
|
3017
|
-
closestDistance = distance;
|
|
3018
|
-
closestSnapPoint = snapPoint;
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3021
|
-
const closeOffset = popupHeight;
|
|
3022
|
-
const closeDistance = Math.abs(targetOffset - closeOffset);
|
|
3023
|
-
if (closeDistance < closestDistance) {
|
|
2796
|
+
const closestSnapPoint = resolvedSnapPoints[closestSnapPointIndex(resolvedSnapPoints.map(point => point.offset), targetOffset)];
|
|
2797
|
+
const closeDistance = Math.abs(targetOffset - popupHeight);
|
|
2798
|
+
if (closeDistance < Math.abs(targetOffset - closestSnapPoint.offset)) {
|
|
3024
2799
|
return closeFromSnapPoints();
|
|
3025
2800
|
}
|
|
3026
|
-
setActiveSnapPoint
|
|
2801
|
+
setActiveSnapPoint(closestSnapPoint.value, snapPointEventDetails);
|
|
3027
2802
|
clearSwipeRelease();
|
|
3028
2803
|
return false;
|
|
3029
2804
|
},
|
|
@@ -3042,7 +2817,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3042
2817
|
if (dismissEventDetails.isCanceled) {
|
|
3043
2818
|
const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;
|
|
3044
2819
|
if (pendingSnapPoint !== undefined) {
|
|
3045
|
-
setActiveSnapPoint
|
|
2820
|
+
setActiveSnapPoint(pendingSnapPoint, createChangeEventDetails(swipe, event));
|
|
3046
2821
|
}
|
|
3047
2822
|
pendingSwipeCloseSnapPointRef.current = undefined;
|
|
3048
2823
|
resetSwipeRef.current?.();
|
|
@@ -3064,7 +2839,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3064
2839
|
// Parent rejected: revert animation and restore snap point.
|
|
3065
2840
|
const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;
|
|
3066
2841
|
if (pendingSnapPoint !== undefined) {
|
|
3067
|
-
setActiveSnapPoint
|
|
2842
|
+
setActiveSnapPoint(pendingSnapPoint, createChangeEventDetails(swipe, savedEvent));
|
|
3068
2843
|
}
|
|
3069
2844
|
pendingSwipeCloseSnapPointRef.current = undefined;
|
|
3070
2845
|
clearSwipeRelease();
|
|
@@ -3094,42 +2869,18 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3094
2869
|
}
|
|
3095
2870
|
const resolvedRootElement = rootElement;
|
|
3096
2871
|
const doc = ownerDocument(resolvedRootElement);
|
|
3097
|
-
|
|
3098
|
-
function handleNativeTouchMove(event) {
|
|
3099
|
-
// The virtual keyboard provider observes the move to tell a tap apart from a drag.
|
|
3100
|
-
// It must run even when the swipe gesture below claims the event with
|
|
3101
|
-
// `stopPropagation()`, which would otherwise prevent React's delegated handlers
|
|
3102
|
-
// (and the provider) from ever seeing the move.
|
|
3103
|
-
virtualKeyboard?.onTouchMove(event);
|
|
3104
|
-
if (ignoreTouchSwipeRef.current) {
|
|
3105
|
-
return;
|
|
3106
|
-
}
|
|
3107
|
-
const touchState = touchScrollStateRef.current;
|
|
3108
|
-
const touch = event.touches[0];
|
|
3109
|
-
if (!touch || !touchState) {
|
|
3110
|
-
return;
|
|
3111
|
-
}
|
|
2872
|
+
function processTouchMove(event, touchState, touch) {
|
|
3112
2873
|
const drawerAxisDelta = isVerticalScrollAxis ? touch.clientY - touchState.lastY : touch.clientX - touchState.lastX;
|
|
3113
2874
|
|
|
3114
|
-
// Preserve native range interaction by never locking touchmove for range inputs.
|
|
3115
|
-
if (isEventOnRangeInput(event, win)) {
|
|
3116
|
-
touchState.allowSwipe = false;
|
|
3117
|
-
updateTouchScrollPosition(touchState, touch);
|
|
3118
|
-
return;
|
|
3119
|
-
}
|
|
3120
|
-
|
|
3121
2875
|
// Avoid blocking pinch zoom or text selection adjustments on iOS Safari.
|
|
3122
2876
|
if (event.touches.length === 2) {
|
|
3123
|
-
updateTouchScrollPosition(touchState, touch);
|
|
3124
2877
|
return;
|
|
3125
2878
|
}
|
|
3126
2879
|
const allowTouchMove = shouldIgnoreSwipeForTextSelection(doc, resolvedRootElement);
|
|
3127
2880
|
if (allowTouchMove || !open || !mounted || nestedDrawerOpen) {
|
|
3128
|
-
updateTouchScrollPosition(touchState, touch);
|
|
3129
2881
|
return;
|
|
3130
2882
|
}
|
|
3131
|
-
if (
|
|
3132
|
-
updateTouchScrollPosition(touchState, touch);
|
|
2883
|
+
if (shouldYieldTouchMove(touchState, event, touch, isVerticalScrollAxis)) {
|
|
3133
2884
|
return;
|
|
3134
2885
|
}
|
|
3135
2886
|
const scrollTarget = touchState.scrollTarget;
|
|
@@ -3141,27 +2892,21 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3141
2892
|
// move through React re-rasterizes the popup content on every frame.
|
|
3142
2893
|
event.stopPropagation();
|
|
3143
2894
|
moveSwipeNative(event, resolvedRootElement);
|
|
3144
|
-
updateTouchScrollPosition(touchState, touch);
|
|
3145
2895
|
return;
|
|
3146
2896
|
}
|
|
3147
|
-
|
|
3148
|
-
if (!hasScrollableContent) {
|
|
2897
|
+
if (!hasScrollableContentOnAxis(scrollTarget, scrollAxis)) {
|
|
3149
2898
|
// If the scroll container doesn't overflow on the drawer axis, prevent the window from
|
|
3150
2899
|
// scrolling instead.
|
|
3151
2900
|
if (event.cancelable) {
|
|
3152
2901
|
event.preventDefault();
|
|
3153
2902
|
}
|
|
3154
2903
|
event.stopPropagation();
|
|
3155
|
-
updateTouchScrollPosition(touchState, touch);
|
|
3156
2904
|
return;
|
|
3157
2905
|
}
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
const canSwipeFromScrollEdge = canSwipeFromScrollEdgeOnMove(scrollTarget, scrollAxis, swipeDirection, delta);
|
|
2906
|
+
if (drawerAxisDelta !== 0) {
|
|
2907
|
+
const canSwipeFromScrollEdge = canSwipeFromScrollEdgeOnMove(scrollTarget, scrollAxis, swipeDirection, drawerAxisDelta);
|
|
3161
2908
|
if (!touchState.allowSwipe) {
|
|
3162
|
-
if (
|
|
3163
|
-
touchState.allowSwipe = false;
|
|
3164
|
-
} else if (canSwipeFromScrollEdge) {
|
|
2909
|
+
if (event.cancelable && canSwipeFromScrollEdge) {
|
|
3165
2910
|
touchState.allowSwipe = true;
|
|
3166
2911
|
event.preventDefault();
|
|
3167
2912
|
} else {
|
|
@@ -3175,6 +2920,22 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3175
2920
|
event.stopPropagation();
|
|
3176
2921
|
moveSwipeNative(event, resolvedRootElement);
|
|
3177
2922
|
}
|
|
2923
|
+
}
|
|
2924
|
+
function handleNativeTouchMove(event) {
|
|
2925
|
+
// The virtual keyboard provider observes the move to tell a tap apart from a drag.
|
|
2926
|
+
// It must run even when the swipe gesture below claims the event with
|
|
2927
|
+
// `stopPropagation()`, which would otherwise prevent React's delegated handlers
|
|
2928
|
+
// (and the provider) from ever seeing the move.
|
|
2929
|
+
virtualKeyboard?.onTouchMove(event);
|
|
2930
|
+
if (ignoreTouchSwipeRef.current) {
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
const touchState = touchScrollStateRef.current;
|
|
2934
|
+
const touch = event.touches[0];
|
|
2935
|
+
if (!touch || !touchState) {
|
|
2936
|
+
return;
|
|
2937
|
+
}
|
|
2938
|
+
processTouchMove(event, touchState, touch);
|
|
3178
2939
|
updateTouchScrollPosition(touchState, touch);
|
|
3179
2940
|
}
|
|
3180
2941
|
return addEventListener(doc, 'touchmove', handleNativeTouchMove, {
|
|
@@ -3182,18 +2943,13 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3182
2943
|
capture: true
|
|
3183
2944
|
});
|
|
3184
2945
|
}, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, moveSwipeNative, viewportElement, virtualKeyboard]);
|
|
3185
|
-
|
|
2946
|
+
useIsoLayoutEffect(() => {
|
|
3186
2947
|
if (!snapPointRange || swipe$1.swiping) {
|
|
3187
2948
|
return;
|
|
3188
2949
|
}
|
|
3189
|
-
|
|
3190
|
-
applySwipeProgress({
|
|
3191
|
-
resolvedProgress,
|
|
3192
|
-
shouldTrackProgress: true,
|
|
3193
|
-
notifyParent: false
|
|
3194
|
-
});
|
|
2950
|
+
applySwipeProgress(!open || nested ? 0 : snapPointProgress ?? 0, true, false);
|
|
3195
2951
|
}, [applySwipeProgress, frontmostHeight, nested, notifyParentSwipeProgressChange, open, snapPointProgress, snapPointRange, swipe$1.swiping, store, visualStateStore]);
|
|
3196
|
-
|
|
2952
|
+
useIsoLayoutEffect(() => {
|
|
3197
2953
|
if (!notifyParentSwipeProgressChange) {
|
|
3198
2954
|
return undefined;
|
|
3199
2955
|
}
|
|
@@ -3204,13 +2960,19 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3204
2960
|
notifyParentSwipeProgressChange(0);
|
|
3205
2961
|
};
|
|
3206
2962
|
}, [notifyParentSwipeProgressChange, open]);
|
|
3207
|
-
|
|
2963
|
+
useIsoLayoutEffect(() => {
|
|
3208
2964
|
if (open) {
|
|
3209
|
-
resetSwipe
|
|
2965
|
+
// Skip `resetSwipe` while `Drawer.SwipeArea` is driving the open: it zeroes the popup's
|
|
2966
|
+
// `--swipe-movement-*` (via `syncDragStyles(false)`), flashing it fully open for a frame.
|
|
2967
|
+
// `clearSwipeRelease` doesn't touch those vars, so always run it to clear any leftover
|
|
2968
|
+
// release state from a prior dismiss (e.g. when the popup is kept mounted).
|
|
2969
|
+
if (!swipeAreaActiveRef.current) {
|
|
2970
|
+
resetSwipe();
|
|
2971
|
+
}
|
|
3210
2972
|
clearSwipeRelease();
|
|
3211
2973
|
}
|
|
3212
|
-
}, [clearSwipeRelease, open, resetSwipe]);
|
|
3213
|
-
|
|
2974
|
+
}, [clearSwipeRelease, open, resetSwipe, swipeAreaActiveRef]);
|
|
2975
|
+
useIsoLayoutEffect(() => {
|
|
3214
2976
|
const backdropElement = backdropRef.current;
|
|
3215
2977
|
return () => {
|
|
3216
2978
|
visualStateStore?.set({
|
|
@@ -3235,12 +2997,19 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3235
2997
|
swipeStrength: swipeRelease ?? null,
|
|
3236
2998
|
setSwipeDismissed
|
|
3237
2999
|
}), [setSwipeDismissed, swipe$1.getDragStyles, swipe$1.swiping, swipeRelease]);
|
|
3238
|
-
function
|
|
3239
|
-
ignoreTouchSwipeRef.current =
|
|
3000
|
+
function resetTouchSwipeState(ignoreSwipe) {
|
|
3001
|
+
ignoreTouchSwipeRef.current = ignoreSwipe;
|
|
3240
3002
|
touchScrollStateRef.current = null;
|
|
3003
|
+
}
|
|
3004
|
+
function resetTouchTrackingState() {
|
|
3005
|
+
resetTouchSwipeState(false);
|
|
3241
3006
|
lastPointerTypeRef.current = '';
|
|
3242
3007
|
ignoreNextTouchStartFromPenRef.current = false;
|
|
3243
3008
|
}
|
|
3009
|
+
function handlePointerEnd(event) {
|
|
3010
|
+
lastPointerTypeRef.current = '';
|
|
3011
|
+
return event.pointerType !== 'touch';
|
|
3012
|
+
}
|
|
3244
3013
|
return /*#__PURE__*/jsx(DialogViewport, {
|
|
3245
3014
|
ref: forwardedRef,
|
|
3246
3015
|
className: className,
|
|
@@ -3253,8 +3022,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3253
3022
|
if (!open || !mounted || nestedDrawerOpen) {
|
|
3254
3023
|
return;
|
|
3255
3024
|
}
|
|
3256
|
-
const
|
|
3257
|
-
const elementAtPoint = getElementAtPoint(doc, event.clientX, event.clientY);
|
|
3025
|
+
const elementAtPoint = getElementAtPoint(event.currentTarget.getRootNode(), event.clientX, event.clientY);
|
|
3258
3026
|
if (isSwipeIgnoredTarget(elementAtPoint) || isDrawerContentTarget(elementAtPoint)) {
|
|
3259
3027
|
return;
|
|
3260
3028
|
}
|
|
@@ -3270,34 +3038,24 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3270
3038
|
swipePointerProps.onPointerMove?.(event);
|
|
3271
3039
|
},
|
|
3272
3040
|
onPointerUp(event) {
|
|
3273
|
-
if (
|
|
3274
|
-
|
|
3275
|
-
}
|
|
3276
|
-
if (event.pointerType === 'touch') {
|
|
3277
|
-
return;
|
|
3041
|
+
if (handlePointerEnd(event)) {
|
|
3042
|
+
swipePointerProps.onPointerUp?.(event);
|
|
3278
3043
|
}
|
|
3279
|
-
swipePointerProps.onPointerUp?.(event);
|
|
3280
3044
|
},
|
|
3281
3045
|
onPointerCancel(event) {
|
|
3282
|
-
if (
|
|
3283
|
-
|
|
3284
|
-
}
|
|
3285
|
-
if (event.pointerType === 'touch') {
|
|
3286
|
-
return;
|
|
3046
|
+
if (handlePointerEnd(event)) {
|
|
3047
|
+
swipePointerProps.onPointerCancel?.(event);
|
|
3287
3048
|
}
|
|
3288
|
-
swipePointerProps.onPointerCancel?.(event);
|
|
3289
3049
|
},
|
|
3290
3050
|
onTouchStart(event) {
|
|
3291
3051
|
const startedFromPenPointerDown = lastPointerTypeRef.current === 'pen' && ignoreNextTouchStartFromPenRef.current;
|
|
3292
3052
|
if (startedFromPenPointerDown) {
|
|
3293
3053
|
ignoreNextTouchStartFromPenRef.current = false;
|
|
3294
|
-
|
|
3295
|
-
touchScrollStateRef.current = null;
|
|
3054
|
+
resetTouchSwipeState(false);
|
|
3296
3055
|
return;
|
|
3297
3056
|
}
|
|
3298
3057
|
if (!open || !mounted || nestedDrawerOpen) {
|
|
3299
|
-
|
|
3300
|
-
touchScrollStateRef.current = null;
|
|
3058
|
+
resetTouchSwipeState(false);
|
|
3301
3059
|
return;
|
|
3302
3060
|
}
|
|
3303
3061
|
const touch = event.touches[0];
|
|
@@ -3305,32 +3063,25 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3305
3063
|
return;
|
|
3306
3064
|
}
|
|
3307
3065
|
if (isReactTouchEventOnRangeInput(event)) {
|
|
3308
|
-
|
|
3309
|
-
touchScrollStateRef.current = null;
|
|
3066
|
+
resetTouchSwipeState(false);
|
|
3310
3067
|
return;
|
|
3311
3068
|
}
|
|
3312
|
-
const
|
|
3313
|
-
const elementAtPoint = getElementAtPoint(
|
|
3314
|
-
const rootElement = viewportElement ?? popupElementState;
|
|
3069
|
+
const rootElement = event.currentTarget;
|
|
3070
|
+
const elementAtPoint = getElementAtPoint(rootElement.getRootNode(), touch.clientX, touch.clientY);
|
|
3315
3071
|
const eventTarget = getTarget(event.nativeEvent);
|
|
3316
|
-
const target = isElement(eventTarget) ? eventTarget :
|
|
3317
|
-
if (
|
|
3318
|
-
|
|
3319
|
-
touchScrollStateRef.current = null;
|
|
3072
|
+
const target = isElement(eventTarget) ? eventTarget : rootElement;
|
|
3073
|
+
if (!contains(rootElement, target)) {
|
|
3074
|
+
resetTouchSwipeState(true);
|
|
3320
3075
|
return;
|
|
3321
3076
|
}
|
|
3322
3077
|
virtualKeyboard?.onTouchStart(event);
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
touchScrollStateRef.current = null;
|
|
3078
|
+
if (isSwipeIgnoredTarget(elementAtPoint)) {
|
|
3079
|
+
resetTouchSwipeState(true);
|
|
3326
3080
|
return;
|
|
3327
3081
|
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
scrollTarget = findScrollableTouchTarget(target, rootElement, scrollAxis);
|
|
3332
|
-
hasCrossAxisScrollableContent = findScrollableTouchTarget(target, rootElement, crossScrollAxis) != null;
|
|
3333
|
-
}
|
|
3082
|
+
ignoreTouchSwipeRef.current = false;
|
|
3083
|
+
const scrollTarget = findScrollableTouchTarget(target, rootElement, scrollAxis);
|
|
3084
|
+
const hasCrossAxisScrollableContent = findScrollableTouchTarget(target, rootElement, crossScrollAxis) != null;
|
|
3334
3085
|
let allowSwipe = null;
|
|
3335
3086
|
if (scrollTarget) {
|
|
3336
3087
|
const canSwipeFromEdge = isAtSwipeStartEdge(scrollTarget, scrollAxis, swipeDirection);
|
|
@@ -3344,7 +3095,8 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3344
3095
|
scrollTarget,
|
|
3345
3096
|
hasCrossAxisScrollableContent,
|
|
3346
3097
|
allowSwipe,
|
|
3347
|
-
preserveNativeCrossAxisScroll: false
|
|
3098
|
+
preserveNativeCrossAxisScroll: false,
|
|
3099
|
+
drawerAxisAttributed: false
|
|
3348
3100
|
};
|
|
3349
3101
|
swipeTouchProps.onTouchStart?.(event);
|
|
3350
3102
|
},
|
|
@@ -3369,24 +3121,8 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3369
3121
|
});
|
|
3370
3122
|
});
|
|
3371
3123
|
if (process.env.NODE_ENV !== "production") DrawerViewport.displayName = "DrawerViewport";
|
|
3372
|
-
function setSwipeDismissedElements(popupElement, backdropElement, dismissed) {
|
|
3373
|
-
if (dismissed) {
|
|
3374
|
-
popupElement?.setAttribute(DrawerPopupDataAttributes.swipeDismiss, '');
|
|
3375
|
-
backdropElement?.setAttribute(DrawerPopupDataAttributes.swipeDismiss, '');
|
|
3376
|
-
return;
|
|
3377
|
-
}
|
|
3378
|
-
popupElement?.removeAttribute(DrawerPopupDataAttributes.swipeDismiss);
|
|
3379
|
-
backdropElement?.removeAttribute(DrawerPopupDataAttributes.swipeDismiss);
|
|
3380
|
-
}
|
|
3381
3124
|
function setBackdropSwipingAttribute(backdropElement, swiping) {
|
|
3382
|
-
|
|
3383
|
-
return;
|
|
3384
|
-
}
|
|
3385
|
-
if (swiping) {
|
|
3386
|
-
backdropElement.setAttribute(DrawerPopupDataAttributes.swiping, '');
|
|
3387
|
-
return;
|
|
3388
|
-
}
|
|
3389
|
-
backdropElement.removeAttribute(DrawerPopupDataAttributes.swiping);
|
|
3125
|
+
backdropElement?.toggleAttribute(DrawerPopupDataAttributes.swiping, swiping);
|
|
3390
3126
|
}
|
|
3391
3127
|
function isSwipeIgnoredTarget(target) {
|
|
3392
3128
|
return Boolean(target?.closest(BASE_UI_SWIPE_IGNORE_SELECTOR));
|
|
@@ -3394,17 +3130,16 @@ function isSwipeIgnoredTarget(target) {
|
|
|
3394
3130
|
function isDrawerContentTarget(target) {
|
|
3395
3131
|
return Boolean(target?.closest(DRAWER_CONTENT_SELECTOR));
|
|
3396
3132
|
}
|
|
3133
|
+
function getBaseSwipeSize(element, direction) {
|
|
3134
|
+
return direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;
|
|
3135
|
+
}
|
|
3397
3136
|
function getBaseSwipeThreshold(element, direction) {
|
|
3398
|
-
|
|
3399
|
-
return Math.max(size * 0.5, MIN_SWIPE_THRESHOLD);
|
|
3137
|
+
return Math.max(getBaseSwipeSize(element, direction) * 0.5, MIN_SWIPE_THRESHOLD);
|
|
3400
3138
|
}
|
|
3401
3139
|
function isRangeInput(target, win) {
|
|
3402
3140
|
return target instanceof win.HTMLInputElement && target.type === 'range';
|
|
3403
3141
|
}
|
|
3404
3142
|
function isTextSelectionControl(target) {
|
|
3405
|
-
if (!isElement(target)) {
|
|
3406
|
-
return false;
|
|
3407
|
-
}
|
|
3408
3143
|
return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
|
|
3409
3144
|
}
|
|
3410
3145
|
function hasExpandedSelectionWithinTarget(selection, target) {
|
|
@@ -3414,8 +3149,7 @@ function hasExpandedSelectionWithinTarget(selection, target) {
|
|
|
3414
3149
|
}
|
|
3415
3150
|
function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
|
|
3416
3151
|
const activeEl = activeElement(doc);
|
|
3417
|
-
|
|
3418
|
-
if (activeElementWithinRoot && isTextSelectionControl(activeEl)) {
|
|
3152
|
+
if (activeEl && contains(rootElement, activeEl) && isTextSelectionControl(activeEl)) {
|
|
3419
3153
|
const {
|
|
3420
3154
|
selectionStart,
|
|
3421
3155
|
selectionEnd
|
|
@@ -3431,11 +3165,7 @@ function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
|
|
|
3431
3165
|
return hasExpandedSelectionWithinTarget(selection, rootElement);
|
|
3432
3166
|
}
|
|
3433
3167
|
function isEventOnRangeInput(event, win) {
|
|
3434
|
-
|
|
3435
|
-
if (composedPath) {
|
|
3436
|
-
return composedPath.some(pathTarget => isRangeInput(pathTarget, win));
|
|
3437
|
-
}
|
|
3438
|
-
return isRangeInput(getTarget(event), win);
|
|
3168
|
+
return event.composedPath().some(pathTarget => isRangeInput(pathTarget, win));
|
|
3439
3169
|
}
|
|
3440
3170
|
function isReactTouchEventOnRangeInput(event) {
|
|
3441
3171
|
return isEventOnRangeInput(event.nativeEvent, getWindow(event.currentTarget));
|
|
@@ -3444,25 +3174,51 @@ function updateTouchScrollPosition(touchState, touch) {
|
|
|
3444
3174
|
touchState.lastX = touch.clientX;
|
|
3445
3175
|
touchState.lastY = touch.clientY;
|
|
3446
3176
|
}
|
|
3447
|
-
|
|
3177
|
+
|
|
3178
|
+
/**
|
|
3179
|
+
* Arbitrates a touchmove between the drawer swipe and a native cross-axis scroll.
|
|
3180
|
+
* Returns `true` when the move must be left alone — either because the cross axis already won the
|
|
3181
|
+
* gesture, or because neither axis has passed the slop yet and the gesture cannot be attributed.
|
|
3182
|
+
*/
|
|
3183
|
+
function shouldYieldTouchMove(touchState, event, touch, isVerticalScrollAxis) {
|
|
3448
3184
|
if (touchState.preserveNativeCrossAxisScroll) {
|
|
3449
3185
|
return true;
|
|
3450
3186
|
}
|
|
3451
|
-
|
|
3187
|
+
|
|
3188
|
+
// Attribution happens once per gesture. Re-arbitrating after the drawer axis has won would let
|
|
3189
|
+
// the pre-attribution branches below fire mid-drag (the slop is measured from the touch origin,
|
|
3190
|
+
// which is never re-baselined), freezing the popup and dropping `preventDefault()`.
|
|
3191
|
+
if (touchState.drawerAxisAttributed || touchState.allowSwipe === true || !touchState.hasCrossAxisScrollableContent) {
|
|
3452
3192
|
return false;
|
|
3453
3193
|
}
|
|
3194
|
+
|
|
3195
|
+
// A non-cancelable touchmove means the browser has already committed the gesture to a native
|
|
3196
|
+
// scroll; claiming it for the swipe would drag the popup alongside the scrolling content.
|
|
3197
|
+
if (!event.cancelable) {
|
|
3198
|
+
touchState.preserveNativeCrossAxisScroll = true;
|
|
3199
|
+
return true;
|
|
3200
|
+
}
|
|
3454
3201
|
const drawerAxisGestureDelta = isVerticalScrollAxis ? touch.clientY - touchState.startY : touch.clientX - touchState.startX;
|
|
3455
3202
|
const crossAxisGestureDelta = isVerticalScrollAxis ? touch.clientX - touchState.startX : touch.clientY - touchState.startY;
|
|
3456
3203
|
const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
|
|
3457
3204
|
const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
|
|
3458
|
-
if (absCrossAxisGestureDelta
|
|
3205
|
+
if (absCrossAxisGestureDelta >= AXIS_LOCK_SLOP && absCrossAxisGestureDelta > absDrawerAxisGestureDelta + AXIS_LOCK_BIAS) {
|
|
3206
|
+
touchState.preserveNativeCrossAxisScroll = true;
|
|
3207
|
+
return true;
|
|
3208
|
+
}
|
|
3209
|
+
if (absDrawerAxisGestureDelta >= AXIS_LOCK_SLOP) {
|
|
3210
|
+
touchState.drawerAxisAttributed = true;
|
|
3459
3211
|
return false;
|
|
3460
3212
|
}
|
|
3461
|
-
|
|
3213
|
+
|
|
3214
|
+
// Neither axis has traveled past the slop yet, so the gesture cannot be attributed. Leave the
|
|
3215
|
+
// event alone: on iOS, `preventDefault()` on the first cancelable touchmove cancels native
|
|
3216
|
+
// scrolling for the entire gesture, which would lock a cross-axis scroll that only passes the
|
|
3217
|
+
// slop on a later move.
|
|
3462
3218
|
return true;
|
|
3463
3219
|
}
|
|
3464
3220
|
function hasScrollableContentOnAxis(scrollTarget, axis) {
|
|
3465
|
-
return
|
|
3221
|
+
return getScrollMetrics(scrollTarget, axis).max > 0;
|
|
3466
3222
|
}
|
|
3467
3223
|
function getScrollMetrics(scrollTarget, axis) {
|
|
3468
3224
|
if (axis === 'vertical') {
|
|
@@ -3479,48 +3235,23 @@ function getScrollMetrics(scrollTarget, axis) {
|
|
|
3479
3235
|
};
|
|
3480
3236
|
}
|
|
3481
3237
|
function isAtSwipeStartEdge(scrollTarget, axis, direction) {
|
|
3238
|
+
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
3482
3239
|
const {
|
|
3483
3240
|
offset,
|
|
3484
3241
|
max
|
|
3485
3242
|
} = getScrollMetrics(scrollTarget, axis);
|
|
3486
|
-
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
3487
|
-
if (dismissFromStartEdge === null) {
|
|
3488
|
-
return false;
|
|
3489
|
-
}
|
|
3490
3243
|
return dismissFromStartEdge ? offset <= 0 : offset >= max;
|
|
3491
3244
|
}
|
|
3492
3245
|
function canSwipeFromScrollEdgeOnMove(scrollTarget, axis, direction, delta) {
|
|
3493
|
-
const {
|
|
3494
|
-
offset,
|
|
3495
|
-
max
|
|
3496
|
-
} = getScrollMetrics(scrollTarget, axis);
|
|
3497
3246
|
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
3498
|
-
if (dismissFromStartEdge === null) {
|
|
3499
|
-
return false;
|
|
3500
|
-
}
|
|
3501
3247
|
const movingTowardDismiss = dismissFromStartEdge ? delta > 0 : delta < 0;
|
|
3502
3248
|
if (!movingTowardDismiss) {
|
|
3503
3249
|
return false;
|
|
3504
3250
|
}
|
|
3505
|
-
return
|
|
3251
|
+
return isAtSwipeStartEdge(scrollTarget, axis, direction);
|
|
3506
3252
|
}
|
|
3507
3253
|
function shouldDismissFromStartEdge(direction, axis) {
|
|
3508
|
-
|
|
3509
|
-
if (direction === 'down') {
|
|
3510
|
-
return true;
|
|
3511
|
-
}
|
|
3512
|
-
if (direction === 'up') {
|
|
3513
|
-
return false;
|
|
3514
|
-
}
|
|
3515
|
-
return null;
|
|
3516
|
-
}
|
|
3517
|
-
if (direction === 'right') {
|
|
3518
|
-
return true;
|
|
3519
|
-
}
|
|
3520
|
-
if (direction === 'left') {
|
|
3521
|
-
return false;
|
|
3522
|
-
}
|
|
3523
|
-
return null;
|
|
3254
|
+
return axis === 'vertical' ? direction === 'down' : direction === 'right';
|
|
3524
3255
|
}
|
|
3525
3256
|
|
|
3526
3257
|
const ScrollAreaRootContext = /*#__PURE__*/React.createContext(undefined);
|
|
@@ -3533,20 +3264,6 @@ function useScrollAreaRootContext() {
|
|
|
3533
3264
|
return context;
|
|
3534
3265
|
}
|
|
3535
3266
|
|
|
3536
|
-
let ScrollAreaRootCssVars = /*#__PURE__*/function (ScrollAreaRootCssVars) {
|
|
3537
|
-
/**
|
|
3538
|
-
* The scroll area's corner height.
|
|
3539
|
-
* @type {number}
|
|
3540
|
-
*/
|
|
3541
|
-
ScrollAreaRootCssVars["scrollAreaCornerHeight"] = "--scroll-area-corner-height";
|
|
3542
|
-
/**
|
|
3543
|
-
* The scroll area's corner width.
|
|
3544
|
-
* @type {number}
|
|
3545
|
-
*/
|
|
3546
|
-
ScrollAreaRootCssVars["scrollAreaCornerWidth"] = "--scroll-area-corner-width";
|
|
3547
|
-
return ScrollAreaRootCssVars;
|
|
3548
|
-
}({});
|
|
3549
|
-
|
|
3550
3267
|
const SCROLL_TIMEOUT = 500;
|
|
3551
3268
|
const MIN_THUMB_SIZE = 16;
|
|
3552
3269
|
|
|
@@ -3555,108 +3272,28 @@ function getOffset(element, prop, axis) {
|
|
|
3555
3272
|
return 0;
|
|
3556
3273
|
}
|
|
3557
3274
|
const styles = getComputedStyle(element);
|
|
3558
|
-
const
|
|
3275
|
+
const key = `${prop}${axis === 'x' ? 'Inline' : 'Block'}`;
|
|
3276
|
+
const start = parseFloat(styles[`${key}Start`]);
|
|
3559
3277
|
|
|
3560
3278
|
// Safari misreports `marginInlineEnd` in RTL.
|
|
3561
3279
|
// We have to assume the start/end values are symmetrical, which is likely.
|
|
3562
3280
|
if (axis === 'x' && prop === 'margin') {
|
|
3563
|
-
return
|
|
3281
|
+
return start * 2;
|
|
3564
3282
|
}
|
|
3565
|
-
return
|
|
3283
|
+
return start + parseFloat(styles[`${key}End`]);
|
|
3566
3284
|
}
|
|
3567
3285
|
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
*/
|
|
3573
|
-
ScrollAreaScrollbarDataAttributes["orientation"] = "data-orientation";
|
|
3574
|
-
/**
|
|
3575
|
-
* Present when the pointer is over the scroll area.
|
|
3576
|
-
*/
|
|
3577
|
-
ScrollAreaScrollbarDataAttributes["hovering"] = "data-hovering";
|
|
3578
|
-
/**
|
|
3579
|
-
* Present when the user scrolls inside the scroll area.
|
|
3580
|
-
*/
|
|
3581
|
-
ScrollAreaScrollbarDataAttributes["scrolling"] = "data-scrolling";
|
|
3582
|
-
/**
|
|
3583
|
-
* Present when the scroll area content is wider than the viewport.
|
|
3584
|
-
*/
|
|
3585
|
-
ScrollAreaScrollbarDataAttributes["hasOverflowX"] = "data-has-overflow-x";
|
|
3586
|
-
/**
|
|
3587
|
-
* Present when the scroll area content is taller than the viewport.
|
|
3588
|
-
*/
|
|
3589
|
-
ScrollAreaScrollbarDataAttributes["hasOverflowY"] = "data-has-overflow-y";
|
|
3590
|
-
/**
|
|
3591
|
-
* Present when there is overflow on the horizontal start side.
|
|
3592
|
-
*/
|
|
3593
|
-
ScrollAreaScrollbarDataAttributes["overflowXStart"] = "data-overflow-x-start";
|
|
3594
|
-
/**
|
|
3595
|
-
* Present when there is overflow on the horizontal end side.
|
|
3596
|
-
*/
|
|
3597
|
-
ScrollAreaScrollbarDataAttributes["overflowXEnd"] = "data-overflow-x-end";
|
|
3598
|
-
/**
|
|
3599
|
-
* Present when there is overflow on the vertical start side.
|
|
3600
|
-
*/
|
|
3601
|
-
ScrollAreaScrollbarDataAttributes["overflowYStart"] = "data-overflow-y-start";
|
|
3602
|
-
/**
|
|
3603
|
-
* Present when there is overflow on the vertical end side.
|
|
3604
|
-
*/
|
|
3605
|
-
ScrollAreaScrollbarDataAttributes["overflowYEnd"] = "data-overflow-y-end";
|
|
3606
|
-
return ScrollAreaScrollbarDataAttributes;
|
|
3607
|
-
}({});
|
|
3608
|
-
|
|
3609
|
-
let ScrollAreaRootDataAttributes = /*#__PURE__*/function (ScrollAreaRootDataAttributes) {
|
|
3610
|
-
/**
|
|
3611
|
-
* Present when the user scrolls inside the scroll area.
|
|
3612
|
-
*/
|
|
3613
|
-
ScrollAreaRootDataAttributes["scrolling"] = "data-scrolling";
|
|
3614
|
-
/**
|
|
3615
|
-
* Present when the scroll area content is wider than the viewport.
|
|
3616
|
-
*/
|
|
3617
|
-
ScrollAreaRootDataAttributes["hasOverflowX"] = "data-has-overflow-x";
|
|
3618
|
-
/**
|
|
3619
|
-
* Present when the scroll area content is taller than the viewport.
|
|
3620
|
-
*/
|
|
3621
|
-
ScrollAreaRootDataAttributes["hasOverflowY"] = "data-has-overflow-y";
|
|
3622
|
-
/**
|
|
3623
|
-
* Present when there is overflow on the horizontal start side.
|
|
3624
|
-
*/
|
|
3625
|
-
ScrollAreaRootDataAttributes["overflowXStart"] = "data-overflow-x-start";
|
|
3626
|
-
/**
|
|
3627
|
-
* Present when there is overflow on the horizontal end side.
|
|
3628
|
-
*/
|
|
3629
|
-
ScrollAreaRootDataAttributes["overflowXEnd"] = "data-overflow-x-end";
|
|
3630
|
-
/**
|
|
3631
|
-
* Present when there is overflow on the vertical start side.
|
|
3632
|
-
*/
|
|
3633
|
-
ScrollAreaRootDataAttributes["overflowYStart"] = "data-overflow-y-start";
|
|
3634
|
-
/**
|
|
3635
|
-
* Present when there is overflow on the vertical end side.
|
|
3636
|
-
*/
|
|
3637
|
-
ScrollAreaRootDataAttributes["overflowYEnd"] = "data-overflow-y-end";
|
|
3638
|
-
return ScrollAreaRootDataAttributes;
|
|
3639
|
-
}({});
|
|
3640
|
-
|
|
3286
|
+
// Data-attribute strings inlined so `ScrollAreaRootDataAttributes` tree-shakes out.
|
|
3287
|
+
const attr = name => value => value ? {
|
|
3288
|
+
[name]: ''
|
|
3289
|
+
} : null;
|
|
3641
3290
|
const scrollAreaStateAttributesMapping = {
|
|
3642
|
-
hasOverflowX:
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
overflowXStart: value => value ? {
|
|
3649
|
-
[ScrollAreaRootDataAttributes.overflowXStart]: ''
|
|
3650
|
-
} : null,
|
|
3651
|
-
overflowXEnd: value => value ? {
|
|
3652
|
-
[ScrollAreaRootDataAttributes.overflowXEnd]: ''
|
|
3653
|
-
} : null,
|
|
3654
|
-
overflowYStart: value => value ? {
|
|
3655
|
-
[ScrollAreaRootDataAttributes.overflowYStart]: ''
|
|
3656
|
-
} : null,
|
|
3657
|
-
overflowYEnd: value => value ? {
|
|
3658
|
-
[ScrollAreaRootDataAttributes.overflowYEnd]: ''
|
|
3659
|
-
} : null,
|
|
3291
|
+
hasOverflowX: attr('data-has-overflow-x'),
|
|
3292
|
+
hasOverflowY: attr('data-has-overflow-y'),
|
|
3293
|
+
overflowXStart: attr('data-overflow-x-start'),
|
|
3294
|
+
overflowXEnd: attr('data-overflow-x-end'),
|
|
3295
|
+
overflowYStart: attr('data-overflow-y-start'),
|
|
3296
|
+
overflowYEnd: attr('data-overflow-y-end'),
|
|
3660
3297
|
cornerHidden: () => null
|
|
3661
3298
|
};
|
|
3662
3299
|
|
|
@@ -3722,99 +3359,145 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3722
3359
|
const thumbYRef = React.useRef(null);
|
|
3723
3360
|
const thumbXRef = React.useRef(null);
|
|
3724
3361
|
const cornerRef = React.useRef(null);
|
|
3725
|
-
const
|
|
3362
|
+
const activePointerIdRef = React.useRef(null);
|
|
3726
3363
|
const startYRef = React.useRef(0);
|
|
3727
3364
|
const startXRef = React.useRef(0);
|
|
3728
3365
|
const startScrollTopRef = React.useRef(0);
|
|
3729
3366
|
const startScrollLeftRef = React.useRef(0);
|
|
3730
3367
|
const currentOrientationRef = React.useRef('vertical');
|
|
3731
3368
|
const scrollPositionRef = React.useRef(DEFAULT_COORDS);
|
|
3369
|
+
const savedSnapTypeRef = React.useRef(null);
|
|
3370
|
+
function startScrolling(vertical) {
|
|
3371
|
+
const setScrolling = vertical ? setScrollingY : setScrollingX;
|
|
3372
|
+
const timeout = vertical ? scrollYTimeout : scrollXTimeout;
|
|
3373
|
+
setScrolling(true);
|
|
3374
|
+
timeout.start(SCROLL_TIMEOUT, () => {
|
|
3375
|
+
setScrolling(false);
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3732
3378
|
const handleScroll = useStableCallback(scrollPosition => {
|
|
3733
3379
|
const offsetX = scrollPosition.x - scrollPositionRef.current.x;
|
|
3734
3380
|
const offsetY = scrollPosition.y - scrollPositionRef.current.y;
|
|
3735
3381
|
scrollPositionRef.current = scrollPosition;
|
|
3736
3382
|
if (offsetY !== 0) {
|
|
3737
|
-
|
|
3738
|
-
scrollYTimeout.start(SCROLL_TIMEOUT, () => {
|
|
3739
|
-
setScrollingY(false);
|
|
3740
|
-
});
|
|
3383
|
+
startScrolling(true);
|
|
3741
3384
|
}
|
|
3742
3385
|
if (offsetX !== 0) {
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3386
|
+
startScrolling(false);
|
|
3387
|
+
}
|
|
3388
|
+
});
|
|
3389
|
+
|
|
3390
|
+
// CSS scroll snap forces every programmatic scroll to land on a snap
|
|
3391
|
+
// point, making thumb dragging jump between snap points. Native
|
|
3392
|
+
// scrollbars suppress snapping while dragging, so disable it until the
|
|
3393
|
+
// pointer is released; restoring the value re-snaps the viewport. The
|
|
3394
|
+
// save is guarded so a second pointer during an active drag can't
|
|
3395
|
+
// clobber the saved value with `none`.
|
|
3396
|
+
const disableViewportSnap = useStableCallback(() => {
|
|
3397
|
+
const viewportEl = viewportRef.current;
|
|
3398
|
+
if (viewportEl && savedSnapTypeRef.current === null) {
|
|
3399
|
+
savedSnapTypeRef.current = viewportEl.style.scrollSnapType;
|
|
3400
|
+
viewportEl.style.scrollSnapType = 'none';
|
|
3747
3401
|
}
|
|
3748
3402
|
});
|
|
3749
3403
|
const handlePointerDown = useStableCallback(event => {
|
|
3750
3404
|
if (event.button !== 0) {
|
|
3751
3405
|
return;
|
|
3752
3406
|
}
|
|
3753
|
-
|
|
3407
|
+
if (activePointerIdRef.current !== null) {
|
|
3408
|
+
const activeThumb = currentOrientationRef.current === 'vertical' ? thumbYRef.current : thumbXRef.current;
|
|
3409
|
+
// A live drag holds capture for the active pointer — ignore other pointers.
|
|
3410
|
+
// No capture means the release went missing entirely (silent capture drop
|
|
3411
|
+
// with an id that never reappears, e.g. a lost touch contact), so let the
|
|
3412
|
+
// new pointer take over the latch instead of leaving dragging dead.
|
|
3413
|
+
if (activeThumb?.hasPointerCapture(activePointerIdRef.current)) {
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
activePointerIdRef.current = event.pointerId;
|
|
3754
3418
|
startYRef.current = event.clientY;
|
|
3755
3419
|
startXRef.current = event.clientX;
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
if (
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
thumbXRef.current.setPointerCapture(event.pointerId);
|
|
3420
|
+
// Literal instead of `ScrollAreaScrollbarDataAttributes.orientation`: referencing an
|
|
3421
|
+
// enum member retains its whole object in the bundle, so the strings are inlined and
|
|
3422
|
+
// the enums kept for docs only.
|
|
3423
|
+
currentOrientationRef.current = event.currentTarget.getAttribute('data-orientation');
|
|
3424
|
+
const viewportEl = viewportRef.current;
|
|
3425
|
+
if (viewportEl) {
|
|
3426
|
+
startScrollTopRef.current = viewportEl.scrollTop;
|
|
3427
|
+
startScrollLeftRef.current = viewportEl.scrollLeft;
|
|
3428
|
+
disableViewportSnap();
|
|
3766
3429
|
}
|
|
3430
|
+
const thumb = currentOrientationRef.current === 'vertical' ? thumbYRef.current : thumbXRef.current;
|
|
3431
|
+
thumb?.setPointerCapture(event.pointerId);
|
|
3767
3432
|
});
|
|
3768
|
-
const
|
|
3769
|
-
if (
|
|
3433
|
+
const handlePointerUp = useStableCallback(event => {
|
|
3434
|
+
if (event.pointerId !== activePointerIdRef.current) {
|
|
3770
3435
|
return;
|
|
3771
3436
|
}
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
const scrollbarYOffset = getOffset(scrollbarYRef.current, 'padding', 'y');
|
|
3781
|
-
const thumbYOffset = getOffset(thumbYRef.current, 'margin', 'y');
|
|
3782
|
-
const thumbHeight = thumbYRef.current.offsetHeight;
|
|
3783
|
-
const maxThumbOffsetY = scrollbarYRef.current.offsetHeight - thumbHeight - scrollbarYOffset - thumbYOffset;
|
|
3784
|
-
const scrollRatioY = deltaY / maxThumbOffsetY;
|
|
3785
|
-
viewportRef.current.scrollTop = startScrollTopRef.current + scrollRatioY * (scrollableContentHeight - viewportHeight);
|
|
3786
|
-
event.preventDefault();
|
|
3787
|
-
setScrollingY(true);
|
|
3788
|
-
scrollYTimeout.start(SCROLL_TIMEOUT, () => {
|
|
3789
|
-
setScrollingY(false);
|
|
3790
|
-
});
|
|
3791
|
-
}
|
|
3792
|
-
if (thumbXRef.current && scrollbarXRef.current && currentOrientationRef.current === 'horizontal') {
|
|
3793
|
-
const scrollbarXOffset = getOffset(scrollbarXRef.current, 'padding', 'x');
|
|
3794
|
-
const thumbXOffset = getOffset(thumbXRef.current, 'margin', 'x');
|
|
3795
|
-
const thumbWidth = thumbXRef.current.offsetWidth;
|
|
3796
|
-
const maxThumbOffsetX = scrollbarXRef.current.offsetWidth - thumbWidth - scrollbarXOffset - thumbXOffset;
|
|
3797
|
-
const scrollRatioX = deltaX / maxThumbOffsetX;
|
|
3798
|
-
viewportRef.current.scrollLeft = startScrollLeftRef.current + scrollRatioX * (scrollableContentWidth - viewportWidth);
|
|
3799
|
-
event.preventDefault();
|
|
3800
|
-
setScrollingX(true);
|
|
3801
|
-
scrollXTimeout.start(SCROLL_TIMEOUT, () => {
|
|
3802
|
-
setScrollingX(false);
|
|
3803
|
-
});
|
|
3437
|
+
activePointerIdRef.current = null;
|
|
3438
|
+
// Clear the drag's scrolling state immediately rather than waiting for the
|
|
3439
|
+
// `SCROLL_TIMEOUT` timer armed by the last drag move, so every release path
|
|
3440
|
+
// (real, `pointercancel`, or the missed-release fallback) behaves the same.
|
|
3441
|
+
(currentOrientationRef.current === 'vertical' ? setScrollingY : setScrollingX)(false);
|
|
3442
|
+
if (savedSnapTypeRef.current !== null) {
|
|
3443
|
+
if (viewportRef.current) {
|
|
3444
|
+
viewportRef.current.style.scrollSnapType = savedSnapTypeRef.current;
|
|
3804
3445
|
}
|
|
3446
|
+
savedSnapTypeRef.current = null;
|
|
3805
3447
|
}
|
|
3806
|
-
|
|
3807
|
-
const handlePointerUp = useStableCallback(event => {
|
|
3808
|
-
thumbDraggingRef.current = false;
|
|
3809
|
-
|
|
3448
|
+
const thumb = currentOrientationRef.current === 'vertical' ? thumbYRef.current : thumbXRef.current;
|
|
3810
3449
|
// `pointercancel` releases capture implicitly, so guard against releasing a
|
|
3811
3450
|
// capture we no longer hold (which would throw).
|
|
3812
|
-
if (
|
|
3813
|
-
|
|
3451
|
+
if (thumb?.hasPointerCapture(event.pointerId)) {
|
|
3452
|
+
thumb.releasePointerCapture(event.pointerId);
|
|
3814
3453
|
}
|
|
3815
|
-
|
|
3816
|
-
|
|
3454
|
+
});
|
|
3455
|
+
const handlePointerMove = useStableCallback(event => {
|
|
3456
|
+
if (event.pointerId !== activePointerIdRef.current) {
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
// The release can go missing entirely (e.g. the browser drops pointer
|
|
3461
|
+
// capture while the scrollbar is hidden mid-drag), leaving the drag
|
|
3462
|
+
// latched so a buttonless hover over the thumb scrolls the viewport.
|
|
3463
|
+
// Treat a move without the primary button held (`buttons` bit 1 unset)
|
|
3464
|
+
// as the missed release.
|
|
3465
|
+
if (event.buttons % 2 === 0) {
|
|
3466
|
+
handlePointerUp(event);
|
|
3467
|
+
return;
|
|
3817
3468
|
}
|
|
3469
|
+
const viewportEl = viewportRef.current;
|
|
3470
|
+
if (!viewportEl) {
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3473
|
+
const vertical = currentOrientationRef.current === 'vertical';
|
|
3474
|
+
const thumbEl = vertical ? thumbYRef.current : thumbXRef.current;
|
|
3475
|
+
const scrollbarEl = vertical ? scrollbarYRef.current : scrollbarXRef.current;
|
|
3476
|
+
if (!thumbEl || !scrollbarEl) {
|
|
3477
|
+
return;
|
|
3478
|
+
}
|
|
3479
|
+
const axis = vertical ? 'y' : 'x';
|
|
3480
|
+
const scrollbarOffset = getOffset(scrollbarEl, 'padding', axis);
|
|
3481
|
+
const thumbOffset = getOffset(thumbEl, 'margin', axis);
|
|
3482
|
+
const thumbSizePx = vertical ? thumbEl.offsetHeight : thumbEl.offsetWidth;
|
|
3483
|
+
const trackSize = vertical ? scrollbarEl.offsetHeight : scrollbarEl.offsetWidth;
|
|
3484
|
+
const maxThumbOffset = trackSize - thumbSizePx - scrollbarOffset - thumbOffset;
|
|
3485
|
+
// A short or heavily padded track can drive `maxThumbOffset` to zero or
|
|
3486
|
+
// negative once the thumb hits its `MIN_THUMB_SIZE` floor. Dividing by it
|
|
3487
|
+
// would yield a non-finite (`Infinity`/`NaN`) or inverted scroll position.
|
|
3488
|
+
const delta = vertical ? event.clientY - startYRef.current : event.clientX - startXRef.current;
|
|
3489
|
+
const scrollRatio = maxThumbOffset <= 0 ? 0 : delta / maxThumbOffset;
|
|
3490
|
+
const scrollableSize = vertical ? viewportEl.scrollHeight : viewportEl.scrollWidth;
|
|
3491
|
+
const viewportSize = vertical ? viewportEl.clientHeight : viewportEl.clientWidth;
|
|
3492
|
+
const startScroll = vertical ? startScrollTopRef.current : startScrollLeftRef.current;
|
|
3493
|
+
const nextScroll = startScroll + scrollRatio * (scrollableSize - viewportSize);
|
|
3494
|
+
if (vertical) {
|
|
3495
|
+
viewportEl.scrollTop = nextScroll;
|
|
3496
|
+
} else {
|
|
3497
|
+
viewportEl.scrollLeft = nextScroll;
|
|
3498
|
+
}
|
|
3499
|
+
event.preventDefault();
|
|
3500
|
+
startScrolling(vertical);
|
|
3818
3501
|
});
|
|
3819
3502
|
function handleTouchModalityChange(event) {
|
|
3820
3503
|
setTouchModality(event.pointerType === 'touch');
|
|
@@ -3846,8 +3529,8 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3846
3529
|
},
|
|
3847
3530
|
style: {
|
|
3848
3531
|
position: 'relative',
|
|
3849
|
-
[
|
|
3850
|
-
[
|
|
3532
|
+
['--scroll-area-corner-height']: `${cornerSize.height}px`,
|
|
3533
|
+
['--scroll-area-corner-width']: `${cornerSize.width}px`
|
|
3851
3534
|
}
|
|
3852
3535
|
};
|
|
3853
3536
|
const element = useRenderElement('div', componentProps, {
|
|
@@ -3861,6 +3544,7 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3861
3544
|
handlePointerMove,
|
|
3862
3545
|
handlePointerUp,
|
|
3863
3546
|
handleScroll,
|
|
3547
|
+
disableViewportSnap,
|
|
3864
3548
|
cornerSize,
|
|
3865
3549
|
setCornerSize,
|
|
3866
3550
|
thumbSize,
|
|
@@ -3870,13 +3554,10 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3870
3554
|
touchModality,
|
|
3871
3555
|
cornerRef,
|
|
3872
3556
|
scrollingX,
|
|
3873
|
-
setScrollingX,
|
|
3874
3557
|
scrollingY,
|
|
3875
|
-
setScrollingY,
|
|
3876
3558
|
hovering,
|
|
3877
3559
|
setHovering,
|
|
3878
3560
|
viewportRef,
|
|
3879
|
-
rootRef,
|
|
3880
3561
|
scrollbarYRef,
|
|
3881
3562
|
scrollbarXRef,
|
|
3882
3563
|
thumbYRef,
|
|
@@ -3893,7 +3574,7 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3893
3574
|
yStart,
|
|
3894
3575
|
yEnd
|
|
3895
3576
|
}
|
|
3896
|
-
}), [handlePointerDown, handlePointerMove, handlePointerUp, handleScroll, cornerSize, thumbSize, hasMeasuredScrollbar, touchModality, scrollingX,
|
|
3577
|
+
}), [handlePointerDown, handlePointerMove, handlePointerUp, handleScroll, disableViewportSnap, cornerSize, thumbSize, hasMeasuredScrollbar, touchModality, scrollingX, scrollingY, hovering, rootId, hiddenState, overflowEdges, state, xStart, xEnd, yStart, yEnd]);
|
|
3897
3578
|
return /*#__PURE__*/jsxs(ScrollAreaRootContext.Provider, {
|
|
3898
3579
|
value: contextValue,
|
|
3899
3580
|
children: [!disableStyleElements && styleDisableScrollbar.getElement(nonce), element]
|
|
@@ -3901,20 +3582,17 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3901
3582
|
});
|
|
3902
3583
|
if (process.env.NODE_ENV !== "production") ScrollAreaRoot.displayName = "ScrollAreaRoot";
|
|
3903
3584
|
function normalizeOverflowEdgeThreshold(threshold) {
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
yEnd: value
|
|
3911
|
-
};
|
|
3912
|
-
}
|
|
3585
|
+
const thresholds = typeof threshold === 'number' ? {
|
|
3586
|
+
xStart: threshold,
|
|
3587
|
+
xEnd: threshold,
|
|
3588
|
+
yStart: threshold,
|
|
3589
|
+
yEnd: threshold
|
|
3590
|
+
} : threshold;
|
|
3913
3591
|
return {
|
|
3914
|
-
xStart: Math.max(0,
|
|
3915
|
-
xEnd: Math.max(0,
|
|
3916
|
-
yStart: Math.max(0,
|
|
3917
|
-
yEnd: Math.max(0,
|
|
3592
|
+
xStart: Math.max(0, thresholds?.xStart || 0),
|
|
3593
|
+
xEnd: Math.max(0, thresholds?.xEnd || 0),
|
|
3594
|
+
yStart: Math.max(0, thresholds?.yStart || 0),
|
|
3595
|
+
yEnd: Math.max(0, thresholds?.yEnd || 0)
|
|
3918
3596
|
};
|
|
3919
3597
|
}
|
|
3920
3598
|
|
|
@@ -3928,30 +3606,10 @@ function useScrollAreaViewportContext() {
|
|
|
3928
3606
|
return context;
|
|
3929
3607
|
}
|
|
3930
3608
|
|
|
3931
|
-
|
|
3932
|
-
/**
|
|
3933
|
-
* The distance from the horizontal start edge in pixels.
|
|
3934
|
-
* @type {number}
|
|
3935
|
-
*/
|
|
3936
|
-
ScrollAreaViewportCssVars["scrollAreaOverflowXStart"] = "--scroll-area-overflow-x-start";
|
|
3937
|
-
/**
|
|
3938
|
-
* The distance from the horizontal end edge in pixels.
|
|
3939
|
-
* @type {number}
|
|
3940
|
-
*/
|
|
3941
|
-
ScrollAreaViewportCssVars["scrollAreaOverflowXEnd"] = "--scroll-area-overflow-x-end";
|
|
3942
|
-
/**
|
|
3943
|
-
* The distance from the vertical start edge in pixels.
|
|
3944
|
-
* @type {number}
|
|
3945
|
-
*/
|
|
3946
|
-
ScrollAreaViewportCssVars["scrollAreaOverflowYStart"] = "--scroll-area-overflow-y-start";
|
|
3947
|
-
/**
|
|
3948
|
-
* The distance from the vertical end edge in pixels.
|
|
3949
|
-
* @type {number}
|
|
3950
|
-
*/
|
|
3951
|
-
ScrollAreaViewportCssVars["scrollAreaOverflowYEnd"] = "--scroll-area-overflow-y-end";
|
|
3952
|
-
return ScrollAreaViewportCssVars;
|
|
3953
|
-
}({});
|
|
3609
|
+
const OVERFLOW_EDGE_VARS = ['--scroll-area-overflow-x-start', '--scroll-area-overflow-x-end', '--scroll-area-overflow-y-start', '--scroll-area-overflow-y-end'];
|
|
3954
3610
|
|
|
3611
|
+
// Module-level flag to ensure we only register the CSS properties once,
|
|
3612
|
+
// regardless of how many Scroll Area components are mounted.
|
|
3955
3613
|
let scrollAreaOverflowVarsRegistered = false;
|
|
3956
3614
|
|
|
3957
3615
|
/**
|
|
@@ -3970,7 +3628,7 @@ function removeCSSVariableInheritance() {
|
|
|
3970
3628
|
return;
|
|
3971
3629
|
}
|
|
3972
3630
|
if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {
|
|
3973
|
-
|
|
3631
|
+
OVERFLOW_EDGE_VARS.forEach(name => {
|
|
3974
3632
|
try {
|
|
3975
3633
|
CSS.registerProperty({
|
|
3976
3634
|
name,
|
|
@@ -4014,12 +3672,11 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
4014
3672
|
hiddenState,
|
|
4015
3673
|
setHasMeasuredScrollbar,
|
|
4016
3674
|
handleScroll,
|
|
3675
|
+
touchModality,
|
|
4017
3676
|
setHovering,
|
|
4018
3677
|
setOverflowEdges,
|
|
4019
|
-
overflowEdges,
|
|
4020
3678
|
overflowEdgeThreshold,
|
|
4021
|
-
|
|
4022
|
-
scrollingY
|
|
3679
|
+
viewportState
|
|
4023
3680
|
} = useScrollAreaRootContext();
|
|
4024
3681
|
const direction = useDirection();
|
|
4025
3682
|
const programmaticScrollRef = React.useRef(true);
|
|
@@ -4064,18 +3721,12 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
4064
3721
|
let scrollLeftFromStart = 0;
|
|
4065
3722
|
let scrollLeftFromEnd = 0;
|
|
4066
3723
|
if (!scrollbarXHidden) {
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
rawScrollLeftFromStart = clamp(-scrollLeft, 0, maxScrollLeft);
|
|
4070
|
-
} else {
|
|
4071
|
-
rawScrollLeftFromStart = clamp(scrollLeft, 0, maxScrollLeft);
|
|
4072
|
-
}
|
|
4073
|
-
scrollLeftFromStart = normalizeScrollOffset(rawScrollLeftFromStart, maxScrollLeft);
|
|
3724
|
+
// `normalizeScrollOffset` clamps internally.
|
|
3725
|
+
scrollLeftFromStart = normalizeScrollOffset(direction === 'rtl' ? -scrollLeft : scrollLeft, maxScrollLeft);
|
|
4074
3726
|
scrollLeftFromEnd = maxScrollLeft - scrollLeftFromStart;
|
|
4075
3727
|
}
|
|
4076
|
-
const
|
|
4077
|
-
const
|
|
4078
|
-
const scrollTopFromEnd = !scrollbarYHidden ? maxScrollTop - scrollTopFromStart : 0;
|
|
3728
|
+
const scrollTopFromStart = scrollbarYHidden ? 0 : normalizeScrollOffset(scrollTop, maxScrollTop);
|
|
3729
|
+
const scrollTopFromEnd = scrollbarYHidden ? 0 : maxScrollTop - scrollTopFromStart;
|
|
4079
3730
|
const nextWidth = scrollbarXHidden ? 0 : viewportWidth;
|
|
4080
3731
|
const nextHeight = scrollbarYHidden ? 0 : viewportHeight;
|
|
4081
3732
|
let nextCornerWidth = 0;
|
|
@@ -4100,76 +3751,53 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
4100
3751
|
const maxNextHeight = scrollbarYEl ? Math.min(scrollbarYEl.offsetHeight - cornerHeightOffset, idealNextHeight) : idealNextHeight;
|
|
4101
3752
|
const clampedNextWidth = Math.max(MIN_THUMB_SIZE, maxNextWidth * ratioX);
|
|
4102
3753
|
const clampedNextHeight = Math.max(MIN_THUMB_SIZE, maxNextHeight * ratioY);
|
|
4103
|
-
setThumbSize(prevSize => {
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
return {
|
|
4108
|
-
width: clampedNextWidth,
|
|
4109
|
-
height: clampedNextHeight
|
|
4110
|
-
};
|
|
4111
|
-
});
|
|
3754
|
+
setThumbSize(prevSize => pickState(prevSize, {
|
|
3755
|
+
width: clampedNextWidth,
|
|
3756
|
+
height: clampedNextHeight
|
|
3757
|
+
}));
|
|
4112
3758
|
|
|
4113
3759
|
// Handle Y (vertical) scroll
|
|
4114
3760
|
if (scrollbarYEl && thumbYEl) {
|
|
4115
3761
|
const maxThumbOffsetY = scrollbarYEl.offsetHeight - clampedNextHeight - scrollbarYOffset - thumbYOffset;
|
|
4116
|
-
const
|
|
4117
|
-
const scrollRatioY = scrollRangeY === 0 ? 0 : scrollTop / scrollRangeY;
|
|
4118
|
-
|
|
4119
|
-
// In Safari, don't allow it to go negative or too far as `scrollTop` considers the rubber
|
|
4120
|
-
// band effect.
|
|
4121
|
-
const thumbOffsetY = Math.min(maxThumbOffsetY, Math.max(0, scrollRatioY * maxThumbOffsetY));
|
|
3762
|
+
const thumbOffsetY = applyOverscrollThumb(thumbYEl, '--scroll-area-thumb-height', scrollTop, maxScrollTop, scrollableContentHeight, clampedNextHeight, maxThumbOffsetY);
|
|
4122
3763
|
thumbYEl.style.transform = `translate3d(0,${thumbOffsetY}px,0)`;
|
|
4123
3764
|
}
|
|
4124
3765
|
|
|
4125
3766
|
// Handle X (horizontal) scroll
|
|
4126
3767
|
if (scrollbarXEl && thumbXEl) {
|
|
4127
3768
|
const maxThumbOffsetX = scrollbarXEl.offsetWidth - clampedNextWidth - scrollbarXOffset - thumbXOffset;
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
viewportEl.style.setProperty(cssVar, `${value}px`);
|
|
4139
|
-
}
|
|
3769
|
+
// RTL scrolls from 0 down to `-maxScrollLeft`; measure from the inline start edge so the
|
|
3770
|
+
// overscroll math is direction-agnostic, then flip the resulting offset back below.
|
|
3771
|
+
const scrollFromStart = direction === 'rtl' ? -scrollLeft : scrollLeft;
|
|
3772
|
+
const offsetX = applyOverscrollThumb(thumbXEl, '--scroll-area-thumb-width', scrollFromStart, maxScrollLeft, scrollableContentWidth, clampedNextWidth, maxThumbOffsetX);
|
|
3773
|
+
thumbXEl.style.transform = `translate3d(${direction === 'rtl' ? -offsetX : offsetX}px,0,0)`;
|
|
3774
|
+
}
|
|
3775
|
+
const overflowMetricsPx = [scrollLeftFromStart, scrollLeftFromEnd, scrollTopFromStart, scrollTopFromEnd];
|
|
3776
|
+
OVERFLOW_EDGE_VARS.forEach((cssVar, index) => {
|
|
3777
|
+
viewportEl.style.setProperty(cssVar, `${overflowMetricsPx[index]}px`);
|
|
3778
|
+
});
|
|
4140
3779
|
if (cornerEl) {
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
}
|
|
4152
|
-
}
|
|
4153
|
-
setHiddenState(prevState => mergeHiddenState(prevState, nextHiddenState));
|
|
3780
|
+
// Bail when the size is unchanged (like `setThumbSize` above); otherwise a
|
|
3781
|
+
// fresh object literal on every scroll frame rebuilds the root context and
|
|
3782
|
+
// re-renders every scroll-area part.
|
|
3783
|
+
// `nextCornerWidth`/`nextCornerHeight` stay 0 when either scrollbar is hidden.
|
|
3784
|
+
setCornerSize(prevSize => pickState(prevSize, {
|
|
3785
|
+
width: nextCornerWidth,
|
|
3786
|
+
height: nextCornerHeight
|
|
3787
|
+
}));
|
|
3788
|
+
}
|
|
3789
|
+
setHiddenState(prevState => pickState(prevState, nextHiddenState));
|
|
4154
3790
|
const nextOverflowEdges = {
|
|
4155
3791
|
xStart: !scrollbarXHidden && scrollLeftFromStart > overflowEdgeThreshold.xStart,
|
|
4156
3792
|
xEnd: !scrollbarXHidden && scrollLeftFromEnd > overflowEdgeThreshold.xEnd,
|
|
4157
3793
|
yStart: !scrollbarYHidden && scrollTopFromStart > overflowEdgeThreshold.yStart,
|
|
4158
3794
|
yEnd: !scrollbarYHidden && scrollTopFromEnd > overflowEdgeThreshold.yEnd
|
|
4159
3795
|
};
|
|
4160
|
-
setOverflowEdges(prev =>
|
|
4161
|
-
if (prev.xStart === nextOverflowEdges.xStart && prev.xEnd === nextOverflowEdges.xEnd && prev.yStart === nextOverflowEdges.yStart && prev.yEnd === nextOverflowEdges.yEnd) {
|
|
4162
|
-
return prev;
|
|
4163
|
-
}
|
|
4164
|
-
return nextOverflowEdges;
|
|
4165
|
-
});
|
|
3796
|
+
setOverflowEdges(prev => pickState(prev, nextOverflowEdges));
|
|
4166
3797
|
});
|
|
4167
3798
|
useIsoLayoutEffect(() => {
|
|
4168
|
-
if (!viewportRef.current) {
|
|
4169
|
-
return;
|
|
4170
|
-
}
|
|
4171
3799
|
removeCSSVariableInheritance();
|
|
4172
|
-
}, [
|
|
3800
|
+
}, []);
|
|
4173
3801
|
useIsoLayoutEffect(() => {
|
|
4174
3802
|
// Wait for scrollbar and thumb refs after hidden-state toggles, refresh math on direction
|
|
4175
3803
|
// flips, and re-evaluate overflow edges when the threshold changes.
|
|
@@ -4212,6 +3840,10 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
4212
3840
|
if (animations.length === 0) {
|
|
4213
3841
|
return;
|
|
4214
3842
|
}
|
|
3843
|
+
|
|
3844
|
+
// `allSettled` never rejects, but `computeThumbPosition` can still run against a
|
|
3845
|
+
// torn-down tree once the animations resolve. Swallow instead of leaking an unhandled
|
|
3846
|
+
// rejection; `void` alone would only silence the floating-promise lint.
|
|
4215
3847
|
Promise.allSettled(animations.map(animation => animation.finished)).then(computeThumbPosition).catch(() => {});
|
|
4216
3848
|
});
|
|
4217
3849
|
return () => {
|
|
@@ -4239,7 +3871,13 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
4239
3871
|
return;
|
|
4240
3872
|
}
|
|
4241
3873
|
computeThumbPosition();
|
|
4242
|
-
|
|
3874
|
+
|
|
3875
|
+
// WebKit consumes a touch that catches an in-flight momentum scroll or
|
|
3876
|
+
// rubber-band bounce without dispatching any DOM events for the whole
|
|
3877
|
+
// gesture (not even `touchstart`), so scrolls cannot be attributed to
|
|
3878
|
+
// the user through events. Treat every scroll in touch modality as
|
|
3879
|
+
// user-driven instead.
|
|
3880
|
+
if (touchModality || !programmaticScrollRef.current) {
|
|
4243
3881
|
handleScroll({
|
|
4244
3882
|
x: viewportRef.current.scrollLeft,
|
|
4245
3883
|
y: viewportRef.current.scrollTop
|
|
@@ -4257,21 +3895,10 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
4257
3895
|
});
|
|
4258
3896
|
},
|
|
4259
3897
|
onWheel: handleUserInteraction,
|
|
4260
|
-
onTouchMove: handleUserInteraction,
|
|
4261
3898
|
onPointerMove: handleUserInteraction,
|
|
4262
3899
|
onPointerEnter: handleUserInteraction,
|
|
4263
3900
|
onKeyDown: handleUserInteraction
|
|
4264
3901
|
};
|
|
4265
|
-
const viewportState = React.useMemo(() => ({
|
|
4266
|
-
scrolling: scrollingX || scrollingY,
|
|
4267
|
-
hasOverflowX: !hiddenState.x,
|
|
4268
|
-
hasOverflowY: !hiddenState.y,
|
|
4269
|
-
overflowXStart: overflowEdges.xStart,
|
|
4270
|
-
overflowXEnd: overflowEdges.xEnd,
|
|
4271
|
-
overflowYStart: overflowEdges.yStart,
|
|
4272
|
-
overflowYEnd: overflowEdges.yEnd,
|
|
4273
|
-
cornerHidden: hiddenState.corner
|
|
4274
|
-
}), [scrollingX, scrollingY, hiddenState.x, hiddenState.y, hiddenState.corner, overflowEdges]);
|
|
4275
3902
|
const element = useRenderElement('div', componentProps, {
|
|
4276
3903
|
ref: [forwardedRef, viewportRef],
|
|
4277
3904
|
state: viewportState,
|
|
@@ -4296,11 +3923,37 @@ function getHiddenState(viewport) {
|
|
|
4296
3923
|
corner: y || x
|
|
4297
3924
|
};
|
|
4298
3925
|
}
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
3926
|
+
|
|
3927
|
+
/**
|
|
3928
|
+
* Returns `prev` when `next` is shallow-equal to it so setState bails out and
|
|
3929
|
+
* scroll-frame updates don't rebuild the root context.
|
|
3930
|
+
*/
|
|
3931
|
+
function pickState(prev, next) {
|
|
3932
|
+
for (const key in next) {
|
|
3933
|
+
if (prev[key] !== next[key]) {
|
|
3934
|
+
return next;
|
|
3935
|
+
}
|
|
4302
3936
|
}
|
|
4303
|
-
return
|
|
3937
|
+
return prev;
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
/**
|
|
3941
|
+
* Sizes the thumb and returns its axis offset. On overscroll (Safari rubber-band only) it shrinks
|
|
3942
|
+
* against the pinned edge, damped by `content / (content + overscroll)` to match native feedback;
|
|
3943
|
+
* the size flows through the thumb-size variable so the resting `var(...)` still applies.
|
|
3944
|
+
*/
|
|
3945
|
+
function applyOverscrollThumb(thumbEl, sizeVar, scrollFromStart, maxScroll, content, size, maxThumbOffset) {
|
|
3946
|
+
const clamped = clamp(scrollFromStart, 0, maxScroll);
|
|
3947
|
+
const overscroll = scrollFromStart - clamped;
|
|
3948
|
+
const nextSize = Math.max(MIN_THUMB_SIZE, size * content / (content + Math.abs(overscroll)));
|
|
3949
|
+
|
|
3950
|
+
// Passing an empty string removes the override, restoring the resting `var(...)` size.
|
|
3951
|
+
thumbEl.style.setProperty(sizeVar, overscroll ? `${nextSize}px` : '');
|
|
3952
|
+
|
|
3953
|
+
// Slide proportionally; at the end edge push down by the shrink so the thumb stays pinned to
|
|
3954
|
+
// it, while a start overscroll pins to offset 0.
|
|
3955
|
+
const offset = maxScroll ? clamped / maxScroll * maxThumbOffset : 0;
|
|
3956
|
+
return offset + (overscroll > 0 ? size - nextSize : 0);
|
|
4304
3957
|
}
|
|
4305
3958
|
|
|
4306
|
-
export {
|
|
3959
|
+
export { getElementTransform as A, DrawerVirtualKeyboardContext as B, getElementAtPoint as C, DrawerTrigger as D, useScrollAreaRootContext as E, getOffset as F, scrollAreaStateAttributesMapping as G, useScrollAreaViewportContext as H, NullStore as N, ScrollAreaRoot as S, DrawerRoot as a, DrawerPortal as b, DrawerBackdrop as c, DrawerViewport as d, DrawerPopup as e, ScrollAreaViewport as f, DrawerContent as g, DialogTrigger as h, DialogPortal as i, DialogViewport as j, useDialogPortalContext as k, dialogStateAttributesMapping as l, useRenderDialogRoot as m, createNullDialogStore as n, createInitialPopupStoreState as o, popupStoreSelectors as p, usePopupHandleStore as q, useDrawerProviderContext as r, DrawerBackdropCssVars as s, DrawerPopupCssVars as t, useDialogRootContext as u, DrawerProviderContext as v, useDrawerRootContext as w, useSwipeDismiss as x, getDisplacement as y, DrawerPopupDataAttributes as z };
|