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 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;CACtB;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;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IA8F5E;;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"}
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.161",
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",