vultus-core 0.1.0 → 0.2.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/README.md +72 -0
- package/dist/LinkContext.d.ts +13 -0
- package/dist/LinkContext.d.ts.map +1 -0
- package/dist/LinkContext.js +8 -0
- package/dist/LinkContext.js.map +1 -0
- package/dist/MessageSink.d.ts +13 -0
- package/dist/MessageSink.d.ts.map +1 -0
- package/dist/MessageSink.js +6 -0
- package/dist/MessageSink.js.map +1 -0
- package/dist/action-core.d.ts +36 -0
- package/dist/action-core.d.ts.map +1 -0
- package/dist/action-core.js +91 -0
- package/dist/action-core.js.map +1 -0
- package/dist/decision.d.ts +10 -0
- package/dist/decision.d.ts.map +1 -0
- package/dist/decision.js +17 -0
- package/dist/decision.js.map +1 -0
- package/dist/field.d.ts +97 -0
- package/dist/field.d.ts.map +1 -0
- package/dist/field.js +95 -0
- package/dist/field.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/parseApiError.d.ts +3 -0
- package/dist/parseApiError.d.ts.map +1 -0
- package/dist/parseApiError.js +11 -0
- package/dist/parseApiError.js.map +1 -0
- package/dist/resolve.d.ts +3 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +6 -0
- package/dist/resolve.js.map +1 -0
- package/dist/routeApiError.d.ts +3 -0
- package/dist/routeApiError.d.ts.map +1 -0
- package/dist/routeApiError.js +19 -0
- package/dist/routeApiError.js.map +1 -0
- package/dist/types.d.ts +104 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/useAction.d.ts +3 -0
- package/dist/useAction.d.ts.map +1 -0
- package/dist/useAction.js +47 -0
- package/dist/useAction.js.map +1 -0
- package/dist/useActionErrorCtx.d.ts +4 -0
- package/dist/useActionErrorCtx.d.ts.map +1 -0
- package/dist/useActionErrorCtx.js +6 -0
- package/dist/useActionErrorCtx.js.map +1 -0
- package/dist/useActionList.d.ts +13 -0
- package/dist/useActionList.d.ts.map +1 -0
- package/dist/useActionList.js +42 -0
- package/dist/useActionList.js.map +1 -0
- package/dist/useBoolField.d.ts +17 -0
- package/dist/useBoolField.d.ts.map +1 -0
- package/dist/useBoolField.js +12 -0
- package/dist/useBoolField.js.map +1 -0
- package/dist/useMixedBoolField.d.ts +24 -0
- package/dist/useMixedBoolField.d.ts.map +1 -0
- package/dist/useMixedBoolField.js +25 -0
- package/dist/useMixedBoolField.js.map +1 -0
- package/package.json +20 -5
- package/src/MessageSink.tsx +8 -5
- package/src/action-core.ts +27 -7
- package/src/decision.ts +4 -0
- package/src/field.ts +197 -0
- package/src/index.ts +8 -0
- package/src/types.ts +28 -1
- package/src/useAction.ts +5 -5
- package/src/useActionList.ts +7 -7
- package/src/useBoolField.ts +30 -0
- package/src/useMixedBoolField.ts +43 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parseApiError } from './parseApiError.js';
|
|
2
|
+
export function routeApiError(err, ctx, registry) {
|
|
3
|
+
const parsed = parseApiError(err);
|
|
4
|
+
const routeMap = registry[ctx.route];
|
|
5
|
+
const entry = parsed.reason ? routeMap?.[parsed.reason] : undefined;
|
|
6
|
+
const text = entry?.i18nKey
|
|
7
|
+
? ctx.t(entry.i18nKey)
|
|
8
|
+
: (parsed.message ?? ctx.t('common.error'));
|
|
9
|
+
if (entry?.field && ctx.form) {
|
|
10
|
+
ctx.form.setFields([{ name: entry.field, errors: [text] }]);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (ctx.setInlineError) {
|
|
14
|
+
ctx.setInlineError(text);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
(ctx.showError ?? (() => { }))(text);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=routeApiError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeApiError.js","sourceRoot":"","sources":["../src/routeApiError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,MAAM,UAAU,aAAa,CAC3B,GAAY,EACZ,GAA6B,EAC7B,QAAsC;IAEtC,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,MAAM,IAAI,GAAG,KAAK,EAAE,OAAO;QACzB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;QACtB,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;IAE9C,IAAI,KAAK,EAAE,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAC7B,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5D,OAAO;IACT,CAAC;IACD,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;QACvB,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO;IACT,CAAC;IACD,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { MessageSink } from './MessageSink.js';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural form contract used by error-routing (antd's FormInstance is
|
|
5
|
+
* assignable to this). Keeps vultus-core antd-free — see AGENTS.md.
|
|
6
|
+
*/
|
|
7
|
+
export interface FormLike {
|
|
8
|
+
setFields(fields: {
|
|
9
|
+
name: string | number | (string | number)[];
|
|
10
|
+
errors: string[];
|
|
11
|
+
}[]): void;
|
|
12
|
+
}
|
|
13
|
+
export type Decision = boolean | {
|
|
14
|
+
verdict: boolean;
|
|
15
|
+
reason?: string;
|
|
16
|
+
};
|
|
17
|
+
export type ValueOrFn<T> = T | (() => T);
|
|
18
|
+
export type Severity = 'info' | 'warning' | 'error';
|
|
19
|
+
/** Something an action (or, later, a field's validation) has to say, with its severity. */
|
|
20
|
+
export interface Message {
|
|
21
|
+
text: string;
|
|
22
|
+
severity: Severity;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What an action's `fire` receives next to its arguments: a way to report
|
|
26
|
+
* messages while it runs (absorbed from the sidedao InputFields). Messages land
|
|
27
|
+
* on the action's status (`messages`) and go to the MessageSink. A thrown
|
|
28
|
+
* error still fails the action; `error()` reports an error without throwing.
|
|
29
|
+
*/
|
|
30
|
+
export interface ExecutionContext {
|
|
31
|
+
info(text: string): void;
|
|
32
|
+
warn(text: string): void;
|
|
33
|
+
error(text: string): void;
|
|
34
|
+
}
|
|
35
|
+
export type ConfirmationSpec = {
|
|
36
|
+
kind: 'popconfirm';
|
|
37
|
+
question: string;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'cascade-modal';
|
|
40
|
+
title: string;
|
|
41
|
+
content: React.ReactNode;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'typing';
|
|
44
|
+
title: string;
|
|
45
|
+
entityName: string;
|
|
46
|
+
description: React.ReactNode;
|
|
47
|
+
};
|
|
48
|
+
export interface ActionOptions<TArgs = void, TResult = void, RouteId extends string = string> {
|
|
49
|
+
id: string;
|
|
50
|
+
label: ValueOrFn<string>;
|
|
51
|
+
icon?: ValueOrFn<React.ReactNode>;
|
|
52
|
+
variant?: ValueOrFn<'default' | 'primary' | 'danger'>;
|
|
53
|
+
enabled?: ValueOrFn<Decision>;
|
|
54
|
+
invisible?: ValueOrFn<boolean>;
|
|
55
|
+
confirmation?: ValueOrFn<ConfirmationSpec | undefined>;
|
|
56
|
+
errorRoute?: RouteId;
|
|
57
|
+
fire: (args: TArgs, ctx: ExecutionContext) => Promise<TResult> | TResult;
|
|
58
|
+
onSuccess?: (result: TResult) => void;
|
|
59
|
+
}
|
|
60
|
+
type FireArgs<TArgs> = [TArgs] extends [void] ? [] : [args: TArgs];
|
|
61
|
+
export interface ActionStatus<TArgs = void> {
|
|
62
|
+
id: string;
|
|
63
|
+
label: string;
|
|
64
|
+
icon?: React.ReactNode;
|
|
65
|
+
variant: 'default' | 'primary' | 'danger';
|
|
66
|
+
pending: boolean;
|
|
67
|
+
disabled: boolean;
|
|
68
|
+
reason?: string;
|
|
69
|
+
invisible: boolean;
|
|
70
|
+
confirmation?: ConfirmationSpec;
|
|
71
|
+
/**
|
|
72
|
+
* Messages of the last run: reported through the ExecutionContext, or the thrown error.
|
|
73
|
+
* Optional so hand-built statuses (adapters, test fixtures) stay valid; read it as `[]` when
|
|
74
|
+
* absent. Statuses from useAction and useActionList always carry it.
|
|
75
|
+
*/
|
|
76
|
+
messages?: Message[];
|
|
77
|
+
/** The error texts among `messages` (kept for callers that only show errors). */
|
|
78
|
+
errors: string[];
|
|
79
|
+
fire(...args: FireArgs<TArgs>): void;
|
|
80
|
+
firePromise(...args: FireArgs<TArgs>): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
export type ParsedApiError = {
|
|
83
|
+
status: number;
|
|
84
|
+
reason?: string;
|
|
85
|
+
message?: string;
|
|
86
|
+
};
|
|
87
|
+
export type RegistryEntry = {
|
|
88
|
+
i18nKey: string;
|
|
89
|
+
field?: string;
|
|
90
|
+
};
|
|
91
|
+
export type ErrorRoutesRegistry<RouteId extends string> = Record<RouteId, Record<string, RegistryEntry>>;
|
|
92
|
+
export type ApiErrorContext<RouteId extends string> = {
|
|
93
|
+
route: RouteId;
|
|
94
|
+
form?: FormLike;
|
|
95
|
+
setInlineError?: (text: string | null) => void;
|
|
96
|
+
t: (key: string) => string;
|
|
97
|
+
showError?: MessageSink;
|
|
98
|
+
};
|
|
99
|
+
export type ActionErrorCtx = {
|
|
100
|
+
form: FormLike;
|
|
101
|
+
setInlineError: (text: string | null) => void;
|
|
102
|
+
};
|
|
103
|
+
export {};
|
|
104
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ActionOptions, ActionStatus, ErrorRoutesRegistry } from './types.js';
|
|
2
|
+
export declare function useAction<TArgs = void, TResult = void, RouteId extends string = string>(opts: ActionOptions<TArgs, TResult, RouteId>, registry?: ErrorRoutesRegistry<RouteId>): ActionStatus<TArgs>;
|
|
3
|
+
//# sourceMappingURL=useAction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAction.d.ts","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,mBAAmB,EAAW,MAAM,YAAY,CAAC;AAE5F,wBAAgB,SAAS,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,SAAS,MAAM,GAAG,MAAM,EACrF,IAAI,EAAE,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAC5C,QAAQ,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,GACtC,YAAY,CAAC,KAAK,CAAC,CAkDrB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { useActionErrorCtx } from './useActionErrorCtx.js';
|
|
4
|
+
import { useMessageSink } from './MessageSink.js';
|
|
5
|
+
import { resolveActionFields, makeFirePromise, assembleStatus } from './action-core.js';
|
|
6
|
+
export function useAction(opts, registry) {
|
|
7
|
+
const errCtx = useActionErrorCtx();
|
|
8
|
+
const errCtxRef = useRef(errCtx);
|
|
9
|
+
errCtxRef.current = errCtx;
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
const messageSink = useMessageSink();
|
|
12
|
+
const [pending, setPending] = useState(false);
|
|
13
|
+
const [messages, setMessages] = useState([]);
|
|
14
|
+
const pendingRef = useRef(pending);
|
|
15
|
+
pendingRef.current = pending;
|
|
16
|
+
const fields = resolveActionFields(opts);
|
|
17
|
+
const firePromise = useMemo(() => makeFirePromise(opts, {
|
|
18
|
+
disabled: fields.disabled,
|
|
19
|
+
reason: fields.reason,
|
|
20
|
+
getPending: () => pendingRef.current,
|
|
21
|
+
setPending,
|
|
22
|
+
setMessages,
|
|
23
|
+
errCtxRef,
|
|
24
|
+
t,
|
|
25
|
+
registry,
|
|
26
|
+
messageSink,
|
|
27
|
+
}), [opts, fields.disabled, fields.reason, registry, t, messageSink]);
|
|
28
|
+
// Granular deps by design: memo invalidates on the specific opts/fields
|
|
29
|
+
// primitives below, not on the whole `opts`/`fields` objects (which are
|
|
30
|
+
// fresh each render and would defeat the memo).
|
|
31
|
+
return useMemo(() => assembleStatus(opts, fields, pending, messages, firePromise),
|
|
32
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
+
[
|
|
34
|
+
opts.id,
|
|
35
|
+
fields.label,
|
|
36
|
+
fields.icon,
|
|
37
|
+
fields.variant,
|
|
38
|
+
pending,
|
|
39
|
+
fields.disabled,
|
|
40
|
+
fields.reason,
|
|
41
|
+
fields.invisible,
|
|
42
|
+
fields.confirmation,
|
|
43
|
+
messages,
|
|
44
|
+
firePromise,
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=useAction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAction.js","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGxF,MAAM,UAAU,SAAS,CACvB,IAA4C,EAC5C,QAAuC;IAEvC,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;IACnC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC;IAC3B,MAAM,EAAE,CAAC,EAAE,GAAG,cAAc,EAAE,CAAC;IAC/B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAY,EAAE,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAE7B,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAEzC,MAAM,WAAW,GAAG,OAAO,CACzB,GAAG,EAAE,CACH,eAAe,CAAC,IAAI,EAAE;QACpB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO;QACpC,UAAU;QACV,WAAW;QACX,SAAS;QACT,CAAC;QACD,QAAQ;QACR,WAAW;KACZ,CAAC,EACJ,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,CAAC,CACjE,CAAC;IAEF,wEAAwE;IACxE,wEAAwE;IACxE,gDAAgD;IAChD,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC;IAClE,uDAAuD;IACvD;QACE,IAAI,CAAC,EAAE;QACP,MAAM,CAAC,KAAK;QACZ,MAAM,CAAC,IAAI;QACX,MAAM,CAAC,OAAO;QACd,OAAO;QACP,MAAM,CAAC,QAAQ;QACf,MAAM,CAAC,MAAM;QACb,MAAM,CAAC,SAAS;QAChB,MAAM,CAAC,YAAY;QACnB,QAAQ;QACR,WAAW;KACZ,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useActionErrorCtx.d.ts","sourceRoot":"","sources":["../src/useActionErrorCtx.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,eAAO,MAAM,kBAAkB,gDAA6C,CAAC;AAE7E,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,IAAI,CAEzD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useActionErrorCtx.js","sourceRoot":"","sources":["../src/useActionErrorCtx.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAGlD,MAAM,CAAC,MAAM,kBAAkB,GAAG,aAAa,CAAwB,IAAI,CAAC,CAAC;AAE7E,MAAM,UAAU,iBAAiB;IAC/B,OAAO,UAAU,CAAC,kBAAkB,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ActionOptions, ActionStatus, ErrorRoutesRegistry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build a DYNAMIC list of actions from a spec array — the list counterpart to
|
|
4
|
+
* useAction. The hook count is fixed regardless of list length (one shared
|
|
5
|
+
* error context, one pending/messages map, one memo), so it is usable where
|
|
6
|
+
* useAction — a per-call hook — cannot be looped (rules of hooks). Pending and
|
|
7
|
+
* messages are tracked per action id in the shared maps; each action gets real
|
|
8
|
+
* per-item pending. The firing machinery + ActionStatus shape are the exact
|
|
9
|
+
* same shared helpers useAction uses (see action-core.ts) — nothing is
|
|
10
|
+
* reimplemented.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useActionList<TArgs = void, TResult = void, RouteId extends string = string>(specs: ActionOptions<TArgs, TResult, RouteId>[], registry?: ErrorRoutesRegistry<RouteId>): ActionStatus<TArgs>[];
|
|
13
|
+
//# sourceMappingURL=useActionList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useActionList.d.ts","sourceRoot":"","sources":["../src/useActionList.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,mBAAmB,EAAW,MAAM,YAAY,CAAC;AAE5F;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,SAAS,MAAM,GAAG,MAAM,EACzF,KAAK,EAAE,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,EAC/C,QAAQ,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,GACtC,YAAY,CAAC,KAAK,CAAC,EAAE,CAqCvB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { useActionErrorCtx } from './useActionErrorCtx.js';
|
|
4
|
+
import { useMessageSink } from './MessageSink.js';
|
|
5
|
+
import { resolveActionFields, makeFirePromise, assembleStatus } from './action-core.js';
|
|
6
|
+
/**
|
|
7
|
+
* Build a DYNAMIC list of actions from a spec array — the list counterpart to
|
|
8
|
+
* useAction. The hook count is fixed regardless of list length (one shared
|
|
9
|
+
* error context, one pending/messages map, one memo), so it is usable where
|
|
10
|
+
* useAction — a per-call hook — cannot be looped (rules of hooks). Pending and
|
|
11
|
+
* messages are tracked per action id in the shared maps; each action gets real
|
|
12
|
+
* per-item pending. The firing machinery + ActionStatus shape are the exact
|
|
13
|
+
* same shared helpers useAction uses (see action-core.ts) — nothing is
|
|
14
|
+
* reimplemented.
|
|
15
|
+
*/
|
|
16
|
+
export function useActionList(specs, registry) {
|
|
17
|
+
const errCtx = useActionErrorCtx();
|
|
18
|
+
const errCtxRef = useRef(errCtx);
|
|
19
|
+
errCtxRef.current = errCtx;
|
|
20
|
+
const { t } = useTranslation();
|
|
21
|
+
const messageSink = useMessageSink();
|
|
22
|
+
const [pendingMap, setPendingMap] = useState({});
|
|
23
|
+
const [messagesMap, setMessagesMap] = useState({});
|
|
24
|
+
const pendingRef = useRef(pendingMap);
|
|
25
|
+
pendingRef.current = pendingMap;
|
|
26
|
+
return useMemo(() => specs.map((opts) => {
|
|
27
|
+
const fields = resolveActionFields(opts);
|
|
28
|
+
const firePromise = makeFirePromise(opts, {
|
|
29
|
+
disabled: fields.disabled,
|
|
30
|
+
reason: fields.reason,
|
|
31
|
+
getPending: () => !!pendingRef.current[opts.id],
|
|
32
|
+
setPending: (value) => setPendingMap((m) => ({ ...m, [opts.id]: value })),
|
|
33
|
+
setMessages: (msgs) => setMessagesMap((m) => ({ ...m, [opts.id]: msgs })),
|
|
34
|
+
errCtxRef,
|
|
35
|
+
t,
|
|
36
|
+
registry,
|
|
37
|
+
messageSink,
|
|
38
|
+
});
|
|
39
|
+
return assembleStatus(opts, fields, !!pendingMap[opts.id], messagesMap[opts.id] ?? [], firePromise);
|
|
40
|
+
}), [specs, pendingMap, messagesMap, registry, t, messageSink]);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=useActionList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useActionList.js","sourceRoot":"","sources":["../src/useActionList.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGxF;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAC3B,KAA+C,EAC/C,QAAuC;IAEvC,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;IACnC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC;IAC3B,MAAM,EAAE,CAAC,EAAE,GAAG,cAAc,EAAE,CAAC;IAC/B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAA0B,EAAE,CAAC,CAAC;IAC1E,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAA4B,EAAE,CAAC,CAAC;IAC9E,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IACtC,UAAU,CAAC,OAAO,GAAG,UAAU,CAAC;IAEhC,OAAO,OAAO,CACZ,GAAG,EAAE,CACH,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACjB,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,EAAE;YACxC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/C,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACzE,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YACzE,SAAS;YACT,CAAC;YACD,QAAQ;YACR,WAAW;SACZ,CAAC,CAAC;QACH,OAAO,cAAc,CACnB,IAAI,EACJ,MAAM,EACN,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,EACrB,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,EAC1B,WAAW,CACZ,CAAC;IACJ,CAAC,CAAC,EACJ,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,CAAC,CAC3D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type FieldControls, type FieldOptions } from './field.js';
|
|
2
|
+
import type { ErrorRoutesRegistry } from './types.js';
|
|
3
|
+
export type BoolFieldOptions<RouteId extends string = string> = FieldOptions<boolean, boolean, RouteId>;
|
|
4
|
+
export interface BoolFieldControls extends FieldControls<boolean, boolean> {
|
|
5
|
+
/**
|
|
6
|
+
* Request the opposite value. Programmatic use; widgets obtain the field's
|
|
7
|
+
* confirmation for `!value` first, then call `setValue`.
|
|
8
|
+
*/
|
|
9
|
+
toggle(): void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A boolean field (moxb's Bool, the sidedao boolean field). Rendered by
|
|
13
|
+
* vultus-antd's BoolCheckbox and BoolSwitch, which take the same controls.
|
|
14
|
+
* Inside the field the value starts at `initialValue` (default false).
|
|
15
|
+
*/
|
|
16
|
+
export declare function useBoolField<RouteId extends string = string>(opts: BoolFieldOptions<RouteId>, registry?: ErrorRoutesRegistry<RouteId>): BoolFieldControls;
|
|
17
|
+
//# sourceMappingURL=useBoolField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBoolField.d.ts","sourceRoot":"","sources":["../src/useBoolField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,MAAM,MAAM,gBAAgB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAExG,MAAM,WAAW,iBAAkB,SAAQ,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC;IACxE;;;OAGG;IACH,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAC1D,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAC/B,QAAQ,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,GACtC,iBAAiB,CAQnB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useFieldCore } from './field.js';
|
|
2
|
+
/**
|
|
3
|
+
* A boolean field (moxb's Bool, the sidedao boolean field). Rendered by
|
|
4
|
+
* vultus-antd's BoolCheckbox and BoolSwitch, which take the same controls.
|
|
5
|
+
* Inside the field the value starts at `initialValue` (default false).
|
|
6
|
+
*/
|
|
7
|
+
export function useBoolField(opts, registry) {
|
|
8
|
+
const inside = !opts.onChange && !opts.commit;
|
|
9
|
+
const field = useFieldCore('boolean', inside ? { ...opts, initialValue: opts.initialValue ?? false } : opts, registry);
|
|
10
|
+
return { ...field, toggle: () => field.setValue(!field.value) };
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=useBoolField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBoolField.js","sourceRoot":"","sources":["../src/useBoolField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAyC,MAAM,YAAY,CAAC;AAajF;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,IAA+B,EAC/B,QAAuC;IAEvC,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAC9C,MAAM,KAAK,GAAG,YAAY,CACxB,SAAS,EACT,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EACrE,QAAQ,CACT,CAAC;IACF,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type FieldControls, type FieldOptions } from './field.js';
|
|
2
|
+
import type { ErrorRoutesRegistry } from './types.js';
|
|
3
|
+
/** What a mixed boolean field shows: on, off, or some of its items on and some off. */
|
|
4
|
+
export type MixedBool = boolean | 'mixed';
|
|
5
|
+
export type MixedBoolFieldOptions<RouteId extends string = string> = FieldOptions<MixedBool, boolean, RouteId>;
|
|
6
|
+
export interface MixedBoolFieldControls extends FieldControls<MixedBool, boolean> {
|
|
7
|
+
/** Request the opposite: off when on, on when off or mixed. Programmatic use, as in BoolFieldControls. */
|
|
8
|
+
toggle(): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A boolean field over an aggregate (moxb's indeterminate Bool, ARIA's
|
|
12
|
+
* "mixed"): it shows true, false or 'mixed', and can be asked for true or
|
|
13
|
+
* false only. Rendered by the same widgets as useBoolField: BoolSwitch puts
|
|
14
|
+
* its knob in the middle, BoolCheckbox is indeterminate. Derive the value with
|
|
15
|
+
* `aggregateBool`; with no items at all, disable the field with a reason.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useMixedBoolField<RouteId extends string = string>(opts: MixedBoolFieldOptions<RouteId>, registry?: ErrorRoutesRegistry<RouteId>): MixedBoolFieldControls;
|
|
18
|
+
/**
|
|
19
|
+
* The aggregate of several booleans: true when all are on, false when none is,
|
|
20
|
+
* 'mixed' otherwise; `undefined` when there are none (a case for disabling the
|
|
21
|
+
* field, not a value).
|
|
22
|
+
*/
|
|
23
|
+
export declare function aggregateBool(values: readonly boolean[]): MixedBool | undefined;
|
|
24
|
+
//# sourceMappingURL=useMixedBoolField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMixedBoolField.d.ts","sourceRoot":"","sources":["../src/useMixedBoolField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,uFAAuF;AACvF,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAE1C,MAAM,MAAM,qBAAqB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAE/G,MAAM,WAAW,sBAAuB,SAAQ,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC;IAC/E,0GAA0G;IAC1G,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAC/D,IAAI,EAAE,qBAAqB,CAAC,OAAO,CAAC,EACpC,QAAQ,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,GACtC,sBAAsB,CAQxB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,GAAG,SAAS,CAI/E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useFieldCore } from './field.js';
|
|
2
|
+
/**
|
|
3
|
+
* A boolean field over an aggregate (moxb's indeterminate Bool, ARIA's
|
|
4
|
+
* "mixed"): it shows true, false or 'mixed', and can be asked for true or
|
|
5
|
+
* false only. Rendered by the same widgets as useBoolField: BoolSwitch puts
|
|
6
|
+
* its knob in the middle, BoolCheckbox is indeterminate. Derive the value with
|
|
7
|
+
* `aggregateBool`; with no items at all, disable the field with a reason.
|
|
8
|
+
*/
|
|
9
|
+
export function useMixedBoolField(opts, registry) {
|
|
10
|
+
const inside = !opts.onChange && !opts.commit;
|
|
11
|
+
const field = useFieldCore('mixed-boolean', inside ? { ...opts, initialValue: opts.initialValue ?? false } : opts, registry);
|
|
12
|
+
return { ...field, toggle: () => field.setValue(field.value !== true) };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The aggregate of several booleans: true when all are on, false when none is,
|
|
16
|
+
* 'mixed' otherwise; `undefined` when there are none (a case for disabling the
|
|
17
|
+
* field, not a value).
|
|
18
|
+
*/
|
|
19
|
+
export function aggregateBool(values) {
|
|
20
|
+
if (values.length === 0)
|
|
21
|
+
return undefined;
|
|
22
|
+
const on = values.filter(Boolean).length;
|
|
23
|
+
return on === values.length ? true : on === 0 ? false : 'mixed';
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=useMixedBoolField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMixedBoolField.js","sourceRoot":"","sources":["../src/useMixedBoolField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAyC,MAAM,YAAY,CAAC;AAajF;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAoC,EACpC,QAAuC;IAEvC,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAC9C,MAAM,KAAK,GAAG,YAAY,CACxB,eAAe,EACf,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EACrE,QAAQ,CACT,CAAC;IACF,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,EAAE,CAAC;AAC1E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAA0B;IACtD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACzC,OAAO,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AAClE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vultus-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Framework-neutral action model + hooks for vultus (antd-free by design)",
|
|
6
6
|
"repository": {
|
|
@@ -11,16 +11,29 @@
|
|
|
11
11
|
"author": "Kristof Csillag <kristof.csillag@deai-labs.com>",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"exports": {
|
|
14
|
-
".":
|
|
15
|
-
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./link": {
|
|
20
|
+
"types": "./dist/LinkContext.d.ts",
|
|
21
|
+
"import": "./dist/LinkContext.js",
|
|
22
|
+
"default": "./dist/LinkContext.js"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
16
25
|
},
|
|
17
26
|
"files": [
|
|
27
|
+
"dist",
|
|
18
28
|
"src",
|
|
19
29
|
"!src/**/__tests__",
|
|
20
30
|
"!src/**/*.test.ts",
|
|
21
31
|
"!src/**/*.test.tsx",
|
|
22
32
|
"README.md"
|
|
23
33
|
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
24
37
|
"peerDependencies": {
|
|
25
38
|
"react": ">=18",
|
|
26
39
|
"react-i18next": ">=15"
|
|
@@ -39,7 +52,9 @@
|
|
|
39
52
|
"vitest": "^3.2.4"
|
|
40
53
|
},
|
|
41
54
|
"scripts": {
|
|
42
|
-
"build": "tsc",
|
|
55
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
43
56
|
"test": "vitest run"
|
|
44
|
-
}
|
|
57
|
+
},
|
|
58
|
+
"main": "./dist/index.js",
|
|
59
|
+
"types": "./dist/index.d.ts"
|
|
45
60
|
}
|
package/src/MessageSink.tsx
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { createContext, useContext } from 'react';
|
|
2
|
+
import type { Severity } from './types.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* How the action framework surfaces an otherwise-unhandled error (no
|
|
5
|
-
* no inline error target)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* How the action framework surfaces messages: an otherwise-unhandled error (no
|
|
6
|
+
* form field, no inline error target), and the messages an action reports
|
|
7
|
+
* through its ExecutionContext. `severity` defaults to 'error'. Default is a
|
|
8
|
+
* no-op so vultus-core stays antd-free and framework-neutral; vultus-antd's
|
|
9
|
+
* VultusProvider wires this to antd's message API. Hosts may inject any
|
|
10
|
+
* toast/notification mechanism.
|
|
8
11
|
*/
|
|
9
|
-
export type MessageSink = (text: string) => void;
|
|
12
|
+
export type MessageSink = (text: string, severity?: Severity) => void;
|
|
10
13
|
|
|
11
14
|
export const MessageSinkContext = createContext<MessageSink>(() => {});
|
|
12
15
|
|
package/src/action-core.ts
CHANGED
|
@@ -12,6 +12,9 @@ import type {
|
|
|
12
12
|
ConfirmationSpec,
|
|
13
13
|
Decision,
|
|
14
14
|
ErrorRoutesRegistry,
|
|
15
|
+
ExecutionContext,
|
|
16
|
+
Message,
|
|
17
|
+
Severity,
|
|
15
18
|
} from './types.js';
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -49,7 +52,7 @@ export interface FirePromiseDeps<RouteId extends string> {
|
|
|
49
52
|
reason: string | undefined;
|
|
50
53
|
getPending: () => boolean;
|
|
51
54
|
setPending: (value: boolean) => void;
|
|
52
|
-
|
|
55
|
+
setMessages: (messages: Message[]) => void;
|
|
53
56
|
errCtxRef: MutableRefObject<ActionErrorCtx | null>;
|
|
54
57
|
t: (key: string, opts?: Record<string, unknown>) => string;
|
|
55
58
|
registry?: ErrorRoutesRegistry<RouteId>;
|
|
@@ -70,13 +73,26 @@ export function makeFirePromise<TArgs, TResult, RouteId extends string>(
|
|
|
70
73
|
console.warn(`Action '${opts.id}': already pending — ignoring fire`);
|
|
71
74
|
return;
|
|
72
75
|
}
|
|
73
|
-
|
|
76
|
+
// Messages of this run: reported through the context while it runs, then
|
|
77
|
+
// the thrown error if it fails. Each report updates the status at once.
|
|
78
|
+
const messages: Message[] = [];
|
|
79
|
+
const report = (severity: Severity) => (text: string) => {
|
|
80
|
+
messages.push({ text, severity });
|
|
81
|
+
deps.setMessages([...messages]);
|
|
82
|
+
deps.messageSink?.(text, severity);
|
|
83
|
+
};
|
|
84
|
+
const ctx: ExecutionContext = { info: report('info'), warn: report('warning'), error: report('error') };
|
|
85
|
+
deps.setMessages([]);
|
|
74
86
|
deps.setPending(true);
|
|
75
87
|
try {
|
|
76
|
-
const result = await opts.fire(args);
|
|
88
|
+
const result = await opts.fire(args, ctx);
|
|
77
89
|
opts.onSuccess?.(result as TResult);
|
|
78
90
|
} catch (err) {
|
|
91
|
+
const parsed = parseApiError(err);
|
|
92
|
+
const text = parsed.message ?? 'Action failed';
|
|
79
93
|
if (opts.errorRoute && deps.registry) {
|
|
94
|
+
// A routed action: routeApiError decides where the error shows (a form
|
|
95
|
+
// field, the inline error, or the sink).
|
|
80
96
|
routeApiError(
|
|
81
97
|
err,
|
|
82
98
|
{
|
|
@@ -88,9 +104,12 @@ export function makeFirePromise<TArgs, TResult, RouteId extends string>(
|
|
|
88
104
|
},
|
|
89
105
|
deps.registry,
|
|
90
106
|
);
|
|
107
|
+
} else {
|
|
108
|
+
// No route: the sink shows it, like the messages reported through ctx.
|
|
109
|
+
deps.messageSink?.(text, 'error');
|
|
91
110
|
}
|
|
92
|
-
|
|
93
|
-
deps.
|
|
111
|
+
messages.push({ text, severity: 'error' });
|
|
112
|
+
deps.setMessages([...messages]);
|
|
94
113
|
} finally {
|
|
95
114
|
deps.setPending(false);
|
|
96
115
|
}
|
|
@@ -102,7 +121,7 @@ export function assembleStatus<TArgs>(
|
|
|
102
121
|
opts: ActionOptions<TArgs, any, any>,
|
|
103
122
|
fields: ResolvedFields,
|
|
104
123
|
pending: boolean,
|
|
105
|
-
|
|
124
|
+
messages: Message[],
|
|
106
125
|
firePromise: (args: TArgs) => Promise<void>,
|
|
107
126
|
): ActionStatus<TArgs> {
|
|
108
127
|
const fire = (args: TArgs) => {
|
|
@@ -118,7 +137,8 @@ export function assembleStatus<TArgs>(
|
|
|
118
137
|
reason: fields.reason,
|
|
119
138
|
invisible: fields.invisible,
|
|
120
139
|
confirmation: fields.confirmation,
|
|
121
|
-
|
|
140
|
+
messages,
|
|
141
|
+
errors: messages.filter((m) => m.severity === 'error').map((m) => m.text),
|
|
122
142
|
fire: fire as ActionStatus<TArgs>['fire'],
|
|
123
143
|
firePromise: firePromise as ActionStatus<TArgs>['firePromise'],
|
|
124
144
|
};
|
package/src/decision.ts
CHANGED
|
@@ -19,3 +19,7 @@ export const andDecisions = (a: Decision, b: Decision): Decision => {
|
|
|
19
19
|
}
|
|
20
20
|
return av ? b : a;
|
|
21
21
|
};
|
|
22
|
+
|
|
23
|
+
/** The opposite verdict, keeping the reason (a reason to disable is a reason not to enable). */
|
|
24
|
+
export const invertDecision = (d: Decision): Decision =>
|
|
25
|
+
typeof d === 'boolean' ? !d : { verdict: !d.verdict, reason: d.reason };
|