Skip to content

Commit b0cf01c

Browse files
committed
Merge branch 'update-eslint-plugin-react-hooks-followup' into update-eslint-plugin-react-hooks-immutability
2 parents bbd6a6f + e88adfa commit b0cf01c

File tree

262 files changed

+6921
-3070
lines changed

Some content is hidden

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

262 files changed

+6921
-3070
lines changed

.cursor/rules/add-subheadings.mdc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
alwaysApply: false
3+
---
4+
5+
# Step 3: Add sub-headings
6+
7+
Within the Enhancements, Fixes, and Under Construction categories, group commits by UI component under sub-headings.
8+
9+
### Sub-heading rules:
10+
- Use sub-headings ONLY for:
11+
- Enhancements
12+
- Fixes
13+
- Under Construction
14+
- Do NOT create sub-headings for:
15+
- Documentation
16+
- To Be Categorized
17+
- S2
18+
- Sub-headings should be in alphabetical order
19+
- Use "Miscellaneous" as a sub-heading for commits that do not belong to a specific component
20+
- Each category can have its own Miscellaneous sub-heading
21+
- Write sub-headings and commits as unordered lists using a hyphen (-)
22+
- Do NOT bold the sub-heading text
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
alwaysApply: false
3+
---
4+
5+
# Step 1: Categorize commits
6+
7+
Sort ALL commit messages into one of six main categories. The main categories are the following:
8+
- Enhancements
9+
- Fixes
10+
- Documentation
11+
- Under Construction
12+
- To Be Categorized
13+
- S2
14+
15+
Before categorizing commits into other groups, check whether each commit should be classified as “Under Construction.”
16+
- Follow the steps below in order:
17+
1. Identify pre-release packages
18+
- Use a command such as grep to scan the repository for package versions that include prerelease identifiers (e.g., alpha, beta, rc)
19+
2. Extract component keywords from commit messages
20+
- Parse each commit message to identify possible component names
21+
- Normalize these keywords (e.g., lowercase, remove punctuation) for easier comparison.
22+
3. Compare extracted keywords with pre-release packages
23+
- If any keyword matches a package in the list, mark the commit as Under Construction.
24+
4. Check for explicit prerelease keywords in commit text
25+
- If the commit message directly includes alpha, beta, or rc, classify it as Under Construction, regardless of package matches.
26+
27+
Next, categorize the remaining commits not categorized as "Under Construction". Use the following keywords to determine the category:
28+
| Keyword | Category |
29+
|----------------------------|----------|
30+
| feat | Enhancements|
31+
| fix | Fixes |
32+
| docs | Documentation |
33+
| chore, revert, bump, build | To Be Categorized |
34+
| S2 | S2 |
35+
36+
Do not duplicate commits. In terms of priority, it should be Under Construction > S2 > To Be Categorized > Enhancements > Fixes > Documentation
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
alwaysApply: false
3+
---
4+
5+
You are a expert technical writer for front-end development.
6+
7+
# Step 2: Rewrite commit messages
8+
9+
Original format: Type (Scope): Summary of changes - [@username](link to username) - [PR](link to PR)
10+
New Format: Summary of changes - [@username](link to username) - [PR](link to PR)
11+
12+
### General Guidelines:
13+
- Keep the summary as a single, grammatically correct sentence
14+
- Verbs should be first person present tense but do NOT include the subject (e.g. I)
15+
- The message should be concise and easy to read
16+
- Wrap any camelCase or code-like terms (e.g. onClick, onAction, isDisabled) in backticks (``)
17+
- Do NOT use backticks for component names
18+
- Replace specific terms:
19+
- RAC -> React Aria
20+
- V3 -> React Spectrum
21+
- ALWAYS capitalize UI component names
22+
- Example:
23+
- toast -> Toast
24+
- inline alert -> InlineAlert
25+
26+
### Component Names to Capitalize:
27+
Accordion, Autocomplete, Badge, Breadcrumbs, Buttons, Calendar, Checkbox, CheckboxGroup, Collections, ColorArea, ColorField, ColorPicker, ColorSlider, ColorSwatch, ColorSwatchPicker, ColorWheel, ComboBox, Date and Time, DateField, DatePicker, DateRangePicker, Dialog, Disclosure, DisclosureGroup, Drag and Drop, DropZone, FileTrigger, Form, InlineAlert, Link, Listbox, ListView, Menu, Meter, Modal, NotificationBadge, NumberField, Picker, ProgressBar, ProgressCircle, RadioGroup, RangeCalendar, SearchField, Select, Slider, StatusLight, Switch, Table, Tabs, TagGroup, TextArea, TextField, TimeField, Toast, ToggleButton, ToggleButtonGroup, Tooltip, Tree, Virtualizer.

packages/@react-aria/autocomplete/src/useAutocomplete.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {AriaLabelingProps, BaseEvent, DOMProps, FocusableElement, FocusEvents, KeyboardEvents, Node, RefObject, ValueBase} from '@react-types/shared';
1414
import {AriaTextFieldProps} from '@react-aria/textfield';
1515
import {AutocompleteProps, AutocompleteState} from '@react-stately/autocomplete';
16-
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getOwnerDocument, isAndroid, isCtrlKeyPressed, isIOS, mergeProps, mergeRefs, useEffectEvent, useEvent, useId, useLabels, useObjectRef} from '@react-aria/utils';
16+
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getOwnerDocument, isAndroid, isCtrlKeyPressed, isIOS, mergeProps, mergeRefs, useEffectEvent, useEvent, useId, useLabels, useLayoutEffect, useObjectRef} from '@react-aria/utils';
1717
import {dispatchVirtualBlur, dispatchVirtualFocus, getVirtuallyFocusedElement, moveVirtualFocus} from '@react-aria/focus';
1818
import {getInteractionModality} from '@react-aria/interactions';
1919
// @ts-ignore
@@ -92,7 +92,6 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
9292
let timeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
9393
let delayNextActiveDescendant = useRef(false);
9494
let queuedActiveDescendant = useRef<string | null>(null);
95-
let lastCollectionNode = useRef<HTMLElement>(null);
9695

9796
// For mobile screen readers, we don't want virtual focus, instead opting to disable FocusScope's restoreFocus and manually
9897
// moving focus back to the subtriggers
@@ -106,7 +105,7 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
106105
return () => clearTimeout(timeout.current);
107106
}, []);
108107

109-
let updateActiveDescendant = useCallback((e: Event) => {
108+
let updateActiveDescendantEvent = useEffectEvent((e: Event) => {
110109
// Ensure input is focused if the user clicks on the collection directly.
111110
if (!e.isTrusted && shouldUseVirtualFocus && inputRef.current && getActiveElement(getOwnerDocument(inputRef.current)) !== inputRef.current) {
112111
inputRef.current.focus();
@@ -138,29 +137,33 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
138137
}
139138

140139
delayNextActiveDescendant.current = false;
141-
}, [shouldUseVirtualFocus, inputRef, collectionRef, state]);
140+
});
142141

143-
let callbackRef = useCallback((collectionNode) => {
144-
if (collectionNode != null) {
145-
// When typing forward, we want to delay the setting of active descendant to not interrupt the native screen reader announcement
146-
// of the letter you just typed. If we recieve another focus event then we clear the queued update
147-
// We track lastCollectionNode to do proper cleanup since callbackRefs just pass null when unmounting. This also handles
148-
// React 19's extra call of the callback ref in strict mode
149-
lastCollectionNode.current?.removeEventListener('focusin', updateActiveDescendant);
150-
lastCollectionNode.current = collectionNode;
151-
collectionNode.addEventListener('focusin', updateActiveDescendant);
142+
let [collectionNode, setCollectionNode] = useState<HTMLElement | null>(null);
143+
let callbackRef = useCallback((node) => {
144+
setCollectionNode(node);
145+
if (node != null) {
152146
// If useSelectableCollection isn't passed shouldUseVirtualFocus even when useAutocomplete provides it
153147
// that means the collection doesn't support it (e.g. Table). If that is the case, we need to disable it here regardless
154148
// of what the user's provided so that the input doesn't recieve the onKeyDown and autocomplete props.
155-
if (collectionNode.getAttribute('tabindex') != null) {
149+
if (node.getAttribute('tabindex') != null) {
156150
setShouldUseVirtualFocus(false);
157151
}
158152
setHasCollection(true);
159153
} else {
160-
lastCollectionNode.current?.removeEventListener('focusin', updateActiveDescendant);
161154
setHasCollection(false);
162155
}
163-
}, [updateActiveDescendant]);
156+
}, []);
157+
useLayoutEffect(() => {
158+
if (collectionNode != null) {
159+
// When typing forward, we want to delay the setting of active descendant to not interrupt the native screen reader announcement
160+
// of the letter you just typed. If we recieve another focus event then we clear the queued update
161+
collectionNode.addEventListener('focusin', updateActiveDescendantEvent);
162+
}
163+
return () => {
164+
collectionNode?.removeEventListener('focusin', updateActiveDescendantEvent);
165+
};
166+
}, [collectionNode]);
164167

165168
// Make sure to memo so that React doesn't keep registering a new event listeners on every rerender of the wrapped collection
166169
let mergedCollectionRef = useObjectRef(useMemo(() => mergeRefs(collectionRef, callbackRef), [collectionRef, callbackRef]));

packages/@react-aria/disclosure/src/useDisclosure.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function useDisclosure(props: AriaDisclosureProps, state: DisclosureState
7373
if (raf.current) {
7474
cancelAnimationFrame(raf.current);
7575
}
76-
if (ref.current && !isDisabled && !isSSR) {
76+
if (ref.current && !isSSR) {
7777
let panel = ref.current;
7878

7979
if (isExpandedRef.current == null || typeof panel.getAnimations !== 'function') {
@@ -155,7 +155,7 @@ export function useDisclosure(props: AriaDisclosureProps, state: DisclosureState
155155
role: 'group',
156156
'aria-labelledby': triggerId,
157157
'aria-hidden': !state.isExpanded,
158-
hidden: !state.isExpanded || undefined
158+
hidden: isSSR ? !state.isExpanded : undefined
159159
}
160160
};
161161
}

packages/@react-aria/disclosure/test/useDisclosure.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ describe('useDisclosure', () => {
9191
});
9292

9393
it('should keep panel hidden when toggling disabled state', () => {
94-
let {result, rerender} = renderHook(({isDisabled}: {isDisabled: boolean}) => {
94+
let {rerender} = renderHook(({isDisabled}: {isDisabled: boolean}) => {
9595
let state = useDisclosureState({});
9696
return useDisclosure({isDisabled}, state, ref);
9797
}, {initialProps: {isDisabled: false}});
@@ -100,13 +100,13 @@ describe('useDisclosure', () => {
100100
rerender({isDisabled: true});
101101
});
102102

103-
expect(result.current.panelProps.hidden).toBe(true);
103+
expect(ref.current.hidden).toBe(true);
104104

105105
act(() => {
106106
rerender({isDisabled: false});
107107
});
108108

109-
expect(result.current.panelProps.hidden).toBe(true);
109+
expect(ref.current.hidden).toBe(true);
110110
});
111111

112112
it('should set correct IDs for accessibility', () => {

packages/@react-aria/dnd/src/DropTargetKeyboardNavigation.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,25 @@ function nextDropTarget(
100100
}
101101
case 'after': {
102102
// If this is the last sibling in a level, traverse to the parent.
103-
let targetNode = collection.getItem(target.key);
104-
if (targetNode && targetNode.nextKey == null && targetNode.parentKey != null) {
103+
let targetNode = collection.getItem(target.key);
104+
let nextItemInSameLevel = targetNode?.nextKey != null ? collection.getItem(targetNode.nextKey) : null;
105+
while (nextItemInSameLevel != null && nextItemInSameLevel.type !== 'item') {
106+
nextItemInSameLevel = nextItemInSameLevel.nextKey != null ? collection.getItem(nextItemInSameLevel.nextKey) : null;
107+
}
108+
109+
if (targetNode && nextItemInSameLevel == null && targetNode.parentKey != null) {
105110
// If the parent item has an item after it, use the "before" position.
106111
let parentNode = collection.getItem(targetNode.parentKey);
107-
if (parentNode?.nextKey != null) {
112+
const nextNode = parentNode?.nextKey != null ? collection.getItem(parentNode.nextKey) : null;
113+
if (nextNode?.type === 'item') {
108114
return {
109115
type: 'item',
110-
key: parentNode.nextKey,
116+
key: nextNode.key,
111117
dropPosition: 'before'
112118
};
113119
}
114120

115-
if (parentNode) {
121+
if (parentNode?.type === 'item') {
116122
return {
117123
type: 'item',
118124
key: parentNode.key,
@@ -121,10 +127,10 @@ function nextDropTarget(
121127
}
122128
}
123129

124-
if (targetNode?.nextKey != null) {
130+
if (nextItemInSameLevel) {
125131
return {
126132
type: 'item',
127-
key: targetNode.nextKey,
133+
key: nextItemInSameLevel.key,
128134
dropPosition: 'on'
129135
};
130136
}
@@ -154,8 +160,11 @@ function previousDropTarget(
154160
let prevKey: Key | null = null;
155161
let lastKey = keyboardDelegate.getLastKey?.();
156162
while (lastKey != null) {
157-
prevKey = lastKey;
158163
let node = collection.getItem(lastKey);
164+
if (node?.type !== 'item') {
165+
break;
166+
}
167+
prevKey = lastKey;
159168
lastKey = node?.parentKey;
160169
}
161170

0 commit comments

Comments
 (0)