@async/framework 0.11.14 → 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 CHANGED
@@ -1,5 +1,21 @@
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
+
3
19
  ## 0.11.14 - 2026-06-18
4
20
 
5
21
  - Added condition-specific root declaration targets so browser-conditioned root
@@ -10,9 +26,20 @@
10
26
  - Added packed-artifact export-map, declaration/runtime parity, and static
11
27
  import checks for root browser, root Node, explicit `/browser`, and explicit
12
28
  `/server` entrypoints.
13
- - Bundle size from bundled TypeScript source: `browser.ts` 187,564 B raw /
14
- 35,332 B gzip -> `browser.min.js` 80,009 B raw / 23,677 B gzip
15
- (-107,555 B raw, -11,655 B gzip).
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).
16
43
 
17
44
  ## 0.11.13 - 2026-06-18
18
45
 
package/README.md CHANGED
@@ -610,6 +610,10 @@ Loader scans regular HTML attributes:
610
610
  | `on:click="server.cart.add(productId)"` | Server command with signal args |
611
611
  | `on:attach="setup"` | Component root attach lifecycle pseudo-event |
612
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 |
613
617
  | `signal:text="product.title"` | Text binding |
614
618
  | `signal:value="productId"` | Form value binding with writeback |
615
619
  | `signal:attr:disabled="product.$loading"` | Attribute binding |
@@ -644,6 +648,7 @@ Async.start({
644
648
  attributes: {
645
649
  async: "data-async-",
646
650
  class: "data-class-",
651
+ intersect: "data-intersect-",
647
652
  signal: "data-signal-",
648
653
  on: "data-on-"
649
654
  }
@@ -651,7 +656,8 @@ Async.start({
651
656
  ```
652
657
 
653
658
  That maps to `data-async-container`, `data-on-click="save"`,
654
- `data-signal-text="product.title"`, and `data-class-selected="selected"`.
659
+ `data-signal-text="product.title"`, `data-class-selected="selected"`, and
660
+ `data-intersect-threshold="0.5"`.
655
661
 
656
662
  Inside `html` templates, signal refs can be passed directly to binding
657
663
  attributes:
@@ -1102,6 +1108,8 @@ Component helpers:
1102
1108
  | `this.on(event, fn)` | Fragment lifecycle fallback for `attach`, `visible`, and `destroy` |
1103
1109
  | `this.onMount(fn)` | Compatibility alias for `this.on("attach", fn)` |
1104
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 |
1105
1113
 
1106
1114
  `this.suspense(...)` is sugar for Loader boundaries:
1107
1115
  `asyncSignal + async:boundary + async:* templates`. It emits only templates. The
@@ -1176,6 +1184,73 @@ this.on("destroy", () => {
1176
1184
  the component root first becomes visible. Lifecycle events do not drive
1177
1185
  component rerenders.
1178
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
+
1179
1254
  ## Streaming
1180
1255
 
1181
1256
  Out-of-order HTML can target a boundary and keep delegated handlers working:
@@ -1264,6 +1339,8 @@ pnpm run pipeline:github:check
1264
1339
  Useful commands:
1265
1340
 
1266
1341
  ```bash
1342
+ pnpm run bundle
1343
+ pnpm run bundle:clean
1267
1344
  pnpm run pipeline:verify
1268
1345
  pnpm run pipeline:pages
1269
1346
  pnpm run registry:lint
@@ -1271,6 +1348,20 @@ pnpm run pipeline:release:doctor
1271
1348
  pnpm run release:check
1272
1349
  ```
1273
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
+
1274
1365
  `registry:lint` scans package source and examples for declared registry ids
1275
1366
  such as signals, handlers, server functions, partials, routes, and components.
1276
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: LifecycleEventName, fn: (this: ComponentContext, target?: Element) => unknown): void;
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;