@cosmicdrift/kumiko-headless 0.328.0 → 0.328.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.
- package/dist/apex/css.d.ts +3 -0
- package/{src/apex/css.ts → dist/apex/css.js} +11 -24
- package/dist/apex/index.d.ts +229 -0
- package/dist/apex/index.js +360 -0
- package/dist/apex/lightbox.d.ts +4 -0
- package/{src/apex/lightbox.ts → dist/apex/lightbox.js} +0 -4
- package/dist/contracts/asset.d.ts +15 -0
- package/dist/contracts/asset.js +17 -0
- package/dist/contracts/index.d.ts +3 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/locale.d.ts +7 -0
- package/dist/contracts/locale.js +11 -0
- package/dist/contracts/primitives.d.ts +93 -0
- package/dist/contracts/primitives.js +21 -0
- package/dist/dispatcher/index.d.ts +2 -0
- package/dist/dispatcher/index.js +1 -0
- package/dist/dispatcher/types.d.ts +86 -0
- package/dist/dispatcher/types.js +34 -0
- package/dist/form/form-controller.d.ts +2 -0
- package/dist/form/form-controller.js +404 -0
- package/dist/form/index.d.ts +3 -0
- package/dist/form/index.js +2 -0
- package/dist/form/types.d.ts +71 -0
- package/dist/form/types.js +1 -0
- package/dist/form/zod-bridge.d.ts +5 -0
- package/{src/form/zod-bridge.ts → dist/form/zod-bridge.js} +52 -58
- package/dist/format/escape.d.ts +5 -0
- package/{src/format/escape.ts → dist/format/escape.js} +43 -47
- package/dist/format/html-template.d.ts +9 -0
- package/dist/format/html-template.js +39 -0
- package/dist/format/index.d.ts +10 -0
- package/dist/format/index.js +192 -0
- package/dist/format/money.d.ts +1 -0
- package/{src/format/money.ts → dist/format/money.js} +6 -5
- package/dist/index.d.ts +14 -0
- package/dist/index.js +9 -0
- package/dist/locale-routing/index.d.ts +39 -0
- package/dist/locale-routing/index.js +143 -0
- package/dist/nav/index.d.ts +2 -0
- package/dist/nav/index.js +1 -0
- package/dist/nav/resolve.d.ts +2 -0
- package/dist/nav/resolve.js +58 -0
- package/dist/nav/types.d.ts +26 -0
- package/dist/nav/types.js +1 -0
- package/dist/store/create-store.d.ts +2 -0
- package/{src/store/create-store.ts → dist/store/create-store.js} +21 -23
- package/dist/store/equality.d.ts +1 -0
- package/{src/store/equality.ts → dist/store/equality.js} +19 -17
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.js +2 -0
- package/dist/store/types.d.ts +7 -0
- package/dist/store/types.js +10 -0
- package/dist/view-model/edit.d.ts +10 -0
- package/dist/view-model/edit.js +392 -0
- package/dist/view-model/embedded-list.d.ts +27 -0
- package/dist/view-model/embedded-list.js +63 -0
- package/dist/view-model/index.d.ts +7 -0
- package/dist/view-model/index.js +3 -0
- package/dist/view-model/list.d.ts +16 -0
- package/dist/view-model/list.js +201 -0
- package/dist/view-model/types.d.ts +300 -0
- package/dist/view-model/types.js +1 -0
- package/package.json +31 -12
- package/src/apex/__tests__/lightbox.test.ts +0 -20
- package/src/apex/__tests__/page-head.test.ts +0 -63
- package/src/apex/__tests__/render.test.ts +0 -401
- package/src/apex/index.ts +0 -667
- package/src/contracts/__tests__/contracts.test.ts +0 -123
- package/src/contracts/__tests__/field-issue-compat.test.ts +0 -19
- package/src/contracts/asset.ts +0 -48
- package/src/contracts/index.ts +0 -23
- package/src/contracts/locale.ts +0 -33
- package/src/contracts/primitives.ts +0 -158
- package/src/dispatcher/__tests__/contract.test.ts +0 -163
- package/src/dispatcher/index.ts +0 -16
- package/src/dispatcher/types.ts +0 -217
- package/src/form/__tests__/conditional-fields.test.ts +0 -177
- package/src/form/__tests__/form-controller.test.ts +0 -231
- package/src/form/__tests__/submit.test.ts +0 -575
- package/src/form/__tests__/validation.test.ts +0 -274
- package/src/form/form-controller.ts +0 -458
- package/src/form/index.ts +0 -15
- package/src/form/types.ts +0 -297
- package/src/format/__tests__/escape.test.ts +0 -79
- package/src/format/__tests__/format.test.ts +0 -285
- package/src/format/__tests__/html-template.test.ts +0 -47
- package/src/format/html-template.ts +0 -49
- package/src/format/index.ts +0 -223
- package/src/index.ts +0 -118
- package/src/locale-routing/__tests__/locale-routing.test.ts +0 -276
- package/src/locale-routing/index.ts +0 -218
- package/src/nav/__tests__/resolve.test.ts +0 -279
- package/src/nav/index.ts +0 -8
- package/src/nav/resolve.ts +0 -60
- package/src/nav/types.ts +0 -70
- package/src/store/__tests__/create-store.test.ts +0 -139
- package/src/store/__tests__/equality.test.ts +0 -66
- package/src/store/index.ts +0 -3
- package/src/store/types.ts +0 -27
- package/src/view-model/__tests__/edit.test.ts +0 -1072
- package/src/view-model/__tests__/embedded-list.test.ts +0 -96
- package/src/view-model/__tests__/field-labels.test.ts +0 -62
- package/src/view-model/__tests__/list.test.ts +0 -439
- package/src/view-model/edit.ts +0 -645
- package/src/view-model/embedded-list.ts +0 -89
- package/src/view-model/index.ts +0 -43
- package/src/view-model/list.ts +0 -280
- package/src/view-model/types.ts +0 -400
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
function normalizePath(pathname) {
|
|
2
|
+
if (pathname === "" || pathname === "/")
|
|
3
|
+
return "/";
|
|
4
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
5
|
+
}
|
|
6
|
+
function defaultPrefixFor(locale) {
|
|
7
|
+
return `/${locale}`;
|
|
8
|
+
}
|
|
9
|
+
function stripLocalePrefix(path, locale, prefixFor) {
|
|
10
|
+
const prefix = prefixFor(locale);
|
|
11
|
+
if (path === prefix)
|
|
12
|
+
return "/";
|
|
13
|
+
if (path.startsWith(`${prefix}/`))
|
|
14
|
+
return path.slice(prefix.length);
|
|
15
|
+
return path;
|
|
16
|
+
}
|
|
17
|
+
export function createLocaleRouter(config) {
|
|
18
|
+
const { defaultLocale, prefixedLocales, routes, localeHints = {}, prefixFor = defaultPrefixFor, homePage = "home", } = config;
|
|
19
|
+
if (routes[homePage] === undefined) {
|
|
20
|
+
throw new Error(`locale-routing: homePage "${String(homePage)}" has no entry in routes — altLocalePath's fallback would throw at request time`);
|
|
21
|
+
}
|
|
22
|
+
const pathIndex = new Map();
|
|
23
|
+
for (const [page, localePaths] of Object.entries(routes)) {
|
|
24
|
+
for (const [locale, routePath] of Object.entries(localePaths)) {
|
|
25
|
+
pathIndex.set(normalizePath(routePath), { page, locale });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const [hintLocale, hints] of Object.entries(localeHints)) {
|
|
29
|
+
for (const hint of hints) {
|
|
30
|
+
const normalizedHint = normalizePath(hint);
|
|
31
|
+
if (pathIndex.has(normalizedHint))
|
|
32
|
+
continue;
|
|
33
|
+
for (const [page, localePaths] of Object.entries(routes)) {
|
|
34
|
+
const canonical = localePaths[hintLocale];
|
|
35
|
+
if (canonical === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
const canonicalNorm = normalizePath(canonical);
|
|
38
|
+
const hintSlug = stripLocalePrefix(normalizedHint, hintLocale, prefixFor);
|
|
39
|
+
const canonicalSlug = stripLocalePrefix(canonicalNorm, hintLocale, prefixFor);
|
|
40
|
+
if (hintSlug === canonicalSlug) {
|
|
41
|
+
pathIndex.set(normalizedHint, { page, locale: hintLocale });
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function detectLang(pathname) {
|
|
48
|
+
const path = normalizePath(pathname);
|
|
49
|
+
for (const locale of prefixedLocales) {
|
|
50
|
+
const prefix = prefixFor(locale);
|
|
51
|
+
if (path === prefix || path.startsWith(`${prefix}/`))
|
|
52
|
+
return locale;
|
|
53
|
+
}
|
|
54
|
+
const resolved = pathIndex.get(path);
|
|
55
|
+
if (resolved !== undefined)
|
|
56
|
+
return resolved.locale;
|
|
57
|
+
for (const [locale, hints] of Object.entries(localeHints)) {
|
|
58
|
+
if (hints.some((hint) => normalizePath(hint) === path))
|
|
59
|
+
return locale;
|
|
60
|
+
}
|
|
61
|
+
return defaultLocale;
|
|
62
|
+
}
|
|
63
|
+
function resolvePage(pathname) {
|
|
64
|
+
return pathIndex.get(normalizePath(pathname))?.page;
|
|
65
|
+
}
|
|
66
|
+
function publicPath(page, locale) {
|
|
67
|
+
const localePaths = routes[page];
|
|
68
|
+
const routePath = localePaths?.[locale];
|
|
69
|
+
if (localePaths === undefined ||
|
|
70
|
+
!Object.hasOwn(localePaths, locale) ||
|
|
71
|
+
routePath === undefined) {
|
|
72
|
+
throw new Error(`locale-routing: no path for page "${String(page)}" locale "${locale}"`);
|
|
73
|
+
}
|
|
74
|
+
return routePath;
|
|
75
|
+
}
|
|
76
|
+
function otherLocale(currentLocale) {
|
|
77
|
+
if (currentLocale === defaultLocale) {
|
|
78
|
+
return prefixedLocales[0] ?? defaultLocale;
|
|
79
|
+
}
|
|
80
|
+
return defaultLocale;
|
|
81
|
+
}
|
|
82
|
+
function altLocalePath(pathname, targetLocale) {
|
|
83
|
+
const path = normalizePath(pathname);
|
|
84
|
+
const resolved = pathIndex.get(path);
|
|
85
|
+
const locale = targetLocale ?? otherLocale(detectLang(pathname));
|
|
86
|
+
if (resolved !== undefined) {
|
|
87
|
+
return publicPath(resolved.page, locale);
|
|
88
|
+
}
|
|
89
|
+
return publicPath(homePage, locale);
|
|
90
|
+
}
|
|
91
|
+
function sectionAnchor(page, locale, fragment) {
|
|
92
|
+
return `${publicPath(page, locale)}#${fragment}`;
|
|
93
|
+
}
|
|
94
|
+
return { detectLang, publicPath, resolvePage, altLocalePath, sectionAnchor };
|
|
95
|
+
}
|
|
96
|
+
function defaultBrowserPathname() {
|
|
97
|
+
const loc = globalThis.location;
|
|
98
|
+
return typeof loc?.pathname === "string" ? loc.pathname : undefined;
|
|
99
|
+
}
|
|
100
|
+
function defaultSubscribePathname(listener) {
|
|
101
|
+
const g = globalThis;
|
|
102
|
+
if (typeof g.addEventListener !== "function" || typeof g.removeEventListener !== "function") {
|
|
103
|
+
return () => { };
|
|
104
|
+
}
|
|
105
|
+
g.addEventListener("popstate", listener);
|
|
106
|
+
return () => {
|
|
107
|
+
g.removeEventListener?.("popstate", listener);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Prefer URL-derived locale when `resolvePage` matches the current path;
|
|
112
|
+
* otherwise defer to `base.locale()`. Passes through setLocale and the rest of LocaleResolver.
|
|
113
|
+
* Without `translateFor`, `base.translate` must already be locale-live.
|
|
114
|
+
*/
|
|
115
|
+
export function wrapUrlLocaleResolver(base, options) {
|
|
116
|
+
const getPathname = options.pathname ?? defaultBrowserPathname;
|
|
117
|
+
const subscribePathname = options.subscribePathname ?? defaultSubscribePathname;
|
|
118
|
+
const resolveLocale = () => {
|
|
119
|
+
const path = getPathname();
|
|
120
|
+
if (path === undefined)
|
|
121
|
+
return base.locale();
|
|
122
|
+
if (options.resolvePage(path) != null) {
|
|
123
|
+
return options.detectLang(path);
|
|
124
|
+
}
|
|
125
|
+
return base.locale();
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
translate: (key, params) => options.translateFor
|
|
129
|
+
? options.translateFor(resolveLocale(), key, params)
|
|
130
|
+
: base.translate(key, params),
|
|
131
|
+
timeZone: () => base.timeZone(),
|
|
132
|
+
subscribe: (listener) => {
|
|
133
|
+
const unsubBase = base.subscribe(listener);
|
|
134
|
+
const unsubPath = subscribePathname(listener);
|
|
135
|
+
return () => {
|
|
136
|
+
unsubBase();
|
|
137
|
+
unsubPath();
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
setLocale: base.setLocale !== undefined ? (locale) => base.setLocale?.(locale) : undefined,
|
|
141
|
+
locale: resolveLocale,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { resolveNavigation } from "./resolve.js";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isUiAccessGranted } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
// Assembles the renderable nav tree from the registry's pre-grouped
|
|
3
|
+
// indexes (topLevel + byParent). Walks top-down: each node is
|
|
4
|
+
// access-checked; a hidden parent drops its entire subtree implicitly
|
|
5
|
+
// because we never recurse into it. Siblings sort by `order` (ascending,
|
|
6
|
+
// default 0), tie-broken by qualified name so renders stay deterministic
|
|
7
|
+
// across registry iteration orders.
|
|
8
|
+
//
|
|
9
|
+
// Pure — same inputs produce the same tree. The renderer memoizes the
|
|
10
|
+
// result and only recomputes when the registry changes (rare — boot
|
|
11
|
+
// only) or the user's roles change (logout/login, tenant-switch).
|
|
12
|
+
export function resolveNavigation(options) {
|
|
13
|
+
const { source, user } = options;
|
|
14
|
+
function build(entry) {
|
|
15
|
+
// Shared default-visible UI predicate, not hasAccess from the engine:
|
|
16
|
+
// that module pulls server-side deps and would break bundle purity.
|
|
17
|
+
if (!isUiAccessGranted(entry.access, user?.roles))
|
|
18
|
+
return null;
|
|
19
|
+
// `entry.id` is already the qualified name — the registry stores
|
|
20
|
+
// it that way. No reverse-index lookup needed.
|
|
21
|
+
const children = [];
|
|
22
|
+
for (const child of source.byParent(entry.id)) {
|
|
23
|
+
const node = build(child);
|
|
24
|
+
if (node !== null)
|
|
25
|
+
children.push(node);
|
|
26
|
+
}
|
|
27
|
+
children.sort(bySortKey);
|
|
28
|
+
return {
|
|
29
|
+
qualifiedName: entry.id,
|
|
30
|
+
label: entry.label,
|
|
31
|
+
order: entry.order ?? 0,
|
|
32
|
+
children,
|
|
33
|
+
...(entry.icon !== undefined && { icon: entry.icon }),
|
|
34
|
+
...(entry.screen !== undefined && { screen: entry.screen }),
|
|
35
|
+
...(entry.target !== undefined && { target: entry.target }),
|
|
36
|
+
...(entry.actions !== undefined && { actions: entry.actions }),
|
|
37
|
+
...(entry.createAction !== undefined && { createAction: entry.createAction }),
|
|
38
|
+
...(entry.provider !== undefined && { provider: entry.provider }),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const roots = [];
|
|
42
|
+
for (const entry of source.topLevel) {
|
|
43
|
+
const node = build(entry);
|
|
44
|
+
if (node !== null)
|
|
45
|
+
roots.push(node);
|
|
46
|
+
}
|
|
47
|
+
roots.sort(bySortKey);
|
|
48
|
+
return roots;
|
|
49
|
+
}
|
|
50
|
+
function bySortKey(a, b) {
|
|
51
|
+
// Primary key: `order` ascending. Secondary: qualifiedName alphabetic,
|
|
52
|
+
// which is a stable fallback — the registry-iteration order isn't
|
|
53
|
+
// guaranteed across boots, so tied-order entries would otherwise
|
|
54
|
+
// shuffle between renders.
|
|
55
|
+
if (a.order !== b.order)
|
|
56
|
+
return a.order - b.order;
|
|
57
|
+
return a.qualifiedName.localeCompare(b.qualifiedName);
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NavDefinition, TargetRef, TreeAction } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
export type NavNode = {
|
|
3
|
+
readonly qualifiedName: string;
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly icon?: string;
|
|
6
|
+
readonly screen?: string;
|
|
7
|
+
readonly target?: TargetRef;
|
|
8
|
+
readonly actions?: readonly TreeAction[];
|
|
9
|
+
readonly createAction?: TreeAction;
|
|
10
|
+
readonly provider?: boolean;
|
|
11
|
+
readonly order: number;
|
|
12
|
+
readonly children: readonly NavNode[];
|
|
13
|
+
};
|
|
14
|
+
export type NavTree = readonly NavNode[];
|
|
15
|
+
export type NavRegistrySlice = {
|
|
16
|
+
readonly topLevel: readonly NavDefinition[];
|
|
17
|
+
readonly byParent: (parentQualifiedName: string) => readonly NavDefinition[];
|
|
18
|
+
};
|
|
19
|
+
export type ResolveNavigationOptions = {
|
|
20
|
+
readonly source: NavRegistrySlice;
|
|
21
|
+
readonly user?: {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly roles: readonly string[];
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export type { NavDefinition };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { WritableStore } from "./types";
|
|
2
|
-
|
|
3
1
|
// Subscribe/Emit primitive matching React's useSyncExternalStore.
|
|
4
2
|
// Single canonical implementation — every stateful controller in
|
|
5
3
|
// ui-core/renderer land composes on top of this instead of rolling
|
|
@@ -21,25 +19,25 @@ import type { WritableStore } from "./types";
|
|
|
21
19
|
// store.setState(() => myNewFn);
|
|
22
20
|
// In practice, function-valued stores are rare — feature controllers
|
|
23
21
|
// hold values, not callbacks.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
22
|
+
export function createStore(initial) {
|
|
23
|
+
let snapshot = initial;
|
|
24
|
+
const listeners = new Set();
|
|
25
|
+
return {
|
|
26
|
+
getSnapshot: () => snapshot,
|
|
27
|
+
subscribe: (listener) => {
|
|
28
|
+
listeners.add(listener);
|
|
29
|
+
return () => {
|
|
30
|
+
listeners.delete(listener);
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
setState: (next) => {
|
|
34
|
+
const nextValue = typeof next === "function" ? next(snapshot) : next;
|
|
35
|
+
// skip: next value identical to snapshot, avoid notifying listeners
|
|
36
|
+
if (Object.is(nextValue, snapshot))
|
|
37
|
+
return;
|
|
38
|
+
snapshot = nextValue;
|
|
39
|
+
for (const listener of listeners)
|
|
40
|
+
listener();
|
|
41
|
+
},
|
|
42
|
+
};
|
|
45
43
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shallowEqual(a: unknown, b: unknown): boolean;
|
|
@@ -7,28 +7,30 @@
|
|
|
7
7
|
// Mirrors Zustand's `shallow` and react-redux's `shallowEqual` — same
|
|
8
8
|
//8-line shape, no recursion. Apps that need deep equality reach for
|
|
9
9
|
// their own helper.
|
|
10
|
-
|
|
11
10
|
// Signature is `(a: unknown, b: unknown)` not `<T>(a: T, b: T)` because the
|
|
12
11
|
// function legitimately compares cross-type values (primitive vs. object,
|
|
13
12
|
// null vs. {}, etc.) and returns false for mismatches. A generic `<T>`
|
|
14
13
|
// would force callers into type-gymnastics for those cases. Contra-variance
|
|
15
14
|
// ensures this still satisfies `(a: S, b: S) => boolean` slots like
|
|
16
15
|
// useStoreSelector's `equals` arg.
|
|
17
|
-
export function shallowEqual(a
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
export function shallowEqual(a, b) {
|
|
17
|
+
if (Object.is(a, b))
|
|
18
|
+
return true;
|
|
19
|
+
if (typeof a !== "object" || a === null)
|
|
20
|
+
return false;
|
|
21
|
+
if (typeof b !== "object" || b === null)
|
|
22
|
+
return false;
|
|
23
|
+
const keysA = Object.keys(a);
|
|
24
|
+
const keysB = Object.keys(b);
|
|
25
|
+
if (keysA.length !== keysB.length)
|
|
26
|
+
return false;
|
|
27
|
+
for (const key of keysA) {
|
|
28
|
+
if (!Object.hasOwn(b, key))
|
|
29
|
+
return false;
|
|
30
|
+
if (!Object.is(a[key], b[key])) {
|
|
31
|
+
// @cast-boundary generic-record
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
31
34
|
}
|
|
32
|
-
|
|
33
|
-
return true;
|
|
35
|
+
return true;
|
|
34
36
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Store contract — Pull-Style Subscribe/Emit matching React's
|
|
2
|
+
// useSyncExternalStore signature. The single canonical shape for
|
|
3
|
+
// every stateful controller in ui-core/renderer land (form,
|
|
4
|
+
// dispatcher-status, locale, token, nav, lifecycle).
|
|
5
|
+
//
|
|
6
|
+
// Rationale in docs/plans/ui-store.md. Not a new state-management
|
|
7
|
+
// decision — ui-decisions.md already picked Subscribe/Emit; this
|
|
8
|
+
// module just materializes the pattern instead of rolling a new
|
|
9
|
+
// Set<() => void> per controller.
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { EntityDefinition, EntityEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { EditViewModel, Translate } from "./types.js";
|
|
3
|
+
export type ComputeEditViewModelInput<TValues extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>> = {
|
|
4
|
+
readonly screen: EntityEditScreenDefinition;
|
|
5
|
+
readonly entity: EntityDefinition;
|
|
6
|
+
readonly values: TValues;
|
|
7
|
+
readonly translate: Translate;
|
|
8
|
+
readonly featureName: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function computeEditViewModel<TValues extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>>(input: ComputeEditViewModelInput<TValues>): EditViewModel;
|