@dodlhuat/basix 1.2.1 → 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 +252 -5
  2. package/css/lightbox.scss +272 -0
  3. package/css/style.css +256 -0
  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 -3
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 };
package/js/timepicker.js CHANGED
@@ -1,11 +1,18 @@
1
1
  class TimeSpanPicker {
2
- constructor(containerId, options) {
3
- const element = document.getElementById(containerId);
2
+ constructor(elementOrSelector, options) {
3
+ this.handleStartChange = () => { this.handleChange(); };
4
+ this.handleEndChange = () => { this.handleChange(); };
5
+ const element = typeof elementOrSelector === 'string'
6
+ ? (elementOrSelector.startsWith('#') || elementOrSelector.startsWith('.')
7
+ ? document.querySelector(elementOrSelector)
8
+ : document.getElementById(elementOrSelector))
9
+ : elementOrSelector;
4
10
  if (!element) {
5
- throw new Error(`Container with id "${containerId}" not found`);
11
+ throw new Error(`TimeSpanPicker: Element not found for "${elementOrSelector}"`);
6
12
  }
7
13
  this.container = element;
8
14
  this.onChange = options?.onChange;
15
+ this.uid = `tsp-${Math.random().toString(36).slice(2, 9)}`;
9
16
  this.render();
10
17
  this.startTimeInput = this.queryInput('.timespan-start');
11
18
  this.endTimeInput = this.queryInput('.timespan-end');
@@ -29,15 +36,13 @@ class TimeSpanPicker {
29
36
  return input;
30
37
  }
31
38
  render() {
39
+ const startId = `${this.uid}-start`;
40
+ const endId = `${this.uid}-end`;
32
41
  this.container.innerHTML = `
33
42
  <div class="timespan-picker">
34
43
  <div class="timespan-field timespan-field-start">
35
- <label for="timespan-start">From</label>
36
- <input
37
- type="time"
38
- class="timespan-start"
39
- id="timespan-start"
40
- />
44
+ <label for="${startId}">From</label>
45
+ <input type="time" class="timespan-start" id="${startId}"/>
41
46
  </div>
42
47
 
43
48
  <div class="timespan-center">
@@ -46,12 +51,8 @@ class TimeSpanPicker {
46
51
  </div>
47
52
 
48
53
  <div class="timespan-field timespan-field-end">
49
- <label for="timespan-end">To</label>
50
- <input
51
- type="time"
52
- class="timespan-end"
53
- id="timespan-end"
54
- />
54
+ <label for="${endId}">To</label>
55
+ <input type="time" class="timespan-end" id="${endId}"/>
55
56
  </div>
56
57
  </div>
57
58
  <div class="timespan-bar" aria-hidden="true">
@@ -60,8 +61,8 @@ class TimeSpanPicker {
60
61
  `;
61
62
  }
62
63
  attachEventListeners() {
63
- this.startTimeInput.addEventListener('change', () => this.handleChange());
64
- this.endTimeInput.addEventListener('change', () => this.handleChange());
64
+ this.startTimeInput.addEventListener('change', this.handleStartChange);
65
+ this.endTimeInput.addEventListener('change', this.handleEndChange);
65
66
  }
66
67
  toMinutes(time) {
67
68
  const [h, m] = time.split(':').map(Number);
@@ -148,10 +149,8 @@ class TimeSpanPicker {
148
149
  return !!(start && end && start < end);
149
150
  }
150
151
  destroy() {
151
- const startHandler = () => this.handleChange();
152
- const endHandler = () => this.handleChange();
153
- this.startTimeInput.removeEventListener('change', startHandler);
154
- this.endTimeInput.removeEventListener('change', endHandler);
152
+ this.startTimeInput.removeEventListener('change', this.handleStartChange);
153
+ this.endTimeInput.removeEventListener('change', this.handleEndChange);
155
154
  }
156
155
  }
157
156
  export { TimeSpanPicker };
package/js/timepicker.ts CHANGED
@@ -14,15 +14,22 @@ class TimeSpanPicker {
14
14
  private startTimeInput: HTMLInputElement;
15
15
  private endTimeInput: HTMLInputElement;
16
16
  private onChange?: (start: string, end: string) => void;
17
+ private readonly uid: string;
18
+
19
+ constructor(elementOrSelector: string | HTMLElement, options?: TimeSpanPickerOptions) {
20
+ const element = typeof elementOrSelector === 'string'
21
+ ? (elementOrSelector.startsWith('#') || elementOrSelector.startsWith('.')
22
+ ? document.querySelector<HTMLElement>(elementOrSelector)
23
+ : document.getElementById(elementOrSelector))
24
+ : elementOrSelector;
17
25
 
18
- constructor(containerId: string, options?: TimeSpanPickerOptions) {
19
- const element = document.getElementById(containerId);
20
26
  if (!element) {
21
- throw new Error(`Container with id "${containerId}" not found`);
27
+ throw new Error(`TimeSpanPicker: Element not found for "${elementOrSelector}"`);
22
28
  }
23
29
 
24
30
  this.container = element;
25
31
  this.onChange = options?.onChange;
32
+ this.uid = `tsp-${Math.random().toString(36).slice(2, 9)}`;
26
33
 
27
34
  this.render();
28
35
 
@@ -53,15 +60,13 @@ class TimeSpanPicker {
53
60
  }
54
61
 
55
62
  private render(): void {
63
+ const startId = `${this.uid}-start`;
64
+ const endId = `${this.uid}-end`;
56
65
  this.container.innerHTML = `
57
66
  <div class="timespan-picker">
58
67
  <div class="timespan-field timespan-field-start">
59
- <label for="timespan-start">From</label>
60
- <input
61
- type="time"
62
- class="timespan-start"
63
- id="timespan-start"
64
- />
68
+ <label for="${startId}">From</label>
69
+ <input type="time" class="timespan-start" id="${startId}"/>
65
70
  </div>
66
71
 
67
72
  <div class="timespan-center">
@@ -70,12 +75,8 @@ class TimeSpanPicker {
70
75
  </div>
71
76
 
72
77
  <div class="timespan-field timespan-field-end">
73
- <label for="timespan-end">To</label>
74
- <input
75
- type="time"
76
- class="timespan-end"
77
- id="timespan-end"
78
- />
78
+ <label for="${endId}">To</label>
79
+ <input type="time" class="timespan-end" id="${endId}"/>
79
80
  </div>
80
81
  </div>
81
82
  <div class="timespan-bar" aria-hidden="true">
@@ -84,9 +85,12 @@ class TimeSpanPicker {
84
85
  `;
85
86
  }
86
87
 
88
+ private readonly handleStartChange = (): void => { this.handleChange(); };
89
+ private readonly handleEndChange = (): void => { this.handleChange(); };
90
+
87
91
  private attachEventListeners(): void {
88
- this.startTimeInput.addEventListener('change', () => this.handleChange());
89
- this.endTimeInput.addEventListener('change', () => this.handleChange());
92
+ this.startTimeInput.addEventListener('change', this.handleStartChange);
93
+ this.endTimeInput.addEventListener('change', this.handleEndChange);
90
94
  }
91
95
 
92
96
  private toMinutes(time: string): number {
@@ -183,10 +187,8 @@ class TimeSpanPicker {
183
187
  }
184
188
 
185
189
  public destroy(): void {
186
- const startHandler = () => this.handleChange();
187
- const endHandler = () => this.handleChange();
188
- this.startTimeInput.removeEventListener('change', startHandler);
189
- this.endTimeInput.removeEventListener('change', endHandler);
190
+ this.startTimeInput.removeEventListener('change', this.handleStartChange);
191
+ this.endTimeInput.removeEventListener('change', this.handleEndChange);
190
192
  }
191
193
  }
192
194
 
package/js/toast.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { escapeHtml } from './utils.js';
1
2
  class Toast {
2
3
  constructor(contentOrOptions, header = '', type, closeable = true) {
3
4
  this.closureIcon = '<div class="icon icon-close close"></div>';
@@ -79,15 +80,10 @@ class Toast {
79
80
  parts.push(this.closureIcon);
80
81
  }
81
82
  if (this.header) {
82
- parts.push(`<div class="header">${this.escapeHtml(this.header)}</div>`);
83
+ parts.push(`<div class="header">${escapeHtml(this.header)}</div>`);
83
84
  }
84
- parts.push(`<div class="content">${this.escapeHtml(this.content)}</div>`);
85
+ parts.push(`<div class="content">${escapeHtml(this.content)}</div>`);
85
86
  return parts.join('');
86
87
  }
87
- escapeHtml(text) {
88
- const div = document.createElement('div');
89
- div.textContent = text;
90
- return div.innerHTML;
91
- }
92
88
  }
93
89
  export { Toast };
package/js/toast.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { escapeHtml } from './utils.js';
2
+
1
3
  type ToastType = 'success' | 'error' | 'warning' | 'info';
2
4
 
3
5
  interface ToastOptions {
@@ -119,19 +121,13 @@ class Toast {
119
121
  }
120
122
 
121
123
  if (this.header) {
122
- parts.push(`<div class="header">${this.escapeHtml(this.header)}</div>`);
124
+ parts.push(`<div class="header">${escapeHtml(this.header)}</div>`);
123
125
  }
124
126
 
125
- parts.push(`<div class="content">${this.escapeHtml(this.content)}</div>`);
127
+ parts.push(`<div class="content">${escapeHtml(this.content)}</div>`);
126
128
 
127
129
  return parts.join('');
128
130
  }
129
-
130
- private escapeHtml(text: string): string {
131
- const div = document.createElement('div');
132
- div.textContent = text;
133
- return div.innerHTML;
134
- }
135
131
  }
136
132
 
137
133
  export { Toast };
package/js/tooltip.js CHANGED
@@ -23,7 +23,8 @@ class Tooltip {
23
23
  position: options.position ?? 'auto',
24
24
  offset: options.offset ?? 8,
25
25
  delay: options.delay ?? 0,
26
- className: options.className ?? ''
26
+ className: options.className ?? '',
27
+ isHtml: options.isHtml ?? false,
27
28
  };
28
29
  this.attachEvents();
29
30
  }
@@ -34,7 +35,7 @@ class Tooltip {
34
35
  const position = trigger.getAttribute('data-tooltip-position') ?? 'auto';
35
36
  const className = trigger.getAttribute('data-tooltip-class') ?? '';
36
37
  if (content) {
37
- new Tooltip(trigger, content, { position, className });
38
+ new Tooltip(trigger, content, { position, className, isHtml: false });
38
39
  }
39
40
  });
40
41
  // Also support content from separate elements
@@ -47,7 +48,7 @@ class Tooltip {
47
48
  const contentElement = document.getElementById(contentId);
48
49
  if (contentElement) {
49
50
  const content = contentElement.innerHTML;
50
- new Tooltip(trigger, content, { position, className });
51
+ new Tooltip(trigger, content, { position, className, isHtml: true });
51
52
  }
52
53
  }
53
54
  });
@@ -94,7 +95,15 @@ class Tooltip {
94
95
  tooltip.className = 'tooltip';
95
96
  tooltip.id = `tooltip-${++Tooltip.idCounter}`;
96
97
  tooltip.setAttribute('role', 'tooltip');
97
- tooltip.innerHTML = `<div class="tooltip-content">${this.content}</div>`;
98
+ const tooltipContent = document.createElement('div');
99
+ tooltipContent.className = 'tooltip-content';
100
+ if (this.options.isHtml) {
101
+ tooltipContent.innerHTML = this.content;
102
+ }
103
+ else {
104
+ tooltipContent.textContent = this.content;
105
+ }
106
+ tooltip.appendChild(tooltipContent);
98
107
  if (this.options.className) {
99
108
  tooltip.classList.add(this.options.className);
100
109
  }