@dodlhuat/basix 1.3.2 → 1.3.4

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 (119) hide show
  1. package/README.md +13 -7
  2. package/css/accordion.scss +0 -5
  3. package/css/badge.scss +9 -6
  4. package/css/bottom-sheet.scss +3 -8
  5. package/css/breadcrumb.scss +6 -15
  6. package/css/button.scss +63 -16
  7. package/css/calendar.scss +0 -54
  8. package/css/card.scss +0 -5
  9. package/css/carousel.scss +0 -3
  10. package/css/chart.scss +0 -25
  11. package/css/chat-bubbles.scss +0 -15
  12. package/css/checkbox.scss +3 -2
  13. package/css/chips.scss +13 -7
  14. package/css/code-viewer.scss +1 -5
  15. package/css/colors.scss +4 -0
  16. package/css/context-menu.scss +4 -6
  17. package/css/datepicker.scss +4 -7
  18. package/css/docs.scss +0 -8
  19. package/css/dropdown.scss +15 -1
  20. package/css/editor.scss +1 -23
  21. package/css/file-uploader.scss +2 -2
  22. package/css/flyout-menu.scss +66 -44
  23. package/css/form.scss +0 -28
  24. package/css/gallery.scss +2 -3
  25. package/css/group-picker.scss +5 -35
  26. package/css/icons.scss +0 -3
  27. package/css/lightbox.scss +2 -4
  28. package/css/mixins.scss +8 -0
  29. package/css/modal.scss +24 -3
  30. package/css/parameters.scss +6 -1
  31. package/css/popover.scss +3 -15
  32. package/css/progress.scss +0 -6
  33. package/css/push-menu.scss +3 -28
  34. package/css/radiobutton.scss +2 -1
  35. package/css/range-slider.scss +1 -7
  36. package/css/scrollbar.scss +2 -6
  37. package/css/sidebar-nav.scss +24 -11
  38. package/css/stepper.scss +0 -4
  39. package/css/style.css +287 -89
  40. package/css/style.css.map +1 -1
  41. package/css/style.min.css +1 -1
  42. package/css/style.min.css.map +1 -1
  43. package/css/style.scss +1 -1
  44. package/css/table.scss +0 -4
  45. package/css/tabs.scss +49 -2
  46. package/css/timeline.scss +1 -13
  47. package/css/timepicker.scss +6 -7
  48. package/css/toast.scss +1 -1
  49. package/css/tooltip.scss +1 -5
  50. package/css/tree.scss +1 -1
  51. package/css/typography.scss +38 -9
  52. package/css/virtual-dropdown.scss +3 -28
  53. package/js/bottom-sheet.d.ts +3 -1
  54. package/js/bottom-sheet.js +26 -27
  55. package/js/calendar.d.ts +7 -0
  56. package/js/calendar.js +14 -33
  57. package/js/carousel.d.ts +2 -0
  58. package/js/carousel.js +13 -5
  59. package/js/chart.d.ts +4 -0
  60. package/js/chart.js +13 -31
  61. package/js/code-viewer.d.ts +1 -0
  62. package/js/code-viewer.js +4 -0
  63. package/js/context-menu.d.ts +9 -2
  64. package/js/context-menu.js +17 -14
  65. package/js/datepicker.d.ts +4 -0
  66. package/js/datepicker.js +26 -11
  67. package/js/dropdown.d.ts +4 -3
  68. package/js/dropdown.js +17 -9
  69. package/js/editor.d.ts +1 -0
  70. package/js/editor.js +9 -3
  71. package/js/file-uploader.d.ts +4 -0
  72. package/js/file-uploader.js +52 -43
  73. package/js/flyout-menu.d.ts +5 -3
  74. package/js/flyout-menu.js +23 -46
  75. package/js/gallery.d.ts +5 -0
  76. package/js/gallery.js +44 -26
  77. package/js/group-picker.d.ts +5 -0
  78. package/js/group-picker.js +12 -17
  79. package/js/lightbox.d.ts +3 -0
  80. package/js/lightbox.js +12 -6
  81. package/js/modal.d.ts +3 -1
  82. package/js/modal.js +15 -12
  83. package/js/popover.d.ts +2 -0
  84. package/js/popover.js +26 -30
  85. package/js/position.d.ts +2 -0
  86. package/js/position.js +1 -5
  87. package/js/push-menu.d.ts +2 -0
  88. package/js/push-menu.js +22 -35
  89. package/js/range-slider.d.ts +1 -0
  90. package/js/range-slider.js +5 -3
  91. package/js/scroll.d.ts +2 -0
  92. package/js/scroll.js +1 -0
  93. package/js/scrollbar.d.ts +2 -0
  94. package/js/scrollbar.js +24 -36
  95. package/js/select.d.ts +1 -0
  96. package/js/select.js +5 -10
  97. package/js/sidebar-nav.d.ts +12 -0
  98. package/js/sidebar-nav.js +47 -0
  99. package/js/stepper.d.ts +2 -0
  100. package/js/stepper.js +7 -1
  101. package/js/table.d.ts +4 -0
  102. package/js/table.js +15 -22
  103. package/js/tabs.d.ts +2 -0
  104. package/js/tabs.js +6 -14
  105. package/js/theme.d.ts +1 -0
  106. package/js/theme.js +5 -13
  107. package/js/timepicker.d.ts +3 -0
  108. package/js/timepicker.js +81 -67
  109. package/js/toast.d.ts +3 -0
  110. package/js/toast.js +24 -15
  111. package/js/tooltip.d.ts +2 -0
  112. package/js/tooltip.js +21 -19
  113. package/js/tree.d.ts +3 -0
  114. package/js/tree.js +13 -0
  115. package/js/utils.d.ts +1 -3
  116. package/js/utils.js +0 -3
  117. package/js/virtual-dropdown.d.ts +3 -0
  118. package/js/virtual-dropdown.js +25 -0
  119. package/package.json +2 -2
package/js/chart.js CHANGED
@@ -7,13 +7,16 @@ const FALLBACK_COLORS = [
7
7
  '#8B5CF6', '#06B6D4', '#F97316', '#EC4899',
8
8
  ];
9
9
  const SVG_NS = 'http://www.w3.org/2000/svg';
10
- // ─── Chart ──────────────────────────────────────────────────────────────────
10
+ /** SVG-based chart component supporting line, area, column, bar, and pie types. */
11
11
  class Chart {
12
+ container;
13
+ opts;
14
+ tooltip;
15
+ colors = [];
16
+ abortController = new AbortController();
17
+ resizeTimer = null;
18
+ resizeObserver = null;
12
19
  constructor(selector, options) {
13
- this.colors = [];
14
- this.abortController = new AbortController();
15
- this.resizeTimer = null;
16
- this.resizeObserver = null;
17
20
  const el = typeof selector === 'string'
18
21
  ? document.querySelector(selector)
19
22
  : selector;
@@ -37,7 +40,6 @@ class Chart {
37
40
  this.render();
38
41
  this.attachResizeObserver();
39
42
  }
40
- // ── Render ──────────────────────────────────────────────────────────────
41
43
  render() {
42
44
  this.abortController.abort();
43
45
  this.abortController = new AbortController();
@@ -72,7 +74,6 @@ class Chart {
72
74
  this.container.appendChild(this.buildLegend());
73
75
  }
74
76
  }
75
- // ── Line / Area ──────────────────────────────────────────────────────────
76
77
  renderLineOrArea(canvas, isArea) {
77
78
  const { series, height, showGrid, animate, yMin } = this.opts;
78
79
  if (!series.length || !series[0].data.length)
@@ -119,7 +120,6 @@ class Chart {
119
120
  });
120
121
  }
121
122
  svg.appendChild(linePath);
122
- // Data point markers
123
123
  s.data.forEach((d, i) => {
124
124
  const g = this.svgEl('g', {
125
125
  class: 'chart-point-group',
@@ -145,7 +145,6 @@ class Chart {
145
145
  });
146
146
  });
147
147
  }
148
- // ── Column ───────────────────────────────────────────────────────────────
149
148
  renderColumn(canvas) {
150
149
  const { series, height, showGrid, animate, yMin } = this.opts;
151
150
  if (!series.length || !series[0].data.length)
@@ -190,7 +189,6 @@ class Chart {
190
189
  });
191
190
  });
192
191
  }
193
- // ── Bar (horizontal) ─────────────────────────────────────────────────────
194
192
  renderBar(canvas) {
195
193
  const { series, height, animate } = this.opts;
196
194
  if (!series.length || !series[0].data.length)
@@ -206,7 +204,6 @@ class Chart {
206
204
  const numPts = labels.length;
207
205
  const numSeries = series.length;
208
206
  const svg = this.createSVG(canvas, svgW, svgH);
209
- // Vertical grid lines
210
207
  const numTicks = 5;
211
208
  for (let t = 0; t <= numTicks; t++) {
212
209
  const x = m.left + (t / numTicks) * w;
@@ -223,7 +220,6 @@ class Chart {
223
220
  label.textContent = this.fmt(xMax * t / numTicks);
224
221
  svg.appendChild(label);
225
222
  }
226
- // Category labels on Y axis
227
223
  const groupH = h / numPts;
228
224
  labels.forEach((label, i) => {
229
225
  const y = m.top + i * groupH + groupH / 2;
@@ -235,7 +231,6 @@ class Chart {
235
231
  text.textContent = label;
236
232
  svg.appendChild(text);
237
233
  });
238
- // Bars
239
234
  const innerPad = groupH * 0.18;
240
235
  const barH = Math.max(2, (groupH - innerPad) / numSeries - 2);
241
236
  series.forEach((s, si) => {
@@ -259,7 +254,6 @@ class Chart {
259
254
  });
260
255
  });
261
256
  }
262
- // ── Pie ──────────────────────────────────────────────────────────────────
263
257
  renderPie(canvas) {
264
258
  const { series, height, animate, showLegend } = this.opts;
265
259
  const s = series[0];
@@ -273,7 +267,7 @@ class Chart {
273
267
  const r = Math.min(svgW, svgH) / 2 - Math.max(m.top, m.left) - 8;
274
268
  const total = s.data.reduce((sum, d) => sum + d.value, 0);
275
269
  const svg = this.createSVG(canvas, svgW, svgH);
276
- let startAngle = -90; // start at 12 o'clock
270
+ let startAngle = -90;
277
271
  s.data.forEach((d, i) => {
278
272
  const color = this.colors[i % this.colors.length];
279
273
  const sweep = (d.value / total) * 360;
@@ -290,7 +284,6 @@ class Chart {
290
284
  const delay = i * 70;
291
285
  path.style.animationDelay = `${delay}ms`;
292
286
  }
293
- // Hover: nudge slice outward
294
287
  const { x: dx, y: dy } = this.polar(0, 0, 8, midAngle);
295
288
  path.addEventListener('mouseenter', (e) => {
296
289
  path.style.transform = `translate(${dx}px, ${dy}px)`;
@@ -310,7 +303,6 @@ class Chart {
310
303
  this.container.appendChild(this.buildPieLegend(s, total));
311
304
  }
312
305
  }
313
- // ── Axis helpers ─────────────────────────────────────────────────────────
314
306
  renderHGrid(svg, m, w, h, yMin, yMax) {
315
307
  const numTicks = 5;
316
308
  for (let i = 0; i <= numTicks; i++) {
@@ -355,7 +347,6 @@ class Chart {
355
347
  svg.appendChild(text);
356
348
  }
357
349
  }
358
- // ── Geometry helpers ─────────────────────────────────────────────────────
359
350
  buildPath(pts) {
360
351
  switch (this.opts.curve) {
361
352
  case 'linear': return this.linearPath(pts);
@@ -410,7 +401,6 @@ class Chart {
410
401
  const rad = deg * Math.PI / 180;
411
402
  return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
412
403
  }
413
- // ── Legend builders ──────────────────────────────────────────────────────
414
404
  buildHeader() {
415
405
  const el = this.div('chart-header');
416
406
  if (this.opts.title) {
@@ -454,7 +444,6 @@ class Chart {
454
444
  });
455
445
  return el;
456
446
  }
457
- // ── Tooltip ──────────────────────────────────────────────────────────────
458
447
  showTooltip(e, html) {
459
448
  this.tooltip.innerHTML = html;
460
449
  this.tooltip.classList.add('is-visible');
@@ -466,7 +455,6 @@ class Chart {
466
455
  const vh = window.innerHeight;
467
456
  let x = e.clientX + 14;
468
457
  let y = e.clientY - 36;
469
- // Keep inside viewport
470
458
  if (x + 200 > vw)
471
459
  x = e.clientX - 14 - tt.offsetWidth;
472
460
  if (y < 0)
@@ -479,7 +467,6 @@ class Chart {
479
467
  hideTooltip() {
480
468
  this.tooltip.classList.remove('is-visible');
481
469
  }
482
- // ── Event wiring ─────────────────────────────────────────────────────────
483
470
  onPoint(g, s, d, i) {
484
471
  const sig = { signal: this.abortController.signal };
485
472
  g.addEventListener('mouseenter', (e) => {
@@ -499,7 +486,6 @@ class Chart {
499
486
  rect.addEventListener('mouseleave', () => this.hideTooltip(), sig);
500
487
  rect.addEventListener('click', () => this.opts.onPointClick(s, d, i), sig);
501
488
  }
502
- // ── Color resolution ─────────────────────────────────────────────────────
503
489
  resolveColors() {
504
490
  const style = getComputedStyle(this.container);
505
491
  this.colors = (this.opts.type === 'pie' ? this.opts.series[0]?.data ?? [] : this.opts.series)
@@ -507,7 +493,6 @@ class Chart {
507
493
  const css = style.getPropertyValue(`--chart-color-${i + 1}`).trim();
508
494
  return css || FALLBACK_COLORS[i % FALLBACK_COLORS.length];
509
495
  });
510
- // Allow per-series color override (not pie)
511
496
  if (this.opts.type !== 'pie') {
512
497
  this.opts.series.forEach((s, i) => {
513
498
  if (s.color)
@@ -515,7 +500,6 @@ class Chart {
515
500
  });
516
501
  }
517
502
  }
518
- // ── DOM & SVG helpers ────────────────────────────────────────────────────
519
503
  div(className) {
520
504
  const el = document.createElement('div');
521
505
  el.className = className;
@@ -537,13 +521,12 @@ class Chart {
537
521
  return el;
538
522
  }
539
523
  fmt(v) {
540
- if (v >= 1000000)
541
- return `${(v / 1000000).toFixed(1)}M`;
542
- if (v >= 1000)
543
- return `${(v / 1000).toFixed(1)}K`;
524
+ if (v >= 1_000_000)
525
+ return `${(v / 1_000_000).toFixed(1)}M`;
526
+ if (v >= 1_000)
527
+ return `${(v / 1_000).toFixed(1)}K`;
544
528
  return v % 1 === 0 ? String(Math.round(v)) : v.toFixed(1);
545
529
  }
546
- // ── Resize ───────────────────────────────────────────────────────────────
547
530
  attachResizeObserver() {
548
531
  this.resizeObserver = new ResizeObserver(() => {
549
532
  if (this.resizeTimer)
@@ -552,7 +535,6 @@ class Chart {
552
535
  });
553
536
  this.resizeObserver.observe(this.container);
554
537
  }
555
- // ── Public API ───────────────────────────────────────────────────────────
556
538
  update(series) {
557
539
  this.opts.series = series;
558
540
  this.render();
@@ -1,4 +1,5 @@
1
1
  type SupportedLanguage = 'javascript' | 'js' | 'html' | 'css';
2
+ /** Renders syntax-highlighted code inside a container element. */
2
3
  declare class CodeViewer {
3
4
  private container;
4
5
  private code;
package/js/code-viewer.js CHANGED
@@ -1,4 +1,8 @@
1
+ /** Renders syntax-highlighted code inside a container element. */
1
2
  class CodeViewer {
3
+ container;
4
+ code;
5
+ language;
2
6
  constructor(elementOrSelector, code, language = 'javascript') {
3
7
  const element = typeof elementOrSelector === 'string'
4
8
  ? document.querySelector(elementOrSelector)
@@ -1,3 +1,4 @@
1
+ /** Definition for a single context menu item including optional submenu. */
1
2
  interface ContextMenuItemDef {
2
3
  label: string;
3
4
  icon?: string;
@@ -10,13 +11,19 @@ interface ContextMenuItemDef {
10
11
  type ContextMenuInput = ContextMenuItemDef | 'separator' | {
11
12
  group: string;
12
13
  };
14
+ interface ContextMenuOptions {
15
+ /** Path to the SVG sprite file, e.g. `'svg-icons/icons.svg'`. Required to render icons. */
16
+ spritePath?: string;
17
+ }
18
+ /** Right-click context menu with keyboard navigation and nested submenu support. */
13
19
  declare class ContextMenu {
14
20
  private items;
15
21
  private targets;
16
22
  private menuEl;
17
23
  private currentTarget;
18
24
  private abortController;
19
- constructor(selectorOrElement: string | HTMLElement | HTMLElement[], items: ContextMenuInput[]);
25
+ private spritePath;
26
+ constructor(selectorOrElement: string | HTMLElement | HTMLElement[], items: ContextMenuInput[], options?: ContextMenuOptions);
20
27
  private init;
21
28
  private open;
22
29
  private close;
@@ -28,4 +35,4 @@ declare class ContextMenu {
28
35
  private activateFocused;
29
36
  destroy(): void;
30
37
  }
31
- export { ContextMenu, type ContextMenuInput, type ContextMenuItemDef };
38
+ export { ContextMenu, type ContextMenuInput, type ContextMenuItemDef, type ContextMenuOptions };
@@ -1,9 +1,14 @@
1
+ /** Right-click context menu with keyboard navigation and nested submenu support. */
1
2
  class ContextMenu {
2
- constructor(selectorOrElement, items) {
3
- this.menuEl = null;
4
- this.currentTarget = null;
5
- this.abortController = new AbortController();
3
+ items;
4
+ targets;
5
+ menuEl = null;
6
+ currentTarget = null;
7
+ abortController = new AbortController();
8
+ spritePath;
9
+ constructor(selectorOrElement, items, options = {}) {
6
10
  this.items = items;
11
+ this.spritePath = options.spritePath ?? null;
7
12
  if (typeof selectorOrElement === 'string') {
8
13
  this.targets = Array.from(document.querySelectorAll(selectorOrElement));
9
14
  }
@@ -25,7 +30,6 @@ class ContextMenu {
25
30
  }, { signal });
26
31
  });
27
32
  document.addEventListener('click', () => this.close(), { signal });
28
- // Close on right-click outside the menu
29
33
  document.addEventListener('contextmenu', (e) => {
30
34
  if (this.menuEl && !this.menuEl.contains(e.target)) {
31
35
  this.close();
@@ -50,7 +54,6 @@ class ContextMenu {
50
54
  this.activateFocused();
51
55
  }
52
56
  }, { signal });
53
- // Close on scroll outside the menu
54
57
  window.addEventListener('scroll', (e) => {
55
58
  if (!this.menuEl?.contains(e.target))
56
59
  this.close();
@@ -61,14 +64,12 @@ class ContextMenu {
61
64
  this.close();
62
65
  this.menuEl = this.buildMenu(this.items);
63
66
  document.body.appendChild(this.menuEl);
64
- // Use offsetWidth/offsetHeight — unaffected by CSS transform
65
67
  const w = this.menuEl.offsetWidth;
66
68
  const h = this.menuEl.offsetHeight;
67
69
  const vw = window.innerWidth;
68
70
  const vh = window.innerHeight;
69
71
  const left = x + w > vw ? vw - w - 8 : x;
70
72
  const top = y + h > vh ? vh - h - 8 : y;
71
- // Set transform-origin to match the corner the menu opens from
72
73
  const originX = x + w > vw ? 'right' : 'left';
73
74
  const originY = y + h > vh ? 'bottom' : 'top';
74
75
  this.menuEl.style.left = `${left}px`;
@@ -82,7 +83,6 @@ class ContextMenu {
82
83
  const el = this.menuEl;
83
84
  this.menuEl = null;
84
85
  el.classList.remove('is-visible');
85
- // Wait for exit transition then remove from DOM
86
86
  el.addEventListener('transitionend', () => el.remove(), { once: true });
87
87
  setTimeout(() => el.isConnected && el.remove(), 200);
88
88
  }
@@ -116,11 +116,16 @@ class ContextMenu {
116
116
  li.classList.add('is-destructive');
117
117
  if (def.submenu)
118
118
  li.classList.add('has-submenu');
119
- // Always render icon slot — keeps label column aligned across all items
120
119
  const iconWrap = document.createElement('span');
121
120
  iconWrap.className = 'context-menu-icon';
122
- if (def.icon) {
123
- iconWrap.innerHTML = `<svg class="icon-svg"><use href="svg-icons/icons.svg#${def.icon}"/></svg>`;
121
+ if (def.icon && this.spritePath) {
122
+ const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
123
+ svgEl.setAttribute('aria-hidden', 'true');
124
+ svgEl.setAttribute('fill', 'currentColor');
125
+ const useEl = document.createElementNS('http://www.w3.org/2000/svg', 'use');
126
+ useEl.setAttribute('href', `${this.spritePath}#${def.icon}`);
127
+ svgEl.appendChild(useEl);
128
+ iconWrap.appendChild(svgEl);
124
129
  }
125
130
  li.appendChild(iconWrap);
126
131
  const label = document.createElement('span');
@@ -139,7 +144,6 @@ class ContextMenu {
139
144
  li.appendChild(chevron);
140
145
  const submenuEl = this.buildMenu(def.submenu);
141
146
  li.appendChild(submenuEl);
142
- // Determine flip synchronously from parent position — no rAF flash
143
147
  const shouldFlip = () => {
144
148
  const rect = li.getBoundingClientRect();
145
149
  return rect.right + submenuEl.offsetWidth > window.innerWidth;
@@ -179,7 +183,6 @@ class ContextMenu {
179
183
  return li;
180
184
  }
181
185
  closeAllSubmenus(menu) {
182
- // Only close direct-child submenus of this menu level
183
186
  Array.from(menu.children).forEach((child) => {
184
187
  child.classList.remove('is-active');
185
188
  });
@@ -1,7 +1,9 @@
1
+ /** Localised day and month names for the DatePicker. */
1
2
  interface DatePickerLocales {
2
3
  days: string[];
3
4
  months: string[];
4
5
  }
6
+ /** Configuration options for the DatePicker. */
5
7
  interface DatePickerOptions {
6
8
  mode?: 'single' | 'range';
7
9
  startDay?: number;
@@ -10,10 +12,12 @@ interface DatePickerOptions {
10
12
  format?: (date: Date) => string;
11
13
  onSelect?: (date: Date | DateRange) => void;
12
14
  }
15
+ /** A date range with optional start and end dates. */
13
16
  interface DateRange {
14
17
  start: Date | null;
15
18
  end: Date | null;
16
19
  }
20
+ /** Calendar-based date (or date-range) picker that attaches to an input element. */
17
21
  declare class DatePicker {
18
22
  private input;
19
23
  private options;
package/js/datepicker.js CHANGED
@@ -1,6 +1,23 @@
1
+ import { computePosition } from './position.js';
2
+ /** Calendar-based date (or date-range) picker that attaches to an input element. */
1
3
  class DatePicker {
4
+ input;
5
+ options;
6
+ currentDate;
7
+ selectedDate;
8
+ rangeStart;
9
+ rangeEnd;
10
+ viewYear;
11
+ viewMonth;
12
+ viewMode;
13
+ yearRangeStart;
14
+ selectedHours;
15
+ selectedMinutes;
16
+ calendar;
17
+ backdrop;
18
+ handleDocumentClick;
19
+ abortController = new AbortController();
2
20
  constructor(elementOrSelector, options = {}) {
3
- this.abortController = new AbortController();
4
21
  this.input = typeof elementOrSelector === 'string'
5
22
  ? document.querySelector(elementOrSelector)
6
23
  : elementOrSelector;
@@ -96,14 +113,14 @@ class DatePicker {
96
113
  this.backdrop.classList.remove('visible');
97
114
  document.body.style.overflow = '';
98
115
  if (this.input) {
99
- const rect = this.input.getBoundingClientRect();
100
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
101
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
102
- this.calendar.style.top = `${rect.bottom + scrollTop + 5}px`;
103
- this.calendar.style.left = `${rect.left + scrollLeft}px`;
104
- if (rect.left + 320 > window.innerWidth) {
105
- this.calendar.style.left = `${rect.right + scrollLeft - 320}px`;
106
- }
116
+ this.calendar.style.display = 'block';
117
+ this.calendar.style.visibility = 'hidden';
118
+ const calRect = this.calendar.getBoundingClientRect();
119
+ this.calendar.style.display = '';
120
+ this.calendar.style.visibility = '';
121
+ const { left, top } = computePosition(this.input.getBoundingClientRect(), calRect, { placement: 'bottom', align: 'start', offset: 5 });
122
+ this.calendar.style.top = `${top}px`;
123
+ this.calendar.style.left = `${left}px`;
107
124
  }
108
125
  setTimeout(() => {
109
126
  if (this.calendar.classList.contains('visible')) {
@@ -345,7 +362,6 @@ class DatePicker {
345
362
  wrapper.appendChild(label);
346
363
  const controls = document.createElement('div');
347
364
  controls.className = 'datepicker-time-controls';
348
- // Hours spinner
349
365
  const hoursSpinner = this.createSpinner(this.selectedHours, 0, 23, (value) => {
350
366
  this.selectedHours = value;
351
367
  this.applyTimeToSelection();
@@ -353,7 +369,6 @@ class DatePicker {
353
369
  const separator = document.createElement('span');
354
370
  separator.className = 'datepicker-time-separator';
355
371
  separator.textContent = ':';
356
- // Minutes spinner
357
372
  const minutesSpinner = this.createSpinner(this.selectedMinutes, 0, 59, (value) => {
358
373
  this.selectedMinutes = value;
359
374
  this.applyTimeToSelection();
package/js/dropdown.d.ts CHANGED
@@ -1,11 +1,14 @@
1
+ /** Configuration options for a Dropdown instance. */
1
2
  interface DropdownOptions {
2
3
  closeOnSelect?: boolean;
3
4
  allowMultipleOpen?: boolean;
4
5
  }
6
+ /** Event detail payload for the `dropdown-select` custom event. */
5
7
  interface DropdownSelectDetail {
6
8
  text: string;
7
9
  element: HTMLElement;
8
10
  }
11
+ /** Hierarchical dropdown menu with optional multi-open and close-on-select behaviour. */
9
12
  declare class Dropdown {
10
13
  private container;
11
14
  private trigger;
@@ -16,15 +19,13 @@ declare class Dropdown {
16
19
  private init;
17
20
  private attachEventListeners;
18
21
  private setupItems;
22
+ private updatePosition;
19
23
  toggle(): void;
20
24
  close(): void;
21
25
  open(): void;
22
26
  private toggleSubmenu;
23
27
  private closeAllSubmenus;
24
28
  private handleSelection;
25
- /**
26
- * Cleanup method to remove event listeners
27
- */
28
29
  destroy(): void;
29
30
  }
30
31
  export { Dropdown, DropdownSelectDetail };
package/js/dropdown.js CHANGED
@@ -1,4 +1,11 @@
1
+ import { bestPlacement } from './position.js';
2
+ /** Hierarchical dropdown menu with optional multi-open and close-on-select behaviour. */
1
3
  class Dropdown {
4
+ container;
5
+ trigger;
6
+ menu;
7
+ options;
8
+ abortController;
2
9
  constructor(selector, options = {}) {
3
10
  const container = document.querySelector(selector);
4
11
  if (!container) {
@@ -26,18 +33,15 @@ class Dropdown {
26
33
  }
27
34
  attachEventListeners() {
28
35
  const { signal } = this.abortController;
29
- // Toggle main dropdown
30
36
  this.trigger.addEventListener('click', (e) => {
31
37
  e.stopPropagation();
32
38
  this.toggle();
33
39
  }, { signal });
34
- // Close when clicking outside
35
40
  document.addEventListener('click', (e) => {
36
41
  if (!this.container.contains(e.target)) {
37
42
  this.close();
38
43
  }
39
44
  }, { signal });
40
- // Handle item clicks using event delegation
41
45
  this.menu.addEventListener('click', (e) => {
42
46
  e.stopPropagation();
43
47
  const target = e.target;
@@ -66,7 +70,16 @@ class Dropdown {
66
70
  }
67
71
  });
68
72
  }
73
+ updatePosition() {
74
+ const triggerRect = this.trigger.getBoundingClientRect();
75
+ const menuRect = this.menu.getBoundingClientRect();
76
+ const placement = bestPlacement(triggerRect, menuRect, 6);
77
+ this.container.classList.toggle('drop-up', placement === 'top');
78
+ }
69
79
  toggle() {
80
+ if (!this.container.classList.contains('active')) {
81
+ this.updatePosition();
82
+ }
70
83
  this.container.classList.toggle('active');
71
84
  }
72
85
  close() {
@@ -74,11 +87,11 @@ class Dropdown {
74
87
  this.closeAllSubmenus();
75
88
  }
76
89
  open() {
90
+ this.updatePosition();
77
91
  this.container.classList.add('active');
78
92
  }
79
93
  toggleSubmenu(li) {
80
94
  const isOpening = !li.classList.contains('open');
81
- // Close siblings if not allowing multiple open menus
82
95
  if (isOpening && !this.options.allowMultipleOpen) {
83
96
  const parent = li.parentElement;
84
97
  if (parent) {
@@ -86,7 +99,6 @@ class Dropdown {
86
99
  siblings.forEach((sibling) => {
87
100
  if (sibling !== li && sibling.classList.contains('open')) {
88
101
  sibling.classList.remove('open');
89
- // Close deeply nested open items
90
102
  const deepOpenItems = sibling.querySelectorAll('.open');
91
103
  deepOpenItems.forEach((el) => el.classList.remove('open'));
92
104
  }
@@ -101,7 +113,6 @@ class Dropdown {
101
113
  }
102
114
  handleSelection(item) {
103
115
  const text = item.textContent?.trim() ?? '';
104
- // Dispatch custom event with proper typing
105
116
  const event = new CustomEvent('dropdown-select', {
106
117
  detail: {
107
118
  text,
@@ -111,9 +122,6 @@ class Dropdown {
111
122
  });
112
123
  this.container.dispatchEvent(event);
113
124
  }
114
- /**
115
- * Cleanup method to remove event listeners
116
- */
117
125
  destroy() {
118
126
  this.abortController.abort();
119
127
  this.close();
package/js/editor.d.ts CHANGED
@@ -3,6 +3,7 @@ interface EditorOptions {
3
3
  * without #code, #preview, or #sidePanel in the DOM. */
4
4
  simple?: boolean;
5
5
  }
6
+ /** Rich-text editor built on contenteditable with undo/redo and code/preview panels. */
6
7
  declare class Editor {
7
8
  private readonly editable;
8
9
  private readonly code;
package/js/editor.js CHANGED
@@ -1,9 +1,15 @@
1
1
  import { sanitizeHtml } from './utils.js';
2
+ /** Rich-text editor built on contenteditable with undo/redo and code/preview panels. */
2
3
  class Editor {
4
+ editable;
5
+ code;
6
+ preview;
7
+ sidePanel;
8
+ wordCount;
9
+ undoStack = [];
10
+ redoStack = [];
11
+ abortController = new AbortController();
3
12
  constructor(options = {}) {
4
- this.undoStack = [];
5
- this.redoStack = [];
6
- this.abortController = new AbortController();
7
13
  const editable = document.getElementById('editable');
8
14
  if (!editable) {
9
15
  throw new Error('Editor: #editable element not found');
@@ -1,17 +1,21 @@
1
+ /** Event detail payload for the `upload-completed` custom event. */
1
2
  interface UploadCompletedDetail {
2
3
  fileCount: number;
3
4
  files: File[];
4
5
  results: PromiseSettledResult<unknown>[];
5
6
  }
7
+ /** Event detail payload for the `file-validation-error` custom event. */
6
8
  interface FileValidationErrorDetail {
7
9
  file: File;
8
10
  reason: 'size' | 'type';
9
11
  }
12
+ /** Configuration options for the FileUploader. */
10
13
  interface FileUploaderConfig {
11
14
  uploadUrl?: string;
12
15
  maxFileSize?: number;
13
16
  allowedTypes?: string[];
14
17
  }
18
+ /** Drag-and-drop file uploader with progress tracking and XHR-based uploads. */
15
19
  declare class FileUploader {
16
20
  private container;
17
21
  private dropZone;