Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ cp -R ../oatbase/dist ./public/vendor/oatbase

CSS-only applications can use `@oddship/oatbase/css` without loading JavaScript. For an application that already loads Oat, `@oddship/oatbase/extensions.css` and `@oddship/oatbase/extensions` provide the extension-only aggregates. Every Oatbase addition also has an independent entry point, such as `@oddship/oatbase/chart.css`, `@oddship/oatbase/drawer.css`, or `@oddship/oatbase/select` plus `@oddship/oatbase/select.css`.

The v0.1.0 complete Oat-inclusive browser payload is 27.9 kB gzipped, and the extension-only aggregate is 18.1 kB gzipped. Individual entry points and `components.json` support selective application bundles; `examples/esgun/` demonstrates that path without making EsGun a library dependency.
The v0.1.0 complete Oat-inclusive browser payload is 28.1 kB gzipped, and the extension-only aggregate is 18.3 kB gzipped. Individual entry points and `components.json` support selective application bundles; `examples/esgun/` demonstrates that path without making EsGun a library dependency.

The Flowctl audit added only the reusable boundaries: Repeater coordinates repeated native fields, Log Viewer coordinates a semantic live log, Combobox can create native options, and Stepper has a compact status track. Page headers, validation summaries, action rails, schedule forms, and workflow workspaces remain recipes or blocks. CodeMirror, graph layout, cron/timezone conversion, ANSI parsing, streaming, and virtualization remain application or specialist-library concerns.

Expand Down
89 changes: 63 additions & 26 deletions dist/extensions.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -1427,26 +1427,51 @@ define("ot-scrollspy", OtScrollspy);
// src/js/footnotes.js
class OtFootnotes extends HTMLElement {
#abort;
#readyAbort;
#entries = [];
#revision = 0;
connectedCallback() {
this.#abort?.abort();
this.#abort = new AbortController;
if (typeof HTMLElement.prototype.showPopover !== "function")
return;
const references = [...this.querySelectorAll('[data-footnote-ref], .footnote-reference a[href^="#"]')];
references.forEach((reference) => this.#enhance(reference));
if (references.length)
this.toggleAttribute("data-enhanced", true);
this.#readyAbort?.abort();
this.#readyAbort = connectWhenReady(this, () => this.refresh());
}
disconnectedCallback() {
this.#readyAbort?.abort();
this.#abort?.abort();
this.#revision++;
}
#enhance(reference) {
const targetId = decodeURIComponent(reference.hash.slice(1));
const definition = targetId && document.getElementById(targetId);
if (!definition)
return;
get entries() {
return [...this.#entries];
}
refresh() {
this.#abort?.abort();
this.#abort = new AbortController;
const canPopover = typeof HTMLElement.prototype.showPopover === "function";
const references = [...this.querySelectorAll('[data-footnote-ref], .footnote-reference a[href^="#"]')].filter((reference) => !reference.closest("[data-footnote-popover]"));
this.#entries = references.map((reference) => {
const targetId = decodeURIComponent(reference.hash.slice(1));
const definition = targetId && document.getElementById(targetId);
if (!definition)
return null;
if (!canPopover)
return { reference, definition, dropdown: null, popover: null };
return this.#enhance(reference, definition, targetId);
}).filter(Boolean);
this.toggleAttribute("data-enhanced", canPopover && this.#entries.some((entry) => entry.popover));
this.#announce(canPopover);
return this.entries;
}
#announce(enhanced) {
const revision = ++this.#revision;
const entries = this.entries;
queueMicrotask(() => {
if (this.isConnected && revision === this.#revision) {
emit(this, "oatbase:render", { entries, enhanced });
}
});
}
#enhance(reference, definition, targetId) {
let dropdown = reference.closest("ot-dropdown[data-footnote-preview]");
let popover = dropdown?.querySelector("[data-footnote-popover]");
let popover = dropdown?.querySelector(":scope > [data-footnote-popover]");
if (!dropdown || !popover) {
dropdown = document.createElement("ot-dropdown");
dropdown.dataset.footnotePreview = "";
Expand All @@ -1459,21 +1484,15 @@ class OtFootnotes extends HTMLElement {
popover.dataset.footnotePopover = "";
popover.setAttribute("popover", "auto");
popover.setAttribute("role", "note");
popover.setAttribute("aria-label", `Footnote ${reference.textContent.trim()}`);
const content = definition.cloneNode(true);
content.removeAttribute("id");
content.querySelector(".footnote-definition-label, [data-footnote-label]")?.remove();
content.querySelectorAll("[id]").forEach((element) => element.removeAttribute("id"));
content.querySelectorAll('[data-footnote-backref], a[rev="footnote"]').forEach((element) => element.remove());
const body = document.createElement("div");
while (content.firstChild)
body.append(content.firstChild);
popover.append(body);
dropdown.append(popover);
reference.setAttribute("popovertarget", popover.id);
reference.setAttribute("aria-controls", popover.id);
parent.insertBefore(dropdown, next);
}
popover.dataset.footnoteFor = targetId;
popover.setAttribute("aria-label", `Footnote ${reference.textContent.trim()}`);
reference.setAttribute("popovertarget", popover.id);
reference.setAttribute("aria-controls", popover.id);
this.#render(popover, definition);
const entry = { reference, definition, dropdown, popover };
reference.addEventListener("click", (event) => {
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
return;
Expand All @@ -1483,6 +1502,24 @@ class OtFootnotes extends HTMLElement {
else
popover.showPopover();
}, { signal: this.#abort.signal });
popover.addEventListener("toggle", (event) => {
emit(this, "oatbase:toggle", {
...entry,
state: event.newState || (popover.matches(":popover-open") ? "open" : "closed")
});
}, { signal: this.#abort.signal });
return entry;
}
#render(popover, definition) {
const content = definition.cloneNode(true);
content.removeAttribute("id");
content.querySelector(".footnote-definition-label, [data-footnote-label]")?.remove();
content.querySelectorAll("[id]").forEach((element) => element.removeAttribute("id"));
content.querySelectorAll('[data-footnote-backref], a[rev="footnote"]').forEach((element) => element.remove());
const body = document.createElement("div");
while (content.firstChild)
body.append(content.firstChild);
popover.replaceChildren(body);
}
}
define("ot-footnotes", OtFootnotes);
Expand Down
89 changes: 63 additions & 26 deletions dist/extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1491,26 +1491,51 @@
// src/js/footnotes.js
class OtFootnotes extends HTMLElement {
#abort;
#readyAbort;
#entries = [];
#revision = 0;
connectedCallback() {
this.#abort?.abort();
this.#abort = new AbortController;
if (typeof HTMLElement.prototype.showPopover !== "function")
return;
const references = [...this.querySelectorAll('[data-footnote-ref], .footnote-reference a[href^="#"]')];
references.forEach((reference) => this.#enhance(reference));
if (references.length)
this.toggleAttribute("data-enhanced", true);
this.#readyAbort?.abort();
this.#readyAbort = connectWhenReady(this, () => this.refresh());
}
disconnectedCallback() {
this.#readyAbort?.abort();
this.#abort?.abort();
this.#revision++;
}
#enhance(reference) {
const targetId = decodeURIComponent(reference.hash.slice(1));
const definition = targetId && document.getElementById(targetId);
if (!definition)
return;
get entries() {
return [...this.#entries];
}
refresh() {
this.#abort?.abort();
this.#abort = new AbortController;
const canPopover = typeof HTMLElement.prototype.showPopover === "function";
const references = [...this.querySelectorAll('[data-footnote-ref], .footnote-reference a[href^="#"]')].filter((reference) => !reference.closest("[data-footnote-popover]"));
this.#entries = references.map((reference) => {
const targetId = decodeURIComponent(reference.hash.slice(1));
const definition = targetId && document.getElementById(targetId);
if (!definition)
return null;
if (!canPopover)
return { reference, definition, dropdown: null, popover: null };
return this.#enhance(reference, definition, targetId);
}).filter(Boolean);
this.toggleAttribute("data-enhanced", canPopover && this.#entries.some((entry) => entry.popover));
this.#announce(canPopover);
return this.entries;
}
#announce(enhanced) {
const revision = ++this.#revision;
const entries = this.entries;
queueMicrotask(() => {
if (this.isConnected && revision === this.#revision) {
emit(this, "oatbase:render", { entries, enhanced });
}
});
}
#enhance(reference, definition, targetId) {
let dropdown = reference.closest("ot-dropdown[data-footnote-preview]");
let popover = dropdown?.querySelector("[data-footnote-popover]");
let popover = dropdown?.querySelector(":scope > [data-footnote-popover]");
if (!dropdown || !popover) {
dropdown = document.createElement("ot-dropdown");
dropdown.dataset.footnotePreview = "";
Expand All @@ -1523,21 +1548,15 @@
popover.dataset.footnotePopover = "";
popover.setAttribute("popover", "auto");
popover.setAttribute("role", "note");
popover.setAttribute("aria-label", `Footnote ${reference.textContent.trim()}`);
const content = definition.cloneNode(true);
content.removeAttribute("id");
content.querySelector(".footnote-definition-label, [data-footnote-label]")?.remove();
content.querySelectorAll("[id]").forEach((element) => element.removeAttribute("id"));
content.querySelectorAll('[data-footnote-backref], a[rev="footnote"]').forEach((element) => element.remove());
const body = document.createElement("div");
while (content.firstChild)
body.append(content.firstChild);
popover.append(body);
dropdown.append(popover);
reference.setAttribute("popovertarget", popover.id);
reference.setAttribute("aria-controls", popover.id);
parent.insertBefore(dropdown, next);
}
popover.dataset.footnoteFor = targetId;
popover.setAttribute("aria-label", `Footnote ${reference.textContent.trim()}`);
reference.setAttribute("popovertarget", popover.id);
reference.setAttribute("aria-controls", popover.id);
this.#render(popover, definition);
const entry = { reference, definition, dropdown, popover };
reference.addEventListener("click", (event) => {
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
return;
Expand All @@ -1547,6 +1566,24 @@
else
popover.showPopover();
}, { signal: this.#abort.signal });
popover.addEventListener("toggle", (event) => {
emit(this, "oatbase:toggle", {
...entry,
state: event.newState || (popover.matches(":popover-open") ? "open" : "closed")
});
}, { signal: this.#abort.signal });
return entry;
}
#render(popover, definition) {
const content = definition.cloneNode(true);
content.removeAttribute("id");
content.querySelector(".footnote-definition-label, [data-footnote-label]")?.remove();
content.querySelectorAll("[id]").forEach((element) => element.removeAttribute("id"));
content.querySelectorAll('[data-footnote-backref], a[rev="footnote"]').forEach((element) => element.remove());
const body = document.createElement("div");
while (content.firstChild)
body.append(content.firstChild);
popover.replaceChildren(body);
}
}
define("ot-footnotes", OtFootnotes);
Expand Down
4 changes: 2 additions & 2 deletions dist/extensions.min.js

Large diffs are not rendered by default.

108 changes: 82 additions & 26 deletions dist/footnotes.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,74 @@ function define(name, constructor) {
if (!customElements.get(name))
customElements.define(name, constructor);
}
function connectWhenReady(element, connect) {
if (document.readyState !== "loading") {
connect();
return;
}
const controller = new AbortController;
document.addEventListener("DOMContentLoaded", () => {
if (element.isConnected)
connect();
}, { once: true, signal: controller.signal });
return controller;
}
function emit(element, name, detail) {
element.dispatchEvent(new CustomEvent(name, {
bubbles: true,
composed: true,
detail
}));
}

// src/js/footnotes.js
class OtFootnotes extends HTMLElement {
#abort;
#readyAbort;
#entries = [];
#revision = 0;
connectedCallback() {
this.#abort?.abort();
this.#abort = new AbortController;
if (typeof HTMLElement.prototype.showPopover !== "function")
return;
const references = [...this.querySelectorAll('[data-footnote-ref], .footnote-reference a[href^="#"]')];
references.forEach((reference) => this.#enhance(reference));
if (references.length)
this.toggleAttribute("data-enhanced", true);
this.#readyAbort?.abort();
this.#readyAbort = connectWhenReady(this, () => this.refresh());
}
disconnectedCallback() {
this.#readyAbort?.abort();
this.#abort?.abort();
this.#revision++;
}
get entries() {
return [...this.#entries];
}
refresh() {
this.#abort?.abort();
this.#abort = new AbortController;
const canPopover = typeof HTMLElement.prototype.showPopover === "function";
const references = [...this.querySelectorAll('[data-footnote-ref], .footnote-reference a[href^="#"]')].filter((reference) => !reference.closest("[data-footnote-popover]"));
this.#entries = references.map((reference) => {
const targetId = decodeURIComponent(reference.hash.slice(1));
const definition = targetId && document.getElementById(targetId);
if (!definition)
return null;
if (!canPopover)
return { reference, definition, dropdown: null, popover: null };
return this.#enhance(reference, definition, targetId);
}).filter(Boolean);
this.toggleAttribute("data-enhanced", canPopover && this.#entries.some((entry) => entry.popover));
this.#announce(canPopover);
return this.entries;
}
#enhance(reference) {
const targetId = decodeURIComponent(reference.hash.slice(1));
const definition = targetId && document.getElementById(targetId);
if (!definition)
return;
#announce(enhanced) {
const revision = ++this.#revision;
const entries = this.entries;
queueMicrotask(() => {
if (this.isConnected && revision === this.#revision) {
emit(this, "oatbase:render", { entries, enhanced });
}
});
}
#enhance(reference, definition, targetId) {
let dropdown = reference.closest("ot-dropdown[data-footnote-preview]");
let popover = dropdown?.querySelector("[data-footnote-popover]");
let popover = dropdown?.querySelector(":scope > [data-footnote-popover]");
if (!dropdown || !popover) {
dropdown = document.createElement("ot-dropdown");
dropdown.dataset.footnotePreview = "";
Expand All @@ -41,21 +85,15 @@ class OtFootnotes extends HTMLElement {
popover.dataset.footnotePopover = "";
popover.setAttribute("popover", "auto");
popover.setAttribute("role", "note");
popover.setAttribute("aria-label", `Footnote ${reference.textContent.trim()}`);
const content = definition.cloneNode(true);
content.removeAttribute("id");
content.querySelector(".footnote-definition-label, [data-footnote-label]")?.remove();
content.querySelectorAll("[id]").forEach((element) => element.removeAttribute("id"));
content.querySelectorAll('[data-footnote-backref], a[rev="footnote"]').forEach((element) => element.remove());
const body = document.createElement("div");
while (content.firstChild)
body.append(content.firstChild);
popover.append(body);
dropdown.append(popover);
reference.setAttribute("popovertarget", popover.id);
reference.setAttribute("aria-controls", popover.id);
parent.insertBefore(dropdown, next);
}
popover.dataset.footnoteFor = targetId;
popover.setAttribute("aria-label", `Footnote ${reference.textContent.trim()}`);
reference.setAttribute("popovertarget", popover.id);
reference.setAttribute("aria-controls", popover.id);
this.#render(popover, definition);
const entry = { reference, definition, dropdown, popover };
reference.addEventListener("click", (event) => {
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
return;
Expand All @@ -65,6 +103,24 @@ class OtFootnotes extends HTMLElement {
else
popover.showPopover();
}, { signal: this.#abort.signal });
popover.addEventListener("toggle", (event) => {
emit(this, "oatbase:toggle", {
...entry,
state: event.newState || (popover.matches(":popover-open") ? "open" : "closed")
});
}, { signal: this.#abort.signal });
return entry;
}
#render(popover, definition) {
const content = definition.cloneNode(true);
content.removeAttribute("id");
content.querySelector(".footnote-definition-label, [data-footnote-label]")?.remove();
content.querySelectorAll("[id]").forEach((element) => element.removeAttribute("id"));
content.querySelectorAll('[data-footnote-backref], a[rev="footnote"]').forEach((element) => element.remove());
const body = document.createElement("div");
while (content.firstChild)
body.append(content.firstChild);
popover.replaceChildren(body);
}
}
define("ot-footnotes", OtFootnotes);
Expand Down
Loading
Loading