microtags 0.0.1 → 0.0.3
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 +206 -43
- package/dist/chunk-7QVYU63E.js +7 -0
- package/dist/chunk-7QVYU63E.js.map +7 -0
- package/dist/chunk-CCYWRJW6.js +98 -0
- package/dist/chunk-CCYWRJW6.js.map +7 -0
- package/dist/chunk-E7ZGOHL3.js +51 -0
- package/dist/chunk-E7ZGOHL3.js.map +7 -0
- package/dist/context.cjs +10 -4
- package/dist/context.cjs.map +3 -3
- package/dist/context.js +7 -63
- package/dist/context.js.map +3 -3
- package/dist/index.cjs +416 -5
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +345 -2
- package/dist/index.js.map +3 -3
- package/dist/meta.json +276 -26
- package/dist/render.cjs +6 -4
- package/dist/render.cjs.map +1 -1
- package/dist/render.js +9 -6
- package/dist/render.js.map +1 -1
- package/dist/util.cjs +68 -0
- package/dist/util.cjs.map +7 -0
- package/dist/util.js +16 -0
- package/dist/util.js.map +7 -0
- package/dist/util.min.js +2 -0
- package/dist/util.min.js.map +7 -0
- package/package.json +26 -14
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# microtags
|
|
2
2
|
|
|
3
|
-
](https://github.com/nichoth/microtags/actions/workflows/nodejs.yml)
|
|
4
4
|
[](README.md)
|
|
5
5
|
[](README.md)
|
|
6
6
|
[](https://semver.org/)
|
|
@@ -13,6 +13,8 @@ Web component helper with reactive props, automatic cleanup,
|
|
|
13
13
|
and no Shadow DOM. Reactive via
|
|
14
14
|
[alien-signals](https://github.com/stackblitz/alien-signals).
|
|
15
15
|
|
|
16
|
+
**Size**: 3.41 kB with all dependencies, minified and brotlied
|
|
17
|
+
|
|
16
18
|
Inspired by [nanotags](https://nanotags.psdcoder.dev/). Reactive props use
|
|
17
19
|
**alien-signals** instead of nanostores atoms.
|
|
18
20
|
|
|
@@ -21,15 +23,23 @@ Inspired by [nanotags](https://nanotags.psdcoder.dev/). Reactive props use
|
|
|
21
23
|
<!-- toc -->
|
|
22
24
|
|
|
23
25
|
- [Install](#install)
|
|
26
|
+
* [Import map](#import-map)
|
|
27
|
+
* [Pre-bundled](#pre-bundled)
|
|
24
28
|
- [Example](#example)
|
|
29
|
+
* [Client-side validation](#client-side-validation)
|
|
25
30
|
- [Serverside Example](#serverside-example)
|
|
31
|
+
* [`.TAG`](#tag)
|
|
32
|
+
* [`.refs`](#refs)
|
|
26
33
|
- [API](#api)
|
|
27
34
|
* [Props](#props)
|
|
35
|
+
+ [`withProps`](#withprops)
|
|
36
|
+
* [Refs](#refs)
|
|
37
|
+
+ [`withRefs`](#withrefs)
|
|
38
|
+
+ [Element types](#element-types)
|
|
28
39
|
* [define](#define)
|
|
29
|
-
* [withProps](#withprops)
|
|
30
|
-
* [withRefs](#withrefs)
|
|
31
40
|
* [withContexts](#withcontexts)
|
|
32
41
|
* [setup and ctx](#setup-and-ctx)
|
|
42
|
+
+ [peek and untracked](#peek-and-untracked)
|
|
33
43
|
- [Subpaths](#subpaths)
|
|
34
44
|
* [microtags/context](#microtagscontext)
|
|
35
45
|
* [microtags/render](#microtagsrender)
|
|
@@ -39,12 +49,55 @@ Inspired by [nanotags](https://nanotags.psdcoder.dev/). Reactive props use
|
|
|
39
49
|
|
|
40
50
|
</details>
|
|
41
51
|
|
|
52
|
+
----------------------------------------------------------------------
|
|
42
53
|
## Install
|
|
54
|
+
----------------------------------------------------------------------
|
|
43
55
|
|
|
44
56
|
```sh
|
|
45
57
|
npm i -S microtags
|
|
46
58
|
```
|
|
47
59
|
|
|
60
|
+
### Import map
|
|
61
|
+
|
|
62
|
+
No build step needed. Resolve `microtags` with an
|
|
63
|
+
[import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap)
|
|
64
|
+
and `esm.sh`.
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<script type="importmap">
|
|
69
|
+
{
|
|
70
|
+
"imports": {
|
|
71
|
+
"microtags": "https://esm.sh/microtags"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<script type="module">
|
|
77
|
+
import { define } from 'microtags'
|
|
78
|
+
</script>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Subpaths follow the same URL pattern: `microtags/context`, `microtags/render`.
|
|
82
|
+
|
|
83
|
+
### Pre-bundled
|
|
84
|
+
|
|
85
|
+
For raw-file CDNs (jsDelivr, unpkg) or local files, where dependencies are
|
|
86
|
+
not resolved for you, use the pre-bundled `microtags/min` build. It is a
|
|
87
|
+
single self-contained file with `alien-signals` inlined, so no other
|
|
88
|
+
mappings are needed:
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<script type="importmap">
|
|
92
|
+
{
|
|
93
|
+
"imports": {
|
|
94
|
+
"microtags": "https://cdn.jsdelivr.net/npm/microtags/dist/index.min.js"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
</script>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
|
|
48
101
|
----------------------------------------------------------------------
|
|
49
102
|
## Example
|
|
50
103
|
----------------------------------------------------------------------
|
|
@@ -54,13 +107,13 @@ to do client-side-rendered components, that is possible. You would update the
|
|
|
54
107
|
DOM inside `.setup`.
|
|
55
108
|
|
|
56
109
|
Since the full HTML exists already, defining your components in the
|
|
57
|
-
client-side JS is about "hydrating" the page
|
|
110
|
+
client-side JS is about "hydrating" the page (adding behavior).
|
|
58
111
|
|
|
59
112
|
---
|
|
60
113
|
|
|
61
114
|
`withProps`, `withRefs`, and `withContexts` are optional and can appear in
|
|
62
|
-
any order. setup ends the chain: it calls `customElements.define` under the
|
|
63
|
-
and returns a typed constructor.
|
|
115
|
+
any order. `.setup` ends the chain: it calls `customElements.define` under the
|
|
116
|
+
hood and returns a typed constructor.
|
|
64
117
|
|
|
65
118
|
```ts
|
|
66
119
|
import { define } from 'microtags'
|
|
@@ -117,36 +170,130 @@ export const myCounter = define('my-counter')
|
|
|
117
170
|
</my-counter>
|
|
118
171
|
```
|
|
119
172
|
|
|
173
|
+
### Client-side validation
|
|
174
|
+
|
|
175
|
+
See [example/subscribe-form.ts](./example/subscribe-form.ts).
|
|
176
|
+
|
|
177
|
+
There are two separate things people call "validation", and `microtags`
|
|
178
|
+
handles them in different places:
|
|
179
|
+
|
|
180
|
+
1. **Prop validation** checks the attributes passed *into* a component.
|
|
181
|
+
Pass a [Standard Schema](https://standardschema.dev) (Zod, Valibot,
|
|
182
|
+
ArkType) to [`p.schema`](#withprops); it coerces the attribute value
|
|
183
|
+
and falls back to `undefined` when the value doesn't match.
|
|
184
|
+
2. **Form input validation** checks live user input in a form, for example a
|
|
185
|
+
value that changes on every keystroke and needs to report *why* it is invalid.
|
|
186
|
+
There is no dedicated API for this. Validate the schema directly
|
|
187
|
+
inside `.setup()` and hold the result in a signal:
|
|
188
|
+
|
|
189
|
+
The example below is the second kind -- form input (client-side) validation.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { signal, startBatch, endBatch } from 'alien-signals'
|
|
193
|
+
import { z } from 'zod'
|
|
194
|
+
import { define } from 'microtags'
|
|
195
|
+
|
|
196
|
+
const Email = z.email('Please enter a valid email address.')
|
|
197
|
+
|
|
198
|
+
define('subscribe-form')
|
|
199
|
+
.withRefs(r => ({
|
|
200
|
+
form: r.one('form'),
|
|
201
|
+
input: r.one('input'),
|
|
202
|
+
submit: r.one('button'),
|
|
203
|
+
error: r.one('.error'),
|
|
204
|
+
status: r.one('.status'),
|
|
205
|
+
}))
|
|
206
|
+
.setup(ctx => {
|
|
207
|
+
const value = signal('')
|
|
208
|
+
const touched = signal(false)
|
|
209
|
+
|
|
210
|
+
// capture the live value (two-way; clears the field on reset)
|
|
211
|
+
ctx.bind(value, ctx.refs.input)
|
|
212
|
+
// only reveal errors after the first blur
|
|
213
|
+
ctx.on(ctx.refs.input, 'blur', () => touched(true))
|
|
214
|
+
|
|
215
|
+
ctx.effect(() => {
|
|
216
|
+
const result = Email.safeParse(value())
|
|
217
|
+
const message = result.success ?
|
|
218
|
+
'' :
|
|
219
|
+
result.error.issues[0].message
|
|
220
|
+
const showError = touched() && !result.success
|
|
221
|
+
|
|
222
|
+
ctx.refs.submit.disabled = !result.success
|
|
223
|
+
ctx.refs.input.setAttribute('aria-invalid', String(showError))
|
|
224
|
+
ctx.refs.error.textContent = showError ? message : ''
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
ctx.on(ctx.refs.form, 'submit', ev => {
|
|
228
|
+
ev.preventDefault()
|
|
229
|
+
if (!Email.safeParse(value()).success) return
|
|
230
|
+
ctx.refs.status.textContent = 'Thanks for subscribing!'
|
|
231
|
+
startBatch()
|
|
232
|
+
value('')
|
|
233
|
+
touched(false)
|
|
234
|
+
endBatch()
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The error stays hidden until the field is blurred once, then updates live;
|
|
240
|
+
the submit button is gated on validity. See `example/subscribe-form.ts`
|
|
241
|
+
for the full working component.
|
|
242
|
+
|
|
120
243
|
|
|
121
244
|
----------------------------------------------------------------------
|
|
122
245
|
## Serverside Example
|
|
123
246
|
----------------------------------------------------------------------
|
|
124
247
|
|
|
248
|
+
### `.TAG`
|
|
249
|
+
|
|
125
250
|
Each tag name that you pass to `define` is exposed as a property `.TAG`
|
|
126
251
|
on the return value. It is possible to import your web components in Node,
|
|
127
252
|
even though Node does not have browser APIs.
|
|
128
253
|
|
|
254
|
+
### `.refs`
|
|
255
|
+
|
|
256
|
+
All refs are defined per-component, and the ref names are exposed at `.refs`,
|
|
257
|
+
so you can import your web components on the server and read the ref names
|
|
258
|
+
as strings.
|
|
259
|
+
|
|
129
260
|
```ts
|
|
130
|
-
import {
|
|
261
|
+
import { MyCounter, CountBtn, CopyBtn } from './index.js'
|
|
131
262
|
|
|
132
263
|
// this runs in Node
|
|
133
264
|
|
|
134
265
|
export function render ():string {
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
266
|
+
return `<div>
|
|
267
|
+
<${MyCounter.TAG} start="${START}">
|
|
268
|
+
<div data-ref="${MyCounter.refs.display}"
|
|
269
|
+
role="status"
|
|
270
|
+
class="count"
|
|
271
|
+
>${START}</div>
|
|
139
272
|
|
|
140
273
|
<div class="controls">
|
|
141
|
-
<${
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
274
|
+
<${CountBtn.TAG}
|
|
275
|
+
class="dec"
|
|
276
|
+
data-ref="${MyCounter.refs.dec}"
|
|
277
|
+
aria-label="Decrement"
|
|
278
|
+
>
|
|
279
|
+
<button>-</button>
|
|
280
|
+
</${CountBtn.TAG}>
|
|
281
|
+
|
|
282
|
+
<${CopyBtn.TAG}
|
|
283
|
+
class="copy"
|
|
284
|
+
data-ref="${MyCounter.refs.copy}"
|
|
285
|
+
></${CopyBtn.TAG}>
|
|
286
|
+
|
|
287
|
+
<${CountBtn.TAG}
|
|
288
|
+
class="inc"
|
|
289
|
+
data-ref="${MyCounter.refs.inc}"
|
|
290
|
+
aria-label="Increment"
|
|
291
|
+
>
|
|
292
|
+
<button>+</button>
|
|
293
|
+
</${CountBtn.TAG}>
|
|
146
294
|
</div>
|
|
147
|
-
</${
|
|
148
|
-
</div
|
|
149
|
-
`
|
|
295
|
+
</${MyCounter.TAG}>
|
|
296
|
+
</div>`
|
|
150
297
|
}
|
|
151
298
|
```
|
|
152
299
|
|
|
@@ -156,7 +303,7 @@ export function render ():string {
|
|
|
156
303
|
|
|
157
304
|
### Props
|
|
158
305
|
|
|
159
|
-
Declare reactive attributes via withProps
|
|
306
|
+
Declare reactive attributes via `withProps`. Each prop becomes:
|
|
160
307
|
|
|
161
308
|
* An observed HTML attribute (auto-synced via `attributeChangedCallback`)
|
|
162
309
|
* A signal at `ctx.props.propName`
|
|
@@ -170,27 +317,17 @@ Declare reactive attributes via withProps. Each prop becomes:
|
|
|
170
317
|
| `p.boolean()` | `"true"` / `""` → `true`, `"false"` → `false` | `false` |
|
|
171
318
|
| `p.oneOf(opts)` | Picklist enum, throws on invalid | throws |
|
|
172
319
|
|
|
173
|
-
### define
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
import { define } from 'microtags'
|
|
177
320
|
|
|
178
|
-
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Returns a fluent builder. Call `.setup()` to register the custom element.
|
|
182
|
-
All builder methods are chainable and fully type-inferred.
|
|
183
|
-
|
|
184
|
-
### withProps
|
|
321
|
+
#### `withProps`
|
|
185
322
|
|
|
186
323
|
```ts
|
|
187
324
|
.withProps(p => ({
|
|
188
|
-
count: p.number(),
|
|
189
|
-
label: p.string(),
|
|
190
|
-
active: p.boolean(),
|
|
191
|
-
data: p.json<MyType>(),
|
|
192
|
-
schema: p.schema(zodSchema), // Standard Schema (Zod, Valibot, ArkType
|
|
193
|
-
value: p.prop(0),
|
|
325
|
+
count: p.number(), // attribute -> number (NaN when absent)
|
|
326
|
+
label: p.string(), // attribute -> string ('' when absent)
|
|
327
|
+
active: p.boolean(), // attribute -> boolean (true if present)
|
|
328
|
+
data: p.json<MyType>(), // attribute -> JSON.parse or undefined
|
|
329
|
+
schema: p.schema(zodSchema), // Standard Schema (Zod, Valibot, ArkType)
|
|
330
|
+
value: p.prop(0), // property-only, not an observed attribute
|
|
194
331
|
}))
|
|
195
332
|
```
|
|
196
333
|
|
|
@@ -201,7 +338,21 @@ ctx.props.count() // read
|
|
|
201
338
|
ctx.props.count(42) // write
|
|
202
339
|
```
|
|
203
340
|
|
|
204
|
-
|
|
341
|
+
|
|
342
|
+
### Refs
|
|
343
|
+
|
|
344
|
+
The `data-ref` attribute is a fallback. It's used when you don't pass a
|
|
345
|
+
selector to `r.one()`. If you pass explicit CSS selectors to `withRefs`,
|
|
346
|
+
e.g. `{ copy: r.one('button') }`, then the HTML does not require a `data-ref`
|
|
347
|
+
attribute.
|
|
348
|
+
|
|
349
|
+
#### `withRefs`
|
|
350
|
+
|
|
351
|
+
Call `r.one()` / `r.all()` with no argument to match `[data-ref="<key>"]`,
|
|
352
|
+
where `key` is the key name you pass in, or pass a CSS selector to query by
|
|
353
|
+
it instead. Queries are resolved relative to the host element, during
|
|
354
|
+
[the `connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).
|
|
355
|
+
|
|
205
356
|
|
|
206
357
|
```ts
|
|
207
358
|
.withRefs(r => ({
|
|
@@ -210,9 +361,7 @@ ctx.props.count(42) // write
|
|
|
210
361
|
}))
|
|
211
362
|
```
|
|
212
363
|
|
|
213
|
-
|
|
214
|
-
or pass a CSS selector to query by it instead — resolved relative to the
|
|
215
|
-
host element:
|
|
364
|
+
If there are no `data-ref` attribtues in the HTML, use CSS query selectors.
|
|
216
365
|
|
|
217
366
|
```ts
|
|
218
367
|
.withRefs(r => ({
|
|
@@ -221,7 +370,9 @@ host element:
|
|
|
221
370
|
}))
|
|
222
371
|
```
|
|
223
372
|
|
|
224
|
-
|
|
373
|
+
See [#refs in serverside-rendering](#refs). Each component created with
|
|
374
|
+
`micortags` exposes a `.refs` property that is an object map
|
|
375
|
+
of ref names, eg `MyComponent.refs.copyButton`.
|
|
225
376
|
|
|
226
377
|
#### Element types
|
|
227
378
|
|
|
@@ -255,6 +406,18 @@ Non-HTML refs (SVG / MathML) are out of scope: the type parameter is
|
|
|
255
406
|
constrained to `HTMLElement`, so cast at the use site if you need one
|
|
256
407
|
(e.g. `ctx.refs.icon as unknown as SVGSVGElement`).
|
|
257
408
|
|
|
409
|
+
|
|
410
|
+
### define
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
import { define } from 'microtags'
|
|
414
|
+
|
|
415
|
+
define(tagName: string): ComponentBuilder
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Returns a fluent builder. Call `.setup()` to register the custom element.
|
|
419
|
+
All builder methods are chainable and fully type-inferred.
|
|
420
|
+
|
|
258
421
|
### withContexts
|
|
259
422
|
|
|
260
423
|
```ts
|
|
@@ -376,5 +539,5 @@ Props declared with `withProps` are native alien-signals callable signals.
|
|
|
376
539
|
There is no `$` prefix convention — all prop names are plain identifiers.
|
|
377
540
|
|
|
378
541
|
```
|
|
379
|
-
/ed3d-plan-and-execute:execute-implementation-plan /Users/nick/code/
|
|
542
|
+
/ed3d-plan-and-execute:execute-implementation-plan /Users/nick/code/_microtags/docs/implementation-plans/2026-06-28-zod-validation-example/ /Users/nick/code/_microtags/
|
|
380
543
|
```
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "./chunk-7QVYU63E.js";
|
|
4
|
+
|
|
5
|
+
// src/reactivity.ts
|
|
6
|
+
import {
|
|
7
|
+
signal,
|
|
8
|
+
effect,
|
|
9
|
+
effectScope,
|
|
10
|
+
setActiveSub
|
|
11
|
+
} from "alien-signals";
|
|
12
|
+
function untracked(fn) {
|
|
13
|
+
const prev = setActiveSub(void 0);
|
|
14
|
+
try {
|
|
15
|
+
return fn();
|
|
16
|
+
} finally {
|
|
17
|
+
setActiveSub(prev);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
__name(untracked, "untracked");
|
|
21
|
+
function peek(sig) {
|
|
22
|
+
return untracked(() => sig());
|
|
23
|
+
}
|
|
24
|
+
__name(peek, "peek");
|
|
25
|
+
|
|
26
|
+
// src/context.ts
|
|
27
|
+
var providerStorage = /* @__PURE__ */ new WeakMap();
|
|
28
|
+
var pendingCallbacks = /* @__PURE__ */ new Map();
|
|
29
|
+
function createContext() {
|
|
30
|
+
return { id: /* @__PURE__ */ Symbol("microtag-context") };
|
|
31
|
+
}
|
|
32
|
+
__name(createContext, "createContext");
|
|
33
|
+
function provide(element, token, value) {
|
|
34
|
+
let map = providerStorage.get(element);
|
|
35
|
+
if (!map) {
|
|
36
|
+
map = /* @__PURE__ */ new Map();
|
|
37
|
+
providerStorage.set(element, map);
|
|
38
|
+
}
|
|
39
|
+
const existing = map.get(token.id);
|
|
40
|
+
if (existing) {
|
|
41
|
+
existing(value);
|
|
42
|
+
} else {
|
|
43
|
+
const sig = signal(value);
|
|
44
|
+
map.set(token.id, sig);
|
|
45
|
+
}
|
|
46
|
+
notifyPending(token.id);
|
|
47
|
+
return () => {
|
|
48
|
+
const m = providerStorage.get(element);
|
|
49
|
+
if (m) {
|
|
50
|
+
m.delete(token.id);
|
|
51
|
+
if (m.size === 0) providerStorage.delete(element);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
__name(provide, "provide");
|
|
56
|
+
function resolveContextSignal(element, token) {
|
|
57
|
+
let current = element.parentElement;
|
|
58
|
+
while (current) {
|
|
59
|
+
const map = providerStorage.get(current);
|
|
60
|
+
if (map && map.has(token.id)) {
|
|
61
|
+
return map.get(token.id);
|
|
62
|
+
}
|
|
63
|
+
current = current.parentElement;
|
|
64
|
+
}
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
__name(resolveContextSignal, "resolveContextSignal");
|
|
68
|
+
function onContextAvailable(id, callback) {
|
|
69
|
+
let set = pendingCallbacks.get(id);
|
|
70
|
+
if (!set) {
|
|
71
|
+
set = /* @__PURE__ */ new Set();
|
|
72
|
+
pendingCallbacks.set(id, set);
|
|
73
|
+
}
|
|
74
|
+
set.add(callback);
|
|
75
|
+
return () => {
|
|
76
|
+
pendingCallbacks.get(id)?.delete(callback);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
__name(onContextAvailable, "onContextAvailable");
|
|
80
|
+
function notifyPending(id) {
|
|
81
|
+
const set = pendingCallbacks.get(id);
|
|
82
|
+
if (set) {
|
|
83
|
+
for (const cb of set) cb();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
__name(notifyPending, "notifyPending");
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
signal,
|
|
90
|
+
effect,
|
|
91
|
+
untracked,
|
|
92
|
+
peek,
|
|
93
|
+
createContext,
|
|
94
|
+
provide,
|
|
95
|
+
resolveContextSignal,
|
|
96
|
+
onContextAvailable
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=chunk-CCYWRJW6.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/reactivity.ts", "../src/context.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n signal,\n effect,\n effectScope,\n setActiveSub,\n} from 'alien-signals'\nimport type { Signal } from './types.js'\n\n// Re-export alien-signals primitives under stable names for internal use.\nexport { signal, effect, effectScope }\n\n/**\n * Run fn without tracking any signals it reads.\n * Suspends the current subscriber via setActiveSub(undefined),\n * restoring it in a finally block so nested effects are unaffected.\n */\nexport function untracked<T> (fn:() => T):T {\n const prev = setActiveSub(undefined)\n try {\n return fn()\n } finally {\n setActiveSub(prev)\n }\n}\n\n/**\n * Read a signal's current value without subscribing.\n * Equivalent to untracked(() => sig()).\n */\nexport function peek<T> (sig:Signal<T>):T {\n return untracked(() => sig())\n}\n", "import { signal } from './reactivity.js'\nimport type { ContextToken, Signal } from './types.js'\n\n// Provider storage: element -> (contextId -> Signal)\nconst providerStorage = new WeakMap<Element, Map<symbol, Signal<unknown>>>()\n\n// Pending consumers waiting for a context by id\nconst pendingCallbacks = new Map<symbol, Set<() => void>>()\n\n/**\n * Create a new context token.\n * Each token uniquely identifies a type of shared context.\n */\nexport function createContext<T> ():ContextToken<T> {\n return { id: Symbol('microtag-context') }\n}\n\n/**\n * Provide a context value from an element to its descendants.\n * Returns a cleanup function that removes the provided value.\n */\nexport function provide<T> (\n element:Element,\n token:ContextToken<T>,\n value:T\n):() => void {\n let map = providerStorage.get(element)\n if (!map) {\n map = new Map()\n providerStorage.set(element, map)\n }\n\n const existing = map.get(token.id) as Signal<T> | undefined\n if (existing) {\n existing(value)\n } else {\n const sig = signal(value) as unknown as Signal<T>\n map.set(token.id, sig as Signal<unknown>)\n }\n\n notifyPending(token.id)\n\n return () => {\n const m = providerStorage.get(element)\n if (m) {\n m.delete(token.id)\n if (m.size === 0) providerStorage.delete(element)\n }\n }\n}\n\n/**\n * Walk up the DOM from element to find the nearest provider signal.\n * Returns the signal (not the value) so callers can subscribe to changes.\n */\nexport function resolveContextSignal<T> (\n element:Element,\n token:ContextToken<T>\n):Signal<T> | undefined {\n let current:Element | null = element.parentElement\n while (current) {\n const map = providerStorage.get(current)\n if (map && map.has(token.id)) {\n return map.get(token.id) as Signal<T>\n }\n current = current.parentElement\n }\n return undefined\n}\n\n/**\n * Register a callback to run when the given context becomes available.\n * Returns a cleanup function to unregister.\n */\nexport function onContextAvailable (\n id:symbol,\n callback:() => void\n):() => void {\n let set = pendingCallbacks.get(id)\n if (!set) {\n set = new Set()\n pendingCallbacks.set(id, set)\n }\n set.add(callback)\n return () => { pendingCallbacks.get(id)?.delete(callback) }\n}\n\nfunction notifyPending (id:symbol):void {\n const set = pendingCallbacks.get(id)\n if (set) {\n for (const cb of set) cb()\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;AAAA;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAWA,SAAS,UAAc,IAAc;AACxC,QAAM,OAAO,aAAa,MAAS;AACnC,MAAI;AACA,WAAO,GAAG;AAAA,EACd,UAAE;AACE,iBAAa,IAAI;AAAA,EACrB;AACJ;AAPgB;AAaT,SAAS,KAAS,KAAiB;AACtC,SAAO,UAAU,MAAM,IAAI,CAAC;AAChC;AAFgB;;;ACzBhB,IAAM,kBAAkB,oBAAI,QAA+C;AAG3E,IAAM,mBAAmB,oBAAI,IAA6B;AAMnD,SAAS,gBAAoC;AAChD,SAAO,EAAE,IAAI,uBAAO,kBAAkB,EAAE;AAC5C;AAFgB;AAQT,SAAS,QACZ,SACA,OACA,OACS;AACT,MAAI,MAAM,gBAAgB,IAAI,OAAO;AACrC,MAAI,CAAC,KAAK;AACN,UAAM,oBAAI,IAAI;AACd,oBAAgB,IAAI,SAAS,GAAG;AAAA,EACpC;AAEA,QAAM,WAAW,IAAI,IAAI,MAAM,EAAE;AACjC,MAAI,UAAU;AACV,aAAS,KAAK;AAAA,EAClB,OAAO;AACH,UAAM,MAAM,OAAO,KAAK;AACxB,QAAI,IAAI,MAAM,IAAI,GAAsB;AAAA,EAC5C;AAEA,gBAAc,MAAM,EAAE;AAEtB,SAAO,MAAM;AACT,UAAM,IAAI,gBAAgB,IAAI,OAAO;AACrC,QAAI,GAAG;AACH,QAAE,OAAO,MAAM,EAAE;AACjB,UAAI,EAAE,SAAS,EAAG,iBAAgB,OAAO,OAAO;AAAA,IACpD;AAAA,EACJ;AACJ;AA5BgB;AAkCT,SAAS,qBACZ,SACA,OACoB;AACpB,MAAI,UAAyB,QAAQ;AACrC,SAAO,SAAS;AACZ,UAAM,MAAM,gBAAgB,IAAI,OAAO;AACvC,QAAI,OAAO,IAAI,IAAI,MAAM,EAAE,GAAG;AAC1B,aAAO,IAAI,IAAI,MAAM,EAAE;AAAA,IAC3B;AACA,cAAU,QAAQ;AAAA,EACtB;AACA,SAAO;AACX;AAbgB;AAmBT,SAAS,mBACZ,IACA,UACS;AACT,MAAI,MAAM,iBAAiB,IAAI,EAAE;AACjC,MAAI,CAAC,KAAK;AACN,UAAM,oBAAI,IAAI;AACd,qBAAiB,IAAI,IAAI,GAAG;AAAA,EAChC;AACA,MAAI,IAAI,QAAQ;AAChB,SAAO,MAAM;AAAE,qBAAiB,IAAI,EAAE,GAAG,OAAO,QAAQ;AAAA,EAAE;AAC9D;AAXgB;AAahB,SAAS,cAAe,IAAgB;AACpC,QAAM,MAAM,iBAAiB,IAAI,EAAE;AACnC,MAAI,KAAK;AACL,eAAW,MAAM,IAAK,IAAG;AAAA,EAC7B;AACJ;AALS;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "./chunk-7QVYU63E.js";
|
|
4
|
+
|
|
5
|
+
// src/util.ts
|
|
6
|
+
function coerceNumber(raw) {
|
|
7
|
+
return raw === null ? NaN : Number(raw);
|
|
8
|
+
}
|
|
9
|
+
__name(coerceNumber, "coerceNumber");
|
|
10
|
+
function coerceString(raw) {
|
|
11
|
+
return raw ?? "";
|
|
12
|
+
}
|
|
13
|
+
__name(coerceString, "coerceString");
|
|
14
|
+
function coerceBoolean(raw) {
|
|
15
|
+
return raw !== null;
|
|
16
|
+
}
|
|
17
|
+
__name(coerceBoolean, "coerceBoolean");
|
|
18
|
+
function coerceJson(raw) {
|
|
19
|
+
if (raw === null) return void 0;
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
} catch {
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
__name(coerceJson, "coerceJson");
|
|
27
|
+
function runCleanups(fns) {
|
|
28
|
+
let firstError;
|
|
29
|
+
let caught = false;
|
|
30
|
+
for (const fn of fns) {
|
|
31
|
+
try {
|
|
32
|
+
fn();
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (!caught) {
|
|
35
|
+
firstError = err;
|
|
36
|
+
caught = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (caught) throw firstError;
|
|
41
|
+
}
|
|
42
|
+
__name(runCleanups, "runCleanups");
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
coerceNumber,
|
|
46
|
+
coerceString,
|
|
47
|
+
coerceBoolean,
|
|
48
|
+
coerceJson,
|
|
49
|
+
runCleanups
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=chunk-E7ZGOHL3.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/util.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Cleanup } from './types.js'\n\n/**\n * Coerce a raw attribute string to a number.\n * Returns NaN if the value cannot be parsed.\n */\nexport function coerceNumber (raw:string | null):number {\n return raw === null ? NaN : Number(raw)\n}\n\n/**\n * Coerce a raw attribute string to a string.\n * Converts null to empty string.\n */\nexport function coerceString (raw:string | null):string {\n return raw ?? ''\n}\n\n/**\n * Coerce a raw attribute string to a boolean.\n * Absence (null) is false; presence of any value (including '') is true.\n */\nexport function coerceBoolean (raw:string | null):boolean {\n return raw !== null\n}\n\n/**\n * Coerce a raw attribute string to a parsed JSON value.\n * Returns undefined if the value is null or cannot be parsed.\n */\nexport function coerceJson<T = unknown> (raw:string | null):T | undefined {\n if (raw === null) return undefined\n try {\n return JSON.parse(raw) as T\n } catch {\n return undefined\n }\n}\n\n/**\n * Run every cleanup in the list, ensuring all run even if some throw.\n * After all have run, re-throws the first error encountered.\n */\nexport function runCleanups (fns:Cleanup[]):void {\n let firstError:unknown\n let caught = false\n for (const fn of fns) {\n try {\n fn()\n } catch (err) {\n if (!caught) {\n firstError = err\n caught = true\n }\n }\n }\n if (caught) throw firstError\n}\n"],
|
|
5
|
+
"mappings": ";;;;;AAMO,SAAS,aAAc,KAA0B;AACpD,SAAO,QAAQ,OAAO,MAAM,OAAO,GAAG;AAC1C;AAFgB;AAQT,SAAS,aAAc,KAA0B;AACpD,SAAO,OAAO;AAClB;AAFgB;AAQT,SAAS,cAAe,KAA2B;AACtD,SAAO,QAAQ;AACnB;AAFgB;AAQT,SAAS,WAAyB,KAAiC;AACtE,MAAI,QAAQ,KAAM,QAAO;AACzB,MAAI;AACA,WAAO,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAPgB;AAaT,SAAS,YAAa,KAAoB;AAC7C,MAAI;AACJ,MAAI,SAAS;AACb,aAAW,MAAM,KAAK;AAClB,QAAI;AACA,SAAG;AAAA,IACP,SAAS,KAAK;AACV,UAAI,CAAC,QAAQ;AACT,qBAAa;AACb,iBAAS;AAAA,MACb;AAAA,IACJ;AAAA,EACJ;AACA,MAAI,OAAQ,OAAM;AACtB;AAdgB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/context.cjs
CHANGED
|
@@ -17,6 +17,8 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
19
19
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/context.ts
|
|
20
22
|
var context_exports = {};
|
|
21
23
|
__export(context_exports, {
|
|
22
24
|
createContext: () => createContext,
|
|
@@ -25,9 +27,13 @@ __export(context_exports, {
|
|
|
25
27
|
resolveContextSignal: () => resolveContextSignal
|
|
26
28
|
});
|
|
27
29
|
module.exports = __toCommonJS(context_exports);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
|
|
31
|
+
// src/reactivity.ts
|
|
32
|
+
var import_alien_signals = require("alien-signals");
|
|
33
|
+
|
|
34
|
+
// src/context.ts
|
|
35
|
+
var providerStorage = /* @__PURE__ */ new WeakMap();
|
|
36
|
+
var pendingCallbacks = /* @__PURE__ */ new Map();
|
|
31
37
|
function createContext() {
|
|
32
38
|
return { id: /* @__PURE__ */ Symbol("microtag-context") };
|
|
33
39
|
}
|
|
@@ -42,7 +48,7 @@ function provide(element, token, value) {
|
|
|
42
48
|
if (existing) {
|
|
43
49
|
existing(value);
|
|
44
50
|
} else {
|
|
45
|
-
const sig = (0,
|
|
51
|
+
const sig = (0, import_alien_signals.signal)(value);
|
|
46
52
|
map.set(token.id, sig);
|
|
47
53
|
}
|
|
48
54
|
notifyPending(token.id);
|
package/dist/context.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/context.ts"],
|
|
4
|
-
"sourcesContent": ["import { signal } from './reactivity.js'\nimport type { ContextToken, Signal } from './types.js'\n\n// Provider storage: element -> (contextId -> Signal)\nconst providerStorage = new WeakMap<Element, Map<symbol, Signal<unknown>>>()\n\n// Pending consumers waiting for a context by id\nconst pendingCallbacks = new Map<symbol, Set<() => void>>()\n\n/**\n * Create a new context token.\n * Each token uniquely identifies a type of shared context.\n */\nexport function createContext<T> ():ContextToken<T> {\n return { id: Symbol('microtag-context') }\n}\n\n/**\n * Provide a context value from an element to its descendants.\n * Returns a cleanup function that removes the provided value.\n */\nexport function provide<T> (\n element:Element,\n token:ContextToken<T>,\n value:T\n):() => void {\n let map = providerStorage.get(element)\n if (!map) {\n map = new Map()\n providerStorage.set(element, map)\n }\n\n const existing = map.get(token.id) as Signal<T> | undefined\n if (existing) {\n existing(value)\n } else {\n const sig = signal(value) as unknown as Signal<T>\n map.set(token.id, sig as Signal<unknown>)\n }\n\n notifyPending(token.id)\n\n return () => {\n const m = providerStorage.get(element)\n if (m) {\n m.delete(token.id)\n if (m.size === 0) providerStorage.delete(element)\n }\n }\n}\n\n/**\n * Walk up the DOM from element to find the nearest provider signal.\n * Returns the signal (not the value) so callers can subscribe to changes.\n */\nexport function resolveContextSignal<T> (\n element:Element,\n token:ContextToken<T>\n):Signal<T> | undefined {\n let current:Element | null = element.parentElement\n while (current) {\n const map = providerStorage.get(current)\n if (map && map.has(token.id)) {\n return map.get(token.id) as Signal<T>\n }\n current = current.parentElement\n }\n return undefined\n}\n\n/**\n * Register a callback to run when the given context becomes available.\n * Returns a cleanup function to unregister.\n */\nexport function onContextAvailable (\n id:symbol,\n callback:() => void\n):() => void {\n let set = pendingCallbacks.get(id)\n if (!set) {\n set = new Set()\n pendingCallbacks.set(id, set)\n }\n set.add(callback)\n return () => { pendingCallbacks.get(id)?.delete(callback) }\n}\n\nfunction notifyPending (id:symbol):void {\n const set = pendingCallbacks.get(id)\n if (set) {\n for (const cb of set) cb()\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
3
|
+
"sources": ["../src/context.ts", "../src/reactivity.ts"],
|
|
4
|
+
"sourcesContent": ["import { signal } from './reactivity.js'\nimport type { ContextToken, Signal } from './types.js'\n\n// Provider storage: element -> (contextId -> Signal)\nconst providerStorage = new WeakMap<Element, Map<symbol, Signal<unknown>>>()\n\n// Pending consumers waiting for a context by id\nconst pendingCallbacks = new Map<symbol, Set<() => void>>()\n\n/**\n * Create a new context token.\n * Each token uniquely identifies a type of shared context.\n */\nexport function createContext<T> ():ContextToken<T> {\n return { id: Symbol('microtag-context') }\n}\n\n/**\n * Provide a context value from an element to its descendants.\n * Returns a cleanup function that removes the provided value.\n */\nexport function provide<T> (\n element:Element,\n token:ContextToken<T>,\n value:T\n):() => void {\n let map = providerStorage.get(element)\n if (!map) {\n map = new Map()\n providerStorage.set(element, map)\n }\n\n const existing = map.get(token.id) as Signal<T> | undefined\n if (existing) {\n existing(value)\n } else {\n const sig = signal(value) as unknown as Signal<T>\n map.set(token.id, sig as Signal<unknown>)\n }\n\n notifyPending(token.id)\n\n return () => {\n const m = providerStorage.get(element)\n if (m) {\n m.delete(token.id)\n if (m.size === 0) providerStorage.delete(element)\n }\n }\n}\n\n/**\n * Walk up the DOM from element to find the nearest provider signal.\n * Returns the signal (not the value) so callers can subscribe to changes.\n */\nexport function resolveContextSignal<T> (\n element:Element,\n token:ContextToken<T>\n):Signal<T> | undefined {\n let current:Element | null = element.parentElement\n while (current) {\n const map = providerStorage.get(current)\n if (map && map.has(token.id)) {\n return map.get(token.id) as Signal<T>\n }\n current = current.parentElement\n }\n return undefined\n}\n\n/**\n * Register a callback to run when the given context becomes available.\n * Returns a cleanup function to unregister.\n */\nexport function onContextAvailable (\n id:symbol,\n callback:() => void\n):() => void {\n let set = pendingCallbacks.get(id)\n if (!set) {\n set = new Set()\n pendingCallbacks.set(id, set)\n }\n set.add(callback)\n return () => { pendingCallbacks.get(id)?.delete(callback) }\n}\n\nfunction notifyPending (id:symbol):void {\n const set = pendingCallbacks.get(id)\n if (set) {\n for (const cb of set) cb()\n }\n}\n", "import {\n signal,\n effect,\n effectScope,\n setActiveSub,\n} from 'alien-signals'\nimport type { Signal } from './types.js'\n\n// Re-export alien-signals primitives under stable names for internal use.\nexport { signal, effect, effectScope }\n\n/**\n * Run fn without tracking any signals it reads.\n * Suspends the current subscriber via setActiveSub(undefined),\n * restoring it in a finally block so nested effects are unaffected.\n */\nexport function untracked<T> (fn:() => T):T {\n const prev = setActiveSub(undefined)\n try {\n return fn()\n } finally {\n setActiveSub(prev)\n }\n}\n\n/**\n * Read a signal's current value without subscribing.\n * Equivalent to untracked(() => sig()).\n */\nexport function peek<T> (sig:Signal<T>):T {\n return untracked(() => sig())\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,2BAKO;;;ADDP,IAAM,kBAAkB,oBAAI,QAA+C;AAG3E,IAAM,mBAAmB,oBAAI,IAA6B;AAMnD,SAAS,gBAAoC;AAChD,SAAO,EAAE,IAAI,uBAAO,kBAAkB,EAAE;AAC5C;AAFgB;AAQT,SAAS,QACZ,SACA,OACA,OACS;AACT,MAAI,MAAM,gBAAgB,IAAI,OAAO;AACrC,MAAI,CAAC,KAAK;AACN,UAAM,oBAAI,IAAI;AACd,oBAAgB,IAAI,SAAS,GAAG;AAAA,EACpC;AAEA,QAAM,WAAW,IAAI,IAAI,MAAM,EAAE;AACjC,MAAI,UAAU;AACV,aAAS,KAAK;AAAA,EAClB,OAAO;AACH,UAAM,UAAM,6BAAO,KAAK;AACxB,QAAI,IAAI,MAAM,IAAI,GAAsB;AAAA,EAC5C;AAEA,gBAAc,MAAM,EAAE;AAEtB,SAAO,MAAM;AACT,UAAM,IAAI,gBAAgB,IAAI,OAAO;AACrC,QAAI,GAAG;AACH,QAAE,OAAO,MAAM,EAAE;AACjB,UAAI,EAAE,SAAS,EAAG,iBAAgB,OAAO,OAAO;AAAA,IACpD;AAAA,EACJ;AACJ;AA5BgB;AAkCT,SAAS,qBACZ,SACA,OACoB;AACpB,MAAI,UAAyB,QAAQ;AACrC,SAAO,SAAS;AACZ,UAAM,MAAM,gBAAgB,IAAI,OAAO;AACvC,QAAI,OAAO,IAAI,IAAI,MAAM,EAAE,GAAG;AAC1B,aAAO,IAAI,IAAI,MAAM,EAAE;AAAA,IAC3B;AACA,cAAU,QAAQ;AAAA,EACtB;AACA,SAAO;AACX;AAbgB;AAmBT,SAAS,mBACZ,IACA,UACS;AACT,MAAI,MAAM,iBAAiB,IAAI,EAAE;AACjC,MAAI,CAAC,KAAK;AACN,UAAM,oBAAI,IAAI;AACd,qBAAiB,IAAI,IAAI,GAAG;AAAA,EAChC;AACA,MAAI,IAAI,QAAQ;AAChB,SAAO,MAAM;AAAE,qBAAiB,IAAI,EAAE,GAAG,OAAO,QAAQ;AAAA,EAAE;AAC9D;AAXgB;AAahB,SAAS,cAAe,IAAgB;AACpC,QAAM,MAAM,iBAAiB,IAAI,EAAE;AACnC,MAAI,KAAK;AACL,eAAW,MAAM,IAAK,IAAG;AAAA,EAC7B;AACJ;AALS;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|