jails-js 6.9.8 → 6.9.9

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/ai/recipes.md ADDED
@@ -0,0 +1,393 @@
1
+ # Recipes
2
+
3
+ ## Counter
4
+
5
+ Explanation: minimal local state update with delegated events and `html-inner`.
6
+
7
+ ```html
8
+ <app-counter>
9
+ <button data-subtract>-</button>
10
+ <span html-inner="counter">0</span>
11
+ <button data-add>+</button>
12
+ </app-counter>
13
+ ```
14
+
15
+ ```ts
16
+ export default function appCounter({ main, on, state }) {
17
+ main(() => {
18
+ on('click', '[data-add]', add)
19
+ on('click', '[data-subtract]', subtract)
20
+ })
21
+
22
+ const add = () => {
23
+ state.set(s => {
24
+ s.counter += 1
25
+ })
26
+ }
27
+
28
+ const subtract = () => {
29
+ state.set(s => {
30
+ s.counter -= 1
31
+ })
32
+ }
33
+ }
34
+
35
+ export const model = {
36
+ counter: 0
37
+ }
38
+ ```
39
+
40
+ Rendering explanation: `html-inner="counter"` replaces fallback `0` after mount and updates after each `state.set()`.
41
+
42
+ Performance notes: one listener per event type at the component root; no per-button rebinding.
43
+
44
+ Common mistakes: omitting `state` from controller helpers; reading DOM text as source of truth instead of state.
45
+
46
+ ## Loading and Error States
47
+
48
+ ```html
49
+ <app-users>
50
+ <p html-if="loading">Loading</p>
51
+ <p html-if="error">{{ error.message }}</p>
52
+ <ul html-if="!loading && !error">
53
+ <li html-for="user in users">{{ user.name }}</li>
54
+ </ul>
55
+ </app-users>
56
+ ```
57
+
58
+ ```ts
59
+ export default function appUsers({ main, state, dependencies }) {
60
+ const { http } = dependencies
61
+
62
+ main(() => {
63
+ load()
64
+ })
65
+
66
+ const load = () => {
67
+ state.set({ loading: true, error: null })
68
+ http.get('/users')
69
+ .then(users => state.set({ users, loading: false }))
70
+ .catch(error => state.set({ error, loading: false }))
71
+ }
72
+ }
73
+
74
+ export const model = {
75
+ users: [],
76
+ loading: false,
77
+ error: null
78
+ }
79
+ ```
80
+
81
+ Rendering explanation: mutually exclusive `html-if` regions create/remove the visible branch.
82
+
83
+ Performance notes: fetch never runs from the template. The loop only renders when not loading and without error.
84
+
85
+ Common mistakes: storing formatted error strings only; keep the original error if handlers need it.
86
+
87
+ ## Modal
88
+
89
+ ```html
90
+ <app-modal>
91
+ <button data-open>Open</button>
92
+ <section html-if="open" role="dialog" aria-modal="true">
93
+ <h2>{{ title }}</h2>
94
+ <button data-close>Close</button>
95
+ </section>
96
+ </app-modal>
97
+ ```
98
+
99
+ ```ts
100
+ export default function appModal({ main, on, state }) {
101
+ main(() => {
102
+ on('click', '[data-open]', () => state.set({ open: true }))
103
+ on('click', '[data-close]', () => state.set({ open: false }))
104
+ })
105
+ }
106
+
107
+ export const model = {
108
+ open: false,
109
+ title: 'Dialog'
110
+ }
111
+ ```
112
+
113
+ Rendering explanation: modal DOM is created only when `open` is truthy and removed when false.
114
+
115
+ Performance notes: fine for small dialogs. For heavy dialog content that should preserve input state, use CSS visibility or place form controls under `html-static` as appropriate.
116
+
117
+ Common mistakes: expecting internal form values to persist across `html-if` removal.
118
+
119
+ ## Tabs
120
+
121
+ ```html
122
+ <app-tabs>
123
+ <nav>
124
+ <button data-tab="profile" html-class="activeTab === 'profile' ? 'active' : ''">Profile</button>
125
+ <button data-tab="billing" html-class="activeTab === 'billing' ? 'active' : ''">Billing</button>
126
+ </nav>
127
+ <section html-if="activeTab === 'profile'">Profile content</section>
128
+ <section html-if="activeTab === 'billing'">Billing content</section>
129
+ </app-tabs>
130
+ ```
131
+
132
+ ```ts
133
+ export default function appTabs({ main, on, state }) {
134
+ main(() => {
135
+ on('click', '[data-tab]', select)
136
+ })
137
+
138
+ const select = e => {
139
+ state.set({ activeTab: e.delegateTarget.dataset.tab })
140
+ }
141
+ }
142
+
143
+ export const model = {
144
+ activeTab: 'profile'
145
+ }
146
+ ```
147
+
148
+ Rendering explanation: button classes and panel branches derive from one state property.
149
+
150
+ Performance notes: no listener per tab panel. Avoid rendering large inactive panels repeatedly.
151
+
152
+ Common mistakes: reading `event.target.dataset.tab`; nested elements can make `event.target` wrong. Use `delegateTarget`.
153
+
154
+ ## Accordion
155
+
156
+ ```html
157
+ <app-accordion>
158
+ <article html-for="item in items">
159
+ <button data-toggle html-data-index="$index">{{ item.title }}</button>
160
+ <div html-if="openIndex === $index">{{ item.body }}</div>
161
+ </article>
162
+ </app-accordion>
163
+ ```
164
+
165
+ ```ts
166
+ export default function appAccordion({ main, on, state }) {
167
+ main(() => {
168
+ on('click', '[data-toggle]', toggle)
169
+ })
170
+
171
+ const toggle = e => {
172
+ const index = Number(e.delegateTarget.dataset.index)
173
+ state.set(s => {
174
+ s.openIndex = s.openIndex === index ? -1 : index
175
+ })
176
+ }
177
+ }
178
+
179
+ export const model = {
180
+ openIndex: -1,
181
+ items: []
182
+ }
183
+ ```
184
+
185
+ Rendering explanation: each repeated item receives `$index`; only matching content branch renders.
186
+
187
+ Performance notes: for long accordions, do not put heavy widgets inside every body unless guarded or static.
188
+
189
+ Common mistakes: not converting dataset strings to numbers.
190
+
191
+ ## Fetch API With Service Dependency
192
+
193
+ ```ts
194
+ // main.ts
195
+ register('app-posts', appPosts, { http })
196
+ ```
197
+
198
+ ```ts
199
+ export default function appPosts({ main, state, dependencies }) {
200
+ const { http } = dependencies
201
+
202
+ main(() => {
203
+ load()
204
+ })
205
+
206
+ const load = async () => {
207
+ state.set({ loading: true })
208
+ const posts = await http.get('/posts')
209
+ state.set({ posts, loading: false })
210
+ }
211
+ }
212
+
213
+ export const model = {
214
+ posts: [],
215
+ loading: false
216
+ }
217
+ ```
218
+
219
+ Rendering explanation: state changes drive any `html-if` and `html-for` bound to `loading` and `posts`.
220
+
221
+ Performance notes: dependency injection keeps generic components free of unused service code.
222
+
223
+ Common mistakes: importing app services inside reusable library components.
224
+
225
+ ## Optimistic Updates
226
+
227
+ ```ts
228
+ export default function appTodos({ main, on, state, dependencies }) {
229
+ const { todosApi } = dependencies
230
+
231
+ main(() => {
232
+ on('click', '[data-toggle]', toggle)
233
+ })
234
+
235
+ const toggle = async e => {
236
+ const id = e.delegateTarget.dataset.id
237
+ const previous = state.get().todos
238
+ const todos = previous.map(todo =>
239
+ todo.id === id ? { ...todo, done: !todo.done } : todo
240
+ )
241
+ await state.set({ todos })
242
+
243
+ try {
244
+ await todosApi.toggle(id)
245
+ } catch (error) {
246
+ state.set({ todos: previous, error })
247
+ }
248
+ }
249
+ }
250
+ ```
251
+
252
+ Rendering explanation: UI updates before the API resolves; rollback restores previous state on failure.
253
+
254
+ Performance notes: clone changed collections instead of mutating external references.
255
+
256
+ Common mistakes: losing the previous state snapshot before the optimistic write.
257
+
258
+ ## Nested Templates
259
+
260
+ ```html
261
+ <app-page>
262
+ <h1>Server-rendered heading</h1>
263
+ <template>
264
+ <section html-if="user">
265
+ Welcome, {{ user.name }}
266
+ </section>
267
+ </template>
268
+ </app-page>
269
+ ```
270
+
271
+ Rendering explanation: native `<template>` prevents unresolved mustache markers from flashing before data exists.
272
+
273
+ Performance notes: use this for correctness of first paint; it is not a substitute for limiting render work.
274
+
275
+ Common mistakes: hiding content that should be visible at first paint.
276
+
277
+ ## Debounced Input
278
+
279
+ ```html
280
+ <app-search>
281
+ <input type="search" html-static>
282
+ <p html-if="loading">Searching</p>
283
+ <ul>
284
+ <li html-for="item in results">{{ item.label }}</li>
285
+ </ul>
286
+ </app-search>
287
+ ```
288
+
289
+ ```ts
290
+ export default function appSearch({ main, on, state, dependencies }) {
291
+ const { search } = dependencies
292
+ let timer
293
+
294
+ main(() => {
295
+ on('input', 'input[type=search]', schedule)
296
+ })
297
+
298
+ const schedule = e => {
299
+ const query = e.delegateTarget.value
300
+ clearTimeout(timer)
301
+ timer = setTimeout(() => run(query), 250)
302
+ }
303
+
304
+ const run = async query => {
305
+ state.set({ loading: true })
306
+ const results = await search(query)
307
+ state.set({ results, loading: false })
308
+ }
309
+ }
310
+
311
+ export const model = {
312
+ results: [],
313
+ loading: false
314
+ }
315
+ ```
316
+
317
+ Rendering explanation: input value is browser-owned through `html-static`; search results are Jails-owned state.
318
+
319
+ Performance notes: debounce avoids one network request and render per keystroke.
320
+
321
+ Common mistakes: binding input value to state when no other render logic needs it.
322
+
323
+ ## Intersection Observer Lazy Rendering
324
+
325
+ ```ts
326
+ export default function lazyPanel({ main, elm, state, unmount }) {
327
+ let observer
328
+
329
+ main(() => {
330
+ observer = new IntersectionObserver(entries => {
331
+ if (entries.some(entry => entry.isIntersecting)) {
332
+ state.set({ visible: true })
333
+ observer.disconnect()
334
+ }
335
+ })
336
+ observer.observe(elm)
337
+ })
338
+
339
+ unmount(() => {
340
+ if (observer) observer.disconnect()
341
+ })
342
+ }
343
+
344
+ export const model = {
345
+ visible: false
346
+ }
347
+ ```
348
+
349
+ ```html
350
+ <lazy-panel>
351
+ <section html-if="visible">Expensive content</section>
352
+ </lazy-panel>
353
+ ```
354
+
355
+ Rendering explanation: expensive content is not created until the component enters the viewport.
356
+
357
+ Performance notes: cleanup prevents observers from retaining detached elements.
358
+
359
+ Common mistakes: forgetting `unmount()`.
360
+
361
+ ## Virtual List Boundary
362
+
363
+ ```html
364
+ <app-virtual-list>
365
+ <div class="virtual-list" html-static></div>
366
+ <p>Total: {{ total }}</p>
367
+ </app-virtual-list>
368
+ ```
369
+
370
+ ```ts
371
+ export default function appVirtualList({ main, elm, state, dependencies }) {
372
+ const { createVirtualList } = dependencies
373
+ const target = elm.querySelector('.virtual-list')
374
+ let list
375
+
376
+ main(() => {
377
+ list = createVirtualList(target, {
378
+ onCountChange: total => state.set({ total })
379
+ })
380
+ })
381
+ }
382
+
383
+ export const model = {
384
+ total: 0
385
+ }
386
+ ```
387
+
388
+ Rendering explanation: the virtual list library owns its DOM; Jails renders surrounding counters/state.
389
+
390
+ Performance notes: `html-static` prevents Jails diffing a large, imperatively managed list.
391
+
392
+ Common mistakes: putting `html-for` inside a virtualized area managed by another library.
393
+
package/package.json CHANGED
@@ -1,24 +1,39 @@
1
1
  {
2
2
  "name": "jails-js",
3
- "version": "6.9.8",
3
+ "version": "6.9.9",
4
4
  "description": "Jails - Elegant and Minimalistic Javascript Application Library",
5
5
  "module": "./dist/index.js",
6
6
  "main": "./dist/jails.js",
7
+ "types": "./types.d.ts",
7
8
  "exports": {
9
+
8
10
  "./html": {
9
11
  "types": "./types.d.ts",
10
12
  "import": "./html.js",
11
13
  "require": "./html.js"
12
14
  },
15
+
13
16
  ".": {
14
17
  "types": "./types.d.ts",
15
18
  "import": "./dist/index.js",
16
19
  "require": "./dist/jails.js"
17
20
  },
21
+
18
22
  "./internal": {
19
23
  "types": "./types.d.ts",
20
24
  "default": "./types.d.ts"
21
- }
25
+ },
26
+
27
+ "./ai/*": "./ai-docs/*",
28
+ "./schemas/*": "./ai-docs/schemas/*",
29
+ "./canonical/*": "./ai-docs/canonical/*"
30
+ },
31
+
32
+ "ai": {
33
+ "entry": "./ai-docs/LLM.md",
34
+ "schemas": "./ai-docs/schemas",
35
+ "examples": "./ai-docs/canonical",
36
+ "training": "./ai-docs/training/training.jsonl"
22
37
  },
23
38
  "scripts": {
24
39
  "dev": "vite build --watch",
@@ -27,28 +42,35 @@
27
42
  "publish-beta": "yarn build && npm version prerelease --preid=beta && npm publish --tag beta",
28
43
  "test": "echo \"Error: no test specified\" && exit 1"
29
44
  },
45
+
30
46
  "repository": {
31
47
  "type": "git",
32
48
  "url": "git+https://github.com/jails-org/Jails.git"
33
49
  },
50
+
34
51
  "keywords": [
35
52
  "Jails",
36
53
  "Javascript",
37
54
  "Component",
38
55
  "Micro-Library"
39
56
  ],
57
+
40
58
  "author": "javiani",
41
59
  "license": "MIT",
60
+
42
61
  "bugs": {
43
62
  "url": "https://github.com/jails-org/Jails/issues"
44
63
  },
64
+
45
65
  "homepage": "https://github.com/jails-org/Jails",
66
+
46
67
  "devDependencies": {
47
68
  "@types/node": "^22.10.10",
48
69
  "terser": "^5.37.0",
49
70
  "typescript": "^5.7.3",
50
71
  "vite": "^6.0.11"
51
72
  },
73
+
52
74
  "dependencies": {
53
75
  "idiomorph": "^0.7.4"
54
76
  }
package/readme.md CHANGED
@@ -1,13 +1,16 @@
1
- <p align="center">
2
- <img width="200" src="https://github.com/user-attachments/assets/8b4dbfb9-f05e-4b83-8d42-8847038a97e2" alt="Jails" />
3
- </p>
4
1
 
5
- <h1 align="center">Jails</h1>
6
2
 
7
- <h3 align="center">An Elegant and Minimalistic<br /> Web Components Micro-Framework</h3>
3
+ <p align="center" height="100">
4
+ <img width="250" alt="Jails - A minimalistic Micro Framework" src="https://github.com/user-attachments/assets/5e2eee1a-5f8f-4eed-abba-7422a5258245" />
5
+
6
+ </p>
7
+
8
+ <h1 align="center">An Elegant and Minimalistic<br /> Micro-Framework</h3>
8
9
 
9
10
  <div align="center">
10
- <table align="center" border="0">
11
+ <br />
12
+ <br />
13
+ <table width="100%" align="center" border="0">
11
14
  <tr><td align="center">🏝 Built for <br/><a href="https://www.patterns.dev/posts/islands-architecture/" target="_blank">Island Architecture</a></td</tr>
12
15
  <td align="center">ƛ Inspired by <br/><a href="https://guide.elm-lang.org/architecture/" target="_blank">Elm Architecture</a></td>
13
16
  <td align="center">🔗 Ready for <br/><a href="https://htmx.org/essays/hypermedia-driven-applications" target="_blank">hypermedia applications</a></td></tr>
@@ -28,9 +31,19 @@
28
31
  The JavaScript ecosystem, including browsers and tools, has undergone significant evolution over the years. However, it's evident that many of the complexities introduced by frameworks today may not be essential for the majority of web applications. The prevailing trend in modern app development seems to prioritize expertise in frameworks over mastery of the language itself, often leading to a sense of being confined within the framework ecosystem.
29
32
 
30
33
  Jails was designed to be:
31
- - **Decoupled** - Backend-agnostic, our solution seamlessly integrates with any backend framework or programming language. It refrains from importing styles and ensures a clean demarcation by avoiding mixing HTML within the codebase.
32
- - **Lightweight** - Lightweight, weighing in at just around 5kb when gzipped. It enhances your application's performance by progressively lightening the load, separating HTML from your JavaScript code. This practice results in smaller bundles and a more streamlined application.
33
- - **Interoperable** - It seamlessly integrates and functions alongside any other vanilla UI or behavioral libraries. You no longer need to wait for a specific Jails Chart.js library to be available; simply integrate it into your application hassle-free.
34
+
35
+ <table width="100%" align="center" border="0">
36
+ <tr>
37
+ <th>🧩 Decoupled</th>
38
+ <th>⚡️ Lightweight</th>
39
+ <th>📦 Interoperable</th>
40
+ </tr>
41
+ <tr>
42
+ <td align="center">Backend-agnostic, our solution seamlessly integrates with any backend framework or back-end programming language.</td>
43
+ <td align="center">Lightweight, just around 5kb when gzipped. It enhances your application's performance by progressively, separating HTML from your JavaScript code. </td>
44
+ <td align="center">It seamlessly integrates and functions alongside any other vanilla UI or behavioral libraries. </td>
45
+ </tr>
46
+ </table>
34
47
 
35
48
  <br clear="all" />
36
49
  <br />
package/tsconfig.json CHANGED
@@ -2,7 +2,6 @@
2
2
  "compilerOptions": {
3
3
  "declaration": true,
4
4
  "emitDeclarationOnly": true,
5
- "outDir": "dist",
6
5
  /* Visit https://aka.ms/tsconfig to read more about this file */
7
6
 
8
7
  /* Projects */
package/types.d.ts CHANGED
@@ -9,7 +9,7 @@ export declare const templateConfig: (options: any) => void;
9
9
  export declare const register: (name: string, module: any, dependencies?: any) => void
10
10
  export declare const start: (target?: HTMLElement) => void;
11
11
  export declare const subscribe:( subject: string, callback: (data:any) => void ) => Function
12
- export declare const publish: ( subject: string, data :any ) => void
12
+ export declare const publish: ( subject: string, data ?:any ) => void
13
13
 
14
14
  export type Component = {
15
15
 
@@ -24,11 +24,11 @@ export type Component = {
24
24
  getRaw() : any
25
25
  }
26
26
 
27
- effect( callback: ( state: any ) => Promise<any> | void )
27
+ effect( callback: ( state: any ) => Promise<any> | void ): void
28
28
 
29
29
  main( mainArgs: ( t: Component ) => void ): void
30
30
 
31
- publish( name: string, value: any ) : void
31
+ publish( name: string, value?: any ) : void
32
32
 
33
33
  subscribe( name: string, value: Function ) : Function
34
34