@alegendstale/holly-components
Version:
Reusable UI components created using lit
215 lines (214 loc) • 8.02 kB
JavaScript
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
};