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,111 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+
3
+ /**
4
+ * Paragraph options
5
+ */
6
+ export interface ParagraphOptions {
7
+ text?: string;
8
+ class?: string;
9
+ style?: string;
10
+ }
11
+
12
+ /**
13
+ * Paragraph state
14
+ */
15
+ type ParagraphState = {
16
+ text: string;
17
+ class: string;
18
+ style: string;
19
+ };
20
+
21
+ /**
22
+ * Paragraph component - semantic paragraph element
23
+ *
24
+ * Usage:
25
+ * jux.paragraph('intro', { text: 'Welcome to JUX' }).render('#app');
26
+ * jux.paragraph('description').text('A simple framework').render('#app');
27
+ */
28
+ export class Paragraph {
29
+ state: ParagraphState;
30
+ container: HTMLElement | null = null;
31
+ _id: string;
32
+ id: string;
33
+
34
+ constructor(id: string, options: ParagraphOptions = {}) {
35
+ this._id = id;
36
+ this.id = id;
37
+
38
+ this.state = {
39
+ text: options.text ?? '',
40
+ class: options.class ?? '',
41
+ style: options.style ?? ''
42
+ };
43
+ }
44
+
45
+ /* -------------------------
46
+ * Fluent API
47
+ * ------------------------- */
48
+
49
+ text(value: string): this {
50
+ this.state.text = value;
51
+ return this;
52
+ }
53
+
54
+ class(value: string): this {
55
+ this.state.class = value;
56
+ return this;
57
+ }
58
+
59
+ style(value: string): this {
60
+ this.state.style = value;
61
+ return this;
62
+ }
63
+
64
+ /* -------------------------
65
+ * Render
66
+ * ------------------------- */
67
+
68
+ render(targetId?: string | HTMLElement): this {
69
+ let container: HTMLElement;
70
+
71
+ if (targetId) {
72
+ if (targetId instanceof HTMLElement) {
73
+ container = targetId;
74
+ } else {
75
+ const target = document.querySelector(targetId);
76
+ if (!target || !(target instanceof HTMLElement)) {
77
+ throw new Error(`Paragraph: Target element "${targetId}" not found`);
78
+ }
79
+ container = target;
80
+ }
81
+ } else {
82
+ container = getOrCreateContainer(this._id);
83
+ }
84
+
85
+ this.container = container;
86
+ const { text, class: className, style } = this.state;
87
+
88
+ const p = document.createElement('p');
89
+ p.id = this._id;
90
+ p.textContent = text;
91
+
92
+ if (className) {
93
+ p.className = className;
94
+ }
95
+
96
+ if (style) {
97
+ p.setAttribute('style', style);
98
+ }
99
+
100
+ container.appendChild(p);
101
+
102
+ return this;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Factory helper
108
+ */
109
+ export function paragraph(id: string, options: ParagraphOptions = {}): Paragraph {
110
+ return new Paragraph(id, options);
111
+ }
@@ -0,0 +1,276 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
3
+
4
+ /**
5
+ * Progress component options
6
+ */
7
+ export interface ProgressOptions {
8
+ value?: number;
9
+ max?: number;
10
+ label?: string;
11
+ showPercentage?: boolean;
12
+ variant?: 'default' | 'success' | 'warning' | 'error' | 'info';
13
+ size?: 'sm' | 'md' | 'lg';
14
+ striped?: boolean;
15
+ animated?: boolean;
16
+ style?: string;
17
+ class?: string;
18
+ }
19
+
20
+ /**
21
+ * Progress component state
22
+ */
23
+ type ProgressState = {
24
+ value: number;
25
+ max: number;
26
+ label: string;
27
+ showPercentage: boolean;
28
+ variant: string;
29
+ size: string;
30
+ striped: boolean;
31
+ animated: boolean;
32
+ style: string;
33
+ class: string;
34
+ };
35
+
36
+ /**
37
+ * Progress component - Progress bar for loading/completion
38
+ *
39
+ * Usage:
40
+ * jux.progress('upload', {
41
+ * value: 45,
42
+ * max: 100,
43
+ * label: 'Uploading...',
44
+ * showPercentage: true,
45
+ * animated: true
46
+ * }).render('#app');
47
+ *
48
+ * // Update progress
49
+ * const prog = jux.progress('upload').render('#app');
50
+ * prog.value(75);
51
+ */
52
+ export class Progress {
53
+ state: ProgressState;
54
+ container: HTMLElement | null = null;
55
+ _id: string;
56
+ id: string;
57
+ private _boundState?: State<number>;
58
+
59
+ constructor(id: string, options: ProgressOptions = {}) {
60
+ this._id = id;
61
+ this.id = id;
62
+
63
+ this.state = {
64
+ value: options.value ?? 0,
65
+ max: options.max ?? 100,
66
+ label: options.label ?? '',
67
+ showPercentage: options.showPercentage ?? false,
68
+ variant: options.variant ?? 'default',
69
+ size: options.size ?? 'md',
70
+ striped: options.striped ?? false,
71
+ animated: options.animated ?? false,
72
+ style: options.style ?? '',
73
+ class: options.class ?? ''
74
+ };
75
+ }
76
+
77
+ /* -------------------------
78
+ * Fluent API
79
+ * ------------------------- */
80
+
81
+ value(value: number): this {
82
+ this.state.value = Math.max(0, Math.min(value, this.state.max));
83
+ this._updateElement();
84
+ return this;
85
+ }
86
+
87
+ max(value: number): this {
88
+ this.state.max = value;
89
+ return this;
90
+ }
91
+
92
+ label(value: string): this {
93
+ this.state.label = value;
94
+ this._updateElement();
95
+ return this;
96
+ }
97
+
98
+ showPercentage(value: boolean): this {
99
+ this.state.showPercentage = value;
100
+ this._updateElement();
101
+ return this;
102
+ }
103
+
104
+ variant(value: 'default' | 'success' | 'warning' | 'error' | 'info'): this {
105
+ this.state.variant = value;
106
+ this._updateElement();
107
+ return this;
108
+ }
109
+
110
+ size(value: 'sm' | 'md' | 'lg'): this {
111
+ this.state.size = value;
112
+ return this;
113
+ }
114
+
115
+ striped(value: boolean): this {
116
+ this.state.striped = value;
117
+ this._updateElement();
118
+ return this;
119
+ }
120
+
121
+ animated(value: boolean): this {
122
+ this.state.animated = value;
123
+ this._updateElement();
124
+ return this;
125
+ }
126
+
127
+ style(value: string): this {
128
+ this.state.style = value;
129
+ return this;
130
+ }
131
+
132
+ class(value: string): this {
133
+ this.state.class = value;
134
+ return this;
135
+ }
136
+
137
+ /**
138
+ * Two-way binding to state
139
+ */
140
+ bind(stateObj: State<number>): this {
141
+ this._boundState = stateObj;
142
+
143
+ stateObj.subscribe((val) => {
144
+ this.value(val);
145
+ });
146
+
147
+ return this;
148
+ }
149
+
150
+ /* -------------------------
151
+ * Helpers
152
+ * ------------------------- */
153
+
154
+ private _updateElement(): void {
155
+ const wrapper = document.getElementById(this._id);
156
+ const bar = document.getElementById(`${this._id}-bar`);
157
+ const labelEl = document.getElementById(`${this._id}-label`);
158
+
159
+ // If element has a value attribute set externally (e.g., by bindValue), sync state
160
+ if (wrapper && wrapper.hasAttribute('data-value')) {
161
+ const externalValue = parseFloat(wrapper.getAttribute('data-value') || '0');
162
+ this.state.value = externalValue;
163
+ }
164
+
165
+ if (bar) {
166
+ const percentage = (this.state.value / this.state.max) * 100;
167
+ bar.style.width = `${percentage}%`;
168
+ bar.setAttribute('aria-valuenow', this.state.value.toString());
169
+ bar.className = `jux-progress-bar jux-progress-bar-${this.state.variant}`;
170
+
171
+ if (this.state.striped) {
172
+ bar.classList.add('jux-progress-bar-striped');
173
+ }
174
+ if (this.state.animated) {
175
+ bar.classList.add('jux-progress-bar-animated');
176
+ }
177
+ }
178
+
179
+ if (labelEl) {
180
+ const percentage = Math.round((this.state.value / this.state.max) * 100);
181
+ const text = this.state.showPercentage
182
+ ? `${this.state.label} ${percentage}%`.trim()
183
+ : this.state.label;
184
+ labelEl.textContent = text;
185
+ }
186
+ }
187
+
188
+ getPercentage(): number {
189
+ return Math.round((this.state.value / this.state.max) * 100);
190
+ }
191
+
192
+ /* -------------------------
193
+ * Render
194
+ * ------------------------- */
195
+
196
+ render(targetId?: string): this {
197
+ let container: HTMLElement;
198
+
199
+ if (targetId) {
200
+ const target = document.querySelector(targetId);
201
+ if (!target || !(target instanceof HTMLElement)) {
202
+ throw new Error(`Progress: Target element "${targetId}" not found`);
203
+ }
204
+ container = target;
205
+ } else {
206
+ container = getOrCreateContainer(this._id);
207
+ }
208
+
209
+ this.container = container;
210
+ const { value, max, label, showPercentage, variant, size, striped, animated, style, class: className } = this.state;
211
+
212
+ const wrapper = document.createElement('div');
213
+ wrapper.className = `jux-progress jux-progress-${size}`;
214
+ wrapper.id = this._id;
215
+
216
+ if (className) {
217
+ wrapper.className += ` ${className}`;
218
+ }
219
+
220
+ if (style) {
221
+ wrapper.setAttribute('style', style);
222
+ }
223
+
224
+ // Label
225
+ if (label || showPercentage) {
226
+ const labelEl = document.createElement('div');
227
+ labelEl.className = 'jux-progress-label';
228
+ labelEl.id = `${this._id}-label`;
229
+ const percentage = Math.round((value / max) * 100);
230
+ const text = showPercentage
231
+ ? `${label} ${percentage}%`.trim()
232
+ : label;
233
+ labelEl.textContent = text;
234
+ wrapper.appendChild(labelEl);
235
+ }
236
+
237
+ // Progress track
238
+ const track = document.createElement('div');
239
+ track.className = 'jux-progress-track';
240
+
241
+ // Progress bar
242
+ const bar = document.createElement('div');
243
+ bar.className = `jux-progress-bar jux-progress-bar-${variant}`;
244
+ bar.id = `${this._id}-bar`;
245
+ bar.setAttribute('role', 'progressbar');
246
+ bar.setAttribute('aria-valuenow', value.toString());
247
+ bar.setAttribute('aria-valuemin', '0');
248
+ bar.setAttribute('aria-valuemax', max.toString());
249
+
250
+ const percentage = (value / max) * 100;
251
+ bar.style.width = `${percentage}%`;
252
+
253
+ if (striped) {
254
+ bar.classList.add('jux-progress-bar-striped');
255
+ }
256
+ if (animated) {
257
+ bar.classList.add('jux-progress-bar-animated');
258
+ }
259
+
260
+ track.appendChild(bar);
261
+ wrapper.appendChild(track);
262
+ container.appendChild(wrapper);
263
+ return this;
264
+ }
265
+
266
+ renderTo(juxComponent: any): this {
267
+ if (!juxComponent?._id) {
268
+ throw new Error('Progress.renderTo: Invalid component');
269
+ }
270
+ return this.render(`#${juxComponent._id}`);
271
+ }
272
+ }
273
+
274
+ export function progress(id: string, options: ProgressOptions = {}): Progress {
275
+ return new Progress(id, options);
276
+ }
@@ -0,0 +1,236 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
3
+
4
+ /**
5
+ * Radio option
6
+ */
7
+ export interface RadioOption {
8
+ label: string;
9
+ value: string;
10
+ disabled?: boolean;
11
+ }
12
+
13
+ /**
14
+ * Radio component options
15
+ */
16
+ export interface RadioOptions {
17
+ options?: RadioOption[];
18
+ value?: string;
19
+ name?: string;
20
+ onChange?: (value: string) => void;
21
+ orientation?: 'vertical' | 'horizontal';
22
+ style?: string;
23
+ class?: string;
24
+ }
25
+
26
+ /**
27
+ * Radio component state
28
+ */
29
+ type RadioState = {
30
+ options: RadioOption[];
31
+ value: string;
32
+ name: string;
33
+ orientation: string;
34
+ style: string;
35
+ class: string;
36
+ };
37
+
38
+ /**
39
+ * Radio component - Radio button group
40
+ *
41
+ * Usage:
42
+ * jux.radio('size', {
43
+ * options: [
44
+ * { label: 'Small', value: 's' },
45
+ * { label: 'Medium', value: 'm' },
46
+ * { label: 'Large', value: 'l' }
47
+ * ],
48
+ * value: 'm',
49
+ * onChange: (val) => console.log(val)
50
+ * }).render('#form');
51
+ *
52
+ * // Two-way binding
53
+ * const sizeState = state('m');
54
+ * jux.radio('size').bind(sizeState).render('#form');
55
+ */
56
+ export class Radio {
57
+ state: RadioState;
58
+ container: HTMLElement | null = null;
59
+ _id: string;
60
+ id: string;
61
+ private _onChange?: (value: string) => void;
62
+ private _boundState?: State<string>;
63
+
64
+ constructor(id: string, options: RadioOptions = {}) {
65
+ this._id = id;
66
+ this.id = id;
67
+ this._onChange = options.onChange;
68
+
69
+ this.state = {
70
+ options: options.options ?? [],
71
+ value: options.value ?? '',
72
+ name: options.name ?? id,
73
+ orientation: options.orientation ?? 'vertical',
74
+ style: options.style ?? '',
75
+ class: options.class ?? ''
76
+ };
77
+ }
78
+
79
+ /* -------------------------
80
+ * Fluent API
81
+ * ------------------------- */
82
+
83
+ options(value: RadioOption[]): this {
84
+ this.state.options = value;
85
+ return this;
86
+ }
87
+
88
+ addOption(option: RadioOption): this {
89
+ this.state.options = [...this.state.options, option];
90
+ return this;
91
+ }
92
+
93
+ value(value: string): this {
94
+ this.state.value = value;
95
+ this._updateElement();
96
+ return this;
97
+ }
98
+
99
+ name(value: string): this {
100
+ this.state.name = value;
101
+ return this;
102
+ }
103
+
104
+ orientation(value: 'vertical' | 'horizontal'): this {
105
+ this.state.orientation = value;
106
+ return this;
107
+ }
108
+
109
+ style(value: string): this {
110
+ this.state.style = value;
111
+ return this;
112
+ }
113
+
114
+ class(value: string): this {
115
+ this.state.class = value;
116
+ return this;
117
+ }
118
+
119
+ onChange(handler: (value: string) => void): this {
120
+ this._onChange = handler;
121
+ return this;
122
+ }
123
+
124
+ /**
125
+ * Two-way binding to state
126
+ */
127
+ bind(stateObj: State<string>): this {
128
+ this._boundState = stateObj;
129
+
130
+ // Update radio when state changes
131
+ stateObj.subscribe((val) => {
132
+ this.state.value = val;
133
+ this._updateElement();
134
+ });
135
+
136
+ // Update state when radio changes
137
+ this.onChange((value) => stateObj.set(value));
138
+
139
+ return this;
140
+ }
141
+
142
+ /* -------------------------
143
+ * Helpers
144
+ * ------------------------- */
145
+
146
+ private _updateElement(): void {
147
+ const inputs = document.querySelectorAll(`input[name="${this.state.name}"]`);
148
+ inputs.forEach((input) => {
149
+ const radioInput = input as HTMLInputElement;
150
+ radioInput.checked = radioInput.value === this.state.value;
151
+ });
152
+ }
153
+
154
+ getValue(): string {
155
+ return this.state.value;
156
+ }
157
+
158
+ /* -------------------------
159
+ * Render
160
+ * ------------------------- */
161
+
162
+ render(targetId?: string): this {
163
+ let container: HTMLElement;
164
+
165
+ if (targetId) {
166
+ const target = document.querySelector(targetId);
167
+ if (!target || !(target instanceof HTMLElement)) {
168
+ throw new Error(`Radio: Target element "${targetId}" not found`);
169
+ }
170
+ container = target;
171
+ } else {
172
+ container = getOrCreateContainer(this._id);
173
+ }
174
+
175
+ this.container = container;
176
+ const { options, value, name, orientation, style, class: className } = this.state;
177
+
178
+ const wrapper = document.createElement('div');
179
+ wrapper.className = `jux-radio jux-radio-${orientation}`;
180
+ wrapper.id = this._id;
181
+ wrapper.setAttribute('role', 'radiogroup');
182
+
183
+ if (className) {
184
+ wrapper.className += ` ${className}`;
185
+ }
186
+
187
+ if (style) {
188
+ wrapper.setAttribute('style', style);
189
+ }
190
+
191
+ options.forEach((opt, index) => {
192
+ const label = document.createElement('label');
193
+ label.className = 'jux-radio-label';
194
+
195
+ const input = document.createElement('input');
196
+ input.type = 'radio';
197
+ input.className = 'jux-radio-input';
198
+ input.id = `${this._id}-${index}`;
199
+ input.name = name;
200
+ input.value = opt.value;
201
+ input.checked = opt.value === value;
202
+ input.disabled = opt.disabled ?? false;
203
+
204
+ input.addEventListener('change', (e) => {
205
+ const target = e.target as HTMLInputElement;
206
+ this.state.value = target.value;
207
+ if (this._onChange) {
208
+ this._onChange(target.value);
209
+ }
210
+ });
211
+
212
+ label.appendChild(input);
213
+
214
+ const span = document.createElement('span');
215
+ span.className = 'jux-radio-text';
216
+ span.textContent = opt.label;
217
+ label.appendChild(span);
218
+
219
+ wrapper.appendChild(label);
220
+ });
221
+
222
+ container.appendChild(wrapper);
223
+ return this;
224
+ }
225
+
226
+ renderTo(juxComponent: any): this {
227
+ if (!juxComponent?._id) {
228
+ throw new Error('Radio.renderTo: Invalid component');
229
+ }
230
+ return this.render(`#${juxComponent._id}`);
231
+ }
232
+ }
233
+
234
+ export function radio(id: string, options: RadioOptions = {}): Radio {
235
+ return new Radio(id, options);
236
+ }