@substrate-system/tonic 16.0.5

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/LICENSE.txt ADDED
@@ -0,0 +1,18 @@
1
+ THE MIT LICENSE (MIT)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the “Software”), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,217 @@
1
+ ![tests](https://github.com/substrate-system/tonic/actions/workflows/nodejs.yml/badge.svg)
2
+ [![module](https://img.shields.io/badge/module-ESM-blue?style=flat-square)](README.md)
3
+ [![semantic versioning](https://img.shields.io/badge/semver-2.0.0-blue?logo=semver&style=flat-square)](https://semver.org/)
4
+ [![dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen.svg?style=flat-square)](package.json)
5
+ [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
6
+
7
+ <picture>
8
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/substrate-system/tonic/fork/readme-tonic-dark.png">
9
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/substrate-system/tonic/fork/readme-tonic.png">
10
+ <img alt="tonic" src="https://raw.githubusercontent.com/substrate-system/tonic/fork/readme-tonic.png">
11
+ </picture>
12
+
13
+ <p align="center">
14
+ https://tonicframework.dev
15
+ </p>
16
+
17
+ <br/>
18
+ <br/>
19
+
20
+ Tonic is a low profile component framework for the web. It's one file, less than 3kb gzipped and has no dependencies. It's designed to be used with modern Javascript and is compatible with all modern browsers and built on top of Web Components.
21
+
22
+ [See the API docs](https://substrate-system.github.io/tonic/index.html)
23
+
24
+ The tl;dr is that this allows you to pass full JS objects between components, not just strings as in HTML.
25
+
26
+ ## Contents
27
+
28
+ <!-- toc -->
29
+
30
+ - [Install](#install)
31
+ - [Use](#use)
32
+ - [fork](#fork)
33
+ * [additions](#additions)
34
+ - [Useful links](#useful-links)
35
+
36
+ <!-- tocstop -->
37
+
38
+ ## Install
39
+
40
+ ```sh
41
+ npm i -S @substrate-system/tonic
42
+ ```
43
+
44
+ ## Use
45
+
46
+ ```js
47
+ import Tonic from '@substrate-system/tonic'
48
+ ```
49
+
50
+ You can use functions as components. They can be async or even an async generator function.
51
+
52
+ ```js
53
+ async function MyGreeting () {
54
+ const data = await (await fetch('https://example.com/data')).text()
55
+ return this.html`<h1>Hello, ${data}</h1>`
56
+ }
57
+ ```
58
+
59
+ Or you can use classes. Every class must have a render method.
60
+
61
+ ```js
62
+ class MyGreeting extends Tonic {
63
+ async * render () {
64
+ yield this.html`<div>Loading...</div>`
65
+
66
+ const data = await (await fetch('https://example.com/data')).text()
67
+ return this.html`<div>Hello, ${data}.</div>`
68
+ }
69
+ }
70
+ ```
71
+
72
+ ```js
73
+ Tonic.add(MyGreeting, 'my-greeting')
74
+ ```
75
+
76
+ After adding your Javascript to your HTML, you can use your component anywhere.
77
+
78
+ ```html
79
+ <html>
80
+ <head>
81
+ <script src="my-greeting.js"></script>
82
+ </head>
83
+ <body>
84
+ <my-greeting></my-greeting>
85
+ </body>
86
+ </html>
87
+ ```
88
+
89
+ ## fork
90
+ This is a fork of [@socketsupply/tonic](https://github.com/socketsupply/tonic).
91
+
92
+ See [API docs](https://substrate-system.github.io/tonic/).
93
+
94
+ ### additions
95
+ Things added to the forked version:
96
+
97
+ #### types
98
+ See [src/index.ts](./src/index.ts).
99
+
100
+ #### `tag`
101
+ Get the HTML tag name given a Tonic class.
102
+
103
+ ```ts
104
+ static get tag():string;
105
+ ```
106
+
107
+ ```js
108
+ class ExampleTwo extends Tonic {
109
+ render () {
110
+ return this.html`<div>example two</div>`
111
+ }
112
+ }
113
+
114
+ ExampleTwo.tag
115
+ // => 'example-two'
116
+ ```
117
+
118
+ #### `emit`
119
+ Emit namespaced events, following a naming convention. The return value is the call to [element.dispatchEvent()](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent).
120
+
121
+ Given an event name, the dispatched event will be prefixed with the element name, for example, `my-element:event-name`.
122
+
123
+ ```ts
124
+ emit (type:string, detail:string|object|any[] = {}, opts:Partial<{
125
+ bubbles:boolean;
126
+ cancelable:boolean
127
+ }> = {}):boolean
128
+ ```
129
+
130
+ ##### emit example
131
+
132
+ ```js
133
+ class EventsExample extends Tonic {
134
+ // ...
135
+ }
136
+
137
+ // EventsExample.event('name') will return the namespace event name
138
+ const evName = EventsExample.event('testing')
139
+
140
+ document.body.addEventListener(evName, ev => {
141
+ // events bubble by default
142
+ console.log(ev.type) // => 'events-example:testing'
143
+ console.log(ev.detail) // => 'some data'
144
+ })
145
+
146
+ const el = document.querySelector('events-example')
147
+ // use default values for `bubbles = true` and `cancelable = true`
148
+ el.emit('testing', 'some data')
149
+
150
+ // override default values, `bubbles` and `cancelable`
151
+ el.emit('more testing', 'some data', {
152
+ bubbles: false
153
+ cancelable: false
154
+ })
155
+ ```
156
+
157
+ #### `static event`
158
+ Return the namespaced event name given a string.
159
+
160
+ ```ts
161
+ class {
162
+ static event (type:string):string {
163
+ return `${this.tag}:${type}`
164
+ }
165
+ }
166
+ ```
167
+
168
+ ##### example
169
+ ```js
170
+ class EventsExample extends Tonic {
171
+ // ...
172
+ }
173
+
174
+ EventsExample.event('testing')
175
+ // => 'events-example:testing'
176
+ ```
177
+
178
+ #### `dispatch`
179
+ Emit a regular, non-namespaced event.
180
+
181
+ ```ts
182
+ {
183
+ dispatch (eventName:string, detail = null):void
184
+ }
185
+ ```
186
+
187
+ ##### `dispatch` example
188
+
189
+ ```js
190
+ class EventsExample extends Tonic {
191
+ // ...
192
+ }
193
+
194
+ document.body.addEventListener('testing', ev => {
195
+ // events bubble by default
196
+ console.log(ev.type) // => 'testing'
197
+ console.log(ev.detail) // => 'some data'
198
+ })
199
+
200
+ const el = document.querySelector('events-example')
201
+ el.dispatch('testing', 'some data')
202
+
203
+ // override default values
204
+ el.dispatch('more testing', 'some data', {
205
+ bubbles: false
206
+ cancelable: false
207
+ })
208
+ ```
209
+
210
+ ## Useful links
211
+ - [Tonic components](https://github.com/socketsupply/components)
212
+ - [Migration from the early versions of Tonic](./MIGRATION.md)
213
+ - [API](./API.md)
214
+ - [Troubleshooting](./HELP.md)
215
+ - [Web Component lifecycle methods](https://gomakethings.com/the-web-component-lifecycle-methods/)
216
+ - [How to detect when attributes change on a Web Component](https://gomakethings.com/how-to-detect-when-attributes-change-on-a-web-component/)
217
+ - [API docs generated from typescript](https://substrate-system.github.io/tonic/classes/Tonic.html)
package/dist/tonic.js ADDED
@@ -0,0 +1,458 @@
1
+ export class TonicTemplate {
2
+ constructor(rawText, templateStrings, unsafe) {
3
+ this.isTonicTemplate = true;
4
+ this.unsafe = !!unsafe;
5
+ this.rawText = rawText;
6
+ this.templateStrings = templateStrings;
7
+ }
8
+ valueOf() {
9
+ return this.rawText;
10
+ }
11
+ toString() {
12
+ return this.rawText;
13
+ }
14
+ }
15
+ export class Tonic extends window.HTMLElement {
16
+ constructor() {
17
+ super();
18
+ this._props = Tonic.getPropertyNames(this);
19
+ const state = Tonic._states[super.id];
20
+ delete Tonic._states[super.id];
21
+ this._state = state || {};
22
+ this.preventRenderOnReconnect = false;
23
+ this.props = {};
24
+ this.elements = [...this.children];
25
+ this.elements.__children__ = true;
26
+ this.nodes = [...this.childNodes];
27
+ this.nodes.__children__ = true;
28
+ this._events();
29
+ }
30
+ static {
31
+ this._tags = "";
32
+ }
33
+ static {
34
+ this._refIds = [];
35
+ }
36
+ static {
37
+ this._data = {};
38
+ }
39
+ static {
40
+ this._states = {};
41
+ }
42
+ static {
43
+ this._children = {};
44
+ }
45
+ static {
46
+ this._reg = {};
47
+ }
48
+ static {
49
+ this._stylesheetRegistry = [];
50
+ }
51
+ static {
52
+ this._index = 0;
53
+ }
54
+ // @ts-expect-error VERSION is injected during build
55
+ static get version() {
56
+ return "16.0.5";
57
+ }
58
+ static get SPREAD() {
59
+ return /\.\.\.\s?(__\w+__\w+__)/g;
60
+ }
61
+ static get ESC() {
62
+ return /["&'<>`/]/g;
63
+ }
64
+ static get AsyncFunctionGenerator() {
65
+ return async function* () {
66
+ }.constructor;
67
+ }
68
+ // eslint-disable-next-line
69
+ static get AsyncFunction() {
70
+ return async function() {
71
+ }.constructor;
72
+ }
73
+ static get MAP() {
74
+ return {
75
+ '"': "&quot;",
76
+ "&": "&amp;",
77
+ "'": "&#x27;",
78
+ "<": "&lt;",
79
+ ">": "&gt;",
80
+ "`": "&#x60;",
81
+ "/": "&#x2F;"
82
+ };
83
+ }
84
+ get isTonicComponent() {
85
+ return true;
86
+ }
87
+ /**
88
+ * Get a namespaced event name, given a non-namespaced string.
89
+ *
90
+ * @example
91
+ * MyElement.event('example') // => my-element:example
92
+ *
93
+ * @param {string} type The name of the event
94
+ * @returns {string} The namespaced event name
95
+ */
96
+ static event(type) {
97
+ return `${this.tag}:${type}`;
98
+ }
99
+ /**
100
+ * Get the tag name of this component.
101
+ */
102
+ static get tag() {
103
+ return Tonic.getTagName(this.name);
104
+ }
105
+ static _createId() {
106
+ return `tonic${Tonic._index++}`;
107
+ }
108
+ static _normalizeAttrs(o, x = {}) {
109
+ [...o].forEach((o2) => x[o2.name] = o2.value);
110
+ return x;
111
+ }
112
+ _checkId() {
113
+ const _id = super.id;
114
+ if (!_id) {
115
+ const html = this.outerHTML.replace(this.innerHTML, "...");
116
+ throw new Error(`Component: ${html} has no id`);
117
+ }
118
+ return _id;
119
+ }
120
+ /**
121
+ * Get the component state property.
122
+ */
123
+ get state() {
124
+ return this._checkId(), this._state;
125
+ }
126
+ set state(newState) {
127
+ this._state = (this._checkId(), newState);
128
+ }
129
+ _events() {
130
+ const hp = Object.getOwnPropertyNames(window.HTMLElement.prototype);
131
+ for (const p of this._props) {
132
+ if (hp.indexOf("on" + p) === -1) continue;
133
+ this.addEventListener(p, this);
134
+ }
135
+ }
136
+ _prop(o) {
137
+ const id = this._id;
138
+ const p = `__${id}__${Tonic._createId()}__`;
139
+ Tonic._data[id] = Tonic._data[id] || {};
140
+ Tonic._data[id][p] = o;
141
+ return p;
142
+ }
143
+ _placehold(r) {
144
+ const id = this._id;
145
+ const ref = `placehold:${id}:${Tonic._createId()}__`;
146
+ Tonic._children[id] = Tonic._children[id] || {};
147
+ Tonic._children[id][ref] = r;
148
+ return ref;
149
+ }
150
+ static match(el, s) {
151
+ if (!el.matches) el = el.parentElement;
152
+ return el.matches(s) ? el : el.closest(s);
153
+ }
154
+ static getTagName(camelName) {
155
+ return camelName.match(/[A-Z][a-z0-9]*/g).join("-").toLowerCase();
156
+ }
157
+ static getPropertyNames(proto) {
158
+ const props = [];
159
+ while (proto && proto !== Tonic.prototype) {
160
+ props.push(...Object.getOwnPropertyNames(proto));
161
+ proto = Object.getPrototypeOf(proto);
162
+ }
163
+ return props;
164
+ }
165
+ /**
166
+ * Add a component. Calls `window.customElements.define` with the
167
+ * component's name.
168
+ *
169
+ * @param {TonicComponent} c
170
+ * @param {string} [htmlName] Name of the element, default to the class name
171
+ * @returns {void}
172
+ */
173
+ static add(c, htmlName) {
174
+ const hasValidName = htmlName || c.name && c.name.length > 1;
175
+ if (!hasValidName) {
176
+ throw Error("Mangling. https://bit.ly/2TkJ6zP");
177
+ }
178
+ if (!htmlName) htmlName = Tonic.getTagName(c.name);
179
+ if (!Tonic.ssr && window.customElements.get(htmlName)) {
180
+ throw new Error(`Cannot Tonic.add(${c.name}, '${htmlName}') twice`);
181
+ }
182
+ if (!c.prototype || !c.prototype.isTonicComponent) {
183
+ const tmp = { [c.name]: class extends Tonic {
184
+ } }[c.name];
185
+ tmp.prototype.render = c;
186
+ c = tmp;
187
+ }
188
+ c.prototype._props = Tonic.getPropertyNames(c.prototype);
189
+ Tonic._reg[htmlName] = c;
190
+ Tonic._tags = Object.keys(Tonic._reg).join();
191
+ window.customElements.define(htmlName, c);
192
+ if (typeof c.stylesheet === "function") {
193
+ Tonic.registerStyles(c.stylesheet);
194
+ }
195
+ return c;
196
+ }
197
+ static registerStyles(stylesheetFn) {
198
+ if (Tonic._stylesheetRegistry.includes(stylesheetFn)) return;
199
+ Tonic._stylesheetRegistry.push(stylesheetFn);
200
+ const styleNode = document.createElement("style");
201
+ if (Tonic.nonce) styleNode.setAttribute("nonce", Tonic.nonce);
202
+ styleNode.appendChild(document.createTextNode(stylesheetFn()));
203
+ if (document.head) document.head.appendChild(styleNode);
204
+ }
205
+ static escape(s) {
206
+ return s.replace(Tonic.ESC, (c) => Tonic.MAP[c]);
207
+ }
208
+ static unsafeRawString(s, templateStrings) {
209
+ return new TonicTemplate(s, templateStrings, true);
210
+ }
211
+ /**
212
+ * Emit a regular, non-namespaced event.
213
+ *
214
+ * @param {string} eventName Event name as a string.
215
+ * @param {any} detail Any data to go with the event.
216
+ */
217
+ dispatch(eventName, detail = null) {
218
+ const opts = { bubbles: true, detail };
219
+ this.dispatchEvent(new window.CustomEvent(eventName, opts));
220
+ }
221
+ /**
222
+ * Emit a namespaced event, using a convention for event names.
223
+ *
224
+ * @example
225
+ * myComponent.emit('test') // => `my-compnent:test`
226
+ *
227
+ * @param {string} type The event type, comes after `:` in event name.
228
+ * @param {string|object|any[]} detail detail for Event constructor
229
+ * @param {{ bubbles?:boolean, cancelable?:boolean }} opts `Cancelable` and
230
+ * `bubbles`
231
+ * @returns {boolean}
232
+ */
233
+ emit(type, detail = {}, opts = {}) {
234
+ const namespace = Tonic.getTagName(this.constructor.name);
235
+ const event = new CustomEvent(`${namespace}:${type}`, {
236
+ bubbles: opts.bubbles === void 0 ? true : opts.bubbles,
237
+ cancelable: opts.cancelable === void 0 ? true : opts.cancelable,
238
+ detail
239
+ });
240
+ return this.dispatchEvent(event);
241
+ }
242
+ html(strings, ...values) {
243
+ const refs = (o) => {
244
+ if (o && o.__children__) return this._placehold(o);
245
+ if (o && o.isTonicTemplate) return o.rawText;
246
+ switch (Object.prototype.toString.call(o)) {
247
+ case "[object HTMLCollection]":
248
+ case "[object NodeList]":
249
+ return this._placehold([...o]);
250
+ case "[object Array]": {
251
+ if (o.every((x) => x.isTonicTemplate && !x.unsafe)) {
252
+ return new TonicTemplate(o.join("\n"), null, false);
253
+ }
254
+ return this._prop(o);
255
+ }
256
+ case "[object Object]":
257
+ case "[object Function]":
258
+ case "[object AsyncFunction]":
259
+ case "[object Set]":
260
+ case "[object Map]":
261
+ case "[object WeakMap]":
262
+ case "[object File]":
263
+ return this._prop(o);
264
+ case "[object NamedNodeMap]":
265
+ return this._prop(Tonic._normalizeAttrs(o));
266
+ case "[object Number]":
267
+ return `${o}__float`;
268
+ case "[object String]":
269
+ return Tonic.escape(o);
270
+ case "[object Boolean]":
271
+ return `${o}__boolean`;
272
+ case "[object Null]":
273
+ return `${o}__null`;
274
+ case "[object HTMLElement]":
275
+ return this._placehold([o]);
276
+ }
277
+ if (typeof o === "object" && o && o.nodeType === 1 && typeof o.cloneNode === "function") {
278
+ return this._placehold([o]);
279
+ }
280
+ return o;
281
+ };
282
+ const out = [];
283
+ for (let i = 0; i < strings.length - 1; i++) {
284
+ out.push(strings[i], refs(values[i]));
285
+ }
286
+ out.push(strings[strings.length - 1]);
287
+ const htmlStr = out.join("").replace(Tonic.SPREAD, (_, p) => {
288
+ const o = Tonic._data[p.split("__")[1]][p];
289
+ return Object.entries(o).map(([key, value]) => {
290
+ const k = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
291
+ if (value === true) return k;
292
+ else if (value) return `${k}="${Tonic.escape(String(value))}"`;
293
+ else return "";
294
+ }).filter(Boolean).join(" ");
295
+ });
296
+ return new TonicTemplate(htmlStr, strings, false);
297
+ }
298
+ scheduleReRender(oldProps) {
299
+ if (this.pendingReRender) return this.pendingReRender;
300
+ this.pendingReRender = new Promise((resolve) => setTimeout(() => {
301
+ if (!this.isInDocument(this.shadowRoot || this)) return;
302
+ const p = this._set(this.shadowRoot || this, this.render);
303
+ this.pendingReRender = null;
304
+ if (p && p.then) {
305
+ return p.then(() => {
306
+ this.updated && this.updated(oldProps);
307
+ resolve(this);
308
+ });
309
+ }
310
+ this.updated && this.updated(oldProps);
311
+ resolve(this);
312
+ }, 0));
313
+ return this.pendingReRender;
314
+ }
315
+ /**
316
+ * Update the view
317
+ */
318
+ reRender(o = this.props) {
319
+ const oldProps = { ...this.props };
320
+ this.props = typeof o === "function" ? o(oldProps) : o;
321
+ return this.scheduleReRender(oldProps);
322
+ }
323
+ /**
324
+ * If there is a method with the same name as the event type,
325
+ * then call the method.
326
+ * @see {@link https://gomakethings.com/the-handleevent-method-is-the-absolute-best-way-to-handle-events-in-web-components/#what-is-the-handleevent-method What is the handleEvent() method?}
327
+ */
328
+ handleEvent(ev) {
329
+ this[ev.type] && this[ev.type](ev);
330
+ }
331
+ _drainIterator(target, iterator) {
332
+ return iterator.next().then((result) => {
333
+ this._set(target, null, result.value);
334
+ if (result.done) return;
335
+ return this._drainIterator(target, iterator);
336
+ });
337
+ }
338
+ /**
339
+ * _set
340
+ * @param {Element|InstanceType<typeof Tonic>|ShadowRoot} target
341
+ * @param {()=>any} render
342
+ * @param {string} content
343
+ * @returns {Promise<void>|void}
344
+ * @private
345
+ */
346
+ _set(target, render, content = "") {
347
+ this.willRender && this.willRender();
348
+ for (const node of target.querySelectorAll(Tonic._tags)) {
349
+ if (!node.isTonicComponent) continue;
350
+ const id = node.getAttribute("id");
351
+ if (!id || !Tonic._refIds.includes(id)) continue;
352
+ Tonic._states[id] = node.state;
353
+ }
354
+ if (render instanceof Tonic.AsyncFunction) {
355
+ return render.call(this, this.html, this.props).then((content2) => this._apply(target, content2));
356
+ } else if (render instanceof Tonic.AsyncFunctionGenerator) {
357
+ return this._drainIterator(target, render.call(this));
358
+ } else if (render === null) {
359
+ this._apply(target, content);
360
+ } else if (render instanceof Function) {
361
+ this._apply(target, render.call(this, this.html, this.props) || "");
362
+ }
363
+ }
364
+ _apply(target, content) {
365
+ if (content && content.isTonicTemplate) {
366
+ content = content.rawText;
367
+ } else if (typeof content === "string") {
368
+ content = Tonic.escape(content);
369
+ }
370
+ if (typeof content === "string") {
371
+ if (this.stylesheet) {
372
+ content = `<style nonce=${Tonic.nonce || ""}>${this.stylesheet()}</style>${content}`;
373
+ }
374
+ target.innerHTML = content;
375
+ if (this.styles) {
376
+ const styles = this.styles();
377
+ for (const node of target.querySelectorAll("[styles]")) {
378
+ for (const s of node.getAttribute("styles").split(/\s+/)) {
379
+ Object.assign(node.style, styles[s.trim()]);
380
+ }
381
+ }
382
+ }
383
+ const children = Tonic._children[this._id] || {};
384
+ const walk = (node, fn) => {
385
+ if (node.nodeType === 3) {
386
+ const id = node.textContent.trim();
387
+ if (children[id]) fn(node, children[id], id);
388
+ }
389
+ const childNodes = node.childNodes;
390
+ if (!childNodes) return;
391
+ for (let i = 0; i < childNodes.length; i++) {
392
+ walk(childNodes[i], fn);
393
+ }
394
+ };
395
+ walk(target, (node, children2, id) => {
396
+ for (const child of children2) {
397
+ node.parentNode.insertBefore(child, node);
398
+ }
399
+ delete Tonic._children[this._id][id];
400
+ node.parentNode.removeChild(node);
401
+ });
402
+ } else {
403
+ target.innerHTML = "";
404
+ target.appendChild(content.cloneNode(true));
405
+ }
406
+ }
407
+ connectedCallback() {
408
+ this.root = this.shadowRoot || this;
409
+ if (super.id && !Tonic._refIds.includes(super.id)) {
410
+ Tonic._refIds.push(super.id);
411
+ }
412
+ const cc = (s) => s.replace(/-(.)/g, (_, m) => m.toUpperCase());
413
+ for (const { name: _name, value } of this.attributes) {
414
+ const name = cc(_name);
415
+ const p = this.props[name] = value;
416
+ if (/__\w+__\w+__/.test(p)) {
417
+ const { 1: root } = p.split("__");
418
+ this.props[name] = Tonic._data[root][p];
419
+ } else if (/\d+__float/.test(p)) {
420
+ this.props[name] = parseFloat(p);
421
+ } else if (p === "null__null") {
422
+ this.props[name] = null;
423
+ } else if (/\w+__boolean/.test(p)) {
424
+ this.props[name] = p.includes("true");
425
+ } else if (/placehold:\w+:\w+__/.test(p)) {
426
+ const { 1: root } = p.split(":");
427
+ this.props[name] = Tonic._children[root][p][0];
428
+ }
429
+ }
430
+ this.props = Object.assign(
431
+ this.defaults ? this.defaults() : {},
432
+ this.props
433
+ );
434
+ this._id = this._id || Tonic._createId();
435
+ this.willConnect && this.willConnect();
436
+ if (!this.isInDocument(this.root)) return;
437
+ if (!this.preventRenderOnReconnect) {
438
+ if (!this._source) {
439
+ this._source = this.innerHTML;
440
+ } else {
441
+ this.innerHTML = this._source;
442
+ }
443
+ const p = this._set(this.root, this.render);
444
+ if (p && p.then) return p.then(() => this.connected && this.connected());
445
+ }
446
+ this.connected && this.connected();
447
+ }
448
+ isInDocument(target) {
449
+ const root = target.getRootNode();
450
+ return root === document || root.toString() === "[object ShadowRoot]";
451
+ }
452
+ disconnectedCallback() {
453
+ this.disconnected && this.disconnected();
454
+ delete Tonic._data[this._id];
455
+ delete Tonic._children[this._id];
456
+ }
457
+ }
458
+ export default Tonic;
@@ -0,0 +1,2 @@
1
+ var f=Object.defineProperty;var c=(p,l)=>f(p,"name",{value:l,configurable:!0});export class TonicTemplate{static{c(this,"TonicTemplate")}constructor(l,t,e){this.isTonicTemplate=!0,this.unsafe=!!e,this.rawText=l,this.templateStrings=t}valueOf(){return this.rawText}toString(){return this.rawText}}export class Tonic extends window.HTMLElement{constructor(){super();this._props=Tonic.getPropertyNames(this);const t=Tonic._states[super.id];delete Tonic._states[super.id],this._state=t||{},this.preventRenderOnReconnect=!1,this.props={},this.elements=[...this.children],this.elements.__children__=!0,this.nodes=[...this.childNodes],this.nodes.__children__=!0,this._events()}static{c(this,"Tonic")}static{this._tags=""}static{this._refIds=[]}static{this._data={}}static{this._states={}}static{this._children={}}static{this._reg={}}static{this._stylesheetRegistry=[]}static{this._index=0}static get version(){return VERSION??null}static get SPREAD(){return/\.\.\.\s?(__\w+__\w+__)/g}static get ESC(){return/["&'<>`/]/g}static get AsyncFunctionGenerator(){return async function*(){}.constructor}static get AsyncFunction(){return async function(){}.constructor}static get MAP(){return{'"':"&quot;","&":"&amp;","'":"&#x27;","<":"&lt;",">":"&gt;","`":"&#x60;","/":"&#x2F;"}}get isTonicComponent(){return!0}static event(t){return`${this.tag}:${t}`}static get tag(){return Tonic.getTagName(this.name)}static _createId(){return`tonic${Tonic._index++}`}static _normalizeAttrs(t,e={}){return[...t].forEach(n=>e[n.name]=n.value),e}_checkId(){const t=super.id;if(!t){const e=this.outerHTML.replace(this.innerHTML,"...");throw new Error(`Component: ${e} has no id`)}return t}get state(){return this._checkId(),this._state}set state(t){this._state=(this._checkId(),t)}_events(){const t=Object.getOwnPropertyNames(window.HTMLElement.prototype);for(const e of this._props)t.indexOf("on"+e)!==-1&&this.addEventListener(e,this)}_prop(t){const e=this._id,n=`__${e}__${Tonic._createId()}__`;return Tonic._data[e]=Tonic._data[e]||{},Tonic._data[e][n]=t,n}_placehold(t){const e=this._id,n=`placehold:${e}:${Tonic._createId()}__`;return Tonic._children[e]=Tonic._children[e]||{},Tonic._children[e][n]=t,n}static match(t,e){return t.matches||(t=t.parentElement),t.matches(e)?t:t.closest(e)}static getTagName(t){return t.match(/[A-Z][a-z0-9]*/g).join("-").toLowerCase()}static getPropertyNames(t){const e=[];for(;t&&t!==Tonic.prototype;)e.push(...Object.getOwnPropertyNames(t)),t=Object.getPrototypeOf(t);return e}static add(t,e){if(!(e||t.name&&t.name.length>1))throw Error("Mangling. https://bit.ly/2TkJ6zP");if(e||(e=Tonic.getTagName(t.name)),!Tonic.ssr&&window.customElements.get(e))throw new Error(`Cannot Tonic.add(${t.name}, '${e}') twice`);if(!t.prototype||!t.prototype.isTonicComponent){const i={[t.name]:class extends Tonic{}}[t.name];i.prototype.render=t,t=i}return t.prototype._props=Tonic.getPropertyNames(t.prototype),Tonic._reg[e]=t,Tonic._tags=Object.keys(Tonic._reg).join(),window.customElements.define(e,t),typeof t.stylesheet=="function"&&Tonic.registerStyles(t.stylesheet),t}static registerStyles(t){if(Tonic._stylesheetRegistry.includes(t))return;Tonic._stylesheetRegistry.push(t);const e=document.createElement("style");Tonic.nonce&&e.setAttribute("nonce",Tonic.nonce),e.appendChild(document.createTextNode(t())),document.head&&document.head.appendChild(e)}static escape(t){return t.replace(Tonic.ESC,e=>Tonic.MAP[e])}static unsafeRawString(t,e){return new TonicTemplate(t,e,!0)}dispatch(t,e=null){const n={bubbles:!0,detail:e};this.dispatchEvent(new window.CustomEvent(t,n))}emit(t,e={},n={}){const i=Tonic.getTagName(this.constructor.name),r=new CustomEvent(`${i}:${t}`,{bubbles:n.bubbles===void 0?!0:n.bubbles,cancelable:n.cancelable===void 0?!0:n.cancelable,detail:e});return this.dispatchEvent(r)}html(t,...e){const n=c(s=>{if(s&&s.__children__)return this._placehold(s);if(s&&s.isTonicTemplate)return s.rawText;switch(Object.prototype.toString.call(s)){case"[object HTMLCollection]":case"[object NodeList]":return this._placehold([...s]);case"[object Array]":return s.every(a=>a.isTonicTemplate&&!a.unsafe)?new TonicTemplate(s.join(`
2
+ `),null,!1):this._prop(s);case"[object Object]":case"[object Function]":case"[object AsyncFunction]":case"[object Set]":case"[object Map]":case"[object WeakMap]":case"[object File]":return this._prop(s);case"[object NamedNodeMap]":return this._prop(Tonic._normalizeAttrs(s));case"[object Number]":return`${s}__float`;case"[object String]":return Tonic.escape(s);case"[object Boolean]":return`${s}__boolean`;case"[object Null]":return`${s}__null`;case"[object HTMLElement]":return this._placehold([s])}return typeof s=="object"&&s&&s.nodeType===1&&typeof s.cloneNode=="function"?this._placehold([s]):s},"refs"),i=[];for(let s=0;s<t.length-1;s++)i.push(t[s],n(e[s]));i.push(t[t.length-1]);const r=i.join("").replace(Tonic.SPREAD,(s,a)=>{const o=Tonic._data[a.split("__")[1]][a];return Object.entries(o).map(([u,h])=>{const d=u.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();return h===!0?d:h?`${d}="${Tonic.escape(String(h))}"`:""}).filter(Boolean).join(" ")});return new TonicTemplate(r,t,!1)}scheduleReRender(t){return this.pendingReRender?this.pendingReRender:(this.pendingReRender=new Promise(e=>setTimeout(()=>{if(!this.isInDocument(this.shadowRoot||this))return;const n=this._set(this.shadowRoot||this,this.render);if(this.pendingReRender=null,n&&n.then)return n.then(()=>{this.updated&&this.updated(t),e(this)});this.updated&&this.updated(t),e(this)},0)),this.pendingReRender)}reRender(t=this.props){const e={...this.props};return this.props=typeof t=="function"?t(e):t,this.scheduleReRender(e)}handleEvent(t){this[t.type]&&this[t.type](t)}_drainIterator(t,e){return e.next().then(n=>{if(this._set(t,null,n.value),!n.done)return this._drainIterator(t,e)})}_set(t,e,n=""){this.willRender&&this.willRender();for(const i of t.querySelectorAll(Tonic._tags)){if(!i.isTonicComponent)continue;const r=i.getAttribute("id");!r||!Tonic._refIds.includes(r)||(Tonic._states[r]=i.state)}if(e instanceof Tonic.AsyncFunction)return e.call(this,this.html,this.props).then(i=>this._apply(t,i));if(e instanceof Tonic.AsyncFunctionGenerator)return this._drainIterator(t,e.call(this));e===null?this._apply(t,n):e instanceof Function&&this._apply(t,e.call(this,this.html,this.props)||"")}_apply(t,e){if(e&&e.isTonicTemplate?e=e.rawText:typeof e=="string"&&(e=Tonic.escape(e)),typeof e=="string"){if(this.stylesheet&&(e=`<style nonce=${Tonic.nonce||""}>${this.stylesheet()}</style>${e}`),t.innerHTML=e,this.styles){const r=this.styles();for(const s of t.querySelectorAll("[styles]"))for(const a of s.getAttribute("styles").split(/\s+/))Object.assign(s.style,r[a.trim()])}const n=Tonic._children[this._id]||{},i=c((r,s)=>{if(r.nodeType===3){const o=r.textContent.trim();n[o]&&s(r,n[o],o)}const a=r.childNodes;if(a)for(let o=0;o<a.length;o++)i(a[o],s)},"walk");i(t,(r,s,a)=>{for(const o of s)r.parentNode.insertBefore(o,r);delete Tonic._children[this._id][a],r.parentNode.removeChild(r)})}else t.innerHTML="",t.appendChild(e.cloneNode(!0))}connectedCallback(){this.root=this.shadowRoot||this,super.id&&!Tonic._refIds.includes(super.id)&&Tonic._refIds.push(super.id);const t=c(e=>e.replace(/-(.)/g,(n,i)=>i.toUpperCase()),"cc");for(const{name:e,value:n}of this.attributes){const i=t(e),r=this.props[i]=n;if(/__\w+__\w+__/.test(r)){const{1:s}=r.split("__");this.props[i]=Tonic._data[s][r]}else if(/\d+__float/.test(r))this.props[i]=parseFloat(r);else if(r==="null__null")this.props[i]=null;else if(/\w+__boolean/.test(r))this.props[i]=r.includes("true");else if(/placehold:\w+:\w+__/.test(r)){const{1:s}=r.split(":");this.props[i]=Tonic._children[s][r][0]}}if(this.props=Object.assign(this.defaults?this.defaults():{},this.props),this._id=this._id||Tonic._createId(),this.willConnect&&this.willConnect(),!!this.isInDocument(this.root)){if(!this.preventRenderOnReconnect){this._source?this.innerHTML=this._source:this._source=this.innerHTML;const e=this._set(this.root,this.render);if(e&&e.then)return e.then(()=>this.connected&&this.connected())}this.connected&&this.connected()}}isInDocument(t){const e=t.getRootNode();return e===document||e.toString()==="[object ShadowRoot]"}disconnectedCallback(){this.disconnected&&this.disconnected(),delete Tonic._data[this._id],delete Tonic._children[this._id]}}export default Tonic;
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@substrate-system/tonic",
3
+ "version": "16.0.5",
4
+ "description": "A component framework.",
5
+ "main": "dist/tonic.js",
6
+ "files": [
7
+ "dist/*"
8
+ ],
9
+ "type": "module",
10
+ "scripts": {
11
+ "lint": "eslint ./src/index.ts ./test/index.js",
12
+ "test": "npm run build && npm run lint && esbuild --bundle test/index.js | tape-run | tap-spec",
13
+ "ci:test:tape-run": "esbuild --bundle test/index.js | tape-run",
14
+ "test:open": "npm run build && esbuild --bundle test/index.js | tape-run --browser chrome --keep-open",
15
+ "build:main": "esbuild src/index.ts --define:VERSION=\\\"$npm_package_version\\\" --outfile=dist/tonic.js",
16
+ "build:minify": "esbuild src/index.ts --keep-names --minify --outfile=dist/tonic.min.js",
17
+ "build-docs": "typedoc ./src/index.ts",
18
+ "build": "mkdir -p ./dist && rm -rf ./dist/* && npm run build:main && npm run build:minify",
19
+ "toc": "markdown-toc --maxdepth 3 -i README.md",
20
+ "preversion": "npm run lint",
21
+ "version": "npm run toc && auto-changelog -p --template keepachangelog --breaking-pattern 'BREAKING CHANGE:' && git add CHANGELOG.md README.md",
22
+ "postversion": "git push --follow-tags && npm publish",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "devDependencies": {
26
+ "@bicycle-codes/tapzero": "0.10.3",
27
+ "@typescript-eslint/eslint-plugin": "^8.4.0",
28
+ "@typescript-eslint/parser": "^8.4.0",
29
+ "auto-changelog": "2.4.0",
30
+ "benchmark": "^2.1.4",
31
+ "esbuild": "^0.23.1",
32
+ "eslint": "^8.57.0",
33
+ "eslint-config-standard": "^17.1.0",
34
+ "markdown-toc": "1.2.0",
35
+ "tap-spec": "5.0.0",
36
+ "tape-run": "^11.0.0",
37
+ "typedoc": "0.26.6",
38
+ "uuid": "^10.0.0"
39
+ },
40
+ "exports": {
41
+ ".": "./dist/tonic.js",
42
+ "./min": "./dist/tonic.min.js"
43
+ },
44
+ "contributors": [
45
+ {
46
+ "name": "nichoth",
47
+ "email": "nichoth@nichoth.com",
48
+ "url": "https://nichoth.com/"
49
+ },
50
+ {
51
+ "name": "Raynos",
52
+ "email": "raynos2@gmail.com"
53
+ }
54
+ ],
55
+ "license": "MIT",
56
+ "directories": {
57
+ "test": "test"
58
+ },
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/bicycle-codes/tonic.git"
62
+ },
63
+ "bugs": {
64
+ "url": "https://github.com/bicycle-codes/tonic/issues"
65
+ },
66
+ "homepage": "https://tonicframework.dev"
67
+ }