@kirill.konshin/utils 0.0.14 → 0.0.15

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 (107) hide show
  1. package/.ctirc +2 -1
  2. package/.turbo/turbo-build.log +18 -13
  3. package/.turbo/turbo-test.log +78 -25
  4. package/CHANGELOG.md +6 -0
  5. package/README.md +1 -1
  6. package/demo/{cache-demo-clean.ts → worker-demo.ts} +1 -1
  7. package/dist/bootstrap/controls.d.ts +5 -2
  8. package/dist/bootstrap/controls.d.ts.map +1 -1
  9. package/dist/bootstrap/controls.js +1 -0
  10. package/dist/bootstrap/controls.js.map +1 -1
  11. package/dist/bootstrap/controls.stories.d.ts +9 -0
  12. package/dist/bootstrap/controls.stories.d.ts.map +1 -0
  13. package/dist/bootstrap/error.stories.d.ts +8 -0
  14. package/dist/bootstrap/error.stories.d.ts.map +1 -0
  15. package/dist/bootstrap/field.stories.d.ts +8 -0
  16. package/dist/bootstrap/field.stories.d.ts.map +1 -0
  17. package/dist/bootstrap/globalLoading.stories.d.ts +7 -0
  18. package/dist/bootstrap/globalLoading.stories.d.ts.map +1 -0
  19. package/dist/bootstrap/index.js +2 -1
  20. package/dist/bootstrap/loading.stories.d.ts +9 -0
  21. package/dist/bootstrap/loading.stories.d.ts.map +1 -0
  22. package/dist/bootstrap/screen.stories.d.ts +14 -0
  23. package/dist/bootstrap/screen.stories.d.ts.map +1 -0
  24. package/dist/bootstrap/toaster.stories.d.ts +8 -0
  25. package/dist/bootstrap/toaster.stories.d.ts.map +1 -0
  26. package/dist/bootstrap/useModal.stories.d.ts +9 -0
  27. package/dist/bootstrap/useModal.stories.d.ts.map +1 -0
  28. package/dist/core/cache.d.ts +7 -8
  29. package/dist/core/cache.d.ts.map +1 -1
  30. package/dist/core/cache.js +7 -4
  31. package/dist/core/cache.js.map +1 -1
  32. package/dist/core/cache.test.d.ts +2 -0
  33. package/dist/core/cache.test.d.ts.map +1 -0
  34. package/dist/core/files.d.ts +3 -0
  35. package/dist/core/files.d.ts.map +1 -0
  36. package/dist/core/files.js +27 -0
  37. package/dist/core/files.js.map +1 -0
  38. package/dist/core/index.d.ts +4 -0
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +14 -2
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/core/ls.d.ts +6 -0
  43. package/dist/core/ls.d.ts.map +1 -0
  44. package/dist/core/ls.js +12 -0
  45. package/dist/core/ls.js.map +1 -0
  46. package/dist/core/worker.d.ts +23 -9
  47. package/dist/core/worker.d.ts.map +1 -1
  48. package/dist/core/worker.js +93 -90
  49. package/dist/core/worker.js.map +1 -1
  50. package/dist/core/worker.test.d.ts +2 -0
  51. package/dist/core/worker.test.d.ts.map +1 -0
  52. package/dist/core/workerUtils.d.ts +3 -0
  53. package/dist/core/workerUtils.d.ts.map +1 -0
  54. package/dist/core/workerUtils.js +31 -0
  55. package/dist/core/workerUtils.js.map +1 -0
  56. package/dist/core/wrpc.d.ts +45 -0
  57. package/dist/core/wrpc.d.ts.map +1 -0
  58. package/dist/core/wrpc.fixture.d.ts +29 -0
  59. package/dist/core/wrpc.fixture.d.ts.map +1 -0
  60. package/dist/core/wrpc.js +242 -0
  61. package/dist/core/wrpc.js.map +1 -0
  62. package/dist/core/wrpc.test.d.ts +1 -0
  63. package/dist/core/wrpc.test.d.ts.map +1 -0
  64. package/dist/mui/formControlFieldset.stories.d.ts +7 -0
  65. package/dist/mui/formControlFieldset.stories.d.ts.map +1 -0
  66. package/dist/react/form/form.d.ts +7 -6
  67. package/dist/react/form/form.d.ts.map +1 -1
  68. package/dist/react/form/form.js +14 -20
  69. package/dist/react/form/form.js.map +1 -1
  70. package/dist/react/index.d.ts +1 -0
  71. package/dist/react/index.d.ts.map +1 -1
  72. package/dist/react/index.js +8 -2
  73. package/dist/react/index.js.map +1 -1
  74. package/dist/react/keyboard.d.ts +11 -0
  75. package/dist/react/keyboard.d.ts.map +1 -0
  76. package/dist/react/keyboard.js +38 -0
  77. package/dist/react/keyboard.js.map +1 -0
  78. package/package.json +5 -2
  79. package/src/bootstrap/controls.stories.tsx +2 -2
  80. package/src/bootstrap/controls.tsx +3 -3
  81. package/src/bootstrap/error.stories.tsx +3 -3
  82. package/src/bootstrap/field.stories.tsx +2 -2
  83. package/src/bootstrap/globalLoading.stories.tsx +2 -2
  84. package/src/bootstrap/loading.stories.tsx +2 -2
  85. package/src/bootstrap/screen.stories.tsx +2 -2
  86. package/src/bootstrap/toaster.stories.tsx +2 -2
  87. package/src/bootstrap/useModal.stories.tsx +3 -3
  88. package/src/core/README.md +91 -0
  89. package/src/core/cache.ts +12 -12
  90. package/src/core/files.ts +24 -0
  91. package/src/core/index.ts +4 -0
  92. package/src/core/ls.ts +11 -0
  93. package/src/core/worker.test.ts +197 -0
  94. package/src/core/worker.ts +106 -93
  95. package/src/core/workerUtils.ts +32 -0
  96. package/src/core/wrpc.fixture.ts +72 -0
  97. package/src/core/wrpc.test.ts +225 -0
  98. package/src/core/wrpc.ts +359 -0
  99. package/src/mui/formControlFieldset.stories.tsx +1 -1
  100. package/src/react/form/form.tsx +53 -33
  101. package/src/react/index.ts +1 -0
  102. package/src/react/keyboard.tsx +49 -0
  103. package/src/tailwind/README.md +8 -0
  104. package/tsconfig.json +2 -1
  105. package/vite.config.ts +10 -3
  106. package/vite.exports.ts +3 -3
  107. package/demo/cache-demo.ts +0 -343
@@ -0,0 +1,31 @@
1
+ const g = globalThis;
2
+ const transferrable = [
3
+ g.ArrayBuffer,
4
+ g.MessagePort,
5
+ g.ReadableStream,
6
+ g.WritableStream,
7
+ g.TransformStream,
8
+ // WebTransportReceiveStream, // ReferenceError: WebTransportReceiveStream is not defined
9
+ // WebTransportSendStream, // ReferenceError: WebTransportReceiveStream is not defined
10
+ // AudioData, // TS2304: Cannot find name AudioData
11
+ g.ImageBitmap,
12
+ g.VideoFrame,
13
+ g.OffscreenCanvas,
14
+ g.RTCDataChannel,
15
+ g.MediaSourceHandle
16
+ // MIDIAccess, // ReferenceError: MIDIAccess is not defined
17
+ ].filter(Boolean);
18
+ function isTransferable(obj) {
19
+ return transferrable.some((t) => obj instanceof t) ? obj : null;
20
+ }
21
+ function getTransferrable(data = {}) {
22
+ if (!data) return [];
23
+ return Object.values(data).reduce((r, v) => {
24
+ return Array.isArray(v) ? [...r, ...v.map(isTransferable)] : [...r, isTransferable(v)];
25
+ }, []).filter(Boolean);
26
+ }
27
+ export {
28
+ getTransferrable,
29
+ isTransferable
30
+ };
31
+ //# sourceMappingURL=workerUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workerUtils.js","sources":["../../src/core/workerUtils.ts"],"sourcesContent":["const g = globalThis;\n\n// @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects\nconst transferrable = [\n g.ArrayBuffer,\n g.MessagePort,\n g.ReadableStream,\n g.WritableStream,\n g.TransformStream,\n // WebTransportReceiveStream, // ReferenceError: WebTransportReceiveStream is not defined\n // WebTransportSendStream, // ReferenceError: WebTransportReceiveStream is not defined\n // AudioData, // TS2304: Cannot find name AudioData\n g.ImageBitmap,\n g.VideoFrame,\n g.OffscreenCanvas,\n g.RTCDataChannel,\n g.MediaSourceHandle,\n // MIDIAccess, // ReferenceError: MIDIAccess is not defined\n].filter(Boolean);\n\nexport function isTransferable(obj: any): boolean {\n return transferrable.some((t) => obj instanceof t) ? obj : null;\n}\n\nexport function getTransferrable(data: any = {}): Transferable[] {\n if (!data) return [];\n return Object.values<any>(data)\n .reduce((r, v) => {\n return Array.isArray(v) ? [...r, ...v.map(isTransferable)] : [...r, isTransferable(v)];\n }, [])\n .filter(Boolean);\n}\n"],"names":[],"mappings":"AAAA,MAAM,IAAI;AAGV,MAAM,gBAAgB;AAAA,EAClB,EAAE;AAAA,EACF,EAAE;AAAA,EACF,EAAE;AAAA,EACF,EAAE;AAAA,EACF,EAAE;AAAA;AAAA;AAAA;AAAA,EAIF,EAAE;AAAA,EACF,EAAE;AAAA,EACF,EAAE;AAAA,EACF,EAAE;AAAA,EACF,EAAE;AAAA;AAEN,EAAE,OAAO,OAAO;AAET,SAAS,eAAe,KAAmB;AAC9C,SAAO,cAAc,KAAK,CAAC,MAAM,eAAe,CAAC,IAAI,MAAM;AAC/D;AAEgB,SAAA,iBAAiB,OAAY,IAAoB;AACzD,MAAA,CAAC,KAAM,QAAO,CAAC;AACnB,SAAO,OAAO,OAAY,IAAI,EACzB,OAAO,CAAC,GAAG,MAAM;AACd,WAAO,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,GAAG,EAAE,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,GAAG,eAAe,CAAC,CAAC;AAAA,EAAA,GACtF,CAAE,CAAA,EACJ,OAAO,OAAO;AACvB;"}
@@ -0,0 +1,45 @@
1
+ export type Responders = {
2
+ [key: string]: (arg?: any) => Generator<any> | AsyncGenerator<any> | Promise<any> | any;
3
+ };
4
+ export type Context = {
5
+ message: string;
6
+ id: string;
7
+ signalName?: string;
8
+ done?: boolean;
9
+ abort?: string;
10
+ error?: boolean;
11
+ ack?: boolean;
12
+ promise?: boolean;
13
+ };
14
+ export type Event = MessageEvent<{
15
+ ctx: Context;
16
+ payload: any | Error;
17
+ }>;
18
+ export type WorkerLike = {
19
+ postMessage: (payload: any, transferable: any[]) => void;
20
+ addEventListener: (event: string, listener: (e: any) => void, options?: {
21
+ signal?: AbortSignal;
22
+ once?: boolean;
23
+ }) => void;
24
+ removeEventListener: (event: string, listener: (e: any) => void) => void;
25
+ };
26
+ export declare enum LogLevel {
27
+ disabled = 0,
28
+ error = 1,
29
+ lifecycle = 2,
30
+ detail = 3,
31
+ transport = 4
32
+ }
33
+ export declare function deriveController(signal?: AbortSignal): [AbortController, AbortSignal];
34
+ export declare function rejectOnSignal(promise: Promise<any>, signal: AbortSignal): Promise<any>;
35
+ export declare function wrpc({ debug, name, }?: {
36
+ debug?: LogLevel | undefined;
37
+ name?: string | undefined;
38
+ }): {
39
+ createResponder: <T extends Responders>(worker: WorkerLike, responders: T) => {
40
+ responders: T;
41
+ stop: () => void;
42
+ };
43
+ createCaller: <T extends Responders>(worker: WorkerLike, responders: T) => T;
44
+ };
45
+ //# sourceMappingURL=wrpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrpc.d.ts","sourceRoot":"","sources":["../../src/core/wrpc.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,SAAS,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;CAAE,CAAC;AAErH,MAAM,MAAM,OAAO,GAAG;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,GAAG,GAAG,KAAK,CAAA;CAAE,CAAC,CAAC;AAEzE,MAAM,MAAM,UAAU,GAAG;IACrB,WAAW,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IACzD,gBAAgB,EAAE,CACd,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,EAC1B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KACjD,IAAI,CAAC;IACV,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,KAAK,IAAI,CAAC;CAC5E,CAAC;AAEF,oBAAY,QAAQ;IAChB,QAAQ,IAAI;IACZ,KAAK,IAAI;IACT,SAAS,IAAI;IACb,MAAM,IAAI;IACV,SAAS,IAAI;CAChB;AAID,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC,eAAe,EAAE,WAAW,CAAC,CAGrF;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,gBAOxE;AAID,wBAAgB,IAAI,CAAC,EACjB,KAAyB,EACzB,IAAiE,GACpE;;;CAAK;sBA0EuB,CAAC,SAAS,UAAU,UAAU,UAAU,cAAc,CAAC;;;;mBAqG1D,CAAC,SAAS,UAAU,UAAU,UAAU,cAAc,CAAC,KAAG,CAAC;EA6HpF"}
@@ -0,0 +1,29 @@
1
+ export declare const responder: {
2
+ responders: {
3
+ test: ({ buf, signal }?: {
4
+ signal?: AbortSignal;
5
+ buf?: ArrayBuffer;
6
+ }) => Generator<{
7
+ progress: number;
8
+ aborted?: undefined;
9
+ } | {
10
+ progress: number;
11
+ aborted: boolean;
12
+ }, string, unknown>;
13
+ returnYield: () => Generator<1 | 3 | 2, any, unknown>;
14
+ bidirectional: (prefix?: any) => Generator<string, string, number>;
15
+ error: () => AsyncGenerator<never, never, unknown>;
16
+ promise: () => Promise<{
17
+ test: string;
18
+ }>;
19
+ promiseError: () => Promise<never>;
20
+ setTimeout: ({ timeout }?: {
21
+ timeout?: number;
22
+ }) => Promise<unknown>;
23
+ setInterval: ({ timeout }?: {
24
+ timeout?: number;
25
+ }) => AsyncGenerator<any, never, unknown>;
26
+ };
27
+ stop: () => void;
28
+ };
29
+ //# sourceMappingURL=wrpc.fixture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrpc.fixture.d.ts","sourceRoot":"","sources":["../../src/core/wrpc.fixture.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS;;iCAEgB;YAAE,MAAM,CAAC,EAAE,WAAW,CAAC;YAAC,GAAG,CAAC,EAAE,WAAW,CAAA;SAAE;;;;;;;;yCAYtC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;;;;;;mCAkBjC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE;oCAGT;YAAE,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE;;;CAKxE,CAAC"}
@@ -0,0 +1,242 @@
1
+ import { getTransferrable } from "./workerUtils.js";
2
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
3
+ LogLevel2[LogLevel2["disabled"] = 0] = "disabled";
4
+ LogLevel2[LogLevel2["error"] = 1] = "error";
5
+ LogLevel2[LogLevel2["lifecycle"] = 2] = "lifecycle";
6
+ LogLevel2[LogLevel2["detail"] = 3] = "detail";
7
+ LogLevel2[LogLevel2["transport"] = 4] = "transport";
8
+ return LogLevel2;
9
+ })(LogLevel || {});
10
+ function deriveController(signal) {
11
+ const controller = new AbortController();
12
+ return [controller, AbortSignal.any([controller.signal, signal].filter(Boolean))];
13
+ }
14
+ function rejectOnSignal(promise, signal) {
15
+ return Promise.race([
16
+ promise,
17
+ new Promise((resolve, reject) => {
18
+ signal.addEventListener("abort", () => reject("RaceAborted:" + signal.reason), { once: true, signal });
19
+ })
20
+ ]);
21
+ }
22
+ function wrpc({
23
+ debug = 0,
24
+ name = globalThis.importScripts ? "RESPONDER" : "CALLER"
25
+ } = {}) {
26
+ function logger(type) {
27
+ return (level, ctx, ...args) => {
28
+ if (!debug || debug < level) return;
29
+ const { id, message, ...restCtx } = ctx || {};
30
+ console[type](name, message || "*", id || "*", ...args, restCtx);
31
+ };
32
+ }
33
+ const log = logger("log");
34
+ const logWarn = logger("warn");
35
+ const logError = logger("error");
36
+ function send(worker, ctx, payload) {
37
+ log(4, ctx, "SEND", payload, typeof payload);
38
+ worker.postMessage({ ctx, payload }, getTransferrable(payload));
39
+ }
40
+ function listen(worker, context, listener, signal, once = false) {
41
+ signal.addEventListener(
42
+ "abort",
43
+ () => logWarn(3, context, "LISTEN SIGNAL ABORT", signal.reason),
44
+ {
45
+ signal,
46
+ once: true
47
+ }
48
+ );
49
+ worker.addEventListener(
50
+ "message",
51
+ ({ data }) => {
52
+ var _a, _b;
53
+ log(4, context, "LISTEN EVENT", data);
54
+ if (!context || ((_a = data == null ? void 0 : data.ctx) == null ? void 0 : _a.message) === context.message && ((_b = data.ctx) == null ? void 0 : _b.id) === context.id) listener(data);
55
+ },
56
+ { signal, once }
57
+ );
58
+ }
59
+ function waitFor(worker, ctx, condition = (data) => true, signal) {
60
+ const [waitController, waitSignal] = deriveController(signal);
61
+ return new Promise(
62
+ (res) => listen(
63
+ worker,
64
+ ctx,
65
+ (data) => {
66
+ if (!condition(data)) return;
67
+ waitController.abort("WaitFinished");
68
+ res(data);
69
+ },
70
+ waitSignal
71
+ )
72
+ );
73
+ }
74
+ function createResponder(worker, responders) {
75
+ const mainController = new AbortController();
76
+ worker.addEventListener("messageerror", (e) => logError(1, null, "MESSAGE ERROR", e), {
77
+ signal: mainController.signal
78
+ });
79
+ listen(
80
+ worker,
81
+ null,
82
+ async ({ ctx, payload = {} }) => {
83
+ if (!ctx || !ctx.message || !ctx.id || ctx.abort || ctx.ack) return;
84
+ try {
85
+ const responder = responders[ctx.message];
86
+ if (!responder) throw new Error("Unknown responder", { cause: ctx });
87
+ const [subController, subSignal] = deriveController(mainController.signal);
88
+ if (ctx.signalName) payload[ctx.signalName] = subSignal;
89
+ const iterator = responder(payload);
90
+ log(2, ctx, "START", iterator);
91
+ if (!iterator[Symbol.asyncIterator] && !iterator[Symbol.iterator]) {
92
+ send(worker, { ...ctx, promise: true }, await rejectOnSignal(iterator, subSignal));
93
+ log(3, ctx, "PROMISE RESOLVED");
94
+ return;
95
+ }
96
+ try {
97
+ listen(
98
+ worker,
99
+ ctx,
100
+ (data) => {
101
+ if (!data.ctx.abort) return;
102
+ subController.abort(data.ctx.abort);
103
+ logWarn(2, ctx, "GOT ABORT");
104
+ },
105
+ subSignal
106
+ );
107
+ let res;
108
+ let nextPayload = payload;
109
+ do {
110
+ res = await rejectOnSignal(iterator.next(nextPayload), subSignal);
111
+ send(worker, { ...ctx, done: res.done }, res.value);
112
+ if (res.done) {
113
+ subController.abort("Done");
114
+ break;
115
+ }
116
+ const nextData = await rejectOnSignal(
117
+ waitFor(worker, ctx, (data) => !!data.ctx.ack, subSignal),
118
+ subSignal
119
+ );
120
+ nextPayload = nextData.payload;
121
+ log(3, ctx, "LOOP ACK");
122
+ } while (!res.done && !subSignal.aborted);
123
+ } catch (e) {
124
+ if (!subSignal.aborted && !e.message.includes("RaceAborted")) {
125
+ logError(1, ctx, "LOOP ERROR", e);
126
+ send(worker, { ...ctx, error: true, done: true }, e);
127
+ }
128
+ } finally {
129
+ log(3, ctx, "LOOP DONE");
130
+ }
131
+ } catch (e) {
132
+ logError(1, ctx, "ERROR", e);
133
+ send(worker, { ...ctx, error: true, done: true }, e);
134
+ } finally {
135
+ log(3, ctx, "DONE");
136
+ }
137
+ },
138
+ mainController.signal
139
+ );
140
+ function stop() {
141
+ mainController.abort("Stop");
142
+ }
143
+ return { responders, stop };
144
+ }
145
+ function createCaller(worker, responders) {
146
+ worker.addEventListener("messageerror", (e) => logError(1, null, "MESSAGE ERROR", e));
147
+ return new Proxy(responders, {
148
+ get(target, prop, receiver) {
149
+ function gen(payload) {
150
+ const ctx = { message: prop.toString(), id: crypto.randomUUID() };
151
+ let signal = null;
152
+ if (payload && Object.is(payload, Object(payload))) {
153
+ for (const [key, value] of Object.entries(payload)) {
154
+ if (value instanceof AbortSignal) {
155
+ log(3, ctx, "FOUND SIGNAL", key);
156
+ ctx.signalName = key;
157
+ signal = value;
158
+ payload[key] = null;
159
+ }
160
+ }
161
+ }
162
+ const [mainController, mainSignal] = deriveController(signal);
163
+ const cancel = (notify, reason = "") => {
164
+ if (mainController.signal.aborted) return;
165
+ logWarn(2, ctx, "CANCEL", reason, notify);
166
+ if (reason) mainController.abort(reason);
167
+ if (notify) send(worker, { ...ctx, abort: reason });
168
+ };
169
+ const [promiseController, promiseSignal] = deriveController(mainSignal);
170
+ const promise = waitFor(
171
+ worker,
172
+ ctx,
173
+ (data) => !!data.ctx.promise || !!data.ctx.error,
174
+ promiseSignal
175
+ ).then(({ ctx: ctx2, payload: payload2 }) => {
176
+ if (ctx2.error) {
177
+ logError(1, ctx2, "PROMISE ERROR", payload2);
178
+ throw payload2;
179
+ }
180
+ log(2, ctx2, "PROMISE RESOLVED", payload2);
181
+ return payload2;
182
+ });
183
+ async function* generator() {
184
+ let done = false;
185
+ try {
186
+ mainSignal.addEventListener("abort", () => cancel(true, mainSignal.reason), {
187
+ once: true,
188
+ signal: mainSignal
189
+ });
190
+ log(3, ctx, "GENERATOR START");
191
+ promiseController.abort("GeneratorUsed");
192
+ do {
193
+ const data = await rejectOnSignal(
194
+ waitFor(worker, ctx, void 0, mainSignal),
195
+ mainSignal
196
+ );
197
+ const { ctx: ctxIn, payload: value } = data;
198
+ log(2, ctx, "EVENT", mainSignal.aborted, ctxIn, value);
199
+ if (ctxIn.promise) {
200
+ logError(1, ctx, "ERROR", "Unexpected promise in generator", data);
201
+ throw new Error("Unexpected promise in generator", { cause: data });
202
+ }
203
+ if (ctxIn.error) {
204
+ logError(1, ctx, "ERROR", data);
205
+ cancel(false, "Error");
206
+ throw value;
207
+ }
208
+ if (ctxIn.done) {
209
+ done = true;
210
+ return value;
211
+ } else {
212
+ const nextPayload = yield value;
213
+ if (!mainSignal.aborted) send(worker, { ...ctx, ack: true }, nextPayload);
214
+ }
215
+ } while (!mainSignal.aborted);
216
+ } finally {
217
+ log(3, ctx, "DONE");
218
+ cancel(true, mainSignal.aborted ? mainSignal.reason : done ? "Done" : "Break");
219
+ }
220
+ }
221
+ const iterator = generator();
222
+ send(worker, ctx, payload);
223
+ iterator["then"] = promise.then.bind(promise);
224
+ iterator["catch"] = promise.catch.bind(promise);
225
+ iterator["finally"] = promise.finally.bind(promise);
226
+ return iterator;
227
+ }
228
+ Object.defineProperty(gen, "name", { value: prop.toString() });
229
+ target[prop] = gen;
230
+ return gen;
231
+ }
232
+ });
233
+ }
234
+ return { createResponder, createCaller };
235
+ }
236
+ export {
237
+ LogLevel,
238
+ deriveController,
239
+ rejectOnSignal,
240
+ wrpc
241
+ };
242
+ //# sourceMappingURL=wrpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrpc.js","sources":["../../src/core/wrpc.ts"],"sourcesContent":["import { getTransferrable } from './workerUtils';\n\n// Types\n\nexport type Responders = { [key: string]: (arg?: any) => Generator<any> | AsyncGenerator<any> | Promise<any> | any };\n\nexport type Context = {\n message: string;\n id: string;\n signalName?: string;\n done?: boolean;\n abort?: string;\n error?: boolean;\n ack?: boolean;\n promise?: boolean;\n};\n\nexport type Event = MessageEvent<{ ctx: Context; payload: any | Error }>;\n\nexport type WorkerLike = {\n postMessage: (payload: any, transferable: any[]) => void;\n addEventListener: (\n event: string,\n listener: (e: any) => void,\n options?: { signal?: AbortSignal; once?: boolean },\n ) => void;\n removeEventListener: (event: string, listener: (e: any) => void) => void;\n};\n\nexport enum LogLevel {\n disabled = 0,\n error = 1,\n lifecycle = 2,\n detail = 3,\n transport = 4,\n}\n\n// Helpers\n\nexport function deriveController(signal?: AbortSignal): [AbortController, AbortSignal] {\n const controller = new AbortController();\n return [controller, AbortSignal.any([controller.signal, signal].filter(Boolean) as AbortSignal[])];\n}\n\nexport function rejectOnSignal(promise: Promise<any>, signal: AbortSignal) {\n return Promise.race([\n promise,\n new Promise<Event['data']>((resolve, reject) => {\n signal.addEventListener('abort', () => reject('RaceAborted:' + signal.reason), { once: true, signal });\n }),\n ]);\n}\n\n// Lib\n\nexport function wrpc({\n debug = LogLevel.disabled,\n name = (globalThis as any).importScripts ? 'RESPONDER' : 'CALLER',\n} = {}) {\n // Logger\n\n function logger(type: string) {\n return (level: number, ctx: Context | null, ...args: any[]) => {\n if (!debug || debug < level) return;\n const { id, message, ...restCtx } = ctx || {};\n console[type](name, message || '*', id || '*', ...args, restCtx);\n };\n }\n\n const log = logger('log');\n const logWarn = logger('warn');\n const logError = logger('error');\n\n // Service\n\n function send(worker: WorkerLike, ctx: Context, payload?: any) {\n log(LogLevel.transport, ctx, 'SEND', payload, typeof payload);\n worker.postMessage({ ctx, payload }, getTransferrable(payload));\n }\n\n //TODO Listener error handling\n function listen(\n worker: WorkerLike,\n context: Context | null,\n listener: (event: Event['data']) => void,\n signal: AbortSignal,\n once: boolean = false,\n ) {\n signal.addEventListener(\n 'abort',\n () => logWarn(LogLevel.detail, context, 'LISTEN SIGNAL ABORT', signal.reason),\n {\n signal,\n once: true,\n },\n );\n worker.addEventListener(\n 'message',\n ({ data }: Event) => {\n log(LogLevel.transport, context, 'LISTEN EVENT', data);\n // no context = listen all, or matching context\n if (!context || (data?.ctx?.message === context.message && data.ctx?.id === context.id)) listener(data);\n },\n { signal, once },\n );\n }\n\n function waitFor(\n worker: WorkerLike,\n ctx: Context,\n condition: (data: Event['data']) => boolean = (data) => true,\n signal: AbortSignal,\n ) {\n const [waitController, waitSignal] = deriveController(signal);\n\n return new Promise<Event['data']>((res) =>\n listen(\n worker,\n ctx,\n (data) => {\n if (!condition(data)) return;\n waitController.abort('WaitFinished');\n res(data);\n },\n waitSignal,\n ),\n );\n }\n\n // Responder\n\n //TODO Name arg\n function createResponder<T extends Responders>(worker: WorkerLike, responders: T) {\n const mainController = new AbortController();\n\n worker.addEventListener('messageerror', (e) => logError(LogLevel.error, null, 'MESSAGE ERROR', e), {\n signal: mainController.signal,\n });\n\n listen(\n worker,\n null,\n async ({ ctx, payload = {} }) => {\n if (!ctx || !ctx.message || !ctx.id || ctx.abort || ctx.ack) return; // ignore invalid and service messages\n try {\n const responder = responders[ctx.message];\n\n if (!responder) throw new Error('Unknown responder', { cause: ctx });\n\n const [subController, subSignal] = deriveController(mainController.signal);\n\n // always recover signal so that promises also can abort\n if (ctx.signalName) payload[ctx.signalName] = subSignal;\n\n const iterator = responder(payload); // no await!\n\n log(LogLevel.lifecycle, ctx, 'START', iterator);\n\n // PROMISE\n\n // https://stackoverflow.com/a/19660350/5125659 the only right way is to call function and see what it returned\n if (!iterator[Symbol.asyncIterator] && !iterator[Symbol.iterator]) {\n // Errors are caught in generic handler\n send(worker, { ...ctx, promise: true }, await rejectOnSignal(iterator, subSignal));\n log(LogLevel.detail, ctx, 'PROMISE RESOLVED');\n return;\n }\n\n // GENERATOR\n\n try {\n // Listen outside the loop to react immediately\n // Could be a wait for since abort is a one-off, but listeners are disposed by signal anyway, and we don't need to block the async thread\n listen(\n worker,\n ctx,\n (data) => {\n if (!data.ctx.abort) return;\n subController.abort(data.ctx.abort);\n logWarn(LogLevel.lifecycle, ctx, 'GOT ABORT');\n },\n subSignal,\n );\n\n let res: any;\n let nextPayload: any = payload; // does not matter, will be ignored anyway\n do {\n res = await rejectOnSignal(iterator.next(nextPayload), subSignal);\n\n send(worker, { ...ctx, done: res.done }, res.value);\n\n if (res.done) {\n subController.abort('Done');\n break;\n }\n\n const nextData = await rejectOnSignal(\n waitFor(worker, ctx, (data) => !!data.ctx.ack, subSignal),\n subSignal,\n );\n nextPayload = nextData.payload;\n\n log(LogLevel.detail, ctx, 'LOOP ACK');\n } while (!res.done && !subSignal.aborted);\n } catch (e) {\n if (!subSignal.aborted && !e.message.includes('RaceAborted')) {\n logError(LogLevel.error, ctx, 'LOOP ERROR', e);\n send(worker, { ...ctx, error: true, done: true }, e);\n }\n } finally {\n log(LogLevel.detail, ctx, 'LOOP DONE');\n }\n } catch (e) {\n // Generator-specific error handling\n logError(LogLevel.error, ctx, 'ERROR', e);\n send(worker, { ...ctx, error: true, done: true }, e); // Generic error handling\n } finally {\n log(LogLevel.detail, ctx, 'DONE');\n }\n },\n mainController.signal,\n );\n\n function stop() {\n mainController.abort('Stop');\n }\n\n return { responders, stop }; // responders needed for typing\n }\n\n // Caller\n\n //TODO Name arg\n function createCaller<T extends Responders>(worker: WorkerLike, responders: T): T {\n worker.addEventListener('messageerror', (e) => logError(LogLevel.error, null, 'MESSAGE ERROR', e));\n\n //TODO Stop\n\n return new Proxy<T>(responders as any, {\n get(target, prop, receiver) {\n function gen(payload: any) {\n const ctx: Context = { message: prop.toString(), id: crypto.randomUUID() };\n\n let signal: AbortSignal = null as any;\n\n if (payload && Object.is(payload, Object(payload))) {\n for (const [key, value] of Object.entries(payload)) {\n if (value instanceof AbortSignal) {\n log(LogLevel.detail, ctx, 'FOUND SIGNAL', key);\n ctx.signalName = key;\n signal = value;\n payload[key] = null; // will be recovered in responder\n }\n }\n }\n\n const [mainController, mainSignal] = deriveController(signal);\n\n const cancel = (notify: boolean, reason: string = '') => {\n if (mainController.signal.aborted) return;\n logWarn(LogLevel.lifecycle, ctx, 'CANCEL', reason, notify);\n if (reason) mainController.abort(reason); // stop listening\n if (notify) send(worker, { ...ctx, abort: reason }); // worker will break & acknowledge, but it's not important\n };\n\n // Generator\n\n const [promiseController, promiseSignal] = deriveController(mainSignal);\n\n const promise = waitFor(\n worker,\n ctx,\n (data) => !!data.ctx.promise || !!data.ctx.error,\n promiseSignal,\n ).then(({ ctx, payload }) => {\n if (ctx.error) {\n logError(LogLevel.error, ctx, 'PROMISE ERROR', payload);\n throw payload;\n }\n log(LogLevel.lifecycle, ctx, 'PROMISE RESOLVED', payload);\n return payload;\n });\n\n async function* generator() {\n let done = false;\n try {\n // React immediately\n mainSignal.addEventListener('abort', () => cancel(true, mainSignal.reason), {\n once: true,\n signal: mainSignal,\n });\n\n log(LogLevel.detail, ctx, 'GENERATOR START');\n promiseController.abort('GeneratorUsed');\n\n do {\n const data = await rejectOnSignal(\n waitFor(worker, ctx, undefined, mainSignal),\n mainSignal,\n );\n\n const { ctx: ctxIn, payload: value } = data;\n\n log(LogLevel.lifecycle, ctx, 'EVENT', mainSignal.aborted, ctxIn, value);\n\n if (ctxIn.promise) {\n // && !ctxIn.error\n // cancel(false, 'Resolved');\n // return payloadIn;\n logError(LogLevel.error, ctx, 'ERROR', 'Unexpected promise in generator', data);\n throw new Error('Unexpected promise in generator', { cause: data });\n }\n\n if (ctxIn.error) {\n logError(LogLevel.error, ctx, 'ERROR', data);\n cancel(false, 'Error');\n throw value;\n }\n\n if (ctxIn.done) {\n done = true;\n return value;\n } else {\n const nextPayload = yield value;\n if (!mainSignal.aborted) send(worker, { ...ctx, ack: true }, nextPayload);\n }\n } while (!mainSignal.aborted);\n } finally {\n // prevent further cycles in worker once main is closed/cancelled/aborted\n log(LogLevel.detail, ctx, 'DONE');\n cancel(true, mainSignal.aborted ? mainSignal.reason : done ? 'Done' : 'Break');\n }\n }\n\n const iterator = generator();\n\n send(worker, ctx, payload);\n\n // FIXME Nasty hack, return both iterator and promise\n // Consuming code will get both, but IDE should show only proper type\n // Consuming code will likely use only one except someone calls `for await ... of await` which is not useful in any case\n iterator['then'] = promise.then.bind(promise);\n iterator['catch'] = promise.catch.bind(promise);\n iterator['finally'] = promise.finally.bind(promise);\n return iterator;\n }\n\n //TODO mimic-function(gen, fn); but no access to original\n Object.defineProperty(gen, 'name', { value: prop.toString() });\n\n (target as any)[prop] = gen; // so that fn is visible after use, maybe not needed\n\n return gen;\n },\n }) as T;\n }\n\n return { createResponder, createCaller };\n}\n"],"names":["LogLevel","ctx","payload"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=wrpc.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrpc.test.d.ts","sourceRoot":"","sources":["../../src/core/wrpc.test.ts"],"names":[],"mappings":"AAAA,OAAO,oBAAoB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { GenericControl } from './genericControl';
3
+ declare const meta: Meta<typeof GenericControl>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof meta>;
6
+ export declare const Default: Story;
7
+ //# sourceMappingURL=formControlFieldset.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formControlFieldset.stories.d.ts","sourceRoot":"","sources":["../../src/mui/formControlFieldset.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIvD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMlD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,cAAc,CA6CC,CAAC;AAExC,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAQnC,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC"}
@@ -16,6 +16,13 @@ export type Validation<S extends ZodObject> = {
16
16
  data?: MaybeTypeOf<S>;
17
17
  errors?: Errors<S>;
18
18
  };
19
+ export declare const FormContext: import('react').Context<{
20
+ schema: ZodObject;
21
+ }>;
22
+ export declare const Form: <S extends ZodObject>({ schema, children }: {
23
+ schema: S;
24
+ children: any;
25
+ }) => import("react/jsx-runtime").JSX.Element;
19
26
  export declare function create<S extends ZodObject>(schema: S): {
20
27
  register: (name: keyof TypeOf<S>, data?: MaybeTypeOf<S>, errors?: Errors<S>, mui?: boolean) => {
21
28
  label?: any;
@@ -29,12 +36,6 @@ export declare function create<S extends ZodObject>(schema: S): {
29
36
  defaultValue: Partial<z.TypeOf<S>>[keyof z.TypeOf<S>] | undefined;
30
37
  };
31
38
  validate: (formData: FormData) => Validation<S>;
32
- Field: ({ children, name, errors, hint, }: {
33
- children?: any;
34
- name: string;
35
- errors?: Validation<S>["errors"];
36
- hint?: string;
37
- }) => import("react/jsx-runtime").JSX.Element;
38
39
  validationError: (data: MaybeTypeOf<S>, errors: Errors<S>) => Validation<S>;
39
40
  };
40
41
  export declare function Hint({ children, error }: {
@@ -1 +1 @@
1
- {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../src/react/form/form.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,CAAC,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAI3E,eAAO,MAAM,cAAc,mBAAgE,CAAC;AAC5F,eAAO,MAAM,SAAS,WAAY,CAAC,CAAC,SAAS,KAAG,MAA+B,CAAC;AAChF,eAAO,MAAM,SAAS,WAAY,CAAC,CAAC,SAAS,KAAG,MAA+B,CAAC;AAChF,eAAO,MAAM,UAAU,WAAY,CAAC,CAAC,SAAS,KAAG,OAAgC,CAAC;AAElF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1E,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,SAAS,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAChG,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,SAAS,IAAI,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;AACzF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,SAAS,IACpC;IACI,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC;CAClB,GACD;IACI,OAAO,EAAE,KAAK,CAAC;IACf,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CACtB,CAAC;AAER,wBAAgB,MAAM,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,CAAC;qBAQzB,MAAM,MAAM,CAAC,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,CAAC,CAAC,QAAO,OAAO;;;;;;;;;;;yBA2BpE,QAAQ,KAAG,UAAU,CAAC,CAAC,CAAC;+CAqBjD;QACC,QAAQ,CAAC,EAAE,GAAG,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB;4BAlC8B,WAAW,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,CAAC,CAAC,KAAG,UAAU,CAAC,CAAC,CAAC;EAoDnF;AAED,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,2CAE3E"}
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../src/react/form/form.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,CAAC,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAM3E,eAAO,MAAM,cAAc,mBAAgE,CAAC;AAC5F,eAAO,MAAM,SAAS,WAAY,CAAC,CAAC,SAAS,KAAG,MAA+B,CAAC;AAChF,eAAO,MAAM,SAAS,WAAY,CAAC,CAAC,SAAS,KAAG,MAA+B,CAAC;AAChF,eAAO,MAAM,UAAU,WAAY,CAAC,CAAC,SAAS,KAAG,OAAgC,CAAC;AAElF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1E,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,SAAS,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAChG,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,SAAS,IAAI,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;AACzF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,SAAS,IACpC;IACI,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC;CAClB,GACD;IACI,OAAO,EAAE,KAAK,CAAC;IACf,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CACtB,CAAC;AAER,eAAO,MAAM,WAAW;YACZ,SAAS;EACJ,CAAC;AAElB,eAAO,MAAM,IAAI,GAAI,CAAC,SAAS,SAAS,wBAAwB;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAA;CAAE,4CAG3F,CAAC;AAKF,wBAAgB,MAAM,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,CAAC;qBAKzB,MAAM,MAAM,CAAC,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,CAAC,CAAC,QAAO,OAAO;;;;;;;;;;;yBA2BpE,QAAQ,KAAG,UAAU,CAAC,CAAC,CAAC;4BARrB,WAAW,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,CAAC,CAAC,KAAG,UAAU,CAAC,CAAC,CAAC;EAwBnF;AAuCD,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,2CAE3E"}
@@ -1,18 +1,25 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { jsx } from "react/jsx-runtime";
2
2
  import { z } from "zod";
3
+ import { createContext, useMemo } from "react";
4
+ import "clsx";
3
5
  const nonEmpty = "This field cannot be empty";
4
6
  const stringRequired = () => z.string({ required_error: nonEmpty }).min(1, nonEmpty);
5
7
  const maxLength = (schema) => schema.maxLength || 0;
6
8
  const minLength = (schema) => schema.minLength || 0;
7
9
  const isRequired = (schema) => minLength(schema) > 0;
10
+ const FormContext = createContext(null);
11
+ const Form = ({ schema, children }) => {
12
+ const value = useMemo(() => ({ schema }), [schema]);
13
+ return /* @__PURE__ */ jsx(FormContext.Provider, { value, children });
14
+ };
15
+ const getShape = (schema) => schema.shape || schema.sourceType().shape;
8
16
  function create(schema) {
9
- const getShape = () => schema.shape || schema.sourceType().shape;
10
- if (!getShape()) {
17
+ if (!getShape(schema)) {
11
18
  throw new Error("Invalid schema: only z.object() or z.object().refine() are supported");
12
19
  }
13
20
  function register(name, data, errors, mui = false) {
14
21
  var _a, _b;
15
- const field = getShape()[name];
22
+ const field = getShape(schema)[name];
16
23
  return {
17
24
  name,
18
25
  id: name,
@@ -44,27 +51,14 @@ function create(schema) {
44
51
  }
45
52
  return { success: true, data };
46
53
  }
47
- function Field({
48
- children,
49
- name,
50
- errors,
51
- hint
52
- }) {
53
- var _a;
54
- const { description } = getShape()[name];
55
- return /* @__PURE__ */ jsxs("div", { className: "form-row", children: [
56
- description && /* @__PURE__ */ jsx("label", { htmlFor: name, children: description }),
57
- children,
58
- hint && /* @__PURE__ */ jsx(Hint, { children: hint }),
59
- (_a = errors == null ? void 0 : errors[name]) == null ? void 0 : _a.map((e) => /* @__PURE__ */ jsx(Hint, { error: true, children: e }, e))
60
- ] });
61
- }
62
- return { register, validate, Field, validationError };
54
+ return { register, validate, validationError };
63
55
  }
64
56
  function Hint({ children, error }) {
65
57
  return /* @__PURE__ */ jsx("div", { className: `hint ${error ? "hint-error" : ""}`, children });
66
58
  }
67
59
  export {
60
+ Form,
61
+ FormContext,
68
62
  Hint,
69
63
  create,
70
64
  isRequired,
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","sources":["../../../src/react/form/form.tsx"],"sourcesContent":["import { typeToFlattenedError, z, SafeParseReturnType, TypeOf } from 'zod';\n\nconst nonEmpty = 'This field cannot be empty';\n\nexport const stringRequired = () => z.string({ required_error: nonEmpty }).min(1, nonEmpty);\nexport const maxLength = (schema: z.ZodString): number => schema.maxLength || 0;\nexport const minLength = (schema: z.ZodString): number => schema.minLength || 0;\nexport const isRequired = (schema: z.ZodString): boolean => minLength(schema) > 0;\n\nexport type ZodObject = z.ZodObject<any> | z.ZodEffects<z.ZodObject<any>>; // z.ZodType<any, any, any>\nexport type MaybeTypeOf<S extends ZodObject> = Partial<TypeOf<S>>;\nexport type SafeTypeOf<S extends ZodObject> = SafeParseReturnType<TypeOf<S>, TypeOf<S>>['data'];\nexport type Errors<S extends ZodObject> = typeToFlattenedError<TypeOf<S>>['fieldErrors'];\nexport type Validation<S extends ZodObject> =\n | {\n success: true; // this is true only if form was validated successfully\n data: SafeTypeOf<S>;\n errors?: never;\n }\n | {\n success: false;\n data?: MaybeTypeOf<S>;\n errors?: Errors<S>;\n };\n\nexport function create<S extends ZodObject>(schema: S) {\n const getShape = () =>\n (schema as z.ZodObject<any>).shape || (schema as z.ZodEffects<z.ZodObject<any>>).sourceType().shape;\n\n if (!getShape()) {\n throw new Error('Invalid schema: only z.object() or z.object().refine() are supported');\n }\n\n function register(name: keyof TypeOf<S>, data?: MaybeTypeOf<S>, errors?: Errors<S>, mui: boolean = false) {\n const field = getShape()[name];\n return {\n name,\n id: name,\n required: isRequired(field),\n maxLength: maxLength(field),\n type: field.isEmail ? 'email' : (name as string).includes('password') ? 'password' : 'text',\n defaultValue: data?.[name],\n ...(mui\n ? {\n label: field.description,\n helperText: errors?.[name]?.join(', '),\n error: !!errors?.[name]?.length,\n }\n : {}),\n };\n }\n\n function validationError(data: MaybeTypeOf<S>, errors: Errors<S>): Validation<S> {\n return {\n success: false,\n data, // data is undefined if there are errors\n errors, // Next.js will butcher error object, so we provide something more primitive\n };\n }\n\n function validate(formData: FormData): Validation<S> {\n const rawData = Object.fromEntries(formData) as TypeOf<S>;\n const { error, data } = schema.safeParse(rawData);\n\n // console.log('Validate result', { error, data, rawData });\n\n if (error) {\n // data is undefined if there are errors\n // Next.js will butcher error object, so we provide something more primitive\n return validationError(rawData, error.flatten().fieldErrors as any);\n }\n\n return { success: true, data };\n }\n\n //FIXME Context?\n function Field({\n children,\n name,\n errors,\n hint,\n }: {\n children?: any;\n name: string;\n errors?: Validation<S>['errors'];\n hint?: string;\n }) {\n const { description } = getShape()[name];\n\n return (\n <div className=\"form-row\">\n {description && <label htmlFor={name}>{description}</label>}\n {children}\n {hint && <Hint>{hint}</Hint>}\n {errors?.[name]?.map((e: string) => (\n <Hint error key={e}>\n {e}\n </Hint>\n ))}\n </div>\n );\n }\n\n return { register, validate, Field, validationError };\n}\n\nexport function Hint({ children, error }: { children: any; error?: boolean }) {\n return <div className={`hint ${error ? 'hint-error' : ''}`}>{children}</div>;\n}\n"],"names":[],"mappings":";;AAEA,MAAM,WAAW;AAEJ,MAAA,iBAAiB,MAAM,EAAE,OAAO,EAAE,gBAAgB,UAAU,EAAE,IAAI,GAAG,QAAQ;AACnF,MAAM,YAAY,CAAC,WAAgC,OAAO,aAAa;AACvE,MAAM,YAAY,CAAC,WAAgC,OAAO,aAAa;AACvE,MAAM,aAAa,CAAC,WAAiC,UAAU,MAAM,IAAI;AAkBzE,SAAS,OAA4B,QAAW;AACnD,QAAM,WAAW,MACZ,OAA4B,SAAU,OAA0C,aAAa;AAE9F,MAAA,CAAC,YAAY;AACP,UAAA,IAAI,MAAM,sEAAsE;AAAA,EAAA;AAG1F,WAAS,SAAS,MAAuB,MAAuB,QAAoB,MAAe,OAAO;;AAChG,UAAA,QAAQ,SAAS,EAAE,IAAI;AACtB,WAAA;AAAA,MACH;AAAA,MACA,IAAI;AAAA,MACJ,UAAU,WAAW,KAAK;AAAA,MAC1B,WAAW,UAAU,KAAK;AAAA,MAC1B,MAAM,MAAM,UAAU,UAAW,KAAgB,SAAS,UAAU,IAAI,aAAa;AAAA,MACrF,cAAc,6BAAO;AAAA,MACrB,GAAI,MACE;AAAA,QACI,OAAO,MAAM;AAAA,QACb,aAAY,sCAAS,UAAT,mBAAgB,KAAK;AAAA,QACjC,OAAO,CAAC,GAAC,sCAAS,UAAT,mBAAgB;AAAA,MAAA,IAE7B,CAAA;AAAA,IACV;AAAA,EAAA;AAGK,WAAA,gBAAgB,MAAsB,QAAkC;AACtE,WAAA;AAAA,MACH,SAAS;AAAA,MACT;AAAA;AAAA,MACA;AAAA;AAAA,IACJ;AAAA,EAAA;AAGJ,WAAS,SAAS,UAAmC;AAC3C,UAAA,UAAU,OAAO,YAAY,QAAQ;AAC3C,UAAM,EAAE,OAAO,KAAA,IAAS,OAAO,UAAU,OAAO;AAIhD,QAAI,OAAO;AAGP,aAAO,gBAAgB,SAAS,MAAM,QAAA,EAAU,WAAkB;AAAA,IAAA;AAG/D,WAAA,EAAE,SAAS,MAAM,KAAK;AAAA,EAAA;AAIjC,WAAS,MAAM;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,GAMD;;AACC,UAAM,EAAE,YAAA,IAAgB,SAAA,EAAW,IAAI;AAGnC,WAAA,qBAAC,OAAI,EAAA,WAAU,YACV,UAAA;AAAA,MAAA,eAAgB,oBAAA,SAAA,EAAM,SAAS,MAAO,UAAY,aAAA;AAAA,MAClD;AAAA,MACA,QAAS,oBAAA,MAAA,EAAM,UAAK,KAAA,CAAA;AAAA,OACpB,sCAAS,UAAT,mBAAgB,IAAI,CAAC,MACjB,oBAAA,MAAA,EAAK,OAAK,MACN,UADY,EAAA,GAAA,CAEjB;AAAA,IACH,GACL;AAAA,EAAA;AAIR,SAAO,EAAE,UAAU,UAAU,OAAO,gBAAgB;AACxD;AAEO,SAAS,KAAK,EAAE,UAAU,SAA6C;AACnE,SAAA,oBAAC,SAAI,WAAW,QAAQ,QAAQ,eAAe,EAAE,IAAK,SAAS,CAAA;AAC1E;"}
1
+ {"version":3,"file":"form.js","sources":["../../../src/react/form/form.tsx"],"sourcesContent":["import { typeToFlattenedError, z, SafeParseReturnType, TypeOf } from 'zod';\nimport { createContext, useContext, useMemo } from 'react';\nimport clsx from 'clsx';\n\nconst nonEmpty = 'This field cannot be empty';\n\nexport const stringRequired = () => z.string({ required_error: nonEmpty }).min(1, nonEmpty);\nexport const maxLength = (schema: z.ZodString): number => schema.maxLength || 0;\nexport const minLength = (schema: z.ZodString): number => schema.minLength || 0;\nexport const isRequired = (schema: z.ZodString): boolean => minLength(schema) > 0;\n\nexport type ZodObject = z.ZodObject<any> | z.ZodEffects<z.ZodObject<any>>; // z.ZodType<any, any, any>\nexport type MaybeTypeOf<S extends ZodObject> = Partial<TypeOf<S>>;\nexport type SafeTypeOf<S extends ZodObject> = SafeParseReturnType<TypeOf<S>, TypeOf<S>>['data'];\nexport type Errors<S extends ZodObject> = typeToFlattenedError<TypeOf<S>>['fieldErrors'];\nexport type Validation<S extends ZodObject> =\n | {\n success: true; // this is true only if form was validated successfully\n data: SafeTypeOf<S>;\n errors?: never;\n }\n | {\n success: false;\n data?: MaybeTypeOf<S>;\n errors?: Errors<S>;\n };\n\nexport const FormContext = createContext<{\n schema: ZodObject;\n}>(null as never);\n\nexport const Form = <S extends ZodObject>({ schema, children }: { schema: S; children: any }) => {\n const value = useMemo(() => ({ schema }), [schema]);\n return <FormContext.Provider value={value}>{children}</FormContext.Provider>;\n};\n\nconst getShape = <S extends ZodObject>(schema: S) =>\n (schema as z.ZodObject<any>).shape || (schema as z.ZodEffects<z.ZodObject<any>>).sourceType().shape;\n\nexport function create<S extends ZodObject>(schema: S) {\n if (!getShape(schema)) {\n throw new Error('Invalid schema: only z.object() or z.object().refine() are supported');\n }\n\n function register(name: keyof TypeOf<S>, data?: MaybeTypeOf<S>, errors?: Errors<S>, mui: boolean = false) {\n const field = getShape(schema)[name];\n return {\n name,\n id: name,\n required: isRequired(field),\n maxLength: maxLength(field),\n type: field.isEmail ? 'email' : (name as string).includes('password') ? 'password' : 'text',\n defaultValue: data?.[name],\n ...(mui\n ? {\n label: field.description,\n helperText: errors?.[name]?.join(', '),\n error: !!errors?.[name]?.length,\n }\n : {}),\n };\n }\n\n function validationError(data: MaybeTypeOf<S>, errors: Errors<S>): Validation<S> {\n return {\n success: false,\n data, // data is undefined if there are errors\n errors, // Next.js will butcher error object, so we provide something more primitive\n };\n }\n\n function validate(formData: FormData): Validation<S> {\n const rawData = Object.fromEntries(formData) as TypeOf<S>;\n const { error, data } = schema.safeParse(rawData);\n\n // console.log('Validate result', { error, data, rawData });\n\n if (error) {\n // data is undefined if there are errors\n // Next.js will butcher error object, so we provide something more primitive\n return validationError(rawData, error.flatten().fieldErrors as any);\n }\n\n return { success: true, data };\n }\n\n return { register, validate, validationError };\n}\n\nfunction Field<S extends ZodObject>({\n children,\n name,\n errors,\n hint,\n className,\n labelProps,\n ...props\n}: {\n children?: any;\n name: keyof TypeOf<S>;\n errors?: Validation<S>['errors'];\n hint?: string;\n className?: string;\n labelProps?: any;\n}) {\n const { schema } = useContext(FormContext);\n const { description } = getShape(schema)[name];\n\n return (\n <div {...props} className={clsx('form-row', className)}>\n {description && (\n <label {...labelProps} htmlFor={name}>\n {description}\n </label>\n )}\n {children}\n {hint && <Hint>{hint}</Hint>}\n {errors?.[name]?.map((e: string) => (\n <Hint error key={e}>\n {e}\n </Hint>\n ))}\n </div>\n );\n}\n\nexport function Hint({ children, error }: { children: any; error?: boolean }) {\n return <div className={`hint ${error ? 'hint-error' : ''}`}>{children}</div>;\n}\n"],"names":[],"mappings":";;;;AAIA,MAAM,WAAW;AAEJ,MAAA,iBAAiB,MAAM,EAAE,OAAO,EAAE,gBAAgB,UAAU,EAAE,IAAI,GAAG,QAAQ;AACnF,MAAM,YAAY,CAAC,WAAgC,OAAO,aAAa;AACvE,MAAM,YAAY,CAAC,WAAgC,OAAO,aAAa;AACvE,MAAM,aAAa,CAAC,WAAiC,UAAU,MAAM,IAAI;AAkBnE,MAAA,cAAc,cAExB,IAAa;AAET,MAAM,OAAO,CAAsB,EAAE,QAAQ,eAA6C;AACvF,QAAA,QAAQ,QAAQ,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC;AAClD,SAAQ,oBAAA,YAAY,UAAZ,EAAqB,OAAe,SAAS,CAAA;AACzD;AAEA,MAAM,WAAW,CAAsB,WAClC,OAA4B,SAAU,OAA0C,aAAa;AAE3F,SAAS,OAA4B,QAAW;AAC/C,MAAA,CAAC,SAAS,MAAM,GAAG;AACb,UAAA,IAAI,MAAM,sEAAsE;AAAA,EAAA;AAG1F,WAAS,SAAS,MAAuB,MAAuB,QAAoB,MAAe,OAAO;;AACtG,UAAM,QAAQ,SAAS,MAAM,EAAE,IAAI;AAC5B,WAAA;AAAA,MACH;AAAA,MACA,IAAI;AAAA,MACJ,UAAU,WAAW,KAAK;AAAA,MAC1B,WAAW,UAAU,KAAK;AAAA,MAC1B,MAAM,MAAM,UAAU,UAAW,KAAgB,SAAS,UAAU,IAAI,aAAa;AAAA,MACrF,cAAc,6BAAO;AAAA,MACrB,GAAI,MACE;AAAA,QACI,OAAO,MAAM;AAAA,QACb,aAAY,sCAAS,UAAT,mBAAgB,KAAK;AAAA,QACjC,OAAO,CAAC,GAAC,sCAAS,UAAT,mBAAgB;AAAA,MAAA,IAE7B,CAAA;AAAA,IACV;AAAA,EAAA;AAGK,WAAA,gBAAgB,MAAsB,QAAkC;AACtE,WAAA;AAAA,MACH,SAAS;AAAA,MACT;AAAA;AAAA,MACA;AAAA;AAAA,IACJ;AAAA,EAAA;AAGJ,WAAS,SAAS,UAAmC;AAC3C,UAAA,UAAU,OAAO,YAAY,QAAQ;AAC3C,UAAM,EAAE,OAAO,KAAA,IAAS,OAAO,UAAU,OAAO;AAIhD,QAAI,OAAO;AAGP,aAAO,gBAAgB,SAAS,MAAM,QAAA,EAAU,WAAkB;AAAA,IAAA;AAG/D,WAAA,EAAE,SAAS,MAAM,KAAK;AAAA,EAAA;AAG1B,SAAA,EAAE,UAAU,UAAU,gBAAgB;AACjD;AAuCO,SAAS,KAAK,EAAE,UAAU,SAA6C;AACnE,SAAA,oBAAC,SAAI,WAAW,QAAQ,QAAQ,eAAe,EAAE,IAAK,SAAS,CAAA;AAC1E;"}
@@ -1,5 +1,6 @@
1
1
  export * from './form';
2
2
  export * from './apiCall';
3
+ export * from './keyboard';
3
4
  export * from './useFetch';
4
5
  export * from './useFetcher';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
@@ -1,10 +1,15 @@
1
1
  import { createClient } from "./form/client.js";
2
- import { Hint, create, isRequired, maxLength, minLength, stringRequired } from "./form/form.js";
2
+ import { Form, FormContext, Hint, create, isRequired, maxLength, minLength, stringRequired } from "./form/form.js";
3
3
  import { apiCall, jsonContentType } from "./apiCall.js";
4
+ import { HotkeysContext, HotkeysProvider, useHotkeys } from "./keyboard.js";
4
5
  import { useFetch } from "./useFetch.js";
5
6
  import { useFetcher } from "./useFetcher.js";
6
7
  export {
8
+ Form,
9
+ FormContext,
7
10
  Hint,
11
+ HotkeysContext,
12
+ HotkeysProvider,
8
13
  apiCall,
9
14
  create,
10
15
  createClient,
@@ -14,6 +19,7 @@ export {
14
19
  minLength,
15
20
  stringRequired,
16
21
  useFetch,
17
- useFetcher
22
+ useFetcher,
23
+ useHotkeys
18
24
  };
19
25
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
@@ -0,0 +1,11 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+ export type Hotkeys = Record<KeyboardEvent['code'], (e: KeyboardEvent) => void>;
3
+ export declare const HotkeysContext: import('react').Context<{
4
+ enabled: boolean;
5
+ setEnabled: Dispatch<SetStateAction<boolean>>;
6
+ }>;
7
+ export declare const HotkeysProvider: ({ children }: {
8
+ children: any;
9
+ }) => import("react/jsx-runtime").JSX.Element;
10
+ export declare const useHotkeys: (hotkeys: Hotkeys) => void;
11
+ //# sourceMappingURL=keyboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../src/react/keyboard.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA+C,QAAQ,EAAE,cAAc,EAAc,MAAM,OAAO,CAAC;AAM1G,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC,CAAC;AAEhF,eAAO,MAAM,cAAc;aACd,OAAO;gBACJ,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;EAChC,CAAC;AAElB,eAAO,MAAM,eAAe;;6CAM3B,CAAC;AAEF,eAAO,MAAM,UAAU,YAAa,OAAO,SAyB1C,CAAC"}