@b9g/crank 0.7.0 → 0.7.2

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/_css.cjs ADDED
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * CSS utility functions for style property transformation.
5
+ *
6
+ * This module handles camelCase to kebab-case conversion and automatic
7
+ * px unit conversion for numeric CSS values, making Crank more React-compatible.
8
+ */
9
+ /**
10
+ * Converts camelCase CSS property names to kebab-case.
11
+ * Handles vendor prefixes correctly (WebkitTransform -> -webkit-transform).
12
+ */
13
+ function camelToKebabCase(str) {
14
+ // Handle vendor prefixes that start with capital letters (WebkitTransform -> -webkit-transform)
15
+ if (/^[A-Z]/.test(str)) {
16
+ return `-${str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`).slice(1)}`;
17
+ }
18
+ // Handle normal camelCase (fontSize -> font-size)
19
+ return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
20
+ }
21
+ /**
22
+ * CSS properties that should remain unitless when given numeric values.
23
+ * Based on React's list of unitless properties.
24
+ */
25
+ const UNITLESS_PROPERTIES = new Set([
26
+ "animation-iteration-count",
27
+ "aspect-ratio",
28
+ "border-image-outset",
29
+ "border-image-slice",
30
+ "border-image-width",
31
+ "box-flex",
32
+ "box-flex-group",
33
+ "box-ordinal-group",
34
+ "column-count",
35
+ "columns",
36
+ "flex",
37
+ "flex-grow",
38
+ "flex-positive",
39
+ "flex-shrink",
40
+ "flex-negative",
41
+ "flex-order",
42
+ "font-weight",
43
+ "grid-area",
44
+ "grid-column",
45
+ "grid-column-end",
46
+ "grid-column-span",
47
+ "grid-column-start",
48
+ "grid-row",
49
+ "grid-row-end",
50
+ "grid-row-span",
51
+ "grid-row-start",
52
+ "line-height",
53
+ "opacity",
54
+ "order",
55
+ "orphans",
56
+ "tab-size",
57
+ "widows",
58
+ "z-index",
59
+ "zoom",
60
+ ]);
61
+ /**
62
+ * Formats CSS property values, automatically adding "px" to numeric values
63
+ * for properties that are not unitless.
64
+ */
65
+ function formatStyleValue(name, value) {
66
+ if (typeof value === "number") {
67
+ // If the property should remain unitless, keep the number as-is
68
+ if (UNITLESS_PROPERTIES.has(name)) {
69
+ return String(value);
70
+ }
71
+ // Otherwise, append "px" for numeric values
72
+ return `${value}px`;
73
+ }
74
+ return String(value);
75
+ }
76
+
77
+ exports.UNITLESS_PROPERTIES = UNITLESS_PROPERTIES;
78
+ exports.camelToKebabCase = camelToKebabCase;
79
+ exports.formatStyleValue = formatStyleValue;
80
+ //# sourceMappingURL=_css.cjs.map
package/_css.cjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_css.cjs","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/_css.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * CSS utility functions for style property transformation.
3
+ *
4
+ * This module handles camelCase to kebab-case conversion and automatic
5
+ * px unit conversion for numeric CSS values, making Crank more React-compatible.
6
+ */
7
+ /**
8
+ * Converts camelCase CSS property names to kebab-case.
9
+ * Handles vendor prefixes correctly (WebkitTransform -> -webkit-transform).
10
+ */
11
+ export declare function camelToKebabCase(str: string): string;
12
+ /**
13
+ * CSS properties that should remain unitless when given numeric values.
14
+ * Based on React's list of unitless properties.
15
+ */
16
+ export declare const UNITLESS_PROPERTIES: Set<string>;
17
+ /**
18
+ * Formats CSS property values, automatically adding "px" to numeric values
19
+ * for properties that are not unitless.
20
+ */
21
+ export declare function formatStyleValue(name: string, value: unknown): string;
package/_css.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * CSS utility functions for style property transformation.
3
+ *
4
+ * This module handles camelCase to kebab-case conversion and automatic
5
+ * px unit conversion for numeric CSS values, making Crank more React-compatible.
6
+ */
7
+ /**
8
+ * Converts camelCase CSS property names to kebab-case.
9
+ * Handles vendor prefixes correctly (WebkitTransform -> -webkit-transform).
10
+ */
11
+ function camelToKebabCase(str) {
12
+ // Handle vendor prefixes that start with capital letters (WebkitTransform -> -webkit-transform)
13
+ if (/^[A-Z]/.test(str)) {
14
+ return `-${str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`).slice(1)}`;
15
+ }
16
+ // Handle normal camelCase (fontSize -> font-size)
17
+ return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
18
+ }
19
+ /**
20
+ * CSS properties that should remain unitless when given numeric values.
21
+ * Based on React's list of unitless properties.
22
+ */
23
+ const UNITLESS_PROPERTIES = new Set([
24
+ "animation-iteration-count",
25
+ "aspect-ratio",
26
+ "border-image-outset",
27
+ "border-image-slice",
28
+ "border-image-width",
29
+ "box-flex",
30
+ "box-flex-group",
31
+ "box-ordinal-group",
32
+ "column-count",
33
+ "columns",
34
+ "flex",
35
+ "flex-grow",
36
+ "flex-positive",
37
+ "flex-shrink",
38
+ "flex-negative",
39
+ "flex-order",
40
+ "font-weight",
41
+ "grid-area",
42
+ "grid-column",
43
+ "grid-column-end",
44
+ "grid-column-span",
45
+ "grid-column-start",
46
+ "grid-row",
47
+ "grid-row-end",
48
+ "grid-row-span",
49
+ "grid-row-start",
50
+ "line-height",
51
+ "opacity",
52
+ "order",
53
+ "orphans",
54
+ "tab-size",
55
+ "widows",
56
+ "z-index",
57
+ "zoom",
58
+ ]);
59
+ /**
60
+ * Formats CSS property values, automatically adding "px" to numeric values
61
+ * for properties that are not unitless.
62
+ */
63
+ function formatStyleValue(name, value) {
64
+ if (typeof value === "number") {
65
+ // If the property should remain unitless, keep the number as-is
66
+ if (UNITLESS_PROPERTIES.has(name)) {
67
+ return String(value);
68
+ }
69
+ // Otherwise, append "px" for numeric values
70
+ return `${value}px`;
71
+ }
72
+ return String(value);
73
+ }
74
+
75
+ export { UNITLESS_PROPERTIES, camelToKebabCase, formatStyleValue };
76
+ //# sourceMappingURL=_css.js.map
package/_css.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_css.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/_utils.cjs ADDED
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ function wrap(value) {
4
+ return value === undefined ? [] : Array.isArray(value) ? value : [value];
5
+ }
6
+ function unwrap(arr) {
7
+ return arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;
8
+ }
9
+ /**
10
+ * Ensures a value is an array.
11
+ *
12
+ * This function does the same thing as wrap() above except it handles nulls
13
+ * and iterables, so it is appropriate for wrapping user-provided element
14
+ * children.
15
+ */
16
+ function arrayify(value) {
17
+ return value == null
18
+ ? []
19
+ : Array.isArray(value)
20
+ ? value
21
+ : typeof value === "string" ||
22
+ typeof value[Symbol.iterator] !== "function"
23
+ ? [value]
24
+ : [...value];
25
+ }
26
+ function isIteratorLike(value) {
27
+ return value != null && typeof value.next === "function";
28
+ }
29
+ function isPromiseLike(value) {
30
+ return value != null && typeof value.then === "function";
31
+ }
32
+ function createRaceRecord(contender) {
33
+ const deferreds = new Set();
34
+ const record = { deferreds, settled: false };
35
+ // This call to `then` happens once for the lifetime of the value.
36
+ Promise.resolve(contender).then((value) => {
37
+ for (const { resolve } of deferreds) {
38
+ resolve(value);
39
+ }
40
+ deferreds.clear();
41
+ record.settled = true;
42
+ }, (err) => {
43
+ for (const { reject } of deferreds) {
44
+ reject(err);
45
+ }
46
+ deferreds.clear();
47
+ record.settled = true;
48
+ });
49
+ return record;
50
+ }
51
+ // Promise.race is memory unsafe. This is alternative which is. See:
52
+ // https://github.com/nodejs/node/issues/17469#issuecomment-685235106
53
+ // Keys are the values passed to race.
54
+ // Values are a record of data containing a set of deferreds and whether the
55
+ // value has settled.
56
+ const wm = new WeakMap();
57
+ function safeRace(contenders) {
58
+ let deferred;
59
+ const result = new Promise((resolve, reject) => {
60
+ deferred = { resolve, reject };
61
+ for (const contender of contenders) {
62
+ if (!isPromiseLike(contender)) {
63
+ // If the contender is a not a then-able, attempting to use it as a key
64
+ // in the weakmap would throw an error. Luckily, it is safe to call
65
+ // `Promise.resolve(contender).then` on regular values multiple
66
+ // times because the promise fulfills immediately.
67
+ Promise.resolve(contender).then(resolve, reject);
68
+ continue;
69
+ }
70
+ let record = wm.get(contender);
71
+ if (record === undefined) {
72
+ record = createRaceRecord(contender);
73
+ record.deferreds.add(deferred);
74
+ wm.set(contender, record);
75
+ }
76
+ else if (record.settled) {
77
+ // If the value has settled, it is safe to call
78
+ // `Promise.resolve(contender).then` on it.
79
+ Promise.resolve(contender).then(resolve, reject);
80
+ }
81
+ else {
82
+ record.deferreds.add(deferred);
83
+ }
84
+ }
85
+ });
86
+ // The finally callback executes when any value settles, preventing any of
87
+ // the unresolved values from retaining a reference to the resolved value.
88
+ return result.finally(() => {
89
+ for (const contender of contenders) {
90
+ if (isPromiseLike(contender)) {
91
+ const record = wm.get(contender);
92
+ if (record) {
93
+ record.deferreds.delete(deferred);
94
+ }
95
+ }
96
+ }
97
+ });
98
+ }
99
+
100
+ exports.arrayify = arrayify;
101
+ exports.isIteratorLike = isIteratorLike;
102
+ exports.isPromiseLike = isPromiseLike;
103
+ exports.safeRace = safeRace;
104
+ exports.unwrap = unwrap;
105
+ exports.wrap = wrap;
106
+ //# sourceMappingURL=_utils.cjs.map
package/_utils.cjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_utils.cjs","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/_utils.js ADDED
@@ -0,0 +1,99 @@
1
+ function wrap(value) {
2
+ return value === undefined ? [] : Array.isArray(value) ? value : [value];
3
+ }
4
+ function unwrap(arr) {
5
+ return arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;
6
+ }
7
+ /**
8
+ * Ensures a value is an array.
9
+ *
10
+ * This function does the same thing as wrap() above except it handles nulls
11
+ * and iterables, so it is appropriate for wrapping user-provided element
12
+ * children.
13
+ */
14
+ function arrayify(value) {
15
+ return value == null
16
+ ? []
17
+ : Array.isArray(value)
18
+ ? value
19
+ : typeof value === "string" ||
20
+ typeof value[Symbol.iterator] !== "function"
21
+ ? [value]
22
+ : [...value];
23
+ }
24
+ function isIteratorLike(value) {
25
+ return value != null && typeof value.next === "function";
26
+ }
27
+ function isPromiseLike(value) {
28
+ return value != null && typeof value.then === "function";
29
+ }
30
+ function createRaceRecord(contender) {
31
+ const deferreds = new Set();
32
+ const record = { deferreds, settled: false };
33
+ // This call to `then` happens once for the lifetime of the value.
34
+ Promise.resolve(contender).then((value) => {
35
+ for (const { resolve } of deferreds) {
36
+ resolve(value);
37
+ }
38
+ deferreds.clear();
39
+ record.settled = true;
40
+ }, (err) => {
41
+ for (const { reject } of deferreds) {
42
+ reject(err);
43
+ }
44
+ deferreds.clear();
45
+ record.settled = true;
46
+ });
47
+ return record;
48
+ }
49
+ // Promise.race is memory unsafe. This is alternative which is. See:
50
+ // https://github.com/nodejs/node/issues/17469#issuecomment-685235106
51
+ // Keys are the values passed to race.
52
+ // Values are a record of data containing a set of deferreds and whether the
53
+ // value has settled.
54
+ const wm = new WeakMap();
55
+ function safeRace(contenders) {
56
+ let deferred;
57
+ const result = new Promise((resolve, reject) => {
58
+ deferred = { resolve, reject };
59
+ for (const contender of contenders) {
60
+ if (!isPromiseLike(contender)) {
61
+ // If the contender is a not a then-able, attempting to use it as a key
62
+ // in the weakmap would throw an error. Luckily, it is safe to call
63
+ // `Promise.resolve(contender).then` on regular values multiple
64
+ // times because the promise fulfills immediately.
65
+ Promise.resolve(contender).then(resolve, reject);
66
+ continue;
67
+ }
68
+ let record = wm.get(contender);
69
+ if (record === undefined) {
70
+ record = createRaceRecord(contender);
71
+ record.deferreds.add(deferred);
72
+ wm.set(contender, record);
73
+ }
74
+ else if (record.settled) {
75
+ // If the value has settled, it is safe to call
76
+ // `Promise.resolve(contender).then` on it.
77
+ Promise.resolve(contender).then(resolve, reject);
78
+ }
79
+ else {
80
+ record.deferreds.add(deferred);
81
+ }
82
+ }
83
+ });
84
+ // The finally callback executes when any value settles, preventing any of
85
+ // the unresolved values from retaining a reference to the resolved value.
86
+ return result.finally(() => {
87
+ for (const contender of contenders) {
88
+ if (isPromiseLike(contender)) {
89
+ const record = wm.get(contender);
90
+ if (record) {
91
+ record.deferreds.delete(deferred);
92
+ }
93
+ }
94
+ }
95
+ });
96
+ }
97
+
98
+ export { arrayify, isIteratorLike, isPromiseLike, safeRace, unwrap, wrap };
99
+ //# sourceMappingURL=_utils.js.map
package/_utils.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_utils.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/async.cjs CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  var crank = require('./crank.cjs');
4
- require('./event-target.cjs');
5
4
 
6
5
  /**
7
6
  * Creates a lazy-loaded component from an initializer function.
@@ -37,16 +36,12 @@ async function SuspenseEmpty() {
37
36
  return null;
38
37
  }
39
38
  async function SuspenseFallback({ children, timeout, schedule, }) {
40
- if (schedule) {
41
- this.schedule(schedule);
42
- }
39
+ this.schedule(schedule);
43
40
  await new Promise((resolve) => setTimeout(resolve, timeout));
44
41
  return children;
45
42
  }
46
43
  function SuspenseChildren({ children, schedule, }) {
47
- if (schedule) {
48
- this.schedule(schedule);
49
- }
44
+ this.schedule(schedule);
50
45
  return children;
51
46
  }
52
47
  /**
@@ -69,11 +64,7 @@ function SuspenseChildren({ children, schedule, }) {
69
64
  */
70
65
  async function* Suspense({ children, fallback, timeout, }) {
71
66
  const controller = this.consume(SuspenseListController);
72
- if (controller) {
73
- controller.register(this);
74
- }
75
67
  this.provide(SuspenseListController, undefined);
76
- let initial = true;
77
68
  for await ({ children, fallback, timeout } of this) {
78
69
  if (timeout == null) {
79
70
  if (controller) {
@@ -87,35 +78,32 @@ async function* Suspense({ children, fallback, timeout, }) {
87
78
  yield crank.createElement(SuspenseFallback, {
88
79
  timeout: timeout,
89
80
  children: fallback,
81
+ schedule: () => { },
90
82
  });
91
83
  yield children;
92
84
  continue;
93
85
  }
86
+ const items = await controller.register(this);
94
87
  if (controller.revealOrder !== "together") {
95
- if (!controller.isHead(this)) {
88
+ if (!controller.isHead(this, items)) {
96
89
  yield crank.createElement(SuspenseEmpty);
97
90
  }
98
91
  if (controller.tail !== "hidden") {
99
92
  yield crank.createElement(SuspenseFallback, {
100
93
  timeout: timeout,
101
- schedule: initial
102
- ? () => controller.scheduleFallback(this)
103
- : undefined,
104
94
  children: fallback,
95
+ schedule: () => controller.scheduleFallback(this, items),
105
96
  });
106
97
  }
107
98
  }
108
99
  yield crank.createElement(SuspenseChildren, {
109
- schedule: initial ? () => controller.scheduleChildren(this) : undefined,
110
100
  children,
101
+ schedule: () => controller.scheduleChildren(this, items),
111
102
  });
112
- initial = false;
113
103
  }
114
104
  }
115
105
  const SuspenseListController = Symbol.for("SuspenseListController");
116
106
  /**
117
- * Coordinates the reveal order of multiple <Suspense> children.
118
- *
119
107
  * Controls when child <Suspense> components show their content or fallbacks
120
108
  * based on the specified reveal order. The <SuspenseList> resolves when
121
109
  * coordination effort is complete (not necessarily when all content is
@@ -129,7 +117,7 @@ const SuspenseListController = Symbol.for("SuspenseListController");
129
117
  * In Crank, the default behavior of async components is to render together,
130
118
  * so "together" might not be necessary if you are not using <Suspense>
131
119
  * fallbacks.
132
- * @param tail - How to handle fallbacks:
120
+ *e@param tail - How to handle fallbacks:
133
121
  * - "collapsed" (default): Show only the fallback for the next unresolved
134
122
  * Suspense component
135
123
  * - "hidden": Hide all fallbacks
@@ -152,25 +140,30 @@ const SuspenseListController = Symbol.for("SuspenseListController");
152
140
  * ```
153
141
  */
154
142
  function* SuspenseList({ revealOrder = "forwards", tail = "collapsed", timeout, children, }) {
155
- let registering = true;
156
- const suspenseItems = [];
143
+ let finishRegistration;
144
+ let registering = null;
145
+ let items = [];
157
146
  const controller = {
158
147
  timeout,
159
148
  revealOrder,
160
149
  tail,
161
- register(ctx) {
150
+ async register(ctx) {
162
151
  if (registering) {
163
152
  let childrenResolver;
164
153
  const childrenPromise = new Promise((r) => (childrenResolver = r));
165
- suspenseItems.push({
154
+ items.push({
166
155
  ctx,
167
- childrenResolver: childrenResolver,
168
- childrenPromise,
156
+ resolve: childrenResolver,
157
+ promise: childrenPromise,
169
158
  });
170
- return;
159
+ // Wait for registration to complete
160
+ await registering;
161
+ return items;
171
162
  }
163
+ console.error("Component registered outside SuspenseList registration window");
164
+ return [];
172
165
  },
173
- isHead(ctx) {
166
+ isHead(ctx, suspenseItems) {
174
167
  const index = suspenseItems.findIndex((item) => item.ctx === ctx);
175
168
  if (index === -1) {
176
169
  return false;
@@ -183,34 +176,40 @@ function* SuspenseList({ revealOrder = "forwards", tail = "collapsed", timeout,
183
176
  }
184
177
  return false;
185
178
  },
186
- async scheduleFallback(ctx) {
179
+ async scheduleFallback(ctx, suspenseItems) {
187
180
  const index = suspenseItems.findIndex((item) => item.ctx === ctx);
188
181
  if (index === -1) {
189
182
  return;
190
183
  }
191
184
  else if (revealOrder === "forwards") {
192
- await Promise.all(suspenseItems.slice(0, index).map((item) => item.childrenPromise));
185
+ await Promise.all(suspenseItems.slice(0, index).map((item) => item.promise));
193
186
  }
194
187
  else if (revealOrder === "backwards") {
195
- await Promise.all(suspenseItems.slice(index + 1).map((item) => item.childrenPromise));
188
+ await Promise.all(suspenseItems.slice(index + 1).map((item) => item.promise));
196
189
  }
197
190
  },
198
- async scheduleChildren(ctx) {
191
+ async scheduleChildren(ctx, suspenseItems) {
199
192
  const index = suspenseItems.findIndex((item) => item.ctx === ctx);
200
193
  if (index === -1) {
201
194
  return;
202
195
  }
203
196
  // This children content is ready
204
- suspenseItems[index].childrenResolver();
197
+ suspenseItems[index].resolve();
205
198
  // Children coordination - determine when this content should show
206
199
  if (revealOrder === "together") {
207
- await Promise.all(suspenseItems.map((item) => item.childrenPromise));
200
+ await Promise.all(suspenseItems.map((item) => item.promise));
208
201
  }
209
202
  else if (revealOrder === "forwards") {
210
- await Promise.all(suspenseItems.slice(0, index + 1).map((item) => item.childrenPromise));
203
+ const waitFor = suspenseItems
204
+ .slice(0, index + 1)
205
+ .map((item) => item.promise);
206
+ await Promise.all(waitFor);
211
207
  }
212
208
  else if (revealOrder === "backwards") {
213
- await Promise.all(suspenseItems.slice(index).map((item) => item.childrenPromise));
209
+ const waitFor = suspenseItems
210
+ .slice(index + 1)
211
+ .map((item) => item.promise);
212
+ await Promise.all(waitFor);
214
213
  }
215
214
  },
216
215
  };
@@ -221,12 +220,16 @@ function* SuspenseList({ revealOrder = "forwards", tail = "collapsed", timeout,
221
220
  timeout,
222
221
  children,
223
222
  } of this) {
224
- registering = true;
225
- // TODO: Is there a fixed amount of microtasks that we can wait for?
226
- setTimeout(() => (registering = false));
223
+ items = [];
227
224
  controller.timeout = timeout;
228
225
  controller.revealOrder = revealOrder;
229
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
+ });
230
233
  yield children;
231
234
  }
232
235
  }