@symbiotejs/symbiote 2.0.0-alpha.8 → 2.0.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/README.md +23 -15
- package/core/AppRouter.js +33 -22
- package/core/PubSub.js +19 -4
- package/core/README.md +13 -7
- package/core/{BaseComponent.js → Symbiote.js} +55 -65
- package/core/css.js +27 -3
- package/core/dictionary.js +15 -9
- package/core/html.js +20 -5
- package/core/index.js +5 -1
- package/core/{repeatProcessor.js → itemizeProcessor.js} +6 -6
- package/core/slotProcessor.js +48 -0
- package/core/tpl-processors.js +16 -63
- package/package.json +2 -42
- package/types/symbiote.d.ts +93 -91
- package/utils/UID.js +10 -2
- package/utils/README.md +0 -10
package/README.md
CHANGED
|
@@ -21,9 +21,11 @@ Best for:
|
|
|
21
21
|
> Symbiote.js is designed to give the level of freedom, you got with Vanilla JS and to give the convenience level, as you got from the modern frameworks at the same time.
|
|
22
22
|
|
|
23
23
|
## 🔥 Core benefits
|
|
24
|
-
*
|
|
24
|
+
* Symbiote.js - is a total agnostic. It can work with any of modern tools or in the target environment directly.
|
|
25
25
|
* No extra dependencies.
|
|
26
|
-
* Ultralight (~
|
|
26
|
+
* Ultralight (~6kb br/gzip).
|
|
27
|
+
* Global state-management already included, more powerful and flexible, than ever before.
|
|
28
|
+
* Styling engine already included, based on cutting-edge platform features.
|
|
27
29
|
* Blazing fast.
|
|
28
30
|
* Memory friendly (no immutables).
|
|
29
31
|
* CSP friendly - good for enterprise usage.
|
|
@@ -31,34 +33,44 @@ Best for:
|
|
|
31
33
|
* Easy to learn - nothing completely new for experienced developers, nothing complicated for newbies.
|
|
32
34
|
* Works in all modern browsers. As is.
|
|
33
35
|
* Easy to test.
|
|
34
|
-
* TypeScript friendly - use it in TS or JS projects from the
|
|
36
|
+
* TypeScript friendly - use it in TS or JS projects from the same source code.
|
|
35
37
|
* Integration friendly: works with any modern development stack.
|
|
36
|
-
* Lifecycle control: no need to initiate something from outside.
|
|
38
|
+
* Lifecycle control: no need to initiate or to remove something from outside.
|
|
37
39
|
* ESM friendly - native JavaScript modules are best!
|
|
38
40
|
* Developer Experience on the mind: compact & convenient APIs, habitual syntax.
|
|
39
41
|
* Open source (MIT license).
|
|
40
42
|
|
|
41
43
|
## 💎 Tech concept keypoints
|
|
42
|
-
* Native modern APIs instead of expensive libraries.
|
|
44
|
+
* Native modern APIs instead of performance-expensive external libraries.
|
|
43
45
|
* Shadow DOM is optional. Use it when you need it only.
|
|
44
46
|
* Total styling freedom: from the old classics to the cutting edge platform abilities.
|
|
45
47
|
* Native HTML instead of custom template syntax processing.
|
|
46
48
|
* Templates are out of the component or render function context. It’s just a simple JavaScript template literals. So you can keep or process them wherever you want.
|
|
47
|
-
* No logical operators in templates. Logic and presentation are strictly separated.
|
|
48
49
|
* Fast synchronous UI updates, no unexpected redraws.
|
|
49
50
|
* Full data context access from the document structure.
|
|
50
51
|
* Full data context availability for template bindings.
|
|
51
52
|
* DOM API friendly approach for the most performant solutions.
|
|
52
53
|
* Convenient object model access instead of opaque abstractions.
|
|
54
|
+
* CDN friendly: every module is a possible endpoint for build or delivery process.
|
|
55
|
+
|
|
56
|
+
## 🧠 DX
|
|
57
|
+
We believe, that good Developer Experience is not just a vulgar minimum of symbols you typing.
|
|
58
|
+
We believe, that good DX is ability to easily understand, what exactly you see in the code and what exactly you doing. Mental models behind your work are very important for the best result achievement. So, we don't like an excess abstraction layers and the black-box magic compilers.
|
|
59
|
+
|
|
60
|
+
We build our DX philosophy on these principles:
|
|
61
|
+
* We keep our entities close to platform native ones
|
|
62
|
+
* We don't invent things, which are should be explained on a special developer conference
|
|
63
|
+
* We try to keep maximum similarity between your code and what happens in your runtime
|
|
64
|
+
* We keep in minimum the count of the necessary steps needed to deploy your code
|
|
53
65
|
|
|
54
66
|
## 🍏 Quick start
|
|
55
67
|
The easiest way to try Symbiote.js is to create a simple `html` file in your text editor and connect the Symbiote base class from web:
|
|
56
68
|
|
|
57
69
|
```html
|
|
58
70
|
<script type="module">
|
|
59
|
-
import {
|
|
71
|
+
import { Symbiote, html } from 'https://symbiotejs.github.io/symbiote.js/core/Symbiote.js';
|
|
60
72
|
|
|
61
|
-
class MyComponent extends
|
|
73
|
+
class MyComponent extends Symbiote {
|
|
62
74
|
init$ = {
|
|
63
75
|
count: 0,
|
|
64
76
|
increment: () => {
|
|
@@ -67,9 +79,9 @@ The easiest way to try Symbiote.js is to create a simple `html` file in your tex
|
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
|
|
70
|
-
MyComponent.template =
|
|
82
|
+
MyComponent.template = html`
|
|
71
83
|
<h2>{{count}}</h2>
|
|
72
|
-
<button
|
|
84
|
+
<button ${{onclick: 'increment'}}>Click me!</button>
|
|
73
85
|
`;
|
|
74
86
|
|
|
75
87
|
MyComponent.reg('my-component');
|
|
@@ -100,11 +112,7 @@ Symbiote.js is supported and tested in all major modern desktop and mobile brows
|
|
|
100
112
|
* Opera
|
|
101
113
|
* etc.
|
|
102
114
|
|
|
103
|
-
**Internet Explorer** - is outdated and not supported anymore
|
|
104
|
-
|
|
105
|
-
https://uploadcare.com/blog/uploadcare-stops-internet-explorer-support/
|
|
106
|
-
|
|
107
|
-
(But it's possible with polyfills: https://github.com/webcomponents/polyfills/tree/master/packages/webcomponentsjs)
|
|
115
|
+
**Internet Explorer** - is outdated and not supported anymore.
|
|
108
116
|
|
|
109
117
|
## 💰 General sponsor
|
|
110
118
|
Big thanks to 🟡 **Uploadcare** for supporting this project!
|
package/core/AppRouter.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import PubSub from './PubSub.js';
|
|
2
2
|
|
|
3
3
|
export class AppRouter {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {{title?: String, default?: Boolean, error?: Boolean}} RouteDescriptor
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** @type {() => void} */
|
|
10
|
+
static #onPopstate;
|
|
11
|
+
/** @type {String} */
|
|
12
|
+
static #separator;
|
|
13
|
+
/** @type {String} */
|
|
14
|
+
static #routingEventName;
|
|
15
|
+
/** @type {Object<string, RouteDescriptor>} */
|
|
16
|
+
static appMap = Object.create(null);
|
|
17
|
+
|
|
18
|
+
static #print(msg) {
|
|
6
19
|
console.warn(msg);
|
|
7
20
|
}
|
|
8
21
|
|
|
@@ -26,12 +39,12 @@ export class AppRouter {
|
|
|
26
39
|
/** @param {String} name */
|
|
27
40
|
static set routingEventName(name) {
|
|
28
41
|
/** @private */
|
|
29
|
-
this
|
|
42
|
+
this.#routingEventName = name;
|
|
30
43
|
}
|
|
31
44
|
|
|
32
45
|
/** @returns {String} */
|
|
33
46
|
static get routingEventName() {
|
|
34
|
-
return this
|
|
47
|
+
return this.#routingEventName || 'sym-on-route';
|
|
35
48
|
}
|
|
36
49
|
|
|
37
50
|
static readAddressBar() {
|
|
@@ -69,7 +82,7 @@ export class AppRouter {
|
|
|
69
82
|
this.applyRoute(this.defaultRoute);
|
|
70
83
|
return;
|
|
71
84
|
} else if (!routeScheme) {
|
|
72
|
-
this
|
|
85
|
+
this.#print(`Route "${routeBase.route}" not found...`);
|
|
73
86
|
return;
|
|
74
87
|
}
|
|
75
88
|
let event = new CustomEvent(AppRouter.routingEventName, {
|
|
@@ -88,7 +101,7 @@ export class AppRouter {
|
|
|
88
101
|
static reflect(route, options = {}) {
|
|
89
102
|
let routeScheme = this.appMap[route];
|
|
90
103
|
if (!routeScheme) {
|
|
91
|
-
this
|
|
104
|
+
this.#print('Wrong route: ' + route);
|
|
92
105
|
return;
|
|
93
106
|
}
|
|
94
107
|
let routeStr = '?' + route;
|
|
@@ -116,22 +129,22 @@ export class AppRouter {
|
|
|
116
129
|
/** @param {String} char */
|
|
117
130
|
static setSeparator(char) {
|
|
118
131
|
/** @private */
|
|
119
|
-
this
|
|
132
|
+
this.#separator = char;
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
/** @returns {String} */
|
|
123
136
|
static get separator() {
|
|
124
|
-
return this
|
|
137
|
+
return this.#separator || '&';
|
|
125
138
|
}
|
|
126
139
|
|
|
127
140
|
/**
|
|
128
141
|
* @param {String} ctxName
|
|
129
|
-
* @param {Object<string,
|
|
142
|
+
* @param {Object<string, RouteDescriptor>} routingMap
|
|
130
143
|
* @returns {PubSub}
|
|
131
144
|
*/
|
|
132
|
-
static
|
|
145
|
+
static initRoutingCtx(ctxName, routingMap) {
|
|
133
146
|
this.setRoutingMap(routingMap);
|
|
134
|
-
let
|
|
147
|
+
let routingCtx = PubSub.registerCtx(
|
|
135
148
|
{
|
|
136
149
|
route: null,
|
|
137
150
|
options: null,
|
|
@@ -140,34 +153,32 @@ export class AppRouter {
|
|
|
140
153
|
ctxName
|
|
141
154
|
);
|
|
142
155
|
window.addEventListener(this.routingEventName, (/** @type {CustomEvent} */ e) => {
|
|
143
|
-
|
|
144
|
-
route: e.detail.route,
|
|
156
|
+
routingCtx.multiPub({
|
|
145
157
|
options: e.detail.options,
|
|
146
158
|
title: e.detail.options?.title || this.defaultTitle || '',
|
|
159
|
+
route: e.detail.route,
|
|
147
160
|
});
|
|
148
161
|
});
|
|
149
162
|
AppRouter.notify();
|
|
150
|
-
this
|
|
151
|
-
return
|
|
163
|
+
this.#initPopstateListener();
|
|
164
|
+
return routingCtx;
|
|
152
165
|
}
|
|
153
166
|
|
|
154
|
-
static initPopstateListener() {
|
|
155
|
-
if (this
|
|
167
|
+
static #initPopstateListener() {
|
|
168
|
+
if (this.#onPopstate) {
|
|
156
169
|
return;
|
|
157
170
|
}
|
|
158
171
|
/** @private */
|
|
159
172
|
this.__onPopstate = () => {
|
|
160
173
|
this.notify();
|
|
161
174
|
};
|
|
162
|
-
window.addEventListener('popstate', this
|
|
175
|
+
window.addEventListener('popstate', this.#onPopstate);
|
|
163
176
|
}
|
|
164
177
|
|
|
165
178
|
static removePopstateListener() {
|
|
166
|
-
window.removeEventListener('popstate', this
|
|
167
|
-
this
|
|
179
|
+
window.removeEventListener('popstate', this.#onPopstate);
|
|
180
|
+
this.#onPopstate = null;
|
|
168
181
|
}
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
AppRouter.appMap = Object.create(null);
|
|
172
|
-
|
|
173
184
|
export default AppRouter;
|
package/core/PubSub.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DICT } from './dictionary.js';
|
|
2
2
|
|
|
3
|
+
// structuredClone() is limited by supported types, so we use custom cloning:
|
|
3
4
|
function cloneObj(obj) {
|
|
4
5
|
let clone = (o) => {
|
|
5
6
|
for (let prop in o) {
|
|
@@ -45,7 +46,7 @@ export class PubSub {
|
|
|
45
46
|
PubSub.#warn('read', prop);
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
48
|
-
if (typeof prop === 'string' && prop.
|
|
49
|
+
if (typeof prop === 'string' && prop.startsWith(DICT.COMPUTED_PX)) {
|
|
49
50
|
/** @type {Function} */
|
|
50
51
|
let compFn = this.store[prop];
|
|
51
52
|
if (!this.__computedSet) {
|
|
@@ -89,6 +90,15 @@ export class PubSub {
|
|
|
89
90
|
PubSub.#warn('publish', prop);
|
|
90
91
|
return;
|
|
91
92
|
}
|
|
93
|
+
// @ts-expect-error
|
|
94
|
+
if (prop?.startsWith(DICT.COMPUTED_PX) && val.constructor !== Function) {
|
|
95
|
+
PubSub.#warn('publish computed', prop);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!(this.store[prop] === null || val === null) && typeof this.store[prop] !== typeof val) {
|
|
99
|
+
// @ts-expect-error
|
|
100
|
+
console.warn(`Symbiote PubSub: type warning for "${prop}" [${typeof this.store[prop]} -> ${typeof val}]`);
|
|
101
|
+
}
|
|
92
102
|
this.store[prop] = val;
|
|
93
103
|
this.notify(prop);
|
|
94
104
|
}
|
|
@@ -120,7 +130,12 @@ export class PubSub {
|
|
|
120
130
|
#processComputed() {
|
|
121
131
|
if (this.__computedSet) {
|
|
122
132
|
this.__computedSet.forEach((prop) => {
|
|
123
|
-
this
|
|
133
|
+
if (this[`__${prop}_timeout`]) {
|
|
134
|
+
window.clearTimeout(this[`__${prop}_timeout`]);
|
|
135
|
+
}
|
|
136
|
+
this[`__${prop}_timeout`] = window.setTimeout(() => {
|
|
137
|
+
this.notify(prop);
|
|
138
|
+
});
|
|
124
139
|
});
|
|
125
140
|
}
|
|
126
141
|
}
|
|
@@ -131,9 +146,9 @@ export class PubSub {
|
|
|
131
146
|
this.callbackMap[prop].forEach((callback) => {
|
|
132
147
|
callback(this.read(prop));
|
|
133
148
|
});
|
|
134
|
-
// @ts-expect-error
|
|
135
|
-
!prop?.startsWith(DICT.COMPUTED_PX) && this.#processComputed();
|
|
136
149
|
}
|
|
150
|
+
// @ts-expect-error
|
|
151
|
+
!prop?.startsWith(DICT.COMPUTED_PX) && this.#processComputed();
|
|
137
152
|
}
|
|
138
153
|
|
|
139
154
|
/**
|
package/core/README.md
CHANGED
|
@@ -3,23 +3,29 @@
|
|
|
3
3
|
### index.js
|
|
4
4
|
All-in-one exports.
|
|
5
5
|
|
|
6
|
-
###
|
|
6
|
+
### Symbiote.js
|
|
7
7
|
Base component class. Major utility for the web-component creation, template data binding and data management.
|
|
8
8
|
|
|
9
9
|
### html.js
|
|
10
|
-
Template literal tag
|
|
10
|
+
Template literal tag-function, that transforms interpolated binding descriptions into resulting html.
|
|
11
11
|
|
|
12
|
-
###
|
|
12
|
+
### css.js
|
|
13
|
+
Template literal tag-function, that creates the CSSStyleSheet instance.
|
|
14
|
+
|
|
15
|
+
### PubSub.js
|
|
13
16
|
Implements data layer for the local component context and the top level context both. The state management approach is based on simple well known pub/sub pattern.
|
|
14
17
|
|
|
15
18
|
### AppRouter.js
|
|
16
|
-
SPA routing utility. Based on native History API.
|
|
19
|
+
SPA routing utility. Based on browser-native History API.
|
|
17
20
|
|
|
18
21
|
### tpl-rpcessors.js
|
|
19
22
|
Template processing functions. Implements basic template processing flow.
|
|
20
23
|
|
|
21
|
-
###
|
|
22
|
-
Dynamic list rendering implementation.
|
|
24
|
+
### itemizeProcessor.js
|
|
25
|
+
Dynamic list items rendering implementation.
|
|
23
26
|
|
|
24
27
|
### dictionary.js
|
|
25
|
-
Dictionary for the set of the basic keys.
|
|
28
|
+
Dictionary for the set of the basic keys.
|
|
29
|
+
|
|
30
|
+
### slotProcessor.js
|
|
31
|
+
Light DOM support for the template `slot`s. This processor is optional since 2.x. and excluded from default template processing pipeline.
|
|
@@ -7,22 +7,21 @@ import { prepareStyleSheet } from '../utils/prepareStyleSheet.js';
|
|
|
7
7
|
import PROCESSORS from './tpl-processors.js';
|
|
8
8
|
import { parseCssPropertyValue } from '../utils/parseCssPropertyValue.js';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
let styleMutationObserver = null;
|
|
10
|
+
export { html } from './html.js';
|
|
11
|
+
export { css } from './css.js';
|
|
12
|
+
export { UID, PubSub }
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
let styleMutationObserverCbList = null;
|
|
14
|
+
let autoTagsCount = 0;
|
|
17
15
|
|
|
18
16
|
/** @template S */
|
|
19
|
-
export class
|
|
17
|
+
export class Symbiote extends HTMLElement {
|
|
20
18
|
/** @type {Boolean} */
|
|
21
19
|
#initialized;
|
|
22
20
|
/** @type {String} */
|
|
23
21
|
#autoCtxName;
|
|
24
22
|
/** @type {String} */
|
|
25
23
|
#cachedCtxName;
|
|
24
|
+
/** @type {PubSub} */
|
|
26
25
|
#localCtx;
|
|
27
26
|
#stateProxy;
|
|
28
27
|
/** @type {Boolean} */
|
|
@@ -32,15 +31,15 @@ export class BaseComponent extends HTMLElement {
|
|
|
32
31
|
#computedStyle;
|
|
33
32
|
#boundCssProps;
|
|
34
33
|
|
|
35
|
-
/** @type {typeof
|
|
36
|
-
// @ts-
|
|
34
|
+
/** @type {typeof Symbiote} */
|
|
35
|
+
// @ts-expect-error
|
|
37
36
|
#super = this.constructor;
|
|
38
37
|
|
|
39
38
|
/** @type {HTMLTemplateElement} */
|
|
40
39
|
static __tpl;
|
|
41
40
|
|
|
42
|
-
get
|
|
43
|
-
return
|
|
41
|
+
get Symbiote() {
|
|
42
|
+
return Symbiote;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
initCallback() {}
|
|
@@ -70,14 +69,14 @@ export class BaseComponent extends HTMLElement {
|
|
|
70
69
|
});
|
|
71
70
|
}
|
|
72
71
|
if (this.allowCustomTemplate) {
|
|
73
|
-
let customTplSelector = this.getAttribute(DICT.
|
|
72
|
+
let customTplSelector = this.getAttribute(DICT.USE_TPL_ATTR);
|
|
74
73
|
if (customTplSelector) {
|
|
75
74
|
let root = this.getRootNode();
|
|
76
75
|
/** @type {HTMLTemplateElement} */
|
|
77
|
-
// @ts-
|
|
76
|
+
// @ts-expect-error
|
|
78
77
|
let customTpl = root?.querySelector(customTplSelector) || document.querySelector(customTplSelector);
|
|
79
78
|
if (customTpl) {
|
|
80
|
-
// @ts-
|
|
79
|
+
// @ts-expect-error
|
|
81
80
|
template = customTpl.content.cloneNode(true);
|
|
82
81
|
} else {
|
|
83
82
|
console.warn(`Symbiote template "${customTplSelector}" is not found...`);
|
|
@@ -99,10 +98,10 @@ export class BaseComponent extends HTMLElement {
|
|
|
99
98
|
} else if (template?.constructor === String) {
|
|
100
99
|
let tpl = document.createElement('template');
|
|
101
100
|
tpl.innerHTML = template;
|
|
102
|
-
// @ts-
|
|
101
|
+
// @ts-expect-error
|
|
103
102
|
fr = tpl.content.cloneNode(true);
|
|
104
103
|
} else if (this.#super.__tpl) {
|
|
105
|
-
// @ts-
|
|
104
|
+
// @ts-expect-error
|
|
106
105
|
fr = this.#super.__tpl.content.cloneNode(true);
|
|
107
106
|
}
|
|
108
107
|
for (let fn of this.tplProcessors) {
|
|
@@ -129,7 +128,7 @@ export class BaseComponent extends HTMLElement {
|
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
/**
|
|
132
|
-
* @template {
|
|
131
|
+
* @template {Symbiote} T
|
|
133
132
|
* @param {(fr: DocumentFragment | T, fnCtx: T) => void} processorFn
|
|
134
133
|
*/
|
|
135
134
|
addTemplateProcessor(processorFn) {
|
|
@@ -140,9 +139,9 @@ export class BaseComponent extends HTMLElement {
|
|
|
140
139
|
super();
|
|
141
140
|
/** @type {S} */
|
|
142
141
|
this.init$ = Object.create(null);
|
|
143
|
-
/** @type {Object<string,
|
|
142
|
+
/** @type {Object<string, *>} */
|
|
144
143
|
this.cssInit$ = Object.create(null);
|
|
145
|
-
/** @type {Set<(fr: DocumentFragment |
|
|
144
|
+
/** @type {Set<(fr: DocumentFragment | Symbiote, fnCtx: unknown) => void>} */
|
|
146
145
|
this.tplProcessors = new Set();
|
|
147
146
|
/** @type {Object<string, any>} */
|
|
148
147
|
this.ref = Object.create(null);
|
|
@@ -156,6 +155,8 @@ export class BaseComponent extends HTMLElement {
|
|
|
156
155
|
/** @type {Boolean} */
|
|
157
156
|
this.processInnerHtml = false;
|
|
158
157
|
/** @type {Boolean} */
|
|
158
|
+
this.ssrMode = false;
|
|
159
|
+
/** @type {Boolean} */
|
|
159
160
|
this.allowCustomTemplate = false;
|
|
160
161
|
/** @type {Boolean} */
|
|
161
162
|
this.ctxOwner = false;
|
|
@@ -198,12 +199,12 @@ export class BaseComponent extends HTMLElement {
|
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
/** @returns {PubSub} */
|
|
201
|
-
get
|
|
202
|
+
get sharedCtx() {
|
|
202
203
|
return PubSub.getCtx(this.ctxName, false) || PubSub.registerCtx({}, this.ctxName);
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
/**
|
|
206
|
-
* @template {
|
|
207
|
+
* @template {Symbiote} T
|
|
207
208
|
* @param {String} prop
|
|
208
209
|
* @param {T} fnCtx
|
|
209
210
|
*/
|
|
@@ -213,20 +214,26 @@ export class BaseComponent extends HTMLElement {
|
|
|
213
214
|
/** @type {String} */
|
|
214
215
|
let name;
|
|
215
216
|
if (prop.startsWith(DICT.SHARED_CTX_PX)) {
|
|
216
|
-
ctx = fnCtx.
|
|
217
|
+
ctx = fnCtx.sharedCtx;
|
|
217
218
|
name = prop.replace(DICT.SHARED_CTX_PX, '');
|
|
218
219
|
} else if (prop.startsWith(DICT.PARENT_CTX_PX)) {
|
|
219
220
|
name = prop.replace(DICT.PARENT_CTX_PX, '');
|
|
220
221
|
let found = fnCtx;
|
|
221
222
|
while (found && !found?.has?.(name)) {
|
|
222
|
-
// @ts-
|
|
223
|
-
found = found.parentElement;
|
|
223
|
+
// @ts-expect-error
|
|
224
|
+
found = found.parentElement || found.parentNode || found.host;
|
|
224
225
|
}
|
|
225
226
|
ctx = found?.localCtx || fnCtx.localCtx;
|
|
226
227
|
} else if (prop.includes(DICT.NAMED_CTX_SPLTR)) {
|
|
227
228
|
let pArr = prop.split(DICT.NAMED_CTX_SPLTR);
|
|
228
229
|
ctx = PubSub.getCtx(pArr[0]);
|
|
229
230
|
name = pArr[1];
|
|
231
|
+
} else if (prop.startsWith(DICT.CSS_DATA_PX)) {
|
|
232
|
+
ctx = fnCtx.localCtx;
|
|
233
|
+
name = prop;
|
|
234
|
+
if (!ctx.has(name)) {
|
|
235
|
+
fnCtx.bindCssData(name);
|
|
236
|
+
}
|
|
230
237
|
} else {
|
|
231
238
|
ctx = fnCtx.localCtx;
|
|
232
239
|
name = prop;
|
|
@@ -245,12 +252,12 @@ export class BaseComponent extends HTMLElement {
|
|
|
245
252
|
*/
|
|
246
253
|
sub(prop, handler, init = true) {
|
|
247
254
|
let subCb = (val) => {
|
|
248
|
-
if (
|
|
255
|
+
if (this.#noInit) {
|
|
249
256
|
return;
|
|
250
257
|
}
|
|
251
258
|
handler(val);
|
|
252
259
|
};
|
|
253
|
-
let parsed =
|
|
260
|
+
let parsed = Symbiote.#parseProp(/** @type {string} */ (prop), this);
|
|
254
261
|
if (!parsed.ctx.has(parsed.name)) {
|
|
255
262
|
// Avoid *prop binding race:
|
|
256
263
|
window.setTimeout(() => {
|
|
@@ -263,13 +270,13 @@ export class BaseComponent extends HTMLElement {
|
|
|
263
270
|
|
|
264
271
|
/** @param {String} prop */
|
|
265
272
|
notify(prop) {
|
|
266
|
-
let parsed =
|
|
273
|
+
let parsed = Symbiote.#parseProp(prop, this);
|
|
267
274
|
parsed.ctx.notify(parsed.name);
|
|
268
275
|
}
|
|
269
276
|
|
|
270
277
|
/** @param {String} prop */
|
|
271
278
|
has(prop) {
|
|
272
|
-
let parsed =
|
|
279
|
+
let parsed = Symbiote.#parseProp(prop, this);
|
|
273
280
|
return parsed.ctx.has(parsed.name);
|
|
274
281
|
}
|
|
275
282
|
|
|
@@ -280,7 +287,7 @@ export class BaseComponent extends HTMLElement {
|
|
|
280
287
|
* @param {Boolean} [rewrite]
|
|
281
288
|
*/
|
|
282
289
|
add(prop, val, rewrite = false) {
|
|
283
|
-
let parsed =
|
|
290
|
+
let parsed = Symbiote.#parseProp(prop, this);
|
|
284
291
|
parsed.ctx.add(parsed.name, val, rewrite);
|
|
285
292
|
}
|
|
286
293
|
|
|
@@ -300,12 +307,12 @@ export class BaseComponent extends HTMLElement {
|
|
|
300
307
|
let o = Object.create(null);
|
|
301
308
|
this.#stateProxy = new Proxy(o, {
|
|
302
309
|
set: (obj, /** @type {String} */ prop, val) => {
|
|
303
|
-
let parsed =
|
|
310
|
+
let parsed = Symbiote.#parseProp(prop, this);
|
|
304
311
|
parsed.ctx.pub(parsed.name, val);
|
|
305
312
|
return true;
|
|
306
313
|
},
|
|
307
314
|
get: (obj, /** @type {String} */ prop) => {
|
|
308
|
-
let parsed =
|
|
315
|
+
let parsed = Symbiote.#parseProp(prop, this);
|
|
309
316
|
return parsed.ctx.read(parsed.name);
|
|
310
317
|
},
|
|
311
318
|
});
|
|
@@ -346,7 +353,7 @@ export class BaseComponent extends HTMLElement {
|
|
|
346
353
|
}
|
|
347
354
|
for (let prop in this.init$) {
|
|
348
355
|
if (prop.startsWith(DICT.SHARED_CTX_PX)) {
|
|
349
|
-
this.
|
|
356
|
+
this.sharedCtx.add(prop.replace(DICT.SHARED_CTX_PX, ''), this.init$[prop], this.#ctxOwner);
|
|
350
357
|
} else if (prop.includes(DICT.NAMED_CTX_SPLTR)) {
|
|
351
358
|
let propArr = prop.split(DICT.NAMED_CTX_SPLTR);
|
|
352
359
|
let ctxName = propArr[0].trim();
|
|
@@ -368,10 +375,14 @@ export class BaseComponent extends HTMLElement {
|
|
|
368
375
|
this.#dataCtxInitialized = true;
|
|
369
376
|
}
|
|
370
377
|
|
|
371
|
-
|
|
378
|
+
get #noInit() {
|
|
379
|
+
return !this.isVirtual && !this.isConnected;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#initComponent() {
|
|
372
383
|
// As `connectedCallback` calls are queued, it could be called after element being detached from DOM
|
|
373
384
|
// See example at https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance
|
|
374
|
-
if (
|
|
385
|
+
if (this.#noInit) {
|
|
375
386
|
return;
|
|
376
387
|
}
|
|
377
388
|
if (this.#disconnectTimeout) {
|
|
@@ -398,7 +409,7 @@ export class BaseComponent extends HTMLElement {
|
|
|
398
409
|
} else {
|
|
399
410
|
if (this.#super.rootStyleSheets) {
|
|
400
411
|
/** @type {Document | ShadowRoot} */
|
|
401
|
-
// @ts-
|
|
412
|
+
// @ts-expect-error
|
|
402
413
|
let root = this.getRootNode();
|
|
403
414
|
if (!root) {
|
|
404
415
|
return;
|
|
@@ -412,6 +423,10 @@ export class BaseComponent extends HTMLElement {
|
|
|
412
423
|
this.connectedOnce = true;
|
|
413
424
|
}
|
|
414
425
|
|
|
426
|
+
connectedCallback() {
|
|
427
|
+
this.#initComponent();
|
|
428
|
+
}
|
|
429
|
+
|
|
415
430
|
destroyCallback() {}
|
|
416
431
|
|
|
417
432
|
disconnectedCallback() {
|
|
@@ -435,12 +450,6 @@ export class BaseComponent extends HTMLElement {
|
|
|
435
450
|
for (let proc of this.tplProcessors) {
|
|
436
451
|
this.tplProcessors.delete(proc);
|
|
437
452
|
}
|
|
438
|
-
styleMutationObserverCbList?.delete(this.updateCssData);
|
|
439
|
-
if (!styleMutationObserverCbList?.size) {
|
|
440
|
-
styleMutationObserver?.disconnect();
|
|
441
|
-
styleMutationObserver = null;
|
|
442
|
-
styleMutationObserverCbList = null;
|
|
443
|
-
}
|
|
444
453
|
}, 100);
|
|
445
454
|
}
|
|
446
455
|
|
|
@@ -544,27 +553,6 @@ export class BaseComponent extends HTMLElement {
|
|
|
544
553
|
});
|
|
545
554
|
};
|
|
546
555
|
|
|
547
|
-
#initStyleAttrObserver() {
|
|
548
|
-
if (!styleMutationObserverCbList) {
|
|
549
|
-
styleMutationObserverCbList = new Set();
|
|
550
|
-
}
|
|
551
|
-
styleMutationObserverCbList.add(this.updateCssData);
|
|
552
|
-
if (!styleMutationObserver) {
|
|
553
|
-
styleMutationObserver = new MutationObserver((/** @type {MutationRecord[]} */ records) => {
|
|
554
|
-
records[0].type === 'attributes' &&
|
|
555
|
-
styleMutationObserverCbList.forEach((cb) => {
|
|
556
|
-
cb();
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
styleMutationObserver.observe(document, {
|
|
560
|
-
childList: true,
|
|
561
|
-
subtree: true,
|
|
562
|
-
attributes: true,
|
|
563
|
-
attributeFilter: ['style'],
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
556
|
/**
|
|
569
557
|
* @param {String} propName
|
|
570
558
|
* @param {any} [initValue] Uses empty string by default to make value useful in template
|
|
@@ -576,8 +564,10 @@ export class BaseComponent extends HTMLElement {
|
|
|
576
564
|
this.#boundCssProps.add(propName);
|
|
577
565
|
let val = this.getCssData(this.#extractCssName(propName), true);
|
|
578
566
|
val === null && (val = initValue);
|
|
579
|
-
|
|
580
|
-
|
|
567
|
+
propName.startsWith(DICT.CSS_DATA_PX)
|
|
568
|
+
// To prevent prop name parsing in cycle:
|
|
569
|
+
? this.localCtx.add(propName, val)
|
|
570
|
+
: this.add(propName, val);
|
|
581
571
|
}
|
|
582
572
|
|
|
583
573
|
dropCssDataCache() {
|
|
@@ -640,4 +630,4 @@ export class BaseComponent extends HTMLElement {
|
|
|
640
630
|
}
|
|
641
631
|
}
|
|
642
632
|
|
|
643
|
-
export default
|
|
633
|
+
export default Symbiote;
|
package/core/css.js
CHANGED
|
@@ -3,8 +3,32 @@
|
|
|
3
3
|
* @returns {CSSStyleSheet}
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export function css(parts) {
|
|
6
|
+
export function css(parts, ...props) {
|
|
7
|
+
let cssTxt = '';
|
|
7
8
|
let sheet = new CSSStyleSheet();
|
|
8
|
-
|
|
9
|
+
parts.forEach((part, idx) => {
|
|
10
|
+
cssTxt += part + props[idx];
|
|
11
|
+
});
|
|
12
|
+
css.processors.forEach((prFn) => {
|
|
13
|
+
cssTxt = prFn(cssTxt);
|
|
14
|
+
});
|
|
15
|
+
css.clearProcessors();
|
|
16
|
+
sheet.replaceSync(cssTxt);
|
|
9
17
|
return sheet;
|
|
10
|
-
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** @type {((cssTxt: string) => String)[]} */
|
|
21
|
+
css.processors = [];
|
|
22
|
+
|
|
23
|
+
css.clearProcessors = function() {
|
|
24
|
+
css.processors = [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @param {...(cssTxt: string) => String} args
|
|
30
|
+
*/
|
|
31
|
+
css.useProcessor = function(...args) {
|
|
32
|
+
css.processors = [...css.processors, ...args];
|
|
33
|
+
return css;
|
|
34
|
+
};
|