Skip to content

Commit b9185de

Browse files
committed
Merge branch 'main' of github.com:adobe/react-spectrum into update-react-types
# Conflicts: # packages/@react-aria/virtualizer/src/Virtualizer.tsx # packages/@react-aria/virtualizer/src/VirtualizerItem.tsx # packages/@react-spectrum/table/src/TableView.tsx
2 parents a2ff378 + c4c625e commit b9185de

File tree

115 files changed

+3293
-6786
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+3293
-6786
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ packages/*/*/dist
88
packages/react-aria/dist
99
packages/react-stately/dist
1010
packages/dev/storybook-builder-parcel/preview.js
11+
examples/**

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ module.exports = {
169169
'rulesdir/sort-imports': [ERROR],
170170
'rulesdir/imports': [ERROR],
171171
'rulesdir/useLayoutEffectRule': [ERROR],
172+
'rulesdir/pure-render': [ERROR],
172173

173174
// jsx-a11y rules
174175
'jsx-a11y/accessible-emoji': ERROR,

.storybook/custom-addons/strictmode/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, {StrictMode, useEffect, useState} from 'react';
44

55
function StrictModeDecorator(props) {
66
let {children} = props;
7-
let [isStrict, setStrict] = useState(getQueryParams()?.strict === 'true' || false);
7+
let [isStrict, setStrict] = useState(getQueryParams()?.strict !== 'false');
88

99
useEffect(() => {
1010
let channel = addons.getChannel();

.storybook/custom-addons/strictmode/register.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, {useEffect, useState} from 'react';
44

55
const StrictModeToolBar = ({api}) => {
66
let channel = addons.getChannel();
7-
let [isStrict, setStrict] = useState(getQueryParams()?.strict === 'true' || false);
7+
let [isStrict, setStrict] = useState(getQueryParams()?.strict !== 'false');
88
let onChange = () => {
99
setStrict((old) => {
1010
channel.emit('strict/updated', !old);
@@ -29,12 +29,14 @@ const StrictModeToolBar = ({api}) => {
2929
);
3030
};
3131

32-
addons.register('StrictModeSwitcher', (api) => {
33-
addons.add('StrictModeSwitcher', {
34-
title: 'Strict mode switcher',
35-
type: types.TOOL,
36-
//👇 Shows the Toolbar UI element if either the Canvas or Docs tab is active
37-
match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)),
38-
render: () => <StrictModeToolBar api={api} />
32+
if (process.env.NODE_ENV !== 'production') {
33+
addons.register('StrictModeSwitcher', (api) => {
34+
addons.add('StrictModeSwitcher', {
35+
title: 'Strict mode switcher',
36+
type: types.TOOL,
37+
//👇 Shows the Toolbar UI element if either the Canvas or Docs tab is active
38+
match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)),
39+
render: () => <StrictModeToolBar api={api} />
40+
});
3941
});
40-
});
42+
}

.storybook/preview.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ export const parameters = {
2626

2727
export const decorators = [
2828
withScrollingSwitcher,
29-
withStrictModeSwitcher,
29+
...(process.env.NODE_ENV !== 'production' ? [withStrictModeSwitcher] : []),
3030
withProviderSwitcher
3131
];

bin/pure-render.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
// Copied from https://github.com/facebook/react/pull/24506
14+
module.exports = {
15+
meta: {
16+
type: 'problem',
17+
docs: {
18+
description: 'enforces component render purity',
19+
recommended: true,
20+
url: 'https://beta.reactjs.org/learn/keeping-components-pure'
21+
}
22+
},
23+
create(context) {
24+
return {
25+
MemberExpression(member) {
26+
// Look for member expressions that look like refs (i.e. `ref.current`).
27+
if (
28+
member.object.type !== 'Identifier' ||
29+
member.computed ||
30+
member.property.type !== 'Identifier' ||
31+
member.property.name !== 'current'
32+
) {
33+
return;
34+
}
35+
36+
// Find the parent function of this node, as well as any if statement matching against the ref value
37+
// (i.e. lazy init pattern shown in React docs).
38+
let node = member;
39+
let fn;
40+
let conditional;
41+
while (node) {
42+
if (
43+
node.type === 'FunctionDeclaration' ||
44+
node.type === 'FunctionExpression' ||
45+
node.type === 'ArrowFunctionExpression'
46+
) {
47+
fn = node;
48+
break;
49+
}
50+
51+
if (
52+
node.type === 'IfStatement' &&
53+
node.test.type === 'BinaryExpression' &&
54+
(node.test.operator === '==' || node.test.operator === '===') &&
55+
isMemberExpressionEqual(node.test.left, member)
56+
) {
57+
conditional = node.test;
58+
}
59+
60+
node = node.parent;
61+
}
62+
63+
if (!fn) {
64+
return;
65+
}
66+
67+
// Find the variable definition for the object.
68+
const variable = getVariable(context.getScope(), member.object.name);
69+
if (!variable) {
70+
return;
71+
}
72+
73+
// Find the initialization of the variable and see if it's a call to useRef.
74+
const refDefinition = variable.defs.find(def => {
75+
const init = def.node.init;
76+
if (!init) {
77+
return false;
78+
}
79+
80+
return (
81+
init.type === 'CallExpression' &&
82+
((init.callee.type === 'Identifier' &&
83+
init.callee.name === 'useRef') ||
84+
(init.callee.type === 'MemberExpression' &&
85+
init.callee.object.type === 'Identifier' &&
86+
init.callee.object.name === 'React' &&
87+
init.callee.property.type === 'Identifier' &&
88+
init.callee.property.name === 'useRef')) &&
89+
parentFunction(def.node) === fn
90+
);
91+
});
92+
93+
if (refDefinition) {
94+
// If within an if statement, check if comparing with the initial value passed to useRef.
95+
// This indicates the lazy init pattern, which is allowed.
96+
if (conditional) {
97+
const init = refDefinition.node.init.arguments[0] || {
98+
type: 'Identifier',
99+
name: 'undefined'
100+
};
101+
if (isLiteralEqual(conditional.operator, init, conditional.right)) {
102+
return;
103+
}
104+
}
105+
106+
// Otherwise, report an error for either writing or reading to this ref based on parent expression.
107+
context.report({
108+
node: member,
109+
message:
110+
member.parent.type === 'AssignmentExpression' &&
111+
member.parent.left === member
112+
? 'Writing to refs during rendering is not allowed. Move this into a useEffect or useLayoutEffect. See https://beta.reactjs.org/apis/useref'
113+
: 'Reading from refs during rendering is not allowed. See https://beta.reactjs.org/apis/useref'
114+
});
115+
}
116+
}
117+
};
118+
}
119+
};
120+
121+
function getVariable(scope, name) {
122+
while (scope) {
123+
const variable = scope.set.get(name);
124+
if (variable) {
125+
return variable;
126+
}
127+
128+
scope = scope.upper;
129+
}
130+
}
131+
132+
function parentFunction(node) {
133+
while (node) {
134+
if (
135+
node.type === 'FunctionDeclaration' ||
136+
node.type === 'FunctionExpression' ||
137+
node.type === 'ArrowFunctionExpression'
138+
) {
139+
return node;
140+
}
141+
142+
node = node.parent;
143+
}
144+
}
145+
146+
function isMemberExpressionEqual(a, b) {
147+
if (a === b) {
148+
return true;
149+
}
150+
151+
return (
152+
a.type === 'MemberExpression' &&
153+
b.type === 'MemberExpression' &&
154+
a.object.type === 'Identifier' &&
155+
b.object.type === 'Identifier' &&
156+
a.object.name === b.object.name &&
157+
a.property.type === 'Identifier' &&
158+
b.property.type === 'Identifier' &&
159+
a.property.name === b.property.name
160+
);
161+
}
162+
163+
function isLiteralEqual(operator, a, b) {
164+
let aValue, bValue;
165+
if (a.type === 'Identifier' && a.name === 'undefined') {
166+
aValue = undefined;
167+
} else if (a.type === 'Literal') {
168+
aValue = a.value;
169+
} else {
170+
return;
171+
}
172+
173+
if (b.type === 'Identifier' && b.name === 'undefined') {
174+
bValue = undefined;
175+
} else if (b.type === 'Literal') {
176+
bValue = b.value;
177+
} else {
178+
return;
179+
}
180+
181+
if (operator === '===') {
182+
return aValue === bValue;
183+
} else if (operator === '==') {
184+
// eslint-disable-next-line
185+
return aValue == bValue;
186+
}
187+
188+
return false;
189+
}

examples/rac-tailwind/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
},
88
"dependencies": {
99
"@heroicons/react": "^2.0.16",
10+
"framer-motion": "^10.12.16",
1011
"parcel": "^2.8.3",
1112
"postcss": "^8.4.21",
1213
"react": "^18.2.0",
13-
"react-aria-components": "^1.0.0-alpha.0",
14+
"react-aria-components": "^1.0.0-alpha.4",
1415
"react-dom": "^18.2.0",
16+
"react-stately": "^3.23.0",
1517
"tailwindcss": "^3.3.0",
1618
"tailwindcss-animate": "^1.0.5"
1719
},

0 commit comments

Comments
 (0)