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,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightview Components - Range
|
|
3
|
+
* A range slider component using DaisyUI 5 styling
|
|
4
|
+
* @see https://daisyui.com/components/range/
|
|
5
|
+
*
|
|
6
|
+
* Uses DaisyUI's fieldset pattern for labeled ranges:
|
|
7
|
+
* <fieldset class="fieldset">
|
|
8
|
+
* <legend class="fieldset-legend">Label</legend>
|
|
9
|
+
* <input type="range" class="range" />
|
|
10
|
+
* <p class="label">Helper</p>
|
|
11
|
+
* </fieldset>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import '../daisyui.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Range Component
|
|
18
|
+
* @param {Object} props - Range properties
|
|
19
|
+
* @param {number|Signal} props.value - Current value (controlled)
|
|
20
|
+
* @param {number} props.defaultValue - Default value (uncontrolled)
|
|
21
|
+
* @param {number} props.min - Minimum value (default: 0)
|
|
22
|
+
* @param {number} props.max - Maximum value (default: 100)
|
|
23
|
+
* @param {number} props.step - Step increment (default: 1)
|
|
24
|
+
* @param {string} props.size - 'xs' | 'sm' | 'md' | 'lg' (default: 'md')
|
|
25
|
+
* @param {string} props.color - 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error'
|
|
26
|
+
* @param {boolean} props.disabled - Disable range
|
|
27
|
+
* @param {string} props.label - Label text
|
|
28
|
+
* @param {string} props.helper - Helper text
|
|
29
|
+
* @param {boolean} props.showValue - Show current value display
|
|
30
|
+
* @param {Function} props.formatValue - Format function for displayed value
|
|
31
|
+
* @param {Function} props.onChange - Change handler
|
|
32
|
+
* @param {boolean} props.useShadow - Render in Shadow DOM
|
|
33
|
+
*/
|
|
34
|
+
const Range = (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, fieldset, legend, p, span, shadowDOM } = tags;
|
|
44
|
+
|
|
45
|
+
const {
|
|
46
|
+
value,
|
|
47
|
+
defaultValue = 0,
|
|
48
|
+
min = 0,
|
|
49
|
+
max = 100,
|
|
50
|
+
step = 1,
|
|
51
|
+
size = 'md',
|
|
52
|
+
color,
|
|
53
|
+
disabled = false,
|
|
54
|
+
label: labelText,
|
|
55
|
+
helper,
|
|
56
|
+
showValue = false,
|
|
57
|
+
formatValue = (v) => v,
|
|
58
|
+
onChange,
|
|
59
|
+
name,
|
|
60
|
+
id,
|
|
61
|
+
class: className = '',
|
|
62
|
+
useShadow,
|
|
63
|
+
...rest
|
|
64
|
+
} = props;
|
|
65
|
+
|
|
66
|
+
const rangeId = id || `range-${Math.random().toString(36).slice(2, 9)}`;
|
|
67
|
+
|
|
68
|
+
// Internal state
|
|
69
|
+
const internalValue = signal ? signal(defaultValue) : { value: defaultValue };
|
|
70
|
+
|
|
71
|
+
const isControlled = value !== undefined;
|
|
72
|
+
|
|
73
|
+
const getValue = () => {
|
|
74
|
+
if (isControlled) {
|
|
75
|
+
return typeof value === 'function' ? value() :
|
|
76
|
+
(value && typeof value.value !== 'undefined') ? value.value : value;
|
|
77
|
+
}
|
|
78
|
+
return internalValue.value;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleInput = (e) => {
|
|
82
|
+
const newValue = Number(e.target.value);
|
|
83
|
+
|
|
84
|
+
if (!isControlled) {
|
|
85
|
+
internalValue.value = newValue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isControlled && value && typeof value.value !== 'undefined') {
|
|
89
|
+
value.value = newValue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (onChange) onChange(newValue, e);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Build DaisyUI range classes
|
|
96
|
+
const getRangeClass = () => {
|
|
97
|
+
const classes = ['range', 'w-full'];
|
|
98
|
+
|
|
99
|
+
if (size && size !== 'md') {
|
|
100
|
+
classes.push(`range-${size}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (color) {
|
|
104
|
+
classes.push(`range-${color}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return classes.join(' ');
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const rangeInput = input({
|
|
111
|
+
type: 'range',
|
|
112
|
+
class: getRangeClass(),
|
|
113
|
+
value: isControlled
|
|
114
|
+
? (typeof value === 'function' ? value : () => getValue())
|
|
115
|
+
: () => internalValue.value,
|
|
116
|
+
min,
|
|
117
|
+
max,
|
|
118
|
+
step,
|
|
119
|
+
disabled: typeof disabled === 'function' ? disabled : disabled,
|
|
120
|
+
name,
|
|
121
|
+
id: rangeId,
|
|
122
|
+
oninput: handleInput,
|
|
123
|
+
...rest
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// If no label and no showValue, return just the range
|
|
127
|
+
if (!labelText && !showValue && !helper) {
|
|
128
|
+
// Check if we should use shadow DOM
|
|
129
|
+
let usesShadow = false;
|
|
130
|
+
if (LVX.shouldUseShadow) {
|
|
131
|
+
usesShadow = LVX.shouldUseShadow(useShadow);
|
|
132
|
+
} else {
|
|
133
|
+
usesShadow = useShadow === true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (usesShadow) {
|
|
137
|
+
const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
|
|
138
|
+
const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
|
|
139
|
+
|
|
140
|
+
return div({ class: 'content', style: 'display: inline-block' },
|
|
141
|
+
shadowDOM({ mode: 'open', adoptedStyleSheets },
|
|
142
|
+
div({ 'data-theme': themeValue }, rangeInput)
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return rangeInput;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Build the component using DaisyUI fieldset pattern
|
|
151
|
+
const fieldsetContent = [];
|
|
152
|
+
|
|
153
|
+
// Legend/Label with optional value display
|
|
154
|
+
if (labelText || showValue) {
|
|
155
|
+
fieldsetContent.push(
|
|
156
|
+
div({ class: 'flex justify-between items-center mb-1' },
|
|
157
|
+
labelText ? legend({ class: 'fieldset-legend' }, labelText) : span(),
|
|
158
|
+
showValue ? span({ class: 'text-sm font-mono opacity-70' },
|
|
159
|
+
() => formatValue(getValue())
|
|
160
|
+
) : null
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Range input
|
|
166
|
+
fieldsetContent.push(rangeInput);
|
|
167
|
+
|
|
168
|
+
// Helper text
|
|
169
|
+
if (helper) {
|
|
170
|
+
fieldsetContent.push(p({ class: 'label' }, helper));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Wrapper
|
|
174
|
+
const wrapperEl = fieldset({
|
|
175
|
+
class: `fieldset ${className}`.trim()
|
|
176
|
+
}, ...fieldsetContent);
|
|
177
|
+
|
|
178
|
+
// Check if we should use shadow DOM
|
|
179
|
+
let usesShadow = false;
|
|
180
|
+
if (LVX.shouldUseShadow) {
|
|
181
|
+
usesShadow = LVX.shouldUseShadow(useShadow);
|
|
182
|
+
} else {
|
|
183
|
+
usesShadow = useShadow === true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (usesShadow) {
|
|
187
|
+
const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
|
|
188
|
+
|
|
189
|
+
const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
|
|
190
|
+
|
|
191
|
+
return span({ style: 'margin-right: 0.5rem' },
|
|
192
|
+
shadowDOM({ mode: 'open', adoptedStyleSheets },
|
|
193
|
+
div({ 'data-theme': themeValue, style: 'display: inline-block' }, wrapperEl)
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return wrapperEl;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Auto-register
|
|
202
|
+
window.Lightview.tags.Range = Range;
|
|
203
|
+
|
|
204
|
+
export default Range;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightview Components - Rating
|
|
3
|
+
* A rating component using DaisyUI 5 styling
|
|
4
|
+
* @see https://daisyui.com/components/rating/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import '../daisyui.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Rating Component
|
|
11
|
+
* @param {Object} props - Rating properties
|
|
12
|
+
* @param {number|Signal} props.value - Current rating value (controlled)
|
|
13
|
+
* @param {number} props.defaultValue - Default rating value (uncontrolled)
|
|
14
|
+
* @param {number} props.max - Maximum stars (default: 5)
|
|
15
|
+
* @param {string} props.size - 'xs' | 'sm' | 'md' | 'lg'
|
|
16
|
+
* @param {boolean} props.half - Allow half stars
|
|
17
|
+
* @param {string} props.color - Color for stars (default: 'orange-400')
|
|
18
|
+
* @param {string} props.mask - 'star' | 'star-2' | 'heart' | 'circle' | 'square' | 'diamond' (default: 'star-2')
|
|
19
|
+
* @param {boolean} props.hidden - Include hidden 0-star option for clearing
|
|
20
|
+
* @param {boolean} props.disabled - Disable rating
|
|
21
|
+
* @param {boolean} props.readOnly - Make read-only (just display)
|
|
22
|
+
* @param {string} props.label - Label text
|
|
23
|
+
* @param {string} props.helper - Helper text
|
|
24
|
+
* @param {Function} props.onChange - Change handler
|
|
25
|
+
* @param {boolean} props.useShadow - Render in Shadow DOM
|
|
26
|
+
*/
|
|
27
|
+
const Rating = (props = {}) => {
|
|
28
|
+
const { tags, signal } = window.Lightview || {};
|
|
29
|
+
const LVX = window.LightviewX || {};
|
|
30
|
+
|
|
31
|
+
if (!tags) {
|
|
32
|
+
console.error('Lightview not found');
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { div, input, fieldset, legend, p, span, shadowDOM } = tags;
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
value,
|
|
40
|
+
defaultValue = 0,
|
|
41
|
+
max = 5,
|
|
42
|
+
size,
|
|
43
|
+
half = false,
|
|
44
|
+
color = 'orange-400',
|
|
45
|
+
mask = 'star-2',
|
|
46
|
+
hidden = false,
|
|
47
|
+
disabled = false,
|
|
48
|
+
readOnly = false,
|
|
49
|
+
label: labelText,
|
|
50
|
+
helper,
|
|
51
|
+
name = `rating-${Math.random().toString(36).slice(2, 9)}`,
|
|
52
|
+
onChange,
|
|
53
|
+
class: className = '',
|
|
54
|
+
useShadow,
|
|
55
|
+
...rest
|
|
56
|
+
} = props;
|
|
57
|
+
|
|
58
|
+
// Internal state
|
|
59
|
+
const internalValue = signal ? signal(defaultValue) : { value: defaultValue };
|
|
60
|
+
|
|
61
|
+
const isControlled = value !== undefined;
|
|
62
|
+
|
|
63
|
+
const getValue = () => {
|
|
64
|
+
if (isControlled) {
|
|
65
|
+
return typeof value === 'function' ? value() :
|
|
66
|
+
(value && typeof value.value !== 'undefined') ? value.value : value;
|
|
67
|
+
}
|
|
68
|
+
return internalValue.value;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const handleChange = (rating) => {
|
|
72
|
+
if (readOnly || disabled) return;
|
|
73
|
+
|
|
74
|
+
if (!isControlled) {
|
|
75
|
+
internalValue.value = rating;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isControlled && value && typeof value.value !== 'undefined') {
|
|
79
|
+
value.value = rating;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (onChange) onChange(rating);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Build rating classes
|
|
86
|
+
const getRatingClass = () => {
|
|
87
|
+
const classes = ['rating'];
|
|
88
|
+
if (size) classes.push(`rating-${size}`);
|
|
89
|
+
if (half) classes.push('rating-half');
|
|
90
|
+
return classes.join(' ');
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const bgClass = `bg-${color}`;
|
|
94
|
+
|
|
95
|
+
// Build rating inputs
|
|
96
|
+
const buildInputs = () => {
|
|
97
|
+
const inputs = [];
|
|
98
|
+
const currentValue = getValue();
|
|
99
|
+
|
|
100
|
+
if (hidden) {
|
|
101
|
+
inputs.push(input({
|
|
102
|
+
type: 'radio',
|
|
103
|
+
name,
|
|
104
|
+
class: 'rating-hidden',
|
|
105
|
+
checked: () => getValue() === 0,
|
|
106
|
+
disabled,
|
|
107
|
+
onchange: () => handleChange(0)
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (let i = 1; i <= max; i++) {
|
|
112
|
+
if (half) {
|
|
113
|
+
// Half stars
|
|
114
|
+
inputs.push(input({
|
|
115
|
+
type: 'radio',
|
|
116
|
+
name,
|
|
117
|
+
class: `mask mask-${mask} mask-half-1 ${bgClass}`,
|
|
118
|
+
'aria-label': `${i - 0.5} stars`,
|
|
119
|
+
checked: () => getValue() === i - 0.5,
|
|
120
|
+
disabled,
|
|
121
|
+
onchange: () => handleChange(i - 0.5)
|
|
122
|
+
}));
|
|
123
|
+
inputs.push(input({
|
|
124
|
+
type: 'radio',
|
|
125
|
+
name,
|
|
126
|
+
class: `mask mask-${mask} mask-half-2 ${bgClass}`,
|
|
127
|
+
'aria-label': `${i} stars`,
|
|
128
|
+
checked: () => getValue() === i,
|
|
129
|
+
disabled,
|
|
130
|
+
onchange: () => handleChange(i)
|
|
131
|
+
}));
|
|
132
|
+
} else {
|
|
133
|
+
inputs.push(input({
|
|
134
|
+
type: 'radio',
|
|
135
|
+
name,
|
|
136
|
+
class: `mask mask-${mask} ${bgClass}`,
|
|
137
|
+
'aria-label': `${i} stars`,
|
|
138
|
+
checked: () => getValue() === i,
|
|
139
|
+
disabled,
|
|
140
|
+
onchange: () => handleChange(i)
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return inputs;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const ratingEl = div({
|
|
149
|
+
class: getRatingClass(),
|
|
150
|
+
...rest
|
|
151
|
+
}, ...buildInputs());
|
|
152
|
+
|
|
153
|
+
// If no label and no helper, return just the rating
|
|
154
|
+
if (!labelText && !helper) {
|
|
155
|
+
let usesShadow = false;
|
|
156
|
+
if (LVX.shouldUseShadow) {
|
|
157
|
+
usesShadow = LVX.shouldUseShadow(useShadow);
|
|
158
|
+
} else {
|
|
159
|
+
usesShadow = useShadow === true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (usesShadow) {
|
|
163
|
+
const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
|
|
164
|
+
const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
|
|
165
|
+
|
|
166
|
+
return div({ class: 'content', style: 'display: inline-block' },
|
|
167
|
+
shadowDOM({ mode: 'open', adoptedStyleSheets },
|
|
168
|
+
div({ 'data-theme': themeValue }, ratingEl)
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return ratingEl;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Build with fieldset pattern
|
|
177
|
+
const fieldsetContent = [];
|
|
178
|
+
|
|
179
|
+
if (labelText) {
|
|
180
|
+
fieldsetContent.push(legend({ class: 'fieldset-legend' }, labelText));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fieldsetContent.push(ratingEl);
|
|
184
|
+
|
|
185
|
+
if (helper) {
|
|
186
|
+
fieldsetContent.push(p({ class: 'label' }, helper));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const wrapperEl = fieldset({
|
|
190
|
+
class: `fieldset ${className}`.trim()
|
|
191
|
+
}, ...fieldsetContent);
|
|
192
|
+
|
|
193
|
+
// Check if we should use shadow DOM
|
|
194
|
+
let usesShadow = false;
|
|
195
|
+
if (LVX.shouldUseShadow) {
|
|
196
|
+
usesShadow = LVX.shouldUseShadow(useShadow);
|
|
197
|
+
} else {
|
|
198
|
+
usesShadow = useShadow === true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (usesShadow) {
|
|
202
|
+
const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
|
|
203
|
+
|
|
204
|
+
const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
|
|
205
|
+
|
|
206
|
+
return span({ style: 'margin-right: 0.5rem' },
|
|
207
|
+
shadowDOM({ mode: 'open', adoptedStyleSheets },
|
|
208
|
+
div({ 'data-theme': themeValue, style: 'display: inline-block' }, wrapperEl)
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return wrapperEl;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Auto-register
|
|
217
|
+
window.Lightview.tags.Rating = Rating;
|
|
218
|
+
|
|
219
|
+
export default Rating;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightview Components - Select
|
|
3
|
+
* A select dropdown component using DaisyUI 5 styling with validation support
|
|
4
|
+
* @see https://daisyui.com/components/select/
|
|
5
|
+
*
|
|
6
|
+
* Uses DaisyUI's fieldset pattern:
|
|
7
|
+
* <fieldset class="fieldset">
|
|
8
|
+
* <legend class="fieldset-legend">Label</legend>
|
|
9
|
+
* <select class="select" />
|
|
10
|
+
* <p class="label">Helper text</p>
|
|
11
|
+
* </fieldset>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import '../daisyui.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Select Component
|
|
18
|
+
* @param {Object} props - Select properties
|
|
19
|
+
* @param {Array} props.options - Array of options: string[] or {value, label, disabled}[]
|
|
20
|
+
* @param {*|Signal} props.value - Selected value (controlled)
|
|
21
|
+
* @param {*} props.defaultValue - Default value (uncontrolled)
|
|
22
|
+
* @param {string} props.placeholder - Placeholder text (shows as disabled first option)
|
|
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 select
|
|
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 select)
|
|
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 {boolean} props.useShadow - Render in Shadow DOM with isolated DaisyUI styles
|
|
34
|
+
*/
|
|
35
|
+
const Select = (props = {}) => {
|
|
36
|
+
const { tags, signal } = window.Lightview || {};
|
|
37
|
+
const LVX = window.LightviewX || {};
|
|
38
|
+
|
|
39
|
+
if (!tags) {
|
|
40
|
+
console.error('Lightview not found');
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { div, select, option, fieldset, legend, p, span, shadowDOM } = tags;
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
options = [],
|
|
48
|
+
value,
|
|
49
|
+
defaultValue = '',
|
|
50
|
+
placeholder,
|
|
51
|
+
size = 'md',
|
|
52
|
+
color,
|
|
53
|
+
ghost = false,
|
|
54
|
+
disabled = false,
|
|
55
|
+
required = false,
|
|
56
|
+
label: labelText,
|
|
57
|
+
helper,
|
|
58
|
+
error,
|
|
59
|
+
validate,
|
|
60
|
+
onChange,
|
|
61
|
+
name,
|
|
62
|
+
id,
|
|
63
|
+
class: className = '',
|
|
64
|
+
useShadow,
|
|
65
|
+
...rest
|
|
66
|
+
} = props;
|
|
67
|
+
|
|
68
|
+
// Generate unique ID if not provided
|
|
69
|
+
const selectId = id || `select-${Math.random().toString(36).slice(2, 9)}`;
|
|
70
|
+
const selectName = name || selectId;
|
|
71
|
+
|
|
72
|
+
// Internal state
|
|
73
|
+
const internalValue = signal ? signal(defaultValue) : { value: defaultValue };
|
|
74
|
+
const internalError = signal ? signal(null) : { value: null };
|
|
75
|
+
const touched = signal ? signal(false) : { value: false };
|
|
76
|
+
|
|
77
|
+
const isControlled = value !== undefined;
|
|
78
|
+
|
|
79
|
+
// Normalize options - first parse from JSON string if needed (from HTML attribute)
|
|
80
|
+
let parsedOptions = options;
|
|
81
|
+
if (typeof parsedOptions === 'string') {
|
|
82
|
+
try {
|
|
83
|
+
parsedOptions = JSON.parse(parsedOptions);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.error('Select: Failed to parse options JSON:', e);
|
|
86
|
+
parsedOptions = [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const normalizedOptions = (Array.isArray(parsedOptions) ? parsedOptions : []).map(opt =>
|
|
91
|
+
typeof opt === 'string' ? { value: opt, label: opt } : opt
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const getValue = () => {
|
|
95
|
+
if (isControlled) {
|
|
96
|
+
return typeof value === 'function' ? value() :
|
|
97
|
+
(value && typeof value.value !== 'undefined') ? value.value : value;
|
|
98
|
+
}
|
|
99
|
+
return internalValue.value;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const getError = () => {
|
|
103
|
+
// External error takes priority
|
|
104
|
+
if (error) {
|
|
105
|
+
const err = typeof error === 'function' ? error() : error;
|
|
106
|
+
if (err) return err;
|
|
107
|
+
}
|
|
108
|
+
// Then internal validation error
|
|
109
|
+
return internalError.value;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const runValidation = (val) => {
|
|
113
|
+
if (!validate) return null;
|
|
114
|
+
const result = validate(val);
|
|
115
|
+
internalError.value = result;
|
|
116
|
+
return result;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleChange = (e) => {
|
|
120
|
+
const newValue = e.target.value;
|
|
121
|
+
|
|
122
|
+
if (!isControlled) {
|
|
123
|
+
internalValue.value = newValue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (isControlled && value && typeof value.value !== 'undefined') {
|
|
127
|
+
value.value = newValue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Validate on change
|
|
131
|
+
touched.value = true;
|
|
132
|
+
runValidation(newValue);
|
|
133
|
+
|
|
134
|
+
if (onChange) onChange(newValue, e);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Build DaisyUI select classes
|
|
138
|
+
const getSelectClass = () => {
|
|
139
|
+
const classes = ['select', 'w-full'];
|
|
140
|
+
|
|
141
|
+
// Ghost style
|
|
142
|
+
if (ghost) {
|
|
143
|
+
classes.push('select-ghost');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Size
|
|
147
|
+
if (size && size !== 'md') {
|
|
148
|
+
classes.push(`select-${size}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Color
|
|
152
|
+
if (color) {
|
|
153
|
+
classes.push(`select-${color}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Error state
|
|
157
|
+
const currentError = getError();
|
|
158
|
+
if (currentError) {
|
|
159
|
+
classes.push('select-error');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return classes.join(' ');
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Build options elements
|
|
166
|
+
const buildOptions = () => {
|
|
167
|
+
const optionEls = [];
|
|
168
|
+
|
|
169
|
+
// Placeholder option
|
|
170
|
+
if (placeholder) {
|
|
171
|
+
optionEls.push(
|
|
172
|
+
option({
|
|
173
|
+
disabled: true,
|
|
174
|
+
selected: () => !getValue(),
|
|
175
|
+
value: ''
|
|
176
|
+
}, placeholder)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Regular options
|
|
181
|
+
normalizedOptions.forEach(opt => {
|
|
182
|
+
optionEls.push(
|
|
183
|
+
option({
|
|
184
|
+
value: opt.value,
|
|
185
|
+
selected: () => getValue() === opt.value,
|
|
186
|
+
disabled: opt.disabled
|
|
187
|
+
}, opt.label || opt.value)
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return optionEls;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Build select attributes
|
|
195
|
+
const selectAttrs = {
|
|
196
|
+
class: validate || error ? () => getSelectClass() : getSelectClass(),
|
|
197
|
+
disabled: typeof disabled === 'function' ? disabled : disabled,
|
|
198
|
+
required,
|
|
199
|
+
name: selectName,
|
|
200
|
+
id: selectId,
|
|
201
|
+
onchange: handleChange,
|
|
202
|
+
'aria-invalid': () => !!getError(),
|
|
203
|
+
...rest
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const selectEl = select(selectAttrs, ...buildOptions());
|
|
207
|
+
|
|
208
|
+
// Build the component using DaisyUI fieldset pattern
|
|
209
|
+
const fieldsetContent = [];
|
|
210
|
+
|
|
211
|
+
// Legend/Label (DaisyUI fieldset-legend)
|
|
212
|
+
if (labelText) {
|
|
213
|
+
fieldsetContent.push(
|
|
214
|
+
legend({ class: 'fieldset-legend' },
|
|
215
|
+
labelText,
|
|
216
|
+
required ? span({ class: 'text-error' }, ' *') : null
|
|
217
|
+
)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Select element
|
|
222
|
+
fieldsetContent.push(selectEl);
|
|
223
|
+
|
|
224
|
+
// Helper or error text
|
|
225
|
+
if (helper || validate || error) {
|
|
226
|
+
fieldsetContent.push(
|
|
227
|
+
() => {
|
|
228
|
+
const currentError = getError();
|
|
229
|
+
if (currentError) {
|
|
230
|
+
return p({
|
|
231
|
+
class: 'label text-error',
|
|
232
|
+
role: 'alert'
|
|
233
|
+
}, currentError);
|
|
234
|
+
}
|
|
235
|
+
if (helper) {
|
|
236
|
+
return p({
|
|
237
|
+
class: 'label'
|
|
238
|
+
}, helper);
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Wrapper with DaisyUI fieldset class
|
|
246
|
+
const wrapperEl = fieldset({
|
|
247
|
+
class: `fieldset ${className}`.trim()
|
|
248
|
+
}, ...fieldsetContent);
|
|
249
|
+
|
|
250
|
+
// Check if we should use shadow DOM
|
|
251
|
+
let usesShadow = false;
|
|
252
|
+
if (LVX.shouldUseShadow) {
|
|
253
|
+
usesShadow = LVX.shouldUseShadow(useShadow);
|
|
254
|
+
} else {
|
|
255
|
+
usesShadow = useShadow === true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (usesShadow) {
|
|
259
|
+
const adoptedStyleSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets() : [];
|
|
260
|
+
|
|
261
|
+
// Get current theme from document
|
|
262
|
+
const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
|
|
263
|
+
|
|
264
|
+
return div({ class: 'content', style: 'display: inline-block' },
|
|
265
|
+
shadowDOM({ mode: 'open', adoptedStyleSheets },
|
|
266
|
+
div({ 'data-theme': themeValue },
|
|
267
|
+
wrapperEl
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return wrapperEl;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// Auto-register
|
|
277
|
+
window.Lightview.tags.Select = Select;
|
|
278
|
+
|
|
279
|
+
// Register as Custom Element
|
|
280
|
+
if (window.LightviewX?.createCustomElement) {
|
|
281
|
+
const SelectElement = window.LightviewX.createCustomElement(Select);
|
|
282
|
+
if (!customElements.get('lv-select')) {
|
|
283
|
+
customElements.define('lv-select', SelectElement);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export default Select;
|