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 CHANGED
@@ -35,20 +35,16 @@ const myStyleSheet = css`
35
35
  `;
36
36
 
37
37
  const MyElement = customElement((params) => {
38
- const { connectedCallback, refs, adoptStyleSheet } = params;
38
+ const { customCallback, refs, adoptStyleSheet } = params;
39
39
 
40
40
  const [count, setCount] = createSignal(0);
41
41
 
42
- connectedCallback(() => {
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 ref="increment">Increment</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, customElement } from 'thunderous';
203
-
204
- const MyElement = customElement(() => {
205
- const [count, setCount] = createSignal(0);
156
+ import { createSignal } from 'thunderous';
206
157
 
207
- console.log(count()); // 0
158
+ const [count, setCount] = createSignal(0);
208
159
 
209
- setCount(1);
160
+ console.log(count()); // 0
210
161
 
211
- console.log(count()) // 1
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
- 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.
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.increment.addEventListener('click', () => {
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 ref="increment">Increment</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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thunderous",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",