@jobber/components 8.23.0 → 8.23.1
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 +1 -1
- package/dist/Autocomplete/index.mjs +1 -1
- package/dist/Card/index.cjs +4 -4
- package/dist/Card/index.mjs +4 -4
- package/dist/Chip/index.cjs +1 -1
- package/dist/Chip/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
- package/dist/Chips/index.cjs +1 -1
- package/dist/Chips/index.mjs +1 -1
- package/dist/ComboboxChipRemove-cjs.js +668 -379
- package/dist/ComboboxChipRemove-es.js +508 -219
- package/dist/ConfirmationModal/index.cjs +1 -1
- package/dist/ConfirmationModal/index.mjs +1 -1
- package/dist/DataDump/index.cjs +4 -4
- package/dist/DataDump/index.mjs +4 -4
- package/dist/DataList/components/DataListActions/index.cjs +1 -1
- package/dist/DataList/components/DataListActions/index.mjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/index.mjs +1 -1
- package/dist/DataList/components/DataListHeader/index.cjs +1 -1
- package/dist/DataList/components/DataListHeader/index.mjs +1 -1
- package/dist/DataList/components/DataListItem/index.cjs +1 -1
- package/dist/DataList/components/DataListItem/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
- package/dist/DataList/components/DataListItems/index.cjs +1 -1
- package/dist/DataList/components/DataListItems/index.mjs +1 -1
- package/dist/DataList/components/DataListLayout/index.cjs +1 -1
- package/dist/DataList/components/DataListLayout/index.mjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
- package/dist/DataList/index.cjs +1 -1
- package/dist/DataList/index.mjs +1 -1
- package/dist/DatePicker/index.cjs +1 -1
- package/dist/DatePicker/index.mjs +1 -1
- package/dist/Dialog/index.cjs +3 -3
- package/dist/Dialog/index.mjs +3 -3
- package/dist/DrawerDescription-cjs.js +32 -33
- package/dist/DrawerDescription-es.js +19 -20
- package/dist/DrawerTitle-cjs.js +7 -5
- package/dist/DrawerTitle-es.js +7 -5
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
- package/dist/FilterPicker/index.cjs +1 -1
- package/dist/FilterPicker/index.mjs +1 -1
- package/dist/FormFieldWrapper-cjs.js +2 -2
- package/dist/FormFieldWrapper-es.js +2 -2
- package/dist/FormatFile/index.cjs +1 -1
- package/dist/FormatFile/index.mjs +1 -1
- package/dist/Gallery/index.cjs +1 -1
- package/dist/Gallery/index.mjs +1 -1
- package/dist/InputDate/index.cjs +1 -1
- package/dist/InputDate/index.mjs +1 -1
- package/dist/InputNumberExperimental-cjs.js +319 -102
- package/dist/InputNumberExperimental-es.js +321 -104
- package/dist/LightBox/index.cjs +1 -1
- package/dist/LightBox/index.mjs +1 -1
- package/dist/Menu/index.cjs +4 -4
- package/dist/Menu/index.mjs +4 -4
- package/dist/MenuSubmenuTrigger-cjs.js +545 -498
- package/dist/MenuSubmenuTrigger-es.js +490 -443
- package/dist/Modal/index.cjs +1 -1
- package/dist/Modal/index.mjs +1 -1
- package/dist/NumberFieldInput-cjs.js +609 -496
- package/dist/NumberFieldInput-es.js +612 -498
- package/dist/Page/index.cjs +4 -4
- package/dist/Page/index.mjs +4 -4
- package/dist/Popover/index.cjs +1 -1
- package/dist/Popover/index.mjs +1 -1
- package/dist/ScrollAreaViewport-cjs.js +326 -601
- package/dist/ScrollAreaViewport-es.js +278 -548
- package/dist/SelectGroupLabel-cjs.js +494 -528
- package/dist/SelectGroupLabel-es.js +411 -445
- package/dist/Separator-cjs.js +1 -0
- package/dist/Separator-es.js +1 -0
- package/dist/Tooltip/index.cjs +1 -1
- package/dist/Tooltip/index.mjs +1 -1
- package/dist/docs/InputTime/InputTime.md +8 -0
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
- package/dist/floating-ui.react-cjs.js +586 -10
- package/dist/floating-ui.react-dom-cjs.js +92 -92
- package/dist/floating-ui.react-dom-es.js +1 -1
- package/dist/floating-ui.react-es.js +580 -4
- package/dist/floating-ui.utils-cjs.js +159 -0
- package/dist/floating-ui.utils-es.js +136 -0
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
- package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
- package/dist/primitives/BottomSheet/index.cjs +3 -3
- package/dist/primitives/BottomSheet/index.mjs +3 -3
- package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
- package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
- package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
- package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
- package/dist/primitives/SelectPrimitive/index.cjs +5 -5
- package/dist/primitives/SelectPrimitive/index.mjs +5 -5
- package/dist/primitives/index.cjs +4 -4
- package/dist/primitives/index.mjs +4 -4
- package/dist/resolveAriaLabelledBy-cjs.js +5 -0
- package/dist/resolveAriaLabelledBy-es.js +5 -1
- package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
- package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
- package/dist/styles.css +9 -3
- package/dist/unstyledPrimitives/index.cjs +1217 -639
- package/dist/unstyledPrimitives/index.mjs +1163 -585
- package/dist/useButton-cjs.js +283 -317
- package/dist/useButton-es.js +277 -298
- package/dist/useCompositeListItem-cjs.js +328 -279
- package/dist/useCompositeListItem-es.js +293 -246
- package/dist/useLabel-cjs.js +11 -1
- package/dist/useLabel-es.js +11 -2
- package/dist/useLabelableId-cjs.js +37 -91
- package/dist/useLabelableId-es.js +37 -90
- package/dist/useRenderElement-cjs.js +81 -26
- package/dist/useRenderElement-es.js +81 -27
- package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
- package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
- package/package.json +3 -3
- package/dist/index.esm-cjs.js +0 -738
- package/dist/index.esm-es.js +0 -712
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { i as stringifyAsLabel, k as getDefaultLabelId } from '../resolveAriaLabelledBy-es.js';
|
|
2
|
+
import { A as useCoreFilter, B as AriaCombobox, D as useComboboxInputValueContext, e as ComboboxTrigger, d as ComboboxInputGroup, p as ComboboxItem, l as ComboboxArrow, i as ComboboxBackdrop, x as ComboboxClear, v as ComboboxCollection, w as ComboboxEmpty, n as ComboboxGroup, o as ComboboxGroupLabel, m as ComboboxIcon, c as ComboboxInput, f as ComboboxList, k as ComboboxPopup, h as ComboboxPortal, j as ComboboxPositioner, u as ComboboxRow, g as ComboboxStatus, z as useFilteredItems, s as ComboboxChip, t as ComboboxChipRemove, r as ComboboxChips, q as ComboboxItemIndicator, a as ComboboxLabel, C as ComboboxRoot, b as ComboboxValue, y as useComboboxFilter } from '../ComboboxChipRemove-es.js';
|
|
3
|
+
import { a as stringifyAsLabel, l as getDefaultLabelId } from '../resolveAriaLabelledBy-es.js';
|
|
5
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
import { af as pointer, a as useIsoLayoutEffect, b as useStableCallback, o as ownerDocument, q as contains, g as getTarget, h as useTimeout, u as useBaseUiId, d as createChangeEventDetails, ag as swipe, n as useAnimationFrame, B as addEventListener, p as activeElement, ad as TransitionStatusDataAttributes, z as useControlled, V as itemPress, k as useTransitionStatus, f as useOpenChangeComplete, a2 as useAnimationsFinished, ah as scrub, J as createGenericEventDetails, t as transitionStatusMapping } from '../useButton-es.js';
|
|
6
6
|
import { S as Separator } from '../Separator-es.js';
|
|
7
7
|
import { a as DialogBackdrop, d as DialogDescription, f as DialogHandle, c as DialogPopup, b as DialogViewport, e as createDialogHandle, D as DrawerDescription } from '../DrawerDescription-es.js';
|
|
8
8
|
import { c as DialogClose, b as DialogTitle, a as DrawerClose, D as DrawerTitle } from '../DrawerTitle-es.js';
|
|
9
|
-
import { i as DialogPortal, g as DialogRoot, h as DialogTrigger,
|
|
9
|
+
import { i as DialogPortal, g as DialogRoot, h as DialogTrigger, m as useDrawerProviderContext, n as DrawerBackdropCssVars, o as DrawerPopupCssVars, q as DrawerProviderContext, u as useDialogRootContext, r as useDrawerRootContext, s as DrawerPopupDataAttributes, t as useDrawerSnapPoints, v as getSnapPointSwipeMovement, w as DrawerViewportContext, x as DRAWER_CONTENT_ATTRIBUTE, c as DrawerBackdrop, f as DrawerContent, d as DrawerPopup, b as DrawerPortal, a as DrawerRoot, D as DrawerTrigger, y as useScrollAreaRootContext, z as getOffset, A as ScrollAreaRootCssVars, B as scrollAreaStateAttributesMapping, C as useScrollAreaViewportContext, S as ScrollAreaRoot, e as ScrollAreaViewport } from '../ScrollAreaViewport-es.js';
|
|
10
10
|
import { u as useRenderElement, m as mergeProps, f as formatErrorMessage, N as NOOP, E as EMPTY_OBJECT } from '../useRenderElement-es.js';
|
|
11
|
-
import { a as isHTMLElement,
|
|
11
|
+
import { a as isHTMLElement, d as isLastTraversableNode, e as getParentNode, f as getComputedStyle, o as getWindow, b as isElement } from '../floating-ui.utils.dom-es.js';
|
|
12
12
|
import { c as clamp } from '../clamp-es.js';
|
|
13
|
-
import { C as CommonPopupDataAttributes,
|
|
13
|
+
import { C as CommonPopupDataAttributes, aj as useTriggerRegistration, am as BASE_UI_SWIPE_IGNORE_SELECTOR, a0 as isInteractiveElement, p as popupStateMapping, r as useDirection, G as inertValue, X as mergeCleanups, al as gecko, J as webkit, w as useStore, W as triggerOpenStateMapping } from '../useScrollLock-es.js';
|
|
14
14
|
import * as ReactDOM from 'react-dom';
|
|
15
15
|
import { u as useMenuRootContext, o as useMenuPositionerContext, p as useMenuItem, R as REGULAR_ITEM, q as itemMapping, r as MenuStore, h as MenuBackdrop, l as MenuGroup, n as MenuGroupLabel, b as MenuItem, a as MenuLinkItem, j as MenuPopup, g as MenuPortal, i as MenuPositioner, k as MenuRadioGroup, c as MenuRadioItem, d as MenuRadioItemIndicator, f as MenuRoot, m as MenuSubmenuRoot, e as MenuSubmenuTrigger, M as MenuTrigger } from '../MenuSubmenuTrigger-es.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { u as
|
|
16
|
+
import { h as useCompositeListItem } from '../useCompositeListItem-es.js';
|
|
17
|
+
import { u as usePreviousValue, l as useSelectRootContext, s as selectors, m as useSelectPositionerContext, j as SelectGroup, k as SelectGroupLabel, g as SelectItem, i as SelectItemIndicator, h as SelectItemText, f as SelectList, e as SelectPopup, a as SelectPortal, d as SelectPositioner, S as SelectRoot, b as SelectTrigger, c as SelectValue } from '../SelectGroupLabel-es.js';
|
|
18
|
+
import { v as round } from '../floating-ui.utils-es.js';
|
|
19
|
+
import { u as useNumberFieldRootContext, s as stateAttributesMapping$6, d as NumberFieldDecrement, a as NumberFieldGroup, c as NumberFieldIncrement, b as NumberFieldInput, N as NumberFieldRoot } from '../NumberFieldInput-es.js';
|
|
20
20
|
import { b as useFieldRootContext, f as fieldValidityMapping } from '../useLabelableId-es.js';
|
|
21
21
|
import { u as useLabel } from '../useLabel-es.js';
|
|
22
|
-
import '../
|
|
23
|
-
import '../
|
|
22
|
+
import { g as getMaxScrollOffset, n as normalizeScrollOffset, S as SCROLL_EDGE_TOLERANCE_PX } from '../scrollEdges-es.js';
|
|
23
|
+
import '../isElementDisabled-es.js';
|
|
24
24
|
import '../floating-ui.react-dom-es.js';
|
|
25
25
|
|
|
26
26
|
function AutocompleteRoot(props) {
|
|
@@ -56,13 +56,6 @@ function AutocompleteRoot(props) {
|
|
|
56
56
|
} else {
|
|
57
57
|
resolvedInputValue = internalValue;
|
|
58
58
|
}
|
|
59
|
-
const handleValueChange = useStableCallback((nextValue, eventDetails) => {
|
|
60
|
-
setInlineInputValue('');
|
|
61
|
-
if (!isControlled) {
|
|
62
|
-
setInternalValue(nextValue);
|
|
63
|
-
}
|
|
64
|
-
onValueChange?.(nextValue, eventDetails);
|
|
65
|
-
});
|
|
66
59
|
const collator = useCoreFilter();
|
|
67
60
|
const baseFilter = React.useMemo(() => {
|
|
68
61
|
if (other.filter !== undefined) {
|
|
@@ -84,7 +77,14 @@ function AutocompleteRoot(props) {
|
|
|
84
77
|
return baseFilter(item, resolvedQuery, toString);
|
|
85
78
|
};
|
|
86
79
|
}, [baseFilter, mode, resolvedQuery, staticItems]);
|
|
87
|
-
|
|
80
|
+
function handleValueChange(nextValue, eventDetails) {
|
|
81
|
+
setInlineInputValue('');
|
|
82
|
+
if (!isControlled) {
|
|
83
|
+
setInternalValue(nextValue);
|
|
84
|
+
}
|
|
85
|
+
onValueChange?.(nextValue, eventDetails);
|
|
86
|
+
}
|
|
87
|
+
function handleItemHighlighted(highlightedValue, eventDetails) {
|
|
88
88
|
props.onItemHighlighted?.(highlightedValue, eventDetails);
|
|
89
89
|
if (eventDetails.reason === pointer) {
|
|
90
90
|
return;
|
|
@@ -98,7 +98,7 @@ function AutocompleteRoot(props) {
|
|
|
98
98
|
} else {
|
|
99
99
|
setInlineInputValue('');
|
|
100
100
|
}
|
|
101
|
-
}
|
|
101
|
+
}
|
|
102
102
|
return /*#__PURE__*/jsx(AriaCombobox, {
|
|
103
103
|
...other,
|
|
104
104
|
itemToStringLabel: itemToStringValue,
|
|
@@ -132,6 +132,30 @@ function AutocompleteValue(props) {
|
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* A button that opens the popup.
|
|
137
|
+
* Renders a `<button>` element.
|
|
138
|
+
*
|
|
139
|
+
* Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
|
|
140
|
+
*/
|
|
141
|
+
const AutocompleteTrigger = ComboboxTrigger;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A wrapper for the input and its associated controls.
|
|
145
|
+
* Renders a `<div>` element.
|
|
146
|
+
*
|
|
147
|
+
* Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
|
|
148
|
+
*/
|
|
149
|
+
const AutocompleteInputGroup = ComboboxInputGroup;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* An individual item in the list.
|
|
153
|
+
* Renders a `<div>` element.
|
|
154
|
+
*
|
|
155
|
+
* Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
|
|
156
|
+
*/
|
|
157
|
+
const AutocompleteItem = ComboboxItem;
|
|
158
|
+
|
|
135
159
|
var index_parts$7 = /*#__PURE__*/Object.freeze({
|
|
136
160
|
__proto__: null,
|
|
137
161
|
Arrow: ComboboxArrow,
|
|
@@ -143,8 +167,8 @@ var index_parts$7 = /*#__PURE__*/Object.freeze({
|
|
|
143
167
|
GroupLabel: ComboboxGroupLabel,
|
|
144
168
|
Icon: ComboboxIcon,
|
|
145
169
|
Input: ComboboxInput,
|
|
146
|
-
InputGroup:
|
|
147
|
-
Item:
|
|
170
|
+
InputGroup: AutocompleteInputGroup,
|
|
171
|
+
Item: AutocompleteItem,
|
|
148
172
|
List: ComboboxList,
|
|
149
173
|
Popup: ComboboxPopup,
|
|
150
174
|
Portal: ComboboxPortal,
|
|
@@ -153,7 +177,7 @@ var index_parts$7 = /*#__PURE__*/Object.freeze({
|
|
|
153
177
|
Row: ComboboxRow,
|
|
154
178
|
Separator: Separator,
|
|
155
179
|
Status: ComboboxStatus,
|
|
156
|
-
Trigger:
|
|
180
|
+
Trigger: AutocompleteTrigger,
|
|
157
181
|
Value: AutocompleteValue,
|
|
158
182
|
useFilter: useCoreFilter,
|
|
159
183
|
useFilteredItems: useFilteredItems
|
|
@@ -198,6 +222,7 @@ const DrawerIndent = /*#__PURE__*/React.forwardRef(function DrawerIndent(compone
|
|
|
198
222
|
const {
|
|
199
223
|
render,
|
|
200
224
|
className,
|
|
225
|
+
style,
|
|
201
226
|
...elementProps
|
|
202
227
|
} = componentProps;
|
|
203
228
|
const providerContext = useDrawerProviderContext();
|
|
@@ -272,6 +297,7 @@ const DrawerIndentBackground = /*#__PURE__*/React.forwardRef(function DrawerInde
|
|
|
272
297
|
const {
|
|
273
298
|
render,
|
|
274
299
|
className,
|
|
300
|
+
style,
|
|
275
301
|
...elementProps
|
|
276
302
|
} = componentProps;
|
|
277
303
|
const providerContext = useDrawerProviderContext();
|
|
@@ -371,36 +397,49 @@ function createVisualStateStore() {
|
|
|
371
397
|
};
|
|
372
398
|
}
|
|
373
399
|
|
|
374
|
-
function isScrollable(element, axis
|
|
400
|
+
function isScrollable(element, axis,
|
|
401
|
+
// When true, a container that overflows only once extra space is added (e.g. drawer
|
|
402
|
+
// keyboard scroll slack) still counts, as long as it has layout size on the axis.
|
|
403
|
+
allowOverflowIntent = false) {
|
|
375
404
|
const style = getComputedStyle(element);
|
|
376
405
|
if (axis === 'vertical') {
|
|
377
406
|
const overflowY = style.overflowY;
|
|
378
|
-
|
|
407
|
+
if (overflowY !== 'auto' && overflowY !== 'scroll') {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
return allowOverflowIntent ? element.clientHeight > 0 : element.scrollHeight > element.clientHeight;
|
|
379
411
|
}
|
|
380
412
|
const overflowX = style.overflowX;
|
|
381
|
-
|
|
413
|
+
if (overflowX !== 'auto' && overflowX !== 'scroll') {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
return allowOverflowIntent ? element.clientWidth > 0 : element.scrollWidth > element.clientWidth;
|
|
382
417
|
}
|
|
383
418
|
function hasScrollableAncestor(target, root, axes) {
|
|
419
|
+
// `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
|
|
420
|
+
// still walks up to scrollable ancestors in the light DOM.
|
|
384
421
|
let node = target;
|
|
385
|
-
while (node && node !== root) {
|
|
422
|
+
while (isHTMLElement(node) && node !== root && !isLastTraversableNode(node)) {
|
|
386
423
|
for (const axis of axes) {
|
|
387
424
|
if (isScrollable(node, axis)) {
|
|
388
425
|
return true;
|
|
389
426
|
}
|
|
390
427
|
}
|
|
391
|
-
node = node
|
|
428
|
+
node = getParentNode(node);
|
|
392
429
|
}
|
|
393
430
|
return false;
|
|
394
431
|
}
|
|
395
|
-
function findScrollableTouchTarget(target, root, axis = 'vertical') {
|
|
432
|
+
function findScrollableTouchTarget(target, root, axis = 'vertical', allowOverflowIntent = false) {
|
|
433
|
+
// `getParentNode` crosses shadow boundaries (and slots), so a target inside a shadow root
|
|
434
|
+
// still reaches a scrollable ancestor in the light DOM.
|
|
396
435
|
let node = isHTMLElement(target) ? target : null;
|
|
397
|
-
while (node && node !== root) {
|
|
398
|
-
if (isScrollable(node, axis)) {
|
|
436
|
+
while (isHTMLElement(node) && node !== root && !isLastTraversableNode(node)) {
|
|
437
|
+
if (isScrollable(node, axis, allowOverflowIntent)) {
|
|
399
438
|
return node;
|
|
400
439
|
}
|
|
401
|
-
node = node
|
|
440
|
+
node = getParentNode(node);
|
|
402
441
|
}
|
|
403
|
-
return isScrollable(root, axis) ? root : null;
|
|
442
|
+
return isScrollable(root, axis, allowOverflowIntent) ? root : null;
|
|
404
443
|
}
|
|
405
444
|
|
|
406
445
|
function getElementAtPoint(doc, x, y) {
|
|
@@ -429,7 +468,7 @@ function getDisplacement(direction, deltaX, deltaY) {
|
|
|
429
468
|
}
|
|
430
469
|
}
|
|
431
470
|
function getElementTransform(element) {
|
|
432
|
-
const computedStyle =
|
|
471
|
+
const computedStyle = getWindow(element).getComputedStyle(element);
|
|
433
472
|
const transform = computedStyle.transform;
|
|
434
473
|
let translateX = 0;
|
|
435
474
|
let translateY = 0;
|
|
@@ -458,6 +497,9 @@ function getElementTransform(element) {
|
|
|
458
497
|
function getValidTimeStamp(timeStamp) {
|
|
459
498
|
return Number.isFinite(timeStamp) && timeStamp > 0 ? timeStamp : null;
|
|
460
499
|
}
|
|
500
|
+
function getDragTransform(dragOffset, scale) {
|
|
501
|
+
return `translate3d(${dragOffset.x}px,${dragOffset.y}px,0) scale(${scale})`;
|
|
502
|
+
}
|
|
461
503
|
function hasPrimaryMouseButton(buttons) {
|
|
462
504
|
return buttons % 2 === 1;
|
|
463
505
|
}
|
|
@@ -487,6 +529,7 @@ function useSwipeDismiss(options) {
|
|
|
487
529
|
swipeThreshold: swipeThresholdProp,
|
|
488
530
|
onDismiss,
|
|
489
531
|
onProgress,
|
|
532
|
+
onCancel,
|
|
490
533
|
onSwipeStart,
|
|
491
534
|
onRelease,
|
|
492
535
|
onSwipingChange,
|
|
@@ -513,18 +556,7 @@ function useSwipeDismiss(options) {
|
|
|
513
556
|
}, [hasHorizontal, hasVertical]);
|
|
514
557
|
const [currentSwipeDirection, setCurrentSwipeDirection] = React.useState(undefined);
|
|
515
558
|
const [isSwiping, setIsSwiping] = React.useState(false);
|
|
516
|
-
const [isRealSwipe, setIsRealSwipe] = React.useState(false);
|
|
517
559
|
const [dragDismissed, setDragDismissed] = React.useState(false);
|
|
518
|
-
const [dragOffset, setDragOffset] = React.useState({
|
|
519
|
-
x: 0,
|
|
520
|
-
y: 0
|
|
521
|
-
});
|
|
522
|
-
const [initialTransform, setInitialTransform] = React.useState({
|
|
523
|
-
x: 0,
|
|
524
|
-
y: 0,
|
|
525
|
-
scale: 1
|
|
526
|
-
});
|
|
527
|
-
const [lockedDirection, setLockedDirection] = React.useState(null);
|
|
528
560
|
const dragStartPosRef = React.useRef({
|
|
529
561
|
x: 0,
|
|
530
562
|
y: 0
|
|
@@ -546,6 +578,7 @@ function useSwipeDismiss(options) {
|
|
|
546
578
|
x: 0,
|
|
547
579
|
y: 0
|
|
548
580
|
});
|
|
581
|
+
const lockedDirectionRef = React.useRef(null);
|
|
549
582
|
const isFirstPointerMoveRef = React.useRef(false);
|
|
550
583
|
const pendingSwipeRef = React.useRef(false);
|
|
551
584
|
const pendingSwipeStartPosRef = React.useRef(null);
|
|
@@ -565,6 +598,7 @@ function useSwipeDismiss(options) {
|
|
|
565
598
|
});
|
|
566
599
|
const lastProgressDetailsRef = React.useRef(null);
|
|
567
600
|
const isSwipingRef = React.useRef(false);
|
|
601
|
+
const dragStyleSnapshotRef = React.useRef(null);
|
|
568
602
|
const setSwiping = useStableCallback(nextSwiping => {
|
|
569
603
|
if (isSwipingRef.current === nextSwiping) {
|
|
570
604
|
return;
|
|
@@ -610,6 +644,35 @@ function useSwipeDismiss(options) {
|
|
|
610
644
|
}
|
|
611
645
|
onProgress?.(nextProgress, details);
|
|
612
646
|
});
|
|
647
|
+
const syncDragStyles = useStableCallback(swiping => {
|
|
648
|
+
const element = elementRef.current;
|
|
649
|
+
if (!trackDrag || !element) {
|
|
650
|
+
if (!swiping) {
|
|
651
|
+
dragStyleSnapshotRef.current = null;
|
|
652
|
+
}
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const style = element.style;
|
|
656
|
+
const dragStyleSnapshot = dragStyleSnapshotRef.current;
|
|
657
|
+
if (swiping) {
|
|
658
|
+
if (!dragStyleSnapshot) {
|
|
659
|
+
dragStyleSnapshotRef.current = [style.transition, style.transform];
|
|
660
|
+
}
|
|
661
|
+
style.transition = 'none';
|
|
662
|
+
} else if (dragStyleSnapshot) {
|
|
663
|
+
[style.transition, style.transform] = dragStyleSnapshot;
|
|
664
|
+
dragStyleSnapshotRef.current = null;
|
|
665
|
+
}
|
|
666
|
+
const dragOffset = dragOffsetRef.current;
|
|
667
|
+
const initialTransform = initialTransformRef.current;
|
|
668
|
+
const deltaX = dragOffset.x - initialTransform.x;
|
|
669
|
+
const deltaY = dragOffset.y - initialTransform.y;
|
|
670
|
+
if (swiping) {
|
|
671
|
+
style.transform = getDragTransform(dragOffset, initialTransform.scale);
|
|
672
|
+
}
|
|
673
|
+
style.setProperty(movementCssVars.x, `${deltaX}px`);
|
|
674
|
+
style.setProperty(movementCssVars.y, `${deltaY}px`);
|
|
675
|
+
});
|
|
613
676
|
function recordDragSample(offset, timeStamp) {
|
|
614
677
|
if (timeStamp === null) {
|
|
615
678
|
return;
|
|
@@ -631,18 +694,7 @@ function useSwipeDismiss(options) {
|
|
|
631
694
|
const reset = React.useCallback(() => {
|
|
632
695
|
setCurrentSwipeDirection(undefined);
|
|
633
696
|
setSwiping(false);
|
|
634
|
-
setIsRealSwipe(false);
|
|
635
697
|
setDragDismissed(false);
|
|
636
|
-
setDragOffset({
|
|
637
|
-
x: 0,
|
|
638
|
-
y: 0
|
|
639
|
-
});
|
|
640
|
-
setInitialTransform({
|
|
641
|
-
x: 0,
|
|
642
|
-
y: 0,
|
|
643
|
-
scale: 1
|
|
644
|
-
});
|
|
645
|
-
setLockedDirection(null);
|
|
646
698
|
updateSwipeProgress(0);
|
|
647
699
|
swipeThresholdRef.current = swipeThresholdDefault;
|
|
648
700
|
dragStartPosRef.current = {
|
|
@@ -665,6 +717,7 @@ function useSwipeDismiss(options) {
|
|
|
665
717
|
x: 0,
|
|
666
718
|
y: 0
|
|
667
719
|
};
|
|
720
|
+
lockedDirectionRef.current = null;
|
|
668
721
|
isFirstPointerMoveRef.current = false;
|
|
669
722
|
lastMovePosRef.current = null;
|
|
670
723
|
pendingSwipeRef.current = false;
|
|
@@ -682,7 +735,8 @@ function useSwipeDismiss(options) {
|
|
|
682
735
|
y: 0
|
|
683
736
|
};
|
|
684
737
|
lastProgressDetailsRef.current = null;
|
|
685
|
-
|
|
738
|
+
syncDragStyles(false);
|
|
739
|
+
}, [setSwiping, swipeThresholdDefault, syncDragStyles, updateSwipeProgress]);
|
|
686
740
|
React.useEffect(() => {
|
|
687
741
|
if (typeof swipeThresholdProp !== 'function') {
|
|
688
742
|
swipeThresholdRef.current = swipeThresholdDefault;
|
|
@@ -764,11 +818,6 @@ function useSwipeDismiss(options) {
|
|
|
764
818
|
x: transform.x,
|
|
765
819
|
y: transform.y
|
|
766
820
|
};
|
|
767
|
-
setInitialTransform(transform);
|
|
768
|
-
setDragOffset({
|
|
769
|
-
x: transform.x,
|
|
770
|
-
y: transform.y
|
|
771
|
-
});
|
|
772
821
|
recordDragSample({
|
|
773
822
|
x: transform.x,
|
|
774
823
|
y: transform.y
|
|
@@ -779,10 +828,10 @@ function useSwipeDismiss(options) {
|
|
|
779
828
|
}
|
|
780
829
|
onSwipeStart?.(event.nativeEvent);
|
|
781
830
|
setSwiping(true);
|
|
782
|
-
|
|
783
|
-
setLockedDirection(null);
|
|
831
|
+
lockedDirectionRef.current = null;
|
|
784
832
|
isFirstPointerMoveRef.current = true;
|
|
785
833
|
updateSwipeProgress(0);
|
|
834
|
+
syncDragStyles(true);
|
|
786
835
|
return true;
|
|
787
836
|
}
|
|
788
837
|
function resetPendingSwipeState() {
|
|
@@ -800,19 +849,15 @@ function useSwipeDismiss(options) {
|
|
|
800
849
|
return;
|
|
801
850
|
}
|
|
802
851
|
setSwiping(false);
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
const resolvedInitialTransform = trackDrag ? initialTransform : initialTransformRef.current;
|
|
852
|
+
lockedDirectionRef.current = null;
|
|
853
|
+
const resolvedInitialTransform = initialTransformRef.current;
|
|
806
854
|
dragOffsetRef.current = {
|
|
807
855
|
x: resolvedInitialTransform.x,
|
|
808
856
|
y: resolvedInitialTransform.y
|
|
809
857
|
};
|
|
810
|
-
setDragOffset({
|
|
811
|
-
x: resolvedInitialTransform.x,
|
|
812
|
-
y: resolvedInitialTransform.y
|
|
813
|
-
});
|
|
814
858
|
setCurrentSwipeDirection(undefined);
|
|
815
859
|
sawPrimaryButtonsOnMoveRef.current = false;
|
|
860
|
+
syncDragStyles(false);
|
|
816
861
|
const element = elementRef.current;
|
|
817
862
|
if (element) {
|
|
818
863
|
safelyChangePointerCapture(element, event.pointerId, 'releasePointerCapture');
|
|
@@ -822,6 +867,7 @@ function useSwipeDismiss(options) {
|
|
|
822
867
|
deltaY: 0,
|
|
823
868
|
direction: undefined
|
|
824
869
|
});
|
|
870
|
+
onCancel?.(event.nativeEvent);
|
|
825
871
|
}
|
|
826
872
|
function applyDirectionalDamping(deltaX, deltaY) {
|
|
827
873
|
const exponent = value => value >= 0 ? value ** 0.5 : -(Math.abs(value) ** 0.5);
|
|
@@ -942,17 +988,12 @@ function useSwipeDismiss(options) {
|
|
|
942
988
|
const deltaY = clientY - dragStartPosRef.current.y;
|
|
943
989
|
const cancelDeltaY = clientY - swipeCancelBaselineRef.current.y;
|
|
944
990
|
const cancelDeltaX = clientX - swipeCancelBaselineRef.current.x;
|
|
945
|
-
|
|
991
|
+
let lockedDirection = lockedDirectionRef.current;
|
|
992
|
+
if (lockedDirection === null && hasHorizontal && hasVertical) {
|
|
946
993
|
const movementDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
947
994
|
if (movementDistance >= MIN_DRAG_THRESHOLD) {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
if (hasHorizontal && hasVertical) {
|
|
951
|
-
const absX = Math.abs(deltaX);
|
|
952
|
-
const absY = Math.abs(deltaY);
|
|
953
|
-
setLockedDirection(absX > absY ? 'horizontal' : 'vertical');
|
|
954
|
-
}
|
|
955
|
-
}
|
|
995
|
+
lockedDirection = Math.abs(deltaX) > Math.abs(deltaY) ? 'horizontal' : 'vertical';
|
|
996
|
+
lockedDirectionRef.current = lockedDirection;
|
|
956
997
|
}
|
|
957
998
|
}
|
|
958
999
|
let candidate;
|
|
@@ -1017,12 +1058,7 @@ function useSwipeDismiss(options) {
|
|
|
1017
1058
|
x: newOffsetX,
|
|
1018
1059
|
y: newOffsetY
|
|
1019
1060
|
};
|
|
1020
|
-
|
|
1021
|
-
setDragOffset({
|
|
1022
|
-
x: newOffsetX,
|
|
1023
|
-
y: newOffsetY
|
|
1024
|
-
});
|
|
1025
|
-
}
|
|
1061
|
+
syncDragStyles(true);
|
|
1026
1062
|
recordDragSample({
|
|
1027
1063
|
x: newOffsetX,
|
|
1028
1064
|
y: newOffsetY
|
|
@@ -1064,91 +1100,6 @@ function useSwipeDismiss(options) {
|
|
|
1064
1100
|
direction: swipeDirectionDetails
|
|
1065
1101
|
});
|
|
1066
1102
|
}
|
|
1067
|
-
const handleMove = useStableCallback(event => {
|
|
1068
|
-
const currentPos = getPrimaryPointerPosition(event);
|
|
1069
|
-
if (!currentPos) {
|
|
1070
|
-
return;
|
|
1071
|
-
}
|
|
1072
|
-
if (!('touches' in event)) {
|
|
1073
|
-
const hasPrimaryButton = hasPrimaryMouseButton(event.buttons);
|
|
1074
|
-
if (hasPrimaryButton) {
|
|
1075
|
-
sawPrimaryButtonsOnMoveRef.current = true;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
// Cancel the swipe if a non-primary button takes over the interaction.
|
|
1079
|
-
// This handles cases where a right-click interrupts dragging.
|
|
1080
|
-
const lostPrimaryButtonDuringSwipe = event.buttons === 0 && sawPrimaryButtonsOnMoveRef.current;
|
|
1081
|
-
if (event.buttons !== 0 && !hasPrimaryButton || lostPrimaryButtonDuringSwipe) {
|
|
1082
|
-
cancelSwipeInteraction(event);
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
if (!isSwiping && pendingSwipeRef.current) {
|
|
1087
|
-
if (!isTouchLikeEvent(event) && (event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
|
|
1088
|
-
resetPendingSwipeState();
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
const allowedToStart = canStart ? canStart(currentPos, {
|
|
1092
|
-
nativeEvent: event.nativeEvent,
|
|
1093
|
-
direction: primaryDirection
|
|
1094
|
-
}) : true;
|
|
1095
|
-
if (allowedToStart) {
|
|
1096
|
-
const pendingStartPos = pendingSwipeStartPosRef.current;
|
|
1097
|
-
let ignoreScrollableOnStart = false;
|
|
1098
|
-
if (isTouchLikeEvent(event)) {
|
|
1099
|
-
const element = elementRef.current;
|
|
1100
|
-
if (pendingStartPos && element) {
|
|
1101
|
-
const target = getTargetAtPoint(currentPos, event.nativeEvent);
|
|
1102
|
-
const doc = ownerDocument(element);
|
|
1103
|
-
const body = doc.body;
|
|
1104
|
-
const scrollTarget = body ? findGestureScrollableTouchTarget(target, body) : null;
|
|
1105
|
-
if (scrollTarget && (contains(element, scrollTarget) || contains(scrollTarget, element))) {
|
|
1106
|
-
const deltaX = currentPos.x - pendingStartPos.x;
|
|
1107
|
-
const deltaY = currentPos.y - pendingStartPos.y;
|
|
1108
|
-
const canSwipeFromEdge = canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY);
|
|
1109
|
-
if (canSwipeFromEdge === false) {
|
|
1110
|
-
return;
|
|
1111
|
-
}
|
|
1112
|
-
if (canSwipeFromEdge === true) {
|
|
1113
|
-
ignoreScrollableOnStart = true;
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
const started = startSwipeAtPosition(event, currentPos, {
|
|
1119
|
-
ignoreScrollableTarget: ignoreScrollableOnStart,
|
|
1120
|
-
ignoreScrollableAncestors: ignoreScrollableOnStart
|
|
1121
|
-
});
|
|
1122
|
-
if (started) {
|
|
1123
|
-
if (pendingStartPos && ignoreScrollableOnStart) {
|
|
1124
|
-
// Preserve displacement between touchstart and the move that activates swipe from
|
|
1125
|
-
// a scroll-edge so quick flicks can dismiss.
|
|
1126
|
-
clearPendingSwipeStartState();
|
|
1127
|
-
dragStartPosRef.current = pendingStartPos;
|
|
1128
|
-
swipeCancelBaselineRef.current = pendingStartPos;
|
|
1129
|
-
lastMovePosRef.current = pendingStartPos;
|
|
1130
|
-
isFirstPointerMoveRef.current = false;
|
|
1131
|
-
} else {
|
|
1132
|
-
// Start from the current in-bounds position without dropping follow-up move
|
|
1133
|
-
// displacement; this avoids jumps when entering from outside the element while
|
|
1134
|
-
// keeping swipe tracking responsive on the next move.
|
|
1135
|
-
clearPendingSwipeStartState();
|
|
1136
|
-
swipeFromScrollableRef.current = false;
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
const previousPos = lastMovePosRef.current;
|
|
1142
|
-
const movement = previousPos === null ? {
|
|
1143
|
-
x: 0,
|
|
1144
|
-
y: 0
|
|
1145
|
-
} : {
|
|
1146
|
-
x: currentPos.x - previousPos.x,
|
|
1147
|
-
y: currentPos.y - previousPos.y
|
|
1148
|
-
};
|
|
1149
|
-
lastMovePosRef.current = currentPos;
|
|
1150
|
-
handleMoveCore(event, currentPos, movement);
|
|
1151
|
-
});
|
|
1152
1103
|
const handleEnd = useStableCallback(event => {
|
|
1153
1104
|
if (!enabled) {
|
|
1154
1105
|
return;
|
|
@@ -1160,7 +1111,7 @@ function useSwipeDismiss(options) {
|
|
|
1160
1111
|
const progressDetails = {
|
|
1161
1112
|
deltaX: releaseDeltaX,
|
|
1162
1113
|
deltaY: releaseDeltaY,
|
|
1163
|
-
direction:
|
|
1114
|
+
direction: intendedSwipeDirectionRef.current
|
|
1164
1115
|
};
|
|
1165
1116
|
if (!isSwipingRef.current) {
|
|
1166
1117
|
resetPendingSwipeState();
|
|
@@ -1168,8 +1119,7 @@ function useSwipeDismiss(options) {
|
|
|
1168
1119
|
return;
|
|
1169
1120
|
}
|
|
1170
1121
|
setSwiping(false);
|
|
1171
|
-
|
|
1172
|
-
setLockedDirection(null);
|
|
1122
|
+
lockedDirectionRef.current = null;
|
|
1173
1123
|
resetPendingSwipeState();
|
|
1174
1124
|
sawPrimaryButtonsOnMoveRef.current = false;
|
|
1175
1125
|
const element = elementRef.current;
|
|
@@ -1210,7 +1160,7 @@ function useSwipeDismiss(options) {
|
|
|
1210
1160
|
}
|
|
1211
1161
|
const releaseDecision = onRelease?.({
|
|
1212
1162
|
event: event.nativeEvent,
|
|
1213
|
-
direction:
|
|
1163
|
+
direction: intendedSwipeDirectionRef.current,
|
|
1214
1164
|
deltaX,
|
|
1215
1165
|
deltaY,
|
|
1216
1166
|
velocityX,
|
|
@@ -1224,11 +1174,8 @@ function useSwipeDismiss(options) {
|
|
|
1224
1174
|
x: resolvedInitialTransform.x,
|
|
1225
1175
|
y: resolvedInitialTransform.y
|
|
1226
1176
|
};
|
|
1227
|
-
setDragOffset({
|
|
1228
|
-
x: resolvedInitialTransform.x,
|
|
1229
|
-
y: resolvedInitialTransform.y
|
|
1230
|
-
});
|
|
1231
1177
|
setCurrentSwipeDirection(undefined);
|
|
1178
|
+
syncDragStyles(false);
|
|
1232
1179
|
updateSwipeProgress(0, progressDetails);
|
|
1233
1180
|
return;
|
|
1234
1181
|
}
|
|
@@ -1236,7 +1183,7 @@ function useSwipeDismiss(options) {
|
|
|
1236
1183
|
let dismissDirection;
|
|
1237
1184
|
if (hasReleaseDecision) {
|
|
1238
1185
|
shouldClose = releaseDecision;
|
|
1239
|
-
dismissDirection =
|
|
1186
|
+
dismissDirection = intendedSwipeDirectionRef.current ?? primaryDirection;
|
|
1240
1187
|
} else {
|
|
1241
1188
|
for (const direction of directions) {
|
|
1242
1189
|
switch (direction) {
|
|
@@ -1273,6 +1220,7 @@ function useSwipeDismiss(options) {
|
|
|
1273
1220
|
if (shouldClose && dismissDirection) {
|
|
1274
1221
|
setCurrentSwipeDirection(dismissDirection);
|
|
1275
1222
|
setDragDismissed(true);
|
|
1223
|
+
syncDragStyles(false);
|
|
1276
1224
|
onDismiss?.(event.nativeEvent, {
|
|
1277
1225
|
direction: dismissDirection
|
|
1278
1226
|
});
|
|
@@ -1281,34 +1229,150 @@ function useSwipeDismiss(options) {
|
|
|
1281
1229
|
x: resolvedInitialTransform.x,
|
|
1282
1230
|
y: resolvedInitialTransform.y
|
|
1283
1231
|
};
|
|
1284
|
-
setDragOffset({
|
|
1285
|
-
x: resolvedInitialTransform.x,
|
|
1286
|
-
y: resolvedInitialTransform.y
|
|
1287
|
-
});
|
|
1288
1232
|
setCurrentSwipeDirection(undefined);
|
|
1233
|
+
syncDragStyles(false);
|
|
1289
1234
|
updateSwipeProgress(0, progressDetails);
|
|
1290
1235
|
}
|
|
1291
1236
|
});
|
|
1237
|
+
const handleMove = useStableCallback(event => {
|
|
1238
|
+
const currentPos = getPrimaryPointerPosition(event);
|
|
1239
|
+
if (!currentPos) {
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
let endAfterMove = false;
|
|
1243
|
+
if (!('touches' in event)) {
|
|
1244
|
+
const hasPrimaryButton = hasPrimaryMouseButton(event.buttons);
|
|
1245
|
+
if (hasPrimaryButton) {
|
|
1246
|
+
sawPrimaryButtonsOnMoveRef.current = true;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Cancel the swipe if a non-primary button takes over the interaction.
|
|
1250
|
+
// This handles cases where a right-click interrupts dragging.
|
|
1251
|
+
if (event.buttons !== 0 && !hasPrimaryButton) {
|
|
1252
|
+
cancelSwipeInteraction(event);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// A `buttons: 0` pointermove means the primary button was already released, so the gesture is
|
|
1257
|
+
// over even if no pointerup reached us. On fast trackpad flicks this trailing move is
|
|
1258
|
+
// dispatched just before pointerup; treat it as the release (mirroring touchend) instead of
|
|
1259
|
+
// cancelling and snapping the element back.
|
|
1260
|
+
if (event.buttons === 0 && sawPrimaryButtonsOnMoveRef.current) {
|
|
1261
|
+
if (!isSwipingRef.current) {
|
|
1262
|
+
// The gesture never activated — discard it.
|
|
1263
|
+
handleEnd(event);
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
// This release move can itself carry the threshold-crossing displacement (and the peak
|
|
1267
|
+
// release velocity), so let it flow through `handleMoveCore` below to update the drag
|
|
1268
|
+
// offset / velocity sample, then commit the release afterwards.
|
|
1269
|
+
endAfterMove = true;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
if (!isSwiping && pendingSwipeRef.current) {
|
|
1273
|
+
if (!isTouchLikeEvent(event) && (event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
|
|
1274
|
+
resetPendingSwipeState();
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const allowedToStart = canStart ? canStart(currentPos, {
|
|
1278
|
+
nativeEvent: event.nativeEvent,
|
|
1279
|
+
direction: primaryDirection
|
|
1280
|
+
}) : true;
|
|
1281
|
+
if (allowedToStart) {
|
|
1282
|
+
const pendingStartPos = pendingSwipeStartPosRef.current;
|
|
1283
|
+
let ignoreScrollableOnStart = false;
|
|
1284
|
+
if (isTouchLikeEvent(event)) {
|
|
1285
|
+
const element = elementRef.current;
|
|
1286
|
+
if (pendingStartPos && element) {
|
|
1287
|
+
const target = getTargetAtPoint(currentPos, event.nativeEvent);
|
|
1288
|
+
const doc = ownerDocument(element);
|
|
1289
|
+
const body = doc.body;
|
|
1290
|
+
const scrollTarget = body ? findGestureScrollableTouchTarget(target, body) : null;
|
|
1291
|
+
if (scrollTarget && (contains(element, scrollTarget) || contains(scrollTarget, element))) {
|
|
1292
|
+
const deltaX = currentPos.x - pendingStartPos.x;
|
|
1293
|
+
const deltaY = currentPos.y - pendingStartPos.y;
|
|
1294
|
+
const canSwipeFromEdge = canSwipeFromScrollEdgeOnPendingMove(scrollTarget, deltaX, deltaY);
|
|
1295
|
+
if (canSwipeFromEdge === false) {
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
if (canSwipeFromEdge === true) {
|
|
1299
|
+
ignoreScrollableOnStart = true;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
const started = startSwipeAtPosition(event, currentPos, {
|
|
1305
|
+
ignoreScrollableTarget: ignoreScrollableOnStart,
|
|
1306
|
+
ignoreScrollableAncestors: ignoreScrollableOnStart
|
|
1307
|
+
});
|
|
1308
|
+
if (started) {
|
|
1309
|
+
if (pendingStartPos && ignoreScrollableOnStart) {
|
|
1310
|
+
// Preserve displacement between touchstart and the move that activates swipe from
|
|
1311
|
+
// a scroll-edge so quick flicks can dismiss.
|
|
1312
|
+
clearPendingSwipeStartState();
|
|
1313
|
+
dragStartPosRef.current = pendingStartPos;
|
|
1314
|
+
swipeCancelBaselineRef.current = pendingStartPos;
|
|
1315
|
+
lastMovePosRef.current = pendingStartPos;
|
|
1316
|
+
isFirstPointerMoveRef.current = false;
|
|
1317
|
+
} else {
|
|
1318
|
+
// Start from the current in-bounds position without dropping follow-up move
|
|
1319
|
+
// displacement; this avoids jumps when entering from outside the element while
|
|
1320
|
+
// keeping swipe tracking responsive on the next move.
|
|
1321
|
+
clearPendingSwipeStartState();
|
|
1322
|
+
swipeFromScrollableRef.current = false;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
const previousPos = lastMovePosRef.current;
|
|
1328
|
+
const movement = previousPos === null ? {
|
|
1329
|
+
x: 0,
|
|
1330
|
+
y: 0
|
|
1331
|
+
} : {
|
|
1332
|
+
x: currentPos.x - previousPos.x,
|
|
1333
|
+
y: currentPos.y - previousPos.y
|
|
1334
|
+
};
|
|
1335
|
+
lastMovePosRef.current = currentPos;
|
|
1336
|
+
handleMoveCore(event, currentPos, movement);
|
|
1337
|
+
|
|
1338
|
+
// `endAfterMove` is only set in the non-touch branch above; the `'touches'` guard re-narrows the
|
|
1339
|
+
// event type for `handleEnd` after the shared move handling has run.
|
|
1340
|
+
if (endAfterMove && !('touches' in event)) {
|
|
1341
|
+
handleEnd(event);
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
// Feeds a native touchmove into the swipe pipeline. Used by consumers that claim the gesture
|
|
1346
|
+
// in a capture-phase listener and stop it from reaching React's delegated touch handlers.
|
|
1347
|
+
const moveNative = useStableCallback((nativeEvent, currentTarget) => {
|
|
1348
|
+
handleMove({
|
|
1349
|
+
touches: nativeEvent.touches,
|
|
1350
|
+
currentTarget,
|
|
1351
|
+
nativeEvent,
|
|
1352
|
+
defaultPrevented: nativeEvent.defaultPrevented,
|
|
1353
|
+
timeStamp: nativeEvent.timeStamp
|
|
1354
|
+
});
|
|
1355
|
+
});
|
|
1292
1356
|
const getDragStyles = React.useCallback(() => {
|
|
1293
|
-
const
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1357
|
+
const dragOffset = dragOffsetRef.current;
|
|
1358
|
+
const initialTransform = initialTransformRef.current;
|
|
1359
|
+
const deltaX = dragOffset.x - initialTransform.x;
|
|
1360
|
+
const deltaY = dragOffset.y - initialTransform.y;
|
|
1361
|
+
if (!isSwiping && deltaX === 0 && deltaY === 0 && !dragDismissed) {
|
|
1296
1362
|
return {
|
|
1297
1363
|
[movementCssVars.x]: '0px',
|
|
1298
1364
|
[movementCssVars.y]: '0px'
|
|
1299
1365
|
};
|
|
1300
1366
|
}
|
|
1301
|
-
const deltaX = resolvedDragOffset.x - resolvedInitialTransform.x;
|
|
1302
|
-
const deltaY = resolvedDragOffset.y - resolvedInitialTransform.y;
|
|
1303
1367
|
return {
|
|
1304
1368
|
transition: isSwiping ? 'none' : undefined,
|
|
1305
1369
|
// While swiping, freeze the element at its current visual transform so it doesn't snap to the
|
|
1306
1370
|
// end position.
|
|
1307
|
-
transform: isSwiping ?
|
|
1371
|
+
transform: isSwiping ? getDragTransform(dragOffset, initialTransform.scale) : undefined,
|
|
1308
1372
|
[movementCssVars.x]: `${deltaX}px`,
|
|
1309
1373
|
[movementCssVars.y]: `${deltaY}px`
|
|
1310
1374
|
};
|
|
1311
|
-
}, [dragDismissed,
|
|
1375
|
+
}, [dragDismissed, isSwiping, movementCssVars]);
|
|
1312
1376
|
const getPointerProps = React.useCallback(() => {
|
|
1313
1377
|
if (!enabled) {
|
|
1314
1378
|
return {};
|
|
@@ -1337,6 +1401,7 @@ function useSwipeDismiss(options) {
|
|
|
1337
1401
|
dragDismissed,
|
|
1338
1402
|
getPointerProps,
|
|
1339
1403
|
getTouchProps,
|
|
1404
|
+
moveNative,
|
|
1340
1405
|
getDragStyles,
|
|
1341
1406
|
reset
|
|
1342
1407
|
};
|
|
@@ -1417,8 +1482,9 @@ function resolveTouchAction(direction) {
|
|
|
1417
1482
|
*/
|
|
1418
1483
|
const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(componentProps, forwardedRef) {
|
|
1419
1484
|
const {
|
|
1420
|
-
className,
|
|
1421
1485
|
render,
|
|
1486
|
+
className,
|
|
1487
|
+
style,
|
|
1422
1488
|
disabled = false,
|
|
1423
1489
|
swipeDirection: swipeDirectionProp,
|
|
1424
1490
|
...elementProps
|
|
@@ -1446,13 +1512,13 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
1446
1512
|
const swipeAreaId = useBaseUiId(componentProps.id);
|
|
1447
1513
|
const registerTrigger = useTriggerRegistration(swipeAreaId, store);
|
|
1448
1514
|
const open = store.useState('open');
|
|
1449
|
-
const resolvedSwipeDirection = swipeDirectionProp ?? oppositeSwipeDirection[swipeDirection];
|
|
1450
|
-
const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
|
|
1451
|
-
const enabled = !disabled && (!open || swipeActive);
|
|
1452
1515
|
const resetDragDelta = useStableCallback(() => {
|
|
1453
1516
|
dragDeltaRef.current.x = 0;
|
|
1454
1517
|
dragDeltaRef.current.y = 0;
|
|
1455
1518
|
});
|
|
1519
|
+
const resolvedSwipeDirection = swipeDirectionProp ?? oppositeSwipeDirection[swipeDirection];
|
|
1520
|
+
const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
|
|
1521
|
+
const enabled = !disabled && (!open || swipeActive);
|
|
1456
1522
|
function disableDismissForSwipe() {
|
|
1457
1523
|
releaseDismissTimeout.clear();
|
|
1458
1524
|
store.context.outsidePressEnabledRef.current = false;
|
|
@@ -1592,6 +1658,18 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
1592
1658
|
}
|
|
1593
1659
|
store.setOpen(false, createChangeEventDetails(swipe, event, swipeAreaRef.current ?? undefined));
|
|
1594
1660
|
}
|
|
1661
|
+
function resetSwipeInteractionState() {
|
|
1662
|
+
swipeStartEventRef.current = null;
|
|
1663
|
+
openedBySwipeRef.current = false;
|
|
1664
|
+
closedOffsetRef.current = null;
|
|
1665
|
+
setSwipeActive(false);
|
|
1666
|
+
}
|
|
1667
|
+
function finishSwipeInteraction() {
|
|
1668
|
+
resetSwipeInteractionState();
|
|
1669
|
+
enableDismissAfterRelease();
|
|
1670
|
+
resetDragDelta();
|
|
1671
|
+
clearSwipeStyles();
|
|
1672
|
+
}
|
|
1595
1673
|
const swipe$1 = useSwipeDismiss({
|
|
1596
1674
|
enabled,
|
|
1597
1675
|
directions: [resolvedSwipeDirection],
|
|
@@ -1650,15 +1728,10 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
1650
1728
|
} else if (openedBySwipeRef.current) {
|
|
1651
1729
|
closeDrawer(event);
|
|
1652
1730
|
}
|
|
1653
|
-
|
|
1654
|
-
openedBySwipeRef.current = false;
|
|
1655
|
-
setSwipeActive(false);
|
|
1656
|
-
closedOffsetRef.current = null;
|
|
1657
|
-
enableDismissAfterRelease();
|
|
1658
|
-
resetDragDelta();
|
|
1659
|
-
clearSwipeStyles();
|
|
1731
|
+
finishSwipeInteraction();
|
|
1660
1732
|
return false;
|
|
1661
|
-
}
|
|
1733
|
+
},
|
|
1734
|
+
onCancel: finishSwipeInteraction
|
|
1662
1735
|
});
|
|
1663
1736
|
const swipePointerProps = swipe$1.getPointerProps();
|
|
1664
1737
|
const swipeTouchProps = swipe$1.getTouchProps();
|
|
@@ -1668,10 +1741,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
1668
1741
|
resetSwipe();
|
|
1669
1742
|
resetDragDelta();
|
|
1670
1743
|
clearSwipeStyles();
|
|
1671
|
-
|
|
1672
|
-
openedBySwipeRef.current = false;
|
|
1673
|
-
swipeStartEventRef.current = null;
|
|
1674
|
-
closedOffsetRef.current = null;
|
|
1744
|
+
resetSwipeInteractionState();
|
|
1675
1745
|
}
|
|
1676
1746
|
}, [clearSwipeStyles, enabled, resetDragDelta, resetSwipe]);
|
|
1677
1747
|
React.useEffect(() => {
|
|
@@ -1701,6 +1771,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
1701
1771
|
return;
|
|
1702
1772
|
}
|
|
1703
1773
|
swipePointerProps.onPointerDown?.(event);
|
|
1774
|
+
|
|
1704
1775
|
// Prevent native text selection/drag gestures from competing with swipe-open dragging.
|
|
1705
1776
|
if (event.cancelable) {
|
|
1706
1777
|
event.preventDefault();
|
|
@@ -1731,6 +1802,12 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
1731
1802
|
});
|
|
1732
1803
|
if (process.env.NODE_ENV !== "production") DrawerSwipeArea.displayName = "DrawerSwipeArea";
|
|
1733
1804
|
|
|
1805
|
+
const DrawerVirtualKeyboardContext = /*#__PURE__*/React.createContext(undefined);
|
|
1806
|
+
if (process.env.NODE_ENV !== "production") DrawerVirtualKeyboardContext.displayName = "DrawerVirtualKeyboardContext";
|
|
1807
|
+
function useDrawerVirtualKeyboardContext() {
|
|
1808
|
+
return React.useContext(DrawerVirtualKeyboardContext);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1734
1811
|
const MIN_SWIPE_THRESHOLD = 10;
|
|
1735
1812
|
const FAST_SWIPE_VELOCITY = 0.5;
|
|
1736
1813
|
const SNAP_VELOCITY_THRESHOLD = 0.5;
|
|
@@ -1751,14 +1828,17 @@ const DRAWER_CONTENT_SELECTOR = `[${DRAWER_CONTENT_ATTRIBUTE}]`;
|
|
|
1751
1828
|
*/
|
|
1752
1829
|
const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(props, forwardedRef) {
|
|
1753
1830
|
const {
|
|
1754
|
-
className,
|
|
1755
1831
|
render,
|
|
1832
|
+
className,
|
|
1833
|
+
style,
|
|
1756
1834
|
children,
|
|
1757
1835
|
...elementProps
|
|
1758
1836
|
} = props;
|
|
1759
1837
|
const {
|
|
1760
1838
|
store
|
|
1761
1839
|
} = useDialogRootContext();
|
|
1840
|
+
const popupRef = store.context.popupRef;
|
|
1841
|
+
const backdropRef = store.context.backdropRef;
|
|
1762
1842
|
const {
|
|
1763
1843
|
swipeDirection,
|
|
1764
1844
|
notifyParentSwipingChange,
|
|
@@ -1767,17 +1847,6 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
1767
1847
|
snapToSequentialPoints
|
|
1768
1848
|
} = useDrawerRootContext();
|
|
1769
1849
|
const providerContext = useDrawerProviderContext();
|
|
1770
|
-
const visualStateStore = providerContext?.visualStateStore;
|
|
1771
|
-
const open = store.useState('open');
|
|
1772
|
-
const mounted = store.useState('mounted');
|
|
1773
|
-
const nested = store.useState('nested');
|
|
1774
|
-
const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
|
|
1775
|
-
const viewportElement = store.useState('viewportElement');
|
|
1776
|
-
const popupElementState = store.useState('popupElement');
|
|
1777
|
-
const nestedDrawerOpen = nestedOpenDialogCount > 0;
|
|
1778
|
-
const scrollAxis = swipeDirection === 'left' || swipeDirection === 'right' ? 'horizontal' : 'vertical';
|
|
1779
|
-
const isVerticalScrollAxis = scrollAxis === 'vertical';
|
|
1780
|
-
const crossScrollAxis = isVerticalScrollAxis ? 'horizontal' : 'vertical';
|
|
1781
1850
|
const {
|
|
1782
1851
|
snapPoints,
|
|
1783
1852
|
resolvedSnapPoints,
|
|
@@ -1786,15 +1855,28 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
1786
1855
|
setActiveSnapPoint,
|
|
1787
1856
|
popupHeight
|
|
1788
1857
|
} = useDrawerSnapPoints();
|
|
1858
|
+
const open = store.useState('open');
|
|
1859
|
+
const mounted = store.useState('mounted');
|
|
1860
|
+
const nested = store.useState('nested');
|
|
1861
|
+
const nestedOpenDrawerCount = store.useState('nestedOpenDrawerCount');
|
|
1862
|
+
const viewportElement = store.useState('viewportElement');
|
|
1863
|
+
const popupElementState = store.useState('popupElement');
|
|
1864
|
+
const visualStateStore = providerContext?.visualStateStore;
|
|
1865
|
+
const nestedDrawerOpen = nestedOpenDrawerCount > 0;
|
|
1866
|
+
const scrollAxis = swipeDirection === 'left' || swipeDirection === 'right' ? 'horizontal' : 'vertical';
|
|
1867
|
+
const isVerticalScrollAxis = scrollAxis === 'vertical';
|
|
1868
|
+
const crossScrollAxis = isVerticalScrollAxis ? 'horizontal' : 'vertical';
|
|
1789
1869
|
const [swipeRelease, setSwipeRelease] = React.useState(null);
|
|
1790
1870
|
const pendingSwipeCloseSnapPointRef = React.useRef(undefined);
|
|
1791
1871
|
const resetSwipeRef = React.useRef(null);
|
|
1792
1872
|
const controlledDismissFrame = useAnimationFrame();
|
|
1873
|
+
const swipingRef = React.useRef(false);
|
|
1793
1874
|
const nestedSwipeActiveRef = React.useRef(false);
|
|
1794
1875
|
const lastPointerTypeRef = React.useRef('');
|
|
1795
1876
|
const ignoreNextTouchStartFromPenRef = React.useRef(false);
|
|
1796
1877
|
const ignoreTouchSwipeRef = React.useRef(false);
|
|
1797
1878
|
const touchScrollStateRef = React.useRef(null);
|
|
1879
|
+
const virtualKeyboard = useDrawerVirtualKeyboardContext();
|
|
1798
1880
|
const snapPointRange = React.useMemo(() => {
|
|
1799
1881
|
if (!snapPoints || snapPoints.length < 2) {
|
|
1800
1882
|
return null;
|
|
@@ -1838,13 +1920,20 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
1838
1920
|
return [swipeDirection];
|
|
1839
1921
|
}, [snapPoints, swipeDirection]);
|
|
1840
1922
|
const setSwipeDismissed = useStableCallback(dismissed => {
|
|
1841
|
-
setSwipeDismissedElements(
|
|
1923
|
+
setSwipeDismissedElements(popupRef.current, backdropRef.current, dismissed);
|
|
1842
1924
|
});
|
|
1843
1925
|
const clearSwipeRelease = useStableCallback(() => {
|
|
1844
1926
|
setSwipeDismissed(false);
|
|
1845
|
-
|
|
1927
|
+
popupRef.current?.removeAttribute(TransitionStatusDataAttributes.endingStyle);
|
|
1846
1928
|
setSwipeRelease(null);
|
|
1847
1929
|
});
|
|
1930
|
+
const finishNestedSwipe = useStableCallback(() => {
|
|
1931
|
+
if (!nestedSwipeActiveRef.current) {
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
nestedSwipeActiveRef.current = false;
|
|
1935
|
+
notifyParentSwipingChange?.(false);
|
|
1936
|
+
});
|
|
1848
1937
|
const applySwipeProgress = useStableCallback(({
|
|
1849
1938
|
resolvedProgress,
|
|
1850
1939
|
shouldTrackProgress,
|
|
@@ -1852,15 +1941,18 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
1852
1941
|
}) => {
|
|
1853
1942
|
const isActive = open && !nested && shouldTrackProgress;
|
|
1854
1943
|
const swipeProgress = isActive ? resolvedProgress : 0;
|
|
1944
|
+
const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
|
|
1855
1945
|
if (notifyParent && notifyParentSwipeProgressChange) {
|
|
1856
|
-
const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;
|
|
1857
1946
|
notifyParentSwipeProgressChange(nestedSwipeProgress);
|
|
1947
|
+
if (nestedSwipeProgress <= 0) {
|
|
1948
|
+
finishNestedSwipe();
|
|
1949
|
+
}
|
|
1858
1950
|
}
|
|
1859
1951
|
visualStateStore?.set({
|
|
1860
1952
|
swipeProgress,
|
|
1861
1953
|
frontmostHeight: swipeProgress > 0 ? frontmostHeight : 0
|
|
1862
1954
|
});
|
|
1863
|
-
const backdropElement =
|
|
1955
|
+
const backdropElement = backdropRef.current;
|
|
1864
1956
|
if (!backdropElement) {
|
|
1865
1957
|
return;
|
|
1866
1958
|
}
|
|
@@ -1955,7 +2047,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
1955
2047
|
if ('touches' in event || 'pointerType' in event && event.pointerType === 'touch') {
|
|
1956
2048
|
return;
|
|
1957
2049
|
}
|
|
1958
|
-
const popupElement =
|
|
2050
|
+
const popupElement = popupRef.current;
|
|
1959
2051
|
if (!popupElement) {
|
|
1960
2052
|
return;
|
|
1961
2053
|
}
|
|
@@ -1972,10 +2064,10 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
1972
2064
|
selection.removeAllRanges();
|
|
1973
2065
|
},
|
|
1974
2066
|
onSwipingChange(swiping) {
|
|
2067
|
+
swipingRef.current = swiping;
|
|
1975
2068
|
setBackdropSwipingAttribute(store.context.backdropRef.current, swiping);
|
|
1976
|
-
if (!swiping) {
|
|
1977
|
-
|
|
1978
|
-
notifyParentSwipingChange?.(false);
|
|
2069
|
+
if (!swiping && !notifyParentSwipeProgressChange) {
|
|
2070
|
+
finishNestedSwipe();
|
|
1979
2071
|
}
|
|
1980
2072
|
},
|
|
1981
2073
|
swipeThreshold({
|
|
@@ -2006,9 +2098,16 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2006
2098
|
},
|
|
2007
2099
|
onProgress(progress, details) {
|
|
2008
2100
|
updateNestedSwipeActive(details);
|
|
2101
|
+
const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
|
|
2102
|
+
if (swipingRef.current && swipeDirection === 'down' && hasSnapPoints && details && Number.isFinite(details.deltaY)) {
|
|
2103
|
+
const popupElement = store.context.popupRef.current;
|
|
2104
|
+
if (popupElement) {
|
|
2105
|
+
popupElement.style.removeProperty('transform');
|
|
2106
|
+
popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementY, `${getSnapPointSwipeMovement(activeSnapPointOffset ?? 0, details.deltaY)}px`);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2009
2109
|
const currentDirection = details?.direction ?? swipe$1.swipeDirection;
|
|
2010
2110
|
const isDismissSwipe = currentDirection === undefined || currentDirection === swipeDirection;
|
|
2011
|
-
const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);
|
|
2012
2111
|
const isVerticalSwipe = swipeDirection === 'down' || swipeDirection === 'up';
|
|
2013
2112
|
const shouldTrackProgress = hasSnapPoints && isVerticalSwipe || !hasSnapPoints || swipeDirection === 'left' || swipeDirection === 'right' || isDismissSwipe;
|
|
2014
2113
|
let resolvedProgress = progress;
|
|
@@ -2057,7 +2156,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2057
2156
|
if (!popupElement) {
|
|
2058
2157
|
return;
|
|
2059
2158
|
}
|
|
2060
|
-
|
|
2159
|
+
finishNestedSwipe();
|
|
2061
2160
|
setSwipeDismissed(true);
|
|
2062
2161
|
popupElement.style.removeProperty('transition');
|
|
2063
2162
|
popupElement.setAttribute(TransitionStatusDataAttributes.endingStyle, '');
|
|
@@ -2264,7 +2363,10 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2264
2363
|
});
|
|
2265
2364
|
const swipePointerProps = swipe$1.getPointerProps();
|
|
2266
2365
|
const swipeTouchProps = swipe$1.getTouchProps();
|
|
2267
|
-
const
|
|
2366
|
+
const {
|
|
2367
|
+
moveNative: moveSwipeNative,
|
|
2368
|
+
reset: resetSwipe
|
|
2369
|
+
} = swipe$1;
|
|
2268
2370
|
resetSwipeRef.current = resetSwipe;
|
|
2269
2371
|
React.useEffect(() => {
|
|
2270
2372
|
const rootElement = viewportElement ?? popupElementState;
|
|
@@ -2275,6 +2377,11 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2275
2377
|
const doc = ownerDocument(resolvedRootElement);
|
|
2276
2378
|
const win = getWindow(doc);
|
|
2277
2379
|
function handleNativeTouchMove(event) {
|
|
2380
|
+
// The virtual keyboard provider observes the move to tell a tap apart from a drag.
|
|
2381
|
+
// It must run even when the swipe gesture below claims the event with
|
|
2382
|
+
// `stopPropagation()`, which would otherwise prevent React's delegated handlers
|
|
2383
|
+
// (and the provider) from ever seeing the move.
|
|
2384
|
+
virtualKeyboard?.onTouchMove(event);
|
|
2278
2385
|
if (ignoreTouchSwipeRef.current) {
|
|
2279
2386
|
return;
|
|
2280
2387
|
}
|
|
@@ -2311,6 +2418,10 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2311
2418
|
if (event.cancelable) {
|
|
2312
2419
|
event.preventDefault();
|
|
2313
2420
|
}
|
|
2421
|
+
// Claim the gesture before React's delegated touch handlers see it; dispatching the
|
|
2422
|
+
// move through React re-rasterizes the popup content on every frame.
|
|
2423
|
+
event.stopPropagation();
|
|
2424
|
+
moveSwipeNative(event, resolvedRootElement);
|
|
2314
2425
|
updateTouchScrollPosition(touchState, touch);
|
|
2315
2426
|
return;
|
|
2316
2427
|
}
|
|
@@ -2321,6 +2432,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2321
2432
|
if (event.cancelable) {
|
|
2322
2433
|
event.preventDefault();
|
|
2323
2434
|
}
|
|
2435
|
+
event.stopPropagation();
|
|
2324
2436
|
updateTouchScrollPosition(touchState, touch);
|
|
2325
2437
|
return;
|
|
2326
2438
|
}
|
|
@@ -2340,18 +2452,17 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2340
2452
|
event.preventDefault();
|
|
2341
2453
|
}
|
|
2342
2454
|
}
|
|
2455
|
+
if (touchState.allowSwipe === true) {
|
|
2456
|
+
event.stopPropagation();
|
|
2457
|
+
moveSwipeNative(event, resolvedRootElement);
|
|
2458
|
+
}
|
|
2343
2459
|
updateTouchScrollPosition(touchState, touch);
|
|
2344
2460
|
}
|
|
2345
|
-
|
|
2461
|
+
return addEventListener(doc, 'touchmove', handleNativeTouchMove, {
|
|
2346
2462
|
passive: false,
|
|
2347
2463
|
capture: true
|
|
2348
2464
|
});
|
|
2349
|
-
|
|
2350
|
-
doc.removeEventListener('touchmove', handleNativeTouchMove, {
|
|
2351
|
-
capture: true
|
|
2352
|
-
});
|
|
2353
|
-
};
|
|
2354
|
-
}, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, viewportElement]);
|
|
2465
|
+
}, [mounted, nestedDrawerOpen, open, popupElementState, isVerticalScrollAxis, scrollAxis, swipeDirection, moveSwipeNative, viewportElement, virtualKeyboard]);
|
|
2355
2466
|
React.useEffect(() => {
|
|
2356
2467
|
if (!snapPointRange || swipe$1.swiping) {
|
|
2357
2468
|
return;
|
|
@@ -2381,23 +2492,30 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2381
2492
|
}
|
|
2382
2493
|
}, [clearSwipeRelease, open, resetSwipe]);
|
|
2383
2494
|
React.useEffect(() => {
|
|
2495
|
+
const backdropElement = backdropRef.current;
|
|
2384
2496
|
return () => {
|
|
2385
2497
|
visualStateStore?.set({
|
|
2386
2498
|
swipeProgress: 0,
|
|
2387
2499
|
frontmostHeight: 0
|
|
2388
2500
|
});
|
|
2389
|
-
setBackdropSwipingAttribute(
|
|
2390
|
-
|
|
2501
|
+
setBackdropSwipingAttribute(backdropElement, false);
|
|
2502
|
+
// `data-swiping` is set on whichever backdrop is current when a swipe starts, which can
|
|
2503
|
+
// differ from the captured element if the backdrop mounted late or changed identity.
|
|
2504
|
+
// Reading the live ref here is intentional so the current backdrop is cleared too.
|
|
2505
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2506
|
+
const currentBackdrop = backdropRef.current;
|
|
2507
|
+
if (currentBackdrop !== backdropElement) {
|
|
2508
|
+
setBackdropSwipingAttribute(currentBackdrop, false);
|
|
2509
|
+
}
|
|
2510
|
+
finishNestedSwipe();
|
|
2391
2511
|
};
|
|
2392
|
-
}, [
|
|
2512
|
+
}, [backdropRef, finishNestedSwipe, visualStateStore]);
|
|
2393
2513
|
const swipeProviderValue = React.useMemo(() => ({
|
|
2394
2514
|
swiping: swipe$1.swiping,
|
|
2395
2515
|
getDragStyles: swipe$1.getDragStyles,
|
|
2396
2516
|
swipeStrength: swipeRelease ?? null,
|
|
2397
|
-
setSwipeDismissed
|
|
2398
|
-
|
|
2399
|
-
}
|
|
2400
|
-
}), [store, swipe$1.getDragStyles, swipe$1.swiping, swipeRelease]);
|
|
2517
|
+
setSwipeDismissed
|
|
2518
|
+
}), [setSwipeDismissed, swipe$1.getDragStyles, swipe$1.swiping, swipeRelease]);
|
|
2401
2519
|
function resetTouchTrackingState() {
|
|
2402
2520
|
ignoreTouchSwipeRef.current = false;
|
|
2403
2521
|
touchScrollStateRef.current = null;
|
|
@@ -2407,6 +2525,7 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2407
2525
|
return /*#__PURE__*/jsx(DialogViewport, {
|
|
2408
2526
|
ref: forwardedRef,
|
|
2409
2527
|
className: className,
|
|
2528
|
+
style: style,
|
|
2410
2529
|
render: render,
|
|
2411
2530
|
...mergeProps(elementProps, {
|
|
2412
2531
|
onPointerDown(event) {
|
|
@@ -2473,16 +2592,23 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2473
2592
|
}
|
|
2474
2593
|
const doc = ownerDocument(event.currentTarget);
|
|
2475
2594
|
const elementAtPoint = getElementAtPoint(doc, touch.clientX, touch.clientY);
|
|
2595
|
+
const rootElement = viewportElement ?? popupElementState;
|
|
2596
|
+
const eventTarget = getTarget(event.nativeEvent);
|
|
2597
|
+
const target = isElement(eventTarget) ? eventTarget : null;
|
|
2598
|
+
if (rootElement && target && !contains(rootElement, target)) {
|
|
2599
|
+
ignoreTouchSwipeRef.current = true;
|
|
2600
|
+
touchScrollStateRef.current = null;
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
virtualKeyboard?.onTouchStart(event);
|
|
2476
2604
|
ignoreTouchSwipeRef.current = isSwipeIgnoredTarget(elementAtPoint);
|
|
2477
2605
|
if (ignoreTouchSwipeRef.current) {
|
|
2478
2606
|
touchScrollStateRef.current = null;
|
|
2479
2607
|
return;
|
|
2480
2608
|
}
|
|
2481
|
-
const rootElement = viewportElement ?? popupElementState;
|
|
2482
|
-
const target = isElement(event.target) ? event.target : null;
|
|
2483
2609
|
let scrollTarget = null;
|
|
2484
2610
|
let hasCrossAxisScrollableContent = false;
|
|
2485
|
-
if (rootElement && target
|
|
2611
|
+
if (rootElement && target) {
|
|
2486
2612
|
scrollTarget = findScrollableTouchTarget(target, rootElement, scrollAxis);
|
|
2487
2613
|
hasCrossAxisScrollableContent = findScrollableTouchTarget(target, rootElement, crossScrollAxis) != null;
|
|
2488
2614
|
}
|
|
@@ -2503,27 +2629,13 @@ const DrawerViewport = /*#__PURE__*/React.forwardRef(function DrawerViewport(pro
|
|
|
2503
2629
|
};
|
|
2504
2630
|
swipeTouchProps.onTouchStart?.(event);
|
|
2505
2631
|
},
|
|
2506
|
-
onTouchMove(event) {
|
|
2507
|
-
if (ignoreTouchSwipeRef.current) {
|
|
2508
|
-
return;
|
|
2509
|
-
}
|
|
2510
|
-
if (isReactTouchEventOnRangeInput(event)) {
|
|
2511
|
-
return;
|
|
2512
|
-
}
|
|
2513
|
-
const touchState = touchScrollStateRef.current;
|
|
2514
|
-
if (touchState?.preserveNativeCrossAxisScroll) {
|
|
2515
|
-
return;
|
|
2516
|
-
}
|
|
2517
|
-
if (touchState?.allowSwipe === false || touchState?.scrollTarget != null && !touchState.allowSwipe) {
|
|
2518
|
-
return;
|
|
2519
|
-
}
|
|
2520
|
-
swipeTouchProps.onTouchMove?.(event);
|
|
2521
|
-
},
|
|
2522
2632
|
onTouchEnd(event) {
|
|
2633
|
+
virtualKeyboard?.onTouchEnd(event);
|
|
2523
2634
|
resetTouchTrackingState();
|
|
2524
2635
|
swipeTouchProps.onTouchEnd?.(event);
|
|
2525
2636
|
},
|
|
2526
2637
|
onTouchCancel(event) {
|
|
2638
|
+
virtualKeyboard?.onTouchCancel();
|
|
2527
2639
|
resetTouchTrackingState();
|
|
2528
2640
|
swipeTouchProps.onTouchCancel?.(event);
|
|
2529
2641
|
},
|
|
@@ -2582,114 +2694,582 @@ function hasExpandedSelectionWithinTarget(selection, target) {
|
|
|
2582
2694
|
return selection.containsNode(target, true) || contains(target, anchorElement) || contains(target, focusElement);
|
|
2583
2695
|
}
|
|
2584
2696
|
function shouldIgnoreSwipeForTextSelection(doc, rootElement) {
|
|
2585
|
-
const
|
|
2586
|
-
const activeElementWithinRoot = Boolean(
|
|
2587
|
-
if (activeElementWithinRoot && isTextSelectionControl(
|
|
2697
|
+
const activeEl = activeElement(doc);
|
|
2698
|
+
const activeElementWithinRoot = Boolean(activeEl && contains(rootElement, activeEl));
|
|
2699
|
+
if (activeElementWithinRoot && isTextSelectionControl(activeEl)) {
|
|
2588
2700
|
const {
|
|
2589
2701
|
selectionStart,
|
|
2590
2702
|
selectionEnd
|
|
2591
|
-
} =
|
|
2703
|
+
} = activeEl;
|
|
2592
2704
|
if (selectionStart != null && selectionEnd != null && selectionStart < selectionEnd) {
|
|
2593
2705
|
return true;
|
|
2594
2706
|
}
|
|
2595
|
-
}
|
|
2596
|
-
const selection = doc.getSelection?.();
|
|
2597
|
-
if (!selection || selection.isCollapsed) {
|
|
2598
|
-
return false;
|
|
2599
|
-
}
|
|
2600
|
-
return hasExpandedSelectionWithinTarget(selection, rootElement);
|
|
2601
|
-
}
|
|
2602
|
-
function isEventOnRangeInput(event, win) {
|
|
2603
|
-
const composedPath = event.composedPath();
|
|
2604
|
-
if (composedPath) {
|
|
2605
|
-
return composedPath.some(pathTarget => isRangeInput(pathTarget, win));
|
|
2606
|
-
}
|
|
2607
|
-
return isRangeInput(event
|
|
2608
|
-
}
|
|
2609
|
-
function isReactTouchEventOnRangeInput(event) {
|
|
2610
|
-
return isEventOnRangeInput(event.nativeEvent, getWindow(event.currentTarget));
|
|
2611
|
-
}
|
|
2612
|
-
function updateTouchScrollPosition(touchState, touch) {
|
|
2613
|
-
touchState.lastX = touch.clientX;
|
|
2614
|
-
touchState.lastY = touch.clientY;
|
|
2707
|
+
}
|
|
2708
|
+
const selection = doc.getSelection?.();
|
|
2709
|
+
if (!selection || selection.isCollapsed) {
|
|
2710
|
+
return false;
|
|
2711
|
+
}
|
|
2712
|
+
return hasExpandedSelectionWithinTarget(selection, rootElement);
|
|
2713
|
+
}
|
|
2714
|
+
function isEventOnRangeInput(event, win) {
|
|
2715
|
+
const composedPath = event.composedPath();
|
|
2716
|
+
if (composedPath) {
|
|
2717
|
+
return composedPath.some(pathTarget => isRangeInput(pathTarget, win));
|
|
2718
|
+
}
|
|
2719
|
+
return isRangeInput(getTarget(event), win);
|
|
2720
|
+
}
|
|
2721
|
+
function isReactTouchEventOnRangeInput(event) {
|
|
2722
|
+
return isEventOnRangeInput(event.nativeEvent, getWindow(event.currentTarget));
|
|
2723
|
+
}
|
|
2724
|
+
function updateTouchScrollPosition(touchState, touch) {
|
|
2725
|
+
touchState.lastX = touch.clientX;
|
|
2726
|
+
touchState.lastY = touch.clientY;
|
|
2727
|
+
}
|
|
2728
|
+
function preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis) {
|
|
2729
|
+
if (touchState.preserveNativeCrossAxisScroll) {
|
|
2730
|
+
return true;
|
|
2731
|
+
}
|
|
2732
|
+
if (touchState.allowSwipe === true || !touchState.hasCrossAxisScrollableContent) {
|
|
2733
|
+
return false;
|
|
2734
|
+
}
|
|
2735
|
+
const drawerAxisGestureDelta = isVerticalScrollAxis ? touch.clientY - touchState.startY : touch.clientX - touchState.startX;
|
|
2736
|
+
const crossAxisGestureDelta = isVerticalScrollAxis ? touch.clientX - touchState.startX : touch.clientY - touchState.startY;
|
|
2737
|
+
const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
|
|
2738
|
+
const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
|
|
2739
|
+
if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {
|
|
2740
|
+
return false;
|
|
2741
|
+
}
|
|
2742
|
+
touchState.preserveNativeCrossAxisScroll = true;
|
|
2743
|
+
return true;
|
|
2744
|
+
}
|
|
2745
|
+
function hasScrollableContentOnAxis(scrollTarget, axis) {
|
|
2746
|
+
return axis === 'vertical' ? scrollTarget.scrollHeight > scrollTarget.clientHeight : scrollTarget.scrollWidth > scrollTarget.clientWidth;
|
|
2747
|
+
}
|
|
2748
|
+
function getScrollMetrics(scrollTarget, axis) {
|
|
2749
|
+
if (axis === 'vertical') {
|
|
2750
|
+
const max = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
|
|
2751
|
+
return {
|
|
2752
|
+
offset: scrollTarget.scrollTop,
|
|
2753
|
+
max
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
const max = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
|
|
2757
|
+
return {
|
|
2758
|
+
offset: scrollTarget.scrollLeft,
|
|
2759
|
+
max
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
function isAtSwipeStartEdge(scrollTarget, axis, direction) {
|
|
2763
|
+
const {
|
|
2764
|
+
offset,
|
|
2765
|
+
max
|
|
2766
|
+
} = getScrollMetrics(scrollTarget, axis);
|
|
2767
|
+
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
2768
|
+
if (dismissFromStartEdge === null) {
|
|
2769
|
+
return false;
|
|
2770
|
+
}
|
|
2771
|
+
return dismissFromStartEdge ? offset <= 0 : offset >= max;
|
|
2772
|
+
}
|
|
2773
|
+
function canSwipeFromScrollEdgeOnMove(scrollTarget, axis, direction, delta) {
|
|
2774
|
+
const {
|
|
2775
|
+
offset,
|
|
2776
|
+
max
|
|
2777
|
+
} = getScrollMetrics(scrollTarget, axis);
|
|
2778
|
+
const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);
|
|
2779
|
+
if (dismissFromStartEdge === null) {
|
|
2780
|
+
return false;
|
|
2781
|
+
}
|
|
2782
|
+
const movingTowardDismiss = dismissFromStartEdge ? delta > 0 : delta < 0;
|
|
2783
|
+
if (!movingTowardDismiss) {
|
|
2784
|
+
return false;
|
|
2785
|
+
}
|
|
2786
|
+
return dismissFromStartEdge ? offset <= 0 : offset >= max;
|
|
2787
|
+
}
|
|
2788
|
+
function shouldDismissFromStartEdge(direction, axis) {
|
|
2789
|
+
if (axis === 'vertical') {
|
|
2790
|
+
if (direction === 'down') {
|
|
2791
|
+
return true;
|
|
2792
|
+
}
|
|
2793
|
+
if (direction === 'up') {
|
|
2794
|
+
return false;
|
|
2795
|
+
}
|
|
2796
|
+
return null;
|
|
2797
|
+
}
|
|
2798
|
+
if (direction === 'right') {
|
|
2799
|
+
return true;
|
|
2800
|
+
}
|
|
2801
|
+
if (direction === 'left') {
|
|
2802
|
+
return false;
|
|
2803
|
+
}
|
|
2804
|
+
return null;
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
let DrawerViewportCssVars = /*#__PURE__*/function (DrawerViewportCssVars) {
|
|
2808
|
+
/**
|
|
2809
|
+
* The software keyboard inset, measured from the bottom edge of the layout viewport.
|
|
2810
|
+
* Present only when the drawer is wrapped in `Drawer.VirtualKeyboardProvider`.
|
|
2811
|
+
* @type {CSS length}
|
|
2812
|
+
*/
|
|
2813
|
+
DrawerViewportCssVars["keyboardInset"] = "--drawer-keyboard-inset";
|
|
2814
|
+
return DrawerViewportCssVars;
|
|
2815
|
+
}({});
|
|
2816
|
+
|
|
2817
|
+
const KEYBOARD_RESIZE_THRESHOLD = 60;
|
|
2818
|
+
const KEYBOARD_VISIBILITY_MARGIN = 16;
|
|
2819
|
+
// Extra breathing room (px) added below the focused field, on top of its measured
|
|
2820
|
+
// keyboard overlap, so the field can be scrolled clear of the keyboard instead of
|
|
2821
|
+
// ending up flush against it. Only applied when there is actual overlap.
|
|
2822
|
+
const KEYBOARD_SCROLL_SLACK = 48;
|
|
2823
|
+
const INPUT_TAP_MOVE_THRESHOLD = 10;
|
|
2824
|
+
const INPUT_TAP_HIT_SLOP = 16;
|
|
2825
|
+
const KEYBOARD_INPUT_TYPES = new Set(['email', 'number', 'password', 'search', 'tel', 'text', 'url']);
|
|
2826
|
+
|
|
2827
|
+
// Snapshot of a scroll container's relevant styles taken before keyboard slack is
|
|
2828
|
+
// applied. The string fields are the exact inline values to restore on cleanup;
|
|
2829
|
+
// the parsed numbers are the computed baselines that slack is added on top of.
|
|
2830
|
+
|
|
2831
|
+
// Returned by the point-based resolver when the lift point lands on another
|
|
2832
|
+
// interactive/label element. It signals that the tap was intentionally rejected, so the
|
|
2833
|
+
// caller must NOT fall back to the touchstart target (`touchend.target` stays at the
|
|
2834
|
+
// touchstart node on mobile) — doing so would steal a tap meant for that element.
|
|
2835
|
+
const KEYBOARD_TAP_BLOCKED = Symbol('KeyboardTapBlocked');
|
|
2836
|
+
|
|
2837
|
+
/**
|
|
2838
|
+
* Provides keyboard-aware focus and scroll handling for bottom-sheet drawers with form fields.
|
|
2839
|
+
*
|
|
2840
|
+
* Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
|
|
2841
|
+
*/
|
|
2842
|
+
function DrawerVirtualKeyboardProvider(props) {
|
|
2843
|
+
const {
|
|
2844
|
+
children
|
|
2845
|
+
} = props;
|
|
2846
|
+
const {
|
|
2847
|
+
store
|
|
2848
|
+
} = useDialogRootContext();
|
|
2849
|
+
const open = store.useState('open');
|
|
2850
|
+
const mounted = store.useState('mounted');
|
|
2851
|
+
const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
|
|
2852
|
+
const viewportElement = store.useState('viewportElement');
|
|
2853
|
+
|
|
2854
|
+
// The provider requires a `<Drawer.Viewport>` to act as the measurement and containment
|
|
2855
|
+
// root and to host the keyboard inset variable; `<Drawer.Popup>` already warns when the
|
|
2856
|
+
// viewport is missing, so there is no need to fall back to the popup element here.
|
|
2857
|
+
const rootElement = viewportElement;
|
|
2858
|
+
const nestedDrawerOpen = nestedOpenDialogCount > 0;
|
|
2859
|
+
const pendingKeyboardFocusMovedRef = React.useRef(false);
|
|
2860
|
+
const keyboardTouchStartRef = React.useRef(null);
|
|
2861
|
+
const focusedKeyboardTargetRef = React.useRef(null);
|
|
2862
|
+
const keyboardScrollAdjustmentRef = React.useRef(null);
|
|
2863
|
+
const keyboardFocusFrame = useAnimationFrame();
|
|
2864
|
+
const restoreKeyboardScrollAdjustment = useStableCallback(() => {
|
|
2865
|
+
const adjustment = keyboardScrollAdjustmentRef.current;
|
|
2866
|
+
if (!adjustment) {
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
adjustment.element.style.overflowAnchor = adjustment.overflowAnchor;
|
|
2870
|
+
adjustment.element.style.paddingBottom = adjustment.paddingBottom;
|
|
2871
|
+
adjustment.element.style.scrollPaddingBottom = adjustment.scrollPaddingBottom;
|
|
2872
|
+
keyboardScrollAdjustmentRef.current = null;
|
|
2873
|
+
});
|
|
2874
|
+
const setKeyboardScrollSlack = useStableCallback((element, slack) => {
|
|
2875
|
+
const roundedSlack = Math.max(0, Math.ceil(slack));
|
|
2876
|
+
let adjustment = keyboardScrollAdjustmentRef.current;
|
|
2877
|
+
if (adjustment && !adjustment.element.isConnected) {
|
|
2878
|
+
restoreKeyboardScrollAdjustment();
|
|
2879
|
+
adjustment = null;
|
|
2880
|
+
}
|
|
2881
|
+
if (roundedSlack === 0) {
|
|
2882
|
+
restoreKeyboardScrollAdjustment();
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
if (adjustment && adjustment.element !== element) {
|
|
2886
|
+
restoreKeyboardScrollAdjustment();
|
|
2887
|
+
adjustment = null;
|
|
2888
|
+
}
|
|
2889
|
+
if (!adjustment) {
|
|
2890
|
+
const styles = getComputedStyle(element);
|
|
2891
|
+
adjustment = {
|
|
2892
|
+
element,
|
|
2893
|
+
overflowAnchor: element.style.overflowAnchor,
|
|
2894
|
+
paddingBottom: element.style.paddingBottom,
|
|
2895
|
+
scrollPaddingBottom: element.style.scrollPaddingBottom,
|
|
2896
|
+
computedPaddingBottom: Number.parseFloat(styles.paddingBottom) || 0,
|
|
2897
|
+
computedScrollPaddingBottom: Number.parseFloat(styles.scrollPaddingBottom) || 0
|
|
2898
|
+
};
|
|
2899
|
+
keyboardScrollAdjustmentRef.current = adjustment;
|
|
2900
|
+
}
|
|
2901
|
+
element.style.overflowAnchor = 'none';
|
|
2902
|
+
element.style.paddingBottom = `${adjustment.computedPaddingBottom + roundedSlack}px`;
|
|
2903
|
+
element.style.scrollPaddingBottom = `${adjustment.computedScrollPaddingBottom + KEYBOARD_VISIBILITY_MARGIN}px`;
|
|
2904
|
+
});
|
|
2905
|
+
const animateKeyboardScroll = useStableCallback((element, scrollTop) => {
|
|
2906
|
+
const win = getWindow(element);
|
|
2907
|
+
const behavior = win.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ? 'auto' : 'smooth';
|
|
2908
|
+
element.scrollTo({
|
|
2909
|
+
top: scrollTop,
|
|
2910
|
+
behavior
|
|
2911
|
+
});
|
|
2912
|
+
});
|
|
2913
|
+
const resetTouchTrackingState = useStableCallback(() => {
|
|
2914
|
+
pendingKeyboardFocusMovedRef.current = false;
|
|
2915
|
+
keyboardTouchStartRef.current = null;
|
|
2916
|
+
});
|
|
2917
|
+
React.useEffect(() => {
|
|
2918
|
+
if (!mounted || !open) {
|
|
2919
|
+
focusedKeyboardTargetRef.current = null;
|
|
2920
|
+
restoreKeyboardScrollAdjustment();
|
|
2921
|
+
keyboardFocusFrame.cancel();
|
|
2922
|
+
return undefined;
|
|
2923
|
+
}
|
|
2924
|
+
if (!rootElement) {
|
|
2925
|
+
restoreKeyboardScrollAdjustment();
|
|
2926
|
+
return undefined;
|
|
2927
|
+
}
|
|
2928
|
+
const doc = ownerDocument(rootElement);
|
|
2929
|
+
const win = getWindow(rootElement);
|
|
2930
|
+
const visualViewport = win.visualViewport;
|
|
2931
|
+
const setDrawerKeyboardInset = inset => {
|
|
2932
|
+
rootElement.style.setProperty(DrawerViewportCssVars.keyboardInset, `${Math.max(0, Math.ceil(inset))}px`);
|
|
2933
|
+
};
|
|
2934
|
+
const resetDrawerKeyboardInset = () => {
|
|
2935
|
+
setDrawerKeyboardInset(0);
|
|
2936
|
+
};
|
|
2937
|
+
const clearFocusedKeyboardTarget = () => {
|
|
2938
|
+
focusedKeyboardTargetRef.current = null;
|
|
2939
|
+
resetDrawerKeyboardInset();
|
|
2940
|
+
restoreKeyboardScrollAdjustment();
|
|
2941
|
+
keyboardFocusFrame.cancel();
|
|
2942
|
+
};
|
|
2943
|
+
const alignFocusedKeyboardTarget = () => {
|
|
2944
|
+
const target = focusedKeyboardTargetRef.current;
|
|
2945
|
+
// If the focused field is removed from the DOM without firing `focusout` (e.g. it is
|
|
2946
|
+
// conditionally rendered away), any applied scroll slack is restored here on the next
|
|
2947
|
+
// focus/viewport event or when the drawer closes. This self-corrects rather than
|
|
2948
|
+
// tracking each field's lifecycle.
|
|
2949
|
+
if (nestedDrawerOpen || !target || !contains(rootElement, target)) {
|
|
2950
|
+
resetDrawerKeyboardInset();
|
|
2951
|
+
restoreKeyboardScrollAdjustment();
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
const keyboardViewport = getKeyboardVisualViewport(win);
|
|
2955
|
+
if (!keyboardViewport) {
|
|
2956
|
+
resetDrawerKeyboardInset();
|
|
2957
|
+
restoreKeyboardScrollAdjustment();
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2960
|
+
setDrawerKeyboardInset(getDrawerKeyboardInset(win, keyboardViewport));
|
|
2961
|
+
const scrollTarget = findKeyboardScrollTarget(target, rootElement);
|
|
2962
|
+
if (!scrollTarget) {
|
|
2963
|
+
restoreKeyboardScrollAdjustment();
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
if (!scrollTarget.isConnected || !contains(rootElement, scrollTarget)) {
|
|
2967
|
+
resetDrawerKeyboardInset();
|
|
2968
|
+
restoreKeyboardScrollAdjustment();
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
const scrollTargetRect = scrollTarget.getBoundingClientRect();
|
|
2972
|
+
const clippedBottom = Math.min(scrollTargetRect.bottom, keyboardViewport.bottom);
|
|
2973
|
+
const overlap = Math.max(0, scrollTargetRect.bottom - keyboardViewport.bottom);
|
|
2974
|
+
setKeyboardScrollSlack(scrollTarget, overlap > 0 ? overlap + KEYBOARD_SCROLL_SLACK : 0);
|
|
2975
|
+
const maxScrollTop = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);
|
|
2976
|
+
if (maxScrollTop <= 0) {
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
const clippedTop = Math.max(scrollTargetRect.top, keyboardViewport.top);
|
|
2980
|
+
const visibleTop = clippedTop + KEYBOARD_VISIBILITY_MARGIN;
|
|
2981
|
+
const visibleBottom = clippedBottom - KEYBOARD_VISIBILITY_MARGIN;
|
|
2982
|
+
if (visibleBottom <= visibleTop) {
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
const targetRect = target.getBoundingClientRect();
|
|
2986
|
+
const targetCenter = (targetRect.top + targetRect.bottom) / 2;
|
|
2987
|
+
const visibleCenter = (visibleTop + visibleBottom) / 2;
|
|
2988
|
+
const nextScrollTop = scrollTarget.scrollTop + targetCenter - visibleCenter;
|
|
2989
|
+
animateKeyboardScroll(scrollTarget, clamp(nextScrollTop, 0, maxScrollTop));
|
|
2990
|
+
};
|
|
2991
|
+
const scheduleKeyboardFocusAlignment = () => {
|
|
2992
|
+
keyboardFocusFrame.request(alignFocusedKeyboardTarget);
|
|
2993
|
+
};
|
|
2994
|
+
const captureFocusedKeyboardTarget = eventTarget => {
|
|
2995
|
+
if (nestedDrawerOpen) {
|
|
2996
|
+
return false;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
// Resolve through the same path as taps so contentEditable hosts (and labelled
|
|
3000
|
+
// controls) are normalized identically for the focus and touch paths.
|
|
3001
|
+
const target = resolveKeyboardInputTarget(eventTarget);
|
|
3002
|
+
if (!target || !contains(rootElement, target)) {
|
|
3003
|
+
return false;
|
|
3004
|
+
}
|
|
3005
|
+
focusedKeyboardTargetRef.current = target;
|
|
3006
|
+
return true;
|
|
3007
|
+
};
|
|
3008
|
+
const handleFocusIn = event => {
|
|
3009
|
+
if (captureFocusedKeyboardTarget(getTarget(event))) {
|
|
3010
|
+
scheduleKeyboardFocusAlignment();
|
|
3011
|
+
}
|
|
3012
|
+
};
|
|
3013
|
+
const handleFocusOut = event => {
|
|
3014
|
+
if (captureFocusedKeyboardTarget(event.relatedTarget)) {
|
|
3015
|
+
scheduleKeyboardFocusAlignment();
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
clearFocusedKeyboardTarget();
|
|
3019
|
+
};
|
|
3020
|
+
const handleViewportUpdate = () => {
|
|
3021
|
+
if (focusedKeyboardTargetRef.current || captureFocusedKeyboardTarget(activeElement(doc))) {
|
|
3022
|
+
scheduleKeyboardFocusAlignment();
|
|
3023
|
+
}
|
|
3024
|
+
};
|
|
3025
|
+
const cleanupListeners = [];
|
|
3026
|
+
if (visualViewport) {
|
|
3027
|
+
cleanupListeners.push(addEventListener(visualViewport, 'resize', handleViewportUpdate), addEventListener(visualViewport, 'scroll', handleViewportUpdate));
|
|
3028
|
+
}
|
|
3029
|
+
cleanupListeners.push(addEventListener(doc, 'focusin', handleFocusIn, true), addEventListener(doc, 'focusout', handleFocusOut, true));
|
|
3030
|
+
if (captureFocusedKeyboardTarget(activeElement(doc))) {
|
|
3031
|
+
scheduleKeyboardFocusAlignment();
|
|
3032
|
+
}
|
|
3033
|
+
return () => {
|
|
3034
|
+
cleanupListeners.forEach(cleanup => cleanup());
|
|
3035
|
+
clearFocusedKeyboardTarget();
|
|
3036
|
+
rootElement.style.removeProperty(DrawerViewportCssVars.keyboardInset);
|
|
3037
|
+
};
|
|
3038
|
+
}, [animateKeyboardScroll, keyboardFocusFrame, mounted, nestedDrawerOpen, open, restoreKeyboardScrollAdjustment, rootElement, setKeyboardScrollSlack]);
|
|
3039
|
+
const onTouchStart = useStableCallback(event => {
|
|
3040
|
+
if (!open || !mounted || nestedDrawerOpen) {
|
|
3041
|
+
resetTouchTrackingState();
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
const touch = event.touches[0];
|
|
3045
|
+
if (!touch) {
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
pendingKeyboardFocusMovedRef.current = false;
|
|
3049
|
+
keyboardTouchStartRef.current = {
|
|
3050
|
+
x: touch.clientX,
|
|
3051
|
+
y: touch.clientY
|
|
3052
|
+
};
|
|
3053
|
+
});
|
|
3054
|
+
const onTouchMove = useStableCallback(event => {
|
|
3055
|
+
const touch = event.touches[0];
|
|
3056
|
+
const touchStart = keyboardTouchStartRef.current;
|
|
3057
|
+
if (!touch || !touchStart || pendingKeyboardFocusMovedRef.current) {
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
// Treat the gesture as a scroll/swipe (not a tap-to-focus) once the finger
|
|
3062
|
+
// moves past the threshold, so we don't open the keyboard on a drag.
|
|
3063
|
+
if (Math.abs(touch.clientX - touchStart.x) > INPUT_TAP_MOVE_THRESHOLD || Math.abs(touch.clientY - touchStart.y) > INPUT_TAP_MOVE_THRESHOLD) {
|
|
3064
|
+
pendingKeyboardFocusMovedRef.current = true;
|
|
3065
|
+
}
|
|
3066
|
+
});
|
|
3067
|
+
const onTouchEnd = useStableCallback(event => {
|
|
3068
|
+
if (!open || !mounted || nestedDrawerOpen || !rootElement || !keyboardTouchStartRef.current || pendingKeyboardFocusMovedRef.current) {
|
|
3069
|
+
resetTouchTrackingState();
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
const touch = event.changedTouches[0] ?? event.touches[0];
|
|
3073
|
+
const doc = ownerDocument(event.currentTarget);
|
|
3074
|
+
const nativeEventTarget = getTarget(event.nativeEvent);
|
|
3075
|
+
const pointTarget = touch ? resolveKeyboardTouchTargetFromPoint(doc, touch.clientX, touch.clientY) : null;
|
|
3076
|
+
|
|
3077
|
+
// The lift point landed on another interactive/label element; let its native tap
|
|
3078
|
+
// through instead of stealing it for the touchstart input.
|
|
3079
|
+
if (pointTarget === KEYBOARD_TAP_BLOCKED) {
|
|
3080
|
+
resetTouchTrackingState();
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
const keyboardTarget = touch && (pointTarget ?? resolveKeyboardTouchTarget(nativeEventTarget));
|
|
3084
|
+
if (keyboardTarget && (!contains(rootElement, keyboardTarget.focusTarget) || !contains(rootElement, keyboardTarget.clickTarget))) {
|
|
3085
|
+
resetTouchTrackingState();
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3088
|
+
if (keyboardTarget) {
|
|
3089
|
+
const {
|
|
3090
|
+
clickTarget: keyboardClickTarget,
|
|
3091
|
+
focusTarget: keyboardFocusTarget
|
|
3092
|
+
} = keyboardTarget;
|
|
3093
|
+
const win = getWindow(keyboardFocusTarget);
|
|
3094
|
+
|
|
3095
|
+
// While pinch-zoomed, keyboard alignment is suspended; let native behavior
|
|
3096
|
+
// handle focus and caret placement instead of blurring and re-focusing.
|
|
3097
|
+
if (win.visualViewport && win.visualViewport.scale !== 1) {
|
|
3098
|
+
resetTouchTrackingState();
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
// Already focused with the keyboard up: let the native tap through so it can
|
|
3103
|
+
// reposition the caret, rather than blurring and re-focusing the same input.
|
|
3104
|
+
if (activeElement(ownerDocument(keyboardFocusTarget)) === keyboardFocusTarget && isKeyboardVisualViewportOpen(win)) {
|
|
3105
|
+
resetTouchTrackingState();
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
// iOS only opens the software keyboard when focus happens synchronously
|
|
3110
|
+
// inside the touch gesture.
|
|
3111
|
+
event.preventDefault();
|
|
3112
|
+
focusKeyboardInputWithoutPageScroll(keyboardFocusTarget);
|
|
3113
|
+
// Preventing the touchend default also suppresses the compatibility mouse
|
|
3114
|
+
// events, including `click`; redispatch an untrusted replacement on the
|
|
3115
|
+
// original tap target so click handlers still run with the tap coordinates.
|
|
3116
|
+
dispatchKeyboardClick(keyboardClickTarget, touch);
|
|
3117
|
+
resetTouchTrackingState();
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
resetTouchTrackingState();
|
|
3121
|
+
});
|
|
3122
|
+
const contextValue = React.useMemo(() => ({
|
|
3123
|
+
onTouchStart,
|
|
3124
|
+
onTouchMove,
|
|
3125
|
+
onTouchEnd,
|
|
3126
|
+
onTouchCancel: resetTouchTrackingState
|
|
3127
|
+
}), [onTouchEnd, onTouchMove, onTouchStart, resetTouchTrackingState]);
|
|
3128
|
+
return /*#__PURE__*/jsx(DrawerVirtualKeyboardContext.Provider, {
|
|
3129
|
+
value: contextValue,
|
|
3130
|
+
children: children
|
|
3131
|
+
});
|
|
2615
3132
|
}
|
|
2616
|
-
function
|
|
2617
|
-
if (
|
|
3133
|
+
function isKeyboardInputElement(element) {
|
|
3134
|
+
if (element.isContentEditable) {
|
|
2618
3135
|
return true;
|
|
2619
3136
|
}
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);
|
|
2626
|
-
const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);
|
|
2627
|
-
if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {
|
|
2628
|
-
return false;
|
|
3137
|
+
const win = getWindow(element);
|
|
3138
|
+
if (element instanceof win.HTMLTextAreaElement || element instanceof win.HTMLInputElement && KEYBOARD_INPUT_TYPES.has(element.type)) {
|
|
3139
|
+
// Disabled controls can't focus or open the keyboard, so tap-to-focus must skip them —
|
|
3140
|
+
// otherwise the dispatched click fires handlers a native tap on a disabled control never would.
|
|
3141
|
+
return !element.matches(':disabled');
|
|
2629
3142
|
}
|
|
2630
|
-
|
|
2631
|
-
return true;
|
|
3143
|
+
return false;
|
|
2632
3144
|
}
|
|
2633
|
-
function
|
|
2634
|
-
|
|
3145
|
+
function resolveKeyboardInputTarget(target) {
|
|
3146
|
+
if (!isHTMLElement(target)) {
|
|
3147
|
+
return null;
|
|
3148
|
+
}
|
|
3149
|
+
if (isKeyboardInputElement(target)) {
|
|
3150
|
+
return target.isContentEditable ? getContentEditableHost(target) : target;
|
|
3151
|
+
}
|
|
3152
|
+
const label = target.closest('label');
|
|
3153
|
+
const control = label?.control ?? null;
|
|
3154
|
+
return isHTMLElement(control) && isKeyboardInputElement(control) ? control : null;
|
|
2635
3155
|
}
|
|
2636
|
-
function
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
return
|
|
2640
|
-
offset: scrollTarget.scrollTop,
|
|
2641
|
-
max
|
|
2642
|
-
};
|
|
3156
|
+
function resolveKeyboardTouchTarget(target) {
|
|
3157
|
+
const focusTarget = resolveKeyboardInputTarget(target);
|
|
3158
|
+
if (!focusTarget) {
|
|
3159
|
+
return null;
|
|
2643
3160
|
}
|
|
2644
|
-
const max = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);
|
|
2645
3161
|
return {
|
|
2646
|
-
|
|
2647
|
-
|
|
3162
|
+
focusTarget,
|
|
3163
|
+
clickTarget: isHTMLElement(target) ? target : focusTarget
|
|
2648
3164
|
};
|
|
2649
3165
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
return false;
|
|
3166
|
+
|
|
3167
|
+
// Inherited-editable descendants (no `contenteditable` attribute of their own) are not
|
|
3168
|
+
// focusable, so focusing them is a no-op; resolve taps on them to the editing host.
|
|
3169
|
+
function getContentEditableHost(element) {
|
|
3170
|
+
let host = element;
|
|
3171
|
+
while (host.parentElement?.isContentEditable) {
|
|
3172
|
+
host = host.parentElement;
|
|
2658
3173
|
}
|
|
2659
|
-
return
|
|
3174
|
+
return host;
|
|
2660
3175
|
}
|
|
2661
|
-
function
|
|
2662
|
-
const
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
3176
|
+
function resolveKeyboardTouchTargetFromPoint(doc, clientX, clientY) {
|
|
3177
|
+
const exactTarget = getElementAtPoint(doc, clientX, clientY);
|
|
3178
|
+
const exactKeyboardTarget = resolveKeyboardInputTarget(exactTarget);
|
|
3179
|
+
if (exactKeyboardTarget) {
|
|
3180
|
+
return {
|
|
3181
|
+
focusTarget: exactKeyboardTarget,
|
|
3182
|
+
clickTarget: isHTMLElement(exactTarget) ? exactTarget : exactKeyboardTarget
|
|
3183
|
+
};
|
|
2669
3184
|
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
3185
|
+
|
|
3186
|
+
// Probing nearby points compensates for iOS retargeting taps while the page reacts
|
|
3187
|
+
// to the keyboard, but it must not steal a tap that lands on another interactive
|
|
3188
|
+
// element — that would suppress its click and focus a neighboring field instead.
|
|
3189
|
+
// `closest('label')` covers labels of non-keyboard controls (e.g. checkboxes).
|
|
3190
|
+
// Returning the blocked sentinel (rather than `null`) stops the caller from falling
|
|
3191
|
+
// back to the touchstart target, which would re-steal the very tap rejected here.
|
|
3192
|
+
if (isInteractiveElement(exactTarget) || exactTarget?.closest('label') != null) {
|
|
3193
|
+
return KEYBOARD_TAP_BLOCKED;
|
|
2673
3194
|
}
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
3195
|
+
for (const [offsetX, offsetY] of [[0, INPUT_TAP_HIT_SLOP], [0, -INPUT_TAP_HIT_SLOP], [INPUT_TAP_HIT_SLOP, 0], [-INPUT_TAP_HIT_SLOP, 0]]) {
|
|
3196
|
+
const keyboardTarget = resolveKeyboardInputTarget(getElementAtPoint(doc, clientX + offsetX, clientY + offsetY));
|
|
3197
|
+
if (keyboardTarget) {
|
|
3198
|
+
return {
|
|
3199
|
+
focusTarget: keyboardTarget,
|
|
3200
|
+
clickTarget: keyboardTarget
|
|
3201
|
+
};
|
|
2680
3202
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
3203
|
+
}
|
|
3204
|
+
return null;
|
|
3205
|
+
}
|
|
3206
|
+
function dispatchKeyboardClick(target, touch) {
|
|
3207
|
+
const win = getWindow(target);
|
|
3208
|
+
const ClickEvent = win.PointerEvent ?? win.MouseEvent;
|
|
3209
|
+
target.dispatchEvent(new ClickEvent('click', {
|
|
3210
|
+
bubbles: true,
|
|
3211
|
+
cancelable: true,
|
|
3212
|
+
clientX: touch.clientX,
|
|
3213
|
+
clientY: touch.clientY,
|
|
3214
|
+
detail: 1,
|
|
3215
|
+
view: win
|
|
3216
|
+
}));
|
|
3217
|
+
}
|
|
3218
|
+
function focusKeyboardInputWithoutPageScroll(target) {
|
|
3219
|
+
const wasFocused = activeElement(ownerDocument(target)) === target;
|
|
3220
|
+
const previousOpacity = target.style.opacity;
|
|
3221
|
+
const previousTransform = target.style.transform;
|
|
3222
|
+
const previousTransition = target.style.transition;
|
|
3223
|
+
|
|
3224
|
+
// iOS Safari can still scroll the page for transformed sheets even with preventScroll.
|
|
3225
|
+
// Move the input off-screen only for the synchronous focus call.
|
|
3226
|
+
target.style.transition = 'none';
|
|
3227
|
+
target.style.opacity = '0';
|
|
3228
|
+
target.style.transform = 'translateY(-2000px)';
|
|
3229
|
+
try {
|
|
3230
|
+
if (wasFocused) {
|
|
3231
|
+
target.blur();
|
|
2683
3232
|
}
|
|
2684
|
-
|
|
3233
|
+
target.focus({
|
|
3234
|
+
preventScroll: true
|
|
3235
|
+
});
|
|
3236
|
+
} finally {
|
|
3237
|
+
target.style.opacity = previousOpacity;
|
|
3238
|
+
target.style.transform = previousTransform;
|
|
3239
|
+
target.style.transition = previousTransition;
|
|
2685
3240
|
}
|
|
2686
|
-
|
|
2687
|
-
|
|
3241
|
+
}
|
|
3242
|
+
function findKeyboardScrollTarget(target, root) {
|
|
3243
|
+
// Start at the parent: scrolling the focused field's own content (an overflowing
|
|
3244
|
+
// textarea is scrollable itself) can never move its box out from under the keyboard.
|
|
3245
|
+
// `getParentNode` crosses shadow boundaries so an input inside a shadow root still reaches
|
|
3246
|
+
// the drawer body scroller. Prefer an already-scrollable ancestor, then fall back to one
|
|
3247
|
+
// that only becomes scrollable once keyboard slack is added (overflow intent without
|
|
3248
|
+
// current overflow).
|
|
3249
|
+
const scrollStart = getParentNode(target);
|
|
3250
|
+
return findScrollableTouchTarget(scrollStart, root, 'vertical') ?? findScrollableTouchTarget(scrollStart, root, 'vertical', true);
|
|
3251
|
+
}
|
|
3252
|
+
function getKeyboardVisualViewport(win) {
|
|
3253
|
+
const visualViewport = win.visualViewport;
|
|
3254
|
+
if (!visualViewport || visualViewport.scale !== 1) {
|
|
3255
|
+
return null;
|
|
2688
3256
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
3257
|
+
const reducedHeight = win.innerHeight - visualViewport.height;
|
|
3258
|
+
// Treat small viewport changes as browser chrome movement, not the software keyboard.
|
|
3259
|
+
if (reducedHeight <= KEYBOARD_RESIZE_THRESHOLD) {
|
|
3260
|
+
return null;
|
|
2691
3261
|
}
|
|
2692
|
-
|
|
3262
|
+
const top = Math.max(0, visualViewport.offsetTop);
|
|
3263
|
+
return {
|
|
3264
|
+
top,
|
|
3265
|
+
bottom: Math.min(win.innerHeight, top + visualViewport.height)
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
function getDrawerKeyboardInset(win, keyboardViewport) {
|
|
3269
|
+
return Math.max(0, win.innerHeight - keyboardViewport.bottom);
|
|
3270
|
+
}
|
|
3271
|
+
function isKeyboardVisualViewportOpen(win) {
|
|
3272
|
+
return !win.visualViewport || getKeyboardVisualViewport(win) != null;
|
|
2693
3273
|
}
|
|
2694
3274
|
|
|
2695
3275
|
var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
@@ -2709,6 +3289,7 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
|
2709
3289
|
Title: DrawerTitle,
|
|
2710
3290
|
Trigger: DrawerTrigger,
|
|
2711
3291
|
Viewport: DrawerViewport,
|
|
3292
|
+
VirtualKeyboardProvider: DrawerVirtualKeyboardProvider,
|
|
2712
3293
|
createHandle: createDialogHandle
|
|
2713
3294
|
});
|
|
2714
3295
|
|
|
@@ -2720,8 +3301,9 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
|
2720
3301
|
*/
|
|
2721
3302
|
const MenuArrow = /*#__PURE__*/React.forwardRef(function MenuArrow(componentProps, forwardedRef) {
|
|
2722
3303
|
const {
|
|
2723
|
-
className,
|
|
2724
3304
|
render,
|
|
3305
|
+
className,
|
|
3306
|
+
style,
|
|
2725
3307
|
...elementProps
|
|
2726
3308
|
} = componentProps;
|
|
2727
3309
|
const {
|
|
@@ -2782,6 +3364,7 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
|
|
|
2782
3364
|
checked: checkedProp,
|
|
2783
3365
|
defaultChecked,
|
|
2784
3366
|
onCheckedChange,
|
|
3367
|
+
style,
|
|
2785
3368
|
...elementProps
|
|
2786
3369
|
} = componentProps;
|
|
2787
3370
|
const listItem = useCompositeListItem({
|
|
@@ -2810,7 +3393,7 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
|
|
|
2810
3393
|
id,
|
|
2811
3394
|
store,
|
|
2812
3395
|
nativeButton,
|
|
2813
|
-
nodeId: menuPositionerContext?.nodeId,
|
|
3396
|
+
nodeId: menuPositionerContext?.context.nodeId,
|
|
2814
3397
|
itemMetadata: REGULAR_ITEM
|
|
2815
3398
|
});
|
|
2816
3399
|
const state = React.useMemo(() => ({
|
|
@@ -2818,17 +3401,16 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
|
|
|
2818
3401
|
highlighted,
|
|
2819
3402
|
checked
|
|
2820
3403
|
}), [disabled, highlighted, checked]);
|
|
2821
|
-
|
|
2822
|
-
const details = {
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
};
|
|
3404
|
+
function handleClick(event) {
|
|
3405
|
+
const details = createChangeEventDetails(itemPress, event.nativeEvent, undefined, {
|
|
3406
|
+
preventUnmountOnClose() {}
|
|
3407
|
+
});
|
|
2826
3408
|
onCheckedChange?.(!checked, details);
|
|
2827
3409
|
if (details.isCanceled) {
|
|
2828
3410
|
return;
|
|
2829
3411
|
}
|
|
2830
3412
|
setChecked(currentlyChecked => !currentlyChecked);
|
|
2831
|
-
}
|
|
3413
|
+
}
|
|
2832
3414
|
const element = useRenderElement('div', componentProps, {
|
|
2833
3415
|
state,
|
|
2834
3416
|
stateAttributesMapping: itemMapping,
|
|
@@ -2856,6 +3438,7 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React.forwardRef(function MenuChe
|
|
|
2856
3438
|
const {
|
|
2857
3439
|
render,
|
|
2858
3440
|
className,
|
|
3441
|
+
style,
|
|
2859
3442
|
keepMounted = false,
|
|
2860
3443
|
...elementProps
|
|
2861
3444
|
} = componentProps;
|
|
@@ -2894,25 +3477,6 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React.forwardRef(function MenuChe
|
|
|
2894
3477
|
});
|
|
2895
3478
|
if (process.env.NODE_ENV !== "production") MenuCheckboxItemIndicator.displayName = "MenuCheckboxItemIndicator";
|
|
2896
3479
|
|
|
2897
|
-
/**
|
|
2898
|
-
* Returns a previous value of its argument.
|
|
2899
|
-
* @param value Current value.
|
|
2900
|
-
* @returns Previous value, or null if there is no previous value.
|
|
2901
|
-
*/
|
|
2902
|
-
function usePreviousValue(value) {
|
|
2903
|
-
const [state, setState] = React.useState({
|
|
2904
|
-
current: value,
|
|
2905
|
-
previous: null
|
|
2906
|
-
});
|
|
2907
|
-
if (value !== state.current) {
|
|
2908
|
-
setState({
|
|
2909
|
-
current: value,
|
|
2910
|
-
previous: state.current
|
|
2911
|
-
});
|
|
2912
|
-
}
|
|
2913
|
-
return state.previous;
|
|
2914
|
-
}
|
|
2915
|
-
|
|
2916
3480
|
function getCssDimensions(element) {
|
|
2917
3481
|
const css = getComputedStyle(element);
|
|
2918
3482
|
// In testing environments, the `width` and `height` properties are empty
|
|
@@ -2933,8 +3497,6 @@ function getCssDimensions(element) {
|
|
|
2933
3497
|
};
|
|
2934
3498
|
}
|
|
2935
3499
|
|
|
2936
|
-
const DEFAULT_ENABLED = () => true;
|
|
2937
|
-
|
|
2938
3500
|
/**
|
|
2939
3501
|
* Allows the element to automatically resize based on its content while supporting animations.
|
|
2940
3502
|
*/
|
|
@@ -2944,7 +3506,6 @@ function usePopupAutoResize(parameters) {
|
|
|
2944
3506
|
positionerElement,
|
|
2945
3507
|
content,
|
|
2946
3508
|
mounted,
|
|
2947
|
-
enabled = DEFAULT_ENABLED,
|
|
2948
3509
|
onMeasureLayout: onMeasureLayoutParam,
|
|
2949
3510
|
onMeasureLayoutComplete: onMeasureLayoutCompleteParam,
|
|
2950
3511
|
side,
|
|
@@ -2953,7 +3514,6 @@ function usePopupAutoResize(parameters) {
|
|
|
2953
3514
|
const runOnceAnimationsFinish = useAnimationsFinished(popupElement, true, false);
|
|
2954
3515
|
const animationFrame = useAnimationFrame();
|
|
2955
3516
|
const committedDimensionsRef = React.useRef(null);
|
|
2956
|
-
const liveDimensionsRef = React.useRef(null);
|
|
2957
3517
|
const isInitialRenderRef = React.useRef(true);
|
|
2958
3518
|
const restoreAnchoringStylesRef = React.useRef(NOOP);
|
|
2959
3519
|
const onMeasureLayout = useStableCallback(onMeasureLayoutParam);
|
|
@@ -2977,27 +3537,16 @@ function usePopupAutoResize(parameters) {
|
|
|
2977
3537
|
}, [side, direction]);
|
|
2978
3538
|
useIsoLayoutEffect(() => {
|
|
2979
3539
|
// Reset the state when the popup is closed.
|
|
2980
|
-
if (!mounted
|
|
3540
|
+
if (!mounted) {
|
|
2981
3541
|
restoreAnchoringStylesRef.current = NOOP;
|
|
2982
3542
|
isInitialRenderRef.current = true;
|
|
2983
3543
|
committedDimensionsRef.current = null;
|
|
2984
|
-
liveDimensionsRef.current = null;
|
|
2985
3544
|
return undefined;
|
|
2986
3545
|
}
|
|
2987
3546
|
if (!popupElement || !positionerElement) {
|
|
2988
3547
|
return undefined;
|
|
2989
3548
|
}
|
|
2990
3549
|
restoreAnchoringStylesRef.current = applyElementStyles(popupElement, anchoringStyles);
|
|
2991
|
-
const observer = new ResizeObserver(entries => {
|
|
2992
|
-
const entry = entries[0];
|
|
2993
|
-
if (entry) {
|
|
2994
|
-
liveDimensionsRef.current = {
|
|
2995
|
-
width: Math.ceil(entry.borderBoxSize[0].inlineSize),
|
|
2996
|
-
height: Math.ceil(entry.borderBoxSize[0].blockSize)
|
|
2997
|
-
};
|
|
2998
|
-
}
|
|
2999
|
-
});
|
|
3000
|
-
observer.observe(popupElement);
|
|
3001
3550
|
|
|
3002
3551
|
// Measure the rendered size to enable transitions:
|
|
3003
3552
|
setPopupCssSize(popupElement, 'auto');
|
|
@@ -3029,32 +3578,18 @@ function usePopupAutoResize(parameters) {
|
|
|
3029
3578
|
onMeasureLayoutComplete?.(null, dimensions);
|
|
3030
3579
|
isInitialRenderRef.current = false;
|
|
3031
3580
|
return () => {
|
|
3032
|
-
observer.disconnect();
|
|
3033
3581
|
restoreAnchoringStylesRef.current();
|
|
3034
3582
|
restoreAnchoringStylesRef.current = NOOP;
|
|
3035
3583
|
};
|
|
3036
3584
|
}
|
|
3037
3585
|
|
|
3038
3586
|
// Subsequent renders while open (when `content` changes).
|
|
3039
|
-
setPopupCssSize(popupElement, 'auto');
|
|
3040
3587
|
setPositionerCssSize(positionerElement, 'max-content');
|
|
3041
|
-
const previousDimensions = committedDimensionsRef.current
|
|
3588
|
+
const previousDimensions = committedDimensionsRef.current;
|
|
3042
3589
|
const newDimensions = getCssDimensions(popupElement);
|
|
3043
3590
|
|
|
3044
|
-
// Commit immediately so future content changes have a stable previous size
|
|
3045
|
-
// ResizeObserver runs after this point.
|
|
3591
|
+
// Commit immediately so future content changes have a stable previous size.
|
|
3046
3592
|
committedDimensionsRef.current = newDimensions;
|
|
3047
|
-
if (!previousDimensions) {
|
|
3048
|
-
setPositionerCssSize(positionerElement, newDimensions);
|
|
3049
|
-
restoreMeasurementOverridesIncludingScale();
|
|
3050
|
-
onMeasureLayoutComplete?.(null, newDimensions);
|
|
3051
|
-
return () => {
|
|
3052
|
-
observer.disconnect();
|
|
3053
|
-
animationFrame.cancel();
|
|
3054
|
-
restoreAnchoringStylesRef.current();
|
|
3055
|
-
restoreAnchoringStylesRef.current = NOOP;
|
|
3056
|
-
};
|
|
3057
|
-
}
|
|
3058
3593
|
setPopupCssSize(popupElement, previousDimensions);
|
|
3059
3594
|
restoreMeasurementOverridesIncludingScale();
|
|
3060
3595
|
onMeasureLayoutComplete?.(previousDimensions, newDimensions);
|
|
@@ -3068,13 +3603,12 @@ function usePopupAutoResize(parameters) {
|
|
|
3068
3603
|
}, abortController.signal);
|
|
3069
3604
|
});
|
|
3070
3605
|
return () => {
|
|
3071
|
-
observer.disconnect();
|
|
3072
3606
|
abortController.abort();
|
|
3073
3607
|
animationFrame.cancel();
|
|
3074
3608
|
restoreAnchoringStylesRef.current();
|
|
3075
3609
|
restoreAnchoringStylesRef.current = NOOP;
|
|
3076
3610
|
};
|
|
3077
|
-
}, [content, popupElement, positionerElement, runOnceAnimationsFinish, animationFrame,
|
|
3611
|
+
}, [content, popupElement, positionerElement, runOnceAnimationsFinish, animationFrame, mounted, onMeasureLayout, onMeasureLayoutComplete, anchoringStyles]);
|
|
3078
3612
|
}
|
|
3079
3613
|
function overrideElementStyle(element, property, value) {
|
|
3080
3614
|
const originalValue = element.style.getPropertyValue(property);
|
|
@@ -3157,6 +3691,11 @@ function usePopupViewport(parameters) {
|
|
|
3157
3691
|
}
|
|
3158
3692
|
});
|
|
3159
3693
|
const lastHandledTriggerRef = React.useRef(null);
|
|
3694
|
+
useIsoLayoutEffect(() => {
|
|
3695
|
+
if (!open || !mounted) {
|
|
3696
|
+
lastHandledTriggerRef.current = null;
|
|
3697
|
+
}
|
|
3698
|
+
}, [open, mounted]);
|
|
3160
3699
|
useIsoLayoutEffect(() => {
|
|
3161
3700
|
// When a trigger changes, set the captured children HTML to state,
|
|
3162
3701
|
// so we can render both new and old content.
|
|
@@ -3195,7 +3734,7 @@ function usePopupViewport(parameters) {
|
|
|
3195
3734
|
if (!source) {
|
|
3196
3735
|
return;
|
|
3197
3736
|
}
|
|
3198
|
-
const wrapper =
|
|
3737
|
+
const wrapper = ownerDocument(source).createElement('div');
|
|
3199
3738
|
for (const child of Array.from(source.childNodes)) {
|
|
3200
3739
|
wrapper.appendChild(child.cloneNode(true));
|
|
3201
3740
|
}
|
|
@@ -3367,8 +3906,8 @@ const stateAttributesMapping$2 = {
|
|
|
3367
3906
|
|
|
3368
3907
|
/**
|
|
3369
3908
|
* A viewport for displaying content transitions.
|
|
3370
|
-
* This component is only required if one popup can be opened by multiple triggers, its content
|
|
3371
|
-
* and switching between them is animated.
|
|
3909
|
+
* This component is only required if one popup can be opened by multiple triggers, its content
|
|
3910
|
+
* changes based on the trigger, and switching between them is animated.
|
|
3372
3911
|
* Renders a `<div>` element.
|
|
3373
3912
|
*
|
|
3374
3913
|
* Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
|
|
@@ -3377,6 +3916,7 @@ const MenuViewport = /*#__PURE__*/React.forwardRef(function MenuViewport(compone
|
|
|
3377
3916
|
const {
|
|
3378
3917
|
render,
|
|
3379
3918
|
className,
|
|
3919
|
+
style,
|
|
3380
3920
|
children,
|
|
3381
3921
|
...elementProps
|
|
3382
3922
|
} = componentProps;
|
|
@@ -3447,7 +3987,7 @@ class MenuHandle {
|
|
|
3447
3987
|
* Indicates whether the menu is currently open.
|
|
3448
3988
|
*/
|
|
3449
3989
|
get isOpen() {
|
|
3450
|
-
return this.store.
|
|
3990
|
+
return this.store.select('open');
|
|
3451
3991
|
}
|
|
3452
3992
|
}
|
|
3453
3993
|
|
|
@@ -3520,6 +4060,7 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
3520
4060
|
className,
|
|
3521
4061
|
orientation = 'vertical',
|
|
3522
4062
|
keepMounted = false,
|
|
4063
|
+
style,
|
|
3523
4064
|
...elementProps
|
|
3524
4065
|
} = componentProps;
|
|
3525
4066
|
const {
|
|
@@ -3535,6 +4076,7 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
3535
4076
|
thumbXRef,
|
|
3536
4077
|
handlePointerDown,
|
|
3537
4078
|
handlePointerUp,
|
|
4079
|
+
handleScroll,
|
|
3538
4080
|
rootId,
|
|
3539
4081
|
thumbSize,
|
|
3540
4082
|
hasMeasuredScrollbar
|
|
@@ -3556,7 +4098,12 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
3556
4098
|
};
|
|
3557
4099
|
const direction = useDirection();
|
|
3558
4100
|
const hideTrackUntilMeasured = !hasMeasuredScrollbar && !keepMounted;
|
|
4101
|
+
const isHidden = orientation === 'vertical' ? hiddenState.y : hiddenState.x;
|
|
4102
|
+
const shouldRender = keepMounted || !isHidden;
|
|
3559
4103
|
React.useEffect(() => {
|
|
4104
|
+
if (!shouldRender) {
|
|
4105
|
+
return undefined;
|
|
4106
|
+
}
|
|
3560
4107
|
const viewportEl = viewportRef.current;
|
|
3561
4108
|
const scrollbarEl = orientation === 'vertical' ? scrollbarYRef.current : scrollbarXRef.current;
|
|
3562
4109
|
if (!scrollbarEl) {
|
|
@@ -3566,34 +4113,34 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
3566
4113
|
if (!viewportEl || !scrollbarEl || event.ctrlKey) {
|
|
3567
4114
|
return;
|
|
3568
4115
|
}
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
}
|
|
3574
|
-
} else if (viewportEl.scrollLeft === 0 && event.deltaX < 0) {
|
|
4116
|
+
const horizontal = orientation === 'horizontal';
|
|
4117
|
+
const scrollProperty = horizontal ? 'scrollLeft' : 'scrollTop';
|
|
4118
|
+
const delta = horizontal ? event.deltaX : event.deltaY;
|
|
4119
|
+
if (delta === 0) {
|
|
3575
4120
|
return;
|
|
3576
4121
|
}
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
4122
|
+
const maxScroll = horizontal ? viewportEl.scrollWidth - viewportEl.clientWidth : viewportEl.scrollHeight - viewportEl.clientHeight;
|
|
4123
|
+
// RTL horizontal scrolling uses a negative `scrollLeft` range, from 0 to `-maxScroll`.
|
|
4124
|
+
const minScroll = horizontal && direction === 'rtl' ? -maxScroll : 0;
|
|
4125
|
+
const maxScrollValue = horizontal && direction === 'rtl' ? 0 : maxScroll;
|
|
4126
|
+
const scrollValue = viewportEl[scrollProperty];
|
|
4127
|
+
|
|
4128
|
+
// At an edge (or with no overflow), let the wheel event chain to the
|
|
4129
|
+
// parent/page instead of swallowing it via `preventDefault`.
|
|
4130
|
+
if (scrollValue <= minScroll && delta < 0 || scrollValue >= maxScrollValue && delta > 0) {
|
|
3582
4131
|
return;
|
|
3583
4132
|
}
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
viewportEl.scrollLeft
|
|
3588
|
-
|
|
4133
|
+
event.preventDefault();
|
|
4134
|
+
viewportEl[scrollProperty] = Math.min(maxScrollValue, Math.max(minScroll, scrollValue + delta));
|
|
4135
|
+
handleScroll({
|
|
4136
|
+
x: viewportEl.scrollLeft,
|
|
4137
|
+
y: viewportEl.scrollTop
|
|
4138
|
+
});
|
|
3589
4139
|
}
|
|
3590
|
-
|
|
4140
|
+
return addEventListener(scrollbarEl, 'wheel', handleWheel, {
|
|
3591
4141
|
passive: false
|
|
3592
4142
|
});
|
|
3593
|
-
|
|
3594
|
-
scrollbarEl.removeEventListener('wheel', handleWheel);
|
|
3595
|
-
};
|
|
3596
|
-
}, [orientation, scrollbarXRef, scrollbarYRef, viewportRef]);
|
|
4143
|
+
}, [direction, handleScroll, orientation, scrollbarXRef, scrollbarYRef, shouldRender, viewportRef]);
|
|
3597
4144
|
const props = {
|
|
3598
4145
|
...(rootId && {
|
|
3599
4146
|
'data-id': `${rootId}-scrollbar`
|
|
@@ -3602,9 +4149,12 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
3602
4149
|
if (event.button !== 0) {
|
|
3603
4150
|
return;
|
|
3604
4151
|
}
|
|
4152
|
+
const target = getTarget(event.nativeEvent);
|
|
4153
|
+
const thumb = orientation === 'vertical' ? thumbYRef.current : thumbXRef.current;
|
|
3605
4154
|
|
|
3606
|
-
// Ignore clicks on thumb
|
|
3607
|
-
|
|
4155
|
+
// Ignore clicks on thumb, including cases where React retargets the
|
|
4156
|
+
// synthetic event to the track host across a shadow boundary.
|
|
4157
|
+
if (thumb && contains(thumb, target)) {
|
|
3608
4158
|
return;
|
|
3609
4159
|
}
|
|
3610
4160
|
if (!viewportRef.current) {
|
|
@@ -3649,9 +4199,16 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
3649
4199
|
}
|
|
3650
4200
|
viewportRef.current.scrollLeft = newScrollLeft;
|
|
3651
4201
|
}
|
|
4202
|
+
handleScroll({
|
|
4203
|
+
x: viewportRef.current.scrollLeft,
|
|
4204
|
+
y: viewportRef.current.scrollTop
|
|
4205
|
+
});
|
|
3652
4206
|
handlePointerDown(event);
|
|
3653
4207
|
},
|
|
3654
4208
|
onPointerUp: handlePointerUp,
|
|
4209
|
+
// Mirror `onPointerUp` so a browser-cancelled gesture on the track (no thumb
|
|
4210
|
+
// child captures the pointer) still clears the drag state.
|
|
4211
|
+
onPointerCancel: handlePointerUp,
|
|
3655
4212
|
style: {
|
|
3656
4213
|
position: 'absolute',
|
|
3657
4214
|
touchAction: 'none',
|
|
@@ -3681,8 +4238,6 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
3681
4238
|
const contextValue = React.useMemo(() => ({
|
|
3682
4239
|
orientation
|
|
3683
4240
|
}), [orientation]);
|
|
3684
|
-
const isHidden = orientation === 'vertical' ? hiddenState.y : hiddenState.x;
|
|
3685
|
-
const shouldRender = keepMounted || !isHidden;
|
|
3686
4241
|
if (!shouldRender) {
|
|
3687
4242
|
return null;
|
|
3688
4243
|
}
|
|
@@ -3703,34 +4258,42 @@ const ScrollAreaContent = /*#__PURE__*/React.forwardRef(function ScrollAreaConte
|
|
|
3703
4258
|
const {
|
|
3704
4259
|
render,
|
|
3705
4260
|
className,
|
|
4261
|
+
style,
|
|
3706
4262
|
...elementProps
|
|
3707
4263
|
} = componentProps;
|
|
3708
|
-
const contentWrapperRef = React.useRef(null);
|
|
3709
4264
|
const {
|
|
3710
4265
|
computeThumbPosition
|
|
3711
4266
|
} = useScrollAreaViewportContext();
|
|
3712
4267
|
const {
|
|
4268
|
+
hasMeasuredScrollbar,
|
|
3713
4269
|
viewportState
|
|
3714
4270
|
} = useScrollAreaRootContext();
|
|
4271
|
+
const contentWrapperRef = React.useRef(null);
|
|
4272
|
+
const computeOnInitialResizeRef = React.useRef(hasMeasuredScrollbar);
|
|
3715
4273
|
useIsoLayoutEffect(() => {
|
|
3716
4274
|
if (typeof ResizeObserver === 'undefined') {
|
|
3717
4275
|
return undefined;
|
|
3718
4276
|
}
|
|
3719
4277
|
let hasInitialized = false;
|
|
3720
|
-
const
|
|
3721
|
-
// ResizeObserver fires once upon observing, so we skip the initial call
|
|
3722
|
-
// to avoid double-calculating the thumb position on mount.
|
|
4278
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
3723
4279
|
if (!hasInitialized) {
|
|
3724
4280
|
hasInitialized = true;
|
|
3725
|
-
|
|
4281
|
+
|
|
4282
|
+
// ResizeObserver fires once upon observing. Skip that initial call to avoid
|
|
4283
|
+
// double-calculating the thumb position on mount, unless the content mounted
|
|
4284
|
+
// after the viewport's initial measurement (in which case this fire is what
|
|
4285
|
+
// brings the overflow state in sync).
|
|
4286
|
+
if (!computeOnInitialResizeRef.current) {
|
|
4287
|
+
return;
|
|
4288
|
+
}
|
|
3726
4289
|
}
|
|
3727
4290
|
computeThumbPosition();
|
|
3728
4291
|
});
|
|
3729
4292
|
if (contentWrapperRef.current) {
|
|
3730
|
-
|
|
4293
|
+
resizeObserver.observe(contentWrapperRef.current);
|
|
3731
4294
|
}
|
|
3732
4295
|
return () => {
|
|
3733
|
-
|
|
4296
|
+
resizeObserver.disconnect();
|
|
3734
4297
|
};
|
|
3735
4298
|
}, [computeThumbPosition]);
|
|
3736
4299
|
const element = useRenderElement('div', componentProps, {
|
|
@@ -3758,6 +4321,7 @@ const ScrollAreaThumb = /*#__PURE__*/React.forwardRef(function ScrollAreaThumb(c
|
|
|
3758
4321
|
const {
|
|
3759
4322
|
render,
|
|
3760
4323
|
className,
|
|
4324
|
+
style,
|
|
3761
4325
|
...elementProps
|
|
3762
4326
|
} = componentProps;
|
|
3763
4327
|
const {
|
|
@@ -3768,29 +4332,34 @@ const ScrollAreaThumb = /*#__PURE__*/React.forwardRef(function ScrollAreaThumb(c
|
|
|
3768
4332
|
handlePointerUp,
|
|
3769
4333
|
setScrollingX,
|
|
3770
4334
|
setScrollingY,
|
|
4335
|
+
scrollingX,
|
|
4336
|
+
scrollingY,
|
|
3771
4337
|
hasMeasuredScrollbar
|
|
3772
4338
|
} = useScrollAreaRootContext();
|
|
3773
4339
|
const {
|
|
3774
4340
|
orientation
|
|
3775
4341
|
} = useScrollAreaScrollbarContext();
|
|
3776
4342
|
const state = {
|
|
4343
|
+
scrolling: orientation === 'horizontal' ? scrollingX : scrollingY,
|
|
3777
4344
|
orientation
|
|
3778
4345
|
};
|
|
4346
|
+
function endDrag(event) {
|
|
4347
|
+
if (orientation === 'vertical') {
|
|
4348
|
+
setScrollingY(false);
|
|
4349
|
+
}
|
|
4350
|
+
if (orientation === 'horizontal') {
|
|
4351
|
+
setScrollingX(false);
|
|
4352
|
+
}
|
|
4353
|
+
handlePointerUp(event);
|
|
4354
|
+
}
|
|
3779
4355
|
const element = useRenderElement('div', componentProps, {
|
|
3780
4356
|
ref: [forwardedRef, orientation === 'vertical' ? thumbYRef : thumbXRef],
|
|
3781
4357
|
state,
|
|
3782
4358
|
props: [{
|
|
3783
4359
|
onPointerDown: handlePointerDown,
|
|
3784
4360
|
onPointerMove: handlePointerMove,
|
|
3785
|
-
onPointerUp
|
|
3786
|
-
|
|
3787
|
-
setScrollingY(false);
|
|
3788
|
-
}
|
|
3789
|
-
if (orientation === 'horizontal') {
|
|
3790
|
-
setScrollingX(false);
|
|
3791
|
-
}
|
|
3792
|
-
handlePointerUp(event);
|
|
3793
|
-
},
|
|
4361
|
+
onPointerUp: endDrag,
|
|
4362
|
+
onPointerCancel: endDrag,
|
|
3794
4363
|
style: {
|
|
3795
4364
|
visibility: hasMeasuredScrollbar ? undefined : 'hidden',
|
|
3796
4365
|
...(orientation === 'vertical' && {
|
|
@@ -3816,6 +4385,7 @@ const ScrollAreaCorner = /*#__PURE__*/React.forwardRef(function ScrollAreaCorner
|
|
|
3816
4385
|
const {
|
|
3817
4386
|
render,
|
|
3818
4387
|
className,
|
|
4388
|
+
style,
|
|
3819
4389
|
...elementProps
|
|
3820
4390
|
} = componentProps;
|
|
3821
4391
|
const {
|
|
@@ -3894,32 +4464,32 @@ function useNumberFieldScrubAreaContext() {
|
|
|
3894
4464
|
return context;
|
|
3895
4465
|
}
|
|
3896
4466
|
|
|
3897
|
-
// Calculates the
|
|
4467
|
+
// Calculates the bounds the virtual cursor wraps within, as absolute edge coordinates.
|
|
3898
4468
|
function getViewportRect(teleportDistance, scrubAreaEl) {
|
|
3899
4469
|
const win = getWindow(scrubAreaEl);
|
|
3900
|
-
|
|
3901
|
-
|
|
4470
|
+
if (teleportDistance != null) {
|
|
4471
|
+
const rect = scrubAreaEl.getBoundingClientRect();
|
|
3902
4472
|
return {
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
4473
|
+
left: rect.left - teleportDistance / 2,
|
|
4474
|
+
top: rect.top - teleportDistance / 2,
|
|
4475
|
+
right: rect.right + teleportDistance / 2,
|
|
4476
|
+
bottom: rect.bottom + teleportDistance / 2
|
|
3907
4477
|
};
|
|
3908
4478
|
}
|
|
3909
4479
|
const vV = win.visualViewport;
|
|
3910
4480
|
if (vV) {
|
|
3911
4481
|
return {
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
4482
|
+
left: vV.offsetLeft,
|
|
4483
|
+
top: vV.offsetTop,
|
|
4484
|
+
right: vV.offsetLeft + vV.width,
|
|
4485
|
+
bottom: vV.offsetTop + vV.height
|
|
3916
4486
|
};
|
|
3917
4487
|
}
|
|
3918
4488
|
return {
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
4489
|
+
left: 0,
|
|
4490
|
+
top: 0,
|
|
4491
|
+
right: win.document.documentElement.clientWidth,
|
|
4492
|
+
bottom: win.document.documentElement.clientHeight
|
|
3923
4493
|
};
|
|
3924
4494
|
}
|
|
3925
4495
|
|
|
@@ -3936,12 +4506,21 @@ function subscribeToVisualViewportResize(element, visualScaleRef) {
|
|
|
3936
4506
|
}
|
|
3937
4507
|
}
|
|
3938
4508
|
handleVisualResize();
|
|
3939
|
-
|
|
3940
|
-
return () => {
|
|
3941
|
-
vV.removeEventListener('resize', handleVisualResize);
|
|
3942
|
-
};
|
|
4509
|
+
return addEventListener(vV, 'resize', handleVisualResize);
|
|
3943
4510
|
}
|
|
3944
4511
|
|
|
4512
|
+
const SCRUB_AREA_STYLE = {
|
|
4513
|
+
touchAction: 'none',
|
|
4514
|
+
WebkitUserSelect: 'none',
|
|
4515
|
+
userSelect: 'none'
|
|
4516
|
+
};
|
|
4517
|
+
|
|
4518
|
+
/**
|
|
4519
|
+
* An interactive area where the user can click and drag to change the field value.
|
|
4520
|
+
* Renders a `<span>` element.
|
|
4521
|
+
*
|
|
4522
|
+
* Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
|
|
4523
|
+
*/
|
|
3945
4524
|
const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldScrubArea(componentProps, forwardedRef) {
|
|
3946
4525
|
const {
|
|
3947
4526
|
render,
|
|
@@ -3949,6 +4528,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
3949
4528
|
direction = 'horizontal',
|
|
3950
4529
|
pixelSensitivity = 2,
|
|
3951
4530
|
teleportDistance,
|
|
4531
|
+
style,
|
|
3952
4532
|
...elementProps
|
|
3953
4533
|
} = componentProps;
|
|
3954
4534
|
const {
|
|
@@ -3958,6 +4538,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
3958
4538
|
readOnly,
|
|
3959
4539
|
inputRef,
|
|
3960
4540
|
incrementValue,
|
|
4541
|
+
allowInputSyncRef,
|
|
3961
4542
|
getStepAmount,
|
|
3962
4543
|
onValueCommitted,
|
|
3963
4544
|
lastChangedValueRef,
|
|
@@ -4005,15 +4586,15 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4005
4586
|
};
|
|
4006
4587
|
const cursorWidth = virtualCursor.offsetWidth;
|
|
4007
4588
|
const cursorHeight = virtualCursor.offsetHeight;
|
|
4008
|
-
if (newCoords.x + cursorWidth / 2 < rect.
|
|
4009
|
-
newCoords.x = rect.
|
|
4010
|
-
} else if (newCoords.x + cursorWidth / 2 > rect.
|
|
4011
|
-
newCoords.x = rect.
|
|
4589
|
+
if (newCoords.x + cursorWidth / 2 < rect.left) {
|
|
4590
|
+
newCoords.x = rect.right - cursorWidth / 2;
|
|
4591
|
+
} else if (newCoords.x + cursorWidth / 2 > rect.right) {
|
|
4592
|
+
newCoords.x = rect.left - cursorWidth / 2;
|
|
4012
4593
|
}
|
|
4013
|
-
if (newCoords.y + cursorHeight / 2 < rect.
|
|
4014
|
-
newCoords.y = rect.
|
|
4015
|
-
} else if (newCoords.y + cursorHeight / 2 > rect.
|
|
4016
|
-
newCoords.y = rect.
|
|
4594
|
+
if (newCoords.y + cursorHeight / 2 < rect.top) {
|
|
4595
|
+
newCoords.y = rect.bottom - cursorHeight / 2;
|
|
4596
|
+
} else if (newCoords.y + cursorHeight / 2 > rect.bottom) {
|
|
4597
|
+
newCoords.y = rect.top - cursorHeight / 2;
|
|
4017
4598
|
}
|
|
4018
4599
|
virtualCursorCoords.current = newCoords;
|
|
4019
4600
|
updateCursorTransform(newCoords.x, newCoords.y);
|
|
@@ -4056,8 +4637,10 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4056
4637
|
|
|
4057
4638
|
// Manually dispatch a click event if no movement happened, since
|
|
4058
4639
|
// preventDefault on pointerdown prevents the browser click event.
|
|
4059
|
-
|
|
4060
|
-
|
|
4640
|
+
const pointerDownTarget = pointerDownTargetRef.current;
|
|
4641
|
+
const input = inputRef.current;
|
|
4642
|
+
if (!didMoveRef.current && pointerDownTarget != null && input) {
|
|
4643
|
+
pointerDownTarget.dispatchEvent(new (getWindow(input).MouseEvent)('click', {
|
|
4061
4644
|
bubbles: true,
|
|
4062
4645
|
cancelable: true
|
|
4063
4646
|
}));
|
|
@@ -4066,7 +4649,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4066
4649
|
pointerDownTargetRef.current = null;
|
|
4067
4650
|
}
|
|
4068
4651
|
}
|
|
4069
|
-
if (
|
|
4652
|
+
if (gecko) {
|
|
4070
4653
|
// Firefox needs a small delay here when soft-clicking as the pointer
|
|
4071
4654
|
// lock will not release otherwise.
|
|
4072
4655
|
exitPointerLockTimeout.start(20, handler);
|
|
@@ -4091,9 +4674,10 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4091
4674
|
cumulativeDelta = 0;
|
|
4092
4675
|
didMoveRef.current = true;
|
|
4093
4676
|
const dValue = direction === 'vertical' ? -movementY : movementX;
|
|
4094
|
-
const stepAmount = getStepAmount(event)
|
|
4677
|
+
const stepAmount = getStepAmount(event);
|
|
4095
4678
|
const rawAmount = dValue * stepAmount;
|
|
4096
4679
|
if (rawAmount !== 0) {
|
|
4680
|
+
allowInputSyncRef.current = true;
|
|
4097
4681
|
incrementValue(Math.abs(rawAmount), {
|
|
4098
4682
|
direction: rawAmount >= 0 ? 1 : -1,
|
|
4099
4683
|
event,
|
|
@@ -4103,14 +4687,26 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4103
4687
|
}
|
|
4104
4688
|
}
|
|
4105
4689
|
const win = getWindow(inputRef.current);
|
|
4106
|
-
|
|
4107
|
-
win.addEventListener('pointermove', handleScrubPointerMove, true);
|
|
4690
|
+
const unsubscribe = mergeCleanups(addEventListener(win, 'pointerup', handleScrubPointerUp, true), addEventListener(win, 'pointermove', handleScrubPointerMove, true));
|
|
4108
4691
|
return () => {
|
|
4109
4692
|
exitPointerLockTimeout.clear();
|
|
4110
|
-
|
|
4111
|
-
win.removeEventListener('pointermove', handleScrubPointerMove, true);
|
|
4693
|
+
unsubscribe();
|
|
4112
4694
|
};
|
|
4113
|
-
}, [disabled, readOnly, incrementValue, isScrubbing, getStepAmount, inputRef, onScrubbingChange, onScrub, direction, pixelSensitivity, lastChangedValueRef, onValueCommitted, valueRef, exitPointerLockTimeout]);
|
|
4695
|
+
}, [disabled, readOnly, allowInputSyncRef, incrementValue, isScrubbing, getStepAmount, inputRef, onScrubbingChange, onScrub, direction, pixelSensitivity, lastChangedValueRef, onValueCommitted, valueRef, exitPointerLockTimeout]);
|
|
4696
|
+
|
|
4697
|
+
// If the scrub area unmounts mid-scrub, release pointer lock and clear the root's scrubbing
|
|
4698
|
+
// state so it doesn't stay locked or stuck. (No commit: there's no pointer release here.)
|
|
4699
|
+
React.useEffect(() => () => {
|
|
4700
|
+
if (isScrubbingRef.current) {
|
|
4701
|
+
isScrubbingRef.current = false;
|
|
4702
|
+
setRootScrubbing(false);
|
|
4703
|
+
try {
|
|
4704
|
+
ownerDocument(scrubAreaRef.current).exitPointerLock();
|
|
4705
|
+
} catch {
|
|
4706
|
+
// Ignore errors.
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
}, [setRootScrubbing]);
|
|
4114
4710
|
|
|
4115
4711
|
// Prevent scrolling using touch input when scrubbing.
|
|
4116
4712
|
React.useEffect(function registerScrubberTouchPreventListener() {
|
|
@@ -4123,18 +4719,11 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4123
4719
|
event.preventDefault();
|
|
4124
4720
|
}
|
|
4125
4721
|
}
|
|
4126
|
-
|
|
4127
|
-
return () => {
|
|
4128
|
-
element.removeEventListener('touchstart', handleTouchStart);
|
|
4129
|
-
};
|
|
4722
|
+
return addEventListener(element, 'touchstart', handleTouchStart);
|
|
4130
4723
|
}, [disabled, readOnly]);
|
|
4131
4724
|
const defaultProps = {
|
|
4132
4725
|
role: 'presentation',
|
|
4133
|
-
style:
|
|
4134
|
-
touchAction: 'none',
|
|
4135
|
-
WebkitUserSelect: 'none',
|
|
4136
|
-
userSelect: 'none'
|
|
4137
|
-
},
|
|
4726
|
+
style: SCRUB_AREA_STYLE,
|
|
4138
4727
|
async onPointerDown(event) {
|
|
4139
4728
|
const isMainButton = !event.button || event.button === 0;
|
|
4140
4729
|
if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
|
|
@@ -4148,11 +4737,11 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4148
4737
|
}
|
|
4149
4738
|
isScrubbingRef.current = true;
|
|
4150
4739
|
didMoveRef.current = false;
|
|
4151
|
-
pointerDownTargetRef.current = event.
|
|
4740
|
+
pointerDownTargetRef.current = getTarget(event.nativeEvent);
|
|
4152
4741
|
onScrubbingChange(true, event.nativeEvent);
|
|
4153
4742
|
|
|
4154
4743
|
// WebKit causes significant layout shift with the native message, so we can't use it.
|
|
4155
|
-
if (!isTouch && !
|
|
4744
|
+
if (!isTouch && !webkit) {
|
|
4156
4745
|
try {
|
|
4157
4746
|
// Avoid non-deterministic errors in testing environments. This error sometimes
|
|
4158
4747
|
// appears:
|
|
@@ -4162,10 +4751,11 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4162
4751
|
} catch (error) {
|
|
4163
4752
|
setIsPointerLockDenied(true);
|
|
4164
4753
|
} finally {
|
|
4754
|
+
// `onScrubbingChange` already wraps its state updates in `flushSync`, so re-emit the
|
|
4755
|
+
// scrubbing state directly (no extra nested `flushSync`) to reflect the resolved
|
|
4756
|
+
// pointer-lock result on the cursor.
|
|
4165
4757
|
if (isScrubbingRef.current) {
|
|
4166
|
-
|
|
4167
|
-
onScrubbingChange(true, event.nativeEvent);
|
|
4168
|
-
});
|
|
4758
|
+
onScrubbingChange(true, event.nativeEvent);
|
|
4169
4759
|
}
|
|
4170
4760
|
}
|
|
4171
4761
|
}
|
|
@@ -4181,12 +4771,8 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4181
4771
|
isScrubbing,
|
|
4182
4772
|
isTouchInput,
|
|
4183
4773
|
isPointerLockDenied,
|
|
4184
|
-
scrubAreaCursorRef
|
|
4185
|
-
|
|
4186
|
-
direction,
|
|
4187
|
-
pixelSensitivity,
|
|
4188
|
-
teleportDistance
|
|
4189
|
-
}), [isScrubbing, isTouchInput, isPointerLockDenied, direction, pixelSensitivity, teleportDistance]);
|
|
4774
|
+
scrubAreaCursorRef
|
|
4775
|
+
}), [isScrubbing, isTouchInput, isPointerLockDenied]);
|
|
4190
4776
|
return /*#__PURE__*/jsx(NumberFieldScrubAreaContext.Provider, {
|
|
4191
4777
|
value: contextValue,
|
|
4192
4778
|
children: element
|
|
@@ -4194,6 +4780,13 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
4194
4780
|
});
|
|
4195
4781
|
if (process.env.NODE_ENV !== "production") NumberFieldScrubArea.displayName = "NumberFieldScrubArea";
|
|
4196
4782
|
|
|
4783
|
+
const CURSOR_STYLE = {
|
|
4784
|
+
position: 'fixed',
|
|
4785
|
+
top: 0,
|
|
4786
|
+
left: 0,
|
|
4787
|
+
pointerEvents: 'none'
|
|
4788
|
+
};
|
|
4789
|
+
|
|
4197
4790
|
/**
|
|
4198
4791
|
* A custom element to display instead of the native cursor while using the scrub area.
|
|
4199
4792
|
* Renders a `<span>` element.
|
|
@@ -4207,6 +4800,7 @@ const NumberFieldScrubAreaCursor = /*#__PURE__*/React.forwardRef(function Number
|
|
|
4207
4800
|
const {
|
|
4208
4801
|
render,
|
|
4209
4802
|
className,
|
|
4803
|
+
style,
|
|
4210
4804
|
...elementProps
|
|
4211
4805
|
} = componentProps;
|
|
4212
4806
|
const {
|
|
@@ -4219,19 +4813,14 @@ const NumberFieldScrubAreaCursor = /*#__PURE__*/React.forwardRef(function Number
|
|
|
4219
4813
|
scrubAreaCursorRef
|
|
4220
4814
|
} = useNumberFieldScrubAreaContext();
|
|
4221
4815
|
const [domElement, setDomElement] = React.useState(null);
|
|
4222
|
-
const shouldRender = isScrubbing && !
|
|
4816
|
+
const shouldRender = isScrubbing && !webkit && !isTouchInput && !isPointerLockDenied;
|
|
4223
4817
|
const element = useRenderElement('span', componentProps, {
|
|
4224
4818
|
enabled: shouldRender,
|
|
4225
4819
|
ref: [forwardedRef, scrubAreaCursorRef, setDomElement],
|
|
4226
4820
|
state,
|
|
4227
4821
|
props: [{
|
|
4228
4822
|
role: 'presentation',
|
|
4229
|
-
style:
|
|
4230
|
-
position: 'fixed',
|
|
4231
|
-
top: 0,
|
|
4232
|
-
left: 0,
|
|
4233
|
-
pointerEvents: 'none'
|
|
4234
|
-
}
|
|
4823
|
+
style: CURSOR_STYLE
|
|
4235
4824
|
}, elementProps],
|
|
4236
4825
|
stateAttributesMapping: stateAttributesMapping$6
|
|
4237
4826
|
});
|
|
@@ -4260,6 +4849,7 @@ const SelectLabel = /*#__PURE__*/React.forwardRef(function SelectLabel(component
|
|
|
4260
4849
|
const {
|
|
4261
4850
|
render,
|
|
4262
4851
|
className,
|
|
4852
|
+
style,
|
|
4263
4853
|
...elementProps
|
|
4264
4854
|
} = componentProps;
|
|
4265
4855
|
// Keep label id derived from the root and ignore runtime `id` overrides from untyped consumers.
|
|
@@ -4296,8 +4886,9 @@ if (process.env.NODE_ENV !== "production") SelectLabel.displayName = "SelectLabe
|
|
|
4296
4886
|
*/
|
|
4297
4887
|
const SelectIcon = /*#__PURE__*/React.forwardRef(function SelectIcon(componentProps, forwardedRef) {
|
|
4298
4888
|
const {
|
|
4299
|
-
className,
|
|
4300
4889
|
render,
|
|
4890
|
+
className,
|
|
4891
|
+
style,
|
|
4301
4892
|
...elementProps
|
|
4302
4893
|
} = componentProps;
|
|
4303
4894
|
const {
|
|
@@ -4326,15 +4917,16 @@ const stateAttributesMapping$1 = {
|
|
|
4326
4917
|
};
|
|
4327
4918
|
|
|
4328
4919
|
/**
|
|
4329
|
-
* An overlay displayed beneath the
|
|
4920
|
+
* An overlay displayed beneath the select popup.
|
|
4330
4921
|
* Renders a `<div>` element.
|
|
4331
4922
|
*
|
|
4332
4923
|
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
4333
4924
|
*/
|
|
4334
4925
|
const SelectBackdrop = /*#__PURE__*/React.forwardRef(function SelectBackdrop(componentProps, forwardedRef) {
|
|
4335
4926
|
const {
|
|
4336
|
-
className,
|
|
4337
4927
|
render,
|
|
4928
|
+
className,
|
|
4929
|
+
style,
|
|
4338
4930
|
...elementProps
|
|
4339
4931
|
} = componentProps;
|
|
4340
4932
|
const {
|
|
@@ -4377,8 +4969,9 @@ const stateAttributesMapping = {
|
|
|
4377
4969
|
*/
|
|
4378
4970
|
const SelectArrow = /*#__PURE__*/React.forwardRef(function SelectArrow(componentProps, forwardedRef) {
|
|
4379
4971
|
const {
|
|
4380
|
-
className,
|
|
4381
4972
|
render,
|
|
4973
|
+
className,
|
|
4974
|
+
style,
|
|
4382
4975
|
...elementProps
|
|
4383
4976
|
} = componentProps;
|
|
4384
4977
|
const {
|
|
@@ -4392,7 +4985,7 @@ const SelectArrow = /*#__PURE__*/React.forwardRef(function SelectArrow(component
|
|
|
4392
4985
|
arrowUncentered,
|
|
4393
4986
|
alignItemWithTriggerActive
|
|
4394
4987
|
} = useSelectPositionerContext();
|
|
4395
|
-
const open = useStore(store, selectors.open
|
|
4988
|
+
const open = useStore(store, selectors.open);
|
|
4396
4989
|
const state = {
|
|
4397
4990
|
open,
|
|
4398
4991
|
side,
|
|
@@ -4422,10 +5015,12 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
|
|
|
4422
5015
|
const {
|
|
4423
5016
|
render,
|
|
4424
5017
|
className,
|
|
5018
|
+
style,
|
|
4425
5019
|
direction,
|
|
4426
5020
|
keepMounted = false,
|
|
4427
5021
|
...elementProps
|
|
4428
5022
|
} = componentProps;
|
|
5023
|
+
const isUp = direction === 'up';
|
|
4429
5024
|
const {
|
|
4430
5025
|
store,
|
|
4431
5026
|
popupRef,
|
|
@@ -4438,15 +5033,16 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
|
|
|
4438
5033
|
scrollDownArrowRef,
|
|
4439
5034
|
scrollUpArrowRef
|
|
4440
5035
|
} = useSelectPositionerContext();
|
|
4441
|
-
const visibleSelector =
|
|
5036
|
+
const visibleSelector = isUp ? selectors.scrollUpArrowVisible : selectors.scrollDownArrowVisible;
|
|
4442
5037
|
const stateVisible = useStore(store, visibleSelector);
|
|
4443
5038
|
const openMethod = useStore(store, selectors.openMethod);
|
|
4444
5039
|
|
|
4445
5040
|
// Scroll arrows are disabled for touch modality as they are a hover-only element.
|
|
4446
5041
|
const visible = stateVisible && openMethod !== 'touch';
|
|
4447
5042
|
const timeout = useTimeout();
|
|
4448
|
-
const scrollArrowRef =
|
|
5043
|
+
const scrollArrowRef = isUp ? scrollUpArrowRef : scrollDownArrowRef;
|
|
4449
5044
|
const {
|
|
5045
|
+
mounted,
|
|
4450
5046
|
transitionStatus,
|
|
4451
5047
|
setMounted
|
|
4452
5048
|
} = useTransitionStatus(visible);
|
|
@@ -4479,7 +5075,7 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
|
|
|
4479
5075
|
};
|
|
4480
5076
|
const defaultProps = {
|
|
4481
5077
|
'aria-hidden': true,
|
|
4482
|
-
children:
|
|
5078
|
+
children: isUp ? '▲' : '▼',
|
|
4483
5079
|
style: {
|
|
4484
5080
|
position: 'absolute'
|
|
4485
5081
|
},
|
|
@@ -4495,72 +5091,25 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
|
|
|
4495
5091
|
}
|
|
4496
5092
|
store.set('activeIndex', null);
|
|
4497
5093
|
handleScrollArrowVisibility();
|
|
4498
|
-
const
|
|
4499
|
-
const
|
|
4500
|
-
const
|
|
5094
|
+
const maxScrollTop = getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
|
|
5095
|
+
const scrollTop = normalizeScrollOffset(scroller.scrollTop, maxScrollTop);
|
|
5096
|
+
const isScrolledToEdge = scrollTop === (isUp ? 0 : maxScrollTop);
|
|
5097
|
+
const items = listRef.current;
|
|
5098
|
+
if (scrollTop !== scroller.scrollTop) {
|
|
5099
|
+
scroller.scrollTop = scrollTop;
|
|
5100
|
+
}
|
|
4501
5101
|
|
|
4502
5102
|
// Fallback when there are no items registered yet.
|
|
4503
|
-
if (
|
|
4504
|
-
|
|
4505
|
-
store.set('scrollUpArrowVisible', !isScrolledToTop);
|
|
4506
|
-
} else {
|
|
4507
|
-
store.set('scrollDownArrowVisible', !isScrolledToBottom);
|
|
4508
|
-
}
|
|
5103
|
+
if (items.length === 0) {
|
|
5104
|
+
store.set(isUp ? 'scrollUpArrowVisible' : 'scrollDownArrowVisible', !isScrolledToEdge);
|
|
4509
5105
|
}
|
|
4510
|
-
if (
|
|
5106
|
+
if (isScrolledToEdge) {
|
|
4511
5107
|
timeout.clear();
|
|
4512
5108
|
return;
|
|
4513
5109
|
}
|
|
4514
|
-
if (
|
|
4515
|
-
const items = listRef.current;
|
|
5110
|
+
if (items.length > 0) {
|
|
4516
5111
|
const scrollArrowHeight = scrollArrowRef.current?.offsetHeight || 0;
|
|
4517
|
-
|
|
4518
|
-
let firstVisibleIndex = 0;
|
|
4519
|
-
const scrollTop = scroller.scrollTop + scrollArrowHeight;
|
|
4520
|
-
for (let i = 0; i < items.length; i += 1) {
|
|
4521
|
-
const item = items[i];
|
|
4522
|
-
if (item) {
|
|
4523
|
-
const itemTop = item.offsetTop;
|
|
4524
|
-
if (itemTop >= scrollTop) {
|
|
4525
|
-
firstVisibleIndex = i;
|
|
4526
|
-
break;
|
|
4527
|
-
}
|
|
4528
|
-
}
|
|
4529
|
-
}
|
|
4530
|
-
const targetIndex = Math.max(0, firstVisibleIndex - 1);
|
|
4531
|
-
if (targetIndex < firstVisibleIndex) {
|
|
4532
|
-
const targetItem = items[targetIndex];
|
|
4533
|
-
if (targetItem) {
|
|
4534
|
-
scroller.scrollTop = Math.max(0, targetItem.offsetTop - scrollArrowHeight);
|
|
4535
|
-
}
|
|
4536
|
-
} else {
|
|
4537
|
-
// Already at the first item; ensure we reach the absolute top to account for group labels.
|
|
4538
|
-
scroller.scrollTop = 0;
|
|
4539
|
-
}
|
|
4540
|
-
} else {
|
|
4541
|
-
let lastVisibleIndex = items.length - 1;
|
|
4542
|
-
const scrollBottom = scroller.scrollTop + scroller.clientHeight - scrollArrowHeight;
|
|
4543
|
-
for (let i = 0; i < items.length; i += 1) {
|
|
4544
|
-
const item = items[i];
|
|
4545
|
-
if (item) {
|
|
4546
|
-
const itemBottom = item.offsetTop + item.offsetHeight;
|
|
4547
|
-
if (itemBottom > scrollBottom) {
|
|
4548
|
-
lastVisibleIndex = Math.max(0, i - 1);
|
|
4549
|
-
break;
|
|
4550
|
-
}
|
|
4551
|
-
}
|
|
4552
|
-
}
|
|
4553
|
-
const targetIndex = Math.min(items.length - 1, lastVisibleIndex + 1);
|
|
4554
|
-
if (targetIndex > lastVisibleIndex) {
|
|
4555
|
-
const targetItem = items[targetIndex];
|
|
4556
|
-
if (targetItem) {
|
|
4557
|
-
scroller.scrollTop = targetItem.offsetTop + targetItem.offsetHeight - scroller.clientHeight + scrollArrowHeight;
|
|
4558
|
-
}
|
|
4559
|
-
} else {
|
|
4560
|
-
// Already at the last item; ensure we reach the true bottom.
|
|
4561
|
-
scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight;
|
|
4562
|
-
}
|
|
4563
|
-
}
|
|
5112
|
+
scroller.scrollTop = getTargetScrollTop(items, isUp, scrollTop, scroller.clientHeight, scrollArrowHeight, maxScrollTop);
|
|
4564
5113
|
}
|
|
4565
5114
|
timeout.start(40, scrollNextItem);
|
|
4566
5115
|
}
|
|
@@ -4573,15 +5122,44 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
|
|
|
4573
5122
|
const element = useRenderElement('div', componentProps, {
|
|
4574
5123
|
ref: [forwardedRef, scrollArrowRef],
|
|
4575
5124
|
state,
|
|
4576
|
-
props: [defaultProps, elementProps]
|
|
5125
|
+
props: [defaultProps, elementProps],
|
|
5126
|
+
stateAttributesMapping: transitionStatusMapping
|
|
4577
5127
|
});
|
|
4578
|
-
const shouldRender =
|
|
5128
|
+
const shouldRender = mounted || keepMounted;
|
|
4579
5129
|
if (!shouldRender) {
|
|
4580
5130
|
return null;
|
|
4581
5131
|
}
|
|
4582
5132
|
return element;
|
|
4583
5133
|
});
|
|
4584
5134
|
if (process.env.NODE_ENV !== "production") SelectScrollArrow.displayName = "SelectScrollArrow";
|
|
5135
|
+
function getTargetScrollTop(items, isUp, scrollTop, clientHeight, scrollArrowHeight, maxScrollTop) {
|
|
5136
|
+
if (isUp) {
|
|
5137
|
+
let firstVisibleIndex = 0;
|
|
5138
|
+
const visibleTop = scrollTop + scrollArrowHeight - SCROLL_EDGE_TOLERANCE_PX;
|
|
5139
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
5140
|
+
const item = items[i];
|
|
5141
|
+
if (item && item.offsetTop >= visibleTop) {
|
|
5142
|
+
firstVisibleIndex = i;
|
|
5143
|
+
break;
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
const targetIndex = Math.max(0, firstVisibleIndex - 1);
|
|
5147
|
+
const targetItem = items[targetIndex];
|
|
5148
|
+
return targetIndex < firstVisibleIndex && targetItem ? normalizeScrollOffset(targetItem.offsetTop - scrollArrowHeight, maxScrollTop) : 0;
|
|
5149
|
+
}
|
|
5150
|
+
let lastVisibleIndex = items.length - 1;
|
|
5151
|
+
const visibleBottom = scrollTop + clientHeight - scrollArrowHeight + SCROLL_EDGE_TOLERANCE_PX;
|
|
5152
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
5153
|
+
const item = items[i];
|
|
5154
|
+
if (item && item.offsetTop + item.offsetHeight > visibleBottom) {
|
|
5155
|
+
lastVisibleIndex = Math.max(0, i - 1);
|
|
5156
|
+
break;
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
const targetIndex = Math.min(items.length - 1, lastVisibleIndex + 1);
|
|
5160
|
+
const targetItem = items[targetIndex];
|
|
5161
|
+
return targetIndex > lastVisibleIndex && targetItem ? normalizeScrollOffset(targetItem.offsetTop + targetItem.offsetHeight - clientHeight + scrollArrowHeight, maxScrollTop) : maxScrollTop;
|
|
5162
|
+
}
|
|
4585
5163
|
|
|
4586
5164
|
/**
|
|
4587
5165
|
* An element that scrolls the select popup down when hovered. Does not render when using touch input.
|