thunderous 0.1.0 → 0.2.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 +18 -45
- package/dist/index.cjs +20 -5
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +20 -5
- package/package.json +1 -1
package/README.md
CHANGED
@@ -34,15 +34,10 @@ const myStyleSheet = css`
|
|
34
34
|
}
|
35
35
|
`;
|
36
36
|
|
37
|
-
const MyElement = customElement((
|
38
|
-
const { customCallback, refs, adoptStyleSheet } = params;
|
39
|
-
|
37
|
+
const MyElement = customElement(({ customCallback, refs, adoptStyleSheet }) => {
|
40
38
|
const [count, setCount] = createSignal(0);
|
41
|
-
|
42
39
|
const increment = customCallback(() => setCount(count() + 1));
|
43
|
-
|
44
40
|
adoptStyleSheet(myStyleSheet);
|
45
|
-
|
46
41
|
return html`
|
47
42
|
<button onclick="${increment}">Increment</button>
|
48
43
|
<output>${count}</output>
|
@@ -70,7 +65,6 @@ const MyElement = customElement((params) => {
|
|
70
65
|
disconnectedCallback,
|
71
66
|
attributeChangedCallback,
|
72
67
|
} = params;
|
73
|
-
|
74
68
|
/* ... */
|
75
69
|
});
|
76
70
|
```
|
@@ -86,7 +80,6 @@ const MyElement = customElement((params) => {
|
|
86
80
|
formResetCallback,
|
87
81
|
formStateRestoreCallback,
|
88
82
|
} = params;
|
89
|
-
|
90
83
|
/* ... */
|
91
84
|
}, { formAssociated: true });
|
92
85
|
```
|
@@ -98,17 +91,10 @@ You can always define the internals the same as you usually would, and if for so
|
|
98
91
|
|
99
92
|
<!-- prettier-ignore-start -->
|
100
93
|
```ts
|
101
|
-
const MyElement = customElement((
|
102
|
-
const {
|
103
|
-
internals,
|
104
|
-
elementRef,
|
105
|
-
root,
|
106
|
-
} = params;
|
107
|
-
|
94
|
+
const MyElement = customElement(({ internals, elementRef, root }) => {
|
108
95
|
internals.ariaRequired = 'true';
|
109
96
|
const childLink = elementRef.querySelector('a[href]'); // light DOM
|
110
97
|
const innerLink = root.querySelector('a[href]'); // shadow DOM
|
111
|
-
|
112
98
|
/* ... */
|
113
99
|
}, { formAssociated: true });
|
114
100
|
```
|
@@ -133,11 +119,8 @@ const myStyleSheet = css`
|
|
133
119
|
}
|
134
120
|
`;
|
135
121
|
|
136
|
-
const MyElement = customElement((
|
137
|
-
const { adoptStyleSheet } = params;
|
138
|
-
|
122
|
+
const MyElement = customElement(({ adoptStyleSheet }) => {
|
139
123
|
adoptStyleSheet(myStyleSheet);
|
140
|
-
|
141
124
|
/* ... */
|
142
125
|
});
|
143
126
|
```
|
@@ -156,11 +139,8 @@ Creating signals should look pretty familiar to most modern developers.
|
|
156
139
|
import { createSignal } from 'thunderous';
|
157
140
|
|
158
141
|
const [count, setCount] = createSignal(0);
|
159
|
-
|
160
142
|
console.log(count()); // 0
|
161
|
-
|
162
143
|
setCount(1);
|
163
|
-
|
164
144
|
console.log(count()) // 1
|
165
145
|
```
|
166
146
|
<!-- prettier-ignore-end -->
|
@@ -175,9 +155,7 @@ import { createSignal, customElement, html } from 'thunderous';
|
|
175
155
|
|
176
156
|
const MyElement = customElement(() => {
|
177
157
|
const [count, setCount] = createSignal(0);
|
178
|
-
|
179
158
|
// presumably setCount() gets called
|
180
|
-
|
181
159
|
return html`<output>${count}</output>`;
|
182
160
|
});
|
183
161
|
```
|
@@ -191,22 +169,29 @@ By binding signals to templates, you allow fine-grained updates to be made direc
|
|
191
169
|
|
192
170
|
##### Attribute Signals
|
193
171
|
|
194
|
-
|
172
|
+
By default, each element is observed with a `MutationObserver` watching all attributes. Changes to _any_ attribute trigger the `attributeChangedCallback` and you can access all attributes as signals. This makes it much less cumbersome to write reactive attributes.
|
195
173
|
|
196
174
|
<!-- prettier-ignore-start -->
|
197
175
|
```ts
|
198
|
-
const MyElement = customElement((
|
199
|
-
const { attrSignals } = params;
|
200
|
-
|
176
|
+
const MyElement = customElement(({ attrSignals }) => {
|
201
177
|
const [heading, setHeading] = attrSignals['my-heading'];
|
202
|
-
|
203
178
|
// setHeading() will also update the attribute in the DOM.
|
204
|
-
|
205
179
|
return html`<h2>${heading}</h2>`;
|
206
180
|
});
|
207
181
|
```
|
208
182
|
<!-- prettier-ignore-end -->
|
209
183
|
|
184
|
+
However, the `MutationObserver` does impose a small performance tradeoff that may add up if you render a lot of elements. To better optimize for performance, you can pass `observedAttributes` to the options. Doing so will disable the `MutationObserver`, and only the observed attributes will trigger the `attributeChangedCallback`.
|
185
|
+
|
186
|
+
<!-- prettier-ignore-start -->
|
187
|
+
```ts
|
188
|
+
const MyElement = customElement(({ attrSignals }) => {
|
189
|
+
const [heading, setHeading] = attrSignals['my-heading'];
|
190
|
+
return html`<h2>${heading}</h2>`;
|
191
|
+
}, { observedAttributes: ['my-heading'] });
|
192
|
+
```
|
193
|
+
<!-- prettier-ignore-end -->
|
194
|
+
|
210
195
|
Usage:
|
211
196
|
|
212
197
|
```html
|
@@ -223,13 +208,9 @@ If you want to calculate a value based on another signal's value, you should use
|
|
223
208
|
import { derived, createSignal } from 'thunderous';
|
224
209
|
|
225
210
|
const [count, setCount] = createSignal(0);
|
226
|
-
|
227
211
|
const timesTen = derived(() => count() * 10);
|
228
|
-
|
229
212
|
console.log(timesTen()); // 0
|
230
|
-
|
231
213
|
setCount(10);
|
232
|
-
|
233
214
|
console.log(timesTen()); // 100
|
234
215
|
```
|
235
216
|
|
@@ -241,7 +222,6 @@ To run a callback each time a signal is changed, use the `createEffect()` functi
|
|
241
222
|
import { createEffect } from 'thunderous';
|
242
223
|
|
243
224
|
/* ... */
|
244
|
-
|
245
225
|
createEffect(() => {
|
246
226
|
console.log(count());
|
247
227
|
});
|
@@ -253,13 +233,10 @@ The refs property exists for convenience to avoid manually querying the DOM. Sin
|
|
253
233
|
|
254
234
|
<!-- prettier-ignore-start -->
|
255
235
|
```ts
|
256
|
-
const MyElement = customElement((
|
257
|
-
const { connectedCallback, refs } = params;
|
258
|
-
|
236
|
+
const MyElement = customElement(({ connectedCallback, refs }) => {
|
259
237
|
connectedCallback(() => {
|
260
238
|
console.log(refs.heading.textContent); // hello world
|
261
239
|
});
|
262
|
-
|
263
240
|
return html`<h2 ref="heading">hello world</h2>`;
|
264
241
|
});
|
265
242
|
```
|
@@ -271,13 +248,9 @@ While you could bind events in the `connectedCallback()` with `refs.button.addEv
|
|
271
248
|
|
272
249
|
<!-- prettier-ignore-start -->
|
273
250
|
```ts
|
274
|
-
const MyElement = customElement((
|
275
|
-
const { customCallback } = params;
|
276
|
-
|
251
|
+
const MyElement = customElement(({ customCallback }) => {
|
277
252
|
const [count, setCount] = createSignal(0);
|
278
|
-
|
279
253
|
const increment = customCallback(() => setCount(count() + 1));
|
280
|
-
|
281
254
|
return html`
|
282
255
|
<button onclick="${increment}">Increment</button>
|
283
256
|
<output>${count}</output>
|
package/dist/index.cjs
CHANGED
@@ -80,10 +80,11 @@ var createEffect = (fn) => {
|
|
80
80
|
|
81
81
|
// src/custom-element.ts
|
82
82
|
var DEFAULT_RENDER_OPTIONS = {
|
83
|
-
formAssociated: false
|
83
|
+
formAssociated: false,
|
84
|
+
observedAttributes: []
|
84
85
|
};
|
85
86
|
var customElement = (render, options) => {
|
86
|
-
const { formAssociated } = { ...DEFAULT_RENDER_OPTIONS, ...options };
|
87
|
+
const { formAssociated, observedAttributes } = { ...DEFAULT_RENDER_OPTIONS, ...options };
|
87
88
|
class CustomElement extends HTMLElement {
|
88
89
|
#attrSignals = {};
|
89
90
|
#attributeChangedFns = /* @__PURE__ */ new Set();
|
@@ -96,7 +97,7 @@ var customElement = (render, options) => {
|
|
96
97
|
__customCallbackFns = /* @__PURE__ */ new Map();
|
97
98
|
#shadowRoot = this.attachShadow({ mode: "closed" });
|
98
99
|
#internals = this.attachInternals();
|
99
|
-
#observer = new MutationObserver((mutations) => {
|
100
|
+
#observer = observedAttributes.length > 0 ? null : new MutationObserver((mutations) => {
|
100
101
|
for (const mutation of mutations) {
|
101
102
|
const attrName = mutation.attributeName;
|
102
103
|
if (mutation.type !== "attributes" || attrName === null) continue;
|
@@ -161,6 +162,9 @@ var customElement = (render, options) => {
|
|
161
162
|
static get formAssociated() {
|
162
163
|
return formAssociated;
|
163
164
|
}
|
165
|
+
static get observedAttributes() {
|
166
|
+
return observedAttributes;
|
167
|
+
}
|
164
168
|
constructor() {
|
165
169
|
super();
|
166
170
|
for (const attr of this.attributes) {
|
@@ -169,17 +173,28 @@ var customElement = (render, options) => {
|
|
169
173
|
this.#render();
|
170
174
|
}
|
171
175
|
connectedCallback() {
|
172
|
-
this.#observer
|
176
|
+
if (this.#observer !== null) {
|
177
|
+
this.#observer.observe(this, { attributes: true });
|
178
|
+
}
|
173
179
|
for (const fn of this.#connectedFns) {
|
174
180
|
fn();
|
175
181
|
}
|
176
182
|
}
|
177
183
|
disconnectedCallback() {
|
178
|
-
this.#observer
|
184
|
+
if (this.#observer !== null) {
|
185
|
+
this.#observer.disconnect();
|
186
|
+
}
|
179
187
|
for (const fn of this.#disconnectedFns) {
|
180
188
|
fn();
|
181
189
|
}
|
182
190
|
}
|
191
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
192
|
+
const [, setValue] = this.#attrSignals[name];
|
193
|
+
setValue(newValue);
|
194
|
+
for (const fn of this.#attributeChangedFns) {
|
195
|
+
fn(name, oldValue, newValue);
|
196
|
+
}
|
197
|
+
}
|
183
198
|
adoptedCallback() {
|
184
199
|
for (const fn of this.#adoptedCallbackFns) {
|
185
200
|
fn();
|
package/dist/index.d.cts
CHANGED
@@ -28,10 +28,11 @@ type RenderProps = {
|
|
28
28
|
adoptStyleSheet: (stylesheet: CSSStyleSheet) => void;
|
29
29
|
};
|
30
30
|
type RenderOptions = {
|
31
|
-
formAssociated
|
31
|
+
formAssociated: boolean;
|
32
|
+
observedAttributes: string[];
|
32
33
|
};
|
33
34
|
type RenderFunction = (props: RenderProps) => DocumentFragment;
|
34
|
-
declare const customElement: (render: RenderFunction, options?: RenderOptions) => ElementResult;
|
35
|
+
declare const customElement: (render: RenderFunction, options?: Partial<RenderOptions>) => ElementResult;
|
35
36
|
type Registry = {
|
36
37
|
register: (tagName: string, element: CustomElementConstructor) => void;
|
37
38
|
getTagName: (element: CustomElementConstructor) => string | undefined;
|
package/dist/index.d.ts
CHANGED
@@ -28,10 +28,11 @@ type RenderProps = {
|
|
28
28
|
adoptStyleSheet: (stylesheet: CSSStyleSheet) => void;
|
29
29
|
};
|
30
30
|
type RenderOptions = {
|
31
|
-
formAssociated
|
31
|
+
formAssociated: boolean;
|
32
|
+
observedAttributes: string[];
|
32
33
|
};
|
33
34
|
type RenderFunction = (props: RenderProps) => DocumentFragment;
|
34
|
-
declare const customElement: (render: RenderFunction, options?: RenderOptions) => ElementResult;
|
35
|
+
declare const customElement: (render: RenderFunction, options?: Partial<RenderOptions>) => ElementResult;
|
35
36
|
type Registry = {
|
36
37
|
register: (tagName: string, element: CustomElementConstructor) => void;
|
37
38
|
getTagName: (element: CustomElementConstructor) => string | undefined;
|
package/dist/index.js
CHANGED
@@ -49,10 +49,11 @@ var createEffect = (fn) => {
|
|
49
49
|
|
50
50
|
// src/custom-element.ts
|
51
51
|
var DEFAULT_RENDER_OPTIONS = {
|
52
|
-
formAssociated: false
|
52
|
+
formAssociated: false,
|
53
|
+
observedAttributes: []
|
53
54
|
};
|
54
55
|
var customElement = (render, options) => {
|
55
|
-
const { formAssociated } = { ...DEFAULT_RENDER_OPTIONS, ...options };
|
56
|
+
const { formAssociated, observedAttributes } = { ...DEFAULT_RENDER_OPTIONS, ...options };
|
56
57
|
class CustomElement extends HTMLElement {
|
57
58
|
#attrSignals = {};
|
58
59
|
#attributeChangedFns = /* @__PURE__ */ new Set();
|
@@ -65,7 +66,7 @@ var customElement = (render, options) => {
|
|
65
66
|
__customCallbackFns = /* @__PURE__ */ new Map();
|
66
67
|
#shadowRoot = this.attachShadow({ mode: "closed" });
|
67
68
|
#internals = this.attachInternals();
|
68
|
-
#observer = new MutationObserver((mutations) => {
|
69
|
+
#observer = observedAttributes.length > 0 ? null : new MutationObserver((mutations) => {
|
69
70
|
for (const mutation of mutations) {
|
70
71
|
const attrName = mutation.attributeName;
|
71
72
|
if (mutation.type !== "attributes" || attrName === null) continue;
|
@@ -130,6 +131,9 @@ var customElement = (render, options) => {
|
|
130
131
|
static get formAssociated() {
|
131
132
|
return formAssociated;
|
132
133
|
}
|
134
|
+
static get observedAttributes() {
|
135
|
+
return observedAttributes;
|
136
|
+
}
|
133
137
|
constructor() {
|
134
138
|
super();
|
135
139
|
for (const attr of this.attributes) {
|
@@ -138,17 +142,28 @@ var customElement = (render, options) => {
|
|
138
142
|
this.#render();
|
139
143
|
}
|
140
144
|
connectedCallback() {
|
141
|
-
this.#observer
|
145
|
+
if (this.#observer !== null) {
|
146
|
+
this.#observer.observe(this, { attributes: true });
|
147
|
+
}
|
142
148
|
for (const fn of this.#connectedFns) {
|
143
149
|
fn();
|
144
150
|
}
|
145
151
|
}
|
146
152
|
disconnectedCallback() {
|
147
|
-
this.#observer
|
153
|
+
if (this.#observer !== null) {
|
154
|
+
this.#observer.disconnect();
|
155
|
+
}
|
148
156
|
for (const fn of this.#disconnectedFns) {
|
149
157
|
fn();
|
150
158
|
}
|
151
159
|
}
|
160
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
161
|
+
const [, setValue] = this.#attrSignals[name];
|
162
|
+
setValue(newValue);
|
163
|
+
for (const fn of this.#attributeChangedFns) {
|
164
|
+
fn(name, oldValue, newValue);
|
165
|
+
}
|
166
|
+
}
|
152
167
|
adoptedCallback() {
|
153
168
|
for (const fn of this.#adoptedCallbackFns) {
|
154
169
|
fn();
|