juxscript 1.1.161 → 1.1.163
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/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +2 -0
- package/lib/components/dropdown-menu.d.ts +42 -0
- package/lib/components/dropdown-menu.d.ts.map +1 -0
- package/lib/components/dropdown-menu.js +177 -0
- package/lib/components/dropdown-menu.ts +214 -0
- package/lib/facades/dataframe.jux +0 -0
- package/lib/storage/TabularDriver.d.ts +11 -1
- package/lib/storage/TabularDriver.d.ts.map +1 -1
- package/lib/storage/TabularDriver.js +89 -1
- package/lib/storage/TabularDriver.ts +103 -1
- package/package.json +3 -2
package/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { datepicker } from './lib/components/datepicker.js';
|
|
|
12
12
|
import { dialog } from './lib/components/dialog.js';
|
|
13
13
|
import { divider } from './lib/components/divider.js';
|
|
14
14
|
import { dropdown } from './lib/components/dropdown.js';
|
|
15
|
+
import { dropdownMenu } from './lib/components/dropdown-menu.js';
|
|
15
16
|
import { element } from './lib/components/element.js';
|
|
16
17
|
import { fileupload } from './lib/components/fileupload.js';
|
|
17
18
|
import { grid } from './lib/components/grid.js';
|
|
@@ -76,6 +77,7 @@ export declare const jux: {
|
|
|
76
77
|
dialog: typeof dialog;
|
|
77
78
|
divider: typeof divider;
|
|
78
79
|
dropdown: typeof dropdown;
|
|
80
|
+
dropdownMenu: typeof dropdownMenu;
|
|
79
81
|
element: typeof element;
|
|
80
82
|
fileupload: typeof fileupload;
|
|
81
83
|
grid: typeof grid;
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
package/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { datepicker } from './lib/components/datepicker.js';
|
|
|
12
12
|
import { dialog } from './lib/components/dialog.js';
|
|
13
13
|
import { divider } from './lib/components/divider.js';
|
|
14
14
|
import { dropdown } from './lib/components/dropdown.js';
|
|
15
|
+
import { dropdownMenu } from './lib/components/dropdown-menu.js';
|
|
15
16
|
import { element } from './lib/components/element.js';
|
|
16
17
|
import { fileupload } from './lib/components/fileupload.js';
|
|
17
18
|
import { grid } from './lib/components/grid.js';
|
|
@@ -82,6 +83,7 @@ export const jux = {
|
|
|
82
83
|
dialog,
|
|
83
84
|
divider,
|
|
84
85
|
dropdown,
|
|
86
|
+
dropdownMenu,
|
|
85
87
|
element,
|
|
86
88
|
fileupload,
|
|
87
89
|
grid,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BaseComponent, BaseState } from './base/BaseComponent.js';
|
|
2
|
+
export interface DropdownMenuItem {
|
|
3
|
+
type?: 'item' | 'label' | 'separator';
|
|
4
|
+
label?: string;
|
|
5
|
+
value?: string;
|
|
6
|
+
shortcut?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
submenu?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface DropdownMenuOptions {
|
|
11
|
+
trigger?: string;
|
|
12
|
+
items?: DropdownMenuItem[];
|
|
13
|
+
style?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
type DropdownMenuState = BaseState & {
|
|
17
|
+
trigger: string;
|
|
18
|
+
items: DropdownMenuItem[];
|
|
19
|
+
open: boolean;
|
|
20
|
+
};
|
|
21
|
+
export declare class DropdownMenu extends BaseComponent<DropdownMenuState> {
|
|
22
|
+
private _wrapper;
|
|
23
|
+
private _menuEl;
|
|
24
|
+
constructor(id: string, options?: DropdownMenuOptions);
|
|
25
|
+
protected getTriggerEvents(): readonly string[];
|
|
26
|
+
protected getCallbackEvents(): readonly string[];
|
|
27
|
+
trigger(value: string): this;
|
|
28
|
+
items(value: DropdownMenuItem[]): this;
|
|
29
|
+
addItem(item: DropdownMenuItem): this;
|
|
30
|
+
separator(): this;
|
|
31
|
+
label(text: string): this;
|
|
32
|
+
item(label: string, value?: string, shortcut?: string): this;
|
|
33
|
+
open(): this;
|
|
34
|
+
close(): this;
|
|
35
|
+
toggle(): this;
|
|
36
|
+
update(prop: string, value: any): void;
|
|
37
|
+
render(targetId?: string | HTMLElement | BaseComponent<any>): this;
|
|
38
|
+
private _buildItems;
|
|
39
|
+
}
|
|
40
|
+
export declare function dropdownMenu(id: string, options?: DropdownMenuOptions): DropdownMenu;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=dropdown-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropdown-menu.d.ts","sourceRoot":"","sources":["dropdown-menu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAKnE,MAAM,WAAW,gBAAgB;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,iBAAiB,GAAG,SAAS,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,IAAI,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,qBAAa,YAAa,SAAQ,aAAa,CAAC,iBAAiB,CAAC;IAC9D,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,OAAO,CAA4B;gBAE/B,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;IAUzD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAC/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAIhD,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAE5B,KAAK,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,IAAI;IAEtC,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAKrC,SAAS,IAAI,IAAI;IAIjB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIzB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAI5D,IAAI,IAAI,IAAI;IAMZ,KAAK,IAAI,IAAI;IAMb,MAAM,IAAI,IAAI;IAId,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAItC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;IA2ElE,OAAO,CAAC,WAAW;CA+CtB;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,YAAY,CAExF"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { BaseComponent } from './base/BaseComponent.js';
|
|
2
|
+
const TRIGGER_EVENTS = [];
|
|
3
|
+
const CALLBACK_EVENTS = ['select'];
|
|
4
|
+
export class DropdownMenu extends BaseComponent {
|
|
5
|
+
constructor(id, options = {}) {
|
|
6
|
+
super(id, {
|
|
7
|
+
trigger: options.trigger ?? 'Open',
|
|
8
|
+
items: options.items ?? [],
|
|
9
|
+
open: false,
|
|
10
|
+
style: options.style ?? '',
|
|
11
|
+
class: options.class ?? ''
|
|
12
|
+
});
|
|
13
|
+
this._wrapper = null;
|
|
14
|
+
this._menuEl = null;
|
|
15
|
+
}
|
|
16
|
+
getTriggerEvents() { return TRIGGER_EVENTS; }
|
|
17
|
+
getCallbackEvents() { return CALLBACK_EVENTS; }
|
|
18
|
+
/* ═══════════════════════ FLUENT API ═══════════════════════ */
|
|
19
|
+
trigger(value) { this.state.trigger = value; return this; }
|
|
20
|
+
items(value) { this.state.items = value; return this; }
|
|
21
|
+
addItem(item) {
|
|
22
|
+
this.state.items = [...this.state.items, item];
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
separator() {
|
|
26
|
+
return this.addItem({ type: 'separator' });
|
|
27
|
+
}
|
|
28
|
+
label(text) {
|
|
29
|
+
return this.addItem({ type: 'label', label: text });
|
|
30
|
+
}
|
|
31
|
+
item(label, value, shortcut) {
|
|
32
|
+
return this.addItem({ label, value: value ?? label, shortcut });
|
|
33
|
+
}
|
|
34
|
+
open() {
|
|
35
|
+
this.state.open = true;
|
|
36
|
+
if (this._menuEl)
|
|
37
|
+
this._menuEl.style.display = 'flex';
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
close() {
|
|
41
|
+
this.state.open = false;
|
|
42
|
+
if (this._menuEl)
|
|
43
|
+
this._menuEl.style.display = 'none';
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
toggle() {
|
|
47
|
+
return this.state.open ? this.close() : this.open();
|
|
48
|
+
}
|
|
49
|
+
update(prop, value) { }
|
|
50
|
+
/* ═══════════════════════ RENDER ═══════════════════════ */
|
|
51
|
+
render(targetId) {
|
|
52
|
+
const container = this._setupContainer(targetId);
|
|
53
|
+
const { trigger, items, style, class: className } = this.state;
|
|
54
|
+
// Inject styles once
|
|
55
|
+
if (!document.getElementById('jux-dropdown-menu-styles')) {
|
|
56
|
+
const styleEl = document.createElement('style');
|
|
57
|
+
styleEl.id = 'jux-dropdown-menu-styles';
|
|
58
|
+
styleEl.textContent = `
|
|
59
|
+
.jux-dmenu-wrapper { position:relative; display:inline-block; }
|
|
60
|
+
.jux-dmenu-trigger { display:inline-flex; align-items:center; gap:4px; padding:8px 16px; font-size:14px; font-weight:500; border-radius:6px; border:1px solid hsl(var(--border,0 0% 80%)); background:hsl(var(--background,0 0% 100%)); color:hsl(var(--foreground,0 0% 9%)); cursor:pointer; transition:background .15s; }
|
|
61
|
+
.jux-dmenu-trigger:hover { background:hsl(var(--accent,0 0% 96%)); }
|
|
62
|
+
.jux-dmenu-menu { position:absolute; top:calc(100% + 4px); left:0; min-width:220px; padding:4px; border-radius:8px; border:1px solid hsl(var(--border,0 0% 90%)); background:hsl(var(--popover,0 0% 100%)); color:hsl(var(--popover-foreground,0 0% 9%)); box-shadow:0 4px 12px rgba(0,0,0,.08),0 1px 3px rgba(0,0,0,.06); z-index:100; display:none; flex-direction:column; animation:jux-dmenu-in .12s ease-out; }
|
|
63
|
+
@keyframes jux-dmenu-in { from{opacity:0;transform:translateY(-4px) scale(.98)} to{opacity:1;transform:translateY(0) scale(1)} }
|
|
64
|
+
.jux-dmenu-label { padding:6px 8px 4px; font-size:12px; font-weight:600; color:hsl(var(--muted-foreground,0 0% 45%)); user-select:none; }
|
|
65
|
+
.jux-dmenu-sep { height:1px; margin:4px -4px; background:hsl(var(--border,0 0% 90%)); }
|
|
66
|
+
.jux-dmenu-item { display:flex; align-items:center; justify-content:space-between; width:100%; padding:6px 8px; font-size:14px; border-radius:4px; border:none; background:transparent; color:hsl(var(--popover-foreground,0 0% 9%)); cursor:pointer; text-align:left; transition:background .1s; gap:8px; font-family:inherit; }
|
|
67
|
+
.jux-dmenu-item:hover { background:hsl(var(--accent,0 0% 96%)); }
|
|
68
|
+
.jux-dmenu-item:focus-visible { outline:2px solid hsl(var(--ring,0 0% 63%)); outline-offset:-2px; }
|
|
69
|
+
.jux-dmenu-item[disabled] { opacity:.5; pointer-events:none; }
|
|
70
|
+
.jux-dmenu-item-label { flex:1; }
|
|
71
|
+
.jux-dmenu-shortcut { font-size:12px; color:hsl(var(--muted-foreground,0 0% 45%)); letter-spacing:.04em; }
|
|
72
|
+
.jux-dmenu-arrow { font-size:10px; color:hsl(var(--muted-foreground,0 0% 45%)); }
|
|
73
|
+
`;
|
|
74
|
+
document.head.appendChild(styleEl);
|
|
75
|
+
}
|
|
76
|
+
const wrapper = document.createElement('div');
|
|
77
|
+
wrapper.className = 'jux-dmenu-wrapper';
|
|
78
|
+
wrapper.id = this._id;
|
|
79
|
+
if (className)
|
|
80
|
+
wrapper.className += ` ${className}`;
|
|
81
|
+
if (style)
|
|
82
|
+
wrapper.setAttribute('style', style);
|
|
83
|
+
const triggerBtn = document.createElement('button');
|
|
84
|
+
triggerBtn.className = 'jux-dmenu-trigger';
|
|
85
|
+
triggerBtn.textContent = trigger;
|
|
86
|
+
triggerBtn.type = 'button';
|
|
87
|
+
wrapper.appendChild(triggerBtn);
|
|
88
|
+
const menu = document.createElement('div');
|
|
89
|
+
menu.className = 'jux-dmenu-menu';
|
|
90
|
+
this._menuEl = menu;
|
|
91
|
+
this._buildItems(menu, items);
|
|
92
|
+
wrapper.appendChild(menu);
|
|
93
|
+
// Toggle
|
|
94
|
+
triggerBtn.addEventListener('click', (e) => { e.stopPropagation(); this.toggle(); });
|
|
95
|
+
document.addEventListener('click', (e) => { if (!wrapper.contains(e.target))
|
|
96
|
+
this.close(); });
|
|
97
|
+
// Keyboard
|
|
98
|
+
triggerBtn.addEventListener('keydown', (e) => {
|
|
99
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
this.open();
|
|
102
|
+
const first = menu.querySelector('.jux-dmenu-item:not([disabled])');
|
|
103
|
+
if (first)
|
|
104
|
+
first.focus();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
menu.addEventListener('keydown', (e) => {
|
|
108
|
+
const btns = Array.from(menu.querySelectorAll('.jux-dmenu-item:not([disabled])'));
|
|
109
|
+
const idx = btns.indexOf(document.activeElement);
|
|
110
|
+
if (e.key === 'ArrowDown') {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
btns[(idx + 1) % btns.length]?.focus();
|
|
113
|
+
}
|
|
114
|
+
else if (e.key === 'ArrowUp') {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
btns[(idx - 1 + btns.length) % btns.length]?.focus();
|
|
117
|
+
}
|
|
118
|
+
else if (e.key === 'Escape') {
|
|
119
|
+
this.close();
|
|
120
|
+
triggerBtn.focus();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
this._wireStandardEvents(wrapper);
|
|
124
|
+
container.appendChild(wrapper);
|
|
125
|
+
this._wrapper = wrapper;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
_buildItems(menu, items) {
|
|
129
|
+
menu.innerHTML = '';
|
|
130
|
+
items.forEach(item => {
|
|
131
|
+
if (item.type === 'separator') {
|
|
132
|
+
const sep = document.createElement('div');
|
|
133
|
+
sep.className = 'jux-dmenu-sep';
|
|
134
|
+
menu.appendChild(sep);
|
|
135
|
+
}
|
|
136
|
+
else if (item.type === 'label') {
|
|
137
|
+
const lbl = document.createElement('div');
|
|
138
|
+
lbl.className = 'jux-dmenu-label';
|
|
139
|
+
lbl.textContent = item.label ?? '';
|
|
140
|
+
menu.appendChild(lbl);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const btn = document.createElement('button');
|
|
144
|
+
btn.className = 'jux-dmenu-item';
|
|
145
|
+
btn.type = 'button';
|
|
146
|
+
if (item.disabled)
|
|
147
|
+
btn.disabled = true;
|
|
148
|
+
const labelSpan = document.createElement('span');
|
|
149
|
+
labelSpan.className = 'jux-dmenu-item-label';
|
|
150
|
+
labelSpan.textContent = item.label ?? '';
|
|
151
|
+
btn.appendChild(labelSpan);
|
|
152
|
+
if (item.submenu) {
|
|
153
|
+
const arrow = document.createElement('span');
|
|
154
|
+
arrow.className = 'jux-dmenu-arrow';
|
|
155
|
+
arrow.textContent = '›';
|
|
156
|
+
btn.appendChild(arrow);
|
|
157
|
+
}
|
|
158
|
+
else if (item.shortcut) {
|
|
159
|
+
const sc = document.createElement('span');
|
|
160
|
+
sc.className = 'jux-dmenu-shortcut';
|
|
161
|
+
sc.textContent = item.shortcut;
|
|
162
|
+
btn.appendChild(sc);
|
|
163
|
+
}
|
|
164
|
+
btn.addEventListener('click', (e) => {
|
|
165
|
+
if (!item.disabled) {
|
|
166
|
+
this._triggerCallback('select', item.value || item.label, e, this);
|
|
167
|
+
this.close();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
menu.appendChild(btn);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export function dropdownMenu(id, options = {}) {
|
|
176
|
+
return new DropdownMenu(id, options);
|
|
177
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { BaseComponent, BaseState } from './base/BaseComponent.js';
|
|
2
|
+
|
|
3
|
+
const TRIGGER_EVENTS = [] as const;
|
|
4
|
+
const CALLBACK_EVENTS = ['select'] as const;
|
|
5
|
+
|
|
6
|
+
export interface DropdownMenuItem {
|
|
7
|
+
type?: 'item' | 'label' | 'separator';
|
|
8
|
+
label?: string;
|
|
9
|
+
value?: string;
|
|
10
|
+
shortcut?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
submenu?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DropdownMenuOptions {
|
|
16
|
+
trigger?: string;
|
|
17
|
+
items?: DropdownMenuItem[];
|
|
18
|
+
style?: string;
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type DropdownMenuState = BaseState & {
|
|
23
|
+
trigger: string;
|
|
24
|
+
items: DropdownMenuItem[];
|
|
25
|
+
open: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class DropdownMenu extends BaseComponent<DropdownMenuState> {
|
|
29
|
+
private _wrapper: HTMLElement | null = null;
|
|
30
|
+
private _menuEl: HTMLElement | null = null;
|
|
31
|
+
|
|
32
|
+
constructor(id: string, options: DropdownMenuOptions = {}) {
|
|
33
|
+
super(id, {
|
|
34
|
+
trigger: options.trigger ?? 'Open',
|
|
35
|
+
items: options.items ?? [],
|
|
36
|
+
open: false,
|
|
37
|
+
style: options.style ?? '',
|
|
38
|
+
class: options.class ?? ''
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected getTriggerEvents(): readonly string[] { return TRIGGER_EVENTS; }
|
|
43
|
+
protected getCallbackEvents(): readonly string[] { return CALLBACK_EVENTS; }
|
|
44
|
+
|
|
45
|
+
/* ═══════════════════════ FLUENT API ═══════════════════════ */
|
|
46
|
+
|
|
47
|
+
trigger(value: string): this { this.state.trigger = value; return this; }
|
|
48
|
+
|
|
49
|
+
items(value: DropdownMenuItem[]): this { this.state.items = value; return this; }
|
|
50
|
+
|
|
51
|
+
addItem(item: DropdownMenuItem): this {
|
|
52
|
+
this.state.items = [...this.state.items, item];
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
separator(): this {
|
|
57
|
+
return this.addItem({ type: 'separator' });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
label(text: string): this {
|
|
61
|
+
return this.addItem({ type: 'label', label: text });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
item(label: string, value?: string, shortcut?: string): this {
|
|
65
|
+
return this.addItem({ label, value: value ?? label, shortcut });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
open(): this {
|
|
69
|
+
this.state.open = true;
|
|
70
|
+
if (this._menuEl) this._menuEl.style.display = 'flex';
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
close(): this {
|
|
75
|
+
this.state.open = false;
|
|
76
|
+
if (this._menuEl) this._menuEl.style.display = 'none';
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
toggle(): this {
|
|
81
|
+
return this.state.open ? this.close() : this.open();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
update(prop: string, value: any): void { }
|
|
85
|
+
|
|
86
|
+
/* ═══════════════════════ RENDER ═══════════════════════ */
|
|
87
|
+
|
|
88
|
+
render(targetId?: string | HTMLElement | BaseComponent<any>): this {
|
|
89
|
+
const container = this._setupContainer(targetId);
|
|
90
|
+
const { trigger, items, style, class: className } = this.state;
|
|
91
|
+
|
|
92
|
+
// Inject styles once
|
|
93
|
+
if (!document.getElementById('jux-dropdown-menu-styles')) {
|
|
94
|
+
const styleEl = document.createElement('style');
|
|
95
|
+
styleEl.id = 'jux-dropdown-menu-styles';
|
|
96
|
+
styleEl.textContent = `
|
|
97
|
+
.jux-dmenu-wrapper { position:relative; display:inline-block; }
|
|
98
|
+
.jux-dmenu-trigger { display:inline-flex; align-items:center; gap:4px; padding:8px 16px; font-size:14px; font-weight:500; border-radius:6px; border:1px solid hsl(var(--border,0 0% 80%)); background:hsl(var(--background,0 0% 100%)); color:hsl(var(--foreground,0 0% 9%)); cursor:pointer; transition:background .15s; }
|
|
99
|
+
.jux-dmenu-trigger:hover { background:hsl(var(--accent,0 0% 96%)); }
|
|
100
|
+
.jux-dmenu-menu { position:absolute; top:calc(100% + 4px); left:0; min-width:220px; padding:4px; border-radius:8px; border:1px solid hsl(var(--border,0 0% 90%)); background:hsl(var(--popover,0 0% 100%)); color:hsl(var(--popover-foreground,0 0% 9%)); box-shadow:0 4px 12px rgba(0,0,0,.08),0 1px 3px rgba(0,0,0,.06); z-index:100; display:none; flex-direction:column; animation:jux-dmenu-in .12s ease-out; }
|
|
101
|
+
@keyframes jux-dmenu-in { from{opacity:0;transform:translateY(-4px) scale(.98)} to{opacity:1;transform:translateY(0) scale(1)} }
|
|
102
|
+
.jux-dmenu-label { padding:6px 8px 4px; font-size:12px; font-weight:600; color:hsl(var(--muted-foreground,0 0% 45%)); user-select:none; }
|
|
103
|
+
.jux-dmenu-sep { height:1px; margin:4px -4px; background:hsl(var(--border,0 0% 90%)); }
|
|
104
|
+
.jux-dmenu-item { display:flex; align-items:center; justify-content:space-between; width:100%; padding:6px 8px; font-size:14px; border-radius:4px; border:none; background:transparent; color:hsl(var(--popover-foreground,0 0% 9%)); cursor:pointer; text-align:left; transition:background .1s; gap:8px; font-family:inherit; }
|
|
105
|
+
.jux-dmenu-item:hover { background:hsl(var(--accent,0 0% 96%)); }
|
|
106
|
+
.jux-dmenu-item:focus-visible { outline:2px solid hsl(var(--ring,0 0% 63%)); outline-offset:-2px; }
|
|
107
|
+
.jux-dmenu-item[disabled] { opacity:.5; pointer-events:none; }
|
|
108
|
+
.jux-dmenu-item-label { flex:1; }
|
|
109
|
+
.jux-dmenu-shortcut { font-size:12px; color:hsl(var(--muted-foreground,0 0% 45%)); letter-spacing:.04em; }
|
|
110
|
+
.jux-dmenu-arrow { font-size:10px; color:hsl(var(--muted-foreground,0 0% 45%)); }
|
|
111
|
+
`;
|
|
112
|
+
document.head.appendChild(styleEl);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const wrapper = document.createElement('div');
|
|
116
|
+
wrapper.className = 'jux-dmenu-wrapper';
|
|
117
|
+
wrapper.id = this._id;
|
|
118
|
+
if (className) wrapper.className += ` ${className}`;
|
|
119
|
+
if (style) wrapper.setAttribute('style', style);
|
|
120
|
+
|
|
121
|
+
const triggerBtn = document.createElement('button');
|
|
122
|
+
triggerBtn.className = 'jux-dmenu-trigger';
|
|
123
|
+
triggerBtn.textContent = trigger;
|
|
124
|
+
triggerBtn.type = 'button';
|
|
125
|
+
wrapper.appendChild(triggerBtn);
|
|
126
|
+
|
|
127
|
+
const menu = document.createElement('div');
|
|
128
|
+
menu.className = 'jux-dmenu-menu';
|
|
129
|
+
this._menuEl = menu;
|
|
130
|
+
|
|
131
|
+
this._buildItems(menu, items);
|
|
132
|
+
|
|
133
|
+
wrapper.appendChild(menu);
|
|
134
|
+
|
|
135
|
+
// Toggle
|
|
136
|
+
triggerBtn.addEventListener('click', (e) => { e.stopPropagation(); this.toggle(); });
|
|
137
|
+
document.addEventListener('click', (e) => { if (!wrapper.contains(e.target as Node)) this.close(); });
|
|
138
|
+
|
|
139
|
+
// Keyboard
|
|
140
|
+
triggerBtn.addEventListener('keydown', (e) => {
|
|
141
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
|
|
142
|
+
e.preventDefault(); this.open();
|
|
143
|
+
const first = menu.querySelector('.jux-dmenu-item:not([disabled])') as HTMLElement;
|
|
144
|
+
if (first) first.focus();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
menu.addEventListener('keydown', (e) => {
|
|
149
|
+
const btns = Array.from(menu.querySelectorAll('.jux-dmenu-item:not([disabled])')) as HTMLElement[];
|
|
150
|
+
const idx = btns.indexOf(document.activeElement as HTMLElement);
|
|
151
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); btns[(idx + 1) % btns.length]?.focus(); }
|
|
152
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); btns[(idx - 1 + btns.length) % btns.length]?.focus(); }
|
|
153
|
+
else if (e.key === 'Escape') { this.close(); triggerBtn.focus(); }
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
this._wireStandardEvents(wrapper);
|
|
157
|
+
container.appendChild(wrapper);
|
|
158
|
+
this._wrapper = wrapper;
|
|
159
|
+
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private _buildItems(menu: HTMLElement, items: DropdownMenuItem[]): void {
|
|
164
|
+
menu.innerHTML = '';
|
|
165
|
+
|
|
166
|
+
items.forEach(item => {
|
|
167
|
+
if (item.type === 'separator') {
|
|
168
|
+
const sep = document.createElement('div');
|
|
169
|
+
sep.className = 'jux-dmenu-sep';
|
|
170
|
+
menu.appendChild(sep);
|
|
171
|
+
} else if (item.type === 'label') {
|
|
172
|
+
const lbl = document.createElement('div');
|
|
173
|
+
lbl.className = 'jux-dmenu-label';
|
|
174
|
+
lbl.textContent = item.label ?? '';
|
|
175
|
+
menu.appendChild(lbl);
|
|
176
|
+
} else {
|
|
177
|
+
const btn = document.createElement('button');
|
|
178
|
+
btn.className = 'jux-dmenu-item';
|
|
179
|
+
btn.type = 'button';
|
|
180
|
+
if (item.disabled) btn.disabled = true;
|
|
181
|
+
|
|
182
|
+
const labelSpan = document.createElement('span');
|
|
183
|
+
labelSpan.className = 'jux-dmenu-item-label';
|
|
184
|
+
labelSpan.textContent = item.label ?? '';
|
|
185
|
+
btn.appendChild(labelSpan);
|
|
186
|
+
|
|
187
|
+
if (item.submenu) {
|
|
188
|
+
const arrow = document.createElement('span');
|
|
189
|
+
arrow.className = 'jux-dmenu-arrow';
|
|
190
|
+
arrow.textContent = '›';
|
|
191
|
+
btn.appendChild(arrow);
|
|
192
|
+
} else if (item.shortcut) {
|
|
193
|
+
const sc = document.createElement('span');
|
|
194
|
+
sc.className = 'jux-dmenu-shortcut';
|
|
195
|
+
sc.textContent = item.shortcut;
|
|
196
|
+
btn.appendChild(sc);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
btn.addEventListener('click', (e) => {
|
|
200
|
+
if (!item.disabled) {
|
|
201
|
+
this._triggerCallback('select', item.value || item.label, e, this);
|
|
202
|
+
this.close();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
menu.appendChild(btn);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function dropdownMenu(id: string, options: DropdownMenuOptions = {}): DropdownMenu {
|
|
213
|
+
return new DropdownMenu(id, options);
|
|
214
|
+
}
|
|
File without changes
|
|
@@ -17,6 +17,7 @@ export interface ParseOptions {
|
|
|
17
17
|
maxRows?: number;
|
|
18
18
|
skipRows?: number;
|
|
19
19
|
columns?: string[];
|
|
20
|
+
sheet?: string | number;
|
|
20
21
|
}
|
|
21
22
|
export declare class TabularDriver {
|
|
22
23
|
private _dbName;
|
|
@@ -29,9 +30,18 @@ export declare class TabularDriver {
|
|
|
29
30
|
*/
|
|
30
31
|
parseCSV(text: string, options?: ParseOptions): DataFrame;
|
|
31
32
|
/**
|
|
32
|
-
* Stream-parse a File into a DataFrame, with progress callback
|
|
33
|
+
* Stream-parse a File into a DataFrame, with progress callback.
|
|
34
|
+
* Auto-detects XLSX/XLS and routes accordingly.
|
|
33
35
|
*/
|
|
34
36
|
streamFile(file: File, options?: ParseOptions): Promise<DataFrame>;
|
|
37
|
+
/**
|
|
38
|
+
* Parse an XLSX/XLS file into a DataFrame using SheetJS
|
|
39
|
+
*/
|
|
40
|
+
private _parseXLSX;
|
|
41
|
+
/**
|
|
42
|
+
* Get sheet names from an XLSX file (useful for UI)
|
|
43
|
+
*/
|
|
44
|
+
getSheetNames(file: File): Promise<string[]>;
|
|
35
45
|
/**
|
|
36
46
|
* Store a DataFrame to IndexedDB
|
|
37
47
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,GAAG,CAA4B;gBAE3B,MAAM,GAAE,MAAsB,EAAE,SAAS,GAAE,MAAiB;IAKlE,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IA4BlC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,SAAS;IA6C7D;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IAoG5E;;OAEG;YACW,UAAU;IAuExB;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiBlD;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBzF;;OAEG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAwBjD;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA4BzD;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAqBlH;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5B;;OAEG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IA8ExE,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,UAAU;IAmClB,OAAO,CAAC,SAAS;IAYjB,KAAK,IAAI,IAAI;CAMhB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,aAAa,CAEhF"}
|
|
@@ -63,9 +63,14 @@ export class TabularDriver {
|
|
|
63
63
|
return df;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
* Stream-parse a File into a DataFrame, with progress callback
|
|
66
|
+
* Stream-parse a File into a DataFrame, with progress callback.
|
|
67
|
+
* Auto-detects XLSX/XLS and routes accordingly.
|
|
67
68
|
*/
|
|
68
69
|
async streamFile(file, options = {}) {
|
|
70
|
+
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
71
|
+
if (ext === 'xlsx' || ext === 'xls') {
|
|
72
|
+
return this._parseXLSX(file, options);
|
|
73
|
+
}
|
|
69
74
|
const { delimiter = file.name.endsWith('.tsv') ? '\t' : ',', hasHeader = true, chunkSize = 64 * 1024, onProgress, maxRows, skipRows = 0, columns: selectCols } = options;
|
|
70
75
|
const totalSize = file.size;
|
|
71
76
|
let bytesRead = 0;
|
|
@@ -136,6 +141,89 @@ export class TabularDriver {
|
|
|
136
141
|
df = df.select(...selectCols);
|
|
137
142
|
return df;
|
|
138
143
|
}
|
|
144
|
+
/* ═══════════════════════════════════════════════════
|
|
145
|
+
* XLSX / XLS PARSING
|
|
146
|
+
* ═══════════════════════════════════════════════════ */
|
|
147
|
+
/**
|
|
148
|
+
* Parse an XLSX/XLS file into a DataFrame using SheetJS
|
|
149
|
+
*/
|
|
150
|
+
async _parseXLSX(file, options = {}) {
|
|
151
|
+
const { maxRows, skipRows = 0, columns: selectCols, sheet, onProgress, hasHeader = true } = options;
|
|
152
|
+
// Dynamic import — fails gracefully if xlsx not installed
|
|
153
|
+
let XLSX;
|
|
154
|
+
try {
|
|
155
|
+
XLSX = await import('xlsx');
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
|
|
159
|
+
}
|
|
160
|
+
if (onProgress)
|
|
161
|
+
onProgress(0, file.size);
|
|
162
|
+
const buffer = await file.arrayBuffer();
|
|
163
|
+
if (onProgress)
|
|
164
|
+
onProgress(file.size * 0.5, file.size);
|
|
165
|
+
const workbook = XLSX.read(buffer, { type: 'array' });
|
|
166
|
+
// Select sheet
|
|
167
|
+
let sheetName;
|
|
168
|
+
if (typeof sheet === 'number') {
|
|
169
|
+
sheetName = workbook.SheetNames[sheet] || workbook.SheetNames[0];
|
|
170
|
+
}
|
|
171
|
+
else if (typeof sheet === 'string') {
|
|
172
|
+
sheetName = sheet;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
sheetName = workbook.SheetNames[0];
|
|
176
|
+
}
|
|
177
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
178
|
+
if (!worksheet) {
|
|
179
|
+
throw new Error(`Sheet "${sheetName}" not found. Available: ${workbook.SheetNames.join(', ')}`);
|
|
180
|
+
}
|
|
181
|
+
// Convert to JSON rows
|
|
182
|
+
const jsonRows = XLSX.utils.sheet_to_json(worksheet, {
|
|
183
|
+
header: hasHeader ? undefined : 1,
|
|
184
|
+
defval: null,
|
|
185
|
+
raw: true
|
|
186
|
+
});
|
|
187
|
+
if (onProgress)
|
|
188
|
+
onProgress(file.size * 0.8, file.size);
|
|
189
|
+
// Apply skipRows and maxRows
|
|
190
|
+
let rows = jsonRows;
|
|
191
|
+
if (skipRows > 0) {
|
|
192
|
+
rows = rows.slice(skipRows);
|
|
193
|
+
}
|
|
194
|
+
if (maxRows !== undefined) {
|
|
195
|
+
rows = rows.slice(0, maxRows);
|
|
196
|
+
}
|
|
197
|
+
// Auto-type values
|
|
198
|
+
rows = rows.map(row => {
|
|
199
|
+
const typed = {};
|
|
200
|
+
for (const [key, value] of Object.entries(row)) {
|
|
201
|
+
typed[key] = this._autoType(value === null || value === undefined ? '' : String(value));
|
|
202
|
+
}
|
|
203
|
+
return typed;
|
|
204
|
+
});
|
|
205
|
+
if (onProgress)
|
|
206
|
+
onProgress(file.size, file.size);
|
|
207
|
+
let df = new DataFrame(rows);
|
|
208
|
+
if (selectCols)
|
|
209
|
+
df = df.select(...selectCols);
|
|
210
|
+
return df;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get sheet names from an XLSX file (useful for UI)
|
|
214
|
+
*/
|
|
215
|
+
async getSheetNames(file) {
|
|
216
|
+
let XLSX;
|
|
217
|
+
try {
|
|
218
|
+
XLSX = await import('xlsx');
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
|
|
222
|
+
}
|
|
223
|
+
const buffer = await file.arrayBuffer();
|
|
224
|
+
const workbook = XLSX.read(buffer, { type: 'array' });
|
|
225
|
+
return workbook.SheetNames;
|
|
226
|
+
}
|
|
139
227
|
/* ═══════════════════════════════════════════════════
|
|
140
228
|
* INDEXEDDB PERSISTENCE
|
|
141
229
|
* ═══════════════════════════════════════════════════ */
|
|
@@ -19,6 +19,7 @@ export interface ParseOptions {
|
|
|
19
19
|
maxRows?: number;
|
|
20
20
|
skipRows?: number;
|
|
21
21
|
columns?: string[];
|
|
22
|
+
sheet?: string | number; // ✅ For XLSX: sheet name or index
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export class TabularDriver {
|
|
@@ -108,9 +109,16 @@ export class TabularDriver {
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
/**
|
|
111
|
-
* Stream-parse a File into a DataFrame, with progress callback
|
|
112
|
+
* Stream-parse a File into a DataFrame, with progress callback.
|
|
113
|
+
* Auto-detects XLSX/XLS and routes accordingly.
|
|
112
114
|
*/
|
|
113
115
|
async streamFile(file: File, options: ParseOptions = {}): Promise<DataFrame> {
|
|
116
|
+
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
117
|
+
|
|
118
|
+
if (ext === 'xlsx' || ext === 'xls') {
|
|
119
|
+
return this._parseXLSX(file, options);
|
|
120
|
+
}
|
|
121
|
+
|
|
114
122
|
const {
|
|
115
123
|
delimiter = file.name.endsWith('.tsv') ? '\t' : ',',
|
|
116
124
|
hasHeader = true,
|
|
@@ -200,6 +208,100 @@ export class TabularDriver {
|
|
|
200
208
|
return df;
|
|
201
209
|
}
|
|
202
210
|
|
|
211
|
+
/* ═══════════════════════════════════════════════════
|
|
212
|
+
* XLSX / XLS PARSING
|
|
213
|
+
* ═══════════════════════════════════════════════════ */
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Parse an XLSX/XLS file into a DataFrame using SheetJS
|
|
217
|
+
*/
|
|
218
|
+
private async _parseXLSX(file: File, options: ParseOptions = {}): Promise<DataFrame> {
|
|
219
|
+
const { maxRows, skipRows = 0, columns: selectCols, sheet, onProgress, hasHeader = true } = options;
|
|
220
|
+
|
|
221
|
+
// Dynamic import — fails gracefully if xlsx not installed
|
|
222
|
+
let XLSX: any;
|
|
223
|
+
try {
|
|
224
|
+
XLSX = await import('xlsx');
|
|
225
|
+
} catch {
|
|
226
|
+
throw new Error(
|
|
227
|
+
'XLSX support requires the "xlsx" package. Install it with: npm install xlsx'
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (onProgress) onProgress(0, file.size);
|
|
232
|
+
|
|
233
|
+
const buffer = await file.arrayBuffer();
|
|
234
|
+
|
|
235
|
+
if (onProgress) onProgress(file.size * 0.5, file.size);
|
|
236
|
+
|
|
237
|
+
const workbook = XLSX.read(buffer, { type: 'array' });
|
|
238
|
+
|
|
239
|
+
// Select sheet
|
|
240
|
+
let sheetName: string;
|
|
241
|
+
if (typeof sheet === 'number') {
|
|
242
|
+
sheetName = workbook.SheetNames[sheet] || workbook.SheetNames[0];
|
|
243
|
+
} else if (typeof sheet === 'string') {
|
|
244
|
+
sheetName = sheet;
|
|
245
|
+
} else {
|
|
246
|
+
sheetName = workbook.SheetNames[0];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
250
|
+
if (!worksheet) {
|
|
251
|
+
throw new Error(`Sheet "${sheetName}" not found. Available: ${workbook.SheetNames.join(', ')}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Convert to JSON rows
|
|
255
|
+
const jsonRows: Record<string, any>[] = XLSX.utils.sheet_to_json(worksheet, {
|
|
256
|
+
header: hasHeader ? undefined : 1,
|
|
257
|
+
defval: null,
|
|
258
|
+
raw: true
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (onProgress) onProgress(file.size * 0.8, file.size);
|
|
262
|
+
|
|
263
|
+
// Apply skipRows and maxRows
|
|
264
|
+
let rows = jsonRows;
|
|
265
|
+
if (skipRows > 0) {
|
|
266
|
+
rows = rows.slice(skipRows);
|
|
267
|
+
}
|
|
268
|
+
if (maxRows !== undefined) {
|
|
269
|
+
rows = rows.slice(0, maxRows);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Auto-type values
|
|
273
|
+
rows = rows.map(row => {
|
|
274
|
+
const typed: Record<string, any> = {};
|
|
275
|
+
for (const [key, value] of Object.entries(row)) {
|
|
276
|
+
typed[key] = this._autoType(value === null || value === undefined ? '' : String(value));
|
|
277
|
+
}
|
|
278
|
+
return typed;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (onProgress) onProgress(file.size, file.size);
|
|
282
|
+
|
|
283
|
+
let df = new DataFrame(rows);
|
|
284
|
+
if (selectCols) df = df.select(...selectCols);
|
|
285
|
+
|
|
286
|
+
return df;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get sheet names from an XLSX file (useful for UI)
|
|
291
|
+
*/
|
|
292
|
+
async getSheetNames(file: File): Promise<string[]> {
|
|
293
|
+
let XLSX: any;
|
|
294
|
+
try {
|
|
295
|
+
XLSX = await import('xlsx');
|
|
296
|
+
} catch {
|
|
297
|
+
throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const buffer = await file.arrayBuffer();
|
|
301
|
+
const workbook = XLSX.read(buffer, { type: 'array' });
|
|
302
|
+
return workbook.SheetNames;
|
|
303
|
+
}
|
|
304
|
+
|
|
203
305
|
/* ═══════════════════════════════════════════════════
|
|
204
306
|
* INDEXEDDB PERSISTENCE
|
|
205
307
|
* ═══════════════════════════════════════════════════ */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juxscript",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.163",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A JavaScript UX authorship platform",
|
|
6
6
|
"main": "index.js",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"chart.js": "^4.5.1",
|
|
60
60
|
"esbuild": "^0.19.0",
|
|
61
61
|
"express": "^4.18.2",
|
|
62
|
-
"ws": "^8.13.0"
|
|
62
|
+
"ws": "^8.13.0",
|
|
63
|
+
"xlsx": "^0.18.5"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
66
|
"@types/express": "^4.17.17",
|