Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/@internationalized/date/docs/CalendarDate.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ let date = parseDate('2022-02-03');

Today's date can be retrieved using the <TypeLink links={docs.links} type={docs.exports.today} /> function. This requires a time zone identifier to be provided, which is used to determine the local date. The <TypeLink links={docs.links} type={docs.exports.getLocalTimeZone} /> 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 <TypeLink links={docs.links} type={docs.exports.resetLocalTimeZone} />, or mock it in unit tests by calling <TypeLink links={docs.links} type={docs.exports.setLocalTimeZone} />.

```tsx
import {today, getLocalTimeZone} from '@internationalized/date';

Expand Down Expand Up @@ -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 <TypeLink links={docs.links} type={docs.exports.getLocalTimeZone} /> 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 <TypeLink links={docs.links} type={docs.exports.resetLocalTimeZone} />, or mock it in unit tests by calling <TypeLink links={docs.links} type={docs.exports.setLocalTimeZone} />.

```tsx
import {getLocalTimeZone} from '@internationalized/date';

Expand Down
2 changes: 2 additions & 0 deletions packages/@internationalized/date/docs/CalendarDateTime.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <TypeLink links={docs.links} type={docs.exports.getLocalTimeZone} /> 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 <TypeLink links={docs.links} type={docs.exports.resetLocalTimeZone} />, or mock it in unit tests by calling <TypeLink links={docs.links} type={docs.exports.setLocalTimeZone} />.

```tsx
import {getLocalTimeZone} from '@internationalized/date';

Expand Down
2 changes: 2 additions & 0 deletions packages/@internationalized/date/docs/ZonedDateTime.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ let date = fromAbsolute(1688023843144, 'America/Los_Angeles');

The current time can be retrieved using the <TypeLink links={docs.links} type={docs.exports.now} /> function. This requires a time zone identifier to be provided, which is used to determine the local time. The <TypeLink links={docs.links} type={docs.exports.getLocalTimeZone} /> 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 <TypeLink links={docs.links} type={docs.exports.resetLocalTimeZone} />, or mock it in unit tests by calling <TypeLink links={docs.links} type={docs.exports.setLocalTimeZone} />.

```tsx
import {now, getLocalTimeZone} from '@internationalized/date';

Expand Down
3 changes: 2 additions & 1 deletion packages/@react-aria/button/src/useButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ export function useButton(props: AriaButtonOptions<ElementType>, 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']
})
};
}
7 changes: 7 additions & 0 deletions packages/@react-aria/button/test/useButton.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
29 changes: 14 additions & 15 deletions packages/@react-aria/collections/src/BaseCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ export class CollectionNode<T> implements Node<T> {
return node;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: FilterFn<T>): CollectionNode<T> | null {
let clone = this.clone();
newCollection.addDescendants(clone, collection);
return clone;
}
}

export class FilterableNode<T> extends CollectionNode<T> {
filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: FilterFn<T>): CollectionNode<T> | null {
let [firstKey, lastKey] = filterChildren(collection, newCollection, this.firstChildKey, filterFn);
let newNode: Mutable<CollectionNode<T>> = this.clone();
Expand All @@ -80,26 +89,15 @@ export class CollectionNode<T> implements Node<T> {
}
}

// 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<T> extends CollectionNode<T> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: FilterFn<T>): FilterLessNode<T> | null {
let clone = this.clone();
newCollection.addDescendants(clone, collection);
return clone;
}
}

export class HeaderNode extends FilterLessNode<unknown> {
export class HeaderNode extends CollectionNode<unknown> {
static readonly type = 'header';
}

export class LoaderNode extends FilterLessNode<unknown> {
export class LoaderNode extends CollectionNode<unknown> {
static readonly type = 'loader';
}

export class ItemNode<T> extends CollectionNode<T> {
export class ItemNode<T> extends FilterableNode<T> {
static readonly type = 'item';

filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: FilterFn<T>): ItemNode<T> | null {
Expand All @@ -113,7 +111,7 @@ export class ItemNode<T> extends CollectionNode<T> {
}
}

export class SectionNode<T> extends CollectionNode<T> {
export class SectionNode<T> extends FilterableNode<T> {
static readonly type = 'section';

filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: FilterFn<T>): SectionNode<T> | null {
Expand Down Expand Up @@ -142,6 +140,7 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
private lastKey: Key | null = null;
private frozen = false;
private itemCount: number = 0;
isComplete = true;

get size(): number {
return this.itemCount;
Expand Down
1 change: 1 addition & 0 deletions packages/@react-aria/collections/src/CollectionBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function useCollectionDocument<T extends object, C extends BaseCollection<T>>(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.
Expand Down
5 changes: 5 additions & 0 deletions packages/@react-aria/collections/src/Document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
nodeId = 0;
nodesByProps: WeakMap<object, ElementNode<T>> = new WeakMap<object, ElementNode<T>>();
isMounted = true;
isInitialRender = true;
private collection: C;
private nextCollection: C | null = null;
private subscriptions: Set<() => void> = new Set();
Expand Down Expand Up @@ -522,6 +523,10 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
this.nextCollection = null;
}
}

if (this.isInitialRender) {
this.collection.isComplete = false;
}
}

queueUpdate(): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/collections/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
export {CollectionBuilder, Collection, createLeafComponent, createBranchComponent} from './CollectionBuilder';
export {createHideableComponent, useIsHidden} from './Hidden';
export {useCachedChildren} from './useCachedChildren';
export {BaseCollection, CollectionNode, ItemNode, SectionNode, FilterLessNode, LoaderNode, HeaderNode} from './BaseCollection';
export {BaseCollection, CollectionNode, ItemNode, SectionNode, FilterableNode, LoaderNode, HeaderNode} from './BaseCollection';

export type {CollectionBuilderProps, CollectionProps} from './CollectionBuilder';
export type {CachedChildrenOptions} from './useCachedChildren';
6 changes: 3 additions & 3 deletions packages/@react-aria/overlays/src/calculatePosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export interface PositionResult {
position: Position,
arrowOffsetLeft?: number,
arrowOffsetTop?: number,
triggerOrigin: {x: number, y: number},
triggerAnchorPoint: {x: number, y: number},
maxHeight: number,
placement: PlacementAxis
}
Expand Down Expand Up @@ -450,7 +450,7 @@ export function calculatePositionInternal(
}

let crossOrigin = placement === 'left' || placement === 'top' ? overlaySize[size] : 0;
let triggerOrigin = {
let triggerAnchorPoint = {
x: placement === 'top' || placement === 'bottom' ? origin : crossOrigin,
y: placement === 'left' || placement === 'right' ? origin : crossOrigin
};
Expand All @@ -461,7 +461,7 @@ export function calculatePositionInternal(
arrowOffsetLeft: arrowPosition.left,
arrowOffsetTop: arrowPosition.top,
placement,
triggerOrigin
triggerAnchorPoint
};
}

Expand Down
13 changes: 10 additions & 3 deletions packages/@react-aria/overlays/src/useOverlayPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export interface PositionAria {
/** Placement of the overlay with respect to the overlay trigger. */
placement: PlacementAxis | null,
/** The origin of the target in the overlay's coordinate system. Useful for animations. */
triggerOrigin: {x: number, y: number} | null,
triggerAnchorPoint: {x: number, y: number} | null,
/** Updates the position of the overlay. */
updatePosition(): void
}
Expand Down Expand Up @@ -149,6 +149,11 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
return;
}

// Delay updating the position until children are finished rendering (e.g. collections).
if (overlayRef.current.querySelector('[data-react-aria-incomplete]')) {
return;
}

// Don't update while the overlay is animating.
// Things like scale animations can mess up positioning by affecting the overlay's computed size.
if (overlayRef.current.getAnimations?.().length > 0) {
Expand Down Expand Up @@ -294,14 +299,16 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
return {
overlayProps: {
style: {
position: 'absolute',
position: position ? 'absolute' : 'fixed',
top: !position ? 0 : undefined,
left: !position ? 0 : undefined,
zIndex: 100000, // should match the z-index in ModalTrigger
...position?.position,
maxHeight: position?.maxHeight ?? '100vh'
}
},
placement: position?.placement ?? null,
triggerOrigin: position?.triggerOrigin ?? null,
triggerAnchorPoint: position?.triggerAnchorPoint ?? null,
arrowProps: {
'aria-hidden': 'true',
role: 'presentation',
Expand Down
6 changes: 3 additions & 3 deletions packages/@react-aria/overlays/src/usePopover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface PopoverAria {
/** Placement of the popover with respect to the trigger. */
placement: PlacementAxis | null,
/** The origin of the target in the overlay's coordinate system. Useful for animations. */
triggerOrigin: {x: number, y: number} | null
triggerAnchorPoint: {x: number, y: number} | null
}

/**
Expand Down Expand Up @@ -106,7 +106,7 @@ export function usePopover(props: AriaPopoverProps, state: OverlayTriggerState):
groupRef ?? popoverRef
);

let {overlayProps: positionProps, arrowProps, placement, triggerOrigin: origin} = useOverlayPosition({
let {overlayProps: positionProps, arrowProps, placement, triggerAnchorPoint: origin} = useOverlayPosition({
...otherProps,
targetRef: triggerRef,
overlayRef: popoverRef,
Expand All @@ -133,6 +133,6 @@ export function usePopover(props: AriaPopoverProps, state: OverlayTriggerState):
arrowProps,
underlayProps,
placement,
triggerOrigin: origin
triggerAnchorPoint: origin
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ describe('calculatePosition', function () {
arrowOffsetTop: expected[3],
maxHeight,
placement: calculatedPlacement,
triggerOrigin: {
triggerAnchorPoint: {
x: expected[2] ?? (calculatedPlacement === 'left' ? overlaySize.width : 0),
y: expected[3] ?? (calculatedPlacement === 'top' ? Math.min(overlaySize.height, maxHeight) : 0)
}
Expand Down
Loading
Loading