simple-details
Version:
simple animated details package
134 lines (133 loc) • 5.98 kB
JavaScript
const r = class r {
constructor(s) {
this.options = {
...r.optionsDefault,
...s,
selectors: {
...r.optionsDefault.selectors,
...s?.selectors,
scrollbars: {
...r.optionsDefault.selectors.scrollbars,
...s?.selectors?.scrollbars
}
}
}, this.selectors = this.options.selectors, this.elements = [], this.durationMin = this.options.durationMin, this.durationMax = this.options.durationMax, this.durationPerHeight = this.options.durationPerHeight, this.easing = this.options.easing, this.onClick = this.handleClick.bind(this), this.onResize = this.handleResize.bind(this);
}
init() {
this.updateListeners(), this.updateElements();
}
updateElements() {
const { body: s } = document, e = Array.from(s.querySelectorAll(this.selectors.details));
e.length > 0 && (this.elements = this.updateElementsData(e), this.elements.forEach((i) => {
i.details.classList.add(`sd_${this.options.theme.replace(" ", "_")}`);
}));
}
openAll() {
this.elements.forEach((s) => {
s.parameters.isOpen = s.details.classList.contains("open"), s.parameters.isOpen || this.expand(s);
});
}
collapseAll() {
this.elements.forEach((s) => {
s.parameters.isOpen = s.details.classList.contains("open"), s.parameters.isOpen && this.shrink(s);
});
}
calculateDuration(s, e) {
let i = this.durationMin;
const n = Math.abs(e - s);
return i = i + n * this.durationPerHeight, i > this.durationMax && (i = this.durationMax), i = Math.round(i), i;
}
expand(s) {
s.parameters.isOpening = !0, s.details.classList.add("is-opening"), s.content.classList.add("is-opening");
const e = s.details.offsetHeight + "px", i = s.summary.offsetHeight + s.content.offsetHeight + "px";
s.parameters.animation && s.parameters.animation.cancel(), s.parameters.animation = s.details.animate({ height: [e, i] }, {
duration: this.calculateDuration(s.details.offsetHeight, s.summary.offsetHeight + s.content.offsetHeight),
easing: this.easing
}), s.parameters.animation.onfinish = () => this.onAnimationFinish(s, !0), s.parameters.animation.oncancel = () => s.parameters.isOpening = !1;
}
shouldDisable() {
return window.matchMedia(`(min-width: ${this.options.disableAfter}px)`).matches;
}
disable() {
if (this.shouldDisable()) {
this.openAll();
return;
}
}
handleClick(s) {
const e = s.target;
if ((e.closest(this.selectors.summary) || e.closest(this.selectors.button)) && !e.closest("a[href]")) {
s.preventDefault();
const n = e.closest(this.selectors.summary), t = this.elements.find((o) => o.summary === n ? o : !1);
t && (t.details.style.overflow = "hidden", t.parameters.isClosing || !t.parameters.isOpen ? this.open(t) : (t.parameters.isOpening || t.parameters.isOpen) && this.shrink(t));
}
}
handleResize() {
this.updateClickListeners();
}
hasVerticalScrollbar(s) {
return s.scrollHeight > s.clientHeight;
}
onAnimationFinish(s, e) {
s.details.open = e, s.parameters.isOpen = e, s.parameters.animation = null, s.parameters.isClosing = !1, s.parameters.isOpening = !1, s.details.style.height = s.details.style.overflow = "", this.setupScrollbarStyles(s.details), this.updateDetailsStyles(s.details, e), this.updateDetailsStyles(s.content, e), s.content.classList.remove("is-closing"), s.content.classList.remove("is-opening"), s.details.classList.remove("is-closing"), s.details.classList.remove("is-opening");
}
open(s) {
s.details.style.height = s.details.offsetHeight + "px", s.details.open = !0, window.requestAnimationFrame(() => this.expand(s));
}
setupScrollbarStyles(s) {
this.hasVerticalScrollbar(s) ? s.classList.add(this.selectors.scrollbars.vertical) : s.classList.remove(this.selectors.scrollbars.vertical);
}
shrink(s) {
s.parameters.isClosing = !0, s.details.classList.add("is-closing"), s.content.classList.add("is-closing");
const e = s.details.offsetHeight + "px", i = s.summary.offsetHeight + "px";
s.parameters.animation && s.parameters.animation.cancel(), s.parameters.animation = s.details.animate({ height: [e, i] }, {
duration: this.calculateDuration(s.details.offsetHeight, s.summary.offsetHeight),
easing: this.easing
}), s.parameters.animation.onfinish = () => this.onAnimationFinish(s, !1), s.parameters.animation.oncancel = () => s.parameters.isClosing = !1;
}
updateClickListeners() {
this.options.disableAfter !== null ? (document.removeEventListener("click", this.onClick), this.shouldDisable() ? this.openAll() : document.addEventListener("click", this.onClick)) : (document.removeEventListener("click", this.onClick), document.addEventListener("click", this.onClick));
}
updateDetailsStyles(s, e) {
e ? s.classList.add("open") : s.classList.remove("open");
}
updateElementsData(s) {
return s.map((e) => {
const i = e.classList.contains("open") || e.hasAttribute("open"), n = e.querySelector(this.selectors.content);
return i && n.classList.add("open"), {
details: e,
summary: e.querySelector(this.selectors.summary),
button: e.querySelector(this.selectors.button),
content: n,
parameters: {
isOpen: i,
isClosing: !1,
isOpening: !1
}
};
});
}
updateListeners() {
window.removeEventListener("resize", this.onResize), window.addEventListener("resize", this.onResize), this.updateClickListeners();
}
};
r.optionsDefault = {
preferButtonIfExist: !0,
selectors: {
details: ".SimpleDetails",
summary: ".SimpleDetailsSummary",
button: ".SimpleDetailsButton",
content: ".SimpleDetailsBody",
scrollbars: { vertical: "has-vertical-scrollbar" }
},
disableAfter: null,
durationMin: 333,
durationMax: 1e3,
durationPerHeight: 0.5,
easing: "ease-in-out",
theme: "default"
};
let l = r;
export {
l as SimpleDetails
};