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.
Files changed (44) hide show
  1. package/lib/components/alert.ts +124 -128
  2. package/lib/components/areachart.ts +169 -287
  3. package/lib/components/areachartsmooth.ts +2 -2
  4. package/lib/components/badge.ts +63 -72
  5. package/lib/components/barchart.ts +120 -48
  6. package/lib/components/button.ts +99 -101
  7. package/lib/components/card.ts +97 -121
  8. package/lib/components/chart-types.ts +159 -0
  9. package/lib/components/chart-utils.ts +160 -0
  10. package/lib/components/chart.ts +628 -48
  11. package/lib/components/checkbox.ts +137 -51
  12. package/lib/components/code.ts +89 -75
  13. package/lib/components/container.ts +1 -1
  14. package/lib/components/datepicker.ts +93 -78
  15. package/lib/components/dialog.ts +163 -130
  16. package/lib/components/divider.ts +111 -193
  17. package/lib/components/docs-data.json +711 -264
  18. package/lib/components/doughnutchart.ts +125 -57
  19. package/lib/components/dropdown.ts +172 -85
  20. package/lib/components/element.ts +66 -61
  21. package/lib/components/fileupload.ts +142 -171
  22. package/lib/components/heading.ts +64 -21
  23. package/lib/components/hero.ts +109 -34
  24. package/lib/components/icon.ts +247 -0
  25. package/lib/components/icons.ts +174 -0
  26. package/lib/components/include.ts +77 -2
  27. package/lib/components/input.ts +174 -125
  28. package/lib/components/list.ts +120 -79
  29. package/lib/components/menu.ts +97 -2
  30. package/lib/components/modal.ts +144 -63
  31. package/lib/components/nav.ts +153 -52
  32. package/lib/components/paragraph.ts +78 -28
  33. package/lib/components/progress.ts +83 -107
  34. package/lib/components/radio.ts +151 -52
  35. package/lib/components/select.ts +110 -102
  36. package/lib/components/sidebar.ts +148 -105
  37. package/lib/components/switch.ts +124 -125
  38. package/lib/components/table.ts +214 -137
  39. package/lib/components/tabs.ts +194 -113
  40. package/lib/components/theme-toggle.ts +38 -7
  41. package/lib/components/tooltip.ts +207 -47
  42. package/lib/jux.ts +24 -5
  43. package/lib/reactivity/state.ts +13 -299
  44. package/package.json +1 -2
@@ -1,63 +1,48 @@
1
1
  import { getOrCreateContainer } from './helpers.js';
2
2
  import { State } from '../reactivity/state.js';
3
3
 
4
- /**
5
- * Switch component options
6
- */
7
4
  export interface SwitchOptions {
8
- label?: string;
9
5
  checked?: boolean;
10
- disabled?: boolean;
6
+ label?: string;
11
7
  name?: string;
12
- onChange?: (checked: boolean) => void;
8
+ disabled?: boolean;
13
9
  style?: string;
14
10
  class?: string;
15
11
  }
16
12
 
17
- /**
18
- * Switch component state
19
- */
20
13
  type SwitchState = {
21
- label: string;
22
14
  checked: boolean;
23
- disabled: boolean;
15
+ label: string;
24
16
  name: string;
17
+ disabled: boolean;
25
18
  style: string;
26
19
  class: string;
27
20
  };
28
21
 
29
- /**
30
- * Switch component - Toggle switch for boolean values
31
- *
32
- * Usage:
33
- * jux.switch('notifications', {
34
- * label: 'Enable notifications',
35
- * checked: true,
36
- * onChange: (checked) => console.log(checked)
37
- * }).render('#settings');
38
- *
39
- * // Two-way binding
40
- * const notificationsState = state(true);
41
- * jux.switch('notifications').label('Notifications').bind(notificationsState).render('#settings');
42
- */
43
22
  export class Switch {
44
23
  state: SwitchState;
45
24
  container: HTMLElement | null = null;
46
25
  _id: string;
47
26
  id: string;
48
- private _onChange?: (checked: boolean) => void;
49
- private _boundState?: State<boolean>;
27
+
28
+ // CRITICAL: Store bind/sync instructions for deferred wiring
29
+ private _bindings: Array<{ event: string, handler: Function }> = [];
30
+ private _syncBindings: Array<{
31
+ property: string,
32
+ stateObj: State<any>,
33
+ toState?: Function,
34
+ toComponent?: Function
35
+ }> = [];
50
36
 
51
37
  constructor(id: string, options: SwitchOptions = {}) {
52
38
  this._id = id;
53
39
  this.id = id;
54
- this._onChange = options.onChange;
55
40
 
56
41
  this.state = {
57
- label: options.label ?? '',
58
42
  checked: options.checked ?? false,
59
- disabled: options.disabled ?? false,
43
+ label: options.label ?? '',
60
44
  name: options.name ?? id,
45
+ disabled: options.disabled ?? false,
61
46
  style: options.style ?? '',
62
47
  class: options.class ?? ''
63
48
  };
@@ -67,20 +52,14 @@ export class Switch {
67
52
  * Fluent API
68
53
  * ------------------------- */
69
54
 
70
- label(value: string): this {
71
- this.state.label = value;
72
- return this;
73
- }
74
-
75
55
  checked(value: boolean): this {
76
56
  this.state.checked = value;
77
57
  this._updateElement();
78
58
  return this;
79
59
  }
80
60
 
81
- disabled(value: boolean): this {
82
- this.state.disabled = value;
83
- this._updateElement();
61
+ label(value: string): this {
62
+ this.state.label = value;
84
63
  return this;
85
64
  }
86
65
 
@@ -89,6 +68,12 @@ export class Switch {
89
68
  return this;
90
69
  }
91
70
 
71
+ disabled(value: boolean): this {
72
+ this.state.disabled = value;
73
+ this._updateElement();
74
+ return this;
75
+ }
76
+
92
77
  style(value: string): this {
93
78
  this.state.style = value;
94
79
  return this;
@@ -99,26 +84,28 @@ export class Switch {
99
84
  return this;
100
85
  }
101
86
 
102
- onChange(handler: (checked: boolean) => void): this {
103
- this._onChange = handler;
87
+ /**
88
+ * Bind event handler (stores for wiring in render)
89
+ * DOM events only: change, click, etc.
90
+ */
91
+ bind(event: string, handler: Function): this {
92
+ this._bindings.push({ event, handler });
104
93
  return this;
105
94
  }
106
95
 
107
96
  /**
108
- * Two-way binding to state
97
+ * Two-way sync with state (stores for wiring in render)
98
+ *
99
+ * @param property - Component property to sync ('checked', 'label', 'disabled')
100
+ * @param stateObj - State object to sync with
101
+ * @param toState - Optional transform function when going from component to state
102
+ * @param toComponent - Optional transform function when going from state to component
109
103
  */
110
- bind(stateObj: State<boolean>): this {
111
- this._boundState = stateObj;
112
-
113
- // Update switch when state changes
114
- stateObj.subscribe((val) => {
115
- this.state.checked = val;
116
- this._updateElement();
117
- });
118
-
119
- // Update state when switch changes
120
- this.onChange((checked) => stateObj.set(checked));
121
-
104
+ sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
105
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
106
+ throw new Error(`Switch.sync: Expected a State object for property "${property}"`);
107
+ }
108
+ this._syncBindings.push({ property, stateObj, toState, toComponent });
122
109
  return this;
123
110
  }
124
111
 
@@ -128,69 +115,44 @@ export class Switch {
128
115
 
129
116
  private _updateElement(): void {
130
117
  const input = document.getElementById(`${this._id}-input`) as HTMLInputElement;
131
- const track = document.getElementById(`${this._id}-track`);
132
-
133
118
  if (input) {
134
119
  input.checked = this.state.checked;
135
120
  input.disabled = this.state.disabled;
136
121
  }
137
-
138
- if (track) {
139
- if (this.state.checked) {
140
- track.classList.add('jux-switch-track-checked');
141
- } else {
142
- track.classList.remove('jux-switch-track-checked');
143
- }
144
- }
145
- }
146
-
147
- /**
148
- * Toggle the switch
149
- */
150
- toggle(): this {
151
- this.state.checked = !this.state.checked;
152
- this._updateElement();
153
- if (this._onChange) {
154
- this._onChange(this.state.checked);
155
- }
156
- return this;
157
122
  }
158
123
 
159
- isChecked(): boolean {
124
+ getValue(): boolean {
160
125
  return this.state.checked;
161
126
  }
162
127
 
163
128
  /* -------------------------
164
- * Render
129
+ * Render (5-Step Pattern)
165
130
  * ------------------------- */
166
131
 
167
132
  render(targetId?: string): this {
133
+ // === 1. SETUP: Get or create container ===
168
134
  let container: HTMLElement;
169
-
170
135
  if (targetId) {
171
136
  const target = document.querySelector(targetId);
172
137
  if (!target || !(target instanceof HTMLElement)) {
173
- throw new Error(`Switch: Target element "${targetId}" not found`);
138
+ throw new Error(`Switch: Target "${targetId}" not found`);
174
139
  }
175
140
  container = target;
176
141
  } else {
177
142
  container = getOrCreateContainer(this._id);
178
143
  }
179
-
180
144
  this.container = container;
181
- const { label, checked, disabled, name, style, class: className } = this.state;
182
145
 
146
+ // === 2. PREPARE: Destructure state and check sync flags ===
147
+ const { checked, label, disabled, name, style, class: className } = this.state;
148
+ const hasCheckedSync = this._syncBindings.some(b => b.property === 'checked');
149
+
150
+ // === 3. BUILD: Create DOM elements ===
183
151
  const wrapper = document.createElement('div');
184
152
  wrapper.className = 'jux-switch';
185
153
  wrapper.id = this._id;
186
-
187
- if (className) {
188
- wrapper.className += ` ${className}`;
189
- }
190
-
191
- if (style) {
192
- wrapper.setAttribute('style', style);
193
- }
154
+ if (className) wrapper.className += ` ${className}`;
155
+ if (style) wrapper.setAttribute('style', style);
194
156
 
195
157
  const labelEl = document.createElement('label');
196
158
  labelEl.className = 'jux-switch-label';
@@ -202,48 +164,84 @@ export class Switch {
202
164
  input.name = name;
203
165
  input.checked = checked;
204
166
  input.disabled = disabled;
205
- input.setAttribute('role', 'switch');
206
-
207
- input.addEventListener('change', (e) => {
208
- const target = e.target as HTMLInputElement;
209
- this.state.checked = target.checked;
210
-
211
- const track = document.getElementById(`${this._id}-track`);
212
- if (track) {
213
- if (target.checked) {
214
- track.classList.add('jux-switch-track-checked');
215
- } else {
216
- track.classList.remove('jux-switch-track-checked');
217
- }
218
- }
219
167
 
220
- if (this._onChange) {
221
- this._onChange(target.checked);
222
- }
223
- });
224
-
225
- const track = document.createElement('span');
226
- track.className = 'jux-switch-track';
227
- track.id = `${this._id}-track`;
228
- if (checked) {
229
- track.classList.add('jux-switch-track-checked');
230
- }
168
+ const slider = document.createElement('span');
169
+ slider.className = 'jux-switch-slider';
231
170
 
232
- const thumb = document.createElement('span');
233
- thumb.className = 'jux-switch-thumb';
171
+ const text = document.createElement('span');
172
+ text.className = 'jux-switch-text';
173
+ text.textContent = label;
234
174
 
235
- track.appendChild(thumb);
236
175
  labelEl.appendChild(input);
237
- labelEl.appendChild(track);
176
+ labelEl.appendChild(slider);
177
+ if (label) labelEl.appendChild(text);
178
+ wrapper.appendChild(labelEl);
179
+
180
+ // === 4. WIRE: Attach event listeners and sync bindings ===
238
181
 
239
- if (label) {
240
- const span = document.createElement('span');
241
- span.className = 'jux-switch-text';
242
- span.textContent = label;
243
- labelEl.appendChild(span);
182
+ // Default behavior (only if NOT using sync)
183
+ if (!hasCheckedSync) {
184
+ input.addEventListener('change', () => {
185
+ this.state.checked = input.checked;
186
+ });
244
187
  }
245
188
 
246
- wrapper.appendChild(labelEl);
189
+ // Wire custom bindings from .bind() calls
190
+ this._bindings.forEach(({ event, handler }) => {
191
+ wrapper.addEventListener(event, handler as EventListener);
192
+ });
193
+
194
+ // Wire sync bindings from .sync() calls
195
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
196
+ if (property === 'checked') {
197
+ const transformToState = toState || ((v: any) => Boolean(v));
198
+ const transformToComponent = toComponent || ((v: any) => Boolean(v));
199
+
200
+ let isUpdating = false;
201
+
202
+ // State → Component
203
+ stateObj.subscribe((val: any) => {
204
+ if (isUpdating) return;
205
+ const transformed = transformToComponent(val);
206
+ if (input.checked !== transformed) {
207
+ input.checked = transformed;
208
+ this.state.checked = transformed;
209
+ }
210
+ });
211
+
212
+ // Component → State
213
+ input.addEventListener('change', () => {
214
+ if (isUpdating) return;
215
+ isUpdating = true;
216
+
217
+ const transformed = transformToState(input.checked);
218
+ this.state.checked = input.checked;
219
+ stateObj.set(transformed);
220
+
221
+ setTimeout(() => { isUpdating = false; }, 0);
222
+ });
223
+ }
224
+ else if (property === 'label') {
225
+ const transformToComponent = toComponent || ((v: any) => String(v));
226
+
227
+ stateObj.subscribe((val: any) => {
228
+ const transformed = transformToComponent(val);
229
+ text.textContent = transformed;
230
+ this.state.label = transformed;
231
+ });
232
+ }
233
+ else if (property === 'disabled') {
234
+ const transformToComponent = toComponent || ((v: any) => Boolean(v));
235
+
236
+ stateObj.subscribe((val: any) => {
237
+ const transformed = transformToComponent(val);
238
+ input.disabled = transformed;
239
+ this.state.disabled = transformed;
240
+ });
241
+ }
242
+ });
243
+
244
+ // === 5. RENDER: Append to DOM and finalize ===
247
245
  container.appendChild(wrapper);
248
246
  return this;
249
247
  }
@@ -258,4 +256,5 @@ export class Switch {
258
256
 
259
257
  export function switchComponent(id: string, options: SwitchOptions = {}): Switch {
260
258
  return new Switch(id, options);
261
- }
259
+ }
260
+