jails-js 6.9.7 → 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/anti-patterns.md +102 -0
- package/ai/api.md +388 -0
- package/ai/architecture.md +79 -0
- package/ai/best-practices.md +183 -0
- package/ai/concepts.md +228 -0
- package/ai/directives.md +556 -0
- package/ai/examples.md +199 -0
- package/ai/faq.md +70 -0
- package/ai/glossary.md +28 -0
- package/ai/json/api.json +190 -0
- package/ai/json/directives.json +107 -0
- package/ai/json/patterns.json +69 -0
- package/ai/json/recipes.json +96 -0
- package/ai/llms.txt +96 -0
- package/ai/overview.md +45 -0
- package/ai/patterns.md +182 -0
- package/ai/recipes.md +393 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/jails.js +1 -1
- package/dist/jails.js.map +1 -1
- package/package.json +24 -2
- package/readme.md +22 -9
- package/src/template-system.ts +2 -1
- package/tsconfig.json +0 -1
- package/types.d.ts +3 -3
package/ai/examples.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Canonical Examples
|
|
2
|
+
|
|
3
|
+
## Register and Start
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { register, start } from 'jails-js'
|
|
7
|
+
import * as helloWorld from './components/hello-world'
|
|
8
|
+
|
|
9
|
+
register('hello-world', helloWorld)
|
|
10
|
+
start()
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Counter Markup
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<hello-world>
|
|
17
|
+
<h1>Hello World!</h1>
|
|
18
|
+
<p>A simple Counter</p>
|
|
19
|
+
<button class="btn add">+</button>
|
|
20
|
+
<span html-inner="counter">0</span>
|
|
21
|
+
<button class="btn subtract">-</button>
|
|
22
|
+
</hello-world>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Counter Component
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
export default function helloWorld({ main, on, state }) {
|
|
29
|
+
main(() => {
|
|
30
|
+
on('click', 'button.add', add)
|
|
31
|
+
on('click', 'button.subtract', subtract)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const add = () => {
|
|
35
|
+
state.set(s => {
|
|
36
|
+
s.counter += 1
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const subtract = () => {
|
|
41
|
+
state.set(s => {
|
|
42
|
+
s.counter -= 1
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const model = {
|
|
48
|
+
counter: 0
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Conditional Rendering
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<my-component>
|
|
56
|
+
<div html-if="show">Hello, {{ name }}</div>
|
|
57
|
+
<div html-if="!show">I'm hidden!</div>
|
|
58
|
+
</my-component>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
state.set({ name: 'Clark Kent', show: true })
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Loop Rendering
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<my-component>
|
|
69
|
+
<ul>
|
|
70
|
+
<li html-for="item in list">
|
|
71
|
+
<span html-if="item.show">{{ item.name }} {{ $index }}</span>
|
|
72
|
+
</li>
|
|
73
|
+
</ul>
|
|
74
|
+
</my-component>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Attribute Binding
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<my-component html-class="loading ? 'is-loading' : ''">
|
|
81
|
+
<img html-src="imageUrl" alt="">
|
|
82
|
+
</my-component>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Static Third-Party Region
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<app-swiper>
|
|
89
|
+
<input type="number" value="1" min="1" max="9" html-static>
|
|
90
|
+
<p>Chosen page: {{ page }}</p>
|
|
91
|
+
<div class="swiper mySwiper" html-static>
|
|
92
|
+
<div class="swiper-wrapper">
|
|
93
|
+
<div class="swiper-slide">Slide 1</div>
|
|
94
|
+
<div class="swiper-slide">Slide 2</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</app-swiper>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import Swiper from 'swiper'
|
|
102
|
+
|
|
103
|
+
export default function appSwiper({ main, on, elm, state }) {
|
|
104
|
+
const wrapper = elm.querySelector('.swiper')
|
|
105
|
+
const swiper = new Swiper(wrapper)
|
|
106
|
+
|
|
107
|
+
main(() => {
|
|
108
|
+
on('input', 'input[type=number]', goTo)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const goTo = e => {
|
|
112
|
+
const page = Number(e.target.value) || 1
|
|
113
|
+
swiper.slideTo(page - 1)
|
|
114
|
+
state.set({ page })
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const model = {
|
|
119
|
+
page: 1
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Dependency Injection
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { register } from 'jails-js'
|
|
127
|
+
import http from './shared/http'
|
|
128
|
+
import * as myComponent from './components/my-component'
|
|
129
|
+
|
|
130
|
+
register('my-component', myComponent, { http })
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
export default function myComponent({ main, dependencies }) {
|
|
135
|
+
const { http } = dependencies
|
|
136
|
+
|
|
137
|
+
main(() => {
|
|
138
|
+
// use http
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## View Function
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
export const view = state => ({
|
|
147
|
+
...state,
|
|
148
|
+
biggerThen10: state.counter > 10 ? 'bigger' : ''
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```html
|
|
153
|
+
<div html-class="biggerThen10">{{ counter }}</div>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Embedded Template
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { html, attributes } from 'jails-js/html'
|
|
160
|
+
|
|
161
|
+
export const template = ({ children }) => html`
|
|
162
|
+
${children}
|
|
163
|
+
<div>{{ counter }}</div>
|
|
164
|
+
<button data-add ${attributes({ id: 'my-button-id' })}>+</button>
|
|
165
|
+
`
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Global Pub/Sub
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
export default function producer({ main, on, publish }) {
|
|
172
|
+
main(() => {
|
|
173
|
+
on('click', 'button', doSomething)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const doSomething = () => {
|
|
177
|
+
fetch('/service')
|
|
178
|
+
.then(response => response.json())
|
|
179
|
+
.then(data => publish('my-component:fetched', data))
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
export default function consumer({ main, subscribe, unmount }) {
|
|
186
|
+
let unsubscribe
|
|
187
|
+
|
|
188
|
+
main(() => {
|
|
189
|
+
unsubscribe = subscribe('my-component:fetched', data => {
|
|
190
|
+
console.log(data)
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
unmount(() => {
|
|
195
|
+
if (unsubscribe) unsubscribe()
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
package/ai/faq.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# FAQ
|
|
2
|
+
|
|
3
|
+
## What is Jails?
|
|
4
|
+
|
|
5
|
+
Jails is a JavaScript framework for enhancing custom elements in existing HTML. Components are behavior modules attached to DOM islands.
|
|
6
|
+
|
|
7
|
+
## Does Jails require client-side rendering for the whole page?
|
|
8
|
+
|
|
9
|
+
No. The documented model favors SSR, SSG, CMS, or static HTML for first paint, then Jails enhances selected components.
|
|
10
|
+
|
|
11
|
+
## How do updates happen?
|
|
12
|
+
|
|
13
|
+
Use `state.set()`. Jails updates directive-managed DOM and resolves the returned Promise after the DOM update cycle.
|
|
14
|
+
|
|
15
|
+
## Is `state.set()` synchronous?
|
|
16
|
+
|
|
17
|
+
Do not rely on synchronous DOM updates. Use `state.set(...).then(...)` for post-render work.
|
|
18
|
+
|
|
19
|
+
## How does event delegation work?
|
|
20
|
+
|
|
21
|
+
`on(event, selector, callback)` attaches listening behavior to the component root and matches child elements by selector. Use `event.delegateTarget` to access the matched element.
|
|
22
|
+
|
|
23
|
+
## When should I use `emit()`?
|
|
24
|
+
|
|
25
|
+
Use `emit()` for child-to-parent or local DOM-tree communication. It dispatches bubbling custom events.
|
|
26
|
+
|
|
27
|
+
## When should I use `publish()` and `subscribe()`?
|
|
28
|
+
|
|
29
|
+
Use global pub/sub for components in different DOM trees where bubbling DOM events do not provide a natural path.
|
|
30
|
+
|
|
31
|
+
## How should stores be structured?
|
|
32
|
+
|
|
33
|
+
Use component-local state as the default. Parent components may pass props down. Use global pub/sub for messages, not as a hidden global state replacement unless the application deliberately builds that layer.
|
|
34
|
+
|
|
35
|
+
## How do directives interact?
|
|
36
|
+
|
|
37
|
+
Nested directives evaluate inside the scope of their parent. `html-for` adds item variables such as the item alias and `$index`; nested `html-if` can conditionally render content per item. `html-static` stops updates for its node and children.
|
|
38
|
+
|
|
39
|
+
## How does `html-if` affect DOM state?
|
|
40
|
+
|
|
41
|
+
Falsy conditions remove the node from rendered output according to docs examples. Treat state inside that subtree as discarded when removed.
|
|
42
|
+
|
|
43
|
+
## How does `html-for` preserve items?
|
|
44
|
+
|
|
45
|
+
The docs do not specify keyed reconciliation. Assume repeated output is based on iterable order. Avoid relying on per-item DOM preservation unless verified against runtime source.
|
|
46
|
+
|
|
47
|
+
## Why use `html-static`?
|
|
48
|
+
|
|
49
|
+
Use it when a subtree is immutable, browser-owned, or mutated by an external library. Jails skips diffing the node and children.
|
|
50
|
+
|
|
51
|
+
## What are boolean `html-*` attributes?
|
|
52
|
+
|
|
53
|
+
Attributes such as `selected`, `checked`, `readonly`, `disabled`, and `autoplay` are present for truthy expressions and removed for falsy expressions.
|
|
54
|
+
|
|
55
|
+
## What is the difference between `html-inner` and mustache?
|
|
56
|
+
|
|
57
|
+
Both output expression values. `html-inner` targets a specific element and allows fallback content before JavaScript mounts.
|
|
58
|
+
|
|
59
|
+
## How do I avoid mustache placeholders flashing before mount?
|
|
60
|
+
|
|
61
|
+
Use server-rendered fallback content with `html-inner`, or place data-dependent fragments inside native `<template>`.
|
|
62
|
+
|
|
63
|
+
## How do reusable components receive services?
|
|
64
|
+
|
|
65
|
+
Pass services through `register(name, module, dependencies)` and read them from the component `dependencies` helper.
|
|
66
|
+
|
|
67
|
+
## How should cleanup work?
|
|
68
|
+
|
|
69
|
+
Register cleanup with `unmount()` for subscriptions, timers, observers, and third-party library instances.
|
|
70
|
+
|
package/ai/glossary.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Glossary
|
|
2
|
+
|
|
3
|
+
- Component: a JavaScript module attached to each matching custom element instance.
|
|
4
|
+
- Controller: the default export function of a component module.
|
|
5
|
+
- Model: required export that initializes component state.
|
|
6
|
+
- View: optional export that maps state to render-only values.
|
|
7
|
+
- Template: optional export that returns embedded HTML for a component.
|
|
8
|
+
- Directive: an HTML attribute interpreted by Jails, such as `html-if` or `html-for`.
|
|
9
|
+
- Mustache: default interpolation syntax `{{ expression }}`.
|
|
10
|
+
- Render scope: object used to evaluate directives and mustache expressions, normally state transformed by `view`.
|
|
11
|
+
- State: component-local mutable data managed by the `state` helper.
|
|
12
|
+
- `state.set`: method that updates state and schedules DOM updates.
|
|
13
|
+
- Next tick: work performed after `state.set()` resolves its Promise.
|
|
14
|
+
- Event delegation: listening at the component root and matching child selectors.
|
|
15
|
+
- `delegateTarget`: Jails-added event property containing the matched delegated element.
|
|
16
|
+
- Static boundary: a node marked `html-static` whose subtree is skipped by Jails updates.
|
|
17
|
+
- Logic featured component: component that supplies behavior for externally provided HTML.
|
|
18
|
+
- Fully featured component: component that includes embedded HTML via `template`.
|
|
19
|
+
- Dependency injection: passing dependencies through `register()` to the component `dependencies` helper.
|
|
20
|
+
- Pub/sub: global publish/subscribe communication for components in separate DOM trees.
|
|
21
|
+
- `emit`: helper for bubbling DOM CustomEvents upward.
|
|
22
|
+
- `trigger`: helper for programmatically dispatching events.
|
|
23
|
+
- `effect`: lifecycle helper for reacting to parent prop updates.
|
|
24
|
+
- `unmount`: lifecycle helper for cleanup when the component element is detached.
|
|
25
|
+
- SSR: server-side rendering. Jails can enhance SSR HTML.
|
|
26
|
+
- SSG: static site generation. Jails can enhance static HTML.
|
|
27
|
+
- Island: a focused interactive region inside a mostly static page.
|
|
28
|
+
|
package/ai/json/api.json
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "register",
|
|
4
|
+
"signature": "register(name: string, module: Module, dependencies?: object): void",
|
|
5
|
+
"description": "Registers a component module for a custom element name.",
|
|
6
|
+
"parameters": ["name: custom element tag", "module: component module namespace", "dependencies: optional injected dependencies"],
|
|
7
|
+
"returnValue": "Not documented.",
|
|
8
|
+
"sideEffects": ["Stores component registration."],
|
|
9
|
+
"lifecycleImplications": "Call before start() for components that should bootstrap in that scan.",
|
|
10
|
+
"examples": ["register('my-component', myComponent, { http })"],
|
|
11
|
+
"relatedApis": ["start", "dependencies"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "start",
|
|
15
|
+
"signature": "start(target?: HTMLElement): void",
|
|
16
|
+
"description": "Scans a DOM target for registered components and bootstraps them.",
|
|
17
|
+
"parameters": ["target: optional scan root; defaults to document.body"],
|
|
18
|
+
"returnValue": "Not documented.",
|
|
19
|
+
"sideEffects": ["Creates component instances.", "Runs component bootstrap."],
|
|
20
|
+
"lifecycleImplications": "Can be called multiple times but should not be called unnecessarily.",
|
|
21
|
+
"examples": ["register('hello-world', helloWorld); start()"],
|
|
22
|
+
"relatedApis": ["register"]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "templateConfig",
|
|
26
|
+
"signature": "templateConfig({ tags: [string, string] }): void",
|
|
27
|
+
"description": "Changes global mustache delimiters.",
|
|
28
|
+
"parameters": ["tags: opening and closing delimiter strings"],
|
|
29
|
+
"returnValue": "Not documented.",
|
|
30
|
+
"sideEffects": ["Changes template parser configuration."],
|
|
31
|
+
"lifecycleImplications": "Call before templates are processed.",
|
|
32
|
+
"examples": ["jails.templateConfig({ tags: ['@{', '}'] })"],
|
|
33
|
+
"relatedApis": ["mustache"]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "publish",
|
|
37
|
+
"signature": "publish(name: string, data?: any): void",
|
|
38
|
+
"description": "Publishes a global pub/sub event.",
|
|
39
|
+
"parameters": ["name: event name", "data: optional payload"],
|
|
40
|
+
"returnValue": "Not documented.",
|
|
41
|
+
"sideEffects": ["Invokes global subscribers."],
|
|
42
|
+
"lifecycleImplications": "Use for cross-tree communication.",
|
|
43
|
+
"examples": ["publish('my-component:fetched', data)"],
|
|
44
|
+
"relatedApis": ["subscribe"]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "subscribe",
|
|
48
|
+
"signature": "subscribe(name: string, fn: Function): Function",
|
|
49
|
+
"description": "Subscribes to a global pub/sub event.",
|
|
50
|
+
"parameters": ["name: event name", "fn: callback"],
|
|
51
|
+
"returnValue": "Unsubscribe function.",
|
|
52
|
+
"sideEffects": ["Registers global listener."],
|
|
53
|
+
"lifecycleImplications": "Call unsubscribe in unmount() when tied to component lifetime.",
|
|
54
|
+
"examples": ["const unsubscribe = subscribe('my-component:fetched', handler)"],
|
|
55
|
+
"relatedApis": ["publish", "unmount"]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "html",
|
|
59
|
+
"signature": "html(strings: TemplateStringsArray, ...values: any[]): string",
|
|
60
|
+
"description": "Template string helper for component templates.",
|
|
61
|
+
"parameters": ["template string parts", "interpolated values"],
|
|
62
|
+
"returnValue": "HTML string.",
|
|
63
|
+
"sideEffects": [],
|
|
64
|
+
"lifecycleImplications": "Used by template exports.",
|
|
65
|
+
"examples": ["html`<section>${children}</section>`"],
|
|
66
|
+
"relatedApis": ["template", "attributes"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "attributes",
|
|
70
|
+
"signature": "attributes(values: Record<string, any>): string",
|
|
71
|
+
"description": "Serializes attributes for template strings.",
|
|
72
|
+
"parameters": ["values: object of attributes"],
|
|
73
|
+
"returnValue": "Attribute string.",
|
|
74
|
+
"sideEffects": [],
|
|
75
|
+
"lifecycleImplications": "Used when constructing template HTML.",
|
|
76
|
+
"examples": ["attributes({ id: 'my-button-id' })"],
|
|
77
|
+
"relatedApis": ["html", "template"]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "main",
|
|
81
|
+
"signature": "main(fn: Function): void",
|
|
82
|
+
"description": "Registers mount-time component entrypoint.",
|
|
83
|
+
"parameters": ["fn: callback"],
|
|
84
|
+
"returnValue": "void",
|
|
85
|
+
"sideEffects": ["Runs callback after component mount."],
|
|
86
|
+
"lifecycleImplications": "Use for event registration and initial actions.",
|
|
87
|
+
"examples": ["main(() => { on('click', '[data-add]', add) })"],
|
|
88
|
+
"relatedApis": ["on"]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "state.set",
|
|
92
|
+
"signature": "state.set(newprops: object): Promise<object> | state.set((currentState: object) => void): Promise<object>",
|
|
93
|
+
"description": "Updates component-local state and schedules DOM update.",
|
|
94
|
+
"parameters": ["newprops object or mutator function"],
|
|
95
|
+
"returnValue": "Promise resolving after DOM update.",
|
|
96
|
+
"sideEffects": ["Mutates component state.", "Updates directive-managed DOM."],
|
|
97
|
+
"lifecycleImplications": "Use returned Promise for next-tick work.",
|
|
98
|
+
"examples": ["state.set({ isVisible: false })", "state.set(s => { s.counter += 1 })"],
|
|
99
|
+
"relatedApis": ["state.get", "view"]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "state.get",
|
|
103
|
+
"signature": "state.get(): object",
|
|
104
|
+
"description": "Returns current component state.",
|
|
105
|
+
"parameters": [],
|
|
106
|
+
"returnValue": "Current state object.",
|
|
107
|
+
"sideEffects": [],
|
|
108
|
+
"lifecycleImplications": "Use for snapshots before optimistic updates.",
|
|
109
|
+
"examples": ["const current = state.get()"],
|
|
110
|
+
"relatedApis": ["state.set"]
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "state.protected",
|
|
114
|
+
"signature": "state.protected(props: Array<string>): void",
|
|
115
|
+
"description": "Prevents selected child props from being overwritten by parent updates.",
|
|
116
|
+
"parameters": ["props: property names"],
|
|
117
|
+
"returnValue": "void",
|
|
118
|
+
"sideEffects": ["Changes parent-prop update behavior for listed props."],
|
|
119
|
+
"lifecycleImplications": "Call during component setup before relevant parent updates.",
|
|
120
|
+
"examples": ["state.protected(['counter'])"],
|
|
121
|
+
"relatedApis": ["effect"]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "on",
|
|
125
|
+
"signature": "on(event: string, cssSelector: string, callback: Function): void",
|
|
126
|
+
"description": "Registers component-scoped delegated event or attribute-change listener.",
|
|
127
|
+
"parameters": ["event or attribute pattern", "cssSelector", "callback"],
|
|
128
|
+
"returnValue": "void",
|
|
129
|
+
"sideEffects": ["Attaches delegated listener at component boundary."],
|
|
130
|
+
"lifecycleImplications": "Register inside main().",
|
|
131
|
+
"examples": ["on('click', '[data-add]', add)", "on('[src]', 'iframe', iframeSrcChange)"],
|
|
132
|
+
"relatedApis": ["off", "emit", "trigger"]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "off",
|
|
136
|
+
"signature": "off(event: string, callback: Function): void",
|
|
137
|
+
"description": "Removes an event listener.",
|
|
138
|
+
"parameters": ["event", "callback"],
|
|
139
|
+
"returnValue": "void",
|
|
140
|
+
"sideEffects": ["Detaches listener."],
|
|
141
|
+
"lifecycleImplications": "Use for manual listener cleanup.",
|
|
142
|
+
"examples": ["off('click', componentClick)"],
|
|
143
|
+
"relatedApis": ["on"]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "emit",
|
|
147
|
+
"signature": "emit(event: string, data?: any): void",
|
|
148
|
+
"description": "Emits a bubbling DOM CustomEvent from the component.",
|
|
149
|
+
"parameters": ["event", "data"],
|
|
150
|
+
"returnValue": "void",
|
|
151
|
+
"sideEffects": ["Dispatches DOM event."],
|
|
152
|
+
"lifecycleImplications": "Use for child-to-parent communication.",
|
|
153
|
+
"examples": ["emit('time-elapsed', '10 seconds elapsed')"],
|
|
154
|
+
"relatedApis": ["on", "trigger"]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "trigger",
|
|
158
|
+
"signature": "trigger(event: string, data?: object): void | trigger(event: string, selector: string, data?: object): void",
|
|
159
|
+
"description": "Programmatically dispatches an event from the component or selected child.",
|
|
160
|
+
"parameters": ["event", "optional selector", "optional data"],
|
|
161
|
+
"returnValue": "void",
|
|
162
|
+
"sideEffects": ["Dispatches event."],
|
|
163
|
+
"lifecycleImplications": "Useful for component-mediated event flows.",
|
|
164
|
+
"examples": ["trigger('click', { someparam: true })", "trigger('click', 'button', { anotherparam: true })"],
|
|
165
|
+
"relatedApis": ["on", "emit"]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "effect",
|
|
169
|
+
"signature": "effect(callback: Function): void | Promise<any>",
|
|
170
|
+
"description": "Registers sync or async reaction to parent prop updates.",
|
|
171
|
+
"parameters": ["callback receiving props"],
|
|
172
|
+
"returnValue": "void or Promise depending on callback.",
|
|
173
|
+
"sideEffects": ["Can override or compose parent props."],
|
|
174
|
+
"lifecycleImplications": "Runs when parent props change.",
|
|
175
|
+
"examples": ["effect(async props => { props.count += 1 })"],
|
|
176
|
+
"relatedApis": ["state.protected"]
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"name": "unmount",
|
|
180
|
+
"signature": "unmount(callback: Function): void",
|
|
181
|
+
"description": "Registers cleanup for component element detachment.",
|
|
182
|
+
"parameters": ["callback"],
|
|
183
|
+
"returnValue": "void",
|
|
184
|
+
"sideEffects": ["Runs cleanup on detach."],
|
|
185
|
+
"lifecycleImplications": "Use for subscriptions, observers, timers, and widgets.",
|
|
186
|
+
"examples": ["unmount(() => unsubscribe())"],
|
|
187
|
+
"relatedApis": ["subscribe"]
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "html-if",
|
|
4
|
+
"description": "Conditionally renders a node from a JavaScript expression evaluated against component render scope.",
|
|
5
|
+
"syntax": "<div html-if=\"show\">...</div>",
|
|
6
|
+
"renderingBehavior": "Truthy expressions render the node; falsy expressions remove it from rendered output.",
|
|
7
|
+
"domBehavior": "Node is created when condition becomes truthy and removed when falsy. DOM state inside removed branch is lost.",
|
|
8
|
+
"updateBehavior": "Re-evaluated after state updates and parent prop updates.",
|
|
9
|
+
"examples": ["<p html-if=\"isVisible\">Visible</p>"],
|
|
10
|
+
"rules": ["Keep condition expressions cheap.", "Use delegated events for children inside conditional branches."],
|
|
11
|
+
"relatedConcepts": ["state.set", "render scope", "conditional rendering"],
|
|
12
|
+
"antiPatterns": ["Using for large frequently toggled subtrees when DOM state must persist.", "Side effects in condition expressions."],
|
|
13
|
+
"performanceNotes": "Branch creation/removal cost grows with subtree size and nested directives."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "html-for",
|
|
17
|
+
"description": "Repeats an element for each item in an array or object.",
|
|
18
|
+
"syntax": "<li html-for=\"item in list\">{{ item.name }}</li>",
|
|
19
|
+
"renderingBehavior": "The directive element acts as the repeated template. Each iteration receives the item alias, $index, and $key.",
|
|
20
|
+
"domBehavior": "Copies are inserted at the directive position. Extra copies are removed when iterable shrinks.",
|
|
21
|
+
"updateBehavior": "Re-evaluated after state changes. Keyed reconciliation is not documented; assume order-based behavior.",
|
|
22
|
+
"examples": ["<li html-for=\"item in list\"><span html-if=\"item.show\">{{ item.name }} {{ $index }}</span></li>"],
|
|
23
|
+
"rules": ["Avoid expensive filtering/sorting in directive expression.", "Use event delegation instead of per-item listeners."],
|
|
24
|
+
"relatedConcepts": ["loops", "$index", "$key", "event delegation"],
|
|
25
|
+
"antiPatterns": ["Deeply nested loops.", "Relying on undocumented keyed DOM preservation."],
|
|
26
|
+
"performanceNotes": "Cost grows with item count and nested directive complexity."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "html-inner",
|
|
30
|
+
"description": "Writes an expression result into a specific element, replacing fallback content after mount.",
|
|
31
|
+
"syntax": "<span html-inner=\"counter\">0</span>",
|
|
32
|
+
"renderingBehavior": "Equivalent purpose to mustache interpolation but targeted at one element.",
|
|
33
|
+
"domBehavior": "Existing child content is replaced with expression output.",
|
|
34
|
+
"updateBehavior": "Re-evaluated after state changes.",
|
|
35
|
+
"examples": ["<strong html-inner=\"name\">Some default value</strong>"],
|
|
36
|
+
"rules": ["Use for scalar content and progressive fallback text."],
|
|
37
|
+
"relatedConcepts": ["mustache", "fallback content", "state"],
|
|
38
|
+
"antiPatterns": ["Injecting untrusted HTML.", "Using for complex nested markup."],
|
|
39
|
+
"performanceNotes": "Cheap for text/scalar updates because element structure remains stable."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "html-model",
|
|
43
|
+
"description": "Provides initial component state from markup.",
|
|
44
|
+
"syntax": "<my-component html-model=\"{ counter: 5 }\"></my-component>",
|
|
45
|
+
"renderingBehavior": "Initial state includes html-model values, overriding model props as documented.",
|
|
46
|
+
"domBehavior": "Initialization directive; not intended as a final application attribute.",
|
|
47
|
+
"updateBehavior": "Used during bootstrap, not reactive after mount.",
|
|
48
|
+
"examples": ["<my-component html-model=\"{ counter: 5 }\"><span html-inner=\"counter\"></span></my-component>"],
|
|
49
|
+
"rules": ["Use for small per-instance configuration.", "Use valid JavaScript object expression syntax."],
|
|
50
|
+
"relatedConcepts": ["model", "initialState"],
|
|
51
|
+
"antiPatterns": ["Large serialized payloads in attributes.", "Treating html-model as reactive."],
|
|
52
|
+
"performanceNotes": "Cheap bootstrap mechanism; avoid parsing large attribute payloads."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "html-*",
|
|
56
|
+
"description": "Binds standard HTML attributes to JavaScript expressions while stripping the html- prefix.",
|
|
57
|
+
"syntax": "<img html-src=\"imageUrl\" alt=\"\">",
|
|
58
|
+
"renderingBehavior": "Expression result is written to the real attribute name.",
|
|
59
|
+
"domBehavior": "Boolean attributes are present for truthy values and removed for falsy values.",
|
|
60
|
+
"updateBehavior": "Re-evaluated after state changes.",
|
|
61
|
+
"examples": ["<div html-class=\"loading ? 'is-loading' : ''\"></div>", "<button html-disabled=\"isSaving\">Save</button>"],
|
|
62
|
+
"rules": ["Use html-src to avoid premature network requests.", "Treat selected, checked, readonly, disabled, and autoplay as boolean attributes."],
|
|
63
|
+
"relatedConcepts": ["attribute binding", "boolean attributes", "attribute observers"],
|
|
64
|
+
"antiPatterns": ["Expecting disabled=\"false\" for falsy booleans.", "Complex class logic inline."],
|
|
65
|
+
"performanceNotes": "Efficient for scalar attributes; heavy expressions repeat on updates."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "html-static",
|
|
69
|
+
"description": "Marks a node and children as skipped by Jails virtual DOM updates/diffing.",
|
|
70
|
+
"syntax": "<div html-static>...</div>",
|
|
71
|
+
"renderingBehavior": "The marked subtree is excluded from Jails template updates.",
|
|
72
|
+
"domBehavior": "Browser or external libraries may mutate the subtree without Jails reconciling it.",
|
|
73
|
+
"updateBehavior": "State updates skip the subtree. Directives inside should be treated as inert after the static boundary.",
|
|
74
|
+
"examples": ["<div class=\"swiper\" html-static></div>", "<input type=\"number\" value=\"1\" html-static>"],
|
|
75
|
+
"rules": ["Use for third-party DOM libraries.", "Use for browser-owned form values when state does not need every keystroke."],
|
|
76
|
+
"relatedConcepts": ["DOM ownership", "third-party integration", "diff boundary"],
|
|
77
|
+
"antiPatterns": ["Expecting dynamic content inside html-static to update.", "Marking too much of a component static."],
|
|
78
|
+
"performanceNotes": "Reduces diff work and prevents conflicts with imperative DOM mutation."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "mustache",
|
|
82
|
+
"description": "Interpolates JavaScript expression results into text positions.",
|
|
83
|
+
"syntax": "{{ expression }}",
|
|
84
|
+
"renderingBehavior": "Expression result becomes text/content at the interpolation position.",
|
|
85
|
+
"domBehavior": "Text node content changes when expression result changes.",
|
|
86
|
+
"updateBehavior": "Re-evaluated after state updates. Delimiters can be changed with templateConfig.",
|
|
87
|
+
"examples": ["<p>Hello, {{ name }}</p>"],
|
|
88
|
+
"rules": ["Use for scalar output.", "Move formatting into view for clarity."],
|
|
89
|
+
"relatedConcepts": ["templateConfig", "view", "render scope"],
|
|
90
|
+
"antiPatterns": ["Side effects in expressions.", "Large logic in interpolation."],
|
|
91
|
+
"performanceNotes": "Cheap for simple text; complex expressions repeat during updates."
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "template element",
|
|
95
|
+
"description": "Native inert HTML used to hide dynamic fragments from initial page rendering.",
|
|
96
|
+
"syntax": "<template><div html-if=\"user\">{{ user.name }}</div></template>",
|
|
97
|
+
"renderingBehavior": "Browser does not render template contents directly.",
|
|
98
|
+
"domBehavior": "Content stays inert until processed/cloned by template system.",
|
|
99
|
+
"updateBehavior": "Useful for fragments that require data before display.",
|
|
100
|
+
"examples": ["<template><div html-if=\"userData\">Welcome, {{ userData.name }}!</div></template>"],
|
|
101
|
+
"rules": ["Use for data-dependent fragments that should not flash markers."],
|
|
102
|
+
"relatedConcepts": ["progressive enhancement", "mustache", "first paint"],
|
|
103
|
+
"antiPatterns": ["Hiding SSR content that should appear immediately."],
|
|
104
|
+
"performanceNotes": "Improves first-paint correctness, not a diff optimization by itself."
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "Progressive Island Component",
|
|
4
|
+
"description": "Enhance server-rendered or static custom element HTML with Jails behavior.",
|
|
5
|
+
"intent": "Keep first paint outside client-side rendering while adding focused interactivity.",
|
|
6
|
+
"structure": ["custom element HTML", "component module", "register()", "start()"],
|
|
7
|
+
"examples": ["<hello-world><span html-inner=\"counter\">0</span></hello-world>"],
|
|
8
|
+
"rules": ["HTML owns initial structure.", "Controller owns behavior.", "State owns render data."],
|
|
9
|
+
"relatedConcepts": ["SSR", "SSG", "custom element", "directives"],
|
|
10
|
+
"antiPatterns": ["Rendering all content only after JavaScript when static HTML is available."],
|
|
11
|
+
"performanceNotes": "Reduces client JavaScript and avoids full-page hydration."
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Page-Scoped Store",
|
|
15
|
+
"description": "Keep mutable page or island state in the component model/state helper.",
|
|
16
|
+
"intent": "Make state ownership explicit and local.",
|
|
17
|
+
"structure": ["model defines data/loading/error", "controller updates with state.set", "template renders directives"],
|
|
18
|
+
"examples": ["export const model = { items: [], loading: false, error: null }"],
|
|
19
|
+
"rules": ["Define all state keys in model.", "Use parent props for child data.", "Use pub/sub only for cross-tree messages."],
|
|
20
|
+
"relatedConcepts": ["state", "model", "parent props"],
|
|
21
|
+
"antiPatterns": ["Hidden global mutable state shared by many components."],
|
|
22
|
+
"performanceNotes": "Constrains update work to component boundaries."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Services Through Dependencies",
|
|
26
|
+
"description": "Inject reusable services through register dependencies.",
|
|
27
|
+
"intent": "Keep generic components free from application-specific imports.",
|
|
28
|
+
"structure": ["register('component', module, { service })", "controller reads dependencies.service"],
|
|
29
|
+
"examples": ["register('form-validation', formValidation, { validations, masks })"],
|
|
30
|
+
"rules": ["Inject app-specific rules.", "Import only truly generic dependencies directly."],
|
|
31
|
+
"relatedConcepts": ["dependencies", "services", "register"],
|
|
32
|
+
"antiPatterns": ["Bundling every country-specific validation in a generic form component."],
|
|
33
|
+
"performanceNotes": "Avoids unused library code and supports reusable packages."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "Delegated Event Handling",
|
|
37
|
+
"description": "Attach listeners at component root and match child selectors.",
|
|
38
|
+
"intent": "Keep listeners stable across DOM replacement.",
|
|
39
|
+
"structure": ["main()", "on(event, selector, handler)", "event.delegateTarget"],
|
|
40
|
+
"examples": ["on('click', '[data-remove]', remove)"],
|
|
41
|
+
"rules": ["Use stable data attributes.", "Read delegateTarget, not target."],
|
|
42
|
+
"relatedConcepts": ["on", "off", "delegateTarget", "html-for"],
|
|
43
|
+
"antiPatterns": ["One listener per repeated item."],
|
|
44
|
+
"performanceNotes": "Reduces listener count and rebinding cost."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "Static Third-Party Integration",
|
|
48
|
+
"description": "Mark externally mutated DOM with html-static.",
|
|
49
|
+
"intent": "Prevent Jails diffing from conflicting with imperative libraries.",
|
|
50
|
+
"structure": ["html-static widget root", "controller initializes library", "state renders surrounding UI"],
|
|
51
|
+
"examples": ["<div class=\"swiper\" html-static></div>"],
|
|
52
|
+
"rules": ["Do not expect directives inside static subtree to update.", "Keep static boundary as small as practical."],
|
|
53
|
+
"relatedConcepts": ["html-static", "DOM ownership", "innerHTML"],
|
|
54
|
+
"antiPatterns": ["Third-party DOM mutation in Jails-managed subtree."],
|
|
55
|
+
"performanceNotes": "Skips diffing and protects library-managed DOM."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "Parent-Child DOM Event Communication",
|
|
59
|
+
"description": "Children emit bubbling events; parents listen with delegated on().",
|
|
60
|
+
"intent": "Use native DOM topology for local component communication.",
|
|
61
|
+
"structure": ["child emit(name, payload)", "parent on(name, selector, handler)"],
|
|
62
|
+
"examples": ["emit('item-selected', item)"],
|
|
63
|
+
"rules": ["Use emit for upward local events.", "Use pub/sub for disconnected trees."],
|
|
64
|
+
"relatedConcepts": ["emit", "on", "publish", "subscribe"],
|
|
65
|
+
"antiPatterns": ["Global pub/sub for direct child-parent messages."],
|
|
66
|
+
"performanceNotes": "Narrower event scope than global channels."
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
|