Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

sortNodePosition throws when a sibling in enteringChildren has not mounted yet (Firefox: compareDocumentPosition: Argument 1 is not an object)

Closed Beginner friendly
#3,849 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
1/5
Estimated time
1-3 hours
Newbie friendliness
88/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript
Domain
frontend

Research direction

Start in packages/motion-dom/src/render/VisualElement.ts at sortNodePosition, then review the unit-level reproduction with two HTMLVisualElements where only one is mounted. Add coverage for sorting a mounted element against an unmounted sibling and confirm the comparator returns safely without throwing; the existing mounted behavior should remain unchanged.

Written by the indexing model from the issue text.

Description

Summary

VisualElement.sortNodePosition guards this.current but not other.current. When a child that mounts after its parent computes its stagger delay, calcChildStagger sorts parent.enteringChildren by DOM position; siblings that are registered but not mounted yet have current === null, and a.compareDocumentPosition(null) throws:

TypeError: Node.compareDocumentPosition: Argument 1 is not an object.   (Firefox)

Every child whose animateChanges throws never runs its initial → animate transition and stays at its initial values. In a Nuxt app the throw happens in a mounted hook during hydration and turns into the app's error page.

Why it only shows in Firefox

It depends on the argument order of Array.prototype.sort's comparator. SpiderMonkey's merge sort calls compare(earlier, later), so the already-mounted element is this and the unmounted sibling is other — the unguarded side. V8's insertion sort calls compare(pivot, existing) with the later (still unmounted) element as this, which hits the existing !this.current guard and returns 0. Same data, same code, different comparator order — which is why motiondivision/motion-vue#208 could not be reproduced in Chrome.

Where the unmounted siblings come from

motion-vue registers a child with its parent when the VisualElement is created, before it has mounted (motion-state.ts#L228), and animation-state.ts then calls calcChildStagger(parent.enteringChildren, …) for children mounting after the parent (animation-state.ts#L402-L405). The sort in motion-dom should not depend on every entry having an element, though — the guard for this.current already acknowledges that.

Code: packages/motion-dom/src/render/VisualElement.ts#L609-L621

Reproduction

Standalone page (motion-v 2.4.4 / motion-dom 13.4.4, Vue 3.5.43), full analysis and results in https://github.com/motiondivision/motion-vue/issues/208#issuecomment-5831915170. Open it in Firefox: 3 errors and 4 of 5 cards stuck at opacity: 0; Chromium: no errors.

At the unit level: two HTMLVisualElements, mount only one, mounted.sortNodePosition(unmounted) throws instead of returning 0.

Fix

Add !other.current || to the guard in sortNodePosition. PR follows.

Dominant language
TypeScript
Stars
34k
Forks
1.4k
Avg merge
23h 13m
Merged PRs (30d)
90

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from motiondivision/motion

All issues in motiondivision/motion

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.