Mercurial
view design_system/src/components/disclosure.js @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| parents | 2b6e732087ff |
| children |
line wrap: on
line source
let nextDisclosureId = 1; const HTMLElementBase = globalThis.HTMLElement || class {}; function rememberAttribute(state, element, name) { let attributes = state.get(element); if (!attributes) { attributes = new Map(); state.set(element, attributes); } if (!attributes.has(name)) { attributes.set(name, element.getAttribute(name)); } } function setManagedAttribute(state, element, name, value) { rememberAttribute(state, element, name); if (value === null) { if (element.hasAttribute(name)) element.removeAttribute(name); } else if (element.getAttribute(name) !== value) { element.setAttribute(name, value); } } function restoreAttribute(state, element, name) { const attributes = state.get(element); if (!attributes?.has(name)) return; const value = attributes.get(name); if (value === null) element.removeAttribute(name); else element.setAttribute(name, value); attributes.delete(name); if (!attributes.size) state.delete(element); } function restoreElement(state, element) { const attributes = state.get(element); if (!attributes) return; for (const [name, value] of attributes) { if (value === null) element.removeAttribute(name); else element.setAttribute(name, value); } state.delete(element); } function restoreAttributes(state) { for (const element of [...state.keys()]) restoreElement(state, element); } function pruneAttributes(state, retained) { for (const element of [...state.keys()]) { if (!retained.has(element)) restoreElement(state, element); } } function uniqueId(element, prefix) { const document = element.ownerDocument; let id; do id = `${prefix}-${nextDisclosureId++}`; while ([...document.querySelectorAll("[id]")].some(item => item.id === id)); return id; } function ensureId(state, element, prefix) { const document = element.ownerDocument; const duplicate = element.id && [...document.querySelectorAll("[id]")].some( item => item !== element && item.id === element.id, ); if (!element.id || duplicate) { setManagedAttribute(state, element, "id", uniqueId(element, prefix)); } return element.id; } function eventElement(event) { const target = event.target; if (target?.nodeType === 1) return target; return target?.parentElement || null; } function isDisabled(control) { return Boolean(control.disabled) || control.getAttribute("aria-disabled") === "true"; } function emitChange(element, detail) { element.dispatchEvent(new CustomEvent("zen-change", { bubbles: true, detail, })); } function ownedElements(host, selector) { return [...host.querySelectorAll(selector)].filter( element => element.closest(host.localName) === host, ); } function syncDisclosureIcon(details) { const summary = details.querySelector(":scope > summary"); if (!summary) return; let icon = summary.querySelector( ":scope > zen-icon[data-zen-disclosure-icon]", ); if (!icon) { icon = document.createElement("zen-icon"); icon.dataset.zenDisclosureIcon = ""; icon.dataset.zenGenerated = "true"; summary.append(icon); } if (!icon.hasAttribute("name")) icon.setAttribute("name", "chevron-down"); } function removeGeneratedDisclosureIcons(host) { for (const icon of host.querySelectorAll( 'zen-icon[data-zen-disclosure-icon][data-zen-generated="true"]', )) { icon.remove(); } } /** * Coordinates native details elements and summary keyboard navigation. * * @extends {HTMLElement} */ export class ZenAccordion extends HTMLElementBase { static get observedAttributes() { return ["multiple"]; } connectedCallback() { this._details ||= new Set(); this._onToggle ||= event => { const opened = event.currentTarget; if (!opened.open || this.hasAttribute("multiple")) return; for (const details of this._details) { if (details !== opened) details.open = false; } }; this._onKeydown ||= event => { if (!["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) { return; } const summary = eventElement(event)?.closest("summary"); const summaries = this.summaries(); const index = summaries.indexOf(summary); if (index < 0) return; event.preventDefault(); let next = event.key === "Home" ? 0 : summaries.length - 1; if (event.key === "ArrowUp") { next = (index - 1 + summaries.length) % summaries.length; } else if (event.key === "ArrowDown") { next = (index + 1) % summaries.length; } summaries[next]?.focus(); }; this.addEventListener("keydown", this._onKeydown); this._observer ||= new MutationObserver(() => this.sync()); this._observer.observe(this, { childList: true, subtree: true }); this.sync(); } disconnectedCallback() { this._observer?.disconnect(); this.removeEventListener("keydown", this._onKeydown); for (const details of this._details || []) { details.removeEventListener("toggle", this._onToggle); } removeGeneratedDisclosureIcons(this); this._details?.clear(); } attributeChangedCallback() { if (this.isConnected && this._details) this.sync(); } summaries() { return [...this._details] .map(details => details.querySelector(":scope > summary")) .filter(Boolean); } sync() { const current = new Set( ownedElements(this, "details").filter(details => { const parent = details.parentElement?.closest("details"); return !parent || parent.closest("zen-accordion") !== this; }), ); for (const details of this._details) { if (!current.has(details)) { details.removeEventListener("toggle", this._onToggle); } } for (const details of current) { if (!this._details.has(details)) { details.addEventListener("toggle", this._onToggle); } syncDisclosureIcon(details); } this._details = current; if (!this.hasAttribute("multiple")) { const opened = [...current].filter(details => details.open); for (const details of opened.slice(1)) details.open = false; } } } /** * Styles a single native details disclosure without replacing its behavior. * * @extends {HTMLElement} */ export class ZenCollapsible extends HTMLElementBase { connectedCallback() { this._observer ||= new MutationObserver(() => this.sync()); this._observer.observe(this, { childList: true, subtree: true }); this.sync(); } disconnectedCallback() { this._observer?.disconnect(); removeGeneratedDisclosureIcons(this); } sync() { for (const details of ownedElements(this, "details")) { syncDisclosureIcon(details); } } } /** * Connects an ARIA tablist with its native content panels. * * @extends {HTMLElement} */ export class ZenTabs extends HTMLElementBase { connectedCallback() { this._managed ||= new Map(); this._onClick ||= event => { const tab = eventElement(event)?.closest('[role="tab"]'); if (!this.isTab(tab) || isDisabled(tab)) return; this.activate(tab, true); }; this._onKeydown ||= event => { const tab = eventElement(event)?.closest('[role="tab"]'); if (!this.isTab(tab)) return; const tabs = this._tabs.filter(candidate => !isDisabled(candidate)); const index = tabs.indexOf(tab); if (index < 0) return; let next; if (event.key === "Home") next = tabs[0]; else if (event.key === "End") next = tabs[tabs.length - 1]; else if (event.key === "ArrowLeft") { next = tabs[(index - 1 + tabs.length) % tabs.length]; } else if (event.key === "ArrowRight") { next = tabs[(index + 1) % tabs.length]; } else { return; } event.preventDefault(); this._focusTab = next; this.render(); next.focus(); if (this.getAttribute("activation") !== "manual") { this.activate(next, true); } }; this.addEventListener("click", this._onClick); this.addEventListener("keydown", this._onKeydown); this._observer ||= new MutationObserver(() => this.sync()); this._observer.observe(this, { attributeFilter: ["aria-controls", "aria-disabled", "disabled", "id"], attributes: true, childList: true, subtree: true, }); this.sync(); } disconnectedCallback() { this._observer?.disconnect(); this.removeEventListener("click", this._onClick); this.removeEventListener("keydown", this._onKeydown); restoreAttributes(this._managed); this._tablist = null; this._tabs = []; this._panels = []; this._pairs = new Map(); this._active = null; this._focusTab = null; } isTab(tab) { return Boolean( tab && this._tabs?.includes(tab) && this._tablist?.contains(tab), ); } sync() { this._tablist = ownedElements(this, '[role="tablist"]')[0] || null; this._tabs = this._tablist ? [...this._tablist.querySelectorAll('[role="tab"]')].filter( tab => tab.closest("zen-tabs") === this, ) : []; this._panels = ownedElements(this, '[role="tabpanel"]'); pruneAttributes( this._managed, new Set([...this._tabs, ...this._panels]), ); for (const tab of this._tabs) { ensureId(this._managed, tab, "zen-tab"); } for (const panel of this._panels) { ensureId(this._managed, panel, "zen-tabpanel"); } const available = new Set(this._panels); this._pairs = new Map(); for (const [index, tab] of this._tabs.entries()) { const controlled = tab.getAttribute("aria-controls"); let panel = this._panels.find( candidate => available.has(candidate) && candidate.id === controlled, ); if (!panel && available.has(this._panels[index])) { panel = this._panels[index]; } panel ||= [...available][0] || null; if (panel) { available.delete(panel); this._pairs.set(tab, panel); setManagedAttribute( this._managed, tab, "aria-controls", panel.id, ); setManagedAttribute( this._managed, panel, "aria-labelledby", tab.id, ); } else { restoreAttribute(this._managed, tab, "aria-controls"); } } for (const panel of available) { restoreAttribute(this._managed, panel, "aria-labelledby"); } if (!this._tabs.includes(this._active) || isDisabled(this._active)) { this._active = this._tabs.find( tab => !isDisabled(tab) && tab.getAttribute("aria-selected") === "true", ) || this._tabs.find(tab => !isDisabled(tab)) || this._tabs[0] || null; } if (!this._tabs.includes(this._focusTab) || isDisabled(this._focusTab)) { this._focusTab = this._active; } this.render(); } render() { for (const tab of this._tabs) { const active = tab === this._active; setManagedAttribute( this._managed, tab, "aria-selected", String(active), ); setManagedAttribute( this._managed, tab, "tabindex", tab === this._focusTab && !isDisabled(tab) ? "0" : "-1", ); } const activePanel = this._pairs.get(this._active); for (const panel of this._panels) { setManagedAttribute( this._managed, panel, "hidden", panel === activePanel ? null : "", ); } } activate(tab, notify) { if (!this.isTab(tab) || isDisabled(tab)) return; this._focusTab = tab; if (tab === this._active) { this.render(); return; } this._active = tab; this.render(); if (notify) { emitChange(this, { value: tab.dataset.value ?? tab.getAttribute("value") ?? tab.id, }); } } } /** * Manages the pressed state of one direct native button. * * @extends {HTMLElement} */ export class ZenToggle extends HTMLElementBase { static get observedAttributes() { return ["pressed"]; } get pressed() { return this.hasAttribute("pressed"); } set pressed(value) { this.toggleAttribute("pressed", Boolean(value)); } connectedCallback() { this._managed ||= new Map(); this._onClick ||= event => { const button = eventElement(event)?.closest("button"); if (button !== this._button || isDisabled(button)) return; this.pressed = !this.pressed; emitChange(this, { pressed: this.pressed }); }; this.addEventListener("click", this._onClick); this._observer ||= new MutationObserver(() => this.sync()); this._observer.observe(this, { childList: true }); this.sync(); } disconnectedCallback() { this._observer?.disconnect(); this.removeEventListener("click", this._onClick); restoreAttributes(this._managed); this._button = null; } attributeChangedCallback() { if (this.isConnected && this._managed) this.sync(); } sync() { const button = this.querySelector(":scope > button"); if (button !== this._button) { if (this._button) restoreElement(this._managed, this._button); this._button = button; } if (button) { setManagedAttribute( this._managed, button, "aria-pressed", String(this.pressed), ); } } } function buttonValue(button) { return button.getAttribute("value") ?? button.dataset.value ?? ""; } function parseMultipleValue(value) { if (!value) return []; if (value.trim().startsWith("[")) { try { const parsed = JSON.parse(value); if (Array.isArray(parsed)) return parsed.map(String); } catch { // Fall through to token parsing. } } return value.trim().split(/[\s,]+/).filter(Boolean); } /** * Manages single or multiple pressed buttons with roving focus. * * @extends {HTMLElement} */ export class ZenToggleGroup extends HTMLElementBase { static get observedAttributes() { return ["type", "value"]; } get values() { return [...(this._values || [])]; } set values(values) { this.setValues(Array.isArray(values) ? values.map(String) : [], false); } connectedCallback() { this._managed ||= new Map(); this._buttons ||= []; if (!this._initialized) { this._values = this.initialValues(); this._initialized = true; } this._hasConnected = true; this._onClick ||= event => { const button = eventElement(event)?.closest("button"); if (!this._buttons.includes(button) || isDisabled(button)) return; const value = buttonValue(button); this._focusButton = button; let values; if (this.multiple) { values = this._values.includes(value) ? this._values.filter(item => item !== value) : [...this._values, value]; } else { values = this._values.includes(value) ? [] : [value]; } this.setValues(values, true); }; this._onKeydown ||= event => { const button = eventElement(event)?.closest("button"); const buttons = this._buttons.filter(candidate => !isDisabled(candidate)); const index = buttons.indexOf(button); if (index < 0) return; let next; if (event.key === "Home") next = buttons[0]; else if (event.key === "End") next = buttons[buttons.length - 1]; else if (event.key === "ArrowLeft" || event.key === "ArrowUp") { next = buttons[(index - 1 + buttons.length) % buttons.length]; } else if (event.key === "ArrowRight" || event.key === "ArrowDown") { next = buttons[(index + 1) % buttons.length]; } else { return; } event.preventDefault(); this._focusButton = next; this.render(); next.focus(); }; this.addEventListener("click", this._onClick); this.addEventListener("keydown", this._onKeydown); this._observer ||= new MutationObserver(() => this.sync()); this._observer.observe(this, { attributeFilter: ["aria-disabled", "data-value", "disabled", "value"], attributes: true, childList: true, subtree: true, }); this.sync(); } disconnectedCallback() { this._observer?.disconnect(); this.removeEventListener("click", this._onClick); this.removeEventListener("keydown", this._onKeydown); restoreAttributes(this._managed); this._buttons = []; this._focusButton = null; } attributeChangedCallback(name) { if (name === "value" && !this._settingValue && this.hasAttribute("value")) { this._values = this.readValue(); this._initialized = true; } else if (name === "value" && !this._settingValue) { this._values = []; this._initialized = true; } else if (name === "type" && !this._hasConnected && this.hasAttribute("value")) { this._values = this.readValue(); this._initialized = true; } if (this.isConnected && this._managed) this.sync(); } get multiple() { return this.getAttribute("type") === "multiple"; } readValue() { const value = this.getAttribute("value"); if (value === null) return []; return this.multiple ? parseMultipleValue(value) : [value]; } initialValues() { if (this.hasAttribute("value")) return this.readValue(); const pressed = [...this.querySelectorAll(":scope > button")] .filter(button => button.getAttribute("aria-pressed") === "true"); return pressed.map(buttonValue); } sync() { const buttons = [...this.querySelectorAll(":scope > button")]; pruneAttributes(this._managed, new Set([this, ...buttons])); this._buttons = buttons; if (!this.hasAttribute("role")) { setManagedAttribute(this._managed, this, "role", "group"); } if (!this.multiple && this._values.length > 1) { this._values = this._values.slice(0, 1); this.writeValue(); } if (!buttons.includes(this._focusButton) || isDisabled(this._focusButton)) { this._focusButton = buttons.find( button => !isDisabled(button) && button.getAttribute("tabindex") === "0", ) || buttons.find( button => !isDisabled(button) && this._values.includes(buttonValue(button)), ) || buttons.find(button => !isDisabled(button)) || null; } this.render(); } render() { for (const button of this._buttons) { setManagedAttribute( this._managed, button, "aria-pressed", String(this._values.includes(buttonValue(button))), ); setManagedAttribute( this._managed, button, "tabindex", button === this._focusButton ? "0" : "-1", ); } } writeValue() { this._settingValue = true; const value = this.multiple ? (this._values.some(item => /[\s,]/.test(item)) ? JSON.stringify(this._values) : this._values.join(" ")) : this._values[0]; if (value === undefined || value === "") this.removeAttribute("value"); else this.setAttribute("value", value); this._settingValue = false; } setValues(values, notify) { const unique = [...new Set(values.map(String))]; this._values = this.multiple ? unique : unique.slice(0, 1); this.writeValue(); if (this.isConnected) this.render(); if (notify) emitChange(this, { values: this.values }); } } /** * Controls a responsive native sidebar region and its trigger. * * @extends {HTMLElement} */ export class ZenSidebar extends HTMLElementBase { static get observedAttributes() { return ["open"]; } get open() { return this.hasAttribute("open"); } set open(value) { this.toggleAttribute("open", Boolean(value)); } connectedCallback() { this._managed ||= new Map(); this._onClick ||= event => { const trigger = eventElement(event)?.closest( "[data-zen-sidebar-trigger]", ); if (trigger !== this._trigger || isDisabled(trigger)) return; this.setOpen(!this.open, true); }; this._onKeydown ||= event => { if (event.key !== "Escape" || !this.open) return; event.preventDefault(); this.setOpen(false, true); this._trigger?.focus(); }; this._onMediaChange ||= () => this.syncState(); this.addEventListener("click", this._onClick); this.addEventListener("keydown", this._onKeydown); this._observer ||= new MutationObserver(() => this.sync()); this._observer.observe(this, { childList: true }); this._media = globalThis.matchMedia?.("(max-width: 640px)") || null; if (this._media?.addEventListener) { this._media.addEventListener("change", this._onMediaChange); } else { this._media?.addListener?.(this._onMediaChange); } this.sync(); } disconnectedCallback() { this._observer?.disconnect(); this.removeEventListener("click", this._onClick); this.removeEventListener("keydown", this._onKeydown); if (this._media?.removeEventListener) { this._media.removeEventListener("change", this._onMediaChange); } else { this._media?.removeListener?.(this._onMediaChange); } restoreAttributes(this._managed); this._trigger = null; this._panel = null; this._media = null; } attributeChangedCallback() { if (this.isConnected) this.syncState(); } sync() { const trigger = ownedElements( this, "[data-zen-sidebar-trigger]", )[0] || null; const panel = ownedElements( this, "[data-zen-sidebar-panel]", )[0] || null; if (trigger !== this._trigger || panel !== this._panel) { if (this._trigger) restoreElement(this._managed, this._trigger); if (this._panel) restoreElement(this._managed, this._panel); this._trigger = trigger; this._panel = panel; } if (!trigger || !panel) return; const panelId = ensureId(this._managed, panel, "zen-sidebar-panel"); setManagedAttribute( this._managed, trigger, "aria-controls", panelId, ); this.syncState(); } syncState() { if (!this._trigger || !this._panel) return; setManagedAttribute( this._managed, this._panel, "hidden", this.open ? null : "", ); setManagedAttribute( this._managed, this._trigger, "aria-expanded", String(this.open), ); } setOpen(open, notify) { if (this.open === open) return; this.open = open; this.syncState(); if (notify) emitChange(this, { open }); } } const definitions = { "zen-accordion": ZenAccordion, "zen-collapsible": ZenCollapsible, "zen-sidebar": ZenSidebar, "zen-tabs": ZenTabs, "zen-toggle": ZenToggle, "zen-toggle-group": ZenToggleGroup, }; const registry = globalThis.customElements; if (registry) { for (const [name, definition] of Object.entries(definitions)) { if (!registry.get(name)) registry.define(name, definition); } }