@cosmicdrift/kumiko-renderer 0.384.0 → 0.386.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/dist/app/action-form-body.d.ts +50 -0
- package/dist/app/action-form-body.js +275 -0
- package/dist/app/client-actions.js +2 -2
- package/dist/app/config-edit-body.d.ts +9 -0
- package/dist/app/config-edit-body.js +340 -0
- package/dist/app/entity-edit-prefill.d.ts +27 -0
- package/dist/app/entity-edit-prefill.js +377 -0
- package/dist/app/entity-edit-screen.d.ts +11 -0
- package/dist/app/entity-edit-screen.js +395 -0
- package/dist/app/entity-list-screen.d.ts +10 -0
- package/dist/app/entity-list-screen.js +547 -0
- package/dist/app/kumiko-screen.d.ts +1 -36
- package/dist/app/kumiko-screen.js +16 -2855
- package/dist/app/projection-detail-body.d.ts +12 -0
- package/dist/app/projection-detail-body.js +464 -0
- package/dist/app/projection-detail-shim.d.ts +1 -1
- package/dist/app/projection-detail-shim.js +5 -1
- package/dist/app/projection-list-body.d.ts +10 -0
- package/dist/app/projection-list-body.js +245 -0
- package/dist/app/projection-list-shim.d.ts +2 -1
- package/dist/app/projection-list-shim.js +4 -0
- package/dist/app/reference-facet-bridge.js +2 -2
- package/dist/app/row-actions.js +3 -4
- package/dist/app/screen-shared.d.ts +72 -0
- package/dist/app/screen-shared.js +325 -0
- package/dist/app/secret-mint-body.js +3 -3
- package/dist/components/edit-mounts.d.ts +33 -0
- package/dist/components/edit-mounts.js +53 -0
- package/dist/components/error-boundary.js +2 -2
- package/dist/components/reference-create-dialog.js +1 -1
- package/dist/components/related-list-section.js +29 -6
- package/dist/components/render-edit-helpers.d.ts +29 -0
- package/dist/components/render-edit-helpers.js +121 -0
- package/dist/components/render-edit.d.ts +1 -4
- package/dist/components/render-edit.js +41 -443
- package/dist/components/render-field.js +3 -4
- package/dist/components/render-list.d.ts +1 -1
- package/dist/components/render-list.js +2 -2
- package/dist/components/use-wizard-form-draft.d.ts +51 -0
- package/dist/components/use-wizard-form-draft.js +291 -0
- package/dist/dev-console.d.ts +3 -0
- package/dist/dev-console.js +15 -0
- package/dist/headless/contracts/primitives.js +14 -17
- package/dist/headless/format/index.js +3 -3
- package/dist/headless/view-model/edit.d.ts +1 -1
- package/dist/headless/view-model/edit.js +9 -16
- package/dist/headless/view-model/list.d.ts +1 -1
- package/dist/headless/view-model/list.js +3 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -5
- package/dist/live/error-mapping.js +3 -4
- package/dist/web/app/create-app.js +5 -8
- package/dist/web/app/dashboard-body.js +4 -599
- package/dist/web/app/dashboard-panels.d.ts +53 -0
- package/dist/web/app/dashboard-panels.js +373 -0
- package/dist/web/app/dashboard-shared.d.ts +90 -0
- package/dist/web/app/dashboard-shared.js +171 -0
- package/dist/web/app/dashboard-text.d.ts +6 -0
- package/dist/web/app/dashboard-text.js +65 -0
- package/dist/web/layout/nav-tree.js +3 -4
- package/dist/web/layout/target-resolver-stub.js +2 -2
- package/dist/web/primitives/banner.d.ts +4 -0
- package/dist/web/primitives/banner.js +28 -0
- package/dist/web/primitives/button.d.ts +7 -0
- package/dist/web/primitives/button.js +48 -0
- package/dist/web/primitives/card-styles.d.ts +8 -0
- package/dist/web/primitives/card-styles.js +18 -0
- package/dist/web/primitives/card.d.ts +4 -0
- package/dist/web/primitives/card.js +41 -0
- package/dist/web/primitives/contexts.d.ts +22 -0
- package/dist/web/primitives/contexts.js +50 -0
- package/dist/web/primitives/data-table-cell.d.ts +76 -0
- package/dist/web/primitives/data-table-cell.js +403 -0
- package/dist/web/primitives/data-table.d.ts +4 -0
- package/dist/web/primitives/data-table.js +279 -0
- package/dist/web/primitives/default-form.d.ts +4 -0
- package/dist/web/primitives/default-form.js +79 -0
- package/dist/web/primitives/facet-filter.d.ts +14 -0
- package/dist/web/primitives/facet-filter.js +39 -0
- package/dist/web/primitives/field.d.ts +63 -0
- package/dist/web/primitives/field.js +181 -0
- package/dist/web/primitives/form.d.ts +53 -0
- package/dist/web/primitives/form.js +252 -0
- package/dist/web/primitives/index.d.ts +7 -30
- package/dist/web/primitives/index.js +27 -2006
- package/dist/web/primitives/input.d.ts +4 -0
- package/dist/web/primitives/input.js +166 -0
- package/dist/web/primitives/layout-elements.d.ts +14 -0
- package/dist/web/primitives/layout-elements.js +194 -0
- package/dist/web/primitives/modal.js +1 -1
- package/dist/web/primitives/pager.d.ts +22 -0
- package/dist/web/primitives/pager.js +89 -0
- package/dist/web/primitives/row-actions.d.ts +13 -0
- package/dist/web/primitives/row-actions.js +178 -0
- package/dist/web/primitives/secret-reveal.d.ts +4 -0
- package/dist/web/primitives/secret-reveal.js +67 -0
- package/package.json +5 -5
- package/src/changes.json +6 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { evalFieldCondition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
3
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
4
|
+
import { useCallback, useMemo } from "react";
|
|
5
|
+
import { QueryOptionLabel, resolveOptionsQueryPayload, } from "../components/query-options-select.js";
|
|
6
|
+
import { RenderEdit } from "../components/render-edit.js";
|
|
7
|
+
import { useDispatcher } from "../context/dispatcher-context.js";
|
|
8
|
+
import { buildOptionLabels, fieldOptionLabelKey } from "../headless/index.js";
|
|
9
|
+
import { useQuery } from "../hooks/use-query.js";
|
|
10
|
+
import { useTranslation } from "../i18n.js";
|
|
11
|
+
import { usePrimitives } from "../primitives.js";
|
|
12
|
+
import { screenFillsHeight } from "../screen-fills-height.js";
|
|
13
|
+
import { buildConfigEditSchema, synthesizeConfigEditEntity, synthesizeConfigEditScreen, } from "./config-edit-shim.js";
|
|
14
|
+
import { buildInitialValues } from "./entity-edit-prefill.js";
|
|
15
|
+
import { layoutEditFields } from "./layout-fields.js";
|
|
16
|
+
import { QueryErrorBanner, QueryLoadingBanner } from "./query-state-banners.js";
|
|
17
|
+
// A money-typed config-edit field renders through RenderField's entityEdit
|
|
18
|
+
// `{amount, currency}` payload shape (render-field.tsx, #1923), but
|
|
19
|
+
// `ConfigKeyType` (write-helpers.ts validateType) only ever knows
|
|
20
|
+
// number/boolean/text/select — a config value is always a bare scalar.
|
|
21
|
+
// Unwrap back to the amount before it hits config:write:set.
|
|
22
|
+
function unwrapMoneyValue(value) {
|
|
23
|
+
if (typeof value === "object" && value !== null && "amount" in value) {
|
|
24
|
+
const amount = value.amount;
|
|
25
|
+
if (typeof amount === "number")
|
|
26
|
+
return amount;
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
const CONFIG_SCOPE_SOURCE = {
|
|
31
|
+
user: "user-row",
|
|
32
|
+
tenant: "tenant-row",
|
|
33
|
+
system: "system-row",
|
|
34
|
+
};
|
|
35
|
+
function isWriteOnlyTextField(fieldDef) {
|
|
36
|
+
return fieldDef?.type === "text" && fieldDef.writeOnly === true;
|
|
37
|
+
}
|
|
38
|
+
function hasStoredValue(stored) {
|
|
39
|
+
return stored !== undefined && stored !== null && stored !== "";
|
|
40
|
+
}
|
|
41
|
+
const CONFIG_WRITE_ISSUE_PATH = "value";
|
|
42
|
+
// write-helpers report every issue at `value`; in a batch that path is
|
|
43
|
+
// meaningless to the form, so point it at the field whose command failed.
|
|
44
|
+
function mapFailedConfigCommandIssuesToField(error, failedIndex, commandFieldNames) {
|
|
45
|
+
const fieldName = commandFieldNames[failedIndex];
|
|
46
|
+
const fields = error.details?.fields;
|
|
47
|
+
if (fieldName === undefined || fields === undefined)
|
|
48
|
+
return error;
|
|
49
|
+
return {
|
|
50
|
+
...error,
|
|
51
|
+
details: {
|
|
52
|
+
...error.details,
|
|
53
|
+
fields: fields.map((issue) => {
|
|
54
|
+
if (issue.path === CONFIG_WRITE_ISSUE_PATH)
|
|
55
|
+
return { ...issue, path: fieldName };
|
|
56
|
+
if (issue.path.startsWith(`${CONFIG_WRITE_ISSUE_PATH}.`)) {
|
|
57
|
+
return {
|
|
58
|
+
...issue,
|
|
59
|
+
path: `${fieldName}${issue.path.slice(CONFIG_WRITE_ISSUE_PATH.length)}`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return issue;
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// A money field without a declared default starts empty too (buildInitialValues
|
|
68
|
+
// maps it to 0, which a save would persist as a deliberate "0" override); numbers
|
|
69
|
+
// are handled there, except a required one, which config fields never are.
|
|
70
|
+
function buildConfigEditDefaults(fields) {
|
|
71
|
+
const defaults = buildInitialValues(fields); // @cast-boundary render-helper
|
|
72
|
+
for (const [shortName, fieldDef] of Object.entries(fields)) {
|
|
73
|
+
const { type, default: fieldDefault } = fieldDef; // @cast-boundary schema-walk
|
|
74
|
+
if ((type === "number" || type === "money") && fieldDefault === undefined) {
|
|
75
|
+
defaults[shortName] = undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return defaults;
|
|
79
|
+
}
|
|
80
|
+
// A field hidden by its `visible` condition (a provider's settings while another
|
|
81
|
+
// provider is picked) is not saved: the form saves what it shows.
|
|
82
|
+
function hiddenLayoutFieldNames(screen, values) {
|
|
83
|
+
return new Set(layoutEditFields(screen)
|
|
84
|
+
.filter((spec) => spec.visible !== undefined && !evalFieldCondition(spec.visible, values))
|
|
85
|
+
.map((spec) => spec.field));
|
|
86
|
+
}
|
|
87
|
+
function isEmptyNumericFormValue(value) {
|
|
88
|
+
if (value === undefined || value === null || value === "")
|
|
89
|
+
return true;
|
|
90
|
+
return typeof value === "number" && !Number.isFinite(value);
|
|
91
|
+
}
|
|
92
|
+
export function ConfigEditBody({ schema, screen, translate, }) {
|
|
93
|
+
const { ConfigCascadeView } = usePrimitives();
|
|
94
|
+
const t = useTranslation();
|
|
95
|
+
const effectiveTranslate = translate ?? t;
|
|
96
|
+
const dispatcher = useDispatcher();
|
|
97
|
+
// Detail load: config:query:values returns ALL keys of the tenant.
|
|
98
|
+
// We map them via screen.configKeys from short to qualified name onto our
|
|
99
|
+
// form field values.
|
|
100
|
+
const valuesQuery = useQuery("config:query:values", {});
|
|
101
|
+
const cascadeQuery = useQuery("config:query:cascade", {});
|
|
102
|
+
const secretKeys = screen.secretKeys;
|
|
103
|
+
const secretsQuery = useQuery("secrets:query:list", {}, { enabled: secretKeys !== undefined });
|
|
104
|
+
const synthEntity = useMemo(() => synthesizeConfigEditEntity(screen.fields), [screen.fields]);
|
|
105
|
+
const synthScreen = useMemo(() => synthesizeConfigEditScreen(screen), [screen]);
|
|
106
|
+
const boundsSchema = useMemo(() => buildConfigEditSchema(screen.fields), [screen.fields]);
|
|
107
|
+
// Initial values: per field name, fetch the value from `values[qualifiedKey]`.
|
|
108
|
+
// If the key is missing on the server (= never set), we use the field
|
|
109
|
+
// default (createTextField/createNumberField/...).
|
|
110
|
+
// String-coerce only for text fields; other types should already be the
|
|
111
|
+
// native type in the response.
|
|
112
|
+
const initial = useMemo(() => {
|
|
113
|
+
if (valuesQuery.data === null)
|
|
114
|
+
return null;
|
|
115
|
+
if (secretKeys !== undefined && secretsQuery.data === null)
|
|
116
|
+
return null;
|
|
117
|
+
const storedSecretKeys = new Set((secretsQuery.data ?? []).map((row) => row.key));
|
|
118
|
+
const out = {};
|
|
119
|
+
const defaults = buildConfigEditDefaults(screen.fields);
|
|
120
|
+
for (const [shortName, fieldDef] of Object.entries(screen.fields)) {
|
|
121
|
+
const secretKey = secretKeys?.[shortName];
|
|
122
|
+
if (secretKey !== undefined) {
|
|
123
|
+
out[shortName] = storedSecretKeys.has(secretKey) ? true : null;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const qualified = screen.configKeys[shortName];
|
|
127
|
+
if (qualified === undefined) {
|
|
128
|
+
// The author declared a field without a mapping: take the default.
|
|
129
|
+
// The boot validator pins this, it should never hit at runtime.
|
|
130
|
+
out[shortName] = defaults[shortName];
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const stored = valuesQuery.data[qualified]?.value;
|
|
134
|
+
// The read carries a mask (or nothing); only whether a value exists is
|
|
135
|
+
// meaningful, so the field gets the writeOnly wire shape true / null.
|
|
136
|
+
// The read is the effective value: an inherited one is not "set" on this
|
|
137
|
+
// scope, and a reset here would remove nothing.
|
|
138
|
+
if (isWriteOnlyTextField(fieldDef)) {
|
|
139
|
+
const ownedByThisScope = valuesQuery.data[qualified]?.source === CONFIG_SCOPE_SOURCE[screen.scope];
|
|
140
|
+
out[shortName] = ownedByThisScope && hasStoredValue(stored) ? true : null;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (stored === undefined) {
|
|
144
|
+
out[shortName] = defaults[shortName];
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const ftype = propertyOf(fieldDef, "type");
|
|
148
|
+
// Field type coercion: config values are string|number|boolean, but the
|
|
149
|
+
// form state expects the matching native field type.
|
|
150
|
+
if (ftype === "number" || ftype === "money") {
|
|
151
|
+
out[shortName] = typeof stored === "number" ? stored : Number(stored);
|
|
152
|
+
}
|
|
153
|
+
else if (ftype === "boolean") {
|
|
154
|
+
out[shortName] = typeof stored === "boolean" ? stored : stored === "true";
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
out[shortName] = typeof stored === "string" ? stored : String(stored);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}, [
|
|
162
|
+
valuesQuery.data,
|
|
163
|
+
secretsQuery.data,
|
|
164
|
+
secretKeys,
|
|
165
|
+
screen.fields,
|
|
166
|
+
screen.configKeys,
|
|
167
|
+
screen.scope,
|
|
168
|
+
]);
|
|
169
|
+
// Cascade lookup: qualifiedKey -> ConfigCascade for the cascade display
|
|
170
|
+
// under each field. Defensive `levels` shape check so foreign query mocks
|
|
171
|
+
// (e.g. configEdit unit tests that only mock `config:query:values`) do not
|
|
172
|
+
// crash through the cascade rendering.
|
|
173
|
+
const cascades = useMemo(() => {
|
|
174
|
+
if (!cascadeQuery.data)
|
|
175
|
+
return {};
|
|
176
|
+
const out = {};
|
|
177
|
+
for (const [shortName, qualified] of Object.entries(screen.configKeys)) {
|
|
178
|
+
const cascade = cascadeQuery.data[qualified];
|
|
179
|
+
if (cascade && Array.isArray(cascade.levels))
|
|
180
|
+
out[shortName] = cascade;
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}, [cascadeQuery.data, screen.configKeys]);
|
|
184
|
+
// Multi-write submit: a single /api/batch call with N config:write:set
|
|
185
|
+
// commands. Server-side the batch is atomic (transactional: all writes in
|
|
186
|
+
// one DB tx, all-or-nothing) and browser-side it is exactly one HTTP
|
|
187
|
+
// roundtrip, so there is no race between several in-flight fetches that
|
|
188
|
+
// the browser could abort on a mid-submit page.reload. Promise.all over N
|
|
189
|
+
// separate dispatcher.write calls was fragile: the server receives and
|
|
190
|
+
// commits all N, but the browser connection pool sporadically returns
|
|
191
|
+
// "Failed to fetch" for individual responses, customSubmit returns failure
|
|
192
|
+
// although the write went through, and the form stays dirty.
|
|
193
|
+
const customSubmit = useCallback(async (snapshot) => {
|
|
194
|
+
const commands = [];
|
|
195
|
+
const commandFieldNames = [];
|
|
196
|
+
const hiddenFieldNames = hiddenLayoutFieldNames(synthScreen, snapshot.values);
|
|
197
|
+
for (const [shortName, value] of Object.entries(snapshot.changes)) {
|
|
198
|
+
if (hiddenFieldNames.has(shortName))
|
|
199
|
+
continue;
|
|
200
|
+
const secretKey = secretKeys?.[shortName];
|
|
201
|
+
if (secretKey !== undefined) {
|
|
202
|
+
// Same wire shape as a writeOnly config field: null removes, a string replaces.
|
|
203
|
+
if (value === null) {
|
|
204
|
+
commandFieldNames.push(shortName);
|
|
205
|
+
commands.push({ type: "secrets:write:delete", payload: { key: secretKey } });
|
|
206
|
+
}
|
|
207
|
+
else if (typeof value === "string" && value.trim() !== "") {
|
|
208
|
+
commandFieldNames.push(shortName);
|
|
209
|
+
commands.push({ type: "secrets:write:set", payload: { key: secretKey, value } });
|
|
210
|
+
}
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const qualified = screen.configKeys[shortName];
|
|
214
|
+
if (qualified === undefined)
|
|
215
|
+
continue;
|
|
216
|
+
const fieldDef = screen.fields[shortName];
|
|
217
|
+
const ftype = fieldDef?.type;
|
|
218
|
+
if (isWriteOnlyTextField(screen.fields[shortName])) {
|
|
219
|
+
// true (untouched/undone) and "" mean keep the stored secret; null removes it.
|
|
220
|
+
if (value === null) {
|
|
221
|
+
commandFieldNames.push(shortName);
|
|
222
|
+
commands.push({
|
|
223
|
+
type: "config:write:reset",
|
|
224
|
+
payload: { key: qualified, scope: screen.scope },
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
else if (typeof value === "string" && value !== "") {
|
|
228
|
+
commandFieldNames.push(shortName);
|
|
229
|
+
commands.push({
|
|
230
|
+
type: "config:write:set",
|
|
231
|
+
payload: { key: qualified, value, scope: screen.scope },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// A query-backed select is cleared to "" when its dependent value became
|
|
237
|
+
// invalid. config:write:set would store that as an empty override (the
|
|
238
|
+
// badge shows "Mandant" with no value); reset falls back to the inherited
|
|
239
|
+
// value. set can't do it: deleting the row is irreversible, reset is
|
|
240
|
+
// the high-risk handler.
|
|
241
|
+
if (ftype === "select" && fieldDef?.optionsQuery !== undefined && value === "") {
|
|
242
|
+
commandFieldNames.push(shortName);
|
|
243
|
+
commands.push({
|
|
244
|
+
type: "config:write:reset",
|
|
245
|
+
payload: { key: qualified, scope: screen.scope },
|
|
246
|
+
});
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
// Cleared number/money: drop this scope's override instead of storing an empty value.
|
|
250
|
+
if ((ftype === "number" || ftype === "money") &&
|
|
251
|
+
isEmptyNumericFormValue(ftype === "money" ? unwrapMoneyValue(value) : value)) {
|
|
252
|
+
commandFieldNames.push(shortName);
|
|
253
|
+
commands.push({
|
|
254
|
+
type: "config:write:reset",
|
|
255
|
+
payload: { key: qualified, scope: screen.scope },
|
|
256
|
+
});
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
commandFieldNames.push(shortName);
|
|
260
|
+
commands.push({
|
|
261
|
+
type: "config:write:set",
|
|
262
|
+
payload: {
|
|
263
|
+
key: qualified,
|
|
264
|
+
value: ftype === "money" ? unwrapMoneyValue(value) : value,
|
|
265
|
+
scope: screen.scope,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (commands.length === 0) {
|
|
270
|
+
return { validationBlocked: false, isSuccess: true, data: undefined };
|
|
271
|
+
}
|
|
272
|
+
const result = await dispatcher.batch(commands);
|
|
273
|
+
if (!result.isSuccess) {
|
|
274
|
+
return {
|
|
275
|
+
validationBlocked: false,
|
|
276
|
+
isSuccess: false,
|
|
277
|
+
error: mapFailedConfigCommandIssuesToField(result.error, result.failedIndex, commandFieldNames),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// Refresh the cascade disclosure + mask values after the write, otherwise
|
|
281
|
+
// the display stays stale until reload; same refetch calls as onReset.
|
|
282
|
+
// allSettled: the write is already committed, a failed refetch (stale
|
|
283
|
+
// display) must not turn the success result into an error.
|
|
284
|
+
await Promise.allSettled([
|
|
285
|
+
valuesQuery.refetch?.(),
|
|
286
|
+
cascadeQuery.refetch?.(),
|
|
287
|
+
...(secretKeys !== undefined ? [secretsQuery.refetch?.()] : []),
|
|
288
|
+
]);
|
|
289
|
+
return { validationBlocked: false, isSuccess: true, data: undefined };
|
|
290
|
+
}, [
|
|
291
|
+
dispatcher,
|
|
292
|
+
synthScreen,
|
|
293
|
+
secretKeys,
|
|
294
|
+
screen.configKeys,
|
|
295
|
+
screen.fields,
|
|
296
|
+
screen.scope,
|
|
297
|
+
valuesQuery.refetch,
|
|
298
|
+
cascadeQuery.refetch,
|
|
299
|
+
secretsQuery.refetch,
|
|
300
|
+
]);
|
|
301
|
+
if (valuesQuery.loading && valuesQuery.data === null)
|
|
302
|
+
return _jsx(QueryLoadingBanner, {});
|
|
303
|
+
const failedQuery = valuesQuery.error ? valuesQuery : secretsQuery.error ? secretsQuery : null;
|
|
304
|
+
if (failedQuery?.error) {
|
|
305
|
+
return (_jsx(QueryErrorBanner, { error: failedQuery.error, translate: effectiveTranslate, onRetry: failedQuery.refetch }));
|
|
306
|
+
}
|
|
307
|
+
if (initial === null)
|
|
308
|
+
return _jsx(QueryLoadingBanner, {});
|
|
309
|
+
// Cascade values of a select show the option label the select itself shows,
|
|
310
|
+
// not the stored value (a UUID for optionsQuery selects).
|
|
311
|
+
const cascadeValueRenderer = (fieldName) => {
|
|
312
|
+
const fieldDef = screen.fields[fieldName];
|
|
313
|
+
if (fieldDef?.type !== "select")
|
|
314
|
+
return undefined;
|
|
315
|
+
if (fieldDef.optionsQuery !== undefined) {
|
|
316
|
+
const query = fieldDef.optionsQuery;
|
|
317
|
+
const payload = resolveOptionsQueryPayload(fieldDef.optionsQueryPayload ?? {}, initial);
|
|
318
|
+
return (value) => _jsx(QueryOptionLabel, { query: query, payload: payload, value: value });
|
|
319
|
+
}
|
|
320
|
+
if (fieldDef.options.length === 0)
|
|
321
|
+
return undefined;
|
|
322
|
+
return (value) => buildOptionLabels(effectiveTranslate, (option) => fieldOptionLabelKey(schema.featureName, synthScreen.entity, fieldName, option), [String(value)])[String(value)];
|
|
323
|
+
};
|
|
324
|
+
return (_jsx(RenderEdit, { screen: synthScreen, entity: synthEntity, featureName: schema.featureName, initial: initial, schema: boundsSchema, customSubmit: customSubmit, dirtyFooter: true, validateOnChange: true, ...(screenFillsHeight(screen) && { fillScreenHeight: true }), ...(screen.submitLabel !== undefined && { submitLabel: screen.submitLabel }), ...(translate !== undefined && { translate }), fieldAppendixPlacement: "below-control", fieldAccent: (fieldName) => cascades[fieldName]?.levels.find((level) => level.isActive)?.source ===
|
|
325
|
+
CONFIG_SCOPE_SOURCE[screen.scope], markChangedFields: true, ...(screen.fieldDescriptions !== undefined && {
|
|
326
|
+
fieldDescription: (fieldName) => {
|
|
327
|
+
const key = screen.fieldDescriptions?.[fieldName];
|
|
328
|
+
return key === undefined ? undefined : effectiveTranslate(key);
|
|
329
|
+
},
|
|
330
|
+
}), fieldAppendix: (fieldName) => {
|
|
331
|
+
const cascade = cascades[fieldName];
|
|
332
|
+
if (cascade === undefined)
|
|
333
|
+
return undefined;
|
|
334
|
+
const renderValue = cascadeValueRenderer(fieldName);
|
|
335
|
+
return (_jsx(ConfigCascadeView, { cascade: cascade, screenScope: screen.scope, qualifiedKey: screen.configKeys[fieldName], required: screen.requiredFields?.includes(fieldName) ?? false, ...(renderValue !== undefined && { renderValue }), onReset: async (key, scope) => {
|
|
336
|
+
await dispatcher.write("config:write:reset", { key, scope });
|
|
337
|
+
await Promise.allSettled([valuesQuery.refetch?.(), cascadeQuery.refetch?.()]);
|
|
338
|
+
} }));
|
|
339
|
+
} }));
|
|
340
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FormValues } from "../headless/index.ts";
|
|
2
|
+
export declare function versionOf(record: Readonly<Record<string, unknown>>): number;
|
|
3
|
+
export declare function buildInitialValues(fields: Readonly<Record<string, unknown>>, defaultCurrency?: string, moneyCurrencyOverrides?: Readonly<Record<string, string>>): FormValues;
|
|
4
|
+
export declare function tenantCurrencyMoneyFieldNames(fields: Readonly<Record<string, unknown>>): readonly string[];
|
|
5
|
+
export declare function literalCurrencyOverrides(fields: Readonly<Record<string, unknown>>): Readonly<Record<string, string>>;
|
|
6
|
+
export declare function useMoneyCurrencyOverrides(fields: Readonly<Record<string, unknown>>, tenantFieldNames: readonly string[], fallback: string | undefined): {
|
|
7
|
+
readonly overrides: Readonly<Record<string, string>> | undefined;
|
|
8
|
+
readonly loading: boolean;
|
|
9
|
+
readonly unresolved: boolean;
|
|
10
|
+
};
|
|
11
|
+
type MoneyValue = {
|
|
12
|
+
readonly amount: number;
|
|
13
|
+
readonly currency: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function isMoneyValue(value: unknown): value is MoneyValue;
|
|
16
|
+
export declare const ISO_DATE_PATTERN: RegExp;
|
|
17
|
+
export type InitialValueSources = {
|
|
18
|
+
readonly searchParams: Readonly<Record<string, string>>;
|
|
19
|
+
readonly urlPrefillFields: readonly string[] | undefined;
|
|
20
|
+
readonly renderableFields?: ReadonlySet<string>;
|
|
21
|
+
readonly defaultCurrency?: string;
|
|
22
|
+
readonly moneyCurrencyOverrides?: Readonly<Record<string, string>>;
|
|
23
|
+
readonly drawerOverrides?: Readonly<Record<string, unknown>>;
|
|
24
|
+
readonly handoffValues?: Readonly<Record<string, unknown>>;
|
|
25
|
+
};
|
|
26
|
+
export declare function mergeSearchParamsIntoInitial(fields: Readonly<Record<string, unknown>>, sources: InitialValueSources): Record<string, unknown>;
|
|
27
|
+
export {};
|