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,300 @@
1
+ /**
2
+ * Request information and utilities
3
+ * Provides access to URL, query params, path, referrer, etc.
4
+ */
5
+
6
+ export interface RequestInfo {
7
+ url: string;
8
+ path: string;
9
+ query: Record<string, string>;
10
+ params: Record<string, string>;
11
+ hash: string;
12
+ referrer: string;
13
+ method: string;
14
+ headers: Record<string, string>;
15
+ }
16
+
17
+ export class Req {
18
+ private static _instance: Req | null = null;
19
+ private _info: RequestInfo;
20
+
21
+ private constructor() {
22
+ this._info = this._parseRequest();
23
+ }
24
+
25
+ /**
26
+ * Singleton instance
27
+ */
28
+ static get instance(): Req {
29
+ if (!Req._instance) {
30
+ Req._instance = new Req();
31
+ }
32
+ return Req._instance;
33
+ }
34
+
35
+ /**
36
+ * Parse current request information
37
+ */
38
+ private _parseRequest(): RequestInfo {
39
+ const url = new URL(window.location.href);
40
+
41
+ // Parse query string
42
+ const query: Record<string, string> = {};
43
+ url.searchParams.forEach((value, key) => {
44
+ query[key] = value;
45
+ });
46
+
47
+ // Parse path segments as params (basic routing)
48
+ const pathSegments = url.pathname.split('/').filter(s => s);
49
+ const params: Record<string, string> = {};
50
+ pathSegments.forEach((segment, index) => {
51
+ params[`${index}`] = segment;
52
+ });
53
+
54
+ return {
55
+ url: url.href,
56
+ path: url.pathname,
57
+ query,
58
+ params,
59
+ hash: url.hash.slice(1), // Remove leading #
60
+ referrer: document.referrer,
61
+ method: 'GET', // Browser requests are always GET initially
62
+ headers: {
63
+ 'user-agent': navigator.userAgent,
64
+ 'accept-language': navigator.language
65
+ }
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Get full URL
71
+ */
72
+ get url(): string {
73
+ return this._info.url;
74
+ }
75
+
76
+ /**
77
+ * Get current path (e.g., "/examples/sample/dashboards")
78
+ */
79
+ get path(): string {
80
+ return this._info.path;
81
+ }
82
+
83
+ /**
84
+ * Get query string parameters
85
+ */
86
+ get query(): Record<string, string> {
87
+ return { ...this._info.query };
88
+ }
89
+
90
+ /**
91
+ * Get path parameters
92
+ */
93
+ get params(): Record<string, string> {
94
+ return { ...this._info.params };
95
+ }
96
+
97
+ /**
98
+ * Get URL hash (without #)
99
+ */
100
+ get hash(): string {
101
+ return this._info.hash;
102
+ }
103
+
104
+ /**
105
+ * Get referrer URL
106
+ */
107
+ get referrer(): string {
108
+ return this._info.referrer;
109
+ }
110
+
111
+ /**
112
+ * Get request method (always GET for browser)
113
+ */
114
+ get method(): string {
115
+ return this._info.method;
116
+ }
117
+
118
+ /**
119
+ * Get request headers
120
+ */
121
+ get headers(): Record<string, string> {
122
+ return { ...this._info.headers };
123
+ }
124
+
125
+ /**
126
+ * Check if current path matches a pattern
127
+ * @param pattern - Path pattern (supports wildcards)
128
+ *
129
+ * Examples:
130
+ * req.matches('/examples/sample/*')
131
+ * req.matches('/examples/sample/dashboards')
132
+ * req.matches('/examples/*\/dashboards')
133
+ */
134
+ matches(pattern: string): boolean {
135
+ const regexPattern = pattern
136
+ .replace(/\*/g, '.*')
137
+ .replace(/\//g, '\\/');
138
+ const regex = new RegExp(`^${regexPattern}$`);
139
+ return regex.test(this.path);
140
+ }
141
+
142
+ /**
143
+ * Check if current path starts with prefix
144
+ */
145
+ startsWith(prefix: string): boolean {
146
+ return this.path.startsWith(prefix);
147
+ }
148
+
149
+ /**
150
+ * Check if current path ends with suffix
151
+ */
152
+ endsWith(suffix: string): boolean {
153
+ return this.path.endsWith(suffix);
154
+ }
155
+
156
+ /**
157
+ * Get query parameter value
158
+ */
159
+ getQuery(key: string, defaultValue?: string): string | undefined {
160
+ return this.query[key] ?? defaultValue;
161
+ }
162
+
163
+ /**
164
+ * Get path parameter value
165
+ */
166
+ getParam(key: string, defaultValue?: string): string | undefined {
167
+ return this.params[key] ?? defaultValue;
168
+ }
169
+
170
+ /**
171
+ * Check if query parameter exists
172
+ */
173
+ hasQuery(key: string): boolean {
174
+ return key in this.query;
175
+ }
176
+
177
+ /**
178
+ * Get all query parameters as URLSearchParams
179
+ */
180
+ getSearchParams(): URLSearchParams {
181
+ return new URLSearchParams(this.query);
182
+ }
183
+
184
+ /**
185
+ * Refresh request info (call after navigation)
186
+ */
187
+ refresh(): void {
188
+ this._info = this._parseRequest();
189
+ }
190
+
191
+ /**
192
+ * Set active state on nav items based on current path
193
+ * @param items - Nav items to update
194
+ * @returns Updated nav items with active state set
195
+ */
196
+ setActiveNavItems(items: Array<{ href: string; active?: boolean }>): Array<{ href: string; active: boolean }> {
197
+ return items.map(item => ({
198
+ ...item,
199
+ active: this.isActiveNavItem(item.href)
200
+ }));
201
+ }
202
+
203
+ /**
204
+ * Check if a nav item should be active based on current path
205
+ * @param href - The nav item's href
206
+ */
207
+ isActiveNavItem(href: string): boolean {
208
+ // Exact match
209
+ if (href === this.path) {
210
+ return true;
211
+ }
212
+
213
+ // Starts with match (for parent routes)
214
+ // e.g., "/examples/sample" matches "/examples/sample/dashboards"
215
+ if (href !== '/' && this.path.startsWith(href)) {
216
+ return true;
217
+ }
218
+
219
+ return false;
220
+ }
221
+
222
+ /**
223
+ * Get breadcrumbs from current path
224
+ * @returns Array of breadcrumb objects with label and href
225
+ */
226
+ getBreadcrumbs(): Array<{ label: string; href: string }> {
227
+ const segments = this.path.split('/').filter(s => s);
228
+ const breadcrumbs: Array<{ label: string; href: string }> = [
229
+ { label: 'Home', href: '/' }
230
+ ];
231
+
232
+ let currentPath = '';
233
+ segments.forEach(segment => {
234
+ currentPath += `/${segment}`;
235
+ breadcrumbs.push({
236
+ label: this._formatBreadcrumbLabel(segment),
237
+ href: currentPath
238
+ });
239
+ });
240
+
241
+ return breadcrumbs;
242
+ }
243
+
244
+ /**
245
+ * Format segment into readable breadcrumb label
246
+ */
247
+ private _formatBreadcrumbLabel(segment: string): string {
248
+ return segment
249
+ .split('-')
250
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
251
+ .join(' ');
252
+ }
253
+
254
+ /**
255
+ * Check if this is the home/root path
256
+ */
257
+ get isHome(): boolean {
258
+ return this.path === '/' || this.path === '';
259
+ }
260
+
261
+ /**
262
+ * Get current domain
263
+ */
264
+ get domain(): string {
265
+ return window.location.hostname;
266
+ }
267
+
268
+ /**
269
+ * Get current port
270
+ */
271
+ get port(): string {
272
+ return window.location.port;
273
+ }
274
+
275
+ /**
276
+ * Get current protocol
277
+ */
278
+ get protocol(): string {
279
+ return window.location.protocol.replace(':', '');
280
+ }
281
+
282
+ /**
283
+ * Check if HTTPS
284
+ */
285
+ get isSecure(): boolean {
286
+ return this.protocol === 'https';
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Global request instance
292
+ */
293
+ export const req = Req.instance;
294
+
295
+ /**
296
+ * Factory function (returns singleton)
297
+ */
298
+ export function request(): Req {
299
+ return Req.instance;
300
+ }
@@ -1,32 +1,20 @@
1
1
  import { ErrorHandler } from './error-handler.js';
2
2
 
3
3
  /**
4
- * Script - Inject JavaScript into the document
4
+ * Script - Inject inline JavaScript into the document
5
+ * For external scripts, use Import component instead
5
6
  * Auto-renders when created or modified
6
7
  */
7
8
  export class Script {
8
9
  private _content: string = '';
9
- private _src: string = '';
10
- private _isSrc: boolean = false;
11
- private _async: boolean = false;
12
- private _defer: boolean = false;
13
10
  private _type: string = '';
14
11
  private _element: HTMLScriptElement | null = null;
15
12
 
16
- constructor(contentOrSrc: string = '') {
17
- // Detect if it's a URL or inline script
18
- if (contentOrSrc.trim().startsWith('http') ||
19
- contentOrSrc.endsWith('.js') ||
20
- contentOrSrc.includes('/')) {
21
- this._src = contentOrSrc;
22
- this._isSrc = true;
23
- } else {
24
- this._content = contentOrSrc;
25
- this._isSrc = false;
26
- }
27
-
28
- // Auto-render if content/src provided
29
- if (contentOrSrc) {
13
+ constructor(content: string = '') {
14
+ this._content = content;
15
+
16
+ // Auto-render if content provided
17
+ if (content) {
30
18
  this.render();
31
19
  }
32
20
  }
@@ -36,52 +24,30 @@ export class Script {
36
24
  */
37
25
  content(js: string): this {
38
26
  this._content = js;
39
- this._isSrc = false;
40
- this._src = '';
41
27
  this.render();
42
28
  return this;
43
29
  }
44
30
 
45
31
  /**
46
- * Set external script URL
47
- */
48
- src(url: string): this {
49
- this._src = url;
50
- this._isSrc = true;
51
- this._content = '';
52
- this.render();
53
- return this;
54
- }
55
-
56
- /**
57
- * Enable async loading (for external scripts)
58
- */
59
- async(value: boolean = true): this {
60
- this._async = value;
61
- this.render();
62
- return this;
63
- }
64
-
65
- /**
66
- * Enable defer loading (for external scripts)
32
+ * Set script type (e.g., 'module', 'text/javascript')
67
33
  */
68
- defer(value: boolean = true): this {
69
- this._defer = value;
34
+ type(value: string): this {
35
+ this._type = value;
70
36
  this.render();
71
37
  return this;
72
38
  }
73
39
 
74
40
  /**
75
- * Set script type (e.g., 'module', 'text/javascript')
41
+ * Set as module script
76
42
  */
77
- type(value: string): this {
78
- this._type = value;
43
+ module(): this {
44
+ this._type = 'module';
79
45
  this.render();
80
46
  return this;
81
47
  }
82
48
 
83
49
  /**
84
- * Render the script element
50
+ * Render the inline script element
85
51
  */
86
52
  render(): this {
87
53
  if (typeof document === 'undefined') {
@@ -93,34 +59,16 @@ export class Script {
93
59
  this.remove();
94
60
 
95
61
  const script = document.createElement('script');
62
+ script.textContent = this._content;
96
63
 
97
- if (this._isSrc) {
98
- script.src = this._src;
99
-
100
- // Add error handler for failed loads
101
- script.onerror = () => {
102
- ErrorHandler.captureError({
103
- component: 'Script',
104
- method: 'render',
105
- message: `Failed to load script: ${this._src}`,
106
- timestamp: new Date(),
107
- context: { src: this._src, type: 'external', error: 'load_failed' }
108
- });
109
- };
110
-
111
- script.onload = () => {
112
- console.log(`✓ Script loaded: ${this._src}`);
113
- };
114
- } else {
115
- script.textContent = this._content;
64
+ if (this._type) {
65
+ script.type = this._type;
116
66
  }
117
67
 
118
- if (this._async) script.async = true;
119
- if (this._defer) script.defer = true;
120
- if (this._type) script.type = this._type;
121
-
122
68
  document.head.appendChild(script);
123
69
  this._element = script;
70
+
71
+ console.log('✓ Inline script rendered');
124
72
  } catch (error: any) {
125
73
  ErrorHandler.captureError({
126
74
  component: 'Script',
@@ -128,9 +76,8 @@ export class Script {
128
76
  message: error.message,
129
77
  stack: error.stack,
130
78
  timestamp: new Date(),
131
- context: {
132
- isSrc: this._isSrc,
133
- src: this._src,
79
+ context: {
80
+ type: this._type || 'inline',
134
81
  error: 'runtime_exception'
135
82
  }
136
83
  });
@@ -149,4 +96,16 @@ export class Script {
149
96
  }
150
97
  return this;
151
98
  }
99
+ }
100
+
101
+ /**
102
+ * Factory function
103
+ *
104
+ * Usage:
105
+ * jux.script('console.log("Hello")');
106
+ * jux.script().content('alert("Hi")');
107
+ * jux.script('export const x = 1;').module();
108
+ */
109
+ export function script(content: string = ''): Script {
110
+ return new Script(content);
152
111
  }
@@ -0,0 +1,247 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
3
+
4
+ /**
5
+ * Select option
6
+ */
7
+ export interface SelectOption {
8
+ label: string;
9
+ value: string;
10
+ disabled?: boolean;
11
+ }
12
+
13
+ /**
14
+ * Select component options
15
+ */
16
+ export interface SelectOptions {
17
+ options?: SelectOption[];
18
+ value?: string;
19
+ placeholder?: string;
20
+ disabled?: boolean;
21
+ name?: string;
22
+ onChange?: (value: string) => void;
23
+ style?: string;
24
+ class?: string;
25
+ }
26
+
27
+ /**
28
+ * Select component state
29
+ */
30
+ type SelectState = {
31
+ options: SelectOption[];
32
+ value: string;
33
+ placeholder: string;
34
+ disabled: boolean;
35
+ name: string;
36
+ style: string;
37
+ class: string;
38
+ };
39
+
40
+ /**
41
+ * Select component - Dropdown select
42
+ *
43
+ * Usage:
44
+ * jux.select('country', {
45
+ * placeholder: 'Select country',
46
+ * options: [
47
+ * { label: 'USA', value: 'us' },
48
+ * { label: 'Canada', value: 'ca' }
49
+ * ],
50
+ * onChange: (val) => console.log(val)
51
+ * }).render('#form');
52
+ *
53
+ * // Two-way binding
54
+ * const countryState = state('us');
55
+ * jux.select('country').bind(countryState).render('#form');
56
+ */
57
+ export class Select {
58
+ state: SelectState;
59
+ container: HTMLElement | null = null;
60
+ _id: string;
61
+ id: string;
62
+ private _onChange?: (value: string) => void;
63
+ private _boundState?: State<string>;
64
+
65
+ constructor(id: string, options: SelectOptions = {}) {
66
+ this._id = id;
67
+ this.id = id;
68
+ this._onChange = options.onChange;
69
+
70
+ this.state = {
71
+ options: options.options ?? [],
72
+ value: options.value ?? '',
73
+ placeholder: options.placeholder ?? 'Select...',
74
+ disabled: options.disabled ?? false,
75
+ name: options.name ?? id,
76
+ style: options.style ?? '',
77
+ class: options.class ?? ''
78
+ };
79
+ }
80
+
81
+ /* -------------------------
82
+ * Fluent API
83
+ * ------------------------- */
84
+
85
+ options(value: SelectOption[]): this {
86
+ this.state.options = value;
87
+ return this;
88
+ }
89
+
90
+ addOption(option: SelectOption): this {
91
+ this.state.options = [...this.state.options, option];
92
+ return this;
93
+ }
94
+
95
+ value(value: string): this {
96
+ this.state.value = value;
97
+ this._updateElement();
98
+ return this;
99
+ }
100
+
101
+ placeholder(value: string): this {
102
+ this.state.placeholder = value;
103
+ return this;
104
+ }
105
+
106
+ disabled(value: boolean): this {
107
+ this.state.disabled = value;
108
+ this._updateElement();
109
+ return this;
110
+ }
111
+
112
+ name(value: string): this {
113
+ this.state.name = value;
114
+ return this;
115
+ }
116
+
117
+ style(value: string): this {
118
+ this.state.style = value;
119
+ return this;
120
+ }
121
+
122
+ class(value: string): this {
123
+ this.state.class = value;
124
+ return this;
125
+ }
126
+
127
+ onChange(handler: (value: string) => void): this {
128
+ this._onChange = handler;
129
+ return this;
130
+ }
131
+
132
+ /**
133
+ * Two-way binding to state
134
+ */
135
+ bind(stateObj: State<string>): this {
136
+ this._boundState = stateObj;
137
+
138
+ // Update select when state changes
139
+ stateObj.subscribe((val) => {
140
+ this.state.value = val;
141
+ this._updateElement();
142
+ });
143
+
144
+ // Update state when select changes
145
+ this.onChange((value) => stateObj.set(value));
146
+
147
+ return this;
148
+ }
149
+
150
+ /* -------------------------
151
+ * Helpers
152
+ * ------------------------- */
153
+
154
+ private _updateElement(): void {
155
+ const select = document.getElementById(`${this._id}-select`) as HTMLSelectElement;
156
+ if (select) {
157
+ select.value = this.state.value;
158
+ select.disabled = this.state.disabled;
159
+ }
160
+ }
161
+
162
+ getValue(): string {
163
+ return this.state.value;
164
+ }
165
+
166
+ /* -------------------------
167
+ * Render
168
+ * ------------------------- */
169
+
170
+ render(targetId?: string): this {
171
+ let container: HTMLElement;
172
+
173
+ if (targetId) {
174
+ const target = document.querySelector(targetId);
175
+ if (!target || !(target instanceof HTMLElement)) {
176
+ throw new Error(`Select: Target element "${targetId}" not found`);
177
+ }
178
+ container = target;
179
+ } else {
180
+ container = getOrCreateContainer(this._id);
181
+ }
182
+
183
+ this.container = container;
184
+ const { options, value, placeholder, disabled, name, style, class: className } = this.state;
185
+
186
+ const wrapper = document.createElement('div');
187
+ wrapper.className = 'jux-select';
188
+ wrapper.id = this._id;
189
+
190
+ if (className) {
191
+ wrapper.className += ` ${className}`;
192
+ }
193
+
194
+ if (style) {
195
+ wrapper.setAttribute('style', style);
196
+ }
197
+
198
+ const select = document.createElement('select');
199
+ select.className = 'jux-select-element';
200
+ select.id = `${this._id}-select`;
201
+ select.name = name;
202
+ select.disabled = disabled;
203
+
204
+ // Placeholder option
205
+ if (placeholder) {
206
+ const placeholderOpt = document.createElement('option');
207
+ placeholderOpt.value = '';
208
+ placeholderOpt.textContent = placeholder;
209
+ placeholderOpt.disabled = true;
210
+ placeholderOpt.selected = value === '';
211
+ select.appendChild(placeholderOpt);
212
+ }
213
+
214
+ // Options
215
+ options.forEach(opt => {
216
+ const option = document.createElement('option');
217
+ option.value = opt.value;
218
+ option.textContent = opt.label;
219
+ option.disabled = opt.disabled ?? false;
220
+ option.selected = opt.value === value;
221
+ select.appendChild(option);
222
+ });
223
+
224
+ select.addEventListener('change', (e) => {
225
+ const target = e.target as HTMLSelectElement;
226
+ this.state.value = target.value;
227
+ if (this._onChange) {
228
+ this._onChange(target.value);
229
+ }
230
+ });
231
+
232
+ wrapper.appendChild(select);
233
+ container.appendChild(wrapper);
234
+ return this;
235
+ }
236
+
237
+ renderTo(juxComponent: any): this {
238
+ if (!juxComponent?._id) {
239
+ throw new Error('Select.renderTo: Invalid component');
240
+ }
241
+ return this.render(`#${juxComponent._id}`);
242
+ }
243
+ }
244
+
245
+ export function select(id: string, options: SelectOptions = {}): Select {
246
+ return new Select(id, options);
247
+ }