@symbiotejs/symbiote 3.8.0 → 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/docs/context.md CHANGED
@@ -1,38 +1,45 @@
1
1
  # Context
2
2
 
3
- Usage context is one of the central things in Symbiote.js. Every Symbiote component is able to analyze its environment, read external settings from its actual position in DOM, and provide data to related components. Symbiote.js utilizes the DOM structure as the basic entity for data flow management and interconnection.
3
+ Context is the central concept in Symbiote.js. Rather than passing data through prop chains or maintaining a separate global store, Symbiote uses the DOM structure itself as the data flow graph. Every component can read from and write to multiple data sources - its **context** is the union of all the sources currently accessible to it.
4
4
 
5
- Component context is a sum of all accessible data sources. Each source represents its own type of interaction. Let's have a look at them.
5
+ There are seven context types, each addressed by a token prefix in property keys:
6
+
7
+ | Token | Context Type | Scope |
8
+ |-------|-------------|-------|
9
+ | _(none)_ | [Local](#local-context) | Component instance |
10
+ | `^` | [Pop-up](#pop-up-context) | Nearest ancestor that owns the property |
11
+ | `*` | [Shared](#shared-context) | All components sharing a `ctx` attribute |
12
+ | `name/` | [Named](#named-context) | Any component, anywhere |
13
+ | `--` | [CSS Data](#css-data-context) | Inherited from the CSS cascade |
14
+ | `@` | [Attribute](./attributes.md) | HTML attribute on the element |
15
+ | `+` | [Computed](./properties.md#computed-properties) | Derived, auto-recalculated value |
16
+
17
+ ---
6
18
 
7
19
  ## Local context
8
20
 
9
- Local context properties work the same way as component state in most other libraries:
21
+ Local context is the component's own reactive state, scoped to the instance. It works the same way as component state in other frameworks and is invisible to other components.
22
+
23
+ Define properties in `init$`:
10
24
  ```js
11
25
  class MyComponent extends Symbiote {
12
-
13
26
  init$ = {
14
27
  myProperty: 'some value',
15
28
  }
16
-
17
29
  }
18
30
  ```
19
31
 
20
- Use the `$` proxy to read and write values:
32
+ Use the `$` proxy to read and write values at runtime:
21
33
  ```js
22
34
  class MyComponent extends Symbiote {
23
-
24
35
  init$ = {
25
36
  myProperty: 'some value',
26
37
  }
27
38
 
28
39
  renderCallback() {
29
- // Read:
30
40
  console.log(this.$.myProperty); // > 'some value'
31
-
32
- // Write:
33
41
  this.$.myProperty = 'new value';
34
42
  }
35
-
36
43
  }
37
44
 
38
45
  MyComponent.template = html`
@@ -40,97 +47,143 @@ MyComponent.template = html`
40
47
  `;
41
48
  ```
42
49
 
50
+ For simple components without shared or computed props, you can also declare properties as plain class fields - Symbiote picks them up automatically via class-field fallback:
51
+ ```js
52
+ class MyComponent extends Symbiote {
53
+ count = 0;
54
+ label = 'Hello';
55
+ }
56
+ ```
57
+
58
+ > Use `init$` when you need shared (`*`), computed (`+`), or attribute (`@`) props in the same declaration - those tokens are only recognized inside `init$`.
59
+
60
+ See [Properties →](./properties.md) for the full property API: `add$()`, `sub()`, `set$()`, computed props, and more.
61
+
62
+ ---
63
+
43
64
  ## Named context
44
65
 
45
- Named context is an external abstract data source accessed by its name. It can contain any application data or be used for a dedicated purpose.
66
+ Named context is a global, named data store created independently of any component. Any component can read from or write to it using the `CONTEXT_NAME/property` syntax - regardless of DOM position.
67
+
68
+ Use named context when data must be shared across unrelated parts of the application.
69
+
70
+ **Creating a named context:**
71
+ ```js
72
+ import { PubSub } from '@symbiotejs/symbiote';
73
+
74
+ let appCtx = PubSub.registerCtx({
75
+ theme: 'light',
76
+ user: null,
77
+ }, 'APP');
78
+ ```
79
+
80
+ **Accessing it from a component:**
81
+ ```js
82
+ class MyComponent extends Symbiote {
83
+ init$ = {
84
+ 'APP/theme': 'light', // optional local fallback - used if the named context hasn't published yet
85
+ }
86
+
87
+ renderCallback() {
88
+ console.log(this.$['APP/theme']); // read
89
+ this.$['APP/theme'] = 'dark'; // write - updates the named context for all subscribers
90
+ }
91
+ }
92
+
93
+ MyComponent.template = html`
94
+ <div ${{'@class': 'APP/theme'}}>...</div>
95
+ `;
96
+ ```
46
97
 
47
- Example using named context as a localization tool:
98
+ **Example - localization:**
48
99
  ```js
49
100
  import Symbiote, { html, PubSub } from '@symbiotejs/symbiote';
50
101
 
51
- // Create localization map for English:
52
- let EN = {
102
+ let l10nCtx = PubSub.registerCtx({
53
103
  users: 'Users',
54
104
  comments: 'Comments',
55
105
  likes: 'Likes',
56
- };
57
-
58
- // Create localization context:
59
- let l10nCtx = PubSub.registerCtx(EN, 'L10N');
60
-
61
- // Use localized strings in templates:
62
- class MyComponent extends Symbiote { ... }
106
+ }, 'L10N');
63
107
 
64
108
  MyComponent.template = html`
65
109
  <div>{{L10N/users}} - {{numberOfUsers}}</div>
66
110
  <div>{{L10N/comments}} - {{numberOfComments}}</div>
67
111
  <div>{{L10N/likes}} - {{numberOfLikes}}</div>
68
112
  `;
69
- ```
70
-
71
- Use `/` token with the context key to access properties: `L10N/users`.
72
113
 
73
- Switching language:
74
- ```js
75
- let ES = {
114
+ // Switch language at any time - all subscribed components update instantly:
115
+ l10nCtx.multiPub({
76
116
  users: 'Usuarios',
77
117
  comments: 'Comentarios',
78
118
  likes: 'Gustos',
79
- };
80
-
81
- l10nCtx.multiPub(ES);
119
+ });
82
120
  ```
83
121
 
84
- Read and modify named context properties with the `$` proxy:
122
+ You can also read and modify named context directly from any component using the `$` proxy:
85
123
  ```js
86
124
  class MyComponent extends Symbiote {
87
125
  renderCallback() {
88
- // Read:
89
126
  console.log(this.$['L10N/users']);
90
-
91
- // Modify:
92
127
  this.$['L10N/users'] = 'ユーザー';
93
128
  }
94
129
  }
95
130
  ```
96
131
 
97
- More information about `PubSub` in the [PubSub section](./pubsub.md).
132
+ More information about `PubSub` in the [PubSub ](./pubsub.md) section.
133
+
134
+ ---
98
135
 
99
136
  ## Pop-up context
100
137
 
101
- Pop-up binding helps control component interactions based on their DOM position and hierarchy. It works similarly to the CSS cascade model.
138
+ Pop-up context lets a child component reach a property defined by an ancestor, without the ancestor needing to pass it down explicitly. Symbiote walks up the DOM tree until it finds a component that has the requested property in its data context.
139
+
140
+ Use the `^` token to reference a pop-up property - in both text bindings and event handlers:
141
+ ```html
142
+ <!-- Text binding to ancestor property: -->
143
+ <div>{{^parentTitle}}</div>
144
+
145
+ <!-- Handler binding to ancestor method: -->
146
+ <button ${{onclick: '^onButtonClicked'}}>Click me!</button>
147
+ ```
102
148
 
103
- Use the `^` token to reference a higher-level property:
104
149
  ```js
105
- class MyComponent extends Symbiote {}
150
+ class MyButton extends Symbiote {}
106
151
 
107
- MyComponent.template = html`
152
+ MyButton.template = html`
153
+ <span>{{^parentTitle}}</span>
108
154
  <button ${{onclick: '^onButtonClicked'}}>Click me!</button>
109
155
  `;
110
156
  ```
111
- Symbiote walks up the DOM tree until it finds the component with `onButtonClicked` registered in its data context.
157
+
158
+ Symbiote walks up the DOM tree until it finds a component with the requested property registered in its context:
159
+ ```js
160
+ class MyEditor extends Symbiote {
161
+ init$ = {
162
+ onButtonClicked: () => console.log('clicked'),
163
+ editorTitle: 'My Editor',
164
+ }
165
+ }
166
+ ```
112
167
 
113
168
  > [!IMPORTANT]
114
- > The `^` lookup only checks the parent's **data context** (properties registered via `init$` or `add$()`). Class property fallbacks are **not** resolved during this walk. Always define `^`-targeted properties in the parent's `init$`:
169
+ > Pop-up lookup only searches the **data context** (properties registered via `init$` or `add$()`). Plain class properties are not resolved this way. Always declare `^`-targeted properties in the parent's `init$`:
115
170
  > ```js
116
171
  > class ParentComponent extends Symbiote {
117
172
  > init$ = {
118
- > onButtonClicked: () => { console.log('clicked'); },
173
+ > onButtonClicked: () => console.log('clicked'),
119
174
  > parentTitle: 'Hello',
120
175
  > }
121
176
  > }
122
177
  > ```
123
178
 
124
- This is useful for composition, customization, and responsibility splitting:
179
+ Pop-up context is useful for composition - the same child component adapts to whichever ancestor provides the expected behavior:
125
180
  ```js
126
181
  html`
127
182
  <my-text-editor>
128
183
  <complete-toolbar></complete-toolbar>
129
184
  </my-text-editor>
130
185
  `;
131
- ```
132
- Or:
133
- ```js
186
+ // or:
134
187
  html`
135
188
  <my-text-editor>
136
189
  <simplified-toolbar></simplified-toolbar>
@@ -139,19 +192,21 @@ html`
139
192
  `;
140
193
  ```
141
194
 
142
- > Like in CSS, pop-up properties have no collision guard use additional prefixes in uncontrolled environments.
195
+ > Like the CSS cascade, pop-up context has no collision guard. Use additional prefixes (e.g. `myApp_onSave`) in environments you don't fully control.
196
+
197
+ ---
143
198
 
144
199
  ## Shared context
145
200
 
146
- Shared context works similarly to native HTML radio inputs set the `name` attribute and different inputs connect into one workflow.
201
+ Shared context is inspired by native HTML `name` attributes - the same way `<input name="group">` connects radio buttons into one workflow, the `ctx` attribute connects Symbiote components into a shared data context. Components with the same `ctx` name access a common reactive store with no intermediary component required.
147
202
 
148
- In 3.x, an explicit context name is **required** for shared properties. Set it using the `ctx` HTML attribute:
203
+ **Assign a context name via the `ctx` HTML attribute:**
149
204
  ```html
150
205
  <upload-btn ctx="gallery"></upload-btn>
151
206
  <file-list ctx="gallery"></file-list>
152
207
  ```
153
208
 
154
- Or using the `--ctx` CSS custom property:
209
+ **Or via the `--ctx` CSS custom property**, which cascades like any CSS variable:
155
210
  ```css
156
211
  .gallery-section {
157
212
  --ctx: gallery;
@@ -165,49 +220,64 @@ Or using the `--ctx` CSS custom property:
165
220
  ```
166
221
 
167
222
  The CSS approach is useful when:
168
- - You want **layout-driven grouping** components inherit the context from their visual container rather than repeating the attribute on each one
169
- - You need to **reassign context at different DOM levels** just like any CSS custom property, `--ctx` cascades and can be overridden in nested selectors
170
- - You work in a **framework-agnostic setup** CSS can be managed separately from markup, so context assignment doesn't depend on template engine or host framework
223
+ - You want **layout-driven grouping** - components inherit context from their visual container rather than repeating the attribute on each one
224
+ - You need to **override context at different DOM levels** - just like any CSS custom property, `--ctx` cascades and can be reassigned in nested selectors
225
+ - You work in a **framework-agnostic setup** - CSS context assignment is independent of the host template engine
171
226
 
172
- Then use the `*` token to define shared properties:
227
+ **Define shared properties with the `*` token:**
173
228
  ```js
174
229
  class UploadBtn extends Symbiote {
175
230
  init$ = { '*files': [] }
176
231
 
177
- onUpload() {
232
+ onUpload(newFile) {
178
233
  this.$['*files'] = [...this.$['*files'], newFile];
179
234
  }
180
235
  }
181
236
 
182
237
  class FileList extends Symbiote {
183
- init$ = { '*files': [] } // same shared prop first-registered value wins
238
+ init$ = { '*files': [] } // same shared prop - first-registered value wins
184
239
  }
185
240
  ```
186
241
 
187
- Both components access the same `*files` state no parent component, no prop drilling, no global store.
242
+ Both components read and write the same `*files` store. When one updates it, the other reacts automatically - no parent component, no prop drilling, no global store.
188
243
 
189
244
  ### Context name resolution
190
245
 
191
- The context name is resolved in this order (first match wins):
246
+ The `ctx` name is resolved in this order (first match wins):
247
+
248
+ 1. `ctx="name"` HTML attribute on the element
249
+ 2. `--ctx` CSS custom property inherited from ancestors
192
250
 
193
- 1. `ctx="name"` HTML attribute
194
- 2. `--ctx` CSS custom property (inherited from ancestors)
251
+ > **IMPORTANT**: In Symbiote 3.x, `*` properties **require** an explicit `ctx` attribute or `--ctx` variable. Without one, no shared context is created, `*` props have no effect, and dev mode will warn (W6).
195
252
 
196
- > **IMPORTANT**: In 3.x, `*` properties **require** an explicit `ctx` attribute or `--ctx` CSS variable. Without one, the shared context is not created, `*` props have no effect, and dev mode will warn about it.
253
+ ---
197
254
 
198
255
  ## CSS Data context
199
256
 
200
- Symbiote components can initiate their properties from CSS custom property values:
201
- ```css
202
- :root {
203
- --header: 'CSS Data';
204
- --text: 'Hello!';
257
+ Symbiote components can initialize their properties from CSS custom property values, enabling CSS-driven configuration: theme tokens, layout parameters, or localized strings - all settable from a stylesheet without touching JavaScript.
258
+
259
+ Use `cssInit$` to explicitly declare CSS-initialized properties with fallback values:
260
+ ```js
261
+ class MyWidget extends Symbiote {
262
+ cssInit$ = {
263
+ '--columns': 1,
264
+ '--label': '',
265
+ }
205
266
  }
267
+
268
+ MyWidget.template = html`
269
+ <span>{{--label}}</span>
270
+ `;
206
271
  ```
207
272
 
208
- > CSS custom property values should be valid JSON values, parseable with `JSON.parse()`. Use numbers for boolean flags (`0`/`1`).
273
+ ```css
274
+ my-widget {
275
+ --columns: 3;
276
+ --label: 'Click me';
277
+ }
278
+ ```
209
279
 
210
- Use CSS values in templates directly:
280
+ You can also use `--` bindings directly in templates without `cssInit$`:
211
281
  ```js
212
282
  class TestApp extends Symbiote {}
213
283
 
@@ -216,10 +286,79 @@ TestApp.template = html`
216
286
  <div>{{--text}}</div>
217
287
  `;
218
288
  ```
289
+ ```css
290
+ :root {
291
+ --header: 'CSS Data';
292
+ --text: 'Hello!';
293
+ }
294
+ ```
295
+
296
+ > CSS custom property values must be valid JSON - use quoted strings (`'text'`), numbers, and `0`/`1` for booleans.
219
297
 
220
- > CSS custom properties are used for value initialization only. After that, they act like normal local context properties.
298
+ > CSS properties are used for **initialization only**. After the component mounts, they act as normal local context properties and no longer track CSS changes. Call `this.updateCssData()` to re-read them after runtime CSS updates.
221
299
 
222
- More details in the [CSS Data](./css-data.md) section.
300
+ Full details - `updateCssData()`, `dropCssDataCache()`, `ResizeObserver` patterns, and SSR caveats - in the [CSS Data](./css-data.md) section.
301
+
302
+ ---
303
+
304
+ ## Choosing the right context type
305
+
306
+ | Situation | Use |
307
+ |-----------|-----|
308
+ | Component-local reactive state | Local - no token |
309
+ | Behavior or data passed from an ancestor | Pop-up - `^` |
310
+ | Sibling components sharing a workflow state | Shared - `*` |
311
+ | App-wide data, accessed from anywhere in the tree | Named - `/` |
312
+ | Component configured via CSS / design tokens | CSS Data - `--` |
313
+ | Reacting to HTML attribute values | Attribute - `@` |
314
+ | Values derived from other properties | Computed - `+` |
315
+
316
+ ---
317
+
318
+ ## All context types in one component
319
+
320
+ A concise example showing local, attribute, named, shared, and pop-up contexts working together:
321
+
322
+ ```js
323
+ import Symbiote, { html } from '@symbiotejs/symbiote';
324
+
325
+ class MyApp extends Symbiote {
326
+ init$ = {
327
+ localCtxProp: 'LOCAL',
328
+ '@attr-test': '', // bound to HTML attribute
329
+ 'APP/namedProp': 'NAMED', // named context
330
+ '*sharedProp': 'SHARED', // shared context (requires ctx="..." in HTML)
331
+ }
332
+
333
+ onUpdate() {
334
+ let suffix = ' updated';
335
+ this.$.localCtxProp += suffix;
336
+ this.$['APP/namedProp'] += suffix;
337
+ this.$['*sharedProp'] += suffix;
338
+ }
339
+ }
340
+
341
+ MyApp.template = html`
342
+ <div>local: {{localCtxProp}}</div>
343
+ <div>attribute: {{@attr-test}}</div>
344
+ <div>named: {{APP/namedProp}}</div>
345
+ <div>shared: {{*sharedProp}}</div>
346
+ <button ${{onclick: 'onUpdate'}}>Update</button>
347
+ <inner-el></inner-el> <!-- reads ^localCtxProp via pop-up -->
348
+ `;
349
+
350
+ MyApp.reg('my-app');
351
+
352
+ class InnerEl extends Symbiote {}
353
+ InnerEl.template = html`<h2>pop-up: {{^localCtxProp}}</h2>`;
354
+ InnerEl.reg('inner-el');
355
+ ```
356
+
357
+ ```html
358
+ <my-app attr-test="HTML value" ctx="my-ctx"></my-app>
359
+ ```
360
+
361
+ ---
223
362
 
224
363
  ## Property token summary
225
364
 
@@ -230,8 +369,8 @@ More details in the [CSS Data](./css-data.md) section.
230
369
  | `*` | Shared | `*sharedProp` |
231
370
  | `/` | Named | `APP/myProp` |
232
371
  | `--` | CSS Data | `--my-css-var` |
233
- | `@` | Attribute | `@my-attribute` |
234
- | `+` | Computed | `+computedProp` |
372
+ | `@` | Attribute - see [Attributes →](./attributes.md) | `@my-attribute` |
373
+ | `+` | Computed - see [Properties →](./properties.md#computed-properties) | `+computedProp` |
235
374
 
236
375
  ---
237
376
 
package/docs/ecosystem.md CHANGED
@@ -105,6 +105,3 @@ npx jsda init
105
105
  npx jsda serve
106
106
  ```
107
107
 
108
- ---
109
-
110
- Next: [Migration 2.x → 3.x →](./migration-2x-to-3x.md)
@@ -8,7 +8,6 @@ Symbiote.js extends HTMLElement natively. State changes update the DOM synchrono
8
8
 
9
9
  - [Full documentation (single file)](https://rnd-pro.com/symbiote/llms-full.txt): Complete merged reference — paste this into any AI tool for full context
10
10
  - [README](https://github.com/symbiotejs/symbiote.js/blob/main/README.md): Overview, quick start, and feature summary
11
- - [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md): Version history
12
11
 
13
12
  ## Core concepts
14
13
 
package/docs/webmcp.md CHANGED
@@ -1,12 +1,6 @@
1
- # WebMCP Experimental
1
+ # WebMCP
2
2
 
3
- Symbiote.js can expose the current browser UI state as native WebMCP tools. This feature is experimental and intended for testing with browser builds that expose native WebMCP APIs, such as Chrome Canary 150.
4
-
5
- Install the experimental npm release with the `webmcp` tag:
6
-
7
- ```shell
8
- npm i @symbiotejs/symbiote@webmcp
9
- ```
3
+ Symbiote.js can expose the current browser UI state as native WebMCP tools.
10
4
 
11
5
  ## Activation
12
6
 
@@ -14,7 +8,7 @@ WebMCP is optional. Import the extension before WebMCP-enabled components render
14
8
 
15
9
  ```js
16
10
  import Symbiote, { html } from '@symbiotejs/symbiote';
17
- import { ToolDescriptor } from '@symbiotejs/symbiote/webmcp';
11
+ import '@symbiotejs/symbiote/webmcp';
18
12
  ```
19
13
 
20
14
  ## Automatic Tools
@@ -64,6 +58,8 @@ removeItem_in_task-list_task-item_alpha
64
58
  Use `ToolDescriptor` for descriptions, input schemas, execution logic, and dynamic visibility:
65
59
 
66
60
  ```js
61
+ import { ToolDescriptor } from '@symbiotejs/symbiote/webmcp';
62
+
67
63
  class WebmcpPanel extends Symbiote {
68
64
  componentDescription = async () => {
69
65
  return 'Visible order editor with selected item state.';