@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/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
@@ -4,20 +4,23 @@ interface ToastOptions {
4
4
  header?: string;
5
5
  type?: ToastType;
6
6
  closeable?: boolean;
7
+ iconBasePath?: string;
7
8
  }
8
9
  declare class Toast {
9
10
  private readonly content;
10
11
  private readonly header;
11
12
  private readonly type?;
12
13
  private readonly closeable;
14
+ private readonly iconBasePath;
13
15
  private readonly closureIcon;
14
16
  private readonly template;
15
17
  private toastElement;
16
18
  private timerId;
19
+ private openListeners;
17
20
  constructor(options: ToastOptions);
18
21
  constructor(content: string, header?: string, type?: ToastType, closeable?: boolean);
19
22
  show(ms?: number): void;
20
- hide: () => void;
23
+ hide(): void;
21
24
  private startTimer;
22
25
  destroy(): void;
23
26
  private buildTemplate;
package/js/toast.js CHANGED
@@ -1,26 +1,32 @@
1
1
  import { escapeHtml } from './utils.js';
2
+ import { ListenerGroup } from './listeners.js';
2
3
  class Toast {
3
4
  content;
4
5
  header;
5
6
  type;
6
7
  closeable;
7
- closureIcon = '<div class="icon icon-close close"></div>';
8
+ iconBasePath;
9
+ closureIcon;
8
10
  template;
9
11
  toastElement = null;
10
12
  timerId = null;
13
+ openListeners = null;
11
14
  constructor(contentOrOptions, header = '', type, closeable = true) {
12
15
  if (typeof contentOrOptions === 'object') {
13
16
  this.content = contentOrOptions.content;
14
17
  this.header = contentOrOptions.header ?? '';
15
18
  this.type = contentOrOptions.type;
16
19
  this.closeable = contentOrOptions.closeable ?? true;
20
+ this.iconBasePath = contentOrOptions.iconBasePath ?? 'svg-icons/';
17
21
  }
18
22
  else {
19
23
  this.content = contentOrOptions;
20
24
  this.header = header;
21
25
  this.type = type;
22
26
  this.closeable = closeable;
27
+ this.iconBasePath = 'svg-icons/';
23
28
  }
29
+ this.closureIcon = `<svg class="icon-svg close" aria-hidden="true"><use href="${this.iconBasePath}icons.svg#close"/></svg>`;
24
30
  this.template = this.buildTemplate();
25
31
  }
26
32
  show(ms) {
@@ -37,7 +43,8 @@ class Toast {
37
43
  this.toastElement?.classList.add('show');
38
44
  const closeButton = this.toastElement?.querySelector('.close');
39
45
  if (closeButton) {
40
- closeButton.addEventListener('click', this.hide);
46
+ this.openListeners = new ListenerGroup();
47
+ closeButton.addEventListener('click', () => this.hide(), { signal: this.openListeners.signal });
41
48
  }
42
49
  if (ms !== undefined && ms > 0) {
43
50
  this.startTimer(ms);
@@ -45,18 +52,19 @@ class Toast {
45
52
  });
46
53
  });
47
54
  }
48
- hide = () => {
55
+ hide() {
49
56
  if (this.timerId !== null) {
50
57
  clearTimeout(this.timerId);
51
58
  this.timerId = null;
52
59
  }
53
60
  this.toastElement?.classList.remove('show');
61
+ this.openListeners?.destroy();
62
+ this.openListeners = null;
54
63
  setTimeout(() => {
55
- this.toastElement?.querySelector('.close')?.removeEventListener('click', this.hide);
56
64
  this.toastElement?.remove();
57
65
  this.toastElement = null;
58
66
  }, 150);
59
- };
67
+ }
60
68
  startTimer(ms, elapsed = 0) {
61
69
  const stepSize = 250;
62
70
  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;
package/js/tree.js CHANGED
@@ -22,9 +22,7 @@ class TreeComponent {
22
22
  selectedNode;
23
23
  options;
24
24
  constructor(elementOrSelector, data, options = {}) {
25
- const container = typeof elementOrSelector === 'string'
26
- ? document.querySelector(elementOrSelector)
27
- : elementOrSelector;
25
+ const container = typeof elementOrSelector === 'string' ? document.querySelector(elementOrSelector) : elementOrSelector;
28
26
  if (!container) {
29
27
  throw new Error(`TreeComponent: Element not found for selector "${elementOrSelector}"`);
30
28
  }
@@ -32,14 +30,11 @@ class TreeComponent {
32
30
  this.data = data;
33
31
  this.selectedNode = null;
34
32
  this.options = options;
35
- this.init();
36
- }
37
- init() {
38
33
  this.render();
39
34
  }
40
35
  render() {
41
36
  this.container.innerHTML = '';
42
- this.data.forEach(node => {
37
+ this.data.forEach((node) => {
43
38
  this.renderNode(node, this.container);
44
39
  });
45
40
  }
@@ -107,7 +102,7 @@ class TreeComponent {
107
102
  childrenUl.classList.add('expanded');
108
103
  childrenUl.style.height = 'auto';
109
104
  }
110
- node.children.forEach(child => {
105
+ node.children.forEach((child) => {
111
106
  this.renderNode(child, childrenUl);
112
107
  });
113
108
  return childrenUl;
@@ -126,13 +121,13 @@ class TreeComponent {
126
121
  }
127
122
  }
128
123
  expandChildren(container) {
129
- container.style.height = container.scrollHeight + 'px';
124
+ container.style.height = `${container.scrollHeight}px`;
130
125
  container.addEventListener('transitionend', () => {
131
126
  container.style.height = 'auto';
132
127
  }, { once: true });
133
128
  }
134
129
  collapseChildren(container) {
135
- container.style.height = container.offsetHeight + 'px';
130
+ container.style.height = `${container.offsetHeight}px`;
136
131
  requestAnimationFrame(() => {
137
132
  container.style.height = '0';
138
133
  });
@@ -172,7 +167,7 @@ class TreeComponent {
172
167
  });
173
168
  }
174
169
  traverseNodes(nodes, callback) {
175
- nodes.forEach(node => {
170
+ nodes.forEach((node) => {
176
171
  callback(node);
177
172
  if (node.children.length > 0) {
178
173
  this.traverseNodes(node.children, callback);
package/js/utils.js CHANGED
@@ -9,10 +9,10 @@ const utils = {
9
9
  },
10
10
  value(element) {
11
11
  if (element.hasAttribute('value')) {
12
- return element.getAttribute('value') || '';
12
+ return element.getAttribute('value');
13
13
  }
14
14
  if (element.hasAttribute('data-value')) {
15
- return element.getAttribute('data-value') || '';
15
+ return element.getAttribute('data-value');
16
16
  }
17
17
  return element.innerText;
18
18
  },
@@ -26,11 +26,11 @@ const utils = {
26
26
  return undefined;
27
27
  },
28
28
  isList(element) {
29
- return NodeList.prototype.isPrototypeOf(element);
29
+ return element instanceof NodeList;
30
30
  },
31
31
  isHidden(element) {
32
32
  return element.offsetParent === null;
33
- }
33
+ },
34
34
  };
35
35
  function escapeHtml(text) {
36
36
  const div = document.createElement('div');
@@ -40,11 +40,10 @@ function escapeHtml(text) {
40
40
  function sanitizeHtml(html) {
41
41
  const parser = new DOMParser();
42
42
  const doc = parser.parseFromString(html, 'text/html');
43
- doc.querySelectorAll('script, style, iframe, object, embed').forEach(el => el.remove());
44
- doc.querySelectorAll('*').forEach(el => {
43
+ doc.querySelectorAll('script, style, iframe, object, embed').forEach((el) => el.remove());
44
+ doc.querySelectorAll('*').forEach((el) => {
45
45
  for (const attr of Array.from(el.attributes)) {
46
- if (attr.name.startsWith('on') ||
47
- attr.value.trim().toLowerCase().startsWith('javascript:')) {
46
+ if (attr.name.startsWith('on') || attr.value.trim().toLowerCase().startsWith('javascript:')) {
48
47
  el.removeAttribute(attr.name);
49
48
  }
50
49
  }
@@ -34,7 +34,7 @@ declare class VirtualDropdown {
34
34
  private filteredOptions;
35
35
  private isOpen;
36
36
  private scrollTop;
37
- private abortController;
37
+ private listeners;
38
38
  constructor(config: VirtualDropdownConfig);
39
39
  private init;
40
40
  private renderBase;
@@ -1,4 +1,5 @@
1
1
  import { escapeHtml } from './utils.js';
2
+ import { ListenerGroup } from './listeners.js';
2
3
  class VirtualDropdown {
3
4
  container;
4
5
  options;
@@ -21,11 +22,9 @@ class VirtualDropdown {
21
22
  filteredOptions;
22
23
  isOpen;
23
24
  scrollTop;
24
- abortController = new AbortController();
25
+ listeners = new ListenerGroup();
25
26
  constructor(config) {
26
- const containerElement = typeof config.container === 'string'
27
- ? document.querySelector(config.container)
28
- : config.container;
27
+ const containerElement = typeof config.container === 'string' ? document.querySelector(config.container) : config.container;
29
28
  if (!containerElement) {
30
29
  throw new Error('Container element not found');
31
30
  }
@@ -34,8 +33,8 @@ class VirtualDropdown {
34
33
  this.multiSelect = config.multiSelect ?? false;
35
34
  this.searchable = config.searchable ?? false;
36
35
  this.placeholder = config.placeholder || 'Select...';
37
- this.renderLimit = config.renderLimit || 20;
38
- this.itemHeight = config.itemHeight || 40;
36
+ this.renderLimit = config.renderLimit ?? 20;
37
+ this.itemHeight = config.itemHeight ?? 40;
39
38
  this.onSelect = config.onSelect ?? null;
40
39
  this.anchorName = `--vd-${Math.random().toString(36).slice(2, 9)}`;
41
40
  this.selectedValues = new Set();
@@ -87,7 +86,7 @@ class VirtualDropdown {
87
86
  return element;
88
87
  }
89
88
  bindEvents() {
90
- const sig = { signal: this.abortController.signal };
89
+ const sig = { signal: this.listeners.signal };
91
90
  this.trigger.addEventListener('click', () => this.toggle(), sig);
92
91
  this.trigger.addEventListener('keydown', (e) => {
93
92
  if (e.key === 'Enter' || e.key === ' ') {
@@ -122,7 +121,12 @@ class VirtualDropdown {
122
121
  }, sig);
123
122
  }
124
123
  toggle() {
125
- this.isOpen ? this.close() : this.open();
124
+ if (this.isOpen) {
125
+ this.close();
126
+ }
127
+ else {
128
+ this.open();
129
+ }
126
130
  }
127
131
  open() {
128
132
  this.isOpen = true;
@@ -146,7 +150,7 @@ class VirtualDropdown {
146
150
  }
147
151
  else {
148
152
  const lowerQuery = query.toLowerCase();
149
- this.filteredOptions = this.options.filter(opt => opt.label.toLowerCase().includes(lowerQuery));
153
+ this.filteredOptions = this.options.filter((opt) => opt.label.toLowerCase().includes(lowerQuery));
150
154
  }
151
155
  this.listWrapper.scrollTop = 0;
152
156
  this.scrollTop = 0;
@@ -180,29 +184,25 @@ class VirtualDropdown {
180
184
  `;
181
185
  })
182
186
  .join('');
183
- this.content.querySelectorAll('.dropdown-item').forEach(item => {
184
- const handleItemClick = (e) => {
187
+ this.content.querySelectorAll('.dropdown-item').forEach((item) => {
188
+ item.addEventListener('click', (e) => {
185
189
  e.stopPropagation();
186
190
  const value = item.dataset.value;
187
- if (value) {
191
+ if (value)
188
192
  this.handleSelect(value);
189
- }
190
- };
191
- const handleItemKeydown = (e) => {
193
+ });
194
+ item.addEventListener('keydown', (e) => {
192
195
  if (e.key === 'Enter' || e.key === ' ') {
193
196
  e.preventDefault();
194
197
  const value = item.dataset.value;
195
- if (value) {
198
+ if (value)
196
199
  this.handleSelect(value);
197
- }
198
200
  }
199
- };
200
- item.addEventListener('click', handleItemClick);
201
- item.addEventListener('keydown', handleItemKeydown);
201
+ });
202
202
  });
203
203
  }
204
204
  handleSelect(valueString) {
205
- const selectedOpt = this.filteredOptions.find(o => String(o.value) === valueString);
205
+ const selectedOpt = this.filteredOptions.find((o) => String(o.value) === valueString);
206
206
  if (!selectedOpt)
207
207
  return;
208
208
  const val = selectedOpt.value;
@@ -221,9 +221,7 @@ class VirtualDropdown {
221
221
  this.close();
222
222
  }
223
223
  this.updateTrigger();
224
- if (this.onSelect) {
225
- this.onSelect(Array.from(this.selectedValues));
226
- }
224
+ this.onSelect?.(Array.from(this.selectedValues));
227
225
  }
228
226
  updateTrigger() {
229
227
  if (this.selectedValues.size === 0) {
@@ -238,7 +236,7 @@ class VirtualDropdown {
238
236
  }
239
237
  else {
240
238
  const val = Array.from(this.selectedValues)[0];
241
- const opt = this.options.find(o => o.value === val);
239
+ const opt = this.options.find((o) => o.value === val);
242
240
  this.triggerText.textContent = opt ? opt.label : String(val);
243
241
  }
244
242
  }
@@ -248,8 +246,8 @@ class VirtualDropdown {
248
246
  }
249
247
  setValue(values) {
250
248
  this.selectedValues.clear();
251
- values.forEach(val => {
252
- if (this.options.some(opt => opt.value === val)) {
249
+ values.forEach((val) => {
250
+ if (this.options.some((opt) => opt.value === val)) {
253
251
  this.selectedValues.add(val);
254
252
  }
255
253
  });
@@ -268,7 +266,7 @@ class VirtualDropdown {
268
266
  destroy() {
269
267
  if (this.isOpen)
270
268
  this.menu.hidePopover();
271
- this.abortController.abort();
269
+ this.listeners.destroy();
272
270
  this.container.innerHTML = '';
273
271
  this.container.classList.remove('custom-dropdown', 'open');
274
272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dodlhuat/basix",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "Basix is intended as a starter for the rapid development of a design. Each design element can be added individually to include only the data required. It is using plain javascript / typescript and therefore is not dependent on any plugin.",
5
5
  "exports": {
6
6
  "./css/*": "./css/*",
@@ -36,6 +36,7 @@
36
36
  "eslint": "^10.5.0",
37
37
  "eslint-config-prettier": "^10.1.8",
38
38
  "prettier": "^3.8.4",
39
+ "sass": "^1.100.0",
39
40
  "typescript": "^6.0.0",
40
41
  "typescript-eslint": "^8.61.0"
41
42
  }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M530-481 332-679l43-43 241 241-241 241-43-43 198-198Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M480-344 240-584l43-43 197 197 197-197 43 43-240 240Z"/></svg>