@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,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,11 +18,14 @@ 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;
23
+ private showingSkeleton;
24
+ private static readonly SKELETON_HEIGHTS;
24
25
  constructor(containerId: string, options: MasonryGalleryOptions);
25
26
  private init;
27
+ private renderSkeleton;
28
+ private createSkeletonCard;
26
29
  private setupLayout;
27
30
  private buildColumns;
28
31
  private addEventListeners;
@@ -30,6 +33,7 @@ declare class MasonryGallery {
30
33
  private handleScroll;
31
34
  private loadMoreImages;
32
35
  private toggleLoader;
36
+ private clearSkeleton;
33
37
  private renderImages;
34
38
  private createCard;
35
39
  private addToShortestColumn;
package/js/gallery.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { escapeHtml } from './utils.js';
2
2
  import { Lightbox } from './lightbox.js';
3
+ import { ListenerGroup } from './listeners.js';
3
4
  class MasonryGallery {
4
5
  container;
5
6
  loader;
@@ -7,9 +8,10 @@ class MasonryGallery {
7
8
  columns = [];
8
9
  allImages = [];
9
10
  isFetching = false;
10
- resizeObserver = null;
11
- abortController = null;
11
+ listeners = new ListenerGroup();
12
12
  reloaded = 0;
13
+ showingSkeleton = false;
14
+ static SKELETON_HEIGHTS = [180, 260, 220, 300, 240, 190];
13
15
  constructor(containerId, options) {
14
16
  const container = document.getElementById(containerId);
15
17
  if (!container) {
@@ -28,9 +30,32 @@ class MasonryGallery {
28
30
  }
29
31
  init() {
30
32
  this.setupLayout();
33
+ this.renderSkeleton();
31
34
  this.loadMoreImages();
32
35
  this.addEventListeners();
33
36
  }
37
+ renderSkeleton() {
38
+ this.showingSkeleton = true;
39
+ this.columns.forEach((col, colIndex) => {
40
+ for (let i = 0; i < 2; i++) {
41
+ const height = MasonryGallery.SKELETON_HEIGHTS[(colIndex + i) % MasonryGallery.SKELETON_HEIGHTS.length];
42
+ col.appendChild(this.createSkeletonCard(height));
43
+ }
44
+ });
45
+ }
46
+ createSkeletonCard(height) {
47
+ const div = document.createElement('div');
48
+ div.className = 'masonry-item masonry-item--skeleton';
49
+ div.setAttribute('aria-hidden', 'true');
50
+ div.innerHTML = `
51
+ <div class="masonry-item-skeleton-img" style="height:${height}px"></div>
52
+ <div class="masonry-item-info">
53
+ <div class="placeholder w-8" style="display: block;"></div>
54
+ <div class="placeholder w-5" style="display: block; margin-top: 6px;"></div>
55
+ </div>
56
+ `;
57
+ return div;
58
+ }
34
59
  setupLayout() {
35
60
  const containerWidth = this.container.getBoundingClientRect().width;
36
61
  const numColumns = Math.max(1, Math.floor(containerWidth / this.options.minColumnWidth));
@@ -50,35 +75,36 @@ class MasonryGallery {
50
75
  }
51
76
  }
52
77
  addEventListeners() {
53
- this.abortController = new AbortController();
54
- const sig = this.abortController.signal;
78
+ const sig = { signal: this.listeners.signal };
55
79
  let resizeTimeout;
56
80
  window.addEventListener('resize', () => {
57
81
  clearTimeout(resizeTimeout);
58
82
  resizeTimeout = setTimeout(() => this.reLayout(), 200);
59
- }, { signal: sig });
60
- window.addEventListener('scroll', this.handleScroll, { passive: true, signal: sig });
83
+ }, sig);
84
+ window.addEventListener('scroll', () => this.handleScroll(), { ...sig, passive: true });
61
85
  }
62
86
  reLayout() {
63
- const items = this.columns.flatMap(col => Array.from(col.children));
87
+ const items = this.columns.flatMap((col) => Array.from(col.children));
64
88
  const availableWidth = Math.min(1200, window.innerWidth - 40);
65
89
  const numColumns = Math.max(1, Math.floor(availableWidth / this.options.minColumnWidth));
66
90
  if (this.columns.length !== numColumns) {
67
91
  this.buildColumns(numColumns);
68
92
  }
69
93
  else {
70
- this.columns.forEach(col => { col.innerHTML = ''; });
94
+ this.columns.forEach((col) => {
95
+ col.innerHTML = '';
96
+ });
71
97
  }
72
- items.forEach(item => this.addToShortestColumn(item));
98
+ items.forEach((item) => this.addToShortestColumn(item));
73
99
  }
74
- handleScroll = () => {
100
+ handleScroll() {
75
101
  if (this.isFetching)
76
102
  return;
77
103
  const rect = this.container.getBoundingClientRect();
78
104
  if (rect.bottom > 0 && rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
79
105
  this.loadMoreImages();
80
106
  }
81
- };
107
+ }
82
108
  async loadMoreImages(isAutoFill = false) {
83
109
  if (!isAutoFill)
84
110
  this.reloaded++;
@@ -96,6 +122,7 @@ class MasonryGallery {
96
122
  }
97
123
  catch (error) {
98
124
  console.error('MasonryGallery: error loading images', error);
125
+ this.clearSkeleton();
99
126
  }
100
127
  finally {
101
128
  this.isFetching = false;
@@ -113,7 +140,16 @@ class MasonryGallery {
113
140
  this.loader.classList.toggle('hidden', !show);
114
141
  }
115
142
  }
143
+ clearSkeleton() {
144
+ if (!this.showingSkeleton)
145
+ return;
146
+ this.showingSkeleton = false;
147
+ this.columns.forEach((col) => {
148
+ col.innerHTML = '';
149
+ });
150
+ }
116
151
  renderImages(imageDataList) {
152
+ this.clearSkeleton();
117
153
  const startIndex = this.allImages.length;
118
154
  this.allImages.push(...imageDataList);
119
155
  const sorted = [...this.columns].sort((a, b) => a.offsetHeight - b.offsetHeight);
@@ -123,7 +159,7 @@ class MasonryGallery {
123
159
  const index = startIndex + i;
124
160
  item.addEventListener('click', () => {
125
161
  new Lightbox({
126
- images: this.allImages.map(img => ({
162
+ images: this.allImages.map((img) => ({
127
163
  src: img.src,
128
164
  alt: img.title,
129
165
  caption: img.desc,
@@ -174,14 +210,7 @@ class MasonryGallery {
174
210
  shortestCol.appendChild(element);
175
211
  }
176
212
  destroy() {
177
- 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
- }
213
+ this.listeners.destroy();
185
214
  this.allImages = [];
186
215
  }
187
216
  }
@@ -21,12 +21,13 @@ interface GroupPickerOptions {
21
21
  deselectLabel?: string;
22
22
  emptyLabel?: string;
23
23
  selectionPlaceholder?: string;
24
+ iconBasePath?: string;
24
25
  }
25
26
  declare class GroupPicker {
26
27
  private container;
27
28
  private data;
28
29
  private options;
29
- private abortController;
30
+ private listeners;
30
31
  private selectedParents;
31
32
  private selectedSubs;
32
33
  private expandedGroups;