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
package/lib/components/nav.ts
CHANGED
|
@@ -1,72 +1,66 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseComponent } from './base/BaseComponent.js';
|
|
2
|
+
import { renderIcon } from './icons.js';
|
|
2
3
|
import { req } from './req.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
// Event definitions
|
|
6
|
+
const TRIGGER_EVENTS = [] as const;
|
|
7
|
+
const CALLBACK_EVENTS = [] as const;
|
|
8
|
+
|
|
7
9
|
export interface NavItem {
|
|
8
10
|
label: string;
|
|
9
11
|
href: string;
|
|
10
12
|
active?: boolean;
|
|
13
|
+
itemClass?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface NavBrand {
|
|
17
|
+
text?: string;
|
|
18
|
+
href?: string;
|
|
19
|
+
icon?: string;
|
|
11
20
|
}
|
|
12
21
|
|
|
13
|
-
/**
|
|
14
|
-
* Nav component options
|
|
15
|
-
*/
|
|
16
22
|
export interface NavOptions {
|
|
17
23
|
items?: NavItem[];
|
|
24
|
+
brand?: NavBrand;
|
|
18
25
|
variant?: 'default' | 'pills' | 'tabs';
|
|
26
|
+
sticky?: boolean;
|
|
19
27
|
style?: string;
|
|
20
28
|
class?: string;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
|
-
/**
|
|
24
|
-
* Nav component state
|
|
25
|
-
*/
|
|
26
31
|
type NavState = {
|
|
27
32
|
items: NavItem[];
|
|
33
|
+
brand?: NavBrand;
|
|
28
34
|
variant: string;
|
|
35
|
+
sticky: boolean;
|
|
29
36
|
style: string;
|
|
30
37
|
class: string;
|
|
31
38
|
};
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*
|
|
36
|
-
* Usage:
|
|
37
|
-
* const nav = jux.nav('myNav', {
|
|
38
|
-
* variant: 'pills',
|
|
39
|
-
* items: [
|
|
40
|
-
* { label: 'Home', href: '/', active: true },
|
|
41
|
-
* { label: 'About', href: '/about' }
|
|
42
|
-
* ]
|
|
43
|
-
* });
|
|
44
|
-
* nav.render();
|
|
45
|
-
*/
|
|
46
|
-
export class Nav {
|
|
47
|
-
state: NavState;
|
|
48
|
-
container: HTMLElement | null = null;
|
|
49
|
-
_id: string;
|
|
50
|
-
id: string;
|
|
40
|
+
export class Nav extends BaseComponent<NavState> {
|
|
41
|
+
private _nav: HTMLElement | null = null;
|
|
51
42
|
|
|
52
43
|
constructor(id: string, options: NavOptions = {}) {
|
|
53
|
-
|
|
54
|
-
this.id = id;
|
|
55
|
-
|
|
56
|
-
this.state = {
|
|
44
|
+
super(id, {
|
|
57
45
|
items: options.items ?? [],
|
|
46
|
+
brand: options.brand,
|
|
58
47
|
variant: options.variant ?? 'default',
|
|
48
|
+
sticky: options.sticky ?? false,
|
|
59
49
|
style: options.style ?? '',
|
|
60
50
|
class: options.class ?? ''
|
|
61
|
-
};
|
|
51
|
+
});
|
|
62
52
|
|
|
63
|
-
// Auto-set active state based on current path
|
|
64
53
|
this._setActiveStates();
|
|
65
54
|
}
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
56
|
+
protected getTriggerEvents(): readonly string[] {
|
|
57
|
+
return TRIGGER_EVENTS;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected getCallbackEvents(): readonly string[] {
|
|
61
|
+
return CALLBACK_EVENTS;
|
|
62
|
+
}
|
|
63
|
+
|
|
70
64
|
private _setActiveStates(): void {
|
|
71
65
|
this.state.items = this.state.items.map(item => ({
|
|
72
66
|
...item,
|
|
@@ -74,9 +68,11 @@ export class Nav {
|
|
|
74
68
|
}));
|
|
75
69
|
}
|
|
76
70
|
|
|
77
|
-
/*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
71
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
72
|
+
* FLUENT API
|
|
73
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
74
|
+
|
|
75
|
+
// ✅ Inherited from BaseComponent
|
|
80
76
|
|
|
81
77
|
items(value: NavItem[]): this {
|
|
82
78
|
this.state.items = value;
|
|
@@ -90,89 +86,172 @@ export class Nav {
|
|
|
90
86
|
return this;
|
|
91
87
|
}
|
|
92
88
|
|
|
93
|
-
|
|
94
|
-
this.state.
|
|
89
|
+
itemClass(className: string): this {
|
|
90
|
+
this.state.items = this.state.items.map(item => ({
|
|
91
|
+
...item,
|
|
92
|
+
itemClass: className
|
|
93
|
+
}));
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
brand(value: NavBrand): this {
|
|
98
|
+
this.state.brand = value;
|
|
95
99
|
return this;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
this.state.
|
|
102
|
+
variant(value: string): this {
|
|
103
|
+
this.state.variant = value;
|
|
100
104
|
return this;
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
|
|
104
|
-
this.state.
|
|
107
|
+
sticky(value: boolean): this {
|
|
108
|
+
this.state.sticky = value;
|
|
105
109
|
return this;
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
/*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
112
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
113
|
+
* RENDER
|
|
114
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
111
115
|
|
|
112
116
|
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(`Nav: Target element "${targetId}" not found`);
|
|
119
|
-
}
|
|
120
|
-
container = target;
|
|
121
|
-
} else {
|
|
122
|
-
container = getOrCreateContainer(this._id);
|
|
123
|
-
}
|
|
117
|
+
const container = this._setupContainer(targetId);
|
|
124
118
|
|
|
125
|
-
|
|
126
|
-
const { items, variant, style, class: className } = this.state;
|
|
119
|
+
const { brand, items, variant, sticky, style, class: className } = this.state;
|
|
127
120
|
|
|
128
121
|
const nav = document.createElement('nav');
|
|
129
122
|
nav.className = `jux-nav jux-nav-${variant}`;
|
|
130
123
|
nav.id = this._id;
|
|
124
|
+
if (sticky) nav.classList.add('jux-nav-sticky');
|
|
125
|
+
if (className) nav.className += ` ${className}`;
|
|
126
|
+
if (style) nav.setAttribute('style', style);
|
|
131
127
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
128
|
+
// Brand/Logo
|
|
129
|
+
if (brand) {
|
|
130
|
+
const brandEl = document.createElement('div');
|
|
131
|
+
brandEl.className = 'jux-nav-brand';
|
|
135
132
|
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
if (brand.href) {
|
|
134
|
+
const link = document.createElement('a');
|
|
135
|
+
link.href = brand.href;
|
|
136
|
+
if (brand.icon) {
|
|
137
|
+
const icon = document.createElement('span');
|
|
138
|
+
icon.appendChild(renderIcon(brand.icon));
|
|
139
|
+
link.appendChild(icon);
|
|
140
|
+
}
|
|
141
|
+
if (brand.text) {
|
|
142
|
+
const text = document.createElement('span');
|
|
143
|
+
text.textContent = brand.text;
|
|
144
|
+
link.appendChild(text);
|
|
145
|
+
}
|
|
146
|
+
brandEl.appendChild(link);
|
|
147
|
+
} else {
|
|
148
|
+
if (brand.icon) {
|
|
149
|
+
const icon = document.createElement('span');
|
|
150
|
+
icon.appendChild(renderIcon(brand.icon));
|
|
151
|
+
brandEl.appendChild(icon);
|
|
152
|
+
}
|
|
153
|
+
if (brand.text) {
|
|
154
|
+
const text = document.createElement('span');
|
|
155
|
+
text.textContent = brand.text;
|
|
156
|
+
brandEl.appendChild(text);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
nav.appendChild(brandEl);
|
|
138
161
|
}
|
|
139
162
|
|
|
163
|
+
// Nav items container
|
|
164
|
+
const itemsContainer = document.createElement('div');
|
|
165
|
+
itemsContainer.className = 'jux-nav-items';
|
|
166
|
+
|
|
140
167
|
items.forEach(item => {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
168
|
+
const itemWrapper = document.createElement('div');
|
|
169
|
+
itemWrapper.className = 'jux-nav-item-wrapper';
|
|
170
|
+
|
|
171
|
+
if (item.itemClass) {
|
|
172
|
+
itemWrapper.className += ` ${item.itemClass}`;
|
|
173
|
+
}
|
|
145
174
|
|
|
175
|
+
const navLink = document.createElement('a');
|
|
176
|
+
navLink.className = 'jux-nav-link';
|
|
177
|
+
navLink.href = item.href;
|
|
178
|
+
navLink.textContent = item.label;
|
|
146
179
|
if (item.active) {
|
|
147
|
-
|
|
180
|
+
itemWrapper.classList.add('jux-nav-item-active');
|
|
181
|
+
navLink.classList.add('jux-nav-link-active');
|
|
148
182
|
}
|
|
149
183
|
|
|
150
|
-
|
|
184
|
+
itemWrapper.appendChild(navLink);
|
|
185
|
+
itemsContainer.appendChild(itemWrapper);
|
|
151
186
|
});
|
|
152
187
|
|
|
153
|
-
|
|
154
|
-
return this;
|
|
155
|
-
}
|
|
188
|
+
nav.appendChild(itemsContainer);
|
|
156
189
|
|
|
157
|
-
|
|
158
|
-
* Render to another Jux component's container
|
|
159
|
-
*/
|
|
160
|
-
renderTo(juxComponent: any): this {
|
|
161
|
-
if (!juxComponent || typeof juxComponent !== 'object') {
|
|
162
|
-
throw new Error('Nav.renderTo: Invalid component - not an object');
|
|
163
|
-
}
|
|
190
|
+
this._wireStandardEvents(nav);
|
|
164
191
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
192
|
+
// Wire sync bindings
|
|
193
|
+
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
194
|
+
if (property === 'items') {
|
|
195
|
+
const transform = toComponent || ((v: any) => v);
|
|
196
|
+
|
|
197
|
+
stateObj.subscribe((val: any) => {
|
|
198
|
+
const transformed = transform(val);
|
|
199
|
+
this.state.items = transformed;
|
|
200
|
+
this._setActiveStates();
|
|
201
|
+
|
|
202
|
+
itemsContainer.innerHTML = '';
|
|
203
|
+
this.state.items.forEach((item: any) => {
|
|
204
|
+
const itemWrapper = document.createElement('div');
|
|
205
|
+
itemWrapper.className = 'jux-nav-item-wrapper';
|
|
206
|
+
|
|
207
|
+
if (item.itemClass) {
|
|
208
|
+
itemWrapper.className += ` ${item.itemClass}`;
|
|
209
|
+
}
|
|
168
210
|
|
|
169
|
-
|
|
211
|
+
const navLink = document.createElement('a');
|
|
212
|
+
navLink.className = 'jux-nav-link';
|
|
213
|
+
navLink.href = item.href;
|
|
214
|
+
navLink.textContent = item.label;
|
|
215
|
+
if (item.active) {
|
|
216
|
+
itemWrapper.classList.add('jux-nav-item-active');
|
|
217
|
+
navLink.classList.add('jux-nav-link-active');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
itemWrapper.appendChild(navLink);
|
|
221
|
+
itemsContainer.appendChild(itemWrapper);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
else if (property === 'brand') {
|
|
226
|
+
const transform = toComponent || ((v: any) => v);
|
|
227
|
+
|
|
228
|
+
stateObj.subscribe((val: any) => {
|
|
229
|
+
const transformed = transform(val);
|
|
230
|
+
const brandEl = nav.querySelector('.jux-nav-brand');
|
|
231
|
+
if (brandEl && transformed.text) {
|
|
232
|
+
const textEl = brandEl.querySelector('span:last-child');
|
|
233
|
+
if (textEl) {
|
|
234
|
+
textEl.textContent = transformed.text;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
this.state.brand = transformed;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
container.appendChild(nav);
|
|
243
|
+
this._nav = nav;
|
|
244
|
+
|
|
245
|
+
requestAnimationFrame(() => {
|
|
246
|
+
if ((window as any).lucide) {
|
|
247
|
+
(window as any).lucide.createIcons();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return this;
|
|
170
252
|
}
|
|
171
253
|
}
|
|
172
254
|
|
|
173
|
-
/**
|
|
174
|
-
* Factory helper
|
|
175
|
-
*/
|
|
176
255
|
export function nav(id: string, options: NavOptions = {}): Nav {
|
|
177
256
|
return new Nav(id, options);
|
|
178
257
|
}
|
|
@@ -1,198 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
/**
|
|
5
|
-
* Paragraph options
|
|
6
|
-
*/
|
|
7
7
|
export interface ParagraphOptions {
|
|
8
8
|
text?: string;
|
|
9
9
|
class?: string;
|
|
10
10
|
style?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Paragraph state
|
|
15
|
-
*/
|
|
16
13
|
type ParagraphState = {
|
|
17
14
|
text: string;
|
|
18
15
|
class: string;
|
|
19
16
|
style: string;
|
|
20
17
|
};
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
* Paragraph component - semantic paragraph element
|
|
24
|
-
*
|
|
25
|
-
* Usage:
|
|
26
|
-
* jux.paragraph('intro', { text: 'Welcome to JUX' }).render('#app');
|
|
27
|
-
* jux.paragraph('description').text('A simple framework').render('#app');
|
|
28
|
-
*
|
|
29
|
-
* // With state binding
|
|
30
|
-
* jux.paragraph('counter')
|
|
31
|
-
* .text('Count: 0')
|
|
32
|
-
* .bind('text', count, (val) => `Count: ${val}`)
|
|
33
|
-
* .render('#app');
|
|
34
|
-
*
|
|
35
|
-
* // With sync (one-way for paragraph)
|
|
36
|
-
* jux.paragraph('display')
|
|
37
|
-
* .sync('text', count, (val) => `Count: ${val}`)
|
|
38
|
-
* .render('#app');
|
|
39
|
-
*/
|
|
40
|
-
export class Paragraph {
|
|
41
|
-
state: ParagraphState;
|
|
42
|
-
container: HTMLElement | null = null;
|
|
43
|
-
_id: string;
|
|
44
|
-
id: string;
|
|
45
|
-
|
|
46
|
-
// Store bind() instructions
|
|
47
|
-
private _bindings: Array<{ event: string, handler: Function, stateObj?: State<any>, transform?: Function }> = [];
|
|
48
|
-
|
|
49
|
-
// Store sync() instructions
|
|
50
|
-
private _syncBindings: Array<{ property: string, stateObj: State<any>, transform?: Function }> = [];
|
|
51
|
-
|
|
19
|
+
export class Paragraph extends BaseComponent<ParagraphState> {
|
|
52
20
|
constructor(id: string, options: ParagraphOptions = {}) {
|
|
53
|
-
|
|
54
|
-
this.id = id;
|
|
55
|
-
|
|
56
|
-
this.state = {
|
|
21
|
+
super(id, {
|
|
57
22
|
text: options.text ?? '',
|
|
58
23
|
class: options.class ?? '',
|
|
59
24
|
style: options.style ?? ''
|
|
60
|
-
};
|
|
25
|
+
});
|
|
61
26
|
}
|
|
62
27
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* ------------------------- */
|
|
66
|
-
|
|
67
|
-
text(value: string): this {
|
|
68
|
-
this.state.text = value;
|
|
69
|
-
return this;
|
|
28
|
+
protected getTriggerEvents(): readonly string[] {
|
|
29
|
+
return TRIGGER_EVENTS;
|
|
70
30
|
}
|
|
71
31
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return this;
|
|
32
|
+
protected getCallbackEvents(): readonly string[] {
|
|
33
|
+
return CALLBACK_EVENTS;
|
|
75
34
|
}
|
|
76
35
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
36
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
37
|
+
* FLUENT API
|
|
38
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
81
39
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (typeof source === 'function') {
|
|
87
|
-
// Event binding
|
|
88
|
-
this._bindings.push({ event: property, handler: source });
|
|
89
|
-
} else {
|
|
90
|
-
// Validate it's a State object
|
|
91
|
-
if (!source || typeof source.subscribe !== 'function') {
|
|
92
|
-
throw new Error(`Paragraph.bind: Expected a State object, got ${typeof source}. Did you pass 'state.value' instead of 'state'?`);
|
|
93
|
-
}
|
|
94
|
-
// State binding
|
|
95
|
-
this._bindings.push({ event: property, handler: () => { }, stateObj: source, transform });
|
|
96
|
-
}
|
|
97
|
-
return this;
|
|
98
|
-
}
|
|
40
|
+
// ✅ Inherited from BaseComponent:
|
|
41
|
+
// - style(), class()
|
|
42
|
+
// - bind(), sync(), renderTo()
|
|
43
|
+
// - All other base methods
|
|
99
44
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
*
|
|
103
|
-
* @param property - Component property to sync ('text', 'class', 'style')
|
|
104
|
-
* @param stateObj - State object to sync with
|
|
105
|
-
* @param transform - Optional transform function when going from state to component
|
|
106
|
-
*/
|
|
107
|
-
sync(property: string, stateObj: State<any>, transform?: Function): this {
|
|
108
|
-
// Validate it's a State object
|
|
109
|
-
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
110
|
-
throw new Error(`Paragraph.sync: Expected a State object, got ${typeof stateObj}. Did you pass 'state.value' instead of 'state'?`);
|
|
111
|
-
}
|
|
112
|
-
this._syncBindings.push({ property, stateObj, transform });
|
|
45
|
+
text(value: string): this {
|
|
46
|
+
this.state.text = value;
|
|
113
47
|
return this;
|
|
114
48
|
}
|
|
115
49
|
|
|
116
|
-
/*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
|
|
120
|
-
render(targetId?: string | HTMLElement): this {
|
|
121
|
-
let container: HTMLElement;
|
|
122
|
-
|
|
123
|
-
if (targetId) {
|
|
124
|
-
if (targetId instanceof HTMLElement) {
|
|
125
|
-
container = targetId;
|
|
126
|
-
} else {
|
|
127
|
-
const target = document.querySelector(targetId);
|
|
128
|
-
if (!target || !(target instanceof HTMLElement)) {
|
|
129
|
-
throw new Error(`Paragraph: Target element "${targetId}" not found`);
|
|
130
|
-
}
|
|
131
|
-
container = target;
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
container = getOrCreateContainer(this._id);
|
|
135
|
-
}
|
|
50
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
51
|
+
* RENDER
|
|
52
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
136
53
|
|
|
137
|
-
|
|
138
|
-
const
|
|
54
|
+
render(targetId?: string): this {
|
|
55
|
+
const container = this._setupContainer(targetId);
|
|
139
56
|
|
|
140
|
-
const
|
|
141
|
-
p.id = this._id;
|
|
142
|
-
p.textContent = text;
|
|
57
|
+
const { text, style, class: className } = this.state;
|
|
143
58
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
59
|
+
const paragraph = document.createElement('p');
|
|
60
|
+
paragraph.className = 'jux-paragraph';
|
|
61
|
+
paragraph.id = this._id;
|
|
62
|
+
paragraph.textContent = text;
|
|
63
|
+
if (className) paragraph.className += ` ${className}`;
|
|
64
|
+
if (style) paragraph.setAttribute('style', style);
|
|
147
65
|
|
|
148
|
-
|
|
149
|
-
p.setAttribute('style', style);
|
|
150
|
-
}
|
|
66
|
+
this._wireStandardEvents(paragraph);
|
|
151
67
|
|
|
152
|
-
|
|
68
|
+
// Wire sync bindings
|
|
69
|
+
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
70
|
+
if (property === 'text') {
|
|
71
|
+
const transform = toComponent || ((v: any) => String(v));
|
|
153
72
|
|
|
154
|
-
// Wire up bind() bindings after DOM element is created
|
|
155
|
-
this._bindings.forEach(({ event, handler, stateObj, transform }) => {
|
|
156
|
-
if (stateObj) {
|
|
157
|
-
// State binding - subscribe to state changes
|
|
158
73
|
stateObj.subscribe((val: any) => {
|
|
159
|
-
const transformed = transform
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
this.state.text = transformed;
|
|
163
|
-
}
|
|
74
|
+
const transformed = transform(val);
|
|
75
|
+
paragraph.textContent = transformed;
|
|
76
|
+
this.state.text = transformed;
|
|
164
77
|
});
|
|
165
|
-
} else {
|
|
166
|
-
// Event binding
|
|
167
|
-
p.addEventListener(event, handler as EventListener);
|
|
168
78
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// Wire up sync() bindings (State → Component)
|
|
172
|
-
this._syncBindings.forEach(({ property, stateObj, transform }) => {
|
|
173
|
-
stateObj.subscribe((val: any) => {
|
|
174
|
-
const transformed = transform ? transform(val) : val;
|
|
79
|
+
else if (property === 'class') {
|
|
80
|
+
const transform = toComponent || ((v: any) => String(v));
|
|
175
81
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} else if (property === 'class') {
|
|
180
|
-
p.className = transformed;
|
|
82
|
+
stateObj.subscribe((val: any) => {
|
|
83
|
+
const transformed = transform(val);
|
|
84
|
+
paragraph.className = `jux-paragraph ${transformed}`;
|
|
181
85
|
this.state.class = transformed;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
this.state.style = transformed;
|
|
185
|
-
}
|
|
186
|
-
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
187
88
|
});
|
|
188
89
|
|
|
90
|
+
container.appendChild(paragraph);
|
|
189
91
|
return this;
|
|
190
92
|
}
|
|
191
93
|
}
|
|
192
94
|
|
|
193
|
-
/**
|
|
194
|
-
* Factory helper
|
|
195
|
-
*/
|
|
196
95
|
export function paragraph(id: string, options: ParagraphOptions = {}): Paragraph {
|
|
197
96
|
return new Paragraph(id, options);
|
|
198
97
|
}
|