@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.
Files changed (121) hide show
  1. package/README.md +55 -9
  2. package/css/alert.scss +9 -9
  3. package/css/badge.scss +5 -25
  4. package/css/bottom-sheet.scss +2 -6
  5. package/css/breadcrumb.scss +18 -9
  6. package/css/calendar.scss +19 -29
  7. package/css/carousel.scss +3 -3
  8. package/css/chart.scss +1 -6
  9. package/css/checkbox.scss +1 -1
  10. package/css/chips.scss +13 -23
  11. package/css/code-viewer.scss +2 -2
  12. package/css/color-picker.scss +3 -5
  13. package/css/context-menu.scss +3 -8
  14. package/css/datepicker.scss +84 -71
  15. package/css/defaults.scss +25 -0
  16. package/css/docs.scss +6 -6
  17. package/css/dropdown.scss +3 -8
  18. package/css/editor.scss +15 -18
  19. package/css/file-uploader.scss +6 -17
  20. package/css/flyout-menu.scss +5 -1
  21. package/css/form.scss +43 -57
  22. package/css/gallery.scss +15 -0
  23. package/css/group-picker.scss +28 -17
  24. package/css/icons.scss +7 -327
  25. package/css/lightbox.scss +7 -7
  26. package/css/mixins.scss +77 -0
  27. package/css/modal.scss +4 -8
  28. package/css/parameters.scss +8 -2
  29. package/css/placeholder.scss +2 -17
  30. package/css/popover.scss +2 -7
  31. package/css/properties.scss +0 -5
  32. package/css/push-menu.scss +1 -1
  33. package/css/range-slider.scss +7 -6
  34. package/css/stepper.scss +4 -3
  35. package/css/style.css +279 -554
  36. package/css/style.css.map +1 -1
  37. package/css/style.min.css +1 -1
  38. package/css/style.min.css.map +1 -1
  39. package/css/tabs.scss +3 -7
  40. package/css/timepicker.scss +3 -3
  41. package/css/tooltip.scss +1 -1
  42. package/css/typography.scss +3 -3
  43. package/css/virtual-dropdown.scss +12 -27
  44. package/js/bottom-sheet.d.ts +5 -5
  45. package/js/bottom-sheet.js +41 -58
  46. package/js/calendar.d.ts +1 -2
  47. package/js/calendar.js +77 -84
  48. package/js/carousel.d.ts +2 -1
  49. package/js/carousel.js +25 -16
  50. package/js/chart.d.ts +1 -1
  51. package/js/chart.js +104 -65
  52. package/js/code-viewer.d.ts +1 -1
  53. package/js/code-viewer.js +7 -18
  54. package/js/color-picker.d.ts +2 -2
  55. package/js/color-picker.js +20 -22
  56. package/js/context-menu.d.ts +1 -1
  57. package/js/context-menu.js +10 -12
  58. package/js/datepicker.d.ts +14 -3
  59. package/js/datepicker.js +180 -99
  60. package/js/docs-nav.js +1 -1
  61. package/js/dropdown.d.ts +1 -1
  62. package/js/dropdown.js +4 -5
  63. package/js/editor.d.ts +1 -1
  64. package/js/editor.js +30 -34
  65. package/js/file-uploader.d.ts +2 -9
  66. package/js/file-uploader.js +57 -86
  67. package/js/flyout-menu.d.ts +3 -3
  68. package/js/flyout-menu.js +20 -22
  69. package/js/gallery.d.ts +6 -2
  70. package/js/gallery.js +49 -20
  71. package/js/group-picker.d.ts +2 -1
  72. package/js/group-picker.js +30 -34
  73. package/js/lightbox.d.ts +5 -3
  74. package/js/lightbox.js +36 -31
  75. package/js/listeners.d.ts +7 -0
  76. package/js/listeners.js +14 -0
  77. package/js/modal.d.ts +4 -1
  78. package/js/modal.js +17 -22
  79. package/js/popover.d.ts +2 -2
  80. package/js/popover.js +42 -37
  81. package/js/position.js +3 -5
  82. package/js/push-menu.d.ts +8 -2
  83. package/js/push-menu.js +34 -23
  84. package/js/range-slider.d.ts +2 -2
  85. package/js/range-slider.js +7 -6
  86. package/js/scroll.js +4 -7
  87. package/js/scrollbar.d.ts +3 -3
  88. package/js/scrollbar.js +39 -42
  89. package/js/select.d.ts +1 -1
  90. package/js/select.js +20 -24
  91. package/js/sidebar-nav.d.ts +2 -1
  92. package/js/sidebar-nav.js +15 -10
  93. package/js/stepper.d.ts +1 -1
  94. package/js/stepper.js +9 -11
  95. package/js/table.d.ts +1 -1
  96. package/js/table.js +24 -23
  97. package/js/tabs.d.ts +1 -1
  98. package/js/tabs.js +10 -17
  99. package/js/theme.d.ts +1 -0
  100. package/js/theme.js +6 -15
  101. package/js/timepicker.d.ts +8 -6
  102. package/js/timepicker.js +36 -36
  103. package/js/toast.d.ts +4 -1
  104. package/js/toast.js +13 -5
  105. package/js/tooltip.d.ts +1 -4
  106. package/js/tooltip.js +14 -23
  107. package/js/tree.d.ts +0 -1
  108. package/js/tree.js +6 -11
  109. package/js/utils.js +7 -8
  110. package/js/virtual-dropdown.d.ts +1 -1
  111. package/js/virtual-dropdown.js +26 -28
  112. package/package.json +2 -1
  113. package/svg-icons/chevron_right.svg +1 -0
  114. package/svg-icons/keyboard_arrow_down.svg +1 -0
  115. package/css/calendar.css +0 -928
  116. package/css/guitar-chords.css +0 -251
  117. package/fonts/MaterialSymbolsOutlined.woff2 +0 -0
  118. package/js/form-builder.js +0 -107
  119. package/js/guitar-chords.js +0 -268
  120. package/js/lazy-loader.js +0 -121
  121. package/js/request.js +0 -51
@@ -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
- abortController;
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
- <span class="icon icon-search group-picker__search-icon" aria-hidden="true"></span>
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
- <span class="icon icon-search" aria-hidden="true"></span>
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 = 'icon icon-navigate_next group-picker__chevron';
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.abortController.signal });
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.abortController.signal });
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.abortController.signal });
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 + 'px';
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 = 'icon icon-check group-picker__leaf-check';
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.abortController.signal });
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 = `<span class="icon icon-close"></span>`;
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.abortController.signal });
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 + 'px';
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 + 'px';
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.abortController.signal });
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.abortController.abort();
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 abortController;
29
+ private listeners;
28
30
  constructor(options: LightboxOptions);
29
31
  show(): void;
30
- hide: () => void;
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
- abortController = new AbortController();
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.abortController.signal };
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.abortController.abort();
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 = (e) => {
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 = (e) => {
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.abortController.signal });
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 ? this.next() : this.prev();
227
+ if (deltaX < 0) {
228
+ this.next();
229
+ }
230
+ else {
231
+ this.prev();
232
+ }
226
233
  }
227
234
  isDragging = false;
228
- }, { signal: this.abortController.signal });
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 ? '<button class="lightbox-close" aria-label="Close lightbox"><span class="icon icon-close"></span></button>' : ''}
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
- <span class="icon icon-navigate_before"></span>
250
+ ${icon('chevron_left')}
243
251
  </button>
244
252
  <button class="lightbox-next" aria-label="Next image" hidden>
245
- <span class="icon icon-navigate_next"></span>
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
  });
@@ -0,0 +1,7 @@
1
+ declare class ListenerGroup {
2
+ private controller;
3
+ get signal(): AbortSignal;
4
+ reset(): void;
5
+ destroy(): void;
6
+ }
7
+ export { ListenerGroup };
@@ -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: () => void;
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
- const CLOSE_ICON = '<div class="icon icon-close close"></div>';
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
- const closeBtn = wrapper.querySelector('.close');
37
- closeBtn?.addEventListener('click', this.hide);
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
- const closeBtn = wrapper.querySelector('.close');
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 = (e) => {
63
+ }
64
+ handleEscape(e) {
70
65
  if (e.key === 'Escape') {
71
66
  this.hide();
72
67
  }
73
- };
74
- handleBackgroundClick = (e) => {
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(CLOSE_ICON);
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 };