foldkit 0.128.1 → 0.130.0
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/THIRD-PARTY-NOTICES.md +30 -0
- package/dist/brand/brand.d.ts +25 -0
- package/dist/brand/brand.d.ts.map +1 -0
- package/dist/brand/brand.js +50 -0
- package/dist/brand/index.d.ts +2 -0
- package/dist/brand/index.d.ts.map +1 -0
- package/dist/brand/index.js +1 -0
- package/dist/brand/public.d.ts +2 -0
- package/dist/brand/public.d.ts.map +1 -0
- package/dist/brand/public.js +1 -0
- package/dist/canvas/view.d.ts.map +1 -1
- package/dist/canvas/view.js +1 -1
- package/dist/command/index.d.ts +6 -1
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/interruptible/index.d.ts +2 -0
- package/dist/command/interruptible/index.d.ts.map +1 -0
- package/dist/command/interruptible/index.js +1 -0
- package/dist/command/interruptible/interruptible.d.ts +177 -0
- package/dist/command/interruptible/interruptible.d.ts.map +1 -0
- package/dist/command/interruptible/interruptible.js +155 -0
- package/dist/command/interruptible/public.d.ts +3 -0
- package/dist/command/interruptible/public.d.ts.map +1 -0
- package/dist/command/interruptible/public.js +1 -0
- package/dist/command/public.d.ts +1 -0
- package/dist/command/public.d.ts.map +1 -1
- package/dist/command/public.js +1 -0
- package/dist/devTools/protocol.d.ts +1 -1
- package/dist/devTools/protocol.d.ts.map +1 -1
- package/dist/devTools/protocol.js +1 -1
- package/dist/devTools/store.js +2 -2
- package/dist/devTools/webSocketBridge.d.ts +1 -1
- package/dist/devTools/webSocketBridge.js +1 -1
- package/dist/html/index.d.ts +18 -18
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +530 -460
- package/dist/html/lazy.d.ts +1 -1
- package/dist/html/lazy.d.ts.map +1 -1
- package/dist/html/lazy.js +11 -2
- package/dist/propsModule.d.ts +1 -1
- package/dist/propsModule.d.ts.map +1 -1
- package/dist/propsModule.js +6 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +472 -279
- package/dist/snabbdom/attributes.d.ts +4 -0
- package/dist/snabbdom/attributes.d.ts.map +1 -0
- package/dist/snabbdom/attributes.js +63 -0
- package/dist/snabbdom/class.d.ts +4 -0
- package/dist/snabbdom/class.d.ts.map +1 -0
- package/dist/snabbdom/class.js +31 -0
- package/dist/snabbdom/dataset.d.ts +4 -0
- package/dist/snabbdom/dataset.d.ts.map +1 -0
- package/dist/snabbdom/dataset.js +42 -0
- package/dist/snabbdom/eventlisteners.d.ts +11 -0
- package/dist/snabbdom/eventlisteners.d.ts.map +1 -0
- package/dist/snabbdom/eventlisteners.js +85 -0
- package/dist/snabbdom/h.d.ts +11 -0
- package/dist/snabbdom/h.d.ts.map +1 -0
- package/dist/snabbdom/h.js +61 -0
- package/dist/snabbdom/hooks.d.ts +24 -0
- package/dist/snabbdom/hooks.d.ts.map +1 -0
- package/dist/snabbdom/hooks.js +1 -0
- package/dist/snabbdom/htmldomapi.d.ts +34 -0
- package/dist/snabbdom/htmldomapi.d.ts.map +1 -0
- package/dist/snabbdom/htmldomapi.js +111 -0
- package/dist/snabbdom/index.d.ts +15 -0
- package/dist/snabbdom/index.d.ts.map +1 -0
- package/dist/snabbdom/index.js +9 -0
- package/dist/snabbdom/init.d.ts +14 -0
- package/dist/snabbdom/init.d.ts.map +1 -0
- package/dist/snabbdom/init.js +486 -0
- package/dist/snabbdom/is.d.ts +3 -0
- package/dist/snabbdom/is.d.ts.map +1 -0
- package/dist/snabbdom/is.js +7 -0
- package/dist/snabbdom/module.d.ts +11 -0
- package/dist/snabbdom/module.d.ts.map +1 -0
- package/dist/snabbdom/module.js +1 -0
- package/dist/snabbdom/props.d.ts +2 -0
- package/dist/snabbdom/props.d.ts.map +1 -0
- package/dist/snabbdom/props.js +1 -0
- package/dist/snabbdom/style.d.ts +9 -0
- package/dist/snabbdom/style.d.ts.map +1 -0
- package/dist/snabbdom/style.js +123 -0
- package/dist/snabbdom/tovnode.d.ts +4 -0
- package/dist/snabbdom/tovnode.d.ts.map +1 -0
- package/dist/snabbdom/tovnode.js +69 -0
- package/dist/snabbdom/vnode.d.ts +46 -0
- package/dist/snabbdom/vnode.d.ts.map +1 -0
- package/dist/snabbdom/vnode.js +13 -0
- package/dist/test/apps/uploads.d.ts +81 -0
- package/dist/test/apps/uploads.d.ts.map +1 -0
- package/dist/test/apps/uploads.js +102 -0
- package/dist/test/internal.d.ts +16 -2
- package/dist/test/internal.d.ts.map +1 -1
- package/dist/test/internal.js +20 -4
- package/dist/vdom.d.ts +2 -2
- package/dist/vdom.d.ts.map +1 -1
- package/dist/vdom.js +1 -1
- package/package.json +7 -5
- package/dist/runtime/messagePriority.d.ts +0 -17
- package/dist/runtime/messagePriority.d.ts.map +0 -1
- package/dist/runtime/messagePriority.js +0 -33
- package/dist/runtime/renderLoop.d.ts +0 -27
- package/dist/runtime/renderLoop.d.ts.map +0 -1
- package/dist/runtime/renderLoop.js +0 -25
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
2
|
+
import { addNS } from './h.js';
|
|
3
|
+
import { htmlDomApi } from './htmldomapi.js';
|
|
4
|
+
import { vnode } from './vnode.js';
|
|
5
|
+
/**
|
|
6
|
+
* Transforms the given attribute name into a valid dataset property key
|
|
7
|
+
* according to https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset#name_conversion
|
|
8
|
+
*
|
|
9
|
+
* @param attributeName data- attribute name, must start with data-
|
|
10
|
+
*/
|
|
11
|
+
function datasetKey(attributeName) {
|
|
12
|
+
return attributeName
|
|
13
|
+
.slice(5)
|
|
14
|
+
.replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
|
|
15
|
+
}
|
|
16
|
+
export function toVNode(node, domApi) {
|
|
17
|
+
const api = domApi !== undefined ? domApi : htmlDomApi;
|
|
18
|
+
let text;
|
|
19
|
+
if (api.isElement(node)) {
|
|
20
|
+
const id = node.id ? '#' + node.id : '';
|
|
21
|
+
const cn = node.getAttribute('class')?.match(/[^\t\r\n\f ]+/g);
|
|
22
|
+
const c = cn ? '.' + cn.join('.') : '';
|
|
23
|
+
const sel = api.tagName(node).toLowerCase() + id + c;
|
|
24
|
+
const attrs = {};
|
|
25
|
+
const dataset = {};
|
|
26
|
+
const data = {};
|
|
27
|
+
const children = [];
|
|
28
|
+
let name;
|
|
29
|
+
let i, n;
|
|
30
|
+
const elmAttrs = node.attributes;
|
|
31
|
+
const elmChildren = node.childNodes;
|
|
32
|
+
// NOTE: upstream reads attr.nodeName / attr.nodeValue. Attr.name /
|
|
33
|
+
// Attr.value are identical per the DOM spec, but happy-dom (the repo test
|
|
34
|
+
// environment) returns '' / null for the nodeName / nodeValue aliases, so
|
|
35
|
+
// the spec-equal accessors are used here.
|
|
36
|
+
for (i = 0, n = elmAttrs.length; i < n; i++) {
|
|
37
|
+
name = elmAttrs[i].name;
|
|
38
|
+
if (name.startsWith('data-')) {
|
|
39
|
+
dataset[datasetKey(name)] = elmAttrs[i].value || '';
|
|
40
|
+
}
|
|
41
|
+
else if (name !== 'id' && name !== 'class') {
|
|
42
|
+
attrs[name] = elmAttrs[i].value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (i = 0, n = elmChildren.length; i < n; i++) {
|
|
46
|
+
children.push(toVNode(elmChildren[i], domApi));
|
|
47
|
+
}
|
|
48
|
+
if (Object.keys(attrs).length > 0)
|
|
49
|
+
data.attrs = attrs;
|
|
50
|
+
if (Object.keys(dataset).length > 0)
|
|
51
|
+
data.dataset = dataset;
|
|
52
|
+
if (sel.startsWith('svg') &&
|
|
53
|
+
(sel.length === 3 || sel[3] === '.' || sel[3] === '#')) {
|
|
54
|
+
addNS(data, children, sel);
|
|
55
|
+
}
|
|
56
|
+
return vnode(sel, data, children, undefined, node);
|
|
57
|
+
}
|
|
58
|
+
else if (api.isText(node)) {
|
|
59
|
+
text = api.getTextContent(node);
|
|
60
|
+
return vnode(undefined, undefined, undefined, text, node);
|
|
61
|
+
}
|
|
62
|
+
else if (api.isComment(node)) {
|
|
63
|
+
text = api.getTextContent(node);
|
|
64
|
+
return vnode('!', {}, [], text, node);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
return vnode('', {}, [], undefined, node);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Attrs } from './attributes.js';
|
|
2
|
+
import type { Classes } from './class.js';
|
|
3
|
+
import type { Dataset } from './dataset.js';
|
|
4
|
+
import type { On } from './eventlisteners.js';
|
|
5
|
+
import type { Hooks } from './hooks.js';
|
|
6
|
+
import type { Props } from './props.js';
|
|
7
|
+
import type { VNodeStyle } from './style.js';
|
|
8
|
+
export type Key = PropertyKey;
|
|
9
|
+
export declare const vnodeDataMaskKey: unique symbol;
|
|
10
|
+
export declare const VNodeDataMask: {
|
|
11
|
+
Attrs: number;
|
|
12
|
+
Class: number;
|
|
13
|
+
Dataset: number;
|
|
14
|
+
On: number;
|
|
15
|
+
Props: number;
|
|
16
|
+
Style: number;
|
|
17
|
+
};
|
|
18
|
+
export interface VNode {
|
|
19
|
+
sel: string | undefined;
|
|
20
|
+
data: VNodeData | undefined;
|
|
21
|
+
children: Array<VNode | string> | undefined;
|
|
22
|
+
elm: Node | undefined;
|
|
23
|
+
text: string | undefined;
|
|
24
|
+
key: Key | undefined;
|
|
25
|
+
/** Framework-managed identity stamped by `foldkit/brand`. Independent of
|
|
26
|
+
* `key`: it never enters the keyed index, and joins the `sameVnode`
|
|
27
|
+
* compatibility check exactly where `sel` is consulted. A mismatch
|
|
28
|
+
* replaces the node instead of patching it. */
|
|
29
|
+
identity?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface VNodeData<VNodeProps = Props> {
|
|
32
|
+
[vnodeDataMaskKey]?: number;
|
|
33
|
+
props?: VNodeProps;
|
|
34
|
+
attrs?: Attrs;
|
|
35
|
+
class?: Classes;
|
|
36
|
+
style?: VNodeStyle;
|
|
37
|
+
dataset?: Dataset;
|
|
38
|
+
on?: On;
|
|
39
|
+
hook?: Hooks;
|
|
40
|
+
key?: Key;
|
|
41
|
+
ns?: string;
|
|
42
|
+
is?: string;
|
|
43
|
+
[key: string]: any;
|
|
44
|
+
}
|
|
45
|
+
export declare function vnode(sel: string | undefined, data: any | undefined, children: Array<VNode | string> | undefined, text: string | undefined, elm: Element | DocumentFragment | Text | undefined): VNode;
|
|
46
|
+
//# sourceMappingURL=vnode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vnode.d.ts","sourceRoot":"","sources":["../../src/snabbdom/vnode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAA;AAC7C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,MAAM,MAAM,GAAG,GAAG,WAAW,CAAA;AAE7B,eAAO,MAAM,gBAAgB,eAAoC,CAAA;AAEjE,eAAO,MAAM,aAAa;;;;;;;CAOzB,CAAA;AAED,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IACvB,IAAI,EAAE,SAAS,GAAG,SAAS,CAAA;IAC3B,QAAQ,EAAE,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,SAAS,CAAA;IAC3C,GAAG,EAAE,IAAI,GAAG,SAAS,CAAA;IACrB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACxB,GAAG,EAAE,GAAG,GAAG,SAAS,CAAA;IACpB;;;oDAGgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS,CAAC,UAAU,GAAG,KAAK;IAC3C,CAAC,gBAAgB,CAAC,CAAC,EAAE,MAAM,CAAA;IAC3B,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,EAAE,CAAC,EAAE,EAAE,CAAA;IACP,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED,wBAAgB,KAAK,CACnB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,GAAG,GAAG,SAAS,EACrB,QAAQ,EAAE,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,SAAS,EAC3C,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,GAAG,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,GAAG,SAAS,GACjD,KAAK,CAGP"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const vnodeDataMaskKey = Symbol('foldkit/vnode-data-mask');
|
|
2
|
+
export const VNodeDataMask = {
|
|
3
|
+
Attrs: 1,
|
|
4
|
+
Class: 2,
|
|
5
|
+
Dataset: 4,
|
|
6
|
+
On: 8,
|
|
7
|
+
Props: 16,
|
|
8
|
+
Style: 32,
|
|
9
|
+
};
|
|
10
|
+
export function vnode(sel, data, children, text, elm) {
|
|
11
|
+
const key = data === undefined ? undefined : data.key;
|
|
12
|
+
return { sel, data, children, text, elm, key };
|
|
13
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Effect, Schema as S } from 'effect';
|
|
2
|
+
import * as Command from '../../command/index.js';
|
|
3
|
+
import * as Interruptible from '../../command/interruptible/index.js';
|
|
4
|
+
import { type Document } from '../../html/index.js';
|
|
5
|
+
export declare const UploadStatus: S.Literals<readonly ["Uploading", "Done", "Cancelled", "Failed"]>;
|
|
6
|
+
export type UploadStatus = typeof UploadStatus.Type;
|
|
7
|
+
export declare const Upload: S.Struct<{
|
|
8
|
+
readonly id: S.Number;
|
|
9
|
+
readonly status: S.Literals<readonly ["Uploading", "Done", "Cancelled", "Failed"]>;
|
|
10
|
+
}>;
|
|
11
|
+
export type Upload = typeof Upload.Type;
|
|
12
|
+
export declare const Model: S.Struct<{
|
|
13
|
+
readonly uploadId: S.Number;
|
|
14
|
+
readonly uploads: S.$Array<S.Struct<{
|
|
15
|
+
readonly id: S.Number;
|
|
16
|
+
readonly status: S.Literals<readonly ["Uploading", "Done", "Cancelled", "Failed"]>;
|
|
17
|
+
}>>;
|
|
18
|
+
}>;
|
|
19
|
+
export type Model = typeof Model.Type;
|
|
20
|
+
export declare const ClickedStartUpload: import("../../schema/index.js").CallableTaggedStruct<"ClickedStartUpload", {}>;
|
|
21
|
+
export declare const ClickedRetryUpload: import("../../schema/index.js").CallableTaggedStruct<"ClickedRetryUpload", {
|
|
22
|
+
uploadId: S.Number;
|
|
23
|
+
}>;
|
|
24
|
+
export declare const ClickedCancelUpload: import("../../schema/index.js").CallableTaggedStruct<"ClickedCancelUpload", {
|
|
25
|
+
uploadId: S.Number;
|
|
26
|
+
}>;
|
|
27
|
+
export declare const SucceededUploadFile: import("../../schema/index.js").CallableTaggedStruct<"SucceededUploadFile", {
|
|
28
|
+
uploadId: S.Number;
|
|
29
|
+
}>;
|
|
30
|
+
export declare const FailedUploadFile: import("../../schema/index.js").CallableTaggedStruct<"FailedUploadFile", {
|
|
31
|
+
uploadId: S.Number;
|
|
32
|
+
}>;
|
|
33
|
+
export declare const CompletedCancelUploadFile: import("../../schema/index.js").CallableTaggedStruct<"CompletedCancelUploadFile", {
|
|
34
|
+
uploadId: S.Number;
|
|
35
|
+
outcome: S.Union<readonly [import("../../schema/index.js").CallableTaggedStruct<"Interrupted", {}>, import("../../schema/index.js").CallableTaggedStruct<"NotFound", {}>]>;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const Message: S.Union<readonly [import("../../schema/index.js").CallableTaggedStruct<"ClickedStartUpload", {}>, import("../../schema/index.js").CallableTaggedStruct<"ClickedRetryUpload", {
|
|
38
|
+
uploadId: S.Number;
|
|
39
|
+
}>, import("../../schema/index.js").CallableTaggedStruct<"ClickedCancelUpload", {
|
|
40
|
+
uploadId: S.Number;
|
|
41
|
+
}>, import("../../schema/index.js").CallableTaggedStruct<"SucceededUploadFile", {
|
|
42
|
+
uploadId: S.Number;
|
|
43
|
+
}>, import("../../schema/index.js").CallableTaggedStruct<"FailedUploadFile", {
|
|
44
|
+
uploadId: S.Number;
|
|
45
|
+
}>, import("../../schema/index.js").CallableTaggedStruct<"CompletedCancelUploadFile", {
|
|
46
|
+
uploadId: S.Number;
|
|
47
|
+
outcome: S.Union<readonly [import("../../schema/index.js").CallableTaggedStruct<"Interrupted", {}>, import("../../schema/index.js").CallableTaggedStruct<"NotFound", {}>]>;
|
|
48
|
+
}>]>;
|
|
49
|
+
export type Message = typeof Message.Type;
|
|
50
|
+
export declare const UploadFileArgs: S.Struct<{
|
|
51
|
+
readonly uploadId: S.Number;
|
|
52
|
+
}>;
|
|
53
|
+
export type UploadFileArgs = typeof UploadFileArgs.Type;
|
|
54
|
+
export declare const UploadFile: Interruptible.DefinitionWithArgs<"UploadFile", {
|
|
55
|
+
readonly uploadId: S.Number;
|
|
56
|
+
}, S.Struct.ReadonlySide<{
|
|
57
|
+
readonly uploadId: S.Number;
|
|
58
|
+
}, "Type">, Effect.Effect<{
|
|
59
|
+
readonly _tag: "SucceededUploadFile";
|
|
60
|
+
readonly uploadId: number;
|
|
61
|
+
}, never, never>>;
|
|
62
|
+
export declare const CancelUploadFile: ({ uploadId }: UploadFileArgs) => Readonly<{
|
|
63
|
+
name: "UploadFile.Interrupt";
|
|
64
|
+
args: S.Struct.ReadonlySide<{
|
|
65
|
+
readonly uploadId: S.Number;
|
|
66
|
+
}, "Type">;
|
|
67
|
+
interruptsKey: string;
|
|
68
|
+
effect: Effect.Effect<{
|
|
69
|
+
readonly _tag: "CompletedCancelUploadFile";
|
|
70
|
+
readonly uploadId: number;
|
|
71
|
+
readonly outcome: S.Struct.ReadonlySide<{
|
|
72
|
+
readonly _tag: S.tag<"Interrupted">;
|
|
73
|
+
}, "Type"> | S.Struct.ReadonlySide<{
|
|
74
|
+
readonly _tag: S.tag<"NotFound">;
|
|
75
|
+
}, "Type">;
|
|
76
|
+
}, never, never>;
|
|
77
|
+
}>;
|
|
78
|
+
export declare const initialModel: Model;
|
|
79
|
+
export declare const update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command.Command<Message>>];
|
|
80
|
+
export declare const view: (model: Model) => Document;
|
|
81
|
+
//# sourceMappingURL=uploads.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploads.d.ts","sourceRoot":"","sources":["../../../src/test/apps/uploads.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAsB,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAEvE,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAA;AACjD,OAAO,KAAK,aAAa,MAAM,sCAAsC,CAAA;AACrE,OAAO,EAAE,KAAK,QAAQ,EAAQ,MAAM,qBAAqB,CAAA;AAMzD,eAAO,MAAM,YAAY,mEAKvB,CAAA;AACF,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD,eAAO,MAAM,MAAM;;;EAGjB,CAAA;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;AAEvC,eAAO,MAAM,KAAK;;;;;;EAGhB,CAAA;AACF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,eAAO,MAAM,kBAAkB,gFAA0B,CAAA;AACzD,eAAO,MAAM,kBAAkB;;EAE7B,CAAA;AACF,eAAO,MAAM,mBAAmB;;EAE9B,CAAA;AACF,eAAO,MAAM,mBAAmB;;EAE9B,CAAA;AACF,eAAO,MAAM,gBAAgB;;EAAgD,CAAA;AAC7E,eAAO,MAAM,yBAAyB;;;EAGpC,CAAA;AAEF,eAAO,MAAM,OAAO;;;;;;;;;;;IAOlB,CAAA;AACF,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAIzC,eAAO,MAAM,cAAc;;EAAmC,CAAA;AAC9D,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAEvD,eAAO,MAAM,UAAU;;;;;;;iBAMwD,CAAA;AAE/E,eAAO,MAAM,gBAAgB,GAAI,cAAc,cAAc;;;;;;;;;;;;;;;EAG1D,CAAA;AAIH,eAAO,MAAM,YAAY,EAAE,KAAoC,CAAA;AAS/D,eAAO,MAAM,MAAM,GACjB,OAAO,KAAK,EACZ,SAAS,OAAO,KACf,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAiDxD,CAAA;AAIH,eAAO,MAAM,IAAI,GAAI,OAAO,KAAK,KAAG,QA2BnC,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Array, Effect, Match as M, Number, Schema as S } from 'effect';
|
|
2
|
+
import * as Interruptible from '../../command/interruptible/index.js';
|
|
3
|
+
import { html } from '../../html/index.js';
|
|
4
|
+
import { m } from '../../message/index.js';
|
|
5
|
+
import { evo } from '../../struct/index.js';
|
|
6
|
+
// MODEL
|
|
7
|
+
export const UploadStatus = S.Literals([
|
|
8
|
+
'Uploading',
|
|
9
|
+
'Done',
|
|
10
|
+
'Cancelled',
|
|
11
|
+
'Failed',
|
|
12
|
+
]);
|
|
13
|
+
export const Upload = S.Struct({
|
|
14
|
+
id: S.Number,
|
|
15
|
+
status: UploadStatus,
|
|
16
|
+
});
|
|
17
|
+
export const Model = S.Struct({
|
|
18
|
+
uploadId: S.Number,
|
|
19
|
+
uploads: S.Array(Upload),
|
|
20
|
+
});
|
|
21
|
+
// MESSAGE
|
|
22
|
+
export const ClickedStartUpload = m('ClickedStartUpload');
|
|
23
|
+
export const ClickedRetryUpload = m('ClickedRetryUpload', {
|
|
24
|
+
uploadId: S.Number,
|
|
25
|
+
});
|
|
26
|
+
export const ClickedCancelUpload = m('ClickedCancelUpload', {
|
|
27
|
+
uploadId: S.Number,
|
|
28
|
+
});
|
|
29
|
+
export const SucceededUploadFile = m('SucceededUploadFile', {
|
|
30
|
+
uploadId: S.Number,
|
|
31
|
+
});
|
|
32
|
+
export const FailedUploadFile = m('FailedUploadFile', { uploadId: S.Number });
|
|
33
|
+
export const CompletedCancelUploadFile = m('CompletedCancelUploadFile', {
|
|
34
|
+
uploadId: S.Number,
|
|
35
|
+
outcome: Interruptible.Outcome,
|
|
36
|
+
});
|
|
37
|
+
export const Message = S.Union([
|
|
38
|
+
ClickedStartUpload,
|
|
39
|
+
ClickedRetryUpload,
|
|
40
|
+
ClickedCancelUpload,
|
|
41
|
+
SucceededUploadFile,
|
|
42
|
+
FailedUploadFile,
|
|
43
|
+
CompletedCancelUploadFile,
|
|
44
|
+
]);
|
|
45
|
+
// COMMAND
|
|
46
|
+
export const UploadFileArgs = S.Struct({ uploadId: S.Number });
|
|
47
|
+
export const UploadFile = Interruptible.define('UploadFile', UploadFileArgs.fields, ({ uploadId }) => String(uploadId), SucceededUploadFile, FailedUploadFile)(({ uploadId }) => Effect.as(Effect.never, SucceededUploadFile({ uploadId })));
|
|
48
|
+
export const CancelUploadFile = ({ uploadId }) => UploadFile.Interrupt({ uploadId }, outcome => CompletedCancelUploadFile({ uploadId, outcome }));
|
|
49
|
+
// INIT
|
|
50
|
+
export const initialModel = { uploadId: 0, uploads: [] };
|
|
51
|
+
// UPDATE
|
|
52
|
+
const setStatusById = (uploadId, status) => Array.map((upload) => upload.id === uploadId ? evo(upload, { status: () => status }) : upload);
|
|
53
|
+
export const update = (model, message) => M.value(message).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
54
|
+
ClickedStartUpload: () => {
|
|
55
|
+
const startedUpload = {
|
|
56
|
+
id: model.uploadId,
|
|
57
|
+
status: 'Uploading',
|
|
58
|
+
};
|
|
59
|
+
return [
|
|
60
|
+
evo(model, {
|
|
61
|
+
uploadId: Number.increment,
|
|
62
|
+
uploads: Array.append(startedUpload),
|
|
63
|
+
}),
|
|
64
|
+
[UploadFile({ uploadId: model.uploadId })],
|
|
65
|
+
];
|
|
66
|
+
},
|
|
67
|
+
ClickedRetryUpload: ({ uploadId }) => [
|
|
68
|
+
evo(model, { uploads: setStatusById(uploadId, 'Uploading') }),
|
|
69
|
+
[UploadFile({ uploadId })],
|
|
70
|
+
],
|
|
71
|
+
ClickedCancelUpload: ({ uploadId }) => [
|
|
72
|
+
model,
|
|
73
|
+
[CancelUploadFile({ uploadId })],
|
|
74
|
+
],
|
|
75
|
+
SucceededUploadFile: ({ uploadId }) => [
|
|
76
|
+
evo(model, { uploads: setStatusById(uploadId, 'Done') }),
|
|
77
|
+
[],
|
|
78
|
+
],
|
|
79
|
+
FailedUploadFile: ({ uploadId }) => [
|
|
80
|
+
evo(model, { uploads: setStatusById(uploadId, 'Failed') }),
|
|
81
|
+
[],
|
|
82
|
+
],
|
|
83
|
+
CompletedCancelUploadFile: ({ uploadId, outcome }) => M.value(outcome).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
84
|
+
Interrupted: () => [
|
|
85
|
+
evo(model, { uploads: setStatusById(uploadId, 'Cancelled') }),
|
|
86
|
+
[],
|
|
87
|
+
],
|
|
88
|
+
NotFound: () => [model, []],
|
|
89
|
+
})),
|
|
90
|
+
}));
|
|
91
|
+
// VIEW
|
|
92
|
+
export const view = (model) => {
|
|
93
|
+
const h = html();
|
|
94
|
+
const body = h.div([], [
|
|
95
|
+
h.button([h.OnClick(ClickedStartUpload())], ['Start upload']),
|
|
96
|
+
h.ul([], Array.map(model.uploads, upload => h.keyed('li')(String(upload.id), [], [
|
|
97
|
+
h.span([], [`upload ${upload.id}: ${upload.status}`]),
|
|
98
|
+
h.button([h.OnClick(ClickedCancelUpload({ uploadId: upload.id }))], [`Cancel upload ${upload.id}`]),
|
|
99
|
+
]))),
|
|
100
|
+
]);
|
|
101
|
+
return { title: 'Uploads', body };
|
|
102
|
+
};
|
package/dist/test/internal.d.ts
CHANGED
|
@@ -10,10 +10,18 @@ import { type MountDefinition } from '../mount/index.js';
|
|
|
10
10
|
* `Command.mapMessage`/`mapMessages`). It rides along on pending Commands so
|
|
11
11
|
* `resolve` can apply the parent's wrapping to a substitute result Message
|
|
12
12
|
* without the test restating it. Absent on synthetic matchers built for
|
|
13
|
-
* formatting; treated as empty then.
|
|
13
|
+
* formatting; treated as empty then.
|
|
14
|
+
*
|
|
15
|
+
* `key` is present on Commands built with `Command.Interruptible.define`:
|
|
16
|
+
* such Commands may stay pending across Messages (they model long-running
|
|
17
|
+
* work). `interruptsKey` is present on Interrupt Commands: resolving one
|
|
18
|
+
* drops every pending Command holding that key, mirroring the runtime
|
|
19
|
+
* guarantee that an interrupted Command's result Message never dispatches. */
|
|
14
20
|
export type AnyCommand = Readonly<{
|
|
15
21
|
name: string;
|
|
16
22
|
args?: Record<string, unknown>;
|
|
23
|
+
key?: string;
|
|
24
|
+
interruptsKey?: string;
|
|
17
25
|
messageMappers?: ReadonlyArray<(message: unknown) => unknown>;
|
|
18
26
|
}>;
|
|
19
27
|
/** Pattern for matching a Command in test assertions. A Definition matches
|
|
@@ -135,7 +143,13 @@ export declare const assertHasCommands: (commands: ReadonlyArray<AnyCommand>, ma
|
|
|
135
143
|
export declare const assertExactCommands: (commands: ReadonlyArray<AnyCommand>, matchers: ReadonlyArray<CommandMatcher>) => void;
|
|
136
144
|
/** Throws if there are any pending Commands. */
|
|
137
145
|
export declare const assertZeroCommands: (commands: ReadonlyArray<AnyCommand>) => void;
|
|
138
|
-
/** Throws when trying to send a message with unresolved Commands.
|
|
146
|
+
/** Throws when trying to send a message with unresolved Commands. Keyed
|
|
147
|
+
* Commands (built with `Command.Interruptible.define`) and Interrupt
|
|
148
|
+
* Commands are exempt: they model in-flight async work that legitimately
|
|
149
|
+
* stays pending while later Messages arrive, so a story can dispatch the
|
|
150
|
+
* Message that interrupts a keyed Command, or keep typing while a
|
|
151
|
+
* cancellation is in flight. They must still be resolved by the end of the
|
|
152
|
+
* test. */
|
|
139
153
|
export declare const assertNoUnresolvedCommands: (commands: ReadonlyArray<AnyCommand>, context: string) => void;
|
|
140
154
|
/** Throws when Commands remain at the end of a test. */
|
|
141
155
|
export declare const assertAllCommandsResolved: (commands: ReadonlyArray<AnyCommand>) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/test/internal.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,KAAK,eAAe,EAAyB,MAAM,mBAAmB,CAAA;AAE/E
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/test/internal.ts"],"names":[],"mappings":"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"}
|
package/dist/test/internal.js
CHANGED
|
@@ -31,6 +31,14 @@ export const mountMatches = (matcher, mount) => matcher.name === mount.name &&
|
|
|
31
31
|
export const formatMountMatcher = (matcher) => isMountDefinitionMatcher(matcher)
|
|
32
32
|
? matcher.name
|
|
33
33
|
: `${matcher.name}${formatArgs(matcher.args)}`;
|
|
34
|
+
/** Drops every pending Command holding `interruptsKey` when a resolved
|
|
35
|
+
* Command declares one (an Interrupt Command). Resolving the Interrupt
|
|
36
|
+
* before the target declares that the interrupt won the race, so the
|
|
37
|
+
* target's result Message never arrives; resolving the target first models
|
|
38
|
+
* completion before the interrupt ran. */
|
|
39
|
+
const dropInterruptedCommands = (pending, resolvedCommand) => Predicate.isUndefined(resolvedCommand.interruptsKey)
|
|
40
|
+
? pending
|
|
41
|
+
: Array.filter(pending, pendingCommand => pendingCommand.key !== resolvedCommand.interruptsKey);
|
|
34
42
|
/** Folds a recorded message-mapping chain over `resultMessage`, reproducing the
|
|
35
43
|
* wrapping a resolved Command or mounted child's result travels through in
|
|
36
44
|
* production: the parent's `Command.mapMessages` for a Command, or the
|
|
@@ -45,7 +53,7 @@ const foldMessageMappers = (messageMappers, resultMessage) => Array.reduce(messa
|
|
|
45
53
|
export const resolveByMatcher = (internal, matcher, resultMessage) => pipe(internal.commands, Array.findFirstIndex(command => commandMatches(matcher, command)), Option.flatMap(commandIndex => pipe(internal.commands, Array.get(commandIndex), Option.map(matchedCommand => {
|
|
46
54
|
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
47
55
|
const resolverMessage = foldMessageMappers(matchedCommand.messageMappers, resultMessage);
|
|
48
|
-
const remainingCommands = Array.remove(internal.commands, commandIndex);
|
|
56
|
+
const remainingCommands = dropInterruptedCommands(Array.remove(internal.commands, commandIndex), matchedCommand);
|
|
49
57
|
const [nextModel, newCommands, ...rest] = internal.updateFn(internal.model, resolverMessage);
|
|
50
58
|
const outMessage = Array.matchLeft(rest, {
|
|
51
59
|
onEmpty: () => internal.outMessage,
|
|
@@ -155,10 +163,18 @@ export const assertZeroCommands = (commands) => {
|
|
|
155
163
|
throw new Error(`Expected no Commands but found:\n\n${formatCommandList(commands)}`);
|
|
156
164
|
}
|
|
157
165
|
};
|
|
158
|
-
/** Throws when trying to send a message with unresolved Commands.
|
|
166
|
+
/** Throws when trying to send a message with unresolved Commands. Keyed
|
|
167
|
+
* Commands (built with `Command.Interruptible.define`) and Interrupt
|
|
168
|
+
* Commands are exempt: they model in-flight async work that legitimately
|
|
169
|
+
* stays pending while later Messages arrive, so a story can dispatch the
|
|
170
|
+
* Message that interrupts a keyed Command, or keep typing while a
|
|
171
|
+
* cancellation is in flight. They must still be resolved by the end of the
|
|
172
|
+
* test. */
|
|
159
173
|
export const assertNoUnresolvedCommands = (commands, context) => {
|
|
160
|
-
|
|
161
|
-
|
|
174
|
+
const unresolvable = Array.filter(commands, command => Predicate.isUndefined(command.key) &&
|
|
175
|
+
Predicate.isUndefined(command.interruptsKey));
|
|
176
|
+
if (Array.isReadonlyArrayNonEmpty(unresolvable)) {
|
|
177
|
+
throw new Error(`I found unresolved Commands ${context}:\n\n${formatCommandList(unresolvable)}\n\n` +
|
|
162
178
|
'Resolve all Commands before sending the next Message.\n' +
|
|
163
179
|
'Use resolve(Definition | instance, ResultMessage) for each one,\n' +
|
|
164
180
|
'or resolveAll(...resolvers) to resolve them all at once.');
|
package/dist/vdom.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Option } from 'effect';
|
|
2
|
-
import { type VNode, toVNode } from 'snabbdom';
|
|
3
|
-
export type { VNode } from 'snabbdom';
|
|
2
|
+
import { type VNode, toVNode } from './snabbdom/index.js';
|
|
3
|
+
export type { VNode } from './snabbdom/index.js';
|
|
4
4
|
export { toVNode };
|
|
5
5
|
export declare const patch: (oldVnode: VNode | Element | DocumentFragment, vnode: VNode) => VNode;
|
|
6
6
|
export declare const memoizedVNodes: WeakSet<VNode>;
|
package/dist/vdom.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vdom.d.ts","sourceRoot":"","sources":["../src/vdom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAa,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"vdom.d.ts","sourceRoot":"","sources":["../src/vdom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAa,MAAM,QAAQ,CAAA;AAG1C,OAAO,EACL,KAAK,KAAK,EAQV,OAAO,EACR,MAAM,qBAAqB,CAAA;AAE5B,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,CAAA;AAElB,eAAO,MAAM,KAAK,uEAOhB,CAAA;AAsBF,eAAO,MAAM,cAAc,gBAAuB,CAAA;AA2ClD;;;;mBAImB;AACnB,eAAO,MAAM,kBAAkB,GAC7B,MAAM,KAAK,EACX,OAAM,GAAG,CAAC,MAAM,CAAa,KAC5B,KAA0B,CAAA;AAE7B;;;;;mBAKmB;AACnB,eAAO,MAAM,oBAAoB,GAC/B,MAAM,KAAK,EACX,OAAM,GAAG,CAAC,MAAM,CAAa,KAC5B,KAMF,CAAA;AAED,eAAO,MAAM,YAAY,GACvB,mBAAmB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EACvC,WAAW,KAAK,GAAG,IAAI,EACvB,WAAW,WAAW,EACtB,OAAO,GAAG,CAAC,MAAM,CAAC,KACjB,KASF,CAAA"}
|
package/dist/vdom.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Option, Predicate } from 'effect';
|
|
2
|
-
import { attributesModule, classModule, datasetModule, eventListenersModule, h, init, styleModule, toVNode, } from 'snabbdom';
|
|
3
2
|
import { propsModule } from './propsModule.js';
|
|
3
|
+
import { attributesModule, classModule, datasetModule, eventListenersModule, h, init, styleModule, toVNode, } from './snabbdom/index.js';
|
|
4
4
|
export { toVNode };
|
|
5
5
|
export const patch = init([
|
|
6
6
|
attributesModule,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foldkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.130.0",
|
|
4
4
|
"description": "A TypeScript frontend framework, built on Effect and architected like Elm",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"types": "./dist/asyncData/public.d.ts",
|
|
16
16
|
"import": "./dist/asyncData/public.js"
|
|
17
17
|
},
|
|
18
|
+
"./brand": {
|
|
19
|
+
"types": "./dist/brand/public.d.ts",
|
|
20
|
+
"import": "./dist/brand/public.js"
|
|
21
|
+
},
|
|
18
22
|
"./calendar": {
|
|
19
23
|
"types": "./dist/calendar/public.d.ts",
|
|
20
24
|
"import": "./dist/calendar/public.js"
|
|
@@ -146,7 +150,8 @@
|
|
|
146
150
|
},
|
|
147
151
|
"sideEffects": false,
|
|
148
152
|
"files": [
|
|
149
|
-
"dist"
|
|
153
|
+
"dist",
|
|
154
|
+
"THIRD-PARTY-NOTICES.md"
|
|
150
155
|
],
|
|
151
156
|
"peerDependencies": {
|
|
152
157
|
"@effect/platform-browser": "4.0.0-beta.97",
|
|
@@ -177,9 +182,6 @@
|
|
|
177
182
|
"url": "https://github.com/foldkit/foldkit.git",
|
|
178
183
|
"directory": "packages/foldkit"
|
|
179
184
|
},
|
|
180
|
-
"dependencies": {
|
|
181
|
-
"snabbdom": "^3.6.3"
|
|
182
|
-
},
|
|
183
185
|
"publishConfig": {
|
|
184
186
|
"access": "public"
|
|
185
187
|
},
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type Priority = 'High' | 'Normal';
|
|
2
|
-
export type EnvelopedMessage<Message> = Readonly<{
|
|
3
|
-
priority: Priority;
|
|
4
|
-
message: Message;
|
|
5
|
-
}>;
|
|
6
|
-
/** Reorders a batch of EnvelopedMessages so all `High` envelopes appear
|
|
7
|
-
* before any `Normal` envelope, preserving FIFO order within each priority
|
|
8
|
-
* class. The runtime calls this on each `Queue.takeAll` batch so user input
|
|
9
|
-
* (view dispatch, navigation, subscription events, managed-resource events,
|
|
10
|
-
* external dispatchers) lands ahead of chain-derived work (Command results)
|
|
11
|
-
* whenever both share a frame.
|
|
12
|
-
*
|
|
13
|
-
* Single-pass partition: walks the batch once and pushes each unwrapped
|
|
14
|
-
* message into either `highs` or `normals`, then concatenates. Avoids the
|
|
15
|
-
* four allocations of the previous filter/filter/appendAll/map chain. */
|
|
16
|
-
export declare const orderByPriority: <Message>(batch: ReadonlyArray<EnvelopedMessage<Message>>) => ReadonlyArray<Message>;
|
|
17
|
-
//# sourceMappingURL=messagePriority.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"messagePriority.d.ts","sourceRoot":"","sources":["../../src/runtime/messagePriority.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;AAExC,MAAM,MAAM,gBAAgB,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC/C,QAAQ,EAAE,QAAQ,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;CACjB,CAAC,CAAA;AAEF;;;;;;;;;0EAS0E;AAC1E,eAAO,MAAM,eAAe,GAAI,OAAO,EACrC,OAAO,aAAa,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,KAC9C,aAAa,CAAC,OAAO,CAoBvB,CAAA"}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Array } from 'effect';
|
|
2
|
-
/** Reorders a batch of EnvelopedMessages so all `High` envelopes appear
|
|
3
|
-
* before any `Normal` envelope, preserving FIFO order within each priority
|
|
4
|
-
* class. The runtime calls this on each `Queue.takeAll` batch so user input
|
|
5
|
-
* (view dispatch, navigation, subscription events, managed-resource events,
|
|
6
|
-
* external dispatchers) lands ahead of chain-derived work (Command results)
|
|
7
|
-
* whenever both share a frame.
|
|
8
|
-
*
|
|
9
|
-
* Single-pass partition: walks the batch once and pushes each unwrapped
|
|
10
|
-
* message into either `highs` or `normals`, then concatenates. Avoids the
|
|
11
|
-
* four allocations of the previous filter/filter/appendAll/map chain. */
|
|
12
|
-
export const orderByPriority = (batch) => {
|
|
13
|
-
const highs = [];
|
|
14
|
-
const normals = [];
|
|
15
|
-
for (const envelope of batch) {
|
|
16
|
-
if (envelope.priority === 'High') {
|
|
17
|
-
highs.push(envelope.message);
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
normals.push(envelope.message);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
if (Array.isReadonlyArrayEmpty(normals)) {
|
|
24
|
-
return highs;
|
|
25
|
-
}
|
|
26
|
-
if (Array.isReadonlyArrayEmpty(highs)) {
|
|
27
|
-
return normals;
|
|
28
|
-
}
|
|
29
|
-
for (const message of normals) {
|
|
30
|
-
highs.push(message);
|
|
31
|
-
}
|
|
32
|
-
return highs;
|
|
33
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Effect, SubscriptionRef } from 'effect';
|
|
2
|
-
export type RenderLoopConfig = Readonly<{
|
|
3
|
-
/** SubscriptionRef holding the dirty bit. The loop subscribes to its changes
|
|
4
|
-
* Stream so it can suspend entirely when the bit stays false. */
|
|
5
|
-
pendingRef: SubscriptionRef.SubscriptionRef<boolean>;
|
|
6
|
-
/** Effect that yields once the next animation frame fires. */
|
|
7
|
-
awaitNextFrame: Effect.Effect<void>;
|
|
8
|
-
/** Effect that returns whether the runtime is paused (e.g. via DevTools
|
|
9
|
-
* time-travel). When true, the body skips render but still clears the bit. */
|
|
10
|
-
isPaused: Effect.Effect<boolean>;
|
|
11
|
-
/** Effect that performs one render with the latest model. */
|
|
12
|
-
render: Effect.Effect<void>;
|
|
13
|
-
}>;
|
|
14
|
-
/** Builds an idle-suspending render loop driven by a SubscriptionRef of
|
|
15
|
-
* "render is pending."
|
|
16
|
-
*
|
|
17
|
-
* The loop subscribes to the ref's changes Stream and runs the body once per
|
|
18
|
-
* false-to-true transition. Stream.changes filters consecutive equals so a
|
|
19
|
-
* burst of `set(true)` calls inside one frame produces a single body run.
|
|
20
|
-
*
|
|
21
|
-
* The body waits for the next animation frame, clears the dirty bit, then
|
|
22
|
-
* renders if not paused. The bit is cleared on every body run (including
|
|
23
|
-
* paused early-returns) so the next dispatch is always a real false-to-true
|
|
24
|
-
* transition. This makes the loop self-recovering even when paused state
|
|
25
|
-
* flips externally without a synchronous render. */
|
|
26
|
-
export declare const makeRenderLoop: ({ pendingRef, awaitNextFrame, isPaused, render, }: RenderLoopConfig) => Effect.Effect<void>;
|
|
27
|
-
//# sourceMappingURL=renderLoop.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"renderLoop.d.ts","sourceRoot":"","sources":["../../src/runtime/renderLoop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,eAAe,EAAE,MAAM,QAAQ,CAAA;AAExD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC;sEACkE;IAClE,UAAU,EAAE,eAAe,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IACpD,8DAA8D;IAC9D,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACnC;mFAC+E;IAC/E,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAChC,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAC5B,CAAC,CAAA;AAEF;;;;;;;;;;;qDAWqD;AACrD,eAAO,MAAM,cAAc,GAAI,mDAK5B,gBAAgB,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAgBvC,CAAA"}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Effect, Stream, SubscriptionRef } from 'effect';
|
|
2
|
-
/** Builds an idle-suspending render loop driven by a SubscriptionRef of
|
|
3
|
-
* "render is pending."
|
|
4
|
-
*
|
|
5
|
-
* The loop subscribes to the ref's changes Stream and runs the body once per
|
|
6
|
-
* false-to-true transition. Stream.changes filters consecutive equals so a
|
|
7
|
-
* burst of `set(true)` calls inside one frame produces a single body run.
|
|
8
|
-
*
|
|
9
|
-
* The body waits for the next animation frame, clears the dirty bit, then
|
|
10
|
-
* renders if not paused. The bit is cleared on every body run (including
|
|
11
|
-
* paused early-returns) so the next dispatch is always a real false-to-true
|
|
12
|
-
* transition. This makes the loop self-recovering even when paused state
|
|
13
|
-
* flips externally without a synchronous render. */
|
|
14
|
-
export const makeRenderLoop = ({ pendingRef, awaitNextFrame, isPaused, render, }) => {
|
|
15
|
-
const tick = Effect.gen(function* () {
|
|
16
|
-
yield* awaitNextFrame;
|
|
17
|
-
yield* SubscriptionRef.set(pendingRef, false);
|
|
18
|
-
const paused = yield* isPaused;
|
|
19
|
-
if (paused) {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
yield* render;
|
|
23
|
-
});
|
|
24
|
-
return SubscriptionRef.changes(pendingRef).pipe(Stream.changes, Stream.filter(isPending => isPending), Stream.runForEach(() => tick));
|
|
25
|
-
};
|