@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 +18 -0
- package/README.md +217 -0
- package/dist/tonic.js +458 -0
- package/dist/tonic.min.js +2 -0
- package/package.json +67 -0
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
|
+

|
|
2
|
+
[](README.md)
|
|
3
|
+
[](https://semver.org/)
|
|
4
|
+
[](package.json)
|
|
5
|
+
[](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
|
+
'"': """,
|
|
76
|
+
"&": "&",
|
|
77
|
+
"'": "'",
|
|
78
|
+
"<": "<",
|
|
79
|
+
">": ">",
|
|
80
|
+
"`": "`",
|
|
81
|
+
"/": "/"
|
|
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{'"':""","&":"&","'":"'","<":"<",">":">","`":"`","/":"/"}}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
|
+
}
|