@dodlhuat/basix 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +55 -9
  2. package/css/alert.scss +9 -9
  3. package/css/badge.scss +5 -25
  4. package/css/bottom-sheet.scss +2 -6
  5. package/css/breadcrumb.scss +18 -9
  6. package/css/calendar.scss +19 -29
  7. package/css/carousel.scss +3 -3
  8. package/css/chart.scss +1 -6
  9. package/css/checkbox.scss +1 -1
  10. package/css/chips.scss +13 -23
  11. package/css/code-viewer.scss +2 -2
  12. package/css/color-picker.scss +3 -5
  13. package/css/context-menu.scss +3 -8
  14. package/css/datepicker.scss +84 -71
  15. package/css/defaults.scss +25 -0
  16. package/css/docs.scss +6 -6
  17. package/css/dropdown.scss +3 -8
  18. package/css/editor.scss +15 -18
  19. package/css/file-uploader.scss +6 -17
  20. package/css/flyout-menu.scss +5 -1
  21. package/css/form.scss +43 -57
  22. package/css/gallery.scss +15 -0
  23. package/css/group-picker.scss +28 -17
  24. package/css/icons.scss +7 -327
  25. package/css/lightbox.scss +7 -7
  26. package/css/mixins.scss +77 -0
  27. package/css/modal.scss +4 -8
  28. package/css/parameters.scss +8 -2
  29. package/css/placeholder.scss +2 -17
  30. package/css/popover.scss +2 -7
  31. package/css/properties.scss +0 -5
  32. package/css/push-menu.scss +1 -1
  33. package/css/range-slider.scss +7 -6
  34. package/css/stepper.scss +4 -3
  35. package/css/style.css +279 -554
  36. package/css/style.css.map +1 -1
  37. package/css/style.min.css +1 -1
  38. package/css/style.min.css.map +1 -1
  39. package/css/tabs.scss +3 -7
  40. package/css/timepicker.scss +3 -3
  41. package/css/tooltip.scss +1 -1
  42. package/css/typography.scss +3 -3
  43. package/css/virtual-dropdown.scss +12 -27
  44. package/js/bottom-sheet.d.ts +5 -5
  45. package/js/bottom-sheet.js +41 -58
  46. package/js/calendar.d.ts +1 -2
  47. package/js/calendar.js +77 -84
  48. package/js/carousel.d.ts +2 -1
  49. package/js/carousel.js +25 -16
  50. package/js/chart.d.ts +1 -1
  51. package/js/chart.js +104 -65
  52. package/js/code-viewer.d.ts +1 -1
  53. package/js/code-viewer.js +7 -18
  54. package/js/color-picker.d.ts +2 -2
  55. package/js/color-picker.js +20 -22
  56. package/js/context-menu.d.ts +1 -1
  57. package/js/context-menu.js +10 -12
  58. package/js/datepicker.d.ts +14 -3
  59. package/js/datepicker.js +180 -99
  60. package/js/docs-nav.js +1 -1
  61. package/js/dropdown.d.ts +1 -1
  62. package/js/dropdown.js +4 -5
  63. package/js/editor.d.ts +1 -1
  64. package/js/editor.js +30 -34
  65. package/js/file-uploader.d.ts +2 -9
  66. package/js/file-uploader.js +57 -86
  67. package/js/flyout-menu.d.ts +3 -3
  68. package/js/flyout-menu.js +20 -22
  69. package/js/gallery.d.ts +6 -2
  70. package/js/gallery.js +49 -20
  71. package/js/group-picker.d.ts +2 -1
  72. package/js/group-picker.js +30 -34
  73. package/js/lightbox.d.ts +5 -3
  74. package/js/lightbox.js +36 -31
  75. package/js/listeners.d.ts +7 -0
  76. package/js/listeners.js +14 -0
  77. package/js/modal.d.ts +4 -1
  78. package/js/modal.js +17 -22
  79. package/js/popover.d.ts +2 -2
  80. package/js/popover.js +42 -37
  81. package/js/position.js +3 -5
  82. package/js/push-menu.d.ts +8 -2
  83. package/js/push-menu.js +34 -23
  84. package/js/range-slider.d.ts +2 -2
  85. package/js/range-slider.js +7 -6
  86. package/js/scroll.js +4 -7
  87. package/js/scrollbar.d.ts +3 -3
  88. package/js/scrollbar.js +39 -42
  89. package/js/select.d.ts +1 -1
  90. package/js/select.js +20 -24
  91. package/js/sidebar-nav.d.ts +2 -1
  92. package/js/sidebar-nav.js +15 -10
  93. package/js/stepper.d.ts +1 -1
  94. package/js/stepper.js +9 -11
  95. package/js/table.d.ts +1 -1
  96. package/js/table.js +24 -23
  97. package/js/tabs.d.ts +1 -1
  98. package/js/tabs.js +10 -17
  99. package/js/theme.d.ts +1 -0
  100. package/js/theme.js +6 -15
  101. package/js/timepicker.d.ts +8 -6
  102. package/js/timepicker.js +36 -36
  103. package/js/toast.d.ts +4 -1
  104. package/js/toast.js +13 -5
  105. package/js/tooltip.d.ts +1 -4
  106. package/js/tooltip.js +14 -23
  107. package/js/tree.d.ts +0 -1
  108. package/js/tree.js +6 -11
  109. package/js/utils.js +7 -8
  110. package/js/virtual-dropdown.d.ts +1 -1
  111. package/js/virtual-dropdown.js +26 -28
  112. package/package.json +2 -1
  113. package/svg-icons/chevron_right.svg +1 -0
  114. package/svg-icons/keyboard_arrow_down.svg +1 -0
  115. package/css/calendar.css +0 -928
  116. package/css/guitar-chords.css +0 -251
  117. package/fonts/MaterialSymbolsOutlined.woff2 +0 -0
  118. package/js/form-builder.js +0 -107
  119. package/js/guitar-chords.js +0 -268
  120. package/js/lazy-loader.js +0 -121
  121. package/js/request.js +0 -51
package/js/select.js CHANGED
@@ -1,12 +1,11 @@
1
+ import { ListenerGroup } from './listeners.js';
1
2
  class Select {
2
3
  element;
3
4
  isMultiselect;
4
5
  dropdown;
5
- documentClickHandler;
6
+ listeners = new ListenerGroup();
6
7
  constructor(elementOrSelector) {
7
- const element = typeof elementOrSelector === 'string'
8
- ? document.querySelector(elementOrSelector)
9
- : elementOrSelector;
8
+ const element = typeof elementOrSelector === 'string' ? document.querySelector(elementOrSelector) : elementOrSelector;
10
9
  if (!element) {
11
10
  throw new Error(`Select: Element not found for selector "${elementOrSelector}"`);
12
11
  }
@@ -17,39 +16,36 @@ class Select {
17
16
  }
18
17
  this.isMultiselect = result.isMulti;
19
18
  this.dropdown = result.dropdown;
20
- this.documentClickHandler = (e) => {
19
+ document.addEventListener('click', (e) => {
21
20
  if (this.dropdown && !this.dropdown.contains(e.target)) {
22
21
  this.dropdown.classList.remove('open');
23
22
  }
24
- };
25
- document.addEventListener('click', this.documentClickHandler);
23
+ }, { signal: this.listeners.signal });
26
24
  }
27
25
  destroy() {
28
- document.removeEventListener('click', this.documentClickHandler);
26
+ this.listeners.destroy();
29
27
  this.dropdown?.classList.remove('open');
30
28
  }
31
29
  value() {
32
30
  const selectedValues = Array.from(this.element.options)
33
- .filter(option => option.selected)
34
- .map(option => option.value);
31
+ .filter((option) => option.selected)
32
+ .map((option) => option.value);
35
33
  return this.isMultiselect ? selectedValues : selectedValues[0];
36
34
  }
37
35
  static init(elementOrSelector) {
38
- const element = typeof elementOrSelector === 'string'
39
- ? document.querySelector(elementOrSelector)
40
- : elementOrSelector;
36
+ const element = typeof elementOrSelector === 'string' ? document.querySelector(elementOrSelector) : elementOrSelector;
41
37
  if (!element)
42
38
  return null;
43
39
  const result = Select.initElement(element);
44
40
  if (!result)
45
41
  return null;
46
- const handler = (e) => {
42
+ const listeners = new ListenerGroup();
43
+ document.addEventListener('click', (e) => {
47
44
  if (!result.dropdown.contains(e.target)) {
48
45
  result.dropdown.classList.remove('open');
49
46
  }
50
- };
51
- document.addEventListener('click', handler);
52
- return () => document.removeEventListener('click', handler);
47
+ }, { signal: listeners.signal });
48
+ return () => listeners.destroy();
53
49
  }
54
50
  static initElement(element) {
55
51
  if (!Select.transformSelect(element)) {
@@ -94,7 +90,7 @@ class Select {
94
90
  return { isMulti, dropdown };
95
91
  }
96
92
  static closeAllDropdowns(exceptDropdown) {
97
- document.querySelectorAll('.dropdown').forEach(dropdown => {
93
+ document.querySelectorAll('.dropdown').forEach((dropdown) => {
98
94
  if (dropdown !== exceptDropdown) {
99
95
  dropdown.classList.remove('open');
100
96
  }
@@ -103,15 +99,15 @@ class Select {
103
99
  static handleMultiSelect(option, optionsContainer, selected, selectElement) {
104
100
  option.classList.toggle('selected');
105
101
  const selectedOptions = Array.from(optionsContainer.querySelectorAll('.dropdown-option.selected'));
106
- const values = selectedOptions.map(opt => opt.textContent?.trim() || '');
102
+ const values = selectedOptions.map((opt) => opt.textContent?.trim() || '');
107
103
  selected.textContent = values.length ? values.join(', ') : 'Select options';
108
- const selectedValues = selectedOptions.map(opt => opt.dataset.value || '');
109
- Array.from(selectElement.options).forEach(opt => {
104
+ const selectedValues = selectedOptions.map((opt) => opt.dataset.value || '');
105
+ Array.from(selectElement.options).forEach((opt) => {
110
106
  opt.selected = selectedValues.includes(opt.value);
111
107
  });
112
108
  }
113
109
  static handleSingleSelect(option, optionsContainer, selected, dropdown, selectElement) {
114
- optionsContainer.querySelectorAll('.dropdown-option').forEach(opt => {
110
+ optionsContainer.querySelectorAll('.dropdown-option').forEach((opt) => {
115
111
  opt.classList.remove('selected');
116
112
  });
117
113
  option.classList.add('selected');
@@ -144,9 +140,9 @@ class Select {
144
140
  dropdownOptions.className = 'dropdown-options';
145
141
  const optionsMenu = document.createElement('div');
146
142
  optionsMenu.className = 'dropdown-options-menu hidden';
147
- optionsMenu.innerHTML = 'Select options<span class="dropdown-options-icon icon icon-close"></span>';
143
+ optionsMenu.innerHTML = 'Select options<svg class="icon-svg dropdown-options-icon" aria-hidden="true"><use href="svg-icons/icons.svg#close"/></svg>';
148
144
  dropdownOptions.appendChild(optionsMenu);
149
- Array.from(select.options).forEach(opt => {
145
+ Array.from(select.options).forEach((opt) => {
150
146
  const optDiv = document.createElement('div');
151
147
  optDiv.className = 'dropdown-option';
152
148
  optDiv.dataset.value = opt.value;
@@ -3,6 +3,7 @@ interface SidebarNavOptions {
3
3
  breakpoint?: number;
4
4
  swipeThreshold?: number;
5
5
  swipeEdge?: number;
6
+ iconBasePath?: string;
6
7
  }
7
8
  declare class SidebarNav {
8
9
  private nav;
@@ -12,7 +13,7 @@ declare class SidebarNav {
12
13
  private opts;
13
14
  private touchStartX;
14
15
  private touchStartY;
15
- private abortController;
16
+ private listeners;
16
17
  constructor(containerOrSelector: string | HTMLElement, options?: SidebarNavOptions);
17
18
  open(): void;
18
19
  close(): void;
package/js/sidebar-nav.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ListenerGroup } from './listeners.js';
1
2
  class SidebarNav {
2
3
  nav;
3
4
  backdrop;
@@ -6,21 +7,20 @@ class SidebarNav {
6
7
  opts;
7
8
  touchStartX = 0;
8
9
  touchStartY = 0;
9
- abortController = new AbortController();
10
+ listeners = new ListenerGroup();
10
11
  constructor(containerOrSelector, options = {}) {
11
- const container = typeof containerOrSelector === 'string'
12
- ? document.querySelector(containerOrSelector)
13
- : containerOrSelector;
12
+ const container = typeof containerOrSelector === 'string' ? document.querySelector(containerOrSelector) : containerOrSelector;
14
13
  this.opts = {
15
14
  toggleSelector: options.toggleSelector ?? '.sidebar-toggle',
16
15
  breakpoint: options.breakpoint ?? 768,
17
16
  swipeThreshold: options.swipeThreshold ?? 60,
18
17
  swipeEdge: options.swipeEdge ?? 20,
18
+ iconBasePath: options.iconBasePath ?? 'svg-icons/',
19
19
  };
20
20
  this.nav = container?.querySelector('.sidebar-nav') ?? null;
21
21
  this.backdrop = container?.querySelector('.sidebar-backdrop') ?? null;
22
22
  this.toggleBtn = document.querySelector(this.opts.toggleSelector);
23
- const sig = { signal: this.abortController.signal };
23
+ const sig = { signal: this.listeners.signal };
24
24
  this.toggleBtn?.addEventListener('click', () => this.toggle(), sig);
25
25
  this.backdrop?.addEventListener('click', () => this.close(), sig);
26
26
  window.addEventListener('resize', () => {
@@ -30,7 +30,7 @@ class SidebarNav {
30
30
  document.addEventListener('touchstart', (e) => {
31
31
  this.touchStartX = e.touches[0].clientX;
32
32
  this.touchStartY = e.touches[0].clientY;
33
- }, { passive: true, signal: this.abortController.signal });
33
+ }, { ...sig, passive: true });
34
34
  document.addEventListener('touchend', (e) => {
35
35
  if (window.innerWidth > this.opts.breakpoint)
36
36
  return;
@@ -44,11 +44,11 @@ class SidebarNav {
44
44
  else if (this.isOpen() && dx <= -this.opts.swipeThreshold) {
45
45
  this.close();
46
46
  }
47
- }, { passive: true, signal: this.abortController.signal });
47
+ }, { ...sig, passive: true });
48
48
  this.closeBtn = document.createElement('button');
49
49
  this.closeBtn.className = 'sidebar-close';
50
50
  this.closeBtn.setAttribute('aria-label', 'Close navigation');
51
- this.closeBtn.innerHTML = '<div class="icon icon-close"></div>';
51
+ this.closeBtn.innerHTML = `<svg class="icon-svg" aria-hidden="true"><use href="${this.opts.iconBasePath}icons.svg#close"/></svg>`;
52
52
  this.closeBtn.addEventListener('click', () => this.close(), sig);
53
53
  this.nav?.append(this.closeBtn);
54
54
  }
@@ -61,13 +61,18 @@ class SidebarNav {
61
61
  this.backdrop?.classList.remove('is-visible');
62
62
  }
63
63
  toggle() {
64
- this.nav?.classList.contains('is-open') ? this.close() : this.open();
64
+ if (this.nav?.classList.contains('is-open')) {
65
+ this.close();
66
+ }
67
+ else {
68
+ this.open();
69
+ }
65
70
  }
66
71
  isOpen() {
67
72
  return this.nav?.classList.contains('is-open') ?? false;
68
73
  }
69
74
  destroy() {
70
- this.abortController.abort();
75
+ this.listeners.destroy();
71
76
  this.closeBtn?.remove();
72
77
  }
73
78
  }
package/js/stepper.d.ts CHANGED
@@ -11,7 +11,7 @@ declare class Stepper {
11
11
  private current;
12
12
  private readonly onChange?;
13
13
  private readonly iconBasePath;
14
- private abortController;
14
+ private listeners;
15
15
  private injectedConnectors;
16
16
  constructor(elementOrSelector: string | HTMLElement, options?: StepperOptions);
17
17
  private injectConnectors;
package/js/stepper.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ListenerGroup } from './listeners.js';
1
2
  class Stepper {
2
3
  container;
3
4
  steps;
@@ -5,12 +6,10 @@ class Stepper {
5
6
  current;
6
7
  onChange;
7
8
  iconBasePath;
8
- abortController = new AbortController();
9
+ listeners = new ListenerGroup();
9
10
  injectedConnectors = false;
10
11
  constructor(elementOrSelector, options = {}) {
11
- const element = typeof elementOrSelector === 'string'
12
- ? document.querySelector(elementOrSelector)
13
- : elementOrSelector;
12
+ const element = typeof elementOrSelector === 'string' ? document.querySelector(elementOrSelector) : elementOrSelector;
14
13
  if (!element)
15
14
  throw new Error('Stepper: element not found');
16
15
  this.container = element;
@@ -27,7 +26,7 @@ class Stepper {
27
26
  if (options.clickable) {
28
27
  this.container.classList.add('stepper-clickable');
29
28
  this.steps.forEach((step, i) => {
30
- step.addEventListener('click', () => this.goTo(i), { signal: this.abortController.signal });
29
+ step.addEventListener('click', () => this.goTo(i), { signal: this.listeners.signal });
31
30
  });
32
31
  }
33
32
  this.render();
@@ -88,9 +87,8 @@ class Stepper {
88
87
  const previous = this.current;
89
88
  this.current = index;
90
89
  this.render();
91
- if (this.onChange && previous !== index) {
92
- this.onChange(index, previous);
93
- }
90
+ if (previous !== index)
91
+ this.onChange?.(index, previous);
94
92
  }
95
93
  setError(index) {
96
94
  if (index < 0 || index >= this.steps.length)
@@ -115,12 +113,12 @@ class Stepper {
115
113
  return this.current === this.steps.length - 1;
116
114
  }
117
115
  destroy() {
118
- this.abortController.abort();
116
+ this.listeners.destroy();
119
117
  if (this.injectedConnectors) {
120
- this.connectors.forEach(c => c.remove());
118
+ this.connectors.forEach((c) => c.remove());
121
119
  this.connectors = [];
122
120
  }
123
- this.steps.forEach(step => step.removeAttribute('aria-current'));
121
+ this.steps.forEach((step) => step.removeAttribute('aria-current'));
124
122
  }
125
123
  }
126
124
  export { Stepper };
package/js/table.d.ts CHANGED
@@ -23,7 +23,7 @@ declare class Table {
23
23
  private tableBody;
24
24
  private tableHeader;
25
25
  private paginationContainer;
26
- private abortController;
26
+ private listeners;
27
27
  constructor(elementOrSelector: string | HTMLElement, options?: TableOptions);
28
28
  private parseTableFromDOM;
29
29
  private init;
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;