@wpmoo/ui 2.1.1 → 2.2.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 +23 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/certification.json +2 -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/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 +238 -1
- 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 +3 -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/chart.js
CHANGED
package/dist/js/chart.min.js
CHANGED
package/dist/js/combobox.js
CHANGED
package/dist/js/context-menu.js
CHANGED
package/dist/js/datatable.js
CHANGED
package/dist/js/datepicker.js
CHANGED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Moo UI image-gallery.js v2.2.0 (https://wpmoo.org/)
|
|
3
|
+
* Copyright 2026 WPMoo Authors
|
|
4
|
+
* Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const instances = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
const SLIDESHOW_INTERVAL = 5e3;
|
|
9
|
+
const RESIZE_DURATION = 250;
|
|
10
|
+
class ImageGallery {
|
|
11
|
+
static getInstance(element) {
|
|
12
|
+
return element?.nodeType === 1 ? instances.get(element) || null : null;
|
|
13
|
+
}
|
|
14
|
+
static getOrCreateInstance(element) {
|
|
15
|
+
return ImageGallery.getInstance(element) || new ImageGallery(element);
|
|
16
|
+
}
|
|
17
|
+
constructor(element) {
|
|
18
|
+
if (element?.nodeType !== 1 || !element.matches("[data-image-gallery]")) {
|
|
19
|
+
throw new TypeError("ImageGallery requires a [data-image-gallery] root element.");
|
|
20
|
+
}
|
|
21
|
+
const existing = instances.get(element);
|
|
22
|
+
if (existing) return existing;
|
|
23
|
+
this._element = element;
|
|
24
|
+
this._window = element.ownerDocument.defaultView;
|
|
25
|
+
this._dialog = element.querySelector(".modal");
|
|
26
|
+
this._carousel = this._dialog?.querySelector(".carousel");
|
|
27
|
+
if (!this._dialog || !this._carousel) {
|
|
28
|
+
throw new TypeError("ImageGallery requires a lightbox Dialog with a Carousel.");
|
|
29
|
+
}
|
|
30
|
+
this._slides = Array.from(this._carousel.querySelectorAll(".carousel-item"));
|
|
31
|
+
this._counter = this._dialog.querySelector("[data-gallery-counter]");
|
|
32
|
+
this._caption = this._dialog.querySelector("[data-gallery-caption]");
|
|
33
|
+
this._credit = this._dialog.querySelector("[data-gallery-credit]");
|
|
34
|
+
this._stage = this._dialog.querySelector("[data-gallery-stage]");
|
|
35
|
+
this._frame = this._dialog.querySelector(".modal-dialog");
|
|
36
|
+
this._progress = this._dialog.querySelector(".progress");
|
|
37
|
+
this._progressBar = this._progress?.querySelector(".progress-bar");
|
|
38
|
+
this._progress?.setAttribute("aria-hidden", "true");
|
|
39
|
+
const controls = (suffix) => this._dialog.id ? element.ownerDocument.getElementById(`${this._dialog.id}-${suffix}`) : null;
|
|
40
|
+
this._enterButton = controls("fullscreen");
|
|
41
|
+
this._exitButton = controls("windowed");
|
|
42
|
+
this._playButton = controls("play");
|
|
43
|
+
this._pauseButton = controls("pause");
|
|
44
|
+
this._listeners = [];
|
|
45
|
+
this._listen(this._dialog, "show.bs.modal", (event) => this._onShow(event));
|
|
46
|
+
this._listen(this._window, "resize", () => this._layoutStage());
|
|
47
|
+
this._listen(this._dialog, "shown.bs.modal", () => this._carouselInstance());
|
|
48
|
+
this._listen(this._carousel, "slide.bs.carousel", (event) => this.show(event.to, false));
|
|
49
|
+
this._listen(this._carousel, "slid.bs.carousel", () => this._restartTimer());
|
|
50
|
+
this._listen(this._dialog, "keydown", (event) => this._onKeydown(event));
|
|
51
|
+
this._listen(this._dialog, "hidden.bs.modal", () => {
|
|
52
|
+
this.play(false);
|
|
53
|
+
this.fullscreen(false);
|
|
54
|
+
});
|
|
55
|
+
const actions = [
|
|
56
|
+
[controls("previous"), () => this._step(false)],
|
|
57
|
+
[controls("next"), () => this._step(true)],
|
|
58
|
+
[this._playButton, () => this.play(true, true)],
|
|
59
|
+
[this._pauseButton, () => this.play(false, true)],
|
|
60
|
+
[this._enterButton, () => this.fullscreen(true, true)],
|
|
61
|
+
[this._exitButton, () => this.fullscreen(false, true)]
|
|
62
|
+
];
|
|
63
|
+
for (const [button, action] of actions) {
|
|
64
|
+
if (button) this._listen(button, "click", action);
|
|
65
|
+
}
|
|
66
|
+
instances.set(element, this);
|
|
67
|
+
}
|
|
68
|
+
get index() {
|
|
69
|
+
const active = this._slides.findIndex((slide) => slide.classList.contains("active"));
|
|
70
|
+
return Math.max(active, 0);
|
|
71
|
+
}
|
|
72
|
+
/** Marks photo `index` current, loads it and its neighbours. */
|
|
73
|
+
show(index, activate = true) {
|
|
74
|
+
const count = this._slides.length;
|
|
75
|
+
if (!count) return this;
|
|
76
|
+
const current = (Number(index) % count + count) % count;
|
|
77
|
+
if (activate) {
|
|
78
|
+
this._slides.forEach((slide2, position) => {
|
|
79
|
+
slide2.classList.toggle("active", position === current);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
for (const offset of [0, 1, -1]) {
|
|
83
|
+
this._load(this._slides[(current + offset + count) % count]);
|
|
84
|
+
}
|
|
85
|
+
const slide = this._slides[current];
|
|
86
|
+
if (this._caption) {
|
|
87
|
+
this._caption.textContent = slide.getAttribute("data-gallery-slide-title") || slide.querySelector("img")?.alt || "";
|
|
88
|
+
}
|
|
89
|
+
if (this._credit) {
|
|
90
|
+
const credit = slide.getAttribute("data-gallery-slide-credit") || "";
|
|
91
|
+
this._credit.textContent = credit;
|
|
92
|
+
this._credit.classList.toggle("d-none", !credit);
|
|
93
|
+
}
|
|
94
|
+
if (this._counter) {
|
|
95
|
+
const template = this._counter.getAttribute("data-gallery-counter") || "{current} / {total}";
|
|
96
|
+
this._counter.textContent = template.replaceAll("{current}", String(current + 1)).replaceAll("{total}", String(count));
|
|
97
|
+
}
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
/** Starts or stops the slideshow. */
|
|
101
|
+
play(on = true, moveFocus = false) {
|
|
102
|
+
this._playing = Boolean(on) && Boolean(this._carouselInstance());
|
|
103
|
+
this._progress?.classList.toggle("d-none", !this._playing);
|
|
104
|
+
this._restartTimer();
|
|
105
|
+
this._playButton?.classList.toggle("d-none", this._playing);
|
|
106
|
+
this._pauseButton?.classList.toggle("d-none", !this._playing);
|
|
107
|
+
this._counter?.setAttribute("aria-live", this._playing ? "off" : "polite");
|
|
108
|
+
if (moveFocus) (this._playing ? this._pauseButton : this._playButton)?.focus();
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
get isFullscreen() {
|
|
112
|
+
return Boolean(this._frame?.classList.contains("modal-fullscreen"));
|
|
113
|
+
}
|
|
114
|
+
/** Switches between the large dialog and full screen. */
|
|
115
|
+
fullscreen(on = true, moveFocus = false) {
|
|
116
|
+
if (!this._frame) return this;
|
|
117
|
+
const value = Boolean(on);
|
|
118
|
+
this._resize(() => {
|
|
119
|
+
this._frame.classList.toggle("modal-fullscreen", value);
|
|
120
|
+
this._layoutStage();
|
|
121
|
+
});
|
|
122
|
+
if (moveFocus) (value ? this._exitButton : this._enterButton)?.focus();
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
dispose() {
|
|
126
|
+
this._stopTimer();
|
|
127
|
+
for (const [target, type, listener] of this._listeners) {
|
|
128
|
+
target.removeEventListener(type, listener);
|
|
129
|
+
}
|
|
130
|
+
this._listeners = [];
|
|
131
|
+
instances.delete(this._element);
|
|
132
|
+
}
|
|
133
|
+
_listen(target, type, listener) {
|
|
134
|
+
target.addEventListener(type, listener);
|
|
135
|
+
this._listeners.push([target, type, listener]);
|
|
136
|
+
}
|
|
137
|
+
// The dialog fills the screen after the full-screen button, or below the
|
|
138
|
+
// breakpoint of Bootstrap's .modal-fullscreen-{breakpoint}-down.
|
|
139
|
+
_fillsScreen() {
|
|
140
|
+
if (this.isFullscreen) return true;
|
|
141
|
+
const breakpoint = this._frame?.className.match(/\bmodal-fullscreen-(\w+)-down\b/)?.[1];
|
|
142
|
+
if (!breakpoint) return false;
|
|
143
|
+
const width = Number.parseFloat(this._window.getComputedStyle(this._element.ownerDocument.documentElement).getPropertyValue(`--bs-breakpoint-${breakpoint}`));
|
|
144
|
+
return Number.isFinite(width) && this._window.matchMedia(`(max-width: ${width - 0.02}px)`).matches;
|
|
145
|
+
}
|
|
146
|
+
// Runs one photo's time on the Progress bar, then shows the next photo.
|
|
147
|
+
_restartTimer() {
|
|
148
|
+
this._stopTimer();
|
|
149
|
+
if (!this._playing) return;
|
|
150
|
+
const advance = () => {
|
|
151
|
+
if (this._playing) this._step(true);
|
|
152
|
+
};
|
|
153
|
+
if (typeof this._progressBar?.animate === "function") {
|
|
154
|
+
const timer = this._progressBar.animate(
|
|
155
|
+
[{ width: "0%" }, { width: "100%" }],
|
|
156
|
+
{ duration: SLIDESHOW_INTERVAL, easing: "linear", fill: "forwards" }
|
|
157
|
+
);
|
|
158
|
+
timer.addEventListener("finish", () => {
|
|
159
|
+
if (this._timer === timer) advance();
|
|
160
|
+
});
|
|
161
|
+
this._timer = timer;
|
|
162
|
+
} else {
|
|
163
|
+
this._timeout = this._window.setTimeout(advance, SLIDESHOW_INTERVAL);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
_stopTimer() {
|
|
167
|
+
this._timer?.cancel();
|
|
168
|
+
this._timer = null;
|
|
169
|
+
this._window.clearTimeout(this._timeout);
|
|
170
|
+
}
|
|
171
|
+
// Animates the dialog from its old box to the new one: the content keeps
|
|
172
|
+
// its final layout and starts scaled to where it was (first, last, invert,
|
|
173
|
+
// play). Without motion the change applies at once.
|
|
174
|
+
_resize(change) {
|
|
175
|
+
const content = this._frame.querySelector(".modal-content");
|
|
176
|
+
const reduced = this._window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
177
|
+
if (!content || typeof content.animate !== "function" || reduced || !this._dialog.classList.contains("show")) {
|
|
178
|
+
change();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this._resizing?.cancel();
|
|
182
|
+
const first = content.getBoundingClientRect();
|
|
183
|
+
change();
|
|
184
|
+
const last = content.getBoundingClientRect();
|
|
185
|
+
if (!first.width || !first.height || !last.width || !last.height) return;
|
|
186
|
+
this._resizing = content.animate([
|
|
187
|
+
{
|
|
188
|
+
transformOrigin: "0 0",
|
|
189
|
+
transform: `translate(${first.left - last.left}px, ${first.top - last.top}px) scale(${first.width / last.width}, ${first.height / last.height})`
|
|
190
|
+
},
|
|
191
|
+
{ transformOrigin: "0 0", transform: "none" }
|
|
192
|
+
], { duration: RESIZE_DURATION, easing: "cubic-bezier(0.2, 0, 0, 1)" });
|
|
193
|
+
}
|
|
194
|
+
_layoutStage() {
|
|
195
|
+
const fill = this._fillsScreen();
|
|
196
|
+
this._stage?.classList.toggle("gallery-stage", !fill);
|
|
197
|
+
this._enterButton?.classList.toggle("d-none", fill);
|
|
198
|
+
this._exitButton?.classList.toggle("d-none", !this.isFullscreen);
|
|
199
|
+
}
|
|
200
|
+
_onShow(event) {
|
|
201
|
+
this._layoutStage();
|
|
202
|
+
const trigger = event.relatedTarget;
|
|
203
|
+
const value = trigger?.closest?.("[data-gallery-index]")?.getAttribute("data-gallery-index");
|
|
204
|
+
this.show(value === null || value === void 0 ? 0 : Number.parseInt(value, 10) || 0);
|
|
205
|
+
}
|
|
206
|
+
_onKeydown(event) {
|
|
207
|
+
if (!["ArrowLeft", "ArrowRight"].includes(event.key)) return;
|
|
208
|
+
if (this._carousel.contains(event.target)) return;
|
|
209
|
+
event.preventDefault();
|
|
210
|
+
const rtl = this._element.ownerDocument.documentElement.dir === "rtl";
|
|
211
|
+
this._step(event.key === "ArrowRight" !== rtl);
|
|
212
|
+
}
|
|
213
|
+
_step(forward) {
|
|
214
|
+
const carousel = this._carouselInstance();
|
|
215
|
+
if (!carousel) return;
|
|
216
|
+
if (forward) carousel.next();
|
|
217
|
+
else carousel.prev();
|
|
218
|
+
}
|
|
219
|
+
_carouselInstance(create = true) {
|
|
220
|
+
const Carousel = this._window.bootstrap?.Carousel;
|
|
221
|
+
if (!Carousel) return null;
|
|
222
|
+
if (!create) return Carousel.getInstance(this._carousel);
|
|
223
|
+
return Carousel.getOrCreateInstance(this._carousel, {
|
|
224
|
+
interval: false,
|
|
225
|
+
pause: false,
|
|
226
|
+
ride: false,
|
|
227
|
+
keyboard: true,
|
|
228
|
+
touch: true,
|
|
229
|
+
wrap: true
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
_load(slide) {
|
|
233
|
+
const image = slide?.querySelector("img[data-gallery-src]");
|
|
234
|
+
if (!image || image.hasAttribute("src")) return;
|
|
235
|
+
const srcset = image.getAttribute("data-gallery-srcset");
|
|
236
|
+
if (srcset) image.setAttribute("srcset", srcset);
|
|
237
|
+
image.setAttribute("src", image.getAttribute("data-gallery-src"));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function initImageGalleries(root = document) {
|
|
241
|
+
const galleries = Array.from(root.querySelectorAll("[data-image-gallery]")).map((element) => ImageGallery.getOrCreateInstance(element));
|
|
242
|
+
return () => galleries.forEach((gallery) => gallery.dispose());
|
|
243
|
+
}
|
|
244
|
+
export {
|
|
245
|
+
ImageGallery as default,
|
|
246
|
+
initImageGalleries
|
|
247
|
+
};
|
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.2.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
|
*/
|
|
@@ -4597,6 +4597,241 @@ 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
|
+
|
|
4600
4835
|
// src/js/moo-ui.js
|
|
4601
4836
|
import PasswordRequirements from "./password-requirements.js";
|
|
4602
4837
|
async function loadChart() {
|
|
@@ -4606,6 +4841,7 @@ async function loadChart() {
|
|
|
4606
4841
|
var MooUI = {
|
|
4607
4842
|
PasswordRequirements,
|
|
4608
4843
|
FloatingPanel,
|
|
4844
|
+
ImageGallery,
|
|
4609
4845
|
loadChart,
|
|
4610
4846
|
Combobox,
|
|
4611
4847
|
ContextMenu,
|
|
@@ -4626,6 +4862,7 @@ export {
|
|
|
4626
4862
|
DataTable,
|
|
4627
4863
|
MooDatepicker as Datepicker,
|
|
4628
4864
|
FloatingPanel,
|
|
4865
|
+
ImageGallery,
|
|
4629
4866
|
MooCalendar,
|
|
4630
4867
|
MooDateRangePicker,
|
|
4631
4868
|
OverlayScrollbar,
|