thunderous 0.0.2 → 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.
Files changed (3) hide show
  1. package/README.md +311 -0
  2. package/package.json +18 -3
  3. package/readme.md +0 -75
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.2",
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,75 +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 `customElement` function.
25
-
26
- ```ts
27
- import { customElement, html, css, createSignal } from 'thunderous';
28
-
29
- const MyElement = customElement(
30
- ({ attrSignals, connectedCallback, refs, adoptStyleSheet }) => {
31
- const [heading] = attrSignals.heading;
32
- const [count, setCount] = createSignal(0);
33
- connectedCallback(() => {
34
- refs.increment.addEventListener('click', () => {
35
- setCount(count() + 1);
36
- });
37
- });
38
- adoptStyleSheet(css`
39
- :host {
40
- display: block;
41
- font-family: sans-serif;
42
- }
43
- `);
44
- return html`
45
- <h2>${heading}</h2>
46
- <button ref="increment">Increment</button>
47
- <output>${count}</output>
48
- `;
49
- },
50
- );
51
-
52
- MyElement.define('my-element');
53
- ```
54
-
55
- If you should need to access the class directly for some reason, you can use the `eject` method.
56
-
57
- ```ts
58
- const MyElementClass = MyElement.eject();
59
- ```
60
-
61
- [more examples to be updated...]
62
-
63
- ## Development
64
-
65
- ### Local Server
66
-
67
- To see it in action, start the demo server with:
68
-
69
- ```sh
70
- npm run demo
71
- ```
72
-
73
- ## License
74
-
75
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.