juxscript 1.0.19 → 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 (77) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +212 -165
  3. package/lib/components/badge.ts +93 -103
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +63 -122
  7. package/lib/components/card.ts +109 -155
  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/charts/lib/chart-types.ts +159 -0
  13. package/lib/components/charts/lib/chart-utils.ts +160 -0
  14. package/lib/components/charts/lib/chart.ts +707 -0
  15. package/lib/components/checkbox.ts +264 -127
  16. package/lib/components/code.ts +75 -108
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +195 -147
  20. package/lib/components/dialog.ts +187 -157
  21. package/lib/components/divider.ts +85 -191
  22. package/lib/components/docs-data.json +544 -2027
  23. package/lib/components/dropdown.ts +178 -136
  24. package/lib/components/element.ts +227 -171
  25. package/lib/components/fileupload.ts +285 -228
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +46 -69
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +107 -95
  30. package/lib/components/icon.ts +160 -0
  31. package/lib/components/icons.ts +175 -0
  32. package/lib/components/include.ts +153 -5
  33. package/lib/components/input.ts +174 -374
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +378 -240
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +103 -97
  38. package/lib/components/modal.ts +138 -144
  39. package/lib/components/nav.ts +169 -90
  40. package/lib/components/paragraph.ts +49 -150
  41. package/lib/components/progress.ts +118 -200
  42. package/lib/components/radio.ts +297 -149
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +184 -186
  45. package/lib/components/sidebar.ts +152 -140
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +258 -188
  48. package/lib/components/table.ts +1117 -170
  49. package/lib/components/tabs.ts +162 -145
  50. package/lib/components/theme-toggle.ts +108 -169
  51. package/lib/components/tooltip.ts +86 -157
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +86 -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 -2
  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 -1246
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1250
  69. package/lib/components/chart.ts +0 -127
  70. package/lib/components/doughnutchart.ts +0 -1191
  71. package/lib/components/footer.ts +0 -165
  72. package/lib/components/header.ts +0 -187
  73. package/lib/components/layout.ts +0 -239
  74. package/lib/components/main.ts +0 -137
  75. package/lib/layouts/default.jux +0 -8
  76. package/lib/layouts/figma.jux +0 -0
  77. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -0,0 +1,92 @@
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+ import { State } from '../reactivity/state.js';
3
+
4
+ // Extend Window interface to include Jux navigation hooks
5
+ declare global {
6
+ interface Window {
7
+ juxBeforeNavigate?: (from: string, to: string) => boolean | string;
8
+ juxAfterNavigate?: (path: string) => void;
9
+ }
10
+ }
11
+
12
+ // Event definitions
13
+ const TRIGGER_EVENTS = [] as const;
14
+ const CALLBACK_EVENTS = ['blocked', 'allowed'] as const;
15
+
16
+ export interface GuardOptions {
17
+ authState?: State<boolean>; // Check if user is authenticated
18
+ loginPath?: string; // Where to redirect if blocked
19
+ protectedPaths?: string[]; // Paths that require auth
20
+ }
21
+
22
+ type GuardState = {
23
+ authState: State<boolean> | null;
24
+ loginPath: string;
25
+ protectedPaths: string[];
26
+ isActive: boolean;
27
+ };
28
+
29
+ /**
30
+ * ⚠️ DEPRECATED: Guard component is no longer supported after removing global middleware.
31
+ *
32
+ * Recommended alternatives:
33
+ * 1. Server-side authentication (Express, FastAPI, Laravel)
34
+ * 2. Manual route checks in each view
35
+ * 3. Custom wrapper components
36
+ *
37
+ * This component will be removed in a future version.
38
+ */
39
+ export class Guard extends BaseComponent<GuardState> {
40
+ constructor(id: string, options: GuardOptions = {}) {
41
+ super(id, {
42
+ authState: options.authState ?? null,
43
+ loginPath: options.loginPath ?? '/login',
44
+ protectedPaths: options.protectedPaths ?? [],
45
+ isActive: false
46
+ });
47
+
48
+ console.warn(
49
+ '[Jux Guard] DEPRECATED: Guard component no longer supported after middleware removal.\n' +
50
+ 'Use server-side auth or manual checks instead.'
51
+ );
52
+ }
53
+
54
+ protected getTriggerEvents(): readonly string[] {
55
+ return TRIGGER_EVENTS;
56
+ }
57
+
58
+ protected getCallbackEvents(): readonly string[] {
59
+ return CALLBACK_EVENTS;
60
+ }
61
+
62
+ /* ═════════════════════════════════════════════════════════════════
63
+ * FLUENT API (No-ops now)
64
+ * ═════════════════════════════════════════════════════════════════ */
65
+
66
+ requireAuth(authState: State<boolean>, loginPath?: string): this {
67
+ console.warn('[Jux Guard] DEPRECATED: requireAuth() has no effect');
68
+ return this;
69
+ }
70
+
71
+ protect(...paths: string[]): this {
72
+ console.warn('[Jux Guard] DEPRECATED: protect() has no effect');
73
+ return this;
74
+ }
75
+
76
+ /* ═════════════════════════════════════════════════════════════════
77
+ * RENDER (No-op)
78
+ * ═════════════════════════════════════════════════════════════════ */
79
+
80
+ render(targetId?: string): this {
81
+ console.warn('[Jux Guard] DEPRECATED: Guard rendering has no effect');
82
+ return this;
83
+ }
84
+
85
+ deactivate(): this {
86
+ return this;
87
+ }
88
+ }
89
+
90
+ export function guard(id: string, options?: GuardOptions): Guard {
91
+ return new Guard(id, options);
92
+ }
@@ -1,8 +1,9 @@
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; // Headings are display-only, no events
2
6
 
3
- /**
4
- * Heading options
5
- */
6
7
  export interface HeadingOptions {
7
8
  level?: 1 | 2 | 3 | 4 | 5 | 6;
8
9
  text?: string;
@@ -10,9 +11,6 @@ export interface HeadingOptions {
10
11
  style?: string;
11
12
  }
12
13
 
13
- /**
14
- * Heading state
15
- */
16
14
  type HeadingState = {
17
15
  level: 1 | 2 | 3 | 4 | 5 | 6;
18
16
  text: string;
@@ -20,34 +18,32 @@ type HeadingState = {
20
18
  style: string;
21
19
  };
22
20
 
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
-
21
+ export class Heading extends BaseComponent<HeadingState> {
36
22
  constructor(id: string, options: HeadingOptions = {}) {
37
- this._id = id;
38
- this.id = id;
39
-
40
- this.state = {
23
+ super(id, {
41
24
  level: options.level ?? 1,
42
25
  text: options.text ?? '',
43
26
  class: options.class ?? '',
44
27
  style: options.style ?? ''
45
- };
28
+ });
46
29
  }
47
30
 
48
- /* -------------------------
49
- * Fluent API
50
- * ------------------------- */
31
+ protected getTriggerEvents(): readonly string[] {
32
+ return TRIGGER_EVENTS;
33
+ }
34
+
35
+ protected getCallbackEvents(): readonly string[] {
36
+ return CALLBACK_EVENTS;
37
+ }
38
+
39
+ /* ═════════════════════════════════════════════════════════════════
40
+ * FLUENT API
41
+ * ═════════════════════════════════════════════════════════════════ */
42
+
43
+ // ✅ Inherited from BaseComponent:
44
+ // - style(), class()
45
+ // - bind(), sync(), renderTo()
46
+ // - All other base methods
51
47
 
52
48
  level(value: 1 | 2 | 3 | 4 | 5 | 6): this {
53
49
  this.state.level = value;
@@ -59,61 +55,42 @@ export class Heading {
59
55
  return this;
60
56
  }
61
57
 
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
- }
58
+ /* ═════════════════════════════════════════════════════════════════
59
+ * RENDER
60
+ * ═════════════════════════════════════════════════════════════════ */
71
61
 
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
- }
62
+ render(targetId?: string): this {
63
+ const container = this._setupContainer(targetId);
92
64
 
93
- this.container = container;
94
- const { level, text, class: className, style } = this.state;
65
+ const { text, level, style, class: className } = this.state;
95
66
 
96
67
  const heading = document.createElement(`h${level}`) as HTMLHeadingElement;
68
+ heading.className = `jux-heading jux-heading-${level}`;
97
69
  heading.id = this._id;
98
70
  heading.textContent = text;
71
+ if (className) heading.className += ` ${className}`;
72
+ if (style) heading.setAttribute('style', style);
99
73
 
100
- if (className) {
101
- heading.className = className;
102
- }
74
+ this._wireStandardEvents(heading);
103
75
 
104
- if (style) {
105
- heading.setAttribute('style', style);
106
- }
76
+ // Wire sync bindings
77
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
78
+ if (property === 'text') {
79
+ const transform = toComponent || ((v: any) => String(v));
107
80
 
108
- container.appendChild(heading);
81
+ stateObj.subscribe((val: any) => {
82
+ const transformed = transform(val);
83
+ heading.textContent = transformed;
84
+ this.state.text = transformed;
85
+ });
86
+ }
87
+ });
109
88
 
89
+ container.appendChild(heading);
110
90
  return this;
111
91
  }
112
92
  }
113
93
 
114
- /**
115
- * Factory helper
116
- */
117
94
  export function heading(id: string, options: HeadingOptions = {}): Heading {
118
95
  return new Heading(id, options);
119
96
  }
@@ -22,12 +22,19 @@ export function getOrCreateContainer(id: string): HTMLElement {
22
22
  container = document.createElement('div');
23
23
  container.id = id;
24
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;
25
+ // Find appropriate parent - [data-jux-page] takes precedence, then #app, then body
26
+ const dataJuxPage = document.querySelector('[data-jux-page]') as HTMLElement;
27
+ const app = document.getElementById('app');
28
+
29
+ const parent: HTMLElement = (dataJuxPage || app || document.body) as HTMLElement;
30
+
31
+ // Log warning if falling back to body
32
+ if (!dataJuxPage && !app) {
33
+ console.warn(
34
+ `[Jux] Preferred container targets "[data-jux-page]" or "#app" not found. Creating container "#${id}" in fallback parent: body`,
35
+ );
36
+ }
37
+
31
38
  parent.appendChild(container);
32
39
 
33
40
  return container;
@@ -1,8 +1,9 @@
1
- import { getOrCreateContainer } from './helpers.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions - Hero is display-only, but CTA button can trigger actions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = ['ctaClick'] as const; // ✅ When CTA button is clicked
2
6
 
3
- /**
4
- * Hero component options
5
- */
6
7
  export interface HeroOptions {
7
8
  title?: string;
8
9
  subtitle?: string;
@@ -14,9 +15,6 @@ export interface HeroOptions {
14
15
  class?: string;
15
16
  }
16
17
 
17
- /**
18
- * Hero component state
19
- */
20
18
  type HeroState = {
21
19
  title: string;
22
20
  subtitle: string;
@@ -24,46 +22,43 @@ type HeroState = {
24
22
  ctaLink: string;
25
23
  backgroundImage: string;
26
24
  variant: string;
25
+ content: string;
26
+ backgroundOverlay: boolean;
27
+ centered: boolean;
27
28
  style: string;
28
29
  class: string;
29
30
  };
30
31
 
31
- /**
32
- * Hero component
33
- *
34
- * Usage:
35
- * const hero = jux.hero('myHero', {
36
- * title: 'Welcome',
37
- * subtitle: 'Get started today',
38
- * cta: 'Learn More'
39
- * });
40
- * hero.render();
41
- */
42
- export class Hero {
43
- state: HeroState;
44
- container: HTMLElement | null = null;
45
- _id: string;
46
- id: string;
47
-
32
+ export class Hero extends BaseComponent<HeroState> {
48
33
  constructor(id: string, options: HeroOptions = {}) {
49
- this._id = id;
50
- this.id = id;
51
-
52
- this.state = {
34
+ super(id, {
53
35
  title: options.title ?? '',
54
36
  subtitle: options.subtitle ?? '',
55
37
  cta: options.cta ?? '',
56
38
  ctaLink: options.ctaLink ?? '#',
57
39
  backgroundImage: options.backgroundImage ?? '',
58
40
  variant: options.variant ?? 'default',
41
+ content: '',
42
+ backgroundOverlay: false,
43
+ centered: false,
59
44
  style: options.style ?? '',
60
45
  class: options.class ?? ''
61
- };
46
+ });
62
47
  }
63
48
 
64
- /* -------------------------
65
- * Fluent API
66
- * ------------------------- */
49
+ protected getTriggerEvents(): readonly string[] {
50
+ return TRIGGER_EVENTS;
51
+ }
52
+
53
+ protected getCallbackEvents(): readonly string[] {
54
+ return CALLBACK_EVENTS;
55
+ }
56
+
57
+ /* ═════════════════════════════════════════════════════════════════
58
+ * FLUENT API
59
+ * ═════════════════════════════════════════════════════════════════ */
60
+
61
+ // ✅ Inherited from BaseComponent
67
62
 
68
63
  title(value: string): this {
69
64
  this.state.title = value;
@@ -95,102 +90,119 @@ export class Hero {
95
90
  return this;
96
91
  }
97
92
 
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
-
108
- /* -------------------------
109
- * Render
110
- * ------------------------- */
93
+ /* ═════════════════════════════════════════════════════════════════
94
+ * RENDER
95
+ * ═════════════════════════════════════════════════════════════════ */
111
96
 
112
97
  render(targetId?: string): this {
113
- let container: HTMLElement;
114
-
115
- if (targetId) {
116
- const target = document.querySelector(targetId);
117
- if (!target || !(target instanceof HTMLElement)) {
118
- throw new Error(`Hero: Target element "${targetId}" not found`);
119
- }
120
- container = target;
121
- } else {
122
- container = getOrCreateContainer(this._id);
123
- }
98
+ const container = this._setupContainer(targetId);
124
99
 
125
- this.container = container;
126
- const { title, subtitle, cta, ctaLink, backgroundImage, variant, style, class: className } = this.state;
100
+ const { title, subtitle, content, backgroundImage, backgroundOverlay, centered, style, class: className } = this.state;
127
101
 
128
- const hero = document.createElement('div');
129
- hero.className = `jux-hero jux-hero-${variant}`;
102
+ const hero = document.createElement('section');
103
+ hero.className = 'jux-hero';
130
104
  hero.id = this._id;
131
-
132
- if (className) {
133
- hero.className += ` ${className}`;
134
- }
135
-
136
- if (style) {
137
- hero.setAttribute('style', style);
138
- }
105
+ if (centered) hero.classList.add('jux-hero-centered');
106
+ if (className) hero.className += ` ${className}`;
107
+ if (style) hero.setAttribute('style', style);
139
108
 
140
109
  if (backgroundImage) {
141
110
  hero.style.backgroundImage = `url(${backgroundImage})`;
111
+ if (backgroundOverlay) {
112
+ const overlay = document.createElement('div');
113
+ overlay.className = 'jux-hero-overlay';
114
+ hero.appendChild(overlay);
115
+ }
142
116
  }
143
117
 
144
- const content = document.createElement('div');
145
- content.className = 'jux-hero-content';
118
+ const contentContainer = document.createElement('div');
119
+ contentContainer.className = 'jux-hero-content';
146
120
 
147
121
  if (title) {
148
122
  const titleEl = document.createElement('h1');
149
123
  titleEl.className = 'jux-hero-title';
124
+ titleEl.id = `${this._id}-title`;
150
125
  titleEl.textContent = title;
151
- content.appendChild(titleEl);
126
+ contentContainer.appendChild(titleEl);
152
127
  }
153
128
 
154
129
  if (subtitle) {
155
130
  const subtitleEl = document.createElement('p');
156
131
  subtitleEl.className = 'jux-hero-subtitle';
132
+ subtitleEl.id = `${this._id}-subtitle`;
157
133
  subtitleEl.textContent = subtitle;
158
- content.appendChild(subtitleEl);
134
+ contentContainer.appendChild(subtitleEl);
159
135
  }
160
136
 
161
- if (cta) {
162
- const ctaEl = document.createElement('a');
163
- ctaEl.className = 'jux-hero-cta jux-button jux-button-primary';
164
- ctaEl.href = ctaLink;
165
- ctaEl.textContent = cta;
166
- content.appendChild(ctaEl);
137
+ if (content) {
138
+ const contentEl = document.createElement('div');
139
+ contentEl.className = 'jux-hero-body';
140
+ contentEl.innerHTML = content;
141
+ contentContainer.appendChild(contentEl);
167
142
  }
168
143
 
169
- hero.appendChild(content);
170
- container.appendChild(hero);
144
+ if (this.state.cta) {
145
+ const ctaButton = document.createElement('a');
146
+ ctaButton.className = 'jux-hero-cta';
147
+ ctaButton.href = this.state.ctaLink;
148
+ ctaButton.textContent = this.state.cta;
171
149
 
172
- return this;
173
- }
150
+ // ✅ Fire callback when CTA is clicked
151
+ ctaButton.addEventListener('click', (e) => {
152
+ this._triggerCallback('ctaClick', e);
153
+ });
174
154
 
175
- /**
176
- * Render to another Jux component's container
177
- */
178
- renderTo(juxComponent: any): this {
179
- if (!juxComponent || typeof juxComponent !== 'object') {
180
- throw new Error('Hero.renderTo: Invalid component - not an object');
155
+ contentContainer.appendChild(ctaButton);
181
156
  }
182
157
 
183
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
184
- throw new Error('Hero.renderTo: Invalid component - missing _id (not a Jux component)');
185
- }
158
+ hero.appendChild(contentContainer);
186
159
 
187
- return this.render(`#${juxComponent._id}`);
160
+ this._wireStandardEvents(hero);
161
+
162
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
163
+ if (property === 'title') {
164
+ const transform = toComponent || ((v: any) => String(v));
165
+
166
+ stateObj.subscribe((val: any) => {
167
+ const transformed = transform(val);
168
+ const titleEl = document.getElementById(`${this._id}-title`);
169
+ if (titleEl) {
170
+ titleEl.textContent = transformed;
171
+ }
172
+ this.state.title = transformed;
173
+ });
174
+ }
175
+ else if (property === 'subtitle') {
176
+ const transform = toComponent || ((v: any) => String(v));
177
+
178
+ stateObj.subscribe((val: any) => {
179
+ const transformed = transform(val);
180
+ const subtitleEl = document.getElementById(`${this._id}-subtitle`);
181
+ if (subtitleEl) {
182
+ subtitleEl.textContent = transformed;
183
+ }
184
+ this.state.subtitle = transformed;
185
+ });
186
+ }
187
+ else if (property === 'content') {
188
+ const transform = toComponent || ((v: any) => String(v));
189
+
190
+ stateObj.subscribe((val: any) => {
191
+ const transformed = transform(val);
192
+ const contentEl = hero.querySelector('.jux-hero-body');
193
+ if (contentEl) {
194
+ contentEl.innerHTML = transformed;
195
+ }
196
+ this.state.content = transformed;
197
+ });
198
+ }
199
+ });
200
+
201
+ container.appendChild(hero);
202
+ return this;
188
203
  }
189
204
  }
190
205
 
191
- /**
192
- * Factory helper
193
- */
194
206
  export function hero(id: string, options: HeroOptions = {}): Hero {
195
207
  return new Hero(id, options);
196
208
  }