juxscript 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +37 -92
  2. package/bin/cli.js +57 -56
  3. package/lib/components/alert.ts +240 -0
  4. package/lib/components/app.ts +216 -82
  5. package/lib/components/badge.ts +164 -0
  6. package/lib/components/button.ts +188 -53
  7. package/lib/components/card.ts +75 -61
  8. package/lib/components/chart.ts +17 -15
  9. package/lib/components/checkbox.ts +228 -0
  10. package/lib/components/code.ts +66 -152
  11. package/lib/components/container.ts +104 -208
  12. package/lib/components/data.ts +1 -3
  13. package/lib/components/datepicker.ts +226 -0
  14. package/lib/components/dialog.ts +258 -0
  15. package/lib/components/docs-data.json +1697 -388
  16. package/lib/components/dropdown.ts +244 -0
  17. package/lib/components/element.ts +271 -0
  18. package/lib/components/fileupload.ts +319 -0
  19. package/lib/components/footer.ts +37 -18
  20. package/lib/components/header.ts +53 -33
  21. package/lib/components/heading.ts +119 -0
  22. package/lib/components/helpers.ts +34 -0
  23. package/lib/components/hero.ts +57 -31
  24. package/lib/components/include.ts +292 -0
  25. package/lib/components/input.ts +166 -78
  26. package/lib/components/layout.ts +144 -18
  27. package/lib/components/list.ts +83 -74
  28. package/lib/components/loading.ts +263 -0
  29. package/lib/components/main.ts +43 -17
  30. package/lib/components/menu.ts +108 -24
  31. package/lib/components/modal.ts +50 -21
  32. package/lib/components/nav.ts +60 -18
  33. package/lib/components/paragraph.ts +111 -0
  34. package/lib/components/progress.ts +276 -0
  35. package/lib/components/radio.ts +236 -0
  36. package/lib/components/req.ts +300 -0
  37. package/lib/components/script.ts +33 -74
  38. package/lib/components/select.ts +247 -0
  39. package/lib/components/sidebar.ts +86 -36
  40. package/lib/components/style.ts +47 -70
  41. package/lib/components/switch.ts +261 -0
  42. package/lib/components/table.ts +47 -24
  43. package/lib/components/tabs.ts +105 -63
  44. package/lib/components/theme-toggle.ts +361 -0
  45. package/lib/components/token-calculator.ts +380 -0
  46. package/lib/components/tooltip.ts +244 -0
  47. package/lib/components/view.ts +36 -20
  48. package/lib/components/write.ts +284 -0
  49. package/lib/globals.d.ts +21 -0
  50. package/lib/jux.ts +172 -68
  51. package/lib/presets/notion.css +521 -0
  52. package/lib/presets/notion.jux +27 -0
  53. package/lib/reactivity/state.ts +364 -0
  54. package/machinery/compiler.js +126 -38
  55. package/machinery/generators/html.js +2 -3
  56. package/machinery/server.js +2 -2
  57. package/package.json +29 -3
  58. package/lib/components/import.ts +0 -430
  59. package/lib/components/node.ts +0 -200
  60. package/lib/components/reactivity.js +0 -104
  61. package/lib/components/theme.ts +0 -97
  62. package/lib/layouts/notion.css +0 -258
  63. package/lib/styles/base-theme.css +0 -186
  64. package/lib/styles/dark-theme.css +0 -144
  65. package/lib/styles/light-theme.css +0 -144
  66. package/lib/styles/tokens/dark.css +0 -86
  67. package/lib/styles/tokens/light.css +0 -86
  68. package/lib/templates/index.juxt +0 -33
  69. package/lib/themes/dark.css +0 -86
  70. package/lib/themes/light.css +0 -86
  71. /package/lib/{styles → presets}/global.css +0 -0
@@ -0,0 +1,244 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+
3
+ /**
4
+ * Dropdown menu item
5
+ */
6
+ export interface DropdownItem {
7
+ label: string;
8
+ value?: string;
9
+ icon?: string;
10
+ click?: () => void;
11
+ disabled?: boolean;
12
+ divider?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Dropdown component options
17
+ */
18
+ export interface DropdownOptions {
19
+ items?: DropdownItem[];
20
+ trigger?: string;
21
+ position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
22
+ style?: string;
23
+ class?: string;
24
+ }
25
+
26
+ /**
27
+ * Dropdown component state
28
+ */
29
+ type DropdownState = {
30
+ items: DropdownItem[];
31
+ trigger: string;
32
+ position: string;
33
+ style: string;
34
+ class: string;
35
+ isOpen: boolean;
36
+ };
37
+
38
+ /**
39
+ * Dropdown Menu component - Context menus, action menus
40
+ *
41
+ * Usage:
42
+ * jux.dropdown('actions', {
43
+ * trigger: 'Actions ▾',
44
+ * position: 'bottom-right',
45
+ * items: [
46
+ * { label: 'Edit', icon: '✏️', click: () => console.log('Edit') },
47
+ * { label: 'Delete', icon: '🗑️', click: () => console.log('Delete') },
48
+ * { divider: true },
49
+ * { label: 'Archive', click: () => console.log('Archive') }
50
+ * ]
51
+ * }).render('#toolbar');
52
+ *
53
+ * // Control programmatically
54
+ * const dd = jux.dropdown('my-dropdown').render();
55
+ * dd.open();
56
+ * dd.close();
57
+ */
58
+ export class Dropdown {
59
+ state: DropdownState;
60
+ container: HTMLElement | null = null;
61
+ _id: string;
62
+ id: string;
63
+
64
+ constructor(id: string, options: DropdownOptions = {}) {
65
+ this._id = id;
66
+ this.id = id;
67
+
68
+ this.state = {
69
+ items: options.items ?? [],
70
+ trigger: options.trigger ?? 'Menu',
71
+ position: options.position ?? 'bottom-left',
72
+ style: options.style ?? '',
73
+ class: options.class ?? '',
74
+ isOpen: false
75
+ };
76
+ }
77
+
78
+ /* -------------------------
79
+ * Fluent API
80
+ * ------------------------- */
81
+
82
+ items(value: DropdownItem[]): this {
83
+ this.state.items = value;
84
+ return this;
85
+ }
86
+
87
+ addItem(item: DropdownItem): this {
88
+ this.state.items = [...this.state.items, item];
89
+ return this;
90
+ }
91
+
92
+ trigger(value: string): this {
93
+ this.state.trigger = value;
94
+ return this;
95
+ }
96
+
97
+ position(value: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'): this {
98
+ this.state.position = value;
99
+ return this;
100
+ }
101
+
102
+ style(value: string): this {
103
+ this.state.style = value;
104
+ return this;
105
+ }
106
+
107
+ class(value: string): this {
108
+ this.state.class = value;
109
+ return this;
110
+ }
111
+
112
+ /* -------------------------
113
+ * Methods
114
+ * ------------------------- */
115
+
116
+ open(): void {
117
+ this.state.isOpen = true;
118
+ const menu = document.getElementById(`${this._id}-menu`);
119
+ if (menu) {
120
+ menu.classList.add('jux-dropdown-menu-open');
121
+ }
122
+ }
123
+
124
+ close(): void {
125
+ this.state.isOpen = false;
126
+ const menu = document.getElementById(`${this._id}-menu`);
127
+ if (menu) {
128
+ menu.classList.remove('jux-dropdown-menu-open');
129
+ }
130
+ }
131
+
132
+ toggle(): void {
133
+ if (this.state.isOpen) {
134
+ this.close();
135
+ } else {
136
+ this.open();
137
+ }
138
+ }
139
+
140
+ /* -------------------------
141
+ * Render
142
+ * ------------------------- */
143
+
144
+ render(targetId?: string): this {
145
+ let container: HTMLElement;
146
+
147
+ if (targetId) {
148
+ const target = document.querySelector(targetId);
149
+ if (!target || !(target instanceof HTMLElement)) {
150
+ throw new Error(`Dropdown: Target element "${targetId}" not found`);
151
+ }
152
+ container = target;
153
+ } else {
154
+ container = getOrCreateContainer(this._id);
155
+ }
156
+
157
+ this.container = container;
158
+ const { items, trigger, position, style, class: className } = this.state;
159
+
160
+ // Wrapper
161
+ const wrapper = document.createElement('div');
162
+ wrapper.className = 'jux-dropdown';
163
+ wrapper.id = this._id;
164
+
165
+ if (className) {
166
+ wrapper.className += ` ${className}`;
167
+ }
168
+
169
+ if (style) {
170
+ wrapper.setAttribute('style', style);
171
+ }
172
+
173
+ // Trigger button
174
+ const triggerBtn = document.createElement('button');
175
+ triggerBtn.className = 'jux-dropdown-trigger';
176
+ triggerBtn.textContent = trigger;
177
+ triggerBtn.addEventListener('click', (e) => {
178
+ e.stopPropagation();
179
+ this.toggle();
180
+ });
181
+
182
+ // Menu
183
+ const menu = document.createElement('div');
184
+ menu.className = `jux-dropdown-menu jux-dropdown-menu-${position}`;
185
+ menu.id = `${this._id}-menu`;
186
+
187
+ items.forEach(item => {
188
+ if (item.divider) {
189
+ const divider = document.createElement('div');
190
+ divider.className = 'jux-dropdown-divider';
191
+ menu.appendChild(divider);
192
+ } else {
193
+ const menuItem = document.createElement('button');
194
+ menuItem.className = 'jux-dropdown-item';
195
+ menuItem.disabled = item.disabled ?? false;
196
+
197
+ if (item.icon) {
198
+ const icon = document.createElement('span');
199
+ icon.className = 'jux-dropdown-item-icon';
200
+ icon.textContent = item.icon;
201
+ menuItem.appendChild(icon);
202
+ }
203
+
204
+ const label = document.createElement('span');
205
+ label.className = 'jux-dropdown-item-label';
206
+ label.textContent = item.label;
207
+ menuItem.appendChild(label);
208
+
209
+ menuItem.addEventListener('click', () => {
210
+ if (item.click) {
211
+ item.click();
212
+ }
213
+ this.close();
214
+ });
215
+
216
+ menu.appendChild(menuItem);
217
+ }
218
+ });
219
+
220
+ wrapper.appendChild(triggerBtn);
221
+ wrapper.appendChild(menu);
222
+ container.appendChild(wrapper);
223
+
224
+ // Close on outside click
225
+ document.addEventListener('click', (e) => {
226
+ if (!wrapper.contains(e.target as Node)) {
227
+ this.close();
228
+ }
229
+ });
230
+
231
+ return this;
232
+ }
233
+
234
+ renderTo(juxComponent: any): this {
235
+ if (!juxComponent?._id) {
236
+ throw new Error('Dropdown.renderTo: Invalid component');
237
+ }
238
+ return this.render(`#${juxComponent._id}`);
239
+ }
240
+ }
241
+
242
+ export function dropdown(id: string, options: DropdownOptions = {}): Dropdown {
243
+ return new Dropdown(id, options);
244
+ }
@@ -0,0 +1,271 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+
3
+ /**
4
+ * Element component options
5
+ */
6
+ export interface ElementOptions {
7
+ tagType?: string;
8
+ className?: string;
9
+ text?: string;
10
+ innerHTML?: string;
11
+ attributes?: Record<string, string>;
12
+ styles?: Record<string, string>;
13
+ style?: string;
14
+ class?: string;
15
+ }
16
+
17
+ /**
18
+ * Element component state
19
+ */
20
+ type ElementState = {
21
+ tagType: string;
22
+ className: string;
23
+ text: string;
24
+ innerHTML: string;
25
+ attributes: Record<string, string>;
26
+ styles: Record<string, string>;
27
+ style: string;
28
+ class: string;
29
+ };
30
+
31
+ /**
32
+ * Element component - Create arbitrary HTML elements
33
+ *
34
+ * Usage:
35
+ * const div = jux.element('myDiv', { tagType: 'div', className: 'container' });
36
+ * div.render('#target');
37
+ *
38
+ * const span = jux.element('mySpan')
39
+ * .tagType('span')
40
+ * .className('highlight')
41
+ * .text('Hello World')
42
+ * .render();
43
+ *
44
+ * For reactive content, use State bindings after render:
45
+ * const count = state(0);
46
+ * jux.element('counter').render('#app');
47
+ * count.bindText('counter', (val) => `Count: ${val}`);
48
+ */
49
+ export class Element {
50
+ state: ElementState;
51
+ container: HTMLElement | null = null;
52
+ _id: string;
53
+ id: string;
54
+
55
+ constructor(id: string, options: ElementOptions = {}) {
56
+ this._id = id;
57
+ this.id = id;
58
+
59
+ this.state = {
60
+ tagType: options.tagType ?? 'div',
61
+ className: options.className ?? '',
62
+ text: options.text ?? '',
63
+ innerHTML: options.innerHTML ?? '',
64
+ attributes: options.attributes ?? {},
65
+ styles: options.styles ?? {},
66
+ style: options.style ?? '',
67
+ class: options.class ?? ''
68
+ };
69
+ }
70
+
71
+ /* -------------------------
72
+ * Fluent API
73
+ * ------------------------- */
74
+
75
+ /**
76
+ * Set the HTML tag type
77
+ */
78
+ tagType(value: string): Element {
79
+ this.state.tagType = value;
80
+ return this;
81
+ }
82
+
83
+ /**
84
+ * Set the className
85
+ */
86
+ className(value: string): Element {
87
+ this.state.className = value;
88
+ return this;
89
+ }
90
+
91
+ /**
92
+ * Add a class to existing classes
93
+ */
94
+ addClass(value: string): Element {
95
+ const classes = this.state.className.split(' ').filter(c => c);
96
+ if (!classes.includes(value)) {
97
+ classes.push(value);
98
+ this.state.className = classes.join(' ');
99
+ }
100
+ return this;
101
+ }
102
+
103
+ /**
104
+ * Remove a class from existing classes
105
+ */
106
+ removeClass(value: string): Element {
107
+ const classes = this.state.className.split(' ').filter(c => c && c !== value);
108
+ this.state.className = classes.join(' ');
109
+ return this;
110
+ }
111
+
112
+ /**
113
+ * Set text content (static)
114
+ */
115
+ text(value: string): Element {
116
+ this.state.text = value;
117
+ this.state.innerHTML = '';
118
+ return this;
119
+ }
120
+
121
+ /**
122
+ * Set HTML content (static, clears text)
123
+ */
124
+ innerHTML(value: string): Element {
125
+ this.state.innerHTML = value;
126
+ this.state.text = '';
127
+ return this;
128
+ }
129
+
130
+ /**
131
+ * Set a single attribute
132
+ */
133
+ attr(name: string, value: string): Element {
134
+ this.state.attributes = { ...this.state.attributes, [name]: value };
135
+ return this;
136
+ }
137
+
138
+ /**
139
+ * Set multiple attributes
140
+ */
141
+ attrs(attributes: Record<string, string>): Element {
142
+ this.state.attributes = { ...this.state.attributes, ...attributes };
143
+ return this;
144
+ }
145
+
146
+ /**
147
+ * Set inline styles
148
+ * @param propertyOrValue - CSS property name, style string, or style object
149
+ * @param value - CSS value (when first arg is property name)
150
+ */
151
+ style(propertyOrValue: string | Record<string, string>, value?: string): Element {
152
+ if (typeof propertyOrValue === 'string' && value !== undefined) {
153
+ // Called as .style('color', 'red')
154
+ this.state.styles = { ...this.state.styles, [propertyOrValue]: value };
155
+ } else if (typeof propertyOrValue === 'string') {
156
+ // Called as .style('color: red; background: blue')
157
+ this.state.style = propertyOrValue;
158
+ } else if (typeof propertyOrValue === 'object') {
159
+ // Called as .style({ color: 'red', background: 'blue' })
160
+ this.state.styles = { ...this.state.styles, ...propertyOrValue };
161
+ }
162
+ return this;
163
+ }
164
+
165
+ /**
166
+ * Set multiple styles as object
167
+ */
168
+ styles(styles: Record<string, string>): Element {
169
+ this.state.styles = { ...this.state.styles, ...styles };
170
+ return this;
171
+ }
172
+
173
+ /**
174
+ * Add additional classes (fluent alternative to className)
175
+ */
176
+ class(value: string): Element {
177
+ this.state.class = value;
178
+ return this;
179
+ }
180
+
181
+ /* -------------------------
182
+ * Render
183
+ * ------------------------- */
184
+
185
+ /**
186
+ * Render the element to a target element
187
+ * @param targetId - CSS selector for target element (optional, defaults to body)
188
+ */
189
+ render(targetId?: string): Element {
190
+ let container: HTMLElement;
191
+
192
+ if (targetId) {
193
+ const target = document.querySelector(targetId);
194
+ if (!target || !(target instanceof HTMLElement)) {
195
+ throw new Error(`Element: Target element "${targetId}" not found`);
196
+ }
197
+ container = target;
198
+ } else {
199
+ container = getOrCreateContainer(this._id);
200
+ }
201
+
202
+ this.container = container;
203
+ const { tagType, className, text, innerHTML, attributes, styles, style, class: classValue } = this.state;
204
+
205
+ // Create the element
206
+ const element = document.createElement(tagType);
207
+
208
+ // Set ID to component ID
209
+ element.id = this._id;
210
+
211
+ // Set className
212
+ if (className) {
213
+ element.className = className;
214
+ }
215
+
216
+ if (classValue) {
217
+ element.className = element.className ? `${element.className} ${classValue}` : classValue;
218
+ }
219
+
220
+ // Set content (innerHTML takes precedence over text)
221
+ if (innerHTML) {
222
+ element.innerHTML = innerHTML;
223
+ } else if (text) {
224
+ element.textContent = text;
225
+ }
226
+
227
+ // Set attributes
228
+ Object.entries(attributes).forEach(([name, value]) => {
229
+ element.setAttribute(name, value);
230
+ });
231
+
232
+ // Set styles (object)
233
+ Object.entries(styles).forEach(([property, value]) => {
234
+ element.style.setProperty(property, value);
235
+ });
236
+
237
+ // Set style (string)
238
+ if (style) {
239
+ element.setAttribute('style', style);
240
+ }
241
+
242
+ container.appendChild(element);
243
+ return this;
244
+ }
245
+
246
+ /**
247
+ * Render to another Jux component's container
248
+ *
249
+ * Usage:
250
+ * const container = jux.element('myContainer');
251
+ * const child = jux.element('myChild').renderTo(container);
252
+ */
253
+ renderTo(juxComponent: Element): Element {
254
+ if (!juxComponent || typeof juxComponent !== 'object') {
255
+ throw new Error('Element.renderTo: Invalid component - not an object');
256
+ }
257
+
258
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
259
+ throw new Error('Element.renderTo: Invalid component - missing _id (not a Jux component)');
260
+ }
261
+
262
+ return this.render(`#${juxComponent._id}`);
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Factory helper
268
+ */
269
+ export function element(id: string, options: ElementOptions = {}): Element {
270
+ return new Element(id, options);
271
+ }