@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.
Files changed (90) hide show
  1. package/README.md +38 -1
  2. package/css/calendar.scss +4 -3
  3. package/css/code-viewer.scss +1 -1
  4. package/css/datepicker.scss +83 -70
  5. package/css/editor.scss +2 -1
  6. package/css/flyout-menu.scss +2 -1
  7. package/css/form.scss +11 -32
  8. package/css/mixins.scss +25 -0
  9. package/css/range-slider.scss +63 -6
  10. package/css/stepper.scss +2 -1
  11. package/css/style.css +124 -71
  12. package/css/style.css.map +1 -1
  13. package/css/style.min.css +1 -1
  14. package/css/style.min.css.map +1 -1
  15. package/css/timepicker.scss +1 -1
  16. package/css/virtual-dropdown.scss +6 -16
  17. package/js/bottom-sheet.d.ts +3 -5
  18. package/js/bottom-sheet.js +39 -58
  19. package/js/calendar.d.ts +1 -2
  20. package/js/calendar.js +77 -84
  21. package/js/carousel.d.ts +1 -1
  22. package/js/carousel.js +22 -14
  23. package/js/chart.d.ts +1 -1
  24. package/js/chart.js +104 -65
  25. package/js/code-viewer.d.ts +1 -1
  26. package/js/code-viewer.js +7 -18
  27. package/js/color-picker.d.ts +2 -2
  28. package/js/color-picker.js +20 -22
  29. package/js/context-menu.d.ts +1 -1
  30. package/js/context-menu.js +10 -12
  31. package/js/datepicker.d.ts +14 -3
  32. package/js/datepicker.js +180 -99
  33. package/js/dropdown.d.ts +1 -1
  34. package/js/dropdown.js +4 -5
  35. package/js/editor.d.ts +1 -1
  36. package/js/editor.js +30 -34
  37. package/js/file-uploader.d.ts +2 -9
  38. package/js/file-uploader.js +57 -86
  39. package/js/flyout-menu.d.ts +3 -3
  40. package/js/flyout-menu.js +20 -22
  41. package/js/gallery.d.ts +1 -2
  42. package/js/gallery.js +14 -20
  43. package/js/group-picker.d.ts +1 -1
  44. package/js/group-picker.js +22 -29
  45. package/js/lightbox.d.ts +2 -2
  46. package/js/lightbox.js +28 -27
  47. package/js/listeners.d.ts +7 -0
  48. package/js/listeners.js +14 -0
  49. package/js/modal.d.ts +2 -1
  50. package/js/modal.js +13 -20
  51. package/js/popover.d.ts +2 -2
  52. package/js/popover.js +42 -37
  53. package/js/position.js +3 -5
  54. package/js/push-menu.d.ts +2 -0
  55. package/js/push-menu.js +21 -15
  56. package/js/range-slider.d.ts +15 -1
  57. package/js/range-slider.js +67 -7
  58. package/js/scroll.js +4 -7
  59. package/js/scrollbar.d.ts +3 -3
  60. package/js/scrollbar.js +39 -42
  61. package/js/select.d.ts +1 -1
  62. package/js/select.js +19 -23
  63. package/js/sidebar-nav.d.ts +1 -1
  64. package/js/sidebar-nav.js +13 -9
  65. package/js/stepper.d.ts +1 -1
  66. package/js/stepper.js +9 -11
  67. package/js/table.d.ts +1 -1
  68. package/js/table.js +24 -23
  69. package/js/tabs.d.ts +1 -1
  70. package/js/tabs.js +10 -17
  71. package/js/theme.d.ts +1 -0
  72. package/js/theme.js +6 -15
  73. package/js/timepicker.d.ts +8 -6
  74. package/js/timepicker.js +36 -36
  75. package/js/toast.d.ts +2 -1
  76. package/js/toast.js +8 -4
  77. package/js/tooltip.d.ts +1 -4
  78. package/js/tooltip.js +14 -23
  79. package/js/tree.d.ts +0 -1
  80. package/js/tree.js +6 -11
  81. package/js/utils.js +7 -8
  82. package/js/virtual-dropdown.d.ts +1 -1
  83. package/js/virtual-dropdown.js +26 -28
  84. package/package.json +1 -1
  85. package/css/calendar.css +0 -928
  86. package/css/guitar-chords.css +0 -251
  87. package/js/form-builder.js +0 -107
  88. package/js/guitar-chords.js +0 -268
  89. package/js/lazy-loader.js +0 -121
  90. package/js/request.js +0 -51
@@ -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
- abortControllers = new Map();
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
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => {
46
- this.dropZone.addEventListener(event, this.preventDefaults);
47
- });
48
- ['dragenter', 'dragover'].forEach(event => {
49
- this.dropZone.addEventListener(event, this.handleDragEnter);
50
- });
51
- ['dragleave', 'drop'].forEach(event => {
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
- handleDropZoneClick = () => {
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
- handleUploadClick = async () => {
86
- if (this.files.size === 0)
87
- return;
88
- this.uploadBtn.disabled = true;
89
- this.uploadBtn.textContent = 'Uploading...';
90
- const uploadPromises = Array.from(this.files.values()).map(({ file, element }) => this.uploadFile(file, element));
91
- const results = await Promise.allSettled(uploadPromises);
92
- this.uploadBtn.textContent = 'Upload Complete';
93
- setTimeout(() => {
94
- this.dispatchUploadCompletedEvent(results);
95
- this.fileList.innerHTML = '';
96
- this.files.clear();
97
- this.updateUploadButton();
98
- }, 1500);
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.abortControllers.set(key, () => xhr.abort());
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.abortControllers.delete(key);
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.abortControllers.delete(key);
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.abortControllers.delete(key);
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.abortControllers.get(key);
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.abortControllers.forEach(abort => abort());
269
- this.abortControllers.clear();
270
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => {
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
  }
@@ -19,7 +19,7 @@ declare class FlyoutMenu {
19
19
  private closeBtn;
20
20
  private submenuToggles;
21
21
  private menuLinks;
22
- private abortController;
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: () => void;
31
- close: () => void;
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
- abortController = new AbortController();
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: '&copy; 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.abortController.signal };
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 = (e) => {
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.abortController.abort();
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 resizeObserver;
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
- resizeObserver = null;
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
- this.abortController = new AbortController();
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
- }, { signal: sig });
60
- window.addEventListener('scroll', this.handleScroll, { passive: true, signal: sig });
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 => { col.innerHTML = ''; });
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
- if (this.resizeObserver) {
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
  }
@@ -26,7 +26,7 @@ declare class GroupPicker {
26
26
  private container;
27
27
  private data;
28
28
  private options;
29
- private abortController;
29
+ private listeners;
30
30
  private selectedParents;
31
31
  private selectedSubs;
32
32
  private expandedGroups;
@@ -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...',
@@ -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.abortController.signal });
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.abortController.signal });
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.abortController.signal });
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 + 'px';
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.abortController.signal });
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.abortController.signal });
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 + 'px';
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 + 'px';
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.abortController.signal });
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.abortController.abort();
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 abortController;
27
+ private listeners;
28
28
  constructor(options: LightboxOptions);
29
29
  show(): void;
30
- hide: () => void;
30
+ hide(): void;
31
31
  next(): void;
32
32
  prev(): void;
33
33
  isVisible(): boolean;