Skip to content

Migrate to async node measurement#212

Open
xavi160 wants to merge 8 commits intomainfrom
async-layout-measurement
Open

Migrate to async node measurement#212
xavi160 wants to merge 8 commits intomainfrom
async-layout-measurement

Conversation

@xavi160
Copy link
Copy Markdown
Collaborator

@xavi160 xavi160 commented Mar 20, 2026

Motivation

In other platforms (like react-native) node measurement is async . This PR does the prep work to support that.

Changes

  • Introduce new layoutAdapter option
  • Expose 2 bundled adapters for the DOM: defaultLayoutAdapter and getBoundingClientRectAdapter
  • Deprecate useGetBoundingClientRect option
  • Updated and fixed tests
  • Added test step to the PR workflow
  • Updated docs

xavi160 added 5 commits March 20, 2026 12:26
- Fixed jest setup to work with lodash-es using babel
…ion of useGetBoundingClientRect and introduce layoutAdapter for improved layout measurement
@xavi160 xavi160 requested a review from a team as a code owner March 20, 2026 11:50
@xavi160 xavi160 self-assigned this Mar 20, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 20, 2026

🦋 Changeset detected

Latest commit: ddd7217

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@noriginmedia/norigin-spatial-navigation-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

SMishra25
SMishra25 previously approved these changes Mar 23, 2026
Copy link
Copy Markdown

@SMishra25 SMishra25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

predikament
predikament previously approved these changes Mar 23, 2026
Copy link
Copy Markdown
Collaborator

@predikament predikament left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@predikament predikament added enhancement New feature or request documentation Improvements or additions to documentation labels Mar 23, 2026
asgvard
asgvard previously approved these changes Mar 23, 2026
Copy link
Copy Markdown
Collaborator

@asgvard asgvard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good 🚀

guilleccc
guilleccc previously approved these changes Mar 23, 2026
@oleksii-pylypenko
Copy link
Copy Markdown
Collaborator

Hey @xavi160, one concern here. Several internal calls (smartNavigate, setFocus, updateLayout) became async but aren't awaited by their callers (key event handler, addFocusable, recursive calls in smartNavigate).
This means navigation promises resolve before focus actually settles, and a throwing layoutAdapter.measureLayout would produce unhandled rejections. Also possible race conditions if user rapidly presses keys since multiple async navigations can run concurrently.

Comment on lines +15 to +27
private async tick(): Promise<void> {
await this.currentTask?.();
if (this.nextPriorityTasks.length > 0) {
this.currentTask = () =>
Promise.all(this.nextPriorityTasks.map((task) => task()));
this.nextPriorityTasks = [];
await this.tick();
} else {
this.currentTask = this.nextTask;
this.nextTask = undefined;
await this.tick();
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this.currentTask becomes undefined then we'll probably end up in an infinite loop.
I think we need to wrap await this.tick(); on line 25 with an if (this.currentTask) guard.

(component) =>
component.parentFocusKey === parentFocusKey &&
component.focusable &&
component.layoutUpdatedAt > threshold
Copy link
Copy Markdown
Collaborator

@oleksii-pylypenko oleksii-pylypenko Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be:

Suggested change
component.layoutUpdatedAt > threshold
component.layoutUpdatedAt <= threshold

Otherwise we are doing it backwards, updating layout for fresh elements and skipping the stale.


```typescript
setFocus(focusKey: string, focusDetails?: FocusDetails): void
setFocus(focusKey: string, focusDetails?: FocusDetails): Promise<void>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setFocus is still sync.

this.schedulePriority(async () => fn.bind(context)(...args));
}

schedule(task: Task) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, I'll fix it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants