diff --git a/package.json b/package.json index 30fc1b77691..e6bb5e1505b 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", @@ -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", @@ -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": { diff --git a/packages/@react-aria/overlays/src/usePreventScroll.ts b/packages/@react-aria/overlays/src/usePreventScroll.ts index 88bbb44bea3..6c4b7ee5772 100644 --- a/packages/@react-aria/overlays/src/usePreventScroll.ts +++ b/packages/@react-aria/overlays/src/usePreventScroll.ts @@ -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. */ @@ -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; } }; @@ -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(); diff --git a/packages/@react-aria/utils/src/useViewportSize.ts b/packages/@react-aria/utils/src/useViewportSize.ts index 2a369a82356..30fc9d26385 100644 --- a/packages/@react-aria/utils/src/useViewportSize.ts +++ b/packages/@react-aria/utils/src/useViewportSize.ts @@ -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) { @@ -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(() => { @@ -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 }; } diff --git a/packages/@react-stately/select/src/useSelectState.ts b/packages/@react-stately/select/src/useSelectState.ts index e172b750c44..c15f1b8978f 100644 --- a/packages/@react-stately/select/src/useSelectState.ts +++ b/packages/@react-stately/select/src/useSelectState.ts @@ -88,12 +88,14 @@ export function useSelectState; }, [props.value, props.selectedKey, selectionMode]); - let [controlledValue, setControlledValue] = useControlledState>(value as any, defaultValue as any, props.onChange); + let [controlledValue, setControlledValue] = useControlledState(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); - if (key !== controlledValue) { + setControlledValue(key); + if (key !== displayValue) { props.onSelectionChange?.(key); } } else { @@ -104,7 +106,7 @@ export function useSelectState convertValue(controlledValue), [controlledValue]), + selectedKeys: useMemo(() => convertValue(displayValue), [displayValue]), onSelectionChange: (keys: Selection) => { // impossible, but TS doesn't know that if (keys === 'all') { @@ -139,18 +141,18 @@ export function useSelectState][]; -+type IconNode = [elementName: SVGElementType, attrs: Record][]; - type SVGAttributes = Partial>; - type ComponentAttributes = RefAttributes & SVGAttributes; - interface LucideProps extends ComponentAttributes { diff --git a/scripts/oldReactSupport.mjs b/scripts/oldReactSupport.mjs index 7f19d4ba35b..8171bb21f37 100644 --- a/scripts/oldReactSupport.mjs +++ b/scripts/oldReactSupport.mjs @@ -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); diff --git a/starters/docs/package.json b/starters/docs/package.json index 024f3d177ce..aa953d80e1b 100644 --- a/starters/docs/package.json +++ b/starters/docs/package.json @@ -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", @@ -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", diff --git a/starters/tailwind/package.json b/starters/tailwind/package.json index 711117f1209..51a667147ff 100644 --- a/starters/tailwind/package.json +++ b/starters/tailwind/package.json @@ -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" diff --git a/yarn.lock b/yarn.lock index 9833c040cab..a47a46dab9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22177,7 +22177,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -22300,12 +22300,12 @@ __metadata: languageName: node linkType: hard -"lucide-react@npm:^0.294.0": - version: 0.294.0 - resolution: "lucide-react@npm:0.294.0" +"lucide-react@npm:^0.517.0": + version: 0.517.0 + resolution: "lucide-react@npm:0.517.0" peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 - checksum: 10c0/3c8d05743c5bac0299ac23ebcb78b6c0ae03182878cd80f4a51ccb89507ea564145cc8bc77757874066044e7a96af14a2a986cd759ae2b971dcab953c6b0c566 + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/9e827d7c5fd441b9628778e4a121fca4c6354b6aa4fab8b3efda1b060dd3d0b4dac43ee813161ef30f30d0919009fc4565e620d59d4e9bf9425269e242156edb languageName: node linkType: hard @@ -26471,18 +26471,6 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^18.2.0": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" - peerDependencies: - react: ^18.3.1 - checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 - languageName: node - linkType: hard - "react-frame-component@npm:^5.0.0": version: 5.2.6 resolution: "react-frame-component@npm:5.2.6" @@ -26494,13 +26482,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.3.1": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - "react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -26515,6 +26496,20 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"react-is@npm:^19.1.0": + version: 19.1.1 + resolution: "react-is@npm:19.1.1" + checksum: 10c0/3dba763fcd69835ae263dcd6727d7ffcc44c1d616f04b7329e67aefdc66a567af4f8dcecdd29454c7a707c968aa1eb85083a83fb616f01675ef25e71cf082f97 + languageName: node + linkType: hard + "react-lowlight@npm:^2.0.0": version: 2.0.0 resolution: "react-lowlight@npm:2.0.0" @@ -26545,18 +26540,6 @@ __metadata: languageName: node linkType: hard -"react-shallow-renderer@npm:^16.15.0": - version: 16.15.0 - resolution: "react-shallow-renderer@npm:16.15.0" - dependencies: - object-assign: "npm:^4.1.1" - react-is: "npm:^16.12.0 || ^17.0.0 || ^18.0.0" - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/c194d741792e86043a4ae272f7353c1cb9412bc649945c4220c6a101a6ea5410cceb3d65d5a4d750f11a24f7426e8eec7977e8a4e3ad5d3ee235ca2b18166fa8 - languageName: node - linkType: hard - "react-spectrum-monorepo@workspace:.": version: 0.0.0-use.local resolution: "react-spectrum-monorepo@workspace:." @@ -26668,7 +26651,7 @@ __metadata: jest-matchmedia-mock: "npm:^1.1.0" lerna: "npm:^3.13.2" lfcdn: "npm:^0.4.2" - lucide-react: "npm:^0.294.0" + lucide-react: "npm:^0.517.0" md5: "npm:^2.2.1" motion: "npm:^12.23.6" npm-cli-login: "npm:^1.0.0" @@ -26689,7 +26672,7 @@ __metadata: react-axe: "npm:^3.0.2" react-dom: "npm:^19.1.0" react-frame-component: "npm:^5.0.0" - react-test-renderer: "npm:^18.3.1" + react-test-renderer: "npm:^19.1.0" recast: "npm:^0.23" recursive-readdir: "npm:^2.2.2" regenerator-runtime: "npm:0.13.3" @@ -26750,16 +26733,15 @@ __metadata: languageName: unknown linkType: soft -"react-test-renderer@npm:^18.3.1": - version: 18.3.1 - resolution: "react-test-renderer@npm:18.3.1" +"react-test-renderer@npm:19.1.0": + version: 19.1.0 + resolution: "react-test-renderer@npm:19.1.0" dependencies: - react-is: "npm:^18.3.1" - react-shallow-renderer: "npm:^16.15.0" - scheduler: "npm:^0.23.2" + react-is: "npm:^19.1.0" + scheduler: "npm:^0.26.0" peerDependencies: - react: ^18.3.1 - checksum: 10c0/c633558ef9af33bc68f0c4dbb5163a004c4fb9eade7bd0a7cfc0355fb367f36bd9d96533c90b7e85a146be6c525113a15f58683d269e0177ad77e2b04d4fe51c + react: ^19.1.0 + checksum: 10c0/34ed4a37ba8b0beb96c048de6ff28574f018a18dd1042c24f8f46142d48eb5b27f82ff7c2823d082932fd3983c5a3529ab8cc8f15191d4306df0082f9f84678f languageName: node linkType: hard @@ -26785,12 +26767,10 @@ __metadata: languageName: node linkType: hard -"react@npm:^18.2.0": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 +"react@npm:^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0": + version: 19.1.1 + resolution: "react@npm:19.1.1" + checksum: 10c0/8c9769a2dfd02e603af6445058325e6c8a24b47b185d0e461f66a6454765ddcaecb3f0a90184836c68bb509f3c38248359edbc42f0d07c23eb500a5c30c87b4e languageName: node linkType: hard @@ -27844,15 +27824,6 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.23.2": - version: 0.23.2 - resolution: "scheduler@npm:0.23.2" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 - languageName: node - linkType: hard - "scheduler@npm:^0.26.0": version: 0.26.0 resolution: "scheduler@npm:0.26.0" @@ -28867,13 +28838,13 @@ __metadata: resolution: "storybook-react-parcel@workspace:packages/dev/storybook-react-parcel" dependencies: "@storybook/react": "npm:^8.6.14" - react: "npm:^18.2.0" - react-dom: "npm:^18.2.0" + react: "npm:^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + react-dom: "npm:^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" storybook: "npm:^8.6.14" storybook-builder-parcel: "npm:>=0.0.1" 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 languageName: unknown linkType: soft