juxscript 1.0.18 → 1.0.20
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/lib/components/alert.ts +124 -128
- package/lib/components/areachart.ts +169 -287
- package/lib/components/areachartsmooth.ts +2 -2
- package/lib/components/badge.ts +63 -72
- package/lib/components/barchart.ts +120 -48
- package/lib/components/button.ts +99 -101
- package/lib/components/card.ts +97 -121
- package/lib/components/chart-types.ts +159 -0
- package/lib/components/chart-utils.ts +160 -0
- package/lib/components/chart.ts +628 -48
- package/lib/components/checkbox.ts +137 -51
- package/lib/components/code.ts +89 -75
- package/lib/components/container.ts +1 -1
- package/lib/components/datepicker.ts +93 -78
- package/lib/components/dialog.ts +163 -130
- package/lib/components/divider.ts +111 -193
- package/lib/components/docs-data.json +711 -264
- package/lib/components/doughnutchart.ts +125 -57
- package/lib/components/dropdown.ts +172 -85
- package/lib/components/element.ts +66 -61
- package/lib/components/fileupload.ts +142 -171
- package/lib/components/heading.ts +64 -21
- package/lib/components/hero.ts +109 -34
- package/lib/components/icon.ts +247 -0
- package/lib/components/icons.ts +174 -0
- package/lib/components/include.ts +77 -2
- package/lib/components/input.ts +174 -125
- package/lib/components/list.ts +120 -79
- package/lib/components/menu.ts +97 -2
- package/lib/components/modal.ts +144 -63
- package/lib/components/nav.ts +153 -52
- package/lib/components/paragraph.ts +78 -28
- package/lib/components/progress.ts +83 -107
- package/lib/components/radio.ts +151 -52
- package/lib/components/select.ts +110 -102
- package/lib/components/sidebar.ts +148 -105
- package/lib/components/switch.ts +124 -125
- package/lib/components/table.ts +214 -137
- package/lib/components/tabs.ts +194 -113
- package/lib/components/theme-toggle.ts +38 -7
- package/lib/components/tooltip.ts +207 -47
- package/lib/jux.ts +24 -5
- package/lib/reactivity/state.ts +13 -299
- package/package.json +1 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
import { State } from '../reactivity/state.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Paragraph options
|
|
@@ -20,10 +21,6 @@ type ParagraphState = {
|
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Paragraph component - semantic paragraph element
|
|
23
|
-
*
|
|
24
|
-
* Usage:
|
|
25
|
-
* jux.paragraph('intro', { text: 'Welcome to JUX' }).render('#app');
|
|
26
|
-
* jux.paragraph('description').text('A simple framework').render('#app');
|
|
27
24
|
*/
|
|
28
25
|
export class Paragraph {
|
|
29
26
|
state: ParagraphState;
|
|
@@ -31,6 +28,15 @@ export class Paragraph {
|
|
|
31
28
|
_id: string;
|
|
32
29
|
id: string;
|
|
33
30
|
|
|
31
|
+
// CRITICAL: Store bind/sync instructions for deferred wiring
|
|
32
|
+
private _bindings: Array<{ event: string, handler: Function }> = [];
|
|
33
|
+
private _syncBindings: Array<{
|
|
34
|
+
property: string,
|
|
35
|
+
stateObj: State<any>,
|
|
36
|
+
toState?: Function,
|
|
37
|
+
toComponent?: Function
|
|
38
|
+
}> = [];
|
|
39
|
+
|
|
34
40
|
constructor(id: string, options: ParagraphOptions = {}) {
|
|
35
41
|
this._id = id;
|
|
36
42
|
this.id = id;
|
|
@@ -61,45 +67,89 @@ export class Paragraph {
|
|
|
61
67
|
return this;
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Bind event handler (stores for wiring in render)
|
|
72
|
+
* DOM events only: click, mouseenter, etc.
|
|
73
|
+
*/
|
|
74
|
+
bind(event: string, handler: Function): this {
|
|
75
|
+
this._bindings.push({ event, handler });
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sync with state (one-way: State → Component)
|
|
81
|
+
*
|
|
82
|
+
* @param property - Component property to sync ('text', 'class', 'style')
|
|
83
|
+
* @param stateObj - State object to sync with
|
|
84
|
+
* @param transform - Optional transform function from state to component
|
|
85
|
+
*/
|
|
86
|
+
sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
|
|
87
|
+
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
88
|
+
throw new Error(`Paragraph.sync: Expected a State object for property "${property}"`);
|
|
89
|
+
}
|
|
90
|
+
this._syncBindings.push({ property, stateObj, toState, toComponent });
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
64
94
|
/* -------------------------
|
|
65
95
|
* Render
|
|
66
96
|
* ------------------------- */
|
|
67
97
|
|
|
68
|
-
render(targetId?: string
|
|
98
|
+
render(targetId?: string): this {
|
|
99
|
+
// === 1. SETUP: Get or create container ===
|
|
69
100
|
let container: HTMLElement;
|
|
70
|
-
|
|
71
101
|
if (targetId) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const target = document.querySelector(targetId);
|
|
76
|
-
if (!target || !(target instanceof HTMLElement)) {
|
|
77
|
-
throw new Error(`Paragraph: Target element "${targetId}" not found`);
|
|
78
|
-
}
|
|
79
|
-
container = target;
|
|
102
|
+
const target = document.querySelector(targetId);
|
|
103
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
104
|
+
throw new Error(`Paragraph: Target "${targetId}" not found`);
|
|
80
105
|
}
|
|
106
|
+
container = target;
|
|
81
107
|
} else {
|
|
82
108
|
container = getOrCreateContainer(this._id);
|
|
83
109
|
}
|
|
84
|
-
|
|
85
110
|
this.container = container;
|
|
86
|
-
const { text, class: className, style } = this.state;
|
|
87
111
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
// === 2. PREPARE: Destructure state ===
|
|
113
|
+
const { text, style, class: className } = this.state;
|
|
114
|
+
|
|
115
|
+
// === 3. BUILD: Create DOM elements ===
|
|
116
|
+
const paragraph = document.createElement('p');
|
|
117
|
+
paragraph.className = 'jux-paragraph';
|
|
118
|
+
paragraph.id = this._id;
|
|
119
|
+
paragraph.textContent = text;
|
|
120
|
+
if (className) paragraph.className += ` ${className}`;
|
|
121
|
+
if (style) paragraph.setAttribute('style', style);
|
|
122
|
+
|
|
123
|
+
// === 4. WIRE: Attach event listeners and sync bindings ===
|
|
124
|
+
|
|
125
|
+
// Wire custom bindings from .bind() calls
|
|
126
|
+
this._bindings.forEach(({ event, handler }) => {
|
|
127
|
+
paragraph.addEventListener(event, handler as EventListener);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Wire sync bindings from .sync() calls
|
|
131
|
+
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
132
|
+
if (property === 'text') {
|
|
133
|
+
const transformToComponent = toComponent || ((v: any) => String(v));
|
|
134
|
+
|
|
135
|
+
stateObj.subscribe((val: any) => {
|
|
136
|
+
const transformed = transformToComponent(val);
|
|
137
|
+
paragraph.textContent = transformed;
|
|
138
|
+
this.state.text = transformed;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
91
142
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
143
|
+
// === 5. RENDER: Append to DOM and finalize ===
|
|
144
|
+
container.appendChild(paragraph);
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
95
147
|
|
|
96
|
-
|
|
97
|
-
|
|
148
|
+
renderTo(juxComponent: any): this {
|
|
149
|
+
if (!juxComponent?._id) {
|
|
150
|
+
throw new Error('Paragraph.renderTo: Invalid component');
|
|
98
151
|
}
|
|
99
|
-
|
|
100
|
-
container.appendChild(p);
|
|
101
|
-
|
|
102
|
-
return this;
|
|
152
|
+
return this.render(`#${juxComponent._id}`);
|
|
103
153
|
}
|
|
104
154
|
}
|
|
105
155
|
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { getOrCreateContainer } from './helpers.js';
|
|
2
2
|
import { State } from '../reactivity/state.js';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Progress component options
|
|
6
|
-
*/
|
|
7
4
|
export interface ProgressOptions {
|
|
8
5
|
value?: number;
|
|
9
6
|
max?: number;
|
|
@@ -17,9 +14,6 @@ export interface ProgressOptions {
|
|
|
17
14
|
class?: string;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
/**
|
|
21
|
-
* Progress component state
|
|
22
|
-
*/
|
|
23
17
|
type ProgressState = {
|
|
24
18
|
value: number;
|
|
25
19
|
max: number;
|
|
@@ -33,28 +27,20 @@ type ProgressState = {
|
|
|
33
27
|
class: string;
|
|
34
28
|
};
|
|
35
29
|
|
|
36
|
-
/**
|
|
37
|
-
* Progress component - Progress bar for loading/completion
|
|
38
|
-
*
|
|
39
|
-
* Usage:
|
|
40
|
-
* jux.progress('upload', {
|
|
41
|
-
* value: 45,
|
|
42
|
-
* max: 100,
|
|
43
|
-
* label: 'Uploading...',
|
|
44
|
-
* showPercentage: true,
|
|
45
|
-
* animated: true
|
|
46
|
-
* }).render('#app');
|
|
47
|
-
*
|
|
48
|
-
* // Update progress
|
|
49
|
-
* const prog = jux.progress('upload').render('#app');
|
|
50
|
-
* prog.value(75);
|
|
51
|
-
*/
|
|
52
30
|
export class Progress {
|
|
53
31
|
state: ProgressState;
|
|
54
32
|
container: HTMLElement | null = null;
|
|
55
33
|
_id: string;
|
|
56
34
|
id: string;
|
|
57
|
-
|
|
35
|
+
|
|
36
|
+
// CRITICAL: Store bind/sync instructions for deferred wiring
|
|
37
|
+
private _bindings: Array<{ event: string, handler: Function }> = [];
|
|
38
|
+
private _syncBindings: Array<{
|
|
39
|
+
property: string,
|
|
40
|
+
stateObj: State<any>,
|
|
41
|
+
toState?: Function,
|
|
42
|
+
toComponent?: Function
|
|
43
|
+
}> = [];
|
|
58
44
|
|
|
59
45
|
constructor(id: string, options: ProgressOptions = {}) {
|
|
60
46
|
this._id = id;
|
|
@@ -79,31 +65,29 @@ export class Progress {
|
|
|
79
65
|
* ------------------------- */
|
|
80
66
|
|
|
81
67
|
value(value: number): this {
|
|
82
|
-
this.state.value =
|
|
83
|
-
this.
|
|
68
|
+
this.state.value = value;
|
|
69
|
+
this._updateProgress();
|
|
84
70
|
return this;
|
|
85
71
|
}
|
|
86
72
|
|
|
87
73
|
max(value: number): this {
|
|
88
74
|
this.state.max = value;
|
|
75
|
+
this._updateProgress();
|
|
89
76
|
return this;
|
|
90
77
|
}
|
|
91
78
|
|
|
92
79
|
label(value: string): this {
|
|
93
80
|
this.state.label = value;
|
|
94
|
-
this._updateElement();
|
|
95
81
|
return this;
|
|
96
82
|
}
|
|
97
83
|
|
|
98
84
|
showPercentage(value: boolean): this {
|
|
99
85
|
this.state.showPercentage = value;
|
|
100
|
-
this._updateElement();
|
|
101
86
|
return this;
|
|
102
87
|
}
|
|
103
88
|
|
|
104
89
|
variant(value: 'default' | 'success' | 'warning' | 'error' | 'info'): this {
|
|
105
90
|
this.state.variant = value;
|
|
106
|
-
this._updateElement();
|
|
107
91
|
return this;
|
|
108
92
|
}
|
|
109
93
|
|
|
@@ -114,13 +98,11 @@ export class Progress {
|
|
|
114
98
|
|
|
115
99
|
striped(value: boolean): this {
|
|
116
100
|
this.state.striped = value;
|
|
117
|
-
this._updateElement();
|
|
118
101
|
return this;
|
|
119
102
|
}
|
|
120
103
|
|
|
121
104
|
animated(value: boolean): this {
|
|
122
105
|
this.state.animated = value;
|
|
123
|
-
this._updateElement();
|
|
124
106
|
return this;
|
|
125
107
|
}
|
|
126
108
|
|
|
@@ -134,131 +116,125 @@ export class Progress {
|
|
|
134
116
|
return this;
|
|
135
117
|
}
|
|
136
118
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
this._boundState = stateObj;
|
|
142
|
-
|
|
143
|
-
stateObj.subscribe((val) => {
|
|
144
|
-
this.value(val);
|
|
145
|
-
});
|
|
119
|
+
bind(event: string, handler: Function): this {
|
|
120
|
+
this._bindings.push({ event, handler });
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
146
123
|
|
|
124
|
+
sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
|
|
125
|
+
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
126
|
+
throw new Error(`Progress.sync: Expected a State object for property "${property}"`);
|
|
127
|
+
}
|
|
128
|
+
this._syncBindings.push({ property, stateObj, toState, toComponent });
|
|
147
129
|
return this;
|
|
148
130
|
}
|
|
149
131
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
132
|
+
getPercentage(): number {
|
|
133
|
+
return Math.round((this.state.value / this.state.max) * 100);
|
|
134
|
+
}
|
|
153
135
|
|
|
154
|
-
private
|
|
155
|
-
const wrapper = document.getElementById(this._id);
|
|
136
|
+
private _updateProgress(): void {
|
|
156
137
|
const bar = document.getElementById(`${this._id}-bar`);
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
// If element has a value attribute set externally (e.g., by bindValue), sync state
|
|
160
|
-
if (wrapper && wrapper.hasAttribute('data-value')) {
|
|
161
|
-
const externalValue = parseFloat(wrapper.getAttribute('data-value') || '0');
|
|
162
|
-
this.state.value = externalValue;
|
|
163
|
-
}
|
|
138
|
+
const percentageEl = document.getElementById(`${this._id}-percentage`);
|
|
164
139
|
|
|
165
140
|
if (bar) {
|
|
166
|
-
const percentage =
|
|
141
|
+
const percentage = this.getPercentage();
|
|
167
142
|
bar.style.width = `${percentage}%`;
|
|
168
|
-
bar.setAttribute('aria-valuenow', this.state.value.toString());
|
|
169
|
-
bar.className = `jux-progress-bar jux-progress-bar-${this.state.variant}`;
|
|
170
143
|
|
|
171
|
-
if (
|
|
172
|
-
|
|
144
|
+
if (percentageEl) {
|
|
145
|
+
percentageEl.textContent = `${percentage}%`;
|
|
173
146
|
}
|
|
174
|
-
if (this.state.animated) {
|
|
175
|
-
bar.classList.add('jux-progress-bar-animated');
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (labelEl) {
|
|
180
|
-
const percentage = Math.round((this.state.value / this.state.max) * 100);
|
|
181
|
-
const text = this.state.showPercentage
|
|
182
|
-
? `${this.state.label} ${percentage}%`.trim()
|
|
183
|
-
: this.state.label;
|
|
184
|
-
labelEl.textContent = text;
|
|
185
147
|
}
|
|
186
148
|
}
|
|
187
149
|
|
|
188
|
-
getPercentage(): number {
|
|
189
|
-
return Math.round((this.state.value / this.state.max) * 100);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
150
|
/* -------------------------
|
|
193
151
|
* Render
|
|
194
152
|
* ------------------------- */
|
|
195
153
|
|
|
196
154
|
render(targetId?: string): this {
|
|
155
|
+
// === 1. SETUP: Get or create container ===
|
|
197
156
|
let container: HTMLElement;
|
|
198
|
-
|
|
199
157
|
if (targetId) {
|
|
200
158
|
const target = document.querySelector(targetId);
|
|
201
159
|
if (!target || !(target instanceof HTMLElement)) {
|
|
202
|
-
throw new Error(`Progress: Target
|
|
160
|
+
throw new Error(`Progress: Target "${targetId}" not found`);
|
|
203
161
|
}
|
|
204
162
|
container = target;
|
|
205
163
|
} else {
|
|
206
164
|
container = getOrCreateContainer(this._id);
|
|
207
165
|
}
|
|
208
|
-
|
|
209
166
|
this.container = container;
|
|
210
|
-
const { value, max, label, showPercentage, variant, size, striped, animated, style, class: className } = this.state;
|
|
211
167
|
|
|
168
|
+
// === 2. PREPARE: Destructure state ===
|
|
169
|
+
const { value, max, variant, showPercentage, label, style, class: className } = this.state;
|
|
170
|
+
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
|
171
|
+
|
|
172
|
+
// === 3. BUILD: Create DOM elements ===
|
|
212
173
|
const wrapper = document.createElement('div');
|
|
213
|
-
wrapper.className =
|
|
174
|
+
wrapper.className = 'jux-progress';
|
|
214
175
|
wrapper.id = this._id;
|
|
176
|
+
if (className) wrapper.className += ` ${className}`;
|
|
177
|
+
if (style) wrapper.setAttribute('style', style);
|
|
215
178
|
|
|
216
|
-
if (
|
|
217
|
-
wrapper.className += ` ${className}`;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (style) {
|
|
221
|
-
wrapper.setAttribute('style', style);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Label
|
|
225
|
-
if (label || showPercentage) {
|
|
179
|
+
if (showPercentage || label) {
|
|
226
180
|
const labelEl = document.createElement('div');
|
|
227
181
|
labelEl.className = 'jux-progress-label';
|
|
228
|
-
labelEl.
|
|
229
|
-
const percentage = Math.round((value / max) * 100);
|
|
230
|
-
const text = showPercentage
|
|
231
|
-
? `${label} ${percentage}%`.trim()
|
|
232
|
-
: label;
|
|
233
|
-
labelEl.textContent = text;
|
|
182
|
+
labelEl.textContent = label || `${Math.round(percentage)}%`;
|
|
234
183
|
wrapper.appendChild(labelEl);
|
|
235
184
|
}
|
|
236
185
|
|
|
237
|
-
// Progress track
|
|
238
186
|
const track = document.createElement('div');
|
|
239
187
|
track.className = 'jux-progress-track';
|
|
240
188
|
|
|
241
|
-
// Progress bar
|
|
242
189
|
const bar = document.createElement('div');
|
|
243
190
|
bar.className = `jux-progress-bar jux-progress-bar-${variant}`;
|
|
244
191
|
bar.id = `${this._id}-bar`;
|
|
245
|
-
bar.setAttribute('role', 'progressbar');
|
|
246
|
-
bar.setAttribute('aria-valuenow', value.toString());
|
|
247
|
-
bar.setAttribute('aria-valuemin', '0');
|
|
248
|
-
bar.setAttribute('aria-valuemax', max.toString());
|
|
249
|
-
|
|
250
|
-
const percentage = (value / max) * 100;
|
|
251
192
|
bar.style.width = `${percentage}%`;
|
|
252
193
|
|
|
253
|
-
if (striped) {
|
|
254
|
-
bar.classList.add('jux-progress-bar-striped');
|
|
255
|
-
}
|
|
256
|
-
if (animated) {
|
|
257
|
-
bar.classList.add('jux-progress-bar-animated');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
194
|
track.appendChild(bar);
|
|
261
195
|
wrapper.appendChild(track);
|
|
196
|
+
|
|
197
|
+
// === 4. WIRE: Attach event listeners and sync bindings ===
|
|
198
|
+
|
|
199
|
+
// Wire custom bindings from .bind() calls
|
|
200
|
+
this._bindings.forEach(({ event, handler }) => {
|
|
201
|
+
wrapper.addEventListener(event, handler as EventListener);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Wire sync bindings from .sync() calls
|
|
205
|
+
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
206
|
+
if (property === 'value') {
|
|
207
|
+
const transformToComponent = toComponent || ((v: any) => Number(v));
|
|
208
|
+
|
|
209
|
+
stateObj.subscribe((val: any) => {
|
|
210
|
+
const transformed = transformToComponent(val);
|
|
211
|
+
this.state.value = transformed;
|
|
212
|
+
const newPercentage = Math.min(100, Math.max(0, (transformed / this.state.max) * 100));
|
|
213
|
+
bar.style.width = `${newPercentage}%`;
|
|
214
|
+
|
|
215
|
+
if (showPercentage && !label) {
|
|
216
|
+
const labelEl = wrapper.querySelector('.jux-progress-label');
|
|
217
|
+
if (labelEl) {
|
|
218
|
+
labelEl.textContent = `${Math.round(newPercentage)}%`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
else if (property === 'label') {
|
|
224
|
+
const transformToComponent = toComponent || ((v: any) => String(v));
|
|
225
|
+
|
|
226
|
+
stateObj.subscribe((val: any) => {
|
|
227
|
+
const transformed = transformToComponent(val);
|
|
228
|
+
const labelEl = wrapper.querySelector('.jux-progress-label');
|
|
229
|
+
if (labelEl) {
|
|
230
|
+
labelEl.textContent = transformed;
|
|
231
|
+
}
|
|
232
|
+
this.state.label = transformed;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// === 5. RENDER: Append to DOM and finalize ===
|
|
262
238
|
container.appendChild(wrapper);
|
|
263
239
|
return this;
|
|
264
240
|
}
|