Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"check-types": "tsc",
"install-16": "node scripts/react-16-install-prep.mjs && yarn add react@^16.8.0 react-dom@^16.8.0 @testing-library/react@^12 @testing-library/react-hooks@^8 @testing-library/dom@^8 react-test-renderer@^16.9.0 && node scripts/oldReactSupport.mjs",
"install-17": "node scripts/react-17-install-prep.mjs && yarn add react@^17 react-dom@^17 @testing-library/react@^12 @testing-library/react-hooks@^8 @testing-library/dom@^8 react-test-renderer@^16.9.0 && node scripts/oldReactSupport.mjs",
"install-18": "node scripts/react-18-install-prep.mjs && yarn add react@^18 react-dom@^18",
"install-18": "node scripts/react-18-install-prep.mjs && yarn add react@^18 react-dom@^18 react-test-renderer@^18.3.1 && node scripts/oldReactSupport.mjs",
"install-canary": "node scripts/react-canary-install-prep.mjs && yarn add react@canary react-dom@canary",
"start": "cross-env NODE_ENV=storybook storybook dev -p 9003 --ci -c '.storybook'",
"build:storybook": "storybook build -c .storybook -o dist/$(git rev-parse HEAD)/storybook",
Expand Down Expand Up @@ -178,7 +178,7 @@
"jest-matchmedia-mock": "^1.1.0",
"lerna": "^3.13.2",
"lfcdn": "^0.4.2",
"lucide-react": "^0.294.0",
"lucide-react": "^0.517.0",
"md5": "^2.2.1",
"motion": "^12.23.6",
"npm-cli-login": "^1.0.0",
Expand All @@ -199,7 +199,7 @@
"react-axe": "^3.0.2",
"react-dom": "^19.1.0",
"react-frame-component": "^5.0.0",
"react-test-renderer": "^18.3.1",
"react-test-renderer": "^19.1.0",
"recast": "^0.23",
"recursive-readdir": "^2.2.2",
"regenerator-runtime": "0.13.3",
Expand Down Expand Up @@ -251,7 +251,8 @@
"remark-mdx": "patch:remark-mdx@npm%3A2.0.0-rc.2#~/.yarn/patches/remark-mdx-npm-2.0.0-rc.2-7a71234e1f.patch",
"remark-parse": "patch:remark-parse@npm%3A10.0.1#~/.yarn/patches/remark-parse-npm-10.0.1-e654d7df78.patch",
"lightningcss": "1.30.1",
"react-server-dom-parcel": "canary"
"react-server-dom-parcel": "canary",
"react-test-renderer": "19.1.0"
},
"@parcel/transformer-css": {
"cssModules": {
Expand Down
32 changes: 27 additions & 5 deletions packages/@react-aria/overlays/src/usePreventScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {chain, getScrollParent, isIOS, useLayoutEffect, willOpenKeyboard} from '@react-aria/utils';
import {chain, getScrollParent, isIOS, isScrollable, useLayoutEffect, willOpenKeyboard} from '@react-aria/utils';

interface PreventScrollOptions {
/** Whether the scroll lock is disabled. */
Expand Down Expand Up @@ -85,18 +85,35 @@ function preventScrollStandard() {
// on the window.
// 2. Set `overscroll-behavior: contain` on nested scrollable regions so they do not scroll the page when at
// the top or bottom. Work around a bug where this does not work when the element does not actually overflow
// by preventing default in a `touchmove` event.
// by preventing default in a `touchmove` event. This is best effort: we can't prevent default when pinch
// zooming or when an element contains text selection, which may allow scrolling in some cases.
// 3. Prevent default on `touchend` events on input elements and handle focusing the element ourselves.
// 4. When focus moves to an input, create an off screen input and focus that temporarily. This prevents
// Safari from scrolling the page. After a small delay, focus the real input and scroll it into view
// ourselves, without scrolling the whole page.
function preventScrollMobileSafari() {
let scrollable: Element;
let allowTouchMove = false;
let onTouchStart = (e: TouchEvent) => {
// 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;
let target = e.target as Element;
scrollable = isScrollable(target) ? target : getScrollParent(target, true);
allowTouchMove = false;

// If the target is selected, don't preventDefault in touchmove to allow user to adjust selection.
let selection = target.ownerDocument.defaultView!.getSelection();
if (selection && !selection.isCollapsed && selection.containsNode(target, true)) {
allowTouchMove = true;
}

// If this is a focused input element with a selected range, allow user to drag the selection handles.
if (
'selectionStart' in target &&
'selectionEnd' in target &&
(target.selectionStart as number) < (target.selectionEnd as number) &&
target.ownerDocument.activeElement === target
) {
allowTouchMove = true;
}
};

Expand All @@ -114,6 +131,11 @@ function preventScrollMobileSafari() {
document.head.prepend(style);

let onTouchMove = (e: TouchEvent) => {
// Allow pinch-zooming.
if (e.touches.length === 2 || allowTouchMove) {
return;
}

// Prevent scrolling the window.
if (!scrollable || scrollable === document.documentElement || scrollable === document.body) {
e.preventDefault();
Expand Down
14 changes: 12 additions & 2 deletions packages/@react-aria/utils/src/useViewportSize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export function useViewportSize(): ViewportSize {
useEffect(() => {
// Use visualViewport api to track available height even on iOS virtual keyboard opening
let onResize = () => {
// Ignore updates when zoomed.
if (visualViewport && visualViewport.scale > 1) {
return;
}

setSize(size => {
let newSize = getViewportSize();
if (newSize.width === size.width && newSize.height === size.height) {
Expand All @@ -41,6 +46,10 @@ export function useViewportSize(): ViewportSize {
// We can anticipate this and resize early by handling the blur event and using the layout size.
let frame: number;
let onBlur = (e: FocusEvent) => {
if (visualViewport && visualViewport.scale > 1) {
return;
}

if (willOpenKeyboard(e.target as Element)) {
// Wait one frame to see if a new element gets focused.
frame = requestAnimationFrame(() => {
Expand Down Expand Up @@ -81,7 +90,8 @@ export function useViewportSize(): ViewportSize {

function getViewportSize(): ViewportSize {
return {
width: (visualViewport && visualViewport?.width) || window.innerWidth,
height: (visualViewport && visualViewport?.height) || window.innerHeight
// Multiply by the visualViewport scale to get the "natural" size, unaffected by pinch zooming.
width: visualViewport ? visualViewport.width * visualViewport.scale : window.innerWidth,
height: visualViewport ? visualViewport.height * visualViewport.scale : window.innerHeight
};
}
20 changes: 11 additions & 9 deletions packages/@react-stately/select/src/useSelectState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ export function useSelectState<T extends object, M extends SelectionMode = 'sing
let value = useMemo(() => {
return props.value ?? (selectionMode === 'single' ? props.selectedKey : undefined) as ValueType<M>;
}, [props.value, props.selectedKey, selectionMode]);
let [controlledValue, setControlledValue] = useControlledState<ValueType<M>>(value as any, defaultValue as any, props.onChange);
let [controlledValue, setControlledValue] = useControlledState<Key | Key[] | null>(value, defaultValue, props.onChange as any);
// Only display the first selected item if in single selection mode but the value is an array.
let displayValue = selectionMode === 'single' && Array.isArray(controlledValue) ? controlledValue[0] : controlledValue;
let setValue = (value: Key | Key[] | null) => {
if (selectionMode === 'single') {
let key = Array.isArray(value) ? value[0] ?? null : value;
setControlledValue(key as ValueType<M>);
if (key !== controlledValue) {
setControlledValue(key);
if (key !== displayValue) {
props.onSelectionChange?.(key);
}
} else {
Expand All @@ -104,7 +106,7 @@ export function useSelectState<T extends object, M extends SelectionMode = 'sing
keys = [value];
}

setControlledValue(keys as ValueType<M>);
setControlledValue(keys);
}
};

Expand All @@ -113,7 +115,7 @@ export function useSelectState<T extends object, M extends SelectionMode = 'sing
selectionMode,
disallowEmptySelection: selectionMode === 'single',
allowDuplicateSelectionEvents: true,
selectedKeys: useMemo(() => convertValue(controlledValue), [controlledValue]),
selectedKeys: useMemo(() => convertValue(displayValue), [displayValue]),
onSelectionChange: (keys: Selection) => {
// impossible, but TS doesn't know that
if (keys === 'all') {
Expand All @@ -139,18 +141,18 @@ export function useSelectState<T extends object, M extends SelectionMode = 'sing

let validationState = useFormValidationState({
...props,
value: Array.isArray(controlledValue) && controlledValue.length === 0 ? null : controlledValue as any
value: Array.isArray(displayValue) && displayValue.length === 0 ? null : displayValue as any
});

let [isFocused, setFocused] = useState(false);
let [initialValue] = useState(controlledValue);
let [initialValue] = useState(displayValue);

return {
...validationState,
...listState,
...triggerState,
value: controlledValue,
defaultValue: defaultValue ?? initialValue,
value: displayValue as ValueType<M>,
defaultValue: defaultValue ?? initialValue as ValueType<M>,
setValue,
selectedKey,
setSelectedKey: setValue,
Expand Down
8 changes: 4 additions & 4 deletions packages/dev/storybook-react-parcel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
"storybook-builder-parcel": ">=0.0.1"
},
"devDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
"storybook": "^8.6.14"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
"storybook": "^8.6.14"
},
"scripts": {
Expand Down
14 changes: 0 additions & 14 deletions patches/lucide-react+0.294.0.patch

This file was deleted.

2 changes: 2 additions & 0 deletions scripts/oldReactSupport.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ try {
let content = fs.readFileSync('./package.json', 'utf8');
let pkg = JSON.parse(content);
delete pkg.alias;
delete pkg.resolutions.react;
delete pkg.resolutions['react-dom'];
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2));
} catch (e) {
console.error('Error:', e);
Expand Down
4 changes: 3 additions & 1 deletion starters/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"build-storybook": "storybook build"
},
"devDependencies": {
"@babel/preset-env": "^7.28.3",
"@babel/preset-react": "^7.24.1",
"@babel/preset-typescript": "^7.27.1",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-interactions": "^8.6.14",
"@storybook/addon-links": "^8.6.14",
Expand All @@ -17,7 +19,7 @@
"@types/react-dom": "^18.3.0",
"clsx": "^2.1.1",
"lightningcss-loader": "^2.1.0",
"lucide-react": "^0.292.0",
"lucide-react": "^0.517.0",
"prop-types": "^15.8.1",
"react": "^19.1.0",
"react-aria-components": "latest",
Expand Down
2 changes: 1 addition & 1 deletion starters/tailwind/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"build-storybook": "storybook build"
},
"dependencies": {
"lucide-react": "^0.292.0",
"lucide-react": "^0.517.0",
"postcss": "^8.4.31",
"react-aria-components": "latest",
"tailwind-variants": "^0.3.1"
Expand Down
Loading
Loading