diff --git a/packages/@adobe/spectrum-css-temp/components/tray/index.css b/packages/@adobe/spectrum-css-temp/components/tray/index.css index cd58246a1eb..b82f9ae6250 100644 --- a/packages/@adobe/spectrum-css-temp/components/tray/index.css +++ b/packages/@adobe/spectrum-css-temp/components/tray/index.css @@ -20,14 +20,13 @@ .spectrum-Tray-wrapper { inset-inline-start: 0; - /* Positioned at the top of the window */ - position: fixed; + position: absolute; top: 0; display: flex; justify-content: center; width: 100%; - height: 100vh; + height: 100dvh; /* Don't catch clicks */ pointer-events: none; @@ -55,10 +54,12 @@ max-height: calc(var(--spectrum-visual-viewport-height) - var(--spectrum-tray-margin-top)); /* Add padding at the bottom to account for the rest of the viewport height behind the keyboard. * This is necessary so that there isn't a visible gap that appears while the keyboard is animating - * in and out. Fall back to the safe area inset to account for things like iOS home indicator. */ - padding-bottom: max(calc(100vh - var(--spectrum-visual-viewport-height)), env(safe-area-inset-bottom)); + * in and out. Fall back to the safe area inset to account for things like iOS home indicator. + We also add an additional 100vh of padding (offset by the bottom position below) so the tray + extends behind Safari's address bar and keyboard in iOS 26. */ + padding-bottom: calc(max(calc(100dvh - var(--spectrum-visual-viewport-height)), env(safe-area-inset-bottom)) + 100vh); position: absolute; - bottom: 0; + bottom: -100vh; outline: none; display: flex; flex-direction: column; diff --git a/packages/@adobe/spectrum-css-temp/components/underlay/index.css b/packages/@adobe/spectrum-css-temp/components/underlay/index.css index 95de662061b..82da0097342 100644 --- a/packages/@adobe/spectrum-css-temp/components/underlay/index.css +++ b/packages/@adobe/spectrum-css-temp/components/underlay/index.css @@ -23,7 +23,8 @@ governing permissions and limitations under the License. .spectrum-Underlay { composes: spectrum-overlay; - position: fixed; + /* Use position: absolute instead of fixed to avoid being clipped to the "inner" viewport in iOS 26 */ + position: absolute; top: 0; right: 0; bottom: 0; diff --git a/packages/@react-aria/autocomplete/src/useAutocomplete.ts b/packages/@react-aria/autocomplete/src/useAutocomplete.ts index 2cd204e2e15..7f895a676b1 100644 --- a/packages/@react-aria/autocomplete/src/useAutocomplete.ts +++ b/packages/@react-aria/autocomplete/src/useAutocomplete.ts @@ -13,7 +13,7 @@ import {AriaLabelingProps, BaseEvent, DOMProps, FocusableElement, FocusEvents, KeyboardEvents, Node, RefObject, ValueBase} from '@react-types/shared'; import {AriaTextFieldProps} from '@react-aria/textfield'; import {AutocompleteProps, AutocompleteState} from '@react-stately/autocomplete'; -import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getOwnerDocument, getOwnerWindow, isAndroid, isCtrlKeyPressed, isIOS, mergeProps, mergeRefs, useEffectEvent, useEvent, useLabels, useObjectRef, useSlotId} from '@react-aria/utils'; +import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getOwnerDocument, isAndroid, isCtrlKeyPressed, isIOS, mergeProps, mergeRefs, useEffectEvent, useEvent, useLabels, useObjectRef, useSlotId} from '@react-aria/utils'; import {dispatchVirtualBlur, dispatchVirtualFocus, getVirtuallyFocusedElement, moveVirtualFocus} from '@react-aria/focus'; import {getInteractionModality} from '@react-aria/interactions'; // @ts-ignore @@ -106,9 +106,6 @@ export function useAutocomplete(props: AriaAutocompleteOptions, state: Aut // Ensure input is focused if the user clicks on the collection directly. if (!e.isTrusted && shouldUseVirtualFocus && inputRef.current && getActiveElement(getOwnerDocument(inputRef.current)) !== inputRef.current) { inputRef.current.focus(); - if (inputRef.current instanceof getOwnerWindow(inputRef.current).HTMLInputElement) { - inputRef.current.select(); - } } let target = e.target as Element | null; diff --git a/packages/@react-aria/collections/src/Document.ts b/packages/@react-aria/collections/src/Document.ts index 7c50285dcf2..453ce572404 100644 --- a/packages/@react-aria/collections/src/Document.ts +++ b/packages/@react-aria/collections/src/Document.ts @@ -461,16 +461,14 @@ export class Document = BaseCollection> extend } private removeNode(node: ElementNode): void { - if (node.node == null) { - return; - } - for (let child of node) { this.removeNode(child); } - let collection = this.getMutableCollection(); - collection.removeNode(node.node.key); + if (node.node) { + let collection = this.getMutableCollection(); + collection.removeNode(node.node.key); + } } /** Finalizes the collection update, updating all nodes and freezing the collection. */ @@ -508,12 +506,16 @@ export class Document = BaseCollection> extend this.addNode(element); } + if (element.node) { + this.dirtyNodes.delete(element); + } + element.isMutated = false; + } else { + this.dirtyNodes.delete(element); } } - this.dirtyNodes.clear(); - // Finally, update the collection. if (this.nextCollection) { this.nextCollection.commit(this.firstVisibleChild?.node?.key ?? null, this.lastVisibleChild?.node?.key ?? null, this.isSSR); diff --git a/packages/@react-aria/dialog/docs/useDialog.mdx b/packages/@react-aria/dialog/docs/useDialog.mdx index b206ed281ff..58a006f6f8c 100644 --- a/packages/@react-aria/dialog/docs/useDialog.mdx +++ b/packages/@react-aria/dialog/docs/useDialog.mdx @@ -131,6 +131,7 @@ The `Modal` and `ModalTrigger` components render the dialog within a typical mod ```tsx example export=true render=false import {useOverlayTriggerState} from '@react-stately/overlays'; import {Overlay, useModalOverlay, useOverlayTrigger} from '@react-aria/overlays'; +import {useViewportSize} from '@react-aria/utils'; function Modal({state, children, ...props}) { let ref = React.useRef(null); @@ -140,18 +141,27 @@ function Modal({state, children, ...props}) {
+
+ }}>
, contain?: bo // restore focus to the previously focused node or the first tabbable element in the active scope. if (focusedNode.current) { focusedNode.current.focus(); - - if (focusedNode.current instanceof getOwnerWindow(focusedNode.current).HTMLInputElement) { - focusedNode.current.select(); - } } else if (activeScope && activeScope.current) { focusFirstInScope(activeScope.current); } @@ -404,9 +399,6 @@ function useFocusContainment(scopeRef: RefObject, contain?: bo if (target && target.isConnected) { focusedNode.current = target; focusedNode.current?.focus(); - if (focusedNode.current instanceof getOwnerWindow(focusedNode.current).HTMLInputElement) { - focusedNode.current.select(); - } } else if (activeScope.current) { focusFirstInScope(activeScope.current); } @@ -494,9 +486,6 @@ function focusElement(element: FocusableElement | null, scroll = false) { } else if (element != null) { try { element.focus(); - if (element instanceof getOwnerWindow(element).HTMLInputElement) { - element.select(); - } } catch { // ignore } diff --git a/packages/@react-aria/interactions/src/focusSafely.ts b/packages/@react-aria/interactions/src/focusSafely.ts index 46d95d917cf..384f3f363f7 100644 --- a/packages/@react-aria/interactions/src/focusSafely.ts +++ b/packages/@react-aria/interactions/src/focusSafely.ts @@ -15,7 +15,6 @@ import { focusWithoutScrolling, getActiveElement, getOwnerDocument, - getOwnerWindow, runAfterTransition } from '@react-aria/utils'; import {getInteractionModality} from './useFocusVisible'; @@ -38,15 +37,9 @@ export function focusSafely(element: FocusableElement): void { // If focus did not move and the element is still in the document, focus it. if (getActiveElement(ownerDocument) === lastFocusedElement && element.isConnected) { focusWithoutScrolling(element); - if (element instanceof getOwnerWindow(element).HTMLInputElement) { - element.select(); - } } }); } else { focusWithoutScrolling(element); - if (element instanceof getOwnerWindow(element).HTMLInputElement) { - element.select(); - } } } diff --git a/packages/@react-aria/overlays/docs/useModalOverlay.mdx b/packages/@react-aria/overlays/docs/useModalOverlay.mdx index 8b43b9be036..a60b08a8457 100644 --- a/packages/@react-aria/overlays/docs/useModalOverlay.mdx +++ b/packages/@react-aria/overlays/docs/useModalOverlay.mdx @@ -75,6 +75,7 @@ The `Modal` component uses an <
+
+ }}>
{ // Store the nearest scrollable parent element from the element that the user touched. scrollable = getScrollParent(e.target as Element, true); if (scrollable === document.documentElement && scrollable === document.body) { return; } - - // Prevent scrolling up when at the top and scrolling down when at the bottom - // of a nested scrollable area, otherwise mobile Safari will start scrolling - // the window instead. - if (scrollable instanceof HTMLElement && window.getComputedStyle(scrollable).overscrollBehavior === 'auto') { - restoreScrollableStyles = setStyle(scrollable, 'overscrollBehavior', 'contain'); - } }; + // Prevent scrolling up when at the top and scrolling down when at the bottom + // of a nested scrollable area, otherwise mobile Safari will start scrolling + // the window instead. + // This must be applied before the touchstart event as of iOS 26, so inject it as a