@symbiotejs/symbiote 3.0.4 → 3.1.1
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_REFERENCE.md +37 -41
- package/CHANGELOG.md +17 -2
- package/README.md +22 -30
- package/core/tpl-processors.js +11 -5
- package/package.json +2 -2
- package/types/core/tpl-processors.d.ts.map +1 -1
package/AI_REFERENCE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Symbiote.js — AI Context Reference (v3.x)
|
|
2
2
|
|
|
3
3
|
> **Purpose**: Authoritative reference for AI code assistants. All information is derived from source code analysis of [symbiote.js](https://github.com/symbiotejs/symbiote.js).
|
|
4
|
-
> Current version: **3.
|
|
4
|
+
> Current version: **3.1.0**. Zero dependencies. ~6 KB gzip.
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -33,25 +33,13 @@ Symbiote extends `HTMLElement`. Every component is a Custom Element.
|
|
|
33
33
|
|
|
34
34
|
```js
|
|
35
35
|
class MyComponent extends Symbiote {
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
count: 0,
|
|
40
|
-
onBtnClick: () => {
|
|
41
|
-
this.$.count++;
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Called once after init$ is processed but BEFORE template is rendered
|
|
46
|
-
initCallback() {}
|
|
36
|
+
// Class properties as initial values (fallback)
|
|
37
|
+
name = 'World';
|
|
38
|
+
count = 0;
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Safe to access this.ref, this.$, DOM children here
|
|
40
|
+
onBtnClick() {
|
|
41
|
+
this.$.count++;
|
|
51
42
|
}
|
|
52
|
-
|
|
53
|
-
// Called when element is disconnected and readyToDestroy is true
|
|
54
|
-
destroyCallback() {}
|
|
55
43
|
}
|
|
56
44
|
|
|
57
45
|
// Template — assigned via static property SETTER, outside the class body
|
|
@@ -70,6 +58,13 @@ MyComponent.reg('my-component');
|
|
|
70
58
|
> Using `static template = html\`...\`` inside the class declaration **will NOT work**.
|
|
71
59
|
> Templates are plain HTML strings, NOT JSX. Use the `html` tagged template literal.
|
|
72
60
|
|
|
61
|
+
### Lifecycle callbacks (override in subclass)
|
|
62
|
+
| Method | When called |
|
|
63
|
+
|--------|------------|
|
|
64
|
+
| `initCallback()` | Once, after state initialized, before render (if `pauseRender=true`) or normally after render |
|
|
65
|
+
| `renderCallback()` | Once, after template is rendered and attached to DOM |
|
|
66
|
+
| `destroyCallback()` | On disconnect, after 100ms delay, only if `readyToDestroy=true` |
|
|
67
|
+
|
|
73
68
|
### Usage in HTML
|
|
74
69
|
```html
|
|
75
70
|
<my-component></my-component>
|
|
@@ -95,21 +90,24 @@ Binds `propName` from component state to the text content of a text node. Works
|
|
|
95
90
|
### Property binding (element's own properties)
|
|
96
91
|
```html
|
|
97
92
|
<button ${{onclick: 'handlerName'}}>Click</button>
|
|
98
|
-
<div
|
|
93
|
+
<div>{{myProp}}</div>
|
|
99
94
|
```
|
|
100
95
|
The `${{key: 'value'}}` interpolation creates a `bind="key:value;"` attribute. Keys are DOM element property names. Values are component state property names (strings).
|
|
101
96
|
|
|
102
|
-
**
|
|
97
|
+
**Class property fallback (3.x):** For any binding key not found in `init$`, Symbiote checks own instance properties first (`Object.hasOwn` — safe from inherited `HTMLElement` collisions), then prototype methods. Functions are automatically `.bind()`-ed to the component instance:
|
|
103
98
|
```js
|
|
104
99
|
class MyComp extends Symbiote {
|
|
105
|
-
// Approach 1: state property
|
|
106
|
-
init$ = {
|
|
100
|
+
// Approach 1: state property in init$
|
|
101
|
+
init$ = { count: 0 };
|
|
107
102
|
|
|
108
|
-
// Approach 2: class method (fallback)
|
|
103
|
+
// Approach 2: class property / method (fallback)
|
|
104
|
+
label = 'Click me';
|
|
109
105
|
onSubmit() { console.log('submitted'); }
|
|
110
106
|
}
|
|
111
107
|
```
|
|
112
108
|
|
|
109
|
+
> **Recommendation:** Use class property fallback for **simple components** — keeps code compact. For **complex components** with many reactive properties, prefer `init$` to explicitly separate reactive state from regular class properties.
|
|
110
|
+
|
|
113
111
|
### Nested property binding
|
|
114
112
|
```html
|
|
115
113
|
<div ${{'style.color': 'colorProp'}}>Text</div>
|
|
@@ -155,16 +153,16 @@ Prefixes control which data context a binding resolves to:
|
|
|
155
153
|
| `^` | Parent inherited | `{{^parentProp}}` | Walk up DOM ancestry to find nearest component that has this prop |
|
|
156
154
|
| `*` | Shared context | `{{*sharedProp}}` | Shared context scoped by `ctx` attribute or CSS `--ctx` |
|
|
157
155
|
| `/` | Named context | `{{APP/myProp}}` | Global named context identified by key before `/` |
|
|
158
|
-
| `--` | CSS Data |
|
|
156
|
+
| `--` | CSS Data | `{{--my-css-var}}` | Read value from CSS custom property |
|
|
159
157
|
| `+` | Computed | (in init$) `'+sum': () => ...` | Function recalculated when local dependencies change (auto-tracked) |
|
|
160
158
|
|
|
161
159
|
### Examples in init$
|
|
162
160
|
```js
|
|
163
161
|
init$ = {
|
|
164
|
-
localProp: 'hello', // local
|
|
165
|
-
'*sharedProp': 'shared value', // shared context
|
|
166
|
-
'APP/globalProp': 42, // named context
|
|
167
|
-
'+computed': () => this.$.a + this.$.b, // local computed (
|
|
162
|
+
localProp: 'hello', // local (prefer class properties for simple cases)
|
|
163
|
+
'*sharedProp': 'shared value', // shared context (requires init$)
|
|
164
|
+
'APP/globalProp': 42, // named context (requires init$)
|
|
165
|
+
'+computed': () => this.$.a + this.$.b, // local computed (requires init$)
|
|
168
166
|
};
|
|
169
167
|
```
|
|
170
168
|
|
|
@@ -277,18 +275,15 @@ Components grouped by the `ctx` HTML attribute (or `--ctx` CSS custom property)
|
|
|
277
275
|
|
|
278
276
|
```js
|
|
279
277
|
class UploadBtn extends Symbiote {
|
|
280
|
-
init$ = {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
},
|
|
278
|
+
init$ = { '*files': [] }
|
|
279
|
+
|
|
280
|
+
onUpload() {
|
|
281
|
+
this.$['*files'] = [...this.$['*files'], newFile];
|
|
285
282
|
}
|
|
286
283
|
}
|
|
287
284
|
|
|
288
285
|
class FileList extends Symbiote {
|
|
289
|
-
init$ = {
|
|
290
|
-
'*files': [], // same shared prop — first-registered value wins
|
|
291
|
-
}
|
|
286
|
+
init$ = { '*files': [] } // same shared prop — first-registered value wins
|
|
292
287
|
}
|
|
293
288
|
```
|
|
294
289
|
|
|
@@ -434,10 +429,11 @@ class MyList extends Symbiote {
|
|
|
434
429
|
{ name: 'Alice', role: 'Admin' },
|
|
435
430
|
{ name: 'Bob', role: 'User' },
|
|
436
431
|
],
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
onItemClick(e) {
|
|
435
|
+
console.log('clicked');
|
|
436
|
+
}
|
|
441
437
|
}
|
|
442
438
|
|
|
443
439
|
MyList.template = html`
|
|
@@ -710,7 +706,7 @@ class MyComponent extends Symbiote {
|
|
|
710
706
|
|
|
711
707
|
Or in template:
|
|
712
708
|
```html
|
|
713
|
-
<div
|
|
709
|
+
<div>{{--my-css-prop}}</div>
|
|
714
710
|
```
|
|
715
711
|
|
|
716
712
|
Update with: `this.updateCssData()` / `this.dropCssDataCache()`.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.1.0
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Class property fallback (generalized).**
|
|
8
|
+
Bindings not found in `init$` now fall back to own class properties (checked via `Object.hasOwn`), not just `on*` event handlers. Functions are auto-bound to the component instance. Inherited `HTMLElement` properties are never picked up.
|
|
9
|
+
```js
|
|
10
|
+
class MyComp extends Symbiote {
|
|
11
|
+
label = 'Click me';
|
|
12
|
+
onSubmit() { console.log('submitted'); }
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
Previously only `on*` handlers supported this fallback.
|
|
16
|
+
|
|
3
17
|
## 3.0.0
|
|
4
18
|
|
|
5
19
|
### ⚠️ Breaking Changes
|
|
@@ -128,9 +142,10 @@
|
|
|
128
142
|
{ pattern: '/settings', load: () => import('./pages/settings.js') }
|
|
129
143
|
```
|
|
130
144
|
|
|
131
|
-
- **
|
|
132
|
-
`
|
|
145
|
+
- **Class property fallback.**
|
|
146
|
+
Bindings not in `init$` fall back to own class properties/methods:
|
|
133
147
|
```js
|
|
148
|
+
label = 'Click me';
|
|
134
149
|
onSubmit() { console.log('submitted'); }
|
|
135
150
|
```
|
|
136
151
|
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Symbiote.js gives you the convenience of a modern framework while staying close
|
|
|
17
17
|
- **Exit animations** — `animateOut(el)` for CSS-driven exit transitions, integrated into itemize API.
|
|
18
18
|
- **Dev mode** — `Symbiote.devMode` enables verbose warnings for unresolved bindings.
|
|
19
19
|
- **DSD hydration** — `ssrMode` supports both light DOM and Declarative Shadow DOM.
|
|
20
|
-
- **
|
|
20
|
+
- **Class property fallback** — binding keys not in `init$` fall back to own class properties/methods.
|
|
21
21
|
- And [more](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md).
|
|
22
22
|
|
|
23
23
|
## Quick start
|
|
@@ -29,11 +29,9 @@ No install needed — run this directly in a browser:
|
|
|
29
29
|
import Symbiote, { html } from 'https://esm.run/@symbiotejs/symbiote';
|
|
30
30
|
|
|
31
31
|
class MyCounter extends Symbiote {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.$.count++;
|
|
36
|
-
},
|
|
32
|
+
count = 0;
|
|
33
|
+
increment() {
|
|
34
|
+
this.$.count++;
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
37
|
|
|
@@ -116,12 +114,10 @@ http.createServer(async (req, res) => {
|
|
|
116
114
|
|
|
117
115
|
```js
|
|
118
116
|
class TodoItem extends Symbiote {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.$.done = !this.$.done;
|
|
124
|
-
},
|
|
117
|
+
text = '';
|
|
118
|
+
done = false;
|
|
119
|
+
toggle() {
|
|
120
|
+
this.$.done = !this.$.done;
|
|
125
121
|
}
|
|
126
122
|
}
|
|
127
123
|
|
|
@@ -153,7 +149,7 @@ export default html`
|
|
|
153
149
|
```
|
|
154
150
|
|
|
155
151
|
The `html` function supports two interpolation modes:
|
|
156
|
-
- **Object** → reactive binding: `${{
|
|
152
|
+
- **Object** → reactive binding: `${{onclick: 'handler'}}`
|
|
157
153
|
- **String/number** → native concatenation: `${pageTitle}`
|
|
158
154
|
|
|
159
155
|
### Itemize (lists)
|
|
@@ -167,9 +163,10 @@ class TaskList extends Symbiote {
|
|
|
167
163
|
{ name: 'Buy groceries' },
|
|
168
164
|
{ name: 'Write docs' },
|
|
169
165
|
],
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
onItemClick() {
|
|
169
|
+
console.log('clicked!');
|
|
173
170
|
}
|
|
174
171
|
}
|
|
175
172
|
|
|
@@ -224,25 +221,19 @@ Inspired by native HTML `name` attributes — like how `<input name="group">` gr
|
|
|
224
221
|
|
|
225
222
|
```js
|
|
226
223
|
class UploadBtn extends Symbiote {
|
|
227
|
-
init$ = {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.$['*files'] = [...this.$['*files'], newFile];
|
|
232
|
-
},
|
|
224
|
+
init$ = { '*files': [] }
|
|
225
|
+
|
|
226
|
+
onUpload() {
|
|
227
|
+
this.$['*files'] = [...this.$['*files'], newFile];
|
|
233
228
|
}
|
|
234
229
|
}
|
|
235
230
|
|
|
236
231
|
class FileList extends Symbiote {
|
|
237
|
-
init$ = {
|
|
238
|
-
'*files': [],
|
|
239
|
-
}
|
|
232
|
+
init$ = { '*files': [] }
|
|
240
233
|
}
|
|
241
234
|
|
|
242
235
|
class StatusBar extends Symbiote {
|
|
243
|
-
init$ = {
|
|
244
|
-
'*files': [],
|
|
245
|
-
}
|
|
236
|
+
init$ = { '*files': [] }
|
|
246
237
|
}
|
|
247
238
|
```
|
|
248
239
|
|
|
@@ -328,7 +319,7 @@ class MyWidget extends Symbiote {
|
|
|
328
319
|
}
|
|
329
320
|
|
|
330
321
|
MyWidget.template = html`
|
|
331
|
-
<span
|
|
322
|
+
<span>{{--label}}</span>
|
|
332
323
|
`;
|
|
333
324
|
```
|
|
334
325
|
|
|
@@ -356,9 +347,10 @@ Symbiote and Lit have similar base sizes, but Symbiote's **5.9 kb** includes mor
|
|
|
356
347
|
|
|
357
348
|
All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
|
|
358
349
|
|
|
359
|
-
## Docs
|
|
350
|
+
## Docs & Examples
|
|
360
351
|
|
|
361
352
|
- [Documentation](https://github.com/symbiotejs/symbiote.js/blob/main/docs/README.md)
|
|
353
|
+
- [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
|
|
362
354
|
- [AI Reference](https://github.com/symbiotejs/symbiote.js/blob/main/AI_REFERENCE.md)
|
|
363
355
|
- [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md)
|
|
364
356
|
|
package/core/tpl-processors.js
CHANGED
|
@@ -60,10 +60,15 @@ function domBindProcessor(fr, fnCtx) {
|
|
|
60
60
|
if (!fnCtx.has(valKey) && fnCtx.allowTemplateInits) {
|
|
61
61
|
if (valKey.startsWith(DICT.ATTR_BIND_PX)) {
|
|
62
62
|
fnCtx.add(valKey, fnCtx.getAttribute(valKey.replace(DICT.ATTR_BIND_PX, '')));
|
|
63
|
+
} else if (Object.hasOwn(fnCtx, valKey) && fnCtx[valKey] !== undefined) {
|
|
64
|
+
let ownVal = fnCtx[valKey];
|
|
65
|
+
fnCtx.add(valKey, typeof ownVal === 'function' ? ownVal.bind(fnCtx) : ownVal);
|
|
66
|
+
} else if (typeof fnCtx[valKey] === 'function') {
|
|
67
|
+
fnCtx.add(valKey, fnCtx[valKey].bind(fnCtx));
|
|
63
68
|
} else {
|
|
64
69
|
fnCtx.add(valKey, null);
|
|
65
70
|
// Dev-only: warn about bindings that aren't in init$ (likely typos)
|
|
66
|
-
if (fnCtx.Symbiote?.devMode
|
|
71
|
+
if (fnCtx.Symbiote?.devMode) {
|
|
67
72
|
let known = Object.keys(fnCtx.init$).filter((k) => !k.startsWith('+'));
|
|
68
73
|
console.warn(
|
|
69
74
|
`[Symbiote dev] <${fnCtx.localName}>: binding key "${valKey}" not found in init$ (auto-initialized to null).\n`
|
|
@@ -72,10 +77,6 @@ function domBindProcessor(fr, fnCtx) {
|
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
|
-
// In case of event handler is null, bind to fallback method (if defined):
|
|
76
|
-
if (prop.startsWith('on') && fnCtx.localCtx.read(valKey) === null && typeof fnCtx[valKey] === 'function') {
|
|
77
|
-
fnCtx.add(valKey, fnCtx[valKey].bind(fnCtx), true);
|
|
78
|
-
}
|
|
79
80
|
fnCtx.sub(valKey, (val) => {
|
|
80
81
|
if (castType === 'double') {
|
|
81
82
|
val = !!val;
|
|
@@ -159,6 +160,11 @@ const txtNodesProcessor = function (fr, fnCtx) {
|
|
|
159
160
|
if (prop.startsWith(DICT.ATTR_BIND_PX)) {
|
|
160
161
|
fnCtx.add(prop, fnCtx.getAttribute(prop.replace(DICT.ATTR_BIND_PX, '')));
|
|
161
162
|
fnCtx.initAttributeObserver();
|
|
163
|
+
} else if (Object.hasOwn(fnCtx, prop) && fnCtx[prop] !== undefined) {
|
|
164
|
+
let ownVal = fnCtx[prop];
|
|
165
|
+
fnCtx.add(prop, typeof ownVal === 'function' ? ownVal.bind(fnCtx) : ownVal);
|
|
166
|
+
} else if (typeof fnCtx[prop] === 'function') {
|
|
167
|
+
fnCtx.add(prop, fnCtx[prop].bind(fnCtx));
|
|
162
168
|
} else {
|
|
163
169
|
fnCtx.add(prop, null);
|
|
164
170
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@symbiotejs/symbiote",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.1.1",
|
|
5
5
|
"description": "Symbiote.js - zero-dependency close-to-platform frontend library to build super-powered web components",
|
|
6
6
|
"author": "team@rnd-pro.com",
|
|
7
7
|
"license": "MIT",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"prepare": "git config core.hooksPath .git-hooks",
|
|
10
10
|
"types": "rm -rf types && tsc -p dts.cfg.json && node scripts/clean-dts.js",
|
|
11
11
|
"prepublishOnly": "npm test",
|
|
12
|
-
"pub": "npm run types && node scripts/update-exports.js && npm publish
|
|
12
|
+
"pub": "npm run types && node scripts/update-exports.js && npm publish",
|
|
13
13
|
"postinstall": "node scripts/postinstall.js",
|
|
14
14
|
"test": "node --test test/node/*.test.js && npx playwright test",
|
|
15
15
|
"test:unit": "node --test test/node/*.test.js",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tpl-processors.d.ts","sourceRoot":"","sources":["../../core/tpl-processors.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tpl-processors.d.ts","sourceRoot":"","sources":["../../core/tpl-processors.js"],"names":[],"mappings":"0BAiIgD,CAAC,SAApC,qCAAkC,MACpC,gBAAgB,SAChB,CAAC"}
|