@dodlhuat/basix 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +38 -1
  2. package/css/calendar.scss +4 -3
  3. package/css/code-viewer.scss +1 -1
  4. package/css/datepicker.scss +83 -70
  5. package/css/editor.scss +2 -1
  6. package/css/flyout-menu.scss +2 -1
  7. package/css/form.scss +11 -32
  8. package/css/mixins.scss +25 -0
  9. package/css/range-slider.scss +63 -6
  10. package/css/stepper.scss +2 -1
  11. package/css/style.css +124 -71
  12. package/css/style.css.map +1 -1
  13. package/css/style.min.css +1 -1
  14. package/css/style.min.css.map +1 -1
  15. package/css/timepicker.scss +1 -1
  16. package/css/virtual-dropdown.scss +6 -16
  17. package/js/bottom-sheet.d.ts +3 -5
  18. package/js/bottom-sheet.js +39 -58
  19. package/js/calendar.d.ts +1 -2
  20. package/js/calendar.js +77 -84
  21. package/js/carousel.d.ts +1 -1
  22. package/js/carousel.js +22 -14
  23. package/js/chart.d.ts +1 -1
  24. package/js/chart.js +104 -65
  25. package/js/code-viewer.d.ts +1 -1
  26. package/js/code-viewer.js +7 -18
  27. package/js/color-picker.d.ts +2 -2
  28. package/js/color-picker.js +20 -22
  29. package/js/context-menu.d.ts +1 -1
  30. package/js/context-menu.js +10 -12
  31. package/js/datepicker.d.ts +14 -3
  32. package/js/datepicker.js +180 -99
  33. package/js/dropdown.d.ts +1 -1
  34. package/js/dropdown.js +4 -5
  35. package/js/editor.d.ts +1 -1
  36. package/js/editor.js +30 -34
  37. package/js/file-uploader.d.ts +2 -9
  38. package/js/file-uploader.js +57 -86
  39. package/js/flyout-menu.d.ts +3 -3
  40. package/js/flyout-menu.js +20 -22
  41. package/js/gallery.d.ts +1 -2
  42. package/js/gallery.js +14 -20
  43. package/js/group-picker.d.ts +1 -1
  44. package/js/group-picker.js +22 -29
  45. package/js/lightbox.d.ts +2 -2
  46. package/js/lightbox.js +28 -27
  47. package/js/listeners.d.ts +7 -0
  48. package/js/listeners.js +14 -0
  49. package/js/modal.d.ts +2 -1
  50. package/js/modal.js +13 -20
  51. package/js/popover.d.ts +2 -2
  52. package/js/popover.js +42 -37
  53. package/js/position.js +3 -5
  54. package/js/push-menu.d.ts +2 -0
  55. package/js/push-menu.js +21 -15
  56. package/js/range-slider.d.ts +15 -1
  57. package/js/range-slider.js +67 -7
  58. package/js/scroll.js +4 -7
  59. package/js/scrollbar.d.ts +3 -3
  60. package/js/scrollbar.js +39 -42
  61. package/js/select.d.ts +1 -1
  62. package/js/select.js +19 -23
  63. package/js/sidebar-nav.d.ts +1 -1
  64. package/js/sidebar-nav.js +13 -9
  65. package/js/stepper.d.ts +1 -1
  66. package/js/stepper.js +9 -11
  67. package/js/table.d.ts +1 -1
  68. package/js/table.js +24 -23
  69. package/js/tabs.d.ts +1 -1
  70. package/js/tabs.js +10 -17
  71. package/js/theme.d.ts +1 -0
  72. package/js/theme.js +6 -15
  73. package/js/timepicker.d.ts +8 -6
  74. package/js/timepicker.js +36 -36
  75. package/js/toast.d.ts +2 -1
  76. package/js/toast.js +8 -4
  77. package/js/tooltip.d.ts +1 -4
  78. package/js/tooltip.js +14 -23
  79. package/js/tree.d.ts +0 -1
  80. package/js/tree.js +6 -11
  81. package/js/utils.js +7 -8
  82. package/js/virtual-dropdown.d.ts +1 -1
  83. package/js/virtual-dropdown.js +26 -28
  84. package/package.json +1 -1
  85. package/css/calendar.css +0 -928
  86. package/css/guitar-chords.css +0 -251
  87. package/js/form-builder.js +0 -107
  88. package/js/guitar-chords.js +0 -268
  89. package/js/lazy-loader.js +0 -121
  90. package/js/request.js +0 -51
package/js/table.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Select } from './select.js';
2
+ import { ListenerGroup } from './listeners.js';
2
3
  class Table {
3
4
  container;
4
5
  data;
@@ -11,11 +12,9 @@ class Table {
11
12
  tableBody;
12
13
  tableHeader;
13
14
  paginationContainer;
14
- abortController = new AbortController();
15
+ listeners = new ListenerGroup();
15
16
  constructor(elementOrSelector, options = {}) {
16
- const element = typeof elementOrSelector === 'string'
17
- ? document.querySelector(elementOrSelector)
18
- : elementOrSelector;
17
+ const element = typeof elementOrSelector === 'string' ? document.querySelector(elementOrSelector) : elementOrSelector;
19
18
  if (!element) {
20
19
  throw new Error(`Table: Element not found for selector "${elementOrSelector}"`);
21
20
  }
@@ -44,10 +43,10 @@ class Table {
44
43
  this.columns = Array.from(ths).map((th, index) => ({
45
44
  key: `col${index}`,
46
45
  label: th.textContent?.trim() || '',
47
- sortable: true
46
+ sortable: true,
48
47
  }));
49
48
  const trs = tbody.querySelectorAll('tr');
50
- this.data = Array.from(trs).map(tr => {
49
+ this.data = Array.from(trs).map((tr) => {
51
50
  const row = {};
52
51
  const tds = tr.querySelectorAll('td');
53
52
  tds.forEach((td, index) => {
@@ -73,7 +72,7 @@ class Table {
73
72
  searchInput.className = 'search-input';
74
73
  searchInput.addEventListener('input', (e) => {
75
74
  this.handleSearch(e.target.value);
76
- }, { signal: this.abortController.signal });
75
+ }, { signal: this.listeners.signal });
77
76
  controlsDiv.appendChild(searchInput);
78
77
  const selectGroup = document.createElement('div');
79
78
  selectGroup.className = 'select-group';
@@ -82,7 +81,7 @@ class Table {
82
81
  selectGroup.appendChild(label);
83
82
  const pageSizeSelect = document.createElement('select');
84
83
  pageSizeSelect.className = 'page-size-select';
85
- [5, 10, 20, 50].forEach(size => {
84
+ [5, 10, 20, 50].forEach((size) => {
86
85
  const option = document.createElement('option');
87
86
  option.value = String(size);
88
87
  option.textContent = `${size} per page`;
@@ -91,7 +90,7 @@ class Table {
91
90
  });
92
91
  pageSizeSelect.addEventListener('change', (e) => {
93
92
  this.handlePageSizeChange(parseInt(e.target.value, 10));
94
- }, { signal: this.abortController.signal });
93
+ }, { signal: this.listeners.signal });
95
94
  this.assignUniqueId(pageSizeSelect, 'page-size-select-0');
96
95
  selectGroup.appendChild(pageSizeSelect);
97
96
  controlsDiv.appendChild(selectGroup);
@@ -105,13 +104,13 @@ class Table {
105
104
  const thead = document.createElement('thead');
106
105
  const tbody = document.createElement('tbody');
107
106
  const tr = document.createElement('tr');
108
- this.columns.forEach(col => {
107
+ this.columns.forEach((col) => {
109
108
  const th = document.createElement('th');
110
109
  th.textContent = col.label;
111
110
  th.dataset.key = col.key;
112
111
  if (col.sortable !== false) {
113
112
  th.classList.add('sortable');
114
- th.addEventListener('click', () => this.handleSort(col.key), { signal: this.abortController.signal });
113
+ th.addEventListener('click', () => this.handleSort(col.key), { signal: this.listeners.signal });
115
114
  }
116
115
  tr.appendChild(th);
117
116
  });
@@ -131,17 +130,18 @@ class Table {
131
130
  let processedData = [...this.data];
132
131
  if (this.filterText) {
133
132
  const lowerFilter = this.filterText.toLowerCase();
134
- processedData = processedData.filter(row => {
135
- return this.columns.some(col => {
133
+ processedData = processedData.filter((row) => {
134
+ return this.columns.some((col) => {
136
135
  const val = String(row[col.key] ?? '').toLowerCase();
137
136
  return val.includes(lowerFilter);
138
137
  });
139
138
  });
140
139
  }
141
140
  if (this.sortColumn) {
141
+ const col = this.sortColumn;
142
142
  processedData.sort((a, b) => {
143
- const valA = a[this.sortColumn];
144
- const valB = b[this.sortColumn];
143
+ const valA = a[col];
144
+ const valB = b[col];
145
145
  if (valA == null && valB == null)
146
146
  return 0;
147
147
  if (valA == null)
@@ -186,9 +186,9 @@ class Table {
186
186
  this.tableBody.appendChild(tr);
187
187
  return;
188
188
  }
189
- data.forEach(row => {
189
+ data.forEach((row) => {
190
190
  const tr = document.createElement('tr');
191
- this.columns.forEach(col => {
191
+ this.columns.forEach((col) => {
192
192
  const td = document.createElement('td');
193
193
  td.textContent = String(row[col.key] ?? '');
194
194
  td.setAttribute('data-label', col.label);
@@ -199,7 +199,7 @@ class Table {
199
199
  }
200
200
  updateHeaderSortIcons() {
201
201
  const ths = this.tableHeader.querySelectorAll('th');
202
- ths.forEach(th => {
202
+ ths.forEach((th) => {
203
203
  th.classList.remove('sort-asc', 'sort-desc');
204
204
  if (th.dataset.key === this.sortColumn) {
205
205
  th.classList.add(this.sortDirection === 'asc' ? 'sort-asc' : 'sort-desc');
@@ -216,14 +216,15 @@ class Table {
216
216
  this.paginationContainer.appendChild(info);
217
217
  const buttonsDiv = document.createElement('div');
218
218
  buttonsDiv.className = 'pagination-buttons';
219
+ const sig = { signal: this.listeners.signal };
219
220
  const prevBtn = document.createElement('button');
220
221
  prevBtn.className = 'page-btn';
221
222
  prevBtn.textContent = 'Previous';
222
223
  prevBtn.disabled = this.currentPage === 1;
223
- prevBtn.addEventListener('click', () => this.setPage(this.currentPage - 1));
224
+ prevBtn.addEventListener('click', () => this.setPage(this.currentPage - 1), sig);
224
225
  buttonsDiv.appendChild(prevBtn);
225
226
  let startPage = Math.max(1, this.currentPage - 2);
226
- let endPage = Math.min(totalPages, startPage + 4);
227
+ const endPage = Math.min(totalPages, startPage + 4);
227
228
  if (endPage - startPage < 4) {
228
229
  startPage = Math.max(1, endPage - 4);
229
230
  }
@@ -231,14 +232,14 @@ class Table {
231
232
  const btn = document.createElement('button');
232
233
  btn.className = `page-btn ${i === this.currentPage ? 'active' : ''}`;
233
234
  btn.textContent = String(i);
234
- btn.addEventListener('click', () => this.setPage(i));
235
+ btn.addEventListener('click', () => this.setPage(i), sig);
235
236
  buttonsDiv.appendChild(btn);
236
237
  }
237
238
  const nextBtn = document.createElement('button');
238
239
  nextBtn.className = 'page-btn';
239
240
  nextBtn.textContent = 'Next';
240
241
  nextBtn.disabled = this.currentPage === totalPages;
241
- nextBtn.addEventListener('click', () => this.setPage(this.currentPage + 1));
242
+ nextBtn.addEventListener('click', () => this.setPage(this.currentPage + 1), sig);
242
243
  buttonsDiv.appendChild(nextBtn);
243
244
  this.paginationContainer.appendChild(buttonsDiv);
244
245
  }
@@ -298,7 +299,7 @@ class Table {
298
299
  return this.getFilteredAndSortedData();
299
300
  }
300
301
  destroy() {
301
- this.abortController.abort();
302
+ this.listeners.destroy();
302
303
  this.container.innerHTML = '';
303
304
  }
304
305
  }
package/js/tabs.d.ts CHANGED
@@ -12,7 +12,7 @@ declare class Tabs {
12
12
  private tabItems;
13
13
  private tabPanels;
14
14
  private currentTab;
15
- private abortController;
15
+ private listeners;
16
16
  constructor(elementOrSelector: string | HTMLElement, options?: TabsOptions);
17
17
  private init;
18
18
  private bindEvents;
package/js/tabs.js CHANGED
@@ -1,14 +1,13 @@
1
+ import { ListenerGroup } from './listeners.js';
1
2
  class Tabs {
2
3
  container;
3
4
  options;
4
5
  tabItems;
5
6
  tabPanels;
6
7
  currentTab;
7
- abortController = new AbortController();
8
+ listeners = new ListenerGroup();
8
9
  constructor(elementOrSelector, options = {}) {
9
- const element = typeof elementOrSelector === 'string'
10
- ? document.querySelector(elementOrSelector)
11
- : elementOrSelector;
10
+ const element = typeof elementOrSelector === 'string' ? document.querySelector(elementOrSelector) : elementOrSelector;
12
11
  if (!element) {
13
12
  throw new Error(`Tabs: Element not found for selector "${elementOrSelector}"`);
14
13
  }
@@ -18,7 +17,7 @@ class Tabs {
18
17
  layout,
19
18
  defaultTab: options.defaultTab ?? 0,
20
19
  menuPos: options.menuPos ?? (layout === 'vertical' ? 'left' : 'top'),
21
- onChange: options.onChange
20
+ onChange: options.onChange,
22
21
  };
23
22
  this.currentTab = this.options.defaultTab;
24
23
  this.tabItems = this.container.querySelectorAll('.tab-item');
@@ -29,8 +28,6 @@ class Tabs {
29
28
  if (this.options.layout === 'vertical') {
30
29
  this.container.classList.add('tabs-vertical');
31
30
  }
32
- this.tabItems = this.container.querySelectorAll('.tab-item');
33
- this.tabPanels = this.container.querySelectorAll('.tab-panel');
34
31
  if (this.tabItems.length === 0) {
35
32
  console.warn('No tab items found in container');
36
33
  return;
@@ -46,16 +43,13 @@ class Tabs {
46
43
  this.activateTab(this.options.defaultTab);
47
44
  }
48
45
  bindEvents() {
49
- const sig = { signal: this.abortController.signal };
46
+ const sig = { signal: this.listeners.signal };
50
47
  this.tabItems.forEach((item, index) => {
51
48
  item.addEventListener('click', (e) => {
52
49
  e.preventDefault();
53
50
  this.activateTab(index);
54
51
  }, sig);
55
- item.addEventListener('keydown', (e) => {
56
- const keyEvent = e;
57
- this.handleKeyboardNavigation(keyEvent, index);
58
- }, sig);
52
+ item.addEventListener('keydown', (e) => this.handleKeyboardNavigation(e, index), sig);
59
53
  item.setAttribute('role', 'tab');
60
54
  item.setAttribute('tabindex', index === this.options.defaultTab ? '0' : '-1');
61
55
  item.setAttribute('aria-selected', index === this.options.defaultTab ? 'true' : 'false');
@@ -114,7 +108,7 @@ class Tabs {
114
108
  console.warn(`Invalid tab index: ${index}`);
115
109
  return;
116
110
  }
117
- this.tabItems.forEach((item, i) => {
111
+ this.tabItems.forEach((item) => {
118
112
  item.classList.remove('active');
119
113
  item.setAttribute('tabindex', '-1');
120
114
  item.setAttribute('aria-selected', 'false');
@@ -130,9 +124,8 @@ class Tabs {
130
124
  this.tabPanels[index].setAttribute('aria-hidden', 'false');
131
125
  const previousTab = this.currentTab;
132
126
  this.currentTab = index;
133
- if (this.options.onChange && previousTab !== index) {
134
- this.options.onChange(index);
135
- }
127
+ if (previousTab !== index)
128
+ this.options.onChange?.(index);
136
129
  }
137
130
  goToTab(index) {
138
131
  this.activateTab(index);
@@ -169,7 +162,7 @@ class Tabs {
169
162
  }
170
163
  }
171
164
  destroy() {
172
- this.abortController.abort();
165
+ this.listeners.destroy();
173
166
  this.container.classList.remove('tabs-vertical');
174
167
  this.tabItems.forEach((item) => {
175
168
  item.removeAttribute('role');
package/js/theme.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  type ThemeMode = 'light' | 'dark';
2
2
  declare class Theme {
3
3
  private static readonly STORAGE_KEY;
4
+ private static readonly isMac;
4
5
  private static root;
5
6
  private static elements;
6
7
  private static mediaQuery;
package/js/theme.js CHANGED
@@ -1,5 +1,6 @@
1
1
  class Theme {
2
2
  static STORAGE_KEY = 'theme';
3
+ static isMac = /Mac|iPhone|iPod|iPad/i.test(navigator.platform);
3
4
  static root;
4
5
  static elements = null;
5
6
  static mediaQuery = null;
@@ -99,14 +100,11 @@ class Theme {
99
100
  static bindToggleClick() {
100
101
  if (!this.elements)
101
102
  return;
102
- this.elements.toggleBtn.addEventListener('click', () => {
103
- this.toggleTheme();
104
- });
103
+ this.elements.toggleBtn.addEventListener('click', () => this.toggleTheme());
105
104
  }
106
105
  static bindKeyboardShortcut() {
107
106
  window.addEventListener('keydown', (ev) => {
108
- const isMac = /Mac|iPhone|iPod|iPad/i.test(navigator.platform);
109
- const modifierPressed = isMac ? ev.metaKey : ev.ctrlKey;
107
+ const modifierPressed = Theme.isMac ? ev.metaKey : ev.ctrlKey;
110
108
  if (modifierPressed && ev.key.toLowerCase() === 'j') {
111
109
  ev.preventDefault();
112
110
  this.toggleTheme();
@@ -116,18 +114,11 @@ class Theme {
116
114
  static bindSystemThemeChange() {
117
115
  if (!this.mediaQuery)
118
116
  return;
119
- const handler = (e) => {
117
+ this.mediaQuery.addEventListener('change', (e) => {
120
118
  if (!this.getSavedTheme()) {
121
- const matches = 'matches' in e ? e.matches : e.matches;
122
- this.applyTheme(matches ? 'dark' : 'light');
119
+ this.applyTheme(e.matches ? 'dark' : 'light');
123
120
  }
124
- };
125
- if ('addEventListener' in this.mediaQuery) {
126
- this.mediaQuery.addEventListener('change', handler);
127
- }
128
- else if ('addListener' in this.mediaQuery) {
129
- this.mediaQuery.addListener(handler);
130
- }
121
+ });
131
122
  }
132
123
  static getTheme() {
133
124
  return this.getCurrentTheme();
@@ -24,18 +24,20 @@ declare class TimeSpanPicker {
24
24
  private startHandleEl;
25
25
  private endHandleEl;
26
26
  private dragState;
27
+ private listeners;
28
+ private dragListeners;
27
29
  constructor(elementOrSelector: string | HTMLElement, options?: TimeSpanPickerOptions);
28
30
  private queryEl;
29
31
  private render;
30
- private readonly handleChange;
32
+ private handleChange;
31
33
  private attachEventListeners;
32
34
  private attachBarListeners;
33
35
  private beginDrag;
34
- private readonly onStartHandleDown;
35
- private readonly onEndHandleDown;
36
- private readonly onFillDown;
37
- private readonly onPointerMove;
38
- private readonly onPointerUp;
36
+ private onStartHandleDown;
37
+ private onEndHandleDown;
38
+ private onFillDown;
39
+ private onPointerMove;
40
+ private onPointerUp;
39
41
  private toMinutes;
40
42
  private minutesToTime;
41
43
  private snap;
package/js/timepicker.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ListenerGroup } from './listeners.js';
1
2
  class TimeSpanPicker {
2
3
  container;
3
4
  startTimeInput;
@@ -13,11 +14,13 @@ class TimeSpanPicker {
13
14
  startHandleEl;
14
15
  endHandleEl;
15
16
  dragState = null;
17
+ listeners = new ListenerGroup();
18
+ dragListeners = null;
16
19
  constructor(elementOrSelector, options) {
17
20
  const element = typeof elementOrSelector === 'string'
18
- ? (elementOrSelector.startsWith('#') || elementOrSelector.startsWith('.')
21
+ ? elementOrSelector.startsWith('#') || elementOrSelector.startsWith('.')
19
22
  ? document.querySelector(elementOrSelector)
20
- : document.getElementById(elementOrSelector))
23
+ : document.getElementById(elementOrSelector)
21
24
  : elementOrSelector;
22
25
  if (!element) {
23
26
  throw new Error(`TimeSpanPicker: Element not found for "${elementOrSelector}"`);
@@ -74,17 +77,17 @@ class TimeSpanPicker {
74
77
  </div>
75
78
  `;
76
79
  }
77
- handleChange = () => {
80
+ handleChange() {
78
81
  this.updateUI();
79
82
  const start = this.startTimeInput.value;
80
83
  const end = this.endTimeInput.value;
81
- if (this.onChange && start && end) {
82
- this.onChange(start, end);
83
- }
84
- };
84
+ if (start && end)
85
+ this.onChange?.(start, end);
86
+ }
85
87
  attachEventListeners() {
86
- this.startTimeInput.addEventListener('change', this.handleChange);
87
- this.endTimeInput.addEventListener('change', this.handleChange);
88
+ const sig = { signal: this.listeners.signal };
89
+ this.startTimeInput.addEventListener('change', () => this.handleChange(), sig);
90
+ this.endTimeInput.addEventListener('change', () => this.handleChange(), sig);
88
91
  }
89
92
  attachBarListeners() {
90
93
  this.pickerEl = this.queryEl('.timespan-picker');
@@ -93,9 +96,10 @@ class TimeSpanPicker {
93
96
  this.barFillEl = this.queryEl('.timespan-bar-fill');
94
97
  this.startHandleEl = this.queryEl('.timespan-handle-start');
95
98
  this.endHandleEl = this.queryEl('.timespan-handle-end');
96
- this.startHandleEl.addEventListener('pointerdown', this.onStartHandleDown);
97
- this.endHandleEl.addEventListener('pointerdown', this.onEndHandleDown);
98
- this.barFillEl.addEventListener('pointerdown', this.onFillDown);
99
+ const sig = { signal: this.listeners.signal };
100
+ this.startHandleEl.addEventListener('pointerdown', (e) => this.onStartHandleDown(e), sig);
101
+ this.endHandleEl.addEventListener('pointerdown', (e) => this.onEndHandleDown(e), sig);
102
+ this.barFillEl.addEventListener('pointerdown', (e) => this.onFillDown(e), sig);
99
103
  }
100
104
  beginDrag(type, clickOffsetMins = 0, rect) {
101
105
  const start = this.startTimeInput.value;
@@ -112,20 +116,22 @@ class TimeSpanPicker {
112
116
  clickOffsetMins,
113
117
  };
114
118
  this.barEl.classList.add('is-dragging');
115
- document.addEventListener('pointermove', this.onPointerMove);
116
- document.addEventListener('pointerup', this.onPointerUp);
119
+ this.dragListeners = new ListenerGroup();
120
+ const dragSig = { signal: this.dragListeners.signal };
121
+ document.addEventListener('pointermove', (e) => this.onPointerMove(e), dragSig);
122
+ document.addEventListener('pointerup', () => this.onPointerUp(), dragSig);
117
123
  }
118
- onStartHandleDown = (e) => {
124
+ onStartHandleDown(e) {
119
125
  e.stopPropagation();
120
126
  e.preventDefault();
121
127
  this.beginDrag('start');
122
- };
123
- onEndHandleDown = (e) => {
128
+ }
129
+ onEndHandleDown(e) {
124
130
  e.stopPropagation();
125
131
  e.preventDefault();
126
132
  this.beginDrag('end');
127
- };
128
- onFillDown = (e) => {
133
+ }
134
+ onFillDown(e) {
129
135
  if (e.target.classList.contains('timespan-handle'))
130
136
  return;
131
137
  e.preventDefault();
@@ -135,8 +141,8 @@ class TimeSpanPicker {
135
141
  const rect = this.barEl.getBoundingClientRect();
136
142
  const clickMins = ((e.clientX - rect.left) / rect.width) * 1440;
137
143
  this.beginDrag('move', clickMins - this.toMinutes(start), rect);
138
- };
139
- onPointerMove = (e) => {
144
+ }
145
+ onPointerMove(e) {
140
146
  if (!this.dragState)
141
147
  return;
142
148
  e.preventDefault();
@@ -162,17 +168,16 @@ class TimeSpanPicker {
162
168
  this.endTimeInput.value = end;
163
169
  }
164
170
  this.updateUI();
165
- if (this.onChange)
166
- this.onChange(start, end);
167
- };
168
- onPointerUp = () => {
171
+ this.onChange?.(start, end);
172
+ }
173
+ onPointerUp() {
169
174
  if (!this.dragState)
170
175
  return;
171
176
  this.dragState = null;
172
177
  this.barEl.classList.remove('is-dragging');
173
- document.removeEventListener('pointermove', this.onPointerMove);
174
- document.removeEventListener('pointerup', this.onPointerUp);
175
- };
178
+ this.dragListeners?.destroy();
179
+ this.dragListeners = null;
180
+ }
176
181
  toMinutes(time) {
177
182
  const [h, m] = time.split(':').map(Number);
178
183
  return h * 60 + m;
@@ -225,7 +230,7 @@ class TimeSpanPicker {
225
230
  getValue() {
226
231
  return {
227
232
  start: this.startTimeInput.value,
228
- end: this.endTimeInput.value
233
+ end: this.endTimeInput.value,
229
234
  };
230
235
  }
231
236
  setValue(start, end) {
@@ -248,13 +253,8 @@ class TimeSpanPicker {
248
253
  return !!(start && end && start < end);
249
254
  }
250
255
  destroy() {
251
- this.startTimeInput.removeEventListener('change', this.handleChange);
252
- this.endTimeInput.removeEventListener('change', this.handleChange);
253
- this.startHandleEl.removeEventListener('pointerdown', this.onStartHandleDown);
254
- this.endHandleEl.removeEventListener('pointerdown', this.onEndHandleDown);
255
- this.barFillEl.removeEventListener('pointerdown', this.onFillDown);
256
- document.removeEventListener('pointermove', this.onPointerMove);
257
- document.removeEventListener('pointerup', this.onPointerUp);
256
+ this.listeners.destroy();
257
+ this.dragListeners?.destroy();
258
258
  }
259
259
  }
260
260
  export { TimeSpanPicker };
package/js/toast.d.ts CHANGED
@@ -14,10 +14,11 @@ declare class Toast {
14
14
  private readonly template;
15
15
  private toastElement;
16
16
  private timerId;
17
+ private openListeners;
17
18
  constructor(options: ToastOptions);
18
19
  constructor(content: string, header?: string, type?: ToastType, closeable?: boolean);
19
20
  show(ms?: number): void;
20
- hide: () => void;
21
+ hide(): void;
21
22
  private startTimer;
22
23
  destroy(): void;
23
24
  private buildTemplate;
package/js/toast.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { escapeHtml } from './utils.js';
2
+ import { ListenerGroup } from './listeners.js';
2
3
  class Toast {
3
4
  content;
4
5
  header;
@@ -8,6 +9,7 @@ class Toast {
8
9
  template;
9
10
  toastElement = null;
10
11
  timerId = null;
12
+ openListeners = null;
11
13
  constructor(contentOrOptions, header = '', type, closeable = true) {
12
14
  if (typeof contentOrOptions === 'object') {
13
15
  this.content = contentOrOptions.content;
@@ -37,7 +39,8 @@ class Toast {
37
39
  this.toastElement?.classList.add('show');
38
40
  const closeButton = this.toastElement?.querySelector('.close');
39
41
  if (closeButton) {
40
- closeButton.addEventListener('click', this.hide);
42
+ this.openListeners = new ListenerGroup();
43
+ closeButton.addEventListener('click', () => this.hide(), { signal: this.openListeners.signal });
41
44
  }
42
45
  if (ms !== undefined && ms > 0) {
43
46
  this.startTimer(ms);
@@ -45,18 +48,19 @@ class Toast {
45
48
  });
46
49
  });
47
50
  }
48
- hide = () => {
51
+ hide() {
49
52
  if (this.timerId !== null) {
50
53
  clearTimeout(this.timerId);
51
54
  this.timerId = null;
52
55
  }
53
56
  this.toastElement?.classList.remove('show');
57
+ this.openListeners?.destroy();
58
+ this.openListeners = null;
54
59
  setTimeout(() => {
55
- this.toastElement?.querySelector('.close')?.removeEventListener('click', this.hide);
56
60
  this.toastElement?.remove();
57
61
  this.toastElement = null;
58
62
  }, 150);
59
- };
63
+ }
60
64
  startTimer(ms, elapsed = 0) {
61
65
  const stepSize = 250;
62
66
  if (elapsed >= ms) {
package/js/tooltip.d.ts CHANGED
@@ -14,6 +14,7 @@ declare class Tooltip {
14
14
  private tooltipElement;
15
15
  private showTimeout;
16
16
  private isVisible;
17
+ private listeners;
17
18
  constructor(trigger: HTMLElement, content: string, options?: TooltipOptions);
18
19
  static initializeAll(): void;
19
20
  show(): void;
@@ -22,10 +23,6 @@ declare class Tooltip {
22
23
  private createTooltip;
23
24
  private position;
24
25
  private attachEvents;
25
- private handleMouseEnter;
26
- private handleMouseLeave;
27
- private handleFocus;
28
- private handleBlur;
29
26
  destroy(): void;
30
27
  }
31
28
  export { Tooltip };
package/js/tooltip.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { computePosition } from './position.js';
2
+ import { ListenerGroup } from './listeners.js';
2
3
  class Tooltip {
3
4
  static activeTooltip = null;
4
5
  static idCounter = 0;
@@ -8,6 +9,7 @@ class Tooltip {
8
9
  tooltipElement = null;
9
10
  showTimeout = null;
10
11
  isVisible = false;
12
+ listeners = new ListenerGroup();
11
13
  constructor(trigger, content, options = {}) {
12
14
  this.trigger = trigger;
13
15
  this.content = content;
@@ -22,7 +24,7 @@ class Tooltip {
22
24
  }
23
25
  static initializeAll() {
24
26
  const triggers = document.querySelectorAll('[data-tooltip]');
25
- triggers.forEach(trigger => {
27
+ triggers.forEach((trigger) => {
26
28
  const content = trigger.getAttribute('data-tooltip');
27
29
  const position = trigger.getAttribute('data-tooltip-position') ?? 'auto';
28
30
  const className = trigger.getAttribute('data-tooltip-class') ?? '';
@@ -31,7 +33,7 @@ class Tooltip {
31
33
  }
32
34
  });
33
35
  const advancedTriggers = document.querySelectorAll('[data-tooltip-id]');
34
- advancedTriggers.forEach(trigger => {
36
+ advancedTriggers.forEach((trigger) => {
35
37
  const contentId = trigger.getAttribute('data-tooltip-id');
36
38
  const position = trigger.getAttribute('data-tooltip-position') ?? 'auto';
37
39
  const className = trigger.getAttribute('data-tooltip-class') ?? '';
@@ -107,35 +109,24 @@ class Tooltip {
107
109
  position() {
108
110
  if (!this.tooltipElement)
109
111
  return;
110
- const { left, top, placement } = computePosition(this.trigger.getBoundingClientRect(), this.tooltipElement.getBoundingClientRect(), { placement: this.options.position, offset: this.options.offset });
112
+ const { left, top, placement } = computePosition(this.trigger.getBoundingClientRect(), this.tooltipElement.getBoundingClientRect(), {
113
+ placement: this.options.position,
114
+ offset: this.options.offset,
115
+ });
111
116
  this.tooltipElement.style.left = `${left}px`;
112
117
  this.tooltipElement.style.top = `${top}px`;
113
118
  this.tooltipElement.setAttribute('data-position', placement);
114
119
  }
115
120
  attachEvents() {
116
- this.trigger.addEventListener('mouseenter', this.handleMouseEnter);
117
- this.trigger.addEventListener('mouseleave', this.handleMouseLeave);
118
- this.trigger.addEventListener('focus', this.handleFocus);
119
- this.trigger.addEventListener('blur', this.handleBlur);
121
+ const sig = { signal: this.listeners.signal };
122
+ this.trigger.addEventListener('mouseenter', () => this.show(), sig);
123
+ this.trigger.addEventListener('mouseleave', () => this.hide(), sig);
124
+ this.trigger.addEventListener('focus', () => this.show(), sig);
125
+ this.trigger.addEventListener('blur', () => this.hide(), sig);
120
126
  }
121
- handleMouseEnter = () => {
122
- this.show();
123
- };
124
- handleMouseLeave = () => {
125
- this.hide();
126
- };
127
- handleFocus = () => {
128
- this.show();
129
- };
130
- handleBlur = () => {
131
- this.hide();
132
- };
133
127
  destroy() {
134
128
  this.hide();
135
- this.trigger.removeEventListener('mouseenter', this.handleMouseEnter);
136
- this.trigger.removeEventListener('mouseleave', this.handleMouseLeave);
137
- this.trigger.removeEventListener('focus', this.handleFocus);
138
- this.trigger.removeEventListener('blur', this.handleBlur);
129
+ this.listeners.destroy();
139
130
  const previousDescribedBy = this.trigger.getAttribute('data-previous-describedby');
140
131
  if (previousDescribedBy) {
141
132
  this.trigger.setAttribute('aria-describedby', previousDescribedBy);
package/js/tree.d.ts CHANGED
@@ -18,7 +18,6 @@ declare class TreeComponent {
18
18
  private selectedNode;
19
19
  private readonly options;
20
20
  constructor(elementOrSelector: string | HTMLElement, data: TreeNode[], options?: TreeOptions);
21
- private init;
22
21
  render(): void;
23
22
  private renderNode;
24
23
  private createIconElement;