@dodlhuat/basix 1.4.1 → 1.4.3
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 +38 -1
- package/css/calendar.scss +4 -3
- package/css/code-viewer.scss +1 -1
- package/css/datepicker.scss +83 -70
- package/css/editor.scss +2 -1
- package/css/flyout-menu.scss +2 -1
- package/css/form.scss +11 -32
- package/css/mixins.scss +25 -0
- package/css/range-slider.scss +63 -6
- package/css/stepper.scss +2 -1
- package/css/style.css +124 -71
- package/css/style.css.map +1 -1
- package/css/style.min.css +1 -1
- package/css/style.min.css.map +1 -1
- package/css/timepicker.scss +1 -1
- package/css/virtual-dropdown.scss +6 -16
- package/js/bottom-sheet.d.ts +3 -5
- package/js/bottom-sheet.js +39 -58
- package/js/calendar.d.ts +1 -2
- package/js/calendar.js +77 -84
- package/js/carousel.d.ts +1 -1
- package/js/carousel.js +22 -14
- 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/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 +1 -2
- package/js/gallery.js +14 -20
- package/js/group-picker.d.ts +1 -1
- package/js/group-picker.js +22 -29
- package/js/lightbox.d.ts +2 -2
- package/js/lightbox.js +28 -27
- package/js/listeners.d.ts +7 -0
- package/js/listeners.js +14 -0
- package/js/modal.d.ts +2 -1
- package/js/modal.js +13 -20
- 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 +2 -0
- package/js/push-menu.js +21 -15
- package/js/range-slider.d.ts +15 -1
- package/js/range-slider.js +67 -7
- 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 +19 -23
- package/js/sidebar-nav.d.ts +1 -1
- package/js/sidebar-nav.js +13 -9
- 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 +2 -1
- package/js/toast.js +8 -4
- 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 +1 -1
- package/css/calendar.css +0 -928
- package/css/guitar-chords.css +0 -251
- 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,8 +18,7 @@ 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;
|
|
24
23
|
constructor(containerId: string, options: MasonryGalleryOptions);
|
|
25
24
|
private init;
|
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,8 +8,7 @@ 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
13
|
constructor(containerId, options) {
|
|
14
14
|
const container = document.getElementById(containerId);
|
|
@@ -50,35 +50,36 @@ class MasonryGallery {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
addEventListeners() {
|
|
53
|
-
|
|
54
|
-
const sig = this.abortController.signal;
|
|
53
|
+
const sig = { signal: this.listeners.signal };
|
|
55
54
|
let resizeTimeout;
|
|
56
55
|
window.addEventListener('resize', () => {
|
|
57
56
|
clearTimeout(resizeTimeout);
|
|
58
57
|
resizeTimeout = setTimeout(() => this.reLayout(), 200);
|
|
59
|
-
},
|
|
60
|
-
window.addEventListener('scroll', this.handleScroll, { passive: true
|
|
58
|
+
}, sig);
|
|
59
|
+
window.addEventListener('scroll', () => this.handleScroll(), { ...sig, passive: true });
|
|
61
60
|
}
|
|
62
61
|
reLayout() {
|
|
63
|
-
const items = this.columns.flatMap(col => Array.from(col.children));
|
|
62
|
+
const items = this.columns.flatMap((col) => Array.from(col.children));
|
|
64
63
|
const availableWidth = Math.min(1200, window.innerWidth - 40);
|
|
65
64
|
const numColumns = Math.max(1, Math.floor(availableWidth / this.options.minColumnWidth));
|
|
66
65
|
if (this.columns.length !== numColumns) {
|
|
67
66
|
this.buildColumns(numColumns);
|
|
68
67
|
}
|
|
69
68
|
else {
|
|
70
|
-
this.columns.forEach(col => {
|
|
69
|
+
this.columns.forEach((col) => {
|
|
70
|
+
col.innerHTML = '';
|
|
71
|
+
});
|
|
71
72
|
}
|
|
72
|
-
items.forEach(item => this.addToShortestColumn(item));
|
|
73
|
+
items.forEach((item) => this.addToShortestColumn(item));
|
|
73
74
|
}
|
|
74
|
-
handleScroll
|
|
75
|
+
handleScroll() {
|
|
75
76
|
if (this.isFetching)
|
|
76
77
|
return;
|
|
77
78
|
const rect = this.container.getBoundingClientRect();
|
|
78
79
|
if (rect.bottom > 0 && rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
|
|
79
80
|
this.loadMoreImages();
|
|
80
81
|
}
|
|
81
|
-
}
|
|
82
|
+
}
|
|
82
83
|
async loadMoreImages(isAutoFill = false) {
|
|
83
84
|
if (!isAutoFill)
|
|
84
85
|
this.reloaded++;
|
|
@@ -123,7 +124,7 @@ class MasonryGallery {
|
|
|
123
124
|
const index = startIndex + i;
|
|
124
125
|
item.addEventListener('click', () => {
|
|
125
126
|
new Lightbox({
|
|
126
|
-
images: this.allImages.map(img => ({
|
|
127
|
+
images: this.allImages.map((img) => ({
|
|
127
128
|
src: img.src,
|
|
128
129
|
alt: img.title,
|
|
129
130
|
caption: img.desc,
|
|
@@ -174,14 +175,7 @@ class MasonryGallery {
|
|
|
174
175
|
shortestCol.appendChild(element);
|
|
175
176
|
}
|
|
176
177
|
destroy() {
|
|
177
|
-
|
|
178
|
-
this.resizeObserver.disconnect();
|
|
179
|
-
this.resizeObserver = null;
|
|
180
|
-
}
|
|
181
|
-
if (this.abortController) {
|
|
182
|
-
this.abortController.abort();
|
|
183
|
-
this.abortController = null;
|
|
184
|
-
}
|
|
178
|
+
this.listeners.destroy();
|
|
185
179
|
this.allImages = [];
|
|
186
180
|
}
|
|
187
181
|
}
|
package/js/group-picker.d.ts
CHANGED
package/js/group-picker.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
|
+
import { ListenerGroup } from './listeners.js';
|
|
2
3
|
class GroupPicker {
|
|
3
4
|
container;
|
|
4
5
|
data;
|
|
5
6
|
options;
|
|
6
|
-
|
|
7
|
+
listeners = new ListenerGroup();
|
|
7
8
|
selectedParents = new Set();
|
|
8
9
|
selectedSubs = new Map();
|
|
9
10
|
expandedGroups = new Set();
|
|
@@ -12,14 +13,11 @@ class GroupPicker {
|
|
|
12
13
|
listEl;
|
|
13
14
|
selectionEl;
|
|
14
15
|
constructor(selector, data, options = {}) {
|
|
15
|
-
const el = typeof selector === 'string'
|
|
16
|
-
? document.querySelector(selector)
|
|
17
|
-
: selector;
|
|
16
|
+
const el = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
|
18
17
|
if (!el)
|
|
19
18
|
throw new Error(`GroupPicker: Element not found for "${selector}"`);
|
|
20
19
|
this.container = el;
|
|
21
20
|
this.data = data;
|
|
22
|
-
this.abortController = new AbortController();
|
|
23
21
|
this.options = {
|
|
24
22
|
onSelectionChange: options.onSelectionChange ?? (() => { }),
|
|
25
23
|
searchPlaceholder: options.searchPlaceholder ?? 'Gruppen durchsuchen...',
|
|
@@ -61,7 +59,7 @@ class GroupPicker {
|
|
|
61
59
|
for (const group of this.data) {
|
|
62
60
|
const subs = group.subgroups ?? [];
|
|
63
61
|
const groupMatches = group.label.toLowerCase().includes(query);
|
|
64
|
-
const matchingSubs = subs.filter(s => s.label.toLowerCase().includes(query));
|
|
62
|
+
const matchingSubs = subs.filter((s) => s.label.toLowerCase().includes(query));
|
|
65
63
|
if (!groupMatches && matchingSubs.length === 0 && query)
|
|
66
64
|
continue;
|
|
67
65
|
visibleCount++;
|
|
@@ -86,8 +84,7 @@ class GroupPicker {
|
|
|
86
84
|
el.dataset.groupId = group.id;
|
|
87
85
|
if (!hasChildren)
|
|
88
86
|
el.classList.add('is-leaf');
|
|
89
|
-
const isExpanded = hasChildren && (this.expandedGroups.has(group.id) ||
|
|
90
|
-
(query.length > 0 && matchingSubs.length > 0));
|
|
87
|
+
const isExpanded = hasChildren && (this.expandedGroups.has(group.id) || (query.length > 0 && matchingSubs.length > 0));
|
|
91
88
|
const isParentSelected = this.selectedParents.has(group.id);
|
|
92
89
|
if (isExpanded)
|
|
93
90
|
el.classList.add('is-expanded');
|
|
@@ -97,9 +94,7 @@ class GroupPicker {
|
|
|
97
94
|
header.className = 'group-picker__group-header';
|
|
98
95
|
const label = document.createElement('span');
|
|
99
96
|
label.className = 'group-picker__group-label';
|
|
100
|
-
label.innerHTML = query && groupMatches
|
|
101
|
-
? this.highlightText(group.label, query)
|
|
102
|
-
: escapeHtml(group.label);
|
|
97
|
+
label.innerHTML = query && groupMatches ? this.highlightText(group.label, query) : escapeHtml(group.label);
|
|
103
98
|
if (hasChildren) {
|
|
104
99
|
const chevron = document.createElement('span');
|
|
105
100
|
chevron.className = 'icon icon-navigate_next group-picker__chevron';
|
|
@@ -119,11 +114,11 @@ class GroupPicker {
|
|
|
119
114
|
actionBtn.addEventListener('click', (e) => {
|
|
120
115
|
e.stopPropagation();
|
|
121
116
|
this.toggleParentGroup(group.id);
|
|
122
|
-
}, { signal: this.
|
|
117
|
+
}, { signal: this.listeners.signal });
|
|
123
118
|
header.append(chevron, label, count, actionBtn);
|
|
124
119
|
header.addEventListener('click', () => {
|
|
125
120
|
this.toggleExpand(group.id);
|
|
126
|
-
}, { signal: this.
|
|
121
|
+
}, { signal: this.listeners.signal });
|
|
127
122
|
const subsContainer = document.createElement('div');
|
|
128
123
|
subsContainer.className = 'group-picker__subgroups';
|
|
129
124
|
const subsList = document.createElement('div');
|
|
@@ -144,14 +139,14 @@ class GroupPicker {
|
|
|
144
139
|
if (!isParentSelected) {
|
|
145
140
|
this.toggleSubgroup(group.id, sub.id);
|
|
146
141
|
}
|
|
147
|
-
}, { signal: this.
|
|
142
|
+
}, { signal: this.listeners.signal });
|
|
148
143
|
subsList.appendChild(subEl);
|
|
149
144
|
}
|
|
150
145
|
subsContainer.appendChild(subsList);
|
|
151
146
|
el.append(header, subsContainer);
|
|
152
147
|
if (isExpanded) {
|
|
153
148
|
requestAnimationFrame(() => {
|
|
154
|
-
subsContainer.style.height = subsContainer.scrollHeight
|
|
149
|
+
subsContainer.style.height = `${subsContainer.scrollHeight}px`;
|
|
155
150
|
subsContainer.addEventListener('transitionend', () => {
|
|
156
151
|
subsContainer.style.height = 'auto';
|
|
157
152
|
}, { once: true });
|
|
@@ -165,7 +160,7 @@ class GroupPicker {
|
|
|
165
160
|
header.append(label, checkEl);
|
|
166
161
|
header.addEventListener('click', () => {
|
|
167
162
|
this.toggleParentGroup(group.id);
|
|
168
|
-
}, { signal: this.
|
|
163
|
+
}, { signal: this.listeners.signal });
|
|
169
164
|
el.appendChild(header);
|
|
170
165
|
}
|
|
171
166
|
return el;
|
|
@@ -173,17 +168,17 @@ class GroupPicker {
|
|
|
173
168
|
renderSelection() {
|
|
174
169
|
this.selectionEl.innerHTML = '';
|
|
175
170
|
for (const groupId of this.selectedParents) {
|
|
176
|
-
const group = this.data.find(g => g.id === groupId);
|
|
171
|
+
const group = this.data.find((g) => g.id === groupId);
|
|
177
172
|
if (!group)
|
|
178
173
|
continue;
|
|
179
174
|
this.selectionEl.appendChild(this.createChip(group.label, true, () => this.toggleParentGroup(groupId)));
|
|
180
175
|
}
|
|
181
176
|
for (const [groupId, subs] of this.selectedSubs) {
|
|
182
|
-
const group = this.data.find(g => g.id === groupId);
|
|
177
|
+
const group = this.data.find((g) => g.id === groupId);
|
|
183
178
|
if (!group)
|
|
184
179
|
continue;
|
|
185
180
|
for (const subId of subs) {
|
|
186
|
-
const sub = group.subgroups?.find(s => s.id === subId);
|
|
181
|
+
const sub = group.subgroups?.find((s) => s.id === subId);
|
|
187
182
|
if (!sub)
|
|
188
183
|
continue;
|
|
189
184
|
this.selectionEl.appendChild(this.createChip(sub.label, false, () => this.toggleSubgroup(groupId, subId)));
|
|
@@ -192,16 +187,14 @@ class GroupPicker {
|
|
|
192
187
|
}
|
|
193
188
|
createChip(label, isParent, onRemove) {
|
|
194
189
|
const chip = document.createElement('span');
|
|
195
|
-
chip.className = isParent
|
|
196
|
-
? 'chip closeable group-picker__chip--parent'
|
|
197
|
-
: 'chip closeable';
|
|
190
|
+
chip.className = isParent ? 'chip closeable group-picker__chip--parent' : 'chip closeable';
|
|
198
191
|
const btn = document.createElement('button');
|
|
199
192
|
btn.setAttribute('aria-label', `${label} entfernen`);
|
|
200
193
|
btn.innerHTML = `<span class="icon icon-close"></span>`;
|
|
201
194
|
btn.addEventListener('click', (e) => {
|
|
202
195
|
e.stopPropagation();
|
|
203
196
|
onRemove();
|
|
204
|
-
}, { signal: this.
|
|
197
|
+
}, { signal: this.listeners.signal });
|
|
205
198
|
chip.append(document.createTextNode(label), btn);
|
|
206
199
|
return chip;
|
|
207
200
|
}
|
|
@@ -229,7 +222,7 @@ class GroupPicker {
|
|
|
229
222
|
else {
|
|
230
223
|
subs.add(subId);
|
|
231
224
|
}
|
|
232
|
-
const group = this.data.find(g => g.id === groupId);
|
|
225
|
+
const group = this.data.find((g) => g.id === groupId);
|
|
233
226
|
if (group && subs.size === (group.subgroups ?? []).length) {
|
|
234
227
|
this.selectedSubs.delete(groupId);
|
|
235
228
|
this.selectedParents.add(groupId);
|
|
@@ -244,7 +237,7 @@ class GroupPicker {
|
|
|
244
237
|
this.expandedGroups.delete(groupId);
|
|
245
238
|
groupEl?.classList.remove('is-expanded');
|
|
246
239
|
if (subsEl) {
|
|
247
|
-
subsEl.style.height = subsEl.scrollHeight
|
|
240
|
+
subsEl.style.height = `${subsEl.scrollHeight}px`;
|
|
248
241
|
requestAnimationFrame(() => {
|
|
249
242
|
subsEl.style.height = '0';
|
|
250
243
|
});
|
|
@@ -254,7 +247,7 @@ class GroupPicker {
|
|
|
254
247
|
this.expandedGroups.add(groupId);
|
|
255
248
|
groupEl?.classList.add('is-expanded');
|
|
256
249
|
if (subsEl) {
|
|
257
|
-
subsEl.style.height = subsEl.scrollHeight
|
|
250
|
+
subsEl.style.height = `${subsEl.scrollHeight}px`;
|
|
258
251
|
subsEl.addEventListener('transitionend', () => {
|
|
259
252
|
if (this.expandedGroups.has(groupId)) {
|
|
260
253
|
subsEl.style.height = 'auto';
|
|
@@ -275,7 +268,7 @@ class GroupPicker {
|
|
|
275
268
|
this.searchQuery = this.searchInput.value;
|
|
276
269
|
this.renderGroups();
|
|
277
270
|
}, 120);
|
|
278
|
-
}, { signal: this.
|
|
271
|
+
}, { signal: this.listeners.signal });
|
|
279
272
|
}
|
|
280
273
|
emitChange() {
|
|
281
274
|
const selection = this.getSelection();
|
|
@@ -322,7 +315,7 @@ class GroupPicker {
|
|
|
322
315
|
this.emitChange();
|
|
323
316
|
}
|
|
324
317
|
expandAll() {
|
|
325
|
-
this.data.forEach(g => this.expandedGroups.add(g.id));
|
|
318
|
+
this.data.forEach((g) => this.expandedGroups.add(g.id));
|
|
326
319
|
this.renderGroups();
|
|
327
320
|
}
|
|
328
321
|
collapseAll() {
|
|
@@ -330,7 +323,7 @@ class GroupPicker {
|
|
|
330
323
|
this.renderGroups();
|
|
331
324
|
}
|
|
332
325
|
destroy() {
|
|
333
|
-
this.
|
|
326
|
+
this.listeners.destroy();
|
|
334
327
|
this.container.innerHTML = '';
|
|
335
328
|
this.container.classList.remove('group-picker');
|
|
336
329
|
}
|
package/js/lightbox.d.ts
CHANGED
|
@@ -24,10 +24,10 @@ declare class Lightbox {
|
|
|
24
24
|
private captionEl;
|
|
25
25
|
private counterEl;
|
|
26
26
|
private isZoomed;
|
|
27
|
-
private
|
|
27
|
+
private listeners;
|
|
28
28
|
constructor(options: LightboxOptions);
|
|
29
29
|
show(): void;
|
|
30
|
-
hide
|
|
30
|
+
hide(): void;
|
|
31
31
|
next(): void;
|
|
32
32
|
prev(): void;
|
|
33
33
|
isVisible(): boolean;
|