@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/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...',
|
|
@@ -27,6 +25,7 @@ class GroupPicker {
|
|
|
27
25
|
deselectLabel: options.deselectLabel ?? 'Abwählen',
|
|
28
26
|
emptyLabel: options.emptyLabel ?? 'Keine Ergebnisse',
|
|
29
27
|
selectionPlaceholder: options.selectionPlaceholder ?? 'Noch keine Auswahl getroffen',
|
|
28
|
+
iconBasePath: options.iconBasePath ?? 'svg-icons/',
|
|
30
29
|
};
|
|
31
30
|
this.init();
|
|
32
31
|
}
|
|
@@ -43,7 +42,7 @@ class GroupPicker {
|
|
|
43
42
|
const searchWrap = document.createElement('div');
|
|
44
43
|
searchWrap.className = 'group-picker__search';
|
|
45
44
|
searchWrap.innerHTML = `
|
|
46
|
-
<
|
|
45
|
+
<svg class="icon-svg group-picker__search-icon" aria-hidden="true"><use href="${this.options.iconBasePath}icons.svg#search"/></svg>
|
|
47
46
|
<input type="text" />
|
|
48
47
|
`;
|
|
49
48
|
this.searchInput = searchWrap.querySelector('input');
|
|
@@ -61,7 +60,7 @@ class GroupPicker {
|
|
|
61
60
|
for (const group of this.data) {
|
|
62
61
|
const subs = group.subgroups ?? [];
|
|
63
62
|
const groupMatches = group.label.toLowerCase().includes(query);
|
|
64
|
-
const matchingSubs = subs.filter(s => s.label.toLowerCase().includes(query));
|
|
63
|
+
const matchingSubs = subs.filter((s) => s.label.toLowerCase().includes(query));
|
|
65
64
|
if (!groupMatches && matchingSubs.length === 0 && query)
|
|
66
65
|
continue;
|
|
67
66
|
visibleCount++;
|
|
@@ -72,7 +71,7 @@ class GroupPicker {
|
|
|
72
71
|
const empty = document.createElement('div');
|
|
73
72
|
empty.className = 'group-picker__empty';
|
|
74
73
|
empty.innerHTML = `
|
|
75
|
-
<
|
|
74
|
+
<svg class="icon-svg" aria-hidden="true"><use href="${this.options.iconBasePath}icons.svg#search"/></svg>
|
|
76
75
|
<span>${this.options.emptyLabel}</span>
|
|
77
76
|
`;
|
|
78
77
|
this.listEl.appendChild(empty);
|
|
@@ -86,8 +85,7 @@ class GroupPicker {
|
|
|
86
85
|
el.dataset.groupId = group.id;
|
|
87
86
|
if (!hasChildren)
|
|
88
87
|
el.classList.add('is-leaf');
|
|
89
|
-
const isExpanded = hasChildren && (this.expandedGroups.has(group.id) ||
|
|
90
|
-
(query.length > 0 && matchingSubs.length > 0));
|
|
88
|
+
const isExpanded = hasChildren && (this.expandedGroups.has(group.id) || (query.length > 0 && matchingSubs.length > 0));
|
|
91
89
|
const isParentSelected = this.selectedParents.has(group.id);
|
|
92
90
|
if (isExpanded)
|
|
93
91
|
el.classList.add('is-expanded');
|
|
@@ -97,13 +95,12 @@ class GroupPicker {
|
|
|
97
95
|
header.className = 'group-picker__group-header';
|
|
98
96
|
const label = document.createElement('span');
|
|
99
97
|
label.className = 'group-picker__group-label';
|
|
100
|
-
label.innerHTML = query && groupMatches
|
|
101
|
-
? this.highlightText(group.label, query)
|
|
102
|
-
: escapeHtml(group.label);
|
|
98
|
+
label.innerHTML = query && groupMatches ? this.highlightText(group.label, query) : escapeHtml(group.label);
|
|
103
99
|
if (hasChildren) {
|
|
104
100
|
const chevron = document.createElement('span');
|
|
105
|
-
chevron.className = '
|
|
101
|
+
chevron.className = 'group-picker__chevron';
|
|
106
102
|
chevron.setAttribute('aria-hidden', 'true');
|
|
103
|
+
chevron.innerHTML = `<svg class="icon-svg" aria-hidden="true"><use href="${this.options.iconBasePath}icons.svg#chevron_right"/></svg>`;
|
|
107
104
|
const count = document.createElement('span');
|
|
108
105
|
count.className = 'badge badge-sm';
|
|
109
106
|
count.textContent = `${subs.length}`;
|
|
@@ -119,11 +116,11 @@ class GroupPicker {
|
|
|
119
116
|
actionBtn.addEventListener('click', (e) => {
|
|
120
117
|
e.stopPropagation();
|
|
121
118
|
this.toggleParentGroup(group.id);
|
|
122
|
-
}, { signal: this.
|
|
119
|
+
}, { signal: this.listeners.signal });
|
|
123
120
|
header.append(chevron, label, count, actionBtn);
|
|
124
121
|
header.addEventListener('click', () => {
|
|
125
122
|
this.toggleExpand(group.id);
|
|
126
|
-
}, { signal: this.
|
|
123
|
+
}, { signal: this.listeners.signal });
|
|
127
124
|
const subsContainer = document.createElement('div');
|
|
128
125
|
subsContainer.className = 'group-picker__subgroups';
|
|
129
126
|
const subsList = document.createElement('div');
|
|
@@ -144,14 +141,14 @@ class GroupPicker {
|
|
|
144
141
|
if (!isParentSelected) {
|
|
145
142
|
this.toggleSubgroup(group.id, sub.id);
|
|
146
143
|
}
|
|
147
|
-
}, { signal: this.
|
|
144
|
+
}, { signal: this.listeners.signal });
|
|
148
145
|
subsList.appendChild(subEl);
|
|
149
146
|
}
|
|
150
147
|
subsContainer.appendChild(subsList);
|
|
151
148
|
el.append(header, subsContainer);
|
|
152
149
|
if (isExpanded) {
|
|
153
150
|
requestAnimationFrame(() => {
|
|
154
|
-
subsContainer.style.height = subsContainer.scrollHeight
|
|
151
|
+
subsContainer.style.height = `${subsContainer.scrollHeight}px`;
|
|
155
152
|
subsContainer.addEventListener('transitionend', () => {
|
|
156
153
|
subsContainer.style.height = 'auto';
|
|
157
154
|
}, { once: true });
|
|
@@ -160,12 +157,13 @@ class GroupPicker {
|
|
|
160
157
|
}
|
|
161
158
|
else {
|
|
162
159
|
const checkEl = document.createElement('span');
|
|
163
|
-
checkEl.className = '
|
|
160
|
+
checkEl.className = 'group-picker__leaf-check';
|
|
164
161
|
checkEl.setAttribute('aria-hidden', 'true');
|
|
162
|
+
checkEl.innerHTML = `<svg class="icon-svg" aria-hidden="true"><use href="${this.options.iconBasePath}icons.svg#check"/></svg>`;
|
|
165
163
|
header.append(label, checkEl);
|
|
166
164
|
header.addEventListener('click', () => {
|
|
167
165
|
this.toggleParentGroup(group.id);
|
|
168
|
-
}, { signal: this.
|
|
166
|
+
}, { signal: this.listeners.signal });
|
|
169
167
|
el.appendChild(header);
|
|
170
168
|
}
|
|
171
169
|
return el;
|
|
@@ -173,17 +171,17 @@ class GroupPicker {
|
|
|
173
171
|
renderSelection() {
|
|
174
172
|
this.selectionEl.innerHTML = '';
|
|
175
173
|
for (const groupId of this.selectedParents) {
|
|
176
|
-
const group = this.data.find(g => g.id === groupId);
|
|
174
|
+
const group = this.data.find((g) => g.id === groupId);
|
|
177
175
|
if (!group)
|
|
178
176
|
continue;
|
|
179
177
|
this.selectionEl.appendChild(this.createChip(group.label, true, () => this.toggleParentGroup(groupId)));
|
|
180
178
|
}
|
|
181
179
|
for (const [groupId, subs] of this.selectedSubs) {
|
|
182
|
-
const group = this.data.find(g => g.id === groupId);
|
|
180
|
+
const group = this.data.find((g) => g.id === groupId);
|
|
183
181
|
if (!group)
|
|
184
182
|
continue;
|
|
185
183
|
for (const subId of subs) {
|
|
186
|
-
const sub = group.subgroups?.find(s => s.id === subId);
|
|
184
|
+
const sub = group.subgroups?.find((s) => s.id === subId);
|
|
187
185
|
if (!sub)
|
|
188
186
|
continue;
|
|
189
187
|
this.selectionEl.appendChild(this.createChip(sub.label, false, () => this.toggleSubgroup(groupId, subId)));
|
|
@@ -192,16 +190,14 @@ class GroupPicker {
|
|
|
192
190
|
}
|
|
193
191
|
createChip(label, isParent, onRemove) {
|
|
194
192
|
const chip = document.createElement('span');
|
|
195
|
-
chip.className = isParent
|
|
196
|
-
? 'chip closeable group-picker__chip--parent'
|
|
197
|
-
: 'chip closeable';
|
|
193
|
+
chip.className = isParent ? 'chip closeable group-picker__chip--parent' : 'chip closeable';
|
|
198
194
|
const btn = document.createElement('button');
|
|
199
195
|
btn.setAttribute('aria-label', `${label} entfernen`);
|
|
200
|
-
btn.innerHTML = `<
|
|
196
|
+
btn.innerHTML = `<svg class="icon-svg" aria-hidden="true"><use href="${this.options.iconBasePath}icons.svg#close"/></svg>`;
|
|
201
197
|
btn.addEventListener('click', (e) => {
|
|
202
198
|
e.stopPropagation();
|
|
203
199
|
onRemove();
|
|
204
|
-
}, { signal: this.
|
|
200
|
+
}, { signal: this.listeners.signal });
|
|
205
201
|
chip.append(document.createTextNode(label), btn);
|
|
206
202
|
return chip;
|
|
207
203
|
}
|
|
@@ -229,7 +225,7 @@ class GroupPicker {
|
|
|
229
225
|
else {
|
|
230
226
|
subs.add(subId);
|
|
231
227
|
}
|
|
232
|
-
const group = this.data.find(g => g.id === groupId);
|
|
228
|
+
const group = this.data.find((g) => g.id === groupId);
|
|
233
229
|
if (group && subs.size === (group.subgroups ?? []).length) {
|
|
234
230
|
this.selectedSubs.delete(groupId);
|
|
235
231
|
this.selectedParents.add(groupId);
|
|
@@ -244,7 +240,7 @@ class GroupPicker {
|
|
|
244
240
|
this.expandedGroups.delete(groupId);
|
|
245
241
|
groupEl?.classList.remove('is-expanded');
|
|
246
242
|
if (subsEl) {
|
|
247
|
-
subsEl.style.height = subsEl.scrollHeight
|
|
243
|
+
subsEl.style.height = `${subsEl.scrollHeight}px`;
|
|
248
244
|
requestAnimationFrame(() => {
|
|
249
245
|
subsEl.style.height = '0';
|
|
250
246
|
});
|
|
@@ -254,7 +250,7 @@ class GroupPicker {
|
|
|
254
250
|
this.expandedGroups.add(groupId);
|
|
255
251
|
groupEl?.classList.add('is-expanded');
|
|
256
252
|
if (subsEl) {
|
|
257
|
-
subsEl.style.height = subsEl.scrollHeight
|
|
253
|
+
subsEl.style.height = `${subsEl.scrollHeight}px`;
|
|
258
254
|
subsEl.addEventListener('transitionend', () => {
|
|
259
255
|
if (this.expandedGroups.has(groupId)) {
|
|
260
256
|
subsEl.style.height = 'auto';
|
|
@@ -275,7 +271,7 @@ class GroupPicker {
|
|
|
275
271
|
this.searchQuery = this.searchInput.value;
|
|
276
272
|
this.renderGroups();
|
|
277
273
|
}, 120);
|
|
278
|
-
}, { signal: this.
|
|
274
|
+
}, { signal: this.listeners.signal });
|
|
279
275
|
}
|
|
280
276
|
emitChange() {
|
|
281
277
|
const selection = this.getSelection();
|
|
@@ -322,7 +318,7 @@ class GroupPicker {
|
|
|
322
318
|
this.emitChange();
|
|
323
319
|
}
|
|
324
320
|
expandAll() {
|
|
325
|
-
this.data.forEach(g => this.expandedGroups.add(g.id));
|
|
321
|
+
this.data.forEach((g) => this.expandedGroups.add(g.id));
|
|
326
322
|
this.renderGroups();
|
|
327
323
|
}
|
|
328
324
|
collapseAll() {
|
|
@@ -330,7 +326,7 @@ class GroupPicker {
|
|
|
330
326
|
this.renderGroups();
|
|
331
327
|
}
|
|
332
328
|
destroy() {
|
|
333
|
-
this.
|
|
329
|
+
this.listeners.destroy();
|
|
334
330
|
this.container.innerHTML = '';
|
|
335
331
|
this.container.classList.remove('group-picker');
|
|
336
332
|
}
|
package/js/lightbox.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ interface LightboxOptions {
|
|
|
12
12
|
startIndex?: number;
|
|
13
13
|
onOpen?: () => void;
|
|
14
14
|
onClose?: () => void;
|
|
15
|
+
iconBasePath?: string;
|
|
15
16
|
}
|
|
16
17
|
declare class Lightbox {
|
|
17
18
|
private images;
|
|
@@ -19,15 +20,16 @@ declare class Lightbox {
|
|
|
19
20
|
private readonly closeable;
|
|
20
21
|
private readonly onOpen?;
|
|
21
22
|
private readonly onClose?;
|
|
23
|
+
private readonly iconBasePath;
|
|
22
24
|
private wrapper;
|
|
23
25
|
private imgEl;
|
|
24
26
|
private captionEl;
|
|
25
27
|
private counterEl;
|
|
26
28
|
private isZoomed;
|
|
27
|
-
private
|
|
29
|
+
private listeners;
|
|
28
30
|
constructor(options: LightboxOptions);
|
|
29
31
|
show(): void;
|
|
30
|
-
hide
|
|
32
|
+
hide(): void;
|
|
31
33
|
next(): void;
|
|
32
34
|
prev(): void;
|
|
33
35
|
isVisible(): boolean;
|
|
@@ -41,6 +43,6 @@ declare class Lightbox {
|
|
|
41
43
|
private handleBackgroundClick;
|
|
42
44
|
private addTouchSupport;
|
|
43
45
|
private buildTemplate;
|
|
44
|
-
static bind(selector?: string): void;
|
|
46
|
+
static bind(selector?: string, iconBasePath?: string): void;
|
|
45
47
|
}
|
|
46
48
|
export { Lightbox, type LightboxOptions, type LightboxImage };
|
package/js/lightbox.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import { ListenerGroup } from './listeners.js';
|
|
1
2
|
class Lightbox {
|
|
2
3
|
images;
|
|
3
4
|
currentIndex;
|
|
4
5
|
closeable;
|
|
5
6
|
onOpen;
|
|
6
7
|
onClose;
|
|
8
|
+
iconBasePath;
|
|
7
9
|
wrapper = null;
|
|
8
10
|
imgEl = null;
|
|
9
11
|
captionEl = null;
|
|
10
12
|
counterEl = null;
|
|
11
13
|
isZoomed = false;
|
|
12
|
-
|
|
14
|
+
listeners = new ListenerGroup();
|
|
13
15
|
constructor(options) {
|
|
14
16
|
if (options.images && options.images.length > 0) {
|
|
15
17
|
this.images = options.images;
|
|
@@ -21,6 +23,7 @@ class Lightbox {
|
|
|
21
23
|
this.closeable = options.closeable ?? true;
|
|
22
24
|
this.onOpen = options.onOpen;
|
|
23
25
|
this.onClose = options.onClose;
|
|
26
|
+
this.iconBasePath = options.iconBasePath ?? 'svg-icons/';
|
|
24
27
|
}
|
|
25
28
|
show() {
|
|
26
29
|
this.hide();
|
|
@@ -36,12 +39,12 @@ class Lightbox {
|
|
|
36
39
|
this.imgEl = wrapper.querySelector('.lightbox-img');
|
|
37
40
|
this.captionEl = wrapper.querySelector('.lightbox-caption');
|
|
38
41
|
this.counterEl = wrapper.querySelector('.lightbox-counter');
|
|
39
|
-
const sig = { signal: this.
|
|
42
|
+
const sig = { signal: this.listeners.signal };
|
|
40
43
|
if (this.closeable) {
|
|
41
|
-
wrapper.querySelector('.lightbox-close')?.addEventListener('click', this.hide, sig);
|
|
42
|
-
wrapper.querySelector('.lightbox-background')?.addEventListener('click', this.handleBackgroundClick, sig);
|
|
44
|
+
wrapper.querySelector('.lightbox-close')?.addEventListener('click', () => this.hide(), sig);
|
|
45
|
+
wrapper.querySelector('.lightbox-background')?.addEventListener('click', (e) => this.handleBackgroundClick(e), sig);
|
|
43
46
|
}
|
|
44
|
-
document.addEventListener('keydown', this.handleKeydown, sig);
|
|
47
|
+
document.addEventListener('keydown', (e) => this.handleKeydown(e), sig);
|
|
45
48
|
if (this.images.length > 1) {
|
|
46
49
|
wrapper.querySelector('.lightbox-prev')?.addEventListener('click', () => this.prev(), sig);
|
|
47
50
|
wrapper.querySelector('.lightbox-next')?.addEventListener('click', () => this.next(), sig);
|
|
@@ -57,12 +60,11 @@ class Lightbox {
|
|
|
57
60
|
});
|
|
58
61
|
this.onOpen?.();
|
|
59
62
|
}
|
|
60
|
-
hide
|
|
63
|
+
hide() {
|
|
61
64
|
const wrapper = this.wrapper;
|
|
62
65
|
if (!wrapper)
|
|
63
66
|
return;
|
|
64
|
-
this.
|
|
65
|
-
this.abortController = new AbortController();
|
|
67
|
+
this.listeners.reset();
|
|
66
68
|
document.body.style.overflow = '';
|
|
67
69
|
wrapper.classList.remove('is-visible');
|
|
68
70
|
setTimeout(() => {
|
|
@@ -76,7 +78,7 @@ class Lightbox {
|
|
|
76
78
|
}
|
|
77
79
|
this.onClose?.();
|
|
78
80
|
}, 300);
|
|
79
|
-
}
|
|
81
|
+
}
|
|
80
82
|
next() {
|
|
81
83
|
if (this.images.length <= 1)
|
|
82
84
|
return;
|
|
@@ -137,7 +139,7 @@ class Lightbox {
|
|
|
137
139
|
return;
|
|
138
140
|
const prevIdx = (index - 1 + this.images.length) % this.images.length;
|
|
139
141
|
const nextIdx = (index + 1) % this.images.length;
|
|
140
|
-
[prevIdx, nextIdx].forEach(i => {
|
|
142
|
+
[prevIdx, nextIdx].forEach((i) => {
|
|
141
143
|
const img = new Image();
|
|
142
144
|
img.src = this.images[i].src;
|
|
143
145
|
});
|
|
@@ -164,7 +166,7 @@ class Lightbox {
|
|
|
164
166
|
this.imgEl.classList.toggle('is-zoomed', this.isZoomed);
|
|
165
167
|
this.wrapper?.querySelector('.lightbox-img-wrap')?.classList.toggle('is-zoomed', this.isZoomed);
|
|
166
168
|
}
|
|
167
|
-
handleKeydown
|
|
169
|
+
handleKeydown(e) {
|
|
168
170
|
switch (e.key) {
|
|
169
171
|
case 'Escape':
|
|
170
172
|
if (this.closeable)
|
|
@@ -180,11 +182,11 @@ class Lightbox {
|
|
|
180
182
|
this.trapFocus(e);
|
|
181
183
|
break;
|
|
182
184
|
}
|
|
183
|
-
}
|
|
185
|
+
}
|
|
184
186
|
trapFocus(e) {
|
|
185
187
|
if (!this.wrapper)
|
|
186
188
|
return;
|
|
187
|
-
const focusable = Array.from(this.wrapper.querySelectorAll('button:not([hidden]), [tabindex]:not([tabindex="-1"]):not([hidden])')).filter(el => el.offsetParent !== null);
|
|
189
|
+
const focusable = Array.from(this.wrapper.querySelectorAll('button:not([hidden]), [tabindex]:not([tabindex="-1"]):not([hidden])')).filter((el) => el.offsetParent !== null);
|
|
188
190
|
if (focusable.length === 0)
|
|
189
191
|
return;
|
|
190
192
|
const first = focusable[0];
|
|
@@ -202,11 +204,11 @@ class Lightbox {
|
|
|
202
204
|
}
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
|
-
handleBackgroundClick
|
|
207
|
+
handleBackgroundClick(e) {
|
|
206
208
|
if (e.target?.classList.contains('lightbox-background')) {
|
|
207
209
|
this.hide();
|
|
208
210
|
}
|
|
209
|
-
}
|
|
211
|
+
}
|
|
210
212
|
addTouchSupport() {
|
|
211
213
|
const wrap = this.wrapper?.querySelector('.lightbox-img-wrap');
|
|
212
214
|
if (!wrap)
|
|
@@ -216,20 +218,26 @@ class Lightbox {
|
|
|
216
218
|
wrap.addEventListener('touchstart', (e) => {
|
|
217
219
|
startX = e.touches[0].clientX;
|
|
218
220
|
isDragging = true;
|
|
219
|
-
}, { passive: true, signal: this.
|
|
221
|
+
}, { passive: true, signal: this.listeners.signal });
|
|
220
222
|
wrap.addEventListener('touchend', (e) => {
|
|
221
223
|
if (!isDragging)
|
|
222
224
|
return;
|
|
223
225
|
const deltaX = e.changedTouches[0].clientX - startX;
|
|
224
226
|
if (Math.abs(deltaX) > 50) {
|
|
225
|
-
deltaX < 0
|
|
227
|
+
if (deltaX < 0) {
|
|
228
|
+
this.next();
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
this.prev();
|
|
232
|
+
}
|
|
226
233
|
}
|
|
227
234
|
isDragging = false;
|
|
228
|
-
}, { signal: this.
|
|
235
|
+
}, { signal: this.listeners.signal });
|
|
229
236
|
}
|
|
230
237
|
buildTemplate() {
|
|
238
|
+
const icon = (name) => `<svg class="icon-svg" aria-hidden="true"><use href="${this.iconBasePath}icons.svg#${name}"/></svg>`;
|
|
231
239
|
return `
|
|
232
|
-
${this.closeable ?
|
|
240
|
+
${this.closeable ? `<button class="lightbox-close" aria-label="Close lightbox">${icon('close')}</button>` : ''}
|
|
233
241
|
<div class="lightbox" role="document">
|
|
234
242
|
<div class="lightbox-img-wrap">
|
|
235
243
|
<div class="lightbox-spinner"><div class="spinner"></div></div>
|
|
@@ -239,24 +247,20 @@ class Lightbox {
|
|
|
239
247
|
<div class="lightbox-counter" hidden></div>
|
|
240
248
|
</div>
|
|
241
249
|
<button class="lightbox-prev" aria-label="Previous image" hidden>
|
|
242
|
-
|
|
250
|
+
${icon('chevron_left')}
|
|
243
251
|
</button>
|
|
244
252
|
<button class="lightbox-next" aria-label="Next image" hidden>
|
|
245
|
-
|
|
253
|
+
${icon('chevron_right')}
|
|
246
254
|
</button>
|
|
247
255
|
<div class="lightbox-background"></div>
|
|
248
256
|
`;
|
|
249
257
|
}
|
|
250
|
-
static bind(selector = '[data-lightbox]') {
|
|
258
|
+
static bind(selector = '[data-lightbox]', iconBasePath) {
|
|
251
259
|
const elements = document.querySelectorAll(selector);
|
|
252
260
|
const groups = new Map();
|
|
253
|
-
elements.forEach(el => {
|
|
261
|
+
elements.forEach((el) => {
|
|
254
262
|
const groupKey = el.dataset.lightbox || `__solo__${el.dataset.lightboxId ?? Math.random()}`;
|
|
255
|
-
const src = el instanceof HTMLAnchorElement
|
|
256
|
-
? el.href
|
|
257
|
-
: el instanceof HTMLImageElement
|
|
258
|
-
? el.src
|
|
259
|
-
: (el.dataset.src ?? '');
|
|
263
|
+
const src = el instanceof HTMLAnchorElement ? el.href : el instanceof HTMLImageElement ? el.src : (el.dataset.src ?? '');
|
|
260
264
|
const imgChild = el.querySelector('img');
|
|
261
265
|
const alt = el instanceof HTMLImageElement ? el.alt : (imgChild?.alt ?? '');
|
|
262
266
|
const caption = el.dataset.lightboxCaption;
|
|
@@ -264,14 +268,15 @@ class Lightbox {
|
|
|
264
268
|
groups.set(groupKey, []);
|
|
265
269
|
groups.get(groupKey).push({ el, image: { src, alt, caption } });
|
|
266
270
|
});
|
|
267
|
-
groups.forEach(items => {
|
|
271
|
+
groups.forEach((items) => {
|
|
268
272
|
items.forEach(({ el }, idx) => {
|
|
269
273
|
el.style.cursor = 'zoom-in';
|
|
270
|
-
el.addEventListener('click', e => {
|
|
274
|
+
el.addEventListener('click', (e) => {
|
|
271
275
|
e.preventDefault();
|
|
272
276
|
new Lightbox({
|
|
273
|
-
images: items.map(i => i.image),
|
|
277
|
+
images: items.map((i) => i.image),
|
|
274
278
|
startIndex: idx,
|
|
279
|
+
iconBasePath,
|
|
275
280
|
}).show();
|
|
276
281
|
});
|
|
277
282
|
});
|
package/js/listeners.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class ListenerGroup {
|
|
2
|
+
controller = new AbortController();
|
|
3
|
+
get signal() {
|
|
4
|
+
return this.controller.signal;
|
|
5
|
+
}
|
|
6
|
+
reset() {
|
|
7
|
+
this.controller.abort();
|
|
8
|
+
this.controller = new AbortController();
|
|
9
|
+
}
|
|
10
|
+
destroy() {
|
|
11
|
+
this.controller.abort();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export { ListenerGroup };
|
package/js/modal.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ interface ModalOptions {
|
|
|
5
5
|
footer?: string;
|
|
6
6
|
closeable?: boolean;
|
|
7
7
|
type?: ModalType;
|
|
8
|
+
iconBasePath?: string;
|
|
8
9
|
}
|
|
9
10
|
declare class Modal {
|
|
10
11
|
private content;
|
|
@@ -12,12 +13,14 @@ declare class Modal {
|
|
|
12
13
|
private readonly footer?;
|
|
13
14
|
private readonly closeable;
|
|
14
15
|
private readonly type;
|
|
16
|
+
private readonly iconBasePath;
|
|
15
17
|
private template;
|
|
16
18
|
private modalWrapper;
|
|
19
|
+
private listeners;
|
|
17
20
|
constructor(options: ModalOptions);
|
|
18
21
|
constructor(content: string, header?: string, footer?: string, closeable?: boolean, type?: ModalType);
|
|
19
22
|
show(): void;
|
|
20
|
-
hide
|
|
23
|
+
hide(): void;
|
|
21
24
|
private handleEscape;
|
|
22
25
|
private handleBackgroundClick;
|
|
23
26
|
private buildTemplate;
|
package/js/modal.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { sanitizeHtml } from './utils.js';
|
|
2
|
-
|
|
2
|
+
import { ListenerGroup } from './listeners.js';
|
|
3
3
|
class Modal {
|
|
4
4
|
content;
|
|
5
5
|
header;
|
|
6
6
|
footer;
|
|
7
7
|
closeable;
|
|
8
8
|
type;
|
|
9
|
+
iconBasePath;
|
|
9
10
|
template;
|
|
10
11
|
modalWrapper = null;
|
|
12
|
+
listeners = new ListenerGroup();
|
|
11
13
|
constructor(contentOrOptions, header, footer, closeable = true, type = 'default') {
|
|
12
14
|
if (typeof contentOrOptions === 'object') {
|
|
13
15
|
this.content = contentOrOptions.content;
|
|
@@ -15,6 +17,7 @@ class Modal {
|
|
|
15
17
|
this.footer = contentOrOptions.footer;
|
|
16
18
|
this.closeable = contentOrOptions.closeable ?? true;
|
|
17
19
|
this.type = contentOrOptions.type ?? 'default';
|
|
20
|
+
this.iconBasePath = contentOrOptions.iconBasePath ?? 'svg-icons/';
|
|
18
21
|
}
|
|
19
22
|
else {
|
|
20
23
|
this.content = contentOrOptions;
|
|
@@ -22,6 +25,7 @@ class Modal {
|
|
|
22
25
|
this.footer = footer;
|
|
23
26
|
this.closeable = closeable;
|
|
24
27
|
this.type = type;
|
|
28
|
+
this.iconBasePath = 'svg-icons/';
|
|
25
29
|
}
|
|
26
30
|
this.template = this.buildTemplate();
|
|
27
31
|
}
|
|
@@ -32,31 +36,22 @@ class Modal {
|
|
|
32
36
|
wrapper.innerHTML = this.template;
|
|
33
37
|
document.body.append(wrapper);
|
|
34
38
|
this.modalWrapper = wrapper;
|
|
39
|
+
const sig = { signal: this.listeners.signal };
|
|
35
40
|
if (this.closeable) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const background = wrapper.querySelector('.modal-background');
|
|
40
|
-
if (this.closeable && background) {
|
|
41
|
-
background.addEventListener('click', this.handleBackgroundClick);
|
|
42
|
-
}
|
|
43
|
-
if (this.closeable) {
|
|
44
|
-
document.addEventListener('keydown', this.handleEscape);
|
|
41
|
+
wrapper.querySelector('.close')?.addEventListener('click', () => this.hide(), sig);
|
|
42
|
+
wrapper.querySelector('.modal-background')?.addEventListener('click', (e) => this.handleBackgroundClick(e), sig);
|
|
43
|
+
document.addEventListener('keydown', (e) => this.handleEscape(e), sig);
|
|
45
44
|
}
|
|
46
45
|
document.body.style.overflow = 'hidden';
|
|
47
46
|
requestAnimationFrame(() => {
|
|
48
47
|
wrapper.classList.add('is-visible');
|
|
49
48
|
});
|
|
50
49
|
}
|
|
51
|
-
hide
|
|
50
|
+
hide() {
|
|
52
51
|
const wrapper = this.modalWrapper;
|
|
53
52
|
if (!wrapper)
|
|
54
53
|
return;
|
|
55
|
-
|
|
56
|
-
closeBtn?.removeEventListener('click', this.hide);
|
|
57
|
-
const background = wrapper.querySelector('.modal-background');
|
|
58
|
-
background?.removeEventListener('click', this.handleBackgroundClick);
|
|
59
|
-
document.removeEventListener('keydown', this.handleEscape);
|
|
54
|
+
this.listeners.reset();
|
|
60
55
|
document.body.style.overflow = '';
|
|
61
56
|
wrapper.classList.remove('is-visible');
|
|
62
57
|
setTimeout(() => {
|
|
@@ -65,21 +60,21 @@ class Modal {
|
|
|
65
60
|
this.modalWrapper = null;
|
|
66
61
|
}
|
|
67
62
|
}, 300);
|
|
68
|
-
}
|
|
69
|
-
handleEscape
|
|
63
|
+
}
|
|
64
|
+
handleEscape(e) {
|
|
70
65
|
if (e.key === 'Escape') {
|
|
71
66
|
this.hide();
|
|
72
67
|
}
|
|
73
|
-
}
|
|
74
|
-
handleBackgroundClick
|
|
68
|
+
}
|
|
69
|
+
handleBackgroundClick(e) {
|
|
75
70
|
if (e.target?.classList.contains('modal-background')) {
|
|
76
71
|
this.hide();
|
|
77
72
|
}
|
|
78
|
-
}
|
|
73
|
+
}
|
|
79
74
|
buildTemplate() {
|
|
80
75
|
const parts = [`<div class="modal modal-${this.type}">`];
|
|
81
76
|
if (this.closeable) {
|
|
82
|
-
parts.push(
|
|
77
|
+
parts.push(`<svg class="icon-svg close" aria-hidden="true"><use href="${this.iconBasePath}icons.svg#close"/></svg>`);
|
|
83
78
|
}
|
|
84
79
|
if (this.header !== undefined) {
|
|
85
80
|
const headerClass = `header ${this.type}-bg`;
|
package/js/popover.d.ts
CHANGED
|
@@ -21,8 +21,9 @@ declare class Popover {
|
|
|
21
21
|
private readonly trigger;
|
|
22
22
|
private readonly opts;
|
|
23
23
|
private popoverEl;
|
|
24
|
-
private _isOpen;
|
|
25
24
|
private hoverTimer;
|
|
25
|
+
private listeners;
|
|
26
|
+
private openListeners;
|
|
26
27
|
constructor(triggerEl: HTMLElement | string, options: PopoverOptions);
|
|
27
28
|
get isOpen(): boolean;
|
|
28
29
|
open(): void;
|
|
@@ -39,7 +40,6 @@ declare class Popover {
|
|
|
39
40
|
private onOutsideClick;
|
|
40
41
|
private onEscape;
|
|
41
42
|
private attachTrigger;
|
|
42
|
-
private detachTrigger;
|
|
43
43
|
}
|
|
44
44
|
export { Popover };
|
|
45
45
|
export type { PopoverOptions, PopoverPlacement, PopoverAlign, PopoverTrigger };
|