juxscript 1.1.4 → 1.1.6

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 (205) hide show
  1. package/index.d.ts +10 -10
  2. package/index.d.ts.map +1 -0
  3. package/lib/components/alert.d.ts +32 -0
  4. package/lib/components/alert.d.ts.map +1 -0
  5. package/lib/components/alert.js +153 -0
  6. package/lib/components/alert.ts +200 -0
  7. package/lib/components/app.d.ts +89 -0
  8. package/lib/components/app.d.ts.map +1 -0
  9. package/lib/components/app.js +175 -0
  10. package/lib/components/app.ts +247 -0
  11. package/lib/components/badge.d.ts +27 -0
  12. package/lib/components/badge.d.ts.map +1 -0
  13. package/lib/components/badge.js +70 -0
  14. package/lib/components/badge.ts +101 -0
  15. package/lib/components/base/BaseComponent.d.ts +142 -0
  16. package/lib/components/base/BaseComponent.d.ts.map +1 -0
  17. package/lib/components/base/BaseComponent.js +363 -0
  18. package/lib/components/base/BaseComponent.ts +421 -0
  19. package/lib/components/base/FormInput.d.ts +73 -0
  20. package/lib/components/base/FormInput.d.ts.map +1 -0
  21. package/lib/components/base/FormInput.js +163 -0
  22. package/lib/components/base/FormInput.ts +227 -0
  23. package/lib/components/button.d.ts +48 -0
  24. package/lib/components/button.d.ts.map +1 -0
  25. package/lib/components/button.js +121 -0
  26. package/lib/components/button.ts +178 -0
  27. package/lib/components/card.d.ts +34 -0
  28. package/lib/components/card.d.ts.map +1 -0
  29. package/lib/components/card.js +127 -0
  30. package/lib/components/card.ts +173 -0
  31. package/lib/components/chart.d.ts +45 -0
  32. package/lib/components/chart.d.ts.map +1 -0
  33. package/lib/components/chart.js +186 -0
  34. package/lib/components/chart.ts +231 -0
  35. package/lib/components/checkbox.d.ts +31 -0
  36. package/lib/components/checkbox.d.ts.map +1 -0
  37. package/lib/components/checkbox.js +185 -0
  38. package/lib/components/checkbox.ts +242 -0
  39. package/lib/components/code.d.ts +24 -0
  40. package/lib/components/code.d.ts.map +1 -0
  41. package/lib/components/code.js +88 -0
  42. package/lib/components/code.ts +123 -0
  43. package/lib/components/container.d.ts +42 -0
  44. package/lib/components/container.d.ts.map +1 -0
  45. package/lib/components/container.js +93 -0
  46. package/lib/components/container.ts +140 -0
  47. package/lib/components/data.d.ts +36 -0
  48. package/lib/components/data.d.ts.map +1 -0
  49. package/lib/components/data.js +110 -0
  50. package/lib/components/data.ts +135 -0
  51. package/lib/components/datepicker.d.ts +38 -0
  52. package/lib/components/datepicker.d.ts.map +1 -0
  53. package/lib/components/datepicker.js +177 -0
  54. package/lib/components/datepicker.ts +234 -0
  55. package/lib/components/dialog.d.ts +38 -0
  56. package/lib/components/dialog.d.ts.map +1 -0
  57. package/lib/components/dialog.js +126 -0
  58. package/lib/components/dialog.ts +172 -0
  59. package/lib/components/divider.d.ts +30 -0
  60. package/lib/components/divider.d.ts.map +1 -0
  61. package/lib/components/divider.js +69 -0
  62. package/lib/components/divider.ts +100 -0
  63. package/lib/components/dropdown.d.ts +39 -0
  64. package/lib/components/dropdown.d.ts.map +1 -0
  65. package/lib/components/dropdown.js +133 -0
  66. package/lib/components/dropdown.ts +186 -0
  67. package/lib/components/element.d.ts +50 -0
  68. package/lib/components/element.d.ts.map +1 -0
  69. package/lib/components/element.js +206 -0
  70. package/lib/components/element.ts +267 -0
  71. package/lib/components/fileupload.d.ts +40 -0
  72. package/lib/components/fileupload.d.ts.map +1 -0
  73. package/lib/components/fileupload.js +241 -0
  74. package/lib/components/fileupload.ts +309 -0
  75. package/lib/components/grid.d.ts +87 -0
  76. package/lib/components/grid.d.ts.map +1 -0
  77. package/lib/components/grid.js +205 -0
  78. package/lib/components/grid.ts +291 -0
  79. package/lib/components/guard.d.ts +41 -0
  80. package/lib/components/guard.d.ts.map +1 -0
  81. package/lib/components/guard.js +56 -0
  82. package/lib/components/guard.ts +92 -0
  83. package/lib/components/heading.d.ts +24 -0
  84. package/lib/components/heading.d.ts.map +1 -0
  85. package/lib/components/heading.js +67 -0
  86. package/lib/components/heading.ts +96 -0
  87. package/lib/components/helpers.d.ts +9 -0
  88. package/lib/components/helpers.d.ts.map +1 -0
  89. package/lib/components/helpers.js +30 -0
  90. package/lib/components/helpers.ts +41 -0
  91. package/lib/components/hero.d.ts +45 -0
  92. package/lib/components/hero.d.ts.map +1 -0
  93. package/lib/components/hero.js +165 -0
  94. package/lib/components/hero.ts +224 -0
  95. package/lib/components/icon.d.ts +35 -0
  96. package/lib/components/icon.d.ts.map +1 -0
  97. package/lib/components/icon.js +132 -0
  98. package/lib/components/icon.ts +178 -0
  99. package/lib/components/icons.d.ts +25 -0
  100. package/lib/components/icons.d.ts.map +1 -0
  101. package/lib/components/icons.js +440 -0
  102. package/lib/components/icons.ts +464 -0
  103. package/lib/components/include.d.ts +120 -0
  104. package/lib/components/include.d.ts.map +1 -0
  105. package/lib/components/include.js +350 -0
  106. package/lib/components/include.ts +410 -0
  107. package/lib/components/input.d.ts +83 -0
  108. package/lib/components/input.d.ts.map +1 -0
  109. package/lib/components/input.js +348 -0
  110. package/lib/components/input.ts +457 -0
  111. package/lib/components/list.d.ts +82 -0
  112. package/lib/components/list.d.ts.map +1 -0
  113. package/lib/components/list.js +311 -0
  114. package/lib/components/list.ts +419 -0
  115. package/lib/components/loading.d.ts +24 -0
  116. package/lib/components/loading.d.ts.map +1 -0
  117. package/lib/components/loading.js +73 -0
  118. package/lib/components/loading.ts +100 -0
  119. package/lib/components/menu.d.ts +37 -0
  120. package/lib/components/menu.d.ts.map +1 -0
  121. package/lib/components/menu.js +202 -0
  122. package/lib/components/menu.ts +275 -0
  123. package/lib/components/modal.d.ts +51 -0
  124. package/lib/components/modal.d.ts.map +1 -0
  125. package/lib/components/modal.js +227 -0
  126. package/lib/components/modal.ts +284 -0
  127. package/lib/components/nav.d.ts +45 -0
  128. package/lib/components/nav.d.ts.map +1 -0
  129. package/lib/components/nav.js +190 -0
  130. package/lib/components/nav.ts +257 -0
  131. package/lib/components/paragraph.d.ts +21 -0
  132. package/lib/components/paragraph.d.ts.map +1 -0
  133. package/lib/components/paragraph.js +70 -0
  134. package/lib/components/paragraph.ts +97 -0
  135. package/lib/components/progress.d.ts +39 -0
  136. package/lib/components/progress.d.ts.map +1 -0
  137. package/lib/components/progress.js +113 -0
  138. package/lib/components/progress.ts +159 -0
  139. package/lib/components/radio.d.ts +41 -0
  140. package/lib/components/radio.d.ts.map +1 -0
  141. package/lib/components/radio.js +203 -0
  142. package/lib/components/radio.ts +278 -0
  143. package/lib/components/req.d.ts +155 -0
  144. package/lib/components/req.d.ts.map +1 -0
  145. package/lib/components/req.js +253 -0
  146. package/lib/components/req.ts +303 -0
  147. package/lib/components/script.d.ts +14 -0
  148. package/lib/components/script.d.ts.map +1 -0
  149. package/lib/components/script.js +33 -0
  150. package/lib/components/script.ts +41 -0
  151. package/lib/components/select.d.ts +40 -0
  152. package/lib/components/select.d.ts.map +1 -0
  153. package/lib/components/select.js +183 -0
  154. package/lib/components/select.ts +252 -0
  155. package/lib/components/sidebar.d.ts +48 -0
  156. package/lib/components/sidebar.d.ts.map +1 -0
  157. package/lib/components/sidebar.js +207 -0
  158. package/lib/components/sidebar.ts +275 -0
  159. package/lib/components/style.d.ts +14 -0
  160. package/lib/components/style.d.ts.map +1 -0
  161. package/lib/components/style.js +33 -0
  162. package/lib/components/style.ts +41 -0
  163. package/lib/components/switch.d.ts +32 -0
  164. package/lib/components/switch.d.ts.map +1 -0
  165. package/lib/components/switch.js +186 -0
  166. package/lib/components/switch.ts +246 -0
  167. package/lib/components/table.d.ts +137 -0
  168. package/lib/components/table.d.ts.map +1 -0
  169. package/lib/components/table.js +1045 -0
  170. package/lib/components/table.ts +1249 -0
  171. package/lib/components/tabs.d.ts +36 -0
  172. package/lib/components/tabs.d.ts.map +1 -0
  173. package/lib/components/tabs.js +198 -0
  174. package/lib/components/tabs.ts +250 -0
  175. package/lib/components/theme-toggle.d.ts +44 -0
  176. package/lib/components/theme-toggle.d.ts.map +1 -0
  177. package/lib/components/theme-toggle.js +215 -0
  178. package/lib/components/theme-toggle.ts +293 -0
  179. package/lib/components/tooltip.d.ts +30 -0
  180. package/lib/components/tooltip.d.ts.map +1 -0
  181. package/lib/components/tooltip.js +109 -0
  182. package/lib/components/tooltip.ts +144 -0
  183. package/lib/components/view.d.ts +48 -0
  184. package/lib/components/view.d.ts.map +1 -0
  185. package/lib/components/view.js +149 -0
  186. package/lib/components/view.ts +190 -0
  187. package/lib/components/write.d.ts +107 -0
  188. package/lib/components/write.d.ts.map +1 -0
  189. package/lib/components/write.js +222 -0
  190. package/lib/components/write.ts +272 -0
  191. package/lib/layouts/default.css +260 -0
  192. package/lib/layouts/figma.css +334 -0
  193. package/lib/reactivity/state.d.ts +36 -0
  194. package/lib/reactivity/state.d.ts.map +1 -0
  195. package/lib/reactivity/state.js +67 -0
  196. package/lib/reactivity/state.ts +78 -0
  197. package/lib/utils/fetch.d.ts +176 -0
  198. package/lib/utils/fetch.d.ts.map +1 -0
  199. package/lib/utils/fetch.js +427 -0
  200. package/lib/utils/fetch.ts +553 -0
  201. package/machinery/compiler3.js +78 -0
  202. package/machinery/doc-generator.js +136 -0
  203. package/machinery/imports.js +155 -0
  204. package/machinery/ts-shim.js +46 -0
  205. package/package.json +9 -15
@@ -0,0 +1,421 @@
1
+ import { State } from '../../reactivity/state.js';
2
+ import { getOrCreateContainer } from '../helpers.js';
3
+
4
+ /**
5
+ * Abstract base class for all JUX components
6
+ * Provides common storage, event routing, and lifecycle methods
7
+ *
8
+ * Children must provide:
9
+ * - TRIGGER_EVENTS constant (readonly string[])
10
+ * - CALLBACK_EVENTS constant (readonly string[])
11
+ * - render() implementation
12
+ */
13
+ export abstract class BaseComponent<TState extends Record<string, any>> {
14
+ // Common properties (all components have these)
15
+ state: TState;
16
+ container: HTMLElement | null = null;
17
+ _id: string;
18
+ id: string;
19
+
20
+ // Event & sync storage (populated by bind() and sync())
21
+ protected _bindings: Array<{ event: string, handler: Function }> = [];
22
+ protected _syncBindings: Array<{
23
+ property: string,
24
+ stateObj: State<any>,
25
+ toState?: Function,
26
+ toComponent?: Function
27
+ }> = [];
28
+ protected _triggerHandlers: Map<string, Function> = new Map();
29
+ protected _callbackHandlers: Map<string, Function> = new Map();
30
+
31
+ constructor(id: string, initialState: TState) {
32
+ this._id = id;
33
+ this.id = id;
34
+ this.state = initialState;
35
+ }
36
+
37
+ /* ═════════════════════════════════════════════════════════════════
38
+ * ABSTRACT METHODS (Child must implement)
39
+ * ═════════════════════════════════════════════════════════════════ */
40
+
41
+ protected abstract getTriggerEvents(): readonly string[];
42
+ protected abstract getCallbackEvents(): readonly string[];
43
+ abstract render(targetId?: string): this;
44
+
45
+ /* ═════════════════════════════════════════════════════════════════
46
+ * COMMON FLUENT API (Inherited by all components)
47
+ * ═════════════════════════════════════════════════════════════════ */
48
+
49
+ /**
50
+ * Set component style
51
+ */
52
+ style(value: string): this {
53
+ (this.state as any).style = value;
54
+ return this;
55
+ }
56
+
57
+ /**
58
+ * Set component class
59
+ */
60
+ class(value: string): this {
61
+ (this.state as any).class = value;
62
+ return this;
63
+ }
64
+
65
+ /* ═════════════════════════════════════════════════════════════════
66
+ * CSS CLASS MANAGEMENT
67
+ * ═════════════════════════════════════════════════════════════════ */
68
+
69
+ /**
70
+ * Add a CSS class to the component
71
+ */
72
+ addClass(value: string): this {
73
+ const current = (this.state as any).class || '';
74
+ const classes = current.split(' ').filter((c: string) => c);
75
+ if (!classes.includes(value)) {
76
+ classes.push(value);
77
+ (this.state as any).class = classes.join(' ');
78
+ if (this.container) this.container.classList.add(value);
79
+ }
80
+ return this;
81
+ }
82
+
83
+ /**
84
+ * Remove a CSS class from the component
85
+ */
86
+ removeClass(value: string): this {
87
+ const current = (this.state as any).class || '';
88
+ const classes = current.split(' ').filter((c: string) => c && c !== value);
89
+ (this.state as any).class = classes.join(' ');
90
+ if (this.container) this.container.classList.remove(value);
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * Toggle a CSS class on the component
96
+ */
97
+ toggleClass(value: string): this {
98
+ const current = (this.state as any).class || '';
99
+ const hasClass = current.split(' ').includes(value);
100
+ return hasClass ? this.removeClass(value) : this.addClass(value);
101
+ }
102
+
103
+ /* ═════════════════════════════════════════════════════════════════
104
+ * VISIBILITY CONTROL
105
+ * ═════════════════════════════════════════════════════════════════ */
106
+
107
+ /**
108
+ * Set component visibility
109
+ */
110
+ visible(value: boolean): this {
111
+ (this.state as any).visible = value;
112
+ if (this.container) {
113
+ // Find the actual component wrapper, not the parent container
114
+ const wrapper = this.container.querySelector(`#${this._id}`) as HTMLElement;
115
+ if (wrapper) {
116
+ wrapper.style.display = value ? '' : 'none';
117
+ }
118
+ }
119
+ return this;
120
+ }
121
+
122
+ /**
123
+ * Show the component
124
+ */
125
+ show(): this {
126
+ return this.visible(true);
127
+ }
128
+
129
+ /**
130
+ * Hide the component
131
+ */
132
+ hide(): this {
133
+ return this.visible(false);
134
+ }
135
+
136
+ /**
137
+ * Toggle component visibility
138
+ */
139
+ toggleVisibility(): this {
140
+ const isVisible = (this.state as any).visible ?? true;
141
+ return this.visible(!isVisible);
142
+ }
143
+
144
+ /* ═════════════════════════════════════════════════════════════════
145
+ * ATTRIBUTE MANAGEMENT
146
+ * ═════════════════════════════════════════════════════════════════ */
147
+
148
+ /**
149
+ * Set a single HTML attribute
150
+ */
151
+ attr(name: string, value: string): this {
152
+ const attrs = (this.state as any).attributes || {};
153
+ (this.state as any).attributes = { ...attrs, [name]: value };
154
+ if (this.container) this.container.setAttribute(name, value);
155
+ return this;
156
+ }
157
+
158
+ /**
159
+ * Set multiple HTML attributes
160
+ */
161
+ attrs(attributes: Record<string, string>): this {
162
+ Object.entries(attributes).forEach(([name, value]) => {
163
+ this.attr(name, value);
164
+ });
165
+ return this;
166
+ }
167
+
168
+ /**
169
+ * Remove an HTML attribute
170
+ */
171
+ removeAttr(name: string): this {
172
+ const attrs = (this.state as any).attributes || {};
173
+ delete attrs[name];
174
+ if (this.container) this.container.removeAttribute(name);
175
+ return this;
176
+ }
177
+
178
+ /* ═════════════════════════════════════════════════════════════════
179
+ * DISABLED STATE
180
+ * ═════════════════════════════════════════════════════════════════ */
181
+
182
+ /**
183
+ * Set disabled state for interactive elements
184
+ */
185
+ disabled(value: boolean): this {
186
+ (this.state as any).disabled = value;
187
+ if (this.container) {
188
+ const inputs = this.container.querySelectorAll('input, button, select, textarea');
189
+ inputs.forEach(el => {
190
+ (el as HTMLInputElement).disabled = value;
191
+ });
192
+ this.container.setAttribute('aria-disabled', String(value));
193
+ }
194
+ return this;
195
+ }
196
+
197
+ /**
198
+ * Enable the component
199
+ */
200
+ enable(): this {
201
+ return this.disabled(false);
202
+ }
203
+
204
+ /**
205
+ * Disable the component
206
+ */
207
+ disable(): this {
208
+ return this.disabled(true);
209
+ }
210
+
211
+ /* ═════════════════════════════════════════════════════════════════
212
+ * LOADING STATE
213
+ * ═════════════════════════════════════════════════════════════════ */
214
+
215
+ /**
216
+ * Set loading state
217
+ */
218
+ loading(value: boolean): this {
219
+ (this.state as any).loading = value;
220
+ if (this.container) {
221
+ if (value) {
222
+ this.container.classList.add('jux-loading');
223
+ this.container.setAttribute('aria-busy', 'true');
224
+ } else {
225
+ this.container.classList.remove('jux-loading');
226
+ this.container.removeAttribute('aria-busy');
227
+ }
228
+ }
229
+ return this;
230
+ }
231
+
232
+ /* ═════════════════════════════════════════════════════════════════
233
+ * FOCUS MANAGEMENT
234
+ * ═════════════════════════════════════════════════════════════════ */
235
+
236
+ /**
237
+ * Focus the first focusable element in the component
238
+ */
239
+ focus(): this {
240
+ if (this.container) {
241
+ const focusable = this.container.querySelector('input, button, select, textarea, [tabindex]');
242
+ if (focusable) (focusable as HTMLElement).focus();
243
+ }
244
+ return this;
245
+ }
246
+
247
+ /**
248
+ * Blur the currently focused element in the component
249
+ */
250
+ blur(): this {
251
+ if (this.container) {
252
+ const focused = this.container.querySelector(':focus');
253
+ if (focused) (focused as HTMLElement).blur();
254
+ }
255
+ return this;
256
+ }
257
+
258
+ /* ═════════════════════════════════════════════════════════════════
259
+ * DOM MANIPULATION
260
+ * ═════════════════════════════════════════════════════════════════ */
261
+
262
+ /**
263
+ * Remove the component from the DOM
264
+ */
265
+ remove(): this {
266
+ if (this.container) {
267
+ this.container.remove();
268
+ this.container = null;
269
+ }
270
+ return this;
271
+ }
272
+
273
+ /* ═════════════════════════════════════════════════════════════════
274
+ * EVENT BINDING (Shared logic)
275
+ * ═════════════════════════════════════════════════════════════════ */
276
+
277
+ bind(event: string, handler: Function): this {
278
+ if (this._isTriggerEvent(event)) {
279
+ this._triggerHandlers.set(event, handler);
280
+ } else if (this._isCallbackEvent(event)) {
281
+ this._callbackHandlers.set(event, handler);
282
+ } else {
283
+ this._bindings.push({ event, handler });
284
+ }
285
+ return this;
286
+ }
287
+
288
+ /**
289
+ * Sync a component property with a State object
290
+ * @param property - The property to sync
291
+ * @param stateObj - The State object to sync with
292
+ * @param toStateOrTransform - Either toState function OR a simple transform function
293
+ * @param toComponent - Optional toComponent function (if toState was provided)
294
+ */
295
+ sync(property: string, stateObj: State<any>, toStateOrTransform?: Function, toComponent?: Function): this {
296
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
297
+ throw new Error(`${this.constructor.name}.sync: Expected a State object for property "${property}"`);
298
+ }
299
+
300
+ // If only 3 args provided, treat the function as toComponent (the common case)
301
+ const actualToState = (toComponent !== undefined) ? toStateOrTransform : undefined;
302
+ const actualToComponent = (toComponent !== undefined) ? toComponent : toStateOrTransform;
303
+
304
+ this._syncBindings.push({
305
+ property,
306
+ stateObj,
307
+ toState: actualToState,
308
+ toComponent: actualToComponent
309
+ });
310
+ return this;
311
+ }
312
+
313
+ protected _isTriggerEvent(event: string): boolean {
314
+ return this.getTriggerEvents().includes(event);
315
+ }
316
+
317
+ protected _isCallbackEvent(event: string): boolean {
318
+ return this.getCallbackEvents().includes(event);
319
+ }
320
+
321
+ protected _triggerCallback(eventName: string, ...args: any[]): void {
322
+
323
+ if (this._callbackHandlers.has(eventName)) {
324
+ const handler = this._callbackHandlers.get(eventName)!;
325
+ handler(...args);
326
+ } else {
327
+ console.warn(`🔍 No handler found for "${eventName}"`);
328
+ }
329
+ }
330
+
331
+ /* ═════════════════════════════════════════════════════════════════
332
+ * COMMON RENDER HELPERS
333
+ * ═════════════════════════════════════════════════════════════════ */
334
+
335
+ protected _setupContainer(targetId?: string): HTMLElement {
336
+ let container: HTMLElement;
337
+ if (targetId) {
338
+ // Strip leading # if present
339
+ const id = targetId.startsWith('#') ? targetId.slice(1) : targetId;
340
+ const target = document.getElementById(id);
341
+ if (target) {
342
+ container = target;
343
+ } else {
344
+ // Gracefully create the container instead of throwing
345
+ console.warn(`[Jux] Target "${targetId}" not found, creating it with graceful fallback`);
346
+ container = getOrCreateContainer(id);
347
+ }
348
+ } else {
349
+ container = getOrCreateContainer(this._id);
350
+ }
351
+
352
+ // Add universal component class for DOM inspection
353
+ // container.classList.add('jux-component');
354
+
355
+ this.container = container;
356
+ return container;
357
+ }
358
+
359
+ protected _wireStandardEvents(element: HTMLElement): void {
360
+ this._bindings.forEach(({ event, handler }) => {
361
+ element.addEventListener(event, handler as EventListener);
362
+ });
363
+ }
364
+
365
+ /**
366
+ * Automatically wire ALL sync bindings by calling the corresponding method
367
+ * if it exists on the component
368
+ */
369
+ protected _wireAllSyncs(): void {
370
+ this._syncBindings.forEach(({ property, stateObj, toComponent }) => {
371
+ const transform = toComponent || ((v: any) => v);
372
+
373
+ // Check if component has a method matching the property name
374
+ const method = (this as any)[property];
375
+
376
+ if (typeof method === 'function') {
377
+ // Set initial value
378
+ const initialValue = transform(stateObj.value);
379
+ method.call(this, initialValue);
380
+
381
+ // Subscribe to changes
382
+ stateObj.subscribe((val: any) => {
383
+ const transformed = transform(val);
384
+ method.call(this, transformed);
385
+ });
386
+ } else {
387
+ console.warn(
388
+ `[Jux] ${this.constructor.name}.sync('${property}'): ` +
389
+ `No method .${property}() found. Property will not be synced.`
390
+ );
391
+ }
392
+ });
393
+ }
394
+
395
+ renderTo(juxComponent: any): this {
396
+ if (!juxComponent?._id) {
397
+ throw new Error(`${this.constructor.name}.renderTo: Invalid component`);
398
+ }
399
+ return this.render(`#${juxComponent._id}`);
400
+ }
401
+
402
+ /* ═════════════════════════════════════════════════════════════════
403
+ * PROPS ACCESSOR - Read-only access to component state
404
+ * ═════════════════════════════════════════════════════════════════ */
405
+
406
+ /**
407
+ * ✅ Read-only accessor for component state
408
+ * Provides clear separation between setters (fluent methods) and getters
409
+ *
410
+ * @example
411
+ * const myCard = card('example')
412
+ * .title('Hello') // ✅ SETTER (fluent)
413
+ * .content('World'); // ✅ SETTER (fluent)
414
+ *
415
+ * console.log(myCard.props.title); // ✅ GETTER: 'Hello'
416
+ * console.log(myCard.props.content); // ✅ GETTER: 'World'
417
+ */
418
+ get props(): Readonly<TState> {
419
+ return this.state as Readonly<TState>;
420
+ }
421
+ }
@@ -0,0 +1,73 @@
1
+ import { BaseComponent } from './BaseComponent.js';
2
+ /**
3
+ * Base state interface for all form inputs
4
+ */
5
+ export interface FormInputState extends Record<string, any> {
6
+ label: string;
7
+ required: boolean;
8
+ disabled: boolean;
9
+ name: string;
10
+ style: string;
11
+ class: string;
12
+ errorMessage?: string;
13
+ }
14
+ /**
15
+ * Abstract base class for all form input components
16
+ * Extends BaseComponent with form-specific functionality
17
+ */
18
+ export declare abstract class FormInput<TState extends FormInputState> extends BaseComponent<TState> {
19
+ protected _inputElement: HTMLElement | null;
20
+ protected _labelElement: HTMLLabelElement | null;
21
+ protected _errorElement: HTMLElement | null;
22
+ protected _onValidate?: (value: any) => boolean | string;
23
+ protected _hasBeenValidated: boolean;
24
+ /**
25
+ * Get the current value of the input
26
+ */
27
+ abstract getValue(): any;
28
+ /**
29
+ * Set the value of the input
30
+ */
31
+ abstract setValue(value: any): this;
32
+ /**
33
+ * Build the actual input element (input, select, textarea, etc.)
34
+ */
35
+ protected abstract _buildInputElement(): HTMLElement;
36
+ /**
37
+ * Validate the current value
38
+ */
39
+ protected abstract _validateValue(value: any): boolean | string;
40
+ label(value: string): this;
41
+ required(value: boolean): this;
42
+ name(value: string): this;
43
+ onValidate(handler: (value: any) => boolean | string): this;
44
+ /**
45
+ * Validate the current value and show/hide errors
46
+ */
47
+ validate(): boolean;
48
+ /**
49
+ * Check if current value is valid without showing errors
50
+ */
51
+ isValid(): boolean;
52
+ /**
53
+ * Show error message
54
+ */
55
+ protected _showError(message: string): void;
56
+ /**
57
+ * Clear error message
58
+ */
59
+ protected _clearError(): void;
60
+ /**
61
+ * Build label element
62
+ */
63
+ protected _renderLabel(): HTMLLabelElement;
64
+ /**
65
+ * Build error element
66
+ */
67
+ protected _renderError(): HTMLElement;
68
+ /**
69
+ * Wire up two-way sync for value property
70
+ */
71
+ protected _wireFormSync(inputElement: HTMLElement, eventName?: string): void;
72
+ }
73
+ //# sourceMappingURL=FormInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormInput.d.ts","sourceRoot":"","sources":["FormInput.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,cAAe,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,8BAAsB,SAAS,CAAC,MAAM,SAAS,cAAc,CAAE,SAAQ,aAAa,CAAC,MAAM,CAAC;IACxF,SAAS,CAAC,aAAa,EAAE,WAAW,GAAG,IAAI,CAAQ;IACnD,SAAS,CAAC,aAAa,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACxD,SAAS,CAAC,aAAa,EAAE,WAAW,GAAG,IAAI,CAAQ;IACnD,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,GAAG,MAAM,CAAC;IACzD,SAAS,CAAC,iBAAiB,EAAE,OAAO,CAAS;IAM7C;;OAEG;IACH,QAAQ,CAAC,QAAQ,IAAI,GAAG;IAExB;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAEnC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,kBAAkB,IAAI,WAAW;IAEpD;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,GAAG,MAAM;IAM/D,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ1B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAK9B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKzB,UAAU,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,GAAG,MAAM,GAAG,IAAI;IAS3D;;OAEG;IACH,QAAQ,IAAI,OAAO;IAcnB;;OAEG;IACH,OAAO,IAAI,OAAO;IAKlB;;OAEG;IACH,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAa3C;;OAEG;IACH,SAAS,CAAC,WAAW,IAAI,IAAI;IAiB7B;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,gBAAgB;IAmB1C;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,WAAW;IAUrC;;OAEG;IACH,SAAS,CAAC,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,SAAS,GAAE,MAAgB,GAAG,IAAI;CA8CxF"}
@@ -0,0 +1,163 @@
1
+ import { BaseComponent } from './BaseComponent.js';
2
+ /**
3
+ * Abstract base class for all form input components
4
+ * Extends BaseComponent with form-specific functionality
5
+ */
6
+ export class FormInput extends BaseComponent {
7
+ constructor() {
8
+ super(...arguments);
9
+ this._inputElement = null;
10
+ this._labelElement = null;
11
+ this._errorElement = null;
12
+ this._hasBeenValidated = false; // NEW: Track if user has submitted/validated
13
+ }
14
+ /* ═════════════════════════════════════════════════════════════════
15
+ * COMMON FORM INPUT API
16
+ * ═════════════════════════════════════════════════════════════════ */
17
+ label(value) {
18
+ this.state.label = value;
19
+ if (this._labelElement) {
20
+ this._labelElement.textContent = value;
21
+ }
22
+ return this;
23
+ }
24
+ required(value) {
25
+ this.state.required = value;
26
+ return this;
27
+ }
28
+ name(value) {
29
+ this.state.name = value;
30
+ return this;
31
+ }
32
+ onValidate(handler) {
33
+ this._onValidate = handler;
34
+ return this;
35
+ }
36
+ /* ═════════════════════════════════════════════════════════════════
37
+ * VALIDATION
38
+ * ═════════════════════════════════════════════════════════════════ */
39
+ /**
40
+ * Validate the current value and show/hide errors
41
+ */
42
+ validate() {
43
+ this._hasBeenValidated = true; // Mark as validated
44
+ const value = this.getValue();
45
+ const result = this._validateValue(value);
46
+ if (result === true) {
47
+ this._clearError();
48
+ return true;
49
+ }
50
+ else {
51
+ this._showError(result);
52
+ return false;
53
+ }
54
+ }
55
+ /**
56
+ * Check if current value is valid without showing errors
57
+ */
58
+ isValid() {
59
+ const value = this.getValue();
60
+ return this._validateValue(value) === true;
61
+ }
62
+ /**
63
+ * Show error message
64
+ */
65
+ _showError(message) {
66
+ if (this._errorElement) {
67
+ this._errorElement.textContent = message;
68
+ this._errorElement.style.display = 'block';
69
+ }
70
+ if (this._inputElement) {
71
+ this._inputElement.classList.add('jux-input-invalid');
72
+ }
73
+ this.state.errorMessage = message;
74
+ }
75
+ /**
76
+ * Clear error message
77
+ */
78
+ _clearError() {
79
+ if (this._errorElement) {
80
+ this._errorElement.textContent = '';
81
+ this._errorElement.style.display = 'none';
82
+ }
83
+ if (this._inputElement) {
84
+ this._inputElement.classList.remove('jux-input-invalid');
85
+ }
86
+ this.state.errorMessage = undefined;
87
+ }
88
+ /* ═════════════════════════════════════════════════════════════════
89
+ * COMMON RENDER HELPERS
90
+ * ═════════════════════════════════════════════════════════════════ */
91
+ /**
92
+ * Build label element
93
+ */
94
+ _renderLabel() {
95
+ const { label, required } = this.state;
96
+ const labelEl = document.createElement('label');
97
+ labelEl.className = 'jux-input-label';
98
+ labelEl.htmlFor = `${this._id}-input`;
99
+ labelEl.textContent = label;
100
+ if (required) {
101
+ const requiredSpan = document.createElement('span');
102
+ requiredSpan.className = 'jux-input-required';
103
+ requiredSpan.textContent = ' *';
104
+ labelEl.appendChild(requiredSpan);
105
+ }
106
+ this._labelElement = labelEl;
107
+ return labelEl;
108
+ }
109
+ /**
110
+ * Build error element
111
+ */
112
+ _renderError() {
113
+ const errorEl = document.createElement('div');
114
+ errorEl.className = 'jux-input-error';
115
+ errorEl.id = `${this._id}-error`;
116
+ errorEl.style.display = 'none';
117
+ this._errorElement = errorEl;
118
+ return errorEl;
119
+ }
120
+ /**
121
+ * Wire up two-way sync for value property
122
+ */
123
+ _wireFormSync(inputElement, eventName = 'input') {
124
+ const valueSync = this._syncBindings.find(b => b.property === 'value');
125
+ if (valueSync) {
126
+ const { stateObj, toState, toComponent } = valueSync;
127
+ // Default transforms
128
+ const transformToState = toState || ((v) => v);
129
+ const transformToComponent = toComponent || ((v) => v);
130
+ let isUpdating = false;
131
+ // State → Component
132
+ stateObj.subscribe((val) => {
133
+ if (isUpdating)
134
+ return;
135
+ const transformed = transformToComponent(val);
136
+ this.setValue(transformed);
137
+ });
138
+ // Component → State
139
+ inputElement.addEventListener(eventName, () => {
140
+ if (isUpdating)
141
+ return;
142
+ isUpdating = true;
143
+ const value = this.getValue();
144
+ const transformed = transformToState(value);
145
+ this._clearError();
146
+ stateObj.set(transformed);
147
+ setTimeout(() => { isUpdating = false; }, 0);
148
+ });
149
+ }
150
+ else {
151
+ // Default behavior without sync
152
+ inputElement.addEventListener(eventName, () => {
153
+ this._clearError();
154
+ });
155
+ }
156
+ // Only validate on blur IF the field has been validated before (e.g., after submit)
157
+ inputElement.addEventListener('blur', () => {
158
+ if (this._hasBeenValidated) {
159
+ this.validate();
160
+ }
161
+ });
162
+ }
163
+ }