@dodlhuat/basix 1.2.2 → 1.2.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 (55) hide show
  1. package/README.md +42 -0
  2. package/css/lightbox.scss +272 -0
  3. package/css/style.css +263 -3
  4. package/css/style.css.map +1 -1
  5. package/css/style.scss +1 -0
  6. package/js/bottom-sheet.js +4 -3
  7. package/js/bottom-sheet.ts +5 -3
  8. package/js/calendar.js +9 -5
  9. package/js/calendar.ts +7 -2
  10. package/js/carousel.js +15 -11
  11. package/js/carousel.ts +16 -11
  12. package/js/chart.js +4 -4
  13. package/js/chart.ts +5 -3
  14. package/js/datepicker.js +11 -3
  15. package/js/datepicker.ts +13 -3
  16. package/js/docs-nav.js +1 -0
  17. package/js/editor.js +28 -20
  18. package/js/editor.ts +28 -20
  19. package/js/file-uploader.js +6 -10
  20. package/js/file-uploader.ts +7 -11
  21. package/js/flyout-menu.js +8 -2
  22. package/js/flyout-menu.ts +7 -2
  23. package/js/gallery.js +6 -13
  24. package/js/gallery.ts +8 -16
  25. package/js/group-picker.js +10 -7
  26. package/js/group-picker.ts +11 -7
  27. package/js/lightbox.js +277 -0
  28. package/js/lightbox.ts +331 -0
  29. package/js/modal.js +5 -4
  30. package/js/modal.ts +6 -4
  31. package/js/popover.js +4 -2
  32. package/js/popover.ts +4 -2
  33. package/js/push-menu.js +3 -2
  34. package/js/push-menu.ts +4 -2
  35. package/js/scrollbar.js +31 -23
  36. package/js/scrollbar.ts +36 -26
  37. package/js/select.js +23 -9
  38. package/js/select.ts +29 -11
  39. package/js/stepper.js +5 -1
  40. package/js/stepper.ts +6 -1
  41. package/js/table.js +8 -3
  42. package/js/table.ts +9 -3
  43. package/js/timepicker.js +20 -21
  44. package/js/timepicker.ts +23 -21
  45. package/js/toast.js +3 -7
  46. package/js/toast.ts +4 -8
  47. package/js/tooltip.js +13 -4
  48. package/js/tooltip.ts +16 -4
  49. package/js/tree.js +4 -0
  50. package/js/tree.ts +5 -0
  51. package/js/utils.js +29 -1
  52. package/js/utils.ts +36 -1
  53. package/js/virtual-dropdown.js +4 -8
  54. package/js/virtual-dropdown.ts +5 -9
  55. package/package.json +1 -1
package/js/modal.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { sanitizeHtml } from './utils.js';
2
+
1
3
  const CLOSE_ICON = '<div class="icon icon-close close"></div>';
2
4
 
3
5
  type ModalType = 'default' | 'success' | 'error' | 'warning' | 'info';
@@ -81,7 +83,7 @@ class Modal {
81
83
  }
82
84
 
83
85
  public hide(): void {
84
- const wrapper = document.querySelector('.modal-wrapper');
86
+ const wrapper = this.modalWrapper;
85
87
  if (!wrapper) return;
86
88
 
87
89
  // Remove event listeners
@@ -123,13 +125,13 @@ class Modal {
123
125
 
124
126
  if (this.header !== undefined) {
125
127
  const headerClass = `header ${this.type}-bg`;
126
- parts.push(`<div class="${headerClass}">${this.header}</div>`);
128
+ parts.push(`<div class="${headerClass}">${sanitizeHtml(this.header)}</div>`);
127
129
  }
128
130
 
129
- parts.push(this.content);
131
+ parts.push(sanitizeHtml(this.content));
130
132
 
131
133
  if (this.footer !== undefined) {
132
- parts.push(`<div class="footer">${this.footer}</div>`);
134
+ parts.push(`<div class="footer">${sanitizeHtml(this.footer)}</div>`);
133
135
  }
134
136
 
135
137
  parts.push('</div>');
package/js/popover.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { computePosition } from './position.js';
2
+ import { sanitizeHtml } from './utils.js';
2
3
  // Must match $arrow in popover.scss
3
4
  const ARROW_SIZE = 6;
4
5
  class Popover {
@@ -119,9 +120,10 @@ class Popover {
119
120
  // Wrap plain content in .popover-body so it gets proper padding.
120
121
  // Skip wrapping when content already uses structured popover elements.
121
122
  const hasStructure = /class="popover-(header|body|footer|menu)/.test(this.opts.content);
123
+ const safeContent = sanitizeHtml(this.opts.content);
122
124
  el.innerHTML = hasStructure
123
- ? this.opts.content
124
- : `<div class="popover-body">${this.opts.content}</div>`;
125
+ ? safeContent
126
+ : `<div class="popover-body">${safeContent}</div>`;
125
127
  this.trigger.setAttribute('aria-expanded', 'true');
126
128
  this.trigger.setAttribute('aria-controls', id);
127
129
  return el;
package/js/popover.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { computePosition } from './position.js';
2
2
  import type { Placement } from './position.js';
3
+ import { sanitizeHtml } from './utils.js';
3
4
 
4
5
  type PopoverPlacement = Placement | 'auto';
5
6
  type PopoverAlign = 'start' | 'center' | 'end';
@@ -142,9 +143,10 @@ class Popover {
142
143
  // Wrap plain content in .popover-body so it gets proper padding.
143
144
  // Skip wrapping when content already uses structured popover elements.
144
145
  const hasStructure = /class="popover-(header|body|footer|menu)/.test(this.opts.content);
146
+ const safeContent = sanitizeHtml(this.opts.content);
145
147
  el.innerHTML = hasStructure
146
- ? this.opts.content
147
- : `<div class="popover-body">${this.opts.content}</div>`;
148
+ ? safeContent
149
+ : `<div class="popover-body">${safeContent}</div>`;
148
150
 
149
151
  this.trigger.setAttribute('aria-expanded', 'true');
150
152
  this.trigger.setAttribute('aria-controls', id);
package/js/push-menu.js CHANGED
@@ -9,7 +9,8 @@ class PushMenu {
9
9
  throw new Error('PushMenu: Required elements not found (.navigation, .push-content)');
10
10
  }
11
11
  this.buildPanels();
12
- this.elements.navigation.addEventListener('change', this.handleNavigationChange.bind(this));
12
+ this.boundHandleNavigationChange = this.handleNavigationChange.bind(this);
13
+ this.elements.navigation.addEventListener('change', this.boundHandleNavigationChange);
13
14
  this.elements.backdrop?.addEventListener('click', this.handleBackdropClick);
14
15
  this.initialized = true;
15
16
  }
@@ -179,7 +180,7 @@ class PushMenu {
179
180
  static destroy() {
180
181
  if (!this.initialized)
181
182
  return;
182
- this.elements.navigation?.removeEventListener('change', this.handleNavigationChange);
183
+ this.elements.navigation?.removeEventListener('change', this.boundHandleNavigationChange);
183
184
  this.elements.content?.removeEventListener('click', this.clickNav);
184
185
  this.elements.backdrop?.removeEventListener('click', this.handleBackdropClick);
185
186
  this.close();
package/js/push-menu.ts CHANGED
@@ -19,6 +19,7 @@ class PushMenu {
19
19
 
20
20
  private static initialized = false;
21
21
  private static panelStack: HTMLElement[] = [];
22
+ private static boundHandleNavigationChange: () => void;
22
23
 
23
24
  public static init(): void {
24
25
  if (this.initialized) {
@@ -34,7 +35,8 @@ class PushMenu {
34
35
 
35
36
  this.buildPanels();
36
37
 
37
- this.elements.navigation.addEventListener('change', this.handleNavigationChange.bind(this));
38
+ this.boundHandleNavigationChange = this.handleNavigationChange.bind(this);
39
+ this.elements.navigation.addEventListener('change', this.boundHandleNavigationChange);
38
40
  this.elements.backdrop?.addEventListener('click', this.handleBackdropClick);
39
41
 
40
42
  this.initialized = true;
@@ -252,7 +254,7 @@ class PushMenu {
252
254
  public static destroy(): void {
253
255
  if (!this.initialized) return;
254
256
 
255
- this.elements.navigation?.removeEventListener('change', this.handleNavigationChange);
257
+ this.elements.navigation?.removeEventListener('change', this.boundHandleNavigationChange);
256
258
  this.elements.content?.removeEventListener('click', this.clickNav);
257
259
  this.elements.backdrop?.removeEventListener('click', this.handleBackdropClick);
258
260
 
package/js/scrollbar.js CHANGED
@@ -1,20 +1,9 @@
1
1
  class Scrollbar {
2
- constructor(elementOrSelector) {
2
+ constructor(container) {
3
3
  this.dragging = false;
4
4
  this.activePointerId = null;
5
5
  this.startPointerY = 0;
6
6
  this.startThumbTop = 0;
7
- const container = typeof elementOrSelector === 'string'
8
- ? document.querySelector(elementOrSelector)
9
- : elementOrSelector;
10
- if (!container) {
11
- throw new Error(`Scrollbar: Element not found for selector "${elementOrSelector}"`);
12
- }
13
- // Return existing instance if already initialized
14
- const existingInstance = Scrollbar.instances.get(container);
15
- if (existingInstance) {
16
- return existingInstance;
17
- }
18
7
  this.container = container;
19
8
  // Query and validate required elements
20
9
  const elements = this.getRequiredElements(container);
@@ -37,7 +26,8 @@ class Scrollbar {
37
26
  // Initialize
38
27
  this.attachEventListeners();
39
28
  Scrollbar.instances.set(container, this);
40
- // Install global listeners once for all instances
29
+ // Track instances and install global listeners once for all
30
+ Scrollbar.instanceCount++;
41
31
  if (!Scrollbar.globalListenersInstalled) {
42
32
  Scrollbar.installGlobalListeners();
43
33
  }
@@ -64,16 +54,17 @@ class Scrollbar {
64
54
  return Math.max(absoluteMin, parsed || defaultMin);
65
55
  }
66
56
  static installGlobalListeners() {
67
- // Route pointer events to the active scrollbar instance
57
+ const ac = new AbortController();
58
+ Scrollbar.globalListenerAbortController = ac;
68
59
  document.addEventListener('pointermove', (e) => {
69
60
  Scrollbar.activeInstance?.boundPointerMove(e);
70
- }, { passive: false });
61
+ }, { passive: false, signal: ac.signal });
71
62
  document.addEventListener('pointerup', (e) => {
72
63
  Scrollbar.activeInstance?.boundPointerUp(e);
73
- });
64
+ }, { signal: ac.signal });
74
65
  document.addEventListener('pointercancel', (e) => {
75
66
  Scrollbar.activeInstance?.boundPointerUp(e);
76
- });
67
+ }, { signal: ac.signal });
77
68
  Scrollbar.globalListenersInstalled = true;
78
69
  }
79
70
  attachEventListeners() {
@@ -209,24 +200,41 @@ class Scrollbar {
209
200
  if (Scrollbar.activeInstance === this) {
210
201
  Scrollbar.activeInstance = null;
211
202
  }
203
+ // Uninstall global listeners when last instance is destroyed
204
+ Scrollbar.instanceCount--;
205
+ if (Scrollbar.instanceCount === 0) {
206
+ Scrollbar.globalListenerAbortController?.abort();
207
+ Scrollbar.globalListenerAbortController = null;
208
+ Scrollbar.globalListenersInstalled = false;
209
+ }
212
210
  }
213
211
  // Static factory methods
212
+ static create(elementOrSelector) {
213
+ const container = typeof elementOrSelector === 'string'
214
+ ? document.querySelector(elementOrSelector)
215
+ : elementOrSelector;
216
+ if (!container) {
217
+ throw new Error(`Scrollbar: Element not found for selector "${elementOrSelector}"`);
218
+ }
219
+ const existing = Scrollbar.instances.get(container);
220
+ if (existing)
221
+ return existing;
222
+ return new Scrollbar(container);
223
+ }
214
224
  static initAll(selector) {
215
225
  const containers = document.querySelectorAll(selector);
216
- return Array.from(containers).map(container => new Scrollbar(container));
226
+ return Array.from(containers).map(container => Scrollbar.create(container));
217
227
  }
218
228
  static initOne(elementOrSelector) {
219
- return new Scrollbar(elementOrSelector);
229
+ return Scrollbar.create(elementOrSelector);
220
230
  }
221
231
  static getInstance(container) {
222
232
  return Scrollbar.instances.get(container);
223
233
  }
224
- static destroyAll() {
225
- // Note: WeakMap doesn't support iteration, so this is a no-op
226
- // Individual instances should be destroyed by calling destroy()
227
- }
228
234
  }
229
235
  Scrollbar.instances = new WeakMap();
230
236
  Scrollbar.activeInstance = null;
231
237
  Scrollbar.globalListenersInstalled = false;
238
+ Scrollbar.instanceCount = 0;
239
+ Scrollbar.globalListenerAbortController = null;
232
240
  export { Scrollbar };
package/js/scrollbar.ts CHANGED
@@ -9,6 +9,8 @@ class Scrollbar {
9
9
  private static readonly instances = new WeakMap<HTMLElement, Scrollbar>();
10
10
  private static activeInstance: Scrollbar | null = null;
11
11
  private static globalListenersInstalled = false;
12
+ private static instanceCount = 0;
13
+ private static globalListenerAbortController: AbortController | null = null;
12
14
 
13
15
  private readonly container!: HTMLElement;
14
16
  private readonly viewport!: HTMLElement;
@@ -32,21 +34,7 @@ class Scrollbar {
32
34
  private readonly boundUpdateThumb!: () => void;
33
35
  private readonly boundContainerWheel!: (e: WheelEvent) => void;
34
36
 
35
- constructor(elementOrSelector: string | HTMLElement) {
36
- const container = typeof elementOrSelector === 'string'
37
- ? document.querySelector<HTMLElement>(elementOrSelector)
38
- : elementOrSelector;
39
-
40
- if (!container) {
41
- throw new Error(`Scrollbar: Element not found for selector "${elementOrSelector}"`);
42
- }
43
-
44
- // Return existing instance if already initialized
45
- const existingInstance = Scrollbar.instances.get(container);
46
- if (existingInstance) {
47
- return existingInstance as any;
48
- }
49
-
37
+ private constructor(container: HTMLElement) {
50
38
  this.container = container;
51
39
 
52
40
  // Query and validate required elements
@@ -75,7 +63,8 @@ class Scrollbar {
75
63
  this.attachEventListeners();
76
64
  Scrollbar.instances.set(container, this);
77
65
 
78
- // Install global listeners once for all instances
66
+ // Track instances and install global listeners once for all
67
+ Scrollbar.instanceCount++;
79
68
  if (!Scrollbar.globalListenersInstalled) {
80
69
  Scrollbar.installGlobalListeners();
81
70
  }
@@ -112,18 +101,20 @@ class Scrollbar {
112
101
  }
113
102
 
114
103
  private static installGlobalListeners(): void {
115
- // Route pointer events to the active scrollbar instance
104
+ const ac = new AbortController();
105
+ Scrollbar.globalListenerAbortController = ac;
106
+
116
107
  document.addEventListener('pointermove', (e: PointerEvent) => {
117
108
  Scrollbar.activeInstance?.boundPointerMove(e);
118
- }, { passive: false });
109
+ }, { passive: false, signal: ac.signal });
119
110
 
120
111
  document.addEventListener('pointerup', (e: PointerEvent) => {
121
112
  Scrollbar.activeInstance?.boundPointerUp(e);
122
- });
113
+ }, { signal: ac.signal });
123
114
 
124
115
  document.addEventListener('pointercancel', (e: PointerEvent) => {
125
116
  Scrollbar.activeInstance?.boundPointerUp(e);
126
- });
117
+ }, { signal: ac.signal });
127
118
 
128
119
  Scrollbar.globalListenersInstalled = true;
129
120
  }
@@ -300,26 +291,45 @@ class Scrollbar {
300
291
  if (Scrollbar.activeInstance === this) {
301
292
  Scrollbar.activeInstance = null;
302
293
  }
294
+
295
+ // Uninstall global listeners when last instance is destroyed
296
+ Scrollbar.instanceCount--;
297
+ if (Scrollbar.instanceCount === 0) {
298
+ Scrollbar.globalListenerAbortController?.abort();
299
+ Scrollbar.globalListenerAbortController = null;
300
+ Scrollbar.globalListenersInstalled = false;
301
+ }
303
302
  }
304
303
 
305
304
  // Static factory methods
305
+ public static create(elementOrSelector: string | HTMLElement): Scrollbar {
306
+ const container = typeof elementOrSelector === 'string'
307
+ ? document.querySelector<HTMLElement>(elementOrSelector)
308
+ : elementOrSelector;
309
+
310
+ if (!container) {
311
+ throw new Error(`Scrollbar: Element not found for selector "${elementOrSelector}"`);
312
+ }
313
+
314
+ const existing = Scrollbar.instances.get(container);
315
+ if (existing) return existing;
316
+
317
+ return new Scrollbar(container);
318
+ }
319
+
306
320
  public static initAll(selector: string): Scrollbar[] {
307
321
  const containers = document.querySelectorAll<HTMLElement>(selector);
308
- return Array.from(containers).map(container => new Scrollbar(container));
322
+ return Array.from(containers).map(container => Scrollbar.create(container));
309
323
  }
310
324
 
311
325
  public static initOne(elementOrSelector: string | HTMLElement): Scrollbar {
312
- return new Scrollbar(elementOrSelector);
326
+ return Scrollbar.create(elementOrSelector);
313
327
  }
314
328
 
315
329
  public static getInstance(container: HTMLElement): Scrollbar | undefined {
316
330
  return Scrollbar.instances.get(container);
317
331
  }
318
332
 
319
- public static destroyAll(): void {
320
- // Note: WeakMap doesn't support iteration, so this is a no-op
321
- // Individual instances should be destroyed by calling destroy()
322
- }
323
333
  }
324
334
 
325
335
  export { Scrollbar, ScrollbarElements };
package/js/select.js CHANGED
@@ -11,7 +11,18 @@ class Select {
11
11
  if (result === null) {
12
12
  throw new Error(`Select: Failed to initialize select for "${elementOrSelector}"`);
13
13
  }
14
- this.isMultiselect = result;
14
+ this.isMultiselect = result.isMulti;
15
+ this.dropdown = result.dropdown;
16
+ this.documentClickHandler = (e) => {
17
+ if (this.dropdown && !this.dropdown.contains(e.target)) {
18
+ this.dropdown.classList.remove('open');
19
+ }
20
+ };
21
+ document.addEventListener('click', this.documentClickHandler);
22
+ }
23
+ destroy() {
24
+ document.removeEventListener('click', this.documentClickHandler);
25
+ this.dropdown?.classList.remove('open');
15
26
  }
16
27
  value() {
17
28
  if (!this.element) {
@@ -29,7 +40,16 @@ class Select {
29
40
  if (!element) {
30
41
  return null;
31
42
  }
32
- return Select.initElement(element);
43
+ const result = Select.initElement(element);
44
+ if (!result)
45
+ return null;
46
+ // Static init path: add document listener without lifecycle management
47
+ document.addEventListener('click', (e) => {
48
+ if (!result.dropdown.contains(e.target)) {
49
+ result.dropdown.classList.remove('open');
50
+ }
51
+ });
52
+ return result.isMulti;
33
53
  }
34
54
  static initElement(element) {
35
55
  if (!Select.transformSelect(element)) {
@@ -74,13 +94,7 @@ class Select {
74
94
  dropdown.classList.remove('open');
75
95
  });
76
96
  }
77
- // Close dropdown when clicking outside
78
- document.addEventListener('click', (e) => {
79
- if (!dropdown.contains(e.target)) {
80
- dropdown.classList.remove('open');
81
- }
82
- });
83
- return isMulti;
97
+ return { isMulti, dropdown };
84
98
  }
85
99
  static closeAllDropdowns(exceptDropdown) {
86
100
  document.querySelectorAll('.dropdown').forEach(dropdown => {
package/js/select.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  class Select {
2
2
  private readonly element: HTMLSelectElement;
3
3
  private readonly isMultiselect: boolean;
4
+ private readonly dropdown: HTMLElement | null;
5
+ private readonly documentClickHandler: (e: Event) => void;
4
6
 
5
7
  constructor(elementOrSelector: string | HTMLSelectElement) {
6
8
  const element = typeof elementOrSelector === 'string'
@@ -18,7 +20,20 @@ class Select {
18
20
  throw new Error(`Select: Failed to initialize select for "${elementOrSelector}"`);
19
21
  }
20
22
 
21
- this.isMultiselect = result;
23
+ this.isMultiselect = result.isMulti;
24
+ this.dropdown = result.dropdown;
25
+
26
+ this.documentClickHandler = (e: Event) => {
27
+ if (this.dropdown && !this.dropdown.contains(e.target as Node)) {
28
+ this.dropdown.classList.remove('open');
29
+ }
30
+ };
31
+ document.addEventListener('click', this.documentClickHandler);
32
+ }
33
+
34
+ public destroy(): void {
35
+ document.removeEventListener('click', this.documentClickHandler);
36
+ this.dropdown?.classList.remove('open');
22
37
  }
23
38
 
24
39
  public value(): string | string[] | undefined {
@@ -42,10 +57,20 @@ class Select {
42
57
  return null;
43
58
  }
44
59
 
45
- return Select.initElement(element);
60
+ const result = Select.initElement(element);
61
+ if (!result) return null;
62
+
63
+ // Static init path: add document listener without lifecycle management
64
+ document.addEventListener('click', (e: Event) => {
65
+ if (!result.dropdown.contains(e.target as Node)) {
66
+ result.dropdown.classList.remove('open');
67
+ }
68
+ });
69
+
70
+ return result.isMulti;
46
71
  }
47
72
 
48
- private static initElement(element: HTMLSelectElement): boolean | null {
73
+ private static initElement(element: HTMLSelectElement): { isMulti: boolean; dropdown: HTMLElement } | null {
49
74
  if (!Select.transformSelect(element)) {
50
75
  return null;
51
76
  }
@@ -98,14 +123,7 @@ class Select {
98
123
  });
99
124
  }
100
125
 
101
- // Close dropdown when clicking outside
102
- document.addEventListener('click', (e: Event) => {
103
- if (!dropdown.contains(e.target as Node)) {
104
- dropdown.classList.remove('open');
105
- }
106
- });
107
-
108
- return isMulti;
126
+ return { isMulti, dropdown };
109
127
  }
110
128
 
111
129
  private static closeAllDropdowns(exceptDropdown?: HTMLElement): void {
package/js/stepper.js CHANGED
@@ -1,5 +1,6 @@
1
1
  class Stepper {
2
2
  constructor(elementOrSelector, options = {}) {
3
+ this.abortController = new AbortController();
3
4
  const element = typeof elementOrSelector === 'string'
4
5
  ? document.querySelector(elementOrSelector)
5
6
  : elementOrSelector;
@@ -17,7 +18,7 @@ class Stepper {
17
18
  if (options.clickable) {
18
19
  this.container.classList.add('stepper-clickable');
19
20
  this.steps.forEach((step, i) => {
20
- step.addEventListener('click', () => this.goTo(i));
21
+ step.addEventListener('click', () => this.goTo(i), { signal: this.abortController.signal });
21
22
  });
22
23
  }
23
24
  this.render();
@@ -76,5 +77,8 @@ class Stepper {
76
77
  isLast() {
77
78
  return this.current === this.steps.length - 1;
78
79
  }
80
+ destroy() {
81
+ this.abortController.abort();
82
+ }
79
83
  }
80
84
  export { Stepper };
package/js/stepper.ts CHANGED
@@ -10,6 +10,7 @@ class Stepper {
10
10
  private connectors: HTMLElement[];
11
11
  private current: number;
12
12
  private readonly onChange?: (current: number, previous: number) => void;
13
+ private abortController = new AbortController();
13
14
 
14
15
  constructor(elementOrSelector: string | HTMLElement, options: StepperOptions = {}) {
15
16
  const element = typeof elementOrSelector === 'string'
@@ -32,7 +33,7 @@ class Stepper {
32
33
  if (options.clickable) {
33
34
  this.container.classList.add('stepper-clickable');
34
35
  this.steps.forEach((step, i) => {
35
- step.addEventListener('click', () => this.goTo(i));
36
+ step.addEventListener('click', () => this.goTo(i), { signal: this.abortController.signal });
36
37
  });
37
38
  }
38
39
 
@@ -99,6 +100,10 @@ class Stepper {
99
100
  public isLast(): boolean {
100
101
  return this.current === this.steps.length - 1;
101
102
  }
103
+
104
+ public destroy(): void {
105
+ this.abortController.abort();
106
+ }
102
107
  }
103
108
 
104
109
  export { Stepper, type StepperOptions };
package/js/table.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Select } from "./select.js";
2
2
  class Table {
3
3
  constructor(elementOrSelector, options = {}) {
4
+ this.abortController = new AbortController();
4
5
  const element = typeof elementOrSelector === 'string'
5
6
  ? document.querySelector(elementOrSelector)
6
7
  : elementOrSelector;
@@ -74,7 +75,7 @@ class Table {
74
75
  searchInput.className = 'search-input';
75
76
  searchInput.addEventListener('input', (e) => {
76
77
  this.handleSearch(e.target.value);
77
- });
78
+ }, { signal: this.abortController.signal });
78
79
  controlsDiv.appendChild(searchInput);
79
80
  // Page size selector
80
81
  const selectGroup = document.createElement('div');
@@ -93,7 +94,7 @@ class Table {
93
94
  });
94
95
  pageSizeSelect.addEventListener('change', (e) => {
95
96
  this.handlePageSizeChange(parseInt(e.target.value, 10));
96
- });
97
+ }, { signal: this.abortController.signal });
97
98
  this.assignUniqueId(pageSizeSelect, 'page-size-select-0');
98
99
  selectGroup.appendChild(pageSizeSelect);
99
100
  controlsDiv.appendChild(selectGroup);
@@ -117,7 +118,7 @@ class Table {
117
118
  th.dataset.key = col.key;
118
119
  if (col.sortable !== false) {
119
120
  th.classList.add('sortable');
120
- th.addEventListener('click', () => this.handleSort(col.key));
121
+ th.addEventListener('click', () => this.handleSort(col.key), { signal: this.abortController.signal });
121
122
  }
122
123
  tr.appendChild(th);
123
124
  });
@@ -355,5 +356,9 @@ class Table {
355
356
  getData() {
356
357
  return this.getFilteredAndSortedData();
357
358
  }
359
+ destroy() {
360
+ this.abortController.abort();
361
+ this.container.innerHTML = '';
362
+ }
358
363
  }
359
364
  export { Table };
package/js/table.ts CHANGED
@@ -30,6 +30,7 @@ class Table {
30
30
  private tableBody!: HTMLTableSectionElement;
31
31
  private tableHeader!: HTMLTableSectionElement;
32
32
  private paginationContainer!: HTMLDivElement;
33
+ private abortController = new AbortController();
33
34
 
34
35
  constructor(elementOrSelector: string | HTMLElement, options: TableOptions = {}) {
35
36
  const element = typeof elementOrSelector === 'string'
@@ -118,7 +119,7 @@ class Table {
118
119
  searchInput.className = 'search-input';
119
120
  searchInput.addEventListener('input', (e) => {
120
121
  this.handleSearch((e.target as HTMLInputElement).value);
121
- });
122
+ }, { signal: this.abortController.signal });
122
123
  controlsDiv.appendChild(searchInput);
123
124
 
124
125
  // Page size selector
@@ -142,7 +143,7 @@ class Table {
142
143
 
143
144
  pageSizeSelect.addEventListener('change', (e) => {
144
145
  this.handlePageSizeChange(parseInt((e.target as HTMLSelectElement).value, 10));
145
- });
146
+ }, { signal: this.abortController.signal });
146
147
 
147
148
  this.assignUniqueId(pageSizeSelect, 'page-size-select-0');
148
149
  selectGroup.appendChild(pageSizeSelect);
@@ -172,7 +173,7 @@ class Table {
172
173
 
173
174
  if (col.sortable !== false) {
174
175
  th.classList.add('sortable');
175
- th.addEventListener('click', () => this.handleSort(col.key));
176
+ th.addEventListener('click', () => this.handleSort(col.key), { signal: this.abortController.signal });
176
177
  }
177
178
 
178
179
  tr.appendChild(th);
@@ -448,6 +449,11 @@ class Table {
448
449
  public getData(): TableRow[] {
449
450
  return this.getFilteredAndSortedData();
450
451
  }
452
+
453
+ public destroy(): void {
454
+ this.abortController.abort();
455
+ this.container.innerHTML = '';
456
+ }
451
457
  }
452
458
 
453
459
  export { Table, TableRow, TableColumn, TableOptions };