@dodlhuat/basix 1.3.5 → 1.4.1

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 (82) hide show
  1. package/README.md +92 -7
  2. package/css/checkbox.scss +15 -2
  3. package/css/color-picker.scss +278 -0
  4. package/css/datepicker.scss +5 -3
  5. package/css/push-menu.scss +7 -0
  6. package/css/stepper.scss +1 -1
  7. package/css/style.css +291 -6
  8. package/css/style.css.map +1 -1
  9. package/css/style.min.css +1 -1
  10. package/css/style.min.css.map +1 -1
  11. package/css/style.scss +1 -0
  12. package/css/table.scss +33 -0
  13. package/js/bottom-sheet.d.ts +0 -2
  14. package/js/bottom-sheet.js +0 -2
  15. package/js/calendar.d.ts +9 -17
  16. package/js/calendar.js +4 -7
  17. package/js/carousel.d.ts +0 -2
  18. package/js/carousel.js +1 -3
  19. package/js/chart.d.ts +6 -14
  20. package/js/chart.js +3 -5
  21. package/js/code-viewer.d.ts +2 -1
  22. package/js/code-viewer.js +8 -2
  23. package/js/color-picker.d.ts +50 -0
  24. package/js/color-picker.js +291 -0
  25. package/js/context-menu.d.ts +0 -3
  26. package/js/context-menu.js +0 -3
  27. package/js/datepicker.d.ts +0 -4
  28. package/js/datepicker.js +0 -1
  29. package/js/docs-nav.js +1 -0
  30. package/js/dropdown.d.ts +1 -4
  31. package/js/dropdown.js +0 -1
  32. package/js/editor.d.ts +0 -4
  33. package/js/editor.js +3 -5
  34. package/js/file-uploader.d.ts +0 -4
  35. package/js/file-uploader.js +0 -1
  36. package/js/flyout-menu.d.ts +0 -2
  37. package/js/flyout-menu.js +0 -1
  38. package/js/gallery.d.ts +1 -4
  39. package/js/gallery.js +18 -23
  40. package/js/group-picker.d.ts +0 -5
  41. package/js/group-picker.js +6 -8
  42. package/js/lightbox.d.ts +1 -4
  43. package/js/lightbox.js +6 -10
  44. package/js/modal.d.ts +0 -2
  45. package/js/modal.js +0 -1
  46. package/js/popover.d.ts +0 -3
  47. package/js/popover.js +0 -2
  48. package/js/position.d.ts +0 -13
  49. package/js/position.js +0 -12
  50. package/js/push-menu.d.ts +0 -3
  51. package/js/push-menu.js +6 -11
  52. package/js/range-slider.d.ts +0 -1
  53. package/js/range-slider.js +2 -3
  54. package/js/scroll.d.ts +0 -2
  55. package/js/scroll.js +5 -6
  56. package/js/scrollbar.d.ts +2 -10
  57. package/js/scrollbar.js +25 -42
  58. package/js/select.d.ts +2 -3
  59. package/js/select.js +6 -9
  60. package/js/sidebar-nav.d.ts +4 -15
  61. package/js/sidebar-nav.js +21 -35
  62. package/js/stepper.d.ts +5 -2
  63. package/js/stepper.js +42 -6
  64. package/js/table.d.ts +1 -56
  65. package/js/table.js +4 -56
  66. package/js/tabs.d.ts +1 -32
  67. package/js/tabs.js +7 -39
  68. package/js/theme.d.ts +0 -44
  69. package/js/theme.js +0 -44
  70. package/js/timepicker.d.ts +1 -4
  71. package/js/timepicker.js +1 -2
  72. package/js/toast.d.ts +0 -2
  73. package/js/toast.js +0 -1
  74. package/js/tooltip.d.ts +0 -4
  75. package/js/tooltip.js +0 -1
  76. package/js/tree.d.ts +0 -3
  77. package/js/tree.js +9 -11
  78. package/js/utils.d.ts +0 -10
  79. package/js/utils.js +2 -42
  80. package/js/virtual-dropdown.d.ts +3 -5
  81. package/js/virtual-dropdown.js +17 -66
  82. package/package.json +6 -2
@@ -0,0 +1,291 @@
1
+ class ColorPicker {
2
+ container;
3
+ onChange;
4
+ canvas;
5
+ ctx;
6
+ cursor;
7
+ hueSlider;
8
+ hexInput;
9
+ rInput;
10
+ gInput;
11
+ bInput;
12
+ preview;
13
+ hue = 0;
14
+ saturation = 100;
15
+ brightness = 100;
16
+ isDragging = false;
17
+ abortController = new AbortController();
18
+ ro;
19
+ constructor(elementOrSelector, options = {}) {
20
+ const el = typeof elementOrSelector === 'string'
21
+ ? document.querySelector(elementOrSelector)
22
+ : elementOrSelector;
23
+ if (!el)
24
+ throw new Error(`ColorPicker: element not found for "${elementOrSelector}"`);
25
+ this.container = el;
26
+ this.onChange = options.onChange;
27
+ this.build();
28
+ this.bindEvents();
29
+ this.ro = new ResizeObserver(() => this.resizeCanvas());
30
+ this.ro.observe(this.canvas.parentElement);
31
+ this.resizeCanvas();
32
+ if (options.value) {
33
+ this.setValue(options.value);
34
+ }
35
+ else {
36
+ this.updateFromHSB();
37
+ }
38
+ }
39
+ build() {
40
+ this.container.classList.add('color-picker');
41
+ this.container.innerHTML = `
42
+ <div class="color-picker__field">
43
+ <canvas class="color-picker__canvas"></canvas>
44
+ <div class="color-picker__cursor" aria-hidden="true"></div>
45
+ </div>
46
+ <div class="color-picker__controls">
47
+ <div class="color-picker__hue-track">
48
+ <input type="range" class="color-picker__hue-slider" min="0" max="360" value="0" aria-label="Hue">
49
+ </div>
50
+ <div class="color-picker__preview" aria-hidden="true"></div>
51
+ </div>
52
+ <div class="color-picker__inputs">
53
+ <div class="color-picker__input-group color-picker__input-group--hex">
54
+ <label>HEX</label>
55
+ <input type="text" class="color-picker__hex" value="#FF0000" spellcheck="false" autocomplete="off" maxlength="7">
56
+ </div>
57
+ <div class="color-picker__input-group">
58
+ <label>R</label>
59
+ <input type="number" class="color-picker__r" min="0" max="255" value="255">
60
+ </div>
61
+ <div class="color-picker__input-group">
62
+ <label>G</label>
63
+ <input type="number" class="color-picker__g" min="0" max="255" value="0">
64
+ </div>
65
+ <div class="color-picker__input-group">
66
+ <label>B</label>
67
+ <input type="number" class="color-picker__b" min="0" max="255" value="0">
68
+ </div>
69
+ </div>
70
+ `;
71
+ const canvas = this.container.querySelector('.color-picker__canvas');
72
+ const ctx = canvas?.getContext('2d');
73
+ if (!canvas || !ctx)
74
+ throw new Error('ColorPicker: canvas context unavailable');
75
+ this.canvas = canvas;
76
+ this.ctx = ctx;
77
+ this.cursor = this.container.querySelector('.color-picker__cursor');
78
+ this.hueSlider = this.container.querySelector('.color-picker__hue-slider');
79
+ this.hexInput = this.container.querySelector('.color-picker__hex');
80
+ this.rInput = this.container.querySelector('.color-picker__r');
81
+ this.gInput = this.container.querySelector('.color-picker__g');
82
+ this.bInput = this.container.querySelector('.color-picker__b');
83
+ this.preview = this.container.querySelector('.color-picker__preview');
84
+ }
85
+ bindEvents() {
86
+ const sig = { signal: this.abortController.signal };
87
+ this.canvas.addEventListener('pointerdown', (e) => {
88
+ e.preventDefault();
89
+ this.canvas.setPointerCapture(e.pointerId);
90
+ this.isDragging = true;
91
+ this.handleFieldInteraction(e);
92
+ }, sig);
93
+ this.canvas.addEventListener('pointermove', (e) => {
94
+ if (!this.isDragging)
95
+ return;
96
+ this.handleFieldInteraction(e);
97
+ }, sig);
98
+ this.canvas.addEventListener('pointerup', () => {
99
+ this.isDragging = false;
100
+ }, sig);
101
+ this.canvas.addEventListener('pointercancel', () => {
102
+ this.isDragging = false;
103
+ }, sig);
104
+ this.hueSlider.addEventListener('input', () => {
105
+ this.hue = +this.hueSlider.value;
106
+ this.drawField();
107
+ this.updateFromHSB();
108
+ }, sig);
109
+ this.hexInput.addEventListener('change', () => this.handleHexInput(), sig);
110
+ this.rInput.addEventListener('change', () => this.handleRGBInput(), sig);
111
+ this.gInput.addEventListener('change', () => this.handleRGBInput(), sig);
112
+ this.bInput.addEventListener('change', () => this.handleRGBInput(), sig);
113
+ }
114
+ resizeCanvas() {
115
+ const w = this.canvas.offsetWidth;
116
+ const h = this.canvas.offsetHeight;
117
+ if (w === 0 || h === 0)
118
+ return;
119
+ if (this.canvas.width === w && this.canvas.height === h)
120
+ return;
121
+ this.canvas.width = w;
122
+ this.canvas.height = h;
123
+ this.drawField();
124
+ this.updateCursor();
125
+ }
126
+ drawField() {
127
+ const { width, height } = this.canvas;
128
+ if (width === 0 || height === 0)
129
+ return;
130
+ const gradH = this.ctx.createLinearGradient(0, 0, width, 0);
131
+ gradH.addColorStop(0, '#fff');
132
+ gradH.addColorStop(1, `hsl(${this.hue}, 100%, 50%)`);
133
+ this.ctx.fillStyle = gradH;
134
+ this.ctx.fillRect(0, 0, width, height);
135
+ const gradV = this.ctx.createLinearGradient(0, 0, 0, height);
136
+ gradV.addColorStop(0, 'rgba(0,0,0,0)');
137
+ gradV.addColorStop(1, '#000');
138
+ this.ctx.fillStyle = gradV;
139
+ this.ctx.fillRect(0, 0, width, height);
140
+ }
141
+ handleFieldInteraction(e) {
142
+ const rect = this.canvas.getBoundingClientRect();
143
+ const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
144
+ const y = Math.max(0, Math.min(e.clientY - rect.top, rect.height));
145
+ this.saturation = (x / rect.width) * 100;
146
+ this.brightness = 100 - (y / rect.height) * 100;
147
+ this.updateCursor();
148
+ this.updateFromHSB();
149
+ }
150
+ updateCursor() {
151
+ const rect = this.canvas.getBoundingClientRect();
152
+ const x = (this.saturation / 100) * rect.width;
153
+ const y = (1 - this.brightness / 100) * rect.height;
154
+ this.cursor.style.left = `${x}px`;
155
+ this.cursor.style.top = `${y}px`;
156
+ this.cursor.classList.toggle('color-picker__cursor--dark', this.brightness > 50 && this.saturation < 80);
157
+ }
158
+ updateFromHSB() {
159
+ const rgb = this.hsbToRgb(this.hue, this.saturation, this.brightness);
160
+ const hex = this.rgbToHex(rgb);
161
+ this.syncInputs(rgb, hex);
162
+ this.syncPreview(rgb);
163
+ this.onChange?.(hex, rgb);
164
+ }
165
+ syncInputs(rgb, hex) {
166
+ this.hexInput.value = hex;
167
+ this.rInput.value = String(rgb.r);
168
+ this.gInput.value = String(rgb.g);
169
+ this.bInput.value = String(rgb.b);
170
+ }
171
+ syncPreview(rgb) {
172
+ this.preview.style.backgroundColor = `rgb(${rgb.r},${rgb.g},${rgb.b})`;
173
+ }
174
+ handleHexInput() {
175
+ const hex = this.hexInput.value.trim();
176
+ if (/^#[0-9a-f]{6}$/i.test(hex)) {
177
+ this.setFromRGB(this.hexToRgb(hex));
178
+ }
179
+ else {
180
+ this.hexInput.value = this.rgbToHex(this.hsbToRgb(this.hue, this.saturation, this.brightness));
181
+ }
182
+ }
183
+ handleRGBInput() {
184
+ const clamp = (v) => Math.max(0, Math.min(255, isNaN(v) ? 0 : v));
185
+ this.setFromRGB({
186
+ r: clamp(+this.rInput.value),
187
+ g: clamp(+this.gInput.value),
188
+ b: clamp(+this.bInput.value),
189
+ });
190
+ }
191
+ setFromRGB(rgb) {
192
+ const { h, s, v } = this.rgbToHsb(rgb);
193
+ this.hue = h;
194
+ this.saturation = s;
195
+ this.brightness = v;
196
+ this.hueSlider.value = String(this.hue);
197
+ this.drawField();
198
+ this.updateCursor();
199
+ this.updateFromHSB();
200
+ }
201
+ getValue() {
202
+ return this.rgbToHex(this.hsbToRgb(this.hue, this.saturation, this.brightness));
203
+ }
204
+ setValue(hex) {
205
+ if (!/^#[0-9a-f]{6}$/i.test(hex))
206
+ return;
207
+ this.setFromRGB(this.hexToRgb(hex));
208
+ }
209
+ destroy() {
210
+ this.abortController.abort();
211
+ this.ro.disconnect();
212
+ this.container.classList.remove('color-picker');
213
+ this.container.innerHTML = '';
214
+ }
215
+ hsbToRgb(h, s, v) {
216
+ s /= 100;
217
+ v /= 100;
218
+ const c = v * s;
219
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
220
+ const m = v - c;
221
+ let r = 0, g = 0, b = 0;
222
+ if (h < 60) {
223
+ r = c;
224
+ g = x;
225
+ b = 0;
226
+ }
227
+ else if (h < 120) {
228
+ r = x;
229
+ g = c;
230
+ b = 0;
231
+ }
232
+ else if (h < 180) {
233
+ r = 0;
234
+ g = c;
235
+ b = x;
236
+ }
237
+ else if (h < 240) {
238
+ r = 0;
239
+ g = x;
240
+ b = c;
241
+ }
242
+ else if (h < 300) {
243
+ r = x;
244
+ g = 0;
245
+ b = c;
246
+ }
247
+ else {
248
+ r = c;
249
+ g = 0;
250
+ b = x;
251
+ }
252
+ return {
253
+ r: Math.round((r + m) * 255),
254
+ g: Math.round((g + m) * 255),
255
+ b: Math.round((b + m) * 255),
256
+ };
257
+ }
258
+ rgbToHsb({ r, g, b }) {
259
+ r /= 255;
260
+ g /= 255;
261
+ b /= 255;
262
+ const max = Math.max(r, g, b);
263
+ const min = Math.min(r, g, b);
264
+ const d = max - min;
265
+ let h = 0;
266
+ if (d !== 0) {
267
+ if (max === r)
268
+ h = ((g - b) / d) % 6;
269
+ else if (max === g)
270
+ h = (b - r) / d + 2;
271
+ else
272
+ h = (r - g) / d + 4;
273
+ }
274
+ h = Math.round(h * 60);
275
+ if (h < 0)
276
+ h += 360;
277
+ return {
278
+ h,
279
+ s: max === 0 ? 0 : Math.round((d / max) * 100),
280
+ v: Math.round(max * 100),
281
+ };
282
+ }
283
+ rgbToHex({ r, g, b }) {
284
+ return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
285
+ }
286
+ hexToRgb(hex) {
287
+ const m = hex.match(/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
288
+ return { r: parseInt(m[1], 16), g: parseInt(m[2], 16), b: parseInt(m[3], 16) };
289
+ }
290
+ }
291
+ export { ColorPicker };
@@ -1,4 +1,3 @@
1
- /** Definition for a single context menu item including optional submenu. */
2
1
  interface ContextMenuItemDef {
3
2
  label: string;
4
3
  icon?: string;
@@ -12,10 +11,8 @@ type ContextMenuInput = ContextMenuItemDef | 'separator' | {
12
11
  group: string;
13
12
  };
14
13
  interface ContextMenuOptions {
15
- /** Path to the SVG sprite file, e.g. `'svg-icons/icons.svg'`. Required to render icons. */
16
14
  spritePath?: string;
17
15
  }
18
- /** Right-click context menu with keyboard navigation and nested submenu support. */
19
16
  declare class ContextMenu {
20
17
  private items;
21
18
  private targets;
@@ -1,4 +1,3 @@
1
- /** Right-click context menu with keyboard navigation and nested submenu support. */
2
1
  class ContextMenu {
3
2
  items;
4
3
  targets;
@@ -148,8 +147,6 @@ class ContextMenu {
148
147
  const rect = li.getBoundingClientRect();
149
148
  return rect.right + submenuEl.offsetWidth > window.innerWidth;
150
149
  };
151
- // Delay timer prevents the submenu closing when mouse travels from
152
- // item → submenu (mouseleave fires before mouseenter on the submenu)
153
150
  let closeTimer = null;
154
151
  const openSub = () => {
155
152
  if (closeTimer) {
@@ -1,9 +1,7 @@
1
- /** Localised day and month names for the DatePicker. */
2
1
  interface DatePickerLocales {
3
2
  days: string[];
4
3
  months: string[];
5
4
  }
6
- /** Configuration options for the DatePicker. */
7
5
  interface DatePickerOptions {
8
6
  mode?: 'single' | 'range';
9
7
  startDay?: number;
@@ -12,12 +10,10 @@ interface DatePickerOptions {
12
10
  format?: (date: Date) => string;
13
11
  onSelect?: (date: Date | DateRange) => void;
14
12
  }
15
- /** A date range with optional start and end dates. */
16
13
  interface DateRange {
17
14
  start: Date | null;
18
15
  end: Date | null;
19
16
  }
20
- /** Calendar-based date (or date-range) picker that attaches to an input element. */
21
17
  declare class DatePicker {
22
18
  private input;
23
19
  private options;
package/js/datepicker.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { computePosition } from './position.js';
2
- /** Calendar-based date (or date-range) picker that attaches to an input element. */
3
2
  class DatePicker {
4
3
  input;
5
4
  options;
package/js/docs-nav.js CHANGED
@@ -16,6 +16,7 @@ const NAV = [
16
16
  items: [
17
17
  { label: 'Inputs & Forms', href: 'forms/inputs.html' },
18
18
  { label: 'Date Picker', href: 'forms/datepicker.html' },
19
+ { label: 'Color Picker', href: 'forms/color-picker.html' },
19
20
  { label: 'Range Slider', href: 'forms/range-slider.html' },
20
21
  { label: 'File Uploader', href: 'forms/file-uploader.html' },
21
22
  ],
package/js/dropdown.d.ts CHANGED
@@ -1,14 +1,11 @@
1
- /** Configuration options for a Dropdown instance. */
2
1
  interface DropdownOptions {
3
2
  closeOnSelect?: boolean;
4
3
  allowMultipleOpen?: boolean;
5
4
  }
6
- /** Event detail payload for the `dropdown-select` custom event. */
7
5
  interface DropdownSelectDetail {
8
6
  text: string;
9
7
  element: HTMLElement;
10
8
  }
11
- /** Hierarchical dropdown menu with optional multi-open and close-on-select behaviour. */
12
9
  declare class Dropdown {
13
10
  private container;
14
11
  private trigger;
@@ -28,4 +25,4 @@ declare class Dropdown {
28
25
  private handleSelection;
29
26
  destroy(): void;
30
27
  }
31
- export { Dropdown, DropdownSelectDetail };
28
+ export { Dropdown, type DropdownSelectDetail };
package/js/dropdown.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { bestPlacement } from './position.js';
2
- /** Hierarchical dropdown menu with optional multi-open and close-on-select behaviour. */
3
2
  class Dropdown {
4
3
  container;
5
4
  trigger;
package/js/editor.d.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  interface EditorOptions {
2
- /** Hides the entire side panel (code/preview) permanently. Safe to use
3
- * without [data-editor="code"], [data-editor="preview"], or [data-editor="side-panel"] in the DOM. */
4
2
  simple?: boolean;
5
- /** Root container element or CSS selector. Required when using multiple editors on one page. */
6
3
  root?: string | HTMLElement;
7
4
  }
8
- /** Rich-text editor built on contenteditable with undo/redo and code/preview panels. */
9
5
  declare class Editor {
10
6
  private readonly root;
11
7
  private readonly editable;
package/js/editor.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { sanitizeHtml } from './utils.js';
2
- /** Rich-text editor built on contenteditable with undo/redo and code/preview panels. */
3
2
  class Editor {
4
3
  root;
5
4
  editable;
@@ -111,17 +110,16 @@ class Editor {
111
110
  }, sig);
112
111
  if (this.code) {
113
112
  const code = this.code;
114
- const codeActions = this.qAll('.code-actions button');
115
- codeActions[0]?.addEventListener('click', () => {
113
+ this.q('[data-editor-action="apply-code"]')?.addEventListener('click', () => {
116
114
  this.editable.innerHTML = sanitizeHtml(code.value);
117
115
  this.onContentChange();
118
116
  }, sig);
119
- codeActions[1]?.addEventListener('click', () => {
117
+ this.q('[data-editor-action="sanitize-code"]')?.addEventListener('click', () => {
120
118
  code.value = sanitizeHtml(code.value);
121
119
  this.editable.innerHTML = code.value;
122
120
  this.onContentChange();
123
121
  }, sig);
124
- codeActions[2]?.addEventListener('click', () => {
122
+ this.q('[data-editor-action="minify-code"]')?.addEventListener('click', () => {
125
123
  code.value = code.value
126
124
  .replace(/\n/g, '')
127
125
  .replace(/>\s+</g, '><')
@@ -1,21 +1,17 @@
1
- /** Event detail payload for the `upload-completed` custom event. */
2
1
  interface UploadCompletedDetail {
3
2
  fileCount: number;
4
3
  files: File[];
5
4
  results: PromiseSettledResult<unknown>[];
6
5
  }
7
- /** Event detail payload for the `file-validation-error` custom event. */
8
6
  interface FileValidationErrorDetail {
9
7
  file: File;
10
8
  reason: 'size' | 'type';
11
9
  }
12
- /** Configuration options for the FileUploader. */
13
10
  interface FileUploaderConfig {
14
11
  uploadUrl?: string;
15
12
  maxFileSize?: number;
16
13
  allowedTypes?: string[];
17
14
  }
18
- /** Drag-and-drop file uploader with progress tracking and XHR-based uploads. */
19
15
  declare class FileUploader {
20
16
  private container;
21
17
  private dropZone;
@@ -1,5 +1,4 @@
1
1
  import { escapeHtml } from './utils.js';
2
- /** Drag-and-drop file uploader with progress tracking and XHR-based uploads. */
3
2
  class FileUploader {
4
3
  container;
5
4
  dropZone;
@@ -1,4 +1,3 @@
1
- /** Configuration options for the FlyoutMenu. */
2
1
  interface FlyoutMenuOptions {
3
2
  triggerSelector?: string;
4
3
  menuSelector?: string;
@@ -12,7 +11,6 @@ interface FlyoutMenuOptions {
12
11
  enableHeader?: boolean;
13
12
  enableFooter?: boolean;
14
13
  }
15
- /** Off-canvas flyout navigation with nested submenu support. */
16
14
  declare class FlyoutMenu {
17
15
  private options;
18
16
  private menuTrigger;
package/js/flyout-menu.js CHANGED
@@ -1,4 +1,3 @@
1
- /** Off-canvas flyout navigation with nested submenu support. */
2
1
  class FlyoutMenu {
3
2
  options;
4
3
  menuTrigger;
package/js/gallery.d.ts CHANGED
@@ -1,10 +1,8 @@
1
- /** A single image record for MasonryGallery. */
2
1
  interface ImageData {
3
2
  src: string;
4
3
  title: string;
5
4
  desc: string;
6
5
  }
7
- /** Configuration options for MasonryGallery. */
8
6
  interface MasonryGalleryOptions {
9
7
  fetchFunction: () => Promise<ImageData[]>;
10
8
  minColumnWidth?: number;
@@ -13,7 +11,6 @@ interface MasonryGalleryOptions {
13
11
  reload?: number;
14
12
  enableLightbox?: boolean;
15
13
  }
16
- /** Infinite-scroll masonry gallery that distributes images across dynamically sized columns. */
17
14
  declare class MasonryGallery {
18
15
  private container;
19
16
  private readonly loader;
@@ -38,4 +35,4 @@ declare class MasonryGallery {
38
35
  private addToShortestColumn;
39
36
  destroy(): void;
40
37
  }
41
- export { MasonryGallery, ImageData };
38
+ export { MasonryGallery, type ImageData };
package/js/gallery.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { escapeHtml } from './utils.js';
2
2
  import { Lightbox } from './lightbox.js';
3
- /** Infinite-scroll masonry gallery that distributes images across dynamically sized columns. */
4
3
  class MasonryGallery {
5
4
  container;
6
5
  loader;
@@ -17,11 +16,11 @@ class MasonryGallery {
17
16
  throw new Error(`Container with id "${containerId}" not found`);
18
17
  }
19
18
  this.container = container;
20
- this.loader = document.querySelector(options.loaderSelector || ".loader");
19
+ this.loader = document.querySelector(options.loaderSelector ?? '.loader');
21
20
  this.options = {
22
21
  minColumnWidth: options.minColumnWidth ?? 250,
23
22
  scrollThreshold: options.scrollThreshold ?? 100,
24
- reload: 2,
23
+ reload: options.reload ?? 2,
25
24
  fetchFunction: options.fetchFunction,
26
25
  enableLightbox: options.enableLightbox ?? true,
27
26
  };
@@ -54,11 +53,11 @@ class MasonryGallery {
54
53
  this.abortController = new AbortController();
55
54
  const sig = this.abortController.signal;
56
55
  let resizeTimeout;
57
- window.addEventListener("resize", () => {
56
+ window.addEventListener('resize', () => {
58
57
  clearTimeout(resizeTimeout);
59
58
  resizeTimeout = setTimeout(() => this.reLayout(), 200);
60
59
  }, { signal: sig });
61
- window.addEventListener("scroll", this.handleScroll, { passive: true, signal: sig });
60
+ window.addEventListener('scroll', this.handleScroll, { passive: true, signal: sig });
62
61
  }
63
62
  reLayout() {
64
63
  const items = this.columns.flatMap(col => Array.from(col.children));
@@ -84,7 +83,7 @@ class MasonryGallery {
84
83
  if (!isAutoFill)
85
84
  this.reloaded++;
86
85
  if (this.options.reload > 0 && this.reloaded > this.options.reload) {
87
- console.warn("Maximum reload limit reached.");
86
+ console.warn('Maximum reload limit reached.');
88
87
  return;
89
88
  }
90
89
  if (this.isFetching)
@@ -96,7 +95,7 @@ class MasonryGallery {
96
95
  this.renderImages(newImages);
97
96
  }
98
97
  catch (error) {
99
- throw new Error("Error loading images: " + error);
98
+ console.error('MasonryGallery: error loading images', error);
100
99
  }
101
100
  finally {
102
101
  this.isFetching = false;
@@ -111,16 +110,12 @@ class MasonryGallery {
111
110
  }
112
111
  toggleLoader(show) {
113
112
  if (this.loader) {
114
- this.loader.classList.toggle("hidden", !show);
113
+ this.loader.classList.toggle('hidden', !show);
115
114
  }
116
115
  }
117
116
  renderImages(imageDataList) {
118
117
  const startIndex = this.allImages.length;
119
118
  this.allImages.push(...imageDataList);
120
- // Sort columns by current height so we start filling from the shortest.
121
- // Then round-robin across them — this avoids the problem where unloaded
122
- // images (0 height) cause offsetHeight-based distribution to pile all
123
- // new items into a single column.
124
119
  const sorted = [...this.columns].sort((a, b) => a.offsetHeight - b.offsetHeight);
125
120
  imageDataList.forEach((data, i) => {
126
121
  const item = this.createCard(data);
@@ -140,28 +135,28 @@ class MasonryGallery {
140
135
  const col = sorted[i % sorted.length];
141
136
  col.appendChild(item);
142
137
  requestAnimationFrame(() => {
143
- const img = item.querySelector("img");
138
+ const img = item.querySelector('img');
144
139
  if (img) {
145
- img.addEventListener("load", () => img.classList.add("loaded"), {
140
+ img.addEventListener('load', () => img.classList.add('loaded'), {
146
141
  once: true,
147
142
  });
148
143
  if (img.complete) {
149
- img.classList.add("loaded");
144
+ img.classList.add('loaded');
150
145
  }
151
146
  }
152
147
  });
153
148
  });
154
149
  }
155
150
  createCard(data) {
156
- const div = document.createElement("div");
157
- div.className = "masonry-item";
151
+ const div = document.createElement('div');
152
+ div.className = 'masonry-item';
158
153
  div.innerHTML = `
159
- <img src="${escapeHtml(data.src)}" alt="${escapeHtml(data.title)}" loading="lazy">
160
- <div class="masonry-item-info">
161
- <h3 class="masonry-item-title">${escapeHtml(data.title)}</h3>
162
- <p class="masonry-item-desc">${escapeHtml(data.desc)}</p>
163
- </div>
164
- `;
154
+ <img src="${escapeHtml(data.src)}" alt="${escapeHtml(data.title)}" loading="lazy">
155
+ <div class="masonry-item-info">
156
+ <h3 class="masonry-item-title">${escapeHtml(data.title)}</h3>
157
+ <p class="masonry-item-desc">${escapeHtml(data.desc)}</p>
158
+ </div>
159
+ `;
165
160
  return div;
166
161
  }
167
162
  addToShortestColumn(element) {
@@ -1,15 +1,12 @@
1
- /** A single subgroup item within a GroupPicker group. */
2
1
  interface SubgroupData {
3
2
  id: string;
4
3
  label: string;
5
4
  }
6
- /** A group with an optional list of subgroups for GroupPicker. */
7
5
  interface GroupData {
8
6
  id: string;
9
7
  label: string;
10
8
  subgroups?: SubgroupData[];
11
9
  }
12
- /** The current selection state returned by GroupPicker. */
13
10
  interface GroupPickerSelection {
14
11
  parentGroups: string[];
15
12
  subgroups: {
@@ -17,7 +14,6 @@ interface GroupPickerSelection {
17
14
  subgroupId: string;
18
15
  }[];
19
16
  }
20
- /** Configuration options for the GroupPicker component. */
21
17
  interface GroupPickerOptions {
22
18
  onSelectionChange?: (selection: GroupPickerSelection) => void;
23
19
  searchPlaceholder?: string;
@@ -26,7 +22,6 @@ interface GroupPickerOptions {
26
22
  emptyLabel?: string;
27
23
  selectionPlaceholder?: string;
28
24
  }
29
- /** Searchable picker for selecting groups and their subgroups. */
30
25
  declare class GroupPicker {
31
26
  private container;
32
27
  private data;