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.
Files changed (104) hide show
  1. package/THIRD-PARTY-NOTICES.md +30 -0
  2. package/dist/brand/brand.d.ts +25 -0
  3. package/dist/brand/brand.d.ts.map +1 -0
  4. package/dist/brand/brand.js +50 -0
  5. package/dist/brand/index.d.ts +2 -0
  6. package/dist/brand/index.d.ts.map +1 -0
  7. package/dist/brand/index.js +1 -0
  8. package/dist/brand/public.d.ts +2 -0
  9. package/dist/brand/public.d.ts.map +1 -0
  10. package/dist/brand/public.js +1 -0
  11. package/dist/canvas/view.d.ts.map +1 -1
  12. package/dist/canvas/view.js +1 -1
  13. package/dist/command/index.d.ts +6 -1
  14. package/dist/command/index.d.ts.map +1 -1
  15. package/dist/command/interruptible/index.d.ts +2 -0
  16. package/dist/command/interruptible/index.d.ts.map +1 -0
  17. package/dist/command/interruptible/index.js +1 -0
  18. package/dist/command/interruptible/interruptible.d.ts +177 -0
  19. package/dist/command/interruptible/interruptible.d.ts.map +1 -0
  20. package/dist/command/interruptible/interruptible.js +155 -0
  21. package/dist/command/interruptible/public.d.ts +3 -0
  22. package/dist/command/interruptible/public.d.ts.map +1 -0
  23. package/dist/command/interruptible/public.js +1 -0
  24. package/dist/command/public.d.ts +1 -0
  25. package/dist/command/public.d.ts.map +1 -1
  26. package/dist/command/public.js +1 -0
  27. package/dist/devTools/protocol.d.ts +1 -1
  28. package/dist/devTools/protocol.d.ts.map +1 -1
  29. package/dist/devTools/protocol.js +1 -1
  30. package/dist/devTools/store.js +2 -2
  31. package/dist/devTools/webSocketBridge.d.ts +1 -1
  32. package/dist/devTools/webSocketBridge.js +1 -1
  33. package/dist/html/index.d.ts +18 -18
  34. package/dist/html/index.d.ts.map +1 -1
  35. package/dist/html/index.js +530 -460
  36. package/dist/html/lazy.d.ts +1 -1
  37. package/dist/html/lazy.d.ts.map +1 -1
  38. package/dist/html/lazy.js +11 -2
  39. package/dist/propsModule.d.ts +1 -1
  40. package/dist/propsModule.d.ts.map +1 -1
  41. package/dist/propsModule.js +6 -1
  42. package/dist/runtime/runtime.d.ts.map +1 -1
  43. package/dist/runtime/runtime.js +472 -279
  44. package/dist/snabbdom/attributes.d.ts +4 -0
  45. package/dist/snabbdom/attributes.d.ts.map +1 -0
  46. package/dist/snabbdom/attributes.js +63 -0
  47. package/dist/snabbdom/class.d.ts +4 -0
  48. package/dist/snabbdom/class.d.ts.map +1 -0
  49. package/dist/snabbdom/class.js +31 -0
  50. package/dist/snabbdom/dataset.d.ts +4 -0
  51. package/dist/snabbdom/dataset.d.ts.map +1 -0
  52. package/dist/snabbdom/dataset.js +42 -0
  53. package/dist/snabbdom/eventlisteners.d.ts +11 -0
  54. package/dist/snabbdom/eventlisteners.d.ts.map +1 -0
  55. package/dist/snabbdom/eventlisteners.js +85 -0
  56. package/dist/snabbdom/h.d.ts +11 -0
  57. package/dist/snabbdom/h.d.ts.map +1 -0
  58. package/dist/snabbdom/h.js +61 -0
  59. package/dist/snabbdom/hooks.d.ts +24 -0
  60. package/dist/snabbdom/hooks.d.ts.map +1 -0
  61. package/dist/snabbdom/hooks.js +1 -0
  62. package/dist/snabbdom/htmldomapi.d.ts +34 -0
  63. package/dist/snabbdom/htmldomapi.d.ts.map +1 -0
  64. package/dist/snabbdom/htmldomapi.js +111 -0
  65. package/dist/snabbdom/index.d.ts +15 -0
  66. package/dist/snabbdom/index.d.ts.map +1 -0
  67. package/dist/snabbdom/index.js +9 -0
  68. package/dist/snabbdom/init.d.ts +14 -0
  69. package/dist/snabbdom/init.d.ts.map +1 -0
  70. package/dist/snabbdom/init.js +486 -0
  71. package/dist/snabbdom/is.d.ts +3 -0
  72. package/dist/snabbdom/is.d.ts.map +1 -0
  73. package/dist/snabbdom/is.js +7 -0
  74. package/dist/snabbdom/module.d.ts +11 -0
  75. package/dist/snabbdom/module.d.ts.map +1 -0
  76. package/dist/snabbdom/module.js +1 -0
  77. package/dist/snabbdom/props.d.ts +2 -0
  78. package/dist/snabbdom/props.d.ts.map +1 -0
  79. package/dist/snabbdom/props.js +1 -0
  80. package/dist/snabbdom/style.d.ts +9 -0
  81. package/dist/snabbdom/style.d.ts.map +1 -0
  82. package/dist/snabbdom/style.js +123 -0
  83. package/dist/snabbdom/tovnode.d.ts +4 -0
  84. package/dist/snabbdom/tovnode.d.ts.map +1 -0
  85. package/dist/snabbdom/tovnode.js +69 -0
  86. package/dist/snabbdom/vnode.d.ts +46 -0
  87. package/dist/snabbdom/vnode.d.ts.map +1 -0
  88. package/dist/snabbdom/vnode.js +13 -0
  89. package/dist/test/apps/uploads.d.ts +81 -0
  90. package/dist/test/apps/uploads.d.ts.map +1 -0
  91. package/dist/test/apps/uploads.js +102 -0
  92. package/dist/test/internal.d.ts +16 -2
  93. package/dist/test/internal.d.ts.map +1 -1
  94. package/dist/test/internal.js +20 -4
  95. package/dist/vdom.d.ts +2 -2
  96. package/dist/vdom.d.ts.map +1 -1
  97. package/dist/vdom.js +1 -1
  98. package/package.json +7 -5
  99. package/dist/runtime/messagePriority.d.ts +0 -17
  100. package/dist/runtime/messagePriority.d.ts.map +0 -1
  101. package/dist/runtime/messagePriority.js +0 -33
  102. package/dist/runtime/renderLoop.d.ts +0 -27
  103. package/dist/runtime/renderLoop.d.ts.map +0 -1
  104. 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
+ };
@@ -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":"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"}
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/test/internal.ts"],"names":[],"mappings":"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"}
@@ -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
- if (Array.isReadonlyArrayNonEmpty(commands)) {
161
- throw new Error(`I found unresolved Commands ${context}:\n\n${formatCommandList(commands)}\n\n` +
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>;
@@ -1 +1 @@
1
- {"version":3,"file":"vdom.d.ts","sourceRoot":"","sources":["../src/vdom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAa,MAAM,QAAQ,CAAA;AAC1C,OAAO,EACL,KAAK,KAAK,EAQV,OAAO,EACR,MAAM,UAAU,CAAA;AAIjB,YAAY,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AACrC,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"}
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.128.1",
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
- };