@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/popover.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { computePosition } from './position.js';
2
2
  import { sanitizeHtml } from './utils.js';
3
+ import { ListenerGroup } from './listeners.js';
3
4
  const ARROW_SIZE = 6;
4
5
  class Popover {
5
6
  static openPopovers = new Set();
@@ -7,12 +8,11 @@ class Popover {
7
8
  trigger;
8
9
  opts;
9
10
  popoverEl = null;
10
- _isOpen = false;
11
11
  hoverTimer = null;
12
+ listeners = new ListenerGroup();
13
+ openListeners = null;
12
14
  constructor(triggerEl, options) {
13
- const el = typeof triggerEl === 'string'
14
- ? document.querySelector(triggerEl)
15
- : triggerEl;
15
+ const el = typeof triggerEl === 'string' ? document.querySelector(triggerEl) : triggerEl;
16
16
  if (!el)
17
17
  throw new Error('Popover: trigger element not found');
18
18
  this.trigger = el;
@@ -31,9 +31,11 @@ class Popover {
31
31
  };
32
32
  this.attachTrigger();
33
33
  }
34
- get isOpen() { return this._isOpen; }
34
+ get isOpen() {
35
+ return this.popoverEl !== null;
36
+ }
35
37
  open() {
36
- if (this._isOpen)
38
+ if (this.popoverEl)
37
39
  return;
38
40
  if (this.opts.triggerMode === 'click')
39
41
  Popover.closeAll();
@@ -42,40 +44,47 @@ class Popover {
42
44
  this.reposition();
43
45
  requestAnimationFrame(() => {
44
46
  this.popoverEl?.classList.add('is-open');
45
- this._isOpen = true;
46
47
  Popover.openPopovers.add(this);
47
48
  this.opts.onOpen();
49
+ this.openListeners = new ListenerGroup();
50
+ const openSig = { signal: this.openListeners.signal };
48
51
  if (this.opts.closeOnOutsideClick)
49
- document.addEventListener('pointerdown', this.onOutsideClick, { capture: true });
52
+ document.addEventListener('pointerdown', (e) => this.onOutsideClick(e), { ...openSig, capture: true });
50
53
  if (this.opts.closeOnEscape)
51
- document.addEventListener('keydown', this.onEscape);
54
+ document.addEventListener('keydown', (e) => this.onEscape(e), openSig);
52
55
  });
53
56
  }
54
57
  close() {
55
- if (!this._isOpen || !this.popoverEl)
58
+ if (!this.popoverEl)
56
59
  return;
57
60
  this.popoverEl.classList.remove('is-open');
58
- this._isOpen = false;
59
61
  Popover.openPopovers.delete(this);
60
62
  this.opts.onClose();
61
- document.removeEventListener('pointerdown', this.onOutsideClick, { capture: true });
62
- document.removeEventListener('keydown', this.onEscape);
63
+ this.openListeners?.destroy();
64
+ this.openListeners = null;
63
65
  this.trigger.removeAttribute('aria-expanded');
64
66
  this.trigger.removeAttribute('aria-controls');
65
67
  const el = this.popoverEl;
66
68
  setTimeout(() => el.remove(), 200);
67
69
  this.popoverEl = null;
68
70
  }
69
- toggle() { this._isOpen ? this.close() : this.open(); }
71
+ toggle() {
72
+ if (this.popoverEl) {
73
+ this.close();
74
+ }
75
+ else {
76
+ this.open();
77
+ }
78
+ }
70
79
  destroy() {
71
80
  this.close();
72
- this.detachTrigger();
81
+ this.listeners.destroy();
73
82
  }
74
83
  static closeAll() {
75
- Popover.openPopovers.forEach(p => p.close());
84
+ Popover.openPopovers.forEach((p) => p.close());
76
85
  }
77
86
  static initAll() {
78
- document.querySelectorAll('[data-popover]').forEach(trigger => {
87
+ document.querySelectorAll('[data-popover]').forEach((trigger) => {
79
88
  const sel = trigger.getAttribute('data-popover');
80
89
  if (!sel)
81
90
  return;
@@ -100,9 +109,7 @@ class Popover {
100
109
  el.setAttribute('data-arrow', String(this.opts.arrow));
101
110
  const hasStructure = /class="popover-(header|body|footer|menu)/.test(this.opts.content);
102
111
  const safeContent = sanitizeHtml(this.opts.content);
103
- el.innerHTML = hasStructure
104
- ? safeContent
105
- : `<div class="popover-body">${safeContent}</div>`;
112
+ el.innerHTML = hasStructure ? safeContent : `<div class="popover-body">${safeContent}</div>`;
106
113
  this.trigger.setAttribute('aria-expanded', 'true');
107
114
  this.trigger.setAttribute('aria-controls', id);
108
115
  return el;
@@ -123,37 +130,35 @@ class Popover {
123
130
  this.popoverEl.style.left = `${left}px`;
124
131
  this.popoverEl.style.top = `${top}px`;
125
132
  }
126
- onClick = () => { this.toggle(); };
127
- onMouseEnter = () => {
133
+ onClick() {
134
+ this.toggle();
135
+ }
136
+ onMouseEnter() {
128
137
  if (this.hoverTimer !== null)
129
138
  clearTimeout(this.hoverTimer);
130
139
  this.open();
131
- };
132
- onMouseLeave = () => {
140
+ }
141
+ onMouseLeave() {
133
142
  this.hoverTimer = window.setTimeout(() => this.close(), 120);
134
- };
135
- onOutsideClick = (e) => {
143
+ }
144
+ onOutsideClick(e) {
136
145
  const t = e.target;
137
146
  if (!this.popoverEl?.contains(t) && !this.trigger.contains(t))
138
147
  this.close();
139
- };
140
- onEscape = (e) => {
148
+ }
149
+ onEscape(e) {
141
150
  if (e.key === 'Escape')
142
151
  this.close();
143
- };
152
+ }
144
153
  attachTrigger() {
154
+ const sig = { signal: this.listeners.signal };
145
155
  if (this.opts.triggerMode === 'click') {
146
- this.trigger.addEventListener('click', this.onClick);
156
+ this.trigger.addEventListener('click', () => this.onClick(), sig);
147
157
  }
148
158
  else {
149
- this.trigger.addEventListener('mouseenter', this.onMouseEnter);
150
- this.trigger.addEventListener('mouseleave', this.onMouseLeave);
159
+ this.trigger.addEventListener('mouseenter', () => this.onMouseEnter(), sig);
160
+ this.trigger.addEventListener('mouseleave', () => this.onMouseLeave(), sig);
151
161
  }
152
162
  }
153
- detachTrigger() {
154
- this.trigger.removeEventListener('click', this.onClick);
155
- this.trigger.removeEventListener('mouseenter', this.onMouseEnter);
156
- this.trigger.removeEventListener('mouseleave', this.onMouseLeave);
157
- }
158
163
  }
159
164
  export { Popover };
package/js/position.js CHANGED
@@ -13,7 +13,7 @@ function bestPlacement(trigger, floating, offset) {
13
13
  return 'right';
14
14
  if (space.left >= floating.width)
15
15
  return 'left';
16
- return (Object.entries(space).sort((a, b) => b[1] - a[1])[0][0]);
16
+ return Object.entries(space).sort((a, b) => b[1] - a[1])[0][0];
17
17
  }
18
18
  function maybeFlip(placement, trigger, floating, offset) {
19
19
  const fits = {
@@ -23,15 +23,13 @@ function maybeFlip(placement, trigger, floating, offset) {
23
23
  right: window.innerWidth - trigger.right - offset >= floating.width,
24
24
  };
25
25
  const opp = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' };
26
- return (!fits[placement] && fits[opp[placement]]) ? opp[placement] : placement;
26
+ return !fits[placement] && fits[opp[placement]] ? opp[placement] : placement;
27
27
  }
28
28
  function computePosition(trigger, floating, opts) {
29
29
  const offset = opts.offset ?? 8;
30
30
  const margin = opts.margin ?? 8;
31
31
  const align = opts.align ?? 'center';
32
- const placement = opts.placement === 'auto'
33
- ? bestPlacement(trigger, floating, offset)
34
- : maybeFlip(opts.placement, trigger, floating, offset);
32
+ const placement = opts.placement === 'auto' ? bestPlacement(trigger, floating, offset) : maybeFlip(opts.placement, trigger, floating, offset);
35
33
  let left = 0, top = 0;
36
34
  switch (placement) {
37
35
  case 'top':
package/js/push-menu.d.ts CHANGED
@@ -6,11 +6,17 @@ interface PushMenuElements {
6
6
  controlIcon: HTMLElement | null;
7
7
  backdrop: HTMLElement | null;
8
8
  }
9
+ interface PushMenuOptions {
10
+ iconBasePath?: string;
11
+ }
9
12
  declare class PushMenu {
13
+ private static iconBasePath;
10
14
  private static elements;
11
15
  private static initialized;
12
16
  private static panelStack;
13
- static init(): void;
17
+ private static listeners;
18
+ private static clickNavListeners;
19
+ static init(options?: PushMenuOptions): void;
14
20
  private static buildPanels;
15
21
  private static extractSubPanels;
16
22
  static openPanel(panel: HTMLElement): void;
@@ -26,4 +32,4 @@ declare class PushMenu {
26
32
  static destroy(): void;
27
33
  static refresh(): void;
28
34
  }
29
- export { PushMenu, type PushMenuElements };
35
+ export { PushMenu, type PushMenuElements, type PushMenuOptions };
package/js/push-menu.js CHANGED
@@ -1,26 +1,32 @@
1
+ import { ListenerGroup } from './listeners.js';
1
2
  class PushMenu {
3
+ static iconBasePath = 'svg-icons/';
2
4
  static elements = {
3
5
  navigation: null,
4
6
  content: null,
5
7
  menu: null,
6
8
  header: null,
7
9
  controlIcon: null,
8
- backdrop: null
10
+ backdrop: null,
9
11
  };
10
12
  static initialized = false;
11
13
  static panelStack = [];
12
- static init() {
14
+ static listeners = new ListenerGroup();
15
+ static clickNavListeners = null;
16
+ static init(options = {}) {
13
17
  if (this.initialized) {
14
18
  console.warn('PushMenu: Already initialized');
15
19
  return;
16
20
  }
21
+ this.iconBasePath = options.iconBasePath ?? 'svg-icons/';
17
22
  this.refresh();
18
23
  if (!this.elements.navigation || !this.elements.content) {
19
24
  throw new Error('PushMenu: Required elements not found (.navigation, .push-content)');
20
25
  }
21
26
  this.buildPanels();
22
- this.elements.navigation.addEventListener('change', this.handleNavigationChange);
23
- this.elements.backdrop?.addEventListener('click', this.handleBackdropClick);
27
+ const sig = { signal: this.listeners.signal };
28
+ this.elements.navigation.addEventListener('change', () => this.handleNavigationChange(), sig);
29
+ this.elements.backdrop?.addEventListener('click', () => this.handleBackdropClick(), sig);
24
30
  this.initialized = true;
25
31
  }
26
32
  static buildPanels() {
@@ -56,7 +62,7 @@ class PushMenu {
56
62
  const backBtn = document.createElement('button');
57
63
  backBtn.classList.add('push-menu-back');
58
64
  backBtn.setAttribute('aria-label', 'Back');
59
- backBtn.innerHTML = `<span class="icon icon-navigate_before" aria-hidden="true"></span>`;
65
+ backBtn.innerHTML = `<svg class="icon-svg" aria-hidden="true"><use href="${PushMenu.iconBasePath}icons.svg#chevron_left"/></svg>`;
60
66
  header.addEventListener('click', () => PushMenu.goBack());
61
67
  const titleEl = document.createElement('span');
62
68
  titleEl.classList.add('push-menu-panel-title');
@@ -72,7 +78,7 @@ class PushMenu {
72
78
  const chevron = document.createElement('span');
73
79
  chevron.classList.add('push-menu-chevron');
74
80
  chevron.setAttribute('aria-hidden', 'true');
75
- chevron.innerHTML = `<span class="icon icon-navigate_next" aria-hidden="true"></span>`;
81
+ chevron.innerHTML = `<svg class="icon-svg" aria-hidden="true"><use href="${PushMenu.iconBasePath}icons.svg#chevron_right"/></svg>`;
76
82
  trigger.appendChild(chevron);
77
83
  if (parentAnchor) {
78
84
  parentAnchor.replaceWith(trigger);
@@ -117,17 +123,19 @@ class PushMenu {
117
123
  }
118
124
  }, 300);
119
125
  }
120
- static handleNavigationChange = () => {
126
+ static handleNavigationChange() {
121
127
  const isPushed = this.elements.content?.classList.contains('pushed') ?? false;
122
128
  if (!isPushed) {
123
- this.elements.content?.addEventListener('click', this.clickNav);
129
+ this.clickNavListeners = new ListenerGroup();
130
+ this.elements.content?.addEventListener('click', () => this.clickNav(), { signal: this.clickNavListeners.signal });
124
131
  }
125
132
  else {
126
- this.elements.content?.removeEventListener('click', this.clickNav);
133
+ this.clickNavListeners?.destroy();
134
+ this.clickNavListeners = null;
127
135
  this.resetPanels();
128
136
  }
129
137
  this.pushToggle();
130
- };
138
+ }
131
139
  static pushToggle() {
132
140
  if (!this.elements.content || !this.elements.menu) {
133
141
  throw new Error('PushMenu: Required elements not found (.push-content, .push-menu)');
@@ -138,24 +146,27 @@ class PushMenu {
138
146
  this.elements.header?.classList.toggle('pushed', !isPushed);
139
147
  this.elements.backdrop?.classList.toggle('pushed', !isPushed);
140
148
  if (this.elements.controlIcon) {
141
- if (isPushed) {
142
- this.elements.controlIcon.classList.remove('icon-menu_open');
143
- this.elements.controlIcon.classList.add('icon-menu');
149
+ const iconName = isPushed ? 'menu' : 'menu_open';
150
+ const useEl = this.elements.controlIcon.querySelector('use');
151
+ if (useEl) {
152
+ const existingHref = useEl.getAttribute('href') ?? '';
153
+ const basePath = existingHref.includes('#') ? existingHref.split('#')[0] : PushMenu.iconBasePath + 'icons.svg';
154
+ useEl.setAttribute('href', `${basePath}#${iconName}`);
144
155
  }
145
156
  else {
146
- this.elements.controlIcon.classList.add('icon-menu_open');
147
- this.elements.controlIcon.classList.remove('icon-menu');
157
+ this.elements.controlIcon.classList.remove('icon-menu_open', 'icon-menu');
158
+ this.elements.controlIcon.classList.add(`icon-${iconName}`);
148
159
  }
149
160
  }
150
161
  }
151
- static clickNav = () => {
162
+ static clickNav() {
152
163
  PushMenu.elements.navigation.click();
153
- };
154
- static handleBackdropClick = () => {
164
+ }
165
+ static handleBackdropClick() {
155
166
  if (PushMenu.isOpen()) {
156
167
  PushMenu.elements.navigation.click();
157
168
  }
158
- };
169
+ }
159
170
  static open() {
160
171
  if (!this.elements.content?.classList.contains('pushed')) {
161
172
  this.pushToggle();
@@ -172,9 +183,9 @@ class PushMenu {
172
183
  static destroy() {
173
184
  if (!this.initialized)
174
185
  return;
175
- this.elements.navigation?.removeEventListener('change', this.handleNavigationChange);
176
- this.elements.content?.removeEventListener('click', this.clickNav);
177
- this.elements.backdrop?.removeEventListener('click', this.handleBackdropClick);
186
+ this.listeners.reset();
187
+ this.clickNavListeners?.destroy();
188
+ this.clickNavListeners = null;
178
189
  this.close();
179
190
  this.elements = {
180
191
  navigation: null,
@@ -182,7 +193,7 @@ class PushMenu {
182
193
  menu: null,
183
194
  header: null,
184
195
  controlIcon: null,
185
- backdrop: null
196
+ backdrop: null,
186
197
  };
187
198
  this.panelStack = [];
188
199
  this.initialized = false;
@@ -1,6 +1,6 @@
1
1
  declare class RangeSlider {
2
2
  private readonly input;
3
- private abortController;
3
+ private listeners;
4
4
  constructor(input: HTMLInputElement);
5
5
  static initAll(selector?: string): void;
6
6
  private update;
@@ -12,7 +12,7 @@ declare class RangeSliderRange {
12
12
  private readonly endInput;
13
13
  private readonly fill;
14
14
  private readonly fillCreatedByUs;
15
- private abortController;
15
+ private listeners;
16
16
  constructor(container: HTMLElement);
17
17
  static initAll(selector?: string): void;
18
18
  values(): [number, number];
@@ -1,10 +1,11 @@
1
+ import { ListenerGroup } from './listeners.js';
1
2
  class RangeSlider {
2
3
  input;
3
- abortController = new AbortController();
4
+ listeners = new ListenerGroup();
4
5
  constructor(input) {
5
6
  this.input = input;
6
7
  this.update();
7
- this.input.addEventListener('input', () => this.update(), { signal: this.abortController.signal });
8
+ this.input.addEventListener('input', () => this.update(), { signal: this.listeners.signal });
8
9
  }
9
10
  static initAll(selector = '.range-slider input[type="range"]') {
10
11
  document.querySelectorAll(selector).forEach((input) => {
@@ -18,7 +19,7 @@ class RangeSlider {
18
19
  this.input.style.setProperty('--range-fill', `${pct}%`);
19
20
  }
20
21
  destroy() {
21
- this.abortController.abort();
22
+ this.listeners.destroy();
22
23
  this.input.style.removeProperty('--range-fill');
23
24
  }
24
25
  }
@@ -28,7 +29,7 @@ class RangeSliderRange {
28
29
  endInput;
29
30
  fill;
30
31
  fillCreatedByUs;
31
- abortController = new AbortController();
32
+ listeners = new ListenerGroup();
32
33
  constructor(container) {
33
34
  const inputs = container.querySelectorAll('input[type="range"]');
34
35
  if (inputs.length !== 2) {
@@ -45,7 +46,7 @@ class RangeSliderRange {
45
46
  container.insertBefore(this.fill, container.firstChild);
46
47
  }
47
48
  this.update();
48
- const signal = this.abortController.signal;
49
+ const signal = this.listeners.signal;
49
50
  this.startInput.addEventListener('input', () => this.handleInput(this.startInput), { signal });
50
51
  this.endInput.addEventListener('input', () => this.handleInput(this.endInput), { signal });
51
52
  }
@@ -75,7 +76,7 @@ class RangeSliderRange {
75
76
  this.container.style.setProperty('--range-end', `${endPct}%`);
76
77
  }
77
78
  destroy() {
78
- this.abortController.abort();
79
+ this.listeners.destroy();
79
80
  this.container.style.removeProperty('--range-start');
80
81
  this.container.style.removeProperty('--range-end');
81
82
  if (this.fillCreatedByUs) {
package/js/scroll.js CHANGED
@@ -4,14 +4,11 @@ class Scroll {
4
4
  const offset = fixedHeader ? fixedHeader.offsetHeight : 0;
5
5
  const settings = {
6
6
  behavior: 'smooth',
7
- offset: offset,
7
+ offset,
8
8
  block: 'start',
9
- ...options
9
+ ...options,
10
10
  };
11
- let el = target instanceof Element ? target : null;
12
- if (typeof target === 'string') {
13
- el = document.querySelector(target);
14
- }
11
+ const el = typeof target === 'string' ? document.querySelector(target) : target;
15
12
  if (!el)
16
13
  return;
17
14
  const rect = el.getBoundingClientRect();
@@ -19,7 +16,7 @@ class Scroll {
19
16
  const offsetTop = rect.top + scrollTop - settings.offset;
20
17
  window.scrollTo({
21
18
  top: offsetTop,
22
- behavior: settings.behavior
19
+ behavior: settings.behavior,
23
20
  });
24
21
  }
25
22
  }
package/js/scrollbar.d.ts CHANGED
@@ -9,19 +9,19 @@ declare class Scrollbar {
9
9
  private static activeInstance;
10
10
  private static globalListenersInstalled;
11
11
  private static instanceCount;
12
- private static globalListenerAbortController;
12
+ private static globalListeners;
13
13
  private readonly container;
14
14
  private readonly viewport;
15
15
  private readonly content;
16
16
  private readonly track;
17
17
  private readonly thumb;
18
18
  private readonly MIN_THUMB_HEIGHT;
19
- private readonly ro;
19
+ private readonly resizeObserver;
20
20
  private dragging;
21
21
  private activePointerId;
22
22
  private startPointerY;
23
23
  private startThumbTop;
24
- private abortController;
24
+ private listeners;
25
25
  private constructor();
26
26
  private getRequiredElements;
27
27
  private getMinThumbHeight;
package/js/scrollbar.js CHANGED
@@ -1,21 +1,22 @@
1
+ import { ListenerGroup } from './listeners.js';
1
2
  class Scrollbar {
2
3
  static instances = new WeakMap();
3
4
  static activeInstance = null;
4
5
  static globalListenersInstalled = false;
5
6
  static instanceCount = 0;
6
- static globalListenerAbortController = null;
7
+ static globalListeners = null;
7
8
  container;
8
9
  viewport;
9
10
  content;
10
11
  track;
11
12
  thumb;
12
13
  MIN_THUMB_HEIGHT;
13
- ro;
14
+ resizeObserver;
14
15
  dragging = false;
15
16
  activePointerId = null;
16
17
  startPointerY = 0;
17
18
  startThumbTop = 0;
18
- abortController = new AbortController();
19
+ listeners = new ListenerGroup();
19
20
  constructor(container) {
20
21
  this.container = container;
21
22
  const elements = this.getRequiredElements(container);
@@ -24,14 +25,14 @@ class Scrollbar {
24
25
  this.track = elements.track;
25
26
  this.thumb = elements.thumb;
26
27
  this.MIN_THUMB_HEIGHT = this.getMinThumbHeight();
27
- this.ro = new ResizeObserver(this.updateThumb);
28
+ this.resizeObserver = new ResizeObserver(() => this.updateThumb());
28
29
  this.attachEventListeners();
29
30
  Scrollbar.instances.set(container, this);
30
31
  Scrollbar.instanceCount++;
31
32
  if (!Scrollbar.globalListenersInstalled) {
32
33
  Scrollbar.installGlobalListeners();
33
34
  }
34
- requestAnimationFrame(this.updateThumb);
35
+ requestAnimationFrame(() => this.updateThumb());
35
36
  }
36
37
  getRequiredElements(container) {
37
38
  const viewport = container.querySelector('.viewport');
@@ -44,39 +45,37 @@ class Scrollbar {
44
45
  return { viewport, content, track, thumb };
45
46
  }
46
47
  getMinThumbHeight() {
47
- const cssValue = getComputedStyle(document.documentElement)
48
- .getPropertyValue('--thumb-min')
49
- .trim();
48
+ const cssValue = getComputedStyle(document.documentElement).getPropertyValue('--thumb-min').trim();
50
49
  const parsed = parseInt(cssValue, 10);
51
50
  const defaultMin = 28;
52
51
  const absoluteMin = 16;
53
52
  return Math.max(absoluteMin, parsed || defaultMin);
54
53
  }
55
54
  static installGlobalListeners() {
56
- const ac = new AbortController();
57
- Scrollbar.globalListenerAbortController = ac;
55
+ const listeners = new ListenerGroup();
56
+ Scrollbar.globalListeners = listeners;
58
57
  document.addEventListener('pointermove', (e) => {
59
58
  Scrollbar.activeInstance?.handlePointerMove(e);
60
- }, { passive: false, signal: ac.signal });
59
+ }, { passive: false, signal: listeners.signal });
61
60
  document.addEventListener('pointerup', (e) => {
62
61
  Scrollbar.activeInstance?.handlePointerUp(e);
63
- }, { signal: ac.signal });
62
+ }, { signal: listeners.signal });
64
63
  document.addEventListener('pointercancel', (e) => {
65
64
  Scrollbar.activeInstance?.handlePointerUp(e);
66
- }, { signal: ac.signal });
65
+ }, { signal: listeners.signal });
67
66
  Scrollbar.globalListenersInstalled = true;
68
67
  }
69
68
  attachEventListeners() {
70
- const sig = { signal: this.abortController.signal };
71
- this.viewport.addEventListener('scroll', this.updateThumb, { passive: true, signal: sig.signal });
72
- this.thumb.addEventListener('pointerdown', this.handleThumbPointerDown, sig);
73
- this.track.addEventListener('click', this.handleTrackClick, sig);
74
- this.container.addEventListener('wheel', this.handleContainerWheel, { passive: false, signal: sig.signal });
75
- window.addEventListener('resize', this.updateThumb, sig);
76
- this.ro.observe(this.viewport);
77
- this.ro.observe(this.content);
78
- }
79
- updateThumb = () => {
69
+ const sig = { signal: this.listeners.signal };
70
+ this.viewport.addEventListener('scroll', () => this.updateThumb(), { ...sig, passive: true });
71
+ this.thumb.addEventListener('pointerdown', (e) => this.handleThumbPointerDown(e), sig);
72
+ this.track.addEventListener('click', (e) => this.handleTrackClick(e), sig);
73
+ this.container.addEventListener('wheel', (e) => this.handleContainerWheel(e), { ...sig, passive: false });
74
+ window.addEventListener('resize', () => this.updateThumb(), sig);
75
+ this.resizeObserver.observe(this.viewport);
76
+ this.resizeObserver.observe(this.content);
77
+ }
78
+ updateThumb() {
80
79
  const viewportHeight = this.viewport.clientHeight;
81
80
  const contentHeight = this.content.scrollHeight;
82
81
  const trackHeight = this.track.clientHeight;
@@ -93,8 +92,8 @@ class Scrollbar {
93
92
  const scrollRatio = this.viewport.scrollTop / (maxScroll || 1);
94
93
  const thumbTop = scrollRatio * (maxThumbTop || 0);
95
94
  this.thumb.style.top = `${thumbTop}px`;
96
- };
97
- handleThumbPointerDown = (e) => {
95
+ }
96
+ handleThumbPointerDown(e) {
98
97
  e.preventDefault();
99
98
  this.dragging = true;
100
99
  this.activePointerId = e.pointerId;
@@ -110,8 +109,8 @@ class Scrollbar {
110
109
  const trackRect = this.track.getBoundingClientRect();
111
110
  this.startThumbTop = thumbRect.top - trackRect.top;
112
111
  document.body.style.userSelect = 'none';
113
- };
114
- handlePointerMove = (e) => {
112
+ }
113
+ handlePointerMove(e) {
115
114
  if (!this.dragging || this.activePointerId !== e.pointerId) {
116
115
  return;
117
116
  }
@@ -127,8 +126,8 @@ class Scrollbar {
127
126
  const maxScroll = contentHeight - viewportHeight;
128
127
  const scrollRatio = newThumbTop / (maxThumbTop || 1);
129
128
  this.viewport.scrollTop = scrollRatio * (maxScroll || 0);
130
- };
131
- handlePointerUp = (e) => {
129
+ }
130
+ handlePointerUp(e) {
132
131
  if (!this.dragging || this.activePointerId !== e.pointerId) {
133
132
  return;
134
133
  }
@@ -142,8 +141,8 @@ class Scrollbar {
142
141
  this.activePointerId = null;
143
142
  Scrollbar.activeInstance = null;
144
143
  document.body.style.userSelect = '';
145
- };
146
- handleTrackClick = (e) => {
144
+ }
145
+ handleTrackClick(e) {
147
146
  if (e.target === this.thumb) {
148
147
  return;
149
148
  }
@@ -160,8 +159,8 @@ class Scrollbar {
160
159
  const scrollRatio = clampedThumbTop / (maxThumbTop || 1);
161
160
  const scrollTop = scrollRatio * (maxScroll || 0);
162
161
  this.viewport.scrollTo({ top: scrollTop, behavior: 'smooth' });
163
- };
164
- handleContainerWheel = (e) => {
162
+ }
163
+ handleContainerWheel(e) {
165
164
  const { scrollTop, scrollHeight, clientHeight } = this.viewport;
166
165
  const scrollable = scrollHeight > clientHeight;
167
166
  const atTop = scrollTop === 0 && e.deltaY < 0;
@@ -170,25 +169,23 @@ class Scrollbar {
170
169
  e.preventDefault();
171
170
  }
172
171
  this.viewport.scrollTop += e.deltaY;
173
- };
172
+ }
174
173
  destroy() {
175
- this.abortController.abort();
176
- this.ro.disconnect();
174
+ this.listeners.destroy();
175
+ this.resizeObserver.disconnect();
177
176
  Scrollbar.instances.delete(this.container);
178
177
  if (Scrollbar.activeInstance === this) {
179
178
  Scrollbar.activeInstance = null;
180
179
  }
181
180
  Scrollbar.instanceCount--;
182
181
  if (Scrollbar.instanceCount === 0) {
183
- Scrollbar.globalListenerAbortController?.abort();
184
- Scrollbar.globalListenerAbortController = null;
182
+ Scrollbar.globalListeners?.destroy();
183
+ Scrollbar.globalListeners = null;
185
184
  Scrollbar.globalListenersInstalled = false;
186
185
  }
187
186
  }
188
187
  static create(elementOrSelector) {
189
- const container = typeof elementOrSelector === 'string'
190
- ? document.querySelector(elementOrSelector)
191
- : elementOrSelector;
188
+ const container = typeof elementOrSelector === 'string' ? document.querySelector(elementOrSelector) : elementOrSelector;
192
189
  if (!container) {
193
190
  throw new Error(`Scrollbar: Element not found for selector "${elementOrSelector}"`);
194
191
  }
@@ -199,7 +196,7 @@ class Scrollbar {
199
196
  }
200
197
  static initAll(selector) {
201
198
  const containers = document.querySelectorAll(selector);
202
- return Array.from(containers).map(container => Scrollbar.create(container));
199
+ return Array.from(containers).map((container) => Scrollbar.create(container));
203
200
  }
204
201
  static initOne(elementOrSelector) {
205
202
  return Scrollbar.create(elementOrSelector);
package/js/select.d.ts CHANGED
@@ -2,7 +2,7 @@ declare class Select {
2
2
  private readonly element;
3
3
  private readonly isMultiselect;
4
4
  private readonly dropdown;
5
- private readonly documentClickHandler;
5
+ private listeners;
6
6
  constructor(elementOrSelector: string | HTMLSelectElement);
7
7
  destroy(): void;
8
8
  value(): string | string[];