@dodlhuat/basix 1.4.2 → 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 +13 -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 +7 -6
  10. package/css/stepper.scss +2 -1
  11. package/css/style.css +84 -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 +2 -2
  57. package/js/range-slider.js +7 -6
  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/datepicker.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { computePosition } from './position.js';
2
+ import { ListenerGroup } from './listeners.js';
2
3
  class DatePicker {
4
+ static CLOCK_OUTER_RADIUS_PERCENT = 38;
5
+ static CLOCK_INNER_RADIUS_PERCENT = 23;
3
6
  input;
4
7
  options;
5
8
  currentDate;
@@ -14,12 +17,12 @@ class DatePicker {
14
17
  selectedMinutes;
15
18
  calendar;
16
19
  backdrop;
17
- handleDocumentClick;
18
- abortController = new AbortController();
20
+ clockMode = 'hours';
21
+ listeners = new ListenerGroup();
22
+ showListeners = null;
23
+ clockListeners = null;
19
24
  constructor(elementOrSelector, options = {}) {
20
- this.input = typeof elementOrSelector === 'string'
21
- ? document.querySelector(elementOrSelector)
22
- : elementOrSelector;
25
+ this.input = typeof elementOrSelector === 'string' ? document.querySelector(elementOrSelector) : elementOrSelector;
23
26
  if (!this.input) {
24
27
  throw new Error(`DatePicker: Element not found for selector "${elementOrSelector}"`);
25
28
  }
@@ -30,10 +33,7 @@ class DatePicker {
30
33
  timePicker,
31
34
  locales: {
32
35
  days: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
33
- months: [
34
- 'January', 'February', 'March', 'April', 'May', 'June',
35
- 'July', 'August', 'September', 'October', 'November', 'December'
36
- ]
36
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
37
37
  },
38
38
  format: timePicker
39
39
  ? (date) => {
@@ -43,7 +43,7 @@ class DatePicker {
43
43
  }
44
44
  : (date) => date.toDateString(),
45
45
  onSelect: () => { },
46
- ...options
46
+ ...options,
47
47
  };
48
48
  this.currentDate = new Date();
49
49
  this.selectedDate = null;
@@ -69,7 +69,6 @@ class DatePicker {
69
69
  this.backdrop = document.createElement('div');
70
70
  this.backdrop.className = 'datepicker-backdrop';
71
71
  document.body.appendChild(this.backdrop);
72
- this.backdrop.addEventListener('click', () => this.hide(), { signal: this.abortController.signal });
73
72
  }
74
73
  attachEvents() {
75
74
  const toggle = (e) => {
@@ -82,23 +81,19 @@ class DatePicker {
82
81
  this.show();
83
82
  }
84
83
  };
85
- const sig = { signal: this.abortController.signal };
84
+ const sig = { signal: this.listeners.signal };
86
85
  this.input?.addEventListener('click', toggle, sig);
87
86
  this.backdrop.addEventListener('click', (e) => {
88
87
  e.preventDefault();
89
88
  e.stopPropagation();
90
89
  this.hide();
91
90
  }, sig);
92
- this.handleDocumentClick = (e) => {
93
- if (this.calendar.classList.contains('mobile'))
94
- return;
95
- const target = e.target;
96
- if (!this.calendar.contains(target) && target !== this.input) {
97
- this.hide();
98
- }
99
- };
100
91
  }
101
92
  show() {
93
+ if (this.options.timePicker) {
94
+ this.clockMode = 'hours';
95
+ this.renderTimePicker();
96
+ }
102
97
  const isMobile = window.innerWidth <= 640;
103
98
  if (isMobile) {
104
99
  this.calendar.classList.add('mobile');
@@ -123,7 +118,13 @@ class DatePicker {
123
118
  }
124
119
  setTimeout(() => {
125
120
  if (this.calendar.classList.contains('visible')) {
126
- document.addEventListener('click', this.handleDocumentClick);
121
+ this.showListeners = new ListenerGroup();
122
+ document.addEventListener('click', (e) => {
123
+ const target = e.target;
124
+ if (!this.calendar.contains(target) && target !== this.input) {
125
+ this.hide();
126
+ }
127
+ }, { signal: this.showListeners.signal });
127
128
  }
128
129
  }, 0);
129
130
  }
@@ -133,7 +134,8 @@ class DatePicker {
133
134
  this.calendar.classList.remove('visible');
134
135
  this.backdrop.classList.remove('visible');
135
136
  document.body.style.overflow = '';
136
- document.removeEventListener('click', this.handleDocumentClick);
137
+ this.showListeners?.destroy();
138
+ this.showListeners = null;
137
139
  }
138
140
  render() {
139
141
  this.calendar.innerHTML = '';
@@ -151,8 +153,7 @@ class DatePicker {
151
153
  this.calendar.appendChild(header);
152
154
  this.calendar.appendChild(content);
153
155
  if (this.options.timePicker && this.viewMode === 'days') {
154
- const timeSection = this.createTimePicker();
155
- this.calendar.appendChild(timeSection);
156
+ this.calendar.appendChild(this.createTimePicker());
156
157
  const setBtn = document.createElement('button');
157
158
  setBtn.className = 'datepicker-set-btn';
158
159
  setBtn.textContent = 'Set';
@@ -163,6 +164,16 @@ class DatePicker {
163
164
  this.calendar.appendChild(setBtn);
164
165
  }
165
166
  }
167
+ renderTimePicker() {
168
+ if (!this.options.timePicker || this.viewMode !== 'days')
169
+ return;
170
+ const existing = this.calendar.querySelector('.datepicker-clock');
171
+ if (!existing) {
172
+ this.render();
173
+ return;
174
+ }
175
+ existing.replaceWith(this.createTimePicker());
176
+ }
166
177
  createHeader() {
167
178
  const header = document.createElement('div');
168
179
  header.className = 'datepicker-header';
@@ -242,6 +253,7 @@ class DatePicker {
242
253
  createMonthGrid() {
243
254
  const grid = document.createElement('div');
244
255
  grid.className = 'datepicker-grid-months';
256
+ const now = new Date();
245
257
  this.options?.locales?.months.forEach((month, index) => {
246
258
  const el = document.createElement('div');
247
259
  el.className = 'datepicker-month';
@@ -249,7 +261,7 @@ class DatePicker {
249
261
  if (index === this.viewMonth) {
250
262
  el.classList.add('selected');
251
263
  }
252
- if (index === new Date().getMonth() && this.viewYear === new Date().getFullYear()) {
264
+ if (index === now.getMonth() && this.viewYear === now.getFullYear()) {
253
265
  el.classList.add('current');
254
266
  }
255
267
  el.onclick = (e) => {
@@ -292,7 +304,7 @@ class DatePicker {
292
304
  const days = this.options?.locales?.days ?? ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
293
305
  const startDay = this.options.startDay ?? 0;
294
306
  const adjustedDays = [...days.slice(startDay), ...days.slice(0, startDay)];
295
- adjustedDays.forEach(day => {
307
+ adjustedDays.forEach((day) => {
296
308
  const el = document.createElement('div');
297
309
  el.className = 'datepicker-day-header';
298
310
  el.textContent = day;
@@ -353,88 +365,156 @@ class DatePicker {
353
365
  return grid;
354
366
  }
355
367
  createTimePicker() {
368
+ this.clockListeners?.destroy();
369
+ this.clockListeners = new ListenerGroup();
356
370
  const wrapper = document.createElement('div');
357
- wrapper.className = 'datepicker-time';
358
- const label = document.createElement('div');
359
- label.className = 'datepicker-time-label';
360
- label.textContent = 'Time';
361
- wrapper.appendChild(label);
362
- const controls = document.createElement('div');
363
- controls.className = 'datepicker-time-controls';
364
- const hoursSpinner = this.createSpinner(this.selectedHours, 0, 23, (value) => {
365
- this.selectedHours = value;
366
- this.applyTimeToSelection();
367
- });
368
- const separator = document.createElement('span');
369
- separator.className = 'datepicker-time-separator';
370
- separator.textContent = ':';
371
- const minutesSpinner = this.createSpinner(this.selectedMinutes, 0, 59, (value) => {
372
- this.selectedMinutes = value;
373
- this.applyTimeToSelection();
374
- });
375
- controls.appendChild(hoursSpinner);
376
- controls.appendChild(separator);
377
- controls.appendChild(minutesSpinner);
378
- wrapper.appendChild(controls);
371
+ wrapper.className = 'datepicker-clock';
372
+ wrapper.appendChild(this.createClockHeader());
373
+ wrapper.appendChild(this.createClockFace());
379
374
  return wrapper;
380
375
  }
381
- createSpinner(value, min, max, onChange) {
382
- const spinner = document.createElement('div');
383
- spinner.className = 'datepicker-time-spinner';
384
- const upBtn = document.createElement('button');
385
- upBtn.className = 'datepicker-time-btn';
386
- upBtn.innerHTML = '&#9650;';
387
- upBtn.onclick = (e) => {
376
+ createClockHeader() {
377
+ const header = document.createElement('div');
378
+ header.className = 'datepicker-clock-header';
379
+ const display = document.createElement('div');
380
+ display.className = 'datepicker-clock-display';
381
+ const hourSeg = document.createElement('span');
382
+ hourSeg.className = 'datepicker-clock-display-segment';
383
+ hourSeg.classList.toggle('active', this.clockMode === 'hours');
384
+ hourSeg.textContent = String(this.selectedHours).padStart(2, '0');
385
+ hourSeg.onclick = (e) => {
388
386
  e.stopPropagation();
389
- const next = value + 1 > max ? min : value + 1;
390
- onChange(next);
391
- this.render();
387
+ this.clockMode = 'hours';
388
+ this.renderTimePicker();
392
389
  };
393
- const display = document.createElement('input');
394
- display.className = 'datepicker-time-display';
395
- display.type = 'text';
396
- display.inputMode = 'numeric';
397
- display.value = String(value).padStart(2, '0');
398
- display.maxLength = 2;
399
- display.addEventListener('click', (e) => e.stopPropagation());
400
- display.addEventListener('focus', () => display.select());
401
- display.addEventListener('change', (e) => {
390
+ const separator = document.createElement('span');
391
+ separator.className = 'datepicker-clock-display-separator';
392
+ separator.textContent = ':';
393
+ const minuteSeg = document.createElement('span');
394
+ minuteSeg.className = 'datepicker-clock-display-segment';
395
+ minuteSeg.classList.toggle('active', this.clockMode === 'minutes');
396
+ minuteSeg.textContent = String(this.selectedMinutes).padStart(2, '0');
397
+ minuteSeg.onclick = (e) => {
402
398
  e.stopPropagation();
403
- let parsed = parseInt(display.value, 10);
404
- if (isNaN(parsed) || parsed < min || parsed > max) {
405
- display.value = String(value).padStart(2, '0');
406
- return;
399
+ this.clockMode = 'minutes';
400
+ this.renderTimePicker();
401
+ };
402
+ display.appendChild(hourSeg);
403
+ display.appendChild(separator);
404
+ display.appendChild(minuteSeg);
405
+ header.appendChild(display);
406
+ return header;
407
+ }
408
+ createClockFace() {
409
+ const face = document.createElement('div');
410
+ face.className = 'datepicker-clock-face';
411
+ const center = document.createElement('div');
412
+ center.className = 'datepicker-clock-center';
413
+ face.appendChild(center);
414
+ const hand = document.createElement('div');
415
+ hand.className = 'datepicker-clock-hand';
416
+ face.appendChild(hand);
417
+ if (this.clockMode === 'hours') {
418
+ for (let slot = 0; slot < 12; slot++) {
419
+ const outerValue = slot * 2;
420
+ const innerValue = slot * 2 + 1;
421
+ face.appendChild(this.createClockNumber(outerValue, this.clockPosition(slot, 12, DatePicker.CLOCK_OUTER_RADIUS_PERCENT), outerValue === this.selectedHours, false));
422
+ face.appendChild(this.createClockNumber(innerValue, this.clockPosition(slot, 12, DatePicker.CLOCK_INNER_RADIUS_PERCENT), innerValue === this.selectedHours, true));
407
423
  }
408
- onChange(parsed);
409
- this.render();
410
- });
411
- display.addEventListener('keydown', (e) => {
412
- if (e.key === 'ArrowUp') {
413
- e.preventDefault();
414
- const next = value + 1 > max ? min : value + 1;
415
- onChange(next);
416
- this.render();
424
+ }
425
+ else {
426
+ for (let slot = 0; slot < 12; slot++) {
427
+ const value = slot * 5;
428
+ const isSelected = Math.round(this.selectedMinutes / 5) % 12 === slot;
429
+ face.appendChild(this.createClockNumber(value, this.clockPosition(slot, 12, DatePicker.CLOCK_OUTER_RADIUS_PERCENT), isSelected, false));
417
430
  }
418
- else if (e.key === 'ArrowDown') {
419
- e.preventDefault();
420
- const next = value - 1 < min ? max : value - 1;
421
- onChange(next);
422
- this.render();
431
+ }
432
+ const activeValue = this.clockMode === 'hours' ? this.selectedHours : this.selectedMinutes;
433
+ this.positionHand(hand, activeValue);
434
+ this.bindClockDrag(face, hand);
435
+ return face;
436
+ }
437
+ createClockNumber(value, position, selected, inner) {
438
+ const number = document.createElement('div');
439
+ number.className = 'datepicker-clock-number';
440
+ number.classList.toggle('inner', inner);
441
+ number.classList.toggle('selected', selected);
442
+ number.style.left = `${position.x}%`;
443
+ number.style.top = `${position.y}%`;
444
+ number.textContent = String(value).padStart(2, '0');
445
+ return number;
446
+ }
447
+ clockPosition(index, count, radiusPercent) {
448
+ const angle = (index / count) * 2 * Math.PI - Math.PI / 2;
449
+ const x = 50 + radiusPercent * Math.cos(angle);
450
+ const y = 50 + radiusPercent * Math.sin(angle);
451
+ return { x, y };
452
+ }
453
+ positionHand(hand, value) {
454
+ if (this.clockMode === 'hours') {
455
+ const slot = Math.floor(value / 2) % 12;
456
+ const isInner = value % 2 === 1;
457
+ hand.style.transform = `rotate(${(slot / 12) * 360}deg)`;
458
+ hand.style.height = `${isInner ? DatePicker.CLOCK_INNER_RADIUS_PERCENT : DatePicker.CLOCK_OUTER_RADIUS_PERCENT}%`;
459
+ }
460
+ else {
461
+ hand.style.transform = `rotate(${(value / 60) * 360}deg)`;
462
+ hand.style.height = `${DatePicker.CLOCK_OUTER_RADIUS_PERCENT}%`;
463
+ }
464
+ }
465
+ bindClockDrag(face, hand) {
466
+ const sig = { signal: this.clockListeners.signal };
467
+ let dragging = false;
468
+ const valueFromPointer = (e) => {
469
+ const rect = face.getBoundingClientRect();
470
+ const dx = e.clientX - (rect.left + rect.width / 2);
471
+ const dy = e.clientY - (rect.top + rect.height / 2);
472
+ const angle = Math.atan2(dy, dx) + Math.PI / 2;
473
+ const normalized = (angle / (2 * Math.PI) + 1) % 1;
474
+ const slot = Math.round(normalized * 12) % 12;
475
+ let value;
476
+ if (this.clockMode === 'hours') {
477
+ const distancePercent = (Math.sqrt(dx * dx + dy * dy) / rect.width) * 100;
478
+ const ringThreshold = (DatePicker.CLOCK_OUTER_RADIUS_PERCENT + DatePicker.CLOCK_INNER_RADIUS_PERCENT) / 2;
479
+ value = distancePercent > ringThreshold ? slot * 2 : slot * 2 + 1;
423
480
  }
424
- });
425
- const downBtn = document.createElement('button');
426
- downBtn.className = 'datepicker-time-btn';
427
- downBtn.innerHTML = '&#9660;';
428
- downBtn.onclick = (e) => {
429
- e.stopPropagation();
430
- const next = value - 1 < min ? max : value - 1;
431
- onChange(next);
432
- this.render();
481
+ else {
482
+ value = Math.round(normalized * 60) % 60;
483
+ }
484
+ this.positionHand(hand, value);
485
+ return value;
433
486
  };
434
- spinner.appendChild(upBtn);
435
- spinner.appendChild(display);
436
- spinner.appendChild(downBtn);
437
- return spinner;
487
+ face.addEventListener('pointerdown', (e) => {
488
+ e.stopPropagation();
489
+ face.setPointerCapture(e.pointerId);
490
+ dragging = true;
491
+ valueFromPointer(e);
492
+ }, sig);
493
+ face.addEventListener('pointermove', (e) => {
494
+ if (!dragging)
495
+ return;
496
+ valueFromPointer(e);
497
+ }, sig);
498
+ for (const type of ['pointerup', 'pointercancel']) {
499
+ face.addEventListener(type, (e) => {
500
+ if (!dragging)
501
+ return;
502
+ dragging = false;
503
+ this.selectClockValue(valueFromPointer(e));
504
+ }, sig);
505
+ }
506
+ }
507
+ selectClockValue(value) {
508
+ if (this.clockMode === 'hours') {
509
+ this.selectedHours = value;
510
+ this.applyTimeToSelection();
511
+ this.clockMode = 'minutes';
512
+ }
513
+ else {
514
+ this.selectedMinutes = value;
515
+ this.applyTimeToSelection();
516
+ }
517
+ this.renderTimePicker();
438
518
  }
439
519
  applyTimeToSelection() {
440
520
  if (this.options.mode === 'single' && this.selectedDate) {
@@ -521,7 +601,8 @@ class DatePicker {
521
601
  }
522
602
  destroy() {
523
603
  this.hide();
524
- this.abortController.abort();
604
+ this.listeners.destroy();
605
+ this.clockListeners?.destroy();
525
606
  this.calendar.remove();
526
607
  this.backdrop.remove();
527
608
  }
package/js/dropdown.d.ts CHANGED
@@ -11,7 +11,7 @@ declare class Dropdown {
11
11
  private trigger;
12
12
  private menu;
13
13
  private options;
14
- private abortController;
14
+ private listeners;
15
15
  constructor(selector: string, options?: DropdownOptions);
16
16
  private init;
17
17
  private attachEventListeners;
package/js/dropdown.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { bestPlacement } from './position.js';
2
+ import { ListenerGroup } from './listeners.js';
2
3
  class Dropdown {
3
4
  container;
4
5
  trigger;
5
6
  menu;
6
7
  options;
7
- abortController;
8
+ listeners = new ListenerGroup();
8
9
  constructor(selector, options = {}) {
9
10
  const container = document.querySelector(selector);
10
11
  if (!container) {
11
- console.error(`Dropdown container not found: ${selector}`);
12
12
  throw new Error(`Dropdown container "${selector}" not found`);
13
13
  }
14
14
  this.container = container;
@@ -23,7 +23,6 @@ class Dropdown {
23
23
  closeOnSelect: options.closeOnSelect ?? true,
24
24
  allowMultipleOpen: options.allowMultipleOpen ?? false,
25
25
  };
26
- this.abortController = new AbortController();
27
26
  this.init();
28
27
  }
29
28
  init() {
@@ -31,7 +30,7 @@ class Dropdown {
31
30
  this.attachEventListeners();
32
31
  }
33
32
  attachEventListeners() {
34
- const { signal } = this.abortController;
33
+ const { signal } = this.listeners;
35
34
  this.trigger.addEventListener('click', (e) => {
36
35
  e.stopPropagation();
37
36
  this.toggle();
@@ -122,7 +121,7 @@ class Dropdown {
122
121
  this.container.dispatchEvent(event);
123
122
  }
124
123
  destroy() {
125
- this.abortController.abort();
124
+ this.listeners.destroy();
126
125
  this.close();
127
126
  }
128
127
  }
package/js/editor.d.ts CHANGED
@@ -11,7 +11,7 @@ declare class Editor {
11
11
  private readonly wordCount;
12
12
  private undoStack;
13
13
  private redoStack;
14
- private abortController;
14
+ private listeners;
15
15
  constructor(options?: EditorOptions);
16
16
  private q;
17
17
  private qAll;
package/js/editor.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { sanitizeHtml } from './utils.js';
2
+ import { ListenerGroup } from './listeners.js';
2
3
  class Editor {
3
4
  root;
4
5
  editable;
@@ -8,7 +9,7 @@ class Editor {
8
9
  wordCount;
9
10
  undoStack = [];
10
11
  redoStack = [];
11
- abortController = new AbortController();
12
+ listeners = new ListenerGroup();
12
13
  constructor(options = {}) {
13
14
  if (options.root instanceof HTMLElement) {
14
15
  this.root = options.root;
@@ -60,8 +61,8 @@ class Editor {
60
61
  return this.root.querySelectorAll(selector);
61
62
  }
62
63
  bindToolbar() {
63
- const sig = { signal: this.abortController.signal };
64
- this.qAll('[data-cmd]').forEach(btn => {
64
+ const sig = { signal: this.listeners.signal };
65
+ this.qAll('[data-cmd]').forEach((btn) => {
65
66
  btn.addEventListener('click', () => {
66
67
  const cmd = btn.dataset.cmd;
67
68
  const val = btn.dataset.value ?? null;
@@ -71,7 +72,7 @@ class Editor {
71
72
  });
72
73
  }
73
74
  bindActions() {
74
- const sig = { signal: this.abortController.signal };
75
+ const sig = { signal: this.listeners.signal };
75
76
  this.q('[data-editor-action="link"]')?.addEventListener('click', () => {
76
77
  const url = prompt('Enter URL:', 'https://');
77
78
  if (url)
@@ -120,10 +121,7 @@ class Editor {
120
121
  this.onContentChange();
121
122
  }, sig);
122
123
  this.q('[data-editor-action="minify-code"]')?.addEventListener('click', () => {
123
- code.value = code.value
124
- .replace(/\n/g, '')
125
- .replace(/>\s+</g, '><')
126
- .trim();
124
+ code.value = code.value.replace(/\n/g, '').replace(/>\s+</g, '><').trim();
127
125
  }, sig);
128
126
  }
129
127
  this.q('[data-editor-action="save"]')?.addEventListener('click', () => this.downloadHTML(), sig);
@@ -172,10 +170,10 @@ class Editor {
172
170
  e.preventDefault();
173
171
  this.redo();
174
172
  }
175
- }, { signal: this.abortController.signal });
173
+ }, { signal: this.listeners.signal });
176
174
  }
177
175
  bindEditable() {
178
- const sig = { signal: this.abortController.signal };
176
+ const sig = { signal: this.listeners.signal };
179
177
  this.editable.addEventListener('input', () => this.onContentChange(), sig);
180
178
  this.editable.addEventListener('paste', (e) => {
181
179
  e.preventDefault();
@@ -186,12 +184,12 @@ class Editor {
186
184
  this.editable.addEventListener('mouseup', () => this.refreshActiveState(), sig);
187
185
  }
188
186
  bindTabs() {
189
- const sig = { signal: this.abortController.signal };
190
- this.qAll('.side-tab[data-tab]').forEach(tab => {
187
+ const sig = { signal: this.listeners.signal };
188
+ this.qAll('.side-tab[data-tab]').forEach((tab) => {
191
189
  tab.addEventListener('click', () => {
192
190
  const target = tab.dataset.tab;
193
- this.qAll('.side-tab').forEach(t => t.classList.remove('active'));
194
- this.qAll('.side-panel').forEach(p => p.classList.remove('active'));
191
+ this.qAll('.side-tab').forEach((t) => t.classList.remove('active'));
192
+ this.qAll('.side-panel').forEach((p) => p.classList.remove('active'));
195
193
  tab.classList.add('active');
196
194
  this.q(`[data-editor="${target}"]`)?.classList.add('active');
197
195
  }, sig);
@@ -211,8 +209,11 @@ class Editor {
211
209
  updateWordCount() {
212
210
  if (!this.wordCount)
213
211
  return;
214
- const text = this.editable.innerText || '';
215
- const words = text.trim().split(/\s+/).filter(w => w.length > 0);
212
+ const text = this.editable.innerText;
213
+ const words = text
214
+ .trim()
215
+ .split(/\s+/)
216
+ .filter((w) => w.length > 0);
216
217
  const count = words.length;
217
218
  this.wordCount.textContent = `${count} word${count !== 1 ? 's' : ''}`;
218
219
  }
@@ -310,12 +311,11 @@ class Editor {
310
311
  return;
311
312
  const range = sel.getRangeAt(0);
312
313
  const container = range.commonAncestorContainer;
313
- let current = container.nodeType === Node.TEXT_NODE
314
- ? container.parentElement
315
- : container;
314
+ let current = container.nodeType === Node.TEXT_NODE ? container.parentElement : container;
315
+ const upperTag = tagName.toUpperCase();
316
316
  let wrapper = null;
317
317
  while (current && current !== this.editable) {
318
- if (current.tagName === tagName.toUpperCase()) {
318
+ if (current.tagName === upperTag) {
319
319
  wrapper = current;
320
320
  break;
321
321
  }
@@ -357,9 +357,7 @@ class Editor {
357
357
  return;
358
358
  const range = sel.getRangeAt(0);
359
359
  const container = range.commonAncestorContainer;
360
- let blockElement = container.nodeType === Node.TEXT_NODE
361
- ? container.parentElement
362
- : container;
360
+ let blockElement = container.nodeType === Node.TEXT_NODE ? container.parentElement : container;
363
361
  while (blockElement && blockElement !== this.editable && blockElement.parentElement !== this.editable) {
364
362
  blockElement = blockElement.parentElement;
365
363
  }
@@ -377,7 +375,7 @@ class Editor {
377
375
  const range = sel.getRangeAt(0);
378
376
  const text = range.toString();
379
377
  const list = document.createElement(listTag);
380
- const lines = text ? text.split('\n').filter(l => l.trim()) : [''];
378
+ const lines = text ? text.split('\n').filter((l) => l.trim()) : [''];
381
379
  for (const line of lines) {
382
380
  const li = document.createElement('li');
383
381
  li.textContent = line.trim() || '​';
@@ -396,15 +394,15 @@ class Editor {
396
394
  }
397
395
  setAlignment(cmd) {
398
396
  const align = {
399
- justifyLeft: 'left', justifyCenter: 'center', justifyRight: 'right',
397
+ justifyLeft: 'left',
398
+ justifyCenter: 'center',
399
+ justifyRight: 'right',
400
400
  };
401
401
  const sel = window.getSelection();
402
402
  if (!sel || sel.rangeCount === 0)
403
403
  return;
404
404
  const container = sel.getRangeAt(0).commonAncestorContainer;
405
- let block = container.nodeType === Node.TEXT_NODE
406
- ? container.parentElement
407
- : container;
405
+ let block = container.nodeType === Node.TEXT_NODE ? container.parentElement : container;
408
406
  while (block && block !== this.editable && block.parentElement !== this.editable) {
409
407
  block = block.parentElement;
410
408
  }
@@ -451,15 +449,13 @@ ${content}
451
449
  return;
452
450
  const range = sel.getRangeAt(0);
453
451
  const container = range.commonAncestorContainer;
454
- const element = container.nodeType === Node.TEXT_NODE
455
- ? container.parentElement
456
- : container;
457
- this.qAll('[data-cmd]').forEach(btn => {
452
+ const element = container.nodeType === Node.TEXT_NODE ? container.parentElement : container;
453
+ this.qAll('[data-cmd]').forEach((btn) => {
458
454
  const cmd = btn.dataset.cmd;
459
455
  let active = false;
460
456
  let current = element;
461
457
  while (current && current !== this.editable) {
462
- const tag = current.tagName?.toLowerCase();
458
+ const tag = current.tagName.toLowerCase();
463
459
  if ((cmd === 'bold' && (tag === 'strong' || tag === 'b')) ||
464
460
  (cmd === 'italic' && (tag === 'em' || tag === 'i')) ||
465
461
  (cmd === 'underline' && tag === 'u') ||
@@ -473,7 +469,7 @@ ${content}
473
469
  });
474
470
  }
475
471
  destroy() {
476
- this.abortController.abort();
472
+ this.listeners.destroy();
477
473
  }
478
474
  }
479
475
  export { Editor };
@@ -22,18 +22,11 @@ declare class FileUploader {
22
22
  private uploadUrl;
23
23
  private maxFileSize?;
24
24
  private allowedTypes?;
25
- private abortControllers;
25
+ private xhrAborts;
26
+ private listeners;
26
27
  constructor(elementOrSelector: string | HTMLElement, config?: FileUploaderConfig);
27
- private init;
28
28
  private fileKey;
29
29
  private setupEventListeners;
30
- private preventDefaults;
31
- private handleDragEnter;
32
- private handleDragLeave;
33
- private handleDrop;
34
- private handleDropZoneClick;
35
- private handleFileInputChange;
36
- private handleUploadClick;
37
30
  private handleFiles;
38
31
  private validateFile;
39
32
  private addFileToUI;