@jobber/components 9.5.3 → 9.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Combobox.d.ts +1 -0
- package/Combobox.js +17 -0
- package/dist/Autocomplete/index.cjs +0 -2
- package/dist/Autocomplete/index.mjs +0 -2
- package/dist/Autocomplete-cjs.js +4 -5
- package/dist/Autocomplete-es.js +1 -2
- package/dist/Card/index.cjs +2 -2
- package/dist/Card/index.mjs +2 -2
- package/dist/Chip/index.cjs +0 -2
- package/dist/Chip/index.mjs +0 -2
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +0 -2
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +0 -2
- package/dist/Chips/InternalChipDismissible/index.cjs +0 -2
- package/dist/Chips/InternalChipDismissible/index.mjs +0 -2
- package/dist/Chips/index.cjs +0 -2
- package/dist/Chips/index.mjs +0 -2
- package/dist/Combobox/Combobox.contentSlots.d.ts +5 -0
- package/dist/Combobox/Combobox.context.d.ts +34 -0
- package/dist/Combobox/Combobox.d.ts +69 -0
- package/dist/Combobox/Combobox.footer.d.ts +7 -0
- package/dist/Combobox/Combobox.items.d.ts +25 -0
- package/dist/Combobox/Combobox.responsive.d.ts +54 -0
- package/dist/Combobox/Combobox.selectAll.d.ts +4 -0
- package/dist/Combobox/Combobox.slotFactory.d.ts +7 -0
- package/dist/Combobox/Combobox.testUtils.d.ts +12 -0
- package/dist/Combobox/Combobox.types.d.ts +166 -0
- package/dist/Combobox/index.cjs +35 -0
- package/dist/Combobox/index.d.ts +2 -0
- package/dist/Combobox/index.mjs +29 -0
- package/dist/Combobox-cjs.js +675 -0
- package/dist/Combobox-es.js +673 -0
- package/dist/ComboboxPrimitive-cjs.js +88 -47
- package/dist/ComboboxPrimitive-es.js +39 -23
- package/dist/{ComboboxChipRemove-cjs.js → ComboboxSeparator-cjs.js} +501 -455
- package/dist/{ComboboxChipRemove-es.js → ComboboxSeparator-es.js} +500 -456
- package/dist/ConfirmationModal/index.cjs +0 -2
- package/dist/ConfirmationModal/index.mjs +0 -2
- package/dist/ContentBlock/index.cjs +1 -0
- package/dist/ContentBlock/index.mjs +1 -0
- package/dist/ContentBlock-cjs.js +2 -18
- package/dist/ContentBlock-es.js +1 -17
- package/dist/DataDump/index.cjs +2 -2
- package/dist/DataDump/index.mjs +2 -2
- package/dist/DataList/components/DataListActions/index.cjs +0 -2
- package/dist/DataList/components/DataListActions/index.mjs +0 -2
- package/dist/DataList/components/DataListBulkActions/index.cjs +0 -2
- package/dist/DataList/components/DataListBulkActions/index.mjs +0 -2
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +0 -2
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +0 -2
- package/dist/DataList/components/DataListFilters/index.cjs +0 -2
- package/dist/DataList/components/DataListFilters/index.mjs +0 -2
- package/dist/DataList/components/DataListHeader/index.cjs +0 -2
- package/dist/DataList/components/DataListHeader/index.mjs +0 -2
- package/dist/DataList/components/DataListItem/index.cjs +0 -2
- package/dist/DataList/components/DataListItem/index.mjs +0 -2
- package/dist/DataList/components/DataListItemActions/index.cjs +0 -2
- package/dist/DataList/components/DataListItemActions/index.mjs +0 -2
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +0 -2
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +0 -2
- package/dist/DataList/components/DataListItems/index.cjs +0 -2
- package/dist/DataList/components/DataListItems/index.mjs +0 -2
- package/dist/DataList/components/DataListLayout/index.cjs +0 -2
- package/dist/DataList/components/DataListLayout/index.mjs +0 -2
- package/dist/DataList/components/DataListLayoutActions/index.cjs +0 -2
- package/dist/DataList/components/DataListLayoutActions/index.mjs +0 -2
- package/dist/DataList/index.cjs +0 -2
- package/dist/DataList/index.mjs +0 -2
- package/dist/DatePicker/index.cjs +0 -2
- package/dist/DatePicker/index.mjs +0 -2
- package/dist/DatePicker-cjs.js +4 -5
- package/dist/DatePicker-es.js +1 -2
- package/dist/Dialog/index.cjs +2 -2
- package/dist/Dialog/index.mjs +2 -2
- package/dist/DrawerDescription-cjs.js +268 -99
- package/dist/DrawerDescription-es.js +270 -103
- package/dist/DrawerTitle-cjs.js +2 -6
- package/dist/DrawerTitle-es.js +3 -7
- package/dist/FieldDescription-cjs.js +184 -181
- package/dist/FieldDescription-es.js +187 -184
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +0 -2
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +0 -2
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +0 -2
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +0 -2
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +0 -2
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +0 -2
- package/dist/FilterPicker/index.cjs +0 -2
- package/dist/FilterPicker/index.mjs +0 -2
- package/dist/FilterPickerContent-cjs.js +4 -5
- package/dist/FilterPickerContent-es.js +1 -2
- package/dist/FormatFile/index.cjs +0 -2
- package/dist/FormatFile/index.mjs +0 -2
- package/dist/Gallery/index.cjs +0 -2
- package/dist/Gallery/index.mjs +0 -2
- package/dist/InputDate/index.cjs +0 -2
- package/dist/InputDate/index.mjs +0 -2
- package/dist/InputNumber/index.cjs +2 -2
- package/dist/InputNumber/index.mjs +2 -2
- package/dist/InputNumber/types.d.ts +5 -0
- package/dist/InputNumber-cjs.js +2 -2
- package/dist/InputNumber-es.js +2 -2
- package/dist/LightBox/index.cjs +0 -2
- package/dist/LightBox/index.mjs +0 -2
- package/dist/Menu/index.cjs +2 -2
- package/dist/Menu/index.mjs +2 -2
- package/dist/Menu-cjs.js +4 -5
- package/dist/Menu-es.js +1 -2
- package/dist/MenuSubmenuTrigger-cjs.js +206 -164
- package/dist/MenuSubmenuTrigger-es.js +194 -152
- package/dist/Modal/index.cjs +0 -2
- package/dist/Modal/index.mjs +1 -3
- package/dist/NumberFieldInput-cjs.js +187 -308
- package/dist/NumberFieldInput-es.js +187 -308
- package/dist/Page/index.cjs +2 -2
- package/dist/Page/index.mjs +2 -2
- package/dist/Popover/index.cjs +0 -2
- package/dist/Popover/index.mjs +0 -2
- package/dist/Popover-cjs.js +7 -8
- package/dist/Popover-es.js +1 -2
- package/dist/ScrollAreaViewport-cjs.js +747 -1093
- package/dist/ScrollAreaViewport-es.js +734 -1081
- package/dist/Select/index.cjs +2 -2
- package/dist/Select/index.mjs +2 -2
- package/dist/SelectGroupLabel-cjs.js +207 -306
- package/dist/SelectGroupLabel-es.js +203 -302
- package/dist/SelectPrimitive-cjs.js +11 -17
- package/dist/SelectPrimitive-es.js +1 -7
- package/dist/Tooltip/index.cjs +0 -2
- package/dist/Tooltip/index.mjs +0 -2
- package/dist/Tooltip-cjs.js +6 -7
- package/dist/Tooltip-es.js +1 -2
- package/dist/buttonRenderAdapter-cjs.js +2 -1
- package/dist/buttonRenderAdapter-es.js +2 -1
- package/dist/dialogReturnFocus-cjs.js +1 -1
- package/dist/dialogReturnFocus-es.js +2 -2
- package/dist/docs/InputNumber/InputNumber.md +2 -2
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +1 -1
- package/dist/floating-ui.react-cjs.js +2557 -185
- package/dist/floating-ui.react-es.js +2512 -149
- package/dist/getMappedBreakpointWidth-cjs.js +21 -0
- package/dist/getMappedBreakpointWidth-es.js +18 -0
- package/dist/index.cjs +15 -10
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +14 -10
- package/dist/mergeClassName-cjs.js +12 -0
- package/dist/mergeClassName-es.js +10 -0
- package/dist/primitives/BottomSheet/index.cjs +1 -1
- package/dist/primitives/BottomSheet/index.mjs +1 -1
- package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +2 -2
- package/dist/primitives/ComboboxPrimitive/index.cjs +4 -5
- package/dist/primitives/ComboboxPrimitive/index.mjs +4 -5
- package/dist/primitives/SelectPrimitive/index.cjs +1 -2
- package/dist/primitives/SelectPrimitive/index.mjs +1 -2
- package/dist/primitives/index.cjs +3 -3
- package/dist/primitives/index.mjs +3 -3
- package/dist/scrollEdges-cjs.js +1 -11
- package/dist/scrollEdges-es.js +1 -11
- package/dist/scrollable-cjs.js +58 -0
- package/dist/scrollable-es.js +54 -0
- package/dist/styles.css +759 -42
- package/dist/unstyledPrimitives/index.cjs +720 -478
- package/dist/unstyledPrimitives/index.mjs +663 -421
- package/dist/useCompositeListItem-cjs.js +2108 -361
- package/dist/useCompositeListItem-es.js +2076 -331
- package/dist/useLabel-cjs.js +2 -3
- package/dist/useLabel-es.js +2 -3
- package/dist/useRenderElement-cjs.js +9 -7
- package/dist/useRenderElement-es.js +9 -7
- package/dist/useScrollLock-cjs.js +819 -344
- package/dist/useScrollLock-es.js +732 -296
- package/dist/useScrollToActive-cjs.js +4 -5
- package/dist/useScrollToActive-es.js +1 -2
- package/dist/utils/meta/meta.json +43 -1
- package/package.json +8 -3
- package/dist/floating-ui.react-dom-cjs.js +0 -2103
- package/dist/floating-ui.react-dom-es.js +0 -2072
- package/dist/floating-ui.utils-cjs.js +0 -342
- package/dist/floating-ui.utils-es.js +0 -300
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { B as useCoreFilter, D as AriaCombobox, E as useComboboxInputValueContext, e as ComboboxTrigger, d as ComboboxInputGroup, p as ComboboxItem, L as ListboxSeparator, 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, A as useFilteredItems, s as ComboboxChip, t as ComboboxChipRemove, r as ComboboxChips, q as ComboboxItemIndicator, a as ComboboxLabel, C as ComboboxRoot, y as ComboboxSeparator, b as ComboboxValue, z as useComboboxFilter } from '../ComboboxSeparator-es.js';
|
|
3
|
+
import { n as stringifyAsLabel, A as useCompositeListItem, M as DEFAULT_SIDES, b as useFieldRootContext, f as fieldValidityMapping, K as getDefaultLabelId } from '../useCompositeListItem-es.js';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { a as DialogBackdrop, c as DialogDescription, e as DialogHandle, b as DialogPopup, d as createDialogHandle, D as DrawerDescription } from '../DrawerDescription-es.js';
|
|
5
|
+
import { bX as pointer, c as useIsoLayoutEffect, d as useStableCallback, bf as CommonPopupDataAttributes, u as useBaseUiId, bS as useTriggerRegistration, o as ownerDocument, h as createChangeEventDetails, bj as swipe, ac as isVirtualClick, a8 as useAnimationFrame, q as useTimeout, I as getWindow, y as addEventListener, D as activeElement, g as getComputedStyle, a0 as contains, e as getTarget, i as isHTMLElement, bv as isInteractiveElement, b as getParentNode, a6 as popupStateMapping, v as useControlled, ad as itemPress, s as useTransitionStatus, l as useOpenChangeComplete, aw as round, bz as useAnimationsFinished, af as getSide, a7 as useDirection, a4 as inertValue, bm as mergeCleanups, bY as gecko, b$ as scrub, a9 as webkit, G as createGenericEventDetails, U as useStore, be as triggerOpenStateMapping, t as transitionStatusMapping, p as popupTransitionStateMapping } from '../useScrollLock-es.js';
|
|
6
|
+
import { b as DialogBackdrop, d as DialogDescription, f as DialogHandle, c as DialogPopup, a as DialogRoot, e as createDialogHandle, D as DrawerDescription, B as BasePopupHandle } from '../DrawerDescription-es.js';
|
|
8
7
|
import { c as DialogClose, b as DialogTitle, a as DrawerClose, D as DrawerTitle } from '../DrawerTitle-es.js';
|
|
9
|
-
import {
|
|
10
|
-
import { u as useRenderElement,
|
|
11
|
-
import { K as getWindow, h as getComputedStyle, i as isHTMLElement, e as getParentNode, G as round } from '../floating-ui.utils-es.js';
|
|
8
|
+
import { i as DialogPortal, h as DialogTrigger, j as DialogViewport, r as useDrawerProviderContext, s as DrawerBackdropCssVars, t as DrawerPopupCssVars, v as DrawerProviderContext, u as useDialogRootContext, w as useDrawerRootContext, x as useSwipeDismiss, y as getDisplacement, z as DrawerPopupDataAttributes, A as getElementTransform, B as DrawerVirtualKeyboardContext, C as getElementAtPoint, c as DrawerBackdrop, g as DrawerContent, e as DrawerPopup, b as DrawerPortal, a as DrawerRoot, D as DrawerTrigger, d as DrawerViewport, E as useScrollAreaRootContext, F as getOffset, G as scrollAreaStateAttributesMapping, H as useScrollAreaViewportContext, S as ScrollAreaRoot, f as ScrollAreaViewport } from '../ScrollAreaViewport-es.js';
|
|
9
|
+
import { u as useRenderElement, N as NOOP, f as formatErrorMessage, E as EMPTY_OBJECT } from '../useRenderElement-es.js';
|
|
12
10
|
import { c as clamp, g as getMaxScrollOffset, n as normalizeScrollOffset, S as SCROLL_EDGE_TOLERANCE_PX } from '../scrollEdges-es.js';
|
|
13
|
-
import {
|
|
11
|
+
import { f as findScrollableTouchTarget } from '../scrollable-es.js';
|
|
12
|
+
import { u as useMenuRootContext, o as useMenuPositionerContext, p as useMenuItem, R as REGULAR_ITEM, q as itemMapping, r as createNullMenuStore, 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';
|
|
14
13
|
import * as ReactDOM from 'react-dom';
|
|
15
14
|
import { m as usePreviousValue, n as useSelectRootContext, s as selectors, o 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';
|
|
16
|
-
import {
|
|
15
|
+
import { S as Separator } from '../Separator-es.js';
|
|
16
|
+
import { u as useNumberFieldRootContext, s as stateAttributesMapping$4, d as NumberFieldDecrement, a as NumberFieldGroup, c as NumberFieldIncrement, b as NumberFieldInput, N as NumberFieldRoot } from '../NumberFieldInput-es.js';
|
|
17
17
|
import { u as useLabel } from '../useLabel-es.js';
|
|
18
18
|
import '../stringifyLocale-es.js';
|
|
19
|
-
import '../floating-ui.react-dom-es.js';
|
|
20
19
|
|
|
21
20
|
function AutocompleteRoot(props) {
|
|
22
21
|
const {
|
|
@@ -51,7 +50,9 @@ function AutocompleteRoot(props) {
|
|
|
51
50
|
} else {
|
|
52
51
|
resolvedInputValue = internalValue;
|
|
53
52
|
}
|
|
54
|
-
const collator = useCoreFilter(
|
|
53
|
+
const collator = useCoreFilter({
|
|
54
|
+
locale: other.locale
|
|
55
|
+
});
|
|
55
56
|
const baseFilter = React.useMemo(() => {
|
|
56
57
|
if (other.filter !== undefined) {
|
|
57
58
|
return other.filter;
|
|
@@ -84,15 +85,7 @@ function AutocompleteRoot(props) {
|
|
|
84
85
|
if (eventDetails.reason === pointer) {
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
|
-
|
|
88
|
-
if (highlightedValue == null) {
|
|
89
|
-
setInlineInputValue('');
|
|
90
|
-
} else {
|
|
91
|
-
setInlineInputValue(stringifyAsLabel(highlightedValue, itemToStringValue));
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
setInlineInputValue('');
|
|
95
|
-
}
|
|
88
|
+
setInlineInputValue(enableInline && highlightedValue != null ? stringifyAsLabel(highlightedValue, itemToStringValue) : '');
|
|
96
89
|
}
|
|
97
90
|
return /*#__PURE__*/jsx(AriaCombobox, {
|
|
98
91
|
...other,
|
|
@@ -151,6 +144,14 @@ const AutocompleteInputGroup = ComboboxInputGroup;
|
|
|
151
144
|
*/
|
|
152
145
|
const AutocompleteItem = ComboboxItem;
|
|
153
146
|
|
|
147
|
+
/**
|
|
148
|
+
* A visual separator between items or groups.
|
|
149
|
+
* Renders a `<div>` element.
|
|
150
|
+
*
|
|
151
|
+
* Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)
|
|
152
|
+
*/
|
|
153
|
+
const AutocompleteSeparator = ListboxSeparator;
|
|
154
|
+
|
|
154
155
|
var index_parts$7 = /*#__PURE__*/Object.freeze({
|
|
155
156
|
__proto__: null,
|
|
156
157
|
Arrow: ComboboxArrow,
|
|
@@ -170,7 +171,7 @@ var index_parts$7 = /*#__PURE__*/Object.freeze({
|
|
|
170
171
|
Positioner: ComboboxPositioner,
|
|
171
172
|
Root: AutocompleteRoot,
|
|
172
173
|
Row: ComboboxRow,
|
|
173
|
-
Separator:
|
|
174
|
+
Separator: AutocompleteSeparator,
|
|
174
175
|
Status: ComboboxStatus,
|
|
175
176
|
Trigger: AutocompleteTrigger,
|
|
176
177
|
Value: AutocompleteValue,
|
|
@@ -193,7 +194,7 @@ var index_parts$6 = /*#__PURE__*/Object.freeze({
|
|
|
193
194
|
createHandle: createDialogHandle
|
|
194
195
|
});
|
|
195
196
|
|
|
196
|
-
const stateAttributesMapping$
|
|
197
|
+
const stateAttributesMapping$3 = {
|
|
197
198
|
active(value) {
|
|
198
199
|
if (value) {
|
|
199
200
|
return {
|
|
@@ -264,12 +265,12 @@ const DrawerIndent = /*#__PURE__*/React.forwardRef(function DrawerIndent(compone
|
|
|
264
265
|
[DrawerBackdropCssVars.swipeProgress]: '0'
|
|
265
266
|
}
|
|
266
267
|
}, elementProps],
|
|
267
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
268
|
+
stateAttributesMapping: stateAttributesMapping$3
|
|
268
269
|
});
|
|
269
270
|
});
|
|
270
271
|
if (process.env.NODE_ENV !== "production") DrawerIndent.displayName = "DrawerIndent";
|
|
271
272
|
|
|
272
|
-
const stateAttributesMapping$
|
|
273
|
+
const stateAttributesMapping$2 = {
|
|
273
274
|
active(value) {
|
|
274
275
|
if (value) {
|
|
275
276
|
return {
|
|
@@ -304,7 +305,7 @@ const DrawerIndentBackground = /*#__PURE__*/React.forwardRef(function DrawerInde
|
|
|
304
305
|
ref: forwardedRef,
|
|
305
306
|
state,
|
|
306
307
|
props: elementProps,
|
|
307
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
308
|
+
stateAttributesMapping: stateAttributesMapping$2
|
|
308
309
|
});
|
|
309
310
|
});
|
|
310
311
|
if (process.env.NODE_ENV !== "production") DrawerIndentBackground.displayName = "DrawerIndentBackground";
|
|
@@ -313,37 +314,26 @@ function DrawerProvider(props) {
|
|
|
313
314
|
const {
|
|
314
315
|
children
|
|
315
316
|
} = props;
|
|
316
|
-
const [
|
|
317
|
+
const [openDrawers, setOpenDrawers] = React.useState(() => new Set());
|
|
317
318
|
const [visualStateStore] = React.useState(createVisualStateStore);
|
|
318
|
-
const setDrawerOpen = useStableCallback((
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (prevOpen === open) {
|
|
319
|
+
const setDrawerOpen = useStableCallback((drawer, open) => {
|
|
320
|
+
setOpenDrawers(prev => {
|
|
321
|
+
if (prev.has(drawer) === open) {
|
|
322
322
|
return prev;
|
|
323
323
|
}
|
|
324
|
-
const next = new
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const removeDrawer = useStableCallback(drawerId => {
|
|
330
|
-
setOpenById(prev => {
|
|
331
|
-
if (!prev.has(drawerId)) {
|
|
332
|
-
return prev;
|
|
324
|
+
const next = new Set(prev);
|
|
325
|
+
if (open) {
|
|
326
|
+
next.add(drawer);
|
|
327
|
+
} else {
|
|
328
|
+
next.delete(drawer);
|
|
333
329
|
}
|
|
334
|
-
const next = new Map(prev);
|
|
335
|
-
next.delete(drawerId);
|
|
336
330
|
return next;
|
|
337
331
|
});
|
|
338
332
|
});
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return false;
|
|
346
|
-
}, [openById]);
|
|
333
|
+
const removeDrawer = useStableCallback(drawer => {
|
|
334
|
+
setDrawerOpen(drawer, false);
|
|
335
|
+
});
|
|
336
|
+
const active = openDrawers.size > 0;
|
|
347
337
|
const contextValue = React.useMemo(() => ({
|
|
348
338
|
setDrawerOpen,
|
|
349
339
|
removeDrawer,
|
|
@@ -433,7 +423,7 @@ const SWIPE_AREA_SWIPING_HOOK = {
|
|
|
433
423
|
const SWIPE_AREA_DISABLED_HOOK = {
|
|
434
424
|
[DrawerSwipeAreaDataAttributes.disabled]: ''
|
|
435
425
|
};
|
|
436
|
-
const stateAttributesMapping$
|
|
426
|
+
const stateAttributesMapping$1 = {
|
|
437
427
|
open(value) {
|
|
438
428
|
return value ? SWIPE_AREA_OPEN_HOOK : SWIPE_AREA_CLOSED_HOOK;
|
|
439
429
|
},
|
|
@@ -441,9 +431,9 @@ const stateAttributesMapping$3 = {
|
|
|
441
431
|
return value ? SWIPE_AREA_SWIPING_HOOK : null;
|
|
442
432
|
},
|
|
443
433
|
swipeDirection(value) {
|
|
444
|
-
return
|
|
434
|
+
return {
|
|
445
435
|
[DrawerSwipeAreaDataAttributes.swipeDirection]: value
|
|
446
|
-
}
|
|
436
|
+
};
|
|
447
437
|
},
|
|
448
438
|
disabled(value) {
|
|
449
439
|
return value ? SWIPE_AREA_DISABLED_HOOK : null;
|
|
@@ -474,16 +464,14 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
474
464
|
swipeDirection: swipeDirectionProp,
|
|
475
465
|
...elementProps
|
|
476
466
|
} = componentProps;
|
|
477
|
-
const
|
|
478
|
-
store
|
|
479
|
-
} = useDialogRootContext();
|
|
467
|
+
const store = useDialogRootContext();
|
|
480
468
|
const {
|
|
481
469
|
swipeDirection,
|
|
482
|
-
frontmostHeight
|
|
470
|
+
frontmostHeight,
|
|
471
|
+
swipeAreaActiveRef
|
|
483
472
|
} = useDrawerRootContext();
|
|
484
473
|
const providerContext = useDrawerProviderContext();
|
|
485
474
|
const [swipeActive, setSwipeActive] = React.useState(false);
|
|
486
|
-
const releaseDismissTimeout = useTimeout();
|
|
487
475
|
const swipeAreaRef = React.useRef(null);
|
|
488
476
|
const swipeStartEventRef = React.useRef(null);
|
|
489
477
|
const openedBySwipeRef = React.useRef(false);
|
|
@@ -493,7 +481,10 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
493
481
|
});
|
|
494
482
|
const closedOffsetRef = React.useRef(null);
|
|
495
483
|
const appliedSwipeStylesRef = React.useRef(false);
|
|
484
|
+
const swipePopupElementRef = React.useRef(null);
|
|
485
|
+
const swipeBackdropElementRef = React.useRef(null);
|
|
496
486
|
const popupTransitionRef = React.useRef(null);
|
|
487
|
+
const releaseGuardCleanupRef = React.useRef(NOOP);
|
|
497
488
|
const swipeAreaId = useBaseUiId(componentProps.id);
|
|
498
489
|
const registerTrigger = useTriggerRegistration(swipeAreaId, store);
|
|
499
490
|
const open = store.useState('open');
|
|
@@ -505,21 +496,40 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
505
496
|
const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];
|
|
506
497
|
const enabled = !disabled && (!open || swipeActive);
|
|
507
498
|
function disableDismissForSwipe() {
|
|
508
|
-
|
|
499
|
+
releaseGuardCleanupRef.current();
|
|
509
500
|
store.context.outsidePressEnabledRef.current = false;
|
|
510
501
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
502
|
+
const enableDismissAfterRelease = useStableCallback(() => {
|
|
503
|
+
releaseGuardCleanupRef.current();
|
|
504
|
+
const doc = ownerDocument(swipeAreaRef.current);
|
|
505
|
+
function restore(event) {
|
|
506
|
+
// The gesture's trailing release click is the one physical click with no `pointerdown` of
|
|
507
|
+
// its own. Ignore it and keep waiting, so it cannot dismiss the drawer it just opened,
|
|
508
|
+
// while a click-only activation (keyboard or assistive tech) still re-enables in time.
|
|
509
|
+
if (event?.type === 'click' && event.detail !== 0 && !isVirtualClick(event)) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
releaseGuardCleanupRef.current = NOOP;
|
|
513
|
+
doc.removeEventListener('pointerdown', restore, true);
|
|
514
|
+
doc.removeEventListener('click', restore, true);
|
|
515
515
|
store.context.outsidePressEnabledRef.current = true;
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
function resolvePopupSize() {
|
|
519
|
-
const popupElement = store.context.popupRef.current;
|
|
520
|
-
if (!popupElement) {
|
|
521
|
-
return null;
|
|
522
516
|
}
|
|
517
|
+
|
|
518
|
+
// The pointerup that ends a swipe-open gesture synthesizes a `click`. When the drag released
|
|
519
|
+
// outside the popup (e.g. it was dragged past the popup's size), that click would be treated as
|
|
520
|
+
// an outside press and immediately dismiss the drawer that was just opened. Keep outside-press
|
|
521
|
+
// dismissal disabled until the next interaction that isn't that release click: a deliberate
|
|
522
|
+
// outside press starts with a `pointerdown`, and a click-only activation (keyboard or
|
|
523
|
+
// assistive tech) is distinguishable from a physical release. This is deterministic, unlike
|
|
524
|
+
// re-enabling on a timer that can race the synthesized click and dismiss at random.
|
|
525
|
+
//
|
|
526
|
+
// `restore` runs in document capture, ahead of floating-ui's own outside-press check (which
|
|
527
|
+
// happens on the event target, after capture), so the triggering press still dismisses.
|
|
528
|
+
releaseGuardCleanupRef.current = restore;
|
|
529
|
+
doc.addEventListener('pointerdown', restore, true);
|
|
530
|
+
doc.addEventListener('click', restore, true);
|
|
531
|
+
});
|
|
532
|
+
function getPopupSize(popupElement) {
|
|
523
533
|
const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';
|
|
524
534
|
const size = isHorizontal ? popupElement.offsetWidth : popupElement.offsetHeight;
|
|
525
535
|
if (size <= 0) {
|
|
@@ -527,15 +537,15 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
527
537
|
}
|
|
528
538
|
return size;
|
|
529
539
|
}
|
|
530
|
-
function
|
|
531
|
-
const
|
|
540
|
+
function resolvePopupSize() {
|
|
541
|
+
const popupElement = store.context.popupRef.current;
|
|
542
|
+
return popupElement ? getPopupSize(popupElement) : null;
|
|
543
|
+
}
|
|
544
|
+
function resolveClosedOffset(popupElement) {
|
|
545
|
+
const offset = getPopupSize(popupElement);
|
|
532
546
|
if (offset == null) {
|
|
533
547
|
return null;
|
|
534
548
|
}
|
|
535
|
-
const popupElement = store.context.popupRef.current;
|
|
536
|
-
if (!popupElement) {
|
|
537
|
-
return offset;
|
|
538
|
-
}
|
|
539
549
|
const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';
|
|
540
550
|
const transform = getElementTransform(popupElement);
|
|
541
551
|
const transformOffset = isHorizontal ? transform.x : transform.y;
|
|
@@ -552,9 +562,6 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
552
562
|
return popupSize * DEFAULT_SWIPE_OPEN_RATIO;
|
|
553
563
|
}
|
|
554
564
|
function applySwipeMovement() {
|
|
555
|
-
if (!swipeActive) {
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
565
|
const popupElement = store.context.popupRef.current;
|
|
559
566
|
if (!popupElement) {
|
|
560
567
|
return;
|
|
@@ -563,10 +570,10 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
563
570
|
return;
|
|
564
571
|
}
|
|
565
572
|
if (closedOffsetRef.current == null) {
|
|
566
|
-
closedOffsetRef.current = resolveClosedOffset();
|
|
573
|
+
closedOffsetRef.current = resolveClosedOffset(popupElement);
|
|
567
574
|
}
|
|
568
575
|
const closedOffset = closedOffsetRef.current;
|
|
569
|
-
if (
|
|
576
|
+
if (closedOffset === null) {
|
|
570
577
|
return;
|
|
571
578
|
}
|
|
572
579
|
const {
|
|
@@ -587,6 +594,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
587
594
|
popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementX, `${movementX}px`);
|
|
588
595
|
popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementY, `${movementY}px`);
|
|
589
596
|
popupElement.setAttribute(DrawerPopupDataAttributes.swiping, '');
|
|
597
|
+
swipePopupElementRef.current = popupElement;
|
|
590
598
|
if (popupTransitionRef.current === null) {
|
|
591
599
|
popupTransitionRef.current = popupElement.style.transition;
|
|
592
600
|
}
|
|
@@ -594,6 +602,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
594
602
|
const backdropElement = store.context.backdropRef.current;
|
|
595
603
|
if (backdropElement) {
|
|
596
604
|
backdropElement.setAttribute(DrawerPopupDataAttributes.swiping, '');
|
|
605
|
+
swipeBackdropElementRef.current = backdropElement;
|
|
597
606
|
backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${backdropProgress}`);
|
|
598
607
|
if (openProgress > 0 && frontmostHeight > 0) {
|
|
599
608
|
backdropElement.style.setProperty(DrawerPopupCssVars.height, `${frontmostHeight}px`);
|
|
@@ -606,10 +615,11 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
606
615
|
frontmostHeight: openProgress > 0 ? frontmostHeight : 0
|
|
607
616
|
});
|
|
608
617
|
appliedSwipeStylesRef.current = true;
|
|
618
|
+
swipeAreaActiveRef.current = true;
|
|
609
619
|
}
|
|
610
620
|
const clearSwipeStyles = useStableCallback(() => {
|
|
611
|
-
const popupElement =
|
|
612
|
-
if (popupElement
|
|
621
|
+
const popupElement = swipePopupElementRef.current;
|
|
622
|
+
if (popupElement) {
|
|
613
623
|
popupElement.style.removeProperty(DrawerPopupCssVars.swipeMovementX);
|
|
614
624
|
popupElement.style.removeProperty(DrawerPopupCssVars.swipeMovementY);
|
|
615
625
|
popupElement.removeAttribute(DrawerPopupDataAttributes.swiping);
|
|
@@ -618,7 +628,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
618
628
|
popupElement.style.transition = popupTransitionRef.current;
|
|
619
629
|
popupTransitionRef.current = null;
|
|
620
630
|
}
|
|
621
|
-
const backdropElement =
|
|
631
|
+
const backdropElement = swipeBackdropElementRef.current;
|
|
622
632
|
if (backdropElement) {
|
|
623
633
|
backdropElement.removeAttribute(DrawerPopupDataAttributes.swiping);
|
|
624
634
|
backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');
|
|
@@ -629,19 +639,16 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
629
639
|
frontmostHeight: 0
|
|
630
640
|
});
|
|
631
641
|
appliedSwipeStylesRef.current = false;
|
|
642
|
+
swipePopupElementRef.current = null;
|
|
643
|
+
swipeBackdropElementRef.current = null;
|
|
644
|
+
swipeAreaActiveRef.current = false;
|
|
632
645
|
});
|
|
633
646
|
function openDrawer(event) {
|
|
634
|
-
if (store.select('open')) {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
647
|
openedBySwipeRef.current = true;
|
|
638
|
-
store.setOpen(true, createChangeEventDetails(swipe, event, swipeAreaRef.current
|
|
648
|
+
store.setOpen(true, createChangeEventDetails(swipe, event, swipeAreaRef.current));
|
|
639
649
|
}
|
|
640
650
|
function closeDrawer(event) {
|
|
641
|
-
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
store.setOpen(false, createChangeEventDetails(swipe, event, swipeAreaRef.current ?? undefined));
|
|
651
|
+
store.setOpen(false, createChangeEventDetails(swipe, event, swipeAreaRef.current));
|
|
645
652
|
}
|
|
646
653
|
function resetSwipeInteractionState() {
|
|
647
654
|
swipeStartEventRef.current = null;
|
|
@@ -684,10 +691,10 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
684
691
|
return;
|
|
685
692
|
}
|
|
686
693
|
const displacement = getDisplacement(resolvedSwipeDirection, details.deltaX, details.deltaY);
|
|
687
|
-
if (displacement < MIN_SWIPE_START_DISTANCE
|
|
694
|
+
if (!openedBySwipeRef.current && displacement < MIN_SWIPE_START_DISTANCE) {
|
|
688
695
|
return;
|
|
689
696
|
}
|
|
690
|
-
if (!openedBySwipeRef.current) {
|
|
697
|
+
if (!openedBySwipeRef.current && !store.select('open')) {
|
|
691
698
|
openDrawer(swipeStartEventRef.current);
|
|
692
699
|
}
|
|
693
700
|
applySwipeMovement();
|
|
@@ -703,14 +710,14 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
703
710
|
const displacement = getDisplacement(resolvedSwipeDirection, deltaX, deltaY);
|
|
704
711
|
const releaseVelocity = getDisplacement(resolvedSwipeDirection, releaseVelocityX, releaseVelocityY);
|
|
705
712
|
const threshold = resolveSwipeOpenThreshold();
|
|
706
|
-
const hasEnoughDistance =
|
|
713
|
+
const hasEnoughDistance = displacement >= threshold;
|
|
707
714
|
const hasEnoughVelocity = releaseVelocity >= VELOCITY_THRESHOLD;
|
|
708
|
-
const shouldOpen =
|
|
715
|
+
const shouldOpen = direction === resolvedSwipeDirection && (hasEnoughDistance || hasEnoughVelocity) && !disabled;
|
|
709
716
|
if (shouldOpen) {
|
|
710
717
|
if (!store.select('open')) {
|
|
711
718
|
openDrawer(event);
|
|
712
719
|
}
|
|
713
|
-
} else if (openedBySwipeRef.current) {
|
|
720
|
+
} else if (openedBySwipeRef.current && store.select('open')) {
|
|
714
721
|
closeDrawer(event);
|
|
715
722
|
}
|
|
716
723
|
finishSwipeInteraction();
|
|
@@ -721,16 +728,29 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
721
728
|
const swipePointerProps = swipe$1.getPointerProps();
|
|
722
729
|
const swipeTouchProps = swipe$1.getTouchProps();
|
|
723
730
|
const resetSwipe = swipe$1.reset;
|
|
724
|
-
|
|
731
|
+
|
|
732
|
+
// The commit that opens the drawer re-renders the popup, resetting `--swipe-movement-*` to `0px`
|
|
733
|
+
// (the viewport isn't swiping). Re-assert after the DOM mutation but before paint. No deps: must
|
|
734
|
+
// run on every commit the swipe area participates in.
|
|
735
|
+
useIsoLayoutEffect(() => {
|
|
736
|
+
if (swipeActive && appliedSwipeStylesRef.current) {
|
|
737
|
+
applySwipeMovement();
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
useIsoLayoutEffect(() => {
|
|
725
741
|
if (!enabled) {
|
|
742
|
+
if (swipeActive) {
|
|
743
|
+
enableDismissAfterRelease();
|
|
744
|
+
}
|
|
726
745
|
resetSwipe();
|
|
727
746
|
resetDragDelta();
|
|
728
747
|
clearSwipeStyles();
|
|
729
748
|
resetSwipeInteractionState();
|
|
730
749
|
}
|
|
731
|
-
}, [clearSwipeStyles, enabled, resetDragDelta, resetSwipe]);
|
|
750
|
+
}, [clearSwipeStyles, enableDismissAfterRelease, enabled, resetDragDelta, resetSwipe, swipeActive]);
|
|
732
751
|
React.useEffect(() => {
|
|
733
752
|
return () => {
|
|
753
|
+
releaseGuardCleanupRef.current();
|
|
734
754
|
store.context.outsidePressEnabledRef.current = true;
|
|
735
755
|
};
|
|
736
756
|
}, [store]);
|
|
@@ -743,7 +763,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
|
|
|
743
763
|
return useRenderElement('div', componentProps, {
|
|
744
764
|
state,
|
|
745
765
|
ref: [forwardedRef, swipeAreaRef, registerTrigger],
|
|
746
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
766
|
+
stateAttributesMapping: stateAttributesMapping$1,
|
|
747
767
|
props: [{
|
|
748
768
|
role: 'presentation',
|
|
749
769
|
'aria-hidden': true,
|
|
@@ -803,6 +823,15 @@ const KEYBOARD_VISIBILITY_MARGIN = 16;
|
|
|
803
823
|
// keyboard overlap, so the field can be scrolled clear of the keyboard instead of
|
|
804
824
|
// ending up flush against it. Only applied when there is actual overlap.
|
|
805
825
|
const KEYBOARD_SCROLL_SLACK = 48;
|
|
826
|
+
// Cadence of the settle-watching realign passes after focus moves with the keyboard open:
|
|
827
|
+
// long enough for a smooth scroll to show progress between passes, short enough to recover
|
|
828
|
+
// quickly from a scroll canceled by WebKit's reveal; the pass count covers CSS transitions
|
|
829
|
+
// reacting to the focus change (e.g. a 260ms footer resize) with room to spare.
|
|
830
|
+
const KEYBOARD_REALIGN_INTERVAL = 150;
|
|
831
|
+
const KEYBOARD_REALIGN_MAX_PASSES = 4;
|
|
832
|
+
// Frames the alignment waits for the scroll destination to stop moving (layout reacting to
|
|
833
|
+
// the focus change, e.g. a footer resizing over a CSS transition) before scrolling anyway.
|
|
834
|
+
const KEYBOARD_SETTLE_FRAME_LIMIT = 60;
|
|
806
835
|
const INPUT_TAP_MOVE_THRESHOLD = 10;
|
|
807
836
|
const INPUT_TAP_HIT_SLOP = 16;
|
|
808
837
|
const KEYBOARD_INPUT_TYPES = new Set(['email', 'number', 'password', 'search', 'tel', 'text', 'url']);
|
|
@@ -826,11 +855,10 @@ function DrawerVirtualKeyboardProvider(props) {
|
|
|
826
855
|
const {
|
|
827
856
|
children
|
|
828
857
|
} = props;
|
|
829
|
-
const
|
|
830
|
-
store
|
|
831
|
-
} = useDialogRootContext();
|
|
858
|
+
const store = useDialogRootContext();
|
|
832
859
|
const open = store.useState('open');
|
|
833
860
|
const mounted = store.useState('mounted');
|
|
861
|
+
const modal = store.useState('modal');
|
|
834
862
|
const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
|
|
835
863
|
const viewportElement = store.useState('viewportElement');
|
|
836
864
|
|
|
@@ -843,7 +871,9 @@ function DrawerVirtualKeyboardProvider(props) {
|
|
|
843
871
|
const keyboardTouchStartRef = React.useRef(null);
|
|
844
872
|
const focusedKeyboardTargetRef = React.useRef(null);
|
|
845
873
|
const keyboardScrollAdjustmentRef = React.useRef(null);
|
|
874
|
+
const programmaticKeyboardFocusRef = React.useRef(false);
|
|
846
875
|
const keyboardFocusFrame = useAnimationFrame();
|
|
876
|
+
const keyboardRealignTimeout = useTimeout();
|
|
847
877
|
const restoreKeyboardScrollAdjustment = useStableCallback(() => {
|
|
848
878
|
const adjustment = keyboardScrollAdjustmentRef.current;
|
|
849
879
|
if (!adjustment) {
|
|
@@ -911,46 +941,100 @@ function DrawerVirtualKeyboardProvider(props) {
|
|
|
911
941
|
const doc = ownerDocument(rootElement);
|
|
912
942
|
const win = getWindow(rootElement);
|
|
913
943
|
const visualViewport = win.visualViewport;
|
|
944
|
+
|
|
945
|
+
// Alignment scroll bookkeeping: destination stability, whether a scroll was issued,
|
|
946
|
+
// and the last observed progress so delayed passes can distinguish moving from stalled.
|
|
947
|
+
let keyboardScrollElement = null;
|
|
948
|
+
let keyboardScrollDestination = 0;
|
|
949
|
+
let keyboardScrollChecks = 0;
|
|
950
|
+
let keyboardScrollObserved = -1;
|
|
914
951
|
const setDrawerKeyboardInset = inset => {
|
|
915
952
|
rootElement.style.setProperty(DrawerViewportCssVars.keyboardInset, `${Math.max(0, Math.ceil(inset))}px`);
|
|
916
953
|
};
|
|
917
|
-
const resetDrawerKeyboardInset = () => {
|
|
918
|
-
setDrawerKeyboardInset(0);
|
|
919
|
-
};
|
|
920
954
|
const clearFocusedKeyboardTarget = () => {
|
|
921
955
|
focusedKeyboardTargetRef.current = null;
|
|
922
|
-
|
|
956
|
+
keyboardScrollElement = null;
|
|
957
|
+
setDrawerKeyboardInset(0);
|
|
923
958
|
restoreKeyboardScrollAdjustment();
|
|
924
959
|
keyboardFocusFrame.cancel();
|
|
960
|
+
keyboardRealignTimeout.clear();
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
// WebKit's native reveal scroll can move the page even while the scroll lock hides
|
|
964
|
+
// overflow (e.g. when the software keyboard's previous/next field arrows move focus).
|
|
965
|
+
// While the drawer is modal, any window scroll during keyboard interaction is spurious,
|
|
966
|
+
// so pin the page to the position it had when the drawer opened.
|
|
967
|
+
const baseScrollX = win.scrollX;
|
|
968
|
+
const baseScrollY = win.scrollY;
|
|
969
|
+
const restoreWindowScroll = () => {
|
|
970
|
+
if (modal !== true || nestedDrawerOpen || !focusedKeyboardTargetRef.current || getKeyboardVisualViewport(win) == null) {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
if (win.scrollX !== baseScrollX || win.scrollY !== baseScrollY) {
|
|
974
|
+
// Force an instant jump: the two-argument form defaults `behavior` to `auto`, which
|
|
975
|
+
// obeys the page's `scroll-behavior`, so a global `scroll-behavior: smooth` would
|
|
976
|
+
// animate the restore. The measurements that follow assume the page is already back
|
|
977
|
+
// at rest, and a smooth restore also re-emits `scroll`, re-entering this handler.
|
|
978
|
+
win.scrollTo({
|
|
979
|
+
left: baseScrollX,
|
|
980
|
+
top: baseScrollY,
|
|
981
|
+
behavior: 'instant'
|
|
982
|
+
});
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
return false;
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
// Focus moved by the drawer itself goes through `focusKeyboardInputWithoutPageScroll`,
|
|
989
|
+
// but native focus changes (the iOS keyboard's previous/next field arrows) commit
|
|
990
|
+
// WebKit's reveal scroll before `focusin` reaches us — cancelling it afterwards
|
|
991
|
+
// visibly jitters the sheet. `focusout` on the outgoing field fires before focus
|
|
992
|
+
// lands, so override the incoming field's geometry there and restore it in `focusin`.
|
|
993
|
+
let restorePreemptedFocus = null;
|
|
994
|
+
const consumePreemptedFocus = () => {
|
|
995
|
+
restorePreemptedFocus?.();
|
|
996
|
+
restorePreemptedFocus = null;
|
|
997
|
+
};
|
|
998
|
+
const preemptFocusReveal = (target, keyboardViewport) => {
|
|
999
|
+
consumePreemptedFocus();
|
|
1000
|
+
|
|
1001
|
+
// Native focus carries no preventScroll, so a fake off-screen rect would make WebKit
|
|
1002
|
+
// scroll the drawer's own scroll containers thousands of pixels chasing it. Presenting
|
|
1003
|
+
// the field as already centered in the visible band above the keyboard instead makes
|
|
1004
|
+
// both the in-page reveal and the viewport pan no-ops.
|
|
1005
|
+
const rect = target.getBoundingClientRect();
|
|
1006
|
+
restorePreemptedFocus = overrideGeometryDuringFocus(target, (keyboardViewport.top + keyboardViewport.bottom - rect.top - rect.bottom) / 2);
|
|
925
1007
|
};
|
|
926
1008
|
const alignFocusedKeyboardTarget = () => {
|
|
1009
|
+
// If focus never lands on a preempted target, the focusout-scheduled alignment still
|
|
1010
|
+
// restores it on the next frame before paint.
|
|
1011
|
+
consumePreemptedFocus();
|
|
927
1012
|
const target = focusedKeyboardTargetRef.current;
|
|
928
1013
|
// If the focused field is removed from the DOM without firing `focusout` (e.g. it is
|
|
929
1014
|
// conditionally rendered away), any applied scroll slack is restored here on the next
|
|
930
1015
|
// focus/viewport event or when the drawer closes. This self-corrects rather than
|
|
931
1016
|
// tracking each field's lifecycle.
|
|
932
1017
|
if (nestedDrawerOpen || !target || !contains(rootElement, target)) {
|
|
933
|
-
|
|
1018
|
+
setDrawerKeyboardInset(0);
|
|
934
1019
|
restoreKeyboardScrollAdjustment();
|
|
935
1020
|
return;
|
|
936
1021
|
}
|
|
1022
|
+
|
|
1023
|
+
// Undo any reveal scroll WebKit applied to the locked page before measuring, so the
|
|
1024
|
+
// keyboard inset and alignment are computed against the resting viewport.
|
|
1025
|
+
restoreWindowScroll();
|
|
937
1026
|
const keyboardViewport = getKeyboardVisualViewport(win);
|
|
938
1027
|
if (!keyboardViewport) {
|
|
939
|
-
|
|
1028
|
+
setDrawerKeyboardInset(0);
|
|
940
1029
|
restoreKeyboardScrollAdjustment();
|
|
941
1030
|
return;
|
|
942
1031
|
}
|
|
943
|
-
setDrawerKeyboardInset(
|
|
1032
|
+
setDrawerKeyboardInset(Math.max(0, win.innerHeight - keyboardViewport.bottom));
|
|
944
1033
|
const scrollTarget = findKeyboardScrollTarget(target, rootElement);
|
|
945
1034
|
if (!scrollTarget) {
|
|
946
1035
|
restoreKeyboardScrollAdjustment();
|
|
947
1036
|
return;
|
|
948
1037
|
}
|
|
949
|
-
if (!scrollTarget.isConnected || !contains(rootElement, scrollTarget)) {
|
|
950
|
-
resetDrawerKeyboardInset();
|
|
951
|
-
restoreKeyboardScrollAdjustment();
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
1038
|
const scrollTargetRect = scrollTarget.getBoundingClientRect();
|
|
955
1039
|
const clippedBottom = Math.min(scrollTargetRect.bottom, keyboardViewport.bottom);
|
|
956
1040
|
const overlap = Math.max(0, scrollTargetRect.bottom - keyboardViewport.bottom);
|
|
@@ -966,14 +1050,70 @@ function DrawerVirtualKeyboardProvider(props) {
|
|
|
966
1050
|
return;
|
|
967
1051
|
}
|
|
968
1052
|
const targetRect = target.getBoundingClientRect();
|
|
969
|
-
const
|
|
970
|
-
const
|
|
971
|
-
const
|
|
972
|
-
|
|
1053
|
+
const nextScrollTop = scrollTarget.scrollTop + (targetRect.top + targetRect.bottom - visibleTop - visibleBottom) / 2;
|
|
1054
|
+
const destination = Math.round(clamp(nextScrollTop, 0, maxScrollTop));
|
|
1055
|
+
const settled = keyboardScrollElement === scrollTarget && Math.abs(keyboardScrollDestination - destination) <= 1;
|
|
1056
|
+
if (!settled) {
|
|
1057
|
+
// Commit the scroll only once the destination holds across two consecutive checks.
|
|
1058
|
+
// Layout may still be reacting to the focus change (e.g. a footer sized by
|
|
1059
|
+
// `:focus-within` resizing over a transition), and scrolling toward a
|
|
1060
|
+
// mid-transition destination overshoots, then visibly pulls back once corrected.
|
|
1061
|
+
// The destination is scroll-position-invariant, so an in-flight scroll of the
|
|
1062
|
+
// container itself never defers the commit.
|
|
1063
|
+
const checks = keyboardScrollElement === scrollTarget ? keyboardScrollChecks + 1 : 1;
|
|
1064
|
+
keyboardScrollElement = scrollTarget;
|
|
1065
|
+
keyboardScrollDestination = destination;
|
|
1066
|
+
keyboardScrollChecks = checks;
|
|
1067
|
+
keyboardScrollObserved = -1;
|
|
1068
|
+
// Re-check next frame; give up and scroll anyway if layout never settles.
|
|
1069
|
+
if (checks <= KEYBOARD_SETTLE_FRAME_LIMIT) {
|
|
1070
|
+
keyboardFocusFrame.request(alignFocusedKeyboardTarget);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
} else if (keyboardScrollObserved >= 0) {
|
|
1074
|
+
// A scroll toward this destination is already out. Leave it alone while it has
|
|
1075
|
+
// arrived or is still progressing — re-issuing restarts the smooth-scroll easing
|
|
1076
|
+
// curve, a visible stutter on every realign pass — and re-issue only when it
|
|
1077
|
+
// stalled short (WebKit cancels in-flight smooth scrolls when its native reveal
|
|
1078
|
+
// for the focus resolves).
|
|
1079
|
+
const current = scrollTarget.scrollTop;
|
|
1080
|
+
if (Math.abs(current - destination) <= 1) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
if (current !== keyboardScrollObserved) {
|
|
1084
|
+
keyboardScrollObserved = current;
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
keyboardScrollElement = scrollTarget;
|
|
1089
|
+
keyboardScrollDestination = destination;
|
|
1090
|
+
keyboardScrollChecks = 0;
|
|
1091
|
+
keyboardScrollObserved = scrollTarget.scrollTop;
|
|
1092
|
+
animateKeyboardScroll(scrollTarget, destination);
|
|
973
1093
|
};
|
|
974
1094
|
const scheduleKeyboardFocusAlignment = () => {
|
|
975
1095
|
keyboardFocusFrame.request(alignFocusedKeyboardTarget);
|
|
976
1096
|
};
|
|
1097
|
+
|
|
1098
|
+
// When focus moves with the keyboard already up, no viewport resize events follow to
|
|
1099
|
+
// re-run alignment, and the single frame-scheduled pass is unreliable: WebKit resolves
|
|
1100
|
+
// the reveal for the focus asynchronously (a UI-process round trip) and cancels any
|
|
1101
|
+
// in-flight scroll the pass started when it lands, and layout reacting to the focus
|
|
1102
|
+
// change (e.g. a footer sized by `:focus-within` and the keyboard inset) settles only
|
|
1103
|
+
// after its transition, so the pass measures stale geometry. Re-align on an interval
|
|
1104
|
+
// until both settle; the alignment's scroll bookkeeping observes before re-issuing,
|
|
1105
|
+
// so passes that find the scroll on course are inert.
|
|
1106
|
+
const scheduleDelayedKeyboardRealign = () => {
|
|
1107
|
+
let remainingPasses = KEYBOARD_REALIGN_MAX_PASSES;
|
|
1108
|
+
const realign = () => {
|
|
1109
|
+
alignFocusedKeyboardTarget();
|
|
1110
|
+
remainingPasses -= 1;
|
|
1111
|
+
if (remainingPasses > 0) {
|
|
1112
|
+
keyboardRealignTimeout.start(KEYBOARD_REALIGN_INTERVAL, realign);
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
keyboardRealignTimeout.start(KEYBOARD_REALIGN_INTERVAL, realign);
|
|
1116
|
+
};
|
|
977
1117
|
const captureFocusedKeyboardTarget = eventTarget => {
|
|
978
1118
|
if (nestedDrawerOpen) {
|
|
979
1119
|
return false;
|
|
@@ -985,16 +1125,56 @@ function DrawerVirtualKeyboardProvider(props) {
|
|
|
985
1125
|
if (!target || !contains(rootElement, target)) {
|
|
986
1126
|
return false;
|
|
987
1127
|
}
|
|
1128
|
+
|
|
1129
|
+
// A new field starts a fresh alignment; the scroll bookkeeping must not carry
|
|
1130
|
+
// over from the previous field's scroll.
|
|
1131
|
+
if (focusedKeyboardTargetRef.current !== target) {
|
|
1132
|
+
keyboardScrollElement = null;
|
|
1133
|
+
}
|
|
988
1134
|
focusedKeyboardTargetRef.current = target;
|
|
989
1135
|
return true;
|
|
990
1136
|
};
|
|
991
1137
|
const handleFocusIn = event => {
|
|
992
|
-
|
|
993
|
-
|
|
1138
|
+
// The programmatic transition is over once focus lands, which happens before
|
|
1139
|
+
// `.focus()` returns. Any later `focusout` is the consumer's own — an `onFocus`
|
|
1140
|
+
// handler that blurs or moves focus — and must be reconciled rather than
|
|
1141
|
+
// suppressed, so stop suppressing here instead of waiting for `.focus()` to return.
|
|
1142
|
+
programmaticKeyboardFocusRef.current = false;
|
|
1143
|
+
consumePreemptedFocus();
|
|
1144
|
+
if (!captureFocusedKeyboardTarget(getTarget(event))) {
|
|
1145
|
+
// Focus landed outside any drawer keyboard input. The `focusout` on the previous
|
|
1146
|
+
// field normally clears the tracked target, but the tap path suppresses that
|
|
1147
|
+
// `focusout` via `programmaticKeyboardFocusRef`, so a consumer `onFocus` handler that
|
|
1148
|
+
// redirects focus out of the drawer would otherwise leave a stale target holding its
|
|
1149
|
+
// inset, slack, and pending realign. Reconcile against the real focus here.
|
|
1150
|
+
// A handler that ends focus entirely emits no `focusin` to reconcile from; that case
|
|
1151
|
+
// is covered by clearing the suppression flag above so its `focusout` is handled.
|
|
1152
|
+
clearFocusedKeyboardTarget();
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Covers every way focus can land with the keyboard already up: native moves (the
|
|
1157
|
+
// keyboard's previous/next arrows) and the tap path (which suppresses `focusout`
|
|
1158
|
+
// handling via the programmatic flag).
|
|
1159
|
+
if (getKeyboardVisualViewport(win) != null) {
|
|
1160
|
+
scheduleDelayedKeyboardRealign();
|
|
994
1161
|
}
|
|
1162
|
+
scheduleKeyboardFocusAlignment();
|
|
995
1163
|
};
|
|
996
1164
|
const handleFocusOut = event => {
|
|
1165
|
+
// The blur inside `focusKeyboardInputWithoutPageScroll` is followed synchronously by
|
|
1166
|
+
// a re-focus; clearing state here would drop the keyboard inset for a frame.
|
|
1167
|
+
if (programmaticKeyboardFocusRef.current) {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
997
1170
|
if (captureFocusedKeyboardTarget(event.relatedTarget)) {
|
|
1171
|
+
const target = focusedKeyboardTargetRef.current;
|
|
1172
|
+
const keyboardViewport = getKeyboardVisualViewport(win);
|
|
1173
|
+
// The delayed realign passes are scheduled by the `focusin` that follows once
|
|
1174
|
+
// focus lands on the captured target.
|
|
1175
|
+
if (target && keyboardViewport) {
|
|
1176
|
+
preemptFocusReveal(target, keyboardViewport);
|
|
1177
|
+
}
|
|
998
1178
|
scheduleKeyboardFocusAlignment();
|
|
999
1179
|
return;
|
|
1000
1180
|
}
|
|
@@ -1009,25 +1189,36 @@ function DrawerVirtualKeyboardProvider(props) {
|
|
|
1009
1189
|
if (visualViewport) {
|
|
1010
1190
|
cleanupListeners.push(addEventListener(visualViewport, 'resize', handleViewportUpdate), addEventListener(visualViewport, 'scroll', handleViewportUpdate));
|
|
1011
1191
|
}
|
|
1012
|
-
|
|
1192
|
+
const handleWindowScroll = () => {
|
|
1193
|
+
if (restoreWindowScroll()) {
|
|
1194
|
+
// Recompute the keyboard inset once the page is back at rest; measurements taken
|
|
1195
|
+
// mid-reveal (a non-zero visual viewport offset) inflate it and push the popup up.
|
|
1196
|
+
scheduleKeyboardFocusAlignment();
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
// Once the user puts a finger down they own the scroll position. Stop both the frame settle
|
|
1201
|
+
// loop, which can center after changing layout settles, and the delayed passes, which can't
|
|
1202
|
+
// distinguish a user scroll from a reveal scroll WebKit canceled. A later focus or viewport
|
|
1203
|
+
// change reschedules alignment if it's still needed.
|
|
1204
|
+
const cancelKeyboardRealignOnPointerDown = () => {
|
|
1205
|
+
keyboardFocusFrame.cancel();
|
|
1206
|
+
keyboardRealignTimeout.clear();
|
|
1207
|
+
keyboardScrollElement = null;
|
|
1208
|
+
};
|
|
1209
|
+
cleanupListeners.push(addEventListener(doc, 'focusin', handleFocusIn, true), addEventListener(doc, 'focusout', handleFocusOut, true), addEventListener(win, 'scroll', handleWindowScroll), addEventListener(doc, 'pointerdown', cancelKeyboardRealignOnPointerDown, true));
|
|
1013
1210
|
if (captureFocusedKeyboardTarget(activeElement(doc))) {
|
|
1014
1211
|
scheduleKeyboardFocusAlignment();
|
|
1015
1212
|
}
|
|
1016
1213
|
return () => {
|
|
1017
1214
|
cleanupListeners.forEach(cleanup => cleanup());
|
|
1215
|
+
consumePreemptedFocus();
|
|
1018
1216
|
clearFocusedKeyboardTarget();
|
|
1019
1217
|
rootElement.style.removeProperty(DrawerViewportCssVars.keyboardInset);
|
|
1020
1218
|
};
|
|
1021
|
-
}, [animateKeyboardScroll, keyboardFocusFrame, mounted, nestedDrawerOpen, open, restoreKeyboardScrollAdjustment, rootElement, setKeyboardScrollSlack]);
|
|
1219
|
+
}, [animateKeyboardScroll, keyboardFocusFrame, keyboardRealignTimeout, modal, mounted, nestedDrawerOpen, open, restoreKeyboardScrollAdjustment, rootElement, setKeyboardScrollSlack]);
|
|
1022
1220
|
const onTouchStart = useStableCallback(event => {
|
|
1023
|
-
if (!open || !mounted || nestedDrawerOpen) {
|
|
1024
|
-
resetTouchTrackingState();
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
1221
|
const touch = event.touches[0];
|
|
1028
|
-
if (!touch) {
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
1222
|
pendingKeyboardFocusMovedRef.current = false;
|
|
1032
1223
|
keyboardTouchStartRef.current = {
|
|
1033
1224
|
x: touch.clientX,
|
|
@@ -1053,9 +1244,9 @@ function DrawerVirtualKeyboardProvider(props) {
|
|
|
1053
1244
|
return;
|
|
1054
1245
|
}
|
|
1055
1246
|
const touch = event.changedTouches[0] ?? event.touches[0];
|
|
1056
|
-
const
|
|
1247
|
+
const root = rootElement.getRootNode();
|
|
1057
1248
|
const nativeEventTarget = getTarget(event.nativeEvent);
|
|
1058
|
-
const pointTarget = touch ? resolveKeyboardTouchTargetFromPoint(
|
|
1249
|
+
const pointTarget = touch ? resolveKeyboardTouchTargetFromPoint(root, touch.clientX, touch.clientY) : null;
|
|
1059
1250
|
|
|
1060
1251
|
// The lift point landed on another interactive/label element; let its native tap
|
|
1061
1252
|
// through instead of stealing it for the touchstart input.
|
|
@@ -1084,15 +1275,24 @@ function DrawerVirtualKeyboardProvider(props) {
|
|
|
1084
1275
|
|
|
1085
1276
|
// Already focused with the keyboard up: let the native tap through so it can
|
|
1086
1277
|
// reposition the caret, rather than blurring and re-focusing the same input.
|
|
1087
|
-
if (activeElement(ownerDocument(keyboardFocusTarget)) === keyboardFocusTarget &&
|
|
1278
|
+
if (activeElement(ownerDocument(keyboardFocusTarget)) === keyboardFocusTarget && (!win.visualViewport || getKeyboardVisualViewport(win) != null)) {
|
|
1088
1279
|
resetTouchTrackingState();
|
|
1089
1280
|
return;
|
|
1090
1281
|
}
|
|
1091
1282
|
|
|
1092
1283
|
// iOS only opens the software keyboard when focus happens synchronously
|
|
1093
|
-
// inside the touch gesture.
|
|
1284
|
+
// inside the touch gesture. The flag suppresses the `focusout` cleanup the
|
|
1285
|
+
// intermediate blur would otherwise trigger, so the keyboard inset isn't
|
|
1286
|
+
// dropped for a frame between the blur and the re-focus. It is cleared by the
|
|
1287
|
+
// `focusin` that lands, so only that blur is suppressed; the `finally` is the
|
|
1288
|
+
// backstop for a target that never takes focus.
|
|
1094
1289
|
event.preventDefault();
|
|
1095
|
-
|
|
1290
|
+
programmaticKeyboardFocusRef.current = true;
|
|
1291
|
+
try {
|
|
1292
|
+
focusKeyboardInputWithoutPageScroll(keyboardFocusTarget);
|
|
1293
|
+
} finally {
|
|
1294
|
+
programmaticKeyboardFocusRef.current = false;
|
|
1295
|
+
}
|
|
1096
1296
|
// Preventing the touchend default also suppresses the compatibility mouse
|
|
1097
1297
|
// events, including `click`; redispatch an untrusted replacement on the
|
|
1098
1298
|
// original tap target so click handlers still run with the tap coordinates.
|
|
@@ -1137,13 +1337,16 @@ function resolveKeyboardInputTarget(target) {
|
|
|
1137
1337
|
return isHTMLElement(control) && isKeyboardInputElement(control) ? control : null;
|
|
1138
1338
|
}
|
|
1139
1339
|
function resolveKeyboardTouchTarget(target) {
|
|
1340
|
+
if (!isHTMLElement(target)) {
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1140
1343
|
const focusTarget = resolveKeyboardInputTarget(target);
|
|
1141
1344
|
if (!focusTarget) {
|
|
1142
1345
|
return null;
|
|
1143
1346
|
}
|
|
1144
1347
|
return {
|
|
1145
1348
|
focusTarget,
|
|
1146
|
-
clickTarget:
|
|
1349
|
+
clickTarget: target
|
|
1147
1350
|
};
|
|
1148
1351
|
}
|
|
1149
1352
|
|
|
@@ -1156,14 +1359,16 @@ function getContentEditableHost(element) {
|
|
|
1156
1359
|
}
|
|
1157
1360
|
return host;
|
|
1158
1361
|
}
|
|
1159
|
-
function resolveKeyboardTouchTargetFromPoint(
|
|
1160
|
-
const exactTarget = getElementAtPoint(
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1362
|
+
function resolveKeyboardTouchTargetFromPoint(root, clientX, clientY) {
|
|
1363
|
+
const exactTarget = getElementAtPoint(root, clientX, clientY);
|
|
1364
|
+
if (isHTMLElement(exactTarget)) {
|
|
1365
|
+
const exactKeyboardTarget = resolveKeyboardInputTarget(exactTarget);
|
|
1366
|
+
if (exactKeyboardTarget) {
|
|
1367
|
+
return {
|
|
1368
|
+
focusTarget: exactKeyboardTarget,
|
|
1369
|
+
clickTarget: exactTarget
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1167
1372
|
}
|
|
1168
1373
|
|
|
1169
1374
|
// Probing nearby points compensates for iOS retargeting taps while the page reacts
|
|
@@ -1176,7 +1381,7 @@ function resolveKeyboardTouchTargetFromPoint(doc, clientX, clientY) {
|
|
|
1176
1381
|
return KEYBOARD_TAP_BLOCKED;
|
|
1177
1382
|
}
|
|
1178
1383
|
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]]) {
|
|
1179
|
-
const keyboardTarget = resolveKeyboardInputTarget(getElementAtPoint(
|
|
1384
|
+
const keyboardTarget = resolveKeyboardInputTarget(getElementAtPoint(root, clientX + offsetX, clientY + offsetY));
|
|
1180
1385
|
if (keyboardTarget) {
|
|
1181
1386
|
return {
|
|
1182
1387
|
focusTarget: keyboardTarget,
|
|
@@ -1200,15 +1405,10 @@ function dispatchKeyboardClick(target, touch) {
|
|
|
1200
1405
|
}
|
|
1201
1406
|
function focusKeyboardInputWithoutPageScroll(target) {
|
|
1202
1407
|
const wasFocused = activeElement(ownerDocument(target)) === target;
|
|
1203
|
-
const previousOpacity = target.style.opacity;
|
|
1204
|
-
const previousTransform = target.style.transform;
|
|
1205
|
-
const previousTransition = target.style.transition;
|
|
1206
|
-
|
|
1207
1408
|
// iOS Safari can still scroll the page for transformed sheets even with preventScroll.
|
|
1208
|
-
// Move the input off-screen only for the synchronous focus call
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
target.style.transform = 'translateY(-2000px)';
|
|
1409
|
+
// Move the input off-screen only for the synchronous focus call; `preventScroll`
|
|
1410
|
+
// suppresses the in-page ancestor scrolling that would otherwise chase the fake rect.
|
|
1411
|
+
const restoreStyles = overrideGeometryDuringFocus(target, -2000);
|
|
1212
1412
|
try {
|
|
1213
1413
|
if (wasFocused) {
|
|
1214
1414
|
target.blur();
|
|
@@ -1217,10 +1417,25 @@ function focusKeyboardInputWithoutPageScroll(target) {
|
|
|
1217
1417
|
preventScroll: true
|
|
1218
1418
|
});
|
|
1219
1419
|
} finally {
|
|
1420
|
+
restoreStyles();
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// Overrides the painted geometry WebKit samples when an element gains focus. The rect is
|
|
1425
|
+
// hidden (opacity) rather than detached so layout is unaffected; the caller must restore
|
|
1426
|
+
// synchronously before the next paint.
|
|
1427
|
+
function overrideGeometryDuringFocus(target, translateY) {
|
|
1428
|
+
const previousOpacity = target.style.opacity;
|
|
1429
|
+
const previousTransform = target.style.transform;
|
|
1430
|
+
const previousTransition = target.style.transition;
|
|
1431
|
+
target.style.transition = 'none';
|
|
1432
|
+
target.style.opacity = '0';
|
|
1433
|
+
target.style.transform = `translateY(${translateY}px)`;
|
|
1434
|
+
return () => {
|
|
1220
1435
|
target.style.opacity = previousOpacity;
|
|
1221
1436
|
target.style.transform = previousTransform;
|
|
1222
1437
|
target.style.transition = previousTransition;
|
|
1223
|
-
}
|
|
1438
|
+
};
|
|
1224
1439
|
}
|
|
1225
1440
|
function findKeyboardScrollTarget(target, root) {
|
|
1226
1441
|
// Start at the parent: scrolling the focused field's own content (an overflowing
|
|
@@ -1248,11 +1463,22 @@ function getKeyboardVisualViewport(win) {
|
|
|
1248
1463
|
bottom: Math.min(win.innerHeight, top + visualViewport.height)
|
|
1249
1464
|
};
|
|
1250
1465
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1466
|
+
|
|
1467
|
+
/**
|
|
1468
|
+
* Controls a Drawer imperatively and associates detached `Drawer.Trigger` components with a
|
|
1469
|
+
* `Drawer.Root`. Create one with `Drawer.createHandle()` and pass it to the `handle` prop of the
|
|
1470
|
+
* root and of any triggers rendered outside of it.
|
|
1471
|
+
*
|
|
1472
|
+
* The imperative methods take effect only while a root using this handle is mounted; calls made
|
|
1473
|
+
* before a root attaches (or after it unmounts) are ignored.
|
|
1474
|
+
*/
|
|
1475
|
+
class DrawerHandle extends DialogHandle {}
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* Creates a new handle to connect a Drawer.Root with detached Drawer.Trigger components.
|
|
1479
|
+
*/
|
|
1480
|
+
function createDrawerHandle() {
|
|
1481
|
+
return new DrawerHandle();
|
|
1256
1482
|
}
|
|
1257
1483
|
|
|
1258
1484
|
var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
@@ -1261,7 +1487,7 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
|
1261
1487
|
Close: DrawerClose,
|
|
1262
1488
|
Content: DrawerContent,
|
|
1263
1489
|
Description: DrawerDescription,
|
|
1264
|
-
Handle:
|
|
1490
|
+
Handle: DrawerHandle,
|
|
1265
1491
|
Indent: DrawerIndent,
|
|
1266
1492
|
IndentBackground: DrawerIndentBackground,
|
|
1267
1493
|
Popup: DrawerPopup,
|
|
@@ -1273,7 +1499,7 @@ var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
|
1273
1499
|
Trigger: DrawerTrigger,
|
|
1274
1500
|
Viewport: DrawerViewport,
|
|
1275
1501
|
VirtualKeyboardProvider: DrawerVirtualKeyboardProvider,
|
|
1276
|
-
createHandle:
|
|
1502
|
+
createHandle: createDrawerHandle
|
|
1277
1503
|
});
|
|
1278
1504
|
|
|
1279
1505
|
/**
|
|
@@ -1342,7 +1568,7 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
|
|
|
1342
1568
|
id: idProp,
|
|
1343
1569
|
label,
|
|
1344
1570
|
nativeButton = false,
|
|
1345
|
-
disabled = false,
|
|
1571
|
+
disabled: disabledProp = false,
|
|
1346
1572
|
closeOnClick = false,
|
|
1347
1573
|
checked: checkedProp,
|
|
1348
1574
|
defaultChecked,
|
|
@@ -1351,6 +1577,7 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
|
|
|
1351
1577
|
...elementProps
|
|
1352
1578
|
} = componentProps;
|
|
1353
1579
|
const listItem = useCompositeListItem({
|
|
1580
|
+
guess: true,
|
|
1354
1581
|
label
|
|
1355
1582
|
});
|
|
1356
1583
|
const menuPositionerContext = useMenuPositionerContext(true);
|
|
@@ -1358,6 +1585,8 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
|
|
|
1358
1585
|
const {
|
|
1359
1586
|
store
|
|
1360
1587
|
} = useMenuRootContext();
|
|
1588
|
+
const rootDisabled = store.useState('disabled');
|
|
1589
|
+
const disabled = disabledProp || rootDisabled;
|
|
1361
1590
|
const highlighted = store.useState('isActive', listItem.index);
|
|
1362
1591
|
const itemProps = store.useState('itemProps');
|
|
1363
1592
|
const [checked, setChecked] = useControlled({
|
|
@@ -1386,7 +1615,7 @@ const MenuCheckboxItem = /*#__PURE__*/React.forwardRef(function MenuCheckboxItem
|
|
|
1386
1615
|
}), [disabled, highlighted, checked]);
|
|
1387
1616
|
function handleClick(event) {
|
|
1388
1617
|
const details = createChangeEventDetails(itemPress, event.nativeEvent, undefined, {
|
|
1389
|
-
preventUnmountOnClose
|
|
1618
|
+
preventUnmountOnClose: NOOP
|
|
1390
1619
|
});
|
|
1391
1620
|
onCheckedChange?.(!checked, details);
|
|
1392
1621
|
if (details.isCanceled) {
|
|
@@ -1429,6 +1658,7 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React.forwardRef(function MenuChe
|
|
|
1429
1658
|
const indicatorRef = React.useRef(null);
|
|
1430
1659
|
const {
|
|
1431
1660
|
transitionStatus,
|
|
1661
|
+
mounted,
|
|
1432
1662
|
setMounted
|
|
1433
1663
|
} = useTransitionStatus(item.checked);
|
|
1434
1664
|
useOpenChangeComplete({
|
|
@@ -1454,7 +1684,7 @@ const MenuCheckboxItemIndicator = /*#__PURE__*/React.forwardRef(function MenuChe
|
|
|
1454
1684
|
'aria-hidden': true,
|
|
1455
1685
|
...elementProps
|
|
1456
1686
|
},
|
|
1457
|
-
enabled: keepMounted ||
|
|
1687
|
+
enabled: keepMounted || mounted
|
|
1458
1688
|
});
|
|
1459
1689
|
return element;
|
|
1460
1690
|
});
|
|
@@ -1494,30 +1724,14 @@ function usePopupAutoResize(parameters) {
|
|
|
1494
1724
|
side,
|
|
1495
1725
|
direction
|
|
1496
1726
|
} = parameters;
|
|
1497
|
-
const runOnceAnimationsFinish = useAnimationsFinished(popupElement, true
|
|
1727
|
+
const runOnceAnimationsFinish = useAnimationsFinished(popupElement, true);
|
|
1498
1728
|
const animationFrame = useAnimationFrame();
|
|
1499
1729
|
const committedDimensionsRef = React.useRef(null);
|
|
1500
1730
|
const isInitialRenderRef = React.useRef(true);
|
|
1501
1731
|
const restoreAnchoringStylesRef = React.useRef(NOOP);
|
|
1502
1732
|
const onMeasureLayout = useStableCallback(onMeasureLayoutParam);
|
|
1503
1733
|
const onMeasureLayoutComplete = useStableCallback(onMeasureLayoutCompleteParam);
|
|
1504
|
-
const anchoringStyles = React.useMemo(() =>
|
|
1505
|
-
// Ensure popup size transitions correctly when anchored to `bottom` (side=top) or `right` (side=left).
|
|
1506
|
-
let isOriginSide = side === 'top';
|
|
1507
|
-
let isPhysicalLeft = side === 'left';
|
|
1508
|
-
if (direction === 'rtl') {
|
|
1509
|
-
isOriginSide = isOriginSide || side === 'inline-end';
|
|
1510
|
-
isPhysicalLeft = isPhysicalLeft || side === 'inline-end';
|
|
1511
|
-
} else {
|
|
1512
|
-
isOriginSide = isOriginSide || side === 'inline-start';
|
|
1513
|
-
isPhysicalLeft = isPhysicalLeft || side === 'inline-start';
|
|
1514
|
-
}
|
|
1515
|
-
return isOriginSide ? {
|
|
1516
|
-
position: 'absolute',
|
|
1517
|
-
[side === 'top' ? 'bottom' : 'top']: '0',
|
|
1518
|
-
[isPhysicalLeft ? 'right' : 'left']: '0'
|
|
1519
|
-
} : EMPTY_OBJECT;
|
|
1520
|
-
}, [side, direction]);
|
|
1734
|
+
const anchoringStyles = React.useMemo(() => getPopupAnchoringStyles(side, direction), [side, direction]);
|
|
1521
1735
|
useIsoLayoutEffect(() => {
|
|
1522
1736
|
// Reset the state when the popup is closed.
|
|
1523
1737
|
if (!mounted) {
|
|
@@ -1593,6 +1807,19 @@ function usePopupAutoResize(parameters) {
|
|
|
1593
1807
|
};
|
|
1594
1808
|
}, [content, popupElement, positionerElement, runOnceAnimationsFinish, animationFrame, mounted, onMeasureLayout, onMeasureLayoutComplete, anchoringStyles]);
|
|
1595
1809
|
}
|
|
1810
|
+
function getPopupAnchoringStyles(side, direction) {
|
|
1811
|
+
// Ensure popup size transitions correctly when anchored to `bottom` (side=top) or `right` (side=left).
|
|
1812
|
+
const isPhysicalTop = side === 'top';
|
|
1813
|
+
const isPhysicalLeft = side === 'left' || side === (direction === 'rtl' ? 'inline-end' : 'inline-start');
|
|
1814
|
+
if (!isPhysicalTop && !isPhysicalLeft) {
|
|
1815
|
+
return EMPTY_OBJECT;
|
|
1816
|
+
}
|
|
1817
|
+
return {
|
|
1818
|
+
position: 'absolute',
|
|
1819
|
+
[isPhysicalTop ? 'bottom' : 'top']: '0',
|
|
1820
|
+
[isPhysicalLeft ? 'right' : 'left']: '0'
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1596
1823
|
function overrideElementStyle(element, property, value) {
|
|
1597
1824
|
const originalValue = element.style.getPropertyValue(property);
|
|
1598
1825
|
element.style.setProperty(property, value);
|
|
@@ -1622,6 +1849,80 @@ function setPositionerCssSize(positionerElement, size) {
|
|
|
1622
1849
|
positionerElement.style.setProperty('--positioner-height', height);
|
|
1623
1850
|
}
|
|
1624
1851
|
|
|
1852
|
+
const adaptiveOrigin = {
|
|
1853
|
+
name: 'adaptiveOrigin',
|
|
1854
|
+
async fn(state) {
|
|
1855
|
+
const {
|
|
1856
|
+
x: rawX,
|
|
1857
|
+
y: rawY,
|
|
1858
|
+
rects: {
|
|
1859
|
+
floating: floatRect
|
|
1860
|
+
},
|
|
1861
|
+
elements: {
|
|
1862
|
+
floating
|
|
1863
|
+
},
|
|
1864
|
+
platform,
|
|
1865
|
+
strategy,
|
|
1866
|
+
placement
|
|
1867
|
+
} = state;
|
|
1868
|
+
const win = getWindow(floating);
|
|
1869
|
+
const styles = win.getComputedStyle(floating);
|
|
1870
|
+
const hasTransition = styles.transitionDuration !== '0s' && styles.transitionDuration !== '';
|
|
1871
|
+
if (!hasTransition) {
|
|
1872
|
+
return {
|
|
1873
|
+
x: rawX,
|
|
1874
|
+
y: rawY,
|
|
1875
|
+
data: DEFAULT_SIDES
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
const offsetParent = await platform.getOffsetParent?.(floating);
|
|
1879
|
+
let offsetDimensions = {
|
|
1880
|
+
width: 0,
|
|
1881
|
+
height: 0
|
|
1882
|
+
};
|
|
1883
|
+
|
|
1884
|
+
// For fixed strategy, prefer visualViewport if available
|
|
1885
|
+
if (strategy === 'fixed' && win?.visualViewport) {
|
|
1886
|
+
offsetDimensions = {
|
|
1887
|
+
width: win.visualViewport.width,
|
|
1888
|
+
height: win.visualViewport.height
|
|
1889
|
+
};
|
|
1890
|
+
} else if (offsetParent === win) {
|
|
1891
|
+
const doc = ownerDocument(floating);
|
|
1892
|
+
offsetDimensions = {
|
|
1893
|
+
width: doc.documentElement.clientWidth,
|
|
1894
|
+
height: doc.documentElement.clientHeight
|
|
1895
|
+
};
|
|
1896
|
+
} else if (await platform.isElement?.(offsetParent)) {
|
|
1897
|
+
offsetDimensions = await platform.getDimensions(offsetParent);
|
|
1898
|
+
}
|
|
1899
|
+
const currentSide = getSide(placement);
|
|
1900
|
+
let x = rawX;
|
|
1901
|
+
let y = rawY;
|
|
1902
|
+
if (currentSide === 'left') {
|
|
1903
|
+
x = offsetDimensions.width - (rawX + floatRect.width);
|
|
1904
|
+
}
|
|
1905
|
+
if (currentSide === 'top') {
|
|
1906
|
+
y = offsetDimensions.height - (rawY + floatRect.height);
|
|
1907
|
+
}
|
|
1908
|
+
const sideX = currentSide === 'left' ? 'right' : DEFAULT_SIDES.sideX;
|
|
1909
|
+
const sideY = currentSide === 'top' ? 'bottom' : DEFAULT_SIDES.sideY;
|
|
1910
|
+
return {
|
|
1911
|
+
x,
|
|
1912
|
+
y,
|
|
1913
|
+
data: {
|
|
1914
|
+
sideX,
|
|
1915
|
+
sideY
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
|
|
1921
|
+
const popupViewportStateMapping = {
|
|
1922
|
+
activationDirection: value => value ? {
|
|
1923
|
+
'data-activation-direction': value
|
|
1924
|
+
} : null
|
|
1925
|
+
};
|
|
1625
1926
|
/**
|
|
1626
1927
|
* Builds morphing viewport containers for popups that animate between trigger-based content.
|
|
1627
1928
|
* Handles previous-content snapshots, auto-resize, and state attributes for transitions.
|
|
@@ -1630,7 +1931,6 @@ function usePopupViewport(parameters) {
|
|
|
1630
1931
|
const {
|
|
1631
1932
|
store,
|
|
1632
1933
|
side,
|
|
1633
|
-
cssVars,
|
|
1634
1934
|
children
|
|
1635
1935
|
} = parameters;
|
|
1636
1936
|
const direction = useDirection();
|
|
@@ -1650,14 +1950,15 @@ function usePopupViewport(parameters) {
|
|
|
1650
1950
|
const [newTriggerOffset, setNewTriggerOffset] = React.useState(null);
|
|
1651
1951
|
const currentContainerRef = React.useRef(null);
|
|
1652
1952
|
const previousContainerRef = React.useRef(null);
|
|
1653
|
-
const onAnimationsFinished = useAnimationsFinished(currentContainerRef, true
|
|
1953
|
+
const onAnimationsFinished = useAnimationsFinished(currentContainerRef, true);
|
|
1654
1954
|
const cleanupFrame = useAnimationFrame();
|
|
1955
|
+
const cleanupControllerRef = React.useRef(null);
|
|
1655
1956
|
const [previousContentDimensions, setPreviousContentDimensions] = React.useState(null);
|
|
1656
1957
|
const [showStartingStyleAttribute, setShowStartingStyleAttribute] = React.useState(false);
|
|
1657
1958
|
useIsoLayoutEffect(() => {
|
|
1658
|
-
store.set('
|
|
1959
|
+
store.set('adaptiveOrigin', adaptiveOrigin);
|
|
1659
1960
|
return () => {
|
|
1660
|
-
store.set('
|
|
1961
|
+
store.set('adaptiveOrigin', undefined);
|
|
1661
1962
|
};
|
|
1662
1963
|
}, [store]);
|
|
1663
1964
|
const handleMeasureLayout = useStableCallback(() => {
|
|
@@ -1673,6 +1974,16 @@ function usePopupViewport(parameters) {
|
|
|
1673
1974
|
setPreviousContentDimensions(previousDimensions);
|
|
1674
1975
|
}
|
|
1675
1976
|
});
|
|
1977
|
+
const armViewportCleanup = useStableCallback(() => {
|
|
1978
|
+
cleanupControllerRef.current?.abort();
|
|
1979
|
+
const controller = new AbortController();
|
|
1980
|
+
cleanupControllerRef.current = controller;
|
|
1981
|
+
onAnimationsFinished(() => {
|
|
1982
|
+
setPreviousContentNode(null);
|
|
1983
|
+
setPreviousContentDimensions(null);
|
|
1984
|
+
capturedNodeRef.current = null;
|
|
1985
|
+
}, controller.signal);
|
|
1986
|
+
});
|
|
1676
1987
|
const lastHandledTriggerRef = React.useRef(null);
|
|
1677
1988
|
useIsoLayoutEffect(() => {
|
|
1678
1989
|
if (!open || !mounted) {
|
|
@@ -1690,19 +2001,33 @@ function usePopupViewport(parameters) {
|
|
|
1690
2001
|
// so we can pass it to the style hook for animation purposes.
|
|
1691
2002
|
const offset = calculateRelativePosition(previousActiveTrigger, activeTrigger);
|
|
1692
2003
|
setNewTriggerOffset(offset);
|
|
1693
|
-
cleanupFrame.request(() => {
|
|
1694
|
-
ReactDOM.flushSync(() => {
|
|
1695
|
-
setShowStartingStyleAttribute(false);
|
|
1696
|
-
});
|
|
1697
|
-
onAnimationsFinished(() => {
|
|
1698
|
-
setPreviousContentNode(null);
|
|
1699
|
-
setPreviousContentDimensions(null);
|
|
1700
|
-
capturedNodeRef.current = null;
|
|
1701
|
-
});
|
|
1702
|
-
});
|
|
1703
2004
|
lastHandledTriggerRef.current = activeTrigger;
|
|
1704
2005
|
}
|
|
1705
|
-
}, [activeTrigger, previousActiveTrigger
|
|
2006
|
+
}, [activeTrigger, previousActiveTrigger]);
|
|
2007
|
+
|
|
2008
|
+
// Arm cleanup after a trigger change, and re-arm it if the current container remounts
|
|
2009
|
+
// mid-transition when a lagging payload bumps `currentContentKey`. The remount discards
|
|
2010
|
+
// the running entry animation (and with transition-style CSS the replacement mounts at
|
|
2011
|
+
// final styles with no animation at all), so re-run the starting-style choreography —
|
|
2012
|
+
// otherwise the watcher either strands or fires before the previous container's exit
|
|
2013
|
+
// animation finishes.
|
|
2014
|
+
useIsoLayoutEffect(() => {
|
|
2015
|
+
if (previousContentNode == null) {
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
// Abort the stale watcher synchronously. The remount cancels the old container's
|
|
2020
|
+
// animations, and the resulting promise rejection would otherwise run the cleanup
|
|
2021
|
+
// in a microtask before the re-armed watcher below is in place.
|
|
2022
|
+
cleanupControllerRef.current?.abort();
|
|
2023
|
+
setShowStartingStyleAttribute(true);
|
|
2024
|
+
cleanupFrame.request(() => {
|
|
2025
|
+
ReactDOM.flushSync(() => {
|
|
2026
|
+
setShowStartingStyleAttribute(false);
|
|
2027
|
+
});
|
|
2028
|
+
armViewportCleanup();
|
|
2029
|
+
});
|
|
2030
|
+
}, [currentContentKey, previousContentNode, armViewportCleanup, cleanupFrame]);
|
|
1706
2031
|
|
|
1707
2032
|
// Capture a clone of the current content DOM subtree when not transitioning.
|
|
1708
2033
|
// We can't store previous React nodes as they may be stateful; instead we capture DOM clones for visual continuity.
|
|
@@ -1739,8 +2064,8 @@ function usePopupViewport(parameters) {
|
|
|
1739
2064
|
ref: previousContainerRef,
|
|
1740
2065
|
style: {
|
|
1741
2066
|
...(previousContentDimensions ? {
|
|
1742
|
-
|
|
1743
|
-
|
|
2067
|
+
'--popup-width': `${previousContentDimensions.width}px`,
|
|
2068
|
+
'--popup-height': `${previousContentDimensions.height}px`
|
|
1744
2069
|
} : null),
|
|
1745
2070
|
position: 'absolute'
|
|
1746
2071
|
},
|
|
@@ -1865,28 +2190,6 @@ function usePopupContentKey(activeTriggerId, payload) {
|
|
|
1865
2190
|
return `${activeTriggerId ?? 'current'}-${contentKey}`;
|
|
1866
2191
|
}
|
|
1867
2192
|
|
|
1868
|
-
let MenuViewportCssVars = /*#__PURE__*/function (MenuViewportCssVars) {
|
|
1869
|
-
/**
|
|
1870
|
-
* The width of the parent popup.
|
|
1871
|
-
* This variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.
|
|
1872
|
-
* It can be used to freeze the dimensions of the popup when animating between different content.
|
|
1873
|
-
*/
|
|
1874
|
-
MenuViewportCssVars["popupWidth"] = "--popup-width";
|
|
1875
|
-
/**
|
|
1876
|
-
* The height of the parent popup.
|
|
1877
|
-
* This variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.
|
|
1878
|
-
* It can be used to freeze the dimensions of the popup when animating between different content.
|
|
1879
|
-
*/
|
|
1880
|
-
MenuViewportCssVars["popupHeight"] = "--popup-height";
|
|
1881
|
-
return MenuViewportCssVars;
|
|
1882
|
-
}({});
|
|
1883
|
-
|
|
1884
|
-
const stateAttributesMapping$2 = {
|
|
1885
|
-
activationDirection: value => value ? {
|
|
1886
|
-
'data-activation-direction': value
|
|
1887
|
-
} : null
|
|
1888
|
-
};
|
|
1889
|
-
|
|
1890
2193
|
/**
|
|
1891
2194
|
* A viewport for displaying content transitions.
|
|
1892
2195
|
* This component is only required if one popup can be opened by multiple triggers, its content
|
|
@@ -1916,7 +2219,6 @@ const MenuViewport = /*#__PURE__*/React.forwardRef(function MenuViewport(compone
|
|
|
1916
2219
|
} = usePopupViewport({
|
|
1917
2220
|
store,
|
|
1918
2221
|
side,
|
|
1919
|
-
cssVars: MenuViewportCssVars,
|
|
1920
2222
|
children
|
|
1921
2223
|
});
|
|
1922
2224
|
const state = {
|
|
@@ -1930,47 +2232,50 @@ const MenuViewport = /*#__PURE__*/React.forwardRef(function MenuViewport(compone
|
|
|
1930
2232
|
props: [elementProps, {
|
|
1931
2233
|
children: childrenToRender
|
|
1932
2234
|
}],
|
|
1933
|
-
stateAttributesMapping:
|
|
2235
|
+
stateAttributesMapping: popupViewportStateMapping
|
|
1934
2236
|
});
|
|
1935
2237
|
});
|
|
1936
2238
|
if (process.env.NODE_ENV !== "production") MenuViewport.displayName = "MenuViewport";
|
|
1937
2239
|
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
2240
|
+
/**
|
|
2241
|
+
* Controls a Menu imperatively and associates detached `Menu.Trigger` components with a `Menu.Root`.
|
|
2242
|
+
* Create one with `Menu.createHandle()` and pass it to the `handle` prop of the root and of any
|
|
2243
|
+
* triggers rendered outside of it.
|
|
2244
|
+
*
|
|
2245
|
+
* The imperative methods take effect only while a root using this handle is mounted; calls made
|
|
2246
|
+
* before a root attaches (or after it unmounts) are ignored.
|
|
2247
|
+
*/
|
|
2248
|
+
class MenuHandle extends BasePopupHandle {
|
|
1944
2249
|
constructor() {
|
|
1945
|
-
|
|
2250
|
+
super(createNullMenuStore(), 'Menu');
|
|
1946
2251
|
}
|
|
1947
2252
|
|
|
1948
2253
|
/**
|
|
1949
2254
|
* Opens the menu and associates it with the trigger with the given id.
|
|
1950
|
-
* The trigger must be a Menu.Trigger component with this handle passed as a prop.
|
|
1951
2255
|
*
|
|
1952
|
-
*
|
|
2256
|
+
* This method should only be called in an event handler or an effect (not during rendering).
|
|
2257
|
+
*
|
|
2258
|
+
* @param triggerId ID of the trigger to associate with the menu. The trigger must be a matching
|
|
2259
|
+
* `Menu.Trigger` with this handle passed as a prop.
|
|
1953
2260
|
*/
|
|
1954
2261
|
open(triggerId) {
|
|
1955
|
-
|
|
1956
|
-
if (triggerId && !triggerElement) {
|
|
1957
|
-
throw new Error(process.env.NODE_ENV !== "production" ? `Base UI: MenuHandle.open: No trigger found with id "${triggerId}".` : formatErrorMessage(83, triggerId));
|
|
1958
|
-
}
|
|
1959
|
-
this.store.setOpen(true, createChangeEventDetails('imperative-action', undefined, triggerElement));
|
|
2262
|
+
this.openByTrigger(triggerId);
|
|
1960
2263
|
}
|
|
1961
2264
|
|
|
1962
2265
|
/**
|
|
1963
2266
|
* Closes the menu.
|
|
2267
|
+
*
|
|
2268
|
+
* This method should only be called in an event handler or an effect (not during rendering).
|
|
1964
2269
|
*/
|
|
1965
2270
|
close() {
|
|
1966
|
-
this.
|
|
2271
|
+
this.closePopup();
|
|
1967
2272
|
}
|
|
1968
2273
|
|
|
1969
2274
|
/**
|
|
1970
|
-
*
|
|
2275
|
+
* Whether the menu is currently open. Returns `false` while no root is attached to the handle.
|
|
1971
2276
|
*/
|
|
1972
2277
|
get isOpen() {
|
|
1973
|
-
return this.
|
|
2278
|
+
return this.attachedStore?.select('open') ?? false;
|
|
1974
2279
|
}
|
|
1975
2280
|
}
|
|
1976
2281
|
|
|
@@ -2017,20 +2322,6 @@ function useScrollAreaScrollbarContext() {
|
|
|
2017
2322
|
return context;
|
|
2018
2323
|
}
|
|
2019
2324
|
|
|
2020
|
-
let ScrollAreaScrollbarCssVars = /*#__PURE__*/function (ScrollAreaScrollbarCssVars) {
|
|
2021
|
-
/**
|
|
2022
|
-
* The scroll area thumb's height.
|
|
2023
|
-
* @type {number}
|
|
2024
|
-
*/
|
|
2025
|
-
ScrollAreaScrollbarCssVars["scrollAreaThumbHeight"] = "--scroll-area-thumb-height";
|
|
2026
|
-
/**
|
|
2027
|
-
* The scroll area thumb's width.
|
|
2028
|
-
* @type {number}
|
|
2029
|
-
*/
|
|
2030
|
-
ScrollAreaScrollbarCssVars["scrollAreaThumbWidth"] = "--scroll-area-thumb-width";
|
|
2031
|
-
return ScrollAreaScrollbarCssVars;
|
|
2032
|
-
}({});
|
|
2033
|
-
|
|
2034
2325
|
/**
|
|
2035
2326
|
* A vertical or horizontal scrollbar for the scroll area.
|
|
2036
2327
|
* Renders a `<div>` element.
|
|
@@ -2051,7 +2342,6 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
2051
2342
|
scrollingX,
|
|
2052
2343
|
scrollingY,
|
|
2053
2344
|
hiddenState,
|
|
2054
|
-
overflowEdges,
|
|
2055
2345
|
scrollbarYRef,
|
|
2056
2346
|
scrollbarXRef,
|
|
2057
2347
|
viewportRef,
|
|
@@ -2060,43 +2350,37 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
2060
2350
|
handlePointerDown,
|
|
2061
2351
|
handlePointerUp,
|
|
2062
2352
|
handleScroll,
|
|
2353
|
+
disableViewportSnap,
|
|
2063
2354
|
rootId,
|
|
2064
2355
|
thumbSize,
|
|
2065
|
-
hasMeasuredScrollbar
|
|
2356
|
+
hasMeasuredScrollbar,
|
|
2357
|
+
viewportState
|
|
2066
2358
|
} = useScrollAreaRootContext();
|
|
2359
|
+
const vertical = orientation === 'vertical';
|
|
2067
2360
|
const state = {
|
|
2361
|
+
...viewportState,
|
|
2068
2362
|
hovering,
|
|
2069
|
-
scrolling:
|
|
2070
|
-
|
|
2071
|
-
vertical: scrollingY
|
|
2072
|
-
}[orientation],
|
|
2073
|
-
orientation,
|
|
2074
|
-
hasOverflowX: !hiddenState.x,
|
|
2075
|
-
hasOverflowY: !hiddenState.y,
|
|
2076
|
-
overflowXStart: overflowEdges.xStart,
|
|
2077
|
-
overflowXEnd: overflowEdges.xEnd,
|
|
2078
|
-
overflowYStart: overflowEdges.yStart,
|
|
2079
|
-
overflowYEnd: overflowEdges.yEnd,
|
|
2080
|
-
cornerHidden: hiddenState.corner
|
|
2363
|
+
scrolling: vertical ? scrollingY : scrollingX,
|
|
2364
|
+
orientation
|
|
2081
2365
|
};
|
|
2082
2366
|
const direction = useDirection();
|
|
2083
2367
|
const hideTrackUntilMeasured = !hasMeasuredScrollbar && !keepMounted;
|
|
2084
|
-
const isHidden =
|
|
2368
|
+
const isHidden = vertical ? hiddenState.y : hiddenState.x;
|
|
2085
2369
|
const shouldRender = keepMounted || !isHidden;
|
|
2086
2370
|
React.useEffect(() => {
|
|
2087
2371
|
if (!shouldRender) {
|
|
2088
2372
|
return undefined;
|
|
2089
2373
|
}
|
|
2090
2374
|
const viewportEl = viewportRef.current;
|
|
2091
|
-
const scrollbarEl =
|
|
2375
|
+
const scrollbarEl = vertical ? scrollbarYRef.current : scrollbarXRef.current;
|
|
2092
2376
|
if (!scrollbarEl) {
|
|
2093
2377
|
return undefined;
|
|
2094
2378
|
}
|
|
2095
2379
|
function handleWheel(event) {
|
|
2096
|
-
if (!viewportEl ||
|
|
2380
|
+
if (!viewportEl || event.ctrlKey) {
|
|
2097
2381
|
return;
|
|
2098
2382
|
}
|
|
2099
|
-
const horizontal =
|
|
2383
|
+
const horizontal = !vertical;
|
|
2100
2384
|
const scrollProperty = horizontal ? 'scrollLeft' : 'scrollTop';
|
|
2101
2385
|
const delta = horizontal ? event.deltaX : event.deltaY;
|
|
2102
2386
|
if (delta === 0) {
|
|
@@ -2123,7 +2407,7 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
2123
2407
|
return addEventListener(scrollbarEl, 'wheel', handleWheel, {
|
|
2124
2408
|
passive: false
|
|
2125
2409
|
});
|
|
2126
|
-
}, [direction, handleScroll,
|
|
2410
|
+
}, [direction, handleScroll, vertical, scrollbarXRef, scrollbarYRef, shouldRender, viewportRef]);
|
|
2127
2411
|
const props = {
|
|
2128
2412
|
...(rootId && {
|
|
2129
2413
|
'data-id': `${rootId}-scrollbar`
|
|
@@ -2133,58 +2417,55 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
2133
2417
|
return;
|
|
2134
2418
|
}
|
|
2135
2419
|
const target = getTarget(event.nativeEvent);
|
|
2136
|
-
const
|
|
2420
|
+
const thumbEl = vertical ? thumbYRef.current : thumbXRef.current;
|
|
2137
2421
|
|
|
2138
2422
|
// Ignore clicks on thumb, including cases where React retargets the
|
|
2139
2423
|
// synthetic event to the track host across a shadow boundary.
|
|
2140
|
-
if (
|
|
2424
|
+
if (thumbEl && contains(thumbEl, target)) {
|
|
2141
2425
|
return;
|
|
2142
2426
|
}
|
|
2143
|
-
|
|
2427
|
+
const viewportEl = viewportRef.current;
|
|
2428
|
+
if (!viewportEl) {
|
|
2144
2429
|
return;
|
|
2145
2430
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
} else {
|
|
2181
|
-
newScrollLeft = scrollRatioX * (scrollableContentWidth - viewportWidth);
|
|
2182
|
-
}
|
|
2183
|
-
viewportRef.current.scrollLeft = newScrollLeft;
|
|
2431
|
+
const scrollbarEl = vertical ? scrollbarYRef.current : scrollbarXRef.current;
|
|
2432
|
+
if (!thumbEl || !scrollbarEl) {
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
const axis = vertical ? 'y' : 'x';
|
|
2436
|
+
const thumbOffset = getOffset(thumbEl, 'margin', axis);
|
|
2437
|
+
const scrollbarOffset = getOffset(scrollbarEl, 'padding', axis);
|
|
2438
|
+
const thumbSizePx = vertical ? thumbEl.offsetHeight : thumbEl.offsetWidth;
|
|
2439
|
+
const trackRect = scrollbarEl.getBoundingClientRect();
|
|
2440
|
+
const clickPosition = vertical ? event.clientY - trackRect.top - thumbSizePx / 2 - scrollbarOffset + thumbOffset / 2 : event.clientX - trackRect.left - thumbSizePx / 2 - scrollbarOffset + thumbOffset / 2;
|
|
2441
|
+
const scrollableSize = vertical ? viewportEl.scrollHeight : viewportEl.scrollWidth;
|
|
2442
|
+
const viewportSize = vertical ? viewportEl.clientHeight : viewportEl.clientWidth;
|
|
2443
|
+
const trackSize = vertical ? scrollbarEl.offsetHeight : scrollbarEl.offsetWidth;
|
|
2444
|
+
const maxThumbOffset = trackSize - thumbSizePx - scrollbarOffset - thumbOffset;
|
|
2445
|
+
// A short or heavily padded track can drive `maxThumbOffset` to zero or
|
|
2446
|
+
// negative once the thumb hits its `MIN_THUMB_SIZE` floor. Dividing by it
|
|
2447
|
+
// would yield a non-finite (`Infinity`/`NaN`) or inverted scroll position.
|
|
2448
|
+
if (maxThumbOffset <= 0) {
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
const scrollRatio = clickPosition / maxThumbOffset;
|
|
2452
|
+
const maxScrollDistance = scrollableSize - viewportSize;
|
|
2453
|
+
|
|
2454
|
+
// Disable snapping before the jump-to-click assignment, or the
|
|
2455
|
+
// assigned position quantizes to the nearest snap point and the thumb
|
|
2456
|
+
// stays offset from the pointer for the whole drag. `handlePointerDown`
|
|
2457
|
+
// below re-runs this as a guarded no-op for the thumb-drag path.
|
|
2458
|
+
disableViewportSnap();
|
|
2459
|
+
if (vertical) {
|
|
2460
|
+
viewportEl.scrollTop = scrollRatio * maxScrollDistance;
|
|
2461
|
+
} else if (direction === 'rtl') {
|
|
2462
|
+
viewportEl.scrollLeft = -(1 - scrollRatio) * maxScrollDistance;
|
|
2463
|
+
} else {
|
|
2464
|
+
viewportEl.scrollLeft = scrollRatio * maxScrollDistance;
|
|
2184
2465
|
}
|
|
2185
2466
|
handleScroll({
|
|
2186
|
-
x:
|
|
2187
|
-
y:
|
|
2467
|
+
x: viewportEl.scrollLeft,
|
|
2468
|
+
y: viewportEl.scrollTop
|
|
2188
2469
|
});
|
|
2189
2470
|
handlePointerDown(event);
|
|
2190
2471
|
},
|
|
@@ -2198,34 +2479,30 @@ const ScrollAreaScrollbar = /*#__PURE__*/React.forwardRef(function ScrollAreaScr
|
|
|
2198
2479
|
WebkitUserSelect: 'none',
|
|
2199
2480
|
userSelect: 'none',
|
|
2200
2481
|
visibility: hideTrackUntilMeasured ? 'hidden' : undefined,
|
|
2201
|
-
...(
|
|
2482
|
+
...(vertical ? {
|
|
2202
2483
|
top: 0,
|
|
2203
|
-
bottom:
|
|
2484
|
+
bottom: 'var(--scroll-area-corner-height)',
|
|
2204
2485
|
insetInlineEnd: 0,
|
|
2205
|
-
[
|
|
2206
|
-
}
|
|
2207
|
-
...(orientation === 'horizontal' && {
|
|
2486
|
+
['--scroll-area-thumb-height']: `${thumbSize.height}px`
|
|
2487
|
+
} : {
|
|
2208
2488
|
insetInlineStart: 0,
|
|
2209
|
-
insetInlineEnd:
|
|
2489
|
+
insetInlineEnd: 'var(--scroll-area-corner-width)',
|
|
2210
2490
|
bottom: 0,
|
|
2211
|
-
[
|
|
2491
|
+
['--scroll-area-thumb-width']: `${thumbSize.width}px`
|
|
2212
2492
|
})
|
|
2213
2493
|
}
|
|
2214
2494
|
};
|
|
2215
2495
|
const element = useRenderElement('div', componentProps, {
|
|
2216
|
-
ref: [forwardedRef,
|
|
2496
|
+
ref: [forwardedRef, vertical ? scrollbarYRef : scrollbarXRef],
|
|
2217
2497
|
state,
|
|
2218
2498
|
props: [props, elementProps],
|
|
2219
2499
|
stateAttributesMapping: scrollAreaStateAttributesMapping
|
|
2220
2500
|
});
|
|
2221
|
-
const contextValue = React.useMemo(() => ({
|
|
2222
|
-
orientation
|
|
2223
|
-
}), [orientation]);
|
|
2224
2501
|
if (!shouldRender) {
|
|
2225
2502
|
return null;
|
|
2226
2503
|
}
|
|
2227
2504
|
return /*#__PURE__*/jsx(ScrollAreaScrollbarContext.Provider, {
|
|
2228
|
-
value:
|
|
2505
|
+
value: orientation,
|
|
2229
2506
|
children: element
|
|
2230
2507
|
});
|
|
2231
2508
|
});
|
|
@@ -2313,43 +2590,30 @@ const ScrollAreaThumb = /*#__PURE__*/React.forwardRef(function ScrollAreaThumb(c
|
|
|
2313
2590
|
handlePointerDown,
|
|
2314
2591
|
handlePointerMove,
|
|
2315
2592
|
handlePointerUp,
|
|
2316
|
-
setScrollingX,
|
|
2317
|
-
setScrollingY,
|
|
2318
2593
|
scrollingX,
|
|
2319
2594
|
scrollingY,
|
|
2320
2595
|
hasMeasuredScrollbar
|
|
2321
2596
|
} = useScrollAreaRootContext();
|
|
2322
|
-
const
|
|
2323
|
-
|
|
2324
|
-
} = useScrollAreaScrollbarContext();
|
|
2597
|
+
const orientation = useScrollAreaScrollbarContext();
|
|
2598
|
+
const vertical = orientation === 'vertical';
|
|
2325
2599
|
const state = {
|
|
2326
|
-
scrolling:
|
|
2600
|
+
scrolling: vertical ? scrollingY : scrollingX,
|
|
2327
2601
|
orientation
|
|
2328
2602
|
};
|
|
2329
|
-
function endDrag(event) {
|
|
2330
|
-
if (orientation === 'vertical') {
|
|
2331
|
-
setScrollingY(false);
|
|
2332
|
-
}
|
|
2333
|
-
if (orientation === 'horizontal') {
|
|
2334
|
-
setScrollingX(false);
|
|
2335
|
-
}
|
|
2336
|
-
handlePointerUp(event);
|
|
2337
|
-
}
|
|
2338
2603
|
const element = useRenderElement('div', componentProps, {
|
|
2339
|
-
ref: [forwardedRef,
|
|
2604
|
+
ref: [forwardedRef, vertical ? thumbYRef : thumbXRef],
|
|
2340
2605
|
state,
|
|
2341
2606
|
props: [{
|
|
2342
2607
|
onPointerDown: handlePointerDown,
|
|
2343
2608
|
onPointerMove: handlePointerMove,
|
|
2344
|
-
onPointerUp:
|
|
2345
|
-
onPointerCancel:
|
|
2609
|
+
onPointerUp: handlePointerUp,
|
|
2610
|
+
onPointerCancel: handlePointerUp,
|
|
2346
2611
|
style: {
|
|
2347
2612
|
visibility: hasMeasuredScrollbar ? undefined : 'hidden',
|
|
2348
|
-
...(
|
|
2349
|
-
height:
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
width: `var(${ScrollAreaScrollbarCssVars.scrollAreaThumbWidth})`
|
|
2613
|
+
...(vertical ? {
|
|
2614
|
+
height: 'var(--scroll-area-thumb-height)'
|
|
2615
|
+
} : {
|
|
2616
|
+
width: 'var(--scroll-area-thumb-width)'
|
|
2353
2617
|
})
|
|
2354
2618
|
}
|
|
2355
2619
|
}, elementProps]
|
|
@@ -2429,7 +2693,7 @@ var index_parts$2 = /*#__PURE__*/Object.freeze({
|
|
|
2429
2693
|
Positioner: ComboboxPositioner,
|
|
2430
2694
|
Root: ComboboxRoot,
|
|
2431
2695
|
Row: ComboboxRow,
|
|
2432
|
-
Separator:
|
|
2696
|
+
Separator: ComboboxSeparator,
|
|
2433
2697
|
Status: ComboboxStatus,
|
|
2434
2698
|
Trigger: ComboboxTrigger,
|
|
2435
2699
|
Value: ComboboxValue,
|
|
@@ -2476,22 +2740,6 @@ function getViewportRect(teleportDistance, scrubAreaEl) {
|
|
|
2476
2740
|
};
|
|
2477
2741
|
}
|
|
2478
2742
|
|
|
2479
|
-
// This lets us invert the scale of the cursor to match the OS scale, in which the cursor doesn't
|
|
2480
|
-
// scale with the content on pinch-zoom.
|
|
2481
|
-
function subscribeToVisualViewportResize(element, visualScaleRef) {
|
|
2482
|
-
const vV = getWindow(element).visualViewport;
|
|
2483
|
-
if (!vV) {
|
|
2484
|
-
return () => {};
|
|
2485
|
-
}
|
|
2486
|
-
function handleVisualResize() {
|
|
2487
|
-
if (vV) {
|
|
2488
|
-
visualScaleRef.current = vV.scale;
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
handleVisualResize();
|
|
2492
|
-
return addEventListener(vV, 'resize', handleVisualResize);
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
2743
|
const SCRUB_AREA_STYLE = {
|
|
2496
2744
|
touchAction: 'none',
|
|
2497
2745
|
WebkitUserSelect: 'none',
|
|
@@ -2517,8 +2765,6 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
2517
2765
|
const {
|
|
2518
2766
|
state,
|
|
2519
2767
|
setIsScrubbing: setRootScrubbing,
|
|
2520
|
-
disabled,
|
|
2521
|
-
readOnly,
|
|
2522
2768
|
inputRef,
|
|
2523
2769
|
incrementValue,
|
|
2524
2770
|
allowInputSyncRef,
|
|
@@ -2527,6 +2773,10 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
2527
2773
|
lastChangedValueRef,
|
|
2528
2774
|
valueRef
|
|
2529
2775
|
} = useNumberFieldRootContext();
|
|
2776
|
+
const {
|
|
2777
|
+
disabled,
|
|
2778
|
+
readOnly
|
|
2779
|
+
} = state;
|
|
2530
2780
|
const scrubAreaRef = React.useRef(null);
|
|
2531
2781
|
const isScrubbingRef = React.useRef(false);
|
|
2532
2782
|
const didMoveRef = React.useRef(false);
|
|
@@ -2536,21 +2786,15 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
2536
2786
|
x: 0,
|
|
2537
2787
|
y: 0
|
|
2538
2788
|
});
|
|
2539
|
-
const visualScaleRef = React.useRef(1);
|
|
2540
2789
|
const exitPointerLockTimeout = useTimeout();
|
|
2541
2790
|
const [isTouchInput, setIsTouchInput] = React.useState(false);
|
|
2542
2791
|
const [isPointerLockDenied, setIsPointerLockDenied] = React.useState(false);
|
|
2543
2792
|
const [isScrubbing, setIsScrubbing] = React.useState(false);
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
}, [isScrubbing]);
|
|
2550
|
-
function updateCursorTransform(x, y) {
|
|
2551
|
-
if (scrubAreaCursorRef.current) {
|
|
2552
|
-
scrubAreaCursorRef.current.style.transform = `translate3d(${x}px,${y}px,0) scale(${1 / visualScaleRef.current})`;
|
|
2553
|
-
}
|
|
2793
|
+
function updateCursorTransform(virtualCursor, x, y) {
|
|
2794
|
+
// Invert the visual viewport scale so the cursor matches the OS cursor, which doesn't
|
|
2795
|
+
// scale with the content on pinch-zoom.
|
|
2796
|
+
const scale = getWindow(virtualCursor).visualViewport?.scale ?? 1;
|
|
2797
|
+
virtualCursor.style.transform = `translate3d(${x}px,${y}px,0) scale(${1 / scale})`;
|
|
2554
2798
|
}
|
|
2555
2799
|
const onScrub = useStableCallback(({
|
|
2556
2800
|
movementX,
|
|
@@ -2563,24 +2807,23 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
2563
2807
|
}
|
|
2564
2808
|
const rect = getViewportRect(teleportDistance, scrubAreaEl);
|
|
2565
2809
|
const coords = virtualCursorCoords.current;
|
|
2810
|
+
|
|
2811
|
+
// Wrap the cursor to the opposite edge when its center crosses a viewport bound.
|
|
2812
|
+
const wrap = (coord, halfSize, low, high) => {
|
|
2813
|
+
if (coord + halfSize < low) {
|
|
2814
|
+
return high - halfSize;
|
|
2815
|
+
}
|
|
2816
|
+
if (coord + halfSize > high) {
|
|
2817
|
+
return low - halfSize;
|
|
2818
|
+
}
|
|
2819
|
+
return coord;
|
|
2820
|
+
};
|
|
2566
2821
|
const newCoords = {
|
|
2567
|
-
x: Math.round(coords.x + movementX),
|
|
2568
|
-
y: Math.round(coords.y + movementY)
|
|
2822
|
+
x: wrap(Math.round(coords.x + movementX), virtualCursor.offsetWidth / 2, rect.left, rect.right),
|
|
2823
|
+
y: wrap(Math.round(coords.y + movementY), virtualCursor.offsetHeight / 2, rect.top, rect.bottom)
|
|
2569
2824
|
};
|
|
2570
|
-
const cursorWidth = virtualCursor.offsetWidth;
|
|
2571
|
-
const cursorHeight = virtualCursor.offsetHeight;
|
|
2572
|
-
if (newCoords.x + cursorWidth / 2 < rect.left) {
|
|
2573
|
-
newCoords.x = rect.right - cursorWidth / 2;
|
|
2574
|
-
} else if (newCoords.x + cursorWidth / 2 > rect.right) {
|
|
2575
|
-
newCoords.x = rect.left - cursorWidth / 2;
|
|
2576
|
-
}
|
|
2577
|
-
if (newCoords.y + cursorHeight / 2 < rect.top) {
|
|
2578
|
-
newCoords.y = rect.bottom - cursorHeight / 2;
|
|
2579
|
-
} else if (newCoords.y + cursorHeight / 2 > rect.bottom) {
|
|
2580
|
-
newCoords.y = rect.top - cursorHeight / 2;
|
|
2581
|
-
}
|
|
2582
2825
|
virtualCursorCoords.current = newCoords;
|
|
2583
|
-
updateCursorTransform(newCoords.x, newCoords.y);
|
|
2826
|
+
updateCursorTransform(virtualCursor, newCoords.x, newCoords.y);
|
|
2584
2827
|
});
|
|
2585
2828
|
const onScrubbingChange = useStableCallback((scrubbingValue, {
|
|
2586
2829
|
clientX,
|
|
@@ -2599,7 +2842,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
2599
2842
|
y: clientY - virtualCursor.offsetHeight / 2
|
|
2600
2843
|
};
|
|
2601
2844
|
virtualCursorCoords.current = initialCoords;
|
|
2602
|
-
updateCursorTransform(initialCoords.x, initialCoords.y);
|
|
2845
|
+
updateCursorTransform(virtualCursor, initialCoords.x, initialCoords.y);
|
|
2603
2846
|
});
|
|
2604
2847
|
React.useEffect(function registerGlobalScrubbingEventListeners() {
|
|
2605
2848
|
// Only listen while actively scrubbing; avoids unrelated pointerup events committing.
|
|
@@ -2641,6 +2884,9 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
2641
2884
|
}
|
|
2642
2885
|
}
|
|
2643
2886
|
function handleScrubPointerMove(event) {
|
|
2887
|
+
// The effects below can tear down and re-run without unmounting (`<Activity>`), which
|
|
2888
|
+
// clears the ref while `isScrubbing` stays `true` and re-attaches this listener. The ref
|
|
2889
|
+
// is the source of truth for whether a pointer is actually down.
|
|
2644
2890
|
if (!isScrubbingRef.current) {
|
|
2645
2891
|
return;
|
|
2646
2892
|
}
|
|
@@ -2708,8 +2954,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
2708
2954
|
role: 'presentation',
|
|
2709
2955
|
style: SCRUB_AREA_STYLE,
|
|
2710
2956
|
async onPointerDown(event) {
|
|
2711
|
-
|
|
2712
|
-
if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
|
|
2957
|
+
if (event.defaultPrevented || readOnly || event.button || disabled) {
|
|
2713
2958
|
return;
|
|
2714
2959
|
}
|
|
2715
2960
|
const isTouch = event.pointerType === 'touch';
|
|
@@ -2748,7 +2993,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
2748
2993
|
ref: [forwardedRef, scrubAreaRef],
|
|
2749
2994
|
state,
|
|
2750
2995
|
props: [defaultProps, elementProps],
|
|
2751
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
2996
|
+
stateAttributesMapping: stateAttributesMapping$4
|
|
2752
2997
|
});
|
|
2753
2998
|
const contextValue = React.useMemo(() => ({
|
|
2754
2999
|
isScrubbing,
|
|
@@ -2805,7 +3050,7 @@ const NumberFieldScrubAreaCursor = /*#__PURE__*/React.forwardRef(function Number
|
|
|
2805
3050
|
role: 'presentation',
|
|
2806
3051
|
style: CURSOR_STYLE
|
|
2807
3052
|
}, elementProps],
|
|
2808
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
3053
|
+
stateAttributesMapping: stateAttributesMapping$4
|
|
2809
3054
|
});
|
|
2810
3055
|
return element && /*#__PURE__*/ReactDOM.createPortal(element, ownerDocument(domElement).body);
|
|
2811
3056
|
});
|
|
@@ -2849,7 +3094,8 @@ const SelectLabel = /*#__PURE__*/React.forwardRef(function SelectLabel(component
|
|
|
2849
3094
|
id: defaultLabelId,
|
|
2850
3095
|
fallbackControlId: triggerElement?.id ?? rootId,
|
|
2851
3096
|
setLabelId(nextLabelId) {
|
|
2852
|
-
store.
|
|
3097
|
+
const resolvedLabelId = typeof nextLabelId === 'function' ? nextLabelId(store.state.labelId) : nextLabelId;
|
|
3098
|
+
store.set('labelId', resolvedLabelId);
|
|
2853
3099
|
}
|
|
2854
3100
|
});
|
|
2855
3101
|
return useRenderElement('div', componentProps, {
|
|
@@ -2894,7 +3140,7 @@ const SelectIcon = /*#__PURE__*/React.forwardRef(function SelectIcon(componentPr
|
|
|
2894
3140
|
});
|
|
2895
3141
|
if (process.env.NODE_ENV !== "production") SelectIcon.displayName = "SelectIcon";
|
|
2896
3142
|
|
|
2897
|
-
const stateAttributesMapping
|
|
3143
|
+
const stateAttributesMapping = {
|
|
2898
3144
|
...popupStateMapping,
|
|
2899
3145
|
...transitionStatusMapping
|
|
2900
3146
|
};
|
|
@@ -2933,17 +3179,12 @@ const SelectBackdrop = /*#__PURE__*/React.forwardRef(function SelectBackdrop(com
|
|
|
2933
3179
|
WebkitUserSelect: 'none'
|
|
2934
3180
|
}
|
|
2935
3181
|
}, elementProps],
|
|
2936
|
-
stateAttributesMapping
|
|
3182
|
+
stateAttributesMapping
|
|
2937
3183
|
});
|
|
2938
3184
|
return element;
|
|
2939
3185
|
});
|
|
2940
3186
|
if (process.env.NODE_ENV !== "production") SelectBackdrop.displayName = "SelectBackdrop";
|
|
2941
3187
|
|
|
2942
|
-
const stateAttributesMapping = {
|
|
2943
|
-
...popupStateMapping,
|
|
2944
|
-
...transitionStatusMapping
|
|
2945
|
-
};
|
|
2946
|
-
|
|
2947
3188
|
/**
|
|
2948
3189
|
* Displays an element positioned against the select popup anchor.
|
|
2949
3190
|
* Renders a `<div>` element.
|
|
@@ -2982,7 +3223,7 @@ const SelectArrow = /*#__PURE__*/React.forwardRef(function SelectArrow(component
|
|
|
2982
3223
|
style: arrowStyles,
|
|
2983
3224
|
'aria-hidden': true
|
|
2984
3225
|
}, elementProps],
|
|
2985
|
-
stateAttributesMapping
|
|
3226
|
+
stateAttributesMapping: popupTransitionStateMapping
|
|
2986
3227
|
});
|
|
2987
3228
|
if (alignItemWithTriggerActive) {
|
|
2988
3229
|
return null;
|
|
@@ -3000,7 +3241,7 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
|
|
|
3000
3241
|
className,
|
|
3001
3242
|
style,
|
|
3002
3243
|
direction,
|
|
3003
|
-
keepMounted
|
|
3244
|
+
keepMounted,
|
|
3004
3245
|
...elementProps
|
|
3005
3246
|
} = componentProps;
|
|
3006
3247
|
const isUp = direction === 'up';
|
|
@@ -3031,12 +3272,10 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
|
|
|
3031
3272
|
} = useTransitionStatus(visible);
|
|
3032
3273
|
useIsoLayoutEffect(() => {
|
|
3033
3274
|
scrollArrowsMountedCountRef.current += 1;
|
|
3034
|
-
|
|
3035
|
-
store.set('hasScrollArrows', true);
|
|
3036
|
-
}
|
|
3275
|
+
store.set('hasScrollArrows', true);
|
|
3037
3276
|
return () => {
|
|
3038
3277
|
scrollArrowsMountedCountRef.current = Math.max(0, scrollArrowsMountedCountRef.current - 1);
|
|
3039
|
-
if (scrollArrowsMountedCountRef.current === 0
|
|
3278
|
+
if (scrollArrowsMountedCountRef.current === 0) {
|
|
3040
3279
|
store.set('hasScrollArrows', false);
|
|
3041
3280
|
}
|
|
3042
3281
|
};
|
|
@@ -3073,7 +3312,7 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
|
|
|
3073
3312
|
return;
|
|
3074
3313
|
}
|
|
3075
3314
|
store.set('activeIndex', null);
|
|
3076
|
-
handleScrollArrowVisibility();
|
|
3315
|
+
handleScrollArrowVisibility(scroller);
|
|
3077
3316
|
const maxScrollTop = getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
|
|
3078
3317
|
const scrollTop = normalizeScrollOffset(scroller.scrollTop, maxScrollTop);
|
|
3079
3318
|
const isScrolledToEdge = scrollTop === (isUp ? 0 : maxScrollTop);
|
|
@@ -3081,11 +3320,6 @@ const SelectScrollArrow = /*#__PURE__*/React.forwardRef(function SelectScrollArr
|
|
|
3081
3320
|
if (scrollTop !== scroller.scrollTop) {
|
|
3082
3321
|
scroller.scrollTop = scrollTop;
|
|
3083
3322
|
}
|
|
3084
|
-
|
|
3085
|
-
// Fallback when there are no items registered yet.
|
|
3086
|
-
if (items.length === 0) {
|
|
3087
|
-
store.set(isUp ? 'scrollUpArrowVisible' : 'scrollDownArrowVisible', !isScrolledToEdge);
|
|
3088
|
-
}
|
|
3089
3323
|
if (isScrolledToEdge) {
|
|
3090
3324
|
timeout.clear();
|
|
3091
3325
|
return;
|
|
@@ -3174,6 +3408,14 @@ const SelectScrollUpArrow = /*#__PURE__*/React.forwardRef(function SelectScrollU
|
|
|
3174
3408
|
});
|
|
3175
3409
|
if (process.env.NODE_ENV !== "production") SelectScrollUpArrow.displayName = "SelectScrollUpArrow";
|
|
3176
3410
|
|
|
3411
|
+
/**
|
|
3412
|
+
* A visual separator between items or groups.
|
|
3413
|
+
* Renders a `<div>` element.
|
|
3414
|
+
*
|
|
3415
|
+
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
3416
|
+
*/
|
|
3417
|
+
const SelectSeparator = ListboxSeparator;
|
|
3418
|
+
|
|
3177
3419
|
var index_parts = /*#__PURE__*/Object.freeze({
|
|
3178
3420
|
__proto__: null,
|
|
3179
3421
|
Arrow: SelectArrow,
|
|
@@ -3192,7 +3434,7 @@ var index_parts = /*#__PURE__*/Object.freeze({
|
|
|
3192
3434
|
Root: SelectRoot,
|
|
3193
3435
|
ScrollDownArrow: SelectScrollDownArrow,
|
|
3194
3436
|
ScrollUpArrow: SelectScrollUpArrow,
|
|
3195
|
-
Separator:
|
|
3437
|
+
Separator: SelectSeparator,
|
|
3196
3438
|
Trigger: SelectTrigger,
|
|
3197
3439
|
Value: SelectValue
|
|
3198
3440
|
});
|