@wpmoo/ui 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/certification.json +3 -1
- package/dist/assets/css/moo-ui.css +429 -1
- package/dist/assets/css/moo-ui.min.css +2 -2
- package/dist/assets/css/moo.css +349 -1
- package/dist/assets/css/moo.min.css +2 -2
- package/dist/js/chart.js +1 -1
- package/dist/js/chart.min.js +1 -1
- package/dist/js/combobox.js +1 -1
- package/dist/js/consent.js +342 -0
- package/dist/js/context-menu.js +1 -1
- package/dist/js/datatable.js +1 -1
- package/dist/js/datepicker.js +1 -1
- package/dist/js/datepicker.min.js +1 -1
- package/dist/js/floating-panel.js +1 -1
- package/dist/js/image-gallery.js +247 -0
- package/dist/js/moo-ui.js +561 -4
- package/dist/js/moo-ui.min.js +2 -2
- package/dist/js/overlay-scrollbar.js +1 -1
- package/dist/js/password-requirements.js +1 -1
- package/dist/js/sheet.js +1 -1
- package/dist/js/sidebar.js +1 -1
- package/dist/js/slider.js +1 -1
- package/dist/js/state.js +1 -1
- package/dist/js/table-of-contents.js +1 -1
- package/dist/js/toc.js +1 -1
- package/dist/release-manifest.json +4 -4
- package/package.json +5 -1
- package/scss/_components.scss +2 -0
- package/scss/components/_gallery.scss +73 -0
- package/scss/components/_item.scss +96 -0
- package/scss/foundations/_globals.scss +1 -0
- package/scss/foundations/_scope.scss +2 -0
- package/scss/mixins/_banner.scss +1 -1
- package/scss/moo-core.scss +1 -0
- package/scss/moo-ui.scss +2 -0
- package/scss/settings/_component_variables.scss +8 -0
- package/scss/utilities/_gallery.scss +25 -0
- package/scss/utilities/_motion.scss +5 -0
- package/scss/utilities/_motion_primitives.scss +10 -0
package/dist/js/moo-ui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Moo UI moo-ui.js v2.
|
|
2
|
+
* Moo UI moo-ui.js v2.3.0 (https://wpmoo.org/)
|
|
3
3
|
* Copyright 2026 WPMoo Authors
|
|
4
4
|
* Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
|
|
5
5
|
*/
|
|
@@ -364,9 +364,9 @@ var Combobox = class _Combobox {
|
|
|
364
364
|
const needle = normalize(this._input.value);
|
|
365
365
|
let count = 0;
|
|
366
366
|
this._options.forEach((option) => {
|
|
367
|
-
const
|
|
368
|
-
option.hidden = !
|
|
369
|
-
count +=
|
|
367
|
+
const matches2 = !needle || normalize(option.textContent).includes(needle);
|
|
368
|
+
option.hidden = !matches2;
|
|
369
|
+
count += matches2 ? 1 : 0;
|
|
370
370
|
});
|
|
371
371
|
this._syncGroupVisibility();
|
|
372
372
|
this._empty.hidden = count !== 0;
|
|
@@ -4597,6 +4597,559 @@ var FloatingPanel = class _FloatingPanel {
|
|
|
4597
4597
|
}
|
|
4598
4598
|
};
|
|
4599
4599
|
|
|
4600
|
+
// src/js/components/image-gallery.js
|
|
4601
|
+
var instances9 = /* @__PURE__ */ new WeakMap();
|
|
4602
|
+
var SLIDESHOW_INTERVAL = 5e3;
|
|
4603
|
+
var RESIZE_DURATION = 250;
|
|
4604
|
+
var ImageGallery = class _ImageGallery {
|
|
4605
|
+
static getInstance(element) {
|
|
4606
|
+
return element?.nodeType === 1 ? instances9.get(element) || null : null;
|
|
4607
|
+
}
|
|
4608
|
+
static getOrCreateInstance(element) {
|
|
4609
|
+
return _ImageGallery.getInstance(element) || new _ImageGallery(element);
|
|
4610
|
+
}
|
|
4611
|
+
constructor(element) {
|
|
4612
|
+
if (element?.nodeType !== 1 || !element.matches("[data-image-gallery]")) {
|
|
4613
|
+
throw new TypeError("ImageGallery requires a [data-image-gallery] root element.");
|
|
4614
|
+
}
|
|
4615
|
+
const existing = instances9.get(element);
|
|
4616
|
+
if (existing) return existing;
|
|
4617
|
+
this._element = element;
|
|
4618
|
+
this._window = element.ownerDocument.defaultView;
|
|
4619
|
+
this._dialog = element.querySelector(".modal");
|
|
4620
|
+
this._carousel = this._dialog?.querySelector(".carousel");
|
|
4621
|
+
if (!this._dialog || !this._carousel) {
|
|
4622
|
+
throw new TypeError("ImageGallery requires a lightbox Dialog with a Carousel.");
|
|
4623
|
+
}
|
|
4624
|
+
this._slides = Array.from(this._carousel.querySelectorAll(".carousel-item"));
|
|
4625
|
+
this._counter = this._dialog.querySelector("[data-gallery-counter]");
|
|
4626
|
+
this._caption = this._dialog.querySelector("[data-gallery-caption]");
|
|
4627
|
+
this._credit = this._dialog.querySelector("[data-gallery-credit]");
|
|
4628
|
+
this._stage = this._dialog.querySelector("[data-gallery-stage]");
|
|
4629
|
+
this._frame = this._dialog.querySelector(".modal-dialog");
|
|
4630
|
+
this._progress = this._dialog.querySelector(".progress");
|
|
4631
|
+
this._progressBar = this._progress?.querySelector(".progress-bar");
|
|
4632
|
+
this._progress?.setAttribute("aria-hidden", "true");
|
|
4633
|
+
const controls = (suffix) => this._dialog.id ? element.ownerDocument.getElementById(`${this._dialog.id}-${suffix}`) : null;
|
|
4634
|
+
this._enterButton = controls("fullscreen");
|
|
4635
|
+
this._exitButton = controls("windowed");
|
|
4636
|
+
this._playButton = controls("play");
|
|
4637
|
+
this._pauseButton = controls("pause");
|
|
4638
|
+
this._listeners = [];
|
|
4639
|
+
this._listen(this._dialog, "show.bs.modal", (event) => this._onShow(event));
|
|
4640
|
+
this._listen(this._window, "resize", () => this._layoutStage());
|
|
4641
|
+
this._listen(this._dialog, "shown.bs.modal", () => this._carouselInstance());
|
|
4642
|
+
this._listen(this._carousel, "slide.bs.carousel", (event) => this.show(event.to, false));
|
|
4643
|
+
this._listen(this._carousel, "slid.bs.carousel", () => this._restartTimer());
|
|
4644
|
+
this._listen(this._dialog, "keydown", (event) => this._onKeydown(event));
|
|
4645
|
+
this._listen(this._dialog, "hidden.bs.modal", () => {
|
|
4646
|
+
this.play(false);
|
|
4647
|
+
this.fullscreen(false);
|
|
4648
|
+
});
|
|
4649
|
+
const actions = [
|
|
4650
|
+
[controls("previous"), () => this._step(false)],
|
|
4651
|
+
[controls("next"), () => this._step(true)],
|
|
4652
|
+
[this._playButton, () => this.play(true, true)],
|
|
4653
|
+
[this._pauseButton, () => this.play(false, true)],
|
|
4654
|
+
[this._enterButton, () => this.fullscreen(true, true)],
|
|
4655
|
+
[this._exitButton, () => this.fullscreen(false, true)]
|
|
4656
|
+
];
|
|
4657
|
+
for (const [button, action] of actions) {
|
|
4658
|
+
if (button) this._listen(button, "click", action);
|
|
4659
|
+
}
|
|
4660
|
+
instances9.set(element, this);
|
|
4661
|
+
}
|
|
4662
|
+
get index() {
|
|
4663
|
+
const active = this._slides.findIndex((slide) => slide.classList.contains("active"));
|
|
4664
|
+
return Math.max(active, 0);
|
|
4665
|
+
}
|
|
4666
|
+
/** Marks photo `index` current, loads it and its neighbours. */
|
|
4667
|
+
show(index, activate = true) {
|
|
4668
|
+
const count = this._slides.length;
|
|
4669
|
+
if (!count) return this;
|
|
4670
|
+
const current = (Number(index) % count + count) % count;
|
|
4671
|
+
if (activate) {
|
|
4672
|
+
this._slides.forEach((slide2, position) => {
|
|
4673
|
+
slide2.classList.toggle("active", position === current);
|
|
4674
|
+
});
|
|
4675
|
+
}
|
|
4676
|
+
for (const offset of [0, 1, -1]) {
|
|
4677
|
+
this._load(this._slides[(current + offset + count) % count]);
|
|
4678
|
+
}
|
|
4679
|
+
const slide = this._slides[current];
|
|
4680
|
+
if (this._caption) {
|
|
4681
|
+
this._caption.textContent = slide.getAttribute("data-gallery-slide-title") || slide.querySelector("img")?.alt || "";
|
|
4682
|
+
}
|
|
4683
|
+
if (this._credit) {
|
|
4684
|
+
const credit = slide.getAttribute("data-gallery-slide-credit") || "";
|
|
4685
|
+
this._credit.textContent = credit;
|
|
4686
|
+
this._credit.classList.toggle("d-none", !credit);
|
|
4687
|
+
}
|
|
4688
|
+
if (this._counter) {
|
|
4689
|
+
const template = this._counter.getAttribute("data-gallery-counter") || "{current} / {total}";
|
|
4690
|
+
this._counter.textContent = template.replaceAll("{current}", String(current + 1)).replaceAll("{total}", String(count));
|
|
4691
|
+
}
|
|
4692
|
+
return this;
|
|
4693
|
+
}
|
|
4694
|
+
/** Starts or stops the slideshow. */
|
|
4695
|
+
play(on = true, moveFocus = false) {
|
|
4696
|
+
this._playing = Boolean(on) && Boolean(this._carouselInstance());
|
|
4697
|
+
this._progress?.classList.toggle("d-none", !this._playing);
|
|
4698
|
+
this._restartTimer();
|
|
4699
|
+
this._playButton?.classList.toggle("d-none", this._playing);
|
|
4700
|
+
this._pauseButton?.classList.toggle("d-none", !this._playing);
|
|
4701
|
+
this._counter?.setAttribute("aria-live", this._playing ? "off" : "polite");
|
|
4702
|
+
if (moveFocus) (this._playing ? this._pauseButton : this._playButton)?.focus();
|
|
4703
|
+
return this;
|
|
4704
|
+
}
|
|
4705
|
+
get isFullscreen() {
|
|
4706
|
+
return Boolean(this._frame?.classList.contains("modal-fullscreen"));
|
|
4707
|
+
}
|
|
4708
|
+
/** Switches between the large dialog and full screen. */
|
|
4709
|
+
fullscreen(on = true, moveFocus = false) {
|
|
4710
|
+
if (!this._frame) return this;
|
|
4711
|
+
const value = Boolean(on);
|
|
4712
|
+
this._resize(() => {
|
|
4713
|
+
this._frame.classList.toggle("modal-fullscreen", value);
|
|
4714
|
+
this._layoutStage();
|
|
4715
|
+
});
|
|
4716
|
+
if (moveFocus) (value ? this._exitButton : this._enterButton)?.focus();
|
|
4717
|
+
return this;
|
|
4718
|
+
}
|
|
4719
|
+
dispose() {
|
|
4720
|
+
this._stopTimer();
|
|
4721
|
+
for (const [target, type, listener] of this._listeners) {
|
|
4722
|
+
target.removeEventListener(type, listener);
|
|
4723
|
+
}
|
|
4724
|
+
this._listeners = [];
|
|
4725
|
+
instances9.delete(this._element);
|
|
4726
|
+
}
|
|
4727
|
+
_listen(target, type, listener) {
|
|
4728
|
+
target.addEventListener(type, listener);
|
|
4729
|
+
this._listeners.push([target, type, listener]);
|
|
4730
|
+
}
|
|
4731
|
+
// The dialog fills the screen after the full-screen button, or below the
|
|
4732
|
+
// breakpoint of Bootstrap's .modal-fullscreen-{breakpoint}-down.
|
|
4733
|
+
_fillsScreen() {
|
|
4734
|
+
if (this.isFullscreen) return true;
|
|
4735
|
+
const breakpoint = this._frame?.className.match(/\bmodal-fullscreen-(\w+)-down\b/)?.[1];
|
|
4736
|
+
if (!breakpoint) return false;
|
|
4737
|
+
const width = Number.parseFloat(this._window.getComputedStyle(this._element.ownerDocument.documentElement).getPropertyValue(`--bs-breakpoint-${breakpoint}`));
|
|
4738
|
+
return Number.isFinite(width) && this._window.matchMedia(`(max-width: ${width - 0.02}px)`).matches;
|
|
4739
|
+
}
|
|
4740
|
+
// Runs one photo's time on the Progress bar, then shows the next photo.
|
|
4741
|
+
_restartTimer() {
|
|
4742
|
+
this._stopTimer();
|
|
4743
|
+
if (!this._playing) return;
|
|
4744
|
+
const advance = () => {
|
|
4745
|
+
if (this._playing) this._step(true);
|
|
4746
|
+
};
|
|
4747
|
+
if (typeof this._progressBar?.animate === "function") {
|
|
4748
|
+
const timer = this._progressBar.animate(
|
|
4749
|
+
[{ width: "0%" }, { width: "100%" }],
|
|
4750
|
+
{ duration: SLIDESHOW_INTERVAL, easing: "linear", fill: "forwards" }
|
|
4751
|
+
);
|
|
4752
|
+
timer.addEventListener("finish", () => {
|
|
4753
|
+
if (this._timer === timer) advance();
|
|
4754
|
+
});
|
|
4755
|
+
this._timer = timer;
|
|
4756
|
+
} else {
|
|
4757
|
+
this._timeout = this._window.setTimeout(advance, SLIDESHOW_INTERVAL);
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
_stopTimer() {
|
|
4761
|
+
this._timer?.cancel();
|
|
4762
|
+
this._timer = null;
|
|
4763
|
+
this._window.clearTimeout(this._timeout);
|
|
4764
|
+
}
|
|
4765
|
+
// Animates the dialog from its old box to the new one: the content keeps
|
|
4766
|
+
// its final layout and starts scaled to where it was (first, last, invert,
|
|
4767
|
+
// play). Without motion the change applies at once.
|
|
4768
|
+
_resize(change) {
|
|
4769
|
+
const content = this._frame.querySelector(".modal-content");
|
|
4770
|
+
const reduced = this._window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
4771
|
+
if (!content || typeof content.animate !== "function" || reduced || !this._dialog.classList.contains("show")) {
|
|
4772
|
+
change();
|
|
4773
|
+
return;
|
|
4774
|
+
}
|
|
4775
|
+
this._resizing?.cancel();
|
|
4776
|
+
const first = content.getBoundingClientRect();
|
|
4777
|
+
change();
|
|
4778
|
+
const last = content.getBoundingClientRect();
|
|
4779
|
+
if (!first.width || !first.height || !last.width || !last.height) return;
|
|
4780
|
+
this._resizing = content.animate([
|
|
4781
|
+
{
|
|
4782
|
+
transformOrigin: "0 0",
|
|
4783
|
+
transform: `translate(${first.left - last.left}px, ${first.top - last.top}px) scale(${first.width / last.width}, ${first.height / last.height})`
|
|
4784
|
+
},
|
|
4785
|
+
{ transformOrigin: "0 0", transform: "none" }
|
|
4786
|
+
], { duration: RESIZE_DURATION, easing: "cubic-bezier(0.2, 0, 0, 1)" });
|
|
4787
|
+
}
|
|
4788
|
+
_layoutStage() {
|
|
4789
|
+
const fill = this._fillsScreen();
|
|
4790
|
+
this._stage?.classList.toggle("gallery-stage", !fill);
|
|
4791
|
+
this._enterButton?.classList.toggle("d-none", fill);
|
|
4792
|
+
this._exitButton?.classList.toggle("d-none", !this.isFullscreen);
|
|
4793
|
+
}
|
|
4794
|
+
_onShow(event) {
|
|
4795
|
+
this._layoutStage();
|
|
4796
|
+
const trigger = event.relatedTarget;
|
|
4797
|
+
const value = trigger?.closest?.("[data-gallery-index]")?.getAttribute("data-gallery-index");
|
|
4798
|
+
this.show(value === null || value === void 0 ? 0 : Number.parseInt(value, 10) || 0);
|
|
4799
|
+
}
|
|
4800
|
+
_onKeydown(event) {
|
|
4801
|
+
if (!["ArrowLeft", "ArrowRight"].includes(event.key)) return;
|
|
4802
|
+
if (this._carousel.contains(event.target)) return;
|
|
4803
|
+
event.preventDefault();
|
|
4804
|
+
const rtl = this._element.ownerDocument.documentElement.dir === "rtl";
|
|
4805
|
+
this._step(event.key === "ArrowRight" !== rtl);
|
|
4806
|
+
}
|
|
4807
|
+
_step(forward) {
|
|
4808
|
+
const carousel = this._carouselInstance();
|
|
4809
|
+
if (!carousel) return;
|
|
4810
|
+
if (forward) carousel.next();
|
|
4811
|
+
else carousel.prev();
|
|
4812
|
+
}
|
|
4813
|
+
_carouselInstance(create = true) {
|
|
4814
|
+
const Carousel = this._window.bootstrap?.Carousel;
|
|
4815
|
+
if (!Carousel) return null;
|
|
4816
|
+
if (!create) return Carousel.getInstance(this._carousel);
|
|
4817
|
+
return Carousel.getOrCreateInstance(this._carousel, {
|
|
4818
|
+
interval: false,
|
|
4819
|
+
pause: false,
|
|
4820
|
+
ride: false,
|
|
4821
|
+
keyboard: true,
|
|
4822
|
+
touch: true,
|
|
4823
|
+
wrap: true
|
|
4824
|
+
});
|
|
4825
|
+
}
|
|
4826
|
+
_load(slide) {
|
|
4827
|
+
const image = slide?.querySelector("img[data-gallery-src]");
|
|
4828
|
+
if (!image || image.hasAttribute("src")) return;
|
|
4829
|
+
const srcset = image.getAttribute("data-gallery-srcset");
|
|
4830
|
+
if (srcset) image.setAttribute("srcset", srcset);
|
|
4831
|
+
image.setAttribute("src", image.getAttribute("data-gallery-src"));
|
|
4832
|
+
}
|
|
4833
|
+
};
|
|
4834
|
+
|
|
4835
|
+
// src/js/components/consent.js
|
|
4836
|
+
var instances10 = /* @__PURE__ */ new WeakMap();
|
|
4837
|
+
var documents = /* @__PURE__ */ new WeakMap();
|
|
4838
|
+
var RECORD_VERSION = 1;
|
|
4839
|
+
var DAY_SECONDS = 86400;
|
|
4840
|
+
var FOCUSABLE = "button, a[href], input:not([type='hidden']), select, textarea, [tabindex]:not([tabindex='-1'])";
|
|
4841
|
+
function webSource(value, base) {
|
|
4842
|
+
if (!value) return "";
|
|
4843
|
+
try {
|
|
4844
|
+
const url = new URL(value, base);
|
|
4845
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url.href : "";
|
|
4846
|
+
} catch {
|
|
4847
|
+
return "";
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
function matches(pattern, name) {
|
|
4851
|
+
return pattern.endsWith("*") ? name.startsWith(pattern.slice(0, -1)) : name === pattern;
|
|
4852
|
+
}
|
|
4853
|
+
var ConsentManager = class _ConsentManager {
|
|
4854
|
+
/** The manager bound to a [data-consent] element or to a document. */
|
|
4855
|
+
static getInstance(target) {
|
|
4856
|
+
if (target?.nodeType === 9) return documents.get(target) || null;
|
|
4857
|
+
return target?.nodeType === 1 ? instances10.get(target) || null : null;
|
|
4858
|
+
}
|
|
4859
|
+
static getOrCreateInstance(element, options) {
|
|
4860
|
+
return _ConsentManager.getInstance(element) || new _ConsentManager(element, options);
|
|
4861
|
+
}
|
|
4862
|
+
constructor(element, { reload } = {}) {
|
|
4863
|
+
if (element?.nodeType !== 1 || !element.matches("[data-consent]") || !element.id) {
|
|
4864
|
+
throw new TypeError("ConsentManager requires a named [data-consent] element.");
|
|
4865
|
+
}
|
|
4866
|
+
const existing = instances10.get(element);
|
|
4867
|
+
if (existing) return existing;
|
|
4868
|
+
this._element = element;
|
|
4869
|
+
this._document = element.ownerDocument;
|
|
4870
|
+
if (documents.has(this._document)) {
|
|
4871
|
+
throw new TypeError("ConsentManager allows one consent card per document.");
|
|
4872
|
+
}
|
|
4873
|
+
this._window = this._document.defaultView;
|
|
4874
|
+
const source = element.querySelector("script[data-consent-config]");
|
|
4875
|
+
if (!source) throw new TypeError("ConsentManager requires a [data-consent-config] script.");
|
|
4876
|
+
this._config = JSON.parse(source.textContent);
|
|
4877
|
+
this._cookie = { name: "moo_consent", days: 365, ...this._config.cookie || {} };
|
|
4878
|
+
this._categories = this._config.categories || [];
|
|
4879
|
+
this._optional = this._categories.filter((category) => !category.required).map((category) => category.id);
|
|
4880
|
+
this._reload = reload || (() => this._window.location.reload());
|
|
4881
|
+
this._waiting = /* @__PURE__ */ new Map();
|
|
4882
|
+
this._listeners = [];
|
|
4883
|
+
this._trapping = false;
|
|
4884
|
+
this._returnFocus = null;
|
|
4885
|
+
const record = this._read();
|
|
4886
|
+
this._valid = Boolean(record);
|
|
4887
|
+
this._choices = this._normalize(record ? record.c : {});
|
|
4888
|
+
const control = (suffix) => this._document.getElementById(`${element.id}-${suffix}`);
|
|
4889
|
+
const actions = [
|
|
4890
|
+
[control("accept"), () => this.acceptAll()],
|
|
4891
|
+
[control("decline"), () => this.declineAll()],
|
|
4892
|
+
[control("save"), () => this.save(this._switchChoices(), "save")]
|
|
4893
|
+
];
|
|
4894
|
+
for (const [button, action] of actions) {
|
|
4895
|
+
if (button) this._listen(button, "click", action);
|
|
4896
|
+
}
|
|
4897
|
+
this._listen(this._document, "click", (event) => this._onDocumentClick(event));
|
|
4898
|
+
this._listen(element, "keydown", (event) => this._onKeydown(event));
|
|
4899
|
+
instances10.set(element, this);
|
|
4900
|
+
documents.set(this._document, this);
|
|
4901
|
+
this.activate(this._document);
|
|
4902
|
+
if (!this._valid) this._show();
|
|
4903
|
+
this.ready = Promise.resolve(this);
|
|
4904
|
+
queueMicrotask(() => this._emit("ready", { choices: this.choices, valid: this._valid, revision: this.revision }));
|
|
4905
|
+
}
|
|
4906
|
+
get revision() {
|
|
4907
|
+
return String(this._config.revision);
|
|
4908
|
+
}
|
|
4909
|
+
get valid() {
|
|
4910
|
+
return this._valid;
|
|
4911
|
+
}
|
|
4912
|
+
get choices() {
|
|
4913
|
+
return { ...this._choices };
|
|
4914
|
+
}
|
|
4915
|
+
get isOpen() {
|
|
4916
|
+
return !this._element.hidden;
|
|
4917
|
+
}
|
|
4918
|
+
granted(id) {
|
|
4919
|
+
const category = this._categories.find((entry) => entry.id === id);
|
|
4920
|
+
if (!category) return false;
|
|
4921
|
+
return category.required ? true : this._valid && this._choices[id] === true;
|
|
4922
|
+
}
|
|
4923
|
+
/** Runs callback now if granted, otherwise on grant. Returns an unsubscribe function. */
|
|
4924
|
+
whenGranted(id, callback) {
|
|
4925
|
+
if (this.granted(id)) {
|
|
4926
|
+
this._run(callback);
|
|
4927
|
+
return () => {
|
|
4928
|
+
};
|
|
4929
|
+
}
|
|
4930
|
+
if (!this._waiting.has(id)) this._waiting.set(id, /* @__PURE__ */ new Set());
|
|
4931
|
+
this._waiting.get(id).add(callback);
|
|
4932
|
+
return () => this._waiting.get(id)?.delete(callback);
|
|
4933
|
+
}
|
|
4934
|
+
acceptAll() {
|
|
4935
|
+
return this.save(Object.fromEntries(this._optional.map((id) => [id, true])), "accept-all");
|
|
4936
|
+
}
|
|
4937
|
+
declineAll() {
|
|
4938
|
+
return this.save({}, "decline");
|
|
4939
|
+
}
|
|
4940
|
+
grant(id) {
|
|
4941
|
+
return this.save({ ...this._choices, [id]: true }, "grant");
|
|
4942
|
+
}
|
|
4943
|
+
deny(id) {
|
|
4944
|
+
return this.save({ ...this._choices, [id]: false }, "deny");
|
|
4945
|
+
}
|
|
4946
|
+
/** Stores the choice; optional categories not named become false. */
|
|
4947
|
+
save(choices, source = "save") {
|
|
4948
|
+
const previous = this._valid ? this.choices : this._normalize({});
|
|
4949
|
+
const wasValid = this._valid;
|
|
4950
|
+
const next = this._normalize(choices);
|
|
4951
|
+
this._write(next);
|
|
4952
|
+
this._valid = true;
|
|
4953
|
+
this._choices = next;
|
|
4954
|
+
const granted = this._optional.filter((id) => next[id] && !previous[id]);
|
|
4955
|
+
const revoked = wasValid ? this._optional.filter((id) => previous[id] && !next[id]) : [];
|
|
4956
|
+
this.close();
|
|
4957
|
+
this.activate(this._document);
|
|
4958
|
+
for (const id of granted) {
|
|
4959
|
+
const waiting = this._waiting.get(id);
|
|
4960
|
+
this._waiting.delete(id);
|
|
4961
|
+
waiting?.forEach((callback) => this._run(callback));
|
|
4962
|
+
}
|
|
4963
|
+
this._emit("change", { choices: this.choices, previous, granted, revoked, source });
|
|
4964
|
+
if (revoked.length) {
|
|
4965
|
+
revoked.forEach((id) => this._forget(id));
|
|
4966
|
+
this._reload();
|
|
4967
|
+
}
|
|
4968
|
+
return this.choices;
|
|
4969
|
+
}
|
|
4970
|
+
/** Opens the card with the stored choices; traps focus until closed. */
|
|
4971
|
+
open(trigger = null) {
|
|
4972
|
+
this._returnFocus = trigger;
|
|
4973
|
+
this._trapping = true;
|
|
4974
|
+
this._show();
|
|
4975
|
+
this._document.getElementById(`${this._element.id}-title`)?.focus({ preventScroll: true });
|
|
4976
|
+
this._emit("open", {});
|
|
4977
|
+
}
|
|
4978
|
+
/** Hides the card; it stays open while no valid choice exists. */
|
|
4979
|
+
close() {
|
|
4980
|
+
if (!this._valid) return false;
|
|
4981
|
+
const wasOpen = this.isOpen;
|
|
4982
|
+
const returnFocus = this._trapping ? this._returnFocus : null;
|
|
4983
|
+
this._element.hidden = true;
|
|
4984
|
+
this._trapping = false;
|
|
4985
|
+
this._returnFocus = null;
|
|
4986
|
+
if (returnFocus?.isConnected) returnFocus.focus({ preventScroll: true });
|
|
4987
|
+
if (wasOpen) this._emit("close", {});
|
|
4988
|
+
return true;
|
|
4989
|
+
}
|
|
4990
|
+
/** Activates gated elements under root whose category is granted. */
|
|
4991
|
+
activate(root = this._document) {
|
|
4992
|
+
this._activateWhere(root, (id) => this.granted(id));
|
|
4993
|
+
}
|
|
4994
|
+
dispose() {
|
|
4995
|
+
if (instances10.get(this._element) !== this) return;
|
|
4996
|
+
this._listeners.forEach(([target, type, handler]) => target.removeEventListener(type, handler));
|
|
4997
|
+
this._listeners = [];
|
|
4998
|
+
this._waiting.clear();
|
|
4999
|
+
instances10.delete(this._element);
|
|
5000
|
+
documents.delete(this._document);
|
|
5001
|
+
}
|
|
5002
|
+
// A failing site callback must not stop the rest of a save, such as the
|
|
5003
|
+
// cleanup and reload after a withdrawal; the error is still reported.
|
|
5004
|
+
_run(callback) {
|
|
5005
|
+
try {
|
|
5006
|
+
callback(this);
|
|
5007
|
+
} catch (error) {
|
|
5008
|
+
if (typeof this._window.reportError === "function") this._window.reportError(error);
|
|
5009
|
+
else this._window.console?.error(error);
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
_activateWhere(root, allowed) {
|
|
5013
|
+
for (const element of root.querySelectorAll("[data-consent-category]")) {
|
|
5014
|
+
if (allowed(element.getAttribute("data-consent-category"))) this._activateElement(element);
|
|
5015
|
+
}
|
|
5016
|
+
for (const placeholder of root.querySelectorAll("[data-consent-placeholder]")) {
|
|
5017
|
+
if (allowed(placeholder.getAttribute("data-consent-placeholder"))) placeholder.hidden = true;
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
_listen(target, type, handler) {
|
|
5021
|
+
target.addEventListener(type, handler);
|
|
5022
|
+
this._listeners.push([target, type, handler]);
|
|
5023
|
+
}
|
|
5024
|
+
_emit(name, detail) {
|
|
5025
|
+
this._document.dispatchEvent(new this._window.CustomEvent(`moo:consent:${name}`, { bubbles: true, detail }));
|
|
5026
|
+
}
|
|
5027
|
+
_normalize(choices) {
|
|
5028
|
+
return Object.fromEntries(this._optional.map((id) => [id, choices?.[id] === true || choices?.[id] === 1]));
|
|
5029
|
+
}
|
|
5030
|
+
_switch(id) {
|
|
5031
|
+
return this._document.getElementById(`${this._element.id}-category-${id}`);
|
|
5032
|
+
}
|
|
5033
|
+
_switchChoices() {
|
|
5034
|
+
return Object.fromEntries(this._optional.map((id) => [id, Boolean(this._switch(id)?.checked)]));
|
|
5035
|
+
}
|
|
5036
|
+
_show() {
|
|
5037
|
+
for (const id of this._optional) {
|
|
5038
|
+
const input = this._switch(id);
|
|
5039
|
+
if (input) input.checked = this.granted(id);
|
|
5040
|
+
}
|
|
5041
|
+
this._element.hidden = false;
|
|
5042
|
+
}
|
|
5043
|
+
_read() {
|
|
5044
|
+
const prefix = `${this._cookie.name}=`;
|
|
5045
|
+
const raw = this._document.cookie.split("; ").find((part) => part.startsWith(prefix));
|
|
5046
|
+
if (!raw) return null;
|
|
5047
|
+
try {
|
|
5048
|
+
const record = JSON.parse(decodeURIComponent(raw.slice(prefix.length)));
|
|
5049
|
+
const age = Date.now() / 1e3 - Number(record.t);
|
|
5050
|
+
if (record.v !== RECORD_VERSION || record.r !== this.revision) return null;
|
|
5051
|
+
if (!(age >= 0 && age < this._cookie.days * DAY_SECONDS)) return null;
|
|
5052
|
+
return record;
|
|
5053
|
+
} catch {
|
|
5054
|
+
return null;
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
5057
|
+
_write(choices) {
|
|
5058
|
+
const record = {
|
|
5059
|
+
v: RECORD_VERSION,
|
|
5060
|
+
r: this.revision,
|
|
5061
|
+
t: Math.floor(Date.now() / 1e3),
|
|
5062
|
+
c: Object.fromEntries(Object.entries(choices).map(([id, value]) => [id, value ? 1 : 0]))
|
|
5063
|
+
};
|
|
5064
|
+
const secure = this._window.location.protocol === "https:" ? "; Secure" : "";
|
|
5065
|
+
this._document.cookie = `${this._cookie.name}=${encodeURIComponent(JSON.stringify(record))}; Path=/; Max-Age=${this._cookie.days * DAY_SECONDS}; SameSite=Lax${secure}`;
|
|
5066
|
+
}
|
|
5067
|
+
_forget(id) {
|
|
5068
|
+
const category = this._categories.find((entry) => entry.id === id);
|
|
5069
|
+
const host = this._window.location.hostname;
|
|
5070
|
+
const parts = host.split(".");
|
|
5071
|
+
const domains = [""];
|
|
5072
|
+
for (let index = 0; index < parts.length - 1; index += 1) domains.push(`; Domain=${parts.slice(index).join(".")}`);
|
|
5073
|
+
const cookieNames = this._document.cookie.split("; ").map((part) => part.split("=")[0]).filter(Boolean);
|
|
5074
|
+
for (const item of category?.storage || []) {
|
|
5075
|
+
if (item.type === "cookie") {
|
|
5076
|
+
for (const name of cookieNames.filter((candidate) => matches(item.name, candidate))) {
|
|
5077
|
+
for (const domain of domains) this._document.cookie = `${name}=; Path=/; Max-Age=0${domain}`;
|
|
5078
|
+
}
|
|
5079
|
+
} else {
|
|
5080
|
+
try {
|
|
5081
|
+
const storage = item.type === "local" ? this._window.localStorage : this._window.sessionStorage;
|
|
5082
|
+
Object.keys(storage).filter((key) => matches(item.name, key)).forEach((key) => storage.removeItem(key));
|
|
5083
|
+
} catch {
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5087
|
+
}
|
|
5088
|
+
_activateElement(element) {
|
|
5089
|
+
if (element.hasAttribute("data-consent-active")) return;
|
|
5090
|
+
const declared = element.getAttribute("data-consent-src");
|
|
5091
|
+
const src = webSource(declared, this._document.baseURI);
|
|
5092
|
+
if (declared && !src) return;
|
|
5093
|
+
if (element.localName === "script") {
|
|
5094
|
+
const script = this._document.createElement("script");
|
|
5095
|
+
for (const { name, value } of element.attributes) {
|
|
5096
|
+
if (name !== "type" && !name.startsWith("data-consent-")) script.setAttribute(name, value);
|
|
5097
|
+
}
|
|
5098
|
+
const type = element.getAttribute("data-consent-type");
|
|
5099
|
+
if (type) script.type = type;
|
|
5100
|
+
if (src) script.src = src;
|
|
5101
|
+
else script.textContent = element.textContent;
|
|
5102
|
+
script.setAttribute("data-consent-category", element.getAttribute("data-consent-category"));
|
|
5103
|
+
script.setAttribute("data-consent-active", "");
|
|
5104
|
+
element.replaceWith(script);
|
|
5105
|
+
return;
|
|
5106
|
+
}
|
|
5107
|
+
element.setAttribute("data-consent-active", "");
|
|
5108
|
+
if (src) element.setAttribute("src", src);
|
|
5109
|
+
element.hidden = false;
|
|
5110
|
+
}
|
|
5111
|
+
_onDocumentClick(event) {
|
|
5112
|
+
const control = event.target.closest?.("[data-consent-open], [data-consent-grant]");
|
|
5113
|
+
if (!control || control.matches(":disabled")) return;
|
|
5114
|
+
if (control.hasAttribute("data-consent-open")) {
|
|
5115
|
+
if (control.getAttribute("data-consent-open") !== this._element.id) return;
|
|
5116
|
+
event.preventDefault();
|
|
5117
|
+
this.open(control);
|
|
5118
|
+
return;
|
|
5119
|
+
}
|
|
5120
|
+
const id = control.getAttribute("data-consent-grant");
|
|
5121
|
+
if (!this._categories.some((category) => category.id === id)) return;
|
|
5122
|
+
event.preventDefault();
|
|
5123
|
+
const placeholder = control.closest("[data-consent-placeholder]");
|
|
5124
|
+
const scope = control.closest("[data-consent-embed]") || placeholder?.parentElement;
|
|
5125
|
+
if (placeholder?.querySelector("[data-consent-remember]")?.checked || !scope) {
|
|
5126
|
+
this.grant(id);
|
|
5127
|
+
} else {
|
|
5128
|
+
this._activateWhere(scope, (category) => category === id);
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
_onKeydown(event) {
|
|
5132
|
+
if (!this._trapping) return;
|
|
5133
|
+
if (event.key === "Escape") {
|
|
5134
|
+
if (this.close()) event.preventDefault();
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
if (event.key !== "Tab") return;
|
|
5138
|
+
const focusable = Array.from(this._element.querySelectorAll(FOCUSABLE)).filter((control) => !control.matches(":disabled") && control.getClientRects().length > 0);
|
|
5139
|
+
if (!focusable.length) return;
|
|
5140
|
+
const first = focusable[0];
|
|
5141
|
+
const last = focusable[focusable.length - 1];
|
|
5142
|
+
const active = this._document.activeElement;
|
|
5143
|
+
if (event.shiftKey && (active === first || !this._element.contains(active) || active.id === `${this._element.id}-title`)) {
|
|
5144
|
+
event.preventDefault();
|
|
5145
|
+
last.focus();
|
|
5146
|
+
} else if (!event.shiftKey && active === last) {
|
|
5147
|
+
event.preventDefault();
|
|
5148
|
+
first.focus();
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
};
|
|
5152
|
+
|
|
4600
5153
|
// src/js/moo-ui.js
|
|
4601
5154
|
import PasswordRequirements from "./password-requirements.js";
|
|
4602
5155
|
async function loadChart() {
|
|
@@ -4606,6 +5159,8 @@ async function loadChart() {
|
|
|
4606
5159
|
var MooUI = {
|
|
4607
5160
|
PasswordRequirements,
|
|
4608
5161
|
FloatingPanel,
|
|
5162
|
+
ImageGallery,
|
|
5163
|
+
ConsentManager,
|
|
4609
5164
|
loadChart,
|
|
4610
5165
|
Combobox,
|
|
4611
5166
|
ContextMenu,
|
|
@@ -4622,10 +5177,12 @@ var MooUI = {
|
|
|
4622
5177
|
var moo_ui_default = MooUI;
|
|
4623
5178
|
export {
|
|
4624
5179
|
Combobox,
|
|
5180
|
+
ConsentManager,
|
|
4625
5181
|
ContextMenu,
|
|
4626
5182
|
DataTable,
|
|
4627
5183
|
MooDatepicker as Datepicker,
|
|
4628
5184
|
FloatingPanel,
|
|
5185
|
+
ImageGallery,
|
|
4629
5186
|
MooCalendar,
|
|
4630
5187
|
MooDateRangePicker,
|
|
4631
5188
|
OverlayScrollbar,
|