juxscript 1.0.20 → 1.0.21

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 (76) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +143 -92
  3. package/lib/components/badge.ts +93 -94
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +40 -131
  7. package/lib/components/card.ts +57 -79
  8. package/lib/components/charts/areachart.ts +315 -0
  9. package/lib/components/charts/barchart.ts +421 -0
  10. package/lib/components/charts/doughnutchart.ts +263 -0
  11. package/lib/components/charts/lib/BaseChart.ts +402 -0
  12. package/lib/components/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
  13. package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
  14. package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
  15. package/lib/components/checkbox.ts +255 -204
  16. package/lib/components/code.ts +31 -78
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +180 -147
  20. package/lib/components/dialog.ts +218 -221
  21. package/lib/components/divider.ts +63 -87
  22. package/lib/components/docs-data.json +498 -2404
  23. package/lib/components/dropdown.ts +191 -236
  24. package/lib/components/element.ts +196 -145
  25. package/lib/components/fileupload.ts +253 -167
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +31 -97
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +51 -114
  30. package/lib/components/icon.ts +33 -120
  31. package/lib/components/icons.ts +2 -1
  32. package/lib/components/include.ts +76 -3
  33. package/lib/components/input.ts +155 -407
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +358 -261
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +63 -152
  38. package/lib/components/modal.ts +42 -129
  39. package/lib/components/nav.ts +79 -101
  40. package/lib/components/paragraph.ts +38 -102
  41. package/lib/components/progress.ts +108 -166
  42. package/lib/components/radio.ts +283 -234
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +189 -199
  45. package/lib/components/sidebar.ts +110 -141
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +254 -183
  48. package/lib/components/table.ts +1078 -208
  49. package/lib/components/tabs.ts +42 -106
  50. package/lib/components/theme-toggle.ts +73 -165
  51. package/lib/components/tooltip.ts +85 -316
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +67 -41
  54. package/machinery/build.js +466 -0
  55. package/machinery/compiler.js +354 -105
  56. package/machinery/server.js +23 -100
  57. package/machinery/watcher.js +153 -130
  58. package/package.json +1 -1
  59. package/presets/base.css +1166 -0
  60. package/presets/notion.css +2 -1975
  61. package/lib/adapters/base-adapter.js +0 -35
  62. package/lib/adapters/index.js +0 -33
  63. package/lib/adapters/mysql-adapter.js +0 -65
  64. package/lib/adapters/postgres-adapter.js +0 -70
  65. package/lib/adapters/sqlite-adapter.js +0 -56
  66. package/lib/components/areachart.ts +0 -1128
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1322
  69. package/lib/components/doughnutchart.ts +0 -1259
  70. package/lib/components/footer.ts +0 -165
  71. package/lib/components/header.ts +0 -187
  72. package/lib/components/layout.ts +0 -239
  73. package/lib/components/main.ts +0 -137
  74. package/lib/layouts/default.jux +0 -8
  75. package/lib/layouts/figma.jux +0 -0
  76. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,5 +1,8 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = [] as const;
3
6
 
4
7
  export interface CodeOptions {
5
8
  code?: string;
@@ -15,84 +18,49 @@ type CodeState = {
15
18
  class: string;
16
19
  };
17
20
 
18
- export class Code {
19
- state: CodeState;
20
- container: HTMLElement | null = null;
21
- _id: string;
22
- id: string;
23
-
24
- // CRITICAL: Store bind/sync instructions for deferred wiring
25
- private _bindings: Array<{ event: string, handler: Function }> = [];
26
- private _syncBindings: Array<{
27
- property: string,
28
- stateObj: State<any>,
29
- toState?: Function,
30
- toComponent?: Function
31
- }> = [];
32
-
21
+ export class Code extends BaseComponent<CodeState> {
33
22
  constructor(id: string, options: CodeOptions = {}) {
34
- this._id = id;
35
- this.id = id;
36
-
37
- this.state = {
23
+ super(id, {
38
24
  code: options.code ?? '',
39
25
  language: options.language ?? 'javascript',
40
26
  style: options.style ?? '',
41
27
  class: options.class ?? ''
42
- };
28
+ });
43
29
  }
44
30
 
45
- code(value: string): this {
46
- this.state.code = value;
47
- return this;
31
+ protected getTriggerEvents(): readonly string[] {
32
+ return TRIGGER_EVENTS;
48
33
  }
49
34
 
50
- language(value: string): this {
51
- this.state.language = value;
52
- return this;
35
+ protected getCallbackEvents(): readonly string[] {
36
+ return CALLBACK_EVENTS;
53
37
  }
54
38
 
55
- style(value: string): this {
56
- this.state.style = value;
57
- return this;
58
- }
39
+ /* ═════════════════════════════════════════════════════════════════
40
+ * FLUENT API
41
+ * ═════════════════════════════════════════════════════════════════ */
59
42
 
60
- class(value: string): this {
61
- this.state.class = value;
62
- return this;
63
- }
43
+ // Inherited from BaseComponent
64
44
 
65
- bind(event: string, handler: Function): this {
66
- this._bindings.push({ event, handler });
45
+ code(value: string): this {
46
+ this.state.code = value;
67
47
  return this;
68
48
  }
69
49
 
70
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
71
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
72
- throw new Error(`Code.sync: Expected a State object for property "${property}"`);
73
- }
74
- this._syncBindings.push({ property, stateObj, toState, toComponent });
50
+ language(value: string): this {
51
+ this.state.language = value;
75
52
  return this;
76
53
  }
77
54
 
55
+ /* ═════════════════════════════════════════════════════════════════
56
+ * RENDER
57
+ * ═════════════════════════════════════════════════════════════════ */
58
+
78
59
  render(targetId?: string): this {
79
- // === 1. SETUP: Get or create container ===
80
- let container: HTMLElement;
81
- if (targetId) {
82
- const target = document.querySelector(targetId);
83
- if (!target || !(target instanceof HTMLElement)) {
84
- throw new Error(`Code: Target "${targetId}" not found`);
85
- }
86
- container = target;
87
- } else {
88
- container = getOrCreateContainer(this._id);
89
- }
90
- this.container = container;
60
+ const container = this._setupContainer(targetId);
91
61
 
92
- // === 2. PREPARE: Destructure state ===
93
62
  const { code, language, style, class: className } = this.state;
94
63
 
95
- // === 3. BUILD: Create DOM elements ===
96
64
  const wrapper = document.createElement('div');
97
65
  wrapper.className = 'jux-code';
98
66
  wrapper.id = this._id;
@@ -106,34 +74,28 @@ export class Code {
106
74
  pre.appendChild(codeEl);
107
75
  wrapper.appendChild(pre);
108
76
 
109
- // === 4. WIRE: Attach event listeners and sync bindings ===
110
-
111
- // Wire custom bindings from .bind() calls
112
- this._bindings.forEach(({ event, handler }) => {
113
- wrapper.addEventListener(event, handler as EventListener);
114
- });
77
+ this._wireStandardEvents(wrapper);
115
78
 
116
- // Wire sync bindings from .sync() calls
79
+ // Wire sync bindings
117
80
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
118
81
  if (property === 'code') {
119
- const transformToComponent = toComponent || ((v: any) => String(v));
82
+ const transform = toComponent || ((v: any) => String(v));
120
83
 
121
84
  stateObj.subscribe((val: any) => {
122
- const transformed = transformToComponent(val);
85
+ const transformed = transform(val);
123
86
  codeEl.textContent = transformed;
124
87
  this.state.code = transformed;
125
88
 
126
- // Re-trigger syntax highlighting if available
127
89
  if ((window as any).Prism) {
128
90
  (window as any).Prism.highlightElement(codeEl);
129
91
  }
130
92
  });
131
93
  }
132
94
  else if (property === 'language') {
133
- const transformToComponent = toComponent || ((v: any) => String(v));
95
+ const transform = toComponent || ((v: any) => String(v));
134
96
 
135
97
  stateObj.subscribe((val: any) => {
136
- const transformed = transformToComponent(val);
98
+ const transformed = transform(val);
137
99
  codeEl.className = `language-${transformed}`;
138
100
  this.state.language = transformed;
139
101
 
@@ -144,10 +106,8 @@ export class Code {
144
106
  }
145
107
  });
146
108
 
147
- // === 5. RENDER: Append to DOM and finalize ===
148
109
  container.appendChild(wrapper);
149
110
 
150
- // Trigger syntax highlighting
151
111
  requestAnimationFrame(() => {
152
112
  if ((window as any).Prism) {
153
113
  (window as any).Prism.highlightElement(codeEl);
@@ -156,13 +116,6 @@ export class Code {
156
116
 
157
117
  return this;
158
118
  }
159
-
160
- renderTo(juxComponent: any): this {
161
- if (!juxComponent?._id) {
162
- throw new Error('Code.renderTo: Invalid component');
163
- }
164
- return this.render(`#${juxComponent._id}`);
165
- }
166
119
  }
167
120
 
168
121
  export function code(id: string, options: CodeOptions = {}): Code {
@@ -1,178 +1,161 @@
1
- import { getOrCreateContainer } from './helpers.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = [] as const;
2
6
 
3
- /**
4
- * Container options
5
- */
6
7
  export interface ContainerOptions {
7
- class?: string;
8
- style?: string;
9
8
  direction?: 'row' | 'column';
10
- gap?: string | number;
9
+ gap?: number | string;
10
+ wrap?: boolean;
11
11
  align?: 'start' | 'center' | 'end' | 'stretch';
12
12
  justify?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';
13
+ padding?: string;
14
+ style?: string;
15
+ class?: string;
13
16
  }
14
17
 
15
- /**
16
- * Container state
17
- */
18
18
  type ContainerState = {
19
- class: string;
19
+ direction: string;
20
+ gap: number | string;
21
+ wrap: boolean;
22
+ align: string;
23
+ justify: string;
24
+ padding: string;
20
25
  style: string;
21
- direction?: 'row' | 'column';
22
- gap?: string | number;
23
- align?: string;
24
- justify?: string;
26
+ class: string;
25
27
  };
26
28
 
27
- /**
28
- * Container component - a simple div container for grouping elements
29
- *
30
- * Usage:
31
- * // Plain container
32
- * jux.container('wrapper').render('#app');
33
- *
34
- * // Flex container with layout
35
- * jux.container('toolbar')
36
- * .direction('row')
37
- * .gap(16)
38
- * .render('#app');
39
- */
40
- export class Container {
41
- state: ContainerState;
42
- container: HTMLElement | null = null;
43
- _id: string;
44
- id: string;
45
-
29
+ export class Container extends BaseComponent<ContainerState> {
46
30
  constructor(id: string, options: ContainerOptions = {}) {
47
- this._id = id;
48
- this.id = id;
49
-
50
- this.state = {
51
- class: options.class ?? '',
31
+ super(id, {
32
+ direction: options.direction ?? 'column',
33
+ gap: options.gap ?? 0,
34
+ wrap: options.wrap ?? false,
35
+ align: options.align ?? 'stretch',
36
+ justify: options.justify ?? 'start',
37
+ padding: options.padding ?? '0',
52
38
  style: options.style ?? '',
53
- direction: options.direction,
54
- gap: options.gap,
55
- align: options.align,
56
- justify: options.justify
57
- };
39
+ class: options.class ?? ''
40
+ });
58
41
  }
59
42
 
60
- /* -------------------------
61
- * Fluent API
62
- * ------------------------- */
63
-
64
- class(value: string): this {
65
- this.state.class = value;
66
- return this;
43
+ protected getTriggerEvents(): readonly string[] {
44
+ return TRIGGER_EVENTS;
67
45
  }
68
46
 
69
- style(value: string): this {
70
- this.state.style = value;
71
- return this;
47
+ protected getCallbackEvents(): readonly string[] {
48
+ return CALLBACK_EVENTS;
72
49
  }
73
50
 
74
- /**
75
- * Set flex direction (row = horizontal, column = vertical)
76
- * Automatically enables flexbox when called
77
- */
51
+ /* ═════════════════════════════════════════════════════════════════
52
+ * FLUENT API
53
+ * ═════════════════════════════════════════════════════════════════ */
54
+
55
+ // ✅ Inherited from BaseComponent:
56
+ // - style(), class()
57
+ // - addClass(), removeClass(), toggleClass()
58
+ // - visible(), show(), hide()
59
+ // - attr(), attrs(), removeAttr()
60
+ // - disabled(), enable(), disable()
61
+ // - bind(), sync(), renderTo()
62
+
78
63
  direction(value: 'row' | 'column'): this {
79
64
  this.state.direction = value;
80
65
  return this;
81
66
  }
82
67
 
83
- /**
84
- * Set gap between children
85
- * Automatically enables flexbox when called
86
- * @param value - Gap size (number = px, string = any CSS unit)
87
- */
88
- gap(value: string | number): this {
68
+ gap(value: number | string): this {
89
69
  this.state.gap = value;
90
70
  return this;
91
71
  }
92
72
 
93
- /**
94
- * Set alignment of children along cross axis
95
- * Automatically enables flexbox when called
96
- */
73
+ wrap(value: boolean): this {
74
+ this.state.wrap = value;
75
+ return this;
76
+ }
77
+
97
78
  align(value: 'start' | 'center' | 'end' | 'stretch'): this {
98
79
  this.state.align = value;
99
80
  return this;
100
81
  }
101
82
 
102
- /**
103
- * Set justification of children along main axis
104
- * Automatically enables flexbox when called
105
- */
106
83
  justify(value: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly'): this {
107
84
  this.state.justify = value;
108
85
  return this;
109
86
  }
110
87
 
111
- /* -------------------------
112
- * Render
113
- * ------------------------- */
114
-
115
- render(targetId?: string | HTMLElement): this {
116
- let container: HTMLElement;
88
+ padding(value: string): this {
89
+ this.state.padding = value;
90
+ return this;
91
+ }
117
92
 
118
- if (targetId) {
119
- if (targetId instanceof HTMLElement) {
120
- container = targetId;
121
- } else {
122
- const target = document.querySelector(targetId);
123
- if (!target || !(target instanceof HTMLElement)) {
124
- throw new Error(`Container: Target element "${targetId}" not found`);
93
+ /* ═════════════════════════════════════════════════════════════════
94
+ * RENDER
95
+ * ═════════════════════════════════════════════════════════════════ */
96
+
97
+ render(targetId?: string): this {
98
+ const container = this._setupContainer(targetId);
99
+
100
+ const { direction, gap, wrap, align, justify, padding, style, class: className } = this.state;
101
+
102
+ const wrapper = document.createElement('div');
103
+ wrapper.className = 'jux-container';
104
+ wrapper.id = this._id;
105
+ if (className) wrapper.className += ` ${className}`;
106
+
107
+ // Build inline styles
108
+ const gapValue = typeof gap === 'number' ? `${gap}px` : gap;
109
+ const computedStyle = `
110
+ display: flex;
111
+ flex-direction: ${direction};
112
+ gap: ${gapValue};
113
+ flex-wrap: ${wrap ? 'wrap' : 'nowrap'};
114
+ align-items: ${align};
115
+ justify-content: ${justify};
116
+ padding: ${padding};
117
+ ${style}
118
+ `.trim();
119
+
120
+ wrapper.setAttribute('style', computedStyle);
121
+
122
+ this._wireStandardEvents(wrapper);
123
+
124
+ // Wire sync bindings
125
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
126
+ const transform = toComponent || ((v: any) => v);
127
+
128
+ stateObj.subscribe((val: any) => {
129
+ const transformed = transform(val);
130
+
131
+ if (property === 'direction') {
132
+ this.state.direction = String(transformed);
133
+ wrapper.style.flexDirection = this.state.direction;
134
+ } else if (property === 'gap') {
135
+ this.state.gap = transformed;
136
+ const gapVal = typeof transformed === 'number' ? `${transformed}px` : transformed;
137
+ wrapper.style.gap = gapVal;
138
+ } else if (property === 'wrap') {
139
+ this.state.wrap = Boolean(transformed);
140
+ wrapper.style.flexWrap = this.state.wrap ? 'wrap' : 'nowrap';
141
+ } else if (property === 'align') {
142
+ this.state.align = String(transformed);
143
+ wrapper.style.alignItems = this.state.align;
144
+ } else if (property === 'justify') {
145
+ this.state.justify = String(transformed);
146
+ wrapper.style.justifyContent = this.state.justify;
147
+ } else if (property === 'padding') {
148
+ this.state.padding = String(transformed);
149
+ wrapper.style.padding = this.state.padding;
125
150
  }
126
- container = target;
127
- }
128
- } else {
129
- container = getOrCreateContainer(this._id);
130
- }
131
-
132
- this.container = container;
133
- const { class: className, style, direction, gap, align, justify } = this.state;
134
-
135
- const div = document.createElement('div');
136
- div.id = this._id;
137
-
138
- // Always include jux-container class, append custom classes
139
- div.className = className ? `jux-container ${className}` : 'jux-container';
140
-
141
- // Only apply flex styles if any flex properties are set
142
- const usesFlexbox = direction || gap !== undefined || align || justify;
143
-
144
- let computedStyle = style;
145
-
146
- if (usesFlexbox) {
147
- const flexStyles: string[] = ['display: flex', 'width: auto'];
148
-
149
- if (direction) flexStyles.push(`flex-direction: ${direction}`);
150
-
151
- if (gap !== undefined) {
152
- const gapValue = typeof gap === 'number' ? `${gap}px` : gap;
153
- flexStyles.push(`gap: ${gapValue}`);
154
- }
155
- // Only set align-items if explicitly specified
156
- if (align) flexStyles.push(`align-items: ${align}`);
157
- // Only set justify-content if explicitly specified
158
- if (justify) flexStyles.push(`justify-content: ${justify}`);
159
-
160
- computedStyle = `${flexStyles.join('; ')}; ${style}`.trim();
161
- }
162
-
163
- if (computedStyle) {
164
- div.setAttribute('style', computedStyle);
165
- }
166
-
167
- container.appendChild(div);
151
+ });
152
+ });
168
153
 
154
+ container.appendChild(wrapper);
169
155
  return this;
170
156
  }
171
157
  }
172
158
 
173
- /**
174
- * Factory helper
175
- */
176
159
  export function container(id: string, options: ContainerOptions = {}): Container {
177
160
  return new Container(id, options);
178
161
  }
@@ -93,11 +93,43 @@ export class Data {
93
93
  this._data = [];
94
94
  this._loading = false;
95
95
  }
96
+
97
+ // Remove id requirement, make it optional for caching
98
+ static async fetch<T = any>(url: string, options?: RequestInit): Promise<T> {
99
+ const response = await fetch(url, options);
100
+ if (!response.ok) {
101
+ throw new Error(`API error: ${response.statusText}`);
102
+ }
103
+ return response.json();
104
+ }
105
+
106
+ // ✅ Static methods - no ID needed
107
+ static async post<T = any>(url: string, body: any, options?: RequestInit): Promise<T> {
108
+ const response = await fetch(url, options);
109
+ if (!response.ok) {
110
+ throw new Error(`API error: ${response.statusText}`);
111
+ }
112
+ return response.json();
113
+ }
114
+
115
+ static async put<T = any>(url: string, body: any, options?: RequestInit): Promise<T> {
116
+ const response = await fetch(url, options);
117
+ if (!response.ok) {
118
+ throw new Error(`API error: ${response.statusText}`);
119
+ }
120
+ return response.json();
121
+ }
122
+
123
+ static async delete<T = any>(url: string, options?: RequestInit): Promise<T> {
124
+ const response = await fetch(url, options);
125
+ if (!response.ok) {
126
+ throw new Error(`API error: ${response.statusText}`);
127
+ }
128
+ return response.json();
129
+ }
96
130
  }
97
131
 
98
- /**
99
- * Factory helper
100
- */
101
- export function data(sql: string, params: any[] = [], apiUrl: string = '/api/query'): Data {
102
- return new Data(sql, params, apiUrl);
132
+ // ✅ No ID needed - pure data fetching
133
+ export function data<T = any>(url: string, options?: RequestInit): Promise<T> {
134
+ return Data.fetch<T>(url, options);
103
135
  }