lightview 1.8.1-b → 2.0.0
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/.agent/workflows/daisyui-component-migration.md +155 -0
- 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 +1331 -21
- 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 +612 -0
- 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 +487 -0
- 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 +134 -0
- 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 +658 -1109
- package/lightview.js.backup +793 -0
- 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/components/chart/chart.html +0 -17
- package/components/chart/example.html +0 -32
- package/components/chart.html +0 -83
- package/components/components.js +0 -113
- package/components/gantt/example.html +0 -22
- package/components/gantt/gantt.html +0 -42
- package/components/gauge/example.html +0 -28
- package/components/gauge/gauge.html +0 -20
- package/components/gauge.html +0 -60
- package/components/orgchart/example.html +0 -25
- package/components/orgchart/orgchart.html +0 -41
- package/components/repl/code-editor.html +0 -64
- package/components/repl/editor.html +0 -37
- package/components/repl/editorjs-inline-tool/index.js +0 -3
- package/components/repl/editorjs-inline-tool/inline-tools.js +0 -28
- package/components/repl/editorjs-inline-tool/tool.js +0 -175
- package/components/repl/repl-with-wysiwyg.html +0 -355
- package/components/repl/repl.html +0 -345
- package/components/repl/sup.js +0 -44
- package/components/repl/wysiwyg-repl.html +0 -258
- package/components/timeline/example.html +0 -33
- package/components/timeline/timeline.html +0 -44
- package/components/timeline.html +0 -81
- package/examples/anchor.html +0 -11
- package/examples/chart.html +0 -34
- package/examples/counter.html +0 -26
- package/examples/counter.test.mjs +0 -47
- package/examples/counter2.html +0 -26
- package/examples/directives.html +0 -79
- package/examples/foreign.html +0 -50
- package/examples/forgeinform.html +0 -98
- package/examples/form.html +0 -61
- package/examples/gauge.html +0 -18
- package/examples/invalid-template-literals.html +0 -44
- package/examples/medium/remote.html +0 -60
- package/examples/message.html +0 -18
- package/examples/nested.html +0 -11
- package/examples/object-bound-form.html +0 -34
- package/examples/remote-server.js +0 -51
- package/examples/remote.html +0 -34
- package/examples/remote.json +0 -1
- package/examples/scratch.html +0 -69
- package/examples/sensors/index.html +0 -30
- package/examples/sensors/sensor-server.js +0 -30
- package/examples/shared.html +0 -41
- package/examples/template.html +0 -33
- package/examples/timeline.html +0 -21
- package/examples/todo.html +0 -38
- package/examples/top.html +0 -10
- package/examples/types.html +0 -94
- package/examples/xor.html +0 -62
- package/jest-puppeteer.config.js +0 -5
- package/jest.config.json +0 -12
- package/sites/client.html +0 -48
- package/sites/index.html +0 -247
- package/test/basic.html +0 -93
- package/test/basic.test.mjs +0 -315
- package/test/extended.html +0 -29
- package/test/extended.test.mjs +0 -448
- package/types.js +0 -534
- package/unsplash.key +0 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<!-- SEO-friendly SPA Shim -->
|
|
2
|
+
<script src="/lightview-router.js"></script>
|
|
3
|
+
<script>
|
|
4
|
+
if (window.LightviewRouter) {
|
|
5
|
+
LightviewRouter.base('/index.html');
|
|
6
|
+
}
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<div class="docs-layout">
|
|
10
|
+
<aside class="docs-sidebar" src="./nav.html"></aside>
|
|
11
|
+
|
|
12
|
+
<main class="docs-content">
|
|
13
|
+
<h1>Computed</h1>
|
|
14
|
+
<p>
|
|
15
|
+
Computed values are derived from signals. They update automatically when their dependencies change.
|
|
16
|
+
Think of them as formulas that always stay in sync.
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<h2>Basic Usage</h2>
|
|
20
|
+
<pre><code>const { signal, computed } = Lightview;
|
|
21
|
+
|
|
22
|
+
const count = signal(10);
|
|
23
|
+
const doubled = computed(() => count.value * 2);
|
|
24
|
+
|
|
25
|
+
console.log(doubled.value); // 20
|
|
26
|
+
|
|
27
|
+
count.value = 5;
|
|
28
|
+
console.log(doubled.value); // 10 (automatically updated!)</code></pre>
|
|
29
|
+
|
|
30
|
+
<h2>Chaining Computed Values</h2>
|
|
31
|
+
<p>Computed values can depend on other computed values:</p>
|
|
32
|
+
<pre><code>const price = signal(100);
|
|
33
|
+
const quantity = signal(2);
|
|
34
|
+
const taxRate = signal(0.1);
|
|
35
|
+
|
|
36
|
+
const subtotal = computed(() => price.value * quantity.value);
|
|
37
|
+
const tax = computed(() => subtotal.value * taxRate.value);
|
|
38
|
+
const total = computed(() => subtotal.value + tax.value);
|
|
39
|
+
|
|
40
|
+
console.log(total.value); // 220</code></pre>
|
|
41
|
+
|
|
42
|
+
<h2>Reading Computed Values</h2>
|
|
43
|
+
<pre><code>// Same as signals - two ways to read
|
|
44
|
+
console.log(doubled.value); // Property access
|
|
45
|
+
console.log(doubled()); // Function call</code></pre>
|
|
46
|
+
|
|
47
|
+
<h2>In the UI</h2>
|
|
48
|
+
<p>
|
|
49
|
+
Computed values work seamlessly in your UI, just like signals:
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<div class="code-example">
|
|
53
|
+
<div class="code-example-preview" id="computed-demo"></div>
|
|
54
|
+
<div class="code-example-code">
|
|
55
|
+
<pre><code>const price = signal(100);
|
|
56
|
+
const quantity = signal(1);
|
|
57
|
+
const total = computed(() => price.value * quantity.value);
|
|
58
|
+
|
|
59
|
+
div(
|
|
60
|
+
p(() => `Price: $${price.value}`),
|
|
61
|
+
p(() => `Quantity: ${quantity.value}`),
|
|
62
|
+
p(() => `Total: $${total.value}`),
|
|
63
|
+
button({ onclick: () => quantity.value++ }, 'Add One')
|
|
64
|
+
)</code></pre>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<h2>When to Use Computed</h2>
|
|
69
|
+
<ul>
|
|
70
|
+
<li><strong>Derived values</strong> — Calculations based on other state</li>
|
|
71
|
+
<li><strong>Formatting</strong> — Display formatting (dates, currency, etc.)</li>
|
|
72
|
+
<li><strong>Filtering/Sorting</strong> — Processed lists from raw data</li>
|
|
73
|
+
<li><strong>Validation</strong> — Form validity based on field values</li>
|
|
74
|
+
</ul>
|
|
75
|
+
|
|
76
|
+
<h3>Example: Filtered List</h3>
|
|
77
|
+
<pre><code>const todos = signal([
|
|
78
|
+
{ text: 'Learn Lightview', done: true },
|
|
79
|
+
{ text: 'Build something cool', done: false },
|
|
80
|
+
{ text: 'Ship it', done: false }
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const filter = signal('all'); // 'all', 'active', 'done'
|
|
84
|
+
|
|
85
|
+
const filteredTodos = computed(() => {
|
|
86
|
+
const list = todos.value;
|
|
87
|
+
switch (filter.value) {
|
|
88
|
+
case 'active': return list.filter(t => !t.done);
|
|
89
|
+
case 'done': return list.filter(t => t.done);
|
|
90
|
+
default: return list;
|
|
91
|
+
}
|
|
92
|
+
});</code></pre>
|
|
93
|
+
|
|
94
|
+
<h3>Example: Form Validation</h3>
|
|
95
|
+
<pre><code>const email = signal('');
|
|
96
|
+
const password = signal('');
|
|
97
|
+
|
|
98
|
+
const isEmailValid = computed(() =>
|
|
99
|
+
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const isPasswordValid = computed(() =>
|
|
103
|
+
password.value.length >= 8
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const canSubmit = computed(() =>
|
|
107
|
+
isEmailValid.value && isPasswordValid.value
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
button({ disabled: () => !canSubmit.value }, 'Submit')</code></pre>
|
|
111
|
+
|
|
112
|
+
<h2>Computed vs Effect</h2>
|
|
113
|
+
<p>
|
|
114
|
+
Both react to changes, but serve different purposes:
|
|
115
|
+
</p>
|
|
116
|
+
<table class="api-table">
|
|
117
|
+
<thead>
|
|
118
|
+
<tr>
|
|
119
|
+
<th>Computed</th>
|
|
120
|
+
<th>Effect</th>
|
|
121
|
+
</tr>
|
|
122
|
+
</thead>
|
|
123
|
+
<tbody>
|
|
124
|
+
<tr>
|
|
125
|
+
<td>Returns a value</td>
|
|
126
|
+
<td>Doesn't return anything useful</td>
|
|
127
|
+
</tr>
|
|
128
|
+
<tr>
|
|
129
|
+
<td>Pure (no side effects)</td>
|
|
130
|
+
<td>For side effects</td>
|
|
131
|
+
</tr>
|
|
132
|
+
<tr>
|
|
133
|
+
<td>Lazy (computed when read)</td>
|
|
134
|
+
<td>Eager (runs immediately)</td>
|
|
135
|
+
</tr>
|
|
136
|
+
<tr>
|
|
137
|
+
<td>Use in UI for derived data</td>
|
|
138
|
+
<td>Use for logging, storage, API calls</td>
|
|
139
|
+
</tr>
|
|
140
|
+
</tbody>
|
|
141
|
+
</table>
|
|
142
|
+
</main>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<script>
|
|
146
|
+
(function () {
|
|
147
|
+
const { signal, computed, tags } = Lightview;
|
|
148
|
+
const { div, p, button, span } = tags;
|
|
149
|
+
|
|
150
|
+
const price = signal(100);
|
|
151
|
+
const quantity = signal(1);
|
|
152
|
+
const total = computed(() => price.value * quantity.value);
|
|
153
|
+
|
|
154
|
+
const demo = div({ style: 'padding: 0.5rem;' },
|
|
155
|
+
div({ style: 'display: grid; gap: 0.5rem; margin-bottom: 1rem;' },
|
|
156
|
+
p({ style: 'margin: 0;' },
|
|
157
|
+
span({ style: 'color: var(--site-text-secondary);' }, 'Price: '),
|
|
158
|
+
span(() => `$${price.value}`)
|
|
159
|
+
),
|
|
160
|
+
p({ style: 'margin: 0;' },
|
|
161
|
+
span({ style: 'color: var(--site-text-secondary);' }, 'Quantity: '),
|
|
162
|
+
span(() => quantity.value)
|
|
163
|
+
),
|
|
164
|
+
p({ style: 'margin: 0; font-weight: 600; font-size: 1.125rem;' },
|
|
165
|
+
span({ style: 'color: var(--site-text-secondary);' }, 'Total: '),
|
|
166
|
+
span({ style: 'color: var(--site-primary);' }, () => `$${total.value}`)
|
|
167
|
+
)
|
|
168
|
+
),
|
|
169
|
+
div({ style: 'display: flex; gap: 0.5rem;' },
|
|
170
|
+
button({
|
|
171
|
+
onclick: () => quantity.value = Math.max(1, quantity.value - 1),
|
|
172
|
+
style: 'padding: 0.5rem 1rem; cursor: pointer; border: 1px solid var(--site-border); background: var(--site-surface); border-radius: 6px;'
|
|
173
|
+
}, '−'),
|
|
174
|
+
button({
|
|
175
|
+
onclick: () => quantity.value++,
|
|
176
|
+
style: 'padding: 0.5rem 1rem; cursor: pointer; border: 1px solid var(--site-border); background: var(--site-surface); border-radius: 6px;'
|
|
177
|
+
}, '+')
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const container = document.getElementById('computed-demo');
|
|
182
|
+
if (container) container.appendChild(demo.domEl);
|
|
183
|
+
})();
|
|
184
|
+
</script>
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<!-- SEO-friendly SPA Shim -->
|
|
2
|
+
<script src="/lightview-router.js"></script>
|
|
3
|
+
<script>
|
|
4
|
+
if (window.LightviewRouter) {
|
|
5
|
+
LightviewRouter.base('/index.html');
|
|
6
|
+
}
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<div class="docs-layout">
|
|
10
|
+
<aside class="docs-sidebar" src="./nav.html"></aside>
|
|
11
|
+
<main class="docs-content">
|
|
12
|
+
<h1>Effects</h1>
|
|
13
|
+
<p>
|
|
14
|
+
Effects let you run side effects when reactive state changes.
|
|
15
|
+
Logging, DOM manipulation, API calls—anything that needs to happen <em>because</em> something changed.
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<h2>Basic Usage</h2>
|
|
19
|
+
<pre><code>const { signal, effect } = Lightview;
|
|
20
|
+
|
|
21
|
+
const count = signal(0);
|
|
22
|
+
|
|
23
|
+
// This runs immediately, then re-runs when count changes
|
|
24
|
+
effect(() => {
|
|
25
|
+
console.log('Count is now:', count.value);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
count.value = 1; // Logs: "Count is now: 1"
|
|
29
|
+
count.value = 2; // Logs: "Count is now: 2"</code></pre>
|
|
30
|
+
|
|
31
|
+
<h2>Automatic Dependency Tracking</h2>
|
|
32
|
+
<p>
|
|
33
|
+
Effects automatically track which signals they read. No need to declare dependencies—Lightview figures it
|
|
34
|
+
out:
|
|
35
|
+
</p>
|
|
36
|
+
<pre><code>const firstName = signal('Alice');
|
|
37
|
+
const lastName = signal('Smith');
|
|
38
|
+
const showFull = signal(true);
|
|
39
|
+
|
|
40
|
+
effect(() => {
|
|
41
|
+
if (showFull.value) {
|
|
42
|
+
// Tracks firstName, lastName, and showFull
|
|
43
|
+
console.log(`${firstName.value} ${lastName.value}`);
|
|
44
|
+
} else {
|
|
45
|
+
// Only tracks firstName and showFull
|
|
46
|
+
console.log(firstName.value);
|
|
47
|
+
}
|
|
48
|
+
});</code></pre>
|
|
49
|
+
|
|
50
|
+
<h2>Stopping Effects</h2>
|
|
51
|
+
<p>
|
|
52
|
+
Effects return a stop function:
|
|
53
|
+
</p>
|
|
54
|
+
<pre><code>const count = signal(0);
|
|
55
|
+
|
|
56
|
+
const stop = effect(() => {
|
|
57
|
+
console.log('Count:', count.value);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
count.value = 1; // Logs
|
|
61
|
+
count.value = 2; // Logs
|
|
62
|
+
|
|
63
|
+
stop(); // Stop the effect
|
|
64
|
+
|
|
65
|
+
count.value = 3; // Nothing logged</code></pre>
|
|
66
|
+
|
|
67
|
+
<h2>Common Patterns</h2>
|
|
68
|
+
|
|
69
|
+
<h3>Syncing to External Systems</h3>
|
|
70
|
+
<pre><code>const theme = signal('light');
|
|
71
|
+
|
|
72
|
+
effect(() => {
|
|
73
|
+
document.body.setAttribute('data-theme', theme.value);
|
|
74
|
+
});</code></pre>
|
|
75
|
+
|
|
76
|
+
<h3>Local Storage Persistence</h3>
|
|
77
|
+
<pre><code>const settings = signal(
|
|
78
|
+
JSON.parse(localStorage.getItem('settings')) || {}
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
effect(() => {
|
|
82
|
+
localStorage.setItem('settings', JSON.stringify(settings.value));
|
|
83
|
+
});</code></pre>
|
|
84
|
+
|
|
85
|
+
<h3>API Calls</h3>
|
|
86
|
+
<pre><code>const userId = signal(1);
|
|
87
|
+
const userData = signal(null);
|
|
88
|
+
|
|
89
|
+
effect(async () => {
|
|
90
|
+
const id = userId.value;
|
|
91
|
+
const response = await fetch(`/api/users/${id}`);
|
|
92
|
+
userData.value = await response.json();
|
|
93
|
+
});</code></pre>
|
|
94
|
+
|
|
95
|
+
<h2>Effects vs Computed</h2>
|
|
96
|
+
<table class="api-table">
|
|
97
|
+
<thead>
|
|
98
|
+
<tr>
|
|
99
|
+
<th>Use Case</th>
|
|
100
|
+
<th>Use This</th>
|
|
101
|
+
</tr>
|
|
102
|
+
</thead>
|
|
103
|
+
<tbody>
|
|
104
|
+
<tr>
|
|
105
|
+
<td>Derive a value from signals</td>
|
|
106
|
+
<td><code>computed()</code></td>
|
|
107
|
+
</tr>
|
|
108
|
+
<tr>
|
|
109
|
+
<td>Side effects (logging, API calls, DOM)</td>
|
|
110
|
+
<td><code>effect()</code></td>
|
|
111
|
+
</tr>
|
|
112
|
+
<tr>
|
|
113
|
+
<td>Value needed in UI</td>
|
|
114
|
+
<td><code>computed()</code></td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td>Just need to "do something"</td>
|
|
118
|
+
<td><code>effect()</code></td>
|
|
119
|
+
</tr>
|
|
120
|
+
</tbody>
|
|
121
|
+
</table>
|
|
122
|
+
|
|
123
|
+
<div class="code-example">
|
|
124
|
+
<div class="code-example-preview" id="effect-demo"></div>
|
|
125
|
+
<div class="code-example-code">
|
|
126
|
+
<pre><code>const name = signal('World');
|
|
127
|
+
const log = signal([]);
|
|
128
|
+
|
|
129
|
+
effect(() => {
|
|
130
|
+
log.value = [...log.value, `Hello, ${name.value}!`].slice(-5);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Change name to see the effect in action</code></pre>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</main>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<script>
|
|
140
|
+
(function () {
|
|
141
|
+
const { signal, effect, tags } = Lightview;
|
|
142
|
+
const { div, p, input, ul, li, label } = tags;
|
|
143
|
+
|
|
144
|
+
const name = signal('World');
|
|
145
|
+
const log = signal([]);
|
|
146
|
+
|
|
147
|
+
effect(() => {
|
|
148
|
+
const entry = `${new Date().toLocaleTimeString()} - Hello, ${name.value}!`;
|
|
149
|
+
log.value = [...log.value, entry].slice(-5);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const demo = div({ style: 'padding: 0.5rem;' },
|
|
153
|
+
div({ style: 'margin-bottom: 1rem;' },
|
|
154
|
+
label({ style: 'display: block; margin-bottom: 0.25rem; font-size: 0.875rem; color: var(--site-text-secondary);' }, 'Change name to trigger effect:'),
|
|
155
|
+
input({
|
|
156
|
+
type: 'text',
|
|
157
|
+
value: name.value,
|
|
158
|
+
oninput: (e) => name.value = e.target.value,
|
|
159
|
+
style: 'padding: 0.5rem; border: 1px solid var(--site-border); border-radius: 4px; width: 200px;'
|
|
160
|
+
})
|
|
161
|
+
),
|
|
162
|
+
div(
|
|
163
|
+
p({ style: 'font-size: 0.875rem; color: var(--site-text-secondary); margin: 0 0 0.5rem;' }, 'Effect log (last 5):'),
|
|
164
|
+
ul({ style: 'margin: 0; padding-left: 1.25rem; font-family: var(--site-font-mono); font-size: 0.8125rem;' },
|
|
165
|
+
() => log.value.map(entry => li(entry))
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const container = document.getElementById('effect-demo');
|
|
171
|
+
if (container) container.appendChild(demo.domEl);
|
|
172
|
+
})();
|
|
173
|
+
</script>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<!-- SEO-friendly SPA Shim -->
|
|
2
|
+
<script src="/lightview-router.js"></script>
|
|
3
|
+
<script>
|
|
4
|
+
if (window.LightviewRouter) {
|
|
5
|
+
LightviewRouter.base('/index.html');
|
|
6
|
+
}
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<link rel="stylesheet" href="../components/index.css">
|
|
10
|
+
|
|
11
|
+
<div class="docs-layout">
|
|
12
|
+
<aside class="docs-sidebar" src="./nav.html"></aside>
|
|
13
|
+
|
|
14
|
+
<main class="docs-content">
|
|
15
|
+
<h1>Creating Elements</h1>
|
|
16
|
+
<p>
|
|
17
|
+
Lightview gives you three ways to build UI. Pick your favorite - or mix and match.
|
|
18
|
+
They all use the same reactive system under the hood.
|
|
19
|
+
</p>
|
|
20
|
+
<h2>Comparison</h2>
|
|
21
|
+
<table class="api-table">
|
|
22
|
+
<thead>
|
|
23
|
+
<tr>
|
|
24
|
+
<th>Style</th>
|
|
25
|
+
<th>Pros</th>
|
|
26
|
+
<th>Cons</th>
|
|
27
|
+
</tr>
|
|
28
|
+
</thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
<tr>
|
|
31
|
+
<td><strong>Tagged API</strong></td>
|
|
32
|
+
<td>Most concise, natural to write</td>
|
|
33
|
+
<td>Requires destructuring</td>
|
|
34
|
+
</tr>
|
|
35
|
+
<tr>
|
|
36
|
+
<td><strong>vDOM</strong></td>
|
|
37
|
+
<td>JSON serializable, easy to validate</td>
|
|
38
|
+
<td>Most verbose</td>
|
|
39
|
+
</tr>
|
|
40
|
+
<tr>
|
|
41
|
+
<td><strong>Object DOM</strong></td>
|
|
42
|
+
<td>Compact JSON, clean templates</td>
|
|
43
|
+
<td>Harder to validate than explicit vDOM</td>
|
|
44
|
+
</tr>
|
|
45
|
+
</tbody>
|
|
46
|
+
</table>
|
|
47
|
+
<!-- Tabs -->
|
|
48
|
+
<script>
|
|
49
|
+
window.switchSyntaxTab = (tabId) => {
|
|
50
|
+
const tabs = ['tagged', 'vdom', 'object'];
|
|
51
|
+
tabs.forEach(t => {
|
|
52
|
+
const tabEl = document.getElementById(`tab-btn-${t}`);
|
|
53
|
+
const contentEl = document.getElementById(`syntax-${t}`);
|
|
54
|
+
if (t === tabId) {
|
|
55
|
+
tabEl.classList.add('syntax-tab-active');
|
|
56
|
+
contentEl.style.display = 'block';
|
|
57
|
+
} else {
|
|
58
|
+
tabEl.classList.remove('syntax-tab-active');
|
|
59
|
+
contentEl.style.display = 'none';
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
</script>
|
|
64
|
+
<div role="tablist" class="syntax-tabs" style="margin-bottom: 1rem;">
|
|
65
|
+
<button id="tab-btn-tagged" role="tab" class="syntax-tab syntax-tab-active"
|
|
66
|
+
onclick="switchSyntaxTab('tagged')">Tagged API</button>
|
|
67
|
+
<button id="tab-btn-vdom" role="tab" class="syntax-tab" onclick="switchSyntaxTab('vdom')">vDOM</button>
|
|
68
|
+
<button id="tab-btn-object" role="tab" class="syntax-tab" onclick="switchSyntaxTab('object')">Object
|
|
69
|
+
DOM</button>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- Tagged Syntax -->
|
|
73
|
+
<div id="syntax-tagged">
|
|
74
|
+
<pre><script>
|
|
75
|
+
examplify(document.currentScript.nextElementSibling, {
|
|
76
|
+
at: document.currentScript.parentElement,
|
|
77
|
+
scripts: ['/lightview.js', '/lightview-x.js'],
|
|
78
|
+
type: 'module',
|
|
79
|
+
minHeight: 120
|
|
80
|
+
});
|
|
81
|
+
</script><code contenteditable="true">const { signal, tags, $ } = Lightview;
|
|
82
|
+
const { div, h1, p, button } = tags;
|
|
83
|
+
const count = signal(0);
|
|
84
|
+
const app = div({ class: 'container' },
|
|
85
|
+
h1('Hello Lightview'),
|
|
86
|
+
p(() => `Count: ${count.value}`),
|
|
87
|
+
button({ onclick: () => count.value++ }, 'Click me')
|
|
88
|
+
);
|
|
89
|
+
$('#example').content(app);</code></pre>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<!-- vDOM Syntax -->
|
|
93
|
+
<div id="syntax-vdom" style="display: none;">
|
|
94
|
+
<pre><script>
|
|
95
|
+
examplify(document.currentScript.nextElementSibling, {
|
|
96
|
+
at: document.currentScript.parentElement,
|
|
97
|
+
scripts: ['/lightview.js', '/lightview-x.js'],
|
|
98
|
+
type: 'module',
|
|
99
|
+
minHeight: 120
|
|
100
|
+
});
|
|
101
|
+
</script><code contenteditable="true">const { signal, element, $ } = Lightview;
|
|
102
|
+
const count = signal(0);
|
|
103
|
+
const app = { tag:'div', attributes: { class: 'container' }, children: [
|
|
104
|
+
{ tag: 'h1', attributes: {}, children: ['Hello Lightview'] },
|
|
105
|
+
{ tag: 'p', attributes: {}, children: [() => `Count: ${count.value}`] },
|
|
106
|
+
{ tag: 'button', attributes: { onclick: () => count.value++ }, children: ['Click me'] }
|
|
107
|
+
]};
|
|
108
|
+
$('#example').content(app);</code></pre>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- Object DOM Syntax -->
|
|
112
|
+
<div id="syntax-object" style="display: none;">
|
|
113
|
+
<pre><script>
|
|
114
|
+
examplify(document.currentScript.nextElementSibling, {
|
|
115
|
+
at: document.currentScript.parentElement,
|
|
116
|
+
scripts: ['/lightview.js', '/lightview-x.js'],
|
|
117
|
+
type: 'module',
|
|
118
|
+
minHeight: 120
|
|
119
|
+
});
|
|
120
|
+
</script><code contenteditable="true">const { signal, tags, $ } = Lightview;
|
|
121
|
+
const { div } = tags;
|
|
122
|
+
const count = signal(0);
|
|
123
|
+
const app = { div: { class: 'container', children: [
|
|
124
|
+
{ h1: { children: ['Hello Lightview'] } },
|
|
125
|
+
{ p: { children: [() => `Count: ${count.value}`] } },
|
|
126
|
+
{ button: { onclick: () => count.value++, children: ['Click me'] } }
|
|
127
|
+
]}};
|
|
128
|
+
$('#example').content(app);</code></pre>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<h2>Attributes & Events</h2>
|
|
132
|
+
<p>
|
|
133
|
+
Pass attributes as the first argument (Tagged API) or in the attributes object (others):
|
|
134
|
+
</p>
|
|
135
|
+
<pre><code>// Standard attributes
|
|
136
|
+
div({
|
|
137
|
+
id: 'my-div',
|
|
138
|
+
class: 'container active',
|
|
139
|
+
style: 'color: red;',
|
|
140
|
+
'data-value': '42'
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Reactive attributes - use functions!
|
|
144
|
+
div({
|
|
145
|
+
class: () => isActive.value ? 'active' : 'inactive',
|
|
146
|
+
style: () => `opacity: ${visible.value ? 1 : 0}`,
|
|
147
|
+
disabled: () => isLoading.value
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Event handlers - use "on" prefix
|
|
151
|
+
button({
|
|
152
|
+
onclick: (e) => handleClick(e),
|
|
153
|
+
onmouseenter: () => setHovered(true),
|
|
154
|
+
onmouseleave: () => setHovered(false)
|
|
155
|
+
})</code></pre>
|
|
156
|
+
|
|
157
|
+
<h2>Children</h2>
|
|
158
|
+
<p>
|
|
159
|
+
Children can be strings, numbers, elements, arrays, or functions:
|
|
160
|
+
</p>
|
|
161
|
+
<pre><code>div(
|
|
162
|
+
'Static text', // String
|
|
163
|
+
42, // Number (converted to string)
|
|
164
|
+
span('Nested element'), // Element
|
|
165
|
+
() => `Dynamic: ${value.value}`, // Reactive function
|
|
166
|
+
() => items.value.map(i => li(i.name)), // Reactive list
|
|
167
|
+
condition && span('Conditional') // Conditional (falsy = not rendered)
|
|
168
|
+
)</code></pre>
|
|
169
|
+
|
|
170
|
+
<h2>The domEl Property</h2>
|
|
171
|
+
<p>
|
|
172
|
+
Every Lightview element has a <code>domEl</code> property - the actual DOM node:
|
|
173
|
+
</p>
|
|
174
|
+
<pre><code>const myDiv = div({ class: 'box' }, 'Hello');
|
|
175
|
+
|
|
176
|
+
// Access the real DOM element
|
|
177
|
+
document.body.appendChild(myDiv.domEl);
|
|
178
|
+
|
|
179
|
+
// You can also manipulate it directly
|
|
180
|
+
myDiv.domEl.classList.add('another-class');</code></pre>
|