@jobber/components 9.6.1 → 9.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Autocomplete/index.cjs +2 -0
- package/dist/Autocomplete/index.mjs +2 -0
- package/dist/Autocomplete-cjs.js +5 -4
- package/dist/Autocomplete-es.js +2 -1
- package/dist/Card/index.cjs +2 -1
- package/dist/Card/index.mjs +2 -1
- package/dist/Chip/index.cjs +2 -0
- package/dist/Chip/index.mjs +2 -0
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +2 -0
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +2 -0
- package/dist/Chips/InternalChipDismissible/index.cjs +2 -0
- package/dist/Chips/InternalChipDismissible/index.mjs +2 -0
- package/dist/Chips/index.cjs +2 -0
- package/dist/Chips/index.mjs +2 -0
- package/dist/Combobox/index.cjs +4 -2
- package/dist/Combobox/index.mjs +4 -2
- package/dist/{ComboboxSeparator-cjs.js → ComboboxChipRemove-cjs.js} +455 -501
- package/dist/{ComboboxSeparator-es.js → ComboboxChipRemove-es.js} +456 -500
- package/dist/ComboboxPrimitive-cjs.js +30 -29
- package/dist/ComboboxPrimitive-es.js +3 -2
- package/dist/ConfirmationModal/index.cjs +2 -0
- package/dist/ConfirmationModal/index.mjs +2 -0
- package/dist/DataDump/index.cjs +2 -1
- package/dist/DataDump/index.mjs +2 -1
- package/dist/DataList/components/DataListActions/index.cjs +2 -0
- package/dist/DataList/components/DataListActions/index.mjs +2 -0
- package/dist/DataList/components/DataListBulkActions/index.cjs +2 -0
- package/dist/DataList/components/DataListBulkActions/index.mjs +2 -0
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +2 -0
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +2 -0
- package/dist/DataList/components/DataListFilters/index.cjs +2 -0
- package/dist/DataList/components/DataListFilters/index.mjs +2 -0
- package/dist/DataList/components/DataListHeader/index.cjs +2 -0
- package/dist/DataList/components/DataListHeader/index.mjs +2 -0
- package/dist/DataList/components/DataListItem/index.cjs +2 -0
- package/dist/DataList/components/DataListItem/index.mjs +2 -0
- package/dist/DataList/components/DataListItemActions/index.cjs +2 -0
- package/dist/DataList/components/DataListItemActions/index.mjs +2 -0
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +2 -0
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +2 -0
- package/dist/DataList/components/DataListItems/index.cjs +2 -0
- package/dist/DataList/components/DataListItems/index.mjs +2 -0
- package/dist/DataList/components/DataListLayout/index.cjs +2 -0
- package/dist/DataList/components/DataListLayout/index.mjs +2 -0
- package/dist/DataList/components/DataListLayoutActions/index.cjs +2 -0
- package/dist/DataList/components/DataListLayoutActions/index.mjs +2 -0
- package/dist/DataList/index.cjs +2 -0
- package/dist/DataList/index.mjs +2 -0
- package/dist/DatePicker/index.cjs +2 -0
- package/dist/DatePicker/index.mjs +2 -0
- package/dist/DatePicker-cjs.js +5 -4
- package/dist/DatePicker-es.js +2 -1
- package/dist/Dialog/index.cjs +2 -1
- package/dist/Dialog/index.mjs +2 -1
- package/dist/DrawerDescription-cjs.js +99 -268
- package/dist/DrawerDescription-es.js +103 -270
- package/dist/DrawerTitle-cjs.js +6 -2
- package/dist/DrawerTitle-es.js +7 -3
- package/dist/FieldDescription-cjs.js +181 -184
- package/dist/FieldDescription-es.js +184 -187
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +2 -0
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +2 -0
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +2 -0
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +2 -0
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +2 -0
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +2 -0
- package/dist/FilterPicker/index.cjs +2 -0
- package/dist/FilterPicker/index.mjs +2 -0
- package/dist/FilterPickerContent-cjs.js +5 -4
- package/dist/FilterPickerContent-es.js +2 -1
- package/dist/FormatFile/index.cjs +2 -0
- package/dist/FormatFile/index.mjs +2 -0
- package/dist/Gallery/index.cjs +2 -0
- package/dist/Gallery/index.mjs +2 -0
- package/dist/InputDate/index.cjs +2 -0
- package/dist/InputDate/index.mjs +2 -0
- package/dist/InputNumber/index.cjs +2 -1
- package/dist/InputNumber/index.mjs +2 -1
- package/dist/LightBox/index.cjs +2 -0
- package/dist/LightBox/index.mjs +2 -0
- package/dist/Menu/index.cjs +2 -1
- package/dist/Menu/index.mjs +2 -1
- package/dist/Menu-cjs.js +5 -4
- package/dist/Menu-es.js +2 -1
- package/dist/MenuSubmenuTrigger-cjs.js +164 -206
- package/dist/MenuSubmenuTrigger-es.js +152 -194
- package/dist/Modal/index.cjs +2 -0
- package/dist/Modal/index.mjs +3 -1
- package/dist/NumberFieldInput-cjs.js +308 -187
- package/dist/NumberFieldInput-es.js +308 -187
- package/dist/Page/index.cjs +2 -1
- package/dist/Page/index.mjs +2 -1
- package/dist/Popover/index.cjs +2 -0
- package/dist/Popover/index.mjs +2 -0
- package/dist/Popover-cjs.js +8 -7
- package/dist/Popover-es.js +2 -1
- package/dist/ScrollAreaViewport-cjs.js +1093 -747
- package/dist/ScrollAreaViewport-es.js +1081 -734
- package/dist/Select/index.cjs +2 -1
- package/dist/Select/index.mjs +2 -1
- package/dist/SelectGroupLabel-cjs.js +306 -207
- package/dist/SelectGroupLabel-es.js +302 -203
- package/dist/Tooltip/index.cjs +2 -0
- package/dist/Tooltip/index.mjs +2 -0
- package/dist/Tooltip-cjs.js +7 -6
- package/dist/Tooltip-es.js +2 -1
- package/dist/dialogReturnFocus-cjs.js +1 -1
- package/dist/dialogReturnFocus-es.js +2 -2
- package/dist/floating-ui.react-cjs.js +192 -2564
- package/dist/floating-ui.react-dom-cjs.js +2103 -0
- package/dist/floating-ui.react-dom-es.js +2072 -0
- package/dist/floating-ui.react-es.js +156 -2519
- package/dist/floating-ui.utils-cjs.js +342 -0
- package/dist/floating-ui.utils-es.js +300 -0
- package/dist/index.cjs +4 -3
- package/dist/index.mjs +4 -3
- package/dist/primitives/BottomSheet/index.cjs +1 -1
- package/dist/primitives/BottomSheet/index.mjs +1 -1
- package/dist/primitives/ComboboxPrimitive/index.cjs +4 -2
- package/dist/primitives/ComboboxPrimitive/index.mjs +4 -2
- package/dist/primitives/SelectPrimitive/index.cjs +2 -0
- package/dist/primitives/SelectPrimitive/index.mjs +2 -0
- package/dist/primitives/index.cjs +3 -2
- package/dist/primitives/index.mjs +3 -2
- package/dist/scrollEdges-cjs.js +11 -1
- package/dist/scrollEdges-es.js +11 -1
- package/dist/unstyledPrimitives/index.cjs +478 -720
- package/dist/unstyledPrimitives/index.mjs +421 -663
- package/dist/useCompositeListItem-cjs.js +361 -2108
- package/dist/useCompositeListItem-es.js +331 -2076
- package/dist/useLabel-cjs.js +3 -2
- package/dist/useLabel-es.js +3 -2
- package/dist/useRenderElement-cjs.js +7 -9
- package/dist/useRenderElement-es.js +7 -9
- package/dist/useScrollLock-cjs.js +344 -819
- package/dist/useScrollLock-es.js +296 -732
- package/dist/useScrollToActive-cjs.js +5 -4
- package/dist/useScrollToActive-es.js +2 -1
- package/package.json +3 -3
- package/dist/scrollable-cjs.js +0 -58
- package/dist/scrollable-es.js +0 -54
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { E as EMPTY_OBJECT,
|
|
3
|
-
import {
|
|
2
|
+
import { E as EMPTY_OBJECT, f as formatErrorMessage, u as useRenderElement, m as mergeProps } from './useRenderElement-es.js';
|
|
3
|
+
import { ac as FloatingRootStore, ad as PopupTriggerMap, N as createSelector, a0 as FloatingPortal, a1 as InternalBackdrop, a2 as inertValue, b9 as usePopupRootSync, aS as useImplicitActiveTrigger, aT as useOpenStateTransitions, d as createChangeEventDetails, j as imperativeAction, R as useDismiss, g as getTarget, _ as contains, av as useScrollLock, aY as usePopupInteractionProps, aN as ReactStore, aV as setPopupOpenState, ba as usePopupStore, U as useOnFirstRender, C as CommonPopupDataAttributes, p as popupStateMapping, t as transitionStatusMapping, u as useBaseUiId, b4 as useTriggerDataForwarding, c as useButton, Q as useClick, bb as useOpenMethodTriggerProps, bc as CLICK_TRIGGER_IDENTIFIER, b8 as triggerOpenStateMapping, b as useStableCallback, o as ownerDocument, a as useIsoLayoutEffect, S as SafeReact, l as error, f as useOpenChangeComplete, F as FOCUSABLE_POPUP_PROPS, i as FloatingFocusManager, n as useControlled, v as none, aa as useId, bd as android, s as addEventListener, be as closeWatcher, a5 as useAnimationFrame, bf as BASE_UI_SWIPE_IGNORE_SELECTOR, D as activeElement, bg as TransitionStatusDataAttributes, bh as swipe, k as useTimeout, a4 as useDirection, a6 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';
|
|
6
7
|
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 = state => state.triggerIdProp ?? state.activeTriggerId;
|
|
62
|
-
const openSelector = state => state.openProp ?? state.open;
|
|
63
|
-
const popupIdSelector = state => {
|
|
61
|
+
const activeTriggerIdSelector = createSelector(state => state.triggerIdProp ?? state.activeTriggerId);
|
|
62
|
+
const openSelector = createSelector(state => state.openProp ?? state.open);
|
|
63
|
+
const popupIdSelector = createSelector(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,81 +75,47 @@ function triggerOwnsOpenPopupOrIsOnlyTrigger(state, triggerId) {
|
|
|
75
75
|
}
|
|
76
76
|
const popupStoreSelectors = {
|
|
77
77
|
open: openSelector,
|
|
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,
|
|
78
|
+
mounted: createSelector(state => state.mounted),
|
|
79
|
+
transitionStatus: createSelector(state => state.transitionStatus),
|
|
80
|
+
floatingRootContext: createSelector(state => state.floatingRootContext),
|
|
81
|
+
triggerCount: createSelector(state => state.triggerCount),
|
|
82
|
+
preventUnmountingOnClose: createSelector(state => state.preventUnmountingOnClose),
|
|
83
|
+
payload: createSelector(state => state.payload),
|
|
84
84
|
activeTriggerId: activeTriggerIdSelector,
|
|
85
|
-
activeTriggerElement: state => state.mounted ? state.activeTriggerElement : null,
|
|
85
|
+
activeTriggerElement: createSelector(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: (state, triggerId) => triggerId !== undefined && activeTriggerIdSelector(state) === triggerId,
|
|
90
|
+
isTriggerActive: createSelector((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: (state, triggerId) => triggerOwnsOpenPopup(state, triggerId),
|
|
94
|
+
isOpenedByTrigger: createSelector((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: (state, triggerId) => triggerId !== undefined && activeTriggerIdSelector(state) === triggerId && state.mounted,
|
|
99
|
-
triggerProps: (state, isActive) => isActive ? state.activeTriggerProps : state.inactiveTriggerProps,
|
|
98
|
+
isMountedByTrigger: createSelector((state, triggerId) => triggerId !== undefined && activeTriggerIdSelector(state) === triggerId && state.mounted),
|
|
99
|
+
triggerProps: createSelector((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: (state, triggerId) => triggerOwnsOpenPopupOrIsOnlyTrigger(state, triggerId) ? popupIdSelector(state) : undefined,
|
|
104
|
-
popupProps: state => state.popupProps,
|
|
105
|
-
popupElement: state => state.popupElement,
|
|
106
|
-
positionerElement: state => state.positionerElement
|
|
103
|
+
triggerPopupId: createSelector((state, triggerId) => triggerOwnsOpenPopupOrIsOnlyTrigger(state, triggerId) ? popupIdSelector(state) : undefined),
|
|
104
|
+
popupProps: createSelector(state => state.popupProps),
|
|
105
|
+
popupElement: createSelector(state => state.popupElement),
|
|
106
|
+
positionerElement: createSelector(state => state.positionerElement)
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
109
|
+
const IsDrawerContext = /*#__PURE__*/React.createContext(false);
|
|
110
|
+
if (process.env.NODE_ENV !== "production") IsDrawerContext.displayName = "IsDrawerContext";
|
|
145
111
|
const DialogRootContext = /*#__PURE__*/React.createContext(undefined);
|
|
146
112
|
if (process.env.NODE_ENV !== "production") DialogRootContext.displayName = "DialogRootContext";
|
|
147
113
|
function useDialogRootContext(optional) {
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
114
|
+
const dialogRootContext = React.useContext(DialogRootContext);
|
|
115
|
+
if (optional === false && dialogRootContext === undefined) {
|
|
150
116
|
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DialogRootContext is missing. Dialog parts must be placed within <Dialog.Root>.' : formatErrorMessage(27));
|
|
151
117
|
}
|
|
152
|
-
return
|
|
118
|
+
return dialogRootContext;
|
|
153
119
|
}
|
|
154
120
|
|
|
155
121
|
const DialogPortalContext = /*#__PURE__*/React.createContext(undefined);
|
|
@@ -162,26 +128,14 @@ function useDialogPortalContext() {
|
|
|
162
128
|
return value;
|
|
163
129
|
}
|
|
164
130
|
|
|
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
|
-
|
|
179
131
|
const DialogPortal = /*#__PURE__*/React.forwardRef(function DialogPortal(props, forwardedRef) {
|
|
180
132
|
const {
|
|
181
133
|
keepMounted = false,
|
|
182
134
|
...portalProps
|
|
183
135
|
} = props;
|
|
184
|
-
const
|
|
136
|
+
const {
|
|
137
|
+
store
|
|
138
|
+
} = useDialogRootContext();
|
|
185
139
|
const mounted = store.useState('mounted');
|
|
186
140
|
const modal = store.useState('modal');
|
|
187
141
|
const open = store.useState('open');
|
|
@@ -203,6 +157,25 @@ const DialogPortal = /*#__PURE__*/React.forwardRef(function DialogPortal(props,
|
|
|
203
157
|
});
|
|
204
158
|
if (process.env.NODE_ENV !== "production") DialogPortal.displayName = "DialogPortal";
|
|
205
159
|
|
|
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
|
+
}
|
|
206
179
|
function DialogInteractions({
|
|
207
180
|
store,
|
|
208
181
|
parentContext,
|
|
@@ -238,18 +211,8 @@ function DialogInteractions({
|
|
|
238
211
|
if ('button' in event && event.button !== 0) {
|
|
239
212
|
return false;
|
|
240
213
|
}
|
|
241
|
-
if ('touches' in event) {
|
|
242
|
-
|
|
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
|
-
}
|
|
214
|
+
if ('touches' in event && event.touches.length !== 1) {
|
|
215
|
+
return false;
|
|
253
216
|
}
|
|
254
217
|
const target = getTarget(event);
|
|
255
218
|
if (isTopmost && !disablePointerDismissal) {
|
|
@@ -257,9 +220,7 @@ function DialogInteractions({
|
|
|
257
220
|
// This supports multiple modal dialogs that aren't nested in the React tree:
|
|
258
221
|
// https://github.com/mui/base-ui/issues/1320
|
|
259
222
|
if (modal) {
|
|
260
|
-
|
|
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;
|
|
223
|
+
return store.context.internalBackdropRef.current || store.context.backdropRef.current ? store.context.internalBackdropRef.current === target || store.context.backdropRef.current === target || contains(target, popupElement) && !target?.hasAttribute('data-base-ui-portal') : true;
|
|
263
224
|
}
|
|
264
225
|
return true;
|
|
265
226
|
}
|
|
@@ -270,82 +231,73 @@ function DialogInteractions({
|
|
|
270
231
|
useScrollLock(open && modal === true, popupElement);
|
|
271
232
|
|
|
272
233
|
// Listen for nested open/close events on this store to maintain the counts.
|
|
273
|
-
// A close notification is an open notification with zeroed counts.
|
|
274
234
|
store.useContextCallback('onNestedDialogOpen', (dialogCount, drawerCount) => {
|
|
275
235
|
setOwnNestedOpenDialogs(dialogCount);
|
|
276
236
|
setOwnNestedOpenDrawers(drawerCount);
|
|
277
237
|
});
|
|
238
|
+
store.useContextCallback('onNestedDialogClose', () => {
|
|
239
|
+
setOwnNestedOpenDialogs(0);
|
|
240
|
+
setOwnNestedOpenDrawers(0);
|
|
241
|
+
});
|
|
278
242
|
|
|
279
243
|
// Notify parent of our open/close state using parent callbacks, if any
|
|
280
|
-
|
|
281
|
-
if (parentContext?.onNestedDialogOpen) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
244
|
+
React.useEffect(() => {
|
|
245
|
+
if (parentContext?.onNestedDialogOpen && open) {
|
|
246
|
+
parentContext.onNestedDialogOpen(ownNestedOpenDialogs + 1, ownNestedOpenDrawers + (isDrawer ? 1 : 0));
|
|
247
|
+
}
|
|
248
|
+
if (parentContext?.onNestedDialogClose && !open) {
|
|
249
|
+
parentContext.onNestedDialogClose();
|
|
287
250
|
}
|
|
288
251
|
return () => {
|
|
289
|
-
if (parentContext?.
|
|
290
|
-
parentContext.
|
|
252
|
+
if (parentContext?.onNestedDialogClose && open) {
|
|
253
|
+
parentContext.onNestedDialogClose();
|
|
291
254
|
}
|
|
292
255
|
};
|
|
293
256
|
}, [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;
|
|
294
262
|
usePopupInteractionProps(store, {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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,
|
|
263
|
+
activeTriggerProps,
|
|
264
|
+
inactiveTriggerProps,
|
|
265
|
+
popupProps,
|
|
302
266
|
nestedOpenDialogCount: ownNestedOpenDialogs,
|
|
303
267
|
nestedOpenDrawerCount: ownNestedOpenDrawers
|
|
304
268
|
});
|
|
305
269
|
return null;
|
|
306
270
|
}
|
|
307
271
|
|
|
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
|
-
|
|
324
272
|
const selectors = {
|
|
325
273
|
...popupStoreSelectors,
|
|
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
|
|
274
|
+
modal: createSelector(state => state.modal),
|
|
275
|
+
nested: createSelector(state => state.nested),
|
|
276
|
+
nestedOpenDialogCount: createSelector(state => state.nestedOpenDialogCount),
|
|
277
|
+
nestedOpenDrawerCount: createSelector(state => state.nestedOpenDrawerCount),
|
|
278
|
+
disablePointerDismissal: createSelector(state => state.disablePointerDismissal),
|
|
279
|
+
openMethod: createSelector(state => state.openMethod),
|
|
280
|
+
descriptionElementId: createSelector(state => state.descriptionElementId),
|
|
281
|
+
titleElementId: createSelector(state => state.titleElementId),
|
|
282
|
+
viewportElement: createSelector(state => state.viewportElement),
|
|
283
|
+
role: createSelector(state => state.role)
|
|
336
284
|
};
|
|
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
|
-
|
|
344
285
|
class DialogStore extends ReactStore {
|
|
345
|
-
constructor(initialState, floatingId, nested) {
|
|
286
|
+
constructor(initialState, floatingId, nested = false) {
|
|
346
287
|
const triggerElements = new PopupTriggerMap();
|
|
347
|
-
const state = createInitialState(initialState
|
|
348
|
-
|
|
288
|
+
const state = createInitialState(initialState);
|
|
289
|
+
state.floatingRootContext = createPopupFloatingRootContext(triggerElements, floatingId, nested);
|
|
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);
|
|
349
301
|
}
|
|
350
302
|
setOpen = (nextOpen, eventDetails) => {
|
|
351
303
|
eventDetails.preventUnmountOnClose = () => {
|
|
@@ -367,22 +319,20 @@ class DialogStore extends ReactStore {
|
|
|
367
319
|
setPopupOpenState(updatedState, nextOpen, eventDetails.trigger);
|
|
368
320
|
this.update(updatedState);
|
|
369
321
|
};
|
|
370
|
-
|
|
322
|
+
static useStore(externalStore, initialState) {
|
|
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 */
|
|
371
326
|
|
|
372
|
-
|
|
373
|
-
|
|
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);
|
|
327
|
+
return store;
|
|
328
|
+
}
|
|
380
329
|
}
|
|
381
|
-
function createInitialState(initialState
|
|
382
|
-
|
|
330
|
+
function createInitialState(initialState = {}) {
|
|
331
|
+
return {
|
|
383
332
|
...createInitialPopupStoreState(),
|
|
384
333
|
modal: true,
|
|
385
334
|
disablePointerDismissal: false,
|
|
335
|
+
popupElement: null,
|
|
386
336
|
viewportElement: null,
|
|
387
337
|
descriptionElementId: undefined,
|
|
388
338
|
titleElementId: undefined,
|
|
@@ -393,24 +343,9 @@ function createInitialState(initialState, triggerElements, floatingId, nested =
|
|
|
393
343
|
role: 'dialog',
|
|
394
344
|
...initialState
|
|
395
345
|
};
|
|
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
|
-
};
|
|
411
346
|
}
|
|
412
347
|
|
|
413
|
-
function useRenderDialogRoot(
|
|
348
|
+
function useRenderDialogRoot(props, mode = 'dialog') {
|
|
414
349
|
const {
|
|
415
350
|
children,
|
|
416
351
|
open: openProp,
|
|
@@ -429,26 +364,38 @@ function useRenderDialogRoot(mode, props) {
|
|
|
429
364
|
const modal = isAlertDialog ? true : modalProp;
|
|
430
365
|
const disablePointerDismissal = isAlertDialog || disablePointerDismissalProp;
|
|
431
366
|
const role = isAlertDialog ? 'alertdialog' : 'dialog';
|
|
432
|
-
const
|
|
433
|
-
const nested =
|
|
367
|
+
const parentDialogRootContext = useDialogRootContext(true);
|
|
368
|
+
const nested = Boolean(parentDialogRootContext);
|
|
434
369
|
const rootState = {
|
|
435
370
|
modal,
|
|
436
371
|
disablePointerDismissal,
|
|
437
372
|
nested,
|
|
438
373
|
role
|
|
439
374
|
};
|
|
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({
|
|
375
|
+
const store = DialogStore.useStore(handle?.store, {
|
|
446
376
|
open: defaultOpen,
|
|
447
377
|
openProp,
|
|
448
378
|
activeTriggerId: defaultTriggerIdProp,
|
|
449
379
|
triggerIdProp,
|
|
450
380
|
...rootState
|
|
451
|
-
}
|
|
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
|
+
});
|
|
452
399
|
store.useControlledProp('openProp', openProp);
|
|
453
400
|
store.useControlledProp('triggerIdProp', triggerIdProp);
|
|
454
401
|
store.useSyncedValues(rootState);
|
|
@@ -457,31 +404,83 @@ function useRenderDialogRoot(mode, props) {
|
|
|
457
404
|
const open = store.useState('open');
|
|
458
405
|
const mounted = store.useState('mounted');
|
|
459
406
|
const payload = store.useState('payload');
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
} = useOpenStateTransitions(open, store);
|
|
465
|
-
React.useImperativeHandle(actionsRef, () => ({
|
|
466
|
-
unmount: forceUnmount,
|
|
467
|
-
close: () => store.setOpen(false, createChangeEventDetails(imperativeAction))
|
|
468
|
-
}), [forceUnmount, store]);
|
|
407
|
+
useDialogRoot({
|
|
408
|
+
store,
|
|
409
|
+
actionsRef
|
|
410
|
+
});
|
|
469
411
|
const shouldRenderInteractions = open || mounted;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
412
|
+
const contextValue = React.useMemo(() => ({
|
|
413
|
+
store
|
|
414
|
+
}), [store]);
|
|
415
|
+
return /*#__PURE__*/jsx(IsDrawerContext.Provider, {
|
|
416
|
+
value: false,
|
|
417
|
+
children: /*#__PURE__*/jsxs(DialogRootContext.Provider, {
|
|
418
|
+
value: contextValue,
|
|
419
|
+
children: [shouldRenderInteractions && /*#__PURE__*/jsx(DialogInteractions, {
|
|
420
|
+
store: store,
|
|
421
|
+
parentContext: parentDialogRootContext?.store.context,
|
|
422
|
+
isDrawer: isDrawer
|
|
423
|
+
}), typeof children === 'function' ? children({
|
|
424
|
+
payload
|
|
425
|
+
}) : children]
|
|
426
|
+
})
|
|
482
427
|
});
|
|
483
428
|
}
|
|
484
429
|
|
|
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
|
+
|
|
485
484
|
/**
|
|
486
485
|
* A positioning container for the dialog popup that can be made scrollable.
|
|
487
486
|
* Renders a `<div>` element.
|
|
@@ -497,7 +496,9 @@ const DialogViewport = /*#__PURE__*/React.forwardRef(function DialogViewport(com
|
|
|
497
496
|
...elementProps
|
|
498
497
|
} = componentProps;
|
|
499
498
|
const keepMounted = useDialogPortalContext();
|
|
500
|
-
const
|
|
499
|
+
const {
|
|
500
|
+
store
|
|
501
|
+
} = useDialogRootContext();
|
|
501
502
|
const open = store.useState('open');
|
|
502
503
|
const nested = store.useState('nested');
|
|
503
504
|
const transitionStatus = store.useState('transitionStatus');
|
|
@@ -516,7 +517,7 @@ const DialogViewport = /*#__PURE__*/React.forwardRef(function DialogViewport(com
|
|
|
516
517
|
enabled: shouldRender,
|
|
517
518
|
state,
|
|
518
519
|
ref: [forwardedRef, setViewportElement],
|
|
519
|
-
stateAttributesMapping:
|
|
520
|
+
stateAttributesMapping: stateAttributesMapping$2,
|
|
520
521
|
props: [{
|
|
521
522
|
role: 'presentation',
|
|
522
523
|
hidden: !mounted,
|
|
@@ -547,9 +548,8 @@ const DialogTrigger = /*#__PURE__*/React.forwardRef(function DialogTrigger(compo
|
|
|
547
548
|
handle,
|
|
548
549
|
...elementProps
|
|
549
550
|
} = componentProps;
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
const store = handleStore ?? dialogRootStore;
|
|
551
|
+
const dialogRootContext = useDialogRootContext(true);
|
|
552
|
+
const store = handle?.store ?? dialogRootContext?.store;
|
|
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,7 +571,9 @@ const DialogTrigger = /*#__PURE__*/React.forwardRef(function DialogTrigger(compo
|
|
|
571
571
|
disabled,
|
|
572
572
|
native: nativeButton
|
|
573
573
|
});
|
|
574
|
-
const click = useClick(floatingContext
|
|
574
|
+
const click = useClick(floatingContext, {
|
|
575
|
+
enabled: floatingContext != null
|
|
576
|
+
});
|
|
575
577
|
const interactionTypeProps = useOpenMethodTriggerProps(() => store.select('open'), interactionType => {
|
|
576
578
|
store.set('openMethod', interactionType);
|
|
577
579
|
});
|
|
@@ -643,6 +645,11 @@ let DrawerBackdropCssVars = /*#__PURE__*/function (DrawerBackdropCssVars) {
|
|
|
643
645
|
return DrawerBackdropCssVars;
|
|
644
646
|
}({});
|
|
645
647
|
|
|
648
|
+
const stateAttributesMapping$1 = {
|
|
649
|
+
...popupStateMapping,
|
|
650
|
+
...transitionStatusMapping
|
|
651
|
+
};
|
|
652
|
+
|
|
646
653
|
/**
|
|
647
654
|
* An overlay displayed beneath the popup.
|
|
648
655
|
* Renders a `<div>` element.
|
|
@@ -657,7 +664,9 @@ const DrawerBackdrop = /*#__PURE__*/React.forwardRef(function DrawerBackdrop(com
|
|
|
657
664
|
forceRender = false,
|
|
658
665
|
...elementProps
|
|
659
666
|
} = componentProps;
|
|
660
|
-
const
|
|
667
|
+
const {
|
|
668
|
+
store
|
|
669
|
+
} = useDialogRootContext();
|
|
661
670
|
const open = store.useState('open');
|
|
662
671
|
const nested = store.useState('nested');
|
|
663
672
|
const mounted = store.useState('mounted');
|
|
@@ -669,7 +678,7 @@ const DrawerBackdrop = /*#__PURE__*/React.forwardRef(function DrawerBackdrop(com
|
|
|
669
678
|
return useRenderElement('div', componentProps, {
|
|
670
679
|
state,
|
|
671
680
|
ref: [store.context.backdropRef, forwardedRef],
|
|
672
|
-
stateAttributesMapping:
|
|
681
|
+
stateAttributesMapping: stateAttributesMapping$1,
|
|
673
682
|
props: [{
|
|
674
683
|
role: 'presentation',
|
|
675
684
|
hidden: !mounted,
|
|
@@ -713,8 +722,9 @@ if (process.env.NODE_ENV !== "production") DrawerContent.displayName = "DrawerCo
|
|
|
713
722
|
|
|
714
723
|
const DrawerProviderContext = /*#__PURE__*/React.createContext(undefined);
|
|
715
724
|
if (process.env.NODE_ENV !== "production") DrawerProviderContext.displayName = "DrawerProviderContext";
|
|
716
|
-
function useDrawerProviderContext() {
|
|
717
|
-
|
|
725
|
+
function useDrawerProviderContext(optional) {
|
|
726
|
+
const context = React.useContext(DrawerProviderContext);
|
|
727
|
+
return context;
|
|
718
728
|
}
|
|
719
729
|
|
|
720
730
|
let DrawerPopupDataAttributes = function (DrawerPopupDataAttributes) {
|
|
@@ -727,7 +737,7 @@ let DrawerPopupDataAttributes = function (DrawerPopupDataAttributes) {
|
|
|
727
737
|
*/
|
|
728
738
|
DrawerPopupDataAttributes[DrawerPopupDataAttributes["closed"] = CommonPopupDataAttributes.closed] = "closed";
|
|
729
739
|
/**
|
|
730
|
-
* Present when the drawer
|
|
740
|
+
* Present when the drawer is animating in.
|
|
731
741
|
*/
|
|
732
742
|
DrawerPopupDataAttributes[DrawerPopupDataAttributes["startingStyle"] = CommonPopupDataAttributes.startingStyle] = "startingStyle";
|
|
733
743
|
/**
|
|
@@ -766,7 +776,7 @@ const DrawerRootContext = /*#__PURE__*/React.createContext(undefined);
|
|
|
766
776
|
if (process.env.NODE_ENV !== "production") DrawerRootContext.displayName = "DrawerRootContext";
|
|
767
777
|
function useDrawerRootContext(optional) {
|
|
768
778
|
const drawerRootContext = React.useContext(DrawerRootContext);
|
|
769
|
-
if (optional
|
|
779
|
+
if (optional === false && drawerRootContext === undefined) {
|
|
770
780
|
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DrawerRootContext is missing. Drawer parts must be placed within <Drawer.Root>.' : formatErrorMessage(90));
|
|
771
781
|
}
|
|
772
782
|
return drawerRootContext;
|
|
@@ -807,24 +817,22 @@ function resolveSnapPointValue(snapPoint, viewportHeight, rootFontSize) {
|
|
|
807
817
|
}
|
|
808
818
|
return null;
|
|
809
819
|
}
|
|
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;
|
|
820
|
+
function findClosestSnapPoint(height, points) {
|
|
821
|
+
let closest = null;
|
|
816
822
|
let closestDistance = Infinity;
|
|
817
|
-
for (
|
|
818
|
-
const distance = Math.abs(
|
|
823
|
+
for (const point of points) {
|
|
824
|
+
const distance = Math.abs(point.height - height);
|
|
819
825
|
if (distance < closestDistance) {
|
|
820
826
|
closestDistance = distance;
|
|
821
|
-
|
|
827
|
+
closest = point;
|
|
822
828
|
}
|
|
823
829
|
}
|
|
824
|
-
return
|
|
830
|
+
return closest;
|
|
825
831
|
}
|
|
826
832
|
function useDrawerSnapPoints() {
|
|
827
|
-
const
|
|
833
|
+
const {
|
|
834
|
+
store
|
|
835
|
+
} = useDialogRootContext();
|
|
828
836
|
const {
|
|
829
837
|
snapPoints,
|
|
830
838
|
activeSnapPoint,
|
|
@@ -837,7 +845,12 @@ function useDrawerSnapPoints() {
|
|
|
837
845
|
const measureViewportHeight = useStableCallback(() => {
|
|
838
846
|
const doc = ownerDocument(viewportElement);
|
|
839
847
|
const html = doc.documentElement;
|
|
840
|
-
|
|
848
|
+
if (viewportElement) {
|
|
849
|
+
setViewportHeight(viewportElement.offsetHeight);
|
|
850
|
+
}
|
|
851
|
+
if (!viewportElement) {
|
|
852
|
+
setViewportHeight(html.clientHeight);
|
|
853
|
+
}
|
|
841
854
|
const fontSize = parseFloat(getComputedStyle(html).fontSize);
|
|
842
855
|
if (Number.isFinite(fontSize)) {
|
|
843
856
|
setRootFontSize(fontSize);
|
|
@@ -859,9 +872,12 @@ function useDrawerSnapPoints() {
|
|
|
859
872
|
return [];
|
|
860
873
|
}
|
|
861
874
|
const maxHeight = Math.min(popupHeight, viewportHeight);
|
|
875
|
+
if (!Number.isFinite(maxHeight) || maxHeight <= 0) {
|
|
876
|
+
return [];
|
|
877
|
+
}
|
|
862
878
|
const resolved = snapPoints.map(value => {
|
|
863
879
|
const resolvedHeight = resolveSnapPointValue(value, viewportHeight, rootFontSize);
|
|
864
|
-
if (resolvedHeight === null) {
|
|
880
|
+
if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
|
|
865
881
|
return null;
|
|
866
882
|
}
|
|
867
883
|
const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
|
|
@@ -889,6 +905,9 @@ function useDrawerSnapPoints() {
|
|
|
889
905
|
return deduped;
|
|
890
906
|
}, [popupHeight, rootFontSize, snapPoints, viewportHeight]);
|
|
891
907
|
const resolvedActiveSnapPoint = React.useMemo(() => {
|
|
908
|
+
if (activeSnapPoint === undefined) {
|
|
909
|
+
return resolvedSnapPoints[0];
|
|
910
|
+
}
|
|
892
911
|
if (activeSnapPoint === null) {
|
|
893
912
|
return undefined;
|
|
894
913
|
}
|
|
@@ -898,11 +917,11 @@ function useDrawerSnapPoints() {
|
|
|
898
917
|
}
|
|
899
918
|
const maxHeight = Math.min(popupHeight, viewportHeight);
|
|
900
919
|
const resolvedHeight = resolveSnapPointValue(activeSnapPoint, viewportHeight, rootFontSize);
|
|
901
|
-
if (resolvedHeight === null) {
|
|
920
|
+
if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
|
|
902
921
|
return undefined;
|
|
903
922
|
}
|
|
904
923
|
const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
|
|
905
|
-
return
|
|
924
|
+
return findClosestSnapPoint(clampedHeight, resolvedSnapPoints) ?? undefined;
|
|
906
925
|
}, [activeSnapPoint, popupHeight, resolvedSnapPoints, rootFontSize, viewportHeight]);
|
|
907
926
|
return {
|
|
908
927
|
snapPoints,
|
|
@@ -917,8 +936,9 @@ function useDrawerSnapPoints() {
|
|
|
917
936
|
|
|
918
937
|
const DrawerViewportContext = /*#__PURE__*/React.createContext(null);
|
|
919
938
|
if (process.env.NODE_ENV !== "production") DrawerViewportContext.displayName = "DrawerViewportContext";
|
|
920
|
-
function useDrawerViewportContext() {
|
|
921
|
-
|
|
939
|
+
function useDrawerViewportContext(optional) {
|
|
940
|
+
const context = React.useContext(DrawerViewportContext);
|
|
941
|
+
return context;
|
|
922
942
|
}
|
|
923
943
|
|
|
924
944
|
let drawerSwipeVarsRegistered = false;
|
|
@@ -976,7 +996,8 @@ function removeCSSVariableInheritance$1() {
|
|
|
976
996
|
drawerSwipeVarsRegistered = true;
|
|
977
997
|
}
|
|
978
998
|
const stateAttributesMapping = {
|
|
979
|
-
...
|
|
999
|
+
...popupStateMapping,
|
|
1000
|
+
...transitionStatusMapping,
|
|
980
1001
|
expanded(value) {
|
|
981
1002
|
return value ? {
|
|
982
1003
|
[DrawerPopupDataAttributes.expanded]: ''
|
|
@@ -993,9 +1014,9 @@ const stateAttributesMapping = {
|
|
|
993
1014
|
} : null;
|
|
994
1015
|
},
|
|
995
1016
|
swipeDirection(value) {
|
|
996
|
-
return {
|
|
1017
|
+
return value ? {
|
|
997
1018
|
[DrawerPopupDataAttributes.swipeDirection]: value
|
|
998
|
-
};
|
|
1019
|
+
} : null;
|
|
999
1020
|
},
|
|
1000
1021
|
swiping(value) {
|
|
1001
1022
|
return value ? {
|
|
@@ -1019,7 +1040,9 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1019
1040
|
initialFocus,
|
|
1020
1041
|
...elementProps
|
|
1021
1042
|
} = componentProps;
|
|
1022
|
-
const
|
|
1043
|
+
const {
|
|
1044
|
+
store
|
|
1045
|
+
} = useDialogRootContext();
|
|
1023
1046
|
const popupRef = store.context.popupRef;
|
|
1024
1047
|
const {
|
|
1025
1048
|
swipeDirection,
|
|
@@ -1058,8 +1081,6 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1058
1081
|
const swipeStrength = swipe?.swipeStrength ?? null;
|
|
1059
1082
|
const [popupHeight, setPopupHeight] = React.useState(0);
|
|
1060
1083
|
const popupHeightRef = React.useRef(0);
|
|
1061
|
-
|
|
1062
|
-
/* istanbul ignore else -- process.env.NODE_ENV is a build-time constant. */
|
|
1063
1084
|
if (process.env.NODE_ENV !== 'production') {
|
|
1064
1085
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
1065
1086
|
React.useEffect(() => {
|
|
@@ -1142,7 +1163,7 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1142
1163
|
}
|
|
1143
1164
|
};
|
|
1144
1165
|
}, [nestedSwipeProgressStore, popupRef]);
|
|
1145
|
-
|
|
1166
|
+
React.useEffect(() => {
|
|
1146
1167
|
if (!open) {
|
|
1147
1168
|
return undefined;
|
|
1148
1169
|
}
|
|
@@ -1151,7 +1172,7 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1151
1172
|
notifyParentFrontmostHeight?.(0);
|
|
1152
1173
|
};
|
|
1153
1174
|
}, [frontmostHeight, open, notifyParentFrontmostHeight]);
|
|
1154
|
-
|
|
1175
|
+
React.useEffect(() => {
|
|
1155
1176
|
if (!notifyParentHasNestedDrawer) {
|
|
1156
1177
|
return undefined;
|
|
1157
1178
|
}
|
|
@@ -1195,7 +1216,7 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
|
|
|
1195
1216
|
let dragStyles = swipe ? swipe.getDragStyles() : EMPTY_OBJECT;
|
|
1196
1217
|
if (shouldApplySnapPoints && swipeDirection === 'down') {
|
|
1197
1218
|
const baseOffset = activeSnapPointOffset ?? 0;
|
|
1198
|
-
const movementValue = Number.parseFloat(String(dragStyles[DrawerPopupCssVars.swipeMovementY]));
|
|
1219
|
+
const movementValue = Number.parseFloat(String(dragStyles[DrawerPopupCssVars.swipeMovementY] ?? 0));
|
|
1199
1220
|
if (swiping && Number.isFinite(movementValue)) {
|
|
1200
1221
|
dragStyles = {
|
|
1201
1222
|
...dragStyles,
|
|
@@ -1305,7 +1326,6 @@ function DrawerRoot(props) {
|
|
|
1305
1326
|
state: 'snapPoint'
|
|
1306
1327
|
});
|
|
1307
1328
|
const isNestedDrawerOpenRef = React.useRef(false);
|
|
1308
|
-
const swipeAreaActiveRef = React.useRef(false);
|
|
1309
1329
|
const setActiveSnapPoint = useStableCallback((nextSnapPoint, eventDetails) => {
|
|
1310
1330
|
const resolvedEventDetails = eventDetails ?? createChangeEventDetails(none);
|
|
1311
1331
|
onSnapPointChange?.(nextSnapPoint, resolvedEventDetails);
|
|
@@ -1365,7 +1385,6 @@ function DrawerRoot(props) {
|
|
|
1365
1385
|
});
|
|
1366
1386
|
const contextValue = React.useMemo(() => ({
|
|
1367
1387
|
swipeDirection,
|
|
1368
|
-
swipeAreaActiveRef,
|
|
1369
1388
|
snapToSequentialPoints,
|
|
1370
1389
|
snapPoints,
|
|
1371
1390
|
activeSnapPoint: resolvedActiveSnapPoint,
|
|
@@ -1384,28 +1403,30 @@ function DrawerRoot(props) {
|
|
|
1384
1403
|
notifyParentSwipingChange,
|
|
1385
1404
|
notifyParentSwipeProgressChange,
|
|
1386
1405
|
notifyParentHasNestedDrawer
|
|
1387
|
-
}), [resolvedActiveSnapPoint, frontmostHeight, hasNestedDrawer, nestedSwiping, nestedSwipeProgressStore, notifyParentHasNestedDrawer, notifyParentSwipeProgressChange, notifyParentSwipingChange, notifyParentFrontmostHeight, onNestedDrawerPresenceChange, onNestedFrontmostHeightChange, onNestedSwipeProgressChange, onNestedSwipingChange, onPopupHeightChange, popupHeight, setActiveSnapPoint, snapPoints, snapToSequentialPoints,
|
|
1406
|
+
}), [resolvedActiveSnapPoint, frontmostHeight, hasNestedDrawer, nestedSwiping, nestedSwipeProgressStore, notifyParentHasNestedDrawer, notifyParentSwipeProgressChange, notifyParentSwipingChange, notifyParentFrontmostHeight, onNestedDrawerPresenceChange, onNestedFrontmostHeightChange, onNestedSwipeProgressChange, onNestedSwipingChange, onPopupHeightChange, popupHeight, setActiveSnapPoint, snapPoints, snapToSequentialPoints, swipeDirection]);
|
|
1388
1407
|
const resolvedChildren = typeof children === 'function' ? payload => /*#__PURE__*/jsxs(React.Fragment, {
|
|
1389
1408
|
children: [_DrawerProviderReport || (_DrawerProviderReport = /*#__PURE__*/jsx(DrawerProviderReporter, {})), children(payload)]
|
|
1390
1409
|
}) : /*#__PURE__*/jsxs(React.Fragment, {
|
|
1391
1410
|
children: [_DrawerProviderReport2 || (_DrawerProviderReport2 = /*#__PURE__*/jsx(DrawerProviderReporter, {})), children]
|
|
1392
1411
|
});
|
|
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
|
-
});
|
|
1406
1412
|
return /*#__PURE__*/jsx(DrawerRootContext.Provider, {
|
|
1407
1413
|
value: contextValue,
|
|
1408
|
-
children:
|
|
1414
|
+
children: /*#__PURE__*/jsx(IsDrawerContext.Provider, {
|
|
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
|
+
})
|
|
1409
1430
|
});
|
|
1410
1431
|
}
|
|
1411
1432
|
function createNestedSwipeProgressStore() {
|
|
@@ -1432,25 +1453,29 @@ function createNestedSwipeProgressStore() {
|
|
|
1432
1453
|
};
|
|
1433
1454
|
}
|
|
1434
1455
|
function DrawerProviderReporter() {
|
|
1456
|
+
const drawerId = useId();
|
|
1435
1457
|
const providerContext = useDrawerProviderContext();
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
|
|
1458
|
+
const {
|
|
1459
|
+
store
|
|
1460
|
+
} = useDialogRootContext(false);
|
|
1439
1461
|
const open = store.useState('open');
|
|
1440
1462
|
const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
|
|
1441
1463
|
const popupElement = store.useState('popupElement');
|
|
1442
1464
|
const isTopmost = nestedOpenDialogCount === 0;
|
|
1443
|
-
|
|
1444
|
-
if (!
|
|
1465
|
+
React.useEffect(() => {
|
|
1466
|
+
if (!providerContext || drawerId == null) {
|
|
1445
1467
|
return undefined;
|
|
1446
1468
|
}
|
|
1447
1469
|
return () => {
|
|
1448
|
-
removeDrawer(
|
|
1470
|
+
providerContext.removeDrawer(drawerId);
|
|
1449
1471
|
};
|
|
1450
|
-
}, [
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1472
|
+
}, [drawerId, providerContext]);
|
|
1473
|
+
React.useEffect(() => {
|
|
1474
|
+
if (drawerId == null) {
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
providerContext?.setDrawerOpen(drawerId, open);
|
|
1478
|
+
}, [drawerId, open, providerContext]);
|
|
1454
1479
|
React.useEffect(() => {
|
|
1455
1480
|
// CloseWatcher enables the Android back gesture (Chromium-only).
|
|
1456
1481
|
// Keep this Android-only for now to avoid interfering with Escape/nesting semantics on desktop due to `useDismiss`.
|
|
@@ -1478,11 +1503,53 @@ function DrawerProviderReporter() {
|
|
|
1478
1503
|
return null;
|
|
1479
1504
|
}
|
|
1480
1505
|
|
|
1481
|
-
|
|
1482
|
-
//
|
|
1483
|
-
//
|
|
1484
|
-
|
|
1485
|
-
|
|
1506
|
+
function isScrollable(element, axis,
|
|
1507
|
+
// When true, a container that overflows only once extra space is added (e.g. drawer
|
|
1508
|
+
// keyboard scroll slack) still counts, as long as it has layout size on the axis.
|
|
1509
|
+
allowOverflowIntent = false) {
|
|
1510
|
+
const style = getComputedStyle$1(element);
|
|
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;
|
|
1486
1553
|
}
|
|
1487
1554
|
|
|
1488
1555
|
const DEFAULT_SWIPE_THRESHOLD = 40;
|
|
@@ -1629,7 +1696,6 @@ function useSwipeDismiss(options) {
|
|
|
1629
1696
|
});
|
|
1630
1697
|
const swipeProgressRef = React.useRef(0);
|
|
1631
1698
|
const swipeThresholdRef = React.useRef(swipeThresholdDefault);
|
|
1632
|
-
const swipeThresholdFunctionRef = React.useRef(null);
|
|
1633
1699
|
const swipeStartTimeRef = React.useRef(null);
|
|
1634
1700
|
const lastDragSampleRef = React.useRef(null);
|
|
1635
1701
|
const lastDragVelocityRef = React.useRef({
|
|
@@ -1651,12 +1717,15 @@ function useSwipeDismiss(options) {
|
|
|
1651
1717
|
if (!direction) {
|
|
1652
1718
|
return;
|
|
1653
1719
|
}
|
|
1720
|
+
if (typeof swipeThresholdProp !== 'function') {
|
|
1721
|
+
swipeThresholdRef.current = swipeThresholdDefault;
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1654
1724
|
const element = elementRef.current;
|
|
1655
|
-
|
|
1656
|
-
if (!element || !thresholdFunction) {
|
|
1725
|
+
if (!element) {
|
|
1657
1726
|
return;
|
|
1658
1727
|
}
|
|
1659
|
-
const value =
|
|
1728
|
+
const value = swipeThresholdProp({
|
|
1660
1729
|
element,
|
|
1661
1730
|
direction
|
|
1662
1731
|
});
|
|
@@ -1734,7 +1803,6 @@ function useSwipeDismiss(options) {
|
|
|
1734
1803
|
setDragDismissed(false);
|
|
1735
1804
|
updateSwipeProgress(0);
|
|
1736
1805
|
swipeThresholdRef.current = swipeThresholdDefault;
|
|
1737
|
-
swipeThresholdFunctionRef.current = null;
|
|
1738
1806
|
dragStartPosRef.current = {
|
|
1739
1807
|
x: 0,
|
|
1740
1808
|
y: 0
|
|
@@ -1775,6 +1843,11 @@ function useSwipeDismiss(options) {
|
|
|
1775
1843
|
lastProgressDetailsRef.current = null;
|
|
1776
1844
|
syncDragStyles(false);
|
|
1777
1845
|
}, [setSwiping, swipeThresholdDefault, syncDragStyles, updateSwipeProgress]);
|
|
1846
|
+
React.useEffect(() => {
|
|
1847
|
+
if (typeof swipeThresholdProp !== 'function') {
|
|
1848
|
+
swipeThresholdRef.current = swipeThresholdDefault;
|
|
1849
|
+
}
|
|
1850
|
+
}, [swipeThresholdDefault, swipeThresholdProp]);
|
|
1778
1851
|
function getPrimaryPointerPosition(event) {
|
|
1779
1852
|
if ('touches' in event) {
|
|
1780
1853
|
const touch = event.touches[0];
|
|
@@ -1795,8 +1868,8 @@ function useSwipeDismiss(options) {
|
|
|
1795
1868
|
return event.pointerType === 'touch';
|
|
1796
1869
|
}
|
|
1797
1870
|
function getTargetAtPoint(position, nativeEvent) {
|
|
1798
|
-
const
|
|
1799
|
-
const elementAtPoint = getElementAtPoint(
|
|
1871
|
+
const doc = ownerDocument(elementRef.current);
|
|
1872
|
+
const elementAtPoint = getElementAtPoint(doc, position.x, position.y);
|
|
1800
1873
|
const target = elementAtPoint ?? getTarget(nativeEvent);
|
|
1801
1874
|
return target;
|
|
1802
1875
|
}
|
|
@@ -1839,8 +1912,6 @@ function useSwipeDismiss(options) {
|
|
|
1839
1912
|
swipeStartTimeRef.current = getValidTimeStamp(event.timeStamp);
|
|
1840
1913
|
swipeCancelBaselineRef.current = position;
|
|
1841
1914
|
lastMovePosRef.current = position;
|
|
1842
|
-
swipeThresholdRef.current = swipeThresholdDefault;
|
|
1843
|
-
swipeThresholdFunctionRef.current = typeof swipeThresholdProp === 'function' ? swipeThresholdProp : null;
|
|
1844
1915
|
if (element) {
|
|
1845
1916
|
elementSizeRef.current = {
|
|
1846
1917
|
width: element.offsetWidth,
|
|
@@ -1905,9 +1976,12 @@ function useSwipeDismiss(options) {
|
|
|
1905
1976
|
onCancel?.(event.nativeEvent);
|
|
1906
1977
|
}
|
|
1907
1978
|
function applyDirectionalDamping(deltaX, deltaY) {
|
|
1908
|
-
const exponent = value =>
|
|
1979
|
+
const exponent = value => value >= 0 ? value ** 0.5 : -(Math.abs(value) ** 0.5);
|
|
1909
1980
|
const dampAxis = (delta, allowNegative, allowPositive) => {
|
|
1910
|
-
if (!allowNegative && delta < 0
|
|
1981
|
+
if (!allowNegative && delta < 0) {
|
|
1982
|
+
return exponent(delta);
|
|
1983
|
+
}
|
|
1984
|
+
if (!allowPositive && delta > 0) {
|
|
1911
1985
|
return exponent(delta);
|
|
1912
1986
|
}
|
|
1913
1987
|
return delta;
|
|
@@ -1920,16 +1994,29 @@ function useSwipeDismiss(options) {
|
|
|
1920
1994
|
};
|
|
1921
1995
|
}
|
|
1922
1996
|
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;
|
|
1926
1997
|
const absDeltaX = Math.abs(deltaX);
|
|
1927
1998
|
const absDeltaY = Math.abs(deltaY);
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1999
|
+
const useVerticalAxis = hasVertical && deltaY !== 0 && (!hasHorizontal || absDeltaY >= absDeltaX);
|
|
2000
|
+
if (useVerticalAxis) {
|
|
2001
|
+
const maxScrollTop = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
|
|
2002
|
+
const atTop = scrollTarget.scrollTop <= 0;
|
|
2003
|
+
const atBottom = scrollTarget.scrollTop >= maxScrollTop;
|
|
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;
|
|
1933
2020
|
}
|
|
1934
2021
|
return null;
|
|
1935
2022
|
}
|
|
@@ -1950,7 +2037,7 @@ function useSwipeDismiss(options) {
|
|
|
1950
2037
|
pendingSwipeRef.current = true;
|
|
1951
2038
|
pendingSwipeStartPosRef.current = startPos;
|
|
1952
2039
|
swipeFromScrollableRef.current = false;
|
|
1953
|
-
sawPrimaryButtonsOnMoveRef.current =
|
|
2040
|
+
sawPrimaryButtonsOnMoveRef.current = false;
|
|
1954
2041
|
const allowedToStart = canStart ? canStart(startPos, {
|
|
1955
2042
|
nativeEvent: event.nativeEvent,
|
|
1956
2043
|
direction: primaryDirection
|
|
@@ -1978,21 +2065,14 @@ function useSwipeDismiss(options) {
|
|
|
1978
2065
|
event.preventDefault();
|
|
1979
2066
|
}
|
|
1980
2067
|
if (isFirstPointerMoveRef.current) {
|
|
1981
|
-
|
|
1982
|
-
//
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
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
|
-
}
|
|
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;
|
|
1995
2074
|
}
|
|
2075
|
+
isFirstPointerMoveRef.current = false;
|
|
1996
2076
|
}
|
|
1997
2077
|
const clientX = position.x;
|
|
1998
2078
|
const clientY = position.y;
|
|
@@ -2080,45 +2160,51 @@ function useSwipeDismiss(options) {
|
|
|
2080
2160
|
newOffsetY += dampedDelta.y;
|
|
2081
2161
|
}
|
|
2082
2162
|
}
|
|
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;
|
|
2093
2163
|
dragOffsetRef.current = {
|
|
2094
2164
|
x: newOffsetX,
|
|
2095
2165
|
y: newOffsetY
|
|
2096
2166
|
};
|
|
2097
|
-
|
|
2098
|
-
syncDragStyles(true);
|
|
2099
|
-
}
|
|
2167
|
+
syncDragStyles(true);
|
|
2100
2168
|
recordDragSample({
|
|
2101
2169
|
x: newOffsetX,
|
|
2102
2170
|
y: newOffsetY
|
|
2103
2171
|
}, getValidTimeStamp(event.timeStamp));
|
|
2104
2172
|
const dragDeltaX = newOffsetX - initialTransformRef.current.x;
|
|
2105
2173
|
const dragDeltaY = newOffsetY - initialTransformRef.current.y;
|
|
2106
|
-
const
|
|
2107
|
-
deltaX: dragDeltaX,
|
|
2108
|
-
deltaY: dragDeltaY,
|
|
2109
|
-
direction: intendedSwipeDirectionRef.current
|
|
2110
|
-
};
|
|
2111
|
-
let progress = 0;
|
|
2174
|
+
const swipeDirectionDetails = intendedSwipeDirectionRef.current;
|
|
2112
2175
|
const progressDirection = primaryDirection ?? intendedSwipeDirectionRef.current;
|
|
2113
|
-
if (progressDirection) {
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2176
|
+
if (!progressDirection) {
|
|
2177
|
+
updateSwipeProgress(0, {
|
|
2178
|
+
deltaX: dragDeltaX,
|
|
2179
|
+
deltaY: dragDeltaY,
|
|
2180
|
+
direction: swipeDirectionDetails
|
|
2181
|
+
});
|
|
2182
|
+
return;
|
|
2120
2183
|
}
|
|
2121
|
-
|
|
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), {
|
|
2204
|
+
deltaX: dragDeltaX,
|
|
2205
|
+
deltaY: dragDeltaY,
|
|
2206
|
+
direction: swipeDirectionDetails
|
|
2207
|
+
});
|
|
2122
2208
|
}
|
|
2123
2209
|
const handleEnd = useStableCallback(event => {
|
|
2124
2210
|
if (!enabled) {
|
|
@@ -2206,9 +2292,33 @@ function useSwipeDismiss(options) {
|
|
|
2206
2292
|
dismissDirection = intendedSwipeDirectionRef.current ?? primaryDirection;
|
|
2207
2293
|
} else {
|
|
2208
2294
|
for (const direction of directions) {
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2295
|
+
switch (direction) {
|
|
2296
|
+
case 'right':
|
|
2297
|
+
if (deltaX > swipeThresholdRef.current) {
|
|
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) {
|
|
2212
2322
|
break;
|
|
2213
2323
|
}
|
|
2214
2324
|
}
|
|
@@ -2350,29 +2460,25 @@ function useSwipeDismiss(options) {
|
|
|
2350
2460
|
});
|
|
2351
2461
|
});
|
|
2352
2462
|
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;
|
|
2357
2463
|
const dragOffset = dragOffsetRef.current;
|
|
2358
2464
|
const initialTransform = initialTransformRef.current;
|
|
2359
2465
|
const deltaX = dragOffset.x - initialTransform.x;
|
|
2360
2466
|
const deltaY = dragOffset.y - initialTransform.y;
|
|
2361
|
-
if (!
|
|
2467
|
+
if (!isSwiping && deltaX === 0 && deltaY === 0 && !dragDismissed) {
|
|
2362
2468
|
return {
|
|
2363
2469
|
[movementCssVars.x]: '0px',
|
|
2364
2470
|
[movementCssVars.y]: '0px'
|
|
2365
2471
|
};
|
|
2366
2472
|
}
|
|
2367
2473
|
return {
|
|
2368
|
-
transition:
|
|
2474
|
+
transition: isSwiping ? 'none' : undefined,
|
|
2369
2475
|
// While swiping, freeze the element at its current visual transform so it doesn't snap to the
|
|
2370
2476
|
// end position.
|
|
2371
|
-
transform:
|
|
2477
|
+
transform: isSwiping ? getDragTransform(dragOffset, initialTransform.scale) : undefined,
|
|
2372
2478
|
[movementCssVars.x]: `${deltaX}px`,
|
|
2373
2479
|
[movementCssVars.y]: `${deltaY}px`
|
|
2374
2480
|
};
|
|
2375
|
-
}, [dragDismissed, movementCssVars]);
|
|
2481
|
+
}, [dragDismissed, isSwiping, movementCssVars]);
|
|
2376
2482
|
const getPointerProps = React.useCallback(() => {
|
|
2377
2483
|
if (!enabled) {
|
|
2378
2484
|
return {};
|
|
@@ -2432,8 +2538,6 @@ const MIN_SWIPE_RELEASE_DURATION_MS = 80;
|
|
|
2432
2538
|
const MAX_SWIPE_RELEASE_DURATION_MS = 360;
|
|
2433
2539
|
const MIN_SWIPE_RELEASE_SCALAR = 0.1;
|
|
2434
2540
|
const MAX_SWIPE_RELEASE_SCALAR = 1;
|
|
2435
|
-
const AXIS_LOCK_SLOP = 6;
|
|
2436
|
-
const AXIS_LOCK_BIAS = 2;
|
|
2437
2541
|
const DRAWER_CONTENT_SELECTOR = `[${DRAWER_CONTENT_ATTRIBUTE}]`;
|
|
2438
2542
|
/**
|
|
2439
2543
|
* A positioning container for the drawer popup that can be made scrollable.
|
|
@@ -2449,7 +2553,9 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2449
2553
|
children,
|
|
2450
2554
|
...elementProps
|
|
2451
2555
|
} = props;
|
|
2452
|
-
const
|
|
2556
|
+
const {
|
|
2557
|
+
store
|
|
2558
|
+
} = useDialogRootContext();
|
|
2453
2559
|
const popupRef = store.context.popupRef;
|
|
2454
2560
|
const backdropRef = store.context.backdropRef;
|
|
2455
2561
|
const {
|
|
@@ -2457,8 +2563,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2457
2563
|
notifyParentSwipingChange,
|
|
2458
2564
|
notifyParentSwipeProgressChange,
|
|
2459
2565
|
frontmostHeight,
|
|
2460
|
-
snapToSequentialPoints
|
|
2461
|
-
swipeAreaActiveRef
|
|
2566
|
+
snapToSequentialPoints
|
|
2462
2567
|
} = useDrawerRootContext();
|
|
2463
2568
|
const providerContext = useDrawerProviderContext();
|
|
2464
2569
|
const {
|
|
@@ -2492,13 +2597,30 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2492
2597
|
const touchScrollStateRef = React.useRef(null);
|
|
2493
2598
|
const virtualKeyboard = useDrawerVirtualKeyboardContext();
|
|
2494
2599
|
const snapPointRange = React.useMemo(() => {
|
|
2495
|
-
if (!snapPoints || snapPoints.length < 2
|
|
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) {
|
|
2496
2611
|
return null;
|
|
2497
2612
|
}
|
|
2498
|
-
const offsets = resolvedSnapPoints.map(point => point.offset).sort((a, b) => a - b);
|
|
2499
2613
|
const minOffset = offsets[0];
|
|
2500
2614
|
const nextOffset = offsets[1];
|
|
2501
|
-
const
|
|
2615
|
+
const maxOffset = offsets[offsets.length - 1];
|
|
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
|
+
}
|
|
2502
2624
|
return {
|
|
2503
2625
|
minOffset,
|
|
2504
2626
|
range
|
|
@@ -2517,8 +2639,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2517
2639
|
return [swipeDirection];
|
|
2518
2640
|
}, [snapPoints, swipeDirection]);
|
|
2519
2641
|
const setSwipeDismissed = useStableCallback(dismissed => {
|
|
2520
|
-
popupRef.current
|
|
2521
|
-
backdropRef.current?.toggleAttribute(DrawerPopupDataAttributes.swipeDismiss, dismissed);
|
|
2642
|
+
setSwipeDismissedElements(popupRef.current, backdropRef.current, dismissed);
|
|
2522
2643
|
});
|
|
2523
2644
|
const clearSwipeRelease = useStableCallback(() => {
|
|
2524
2645
|
setSwipeDismissed(false);
|
|
@@ -2532,7 +2653,11 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2532
2653
|
nestedSwipeActiveRef.current = false;
|
|
2533
2654
|
notifyParentSwipingChange?.(false);
|
|
2534
2655
|
});
|
|
2535
|
-
const applySwipeProgress = useStableCallback((
|
|
2656
|
+
const applySwipeProgress = useStableCallback(({
|
|
2657
|
+
resolvedProgress,
|
|
2658
|
+
shouldTrackProgress,
|
|
2659
|
+
notifyParent
|
|
2660
|
+
}) => {
|
|
2536
2661
|
const isActive = open && !nested && shouldTrackProgress;
|
|
2537
2662
|
const swipeProgress = isActive ? resolvedProgress : 0;
|
|
2538
2663
|
const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
|
|
@@ -2550,48 +2675,78 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2550
2675
|
if (!backdropElement) {
|
|
2551
2676
|
return;
|
|
2552
2677
|
}
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2678
|
+
if (!isActive || swipeProgress <= 0) {
|
|
2679
|
+
backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');
|
|
2680
|
+
backdropElement.style.removeProperty(DrawerPopupCssVars.height);
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${swipeProgress}`);
|
|
2684
|
+
if (frontmostHeight > 0) {
|
|
2556
2685
|
backdropElement.style.setProperty(DrawerPopupCssVars.height, `${frontmostHeight}px`);
|
|
2557
2686
|
} else {
|
|
2558
2687
|
backdropElement.style.removeProperty(DrawerPopupCssVars.height);
|
|
2559
2688
|
}
|
|
2560
2689
|
});
|
|
2561
|
-
function resolveSwipeRelease(
|
|
2562
|
-
|
|
2563
|
-
|
|
2690
|
+
function resolveSwipeRelease({
|
|
2691
|
+
direction,
|
|
2692
|
+
deltaX,
|
|
2693
|
+
deltaY,
|
|
2694
|
+
velocityX,
|
|
2695
|
+
velocityY,
|
|
2696
|
+
releaseVelocityX,
|
|
2697
|
+
releaseVelocityY
|
|
2698
|
+
}) {
|
|
2699
|
+
if (!direction) {
|
|
2564
2700
|
return null;
|
|
2565
2701
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
const
|
|
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) {
|
|
2708
|
+
return null;
|
|
2709
|
+
}
|
|
2710
|
+
const axisDelta = direction === 'left' || direction === 'right' ? deltaX : deltaY;
|
|
2711
|
+
const snapPointBaseOffset = snapPoints && snapPoints.length > 0 ? activeSnapPointOffset ?? 0 : 0;
|
|
2712
|
+
let baseOffset = 0;
|
|
2713
|
+
if (direction === 'down') {
|
|
2714
|
+
baseOffset = snapPointBaseOffset;
|
|
2715
|
+
} else if (direction === 'up') {
|
|
2716
|
+
baseOffset = -snapPointBaseOffset;
|
|
2717
|
+
}
|
|
2718
|
+
const translation = baseOffset + axisDelta;
|
|
2719
|
+
const translationAlongDirection = direction === 'left' || direction === 'up' ? -translation : translation;
|
|
2571
2720
|
const remainingDistance = Math.max(0, size - translationAlongDirection);
|
|
2572
|
-
if (remainingDistance <= 0) {
|
|
2721
|
+
if (!Number.isFinite(remainingDistance) || remainingDistance <= 0) {
|
|
2573
2722
|
return null;
|
|
2574
2723
|
}
|
|
2575
|
-
const
|
|
2576
|
-
const
|
|
2577
|
-
|
|
2724
|
+
const axisVelocity = direction === 'left' || direction === 'right' ? releaseVelocityX : releaseVelocityY;
|
|
2725
|
+
const fallbackVelocity = direction === 'left' || direction === 'right' ? velocityX : velocityY;
|
|
2726
|
+
const resolvedVelocity = Math.abs(axisVelocity) > 0 && Number.isFinite(axisVelocity) ? axisVelocity : fallbackVelocity;
|
|
2727
|
+
const directionalVelocity = direction === 'left' || direction === 'up' ? -resolvedVelocity : resolvedVelocity;
|
|
2728
|
+
if (!Number.isFinite(directionalVelocity) || directionalVelocity <= MIN_SWIPE_RELEASE_VELOCITY) {
|
|
2578
2729
|
return null;
|
|
2579
2730
|
}
|
|
2580
2731
|
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].
|
|
2584
2732
|
const durationMs = clamp(remainingDistance / clampedVelocity, MIN_SWIPE_RELEASE_DURATION_MS, MAX_SWIPE_RELEASE_DURATION_MS);
|
|
2733
|
+
if (!Number.isFinite(durationMs)) {
|
|
2734
|
+
return null;
|
|
2735
|
+
}
|
|
2585
2736
|
const normalizedDuration = (durationMs - MIN_SWIPE_RELEASE_DURATION_MS) / (MAX_SWIPE_RELEASE_DURATION_MS - MIN_SWIPE_RELEASE_DURATION_MS);
|
|
2586
|
-
|
|
2737
|
+
const durationScalar = clamp(MIN_SWIPE_RELEASE_SCALAR + normalizedDuration * (MAX_SWIPE_RELEASE_SCALAR - MIN_SWIPE_RELEASE_SCALAR), MIN_SWIPE_RELEASE_SCALAR, MAX_SWIPE_RELEASE_SCALAR);
|
|
2738
|
+
if (!Number.isFinite(durationScalar) || durationScalar <= 0) {
|
|
2739
|
+
return null;
|
|
2740
|
+
}
|
|
2741
|
+
return durationScalar;
|
|
2587
2742
|
}
|
|
2588
2743
|
function updateNestedSwipeActive(details) {
|
|
2589
2744
|
if (nestedSwipeActiveRef.current || !details) {
|
|
2590
2745
|
return;
|
|
2591
2746
|
}
|
|
2592
2747
|
const direction = details.direction ?? swipeDirection;
|
|
2593
|
-
const delta =
|
|
2594
|
-
if (Math.abs(delta) < MIN_SWIPE_THRESHOLD) {
|
|
2748
|
+
const delta = direction === 'left' || direction === 'right' ? details.deltaX : details.deltaY;
|
|
2749
|
+
if (!Number.isFinite(delta) || Math.abs(delta) < MIN_SWIPE_THRESHOLD) {
|
|
2595
2750
|
return;
|
|
2596
2751
|
}
|
|
2597
2752
|
nestedSwipeActiveRef.current = true;
|
|
@@ -2608,10 +2763,13 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2608
2763
|
y: DrawerPopupCssVars.swipeMovementY
|
|
2609
2764
|
},
|
|
2610
2765
|
onSwipeStart(event) {
|
|
2611
|
-
if ('touches' in event || event.pointerType === 'touch') {
|
|
2766
|
+
if ('touches' in event || 'pointerType' in event && event.pointerType === 'touch') {
|
|
2612
2767
|
return;
|
|
2613
2768
|
}
|
|
2614
2769
|
const popupElement = popupRef.current;
|
|
2770
|
+
if (!popupElement) {
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2615
2773
|
const doc = ownerDocument(popupElement);
|
|
2616
2774
|
const selection = doc.getSelection?.();
|
|
2617
2775
|
if (!selection || selection.isCollapsed) {
|
|
@@ -2643,38 +2801,54 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2643
2801
|
return false;
|
|
2644
2802
|
}
|
|
2645
2803
|
const doc = popupElement.ownerDocument;
|
|
2646
|
-
const elementAtPoint = getElementAtPoint(
|
|
2804
|
+
const elementAtPoint = getElementAtPoint(doc, position.x, position.y);
|
|
2647
2805
|
if (!elementAtPoint || !contains(popupElement, elementAtPoint)) {
|
|
2648
2806
|
return false;
|
|
2649
2807
|
}
|
|
2650
2808
|
const nativeEvent = details.nativeEvent;
|
|
2651
|
-
const touchLike = 'touches' in nativeEvent || nativeEvent.pointerType === 'touch';
|
|
2809
|
+
const touchLike = 'touches' in nativeEvent || 'pointerType' in nativeEvent && nativeEvent.pointerType === 'touch';
|
|
2652
2810
|
if (touchLike && shouldIgnoreSwipeForTextSelection(doc, popupElement)) {
|
|
2653
2811
|
return false;
|
|
2654
2812
|
}
|
|
2813
|
+
if (nativeEvent.type === 'touchstart' && isSwipeIgnoredTarget(elementAtPoint)) {
|
|
2814
|
+
return false;
|
|
2815
|
+
}
|
|
2655
2816
|
return true;
|
|
2656
2817
|
},
|
|
2657
2818
|
onProgress(progress, details) {
|
|
2658
2819
|
updateNestedSwipeActive(details);
|
|
2659
2820
|
const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
|
|
2660
|
-
if (swipingRef.current && swipeDirection === 'down' && hasSnapPoints && details) {
|
|
2821
|
+
if (swipingRef.current && swipeDirection === 'down' && hasSnapPoints && details && Number.isFinite(details.deltaY)) {
|
|
2661
2822
|
const popupElement = store.context.popupRef.current;
|
|
2662
2823
|
if (popupElement) {
|
|
2663
2824
|
popupElement.style.removeProperty('transform');
|
|
2664
2825
|
popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementY, `${getSnapPointSwipeMovement(activeSnapPointOffset ?? 0, details.deltaY)}px`);
|
|
2665
2826
|
}
|
|
2666
2827
|
}
|
|
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;
|
|
2667
2832
|
let resolvedProgress = progress;
|
|
2668
2833
|
if (snapPointRange && popupHeight > 0) {
|
|
2669
|
-
const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;
|
|
2670
|
-
const offsetToProgress = nextOffset => clamp((nextOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
|
|
2671
2834
|
if (details && Number.isFinite(details.deltaY)) {
|
|
2672
|
-
|
|
2835
|
+
const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;
|
|
2836
|
+
const nextOffset = clamp(baseOffset + details.deltaY, 0, popupHeight);
|
|
2837
|
+
resolvedProgress = clamp((nextOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);
|
|
2673
2838
|
} else if (snapPointProgress !== null) {
|
|
2674
2839
|
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);
|
|
2675
2845
|
}
|
|
2676
2846
|
}
|
|
2677
|
-
applySwipeProgress(
|
|
2847
|
+
applySwipeProgress({
|
|
2848
|
+
resolvedProgress,
|
|
2849
|
+
shouldTrackProgress,
|
|
2850
|
+
notifyParent: true
|
|
2851
|
+
});
|
|
2678
2852
|
},
|
|
2679
2853
|
onRelease({
|
|
2680
2854
|
event,
|
|
@@ -2686,21 +2860,30 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2686
2860
|
releaseVelocityX,
|
|
2687
2861
|
releaseVelocityY
|
|
2688
2862
|
}) {
|
|
2689
|
-
const
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2863
|
+
const swipeReleasePayload = {
|
|
2864
|
+
deltaX,
|
|
2865
|
+
deltaY,
|
|
2866
|
+
velocityX,
|
|
2867
|
+
velocityY,
|
|
2868
|
+
releaseVelocityX,
|
|
2869
|
+
releaseVelocityY
|
|
2870
|
+
};
|
|
2695
2871
|
function startSwipeRelease(resolvedDirection) {
|
|
2696
2872
|
// Start ending transition styles earlier and synchronously to prevent a period where
|
|
2697
2873
|
// 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
|
+
}
|
|
2698
2878
|
finishNestedSwipe();
|
|
2699
2879
|
setSwipeDismissed(true);
|
|
2700
|
-
|
|
2701
|
-
|
|
2880
|
+
popupElement.style.removeProperty('transition');
|
|
2881
|
+
popupElement.setAttribute(TransitionStatusDataAttributes.endingStyle, '');
|
|
2702
2882
|
ReactDOM.flushSync(() => {
|
|
2703
|
-
setSwipeRelease(resolveSwipeRelease(
|
|
2883
|
+
setSwipeRelease(resolveSwipeRelease({
|
|
2884
|
+
direction: resolvedDirection,
|
|
2885
|
+
...swipeReleasePayload
|
|
2886
|
+
}));
|
|
2704
2887
|
});
|
|
2705
2888
|
}
|
|
2706
2889
|
if (!snapPoints || snapPoints.length === 0) {
|
|
@@ -2708,16 +2891,29 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2708
2891
|
clearSwipeRelease();
|
|
2709
2892
|
return undefined;
|
|
2710
2893
|
}
|
|
2711
|
-
const
|
|
2894
|
+
const element = store.context.popupRef.current;
|
|
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;
|
|
2712
2906
|
if (directionalDelta <= 0) {
|
|
2713
2907
|
clearSwipeRelease();
|
|
2714
2908
|
return false;
|
|
2715
2909
|
}
|
|
2716
|
-
|
|
2910
|
+
const velocity = direction === 'left' || direction === 'right' ? velocityX : velocityY;
|
|
2911
|
+
const directionalVelocity = direction === 'left' || direction === 'up' ? -velocity : velocity;
|
|
2912
|
+
if (directionalVelocity >= FAST_SWIPE_VELOCITY && directionalDelta > 0) {
|
|
2717
2913
|
startSwipeRelease(direction);
|
|
2718
2914
|
return true;
|
|
2719
2915
|
}
|
|
2720
|
-
const shouldClose = directionalDelta >
|
|
2916
|
+
const shouldClose = directionalDelta > baseThreshold;
|
|
2721
2917
|
if (shouldClose) {
|
|
2722
2918
|
startSwipeRelease(direction);
|
|
2723
2919
|
} else {
|
|
@@ -2729,20 +2925,20 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2729
2925
|
clearSwipeRelease();
|
|
2730
2926
|
return undefined;
|
|
2731
2927
|
}
|
|
2732
|
-
if (!popupHeight) {
|
|
2928
|
+
if (!popupHeight || resolvedSnapPoints.length === 0) {
|
|
2733
2929
|
clearSwipeRelease();
|
|
2734
|
-
return
|
|
2930
|
+
return undefined;
|
|
2735
2931
|
}
|
|
2736
|
-
|
|
2932
|
+
const dragDelta = swipeDirection === 'down' ? deltaY : -deltaY;
|
|
2933
|
+
if (!Number.isFinite(dragDelta)) {
|
|
2737
2934
|
clearSwipeRelease();
|
|
2738
2935
|
return undefined;
|
|
2739
2936
|
}
|
|
2740
|
-
const dragDelta = swipeDirection === 'down' ? deltaY : -deltaY;
|
|
2741
2937
|
const dragDirection = Math.sign(dragDelta);
|
|
2742
2938
|
const releaseDirectionalVelocity = swipeDirection === 'down' ? releaseVelocityY : -releaseVelocityY;
|
|
2743
2939
|
const fallbackDirectionalVelocity = swipeDirection === 'down' ? velocityY : -velocityY;
|
|
2744
|
-
let resolvedDirectionalVelocity = releaseDirectionalVelocity;
|
|
2745
|
-
if (dragDirection !== 0 && Math.abs(dragDelta) >= MIN_SWIPE_THRESHOLD) {
|
|
2940
|
+
let resolvedDirectionalVelocity = Number.isFinite(releaseDirectionalVelocity) ? releaseDirectionalVelocity : fallbackDirectionalVelocity;
|
|
2941
|
+
if (dragDirection !== 0 && Math.abs(dragDelta) >= MIN_SWIPE_THRESHOLD && Number.isFinite(resolvedDirectionalVelocity)) {
|
|
2746
2942
|
const velocityDirection = Math.sign(resolvedDirectionalVelocity);
|
|
2747
2943
|
if (velocityDirection !== 0 && velocityDirection !== dragDirection) {
|
|
2748
2944
|
// Ignore touch reversals that would otherwise flip the snap decision.
|
|
@@ -2751,20 +2947,39 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2751
2947
|
}
|
|
2752
2948
|
const currentOffset = activeSnapPointOffset ?? 0;
|
|
2753
2949
|
const dragTargetOffset = clamp(currentOffset + dragDelta, 0, popupHeight);
|
|
2754
|
-
const velocityOffset = Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD ? clamp(resolvedDirectionalVelocity, -MAX_SNAP_VELOCITY, MAX_SNAP_VELOCITY) * SNAP_VELOCITY_MULTIPLIER : 0;
|
|
2950
|
+
const velocityOffset = Number.isFinite(resolvedDirectionalVelocity) && Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD ? clamp(resolvedDirectionalVelocity, -MAX_SNAP_VELOCITY, MAX_SNAP_VELOCITY) * SNAP_VELOCITY_MULTIPLIER : 0;
|
|
2755
2951
|
const targetOffset = snapToSequentialPoints ? dragTargetOffset : clamp(dragTargetOffset + velocityOffset, 0, popupHeight);
|
|
2756
2952
|
const snapPointEventDetails = createChangeEventDetails(swipe, event);
|
|
2757
2953
|
const closeFromSnapPoints = () => {
|
|
2758
2954
|
pendingSwipeCloseSnapPointRef.current = activeSnapPoint;
|
|
2759
|
-
setActiveSnapPoint(null, snapPointEventDetails);
|
|
2955
|
+
setActiveSnapPoint?.(null, snapPointEventDetails);
|
|
2760
2956
|
startSwipeRelease(swipeDirection);
|
|
2761
2957
|
return true;
|
|
2762
2958
|
};
|
|
2763
2959
|
if (snapToSequentialPoints) {
|
|
2764
2960
|
const orderedSnapPoints = [...resolvedSnapPoints].sort((first, second) => first.offset - second.offset);
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2961
|
+
if (orderedSnapPoints.length === 0) {
|
|
2962
|
+
clearSwipeRelease();
|
|
2963
|
+
return false;
|
|
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
|
+
}
|
|
2768
2983
|
const velocityDirection = Math.sign(resolvedDirectionalVelocity);
|
|
2769
2984
|
const shouldAdvance = dragDirection !== 0 && velocityDirection !== 0 && velocityDirection === dragDirection && Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD;
|
|
2770
2985
|
let effectiveTargetOffset = targetOffset;
|
|
@@ -2781,24 +2996,34 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2781
2996
|
return closeFromSnapPoints();
|
|
2782
2997
|
}
|
|
2783
2998
|
}
|
|
2784
|
-
const
|
|
2999
|
+
const closeOffset = popupHeight;
|
|
3000
|
+
const closeDistance = Math.abs(effectiveTargetOffset - closeOffset);
|
|
2785
3001
|
const snapDistance = Math.abs(effectiveTargetOffset - targetSnapPoint.offset);
|
|
2786
3002
|
if (closeDistance < snapDistance) {
|
|
2787
3003
|
return closeFromSnapPoints();
|
|
2788
3004
|
}
|
|
2789
|
-
setActiveSnapPoint(targetSnapPoint.value, snapPointEventDetails);
|
|
3005
|
+
setActiveSnapPoint?.(targetSnapPoint.value, snapPointEventDetails);
|
|
2790
3006
|
clearSwipeRelease();
|
|
2791
3007
|
return false;
|
|
2792
3008
|
}
|
|
2793
3009
|
if (resolvedDirectionalVelocity >= FAST_SWIPE_VELOCITY && dragDelta > 0) {
|
|
2794
3010
|
return closeFromSnapPoints();
|
|
2795
3011
|
}
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
3012
|
+
let closestSnapPoint = resolvedSnapPoints[0];
|
|
3013
|
+
let closestDistance = Math.abs(targetOffset - closestSnapPoint.offset);
|
|
3014
|
+
for (const snapPoint of resolvedSnapPoints) {
|
|
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) {
|
|
2799
3024
|
return closeFromSnapPoints();
|
|
2800
3025
|
}
|
|
2801
|
-
setActiveSnapPoint(closestSnapPoint.value, snapPointEventDetails);
|
|
3026
|
+
setActiveSnapPoint?.(closestSnapPoint.value, snapPointEventDetails);
|
|
2802
3027
|
clearSwipeRelease();
|
|
2803
3028
|
return false;
|
|
2804
3029
|
},
|
|
@@ -2817,7 +3042,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2817
3042
|
if (dismissEventDetails.isCanceled) {
|
|
2818
3043
|
const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;
|
|
2819
3044
|
if (pendingSnapPoint !== undefined) {
|
|
2820
|
-
setActiveSnapPoint(pendingSnapPoint, createChangeEventDetails(swipe, event));
|
|
3045
|
+
setActiveSnapPoint?.(pendingSnapPoint, createChangeEventDetails(swipe, event));
|
|
2821
3046
|
}
|
|
2822
3047
|
pendingSwipeCloseSnapPointRef.current = undefined;
|
|
2823
3048
|
resetSwipeRef.current?.();
|
|
@@ -2839,7 +3064,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2839
3064
|
// Parent rejected: revert animation and restore snap point.
|
|
2840
3065
|
const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;
|
|
2841
3066
|
if (pendingSnapPoint !== undefined) {
|
|
2842
|
-
setActiveSnapPoint(pendingSnapPoint, createChangeEventDetails(swipe, savedEvent));
|
|
3067
|
+
setActiveSnapPoint?.(pendingSnapPoint, createChangeEventDetails(swipe, savedEvent));
|
|
2843
3068
|
}
|
|
2844
3069
|
pendingSwipeCloseSnapPointRef.current = undefined;
|
|
2845
3070
|
clearSwipeRelease();
|
|
@@ -2869,18 +3094,42 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2869
3094
|
}
|
|
2870
3095
|
const resolvedRootElement = rootElement;
|
|
2871
3096
|
const doc = ownerDocument(resolvedRootElement);
|
|
2872
|
-
|
|
3097
|
+
const win = getWindow(doc);
|
|
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
|
+
}
|
|
2873
3112
|
const drawerAxisDelta = isVerticalScrollAxis ? touch.clientY - touchState.lastY : touch.clientX - touchState.lastX;
|
|
2874
3113
|
|
|
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
|
+
|
|
2875
3121
|
// Avoid blocking pinch zoom or text selection adjustments on iOS Safari.
|
|
2876
3122
|
if (event.touches.length === 2) {
|
|
3123
|
+
updateTouchScrollPosition(touchState, touch);
|
|
2877
3124
|
return;
|
|
2878
3125
|
}
|
|
2879
3126
|
const allowTouchMove = shouldIgnoreSwipeForTextSelection(doc, resolvedRootElement);
|
|
2880
3127
|
if (allowTouchMove || !open || !mounted || nestedDrawerOpen) {
|
|
3128
|
+
updateTouchScrollPosition(touchState, touch);
|
|
2881
3129
|
return;
|
|
2882
3130
|
}
|
|
2883
|
-
if (
|
|
3131
|
+
if (preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis)) {
|
|
3132
|
+
updateTouchScrollPosition(touchState, touch);
|
|
2884
3133
|
return;
|
|
2885
3134
|
}
|
|
2886
3135
|
const scrollTarget = touchState.scrollTarget;
|
|
@@ -2892,21 +3141,27 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2892
3141
|
// move through React re-rasterizes the popup content on every frame.
|
|
2893
3142
|
event.stopPropagation();
|
|
2894
3143
|
moveSwipeNative(event, resolvedRootElement);
|
|
3144
|
+
updateTouchScrollPosition(touchState, touch);
|
|
2895
3145
|
return;
|
|
2896
3146
|
}
|
|
2897
|
-
|
|
3147
|
+
const hasScrollableContent = hasScrollableContentOnAxis(scrollTarget, scrollAxis);
|
|
3148
|
+
if (!hasScrollableContent) {
|
|
2898
3149
|
// If the scroll container doesn't overflow on the drawer axis, prevent the window from
|
|
2899
3150
|
// scrolling instead.
|
|
2900
3151
|
if (event.cancelable) {
|
|
2901
3152
|
event.preventDefault();
|
|
2902
3153
|
}
|
|
2903
3154
|
event.stopPropagation();
|
|
3155
|
+
updateTouchScrollPosition(touchState, touch);
|
|
2904
3156
|
return;
|
|
2905
3157
|
}
|
|
2906
|
-
|
|
2907
|
-
|
|
3158
|
+
const delta = drawerAxisDelta;
|
|
3159
|
+
if (delta !== 0) {
|
|
3160
|
+
const canSwipeFromScrollEdge = canSwipeFromScrollEdgeOnMove(scrollTarget, scrollAxis, swipeDirection, delta);
|
|
2908
3161
|
if (!touchState.allowSwipe) {
|
|
2909
|
-
if (event.cancelable
|
|
3162
|
+
if (!event.cancelable) {
|
|
3163
|
+
touchState.allowSwipe = false;
|
|
3164
|
+
} else if (canSwipeFromScrollEdge) {
|
|
2910
3165
|
touchState.allowSwipe = true;
|
|
2911
3166
|
event.preventDefault();
|
|
2912
3167
|
} else {
|
|
@@ -2920,22 +3175,6 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2920
3175
|
event.stopPropagation();
|
|
2921
3176
|
moveSwipeNative(event, resolvedRootElement);
|
|
2922
3177
|
}
|
|
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);
|
|
2939
3178
|
updateTouchScrollPosition(touchState, touch);
|
|
2940
3179
|
}
|
|
2941
3180
|
return addEventListener(doc, 'touchmove', handleNativeTouchMove, {
|
|
@@ -2943,13 +3182,18 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2943
3182
|
capture: true
|
|
2944
3183
|
});
|
|
2945
3184
|
}, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, moveSwipeNative, viewportElement, virtualKeyboard]);
|
|
2946
|
-
|
|
3185
|
+
React.useEffect(() => {
|
|
2947
3186
|
if (!snapPointRange || swipe$1.swiping) {
|
|
2948
3187
|
return;
|
|
2949
3188
|
}
|
|
2950
|
-
|
|
3189
|
+
const resolvedProgress = !open || nested ? 0 : snapPointProgress ?? 0;
|
|
3190
|
+
applySwipeProgress({
|
|
3191
|
+
resolvedProgress,
|
|
3192
|
+
shouldTrackProgress: true,
|
|
3193
|
+
notifyParent: false
|
|
3194
|
+
});
|
|
2951
3195
|
}, [applySwipeProgress, frontmostHeight, nested, notifyParentSwipeProgressChange, open, snapPointProgress, snapPointRange, swipe$1.swiping, store, visualStateStore]);
|
|
2952
|
-
|
|
3196
|
+
React.useEffect(() => {
|
|
2953
3197
|
if (!notifyParentSwipeProgressChange) {
|
|
2954
3198
|
return undefined;
|
|
2955
3199
|
}
|
|
@@ -2960,19 +3204,13 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2960
3204
|
notifyParentSwipeProgressChange(0);
|
|
2961
3205
|
};
|
|
2962
3206
|
}, [notifyParentSwipeProgressChange, open]);
|
|
2963
|
-
|
|
3207
|
+
React.useEffect(() => {
|
|
2964
3208
|
if (open) {
|
|
2965
|
-
|
|
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
|
-
}
|
|
3209
|
+
resetSwipe();
|
|
2972
3210
|
clearSwipeRelease();
|
|
2973
3211
|
}
|
|
2974
|
-
}, [clearSwipeRelease, open, resetSwipe
|
|
2975
|
-
|
|
3212
|
+
}, [clearSwipeRelease, open, resetSwipe]);
|
|
3213
|
+
React.useEffect(() => {
|
|
2976
3214
|
const backdropElement = backdropRef.current;
|
|
2977
3215
|
return () => {
|
|
2978
3216
|
visualStateStore?.set({
|
|
@@ -2997,19 +3235,12 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2997
3235
|
swipeStrength: swipeRelease ?? null,
|
|
2998
3236
|
setSwipeDismissed
|
|
2999
3237
|
}), [setSwipeDismissed, swipe$1.getDragStyles, swipe$1.swiping, swipeRelease]);
|
|
3000
|
-
function resetTouchSwipeState(ignoreSwipe) {
|
|
3001
|
-
ignoreTouchSwipeRef.current = ignoreSwipe;
|
|
3002
|
-
touchScrollStateRef.current = null;
|
|
3003
|
-
}
|
|
3004
3238
|
function resetTouchTrackingState() {
|
|
3005
|
-
|
|
3239
|
+
ignoreTouchSwipeRef.current = false;
|
|
3240
|
+
touchScrollStateRef.current = null;
|
|
3006
3241
|
lastPointerTypeRef.current = '';
|
|
3007
3242
|
ignoreNextTouchStartFromPenRef.current = false;
|
|
3008
3243
|
}
|
|
3009
|
-
function handlePointerEnd(event) {
|
|
3010
|
-
lastPointerTypeRef.current = '';
|
|
3011
|
-
return event.pointerType !== 'touch';
|
|
3012
|
-
}
|
|
3013
3244
|
return /*#__PURE__*/jsx(DialogViewport, {
|
|
3014
3245
|
ref: forwardedRef,
|
|
3015
3246
|
className: className,
|
|
@@ -3022,7 +3253,8 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3022
3253
|
if (!open || !mounted || nestedDrawerOpen) {
|
|
3023
3254
|
return;
|
|
3024
3255
|
}
|
|
3025
|
-
const
|
|
3256
|
+
const doc = ownerDocument(event.currentTarget);
|
|
3257
|
+
const elementAtPoint = getElementAtPoint(doc, event.clientX, event.clientY);
|
|
3026
3258
|
if (isSwipeIgnoredTarget(elementAtPoint) || isDrawerContentTarget(elementAtPoint)) {
|
|
3027
3259
|
return;
|
|
3028
3260
|
}
|
|
@@ -3038,24 +3270,34 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3038
3270
|
swipePointerProps.onPointerMove?.(event);
|
|
3039
3271
|
},
|
|
3040
3272
|
onPointerUp(event) {
|
|
3041
|
-
if (
|
|
3042
|
-
|
|
3273
|
+
if (lastPointerTypeRef.current === event.pointerType) {
|
|
3274
|
+
lastPointerTypeRef.current = '';
|
|
3275
|
+
}
|
|
3276
|
+
if (event.pointerType === 'touch') {
|
|
3277
|
+
return;
|
|
3043
3278
|
}
|
|
3279
|
+
swipePointerProps.onPointerUp?.(event);
|
|
3044
3280
|
},
|
|
3045
3281
|
onPointerCancel(event) {
|
|
3046
|
-
if (
|
|
3047
|
-
|
|
3282
|
+
if (lastPointerTypeRef.current === event.pointerType) {
|
|
3283
|
+
lastPointerTypeRef.current = '';
|
|
3284
|
+
}
|
|
3285
|
+
if (event.pointerType === 'touch') {
|
|
3286
|
+
return;
|
|
3048
3287
|
}
|
|
3288
|
+
swipePointerProps.onPointerCancel?.(event);
|
|
3049
3289
|
},
|
|
3050
3290
|
onTouchStart(event) {
|
|
3051
3291
|
const startedFromPenPointerDown = lastPointerTypeRef.current === 'pen' && ignoreNextTouchStartFromPenRef.current;
|
|
3052
3292
|
if (startedFromPenPointerDown) {
|
|
3053
3293
|
ignoreNextTouchStartFromPenRef.current = false;
|
|
3054
|
-
|
|
3294
|
+
ignoreTouchSwipeRef.current = false;
|
|
3295
|
+
touchScrollStateRef.current = null;
|
|
3055
3296
|
return;
|
|
3056
3297
|
}
|
|
3057
3298
|
if (!open || !mounted || nestedDrawerOpen) {
|
|
3058
|
-
|
|
3299
|
+
ignoreTouchSwipeRef.current = false;
|
|
3300
|
+
touchScrollStateRef.current = null;
|
|
3059
3301
|
return;
|
|
3060
3302
|
}
|
|
3061
3303
|
const touch = event.touches[0];
|
|
@@ -3063,25 +3305,32 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3063
3305
|
return;
|
|
3064
3306
|
}
|
|
3065
3307
|
if (isReactTouchEventOnRangeInput(event)) {
|
|
3066
|
-
|
|
3308
|
+
ignoreTouchSwipeRef.current = false;
|
|
3309
|
+
touchScrollStateRef.current = null;
|
|
3067
3310
|
return;
|
|
3068
3311
|
}
|
|
3069
|
-
const
|
|
3070
|
-
const elementAtPoint = getElementAtPoint(
|
|
3312
|
+
const doc = ownerDocument(event.currentTarget);
|
|
3313
|
+
const elementAtPoint = getElementAtPoint(doc, touch.clientX, touch.clientY);
|
|
3314
|
+
const rootElement = viewportElement ?? popupElementState;
|
|
3071
3315
|
const eventTarget = getTarget(event.nativeEvent);
|
|
3072
|
-
const target = isElement(eventTarget) ? eventTarget :
|
|
3073
|
-
if (!contains(rootElement, target)) {
|
|
3074
|
-
|
|
3316
|
+
const target = isElement(eventTarget) ? eventTarget : null;
|
|
3317
|
+
if (rootElement && target && !contains(rootElement, target)) {
|
|
3318
|
+
ignoreTouchSwipeRef.current = true;
|
|
3319
|
+
touchScrollStateRef.current = null;
|
|
3075
3320
|
return;
|
|
3076
3321
|
}
|
|
3077
3322
|
virtualKeyboard?.onTouchStart(event);
|
|
3078
|
-
|
|
3079
|
-
|
|
3323
|
+
ignoreTouchSwipeRef.current = isSwipeIgnoredTarget(elementAtPoint);
|
|
3324
|
+
if (ignoreTouchSwipeRef.current) {
|
|
3325
|
+
touchScrollStateRef.current = null;
|
|
3080
3326
|
return;
|
|
3081
3327
|
}
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3328
|
+
let scrollTarget = null;
|
|
3329
|
+
let hasCrossAxisScrollableContent = false;
|
|
3330
|
+
if (rootElement && target) {
|
|
3331
|
+
scrollTarget = findScrollableTouchTarget(target, rootElement, scrollAxis);
|
|
3332
|
+
hasCrossAxisScrollableContent = findScrollableTouchTarget(target, rootElement, crossScrollAxis) != null;
|
|
3333
|
+
}
|
|
3085
3334
|
let allowSwipe = null;
|
|
3086
3335
|
if (scrollTarget) {
|
|
3087
3336
|
const canSwipeFromEdge = isAtSwipeStartEdge(scrollTarget, scrollAxis, swipeDirection);
|
|
@@ -3095,8 +3344,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3095
3344
|
scrollTarget,
|
|
3096
3345
|
hasCrossAxisScrollableContent,
|
|
3097
3346
|
allowSwipe,
|
|
3098
|
-
preserveNativeCrossAxisScroll: false
|
|
3099
|
-
drawerAxisAttributed: false
|
|
3347
|
+
preserveNativeCrossAxisScroll: false
|
|
3100
3348
|
};
|
|
3101
3349
|
swipeTouchProps.onTouchStart?.(event);
|
|
3102
3350
|
},
|
|
@@ -3121,8 +3369,24 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
3121
3369
|
});
|
|
3122
3370
|
});
|
|
3123
3371
|
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
|
+
}
|
|
3124
3381
|
function setBackdropSwipingAttribute(backdropElement, swiping) {
|
|
3125
|
-
backdropElement
|
|
3382
|
+
if (!backdropElement) {
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
if (swiping) {
|
|
3386
|
+
backdropElement.setAttribute(DrawerPopupDataAttributes.swiping, '');
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
backdropElement.removeAttribute(DrawerPopupDataAttributes.swiping);
|
|
3126
3390
|
}
|
|
3127
3391
|
function isSwipeIgnoredTarget(target) {
|
|
3128
3392
|
return Boolean(target?.closest(BASE_UI_SWIPE_IGNORE_SELECTOR));
|
|
@@ -3130,16 +3394,17 @@ function isSwipeIgnoredTarget(target) {
|
|
|
3130
3394
|
function isDrawerContentTarget(target) {
|
|
3131
3395
|
return Boolean(target?.closest(DRAWER_CONTENT_SELECTOR));
|
|
3132
3396
|
}
|
|
3133
|
-
function getBaseSwipeSize(element, direction) {
|
|
3134
|
-
return direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;
|
|
3135
|
-
}
|
|
3136
3397
|
function getBaseSwipeThreshold(element, direction) {
|
|
3137
|
-
|
|
3398
|
+
const size = direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;
|
|
3399
|
+
return Math.max(size * 0.5, MIN_SWIPE_THRESHOLD);
|
|
3138
3400
|
}
|
|
3139
3401
|
function isRangeInput(target, win) {
|
|
3140
3402
|
return target instanceof win.HTMLInputElement && target.type === 'range';
|
|
3141
3403
|
}
|
|
3142
3404
|
function isTextSelectionControl(target) {
|
|
3405
|
+
if (!isElement(target)) {
|
|
3406
|
+
return false;
|
|
3407
|
+
}
|
|
3143
3408
|
return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
|
|
3144
3409
|
}
|
|
3145
3410
|
function hasExpandedSelectionWithinTarget(selection, target) {
|
|
@@ -3149,7 +3414,8 @@ function hasExpandedSelectionWithinTarget(selection, target) {
|
|
|
3149
3414
|
}
|
|
3150
3415
|
function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
|
|
3151
3416
|
const activeEl = activeElement(doc);
|
|
3152
|
-
|
|
3417
|
+
const activeElementWithinRoot = Boolean(activeEl && contains(rootElement, activeEl));
|
|
3418
|
+
if (activeElementWithinRoot && isTextSelectionControl(activeEl)) {
|
|
3153
3419
|
const {
|
|
3154
3420
|
selectionStart,
|
|
3155
3421
|
selectionEnd
|
|
@@ -3165,7 +3431,11 @@ function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
|
|
|
3165
3431
|
return hasExpandedSelectionWithinTarget(selection, rootElement);
|
|
3166
3432
|
}
|
|
3167
3433
|
function isEventOnRangeInput(event, win) {
|
|
3168
|
-
|
|
3434
|
+
const composedPath = event.composedPath();
|
|
3435
|
+
if (composedPath) {
|
|
3436
|
+
return composedPath.some(pathTarget => isRangeInput(pathTarget, win));
|
|
3437
|
+
}
|
|
3438
|
+
return isRangeInput(getTarget(event), win);
|
|
3169
3439
|
}
|
|
3170
3440
|
function isReactTouchEventOnRangeInput(event) {
|
|
3171
3441
|
return isEventOnRangeInput(event.nativeEvent, getWindow(event.currentTarget));
|
|
@@ -3174,51 +3444,25 @@ function updateTouchScrollPosition(touchState, touch) {
|
|
|
3174
3444
|
touchState.lastX = touch.clientX;
|
|
3175
3445
|
touchState.lastY = touch.clientY;
|
|
3176
3446
|
}
|
|
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) {
|
|
3447
|
+
function preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis) {
|
|
3184
3448
|
if (touchState.preserveNativeCrossAxisScroll) {
|
|
3185
3449
|
return true;
|
|
3186
3450
|
}
|
|
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) {
|
|
3451
|
+
if (touchState.allowSwipe === true || !touchState.hasCrossAxisScrollableContent) {
|
|
3192
3452
|
return false;
|
|
3193
3453
|
}
|
|
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
|
-
}
|
|
3201
3454
|
const drawerAxisGestureDelta = isVerticalScrollAxis ? touch.clientY - touchState.startY : touch.clientX - touchState.startX;
|
|
3202
3455
|
const crossAxisGestureDelta = isVerticalScrollAxis ? touch.clientX - touchState.startX : touch.clientY - touchState.startY;
|
|
3203
3456
|
const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
|
|
3204
3457
|
const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
|
|
3205
|
-
if (absCrossAxisGestureDelta
|
|
3206
|
-
touchState.preserveNativeCrossAxisScroll = true;
|
|
3207
|
-
return true;
|
|
3208
|
-
}
|
|
3209
|
-
if (absDrawerAxisGestureDelta >= AXIS_LOCK_SLOP) {
|
|
3210
|
-
touchState.drawerAxisAttributed = true;
|
|
3458
|
+
if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {
|
|
3211
3459
|
return false;
|
|
3212
3460
|
}
|
|
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.
|
|
3461
|
+
touchState.preserveNativeCrossAxisScroll = true;
|
|
3218
3462
|
return true;
|
|
3219
3463
|
}
|
|
3220
3464
|
function hasScrollableContentOnAxis(scrollTarget, axis) {
|
|
3221
|
-
return
|
|
3465
|
+
return axis === 'vertical' ? scrollTarget.scrollHeight > scrollTarget.clientHeight : scrollTarget.scrollWidth > scrollTarget.clientWidth;
|
|
3222
3466
|
}
|
|
3223
3467
|
function getScrollMetrics(scrollTarget, axis) {
|
|
3224
3468
|
if (axis === 'vertical') {
|
|
@@ -3235,23 +3479,48 @@ function getScrollMetrics(scrollTarget, axis) {
|
|
|
3235
3479
|
};
|
|
3236
3480
|
}
|
|
3237
3481
|
function isAtSwipeStartEdge(scrollTarget, axis, direction) {
|
|
3238
|
-
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
3239
3482
|
const {
|
|
3240
3483
|
offset,
|
|
3241
3484
|
max
|
|
3242
3485
|
} = getScrollMetrics(scrollTarget, axis);
|
|
3486
|
+
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
3487
|
+
if (dismissFromStartEdge === null) {
|
|
3488
|
+
return false;
|
|
3489
|
+
}
|
|
3243
3490
|
return dismissFromStartEdge ? offset <= 0 : offset >= max;
|
|
3244
3491
|
}
|
|
3245
3492
|
function canSwipeFromScrollEdgeOnMove(scrollTarget, axis, direction, delta) {
|
|
3493
|
+
const {
|
|
3494
|
+
offset,
|
|
3495
|
+
max
|
|
3496
|
+
} = getScrollMetrics(scrollTarget, axis);
|
|
3246
3497
|
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
3498
|
+
if (dismissFromStartEdge === null) {
|
|
3499
|
+
return false;
|
|
3500
|
+
}
|
|
3247
3501
|
const movingTowardDismiss = dismissFromStartEdge ? delta > 0 : delta < 0;
|
|
3248
3502
|
if (!movingTowardDismiss) {
|
|
3249
3503
|
return false;
|
|
3250
3504
|
}
|
|
3251
|
-
return
|
|
3505
|
+
return dismissFromStartEdge ? offset <= 0 : offset >= max;
|
|
3252
3506
|
}
|
|
3253
3507
|
function shouldDismissFromStartEdge(direction, axis) {
|
|
3254
|
-
|
|
3508
|
+
if (axis === 'vertical') {
|
|
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;
|
|
3255
3524
|
}
|
|
3256
3525
|
|
|
3257
3526
|
const ScrollAreaRootContext = /*#__PURE__*/React.createContext(undefined);
|
|
@@ -3264,6 +3533,20 @@ function useScrollAreaRootContext() {
|
|
|
3264
3533
|
return context;
|
|
3265
3534
|
}
|
|
3266
3535
|
|
|
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
|
+
|
|
3267
3550
|
const SCROLL_TIMEOUT = 500;
|
|
3268
3551
|
const MIN_THUMB_SIZE = 16;
|
|
3269
3552
|
|
|
@@ -3272,28 +3555,108 @@ function getOffset(element, prop, axis) {
|
|
|
3272
3555
|
return 0;
|
|
3273
3556
|
}
|
|
3274
3557
|
const styles = getComputedStyle(element);
|
|
3275
|
-
const
|
|
3276
|
-
const start = parseFloat(styles[`${key}Start`]);
|
|
3558
|
+
const propAxis = axis === 'x' ? 'Inline' : 'Block';
|
|
3277
3559
|
|
|
3278
3560
|
// Safari misreports `marginInlineEnd` in RTL.
|
|
3279
3561
|
// We have to assume the start/end values are symmetrical, which is likely.
|
|
3280
3562
|
if (axis === 'x' && prop === 'margin') {
|
|
3281
|
-
return
|
|
3563
|
+
return parseFloat(styles[`${prop}InlineStart`]) * 2;
|
|
3282
3564
|
}
|
|
3283
|
-
return
|
|
3565
|
+
return parseFloat(styles[`${prop}${propAxis}Start`]) + parseFloat(styles[`${prop}${propAxis}End`]);
|
|
3284
3566
|
}
|
|
3285
3567
|
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3568
|
+
let ScrollAreaScrollbarDataAttributes = /*#__PURE__*/function (ScrollAreaScrollbarDataAttributes) {
|
|
3569
|
+
/**
|
|
3570
|
+
* Indicates the orientation of the scrollbar.
|
|
3571
|
+
* @type {'horizontal' | 'vertical'}
|
|
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
|
+
|
|
3290
3641
|
const scrollAreaStateAttributesMapping = {
|
|
3291
|
-
hasOverflowX:
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3642
|
+
hasOverflowX: value => value ? {
|
|
3643
|
+
[ScrollAreaRootDataAttributes.hasOverflowX]: ''
|
|
3644
|
+
} : null,
|
|
3645
|
+
hasOverflowY: value => value ? {
|
|
3646
|
+
[ScrollAreaRootDataAttributes.hasOverflowY]: ''
|
|
3647
|
+
} : null,
|
|
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,
|
|
3297
3660
|
cornerHidden: () => null
|
|
3298
3661
|
};
|
|
3299
3662
|
|
|
@@ -3359,145 +3722,99 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3359
3722
|
const thumbYRef = React.useRef(null);
|
|
3360
3723
|
const thumbXRef = React.useRef(null);
|
|
3361
3724
|
const cornerRef = React.useRef(null);
|
|
3362
|
-
const
|
|
3725
|
+
const thumbDraggingRef = React.useRef(false);
|
|
3363
3726
|
const startYRef = React.useRef(0);
|
|
3364
3727
|
const startXRef = React.useRef(0);
|
|
3365
3728
|
const startScrollTopRef = React.useRef(0);
|
|
3366
3729
|
const startScrollLeftRef = React.useRef(0);
|
|
3367
3730
|
const currentOrientationRef = React.useRef('vertical');
|
|
3368
3731
|
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
|
-
}
|
|
3378
3732
|
const handleScroll = useStableCallback(scrollPosition => {
|
|
3379
3733
|
const offsetX = scrollPosition.x - scrollPositionRef.current.x;
|
|
3380
3734
|
const offsetY = scrollPosition.y - scrollPositionRef.current.y;
|
|
3381
3735
|
scrollPositionRef.current = scrollPosition;
|
|
3382
3736
|
if (offsetY !== 0) {
|
|
3383
|
-
|
|
3737
|
+
setScrollingY(true);
|
|
3738
|
+
scrollYTimeout.start(SCROLL_TIMEOUT, () => {
|
|
3739
|
+
setScrollingY(false);
|
|
3740
|
+
});
|
|
3384
3741
|
}
|
|
3385
3742
|
if (offsetX !== 0) {
|
|
3386
|
-
|
|
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';
|
|
3743
|
+
setScrollingX(true);
|
|
3744
|
+
scrollXTimeout.start(SCROLL_TIMEOUT, () => {
|
|
3745
|
+
setScrollingX(false);
|
|
3746
|
+
});
|
|
3401
3747
|
}
|
|
3402
3748
|
});
|
|
3403
3749
|
const handlePointerDown = useStableCallback(event => {
|
|
3404
3750
|
if (event.button !== 0) {
|
|
3405
3751
|
return;
|
|
3406
3752
|
}
|
|
3407
|
-
|
|
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;
|
|
3753
|
+
thumbDraggingRef.current = true;
|
|
3418
3754
|
startYRef.current = event.clientY;
|
|
3419
3755
|
startXRef.current = event.clientX;
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
const viewportEl = viewportRef.current;
|
|
3425
|
-
if (viewportEl) {
|
|
3426
|
-
startScrollTopRef.current = viewportEl.scrollTop;
|
|
3427
|
-
startScrollLeftRef.current = viewportEl.scrollLeft;
|
|
3428
|
-
disableViewportSnap();
|
|
3429
|
-
}
|
|
3430
|
-
const thumb = currentOrientationRef.current === 'vertical' ? thumbYRef.current : thumbXRef.current;
|
|
3431
|
-
thumb?.setPointerCapture(event.pointerId);
|
|
3432
|
-
});
|
|
3433
|
-
const handlePointerUp = useStableCallback(event => {
|
|
3434
|
-
if (event.pointerId !== activePointerIdRef.current) {
|
|
3435
|
-
return;
|
|
3756
|
+
currentOrientationRef.current = event.currentTarget.getAttribute(ScrollAreaScrollbarDataAttributes.orientation);
|
|
3757
|
+
if (viewportRef.current) {
|
|
3758
|
+
startScrollTopRef.current = viewportRef.current.scrollTop;
|
|
3759
|
+
startScrollLeftRef.current = viewportRef.current.scrollLeft;
|
|
3436
3760
|
}
|
|
3437
|
-
|
|
3438
|
-
|
|
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;
|
|
3445
|
-
}
|
|
3446
|
-
savedSnapTypeRef.current = null;
|
|
3761
|
+
if (thumbYRef.current && currentOrientationRef.current === 'vertical') {
|
|
3762
|
+
thumbYRef.current.setPointerCapture(event.pointerId);
|
|
3447
3763
|
}
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
// capture we no longer hold (which would throw).
|
|
3451
|
-
if (thumb?.hasPointerCapture(event.pointerId)) {
|
|
3452
|
-
thumb.releasePointerCapture(event.pointerId);
|
|
3764
|
+
if (thumbXRef.current && currentOrientationRef.current === 'horizontal') {
|
|
3765
|
+
thumbXRef.current.setPointerCapture(event.pointerId);
|
|
3453
3766
|
}
|
|
3454
3767
|
});
|
|
3455
3768
|
const handlePointerMove = useStableCallback(event => {
|
|
3456
|
-
if (
|
|
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);
|
|
3769
|
+
if (!thumbDraggingRef.current) {
|
|
3467
3770
|
return;
|
|
3468
3771
|
}
|
|
3469
|
-
const
|
|
3470
|
-
|
|
3471
|
-
|
|
3772
|
+
const deltaY = event.clientY - startYRef.current;
|
|
3773
|
+
const deltaX = event.clientX - startXRef.current;
|
|
3774
|
+
if (viewportRef.current) {
|
|
3775
|
+
const scrollableContentHeight = viewportRef.current.scrollHeight;
|
|
3776
|
+
const viewportHeight = viewportRef.current.clientHeight;
|
|
3777
|
+
const scrollableContentWidth = viewportRef.current.scrollWidth;
|
|
3778
|
+
const viewportWidth = viewportRef.current.clientWidth;
|
|
3779
|
+
if (thumbYRef.current && scrollbarYRef.current && currentOrientationRef.current === 'vertical') {
|
|
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
|
+
});
|
|
3804
|
+
}
|
|
3472
3805
|
}
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3806
|
+
});
|
|
3807
|
+
const handlePointerUp = useStableCallback(event => {
|
|
3808
|
+
thumbDraggingRef.current = false;
|
|
3809
|
+
|
|
3810
|
+
// `pointercancel` releases capture implicitly, so guard against releasing a
|
|
3811
|
+
// capture we no longer hold (which would throw).
|
|
3812
|
+
if (thumbYRef.current && currentOrientationRef.current === 'vertical' && thumbYRef.current.hasPointerCapture(event.pointerId)) {
|
|
3813
|
+
thumbYRef.current.releasePointerCapture(event.pointerId);
|
|
3478
3814
|
}
|
|
3479
|
-
|
|
3480
|
-
|
|
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;
|
|
3815
|
+
if (thumbXRef.current && currentOrientationRef.current === 'horizontal' && thumbXRef.current.hasPointerCapture(event.pointerId)) {
|
|
3816
|
+
thumbXRef.current.releasePointerCapture(event.pointerId);
|
|
3498
3817
|
}
|
|
3499
|
-
event.preventDefault();
|
|
3500
|
-
startScrolling(vertical);
|
|
3501
3818
|
});
|
|
3502
3819
|
function handleTouchModalityChange(event) {
|
|
3503
3820
|
setTouchModality(event.pointerType === 'touch');
|
|
@@ -3529,8 +3846,8 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3529
3846
|
},
|
|
3530
3847
|
style: {
|
|
3531
3848
|
position: 'relative',
|
|
3532
|
-
[
|
|
3533
|
-
[
|
|
3849
|
+
[ScrollAreaRootCssVars.scrollAreaCornerHeight]: `${cornerSize.height}px`,
|
|
3850
|
+
[ScrollAreaRootCssVars.scrollAreaCornerWidth]: `${cornerSize.width}px`
|
|
3534
3851
|
}
|
|
3535
3852
|
};
|
|
3536
3853
|
const element = useRenderElement('div', componentProps, {
|
|
@@ -3544,7 +3861,6 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3544
3861
|
handlePointerMove,
|
|
3545
3862
|
handlePointerUp,
|
|
3546
3863
|
handleScroll,
|
|
3547
|
-
disableViewportSnap,
|
|
3548
3864
|
cornerSize,
|
|
3549
3865
|
setCornerSize,
|
|
3550
3866
|
thumbSize,
|
|
@@ -3554,10 +3870,13 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3554
3870
|
touchModality,
|
|
3555
3871
|
cornerRef,
|
|
3556
3872
|
scrollingX,
|
|
3873
|
+
setScrollingX,
|
|
3557
3874
|
scrollingY,
|
|
3875
|
+
setScrollingY,
|
|
3558
3876
|
hovering,
|
|
3559
3877
|
setHovering,
|
|
3560
3878
|
viewportRef,
|
|
3879
|
+
rootRef,
|
|
3561
3880
|
scrollbarYRef,
|
|
3562
3881
|
scrollbarXRef,
|
|
3563
3882
|
thumbYRef,
|
|
@@ -3574,7 +3893,7 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3574
3893
|
yStart,
|
|
3575
3894
|
yEnd
|
|
3576
3895
|
}
|
|
3577
|
-
}), [handlePointerDown, handlePointerMove, handlePointerUp, handleScroll,
|
|
3896
|
+
}), [handlePointerDown, handlePointerMove, handlePointerUp, handleScroll, cornerSize, thumbSize, hasMeasuredScrollbar, touchModality, scrollingX, setScrollingX, scrollingY, setScrollingY, hovering, setHovering, rootId, hiddenState, overflowEdges, state, xStart, xEnd, yStart, yEnd]);
|
|
3578
3897
|
return /*#__PURE__*/jsxs(ScrollAreaRootContext.Provider, {
|
|
3579
3898
|
value: contextValue,
|
|
3580
3899
|
children: [!disableStyleElements && styleDisableScrollbar.getElement(nonce), element]
|
|
@@ -3582,17 +3901,20 @@ const ScrollAreaRoot = /*#__PURE__*/React.forwardRef(function ScrollAreaRoot(com
|
|
|
3582
3901
|
});
|
|
3583
3902
|
if (process.env.NODE_ENV !== "production") ScrollAreaRoot.displayName = "ScrollAreaRoot";
|
|
3584
3903
|
function normalizeOverflowEdgeThreshold(threshold) {
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3904
|
+
if (typeof threshold === 'number') {
|
|
3905
|
+
const value = Math.max(0, threshold);
|
|
3906
|
+
return {
|
|
3907
|
+
xStart: value,
|
|
3908
|
+
xEnd: value,
|
|
3909
|
+
yStart: value,
|
|
3910
|
+
yEnd: value
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3591
3913
|
return {
|
|
3592
|
-
xStart: Math.max(0,
|
|
3593
|
-
xEnd: Math.max(0,
|
|
3594
|
-
yStart: Math.max(0,
|
|
3595
|
-
yEnd: Math.max(0,
|
|
3914
|
+
xStart: Math.max(0, threshold?.xStart || 0),
|
|
3915
|
+
xEnd: Math.max(0, threshold?.xEnd || 0),
|
|
3916
|
+
yStart: Math.max(0, threshold?.yStart || 0),
|
|
3917
|
+
yEnd: Math.max(0, threshold?.yEnd || 0)
|
|
3596
3918
|
};
|
|
3597
3919
|
}
|
|
3598
3920
|
|
|
@@ -3606,10 +3928,30 @@ function useScrollAreaViewportContext() {
|
|
|
3606
3928
|
return context;
|
|
3607
3929
|
}
|
|
3608
3930
|
|
|
3609
|
-
|
|
3931
|
+
let ScrollAreaViewportCssVars = /*#__PURE__*/function (ScrollAreaViewportCssVars) {
|
|
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
|
+
}({});
|
|
3610
3954
|
|
|
3611
|
-
// Module-level flag to ensure we only register the CSS properties once,
|
|
3612
|
-
// regardless of how many Scroll Area components are mounted.
|
|
3613
3955
|
let scrollAreaOverflowVarsRegistered = false;
|
|
3614
3956
|
|
|
3615
3957
|
/**
|
|
@@ -3628,7 +3970,7 @@ function removeCSSVariableInheritance() {
|
|
|
3628
3970
|
return;
|
|
3629
3971
|
}
|
|
3630
3972
|
if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {
|
|
3631
|
-
|
|
3973
|
+
[ScrollAreaViewportCssVars.scrollAreaOverflowXStart, ScrollAreaViewportCssVars.scrollAreaOverflowXEnd, ScrollAreaViewportCssVars.scrollAreaOverflowYStart, ScrollAreaViewportCssVars.scrollAreaOverflowYEnd].forEach(name => {
|
|
3632
3974
|
try {
|
|
3633
3975
|
CSS.registerProperty({
|
|
3634
3976
|
name,
|
|
@@ -3672,11 +4014,12 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
3672
4014
|
hiddenState,
|
|
3673
4015
|
setHasMeasuredScrollbar,
|
|
3674
4016
|
handleScroll,
|
|
3675
|
-
touchModality,
|
|
3676
4017
|
setHovering,
|
|
3677
4018
|
setOverflowEdges,
|
|
4019
|
+
overflowEdges,
|
|
3678
4020
|
overflowEdgeThreshold,
|
|
3679
|
-
|
|
4021
|
+
scrollingX,
|
|
4022
|
+
scrollingY
|
|
3680
4023
|
} = useScrollAreaRootContext();
|
|
3681
4024
|
const direction = useDirection();
|
|
3682
4025
|
const programmaticScrollRef = React.useRef(true);
|
|
@@ -3721,12 +4064,18 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
3721
4064
|
let scrollLeftFromStart = 0;
|
|
3722
4065
|
let scrollLeftFromEnd = 0;
|
|
3723
4066
|
if (!scrollbarXHidden) {
|
|
3724
|
-
|
|
3725
|
-
|
|
4067
|
+
let rawScrollLeftFromStart = 0;
|
|
4068
|
+
if (direction === 'rtl') {
|
|
4069
|
+
rawScrollLeftFromStart = clamp(-scrollLeft, 0, maxScrollLeft);
|
|
4070
|
+
} else {
|
|
4071
|
+
rawScrollLeftFromStart = clamp(scrollLeft, 0, maxScrollLeft);
|
|
4072
|
+
}
|
|
4073
|
+
scrollLeftFromStart = normalizeScrollOffset(rawScrollLeftFromStart, maxScrollLeft);
|
|
3726
4074
|
scrollLeftFromEnd = maxScrollLeft - scrollLeftFromStart;
|
|
3727
4075
|
}
|
|
3728
|
-
const
|
|
3729
|
-
const
|
|
4076
|
+
const rawScrollTopFromStart = !scrollbarYHidden ? clamp(scrollTop, 0, maxScrollTop) : 0;
|
|
4077
|
+
const scrollTopFromStart = !scrollbarYHidden ? normalizeScrollOffset(rawScrollTopFromStart, maxScrollTop) : 0;
|
|
4078
|
+
const scrollTopFromEnd = !scrollbarYHidden ? maxScrollTop - scrollTopFromStart : 0;
|
|
3730
4079
|
const nextWidth = scrollbarXHidden ? 0 : viewportWidth;
|
|
3731
4080
|
const nextHeight = scrollbarYHidden ? 0 : viewportHeight;
|
|
3732
4081
|
let nextCornerWidth = 0;
|
|
@@ -3751,53 +4100,76 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
3751
4100
|
const maxNextHeight = scrollbarYEl ? Math.min(scrollbarYEl.offsetHeight - cornerHeightOffset, idealNextHeight) : idealNextHeight;
|
|
3752
4101
|
const clampedNextWidth = Math.max(MIN_THUMB_SIZE, maxNextWidth * ratioX);
|
|
3753
4102
|
const clampedNextHeight = Math.max(MIN_THUMB_SIZE, maxNextHeight * ratioY);
|
|
3754
|
-
setThumbSize(prevSize =>
|
|
3755
|
-
width
|
|
3756
|
-
|
|
3757
|
-
|
|
4103
|
+
setThumbSize(prevSize => {
|
|
4104
|
+
if (prevSize.height === clampedNextHeight && prevSize.width === clampedNextWidth) {
|
|
4105
|
+
return prevSize;
|
|
4106
|
+
}
|
|
4107
|
+
return {
|
|
4108
|
+
width: clampedNextWidth,
|
|
4109
|
+
height: clampedNextHeight
|
|
4110
|
+
};
|
|
4111
|
+
});
|
|
3758
4112
|
|
|
3759
4113
|
// Handle Y (vertical) scroll
|
|
3760
4114
|
if (scrollbarYEl && thumbYEl) {
|
|
3761
4115
|
const maxThumbOffsetY = scrollbarYEl.offsetHeight - clampedNextHeight - scrollbarYOffset - thumbYOffset;
|
|
3762
|
-
const
|
|
4116
|
+
const scrollRangeY = scrollableContentHeight - viewportHeight;
|
|
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));
|
|
3763
4122
|
thumbYEl.style.transform = `translate3d(0,${thumbOffsetY}px,0)`;
|
|
3764
4123
|
}
|
|
3765
4124
|
|
|
3766
4125
|
// Handle X (horizontal) scroll
|
|
3767
4126
|
if (scrollbarXEl && thumbXEl) {
|
|
3768
4127
|
const maxThumbOffsetX = scrollbarXEl.offsetWidth - clampedNextWidth - scrollbarXOffset - thumbXOffset;
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
4128
|
+
const scrollRangeX = scrollableContentWidth - viewportWidth;
|
|
4129
|
+
const scrollRatioX = scrollRangeX === 0 ? 0 : scrollLeft / scrollRangeX;
|
|
4130
|
+
|
|
4131
|
+
// In Safari, don't allow it to go negative or too far as `scrollLeft` considers the rubber
|
|
4132
|
+
// band effect.
|
|
4133
|
+
const thumbOffsetX = direction === 'rtl' ? clamp(scrollRatioX * maxThumbOffsetX, -maxThumbOffsetX, 0) : clamp(scrollRatioX * maxThumbOffsetX, 0, maxThumbOffsetX);
|
|
4134
|
+
thumbXEl.style.transform = `translate3d(${thumbOffsetX}px,0,0)`;
|
|
4135
|
+
}
|
|
4136
|
+
const overflowMetricsPx = [[ScrollAreaViewportCssVars.scrollAreaOverflowXStart, scrollLeftFromStart], [ScrollAreaViewportCssVars.scrollAreaOverflowXEnd, scrollLeftFromEnd], [ScrollAreaViewportCssVars.scrollAreaOverflowYStart, scrollTopFromStart], [ScrollAreaViewportCssVars.scrollAreaOverflowYEnd, scrollTopFromEnd]];
|
|
4137
|
+
for (const [cssVar, value] of overflowMetricsPx) {
|
|
4138
|
+
viewportEl.style.setProperty(cssVar, `${value}px`);
|
|
4139
|
+
}
|
|
3779
4140
|
if (cornerEl) {
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
4141
|
+
if (scrollbarXHidden || scrollbarYHidden) {
|
|
4142
|
+
setCornerSize({
|
|
4143
|
+
width: 0,
|
|
4144
|
+
height: 0
|
|
4145
|
+
});
|
|
4146
|
+
} else if (!scrollbarXHidden && !scrollbarYHidden) {
|
|
4147
|
+
setCornerSize({
|
|
4148
|
+
width: nextCornerWidth,
|
|
4149
|
+
height: nextCornerHeight
|
|
4150
|
+
});
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
setHiddenState(prevState => mergeHiddenState(prevState, nextHiddenState));
|
|
3790
4154
|
const nextOverflowEdges = {
|
|
3791
4155
|
xStart: !scrollbarXHidden && scrollLeftFromStart > overflowEdgeThreshold.xStart,
|
|
3792
4156
|
xEnd: !scrollbarXHidden && scrollLeftFromEnd > overflowEdgeThreshold.xEnd,
|
|
3793
4157
|
yStart: !scrollbarYHidden && scrollTopFromStart > overflowEdgeThreshold.yStart,
|
|
3794
4158
|
yEnd: !scrollbarYHidden && scrollTopFromEnd > overflowEdgeThreshold.yEnd
|
|
3795
4159
|
};
|
|
3796
|
-
setOverflowEdges(prev =>
|
|
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
|
+
});
|
|
3797
4166
|
});
|
|
3798
4167
|
useIsoLayoutEffect(() => {
|
|
4168
|
+
if (!viewportRef.current) {
|
|
4169
|
+
return;
|
|
4170
|
+
}
|
|
3799
4171
|
removeCSSVariableInheritance();
|
|
3800
|
-
}, []);
|
|
4172
|
+
}, [viewportRef]);
|
|
3801
4173
|
useIsoLayoutEffect(() => {
|
|
3802
4174
|
// Wait for scrollbar and thumb refs after hidden-state toggles, refresh math on direction
|
|
3803
4175
|
// flips, and re-evaluate overflow edges when the threshold changes.
|
|
@@ -3840,10 +4212,6 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
3840
4212
|
if (animations.length === 0) {
|
|
3841
4213
|
return;
|
|
3842
4214
|
}
|
|
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.
|
|
3847
4215
|
Promise.allSettled(animations.map(animation => animation.finished)).then(computeThumbPosition).catch(() => {});
|
|
3848
4216
|
});
|
|
3849
4217
|
return () => {
|
|
@@ -3871,13 +4239,7 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
3871
4239
|
return;
|
|
3872
4240
|
}
|
|
3873
4241
|
computeThumbPosition();
|
|
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) {
|
|
4242
|
+
if (!programmaticScrollRef.current) {
|
|
3881
4243
|
handleScroll({
|
|
3882
4244
|
x: viewportRef.current.scrollLeft,
|
|
3883
4245
|
y: viewportRef.current.scrollTop
|
|
@@ -3895,10 +4257,21 @@ const ScrollAreaViewport = /*#__PURE__*/React.forwardRef(function ScrollAreaView
|
|
|
3895
4257
|
});
|
|
3896
4258
|
},
|
|
3897
4259
|
onWheel: handleUserInteraction,
|
|
4260
|
+
onTouchMove: handleUserInteraction,
|
|
3898
4261
|
onPointerMove: handleUserInteraction,
|
|
3899
4262
|
onPointerEnter: handleUserInteraction,
|
|
3900
4263
|
onKeyDown: handleUserInteraction
|
|
3901
4264
|
};
|
|
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]);
|
|
3902
4275
|
const element = useRenderElement('div', componentProps, {
|
|
3903
4276
|
ref: [forwardedRef, viewportRef],
|
|
3904
4277
|
state: viewportState,
|
|
@@ -3923,37 +4296,11 @@ function getHiddenState(viewport) {
|
|
|
3923
4296
|
corner: y || x
|
|
3924
4297
|
};
|
|
3925
4298
|
}
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
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
|
-
}
|
|
4299
|
+
function mergeHiddenState(prevState, nextState) {
|
|
4300
|
+
if (prevState.y === nextState.y && prevState.x === nextState.x && prevState.corner === nextState.corner) {
|
|
4301
|
+
return prevState;
|
|
3936
4302
|
}
|
|
3937
|
-
return
|
|
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);
|
|
4303
|
+
return nextState;
|
|
3957
4304
|
}
|
|
3958
4305
|
|
|
3959
|
-
export {
|
|
4306
|
+
export { getElementAtPoint as A, findScrollableTouchTarget as B, useScrollAreaRootContext as C, DrawerTrigger as D, getOffset as E, ScrollAreaRootCssVars as F, scrollAreaStateAttributesMapping as G, useScrollAreaViewportContext as H, ScrollAreaRoot as S, DrawerRoot as a, DrawerPortal as b, DrawerBackdrop as c, DrawerViewport as d, DrawerPopup as e, ScrollAreaViewport as f, DrawerContent as g, DialogRoot as h, DialogTrigger as i, DialogPortal as j, DialogViewport as k, useDialogPortalContext as l, DialogStore as m, createInitialPopupStoreState as n, useDrawerProviderContext as o, popupStoreSelectors as p, DrawerBackdropCssVars as q, DrawerPopupCssVars as r, DrawerProviderContext as s, useDrawerRootContext as t, useDialogRootContext as u, useSwipeDismiss as v, getDisplacement as w, DrawerPopupDataAttributes as x, getElementTransform as y, DrawerVirtualKeyboardContext as z };
|