@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.
- package/README.md +13 -7
- package/css/accordion.scss +0 -5
- package/css/badge.scss +9 -6
- package/css/bottom-sheet.scss +3 -8
- package/css/breadcrumb.scss +6 -15
- package/css/button.scss +63 -16
- package/css/calendar.scss +0 -54
- package/css/card.scss +0 -5
- package/css/carousel.scss +0 -3
- package/css/chart.scss +0 -25
- package/css/chat-bubbles.scss +0 -15
- package/css/checkbox.scss +3 -2
- package/css/chips.scss +13 -7
- package/css/code-viewer.scss +1 -5
- package/css/colors.scss +4 -0
- package/css/context-menu.scss +4 -6
- package/css/datepicker.scss +4 -7
- package/css/docs.scss +0 -8
- package/css/dropdown.scss +15 -1
- package/css/editor.scss +1 -23
- package/css/file-uploader.scss +2 -2
- package/css/flyout-menu.scss +66 -44
- package/css/form.scss +0 -28
- package/css/gallery.scss +2 -3
- package/css/group-picker.scss +5 -35
- package/css/icons.scss +0 -3
- package/css/lightbox.scss +2 -4
- package/css/mixins.scss +8 -0
- package/css/modal.scss +24 -3
- package/css/parameters.scss +6 -1
- package/css/popover.scss +3 -15
- package/css/progress.scss +0 -6
- package/css/push-menu.scss +3 -28
- package/css/radiobutton.scss +2 -1
- package/css/range-slider.scss +1 -7
- package/css/scrollbar.scss +2 -6
- package/css/sidebar-nav.scss +24 -11
- package/css/stepper.scss +0 -4
- package/css/style.css +287 -89
- package/css/style.css.map +1 -1
- package/css/style.min.css +1 -1
- package/css/style.min.css.map +1 -1
- package/css/style.scss +1 -1
- package/css/table.scss +0 -4
- package/css/tabs.scss +49 -2
- package/css/timeline.scss +1 -13
- package/css/timepicker.scss +6 -7
- package/css/toast.scss +1 -1
- package/css/tooltip.scss +1 -5
- package/css/tree.scss +1 -1
- package/css/typography.scss +38 -9
- package/css/virtual-dropdown.scss +3 -28
- package/js/bottom-sheet.d.ts +3 -1
- package/js/bottom-sheet.js +26 -27
- package/js/calendar.d.ts +7 -0
- package/js/calendar.js +14 -33
- package/js/carousel.d.ts +2 -0
- package/js/carousel.js +13 -5
- package/js/chart.d.ts +4 -0
- package/js/chart.js +13 -31
- package/js/code-viewer.d.ts +1 -0
- package/js/code-viewer.js +4 -0
- package/js/context-menu.d.ts +9 -2
- package/js/context-menu.js +17 -14
- package/js/datepicker.d.ts +4 -0
- package/js/datepicker.js +26 -11
- package/js/dropdown.d.ts +4 -3
- package/js/dropdown.js +17 -9
- package/js/editor.d.ts +1 -0
- package/js/editor.js +9 -3
- package/js/file-uploader.d.ts +4 -0
- package/js/file-uploader.js +52 -43
- package/js/flyout-menu.d.ts +5 -3
- package/js/flyout-menu.js +23 -46
- package/js/gallery.d.ts +5 -0
- package/js/gallery.js +44 -26
- package/js/group-picker.d.ts +5 -0
- package/js/group-picker.js +12 -17
- package/js/lightbox.d.ts +3 -0
- package/js/lightbox.js +12 -6
- package/js/modal.d.ts +3 -1
- package/js/modal.js +15 -12
- package/js/popover.d.ts +2 -0
- package/js/popover.js +26 -30
- package/js/position.d.ts +2 -0
- package/js/position.js +1 -5
- package/js/push-menu.d.ts +2 -0
- package/js/push-menu.js +22 -35
- package/js/range-slider.d.ts +1 -0
- package/js/range-slider.js +5 -3
- package/js/scroll.d.ts +2 -0
- package/js/scroll.js +1 -0
- package/js/scrollbar.d.ts +2 -0
- package/js/scrollbar.js +24 -36
- package/js/select.d.ts +1 -0
- package/js/select.js +5 -10
- package/js/sidebar-nav.d.ts +12 -0
- package/js/sidebar-nav.js +47 -0
- package/js/stepper.d.ts +2 -0
- package/js/stepper.js +7 -1
- package/js/table.d.ts +4 -0
- package/js/table.js +15 -22
- package/js/tabs.d.ts +2 -0
- package/js/tabs.js +6 -14
- package/js/theme.d.ts +1 -0
- package/js/theme.js +5 -13
- package/js/timepicker.d.ts +3 -0
- package/js/timepicker.js +81 -67
- package/js/toast.d.ts +3 -0
- package/js/toast.js +24 -15
- package/js/tooltip.d.ts +2 -0
- package/js/tooltip.js +21 -19
- package/js/tree.d.ts +3 -0
- package/js/tree.js +13 -0
- package/js/utils.d.ts +1 -3
- package/js/utils.js +0 -3
- package/js/virtual-dropdown.d.ts +3 -0
- package/js/virtual-dropdown.js +25 -0
- 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
|
-
|
|
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;
|
|
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 >=
|
|
541
|
-
return `${(v /
|
|
542
|
-
if (v >=
|
|
543
|
-
return `${(v /
|
|
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();
|
package/js/code-viewer.d.ts
CHANGED
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)
|
package/js/context-menu.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/js/context-menu.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
/** Right-click context menu with keyboard navigation and nested submenu support. */
|
|
1
2
|
class ContextMenu {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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
|
});
|
package/js/datepicker.d.ts
CHANGED
|
@@ -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
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
this.calendar.style.
|
|
103
|
-
this.calendar.style.
|
|
104
|
-
|
|
105
|
-
|
|
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');
|
package/js/file-uploader.d.ts
CHANGED
|
@@ -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;
|