juxscript 1.0.0

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 (61) hide show
  1. package/README.md +292 -0
  2. package/bin/cli.js +149 -0
  3. package/lib/adapters/base-adapter.js +35 -0
  4. package/lib/adapters/index.js +33 -0
  5. package/lib/adapters/mysql-adapter.js +65 -0
  6. package/lib/adapters/postgres-adapter.js +70 -0
  7. package/lib/adapters/sqlite-adapter.js +56 -0
  8. package/lib/components/app.ts +124 -0
  9. package/lib/components/button.ts +136 -0
  10. package/lib/components/card.ts +205 -0
  11. package/lib/components/chart.ts +125 -0
  12. package/lib/components/code.ts +242 -0
  13. package/lib/components/container.ts +282 -0
  14. package/lib/components/data.ts +105 -0
  15. package/lib/components/docs-data.json +1211 -0
  16. package/lib/components/error-handler.ts +285 -0
  17. package/lib/components/footer.ts +146 -0
  18. package/lib/components/header.ts +167 -0
  19. package/lib/components/hero.ts +170 -0
  20. package/lib/components/import.ts +430 -0
  21. package/lib/components/input.ts +175 -0
  22. package/lib/components/layout.ts +113 -0
  23. package/lib/components/list.ts +392 -0
  24. package/lib/components/main.ts +111 -0
  25. package/lib/components/menu.ts +170 -0
  26. package/lib/components/modal.ts +216 -0
  27. package/lib/components/nav.ts +136 -0
  28. package/lib/components/node.ts +200 -0
  29. package/lib/components/reactivity.js +104 -0
  30. package/lib/components/script.ts +152 -0
  31. package/lib/components/sidebar.ts +168 -0
  32. package/lib/components/style.ts +129 -0
  33. package/lib/components/table.ts +279 -0
  34. package/lib/components/tabs.ts +191 -0
  35. package/lib/components/theme.ts +97 -0
  36. package/lib/components/view.ts +174 -0
  37. package/lib/jux.ts +203 -0
  38. package/lib/layouts/default.css +260 -0
  39. package/lib/layouts/default.jux +8 -0
  40. package/lib/layouts/figma.css +334 -0
  41. package/lib/layouts/figma.jux +0 -0
  42. package/lib/layouts/notion.css +258 -0
  43. package/lib/styles/base-theme.css +186 -0
  44. package/lib/styles/dark-theme.css +144 -0
  45. package/lib/styles/global.css +1131 -0
  46. package/lib/styles/light-theme.css +144 -0
  47. package/lib/styles/tokens/dark.css +86 -0
  48. package/lib/styles/tokens/light.css +86 -0
  49. package/lib/themes/dark.css +86 -0
  50. package/lib/themes/light.css +86 -0
  51. package/lib/utils/path-resolver.js +23 -0
  52. package/machinery/compiler.js +262 -0
  53. package/machinery/doc-generator.js +160 -0
  54. package/machinery/generators/css.js +128 -0
  55. package/machinery/generators/html.js +108 -0
  56. package/machinery/imports.js +155 -0
  57. package/machinery/server.js +185 -0
  58. package/machinery/validators/file-validator.js +123 -0
  59. package/machinery/watcher.js +148 -0
  60. package/package.json +58 -0
  61. package/types/globals.d.ts +16 -0
@@ -0,0 +1,200 @@
1
+ import { Reactive, getOrCreateContainer } from './reactivity.js';
2
+
3
+ /**
4
+ * Node component options
5
+ */
6
+ export interface NodeOptions {
7
+ tagType?: string;
8
+ className?: string;
9
+ textContent?: string;
10
+ innerHTML?: string;
11
+ attributes?: Record<string, string>;
12
+ styles?: Record<string, string>;
13
+ }
14
+
15
+ /**
16
+ * Node component state
17
+ */
18
+ type NodeState = {
19
+ tagType: string;
20
+ className: string;
21
+ textContent: string;
22
+ innerHTML: string;
23
+ attributes: Record<string, string>;
24
+ styles: Record<string, string>;
25
+ };
26
+
27
+ /**
28
+ * Node component - Create arbitrary HTML elements
29
+ *
30
+ * Usage:
31
+ * const div = jux.node('myDiv', { tagType: 'div', className: 'container' });
32
+ * div.render('#target');
33
+ *
34
+ * const span = jux.node('mySpan')
35
+ * .tagType('span')
36
+ * .className('highlight')
37
+ * .textContent('Hello World')
38
+ * .render();
39
+ */
40
+ export class Node extends Reactive {
41
+ state!: NodeState;
42
+ container: HTMLElement | null = null;
43
+
44
+ constructor(componentId: string, options: NodeOptions = {}) {
45
+ super();
46
+ this._setComponentId(componentId);
47
+
48
+ this.state = this._createReactiveState({
49
+ tagType: options.tagType ?? 'div',
50
+ className: options.className ?? '',
51
+ textContent: options.textContent ?? '',
52
+ innerHTML: options.innerHTML ?? '',
53
+ attributes: options.attributes ?? {},
54
+ styles: options.styles ?? {}
55
+ }) as NodeState;
56
+ }
57
+
58
+ /* -------------------------
59
+ * Fluent API
60
+ * ------------------------- */
61
+
62
+ tagType(value: string): this {
63
+ this.state.tagType = value;
64
+ return this;
65
+ }
66
+
67
+ className(value: string): this {
68
+ this.state.className = value;
69
+ return this;
70
+ }
71
+
72
+ addClass(value: string): this {
73
+ const classes = this.state.className.split(' ').filter(c => c);
74
+ if (!classes.includes(value)) {
75
+ classes.push(value);
76
+ this.state.className = classes.join(' ');
77
+ }
78
+ return this;
79
+ }
80
+
81
+ removeClass(value: string): this {
82
+ const classes = this.state.className.split(' ').filter(c => c && c !== value);
83
+ this.state.className = classes.join(' ');
84
+ return this;
85
+ }
86
+
87
+ textContent(value: string): this {
88
+ this.state.textContent = value;
89
+ this.state.innerHTML = ''; // Clear innerHTML when setting textContent
90
+ return this;
91
+ }
92
+
93
+ innerHTML(value: string): this {
94
+ this.state.innerHTML = value;
95
+ this.state.textContent = ''; // Clear textContent when setting innerHTML
96
+ return this;
97
+ }
98
+
99
+ attr(name: string, value: string): this {
100
+ this.state.attributes[name] = value;
101
+ return this;
102
+ }
103
+
104
+ attrs(attributes: Record<string, string>): this {
105
+ this.state.attributes = { ...this.state.attributes, ...attributes };
106
+ return this;
107
+ }
108
+
109
+ style(property: string, value: string): this {
110
+ this.state.styles[property] = value;
111
+ return this;
112
+ }
113
+
114
+ styles(styles: Record<string, string>): this {
115
+ this.state.styles = { ...this.state.styles, ...styles };
116
+ return this;
117
+ }
118
+
119
+ /* -------------------------
120
+ * Render
121
+ * ------------------------- */
122
+
123
+ render(targetId?: string): this {
124
+ let container: HTMLElement;
125
+
126
+ if (targetId) {
127
+ // Use provided targetId - must exist
128
+ const target = document.querySelector(targetId);
129
+ if (!target || !(target instanceof HTMLElement)) {
130
+ throw new Error(`Node: Target element "${targetId}" not found`);
131
+ }
132
+ container = target;
133
+ } else {
134
+ // Create or get container with component ID
135
+ container = getOrCreateContainer(this._componentId) as HTMLElement;
136
+ }
137
+
138
+ this.container = container;
139
+ const { tagType, className, textContent, innerHTML, attributes, styles } = this.state;
140
+
141
+ // Create the element
142
+ const element = document.createElement(tagType);
143
+
144
+ // Set ID to component ID
145
+ element.id = this._componentId;
146
+
147
+ // Set className
148
+ if (className) {
149
+ element.className = className;
150
+ }
151
+
152
+ // Set content (innerHTML takes precedence over textContent)
153
+ if (innerHTML) {
154
+ element.innerHTML = innerHTML;
155
+ } else if (textContent) {
156
+ element.textContent = textContent;
157
+ }
158
+
159
+ // Set attributes
160
+ Object.entries(attributes).forEach(([name, value]) => {
161
+ element.setAttribute(name, value);
162
+ });
163
+
164
+ // Set styles
165
+ Object.entries(styles).forEach(([property, value]) => {
166
+ element.style.setProperty(property, value);
167
+ });
168
+
169
+ container.appendChild(element);
170
+ return this;
171
+ }
172
+
173
+ /**
174
+ * Render to another Jux component's container
175
+ *
176
+ * Usage:
177
+ * const container = jux.node('myContainer');
178
+ * const child = jux.node('myChild').renderTo(container);
179
+ */
180
+ renderTo(juxComponent: any): this {
181
+ // Verify it's a Jux component (has _componentId from Reactive base)
182
+ if (!juxComponent || typeof juxComponent !== 'object') {
183
+ throw new Error('Node.renderTo: Invalid component - not an object');
184
+ }
185
+
186
+ if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
187
+ throw new Error('Node.renderTo: Invalid component - missing _componentId (not a Jux component)');
188
+ }
189
+
190
+ // Render to the component's ID as a selector
191
+ return this.render(`#${juxComponent._componentId}`);
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Factory helper
197
+ */
198
+ export function node(componentId: string, options: NodeOptions = {}): Node {
199
+ return new Node(componentId, options);
200
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Ultra-light reactivity system for Jux components
3
+ * Provides event-based state management with emit/on pattern
4
+ */
5
+
6
+ function getOrCreateContainer(componentId) {
7
+ if (typeof document === 'undefined') return null;
8
+
9
+ let container = document.getElementById(componentId);
10
+
11
+ // Container already exists, return it
12
+ if (container) {
13
+ return container;
14
+ }
15
+
16
+ // Auto-create container if it doesn't exist
17
+ container = document.createElement('div');
18
+ container.id = componentId;
19
+
20
+ // Find appropriate parent
21
+ let parent;
22
+ // Page components go inside #appmain (or [data-jux-page] if no layout)
23
+ const appmain = document.getElementById('appmain');
24
+ const dataJuxPage = document.querySelector('[data-jux-page]');
25
+ parent = appmain || dataJuxPage || document.body;
26
+ parent.appendChild(container);
27
+
28
+
29
+ return container;
30
+ }
31
+
32
+ class Reactive {
33
+ constructor() {
34
+ this._listeners = new Map();
35
+ this._state = {};
36
+ this._componentId = null;
37
+ }
38
+
39
+ /**
40
+ * Set the component ID and auto-wire the container getter
41
+ * Call this in component constructors: instance._setComponentId(componentId)
42
+ */
43
+ _setComponentId(componentId) {
44
+ this._componentId = componentId;
45
+ this.id = componentId;
46
+ }
47
+
48
+ _createReactiveState(initialState = {}) {
49
+ const self = this;
50
+ return new Proxy(initialState, {
51
+ set(target, property, value) {
52
+ const oldValue = target[property];
53
+ target[property] = value;
54
+
55
+ if (oldValue !== value) {
56
+ self.emit('stateChange', { property, value, oldValue });
57
+ }
58
+
59
+ return true;
60
+ }
61
+ });
62
+ }
63
+
64
+ on(event, handler) {
65
+ if (!this._listeners.has(event)) {
66
+ this._listeners.set(event, []);
67
+ }
68
+ this._listeners.get(event).push(handler);
69
+
70
+ return this;
71
+ }
72
+
73
+ emit(event, data) {
74
+ if (this._listeners.has(event)) {
75
+ this._listeners.get(event).forEach((handler, index) => {
76
+ try {
77
+ handler(data);
78
+ } catch (err) {
79
+ console.error(`[Reactive] Error in handler for '${event}':`, err);
80
+ }
81
+ });
82
+ }
83
+
84
+ return this;
85
+ }
86
+
87
+ off(event, handler) {
88
+ if (this._listeners.has(event)) {
89
+ const callbacks = this._listeners.get(event);
90
+ const index = callbacks.indexOf(handler);
91
+ if (index > -1) {
92
+ callbacks.splice(index, 1);
93
+ }
94
+ }
95
+
96
+ return this;
97
+ }
98
+
99
+ hasListeners(event) {
100
+ return this._listeners.has(event) && this._listeners.get(event).length > 0;
101
+ }
102
+ }
103
+
104
+ export { Reactive, getOrCreateContainer };
@@ -0,0 +1,152 @@
1
+ import { ErrorHandler } from './error-handler.js';
2
+
3
+ /**
4
+ * Script - Inject JavaScript into the document
5
+ * Auto-renders when created or modified
6
+ */
7
+ export class Script {
8
+ private _content: string = '';
9
+ private _src: string = '';
10
+ private _isSrc: boolean = false;
11
+ private _async: boolean = false;
12
+ private _defer: boolean = false;
13
+ private _type: string = '';
14
+ private _element: HTMLScriptElement | null = null;
15
+
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) {
30
+ this.render();
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Set inline JavaScript content
36
+ */
37
+ content(js: string): this {
38
+ this._content = js;
39
+ this._isSrc = false;
40
+ this._src = '';
41
+ this.render();
42
+ return this;
43
+ }
44
+
45
+ /**
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)
67
+ */
68
+ defer(value: boolean = true): this {
69
+ this._defer = value;
70
+ this.render();
71
+ return this;
72
+ }
73
+
74
+ /**
75
+ * Set script type (e.g., 'module', 'text/javascript')
76
+ */
77
+ type(value: string): this {
78
+ this._type = value;
79
+ this.render();
80
+ return this;
81
+ }
82
+
83
+ /**
84
+ * Render the script element
85
+ */
86
+ render(): this {
87
+ if (typeof document === 'undefined') {
88
+ return this;
89
+ }
90
+
91
+ try {
92
+ // Remove existing element if it exists
93
+ this.remove();
94
+
95
+ const script = document.createElement('script');
96
+
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;
116
+ }
117
+
118
+ if (this._async) script.async = true;
119
+ if (this._defer) script.defer = true;
120
+ if (this._type) script.type = this._type;
121
+
122
+ document.head.appendChild(script);
123
+ this._element = script;
124
+ } catch (error: any) {
125
+ ErrorHandler.captureError({
126
+ component: 'Script',
127
+ method: 'render',
128
+ message: error.message,
129
+ stack: error.stack,
130
+ timestamp: new Date(),
131
+ context: {
132
+ isSrc: this._isSrc,
133
+ src: this._src,
134
+ error: 'runtime_exception'
135
+ }
136
+ });
137
+ }
138
+
139
+ return this;
140
+ }
141
+
142
+ /**
143
+ * Remove the script from the document
144
+ */
145
+ remove(): this {
146
+ if (this._element && this._element.parentNode) {
147
+ this._element.parentNode.removeChild(this._element);
148
+ this._element = null;
149
+ }
150
+ return this;
151
+ }
152
+ }
@@ -0,0 +1,168 @@
1
+ import { Reactive, getOrCreateContainer } from './reactivity.js';
2
+
3
+ /**
4
+ * Sidebar component options
5
+ */
6
+ export interface SidebarOptions {
7
+ title?: string;
8
+ width?: string;
9
+ position?: 'left' | 'right';
10
+ collapsible?: boolean;
11
+ collapsed?: boolean;
12
+ }
13
+
14
+ /**
15
+ * Sidebar component state
16
+ */
17
+ type SidebarState = {
18
+ title: string;
19
+ width: string;
20
+ position: string;
21
+ collapsible: boolean;
22
+ collapsed: boolean;
23
+ };
24
+
25
+ /**
26
+ * Sidebar component
27
+ *
28
+ * Usage:
29
+ * const sidebar = jux.sidebar('mySidebar', {
30
+ * title: 'Navigation',
31
+ * width: '250px',
32
+ * position: 'left'
33
+ * });
34
+ * sidebar.render('#appsidebar');
35
+ */
36
+ export class Sidebar extends Reactive {
37
+ state!: SidebarState;
38
+ container: HTMLElement | null = null;
39
+
40
+ constructor(componentId: string, options: SidebarOptions = {}) {
41
+ super();
42
+ this._setComponentId(componentId);
43
+
44
+ this.state = this._createReactiveState({
45
+ title: options.title ?? '',
46
+ width: options.width ?? '250px',
47
+ position: options.position ?? 'left',
48
+ collapsible: options.collapsible ?? false,
49
+ collapsed: options.collapsed ?? false
50
+ }) as SidebarState;
51
+ }
52
+
53
+ /* -------------------------
54
+ * Fluent API
55
+ * ------------------------- */
56
+
57
+ title(value: string): this {
58
+ this.state.title = value;
59
+ return this;
60
+ }
61
+
62
+ width(value: string): this {
63
+ this.state.width = value;
64
+ return this;
65
+ }
66
+
67
+ position(value: 'left' | 'right'): this {
68
+ this.state.position = value;
69
+ return this;
70
+ }
71
+
72
+ collapsible(value: boolean): this {
73
+ this.state.collapsible = value;
74
+ return this;
75
+ }
76
+
77
+ collapsed(value: boolean): this {
78
+ this.state.collapsed = value;
79
+ return this;
80
+ }
81
+
82
+ toggle(): this {
83
+ this.state.collapsed = !this.state.collapsed;
84
+ return this;
85
+ }
86
+
87
+ /* -------------------------
88
+ * Render
89
+ * ------------------------- */
90
+
91
+ render(targetId?: string): this {
92
+ let container: HTMLElement;
93
+
94
+ if (targetId) {
95
+ const target = document.querySelector(targetId);
96
+ if (!target || !(target instanceof HTMLElement)) {
97
+ throw new Error(`Sidebar: Target element "${targetId}" not found`);
98
+ }
99
+ container = target;
100
+ } else {
101
+ container = getOrCreateContainer(this._componentId) as HTMLElement;
102
+ }
103
+
104
+ this.container = container;
105
+ const { title, width, position, collapsible, collapsed } = this.state;
106
+
107
+ const sidebar = document.createElement('aside');
108
+ sidebar.className = `jux-sidebar jux-sidebar-${position}`;
109
+ sidebar.id = this._componentId;
110
+ sidebar.style.width = collapsed ? '0' : width;
111
+
112
+ if (collapsed) {
113
+ sidebar.classList.add('jux-sidebar-collapsed');
114
+ }
115
+
116
+ if (title) {
117
+ const titleEl = document.createElement('div');
118
+ titleEl.className = 'jux-sidebar-title';
119
+ titleEl.textContent = title;
120
+ sidebar.appendChild(titleEl);
121
+ }
122
+
123
+ const content = document.createElement('div');
124
+ content.className = 'jux-sidebar-content';
125
+ sidebar.appendChild(content);
126
+
127
+ container.appendChild(sidebar);
128
+
129
+ // Event binding - toggle button
130
+ if (collapsible) {
131
+ const toggleBtn = document.createElement('button');
132
+ toggleBtn.className = 'jux-sidebar-toggle';
133
+ toggleBtn.textContent = collapsed ? '>' : '<';
134
+ sidebar.appendChild(toggleBtn);
135
+
136
+ toggleBtn.addEventListener('click', () => {
137
+ 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
+ });
142
+ }
143
+
144
+ return this;
145
+ }
146
+
147
+ /**
148
+ * Render to another Jux component's container
149
+ */
150
+ renderTo(juxComponent: any): this {
151
+ if (!juxComponent || typeof juxComponent !== 'object') {
152
+ throw new Error('Sidebar.renderTo: Invalid component - not an object');
153
+ }
154
+
155
+ if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
156
+ throw new Error('Sidebar.renderTo: Invalid component - missing _componentId (not a Jux component)');
157
+ }
158
+
159
+ return this.render(`#${juxComponent._componentId}`);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Factory helper
165
+ */
166
+ export function sidebar(componentId: string, options: SidebarOptions = {}): Sidebar {
167
+ return new Sidebar(componentId, options);
168
+ }