element-vir 12.5.4 → 13.0.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 +265 -222
- package/dist/declarative-element/declarative-element-init.d.ts +20 -19
- package/dist/declarative-element/declarative-element.d.ts +32 -33
- package/dist/declarative-element/define-element-no-inputs.d.ts +2 -1
- package/dist/declarative-element/define-element-no-inputs.js +25 -14
- package/dist/declarative-element/define-element.d.ts +1 -1
- package/dist/declarative-element/define-element.js +2 -2
- package/dist/declarative-element/definition-options.d.ts +3 -3
- package/dist/declarative-element/definition-options.js +2 -2
- package/dist/declarative-element/{properties/async-state.d.ts → directives/async-prop.d.ts} +12 -12
- package/dist/declarative-element/{properties/async-state.js → directives/async-prop.js} +2 -2
- package/dist/declarative-element/directives/is-render-ready.directive.d.ts +2 -2
- package/dist/declarative-element/directives/is-render-ready.directive.js +3 -3
- package/dist/declarative-element/directives/render-async.directive.d.ts +6 -0
- package/dist/declarative-element/directives/{render-async-state.directive.js → render-async.directive.js} +2 -2
- package/dist/declarative-element/properties/css-properties.d.ts +3 -0
- package/dist/declarative-element/properties/css-properties.js +11 -0
- package/dist/declarative-element/properties/css-vars.d.ts +6 -6
- package/dist/declarative-element/properties/css-vars.js +1 -22
- package/dist/declarative-element/properties/host-classes.d.ts +5 -3
- package/dist/declarative-element/properties/host-classes.js +2 -3
- package/dist/declarative-element/properties/styles.d.ts +19 -16
- package/dist/declarative-element/properties/styles.js +7 -8
- package/dist/declarative-element/properties/tag-name.d.ts +0 -1
- package/dist/declarative-element/properties/tag-name.js +1 -4
- package/dist/declarative-element/render-callback.d.ts +12 -11
- package/dist/declarative-element/wrap-define-element.d.ts +7 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +3 -2
- package/dist/declarative-element/directives/render-async-state.directive.d.ts +0 -6
package/README.md
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
# element-vir
|
|
2
2
|
|
|
3
|
-
A wrapper for [lit-element](http://lit.dev) that adds type-safe custom element usage and I/O with declarative custom element definition.
|
|
4
|
-
|
|
5
3
|
Heroic. Reactive. Declarative. Type safe. Web components without compromise.
|
|
6
4
|
|
|
5
|
+
A wrapper for [lit-element](http://lit.dev) that adds type-safe custom element usage and I/O with declarative element definition.
|
|
6
|
+
|
|
7
7
|
No need for an extra build step,<br>
|
|
8
8
|
no need for side effect imports, <br>
|
|
9
9
|
no need for unique file extensions,<br>
|
|
10
|
-
no need for
|
|
10
|
+
no need for more static analysis tooling,<br>
|
|
11
11
|
no need for a dedicated, unique syntax.<br>
|
|
12
|
-
_**It's just
|
|
12
|
+
_**It's just JavaScript.**_<br>
|
|
13
|
+
<sup>Or TypeScript, if you're into that!</sup>
|
|
13
14
|
|
|
14
|
-
Uses the power of _native_ JavaScript custom web elements, _native_ JavaScript template literals, _native_ JavaScript functions
|
|
15
|
+
Uses the power of _native_ JavaScript custom web elements, _native_ JavaScript template literals, _native_ JavaScript functions, _native_ HTML, and [lit-element](http://lit.dev).
|
|
15
16
|
|
|
16
17
|
[Works in every major web browser except Internet Explorer.](https://caniuse.com/mdn-api_window_customelements)
|
|
17
18
|
|
|
18
|
-
<sub>\*okay I hope it's obvious that functions are native</sub>
|
|
19
|
-
|
|
20
19
|
# Install
|
|
21
20
|
|
|
22
21
|
[Published on npm:](https://www.npmjs.com/package/element-vir)
|
|
@@ -29,24 +28,26 @@ Make sure to install this as a normal dependency (not just a dev dependency) bec
|
|
|
29
28
|
|
|
30
29
|
# Usage
|
|
31
30
|
|
|
32
|
-
Most usage of this package is done through the `defineElement` or `defineElementNoInputs` functions. See the `DeclarativeElementInit` type for that function's inputs.
|
|
31
|
+
Most usage of this package is done through the `defineElement` or `defineElementNoInputs` functions. See the [`DeclarativeElementInit`](https://github.com/electrovir/element-vir/blob/main/src/declarative-element/declarative-element-init.ts) type for that function's full inputs. The inputs are also described below with examples.
|
|
33
32
|
|
|
34
|
-
All of [`lit`](https://lit.dev)'s syntax and functionality is
|
|
33
|
+
All of [`lit`](https://lit.dev)'s syntax and functionality is available for use if you wish.
|
|
35
34
|
|
|
36
35
|
## Simple element definition
|
|
37
36
|
|
|
38
|
-
Use `defineElementNoInputs` to define your element if it's not going to accept any inputs (or
|
|
37
|
+
Use `defineElementNoInputs` to define your element if it's not going to accept any inputs (or if you're just getting started). It's only input is an object with at least `tagName` and `renderCallback` properties (the types enforce this). Here is a bare-minimum example custom element:
|
|
39
38
|
|
|
40
39
|
<!-- example-link: src/readme-examples/my-simple.element.ts -->
|
|
41
40
|
|
|
42
41
|
```TypeScript
|
|
43
42
|
import {defineElementNoInputs, html} from 'element-vir';
|
|
44
43
|
|
|
45
|
-
export const
|
|
44
|
+
export const MySimple = defineElementNoInputs({
|
|
46
45
|
tagName: 'my-simple',
|
|
47
|
-
renderCallback
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
renderCallback() {
|
|
47
|
+
return html`
|
|
48
|
+
<span>Hello there!</span>
|
|
49
|
+
`;
|
|
50
|
+
},
|
|
50
51
|
});
|
|
51
52
|
```
|
|
52
53
|
|
|
@@ -54,27 +55,27 @@ Make sure to export your element definition if you need to use it in other files
|
|
|
54
55
|
|
|
55
56
|
## Using in other elements
|
|
56
57
|
|
|
57
|
-
To use already defined elements (like
|
|
58
|
+
To use already defined elements (like the example above), they must be interpolated into HTML templates like so:
|
|
58
59
|
|
|
59
60
|
<!-- example-link: src/readme-examples/my-app.element.ts -->
|
|
60
61
|
|
|
61
62
|
```TypeScript
|
|
62
63
|
import {defineElementNoInputs, html} from 'element-vir';
|
|
63
|
-
import {
|
|
64
|
+
import {MySimple} from './my-simple.element';
|
|
64
65
|
|
|
65
|
-
export const
|
|
66
|
+
export const MyApp = defineElementNoInputs({
|
|
66
67
|
tagName: 'my-app',
|
|
67
|
-
renderCallback
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
renderCallback() {
|
|
69
|
+
return html`
|
|
70
|
+
<h1>My App</h1>
|
|
71
|
+
<${MySimple}></${MySimple}>
|
|
72
|
+
`;
|
|
73
|
+
},
|
|
71
74
|
});
|
|
72
75
|
```
|
|
73
76
|
|
|
74
77
|
This requirement ensures that the element is properly imported and registered with the browser. (Compare to pure [lit](http://lit.dev) where you must remember to import each element file as a side effect, or without actually referencing any of its exports in your code.)
|
|
75
78
|
|
|
76
|
-
If you wish to bypass this interpolation, make sure to [import the `html` tagged template directly from `lit`](https://lit.dev/docs/components/overview/), `import {html} from 'lit';`, instead of version contained in `element-vir`.
|
|
77
|
-
|
|
78
79
|
## Adding styles
|
|
79
80
|
|
|
80
81
|
Styles are added through the `styles` property when defining a declarative element (similar to [how they are defined in `lit`](https://lit.dev/docs/components/styles/)):
|
|
@@ -84,7 +85,7 @@ Styles are added through the `styles` property when defining a declarative eleme
|
|
|
84
85
|
```TypeScript
|
|
85
86
|
import {css, defineElementNoInputs, html} from 'element-vir';
|
|
86
87
|
|
|
87
|
-
export const
|
|
88
|
+
export const MyWithStyles = defineElementNoInputs({
|
|
88
89
|
tagName: 'my-with-styles',
|
|
89
90
|
styles: css`
|
|
90
91
|
:host {
|
|
@@ -97,14 +98,16 @@ export const MyWithStylesElement = defineElementNoInputs({
|
|
|
97
98
|
margin-top: 16px;
|
|
98
99
|
}
|
|
99
100
|
`,
|
|
100
|
-
renderCallback
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
renderCallback() {
|
|
102
|
+
return html`
|
|
103
|
+
<span>Hello there!</span>
|
|
104
|
+
<span>How are you doing?</span>
|
|
105
|
+
`;
|
|
106
|
+
},
|
|
104
107
|
});
|
|
105
108
|
```
|
|
106
109
|
|
|
107
|
-
###
|
|
110
|
+
### Interpolated CSS tag selectors
|
|
108
111
|
|
|
109
112
|
Declarative element definitions can be used in the `css` tagged template just like in the `html` tagged template. This will be replaced by the element's tag name:
|
|
110
113
|
|
|
@@ -112,18 +115,20 @@ Declarative element definitions can be used in the `css` tagged template just li
|
|
|
112
115
|
|
|
113
116
|
```TypeScript
|
|
114
117
|
import {css, defineElementNoInputs, html} from 'element-vir';
|
|
115
|
-
import {
|
|
118
|
+
import {MySimple} from './my-simple.element';
|
|
116
119
|
|
|
117
|
-
export const
|
|
120
|
+
export const MyWithStylesAndInterpolatedSelector = defineElementNoInputs({
|
|
118
121
|
tagName: 'my-with-styles-and-interpolated-selector',
|
|
119
122
|
styles: css`
|
|
120
|
-
${
|
|
123
|
+
${MySimple} {
|
|
121
124
|
background-color: blue;
|
|
122
125
|
}
|
|
123
126
|
`,
|
|
124
|
-
renderCallback
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
renderCallback() {
|
|
128
|
+
return html`
|
|
129
|
+
<${MySimple}></${MySimple}>
|
|
130
|
+
`;
|
|
131
|
+
},
|
|
127
132
|
});
|
|
128
133
|
```
|
|
129
134
|
|
|
@@ -138,65 +143,75 @@ To use an element's inputs for use in its template, grab `inputs` from `renderCa
|
|
|
138
143
|
```TypeScript
|
|
139
144
|
import {defineElement, html} from 'element-vir';
|
|
140
145
|
|
|
141
|
-
export const
|
|
146
|
+
export const MyWithInputs = defineElement<{
|
|
142
147
|
username: string;
|
|
143
148
|
email: string;
|
|
144
149
|
}>()({
|
|
145
150
|
tagName: 'my-with-inputs',
|
|
146
|
-
renderCallback
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
renderCallback({inputs}) {
|
|
152
|
+
return html`
|
|
153
|
+
<span>Hello there ${inputs.username}!</span>
|
|
154
|
+
`;
|
|
155
|
+
},
|
|
149
156
|
});
|
|
150
157
|
```
|
|
151
158
|
|
|
152
159
|
## Defining internal state
|
|
153
160
|
|
|
154
|
-
Define internal state with the `stateInit` property when defining an element. Grab it with `state` in `renderCallback` to use state. Grab `updateState` in `renderCallback` to update state:
|
|
161
|
+
Define initial internal state values and types with the `stateInit` property when defining an element. Grab it with `state` in `renderCallback` to use state. Grab `updateState` in `renderCallback` to update state:
|
|
155
162
|
|
|
156
163
|
<!-- example-link: src/readme-examples/my-with-update-state.element.ts -->
|
|
157
164
|
|
|
158
165
|
```TypeScript
|
|
159
166
|
import {defineElementNoInputs, html, listen} from 'element-vir';
|
|
160
167
|
|
|
161
|
-
export const
|
|
168
|
+
export const MyWithUpdateState = defineElementNoInputs({
|
|
162
169
|
tagName: 'my-with-update-state',
|
|
163
|
-
|
|
170
|
+
stateInitStatic: {
|
|
164
171
|
username: 'dev',
|
|
172
|
+
/**
|
|
173
|
+
* Use "as" to create state properties that can be types other than the initial value's
|
|
174
|
+
* type. This is particularly useful when, as below, the initial value is undefined.
|
|
175
|
+
*/
|
|
165
176
|
email: undefined as string | undefined,
|
|
166
177
|
},
|
|
167
|
-
renderCallback
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
renderCallback({state, updateState}) {
|
|
179
|
+
return html`
|
|
180
|
+
<span
|
|
181
|
+
${listen('click', () => {
|
|
182
|
+
updateState({username: 'new name!'});
|
|
183
|
+
})}
|
|
184
|
+
>
|
|
185
|
+
Hello there ${state.username}!
|
|
186
|
+
</span>
|
|
187
|
+
`;
|
|
188
|
+
},
|
|
176
189
|
});
|
|
177
190
|
```
|
|
178
191
|
|
|
179
|
-
### Assigning to
|
|
192
|
+
### Assigning to inputs
|
|
180
193
|
|
|
181
|
-
Use the `assign` directive to assign
|
|
194
|
+
Use the `assign` directive to assign values to child custom elements inputs:
|
|
182
195
|
|
|
183
196
|
<!-- example-link: src/readme-examples/my-with-assignment.element.ts -->
|
|
184
197
|
|
|
185
198
|
```TypeScript
|
|
186
199
|
import {assign, defineElementNoInputs, html} from 'element-vir';
|
|
187
|
-
import {
|
|
200
|
+
import {MyWithInputs} from './my-with-inputs.element';
|
|
188
201
|
|
|
189
|
-
export const
|
|
202
|
+
export const MyWithAssignment = defineElementNoInputs({
|
|
190
203
|
tagName: 'my-with-assignment',
|
|
191
|
-
renderCallback
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
204
|
+
renderCallback() {
|
|
205
|
+
return html`
|
|
206
|
+
<h1>My App</h1>
|
|
207
|
+
<${MyWithInputs}
|
|
208
|
+
${assign(MyWithInputs, {
|
|
209
|
+
email: 'user@example.com',
|
|
210
|
+
username: 'user',
|
|
211
|
+
})}
|
|
212
|
+
></${MyWithInputs}>
|
|
213
|
+
`;
|
|
214
|
+
},
|
|
200
215
|
});
|
|
201
216
|
```
|
|
202
217
|
|
|
@@ -204,7 +219,7 @@ export const MyWithAssignmentElement = defineElementNoInputs({
|
|
|
204
219
|
|
|
205
220
|
There are two other callbacks you can define that are sort of similar to lifecycle callbacks. They are much simpler than lifecycle callbacks however.
|
|
206
221
|
|
|
207
|
-
- `initCallback`: called right before the first render
|
|
222
|
+
- `initCallback`: called right before the first render and has all state and inputs setup. (This is similar to `connectedCallback` in standard HTMLElement classes but is fired much later, after inputs are assigned, to avoid race conditions.)
|
|
208
223
|
- `cleanupCallback`: called when an element is removed from the DOM. (This is the same as the `disconnectedCallback` in standard HTMLElement classes.)
|
|
209
224
|
|
|
210
225
|
<!-- example-link: src/readme-examples/my-with-cleanup-callback.element.ts -->
|
|
@@ -212,9 +227,9 @@ There are two other callbacks you can define that are sort of similar to lifecyc
|
|
|
212
227
|
```TypeScript
|
|
213
228
|
import {defineElementNoInputs, html} from 'element-vir';
|
|
214
229
|
|
|
215
|
-
export const
|
|
230
|
+
export const MyWithAssignmentCleanupCallback = defineElementNoInputs({
|
|
216
231
|
tagName: 'my-with-cleanup-callback',
|
|
217
|
-
|
|
232
|
+
stateInitStatic: {
|
|
218
233
|
intervalId: undefined as undefined | number,
|
|
219
234
|
},
|
|
220
235
|
initCallback: ({updateState}) => {
|
|
@@ -222,9 +237,11 @@ export const MyWithAssignmentCleanupCallbackElement = defineElementNoInputs({
|
|
|
222
237
|
intervalId: window.setInterval(() => console.info('hi'), 1000),
|
|
223
238
|
});
|
|
224
239
|
},
|
|
225
|
-
renderCallback
|
|
226
|
-
|
|
227
|
-
|
|
240
|
+
renderCallback() {
|
|
241
|
+
return html`
|
|
242
|
+
<h1>My App</h1>
|
|
243
|
+
`;
|
|
244
|
+
},
|
|
228
245
|
cleanupCallback: ({state, updateState}) => {
|
|
229
246
|
window.clearInterval(state.intervalId);
|
|
230
247
|
updateState({
|
|
@@ -236,55 +253,63 @@ export const MyWithAssignmentCleanupCallbackElement = defineElementNoInputs({
|
|
|
236
253
|
|
|
237
254
|
## Element events (outputs)
|
|
238
255
|
|
|
239
|
-
|
|
256
|
+
When defining a declarative element, use `events` to setup event names and types. Each event must be initialized with `defineElementEvent` and a type parameter but no run-time inputs.
|
|
240
257
|
|
|
241
|
-
To dispatch an event, grab `dispatch` from `renderCallback`'s parameters.
|
|
258
|
+
To dispatch an event, grab `dispatch` and `events` from `renderCallback`'s parameters.
|
|
242
259
|
|
|
243
260
|
<!-- example-link: src/readme-examples/my-with-events.element.ts -->
|
|
244
261
|
|
|
245
262
|
```TypeScript
|
|
246
263
|
import {defineElementEvent, defineElementNoInputs, html, listen} from 'element-vir';
|
|
247
264
|
|
|
248
|
-
export const
|
|
265
|
+
export const MyWithEvents = defineElementNoInputs({
|
|
249
266
|
tagName: 'my-with-events',
|
|
250
267
|
events: {
|
|
251
268
|
logoutClick: defineElementEvent<void>(),
|
|
252
269
|
randomNumber: defineElementEvent<number>(),
|
|
253
270
|
},
|
|
254
|
-
renderCallback
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
271
|
+
renderCallback({dispatch, events}) {
|
|
272
|
+
return html`
|
|
273
|
+
<button ${listen('click', () => dispatch(new events.logoutClick(undefined)))}>
|
|
274
|
+
log out
|
|
275
|
+
</button>
|
|
276
|
+
<button ${listen('click', () => dispatch(new events.randomNumber(Math.random())))}>
|
|
277
|
+
generate random number
|
|
278
|
+
</button>
|
|
279
|
+
`;
|
|
280
|
+
},
|
|
262
281
|
});
|
|
263
282
|
```
|
|
264
283
|
|
|
265
|
-
### Listening to
|
|
284
|
+
### Listening to element events (outputs)
|
|
266
285
|
|
|
267
|
-
Use the `listen` directive to listen to
|
|
286
|
+
Use the `listen` directive to listen to events emitted by your custom elements:
|
|
268
287
|
|
|
269
|
-
<!-- example-link: src/readme-examples/my-with-
|
|
288
|
+
<!-- example-link: src/readme-examples/my-with-event-listening.element.ts -->
|
|
270
289
|
|
|
271
290
|
```TypeScript
|
|
272
|
-
import {
|
|
291
|
+
import {defineElementNoInputs, html, listen} from 'element-vir';
|
|
292
|
+
import {MyWithEvents} from './my-with-events.element';
|
|
273
293
|
|
|
274
|
-
export const
|
|
275
|
-
tagName: 'my-with-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
294
|
+
export const MyWithEventListening = defineElementNoInputs({
|
|
295
|
+
tagName: 'my-with-event-listening',
|
|
296
|
+
stateInitStatic: {
|
|
297
|
+
myNumber: -1,
|
|
298
|
+
},
|
|
299
|
+
renderCallback({state, updateState}) {
|
|
300
|
+
return html`
|
|
301
|
+
<h1>My App</h1>
|
|
302
|
+
<${MyWithEvents}
|
|
303
|
+
${listen(MyWithEvents.events.logoutClick, () => {
|
|
304
|
+
console.info('logout triggered');
|
|
305
|
+
})}
|
|
306
|
+
${listen(MyWithEvents.events.randomNumber, (event) => {
|
|
307
|
+
updateState({myNumber: event.detail});
|
|
308
|
+
})}
|
|
309
|
+
></${MyWithEvents}>
|
|
310
|
+
<span>${state.myNumber}</span>
|
|
311
|
+
`;
|
|
279
312
|
},
|
|
280
|
-
renderCallback: ({dispatch, events}) => html`
|
|
281
|
-
<button ${listen('click', () => dispatch(new events.logoutClick(undefined)))}>
|
|
282
|
-
log out
|
|
283
|
-
</button>
|
|
284
|
-
<button ${listen('click', () => dispatch(new events.randomNumber(Math.random())))}>
|
|
285
|
-
generate random number
|
|
286
|
-
</button>
|
|
287
|
-
`,
|
|
288
313
|
});
|
|
289
314
|
```
|
|
290
315
|
|
|
@@ -292,9 +317,7 @@ export const MyWithEventsElement = defineElementNoInputs({
|
|
|
292
317
|
|
|
293
318
|
## Typed events without an element
|
|
294
319
|
|
|
295
|
-
Create a custom event type with `defineTypedEvent`. Make sure to include the type
|
|
296
|
-
|
|
297
|
-
### Creating a typed event
|
|
320
|
+
Create a custom event type with `defineTypedEvent`. Make sure to include the type parameter and call it twice, the second time with the event type name string to ensure type safety when using your event. Note that event type names should be unique, or they will clash with each other.
|
|
298
321
|
|
|
299
322
|
<!-- example-link: src/readme-examples/my-custom-action.event.ts -->
|
|
300
323
|
|
|
@@ -306,7 +329,7 @@ export const MyCustomActionEvent = defineTypedEvent<number>()('my-custom-action'
|
|
|
306
329
|
|
|
307
330
|
### Using a typed event
|
|
308
331
|
|
|
309
|
-
|
|
332
|
+
Dispatching a custom event and listening to a custom event is the same as doing so for element events:
|
|
310
333
|
|
|
311
334
|
<!-- example-link: src/readme-examples/my-with-custom-events.element.ts -->
|
|
312
335
|
|
|
@@ -314,21 +337,23 @@ Both dispatching a custom event and listening to a custom event:
|
|
|
314
337
|
import {defineElementNoInputs, html, listen} from 'element-vir';
|
|
315
338
|
import {MyCustomActionEvent} from './my-custom-action.event';
|
|
316
339
|
|
|
317
|
-
export const
|
|
340
|
+
export const MyWithCustomEvents = defineElementNoInputs({
|
|
318
341
|
tagName: 'my-with-custom-events',
|
|
319
|
-
renderCallback
|
|
320
|
-
|
|
321
|
-
${listen(MyCustomActionEvent, (event) => {
|
|
322
|
-
console.info(`Got a number! ${event.detail}`);
|
|
323
|
-
})}
|
|
324
|
-
>
|
|
342
|
+
renderCallback({dispatch}) {
|
|
343
|
+
return html`
|
|
325
344
|
<div
|
|
326
|
-
${listen(
|
|
327
|
-
|
|
345
|
+
${listen(MyCustomActionEvent, (event) => {
|
|
346
|
+
console.info(`Got a number! ${event.detail}`);
|
|
328
347
|
})}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
348
|
+
>
|
|
349
|
+
<div
|
|
350
|
+
${listen('click', () => {
|
|
351
|
+
dispatch(new MyCustomActionEvent(Math.random()));
|
|
352
|
+
})}
|
|
353
|
+
></div>
|
|
354
|
+
</div>
|
|
355
|
+
`;
|
|
356
|
+
},
|
|
332
357
|
});
|
|
333
358
|
```
|
|
334
359
|
|
|
@@ -336,34 +361,36 @@ export const MyWithCustomEventsElement = defineElementNoInputs({
|
|
|
336
361
|
|
|
337
362
|
### Defining host classes
|
|
338
363
|
|
|
339
|
-
Host classes can be defined and used with type safety. Host classes are used to provide alternative styles for
|
|
364
|
+
Host classes can be defined and used with type safety. Host classes are used to provide alternative styles for custom elements. They are purely driven by CSS and are thus applied to the the `class` HTML attribute.
|
|
340
365
|
|
|
341
|
-
Host classes
|
|
366
|
+
Host classes are defined by passing an object to `hostClasses` at element definition time. Each property name in the `hostClasses` object creates a host class name (note that host class names must start with the element's tag name). Each value in the `hostClasses` object defines behavior for teh host class:
|
|
342
367
|
|
|
343
|
-
|
|
368
|
+
- if the value is a callback, that host class will automatically be applied if the callback returns true after a render is executed.
|
|
369
|
+
- if the value is `false`, the host class is never automatically applied, it must be manually applied by consumers.
|
|
370
|
+
|
|
371
|
+
Apply host classes in the element's stylesheet by using a callback for the styles property:
|
|
344
372
|
|
|
345
373
|
<!-- example-link: src/readme-examples/my-with-host-class-definition.element.ts -->
|
|
346
374
|
|
|
347
375
|
```TypeScript
|
|
348
376
|
import {css, defineElementNoInputs, html} from 'element-vir';
|
|
349
377
|
|
|
350
|
-
export const
|
|
378
|
+
export const MyWithHostClassDefinition = defineElementNoInputs({
|
|
351
379
|
tagName: 'my-with-host-class-definition',
|
|
352
|
-
|
|
380
|
+
stateInitStatic: {
|
|
353
381
|
myProp: 'hello there',
|
|
354
382
|
},
|
|
355
383
|
hostClasses: {
|
|
356
384
|
/**
|
|
357
|
-
* Setting the value to false means this host class will
|
|
358
|
-
*
|
|
359
|
-
* desired.
|
|
385
|
+
* Setting the value to false means this host class will never be automatically applied. It
|
|
386
|
+
* will simply be a static member on the element for manual application in consumers.
|
|
360
387
|
*/
|
|
361
|
-
|
|
388
|
+
'my-with-host-class-definition-a': false,
|
|
362
389
|
/**
|
|
363
|
-
* This host class will be automatically applied if the given callback evaluated to true
|
|
390
|
+
* This host class will be automatically applied if the given callback is evaluated to true
|
|
364
391
|
* after a call to renderCallback.
|
|
365
392
|
*/
|
|
366
|
-
|
|
393
|
+
'my-with-host-class-definition-automatic': ({state}) => {
|
|
367
394
|
return state.myProp === 'foo';
|
|
368
395
|
},
|
|
369
396
|
},
|
|
@@ -371,74 +398,82 @@ export const MyWithHostClassDefinitionElement = defineElementNoInputs({
|
|
|
371
398
|
* Apply styles to the host classes by using a callback for "styles". The callback's argument
|
|
372
399
|
* contains the host classes defined above in the "hostClasses" property.
|
|
373
400
|
*/
|
|
374
|
-
styles: ({
|
|
375
|
-
${
|
|
401
|
+
styles: ({hostClasses}) => css`
|
|
402
|
+
${hostClasses['my-with-host-class-definition-automatic'].selector} {
|
|
376
403
|
color: blue;
|
|
377
404
|
}
|
|
378
405
|
|
|
379
|
-
${
|
|
406
|
+
${hostClasses['my-with-host-class-definition-a'].selector} {
|
|
380
407
|
color: red;
|
|
381
408
|
}
|
|
382
409
|
`,
|
|
383
|
-
renderCallback
|
|
384
|
-
|
|
385
|
-
|
|
410
|
+
renderCallback({state}) {
|
|
411
|
+
return html`
|
|
412
|
+
${state.myProp}
|
|
413
|
+
`;
|
|
414
|
+
},
|
|
386
415
|
});
|
|
387
416
|
```
|
|
388
417
|
|
|
389
418
|
### Applying host classes
|
|
390
419
|
|
|
391
|
-
To apply a host class in a
|
|
420
|
+
To apply a host class in a consumer, access the child element's `.hostClasses` property:
|
|
392
421
|
|
|
393
422
|
<!-- example-link: src/readme-examples/my-with-host-class-usage.element.ts -->
|
|
394
423
|
|
|
395
424
|
```TypeScript
|
|
396
425
|
import {defineElementNoInputs, html} from 'element-vir';
|
|
397
|
-
import {
|
|
426
|
+
import {MyWithHostClassDefinition} from './my-with-host-class-definition.element';
|
|
398
427
|
|
|
399
|
-
export const
|
|
428
|
+
export const MyWithHostClassUsage = defineElementNoInputs({
|
|
400
429
|
tagName: 'my-with-host-class-usage',
|
|
401
|
-
renderCallback
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
430
|
+
renderCallback() {
|
|
431
|
+
return html`
|
|
432
|
+
<${MyWithHostClassDefinition}
|
|
433
|
+
class=${MyWithHostClassDefinition.hostClasses['my-with-host-class-definition-a']}
|
|
434
|
+
></${MyWithHostClassDefinition}>
|
|
435
|
+
`;
|
|
436
|
+
},
|
|
406
437
|
});
|
|
407
438
|
```
|
|
408
439
|
|
|
409
440
|
## CSS Vars
|
|
410
441
|
|
|
411
|
-
Typed CSS
|
|
442
|
+
Typed CSS variables are created in a similar manner to host classes:
|
|
412
443
|
|
|
413
444
|
<!-- example-link: src/readme-examples/my-with-css-vars.element.ts -->
|
|
414
445
|
|
|
415
446
|
```TypeScript
|
|
416
447
|
import {css, defineElementNoInputs, html} from 'element-vir';
|
|
417
448
|
|
|
418
|
-
export const
|
|
449
|
+
export const MyWithCssVars = defineElementNoInputs({
|
|
419
450
|
tagName: 'my-with-css-vars',
|
|
420
451
|
cssVars: {
|
|
421
|
-
/**
|
|
422
|
-
|
|
423
|
-
* via "cssVarValue".
|
|
424
|
-
*/
|
|
425
|
-
myCssVar: 'blue',
|
|
452
|
+
/** The value assigned here ('blue') becomes the fallback value for this CSS var. */
|
|
453
|
+
'my-with-css-vars-my-var': 'blue',
|
|
426
454
|
},
|
|
427
|
-
styles: ({
|
|
455
|
+
styles: ({cssVars}) => css`
|
|
428
456
|
:host {
|
|
429
|
-
/*
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
457
|
+
/*
|
|
458
|
+
Set CSS vars (or reference the name directly) via the ".name" property
|
|
459
|
+
*/
|
|
460
|
+
${cssVars['my-with-css-vars-my-var'].name}: yellow;
|
|
461
|
+
/*
|
|
462
|
+
Use CSS vars with the ".value" property. This includes a "var" wrapper and the
|
|
463
|
+
assigned fallback value (which in this case is 'blue').
|
|
464
|
+
*/
|
|
465
|
+
color: ${cssVars['my-with-css-vars-my-var'].value};
|
|
433
466
|
}
|
|
434
467
|
`,
|
|
435
|
-
renderCallback
|
|
468
|
+
renderCallback() {
|
|
469
|
+
return html``;
|
|
470
|
+
},
|
|
436
471
|
});
|
|
437
472
|
```
|
|
438
473
|
|
|
439
474
|
## Custom Type Requirements
|
|
440
475
|
|
|
441
|
-
Use `wrapDefineElement` to compose `defineElement` and `defineElementNoInputs`. This is particularly useful to adding restrictions on the element `tagName`, but it can be used for restricting any of the
|
|
476
|
+
Use `wrapDefineElement` to compose `defineElement` and `defineElementNoInputs`. This is particularly useful to adding restrictions on the element `tagName`, but it can be used for restricting any of the type parameters:
|
|
442
477
|
|
|
443
478
|
<!-- example-link: src/readme-examples/my-custom-define.ts -->
|
|
444
479
|
|
|
@@ -480,64 +515,68 @@ export const {
|
|
|
480
515
|
|
|
481
516
|
The following custom [`lit` directives](https://lit.dev/docs/templates/custom-directives/) are contained within this package.
|
|
482
517
|
|
|
483
|
-
|
|
518
|
+
All [built-in `lit` directives](https://lit.dev/docs/templates/directives/) are also exported by `element-vir`.
|
|
484
519
|
|
|
485
|
-
|
|
520
|
+
### onDomCreated
|
|
486
521
|
|
|
487
|
-
This triggers only once when the element it's attached has actually been created in the DOM. If
|
|
522
|
+
This triggers only once when the element it's attached to has actually been created in the DOM. If the attached element changes, the callback will be triggered again.
|
|
488
523
|
|
|
489
524
|
<!-- example-link: src/readme-examples/my-with-on-dom-created.element.ts -->
|
|
490
525
|
|
|
491
526
|
```TypeScript
|
|
492
527
|
import {defineElementNoInputs, html, onDomCreated} from 'element-vir';
|
|
493
528
|
|
|
494
|
-
export const
|
|
529
|
+
export const MyWithOnDomCreated = defineElementNoInputs({
|
|
495
530
|
tagName: 'my-with-on-dom-created',
|
|
496
|
-
renderCallback
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
531
|
+
renderCallback() {
|
|
532
|
+
return html`
|
|
533
|
+
<span
|
|
534
|
+
${onDomCreated((element) => {
|
|
535
|
+
// logs a span element
|
|
536
|
+
console.info(element);
|
|
537
|
+
})}
|
|
538
|
+
>
|
|
539
|
+
Hello there!
|
|
540
|
+
</span>
|
|
541
|
+
`;
|
|
542
|
+
},
|
|
506
543
|
});
|
|
507
544
|
```
|
|
508
545
|
|
|
509
546
|
### onResize
|
|
510
547
|
|
|
511
|
-
This directive
|
|
548
|
+
This directive fires its callback whenever the element it's attached to resizes. The callback is passed an object with a portion of the [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry) properties.
|
|
512
549
|
|
|
513
550
|
<!-- example-link: src/readme-examples/my-with-on-resize.element.ts -->
|
|
514
551
|
|
|
515
552
|
```TypeScript
|
|
516
553
|
import {defineElementNoInputs, html, onResize} from 'element-vir';
|
|
517
554
|
|
|
518
|
-
export const
|
|
555
|
+
export const MyWithOnResize = defineElementNoInputs({
|
|
519
556
|
tagName: 'my-with-on-resize',
|
|
520
|
-
renderCallback
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
557
|
+
renderCallback() {
|
|
558
|
+
return html`
|
|
559
|
+
<span
|
|
560
|
+
${onResize((entry) => {
|
|
561
|
+
// this will track resizing of this span
|
|
562
|
+
// the entry parameter contains target and contentRect properties
|
|
563
|
+
console.info(entry);
|
|
564
|
+
})}
|
|
565
|
+
>
|
|
566
|
+
Hello there!
|
|
567
|
+
</span>
|
|
568
|
+
`;
|
|
569
|
+
},
|
|
531
570
|
});
|
|
532
571
|
```
|
|
533
572
|
|
|
534
573
|
### assign
|
|
535
574
|
|
|
536
|
-
Assign a value to one of a custom element's properties. This is explained in the **Assigning to
|
|
575
|
+
Assign a value to one of a custom element's properties. This is explained in the **Assigning to inputs** section earlier.
|
|
537
576
|
|
|
538
577
|
### listen
|
|
539
578
|
|
|
540
|
-
Listen to a specific event
|
|
579
|
+
Listen to a specific event. This is explained in the **Listening to element events (outputs)** section earlier.
|
|
541
580
|
|
|
542
581
|
### assignWithCleanup
|
|
543
582
|
|
|
@@ -547,28 +586,30 @@ This directive is the same as the `assign` directive but it accepts an additiona
|
|
|
547
586
|
|
|
548
587
|
```TypeScript
|
|
549
588
|
import {assignWithCleanup, defineElementNoInputs, html} from 'element-vir';
|
|
550
|
-
import {
|
|
589
|
+
import {MyWithInputs} from './my-with-inputs.element';
|
|
551
590
|
|
|
552
|
-
export const
|
|
591
|
+
export const MyWithCleanup = defineElementNoInputs({
|
|
553
592
|
tagName: 'my-with-cleanup',
|
|
554
|
-
renderCallback
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
593
|
+
renderCallback() {
|
|
594
|
+
return html`
|
|
595
|
+
<h1>My App</h1>
|
|
596
|
+
<${MyWithInputs}
|
|
597
|
+
${assignWithCleanup(
|
|
598
|
+
MyWithInputs,
|
|
599
|
+
{
|
|
600
|
+
email: 'user@example.com',
|
|
601
|
+
username: 'user',
|
|
602
|
+
},
|
|
603
|
+
(previousValue) => {
|
|
604
|
+
// here would be the cleanup code.
|
|
605
|
+
// In this specific example the value is just a string, so no cleanup is needed
|
|
606
|
+
// and the following line isn't actually doing anything.
|
|
607
|
+
previousValue.username.trim();
|
|
608
|
+
},
|
|
609
|
+
)}
|
|
610
|
+
></${MyWithInputs}>
|
|
611
|
+
`;
|
|
612
|
+
},
|
|
572
613
|
});
|
|
573
614
|
```
|
|
574
615
|
|
|
@@ -581,16 +622,18 @@ Use the `renderIf` directive to easily render a template if a given condition is
|
|
|
581
622
|
```TypeScript
|
|
582
623
|
import {defineElement, html, renderIf} from 'element-vir';
|
|
583
624
|
|
|
584
|
-
export const
|
|
585
|
-
tagName: 'my-
|
|
586
|
-
renderCallback
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
625
|
+
export const MyWithRenderIf = defineElement<{shouldRender: boolean}>()({
|
|
626
|
+
tagName: 'my-with-render-if',
|
|
627
|
+
renderCallback({inputs}) {
|
|
628
|
+
return html`
|
|
629
|
+
${renderIf(
|
|
630
|
+
inputs.shouldRender,
|
|
631
|
+
html`
|
|
632
|
+
I'm conditionally rendered!
|
|
633
|
+
`,
|
|
634
|
+
)}
|
|
635
|
+
`;
|
|
636
|
+
},
|
|
594
637
|
});
|
|
595
638
|
```
|
|
596
639
|
|
|
@@ -601,7 +644,7 @@ Use the `renderAsyncState` directive in conjunction with the `asyncState` proper
|
|
|
601
644
|
<!-- example-link: src/readme-examples/my-with-async-prop.element.ts -->
|
|
602
645
|
|
|
603
646
|
```TypeScript
|
|
604
|
-
import {
|
|
647
|
+
import {asyncProp, defineElement, html, listen, renderAsync} from 'element-vir';
|
|
605
648
|
|
|
606
649
|
type EndpointData = number[];
|
|
607
650
|
|
|
@@ -619,12 +662,12 @@ async function loadSomething(endpoint: string): Promise<EndpointData> {
|
|
|
619
662
|
return data;
|
|
620
663
|
}
|
|
621
664
|
|
|
622
|
-
export const
|
|
623
|
-
tagName: 'my-
|
|
624
|
-
|
|
625
|
-
data:
|
|
665
|
+
export const MyWithAsyncProp = defineElement<{endpoint: string}>()({
|
|
666
|
+
tagName: 'my-with-async-prop',
|
|
667
|
+
stateInitStatic: {
|
|
668
|
+
data: asyncProp<EndpointData>(),
|
|
626
669
|
},
|
|
627
|
-
renderCallback
|
|
670
|
+
renderCallback({inputs, state, updateState}) {
|
|
628
671
|
/**
|
|
629
672
|
* This creates a promise which automatically updates the state.loadsLater prop once the
|
|
630
673
|
* promise resolves. It only creates a new promise if the "trigger" value changes.
|
|
@@ -639,7 +682,7 @@ export const MyWithAsyncStateElement = defineElement<{endpoint: string}>()({
|
|
|
639
682
|
return html`
|
|
640
683
|
Here's the data:
|
|
641
684
|
<br />
|
|
642
|
-
${
|
|
685
|
+
${renderAsync(state.data, 'Loading...', (loadedData) => {
|
|
643
686
|
return html`
|
|
644
687
|
Got the data: ${loadedData}
|
|
645
688
|
`;
|
|
@@ -649,7 +692,7 @@ export const MyWithAsyncStateElement = defineElement<{endpoint: string}>()({
|
|
|
649
692
|
${listen('click', () => {
|
|
650
693
|
updateState({
|
|
651
694
|
data: {
|
|
652
|
-
/** You can force
|
|
695
|
+
/** You can force asyncProp to update by passing in forceUpdate: true. */
|
|
653
696
|
forceUpdate: true,
|
|
654
697
|
},
|
|
655
698
|
});
|
|
@@ -678,7 +721,7 @@ requireAllCustomElementsToBeDeclarativeElements();
|
|
|
678
721
|
|
|
679
722
|
## markdown out of date
|
|
680
723
|
|
|
681
|
-
If you see this: `Code in Markdown file(s) is out of date. Run without --check to update. code-in-markdown failed.`, run `npm run update
|
|
724
|
+
If you see this: `Code in Markdown file(s) is out of date. Run without --check to update. code-in-markdown failed.`, run `npm run docs:update` to fix it.
|
|
682
725
|
|
|
683
726
|
## Testing source map errors
|
|
684
727
|
|
|
@@ -688,4 +731,4 @@ If you see
|
|
|
688
731
|
Error while reading source maps for ...
|
|
689
732
|
```
|
|
690
733
|
|
|
691
|
-
While running `npm test`, don't worry about it. Those only happen when tests fail.
|
|
734
|
+
While running `npm test`, don't worry about it. Those only happen when tests fail and are not indicative of any problem beyond the test failure reasons.
|