@cosmicdrift/kumiko-headless 0.295.0 → 0.297.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-headless",
3
- "version": "0.295.0",
3
+ "version": "0.297.0",
4
4
  "description": "Headless UI logic for Kumiko — Dispatcher contract, Form-Controller, View-Model, Nav-Resolver. Plattform- und React-frei; jeder Renderer (renderer, renderer-web, renderer-native, …) komponiert darauf.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -36,7 +36,7 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
- "@cosmicdrift/kumiko-framework": "0.295.0",
39
+ "@cosmicdrift/kumiko-framework": "0.297.0",
40
40
  "temporal-polyfill": "^0.3.2",
41
41
  "zod": "^4.4.3"
42
42
  },
@@ -119,6 +119,47 @@ describe("computeEditViewModel", () => {
119
119
  expect(asFields(vm.sections[1]).columns).toBe(1);
120
120
  });
121
121
 
122
+ // The two-column default came in with projectionDetail tabs and now also
123
+ // covers entityEdit tabs (fw#3134) — a tab is its own panel, where a stacked
124
+ // form's single column wastes the width.
125
+ test("a tabs layout defaults sections to 2 columns, not 1", () => {
126
+ const vm = computeEditViewModel({
127
+ screen: editScreen({
128
+ mode: "tabs",
129
+ sections: [
130
+ { id: "main", title: "Main", fields: ["customerName"] },
131
+ { id: "notes", title: "Notes", columns: 1, fields: ["notes"] },
132
+ ],
133
+ }),
134
+ entity: orderEntity,
135
+ values: {},
136
+ translate,
137
+ featureName: "orders",
138
+ });
139
+
140
+ expect(asFields(vm.sections[0]).columns).toBe(2);
141
+ expect(asFields(vm.sections[1]).columns).toBe(1);
142
+ });
143
+
144
+ test("tabs sections carry their declared id into the view model", () => {
145
+ const vm = computeEditViewModel({
146
+ screen: editScreen({
147
+ mode: "tabs",
148
+ sections: [
149
+ { id: "main", title: "Main", fields: ["customerName"] },
150
+ { id: "notes", title: "Notes", fields: ["notes"] },
151
+ ],
152
+ }),
153
+ entity: orderEntity,
154
+ values: {},
155
+ translate,
156
+ featureName: "orders",
157
+ });
158
+
159
+ expect(asFields(vm.sections[0]).id).toBe("main");
160
+ expect(asFields(vm.sections[1]).id).toBe("notes");
161
+ });
162
+
122
163
  test("visible predicate evaluated against current values (live-reactive)", () => {
123
164
  const screen = editScreen({
124
165
  sections: [
@@ -149,6 +149,7 @@ export function computeEditViewModel<
149
149
  if (isExtensionEditSection(sectionSpec)) {
150
150
  return {
151
151
  kind: "extension" as const,
152
+ ...(sectionSpec.id !== undefined && { id: sectionSpec.id }),
152
153
  title: translate(sectionSpec.title),
153
154
  component: sectionSpec.component,
154
155
  contributesToFormSubmit: sectionSpec.contributesToFormSubmit === true,
@@ -441,8 +442,9 @@ export function computeEditViewModel<
441
442
  // Boot-validator rejects fields.length === 0 with no groups (screens.ts),
442
443
  // so an empty section never reaches this code.
443
444
  const visible = fields.some((field) => field.visible);
444
- // Tabs mode renders this section as its own card, where two columns reads
445
- // better than the single-column default a stacked form keeps.
445
+ // Tabs mode renders this section as its own panel, where two columns reads
446
+ // better than the single-column default a stacked form keeps. Applies to
447
+ // entityEdit tabs as well since fw#3134, not just projectionDetail.
446
448
  const defaultColumns = screen.layout.mode === "tabs" ? 2 : 1;
447
449
  const groups = sectionSpec.groups?.map((group) => ({
448
450
  title: translate(group.title),
@@ -461,6 +463,7 @@ export function computeEditViewModel<
461
463
  }));
462
464
  return {
463
465
  kind: "fields" as const,
466
+ ...(sectionSpec.id !== undefined && { id: sectionSpec.id }),
464
467
  visible,
465
468
  // Titellose Section (flache Form) → kein h3; nur übersetzen wenn gesetzt.
466
469
  ...(sectionSpec.title !== undefined && { title: translate(sectionSpec.title) }),
@@ -266,6 +266,10 @@ export type EditSectionViewModel =
266
266
 
267
267
  export type EditFieldsSectionViewModel = {
268
268
  readonly kind: "fields";
269
+ /** From `EditFieldsSection.id`. The boot-validator makes it mandatory under
270
+ * `layout.mode: "tabs"` so the tab strip has a stable key per tab; other
271
+ * layouts leave it unset. */
272
+ readonly id?: string;
269
273
  /** False when the section has fields but none of them is visible.
270
274
  * The renderer then draws nothing, but the section stays in the array
271
275
  * (key stability). */
@@ -291,6 +295,9 @@ export type EditFieldsSectionViewModel = {
291
295
 
292
296
  export type EditExtensionSectionViewModel = {
293
297
  readonly kind: "extension";
298
+ /** From `EditExtensionSection.id` — same tab-strip role as on a "fields"
299
+ * section, see EditFieldsSectionViewModel.id. */
300
+ readonly id?: string;
294
301
  readonly title: string;
295
302
  readonly component: PlatformComponent;
296
303
  readonly contributesToFormSubmit: boolean;