@farm.js/islx 0.0.0-stage → 0.0.1

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +198 -2
  4. package/SPEC.md +220 -0
  5. package/dist/_chunks/actions-BCtIGvrP.d.mts +58 -0
  6. package/dist/_chunks/actions-C4dZ1-XD.mjs +615 -0
  7. package/dist/_chunks/actions-CA_K-In7.mjs +310 -0
  8. package/dist/_chunks/actions-Eutk6UzT.mjs +326 -0
  9. package/dist/_chunks/build-COIcs7wm.mjs +9 -0
  10. package/dist/_chunks/codec-BTDkzzPP.mjs +271 -0
  11. package/dist/_chunks/codec-CK8kZp4u.d.mts +64 -0
  12. package/dist/_chunks/handle-C0N9ZyNV.d.mts +35 -0
  13. package/dist/_chunks/hooks-3VZV8f_d.d.mts +30 -0
  14. package/dist/_chunks/hooks-De0gNpOn.d.mts +64 -0
  15. package/dist/_chunks/prehydration-B1GLTLdk.mjs +36 -0
  16. package/dist/_chunks/schedule-BFoGKRrU.mjs +216 -0
  17. package/dist/_chunks/schedule-Dw14dW0H.d.mts +17 -0
  18. package/dist/_chunks/tree-BEV73f_8.mjs +777 -0
  19. package/dist/_chunks/tree-fID6sz25.d.mts +102 -0
  20. package/dist/_chunks/value-store-Dm-7xJ6W.d.mts +19 -0
  21. package/dist/_chunks/wire-C3Rpbwl6.d.mts +98 -0
  22. package/dist/_chunks/wire-CXOodE06.mjs +121 -0
  23. package/dist/client.d.mts +58 -0
  24. package/dist/client.mjs +197 -0
  25. package/dist/compiler.d.mts +2 -0
  26. package/dist/compiler.mjs +1 -0
  27. package/dist/index.d.mts +3 -0
  28. package/dist/index.mjs +3 -0
  29. package/dist/react/client.d.mts +49 -0
  30. package/dist/react/client.mjs +403 -0
  31. package/dist/react/server.d.mts +51 -0
  32. package/dist/react/server.mjs +84 -0
  33. package/dist/react.d.mts +3 -0
  34. package/dist/react.mjs +1 -0
  35. package/dist/server.d.mts +200 -0
  36. package/dist/server.mjs +378 -0
  37. package/dist/vite.d.mts +57 -0
  38. package/dist/vite.mjs +497 -0
  39. package/package.json +121 -4
@@ -0,0 +1,216 @@
1
+ import { a as hasPendingInteraction, o as takePendingInteractions, t as INTERACTION_EVENT } from "./prehydration-B1GLTLdk.mjs";
2
+ import { m as HYDRATED_ATTRIBUTE } from "./wire-CXOodE06.mjs";
3
+ // src/client/schedule.ts
4
+ /**
5
+ * When an island hydrates. Renderer-neutral: every adapter schedules through this.
6
+ *
7
+ * - `load`: as soon as the runtime starts (the default).
8
+ * - `idle`: when the browser is idle, or after 2 seconds.
9
+ * - `visible`: when the island comes within 200px of the viewport.
10
+ * - `interaction`: on the first click, submit, pointerdown or focus inside it. A click or a
11
+ * submit is held while the island's code loads, then replayed on the hydrated island, so the
12
+ * user's first action is not lost.
13
+ *
14
+ * Until it hydrates, an island is its server HTML: links and native forms keep working.
15
+ */
16
+ const ISLAND_STRATEGIES = [
17
+ "load",
18
+ "idle",
19
+ "visible",
20
+ "interaction"
21
+ ];
22
+ function isIslandStrategy(value) {
23
+ return ISLAND_STRATEGIES.includes(value);
24
+ }
25
+ /** Elements whose click is held and replayed rather than merely observed. */
26
+ const REPLAYABLE = "button,[role=\"button\"],input[type=\"button\"],input[type=\"submit\"],input[type=\"reset\"]";
27
+ function whenIdle(callback) {
28
+ const scope = window;
29
+ if (typeof scope.requestIdleCallback === "function") {
30
+ const handle = scope.requestIdleCallback(callback, { timeout: 2e3 });
31
+ return () => scope.cancelIdleCallback?.(handle);
32
+ }
33
+ const handle = window.setTimeout(callback, 1);
34
+ return () => window.clearTimeout(handle);
35
+ }
36
+ const visibility = /* @__PURE__ */ new WeakMap();
37
+ /** One observer per document, shared by every island; targets are released once they fire. */
38
+ function whenVisible(document, targets, start) {
39
+ let registry = visibility.get(document);
40
+ if (!registry) {
41
+ const subscriptions = /* @__PURE__ */ new Map();
42
+ registry = {
43
+ observer: new IntersectionObserver((entries) => {
44
+ const pending = /* @__PURE__ */ new Set();
45
+ for (const entry of entries) if (entry.isIntersecting) for (const callback of subscriptions.get(entry.target) ?? []) pending.add(callback);
46
+ for (const callback of pending) callback();
47
+ }, { rootMargin: "200px" }),
48
+ targets: subscriptions
49
+ };
50
+ visibility.set(document, registry);
51
+ }
52
+ const current = registry;
53
+ for (const target of targets) {
54
+ let callbacks = current.targets.get(target);
55
+ if (!callbacks) {
56
+ callbacks = /* @__PURE__ */ new Set();
57
+ current.targets.set(target, callbacks);
58
+ current.observer.observe(target);
59
+ }
60
+ callbacks.add(start);
61
+ }
62
+ let active = true;
63
+ return () => {
64
+ if (!active) return;
65
+ active = false;
66
+ for (const target of targets) {
67
+ const callbacks = current.targets.get(target);
68
+ callbacks?.delete(start);
69
+ if (callbacks?.size === 0) {
70
+ current.targets.delete(target);
71
+ current.observer.unobserve(target);
72
+ }
73
+ }
74
+ if (current.targets.size === 0) {
75
+ current.observer.disconnect();
76
+ visibility.delete(document);
77
+ }
78
+ };
79
+ }
80
+ /**
81
+ * Replays a held interaction. `requestSubmit()` dispatches a real submit event, so handlers bound
82
+ * during hydration see it and validation runs; `form.submit()` would bypass both.
83
+ */
84
+ function replay({ target, kind }) {
85
+ if (kind === "submit") {
86
+ const form = target;
87
+ if (typeof form.requestSubmit === "function") form.requestSubmit();
88
+ else target.dispatchEvent(new Event("submit", {
89
+ bubbles: true,
90
+ cancelable: true
91
+ }));
92
+ return;
93
+ }
94
+ const clickable = target;
95
+ if (typeof clickable.click === "function") clickable.click();
96
+ else target.dispatchEvent(new MouseEvent("click", {
97
+ bubbles: true,
98
+ cancelable: true
99
+ }));
100
+ }
101
+ function finish(container, held) {
102
+ const interactions = /* @__PURE__ */ new Map();
103
+ if (held) interactions.set(held.target, held);
104
+ for (const pending of takePendingInteractions(container)) if (pending.kind) interactions.set(pending.target, {
105
+ target: pending.target,
106
+ kind: pending.kind
107
+ });
108
+ if (!container.isConnected) return;
109
+ container.setAttribute(HYDRATED_ATTRIBUTE, "");
110
+ for (const interaction of interactions.values()) window.setTimeout(() => {
111
+ if (container.isConnected && interaction.target.isConnected && container.contains(interaction.target)) replay(interaction);
112
+ }, 0);
113
+ }
114
+ /** Runs `hydrate` when the island's strategy says; resolves with its result, or undefined if aborted. */
115
+ function scheduleHydration({ container, strategy, signal, hydrate }) {
116
+ const resolved = strategy ?? "load";
117
+ if (signal?.aborted) return Promise.resolve(void 0);
118
+ if (resolved === "load") return Promise.resolve().then(() => signal?.aborted ? void 0 : hydrate()).then((value) => {
119
+ if (signal?.aborted) return void 0;
120
+ finish(container, null);
121
+ return value;
122
+ });
123
+ return new Promise((resolve, reject) => {
124
+ let started = false;
125
+ let cancelled = false;
126
+ let held = null;
127
+ const triggers = /* @__PURE__ */ new Set();
128
+ const captures = /* @__PURE__ */ new Set();
129
+ let removeAbort = null;
130
+ const cleanupTriggers = () => {
131
+ for (const dispose of triggers) dispose();
132
+ triggers.clear();
133
+ };
134
+ const cleanup = () => {
135
+ cleanupTriggers();
136
+ for (const dispose of captures) dispose();
137
+ captures.clear();
138
+ removeAbort?.();
139
+ removeAbort = null;
140
+ };
141
+ const start = () => {
142
+ if (started || cancelled) return;
143
+ started = true;
144
+ cleanupTriggers();
145
+ Promise.resolve().then(hydrate).then((value) => {
146
+ cleanup();
147
+ if (cancelled || signal?.aborted) return;
148
+ finish(container, held);
149
+ resolve(value);
150
+ }, (error) => {
151
+ cleanup();
152
+ reject(error);
153
+ });
154
+ };
155
+ if (signal) {
156
+ const abort = () => {
157
+ cancelled = true;
158
+ cleanup();
159
+ resolve(void 0);
160
+ };
161
+ signal.addEventListener("abort", abort, { once: true });
162
+ removeAbort = () => signal.removeEventListener("abort", abort);
163
+ }
164
+ if (hasPendingInteraction(container)) return start();
165
+ const onAnnounced = (event) => {
166
+ const detail = event.detail;
167
+ if (detail?.target instanceof Element && container.contains(detail.target)) start();
168
+ };
169
+ document.addEventListener(INTERACTION_EVENT, onAnnounced);
170
+ captures.add(() => document.removeEventListener(INTERACTION_EVENT, onAnnounced));
171
+ if (resolved === "idle") {
172
+ triggers.add(whenIdle(start));
173
+ return;
174
+ }
175
+ if (resolved === "visible") {
176
+ if (typeof IntersectionObserver !== "function") return start();
177
+ const targets = container.getClientRects().length > 0 ? [container] : Array.from(container.children);
178
+ if (targets.length === 0) return start();
179
+ triggers.add(whenVisible(container.ownerDocument, targets, start));
180
+ return;
181
+ }
182
+ const hold = (event, target, kind) => {
183
+ held ??= {
184
+ target,
185
+ kind
186
+ };
187
+ event.preventDefault();
188
+ event.stopImmediatePropagation();
189
+ start();
190
+ };
191
+ const onClick = (event) => {
192
+ const mouse = event;
193
+ const target = event.target instanceof Element ? event.target.closest(REPLAYABLE) : null;
194
+ if (!target || !container.contains(target) || target.closest("a[href]") || mouse.button !== 0 || mouse.metaKey || mouse.altKey || mouse.ctrlKey || mouse.shiftKey) return;
195
+ hold(event, target, "click");
196
+ };
197
+ const onSubmit = (event) => {
198
+ const form = event.target;
199
+ if (!(form instanceof Element) || form.nodeName !== "FORM") return;
200
+ if (!container.contains(form) || event.defaultPrevented) return;
201
+ hold(event, form, "submit");
202
+ };
203
+ const onActivate = (event) => {
204
+ if (event.target instanceof Element && container.contains(event.target)) start();
205
+ };
206
+ const listen = (type, handler) => {
207
+ document.addEventListener(type, handler, true);
208
+ captures.add(() => document.removeEventListener(type, handler, true));
209
+ };
210
+ listen("click", onClick);
211
+ listen("submit", onSubmit);
212
+ listen("pointerdown", onActivate);
213
+ listen("focusin", onActivate);
214
+ });
215
+ }
216
+ export { isIslandStrategy as n, scheduleHydration as r, ISLAND_STRATEGIES as t };
@@ -0,0 +1,17 @@
1
+ type IslandStrategy = "load" | "idle" | "visible" | "interaction";
2
+ declare const ISLAND_STRATEGIES: readonly IslandStrategy[];
3
+ declare function isIslandStrategy(value: unknown): value is IslandStrategy;
4
+ interface ScheduleOptions<T> {
5
+ container: Element;
6
+ strategy?: IslandStrategy | null;
7
+ signal?: AbortSignal;
8
+ hydrate(): T | Promise<T>;
9
+ }
10
+ /** Runs `hydrate` when the island's strategy says; resolves with its result, or undefined if aborted. */
11
+ declare function scheduleHydration<T>({
12
+ container,
13
+ strategy,
14
+ signal,
15
+ hydrate
16
+ }: ScheduleOptions<T>): Promise<T | undefined>;
17
+ export { scheduleHydration as a, isIslandStrategy as i, IslandStrategy as n, ScheduleOptions as r, ISLAND_STRATEGIES as t };