@symbiotejs/symbiote 3.8.1 → 3.8.2

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 CHANGED
@@ -9,12 +9,10 @@
9
9
 
10
10
  A lightweight, standards-first UI library built on Web Components. No virtual DOM, no compiler, no black boxes, no excess repaints. No build step required - works directly in the browser. A bundler is recommended for production performance, but entirely optional.
11
11
 
12
- Symbiote.js gives you the convenience of a modern framework while staying close to the native platform - HTML, CSS, and DOM APIs. Components are custom elements that work everywhere: in any framework, in plain HTML, in a micro-frontend architecture. And with **isomorphic mode**, the same component code works on the server and the client - server-rendered pages hydrate automatically, no diffing, no mismatch errors.
13
-
14
12
  Here are the three most important differences between Symbiote.js and other frameworks:
15
- 1. Natural DOM Extension Philosophy - designed to extend platform, not to replace it
16
- 2. Runtime-Agnostic HTML Templates - outstanding flexibility for rendering strategies and further customization
17
- 3. Powerful App-wide State Management - combine data contexts without bloated boilerplate or external tools
13
+ 1. **Natural DOM Extension Philosophy** - designed to extend platform APIs, not to replace them
14
+ 2. **Runtime-Agnostic HTML Templates** - outstanding flexibility for rendering strategies and further customization
15
+ 3. **Powerful App-wide State Management** - combine data contexts without bloated boilerplate or external tools
18
16
 
19
17
  ## What's new in v3.x?
20
18
 
@@ -28,7 +26,6 @@ Here are the three most important differences between Symbiote.js and other fram
28
26
  - **DSD hydration** - `ssrMode` supports both light DOM and Declarative Shadow DOM.
29
27
  - **Class property fallback** - binding keys not in `init$` fall back to own class properties/methods.
30
28
  - **Lazy mode** - `lazyMode` flag defers component initialization and rendering based on viewport visibility. Can also be enabled via the `lazy` attribute on `itemize` containers to efficiently handle massive data sets.
31
- - And [more](https://github.com/symbiotejs/symbiote.js/blob/webmcp/CHANGELOG.md).
32
29
 
33
30
  ## Quick start
34
31
 
@@ -66,57 +63,6 @@ npm i @symbiotejs/symbiote
66
63
  import Symbiote, { html, css } from '@symbiotejs/symbiote';
67
64
  ```
68
65
 
69
- ## Isomorphic Web Components
70
-
71
- One component. Server-rendered or client-rendered - automatically. Set `isoMode = true` and the component figures it out: if server-rendered content exists, it hydrates; otherwise it renders from template. No conditional logic, no separate server/client versions:
72
- ```js
73
- class MyComponent extends Symbiote {
74
- isoMode = true;
75
- count = 0;
76
- increment() {
77
- this.$.count++;
78
- }
79
- }
80
-
81
- MyComponent.template = html`
82
- <h2 ${{textContent: 'count'}}></h2>
83
- <button ${{onclick: 'increment'}}>Click me!</button>
84
- `;
85
- MyComponent.reg('my-component');
86
- ```
87
-
88
- This exact code runs **everywhere** - SSR on the server, hydration on the client, or pure client rendering. No framework split, no `'use client'` directives, no hydration mismatch errors.
89
-
90
- ### SSR - one class, zero config
91
-
92
- Server rendering doesn't need a virtual DOM, a reconciler, or framework-specific packages:
93
-
94
- ```js
95
- import { SSR } from '@symbiotejs/symbiote/node/SSR.js';
96
-
97
- await SSR.init(); // patches globals with linkedom
98
- await import('./my-app.js'); // components register normally
99
-
100
- let html = await SSR.processHtml('<my-app></my-app>');
101
- SSR.destroy();
102
- ```
103
-
104
- For large pages, stream HTML chunks with `SSR.renderToStream()` for faster TTFB. See [SSR docs](./docs/ssr.md) and [server setup recipes](./docs/ssr-server.md).
105
-
106
- ### How it compares
107
-
108
- | | **Symbiote.js** | **Next.js (React)** | **Lit** (`@lit-labs/ssr`) |
109
- |--|----------------|---------------------|----|
110
- | **Isomorphic code** | Same code, `isoMode` auto-detects | Server Components vs Client Components split | Same code, but load-order constraints |
111
- | **Hydration** | Binding-based - attaches to existing DOM, no diffing | `hydrateRoot()` - must produce identical output or errors | Requires `ssr-client` + hydrate support module |
112
- | **Packages** | 1 module + `linkedom` peer dep | Full framework buy-in | 3 packages: `ssr`, `ssr-client`, `ssr-dom-shim` |
113
- | **Streaming** | `renderToStream()` async generator | `renderToPipeableStream()` | Iterable `RenderResult` |
114
- | **Mismatch handling** | Not needed - bindings attach to whatever DOM exists | Hard errors if server/client output differs | N/A |
115
- | **Template output** | Clean HTML with `bind=` attributes | HTML with framework markers | HTML with `<!--lit-part-->` comment markers |
116
- | **Lock-in** | None - standard Web Components | Full framework commitment | Lit-specific, but Web Components |
117
-
118
- **Key insight:** There are no hydration mismatches because there's no diffing. The server produces HTML with binding attributes. The client reads those attributes and adds reactivity. That's it.
119
-
120
66
  ## Core concepts
121
67
 
122
68
  ### Reactive state
@@ -145,7 +91,7 @@ This makes it easy to control Symbiote-based widgets and microfrontends from any
145
91
 
146
92
  ### Templates
147
93
 
148
- Templates are plain HTML strings - context-free, easy to test, easy to move between files:
94
+ Templates are plain HTML strings - runtime-agnostic, easy to test, easy to move between files:
149
95
 
150
96
  ```js
151
97
  // Separate file: my-component.template.js
@@ -163,34 +109,24 @@ The `html` function supports two interpolation modes:
163
109
 
164
110
  ### Itemize (dynamic reactive lists)
165
111
 
166
- Render lists from data arrays with efficient updates:
112
+ Render lists from data arrays or objects with efficient updates:
167
113
  ```js
168
114
  class TaskList extends Symbiote {
169
115
  tasks = [
170
116
  { name: 'Buy groceries' },
171
117
  { name: 'Write docs' },
172
118
  ];
173
- init$ = {
174
- // Needs to be defined in init$ for pop-up binding to work
175
- onItemClick: () => {
176
- console.log('clicked!');
177
- },
178
- }
179
119
  }
180
120
 
181
121
  TaskList.template = html`
182
- <div itemize="tasks">
122
+ <ul itemize="tasks">
183
123
  <template>
184
- <div ${{onclick: '^onItemClick'}}>{{name}}</div>
124
+ <li>{{name}}</li>
185
125
  </template>
186
- </div>
126
+ </ul>
187
127
  `;
188
128
  ```
189
129
 
190
- Items have their own state scope. Use the **`^` prefix** to reach higher-level component properties and handlers - `'^onItemClick'` binds to the parent's `onItemClick`, not the item's. Properties referenced via `^` must be defined in the parent's `init$`.
191
-
192
- > **Performance Tip:** For massive lists, add the `lazy` attribute to the container (`<div itemize="tasks" lazy>`). It defers component initialization until they enter the viewport and cleans them up when they leave, heavily optimizing memory and rendering performance.
193
-
194
130
  ### Pop-up binding (`^`)
195
131
 
196
132
  The `^` prefix works in any nested component template - it walks up the DOM tree to find the nearest ancestor that has the property registered in its data context (`init$` or `add$()`):
@@ -203,14 +139,11 @@ The `^` prefix works in any nested component template - it walks up the DOM tree
203
139
  <button ${{onclick: '^parentHandler'}}>Click</button>
204
140
  ```
205
141
 
206
- > **Note:** Class property fallbacks are not checked by the `^` walk - the parent must define the property in `init$`.
207
-
208
142
  ### Named data contexts
209
143
 
210
144
  Share state across components without prop drilling:
211
-
212
145
  ```js
213
- import { PubSub } from '@symbiotejs/symbiote';
146
+ import { PubSub, html } from '@symbiotejs/symbiote';
214
147
 
215
148
  PubSub.registerCtx({
216
149
  user: 'Alex',
@@ -219,6 +152,9 @@ PubSub.registerCtx({
219
152
 
220
153
  // Any component can read/write:
221
154
  this.$['APP/user'] = 'New name';
155
+
156
+ // Any template can use property directly:
157
+ let template = html`<h2>{{APP/user}}</h2>`;
222
158
  ```
223
159
 
224
160
  ### Shared context (`*`)
@@ -251,11 +187,10 @@ class StatusBar extends Symbiote {
251
187
 
252
188
  All three components access the same `*files` state - no parent component, no prop drilling, no global store boilerplate. Just set `ctx="gallery"` in HTML and use `*`-prefixed properties. This makes it trivial to build complex component relationships purely in markup, with ready-made components that don't need to know about each other.
253
189
 
254
- The context name can also be inherited via CSS custom property `--ctx`, enabling layout-driven grouping.
255
-
256
- ### Routing (optional module)
190
+ ### Application routing
257
191
 
258
192
  ```js
193
+ // Import optional module:
259
194
  import { AppRouter } from '@symbiotejs/symbiote/core/AppRouter.js';
260
195
 
261
196
  AppRouter.initRoutingCtx('R', {
@@ -265,23 +200,7 @@ AppRouter.initRoutingCtx('R', {
265
200
  });
266
201
  ```
267
202
 
268
- ### Exit animations
269
-
270
- CSS-driven transitions with zero JS animation code:
271
-
272
- ```css
273
- task-item {
274
- opacity: 1;
275
- transition: opacity 0.3s;
276
-
277
- @starting-style { opacity: 0; } /* enter */
278
- &[leaving] { opacity: 0; } /* exit */
279
- }
280
- ```
281
-
282
- `animateOut(el)` sets `[leaving]`, waits for `transitionend`, then removes. Itemize uses this automatically.
283
-
284
- ### Styling
203
+ ### CSS Styling
285
204
 
286
205
  Shadow DOM is **optional** in Symbiote - use it when you need isolation, skip it when you don't. This gives full flexibility:
287
206
 
@@ -298,6 +217,8 @@ MyComponent.rootStyles = css`
298
217
  `;
299
218
  ```
300
219
 
220
+ This style will be applied to nearest upper shadow root, if exists and to common document if not.
221
+
301
222
  **Shadow DOM** - opt-in isolation when needed:
302
223
 
303
224
  ```js
@@ -311,9 +232,9 @@ Isolated.shadowStyles = css`
311
232
 
312
233
  All native CSS features work as expected: CSS variables flow through shadow boundaries, `::part()` exposes internals, modern nesting, `@layer`, `@container` - no framework abstractions in the way. Mix light DOM and shadow DOM components freely in the same app.
313
234
 
314
- ### CSS Data Binding
235
+ ### CSS Data
315
236
 
316
- Components can read CSS custom properties as reactive state via `cssInit$`:
237
+ Components can read CSS custom property values to initiate reactive state:
317
238
 
318
239
  ```css
319
240
  my-widget {
@@ -329,8 +250,6 @@ MyWidget.template = html`
329
250
  `;
330
251
  ```
331
252
 
332
- CSS values are parsed automatically - quoted strings become strings, numbers become numbers. Call `this.updateCssData()` to re-read after runtime CSS changes. This enables CSS-driven configuration: theme values, layout parameters, or localized strings - all settable from CSS without touching JS.
333
-
334
253
  ## Best for
335
254
 
336
255
  - **Complex widgets** embedded in any host application
@@ -341,19 +260,13 @@ CSS values are parsed automatically - quoted strings become strings, numbers bec
341
260
  - **Framework-agnostic solutions** - one codebase, any context
342
261
  - **Modern AI-first web** - expose the application state to WebMCP tools automatically
343
262
 
344
- ## Browser support
345
-
346
- All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
347
-
348
263
  ## Docs & Examples
349
264
 
350
265
  - [Documentation](https://github.com/symbiotejs/symbiote.js/blob/main/docs/README.md)
351
- - [Lit vs Symbiote.js](https://github.com/symbiotejs/symbiote.js/blob/main/docs/lit-vs-symbiote.md) - Side-by-side comparison
352
266
  - [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
353
267
  - [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
354
268
  - [AI / llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
355
269
  - [Full docs (single file)](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
356
- - [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md)
357
270
 
358
271
  ## Related articles
359
272
 
package/docs/README.md CHANGED
@@ -41,19 +41,13 @@
41
41
  - [**TypeScript**](./typescript.md) — JSDoc types, TypeScript usage, and hybrid template checks
42
42
  - [**Security**](./security.md) — Trusted Types and CSP compliance
43
43
 
44
- ## Migration
45
-
46
- - [**2.x → 3.x Migration Guide**](./migration-2x-to-3x.md) — Breaking changes and upgrade path
47
-
48
44
  ## Additional Resources
49
45
 
50
- - [Lit vs Symbiote.js](./lit-vs-symbiote.md) — Side-by-side comparison
51
46
  - [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
52
47
  - [Common Mistakes](./common-mistakes.md) — Patterns that frequently cause bugs
53
48
  - [Examples](./examples.md) — 26 complete working examples
54
49
  - [llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
55
50
  - [llms-full.txt](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
56
- - [Changelog](../CHANGELOG.md)
57
51
  - [npm](https://www.npmjs.com/package/@symbiotejs/symbiote)
58
52
  - [GitHub Discussions](https://github.com/symbiotejs/symbiote.js/discussions)
59
53
 
@@ -38,7 +38,7 @@ my-item {
38
38
 
39
39
  ## Itemize integration
40
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:
41
+ Itemize processors use `animateOut` automatically for item removal. Items with CSS `transition` + `[leaving]` styles will animate out before being removed from the DOM:
42
42
  ```css
43
43
  user-card {
44
44
  opacity: 1;
@@ -22,7 +22,7 @@ class MyComponent extends Symbiote {}
22
22
  MyComponent.template = html`<h1>{{@attribute-name}}</h1>`;
23
23
  ```
24
24
 
25
- Then use it as an attribute in markup:
25
+ Then use it as an component's attribute in markup:
26
26
  ```html
27
27
  <my-component attribute-name="attribute value"></my-component>
28
28
  ```
@@ -46,7 +46,7 @@ MyComponent.observedAttributes = [
46
46
 
47
47
  ## `bindAttributes()` static method
48
48
 
49
- Bind attributes to property values directly:
49
+ Reflect attribute values to property values:
50
50
  ```js
51
51
  class MyComponent extends Symbiote {
52
52
 
@@ -243,7 +243,69 @@ Named external contexts (`CTX/prop`) are different: they are registered globally
243
243
 
244
244
  ---
245
245
 
246
- ## 10. Treating `init$` as a plain object
246
+ ## 10. Using `{{prop}}` binding syntax inside tag definitions
247
+
248
+ `{{prop}}` is a **text node** binding — it only works inside element content. Any binding that targets a tag itself — attributes, DOM properties, event handlers, or CSS custom properties — must use `${{}}` or `bind=""` syntax inside the opening tag.
249
+
250
+ ```html
251
+ <!-- WRONG — {{}} syntax does not work inside tag definitions -->
252
+ <div class="{{APP/theme}}">...</div>
253
+ <img src="{{imageUrl}}">
254
+ <button onclick="{{onAction}}">Go</button>
255
+
256
+ <!-- CORRECT — use ${{}} binding object inside the tag -->
257
+ <div ${{'@class': 'APP/theme'}}>...</div>
258
+ <img ${{'@src': 'imageUrl'}}>
259
+ <button ${{onclick: 'onAction'}}>Go</button>
260
+ <div ${{'style.color': 'colorProp'}}>...</div>
261
+
262
+ <!-- CORRECT — or use the bind attribute string syntax -->
263
+ <div bind="@class: APP/theme">...</div>
264
+ <img bind="@src: imageUrl">
265
+ <button bind="onclick: onAction">Go</button>
266
+
267
+ <!-- CORRECT — {{}} works fine in text node content -->
268
+ <h1>Hello, {{userName}}!</h1>
269
+ <p>Theme: {{APP/theme}}</p>
270
+ ```
271
+
272
+ ---
273
+
274
+ ## 11. Expecting two-way data binding
275
+
276
+ Symbiote.js bindings are **one-way by design** — from state to DOM. There is no `v-model`, `[(ngModel)]`, or `bind:value` equivalent. To react to user input, wire the event handler explicitly and write back to state yourself.
277
+
278
+ ```js
279
+ // WRONG — expecting the binding to also update state on user input
280
+ MyComponent.template = html`
281
+ <input ${{value: 'query'}}>
282
+ `;
283
+
284
+ // CORRECT — handle the input event and write back to state
285
+ class MyComponent extends Symbiote {
286
+ init$ = { query: '' }
287
+ }
288
+
289
+ MyComponent.template = html`
290
+ <input
291
+ ${{value: 'query', oninput: 'onInput'}}
292
+ >
293
+ `;
294
+ ```
295
+ ```js
296
+ class MyComponent extends Symbiote {
297
+ init$ = {
298
+ query: '',
299
+ onInput: (e) => { this.$.query = e.target.value; },
300
+ }
301
+ }
302
+ ```
303
+
304
+ This is intentional — explicit event handling keeps data flow predictable and avoids the hidden side effects that two-way binding can introduce.
305
+
306
+ ---
307
+
308
+ ## 12. Treating `init$` as a plain object
247
309
 
248
310
  `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
311