@async/framework 0.11.13 → 0.11.15
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/CHANGELOG.md +41 -0
- package/README.md +95 -1
- package/browser.d.ts +42 -2
- package/browser.js +288 -16
- package/browser.min.js +1 -1
- package/browser.ts +288 -16
- package/browser.umd.js +288 -16
- package/browser.umd.min.js +1 -1
- package/framework.d.ts +42 -2
- package/framework.ts +288 -16
- package/package.json +17 -75
- package/server.js +288 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.15 - 2026-06-19
|
|
4
|
+
|
|
5
|
+
- Made the source package private and kept its public surface to the minimal
|
|
6
|
+
export spec for root, `/browser`, `/server`, and `/package.json` only.
|
|
7
|
+
- Moved publish staging to generated `dist/package.json` so npm and release
|
|
8
|
+
automation publish from `dist/` while package consumers still receive
|
|
9
|
+
root-level artifacts without `dist/` paths.
|
|
10
|
+
- Removed legacy direct artifact subpath exports plus top-level
|
|
11
|
+
`main`/`module`/`browser`/`types` and generated file lists from the source
|
|
12
|
+
manifest.
|
|
13
|
+
- Updated pack, size, pipeline, and installed-package coverage to verify the
|
|
14
|
+
browser and server entrypoints remain split after packing.
|
|
15
|
+
- Bundle size from bundled TypeScript source: `browser.ts` 197,173 B raw /
|
|
16
|
+
37,198 B gzip -> `browser.min.js` 84,013 B raw / 24,894 B gzip
|
|
17
|
+
(-113,160 B raw, -12,304 B gzip).
|
|
18
|
+
|
|
19
|
+
## 0.11.14 - 2026-06-18
|
|
20
|
+
|
|
21
|
+
- Added condition-specific root declaration targets so browser-conditioned root
|
|
22
|
+
imports resolve to browser declarations while Node/default root imports keep
|
|
23
|
+
server-capable declarations.
|
|
24
|
+
- Preserved the root browser runtime condition and documented that server-only
|
|
25
|
+
APIs remain on the Node/server entrypoints.
|
|
26
|
+
- Added packed-artifact export-map, declaration/runtime parity, and static
|
|
27
|
+
import checks for root browser, root Node, explicit `/browser`, and explicit
|
|
28
|
+
`/server` entrypoints.
|
|
29
|
+
- Added component-scoped continuous intersection helpers with
|
|
30
|
+
`this.intersect(...)` and `this.on("intersect", options?, fn)`, preserving
|
|
31
|
+
`on:visible` as a one-shot visibility lifecycle hook.
|
|
32
|
+
- Added declarative `on:intersect` with `intersect:threshold`,
|
|
33
|
+
`intersect:root-margin`, and `intersect:once` pseudo-event options, including
|
|
34
|
+
custom `intersect` attribute prefix support.
|
|
35
|
+
- Added observer cleanup coverage for component teardown, boundary swaps,
|
|
36
|
+
fallback scheduling, repeated entries, and existing visible compatibility.
|
|
37
|
+
- Moved root release artifacts to an ignored generated-output workflow:
|
|
38
|
+
tests, bundle checks, pack checks, and generated CI tasks materialize the
|
|
39
|
+
current package surface before verification or publish.
|
|
40
|
+
- Bundle size from bundled TypeScript source: `browser.ts` 197,173 B raw /
|
|
41
|
+
37,198 B gzip -> `browser.min.js` 84,013 B raw / 24,894 B gzip
|
|
42
|
+
(-113,160 B raw, -12,304 B gzip).
|
|
43
|
+
|
|
3
44
|
## 0.11.13 - 2026-06-18
|
|
4
45
|
|
|
5
46
|
- Validated server proxy arguments, default input payloads, and selected signal
|
package/README.md
CHANGED
|
@@ -306,6 +306,9 @@ patches, and browser-cache patches. Async does not ship a component resume graph
|
|
|
306
306
|
For npm consumers, `@async/framework` uses conditional exports: browser-aware
|
|
307
307
|
tooling receives the browser entry, while Node receives the server-capable
|
|
308
308
|
entry. Use explicit subpaths when the target matters.
|
|
309
|
+
The root export also uses condition-specific declarations, so browser-conditioned
|
|
310
|
+
root imports expose the same API as `@async/framework/browser`; server-only APIs
|
|
311
|
+
remain declared on the Node/server entrypoints.
|
|
309
312
|
|
|
310
313
|
```js
|
|
311
314
|
import {
|
|
@@ -607,6 +610,10 @@ Loader scans regular HTML attributes:
|
|
|
607
610
|
| `on:click="server.cart.add(productId)"` | Server command with signal args |
|
|
608
611
|
| `on:attach="setup"` | Component root attach lifecycle pseudo-event |
|
|
609
612
|
| `on:visible="trackView"` | Component root visible lifecycle pseudo-event |
|
|
613
|
+
| `on:intersect="trackSection"` | Continuous intersection lifecycle pseudo-event |
|
|
614
|
+
| `intersect:threshold="0,0.5,1"` | Intersection threshold option for `on:intersect` |
|
|
615
|
+
| `intersect:root-margin="-20% 0px -55% 0px"` | Intersection root margin option for `on:intersect` |
|
|
616
|
+
| `intersect:once="true"` | Disconnect `on:intersect` after the first intersecting entry |
|
|
610
617
|
| `signal:text="product.title"` | Text binding |
|
|
611
618
|
| `signal:value="productId"` | Form value binding with writeback |
|
|
612
619
|
| `signal:attr:disabled="product.$loading"` | Attribute binding |
|
|
@@ -641,6 +648,7 @@ Async.start({
|
|
|
641
648
|
attributes: {
|
|
642
649
|
async: "data-async-",
|
|
643
650
|
class: "data-class-",
|
|
651
|
+
intersect: "data-intersect-",
|
|
644
652
|
signal: "data-signal-",
|
|
645
653
|
on: "data-on-"
|
|
646
654
|
}
|
|
@@ -648,7 +656,8 @@ Async.start({
|
|
|
648
656
|
```
|
|
649
657
|
|
|
650
658
|
That maps to `data-async-container`, `data-on-click="save"`,
|
|
651
|
-
`data-signal-text="product.title"`,
|
|
659
|
+
`data-signal-text="product.title"`, `data-class-selected="selected"`, and
|
|
660
|
+
`data-intersect-threshold="0.5"`.
|
|
652
661
|
|
|
653
662
|
Inside `html` templates, signal refs can be passed directly to binding
|
|
654
663
|
attributes:
|
|
@@ -1099,6 +1108,8 @@ Component helpers:
|
|
|
1099
1108
|
| `this.on(event, fn)` | Fragment lifecycle fallback for `attach`, `visible`, and `destroy` |
|
|
1100
1109
|
| `this.onMount(fn)` | Compatibility alias for `this.on("attach", fn)` |
|
|
1101
1110
|
| `this.onVisible(fn)` | Compatibility alias for `this.on("visible", fn)` |
|
|
1111
|
+
| `this.on("intersect", options?, fn)` | Continuous intersection lifecycle for the mounted component scope |
|
|
1112
|
+
| `this.intersect(element, options?, fn)` | Component-owned continuous intersection observer for a direct element |
|
|
1102
1113
|
|
|
1103
1114
|
`this.suspense(...)` is sugar for Loader boundaries:
|
|
1104
1115
|
`asyncSignal + async:boundary + async:* templates`. It emits only templates. The
|
|
@@ -1173,6 +1184,73 @@ this.on("destroy", () => {
|
|
|
1173
1184
|
the component root first becomes visible. Lifecycle events do not drive
|
|
1174
1185
|
component rerenders.
|
|
1175
1186
|
|
|
1187
|
+
Use `on:intersect` when markup should receive continuous intersection updates
|
|
1188
|
+
through a registered handler:
|
|
1189
|
+
|
|
1190
|
+
```html
|
|
1191
|
+
<section
|
|
1192
|
+
on:intersect="trackSection"
|
|
1193
|
+
intersect:threshold="0,0.25,0.5,0.75,1"
|
|
1194
|
+
intersect:root-margin="-20% 0px -55% 0px"
|
|
1195
|
+
>
|
|
1196
|
+
...
|
|
1197
|
+
</section>
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
The handler receives `element`, `entry`, `entries`, `observer`,
|
|
1201
|
+
`isIntersecting`, `intersectionRatio`, and `unsupported`. Custom roots are not
|
|
1202
|
+
selector-based; use `this.intersect(...)` with a direct root element when a
|
|
1203
|
+
custom observer root is needed.
|
|
1204
|
+
|
|
1205
|
+
Use `this.on("intersect", ...)` when a component needs continuous visibility
|
|
1206
|
+
state:
|
|
1207
|
+
|
|
1208
|
+
```js
|
|
1209
|
+
const Card = defineComponent(function Card() {
|
|
1210
|
+
const visible = this.signal(false);
|
|
1211
|
+
|
|
1212
|
+
this.on("intersect", { threshold: 0.5 }, ({ isIntersecting }) => {
|
|
1213
|
+
visible.set(isIntersecting);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
return html`<article class:visible="${visible}">...</article>`;
|
|
1217
|
+
});
|
|
1218
|
+
```
|
|
1219
|
+
|
|
1220
|
+
Use `this.intersect(...)` with a direct element when a parent owns scroll-spy or
|
|
1221
|
+
active-section state:
|
|
1222
|
+
|
|
1223
|
+
```js
|
|
1224
|
+
const Section = defineComponent(function Section({ id, observeSection }) {
|
|
1225
|
+
const attach = this.handler("attach", function ({ element }) {
|
|
1226
|
+
return observeSection(id, element);
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
return html`<section on:attach="${attach}"><h2>${id}</h2></section>`;
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
const Page = defineComponent(function Page() {
|
|
1233
|
+
const active = this.signal("intro");
|
|
1234
|
+
const ratios = new Map();
|
|
1235
|
+
const options = {
|
|
1236
|
+
rootMargin: "-20% 0px -55% 0px",
|
|
1237
|
+
threshold: [0, 0.25, 0.5, 0.75, 1]
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
const observeSection = (id, element) => this.intersect(element, options, ({ entry }) => {
|
|
1241
|
+
ratios.set(id, entry.isIntersecting ? entry.intersectionRatio : 0);
|
|
1242
|
+
const best = [...ratios.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
1243
|
+
active.set(best?.[0] ?? id);
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
return html`
|
|
1247
|
+
<nav signal:text="${active}"></nav>
|
|
1248
|
+
${this.render(Section, { id: "intro", observeSection })}
|
|
1249
|
+
${this.render(Section, { id: "runtime", observeSection })}
|
|
1250
|
+
`;
|
|
1251
|
+
});
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1176
1254
|
## Streaming
|
|
1177
1255
|
|
|
1178
1256
|
Out-of-order HTML can target a boundary and keep delegated handlers working:
|
|
@@ -1261,6 +1339,8 @@ pnpm run pipeline:github:check
|
|
|
1261
1339
|
Useful commands:
|
|
1262
1340
|
|
|
1263
1341
|
```bash
|
|
1342
|
+
pnpm run bundle
|
|
1343
|
+
pnpm run bundle:clean
|
|
1264
1344
|
pnpm run pipeline:verify
|
|
1265
1345
|
pnpm run pipeline:pages
|
|
1266
1346
|
pnpm run registry:lint
|
|
@@ -1268,6 +1348,20 @@ pnpm run pipeline:release:doctor
|
|
|
1268
1348
|
pnpm run release:check
|
|
1269
1349
|
```
|
|
1270
1350
|
|
|
1351
|
+
Release artifacts such as `browser.js`, `browser.min.js`,
|
|
1352
|
+
`browser.umd.min.js`, `browser.ts`, `browser.d.ts`, `framework.ts`,
|
|
1353
|
+
`framework.d.ts`, and `server.js` are generated into `dist/`. The generated
|
|
1354
|
+
`dist/` directory is the package root for `npm pack` and release publishing, so
|
|
1355
|
+
the published package and CDN surface still expose those files at package root
|
|
1356
|
+
rather than under `dist/`. The source `package.json` stays private and owns the
|
|
1357
|
+
minimal public export spec, while omitting legacy `main`/`module`/`browser`
|
|
1358
|
+
entry fields and generated package file lists. `scripts/build-framework-bundle.js`
|
|
1359
|
+
derives the generated `dist/package.json` and staged artifact names from that
|
|
1360
|
+
spec. Feature branches should edit source files and let `pnpm run bundle`,
|
|
1361
|
+
`pnpm test`, `pnpm run pack:check`, or the generated release workflow
|
|
1362
|
+
materialize the publish tree. Use `pnpm run bundle:clean` to remove local
|
|
1363
|
+
generated artifacts after inspection.
|
|
1364
|
+
|
|
1271
1365
|
`registry:lint` scans package source and examples for declared registry ids
|
|
1272
1366
|
such as signals, handlers, server functions, partials, routes, and components.
|
|
1273
1367
|
It writes `.async/registry-manifest.json` plus a per-file cache at
|
package/browser.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface AttributeConfig {
|
|
|
23
23
|
async?: string | string[];
|
|
24
24
|
class?: string | string[];
|
|
25
25
|
signal?: string | string[];
|
|
26
|
+
intersect?: string | string[];
|
|
26
27
|
on?: string | string[];
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -30,6 +31,7 @@ export interface NormalizedAttributeConfig {
|
|
|
30
31
|
async: string[];
|
|
31
32
|
class: string[];
|
|
32
33
|
signal: string[];
|
|
34
|
+
intersect: string[];
|
|
33
35
|
on: string[];
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -403,7 +405,41 @@ export interface Router {
|
|
|
403
405
|
destroy(): void;
|
|
404
406
|
}
|
|
405
407
|
|
|
406
|
-
export type LifecycleEventName = "attach" | "mount" | "visible" | "destroy";
|
|
408
|
+
export type LifecycleEventName = "attach" | "mount" | "visible" | "intersect" | "destroy";
|
|
409
|
+
|
|
410
|
+
export interface IntersectionFallbackEntry {
|
|
411
|
+
target: Element;
|
|
412
|
+
isIntersecting: boolean;
|
|
413
|
+
intersectionRatio: number;
|
|
414
|
+
time: number;
|
|
415
|
+
rootBounds: DOMRectReadOnly | null;
|
|
416
|
+
boundingClientRect: DOMRect | DOMRectReadOnly | null;
|
|
417
|
+
intersectionRect: DOMRect | DOMRectReadOnly | null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export interface IntersectionEvent {
|
|
421
|
+
target: Element;
|
|
422
|
+
element: Element;
|
|
423
|
+
el: Element;
|
|
424
|
+
root: Document | Element | DocumentFragment;
|
|
425
|
+
entry: IntersectionObserverEntry | IntersectionFallbackEntry;
|
|
426
|
+
entries: Array<IntersectionObserverEntry | IntersectionFallbackEntry>;
|
|
427
|
+
observer: IntersectionObserver | null;
|
|
428
|
+
isIntersecting: boolean;
|
|
429
|
+
intersectionRatio: number;
|
|
430
|
+
unsupported: boolean;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export interface IntersectionOptions {
|
|
434
|
+
root?: Element | Document | null;
|
|
435
|
+
rootMargin?: string;
|
|
436
|
+
threshold?: number | number[];
|
|
437
|
+
once?: boolean;
|
|
438
|
+
schedule?: "lifecycle" | "sync";
|
|
439
|
+
key?: string;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export type IntersectionCallback = (this: ComponentContext, event: IntersectionEvent) => unknown;
|
|
407
443
|
|
|
408
444
|
export interface ComponentContext {
|
|
409
445
|
scope: string;
|
|
@@ -423,9 +459,13 @@ export interface ComponentContext {
|
|
|
423
459
|
handler(name: string, fn: HandlerFunction): string;
|
|
424
460
|
render<TProps extends Record<string, unknown> = Record<string, unknown>>(Child: ComponentFunction<TProps>, props?: TProps): TemplateLike;
|
|
425
461
|
suspense(signalRef: Pick<SignalRef, "id">, views: SuspenseViews | SuspenseReadyView): TemplateLike;
|
|
426
|
-
on(eventName:
|
|
462
|
+
on(eventName: "intersect", fn: IntersectionCallback): void;
|
|
463
|
+
on(eventName: "intersect", options: IntersectionOptions | undefined | null, fn: IntersectionCallback): void;
|
|
464
|
+
on(eventName: Exclude<LifecycleEventName, "intersect">, fn: (this: ComponentContext, target?: Element) => unknown): void;
|
|
427
465
|
onMount(fn: (this: ComponentContext, target?: Element) => unknown): void;
|
|
428
466
|
onVisible(fn: (this: ComponentContext, target?: Element) => unknown): void;
|
|
467
|
+
intersect(target: Element, fn: IntersectionCallback): Cleanup;
|
|
468
|
+
intersect(target: Element, options: IntersectionOptions | undefined | null, fn: IntersectionCallback): Cleanup;
|
|
429
469
|
}
|
|
430
470
|
|
|
431
471
|
export type ComponentFunction<TProps extends Record<string, unknown> = Record<string, unknown>> = (this: ComponentContext, props: TProps) => TemplateLike;
|