@cosmicdrift/kumiko-bundled-features 0.34.1 → 0.35.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.34.1",
3
+ "version": "0.35.0",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -110,6 +110,63 @@ describe("CustomFieldsFormSection", () => {
110
110
  });
111
111
  });
112
112
 
113
+ test("pre-fills inputs from initialValues (Edit zeigt den Bestand, nicht write-only)", async () => {
114
+ mockedQueryRows = [
115
+ {
116
+ id: "f1",
117
+ entityName: "component",
118
+ fieldKey: "vendor",
119
+ type: "text",
120
+ required: false,
121
+ displayOrder: 1,
122
+ },
123
+ {
124
+ id: "f2",
125
+ entityName: "component",
126
+ fieldKey: "tier",
127
+ type: "number",
128
+ required: false,
129
+ displayOrder: 2,
130
+ },
131
+ ];
132
+ dispatchSpy.mockClear();
133
+
134
+ render(
135
+ <Wrapper>
136
+ <CustomFieldsFormSection
137
+ entityName="component"
138
+ entityId="row-42"
139
+ initialValues={{ vendor: "Hetzner", tier: 3 }}
140
+ />
141
+ </Wrapper>,
142
+ );
143
+
144
+ // Gespeicherte Werte sind in den Inputs sichtbar (kein write-only mehr).
145
+ const vendorInput = document.getElementById("custom-field-vendor") as HTMLInputElement;
146
+ const tierInput = document.getElementById("custom-field-tier") as HTMLInputElement;
147
+ expect(vendorInput.value).toBe("Hetzner");
148
+ expect(tierInput.value).toBe("3");
149
+
150
+ // Save bleibt disabled bis zur ersten Änderung (pending trackt nur Edits) —
151
+ // sonst würde jeder Mount alle Werte sinnlos neu schreiben.
152
+ const saveBtn = screen.getByTestId("custom-fields-form-save") as HTMLButtonElement;
153
+ expect(saveBtn.disabled).toBe(true);
154
+
155
+ // Nur das geänderte Feld wird geschrieben, nicht der unveränderte Bestand.
156
+ fireEvent.change(vendorInput, { target: { value: "Netcup" } });
157
+ expect(saveBtn.disabled).toBe(false);
158
+ fireEvent.click(saveBtn);
159
+ await Promise.resolve();
160
+ await Promise.resolve();
161
+ expect(dispatchSpy).toHaveBeenCalledTimes(1);
162
+ expect(dispatchSpy).toHaveBeenCalledWith("custom-fields:write:set-custom-field", {
163
+ entityName: "component",
164
+ entityId: "row-42",
165
+ fieldKey: "vendor",
166
+ value: "Netcup",
167
+ });
168
+ });
169
+
113
170
  test("shows create-mode banner when entityId is null and skips the fieldDefinition query", () => {
114
171
  mockedQueryRows = [];
115
172
  useQuerySpy.mockClear();
@@ -35,9 +35,15 @@ type FieldDefinitionListResponse = {
35
35
  export function CustomFieldsFormSection({
36
36
  entityName,
37
37
  entityId,
38
+ initialValues,
38
39
  }: {
39
40
  readonly entityName: string;
40
41
  readonly entityId: string | null;
42
+ /** Bereits gespeicherte customField-Werte der Entity (aus der detail-
43
+ * row durchgereicht). Ohne sie wäre die Section write-only — die Inputs
44
+ * zeigen den Bestand beim Edit. `pending` trackt nur Änderungen, also
45
+ * bleibt der Save-Button bis zur ersten Eingabe disabled. */
46
+ readonly initialValues?: Readonly<Record<string, unknown>>;
41
47
  }): ReactNode {
42
48
  const { Banner, Button, Field, Input, Text } = usePrimitives();
43
49
  const t = useTranslation();
@@ -122,8 +128,10 @@ export function CustomFieldsFormSection({
122
128
  label={field.fieldKey}
123
129
  required={field.required}
124
130
  >
125
- {renderInputFor(field, pending[field.fieldKey] ?? "", (v) =>
126
- setPending((p) => ({ ...p, [field.fieldKey]: v })),
131
+ {renderInputFor(
132
+ field,
133
+ pending[field.fieldKey] ?? displayValue(field.type, initialValues?.[field.fieldKey]),
134
+ (v) => setPending((p) => ({ ...p, [field.fieldKey]: v })),
127
135
  )}
128
136
  </Field>
129
137
  ))}
@@ -189,3 +197,13 @@ function coerceValue(type: string, raw: string): unknown {
189
197
  if (type === "boolean") return raw === "true";
190
198
  return raw;
191
199
  }
200
+
201
+ // Umkehrung von coerceValue: gespeicherter jsonb-Wert → string-Form für den
202
+ // Input. number/boolean/date/text werden so dargestellt, wie der Input sie
203
+ // erwartet; fehlende Werte werden zu "".
204
+ function displayValue(type: string, value: unknown): string {
205
+ if (value === undefined || value === null) return "";
206
+ if (type === "boolean") return value === true ? "true" : "false";
207
+ if (type === "number") return typeof value === "number" ? String(value) : String(value);
208
+ return String(value);
209
+ }