thunderous 0.0.3 → 0.1.0
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 +89 -69
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
@@ -35,20 +35,16 @@ const myStyleSheet = css`
|
|
35
35
|
`;
|
36
36
|
|
37
37
|
const MyElement = customElement((params) => {
|
38
|
-
const {
|
38
|
+
const { customCallback, refs, adoptStyleSheet } = params;
|
39
39
|
|
40
40
|
const [count, setCount] = createSignal(0);
|
41
41
|
|
42
|
-
|
43
|
-
refs.increment.addEventListener('click', () => {
|
44
|
-
setCount(count() + 1);
|
45
|
-
});
|
46
|
-
});
|
42
|
+
const increment = customCallback(() => setCount(count() + 1));
|
47
43
|
|
48
44
|
adoptStyleSheet(myStyleSheet);
|
49
45
|
|
50
46
|
return html`
|
51
|
-
<button
|
47
|
+
<button onclick="${increment}">Increment</button>
|
52
48
|
<output>${count}</output>
|
53
49
|
`;
|
54
50
|
});
|
@@ -59,35 +55,11 @@ MyElement.define('my-element');
|
|
59
55
|
|
60
56
|
### The Native Features
|
61
57
|
|
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()`.
|
58
|
+
Everything the native class definition can do, this function can do too. You'll find that these things are not far removed from the native approach, so they ought to be familiar.
|
87
59
|
|
88
60
|
#### Lifecycle Methods
|
89
61
|
|
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.
|
62
|
+
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 instead.
|
91
63
|
|
92
64
|
<!-- prettier-ignore-start -->
|
93
65
|
```ts
|
@@ -120,24 +92,6 @@ const MyElement = customElement((params) => {
|
|
120
92
|
```
|
121
93
|
<!-- prettier-ignore-end -->
|
122
94
|
|
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
95
|
#### Roots and Element Internals
|
142
96
|
|
143
97
|
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.
|
@@ -199,19 +153,15 @@ Creating signals should look pretty familiar to most modern developers.
|
|
199
153
|
|
200
154
|
<!-- prettier-ignore-start -->
|
201
155
|
```ts
|
202
|
-
import { createSignal
|
203
|
-
|
204
|
-
const MyElement = customElement(() => {
|
205
|
-
const [count, setCount] = createSignal(0);
|
156
|
+
import { createSignal } from 'thunderous';
|
206
157
|
|
207
|
-
|
158
|
+
const [count, setCount] = createSignal(0);
|
208
159
|
|
209
|
-
|
160
|
+
console.log(count()); // 0
|
210
161
|
|
211
|
-
|
162
|
+
setCount(1);
|
212
163
|
|
213
|
-
|
214
|
-
});
|
164
|
+
console.log(count()) // 1
|
215
165
|
```
|
216
166
|
<!-- prettier-ignore-end -->
|
217
167
|
|
@@ -265,33 +215,103 @@ Usage:
|
|
265
215
|
|
266
216
|
> NOTICE: Since `attrSignals` is a `Proxy` object, _any_ property will return a signal and auto-bind it to the attribute it corresponds with.
|
267
217
|
|
218
|
+
##### Derived Signals
|
219
|
+
|
220
|
+
If you want to calculate a value based on another signal's value, you should use the `derived()` function. This signal will trigger its subscribers each time the signals inside change.
|
221
|
+
|
222
|
+
```ts
|
223
|
+
import { derived, createSignal } from 'thunderous';
|
224
|
+
|
225
|
+
const [count, setCount] = createSignal(0);
|
226
|
+
|
227
|
+
const timesTen = derived(() => count() * 10);
|
228
|
+
|
229
|
+
console.log(timesTen()); // 0
|
230
|
+
|
231
|
+
setCount(10);
|
232
|
+
|
233
|
+
console.log(timesTen()); // 100
|
234
|
+
```
|
235
|
+
|
236
|
+
##### Effects
|
237
|
+
|
238
|
+
To run a callback each time a signal is changed, use the `createEffect()` function. Any signal used inside will trigger the callback when they're changed.
|
239
|
+
|
240
|
+
```ts
|
241
|
+
import { createEffect } from 'thunderous';
|
242
|
+
|
243
|
+
/* ... */
|
244
|
+
|
245
|
+
createEffect(() => {
|
246
|
+
console.log(count());
|
247
|
+
});
|
248
|
+
```
|
249
|
+
|
268
250
|
#### Refs
|
269
251
|
|
270
|
-
|
252
|
+
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.
|
271
253
|
|
272
254
|
<!-- prettier-ignore-start -->
|
273
255
|
```ts
|
274
256
|
const MyElement = customElement((params) => {
|
275
257
|
const { connectedCallback, refs } = params;
|
276
258
|
|
277
|
-
const [count, setCount] = createSignal(0);
|
278
|
-
|
279
259
|
connectedCallback(() => {
|
280
|
-
refs.
|
281
|
-
setCount(count() + 1);
|
282
|
-
});
|
260
|
+
console.log(refs.heading.textContent); // hello world
|
283
261
|
});
|
284
262
|
|
263
|
+
return html`<h2 ref="heading">hello world</h2>`;
|
264
|
+
});
|
265
|
+
```
|
266
|
+
<!-- prettier-ignore-end -->
|
267
|
+
|
268
|
+
#### Event Binding
|
269
|
+
|
270
|
+
While you could bind events in the `connectedCallback()` with `refs.button.addEventListener('click', handleClick)` for example, it may be more convenient to register a custom callback and bind it to the template.
|
271
|
+
|
272
|
+
<!-- prettier-ignore-start -->
|
273
|
+
```ts
|
274
|
+
const MyElement = customElement((params) => {
|
275
|
+
const { customCallback } = params;
|
276
|
+
|
277
|
+
const [count, setCount] = createSignal(0);
|
278
|
+
|
279
|
+
const increment = customCallback(() => setCount(count() + 1));
|
280
|
+
|
285
281
|
return html`
|
286
|
-
<button
|
282
|
+
<button onclick="${increment}">Increment</button>
|
287
283
|
<output>${count}</output>
|
288
284
|
`;
|
289
285
|
});
|
290
|
-
|
291
|
-
MyElement.define('my-element');
|
292
286
|
```
|
293
287
|
<!-- prettier-ignore-end -->
|
294
288
|
|
289
|
+
> NOTICE: This uses the native HTML inline event-binding syntax. There is no special syntax for `on` attributes, because it simply renders a reference to `this.getRootNode().host` and extracts the callback from there.
|
290
|
+
|
291
|
+
### Defining Custom Elements
|
292
|
+
|
293
|
+
The `customElement()` function allows you to author a web component, returning an `ElementResult` that has some helpful methods like `define()` and `eject()`.
|
294
|
+
|
295
|
+
- `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.
|
296
|
+
|
297
|
+
```ts
|
298
|
+
const MyElement = customElement(() => html`<slot></slot>`);
|
299
|
+
|
300
|
+
MyElement.define('my-element');
|
301
|
+
```
|
302
|
+
|
303
|
+
- `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.
|
304
|
+
|
305
|
+
```ts
|
306
|
+
const MyElementClass = MyElement.eject();
|
307
|
+
|
308
|
+
class MyOtherElement extends MyElementClass {
|
309
|
+
/* ... */
|
310
|
+
}
|
311
|
+
```
|
312
|
+
|
313
|
+
These may also be chained together, like `MyElement.define('my-element').eject()`.
|
314
|
+
|
295
315
|
## Contributing
|
296
316
|
|
297
317
|
### Local Server
|
package/dist/index.cjs
CHANGED
@@ -93,6 +93,7 @@ var customElement = (render, options) => {
|
|
93
93
|
#formDisabledCallbackFns = /* @__PURE__ */ new Set();
|
94
94
|
#formResetCallbackFns = /* @__PURE__ */ new Set();
|
95
95
|
#formStateRestoreCallbackFns = /* @__PURE__ */ new Set();
|
96
|
+
__customCallbackFns = /* @__PURE__ */ new Map();
|
96
97
|
#shadowRoot = this.attachShadow({ mode: "closed" });
|
97
98
|
#internals = this.attachInternals();
|
98
99
|
#observer = new MutationObserver((mutations) => {
|
@@ -121,6 +122,11 @@ var customElement = (render, options) => {
|
|
121
122
|
formDisabledCallback: (fn) => this.#formDisabledCallbackFns.add(fn),
|
122
123
|
formResetCallback: (fn) => this.#formResetCallbackFns.add(fn),
|
123
124
|
formStateRestoreCallback: (fn) => this.#formStateRestoreCallbackFns.add(fn),
|
125
|
+
customCallback: (fn) => {
|
126
|
+
const key = crypto.randomUUID();
|
127
|
+
this.__customCallbackFns.set(key, fn);
|
128
|
+
return `{{callback:${key}}}`;
|
129
|
+
},
|
124
130
|
attrSignals: new Proxy(
|
125
131
|
{},
|
126
132
|
{
|
@@ -245,6 +251,7 @@ var html = (strings, ...values) => {
|
|
245
251
|
innerHTML += string + String(value);
|
246
252
|
});
|
247
253
|
const fragment = parseFragment(innerHTML);
|
254
|
+
const callbackBindingRegex = /(\{\{callback:.+\}\})/;
|
248
255
|
const signalBindingRegex = /(\{\{signal:.+\}\})/;
|
249
256
|
const parseChildren = (element) => {
|
250
257
|
for (const child of element.childNodes) {
|
@@ -280,6 +287,9 @@ var html = (strings, ...values) => {
|
|
280
287
|
}
|
281
288
|
child.setAttribute(attr.name, newText);
|
282
289
|
});
|
290
|
+
} else if (callbackBindingRegex.test(attr.value)) {
|
291
|
+
const uniqueKey = attr.value.replace(/\{\{callback:(.+)\}\}/, "$1");
|
292
|
+
child.setAttribute(attr.name, `this.getRootNode().host.__customCallbackFns.get('${uniqueKey}')(event)`);
|
283
293
|
}
|
284
294
|
}
|
285
295
|
parseChildren(child);
|
package/dist/index.d.cts
CHANGED
@@ -22,6 +22,7 @@ type RenderProps = {
|
|
22
22
|
formDisabledCallback: (fn: () => void) => void;
|
23
23
|
formResetCallback: (fn: () => void) => void;
|
24
24
|
formStateRestoreCallback: (fn: () => void) => void;
|
25
|
+
customCallback: (fn: () => void) => `{{callback:${string}}}`;
|
25
26
|
attrSignals: Record<string, Signal<string | null>>;
|
26
27
|
refs: Record<string, HTMLElement | null>;
|
27
28
|
adoptStyleSheet: (stylesheet: CSSStyleSheet) => void;
|
package/dist/index.d.ts
CHANGED
@@ -22,6 +22,7 @@ type RenderProps = {
|
|
22
22
|
formDisabledCallback: (fn: () => void) => void;
|
23
23
|
formResetCallback: (fn: () => void) => void;
|
24
24
|
formStateRestoreCallback: (fn: () => void) => void;
|
25
|
+
customCallback: (fn: () => void) => `{{callback:${string}}}`;
|
25
26
|
attrSignals: Record<string, Signal<string | null>>;
|
26
27
|
refs: Record<string, HTMLElement | null>;
|
27
28
|
adoptStyleSheet: (stylesheet: CSSStyleSheet) => void;
|
package/dist/index.js
CHANGED
@@ -62,6 +62,7 @@ var customElement = (render, options) => {
|
|
62
62
|
#formDisabledCallbackFns = /* @__PURE__ */ new Set();
|
63
63
|
#formResetCallbackFns = /* @__PURE__ */ new Set();
|
64
64
|
#formStateRestoreCallbackFns = /* @__PURE__ */ new Set();
|
65
|
+
__customCallbackFns = /* @__PURE__ */ new Map();
|
65
66
|
#shadowRoot = this.attachShadow({ mode: "closed" });
|
66
67
|
#internals = this.attachInternals();
|
67
68
|
#observer = new MutationObserver((mutations) => {
|
@@ -90,6 +91,11 @@ var customElement = (render, options) => {
|
|
90
91
|
formDisabledCallback: (fn) => this.#formDisabledCallbackFns.add(fn),
|
91
92
|
formResetCallback: (fn) => this.#formResetCallbackFns.add(fn),
|
92
93
|
formStateRestoreCallback: (fn) => this.#formStateRestoreCallbackFns.add(fn),
|
94
|
+
customCallback: (fn) => {
|
95
|
+
const key = crypto.randomUUID();
|
96
|
+
this.__customCallbackFns.set(key, fn);
|
97
|
+
return `{{callback:${key}}}`;
|
98
|
+
},
|
93
99
|
attrSignals: new Proxy(
|
94
100
|
{},
|
95
101
|
{
|
@@ -214,6 +220,7 @@ var html = (strings, ...values) => {
|
|
214
220
|
innerHTML += string + String(value);
|
215
221
|
});
|
216
222
|
const fragment = parseFragment(innerHTML);
|
223
|
+
const callbackBindingRegex = /(\{\{callback:.+\}\})/;
|
217
224
|
const signalBindingRegex = /(\{\{signal:.+\}\})/;
|
218
225
|
const parseChildren = (element) => {
|
219
226
|
for (const child of element.childNodes) {
|
@@ -249,6 +256,9 @@ var html = (strings, ...values) => {
|
|
249
256
|
}
|
250
257
|
child.setAttribute(attr.name, newText);
|
251
258
|
});
|
259
|
+
} else if (callbackBindingRegex.test(attr.value)) {
|
260
|
+
const uniqueKey = attr.value.replace(/\{\{callback:(.+)\}\}/, "$1");
|
261
|
+
child.setAttribute(attr.name, `this.getRootNode().host.__customCallbackFns.get('${uniqueKey}')(event)`);
|
252
262
|
}
|
253
263
|
}
|
254
264
|
parseChildren(child);
|