@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 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
- * Minimal but rich.
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 (~4kb br/gzip for the all BaseComponent features).
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 common source code.
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 { BaseComponent } from 'https://symbiotejs.github.io/symbiote.js/core/BaseComponent.js';
71
+ import { Symbiote, html } from 'https://symbiotejs.github.io/symbiote.js/core/Symbiote.js';
60
72
 
61
- class MyComponent extends BaseComponent {
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 = /*html*/ `
82
+ MyComponent.template = html`
71
83
  <h2>{{count}}</h2>
72
- <button set="onclick: increment">Click me!</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
- /** @private */
5
- static _print(msg) {
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.__routingEventName = name;
42
+ this.#routingEventName = name;
30
43
  }
31
44
 
32
45
  /** @returns {String} */
33
46
  static get routingEventName() {
34
- return this.__routingEventName || 'sym-on-route';
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._print(`Route "${routeBase.route}" not found...`);
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._print('Wrong route: ' + route);
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._separator = char;
132
+ this.#separator = char;
120
133
  }
121
134
 
122
135
  /** @returns {String} */
123
136
  static get separator() {
124
- return this._separator || '&';
137
+ return this.#separator || '&';
125
138
  }
126
139
 
127
140
  /**
128
141
  * @param {String} ctxName
129
- * @param {Object<string, {}>} routingMap
142
+ * @param {Object<string, RouteDescriptor>} routingMap
130
143
  * @returns {PubSub}
131
144
  */
132
- static createRouterData(ctxName, routingMap) {
145
+ static initRoutingCtx(ctxName, routingMap) {
133
146
  this.setRoutingMap(routingMap);
134
- let routeData = PubSub.registerCtx(
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
- routeData.multiPub({
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.initPopstateListener();
151
- return routeData;
163
+ this.#initPopstateListener();
164
+ return routingCtx;
152
165
  }
153
166
 
154
- static initPopstateListener() {
155
- if (this.__onPopstate) {
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.__onPopstate);
175
+ window.addEventListener('popstate', this.#onPopstate);
163
176
  }
164
177
 
165
178
  static removePopstateListener() {
166
- window.removeEventListener('popstate', this.__onPopstate);
167
- this.__onPopstate = null;
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.includes(DICT.COMPUTED_PX)) {
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.notify(prop);
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
- ### BaseComponent.js
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 function, that transforms interpolated binding descriptions into resulting html.
10
+ Template literal tag-function, that transforms interpolated binding descriptions into resulting html.
11
11
 
12
- ### Data.js
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
- ### repeatProcessor.js
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
- let autoTagsCount = 0;
11
-
12
- /** @type {MutationObserver} */
13
- let styleMutationObserver = null;
10
+ export { html } from './html.js';
11
+ export { css } from './css.js';
12
+ export { UID, PubSub }
14
13
 
15
- /** @type {Set<() => void>} */
16
- let styleMutationObserverCbList = null;
14
+ let autoTagsCount = 0;
17
15
 
18
16
  /** @template S */
19
- export class BaseComponent extends HTMLElement {
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 BaseComponent} */
36
- // @ts-ignore
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 BaseComponent() {
43
- return BaseComponent;
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.USE_TPL);
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-ignore
76
+ // @ts-expect-error
78
77
  let customTpl = root?.querySelector(customTplSelector) || document.querySelector(customTplSelector);
79
78
  if (customTpl) {
80
- // @ts-ignore
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-ignore
101
+ // @ts-expect-error
103
102
  fr = tpl.content.cloneNode(true);
104
103
  } else if (this.#super.__tpl) {
105
- // @ts-ignore
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 {BaseComponent} T
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, any>} */
142
+ /** @type {Object<string, *>} */
144
143
  this.cssInit$ = Object.create(null);
145
- /** @type {Set<(fr: DocumentFragment | BaseComponent, fnCtx: unknown) => void>} */
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 nodeCtx() {
202
+ get sharedCtx() {
202
203
  return PubSub.getCtx(this.ctxName, false) || PubSub.registerCtx({}, this.ctxName);
203
204
  }
204
205
 
205
206
  /**
206
- * @template {BaseComponent} T
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.nodeCtx;
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-ignore
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 (!this.isVirtual && !this.isConnected) {
255
+ if (this.#noInit) {
249
256
  return;
250
257
  }
251
258
  handler(val);
252
259
  };
253
- let parsed = BaseComponent.#parseProp(/** @type {string} */ (prop), this);
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 = BaseComponent.#parseProp(prop, this);
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 = BaseComponent.#parseProp(prop, this);
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 = BaseComponent.#parseProp(prop, this);
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 = BaseComponent.#parseProp(prop, this);
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 = BaseComponent.#parseProp(prop, this);
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.nodeCtx.add(prop.replace(DICT.SHARED_CTX_PX, ''), this.init$[prop], this.#ctxOwner);
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
- connectedCallback() {
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 (!this.isVirtual && !this.isConnected) {
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-ignore
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
- this.add(propName, val);
580
- this.#initStyleAttrObserver();
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 BaseComponent;
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
- sheet.replaceSync(parts.join(''));
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
+ };