const {openStory, row} = require(process.env.HOME + '/astryx/probe-kit/lib.cjs'); const PORT = 6157; async function open(story) { return openStory('chromium', story, { port: PORT, viewport: {width: 1000, height: 500}, ready: () => document.querySelector('nav[role="navigation"]') != null, launch: {headless: false}, }); } const instrument = page => page.evaluate(() => { window.__log = {focus: [], inert: [], t0: performance.now()}; const navs = [...document.querySelectorAll('nav[role="navigation"]')]; window.__name = el => el == null ? 'null' : el === document.body ? 'BODY' : `${el.tagName.toLowerCase()}[${(el.getAttribute('aria-label')||el.textContent||'').trim().slice(0,18)}]` + (navs.some(n => n.contains(el)) ? ' IN-NAV' : ' outside'); document.addEventListener('focusin', e => window.__log.focus.push({t: Math.round(performance.now()-window.__log.t0), el: window.__name(e.target)}), true); navs.forEach((nav, i) => new MutationObserver(() => { if (nav.hasAttribute('inert')) window.__log.inert.push({ nav: i, t: Math.round(performance.now()-window.__log.t0), active: window.__name(document.activeElement)}); }).observe(nav, {attributes: true, attributeFilter: ['inert']})); }); // ---- 1. no registered toggle: where does focus land? ----------------------- (async () => { { const {browser, page} = await open('scratch-sidenav3890--no-outside-toggle'); await instrument(page); await page.evaluate(() => { const nav = document.querySelector('nav[role="navigation"]'); nav.querySelector('a[href]').focus(); window.__log.t0 = performance.now(); document.querySelector('[data-testid="plain-toggle"]').click(); }); await page.waitForTimeout(350); const r = await page.evaluate(() => ({ log: window.__log, active: window.__name(document.activeElement), activeIsBody: document.activeElement === document.body, navW: document.querySelector('nav[role="navigation"]').getBoundingClientRect().width, inert: document.querySelector('nav[role="navigation"]').hasAttribute('inert'), })); console.log('=== 1. NO REGISTERED TOGGLE ===\n', JSON.stringify(r, null, 1)); await browser.close(); } // ---- 2. built-in button + collapsedWidth 0: can you get back? ------------ { const {browser, page} = await open('scratch-sidenav3890--built-in-button'); await instrument(page); const before = await page.evaluate(() => ({ buttons: [...document.querySelectorAll('button')].map(b => (b.getAttribute('aria-label')||'').slice(0,30)).filter(Boolean), })); await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find(x => /sidebar/i.test(x.getAttribute('aria-label')||'')); window.__log.t0 = performance.now(); b.click(); }); await page.waitForTimeout(350); const after = await page.evaluate(() => { const nav = document.querySelector('nav[role="navigation"]'); const reachable = [...document.querySelectorAll('button')] .filter(b => /sidebar/i.test(b.getAttribute('aria-label')||'')) .map(b => ({label: b.getAttribute('aria-label'), inNav: nav.contains(b), visible: b.checkVisibility({checkVisibilityCSS: true}), w: Math.round(b.getBoundingClientRect().width)})); return {navW: nav.getBoundingClientRect().width, inert: nav.hasAttribute('inert'), active: window.__name(document.activeElement), activeIsBody: document.activeElement === document.body, sidebarButtons: reachable, log: window.__log}; }); console.log('=== 2. BUILT-IN BUTTON + collapsedWidth 0 ===\n', JSON.stringify({before, after}, null, 1)); await browser.close(); } // ---- 3. two navs, one global registry ----------------------------------- { const {browser, page} = await open('scratch-sidenav3890--two-navs-shared'); await instrument(page); await page.evaluate(() => { // focus a link inside nav B (the SECOND nav), then collapse B only. const navs = [...document.querySelectorAll('nav[role="navigation"]')]; navs[1].querySelector('a[href]').focus(); window.__log.t0 = performance.now(); [...document.querySelectorAll('button')].find(b => (b.getAttribute('aria-label')||'') === 'toggle B').click(); }); await page.waitForTimeout(350); const r = await page.evaluate(() => { const navs = [...document.querySelectorAll('nav[role="navigation"]')]; return {log: window.__log, active: window.__name(document.activeElement), navWidths: navs.map(n => Math.round(n.getBoundingClientRect().width)), inerts: navs.map(n => n.hasAttribute('inert'))}; }); console.log('=== 3. TWO NAVS, GLOBAL REGISTRY (collapsed B, focus was in B) ===\n', JSON.stringify(r, null, 1)); await browser.close(); } // ---- 4. interrupt: re-expand mid-slide ---------------------------------- { const {browser, page} = await open('core-sidenav--hidden-collapse'); const click = () => page.evaluate(() => { const nav = document.querySelector('nav[role="navigation"]'); [...document.querySelectorAll('button')] .find(b => !nav.contains(b) && /sidebar/i.test(b.getAttribute('aria-label')||'')).click(); }); const read = () => page.evaluate(() => { const nav = document.querySelector('nav[role="navigation"]'); const layer = nav.firstElementChild; return {navW: Math.round(nav.getBoundingClientRect().width), layerX: Math.round(layer.getBoundingClientRect().x), inert: nav.hasAttribute('inert'), anims: document.getAnimations().filter(a => a.playState === 'running') .map(a => `${a.transitionProperty}@${a.effect.getTiming().duration}/${a.effect.getTiming().delay}`)}; }); await click(); await page.waitForTimeout(60); console.log('=== 4. INTERRUPT === mid-slide (60ms):', JSON.stringify(await read())); await click(); // re-expand mid-slide await page.waitForTimeout(20); console.log(' +20ms after re-expand:', JSON.stringify(await read())); await page.waitForTimeout(200); console.log(' settled:', JSON.stringify(await read())); const tab = await page.evaluate(() => { const nav = document.querySelector('nav[role="navigation"]'); return {inert: nav.hasAttribute('inert'), navW: Math.round(nav.getBoundingClientRect().width), linkVisible: nav.querySelector('a[href]').checkVisibility({checkVisibilityCSS: true})}; }); console.log(' after interrupt, nav usable:', JSON.stringify(tab)); await browser.close(); } })().catch(e => {console.error('PROBE FAILED', e); process.exit(1);});