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,739 @@
|
|
|
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
|
+
|
|
10
|
+
|
|
11
|
+
<div class="section">
|
|
12
|
+
<div class="section-content" style="max-width: 1200px;">
|
|
13
|
+
<h1>Getting Started</h1>
|
|
14
|
+
|
|
15
|
+
<!-- Step Navigation -->
|
|
16
|
+
<div class="tutorial-steps" style="display: flex; gap: 0.5rem; margin-bottom: 2rem; flex-wrap: wrap;">
|
|
17
|
+
<button class="btn btn-primary step-btn active" onclick="switchStep(1)">1. Basics</button>
|
|
18
|
+
<button class="btn btn-secondary step-btn" onclick="switchStep(2)">2. Signals</button>
|
|
19
|
+
<button class="btn btn-secondary step-btn" onclick="switchStep(3)">3. State</button>
|
|
20
|
+
<button class="btn btn-secondary step-btn" onclick="switchStep(4)">4. Hypermedia</button>
|
|
21
|
+
<button class="btn btn-secondary step-btn" onclick="switchStep(5)">5. Components</button>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- Tutorial Layout: Preview on top, Code & Concepts below -->
|
|
25
|
+
<div class="tutorial-wrapper"
|
|
26
|
+
style="border: 1px solid var(--site-border); border-radius: var(--site-radius-lg); overflow: hidden; background: var(--site-surface);">
|
|
27
|
+
|
|
28
|
+
<!-- Preview Area -->
|
|
29
|
+
<div id="tutorial-preview"
|
|
30
|
+
style="min-height: 350px; background: var(--site-bg); border-bottom: 1px solid var(--site-border);">
|
|
31
|
+
<!-- Iframes injected here -->
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Split: Code | Resizer | Concepts -->
|
|
35
|
+
<div id="tutorial-split" style="display: flex; min-height: 400px;">
|
|
36
|
+
|
|
37
|
+
<!-- Code Column -->
|
|
38
|
+
<div id="code-column" class="tutorial-column"
|
|
39
|
+
style="flex: 1; min-width: 200px; display: flex; flex-direction: column; border-right: none;">
|
|
40
|
+
<div
|
|
41
|
+
style="padding: 0.5rem 1rem; background: var(--site-bg-alt); border-bottom: 1px solid var(--site-border);">
|
|
42
|
+
<span
|
|
43
|
+
style="font-weight: 600; color: var(--site-text-secondary); font-size: 0.875rem;">CODE</span>
|
|
44
|
+
</div>
|
|
45
|
+
<!-- Code blocks injected here -->
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<!-- Resizer Handle -->
|
|
49
|
+
<div id="tutorial-resizer"
|
|
50
|
+
style="width: 6px; background: var(--site-border); cursor: col-resize; flex-shrink: 0; transition: background 0.2s;">
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<!-- Concepts Column -->
|
|
54
|
+
<div id="concepts-column" class="tutorial-column"
|
|
55
|
+
style="flex: 1; min-width: 200px; display: flex; flex-direction: column;">
|
|
56
|
+
<div
|
|
57
|
+
style="padding: 0.5rem 1rem; background: var(--site-bg-alt); border-bottom: 1px solid var(--site-border);">
|
|
58
|
+
<span
|
|
59
|
+
style="font-weight: 600; color: var(--site-text-secondary); font-size: 0.875rem;">CONCEPTS</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div id="tutorial-concepts" style="padding: 1.5rem; overflow-y: auto; flex: 1;">
|
|
62
|
+
<!-- Concept blocks injected here -->
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<style>
|
|
72
|
+
.step-btn.active {
|
|
73
|
+
background: var(--site-primary) !important;
|
|
74
|
+
color: white !important;
|
|
75
|
+
border-color: var(--site-primary) !important;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#tutorial-resizer:hover {
|
|
79
|
+
background: var(--site-primary);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#tutorial-resizer.dragging {
|
|
83
|
+
background: var(--site-primary);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Step content visibility */
|
|
87
|
+
.step-content {
|
|
88
|
+
display: none;
|
|
89
|
+
height: 100%;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.step-content.active {
|
|
93
|
+
display: block;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Code column specific layout for active step */
|
|
97
|
+
#code-column .step-content.active {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
flex: 1;
|
|
101
|
+
overflow: auto;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Ensure pre tags fill space */
|
|
105
|
+
#code-column pre {
|
|
106
|
+
flex: 1;
|
|
107
|
+
margin: 0;
|
|
108
|
+
outline: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@media (max-width: 900px) {
|
|
112
|
+
#tutorial-split {
|
|
113
|
+
flex-direction: column !important;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#tutorial-resizer {
|
|
117
|
+
display: none;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#code-column,
|
|
121
|
+
#concepts-column {
|
|
122
|
+
min-width: 100% !important;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
</style>
|
|
126
|
+
|
|
127
|
+
<script>
|
|
128
|
+
(function () {
|
|
129
|
+
const previewContainer = document.getElementById('tutorial-preview');
|
|
130
|
+
const codeContainer = document.getElementById('code-column');
|
|
131
|
+
const conceptsContainer = document.getElementById('tutorial-concepts');
|
|
132
|
+
const stepBtns = document.querySelectorAll('.step-btn');
|
|
133
|
+
|
|
134
|
+
// Tutorial data for each step
|
|
135
|
+
const tutorialData = {
|
|
136
|
+
1: {
|
|
137
|
+
options: {
|
|
138
|
+
autoRun: true,
|
|
139
|
+
},
|
|
140
|
+
code: `// STEP 1: BASICS - Pure UI with Tagged Functions
|
|
141
|
+
const { tags, $ } = Lightview;
|
|
142
|
+
const { div, h1, p, style } = tags;
|
|
143
|
+
|
|
144
|
+
// 1. Build UI using Tagged Functions
|
|
145
|
+
// These functions return standard DOM elements
|
|
146
|
+
const App = div({ class: 'hero' },
|
|
147
|
+
h1('Welcome to Lightview'),
|
|
148
|
+
p('Lightview is a tiny library for building modern web interfaces.')
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// 2. The $ Function for Selections & Content
|
|
152
|
+
// Inject the App into the #app container
|
|
153
|
+
$('#app').content(App);
|
|
154
|
+
|
|
155
|
+
// 3. Injecting Styles
|
|
156
|
+
// Use $ with a location to inject styles at the end of the app
|
|
157
|
+
$('#app').content(style({}, \`
|
|
158
|
+
.hero {
|
|
159
|
+
padding: 2rem;
|
|
160
|
+
background: #f8fafc;
|
|
161
|
+
border-radius: 12px;
|
|
162
|
+
border: 1px solid #e2e8f0;
|
|
163
|
+
font-family: system-ui, sans-serif;
|
|
164
|
+
}
|
|
165
|
+
h1 { color: #1e293b; margin: 0 0 1rem; font-size: 2rem; }
|
|
166
|
+
p { color: #64748b; font-size: 1.1rem; line-height: 1.5; }
|
|
167
|
+
\`), 'beforeend');`,
|
|
168
|
+
concepts: `
|
|
169
|
+
<h3 style="margin-top: 0; color: var(--site-primary);">Step 1: Basic Content Creation</h3>
|
|
170
|
+
<p>Welcome! Let's start with the basics of building UI without reactivity or components.</p>
|
|
171
|
+
|
|
172
|
+
<h4>Key Concepts:</h4>
|
|
173
|
+
<ul style="padding-left: 1.25rem; color: var(--site-text-secondary);">
|
|
174
|
+
<li style="margin-bottom: 0.75rem;"><code>tags</code> — Every HTML tag is available as a function. <code>div(...)</code> returns a real <code>HTMLDivElement</code>.</li>
|
|
175
|
+
<li style="margin-bottom: 0.75rem;"><code>$(selector)</code> — A powerful utility for selecting elements and manipulating them.</li>
|
|
176
|
+
<li style="margin-bottom: 0.75rem;"><code>.content(node, location)</code> — Replaces or appends content. Locations like <code>'beforeend'</code> allow injecting styles or scripts dynamicly.</li>
|
|
177
|
+
</ul>
|
|
178
|
+
<p>This approach results in standard, lightweight DOM elements with no overhead.</p>
|
|
179
|
+
`
|
|
180
|
+
},
|
|
181
|
+
2: {
|
|
182
|
+
options: {
|
|
183
|
+
autoRun: true,
|
|
184
|
+
},
|
|
185
|
+
code: `// STEP 2: SIGNALS - The foundation of reactivity
|
|
186
|
+
const { signal, tags, $ } = Lightview;
|
|
187
|
+
const { div, h3, p, button, img, style } = tags;
|
|
188
|
+
|
|
189
|
+
// Create a reactive signal for the button state
|
|
190
|
+
const added = signal(false);
|
|
191
|
+
|
|
192
|
+
// Build the UI using Lightview's tag functions
|
|
193
|
+
const App = div({ class: 'product-card' },
|
|
194
|
+
img({
|
|
195
|
+
src: 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=500&auto=format&fit=crop',
|
|
196
|
+
class: 'product-image'
|
|
197
|
+
}),
|
|
198
|
+
h3({ class: 'product-title' }, 'Red Nike Sneakers'),
|
|
199
|
+
|
|
200
|
+
// Reactive button - text updates when signal changes
|
|
201
|
+
div({ class: 'product-footer' },
|
|
202
|
+
button({
|
|
203
|
+
class: 'add-btn',
|
|
204
|
+
onclick: () => added.value = !added.value // Toggle state
|
|
205
|
+
}, () => added.value ? '✓ Added' : 'Add to Cart')
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
$('#app').content(App);
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------
|
|
212
|
+
// STYLES
|
|
213
|
+
// ---------------------------------------------------------------
|
|
214
|
+
$('#app').content(style({},
|
|
215
|
+
\`.product-card { width: 280px; font-family: system-ui, sans-serif; }
|
|
216
|
+
.product-image { width: 100%; border-radius: 8px; margin-bottom: 1rem; }
|
|
217
|
+
.product-title { margin: 0 0 0.25rem; font-size: 1.1rem; }
|
|
218
|
+
.product-footer { display: flex; justify-content: flex-end; }
|
|
219
|
+
.add-btn { background: #3b82f6; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: 500; }
|
|
220
|
+
.add-btn:hover { background: #2563eb; }\`), 'beforeend');`,
|
|
221
|
+
concepts: `
|
|
222
|
+
<h3 style="margin-top: 0; color: var(--site-primary);">Step 2: Reactive Signals</h3>
|
|
223
|
+
<p>In this step, we use <strong>Signals</strong> to handle data that changes over time.</p>
|
|
224
|
+
|
|
225
|
+
<h4>Key Concepts:</h4>
|
|
226
|
+
<ul style="padding-left: 1.25rem; color: var(--site-text-secondary);">
|
|
227
|
+
<li style="margin-bottom: 0.75rem;"><code>signal(value)</code> — Creates a reactive container. Changing <code>added.value</code> automatically updates the UI.</li>
|
|
228
|
+
<li style="margin-bottom: 0.75rem;"><code>tags</code> — Build HTML elements with pure JavaScript functions like <code>div()</code>, <code>button()</code>.</li>
|
|
229
|
+
<li style="margin-bottom: 0.75rem;"><code>Inside Tags</code> — Pass a function as a child (e.g., <code>() => added.value ? ...</code>) to create a reactive text node.</li>
|
|
230
|
+
</ul>
|
|
231
|
+
<p><strong>Try it:</strong> Click the "Add to Cart" button to see it toggle!</p>
|
|
232
|
+
`
|
|
233
|
+
},
|
|
234
|
+
3: {
|
|
235
|
+
options: {
|
|
236
|
+
allowSameOrigin: true,
|
|
237
|
+
autoRun: true,
|
|
238
|
+
},
|
|
239
|
+
code: `// STEP 3: STATE + VDOM SYNTAX
|
|
240
|
+
const { tags, $ } = Lightview;
|
|
241
|
+
const { div, style } = tags;
|
|
242
|
+
const { state } = LightviewX;
|
|
243
|
+
|
|
244
|
+
// 1. Deep Reactivity with Optional Persistence
|
|
245
|
+
const session = state({
|
|
246
|
+
cart: [
|
|
247
|
+
{ id: 3, name: 'Green Reeboks', price: 60 }
|
|
248
|
+
],
|
|
249
|
+
items: [
|
|
250
|
+
{ id: 1, name: 'Red Nike Sneakers', price: 99 },
|
|
251
|
+
{ id: 2, name: 'Blue Adidas', price: 85 },
|
|
252
|
+
{ id: 3, name: 'Green Reeboks', price: 60 }
|
|
253
|
+
]
|
|
254
|
+
}, { name: 'shopping-session', storage: sessionStorage });
|
|
255
|
+
|
|
256
|
+
const addToCart = (item) => session.cart.push(item);
|
|
257
|
+
const clearCart = () => {
|
|
258
|
+
session.cart.length = 0; // Clear reactive array
|
|
259
|
+
sessionStorage.removeItem('shopping-session'); // Delete session variable
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// 2. vDOM Syntax: { tag, attributes, children }
|
|
263
|
+
const App = div({ class: 'shop-container' }, [
|
|
264
|
+
// Cart Badge
|
|
265
|
+
{ tag: 'div', attributes: { class: 'cart-badge' }, children: [
|
|
266
|
+
{ tag: 'span', attributes: {}, children: [
|
|
267
|
+
() => '🛒 Cart: ' + session.cart.length + ' items'
|
|
268
|
+
]},
|
|
269
|
+
{ tag: 'button', attributes: {
|
|
270
|
+
class: 'clear-btn',
|
|
271
|
+
onclick: clearCart
|
|
272
|
+
}, children: ['Clear Cart'] }
|
|
273
|
+
]},
|
|
274
|
+
|
|
275
|
+
// Product List
|
|
276
|
+
{ tag: 'div', attributes: { class: 'product-list' }, children:
|
|
277
|
+
session.items.map(item => ({
|
|
278
|
+
tag: 'div', attributes: { class: 'product-row' }, children: [
|
|
279
|
+
{ tag: 'span', children: [item.name + ' ($' + item.price + ')'] },
|
|
280
|
+
{ tag: 'button', attributes: {
|
|
281
|
+
class: 'add-btn',
|
|
282
|
+
onclick: () => addToCart(item)
|
|
283
|
+
}, children: ['Add'] }
|
|
284
|
+
]
|
|
285
|
+
}))
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// Cart Contents
|
|
289
|
+
{ tag: 'h3', attributes: { class: 'cart-title' }, children: ['Cart:'] },
|
|
290
|
+
{ tag: 'ul', attributes: { class: 'cart-list' }, children: [
|
|
291
|
+
() => session.cart.length === 0
|
|
292
|
+
? { tag: 'li', attributes: { class: 'empty' }, children: ['Empty'] }
|
|
293
|
+
: session.cart.map((item, i) => ({
|
|
294
|
+
tag: 'li', attributes: {}, children: [
|
|
295
|
+
item.name + ' ',
|
|
296
|
+
{ tag: 'button', attributes: {
|
|
297
|
+
class: 'remove-btn',
|
|
298
|
+
onclick: () => session.cart.splice(i, 1)
|
|
299
|
+
}, children: ['×'] }
|
|
300
|
+
]
|
|
301
|
+
}))
|
|
302
|
+
]},
|
|
303
|
+
|
|
304
|
+
// Styles
|
|
305
|
+
{ tag: 'style', attributes: {}, children: [
|
|
306
|
+
\`.shop-container { width: 320px; font-family: system-ui, sans-serif; }
|
|
307
|
+
.cart-badge { padding: 0.75rem; background: #f3f4f6; border-radius: 8px; margin-bottom: 1rem; display: flex; justify-content: space-between; align-items: center; }
|
|
308
|
+
.clear-btn { font-size: 0.7rem; padding: 4px 8px; background: #94a3b8; color: white; border: none; border-radius: 4px; cursor: pointer; }
|
|
309
|
+
.product-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
310
|
+
.product-row { display: flex; justify-content: space-between; padding: 0.75rem; border: 1px solid #e5e7eb; border-radius: 6px; }
|
|
311
|
+
.add-btn { background: #3b82f6; color: white; border: none; padding: 0.25rem 0.75rem; border-radius: 4px; cursor: pointer; }
|
|
312
|
+
.cart-title { font-size: 1rem; margin-top: 1.5rem; }
|
|
313
|
+
.cart-list { padding-left: 1.25rem; margin: 0.5rem 0; }
|
|
314
|
+
.cart-list li { margin-bottom: 0.25rem; }
|
|
315
|
+
.cart-list .empty { color: #9ca3af; }
|
|
316
|
+
.remove-btn { font-size: 0.875rem; color: #ef4444; border: none; background: none; cursor: pointer; margin-left: 0.5rem; }\`
|
|
317
|
+
]}
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
$('#app').content(App);`,
|
|
321
|
+
concepts: `
|
|
322
|
+
<h3 style="margin-top: 0; color: var(--site-primary);">Step 3: State & vDOM Syntax</h3>
|
|
323
|
+
<p>This step introduces <strong>State</strong> and the <strong>vDOM</strong> object syntax.</p>
|
|
324
|
+
|
|
325
|
+
<h4>Key Concepts:</h4>
|
|
326
|
+
<ul style="padding-left: 1.25rem; color: var(--site-text-secondary);">
|
|
327
|
+
<li style="margin-bottom: 0.75rem;"><strong>vDOM Syntax</strong> — Elements are plain objects: <code>{ tag: 'div', attributes: {}, children: [] }</code>. This structure is excellent for programmatic generation, serialization (JSON), or building custom components.</li>
|
|
328
|
+
<li style="margin-bottom: 0.75rem;"><strong>Deep Reactivity</strong> — Use <code>LightviewX.state()</code> for complex objects and arrays. Arrays methods like <code>.push()</code> and <code>.splice()</code> trigger updates automatically.</li>
|
|
329
|
+
<li style="margin-bottom: 0.75rem;"><strong>State Persistence</strong> — Pass <code>{ name, storage }</code> to <code>state()</code> to automatically persist data to <code>sessionStorage</code> or <code>localStorage</code>.</li>
|
|
330
|
+
<li style="margin-bottom: 0.75rem;"><strong>Interoperability</strong> — You can pass valid vDOM objects as children to standard <code>tags</code> functions directly.</li>
|
|
331
|
+
</ul>
|
|
332
|
+
<p><strong>Try it:</strong> Add items to the cart and re-run the code to see persistence!</p>
|
|
333
|
+
`
|
|
334
|
+
},
|
|
335
|
+
4: {
|
|
336
|
+
options: {
|
|
337
|
+
allowSameOrigin: true,
|
|
338
|
+
autoRun: true
|
|
339
|
+
},
|
|
340
|
+
code: `// STEP 4: HYPERMEDIA + OBJECT DOM SYNTAX (requires lightview-x)
|
|
341
|
+
// Choose a format to load: HTML, VDOM, or Object DOM
|
|
342
|
+
|
|
343
|
+
// Object DOM syntax integration is handled by lightview-x
|
|
344
|
+
const { signal, element, $ } = Lightview;
|
|
345
|
+
|
|
346
|
+
// Track which format was loaded and the review count
|
|
347
|
+
const currentFormat = signal('none', 'currentFormat');
|
|
348
|
+
const reviewCount = signal(0, 'reviewCount');
|
|
349
|
+
|
|
350
|
+
// Helper to get source code signal
|
|
351
|
+
const sourceCode = signal('Select a format...', 'sourceCode');
|
|
352
|
+
|
|
353
|
+
const loadFormat = async (format, file) => {
|
|
354
|
+
currentFormat.value = format;
|
|
355
|
+
reviewCount.value = 3;
|
|
356
|
+
// Fetch source code for display
|
|
357
|
+
try {
|
|
358
|
+
const text = await fetch(file).then(r => r.text());
|
|
359
|
+
sourceCode.value = text;
|
|
360
|
+
} catch (e) {
|
|
361
|
+
sourceCode.value = 'Error loading source';
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// Build UI using Object DOM syntax - compact JSON format
|
|
366
|
+
const App = element('div', { class: 'reviews-container' }, [
|
|
367
|
+
// Header
|
|
368
|
+
{
|
|
369
|
+
div: {
|
|
370
|
+
class: 'reviews-header', children: [
|
|
371
|
+
{ h3: { class: 'reviews-title', children: ['Customer Reviews'] } },
|
|
372
|
+
{
|
|
373
|
+
p: {
|
|
374
|
+
class: 'reviews-subtitle', children: [
|
|
375
|
+
'Choose a format to load reviews from external files:'
|
|
376
|
+
]
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
// Format selector buttons
|
|
381
|
+
{
|
|
382
|
+
div: {
|
|
383
|
+
class: 'format-buttons', children: [
|
|
384
|
+
{
|
|
385
|
+
button: {
|
|
386
|
+
class: 'format-btn html',
|
|
387
|
+
href: './reviews.html',
|
|
388
|
+
target: '#reviews-box',
|
|
389
|
+
onclick: () => loadFormat('HTML', './reviews.html'),
|
|
390
|
+
children: ['📄 HTML']
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
button: {
|
|
395
|
+
class: 'format-btn vdom',
|
|
396
|
+
href: './reviews.vdom',
|
|
397
|
+
target: '#reviews-box',
|
|
398
|
+
onclick: () => loadFormat('VDOM', './reviews.vdom'),
|
|
399
|
+
children: ['🔷 VDOM']
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
button: {
|
|
404
|
+
class: 'format-btn odom',
|
|
405
|
+
href: './reviews.odom',
|
|
406
|
+
target: '#reviews-box',
|
|
407
|
+
onclick: () => loadFormat('ODOM', './reviews.odom'),
|
|
408
|
+
children: ['🔶 Object DOM']
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
]
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
// Split layout: Rendered UI | Source Code
|
|
419
|
+
{
|
|
420
|
+
div: {
|
|
421
|
+
class: 'split-view', children: [
|
|
422
|
+
// Left: Rendered Content
|
|
423
|
+
{
|
|
424
|
+
div: {
|
|
425
|
+
class: 'render-pane', children: [
|
|
426
|
+
{ p: { class: 'pane-label', children: ['Rendered Output:'] } },
|
|
427
|
+
{ div: { id: 'reviews-box', src: '' } },
|
|
428
|
+
{
|
|
429
|
+
p: {
|
|
430
|
+
class: 'status', children: [
|
|
431
|
+
() => currentFormat.value === 'none'
|
|
432
|
+
? 'Click a button above to load...'
|
|
433
|
+
: 'Loaded ' + reviewCount.value + ' reviews from ' + currentFormat.value
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
]
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
// Right: Source Code
|
|
442
|
+
{
|
|
443
|
+
div: {
|
|
444
|
+
class: 'source-pane', children: [
|
|
445
|
+
{ p: { class: 'pane-label', children: ['Source Code:'] } },
|
|
446
|
+
{
|
|
447
|
+
pre: {
|
|
448
|
+
class: 'source-code', children: [
|
|
449
|
+
() => sourceCode.value
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
]
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
]
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
// Styles
|
|
461
|
+
{ style: { children: [
|
|
462
|
+
\`.reviews-container { width: 100%; box-sizing: border-box; font-family: system-ui, sans-serif; }
|
|
463
|
+
.reviews-header { margin-bottom: 1rem; }
|
|
464
|
+
.reviews-title { margin: 0 0 0.5rem; }
|
|
465
|
+
.reviews-subtitle { color: #6b7280; margin: 0 0 0.75rem; font-size: 0.9em; }
|
|
466
|
+
.format-buttons { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
|
|
467
|
+
.format-btn { flex: 1; padding: 0.5rem; cursor: pointer; border: none; border-radius: 6px; font-weight: 500; font-size: 0.9em; }
|
|
468
|
+
.format-btn.html { background: #dbeafe; color: #1e40af; }
|
|
469
|
+
.format-btn.vdom { background: #ede9fe; color: #6d28d9; }
|
|
470
|
+
.format-btn.odom { background: #ffedd5; color: #c2410c; }
|
|
471
|
+
.split-view { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; width: 100%; box-sizing: border-box; }
|
|
472
|
+
.render-pane, .source-pane { border: 1px solid #e5e7eb; border-radius: 8px; padding: 1rem; background: #fff; min-width: 0; }
|
|
473
|
+
.pane-label { font-size: 0.75rem; font-weight: bold; color: #9ca3af; text-transform: uppercase; margin: 0 0 0.5rem; }
|
|
474
|
+
.source-code { margin: 0; font-family: monospace; font-size: 0.75rem; background: #f8f9fa; padding: 0.5rem; border-radius: 4px; overflow-x: auto; white-space: pre-wrap; color: #374151; height: 90%; overflow-y: auto; }
|
|
475
|
+
.status { color: #6366f1; font-weight: 500; margin: 1rem 0 0; font-size: 0.9em; }\`
|
|
476
|
+
]}}
|
|
477
|
+
]);
|
|
478
|
+
|
|
479
|
+
$('#app').content(App);`,
|
|
480
|
+
concepts: `
|
|
481
|
+
<h3 style="margin-top: 0; color: var(--site-primary);">Step 4: Hypermedia + Multiple Formats</h3>
|
|
482
|
+
<p>Lightview's <strong>hypermedia</strong> feature allows your application to load partial content dynamically from various file formats.</p>
|
|
483
|
+
|
|
484
|
+
<h4>Key Concepts:</h4>
|
|
485
|
+
<ul style="padding-left: 1.25rem; color: var(--site-text-secondary);">
|
|
486
|
+
<li style="margin-bottom: 0.75rem;"><code>href</code> on any element — Clicking it will fetch content and place it into the target.</li>
|
|
487
|
+
<li style="margin-bottom: 0.75rem;"><code>src</code> — Defines the source URL to fetch content from.</li>
|
|
488
|
+
<li style="margin-bottom: 0.75rem;"><strong>Template Literals</strong> — Loaded files (HTML, VDOM, ODOM) can contain <code>\${...}</code> syntax. These are evaluated reactively and can access named <code>signals</code> or <code>state</code> using <code>signal.get('name').value</code>. Note the code <code>Displaying ${signal.get('reviewCount').value} reviews in ${signal.get('currentFormat').value} format.</code> in the examples.</li>
|
|
489
|
+
</ul>
|
|
490
|
+
|
|
491
|
+
<hr style="margin: 1.5rem 0; border: none; border-top: 1px solid var(--site-border);">
|
|
492
|
+
|
|
493
|
+
<h4>Format Comparison:</h4>
|
|
494
|
+
<div style="font-size: 0.875rem;">
|
|
495
|
+
<div style="margin-bottom: 1rem;">
|
|
496
|
+
<strong style="color: #1e40af;">📄 HTML</strong>
|
|
497
|
+
<ul style="margin: 0.25rem 0 0; padding-left: 1.25rem; color: var(--site-text-secondary);">
|
|
498
|
+
<li><strong>Pros:</strong> Standard, easy to read, works with existing backend templates.</li>
|
|
499
|
+
<li><strong>Cons:</strong> Verbose, no type safety, harder to manipulate programmatically.</li>
|
|
500
|
+
</ul>
|
|
501
|
+
</div>
|
|
502
|
+
|
|
503
|
+
<div style="margin-bottom: 1rem;">
|
|
504
|
+
<strong style="color: #6d28d9;">🔷 VDOM JSON</strong>
|
|
505
|
+
<ul style="margin: 0.25rem 0 0; padding-left: 1.25rem; color: var(--site-text-secondary);">
|
|
506
|
+
<li><strong>Pros:</strong> Explicit structure, easy to generate from servers, lightweight.</li>
|
|
507
|
+
<li><strong>Cons:</strong> Verbose JSON structure (repeated "tag", "attributes" keys).</li>
|
|
508
|
+
</ul>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
<div>
|
|
512
|
+
<strong style="color: #c2410c;">🔶 Object DOM JSON</strong>
|
|
513
|
+
<ul style="margin: 0.25rem 0 0; padding-left: 1.25rem; color: var(--site-text-secondary);">
|
|
514
|
+
<li><strong>Pros:</strong> Extremely compact, human-readable JSON, expressive.</li>
|
|
515
|
+
<li><strong>Cons:</strong> Non-standard structure, requires understanding the syntax mapping.</li>
|
|
516
|
+
</ul>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
`
|
|
520
|
+
},
|
|
521
|
+
5: {
|
|
522
|
+
options: {
|
|
523
|
+
autoRun: true
|
|
524
|
+
},
|
|
525
|
+
code: `// STEP 5: COMPONENTS, COMPOSITION & COMPUTED
|
|
526
|
+
// Import the real components from the component library
|
|
527
|
+
import Button from '../../components/actions/button.js';
|
|
528
|
+
import Badge from '../../components/data-display/badge.js';
|
|
529
|
+
import '../../components/data-display/card.js';
|
|
530
|
+
|
|
531
|
+
const { tags, signal, computed, $ } = Lightview;
|
|
532
|
+
const { div, h3, span, Card, style, img } = tags;
|
|
533
|
+
|
|
534
|
+
// Logic for the product card
|
|
535
|
+
const price = 99;
|
|
536
|
+
const quantity = signal(1); // Reactive signal
|
|
537
|
+
const total = computed(() => price * quantity.value); // Computed derived value
|
|
538
|
+
|
|
539
|
+
const styles = \`
|
|
540
|
+
.product-card { width: 500px; font-family: system-ui, sans-serif; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); background: white; border-radius: 1rem; overflow: hidden; }
|
|
541
|
+
.product-body { padding: 1.25rem; }
|
|
542
|
+
.product-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1rem; }
|
|
543
|
+
.card-title { margin: 0; font-size: 1.1rem; font-weight: bold; }
|
|
544
|
+
.product-controls { display: flex; justify-content: space-between; align-items: center; padding-top: 1rem; border-top: 1px solid #f3f4f6; }
|
|
545
|
+
.total-price { font-size: 1.25rem; font-weight: 800; color: #111; }
|
|
546
|
+
.quantity-controls { display: flex; align-items: center; gap: 0.5rem; }
|
|
547
|
+
.quantity-value { width: 1.5rem; text-align: center; font-weight: 500; }
|
|
548
|
+
\`;
|
|
549
|
+
|
|
550
|
+
// 5. Styles for Shadow DOM
|
|
551
|
+
// Registering a named stylesheet allows it to be used inside isolated component styles
|
|
552
|
+
LightviewX.registerStyleSheet('product-styles', styles);
|
|
553
|
+
|
|
554
|
+
const App = Card({
|
|
555
|
+
class: 'product-card',
|
|
556
|
+
styleSheets: ['product-styles'] // Passes the registered sheet into the shadow root
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
img({
|
|
560
|
+
src: 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=500&auto=format&fit=crop',
|
|
561
|
+
style: 'object-fit: cover;',
|
|
562
|
+
alt: 'Red Nike Sneakers'
|
|
563
|
+
}),
|
|
564
|
+
|
|
565
|
+
Card.Body({ class: 'product-body' },
|
|
566
|
+
div({ class: 'product-header' },
|
|
567
|
+
h3({ class: 'card-title' }, 'Red Nike Sneakers'),
|
|
568
|
+
Badge({ color: 'success', variant: 'outline' }, 'In Stock')
|
|
569
|
+
),
|
|
570
|
+
|
|
571
|
+
// Quantity & Price Controls
|
|
572
|
+
div({ class: 'product-controls' },
|
|
573
|
+
span({ class: 'total-price' }, () => '$' + total.value),
|
|
574
|
+
div({ class: 'quantity-controls' },
|
|
575
|
+
Button({ size: 'sm', class: 'btn-circle', onclick: () => quantity.value = Math.max(1, quantity.value - 1) }, '-'),
|
|
576
|
+
span({ class: 'quantity-value' }, () => quantity.value),
|
|
577
|
+
Button({ size: 'sm', class: 'btn-circle', onclick: () => quantity.value++ }, '+')
|
|
578
|
+
)
|
|
579
|
+
)
|
|
580
|
+
)
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
$('#app').content(App);`,
|
|
584
|
+
concepts: `<h3 style="margin-top: 0; color: var(--site-primary);">Step 5: Components & Isolated Styling</h3>
|
|
585
|
+
<p>Use the <strong>Lightview Component Library</strong> for polished UI elements that use Shadow DOM for isolation.</p>
|
|
586
|
+
|
|
587
|
+
<h4>Key Concepts:</h4>
|
|
588
|
+
<ul style="padding-left: 1.25rem; color: var(--site-text-secondary);">
|
|
589
|
+
<li style="margin-bottom: 0.75rem;"><strong>Components</strong> — Reusable blocks like <code>Card</code>, <code>Button</code>, and <code>Badge</code>.</li>
|
|
590
|
+
<li style="margin-bottom: 0.75rem;"><strong>Auto-Registration</strong> — Components auto-register with <code>tags</code> for easy destructuring.</li>
|
|
591
|
+
<li style="margin-bottom: 0.75rem;"><strong>Shadow DOM Isolation</strong> — Components isolate their styles (including DaisyUI/Tailwind) to avoid global CSS pollution.</li>
|
|
592
|
+
<li style="margin-bottom: 0.75rem;"><strong>Named StyleSheets</strong> — Use <code>LightviewX.registerStyleSheet(name, css)</code> to define styles that can be safely passed into a component's <code>styleSheets</code> property.</li>
|
|
593
|
+
<li style="margin-bottom: 0.75rem;"><strong>Computed</strong> — <code>computed(() => ...)</code> creates derived values that reactively update based on other signals.</li>
|
|
594
|
+
</ul>
|
|
595
|
+
<p><strong>Next:</strong> Explore the <a href="/docs/components">Components</a> library!</p>`
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
// Initialize Steps
|
|
602
|
+
Object.keys(tutorialData).forEach(step => {
|
|
603
|
+
const data = tutorialData[step];
|
|
604
|
+
|
|
605
|
+
// 1. Create Preview Container
|
|
606
|
+
const previewEl = document.createElement('div');
|
|
607
|
+
previewEl.id = `preview-${step}`;
|
|
608
|
+
previewEl.className = 'step-content';
|
|
609
|
+
previewContainer.appendChild(previewEl);
|
|
610
|
+
|
|
611
|
+
// 2. Create Code Container
|
|
612
|
+
const codeWrapper = document.createElement('div');
|
|
613
|
+
codeWrapper.id = `code-container-${step}`;
|
|
614
|
+
codeWrapper.className = 'step-content';
|
|
615
|
+
|
|
616
|
+
const preEl = document.createElement('pre');
|
|
617
|
+
preEl.style.flex = '1';
|
|
618
|
+
preEl.style.margin = '0';
|
|
619
|
+
preEl.style.overflow = 'auto';
|
|
620
|
+
|
|
621
|
+
const codeEl = document.createElement('code');
|
|
622
|
+
codeEl.id = `code-${step}`;
|
|
623
|
+
codeEl.contentEditable = "true";
|
|
624
|
+
codeEl.style.display = 'block';
|
|
625
|
+
codeEl.style.padding = '1rem';
|
|
626
|
+
codeEl.style.minHeight = '100%';
|
|
627
|
+
codeEl.style.outline = 'none';
|
|
628
|
+
codeEl.textContent = data.code;
|
|
629
|
+
|
|
630
|
+
preEl.appendChild(codeEl);
|
|
631
|
+
codeWrapper.appendChild(preEl);
|
|
632
|
+
codeContainer.appendChild(codeWrapper);
|
|
633
|
+
|
|
634
|
+
// 3. Create Concepts Container
|
|
635
|
+
const conceptsEl = document.createElement('div');
|
|
636
|
+
conceptsEl.id = `concepts-${step}`;
|
|
637
|
+
conceptsEl.className = 'step-content';
|
|
638
|
+
conceptsEl.innerHTML = data.concepts;
|
|
639
|
+
conceptsContainer.appendChild(conceptsEl);
|
|
640
|
+
|
|
641
|
+
// 4. Initialize Examplify
|
|
642
|
+
// Check if step uses modules (looking for import statement)
|
|
643
|
+
const isModule = data.code.includes('import ');
|
|
644
|
+
|
|
645
|
+
const exResult = examplify(codeEl, {
|
|
646
|
+
scripts: ['../../../lightview.js', '../../../lightview-x.js'],
|
|
647
|
+
html: '<div id="app" style="padding: 1rem;"></div>',
|
|
648
|
+
location: 'afterEnd',
|
|
649
|
+
type: isModule ? 'module' : 'text/javascript',
|
|
650
|
+
...data.options || {}
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
// Move iframe to preview container
|
|
656
|
+
if (exResult && exResult.iframe) {
|
|
657
|
+
previewEl.appendChild(exResult.iframe);
|
|
658
|
+
exResult.iframe.style.width = '100%';
|
|
659
|
+
exResult.iframe.style.border = 'none';
|
|
660
|
+
exResult.iframe.style.height = '100%';
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// Step switching logic
|
|
665
|
+
window.switchStep = function (step) {
|
|
666
|
+
// Update buttons
|
|
667
|
+
stepBtns.forEach((btn, i) => {
|
|
668
|
+
const btnStep = i + 1;
|
|
669
|
+
if (btnStep === step) {
|
|
670
|
+
btn.classList.add('active', 'btn-primary');
|
|
671
|
+
btn.classList.remove('btn-secondary');
|
|
672
|
+
} else {
|
|
673
|
+
btn.classList.remove('active', 'btn-primary');
|
|
674
|
+
btn.classList.add('btn-secondary');
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Toggle Content
|
|
679
|
+
['preview', 'code-container', 'concepts'].forEach(prefix => {
|
|
680
|
+
document.querySelectorAll(`[id ^= "${prefix}-"]`).forEach(el => {
|
|
681
|
+
if (el.id === `${prefix}-${step}`) {
|
|
682
|
+
el.classList.add('active');
|
|
683
|
+
} else {
|
|
684
|
+
el.classList.remove('active');
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// Trigger onLoad for the step if it exists (e.g. fetching files)
|
|
690
|
+
const data = tutorialData[step];
|
|
691
|
+
if (data && data.onLoad) data.onLoad();
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// ---- Resizable columns ----
|
|
697
|
+
const resizer = document.getElementById('tutorial-resizer');
|
|
698
|
+
const splitContainer = document.getElementById('tutorial-split');
|
|
699
|
+
let isResizing = false;
|
|
700
|
+
|
|
701
|
+
resizer.addEventListener('mousedown', (e) => {
|
|
702
|
+
isResizing = true;
|
|
703
|
+
resizer.classList.add('dragging');
|
|
704
|
+
document.body.style.cursor = 'col-resize';
|
|
705
|
+
document.body.style.userSelect = 'none';
|
|
706
|
+
e.preventDefault();
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
document.addEventListener('mousemove', (e) => {
|
|
710
|
+
if (!isResizing) return;
|
|
711
|
+
|
|
712
|
+
const containerRect = splitContainer.getBoundingClientRect();
|
|
713
|
+
// const containerWidth = containerRect.width - 6;
|
|
714
|
+
const offsetX = e.clientX - containerRect.left;
|
|
715
|
+
|
|
716
|
+
// Calculate percentages with min/max constraints
|
|
717
|
+
let codePercent = (offsetX / containerRect.width) * 100;
|
|
718
|
+
codePercent = Math.max(20, Math.min(80, codePercent));
|
|
719
|
+
|
|
720
|
+
codeContainer.style.flex = 'none';
|
|
721
|
+
codeContainer.style.width = codePercent + '%';
|
|
722
|
+
conceptsContainer.style.flex = 'none';
|
|
723
|
+
conceptsContainer.style.width = (100 - codePercent - 1) + '%';
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
document.addEventListener('mouseup', () => {
|
|
727
|
+
if (isResizing) {
|
|
728
|
+
isResizing = false;
|
|
729
|
+
resizer.classList.remove('dragging');
|
|
730
|
+
document.body.style.cursor = '';
|
|
731
|
+
document.body.style.userSelect = '';
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// Initial load
|
|
736
|
+
switchStep(1);
|
|
737
|
+
|
|
738
|
+
})();
|
|
739
|
+
</script>
|