diff --git a/packages/@internationalized/date/docs/CalendarDate.mdx b/packages/@internationalized/date/docs/CalendarDate.mdx
index a14440ba735..b7740ba88d1 100644
--- a/packages/@internationalized/date/docs/CalendarDate.mdx
+++ b/packages/@internationalized/date/docs/CalendarDate.mdx
@@ -50,6 +50,8 @@ let date = parseDate('2022-02-03');
Today's date can be retrieved using the function. This requires a time zone identifier to be provided, which is used to determine the local date. The function can be used to retrieve the user's current time zone.
+**Note:** the local time zone is cached after the first call. You can reset it by calling , or mock it in unit tests by calling .
+
```tsx
import {today, getLocalTimeZone} from '@internationalized/date';
@@ -206,6 +208,8 @@ A `CalendarDate` can be converted to a native JavaScript `Date` object using the
Because a `Date` represents an exact time, a time zone identifier is required to be passed to the `toDate` method. The time of the returned date will be set to midnight in that time zone. The function can be used to retrieve the user's current time zone.
+**Note:** the local time zone is cached after the first call. You can reset it by calling , or mock it in unit tests by calling .
+
```tsx
import {getLocalTimeZone} from '@internationalized/date';
diff --git a/packages/@internationalized/date/docs/CalendarDateTime.mdx b/packages/@internationalized/date/docs/CalendarDateTime.mdx
index 3796777ed49..6e19cd58b2d 100644
--- a/packages/@internationalized/date/docs/CalendarDateTime.mdx
+++ b/packages/@internationalized/date/docs/CalendarDateTime.mdx
@@ -280,6 +280,8 @@ A `CalendarDateTime` can be converted to a native JavaScript `Date` object using
Because a `Date` represents an exact time, a time zone identifier is required to be passed to the `toDate` method. The function can be used to retrieve the user's current time zone.
+**Note:** the local time zone is cached after the first call. You can reset it by calling , or mock it in unit tests by calling .
+
```tsx
import {getLocalTimeZone} from '@internationalized/date';
diff --git a/packages/@internationalized/date/docs/ZonedDateTime.mdx b/packages/@internationalized/date/docs/ZonedDateTime.mdx
index 543f89db6dd..5b7e8ca9940 100644
--- a/packages/@internationalized/date/docs/ZonedDateTime.mdx
+++ b/packages/@internationalized/date/docs/ZonedDateTime.mdx
@@ -76,6 +76,8 @@ let date = fromAbsolute(1688023843144, 'America/Los_Angeles');
The current time can be retrieved using the function. This requires a time zone identifier to be provided, which is used to determine the local time. The function can be used to retrieve the user's current time zone.
+**Note:** the local time zone is cached after the first call. You can reset it by calling , or mock it in unit tests by calling .
+
```tsx
import {now, getLocalTimeZone} from '@internationalized/date';
diff --git a/packages/@react-aria/button/src/useButton.ts b/packages/@react-aria/button/src/useButton.ts
index 8b33496778c..163b00c05e6 100644
--- a/packages/@react-aria/button/src/useButton.ts
+++ b/packages/@react-aria/button/src/useButton.ts
@@ -114,7 +114,8 @@ export function useButton(props: AriaButtonOptions, ref: RefObject<
'aria-expanded': props['aria-expanded'],
'aria-controls': props['aria-controls'],
'aria-pressed': props['aria-pressed'],
- 'aria-current': props['aria-current']
+ 'aria-current': props['aria-current'],
+ 'aria-disabled': props['aria-disabled']
})
};
}
diff --git a/packages/@react-aria/button/test/useButton.test.js b/packages/@react-aria/button/test/useButton.test.js
index 913e1586625..0a9492e0553 100644
--- a/packages/@react-aria/button/test/useButton.test.js
+++ b/packages/@react-aria/button/test/useButton.test.js
@@ -61,4 +61,11 @@ describe('useButton tests', function () {
expect(typeof result.current.buttonProps.onKeyDown).toBe('function');
expect(result.current.buttonProps.rel).toBeUndefined();
});
+
+ it('handles aria-disabled passthrough for button elements', function () {
+ let props = {'aria-disabled': 'true'};
+ let {result} = renderHook(() => useButton(props));
+ expect(result.current.buttonProps['aria-disabled']).toBeTruthy();
+ expect(result.current.buttonProps['disabled']).toBeUndefined();
+ });
});
diff --git a/packages/@react-aria/collections/src/BaseCollection.ts b/packages/@react-aria/collections/src/BaseCollection.ts
index cb41c19a1d3..e06d1a360f4 100644
--- a/packages/@react-aria/collections/src/BaseCollection.ts
+++ b/packages/@react-aria/collections/src/BaseCollection.ts
@@ -71,6 +71,15 @@ export class CollectionNode implements Node {
return node;
}
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ filter(collection: BaseCollection, newCollection: BaseCollection, filterFn: FilterFn): CollectionNode | null {
+ let clone = this.clone();
+ newCollection.addDescendants(clone, collection);
+ return clone;
+ }
+}
+
+export class FilterableNode extends CollectionNode {
filter(collection: BaseCollection, newCollection: BaseCollection, filterFn: FilterFn): CollectionNode | null {
let [firstKey, lastKey] = filterChildren(collection, newCollection, this.firstChildKey, filterFn);
let newNode: Mutable> = this.clone();
@@ -80,26 +89,15 @@ export class CollectionNode implements Node {
}
}
-// TODO: naming, but essentially these nodes shouldn't be affected by filtering (BaseNode)?
-// Perhaps this filter logic should be in CollectionNode instead and the current logic of CollectionNode's filter should move to Table
-export class FilterLessNode extends CollectionNode {
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- filter(collection: BaseCollection, newCollection: BaseCollection, filterFn: FilterFn): FilterLessNode | null {
- let clone = this.clone();
- newCollection.addDescendants(clone, collection);
- return clone;
- }
-}
-
-export class HeaderNode extends FilterLessNode {
+export class HeaderNode extends CollectionNode {
static readonly type = 'header';
}
-export class LoaderNode extends FilterLessNode {
+export class LoaderNode extends CollectionNode {
static readonly type = 'loader';
}
-export class ItemNode extends CollectionNode {
+export class ItemNode extends FilterableNode {
static readonly type = 'item';
filter(collection: BaseCollection, newCollection: BaseCollection, filterFn: FilterFn): ItemNode | null {
@@ -113,7 +111,7 @@ export class ItemNode extends CollectionNode {
}
}
-export class SectionNode extends CollectionNode {
+export class SectionNode extends FilterableNode {
static readonly type = 'section';
filter(collection: BaseCollection, newCollection: BaseCollection, filterFn: FilterFn): SectionNode | null {
@@ -142,6 +140,7 @@ export class BaseCollection implements ICollection> {
private lastKey: Key | null = null;
private frozen = false;
private itemCount: number = 0;
+ isComplete = true;
get size(): number {
return this.itemCount;
diff --git a/packages/@react-aria/collections/src/CollectionBuilder.tsx b/packages/@react-aria/collections/src/CollectionBuilder.tsx
index 83b1e834d57..742fe8c056e 100644
--- a/packages/@react-aria/collections/src/CollectionBuilder.tsx
+++ b/packages/@react-aria/collections/src/CollectionBuilder.tsx
@@ -116,6 +116,7 @@ function useCollectionDocument>(cr
let collection = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
useLayoutEffect(() => {
document.isMounted = true;
+ document.isInitialRender = false;
return () => {
// Mark unmounted so we can skip all of the collection updates caused by
// React calling removeChild on every item in the collection.
diff --git a/packages/@react-aria/collections/src/Document.ts b/packages/@react-aria/collections/src/Document.ts
index 36bdb3491a8..be0510d8efc 100644
--- a/packages/@react-aria/collections/src/Document.ts
+++ b/packages/@react-aria/collections/src/Document.ts
@@ -416,6 +416,7 @@ export class Document = BaseCollection> extend
nodeId = 0;
nodesByProps: WeakMap