juxscript 1.0.3 → 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,119 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+
3
+ /**
4
+ * Heading options
5
+ */
6
+ export interface HeadingOptions {
7
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
8
+ text?: string;
9
+ class?: string;
10
+ style?: string;
11
+ }
12
+
13
+ /**
14
+ * Heading state
15
+ */
16
+ type HeadingState = {
17
+ level: 1 | 2 | 3 | 4 | 5 | 6;
18
+ text: string;
19
+ class: string;
20
+ style: string;
21
+ };
22
+
23
+ /**
24
+ * Heading component - semantic heading elements (h1-h6)
25
+ *
26
+ * Usage:
27
+ * jux.heading('title', { level: 1, text: 'Welcome' }).render('#app');
28
+ * jux.heading('subtitle').level(2).text('Getting Started').render('#app');
29
+ */
30
+ export class Heading {
31
+ state: HeadingState;
32
+ container: HTMLElement | null = null;
33
+ _id: string;
34
+ id: string;
35
+
36
+ constructor(id: string, options: HeadingOptions = {}) {
37
+ this._id = id;
38
+ this.id = id;
39
+
40
+ this.state = {
41
+ level: options.level ?? 1,
42
+ text: options.text ?? '',
43
+ class: options.class ?? '',
44
+ style: options.style ?? ''
45
+ };
46
+ }
47
+
48
+ /* -------------------------
49
+ * Fluent API
50
+ * ------------------------- */
51
+
52
+ level(value: 1 | 2 | 3 | 4 | 5 | 6): this {
53
+ this.state.level = value;
54
+ return this;
55
+ }
56
+
57
+ text(value: string): this {
58
+ this.state.text = value;
59
+ return this;
60
+ }
61
+
62
+ class(value: string): this {
63
+ this.state.class = value;
64
+ return this;
65
+ }
66
+
67
+ style(value: string): this {
68
+ this.state.style = value;
69
+ return this;
70
+ }
71
+
72
+ /* -------------------------
73
+ * Render
74
+ * ------------------------- */
75
+
76
+ render(targetId?: string | HTMLElement): this {
77
+ let container: HTMLElement;
78
+
79
+ if (targetId) {
80
+ if (targetId instanceof HTMLElement) {
81
+ container = targetId;
82
+ } else {
83
+ const target = document.querySelector(targetId);
84
+ if (!target || !(target instanceof HTMLElement)) {
85
+ throw new Error(`Heading: Target element "${targetId}" not found`);
86
+ }
87
+ container = target;
88
+ }
89
+ } else {
90
+ container = getOrCreateContainer(this._id);
91
+ }
92
+
93
+ this.container = container;
94
+ const { level, text, class: className, style } = this.state;
95
+
96
+ const heading = document.createElement(`h${level}`) as HTMLHeadingElement;
97
+ heading.id = this._id;
98
+ heading.textContent = text;
99
+
100
+ if (className) {
101
+ heading.className = className;
102
+ }
103
+
104
+ if (style) {
105
+ heading.setAttribute('style', style);
106
+ }
107
+
108
+ container.appendChild(heading);
109
+
110
+ return this;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Factory helper
116
+ */
117
+ export function heading(id: string, options: HeadingOptions = {}): Heading {
118
+ return new Heading(id, options);
119
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Component helper utilities
3
+ */
4
+
5
+ /**
6
+ * Get or create a container element for a component
7
+ * Auto-creates if it doesn't exist and appends to appropriate parent
8
+ */
9
+ export function getOrCreateContainer(id: string): HTMLElement {
10
+ if (typeof document === 'undefined') {
11
+ throw new Error('Document is not available');
12
+ }
13
+
14
+ let container = document.getElementById(id);
15
+
16
+ // Container already exists, return it
17
+ if (container) {
18
+ return container;
19
+ }
20
+
21
+ // Auto-create container if it doesn't exist
22
+ container = document.createElement('div');
23
+ container.id = id;
24
+
25
+ // Find appropriate parent
26
+ let parent: HTMLElement;
27
+ // Page components go inside #appmain (or [data-jux-page] if no layout)
28
+ const appmain = document.getElementById('appmain');
29
+ const dataJuxPage = document.querySelector('[data-jux-page]');
30
+ parent = (appmain || dataJuxPage || document.body) as HTMLElement;
31
+ parent.appendChild(container);
32
+
33
+ return container;
34
+ }
@@ -1,4 +1,4 @@
1
- import { Reactive, getOrCreateContainer } from './reactivity.js';
1
+ import { getOrCreateContainer } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Hero component options
@@ -10,6 +10,8 @@ export interface HeroOptions {
10
10
  ctaLink?: string;
11
11
  backgroundImage?: string;
12
12
  variant?: 'default' | 'centered' | 'split';
13
+ style?: string;
14
+ class?: string;
13
15
  }
14
16
 
15
17
  /**
@@ -22,6 +24,8 @@ type HeroState = {
22
24
  ctaLink: string;
23
25
  backgroundImage: string;
24
26
  variant: string;
27
+ style: string;
28
+ class: string;
25
29
  };
26
30
 
27
31
  /**
@@ -35,22 +39,26 @@ type HeroState = {
35
39
  * });
36
40
  * hero.render();
37
41
  */
38
- export class Hero extends Reactive {
39
- state!: HeroState;
42
+ export class Hero {
43
+ state: HeroState;
40
44
  container: HTMLElement | null = null;
41
-
42
- constructor(componentId: string, options: HeroOptions = {}) {
43
- super();
44
- this._setComponentId(componentId);
45
-
46
- this.state = this._createReactiveState({
45
+ _id: string;
46
+ id: string;
47
+
48
+ constructor(id: string, options: HeroOptions = {}) {
49
+ this._id = id;
50
+ this.id = id;
51
+
52
+ this.state = {
47
53
  title: options.title ?? '',
48
54
  subtitle: options.subtitle ?? '',
49
55
  cta: options.cta ?? '',
50
56
  ctaLink: options.ctaLink ?? '#',
51
57
  backgroundImage: options.backgroundImage ?? '',
52
- variant: options.variant ?? 'default'
53
- }) as HeroState;
58
+ variant: options.variant ?? 'default',
59
+ style: options.style ?? '',
60
+ class: options.class ?? ''
61
+ };
54
62
  }
55
63
 
56
64
  /* -------------------------
@@ -87,13 +95,23 @@ export class Hero extends Reactive {
87
95
  return this;
88
96
  }
89
97
 
98
+ style(value: string): this {
99
+ this.state.style = value;
100
+ return this;
101
+ }
102
+
103
+ class(value: string): this {
104
+ this.state.class = value;
105
+ return this;
106
+ }
107
+
90
108
  /* -------------------------
91
109
  * Render
92
110
  * ------------------------- */
93
111
 
94
112
  render(targetId?: string): this {
95
113
  let container: HTMLElement;
96
-
114
+
97
115
  if (targetId) {
98
116
  const target = document.querySelector(targetId);
99
117
  if (!target || !(target instanceof HTMLElement)) {
@@ -101,37 +119,45 @@ export class Hero extends Reactive {
101
119
  }
102
120
  container = target;
103
121
  } else {
104
- container = getOrCreateContainer(this._componentId) as HTMLElement;
122
+ container = getOrCreateContainer(this._id);
105
123
  }
106
-
124
+
107
125
  this.container = container;
108
- const { title, subtitle, cta, ctaLink, backgroundImage, variant } = this.state;
109
-
126
+ const { title, subtitle, cta, ctaLink, backgroundImage, variant, style, class: className } = this.state;
127
+
110
128
  const hero = document.createElement('div');
111
129
  hero.className = `jux-hero jux-hero-${variant}`;
112
- hero.id = this._componentId;
113
-
130
+ hero.id = this._id;
131
+
132
+ if (className) {
133
+ hero.className += ` ${className}`;
134
+ }
135
+
136
+ if (style) {
137
+ hero.setAttribute('style', style);
138
+ }
139
+
114
140
  if (backgroundImage) {
115
141
  hero.style.backgroundImage = `url(${backgroundImage})`;
116
142
  }
117
-
143
+
118
144
  const content = document.createElement('div');
119
145
  content.className = 'jux-hero-content';
120
-
146
+
121
147
  if (title) {
122
148
  const titleEl = document.createElement('h1');
123
149
  titleEl.className = 'jux-hero-title';
124
150
  titleEl.textContent = title;
125
151
  content.appendChild(titleEl);
126
152
  }
127
-
153
+
128
154
  if (subtitle) {
129
155
  const subtitleEl = document.createElement('p');
130
156
  subtitleEl.className = 'jux-hero-subtitle';
131
157
  subtitleEl.textContent = subtitle;
132
158
  content.appendChild(subtitleEl);
133
159
  }
134
-
160
+
135
161
  if (cta) {
136
162
  const ctaEl = document.createElement('a');
137
163
  ctaEl.className = 'jux-hero-cta jux-button jux-button-primary';
@@ -139,10 +165,10 @@ export class Hero extends Reactive {
139
165
  ctaEl.textContent = cta;
140
166
  content.appendChild(ctaEl);
141
167
  }
142
-
168
+
143
169
  hero.appendChild(content);
144
170
  container.appendChild(hero);
145
-
171
+
146
172
  return this;
147
173
  }
148
174
 
@@ -153,18 +179,18 @@ export class Hero extends Reactive {
153
179
  if (!juxComponent || typeof juxComponent !== 'object') {
154
180
  throw new Error('Hero.renderTo: Invalid component - not an object');
155
181
  }
156
-
157
- if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
158
- throw new Error('Hero.renderTo: Invalid component - missing _componentId (not a Jux component)');
182
+
183
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
184
+ throw new Error('Hero.renderTo: Invalid component - missing _id (not a Jux component)');
159
185
  }
160
-
161
- return this.render(`#${juxComponent._componentId}`);
186
+
187
+ return this.render(`#${juxComponent._id}`);
162
188
  }
163
189
  }
164
190
 
165
191
  /**
166
192
  * Factory helper
167
193
  */
168
- export function hero(componentId: string, options: HeroOptions = {}): Hero {
169
- return new Hero(componentId, options);
194
+ export function hero(id: string, options: HeroOptions = {}): Hero {
195
+ return new Hero(id, options);
170
196
  }
@@ -0,0 +1,292 @@
1
+ import { ErrorHandler } from './error-handler.js';
2
+
3
+ /**
4
+ * Include - Include external resources (CSS, JS, images, fonts, etc.)
5
+ * Auto-detects resource type from URL and provides simple, fluent API
6
+ */
7
+
8
+ type IncludeType = 'stylesheet' | 'script' | 'image' | 'font' | 'preload' | 'prefetch' | 'module';
9
+ type IncludeLocation = 'head' | 'body-start' | 'body-end';
10
+
11
+ interface IncludeOptions {
12
+ as?: string;
13
+ crossOrigin?: 'anonymous' | 'use-credentials';
14
+ integrity?: string;
15
+ async?: boolean;
16
+ defer?: boolean;
17
+ location?: IncludeLocation;
18
+ }
19
+
20
+ export class Include {
21
+ private url: string;
22
+ private type: IncludeType;
23
+ private options: IncludeOptions = {};
24
+ private element: HTMLElement | null = null;
25
+
26
+ constructor(urlOrFile: string) {
27
+ this.url = urlOrFile;
28
+ this.type = this.detectType(urlOrFile);
29
+ }
30
+
31
+ /* -------------------------
32
+ * Type Detection
33
+ * ------------------------- */
34
+
35
+ private detectType(url: string): IncludeType {
36
+ if (url.endsWith('.css')) return 'stylesheet';
37
+ if (url.endsWith('.js') || url.endsWith('.mjs')) return 'script';
38
+ if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)) return 'image';
39
+ if (url.match(/\.(woff|woff2|ttf|otf|eot)$/i)) return 'font';
40
+ return 'preload';
41
+ }
42
+
43
+ /* -------------------------
44
+ * Fluent Type Setters
45
+ * ------------------------- */
46
+
47
+ withCss(): this {
48
+ this.type = 'stylesheet';
49
+ return this;
50
+ }
51
+
52
+ withJs(options?: { async?: boolean; defer?: boolean }): this {
53
+ this.type = 'script';
54
+ if (options?.async) this.options.async = true;
55
+ if (options?.defer) this.options.defer = true;
56
+ return this;
57
+ }
58
+
59
+ withModule(): this {
60
+ this.type = 'module';
61
+ return this;
62
+ }
63
+
64
+ withImage(): this {
65
+ this.type = 'image';
66
+ return this;
67
+ }
68
+
69
+ withFont(): this {
70
+ this.type = 'font';
71
+ return this;
72
+ }
73
+
74
+ withPreload(as?: string): this {
75
+ this.type = 'preload';
76
+ if (as) this.options.as = as;
77
+ return this;
78
+ }
79
+
80
+ withPrefetch(): this {
81
+ this.type = 'prefetch';
82
+ return this;
83
+ }
84
+
85
+ /* -------------------------
86
+ * Options
87
+ * ------------------------- */
88
+
89
+ with(options: IncludeOptions): this {
90
+ this.options = { ...this.options, ...options };
91
+ return this;
92
+ }
93
+
94
+ inHead(): this {
95
+ this.options.location = 'head';
96
+ return this;
97
+ }
98
+
99
+ inBody(): this {
100
+ this.options.location = 'body-end';
101
+ return this;
102
+ }
103
+
104
+ async(): this {
105
+ this.options.async = true;
106
+ return this;
107
+ }
108
+
109
+ defer(): this {
110
+ this.options.defer = true;
111
+ return this;
112
+ }
113
+
114
+ crossOrigin(value: 'anonymous' | 'use-credentials' = 'anonymous'): this {
115
+ this.options.crossOrigin = value;
116
+ return this;
117
+ }
118
+
119
+ integrity(hash: string): this {
120
+ this.options.integrity = hash;
121
+ return this;
122
+ }
123
+
124
+ /* -------------------------
125
+ * Render
126
+ * ------------------------- */
127
+
128
+ render(): this {
129
+ if (typeof document === 'undefined') return this;
130
+
131
+ try {
132
+ this.remove();
133
+
134
+ let element: HTMLElement;
135
+
136
+ switch (this.type) {
137
+ case 'stylesheet':
138
+ element = this.createStylesheet();
139
+ break;
140
+ case 'script':
141
+ case 'module':
142
+ element = this.createScript();
143
+ break;
144
+ case 'image':
145
+ case 'font':
146
+ case 'preload':
147
+ case 'prefetch':
148
+ element = this.createLink();
149
+ break;
150
+ default:
151
+ throw new Error(`Unknown include type: ${this.type}`);
152
+ }
153
+
154
+ const container = this.getContainer();
155
+ const location = this.options.location || 'head';
156
+
157
+ if (location === 'body-end') {
158
+ container.appendChild(element);
159
+ } else {
160
+ container.insertBefore(element, container.firstChild);
161
+ }
162
+
163
+ this.element = element;
164
+ } catch (error: any) {
165
+ ErrorHandler.captureError({
166
+ component: 'Include',
167
+ method: 'render',
168
+ message: error.message,
169
+ stack: error.stack,
170
+ timestamp: new Date(),
171
+ context: {
172
+ type: this.type,
173
+ url: this.url,
174
+ location: this.options.location,
175
+ error: 'runtime_exception'
176
+ }
177
+ });
178
+ }
179
+
180
+ return this;
181
+ }
182
+
183
+ /* -------------------------
184
+ * Element Creation
185
+ * ------------------------- */
186
+
187
+ private createStylesheet(): HTMLLinkElement {
188
+ const link = document.createElement('link');
189
+ link.rel = 'stylesheet';
190
+ link.href = this.url;
191
+
192
+ if (this.options.crossOrigin) link.crossOrigin = this.options.crossOrigin;
193
+ if (this.options.integrity) link.integrity = this.options.integrity;
194
+
195
+ link.onload = () => console.log(`✓ Stylesheet loaded: ${this.url}`);
196
+ link.onerror = () => this.handleError('stylesheet');
197
+
198
+ return link;
199
+ }
200
+
201
+ private createScript(): HTMLScriptElement {
202
+ const script = document.createElement('script');
203
+ script.src = this.url;
204
+
205
+ if (this.type === 'module') script.type = 'module';
206
+ if (this.options.async) script.async = true;
207
+ if (this.options.defer) script.defer = true;
208
+ if (this.options.crossOrigin) script.crossOrigin = this.options.crossOrigin;
209
+ if (this.options.integrity) script.integrity = this.options.integrity;
210
+
211
+ script.onload = () => console.log(`✓ Script loaded: ${this.url}`);
212
+ script.onerror = () => this.handleError('script');
213
+
214
+ return script;
215
+ }
216
+
217
+ private createLink(): HTMLLinkElement {
218
+ const link = document.createElement('link');
219
+
220
+ // Set rel based on type
221
+ if (this.type === 'prefetch') {
222
+ link.rel = 'prefetch';
223
+ } else {
224
+ link.rel = 'preload';
225
+ }
226
+
227
+ link.href = this.url;
228
+
229
+ // Set 'as' attribute
230
+ if (this.type === 'image') {
231
+ link.as = 'image';
232
+ } else if (this.type === 'font') {
233
+ link.as = 'font';
234
+ link.crossOrigin = this.options.crossOrigin || 'anonymous';
235
+ } else if (this.options.as) {
236
+ link.as = this.options.as;
237
+ }
238
+
239
+ if (this.options.crossOrigin && this.type !== 'font') {
240
+ link.crossOrigin = this.options.crossOrigin;
241
+ }
242
+ if (this.options.integrity) {
243
+ link.integrity = this.options.integrity;
244
+ }
245
+
246
+ return link;
247
+ }
248
+
249
+ /* -------------------------
250
+ * Helpers
251
+ * ------------------------- */
252
+
253
+ private getContainer(): HTMLElement {
254
+ const location = this.options.location || 'head';
255
+ return location === 'head' ? document.head : document.body;
256
+ }
257
+
258
+ private handleError(type: string): void {
259
+ const error = new Error(`Failed to load ${type}: ${this.url}`);
260
+ ErrorHandler.captureError({
261
+ component: 'Include',
262
+ method: `create${type.charAt(0).toUpperCase() + type.slice(1)}`,
263
+ message: error.message,
264
+ timestamp: new Date(),
265
+ context: { url: this.url, type, error: 'load_failed' }
266
+ });
267
+ }
268
+
269
+ remove(): this {
270
+ if (this.element?.parentNode) {
271
+ this.element.parentNode.removeChild(this.element);
272
+ this.element = null;
273
+ }
274
+ return this;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Factory function - auto-detects type and renders immediately
280
+ *
281
+ * Usage:
282
+ * jux.include('styles.css');
283
+ * jux.include('script.js').async();
284
+ * jux.include('app.mjs').withModule();
285
+ * jux.include('custom.js').withJs({ async: true, defer: true });
286
+ * jux.include('https://cdn.com/lib.js').inHead().defer();
287
+ */
288
+ export function include(urlOrFile: string): Include {
289
+ const imp = new Include(urlOrFile);
290
+ imp.render();
291
+ return imp;
292
+ }