juxscript 1.0.3 → 1.0.5

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 (73) 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/barchart.ts +1248 -0
  7. package/lib/components/button.ts +188 -53
  8. package/lib/components/card.ts +75 -61
  9. package/lib/components/chart.ts +17 -15
  10. package/lib/components/checkbox.ts +199 -0
  11. package/lib/components/code.ts +66 -152
  12. package/lib/components/container.ts +104 -208
  13. package/lib/components/data.ts +1 -3
  14. package/lib/components/datepicker.ts +226 -0
  15. package/lib/components/dialog.ts +258 -0
  16. package/lib/components/docs-data.json +1969 -423
  17. package/lib/components/dropdown.ts +244 -0
  18. package/lib/components/element.ts +271 -0
  19. package/lib/components/fileupload.ts +319 -0
  20. package/lib/components/footer.ts +37 -18
  21. package/lib/components/header.ts +53 -33
  22. package/lib/components/heading.ts +119 -0
  23. package/lib/components/helpers.ts +34 -0
  24. package/lib/components/hero.ts +57 -31
  25. package/lib/components/include.ts +292 -0
  26. package/lib/components/input.ts +508 -77
  27. package/lib/components/layout.ts +144 -18
  28. package/lib/components/list.ts +83 -74
  29. package/lib/components/loading.ts +263 -0
  30. package/lib/components/main.ts +43 -17
  31. package/lib/components/menu.ts +108 -24
  32. package/lib/components/modal.ts +50 -21
  33. package/lib/components/nav.ts +60 -18
  34. package/lib/components/paragraph.ts +111 -0
  35. package/lib/components/progress.ts +276 -0
  36. package/lib/components/radio.ts +236 -0
  37. package/lib/components/req.ts +300 -0
  38. package/lib/components/script.ts +33 -74
  39. package/lib/components/select.ts +280 -0
  40. package/lib/components/sidebar.ts +87 -37
  41. package/lib/components/style.ts +47 -70
  42. package/lib/components/switch.ts +261 -0
  43. package/lib/components/table.ts +47 -24
  44. package/lib/components/tabs.ts +105 -63
  45. package/lib/components/theme-toggle.ts +361 -0
  46. package/lib/components/token-calculator.ts +380 -0
  47. package/lib/components/tooltip.ts +244 -0
  48. package/lib/components/view.ts +36 -20
  49. package/lib/components/write.ts +284 -0
  50. package/lib/globals.d.ts +21 -0
  51. package/lib/jux.ts +178 -68
  52. package/lib/presets/notion.css +521 -0
  53. package/lib/presets/notion.jux +27 -0
  54. package/lib/reactivity/state.ts +364 -0
  55. package/lib/themes/charts.js +126 -0
  56. package/machinery/compiler.js +126 -38
  57. package/machinery/generators/html.js +2 -3
  58. package/machinery/server.js +2 -2
  59. package/package.json +29 -3
  60. package/lib/components/import.ts +0 -430
  61. package/lib/components/node.ts +0 -200
  62. package/lib/components/reactivity.js +0 -104
  63. package/lib/components/theme.ts +0 -97
  64. package/lib/layouts/notion.css +0 -258
  65. package/lib/styles/base-theme.css +0 -186
  66. package/lib/styles/dark-theme.css +0 -144
  67. package/lib/styles/light-theme.css +0 -144
  68. package/lib/styles/tokens/dark.css +0 -86
  69. package/lib/styles/tokens/light.css +0 -86
  70. package/lib/templates/index.juxt +0 -33
  71. package/lib/themes/dark.css +0 -86
  72. package/lib/themes/light.css +0 -86
  73. /package/lib/{styles → presets}/global.css +0 -0
@@ -0,0 +1,263 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+
3
+ /**
4
+ * Loading component options
5
+ */
6
+ export interface LoadingOptions {
7
+ variant?: 'spinner' | 'dots' | 'pulse' | 'skeleton';
8
+ size?: 'sm' | 'md' | 'lg';
9
+ text?: string;
10
+ fullscreen?: boolean;
11
+ style?: string;
12
+ class?: string;
13
+ }
14
+
15
+ /**
16
+ * Loading component state
17
+ */
18
+ type LoadingState = {
19
+ variant: string;
20
+ size: string;
21
+ text: string;
22
+ fullscreen: boolean;
23
+ style: string;
24
+ class: string;
25
+ };
26
+
27
+ /**
28
+ * Loading component - Spinners, skeletons, loading indicators
29
+ *
30
+ * Usage:
31
+ * jux.loading('my-spinner', {
32
+ * variant: 'spinner',
33
+ * size: 'lg',
34
+ * text: 'Loading...'
35
+ * }).render('#app');
36
+ *
37
+ * // Fullscreen overlay
38
+ * jux.loading('loading-overlay', {
39
+ * variant: 'spinner',
40
+ * fullscreen: true,
41
+ * text: 'Please wait...'
42
+ * }).render();
43
+ *
44
+ * Variants: spinner, dots, pulse, skeleton
45
+ */
46
+ export class Loading {
47
+ state: LoadingState;
48
+ container: HTMLElement | null = null;
49
+ _id: string;
50
+ id: string;
51
+
52
+ constructor(id: string, options: LoadingOptions = {}) {
53
+ this._id = id;
54
+ this.id = id;
55
+
56
+ this.state = {
57
+ variant: options.variant ?? 'spinner',
58
+ size: options.size ?? 'md',
59
+ text: options.text ?? '',
60
+ fullscreen: options.fullscreen ?? false,
61
+ style: options.style ?? '',
62
+ class: options.class ?? ''
63
+ };
64
+ }
65
+
66
+ /* -------------------------
67
+ * Fluent API
68
+ * ------------------------- */
69
+
70
+ variant(value: 'spinner' | 'dots' | 'pulse' | 'skeleton'): this {
71
+ this.state.variant = value;
72
+ return this;
73
+ }
74
+
75
+ size(value: 'sm' | 'md' | 'lg'): this {
76
+ this.state.size = value;
77
+ return this;
78
+ }
79
+
80
+ text(value: string): this {
81
+ this.state.text = value;
82
+ return this;
83
+ }
84
+
85
+ fullscreen(value: boolean): this {
86
+ this.state.fullscreen = value;
87
+ return this;
88
+ }
89
+
90
+ style(value: string): this {
91
+ this.state.style = value;
92
+ return this;
93
+ }
94
+
95
+ class(value: string): this {
96
+ this.state.class = value;
97
+ return this;
98
+ }
99
+
100
+ /* -------------------------
101
+ * Methods
102
+ * ------------------------- */
103
+
104
+ /**
105
+ * Show the loading indicator
106
+ */
107
+ show(): void {
108
+ const element = document.getElementById(this._id);
109
+ if (element) {
110
+ element.style.display = 'flex';
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Hide the loading indicator
116
+ */
117
+ hide(): void {
118
+ const element = document.getElementById(this._id);
119
+ if (element) {
120
+ element.style.display = 'none';
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Remove the loading indicator
126
+ */
127
+ remove(): void {
128
+ const element = document.getElementById(this._id);
129
+ if (element) {
130
+ element.remove();
131
+ }
132
+ }
133
+
134
+ /* -------------------------
135
+ * Helpers
136
+ * ------------------------- */
137
+
138
+ private _renderSpinner(): HTMLElement {
139
+ const spinner = document.createElement('div');
140
+ spinner.className = 'jux-loading-spinner';
141
+ return spinner;
142
+ }
143
+
144
+ private _renderDots(): HTMLElement {
145
+ const dots = document.createElement('div');
146
+ dots.className = 'jux-loading-dots';
147
+ for (let i = 0; i < 3; i++) {
148
+ const dot = document.createElement('div');
149
+ dot.className = 'jux-loading-dot';
150
+ dots.appendChild(dot);
151
+ }
152
+ return dots;
153
+ }
154
+
155
+ private _renderPulse(): HTMLElement {
156
+ const pulse = document.createElement('div');
157
+ pulse.className = 'jux-loading-pulse';
158
+ return pulse;
159
+ }
160
+
161
+ private _renderSkeleton(): HTMLElement {
162
+ const skeleton = document.createElement('div');
163
+ skeleton.className = 'jux-loading-skeleton';
164
+
165
+ // Create 3 skeleton lines
166
+ for (let i = 0; i < 3; i++) {
167
+ const line = document.createElement('div');
168
+ line.className = 'jux-loading-skeleton-line';
169
+ if (i === 2) line.style.width = '60%'; // Last line shorter
170
+ skeleton.appendChild(line);
171
+ }
172
+ return skeleton;
173
+ }
174
+
175
+ /* -------------------------
176
+ * Render
177
+ * ------------------------- */
178
+
179
+ render(targetId?: string): this {
180
+ let container: HTMLElement;
181
+
182
+ if (targetId) {
183
+ const target = document.querySelector(targetId);
184
+ if (!target || !(target instanceof HTMLElement)) {
185
+ throw new Error(`Loading: Target element "${targetId}" not found`);
186
+ }
187
+ container = target;
188
+ } else {
189
+ container = getOrCreateContainer(this._id);
190
+ }
191
+
192
+ this.container = container;
193
+ const { variant, size, text, fullscreen, style, class: className } = this.state;
194
+
195
+ const loading = document.createElement('div');
196
+ loading.className = `jux-loading jux-loading-${size}`;
197
+ loading.id = this._id;
198
+
199
+ if (fullscreen) {
200
+ loading.className += ' jux-loading-fullscreen';
201
+ }
202
+
203
+ if (className) {
204
+ loading.className += ` ${className}`;
205
+ }
206
+
207
+ if (style) {
208
+ loading.setAttribute('style', style);
209
+ }
210
+
211
+ // Render appropriate variant
212
+ let indicator: HTMLElement;
213
+ switch (variant) {
214
+ case 'dots':
215
+ indicator = this._renderDots();
216
+ break;
217
+ case 'pulse':
218
+ indicator = this._renderPulse();
219
+ break;
220
+ case 'skeleton':
221
+ indicator = this._renderSkeleton();
222
+ break;
223
+ case 'spinner':
224
+ default:
225
+ indicator = this._renderSpinner();
226
+ }
227
+
228
+ loading.appendChild(indicator);
229
+
230
+ // Optional text
231
+ if (text) {
232
+ const textEl = document.createElement('div');
233
+ textEl.className = 'jux-loading-text';
234
+ textEl.textContent = text;
235
+ loading.appendChild(textEl);
236
+ }
237
+
238
+ container.appendChild(loading);
239
+ return this;
240
+ }
241
+
242
+ /**
243
+ * Render to another Jux component's container
244
+ */
245
+ renderTo(juxComponent: any): this {
246
+ if (!juxComponent || typeof juxComponent !== 'object') {
247
+ throw new Error('Loading.renderTo: Invalid component - not an object');
248
+ }
249
+
250
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
251
+ throw new Error('Loading.renderTo: Invalid component - missing _id (not a Jux component)');
252
+ }
253
+
254
+ return this.render(`#${juxComponent._id}`);
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Factory helper
260
+ */
261
+ export function loading(id: string, options: LoadingOptions = {}): Loading {
262
+ return new Loading(id, options);
263
+ }
@@ -1,4 +1,4 @@
1
- import { Reactive, getOrCreateContainer } from './reactivity.js';
1
+ import { getOrCreateContainer } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Main component options
@@ -6,6 +6,8 @@ import { Reactive, getOrCreateContainer } from './reactivity.js';
6
6
  export interface MainOptions {
7
7
  content?: string;
8
8
  padding?: string;
9
+ style?: string;
10
+ class?: string;
9
11
  }
10
12
 
11
13
  /**
@@ -14,6 +16,8 @@ export interface MainOptions {
14
16
  type MainState = {
15
17
  content: string;
16
18
  padding: string;
19
+ style: string;
20
+ class: string;
17
21
  };
18
22
 
19
23
  /**
@@ -26,18 +30,22 @@ type MainState = {
26
30
  * });
27
31
  * main.render('#appmain');
28
32
  */
29
- export class Main extends Reactive {
30
- state!: MainState;
33
+ export class Main {
34
+ state: MainState;
31
35
  container: HTMLElement | null = null;
36
+ _id: string;
37
+ id: string;
32
38
 
33
- constructor(componentId: string, options: MainOptions = {}) {
34
- super();
35
- this._setComponentId(componentId);
39
+ constructor(id: string, options: MainOptions = {}) {
40
+ this._id = id;
41
+ this.id = id;
36
42
 
37
- this.state = this._createReactiveState({
43
+ this.state = {
38
44
  content: options.content ?? '',
39
- padding: options.padding ?? 'var(--space-xl)'
40
- }) as MainState;
45
+ padding: options.padding ?? 'var(--space-xl)',
46
+ style: options.style ?? '',
47
+ class: options.class ?? ''
48
+ };
41
49
  }
42
50
 
43
51
  /* -------------------------
@@ -54,6 +62,16 @@ export class Main extends Reactive {
54
62
  return this;
55
63
  }
56
64
 
65
+ style(value: string): this {
66
+ this.state.style = value;
67
+ return this;
68
+ }
69
+
70
+ class(value: string): this {
71
+ this.state.class = value;
72
+ return this;
73
+ }
74
+
57
75
  /* -------------------------
58
76
  * Render
59
77
  * ------------------------- */
@@ -68,17 +86,25 @@ export class Main extends Reactive {
68
86
  }
69
87
  container = target;
70
88
  } else {
71
- container = getOrCreateContainer(this._componentId) as HTMLElement;
89
+ container = getOrCreateContainer(this._id);
72
90
  }
73
91
 
74
92
  this.container = container;
75
- const { content, padding } = this.state;
93
+ const { content, padding, style, class: className } = this.state;
76
94
 
77
95
  const main = document.createElement('main');
78
96
  main.className = 'jux-main';
79
- main.id = this._componentId;
97
+ main.id = this._id;
80
98
  main.style.padding = padding;
81
99
 
100
+ if (style) {
101
+ main.setAttribute('style', style);
102
+ }
103
+
104
+ if (className) {
105
+ main.className += ` ${className}`;
106
+ }
107
+
82
108
  if (content) {
83
109
  main.innerHTML = content;
84
110
  }
@@ -95,17 +121,17 @@ export class Main extends Reactive {
95
121
  throw new Error('Main.renderTo: Invalid component - not an object');
96
122
  }
97
123
 
98
- if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
99
- throw new Error('Main.renderTo: Invalid component - missing _componentId (not a Jux component)');
124
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
125
+ throw new Error('Main.renderTo: Invalid component - missing _id (not a Jux component)');
100
126
  }
101
127
 
102
- return this.render(`#${juxComponent._componentId}`);
128
+ return this.render(`#${juxComponent._id}`);
103
129
  }
104
130
  }
105
131
 
106
132
  /**
107
133
  * Factory helper
108
134
  */
109
- export function main(componentId: string, options: MainOptions = {}): Main {
110
- return new Main(componentId, options);
135
+ export function main(id: string, options: MainOptions = {}): Main {
136
+ return new Main(id, options);
111
137
  }
@@ -1,4 +1,5 @@
1
- import { Reactive, getOrCreateContainer } from './reactivity.js';
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { req } from './req.js';
2
3
 
3
4
  /**
4
5
  * Menu item configuration
@@ -6,9 +7,10 @@ import { Reactive, getOrCreateContainer } from './reactivity.js';
6
7
  export interface MenuItem {
7
8
  label: string;
8
9
  href?: string;
9
- onClick?: () => void;
10
+ click?: () => void;
10
11
  icon?: string;
11
12
  items?: MenuItem[];
13
+ active?: boolean;
12
14
  }
13
15
 
14
16
  /**
@@ -17,6 +19,8 @@ export interface MenuItem {
17
19
  export interface MenuOptions {
18
20
  items?: MenuItem[];
19
21
  orientation?: 'vertical' | 'horizontal';
22
+ style?: string;
23
+ class?: string;
20
24
  }
21
25
 
22
26
  /**
@@ -25,6 +29,8 @@ export interface MenuOptions {
25
29
  type MenuState = {
26
30
  items: MenuItem[];
27
31
  orientation: string;
32
+ style: string;
33
+ class: string;
28
34
  };
29
35
 
30
36
  /**
@@ -39,19 +45,43 @@ type MenuState = {
39
45
  * ]
40
46
  * });
41
47
  * menu.render();
48
+ *
49
+ * Active states are automatically set based on current URL
42
50
  */
43
- export class Menu extends Reactive {
44
- state!: MenuState;
51
+ export class Menu {
52
+ state: MenuState;
45
53
  container: HTMLElement | null = null;
54
+ _id: string;
55
+ id: string;
46
56
 
47
- constructor(componentId: string, options: MenuOptions = {}) {
48
- super();
49
- this._setComponentId(componentId);
57
+ constructor(id: string, options: MenuOptions = {}) {
58
+ this._id = id;
59
+ this.id = id;
50
60
 
51
- this.state = this._createReactiveState({
61
+ this.state = {
52
62
  items: options.items ?? [],
53
- orientation: options.orientation ?? 'vertical'
54
- }) as MenuState;
63
+ orientation: options.orientation ?? 'vertical',
64
+ style: options.style ?? '',
65
+ class: options.class ?? ''
66
+ };
67
+
68
+ // Auto-set active state based on current path
69
+ this._setActiveStates();
70
+ }
71
+
72
+ /**
73
+ * Set active state on items based on current request path
74
+ */
75
+ private _setActiveStates(): void {
76
+ this.state.items = this.state.items.map(item => ({
77
+ ...item,
78
+ active: item.href ? req.isActiveNavItem(item.href) : false,
79
+ // Recursively set active for subitems
80
+ items: item.items?.map(subItem => ({
81
+ ...subItem,
82
+ active: subItem.href ? req.isActiveNavItem(subItem.href) : false
83
+ }))
84
+ }));
55
85
  }
56
86
 
57
87
  /* -------------------------
@@ -60,11 +90,13 @@ export class Menu extends Reactive {
60
90
 
61
91
  items(value: MenuItem[]): this {
62
92
  this.state.items = value;
93
+ this._setActiveStates();
63
94
  return this;
64
95
  }
65
96
 
66
97
  addItem(item: MenuItem): this {
67
- this.state.items.push(item);
98
+ this.state.items = [...this.state.items, item];
99
+ this._setActiveStates();
68
100
  return this;
69
101
  }
70
102
 
@@ -73,6 +105,16 @@ export class Menu extends Reactive {
73
105
  return this;
74
106
  }
75
107
 
108
+ style(value: string): this {
109
+ this.state.style = value;
110
+ return this;
111
+ }
112
+
113
+ class(value: string): this {
114
+ this.state.class = value;
115
+ return this;
116
+ }
117
+
76
118
  /* -------------------------
77
119
  * Helpers
78
120
  * ------------------------- */
@@ -81,21 +123,55 @@ export class Menu extends Reactive {
81
123
  const menuItem = document.createElement('div');
82
124
  menuItem.className = 'jux-menu-item';
83
125
 
126
+ // Add active class if item is active
127
+ if (item.active) {
128
+ menuItem.classList.add('jux-menu-item-active');
129
+ }
130
+
84
131
  if (item.href) {
85
132
  const link = document.createElement('a');
86
133
  link.className = 'jux-menu-link';
87
134
  link.href = item.href;
88
- link.textContent = item.label;
135
+
136
+ // Add active class to link if item is active
137
+ if (item.active) {
138
+ link.classList.add('jux-menu-link-active');
139
+ }
140
+
141
+ if (item.icon) {
142
+ const icon = document.createElement('span');
143
+ icon.className = 'jux-menu-icon';
144
+ icon.textContent = item.icon;
145
+ link.appendChild(icon);
146
+ }
147
+
148
+ const label = document.createElement('span');
149
+ label.className = 'jux-menu-label';
150
+ label.textContent = item.label;
151
+ link.appendChild(label);
152
+
89
153
  menuItem.appendChild(link);
90
154
  } else {
91
155
  const button = document.createElement('button');
92
156
  button.className = 'jux-menu-button';
93
- button.textContent = item.label;
157
+
158
+ if (item.icon) {
159
+ const icon = document.createElement('span');
160
+ icon.className = 'jux-menu-icon';
161
+ icon.textContent = item.icon;
162
+ button.appendChild(icon);
163
+ }
164
+
165
+ const label = document.createElement('span');
166
+ label.className = 'jux-menu-label';
167
+ label.textContent = item.label;
168
+ button.appendChild(label);
169
+
94
170
  menuItem.appendChild(button);
95
171
 
96
- // Event binding - onClick
97
- if (item.onClick) {
98
- button.addEventListener('click', item.onClick);
172
+ // Event binding - click
173
+ if (item.click) {
174
+ button.addEventListener('click', item.click);
99
175
  }
100
176
  }
101
177
 
@@ -128,15 +204,23 @@ export class Menu extends Reactive {
128
204
  }
129
205
  container = target;
130
206
  } else {
131
- container = getOrCreateContainer(this._componentId) as HTMLElement;
207
+ container = getOrCreateContainer(this._id);
132
208
  }
133
209
 
134
210
  this.container = container;
135
- const { items, orientation } = this.state;
211
+ const { items, orientation, style, class: className } = this.state;
136
212
 
137
213
  const menu = document.createElement('nav');
138
214
  menu.className = `jux-menu jux-menu-${orientation}`;
139
- menu.id = this._componentId;
215
+ menu.id = this._id;
216
+
217
+ if (className) {
218
+ menu.className += ` ${className}`;
219
+ }
220
+
221
+ if (style) {
222
+ menu.setAttribute('style', style);
223
+ }
140
224
 
141
225
  items.forEach(item => {
142
226
  menu.appendChild(this._renderMenuItem(item));
@@ -154,17 +238,17 @@ export class Menu extends Reactive {
154
238
  throw new Error('Menu.renderTo: Invalid component - not an object');
155
239
  }
156
240
 
157
- if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
158
- throw new Error('Menu.renderTo: Invalid component - missing _componentId (not a Jux component)');
241
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
242
+ throw new Error('Menu.renderTo: Invalid component - missing _id (not a Jux component)');
159
243
  }
160
244
 
161
- return this.render(`#${juxComponent._componentId}`);
245
+ return this.render(`#${juxComponent._id}`);
162
246
  }
163
247
  }
164
248
 
165
249
  /**
166
250
  * Factory helper
167
251
  */
168
- export function menu(componentId: string, options: MenuOptions = {}): Menu {
169
- return new Menu(componentId, options);
252
+ export function menu(id: string, options: MenuOptions = {}): Menu {
253
+ return new Menu(id, options);
170
254
  }