@b9g/crank 0.7.1 → 0.7.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 +540 -84
- package/_css.js.map +1 -1
- package/_utils.js.map +1 -1
- package/async.cjs +42 -38
- package/async.cjs.map +1 -1
- package/async.d.ts +10 -7
- package/async.js +42 -38
- package/async.js.map +1 -1
- package/crank.cjs +50 -8
- package/crank.cjs.map +1 -1
- package/crank.js +50 -8
- package/crank.js.map +1 -1
- package/dom.cjs +16 -1
- package/dom.cjs.map +1 -1
- package/dom.js +16 -1
- package/dom.js.map +1 -1
- package/package.json +2 -1
- package/umd.js +66 -9
- package/umd.js.map +1 -1
package/_css.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_css.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"_css.js","sources":["../src/_css.ts"],"sourcesContent":["/**\n * CSS utility functions for style property transformation.\n *\n * This module handles camelCase to kebab-case conversion and automatic\n * px unit conversion for numeric CSS values, making Crank more React-compatible.\n */\n\n/**\n * Converts camelCase CSS property names to kebab-case.\n * Handles vendor prefixes correctly (WebkitTransform -> -webkit-transform).\n */\nexport function camelToKebabCase(str: string): string {\n\t// Handle vendor prefixes that start with capital letters (WebkitTransform -> -webkit-transform)\n\tif (/^[A-Z]/.test(str)) {\n\t\treturn `-${str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`).slice(1)}`;\n\t}\n\t// Handle normal camelCase (fontSize -> font-size)\n\treturn str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);\n}\n\n/**\n * CSS properties that should remain unitless when given numeric values.\n * Based on React's list of unitless properties.\n */\nexport const UNITLESS_PROPERTIES = new Set([\n\t\"animation-iteration-count\",\n\t\"aspect-ratio\",\n\t\"border-image-outset\",\n\t\"border-image-slice\",\n\t\"border-image-width\",\n\t\"box-flex\",\n\t\"box-flex-group\",\n\t\"box-ordinal-group\",\n\t\"column-count\",\n\t\"columns\",\n\t\"flex\",\n\t\"flex-grow\",\n\t\"flex-positive\",\n\t\"flex-shrink\",\n\t\"flex-negative\",\n\t\"flex-order\",\n\t\"font-weight\",\n\t\"grid-area\",\n\t\"grid-column\",\n\t\"grid-column-end\",\n\t\"grid-column-span\",\n\t\"grid-column-start\",\n\t\"grid-row\",\n\t\"grid-row-end\",\n\t\"grid-row-span\",\n\t\"grid-row-start\",\n\t\"line-height\",\n\t\"opacity\",\n\t\"order\",\n\t\"orphans\",\n\t\"tab-size\",\n\t\"widows\",\n\t\"z-index\",\n\t\"zoom\",\n]);\n\n/**\n * Formats CSS property values, automatically adding \"px\" to numeric values\n * for properties that are not unitless.\n */\nexport function formatStyleValue(name: string, value: unknown): string {\n\tif (typeof value === \"number\") {\n\t\t// If the property should remain unitless, keep the number as-is\n\t\tif (UNITLESS_PROPERTIES.has(name)) {\n\t\t\treturn String(value);\n\t\t}\n\t\t// Otherwise, append \"px\" for numeric values\n\t\treturn `${value}px`;\n\t}\n\treturn String(value);\n}\n"],"names":[],"mappings":"AAAA;;;;;AAKG;AAEH;;;AAGG;AACG,SAAU,gBAAgB,CAAC,GAAW,EAAA;;AAE3C,IAAA,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACvB,OAAO,CAAA,CAAA,EAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAE,CAAA,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA,CAAE;;;AAGlF,IAAA,OAAO,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAA,CAAE,CAAC;AACnE;AAEA;;;AAGG;AACU,MAAA,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAC1C,2BAA2B;IAC3B,cAAc;IACd,qBAAqB;IACrB,oBAAoB;IACpB,oBAAoB;IACpB,UAAU;IACV,gBAAgB;IAChB,mBAAmB;IACnB,cAAc;IACd,SAAS;IACT,MAAM;IACN,WAAW;IACX,eAAe;IACf,aAAa;IACb,eAAe;IACf,YAAY;IACZ,aAAa;IACb,WAAW;IACX,aAAa;IACb,iBAAiB;IACjB,kBAAkB;IAClB,mBAAmB;IACnB,UAAU;IACV,cAAc;IACd,eAAe;IACf,gBAAgB;IAChB,aAAa;IACb,SAAS;IACT,OAAO;IACP,SAAS;IACT,UAAU;IACV,QAAQ;IACR,SAAS;IACT,MAAM;AACN,CAAA;AAED;;;AAGG;AACa,SAAA,gBAAgB,CAAC,IAAY,EAAE,KAAc,EAAA;AAC5D,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;;AAE9B,QAAA,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;AAClC,YAAA,OAAO,MAAM,CAAC,KAAK,CAAC;;;QAGrB,OAAO,CAAA,EAAG,KAAK,CAAA,EAAA,CAAI;;AAEpB,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB;;;;"}
|
package/_utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_utils.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"_utils.js","sources":["../src/_utils.ts"],"sourcesContent":["export function wrap<T>(value: Array<T> | T | undefined): Array<T> {\n\treturn value === undefined ? [] : Array.isArray(value) ? value : [value];\n}\n\nexport function unwrap<T>(arr: Array<T>): Array<T> | T | undefined {\n\treturn arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;\n}\n\nexport type NonStringIterable<T> = Iterable<T> & object;\n\n/**\n * Ensures a value is an array.\n *\n * This function does the same thing as wrap() above except it handles nulls\n * and iterables, so it is appropriate for wrapping user-provided element\n * children.\n */\nexport function arrayify<T>(\n\tvalue: NonStringIterable<T> | T | null | undefined,\n): Array<T> {\n\treturn value == null\n\t\t? []\n\t\t: Array.isArray(value)\n\t\t\t? value\n\t\t\t: typeof value === \"string\" ||\n\t\t\t\t typeof (value as any)[Symbol.iterator] !== \"function\"\n\t\t\t\t? [value as T]\n\t\t\t\t: [...(value as NonStringIterable<T>)];\n}\n\nexport function isIteratorLike(\n\tvalue: any,\n): value is Iterator<unknown> | AsyncIterator<unknown> {\n\treturn value != null && typeof value.next === \"function\";\n}\n\nexport function isPromiseLike(value: any): value is PromiseLike<unknown> {\n\treturn value != null && typeof value.then === \"function\";\n}\n\ntype Deferred<T = unknown> = {\n\tresolve: (value: T | PromiseLike<T>) => void;\n\treject: (reason?: unknown) => void;\n};\n\ntype RaceRecord = {\n\tdeferreds: Set<Deferred>;\n\tsettled: boolean;\n};\n\nfunction createRaceRecord(contender: PromiseLike<unknown>): RaceRecord {\n\tconst deferreds = new Set<Deferred>();\n\tconst record = {deferreds, settled: false};\n\n\t// This call to `then` happens once for the lifetime of the value.\n\tPromise.resolve(contender).then(\n\t\t(value) => {\n\t\t\tfor (const {resolve} of deferreds) {\n\t\t\t\tresolve(value);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t\t(err) => {\n\t\t\tfor (const {reject} of deferreds) {\n\t\t\t\treject(err);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t);\n\treturn record;\n}\n\n// Promise.race is memory unsafe. This is alternative which is. See:\n// https://github.com/nodejs/node/issues/17469#issuecomment-685235106\n// Keys are the values passed to race.\n// Values are a record of data containing a set of deferreds and whether the\n// value has settled.\nconst wm = new WeakMap<object, RaceRecord>();\nexport function safeRace<T>(\n\tcontenders: Iterable<T | PromiseLike<T>>,\n): Promise<Awaited<T>> {\n\tlet deferred: Deferred;\n\tconst result = new Promise((resolve, reject) => {\n\t\tdeferred = {resolve, reject};\n\t\tfor (const contender of contenders) {\n\t\t\tif (!isPromiseLike(contender)) {\n\t\t\t\t// If the contender is a not a then-able, attempting to use it as a key\n\t\t\t\t// in the weakmap would throw an error. Luckily, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on regular values multiple\n\t\t\t\t// times because the promise fulfills immediately.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet record = wm.get(contender);\n\t\t\tif (record === undefined) {\n\t\t\t\trecord = createRaceRecord(contender);\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t\twm.set(contender, record);\n\t\t\t} else if (record.settled) {\n\t\t\t\t// If the value has settled, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on it.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t} else {\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t}\n\t\t}\n\t});\n\n\t// The finally callback executes when any value settles, preventing any of\n\t// the unresolved values from retaining a reference to the resolved value.\n\treturn result.finally(() => {\n\t\tfor (const contender of contenders) {\n\t\t\tif (isPromiseLike(contender)) {\n\t\t\t\tconst record = wm.get(contender);\n\t\t\t\tif (record) {\n\t\t\t\t\trecord.deferreds.delete(deferred);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}) as Promise<Awaited<T>>;\n}\n"],"names":[],"mappings":"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"}
|
package/async.cjs
CHANGED
|
@@ -36,16 +36,12 @@ async function SuspenseEmpty() {
|
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
38
|
async function SuspenseFallback({ children, timeout, schedule, }) {
|
|
39
|
-
|
|
40
|
-
this.schedule(schedule);
|
|
41
|
-
}
|
|
39
|
+
this.schedule(schedule);
|
|
42
40
|
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
43
41
|
return children;
|
|
44
42
|
}
|
|
45
43
|
function SuspenseChildren({ children, schedule, }) {
|
|
46
|
-
|
|
47
|
-
this.schedule(schedule);
|
|
48
|
-
}
|
|
44
|
+
this.schedule(schedule);
|
|
49
45
|
return children;
|
|
50
46
|
}
|
|
51
47
|
/**
|
|
@@ -68,11 +64,7 @@ function SuspenseChildren({ children, schedule, }) {
|
|
|
68
64
|
*/
|
|
69
65
|
async function* Suspense({ children, fallback, timeout, }) {
|
|
70
66
|
const controller = this.consume(SuspenseListController);
|
|
71
|
-
if (controller) {
|
|
72
|
-
controller.register(this);
|
|
73
|
-
}
|
|
74
67
|
this.provide(SuspenseListController, undefined);
|
|
75
|
-
let initial = true;
|
|
76
68
|
for await ({ children, fallback, timeout } of this) {
|
|
77
69
|
if (timeout == null) {
|
|
78
70
|
if (controller) {
|
|
@@ -86,35 +78,32 @@ async function* Suspense({ children, fallback, timeout, }) {
|
|
|
86
78
|
yield crank.createElement(SuspenseFallback, {
|
|
87
79
|
timeout: timeout,
|
|
88
80
|
children: fallback,
|
|
81
|
+
schedule: () => { },
|
|
89
82
|
});
|
|
90
83
|
yield children;
|
|
91
84
|
continue;
|
|
92
85
|
}
|
|
86
|
+
const items = await controller.register(this);
|
|
93
87
|
if (controller.revealOrder !== "together") {
|
|
94
|
-
if (!controller.isHead(this)) {
|
|
88
|
+
if (!controller.isHead(this, items)) {
|
|
95
89
|
yield crank.createElement(SuspenseEmpty);
|
|
96
90
|
}
|
|
97
91
|
if (controller.tail !== "hidden") {
|
|
98
92
|
yield crank.createElement(SuspenseFallback, {
|
|
99
93
|
timeout: timeout,
|
|
100
|
-
schedule: initial
|
|
101
|
-
? () => controller.scheduleFallback(this)
|
|
102
|
-
: undefined,
|
|
103
94
|
children: fallback,
|
|
95
|
+
schedule: () => controller.scheduleFallback(this, items),
|
|
104
96
|
});
|
|
105
97
|
}
|
|
106
98
|
}
|
|
107
99
|
yield crank.createElement(SuspenseChildren, {
|
|
108
|
-
schedule: initial ? () => controller.scheduleChildren(this) : undefined,
|
|
109
100
|
children,
|
|
101
|
+
schedule: () => controller.scheduleChildren(this, items),
|
|
110
102
|
});
|
|
111
|
-
initial = false;
|
|
112
103
|
}
|
|
113
104
|
}
|
|
114
105
|
const SuspenseListController = Symbol.for("SuspenseListController");
|
|
115
106
|
/**
|
|
116
|
-
* Coordinates the reveal order of multiple <Suspense> children.
|
|
117
|
-
*
|
|
118
107
|
* Controls when child <Suspense> components show their content or fallbacks
|
|
119
108
|
* based on the specified reveal order. The <SuspenseList> resolves when
|
|
120
109
|
* coordination effort is complete (not necessarily when all content is
|
|
@@ -128,7 +117,7 @@ const SuspenseListController = Symbol.for("SuspenseListController");
|
|
|
128
117
|
* In Crank, the default behavior of async components is to render together,
|
|
129
118
|
* so "together" might not be necessary if you are not using <Suspense>
|
|
130
119
|
* fallbacks.
|
|
131
|
-
*
|
|
120
|
+
*e@param tail - How to handle fallbacks:
|
|
132
121
|
* - "collapsed" (default): Show only the fallback for the next unresolved
|
|
133
122
|
* Suspense component
|
|
134
123
|
* - "hidden": Hide all fallbacks
|
|
@@ -151,25 +140,30 @@ const SuspenseListController = Symbol.for("SuspenseListController");
|
|
|
151
140
|
* ```
|
|
152
141
|
*/
|
|
153
142
|
function* SuspenseList({ revealOrder = "forwards", tail = "collapsed", timeout, children, }) {
|
|
154
|
-
let
|
|
155
|
-
|
|
143
|
+
let finishRegistration;
|
|
144
|
+
let registering = null;
|
|
145
|
+
let items = [];
|
|
156
146
|
const controller = {
|
|
157
147
|
timeout,
|
|
158
148
|
revealOrder,
|
|
159
149
|
tail,
|
|
160
|
-
register(ctx) {
|
|
150
|
+
async register(ctx) {
|
|
161
151
|
if (registering) {
|
|
162
152
|
let childrenResolver;
|
|
163
153
|
const childrenPromise = new Promise((r) => (childrenResolver = r));
|
|
164
|
-
|
|
154
|
+
items.push({
|
|
165
155
|
ctx,
|
|
166
|
-
|
|
167
|
-
childrenPromise,
|
|
156
|
+
resolve: childrenResolver,
|
|
157
|
+
promise: childrenPromise,
|
|
168
158
|
});
|
|
169
|
-
|
|
159
|
+
// Wait for registration to complete
|
|
160
|
+
await registering;
|
|
161
|
+
return items;
|
|
170
162
|
}
|
|
163
|
+
console.error("Component registered outside SuspenseList registration window");
|
|
164
|
+
return [];
|
|
171
165
|
},
|
|
172
|
-
isHead(ctx) {
|
|
166
|
+
isHead(ctx, suspenseItems) {
|
|
173
167
|
const index = suspenseItems.findIndex((item) => item.ctx === ctx);
|
|
174
168
|
if (index === -1) {
|
|
175
169
|
return false;
|
|
@@ -182,34 +176,40 @@ function* SuspenseList({ revealOrder = "forwards", tail = "collapsed", timeout,
|
|
|
182
176
|
}
|
|
183
177
|
return false;
|
|
184
178
|
},
|
|
185
|
-
async scheduleFallback(ctx) {
|
|
179
|
+
async scheduleFallback(ctx, suspenseItems) {
|
|
186
180
|
const index = suspenseItems.findIndex((item) => item.ctx === ctx);
|
|
187
181
|
if (index === -1) {
|
|
188
182
|
return;
|
|
189
183
|
}
|
|
190
184
|
else if (revealOrder === "forwards") {
|
|
191
|
-
await Promise.all(suspenseItems.slice(0, index).map((item) => item.
|
|
185
|
+
await Promise.all(suspenseItems.slice(0, index).map((item) => item.promise));
|
|
192
186
|
}
|
|
193
187
|
else if (revealOrder === "backwards") {
|
|
194
|
-
await Promise.all(suspenseItems.slice(index + 1).map((item) => item.
|
|
188
|
+
await Promise.all(suspenseItems.slice(index + 1).map((item) => item.promise));
|
|
195
189
|
}
|
|
196
190
|
},
|
|
197
|
-
async scheduleChildren(ctx) {
|
|
191
|
+
async scheduleChildren(ctx, suspenseItems) {
|
|
198
192
|
const index = suspenseItems.findIndex((item) => item.ctx === ctx);
|
|
199
193
|
if (index === -1) {
|
|
200
194
|
return;
|
|
201
195
|
}
|
|
202
196
|
// This children content is ready
|
|
203
|
-
suspenseItems[index].
|
|
197
|
+
suspenseItems[index].resolve();
|
|
204
198
|
// Children coordination - determine when this content should show
|
|
205
199
|
if (revealOrder === "together") {
|
|
206
|
-
await Promise.all(suspenseItems.map((item) => item.
|
|
200
|
+
await Promise.all(suspenseItems.map((item) => item.promise));
|
|
207
201
|
}
|
|
208
202
|
else if (revealOrder === "forwards") {
|
|
209
|
-
|
|
203
|
+
const waitFor = suspenseItems
|
|
204
|
+
.slice(0, index + 1)
|
|
205
|
+
.map((item) => item.promise);
|
|
206
|
+
await Promise.all(waitFor);
|
|
210
207
|
}
|
|
211
208
|
else if (revealOrder === "backwards") {
|
|
212
|
-
|
|
209
|
+
const waitFor = suspenseItems
|
|
210
|
+
.slice(index + 1)
|
|
211
|
+
.map((item) => item.promise);
|
|
212
|
+
await Promise.all(waitFor);
|
|
213
213
|
}
|
|
214
214
|
},
|
|
215
215
|
};
|
|
@@ -220,12 +220,16 @@ function* SuspenseList({ revealOrder = "forwards", tail = "collapsed", timeout,
|
|
|
220
220
|
timeout,
|
|
221
221
|
children,
|
|
222
222
|
} of this) {
|
|
223
|
-
|
|
224
|
-
// TODO: Is there a fixed amount of microtasks that we can wait for?
|
|
225
|
-
setTimeout(() => (registering = false));
|
|
223
|
+
items = [];
|
|
226
224
|
controller.timeout = timeout;
|
|
227
225
|
controller.revealOrder = revealOrder;
|
|
228
226
|
controller.tail = tail;
|
|
227
|
+
registering = new Promise((r) => (finishRegistration = r));
|
|
228
|
+
// TODO: Is there a more precise timing for the registration window
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
finishRegistration();
|
|
231
|
+
registering = null;
|
|
232
|
+
});
|
|
229
233
|
yield children;
|
|
230
234
|
}
|
|
231
235
|
}
|
package/async.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.cjs","sources":["../src/async.ts"],"sourcesContent":["import type {Children, Component, Context} from \"./crank.js\";\nimport {createElement} from \"./crank.js\";\n\n/**\n * Creates a lazy-loaded component from an initializer function.\n *\n * @param initializer - Function that returns a Promise resolving to a component or module\n * @returns A component that loads the target component on first render\n *\n * @example\n * ```jsx\n * const LazyComponent = lazy(() => import('./MyComponent'));\n *\n * <Suspense fallback={<div>Loading...</div>}>\n * <LazyComponent prop=\"value\" />\n * </Suspense>\n * ```\n */\nexport function lazy<T extends Component>(\n\tinitializer: () => Promise<T | {default: T}>,\n): T {\n\treturn async function* LazyComponent(\n\t\tthis: Context,\n\t\tprops: any,\n\t): AsyncGenerator<Children> {\n\t\tlet Component = await initializer();\n\t\tif (Component && typeof Component === \"object\" && \"default\" in Component) {\n\t\t\tComponent = Component.default;\n\t\t}\n\n\t\tif (typeof Component !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Lazy component initializer must return a Component or a module with a default export that is a Component.\",\n\t\t\t);\n\t\t}\n\n\t\tfor (props of this) {\n\t\t\tyield createElement(Component, props);\n\t\t}\n\t} as unknown as T;\n}\n\nasync function SuspenseEmpty() {\n\tawait new Promise((resolve) => setTimeout(resolve));\n\treturn null;\n}\n\nasync function SuspenseFallback(\n\tthis: Context,\n\t{\n\t\tchildren,\n\t\ttimeout,\n\t\tschedule,\n\t}: {\n\t\tchildren: Children;\n\t\ttimeout: number;\n\t\tschedule?: () => Promise<unknown>;\n\t},\n): Promise<Children> {\n\tif (schedule) {\n\t\tthis.schedule(schedule);\n\t}\n\n\tawait new Promise((resolve) => setTimeout(resolve, timeout));\n\treturn children;\n}\n\nfunction SuspenseChildren(\n\tthis: Context,\n\t{\n\t\tchildren,\n\t\tschedule,\n\t}: {\n\t\tchildren: Children;\n\t\tschedule?: () => Promise<unknown>;\n\t},\n) {\n\tif (schedule) {\n\t\tthis.schedule(schedule);\n\t}\n\n\treturn children;\n}\n\n/**\n * A component that displays a fallback while its children are loading.\n *\n * When used within a SuspenseList, coordinates with siblings to control\n * reveal order and fallback behavior.\n *\n * @param children - The content to display when loading is complete\n * @param fallback - The content to display while children are loading\n * @param timeout - Time in milliseconds before showing fallback (defaults to\n * 300ms standalone, or inherits from SuspenseList)\n *\n * @example\n * ```jsx\n * <Suspense fallback={<div>Loading...</div>}>\n * <AsyncComponent />\n * </Suspense>\n * ```\n */\nexport async function* Suspense(\n\tthis: Context,\n\t{\n\t\tchildren,\n\t\tfallback,\n\t\ttimeout,\n\t}: {children: Children; fallback: Children; timeout?: number},\n): AsyncGenerator<Children> {\n\tconst controller = this.consume(SuspenseListController);\n\tif (controller) {\n\t\tcontroller.register(this);\n\t}\n\n\tthis.provide(SuspenseListController, undefined);\n\tlet initial = true;\n\tfor await ({children, fallback, timeout} of this) {\n\t\tif (timeout == null) {\n\t\t\tif (controller) {\n\t\t\t\ttimeout = controller.timeout;\n\t\t\t} else {\n\t\t\t\ttimeout = 300;\n\t\t\t}\n\t\t}\n\n\t\tif (!controller) {\n\t\t\tyield createElement(SuspenseFallback, {\n\t\t\t\ttimeout: timeout!,\n\t\t\t\tchildren: fallback,\n\t\t\t});\n\t\t\tyield children;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (controller.revealOrder !== \"together\") {\n\t\t\tif (!controller.isHead(this)) {\n\t\t\t\tyield createElement(SuspenseEmpty);\n\t\t\t}\n\n\t\t\tif (controller.tail !== \"hidden\") {\n\t\t\t\tyield createElement(SuspenseFallback, {\n\t\t\t\t\ttimeout: timeout!,\n\t\t\t\t\tschedule: initial\n\t\t\t\t\t\t? () => controller.scheduleFallback(this)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\tchildren: fallback,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tyield createElement(SuspenseChildren, {\n\t\t\tschedule: initial ? () => controller.scheduleChildren(this) : undefined,\n\t\t\tchildren,\n\t\t});\n\n\t\tinitial = false;\n\t}\n}\n\nconst SuspenseListController = Symbol.for(\"SuspenseListController\");\n\ninterface SuspenseListController {\n\ttimeout?: number;\n\trevealOrder?: \"forwards\" | \"backwards\" | \"together\";\n\ttail?: \"collapsed\" | \"hidden\";\n\tregister(ctx: Context): void;\n\tisHead(ctx: Context): boolean;\n\tscheduleFallback(ctx: Context): Promise<void>;\n\tscheduleChildren(ctx: Context): Promise<void>;\n}\n\ndeclare global {\n\tnamespace Crank {\n\t\tinterface ProvisionMap {\n\t\t\t[SuspenseListController]: SuspenseListController;\n\t\t}\n\t}\n}\n\n/**\n * Coordinates the reveal order of multiple <Suspense> children.\n *\n * Controls when child <Suspense> components show their content or fallbacks\n * based on the specified reveal order. The <SuspenseList> resolves when\n * coordination effort is complete (not necessarily when all content is\n * loaded).\n *\n * @param revealOrder - How children should be revealed:\n * - \"forwards\" (default): Show children in document order, waiting for\n * predecessors\n * - \"backwards\": Show children in reverse order, waiting for successors\n * - \"together\": Show all children simultaneously when all are ready\n * In Crank, the default behavior of async components is to render together,\n * so \"together\" might not be necessary if you are not using <Suspense>\n * fallbacks.\n * @param tail - How to handle fallbacks:\n * - \"collapsed\" (default): Show only the fallback for the next unresolved\n * Suspense component\n * - \"hidden\": Hide all fallbacks\n * Tail behavior only applies when revealOrder is not \"together\".\n * @param timeout - Default timeout for Suspense children in milliseconds\n * @param children - The elements containing Suspense components to coordinate.\n * Suspense components which are not rendered immediately (because they are\n * the children of another async component) will not be coordinated.\n *\n * @example\n * ```jsx\n * <SuspenseList revealOrder=\"forwards\" tail=\"collapsed\">\n * <Suspense fallback={<div>Loading A...</div>}>\n * <ComponentA />\n * </Suspense>\n * <Suspense fallback={<div>Loading B...</div>}>\n * <ComponentB />\n * </Suspense>\n * </SuspenseList>\n * ```\n */\nexport function* SuspenseList(\n\tthis: Context,\n\t{\n\t\trevealOrder = \"forwards\",\n\t\ttail = \"collapsed\",\n\t\ttimeout,\n\t\tchildren,\n\t}: {\n\t\trevealOrder?: \"forwards\" | \"backwards\" | \"together\";\n\t\ttail?: \"collapsed\" | \"hidden\";\n\t\ttimeout?: number;\n\t\tchildren: Children;\n\t},\n): Generator<Children> {\n\tlet registering = true;\n\tconst suspenseItems: Array<{\n\t\tctx: Context;\n\t\tchildrenResolver: () => void;\n\t\tchildrenPromise: Promise<void>;\n\t}> = [];\n\n\tconst controller: SuspenseListController = {\n\t\ttimeout,\n\t\trevealOrder,\n\t\ttail,\n\t\tregister(ctx: Context) {\n\t\t\tif (registering) {\n\t\t\t\tlet childrenResolver: () => void;\n\n\t\t\t\tconst childrenPromise = new Promise<void>(\n\t\t\t\t\t(r) => (childrenResolver = r),\n\t\t\t\t);\n\n\t\t\t\tsuspenseItems.push({\n\t\t\t\t\tctx,\n\t\t\t\t\tchildrenResolver: childrenResolver!,\n\t\t\t\t\tchildrenPromise,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t},\n\n\t\tisHead(ctx: Context): boolean {\n\t\t\tconst index = suspenseItems.findIndex((item) => item.ctx === ctx);\n\t\t\tif (index === -1) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (revealOrder === \"forwards\") {\n\t\t\t\treturn index === 0;\n\t\t\t} else if (revealOrder === \"backwards\") {\n\t\t\t\treturn index === suspenseItems.length - 1;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t},\n\n\t\tasync scheduleFallback(ctx: Context) {\n\t\t\tconst index = suspenseItems.findIndex((item) => item.ctx === ctx);\n\t\t\tif (index === -1) {\n\t\t\t\treturn;\n\t\t\t} else if (revealOrder === \"forwards\") {\n\t\t\t\tawait Promise.all(\n\t\t\t\t\tsuspenseItems.slice(0, index).map((item) => item.childrenPromise),\n\t\t\t\t);\n\t\t\t} else if (revealOrder === \"backwards\") {\n\t\t\t\tawait Promise.all(\n\t\t\t\t\tsuspenseItems.slice(index + 1).map((item) => item.childrenPromise),\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tasync scheduleChildren(ctx: Context) {\n\t\t\tconst index = suspenseItems.findIndex((item) => item.ctx === ctx);\n\t\t\tif (index === -1) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// This children content is ready\n\t\t\tsuspenseItems[index].childrenResolver();\n\t\t\t// Children coordination - determine when this content should show\n\t\t\tif (revealOrder === \"together\") {\n\t\t\t\tawait Promise.all(suspenseItems.map((item) => item.childrenPromise));\n\t\t\t} else if (revealOrder === \"forwards\") {\n\t\t\t\tawait Promise.all(\n\t\t\t\t\tsuspenseItems.slice(0, index + 1).map((item) => item.childrenPromise),\n\t\t\t\t);\n\t\t\t} else if (revealOrder === \"backwards\") {\n\t\t\t\tawait Promise.all(\n\t\t\t\t\tsuspenseItems.slice(index).map((item) => item.childrenPromise),\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t};\n\n\tthis.provide(SuspenseListController, controller);\n\tfor ({\n\t\trevealOrder = \"forwards\",\n\t\ttail = \"collapsed\",\n\t\ttimeout,\n\t\tchildren,\n\t} of this) {\n\t\tregistering = true;\n\t\t// TODO: Is there a fixed amount of microtasks that we can wait for?\n\t\tsetTimeout(() => (registering = false));\n\t\tcontroller.timeout = timeout;\n\t\tcontroller.revealOrder = revealOrder;\n\t\tcontroller.tail = tail;\n\t\tyield children;\n\t}\n}\n"],"names":["createElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"async.cjs","sources":["../src/async.ts"],"sourcesContent":["import type {Children, Component, Context} from \"./crank.js\";\nimport {createElement} from \"./crank.js\";\n\n/**\n * Creates a lazy-loaded component from an initializer function.\n *\n * @param initializer - Function that returns a Promise resolving to a component or module\n * @returns A component that loads the target component on first render\n *\n * @example\n * ```jsx\n * const LazyComponent = lazy(() => import('./MyComponent'));\n *\n * <Suspense fallback={<div>Loading...</div>}>\n * <LazyComponent prop=\"value\" />\n * </Suspense>\n * ```\n */\nexport function lazy<T extends Component>(\n\tinitializer: () => Promise<T | {default: T}>,\n): T {\n\treturn async function* LazyComponent(\n\t\tthis: Context,\n\t\tprops: any,\n\t): AsyncGenerator<Children> {\n\t\tlet Component = await initializer();\n\t\tif (Component && typeof Component === \"object\" && \"default\" in Component) {\n\t\t\tComponent = Component.default;\n\t\t}\n\n\t\tif (typeof Component !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Lazy component initializer must return a Component or a module with a default export that is a Component.\",\n\t\t\t);\n\t\t}\n\n\t\tfor (props of this) {\n\t\t\tyield createElement(Component, props);\n\t\t}\n\t} as unknown as T;\n}\n\nasync function SuspenseEmpty() {\n\tawait new Promise((resolve) => setTimeout(resolve));\n\treturn null;\n}\n\nasync function SuspenseFallback(\n\tthis: Context,\n\t{\n\t\tchildren,\n\t\ttimeout,\n\t\tschedule,\n\t}: {\n\t\tchildren: Children;\n\t\ttimeout: number;\n\t\tschedule: () => Promise<unknown>;\n\t},\n): Promise<Children> {\n\tthis.schedule(schedule);\n\tawait new Promise((resolve) => setTimeout(resolve, timeout));\n\treturn children;\n}\n\nfunction SuspenseChildren(\n\tthis: Context,\n\t{\n\t\tchildren,\n\t\tschedule,\n\t}: {\n\t\tchildren: Children;\n\t\tschedule: () => Promise<unknown>;\n\t},\n) {\n\tthis.schedule(schedule);\n\treturn children;\n}\n\n/**\n * A component that displays a fallback while its children are loading.\n *\n * When used within a SuspenseList, coordinates with siblings to control\n * reveal order and fallback behavior.\n *\n * @param children - The content to display when loading is complete\n * @param fallback - The content to display while children are loading\n * @param timeout - Time in milliseconds before showing fallback (defaults to\n * 300ms standalone, or inherits from SuspenseList)\n *\n * @example\n * ```jsx\n * <Suspense fallback={<div>Loading...</div>}>\n * <AsyncComponent />\n * </Suspense>\n * ```\n */\nexport async function* Suspense(\n\tthis: Context,\n\t{\n\t\tchildren,\n\t\tfallback,\n\t\ttimeout,\n\t}: {children: Children; fallback: Children; timeout?: number},\n): AsyncGenerator<Children> {\n\tconst controller = this.consume(SuspenseListController);\n\tthis.provide(SuspenseListController, undefined);\n\tfor await ({children, fallback, timeout} of this) {\n\t\tif (timeout == null) {\n\t\t\tif (controller) {\n\t\t\t\ttimeout = controller.timeout;\n\t\t\t} else {\n\t\t\t\ttimeout = 300;\n\t\t\t}\n\t\t}\n\n\t\tif (!controller) {\n\t\t\tyield createElement(SuspenseFallback, {\n\t\t\t\ttimeout: timeout!,\n\t\t\t\tchildren: fallback,\n\t\t\t\tschedule: () => {},\n\t\t\t});\n\t\t\tyield children;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst items = await controller.register(this);\n\t\tif (controller.revealOrder !== \"together\") {\n\t\t\tif (!controller.isHead(this, items)) {\n\t\t\t\tyield createElement(SuspenseEmpty);\n\t\t\t}\n\n\t\t\tif (controller.tail !== \"hidden\") {\n\t\t\t\tyield createElement(SuspenseFallback, {\n\t\t\t\t\ttimeout: timeout!,\n\t\t\t\t\tchildren: fallback,\n\t\t\t\t\tschedule: () => controller.scheduleFallback(this, items),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tyield createElement(SuspenseChildren, {\n\t\t\tchildren,\n\t\t\tschedule: () => controller.scheduleChildren(this, items),\n\t\t});\n\t}\n}\n\nconst SuspenseListController = Symbol.for(\"SuspenseListController\");\n\ninterface SuspenseListItem {\n\tctx: Context;\n\tresolve: () => void;\n\tpromise: Promise<void>;\n}\n\ninterface SuspenseListController {\n\ttimeout?: number;\n\trevealOrder?: \"forwards\" | \"backwards\" | \"together\";\n\ttail?: \"collapsed\" | \"hidden\";\n\tregister(ctx: Context): Promise<Array<SuspenseListItem>>;\n\tisHead(ctx: Context, items: Array<SuspenseListItem>): boolean;\n\tscheduleFallback(ctx: Context, items: Array<SuspenseListItem>): Promise<void>;\n\tscheduleChildren(ctx: Context, items: Array<SuspenseListItem>): Promise<void>;\n}\n\ndeclare global {\n\tnamespace Crank {\n\t\tinterface ProvisionMap {\n\t\t\t[SuspenseListController]: SuspenseListController;\n\t\t}\n\t}\n}\n\n/**\n * Controls when child <Suspense> components show their content or fallbacks\n * based on the specified reveal order. The <SuspenseList> resolves when\n * coordination effort is complete (not necessarily when all content is\n * loaded).\n *\n * @param revealOrder - How children should be revealed:\n * - \"forwards\" (default): Show children in document order, waiting for\n * predecessors\n * - \"backwards\": Show children in reverse order, waiting for successors\n * - \"together\": Show all children simultaneously when all are ready\n * In Crank, the default behavior of async components is to render together,\n * so \"together\" might not be necessary if you are not using <Suspense>\n * fallbacks.\n *e@param tail - How to handle fallbacks:\n * - \"collapsed\" (default): Show only the fallback for the next unresolved\n * Suspense component\n * - \"hidden\": Hide all fallbacks\n * Tail behavior only applies when revealOrder is not \"together\".\n * @param timeout - Default timeout for Suspense children in milliseconds\n * @param children - The elements containing Suspense components to coordinate.\n * Suspense components which are not rendered immediately (because they are\n * the children of another async component) will not be coordinated.\n *\n * @example\n * ```jsx\n * <SuspenseList revealOrder=\"forwards\" tail=\"collapsed\">\n * <Suspense fallback={<div>Loading A...</div>}>\n * <ComponentA />\n * </Suspense>\n * <Suspense fallback={<div>Loading B...</div>}>\n * <ComponentB />\n * </Suspense>\n * </SuspenseList>\n * ```\n */\nexport function* SuspenseList(\n\tthis: Context,\n\t{\n\t\trevealOrder = \"forwards\",\n\t\ttail = \"collapsed\",\n\t\ttimeout,\n\t\tchildren,\n\t}: {\n\t\trevealOrder?: \"forwards\" | \"backwards\" | \"together\";\n\t\ttail?: \"collapsed\" | \"hidden\";\n\t\ttimeout?: number;\n\t\tchildren: Children;\n\t},\n): Generator<Children> {\n\tlet finishRegistration: () => void;\n\tlet registering: Promise<void> | null = null;\n\tlet items: Array<SuspenseListItem> = [];\n\tconst controller: SuspenseListController = {\n\t\ttimeout,\n\t\trevealOrder,\n\t\ttail,\n\t\tasync register(ctx: Context) {\n\t\t\tif (registering) {\n\t\t\t\tlet childrenResolver!: () => void;\n\t\t\t\tconst childrenPromise = new Promise<void>(\n\t\t\t\t\t(r) => (childrenResolver = r),\n\t\t\t\t);\n\n\t\t\t\titems.push({\n\t\t\t\t\tctx,\n\t\t\t\t\tresolve: childrenResolver!,\n\t\t\t\t\tpromise: childrenPromise,\n\t\t\t\t});\n\n\t\t\t\t// Wait for registration to complete\n\t\t\t\tawait registering;\n\t\t\t\treturn items;\n\t\t\t}\n\n\t\t\tconsole.error(\n\t\t\t\t\"Component registered outside SuspenseList registration window\",\n\t\t\t);\n\t\t\treturn [];\n\t\t},\n\n\t\tisHead(ctx: Context, suspenseItems: Array<SuspenseListItem>): boolean {\n\t\t\tconst index = suspenseItems.findIndex((item) => item.ctx === ctx);\n\t\t\tif (index === -1) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (revealOrder === \"forwards\") {\n\t\t\t\treturn index === 0;\n\t\t\t} else if (revealOrder === \"backwards\") {\n\t\t\t\treturn index === suspenseItems.length - 1;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t},\n\n\t\tasync scheduleFallback(\n\t\t\tctx: Context,\n\t\t\tsuspenseItems: Array<SuspenseListItem>,\n\t\t) {\n\t\t\tconst index = suspenseItems.findIndex((item) => item.ctx === ctx);\n\t\t\tif (index === -1) {\n\t\t\t\treturn;\n\t\t\t} else if (revealOrder === \"forwards\") {\n\t\t\t\tawait Promise.all(\n\t\t\t\t\tsuspenseItems.slice(0, index).map((item) => item.promise),\n\t\t\t\t);\n\t\t\t} else if (revealOrder === \"backwards\") {\n\t\t\t\tawait Promise.all(\n\t\t\t\t\tsuspenseItems.slice(index + 1).map((item) => item.promise),\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tasync scheduleChildren(\n\t\t\tctx: Context,\n\t\t\tsuspenseItems: Array<SuspenseListItem>,\n\t\t) {\n\t\t\tconst index = suspenseItems.findIndex((item) => item.ctx === ctx);\n\t\t\tif (index === -1) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// This children content is ready\n\t\t\tsuspenseItems[index].resolve();\n\t\t\t// Children coordination - determine when this content should show\n\t\t\tif (revealOrder === \"together\") {\n\t\t\t\tawait Promise.all(suspenseItems.map((item) => item.promise));\n\t\t\t} else if (revealOrder === \"forwards\") {\n\t\t\t\tconst waitFor = suspenseItems\n\t\t\t\t\t.slice(0, index + 1)\n\t\t\t\t\t.map((item) => item.promise);\n\t\t\t\tawait Promise.all(waitFor);\n\t\t\t} else if (revealOrder === \"backwards\") {\n\t\t\t\tconst waitFor = suspenseItems\n\t\t\t\t\t.slice(index + 1)\n\t\t\t\t\t.map((item) => item.promise);\n\t\t\t\tawait Promise.all(waitFor);\n\t\t\t}\n\t\t},\n\t};\n\n\tthis.provide(SuspenseListController, controller);\n\tfor ({\n\t\trevealOrder = \"forwards\",\n\t\ttail = \"collapsed\",\n\t\ttimeout,\n\t\tchildren,\n\t} of this) {\n\t\titems = [];\n\t\tcontroller.timeout = timeout;\n\t\tcontroller.revealOrder = revealOrder;\n\t\tcontroller.tail = tail;\n\t\tregistering = new Promise((r) => (finishRegistration = r));\n\t\t// TODO: Is there a more precise timing for the registration window\n\t\tsetTimeout(() => {\n\t\t\tfinishRegistration();\n\t\t\tregistering = null;\n\t\t});\n\t\tyield children;\n\t}\n}\n"],"names":["createElement"],"mappings":"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"}
|
package/async.d.ts
CHANGED
|
@@ -41,14 +41,19 @@ export declare function Suspense(this: Context, { children, fallback, timeout, }
|
|
|
41
41
|
timeout?: number;
|
|
42
42
|
}): AsyncGenerator<Children>;
|
|
43
43
|
declare const SuspenseListController: unique symbol;
|
|
44
|
+
interface SuspenseListItem {
|
|
45
|
+
ctx: Context;
|
|
46
|
+
resolve: () => void;
|
|
47
|
+
promise: Promise<void>;
|
|
48
|
+
}
|
|
44
49
|
interface SuspenseListController {
|
|
45
50
|
timeout?: number;
|
|
46
51
|
revealOrder?: "forwards" | "backwards" | "together";
|
|
47
52
|
tail?: "collapsed" | "hidden";
|
|
48
|
-
register(ctx: Context):
|
|
49
|
-
isHead(ctx: Context): boolean;
|
|
50
|
-
scheduleFallback(ctx: Context): Promise<void>;
|
|
51
|
-
scheduleChildren(ctx: Context): Promise<void>;
|
|
53
|
+
register(ctx: Context): Promise<Array<SuspenseListItem>>;
|
|
54
|
+
isHead(ctx: Context, items: Array<SuspenseListItem>): boolean;
|
|
55
|
+
scheduleFallback(ctx: Context, items: Array<SuspenseListItem>): Promise<void>;
|
|
56
|
+
scheduleChildren(ctx: Context, items: Array<SuspenseListItem>): Promise<void>;
|
|
52
57
|
}
|
|
53
58
|
declare global {
|
|
54
59
|
namespace Crank {
|
|
@@ -58,8 +63,6 @@ declare global {
|
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
|
-
* Coordinates the reveal order of multiple <Suspense> children.
|
|
62
|
-
*
|
|
63
66
|
* Controls when child <Suspense> components show their content or fallbacks
|
|
64
67
|
* based on the specified reveal order. The <SuspenseList> resolves when
|
|
65
68
|
* coordination effort is complete (not necessarily when all content is
|
|
@@ -73,7 +76,7 @@ declare global {
|
|
|
73
76
|
* In Crank, the default behavior of async components is to render together,
|
|
74
77
|
* so "together" might not be necessary if you are not using <Suspense>
|
|
75
78
|
* fallbacks.
|
|
76
|
-
*
|
|
79
|
+
*e@param tail - How to handle fallbacks:
|
|
77
80
|
* - "collapsed" (default): Show only the fallback for the next unresolved
|
|
78
81
|
* Suspense component
|
|
79
82
|
* - "hidden": Hide all fallbacks
|
package/async.js
CHANGED
|
@@ -35,16 +35,12 @@ async function SuspenseEmpty() {
|
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
37
|
async function SuspenseFallback({ children, timeout, schedule, }) {
|
|
38
|
-
|
|
39
|
-
this.schedule(schedule);
|
|
40
|
-
}
|
|
38
|
+
this.schedule(schedule);
|
|
41
39
|
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
42
40
|
return children;
|
|
43
41
|
}
|
|
44
42
|
function SuspenseChildren({ children, schedule, }) {
|
|
45
|
-
|
|
46
|
-
this.schedule(schedule);
|
|
47
|
-
}
|
|
43
|
+
this.schedule(schedule);
|
|
48
44
|
return children;
|
|
49
45
|
}
|
|
50
46
|
/**
|
|
@@ -67,11 +63,7 @@ function SuspenseChildren({ children, schedule, }) {
|
|
|
67
63
|
*/
|
|
68
64
|
async function* Suspense({ children, fallback, timeout, }) {
|
|
69
65
|
const controller = this.consume(SuspenseListController);
|
|
70
|
-
if (controller) {
|
|
71
|
-
controller.register(this);
|
|
72
|
-
}
|
|
73
66
|
this.provide(SuspenseListController, undefined);
|
|
74
|
-
let initial = true;
|
|
75
67
|
for await ({ children, fallback, timeout } of this) {
|
|
76
68
|
if (timeout == null) {
|
|
77
69
|
if (controller) {
|
|
@@ -85,35 +77,32 @@ async function* Suspense({ children, fallback, timeout, }) {
|
|
|
85
77
|
yield createElement(SuspenseFallback, {
|
|
86
78
|
timeout: timeout,
|
|
87
79
|
children: fallback,
|
|
80
|
+
schedule: () => { },
|
|
88
81
|
});
|
|
89
82
|
yield children;
|
|
90
83
|
continue;
|
|
91
84
|
}
|
|
85
|
+
const items = await controller.register(this);
|
|
92
86
|
if (controller.revealOrder !== "together") {
|
|
93
|
-
if (!controller.isHead(this)) {
|
|
87
|
+
if (!controller.isHead(this, items)) {
|
|
94
88
|
yield createElement(SuspenseEmpty);
|
|
95
89
|
}
|
|
96
90
|
if (controller.tail !== "hidden") {
|
|
97
91
|
yield createElement(SuspenseFallback, {
|
|
98
92
|
timeout: timeout,
|
|
99
|
-
schedule: initial
|
|
100
|
-
? () => controller.scheduleFallback(this)
|
|
101
|
-
: undefined,
|
|
102
93
|
children: fallback,
|
|
94
|
+
schedule: () => controller.scheduleFallback(this, items),
|
|
103
95
|
});
|
|
104
96
|
}
|
|
105
97
|
}
|
|
106
98
|
yield createElement(SuspenseChildren, {
|
|
107
|
-
schedule: initial ? () => controller.scheduleChildren(this) : undefined,
|
|
108
99
|
children,
|
|
100
|
+
schedule: () => controller.scheduleChildren(this, items),
|
|
109
101
|
});
|
|
110
|
-
initial = false;
|
|
111
102
|
}
|
|
112
103
|
}
|
|
113
104
|
const SuspenseListController = Symbol.for("SuspenseListController");
|
|
114
105
|
/**
|
|
115
|
-
* Coordinates the reveal order of multiple <Suspense> children.
|
|
116
|
-
*
|
|
117
106
|
* Controls when child <Suspense> components show their content or fallbacks
|
|
118
107
|
* based on the specified reveal order. The <SuspenseList> resolves when
|
|
119
108
|
* coordination effort is complete (not necessarily when all content is
|
|
@@ -127,7 +116,7 @@ const SuspenseListController = Symbol.for("SuspenseListController");
|
|
|
127
116
|
* In Crank, the default behavior of async components is to render together,
|
|
128
117
|
* so "together" might not be necessary if you are not using <Suspense>
|
|
129
118
|
* fallbacks.
|
|
130
|
-
*
|
|
119
|
+
*e@param tail - How to handle fallbacks:
|
|
131
120
|
* - "collapsed" (default): Show only the fallback for the next unresolved
|
|
132
121
|
* Suspense component
|
|
133
122
|
* - "hidden": Hide all fallbacks
|
|
@@ -150,25 +139,30 @@ const SuspenseListController = Symbol.for("SuspenseListController");
|
|
|
150
139
|
* ```
|
|
151
140
|
*/
|
|
152
141
|
function* SuspenseList({ revealOrder = "forwards", tail = "collapsed", timeout, children, }) {
|
|
153
|
-
let
|
|
154
|
-
|
|
142
|
+
let finishRegistration;
|
|
143
|
+
let registering = null;
|
|
144
|
+
let items = [];
|
|
155
145
|
const controller = {
|
|
156
146
|
timeout,
|
|
157
147
|
revealOrder,
|
|
158
148
|
tail,
|
|
159
|
-
register(ctx) {
|
|
149
|
+
async register(ctx) {
|
|
160
150
|
if (registering) {
|
|
161
151
|
let childrenResolver;
|
|
162
152
|
const childrenPromise = new Promise((r) => (childrenResolver = r));
|
|
163
|
-
|
|
153
|
+
items.push({
|
|
164
154
|
ctx,
|
|
165
|
-
|
|
166
|
-
childrenPromise,
|
|
155
|
+
resolve: childrenResolver,
|
|
156
|
+
promise: childrenPromise,
|
|
167
157
|
});
|
|
168
|
-
|
|
158
|
+
// Wait for registration to complete
|
|
159
|
+
await registering;
|
|
160
|
+
return items;
|
|
169
161
|
}
|
|
162
|
+
console.error("Component registered outside SuspenseList registration window");
|
|
163
|
+
return [];
|
|
170
164
|
},
|
|
171
|
-
isHead(ctx) {
|
|
165
|
+
isHead(ctx, suspenseItems) {
|
|
172
166
|
const index = suspenseItems.findIndex((item) => item.ctx === ctx);
|
|
173
167
|
if (index === -1) {
|
|
174
168
|
return false;
|
|
@@ -181,34 +175,40 @@ function* SuspenseList({ revealOrder = "forwards", tail = "collapsed", timeout,
|
|
|
181
175
|
}
|
|
182
176
|
return false;
|
|
183
177
|
},
|
|
184
|
-
async scheduleFallback(ctx) {
|
|
178
|
+
async scheduleFallback(ctx, suspenseItems) {
|
|
185
179
|
const index = suspenseItems.findIndex((item) => item.ctx === ctx);
|
|
186
180
|
if (index === -1) {
|
|
187
181
|
return;
|
|
188
182
|
}
|
|
189
183
|
else if (revealOrder === "forwards") {
|
|
190
|
-
await Promise.all(suspenseItems.slice(0, index).map((item) => item.
|
|
184
|
+
await Promise.all(suspenseItems.slice(0, index).map((item) => item.promise));
|
|
191
185
|
}
|
|
192
186
|
else if (revealOrder === "backwards") {
|
|
193
|
-
await Promise.all(suspenseItems.slice(index + 1).map((item) => item.
|
|
187
|
+
await Promise.all(suspenseItems.slice(index + 1).map((item) => item.promise));
|
|
194
188
|
}
|
|
195
189
|
},
|
|
196
|
-
async scheduleChildren(ctx) {
|
|
190
|
+
async scheduleChildren(ctx, suspenseItems) {
|
|
197
191
|
const index = suspenseItems.findIndex((item) => item.ctx === ctx);
|
|
198
192
|
if (index === -1) {
|
|
199
193
|
return;
|
|
200
194
|
}
|
|
201
195
|
// This children content is ready
|
|
202
|
-
suspenseItems[index].
|
|
196
|
+
suspenseItems[index].resolve();
|
|
203
197
|
// Children coordination - determine when this content should show
|
|
204
198
|
if (revealOrder === "together") {
|
|
205
|
-
await Promise.all(suspenseItems.map((item) => item.
|
|
199
|
+
await Promise.all(suspenseItems.map((item) => item.promise));
|
|
206
200
|
}
|
|
207
201
|
else if (revealOrder === "forwards") {
|
|
208
|
-
|
|
202
|
+
const waitFor = suspenseItems
|
|
203
|
+
.slice(0, index + 1)
|
|
204
|
+
.map((item) => item.promise);
|
|
205
|
+
await Promise.all(waitFor);
|
|
209
206
|
}
|
|
210
207
|
else if (revealOrder === "backwards") {
|
|
211
|
-
|
|
208
|
+
const waitFor = suspenseItems
|
|
209
|
+
.slice(index + 1)
|
|
210
|
+
.map((item) => item.promise);
|
|
211
|
+
await Promise.all(waitFor);
|
|
212
212
|
}
|
|
213
213
|
},
|
|
214
214
|
};
|
|
@@ -219,12 +219,16 @@ function* SuspenseList({ revealOrder = "forwards", tail = "collapsed", timeout,
|
|
|
219
219
|
timeout,
|
|
220
220
|
children,
|
|
221
221
|
} of this) {
|
|
222
|
-
|
|
223
|
-
// TODO: Is there a fixed amount of microtasks that we can wait for?
|
|
224
|
-
setTimeout(() => (registering = false));
|
|
222
|
+
items = [];
|
|
225
223
|
controller.timeout = timeout;
|
|
226
224
|
controller.revealOrder = revealOrder;
|
|
227
225
|
controller.tail = tail;
|
|
226
|
+
registering = new Promise((r) => (finishRegistration = r));
|
|
227
|
+
// TODO: Is there a more precise timing for the registration window
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
finishRegistration();
|
|
230
|
+
registering = null;
|
|
231
|
+
});
|
|
228
232
|
yield children;
|
|
229
233
|
}
|
|
230
234
|
}
|