vultus-core 0.1.1 → 0.2.1

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 (48) hide show
  1. package/README.md +35 -1
  2. package/dist/MessageSink.d.ts +8 -5
  3. package/dist/MessageSink.d.ts.map +1 -1
  4. package/dist/MessageSink.js.map +1 -1
  5. package/dist/action-core.d.ts +3 -3
  6. package/dist/action-core.d.ts.map +1 -1
  7. package/dist/action-core.js +24 -6
  8. package/dist/action-core.js.map +1 -1
  9. package/dist/decision.d.ts +2 -0
  10. package/dist/decision.d.ts.map +1 -1
  11. package/dist/decision.js +2 -0
  12. package/dist/decision.js.map +1 -1
  13. package/dist/field.d.ts +97 -0
  14. package/dist/field.d.ts.map +1 -0
  15. package/dist/field.js +95 -0
  16. package/dist/field.js.map +1 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +3 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/types.d.ts +34 -1
  22. package/dist/types.d.ts.map +1 -1
  23. package/dist/useAction.d.ts.map +1 -1
  24. package/dist/useAction.js +4 -4
  25. package/dist/useAction.js.map +1 -1
  26. package/dist/useActionList.d.ts +2 -2
  27. package/dist/useActionList.d.ts.map +1 -1
  28. package/dist/useActionList.js +6 -6
  29. package/dist/useActionList.js.map +1 -1
  30. package/dist/useBoolField.d.ts +17 -0
  31. package/dist/useBoolField.d.ts.map +1 -0
  32. package/dist/useBoolField.js +12 -0
  33. package/dist/useBoolField.js.map +1 -0
  34. package/dist/useMixedBoolField.d.ts +24 -0
  35. package/dist/useMixedBoolField.d.ts.map +1 -0
  36. package/dist/useMixedBoolField.js +25 -0
  37. package/dist/useMixedBoolField.js.map +1 -0
  38. package/package.json +1 -1
  39. package/src/MessageSink.tsx +8 -5
  40. package/src/action-core.ts +27 -7
  41. package/src/decision.ts +4 -0
  42. package/src/field.ts +197 -0
  43. package/src/index.ts +8 -0
  44. package/src/types.ts +43 -2
  45. package/src/useAction.ts +5 -5
  46. package/src/useActionList.ts +7 -7
  47. package/src/useBoolField.ts +30 -0
  48. package/src/useMixedBoolField.ts +43 -0
@@ -3,14 +3,14 @@ import { useTranslation } from 'react-i18next';
3
3
  import { useActionErrorCtx } from './useActionErrorCtx.js';
4
4
  import { useMessageSink } from './MessageSink.js';
5
5
  import { resolveActionFields, makeFirePromise, assembleStatus } from './action-core.js';
6
- import type { ActionOptions, ActionStatus, ErrorRoutesRegistry } from './types.js';
6
+ import type { ActionOptions, ActionStatus, ErrorRoutesRegistry, Message } from './types.js';
7
7
 
8
8
  /**
9
9
  * Build a DYNAMIC list of actions from a spec array — the list counterpart to
10
10
  * useAction. The hook count is fixed regardless of list length (one shared
11
- * error context, one pending/errors map, one memo), so it is usable where
11
+ * error context, one pending/messages map, one memo), so it is usable where
12
12
  * useAction — a per-call hook — cannot be looped (rules of hooks). Pending and
13
- * errors are tracked per action id in the shared maps; each action gets real
13
+ * messages are tracked per action id in the shared maps; each action gets real
14
14
  * per-item pending. The firing machinery + ActionStatus shape are the exact
15
15
  * same shared helpers useAction uses (see action-core.ts) — nothing is
16
16
  * reimplemented.
@@ -26,7 +26,7 @@ export function useActionList<TArgs = void, TResult = void, RouteId extends stri
26
26
  const messageSink = useMessageSink();
27
27
 
28
28
  const [pendingMap, setPendingMap] = useState<Record<string, boolean>>({});
29
- const [errorsMap, setErrorsMap] = useState<Record<string, string[]>>({});
29
+ const [messagesMap, setMessagesMap] = useState<Record<string, Message[]>>({});
30
30
  const pendingRef = useRef(pendingMap);
31
31
  pendingRef.current = pendingMap;
32
32
 
@@ -39,7 +39,7 @@ export function useActionList<TArgs = void, TResult = void, RouteId extends stri
39
39
  reason: fields.reason,
40
40
  getPending: () => !!pendingRef.current[opts.id],
41
41
  setPending: (value) => setPendingMap((m) => ({ ...m, [opts.id]: value })),
42
- setErrors: (errs) => setErrorsMap((m) => ({ ...m, [opts.id]: errs })),
42
+ setMessages: (msgs) => setMessagesMap((m) => ({ ...m, [opts.id]: msgs })),
43
43
  errCtxRef,
44
44
  t,
45
45
  registry,
@@ -49,10 +49,10 @@ export function useActionList<TArgs = void, TResult = void, RouteId extends stri
49
49
  opts,
50
50
  fields,
51
51
  !!pendingMap[opts.id],
52
- errorsMap[opts.id] ?? [],
52
+ messagesMap[opts.id] ?? [],
53
53
  firePromise,
54
54
  );
55
55
  }),
56
- [specs, pendingMap, errorsMap, registry, t, messageSink],
56
+ [specs, pendingMap, messagesMap, registry, t, messageSink],
57
57
  );
58
58
  }
@@ -0,0 +1,30 @@
1
+ import { useFieldCore, type FieldControls, type FieldOptions } from './field.js';
2
+ import type { ErrorRoutesRegistry } from './types.js';
3
+
4
+ export type BoolFieldOptions<RouteId extends string = string> = FieldOptions<boolean, boolean, RouteId>;
5
+
6
+ export interface BoolFieldControls extends FieldControls<boolean, boolean> {
7
+ /**
8
+ * Request the opposite value. Programmatic use; widgets obtain the field's
9
+ * confirmation for `!value` first, then call `setValue`.
10
+ */
11
+ toggle(): void;
12
+ }
13
+
14
+ /**
15
+ * A boolean field (moxb's Bool, the sidedao boolean field). Rendered by
16
+ * vultus-antd's BoolCheckbox and BoolSwitch, which take the same controls.
17
+ * Inside the field the value starts at `initialValue` (default false).
18
+ */
19
+ export function useBoolField<RouteId extends string = string>(
20
+ opts: BoolFieldOptions<RouteId>,
21
+ registry?: ErrorRoutesRegistry<RouteId>,
22
+ ): BoolFieldControls {
23
+ const inside = !opts.onChange && !opts.commit;
24
+ const field = useFieldCore<boolean, boolean, RouteId>(
25
+ 'boolean',
26
+ inside ? { ...opts, initialValue: opts.initialValue ?? false } : opts,
27
+ registry,
28
+ );
29
+ return { ...field, toggle: () => field.setValue(!field.value) };
30
+ }
@@ -0,0 +1,43 @@
1
+ import { useFieldCore, type FieldControls, type FieldOptions } from './field.js';
2
+ import type { ErrorRoutesRegistry } from './types.js';
3
+
4
+ /** What a mixed boolean field shows: on, off, or some of its items on and some off. */
5
+ export type MixedBool = boolean | 'mixed';
6
+
7
+ export type MixedBoolFieldOptions<RouteId extends string = string> = FieldOptions<MixedBool, boolean, RouteId>;
8
+
9
+ export interface MixedBoolFieldControls extends FieldControls<MixedBool, boolean> {
10
+ /** Request the opposite: off when on, on when off or mixed. Programmatic use, as in BoolFieldControls. */
11
+ toggle(): void;
12
+ }
13
+
14
+ /**
15
+ * A boolean field over an aggregate (moxb's indeterminate Bool, ARIA's
16
+ * "mixed"): it shows true, false or 'mixed', and can be asked for true or
17
+ * false only. Rendered by the same widgets as useBoolField: BoolSwitch puts
18
+ * its knob in the middle, BoolCheckbox is indeterminate. Derive the value with
19
+ * `aggregateBool`; with no items at all, disable the field with a reason.
20
+ */
21
+ export function useMixedBoolField<RouteId extends string = string>(
22
+ opts: MixedBoolFieldOptions<RouteId>,
23
+ registry?: ErrorRoutesRegistry<RouteId>,
24
+ ): MixedBoolFieldControls {
25
+ const inside = !opts.onChange && !opts.commit;
26
+ const field = useFieldCore<MixedBool, boolean, RouteId>(
27
+ 'mixed-boolean',
28
+ inside ? { ...opts, initialValue: opts.initialValue ?? false } : opts,
29
+ registry,
30
+ );
31
+ return { ...field, toggle: () => field.setValue(field.value !== true) };
32
+ }
33
+
34
+ /**
35
+ * The aggregate of several booleans: true when all are on, false when none is,
36
+ * 'mixed' otherwise; `undefined` when there are none (a case for disabling the
37
+ * field, not a value).
38
+ */
39
+ export function aggregateBool(values: readonly boolean[]): MixedBool | undefined {
40
+ if (values.length === 0) return undefined;
41
+ const on = values.filter(Boolean).length;
42
+ return on === values.length ? true : on === 0 ? false : 'mixed';
43
+ }