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
29 changes: 17 additions & 12 deletions packages/@react-aria/utils/src/scrollIntoView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,23 @@ export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement): v
* offsetLeft or offsetTop through intervening offsetParents.
*/
function relativeOffset(ancestor: HTMLElement, child: HTMLElement, axis: 'left'|'top') {
let childRect = child.getBoundingClientRect();
let ancestorRect = ancestor.getBoundingClientRect();

let viewportOffset = axis === 'left'
? childRect.left - ancestorRect.left
: childRect.top - ancestorRect.top;

let scrollAdjustment = axis === 'left'
? ancestor.scrollLeft
: ancestor.scrollTop;

return viewportOffset + scrollAdjustment;
const prop = axis === 'left' ? 'offsetLeft' : 'offsetTop';
let sum = 0;
while (child.offsetParent) {
sum += child[prop];
if (child.offsetParent === ancestor) {
// Stop once we have found the ancestor we are interested in.
break;
} else if (nodeContains(child.offsetParent, ancestor)) {
// If the ancestor is not `position:relative`, then we stop at
// _its_ offset parent, and we subtract off _its_ offset, so that
// we end up with the proper offset from child to ancestor.
sum -= ancestor[prop];
break;
}
child = child.offsetParent as HTMLElement;
}
return sum;
}

/**
Expand Down
9 changes: 1 addition & 8 deletions packages/@react-aria/utils/src/useEffectEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ import {useLayoutEffect} from './useLayoutEffect';
// before all layout effects, but is available only in React 18 and later.
const useEarlyEffect = React['useInsertionEffect'] ?? useLayoutEffect;

// Starting with React 19.2, this hook has been internalized.
const useModernEffectEvent = React['useEffectEvent'] ?? useLegacyEffectEvent;

function useLegacyEffectEvent<T extends Function>(fn?: T): T {
export function useEffectEvent<T extends Function>(fn?: T): T {
const ref = useRef<T | null | undefined>(null);
useEarlyEffect(() => {
ref.current = fn;
Expand All @@ -31,7 +28,3 @@ function useLegacyEffectEvent<T extends Function>(fn?: T): T {
return f?.(...args);
}, []);
}

export function useEffectEvent<T extends Function>(fn: T): T {
return useModernEffectEvent(fn);
}
5 changes: 2 additions & 3 deletions packages/@react-aria/utils/src/useEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/

import {RefObject} from '@react-types/shared';
import {useCallback, useEffect} from 'react';
import {useEffect} from 'react';
import {useEffectEvent} from './useEffectEvent';

export function useEvent<K extends keyof GlobalEventHandlersEventMap>(
Expand All @@ -20,8 +20,7 @@ export function useEvent<K extends keyof GlobalEventHandlersEventMap>(
handler?: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
): void {
let noop = useCallback(() => {}, []);
let handleEvent = useEffectEvent(handler ?? noop);
let handleEvent = useEffectEvent(handler);
let isDisabled = handler == null;

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/dev/s2-docs/pages/react-aria/ai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Add the server to your MCP client configuration (the exact file and schema may d
</picture>
</Link>

Or follow Cursor's MCP install [guide](https://docs.cursor.com/en/context/mcp#installing-mcp-servers) and use the standard config above.
Or follow Cursor's MCP install [guide](https://cursor.com/docs/context/mcp#installing-mcp-servers) and use the standard config above.

### VS Code

Expand Down
2 changes: 1 addition & 1 deletion packages/dev/s2-docs/pages/s2/ai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Add the server to your MCP client configuration (the exact file and schema may d
</picture>
</Link>

Or follow Cursor's MCP install [guide](https://docs.cursor.com/en/context/mcp#installing-mcp-servers) and use the standard config above.
Or follow Cursor's MCP install [guide](https://cursor.com/docs/context/mcp#installing-mcp-servers) and use the standard config above.

### VS Code

Expand Down
29 changes: 25 additions & 4 deletions packages/dev/s2-docs/src/CodePlatter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,33 @@ export function CodePlatter({children, type, showCoachMark}: CodePlatterProps) {
// Find previous heading element to get hash.
let url = new URL(shareUrl, location.href);
let node: Element | null = codeRef.current;
while (node && node.parentElement?.tagName !== 'ARTICLE') {

// Search for the nearest heading by walking up the tree and checking previous siblings
while (node && node.tagName !== 'ARTICLE') {
// Check previous siblings
let sibling = node.previousElementSibling;
while (sibling) {
if (sibling instanceof HTMLHeadingElement) {
node = sibling;
break;
}
// Also check inside the sibling for headings
let headingInSibling = sibling.querySelector('h1, h2, h3, h4, h5, h6');
if (headingInSibling instanceof HTMLHeadingElement) {
node = headingInSibling;
break;
}
sibling = sibling.previousElementSibling;
}

if (node instanceof HTMLHeadingElement) {
break;
}

// Move up to parent
node = node.parentElement;
}
while (node && !(node instanceof HTMLHeadingElement)) {
node = node.previousElementSibling;
}

if (node instanceof HTMLHeadingElement && node.id) {
url.hash = '#' + node.id;
}
Expand Down
28 changes: 24 additions & 4 deletions packages/dev/s2-docs/src/VisualExampleClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,32 @@ export function VisualExampleClient({component, name, importSource, controls, ch
useEffect(() => {
// Find previous heading element.
let node: Element | null = ref.current;
while (node && node.parentElement?.tagName !== 'ARTICLE') {

// Search for the nearest heading by walking up the tree and checking previous siblings
while (node && node.tagName !== 'ARTICLE') {
// Check previous siblings
let sibling = node.previousElementSibling;
while (sibling) {
if (sibling instanceof HTMLHeadingElement) {
node = sibling;
break;
}
// Also check inside the sibling for headings
let headingInSibling = sibling.querySelector('h1, h2, h3, h4, h5, h6');
if (headingInSibling instanceof HTMLHeadingElement) {
node = headingInSibling;
break;
}
sibling = sibling.previousElementSibling;
}

if (node instanceof HTMLHeadingElement) {
break;
}

// Move up to parent
node = node.parentElement;
}
while (node && !(node instanceof HTMLHeadingElement)) {
node = node.previousElementSibling;
}

let id = node instanceof HTMLHeadingElement ? node.id : null;
if (id && location.hash === '#' + id) {
Expand Down
55 changes: 1 addition & 54 deletions packages/react-aria-components/stories/GridList.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -625,60 +625,6 @@ function GridListInModalPickerRender(props: ModalOverlayProps): JSX.Element {
);
}

export function GridListScrollIntoView() {
let items: {id: number, name: string}[] = [];
for (let i = 0; i < 100; i++) {
items.push({id: i, name: `Item ${i}`});
}

let list = useListData({
initialItems: items
});

const getElement = (id: number) => document.querySelector(`[data-key="${id}"]`) as HTMLElement;

const rowHeight = 25;

return (
<>
<div style={{height: 500, overflow: 'auto'}}>
<GridList
className={styles.menu}
selectionMode="multiple"
aria-label="virtualized listbox"
items={list.items}
style={{
position: 'relative',
overflow: 'hidden',
width: 100,
height: list.items.length * rowHeight
}}>
{item => (
<GridListItem
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
overflow: 'visible',
transform: `translateY(${item.id * rowHeight}px)`
}}>
{item.name}
</GridListItem>)}
</GridList>
</div>
<button onClick={() => getElement(40)?.scrollIntoView({block: 'start'})}>Scroll to item 40</button>
<button
tabIndex={0}
onKeyDown={() => {
getElement(70)?.focus();
}}>
Click, press ESC key focus item 70
</button>
</>
);
}

export let GridListInModalPicker: StoryObj<typeof GridListInModalPickerRender> = {
render: (args) => <GridListInModalPickerRender {...args} />,
parameters: {
Expand All @@ -689,3 +635,4 @@ export let GridListInModalPicker: StoryObj<typeof GridListInModalPickerRender> =
}
}
};

Loading