thunderous 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +311 -0
- package/package.json +18 -3
- package/readme.md +0 -73
package/README.md
ADDED
@@ -0,0 +1,311 @@
|
|
1
|
+
# Thunderous
|
2
|
+
|
3
|
+
Thunderous is a library for writing web components in a functional style, reducing the boilerplate, while signals make it better for managing and sharing state.
|
4
|
+
|
5
|
+
Each component renders only once, then binds signals to DOM nodes for direct updates with _thunderous_ efficiency.
|
6
|
+
|
7
|
+
## Table of Contents
|
8
|
+
|
9
|
+
- [Installation](#installation)
|
10
|
+
- [Usage](#usage)
|
11
|
+
- [Development](#development)
|
12
|
+
- [License](#license)
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Install Thunderous via npm:
|
17
|
+
|
18
|
+
```sh
|
19
|
+
npm install thunderous
|
20
|
+
```
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Thunderous makes it easy to define smaller components with less noise.
|
25
|
+
|
26
|
+
<!-- prettier-ignore-start -->
|
27
|
+
```ts
|
28
|
+
import { customElement, html, css, createSignal } from 'thunderous';
|
29
|
+
|
30
|
+
const myStyleSheet = css`
|
31
|
+
:host {
|
32
|
+
display: block;
|
33
|
+
font-family: sans-serif;
|
34
|
+
}
|
35
|
+
`;
|
36
|
+
|
37
|
+
const MyElement = customElement((params) => {
|
38
|
+
const { connectedCallback, refs, adoptStyleSheet } = params;
|
39
|
+
|
40
|
+
const [count, setCount] = createSignal(0);
|
41
|
+
|
42
|
+
connectedCallback(() => {
|
43
|
+
refs.increment.addEventListener('click', () => {
|
44
|
+
setCount(count() + 1);
|
45
|
+
});
|
46
|
+
});
|
47
|
+
|
48
|
+
adoptStyleSheet(myStyleSheet);
|
49
|
+
|
50
|
+
return html`
|
51
|
+
<button ref="increment">Increment</button>
|
52
|
+
<output>${count}</output>
|
53
|
+
`;
|
54
|
+
});
|
55
|
+
|
56
|
+
MyElement.define('my-element');
|
57
|
+
```
|
58
|
+
<!-- prettier-ignore-end -->
|
59
|
+
|
60
|
+
### The Native Features
|
61
|
+
|
62
|
+
Everything the native class definition can do, this can do too. You'll find that these things are not far removed from the native approach, so they ought to be familiar.
|
63
|
+
|
64
|
+
#### Defining Custom Elements
|
65
|
+
|
66
|
+
The `customElement` function allows you to author the web component with the render function and it returns an `ElementResult` that has some helpful methods like `define()` and `eject()`.
|
67
|
+
|
68
|
+
- `ElementResult.define()` is a little safer than `customElements.define()` because it first checks if the component was already defined, without throwing an error. It will, however, log a warning. There's no need to pass the class since it already has that context.
|
69
|
+
|
70
|
+
```ts
|
71
|
+
const MyElement = customElement(() => html`<slot></slot>`);
|
72
|
+
|
73
|
+
MyElement.define('my-element');
|
74
|
+
```
|
75
|
+
|
76
|
+
- `ElementResult.eject()` is useful in case you need to access the underlying class for some reason; perhaps you want to extend it and/or set static properties.
|
77
|
+
|
78
|
+
```ts
|
79
|
+
const MyElementClass = MyElement.eject();
|
80
|
+
|
81
|
+
class MyOtherElement extends MyElementClass {
|
82
|
+
/* ... */
|
83
|
+
}
|
84
|
+
```
|
85
|
+
|
86
|
+
These may also be chained together, like `MyElement.define('my-element').eject()`.
|
87
|
+
|
88
|
+
#### Lifecycle Methods
|
89
|
+
|
90
|
+
Any lifecycle method you may need can be accessed from the params of your render function. The only difference is that these are callback registrations, so the same callback you would normally write is just passed in.
|
91
|
+
|
92
|
+
<!-- prettier-ignore-start -->
|
93
|
+
```ts
|
94
|
+
const MyElement = customElement((params) => {
|
95
|
+
const {
|
96
|
+
adoptedCallback,
|
97
|
+
connectedCallback,
|
98
|
+
disconnectedCallback,
|
99
|
+
attributeChangedCallback,
|
100
|
+
} = params;
|
101
|
+
|
102
|
+
/* ... */
|
103
|
+
});
|
104
|
+
```
|
105
|
+
<!-- prettier-ignore-end -->
|
106
|
+
|
107
|
+
If you need to support forms, pass an options object as the second argument to `customElement`.
|
108
|
+
|
109
|
+
<!-- prettier-ignore-start -->
|
110
|
+
```ts
|
111
|
+
const MyElement = customElement((params) => {
|
112
|
+
const {
|
113
|
+
formDisabledCallback,
|
114
|
+
formResetCallback,
|
115
|
+
formStateRestoreCallback,
|
116
|
+
} = params;
|
117
|
+
|
118
|
+
/* ... */
|
119
|
+
}, { formAssociated: true });
|
120
|
+
```
|
121
|
+
<!-- prettier-ignore-end -->
|
122
|
+
|
123
|
+
#### Element Internals
|
124
|
+
|
125
|
+
You can always define the internals the same as you usually would.
|
126
|
+
|
127
|
+
<!-- prettier-ignore-start -->
|
128
|
+
```ts
|
129
|
+
const MyElement = customElement((params) => {
|
130
|
+
const {
|
131
|
+
internals,
|
132
|
+
} = params;
|
133
|
+
|
134
|
+
internals.ariaRequired = 'true';
|
135
|
+
|
136
|
+
/* ... */
|
137
|
+
}, { formAssociated: true });
|
138
|
+
```
|
139
|
+
<!-- prettier-ignore-end -->
|
140
|
+
|
141
|
+
#### Roots and Element Internals
|
142
|
+
|
143
|
+
You can always define the internals the same as you usually would, and if for some reason you need access to either the element itself or the shadow root, you can do so as illustrated below.
|
144
|
+
|
145
|
+
<!-- prettier-ignore-start -->
|
146
|
+
```ts
|
147
|
+
const MyElement = customElement((params) => {
|
148
|
+
const {
|
149
|
+
internals,
|
150
|
+
elementRef,
|
151
|
+
root,
|
152
|
+
} = params;
|
153
|
+
|
154
|
+
internals.ariaRequired = 'true';
|
155
|
+
const childLink = elementRef.querySelector('a[href]'); // light DOM
|
156
|
+
const innerLink = root.querySelector('a[href]'); // shadow DOM
|
157
|
+
|
158
|
+
/* ... */
|
159
|
+
}, { formAssociated: true });
|
160
|
+
```
|
161
|
+
<!-- prettier-ignore-end -->
|
162
|
+
|
163
|
+
#### Adopted Style Sheets
|
164
|
+
|
165
|
+
This one diverges from native slightly, since the native approach is a bit manual. For convenience, you can use the `adoptStyleSheet()` function.
|
166
|
+
|
167
|
+
> If you prefer the manual approach, `root.adoptedStyleSheets = []`, you can always do that with the `root` property listed above.
|
168
|
+
|
169
|
+
The `css` tagged template function will construct a `CSSStyleSheet` object that can be adopted by documents and shadow roots.
|
170
|
+
|
171
|
+
<!-- prettier-ignore-start -->
|
172
|
+
```ts
|
173
|
+
import { customElement, css } from 'thunderous';
|
174
|
+
|
175
|
+
const myStyleSheet = css`
|
176
|
+
:host {
|
177
|
+
display: block;
|
178
|
+
font-family: sans-serif;
|
179
|
+
}
|
180
|
+
`;
|
181
|
+
|
182
|
+
const MyElement = customElement((params) => {
|
183
|
+
const { adoptStyleSheet } = params;
|
184
|
+
|
185
|
+
adoptStyleSheet(myStyleSheet);
|
186
|
+
|
187
|
+
/* ... */
|
188
|
+
});
|
189
|
+
```
|
190
|
+
<!-- prettier-ignore-end -->
|
191
|
+
|
192
|
+
### Non-Native extras
|
193
|
+
|
194
|
+
In addition to the native features, there are a few features that supercharge your web components. Most notably, signals.
|
195
|
+
|
196
|
+
#### Signals
|
197
|
+
|
198
|
+
Creating signals should look pretty familiar to most modern developers.
|
199
|
+
|
200
|
+
<!-- prettier-ignore-start -->
|
201
|
+
```ts
|
202
|
+
import { createSignal, customElement } from 'thunderous';
|
203
|
+
|
204
|
+
const MyElement = customElement(() => {
|
205
|
+
const [count, setCount] = createSignal(0);
|
206
|
+
|
207
|
+
console.log(count()); // 0
|
208
|
+
|
209
|
+
setCount(1);
|
210
|
+
|
211
|
+
console.log(count()) // 1
|
212
|
+
|
213
|
+
/* ... */
|
214
|
+
});
|
215
|
+
```
|
216
|
+
<!-- prettier-ignore-end -->
|
217
|
+
|
218
|
+
##### Binding Signals to Templates
|
219
|
+
|
220
|
+
To bind signals to a template, use the provided `html` tagged template function to pass them in.
|
221
|
+
|
222
|
+
<!-- prettier-ignore-start -->
|
223
|
+
```ts
|
224
|
+
import { createSignal, customElement, html } from 'thunderous';
|
225
|
+
|
226
|
+
const MyElement = customElement(() => {
|
227
|
+
const [count, setCount] = createSignal(0);
|
228
|
+
|
229
|
+
// presumably setCount() gets called
|
230
|
+
|
231
|
+
return html`<output>${count}</output>`;
|
232
|
+
});
|
233
|
+
```
|
234
|
+
<!-- prettier-ignore-end -->
|
235
|
+
|
236
|
+
> NOTICE: we are not running the signal's getter above. This is intentional, as we delegate that to the template to run for proper binding.
|
237
|
+
|
238
|
+
By binding signals to templates, you allow fine-grained updates to be made directly to DOM nodes every time the signal changes, without requiring any diffing or re-rendering.
|
239
|
+
|
240
|
+
> This also works for `css`, but bear in mind that passing signals to a shared `CSSStyleSheet` may not have the effect you expect. Sharing Style Sheets across many component instances is best for performance, but signals will update every instance of each component with that approach. The suggested alternative is to write static CSS and toggle classes in the HTML instead.
|
241
|
+
|
242
|
+
##### Attribute Signals
|
243
|
+
|
244
|
+
Instead of worrying about the manual `observedAttributes` approach, each element is observed with a `MutationObserver` watching all attributes. All changes trigger the `attributeChangedCallback` and you can access all attributes as signals. This makes it much less cumbersome to write reactive attributes.
|
245
|
+
|
246
|
+
<!-- prettier-ignore-start -->
|
247
|
+
```ts
|
248
|
+
const MyElement = customElement((params) => {
|
249
|
+
const { attrSignals } = params;
|
250
|
+
|
251
|
+
const [heading, setHeading] = attrSignals['my-heading'];
|
252
|
+
|
253
|
+
// setHeading() will also update the attribute in the DOM.
|
254
|
+
|
255
|
+
return html`<h2>${heading}</h2>`;
|
256
|
+
});
|
257
|
+
```
|
258
|
+
<!-- prettier-ignore-end -->
|
259
|
+
|
260
|
+
Usage:
|
261
|
+
|
262
|
+
```html
|
263
|
+
<my-element my-heading="My Element's Title"></my-element>
|
264
|
+
```
|
265
|
+
|
266
|
+
> NOTICE: Since `attrSignals` is a `Proxy` object, _any_ property will return a signal and auto-bind it to the attribute it corresponds with.
|
267
|
+
|
268
|
+
#### Refs
|
269
|
+
|
270
|
+
Finally, the refs property exists for convenience to avoid manually querying the DOM. Since the DOM is only available after rendering, refs will only work in and after the `connectedCallback` method. This is the best place for event binding to occur.
|
271
|
+
|
272
|
+
<!-- prettier-ignore-start -->
|
273
|
+
```ts
|
274
|
+
const MyElement = customElement((params) => {
|
275
|
+
const { connectedCallback, refs } = params;
|
276
|
+
|
277
|
+
const [count, setCount] = createSignal(0);
|
278
|
+
|
279
|
+
connectedCallback(() => {
|
280
|
+
refs.increment.addEventListener('click', () => {
|
281
|
+
setCount(count() + 1);
|
282
|
+
});
|
283
|
+
});
|
284
|
+
|
285
|
+
return html`
|
286
|
+
<button ref="increment">Increment</button>
|
287
|
+
<output>${count}</output>
|
288
|
+
`;
|
289
|
+
});
|
290
|
+
|
291
|
+
MyElement.define('my-element');
|
292
|
+
```
|
293
|
+
<!-- prettier-ignore-end -->
|
294
|
+
|
295
|
+
## Contributing
|
296
|
+
|
297
|
+
### Local Server
|
298
|
+
|
299
|
+
To see it in action, start the demo server with:
|
300
|
+
|
301
|
+
```sh
|
302
|
+
npm run demo
|
303
|
+
```
|
304
|
+
|
305
|
+
The demo's `package.json` points to the parent directory with the `file:` prefix. To preview the updated library code, you must run `npm run build` at the top level.
|
306
|
+
|
307
|
+
Please open a corresponding issue for any pull request you'd like to raise.
|
308
|
+
|
309
|
+
## License
|
310
|
+
|
311
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "thunderous",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.3",
|
4
4
|
"description": "",
|
5
5
|
"type": "module",
|
6
6
|
"main": "./dist/index.cjs",
|
@@ -12,12 +12,27 @@
|
|
12
12
|
"/README.md",
|
13
13
|
"/LICENSE"
|
14
14
|
],
|
15
|
+
"author": "Jonathan DeWitt",
|
16
|
+
"repository": {
|
17
|
+
"type": "git",
|
18
|
+
"url": "https://github.com/Thunder-Solutions/Thunderous"
|
19
|
+
},
|
20
|
+
"keywords": [
|
21
|
+
"thunderous",
|
22
|
+
"web components",
|
23
|
+
"functional",
|
24
|
+
"signals",
|
25
|
+
"custom elements"
|
26
|
+
],
|
27
|
+
"bugs": {
|
28
|
+
"url": "https://github.com/Thunder-Solutions/Thunderous/issues"
|
29
|
+
},
|
30
|
+
"homepage": "https://github.com/Thunder-Solutions/Thunderous#readme",
|
31
|
+
"license": "MIT",
|
15
32
|
"scripts": {
|
16
33
|
"demo": "cd demo && rm -rf node_modules && npm i && npm start",
|
17
34
|
"build": "tsup src/index.ts --format cjs,esm --dts --no-clean"
|
18
35
|
},
|
19
|
-
"author": "",
|
20
|
-
"license": "MIT",
|
21
36
|
"devDependencies": {
|
22
37
|
"@types/eslint": "^8.56.10",
|
23
38
|
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
package/readme.md
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
# Thunderous
|
2
|
-
|
3
|
-
Thunderous is a library for writing web components in a functional style, reducing the boilerplate, while signals make it better for managing and sharing state.
|
4
|
-
|
5
|
-
Each component renders only once, then binds signals to DOM nodes for direct updates with _thunderous_ efficiency.
|
6
|
-
|
7
|
-
## Table of Contents
|
8
|
-
|
9
|
-
- [Installation](#installation)
|
10
|
-
- [Usage](#usage)
|
11
|
-
- [Development](#development)
|
12
|
-
- [License](#license)
|
13
|
-
|
14
|
-
## Installation
|
15
|
-
|
16
|
-
Install Thunderous via npm:
|
17
|
-
|
18
|
-
```sh
|
19
|
-
npm install thunderous
|
20
|
-
```
|
21
|
-
|
22
|
-
## Usage
|
23
|
-
|
24
|
-
Below is a basic usage of the functions available.
|
25
|
-
|
26
|
-
```ts
|
27
|
-
import { customElement, html, css, createSignal } from 'thunderous';
|
28
|
-
|
29
|
-
const MyElement = customElement(({ attrSignals, connectedCallback, refs, adoptStyleSheet }) => {
|
30
|
-
const [heading] = attrSignals.heading;
|
31
|
-
const [count, setCount] = createSignal(0);
|
32
|
-
connectedCallback(() => {
|
33
|
-
refs.increment.addEventListener('click', () => {
|
34
|
-
setCount(count() + 1);
|
35
|
-
});
|
36
|
-
});
|
37
|
-
adoptStyleSheet(css`
|
38
|
-
:host {
|
39
|
-
display: block;
|
40
|
-
font-family: sans-serif;
|
41
|
-
}
|
42
|
-
`);
|
43
|
-
return html`
|
44
|
-
<h2>${heading}</h2>
|
45
|
-
<button ref="increment">Increment</button>
|
46
|
-
<output>${count}</output>
|
47
|
-
`;
|
48
|
-
});
|
49
|
-
|
50
|
-
MyElement.define('my-element');
|
51
|
-
```
|
52
|
-
|
53
|
-
If you should need to access the class directly for some reason, you can use the `eject` method.
|
54
|
-
|
55
|
-
```ts
|
56
|
-
const MyElementClass = MyElement.eject();
|
57
|
-
```
|
58
|
-
|
59
|
-
[more examples to be updated...]
|
60
|
-
|
61
|
-
## Development
|
62
|
-
|
63
|
-
### Local Server
|
64
|
-
|
65
|
-
To see it in action, start the demo server with:
|
66
|
-
|
67
|
-
```sh
|
68
|
-
npm run demo
|
69
|
-
```
|
70
|
-
|
71
|
-
## License
|
72
|
-
|
73
|
-
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|