microtags 0.0.9 → 0.0.12
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 +462 -177
- package/dist/builders.d.ts +2 -2
- package/dist/builders.d.ts.map +1 -1
- package/dist/{chunk-P4QHFKTJ.js → chunk-FZKCMDSS.js} +4 -4
- package/dist/{chunk-P4QHFKTJ.js.map → chunk-FZKCMDSS.js.map} +2 -2
- package/dist/index.cjs +31 -31
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +31 -31
- package/dist/index.js.map +2 -2
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +3 -3
- package/dist/meta.json +17 -17
- package/dist/setup-context.d.ts.map +1 -1
- package/dist/util.cjs +3 -3
- package/dist/util.cjs.map +2 -2
- package/dist/util.d.ts +2 -2
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +3 -3
- package/dist/util.min.js +1 -1
- package/dist/util.min.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
[](https://bundlephobia.com/package/microtags)
|
|
10
10
|
[](LICENSE)
|
|
11
11
|
|
|
12
|
-
Web component helper with reactive props, automatic cleanup, and
|
|
13
|
-
[Shadow DOM](https://gomakethings.com/the-shadow-dom-is-an-antipattern/).
|
|
12
|
+
Web component helper with reactive props, automatic cleanup, and
|
|
13
|
+
[no Shadow DOM](https://gomakethings.com/the-shadow-dom-is-an-antipattern/).
|
|
14
14
|
Reactive via [alien-signals](https://github.com/stackblitz/alien-signals).
|
|
15
15
|
|
|
16
|
-
**Size: <!-- size -->3.
|
|
16
|
+
**Size: <!-- size -->3.48 KB<!-- /size -->** with all dependencies, minified and brotlied
|
|
17
17
|
|
|
18
18
|
Inspired by [nanotags](https://nanotags.psdcoder.dev/). Reactive props use
|
|
19
19
|
**alien-signals** instead of nanostores atoms.
|
|
@@ -25,22 +25,32 @@ Inspired by [nanotags](https://nanotags.psdcoder.dev/). Reactive props use
|
|
|
25
25
|
- [Install](#install)
|
|
26
26
|
* [Import map](#import-map)
|
|
27
27
|
* [Pre-bundled](#pre-bundled)
|
|
28
|
-
- [
|
|
28
|
+
- [Get Started](#get-started)
|
|
29
|
+
* [1. Call `.define`](#1-call-define)
|
|
30
|
+
* [2. Call the `.with*` Methods](#2-call-the-with-methods)
|
|
31
|
+
* [3. Call `.setup`](#3-call-setup)
|
|
32
|
+
- [Full Example](#full-example)
|
|
33
|
+
- [Validation](#validation)
|
|
34
|
+
* [Prop validation](#prop-validation)
|
|
35
|
+
+ [`p.schema` vs Standard Schema](#pschema-vs-standard-schema)
|
|
29
36
|
* [Client-side validation](#client-side-validation)
|
|
30
37
|
- [Serverside Example](#serverside-example)
|
|
31
38
|
* [`.TAG`](#tag)
|
|
32
39
|
* [`.refs`](#refs)
|
|
33
40
|
- [API](#api)
|
|
34
|
-
* [Props](#props)
|
|
35
|
-
+ [`withProps`](#withprops)
|
|
36
|
-
* [Refs](#refs)
|
|
37
|
-
+ [`withRefs`](#withrefs)
|
|
38
|
-
+ [Element types](#element-types)
|
|
39
41
|
* [`define`](#define)
|
|
40
|
-
|
|
41
|
-
* [`
|
|
42
|
-
+ [
|
|
43
|
-
|
|
42
|
+
+ [`ComponentBuilder`](#componentbuilder)
|
|
43
|
+
* [`builder.withProps`](#builderwithprops)
|
|
44
|
+
+ [`p.json`](#pjson)
|
|
45
|
+
+ [`p.schema`](#pschema)
|
|
46
|
+
* [`builder.withRefs(fn)`](#builderwithrefsfn)
|
|
47
|
+
* [`builder.withContexts`](#builderwithcontexts)
|
|
48
|
+
+ [`.withContexts` Example](#withcontexts-example)
|
|
49
|
+
* [`builder.setup(ctx)`](#buildersetupctx)
|
|
50
|
+
+ [`ctx`](#ctx)
|
|
51
|
+
- [Missing refs](#missing-refs)
|
|
52
|
+
* [Checking refs in tests](#checking-refs-in-tests)
|
|
53
|
+
- [Typed Events](#typed-events)
|
|
44
54
|
- [Templates](#templates)
|
|
45
55
|
- [Subpath Exports](#subpath-exports)
|
|
46
56
|
* [`microtags/context`](#microtagscontext)
|
|
@@ -49,14 +59,14 @@ Inspired by [nanotags](https://nanotags.psdcoder.dev/). Reactive props use
|
|
|
49
59
|
+ [`render`](#render)
|
|
50
60
|
* [`microtags/util`](#microtagsutil)
|
|
51
61
|
+ [Coercion](#coercion)
|
|
52
|
-
+ [`
|
|
62
|
+
+ [`runAll`](#runall)
|
|
53
63
|
+ [`toAttributes`](#toattributes)
|
|
54
|
-
- [
|
|
64
|
+
- [Differences from `nanotags`](#differences-from-nanotags)
|
|
55
65
|
* [Reactivity primitive](#reactivity-primitive)
|
|
56
66
|
* [Effects + Subscriptions](#effects--subscriptions)
|
|
57
67
|
* [Refs: `r.all` instead of `r.many`](#refs-rall-instead-of-rmany)
|
|
58
68
|
* [Context API](#context-api)
|
|
59
|
-
* [Props](#props
|
|
69
|
+
* [Props](#props)
|
|
60
70
|
* [Not ported](#not-ported)
|
|
61
71
|
* [Additions / New Features](#additions--new-features)
|
|
62
72
|
|
|
@@ -64,9 +74,7 @@ Inspired by [nanotags](https://nanotags.psdcoder.dev/). Reactive props use
|
|
|
64
74
|
|
|
65
75
|
</details>
|
|
66
76
|
|
|
67
|
-
----------------------------------------------------------------------
|
|
68
77
|
## Install
|
|
69
|
-
----------------------------------------------------------------------
|
|
70
78
|
|
|
71
79
|
```sh
|
|
72
80
|
npm i -S microtags
|
|
@@ -97,10 +105,8 @@ Subpaths follow the same URL pattern: `microtags/context`, `microtags/render`.
|
|
|
97
105
|
|
|
98
106
|
### Pre-bundled
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
single self-contained file with `alien-signals` inlined, so no other
|
|
103
|
-
mappings are needed:
|
|
108
|
+
This package exposes `microtags/min`, which is a single self-contained file
|
|
109
|
+
with `alien-signals` inlined.
|
|
104
110
|
|
|
105
111
|
```html
|
|
106
112
|
<script type="importmap">
|
|
@@ -112,23 +118,89 @@ mappings are needed:
|
|
|
112
118
|
</script>
|
|
113
119
|
```
|
|
114
120
|
|
|
121
|
+
## Get Started
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
## Example
|
|
118
|
-
----------------------------------------------------------------------
|
|
123
|
+
### 1. Call `.define`
|
|
119
124
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
DOM inside `.setup`.
|
|
125
|
+
```ts
|
|
126
|
+
import { define } from 'microtags'
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
const myCounter = define('my-counter')
|
|
129
|
+
```
|
|
126
130
|
|
|
127
131
|
---
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
### 2. Call the `.with*` Methods
|
|
134
|
+
|
|
135
|
+
Methods `withProps`, `withRefs`, and `withContexts` are optional and can appear
|
|
136
|
+
in any order. `.setup` ends the chain. It calls `customElements.define` under
|
|
137
|
+
the hood and returns a typed constructor.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
myCounter
|
|
141
|
+
.withProps(p => ({
|
|
142
|
+
start: p.number(), // the starting count (an attribute)
|
|
143
|
+
}))
|
|
144
|
+
.withRefs(r => ({ // keys in this object are the `data-ref` names in HTML
|
|
145
|
+
display: r.one<HTMLDivElement>(), // typed as HTMLDivElement,
|
|
146
|
+
inc: r.one(), // depends on a child in HTML with `data-ref="inc"`
|
|
147
|
+
dec: r.one(), // depends on a child in HTML with `data-ref="dec"`
|
|
148
|
+
copy: r.one(), // the <copy-btn> element, via `data-ref="copy"`
|
|
149
|
+
}))
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 3. Call `.setup`
|
|
153
|
+
|
|
154
|
+
Method `.setup` takes a function that gets called with `context` object.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
myCounter
|
|
158
|
+
.setup(ctx => {
|
|
159
|
+
const count = signal(ctx.props.start())
|
|
160
|
+
// `ctx.refs` are expected to be HTML elements with a matching `data-ref`
|
|
161
|
+
const display = ctx.refs.display
|
|
162
|
+
|
|
163
|
+
// whenever `count` changes
|
|
164
|
+
ctx.effect(() => {
|
|
165
|
+
display.textContent = `Count: ${count()}`
|
|
166
|
+
display.style.color = count() < 0 ? 'red' : 'inherit'
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// Reflect the live count onto the copy button's `value` attribute.
|
|
170
|
+
// Because `value` is attribute-backed, this flows into <copy-btn>'s
|
|
171
|
+
// own signal, so it always copies the current count.
|
|
172
|
+
ctx.effect(() => {
|
|
173
|
+
const copy = ctx.refs.copy
|
|
174
|
+
copy.setAttribute('value', String(count()))
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Listen for clicks.
|
|
179
|
+
*/
|
|
180
|
+
ctx.on(ctx.refs.inc, 'click', () => {
|
|
181
|
+
count(count() + 1)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
ctx.on(ctx.refs.dec, 'click', () => {
|
|
185
|
+
count(count() - 1)
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Full Example
|
|
191
|
+
|
|
192
|
+
This example expects the full HTML to already be in the DOM.
|
|
193
|
+
Defining the components is about "hydrating" the page, or adding behavior.
|
|
194
|
+
|
|
195
|
+
If you want to client-side render the components, that is possible too.
|
|
196
|
+
You can update the DOM inside `.setup`.
|
|
197
|
+
|
|
198
|
+
>
|
|
199
|
+
> [!NOTE]
|
|
200
|
+
> The server should know how to create the markup that components depend
|
|
201
|
+
> on here. See [the serverside example](#serverside-example).
|
|
202
|
+
>
|
|
203
|
+
|
|
132
204
|
|
|
133
205
|
```ts
|
|
134
206
|
import { define } from 'microtags'
|
|
@@ -136,7 +208,7 @@ import { signal } from 'alien-signals'
|
|
|
136
208
|
|
|
137
209
|
/**
|
|
138
210
|
* - attribute-backed prop (`start`)
|
|
139
|
-
* - data-ref elements
|
|
211
|
+
* - data-ref elements -- each needs a data-ref attribute, eg data-ref="inc"
|
|
140
212
|
* - ctx.effect, ctx.on
|
|
141
213
|
*/
|
|
142
214
|
export const myCounter = define('my-counter')
|
|
@@ -185,25 +257,102 @@ export const myCounter = define('my-counter')
|
|
|
185
257
|
</my-counter>
|
|
186
258
|
```
|
|
187
259
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
See [example/subscribe-form.ts](./example/subscribe-form.ts).
|
|
260
|
+
## Validation
|
|
191
261
|
|
|
192
|
-
There are two separate things people call "validation"
|
|
193
|
-
handles them in different places:
|
|
262
|
+
There are two separate things people call "validation":
|
|
194
263
|
|
|
195
|
-
1. **Prop validation** checks the attributes passed
|
|
264
|
+
1. **Prop validation** checks the attributes passed into a component.
|
|
196
265
|
Pass a [Standard Schema](https://standardschema.dev) (Zod, Valibot,
|
|
197
266
|
ArkType) to [`p.schema`](#withprops); it coerces the attribute value
|
|
198
|
-
and falls back to `undefined` when the value doesn't
|
|
199
|
-
2. **Form input validation** checks live user input
|
|
200
|
-
value that changes on every keystroke
|
|
267
|
+
and falls back to `undefined` when the value doesn't coerce.
|
|
268
|
+
2. **Form input validation** -- client-side validation -- checks live user input
|
|
269
|
+
in a form, for example a value that changes on every keystroke.
|
|
201
270
|
There is no dedicated API for this. Validate the schema directly
|
|
202
|
-
inside `.setup()` and hold the result
|
|
271
|
+
inside `.setup()` and hold the result as a signal.
|
|
272
|
+
|
|
273
|
+
### Prop validation
|
|
274
|
+
|
|
275
|
+
Pass a [Standard Schema](https://standardschema.dev) validator to `p.schema`.
|
|
276
|
+
The validator runs against the raw attribute and its output becomes the
|
|
277
|
+
signal's value; anything it rejects falls back to `undefined`.
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
import { z } from 'zod'
|
|
281
|
+
import { define } from 'microtags'
|
|
282
|
+
|
|
283
|
+
// Any Standard Schema validator works (Zod, Valibot, ArkType). microtags
|
|
284
|
+
// only sees the shared `~standard` interface, so it has no validation
|
|
285
|
+
// library of its own and no dependency on the one you pick.
|
|
286
|
+
const Variant = z.enum(['info', 'success', 'warning', 'danger'])
|
|
287
|
+
|
|
288
|
+
define('status-badge')
|
|
289
|
+
.withProps(p => ({
|
|
290
|
+
// Validates the raw `variant` attribute. A value outside the
|
|
291
|
+
// four options falls back to `undefined`.
|
|
292
|
+
variant: p.schema(Variant),
|
|
293
|
+
}))
|
|
294
|
+
.setup(ctx => {
|
|
295
|
+
ctx.effect(() => {
|
|
296
|
+
const variant = ctx.props.variant() ?? 'info'
|
|
297
|
+
ctx.host.className = `badge badge-${variant}`
|
|
298
|
+
})
|
|
299
|
+
})
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
```html
|
|
303
|
+
<status-badge variant="success">Saved</status-badge>
|
|
304
|
+
|
|
305
|
+
<!-- "purple" is not in the enum, so ctx.props.variant() is undefined -->
|
|
306
|
+
<status-badge variant="purple">Unknown</status-badge>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### `p.schema` vs Standard Schema
|
|
310
|
+
|
|
311
|
+
[Standard Schema](https://standardschema.dev) is a small shared interface
|
|
312
|
+
that Zod, Valibot and ArkType all implement. `p.schema` validates through that
|
|
313
|
+
interface, so microtags ships no validator of its own.
|
|
314
|
+
|
|
315
|
+
A schema prop is attribute-backed and observed, exactly like `p.string()` or
|
|
316
|
+
`p.number()`: editing the attribute re-runs the validator and pushes the result
|
|
317
|
+
to `ctx.props`.
|
|
318
|
+
|
|
319
|
+
The validator's input is the raw attribute string, or `null` when the attribute
|
|
320
|
+
is absent. Because the input is always a string, schemas for non-string values
|
|
321
|
+
must coerce. Use `z.coerce.number()`, not `z.number()`:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
.withProps(p => ({
|
|
325
|
+
// "5" -> 5; "0" and the absent attribute both fall back to undefined
|
|
326
|
+
quantity: p.schema(z.coerce.number().int().min(1)),
|
|
327
|
+
}))
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**On success the signal holds the validator's output value**; on failure it
|
|
331
|
+
falls back to `undefined`. The inferred signal type is the schema's output,
|
|
332
|
+
so guard for the `undefined` case, or fold a default into the schema so it
|
|
333
|
+
never fails:
|
|
203
334
|
|
|
204
|
-
The example below is the second kind -- form input (client-side) validation.
|
|
205
335
|
|
|
206
336
|
```ts
|
|
337
|
+
{
|
|
338
|
+
// always 'info' | 'success' | 'warning' | 'danger', never undefined
|
|
339
|
+
variant: p.schema(Variant.catch('info')),
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Only synchronous schemas are supported; an async schema throws when the
|
|
344
|
+
attribute is validated.
|
|
345
|
+
|
|
346
|
+
-------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
### Client-side validation
|
|
349
|
+
|
|
350
|
+
There is no special API here. Validate input inside `.setup`,
|
|
351
|
+
|
|
352
|
+
See [example/subscribe-form.ts](./example/subscribe-form.ts).
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
// form input (client-side) validation
|
|
207
356
|
import { signal, startBatch, endBatch } from 'alien-signals'
|
|
208
357
|
import { z } from 'zod'
|
|
209
358
|
import { define } from 'microtags'
|
|
@@ -220,18 +369,21 @@ define('subscribe-form')
|
|
|
220
369
|
}))
|
|
221
370
|
.setup(ctx => {
|
|
222
371
|
const value = signal('')
|
|
223
|
-
const touched = signal(false)
|
|
372
|
+
const touched = signal<boolean>(false)
|
|
224
373
|
|
|
225
374
|
// capture the live value (two-way; clears the field on reset)
|
|
226
375
|
ctx.bind(value, ctx.refs.input)
|
|
376
|
+
|
|
227
377
|
// only reveal errors after the first blur
|
|
228
378
|
ctx.on(ctx.refs.input, 'blur', () => touched(true))
|
|
229
379
|
|
|
380
|
+
// ctx.effect is alien-signals/effect with automatic/correct disposal
|
|
230
381
|
ctx.effect(() => {
|
|
382
|
+
// subscribe to `value` and `touched`
|
|
231
383
|
const result = Email.safeParse(value())
|
|
232
|
-
const message = result.success ?
|
|
384
|
+
const message = (result.success ?
|
|
233
385
|
'' :
|
|
234
|
-
result.error.issues[0].message
|
|
386
|
+
result.error.issues[0].message)
|
|
235
387
|
const showError = touched() && !result.success
|
|
236
388
|
|
|
237
389
|
ctx.refs.submit.disabled = !result.success
|
|
@@ -240,6 +392,7 @@ define('subscribe-form')
|
|
|
240
392
|
})
|
|
241
393
|
|
|
242
394
|
ctx.on(ctx.refs.form, 'submit', ev => {
|
|
395
|
+
// subscribe to DOM events
|
|
243
396
|
ev.preventDefault()
|
|
244
397
|
if (!Email.safeParse(value()).success) return
|
|
245
398
|
ctx.refs.status.textContent = 'Thanks for subscribing!'
|
|
@@ -251,14 +404,16 @@ define('subscribe-form')
|
|
|
251
404
|
})
|
|
252
405
|
```
|
|
253
406
|
|
|
254
|
-
The error stays hidden until the field is blurred once, then updates live
|
|
255
|
-
the submit button is
|
|
256
|
-
|
|
407
|
+
The error stays hidden until the field is blurred once, then updates live,
|
|
408
|
+
and the submit button is disabled until valid. See `example/subscribe-form.ts`.
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
---------------------------------------------------------------
|
|
257
412
|
|
|
258
413
|
|
|
259
|
-
----------------------------------------------------------------------
|
|
260
414
|
## Serverside Example
|
|
261
|
-
|
|
415
|
+
|
|
416
|
+
See [./example/ssr.ts](./example/ssr.ts).
|
|
262
417
|
|
|
263
418
|
### `.TAG`
|
|
264
419
|
|
|
@@ -266,11 +421,11 @@ Each tag name that you pass to `define` is exposed as a property `.TAG`
|
|
|
266
421
|
on the return value. It is possible to import your web components in Node,
|
|
267
422
|
even though Node does not have browser APIs.
|
|
268
423
|
|
|
269
|
-
|
|
424
|
+
The `data-ref` attribute is a fallback. It's used when you don't pass a
|
|
425
|
+
selector to `r.one()`. If you pass explicit CSS selectors to `withRefs`,
|
|
426
|
+
e.g. `{ copy: r.one('button') }`, then the HTML does not require a `data-ref`
|
|
427
|
+
attribute.
|
|
270
428
|
|
|
271
|
-
All refs are defined per-component, and the ref names are exposed at `.refs`,
|
|
272
|
-
so you can import your web components on the server and read the ref names
|
|
273
|
-
as strings.
|
|
274
429
|
|
|
275
430
|
```ts
|
|
276
431
|
import { MyCounter, CountBtn, CopyBtn } from './index.js'
|
|
@@ -312,11 +467,58 @@ export function render ():string {
|
|
|
312
467
|
}
|
|
313
468
|
```
|
|
314
469
|
|
|
315
|
-
|
|
470
|
+
### `.refs`
|
|
471
|
+
|
|
472
|
+
Web components created with `microtags` have a property `.refs` that is an
|
|
473
|
+
object of the ref names you created the coponent with. Use it to create
|
|
474
|
+
`data-ref` attributes.
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
import { MyCounter } from '../example.js'
|
|
478
|
+
|
|
479
|
+
export function render () {
|
|
480
|
+
return `
|
|
481
|
+
<div
|
|
482
|
+
data-ref="${MyCounter.refs.display}"
|
|
483
|
+
role="status"
|
|
484
|
+
class="count"
|
|
485
|
+
>${START}</div>
|
|
486
|
+
`
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
---------------------------------------------------------------------
|
|
491
|
+
|
|
492
|
+
|
|
316
493
|
## API
|
|
317
|
-
----------------------------------------------------------------------
|
|
318
494
|
|
|
319
|
-
###
|
|
495
|
+
### `define`
|
|
496
|
+
|
|
497
|
+
```js
|
|
498
|
+
function define (tagName:string):ComponentBuilder
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Function `define(tagName)` returns a [`ComponentBuilder`](#componentbuilder).
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
#### `ComponentBuilder`
|
|
505
|
+
|
|
506
|
+
The object returned by [`define`](#define). Includes methods:
|
|
507
|
+
|
|
508
|
+
| Method | Returns | Description |
|
|
509
|
+
|--------|---------|-------------|
|
|
510
|
+
| `.TAG` | `string` | The tag name passed to `define`. See [`.TAG`](#tag). |
|
|
511
|
+
| `.withProps(fn)` | `ComponentBuilder` | Declare reactive attributes. See [Props](#props). |
|
|
512
|
+
| `.withRefs(fn)` | `ComponentBuilder` | Declare DOM refs resolved once the element connects. See [Refs](#refs-1). |
|
|
513
|
+
| `.withContexts(fn)` | `ComponentBuilder` | Consume context values published by an ancestor. See [`withContexts`](#withcontexts). |
|
|
514
|
+
| `.setup(fn)` | `MicrotagElementClass` | Registers the custom element and returns its class. See [`setup`](#setup) below. |
|
|
515
|
+
|
|
516
|
+
Methods `withProps`, `withRefs`, and `withContexts` each return a new builder,
|
|
517
|
+
so they chain in any order; `setup` ends the chain by registering the element.
|
|
518
|
+
|
|
519
|
+
---------------------------------------------------------
|
|
520
|
+
|
|
521
|
+
### `builder.withProps`
|
|
320
522
|
|
|
321
523
|
Declare reactive attributes via `withProps`. Each prop becomes:
|
|
322
524
|
|
|
@@ -332,8 +534,6 @@ Declare reactive attributes via `withProps`. Each prop becomes:
|
|
|
332
534
|
| `p.boolean()` | `"true"` / `""` → `true`, `"false"` → `false` | `false` |
|
|
333
535
|
| `p.oneOf(opts)` | Picklist enum, throws on invalid | throws |
|
|
334
536
|
|
|
335
|
-
#### `withProps`
|
|
336
|
-
|
|
337
537
|
```ts
|
|
338
538
|
.withProps(p => ({
|
|
339
539
|
count: p.number(), // attribute -> number (NaN when absent)
|
|
@@ -345,7 +545,15 @@ Declare reactive attributes via `withProps`. Each prop becomes:
|
|
|
345
545
|
}))
|
|
346
546
|
```
|
|
347
547
|
|
|
348
|
-
|
|
548
|
+
Inside `.setup()`, every prop is a callable signal:
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
ctx.props.count() // read
|
|
552
|
+
ctx.props.count(42) // write
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
#### `p.json`
|
|
349
557
|
|
|
350
558
|
`p.json<T>()` -- a prop whose attribute value is parsed with
|
|
351
559
|
`JSON.parse`. The type parameter `T` annotates the parsed result; it is a
|
|
@@ -368,6 +576,7 @@ observed, so editing the attribute re-parses and pushes the new value to
|
|
|
368
576
|
attribute is absent, and when the value is not valid JSON. Parse errors are
|
|
369
577
|
swallowed rather than thrown.
|
|
370
578
|
|
|
579
|
+
|
|
371
580
|
```ts
|
|
372
581
|
// check for `undefined` case
|
|
373
582
|
ctx.effect(() => {
|
|
@@ -377,32 +586,46 @@ ctx.effect(() => {
|
|
|
377
586
|
})
|
|
378
587
|
```
|
|
379
588
|
|
|
589
|
+
#### `p.schema`
|
|
590
|
+
|
|
380
591
|
For input you want validated rather than only parsed, pass a Standard Schema
|
|
381
592
|
to [`p.schema`](#withprops) instead.
|
|
382
593
|
|
|
383
|
-
|
|
594
|
+
|
|
595
|
+
---------------------------------------------------------
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
### `builder.withRefs(fn)`
|
|
599
|
+
|
|
600
|
+
Takes a function that gets called with a helpful query selector, `r`.
|
|
601
|
+
|
|
602
|
+
Call `r.one()` / `r.all()` with no argument to match `[data-ref="<key>"]`,
|
|
603
|
+
where `key` is the key name you passed in. Or pass a CSS selector to query by
|
|
604
|
+
it instead. Queries are resolved relative to the host element, during
|
|
605
|
+
[the `connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).
|
|
606
|
+
|
|
607
|
+
All refs returned from this callback are typed as much as possible, and exposed
|
|
608
|
+
on the `context` object passed to the `.setup` function.
|
|
609
|
+
|
|
384
610
|
|
|
385
611
|
```ts
|
|
386
|
-
|
|
387
|
-
|
|
612
|
+
class ComponentBuilder {
|
|
613
|
+
withRefs<R extends Record<string, RefDef>> (
|
|
614
|
+
fn:(r:RefsDSL) => R
|
|
615
|
+
):ComponentBuilder<Props, R, CtxDefs>
|
|
616
|
+
}
|
|
388
617
|
```
|
|
389
618
|
|
|
390
|
-
---
|
|
391
619
|
|
|
392
|
-
|
|
620
|
+
Takes a function that gets called with a [`RefsDSL`](./src/builders.ts#L54)
|
|
621
|
+
object. It should return an object of child elements that you want to keep
|
|
622
|
+
references to.
|
|
393
623
|
|
|
394
624
|
The `data-ref` attribute is a fallback. It's used when you don't pass a
|
|
395
625
|
selector to `r.one()`. If you pass explicit CSS selectors to `withRefs`,
|
|
396
626
|
e.g. `{ copy: r.one('button') }`, then the HTML does not require a `data-ref`
|
|
397
627
|
attribute.
|
|
398
628
|
|
|
399
|
-
#### `withRefs`
|
|
400
|
-
|
|
401
|
-
Call `r.one()` / `r.all()` with no argument to match `[data-ref="<key>"]`,
|
|
402
|
-
where `key` is the key name you pass in, or pass a CSS selector to query by
|
|
403
|
-
it instead. Queries are resolved relative to the host element, during
|
|
404
|
-
[the `connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).
|
|
405
|
-
|
|
406
629
|
|
|
407
630
|
```ts
|
|
408
631
|
.withRefs(r => ({
|
|
@@ -420,67 +643,41 @@ If there are no `data-ref` attribtues in the HTML, use CSS query selectors.
|
|
|
420
643
|
}))
|
|
421
644
|
```
|
|
422
645
|
|
|
423
|
-
See [#refs in serverside-rendering](#refs). Each component created with
|
|
424
|
-
`micortags` exposes a `.refs` property that is an object map
|
|
425
|
-
of ref names, eg `MyComponent.refs.copyButton`.
|
|
426
646
|
|
|
427
|
-
|
|
647
|
+
---------------------------------------------------------------------
|
|
428
648
|
|
|
429
|
-
An un-annotated `r.one()` is typed as `HTMLElement`, so `.style`,
|
|
430
|
-
`.textContent`, and `.dataset` work without a cast. There are two ways to
|
|
431
|
-
get a more specific element type:
|
|
432
649
|
|
|
433
|
-
|
|
434
|
-
.withRefs(r => ({
|
|
435
|
-
// Generic argument: sets the type, keeps the [data-ref] selector.
|
|
436
|
-
display: r.one<HTMLDivElement>(),
|
|
437
|
-
|
|
438
|
-
// Tag string: infers the type AND queries by that tag, exactly like
|
|
439
|
-
// document.querySelector('button'). It does NOT keep [data-ref].
|
|
440
|
-
save: r.one('button'), // SingleRefDef<HTMLButtonElement>
|
|
441
|
-
items: r.all('li'), // MultipleRefDef<HTMLLIElement>
|
|
442
|
-
button: r.one(), // HTMLElement (un-annotated default)
|
|
443
|
-
}))
|
|
444
|
-
```
|
|
650
|
+
### `builder.withContexts`
|
|
445
651
|
|
|
446
|
-
|
|
652
|
+
Context is for the case where a parent component defines some state, e.g. a
|
|
653
|
+
`theme`, and the top-level (application) knows nothing about the theme, but the
|
|
654
|
+
application *does* determine what children the parent renders
|
|
655
|
+
(the app passes in children), and the children need to know the theme.
|
|
447
656
|
|
|
448
657
|
```ts
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
658
|
+
class ComponentBuilder {
|
|
659
|
+
withContexts<C extends Record<string, ContextToken<unknown>>> (
|
|
660
|
+
fn:(c:ContextDSL) => C
|
|
661
|
+
):ComponentBuilder<Props, RefDefs, C>
|
|
662
|
+
}
|
|
453
663
|
```
|
|
454
664
|
|
|
455
|
-
|
|
456
|
-
constrained to `HTMLElement`, so cast at the use site if you need one
|
|
457
|
-
(e.g. `ctx.refs.icon as unknown as SVGSVGElement`).
|
|
458
|
-
|
|
665
|
+
#### `.withContexts` Example
|
|
459
666
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
667
|
+
```html
|
|
668
|
+
<!-- The application composes this markup. -->
|
|
669
|
+
<theme-provider>
|
|
670
|
+
<some-child></some-child>
|
|
671
|
+
<another-child></another-child>
|
|
672
|
+
</theme-provider>
|
|
466
673
|
```
|
|
467
674
|
|
|
468
|
-
Returns a fluent builder. Call `.setup()` to register the custom element.
|
|
469
|
-
All builder methods are chainable and fully type-inferred.
|
|
470
|
-
|
|
471
|
-
### `withContexts`
|
|
472
|
-
|
|
473
|
-
Context is for the case where a parent component defines some state, e.g. a
|
|
474
|
-
`theme`, and the top-level (application) knows nothing about the theme. But the
|
|
475
|
-
top level application does determine what children the parent renders
|
|
476
|
-
(it passes in children), and the children need to know the theme.
|
|
477
|
-
|
|
478
675
|
In that case, there is no way for the "parent" component to pass the theme to
|
|
479
|
-
the children
|
|
676
|
+
the children. The root level (application) doesn't know what the theme is.
|
|
480
677
|
|
|
481
|
-
Context gives us a
|
|
482
|
-
publishes the value, and any descendants opt in
|
|
483
|
-
`withContexts
|
|
678
|
+
Context gives us a way to model this. The context-provider
|
|
679
|
+
publishes the value, and any descendants opt in with
|
|
680
|
+
`withContexts`.
|
|
484
681
|
|
|
485
682
|
>
|
|
486
683
|
> [!IMPORTANT]
|
|
@@ -488,7 +685,6 @@ publishes the value, and any descendants opt in (or pull values) with
|
|
|
488
685
|
> Context gives you nothing for first paint or the no-JS case.
|
|
489
686
|
>
|
|
490
687
|
|
|
491
|
-
|
|
492
688
|
```ts
|
|
493
689
|
import { define } from 'microtags'
|
|
494
690
|
import { createContext, provide } from 'microtags/context'
|
|
@@ -499,7 +695,8 @@ const ThemeToken = createContext<string>()
|
|
|
499
695
|
// not reference the children below. The application composes those in.
|
|
500
696
|
define('theme-provider')
|
|
501
697
|
.setup(ctx => {
|
|
502
|
-
|
|
698
|
+
const rmContext = provide(ctx.host, ThemeToken, 'dark')
|
|
699
|
+
ctx.onCleanup(rmContext)
|
|
503
700
|
})
|
|
504
701
|
|
|
505
702
|
// Child: opts in to the theme and reads whichever provider it is
|
|
@@ -516,82 +713,166 @@ define('themed-card')
|
|
|
516
713
|
```
|
|
517
714
|
|
|
518
715
|
```html
|
|
519
|
-
<!-- The application writes this markup. It nests <themed-card> inside
|
|
520
|
-
<theme-provider> but never sets the theme itself. -->
|
|
521
716
|
<theme-provider>
|
|
522
717
|
<themed-card>Card content</themed-card>
|
|
523
718
|
</theme-provider>
|
|
524
719
|
```
|
|
525
720
|
|
|
526
|
-
`setup()` is deferred until all required contexts resolve. See
|
|
527
|
-
[`microtags/context`](#microtagscontext) for the provider API.
|
|
528
721
|
|
|
529
|
-
|
|
722
|
+
---------------------------------------------------------
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
### `builder.setup(ctx)`
|
|
726
|
+
|
|
727
|
+
Registers the custom element via `customElements.define` and returns the
|
|
728
|
+
element class. The returned class exposes `.TAG` and `.refs` as static
|
|
729
|
+
members, for use in [serverside rendering](#serverside-example).
|
|
530
730
|
|
|
531
731
|
```ts
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
732
|
+
class ComponentBuilder {
|
|
733
|
+
setup (
|
|
734
|
+
fn:(ctx:SetupContext<
|
|
735
|
+
Props,
|
|
736
|
+
RefsMap<RefDefs>,
|
|
737
|
+
ContextsMap<CtxDefs>
|
|
738
|
+
>) => void
|
|
739
|
+
):MicrotagElementClass<RefDefs> {
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
The callback function runs once per element instance, and is called with the
|
|
744
|
+
following `context` object.
|
|
542
745
|
|
|
543
|
-
// Bind a DOM property to a signal expression; auto-cleaned.
|
|
544
|
-
ctx.bind(() => String(ctx.props.count()), el, { prop: 'textContent' })
|
|
545
746
|
|
|
546
|
-
|
|
547
|
-
ctx.on(el, 'click', handler)
|
|
747
|
+
#### `ctx`
|
|
548
748
|
|
|
549
|
-
|
|
550
|
-
// detail/options) for a bubbling CustomEvent, or a pre-built
|
|
551
|
-
// Event. Returns dispatchEvent's result.
|
|
552
|
-
ctx.emit('change', { value: 42 })
|
|
749
|
+
The argument passed to the callback given to `setup`.
|
|
553
750
|
|
|
554
|
-
|
|
555
|
-
|
|
751
|
+
```ts
|
|
752
|
+
ctx:SetupContext<Props, RefsMap<RefDefs>, ContextsMap<CtxDefs>>
|
|
753
|
+
```
|
|
556
754
|
|
|
557
|
-
|
|
558
|
-
ctx.onCleanup(() => { /* ... */ })
|
|
755
|
+
##### `ctx` Methods
|
|
559
756
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
757
|
+
| Method | Returns | Description |
|
|
758
|
+
|--------|---------|-------------|
|
|
759
|
+
| `.peek(signal)` | `T` | Read a signal's current value without subscribing, even inside `ctx.effect`. |
|
|
760
|
+
| `.on(target, type, handler, options?)` | `void` | Add a DOM event listener, auto-removed on disconnect. |
|
|
761
|
+
| `.emit(event)` / `.emit(name, detail?, options?)` | `boolean` | Dispatch a pre-built `Event` as-is, or construct and dispatch a bubbling `CustomEvent` from a name. Returns `dispatchEvent`'s result: `false` if a cancelable event had `preventDefault()` called. |
|
|
762
|
+
| `.effect(fn)` | `void` | Run an alien-signals effect that auto-tracks the signals it reads. Auto-disposed on disconnect. |
|
|
763
|
+
| `.bind(signal, element, options?)` | `void` | Bind a signal to a DOM element property, auto-cleaned on disconnect. |
|
|
764
|
+
| `.onCleanup(fn)` | `void` | Register custom teardown to run on disconnect. |
|
|
765
|
+
| `.consume(token)` | `T \| undefined` | Read an optional context value published by an ancestor. See [`withContexts`](#withcontexts). |
|
|
766
|
+
|
|
767
|
+
See [Typed Events](#typed-events) for more about `.on` and `.emit`.
|
|
768
|
+
|
|
769
|
+
##### `ctx.bind`
|
|
770
|
+
|
|
771
|
+
The `.bind` method treats the signal as the source of truth. The element
|
|
772
|
+
property is set from the signal.
|
|
773
|
+
With no `options`, the control's element property and write-back event are
|
|
774
|
+
auto-detected from its type:
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
{
|
|
778
|
+
bind<T> (
|
|
779
|
+
source:Signal<T>,
|
|
780
|
+
control:Element,
|
|
781
|
+
opts?:BindOptions
|
|
782
|
+
)
|
|
783
|
+
}
|
|
563
784
|
```
|
|
564
785
|
|
|
565
|
-
|
|
786
|
+
| Control | `prop` | write-back `event` |
|
|
787
|
+
|---------|--------|---------------------|
|
|
788
|
+
| `<input type="checkbox">` | `checked` | `change` |
|
|
789
|
+
| `<input type="number">` / `type="range"` | `valueAsNumber` | `input` |
|
|
790
|
+
| Other `<input>` types / `<textarea>` | `value` | `input` |
|
|
791
|
+
| Anything else, e.g. `<select>` | `value` | `change` |
|
|
566
792
|
|
|
567
|
-
|
|
568
|
-
|
|
793
|
+
Passing `options` overrides the auto-detected `prop`, and replaces the
|
|
794
|
+
write-back `event` -- omit `options.event` for a one-way binding. A read-only
|
|
795
|
+
getter (`() => T`) is also accepted as the `signal` argument, for binding a
|
|
796
|
+
derived value one-way (pass it with `options` and omit `event`).
|
|
569
797
|
|
|
570
798
|
```ts
|
|
571
|
-
|
|
799
|
+
// two-way, auto-detected (checkbox -> `checked` / `change`)
|
|
800
|
+
ctx.bind(checked, ctx.refs.toggle)
|
|
572
801
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
802
|
+
// one-way: push a derived value into a read-only display
|
|
803
|
+
ctx.bind(() => `${count()} items`, ctx.refs.label, { prop: 'textContent' })
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
---
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
## Missing refs
|
|
810
|
+
|
|
811
|
+
Any calls to `r.one` in `.withRefs` will throw if there is not a matching
|
|
812
|
+
child element.
|
|
813
|
+
|
|
814
|
+
```
|
|
815
|
+
Error: MY-COMPONENT: missing refs: button, save
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
The tag name is upper case (it comes from `host.tagName`), and the
|
|
819
|
+
message lists every required ref that was not found.
|
|
820
|
+
|
|
821
|
+
Call to `r.all()` are **never reported**. A missing match resolves to an
|
|
822
|
+
empty array, not an error.
|
|
823
|
+
|
|
824
|
+
This check happens right before the custom element runs your `.setup` function.
|
|
825
|
+
|
|
826
|
+
For a component with no contexts, refs are resolved at the moment the element
|
|
827
|
+
connects to the DOM. For a component that declares
|
|
828
|
+
[`withContexts`](#withcontexts), ref collection is deferred along
|
|
829
|
+
with `setup` until every required context resolves.
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
### Checking refs in tests
|
|
833
|
+
|
|
834
|
+
A missing `r.one` ref throws while the component connects. When the browser
|
|
835
|
+
runs a lifecycle callback (which is what `appendChild` triggers), it reports
|
|
836
|
+
the exception to the global `error` event rather than propagating it to the
|
|
837
|
+
caller, so a try / catch around `appendChild` sees nothing.
|
|
838
|
+
|
|
839
|
+
You need to listen for the global `error` event. When every
|
|
840
|
+
`data-ref` the component declares is present, nothing is reported.
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
import { test } from '@substrate-system/tapzero'
|
|
844
|
+
import './my-counter.js' // the component from the example above
|
|
845
|
+
|
|
846
|
+
test('all refs resolve', t => {
|
|
847
|
+
let err:Error|null = null
|
|
848
|
+
const onError = (ev:ErrorEvent) => { err = ev.error }
|
|
849
|
+
window.addEventListener('error', onError)
|
|
850
|
+
|
|
851
|
+
const el = document.createElement('my-counter')
|
|
852
|
+
el.innerHTML = `
|
|
853
|
+
<div data-ref="display"></div>
|
|
854
|
+
<button data-ref="dec">-</button>
|
|
855
|
+
<button data-ref="inc">+</button>`
|
|
856
|
+
document.body.appendChild(el)
|
|
857
|
+
|
|
858
|
+
t.ok(err === null, 'no missing refs')
|
|
859
|
+
|
|
860
|
+
window.removeEventListener('error', onError)
|
|
861
|
+
document.body.removeChild(el)
|
|
578
862
|
})
|
|
579
863
|
```
|
|
580
864
|
|
|
581
|
-
|
|
865
|
+
|
|
866
|
+
## Typed Events
|
|
582
867
|
|
|
583
868
|
`TypedEvent<Target, Detail>` is a type-only helper that narrows
|
|
584
869
|
`CustomEvent` to a specific `target` and `detail`. Combine it with
|
|
585
|
-
`HTMLElementEventMap` augmentation for app-wide type-safe events
|
|
586
|
-
flow through `ctx.emit` and `ctx.on`:
|
|
870
|
+
`HTMLElementEventMap` augmentation for app-wide type-safe events.
|
|
587
871
|
|
|
588
872
|
```ts
|
|
589
873
|
import type { TypedEvent } from 'microtags'
|
|
590
874
|
|
|
591
|
-
type TabsChangedEvent = TypedEvent<
|
|
592
|
-
InstanceType<typeof XTabs>,
|
|
593
|
-
{ index:number }
|
|
594
|
-
>
|
|
875
|
+
type TabsChangedEvent = TypedEvent<InstanceType<typeof XTabs>, { index:number }>
|
|
595
876
|
|
|
596
877
|
declare global {
|
|
597
878
|
interface HTMLElementEventMap {
|
|
@@ -681,6 +962,7 @@ callback function mutates the DOM through properties like `textContent`.
|
|
|
681
962
|
Nothing concatenates strings into markup, so there is no string-injection
|
|
682
963
|
XSS vector.
|
|
683
964
|
|
|
965
|
+
|
|
684
966
|
## Subpath Exports
|
|
685
967
|
|
|
686
968
|
### `microtags/context`
|
|
@@ -751,6 +1033,10 @@ signal imperatively.
|
|
|
751
1033
|
|
|
752
1034
|
### `microtags/render`
|
|
753
1035
|
|
|
1036
|
+
```ts
|
|
1037
|
+
import { render, renderList } from 'microtags/render'
|
|
1038
|
+
```
|
|
1039
|
+
|
|
754
1040
|
A keyed reconciler for rendering lists and single items from a
|
|
755
1041
|
`<template>`, with no virtual DOM and no diffing library.
|
|
756
1042
|
|
|
@@ -878,7 +1164,8 @@ import {
|
|
|
878
1164
|
coerceString,
|
|
879
1165
|
coerceBoolean,
|
|
880
1166
|
coerceJson,
|
|
881
|
-
|
|
1167
|
+
runAll,
|
|
1168
|
+
toAttributes
|
|
882
1169
|
} from 'microtags/util'
|
|
883
1170
|
```
|
|
884
1171
|
|
|
@@ -904,14 +1191,14 @@ function coerceJson<T = unknown> (raw:string | null):T | undefined
|
|
|
904
1191
|
- `coerceJson` -- `JSON.parse(raw)`, or `undefined` when the attribute is
|
|
905
1192
|
absent or not valid JSON. Pass a type parameter to annotate the result.
|
|
906
1193
|
|
|
907
|
-
#### `
|
|
1194
|
+
#### `runAll`
|
|
908
1195
|
|
|
909
1196
|
Runs every function in the array, even if some throw. After all have run
|
|
910
1197
|
it re-throws the first error encountered, so one failing teardown cannot
|
|
911
1198
|
skip the rest. A `Cleanup` is a no-argument teardown function, `() => void`.
|
|
912
1199
|
|
|
913
1200
|
```ts
|
|
914
|
-
function
|
|
1201
|
+
function runAll (fns:Cleanup[]):void
|
|
915
1202
|
```
|
|
916
1203
|
|
|
917
1204
|
#### `toAttributes`
|
|
@@ -939,7 +1226,7 @@ function toAttributes (attrs:Attrs):string
|
|
|
939
1226
|
|
|
940
1227
|
---
|
|
941
1228
|
|
|
942
|
-
##
|
|
1229
|
+
## Differences from `nanotags`
|
|
943
1230
|
|
|
944
1231
|
### Reactivity primitive
|
|
945
1232
|
|
|
@@ -1017,8 +1304,6 @@ These nanotags APIs have no microtags equivalent:
|
|
|
1017
1304
|
|
|
1018
1305
|
- `ctx.getElement` / `ctx.getElements` -- declare refs with `withRefs`, or
|
|
1019
1306
|
call `ctx.host.querySelector` directly.
|
|
1020
|
-
- The `setup` mixin return value -- `setup` returns `void`; it does not
|
|
1021
|
-
assign members back onto the element.
|
|
1022
1307
|
- The `define(name, setupFn)` two-argument shorthand -- always use the
|
|
1023
1308
|
builder chain ending in `.setup()`.
|
|
1024
1309
|
|