@junojs/core 0.1.2 → 0.1.4
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/dist/cli.cjs +18 -26
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +160 -181
- package/dist/cli.js.map +1 -1
- package/dist/core/bootstrap.d.ts.map +1 -1
- package/dist/core/bootstrap.test.d.ts +2 -0
- package/dist/core/bootstrap.test.d.ts.map +1 -0
- package/dist/core/dom.bench.d.ts +2 -0
- package/dist/core/dom.bench.d.ts.map +1 -0
- package/dist/core/dom.test.d.ts +2 -0
- package/dist/core/dom.test.d.ts.map +1 -0
- package/dist/core/reactivity.bench.d.ts +2 -0
- package/dist/core/reactivity.bench.d.ts.map +1 -0
- package/dist/core/reactivity.d.ts.map +1 -1
- package/dist/core/reactivity.test.d.ts +2 -0
- package/dist/core/reactivity.test.d.ts.map +1 -0
- package/dist/core/template-url.test.d.ts +2 -0
- package/dist/core/template-url.test.d.ts.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +810 -891
- package/dist/index.js.map +1 -1
- package/dist/templates/docs.html +2483 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/package.json +15 -7
|
@@ -0,0 +1,2483 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="bg-white text-gray-700 dark:bg-[#0a0a0a] dark:text-slate-300 font-sans min-h-screen"
|
|
3
|
+
>
|
|
4
|
+
<!-- Fixed Left Sidebar -->
|
|
5
|
+
<aside
|
|
6
|
+
class="fixed top-0 left-0 h-screen w-60 border-r border-gray-200 dark:border-slate-800/60 flex flex-col bg-white dark:bg-[#0a0a0a] overflow-y-auto"
|
|
7
|
+
style="z-index: 40"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
class="h-14 flex items-center px-5 border-b border-gray-200 dark:border-slate-800/60 flex-shrink-0"
|
|
11
|
+
>
|
|
12
|
+
<div class="flex items-center gap-2 flex-1">
|
|
13
|
+
<div
|
|
14
|
+
class="w-6 h-6 rounded bg-blue-500 flex items-center justify-center text-[9px] font-black text-white"
|
|
15
|
+
>
|
|
16
|
+
NF
|
|
17
|
+
</div>
|
|
18
|
+
<span class="font-semibold text-gray-900 dark:text-white text-sm"
|
|
19
|
+
>JunoJS</span
|
|
20
|
+
>
|
|
21
|
+
<span
|
|
22
|
+
class="text-[10px] font-bold text-blue-400 bg-blue-500/10 px-1.5 py-0.5 rounded border border-blue-500/20"
|
|
23
|
+
>v1.0</span
|
|
24
|
+
>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<nav class="flex-1 px-3 py-5 space-y-6">
|
|
28
|
+
<div class="space-y-0.5">
|
|
29
|
+
<p
|
|
30
|
+
class="px-3 py-1 text-[10.5px] font-semibold text-gray-400 dark:text-slate-500 uppercase tracking-widest"
|
|
31
|
+
>
|
|
32
|
+
Getting Started
|
|
33
|
+
</p>
|
|
34
|
+
<button
|
|
35
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'introduction' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
36
|
+
@click="selectTab('introduction')"
|
|
37
|
+
>
|
|
38
|
+
Introduction
|
|
39
|
+
</button>
|
|
40
|
+
<button
|
|
41
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'installation' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
42
|
+
@click="selectTab('installation')"
|
|
43
|
+
>
|
|
44
|
+
Installation
|
|
45
|
+
</button>
|
|
46
|
+
<button
|
|
47
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'structure' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
48
|
+
@click="selectTab('structure')"
|
|
49
|
+
>
|
|
50
|
+
Project Structure
|
|
51
|
+
</button>
|
|
52
|
+
<button
|
|
53
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'aidev' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
54
|
+
@click="selectTab('aidev')"
|
|
55
|
+
>
|
|
56
|
+
AI Development
|
|
57
|
+
</button>
|
|
58
|
+
<button
|
|
59
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'quickstart' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
60
|
+
@click="selectTab('quickstart')"
|
|
61
|
+
>
|
|
62
|
+
Quick Start
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="space-y-0.5">
|
|
66
|
+
<p
|
|
67
|
+
class="px-3 py-1 text-[10.5px] font-semibold text-gray-400 dark:text-slate-500 uppercase tracking-widest"
|
|
68
|
+
>
|
|
69
|
+
Core Concepts
|
|
70
|
+
</p>
|
|
71
|
+
<button
|
|
72
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'reactivity' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
73
|
+
@click="selectTab('reactivity')"
|
|
74
|
+
>
|
|
75
|
+
Reactivity
|
|
76
|
+
</button>
|
|
77
|
+
<button
|
|
78
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'templates' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
79
|
+
@click="selectTab('templates')"
|
|
80
|
+
>
|
|
81
|
+
Template Syntax
|
|
82
|
+
</button>
|
|
83
|
+
<button
|
|
84
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'lifecycle' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
85
|
+
@click="selectTab('lifecycle')"
|
|
86
|
+
>
|
|
87
|
+
Lifecycle Hooks
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="space-y-0.5">
|
|
91
|
+
<p
|
|
92
|
+
class="px-3 py-1 text-[10.5px] font-semibold text-gray-400 dark:text-slate-500 uppercase tracking-widest"
|
|
93
|
+
>
|
|
94
|
+
Common Services
|
|
95
|
+
</p>
|
|
96
|
+
<button
|
|
97
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'http' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
98
|
+
@click="selectTab('http')"
|
|
99
|
+
>
|
|
100
|
+
HTTP Client
|
|
101
|
+
</button>
|
|
102
|
+
<button
|
|
103
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'i18n' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
104
|
+
@click="selectTab('i18n')"
|
|
105
|
+
>
|
|
106
|
+
Internationalization
|
|
107
|
+
</button>
|
|
108
|
+
<button
|
|
109
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'forms' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
110
|
+
@click="selectTab('forms')"
|
|
111
|
+
>
|
|
112
|
+
Forms & Validation
|
|
113
|
+
</button>
|
|
114
|
+
<button
|
|
115
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'routing' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
116
|
+
@click="selectTab('routing')"
|
|
117
|
+
>
|
|
118
|
+
Router Service
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="space-y-0.5">
|
|
122
|
+
<p
|
|
123
|
+
class="px-3 py-1 text-[10.5px] font-semibold text-gray-400 dark:text-slate-500 uppercase tracking-widest"
|
|
124
|
+
>
|
|
125
|
+
API Reference
|
|
126
|
+
</p>
|
|
127
|
+
<button
|
|
128
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'decorators' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
129
|
+
@click="selectTab('decorators')"
|
|
130
|
+
>
|
|
131
|
+
Decorators
|
|
132
|
+
</button>
|
|
133
|
+
<button
|
|
134
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'engine' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
135
|
+
@click="selectTab('engine')"
|
|
136
|
+
>
|
|
137
|
+
Engine API
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="space-y-0.5">
|
|
141
|
+
<p
|
|
142
|
+
class="px-3 py-1 text-[10.5px] font-semibold text-gray-400 dark:text-slate-500 uppercase tracking-widest"
|
|
143
|
+
>
|
|
144
|
+
Resources
|
|
145
|
+
</p>
|
|
146
|
+
<button
|
|
147
|
+
class="w-full text-left px-3 py-[7px] rounded-md text-sm transition-colors {activeTab === 'samples' ? 'bg-gray-100 text-gray-900 font-medium dark:bg-slate-800/70 dark:text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/30'}"
|
|
148
|
+
@click="selectTab('samples')"
|
|
149
|
+
>
|
|
150
|
+
Samples
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</nav>
|
|
154
|
+
</aside>
|
|
155
|
+
|
|
156
|
+
<!-- Main wrapper offset by sidebar -->
|
|
157
|
+
<div class="ml-60">
|
|
158
|
+
<!-- Sticky top header -->
|
|
159
|
+
<header
|
|
160
|
+
class="sticky top-0 h-14 flex items-center justify-between px-8 border-b border-gray-100 dark:border-slate-800/40 bg-white/70 dark:bg-[#0a0a0a]/70 backdrop-blur-md"
|
|
161
|
+
style="z-index: 30"
|
|
162
|
+
>
|
|
163
|
+
<!-- Left: Breadcrumbs -->
|
|
164
|
+
<div class="flex items-center gap-2.5 text-[13px] tracking-tight">
|
|
165
|
+
<div class="flex items-center gap-2 group cursor-default">
|
|
166
|
+
<span
|
|
167
|
+
class="text-gray-400 dark:text-slate-500 group-hover:text-gray-600 dark:group-hover:text-slate-300 transition-colors"
|
|
168
|
+
>{activeSectionName}</span
|
|
169
|
+
>
|
|
170
|
+
<span class="text-gray-300 dark:text-slate-800 font-light select-none"
|
|
171
|
+
>/</span
|
|
172
|
+
>
|
|
173
|
+
<span class="text-gray-900 dark:text-white font-semibold"
|
|
174
|
+
>{activePageName}</span
|
|
175
|
+
>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<!-- Center: Search Mockup -->
|
|
180
|
+
<div class="hidden md:flex flex-1 max-w-md mx-12">
|
|
181
|
+
<div class="w-full relative group">
|
|
182
|
+
<span
|
|
183
|
+
class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-slate-600 text-lg group-focus-within:text-blue-500 transition-colors"
|
|
184
|
+
>search</span
|
|
185
|
+
>
|
|
186
|
+
<input
|
|
187
|
+
type="text"
|
|
188
|
+
placeholder="Search documentation..."
|
|
189
|
+
class="w-full h-9 pl-10 pr-4 bg-gray-100/50 dark:bg-slate-900/50 border border-gray-200/50 dark:border-slate-800/50 rounded-full text-sm text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-slate-600 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500/50 transition-all"
|
|
190
|
+
/>
|
|
191
|
+
<div
|
|
192
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1 opacity-40 group-focus-within:opacity-0 transition-opacity"
|
|
193
|
+
>
|
|
194
|
+
<kbd
|
|
195
|
+
class="px-1.5 py-0.5 rounded border border-gray-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-[10px] font-sans"
|
|
196
|
+
>⌘</kbd
|
|
197
|
+
>
|
|
198
|
+
<kbd
|
|
199
|
+
class="px-1.5 py-0.5 rounded border border-gray-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-[10px] font-sans"
|
|
200
|
+
>K</kbd
|
|
201
|
+
>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<!-- Right: Actions & Theme -->
|
|
207
|
+
<div class="flex items-center gap-1.5">
|
|
208
|
+
<a
|
|
209
|
+
href="https://github.com"
|
|
210
|
+
target="_blank"
|
|
211
|
+
class="p-2 text-gray-500 hover:text-gray-900 dark:text-slate-400 dark:hover:text-white transition-colors"
|
|
212
|
+
>
|
|
213
|
+
<svg viewBox="0 0 24 24" class="w-5 h-5 fill-current">
|
|
214
|
+
<path
|
|
215
|
+
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
|
|
216
|
+
/>
|
|
217
|
+
</svg>
|
|
218
|
+
</a>
|
|
219
|
+
<div class="w-px h-4 bg-gray-200 dark:bg-slate-800 mx-1"></div>
|
|
220
|
+
<button
|
|
221
|
+
@click="toggleTheme"
|
|
222
|
+
class="w-9 h-9 flex items-center justify-center rounded-lg text-gray-500 hover:bg-gray-100 dark:text-slate-400 dark:hover:bg-slate-800/60 transition-all"
|
|
223
|
+
>
|
|
224
|
+
<if condition="{darkMode === 'dark'}">
|
|
225
|
+
<span class="material-symbols-outlined text-[20px]"
|
|
226
|
+
>light_mode</span
|
|
227
|
+
>
|
|
228
|
+
<else>
|
|
229
|
+
<span class="material-symbols-outlined text-[20px]"
|
|
230
|
+
>dark_mode</span
|
|
231
|
+
>
|
|
232
|
+
</else>
|
|
233
|
+
</if>
|
|
234
|
+
</button>
|
|
235
|
+
</div>
|
|
236
|
+
</header>
|
|
237
|
+
|
|
238
|
+
<!-- Content + Right ToC -->
|
|
239
|
+
<div class="flex">
|
|
240
|
+
<!-- Page Content -->
|
|
241
|
+
<main class="flex-1 min-w-0">
|
|
242
|
+
<div class="max-w-2xl px-10 pt-14 pb-28 mx-auto">
|
|
243
|
+
<!-- ── INTRODUCTION ── -->
|
|
244
|
+
<if condition="{activeTab === 'introduction'}">
|
|
245
|
+
<article class="space-y-8">
|
|
246
|
+
<div>
|
|
247
|
+
<p
|
|
248
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
249
|
+
>
|
|
250
|
+
Getting Started
|
|
251
|
+
</p>
|
|
252
|
+
<h1
|
|
253
|
+
id="overview"
|
|
254
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
255
|
+
>
|
|
256
|
+
Introduction
|
|
257
|
+
</h1>
|
|
258
|
+
<p
|
|
259
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
260
|
+
>
|
|
261
|
+
JunoJS is an AI-native reactive framework built on native
|
|
262
|
+
Web Components. It delivers sub-millisecond DOM updates
|
|
263
|
+
through a Proxy-based reactivity system, without a Virtual DOM
|
|
264
|
+
or build-time compilation.
|
|
265
|
+
</p>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
268
|
+
<div>
|
|
269
|
+
<h2
|
|
270
|
+
id="features"
|
|
271
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
272
|
+
>
|
|
273
|
+
Key Features
|
|
274
|
+
</h2>
|
|
275
|
+
<ul class="space-y-3 text-sm text-slate-400 leading-7">
|
|
276
|
+
<li class="flex gap-3">
|
|
277
|
+
<span class="text-blue-500 mt-0.5">▸</span
|
|
278
|
+
><span
|
|
279
|
+
><strong class="text-gray-900 dark:text-slate-200"
|
|
280
|
+
>Web Components</strong
|
|
281
|
+
>
|
|
282
|
+
— Standards-based custom elements that work in any
|
|
283
|
+
environment, no framework lock-in.</span
|
|
284
|
+
>
|
|
285
|
+
</li>
|
|
286
|
+
<li class="flex gap-3">
|
|
287
|
+
<span class="text-blue-500 mt-0.5">▸</span
|
|
288
|
+
><span
|
|
289
|
+
><strong class="text-gray-900 dark:text-slate-200"
|
|
290
|
+
>Proxy Reactivity</strong
|
|
291
|
+
>
|
|
292
|
+
— Atomic state management. Mutate a property and the DOM
|
|
293
|
+
reconciles itself instantly.</span
|
|
294
|
+
>
|
|
295
|
+
</li>
|
|
296
|
+
<li class="flex gap-3">
|
|
297
|
+
<span class="text-blue-500 mt-0.5">▸</span
|
|
298
|
+
><span
|
|
299
|
+
><strong class="text-gray-900 dark:text-slate-200"
|
|
300
|
+
>HTML Templates</strong
|
|
301
|
+
>
|
|
302
|
+
— Structural logic via semantic tags:
|
|
303
|
+
<code
|
|
304
|
+
class="bg-slate-900 text-blue-400 px-1 rounded text-xs"
|
|
305
|
+
><if></code
|
|
306
|
+
>,
|
|
307
|
+
<code
|
|
308
|
+
class="bg-slate-900 text-blue-400 px-1 rounded text-xs"
|
|
309
|
+
><for></code
|
|
310
|
+
>,
|
|
311
|
+
<code
|
|
312
|
+
class="bg-slate-900 text-blue-400 px-1 rounded text-xs"
|
|
313
|
+
><else></code
|
|
314
|
+
>.</span
|
|
315
|
+
>
|
|
316
|
+
</li>
|
|
317
|
+
<li class="flex gap-3">
|
|
318
|
+
<span class="text-blue-500 mt-0.5">▸</span
|
|
319
|
+
><span
|
|
320
|
+
><strong class="text-gray-900 dark:text-slate-200"
|
|
321
|
+
>Common Services</strong
|
|
322
|
+
>
|
|
323
|
+
— Built-in HTTP client, i18n, and form validation —
|
|
324
|
+
batteries included.</span
|
|
325
|
+
>
|
|
326
|
+
</li>
|
|
327
|
+
<li class="flex gap-3">
|
|
328
|
+
<span class="text-blue-500 mt-0.5">▸</span
|
|
329
|
+
><span
|
|
330
|
+
><strong class="text-gray-900 dark:text-slate-200"
|
|
331
|
+
>TypeScript-first</strong
|
|
332
|
+
>
|
|
333
|
+
— Decorator-driven component authoring with full type
|
|
334
|
+
safety.</span
|
|
335
|
+
>
|
|
336
|
+
</li>
|
|
337
|
+
</ul>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
340
|
+
<div>
|
|
341
|
+
<h2
|
|
342
|
+
id="architecture"
|
|
343
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
344
|
+
>
|
|
345
|
+
Architecture
|
|
346
|
+
</h2>
|
|
347
|
+
<p class="text-sm text-gray-600 dark:text-slate-400 leading-7">
|
|
348
|
+
JunoJS consists of two layers. The
|
|
349
|
+
<strong class="text-gray-800 dark:text-slate-200"
|
|
350
|
+
>core engine</strong
|
|
351
|
+
>
|
|
352
|
+
handles reactivity, bootstrapping, and DOM reconciliation. The
|
|
353
|
+
<strong class="text-gray-800 dark:text-slate-200"
|
|
354
|
+
>common services</strong
|
|
355
|
+
>
|
|
356
|
+
layer provides HTTP, i18n, and form utilities. Components
|
|
357
|
+
bridge these layers via TypeScript decorators.
|
|
358
|
+
</p>
|
|
359
|
+
</div>
|
|
360
|
+
</article>
|
|
361
|
+
</if>
|
|
362
|
+
|
|
363
|
+
<!-- ── INSTALLATION ── -->
|
|
364
|
+
<if condition="{activeTab === 'installation'}">
|
|
365
|
+
<article class="space-y-8">
|
|
366
|
+
<div>
|
|
367
|
+
<p
|
|
368
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
369
|
+
>
|
|
370
|
+
Getting Started
|
|
371
|
+
</p>
|
|
372
|
+
<h1
|
|
373
|
+
id="install"
|
|
374
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
375
|
+
>
|
|
376
|
+
Installation
|
|
377
|
+
</h1>
|
|
378
|
+
<p
|
|
379
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
380
|
+
>
|
|
381
|
+
Install JunoJS via npm and configure TypeScript to support
|
|
382
|
+
the decorator metadata required by the framework.
|
|
383
|
+
</p>
|
|
384
|
+
</div>
|
|
385
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
386
|
+
<div>
|
|
387
|
+
<h2
|
|
388
|
+
id="npm"
|
|
389
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
390
|
+
>
|
|
391
|
+
Install the Package
|
|
392
|
+
</h2>
|
|
393
|
+
<pre
|
|
394
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
395
|
+
>
|
|
396
|
+
npm install @junojs/core reflect-metadata</pre
|
|
397
|
+
>
|
|
398
|
+
</div>
|
|
399
|
+
<div>
|
|
400
|
+
<h2
|
|
401
|
+
id="tsconfig"
|
|
402
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
403
|
+
>
|
|
404
|
+
TypeScript Configuration
|
|
405
|
+
</h2>
|
|
406
|
+
<p
|
|
407
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
408
|
+
>
|
|
409
|
+
Enable decorator metadata in your
|
|
410
|
+
<code
|
|
411
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs font-mono"
|
|
412
|
+
>tsconfig.json</code
|
|
413
|
+
>:
|
|
414
|
+
</p>
|
|
415
|
+
<pre
|
|
416
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
417
|
+
>
|
|
418
|
+
{
|
|
419
|
+
"compilerOptions": {
|
|
420
|
+
"experimentalDecorators": true,
|
|
421
|
+
"emitDecoratorMetadata": true,
|
|
422
|
+
"target": "ES2020",
|
|
423
|
+
"module": "ESNext"
|
|
424
|
+
}
|
|
425
|
+
}</pre
|
|
426
|
+
>
|
|
427
|
+
</div>
|
|
428
|
+
<div>
|
|
429
|
+
<h2
|
|
430
|
+
id="entry"
|
|
431
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
432
|
+
>
|
|
433
|
+
Entry Point
|
|
434
|
+
</h2>
|
|
435
|
+
<p
|
|
436
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
437
|
+
>
|
|
438
|
+
Import
|
|
439
|
+
<code
|
|
440
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs font-mono"
|
|
441
|
+
>reflect-metadata</code
|
|
442
|
+
>
|
|
443
|
+
once at the top of your application entry file — before any
|
|
444
|
+
decorators run.
|
|
445
|
+
</p>
|
|
446
|
+
<pre
|
|
447
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
448
|
+
>
|
|
449
|
+
import 'reflect-metadata';
|
|
450
|
+
import { bootstrap } from '@junojs/core';
|
|
451
|
+
import { AppRoot } from './app.component';
|
|
452
|
+
|
|
453
|
+
bootstrap(AppRoot);</pre
|
|
454
|
+
>
|
|
455
|
+
</div>
|
|
456
|
+
</article>
|
|
457
|
+
</if>
|
|
458
|
+
<!-- ── PROJECT STRUCTURE ── -->
|
|
459
|
+
<if condition="{activeTab === 'structure'}">
|
|
460
|
+
<article class="space-y-8">
|
|
461
|
+
<div>
|
|
462
|
+
<p
|
|
463
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
464
|
+
>
|
|
465
|
+
Getting Started
|
|
466
|
+
</p>
|
|
467
|
+
<h1
|
|
468
|
+
id="structure"
|
|
469
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
470
|
+
>
|
|
471
|
+
Project Structure
|
|
472
|
+
</h1>
|
|
473
|
+
<p
|
|
474
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
475
|
+
>
|
|
476
|
+
Master the architectural blueprint of JunoJS. Learn how to
|
|
477
|
+
organize your components, services, and pages to build
|
|
478
|
+
scalable, high-performance reactive applications.
|
|
479
|
+
</p>
|
|
480
|
+
</div>
|
|
481
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
482
|
+
<div>
|
|
483
|
+
<h2
|
|
484
|
+
id="app-structure"
|
|
485
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
486
|
+
>
|
|
487
|
+
Folder Structure
|
|
488
|
+
</h2>
|
|
489
|
+
<p class="text-sm text-gray-600 dark:text-slate-400 mb-4">
|
|
490
|
+
JunoJS follows a component-driven architecture where your
|
|
491
|
+
application is composed of modular elements organized into
|
|
492
|
+
pages and reusable components.
|
|
493
|
+
</p>
|
|
494
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60 mb-6"></div>
|
|
495
|
+
<div class="relative group">
|
|
496
|
+
<pre
|
|
497
|
+
class="bg-gray-50 dark:bg-slate-900/50 border border-gray-200 dark:border-slate-800 rounded-xl p-6 font-mono text-[13px] text-gray-700 dark:text-slate-300 overflow-x-auto leading-relaxed shadow-inner"
|
|
498
|
+
><span class="text-emerald-500 font-bold">my-app/</span>
|
|
499
|
+
├── <span class="text-emerald-400">src/</span>
|
|
500
|
+
│ ├── <span class="text-emerald-300">components/</span> <span class="text-gray-400"># Atomic UI components (Navbar, Card)</span>
|
|
501
|
+
│ │ └── footer/ <span class="text-gray-500">// Individual component folder</span>
|
|
502
|
+
│ │ ├── footer.ts
|
|
503
|
+
│ │ └── footer.css
|
|
504
|
+
│ ├── <span class="text-emerald-300">pages/</span> <span class="text-gray-400"># Routing-level view components (Home)</span>
|
|
505
|
+
│ │ ├── home/ <span class="text-gray-500">// Views linked to specific routes</span>
|
|
506
|
+
│ │ └── cart/
|
|
507
|
+
│ ├── <span class="text-emerald-300">services/</span> <span class="text-gray-400"># Singleton services (Store, API)</span>
|
|
508
|
+
│ ├── <span class="text-emerald-300">assets/</span> <span class="text-gray-400"># Images, global styles, and fonts</span>
|
|
509
|
+
│ ├── <span class="text-emerald-300 font-medium">main.ts</span> <span class="text-gray-500">// Application bootstrap & global config</span>
|
|
510
|
+
│ └── <span class="text-emerald-300 font-medium">app.component.ts</span> <span class="text-gray-500">// Root layout and shell</span>
|
|
511
|
+
├── <span class="text-emerald-400 font-medium">templates/</span> <span class="text-gray-400"># HTML templates for custom elements</span>
|
|
512
|
+
├── index.html <span class="text-gray-500">// Host document</span>
|
|
513
|
+
└── tsconfig.json <span class="text-gray-500">// Decorator & metadata configuration</span></pre>
|
|
514
|
+
|
|
515
|
+
<!-- Copy Button Mockup -->
|
|
516
|
+
<div
|
|
517
|
+
class="absolute top-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
518
|
+
>
|
|
519
|
+
<button
|
|
520
|
+
class="p-2 rounded-lg bg-white/80 dark:bg-slate-800/80 border border-gray-200 dark:border-slate-700 hover:border-blue-500/50 text-gray-500 dark:text-slate-400 transition-all active:scale-90 shadow-sm"
|
|
521
|
+
>
|
|
522
|
+
<span class="material-symbols-outlined text-[18px]"
|
|
523
|
+
>content_copy</span
|
|
524
|
+
>
|
|
525
|
+
</button>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
<div>
|
|
531
|
+
<h2
|
|
532
|
+
id="module-overview"
|
|
533
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
534
|
+
>
|
|
535
|
+
Best Practices
|
|
536
|
+
</h2>
|
|
537
|
+
<div class="space-y-4">
|
|
538
|
+
<div
|
|
539
|
+
class="flex gap-4 p-4 rounded-xl bg-gray-50/50 dark:bg-slate-800/30 border border-gray-200 dark:border-slate-800/60"
|
|
540
|
+
>
|
|
541
|
+
<div
|
|
542
|
+
class="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center text-blue-500"
|
|
543
|
+
>
|
|
544
|
+
<span class="material-symbols-outlined"
|
|
545
|
+
>settings_input_component</span
|
|
546
|
+
>
|
|
547
|
+
</div>
|
|
548
|
+
<div>
|
|
549
|
+
<h3
|
|
550
|
+
class="text-sm font-bold text-gray-950 dark:text-white uppercase tracking-tight"
|
|
551
|
+
>
|
|
552
|
+
Core Layer
|
|
553
|
+
</h3>
|
|
554
|
+
<p class="text-xs text-slate-500 mt-1 leading-relaxed">
|
|
555
|
+
Manages the custom element lifecycle, property-to-state
|
|
556
|
+
mapping via Proxies, and atomic DOM updates through the
|
|
557
|
+
reconciliation engine.
|
|
558
|
+
</p>
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
<div
|
|
562
|
+
class="flex gap-4 p-4 rounded-xl bg-gray-50/50 dark:bg-slate-800/30 border border-gray-200 dark:border-slate-800/60"
|
|
563
|
+
>
|
|
564
|
+
<div
|
|
565
|
+
class="w-10 h-10 rounded-lg bg-emerald-500/10 flex items-center justify-center text-emerald-500"
|
|
566
|
+
>
|
|
567
|
+
<span class="material-symbols-outlined">api</span>
|
|
568
|
+
</div>
|
|
569
|
+
<div>
|
|
570
|
+
<h3
|
|
571
|
+
class="text-sm font-bold text-gray-950 dark:text-white uppercase tracking-tight"
|
|
572
|
+
>
|
|
573
|
+
Common Services
|
|
574
|
+
</h3>
|
|
575
|
+
<p class="text-xs text-slate-500 mt-1 leading-relaxed">
|
|
576
|
+
Provides high-level utilities like the History Router,
|
|
577
|
+
I18n Engine, and HTTP client that integrate seamlessly
|
|
578
|
+
with the reactivity system.
|
|
579
|
+
</p>
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
</article>
|
|
585
|
+
</if>
|
|
586
|
+
|
|
587
|
+
<!-- ── AI DEVELOPMENT ── -->
|
|
588
|
+
<if condition="{activeTab === 'aidev'}">
|
|
589
|
+
<article class="space-y-10">
|
|
590
|
+
<div>
|
|
591
|
+
<p
|
|
592
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
593
|
+
>
|
|
594
|
+
Getting Started
|
|
595
|
+
</p>
|
|
596
|
+
<h1
|
|
597
|
+
id="ai-intro"
|
|
598
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
599
|
+
>
|
|
600
|
+
AI-First Development
|
|
601
|
+
</h1>
|
|
602
|
+
<p
|
|
603
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
604
|
+
>
|
|
605
|
+
JunoJS is designed to be highly compatible with AI coding
|
|
606
|
+
assistants. By providing structured rule files, you can ensure
|
|
607
|
+
your AI understands the core decorators, reactivity model, and
|
|
608
|
+
architectural patterns of the framework.
|
|
609
|
+
</p>
|
|
610
|
+
</div>
|
|
611
|
+
|
|
612
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
613
|
+
|
|
614
|
+
<!-- Tools Grid -->
|
|
615
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
616
|
+
<!-- Cursor -->
|
|
617
|
+
<div
|
|
618
|
+
class="p-6 rounded-2xl bg-[#fafafa] dark:bg-slate-900/50 border border-gray-200 dark:border-slate-800 hover:border-blue-500/50 transition-all group"
|
|
619
|
+
>
|
|
620
|
+
<div class="flex items-center gap-3 mb-4">
|
|
621
|
+
<div
|
|
622
|
+
class="w-10 h-10 rounded-xl bg-blue-500/10 flex items-center justify-center text-blue-500"
|
|
623
|
+
>
|
|
624
|
+
<span class="material-symbols-outlined">smart_toy</span>
|
|
625
|
+
</div>
|
|
626
|
+
<h3 class="font-bold text-gray-900 dark:text-white">
|
|
627
|
+
Cursor
|
|
628
|
+
</h3>
|
|
629
|
+
</div>
|
|
630
|
+
<p
|
|
631
|
+
class="text-xs text-gray-500 dark:text-slate-400 leading-relaxed mb-4"
|
|
632
|
+
>
|
|
633
|
+
Cursor uses
|
|
634
|
+
<code class="text-blue-500">.cursorrules</code> to provide
|
|
635
|
+
project-specific context and enforce architectural
|
|
636
|
+
standards.
|
|
637
|
+
</p>
|
|
638
|
+
<pre
|
|
639
|
+
class="bg-gray-100 dark:bg-slate-900 border border-gray-200 dark:border-slate-800 rounded-lg p-3 text-[11px] font-mono text-gray-600 dark:text-slate-400"
|
|
640
|
+
>
|
|
641
|
+
# Created in project root
|
|
642
|
+
.cursorrules</pre
|
|
643
|
+
>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<!-- Windsurf -->
|
|
647
|
+
<div
|
|
648
|
+
class="p-6 rounded-2xl bg-[#fafafa] dark:bg-slate-900/50 border border-gray-200 dark:border-slate-800 hover:border-emerald-500/50 transition-all group"
|
|
649
|
+
>
|
|
650
|
+
<div class="flex items-center gap-3 mb-4">
|
|
651
|
+
<div
|
|
652
|
+
class="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-500"
|
|
653
|
+
>
|
|
654
|
+
<span class="material-symbols-outlined">waves</span>
|
|
655
|
+
</div>
|
|
656
|
+
<h3 class="font-bold text-gray-900 dark:text-white">
|
|
657
|
+
Windsurf
|
|
658
|
+
</h3>
|
|
659
|
+
</div>
|
|
660
|
+
<p
|
|
661
|
+
class="text-xs text-gray-500 dark:text-slate-400 leading-relaxed mb-4"
|
|
662
|
+
>
|
|
663
|
+
Windsurf reads
|
|
664
|
+
<code class="text-emerald-500">.windsurfrules</code> to
|
|
665
|
+
align its agentic behavior with the JunoJS lifecycle.
|
|
666
|
+
</p>
|
|
667
|
+
<pre
|
|
668
|
+
class="bg-gray-100 dark:bg-slate-900 border border-gray-200 dark:border-slate-800 rounded-lg p-3 text-[11px] font-mono text-gray-600 dark:text-slate-400"
|
|
669
|
+
>
|
|
670
|
+
# Created in project root
|
|
671
|
+
.windsurfrules</pre
|
|
672
|
+
>
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
|
|
676
|
+
<!-- General AI Models -->
|
|
677
|
+
<div class="space-y-6">
|
|
678
|
+
<h2
|
|
679
|
+
id="custom-instructions"
|
|
680
|
+
class="text-xl font-bold text-gray-900 dark:text-white tracking-tight"
|
|
681
|
+
>
|
|
682
|
+
Large Language Models
|
|
683
|
+
</h2>
|
|
684
|
+
<p
|
|
685
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-relaxed"
|
|
686
|
+
>
|
|
687
|
+
For models like Claude (Anthropic) and Gemini (Google), you
|
|
688
|
+
can provide framework context via
|
|
689
|
+
<strong>Custom Instructions</strong> or by uploading a system
|
|
690
|
+
prompt.
|
|
691
|
+
</p>
|
|
692
|
+
|
|
693
|
+
<div
|
|
694
|
+
class="bg-gray-50 dark:bg-slate-900/30 border border-gray-200 dark:border-slate-800 rounded-2xl p-6 space-y-4"
|
|
695
|
+
>
|
|
696
|
+
<div class="flex items-start gap-4">
|
|
697
|
+
<div
|
|
698
|
+
class="p-2 rounded-lg bg-orange-500/10 text-orange-500"
|
|
699
|
+
>
|
|
700
|
+
<span class="material-symbols-outlined">description</span>
|
|
701
|
+
</div>
|
|
702
|
+
<div>
|
|
703
|
+
<h4
|
|
704
|
+
class="text-sm font-bold text-gray-900 dark:text-white"
|
|
705
|
+
>
|
|
706
|
+
Core Instructions
|
|
707
|
+
</h4>
|
|
708
|
+
<p
|
|
709
|
+
class="text-xs text-slate-500 mt-1 leading-relaxed italic"
|
|
710
|
+
>
|
|
711
|
+
"Always use @Component for Web Components. In
|
|
712
|
+
JunoJS, use @State() for reactivity. Do NOT use a
|
|
713
|
+
Virtual DOM—the engine reconciles the real DOM using a
|
|
714
|
+
proxy system."
|
|
715
|
+
</p>
|
|
716
|
+
</div>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
</article>
|
|
721
|
+
</if>
|
|
722
|
+
|
|
723
|
+
<!-- ── QUICK START ── -->
|
|
724
|
+
<if condition="{activeTab === 'quickstart'}">
|
|
725
|
+
<article class="space-y-8">
|
|
726
|
+
<div>
|
|
727
|
+
<p
|
|
728
|
+
class="text-xs font-semibold text-blue-600 dark:text-blue-500 uppercase tracking-widest mb-3"
|
|
729
|
+
>
|
|
730
|
+
Getting Started
|
|
731
|
+
</p>
|
|
732
|
+
<h1
|
|
733
|
+
id="quickstart"
|
|
734
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
735
|
+
>
|
|
736
|
+
Quick Start
|
|
737
|
+
</h1>
|
|
738
|
+
<p
|
|
739
|
+
class="text-base text-gray-600 dark:text-slate-400 leading-relaxed"
|
|
740
|
+
>
|
|
741
|
+
Build your first reactive component in under a minute. This
|
|
742
|
+
example demonstrates state, events, and template
|
|
743
|
+
interpolation.
|
|
744
|
+
</p>
|
|
745
|
+
</div>
|
|
746
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
747
|
+
<div>
|
|
748
|
+
<h2
|
|
749
|
+
id="component"
|
|
750
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
751
|
+
>
|
|
752
|
+
Create a Component
|
|
753
|
+
</h2>
|
|
754
|
+
<pre
|
|
755
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
756
|
+
>
|
|
757
|
+
import { Component, State, bootstrap } from '@junojs/core';
|
|
758
|
+
|
|
759
|
+
@Component({
|
|
760
|
+
tag: 'my-counter',
|
|
761
|
+
shadow: false,
|
|
762
|
+
template: `
|
|
763
|
+
<div>
|
|
764
|
+
<span>{count}</span>
|
|
765
|
+
<button @click="increment">+</button>
|
|
766
|
+
</div>
|
|
767
|
+
`
|
|
768
|
+
})
|
|
769
|
+
class MyCounter {
|
|
770
|
+
@State() count = 0;
|
|
771
|
+
increment() { this.count++; }
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
bootstrap(MyCounter);</pre
|
|
775
|
+
>
|
|
776
|
+
</div>
|
|
777
|
+
<div>
|
|
778
|
+
<h2
|
|
779
|
+
id="live"
|
|
780
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
781
|
+
>
|
|
782
|
+
Live Demo
|
|
783
|
+
</h2>
|
|
784
|
+
<div
|
|
785
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-xl p-8 flex items-center gap-6"
|
|
786
|
+
>
|
|
787
|
+
<span
|
|
788
|
+
class="text-5xl font-black text-gray-900 dark:text-white tabular-nums w-20 text-center"
|
|
789
|
+
>{counter}</span
|
|
790
|
+
>
|
|
791
|
+
<button
|
|
792
|
+
class="px-6 py-2.5 bg-blue-600 hover:bg-blue-500 text-white text-sm font-semibold rounded-lg transition-colors"
|
|
793
|
+
@click="increment"
|
|
794
|
+
>
|
|
795
|
+
Increment
|
|
796
|
+
</button>
|
|
797
|
+
</div>
|
|
798
|
+
<p class="text-xs text-gray-500 dark:text-slate-500 mt-3">
|
|
799
|
+
Counter value is held in a
|
|
800
|
+
<code class="text-blue-600 dark:text-blue-400">@State()</code>
|
|
801
|
+
property. Every mutation triggers DOM reconciliation
|
|
802
|
+
automatically.
|
|
803
|
+
</p>
|
|
804
|
+
</div>
|
|
805
|
+
</article>
|
|
806
|
+
</if>
|
|
807
|
+
|
|
808
|
+
<!-- ── REACTIVITY ── -->
|
|
809
|
+
<if condition="{activeTab === 'reactivity'}">
|
|
810
|
+
<article class="space-y-8">
|
|
811
|
+
<div>
|
|
812
|
+
<p
|
|
813
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
814
|
+
>
|
|
815
|
+
Core Concepts
|
|
816
|
+
</p>
|
|
817
|
+
<h1
|
|
818
|
+
id="reactivity"
|
|
819
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
820
|
+
>
|
|
821
|
+
Reactivity
|
|
822
|
+
</h1>
|
|
823
|
+
<p
|
|
824
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
825
|
+
>
|
|
826
|
+
JunoJS uses a Proxy-based reactivity model. There is no
|
|
827
|
+
Virtual DOM — mutations are detected directly on the
|
|
828
|
+
JavaScript object and the engine reconciles only what changed
|
|
829
|
+
in the real DOM.
|
|
830
|
+
</p>
|
|
831
|
+
</div>
|
|
832
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
833
|
+
<div>
|
|
834
|
+
<h2
|
|
835
|
+
id="state"
|
|
836
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
837
|
+
>
|
|
838
|
+
@State Decorator
|
|
839
|
+
</h2>
|
|
840
|
+
<p
|
|
841
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
842
|
+
>
|
|
843
|
+
Mark any class property with
|
|
844
|
+
<code class="bg-slate-900 text-blue-400 px-1 rounded text-xs"
|
|
845
|
+
>@State()</code
|
|
846
|
+
>
|
|
847
|
+
to make it reactive. The bootstrap system wraps the component
|
|
848
|
+
instance in a
|
|
849
|
+
<code class="bg-slate-900 text-blue-400 px-1 rounded text-xs"
|
|
850
|
+
>Proxy</code
|
|
851
|
+
>
|
|
852
|
+
that intercepts all property writes.
|
|
853
|
+
</p>
|
|
854
|
+
<pre
|
|
855
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
856
|
+
>
|
|
857
|
+
class MyComponent {
|
|
858
|
+
@State() username = '';
|
|
859
|
+
@State() count = 0;
|
|
860
|
+
@State() isLoading = false;
|
|
861
|
+
}</pre
|
|
862
|
+
>
|
|
863
|
+
</div>
|
|
864
|
+
<div>
|
|
865
|
+
<h2
|
|
866
|
+
id="how"
|
|
867
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
868
|
+
>
|
|
869
|
+
How It Works
|
|
870
|
+
</h2>
|
|
871
|
+
<p class="text-sm text-gray-600 dark:text-slate-400 leading-7">
|
|
872
|
+
When a
|
|
873
|
+
<code
|
|
874
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
875
|
+
>@State</code
|
|
876
|
+
>
|
|
877
|
+
property is written to, the Proxy trap fires the scheduler
|
|
878
|
+
which calls
|
|
879
|
+
<code
|
|
880
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
881
|
+
>update()</code
|
|
882
|
+
>. The
|
|
883
|
+
<code
|
|
884
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
885
|
+
>NeuralEngine</code
|
|
886
|
+
>
|
|
887
|
+
re-renders the template into a virtual string, then the
|
|
888
|
+
<code
|
|
889
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
890
|
+
>reconcile()</code
|
|
891
|
+
>
|
|
892
|
+
function walks the real DOM and patches only nodes that differ
|
|
893
|
+
— no full re-render, no Virtual DOM overhead.
|
|
894
|
+
</p>
|
|
895
|
+
</div>
|
|
896
|
+
<div>
|
|
897
|
+
<h2
|
|
898
|
+
id="persist"
|
|
899
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
900
|
+
>
|
|
901
|
+
Persistent State
|
|
902
|
+
</h2>
|
|
903
|
+
<p
|
|
904
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
905
|
+
>
|
|
906
|
+
Combine
|
|
907
|
+
<code
|
|
908
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
909
|
+
>@State()</code
|
|
910
|
+
>
|
|
911
|
+
with
|
|
912
|
+
<code
|
|
913
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
914
|
+
>@Persist()</code
|
|
915
|
+
>
|
|
916
|
+
to automatically sync a property to
|
|
917
|
+
<code
|
|
918
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
919
|
+
>localStorage</code
|
|
920
|
+
>
|
|
921
|
+
or
|
|
922
|
+
<code
|
|
923
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
924
|
+
>sessionStorage</code
|
|
925
|
+
>.
|
|
926
|
+
</p>
|
|
927
|
+
<pre
|
|
928
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
929
|
+
>
|
|
930
|
+
@State() @Persist('local') theme = 'dark';
|
|
931
|
+
@State() @Persist('session') token = '';</pre
|
|
932
|
+
>
|
|
933
|
+
</div>
|
|
934
|
+
</article>
|
|
935
|
+
</if>
|
|
936
|
+
|
|
937
|
+
<!-- ── TEMPLATES ── -->
|
|
938
|
+
<if condition="{activeTab === 'templates'}">
|
|
939
|
+
<article class="space-y-8">
|
|
940
|
+
<div>
|
|
941
|
+
<p
|
|
942
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
943
|
+
>
|
|
944
|
+
Core Concepts
|
|
945
|
+
</p>
|
|
946
|
+
<h1
|
|
947
|
+
id="templates"
|
|
948
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
949
|
+
>
|
|
950
|
+
Template Syntax
|
|
951
|
+
</h1>
|
|
952
|
+
<p
|
|
953
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
954
|
+
>
|
|
955
|
+
Templates are standard HTML enhanced with interpolation
|
|
956
|
+
expressions and semantic structural tags. No JSX, no
|
|
957
|
+
directives — just HTML that thinks.
|
|
958
|
+
</p>
|
|
959
|
+
</div>
|
|
960
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
961
|
+
<div>
|
|
962
|
+
<h2
|
|
963
|
+
id="interpolation"
|
|
964
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
965
|
+
>
|
|
966
|
+
Interpolation
|
|
967
|
+
</h2>
|
|
968
|
+
<p
|
|
969
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
970
|
+
>
|
|
971
|
+
Any expression wrapped in
|
|
972
|
+
<code
|
|
973
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
974
|
+
>{ }</code
|
|
975
|
+
>
|
|
976
|
+
is evaluated against the component context. Supports property
|
|
977
|
+
access, ternaries, and method calls.
|
|
978
|
+
</p>
|
|
979
|
+
<pre
|
|
980
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
981
|
+
>
|
|
982
|
+
<p>Hello, {username}!</p>
|
|
983
|
+
<span>{count > 0 ? count : 'None'}</span>
|
|
984
|
+
<img src="{avatarUrl}" alt="{username}" /></pre
|
|
985
|
+
>
|
|
986
|
+
</div>
|
|
987
|
+
<div>
|
|
988
|
+
<h2
|
|
989
|
+
id="conditionals"
|
|
990
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
991
|
+
>
|
|
992
|
+
Conditionals
|
|
993
|
+
</h2>
|
|
994
|
+
<pre
|
|
995
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
996
|
+
>
|
|
997
|
+
<if condition="{isLoggedIn}">
|
|
998
|
+
<p>Welcome back, {username}</p>
|
|
999
|
+
<else>
|
|
1000
|
+
<p>Please sign in.</p>
|
|
1001
|
+
</else>
|
|
1002
|
+
</if></pre
|
|
1003
|
+
>
|
|
1004
|
+
</div>
|
|
1005
|
+
<div>
|
|
1006
|
+
<h2
|
|
1007
|
+
id="loops"
|
|
1008
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1009
|
+
>
|
|
1010
|
+
Loops
|
|
1011
|
+
</h2>
|
|
1012
|
+
<pre
|
|
1013
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1014
|
+
>
|
|
1015
|
+
<for each="{item in items}">
|
|
1016
|
+
<li>{item.name} — {item.price}</li>
|
|
1017
|
+
</for></pre
|
|
1018
|
+
>
|
|
1019
|
+
</div>
|
|
1020
|
+
<div>
|
|
1021
|
+
<h2
|
|
1022
|
+
id="events"
|
|
1023
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1024
|
+
>
|
|
1025
|
+
Event Binding
|
|
1026
|
+
</h2>
|
|
1027
|
+
<p
|
|
1028
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
1029
|
+
>
|
|
1030
|
+
Use
|
|
1031
|
+
<code
|
|
1032
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1033
|
+
>@click</code
|
|
1034
|
+
>
|
|
1035
|
+
and
|
|
1036
|
+
<code
|
|
1037
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1038
|
+
>@input</code
|
|
1039
|
+
>
|
|
1040
|
+
attributes. Arguments are parsed and passed to the method
|
|
1041
|
+
automatically.
|
|
1042
|
+
</p>
|
|
1043
|
+
<pre
|
|
1044
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1045
|
+
>
|
|
1046
|
+
<button @click="save">Save</button>
|
|
1047
|
+
<button @click="remove(item.id)">Delete</button>
|
|
1048
|
+
<input @input="handleInput" /></pre
|
|
1049
|
+
>
|
|
1050
|
+
</div>
|
|
1051
|
+
</article>
|
|
1052
|
+
</if>
|
|
1053
|
+
|
|
1054
|
+
<!-- ── LIFECYCLE ── -->
|
|
1055
|
+
<if condition="{activeTab === 'lifecycle'}">
|
|
1056
|
+
<article class="space-y-8">
|
|
1057
|
+
<div>
|
|
1058
|
+
<p
|
|
1059
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
1060
|
+
>
|
|
1061
|
+
Core Concepts
|
|
1062
|
+
</p>
|
|
1063
|
+
<h1
|
|
1064
|
+
id="lifecycle"
|
|
1065
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
1066
|
+
>
|
|
1067
|
+
Lifecycle Hooks
|
|
1068
|
+
</h1>
|
|
1069
|
+
<p
|
|
1070
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
1071
|
+
>
|
|
1072
|
+
JunoJS components expose three lifecycle hooks that let
|
|
1073
|
+
you run code at predictable points during a component's
|
|
1074
|
+
lifetime.
|
|
1075
|
+
</p>
|
|
1076
|
+
</div>
|
|
1077
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
1078
|
+
<div class="grid grid-cols-3 gap-4">
|
|
1079
|
+
<div
|
|
1080
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-xl p-5 text-center space-y-2"
|
|
1081
|
+
>
|
|
1082
|
+
<p
|
|
1083
|
+
class="text-[10px] font-bold text-blue-500 uppercase tracking-wider"
|
|
1084
|
+
>
|
|
1085
|
+
Pre-render
|
|
1086
|
+
</p>
|
|
1087
|
+
<code
|
|
1088
|
+
class="text-gray-900 dark:text-white font-bold text-sm block"
|
|
1089
|
+
>onInit()</code
|
|
1090
|
+
>
|
|
1091
|
+
<p class="text-[11px] text-slate-500">
|
|
1092
|
+
Async-safe. Runs before first render.
|
|
1093
|
+
</p>
|
|
1094
|
+
</div>
|
|
1095
|
+
<div
|
|
1096
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-xl p-5 text-center space-y-2"
|
|
1097
|
+
>
|
|
1098
|
+
<p
|
|
1099
|
+
class="text-[10px] font-bold text-blue-500 uppercase tracking-wider"
|
|
1100
|
+
>
|
|
1101
|
+
Post-render
|
|
1102
|
+
</p>
|
|
1103
|
+
<code
|
|
1104
|
+
class="text-gray-900 dark:text-white font-bold text-sm block"
|
|
1105
|
+
>onRender()</code
|
|
1106
|
+
>
|
|
1107
|
+
<p class="text-[11px] text-slate-500">
|
|
1108
|
+
DOM is live. Safe for direct DOM access.
|
|
1109
|
+
</p>
|
|
1110
|
+
</div>
|
|
1111
|
+
<div
|
|
1112
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-xl p-5 text-center space-y-2"
|
|
1113
|
+
>
|
|
1114
|
+
<p
|
|
1115
|
+
class="text-[10px] font-bold text-blue-500 uppercase tracking-wider"
|
|
1116
|
+
>
|
|
1117
|
+
Cleanup
|
|
1118
|
+
</p>
|
|
1119
|
+
<code
|
|
1120
|
+
class="text-gray-900 dark:text-white font-bold text-sm block"
|
|
1121
|
+
>onDestroy()</code
|
|
1122
|
+
>
|
|
1123
|
+
<p class="text-[11px] text-slate-500">
|
|
1124
|
+
Remove timers, subscriptions, listeners.
|
|
1125
|
+
</p>
|
|
1126
|
+
</div>
|
|
1127
|
+
</div>
|
|
1128
|
+
<div>
|
|
1129
|
+
<h2
|
|
1130
|
+
id="example"
|
|
1131
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1132
|
+
>
|
|
1133
|
+
Example
|
|
1134
|
+
</h2>
|
|
1135
|
+
<pre
|
|
1136
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1137
|
+
>
|
|
1138
|
+
class DataComponent {
|
|
1139
|
+
@State() data = [];
|
|
1140
|
+
private timer: number;
|
|
1141
|
+
|
|
1142
|
+
async onInit() {
|
|
1143
|
+
this.data = await fetch('/api/items').then(r => r.json());
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
onRender(el: HTMLElement) {
|
|
1147
|
+
el.querySelector('input')?.focus();
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
onDestroy() {
|
|
1151
|
+
clearInterval(this.timer);
|
|
1152
|
+
}
|
|
1153
|
+
}</pre
|
|
1154
|
+
>
|
|
1155
|
+
</div>
|
|
1156
|
+
</article>
|
|
1157
|
+
</if>
|
|
1158
|
+
|
|
1159
|
+
<!-- ── HTTP ── -->
|
|
1160
|
+
<if condition="{activeTab === 'http'}">
|
|
1161
|
+
<article class="space-y-8">
|
|
1162
|
+
<div>
|
|
1163
|
+
<p
|
|
1164
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
1165
|
+
>
|
|
1166
|
+
Common Services
|
|
1167
|
+
</p>
|
|
1168
|
+
<h1
|
|
1169
|
+
id="http"
|
|
1170
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
1171
|
+
>
|
|
1172
|
+
HTTP Client
|
|
1173
|
+
</h1>
|
|
1174
|
+
<p
|
|
1175
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
1176
|
+
>
|
|
1177
|
+
A typed, interceptor-based HTTP client built on the Fetch API.
|
|
1178
|
+
Supports auto-JSON serialisation, query params, timeout/abort,
|
|
1179
|
+
and pluggable error handling.
|
|
1180
|
+
</p>
|
|
1181
|
+
</div>
|
|
1182
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
1183
|
+
<div>
|
|
1184
|
+
<h2
|
|
1185
|
+
id="methods"
|
|
1186
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1187
|
+
>
|
|
1188
|
+
Methods
|
|
1189
|
+
</h2>
|
|
1190
|
+
<div class="grid grid-cols-2 gap-3">
|
|
1191
|
+
<div
|
|
1192
|
+
class="bg-slate-900 border border-slate-800 rounded-lg p-4"
|
|
1193
|
+
>
|
|
1194
|
+
<code class="text-blue-400 text-xs font-mono font-bold"
|
|
1195
|
+
>http.get<T>(url, config?)</code
|
|
1196
|
+
>
|
|
1197
|
+
<p
|
|
1198
|
+
class="text-[11px] text-gray-500 dark:text-slate-500 mt-1"
|
|
1199
|
+
>
|
|
1200
|
+
Fetch a resource
|
|
1201
|
+
</p>
|
|
1202
|
+
</div>
|
|
1203
|
+
<div
|
|
1204
|
+
class="bg-slate-900 border border-slate-800 rounded-lg p-4"
|
|
1205
|
+
>
|
|
1206
|
+
<code class="text-blue-400 text-xs font-mono font-bold"
|
|
1207
|
+
>http.post<T>(url, body?)</code
|
|
1208
|
+
>
|
|
1209
|
+
<p
|
|
1210
|
+
class="text-[11px] text-gray-500 dark:text-slate-500 mt-1"
|
|
1211
|
+
>
|
|
1212
|
+
Create a resource
|
|
1213
|
+
</p>
|
|
1214
|
+
</div>
|
|
1215
|
+
<div
|
|
1216
|
+
class="bg-slate-900 border border-slate-800 rounded-lg p-4"
|
|
1217
|
+
>
|
|
1218
|
+
<code class="text-blue-400 text-xs font-mono font-bold"
|
|
1219
|
+
>http.put<T>(url, body?)</code
|
|
1220
|
+
>
|
|
1221
|
+
<p
|
|
1222
|
+
class="text-[11px] text-gray-500 dark:text-slate-500 mt-1"
|
|
1223
|
+
>
|
|
1224
|
+
Replace a resource
|
|
1225
|
+
</p>
|
|
1226
|
+
</div>
|
|
1227
|
+
<div
|
|
1228
|
+
class="bg-slate-900 border border-slate-800 rounded-lg p-4"
|
|
1229
|
+
>
|
|
1230
|
+
<code class="text-blue-400 text-xs font-mono font-bold"
|
|
1231
|
+
>http.patch<T>(url, body?)</code
|
|
1232
|
+
>
|
|
1233
|
+
<p
|
|
1234
|
+
class="text-[11px] text-gray-500 dark:text-slate-500 mt-1"
|
|
1235
|
+
>
|
|
1236
|
+
Partial update
|
|
1237
|
+
</p>
|
|
1238
|
+
</div>
|
|
1239
|
+
<div
|
|
1240
|
+
class="bg-slate-900 border border-slate-800 rounded-lg p-4"
|
|
1241
|
+
>
|
|
1242
|
+
<code class="text-blue-400 text-xs font-mono font-bold"
|
|
1243
|
+
>http.delete<T>(url, config?)</code
|
|
1244
|
+
>
|
|
1245
|
+
<p
|
|
1246
|
+
class="text-[11px] text-gray-500 dark:text-slate-500 mt-1"
|
|
1247
|
+
>
|
|
1248
|
+
Remove a resource
|
|
1249
|
+
</p>
|
|
1250
|
+
</div>
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
<div>
|
|
1254
|
+
<h2
|
|
1255
|
+
id="config"
|
|
1256
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1257
|
+
>
|
|
1258
|
+
RequestConfig
|
|
1259
|
+
</h2>
|
|
1260
|
+
<pre
|
|
1261
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1262
|
+
>
|
|
1263
|
+
{
|
|
1264
|
+
headers?: Record<string, string>
|
|
1265
|
+
params?: Record<string, string> // Appended as query string
|
|
1266
|
+
timeout?: number // ms — auto-aborts request
|
|
1267
|
+
signal?: AbortSignal // External AbortController
|
|
1268
|
+
}</pre
|
|
1269
|
+
>
|
|
1270
|
+
</div>
|
|
1271
|
+
<div>
|
|
1272
|
+
<h2
|
|
1273
|
+
id="interceptors"
|
|
1274
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1275
|
+
>
|
|
1276
|
+
Interceptors
|
|
1277
|
+
</h2>
|
|
1278
|
+
<pre
|
|
1279
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1280
|
+
>
|
|
1281
|
+
// Add auth token to every request
|
|
1282
|
+
http.addRequestInterceptor((url, init) => {
|
|
1283
|
+
init.headers['Authorization'] = `Bearer ${token}`;
|
|
1284
|
+
return [url, init];
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
// Handle 401 globally
|
|
1288
|
+
http.addResponseInterceptor((res) => {
|
|
1289
|
+
if (res.status === 401) router.go('/login');
|
|
1290
|
+
return res;
|
|
1291
|
+
});</pre
|
|
1292
|
+
>
|
|
1293
|
+
</div>
|
|
1294
|
+
</article>
|
|
1295
|
+
</if>
|
|
1296
|
+
|
|
1297
|
+
<!-- ── I18N ── -->
|
|
1298
|
+
<if condition="{activeTab === 'i18n'}">
|
|
1299
|
+
<article class="space-y-8">
|
|
1300
|
+
<div>
|
|
1301
|
+
<p
|
|
1302
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
1303
|
+
>
|
|
1304
|
+
Common Services
|
|
1305
|
+
</p>
|
|
1306
|
+
<h1
|
|
1307
|
+
id="i18n"
|
|
1308
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
1309
|
+
>
|
|
1310
|
+
Internationalization
|
|
1311
|
+
</h1>
|
|
1312
|
+
<p
|
|
1313
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
1314
|
+
>
|
|
1315
|
+
A singleton i18n service with multi-locale support,
|
|
1316
|
+
<code
|
|
1317
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1318
|
+
>{{param}}</code
|
|
1319
|
+
>
|
|
1320
|
+
interpolation, and reactive locale-change subscriptions.
|
|
1321
|
+
</p>
|
|
1322
|
+
</div>
|
|
1323
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
1324
|
+
<div>
|
|
1325
|
+
<h2
|
|
1326
|
+
id="setup"
|
|
1327
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1328
|
+
>
|
|
1329
|
+
Setup
|
|
1330
|
+
</h2>
|
|
1331
|
+
<pre
|
|
1332
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1333
|
+
>
|
|
1334
|
+
import { I18nService } from '@junojs/common/i18n';
|
|
1335
|
+
|
|
1336
|
+
const i18n = I18nService.instance;
|
|
1337
|
+
|
|
1338
|
+
i18n.addTranslations('en', {
|
|
1339
|
+
greeting: 'Hello, {{name}}!',
|
|
1340
|
+
items: '{{count}} items found',
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
i18n.addTranslations('de', {
|
|
1344
|
+
greeting: 'Hallo, {{name}}!',
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
i18n.setLocale('en');</pre
|
|
1348
|
+
>
|
|
1349
|
+
</div>
|
|
1350
|
+
<div>
|
|
1351
|
+
<h2
|
|
1352
|
+
id="translate"
|
|
1353
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1354
|
+
>
|
|
1355
|
+
Translating Keys
|
|
1356
|
+
</h2>
|
|
1357
|
+
<pre
|
|
1358
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1359
|
+
>
|
|
1360
|
+
i18n.t('greeting', { name: 'Ada' }); // "Hello, Ada!"
|
|
1361
|
+
i18n.t('items', { count: 42 }); // "42 items found"
|
|
1362
|
+
i18n.t('unknown.key'); // returns the key itself</pre
|
|
1363
|
+
>
|
|
1364
|
+
</div>
|
|
1365
|
+
<div>
|
|
1366
|
+
<h2
|
|
1367
|
+
id="locale-change"
|
|
1368
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1369
|
+
>
|
|
1370
|
+
Reactive Locale Changes
|
|
1371
|
+
</h2>
|
|
1372
|
+
<pre
|
|
1373
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1374
|
+
>
|
|
1375
|
+
class MyComponent {
|
|
1376
|
+
private unsubscribe: () => void;
|
|
1377
|
+
|
|
1378
|
+
onInit() {
|
|
1379
|
+
this.unsubscribe = i18n.onLocaleChange(() => this.update());
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
onDestroy() {
|
|
1383
|
+
this.unsubscribe(); // prevent memory leaks
|
|
1384
|
+
}
|
|
1385
|
+
}</pre
|
|
1386
|
+
>
|
|
1387
|
+
</div>
|
|
1388
|
+
</article>
|
|
1389
|
+
</if>
|
|
1390
|
+
|
|
1391
|
+
<!-- ── FORMS ── -->
|
|
1392
|
+
<if condition="{activeTab === 'forms'}">
|
|
1393
|
+
<article class="space-y-8">
|
|
1394
|
+
<div>
|
|
1395
|
+
<p
|
|
1396
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
1397
|
+
>
|
|
1398
|
+
Common Services
|
|
1399
|
+
</p>
|
|
1400
|
+
<h1
|
|
1401
|
+
id="forms"
|
|
1402
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
1403
|
+
>
|
|
1404
|
+
Forms & Validation
|
|
1405
|
+
</h1>
|
|
1406
|
+
<p
|
|
1407
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
1408
|
+
>
|
|
1409
|
+
Reactive form models with decorator-based validation. Extend
|
|
1410
|
+
<code
|
|
1411
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1412
|
+
>FormModel</code
|
|
1413
|
+
>
|
|
1414
|
+
and annotate fields with built-in validators.
|
|
1415
|
+
</p>
|
|
1416
|
+
</div>
|
|
1417
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
1418
|
+
<div>
|
|
1419
|
+
<h2
|
|
1420
|
+
id="form-model"
|
|
1421
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1422
|
+
>
|
|
1423
|
+
FormModel
|
|
1424
|
+
</h2>
|
|
1425
|
+
<pre
|
|
1426
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1427
|
+
>
|
|
1428
|
+
import { FormModel, Required, Email, Match } from '@junojs/common/forms';
|
|
1429
|
+
|
|
1430
|
+
class RegisterForm extends FormModel {
|
|
1431
|
+
@Required() @Email() email = '';
|
|
1432
|
+
@Required() password = '';
|
|
1433
|
+
@Required() @Match('password', 'Passwords must match')
|
|
1434
|
+
confirmPassword = '';
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
const form = new RegisterForm();
|
|
1438
|
+
form.isValid(); // false (all fields empty)
|
|
1439
|
+
form.getErrors(); // { email: ['...'], password: ['...'] }
|
|
1440
|
+
form.dirty; // false (not changed yet)
|
|
1441
|
+
form.reset(); // restore initial values</pre
|
|
1442
|
+
>
|
|
1443
|
+
</div>
|
|
1444
|
+
<div>
|
|
1445
|
+
<h2
|
|
1446
|
+
id="validators"
|
|
1447
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1448
|
+
>
|
|
1449
|
+
Built-in Validators
|
|
1450
|
+
</h2>
|
|
1451
|
+
<div class="space-y-3">
|
|
1452
|
+
<div
|
|
1453
|
+
class="bg-slate-900 border border-slate-800 rounded-lg p-4 flex gap-4 items-start"
|
|
1454
|
+
>
|
|
1455
|
+
<code
|
|
1456
|
+
class="text-blue-400 text-xs font-mono font-bold whitespace-nowrap"
|
|
1457
|
+
>@Required(msg?)</code
|
|
1458
|
+
>
|
|
1459
|
+
<p
|
|
1460
|
+
class="text-xs text-gray-600 dark:text-slate-400 leading-relaxed mt-0.5"
|
|
1461
|
+
>
|
|
1462
|
+
Field must be non-empty, non-null, and non-undefined.
|
|
1463
|
+
</p>
|
|
1464
|
+
</div>
|
|
1465
|
+
<div
|
|
1466
|
+
class="bg-slate-900 border border-slate-800 rounded-lg p-4 flex gap-4 items-start"
|
|
1467
|
+
>
|
|
1468
|
+
<code
|
|
1469
|
+
class="text-blue-400 text-xs font-mono font-bold whitespace-nowrap"
|
|
1470
|
+
>@Email(msg?)</code
|
|
1471
|
+
>
|
|
1472
|
+
<p
|
|
1473
|
+
class="text-xs text-gray-600 dark:text-slate-400 leading-relaxed mt-0.5"
|
|
1474
|
+
>
|
|
1475
|
+
Must be a valid email address format. Composable with
|
|
1476
|
+
@Required.
|
|
1477
|
+
</p>
|
|
1478
|
+
</div>
|
|
1479
|
+
<div
|
|
1480
|
+
class="bg-slate-900 border border-slate-800 rounded-lg p-4 flex gap-4 items-start"
|
|
1481
|
+
>
|
|
1482
|
+
<code
|
|
1483
|
+
class="text-blue-400 text-xs font-mono font-bold whitespace-nowrap"
|
|
1484
|
+
>@Pattern(regex, msg?)</code
|
|
1485
|
+
>
|
|
1486
|
+
<p
|
|
1487
|
+
class="text-xs text-gray-600 dark:text-slate-400 leading-relaxed mt-0.5"
|
|
1488
|
+
>
|
|
1489
|
+
Field value must match the provided regular expression.
|
|
1490
|
+
</p>
|
|
1491
|
+
</div>
|
|
1492
|
+
<div
|
|
1493
|
+
class="bg-slate-900 border border-slate-800 rounded-lg p-4 flex gap-4 items-start"
|
|
1494
|
+
>
|
|
1495
|
+
<code
|
|
1496
|
+
class="text-blue-400 text-xs font-mono font-bold whitespace-nowrap"
|
|
1497
|
+
>@Match(field, msg?)</code
|
|
1498
|
+
>
|
|
1499
|
+
<p
|
|
1500
|
+
class="text-xs text-gray-600 dark:text-slate-400 leading-relaxed mt-0.5"
|
|
1501
|
+
>
|
|
1502
|
+
Cross-field equality check — e.g. confirm password.
|
|
1503
|
+
</p>
|
|
1504
|
+
</div>
|
|
1505
|
+
</div>
|
|
1506
|
+
</div>
|
|
1507
|
+
</article>
|
|
1508
|
+
</if>
|
|
1509
|
+
|
|
1510
|
+
<!-- ── ROUTER SERVICE ── -->
|
|
1511
|
+
<if condition="{activeTab === 'routing'}">
|
|
1512
|
+
<article class="space-y-8">
|
|
1513
|
+
<div>
|
|
1514
|
+
<p
|
|
1515
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
1516
|
+
>
|
|
1517
|
+
Common Services
|
|
1518
|
+
</p>
|
|
1519
|
+
<h1
|
|
1520
|
+
id="routing"
|
|
1521
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
1522
|
+
>
|
|
1523
|
+
Router Service
|
|
1524
|
+
</h1>
|
|
1525
|
+
<p
|
|
1526
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
1527
|
+
>
|
|
1528
|
+
JunoJS includes a typed
|
|
1529
|
+
<strong class="text-gray-800 dark:text-slate-200"
|
|
1530
|
+
>History-based Router Service</strong
|
|
1531
|
+
>. It synchronizes the browser address bar with internal
|
|
1532
|
+
component state, enabling deep linking and browser history
|
|
1533
|
+
navigation.
|
|
1534
|
+
</p>
|
|
1535
|
+
</div>
|
|
1536
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
1537
|
+
|
|
1538
|
+
<!-- How it works -->
|
|
1539
|
+
<div>
|
|
1540
|
+
<h2
|
|
1541
|
+
id="routing-how"
|
|
1542
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1543
|
+
>
|
|
1544
|
+
How It Works
|
|
1545
|
+
</h2>
|
|
1546
|
+
<p
|
|
1547
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
1548
|
+
>
|
|
1549
|
+
The
|
|
1550
|
+
<code
|
|
1551
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs font-mono"
|
|
1552
|
+
>RouterService</code
|
|
1553
|
+
>
|
|
1554
|
+
listens for
|
|
1555
|
+
<code class="text-blue-300">popstate</code> events. When the
|
|
1556
|
+
URL matches a registered route, it notifies subscribers.
|
|
1557
|
+
Components use the
|
|
1558
|
+
<code class="text-blue-300">onRouteMatch</code> hook to sync
|
|
1559
|
+
their internal
|
|
1560
|
+
<code class="text-blue-300">@State</code> properties with the
|
|
1561
|
+
current route.
|
|
1562
|
+
</p>
|
|
1563
|
+
<pre
|
|
1564
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1565
|
+
>
|
|
1566
|
+
import { RouterService } from '@junojs/common/router';
|
|
1567
|
+
|
|
1568
|
+
const router = RouterService.instance;
|
|
1569
|
+
|
|
1570
|
+
// Register routes
|
|
1571
|
+
router.register([
|
|
1572
|
+
{ path: '/', tab: 'home', title: 'Home' },
|
|
1573
|
+
{ path: '/docs', tab: 'docs', title: 'Documentation' },
|
|
1574
|
+
]);</pre
|
|
1575
|
+
>
|
|
1576
|
+
</div>
|
|
1577
|
+
|
|
1578
|
+
<!-- Navigation -->
|
|
1579
|
+
<div>
|
|
1580
|
+
<h2
|
|
1581
|
+
id="routing-triggers"
|
|
1582
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1583
|
+
>
|
|
1584
|
+
Programmatic Navigation
|
|
1585
|
+
</h2>
|
|
1586
|
+
<p
|
|
1587
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
1588
|
+
>
|
|
1589
|
+
Use the
|
|
1590
|
+
<code class="text-blue-300">navigate(path)</code> method to
|
|
1591
|
+
change routes without a full page reload. This updates the
|
|
1592
|
+
History API and triggers the reconciliation logic.
|
|
1593
|
+
</p>
|
|
1594
|
+
<pre
|
|
1595
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1596
|
+
>
|
|
1597
|
+
router.navigate('/installation');</pre
|
|
1598
|
+
>
|
|
1599
|
+
</div>
|
|
1600
|
+
|
|
1601
|
+
<!-- State Sync -->
|
|
1602
|
+
<div>
|
|
1603
|
+
<h2
|
|
1604
|
+
id="routing-state"
|
|
1605
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1606
|
+
>
|
|
1607
|
+
Component Synchronization
|
|
1608
|
+
</h2>
|
|
1609
|
+
<p
|
|
1610
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
1611
|
+
>
|
|
1612
|
+
Bridging the router to a component's state is simple.
|
|
1613
|
+
Subscribe in <code class="text-blue-300">onInit</code> and
|
|
1614
|
+
update your reactive property.
|
|
1615
|
+
</p>
|
|
1616
|
+
<pre
|
|
1617
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1618
|
+
>
|
|
1619
|
+
class AppDocs {
|
|
1620
|
+
@State() activeTab = 'introduction';
|
|
1621
|
+
|
|
1622
|
+
onInit() {
|
|
1623
|
+
router.onRouteMatch(tab => this.activeTab = tab);
|
|
1624
|
+
router.init(); // Match initial URL
|
|
1625
|
+
}
|
|
1626
|
+
}</pre
|
|
1627
|
+
>
|
|
1628
|
+
</div>
|
|
1629
|
+
|
|
1630
|
+
<!-- Persisting -->
|
|
1631
|
+
<div>
|
|
1632
|
+
<h2
|
|
1633
|
+
id="routing-persist"
|
|
1634
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1635
|
+
>
|
|
1636
|
+
Persistent Routes
|
|
1637
|
+
</h2>
|
|
1638
|
+
<p
|
|
1639
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
1640
|
+
>
|
|
1641
|
+
Combine with the
|
|
1642
|
+
<code
|
|
1643
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs font-mono"
|
|
1644
|
+
>@Persist</code
|
|
1645
|
+
>
|
|
1646
|
+
decorator for tab-scoped persistence that survives refreshes
|
|
1647
|
+
while staying in sync with the address bar.
|
|
1648
|
+
</p>
|
|
1649
|
+
</div>
|
|
1650
|
+
</article>
|
|
1651
|
+
</if>
|
|
1652
|
+
|
|
1653
|
+
<!-- ── DECORATORS ── -->
|
|
1654
|
+
<if condition="{activeTab === 'decorators'}">
|
|
1655
|
+
<article class="space-y-8">
|
|
1656
|
+
<div>
|
|
1657
|
+
<p
|
|
1658
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
1659
|
+
>
|
|
1660
|
+
API Reference
|
|
1661
|
+
</p>
|
|
1662
|
+
<h1
|
|
1663
|
+
id="decorators"
|
|
1664
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
1665
|
+
>
|
|
1666
|
+
Decorators
|
|
1667
|
+
</h1>
|
|
1668
|
+
<p
|
|
1669
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
1670
|
+
>
|
|
1671
|
+
JunoJS is decorator-driven. All framework behaviour is
|
|
1672
|
+
attached to classes and properties via TypeScript decorators
|
|
1673
|
+
backed by
|
|
1674
|
+
<code
|
|
1675
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1676
|
+
>reflect-metadata</code
|
|
1677
|
+
>.
|
|
1678
|
+
</p>
|
|
1679
|
+
</div>
|
|
1680
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
1681
|
+
<div class="space-y-4">
|
|
1682
|
+
<h2
|
|
1683
|
+
id="class-decorators"
|
|
1684
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1685
|
+
>
|
|
1686
|
+
Class Decorators
|
|
1687
|
+
</h2>
|
|
1688
|
+
<div
|
|
1689
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1690
|
+
>
|
|
1691
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1692
|
+
>@Component(config)</code
|
|
1693
|
+
>
|
|
1694
|
+
<p
|
|
1695
|
+
class="text-xs text-gray-600 dark:text-slate-400 mt-1 leading-relaxed"
|
|
1696
|
+
>
|
|
1697
|
+
Registers the class as a custom element.
|
|
1698
|
+
<strong class="text-slate-300">tag</strong> (required),
|
|
1699
|
+
<strong class="text-slate-300">template</strong> or
|
|
1700
|
+
<strong class="text-slate-300">templateUrl</strong>,
|
|
1701
|
+
<strong class="text-slate-300">shadow</strong> (default:
|
|
1702
|
+
true).
|
|
1703
|
+
</p>
|
|
1704
|
+
</div>
|
|
1705
|
+
<div
|
|
1706
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1707
|
+
>
|
|
1708
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1709
|
+
>@Schema(zodSchema)</code
|
|
1710
|
+
>
|
|
1711
|
+
<p
|
|
1712
|
+
class="text-xs text-gray-600 dark:text-slate-400 mt-1 leading-relaxed"
|
|
1713
|
+
>
|
|
1714
|
+
Attach a Zod-compatible validation schema to the component
|
|
1715
|
+
for use with the built-in
|
|
1716
|
+
<code class="text-blue-300">isValid()</code> method.
|
|
1717
|
+
</p>
|
|
1718
|
+
</div>
|
|
1719
|
+
<div
|
|
1720
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1721
|
+
>
|
|
1722
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1723
|
+
>@Refine(options)</code
|
|
1724
|
+
>
|
|
1725
|
+
<p
|
|
1726
|
+
class="text-xs text-gray-600 dark:text-slate-400 mt-1 leading-relaxed"
|
|
1727
|
+
>
|
|
1728
|
+
Cross-field validation at the class level.
|
|
1729
|
+
<strong class="text-slate-300">fields</strong>,
|
|
1730
|
+
<strong class="text-slate-300">validate(data)</strong>, and
|
|
1731
|
+
<strong class="text-slate-300">message</strong>.
|
|
1732
|
+
</p>
|
|
1733
|
+
</div>
|
|
1734
|
+
</div>
|
|
1735
|
+
<div class="space-y-4">
|
|
1736
|
+
<h2
|
|
1737
|
+
id="property-decorators"
|
|
1738
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1739
|
+
>
|
|
1740
|
+
Property Decorators
|
|
1741
|
+
</h2>
|
|
1742
|
+
<div
|
|
1743
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1744
|
+
>
|
|
1745
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1746
|
+
>@State()</code
|
|
1747
|
+
>
|
|
1748
|
+
<p class="text-xs text-gray-600 dark:text-slate-400 mt-1">
|
|
1749
|
+
Makes the property reactive. Writes trigger DOM
|
|
1750
|
+
reconciliation.
|
|
1751
|
+
</p>
|
|
1752
|
+
</div>
|
|
1753
|
+
<div
|
|
1754
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1755
|
+
>
|
|
1756
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1757
|
+
>@Input()</code
|
|
1758
|
+
>
|
|
1759
|
+
<p class="text-xs text-gray-600 dark:text-slate-400 mt-1">
|
|
1760
|
+
Maps an HTML attribute to a class property. Observed via
|
|
1761
|
+
<code class="text-blue-300">attributeChangedCallback</code>.
|
|
1762
|
+
</p>
|
|
1763
|
+
</div>
|
|
1764
|
+
<div
|
|
1765
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1766
|
+
>
|
|
1767
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1768
|
+
>@Persist(storage)</code
|
|
1769
|
+
>
|
|
1770
|
+
<p class="text-xs text-gray-600 dark:text-slate-400 mt-1">
|
|
1771
|
+
Auto-saves to <code class="text-blue-300">'local'</code> or
|
|
1772
|
+
<code class="text-blue-300">'session'</code> storage. Key is
|
|
1773
|
+
auto-derived from class and property name.
|
|
1774
|
+
</p>
|
|
1775
|
+
</div>
|
|
1776
|
+
<div
|
|
1777
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1778
|
+
>
|
|
1779
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1780
|
+
>@Transform()</code
|
|
1781
|
+
>
|
|
1782
|
+
<p class="text-xs text-gray-600 dark:text-slate-400 mt-1">
|
|
1783
|
+
Registers a method as a template pipe function, usable as
|
|
1784
|
+
<code class="text-blue-300"
|
|
1785
|
+
>{value | methodName}</code
|
|
1786
|
+
>.
|
|
1787
|
+
</p>
|
|
1788
|
+
</div>
|
|
1789
|
+
<div
|
|
1790
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1791
|
+
>
|
|
1792
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1793
|
+
>@Action()</code
|
|
1794
|
+
>
|
|
1795
|
+
<p class="text-xs text-gray-600 dark:text-slate-400 mt-1">
|
|
1796
|
+
Marks a method as an emittable action, usable for
|
|
1797
|
+
inter-component communication.
|
|
1798
|
+
</p>
|
|
1799
|
+
</div>
|
|
1800
|
+
<div
|
|
1801
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1802
|
+
>
|
|
1803
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1804
|
+
>@HostElement()</code
|
|
1805
|
+
>
|
|
1806
|
+
<p class="text-xs text-gray-600 dark:text-slate-400 mt-1">
|
|
1807
|
+
Injects the native host HTMLElement into the property.
|
|
1808
|
+
Essential for dispatching bubbling CustomEvents or direct
|
|
1809
|
+
element API access.
|
|
1810
|
+
</p>
|
|
1811
|
+
</div>
|
|
1812
|
+
<div
|
|
1813
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-5 space-y-2"
|
|
1814
|
+
>
|
|
1815
|
+
<code class="text-blue-400 font-mono font-bold"
|
|
1816
|
+
>@Use(ServiceClass)</code
|
|
1817
|
+
>
|
|
1818
|
+
<p class="text-xs text-gray-600 dark:text-slate-400 mt-1">
|
|
1819
|
+
Injects a singleton service instance into the property at
|
|
1820
|
+
bootstrap time.
|
|
1821
|
+
</p>
|
|
1822
|
+
</div>
|
|
1823
|
+
</div>
|
|
1824
|
+
</article>
|
|
1825
|
+
</if>
|
|
1826
|
+
|
|
1827
|
+
<!-- ── ENGINE ── -->
|
|
1828
|
+
<if condition="{activeTab === 'engine'}">
|
|
1829
|
+
<article class="space-y-8">
|
|
1830
|
+
<div>
|
|
1831
|
+
<p
|
|
1832
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
1833
|
+
>
|
|
1834
|
+
API Reference
|
|
1835
|
+
</p>
|
|
1836
|
+
<h1
|
|
1837
|
+
id="engine"
|
|
1838
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
1839
|
+
>
|
|
1840
|
+
Engine API
|
|
1841
|
+
</h1>
|
|
1842
|
+
<p
|
|
1843
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
1844
|
+
>
|
|
1845
|
+
The low-level primitives powering JunoJS. You rarely call
|
|
1846
|
+
these directly — but understanding them helps you build more
|
|
1847
|
+
efficient components.
|
|
1848
|
+
</p>
|
|
1849
|
+
</div>
|
|
1850
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
1851
|
+
<div>
|
|
1852
|
+
<h2
|
|
1853
|
+
id="neural-engine"
|
|
1854
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1855
|
+
>
|
|
1856
|
+
NeuralEngine.render()
|
|
1857
|
+
</h2>
|
|
1858
|
+
<p
|
|
1859
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
1860
|
+
>
|
|
1861
|
+
Processes a template string against a context object. Handles
|
|
1862
|
+
interpolation, structural tags, and attribute resolution.
|
|
1863
|
+
Returns an HTML string.
|
|
1864
|
+
</p>
|
|
1865
|
+
<pre
|
|
1866
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1867
|
+
>
|
|
1868
|
+
NeuralEngine.render(templateString, contextObject): string</pre
|
|
1869
|
+
>
|
|
1870
|
+
</div>
|
|
1871
|
+
<div>
|
|
1872
|
+
<h2
|
|
1873
|
+
id="reconcile"
|
|
1874
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1875
|
+
>
|
|
1876
|
+
reconcile()
|
|
1877
|
+
</h2>
|
|
1878
|
+
<p
|
|
1879
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
1880
|
+
>
|
|
1881
|
+
Walks the real DOM tree and patches it to match the new
|
|
1882
|
+
rendered HTML. Only nodes that differ are touched — no full
|
|
1883
|
+
re-mount, no flicker.
|
|
1884
|
+
</p>
|
|
1885
|
+
<pre
|
|
1886
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1887
|
+
>
|
|
1888
|
+
reconcile(target: HTMLElement | ShadowRoot, html: string): void</pre
|
|
1889
|
+
>
|
|
1890
|
+
</div>
|
|
1891
|
+
<div>
|
|
1892
|
+
<h2
|
|
1893
|
+
id="proxy"
|
|
1894
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1895
|
+
>
|
|
1896
|
+
createReactiveProxy()
|
|
1897
|
+
</h2>
|
|
1898
|
+
<p
|
|
1899
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
1900
|
+
>
|
|
1901
|
+
Wraps a component instance in a Proxy. Any write to a
|
|
1902
|
+
<code
|
|
1903
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1904
|
+
>@State</code
|
|
1905
|
+
>
|
|
1906
|
+
property triggers the
|
|
1907
|
+
<code
|
|
1908
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1909
|
+
>onUpdate</code
|
|
1910
|
+
>
|
|
1911
|
+
callback (which calls
|
|
1912
|
+
<code
|
|
1913
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1914
|
+
>update()</code
|
|
1915
|
+
>). Also auto-persists
|
|
1916
|
+
<code
|
|
1917
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1918
|
+
>@Persist</code
|
|
1919
|
+
>
|
|
1920
|
+
properties.
|
|
1921
|
+
</p>
|
|
1922
|
+
<pre
|
|
1923
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1924
|
+
>
|
|
1925
|
+
createReactiveProxy(instance, onUpdate: () => void): Proxy</pre
|
|
1926
|
+
>
|
|
1927
|
+
</div>
|
|
1928
|
+
<div>
|
|
1929
|
+
<h2
|
|
1930
|
+
id="bootstrap"
|
|
1931
|
+
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
1932
|
+
>
|
|
1933
|
+
bootstrap()
|
|
1934
|
+
</h2>
|
|
1935
|
+
<p
|
|
1936
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7 mb-4"
|
|
1937
|
+
>
|
|
1938
|
+
Reads the
|
|
1939
|
+
<code
|
|
1940
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1941
|
+
>@Component</code
|
|
1942
|
+
>
|
|
1943
|
+
metadata, defines the custom element via
|
|
1944
|
+
<code
|
|
1945
|
+
class="bg-gray-100 text-blue-600 dark:bg-slate-900 dark:text-blue-400 px-1 rounded text-xs"
|
|
1946
|
+
>customElements.define()</code
|
|
1947
|
+
>, and wires up the full lifecycle.
|
|
1948
|
+
</p>
|
|
1949
|
+
<pre
|
|
1950
|
+
class="bg-gray-50 border border-gray-200 dark:bg-slate-900 dark:border-slate-800 rounded-lg p-4 text-[12px] font-mono text-blue-700 dark:text-blue-300 overflow-x-auto"
|
|
1951
|
+
>
|
|
1952
|
+
bootstrap(ComponentClass: any): void</pre
|
|
1953
|
+
>
|
|
1954
|
+
</div>
|
|
1955
|
+
</article>
|
|
1956
|
+
</if>
|
|
1957
|
+
|
|
1958
|
+
<!-- Prev / Next navigation -->
|
|
1959
|
+
<div
|
|
1960
|
+
class="mt-20 pt-8 border-t border-gray-200 dark:border-slate-800/60 flex items-stretch gap-4"
|
|
1961
|
+
>
|
|
1962
|
+
<if condition="{hasPrev}">
|
|
1963
|
+
<button
|
|
1964
|
+
@click="selectPrev"
|
|
1965
|
+
class="group flex flex-col items-start px-5 py-4 rounded-xl border border-gray-200 hover:border-gray-400 dark:border-slate-800 dark:hover:border-slate-600 transition-all flex-1"
|
|
1966
|
+
>
|
|
1967
|
+
<span
|
|
1968
|
+
class="text-[10px] font-semibold text-gray-400 dark:text-slate-500 uppercase tracking-wider mb-1"
|
|
1969
|
+
>← Previous</span
|
|
1970
|
+
>
|
|
1971
|
+
<span
|
|
1972
|
+
class="text-sm font-medium text-gray-700 dark:text-slate-300 group-hover:text-gray-950 dark:group-hover:text-white transition-colors"
|
|
1973
|
+
>{prevPageName}</span
|
|
1974
|
+
>
|
|
1975
|
+
</button>
|
|
1976
|
+
</if>
|
|
1977
|
+
<if condition="{hasNext}">
|
|
1978
|
+
<button
|
|
1979
|
+
@click="selectNext"
|
|
1980
|
+
class="group flex flex-col items-end px-5 py-4 rounded-xl border border-gray-200 hover:border-gray-400 dark:border-slate-800 dark:hover:border-slate-600 transition-all flex-1 ml-auto"
|
|
1981
|
+
>
|
|
1982
|
+
<span
|
|
1983
|
+
class="text-[10px] font-semibold text-gray-400 dark:text-slate-500 uppercase tracking-wider mb-1"
|
|
1984
|
+
>Next →</span
|
|
1985
|
+
>
|
|
1986
|
+
<span
|
|
1987
|
+
class="text-sm font-medium text-gray-700 dark:text-slate-300 group-hover:text-gray-950 dark:group-hover:text-white transition-colors"
|
|
1988
|
+
>{nextPageName}</span
|
|
1989
|
+
>
|
|
1990
|
+
</button>
|
|
1991
|
+
</if>
|
|
1992
|
+
</div>
|
|
1993
|
+
|
|
1994
|
+
<!-- ── SAMPLES ── -->
|
|
1995
|
+
<if condition="{activeTab === 'samples'}">
|
|
1996
|
+
<article class="space-y-12">
|
|
1997
|
+
<div>
|
|
1998
|
+
<p
|
|
1999
|
+
class="text-xs font-semibold text-blue-500 uppercase tracking-widest mb-3"
|
|
2000
|
+
>
|
|
2001
|
+
Resources
|
|
2002
|
+
</p>
|
|
2003
|
+
<h1
|
|
2004
|
+
id="samples"
|
|
2005
|
+
class="text-3xl font-bold text-gray-950 dark:text-white tracking-tight mb-4"
|
|
2006
|
+
>
|
|
2007
|
+
Samples & Examples
|
|
2008
|
+
</h1>
|
|
2009
|
+
<p
|
|
2010
|
+
class="text-base text-gray-500 dark:text-slate-400 leading-relaxed"
|
|
2011
|
+
>
|
|
2012
|
+
Explore real-world applications and boilerplate examples built
|
|
2013
|
+
with JunoJS to understand best practices for architecture
|
|
2014
|
+
and service integration.
|
|
2015
|
+
</p>
|
|
2016
|
+
</div>
|
|
2017
|
+
|
|
2018
|
+
<div class="h-px bg-gray-200 dark:bg-slate-800/60"></div>
|
|
2019
|
+
|
|
2020
|
+
<!-- NeoStore Sample -->
|
|
2021
|
+
<div
|
|
2022
|
+
class="group relative bg-[#fafafa] dark:bg-slate-900/50 border border-gray-200 dark:border-slate-800 rounded-3xl overflow-hidden hover:border-blue-500/50 transition-all duration-500 p-8"
|
|
2023
|
+
>
|
|
2024
|
+
<div class="flex flex-col md:flex-row gap-10 items-center">
|
|
2025
|
+
<div class="flex-1 space-y-6">
|
|
2026
|
+
<div
|
|
2027
|
+
class="inline-flex items-center gap-2 px-2.5 py-1 rounded-full bg-blue-500/10 border border-blue-500/20 text-[10px] font-bold text-blue-600 dark:text-blue-400 uppercase tracking-widest"
|
|
2028
|
+
>
|
|
2029
|
+
Full Application
|
|
2030
|
+
</div>
|
|
2031
|
+
<h2
|
|
2032
|
+
id="neostore"
|
|
2033
|
+
class="text-2xl font-black text-gray-900 dark:text-white tracking-tighter"
|
|
2034
|
+
>
|
|
2035
|
+
NeoStore Shopping App
|
|
2036
|
+
</h2>
|
|
2037
|
+
<p
|
|
2038
|
+
class="text-sm text-gray-600 dark:text-slate-400 leading-7"
|
|
2039
|
+
>
|
|
2040
|
+
A high-fidelity tech store demonstration featuring a
|
|
2041
|
+
premium dark aesthetic. Showcases global state management,
|
|
2042
|
+
authenticated HTTP requests, and History-based routing.
|
|
2043
|
+
</p>
|
|
2044
|
+
|
|
2045
|
+
<div class="flex flex-wrap gap-2">
|
|
2046
|
+
<span
|
|
2047
|
+
class="px-2 py-1 bg-white dark:bg-slate-900 border border-gray-200 dark:border-slate-800 rounded text-[10px] font-bold text-gray-500 tracking-wider"
|
|
2048
|
+
>TAILWIND CSS</span
|
|
2049
|
+
>
|
|
2050
|
+
<span
|
|
2051
|
+
class="px-2 py-1 bg-white dark:bg-slate-900 border border-gray-200 dark:border-slate-800 rounded text-[10px] font-bold text-gray-500 tracking-wider"
|
|
2052
|
+
>ROUTING</span
|
|
2053
|
+
>
|
|
2054
|
+
<span
|
|
2055
|
+
class="px-2 py-1 bg-white dark:bg-slate-900 border border-gray-200 dark:border-slate-800 rounded text-[10px] font-bold text-gray-500 tracking-wider"
|
|
2056
|
+
>I18N</span
|
|
2057
|
+
>
|
|
2058
|
+
<span
|
|
2059
|
+
class="px-2 py-1 bg-white dark:bg-slate-900 border border-gray-200 dark:border-slate-800 rounded text-[10px] font-bold text-gray-500 tracking-wider"
|
|
2060
|
+
>HTTP</span
|
|
2061
|
+
>
|
|
2062
|
+
</div>
|
|
2063
|
+
|
|
2064
|
+
<a
|
|
2065
|
+
href="/samples/shopping-app/index.html"
|
|
2066
|
+
target="_blank"
|
|
2067
|
+
class="inline-flex items-center gap-3 px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold rounded-xl shadow-lg shadow-blue-600/20 active:scale-95 transition-all"
|
|
2068
|
+
>
|
|
2069
|
+
Open Live Project
|
|
2070
|
+
<span class="material-symbols-outlined text-sm"
|
|
2071
|
+
>open_in_new</span
|
|
2072
|
+
>
|
|
2073
|
+
</a>
|
|
2074
|
+
</div>
|
|
2075
|
+
<div
|
|
2076
|
+
class="w-full md:w-64 aspect-square rounded-2xl bg-gray-200 dark:bg-slate-800 overflow-hidden border border-gray-200 dark:border-slate-700/50"
|
|
2077
|
+
>
|
|
2078
|
+
<img
|
|
2079
|
+
src="https://images.unsplash.com/photo-1550745165-9bc0b252726f?auto=format&fit=crop&q=80&w=400&h=400"
|
|
2080
|
+
alt="Tech Store"
|
|
2081
|
+
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700 opacity-60"
|
|
2082
|
+
/>
|
|
2083
|
+
</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
</div>
|
|
2086
|
+
|
|
2087
|
+
<!-- More coming soon -->
|
|
2088
|
+
<div
|
|
2089
|
+
class="flex flex-col items-center justify-center p-12 border-2 border-dashed border-gray-200 dark:border-slate-800 rounded-3xl text-center space-y-3"
|
|
2090
|
+
>
|
|
2091
|
+
<p
|
|
2092
|
+
class="text-sm font-semibold text-gray-500 dark:text-slate-400"
|
|
2093
|
+
>
|
|
2094
|
+
More samples coming soon
|
|
2095
|
+
</p>
|
|
2096
|
+
<p class="text-xs text-gray-400 dark:text-slate-600">
|
|
2097
|
+
We are working on Dashboard, Chat, and AI-integrations
|
|
2098
|
+
samples.
|
|
2099
|
+
</p>
|
|
2100
|
+
</div>
|
|
2101
|
+
</article>
|
|
2102
|
+
</if>
|
|
2103
|
+
</div>
|
|
2104
|
+
</main>
|
|
2105
|
+
|
|
2106
|
+
<!-- Right ToC -->
|
|
2107
|
+
<aside class="hidden xl:block w-52 flex-shrink-0 py-14 pr-8">
|
|
2108
|
+
<div class="sticky top-28">
|
|
2109
|
+
<p
|
|
2110
|
+
class="text-[10.5px] font-semibold text-gray-400 dark:text-slate-500 uppercase tracking-widest mb-4"
|
|
2111
|
+
>
|
|
2112
|
+
On This Page
|
|
2113
|
+
</p>
|
|
2114
|
+
<if condition="{activeTab === 'introduction'}">
|
|
2115
|
+
<ul class="space-y-2.5">
|
|
2116
|
+
<li>
|
|
2117
|
+
<a
|
|
2118
|
+
href="#overview"
|
|
2119
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2120
|
+
>Overview</a
|
|
2121
|
+
>
|
|
2122
|
+
</li>
|
|
2123
|
+
<li>
|
|
2124
|
+
<a
|
|
2125
|
+
href="#features"
|
|
2126
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2127
|
+
>Key Features</a
|
|
2128
|
+
>
|
|
2129
|
+
</li>
|
|
2130
|
+
<li>
|
|
2131
|
+
<a
|
|
2132
|
+
href="#architecture"
|
|
2133
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2134
|
+
>Architecture</a
|
|
2135
|
+
>
|
|
2136
|
+
</li>
|
|
2137
|
+
</ul>
|
|
2138
|
+
</if>
|
|
2139
|
+
<if condition="{activeTab === 'installation'}">
|
|
2140
|
+
<ul class="space-y-2.5">
|
|
2141
|
+
<li>
|
|
2142
|
+
<a
|
|
2143
|
+
href="#install"
|
|
2144
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2145
|
+
>Install Package</a
|
|
2146
|
+
>
|
|
2147
|
+
</li>
|
|
2148
|
+
<li>
|
|
2149
|
+
<a
|
|
2150
|
+
href="#tsconfig"
|
|
2151
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2152
|
+
>TypeScript Config</a
|
|
2153
|
+
>
|
|
2154
|
+
</li>
|
|
2155
|
+
<li>
|
|
2156
|
+
<a
|
|
2157
|
+
href="#entry"
|
|
2158
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2159
|
+
>Entry Point</a
|
|
2160
|
+
>
|
|
2161
|
+
</li>
|
|
2162
|
+
</ul>
|
|
2163
|
+
</if>
|
|
2164
|
+
<if condition="{activeTab === 'structure'}">
|
|
2165
|
+
<ul class="space-y-2.5">
|
|
2166
|
+
<li>
|
|
2167
|
+
<a
|
|
2168
|
+
href="#app-structure"
|
|
2169
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2170
|
+
>Project Structure</a
|
|
2171
|
+
>
|
|
2172
|
+
</li>
|
|
2173
|
+
<li>
|
|
2174
|
+
<a
|
|
2175
|
+
href="#module-overview"
|
|
2176
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2177
|
+
>Best Practices</a
|
|
2178
|
+
>
|
|
2179
|
+
</li>
|
|
2180
|
+
</ul>
|
|
2181
|
+
</if>
|
|
2182
|
+
<if condition="{activeTab === 'aidev'}">
|
|
2183
|
+
<ul class="space-y-2.5">
|
|
2184
|
+
<li>
|
|
2185
|
+
<a
|
|
2186
|
+
href="#ai-intro"
|
|
2187
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2188
|
+
>AI-First Dev</a
|
|
2189
|
+
>
|
|
2190
|
+
</li>
|
|
2191
|
+
<li>
|
|
2192
|
+
<a
|
|
2193
|
+
href="#custom-instructions"
|
|
2194
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2195
|
+
>Custom Instructions</a
|
|
2196
|
+
>
|
|
2197
|
+
</li>
|
|
2198
|
+
</ul>
|
|
2199
|
+
</if>
|
|
2200
|
+
<if condition="{activeTab === 'quickstart'}">
|
|
2201
|
+
<ul class="space-y-2.5">
|
|
2202
|
+
<li>
|
|
2203
|
+
<a
|
|
2204
|
+
href="#component"
|
|
2205
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2206
|
+
>Create a Component</a
|
|
2207
|
+
>
|
|
2208
|
+
</li>
|
|
2209
|
+
<li>
|
|
2210
|
+
<a
|
|
2211
|
+
href="#live"
|
|
2212
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2213
|
+
>Live Demo</a
|
|
2214
|
+
>
|
|
2215
|
+
</li>
|
|
2216
|
+
</ul>
|
|
2217
|
+
</if>
|
|
2218
|
+
<if condition="{activeTab === 'forms'}">
|
|
2219
|
+
<ul class="space-y-2.5">
|
|
2220
|
+
<li>
|
|
2221
|
+
<a
|
|
2222
|
+
href="#form-model"
|
|
2223
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2224
|
+
>FormModel</a
|
|
2225
|
+
>
|
|
2226
|
+
</li>
|
|
2227
|
+
<li>
|
|
2228
|
+
<a
|
|
2229
|
+
href="#validators"
|
|
2230
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2231
|
+
>Built-in Validators</a
|
|
2232
|
+
>
|
|
2233
|
+
</li>
|
|
2234
|
+
</ul>
|
|
2235
|
+
</if>
|
|
2236
|
+
<if condition="{activeTab === 'routing'}">
|
|
2237
|
+
<ul class="space-y-2.5">
|
|
2238
|
+
<li>
|
|
2239
|
+
<a
|
|
2240
|
+
href="#routing-how"
|
|
2241
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2242
|
+
>How It Works</a
|
|
2243
|
+
>
|
|
2244
|
+
</li>
|
|
2245
|
+
<li>
|
|
2246
|
+
<a
|
|
2247
|
+
href="#routing-how"
|
|
2248
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2249
|
+
>Registration</a
|
|
2250
|
+
>
|
|
2251
|
+
</li>
|
|
2252
|
+
<li>
|
|
2253
|
+
<a
|
|
2254
|
+
href="#routing-triggers"
|
|
2255
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2256
|
+
>Navigation</a
|
|
2257
|
+
>
|
|
2258
|
+
</li>
|
|
2259
|
+
<li>
|
|
2260
|
+
<a
|
|
2261
|
+
href="#routing-state"
|
|
2262
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2263
|
+
>Component Sync</a
|
|
2264
|
+
>
|
|
2265
|
+
</li>
|
|
2266
|
+
</ul>
|
|
2267
|
+
</if>
|
|
2268
|
+
<if condition="{activeTab === 'reactivity'}">
|
|
2269
|
+
<ul class="space-y-2.5">
|
|
2270
|
+
<li>
|
|
2271
|
+
<a
|
|
2272
|
+
href="#state"
|
|
2273
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2274
|
+
>@State Decorator</a
|
|
2275
|
+
>
|
|
2276
|
+
</li>
|
|
2277
|
+
<li>
|
|
2278
|
+
<a
|
|
2279
|
+
href="#how"
|
|
2280
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2281
|
+
>How It Works</a
|
|
2282
|
+
>
|
|
2283
|
+
</li>
|
|
2284
|
+
<li>
|
|
2285
|
+
<a
|
|
2286
|
+
href="#persist"
|
|
2287
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2288
|
+
>Persistent State</a
|
|
2289
|
+
>
|
|
2290
|
+
</li>
|
|
2291
|
+
</ul>
|
|
2292
|
+
</if>
|
|
2293
|
+
<if condition="{activeTab === 'templates'}">
|
|
2294
|
+
<ul class="space-y-2.5">
|
|
2295
|
+
<li>
|
|
2296
|
+
<a
|
|
2297
|
+
href="#interpolation"
|
|
2298
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2299
|
+
>Interpolation</a
|
|
2300
|
+
>
|
|
2301
|
+
</li>
|
|
2302
|
+
<li>
|
|
2303
|
+
<a
|
|
2304
|
+
href="#conditionals"
|
|
2305
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2306
|
+
>Conditionals</a
|
|
2307
|
+
>
|
|
2308
|
+
</li>
|
|
2309
|
+
<li>
|
|
2310
|
+
<a
|
|
2311
|
+
href="#loops"
|
|
2312
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2313
|
+
>Loops</a
|
|
2314
|
+
>
|
|
2315
|
+
</li>
|
|
2316
|
+
<li>
|
|
2317
|
+
<a
|
|
2318
|
+
href="#events"
|
|
2319
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2320
|
+
>Event Binding</a
|
|
2321
|
+
>
|
|
2322
|
+
</li>
|
|
2323
|
+
</ul>
|
|
2324
|
+
</if>
|
|
2325
|
+
<if condition="{activeTab === 'lifecycle'}">
|
|
2326
|
+
<ul class="space-y-2.5">
|
|
2327
|
+
<li>
|
|
2328
|
+
<a
|
|
2329
|
+
href="#lifecycle"
|
|
2330
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2331
|
+
>Hooks Overview</a
|
|
2332
|
+
>
|
|
2333
|
+
</li>
|
|
2334
|
+
<li>
|
|
2335
|
+
<a
|
|
2336
|
+
href="#example"
|
|
2337
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2338
|
+
>Full Example</a
|
|
2339
|
+
>
|
|
2340
|
+
</li>
|
|
2341
|
+
</ul>
|
|
2342
|
+
</if>
|
|
2343
|
+
<if condition="{activeTab === 'http'}">
|
|
2344
|
+
<ul class="space-y-2.5">
|
|
2345
|
+
<li>
|
|
2346
|
+
<a
|
|
2347
|
+
href="#methods"
|
|
2348
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2349
|
+
>Methods</a
|
|
2350
|
+
>
|
|
2351
|
+
</li>
|
|
2352
|
+
<li>
|
|
2353
|
+
<a
|
|
2354
|
+
href="#config"
|
|
2355
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2356
|
+
>RequestConfig</a
|
|
2357
|
+
>
|
|
2358
|
+
</li>
|
|
2359
|
+
<li>
|
|
2360
|
+
<a
|
|
2361
|
+
href="#interceptors"
|
|
2362
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2363
|
+
>Interceptors</a
|
|
2364
|
+
>
|
|
2365
|
+
</li>
|
|
2366
|
+
</ul>
|
|
2367
|
+
</if>
|
|
2368
|
+
<if condition="{activeTab === 'i18n'}">
|
|
2369
|
+
<ul class="space-y-2.5">
|
|
2370
|
+
<li>
|
|
2371
|
+
<a
|
|
2372
|
+
href="#setup"
|
|
2373
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2374
|
+
>Setup</a
|
|
2375
|
+
>
|
|
2376
|
+
</li>
|
|
2377
|
+
<li>
|
|
2378
|
+
<a
|
|
2379
|
+
href="#translate"
|
|
2380
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2381
|
+
>Translating Keys</a
|
|
2382
|
+
>
|
|
2383
|
+
</li>
|
|
2384
|
+
<li>
|
|
2385
|
+
<a
|
|
2386
|
+
href="#locale-change"
|
|
2387
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2388
|
+
>Reactive Changes</a
|
|
2389
|
+
>
|
|
2390
|
+
</li>
|
|
2391
|
+
</ul>
|
|
2392
|
+
</if>
|
|
2393
|
+
<if condition="{activeTab === 'forms'}">
|
|
2394
|
+
<ul class="space-y-2.5">
|
|
2395
|
+
<li>
|
|
2396
|
+
<a
|
|
2397
|
+
href="#form-model"
|
|
2398
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2399
|
+
>FormModel</a
|
|
2400
|
+
>
|
|
2401
|
+
</li>
|
|
2402
|
+
<li>
|
|
2403
|
+
<a
|
|
2404
|
+
href="#validators"
|
|
2405
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2406
|
+
>Built-in Validators</a
|
|
2407
|
+
>
|
|
2408
|
+
</li>
|
|
2409
|
+
</ul>
|
|
2410
|
+
</if>
|
|
2411
|
+
<if condition="{activeTab === 'decorators'}">
|
|
2412
|
+
<ul class="space-y-2.5">
|
|
2413
|
+
<li>
|
|
2414
|
+
<a
|
|
2415
|
+
href="#class-decorators"
|
|
2416
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2417
|
+
>Class Decorators</a
|
|
2418
|
+
>
|
|
2419
|
+
</li>
|
|
2420
|
+
<li>
|
|
2421
|
+
<a
|
|
2422
|
+
href="#property-decorators"
|
|
2423
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2424
|
+
>Property Decorators</a
|
|
2425
|
+
>
|
|
2426
|
+
</li>
|
|
2427
|
+
</ul>
|
|
2428
|
+
</if>
|
|
2429
|
+
<if condition="{activeTab === 'engine'}">
|
|
2430
|
+
<ul class="space-y-2.5">
|
|
2431
|
+
<li>
|
|
2432
|
+
<a
|
|
2433
|
+
href="#neural-engine"
|
|
2434
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2435
|
+
>NeuralEngine.render</a
|
|
2436
|
+
>
|
|
2437
|
+
</li>
|
|
2438
|
+
<li>
|
|
2439
|
+
<a
|
|
2440
|
+
href="#reconcile"
|
|
2441
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2442
|
+
>reconcile()</a
|
|
2443
|
+
>
|
|
2444
|
+
</li>
|
|
2445
|
+
<li>
|
|
2446
|
+
<a
|
|
2447
|
+
href="#proxy"
|
|
2448
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2449
|
+
>createReactiveProxy</a
|
|
2450
|
+
>
|
|
2451
|
+
</li>
|
|
2452
|
+
<li>
|
|
2453
|
+
<a
|
|
2454
|
+
href="#bootstrap"
|
|
2455
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2456
|
+
>bootstrap()</a
|
|
2457
|
+
>
|
|
2458
|
+
</li>
|
|
2459
|
+
</ul>
|
|
2460
|
+
</if>
|
|
2461
|
+
<if condition="{activeTab === 'samples'}">
|
|
2462
|
+
<ul class="space-y-2.5">
|
|
2463
|
+
<li>
|
|
2464
|
+
<a
|
|
2465
|
+
href="#samples"
|
|
2466
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2467
|
+
>Overview</a
|
|
2468
|
+
>
|
|
2469
|
+
</li>
|
|
2470
|
+
<li>
|
|
2471
|
+
<a
|
|
2472
|
+
href="#neostore"
|
|
2473
|
+
class="text-xs text-gray-400 dark:text-slate-500 hover:text-gray-900 dark:hover:text-slate-200 transition-colors"
|
|
2474
|
+
>NeoStore App</a
|
|
2475
|
+
>
|
|
2476
|
+
</li>
|
|
2477
|
+
</ul>
|
|
2478
|
+
</if>
|
|
2479
|
+
</div>
|
|
2480
|
+
</aside>
|
|
2481
|
+
</div>
|
|
2482
|
+
</div>
|
|
2483
|
+
</div>
|