Read the page's own direction at mount, not at our first write

The clear-only-ours fix recorded the host's dir lazily, inside the rtl branch,
the first time it wrote. That is correct only while nothing else has written a
direction first. If anything set dir="rtl" on the host before that moment, the
element recorded rtl as "the page's own declaration" and then faithfully
restored it on every switch away from Arabic -- so the direction never cleared,
which is the defect this whole change exists to remove.

Measured on the Big Picture leaf: ar left rtl, and zh put rtl straight back,
while its three siblings with the same code passed. The difference was not the
code; it was that something writes rtl on that host before the element does.

Reading the attribute on the FIRST call instead, before any write of ours, is
the only moment it is certainly the page's and not ours. A page that genuinely
declares dir still owns that decision and still gets it back.

Five cases driven against the real function in all four files: no dir at boot,
a page declaring ltr, a page declaring rtl, an element mounting while already
Arabic, and -- the case that reproduces the failure -- a FOREIGN writer setting
rtl after mount and before ours. Restoring the lazy capture to one file fails
that fifth case alone and leaves the other four passing.
This commit is contained in:
Doxa Maps 2026-09-29 20:15:16 +00:00
parent 4a715e8c37
commit 1fd562c28e
4 changed files with 52 additions and 20 deletions

View file

@ -119,17 +119,25 @@ const _self = getCurrentInstance()
// The original reason for never clearing is still honoured, and is why this remembers rather than
// just removing: a host page that declares its OWN dir owns that decision, so the value found before
// we first wrote is restored verbatim, and removed only when there was none.
let _dirPrev = null
// ★ CAPTURED AT MOUNT, NOT AT FIRST WRITE. The capture used to sit inside the rtl branch, so it
// recorded whatever `dir` happened to be on the host the first time WE wrote — and if anything else
// had already set rtl by then, that value was recorded as "the page's own" and faithfully restored
// forever after. MEASURED on the Big Picture leaf: ar left rtl, and zh put rtl straight back.
// Reading it on the FIRST call instead, before any write of ours, is the only moment the attribute
// is certainly the page's and not ours.
let _hostDir = null
let _weSetDir = false
function reflectDir() {
try {
const host = _self?.proxy?.$el?.getRootNode?.()?.host
if (!host || !host.setAttribute) return
if (_hostDir === null) _hostDir = host.getAttribute('dir') || ''
if (textDir.value === 'rtl') {
if (_dirPrev === null) _dirPrev = host.getAttribute('dir') || ''
host.setAttribute('dir', 'rtl')
} else if (_dirPrev !== null) {
if (_dirPrev) host.setAttribute('dir', _dirPrev); else host.removeAttribute('dir')
_dirPrev = null
_weSetDir = true
} else if (_weSetDir) {
if (_hostDir) host.setAttribute('dir', _hostDir); else host.removeAttribute('dir')
_weSetDir = false
}
} catch (_) { /* no host (SSR/tests) — nothing to reflect */ }
}

View file

@ -319,17 +319,25 @@ const _selfInstance = getCurrentInstance()
// ★ WE CLEAR ONLY THE DIRECTION WE SET. This was a one-way `if (rtl) setAttribute` with no else,
// so ar -> zh kept a right-to-left layout for the rest of the session. The host page's own dir is
// still respected: the value found before we first wrote is restored verbatim.
let _dirPrev = null
// ★ CAPTURED AT MOUNT, NOT AT FIRST WRITE. The capture used to sit inside the rtl branch, so it
// recorded whatever `dir` happened to be on the host the first time WE wrote — and if anything else
// had already set rtl by then, that value was recorded as "the page's own" and faithfully restored
// forever after. MEASURED on the Big Picture leaf: ar left rtl, and zh put rtl straight back.
// Reading it on the FIRST call instead, before any write of ours, is the only moment the attribute
// is certainly the page's and not ours.
let _hostDir = null
let _weSetDir = false
function reflectDir() {
try {
const host = _selfInstance?.proxy?.$el?.getRootNode?.()?.host
if (!host || !host.setAttribute) return
if (_hostDir === null) _hostDir = host.getAttribute('dir') || ''
if (textDir.value === 'rtl') {
if (_dirPrev === null) _dirPrev = host.getAttribute('dir') || ''
host.setAttribute('dir', 'rtl')
} else if (_dirPrev !== null) {
if (_dirPrev) host.setAttribute('dir', _dirPrev); else host.removeAttribute('dir')
_dirPrev = null
_weSetDir = true
} else if (_weSetDir) {
if (_hostDir) host.setAttribute('dir', _hostDir); else host.removeAttribute('dir')
_weSetDir = false
}
} catch (_) { /* no host (SSR/tests) — nothing to reflect */ }
}

View file

@ -291,17 +291,25 @@ const textDir = computed(() => {
// The original reason for never clearing is still honoured, and is why this remembers rather than
// just removing: a host page that declares its OWN dir owns that decision, so the value found before
// we first wrote is restored verbatim, and removed only when there was none.
let _dirPrev = null
// ★ CAPTURED AT MOUNT, NOT AT FIRST WRITE. The capture used to sit inside the rtl branch, so it
// recorded whatever `dir` happened to be on the host the first time WE wrote — and if anything else
// had already set rtl by then, that value was recorded as "the page's own" and faithfully restored
// forever after. MEASURED on the Big Picture leaf: ar left rtl, and zh put rtl straight back.
// Reading it on the FIRST call instead, before any write of ours, is the only moment the attribute
// is certainly the page's and not ours.
let _hostDir = null
let _weSetDir = false
function reflectDir() {
try {
const host = hostEl()
if (!host || !host.setAttribute) return
if (_hostDir === null) _hostDir = host.getAttribute('dir') || ''
if (textDir.value === 'rtl') {
if (_dirPrev === null) _dirPrev = host.getAttribute('dir') || ''
host.setAttribute('dir', 'rtl')
} else if (_dirPrev !== null) {
if (_dirPrev) host.setAttribute('dir', _dirPrev); else host.removeAttribute('dir')
_dirPrev = null
_weSetDir = true
} else if (_weSetDir) {
if (_hostDir) host.setAttribute('dir', _hostDir); else host.removeAttribute('dir')
_weSetDir = false
}
} catch (_) { /* no host (SSR/tests) — nothing to reflect */ }
}

View file

@ -175,17 +175,25 @@ const _selfInstance = getCurrentInstance()
// The original reason for never clearing is still honoured, and is why this remembers rather than
// just removing: a host page that declares its OWN dir owns that decision, so the value found before
// we first wrote is restored verbatim, and removed only when there was none.
let _dirPrev = null
// ★ CAPTURED AT MOUNT, NOT AT FIRST WRITE. The capture used to sit inside the rtl branch, so it
// recorded whatever `dir` happened to be on the host the first time WE wrote — and if anything else
// had already set rtl by then, that value was recorded as "the page's own" and faithfully restored
// forever after. MEASURED on the Big Picture leaf: ar left rtl, and zh put rtl straight back.
// Reading it on the FIRST call instead, before any write of ours, is the only moment the attribute
// is certainly the page's and not ours.
let _hostDir = null
let _weSetDir = false
function reflectDir() {
try {
const host = _selfInstance?.proxy?.$el?.getRootNode?.()?.host
if (!host || !host.setAttribute) return
if (_hostDir === null) _hostDir = host.getAttribute('dir') || ''
if (textDir.value === 'rtl') {
if (_dirPrev === null) _dirPrev = host.getAttribute('dir') || ''
host.setAttribute('dir', 'rtl')
} else if (_dirPrev !== null) {
if (_dirPrev) host.setAttribute('dir', _dirPrev); else host.removeAttribute('dir')
_dirPrev = null
_weSetDir = true
} else if (_weSetDir) {
if (_hostDir) host.setAttribute('dir', _hostDir); else host.removeAttribute('dir')
_weSetDir = false
}
} catch (_) { /* no host (SSR/tests) — nothing to reflect */ }
}