@symbiotejs/symbiote 3.8.0-webmcp.2 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/docs/README.md +62 -0
- package/docs/animations.md +61 -0
- package/docs/attributes.md +85 -0
- package/docs/common-mistakes.md +267 -0
- package/docs/context.md +238 -0
- package/docs/css-data.md +101 -0
- package/docs/dev-mode.md +60 -0
- package/docs/ecosystem.md +110 -0
- package/docs/examples.md +1367 -0
- package/docs/flags.md +205 -0
- package/docs/get-started.md +165 -0
- package/docs/lifecycle.md +90 -0
- package/docs/list-rendering.md +360 -0
- package/docs/lit-vs-symbiote.md +200 -0
- package/docs/llms-index.md +50 -0
- package/docs/migration-2x-to-3x.md +171 -0
- package/docs/properties.md +173 -0
- package/docs/pubsub.md +159 -0
- package/docs/routing.md +234 -0
- package/docs/security.md +53 -0
- package/docs/ssr-server.md +272 -0
- package/docs/ssr.md +222 -0
- package/docs/styling.md +113 -0
- package/docs/templates.md +323 -0
- package/docs/typescript.md +239 -0
- package/docs/webmcp.md +153 -0
- package/llms-full.txt +5300 -0
- package/llms.txt +50 -0
- package/package.json +26 -33
- package/scripts/build-llms.js +58 -0
- package/AI_REFERENCE.md +0 -890
package/README.md
CHANGED
|
@@ -359,7 +359,8 @@ All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
|
|
|
359
359
|
- [Lit vs Symbiote.js](https://github.com/symbiotejs/symbiote.js/blob/main/docs/lit-vs-symbiote.md) - Side-by-side comparison
|
|
360
360
|
- [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
|
|
361
361
|
- [JSDA-Kit](https://github.com/rnd-pro/jsda-kit) - All-in-one companion tool: server, SSG, bundling, import maps, and native Symbiote.js SSR integration
|
|
362
|
-
- [AI
|
|
362
|
+
- [AI / llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
|
|
363
|
+
- [Full docs (single file)](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
|
|
363
364
|
- [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md)
|
|
364
365
|
|
|
365
366
|
## Related articles
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Symbiote.js Documentation
|
|
2
|
+
|
|
3
|
+
> **Version 3.x** — A lightweight, standards-first UI library built on Web Components.
|
|
4
|
+
> Zero dependencies. ~7.3 KB brotli / ~8.1 KB gzip. No virtual DOM, no compiler, no build step required.
|
|
5
|
+
|
|
6
|
+
## Getting Started
|
|
7
|
+
|
|
8
|
+
- [**Get Started**](./get-started.md) — Installation, first component, platform specs
|
|
9
|
+
- [**Ecosystem**](./ecosystem.md) — IDE setup, build tools, code sharing
|
|
10
|
+
|
|
11
|
+
## Core Concepts
|
|
12
|
+
|
|
13
|
+
- [**Templates**](./templates.md) — HTML string templates, binding syntax, slots, element references
|
|
14
|
+
- [**Properties**](./properties.md) — State initialization, `$` proxy, subscriptions, computed properties
|
|
15
|
+
- [**Context**](./context.md) — Local, named, pop-up, and shared data contexts
|
|
16
|
+
- [**List Rendering**](./list-rendering.md) — Itemize API for dynamic lists
|
|
17
|
+
- [**Lifecycle**](./lifecycle.md) — Component lifecycle callbacks and destruction
|
|
18
|
+
- [**Flags**](./flags.md) — Component configuration flags
|
|
19
|
+
- [**Attributes**](./attributes.md) — HTML attribute binding and observation
|
|
20
|
+
|
|
21
|
+
## State Management
|
|
22
|
+
|
|
23
|
+
- [**PubSub**](./pubsub.md) — Standalone state management, named contexts
|
|
24
|
+
|
|
25
|
+
## Styling
|
|
26
|
+
|
|
27
|
+
- [**Styling**](./styling.md) — rootStyles, shadowStyles, CSS processing
|
|
28
|
+
- [**CSS Data**](./css-data.md) — CSS custom properties as reactive state
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- [**WebMCP Experimental**](./webmcp.md) — Expose live UI state and handlers as browser-native tools
|
|
33
|
+
- [**Routing**](./routing.md) — SPA routing with path patterns, guards, lazy loading
|
|
34
|
+
- [**SSR**](./ssr.md) — Server-side rendering, streaming, hydration
|
|
35
|
+
- [**SSR and Your Server Setup**](./ssr-server.md) — Static build, streaming, Express or Fastify
|
|
36
|
+
- [**Animations**](./animations.md) — CSS-driven exit transitions
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
- [**Dev Mode**](./dev-mode.md) — Verbose warnings for debugging
|
|
41
|
+
- [**TypeScript**](./typescript.md) — JSDoc types, TypeScript usage, and hybrid template checks
|
|
42
|
+
- [**Security**](./security.md) — Trusted Types and CSP compliance
|
|
43
|
+
|
|
44
|
+
## Migration
|
|
45
|
+
|
|
46
|
+
- [**2.x → 3.x Migration Guide**](./migration-2x-to-3x.md) — Breaking changes and upgrade path
|
|
47
|
+
|
|
48
|
+
## Additional Resources
|
|
49
|
+
|
|
50
|
+
- [Lit vs Symbiote.js](./lit-vs-symbiote.md) — Side-by-side comparison
|
|
51
|
+
- [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
|
|
52
|
+
- [Common Mistakes](./common-mistakes.md) — Patterns that frequently cause bugs
|
|
53
|
+
- [Examples](./examples.md) — 26 complete working examples
|
|
54
|
+
- [llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
|
|
55
|
+
- [llms-full.txt](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
|
|
56
|
+
- [Changelog](../CHANGELOG.md)
|
|
57
|
+
- [npm](https://www.npmjs.com/package/@symbiotejs/symbiote)
|
|
58
|
+
- [GitHub Discussions](https://github.com/symbiotejs/symbiote.js/discussions)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
© [rnd-pro.com](https://rnd-pro.com) — MIT License
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Animations
|
|
2
|
+
|
|
3
|
+
Symbiote.js provides CSS-driven exit transitions with zero JS animation code.
|
|
4
|
+
|
|
5
|
+
## `animateOut`
|
|
6
|
+
|
|
7
|
+
`animateOut(el)` sets the `[leaving]` attribute on an element, waits for CSS `transitionend`, then removes the element from the DOM. If no CSS transition is defined, removes immediately.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { animateOut } from '@symbiotejs/symbiote';
|
|
11
|
+
|
|
12
|
+
// or as a static method:
|
|
13
|
+
Symbiote.animateOut(el);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## CSS pattern
|
|
17
|
+
|
|
18
|
+
Use `@starting-style` for enter animations and `[leaving]` for exit:
|
|
19
|
+
```css
|
|
20
|
+
my-item {
|
|
21
|
+
opacity: 1;
|
|
22
|
+
transform: translateY(0);
|
|
23
|
+
transition: opacity 0.3s, transform 0.3s;
|
|
24
|
+
|
|
25
|
+
/* Enter (CSS-native, no JS needed): */
|
|
26
|
+
@starting-style {
|
|
27
|
+
opacity: 0;
|
|
28
|
+
transform: translateY(20px);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Exit (triggered by animateOut): */
|
|
32
|
+
&[leaving] {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
transform: translateY(-10px);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Itemize integration
|
|
40
|
+
|
|
41
|
+
Both itemize processors use `animateOut` automatically for item removal. Items with CSS `transition` + `[leaving]` styles will animate out before being removed from the DOM:
|
|
42
|
+
```css
|
|
43
|
+
user-card {
|
|
44
|
+
opacity: 1;
|
|
45
|
+
transition: opacity 0.3s;
|
|
46
|
+
|
|
47
|
+
@starting-style {
|
|
48
|
+
opacity: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&[leaving] {
|
|
52
|
+
opacity: 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
No additional JavaScript is required — just define the CSS transitions and Symbiote handles the rest.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
Next: [CSS Data →](./css-data.md)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Attributes
|
|
2
|
+
|
|
3
|
+
Like all regular DOM elements, Symbiote components can have their own HTML attributes. And like standard Custom Elements, they can react to dynamic attribute changes.
|
|
4
|
+
|
|
5
|
+
## Attribute connection
|
|
6
|
+
|
|
7
|
+
The simplest way to connect a local state property to an attribute value is with the `@` token:
|
|
8
|
+
```js
|
|
9
|
+
class MyComponent extends Symbiote {
|
|
10
|
+
|
|
11
|
+
init$ = {
|
|
12
|
+
'@attribute-name': 'initial value',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or initiate from the component's template directly:
|
|
19
|
+
```js
|
|
20
|
+
class MyComponent extends Symbiote {}
|
|
21
|
+
|
|
22
|
+
MyComponent.template = html`<h1>{{@attribute-name}}</h1>`;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then use it as an attribute in markup:
|
|
26
|
+
```html
|
|
27
|
+
<my-component attribute-name="attribute value"></my-component>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Attribute change reaction
|
|
31
|
+
|
|
32
|
+
Using a property accessor:
|
|
33
|
+
```js
|
|
34
|
+
class MyComponent extends Symbiote {
|
|
35
|
+
|
|
36
|
+
set 'my-attribute'(val) {
|
|
37
|
+
console.log(val);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
MyComponent.observedAttributes = [
|
|
43
|
+
'my-attribute',
|
|
44
|
+
];
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## `bindAttributes()` static method
|
|
48
|
+
|
|
49
|
+
Bind attributes to property values directly:
|
|
50
|
+
```js
|
|
51
|
+
class MyComponent extends Symbiote {
|
|
52
|
+
|
|
53
|
+
init$ = {
|
|
54
|
+
myProp: '',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Map the attribute name to corresponding property key:
|
|
60
|
+
MyComponent.bindAttributes({
|
|
61
|
+
'my-attribute': 'myProp',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// observedAttributes is auto-populated
|
|
65
|
+
|
|
66
|
+
MyComponent.template = html`
|
|
67
|
+
<div>{{myProp}}</div>
|
|
68
|
+
`;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Reserved attribute names
|
|
72
|
+
|
|
73
|
+
These attribute names are used internally by Symbiote.js:
|
|
74
|
+
|
|
75
|
+
- `bind`
|
|
76
|
+
- `ctx`
|
|
77
|
+
- `ref`
|
|
78
|
+
- `itemize`
|
|
79
|
+
- `item-tag`
|
|
80
|
+
- `use-template`
|
|
81
|
+
- `skip-text-nodes`
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
Next: [PubSub →](./pubsub.md)
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Common Mistakes
|
|
2
|
+
|
|
3
|
+
Patterns that frequently trip up new users and AI code generators.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Using `this` inside template strings
|
|
8
|
+
|
|
9
|
+
Templates are context-free strings — they DO NOT execute inside a component instance and do not close over `this`. Binding values by name; the component resolves them at render time.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// WRONG
|
|
13
|
+
MyComponent.template = html`<div>${this.title}</div>`;
|
|
14
|
+
|
|
15
|
+
// CORRECT — use binding syntax
|
|
16
|
+
MyComponent.template = html`<div>{{title}}</div>`;
|
|
17
|
+
// OR
|
|
18
|
+
MyComponent.template = html`<div ${{textContent: 'title'}}></div>`;
|
|
19
|
+
// OR
|
|
20
|
+
MyComponent.template = `<div bind="textContent: title"></div>`;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. Defining `template`, `rootStyles`, or `shadowStyles` inside the class body
|
|
26
|
+
|
|
27
|
+
`template`, `rootStyles`, and `shadowStyles` are **static property setters** on the `Symbiote` base class — not regular static fields. Assigning them inside the class body with `static template = ...` bypasses the setter and does nothing.
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
// WRONG
|
|
31
|
+
class MyComponent extends Symbiote {
|
|
32
|
+
static template = html`<div>{{text}}</div>`; // setter never called
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// CORRECT — assign outside the class body
|
|
36
|
+
class MyComponent extends Symbiote {}
|
|
37
|
+
MyComponent.template = html`<div>{{text}}</div>`;
|
|
38
|
+
MyComponent.rootStyles = css`my-component { display: block; }`;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 3. Missing `^` prefix when referencing a parent-defined handler from an item template
|
|
44
|
+
|
|
45
|
+
Itemize items are real Symbiote components with their own reactive state (the data properties mapped from the source array or object). They can have their own handlers too — either passed as functions in the source data objects, or defined on a separate component class registered for the tag name set by `item-tag` attribute.
|
|
46
|
+
|
|
47
|
+
The common mistake is defining a shared handler on the **parent** (e.g. to handle all item clicks in one place) and then binding to it from the item template *without* `^`. Without `^`, the binding looks for the handler on the item itself — where it doesn't exist — and fails.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
class MyList extends Symbiote {
|
|
51
|
+
init$ = {
|
|
52
|
+
items: [{ name: 'Alice' }, { name: 'Bob' }],
|
|
53
|
+
onItemClick: (e) => { console.log('clicked'); },
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// WRONG — looks for onItemClick on the item, not the parent
|
|
58
|
+
MyList.template = html`
|
|
59
|
+
<ul itemize="items">
|
|
60
|
+
<template>
|
|
61
|
+
<li><button ${{onclick: 'onItemClick'}}>{{name}}</button></li>
|
|
62
|
+
</template>
|
|
63
|
+
</ul>
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
// CORRECT — ^ walks up the DOM to find onItemClick in the parent's init$
|
|
67
|
+
MyList.template = html`
|
|
68
|
+
<ul itemize="items">
|
|
69
|
+
<template>
|
|
70
|
+
<li><button ${{onclick: '^onItemClick'}}>{{name}}</button></li>
|
|
71
|
+
</template>
|
|
72
|
+
</ul>
|
|
73
|
+
`;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> `^`-targeted properties must be defined in the ancestors's `init$` — the walk does not check plain class properties.
|
|
77
|
+
|
|
78
|
+
When each item needs its own independent handler, you can pass it directly in the data:
|
|
79
|
+
```js
|
|
80
|
+
this.$.items = [
|
|
81
|
+
{ name: 'Alice', onItemClick: () => console.log('Alice clicked') },
|
|
82
|
+
{ name: 'Bob', onItemClick: () => console.log('Bob clicked') },
|
|
83
|
+
];
|
|
84
|
+
```
|
|
85
|
+
```html
|
|
86
|
+
<ul itemize="items">
|
|
87
|
+
<template>
|
|
88
|
+
<li><button ${{onclick: 'onItemClick'}}>{{name}}</button></li>
|
|
89
|
+
</template>
|
|
90
|
+
</ul>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or, as it recommended for the most cases, register a dedicated component class for the tag name set by `item-tag`:
|
|
94
|
+
```js
|
|
95
|
+
class MyItem extends Symbiote {
|
|
96
|
+
onItemClick() { console.log(this.$.name); }
|
|
97
|
+
}
|
|
98
|
+
MyItem.template = html`<li><button ${{onclick: 'onItemClick'}}>{{name}}</button></li>`;
|
|
99
|
+
MyItem.reg('my-item');
|
|
100
|
+
```
|
|
101
|
+
```html
|
|
102
|
+
<ul itemize="items" item-tag="my-item"></ul>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 4. Using `@` attribute prefix directly in HTML
|
|
108
|
+
|
|
109
|
+
`@` is a binding syntax prefix only — it means "bind to HTML attribute" inside `${{}}` expressions. It is not a valid HTML attribute prefix.
|
|
110
|
+
|
|
111
|
+
```html
|
|
112
|
+
<!-- WRONG -->
|
|
113
|
+
<div @hidden="isHidden">...</div>
|
|
114
|
+
|
|
115
|
+
<!-- CORRECT — @-prefix is only inside binding objects -->
|
|
116
|
+
<div ${{'@hidden': 'isHidden'}}>...</div>
|
|
117
|
+
<!-- OR, when html template helper is not used: -->
|
|
118
|
+
<div bind="@hidden': isHidden">...</div>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 5. Expecting dotted `init$` keys to create nested state
|
|
124
|
+
|
|
125
|
+
State is flat by design. A key like `'obj.prop'` in `init$` is a literal flat key named `obj.prop` — it does not create a nested object `{ obj: { prop: ... } }`. `this.$['obj.prop']` reads and writes that same flat key and works fine.
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
// MISLEADING — this does NOT create { obj: { prop: 'value' } }
|
|
129
|
+
init$ = { 'obj.prop': 'value' };
|
|
130
|
+
// It creates a flat key: store['obj.prop'] = 'value'
|
|
131
|
+
// Reading/writing it works: this.$['obj.prop'] = 'new' ✓
|
|
132
|
+
|
|
133
|
+
// If you need a nested object in state, store it as a flat key with an object value:
|
|
134
|
+
init$ = { obj: { prop: 'value' } };
|
|
135
|
+
this.$.obj = { ...this.$.obj, prop: 'new' }; // replace the whole object to trigger reactivity
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Dot notation in binding **targets** is fully supported — for both standard DOM properties and child component state:
|
|
139
|
+
```js
|
|
140
|
+
// DOM property path:
|
|
141
|
+
html`<div ${{'style.color': 'colorProp'}}>Text</div>`
|
|
142
|
+
|
|
143
|
+
// Child component's $ state proxy:
|
|
144
|
+
html`<child-el ${{'$.childProp': 'parentProp'}}></child-el>`
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 6. Using `*prop` without a `ctx` attribute or `--ctx` CSS variable
|
|
150
|
+
|
|
151
|
+
Shared context properties (`*`-prefix) require a context name to be set on the component — either via the `ctx` HTML attribute or the `--ctx` CSS custom property. Without one, `*` props silently have no effect (dev mode warns).
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<!-- WRONG — no context name, *files is never shared -->
|
|
155
|
+
<upload-btn></upload-btn>
|
|
156
|
+
<file-list></file-list>
|
|
157
|
+
|
|
158
|
+
<!-- CORRECT -->
|
|
159
|
+
<upload-btn ctx="gallery"></upload-btn>
|
|
160
|
+
<file-list ctx="gallery"></file-list>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 7. Expecting Shadow DOM by default
|
|
166
|
+
|
|
167
|
+
Shadow DOM is opt-in. By default, templates render into the component's light DOM. Use `renderShadow = true` or assign `shadowStyles` to opt in.
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
// Light DOM (default)
|
|
171
|
+
class MyComponent extends Symbiote {}
|
|
172
|
+
|
|
173
|
+
// Shadow DOM — either flag:
|
|
174
|
+
class MyComponent extends Symbiote {
|
|
175
|
+
renderShadow = true;
|
|
176
|
+
}
|
|
177
|
+
// or via shadowStyles (auto-creates shadow root):
|
|
178
|
+
MyComponent.shadowStyles = css`:host { display: block; }`;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 8. Adding a wrapper div inside the template
|
|
184
|
+
|
|
185
|
+
The custom element itself is the container. Adding a wrapping `<div>` as the single root inside the template is unnecessary — it creates extra DOM nesting and duplicates the element's own box. Style the component tag directly instead.
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
// WRONG — the <div class="wrapper"> is redundant, my-widget is already the root
|
|
189
|
+
MyWidget.template = html`
|
|
190
|
+
<div class="wrapper">
|
|
191
|
+
<h1>{{title}}</h1>
|
|
192
|
+
<button ${{onclick: 'onAction'}}>Go</button>
|
|
193
|
+
</div>
|
|
194
|
+
`;
|
|
195
|
+
|
|
196
|
+
// CORRECT — template content renders directly inside the custom element
|
|
197
|
+
MyWidget.template = html`
|
|
198
|
+
<h1>{{title}}</h1>
|
|
199
|
+
<button ${{onclick: 'onAction'}}>Go</button>
|
|
200
|
+
`;
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```css
|
|
204
|
+
/* Style the element tag directly */
|
|
205
|
+
my-widget {
|
|
206
|
+
display: block;
|
|
207
|
+
padding: 20px;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 9. Relying on class property fallbacks for prefixed bindings
|
|
214
|
+
|
|
215
|
+
Class property fallbacks (resolving unregistered keys from own instance properties or prototype methods) only apply to **local, unprefixed** bindings. Any prefixed binding — `^prop`, `*prop` — resolves exclusively through the data context (`init$` or `add$()`). Plain class properties are never checked for these.
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
// WRONG — onItemClick is a class property, not in init$
|
|
219
|
+
// ^ will not find it
|
|
220
|
+
class TaskList extends Symbiote {
|
|
221
|
+
onItemClick() { console.log('clicked'); }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
TaskList.template = html`
|
|
225
|
+
<ul itemize="tasks">
|
|
226
|
+
<template>
|
|
227
|
+
<li ${{onclick: '^onItemClick'}}>{{name}}</li>
|
|
228
|
+
</template>
|
|
229
|
+
</ul>
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
// CORRECT — register in init$ so any prefixed binding can resolve it
|
|
233
|
+
class TaskList extends Symbiote {
|
|
234
|
+
init$ = {
|
|
235
|
+
onItemClick: () => { console.log('clicked'); },
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The same applies to shared (`*`) bindings — the property must exist in the shared context's registered data, not merely as a class property.
|
|
241
|
+
|
|
242
|
+
Named external contexts (`CTX/prop`) are different: they are registered globally via `PubSub.registerCtx()` and are fully independent of any component's `init$` — components reference them freely without declaring anything locally.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 10. Treating `init$` as a plain object
|
|
247
|
+
|
|
248
|
+
`init$` is processed once at connection time to populate the component's reactive context. Mutating it after the fact has no effect. Use `this.$` or `add$()` for runtime changes.
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
// WRONG — modifying init$ after construction does nothing
|
|
252
|
+
class MyComponent extends Symbiote {
|
|
253
|
+
init$ = { count: 0 };
|
|
254
|
+
initCallback() {
|
|
255
|
+
this.init$.count = 10; // too late, already processed
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// CORRECT
|
|
260
|
+
class MyComponent extends Symbiote {
|
|
261
|
+
init$ = { count: 0 };
|
|
262
|
+
initCallback() {
|
|
263
|
+
this.$.count = 10; // write through the $ proxy
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|