Skip to content

Commit 028d2df

Browse files
committed
Merge branch 'constrain-date' of https://github.com/boutahlilsoufiane/react-spectrum into test_constrained_date
2 parents cbe28fb + fda81ce commit 028d2df

File tree

107 files changed

+3543
-1310
lines changed

Some content is hidden

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

107 files changed

+3543
-1310
lines changed

.parcelrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"*.{md,mdx}": ["parcel-transformer-mdx-docs"],
1212
"*.svg": ["@parcel/transformer-svg-react"],
1313
"packages/@react-aria/example-theme/**/*.css": ["@parcel/transformer-css"],
14+
"starters/docs/src/*.css": ["@parcel/transformer-css"],
1415
"*.css": ["...", "parcel-transformer-css-env"],
1516
"*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}": [
1617
"@parcel/transformer-js",

eslint.config.mjs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import reactHooks from "eslint-plugin-react-hooks";
55
import jest from "eslint-plugin-jest";
66
import monorepo from "@jdb8/eslint-plugin-monorepo";
77
import * as rspRules from "eslint-plugin-rsp-rules";
8-
import { fixupPluginRules } from "@eslint/compat";
98
import globals from "globals";
109
import babelParser from "@babel/eslint-parser";
1110
import typescriptEslint from "@typescript-eslint/eslint-plugin";
@@ -67,7 +66,7 @@ export default [{
6766
react,
6867
rulesdir,
6968
"jsx-a11y": jsxA11Y,
70-
"react-hooks": fixupPluginRules(reactHooks),
69+
"react-hooks": reactHooks,
7170
jest,
7271
monorepo,
7372
"rsp-rules": rspRules,
@@ -225,8 +224,28 @@ export default [{
225224
"react/jsx-boolean-value": ERROR,
226225
"react/jsx-first-prop-new-line": [ERROR, "multiline"],
227226
"react/self-closing-comp": ERROR,
227+
228+
// Core hooks rules
228229
"react-hooks/rules-of-hooks": ERROR, // https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md
229230
"react-hooks/exhaustive-deps": WARN,
231+
232+
// React Compiler rules
233+
'react-hooks/config': ERROR,
234+
'react-hooks/error-boundaries': ERROR,
235+
'react-hooks/component-hook-factories': ERROR,
236+
'react-hooks/gating': ERROR,
237+
// 'react-hooks/globals': ERROR,
238+
// 'react-hooks/immutability': ERROR,
239+
// 'react-hooks/preserve-manual-memoization': ERROR,
240+
// 'react-hooks/purity': ERROR,
241+
// 'react-hooks/refs': ERROR,
242+
// 'react-hooks/set-state-in-effect': ERROR,
243+
'react-hooks/set-state-in-render': ERROR,
244+
// 'react-hooks/static-components': ERROR,
245+
'react-hooks/unsupported-syntax': WARN,
246+
'react-hooks/use-memo': ERROR,
247+
'react-hooks/incompatible-library': WARN,
248+
230249
"rsp-rules/no-react-key": [ERROR],
231250
"rsp-rules/sort-imports": [ERROR],
232251
"rulesdir/imports": [ERROR],
@@ -332,7 +351,7 @@ export default [{
332351
react,
333352
rulesdir,
334353
"jsx-a11y": jsxA11Y,
335-
"react-hooks": fixupPluginRules(reactHooks),
354+
"react-hooks": reactHooks,
336355
jest,
337356
"@typescript-eslint": typescriptEslint,
338357
monorepo,

lib/svg.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ declare module 'bundle-text:*' {
1515
export default content;
1616
}
1717

18+
declare module 'url:*' {
19+
const content: string;
20+
export default content;
21+
}
22+
1823
declare module '*.svg' {
1924
import {FunctionComponent, SVGProps} from 'react';
2025
const content: FunctionComponent<SVGProps<SVGSVGElement>> ;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@
151151
"eslint-plugin-jsdoc": "^50.4.1",
152152
"eslint-plugin-jsx-a11y": "^6.10.0",
153153
"eslint-plugin-react": "^7.37.1",
154-
"eslint-plugin-react-hooks": "^5.0.0",
154+
"eslint-plugin-react-hooks": "^7.0.0",
155155
"eslint-plugin-rulesdir": "^0.2.2",
156156
"fast-check": "^2.19.0",
157157
"fast-glob": "^3.1.0",

packages/@internationalized/date/src/CalendarDate.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,13 @@ export class CalendarDate {
6464
constructor(calendar: Calendar, year: number, month: number, day: number, constrainDay?: boolean);
6565
constructor(calendar: Calendar, era: string, year: number, month: number, day: number, constrainDay?: boolean);
6666
constructor(...args: any[]) {
67-
let [calendar, era, year, month, day, constrainDay] = shiftArgs(args);
67+
let [calendar, era, year, month, day] = shiftArgs(args);
6868
this.calendar = calendar;
6969
this.era = era;
7070
this.year = year;
7171
this.month = month;
7272
this.day = day;
73+
const constrainDay = args.shift();
7374

7475
constrain(this, constrainDay);
7576
}
@@ -231,7 +232,7 @@ export class CalendarDateTime {
231232
this.minute = args.shift() || 0;
232233
this.second = args.shift() || 0;
233234
this.millisecond = args.shift() || 0;
234-
const constrainDay = args.shift() || 0;
235+
const constrainDay = args.shift();
235236

236237
constrain(this, constrainDay);
237238
}
@@ -348,7 +349,7 @@ export class ZonedDateTime {
348349
this.minute = args.shift() || 0;
349350
this.second = args.shift() || 0;
350351
this.millisecond = args.shift() || 0;
351-
const constrainDay = args.shift() || 0;
352+
const constrainDay = args.shift();
352353

353354
constrain(this, constrainDay);
354355
}

packages/@react-aria/actiongroup/src/useActionGroupItem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function useActionGroupItem<T>(props: AriaActionGroupItemProps, state: Li
5454
return () => {
5555
onRemovedWithFocus();
5656
};
57-
}, [onRemovedWithFocus]);
57+
}, []);
5858

5959
return {
6060
buttonProps: mergeProps(buttonProps, {

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

Lines changed: 23 additions & 20 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 = useEffectEvent((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();
@@ -140,32 +139,36 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
140139
delayNextActiveDescendant.current = false;
141140
});
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]));
167170

168-
let focusFirstItem = useEffectEvent(() => {
171+
let focusFirstItem = useCallback(() => {
169172
delayNextActiveDescendant.current = true;
170173
collectionRef.current?.dispatchEvent(
171174
new CustomEvent(FOCUS_EVENT, {
@@ -176,9 +179,9 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
176179
}
177180
})
178181
);
179-
});
182+
}, [collectionRef]);
180183

181-
let clearVirtualFocus = useEffectEvent((clearFocusKey?: boolean) => {
184+
let clearVirtualFocus = useCallback((clearFocusKey?: boolean) => {
182185
moveVirtualFocus(getActiveElement());
183186
queuedActiveDescendant.current = null;
184187
state.setFocusedNodeId(null);
@@ -192,7 +195,7 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
192195
clearTimeout(timeout.current);
193196
delayNextActiveDescendant.current = false;
194197
collectionRef.current?.dispatchEvent(clearFocusEvent);
195-
});
198+
}, [collectionRef, state]);
196199

197200
let lastInputType = useRef('');
198201
useEvent(inputRef, 'input', e => {
@@ -346,7 +349,7 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
346349
return () => {
347350
document.removeEventListener('keyup', onKeyUpCapture, true);
348351
};
349-
}, [onKeyUpCapture]);
352+
}, []);
350353

351354
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/autocomplete');
352355
let collectionProps = useLabels({

packages/@react-aria/collections/src/CollectionBuilder.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ const CollectionContext = createContext<CachedChildrenOptions<unknown> | null>(n
221221
export function Collection<T extends object>(props: CollectionProps<T>): JSX.Element {
222222
let ctx = useContext(CollectionContext)!;
223223
let dependencies = (ctx?.dependencies || []).concat(props.dependencies);
224-
let idScope = props.idScope || ctx?.idScope;
224+
let idScope = props.idScope ?? ctx?.idScope;
225225
let children = useCollectionChildren({
226226
...props,
227227
idScope,

packages/@react-aria/collections/src/useCachedChildren.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function useCachedChildren<T extends object>(props: CachedChildrenOptions
5050
throw new Error('Could not determine key for item');
5151
}
5252

53-
if (idScope) {
53+
if (idScope != null) {
5454
key = idScope + ':' + key;
5555
}
5656
// Note: only works if wrapped Item passes through id...

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)