juxscript 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +143 -92
  3. package/lib/components/badge.ts +93 -94
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +40 -131
  7. package/lib/components/card.ts +57 -79
  8. package/lib/components/charts/areachart.ts +315 -0
  9. package/lib/components/charts/barchart.ts +421 -0
  10. package/lib/components/charts/doughnutchart.ts +263 -0
  11. package/lib/components/charts/lib/BaseChart.ts +402 -0
  12. package/lib/components/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
  13. package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
  14. package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
  15. package/lib/components/checkbox.ts +255 -204
  16. package/lib/components/code.ts +31 -78
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +180 -147
  20. package/lib/components/dialog.ts +218 -221
  21. package/lib/components/divider.ts +63 -87
  22. package/lib/components/docs-data.json +498 -2404
  23. package/lib/components/dropdown.ts +191 -236
  24. package/lib/components/element.ts +196 -145
  25. package/lib/components/fileupload.ts +253 -167
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +31 -97
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +51 -114
  30. package/lib/components/icon.ts +33 -120
  31. package/lib/components/icons.ts +2 -1
  32. package/lib/components/include.ts +76 -3
  33. package/lib/components/input.ts +155 -407
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +358 -261
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +63 -152
  38. package/lib/components/modal.ts +42 -129
  39. package/lib/components/nav.ts +79 -101
  40. package/lib/components/paragraph.ts +38 -102
  41. package/lib/components/progress.ts +108 -166
  42. package/lib/components/radio.ts +283 -234
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +189 -199
  45. package/lib/components/sidebar.ts +110 -141
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +254 -183
  48. package/lib/components/table.ts +1078 -208
  49. package/lib/components/tabs.ts +42 -106
  50. package/lib/components/theme-toggle.ts +73 -165
  51. package/lib/components/tooltip.ts +85 -316
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +67 -41
  54. package/machinery/build.js +466 -0
  55. package/machinery/compiler.js +354 -105
  56. package/machinery/server.js +23 -100
  57. package/machinery/watcher.js +153 -130
  58. package/package.json +1 -1
  59. package/presets/base.css +1166 -0
  60. package/presets/notion.css +2 -1975
  61. package/lib/adapters/base-adapter.js +0 -35
  62. package/lib/adapters/index.js +0 -33
  63. package/lib/adapters/mysql-adapter.js +0 -65
  64. package/lib/adapters/postgres-adapter.js +0 -70
  65. package/lib/adapters/sqlite-adapter.js +0 -56
  66. package/lib/components/areachart.ts +0 -1128
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1322
  69. package/lib/components/doughnutchart.ts +0 -1259
  70. package/lib/components/footer.ts +0 -165
  71. package/lib/components/header.ts +0 -187
  72. package/lib/components/layout.ts +0 -239
  73. package/lib/components/main.ts +0 -137
  74. package/lib/layouts/default.jux +0 -8
  75. package/lib/layouts/figma.jux +0 -0
  76. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,189 +1,121 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
3
- import { renderIcon } from './icons.js';
4
-
5
- export interface DropdownOption {
6
- label: string;
7
- value: string;
8
- disabled?: boolean;
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = ['select'] as const;
6
+
7
+ export interface DropdownItem {
8
+ label?: string;
9
+ icon?: string;
10
+ divider?: boolean;
11
+ click?: () => void;
9
12
  }
10
13
 
11
14
  export interface DropdownOptions {
12
- options?: DropdownOption[];
13
- value?: string;
14
- placeholder?: string;
15
- label?: string;
16
- disabled?: boolean;
15
+ trigger?: string;
16
+ items?: DropdownItem[];
17
+ position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
17
18
  style?: string;
18
19
  class?: string;
19
20
  }
20
21
 
21
- export interface DropdownItem {
22
- label: string;
23
- value?: string;
24
- disabled?: boolean;
25
- onClick?: () => void;
26
- }
27
-
28
22
  type DropdownState = {
29
- options: DropdownOption[];
30
- value: string;
31
- placeholder: string;
32
- label: string;
33
- disabled: boolean;
23
+ trigger: string;
24
+ items: DropdownItem[];
25
+ position: string;
26
+ open: boolean;
34
27
  style: string;
35
28
  class: string;
36
- isOpen: boolean;
37
- items: any[];
38
- trigger: string;
39
- position: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
40
29
  };
41
30
 
42
- export class Dropdown {
43
- state: DropdownState;
44
- container: HTMLElement | null = null;
45
- _id: string;
46
- id: string;
47
-
48
- // CRITICAL: Store bind/sync instructions for deferred wiring
49
- private _bindings: Array<{ event: string, handler: Function }> = [];
50
- private _syncBindings: Array<{
51
- property: string,
52
- stateObj: State<any>,
53
- toState?: Function,
54
- toComponent?: Function
55
- }> = [];
31
+ export class Dropdown extends BaseComponent<DropdownState> {
32
+ private _dropdown: HTMLElement | null = null;
33
+ private _menu: HTMLElement | null = null;
56
34
 
57
35
  constructor(id: string, options: DropdownOptions = {}) {
58
- this._id = id;
59
- this.id = id;
60
-
61
- this.state = {
62
- options: options.options ?? [],
63
- value: options.value ?? '',
64
- placeholder: options.placeholder ?? 'Select...',
65
- label: options.label ?? '',
66
- disabled: options.disabled ?? false,
36
+ super(id, {
37
+ trigger: options.trigger ?? 'Menu',
38
+ items: options.items ?? [],
39
+ position: options.position ?? 'bottom-left',
40
+ open: false,
67
41
  style: options.style ?? '',
68
- class: options.class ?? '',
69
- isOpen: false,
70
- items: [],
71
- trigger: '',
72
- position: 'bottom-left'
73
- };
42
+ class: options.class ?? ''
43
+ });
74
44
  }
75
45
 
76
- /* -------------------------
77
- * Fluent API
78
- * ------------------------- */
79
-
80
- items(value: DropdownItem[]): this {
81
- this.state.items = value;
82
- return this;
46
+ protected getTriggerEvents(): readonly string[] {
47
+ return TRIGGER_EVENTS;
83
48
  }
84
49
 
85
- addItem(item: DropdownItem): this {
86
- this.state.items = [...this.state.items, item];
87
- return this;
50
+ protected getCallbackEvents(): readonly string[] {
51
+ return CALLBACK_EVENTS;
88
52
  }
89
53
 
54
+ /* ═════════════════════════════════════════════════════════════════
55
+ * FLUENT API
56
+ * ═════════════════════════════════════════════════════════════════ */
57
+
90
58
  trigger(value: string): this {
91
59
  this.state.trigger = value;
92
60
  return this;
93
61
  }
94
62
 
95
- position(value: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'): this {
96
- this.state.position = value;
97
- return this;
98
- }
99
-
100
- style(value: string): this {
101
- this.state.style = value;
102
- return this;
103
- }
104
-
105
- class(value: string): this {
106
- this.state.class = value;
63
+ items(value: DropdownItem[]): this {
64
+ this.state.items = value;
107
65
  return this;
108
66
  }
109
67
 
110
- bind(event: string, handler: Function): this {
111
- this._bindings.push({ event, handler });
68
+ position(value: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'): this {
69
+ this.state.position = value;
112
70
  return this;
113
71
  }
114
72
 
115
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
116
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
117
- throw new Error(`Dropdown.sync: Expected a State object for property "${property}"`);
73
+ open(): this {
74
+ this.state.open = true;
75
+ if (this._menu) {
76
+ this._menu.style.display = 'block';
118
77
  }
119
- this._syncBindings.push({ property, stateObj, toState, toComponent });
120
78
  return this;
121
79
  }
122
80
 
123
- /* -------------------------
124
- * Methods
125
- * ------------------------- */
126
-
127
- open(): void {
128
- this.state.isOpen = true;
129
- const menu = document.getElementById(`${this._id}-menu`);
130
- if (menu) {
131
- menu.classList.add('jux-dropdown-menu-open');
132
- }
133
- }
134
-
135
- close(): void {
136
- this.state.isOpen = false;
137
- const menu = document.getElementById(`${this._id}-menu`);
138
- if (menu) {
139
- menu.classList.remove('jux-dropdown-menu-open');
81
+ close(): this {
82
+ this.state.open = false;
83
+ if (this._menu) {
84
+ this._menu.style.display = 'none';
140
85
  }
86
+ return this;
141
87
  }
142
88
 
143
- toggle(): void {
144
- if (this.state.isOpen) {
145
- this.close();
146
- } else {
147
- this.open();
148
- }
89
+ toggle(): this {
90
+ return this.state.open ? this.close() : this.open();
149
91
  }
150
92
 
151
- /* -------------------------
152
- * Render
153
- * ------------------------- */
93
+ /* ═════════════════════════════════════════════════════════════════
94
+ * RENDER
95
+ * ═════════════════════════════════════════════════════════════════ */
154
96
 
155
97
  render(targetId?: string): this {
156
- // === 1. SETUP: Get or create container ===
157
- let container: HTMLElement;
158
- if (targetId) {
159
- const target = document.querySelector(targetId);
160
- if (!target || !(target instanceof HTMLElement)) {
161
- throw new Error(`Dropdown: Target "${targetId}" not found`);
162
- }
163
- container = target;
164
- } else {
165
- container = getOrCreateContainer(this._id);
166
- }
167
- this.container = container;
98
+ const container = this._setupContainer(targetId);
168
99
 
169
- // === 2. PREPARE: Destructure state and check sync flags ===
170
- const { label, items, style, class: className } = this.state;
171
- const hasItemsSync = this._syncBindings.some(b => b.property === 'items');
100
+ const { trigger, items, position, style, class: className } = this.state;
172
101
 
173
- // === 3. BUILD: Create DOM elements ===
174
102
  const wrapper = document.createElement('div');
175
103
  wrapper.className = 'jux-dropdown';
176
104
  wrapper.id = this._id;
177
105
  if (className) wrapper.className += ` ${className}`;
178
106
  if (style) wrapper.setAttribute('style', style);
179
107
 
180
- const button = document.createElement('button');
181
- button.className = 'jux-dropdown-button';
182
- button.textContent = label;
108
+ // Trigger button
109
+ const triggerBtn = document.createElement('button');
110
+ triggerBtn.className = 'jux-dropdown-trigger';
111
+ triggerBtn.textContent = trigger;
112
+ triggerBtn.type = 'button';
183
113
 
114
+ // Dropdown menu
184
115
  const menu = document.createElement('div');
185
- menu.className = 'jux-dropdown-menu';
116
+ menu.className = `jux-dropdown-menu jux-dropdown-${position}`;
186
117
  menu.style.display = 'none';
118
+ this._menu = menu;
187
119
 
188
120
  items.forEach(item => {
189
121
  if (item.divider) {
@@ -191,138 +123,161 @@ export class Dropdown {
191
123
  divider.className = 'jux-dropdown-divider';
192
124
  menu.appendChild(divider);
193
125
  } else {
194
- const menuItem = document.createElement('a');
126
+ const menuItem = document.createElement('button');
195
127
  menuItem.className = 'jux-dropdown-item';
196
-
197
- if (item.href) {
198
- menuItem.href = item.href;
199
- }
128
+ menuItem.type = 'button';
200
129
 
201
130
  if (item.icon) {
202
131
  const icon = document.createElement('span');
203
132
  icon.className = 'jux-dropdown-icon';
204
- icon.appendChild(renderIcon(item.icon));
133
+ icon.textContent = item.icon;
205
134
  menuItem.appendChild(icon);
206
135
  }
207
136
 
208
- const text = document.createElement('span');
209
- text.textContent = item.label;
210
- menuItem.appendChild(text);
211
-
212
- if (item.click) {
213
- menuItem.addEventListener('click', (e) => {
214
- e.preventDefault();
215
- item.click!();
216
- menu.style.display = 'none';
217
- });
137
+ if (item.label) {
138
+ const label = document.createElement('span');
139
+ label.textContent = item.label;
140
+ menuItem.appendChild(label);
218
141
  }
219
142
 
143
+ menuItem.addEventListener('click', () => {
144
+ // 🎯 Fire the select callback event
145
+ this._triggerCallback('select', item);
146
+
147
+ // Then fire item-specific click handler
148
+ if (item.click) {
149
+ item.click();
150
+ }
151
+
152
+ this.close();
153
+ });
154
+
220
155
  menu.appendChild(menuItem);
221
156
  }
222
157
  });
223
158
 
224
- wrapper.appendChild(button);
225
- wrapper.appendChild(menu);
226
-
227
- // Toggle functionality
228
- button.addEventListener('click', () => {
229
- menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
159
+ // Toggle on trigger click
160
+ triggerBtn.addEventListener('click', (e) => {
161
+ e.stopPropagation();
162
+ this.toggle();
230
163
  });
231
164
 
232
165
  // Close on outside click
233
166
  document.addEventListener('click', (e) => {
234
- if (!wrapper.contains(e.target as Node)) {
235
- menu.style.display = 'none';
167
+ if (this._dropdown && !this._dropdown.contains(e.target as Node)) {
168
+ this.close();
236
169
  }
237
170
  });
238
171
 
239
- // === 4. WIRE: Attach event listeners and sync bindings ===
240
-
241
- // Wire custom bindings from .bind() calls
242
- this._bindings.forEach(({ event, handler }) => {
243
- wrapper.addEventListener(event, handler as EventListener);
244
- });
245
-
246
- // Wire sync bindings from .sync() calls
247
- this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
248
- if (property === 'items') {
249
- const transformToComponent = toComponent || ((v: any) => v);
250
-
251
- stateObj.subscribe((val: any) => {
252
- const transformed = transformToComponent(val);
253
- this.state.items = transformed;
254
-
255
- // Re-render menu items
256
- menu.innerHTML = '';
257
- transformed.forEach((item: any) => {
258
- if (item.divider) {
259
- const divider = document.createElement('div');
260
- divider.className = 'jux-dropdown-divider';
261
- menu.appendChild(divider);
262
- } else {
263
- const menuItem = document.createElement('a');
264
- menuItem.className = 'jux-dropdown-item';
265
-
266
- if (item.href) menuItem.href = item.href;
267
-
268
- if (item.icon) {
269
- const icon = document.createElement('span');
270
- icon.className = 'jux-dropdown-icon';
271
- icon.appendChild(renderIcon(item.icon));
272
- menuItem.appendChild(icon);
273
- }
274
-
275
- const text = document.createElement('span');
276
- text.textContent = item.label;
277
- menuItem.appendChild(text);
278
-
279
- if (item.click) {
280
- menuItem.addEventListener('click', (e) => {
281
- e.preventDefault();
282
- item.click!();
283
- menu.style.display = 'none';
284
- });
285
- }
286
-
287
- menu.appendChild(menuItem);
288
- }
289
- });
290
-
291
- requestAnimationFrame(() => {
292
- if ((window as any).lucide) {
293
- (window as any).lucide.createIcons();
294
- }
295
- });
296
- });
297
- }
298
- else if (property === 'label') {
299
- const transformToComponent = toComponent || ((v: any) => String(v));
172
+ wrapper.appendChild(triggerBtn);
173
+ wrapper.appendChild(menu);
300
174
 
301
- stateObj.subscribe((val: any) => {
302
- const transformed = transformToComponent(val);
303
- button.textContent = transformed;
304
- this.state.label = transformed;
305
- });
306
- }
307
- });
175
+ this._wireStandardEvents(wrapper);
308
176
 
309
- // === 5. RENDER: Append to DOM and finalize ===
310
177
  container.appendChild(wrapper);
311
-
312
- requestAnimationFrame(() => {
313
- if ((window as any).lucide) {
314
- (window as any).lucide.createIcons();
315
- }
316
- });
178
+ this._dropdown = wrapper;
179
+ this._injectDropdownStyles();
317
180
 
318
181
  return this;
319
182
  }
320
183
 
321
- renderTo(juxComponent: any): this {
322
- if (!juxComponent?._id) {
323
- throw new Error('Dropdown.renderTo: Invalid component');
324
- }
325
- return this.render(`#${juxComponent._id}`);
184
+ private _injectDropdownStyles(): void {
185
+ const styleId = 'jux-dropdown-styles';
186
+ if (document.getElementById(styleId)) return;
187
+
188
+ const style = document.createElement('style');
189
+ style.id = styleId;
190
+ style.textContent = `
191
+ .jux-dropdown {
192
+ position: relative;
193
+ display: inline-block;
194
+ }
195
+
196
+ .jux-dropdown-trigger {
197
+ padding: 8px 16px;
198
+ background: #3b82f6;
199
+ color: white;
200
+ border: none;
201
+ border-radius: 6px;
202
+ font-size: 14px;
203
+ cursor: pointer;
204
+ transition: background 0.2s;
205
+ }
206
+
207
+ .jux-dropdown-trigger:hover {
208
+ background: #2563eb;
209
+ }
210
+
211
+ .jux-dropdown-menu {
212
+ position: absolute;
213
+ background: white;
214
+ border: 1px solid #e5e7eb;
215
+ border-radius: 6px;
216
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
217
+ min-width: 200px;
218
+ z-index: 1000;
219
+ margin-top: 4px;
220
+ }
221
+
222
+ .jux-dropdown-bottom-left {
223
+ left: 0;
224
+ top: 100%;
225
+ }
226
+
227
+ .jux-dropdown-bottom-right {
228
+ right: 0;
229
+ top: 100%;
230
+ }
231
+
232
+ .jux-dropdown-top-left {
233
+ left: 0;
234
+ bottom: 100%;
235
+ margin-bottom: 4px;
236
+ }
237
+
238
+ .jux-dropdown-top-right {
239
+ right: 0;
240
+ bottom: 100%;
241
+ margin-bottom: 4px;
242
+ }
243
+
244
+ .jux-dropdown-item {
245
+ display: flex;
246
+ align-items: center;
247
+ gap: 8px;
248
+ width: 100%;
249
+ padding: 10px 16px;
250
+ border: none;
251
+ background: none;
252
+ text-align: left;
253
+ cursor: pointer;
254
+ transition: background 0.2s;
255
+ font-size: 14px;
256
+ }
257
+
258
+ .jux-dropdown-item:hover {
259
+ background: #f3f4f6;
260
+ }
261
+
262
+ .jux-dropdown-item:first-child {
263
+ border-radius: 6px 6px 0 0;
264
+ }
265
+
266
+ .jux-dropdown-item:last-child {
267
+ border-radius: 0 0 6px 6px;
268
+ }
269
+
270
+ .jux-dropdown-divider {
271
+ height: 1px;
272
+ background: #e5e7eb;
273
+ margin: 4px 0;
274
+ }
275
+
276
+ .jux-dropdown-icon {
277
+ font-size: 16px;
278
+ }
279
+ `;
280
+ document.head.appendChild(style);
326
281
  }
327
282
  }
328
283