@dodlhuat/basix 1.4.2 → 1.5.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 +55 -9
- package/css/alert.scss +9 -9
- package/css/badge.scss +5 -25
- package/css/bottom-sheet.scss +2 -6
- package/css/breadcrumb.scss +18 -9
- package/css/calendar.scss +19 -29
- package/css/carousel.scss +3 -3
- package/css/chart.scss +1 -6
- package/css/checkbox.scss +1 -1
- package/css/chips.scss +13 -23
- package/css/code-viewer.scss +2 -2
- package/css/color-picker.scss +3 -5
- package/css/context-menu.scss +3 -8
- package/css/datepicker.scss +84 -71
- package/css/defaults.scss +25 -0
- package/css/docs.scss +6 -6
- package/css/dropdown.scss +3 -8
- package/css/editor.scss +15 -18
- package/css/file-uploader.scss +6 -17
- package/css/flyout-menu.scss +5 -1
- package/css/form.scss +43 -57
- package/css/gallery.scss +15 -0
- package/css/group-picker.scss +28 -17
- package/css/icons.scss +7 -327
- package/css/lightbox.scss +7 -7
- package/css/mixins.scss +77 -0
- package/css/modal.scss +4 -8
- package/css/parameters.scss +8 -2
- package/css/placeholder.scss +2 -17
- package/css/popover.scss +2 -7
- package/css/properties.scss +0 -5
- package/css/push-menu.scss +1 -1
- package/css/range-slider.scss +7 -6
- package/css/stepper.scss +4 -3
- package/css/style.css +279 -554
- package/css/style.css.map +1 -1
- package/css/style.min.css +1 -1
- package/css/style.min.css.map +1 -1
- package/css/tabs.scss +3 -7
- package/css/timepicker.scss +3 -3
- package/css/tooltip.scss +1 -1
- package/css/typography.scss +3 -3
- package/css/virtual-dropdown.scss +12 -27
- package/js/bottom-sheet.d.ts +5 -5
- package/js/bottom-sheet.js +41 -58
- package/js/calendar.d.ts +1 -2
- package/js/calendar.js +77 -84
- package/js/carousel.d.ts +2 -1
- package/js/carousel.js +25 -16
- package/js/chart.d.ts +1 -1
- package/js/chart.js +104 -65
- package/js/code-viewer.d.ts +1 -1
- package/js/code-viewer.js +7 -18
- package/js/color-picker.d.ts +2 -2
- package/js/color-picker.js +20 -22
- package/js/context-menu.d.ts +1 -1
- package/js/context-menu.js +10 -12
- package/js/datepicker.d.ts +14 -3
- package/js/datepicker.js +180 -99
- package/js/docs-nav.js +1 -1
- package/js/dropdown.d.ts +1 -1
- package/js/dropdown.js +4 -5
- package/js/editor.d.ts +1 -1
- package/js/editor.js +30 -34
- package/js/file-uploader.d.ts +2 -9
- package/js/file-uploader.js +57 -86
- package/js/flyout-menu.d.ts +3 -3
- package/js/flyout-menu.js +20 -22
- package/js/gallery.d.ts +6 -2
- package/js/gallery.js +49 -20
- package/js/group-picker.d.ts +2 -1
- package/js/group-picker.js +30 -34
- package/js/lightbox.d.ts +5 -3
- package/js/lightbox.js +36 -31
- package/js/listeners.d.ts +7 -0
- package/js/listeners.js +14 -0
- package/js/modal.d.ts +4 -1
- package/js/modal.js +17 -22
- package/js/popover.d.ts +2 -2
- package/js/popover.js +42 -37
- package/js/position.js +3 -5
- package/js/push-menu.d.ts +8 -2
- package/js/push-menu.js +34 -23
- package/js/range-slider.d.ts +2 -2
- package/js/range-slider.js +7 -6
- package/js/scroll.js +4 -7
- package/js/scrollbar.d.ts +3 -3
- package/js/scrollbar.js +39 -42
- package/js/select.d.ts +1 -1
- package/js/select.js +20 -24
- package/js/sidebar-nav.d.ts +2 -1
- package/js/sidebar-nav.js +15 -10
- package/js/stepper.d.ts +1 -1
- package/js/stepper.js +9 -11
- package/js/table.d.ts +1 -1
- package/js/table.js +24 -23
- package/js/tabs.d.ts +1 -1
- package/js/tabs.js +10 -17
- package/js/theme.d.ts +1 -0
- package/js/theme.js +6 -15
- package/js/timepicker.d.ts +8 -6
- package/js/timepicker.js +36 -36
- package/js/toast.d.ts +4 -1
- package/js/toast.js +13 -5
- package/js/tooltip.d.ts +1 -4
- package/js/tooltip.js +14 -23
- package/js/tree.d.ts +0 -1
- package/js/tree.js +6 -11
- package/js/utils.js +7 -8
- package/js/virtual-dropdown.d.ts +1 -1
- package/js/virtual-dropdown.js +26 -28
- package/package.json +2 -1
- package/svg-icons/chevron_right.svg +1 -0
- package/svg-icons/keyboard_arrow_down.svg +1 -0
- package/css/calendar.css +0 -928
- package/css/guitar-chords.css +0 -251
- package/fonts/MaterialSymbolsOutlined.woff2 +0 -0
- package/js/form-builder.js +0 -107
- package/js/guitar-chords.js +0 -268
- package/js/lazy-loader.js +0 -121
- package/js/request.js +0 -51
package/js/file-uploader.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
|
+
import { ListenerGroup } from './listeners.js';
|
|
2
3
|
class FileUploader {
|
|
3
4
|
container;
|
|
4
5
|
dropZone;
|
|
@@ -9,11 +10,10 @@ class FileUploader {
|
|
|
9
10
|
uploadUrl;
|
|
10
11
|
maxFileSize;
|
|
11
12
|
allowedTypes;
|
|
12
|
-
|
|
13
|
+
xhrAborts = new Map();
|
|
14
|
+
listeners = new ListenerGroup();
|
|
13
15
|
constructor(elementOrSelector, config = {}) {
|
|
14
|
-
const container = typeof elementOrSelector === 'string'
|
|
15
|
-
? document.querySelector(elementOrSelector)
|
|
16
|
-
: elementOrSelector;
|
|
16
|
+
const container = typeof elementOrSelector === 'string' ? document.querySelector(elementOrSelector) : elementOrSelector;
|
|
17
17
|
if (!container) {
|
|
18
18
|
throw new Error(`FileUploader: Element not found for selector "${elementOrSelector}"`);
|
|
19
19
|
}
|
|
@@ -33,72 +33,57 @@ class FileUploader {
|
|
|
33
33
|
this.uploadUrl = config.uploadUrl ?? 'https://httpbin.org/post';
|
|
34
34
|
this.maxFileSize = config.maxFileSize;
|
|
35
35
|
this.allowedTypes = config.allowedTypes;
|
|
36
|
-
this.init();
|
|
37
|
-
}
|
|
38
|
-
init() {
|
|
39
36
|
this.setupEventListeners();
|
|
40
37
|
}
|
|
41
38
|
fileKey(file) {
|
|
42
39
|
return `${file.name}-${file.size}-${file.lastModified}`;
|
|
43
40
|
}
|
|
44
41
|
setupEventListeners() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.dropZone.addEventListener(event, this.handleDragLeave);
|
|
53
|
-
});
|
|
54
|
-
this.dropZone.addEventListener('drop', this.handleDrop);
|
|
55
|
-
this.dropZone.addEventListener('click', this.handleDropZoneClick);
|
|
56
|
-
this.fileInput.addEventListener('change', this.handleFileInputChange);
|
|
57
|
-
this.uploadBtn.addEventListener('click', this.handleUploadClick);
|
|
58
|
-
}
|
|
59
|
-
preventDefaults = (e) => {
|
|
60
|
-
e.preventDefault();
|
|
61
|
-
e.stopPropagation();
|
|
62
|
-
};
|
|
63
|
-
handleDragEnter = () => {
|
|
64
|
-
this.dropZone.classList.add('drag-over');
|
|
65
|
-
};
|
|
66
|
-
handleDragLeave = () => {
|
|
67
|
-
this.dropZone.classList.remove('drag-over');
|
|
68
|
-
};
|
|
69
|
-
handleDrop = (e) => {
|
|
70
|
-
const droppedFiles = e.dataTransfer?.files;
|
|
71
|
-
if (droppedFiles) {
|
|
72
|
-
this.handleFiles(droppedFiles);
|
|
42
|
+
const sig = { signal: this.listeners.signal };
|
|
43
|
+
const prevent = (e) => {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
};
|
|
47
|
+
for (const event of ['dragenter', 'dragover', 'dragleave', 'drop']) {
|
|
48
|
+
this.dropZone.addEventListener(event, prevent, sig);
|
|
73
49
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
this.fileInput.click();
|
|
77
|
-
};
|
|
78
|
-
handleFileInputChange = (e) => {
|
|
79
|
-
const target = e.target;
|
|
80
|
-
if (target.files) {
|
|
81
|
-
this.handleFiles(target.files);
|
|
82
|
-
target.value = '';
|
|
50
|
+
for (const event of ['dragenter', 'dragover']) {
|
|
51
|
+
this.dropZone.addEventListener(event, () => this.dropZone.classList.add('drag-over'), sig);
|
|
83
52
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
53
|
+
for (const event of ['dragleave', 'drop']) {
|
|
54
|
+
this.dropZone.addEventListener(event, () => this.dropZone.classList.remove('drag-over'), sig);
|
|
55
|
+
}
|
|
56
|
+
this.dropZone.addEventListener('drop', (e) => {
|
|
57
|
+
const droppedFiles = e.dataTransfer?.files;
|
|
58
|
+
if (droppedFiles)
|
|
59
|
+
this.handleFiles(droppedFiles);
|
|
60
|
+
}, sig);
|
|
61
|
+
this.dropZone.addEventListener('click', () => this.fileInput.click(), sig);
|
|
62
|
+
this.fileInput.addEventListener('change', (e) => {
|
|
63
|
+
const target = e.target;
|
|
64
|
+
if (target.files) {
|
|
65
|
+
this.handleFiles(target.files);
|
|
66
|
+
target.value = '';
|
|
67
|
+
}
|
|
68
|
+
}, sig);
|
|
69
|
+
this.uploadBtn.addEventListener('click', async () => {
|
|
70
|
+
if (this.files.size === 0)
|
|
71
|
+
return;
|
|
72
|
+
this.uploadBtn.disabled = true;
|
|
73
|
+
this.uploadBtn.textContent = 'Uploading...';
|
|
74
|
+
const uploadPromises = Array.from(this.files.values()).map(({ file, element }) => this.uploadFile(file, element));
|
|
75
|
+
const results = await Promise.allSettled(uploadPromises);
|
|
76
|
+
this.uploadBtn.textContent = 'Upload Complete';
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
this.dispatchUploadCompletedEvent(results);
|
|
79
|
+
this.fileList.innerHTML = '';
|
|
80
|
+
this.files.clear();
|
|
81
|
+
this.updateUploadButton();
|
|
82
|
+
}, 1500);
|
|
83
|
+
}, sig);
|
|
84
|
+
}
|
|
100
85
|
handleFiles(fileList) {
|
|
101
|
-
Array.from(fileList).forEach(file => {
|
|
86
|
+
Array.from(fileList).forEach((file) => {
|
|
102
87
|
const key = this.fileKey(file);
|
|
103
88
|
if (this.validateFile(file) && !this.files.has(key)) {
|
|
104
89
|
const element = this.addFileToUI(file);
|
|
@@ -181,16 +166,16 @@ class FileUploader {
|
|
|
181
166
|
removeBtn.style.display = 'none';
|
|
182
167
|
const xhr = new XMLHttpRequest();
|
|
183
168
|
const key = this.fileKey(file);
|
|
184
|
-
this.
|
|
169
|
+
this.xhrAborts.set(key, () => xhr.abort());
|
|
185
170
|
xhr.upload.addEventListener('progress', (e) => {
|
|
186
171
|
if (e.lengthComputable) {
|
|
187
172
|
const pct = Math.round((e.loaded / e.total) * 100);
|
|
188
|
-
progressBar.style.width = pct
|
|
189
|
-
statusText.textContent = pct
|
|
173
|
+
progressBar.style.width = `${pct}%`;
|
|
174
|
+
statusText.textContent = `${pct}%`;
|
|
190
175
|
}
|
|
191
176
|
});
|
|
192
177
|
xhr.addEventListener('load', () => {
|
|
193
|
-
this.
|
|
178
|
+
this.xhrAborts.delete(key);
|
|
194
179
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
195
180
|
progressBar.style.width = '100%';
|
|
196
181
|
progressBar.style.backgroundColor = 'var(--success)';
|
|
@@ -212,7 +197,7 @@ class FileUploader {
|
|
|
212
197
|
}
|
|
213
198
|
});
|
|
214
199
|
xhr.addEventListener('error', () => {
|
|
215
|
-
this.
|
|
200
|
+
this.xhrAborts.delete(key);
|
|
216
201
|
progressBar.style.backgroundColor = 'var(--error)';
|
|
217
202
|
statusText.textContent = 'Network Error';
|
|
218
203
|
statusText.classList.add('error');
|
|
@@ -220,7 +205,7 @@ class FileUploader {
|
|
|
220
205
|
reject(new Error('Network error'));
|
|
221
206
|
});
|
|
222
207
|
xhr.addEventListener('abort', () => {
|
|
223
|
-
this.
|
|
208
|
+
this.xhrAborts.delete(key);
|
|
224
209
|
statusText.textContent = 'Cancelled';
|
|
225
210
|
statusText.classList.add('error');
|
|
226
211
|
removeBtn.style.display = 'flex';
|
|
@@ -233,7 +218,7 @@ class FileUploader {
|
|
|
233
218
|
});
|
|
234
219
|
}
|
|
235
220
|
removeFile(key) {
|
|
236
|
-
const abort = this.
|
|
221
|
+
const abort = this.xhrAborts.get(key);
|
|
237
222
|
if (abort)
|
|
238
223
|
abort();
|
|
239
224
|
const fileData = this.files.get(key);
|
|
@@ -245,9 +230,7 @@ class FileUploader {
|
|
|
245
230
|
}
|
|
246
231
|
updateUploadButton() {
|
|
247
232
|
this.uploadBtn.disabled = this.files.size === 0;
|
|
248
|
-
this.uploadBtn.textContent = this.files.size > 0
|
|
249
|
-
? `Upload ${this.files.size} File${this.files.size === 1 ? '' : 's'}`
|
|
250
|
-
: 'Upload Files';
|
|
233
|
+
this.uploadBtn.textContent = this.files.size > 0 ? `Upload ${this.files.size} File${this.files.size === 1 ? '' : 's'}` : 'Upload Files';
|
|
251
234
|
}
|
|
252
235
|
dispatchUploadCompletedEvent(results) {
|
|
253
236
|
const files = Array.from(this.files.values()).map(({ file }) => file);
|
|
@@ -265,21 +248,9 @@ class FileUploader {
|
|
|
265
248
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
266
249
|
}
|
|
267
250
|
destroy() {
|
|
268
|
-
this.
|
|
269
|
-
this.
|
|
270
|
-
|
|
271
|
-
this.dropZone.removeEventListener(event, this.preventDefaults);
|
|
272
|
-
});
|
|
273
|
-
['dragenter', 'dragover'].forEach(event => {
|
|
274
|
-
this.dropZone.removeEventListener(event, this.handleDragEnter);
|
|
275
|
-
});
|
|
276
|
-
['dragleave', 'drop'].forEach(event => {
|
|
277
|
-
this.dropZone.removeEventListener(event, this.handleDragLeave);
|
|
278
|
-
});
|
|
279
|
-
this.dropZone.removeEventListener('drop', this.handleDrop);
|
|
280
|
-
this.dropZone.removeEventListener('click', this.handleDropZoneClick);
|
|
281
|
-
this.fileInput.removeEventListener('change', this.handleFileInputChange);
|
|
282
|
-
this.uploadBtn.removeEventListener('click', this.handleUploadClick);
|
|
251
|
+
this.xhrAborts.forEach((abort) => abort());
|
|
252
|
+
this.xhrAborts.clear();
|
|
253
|
+
this.listeners.destroy();
|
|
283
254
|
this.files.clear();
|
|
284
255
|
this.fileList.innerHTML = '';
|
|
285
256
|
}
|
package/js/flyout-menu.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ declare class FlyoutMenu {
|
|
|
19
19
|
private closeBtn;
|
|
20
20
|
private submenuToggles;
|
|
21
21
|
private menuLinks;
|
|
22
|
-
private
|
|
22
|
+
private listeners;
|
|
23
23
|
constructor(options?: FlyoutMenuOptions);
|
|
24
24
|
private init;
|
|
25
25
|
private hydrateMenu;
|
|
@@ -27,8 +27,8 @@ declare class FlyoutMenu {
|
|
|
27
27
|
private renderHeader;
|
|
28
28
|
private renderFooter;
|
|
29
29
|
private bindEvents;
|
|
30
|
-
open
|
|
31
|
-
close
|
|
30
|
+
open(): void;
|
|
31
|
+
close(): void;
|
|
32
32
|
private handleSubmenu;
|
|
33
33
|
private handleKeydown;
|
|
34
34
|
setDirection(direction: 'left' | 'right'): void;
|
package/js/flyout-menu.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ListenerGroup } from './listeners.js';
|
|
1
2
|
class FlyoutMenu {
|
|
2
3
|
options;
|
|
3
4
|
menuTrigger;
|
|
@@ -6,7 +7,7 @@ class FlyoutMenu {
|
|
|
6
7
|
closeBtn = null;
|
|
7
8
|
submenuToggles = null;
|
|
8
9
|
menuLinks = null;
|
|
9
|
-
|
|
10
|
+
listeners = new ListenerGroup();
|
|
10
11
|
constructor(options = {}) {
|
|
11
12
|
this.options = {
|
|
12
13
|
triggerSelector: '.menu-trigger',
|
|
@@ -20,7 +21,7 @@ class FlyoutMenu {
|
|
|
20
21
|
footerText: '© 2025 Brand Inc.',
|
|
21
22
|
enableHeader: true,
|
|
22
23
|
enableFooter: true,
|
|
23
|
-
...options
|
|
24
|
+
...options,
|
|
24
25
|
};
|
|
25
26
|
this.menuTrigger = document.querySelector(this.options.triggerSelector);
|
|
26
27
|
this.flyoutMenu = document.querySelector(this.options.menuSelector);
|
|
@@ -58,7 +59,7 @@ class FlyoutMenu {
|
|
|
58
59
|
if (nestedUl) {
|
|
59
60
|
li.classList.add('has-submenu');
|
|
60
61
|
nestedUl.classList.add('submenu');
|
|
61
|
-
const textNode = Array.from(li.childNodes).find(node => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() !== '');
|
|
62
|
+
const textNode = Array.from(li.childNodes).find((node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() !== '');
|
|
62
63
|
const text = textNode?.textContent?.trim() || 'Menu Item';
|
|
63
64
|
textNode?.remove();
|
|
64
65
|
const button = document.createElement('button');
|
|
@@ -108,30 +109,30 @@ class FlyoutMenu {
|
|
|
108
109
|
this.flyoutMenu.append(footer);
|
|
109
110
|
}
|
|
110
111
|
bindEvents() {
|
|
111
|
-
const sig = { signal: this.
|
|
112
|
-
this.menuTrigger?.addEventListener('click', this.open, sig);
|
|
113
|
-
this.closeBtn?.addEventListener('click', this.close, sig);
|
|
114
|
-
this.flyoutOverlay?.addEventListener('click', this.close, sig);
|
|
115
|
-
this.submenuToggles?.forEach(toggle => {
|
|
112
|
+
const sig = { signal: this.listeners.signal };
|
|
113
|
+
this.menuTrigger?.addEventListener('click', () => this.open(), sig);
|
|
114
|
+
this.closeBtn?.addEventListener('click', () => this.close(), sig);
|
|
115
|
+
this.flyoutOverlay?.addEventListener('click', () => this.close(), sig);
|
|
116
|
+
this.submenuToggles?.forEach((toggle) => {
|
|
116
117
|
toggle.addEventListener('click', (e) => this.handleSubmenu(e, toggle), sig);
|
|
117
118
|
});
|
|
118
|
-
this.menuLinks?.forEach(link => {
|
|
119
|
-
link.addEventListener('click', this.close, sig);
|
|
119
|
+
this.menuLinks?.forEach((link) => {
|
|
120
|
+
link.addEventListener('click', () => this.close(), sig);
|
|
120
121
|
});
|
|
121
|
-
document.addEventListener('keydown', this.handleKeydown, sig);
|
|
122
|
+
document.addEventListener('keydown', (e) => this.handleKeydown(e), sig);
|
|
122
123
|
}
|
|
123
|
-
open
|
|
124
|
+
open() {
|
|
124
125
|
this.flyoutMenu?.classList.add('is-open');
|
|
125
126
|
this.flyoutOverlay?.classList.add('is-visible');
|
|
126
127
|
document.body.style.overflow = 'hidden';
|
|
127
128
|
this.menuTrigger?.setAttribute('aria-expanded', 'true');
|
|
128
|
-
}
|
|
129
|
-
close
|
|
129
|
+
}
|
|
130
|
+
close() {
|
|
130
131
|
this.flyoutMenu?.classList.remove('is-open');
|
|
131
132
|
this.flyoutOverlay?.classList.remove('is-visible');
|
|
132
133
|
document.body.style.overflow = '';
|
|
133
134
|
this.menuTrigger?.setAttribute('aria-expanded', 'false');
|
|
134
|
-
}
|
|
135
|
+
}
|
|
135
136
|
handleSubmenu(e, toggle) {
|
|
136
137
|
e.preventDefault();
|
|
137
138
|
e.stopPropagation();
|
|
@@ -141,7 +142,7 @@ class FlyoutMenu {
|
|
|
141
142
|
if (!parentUl || !parentLi)
|
|
142
143
|
return;
|
|
143
144
|
const siblings = Array.from(parentUl.children);
|
|
144
|
-
siblings.forEach(sibling => {
|
|
145
|
+
siblings.forEach((sibling) => {
|
|
145
146
|
if (sibling !== parentLi) {
|
|
146
147
|
const siblingSubmenu = sibling.querySelector('.submenu');
|
|
147
148
|
const siblingToggle = sibling.querySelector('.submenu-toggle');
|
|
@@ -154,23 +155,20 @@ class FlyoutMenu {
|
|
|
154
155
|
toggle.classList.toggle('active');
|
|
155
156
|
submenu?.classList.toggle('is-open');
|
|
156
157
|
}
|
|
157
|
-
handleKeydown
|
|
158
|
+
handleKeydown(e) {
|
|
158
159
|
if (e.key === 'Escape' && this.flyoutMenu?.classList.contains('is-open')) {
|
|
159
160
|
this.close();
|
|
160
161
|
}
|
|
161
|
-
}
|
|
162
|
+
}
|
|
162
163
|
setDirection(direction) {
|
|
163
164
|
if (!this.flyoutMenu)
|
|
164
165
|
return;
|
|
165
|
-
const validDirections = ['left', 'right'];
|
|
166
|
-
if (!validDirections.includes(direction))
|
|
167
|
-
return;
|
|
168
166
|
this.flyoutMenu.classList.remove('flyout-from-right', 'flyout-from-left');
|
|
169
167
|
this.flyoutMenu.classList.add(`flyout-from-${direction}`);
|
|
170
168
|
this.options.direction = direction;
|
|
171
169
|
}
|
|
172
170
|
destroy() {
|
|
173
|
-
this.
|
|
171
|
+
this.listeners.destroy();
|
|
174
172
|
document.body.style.overflow = '';
|
|
175
173
|
}
|
|
176
174
|
}
|
package/js/gallery.d.ts
CHANGED
|
@@ -18,11 +18,14 @@ declare class MasonryGallery {
|
|
|
18
18
|
private columns;
|
|
19
19
|
private allImages;
|
|
20
20
|
private isFetching;
|
|
21
|
-
private
|
|
22
|
-
private abortController;
|
|
21
|
+
private listeners;
|
|
23
22
|
private reloaded;
|
|
23
|
+
private showingSkeleton;
|
|
24
|
+
private static readonly SKELETON_HEIGHTS;
|
|
24
25
|
constructor(containerId: string, options: MasonryGalleryOptions);
|
|
25
26
|
private init;
|
|
27
|
+
private renderSkeleton;
|
|
28
|
+
private createSkeletonCard;
|
|
26
29
|
private setupLayout;
|
|
27
30
|
private buildColumns;
|
|
28
31
|
private addEventListeners;
|
|
@@ -30,6 +33,7 @@ declare class MasonryGallery {
|
|
|
30
33
|
private handleScroll;
|
|
31
34
|
private loadMoreImages;
|
|
32
35
|
private toggleLoader;
|
|
36
|
+
private clearSkeleton;
|
|
33
37
|
private renderImages;
|
|
34
38
|
private createCard;
|
|
35
39
|
private addToShortestColumn;
|
package/js/gallery.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
2
|
import { Lightbox } from './lightbox.js';
|
|
3
|
+
import { ListenerGroup } from './listeners.js';
|
|
3
4
|
class MasonryGallery {
|
|
4
5
|
container;
|
|
5
6
|
loader;
|
|
@@ -7,9 +8,10 @@ class MasonryGallery {
|
|
|
7
8
|
columns = [];
|
|
8
9
|
allImages = [];
|
|
9
10
|
isFetching = false;
|
|
10
|
-
|
|
11
|
-
abortController = null;
|
|
11
|
+
listeners = new ListenerGroup();
|
|
12
12
|
reloaded = 0;
|
|
13
|
+
showingSkeleton = false;
|
|
14
|
+
static SKELETON_HEIGHTS = [180, 260, 220, 300, 240, 190];
|
|
13
15
|
constructor(containerId, options) {
|
|
14
16
|
const container = document.getElementById(containerId);
|
|
15
17
|
if (!container) {
|
|
@@ -28,9 +30,32 @@ class MasonryGallery {
|
|
|
28
30
|
}
|
|
29
31
|
init() {
|
|
30
32
|
this.setupLayout();
|
|
33
|
+
this.renderSkeleton();
|
|
31
34
|
this.loadMoreImages();
|
|
32
35
|
this.addEventListeners();
|
|
33
36
|
}
|
|
37
|
+
renderSkeleton() {
|
|
38
|
+
this.showingSkeleton = true;
|
|
39
|
+
this.columns.forEach((col, colIndex) => {
|
|
40
|
+
for (let i = 0; i < 2; i++) {
|
|
41
|
+
const height = MasonryGallery.SKELETON_HEIGHTS[(colIndex + i) % MasonryGallery.SKELETON_HEIGHTS.length];
|
|
42
|
+
col.appendChild(this.createSkeletonCard(height));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
createSkeletonCard(height) {
|
|
47
|
+
const div = document.createElement('div');
|
|
48
|
+
div.className = 'masonry-item masonry-item--skeleton';
|
|
49
|
+
div.setAttribute('aria-hidden', 'true');
|
|
50
|
+
div.innerHTML = `
|
|
51
|
+
<div class="masonry-item-skeleton-img" style="height:${height}px"></div>
|
|
52
|
+
<div class="masonry-item-info">
|
|
53
|
+
<div class="placeholder w-8" style="display: block;"></div>
|
|
54
|
+
<div class="placeholder w-5" style="display: block; margin-top: 6px;"></div>
|
|
55
|
+
</div>
|
|
56
|
+
`;
|
|
57
|
+
return div;
|
|
58
|
+
}
|
|
34
59
|
setupLayout() {
|
|
35
60
|
const containerWidth = this.container.getBoundingClientRect().width;
|
|
36
61
|
const numColumns = Math.max(1, Math.floor(containerWidth / this.options.minColumnWidth));
|
|
@@ -50,35 +75,36 @@ class MasonryGallery {
|
|
|
50
75
|
}
|
|
51
76
|
}
|
|
52
77
|
addEventListeners() {
|
|
53
|
-
|
|
54
|
-
const sig = this.abortController.signal;
|
|
78
|
+
const sig = { signal: this.listeners.signal };
|
|
55
79
|
let resizeTimeout;
|
|
56
80
|
window.addEventListener('resize', () => {
|
|
57
81
|
clearTimeout(resizeTimeout);
|
|
58
82
|
resizeTimeout = setTimeout(() => this.reLayout(), 200);
|
|
59
|
-
},
|
|
60
|
-
window.addEventListener('scroll', this.handleScroll, { passive: true
|
|
83
|
+
}, sig);
|
|
84
|
+
window.addEventListener('scroll', () => this.handleScroll(), { ...sig, passive: true });
|
|
61
85
|
}
|
|
62
86
|
reLayout() {
|
|
63
|
-
const items = this.columns.flatMap(col => Array.from(col.children));
|
|
87
|
+
const items = this.columns.flatMap((col) => Array.from(col.children));
|
|
64
88
|
const availableWidth = Math.min(1200, window.innerWidth - 40);
|
|
65
89
|
const numColumns = Math.max(1, Math.floor(availableWidth / this.options.minColumnWidth));
|
|
66
90
|
if (this.columns.length !== numColumns) {
|
|
67
91
|
this.buildColumns(numColumns);
|
|
68
92
|
}
|
|
69
93
|
else {
|
|
70
|
-
this.columns.forEach(col => {
|
|
94
|
+
this.columns.forEach((col) => {
|
|
95
|
+
col.innerHTML = '';
|
|
96
|
+
});
|
|
71
97
|
}
|
|
72
|
-
items.forEach(item => this.addToShortestColumn(item));
|
|
98
|
+
items.forEach((item) => this.addToShortestColumn(item));
|
|
73
99
|
}
|
|
74
|
-
handleScroll
|
|
100
|
+
handleScroll() {
|
|
75
101
|
if (this.isFetching)
|
|
76
102
|
return;
|
|
77
103
|
const rect = this.container.getBoundingClientRect();
|
|
78
104
|
if (rect.bottom > 0 && rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
|
|
79
105
|
this.loadMoreImages();
|
|
80
106
|
}
|
|
81
|
-
}
|
|
107
|
+
}
|
|
82
108
|
async loadMoreImages(isAutoFill = false) {
|
|
83
109
|
if (!isAutoFill)
|
|
84
110
|
this.reloaded++;
|
|
@@ -96,6 +122,7 @@ class MasonryGallery {
|
|
|
96
122
|
}
|
|
97
123
|
catch (error) {
|
|
98
124
|
console.error('MasonryGallery: error loading images', error);
|
|
125
|
+
this.clearSkeleton();
|
|
99
126
|
}
|
|
100
127
|
finally {
|
|
101
128
|
this.isFetching = false;
|
|
@@ -113,7 +140,16 @@ class MasonryGallery {
|
|
|
113
140
|
this.loader.classList.toggle('hidden', !show);
|
|
114
141
|
}
|
|
115
142
|
}
|
|
143
|
+
clearSkeleton() {
|
|
144
|
+
if (!this.showingSkeleton)
|
|
145
|
+
return;
|
|
146
|
+
this.showingSkeleton = false;
|
|
147
|
+
this.columns.forEach((col) => {
|
|
148
|
+
col.innerHTML = '';
|
|
149
|
+
});
|
|
150
|
+
}
|
|
116
151
|
renderImages(imageDataList) {
|
|
152
|
+
this.clearSkeleton();
|
|
117
153
|
const startIndex = this.allImages.length;
|
|
118
154
|
this.allImages.push(...imageDataList);
|
|
119
155
|
const sorted = [...this.columns].sort((a, b) => a.offsetHeight - b.offsetHeight);
|
|
@@ -123,7 +159,7 @@ class MasonryGallery {
|
|
|
123
159
|
const index = startIndex + i;
|
|
124
160
|
item.addEventListener('click', () => {
|
|
125
161
|
new Lightbox({
|
|
126
|
-
images: this.allImages.map(img => ({
|
|
162
|
+
images: this.allImages.map((img) => ({
|
|
127
163
|
src: img.src,
|
|
128
164
|
alt: img.title,
|
|
129
165
|
caption: img.desc,
|
|
@@ -174,14 +210,7 @@ class MasonryGallery {
|
|
|
174
210
|
shortestCol.appendChild(element);
|
|
175
211
|
}
|
|
176
212
|
destroy() {
|
|
177
|
-
|
|
178
|
-
this.resizeObserver.disconnect();
|
|
179
|
-
this.resizeObserver = null;
|
|
180
|
-
}
|
|
181
|
-
if (this.abortController) {
|
|
182
|
-
this.abortController.abort();
|
|
183
|
-
this.abortController = null;
|
|
184
|
-
}
|
|
213
|
+
this.listeners.destroy();
|
|
185
214
|
this.allImages = [];
|
|
186
215
|
}
|
|
187
216
|
}
|
package/js/group-picker.d.ts
CHANGED
|
@@ -21,12 +21,13 @@ interface GroupPickerOptions {
|
|
|
21
21
|
deselectLabel?: string;
|
|
22
22
|
emptyLabel?: string;
|
|
23
23
|
selectionPlaceholder?: string;
|
|
24
|
+
iconBasePath?: string;
|
|
24
25
|
}
|
|
25
26
|
declare class GroupPicker {
|
|
26
27
|
private container;
|
|
27
28
|
private data;
|
|
28
29
|
private options;
|
|
29
|
-
private
|
|
30
|
+
private listeners;
|
|
30
31
|
private selectedParents;
|
|
31
32
|
private selectedSubs;
|
|
32
33
|
private expandedGroups;
|