ranui
Version:
A framework-agnostic Web Components UI library built on native custom elements, with TypeScript types, light/dark theming, SSR and PWA support.
131 lines (130 loc) • 4.88 kB
JavaScript
import { alignCrossAxis as A, computePlacement as I } from "ranuts/utils";
var F = {
bottom: {
enter: "ran-dropdown-down-in",
exit: "ran-dropdown-down-out"
},
top: {
enter: "ran-dropdown-up-in",
exit: "ran-dropdown-up-out"
},
left: {
enter: "ran-dropdown-left-in",
exit: "ran-dropdown-left-out"
},
right: {
enter: "ran-dropdown-right-in",
exit: "ran-dropdown-right-out"
}
}, L = (t) => t.getAnimationTarget?.() ?? t, R = (t, i) => {
const e = L(t);
if (typeof e.getAnimations != "function" || typeof requestAnimationFrame != "function") {
i();
return;
}
requestAnimationFrame(() => {
const r = e.getAnimations();
if (!r.length) {
i();
return;
}
(async () => (await Promise.allSettled(r.map((d) => d.finished)), i()))();
});
}, N = class {
options;
repositionBound = !1;
opened = !1;
repositionFrame = 0;
generation = 0;
constructor(t) {
this.options = t;
}
get anchor() {
return this.options.anchor?.() ?? this.options.host;
}
apply = (t) => {
const { host: i } = this.options;
i.setAttribute("aria-expanded", t ? "true" : "false");
const e = this.options.panel();
if (!e || t === this.opened) return;
this.opened = t;
const r = ++this.generation;
if (t) {
i.dispatchEvent(new CustomEvent("show")), e.style.setProperty("display", "block"), this.attachReposition(), this.position(!0), R(e, () => {
r === this.generation && (e.removeAttribute("transit"), i.dispatchEvent(new CustomEvent("after-show")));
});
return;
}
this.detachReposition(), i.dispatchEvent(new CustomEvent("hide")), e.setAttribute("transit", F[this.side()].exit), R(e, () => {
r === this.generation && (e.style.setProperty("display", "none"), e.removeAttribute("transit"), i.dispatchEvent(new CustomEvent("after-hide")));
});
};
side = () => this.options.placement().split("-")[0];
position = (t = !1) => {
if (!this.options.panel()) return;
const i = () => {
const e = this.options.panel();
e && this.write(e, t);
};
t && typeof requestAnimationFrame == "function" ? requestAnimationFrame(i) : i();
};
write(t, i) {
const { offset: e = 0, containerId: r, beforeMeasure: d, measurePanel: x, adjust: E, afterPosition: b } = this.options, o = this.anchor.getBoundingClientRect();
d?.(o, t);
const B = this.options.host.getRootNode(), c = r?.() ?? "", l = c ? B.getElementById?.(c) ?? document.getElementById(c) : null, u = t.getBoundingClientRect(), y = x?.(t), P = u, f = y?.width ?? u.width, m = y?.height ?? u.height, v = this.options.placement(), [q, C] = v.split("-"), w = C ?? "start";
let p, h, a = q;
if (l) {
const n = l.getBoundingClientRect();
a === "top" || a === "bottom" ? (p = a === "bottom" ? o.bottom - n.top + e : o.top - n.top - m - e, h = A(w, o.left, o.width, f) - n.left) : (p = A(w, o.top, o.height, m) - n.top, h = a === "right" ? o.right - n.left + e : o.left - n.left - f - e);
} else {
const n = I({
anchor: {
top: o.top,
left: o.left,
width: o.width,
height: o.height
},
floating: {
width: f,
height: m
},
placement: v,
offset: e
});
a = n.side, p = n.top + window.scrollY, h = n.left + window.scrollX;
}
const s = {
top: p,
left: h,
side: a,
align: w,
anchorRect: o,
panelRect: P,
container: l
}, g = E?.(s, t);
g && (s.top = g.top, s.left = g.left), t.style.setProperty("position", "absolute"), t.style.setProperty("inset", `${s.top}px auto auto ${s.left}px`), t.style.setProperty("--ran-x", `${s.left}px`), t.style.setProperty("--ran-y", `${s.top}px`), i && t.setAttribute("transit", F[s.side].enter), b?.(s, t);
}
reposition = () => {
if (!(!this.opened || this.repositionFrame)) {
if (typeof requestAnimationFrame != "function") {
this.position();
return;
}
this.repositionFrame = requestAnimationFrame(() => {
this.repositionFrame = 0, this.opened && this.position();
});
}
};
attachReposition() {
this.repositionBound || typeof window > "u" || (window.addEventListener("scroll", this.reposition, !0), window.addEventListener("resize", this.reposition), this.repositionBound = !0);
}
detachReposition() {
!this.repositionBound || typeof window > "u" || (window.removeEventListener("scroll", this.reposition, !0), window.removeEventListener("resize", this.reposition), this.repositionBound = !1);
}
destroy = () => {
this.generation++, this.opened = !1, this.repositionFrame && typeof cancelAnimationFrame == "function" && (cancelAnimationFrame(this.repositionFrame), this.repositionFrame = 0), this.detachReposition();
};
};
export {
N as t
};