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
@@ -1,4 +1,4 @@
1
- import { Reactive, getOrCreateContainer } from './reactivity.js';
1
+ import { getOrCreateContainer } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Sidebar component options
@@ -9,6 +9,8 @@ export interface SidebarOptions {
9
9
  position?: 'left' | 'right';
10
10
  collapsible?: boolean;
11
11
  collapsed?: boolean;
12
+ style?: string;
13
+ class?: string;
12
14
  }
13
15
 
14
16
  /**
@@ -20,6 +22,8 @@ type SidebarState = {
20
22
  position: string;
21
23
  collapsible: boolean;
22
24
  collapsed: boolean;
25
+ style: string;
26
+ class: string;
23
27
  };
24
28
 
25
29
  /**
@@ -33,21 +37,25 @@ type SidebarState = {
33
37
  * });
34
38
  * sidebar.render('#appsidebar');
35
39
  */
36
- export class Sidebar extends Reactive {
37
- state!: SidebarState;
40
+ export class Sidebar {
41
+ state: SidebarState;
38
42
  container: HTMLElement | null = null;
39
-
40
- constructor(componentId: string, options: SidebarOptions = {}) {
41
- super();
42
- this._setComponentId(componentId);
43
-
44
- this.state = this._createReactiveState({
43
+ _id: string;
44
+ id: string;
45
+
46
+ constructor(id: string, options: SidebarOptions = {}) {
47
+ this._id = id;
48
+ this.id = id;
49
+
50
+ this.state = {
45
51
  title: options.title ?? '',
46
52
  width: options.width ?? '250px',
47
53
  position: options.position ?? 'left',
48
54
  collapsible: options.collapsible ?? false,
49
- collapsed: options.collapsed ?? false
50
- }) as SidebarState;
55
+ collapsed: options.collapsed ?? false,
56
+ style: options.style ?? '',
57
+ class: options.class ?? ''
58
+ };
51
59
  }
52
60
 
53
61
  /* -------------------------
@@ -79,18 +87,55 @@ export class Sidebar extends Reactive {
79
87
  return this;
80
88
  }
81
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
+
82
100
  toggle(): this {
83
101
  this.state.collapsed = !this.state.collapsed;
102
+ this._updateDOM();
84
103
  return this;
85
104
  }
86
105
 
106
+ /* -------------------------
107
+ * Helpers
108
+ * ------------------------- */
109
+
110
+ private _updateDOM(): void {
111
+ if (!this.container) return;
112
+
113
+ const sidebar = this.container.querySelector(`#${this._id}`);
114
+ if (!sidebar || !(sidebar instanceof HTMLElement)) return;
115
+
116
+ const { width, collapsed } = this.state;
117
+
118
+ if (collapsed) {
119
+ sidebar.classList.add('jux-sidebar-collapsed');
120
+ sidebar.style.width = '0';
121
+ } else {
122
+ sidebar.classList.remove('jux-sidebar-collapsed');
123
+ sidebar.style.width = width;
124
+ }
125
+
126
+ const toggleBtn = sidebar.querySelector('.jux-sidebar-toggle');
127
+ if (toggleBtn) {
128
+ toggleBtn.textContent = collapsed ? '>' : '<';
129
+ }
130
+ }
131
+
87
132
  /* -------------------------
88
133
  * Render
89
134
  * ------------------------- */
90
135
 
91
136
  render(targetId?: string): this {
92
137
  let container: HTMLElement;
93
-
138
+
94
139
  if (targetId) {
95
140
  const target = document.querySelector(targetId);
96
141
  if (!target || !(target instanceof HTMLElement)) {
@@ -98,49 +143,54 @@ export class Sidebar extends Reactive {
98
143
  }
99
144
  container = target;
100
145
  } else {
101
- container = getOrCreateContainer(this._componentId) as HTMLElement;
146
+ container = getOrCreateContainer(this._id);
102
147
  }
103
-
148
+
104
149
  this.container = container;
105
- const { title, width, position, collapsible, collapsed } = this.state;
106
-
150
+ const { title, width, position, collapsible, collapsed, style, class: className } = this.state;
151
+
107
152
  const sidebar = document.createElement('aside');
108
153
  sidebar.className = `jux-sidebar jux-sidebar-${position}`;
109
- sidebar.id = this._componentId;
154
+ sidebar.id = this._id;
110
155
  sidebar.style.width = collapsed ? '0' : width;
111
-
156
+
157
+ if (className) {
158
+ sidebar.className += ` ${className}`;
159
+ }
160
+
161
+ if (style) {
162
+ sidebar.setAttribute('style', sidebar.getAttribute('style') + '; ' + style);
163
+ }
164
+
112
165
  if (collapsed) {
113
166
  sidebar.classList.add('jux-sidebar-collapsed');
114
167
  }
115
-
168
+
116
169
  if (title) {
117
170
  const titleEl = document.createElement('div');
118
171
  titleEl.className = 'jux-sidebar-title';
119
172
  titleEl.textContent = title;
120
173
  sidebar.appendChild(titleEl);
121
174
  }
122
-
175
+
123
176
  const content = document.createElement('div');
124
177
  content.className = 'jux-sidebar-content';
125
178
  sidebar.appendChild(content);
126
-
127
- container.appendChild(sidebar);
128
-
179
+
129
180
  // Event binding - toggle button
130
181
  if (collapsible) {
131
182
  const toggleBtn = document.createElement('button');
132
183
  toggleBtn.className = 'jux-sidebar-toggle';
133
184
  toggleBtn.textContent = collapsed ? '>' : '<';
134
- sidebar.appendChild(toggleBtn);
135
-
185
+
136
186
  toggleBtn.addEventListener('click', () => {
137
187
  this.toggle();
138
- sidebar.classList.toggle('jux-sidebar-collapsed');
139
- sidebar.style.width = this.state.collapsed ? '0' : width;
140
- toggleBtn.textContent = this.state.collapsed ? '>' : '<';
141
188
  });
189
+
190
+ sidebar.appendChild(toggleBtn);
142
191
  }
143
-
192
+
193
+ container.appendChild(sidebar);
144
194
  return this;
145
195
  }
146
196
 
@@ -151,18 +201,18 @@ export class Sidebar extends Reactive {
151
201
  if (!juxComponent || typeof juxComponent !== 'object') {
152
202
  throw new Error('Sidebar.renderTo: Invalid component - not an object');
153
203
  }
154
-
155
- if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
156
- throw new Error('Sidebar.renderTo: Invalid component - missing _componentId (not a Jux component)');
204
+
205
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
206
+ throw new Error('Sidebar.renderTo: Invalid component - missing _id (not a Jux component)');
157
207
  }
158
-
159
- return this.render(`#${juxComponent._componentId}`);
208
+
209
+ return this.render(`#${juxComponent._id}`);
160
210
  }
161
211
  }
162
212
 
163
213
  /**
164
214
  * Factory helper
165
215
  */
166
- export function sidebar(componentId: string, options: SidebarOptions = {}): Sidebar {
167
- return new Sidebar(componentId, options);
216
+ export function sidebar(id: string, options: SidebarOptions = {}): Sidebar {
217
+ return new Sidebar(id, options);
168
218
  }
@@ -1,66 +1,53 @@
1
1
  import { ErrorHandler } from './error-handler.js';
2
2
 
3
+ /**
4
+ * Style - Inject inline CSS into the document
5
+ * For external stylesheets, use Import component instead
6
+ * Auto-renders when created or modified
7
+ */
3
8
  export class Style {
4
9
  private _content: string = '';
5
- private _url: string = '';
6
- private _isUrl: boolean = false;
7
- private _element: HTMLLinkElement | HTMLStyleElement | null = null;
10
+ private _element: HTMLStyleElement | null = null;
8
11
 
9
- constructor(contentOrUrl: string = '') {
10
- // Detect if it's a URL or inline CSS
11
- if (contentOrUrl.trim().startsWith('http') ||
12
- contentOrUrl.endsWith('.css') ||
13
- contentOrUrl.includes('/')) {
14
- this._url = contentOrUrl;
15
- this._isUrl = true;
16
- } else {
17
- this._content = contentOrUrl;
18
- this._isUrl = false;
19
- }
20
-
21
- // Auto-render if content/url provided
22
- // render() has its own error handling, so no need for try-catch here
23
- if (contentOrUrl) {
12
+ constructor(content: string = '') {
13
+ this._content = content;
14
+
15
+ // Auto-render if content provided
16
+ if (content) {
24
17
  this.render();
25
18
  }
26
19
  }
27
20
 
21
+ /**
22
+ * Set inline CSS content
23
+ */
28
24
  content(css: string): this {
29
25
  this._content = css;
30
- this._isUrl = false;
31
- this._url = '';
32
- this.render();
33
- return this;
34
- }
35
-
36
- url(href: string): this {
37
- this._url = href;
38
- this._isUrl = true;
39
- this._content = '';
40
26
  this.render();
41
27
  return this;
42
28
  }
43
29
 
30
+ /**
31
+ * Append CSS to existing content
32
+ */
44
33
  append(css: string): this {
45
- if (this._isUrl) {
46
- console.warn('Cannot append to external stylesheet. Use content() to switch to inline styles.');
47
- return this;
48
- }
49
34
  this._content += '\n' + css;
50
35
  this.render();
51
36
  return this;
52
37
  }
53
38
 
39
+ /**
40
+ * Prepend CSS to existing content
41
+ */
54
42
  prepend(css: string): this {
55
- if (this._isUrl) {
56
- console.warn('Cannot prepend to external stylesheet. Use content() to switch to inline styles.');
57
- return this;
58
- }
59
43
  this._content = css + '\n' + this._content;
60
44
  this.render();
61
45
  return this;
62
46
  }
63
47
 
48
+ /**
49
+ * Render the inline style element
50
+ */
64
51
  render(): this {
65
52
  if (typeof document === 'undefined') {
66
53
  return this;
@@ -70,47 +57,22 @@ export class Style {
70
57
  // Remove existing element if it exists
71
58
  this.remove();
72
59
 
73
- if (this._isUrl) {
74
- // Create <link> element for external stylesheet
75
- const link = document.createElement('link');
76
- link.rel = 'stylesheet';
77
- link.href = this._url;
78
-
79
- // Add error handler for failed loads (404, network, etc.)
80
- link.onerror = () => {
81
- ErrorHandler.captureError({
82
- component: 'Style',
83
- method: 'render',
84
- message: `Failed to load stylesheet: ${this._url}`,
85
- timestamp: new Date(),
86
- context: { url: this._url, type: 'external', error: 'load_failed' }
87
- });
88
- };
89
-
90
- link.onload = () => {
91
- console.log(`✓ Stylesheet loaded: ${this._url}`);
92
- };
93
-
94
- document.head.appendChild(link);
95
- this._element = link;
96
- } else {
97
- // Create <style> element for inline CSS
98
- const style = document.createElement('style');
99
- style.textContent = this._content;
100
- document.head.appendChild(style);
101
- this._element = style;
102
- }
60
+ // Create <style> element for inline CSS
61
+ const style = document.createElement('style');
62
+ style.textContent = this._content;
63
+ document.head.appendChild(style);
64
+ this._element = style;
65
+
66
+ console.log('✓ Inline styles rendered');
103
67
  } catch (error: any) {
104
- // Catch DOM manipulation errors, permission errors, etc.
105
68
  ErrorHandler.captureError({
106
69
  component: 'Style',
107
70
  method: 'render',
108
71
  message: error.message,
109
72
  stack: error.stack,
110
73
  timestamp: new Date(),
111
- context: {
112
- isUrl: this._isUrl,
113
- url: this._url,
74
+ context: {
75
+ type: 'inline',
114
76
  error: 'runtime_exception'
115
77
  }
116
78
  });
@@ -119,6 +81,9 @@ export class Style {
119
81
  return this;
120
82
  }
121
83
 
84
+ /**
85
+ * Remove the style from the document
86
+ */
122
87
  remove(): this {
123
88
  if (this._element && this._element.parentNode) {
124
89
  this._element.parentNode.removeChild(this._element);
@@ -126,4 +91,16 @@ export class Style {
126
91
  }
127
92
  return this;
128
93
  }
94
+ }
95
+
96
+ /**
97
+ * Factory function
98
+ *
99
+ * Usage:
100
+ * jux.style('body { margin: 0; }');
101
+ * jux.style().content('.highlight { color: red; }');
102
+ * jux.style('h1 { font-size: 2rem; }').append('h2 { font-size: 1.5rem; }');
103
+ */
104
+ export function style(content: string = ''): Style {
105
+ return new Style(content);
129
106
  }
@@ -0,0 +1,261 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
3
+
4
+ /**
5
+ * Switch component options
6
+ */
7
+ export interface SwitchOptions {
8
+ label?: string;
9
+ checked?: boolean;
10
+ disabled?: boolean;
11
+ name?: string;
12
+ onChange?: (checked: boolean) => void;
13
+ style?: string;
14
+ class?: string;
15
+ }
16
+
17
+ /**
18
+ * Switch component state
19
+ */
20
+ type SwitchState = {
21
+ label: string;
22
+ checked: boolean;
23
+ disabled: boolean;
24
+ name: string;
25
+ style: string;
26
+ class: string;
27
+ };
28
+
29
+ /**
30
+ * Switch component - Toggle switch for boolean values
31
+ *
32
+ * Usage:
33
+ * jux.switch('notifications', {
34
+ * label: 'Enable notifications',
35
+ * checked: true,
36
+ * onChange: (checked) => console.log(checked)
37
+ * }).render('#settings');
38
+ *
39
+ * // Two-way binding
40
+ * const notificationsState = state(true);
41
+ * jux.switch('notifications').label('Notifications').bind(notificationsState).render('#settings');
42
+ */
43
+ export class Switch {
44
+ state: SwitchState;
45
+ container: HTMLElement | null = null;
46
+ _id: string;
47
+ id: string;
48
+ private _onChange?: (checked: boolean) => void;
49
+ private _boundState?: State<boolean>;
50
+
51
+ constructor(id: string, options: SwitchOptions = {}) {
52
+ this._id = id;
53
+ this.id = id;
54
+ this._onChange = options.onChange;
55
+
56
+ this.state = {
57
+ label: options.label ?? '',
58
+ checked: options.checked ?? false,
59
+ disabled: options.disabled ?? false,
60
+ name: options.name ?? id,
61
+ style: options.style ?? '',
62
+ class: options.class ?? ''
63
+ };
64
+ }
65
+
66
+ /* -------------------------
67
+ * Fluent API
68
+ * ------------------------- */
69
+
70
+ label(value: string): this {
71
+ this.state.label = value;
72
+ return this;
73
+ }
74
+
75
+ checked(value: boolean): this {
76
+ this.state.checked = value;
77
+ this._updateElement();
78
+ return this;
79
+ }
80
+
81
+ disabled(value: boolean): this {
82
+ this.state.disabled = value;
83
+ this._updateElement();
84
+ return this;
85
+ }
86
+
87
+ name(value: string): this {
88
+ this.state.name = value;
89
+ return this;
90
+ }
91
+
92
+ style(value: string): this {
93
+ this.state.style = value;
94
+ return this;
95
+ }
96
+
97
+ class(value: string): this {
98
+ this.state.class = value;
99
+ return this;
100
+ }
101
+
102
+ onChange(handler: (checked: boolean) => void): this {
103
+ this._onChange = handler;
104
+ return this;
105
+ }
106
+
107
+ /**
108
+ * Two-way binding to state
109
+ */
110
+ bind(stateObj: State<boolean>): this {
111
+ this._boundState = stateObj;
112
+
113
+ // Update switch when state changes
114
+ stateObj.subscribe((val) => {
115
+ this.state.checked = val;
116
+ this._updateElement();
117
+ });
118
+
119
+ // Update state when switch changes
120
+ this.onChange((checked) => stateObj.set(checked));
121
+
122
+ return this;
123
+ }
124
+
125
+ /* -------------------------
126
+ * Helpers
127
+ * ------------------------- */
128
+
129
+ private _updateElement(): void {
130
+ const input = document.getElementById(`${this._id}-input`) as HTMLInputElement;
131
+ const track = document.getElementById(`${this._id}-track`);
132
+
133
+ if (input) {
134
+ input.checked = this.state.checked;
135
+ input.disabled = this.state.disabled;
136
+ }
137
+
138
+ if (track) {
139
+ if (this.state.checked) {
140
+ track.classList.add('jux-switch-track-checked');
141
+ } else {
142
+ track.classList.remove('jux-switch-track-checked');
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Toggle the switch
149
+ */
150
+ toggle(): this {
151
+ this.state.checked = !this.state.checked;
152
+ this._updateElement();
153
+ if (this._onChange) {
154
+ this._onChange(this.state.checked);
155
+ }
156
+ return this;
157
+ }
158
+
159
+ isChecked(): boolean {
160
+ return this.state.checked;
161
+ }
162
+
163
+ /* -------------------------
164
+ * Render
165
+ * ------------------------- */
166
+
167
+ render(targetId?: string): this {
168
+ let container: HTMLElement;
169
+
170
+ if (targetId) {
171
+ const target = document.querySelector(targetId);
172
+ if (!target || !(target instanceof HTMLElement)) {
173
+ throw new Error(`Switch: Target element "${targetId}" not found`);
174
+ }
175
+ container = target;
176
+ } else {
177
+ container = getOrCreateContainer(this._id);
178
+ }
179
+
180
+ this.container = container;
181
+ const { label, checked, disabled, name, style, class: className } = this.state;
182
+
183
+ const wrapper = document.createElement('div');
184
+ wrapper.className = 'jux-switch';
185
+ wrapper.id = this._id;
186
+
187
+ if (className) {
188
+ wrapper.className += ` ${className}`;
189
+ }
190
+
191
+ if (style) {
192
+ wrapper.setAttribute('style', style);
193
+ }
194
+
195
+ const labelEl = document.createElement('label');
196
+ labelEl.className = 'jux-switch-label';
197
+
198
+ const input = document.createElement('input');
199
+ input.type = 'checkbox';
200
+ input.className = 'jux-switch-input';
201
+ input.id = `${this._id}-input`;
202
+ input.name = name;
203
+ input.checked = checked;
204
+ input.disabled = disabled;
205
+ input.setAttribute('role', 'switch');
206
+
207
+ input.addEventListener('change', (e) => {
208
+ const target = e.target as HTMLInputElement;
209
+ this.state.checked = target.checked;
210
+
211
+ const track = document.getElementById(`${this._id}-track`);
212
+ if (track) {
213
+ if (target.checked) {
214
+ track.classList.add('jux-switch-track-checked');
215
+ } else {
216
+ track.classList.remove('jux-switch-track-checked');
217
+ }
218
+ }
219
+
220
+ if (this._onChange) {
221
+ this._onChange(target.checked);
222
+ }
223
+ });
224
+
225
+ const track = document.createElement('span');
226
+ track.className = 'jux-switch-track';
227
+ track.id = `${this._id}-track`;
228
+ if (checked) {
229
+ track.classList.add('jux-switch-track-checked');
230
+ }
231
+
232
+ const thumb = document.createElement('span');
233
+ thumb.className = 'jux-switch-thumb';
234
+
235
+ track.appendChild(thumb);
236
+ labelEl.appendChild(input);
237
+ labelEl.appendChild(track);
238
+
239
+ if (label) {
240
+ const span = document.createElement('span');
241
+ span.className = 'jux-switch-text';
242
+ span.textContent = label;
243
+ labelEl.appendChild(span);
244
+ }
245
+
246
+ wrapper.appendChild(labelEl);
247
+ container.appendChild(wrapper);
248
+ return this;
249
+ }
250
+
251
+ renderTo(juxComponent: any): this {
252
+ if (!juxComponent?._id) {
253
+ throw new Error('Switch.renderTo: Invalid component');
254
+ }
255
+ return this.render(`#${juxComponent._id}`);
256
+ }
257
+ }
258
+
259
+ export function switchComponent(id: string, options: SwitchOptions = {}): Switch {
260
+ return new Switch(id, options);
261
+ }