UNPKG

@alegendstale/holly-components

Version:

Reusable UI components created using lit

215 lines (214 loc) 8.02 kB
import a from "./util/Bug.js"; import "./util/bugs/transitionrun-loop.js"; import "./util/bugs/unregistered-transition.js"; import "./util/bugs/adopted-style-sheet.js"; import p from "./util/gentle-register-property.js"; import d from "./util/MultiWeakMap.js"; import { toArray as h, getTimesFor as y, wait as u } from "./util.js"; import g from "./rendered-observer.js"; const f = globalThis.CSS?.supports?.("transition-behavior", "allow-discrete") ? " allow-discrete" : ""; globalThis.document && (p("--style-observer-transition", { inherits: !1 }), a.detectAll()); class O { /** * Observed properties to their old values. * @type {Map<string, string>} */ properties; /** * Get the names of all properties currently being observed. * @type { string[] } */ get propertyNames() { return [...this.properties.keys()]; } /** * The element being observed. * @type {Element} */ target; /** * The callback to call when the element's style changes. * @type {StyleObserverCallback} */ callback; /** * The observer options. * @type {StyleObserverOptions} */ options; /** * Whether the observer has been initialized. * @type {boolean} */ #t = !1; /** * @param {Element} target * @param {StyleObserverCallback} callback * @param {StyleObserverOptions} [options] */ constructor(t, r, s = {}) { this.constructor.all.add(t, this), this.properties = /* @__PURE__ */ new Map(), this.target = t, this.callback = r, this.options = { properties: [], ...s }; let e = h(s.properties); this.renderedObserver = new g((i) => { this.propertyNames.length > 0 && this.handleEvent(); }), e.length > 0 && this.observe(e); } /** * Called the first time observe() is called to initialize the target. */ #s() { if (this.#t) return; let t = this.constructor.all.get(this.target).size === 1; this.updateTransition({ firstTime: t }), this.#t = !0; } resolveOptions(t) { return Object.assign(c(t), this.options); } /** * Handle a potential property change * @private * @param {TransitionEvent} [event] */ async handleEvent(t) { if (t && !this.properties.has(t.propertyName)) return; if (a.TRANSITIONRUN_EVENT_LOOP && t?.type === "transitionrun" || this.options.throttle > 0) { let e = a.TRANSITIONRUN_EVENT_LOOP ? "transitionrun" : "transitionstart", i = Math.max(this.options.throttle, 50); if (a.TRANSITIONRUN_EVENT_LOOP) { let n = y( t.propertyName, getComputedStyle(this.target).transition ); i = Math.max(i, n.duration + n.delay + 16); } this.target.removeEventListener(e, this), await u(i), this.target.addEventListener(e, this); } let r = getComputedStyle(this.target), s = []; for (let e of this.propertyNames) { let i = r.getPropertyValue(e), n = this.properties.get(e); i !== n && (s.push({ target: this.target, property: e, value: i, oldValue: n }), this.properties.set(e, i)); } s.length > 0 && this.callback(s); } /** * Observe the target for changes to one or more CSS properties. * @param {string | string[]} properties * @return {void} */ observe(t) { if (t = h(t), t = t.filter((s) => !this.properties.has(s)), t.length === 0) return; this.#s(); let r = getComputedStyle(this.target); for (let s of t) { a.UNREGISTERED_TRANSITION && !this.constructor.properties.has(s) && (p(s, void 0, this.target.ownerDocument), this.constructor.properties.add(s)); let e = r.getPropertyValue(s); this.properties.set(s, e); } a.TRANSITIONRUN_EVENT_LOOP && (this.target.addEventListener("transitionrun", this), a.all.TRANSITIONRUN_EVENT_LOOP.valuePending?.then((s) => { s || this.target.removeEventListener("transitionrun", this); })), this.target.addEventListener("transitionstart", this), this.target.addEventListener("transitionend", this), this.updateTransitionProperties(), this.renderedObserver.observe(this.target); } /** * Update the `--style-observer-transition` property to include all observed properties. */ updateTransitionProperties() { this.setProperty("--style-observer-transition", ""); let t = new Set( getComputedStyle(this.target).transitionProperty.split(", ") ), r = []; for (let e of this.constructor.all.get(this.target)) r.push(...e.propertyNames); r = [...new Set(r)]; let s = r.filter((e) => !t.has(e)).map((e) => `${e} 1ms step-start${f}`).join(", "); this.setProperty("--style-observer-transition", s); } /** * @type { string | undefined } */ #e; /** * Update the target's transition property or refresh it if it was overwritten. * @param {object} options * @param {boolean} [options.firstTime] - Whether this is the first time the transition is being set. */ updateTransition({ firstTime: t } = {}) { const r = "var(--style-observer-transition, --style-observer-noop)", s = this.getProperty("transition"); let e; (t ? s : !s.includes(r)) && (e = this.#e = s), e === void 0 && (t || !this.#e) && (s.includes(r) && this.setProperty("transition", ""), e = getComputedStyle(this.target).transition), e === "all" ? e = "" : e = e.replace(/^none\b/, ""); const i = e ? e + ", " : ""; this.setProperty("transition", i + r), this.updateTransitionProperties(); } /** * Whether the target has an open shadow root (and the modern adoptedStyleSheets API is supported). * @type { boolean } * @private */ get _isHost() { return this.target.shadowRoot && !a.ADOPTED_STYLE_SHEET && !Object.isFrozen(this.target.shadowRoot.adoptedStyleSheets); } /** * Shadow style sheet. Only used if _isHost is true. * @type { CSSStyleSheet | undefined } * @private */ _shadowSheet; /** * Any styles we've set on the target, for any reason. * @type { Record<string, string> } * @private */ _styles = {}; /** * Set a CSS property on the target. * @param {string} property * @param {string} value * @param {string} [priority] * @return {void} */ setProperty(t, r, s) { let e = this.target.style, i = e; if (this._isHost) { if (!this._shadowSheet && (this._shadowSheet = new CSSStyleSheet(), this._shadowSheet.insertRule(":host { }"), this.target.shadowRoot.adoptedStyleSheets.push(this._shadowSheet), Object.keys(this._styles).length > 0)) for (let n in this._styles) { let l = this._styles[n]; this.setProperty(n, l), e.getPropertyValue(n) === l && e.removeProperty(n); } i = this._shadowSheet.cssRules[0].style; } i.setProperty(t, r, s), this._styles[t] = this.getProperty(t); } /** * Get a CSS property from the target. * @param {string} property * @return {string} */ getProperty(t) { return (this._shadowSheet?.cssRules[0]?.style ?? this.target.style).getPropertyValue(t); } /** * Stop observing a target for changes to one or more CSS properties. * @param { string | string[] } [properties] Properties to stop observing. Defaults to all observed properties. * @return {void} */ unobserve(t) { t = h(t), t = t.filter((r) => this.properties.has(r)); for (let r of t) this.properties.delete(r); this.properties.size === 0 && (this.target.removeEventListener("transitionrun", this), this.target.removeEventListener("transitionstart", this), this.target.removeEventListener("transitionend", this), this.renderedObserver.unobserve(this.target)), this.updateTransitionProperties(); } /** All properties ever observed by this class. */ static properties = /* @__PURE__ */ new Set(); /** * All instances ever observed by this class. */ static all = new d(); } function c(o) { return o ? (typeof o == "string" || Array.isArray(o) ? o = { properties: h(o) } : typeof o == "object" && (o = { properties: [], ...o }), o) : {}; } export { O as default, c as resolveOptions };