Merge branch 'rename': the kit takes its own prefix
commit f48fd5bcf86fbd6617093e334d24b4382698a23c · parent a24a232c5b · parent 8027c7aa3b
· browse this tree
Ximianth, 2026-10-10 06:09:11 UTC
diff --git a/packages/e2e/main.ts b/packages/e2e/main.ts index f23b4c4..cc6484a 100644 --- a/packages/e2e/main.ts +++ b/packages/e2e/main.ts @@ -9,9 +9,9 @@ import { Harness } from "./harness.ts"; import { ipDrive } from "./scenarios/ip-drive.ts"; import { lcDrive } from "./scenarios/lc-drive.ts"; import { mcDrive } from "./scenarios/mc-drive.ts"; -import { swDrive } from "./scenarios/sw-drive.ts"; +import { ykDrive } from "./scenarios/yk-button-drive.ts"; -const ALL: Scenario[] = [mcDrive, swDrive, ipDrive, lcDrive]; +const ALL: Scenario[] = [mcDrive, ykDrive, ipDrive, lcDrive]; async function main(): Promise<void> { const wanted = process.argv.slice(2); diff --git a/packages/e2e/scenarios/ip-drive.ts b/packages/e2e/scenarios/ip-drive.ts index 6dae682..d464d70 100644 --- a/packages/e2e/scenarios/ip-drive.ts +++ b/packages/e2e/scenarios/ip-drive.ts @@ -1,7 +1,7 @@ -// Drives the sw-infopop gallery entry with TRUSTED input via CDP: keys via +// Drives the yk-infopop gallery entry with TRUSTED input via CDP: keys via // Input.dispatchKeyEvent and real mouse movement via Input.dispatchMouseEvent // (synthetic pointerover can't prove real hover). Covers: keyboard i on every -// row flavor (sw-button's own claim, the plain-row/slider/aux data-info +// row flavor (yk-button's own claim, the plain-row/slider/aux data-info // fallback), the i toggle (second i on the same anchor dismisses; scope is one // keypress), hover raising the SAME content, pin precedence over hover (incl. // i upgrading a hover popup to pinned), dismissal (nav keys close AND navigate, @@ -16,9 +16,9 @@ import { openGallery } from "../gallery.ts"; // column's focused row and log, and the aria reflection on the current anchor. const STATE_JS = ` (() => { - const shell = document.querySelector("sw-gallery").shadowRoot; - const pop = shell.querySelector("sw-infopop"); - const col = shell.querySelector("swg-infopop-column")?.shadowRoot; + const shell = document.querySelector("yk-gallery").shadowRoot; + const pop = shell.querySelector("yk-infopop"); + const col = shell.querySelector("ykg-infopop-column")?.shadowRoot; const rect = pop.getBoundingClientRect(); const anchorRect = pop.anchor?.getBoundingClientRect() ?? null; return JSON.stringify({ @@ -71,12 +71,12 @@ async function run(ctx: ScenarioContext): Promise<void> { return JSON.parse(box as string); }; - const COL = "document.querySelector('sw-gallery').shadowRoot.querySelector('swg-infopop-column').shadowRoot"; + const COL = "document.querySelector('yk-gallery').shadowRoot.querySelector('ykg-infopop-column').shadowRoot"; await openGallery( ctx, - "#sw-infopop", - "document.readyState === 'complete' && !!document.querySelector('sw-gallery')?.shadowRoot?.querySelector('swg-infopop-column')", + "#yk-infopop", + "document.readyState === 'complete' && !!document.querySelector('yk-gallery')?.shadowRoot?.querySelector('ykg-infopop-column')", ); // Park the mouse in dead space so no hover state lingers between phases. @@ -99,8 +99,8 @@ async function run(ctx: ScenarioContext): Promise<void> { await cdp.key("i"); s = await st(); - check("i-on-sw-button-pins", s.mode === "pinned" && s.visible, JSON.stringify(s)); - check("i-on-sw-button-content", s.text.includes("an sw-button answers i itself"), s.text); + check("i-on-yk-button-pins", s.mode === "pinned" && s.visible, JSON.stringify(s)); + check("i-on-yk-button-content", s.text.includes("a yk-button answers i itself"), s.text); check("aria-reflection-wired", s.anchorAriaWired === true, JSON.stringify(s.anchorAriaWired)); check( "popup-below-anchor", @@ -112,13 +112,13 @@ async function run(ctx: ScenarioContext): Promise<void> { // The toggle: i again on the same anchor dismisses (no blink-reopen)... await cdp.key("i"); s = await st(); - check("i-toggles-off-sw-button", s.mode === "closed", JSON.stringify(s.mode)); + check("i-toggles-off-yk-button", s.mode === "closed", JSON.stringify(s.mode)); // ... and a fresh press raises it right back (suppression is one keypress). await cdp.key("i"); s = await st(); check( - "i-reopens-sw-button", - s.mode === "pinned" && s.text.includes("an sw-button answers i itself"), + "i-reopens-yk-button", + s.mode === "pinned" && s.text.includes("a yk-button answers i itself"), s.text, ); @@ -211,7 +211,7 @@ async function run(ctx: ScenarioContext): Promise<void> { s = await st(); check( "pin-for-precedence", - s.mode === "pinned" && s.text.includes("an sw-button answers i itself"), + s.mode === "pinned" && s.text.includes("a yk-button answers i itself"), s.text, ); pt = await centerOf(`${COL}.querySelector('[data-mc-row="survey"]')`); @@ -220,7 +220,7 @@ async function run(ctx: ScenarioContext): Promise<void> { s = await st(); check( "hover-never-displaces-pin", - s.mode === "pinned" && s.text.includes("an sw-button answers i itself"), + s.mode === "pinned" && s.text.includes("a yk-button answers i itself"), s.text, ); await shot(cdp, "pin-outranks-hover.png"); @@ -270,8 +270,8 @@ async function run(ctx: ScenarioContext): Promise<void> { check("pointerdown-closes", s.mode === "closed", JSON.stringify(s.mode)); // --- edges: clamping and the flip --- - const EDGES = "document.querySelector('sw-gallery').shadowRoot.querySelector('swg-infopop-edges').shadowRoot"; - pt = await centerOf(`${EDGES}.querySelectorAll('sw-button')[1]`); // far right + const EDGES = "document.querySelector('yk-gallery').shadowRoot.querySelector('ykg-infopop-edges').shadowRoot"; + pt = await centerOf(`${EDGES}.querySelectorAll('yk-button')[1]`); // far right await cdp.mouseMove(pt.x, pt.y); await sleep(500); s = await st(); @@ -290,7 +290,7 @@ async function run(ctx: ScenarioContext): Promise<void> { // then hover it -- the popup must open ABOVE the anchor. const targetH = await cdp.js(` (() => { - const el = ${EDGES}.querySelectorAll('sw-button')[0]; + const el = ${EDGES}.querySelectorAll('yk-button')[0]; return Math.round(el.getBoundingClientRect().bottom + 20); })() `); @@ -301,7 +301,7 @@ async function run(ctx: ScenarioContext): Promise<void> { mobile: false, }); await sleep(200); - pt = await centerOf(`${EDGES}.querySelectorAll('sw-button')[0]`); // far left + pt = await centerOf(`${EDGES}.querySelectorAll('yk-button')[0]`); // far left await cdp.mouseMove(pt.x, pt.y); await sleep(500); s = await st(); diff --git a/packages/e2e/scenarios/lc-drive.ts b/packages/e2e/scenarios/lc-drive.ts index 0168db7..58a65d7 100644 --- a/packages/e2e/scenarios/lc-drive.ts +++ b/packages/e2e/scenarios/lc-drive.ts @@ -1,5 +1,5 @@ -// Drives the sw-list gallery entry (the list region component composed into a -// megacolumn console; sw-live-list under it) with TRUSTED input via CDP. +// Drives the yk-list gallery entry (the list region component composed into a +// megacolumn console; yk-live-list under it) with TRUSTED input via CDP. // // The demos churn on their own 4s timers; the scenario STOPS those timers up // front and drives data churn via JS at chosen moments -- data churn is @@ -16,23 +16,23 @@ // the pointer-leave release), the churn lockout (an Enter landing right after // a fallback move is DROPPED; flows again after the window), instant prepend // under key burst (p), focus memory across Tab-away, the drain-to-empty + -// revive story, and the LIST NORMS, sw-list's own behavior: the header row +// revive story, and the LIST NORMS, yk-list's own behavior: the header row // (a nav stop whose focus washes the whole list region) and the add-row // (a dives straight into text entry -- the direct marking on a text row; -// Enter commits via the cancelable sw-list-add event; Escape abandons), and +// Enter commits via the cancelable yk-list-add event; Escape abandons), and // the LIST-SCOPED KEYS: t (back to the list's header) and n (past the list), -// claimed by the sw-list host element off the bubbling megacolumn-key dispatch +// claimed by the yk-list host element off the bubbling megacolumn-key dispatch // and answered with megacolumn-focus-request; outside the list's rows the -// letters are nobody's. Structural row ids are name-prefixed by sw-list +// letters are nobody's. Structural row ids are name-prefixed by yk-list // (name="queue": queue-header, queue-add). import { type Scenario, type ScenarioContext, shot } from "yukey-testkit/checks"; import { sleep } from "yukey-testkit/sleep"; import { openGallery } from "../gallery.ts"; -// demo(i): the i-th swg-livecolumn-demo (0 = refill, 1 = drain). +// demo(i): the i-th ykg-livecolumn-demo (0 = refill, 1 = drain). const demo = (i: number) => - `document.querySelector('sw-gallery').shadowRoot.querySelectorAll('swg-livecolumn-demo')[${i}]`; + `document.querySelector('yk-gallery').shadowRoot.querySelectorAll('ykg-livecolumn-demo')[${i}]`; // Full state of one demo column: every rendered rowslot with its phase-visible // attributes, the machine-visible focus (ring classes + aria), and the log line. @@ -55,18 +55,18 @@ const stateJs = (demoExpr: string) => ` focusWithin: container.classList.contains('mc-focus-within'), emptyFocus: container.classList.contains('mc-empty-focus'), textMode: container.classList.contains('mc-text-mode'), - inputValue: root.querySelector('.sw-list-add [data-mc-input]')?.value ?? null, - inputFocused: root.activeElement === root.querySelector('.sw-list-add [data-mc-input]'), + inputValue: root.querySelector('.yk-list-add [data-mc-input]')?.value ?? null, + inputFocused: root.activeElement === root.querySelector('.yk-list-add [data-mc-input]'), // The header-focus region wash: a computed background on the group wrapper // (driven by the :has() rule in the demo's stylesheet, nothing script-side). groupWashed: (() => { - const g = root.querySelector('sw-list'); + const g = root.querySelector('yk-list'); if (g === null) return null; const bg = getComputedStyle(g).backgroundColor; return bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent'; })(), - groupOutline: root.querySelector('sw-list') === null ? null - : getComputedStyle(root.querySelector('sw-list')).outlineStyle, + groupOutline: root.querySelector('yk-list') === null ? null + : getComputedStyle(root.querySelector('yk-list')).outlineStyle, rowFocused: root.querySelector('.mc-row-focused')?.dataset?.mcRow ?? root.querySelector('.mc-row-focused')?.closest('[data-mc-row]')?.dataset?.mcRow ?? null, auxFocused: root.querySelector('[data-mc-aux].mc-focused') !== null, @@ -109,8 +109,8 @@ async function run(ctx: ScenarioContext): Promise<void> { await openGallery( ctx, - "#sw-list", - "document.readyState === 'complete' && document.querySelector('sw-gallery')?.shadowRoot?.querySelectorAll('swg-livecolumn-demo')?.length === 2", + "#yk-list", + "document.readyState === 'complete' && document.querySelector('yk-gallery')?.shadowRoot?.querySelectorAll('ykg-livecolumn-demo')?.length === 2", ); // Determinism: stop both demos' own churn timers; the scenario churns by hand. @@ -141,8 +141,8 @@ async function run(ctx: ScenarioContext): Promise<void> { const first = s.dataIds[0] as string; // One down from the prepend row is the LIST HEADER ROW: a normal nav stop // that speaks for the region. While it holds focus the whole list washes - // (sw-list's own :has() rule); the focus treatment stays on the header - // itself -- the sw-list host draws no outline of its own. + // (yk-list's own :has() rule); the focus treatment stays on the header + // itself -- the yk-list host draws no outline of its own. await cdp.key("ArrowDown"); s = await st(); check( @@ -174,7 +174,7 @@ async function run(ctx: ScenarioContext): Promise<void> { // Enter on the aux removes the task; the row ghosts in place, sheds its aux, // STAYS focused (identity survives), and position snaps back to main. - // The row also FLASHES (sw-pressed) as instant acknowledgment: the fade to + // The row also FLASHES (yk-pressed) as instant acknowledgment: the fade to // ghost alone was too quiet to confirm the keystroke landed. The flash is // 140ms, so a MutationObserver armed before the keystroke catches it. await cdp.js(` @@ -182,9 +182,9 @@ async function run(ctx: ScenarioContext): Promise<void> { const root = ${demo(0)}.shadowRoot; window.__flashSeen = 0; window.__flashMo = new MutationObserver(muts => { - for (const m of muts) if (m.target.classList.contains('sw-pressed')) window.__flashSeen += 1; + for (const m of muts) if (m.target.classList.contains('yk-pressed')) window.__flashSeen += 1; }); - window.__flashMo.observe(root.querySelector('sw-live-list'), + window.__flashMo.observe(root.querySelector('yk-live-list'), { attributes: true, attributeFilter: ['class'], subtree: true }); })() `); @@ -377,7 +377,7 @@ async function run(ctx: ScenarioContext): Promise<void> { // have shrunk the list away from where the ghost was). const lbox = JSON.parse( (await cdp.js(` - (() => { const r = ${demo(0)}.shadowRoot.querySelector('sw-live-list').getBoundingClientRect(); + (() => { const r = ${demo(0)}.shadowRoot.querySelector('yk-live-list').getBoundingClientRect(); return JSON.stringify({x: r.x + r.width/2, y: r.y + Math.max(r.height/2, 4)}); })() `)) as string, ) as { x: number; y: number }; @@ -462,7 +462,7 @@ async function run(ctx: ScenarioContext): Promise<void> { // old global hover pin story must hold: a hovered ghost defers past every // timer, an insertion waits as a zero-height pending row, and pointer-leave // releases both. - const LIST = `${demo(0)}.shadowRoot.querySelector('sw-live-list')`; + const LIST = `${demo(0)}.shadowRoot.querySelector('yk-live-list')`; await cdp.js(`${LIST}.pointerSafety = 'freeze'`); s = await st(); const fkey = s.dataIds[0] as string; @@ -681,7 +681,7 @@ async function run(ctx: ScenarioContext): Promise<void> { check("committed-row-reachable-above-add-row", s1.rowFocused === newId, s1.rowFocused ?? ""); // --- phase 9: the list-scoped keys (t and n) ----------------------------- - // The t/n norm: sw-list wires it itself (one megacolumn-key listener on + // The t/n norm: yk-list wires it itself (one megacolumn-key listener on // the host element answering with megacolumn-focus-request), deliberately // NOT gold markings. Scope is DOM containment: within the list's rows t // returns to the header and n jumps past the list; outside it the letters @@ -745,27 +745,27 @@ async function run(ctx: ScenarioContext): Promise<void> { JSON.stringify([s1.textMode, s1.inputValue]), ); - // --- phase 10: the sw-list component surface ----------------------------- + // --- phase 10: the yk-list component surface ----------------------------- // Newly reachable since the list norms became a component (the // list-component stage): the group identity for AT, and the cancelable - // commit (an owner that preventDefaults sw-list-add refuses the commit; + // commit (an owner that preventDefaults yk-list-add refuses the commit; // the draft and text mode must survive so the user can fix the text). // The demo's own handler never refuses, and a listener on the same element // would run after it -- so the refusal is arranged upstream: a one-shot // capture listener on the container cancels AND stops propagation (from - // sw-list's side that IS a refusing owner: sw-list-add came back + // yk-list's side that IS a refusing owner: yk-list-add came back // defaultPrevented and nobody appended). await cdp.js(` ${demo(1)}.shadowRoot.querySelector('[data-mc-container]') - .addEventListener('sw-list-add', e => { e.preventDefault(); e.stopPropagation(); }, + .addEventListener('yk-list-add', e => { e.preventDefault(); e.stopPropagation(); }, { once: true, capture: true }) `); const role = await cdp.js(` - (() => { const l = ${demo(1)}.shadowRoot.querySelector('sw-list'); + (() => { const l = ${demo(1)}.shadowRoot.querySelector('yk-list'); return JSON.stringify([l.getAttribute('role'), l.getAttribute('aria-label')]); })() `); check( - "sw-list-group-identity", + "yk-list-group-identity", JSON.stringify(JSON.parse(role as string)) === JSON.stringify(["group", "task queue (live)"]), String(role), ); diff --git a/packages/e2e/scenarios/mc-drive.ts b/packages/e2e/scenarios/mc-drive.ts index a0a7cb4..2dca2bc 100644 --- a/packages/e2e/scenarios/mc-drive.ts +++ b/packages/e2e/scenarios/mc-drive.ts @@ -10,8 +10,8 @@ import { openGallery } from "../gallery.ts"; // real focus, its aria-activedescendant, ring positions, text mode, and the log line. const STATE_JS = ` (() => { - const shell = document.querySelector("sw-gallery").shadowRoot; - const demos = [...shell.querySelectorAll("swg-megacolumn-demo")]; + const shell = document.querySelector("yk-gallery").shadowRoot; + const demos = [...shell.querySelectorAll("ykg-megacolumn-demo")]; const active = shell.activeElement; const out = { realFocus: null, columns: [] }; for (const demo of demos) { @@ -59,8 +59,8 @@ interface McState { // position, and the page scrollers that must NOT move. const SCROLL_JS = ` (() => { - const shell = document.querySelector("sw-gallery").shadowRoot; - const demo = shell.querySelector("swg-megacolumn-scroll-demo"); + const shell = document.querySelector("yk-gallery").shadowRoot; + const demo = shell.querySelector("ykg-megacolumn-scroll-demo"); const sr = demo.shadowRoot; const box = sr.querySelector("[data-mc-container]"); const focused = sr.querySelector(".mc-row-focused"); @@ -97,7 +97,7 @@ async function run(ctx: ScenarioContext): Promise<void> { await openGallery( ctx, "#megacolumn", - "document.readyState === 'complete' && !!document.querySelector('sw-gallery')?.shadowRoot?.querySelector('swg-megacolumn-demo')", + "document.readyState === 'complete' && !!document.querySelector('yk-gallery')?.shadowRoot?.querySelector('ykg-megacolumn-demo')", ); // The gallery seats initial focus in the entry's first tab stop on open: @@ -129,7 +129,7 @@ async function run(ctx: ScenarioContext): Promise<void> { // of 12 = "six nav links" broke when the gallery grew); count the nav links // live and add the demo's own stops with margin. const navLinks = (await cdp.js( - "document.querySelector('sw-gallery').shadowRoot.querySelectorAll('nav a').length", + "document.querySelector('yk-gallery').shadowRoot.querySelectorAll('nav a').length", )) as number; for (let i = 0; i < navLinks + 12; i++) { await cdp.key("Tab"); @@ -323,8 +323,8 @@ async function run(ctx: ScenarioContext): Promise<void> { // megacolumn-key. A tap produces a click, so this is also the touch story. const portCenter = async (selector: string): Promise<{ x: number; y: number }> => { const v = await cdp.js(` - (() => { const el = document.querySelector("sw-gallery").shadowRoot - .querySelector("swg-megacolumn-demo").shadowRoot.querySelector('${selector}'); + (() => { const el = document.querySelector("yk-gallery").shadowRoot + .querySelector("ykg-megacolumn-demo").shadowRoot.querySelector('${selector}'); el.scrollIntoView({block: "nearest", behavior: "instant"}); const r = el.getBoundingClientRect(); return JSON.stringify({x: r.x + r.width/2, y: r.y + r.height/2}); })() diff --git a/packages/e2e/scenarios/sw-drive.ts b/packages/e2e/scenarios/yk-button-drive.ts similarity index 87% rename from packages/e2e/scenarios/sw-drive.ts rename to packages/e2e/scenarios/yk-button-drive.ts index c7c85fe..003030a 100644 --- a/packages/e2e/scenarios/sw-drive.ts +++ b/packages/e2e/scenarios/yk-button-drive.ts @@ -1,9 +1,9 @@ -// Drives the sw-button gallery entry with TRUSTED key events via CDP, +// Drives the yk-button gallery entry with TRUSTED key events via CDP, // reading state through the shadow roots between steps. Covers: standalone // tab traversal + native activation + focused hotkey/i, the in-column claims // (Enter/Space, column-scoped hotkeys, x/Delete on aux, i, disabled // claim-and-decline), the two-column scoping proof, and activation feedback -// (the sw-held / sw-pressed classes; the 120ms flash is caught with a +// (the yk-held / yk-pressed classes; the 120ms flash is caught with a // MutationObserver installed before the keystroke, not with a screenshot race). import { type Scenario, type ScenarioContext, shot } from "yukey-testkit/checks"; @@ -14,10 +14,10 @@ import { openGallery } from "../gallery.ts"; // each demo's log line, and the columns' focused rows. const STATE_JS = ` (() => { - const shell = document.querySelector("sw-gallery").shadowRoot; + const shell = document.querySelector("yk-gallery").shadowRoot; const out = { realFocus: null, standalone: null, columns: [] }; const active = shell.activeElement; - const sa = shell.querySelector("swg-button-standalone"); + const sa = shell.querySelector("ykg-button-standalone"); if (sa) { const sr = sa.shadowRoot; out.standalone = { @@ -27,7 +27,7 @@ const STATE_JS = ` }; if (sa === active) out.realFocus = "standalone/" + (sr.activeElement?.label ?? "?"); } - for (const col of shell.querySelectorAll("swg-button-column")) { + for (const col of shell.querySelectorAll("ykg-button-column")) { const sr = col.shadowRoot; const container = sr.querySelector("[data-mc-container]"); const label = col.getAttribute("label"); @@ -45,7 +45,7 @@ const STATE_JS = ` })() `; -interface SwColumn { +interface YkColumn { label: string; rowFocused: string | null; focusWithin: boolean; @@ -54,19 +54,19 @@ interface SwColumn { cargoRows: number; } -interface SwState { +interface YkState { realFocus: string | null; standalone: { log: string; logIsInfo: boolean; focusedLabel: string | null }; - columns: SwColumn[]; + columns: YkColumn[]; } const READY_JS = - "document.readyState === 'complete' && !!document.querySelector('sw-gallery')?.shadowRoot?.querySelector('swg-button-standalone')"; + "document.readyState === 'complete' && !!document.querySelector('yk-gallery')?.shadowRoot?.querySelector('ykg-button-standalone')"; async function run(ctx: ScenarioContext): Promise<void> { const { cdp, checks } = ctx; const check = (name: string, cond: boolean, detail = "") => checks.check(name, cond, detail); - const st = async (): Promise<SwState> => JSON.parse((await cdp.js(STATE_JS)) as string); + const st = async (): Promise<YkState> => JSON.parse((await cdp.js(STATE_JS)) as string); const centerOf = async (jsElExpr: string): Promise<{ x: number; y: number }> => { const box = await cdp.js(` (() => { @@ -77,11 +77,11 @@ async function run(ctx: ScenarioContext): Promise<void> { return JSON.parse(box as string); }; - await openGallery(ctx, "#sw-button", READY_JS); + await openGallery(ctx, "#yk-button", READY_JS); // --- standalone variant --- // The gallery seats initial focus in the entry's first tab stop on open: - // for #sw-button that is the first standalone button. (Seating happens a + // for #yk-button that is the first standalone button. (Seating happens a // frame after render; poll briefly.) let landed = false; let s = await st(); @@ -156,9 +156,9 @@ async function run(ctx: ScenarioContext): Promise<void> { ); // Mouse click on the dock button (trusted click via CDP). - const SA_ROOT = "document.querySelector(\"sw-gallery\").shadowRoot.querySelector(\"swg-button-standalone\")"; + const SA_ROOT = "document.querySelector(\"yk-gallery\").shadowRoot.querySelector(\"ykg-button-standalone\")"; let pt = await centerOf( - `[...${SA_ROOT}.shadowRoot.querySelectorAll("sw-button")].find(x => x.label === "Dock at station")`, + `[...${SA_ROOT}.shadowRoot.querySelectorAll("yk-button")].find(x => x.label === "Dock at station")`, ); await cdp.click(pt.x, pt.y); s = await st(); @@ -177,7 +177,7 @@ async function run(ctx: ScenarioContext): Promise<void> { } } check("tab-into-ops-console", landed, JSON.stringify(s.realFocus)); - const ops = (v: SwState): SwColumn => v.columns[0] as SwColumn; + const ops = (v: YkState): YkColumn => v.columns[0] as YkColumn; check("ops-first-row-dock", ops(s).rowFocused === "dock", JSON.stringify(ops(s))); // Enter on the focused button row. @@ -221,7 +221,7 @@ async function run(ctx: ScenarioContext): Promise<void> { await cdp.key("ArrowDown"); s = await st(); check("disabled-row-focusable", ops(s).rowFocused === "purge", JSON.stringify(ops(s))); - const MAIN_SCROLL = "document.querySelector('sw-gallery').shadowRoot.querySelector('main').scrollTop"; + const MAIN_SCROLL = "document.querySelector('yk-gallery').shadowRoot.querySelector('main').scrollTop"; const before = await cdp.js(MAIN_SCROLL); await cdp.key(" "); s = await st(); @@ -269,13 +269,13 @@ async function run(ctx: ScenarioContext): Promise<void> { JSON.stringify(ops(s)), ); - // A trusted mouse click on a row-mode sw-button: exactly ONE activation. + // A trusted mouse click on a row-mode yk-button: exactly ONE activation. // The controller synthesizes Enter from unclaimed row clicks (pointer - // activation for plain rows); sw-button claims its own clicks so the + // activation for plain rows); yk-button claims its own clicks so the // synthesis skips it -- a broken dedup would log two activations here. - const OPS_BEACON = `document.querySelector("sw-gallery").shadowRoot - .querySelector("swg-button-column").shadowRoot - .querySelector('[data-mc-row="beacon"] sw-button')`; + const OPS_BEACON = `document.querySelector("yk-gallery").shadowRoot + .querySelector("ykg-button-column").shadowRoot + .querySelector('[data-mc-row="beacon"] yk-button')`; await cdp.js( `window.__acts = 0; (${OPS_BEACON}).addEventListener('click', () => { window.__acts += 1 })`, ); @@ -290,8 +290,8 @@ async function run(ctx: ScenarioContext): Promise<void> { await cdp.key("Tab"); s = await st(); check("tab-into-port", s.realFocus === "port console/container", JSON.stringify(s.realFocus)); - const port = (v: SwState): SwColumn => v.columns[1] as SwColumn; - const starboard = (v: SwState): SwColumn => v.columns[2] as SwColumn; + const port = (v: YkState): YkColumn => v.columns[1] as YkColumn; + const starboard = (v: YkState): YkColumn => v.columns[2] as YkColumn; // The focused row's OWN hotkey does nothing: not a jump (already here), // not an activation (double-tap must not out-power single-tap). await cdp.key("d"); // focus sits on port's dock row @@ -373,16 +373,16 @@ async function run(ctx: ScenarioContext): Promise<void> { check("scroll-memory-not-on-dock", ops(s).rowFocused !== "dock", JSON.stringify(ops(s))); const DOCK_VIS = ` (() => { - const m = document.querySelector('sw-gallery').shadowRoot.querySelector('main'); - const row = document.querySelector('sw-gallery').shadowRoot - .querySelector('swg-button-column').shadowRoot + const m = document.querySelector('yk-gallery').shadowRoot.querySelector('main'); + const row = document.querySelector('yk-gallery').shadowRoot + .querySelector('ykg-button-column').shadowRoot .querySelector('[data-mc-row="dock"]'); const mr = m.getBoundingClientRect(), rr = row.getBoundingClientRect(); return JSON.stringify({scrollTop: m.scrollTop, visible: rr.bottom > mr.top && rr.top < mr.bottom}); })()`; await cdp.js( - "const m = document.querySelector('sw-gallery').shadowRoot.querySelector('main'); m.scrollTop = m.scrollHeight", + "const m = document.querySelector('yk-gallery').shadowRoot.querySelector('main'); m.scrollTop = m.scrollHeight", ); const visBefore = JSON.parse((await cdp.js(DOCK_VIS)) as string) as { scrollTop: number; @@ -404,7 +404,7 @@ async function run(ctx: ScenarioContext): Promise<void> { await shot(cdp, "hotkey-jump.png"); await cdp.cmd("Emulation.clearDeviceMetricsOverride"); - // --- activation feedback: the sw-held / sw-pressed classes --- + // --- activation feedback: the yk-held / yk-pressed classes --- // Reload to reset the demos; initial focus re-seats on the first standalone button. await cdp.cmd("Page.reload", { ignoreCache: true }); for (let i = 0; i < 50; i++) { @@ -421,14 +421,14 @@ async function run(ctx: ScenarioContext): Promise<void> { // keystroke, so a 120ms flash cannot slip between CDP roundtrips. // Each arm disconnects the previous observer: stale observers otherwise // increment whatever __flash is current when their callbacks run. - // find: a JS expression yielding the sw-button host to watch. + // find: a JS expression yielding the yk-button host to watch. const armFlash = async (find: string): Promise<void> => { await cdp.js(` (() => { window.__flash?.obs.disconnect(); const b = ${find}; const obs = new MutationObserver(() => { - if (b.classList.contains("sw-pressed")) window.__flash.count += 1; + if (b.classList.contains("yk-pressed")) window.__flash.count += 1; }); obs.observe(b, { attributes: true, attributeFilter: ["class"] }); window.__flash = { el: b, count: 0, obs }; @@ -444,7 +444,7 @@ async function run(ctx: ScenarioContext): Promise<void> { }; const flashCleared = async (): Promise<boolean> => { for (let i = 0; i < 30; i++) { - if ((await cdp.js("!window.__flash.el.classList.contains('sw-pressed')")) === true) { + if ((await cdp.js("!window.__flash.el.classList.contains('yk-pressed')")) === true) { return true; } await sleep(20); @@ -453,9 +453,9 @@ async function run(ctx: ScenarioContext): Promise<void> { }; const SA_BUTTON = (label: string) => - `[...document.querySelector("sw-gallery").shadowRoot - .querySelector("swg-button-standalone").shadowRoot - .querySelectorAll("sw-button")].find(x => x.label === "${label}")`; + `[...document.querySelector("yk-gallery").shadowRoot + .querySelector("ykg-button-standalone").shadowRoot + .querySelectorAll("yk-button")].find(x => x.label === "${label}")`; // Keyboard activation flashes: trusted Enter on the focused standalone button. await armFlash(SA_BUTTON("Dock at station")); @@ -473,19 +473,19 @@ async function run(ctx: ScenarioContext): Promise<void> { // Held under a trusted pointer: press without release, look, release. pt = await centerOf(SA_BUTTON("Dock at station")); await cdp.mouseDown(pt.x, pt.y); - const held = await cdp.js(`(${SA_BUTTON("Dock at station")}).classList.contains('sw-held')`); + const held = await cdp.js(`(${SA_BUTTON("Dock at station")}).classList.contains('yk-held')`); check("held-while-pointer-down", held === true); // The held look IS the flash look, and it sits still: the one screenshot // a 120ms transient can't reliably give us. await shot(cdp, "press-flash.png"); await cdp.mouseUp(pt.x, pt.y); - const released = await cdp.js(`!(${SA_BUTTON("Dock at station")}).classList.contains('sw-held')`); + const released = await cdp.js(`!(${SA_BUTTON("Dock at station")}).classList.contains('yk-held')`); check("held-releases-on-pointer-up", released === true); // The release click flashes Dock; let that settle before the disabled check // so no mutation from it is in flight when the observer moves. for (let i = 0; i < 30; i++) { if ( - (await cdp.js(`(${SA_BUTTON("Dock at station")}).classList.contains('sw-pressed')`)) !== true + (await cdp.js(`(${SA_BUTTON("Dock at station")}).classList.contains('yk-pressed')`)) !== true ) { break; } @@ -497,7 +497,7 @@ async function run(ctx: ScenarioContext): Promise<void> { await armFlash(SA_BUTTON("self destruct")); await cdp.mouseDown(pt.x, pt.y); const heldDisabled = await cdp.js( - `(${SA_BUTTON("self destruct")}).classList.contains('sw-held')`, + `(${SA_BUTTON("self destruct")}).classList.contains('yk-held')`, ); await cdp.mouseUp(pt.x, pt.y); check("disabled-never-held", heldDisabled !== true); @@ -519,9 +519,9 @@ async function run(ctx: ScenarioContext): Promise<void> { } check("feedback-tab-into-ops", landed, JSON.stringify(s.realFocus)); const COL_BUTTON = (row: string) => - `document.querySelector("sw-gallery").shadowRoot - .querySelector("swg-button-column").shadowRoot - .querySelector('[data-mc-row="${row}"] sw-button')`; + `document.querySelector("yk-gallery").shadowRoot + .querySelector("ykg-button-column").shadowRoot + .querySelector('[data-mc-row="${row}"] yk-button')`; await armFlash(COL_BUTTON("dock")); await cdp.key("Enter"); check("row-flash-on-trusted-enter", await flashSeen()); @@ -541,7 +541,7 @@ async function run(ctx: ScenarioContext): Promise<void> { for (let i = 0; i < 50; i++) { if ( (await cdp.js( - "document.readyState === 'complete' && !!document.querySelector('sw-gallery')?.shadowRoot?.querySelector('swg-megacolumn-demo')", + "document.readyState === 'complete' && !!document.querySelector('yk-gallery')?.shadowRoot?.querySelector('ykg-megacolumn-demo')", )) === true ) { break; @@ -551,8 +551,8 @@ async function run(ctx: ScenarioContext): Promise<void> { landed = false; for (let i = 0; i < 20; i++) { if ( - (await cdp.js(`document.querySelector("sw-gallery").shadowRoot - .querySelector("swg-megacolumn-demo").shadowRoot + (await cdp.js(`document.querySelector("yk-gallery").shadowRoot + .querySelector("ykg-megacolumn-demo").shadowRoot .querySelector("[data-mc-container]").classList.contains("mc-focus-within")`)) === true ) { landed = true; @@ -561,12 +561,12 @@ async function run(ctx: ScenarioContext): Promise<void> { await sleep(50); } check("plain-rows-focus-seated", landed); - await armFlash(`document.querySelector("sw-gallery").shadowRoot - .querySelector("swg-megacolumn-demo").shadowRoot + await armFlash(`document.querySelector("yk-gallery").shadowRoot + .querySelector("ykg-megacolumn-demo").shadowRoot .querySelector('[data-mc-row="beacon"]')`); await cdp.key("Enter"); check("plain-row-flash-on-trusted-enter", await flashSeen()); check("plain-row-flash-clears", await flashCleared()); } -export const swDrive: Scenario = { name: "sw", run }; +export const ykDrive: Scenario = { name: "yk", run }; diff --git a/packages/yukey/README.md b/packages/yukey/README.md index 3655aab..d211c2f 100644 --- a/packages/yukey/README.md +++ b/packages/yukey/README.md @@ -24,14 +24,14 @@ Use Every module is importable by its file name: ```ts -import "yukey/sw-button"; // registers the custom element +import "yukey/yk-button"; // registers the custom element import { MegacolumnController } from "yukey/megacolumn-controller"; import { installTokens } from "yukey/tokens"; installTokens(); // once, at startup: the design tokens into :root ``` -Components (`sw-button`, `sw-list`, `sw-live-list`, `sw-infopop`) register themselves on import. +Components (`yk-button`, `yk-list`, `yk-live-list`, `yk-infopop`) register themselves on import. Mechanisms (`megacolumn-controller`, `tab-cycle`, `focus-stack`, `press-flash`, and so on) are plain exports. The gallery (`yukey/gallery/main`) is the living documentation: every component, every variant, driven by the real thing. diff --git a/packages/yukey/package.json b/packages/yukey/package.json index 705c093..3f297da 100644 --- a/packages/yukey/package.json +++ b/packages/yukey/package.json @@ -4,29 +4,29 @@ "description": "a ui framework for the keyboard lover: Lit web components where the keyboard is on par with the mouse", "license": "Apache-2.0 OR MIT", "sideEffects": [ - "./src/sw-button.ts", - "./src/sw-infopop.ts", - "./src/sw-list.ts", - "./src/sw-live-list.ts", - "./src/gallery/sw-gallery.ts", + "./src/yk-button.ts", + "./src/yk-infopop.ts", + "./src/yk-list.ts", + "./src/yk-live-list.ts", + "./src/gallery/yk-gallery.ts", "./src/gallery/main.ts", "./src/megacolumn.gallery.ts", - "./src/sw-button.gallery.ts", - "./src/sw-infopop.gallery.ts", - "./src/sw-list.gallery.ts", - "./src/sw-live-list.gallery.ts", + "./src/yk-button.gallery.ts", + "./src/yk-infopop.gallery.ts", + "./src/yk-list.gallery.ts", + "./src/yk-live-list.gallery.ts", "./src/tokens.gallery.ts", - "./dist/sw-button.js", - "./dist/sw-infopop.js", - "./dist/sw-list.js", - "./dist/sw-live-list.js", - "./dist/gallery/sw-gallery.js", + "./dist/yk-button.js", + "./dist/yk-infopop.js", + "./dist/yk-list.js", + "./dist/yk-live-list.js", + "./dist/gallery/yk-gallery.js", "./dist/gallery/main.js", "./dist/megacolumn.gallery.js", - "./dist/sw-button.gallery.js", - "./dist/sw-infopop.gallery.js", - "./dist/sw-list.gallery.js", - "./dist/sw-live-list.gallery.js", + "./dist/yk-button.gallery.js", + "./dist/yk-infopop.gallery.js", + "./dist/yk-list.gallery.js", + "./dist/yk-live-list.gallery.js", "./dist/tokens.gallery.js" ], "type": "module", diff --git a/packages/yukey/src/adopt-into-root.ts b/packages/yukey/src/adopt-into-root.ts index 2ad1ec9..9955dfb 100644 --- a/packages/yukey/src/adopt-into-root.ts +++ b/packages/yukey/src/adopt-into-root.ts @@ -1,5 +1,5 @@ // Stylesheet adoption for components that render into their OWNER's tree. -// Megacolumn-composed components (sw-live-list mega mode, sw-list) render light-DOM +// Megacolumn-composed components (yk-live-list mega mode, yk-list) render light-DOM // rows -- the megacolumn scan and aria-activedescendant cannot cross shadow // boundaries -- so their styles must reach the owner's root, not a shadow root of // their own. This appends a sheet to the root's adopted stylesheets, once per root. @@ -8,7 +8,7 @@ // belongs to the owner. Where constructed stylesheets are unsupported (jsdom), // fall back to one marked <style> tag -- the same fallback shape lit itself uses; // `marker` is the attribute name that makes the fallback idempotent (one per -// adopter class, e.g. "data-sw-live-list"). +// adopter class, e.g. "data-yk-live-list"). import type { CSSResult } from "lit"; diff --git a/packages/yukey/src/focus-stack.ts b/packages/yukey/src/focus-stack.ts index e768222..4e67aab 100644 --- a/packages/yukey/src/focus-stack.ts +++ b/packages/yukey/src/focus-stack.ts @@ -99,7 +99,7 @@ export class FocusStack { } // The end of the line for pops: the page shell's one predictable focus home - // (the root surface -- sw-app points this at its map instance). + // (its root surface). setFallback(fallback: () => HTMLElement | null): void { this.#fallback = fallback; } diff --git a/packages/yukey/src/gallery/entry.ts b/packages/yukey/src/gallery/entry.ts index 9933ab1..bcfa1db 100644 --- a/packages/yukey/src/gallery/entry.ts +++ b/packages/yukey/src/gallery/entry.ts @@ -24,7 +24,7 @@ export interface GalleryVariant { } export interface GalleryEntry { - // Hash-route slug; conventionally the element tag name (e.g. "sw-live-list"). + // Hash-route slug; conventionally the element tag name (e.g. "yk-live-list"). id: string; title: string; // What the component is for, in a sentence or two. This is living documentation: diff --git a/packages/yukey/src/gallery/main.ts b/packages/yukey/src/gallery/main.ts index 5a08eb8..226ce27 100644 --- a/packages/yukey/src/gallery/main.ts +++ b/packages/yukey/src/gallery/main.ts @@ -1,6 +1,6 @@ // Gallery entry point (esbuild bundles this to static/gallery.js; gallery.html hosts it). -// Registering <sw-gallery> is all that's needed; it pulls the entry registry in turn. +// Registering <yk-gallery> is all that's needed; it pulls the entry registry in turn. import { installTokens } from "../tokens.js"; -import "./sw-gallery.js"; +import "./yk-gallery.js"; installTokens(); diff --git a/packages/yukey/src/gallery/registry.ts b/packages/yukey/src/gallery/registry.ts index 72744a7..338c97c 100644 --- a/packages/yukey/src/gallery/registry.ts +++ b/packages/yukey/src/gallery/registry.ts @@ -3,18 +3,18 @@ // (See entry.ts for the convention itself.) import megacolumn from "../megacolumn.gallery.js"; -import swButton from "../sw-button.gallery.js"; -import swInfopop from "../sw-infopop.gallery.js"; -import swList from "../sw-list.gallery.js"; -import swLiveList from "../sw-live-list.gallery.js"; import tokens from "../tokens.gallery.js"; +import ykButton from "../yk-button.gallery.js"; +import ykInfopop from "../yk-infopop.gallery.js"; +import ykList from "../yk-list.gallery.js"; +import ykLiveList from "../yk-live-list.gallery.js"; import type { GalleryEntry } from "./entry.js"; export const entries: GalleryEntry[] = [ megacolumn, - swButton, - swInfopop, - swList, - swLiveList, + ykButton, + ykInfopop, + ykList, + ykLiveList, tokens, ]; diff --git a/packages/yukey/src/gallery/sw-gallery.ts b/packages/yukey/src/gallery/yk-gallery.ts similarity index 70% rename from packages/yukey/src/gallery/sw-gallery.ts rename to packages/yukey/src/gallery/yk-gallery.ts index 8e3bbb8..8be2534 100644 --- a/packages/yukey/src/gallery/sw-gallery.ts +++ b/packages/yukey/src/gallery/yk-gallery.ts @@ -1,91 +1,91 @@ // The component gallery shell: nav of registered entries on the left, the selected -// entry's variants on the right. Hash routing (#sw-live-list) so entries are +// entry's variants on the right. Hash routing (#yk-live-list) so entries are // linkable and reload survives. Styled entirely from the design tokens: the shell // is the token vocabulary's first real consumer, and doubles as a proof of it. import { css, html, LitElement, type PropertyValues, type TemplateResult } from "lit"; import { customElement, state } from "lit/decorators.js"; -import "../sw-infopop.js"; +import "../yk-infopop.js"; import { deepTabbables, installTabContainment } from "../tab-cycle.js"; import type { GalleryEntry } from "./entry.js"; import { entries } from "./registry.js"; -@customElement("sw-gallery") -export class SwGallery extends LitElement { +@customElement("yk-gallery") +export class YkGallery extends LitElement { static styles = css` :host { display: grid; grid-template-columns: 220px 1fr; height: 100%; - font: var(--sw-type-body) var(--sw-font); - color: var(--sw-text); - background: var(--sw-bg-void); + font: var(--yk-type-body) var(--yk-font); + color: var(--yk-text); + background: var(--yk-bg-void); } nav { - border-right: 1px solid var(--sw-border-dim); - padding: var(--sw-space-md) 0; + border-right: 1px solid var(--yk-border-dim); + padding: var(--yk-space-md) 0; overflow-y: auto; } nav .head { - padding: 0 var(--sw-space-lg) var(--sw-space-md); - color: var(--sw-text-dim); - letter-spacing: var(--sw-tracking-wide); + padding: 0 var(--yk-space-lg) var(--yk-space-md); + color: var(--yk-text-dim); + letter-spacing: var(--yk-tracking-wide); text-transform: uppercase; - font-size: var(--sw-type-small); + font-size: var(--yk-type-small); } nav a { display: block; - min-height: var(--sw-hit-min-h); + min-height: var(--yk-hit-min-h); box-sizing: border-box; - padding: var(--sw-space-xs) var(--sw-space-lg); - color: var(--sw-accent-bright); + padding: var(--yk-space-xs) var(--yk-space-lg); + color: var(--yk-accent-bright); text-decoration: none; } nav a:hover { - color: var(--sw-text-bright); - background: var(--sw-wash-hover); + color: var(--yk-text-bright); + background: var(--yk-wash-hover); } nav a.current { - color: var(--sw-text-bright); - background: var(--sw-wash-selected); + color: var(--yk-text-bright); + background: var(--yk-wash-selected); } nav a:focus-visible { - outline: var(--sw-focus-ring); - outline-offset: calc(-1 * var(--sw-focus-ring-width)); + outline: var(--yk-focus-ring); + outline-offset: calc(-1 * var(--yk-focus-ring-width)); } main { - padding: var(--sw-space-xl) 28px; + padding: var(--yk-space-xl) 28px; overflow-y: auto; } main h1 { - font-size: var(--sw-type-head); - color: var(--sw-text-bright); - letter-spacing: var(--sw-tracking); - margin: 0 0 var(--sw-space-sm); + font-size: var(--yk-type-head); + color: var(--yk-text-bright); + letter-spacing: var(--yk-tracking); + margin: 0 0 var(--yk-space-sm); } .description { max-width: 70ch; - color: var(--sw-text-hi); - margin-bottom: var(--sw-space-xl); + color: var(--yk-text-hi); + margin-bottom: var(--yk-space-xl); } .variant { - margin-bottom: var(--sw-space-xxl); + margin-bottom: var(--yk-space-xxl); } .variant .caption { max-width: 70ch; - color: var(--sw-text-dim); - margin-bottom: var(--sw-space-sm); + color: var(--yk-text-dim); + margin-bottom: var(--yk-space-sm); } .variant .stage { - border: 1px solid var(--sw-border-dim); - padding: var(--sw-space-lg); + border: 1px solid var(--yk-border-dim); + padding: var(--yk-space-lg); max-width: 560px; } .variant .stage.wide { max-width: none; } .quiet { - color: var(--sw-text-faint); + color: var(--yk-text-faint); } `; @@ -126,7 +126,7 @@ export class SwGallery extends LitElement { const current = entries.find((e) => e.id === this.currentId); return html` <nav> - <div class="head">starweave gallery</div> + <div class="head">yukey gallery</div> ${ entries.map( (e) => @@ -137,7 +137,7 @@ export class SwGallery extends LitElement { } </nav> <main>${current ? this.renderEntry(current) : this.renderIndex()}</main> - <sw-infopop></sw-infopop> + <yk-infopop></yk-infopop> `; } @@ -161,7 +161,7 @@ export class SwGallery extends LitElement { return html` <h1>component gallery</h1> <div class="description"> - Living documentation and dev harness for the starweave component library. + Living documentation and dev harness for yukey. Pick a component from the nav. To add one: write a <span class="quiet">foo.gallery.ts</span> beside the component and register it in <span class="quiet">gallery/registry.ts</span>. diff --git a/packages/yukey/src/infopop-place.ts b/packages/yukey/src/infopop-place.ts index 8170eb9..f8b3880 100644 --- a/packages/yukey/src/infopop-place.ts +++ b/packages/yukey/src/infopop-place.ts @@ -1,6 +1,6 @@ // The infopop placement rule: pure geometry, no DOM types, no measurement. // Given the anchor's rectangle, the popup's size, and the viewport size, decide -// where the popup goes. sw-infopop.ts feeds it measured values and applies the result. +// where the popup goes. yk-infopop.ts feeds it measured values and applies the result. // // The rule (deterministic on purpose; simple beats clever for a transient surface): // diff --git a/packages/yukey/src/megacolumn-controller.test.ts b/packages/yukey/src/megacolumn-controller.test.ts index 86c1853..5c6a6b0 100644 --- a/packages/yukey/src/megacolumn-controller.test.ts +++ b/packages/yukey/src/megacolumn-controller.test.ts @@ -13,7 +13,7 @@ import { MegacolumnController, type MegacolumnKeyDetail, } from "./megacolumn-controller.js"; -import type { SwInfoDetail } from "./sw-info.js"; +import type { YkInfoDetail } from "./yk-info.js"; // jsdom has no layout and implements no scrollIntoView; the controller calls it // on every focus-identity change (logical focus must never sit off-screen). @@ -479,7 +479,7 @@ describe("pointer activation (click synthesizes Enter)", () => { // The lockout protects keystrokes already in flight when focus moved under // them; a click carries its target in its coordinates and its mousedown just // resolved the aim. (Moving-target clicks are the geometry owner's problem: - // sw-live-list's guard stops them in the capture phase before this synthesis.) + // yk-live-list's guard stops them in the capture phase before this synthesis.) const { root, render, ctl, key } = makeRig(threeRows); key("ArrowDown"); // -> b render(`<div data-mc-row="a">alpha</div><div data-mc-row="c">gamma</div>`); // b vanishes @@ -1246,7 +1246,7 @@ describe("column-scoped hotkey markings (data-mc-hotkey)", () => { <div data-mc-row="atlas" data-mc-hotkey="i"><span data-mc-main>atlas</span></div> `); let infos = 0; - container.addEventListener("sw-info", () => { + container.addEventListener("yk-info", () => { infos += 1; }); key("i"); @@ -1263,7 +1263,7 @@ describe("column-scoped hotkey markings (data-mc-hotkey)", () => { <div data-mc-row="atlas" data-mc-hotkey="i" data-mc-hotkey-direct><span data-mc-main>atlas</span></div> `); let infos = 0; - container.addEventListener("sw-info", () => { + container.addEventListener("yk-info", () => { infos += 1; }); key("i"); // nobody claims either dispatch @@ -1310,13 +1310,13 @@ describe("column-scoped hotkey markings (data-mc-hotkey)", () => { }); }); describe("the i fallback for [data-info] carriers", () => { - it("answers an unclaimed i with sw-info from the row's data-info", () => { + it("answers an unclaimed i with yk-info from the row's data-info", () => { const { root, container, key } = makeRig( `<div data-mc-row="a" data-info="alpha is a placeholder row">alpha</div>`, ); - const got: SwInfoDetail[] = []; - container.addEventListener("sw-info", (e) => { - got.push((e as CustomEvent<SwInfoDetail>).detail); + const got: YkInfoDetail[] = []; + container.addEventListener("yk-info", (e) => { + got.push((e as CustomEvent<YkInfoDetail>).detail); }); const e = key("i"); expect(got).toEqual([ @@ -1333,7 +1333,7 @@ describe("the i fallback for [data-info] carriers", () => { if ((e as CustomEvent<MegacolumnKeyDetail>).detail.key === "i") e.preventDefault(); }); let infos = 0; - container.addEventListener("sw-info", () => { + container.addEventListener("yk-info", () => { infos += 1; }); key("i"); @@ -1347,9 +1347,9 @@ describe("the i fallback for [data-info] carriers", () => { <span data-mc-aux data-info="the x's story">x</span> </div> `); - const got: SwInfoDetail[] = []; - container.addEventListener("sw-info", (e) => { - got.push((e as CustomEvent<SwInfoDetail>).detail); + const got: YkInfoDetail[] = []; + container.addEventListener("yk-info", (e) => { + got.push((e as CustomEvent<YkInfoDetail>).detail); }); key("ArrowRight"); // onto the aux control key("i"); @@ -1363,8 +1363,8 @@ describe("the i fallback for [data-info] carriers", () => { it("shifted I answers too", () => { const { container, key } = makeRig(`<div data-mc-row="a" data-info="story">alpha</div>`); const got: string[] = []; - container.addEventListener("sw-info", (e) => { - got.push((e as CustomEvent<SwInfoDetail>).detail.content); + container.addEventListener("yk-info", (e) => { + got.push((e as CustomEvent<YkInfoDetail>).detail.content); }); key("I", { shiftKey: true }); expect(got).toEqual(["story"]); @@ -1373,7 +1373,7 @@ describe("the i fallback for [data-info] carriers", () => { it("i on a row without data-info stays unconsumed and dispatches nothing", () => { const { container, key } = makeRig(threeRows); let infos = 0; - container.addEventListener("sw-info", () => { + container.addEventListener("yk-info", () => { infos += 1; }); const e = key("i"); @@ -1387,7 +1387,7 @@ describe("the i fallback for [data-info] carriers", () => { const { container, key } = makeRig(threeRows); container.dataset.info = "the container's own story"; let infos = 0; - container.addEventListener("sw-info", () => { + container.addEventListener("yk-info", () => { infos += 1; }); const e = key("i"); diff --git a/packages/yukey/src/megacolumn-controller.ts b/packages/yukey/src/megacolumn-controller.ts index 0b7ee16..2d1a2d5 100644 --- a/packages/yukey/src/megacolumn-controller.ts +++ b/packages/yukey/src/megacolumn-controller.ts @@ -63,17 +63,17 @@ // is cheap to cancel, so just give the order; a text dive is // undone by Escape, nothing committed). If reaching // for this makes you think twice, that is the design working. -// [data-info="<content>"] the info norm's carrier attribute (SSOT: sw-info.ts). +// [data-info="<content>"] the info norm's carrier attribute (SSOT: yk-info.ts). // When a forwarded i comes back unclaimed, the container // answers for the nearest carrier from the focused control -// up to the row (inclusive), dispatching sw-info itself -- +// up to the row (inclusive), dispatching yk-info itself -- // so plain rows get the i norm with no widget code. // // Same-shadow-tree note: aria-activedescendant can only reference an id in the same // tree as the container, and this binding enforces that shape by construction -- // rows are found with querySelectorAll under the container, which cannot cross // shadow boundaries or reach slotted light-DOM content. Render rows in the host's -// own shadow root (render-props style, as sw-live-list does); slotting rows in from +// own shadow root (render-props style, as yk-live-list does); slotting rows in from // outside is not supported and scans as an empty column. // // Keys the machine forwards are delivered as a cancelable `megacolumn-key` @@ -89,7 +89,7 @@ // or aux -- and any tap, since touch produces clicks) dispatches the same // megacolumn-key with key "Enter", so Enter, Space, and click are one event and // hosts serve every input modality through one handler. A widget that already -// activates on its own native click (sw-button in row mode) claims the click +// activates on its own native click (yk-button in row mode) claims the click // with preventDefault -- the same claim idiom keys use -- and the synthesis // skips claimed clicks, so nothing double-fires. Clicks on [data-mc-input] // never synthesize (the real-focus dive in onFocusin owns that story), and a @@ -132,7 +132,7 @@ // The churn lockout: when a scan finds the focused row gone and the machine's // fallback moves focus to a neighbor, that move was nobody's keystroke -- and a // decisive key already in flight must not land on the surprise neighbor -// (the guidelines' no-race-conditions rule; the same reasoning as sw-live-list's +// (the guidelines' no-race-conditions rule; the same reasoning as yk-live-list's // click guard, transposed to focus). For FALLBACK_LOCKOUT_MS after an involuntary // focus move, `forward` and `enter-text` routings are consumed without dispatch; // navigation keys stay live, because reorienting is exactly what the user needs -- @@ -163,7 +163,7 @@ import type { ReactiveController, ReactiveControllerHost } from "lit"; import { css } from "lit"; import { MegacolumnNav, type NavRow, type RowPosition } from "./megacolumn-nav.js"; -import { dispatchSwInfo } from "./sw-info.js"; +import { dispatchYkInfo } from "./yk-info.js"; export interface MegacolumnKeyDetail { key: string; @@ -505,13 +505,13 @@ export class MegacolumnController implements ReactiveController { } // Still unresolved and the key is i: the container answers for plain // [data-info] carriers (the info norm's "almost any element"; widgets - // with their own i handling, like sw-button, claimed above and never + // with their own i handling, like yk-button, claimed above and never // reach this). Nearest carrier from the focused control up to the row, // inclusive: a row must not inherit info from outside itself. if (!consumed && e.key.toLowerCase() === "i") { const carrier = this.infoCarrier(control, row); if (carrier !== null) { - dispatchSwInfo(carrier, carrier.dataset.info ?? ""); + dispatchYkInfo(carrier, carrier.dataset.info ?? ""); claimed = true; consumed = true; } @@ -588,7 +588,7 @@ export class MegacolumnController implements ReactiveController { // keystrokes from surprise focus moves, while a click names its target // geometrically and its own mousedown just resolved the aim. Moving-target // clicks are the geometry owner's problem, stopped upstream in the capture - // phase (sw-live-list's click guard) before this bubble listener can hear them. + // phase (yk-live-list's click guard) before this bubble listener can hear them. private onClick(e: MouseEvent): void { if (e.defaultPrevented) return; // a widget activated on this click already and claimed it const target = e.target; @@ -708,32 +708,32 @@ export const megacolumnStyles = css` outline: none; /* the ring is drawn on the focused row instead */ } /* Navigation scrolls (scrollIntoView from applyFocusState) land the focused row - --sw-scroll-peek inside the scrollport edge, keeping about one neighbor row + --yk-scroll-peek inside the scrollport edge, keeping about one neighbor row visible beyond it: the user sees the list continue (or truly end) past where they are. scroll-margin only affects programmatic scrolls into view; free scrolling and PageUp/Down are untouched. */ [data-mc-row] { - scroll-margin-block: var(--sw-scroll-peek); + scroll-margin-block: var(--yk-scroll-peek); } [data-mc-container].mc-empty-focus { - outline: var(--sw-focus-ring); - outline-offset: calc(-1 * var(--sw-focus-ring-width)); + outline: var(--yk-focus-ring); + outline-offset: calc(-1 * var(--yk-focus-ring-width)); } .mc-focus-within [data-mc-row].mc-row-focused { - outline: var(--sw-focus-ring); - outline-offset: calc(-1 * var(--sw-focus-ring-width)); + outline: var(--yk-focus-ring); + outline-offset: calc(-1 * var(--yk-focus-ring-width)); } .mc-focus-within [data-mc-aux].mc-focused { - outline: var(--sw-focus-ring); - outline-offset: calc(-1 * var(--sw-focus-ring-width)); + outline: var(--yk-focus-ring); + outline-offset: calc(-1 * var(--yk-focus-ring-width)); } /* Focus memory stays quietly visible while the container is unfocused, so the user can see where focus will resume before tabbing back in. */ .mc-row-focused { - background: var(--sw-wash-faint); + background: var(--yk-wash-faint); } .mc-focus-within .mc-row-focused { - background: var(--sw-wash-selected); + background: var(--yk-wash-selected); } /* Activation feedback for plain rows: a host that acts on a forwarded activation calls flashPress on the event's target -- the row's control @@ -742,8 +742,8 @@ export const megacolumnStyles = css` "what did I just do", inside the row the ring outlines. Equal specificity, later in the sheet: the flash outranks the focus washes above, while the ring (an outline) coexists untouched. */ - [data-mc-row].sw-pressed, - [data-mc-row] [data-mc-main].sw-pressed { - background: var(--sw-wash-pressed); + [data-mc-row].yk-pressed, + [data-mc-row] [data-mc-main].yk-pressed { + background: var(--yk-wash-pressed); } `; diff --git a/packages/yukey/src/megacolumn.gallery.ts b/packages/yukey/src/megacolumn.gallery.ts index 4db74d3..90ab863 100644 --- a/packages/yukey/src/megacolumn.gallery.ts +++ b/packages/yukey/src/megacolumn.gallery.ts @@ -34,8 +34,8 @@ const systemActions = new Map<string, string>([ // One demo megacolumn: activatable rows, a slider row, a two-control cargo list, // a text-entry row, a disabled row, and a live-churning task queue -- with section // gaps between them that focus jumps across. -@customElement("swg-megacolumn-demo") -export class SwgMegacolumnDemo extends LitElement { +@customElement("ykg-megacolumn-demo") +export class YkgMegacolumnDemo extends LitElement { static styles = [ megacolumnStyles, css` @@ -43,22 +43,22 @@ export class SwgMegacolumnDemo extends LitElement { display: block; } [data-mc-container] { - border: 1px solid var(--sw-border-dim); - padding: var(--sw-space-md); + border: 1px solid var(--yk-border-dim); + padding: var(--yk-space-md); } .title { - color: var(--sw-text-dim); - font-size: var(--sw-type-small); - letter-spacing: var(--sw-tracking-wide); + color: var(--yk-text-dim); + font-size: var(--yk-type-small); + letter-spacing: var(--yk-tracking-wide); text-transform: uppercase; - margin-bottom: var(--sw-space-sm); + margin-bottom: var(--yk-space-sm); } .section { - color: var(--sw-text-faint); - font-size: var(--sw-type-micro); - letter-spacing: var(--sw-tracking-wide); + color: var(--yk-text-faint); + font-size: var(--yk-type-micro); + letter-spacing: var(--yk-tracking-wide); text-transform: uppercase; - margin: var(--sw-space-xl) 0 var(--sw-space-xs); + margin: var(--yk-space-xl) 0 var(--yk-space-xs); } .section:first-of-type { margin-top: 0; @@ -66,47 +66,47 @@ export class SwgMegacolumnDemo extends LitElement { [data-mc-row] { display: flex; align-items: center; - gap: var(--sw-space-sm); - min-height: var(--sw-hit-min-h); - padding: 2px var(--sw-space-sm); + gap: var(--yk-space-sm); + min-height: var(--yk-hit-min-h); + padding: 2px var(--yk-space-sm); } [data-mc-row].offline { - color: var(--sw-text-faint); + color: var(--yk-text-faint); } .grow { flex: 1; } .count { - color: var(--sw-text-faint); + color: var(--yk-text-faint); } /* The hotkey-marking norm's gold letter, marked by hand on these plain - rows (sw-button renders its own; plain rows have no widget to do it). */ + rows (yk-button renders its own; plain rows have no widget to do it). */ .hk { - color: var(--sw-hotkey-accent); + color: var(--yk-hotkey-accent); } .meter { flex: 1; /* the slider's whole visible affordance: at least the indicator token tall (the ant rule covers the visible part of a control) */ - height: var(--sw-hit-indicator); - border: 1px solid var(--sw-border-dim); + height: var(--yk-hit-indicator); + border: 1px solid var(--yk-border-dim); position: relative; } .meter .fill { position: absolute; inset: 0 auto 0 0; - background: var(--sw-accent); + background: var(--yk-accent); } .pct { - color: var(--sw-text-hi); + color: var(--yk-text-hi); width: 4ch; text-align: right; } [data-mc-aux] { - color: var(--sw-danger); + color: var(--yk-danger); border: 1px solid transparent; - min-width: var(--sw-hit-min-w); - min-height: var(--sw-hit-min-h); + min-width: var(--yk-hit-min-w); + min-height: var(--yk-hit-min-h); display: inline-flex; align-items: center; justify-content: center; @@ -114,21 +114,21 @@ export class SwgMegacolumnDemo extends LitElement { } input[data-mc-input] { flex: 1; - background: var(--sw-bg-well); - border: 1px solid var(--sw-border-dim); - color: var(--sw-text-bright); + background: var(--yk-bg-well); + border: 1px solid var(--yk-border-dim); + color: var(--yk-text-bright); font: inherit; - min-height: var(--sw-hit-min-h); + min-height: var(--yk-hit-min-h); box-sizing: border-box; - padding: 0 var(--sw-space-sm); + padding: 0 var(--yk-space-sm); } .mc-text-mode input[data-mc-input] { - border-color: var(--sw-accent); + border-color: var(--yk-accent); } .log { - margin-top: var(--sw-space-md); - color: var(--sw-good); - font-size: var(--sw-type-ui); + margin-top: var(--yk-space-md); + color: var(--yk-good); + font-size: var(--yk-type-ui); min-height: 1.4em; } `, @@ -384,8 +384,8 @@ function relayLabel(nn: string): TemplateResult { return html`${label.slice(0, at)}<span class="hk">${hk}</span>${label.slice(at + 1)}`; } -@customElement("swg-megacolumn-scroll-demo") -export class SwgMegacolumnScrollDemo extends LitElement { +@customElement("ykg-megacolumn-scroll-demo") +export class YkgMegacolumnScrollDemo extends LitElement { static styles = [ megacolumnStyles, css` @@ -393,11 +393,11 @@ export class SwgMegacolumnScrollDemo extends LitElement { display: block; } .title { - color: var(--sw-text-dim); - font-size: var(--sw-type-small); - letter-spacing: var(--sw-tracking-wide); + color: var(--yk-text-dim); + font-size: var(--yk-type-small); + letter-spacing: var(--yk-tracking-wide); text-transform: uppercase; - margin-bottom: var(--sw-space-sm); + margin-bottom: var(--yk-space-sm); } /* The bounded scrollport under test: the container IS the scroller, so the focused element's own box answers native scrolling keys too. @@ -407,21 +407,21 @@ export class SwgMegacolumnScrollDemo extends LitElement { [data-mc-container] { height: 240px; overflow-y: auto; - border: 1px solid var(--sw-border); + border: 1px solid var(--yk-border); } [data-mc-row] { display: flex; align-items: center; - min-height: var(--sw-hit-min-h); - padding: 2px var(--sw-space-sm); + min-height: var(--yk-hit-min-h); + padding: 2px var(--yk-space-sm); } .hk { - color: var(--sw-hotkey-accent); + color: var(--yk-hotkey-accent); } .log { - margin-top: var(--sw-space-md); - color: var(--sw-good); - font-size: var(--sw-type-ui); + margin-top: var(--yk-space-md); + color: var(--yk-good); + font-size: var(--yk-type-ui); min-height: 1.4em; } `, @@ -494,8 +494,8 @@ const entry: GalleryEntry = { render: () => html` <div style="display:flex; gap:24px; align-items:flex-start;"> - <swg-megacolumn-demo style="flex:1" label="port console"></swg-megacolumn-demo> - <swg-megacolumn-demo style="flex:1" label="starboard console"></swg-megacolumn-demo> + <ykg-megacolumn-demo style="flex:1" label="port console"></ykg-megacolumn-demo> + <ykg-megacolumn-demo style="flex:1" label="starboard console"></ykg-megacolumn-demo> </div> `, }, @@ -503,7 +503,7 @@ const entry: GalleryEntry = { caption: "Focus memory under churn: the task queue loses its head and grows a tail every few seconds. " + "Focus a task row and watch focus fall to the nearest surviving neighbor when the row vanishes; " + "the rest of the column is unbothered.", - render: () => html`<swg-megacolumn-demo label="ops console" churn></swg-megacolumn-demo>`, + render: () => html`<ykg-megacolumn-demo label="ops console" churn></ykg-megacolumn-demo>`, }, { caption: @@ -513,7 +513,7 @@ const entry: GalleryEntry = { + "the gold digits jump -- 1/0/2/8 land on relays 01/10/20/28, crossing most of the scroller in one keystroke, " + "the ring arriving in view every time. " + "Enter pings a relay; a few rows carry data-info, so i works mid-scroll.", - render: () => html`<swg-megacolumn-scroll-demo></swg-megacolumn-scroll-demo>`, + render: () => html`<ykg-megacolumn-scroll-demo></ykg-megacolumn-scroll-demo>`, }, ], }; diff --git a/packages/yukey/src/press-flash.test.ts b/packages/yukey/src/press-flash.test.ts index 14d9e94..fcebb29 100644 --- a/packages/yukey/src/press-flash.test.ts +++ b/packages/yukey/src/press-flash.test.ts @@ -2,7 +2,7 @@ // // Tests for the shared activation-flash helper (press-flash.ts). The full // consumer behavior (pointer-held state, disabled suppression, re-arm) is -// exercised through sw-button in sw-button.test.ts; this file pins the helper's +// exercised through yk-button in yk-button.test.ts; this file pins the helper's // own contract: cascade-resolved duration and the fail-closed resolution error. import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -12,8 +12,8 @@ import { installTokens, tokenGroups } from "./tokens.js"; installTokens(); const PRESS_MS = ((): number => { - const t = tokenGroups.flatMap((g) => g.tokens).find((t) => t.name === "--sw-press-ms"); - if (t === undefined) throw new Error("--sw-press-ms token missing"); + const t = tokenGroups.flatMap((g) => g.tokens).find((t) => t.name === "--yk-press-ms"); + if (t === undefined) throw new Error("--yk-press-ms token missing"); return Number.parseFloat(t.value); })(); @@ -30,20 +30,20 @@ describe("press-flash", () => { const el = document.createElement("div"); document.body.append(el); flashPress(el); - expect(el.classList.contains("sw-pressed")).toBe(true); + expect(el.classList.contains("yk-pressed")).toBe(true); vi.advanceTimersByTime(PRESS_MS); - expect(el.classList.contains("sw-pressed")).toBe(false); + expect(el.classList.contains("yk-pressed")).toBe(false); }); it("resolves seconds-denominated overrides too", () => { const el = document.createElement("div"); - el.style.setProperty("--sw-press-ms", "0.3s"); + el.style.setProperty("--yk-press-ms", "0.3s"); document.body.append(el); expect(pressMs(el)).toBe(300); }); it("fails loudly when the duration cannot resolve", () => { const detached = document.createElement("div"); // no cascade to read from - expect(() => pressMs(detached)).toThrow(/--sw-press-ms did not resolve/); + expect(() => pressMs(detached)).toThrow(/--yk-press-ms did not resolve/); }); }); diff --git a/packages/yukey/src/press-flash.ts b/packages/yukey/src/press-flash.ts index 7d84403..0db8626 100644 --- a/packages/yukey/src/press-flash.ts +++ b/packages/yukey/src/press-flash.ts @@ -1,28 +1,28 @@ -// Activation feedback, shared across widgets: the "sw-pressed" flash class that -// acknowledges a successful activation for --sw-press-ms, so keyboard triggers +// Activation feedback, shared across widgets: the "yk-pressed" flash class that +// acknowledges a successful activation for --yk-press-ms, so keyboard triggers // (Enter/Space, hotkeys) are as visible as a mouse press. The duration is one // system-wide design token, read from the live cascade at flash time -- so an // accessibility mode can slow every flash by restyling one custom property. // // Consumers and their class carriers: -// - sw-button flashes its host element (and manages its own pointer-held -// "sw-held" class beside it); its styles render both. +// - yk-button flashes its host element (and manages its own pointer-held +// "yk-held" class beside it); its styles render both. // - Megacolumn hosts with plain activatable rows call flashPress(rowElement) -// when they act on a forwarded activation; the row-flavored .sw-pressed +// when they act on a forwarded activation; the row-flavored .yk-pressed // rendering lives in megacolumnStyles, beside the focus washes it must outrank. -export const PRESSED_CLASS = "sw-pressed"; +export const PRESSED_CLASS = "yk-pressed"; // The flash duration, resolved from the cascade at el. Throws when the property // does not resolve (tokens not installed, or a detached element): a feedback // system that silently picks its own duration would hide exactly the // misconfiguration it exists to surface. export function pressMs(el: Element): number { - const raw = getComputedStyle(el).getPropertyValue("--sw-press-ms").trim(); + const raw = getComputedStyle(el).getPropertyValue("--yk-press-ms").trim(); const m = /^(\d+(?:\.\d+)?)(m?s)$/.exec(raw); if (m === null) { throw new Error( - `press-flash: --sw-press-ms did not resolve on <${el.tagName.toLowerCase()}> ` + `press-flash: --yk-press-ms did not resolve on <${el.tagName.toLowerCase()}> ` + `(got "${raw}"); are the design tokens installed?`, ); } @@ -32,7 +32,7 @@ export function pressMs(el: Element): number { const timers = new WeakMap<HTMLElement, number>(); -// Show the pressed look on el for --sw-press-ms. A re-flash while one is still +// Show the pressed look on el for --yk-press-ms. A re-flash while one is still // showing re-arms the timer, so rapid activations read as sustained activity // instead of getting cut short by the first flash's expiry. export function flashPress(el: HTMLElement): void { diff --git a/packages/yukey/src/tab-cycle.ts b/packages/yukey/src/tab-cycle.ts index 6d7cb8e..e43c250 100644 --- a/packages/yukey/src/tab-cycle.ts +++ b/packages/yukey/src/tab-cycle.ts @@ -11,7 +11,7 @@ // megacolumn containers and native buttons, all visible by construction, and // staying layout-free keeps the walk cheap and jsdom-testable. aria-hidden // subtrees are skipped: components here hide closed content that way -// (sw-live-list's collapsing rows), and a tab stop invisible to AT must not be +// (yk-live-list's collapsing rows), and a tab stop invisible to AT must not be // a tab stop at all. function isTabbable(el: Element): boolean { diff --git a/packages/yukey/src/tokens.gallery.ts b/packages/yukey/src/tokens.gallery.ts index ec6c03e..db9207a 100644 --- a/packages/yukey/src/tokens.gallery.ts +++ b/packages/yukey/src/tokens.gallery.ts @@ -7,52 +7,52 @@ import { css, html, LitElement, type TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; import type { GalleryEntry } from "./gallery/entry.js"; -import "./sw-button.js"; +import "./yk-button.js"; import { type TokenDef, tokenGroups } from "./tokens.js"; // One demo element, four faces (palette / spacing / type / focus): the variants // below pick a face. A single element keeps the styles in one block. -@customElement("swg-tokens") -export class SwgTokens extends LitElement { +@customElement("ykg-tokens") +export class YkgTokens extends LitElement { static styles = css` :host { display: block; - font: var(--sw-type-body) var(--sw-font); - color: var(--sw-text); + font: var(--yk-type-body) var(--yk-font); + color: var(--yk-text); } .group { - margin-bottom: var(--sw-space-xl); + margin-bottom: var(--yk-space-xl); } .group:last-child { margin-bottom: 0; } .group-title { - color: var(--sw-text-dim); - letter-spacing: var(--sw-tracking-wide); + color: var(--yk-text-dim); + letter-spacing: var(--yk-tracking-wide); text-transform: uppercase; - font-size: var(--sw-type-small); - border-bottom: 1px solid var(--sw-border-dim); - padding-bottom: var(--sw-space-xs); - margin-bottom: var(--sw-space-xs); + font-size: var(--yk-type-small); + border-bottom: 1px solid var(--yk-border-dim); + padding-bottom: var(--yk-space-xs); + margin-bottom: var(--yk-space-xs); } .group-about { - color: var(--sw-text-faint); + color: var(--yk-text-faint); max-width: 70ch; - margin-bottom: var(--sw-space-md); + margin-bottom: var(--yk-space-md); } /* palette rows: swatch | name | value | role */ .trow { display: grid; grid-template-columns: 72px 220px minmax(120px, 320px) 1fr; - gap: var(--sw-space-md); + gap: var(--yk-space-md); align-items: center; min-height: 28px; - margin-bottom: var(--sw-space-xs); + margin-bottom: var(--yk-space-xs); } .swatch { height: 28px; - border: 1px solid var(--sw-border-dim); + border: 1px solid var(--yk-border-dim); /* checkerboard underlay so translucent washes read as translucent */ background: repeating-conic-gradient(#2a3448 0 25%, #141c2c 0 50%) 0 0 / 14px 14px; @@ -61,42 +61,42 @@ export class SwgTokens extends LitElement { height: 100%; } .tname { - color: var(--sw-text-hi); + color: var(--yk-text-hi); user-select: all; /* one click selects the whole name, ready to copy */ } .tvalue { - color: var(--sw-text-faint); - font-size: var(--sw-type-small); + color: var(--yk-text-faint); + font-size: var(--yk-type-small); overflow-wrap: anywhere; } .tabout { - color: var(--sw-text-dim); + color: var(--yk-text-dim); } /* spacing & hit-target bars */ .bar { height: 14px; - background: var(--sw-accent); + background: var(--yk-accent); } .hitbox { display: inline-flex; align-items: center; justify-content: center; - min-height: var(--sw-hit-min-h); - min-width: var(--sw-hit-min-w); - border: 1px solid var(--sw-border); - color: var(--sw-text-dim); + min-height: var(--yk-hit-min-h); + min-width: var(--yk-hit-min-w); + border: 1px solid var(--yk-border); + color: var(--yk-text-dim); } .antbox { display: inline-flex; width: 8px; height: 8px; - border: 1px solid var(--sw-warn); + border: 1px solid var(--yk-warn); } /* type specimens */ .specimen { - color: var(--sw-text-bright); + color: var(--yk-text-bright); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -105,34 +105,34 @@ export class SwgTokens extends LitElement { /* focus & hotkey demos */ button.demo { background: none; - border: 1px solid var(--sw-border); - color: var(--sw-text); + border: 1px solid var(--yk-border); + color: var(--yk-text); font: inherit; cursor: pointer; - min-height: var(--sw-hit-min-h); - min-width: var(--sw-hit-min-w); - padding: 2px var(--sw-space-md); + min-height: var(--yk-hit-min-h); + min-width: var(--yk-hit-min-w); + padding: 2px var(--yk-space-md); } button.demo:hover { - color: var(--sw-text-bright); - border-color: var(--sw-border-bright); + color: var(--yk-text-bright); + border-color: var(--yk-border-bright); } button.demo:focus-visible { - outline: var(--sw-focus-ring); - outline-offset: var(--sw-focus-ring-offset); + outline: var(--yk-focus-ring); + outline-offset: var(--yk-focus-ring-offset); } .ring-always { - outline: var(--sw-focus-ring); - outline-offset: var(--sw-focus-ring-offset); + outline: var(--yk-focus-ring); + outline-offset: var(--yk-focus-ring-offset); } .demo-row { display: flex; align-items: center; - gap: var(--sw-space-lg); - margin: var(--sw-space-md) 0 var(--sw-space-lg); + gap: var(--yk-space-lg); + margin: var(--yk-space-md) 0 var(--yk-space-lg); } .note { - color: var(--sw-text-faint); + color: var(--yk-text-faint); max-width: 70ch; } `; @@ -172,7 +172,7 @@ export class SwgTokens extends LitElement { html`<div class="group"> <div class="group-title">${g.title}</div> <div class="group-about">${g.about}</div> - ${g.tokens.map((t) => SwgTokens.paletteRow(t))} + ${g.tokens.map((t) => YkgTokens.paletteRow(t))} </div>`, ) }`; @@ -218,8 +218,8 @@ export class SwgTokens extends LitElement { private renderType(): TemplateResult { const type = tokenGroups.find((g) => g.id === "type"); if (type === undefined) return html`missing group`; - const sizes = type.tokens.filter((t) => t.name.startsWith("--sw-type-")); - const rest = type.tokens.filter((t) => !t.name.startsWith("--sw-type-")); + const sizes = type.tokens.filter((t) => t.name.startsWith("--yk-type-")); + const rest = type.tokens.filter((t) => !t.name.startsWith("--yk-type-")); return html`<div class="group"> <div class="group-title">${type.title}</div> <div class="group-about">${type.about}</div> @@ -263,20 +263,20 @@ export class SwgTokens extends LitElement { <div class="note"> The first two show the ring only on keyboard focus (:focus-visible); the third wears it permanently for inspection. - Composite: --sw-focus-ring, offset by --sw-focus-ring-offset. + Composite: --yk-focus-ring, offset by --yk-focus-ring-offset. </div> </div> <div class="group"> <div class="group-title">hotkey marking</div> <div class="demo-row"> - <sw-button label="dock" hotkey="d"></sw-button> - <sw-button label="engage" hotkey="7"></sw-button> + <yk-button label="dock" hotkey="d"></yk-button> + <yk-button label="engage" hotkey="7"></yk-button> </div> <div class="note"> - The guidelines' button-marking norm, in --sw-hotkey-accent: + The guidelines' button-marking norm, in --yk-hotkey-accent: letter-in-label when the hotkey appears in the label, else the letter in a slot 70% of the way across. - These are live sw-buttons; the full behavior tour is the sw-button entry. + These are live yk-buttons; the full behavior tour is the yk-button entry. </div> </div> <div class="group"> @@ -293,11 +293,11 @@ export class SwgTokens extends LitElement { ) } <div class="demo-row"> - <sw-button label="press me" hotkey="p"></sw-button> + <yk-button label="press me" hotkey="p"></yk-button> <span class="note"> Every activation (click, Enter/Space, hotkey) flashes the pressed look - for --sw-press-ms; holding the mouse down shows the same look for as - long as it is held. The wash is --sw-wash-pressed. + for --yk-press-ms; holding the mouse down shows the same look for as + long as it is held. The wash is --yk-wash-pressed. </span> </div> </div> @@ -308,7 +308,7 @@ export class SwgTokens extends LitElement { const entry: GalleryEntry = { id: "tokens", title: "design tokens", - description: "The visual vocabulary everything else is built from: --sw-* custom properties, " + description: "The visual vocabulary everything else is built from: --yk-* custom properties, " + "rendered live from their definitions in tokens.ts. Names are semantic roles and are " + "the durable part; values are the iteration surface -- edit tokens.ts, rebuild, " + "and this page shows the new truth. Intent notes live in docs/precepts-aesthetics.md.", @@ -317,23 +317,23 @@ const entry: GalleryEntry = { caption: "The palette, by role. Swatches sit on a checkerboard so translucent washes read as " + "translucent; wash values derive from their base color, so retinting a base retints its washes.", wide: true, - render: () => html`<swg-tokens face="palette"></swg-tokens>`, + render: () => html`<ykg-tokens face="palette"></ykg-tokens>`, }, { caption: "The spacing ramp (gaps carry meaning) and the hit-target minimums.", wide: true, - render: () => html`<swg-tokens face="spacing"></swg-tokens>`, + render: () => html`<ykg-tokens face="spacing"></ykg-tokens>`, }, { caption: "The type ramp and tracking values, each rendered at its own size.", wide: true, - render: () => html`<swg-tokens face="type"></swg-tokens>`, + render: () => html`<ykg-tokens face="type"></ykg-tokens>`, }, { caption: "Keyboard visibility: the focus ring (try tabbing through), the two hotkey-marking forms, " + "and the activation flash (press things; watch them acknowledge).", wide: true, - render: () => html`<swg-tokens face="focus"></swg-tokens>`, + render: () => html`<ykg-tokens face="focus"></ykg-tokens>`, }, ], }; diff --git a/packages/yukey/src/tokens.test.ts b/packages/yukey/src/tokens.test.ts index 892d969..1603312 100644 --- a/packages/yukey/src/tokens.test.ts +++ b/packages/yukey/src/tokens.test.ts @@ -10,9 +10,9 @@ import { tokenGroups, tokensCssText } from "./tokens.js"; describe("the token vocabulary", () => { const all = tokenGroups.flatMap((g) => g.tokens); - it("names every token in the --sw- namespace, lowercase-and-dashes only", () => { + it("names every token in the --yk- namespace, lowercase-and-dashes only", () => { for (const t of all) { - expect(t.name).toMatch(/^--sw-[a-z][a-z0-9-]*$/); + expect(t.name).toMatch(/^--yk-[a-z][a-z0-9-]*$/); } }); diff --git a/packages/yukey/src/tokens.ts b/packages/yukey/src/tokens.ts index 5d9fa1d..f413485 100644 --- a/packages/yukey/src/tokens.ts +++ b/packages/yukey/src/tokens.ts @@ -6,7 +6,7 @@ // Tokens are defined as data (tokenGroups) and the :root stylesheet is generated // from it, so the stylesheet and the gallery's living documentation can never // disagree. Both entry points call installTokens(); custom properties inherit -// through shadow boundaries, so components just write `var(--sw-...)`. +// through shadow boundaries, so components just write `var(--yk-...)`. // // Naming is the durable part: tokens are named for their semantic role, so values // can be iterated hard (they will be) while the meaning holds still. The values @@ -21,7 +21,7 @@ import { unsafeCSS } from "lit"; export type TokenKind = "color" | "size" | "font" | "composite" | "duration"; export interface TokenDef { - name: `--sw-${string}`; + name: `--yk-${string}`; value: string; kind: TokenKind; // The role, in a sentence: what kind of thing should use this token. @@ -44,31 +44,31 @@ export const tokenGroups: TokenGroup[] = [ about: "Background layers, from the void of space up to floating overlays.", tokens: [ { - name: "--sw-bg-void", + name: "--yk-bg-void", value: "#05080f", kind: "color", about: "the deepest layer: the page behind everything", }, { - name: "--sw-bg-panel-solid", + name: "--yk-bg-panel-solid", value: "#0a1220", kind: "color", about: "opaque panel surface: cards, sticky headers, toasts -- anywhere bleed-through would muddy", }, { - name: "--sw-bg-panel", - value: "color-mix(in srgb, var(--sw-bg-panel-solid) 94%, transparent)", + name: "--yk-bg-panel", + value: "color-mix(in srgb, var(--yk-bg-panel-solid) 94%, transparent)", kind: "color", about: "translucent panel surface: big workspace panels floating over the page", }, { - name: "--sw-bg-well", + name: "--yk-bg-well", value: "#0d1626", kind: "color", about: "inset areas within a panel: chart plots, code samples", }, { - name: "--sw-bg-raised", + name: "--yk-bg-raised", value: "#101a2c", kind: "color", about: "small floating surfaces above panel content: tooltips, popovers", @@ -81,19 +81,19 @@ export const tokenGroups: TokenGroup[] = [ about: "Panel edges and separators. Brightness tracks prominence.", tokens: [ { - name: "--sw-border", + name: "--yk-border", value: "#3d5578", kind: "color", about: "the standard chrome border: panel edges, buttons, separator bars", }, { - name: "--sw-border-dim", + name: "--yk-border-dim", value: "#1c2a40", kind: "color", about: "subtle borders: section rules, chart frames, quiet separations", }, { - name: "--sw-border-bright", + name: "--yk-border-bright", value: "#5a7290", kind: "color", about: "emphasized borders: hover states on bordered controls", @@ -106,31 +106,31 @@ export const tokenGroups: TokenGroup[] = [ about: "The gray-blue text ramp, five tiers from emphasized to nearly-ambient.", tokens: [ { - name: "--sw-text-bright", + name: "--yk-text-bright", value: "#dfe9f5", kind: "color", about: "primary emphasis: names, values, hover text -- what the eye should land on", }, { - name: "--sw-text-hi", + name: "--yk-text-hi", value: "#a8bdd8", kind: "color", about: "secondary emphasis: entity names in rows, highlighted prose", }, { - name: "--sw-text", + name: "--yk-text", value: "#8194ac", kind: "color", about: "body text: the default reading color", }, { - name: "--sw-text-dim", + name: "--yk-text-dim", value: "#5a7290", kind: "color", about: "dim text: labels, section headings, quiet chrome", }, { - name: "--sw-text-faint", + name: "--yk-text-faint", value: "#48566b", kind: "color", about: "faint text: ids, empty states, the almost-not-there tier", @@ -144,40 +144,40 @@ export const tokenGroups: TokenGroup[] = [ + "Washes derive from the accent, so retinting it retints them.", tokens: [ { - name: "--sw-accent", + name: "--yk-accent", value: "#4f7ba8", kind: "color", about: "the interactive blue: meter fills, checkboxes, chart lines, attention borders", }, { - name: "--sw-accent-bright", + name: "--yk-accent-bright", value: "#8fb3d9", kind: "color", about: "the accent as text: app title, score, links", }, { - name: "--sw-wash-pressed", - value: "hsl(from var(--sw-accent) h calc(s * 1.5) calc(l * 1.2) / 0.5)", + name: "--yk-wash-pressed", + value: "hsl(from var(--yk-accent) h calc(s * 1.5) calc(l * 1.2) / 0.5)", kind: "color", about: "background wash for a control being activated: held under a pointer, " - + "or flashing for --sw-press-ms after triggering. Deliberately more saturated " + + "or flashing for --yk-press-ms after triggering. Deliberately more saturated " + "and brighter than the accent it derives from: activation must outrank selection at a glance", }, { - name: "--sw-wash-selected", - value: "color-mix(in srgb, var(--sw-accent) 25%, transparent)", + name: "--yk-wash-selected", + value: "color-mix(in srgb, var(--yk-accent) 25%, transparent)", kind: "color", about: "background wash for the selected row / the toggled-on button", }, { - name: "--sw-wash-hover", - value: "color-mix(in srgb, var(--sw-accent) 12%, transparent)", + name: "--yk-wash-hover", + value: "color-mix(in srgb, var(--yk-accent) 12%, transparent)", kind: "color", about: "background wash for the hovered row", }, { - name: "--sw-wash-faint", - value: "color-mix(in srgb, var(--sw-accent) 6%, transparent)", + name: "--yk-wash-faint", + value: "color-mix(in srgb, var(--yk-accent) 6%, transparent)", kind: "color", about: "the quietest wash: help strips, gently set-off regions", }, @@ -189,49 +189,49 @@ export const tokenGroups: TokenGroup[] = [ about: "Status colors. Teal means working, amber means stuck, red means unreachable.", tokens: [ { - name: "--sw-good", + name: "--yk-good", value: "#8fd4c0", kind: "color", about: "the working-economy teal: producing, positive flow, promises of movement", }, { - name: "--sw-warn", + name: "--yk-warn", value: "#c8863c", kind: "color", about: "the amber alert: rejected commands, blocked production, negative flow, emergencies", }, { - name: "--sw-warn-border", + name: "--yk-warn-border", value: "#6b4a24", kind: "color", about: "border for amber-alert surfaces (the rejection toast)", }, { - name: "--sw-warn-wash", - value: "color-mix(in srgb, var(--sw-warn) 13%, transparent)", + name: "--yk-warn-wash", + value: "color-mix(in srgb, var(--yk-warn) 13%, transparent)", kind: "color", about: "background wash for rows in an emergency state", }, { - name: "--sw-warn-wash-hover", - value: "color-mix(in srgb, var(--sw-warn) 22%, transparent)", + name: "--yk-warn-wash-hover", + value: "color-mix(in srgb, var(--yk-warn) 22%, transparent)", kind: "color", about: "hover wash for emergency rows", }, { - name: "--sw-warn-wash-selected", - value: "color-mix(in srgb, var(--sw-warn) 30%, transparent)", + name: "--yk-warn-wash-selected", + value: "color-mix(in srgb, var(--yk-warn) 30%, transparent)", kind: "color", about: "selected wash for emergency rows", }, { - name: "--sw-danger", + name: "--yk-danger", value: "#e8a0a0", kind: "color", about: "the red alert: no connection, destructive controls (the row-remove x)", }, { - name: "--sw-danger-border", + name: "--yk-danger-border", value: "#7a4040", kind: "color", about: "border for red-alert surfaces (the no-connection toast)", @@ -245,44 +245,44 @@ export const tokenGroups: TokenGroup[] = [ + "The focus-model and button stages consume them heavily.", tokens: [ { - name: "--sw-hotkey-accent", + name: "--yk-hotkey-accent", value: "#e8c86a", kind: "color", about: "the significant color for hotkey marking: the letter-in-label tint, the 70%-offset hotkey slot. " + "Deliberately the only gold in the palette: hotkeys must pop", }, { - name: "--sw-selection", + name: "--yk-selection", value: "#d8ecff", kind: "color", about: "the selection marker: a dashed ring around the selected entity", }, { - name: "--sw-focus-ring-color", + name: "--yk-focus-ring-color", value: "#d8ecff", kind: "color", about: "the keyboard focus ring: same where-you-are family as selection, drawn solid", }, { - name: "--sw-focus-ring-width", + name: "--yk-focus-ring-width", value: "2px", kind: "size", about: "focus ring stroke width", }, { - name: "--sw-focus-ring-offset", + name: "--yk-focus-ring-offset", value: "2px", kind: "size", about: "gap between a focused element and its ring (pair with outline-offset)", }, { - name: "--sw-focus-ring", - value: "var(--sw-focus-ring-width) solid var(--sw-focus-ring-color)", + name: "--yk-focus-ring", + value: "var(--yk-focus-ring-width) solid var(--yk-focus-ring-color)", kind: "composite", about: "the whole ring, ready for `outline:`", }, { - name: "--sw-scroll-peek", + name: "--yk-scroll-peek", value: "28px", kind: "size", about: "how much neighbor stays visible past the focused row when navigation scrolls: " @@ -298,7 +298,7 @@ export const tokenGroups: TokenGroup[] = [ + "slowing one here slows it everywhere, which is what an accessibility mode needs.", tokens: [ { - name: "--sw-press-ms", + name: "--yk-press-ms", value: "140ms", kind: "duration", about: "how long the activation flash lingers on a triggered control. " @@ -314,31 +314,31 @@ export const tokenGroups: TokenGroup[] = [ + "lg separates groups, xl+ separates sections (the gaps megacolumn focus jumps across).", tokens: [ { - name: "--sw-space-xs", + name: "--yk-space-xs", value: "4px", kind: "size", about: "within a cluster: icon-to-label", }, { - name: "--sw-space-sm", + name: "--yk-space-sm", value: "8px", kind: "size", about: "between related elements: button rows", }, { - name: "--sw-space-md", + name: "--yk-space-md", value: "12px", kind: "size", about: "panel edge padding, roomier relations", }, { - name: "--sw-space-lg", + name: "--yk-space-lg", value: "16px", kind: "size", about: "between groups within a section", }, - { name: "--sw-space-xl", value: "24px", kind: "size", about: "between sections" }, - { name: "--sw-space-xxl", value: "32px", kind: "size", about: "the biggest deliberate gap" }, + { name: "--yk-space-xl", value: "24px", kind: "size", about: "between sections" }, + { name: "--yk-space-xxl", value: "32px", kind: "size", about: "the biggest deliberate gap" }, ], }, { @@ -348,19 +348,19 @@ export const tokenGroups: TokenGroup[] = [ "We are not ants. No interactable renders smaller than this -- and that includes the VISIBLE part, not just the hit zone.", tokens: [ { - name: "--sw-hit-min-h", + name: "--yk-hit-min-h", value: "24px", kind: "size", about: "minimum height of anything interactable", }, { - name: "--sw-hit-min-w", + name: "--yk-hit-min-w", value: "24px", kind: "size", about: "minimum width of anything interactable (icon-only buttons, checkboxes)", }, { - name: "--sw-hit-indicator", + name: "--yk-hit-indicator", value: "16px", kind: "size", about: @@ -373,36 +373,36 @@ export const tokenGroups: TokenGroup[] = [ title: "type", about: "Monospace-heavy by design: this is a SCADA console, not a magazine.", tokens: [ - { name: "--sw-font", value: "monospace", kind: "font", about: "the UI font stack" }, - { name: "--sw-type-micro", value: "10px", kind: "size", about: "column headers, overlines" }, + { name: "--yk-font", value: "monospace", kind: "font", about: "the UI font stack" }, + { name: "--yk-type-micro", value: "10px", kind: "size", about: "column headers, overlines" }, { - name: "--sw-type-small", + name: "--yk-type-small", value: "11px", kind: "size", about: "fine print: ids, section headings", }, { - name: "--sw-type-ui", + name: "--yk-type-ui", value: "12px", kind: "size", about: "dense chrome: bars, tables, panels", }, { - name: "--sw-type-body", + name: "--yk-type-body", value: "13px", kind: "size", about: "reading surfaces: cards, prose", }, - { name: "--sw-type-head", value: "16px", kind: "size", about: "page and panel headings" }, - { name: "--sw-type-title", value: "18px", kind: "size", about: "the biggest: card names" }, + { name: "--yk-type-head", value: "16px", kind: "size", about: "page and panel headings" }, + { name: "--yk-type-title", value: "18px", kind: "size", about: "the biggest: card names" }, { - name: "--sw-tracking", + name: "--yk-tracking", value: "0.08em", kind: "size", about: "letterspacing for emphasized titles at text size", }, { - name: "--sw-tracking-wide", + name: "--yk-tracking-wide", value: "0.12em", kind: "size", about: "letterspacing for uppercase overlines (section headings, app title)", @@ -426,16 +426,16 @@ export function tokensCssText(groups: TokenGroup[]): string { } // unsafeCSS is safe here: the text is generated from the literal definitions in this -// file, never from runtime data (the same argument as sw-balance's GRID_TEMPLATE). +// file, never from runtime data. export const tokens: CSSResult = unsafeCSS(tokensCssText(tokenGroups)); // Installs the token definitions document-wide (a <style> in head). Custom // properties inherit into every shadow root from there. Idempotent: entry points // call it once, but a second call is harmless. export function installTokens(doc: Document = document): void { - if (doc.head.querySelector("style[data-sw-tokens]") !== null) return; + if (doc.head.querySelector("style[data-yk-tokens]") !== null) return; const style = doc.createElement("style"); - style.setAttribute("data-sw-tokens", ""); + style.setAttribute("data-yk-tokens", ""); style.textContent = tokens.cssText; doc.head.append(style); } diff --git a/packages/yukey/src/sw-button.gallery.ts b/packages/yukey/src/yk-button.gallery.ts similarity index 76% rename from packages/yukey/src/sw-button.gallery.ts rename to packages/yukey/src/yk-button.gallery.ts index fae6f8a..f0ad8c5 100644 --- a/packages/yukey/src/sw-button.gallery.ts +++ b/packages/yukey/src/yk-button.gallery.ts @@ -1,4 +1,4 @@ -// Gallery entry for sw-button: every marking form, both contexts, the disabled +// Gallery entry for yk-button: every marking form, both contexts, the disabled // and info behaviors, and a two-column demo proving that hotkey scope is the // focused column. @@ -6,9 +6,9 @@ import { css, html, LitElement, type TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import type { GalleryEntry } from "./gallery/entry.js"; import { MegacolumnController, type MegacolumnKeyDetail, megacolumnStyles } from "./megacolumn-controller.js"; -import "./sw-button.js"; +import "./yk-button.js"; import { flashPress } from "./press-flash.js"; -import type { SwInfoDetail } from "./sw-info.js"; +import type { YkInfoDetail } from "./yk-info.js"; // Shared demo chrome: a title, a log line that shows what fired. const demoStyles = css` @@ -16,32 +16,32 @@ const demoStyles = css` display: block; } .title { - color: var(--sw-text-dim); - font-size: var(--sw-type-small); - letter-spacing: var(--sw-tracking-wide); + color: var(--yk-text-dim); + font-size: var(--yk-type-small); + letter-spacing: var(--yk-tracking-wide); text-transform: uppercase; - margin-bottom: var(--sw-space-sm); + margin-bottom: var(--yk-space-sm); } .log { - margin-top: var(--sw-space-md); - color: var(--sw-good); - font-size: var(--sw-type-ui); + margin-top: var(--yk-space-md); + color: var(--yk-good); + font-size: var(--yk-type-ui); min-height: 1.4em; } .log.info { - color: var(--sw-hotkey-accent); + color: var(--yk-hotkey-accent); } `; // Standalone buttons: native tab stops, all marking forms side by side. -@customElement("swg-button-standalone") -export class SwgButtonStandalone extends LitElement { +@customElement("ykg-button-standalone") +export class YkgButtonStandalone extends LitElement { static styles = [ demoStyles, css` .row { display: flex; - gap: var(--sw-space-lg); + gap: var(--yk-space-lg); align-items: center; flex-wrap: wrap; } @@ -59,73 +59,73 @@ export class SwgButtonStandalone extends LitElement { } private onInfo(e: Event): void { - const { content } = (e as CustomEvent<SwInfoDetail>).detail; + const { content } = (e as CustomEvent<YkInfoDetail>).detail; this.last = `info: ${content}`; this.lastWasInfo = true; } render(): TemplateResult { return html` - <div class="row" @sw-info=${this.onInfo}> - <sw-button + <div class="row" @yk-info=${this.onInfo}> + <yk-button label="Dock at station" hotkey="d" info="requests docking clearance at the selected station" @click=${() => this.act("docked")} - ></sw-button> - <sw-button + ></yk-button> + <yk-button label="units" hotkey="7" info="opens the units registry" @click=${() => this.act("units opened")} - ></sw-button> - <sw-button + ></yk-button> + <yk-button label="transfer" hotkey="t" icon="☰" info="complex: several transfer modes live behind this one" @click=${() => this.act("transfer menu")} - ></sw-button> - <sw-button + ></yk-button> + <yk-button label="cruise" hotkey="c" info="the in-effect option: active but still clickable (re-issuing is the owner's call)" active @click=${() => this.act("cruise re-issued (harmless)")} - ></sw-button> - <sw-button + ></yk-button> + <yk-button label="self destruct" hotkey="s" info="offline: requires command authorization" disabled @click=${() => this.act("BOOM (should never happen)")} - ></sw-button> + ></yk-button> </div> <div class="log ${this.lastWasInfo ? "info" : ""}">${this.last}</div> `; } } -// A megacolumn of sw-buttons: rows are the buttons, plus a two-control row -// composing an sw-button into the aux (small-x) slot. -@customElement("swg-button-column") -export class SwgButtonColumn extends LitElement { +// A megacolumn of yk-buttons: rows are the buttons, plus a two-control row +// composing a yk-button into the aux (small-x) slot. +@customElement("ykg-button-column") +export class YkgButtonColumn extends LitElement { static styles = [ megacolumnStyles, demoStyles, css` [data-mc-container] { - border: 1px solid var(--sw-border-dim); - padding: var(--sw-space-md); + border: 1px solid var(--yk-border-dim); + padding: var(--yk-space-md); } [data-mc-row] { display: flex; align-items: center; - gap: var(--sw-space-sm); - min-height: var(--sw-hit-min-h); - padding: 2px var(--sw-space-sm); + gap: var(--yk-space-sm); + min-height: var(--yk-hit-min-h); + padding: 2px var(--yk-space-sm); } - [data-mc-row] sw-button:first-child, + [data-mc-row] yk-button:first-child, [data-mc-row] > .grow { flex: 1; } @@ -157,7 +157,7 @@ export class SwgButtonColumn extends LitElement { } private onInfo(e: Event): void { - const { content } = (e as CustomEvent<SwInfoDetail>).detail; + const { content } = (e as CustomEvent<YkInfoDetail>).detail; this.last = `info: ${content}`; this.lastWasInfo = true; } @@ -183,31 +183,31 @@ export class SwgButtonColumn extends LitElement { data-mc-container aria-label=${this.label} @megacolumn-key=${this.onMegacolumnKey} - @sw-info=${this.onInfo} + @yk-info=${this.onInfo} > <div data-mc-row="dock"> - <sw-button + <yk-button label="Dock at station" hotkey=${this.dockKey} info="requests docking clearance" @click=${() => this.act("docked")} - ></sw-button> + ></yk-button> </div> <div data-mc-row="beacon"> - <sw-button + <yk-button label="cycle beacon" hotkey=${this.beaconKey} info="cycles the nav beacon" @click=${() => this.act("beacon cycled")} - ></sw-button> + ></yk-button> </div> <div data-mc-row="purge"> - <sw-button + <yk-button label="purge filters" info="offline: filters already clean" disabled @click=${() => this.act("purged (should never happen)")} - ></sw-button> + ></yk-button> </div> ${ this.cargo.map( @@ -216,14 +216,14 @@ export class SwgButtonColumn extends LitElement { <div data-mc-row=${item}> <span class="grow" data-mc-main>${item}</span> <span data-mc-aux> - <sw-button + <yk-button label="×" info="jettisons this crate (x or Delete from anywhere in the row)" @click=${() => { this.cargo = this.cargo.filter((c) => c !== item); this.act(`${item} jettisoned`); }} - ></sw-button> + ></yk-button> </span> </div> `, @@ -236,12 +236,12 @@ export class SwgButtonColumn extends LitElement { } const entry: GalleryEntry = { - id: "sw-button", - title: "sw-button", + id: "yk-button", + title: "yk-button", description: "The button that makes the keyboard norms structural: hotkey marking " - + "(letter-in-label in --sw-hotkey-accent, else the 70%-offset slot, an icon for " - + "complex buttons), the i info hook (sw-info events, rendered by the shell's " - + "sw-infopop -- press i on anything here and watch), token hit targets, and " + + "(letter-in-label in --yk-hotkey-accent, else the 70%-offset slot, an icon for " + + "complex buttons), the i info hook (yk-info events, rendered by the shell's " + + "yk-infopop -- press i on anything here and watch), token hit targets, and " + "dual-context behavior -- a native focusable standalone, a megacolumn-key " + "consumer inside rows.", variants: [ @@ -251,7 +251,7 @@ const entry: GalleryEntry = { + "(transfer), the active in-effect option (cruise: washed, aria-current, still " + "clickable), and disabled (dimmed, unclickable, but i still answers while focused). " + "A button's hotkey and i work while it holds focus; watch the log.", - render: () => html`<swg-button-standalone></swg-button-standalone>`, + render: () => html`<ykg-button-standalone></ykg-button-standalone>`, }, { caption: "Inside a megacolumn: one tab stop for the whole column, buttons as rows. " @@ -260,7 +260,7 @@ const entry: GalleryEntry = { + "before committing. Try: d, then Enter; i for info on the focused control; " + "on a crate row, x or Delete jettisons via the aux ×, or right-arrow onto it and Enter. " + "The disabled row still focuses and answers i, but nothing activates it.", - render: () => html`<swg-button-column label="ops console"></swg-button-column>`, + render: () => html`<ykg-button-column label="ops console"></ykg-button-column>`, }, { caption: "Hotkey scope IS the focused column: both columns mark d for dock, and d jumps only " @@ -270,8 +270,8 @@ const entry: GalleryEntry = { render: () => html` <div style="display:flex; gap:24px; align-items:flex-start;"> - <swg-button-column style="flex:1" label="port console" dockKey="d" beaconKey="b"></swg-button-column> - <swg-button-column style="flex:1" label="starboard console" dockKey="d" beaconKey="c"></swg-button-column> + <ykg-button-column style="flex:1" label="port console" dockKey="d" beaconKey="b"></ykg-button-column> + <ykg-button-column style="flex:1" label="starboard console" dockKey="d" beaconKey="c"></ykg-button-column> </div> `, }, diff --git a/packages/yukey/src/sw-button.test.ts b/packages/yukey/src/yk-button.test.ts similarity index 79% rename from packages/yukey/src/sw-button.test.ts rename to packages/yukey/src/yk-button.test.ts index 241509d..43f7653 100644 --- a/packages/yukey/src/sw-button.test.ts +++ b/packages/yukey/src/yk-button.test.ts @@ -1,16 +1,16 @@ // @vitest-environment jsdom // -// Tests for sw-button (sw-button.ts): the marking logic, standalone activation, +// Tests for yk-button (yk-button.ts): the marking logic, standalone activation, // and row-mode claims driven through a real MegacolumnController rig -- the same // fake-host pattern as megacolumn-controller.test.ts, but with live Lit rendering -// (sw-button's marking must be visible to the controller's scan synchronously). +// (yk-button's marking must be visible to the controller's scan synchronously). import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { MegacolumnController } from "./megacolumn-controller.js"; -import type { SwButton } from "./sw-button.js"; -import "./sw-button.js"; -import type { SwInfoDetail } from "./sw-info.js"; +import type { YkButton } from "./yk-button.js"; +import "./yk-button.js"; import { installTokens, tokenGroups } from "./tokens.js"; +import type { YkInfoDetail } from "./yk-info.js"; // jsdom implements no scrollIntoView; the controller scrolls the focused row // into view on every focus identity change. A no-op stands in. @@ -23,27 +23,27 @@ installTokens(); // The token's defined default, parsed here rather than imported from // press-flash.ts: the tests must not derive expectations from the code under test. const PRESS_MS = ((): number => { - const t = tokenGroups.flatMap((g) => g.tokens).find((t) => t.name === "--sw-press-ms"); - if (t === undefined) throw new Error("--sw-press-ms token missing"); + const t = tokenGroups.flatMap((g) => g.tokens).find((t) => t.name === "--yk-press-ms"); + if (t === undefined) throw new Error("--yk-press-ms token missing"); return Number.parseFloat(t.value); })(); -function makeButton(attrs: Partial<SwButton>): SwButton { - const el = document.createElement("sw-button"); +function makeButton(attrs: Partial<YkButton>): YkButton { + const el = document.createElement("yk-button"); Object.assign(el, attrs); return el; } -async function standalone(attrs: Partial<SwButton>): Promise<SwButton> { +async function standalone(attrs: Partial<YkButton>): Promise<YkButton> { const el = makeButton(attrs); document.body.replaceChildren(el); await el.updateComplete; return el; } -function inner(el: SwButton): HTMLElement { +function inner(el: YkButton): HTMLElement { const btn = el.shadowRoot?.querySelector<HTMLElement>(".btn"); - if (!btn) throw new Error("sw-button rendered no .btn"); + if (!btn) throw new Error("yk-button rendered no .btn"); return btn; } @@ -89,8 +89,8 @@ describe("hotkey marking", () => { }); it("mirrors info onto data-info synchronously (the hover-trigger carrier contract)", () => { - // sw-infopop's hover trigger finds carriers by [data-info]; the mirror makes - // sw-buttons carriers without the popup knowing their class. + // yk-infopop's hover trigger finds carriers by [data-info]; the mirror makes + // yk-buttons carriers without the popup knowing their class. const el = makeButton({ info: "docking clearance" }); expect(el.dataset.info).toBe("docking clearance"); // before any render or connect el.info = ""; @@ -183,11 +183,11 @@ describe("standalone context", () => { expect(clicks).toBe(0); }); - it("i dispatches sw-info with the content and the anchor element", async () => { + it("i dispatches yk-info with the content and the anchor element", async () => { const el = await standalone({ label: "dock", info: "requests docking clearance" }); - const got: SwInfoDetail[] = []; - document.body.addEventListener("sw-info", (e) => { - got.push((e as CustomEvent<SwInfoDetail>).detail); + const got: YkInfoDetail[] = []; + document.body.addEventListener("yk-info", (e) => { + got.push((e as CustomEvent<YkInfoDetail>).detail); }); inner(el).dispatchEvent( new KeyboardEvent("keydown", { key: "i", bubbles: true, composed: true, cancelable: true }), @@ -198,7 +198,7 @@ describe("standalone context", () => { it("i without info does nothing", async () => { const el = await standalone({ label: "dock" }); let infos = 0; - document.body.addEventListener("sw-info", () => { + document.body.addEventListener("yk-info", () => { infos += 1; }); const e = new KeyboardEvent("keydown", { @@ -214,7 +214,7 @@ describe("standalone context", () => { }); // Activation feedback: the held look under a pointer, and the flash that -// acknowledges every successful activation for --sw-press-ms (press-flash.ts). +// acknowledges every successful activation for --yk-press-ms (press-flash.ts). // The classes land on the HOST element: jsdom computes custom properties on // light-DOM elements but not through shadow boundaries, and the host is also // where authors can observe the state from outside. @@ -229,14 +229,14 @@ describe("activation feedback", () => { const pointer = (type: string, init: PointerEventInit = {}): PointerEvent => new PointerEvent(type, { bubbles: true, composed: true, button: 0, ...init }); - it("flashes on activation and clears after --sw-press-ms", async () => { + it("flashes on activation and clears after --yk-press-ms", async () => { const el = await standalone({ label: "go" }); el.click(); - expect(el.classList.contains("sw-pressed")).toBe(true); + expect(el.classList.contains("yk-pressed")).toBe(true); vi.advanceTimersByTime(PRESS_MS - 1); - expect(el.classList.contains("sw-pressed")).toBe(true); + expect(el.classList.contains("yk-pressed")).toBe(true); vi.advanceTimersByTime(1); - expect(el.classList.contains("sw-pressed")).toBe(false); + expect(el.classList.contains("yk-pressed")).toBe(false); }); it("re-arms the flash on rapid re-activation (no early cutoff)", async () => { @@ -245,52 +245,52 @@ describe("activation feedback", () => { vi.advanceTimersByTime(PRESS_MS / 2); el.click(); // second activation, half way through the first flash vi.advanceTimersByTime(PRESS_MS / 2); // the first flash's timer would have fired here - expect(el.classList.contains("sw-pressed")).toBe(true); + expect(el.classList.contains("yk-pressed")).toBe(true); vi.advanceTimersByTime(PRESS_MS / 2); - expect(el.classList.contains("sw-pressed")).toBe(false); + expect(el.classList.contains("yk-pressed")).toBe(false); }); it("reads the flash duration from the live cascade (the accessibility knob)", async () => { const el = await standalone({ label: "go" }); - el.style.setProperty("--sw-press-ms", "50ms"); + el.style.setProperty("--yk-press-ms", "50ms"); el.click(); vi.advanceTimersByTime(49); - expect(el.classList.contains("sw-pressed")).toBe(true); + expect(el.classList.contains("yk-pressed")).toBe(true); vi.advanceTimersByTime(1); - expect(el.classList.contains("sw-pressed")).toBe(false); + expect(el.classList.contains("yk-pressed")).toBe(false); }); it("holds while the pointer is down, releases on pointerup", async () => { const el = await standalone({ label: "go" }); inner(el).dispatchEvent(pointer("pointerdown")); - expect(el.classList.contains("sw-held")).toBe(true); + expect(el.classList.contains("yk-held")).toBe(true); inner(el).dispatchEvent(pointer("pointerup")); - expect(el.classList.contains("sw-held")).toBe(false); + expect(el.classList.contains("yk-held")).toBe(false); }); it("releases the hold when the pointer leaves or is cancelled", async () => { const el = await standalone({ label: "go" }); inner(el).dispatchEvent(pointer("pointerdown")); el.dispatchEvent(new PointerEvent("pointerleave")); // leave does not bubble; it fires on the host itself - expect(el.classList.contains("sw-held")).toBe(false); + expect(el.classList.contains("yk-held")).toBe(false); inner(el).dispatchEvent(pointer("pointerdown")); inner(el).dispatchEvent(pointer("pointercancel")); - expect(el.classList.contains("sw-held")).toBe(false); + expect(el.classList.contains("yk-held")).toBe(false); }); it("only the primary button holds", async () => { const el = await standalone({ label: "go" }); inner(el).dispatchEvent(pointer("pointerdown", { button: 2 })); - expect(el.classList.contains("sw-held")).toBe(false); + expect(el.classList.contains("yk-held")).toBe(false); }); it("disabled: neither holds nor flashes (nothing happened; nothing must claim otherwise)", async () => { const el = await standalone({ label: "go", disabled: true }); inner(el).dispatchEvent(pointer("pointerdown")); - expect(el.classList.contains("sw-held")).toBe(false); + expect(el.classList.contains("yk-held")).toBe(false); inner(el).click(); // dies in the shadow-root suppressor el.click(); // the direct host path the suppressor never sees - expect(el.classList.contains("sw-pressed")).toBe(false); + expect(el.classList.contains("yk-pressed")).toBe(false); }); }); @@ -355,8 +355,8 @@ describe("disabled state", () => { el.addEventListener("click", () => { clicks += 1; }); - document.body.addEventListener("sw-info", (e) => { - infos.push((e as CustomEvent<SwInfoDetail>).detail.content); + document.body.addEventListener("yk-info", (e) => { + infos.push((e as CustomEvent<YkInfoDetail>).detail.content); }); inner(el).dispatchEvent( new KeyboardEvent("keydown", { key: "d", bubbles: true, composed: true, cancelable: true }), @@ -370,13 +370,13 @@ describe("disabled state", () => { }); // Row mode, driven through a real controller: the same rig pattern as -// megacolumn-controller.test.ts, except rows carry live sw-button elements. +// megacolumn-controller.test.ts, except rows carry live yk-button elements. describe("inside a megacolumn row", () => { interface Rig { container: HTMLElement; ctl: MegacolumnController; key: (key: string) => KeyboardEvent; - button: (sel: string) => SwButton; + button: (sel: string) => YkButton; } async function makeRig(innerHtml: string): Promise<Rig> { @@ -385,8 +385,8 @@ describe("inside a megacolumn row", () => { root.innerHTML = `<div data-mc-container>${innerHtml}</div>`; const container = root.querySelector<HTMLElement>("[data-mc-container]"); if (container === null) throw new Error("unreachable: just rendered"); - // Let the sw-buttons complete their first render before the scan. - await Promise.all([...root.querySelectorAll("sw-button")].map((b) => b.updateComplete)); + // Let the yk-buttons complete their first render before the scan. + await Promise.all([...root.querySelectorAll("yk-button")].map((b) => b.updateComplete)); const ctl = new MegacolumnController({ renderRoot: root, addController(): void {}, @@ -400,9 +400,9 @@ describe("inside a megacolumn row", () => { container.dispatchEvent(e); return e; }; - const button = (sel: string): SwButton => { - const b = root.querySelector<SwButton>(sel); - if (b === null) throw new Error(`no sw-button at ${sel}`); + const button = (sel: string): YkButton => { + const b = root.querySelector<YkButton>(sel); + if (b === null) throw new Error(`no yk-button at ${sel}`); return b; }; return { container, ctl, key, button }; @@ -410,9 +410,9 @@ describe("inside a megacolumn row", () => { it("renders non-focusable markup: the controller's tab-stop scan accepts it", async () => { const { button } = await makeRig(` - <div data-mc-row="a"><sw-button label="alpha"></sw-button></div> + <div data-mc-row="a"><yk-button label="alpha"></yk-button></div> `); - expect(inner(button("sw-button")).tagName).toBe("DIV"); // no native button in row mode + expect(inner(button("yk-button")).tagName).toBe("DIV"); // no native button in row mode }); it("throws when loose in a container outside any row", async () => { @@ -428,17 +428,17 @@ describe("inside a megacolumn row", () => { window.addEventListener("error", onError); const root = document.createElement("div"); document.body.replaceChildren(root); - root.innerHTML = `<div data-mc-container><sw-button label="stray"></sw-button></div>`; + root.innerHTML = `<div data-mc-container><yk-button label="stray"></yk-button></div>`; window.removeEventListener("error", onError); expect(reported.some((m) => /outside any row/.test(m))).toBe(true); }); it("Enter and Space at its position activate as a click", async () => { const { key, button } = await makeRig(` - <div data-mc-row="a"><sw-button label="alpha"></sw-button></div> + <div data-mc-row="a"><yk-button label="alpha"></yk-button></div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); expect(key("Enter").defaultPrevented).toBe(true); @@ -448,18 +448,18 @@ describe("inside a megacolumn row", () => { it("a mouse click activates exactly once (the pointer-activation dedup)", async () => { // The controller synthesizes a megacolumn-key Enter from unclaimed clicks on - // row controls; sw-button activates on its own native click and CLAIMS it + // row controls; yk-button activates on its own native click and CLAIMS it // (preventDefault, the same idiom as claiming a forwarded key), so the // synthesis skips it and the author's @click fires once, not twice. const { button } = await makeRig(` - <div data-mc-row="a"><sw-button label="alpha"></sw-button></div> + <div data-mc-row="a"><yk-button label="alpha"></yk-button></div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); const e = new MouseEvent("click", { bubbles: true, cancelable: true, composed: true }); - inner(button("sw-button")).dispatchEvent(e); + inner(button("yk-button")).dispatchEvent(e); expect(clicks).toBe(1); expect(e.defaultPrevented).toBe(true); // claimed: the controller must not synthesize }); @@ -477,11 +477,11 @@ describe("inside a megacolumn row", () => { // row-mode claim marks it, so the controller must not synthesize a second // Enter from the keyboard's own acknowledgment click. const { container, button } = await makeRig(` - <div data-mc-row="a"><sw-button label="alpha"></sw-button></div> + <div data-mc-row="a"><yk-button label="alpha"></yk-button></div> `); let clicks = 0; let mcKeys = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); container.parentElement?.addEventListener("megacolumn-key", () => { @@ -498,10 +498,10 @@ describe("inside a megacolumn row", () => { // The see-before-commit rhythm: the letter is a jump, Enter is the commit. const { ctl, key, button } = await makeRig(` <div data-mc-row="a">plain row</div> - <div data-mc-row="dock"><sw-button label="dock" hotkey="d"></sw-button></div> + <div data-mc-row="dock"><yk-button label="dock" hotkey="d"></yk-button></div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); const e = key("d"); // focus is on row a @@ -515,11 +515,11 @@ describe("inside a megacolumn row", () => { it("its own hotkey while focused does nothing (Enter/Space are the activation)", async () => { // Double-tap must not out-power single-tap: the letter only ever jumps. const { ctl, key, button } = await makeRig(` - <div data-mc-row="dock"><sw-button label="dock" hotkey="d"></sw-button></div> + <div data-mc-row="dock"><yk-button label="dock" hotkey="d"></yk-button></div> <div data-mc-row="b">plain row</div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); const e = key("d"); // focus starts on the dock row itself @@ -531,10 +531,10 @@ describe("inside a megacolumn row", () => { it("hotkey-direct: the letter activates immediately, with focus moved to this row", async () => { const { ctl, key, button } = await makeRig(` <div data-mc-row="a">plain row</div> - <div data-mc-row="dock"><sw-button label="dock" hotkey="d" hotkey-direct></sw-button></div> + <div data-mc-row="dock"><yk-button label="dock" hotkey="d" hotkey-direct></yk-button></div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); const e = key("d"); // focus is on row a @@ -549,11 +549,11 @@ describe("inside a megacolumn row", () => { // A direct letter has the same power from every row, its own included: // there is no jump for it to mean instead. const { key, button } = await makeRig(` - <div data-mc-row="dock"><sw-button label="dock" hotkey="d" hotkey-direct></sw-button></div> + <div data-mc-row="dock"><yk-button label="dock" hotkey="d" hotkey-direct></yk-button></div> <div data-mc-row="b">plain row</div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); expect(key("d").defaultPrevented).toBe(true); // focus starts on the dock row @@ -564,13 +564,13 @@ describe("inside a megacolumn row", () => { const { ctl, key, button } = await makeRig(` <div data-mc-row="crate"> <span data-mc-main>ore crate</span> - <span data-mc-aux><sw-button label="×"></sw-button></span> + <span data-mc-aux><yk-button label="×"></yk-button></span> </div> <div data-mc-row="plain">plain row</div> - <div data-mc-row="wipe"><sw-button label="wipe all" hotkey="x"></sw-button></div> + <div data-mc-row="wipe"><yk-button label="wipe all" hotkey="x"></yk-button></div> `); let auxClicks = 0; - button("[data-mc-aux] sw-button").addEventListener("click", () => { + button("[data-mc-aux] yk-button").addEventListener("click", () => { auxClicks += 1; }); key("x"); // focus on the crate row: its aux claims first, the marking never hears @@ -584,13 +584,13 @@ describe("inside a megacolumn row", () => { it("an i-marked button yields to a focused button's own info (the popup norm survives)", async () => { const { ctl, key } = await makeRig(` - <div data-mc-row="a"><sw-button label="dock" info="requests clearance"></sw-button></div> + <div data-mc-row="a"><yk-button label="dock" info="requests clearance"></yk-button></div> <div data-mc-row="plain">plain row</div> - <div data-mc-row="atlas"><sw-button label="atlas" hotkey="i"></sw-button></div> + <div data-mc-row="atlas"><yk-button label="atlas" hotkey="i"></yk-button></div> `); const infos: string[] = []; - document.body.addEventListener("sw-info", (e) => { - infos.push((e as CustomEvent<SwInfoDetail>).detail.content); + document.body.addEventListener("yk-info", (e) => { + infos.push((e as CustomEvent<YkInfoDetail>).detail.content); }); key("i"); // focus on the dock row: its own info claims i, the marking never hears expect(infos).toEqual(["requests clearance"]); @@ -601,24 +601,24 @@ describe("inside a megacolumn row", () => { expect(infos).toEqual(["requests clearance"]); // a jump, not the info norm }); - it("duplicate sw-button hotkeys in one column throw at scan time", async () => { + it("duplicate yk-button hotkeys in one column throw at scan time", async () => { await expect( makeRig(` - <div data-mc-row="a"><sw-button label="dock" hotkey="d"></sw-button></div> - <div data-mc-row="b"><sw-button label="drift" hotkey="d"></sw-button></div> + <div data-mc-row="a"><yk-button label="dock" hotkey="d"></yk-button></div> + <div data-mc-row="b"><yk-button label="drift" hotkey="d"></yk-button></div> `), ).rejects.toThrow(/duplicate hotkey marking "d"/); }); - it("an aux sw-button answers x and Delete from anywhere in the row", async () => { + it("an aux yk-button answers x and Delete from anywhere in the row", async () => { const { key, button } = await makeRig(` <div data-mc-row="crate"> <span data-mc-main>ore crate</span> - <span data-mc-aux><sw-button label="×"></sw-button></span> + <span data-mc-aux><yk-button label="×"></yk-button></span> </div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); expect(key("x").defaultPrevented).toBe(true); // focus is at main position @@ -626,12 +626,12 @@ describe("inside a megacolumn row", () => { expect(clicks).toBe(2); }); - it("a main sw-button does NOT answer x (removal is the aux norm)", async () => { + it("a main yk-button does NOT answer x (removal is the aux norm)", async () => { const { key, button } = await makeRig(` - <div data-mc-row="a"><sw-button label="alpha"></sw-button></div> + <div data-mc-row="a"><yk-button label="alpha"></yk-button></div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); key("x"); @@ -642,11 +642,11 @@ describe("inside a megacolumn row", () => { const { key, button } = await makeRig(` <div data-mc-row="crate"> <span data-mc-main>ore crate</span> - <span data-mc-aux><sw-button label="×"></sw-button></span> + <span data-mc-aux><yk-button label="×"></yk-button></span> </div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); key("Enter"); // main position: the aux button must stay quiet @@ -656,28 +656,28 @@ describe("inside a megacolumn row", () => { expect(clicks).toBe(1); }); - it("i at its position dispatches sw-info", async () => { + it("i at its position dispatches yk-info", async () => { const { key, button } = await makeRig(` - <div data-mc-row="a"><sw-button label="dock" info="requests clearance"></sw-button></div> + <div data-mc-row="a"><yk-button label="dock" info="requests clearance"></yk-button></div> `); - const got: SwInfoDetail[] = []; - document.body.addEventListener("sw-info", (e) => { - got.push((e as CustomEvent<SwInfoDetail>).detail); + const got: YkInfoDetail[] = []; + document.body.addEventListener("yk-info", (e) => { + got.push((e as CustomEvent<YkInfoDetail>).detail); }); const e = key("i"); expect(got.length).toBe(1); expect(got[0]?.content).toBe("requests clearance"); - expect(got[0]?.source).toBe(button("sw-button")); + expect(got[0]?.source).toBe(button("yk-button")); expect(e.defaultPrevented).toBe(true); }); it("disabled: claims its keys but does not act (Space must not fall through)", async () => { const { ctl, key, button } = await makeRig(` - <div data-mc-row="a"><sw-button label="dock" hotkey="d" hotkey-direct disabled></sw-button></div> + <div data-mc-row="a"><yk-button label="dock" hotkey="d" hotkey-direct disabled></yk-button></div> <div data-mc-row="b">plain row</div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); expect(key(" ").defaultPrevented).toBe(true); // claimed... @@ -692,11 +692,11 @@ describe("inside a megacolumn row", () => { it("disabled, non-direct: its hotkey still jumps focus to it (i can ask why)", async () => { const { ctl, key, button } = await makeRig(` - <div data-mc-row="a"><sw-button label="dock" hotkey="d" disabled></sw-button></div> + <div data-mc-row="a"><yk-button label="dock" hotkey="d" disabled></yk-button></div> <div data-mc-row="b">plain row</div> `); let clicks = 0; - button("sw-button").addEventListener("click", () => { + button("yk-button").addEventListener("click", () => { clicks += 1; }); key("ArrowDown"); // -> b @@ -710,16 +710,16 @@ describe("inside a megacolumn row", () => { it("two buttons in a row each claim only their own position's Enter", async () => { const { key, button } = await makeRig(` <div data-mc-row="pair"> - <span data-mc-main><sw-button label="inspect"></sw-button></span> - <span data-mc-aux><sw-button label="×"></sw-button></span> + <span data-mc-main><yk-button label="inspect"></yk-button></span> + <span data-mc-aux><yk-button label="×"></yk-button></span> </div> `); let mains = 0; let auxes = 0; - button("[data-mc-main] sw-button").addEventListener("click", () => { + button("[data-mc-main] yk-button").addEventListener("click", () => { mains += 1; }); - button("[data-mc-aux] sw-button").addEventListener("click", () => { + button("[data-mc-aux] yk-button").addEventListener("click", () => { auxes += 1; }); key("Enter"); diff --git a/packages/yukey/src/sw-button.ts b/packages/yukey/src/yk-button.ts similarity index 89% rename from packages/yukey/src/sw-button.ts rename to packages/yukey/src/yk-button.ts index 54142c5..89dcd1c 100644 --- a/packages/yukey/src/sw-button.ts +++ b/packages/yukey/src/yk-button.ts @@ -1,21 +1,21 @@ -// sw-button: the button that makes the guidelines' keyboard norms structural. +// yk-button: the button that makes the guidelines' keyboard norms structural. // Use it and the norms happen; there is nothing to remember per screen. // // The norms it embodies (docs/precepts-we-love-the-keyboard.md, "buttons always show their hotkey" // and the megacolumn sections): // // - Hotkey marking: if the hotkey letter occurs in the label, its first -// occurrence renders in --sw-hotkey-accent; otherwise the letter shows in a +// occurrence renders in --yk-hotkey-accent; otherwise the letter shows in a // slot 70% of the way across. A complex button (more than one concept of // activation) sets `icon` instead, which claims that slot: a single letter // would mislead, so the icon says "more here; ask i". // - The i norm: pressing i (forwarded in a row, or while focused standalone) -// dispatches a composed `sw-info` event carrying this button's `info` text. -// sw-infopop renders the popup; `info` is also mirrored onto data-info, -// the attribute sw-infopop's hover trigger finds carriers by +// dispatches a composed `yk-info` event carrying this button's `info` text. +// yk-infopop renders the popup; `info` is also mirrored onto data-info, +// the attribute yk-infopop's hover trigger finds carriers by // (one authored content, two triggers). // i answers even when disabled: it is how you ask why a thing is off. -// - Hit targets from --sw-hit-min-h/w. +// - Hit targets from --yk-hit-min-h/w. // - Active (`active`): the in-effect option among sibling choices, marked with // the selected wash and aria-current -- and STILL clickable: re-issuing an // in-effect choice is the norm's contract (owners decide whether re-issue @@ -46,7 +46,7 @@ // and Enter/Space then activate // (see-before-commit). // x/Delete from any position, aux buttons only-> activate (the removal norm) -// i at this button's own position -> sw-info +// i at this button's own position -> yk-info // - Standalone (no [data-mc-container] ancestor): a real native button; Tab // reaches it, Enter/Space activate natively, the focus ring is the tokens'. // Its hotkey and i answer only while it holds focus (a broader standalone @@ -66,10 +66,10 @@ // click default is not this component's to consume. // // Activation feedback: every successful activation flashes the pressed look for -// --sw-press-ms (press-flash.ts), so keyboard triggers are as visible as a mouse +// --yk-press-ms (press-flash.ts), so keyboard triggers are as visible as a mouse // press; a pointer held down shows the same look for as long as it stays down. // Disabled buttons show neither: nothing happened, nothing must claim otherwise. -// The sw-pressed/sw-held classes live on the HOST element, outside classMap's +// The yk-pressed/yk-held classes live on the HOST element, outside classMap's // managed set, so renders never clobber a flash in flight. import { css, html, LitElement, type TemplateResult } from "lit"; @@ -78,10 +78,10 @@ import { classMap } from "lit/directives/class-map.js"; import { ifDefined } from "lit/directives/if-defined.js"; import type { MegacolumnKeyDetail } from "./megacolumn-controller.js"; import { flashPress } from "./press-flash.js"; -import { dispatchSwInfo } from "./sw-info.js"; +import { dispatchYkInfo } from "./yk-info.js"; -@customElement("sw-button") -export class SwButton extends LitElement { +@customElement("yk-button") +export class YkButton extends LitElement { static styles = css` :host { display: inline-flex; @@ -92,33 +92,33 @@ export class SwButton extends LitElement { align-items: center; justify-content: center; flex: 1; - min-height: var(--sw-hit-min-h); - min-width: var(--sw-hit-min-w); - padding: 2px var(--sw-space-md); + min-height: var(--yk-hit-min-h); + min-width: var(--yk-hit-min-w); + padding: 2px var(--yk-space-md); box-sizing: border-box; background: none; - border: 1px solid var(--sw-border); - color: var(--sw-text); + border: 1px solid var(--yk-border); + color: var(--yk-text); font: inherit; cursor: pointer; user-select: none; white-space: nowrap; } .btn:hover { - color: var(--sw-text-bright); - border-color: var(--sw-border-bright); + color: var(--yk-text-bright); + border-color: var(--yk-border-bright); } button.btn:focus-visible { - outline: var(--sw-focus-ring); - outline-offset: var(--sw-focus-ring-offset); + outline: var(--yk-focus-ring); + outline-offset: var(--yk-focus-ring-offset); } .hk, .slot { - color: var(--sw-hotkey-accent); + color: var(--yk-hotkey-accent); } /* the 70%-offset hotkey slot, per the guidelines' button-marking norm */ .btn.slotted { - padding-right: calc(var(--sw-space-xxl) + var(--sw-space-md)); + padding-right: calc(var(--yk-space-xxl) + var(--yk-space-md)); } .slot { position: absolute; @@ -128,23 +128,23 @@ export class SwButton extends LitElement { } /* the aux (small-x) form: compact, danger-tinted, borderless like the norm */ .btn.aux { - color: var(--sw-danger); + color: var(--yk-danger); border-color: transparent; padding: 0; flex: none; } .btn.aux:hover { - border-color: var(--sw-danger-border); + border-color: var(--yk-danger-border); } .btn.disabled { - color: var(--sw-text-faint); - border-color: var(--sw-border-dim); + color: var(--yk-text-faint); + border-color: var(--yk-border-dim); cursor: default; } /* the active (in-effect) option: selected wash, brighter text, still clickable */ .btn.active { - color: var(--sw-text-bright); - background: var(--sw-wash-selected); + color: var(--yk-text-bright); + background: var(--yk-wash-selected); } /* a dead hotkey must not advertise itself in the significant color */ .btn.disabled .hk, @@ -153,11 +153,11 @@ export class SwButton extends LitElement { } /* activation feedback: the same look for held-under-a-pointer and for the post-activation flash (see the file header) */ - :host(.sw-held) .btn, - :host(.sw-pressed) .btn { - background: var(--sw-wash-pressed); - border-color: var(--sw-accent-bright); - color: var(--sw-text-bright); + :host(.yk-held) .btn, + :host(.yk-pressed) .btn { + background: var(--yk-wash-pressed); + border-color: var(--yk-accent-bright); + color: var(--yk-text-bright); } `; @@ -171,7 +171,7 @@ export class SwButton extends LitElement { active = false; // The info content, mirrored synchronously onto data-info: that attribute is - // the carrier contract sw-infopop's hover trigger finds elements by + // the carrier contract yk-infopop's hover trigger finds elements by // (decision-info-content: one authored content, two triggers), and mirroring // keeps this property a typed accessor over the same single source. #info = ""; @@ -198,7 +198,7 @@ export class SwButton extends LitElement { } set hotkey(v: string) { if (v !== "" && !/^[a-z0-9]$/i.test(v)) { - throw new Error(`sw-button: hotkey "${v}" must be a single [a-z0-9] character`); + throw new Error(`yk-button: hotkey "${v}" must be a single [a-z0-9] character`); } const norm = v.toLowerCase(); this.#hotkey = norm; @@ -233,7 +233,7 @@ export class SwButton extends LitElement { this.#row = this.closest("[data-mc-row]"); if (this.#row === null && this.closest("[data-mc-container]") !== null) { throw new Error( - "sw-button: inside a megacolumn container but outside any row; " + "yk-button: inside a megacolumn container but outside any row; " + "a loose tab stop would fracture the single-tab-stop model", ); } @@ -319,11 +319,11 @@ export class SwButton extends LitElement { #onPointerDown = (e: PointerEvent): void => { if (e.button !== 0 || this.disabled) return; - this.classList.add("sw-held"); + this.classList.add("yk-held"); }; #releaseHold = (): void => { - this.classList.remove("sw-held"); + this.classList.remove("yk-held"); }; // A claimed key is consumed whether or not the button acts: a disabled @@ -335,7 +335,7 @@ export class SwButton extends LitElement { } #dispatchInfo(): void { - dispatchSwInfo(this, this.info); + dispatchYkInfo(this, this.info); } #position(): "main" | "aux" { @@ -397,6 +397,6 @@ export class SwButton extends LitElement { declare global { interface HTMLElementTagNameMap { - "sw-button": SwButton; + "yk-button": YkButton; } } diff --git a/packages/yukey/src/sw-info.ts b/packages/yukey/src/yk-info.ts similarity index 68% rename from packages/yukey/src/sw-info.ts rename to packages/yukey/src/yk-info.ts index d63cf3c..279a9c9 100644 --- a/packages/yukey/src/sw-info.ts +++ b/packages/yukey/src/yk-info.ts @@ -1,31 +1,31 @@ -// The info norm's wiring: the sw-info event shape and the data-info carrier contract. +// The info norm's wiring: the yk-info event shape and the data-info carrier contract. // This file is the SSOT for both. (The norm itself: docs/precepts-we-love-the-keyboard.md, // "info pops" -- i means "info, please" on almost any element.) // // One authored content, two triggers: // -// - `data-info="<content>"` on any element makes it a CARRIER. sw-infopop's +// - `data-info="<content>"` on any element makes it a CARRIER. yk-infopop's // hover trigger finds carriers by this attribute, and the megacolumn // controller answers otherwise-unclaimed i forwards for them. Components -// with an info property (sw-button) mirror it onto this attribute; +// with an info property (yk-button) mirror it onto this attribute; // plain markup writes the attribute directly. Empty means not a carrier. -// - `sw-info` (bubbles + composed) is the keyboard trigger's event: a producer -// that answered i dispatches it, sw-infopop renders it. The detail carries +// - `yk-info` (bubbles + composed) is the keyboard trigger's event: a producer +// that answered i dispatches it, yk-infopop renders it. The detail carries // the anchor element because composed events retarget: a listener outside // the shadow tree sees only the host chain, never the real source. // -// Producers today: sw-button (its own i claim, standalone and in rows) and +// Producers today: yk-button (its own i claim, standalone and in rows) and // MegacolumnController (the fallback for plain [data-info] rows). -export interface SwInfoDetail { +export interface YkInfoDetail { content: string; // The element to anchor the popup to (see the retargeting note above). source: HTMLElement; } -export function dispatchSwInfo(source: HTMLElement, content: string): void { +export function dispatchYkInfo(source: HTMLElement, content: string): void { source.dispatchEvent( - new CustomEvent<SwInfoDetail>("sw-info", { + new CustomEvent<YkInfoDetail>("yk-info", { detail: { content, source }, bubbles: true, composed: true, diff --git a/packages/yukey/src/sw-infopop.gallery.ts b/packages/yukey/src/yk-infopop.gallery.ts similarity index 80% rename from packages/yukey/src/sw-infopop.gallery.ts rename to packages/yukey/src/yk-infopop.gallery.ts index 15e5a42..6ba8abf 100644 --- a/packages/yukey/src/sw-infopop.gallery.ts +++ b/packages/yukey/src/yk-infopop.gallery.ts @@ -1,4 +1,4 @@ -// Gallery entry for sw-infopop: keyboard i and mouse hover raising the same +// Gallery entry for yk-infopop: keyboard i and mouse hover raising the same // authored content, the pin precedence, plain-row participation via data-info, // and the edge flip. The popup instance under demonstration is the gallery // shell's own -- one per app is the model, so the demos raise that one. @@ -7,13 +7,13 @@ import { css, html, LitElement, type TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import type { GalleryEntry } from "./gallery/entry.js"; import { MegacolumnController, type MegacolumnKeyDetail, megacolumnStyles } from "./megacolumn-controller.js"; -import "./sw-button.js"; +import "./yk-button.js"; -// A megacolumn where every row flavor carries info: sw-buttons answer i +// A megacolumn where every row flavor carries info: yk-buttons answer i // themselves; the plain text main, the slider row, and the aux x are answered // by the controller's [data-info] fallback. Hover hits the same attributes. -@customElement("swg-infopop-column") -export class SwgInfopopColumn extends LitElement { +@customElement("ykg-infopop-column") +export class YkgInfopopColumn extends LitElement { static styles = [ megacolumnStyles, css` @@ -21,17 +21,17 @@ export class SwgInfopopColumn extends LitElement { display: block; } [data-mc-container] { - border: 1px solid var(--sw-border-dim); - padding: var(--sw-space-md); + border: 1px solid var(--yk-border-dim); + padding: var(--yk-space-md); } [data-mc-row] { display: flex; align-items: center; - gap: var(--sw-space-sm); - min-height: var(--sw-hit-min-h); - padding: 2px var(--sw-space-sm); + gap: var(--yk-space-sm); + min-height: var(--yk-hit-min-h); + padding: 2px var(--yk-space-sm); } - [data-mc-row] sw-button:first-child { + [data-mc-row] yk-button:first-child { flex: 1; } .grow { @@ -40,31 +40,31 @@ export class SwgInfopopColumn extends LitElement { .meter { flex: 1; height: 8px; - border: 1px solid var(--sw-border-dim); + border: 1px solid var(--yk-border-dim); position: relative; } .meter .fill { position: absolute; inset: 0 auto 0 0; - background: var(--sw-accent); + background: var(--yk-accent); } .pct { - color: var(--sw-text-hi); + color: var(--yk-text-hi); width: 4ch; text-align: right; } [data-mc-aux] { - color: var(--sw-danger); - min-width: var(--sw-hit-min-w); - min-height: var(--sw-hit-min-h); + color: var(--yk-danger); + min-width: var(--yk-hit-min-w); + min-height: var(--yk-hit-min-h); display: inline-flex; align-items: center; justify-content: center; } .log { - margin-top: var(--sw-space-md); - color: var(--sw-good); - font-size: var(--sw-type-ui); + margin-top: var(--yk-space-md); + color: var(--yk-good); + font-size: var(--yk-type-ui); min-height: 1.4em; } `, @@ -105,14 +105,14 @@ export class SwgInfopopColumn extends LitElement { return html` <div data-mc-container aria-label=${this.label} @megacolumn-key=${this.onMegacolumnKey}> <div data-mc-row="dock"> - <sw-button + <yk-button label="Dock at station" hotkey="d" - info="an sw-button answers i itself; hover reads the same data-info mirror" + info="a yk-button answers i itself; hover reads the same data-info mirror" @click=${() => { this.last = "docked"; }} - ></sw-button> + ></yk-button> </div> <div data-mc-row="survey" data-info="a plain text row: the megacolumn answers i for its data-info"> <span class="grow">queue survey</span> @@ -134,8 +134,8 @@ export class SwgInfopopColumn extends LitElement { // Buttons pushed against the stage edges, so the preferred below-centered // placement has to clamp (sides) or flip (bottom edge, when the page is short). -@customElement("swg-infopop-edges") -export class SwgInfopopEdges extends LitElement { +@customElement("ykg-infopop-edges") +export class YkgInfopopEdges extends LitElement { static styles = css` :host { display: block; @@ -149,22 +149,22 @@ export class SwgInfopopEdges extends LitElement { render(): TemplateResult { return html` <div class="corners"> - <sw-button + <yk-button label="far left" info="anchored at the viewport's left: the popup clamps instead of centering" - ></sw-button> - <sw-button + ></yk-button> + <yk-button label="far right" info="anchored at the viewport's right: the popup clamps the other way" - ></sw-button> + ></yk-button> </div> `; } } const entry: GalleryEntry = { - id: "sw-infopop", - title: "sw-infopop", + id: "yk-infopop", + title: "yk-infopop", description: "The info norm's popup: keyboard i and mouse hover raise the SAME authored " + "content (one data-info attribute per element, two triggers). Keyboard-raised " + "popups pin until any key or click; hover popups follow the pointer and never " @@ -173,20 +173,20 @@ const entry: GalleryEntry = { variants: [ { caption: "Every row flavor carries info. Keyboard: focus the column, press i on each row -- " - + "the sw-button answers itself, the plain rows are answered by the megacolumn's data-info " + + "the yk-button answers itself, the plain rows are answered by the megacolumn's data-info " + "fallback, and on the probe row, right-arrow onto the × for its own story. " + "i again on the same row dismisses (a toggle); i after moving to another row follows. " + "Mouse: hover the same places and linger; the identical text appears. " + "Precedence: press i, then wave the mouse over other rows -- the pinned popup holds " + "until a key or click dismisses it. Navigation keys close the popup AND still navigate.", - render: () => html`<swg-infopop-column></swg-infopop-column>`, + render: () => html`<ykg-infopop-column></ykg-infopop-column>`, }, { caption: "Edge behavior: these buttons hug the stage's sides, so the below-centered placement " + "clamps horizontally to stay inside the viewport. Hover or focus-and-i near a viewport " + "edge (shrink the window height to see the bottom flip: the popup jumps above the anchor).", wide: true, - render: () => html`<swg-infopop-edges></swg-infopop-edges>`, + render: () => html`<ykg-infopop-edges></ykg-infopop-edges>`, }, ], }; diff --git a/packages/yukey/src/sw-infopop.test.ts b/packages/yukey/src/yk-infopop.test.ts similarity index 88% rename from packages/yukey/src/sw-infopop.test.ts rename to packages/yukey/src/yk-infopop.test.ts index b1736f0..b41373a 100644 --- a/packages/yukey/src/sw-infopop.test.ts +++ b/packages/yukey/src/yk-infopop.test.ts @@ -1,17 +1,17 @@ // @vitest-environment jsdom // -// Tests for sw-infopop (sw-infopop.ts): triggers, precedence, and dismissal. +// Tests for yk-infopop (yk-infopop.ts): triggers, precedence, and dismissal. // Geometry is NOT tested here (jsdom does no layout; rects are all zero) -- // the placement rule has its own pure tests in infopop-place.test.ts, and the // browser verification covers the real flip. Hover timing runs on fake timers. import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { dispatchSwInfo } from "./sw-info.js"; -import { HOVER_DELAY_MS, type SwInfopop } from "./sw-infopop.js"; -import "./sw-infopop.js"; +import { dispatchYkInfo } from "./yk-info.js"; +import { HOVER_DELAY_MS, type YkInfopop } from "./yk-infopop.js"; +import "./yk-infopop.js"; interface Rig { - pop: SwInfopop; + pop: YkInfopop; rowA: HTMLElement; rowB: HTMLElement; aux: HTMLElement; @@ -27,7 +27,7 @@ async function makeRig(): Promise<Rig> { <div id="rowB" data-info="beta's story">beta</div> <div id="bare">no info here</div> `; - const pop = document.createElement("sw-infopop"); + const pop = document.createElement("yk-infopop"); document.body.append(pop); await pop.updateComplete; const get = (id: string): HTMLElement => { @@ -55,7 +55,7 @@ function key(el: HTMLElement | Window, k: string): KeyboardEvent { return e; } -async function settle(pop: SwInfopop): Promise<void> { +async function settle(pop: YkInfopop): Promise<void> { await pop.updateComplete; } @@ -68,10 +68,10 @@ afterEach(() => { vi.useRealTimers(); }); -describe("the keyboard trigger (sw-info)", () => { +describe("the keyboard trigger (yk-info)", () => { it("opens pinned with the event's content, anchored to its source", async () => { const { pop, rowA } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); await settle(pop); expect(pop.mode).toBe("pinned"); expect(pop.anchor).toBe(rowA); @@ -80,10 +80,10 @@ describe("the keyboard trigger (sw-info)", () => { expect(pop.hasAttribute("aria-hidden")).toBe(false); }); - it("a later sw-info replaces the popup (new anchor, new content)", async () => { + it("a later yk-info replaces the popup (new anchor, new content)", async () => { const { pop, rowA, rowB } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); - dispatchSwInfo(rowB, "beta's story"); + dispatchYkInfo(rowA, "alpha's story"); + dispatchYkInfo(rowB, "beta's story"); await settle(pop); expect(pop.anchor).toBe(rowB); expect(pop.shadowRoot?.textContent).toContain("beta's story"); @@ -176,7 +176,7 @@ describe("the hover trigger", () => { document.body.append(host); const shell = host.attachShadow({ mode: "open" }); shell.innerHTML = `<div id="carrier" data-info="in-shell story">thing</div>`; - const pop = document.createElement("sw-infopop"); + const pop = document.createElement("yk-infopop"); shell.append(pop); await pop.updateComplete; const carrier = shell.getElementById("carrier"); @@ -200,7 +200,7 @@ describe("the hover trigger", () => { describe("precedence: keyboard pins", () => { it("hover never replaces a pinned popup", async () => { const { pop, rowA, rowB } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); hover(rowB); vi.advanceTimersByTime(HOVER_DELAY_MS * 2); await settle(pop); @@ -210,7 +210,7 @@ describe("precedence: keyboard pins", () => { it("pointer movement never closes a pinned popup", async () => { const { pop, rowA, bare } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); hover(bare); await settle(pop); expect(pop.mode).toBe("pinned"); @@ -220,7 +220,7 @@ describe("precedence: keyboard pins", () => { // The pointer sits on a carrier while a pinned popup has precedence; when a // key closes the pinned one, nothing should appear without a fresh action. const { pop, rowA, rowB } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); hover(rowB); vi.advanceTimersByTime(HOVER_DELAY_MS); // fires, sees pinned, skips key(window, "Escape"); @@ -235,7 +235,7 @@ describe("precedence: keyboard pins", () => { vi.advanceTimersByTime(HOVER_DELAY_MS); await settle(pop); expect(pop.mode).toBe("hover"); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); await settle(pop); expect(pop.mode).toBe("pinned"); expect(pop.anchor).toBe(rowA); @@ -245,7 +245,7 @@ describe("precedence: keyboard pins", () => { describe("dismissal", () => { it("any keydown closes, and the key is never consumed", async () => { const { pop, rowA } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); const e = key(rowA, "ArrowDown"); await settle(pop); expect(pop.mode).toBe("closed"); @@ -254,7 +254,7 @@ describe("dismissal", () => { it("Escape that closes the popup is CONSUMED (one Escape, one layer)", async () => { const { pop, rowA } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); const e = key(rowA, "Escape"); await settle(pop); expect(pop.mode).toBe("closed"); @@ -271,19 +271,19 @@ describe("dismissal", () => { it("bare modifiers close nothing", async () => { const { pop, rowA } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); key(rowA, "Shift"); await settle(pop); expect(pop.mode).toBe("pinned"); }); it("one keypress can close the old popup and raise the next (capture order)", async () => { - // The i norm in action: the source's keydown handler dispatches sw-info, + // The i norm in action: the source's keydown handler dispatches yk-info, // and the popup's window-capture close has already run by then. const { pop, rowA, rowB } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); rowB.addEventListener("keydown", (e) => { - if (e.key === "i") dispatchSwInfo(rowB, "beta's story"); + if (e.key === "i") dispatchYkInfo(rowB, "beta's story"); }); key(rowB, "i"); await settle(pop); @@ -296,7 +296,7 @@ describe("dismissal", () => { // element already anchoring the pinned popup: the user is asking it away. const { pop, rowA } = await makeRig(); rowA.addEventListener("keydown", (e) => { - if (e.key === "i") dispatchSwInfo(rowA, "alpha's story"); + if (e.key === "i") dispatchYkInfo(rowA, "alpha's story"); }); key(rowA, "i"); await settle(pop); @@ -309,7 +309,7 @@ describe("dismissal", () => { it("the toggle suppression is scoped to one keypress (i works again afterward)", async () => { const { pop, rowA } = await makeRig(); rowA.addEventListener("keydown", (e) => { - if (e.key === "i") dispatchSwInfo(rowA, "alpha's story"); + if (e.key === "i") dispatchYkInfo(rowA, "alpha's story"); }); key(rowA, "i"); key(rowA, "i"); // toggles off @@ -321,12 +321,12 @@ describe("dismissal", () => { it("i on a DIFFERENT anchor during the same keypress is never suppressed", async () => { // Guards the follow behavior against the toggle: the capture close sees - // key i + pinned mode there too, but the arriving sw-info names another + // key i + pinned mode there too, but the arriving yk-info names another // anchor, so it must open. const { pop, rowA, rowB } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); rowB.addEventListener("keydown", (e) => { - if (e.key === "i") dispatchSwInfo(rowB, "beta's story"); + if (e.key === "i") dispatchYkInfo(rowB, "beta's story"); }); key(rowB, "i"); await settle(pop); @@ -338,7 +338,7 @@ describe("dismissal", () => { // The keyboard upgrade of a hover popup: only PINNED popups toggle off. const { pop, rowA } = await makeRig(); rowA.addEventListener("keydown", (e) => { - if (e.key === "i") dispatchSwInfo(rowA, "alpha's story"); + if (e.key === "i") dispatchYkInfo(rowA, "alpha's story"); }); hover(rowA); vi.advanceTimersByTime(HOVER_DELAY_MS); @@ -352,7 +352,7 @@ describe("dismissal", () => { it("pointerdown anywhere closes", async () => { const { pop, rowA, bare } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); bare.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, composed: true })); await settle(pop); expect(pop.mode).toBe("closed"); @@ -360,7 +360,7 @@ describe("dismissal", () => { it("scroll closes (anchor rects go stale)", async () => { const { pop, rowA } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); document.dispatchEvent(new Event("scroll")); await settle(pop); expect(pop.mode).toBe("closed"); @@ -378,7 +378,7 @@ describe("announcement surface, never interaction", () => { it("returns to aria-hidden when closed again", async () => { const { pop, rowA } = await makeRig(); - dispatchSwInfo(rowA, "alpha's story"); + dispatchYkInfo(rowA, "alpha's story"); await settle(pop); key(window, "Escape"); await settle(pop); diff --git a/packages/yukey/src/sw-infopop.ts b/packages/yukey/src/yk-infopop.ts similarity index 91% rename from packages/yukey/src/sw-infopop.ts rename to packages/yukey/src/yk-infopop.ts index d879d7d..f233a4c 100644 --- a/packages/yukey/src/sw-infopop.ts +++ b/packages/yukey/src/yk-infopop.ts @@ -1,22 +1,22 @@ -// sw-infopop: the info norm's popup surface -- the keyboard's tooltip and the +// yk-infopop: the info norm's popup surface -- the keyboard's tooltip and the // mouse's tooltip as ONE element showing one authored content per anchor -// (the wiring SSOT -- the sw-info event and the data-info carrier attribute -- -// is sw-info.ts; the norm is docs/precepts-we-love-the-keyboard.md "info pops"). +// (the wiring SSOT -- the yk-info event and the data-info carrier attribute -- +// is yk-info.ts; the norm is docs/precepts-we-love-the-keyboard.md "info pops"). // // One per app: place it once in the app shell's render (the gallery shell hosts -// one today; sw-app hosts one at migration), and every sw-info event and every +// one), and every yk-info event and every // [data-info] hover in that tree raises it. Two triggers: // -// - Keyboard: an sw-info event (composed, so it bubbles here from any open +// - Keyboard: a yk-info event (composed, so it bubbles here from any open // shadow root below) opens the popup PINNED: pointer movement never closes // or replaces it. Any later keydown closes it -- heard in capture phase, // so the very key that closes an old popup can raise the next one (i on // another row closes-then-reopens), which is how the popup "follows" row // focus without needing focus-change events. One exception to re-raising: // i on the anchor the pinned popup already shows is a TOGGLE -- the close -// notes which anchor that keypress dismissed, and the matching sw-info +// notes which anchor that keypress dismissed, and the matching yk-info // arriving in the same dispatch is swallowed, so the second i dismisses -// instead of blinking closed-then-open. (A different anchor's sw-info, +// instead of blinking closed-then-open. (A different anchor's yk-info, // or any i once that keypress ends, opens normally; an unpinned hover // popup never toggles -- i upgrades it to pinned.) No key is consumed // except the one Escape that closes the popup (Escape unwinds one layer @@ -48,7 +48,7 @@ import { css, html, LitElement, type TemplateResult } from "lit"; import { customElement, state } from "lit/decorators.js"; import { placeInfopop } from "./infopop-place.js"; -import type { SwInfoDetail } from "./sw-info.js"; +import type { YkInfoDetail } from "./yk-info.js"; export const HOVER_DELAY_MS = 350; @@ -60,8 +60,8 @@ interface AriaReflection { ariaDescribedByElements: Element[] | null; } -@customElement("sw-infopop") -export class SwInfopop extends LitElement { +@customElement("yk-infopop") +export class YkInfopop extends LitElement { static styles = css` :host { position: fixed; @@ -72,11 +72,11 @@ export class SwInfopop extends LitElement { visibility: hidden; pointer-events: none; box-sizing: border-box; - padding: var(--sw-space-sm) var(--sw-space-md); - background: var(--sw-bg-raised); - border: 1px solid var(--sw-border); - color: var(--sw-text-hi); - font: var(--sw-type-body) var(--sw-font); + padding: var(--yk-space-sm) var(--yk-space-md); + background: var(--yk-bg-raised); + border: 1px solid var(--yk-border); + color: var(--yk-text-hi); + font: var(--yk-type-body) var(--yk-font); } `; @@ -90,7 +90,7 @@ export class SwInfopop extends LitElement { #timer: number | undefined; #root: Node | null = null; // Set when an i keydown dismisses a pinned popup: the producer answering that - // same keypress will usually dispatch a fresh sw-info for the same anchor + // same keypress will usually dispatch a fresh yk-info for the same anchor // (our capture close runs first), and swallowing that arrival is what makes // i a toggle. Scoped by the keydown event itself: its eventPhase returns to // NONE once its dispatch ends, so a later i escapes suppression naturally, @@ -117,7 +117,7 @@ export class SwInfopop extends LitElement { // never dispatched here) -- a row-to-aux move inside a component would be // invisible. pointermove has no relatedTarget, is never suppressed, and // its composedPath still names the deep target through open shadow roots. - root.addEventListener("sw-info", this.#onSwInfo); + root.addEventListener("yk-info", this.#onYkInfo); root.addEventListener("pointermove", this.#onPointermove); root.addEventListener("pointerout", this.#onPointerout); // Dismissals, global by nature. Capture phase: closing must precede the @@ -134,7 +134,7 @@ export class SwInfopop extends LitElement { this.#dismissal = null; const root = this.#root; if (root !== null) { - root.removeEventListener("sw-info", this.#onSwInfo); + root.removeEventListener("yk-info", this.#onYkInfo); root.removeEventListener("pointermove", this.#onPointermove); root.removeEventListener("pointerout", this.#onPointerout); this.#root = null; @@ -144,9 +144,9 @@ export class SwInfopop extends LitElement { window.removeEventListener("scroll", this.#onScroll, { capture: true }); } - #onSwInfo = (e: Event): void => { - const { content, source } = (e as CustomEvent<SwInfoDetail>).detail; - // The toggle's second half: this sw-info answers the very keypress that + #onYkInfo = (e: Event): void => { + const { content, source } = (e as CustomEvent<YkInfoDetail>).detail; + // The toggle's second half: this yk-info answers the very keypress that // just dismissed a pinned popup on the same anchor (that keydown is still // mid-dispatch), so it is a dismissal confirmed, not a request to reopen. const d = this.#dismissal; @@ -210,7 +210,7 @@ export class SwInfopop extends LitElement { // popup must still navigate; that is how the popup follows row focus). if (e.key === "Shift" || e.key === "Control" || e.key === "Alt" || e.key === "Meta") return; // The toggle's first half: i closing a pinned popup notes which anchor this - // keypress dismissed, so #onSwInfo can tell a same-anchor re-raise (toggle + // keypress dismissed, so #onYkInfo can tell a same-anchor re-raise (toggle // off) from a different-anchor follow (reopen there). if (this.#mode === "pinned" && this.#anchor !== null && e.key.toLowerCase() === "i") { this.#dismissal = { anchor: this.#anchor, keydown: e }; @@ -304,6 +304,6 @@ function nearestCarrier(path: EventTarget[]): HTMLElement | null { declare global { interface HTMLElementTagNameMap { - "sw-infopop": SwInfopop; + "yk-infopop": YkInfopop; } } diff --git a/packages/yukey/src/sw-list.gallery.test.ts b/packages/yukey/src/yk-list.gallery.test.ts similarity index 85% rename from packages/yukey/src/sw-list.gallery.test.ts rename to packages/yukey/src/yk-list.gallery.test.ts index 5fa3731..93039c0 100644 --- a/packages/yukey/src/sw-list.gallery.test.ts +++ b/packages/yukey/src/yk-list.gallery.test.ts @@ -1,11 +1,11 @@ // @vitest-environment jsdom // -// Tests for the sw-list gallery demo AS A CONSUMER of the component: the owner's -// commit handler (a cancelable sw-list-add append), the task-key routing that must -// skip sw-list's structural rows, and the composed behaviors only a full console +// Tests for the yk-list gallery demo AS A CONSUMER of the component: the owner's +// commit handler (a cancelable yk-list-add append), the task-key routing that must +// skip yk-list's structural rows, and the composed behaviors only a full console // exhibits (the churn lockout swallowing t mid-fallback). The norm MECHANICS -- -// dive/commit/abandon, t/n resolution, group identity -- are sw-list's own and are -// pinned in sw-list.test.ts; this file deliberately does not re-test them. +// dive/commit/abandon, t/n resolution, group identity -- are yk-list's own and are +// pinned in yk-list.test.ts; this file deliberately does not re-test them. // // jsdom note: real browsers fire `change` on blur-with-changed-value and both // text-mode exits blur the input, so the abandon test dispatches the change event @@ -13,11 +13,11 @@ // untouched even then. The trusted-input version runs in lc-drive.py. import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import "./sw-list.gallery.js"; -import type { SwList } from "./sw-list.js"; +import "./yk-list.gallery.js"; import { installTokens } from "./tokens.js"; +import type { YkList } from "./yk-list.js"; -// The task removal path flashes the x (press-flash resolves --sw-press-ms from +// The task removal path flashes the x (press-flash resolves --yk-press-ms from // the live cascade and throws when it can't), so the tokens must be installed. installTokens(); @@ -25,7 +25,7 @@ interface Demo extends HTMLElement { updateComplete: Promise<boolean>; } -// jsdom has no matchMedia (sw-live-list asks about prefers-reduced-motion) and +// jsdom has no matchMedia (yk-live-list asks about prefers-reduced-motion) and // implements no scrollIntoView (the controller scrolls on focus identity change). beforeEach(() => { vi.useFakeTimers(); @@ -41,7 +41,7 @@ interface Rig { demo: Demo; root: ShadowRoot; container: HTMLElement; - list: SwList; + list: YkList; input: HTMLInputElement; key: (key: string, target?: HTMLElement) => KeyboardEvent; focusedRow: () => string | null; @@ -52,22 +52,22 @@ interface Rig { // The drain variant (no refill attribute): starts empty, and its drain interval // never fires under fake timers, so every task in the list is one a test put there. async function makeRig(): Promise<Rig> { - const demo = document.createElement("swg-livecolumn-demo") as Demo; + const demo = document.createElement("ykg-livecolumn-demo") as Demo; document.body.appendChild(demo); await demo.updateComplete; const root = demo.shadowRoot; if (root === null) throw new Error("demo did not render a shadow root"); const container = root.querySelector<HTMLElement>("[data-mc-container]"); - const list = root.querySelector<SwList>("sw-list"); - if (container === null || list === null) throw new Error("demo missing container or sw-list"); + const list = root.querySelector<YkList>("yk-list"); + if (container === null || list === null) throw new Error("demo missing container or yk-list"); await list.updateComplete; const input = list.querySelector<HTMLInputElement>("[data-mc-input]"); if (input === null) throw new Error("demo missing the add input"); - // The task-removal path flashes the x (press-flash resolves --sw-press-ms from + // The task-removal path flashes the x (press-flash resolves --yk-press-ms from // the live cascade and throws when it can't), and jsdom does not carry custom // properties from the document's token stylesheet across the demo's shadow // boundary -- so seed the property inline, inside the shadow tree. - container.style.setProperty("--sw-press-ms", "140ms"); + container.style.setProperty("--yk-press-ms", "140ms"); const key = (k: string, target: HTMLElement = container): KeyboardEvent => { const e = new KeyboardEvent("keydown", { key: k, bubbles: true, cancelable: true }); target.dispatchEvent(e); @@ -87,19 +87,19 @@ async function makeRig(): Promise<Rig> { // Seed one task through the add-row (the drain queue starts empty) and land row // focus back on it. const seedTask = async (rig: Rig, label: string): Promise<void> => { - rig.key("a"); // sw-list's marked direct hotkey: dives into text entry + rig.key("a"); // yk-list's marked direct hotkey: dives into text entry rig.input.value = label; rig.key("Enter", rig.input); await rig.settle(); }; -describe("the demo as an sw-list consumer", () => { - it("composes the console: prepend row, sw-list (header first), status row after", async () => { +describe("the demo as a yk-list consumer", () => { + it("composes the console: prepend row, yk-list (header first), status row after", async () => { const { container, key, focusedRow } = await makeRig(); key("Home"); expect(focusedRow()).toBe("prepend"); key("ArrowDown"); - expect(focusedRow()).toBe("queue-header"); // sw-list's structural id: name="queue" + expect(focusedRow()).toBe("queue-header"); // yk-list's structural id: name="queue" expect(container.getAttribute("aria-activedescendant")).toBe("mc-queue-header"); key("End"); expect(focusedRow()).toBe("status"); @@ -113,7 +113,7 @@ describe("the demo as an sw-list consumer", () => { rig.key("Enter", rig.input); await rig.settle(); expect(rig.taskLabels()).toEqual(["first", "second"]); - const rows = [...rig.list.querySelectorAll<HTMLElement>("sw-live-list [data-mc-row]")]; + const rows = [...rig.list.querySelectorAll<HTMLElement>("yk-live-list [data-mc-row]")]; expect(rows[1]?.querySelector(".origin")?.classList.contains("player")).toBe(true); expect(rig.root.querySelector(".log")?.textContent).toBe("second appended"); }); @@ -131,7 +131,7 @@ describe("the demo as an sw-list consumer", () => { expect(rig.root.querySelector(".log")?.textContent).toBe(logBefore); }); - it("task keys skip sw-list's structural rows (header and add-row decline quietly)", async () => { + it("task keys skip yk-list's structural rows (header and add-row decline quietly)", async () => { const rig = await makeRig(); const logBefore = rig.root.querySelector(".log")?.textContent; rig.key("Home"); @@ -146,10 +146,10 @@ describe("the demo as an sw-list consumer", () => { expect(rig.root.querySelector(".log")?.textContent).toBe(logBefore); }); - it("x on a task row removes it (the owner's key semantics ride through sw-list)", async () => { + it("x on a task row removes it (the owner's key semantics ride through yk-list)", async () => { const rig = await makeRig(); await seedTask(rig, "doomed"); - rig.key("t"); // sw-list's jump to the header + rig.key("t"); // yk-list's jump to the header rig.key("ArrowDown"); // the task expect(rig.focusedRow()).toBe("t1"); rig.key("x"); diff --git a/packages/yukey/src/sw-list.gallery.ts b/packages/yukey/src/yk-list.gallery.ts similarity index 82% rename from packages/yukey/src/sw-list.gallery.ts rename to packages/yukey/src/yk-list.gallery.ts index 69b0b29..fba15df 100644 --- a/packages/yukey/src/sw-list.gallery.ts +++ b/packages/yukey/src/yk-list.gallery.ts @@ -1,12 +1,11 @@ -// Gallery entry for the live megacolumn: sw-list (the list region: header row, +// Gallery entry for the live megacolumn: yk-list (the list region: header row, // live rows, add-row, t/n) composed into a megacolumn console. This demo is the // rehearsal for the infocard's task queue: a queue that churns on its own (heads // complete, assignments refill) AND under player command (prepend, remove, append), // fully keyboard-drivable, with focus surviving every kind of row death. The demo // is deliberately a THIN consumer: it owns the data, the row rendering, and what -// the keys MEAN on task rows; sw-list owns the list norms (sw-list.ts holds that -// contract); sw-live-list under it owns row lifecycle. Copy this shape at -// migration time. +// the keys MEAN on task rows; yk-list owns the list norms (yk-list.ts holds that +// contract); yk-live-list under it owns row lifecycle. Copy this shape. import { css, html, LitElement, nothing, type TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -14,10 +13,10 @@ import { classMap } from "lit/directives/class-map.js"; import type { GalleryEntry } from "./gallery/entry.js"; import { MegacolumnController, type MegacolumnKeyDetail, megacolumnStyles } from "./megacolumn-controller.js"; import { flashPress } from "./press-flash.js"; -import type { SwListAddDetail } from "./sw-list.js"; -import type { LiveListPhase } from "./sw-live-list.js"; -import "./sw-list.js"; -import "./sw-button.js"; +import type { YkListAddDetail } from "./yk-list.js"; +import type { LiveListPhase } from "./yk-live-list.js"; +import "./yk-list.js"; +import "./yk-button.js"; interface QueueTask { id: string; @@ -28,10 +27,10 @@ interface QueueTask { const taskKey = (t: QueueTask): string => t.id; // The task-queue console: one megacolumn whose lower section is a live list. -// The list stamps row identity itself (sw-live-list.ts holds that contract); +// The list stamps row identity itself (yk-live-list.ts holds that contract); // this owner renders row CONTENT (main/aux/data-info) and gives the keys meaning. -@customElement("swg-livecolumn-demo") -export class SwgLivecolumnDemo extends LitElement { +@customElement("ykg-livecolumn-demo") +export class YkgLivecolumnDemo extends LitElement { static styles = [ megacolumnStyles, css` @@ -39,40 +38,40 @@ export class SwgLivecolumnDemo extends LitElement { display: block; } [data-mc-container] { - border: 1px solid var(--sw-border-dim); - padding: var(--sw-space-md); + border: 1px solid var(--yk-border-dim); + padding: var(--yk-space-md); } .title { - color: var(--sw-text-dim); - font-size: var(--sw-type-small); - letter-spacing: var(--sw-tracking-wide); + color: var(--yk-text-dim); + font-size: var(--yk-type-small); + letter-spacing: var(--yk-tracking-wide); text-transform: uppercase; - margin-bottom: var(--sw-space-sm); + margin-bottom: var(--yk-space-sm); } .section { - color: var(--sw-text-faint); - font-size: var(--sw-type-micro); - letter-spacing: var(--sw-tracking-wide); + color: var(--yk-text-faint); + font-size: var(--yk-type-micro); + letter-spacing: var(--yk-tracking-wide); text-transform: uppercase; - margin: var(--sw-space-xl) 0 var(--sw-space-xs); + margin: var(--yk-space-xl) 0 var(--yk-space-xs); } .section:first-of-type { margin-top: 0; } - /* The list norms (header wash, ghosted add-row, hotkey gold) are sw-list's + /* The list norms (header wash, ghosted add-row, hotkey gold) are yk-list's own styles now; this sheet only spaces the list within the console. */ - sw-list { - margin-top: var(--sw-space-xl); + yk-list { + margin-top: var(--yk-space-xl); } [data-mc-row] { - min-height: var(--sw-hit-min-h); + min-height: var(--yk-hit-min-h); } .action-row { display: flex; align-items: center; - padding: 2px var(--sw-space-sm); + padding: 2px var(--yk-space-sm); } - .action-row sw-button { + .action-row yk-button { flex: 1; } /* Task-row content (rendered by the live list into this shadow root, @@ -80,9 +79,9 @@ export class SwgLivecolumnDemo extends LitElement { .task { display: flex; align-items: center; - gap: var(--sw-space-sm); - min-height: var(--sw-hit-min-h); - padding: 2px var(--sw-space-sm); + gap: var(--yk-space-sm); + min-height: var(--yk-hit-min-h); + padding: 2px var(--yk-space-sm); white-space: nowrap; } .task.committed { @@ -91,11 +90,11 @@ export class SwgLivecolumnDemo extends LitElement { .task .origin { flex: none; width: 88px; - font-size: var(--sw-type-small); - color: var(--sw-text-faint); + font-size: var(--yk-type-small); + color: var(--yk-text-faint); } .task .origin.player { - color: var(--sw-good); + color: var(--yk-good); } .task .grow { flex: 1; @@ -110,40 +109,40 @@ export class SwgLivecolumnDemo extends LitElement { not [data-mc-aux]: the ghost drops that attribute in the same re-render, and attribute-keyed styling would un-match mid-flash. */ .task .glyph { - color: var(--sw-danger); + color: var(--yk-danger); border: 1px solid transparent; - min-width: var(--sw-hit-min-w); - min-height: var(--sw-hit-min-h); + min-width: var(--yk-hit-min-w); + min-height: var(--yk-hit-min-h); display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; } .task .glyph.gone { - color: var(--sw-good); + color: var(--yk-good); } /* The aux's own pressed look: danger-family, not the blue activation wash -- ownership distinguishes the flashes, no enumeration of activation kinds. */ - .task .glyph.sw-pressed { - background: hsl(from var(--sw-danger) h calc(s * 2) calc(l * 0.75) / 0.6); - color: var(--sw-text-bright); + .task .glyph.yk-pressed { + background: hsl(from var(--yk-danger) h calc(s * 2) calc(l * 0.75) / 0.6); + color: var(--yk-text-bright); } .idle { - color: var(--sw-text-faint); - padding: 2px var(--sw-space-sm); - line-height: var(--sw-hit-min-h); + color: var(--yk-text-faint); + padding: 2px var(--yk-space-sm); + line-height: var(--yk-hit-min-h); } .status-row { display: flex; align-items: center; - color: var(--sw-text-dim); - padding: 2px var(--sw-space-sm); - margin-top: var(--sw-space-xl); + color: var(--yk-text-dim); + padding: 2px var(--yk-space-sm); + margin-top: var(--yk-space-xl); } .log { - margin-top: var(--sw-space-md); - color: var(--sw-good); - font-size: var(--sw-type-ui); + margin-top: var(--yk-space-md); + color: var(--yk-good); + font-size: var(--yk-type-ui); min-height: 1.4em; } `, @@ -208,24 +207,24 @@ export class SwgLivecolumnDemo extends LitElement { this.lastAction = `${task.label} prepended`; } - // The add-row's commit, arriving as sw-list's cancelable sw-list-add event - // (sw-list owns the dive/abandon/ghost mechanics and the nothing-syncs-until- + // The add-row's commit, arriving as yk-list's cancelable yk-list-add event + // (yk-list owns the dive/abandon/ghost mechanics and the nothing-syncs-until- // commit contract; the owner just appends). Not cancelled here: any trimmed // non-empty text is a legal task. private onListAdd(e: Event): void { - const { text } = (e as CustomEvent<SwListAddDetail>).detail; + const { text } = (e as CustomEvent<YkListAddDetail>).detail; this.seq += 1; this.tasks = [...this.tasks, { id: `t${this.seq}`, label: text, origin: "player" }]; this.lastAction = `${text} appended`; } - // The queue rows' key semantics (the prepend row's keys are sw-button's own + // The queue rows' key semantics (the prepend row's keys are yk-button's own // business -- direct-marked, it claims them before this handler could matter): // x/Delete from anywhere in the row removes; Enter/Space on the aux removes; // Enter/Space on main inspects (activation must not be destructive). private onMegacolumnKey(e: Event): void { const { detail } = e as CustomEvent<MegacolumnKeyDetail>; - // Only task rows have task semantics: sw-list's structural rows (the + // Only task rows have task semantics: yk-list's structural rows (the // "<name>-header"/"<name>-add" id scheme is its contract) and the demo's // own prepend/status rows decline everything here (the add-row's typing // never reaches this handler; text entry dominates). @@ -307,20 +306,20 @@ export class SwgLivecolumnDemo extends LitElement { <!-- Direct-marked (the deliberate exception to hotkeys-move-focus): a prepend is cheaply undone -- x removes the new head -- so the see-before-commit step would only slow the order down. --> - <sw-button + <yk-button label="prepend priority task" hotkey="p" hotkey-direct info="queues a player task at the HEAD of the queue; watch it appear instantly even mid-keyboard-burst" @click=${() => this.prependTask()} - ></sw-button> + ></yk-button> </div> - <!-- The list region: sw-list owns the norms (header row speaking for the + <!-- The list region: yk-list owns the norms (header row speaking for the region, ghosted add-row with the marked direct a, t/n jumps); this owner supplies data, row rendering, and the commit handler. The - norm teaching in the info texts is sw-list's own appendix. --> - <sw-list + norm teaching in the info texts is yk-list's own appendix. --> + <yk-list name="queue" header="task queue (live)" header-info="the task queue; every row is removable -- x or Delete" @@ -330,8 +329,8 @@ export class SwgLivecolumnDemo extends LitElement { .keyOf=${taskKey} .renderItem=${this.renderTask} .renderEmpty=${this.renderQueueEmpty} - @sw-list-add=${this.onListAdd} - ></sw-list> + @yk-list-add=${this.onListAdd} + ></yk-list> <!-- A stop AFTER the list, so n has somewhere to land (and the demo shows what n is for: hopping past a long list without arrowing through @@ -350,10 +349,10 @@ export class SwgLivecolumnDemo extends LitElement { } const entry: GalleryEntry = { - id: "sw-list", - title: "sw-list: the live megacolumn's list region", + id: "yk-list", + title: "yk-list: the live megacolumn's list region", description: "The list region component, composed here into a megacolumn console: header row, live rows " - + "(sw-live-list inside), ghosted add-row, and the t/n list-scoped jumps -- the list norms as " + + "(yk-live-list inside), ghosted add-row, and the t/n list-scoped jumps -- the list norms as " + "things the component does. The queue churns and stays fully keyboard-drivable: ghost rows " + "stay navigable while they hold (a stationary ghost is safe for the keyboard exactly as it " + "is for the mouse); collapsing rows leave navigation and focus falls to a neighbor under a " @@ -370,7 +369,7 @@ const entry: GalleryEntry = { + "Removals flash the × itself, danger-tinted, before the row ghosts (x/Delete removes a task from anywhere in its row; " + "right-arrow reaches the ×; Enter on a ghost still inspects it), and if focus was on a ghost " + "when it collapsed, focus falls to the next row under a brief decisive-key lockout -- hammer " - + "Enter across the collapse and note nothing mis-fires. The LIST NORMS are sw-list's own " + + "Enter across the collapse and note nothing mis-fires. The LIST NORMS are yk-list's own " + "behavior now: a header row (arrow onto it and the " + "whole region washes -- group identity without a nested container; i there describes the list as " + "a list) and the add-row at the tail. Two ways to add, both earning the direct marking under " @@ -379,19 +378,19 @@ const entry: GalleryEntry = { + "(focus following to the orders row), justified because a prepend is cheaply undone -- x the new " + "head and it is gone. The add-row's a dives straight into typing (direct on a text row means " + "what Enter means there), justified because nothing touches the queue until the Enter commit " - + "(the owner hears a cancelable sw-list-add and appends; watch the task animate in) and Escape " + + "(the owner hears a cancelable yk-list-add and appends; watch the task animate in) and Escape " + "abandons the draft. While focus is anywhere " + "in the list -- header, tasks, add-row -- t jumps back up to the header and n jumps past the " - + "list to the next stop after it: list-scoped keys sw-list wires itself, deliberately not " + + "list to the next stop after it: list-scoped keys yk-list wires itself, deliberately not " + "gold-marked (they mean the same thing in every list).", - render: () => html`<swg-livecolumn-demo refill></swg-livecolumn-demo>`, + render: () => html`<ykg-livecolumn-demo refill></ykg-livecolumn-demo>`, }, { caption: "Drain to empty: nothing refills this queue. Prepend a few tasks (p), watch them complete and " + "drain away; the empty state is the owner's renderEmpty render-prop (mega mode has no slots). " + "Focus stays sane through the last row's death: the column itself keeps focus and the arrows " + "revive it the moment a row exists again.", - render: () => html`<swg-livecolumn-demo label="drain queue"></swg-livecolumn-demo>`, + render: () => html`<ykg-livecolumn-demo label="drain queue"></ykg-livecolumn-demo>`, }, ], }; diff --git a/packages/yukey/src/sw-list.test.ts b/packages/yukey/src/yk-list.test.ts similarity index 89% rename from packages/yukey/src/sw-list.test.ts rename to packages/yukey/src/yk-list.test.ts index d63c578..30ccf92 100644 --- a/packages/yukey/src/sw-list.test.ts +++ b/packages/yukey/src/yk-list.test.ts @@ -1,9 +1,9 @@ // @vitest-environment jsdom // -// Tests for sw-list: the list region component (header row, live rows, add-row, +// Tests for yk-list: the list region component (header row, live rows, add-row, // t/n list-scoped keys). The rig is a minimal owner: a Lit host with a // MegacolumnController, one row before the list, one after (n's landing), and -// an sw-list whose knobs the tests flip per case. The add-row's commit/abandon +// a yk-list whose knobs the tests flip per case. The add-row's commit/abandon // distinction is the subtle part: real browsers fire `change` on // blur-with-changed-value and BOTH text-mode exits blur the input, so a commit // hooked to @change would fire on Escape too -- jsdom fires no change on its @@ -14,16 +14,16 @@ import { html, LitElement, nothing, type TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { MegacolumnController, megacolumnStyles } from "./megacolumn-controller.js"; -import type { SwList, SwListAddDetail } from "./sw-list.js"; -import type { LiveListPhase } from "./sw-live-list.js"; -import "./sw-list.js"; +import type { YkList, YkListAddDetail } from "./yk-list.js"; +import type { LiveListPhase } from "./yk-live-list.js"; +import "./yk-list.js"; interface Item { id: string; label: string; } -// jsdom has no matchMedia (sw-live-list asks about prefers-reduced-motion) and +// jsdom has no matchMedia (yk-live-list asks about prefers-reduced-motion) and // implements no scrollIntoView (the controller scrolls on focus identity change). beforeEach(() => { vi.useFakeTimers(); @@ -35,10 +35,10 @@ afterEach(() => { }); Element.prototype.scrollIntoView = (): void => {}; -// The minimal owner. `withAfter` controls whether n has a landing; the sw-list +// The minimal owner. `withAfter` controls whether n has a landing; the yk-list // knobs pass straight through. -@customElement("swt-list-owner") -class SwtListOwner extends LitElement { +@customElement("ykt-list-owner") +class YktListOwner extends LitElement { static styles = [megacolumnStyles]; // biome-ignore lint/correctness/noUnusedPrivateClassMembers: wires itself via addController private mc = new MegacolumnController(this); @@ -56,7 +56,7 @@ class SwtListOwner extends LitElement { render(): TemplateResult { return html`<div data-mc-container aria-label="test column"> <div data-mc-row="before" data-info="the row before the list">before</div> - <sw-list + <yk-list name="tl" header=${this.header} header-info="a list of test items" @@ -66,39 +66,39 @@ class SwtListOwner extends LitElement { .items=${this.items} .keyOf=${(it: Item) => it.id} .renderItem=${(it: Item, _phase: LiveListPhase) => html`<div class="row-content">${it.label}</div>`} - ></sw-list> + ></yk-list> ${this.withAfter ? html`<div data-mc-row="after">after</div>` : nothing} </div>`; } } interface Rig { - owner: SwtListOwner; + owner: YktListOwner; root: ShadowRoot; container: HTMLElement; - list: SwList<Item>; + list: YkList<Item>; input: HTMLInputElement | null; key: (key: string, target?: HTMLElement) => KeyboardEvent; focusedRow: () => string | null; - adds: SwListAddDetail[]; + adds: YkListAddDetail[]; settle: () => Promise<void>; } -async function makeRig(setup?: (owner: SwtListOwner) => void): Promise<Rig> { - const owner = document.createElement("swt-list-owner") as SwtListOwner; +async function makeRig(setup?: (owner: YktListOwner) => void): Promise<Rig> { + const owner = document.createElement("ykt-list-owner") as YktListOwner; setup?.(owner); document.body.appendChild(owner); await owner.updateComplete; const root = owner.shadowRoot; if (root === null) throw new Error("owner did not render a shadow root"); const container = root.querySelector<HTMLElement>("[data-mc-container]"); - const list = root.querySelector<SwList<Item>>("sw-list"); - if (container === null || list === null) throw new Error("rig missing container or sw-list"); + const list = root.querySelector<YkList<Item>>("yk-list"); + if (container === null || list === null) throw new Error("rig missing container or yk-list"); await list.updateComplete; const input = list.querySelector<HTMLInputElement>("[data-mc-input]"); - const adds: SwListAddDetail[] = []; - list.addEventListener("sw-list-add", (e) => { - adds.push((e as CustomEvent<SwListAddDetail>).detail); + const adds: YkListAddDetail[] = []; + list.addEventListener("yk-list-add", (e) => { + adds.push((e as CustomEvent<YkListAddDetail>).detail); }); const key = (k: string, target: HTMLElement = container): KeyboardEvent => { const e = new KeyboardEvent("keydown", { key: k, bubbles: true, cancelable: true }); @@ -121,16 +121,16 @@ describe("structure", () => { const { list } = await makeRig(); const rows = [...list.querySelectorAll<HTMLElement>("[data-mc-row]")]; expect(rows.map((r) => r.dataset.mcRow)).toEqual(["tl-header", "tl-add"]); - expect(list.querySelector("sw-live-list")).not.toBeNull(); - expect(list.querySelector(".sw-list-header")?.textContent).toBe("the test list"); + expect(list.querySelector("yk-live-list")).not.toBeNull(); + expect(list.querySelector(".yk-list-header")?.textContent).toBe("the test list"); }); - it("passes items through to the inner sw-live-list (rows render, keyed)", async () => { + it("passes items through to the inner yk-live-list (rows render, keyed)", async () => { const { owner, list, settle } = await makeRig((o) => { o.items = items("a", "b"); }); await settle(); - const live = list.querySelector("sw-live-list"); + const live = list.querySelector("yk-live-list"); await (live as LitElement | null)?.updateComplete; const labels = [...list.querySelectorAll(".row-content")].map((el) => el.textContent); expect(labels).toEqual(["item a", "item b"]); @@ -146,7 +146,7 @@ describe("structure", () => { o.addable = false; }); expect(list.querySelectorAll("[data-mc-row]").length).toBe(0); - expect(list.querySelector("sw-live-list")).not.toBeNull(); + expect(list.querySelector("yk-live-list")).not.toBeNull(); }); it("the header row is a nav stop the controller sees (rescan announced)", async () => { @@ -159,9 +159,9 @@ describe("structure", () => { }); it("announces its own rescan: a structural row appearing without a live-list update", async () => { - // Toggling addable re-renders sw-list but not the inner sw-live-list (the + // Toggling addable re-renders yk-list but not the inner yk-live-list (the // items reference is unchanged, so its properties report no change) -- the - // add-row's appearance is on sw-list's OWN announcement, nobody else's. + // add-row's appearance is on yk-list's OWN announcement, nobody else's. const { list, key, focusedRow, settle } = await makeRig((o) => { o.addable = false; }); @@ -176,19 +176,19 @@ describe("structure", () => { it("composes owner info with the norm teaching on both structural rows", async () => { const { list } = await makeRig(); - const headerInfo = list.querySelector<HTMLElement>(".sw-list-header")?.dataset.info ?? ""; + const headerInfo = list.querySelector<HTMLElement>(".yk-list-header")?.dataset.info ?? ""; expect(headerInfo).toContain("a list of test items"); expect(headerInfo).toContain("t returns here"); expect(headerInfo).toContain("n jumps past the list"); expect(headerInfo).toContain("a dives into the add-row"); - const addInfo = list.querySelector<HTMLElement>(".sw-list-add")?.dataset.info ?? ""; + const addInfo = list.querySelector<HTMLElement>(".yk-list-add")?.dataset.info ?? ""; expect(addInfo).toContain("appends a test item"); expect(addInfo).toContain("Escape abandons"); }); it("marks the add hotkey letter in the label", async () => { const { list } = await makeRig(); - const hk = list.querySelector(".sw-list-add .sw-list-hk"); + const hk = list.querySelector(".yk-list-add .yk-list-hk"); expect(hk?.textContent).toBe("a"); const marked = list.querySelector<HTMLElement>("[data-mc-hotkey]"); expect(marked?.dataset.mcHotkey).toBe("a"); @@ -201,7 +201,7 @@ describe("owner-rendered header content (renderHeader)", () => { const { list, container, key, focusedRow } = await makeRig((o) => { o.renderHeader = () => html`<div class="fancy">THE LIST · 3 / 9</div>`; }); - const headerRow = list.querySelector<HTMLElement>(".sw-list-header"); + const headerRow = list.querySelector<HTMLElement>(".yk-list-header"); expect(headerRow?.querySelector(".fancy")?.textContent).toBe("THE LIST · 3 / 9"); expect(headerRow?.dataset.mcRow).toBe("tl-header"); key("Home"); @@ -223,7 +223,7 @@ describe("owner-rendered header content (renderHeader)", () => { o.header = ""; o.renderHeader = () => html`<div class="fancy">orphan content</div>`; }); - expect(list.querySelector(".sw-list-header")).toBeNull(); + expect(list.querySelector(".yk-list-header")).toBeNull(); expect(list.querySelector(".fancy")).toBeNull(); }); }); @@ -233,7 +233,7 @@ describe("group identity (the header-role decision)", () => { const { list } = await makeRig(); expect(list.getAttribute("role")).toBe("group"); expect(list.getAttribute("aria-label")).toBe("the test list"); - expect(list.querySelector(".sw-list-header")?.getAttribute("role")).toBe("option"); + expect(list.querySelector(".yk-list-header")?.getAttribute("role")).toBe("option"); }); it("headerless: no group role, no label", async () => { @@ -245,11 +245,11 @@ describe("group identity (the header-role decision)", () => { }); it("an author-set role is left alone", async () => { - const owner = document.createElement("swt-list-owner") as SwtListOwner; + const owner = document.createElement("ykt-list-owner") as YktListOwner; document.body.appendChild(owner); await owner.updateComplete; - const list = owner.shadowRoot?.querySelector<SwList<Item>>("sw-list"); - if (list === undefined || list === null) throw new Error("no sw-list"); + const list = owner.shadowRoot?.querySelector<YkList<Item>>("yk-list"); + if (list === undefined || list === null) throw new Error("no yk-list"); // Re-run the role logic with an author role in place: simulate by clearing // ours, setting an author's, and forcing an update. list.removeAttribute("role"); @@ -314,7 +314,7 @@ describe("the add-row", () => { it("a refused commit (preventDefault) keeps the draft and text mode", async () => { const { root, container, list, input, key } = await makeRig(); if (input === null) throw new Error("no input"); - list.addEventListener("sw-list-add", (e) => e.preventDefault(), { once: true }); + list.addEventListener("yk-list-add", (e) => e.preventDefault(), { once: true }); key("a"); input.value = "rejected by validation"; key("Enter", input); @@ -460,7 +460,7 @@ describe("fail closed", () => { it("throws outside any [data-mc-container]", async () => { // The connect throw surfaces immediately; the already-scheduled first render - // then raises the same complaint from the inner sw-live-list (jsdom reports + // then raises the same complaint from the inner yk-live-list (jsdom reports // custom-element reaction throws as window errors, asynchronously) -- so the // capture stays open across the settled update. let list: LitElement | undefined; @@ -471,7 +471,7 @@ describe("fail closed", () => { }; window.addEventListener("error", onError); try { - list = document.createElement("sw-list") as LitElement; + list = document.createElement("yk-list") as LitElement; document.body.appendChild(list); await list.updateComplete.catch(() => {}); } finally { @@ -485,7 +485,7 @@ describe("fail closed", () => { container.setAttribute("data-mc-container", ""); document.body.appendChild(container); const reported = reportsFrom(() => { - const list = document.createElement("sw-list"); + const list = document.createElement("yk-list"); list.innerHTML = "<div>nope</div>"; container.appendChild(list); }); @@ -517,7 +517,7 @@ describe("fail closed", () => { }; window.addEventListener("error", onError); try { - const twin = document.createElement("sw-list") as SwList<Item>; + const twin = document.createElement("yk-list") as YkList<Item>; twin.name = "tl"; twin.header = "the twin"; twin.keyOf = (it: Item) => it.id; diff --git a/packages/yukey/src/sw-list.ts b/packages/yukey/src/yk-list.ts similarity index 85% rename from packages/yukey/src/sw-list.ts rename to packages/yukey/src/yk-list.ts index 02a56b7..d11e310 100644 --- a/packages/yukey/src/sw-list.ts +++ b/packages/yukey/src/yk-list.ts @@ -1,4 +1,4 @@ -// sw-list: the list region of a megacolumn, with the list norms built in. +// yk-list: the list region of a megacolumn, with the list norms built in. // An owner composes it into its column ([data-mc-container]) and gets, by // construction, the behaviors the guidelines ask every list to have: // @@ -7,7 +7,7 @@ // ring stays on the header row itself -- deliberately no perimeter border, // which would read as a nested focus container. Its data-info describes // the list AS a list. Headerless lists are legal (no header row renders). -// - The LIVE ROWS: an inner sw-live-list in megacolumn mode (that file owns +// - The LIVE ROWS: an inner yk-live-list in megacolumn mode (that file owns // the row-lifecycle contract: churn safety, ghosts, geometry, the key pin). // - The ADD-ROW (optional): the append affordance as a permanent ghosted nav // stop at the tail. Its marked hotkey (default a) is DIRECT: it dives @@ -46,7 +46,7 @@ // addable Renders the add-row. Lists that cannot append (a cargo // manifest) just leave it off. // add-label The add-row's label; default "add…". The hotkey letter is -// marked in the label (first occurrence, sw-button's rule), +// marked in the label (first occurrence, yk-button's rule), // or shown after it when the label lacks the letter. // add-hotkey The marked direct hotkey; default "a" (single [a-z0-9]). // Override only when two addable lists share a column: the @@ -54,12 +54,12 @@ // add-info The owner's info text for the add-row; norm teaching is // appended, as with header-info. // items, keyOf, renderItem, renderEmpty, resetKey -// Passed through to the inner sw-live-list untouched: the +// Passed through to the inner yk-live-list untouched: the // owner renders row CONTENT and keeps data identity; see -// sw-live-list.ts for their contracts. +// yk-live-list.ts for their contracts. // // events: -// sw-list-add The add-row's commit (bubbling, cancelable; detail +// yk-list-add The add-row's commit (bubbling, cancelable; detail // { text: string }, trimmed, never empty). Dispatched only on // the input's own Enter keydown -- never on change/blur, so an // Escape that blurs the input cannot masquerade as a commit. @@ -71,17 +71,17 @@ // // key semantics stay the owner's: megacolumn-key listeners (what Enter, x, // Delete MEAN on a live row) belong on the owner's container, exactly as -// without sw-list. The structural rows decline every key -- owners routing +// without yk-list. The structural rows decline every key -- owners routing // by rowId should skip "<name>-header" and "<name>-add" (that id scheme is // public contract). Owners claiming letters that overlap t/n or the add // hotkey should respect a prior claim (check defaultPrevented), though // wanting to is already a design smell. // // rendered structure (public contract, since it renders into the owner's -// tree): [header row?] <sw-live-list megacolumn> [add-row?], all inside this -// element. sw-live-list's feel knobs (holdMs, keyPinMs, pointerSafety...) +// tree): [header row?] <yk-live-list megacolumn> [add-row?], all inside this +// element. yk-live-list's feel knobs (holdMs, keyPinMs, pointerSafety...) // are deliberately NOT mirrored here -- mirrored twins invite drift -- and are -// set on the inner element where they live. The sw-list-prefixed classes on +// set on the inner element where they live. The yk-list-prefixed classes on // the structural rows are stable; their look is the norm's look. // // Group identity for AT (the header-role decision, settled for flat lists): @@ -94,12 +94,12 @@ // An author-set role on the host is left alone. Trees are explicitly NOT this // component's business: a treeitem reframe (levels, expansion state) belongs to // a distinct tree vocabulary, and nothing here forecloses one -- nesting -// sw-list was never legal (rows come from renderItem, not markup). +// yk-list was never legal (rows come from renderItem, not markup). // -// Like sw-live-list's mega mode, this renders into the owner's tree (light +// Like yk-live-list's mega mode, this renders into the owner's tree (light // DOM): the megacolumn scan and aria-activedescendant cannot cross shadow // boundaries. No shadow root, no slots; styles adopt into the owner's root -// once, sw-list-prefixed. After every update it dispatches megacolumn-rescan +// once, yk-list-prefixed. After every update it dispatches megacolumn-rescan // (structural rows change under the controller's scan on this element's own // schedule, not the owner's). Fail closed: connecting outside a // [data-mc-container] throws, element children throw, a malformed name or @@ -109,10 +109,10 @@ import { css, html, LitElement, nothing, type TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; import { adoptIntoRoot } from "./adopt-into-root.js"; import type { MegacolumnKeyDetail } from "./megacolumn-controller.js"; -import type { LiveListPhase } from "./sw-live-list.js"; -import "./sw-live-list.js"; +import type { LiveListPhase } from "./yk-live-list.js"; +import "./yk-live-list.js"; -export interface SwListAddDetail { +export interface YkListAddDetail { text: string; // trimmed, never empty } @@ -124,54 +124,54 @@ export interface SwListAddDetail { // raises the well and the accent border. The raised look is keyed to the // add-row's OWN focus class, not just the container's text mode: two lists in // one column must not both light up when one of them is being typed in. -const swListStyles = css` - sw-list { +const ykListStyles = css` + yk-list { display: block; } - .mc-focus-within sw-list:has(> .sw-list-header.mc-row-focused) { - background: var(--sw-wash-faint); + .mc-focus-within yk-list:has(> .yk-list-header.mc-row-focused) { + background: var(--yk-wash-faint); } - .sw-list-header { + .yk-list-header { display: flex; align-items: center; - min-height: var(--sw-hit-min-h); - color: var(--sw-text-faint); - font-size: var(--sw-type-micro); - letter-spacing: var(--sw-tracking-wide); + min-height: var(--yk-hit-min-h); + color: var(--yk-text-faint); + font-size: var(--yk-type-micro); + letter-spacing: var(--yk-tracking-wide); text-transform: uppercase; - padding: 2px var(--sw-space-sm); + padding: 2px var(--yk-space-sm); box-sizing: border-box; } - .sw-list-add { + .yk-list-add { display: flex; align-items: center; - gap: var(--sw-space-sm); - min-height: var(--sw-hit-min-h); - padding: 2px var(--sw-space-sm); - color: var(--sw-text-faint); + gap: var(--yk-space-sm); + min-height: var(--yk-hit-min-h); + padding: 2px var(--yk-space-sm); + color: var(--yk-text-faint); box-sizing: border-box; } - .sw-list-add input[data-mc-input] { + .yk-list-add input[data-mc-input] { flex: 1; font: inherit; - color: var(--sw-text-bright); + color: var(--yk-text-bright); background: transparent; border: 1px solid transparent; - min-height: var(--sw-hit-min-h); + min-height: var(--yk-hit-min-h); box-sizing: border-box; - padding: 0 var(--sw-space-sm); + padding: 0 var(--yk-space-sm); } - .mc-text-mode .sw-list-add.mc-row-focused input[data-mc-input] { - background: var(--sw-bg-well); - border-color: var(--sw-accent); + .mc-text-mode .yk-list-add.mc-row-focused input[data-mc-input] { + background: var(--yk-bg-well); + border-color: var(--yk-accent); } - .sw-list-hk { - color: var(--sw-hotkey-accent); + .yk-list-hk { + color: var(--yk-hotkey-accent); } `; -@customElement("sw-list") -export class SwList<T = unknown> extends LitElement { +@customElement("yk-list") +export class YkList<T = unknown> extends LitElement { @property() name = "list"; @property() @@ -216,17 +216,17 @@ export class SwList<T = unknown> extends LitElement { protected override createRenderRoot(): HTMLElement { if (this.children.length > 0) { throw new Error( - "sw-list: renders into the light DOM and has no slots; rows come from renderItem", + "yk-list: renders into the light DOM and has no slots; rows come from renderItem", ); } - adoptIntoRoot(this.getRootNode(), swListStyles, "data-sw-list"); + adoptIntoRoot(this.getRootNode(), ykListStyles, "data-yk-list"); return this; } override connectedCallback(): void { super.connectedCallback(); const container = this.closest<HTMLElement>("[data-mc-container]"); - if (container === null) throw new Error("sw-list: outside any [data-mc-container]"); + if (container === null) throw new Error("yk-list: outside any [data-mc-container]"); this.#container = container; } @@ -238,13 +238,13 @@ export class SwList<T = unknown> extends LitElement { override willUpdate(): void { if (!/^[a-z0-9-]+$/i.test(this.name)) { throw new Error( - `sw-list: name "${this.name}" must be a non-empty [a-z0-9-]+ token ` + `yk-list: name "${this.name}" must be a non-empty [a-z0-9-]+ token ` + "(it namespaces the structural row ids)", ); } if (this.addable && !/^[a-z0-9]$/i.test(this.addHotkey)) { throw new Error( - `sw-list: add-hotkey "${this.addHotkey}" must be a single [a-z0-9] character`, + `yk-list: add-hotkey "${this.addHotkey}" must be a single [a-z0-9] character`, ); } } @@ -252,7 +252,7 @@ export class SwList<T = unknown> extends LitElement { protected override updated(): void { // Structural rows appear and disappear on this element's own update // schedule, after the owner's hostUpdated scan already ran -- announce, - // same contract as sw-live-list (re-scans are idempotent). + // same contract as yk-live-list (re-scans are idempotent). this.dispatchEvent(new CustomEvent("megacolumn-rescan", { bubbles: true })); // Group identity for AT (the header-role decision; see the file header). // An author-set role is left alone; only our own role="group" is retracted @@ -323,7 +323,7 @@ export class SwList<T = unknown> extends LitElement { input.value = ""; return; // an empty commit is no commit; the exit proceeds } - const commit = new CustomEvent<SwListAddDetail>("sw-list-add", { + const commit = new CustomEvent<YkListAddDetail>("yk-list-add", { detail: { text }, bubbles: true, cancelable: true, @@ -347,23 +347,23 @@ export class SwList<T = unknown> extends LitElement { ? nothing : html`<div data-mc-row="${this.name}-header" - class="sw-list-header" + class="yk-list-header" data-info=${this.#composedHeaderInfo()} >${this.renderHeader?.() ?? this.header}</div>` } - <sw-live-list + <yk-live-list megacolumn .items=${this.items} .keyOf=${this.keyOf} .renderItem=${this.renderItem} .renderEmpty=${this.renderEmpty} .resetKey=${this.resetKey} - ></sw-live-list> + ></yk-live-list> ${ this.addable ? html`<div data-mc-row="${this.name}-add" - class="sw-list-add" + class="yk-list-add" data-info=${this.#composedAddInfo()} > <span data-mc-hotkey=${this.addHotkey} data-mc-hotkey-direct>+ ${this.#markedAddLabel()}</span> @@ -391,12 +391,12 @@ export class SwList<T = unknown> extends LitElement { } // The add-row's hotkey marking: the letter accented in the label (first - // case-insensitive occurrence, sw-button's rule), or shown after the label + // case-insensitive occurrence, yk-button's rule), or shown after the label // when it lacks the letter -- a marking must be visible either way. #markedAddLabel(): TemplateResult { const idx = this.addLabel.toLowerCase().indexOf(this.addHotkey.toLowerCase()); - if (idx === -1) return html`${this.addLabel} <span class="sw-list-hk">${this.addHotkey}</span>`; - return html`${this.addLabel.slice(0, idx)}<span class="sw-list-hk">${this.addLabel[idx]}</span>${ + if (idx === -1) return html`${this.addLabel} <span class="yk-list-hk">${this.addHotkey}</span>`; + return html`${this.addLabel.slice(0, idx)}<span class="yk-list-hk">${this.addLabel[idx]}</span>${ this.addLabel.slice(idx + 1) }`; } @@ -404,6 +404,6 @@ export class SwList<T = unknown> extends LitElement { declare global { interface HTMLElementTagNameMap { - "sw-list": SwList; + "yk-list": YkList; } } diff --git a/packages/yukey/src/sw-live-list.gallery.ts b/packages/yukey/src/yk-live-list.gallery.ts similarity index 85% rename from packages/yukey/src/sw-live-list.gallery.ts rename to packages/yukey/src/yk-live-list.gallery.ts index 3bac560..e4ec0c5 100644 --- a/packages/yukey/src/sw-live-list.gallery.ts +++ b/packages/yukey/src/yk-live-list.gallery.ts @@ -1,4 +1,4 @@ -// Gallery entry for sw-live-list, the keyed live list whose rows vanish safely +// Gallery entry for yk-live-list, the keyed live list whose rows vanish safely // under a pointer. The churn demo element below feeds it a queue that drains and // refills on a timer, which is the component's home condition: data that changes // out from under the user, mid-hover. The first two variants contrast the two @@ -7,8 +7,8 @@ import { css, html, LitElement, type TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import type { GalleryEntry } from "./gallery/entry.js"; -import type { LiveListPhase } from "./sw-live-list.js"; -import "./sw-live-list.js"; +import type { LiveListPhase } from "./yk-live-list.js"; +import "./yk-live-list.js"; interface DemoRow { id: string; @@ -29,10 +29,10 @@ const rowStyles = css` .row .mark { flex: none; width: 16px; - color: var(--sw-text-faint); + color: var(--yk-text-faint); } .row .mark.live { - color: var(--sw-good); + color: var(--yk-good); } `; @@ -47,8 +47,8 @@ const keyOf = (item: DemoRow): string => item.id; // A queue that drains itself and gets new work, like a ship's task list does. // Every beat the oldest row completes (commit -> hold -> collapse) and a fresh // one enters at the tail; the interplay with a hovering pointer is the demo. -@customElement("swg-live-list-churn") -export class SwgLiveListChurn extends LitElement { +@customElement("ykg-live-list-churn") +export class YkgLiveListChurn extends LitElement { @property() pointerSafety: "click-guard" | "freeze" = "click-guard"; @state() @@ -78,19 +78,19 @@ export class SwgLiveListChurn extends LitElement { } render(): TemplateResult { - return html`<sw-live-list + return html`<yk-live-list .items=${this.items} .keyOf=${keyOf} .renderItem=${renderRow} .itemStyles=${rowStyles} .pointerSafety=${this.pointerSafety} - ></sw-live-list>`; + ></yk-live-list>`; } } const entry: GalleryEntry = { - id: "sw-live-list", - title: "sw-live-list", + id: "yk-live-list", + title: "yk-live-list", description: "A keyed live list whose rows vanish safely under a pointer. " + "The owner supplies keying and rendering; this component owns row lifecycle: " + "departed rows ghost and hold before collapsing, insertions grow in. " @@ -105,23 +105,23 @@ const entry: GalleryEntry = { + "with the pointer resting inside, nothing waits for it. Try clicking a row the instant one above " + "it collapses: the click on the still-moving row dies quietly (no surprise target), while rows " + "above the motion answer clicks throughout.", - render: () => html`<swg-live-list-churn></swg-live-list-churn>`, + render: () => html`<ykg-live-list-churn></ykg-live-list-churn>`, }, { caption: "The same churn in freeze mode (pointerSafety: freeze, the opt-in): rest the pointer inside " + "the list and geometry freezes -- ghosts hold and insertions wait invisibly until the pointer " + "leaves, and the deferred motion plays out one beat apart. For surfaces that want quiescence " + "under the pointer; the price is a world that visibly waits for the mouse.", - render: () => html`<swg-live-list-churn pointerSafety="freeze"></swg-live-list-churn>`, + render: () => html`<ykg-live-list-churn pointerSafety="freeze"></ykg-live-list-churn>`, }, { caption: "Empty state: the owner provides the empty slot's content.", render: () => - html`<sw-live-list + html`<yk-live-list .items=${[] as DemoRow[]} .keyOf=${keyOf} .renderItem=${renderRow} - ><div slot="empty" style="color:var(--sw-text-faint);">queue empty (assignment may refill it)</div></sw-live-list>`, + ><div slot="empty" style="color:var(--yk-text-faint);">queue empty (assignment may refill it)</div></yk-live-list>`, }, ], }; diff --git a/packages/yukey/src/sw-live-list.test.ts b/packages/yukey/src/yk-live-list.test.ts similarity index 97% rename from packages/yukey/src/sw-live-list.test.ts rename to packages/yukey/src/yk-live-list.test.ts index 07334ab..bdb92a3 100644 --- a/packages/yukey/src/sw-live-list.test.ts +++ b/packages/yukey/src/yk-live-list.test.ts @@ -1,6 +1,6 @@ // @vitest-environment jsdom // -// Tests for sw-live-list's megacolumn mode: light-DOM rendering, phase-dependent +// Tests for yk-live-list's megacolumn mode: light-DOM rendering, phase-dependent // row identity stamping, the megacolumn-rescan announcement, the key-activity pin, // the two pointerSafety modes (click-guard's target-scoped swallow; freeze's // global hover pin + lockout), and the fail-closed contract checks. The full @@ -15,8 +15,8 @@ import { html, type TemplateResult } from "lit"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { MegacolumnController } from "./megacolumn-controller.js"; -import type { LiveListPhase, SwLiveList } from "./sw-live-list.js"; -import "./sw-live-list.js"; +import type { LiveListPhase, YkLiveList } from "./yk-live-list.js"; +import "./yk-live-list.js"; interface Item { id: string; @@ -50,7 +50,7 @@ afterEach(() => { interface MegaRig { root: HTMLElement; container: HTMLElement; - list: SwLiveList<Item>; + list: YkLiveList<Item>; ctl: MegacolumnController; setItems: (next: Item[]) => Promise<void>; key: (key: string) => KeyboardEvent; @@ -64,7 +64,7 @@ async function makeMegaRig(initial: Item[]): Promise<MegaRig> { const container = document.createElement("div"); container.setAttribute("data-mc-container", ""); root.appendChild(container); - const list = document.createElement("sw-live-list") as SwLiveList<Item>; + const list = document.createElement("yk-live-list") as YkLiveList<Item>; list.setAttribute("megacolumn", ""); list.keyOf = keyOf; list.renderItem = renderItem; @@ -101,7 +101,7 @@ const fire = (el: Element, type: "click" | "mousedown"): void => { el.dispatchEvent(new MouseEvent(type, { bubbles: true, composed: true, cancelable: true })); }; -const countItemClicks = (list: SwLiveList<Item>): () => number => { +const countItemClicks = (list: YkLiveList<Item>): () => number => { let n = 0; list.addEventListener("item-click", () => { n += 1; @@ -332,7 +332,7 @@ describe("click-guard (the default pointerSafety): motion proceeds, stale clicks await setItems(items("b")); vi.advanceTimersByTime(list.holdMs); // collapse begins: motion in flight await list.updateComplete; - expect(root.querySelector(".sw-ll-items")?.classList.contains("sw-ll-lockout")).toBe(false); + expect(root.querySelector(".yk-ll-items")?.classList.contains("yk-ll-lockout")).toBe(false); }); it("a click on a row below a collapsing row is swallowed mid-motion, and lands after", async () => { @@ -443,7 +443,7 @@ describe("click-guard (the default pointerSafety): motion proceeds, stale clicks }); it("guards shadow-mode rows the same way (composed events cross the boundary)", async () => { - const list = document.createElement("sw-live-list") as SwLiveList<Item>; + const list = document.createElement("yk-live-list") as YkLiveList<Item>; list.keyOf = keyOf; list.renderItem = renderItem; list.items = items("a", "b", "c"); @@ -498,7 +498,7 @@ describe("freeze mode (the opt-in): geometry holds still under the pointer", () await setItems(items("b")); // pointer outside: motion proceeds vi.advanceTimersByTime(list.holdMs); // collapse begins await list.updateComplete; - expect(root.querySelector(".sw-ll-items")?.classList.contains("sw-ll-lockout")).toBe(true); + expect(root.querySelector(".yk-ll-items")?.classList.contains("yk-ll-lockout")).toBe(true); }); it("a focused ghost held by both pins survives the key pin decaying (hover holds on)", async () => { @@ -588,7 +588,7 @@ describe("mega mode: fail closed", () => { it("throws outside any [data-mc-container]", () => { const reported = reportsFrom(() => { - const list = document.createElement("sw-live-list") as SwLiveList<Item>; + const list = document.createElement("yk-live-list") as YkLiveList<Item>; list.setAttribute("megacolumn", ""); list.keyOf = keyOf; list.renderItem = renderItem; @@ -602,7 +602,7 @@ describe("mega mode: fail closed", () => { container.setAttribute("data-mc-container", ""); document.body.appendChild(container); const reported = reportsFrom(() => { - const list = document.createElement("sw-live-list") as SwLiveList<Item>; + const list = document.createElement("yk-live-list") as YkLiveList<Item>; list.setAttribute("megacolumn", ""); list.innerHTML = `<div slot="empty">nope</div>`; list.keyOf = keyOf; @@ -628,7 +628,7 @@ describe("mega mode: fail closed", () => { describe("shadow mode regression (the pieces this stage touched)", () => { it("renders in its own shadow root, no data-mc-row stamped, empty slot alive", async () => { - const list = document.createElement("sw-live-list") as SwLiveList<Item>; + const list = document.createElement("yk-live-list") as YkLiveList<Item>; list.keyOf = keyOf; list.renderItem = renderItem; list.items = items("a"); @@ -647,7 +647,7 @@ describe("shadow mode regression (the pieces this stage touched)", () => { }); it("renderEmpty wins over the slot in shadow mode too", async () => { - const list = document.createElement("sw-live-list") as SwLiveList<Item>; + const list = document.createElement("yk-live-list") as YkLiveList<Item>; list.keyOf = keyOf; list.renderItem = renderItem; list.items = []; diff --git a/packages/yukey/src/sw-live-list.ts b/packages/yukey/src/yk-live-list.ts similarity index 95% rename from packages/yukey/src/sw-live-list.ts rename to packages/yukey/src/yk-live-list.ts index a71a99e..849a2b5 100644 --- a/packages/yukey/src/sw-live-list.ts +++ b/packages/yukey/src/yk-live-list.ts @@ -1,4 +1,4 @@ -// sw-live-list: a keyed live list whose rows vanish safely under a pointer. +// yk-live-list: a keyed live list whose rows vanish safely under a pointer. // Data replaces wholesale per tick (5Hz here); the owner supplies keying and // rendering, this component owns row lifecycle and click safety: // @@ -65,7 +65,7 @@ // When a focused ghost collapses, the machine's vanished-row fallback moves // focus and the controller's churn lockout covers the move. // - everything INSIDE a row stays the owner's renderItem business: -// data-mc-main/aux/input, data-info, hotkey markings, sw-buttons. +// data-mc-main/aux/input, data-info, hotkey markings, yk-buttons. // // The keyboard's version of the pointer story is the KEY-ACTIVITY PIN (both // pointerSafety modes; the keyboard problem is the same in each): while keys @@ -89,7 +89,7 @@ // renderEmpty render-prop (which shadow mode also honors, over its default slot), // and itemStyles is refused (the owner's own stylesheet reaches light-DOM rows). // The row-lifecycle styles are adopted into the owner's root once, all their -// class names sw-ll- prefixed so nothing collides in the shared tree. +// class names yk-ll- prefixed so nothing collides in the shared tree. // // The row state machine lives here in component state, outside the template; // render() is a pure map over it (keyed repeat() keeps per-row DOM identity across @@ -119,21 +119,21 @@ export interface ItemClickDetail<T> { } // The row-lifecycle styles, shared by both render modes. Class names carry an -// sw-ll- prefix because mega mode renders into the owner's tree, where bare +// yk-ll- prefix because mega mode renders into the owner's tree, where bare // names like "closed" would be collision bait. const lifecycleStyles = css` /* The lockout class only ever renders in freeze mode; click-guard swallows stale clicks per target instead of disabling the pointer wholesale. */ - .sw-ll-items.sw-ll-lockout { + .yk-ll-items.yk-ll-lockout { pointer-events: none; } - .sw-ll-items:focus { + .yk-ll-items:focus { outline: none; } - .sw-ll-rowslot { + .yk-ll-rowslot { display: grid; grid-template-rows: 1fr; - transition: grid-template-rows var(--sw-live-list-collapse, 300ms) ease; + transition: grid-template-rows var(--yk-live-list-collapse, 300ms) ease; /* The rowslot is pure geometry machinery and must be free to reach 0 height (closed rows, and both ends of the grow/collapse animations). Owners set hit-target floors on their row CONTENT; in mega mode a [data-mc-row] @@ -141,16 +141,16 @@ const lifecycleStyles = css` (entrances pop, collapses snap, pending rows hold blank space). */ min-height: 0; } - .sw-ll-rowslot.sw-ll-grew { - animation: sw-live-list-enter var(--sw-live-list-enter, 300ms) ease; + .yk-ll-rowslot.yk-ll-grew { + animation: yk-live-list-enter var(--yk-live-list-enter, 300ms) ease; } - .sw-ll-rowslot.sw-ll-closed { + .yk-ll-rowslot.yk-ll-closed { grid-template-rows: 0fr; } - .sw-ll-rowslot > .sw-ll-clip { + .yk-ll-rowslot > .yk-ll-clip { overflow: hidden; } - @keyframes sw-live-list-enter { + @keyframes yk-live-list-enter { from { grid-template-rows: 0fr; } @@ -159,7 +159,7 @@ const lifecycleStyles = css` semantics -- an announcement plus click safety -- not decoration), and the grace lockout still covers the snap (the JS side zeroes its durations too). */ @media (prefers-reduced-motion: reduce) { - .sw-ll-rowslot { + .yk-ll-rowslot { transition: none; animation: none; } @@ -169,7 +169,7 @@ const lifecycleStyles = css` // The mega-mode sheet: the lifecycle styles plus the host display rule that // :host would otherwise provide, adopted into the OWNER's root (once per root). const megaModeStyles = css` - sw-live-list { + yk-live-list { display: block; } ${lifecycleStyles} @@ -217,8 +217,8 @@ interface RowState<T> { timer: number | undefined; // the phase-advance timer; at most one per row } -@customElement("sw-live-list") -export class SwLiveList<T = unknown> extends LitElement { +@customElement("yk-live-list") +export class YkLiveList<T = unknown> extends LitElement { static styles = [ css` :host { @@ -294,7 +294,7 @@ export class SwLiveList<T = unknown> extends LitElement { private keyPinned = false; private keyPinTimer: number | undefined; - @query(".sw-ll-items") + @query(".yk-ll-items") private itemsEl?: HTMLElement; constructor() { @@ -305,7 +305,7 @@ export class SwLiveList<T = unknown> extends LitElement { this.addEventListener("pointerenter", this.onPointerEnter); this.addEventListener("pointerleave", this.onPointerLeave); // The click guard intercepts in the CAPTURE phase on the host: it must beat - // both the row content's own listeners (an sw-button acting on a stale click) + // both the row content's own listeners (a yk-button acting on a stale click) // and, in mega mode, the megacolumn controller's bubble-phase mousedown // focus-sync on the ancestor container -- stopping propagation here covers // every downstream consumer in one place, and the decision stays in the list @@ -323,11 +323,11 @@ export class SwLiveList<T = unknown> extends LitElement { this.mega = true; if (this.children.length > 0) { throw new Error( - "sw-live-list: megacolumn mode renders into the light DOM and has no slots; " + "yk-live-list: megacolumn mode renders into the light DOM and has no slots; " + "use the renderEmpty render-prop", ); } - adoptIntoRoot(this.getRootNode(), megaModeStyles, "data-sw-live-list"); + adoptIntoRoot(this.getRootNode(), megaModeStyles, "data-yk-live-list"); return this; } @@ -338,7 +338,7 @@ export class SwLiveList<T = unknown> extends LitElement { // keydown a user aims anywhere in the column lands there first. const container = this.closest<HTMLElement>("[data-mc-container]"); if (container === null) { - throw new Error("sw-live-list: megacolumn mode outside any [data-mc-container]"); + throw new Error("yk-live-list: megacolumn mode outside any [data-mc-container]"); } this.megaContainer = container; container.addEventListener("keydown", this.onKeyActivity); @@ -359,13 +359,13 @@ export class SwLiveList<T = unknown> extends LitElement { override willUpdate(changed: PropertyValues<this>): void { if (this.mega && this.itemStyles !== undefined) { throw new Error( - "sw-live-list: itemStyles has no effect in megacolumn mode; " + "yk-live-list: itemStyles has no effect in megacolumn mode; " + "style rows from the owner's own stylesheet", ); } if (this.pointerSafety !== "click-guard" && this.pointerSafety !== "freeze") { throw new Error( - `sw-live-list: unknown pointerSafety "${this.pointerSafety}" ` + `yk-live-list: unknown pointerSafety "${this.pointerSafety}" ` + "(the modes are \"click-guard\" and \"freeze\")", ); } @@ -380,7 +380,7 @@ export class SwLiveList<T = unknown> extends LitElement { protected override updated(changed: PropertyValues<this>): void { if (changed.has("itemStyles") && this.itemStyles !== undefined && this.shadowRoot !== null) { - const own = (this.constructor as typeof SwLiveList).elementStyles; + const own = (this.constructor as typeof YkLiveList).elementStyles; adoptStyles(this.shadowRoot, [...own, this.itemStyles]); } // Mega mode: any completed render may have changed the row set under the @@ -512,7 +512,7 @@ export class SwLiveList<T = unknown> extends LitElement { // If real DOM focus is inside the row being dropped, move it out rather than // letting it die silently: to the mega container (the column's one focusable) // in mega mode, else to the list's own item container. - const slot = this.renderRoot.querySelector(`.sw-ll-rowslot[data-key="${CSS.escape(row.key)}"]`); + const slot = this.renderRoot.querySelector(`.yk-ll-rowslot[data-key="${CSS.escape(row.key)}"]`); const focusRoot = this.shadowRoot ?? (this.getRootNode() as Document | ShadowRoot); const active = focusRoot.activeElement; if (slot !== null && active !== null && slot.contains(active)) { @@ -612,7 +612,7 @@ export class SwLiveList<T = unknown> extends LitElement { if (this.frozen) return; // freeze mode's lockout owns this story instead const target = e.composedPath()[0]; if (!(target instanceof Element)) return; - const slot = target.closest<HTMLElement>(".sw-ll-rowslot"); + const slot = target.closest<HTMLElement>(".yk-ll-rowslot"); if (slot === null) return; const key = slot.dataset.key; if (key === undefined || !this.isMovingTarget(key)) return; @@ -721,7 +721,7 @@ export class SwLiveList<T = unknown> extends LitElement { // Read from the DOM rather than via aria-activedescendant so this stays // independent of the controller's id-derivation rules. private isRowFocused(row: RowState<T>): boolean { - const slot = this.renderRoot.querySelector(`.sw-ll-rowslot[data-key="${CSS.escape(row.key)}"]`); + const slot = this.renderRoot.querySelector(`.yk-ll-rowslot[data-key="${CSS.escape(row.key)}"]`); return slot?.classList.contains("mc-row-focused") ?? false; } @@ -761,13 +761,13 @@ export class SwLiveList<T = unknown> extends LitElement { override render(): TemplateResult { const rowList = [...this.rows.values()]; const anyVisible = rowList.some((r) => r.phase !== "pending"); - const vars = `--sw-live-list-enter: ${this.enterMs}ms; --sw-live-list-collapse: ${this.collapseMs}ms;`; + const vars = `--yk-live-list-enter: ${this.enterMs}ms; --yk-live-list-collapse: ${this.collapseMs}ms;`; const empty = anyVisible ? nothing : (this.renderEmpty?.() ?? (this.mega ? nothing : html`<slot name="empty"></slot>`)); return html` <div - class=${classMap({ "sw-ll-items": true, "sw-ll-lockout": this.frozen && this.locked })} + class=${classMap({ "yk-ll-items": true, "yk-ll-lockout": this.frozen && this.locked })} style=${vars} tabindex="-1" > @@ -794,13 +794,13 @@ export class SwLiveList<T = unknown> extends LitElement { // its focus classes on these elements imperatively, and an interpolated class // attribute would wipe them on the next content tick. return html`<div - class=${classMap({ "sw-ll-rowslot": true, "sw-ll-closed": closed, "sw-ll-grew": row.grew })} + class=${classMap({ "yk-ll-rowslot": true, "yk-ll-closed": closed, "yk-ll-grew": row.grew })} data-key=${row.key} data-mc-row=${this.mega ? row.key : nothing} aria-hidden=${closed ? "true" : nothing} @click=${() => this.onRowClick(row)} > - <div class="sw-ll-clip">${this.renderItem(row.item, phase)}</div> + <div class="yk-ll-clip">${this.renderItem(row.item, phase)}</div> </div>`; } @@ -821,6 +821,6 @@ export class SwLiveList<T = unknown> extends LitElement { declare global { interface HTMLElementTagNameMap { - "sw-live-list": SwLiveList; + "yk-live-list": YkLiveList; } } diff --git a/packages/yukey/static/gallery.html b/packages/yukey/static/gallery.html index bbbd4b2..e8ddbd9 100644 --- a/packages/yukey/static/gallery.html +++ b/packages/yukey/static/gallery.html @@ -12,13 +12,13 @@ } /* Height only: display comes from the component's own :host rule (a document rule here would override it and break the shell's grid). */ - sw-gallery { + yk-gallery { height: 100%; } </style> </head> <body> -<sw-gallery></sw-gallery> +<yk-gallery></yk-gallery> <script type="module" src="/gallery.js"></script> </body> </html>