UNPKG

tablefilter

Version:

A Javascript library making HTML tables filterable and a bit more

102 lines (92 loc) • 3.96 kB
/** * `<tf-status>` custom element: accessible status indicator * (ROADMAP: preliminary-and-milestone-0.8.0-plan.md, "Component 2"). * * Scaffold (#905): an open shadow root exposing the `label` and * `message` parts, rendered statically from the `text` and `message` * attributes. The host is the live region (#906): connecting it enforces * the shared live-region contract (`applyLiveRegion()`) so message * changes are announced. The StatusBar bridge (#907) follows. * * `text` is a plain label and is rendered as text. `message` is rendered * as HTML, matching legacy `StatusBar` (`msgContainer.innerHTML = t`), * so configured messages such as `status_bar.msg_filter` keep working * unchanged (#1012). The markup is passed through `sanitizeHtml()` * (DOMPurify) before injection: ordinary markup is preserved, scripts * and event handlers are stripped. * * Styling is deliberately not shipped here. TableFilter themes through * the `--tf-*` custom properties (#892/#945 — inherited into shadow * trees) and `::part()` hooks, so the element exposes `part="label"` / * `part="message"` and leaves the look to the stylesheet/theme. It also * hardcodes no CSS class: the status class is configurable * (`status_bar.css_class`, default `status`) and #907 applies it to * this host. * * Importing this module registers the element. Like `<table-filter>` it * is deliberately not part of the legacy webpack bundle (PhantomJS * `_wrapNativeSuper`); consumers load it as a module, and shipping it * in a bundle belongs with #965. */ import {sanitizeHtml} from '../sanitize.js'; import {applyLiveRegion} from './attributes.js'; // Cloned into every instance's shadow root. Structure and `part` hooks // only: `part` is the stable internal contract (selected by _update() // and themed via ::part()), and no class or presentational style is // baked in, so themes and consumers stay in control. const TEMPLATE = document.createElement('template'); TEMPLATE.innerHTML = ` <span part="label"></span> <span part="message"></span> `; export class TfStatusElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.shadowRoot.appendChild(TEMPLATE.content.cloneNode(true)); this._labelEl = this.shadowRoot.querySelector('[part="label"]'); this._msgEl = this.shadowRoot.querySelector('[part="message"]'); } static get observedAttributes() { return ['text', 'message']; } connectedCallback() { // Live-region contract (#906): the host is the region, so a // message change is announced once, as "label message". applyLiveRegion(this); this._update(); } attributeChangedCallback() { this._update(); } /** * Set the message, rendered as sanitised HTML into the `message` part. * Values are coerced to strings so a falsy non-string (e.g. 0) is * displayed rather than silently dropped. Markup is allowed to stay * backward compatible with legacy `StatusBar` messages (#1012); it is * sanitised with DOMPurify before injection. * @param {String} msg Message text/HTML */ setMessage(msg) { this.setAttribute('message', msg == null ? '' : String(msg)); } /** * Render the `text` attribute as text and the `message` attribute as * sanitised HTML into the shadow parts. */ _update() { if (this._labelEl) { this._labelEl.textContent = this.getAttribute('text') || ''; } if (this._msgEl) { this._msgEl.innerHTML = sanitizeHtml(this.getAttribute('message') || ''); } } } // Register once: loading this module twice (or on a page that already // defined the element) must not throw. if (typeof customElements !== 'undefined' && !customElements.get('tf-status')) { customElements.define('tf-status', TfStatusElement); }