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