solarite 0.1.0 → 0.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.
@@ -56,7 +56,7 @@ export default class Shell {
56
56
 
57
57
  // Swap out Embedded Solarite Components with ${} attributes.
58
58
  // Later, NodeGroup.render() will search for these and replace them with the real components.
59
- // Ctrl+F "redcomponent-placeholder" in project to find all code that manages subcomponents.
59
+ // Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
60
60
  if (context === htmlContext.Attribute) {
61
61
 
62
62
  let lastIndex, lastMatch;
@@ -66,7 +66,7 @@ export default class Shell {
66
66
  })
67
67
 
68
68
  if (lastMatch) {
69
- let newTagName = lastMatch + '-redcomponent-placeholder';
69
+ let newTagName = lastMatch + '-solarite-placeholder';
70
70
  lastHtml = lastHtml.slice(0, lastIndex) + newTagName + lastHtml.slice(lastIndex + lastMatch.length);
71
71
  componentNames[lastMatch] = newTagName
72
72
  }
@@ -85,7 +85,7 @@ export default class Shell {
85
85
  let template = document.createElement('template'); // Using a single global template won't keep the nodes as children of the DocumentFragment.
86
86
  let joinedHtml = buffer.join('');
87
87
 
88
- // Replace '-redcomponent-placeholder' close tags.
88
+ // Replace '-solarite-placeholder' close tags.
89
89
  // TODO: is there a better way? What if the close tag is inside a comment?
90
90
  for (let name in componentNames)
91
91
  joinedHtml = joinedHtml.replaceAll(`</${name}>`, `</${componentNames[name]}>`);
@@ -210,8 +210,8 @@ export default class Shell {
210
210
  }
211
211
  toRemove.map(el => el.remove());
212
212
 
213
- // Handle redcomponent-placeholder's.
214
- // Ctrl+F "redcomponent-placeholder" in project to find all code that manages subcomponents.
213
+ // Handle solarite-placeholder's.
214
+ // Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
215
215
  //if (componentNames.size)
216
216
  // this.components = [...this.fragment.querySelectorAll([...componentNames].join(','))]
217
217
 
@@ -14,6 +14,9 @@ let Solarite = new Proxy(createSolarite(), {
14
14
  export {Solarite}
15
15
  export {default as r} from './r.js';
16
16
  export {getArg, ArgType} from './getArg.js';
17
+ export {default as Template} from './Template.js';
17
18
 
18
- export {forEach, watchGet, watchSet} from './watch.js' // old, unfinished
19
- export {watch} from './watch2.js'; // unfinished
19
+
20
+ //Experimental:
21
+ //export {forEach, watchGet, watchSet} from './watch.js' // old, unfinished
22
+ //export {watch} from './watch2.js'; // unfinished
@@ -1,6 +1,7 @@
1
1
  import {assert} from "../util/Errors.js";
2
2
  import {getObjectId} from "./hash.js";
3
3
  import NodeGroupManager from "./NodeGroupManager.js";
4
+ import NodeGroup from "./NodeGroup.js";
4
5
 
5
6
 
6
7
  /**
@@ -23,11 +24,11 @@ export default class Template {
23
24
  /** Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
24
25
  hashedFields;
25
26
 
26
- /**
27
- * @deprecated
28
- * @type {ExprPath} Used with forEach() from watch.js
29
- * Set in NodeGroup.applyOneExpr() */
30
- parentPath;
27
+ /**
28
+ * @deprecated
29
+ * @type {ExprPath} Used with forEach() from watch.js
30
+ * Set in ExprPath.apply() */
31
+ parentPath;
31
32
 
32
33
  /** @type {NodeGroup} */
33
34
  nodeGroup;
@@ -71,11 +72,133 @@ export default class Template {
71
72
  return this.hashedFields
72
73
  }
73
74
 
74
- toNode() {
75
- let ngm = new NodeGroupManager();
76
- return ngm.render(this);
75
+ /**
76
+ * Render the main template, which may indirectly call renderTemplate() to create children.
77
+ * @param el {HTMLElement}
78
+ * @param options {RenderOptions}
79
+ * @return {?DocumentFragment|HTMLElement} */
80
+ render(el=null, options={}) {
81
+
82
+ let ng;
83
+ if (!el) {
84
+ ng = new NodeGroup(this);
85
+ el = ng.getParentNode();
86
+ }
87
+
88
+ let ngm = NodeGroupManager.get(el);
89
+ if (ng)
90
+ ng.manager = ngm;
91
+
92
+ //#IFDEV
93
+ ngm.modifications = {
94
+ created: [],
95
+ updated: [],
96
+ moved: [],
97
+ deleted: []
98
+ };
99
+ //#ENDIF
100
+
101
+ ngm.options = options;
102
+ ngm.clearSubscribers = false; // Used for deprecated watch() path?
103
+ ngm.mutationWatcherEnabled = false;
104
+
105
+ // Fast path for empty component.
106
+ if (this.html?.length === 1 && !this.html[0]) {
107
+ el.innerHTML = '';
108
+ }
109
+ else {
110
+
111
+ // Find or create a NodeGroup for the template.
112
+ // This updates all nodes from the template.
113
+ let close;
114
+ let exact = ngm.getNodeGroup(this, true);
115
+ if (!exact) {
116
+ close = ngm.getNodeGroup(this, false);
117
+ }
118
+
119
+ let firstTime = !ngm.rootNg;
120
+ ngm.rootNg = exact || close;
121
+
122
+ // Reparent NodeGroup
123
+ // TODO: Move this to NodeGroup?
124
+ let parent = ngm.rootNg.getParentNode();
125
+
126
+
127
+ // If this is the first time rendering this element.
128
+ if (firstTime) {
129
+
130
+ // Save slot children
131
+ let fragment;
132
+ if (el.childNodes.length) {
133
+ fragment = document.createDocumentFragment();
134
+ fragment.append(...el.childNodes);
135
+ }
136
+
137
+ // Add rendered elements.
138
+ if (parent instanceof DocumentFragment)
139
+ el.append(parent);
140
+ else if (parent)
141
+ el.append(...parent.childNodes)
142
+
143
+ // Apply slot children
144
+ if (fragment) {
145
+ for (let slot of el.querySelectorAll('slot[name]')) {
146
+ let name = slot.getAttribute('name')
147
+ if (name)
148
+ slot.append(...fragment.querySelectorAll(`[slot='${name}']`))
149
+ }
150
+ let unamedSlot = el.querySelector('slot:not([name])')
151
+ if (unamedSlot)
152
+ unamedSlot.append(fragment)
153
+ }
154
+ }
155
+
156
+ ngm.rootEl = el;
157
+
158
+ // this.rootNg was rendered as childrenOnly=true
159
+ // Apply attributes from a root element to the real root element.
160
+ let ng = ngm.rootNg;
161
+ if (ng.pseudoRoot && ng.pseudoRoot !== el) {
162
+ /*#IFDEV*/assert(el)/*#ENDIF*/
163
+
164
+ // Remove old attributes
165
+ // for (let attrib of this.rootEl.attributes)
166
+ // if (attrib.name !== 'is' && attrib.name !== 'data-style' && !ng.pseudoRoot.hasAttribute(attrib.name))
167
+ // this.rootEl.removeAttribute(attrib.name)
168
+
169
+ // Add/set new attributes
170
+ if (firstTime)
171
+ for (let attrib of ng.pseudoRoot.attributes)
172
+ if (!el.hasAttribute(attrib.name))
173
+ el.setAttribute(attrib.name, attrib.value);
174
+
175
+ // ng.startNode = ng.endNode = this.rootEl;
176
+ // ng.nodesCache = [ng.startNode]
177
+ // for (let path of ng.paths) {
178
+ // if (path.nodeMarker === ng.rootEl)
179
+ // path.nodeMarker = this.rootEl;
180
+ // path.nodesCache = null;
181
+ // /*#IFDEV*/assert(path.nodeBefore !== ng.rootEl)/*#ENDIF*/
182
+ // }
183
+ //
184
+ // ng.rootEl = this.rootEl;
185
+ }
186
+
187
+ /*#IFDEV*/ngm.rootNg.verify();/*#ENDIF*/
188
+ ngm.reset(); // Mark all NodeGroups as available, for next render.
189
+ /*#IFDEV*/ngm.rootNg.verify();/*#ENDIF*/
190
+
191
+ window.ngm = ngm;
192
+ }
193
+
194
+ ngm.mutationWatcherEnabled = true;
195
+ return el;
196
+ //#IFDEV
197
+ //return ngm.modifications;
198
+ //#ENDIF
77
199
  }
78
200
 
201
+
79
202
  getCloseKey() {
80
203
  // Use the joined html when debugging?
81
204
  //return '@'+this.html.join('|')
@@ -1,12 +1,15 @@
1
1
  import Util from "../util/Util.js";
2
- import {assert} from "../util/Errors.js";
2
+ //import {assert} from "../util/Errors.js";
3
3
  import delve from "../util/delve.js";
4
4
  import {getArg, ArgType} from "./getArg.js";
5
5
  import {getObjectHash} from "./hash.js";
6
6
  import NodeGroupManager from "./NodeGroupManager.js";
7
7
  import r, {rendered} from "./r.js";
8
8
  import {camelToDashes} from "./Util.js";
9
- import {watchGet, watchSet} from "./watch.js";
9
+
10
+
11
+ //import {watchGet, watchSet} from "./watch.js";
12
+
10
13
 
11
14
 
12
15
  function defineClass(Class, tagName, extendsTag) {
@@ -39,9 +42,15 @@ let connected = new WeakSet();
39
42
  * 2. Calls render() when added to the DOM, if it hasn't been called already.
40
43
  * 3. Child elements are added before constructor is called. But they're also passed to the constructor.
41
44
  * 4. We can use this.html = r`...` to set html.
42
- * 5. We have the onConnect, onFirstConnect, and onDisconnect methods. These could be standalone though.
45
+ * 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
46
+ * Can't figure out how to have these work standalone though, and still be synchronous.
43
47
  * 6. Can we extend from other element types like TR?
44
48
  *
49
+ * Advantages to inheriting from HTMLElement
50
+ * 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
51
+ * 2. We can inherit from things like HTMLTableRowElement directly.
52
+ * 3. There's less magic, since everyone is familiar with defining custom elements.
53
+ *
45
54
  * @param extendsTag {?string}
46
55
  * @return {Class} */
47
56
  export default function createSolarite(extendsTag=null) {
@@ -105,7 +114,7 @@ export default function createSolarite(extendsTag=null) {
105
114
  if (ch)
106
115
  (this.querySelector('slot') || this).append(...ch);
107
116
 
108
-
117
+ /** @deprecated */
109
118
  Object.defineProperty(this, 'html', {
110
119
  set(html) {
111
120
  rendered.add(this);
@@ -155,7 +164,9 @@ export default function createSolarite(extendsTag=null) {
155
164
  defineClass(this, tagName, extendsTag)
156
165
  }
157
166
 
167
+ //#IFDEV
158
168
 
169
+ /** @deprecated */
159
170
  renderWatched() {
160
171
  let ngm = NodeGroupManager.get(this);
161
172
 
@@ -229,8 +240,7 @@ export default function createSolarite(extendsTag=null) {
229
240
  // Create new NodeGroup
230
241
  let ng = ngm.getNodeGroup(template, false, true);
231
242
  ng.parentPath = beforeNg?.parentPath || loopInfo.template.parentPath;
232
- /*#IFDEV*/
233
- assert(ng.parentPath);/*#ENDIF*/
243
+
234
244
  for (let node of ng.getNodes())
235
245
  beforeNode.parentNode.insertBefore(node, beforeNode);
236
246
 
@@ -260,8 +270,10 @@ export default function createSolarite(extendsTag=null) {
260
270
  /**
261
271
  * @deprecated Use the getArg() function instead. */
262
272
  getArg(name, val=null, type=ArgType.String) {
273
+ throw new Error('deprecated');
263
274
  return getArg(this, name, val, type);
264
275
  }
276
+ //#ENDIF
265
277
  }
266
278
  }
267
279
 
@@ -54,19 +54,19 @@ export function getArg(el, name, val=null, type=ArgType.String) {
54
54
  case ArgType.Float:
55
55
  return parseFloat(val);
56
56
  case ArgType.String:
57
- return val || '';
57
+ return [undefined, null, false].includes(val) ? '' : val+'';
58
58
  case ArgType.JSON:
59
- try {
60
- return JSON.parse(val);
61
- } catch (e) {
62
- return val;
63
- }
64
59
  case ArgType.Eval:
65
- try {
66
- return eval(`(${val})`);
67
- } catch (e) {
68
- return val;
69
- }
60
+ if (typeof val === 'string' && val.length)
61
+ try {
62
+ if (type === ArgType.JSON)
63
+ return JSON.parse(val);
64
+ else
65
+ return eval(`(${val})`);
66
+ } catch (e) {
67
+ return val;
68
+ }
69
+ else return val;
70
70
  default:
71
71
  return val;
72
72
  }
@@ -18,7 +18,7 @@ export function getObjectId(obj, prefix=null) {
18
18
  prefix = 'Func';
19
19
  else if (typeof obj === 'object')
20
20
  prefix = 'Obj'
21
- }
21
+ }
22
22
  //#ENDIF
23
23
 
24
24
  prefix = prefix || '~\f';
package/src/solarite/r.js CHANGED
@@ -24,10 +24,10 @@ import NodeGroupManager from "./NodeGroupManager.js";
24
24
  * 6. r('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
25
25
  * 7. r()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which includes properly handling nested components and r`` sub-expressions.
26
26
  * 8. r(template) // Render Template created by #1.
27
- * 9. r(() => r`<b>Hello</b>`); // Create dynamic element that has a render() function.
27
+ * 9. r(() => r`<b>Hello</b>`, {...}); // Create dynamic element that has a render() function.
28
28
  *
29
29
  * @param htmlStrings {?HTMLElement|string|string[]|function():Template}
30
- * @param exprs {*[]|string|Template}
30
+ * @param exprs {*[]|string|Template|Object}
31
31
  * @return {Node|HTMLElement|Template} */
32
32
  export default function r(htmlStrings=undefined, ...exprs) {
33
33
 
@@ -41,9 +41,8 @@ export default function r(htmlStrings=undefined, ...exprs) {
41
41
 
42
42
  // 2. Render template created by #4 to element.
43
43
  if (exprs[0] instanceof Template) {
44
- let ngm = NodeGroupManager.get(parent);
45
44
  let options = exprs[1];
46
- ngm.render(template, options);
45
+ template.render(parent, options);
47
46
 
48
47
  // Append on the first go.
49
48
  if (!parent.childNodes.length && this) {
@@ -62,15 +61,16 @@ export default function r(htmlStrings=undefined, ...exprs) {
62
61
  return (htmlStrings, ...exprs) => {
63
62
  rendered.add(parent)
64
63
  let template = r(htmlStrings, ...exprs);
65
- let ngm = NodeGroupManager.get(parent);
66
- return ngm.render(template, options);
64
+ return template.render(parent, options);
67
65
  }
68
66
  }
69
67
 
70
68
  // null for expr[0], remove whole element.
69
+ // This path never happens?
71
70
  else {
72
- let ngm = NodeGroupManager.get(parent);
73
- ngm.render(null, exprs[1])
71
+ throw new Error('unsupported');
72
+ //let ngm = NodeGroupManager.get(parent);
73
+ //ngm.render(null, exprs[1])
74
74
  }
75
75
  }
76
76
 
@@ -98,16 +98,16 @@ export default function r(htmlStrings=undefined, ...exprs) {
98
98
  return (htmlStrings, ...exprs) => {
99
99
  //rendered.add(parent)
100
100
  let template = r(htmlStrings, ...exprs);
101
- return template.toNode();
101
+ return template.render();
102
102
  }
103
103
  }
104
104
 
105
105
  // 8.
106
106
  else if (htmlStrings instanceof Template) {
107
- let ngm = new NodeGroupManager();
108
- return ngm.render(htmlStrings);
107
+ return htmlStrings.render();
109
108
  }
110
109
 
110
+
111
111
  // 9. Create dynamic element with render() function.
112
112
  else if (typeof htmlStrings === 'function') {
113
113
  let getTemplate = htmlStrings;
@@ -116,17 +116,27 @@ export default function r(htmlStrings=undefined, ...exprs) {
116
116
  if (typeof template === 'string')
117
117
  throw new Error(`Please add the "r" prefix before the string "${template}"`)
118
118
 
119
- let ngm = new NodeGroupManager();
120
119
  template.replaceMode = true;
121
- let el = ngm.render(template);
120
+ let el = template.render();
122
121
 
122
+ // Create the render() function from the function we were given.
123
123
  el.render = (function() {
124
124
  template = getTemplate();
125
- ngm.render(template)
125
+ template.render(el);
126
126
  }).bind(el);
127
127
 
128
+
129
+ // The second argument was an object of additional properties to add.
130
+ let props = exprs[0];
131
+ for (let name in props)
132
+ if (typeof props[name] === 'function')
133
+ el[name] = props[name].bind(el);
134
+ else
135
+ el[name] = props[name];
136
+
128
137
  return el;
129
138
  }
139
+
130
140
  else
131
141
  throw new Error('Unsupported arguments.')
132
142
  }
@@ -136,7 +136,7 @@ export function forEach(arrayPath, callback) {
136
136
  ];
137
137
 
138
138
  // We return a template that wraps the array
139
- // So that NodeGroup.applyOneExpr can set the ExprPath and nextSibling on the template.
139
+ // So that ExprPath.apply() can set the ExprPath and nextSibling on the template.
140
140
  // Then the 'insert' path in renderWatched() uses that data fora dding more nodes.
141
141
  let result = new Template(['', ''], [newItems])
142
142
 
@@ -11,6 +11,7 @@ export {logGets, gets}
11
11
 
12
12
  let withinSet = 0;
13
13
 
14
+
14
15
  /**
15
16
  * Turn the props on obj into JavasCript properties that return Proxies when accessed.
16
17
  * If called more than once, return the already-converted object.
@@ -291,7 +292,7 @@ class ProxyHandler {
291
292
  let obj2 = obj === this.root && this.props ? this.props : obj;
292
293
  let result = Reflect.get(obj2, prop);
293
294
 
294
- // This is read by watchFunction() which is called in NodeGroup.applyOneExpr().
295
+ // This is read by watchFunction() which is called in ExprPath.apply().
295
296
  // It's used to see what variables contribute to an expression.
296
297
  if (logGets)
297
298
  gets.push([this.root, ...this.path, prop]);
@@ -392,7 +393,7 @@ function getProxy(obj, ph, path, path2) {
392
393
 
393
394
  /**
394
395
  * Call a function and record which watched variables it accesess, storing their paths in pathToTemplates.
395
- * Used by NodeGroup.applyOneExpr().
396
+ * Used by ExprPath.apply().
396
397
  * TODO: only allow this to be called once per callback.
397
398
  * @param callback {function}
398
399
  * @param ngm {NodeGroupManager}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Trying to be able to automatically watch primitive values.
3
+ * TODO:
4
+ * 1. Have get() return Proxies for nested updates.
5
+ * 2. Override .map() for loops to capture changes.
6
+ */
7
+
8
+
9
+ // Example:
10
+ /*
11
+ class WatchExample extends Solarite {
12
+
13
+ constructor(items = []) {
14
+ super();
15
+
16
+ this.items = items;
17
+ watch(this, 'items');
18
+
19
+ this.name = 'George';
20
+ watch(this, 'name');
21
+
22
+ this.render();
23
+ }
24
+
25
+ render() {
26
+ r(this)`
27
+ <watch-example>
28
+ ${() => this.name + '!'}
29
+
30
+ ${this.items.map(item => r`
31
+ <div>${item.name}</div>
32
+ `)}
33
+ ${this.items.length}
34
+ </watch-example>`;
35
+ }
36
+ }
37
+ customElements.define('watch-example', WatchExample);
38
+
39
+ let a = new WatchExample();
40
+
41
+ // Items is a Proxy.
42
+ // Calling push() will trigger the map'd ExprPaths to add another at the end.
43
+ // And the .length expression to update.
44
+ // Because accessing .items returns a proxy.
45
+ a.items.push({name: 'Fred'});
46
+ */
47
+
48
+
49
+ import Globals from "./Globals.js";
50
+ import NodeGroupManager from "./NodeGroupManager.js";
51
+
52
+
53
+ export default function watch3(root, path) {
54
+
55
+ /** @type {ExprPath} The ExprPath that's using this variable.*/
56
+ let exprPath;
57
+
58
+ /** @type {function} Function evaluated by the ExprPath. */
59
+ let exprFunction;
60
+
61
+ // Store internal value used by get/set.
62
+ let value = root[path];
63
+
64
+ let ngm = NodeGroupManager.get(root);
65
+
66
+ Object.defineProperty(root, path, {
67
+ get() {
68
+ // Track which ExprPath is using this variable.
69
+ if (Globals.currentExprPath)
70
+ [exprPath, exprFunction] = Globals.currentExprPath;
71
+
72
+ // TODO: Return Proxy for non-primitive value, to track path changed.
73
+ return value;
74
+ },
75
+ set(val) {
76
+ value = val;
77
+
78
+ if (exprFunction) {
79
+ let ng = exprPath.parentNg;
80
+
81
+ ng.applyExprs([exprFunction], [exprPath]); // TODO: Will fail for attribute w/ a value having multiple ExprPaths.
82
+
83
+ // TODO: This doesn't cascade upward.
84
+ //ng.exactKey = getObjectHash(ng.template);
85
+
86
+ ngm.reset(); // TODO: This could be skipped if applyExprs() never marked them as in-use.
87
+
88
+ }
89
+ }
90
+ });
91
+ }
@@ -0,0 +1,79 @@
1
+ // TODO: Everything here fails because our own connectedCallback function is never called.
2
+ // Perhaps b/c these callbacks must be defined before we register the custom element.
3
+
4
+
5
+ /**
6
+ * @type {Map<HTMLElement, Set<function(HTMLElement)>>} */
7
+ let connectCallbacks = new Map();
8
+ /**
9
+ * @type {Map<HTMLElement, Set<function(HTMLElement)>>} */
10
+ let firstConnectCallbacks = new Map();
11
+ /**
12
+ * @type {Map<HTMLElement, Set<function(HTMLElement)>>} */
13
+ let disconnectCallbacks = new Map();
14
+
15
+ function getElCallbacks(el, map) {
16
+ let callbacks = connectCallbacks.get(el);
17
+ if (!callbacks) {
18
+ callbacks = new Set();
19
+ connectCallbacks.set(el, callbacks);
20
+ }
21
+ return callbacks;
22
+ }
23
+
24
+ let createConnectedCallback = el => () => {
25
+ debugger;
26
+ for (let cb of getElCallbacks(el, connectCallbacks))
27
+ cb();
28
+
29
+ let cbs = getElCallbacks(el, firstConnectCallbacks);
30
+ for (let cb of cbs) {
31
+ cb();
32
+ cbs.delete(cb);
33
+ }
34
+ };
35
+
36
+
37
+ /**
38
+ *
39
+ * @param el {HTMLElement}
40
+ * @param callback {function}
41
+ * @return {function()} A function that removes the callback. */
42
+ export function onConnect(el, callback) {
43
+ let callbacks = getElCallbacks(el, connectCallbacks);
44
+ callbacks.add(callback);
45
+
46
+ if (!el.constructor.prototype.hasOwnProperty('connectedCallback'))
47
+ el.constructor.prototype.connectedCallback = createConnectedCallback(el);
48
+
49
+ return () => {
50
+ callbacks.delete(callback);
51
+ }
52
+ }
53
+
54
+ export function onFirstConnect(el, callback) {
55
+ let callbacks = getElCallbacks(el, firstConnectCallbacks);
56
+ callbacks.add(callback);
57
+
58
+ if (!el.hasOwnProperty('connectedCallback'))
59
+ el.connectedCallback = createConnectedCallback(el);
60
+
61
+ return () => {
62
+ callbacks.delete(callback);
63
+ }
64
+ }
65
+
66
+ export function onDisconnect(el, callback) {
67
+ let callbacks = getElCallbacks(el, disconnectCallbacks);
68
+ callbacks.add(callback);
69
+
70
+ if (!el.hasOwnProperty('connectedCallback'))
71
+ el.connectedCallback = () => {
72
+ for (let cb of callbacks)
73
+ cb();
74
+ }
75
+
76
+ return () => {
77
+ callbacks.delete(callback);
78
+ }
79
+ }