lightview 1.8.2 → 2.0.1

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 (262) hide show
  1. package/.codacy/cli.sh +149 -0
  2. package/.codacy/codacy.yaml +15 -0
  3. package/.github/instructions/codacy.instructions.md +72 -0
  4. package/.wranglerignore +21 -0
  5. package/README.md +1330 -19
  6. package/_headers +4 -0
  7. package/build.js +70 -0
  8. package/components/actions/button.js +151 -0
  9. package/components/actions/dropdown.js +120 -0
  10. package/components/actions/modal.js +146 -0
  11. package/components/actions/swap.js +118 -0
  12. package/components/daisyui.js +288 -0
  13. package/components/data-display/accordion.js +128 -0
  14. package/components/data-display/alert.js +112 -0
  15. package/components/data-display/avatar.js +170 -0
  16. package/components/data-display/badge.js +82 -0
  17. package/components/data-display/card.js +151 -0
  18. package/components/data-display/carousel.js +94 -0
  19. package/components/data-display/chart.js +220 -0
  20. package/components/data-display/chat.js +128 -0
  21. package/components/data-display/collapse.js +103 -0
  22. package/components/data-display/countdown.js +69 -0
  23. package/components/data-display/diff.js +111 -0
  24. package/components/data-display/kbd.js +65 -0
  25. package/components/data-display/loading.js +75 -0
  26. package/components/data-display/progress.js +79 -0
  27. package/components/data-display/radial-progress.js +88 -0
  28. package/components/data-display/skeleton.js +66 -0
  29. package/components/data-display/stats.js +159 -0
  30. package/components/data-display/table.js +146 -0
  31. package/components/data-display/timeline.js +146 -0
  32. package/components/data-display/toast.js +72 -0
  33. package/components/data-display/tooltip.js +74 -0
  34. package/components/data-input/checkbox.js +253 -0
  35. package/components/data-input/file-input.js +224 -0
  36. package/components/data-input/input.js +264 -0
  37. package/components/data-input/radio.js +338 -0
  38. package/components/data-input/range.js +204 -0
  39. package/components/data-input/rating.js +219 -0
  40. package/components/data-input/select.js +287 -0
  41. package/components/data-input/textarea.js +287 -0
  42. package/components/data-input/toggle.js +201 -0
  43. package/components/index.js +137 -0
  44. package/components/layout/divider.js +72 -0
  45. package/components/layout/drawer.js +142 -0
  46. package/components/layout/footer.js +100 -0
  47. package/components/layout/hero.js +109 -0
  48. package/components/layout/indicator.js +90 -0
  49. package/components/layout/join.js +78 -0
  50. package/components/layout/navbar.js +110 -0
  51. package/components/navigation/breadcrumbs.js +91 -0
  52. package/components/navigation/dock.js +103 -0
  53. package/components/navigation/menu.js +126 -0
  54. package/components/navigation/pagination.js +105 -0
  55. package/components/navigation/steps.js +89 -0
  56. package/components/navigation/tabs.css +177 -0
  57. package/components/navigation/tabs.js +123 -0
  58. package/components/theme/theme-switch.css +65 -0
  59. package/components/theme/theme-switch.js +177 -0
  60. package/docs/about.html +164 -0
  61. package/docs/api/computed.html +184 -0
  62. package/docs/api/effects.html +173 -0
  63. package/docs/api/elements.html +180 -0
  64. package/docs/api/enhance.html +225 -0
  65. package/docs/api/hypermedia.html +165 -0
  66. package/docs/api/index.html +178 -0
  67. package/docs/api/nav.html +18 -0
  68. package/docs/api/signals.html +136 -0
  69. package/docs/api/state.html +217 -0
  70. package/docs/assets/images/logo-favicon.svg +42 -0
  71. package/docs/assets/images/logo-static.svg +40 -0
  72. package/docs/assets/images/logo.svg +66 -0
  73. package/docs/assets/js/examplify.js +395 -0
  74. package/docs/assets/styles/site.css +1102 -0
  75. package/docs/assets/styles/themes.css +236 -0
  76. package/docs/components/accordion.html +439 -0
  77. package/docs/components/alert.html +528 -0
  78. package/docs/components/avatar.html +586 -0
  79. package/docs/components/badge.html +531 -0
  80. package/docs/components/breadcrumbs.html +278 -0
  81. package/docs/components/button.html +579 -0
  82. package/docs/components/card.html +561 -0
  83. package/docs/components/carousel.html +286 -0
  84. package/docs/components/chart-area.html +702 -0
  85. package/docs/components/chart-bar.html +782 -0
  86. package/docs/components/chart-column.html +735 -0
  87. package/docs/components/chart-line.html +794 -0
  88. package/docs/components/chart-pie.html +823 -0
  89. package/docs/components/chart.html +610 -15
  90. package/docs/components/chat.html +547 -0
  91. package/docs/components/checkbox.html +641 -0
  92. package/docs/components/collapse.html +536 -0
  93. package/docs/components/component-nav.html +53 -0
  94. package/docs/components/countdown.html +470 -0
  95. package/docs/components/diff.html +245 -0
  96. package/docs/components/divider.html +240 -0
  97. package/docs/components/dock.html +277 -0
  98. package/docs/components/drawer.html +515 -0
  99. package/docs/components/dropdown.html +479 -0
  100. package/docs/components/file-input.html +591 -0
  101. package/docs/components/footer.html +301 -0
  102. package/docs/components/gallery.html +504 -0
  103. package/docs/components/hero.html +264 -0
  104. package/docs/components/index.css +840 -0
  105. package/docs/components/index.html +735 -0
  106. package/docs/components/indicator.html +342 -0
  107. package/docs/components/input.html +644 -0
  108. package/docs/components/join.html +285 -0
  109. package/docs/components/kbd.html +322 -0
  110. package/docs/components/loading.html +521 -0
  111. package/docs/components/menu.html +461 -0
  112. package/docs/components/modal.html +639 -0
  113. package/docs/components/navbar.html +321 -0
  114. package/docs/components/pagination.html +279 -0
  115. package/docs/components/progress.html +514 -0
  116. package/docs/components/radial-progress.html +434 -0
  117. package/docs/components/radio.html +655 -0
  118. package/docs/components/range.html +611 -0
  119. package/docs/components/rating.html +642 -0
  120. package/docs/components/select.html +696 -0
  121. package/docs/components/sidebar-setup.js +93 -0
  122. package/docs/components/skeleton.html +447 -0
  123. package/docs/components/spinner.html +68 -0
  124. package/docs/components/stats.html +486 -0
  125. package/docs/components/steps.html +356 -0
  126. package/docs/components/swap.html +517 -0
  127. package/docs/components/switch.html +68 -0
  128. package/docs/components/table.html +668 -0
  129. package/docs/components/tabs.html +506 -0
  130. package/docs/components/text-input.html +68 -0
  131. package/docs/components/textarea.html +603 -0
  132. package/docs/components/timeline.html +485 -42
  133. package/docs/components/toast.html +474 -0
  134. package/docs/components/toggle.html +564 -0
  135. package/docs/components/tooltip.html +423 -0
  136. package/docs/examples/getting-started-example.html +40 -0
  137. package/docs/examples/index.html +93 -0
  138. package/docs/getting-started/index.html +739 -0
  139. package/docs/getting-started/reviews.html +23 -0
  140. package/docs/getting-started/reviews.odom +108 -0
  141. package/docs/getting-started/reviews.vdom +84 -0
  142. package/docs/index.html +132 -42
  143. package/docs/playground.html +416 -0
  144. package/docs/router.html +285 -0
  145. package/docs/styles/index.html +190 -0
  146. package/functions/_middleware.js +32 -0
  147. package/index.html +309 -0
  148. package/lightview-router.js +364 -0
  149. package/lightview-x.js +1577 -0
  150. package/lightview.js +659 -1200
  151. package/middleware/locale.js +25 -0
  152. package/middleware/markdown.js +44 -0
  153. package/middleware/notFound.js +37 -0
  154. package/package.json +27 -41
  155. package/watch.js +92 -0
  156. package/wrangler.toml +12 -0
  157. package/.idea/lightview.iml +0 -12
  158. package/.idea/modules.xml +0 -8
  159. package/.idea/vcs.xml +0 -6
  160. package/LICENSE +0 -21
  161. package/codepen-no-tabs-embed.css +0 -2
  162. package/docs/CNAME +0 -1
  163. package/docs/api.html +0 -674
  164. package/docs/blank.html +0 -10
  165. package/docs/comparedto.html +0 -89
  166. package/docs/components/chart-repl.html +0 -69
  167. package/docs/components/components.js +0 -113
  168. package/docs/components/contents.html +0 -17
  169. package/docs/components/gantt-repl.html +0 -61
  170. package/docs/components/gantt.html +0 -42
  171. package/docs/components/gauge-repl.html +0 -66
  172. package/docs/components/gauge.html +0 -20
  173. package/docs/components/orgchart-repl.html +0 -64
  174. package/docs/components/orgchart.html +0 -41
  175. package/docs/components/repl-as-src.html +0 -17
  176. package/docs/components/repl-repl.html +0 -95
  177. package/docs/components/repl.html +0 -527
  178. package/docs/components/timeline-repl.html +0 -72
  179. package/docs/components.html +0 -14
  180. package/docs/css/highlightjs.min.css +0 -9
  181. package/docs/css/tutorial.css +0 -35
  182. package/docs/examples/anchor.html +0 -11
  183. package/docs/examples/chart.html +0 -34
  184. package/docs/examples/counter.html +0 -26
  185. package/docs/examples/counter.test.mjs +0 -47
  186. package/docs/examples/counter2.html +0 -26
  187. package/docs/examples/directives.html +0 -79
  188. package/docs/examples/foreign.html +0 -50
  189. package/docs/examples/forgeinform.html +0 -98
  190. package/docs/examples/form.html +0 -61
  191. package/docs/examples/gauge.html +0 -18
  192. package/docs/examples/invalid-template-literals.html +0 -44
  193. package/docs/examples/medium/remote.html +0 -60
  194. package/docs/examples/message.html +0 -18
  195. package/docs/examples/nested.html +0 -11
  196. package/docs/examples/object-bound-form.html +0 -34
  197. package/docs/examples/remote-server.js +0 -51
  198. package/docs/examples/remote.html +0 -34
  199. package/docs/examples/remote.json +0 -1
  200. package/docs/examples/scratch.html +0 -69
  201. package/docs/examples/sensors/index.html +0 -44
  202. package/docs/examples/sensors/sensor-server.js +0 -30
  203. package/docs/examples/shared.html +0 -41
  204. package/docs/examples/template.html +0 -33
  205. package/docs/examples/timeline.html +0 -21
  206. package/docs/examples/todo.html +0 -40
  207. package/docs/examples/top.html +0 -10
  208. package/docs/examples/types.html +0 -94
  209. package/docs/examples/xor.html +0 -62
  210. package/docs/examples.html +0 -25
  211. package/docs/javascript/codejar.min.js +0 -8
  212. package/docs/javascript/highlightjs.min.js +0 -1173
  213. package/docs/javascript/isomorphic-git.js +0 -9
  214. package/docs/javascript/json5.min.js +0 -1
  215. package/docs/javascript/lightning-fs.js +0 -1
  216. package/docs/javascript/lightview.js +0 -1285
  217. package/docs/javascript/marked.min.js +0 -6
  218. package/docs/javascript/peerjs.min.js +0 -70
  219. package/docs/javascript/turndown.js +0 -973
  220. package/docs/javascript/types.js +0 -606
  221. package/docs/javascript/utils.js +0 -45
  222. package/docs/lightview.html +0 -63
  223. package/docs/old_index.html +0 -965
  224. package/docs/old_index.md +0 -1132
  225. package/docs/slidein.html +0 -51
  226. package/docs/tutorial/0-getting-started.html +0 -67
  227. package/docs/tutorial/1-intro-to-variables.html +0 -103
  228. package/docs/tutorial/10-template-components.html +0 -80
  229. package/docs/tutorial/11-linked-components.html +0 -76
  230. package/docs/tutorial/12-imported-components.html +0 -67
  231. package/docs/tutorial/13-input-binding.html +0 -94
  232. package/docs/tutorial/14-automatic-variable-creation.html +0 -74
  233. package/docs/tutorial/15-form-binding.html +0 -110
  234. package/docs/tutorial/16-if-directive.html +0 -60
  235. package/docs/tutorial/17-loop-directives.html +0 -83
  236. package/docs/tutorial/18-sanitizing-and-escaping-input.html +0 -79
  237. package/docs/tutorial/2-imported-and-exported-variables.html +0 -80
  238. package/docs/tutorial/3-data-types.html +0 -89
  239. package/docs/tutorial/4-extended-data-types.html +0 -83
  240. package/docs/tutorial/5-extended-functional-types.html +0 -96
  241. package/docs/tutorial/5.1-extended-functional-types.html +0 -79
  242. package/docs/tutorial/5.2-extended-functional-types.html +0 -70
  243. package/docs/tutorial/6-conventional-javascript.html +0 -75
  244. package/docs/tutorial/7-monitoring-with-observers.html +0 -107
  245. package/docs/tutorial/8-event-listeners.html +0 -65
  246. package/docs/tutorial/9-intro-to-components.html +0 -91
  247. package/docs/tutorial/contents.html +0 -32
  248. package/docs/tutorial/my-component.html +0 -29
  249. package/docs/tutorial/remote-value.json +0 -4
  250. package/docs/websiterepl.html +0 -46
  251. package/jest-puppeteer.config.js +0 -5
  252. package/jest.config.json +0 -12
  253. package/lightview.min.js +0 -1
  254. package/lightview_good.js +0 -1267
  255. package/lightview_optimized.js +0 -1274
  256. package/repl_hold.html +0 -320
  257. package/test/basic.html +0 -104
  258. package/test/basic.test.mjs +0 -315
  259. package/test/extended.html +0 -29
  260. package/test/extended.test.mjs +0 -448
  261. package/types.js +0 -607
  262. package/unsplash.key +0 -1
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Lightview Components - Checkbox
3
+ * A checkbox component using DaisyUI 5 styling with validation support
4
+ * @see https://daisyui.com/components/checkbox/
5
+ *
6
+ * Uses DaisyUI's form-control pattern:
7
+ * <div class="form-control">
8
+ * <label class="label cursor-pointer">
9
+ * <span class="label-text">Label</span>
10
+ * <input type="checkbox" class="checkbox" />
11
+ * </label>
12
+ * </div>
13
+ */
14
+
15
+ import '../daisyui.js';
16
+
17
+ /**
18
+ * Checkbox Component
19
+ * @param {Object} props - Checkbox properties
20
+ * @param {boolean|Signal} props.checked - Controlled checked state
21
+ * @param {boolean} props.defaultChecked - Initial checked state (uncontrolled)
22
+ * @param {boolean} props.indeterminate - Indeterminate state
23
+ * @param {string} props.size - 'xs' | 'sm' | 'md' | 'lg' (default: 'md')
24
+ * @param {string} props.color - 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error'
25
+ * @param {boolean} props.disabled - Disable checkbox
26
+ * @param {boolean} props.required - Required field
27
+ * @param {string} props.label - Label text
28
+ * @param {string} props.description - Description text below label
29
+ * @param {string|Function} props.error - Error message
30
+ * @param {Function} props.validate - Validation function (checked) => errorMessage | null
31
+ * @param {Function} props.onChange - Change handler
32
+ * @param {boolean} props.useShadow - Render in Shadow DOM with isolated DaisyUI styles
33
+ */
34
+ const Checkbox = (props = {}) => {
35
+ const { tags, signal } = window.Lightview || {};
36
+ const LVX = window.LightviewX || {};
37
+
38
+ if (!tags) {
39
+ console.error('Lightview not found');
40
+ return null;
41
+ }
42
+
43
+ const { div, input, label, span, p, shadowDOM } = tags;
44
+
45
+ const {
46
+ checked,
47
+ defaultChecked = false,
48
+ indeterminate = false,
49
+ size = 'md',
50
+ color,
51
+ disabled = false,
52
+ required = false,
53
+ label: labelText,
54
+ description,
55
+ error,
56
+ validate,
57
+ onChange,
58
+ name,
59
+ id,
60
+ value,
61
+ class: className = '',
62
+ useShadow,
63
+ ...rest
64
+ } = props;
65
+
66
+ // Generate unique ID if not provided
67
+ const checkboxId = id || `checkbox-${Math.random().toString(36).slice(2, 9)}`;
68
+
69
+ // Internal state
70
+ const internalChecked = signal ? signal(defaultChecked) : { value: defaultChecked };
71
+ const internalError = signal ? signal(null) : { value: null };
72
+
73
+ const isControlled = checked !== undefined;
74
+
75
+ const getChecked = () => {
76
+ if (isControlled) {
77
+ return typeof checked === 'function' ? checked() :
78
+ (checked && typeof checked.value !== 'undefined') ? checked.value : checked;
79
+ }
80
+ return internalChecked.value;
81
+ };
82
+
83
+ const getError = () => {
84
+ if (error) {
85
+ const err = typeof error === 'function' ? error() : error;
86
+ if (err) return err;
87
+ }
88
+ return internalError.value;
89
+ };
90
+
91
+ const runValidation = (val) => {
92
+ if (validate) {
93
+ const result = validate(val);
94
+ internalError.value = result;
95
+ return result;
96
+ }
97
+ if (required && !val) {
98
+ internalError.value = 'This field is required';
99
+ return 'This field is required';
100
+ }
101
+ internalError.value = null;
102
+ return null;
103
+ };
104
+
105
+ const handleChange = (e) => {
106
+ const newValue = e.target.checked;
107
+
108
+ if (!isControlled) {
109
+ internalChecked.value = newValue;
110
+ }
111
+
112
+ if (isControlled && checked && typeof checked.value !== 'undefined') {
113
+ checked.value = newValue;
114
+ }
115
+
116
+ runValidation(newValue);
117
+
118
+ if (onChange) onChange(newValue, e);
119
+ };
120
+
121
+ // Build DaisyUI checkbox classes
122
+ const getCheckboxClass = () => {
123
+ const classes = ['checkbox'];
124
+
125
+ // Size
126
+ if (size && size !== 'md') {
127
+ classes.push(`checkbox-${size}`);
128
+ }
129
+
130
+ // Color
131
+ if (color) {
132
+ classes.push(`checkbox-${color}`);
133
+ }
134
+
135
+ return classes.join(' ');
136
+ };
137
+
138
+ // Build checkbox input
139
+ const checkboxInput = input({
140
+ type: 'checkbox',
141
+ class: getCheckboxClass(),
142
+ checked: isControlled
143
+ ? (typeof checked === 'function' ? checked : () => getChecked())
144
+ : () => internalChecked.value,
145
+ disabled: typeof disabled === 'function' ? disabled : disabled,
146
+ required,
147
+ name,
148
+ id: checkboxId,
149
+ value,
150
+ onchange: handleChange,
151
+ 'aria-invalid': () => !!getError(),
152
+ onmount: (el) => {
153
+ // Handle indeterminate state (can only be set via JS)
154
+ if (indeterminate) {
155
+ el.indeterminate = typeof indeterminate === 'function' ? indeterminate() : indeterminate;
156
+ }
157
+ },
158
+ ...rest
159
+ });
160
+
161
+ // If no label, return just the checkbox
162
+ if (!labelText && !description) {
163
+ // Check if we should use shadow DOM
164
+ let usesShadow = false;
165
+ if (LVX.shouldUseShadow) {
166
+ usesShadow = LVX.shouldUseShadow(useShadow);
167
+ } else {
168
+ usesShadow = useShadow === true;
169
+ }
170
+
171
+ if (usesShadow) {
172
+ const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
173
+ const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
174
+
175
+ return div({ class: 'content', style: 'display: inline-block' },
176
+ shadowDOM({ mode: 'open', adoptedStyleSheets },
177
+ div({ 'data-theme': themeValue }, checkboxInput)
178
+ )
179
+ );
180
+ }
181
+
182
+ return checkboxInput;
183
+ }
184
+
185
+ // Build label content
186
+ const labelContent = [];
187
+ if (labelText) {
188
+ labelContent.push(
189
+ span({ class: 'label-text' },
190
+ labelText,
191
+ required ? span({ class: 'text-error' }, ' *') : null
192
+ )
193
+ );
194
+ }
195
+
196
+ // Build the form control wrapper
197
+ const formControl = div({
198
+ class: `form-control ${className}`.trim()
199
+ },
200
+ label({ class: 'label cursor-pointer', style: 'justify-content: flex-start; gap: 0;' },
201
+ checkboxInput,
202
+ labelContent.length > 0 ? div({ style: 'display: flex; flex-direction: column; margin-left: 0.5rem;' },
203
+ ...labelContent,
204
+ description ? span({ class: 'label-text-alt', style: 'opacity: 0.7;' }, description) : null
205
+ ) : null
206
+ ),
207
+ // Error message
208
+ (validate || error || required) ? () => {
209
+ const currentError = getError();
210
+ return currentError
211
+ ? p({ class: 'label', style: 'color: oklch(var(--er)); font-size: 0.875rem;', role: 'alert' }, currentError)
212
+ : null;
213
+ } : null
214
+ );
215
+
216
+ // Check if we should use shadow DOM
217
+ let usesShadow = false;
218
+ if (LVX.shouldUseShadow) {
219
+ usesShadow = LVX.shouldUseShadow(useShadow);
220
+ } else {
221
+ usesShadow = useShadow === true;
222
+ }
223
+
224
+ if (usesShadow) {
225
+ const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
226
+
227
+ const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
228
+
229
+ return span({ style: 'margin-right: 0.5rem' },
230
+ shadowDOM({ mode: 'open', adoptedStyleSheets },
231
+ div({ 'data-theme': themeValue, style: 'display: inline-block' },
232
+ formControl
233
+ )
234
+ )
235
+ );
236
+ }
237
+
238
+ return formControl;
239
+ };
240
+
241
+ // Auto-register
242
+ window.Lightview.tags.Checkbox = Checkbox;
243
+
244
+ // Register as Custom Element
245
+ if (window.LightviewX?.createCustomElement) {
246
+ const CheckboxElement = window.LightviewX.createCustomElement(Checkbox);
247
+ if (!customElements.get('lv-checkbox')) {
248
+ customElements.define('lv-checkbox', CheckboxElement);
249
+ }
250
+ }
251
+
252
+ export default Checkbox;
253
+
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Lightview Components - FileInput
3
+ * A file input component using DaisyUI 5 styling
4
+ * @see https://daisyui.com/components/file-input/
5
+ *
6
+ * Uses DaisyUI's fieldset pattern:
7
+ * <fieldset class="fieldset">
8
+ * <legend class="fieldset-legend">Label</legend>
9
+ * <input type="file" class="file-input" />
10
+ * <p class="label">Helper text</p>
11
+ * </fieldset>
12
+ */
13
+
14
+ import '../daisyui.js';
15
+
16
+ /**
17
+ * FileInput Component
18
+ * @param {Object} props - FileInput properties
19
+ * @param {string} props.size - 'xs' | 'sm' | 'md' | 'lg' (default: 'md')
20
+ * @param {string} props.color - 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error'
21
+ * @param {boolean} props.ghost - Ghost style (no background)
22
+ * @param {boolean} props.disabled - Disable file input
23
+ * @param {boolean} props.required - Required field
24
+ * @param {string} props.accept - Accepted file types (e.g., '.pdf,.doc', 'image/*')
25
+ * @param {boolean} props.multiple - Allow multiple file selection
26
+ * @param {string} props.label - Label text
27
+ * @param {string} props.helper - Helper text
28
+ * @param {string|Function} props.error - Error message
29
+ * @param {Function} props.validate - Validation function (files) => errorMessage | null
30
+ * @param {Function} props.onChange - Change handler (files, event) => void
31
+ * @param {boolean} props.useShadow - Render in Shadow DOM
32
+ */
33
+ const FileInput = (props = {}) => {
34
+ const { tags, signal } = window.Lightview || {};
35
+ const LVX = window.LightviewX || {};
36
+
37
+ if (!tags) {
38
+ console.error('Lightview not found');
39
+ return null;
40
+ }
41
+
42
+ const { div, input, fieldset, legend, p, span, shadowDOM } = tags;
43
+
44
+ const {
45
+ size = 'md',
46
+ color,
47
+ ghost = false,
48
+ disabled = false,
49
+ required = false,
50
+ accept,
51
+ multiple = false,
52
+ label: labelText,
53
+ helper,
54
+ error,
55
+ validate,
56
+ onChange,
57
+ name,
58
+ id,
59
+ class: className = '',
60
+ useShadow,
61
+ ...rest
62
+ } = props;
63
+
64
+ const fileInputId = id || `file-input-${Math.random().toString(36).slice(2, 9)}`;
65
+
66
+ // Internal state
67
+ const internalError = signal ? signal(null) : { value: null };
68
+
69
+ const getError = () => {
70
+ if (error) {
71
+ const err = typeof error === 'function' ? error() : error;
72
+ if (err) return err;
73
+ }
74
+ return internalError.value;
75
+ };
76
+
77
+ const handleChange = (e) => {
78
+ const files = e.target.files;
79
+
80
+ // Validation
81
+ if (validate) {
82
+ const validationError = validate(files);
83
+ internalError.value = validationError;
84
+ } else if (required && (!files || files.length === 0)) {
85
+ internalError.value = 'Please select a file';
86
+ } else {
87
+ internalError.value = null;
88
+ }
89
+
90
+ if (onChange) onChange(files, e);
91
+ };
92
+
93
+ // Build DaisyUI file-input classes
94
+ const getFileInputClass = () => {
95
+ const classes = ['file-input', 'w-full'];
96
+
97
+ // Ghost style
98
+ if (ghost) {
99
+ classes.push('file-input-ghost');
100
+ }
101
+
102
+ // Size
103
+ if (size && size !== 'md') {
104
+ classes.push(`file-input-${size}`);
105
+ }
106
+
107
+ // Color
108
+ if (color) {
109
+ classes.push(`file-input-${color}`);
110
+ }
111
+
112
+ // Error state
113
+ const currentError = getError();
114
+ if (currentError) {
115
+ classes.push('file-input-error');
116
+ }
117
+
118
+ return classes.join(' ');
119
+ };
120
+
121
+ const fileInputEl = input({
122
+ type: 'file',
123
+ class: validate || error ? () => getFileInputClass() : getFileInputClass(),
124
+ accept,
125
+ multiple,
126
+ disabled,
127
+ required,
128
+ name,
129
+ id: fileInputId,
130
+ onchange: handleChange,
131
+ 'aria-invalid': () => !!getError(),
132
+ ...rest
133
+ });
134
+
135
+ // If no label and no helper, return just the file input
136
+ if (!labelText && !helper && !validate && !error) {
137
+ let usesShadow = false;
138
+ if (LVX.shouldUseShadow) {
139
+ usesShadow = LVX.shouldUseShadow(useShadow);
140
+ } else {
141
+ usesShadow = useShadow === true;
142
+ }
143
+
144
+ if (usesShadow) {
145
+ const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
146
+ const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
147
+
148
+ return div({ class: 'content', style: 'display: inline-block' },
149
+ shadowDOM({ mode: 'open', adoptedStyleSheets },
150
+ div({ 'data-theme': themeValue }, fileInputEl)
151
+ )
152
+ );
153
+ }
154
+
155
+ return fileInputEl;
156
+ }
157
+
158
+ // Build the component using DaisyUI fieldset pattern
159
+ const fieldsetContent = [];
160
+
161
+ // Legend/Label
162
+ if (labelText) {
163
+ fieldsetContent.push(
164
+ legend({ class: 'fieldset-legend' },
165
+ labelText,
166
+ required ? span({ class: 'text-error' }, ' *') : null
167
+ )
168
+ );
169
+ }
170
+
171
+ // File input element
172
+ fieldsetContent.push(fileInputEl);
173
+
174
+ // Helper or error text
175
+ if (helper || validate || error) {
176
+ fieldsetContent.push(
177
+ () => {
178
+ const currentError = getError();
179
+ if (currentError) {
180
+ return p({
181
+ class: 'label text-error',
182
+ role: 'alert'
183
+ }, currentError);
184
+ }
185
+ if (helper) {
186
+ return p({ class: 'label' }, helper);
187
+ }
188
+ return null;
189
+ }
190
+ );
191
+ }
192
+
193
+ // Wrapper
194
+ const wrapperEl = fieldset({
195
+ class: `fieldset ${className}`.trim()
196
+ }, ...fieldsetContent);
197
+
198
+ // Check if we should use shadow DOM
199
+ let usesShadow = false;
200
+ if (LVX.shouldUseShadow) {
201
+ usesShadow = LVX.shouldUseShadow(useShadow);
202
+ } else {
203
+ usesShadow = useShadow === true;
204
+ }
205
+
206
+ if (usesShadow) {
207
+ const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
208
+
209
+ const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
210
+
211
+ return div({ class: 'contents' },
212
+ shadowDOM({ mode: 'open', adoptedStyleSheets },
213
+ div({ 'data-theme': themeValue }, wrapperEl)
214
+ )
215
+ );
216
+ }
217
+
218
+ return wrapperEl;
219
+ };
220
+
221
+ // Auto-register
222
+ window.Lightview.tags.FileInput = FileInput;
223
+
224
+ export default FileInput;