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.
- package/bin/cli.js +121 -72
- package/lib/components/alert.ts +212 -165
- package/lib/components/badge.ts +93 -103
- package/lib/components/base/BaseComponent.ts +397 -0
- package/lib/components/base/FormInput.ts +322 -0
- package/lib/components/button.ts +63 -122
- package/lib/components/card.ts +109 -155
- package/lib/components/charts/areachart.ts +315 -0
- package/lib/components/charts/barchart.ts +421 -0
- package/lib/components/charts/doughnutchart.ts +263 -0
- package/lib/components/charts/lib/BaseChart.ts +402 -0
- package/lib/components/charts/lib/chart-types.ts +159 -0
- package/lib/components/charts/lib/chart-utils.ts +160 -0
- package/lib/components/charts/lib/chart.ts +707 -0
- package/lib/components/checkbox.ts +264 -127
- package/lib/components/code.ts +75 -108
- package/lib/components/container.ts +113 -130
- package/lib/components/data.ts +37 -5
- package/lib/components/datepicker.ts +195 -147
- package/lib/components/dialog.ts +187 -157
- package/lib/components/divider.ts +85 -191
- package/lib/components/docs-data.json +544 -2027
- package/lib/components/dropdown.ts +178 -136
- package/lib/components/element.ts +227 -171
- package/lib/components/fileupload.ts +285 -228
- package/lib/components/guard.ts +92 -0
- package/lib/components/heading.ts +46 -69
- package/lib/components/helpers.ts +13 -6
- package/lib/components/hero.ts +107 -95
- package/lib/components/icon.ts +160 -0
- package/lib/components/icons.ts +175 -0
- package/lib/components/include.ts +153 -5
- package/lib/components/input.ts +174 -374
- package/lib/components/kpicard.ts +16 -16
- package/lib/components/list.ts +378 -240
- package/lib/components/loading.ts +142 -211
- package/lib/components/menu.ts +103 -97
- package/lib/components/modal.ts +138 -144
- package/lib/components/nav.ts +169 -90
- package/lib/components/paragraph.ts +49 -150
- package/lib/components/progress.ts +118 -200
- package/lib/components/radio.ts +297 -149
- package/lib/components/script.ts +19 -87
- package/lib/components/select.ts +184 -186
- package/lib/components/sidebar.ts +152 -140
- package/lib/components/style.ts +19 -82
- package/lib/components/switch.ts +258 -188
- package/lib/components/table.ts +1117 -170
- package/lib/components/tabs.ts +162 -145
- package/lib/components/theme-toggle.ts +108 -169
- package/lib/components/tooltip.ts +86 -157
- package/lib/components/write.ts +108 -127
- package/lib/jux.ts +86 -41
- package/machinery/build.js +466 -0
- package/machinery/compiler.js +354 -105
- package/machinery/server.js +23 -100
- package/machinery/watcher.js +153 -130
- package/package.json +1 -2
- package/presets/base.css +1166 -0
- package/presets/notion.css +2 -1975
- package/lib/adapters/base-adapter.js +0 -35
- package/lib/adapters/index.js +0 -33
- package/lib/adapters/mysql-adapter.js +0 -65
- package/lib/adapters/postgres-adapter.js +0 -70
- package/lib/adapters/sqlite-adapter.js +0 -56
- package/lib/components/areachart.ts +0 -1246
- package/lib/components/areachartsmooth.ts +0 -1380
- package/lib/components/barchart.ts +0 -1250
- package/lib/components/chart.ts +0 -127
- package/lib/components/doughnutchart.ts +0 -1191
- package/lib/components/footer.ts +0 -165
- package/lib/components/header.ts +0 -187
- package/lib/components/layout.ts +0 -239
- package/lib/components/main.ts +0 -137
- package/lib/layouts/default.jux +0 -8
- package/lib/layouts/figma.jux +0 -0
- /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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
style(value: string): this {
|
|
68
|
-
this.state.style = value;
|
|
69
|
-
return this;
|
|
70
|
-
}
|
|
58
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
59
|
+
* RENDER
|
|
60
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
heading.className = className;
|
|
102
|
-
}
|
|
74
|
+
this._wireStandardEvents(heading);
|
|
103
75
|
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
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;
|
package/lib/components/hero.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
129
|
-
hero.className =
|
|
102
|
+
const hero = document.createElement('section');
|
|
103
|
+
hero.className = 'jux-hero';
|
|
130
104
|
hero.id = this._id;
|
|
131
|
-
|
|
132
|
-
if (className) {
|
|
133
|
-
|
|
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
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
contentContainer.appendChild(subtitleEl);
|
|
159
135
|
}
|
|
160
136
|
|
|
161
|
-
if (
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
throw new Error('Hero.renderTo: Invalid component - missing _id (not a Jux component)');
|
|
185
|
-
}
|
|
158
|
+
hero.appendChild(contentContainer);
|
|
186
159
|
|
|
187
|
-
|
|
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
|
}
|