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,267 @@
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const; // Element has no trigger events
5
+ const CALLBACK_EVENTS = [] as const; // Element has no callback events
6
+
7
+ export interface ElementOptions {
8
+ tagType?: string;
9
+ className?: string;
10
+ text?: string;
11
+ innerHTML?: string;
12
+ attributes?: Record<string, string>;
13
+ styles?: Record<string, string>;
14
+ style?: string;
15
+ class?: string;
16
+ }
17
+
18
+ type ElementState = {
19
+ tagType: string;
20
+ className: string;
21
+ text: string;
22
+ innerHTML: string;
23
+ attributes: Record<string, string>;
24
+ styles: Record<string, string>;
25
+ style: string;
26
+ class: string;
27
+ };
28
+
29
+ export class Element extends BaseComponent<ElementState> {
30
+ private _element: HTMLElement | null = null;
31
+
32
+ constructor(id: string, options: ElementOptions = {}) {
33
+ super(id, {
34
+ tagType: options.tagType ?? 'div',
35
+ className: options.className ?? '',
36
+ text: options.text ?? '',
37
+ innerHTML: options.innerHTML ?? '',
38
+ attributes: options.attributes ?? {},
39
+ styles: options.styles ?? {},
40
+ style: options.style ?? '',
41
+ class: options.class ?? ''
42
+ });
43
+ }
44
+
45
+ protected getTriggerEvents(): readonly string[] {
46
+ return TRIGGER_EVENTS;
47
+ }
48
+
49
+ protected getCallbackEvents(): readonly string[] {
50
+ return CALLBACK_EVENTS;
51
+ }
52
+
53
+ /* ═════════════════════════════════════════════════════════════════
54
+ * FLUENT API
55
+ * ═════════════════════════════════════════════════════════════════ */
56
+
57
+ // ✅ Inherited from BaseComponent:
58
+ // - style(), class()
59
+ // - addClass(), removeClass(), toggleClass()
60
+ // - visible(), show(), hide(), toggleVisibility()
61
+ // - attr(), attrs(), removeAttr()
62
+ // - disabled(), enable(), disable()
63
+ // - loading()
64
+ // - focus(), blur()
65
+ // - remove()
66
+ // - bind(), sync(), renderTo()
67
+
68
+ tagType(value: string): Element {
69
+ this.state.tagType = value;
70
+ return this;
71
+ }
72
+
73
+ className(value: string): Element {
74
+ this.state.className = value;
75
+ return this;
76
+ }
77
+
78
+ text(value: string): Element {
79
+ this.state.text = value;
80
+ this.state.innerHTML = '';
81
+ return this;
82
+ }
83
+
84
+ innerHTML(value: string): Element {
85
+ this.state.innerHTML = value;
86
+ this.state.text = '';
87
+ return this;
88
+ }
89
+
90
+ write(content: string, asHTML: boolean = false, newLine: boolean = false): Element {
91
+ if (!this._element) {
92
+ console.warn(`Element.write: Element "${this._id}" not yet rendered`);
93
+ return this;
94
+ }
95
+
96
+ const contentWithBreak = newLine ? (asHTML ? content + '<br>' : content + '\n') : content;
97
+
98
+ if (asHTML) {
99
+ this._element.insertAdjacentHTML('beforeend', contentWithBreak);
100
+ } else {
101
+ const textNode = document.createTextNode(contentWithBreak);
102
+ this._element.appendChild(textNode);
103
+ }
104
+
105
+ return this;
106
+ }
107
+
108
+ prepend(content: string, asHTML: boolean = false, newLine: boolean = false): Element {
109
+ if (!this._element) {
110
+ console.warn(`Element.prepend: Element "${this._id}" not yet rendered`);
111
+ return this;
112
+ }
113
+
114
+ const contentWithBreak = newLine ? (asHTML ? content + '<br>' : content + '\n') : content;
115
+
116
+ if (asHTML) {
117
+ this._element.insertAdjacentHTML('afterbegin', contentWithBreak);
118
+ } else {
119
+ const textNode = document.createTextNode(contentWithBreak);
120
+ this._element.insertBefore(textNode, this._element.firstChild);
121
+ }
122
+
123
+ return this;
124
+ }
125
+
126
+ clear(): Element {
127
+ if (!this._element) {
128
+ console.warn(`Element.clear: Element "${this._id}" not yet rendered`);
129
+ return this;
130
+ }
131
+
132
+ this._element.innerHTML = '';
133
+ return this;
134
+ }
135
+
136
+ /**
137
+ * Append child component or HTML element
138
+ */
139
+ append(child: any): Element {
140
+ if (!this._element) {
141
+ console.warn(`Element.append: Element "${this._id}" not yet rendered`);
142
+ return this;
143
+ }
144
+
145
+ if (child instanceof HTMLElement) {
146
+ this._element.appendChild(child);
147
+ } else if (child?._element) {
148
+ // Another Jux component
149
+ this._element.appendChild(child._element);
150
+ } else if (typeof child === 'string') {
151
+ const div = document.createElement('div');
152
+ div.innerHTML = child;
153
+ this._element.appendChild(div.firstChild || div);
154
+ }
155
+
156
+ return this;
157
+ }
158
+
159
+ render(targetId?: string): this {
160
+ const container = this._setupContainer(targetId);
161
+
162
+ const { tagType, className, text, innerHTML, attributes, styles, style, class: classValue } = this.state;
163
+
164
+ // Build element
165
+ const element = document.createElement(tagType);
166
+ element.id = this._id;
167
+
168
+ if (className) element.className = className;
169
+ if (classValue) element.className = `${element.className} ${classValue}`.trim();
170
+
171
+ if (text) {
172
+ element.textContent = text;
173
+ } else if (innerHTML) {
174
+ element.innerHTML = innerHTML;
175
+ }
176
+
177
+ Object.entries(attributes).forEach(([key, value]) => {
178
+ element.setAttribute(key, value);
179
+ });
180
+
181
+ const styleString = Object.entries(styles)
182
+ .map(([key, value]) => `${key}: ${value}`)
183
+ .join('; ');
184
+
185
+ if (styleString || style) {
186
+ element.setAttribute('style', `${styleString}; ${style}`.trim());
187
+ }
188
+
189
+ // Wire events using inherited method
190
+ this._wireStandardEvents(element);
191
+
192
+ // Wire sync bindings
193
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
194
+ const transform = toComponent || ((v: any) => v);
195
+
196
+ stateObj.subscribe((val: any) => {
197
+ const transformed = transform(val);
198
+
199
+ if (property === 'text') {
200
+ element.textContent = String(transformed);
201
+ this.state.text = String(transformed);
202
+ } else if (property === 'innerHTML') {
203
+ element.innerHTML = String(transformed);
204
+ this.state.innerHTML = String(transformed);
205
+ } else if (property === 'class') {
206
+ element.className = String(transformed);
207
+ this.state.class = String(transformed);
208
+ }
209
+ });
210
+ });
211
+
212
+ container.appendChild(element);
213
+ this._element = element;
214
+
215
+ return this;
216
+ }
217
+ }
218
+
219
+ export function element(id: string, options: ElementOptions = {}): Element {
220
+ return new Element(id, options);
221
+ }
222
+
223
+ // Semantic HTML element factories with default classes
224
+ export function header(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
225
+ const defaultClass = 'jux-header';
226
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
227
+ return new Element(id, { ...options, tagType: 'header', class: mergedClass });
228
+ }
229
+
230
+ export function footer(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
231
+ const defaultClass = 'jux-footer';
232
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
233
+ return new Element(id, { ...options, tagType: 'footer', class: mergedClass });
234
+ }
235
+
236
+ export function main(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
237
+ const defaultClass = 'jux-main';
238
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
239
+ return new Element(id, { ...options, tagType: 'main', class: mergedClass });
240
+ }
241
+
242
+ export function aside(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
243
+ const defaultClass = 'jux-aside';
244
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
245
+ return new Element(id, { ...options, tagType: 'aside', class: mergedClass });
246
+ }
247
+
248
+ export function section(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
249
+ const defaultClass = 'jux-section';
250
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
251
+ return new Element(id, { ...options, tagType: 'section', class: mergedClass });
252
+ }
253
+
254
+ export function article(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
255
+ const defaultClass = 'jux-article';
256
+ const mergedClass = options.class ? `${defaultClass} ${options.class}` : defaultClass;
257
+ return new Element(id, { ...options, tagType: 'article', class: mergedClass });
258
+ }
259
+
260
+ export function div(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
261
+ return new Element(id, { ...options, tagType: 'div' });
262
+ }
263
+
264
+ export function span(id: string, options: Omit<ElementOptions, 'tagType'> = {}): Element {
265
+ return new Element(id, { ...options, tagType: 'span' });
266
+ }
267
+
@@ -0,0 +1,40 @@
1
+ import { FormInput, FormInputState } from './base/FormInput.js';
2
+ export interface FileUploadOptions {
3
+ label?: string;
4
+ accept?: string;
5
+ multiple?: boolean;
6
+ disabled?: boolean;
7
+ name?: string;
8
+ icon?: string;
9
+ required?: boolean;
10
+ style?: string;
11
+ class?: string;
12
+ onValidate?: (files: File[]) => boolean | string;
13
+ }
14
+ interface FileUploadState extends FormInputState {
15
+ files: File[];
16
+ accept: string;
17
+ multiple: boolean;
18
+ icon: string;
19
+ }
20
+ export declare class FileUpload extends FormInput<FileUploadState> {
21
+ private _fileListElement;
22
+ constructor(id: string, options?: FileUploadOptions);
23
+ protected getTriggerEvents(): readonly string[];
24
+ protected getCallbackEvents(): readonly string[];
25
+ accept(value: string): this;
26
+ multiple(value: boolean): this;
27
+ icon(value: string): this;
28
+ clear(): this;
29
+ getValue(): File[];
30
+ setValue(files: File[]): this;
31
+ getFiles(): File[];
32
+ protected _validateValue(files: File[]): boolean | string;
33
+ protected _buildInputElement(): HTMLElement;
34
+ render(targetId?: string): this;
35
+ private _updateFileList;
36
+ private _formatFileSize;
37
+ }
38
+ export declare function fileupload(id: string, options?: FileUploadOptions): FileUpload;
39
+ export {};
40
+ //# sourceMappingURL=fileupload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileupload.d.ts","sourceRoot":"","sources":["fileupload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAOhE,MAAM,WAAW,iBAAiB;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,GAAG,MAAM,CAAC;CACpD;AAED,UAAU,eAAgB,SAAQ,cAAc;IAC5C,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,UAAW,SAAQ,SAAS,CAAC,eAAe,CAAC;IACtD,OAAO,CAAC,gBAAgB,CAA4B;gBAExC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB;IAoBvD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAI/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAehD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK3B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAK9B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKzB,KAAK,IAAI,IAAI;IAiBb,QAAQ,IAAI,IAAI,EAAE;IAIlB,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI;IAQ7B,QAAQ,IAAI,IAAI,EAAE;IAIlB,SAAS,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,GAAG,MAAM;IAiBzD,SAAS,CAAC,kBAAkB,IAAI,WAAW;IAqB3C,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IA8H/B,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,eAAe;CAK1B;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,UAAU,CAElF"}
@@ -0,0 +1,241 @@
1
+ import { FormInput } from './base/FormInput.js';
2
+ import { renderIcon } from './icons.js';
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [];
5
+ const CALLBACK_EVENTS = ['change', 'filesSelected', 'clear'];
6
+ export class FileUpload extends FormInput {
7
+ constructor(id, options = {}) {
8
+ super(id, {
9
+ files: [],
10
+ accept: options.accept ?? '',
11
+ multiple: options.multiple ?? false,
12
+ icon: options.icon ?? 'upload',
13
+ label: options.label ?? '',
14
+ required: options.required ?? false,
15
+ disabled: options.disabled ?? false,
16
+ name: options.name ?? id,
17
+ style: options.style ?? '',
18
+ class: options.class ?? '',
19
+ errorMessage: undefined
20
+ });
21
+ this._fileListElement = null;
22
+ if (options.onValidate) {
23
+ this._onValidate = options.onValidate;
24
+ }
25
+ }
26
+ getTriggerEvents() {
27
+ return TRIGGER_EVENTS;
28
+ }
29
+ getCallbackEvents() {
30
+ return CALLBACK_EVENTS;
31
+ }
32
+ /* ═════════════════════════════════════════════════════════════════
33
+ * FLUENT API
34
+ * ═════════════════════════════════════════════════════════════════ */
35
+ // ✅ Inherited from FormInput/BaseComponent:
36
+ // - label(), required(), name(), onValidate()
37
+ // - validate(), isValid()
38
+ // - style(), class()
39
+ // - bind(), sync(), renderTo()
40
+ // - disabled(), enable(), disable()
41
+ accept(value) {
42
+ this.state.accept = value;
43
+ return this;
44
+ }
45
+ multiple(value) {
46
+ this.state.multiple = value;
47
+ return this;
48
+ }
49
+ icon(value) {
50
+ this.state.icon = value;
51
+ return this;
52
+ }
53
+ clear() {
54
+ this.state.files = [];
55
+ if (this._inputElement) {
56
+ this._inputElement.value = '';
57
+ }
58
+ if (this._fileListElement) {
59
+ this._updateFileList([]);
60
+ }
61
+ // 🎯 Fire the clear callback event
62
+ this._triggerCallback('clear');
63
+ return this;
64
+ }
65
+ /* ═════════════════════════════════════════════════════════════════
66
+ * FORM INPUT IMPLEMENTATION
67
+ * ═════════════════════════════════════════════════════════════════ */
68
+ getValue() {
69
+ return this.state.files;
70
+ }
71
+ setValue(files) {
72
+ this.state.files = files;
73
+ if (this._fileListElement) {
74
+ this._updateFileList(files);
75
+ }
76
+ return this;
77
+ }
78
+ getFiles() {
79
+ return this.getValue();
80
+ }
81
+ _validateValue(files) {
82
+ const { required } = this.state;
83
+ if (required && files.length === 0) {
84
+ return 'Please select at least one file';
85
+ }
86
+ if (this._onValidate) {
87
+ const result = this._onValidate(files);
88
+ if (result !== true) {
89
+ return result || 'Invalid files';
90
+ }
91
+ }
92
+ return true;
93
+ }
94
+ _buildInputElement() {
95
+ const { accept, multiple, required, disabled, name } = this.state;
96
+ const input = document.createElement('input');
97
+ input.type = 'file';
98
+ input.className = 'jux-fileupload-input';
99
+ input.id = `${this._id}-input`;
100
+ input.name = name;
101
+ input.required = required;
102
+ input.disabled = disabled;
103
+ if (accept)
104
+ input.accept = accept;
105
+ if (multiple)
106
+ input.multiple = multiple;
107
+ return input;
108
+ }
109
+ /* ═════════════════════════════════════════════════════════════════
110
+ * RENDER
111
+ * ═════════════════════════════════════════════════════════════════ */
112
+ render(targetId) {
113
+ const container = this._setupContainer(targetId);
114
+ const { icon, style, class: className } = this.state;
115
+ // Build wrapper
116
+ const wrapper = document.createElement('div');
117
+ wrapper.className = 'jux-input jux-fileupload';
118
+ wrapper.id = this._id;
119
+ if (className)
120
+ wrapper.className += ` ${className}`;
121
+ if (style)
122
+ wrapper.setAttribute('style', style);
123
+ // Label
124
+ if (this.state.label) {
125
+ wrapper.appendChild(this._renderLabel());
126
+ }
127
+ // Hidden file input
128
+ const inputEl = this._buildInputElement();
129
+ this._inputElement = inputEl;
130
+ wrapper.appendChild(inputEl);
131
+ // Button container
132
+ const buttonContainer = document.createElement('div');
133
+ buttonContainer.className = 'jux-fileupload-button-container';
134
+ if (icon) {
135
+ const iconEl = document.createElement('span');
136
+ iconEl.className = 'jux-fileupload-icon';
137
+ iconEl.appendChild(renderIcon(icon));
138
+ buttonContainer.appendChild(iconEl);
139
+ }
140
+ const button = document.createElement('button');
141
+ button.type = 'button';
142
+ button.className = 'jux-fileupload-button';
143
+ button.textContent = 'Choose File(s)';
144
+ button.disabled = this.state.disabled;
145
+ buttonContainer.appendChild(button);
146
+ wrapper.appendChild(buttonContainer);
147
+ // File list
148
+ const fileList = document.createElement('div');
149
+ fileList.className = 'jux-fileupload-list';
150
+ this._fileListElement = fileList;
151
+ wrapper.appendChild(fileList);
152
+ // Error element
153
+ wrapper.appendChild(this._renderError());
154
+ // Button click triggers file input
155
+ button.addEventListener('click', () => inputEl.click());
156
+ // Wire events
157
+ this._wireStandardEvents(wrapper);
158
+ // Wire file-specific sync
159
+ const filesSync = this._syncBindings.find(b => b.property === 'files' || b.property === 'value');
160
+ if (filesSync) {
161
+ const { stateObj, toState, toComponent } = filesSync;
162
+ const transformToState = toState || ((v) => v);
163
+ const transformToComponent = toComponent || ((v) => v);
164
+ let isUpdating = false;
165
+ // State → Component
166
+ stateObj.subscribe((val) => {
167
+ if (isUpdating)
168
+ return;
169
+ const transformed = transformToComponent(val);
170
+ this.setValue(transformed);
171
+ });
172
+ // Component → State
173
+ inputEl.addEventListener('change', () => {
174
+ if (isUpdating)
175
+ return;
176
+ isUpdating = true;
177
+ const files = Array.from(inputEl.files || []);
178
+ this.state.files = files;
179
+ this._updateFileList(files);
180
+ this._clearError();
181
+ const transformed = transformToState(files);
182
+ stateObj.set(transformed);
183
+ // 🎯 Fire the callback events
184
+ this._triggerCallback('change', files);
185
+ this._triggerCallback('filesSelected', files);
186
+ setTimeout(() => { isUpdating = false; }, 0);
187
+ });
188
+ }
189
+ else {
190
+ // Default behavior without sync
191
+ inputEl.addEventListener('change', () => {
192
+ const files = Array.from(inputEl.files || []);
193
+ this.state.files = files;
194
+ this._updateFileList(files);
195
+ this._clearError();
196
+ // 🎯 Fire the callback events
197
+ this._triggerCallback('change', files);
198
+ this._triggerCallback('filesSelected', files);
199
+ });
200
+ }
201
+ // Always add blur validation
202
+ inputEl.addEventListener('blur', () => {
203
+ this.validate();
204
+ });
205
+ // Sync label changes
206
+ const labelSync = this._syncBindings.find(b => b.property === 'label');
207
+ if (labelSync) {
208
+ const transform = labelSync.toComponent || ((v) => String(v));
209
+ labelSync.stateObj.subscribe((val) => {
210
+ this.label(transform(val));
211
+ });
212
+ }
213
+ container.appendChild(wrapper);
214
+ return this;
215
+ }
216
+ _updateFileList(files) {
217
+ if (!this._fileListElement)
218
+ return;
219
+ this._fileListElement.innerHTML = '';
220
+ if (files.length === 0) {
221
+ this._fileListElement.textContent = 'No files selected';
222
+ return;
223
+ }
224
+ files.forEach(file => {
225
+ const fileItem = document.createElement('div');
226
+ fileItem.className = 'jux-fileupload-item';
227
+ fileItem.textContent = `${file.name} (${this._formatFileSize(file.size)})`;
228
+ this._fileListElement.appendChild(fileItem);
229
+ });
230
+ }
231
+ _formatFileSize(bytes) {
232
+ if (bytes < 1024)
233
+ return `${bytes} B`;
234
+ if (bytes < 1024 * 1024)
235
+ return `${(bytes / 1024).toFixed(1)} KB`;
236
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
237
+ }
238
+ }
239
+ export function fileupload(id, options = {}) {
240
+ return new FileUpload(id, options);
241
+ }