Element.attachShadow() throws NotSupportedError unconditionally for a customized built-in (<button is="hive-btn">): the spec only allows autonomous custom elements or a fixed list of native tags to host a shadow root, and explicitly excludes any is=-upgraded built-in regardless of which tag it upgrades. button isn't on that list either way. This made every themed dialog (any confirm/prompt, since openDialog always renders at least one button) throw and fail to render in a real browser, though it passed CI since nothing there exercises actual browser DOM. hive-dialog and hive-toast are unaffected — both are genuine autonomous custom elements (extends HTMLElement, no is= upgrade), which are valid shadow hosts. Fix: hive-btn no longer calls attachShadow. Styles adopt onto document once (module-level guard) instead of per-instance shadow root, scoped via the [is="hive-btn"] attribute selector instead of :host — same light-DOM approach the rest of the app's .btn consumers already use. Native button behaviour is untouched, only the styling mechanism changed. Build clean.
43 lines
2 KiB
JavaScript
43 lines
2 KiB
JavaScript
// hive-btn.js — <hive-btn>, a customized built-in `<button>` (`<button
|
|
// is="hive-btn">`) — the button primitive for modal.js's dialog buttons.
|
|
// Extending `HTMLButtonElement` keeps native button behaviour (click/
|
|
// keyboard activation, `:disabled`) for free.
|
|
//
|
|
// NOT shadow-DOM-encapsulated: a customized built-in can't host a shadow
|
|
// root — `attachShadow()` throws `NotSupportedError` unconditionally for
|
|
// any element that isn't an autonomous custom element or on the HTML
|
|
// spec's short fixed list (article/aside/div/span/etc., no `button`),
|
|
// and `is=`-upgraded built-ins are excluded regardless of tag. The prior
|
|
// version called `attachShadow()` here anyway — it shipped and passed
|
|
// review since nothing in CI exercises real browser DOM, then broke the
|
|
// very first live dialog open. Styled via a stylesheet adopted once on
|
|
// `document` instead, scoped with `[is="hive-btn"]` (hive-btn.css) — the
|
|
// light-DOM approach the rest of the app's `.btn` consumers already use.
|
|
// (`<hive-dialog>`/`<hive-toast>` in modal.js are unaffected — genuine
|
|
// autonomous custom elements, valid shadow hosts.)
|
|
//
|
|
// Customized built-ins aren't supported in Safari/WebKit — fine here
|
|
// since the project targets recent Firefox only.
|
|
//
|
|
// Usage: `el('button', { type: 'button', is: 'hive-btn', variant: 'danger' }, 'label')`.
|
|
// `variant` ∈ 'cancel' | 'confirm' | 'danger' | unset (neutral default).
|
|
|
|
import hiveBtnCss from './hive-btn.css';
|
|
|
|
// Adopted once, module-wide — every <hive-btn> instance shares the same
|
|
// document-level stylesheet rather than each instance re-adopting it.
|
|
let styleInstalled = false;
|
|
function ensureStyleInstalled() {
|
|
if (styleInstalled) return;
|
|
styleInstalled = true;
|
|
const sheet = new CSSStyleSheet();
|
|
sheet.replaceSync(hiveBtnCss);
|
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
}
|
|
|
|
class HiveBtn extends HTMLButtonElement {
|
|
connectedCallback() {
|
|
ensureStyleInstalled();
|
|
}
|
|
}
|
|
customElements.define('hive-btn', HiveBtn, { extends: 'button' });
|