@cosmicdrift/kumiko-renderer 0.386.0 → 0.388.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.
Files changed (222) hide show
  1. package/LICENSE +21 -57
  2. package/dist/app/action-form-body.d.ts +2 -2
  3. package/dist/app/action-form-body.js +12 -4
  4. package/dist/app/action-form-shim.d.ts +8 -8
  5. package/dist/app/action-form-shim.js +20 -22
  6. package/dist/app/action-form-support.d.ts +1 -0
  7. package/dist/app/action-form-support.js +1 -1
  8. package/dist/app/app-features-context.js +4 -4
  9. package/dist/app/column-renderers.d.ts +7 -9
  10. package/dist/app/column-renderers.js +15 -18
  11. package/dist/app/config-edit-body.js +7 -3
  12. package/dist/app/config-edit-shim.d.ts +7 -8
  13. package/dist/app/config-edit-shim.js +18 -20
  14. package/dist/app/custom-screens.d.ts +3 -3
  15. package/dist/app/custom-screens.js +8 -8
  16. package/dist/app/dashboard-body.d.ts +2 -2
  17. package/dist/app/dashboard-body.js +2 -2
  18. package/dist/app/entity-edit-prefill.d.ts +11 -0
  19. package/dist/app/entity-edit-prefill.js +18 -13
  20. package/dist/app/entity-edit-screen.js +4 -4
  21. package/dist/app/entity-list-screen.js +3 -3
  22. package/dist/app/extension-form-submit.d.ts +1 -1
  23. package/dist/app/extension-form-submit.js +14 -15
  24. package/dist/app/extension-sections.d.ts +29 -28
  25. package/dist/app/extension-sections.js +10 -10
  26. package/dist/app/feature-schema.js +11 -11
  27. package/dist/app/form-schema.js +2 -2
  28. package/dist/app/header-actions-bar.js +2 -2
  29. package/dist/app/kumiko-screen.js +8 -10
  30. package/dist/app/layout-fields.js +1 -1
  31. package/dist/app/list-facets.js +3 -3
  32. package/dist/app/nav.d.ts +10 -10
  33. package/dist/app/nav.js +13 -13
  34. package/dist/app/projection-detail-body.js +4 -4
  35. package/dist/app/projection-detail-shim.d.ts +6 -5
  36. package/dist/app/projection-detail-shim.js +23 -22
  37. package/dist/app/projection-list-body.js +10 -10
  38. package/dist/app/projection-list-shim.d.ts +5 -5
  39. package/dist/app/projection-list-shim.js +17 -18
  40. package/dist/app/qn.js +10 -12
  41. package/dist/app/qualify-screen-id.js +1 -1
  42. package/dist/app/screen-access.js +2 -2
  43. package/dist/app/screen-shared.js +1 -1
  44. package/dist/app/secret-mint-body.js +4 -4
  45. package/dist/app/write-failed-error.js +8 -7
  46. package/dist/components/edit-mounts.d.ts +2 -2
  47. package/dist/components/embedded-list-field.js +5 -5
  48. package/dist/components/grid-cell-for-field.d.ts +1 -1
  49. package/dist/components/reference-create-dialog.js +3 -3
  50. package/dist/components/related-list-section.d.ts +1 -1
  51. package/dist/components/related-list-section.js +15 -2
  52. package/dist/components/render-edit-action-button.d.ts +1 -3
  53. package/dist/components/render-edit-action-button.js +2 -2
  54. package/dist/components/render-edit-logic.js +1 -1
  55. package/dist/components/render-edit-types.d.ts +6 -6
  56. package/dist/components/render-edit.js +74 -73
  57. package/dist/components/render-field.d.ts +7 -7
  58. package/dist/components/render-field.js +62 -61
  59. package/dist/components/render-list.d.ts +42 -43
  60. package/dist/components/render-list.js +93 -84
  61. package/dist/components/use-wizard-form-draft.js +10 -10
  62. package/dist/context/dispatcher-context.js +5 -5
  63. package/dist/context/user-roles-context.js +2 -2
  64. package/dist/flow-diagram/render.js +9 -1
  65. package/dist/headless/apex/index.d.ts +1 -2
  66. package/dist/headless/apex/index.js +5 -6
  67. package/dist/headless/form/form-controller.js +14 -6
  68. package/dist/headless/form/types.d.ts +1 -0
  69. package/dist/headless/form/zod-bridge.js +2 -2
  70. package/dist/headless/format/index.js +89 -64
  71. package/dist/headless/format/money.js +1 -1
  72. package/dist/headless/index.js +2 -2
  73. package/dist/headless/view-model/edit.js +4 -2
  74. package/dist/headless/view-model/list.js +184 -150
  75. package/dist/headless/view-model/types.d.ts +6 -3
  76. package/dist/hooks/live-refetch-scheduler.js +4 -0
  77. package/dist/hooks/reference-limits.js +11 -12
  78. package/dist/hooks/use-list-url-state.d.ts +13 -13
  79. package/dist/hooks/use-list-url-state.js +21 -22
  80. package/dist/hooks/use-query.js +3 -3
  81. package/dist/hooks/use-reference-lookup.d.ts +9 -1
  82. package/dist/hooks/use-reference-lookup.js +32 -19
  83. package/dist/hooks/use-store.js +18 -22
  84. package/dist/hooks/use-stream-handler.js +4 -2
  85. package/dist/i18n-defaults.js +6 -8
  86. package/dist/i18n.d.ts +10 -10
  87. package/dist/i18n.js +25 -25
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.js +6 -4
  90. package/dist/live/dispatcher-live.js +2 -2
  91. package/dist/primitives.d.ts +244 -221
  92. package/dist/primitives.js +1 -1
  93. package/dist/sse/live-events.d.ts +5 -5
  94. package/dist/sse/live-events.js +17 -17
  95. package/dist/tokens.d.ts +8 -8
  96. package/dist/tokens.js +16 -17
  97. package/dist/web/app/browser-locale.d.ts +9 -9
  98. package/dist/web/app/browser-locale.js +19 -19
  99. package/dist/web/app/client-plugin.d.ts +44 -52
  100. package/dist/web/app/client-plugin.js +4 -5
  101. package/dist/web/app/create-app.d.ts +28 -31
  102. package/dist/web/app/create-app.js +29 -25
  103. package/dist/web/app/dashboard-body.js +17 -1
  104. package/dist/web/app/dashboard-panels.js +12 -2
  105. package/dist/web/app/document-lang-sync.js +1 -1
  106. package/dist/web/app/draft-storage.js +1 -1
  107. package/dist/web/app/nav-providers-context.d.ts +3 -3
  108. package/dist/web/app/nav-providers-context.js +3 -3
  109. package/dist/web/app/nav.d.ts +18 -17
  110. package/dist/web/app/nav.js +66 -64
  111. package/dist/web/app/rich-content-editor.js +1 -1
  112. package/dist/web/components/config-cascade.js +17 -18
  113. package/dist/web/components/config-source-badge.js +4 -4
  114. package/dist/web/index.d.ts +1 -1
  115. package/dist/web/index.js +1 -1
  116. package/dist/web/layout/app-layout.d.ts +9 -9
  117. package/dist/web/layout/app-layout.js +2 -2
  118. package/dist/web/layout/avatar.d.ts +4 -3
  119. package/dist/web/layout/avatar.js +15 -17
  120. package/dist/web/layout/default-app-shell.d.ts +22 -23
  121. package/dist/web/layout/editor-panel.js +3 -3
  122. package/dist/web/layout/nav-tree.js +86 -84
  123. package/dist/web/layout/profile-menu.d.ts +1 -1
  124. package/dist/web/layout/shell-header.js +2 -1
  125. package/dist/web/layout/sidebar-brand.d.ts +7 -7
  126. package/dist/web/layout/sidebar-user.d.ts +1 -1
  127. package/dist/web/layout/sidebar-user.js +4 -4
  128. package/dist/web/layout/sidebar.d.ts +8 -8
  129. package/dist/web/layout/sidebar.js +7 -7
  130. package/dist/web/layout/target-resolver-stub.d.ts +5 -5
  131. package/dist/web/layout/target-resolver-stub.js +20 -20
  132. package/dist/web/layout/target-url.d.ts +7 -7
  133. package/dist/web/layout/target-url.js +22 -22
  134. package/dist/web/layout/theme-toggle.js +4 -5
  135. package/dist/web/layout/uniform-bar-controls.d.ts +1 -0
  136. package/dist/web/layout/uniform-bar-controls.js +4 -0
  137. package/dist/web/layout/workspace-shell.d.ts +21 -21
  138. package/dist/web/layout/workspace-shell.js +29 -30
  139. package/dist/web/lib/cn.d.ts +4 -4
  140. package/dist/web/lib/cn.js +4 -4
  141. package/dist/web/lib/file-ref-download.js +4 -1
  142. package/dist/web/primitives/action-menu.d.ts +8 -8
  143. package/dist/web/primitives/action-menu.js +14 -13
  144. package/dist/web/primitives/calendar-popover.d.ts +1 -1
  145. package/dist/web/primitives/calendar-popover.js +10 -10
  146. package/dist/web/primitives/card-styles.js +1 -1
  147. package/dist/web/primitives/card.js +2 -2
  148. package/dist/web/primitives/code-input.d.ts +6 -0
  149. package/dist/web/primitives/code-input.js +31 -0
  150. package/dist/web/primitives/combobox.d.ts +12 -12
  151. package/dist/web/primitives/combobox.js +34 -35
  152. package/dist/web/primitives/data-table-cell.js +2 -2
  153. package/dist/web/primitives/data-table.js +6 -6
  154. package/dist/web/primitives/date-field.js +2 -2
  155. package/dist/web/primitives/date-input.js +4 -4
  156. package/dist/web/primitives/default-form.js +2 -2
  157. package/dist/web/primitives/dropdown-menu.js +14 -15
  158. package/dist/web/primitives/embedded-list-input.js +3 -3
  159. package/dist/web/primitives/facet-filter.js +2 -2
  160. package/dist/web/primitives/field.js +9 -8
  161. package/dist/web/primitives/file-thumbnail.d.ts +3 -0
  162. package/dist/web/primitives/file-thumbnail.js +5 -0
  163. package/dist/web/primitives/form.d.ts +1 -1
  164. package/dist/web/primitives/form.js +6 -6
  165. package/dist/web/primitives/input.js +8 -3
  166. package/dist/web/primitives/layout-elements.js +7 -5
  167. package/dist/web/primitives/layout.js +2 -2
  168. package/dist/web/primitives/located-timestamp-input.js +4 -4
  169. package/dist/web/primitives/money-input.js +13 -17
  170. package/dist/web/primitives/page-header.js +3 -1
  171. package/dist/web/primitives/pager.js +1 -1
  172. package/dist/web/primitives/row-actions.js +6 -7
  173. package/dist/web/primitives/secret-reveal.js +1 -1
  174. package/dist/web/primitives/timestamp-input.d.ts +9 -9
  175. package/dist/web/primitives/timestamp-input.js +21 -21
  176. package/dist/web/primitives/toast.js +12 -13
  177. package/dist/web/primitives/use-focus-on-mount.d.ts +2 -0
  178. package/dist/web/primitives/use-focus-on-mount.js +9 -0
  179. package/dist/web/sse/live-events.d.ts +6 -6
  180. package/dist/web/sse/live-events.js +3 -3
  181. package/dist/web/tokens.d.ts +11 -12
  182. package/dist/web/tokens.js +35 -37
  183. package/dist/web/version/update-checker.js +14 -13
  184. package/dist/web/widgets/ai-text-field.js +1 -1
  185. package/dist/web/widgets/bounded-load-cache.d.ts +1 -1
  186. package/dist/web/widgets/bounded-load-cache.js +10 -4
  187. package/dist/web/widgets/charts.d.ts +10 -10
  188. package/dist/web/widgets/charts.js +8 -8
  189. package/dist/web/widgets/choice-cards.d.ts +17 -0
  190. package/dist/web/widgets/choice-cards.js +12 -0
  191. package/dist/web/widgets/collapsible-section.d.ts +3 -3
  192. package/dist/web/widgets/collapsible-section.js +7 -7
  193. package/dist/web/widgets/detail-list.d.ts +5 -3
  194. package/dist/web/widgets/detail-list.js +5 -3
  195. package/dist/web/widgets/feed-list.d.ts +2 -2
  196. package/dist/web/widgets/feed-list.js +2 -2
  197. package/dist/web/widgets/form-fields.d.ts +14 -14
  198. package/dist/web/widgets/form-fields.js +14 -13
  199. package/dist/web/widgets/index.d.ts +1 -0
  200. package/dist/web/widgets/index.js +5 -4
  201. package/dist/web/widgets/infinity-list.js +3 -3
  202. package/dist/web/widgets/mode-switch.d.ts +2 -2
  203. package/dist/web/widgets/mode-switch.js +2 -2
  204. package/dist/web/widgets/pdf-preview.js +1 -1
  205. package/dist/web/widgets/plan-card.d.ts +2 -0
  206. package/dist/web/widgets/plan-card.js +1 -1
  207. package/dist/web/widgets/progress-list.d.ts +1 -2
  208. package/dist/web/widgets/progress-list.js +1 -2
  209. package/dist/web/widgets/query-table.d.ts +6 -6
  210. package/dist/web/widgets/result-panel.d.ts +16 -16
  211. package/dist/web/widgets/result-panel.js +10 -10
  212. package/dist/web/widgets/section-card.d.ts +3 -3
  213. package/dist/web/widgets/section-card.js +3 -3
  214. package/dist/web/widgets/stat.d.ts +4 -4
  215. package/dist/web/widgets/stat.js +6 -6
  216. package/dist/web/widgets/states.d.ts +4 -4
  217. package/dist/web/widgets/states.js +4 -4
  218. package/dist/web/widgets/upload-zone.js +13 -5
  219. package/dist/web/widgets/use-draft.d.ts +7 -7
  220. package/dist/web/widgets/use-draft.js +7 -7
  221. package/package.json +6 -6
  222. package/src/changes.json +49 -0
@@ -16,7 +16,7 @@ import { fileIdsOf } from "../file-ids-of.js";
16
16
  import { applyFormatSpec, currencyDecimals, EMPTY_VALUE_PLACEHOLDER, } from "../headless/index.js";
17
17
  import { REFERENCE_COMBOBOX_LIMIT } from "../hooks/reference-limits.js";
18
18
  import { useQuery } from "../hooks/use-query.js";
19
- import { referenceOptionSource } from "../hooks/use-reference-lookup.js";
19
+ import { isCompleteRowPage, referenceOptionSource } from "../hooks/use-reference-lookup.js";
20
20
  import { useLocale, useTranslation } from "../i18n.js";
21
21
  import { usePrimitives } from "../primitives.js";
22
22
  import { AvailabilityOptionsMultiSelect, AvailabilityOptionsSelect, staticSelectOptions, } from "./availability-options-select.js";
@@ -29,11 +29,10 @@ const AllFieldsRequiredContext = createContext(false);
29
29
  export const AllFieldsRequiredProvider = AllFieldsRequiredContext.Provider;
30
30
  export function RenderField({ field, issues, onChange, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay = "form", row, changed, layout, accent, fillDisabled, hideLabel, }) {
31
31
  const { Field, Input, Banner, Text, JsonView } = usePrimitives();
32
- // App-Locale (i18n) für money/date-Inputs — sonst fielen sie auf
33
- // navigator.language (Browser-Sprache) zurück statt der gewählten
34
- // App-Sprache. BEWUSSTE API-Verschärfung (seit 0.38): RenderField ist
35
- // public exportiert und verlangt jetzt einen LocaleProvider —
36
- // Standalone-Consumer/Tests müssen wrappen (createKumikoApp tut es).
32
+ // The app locale is required for money/date inputs; otherwise they fall back
33
+ // to navigator.language instead of the selected app language. RenderField is
34
+ // publicly exported and therefore requires a LocaleProvider; standalone
35
+ // consumers and tests must wrap it (createKumikoApp does this).
37
36
  const appLocale = useLocale().locale();
38
37
  const t = useTranslation();
39
38
  const allFieldsRequired = useContext(AllFieldsRequiredContext);
@@ -45,13 +44,13 @@ export function RenderField({ field, issues, onChange, featureName, labelAppendi
45
44
  // the list-column path (render-list.tsx). Only takes effect once the field
46
45
  // is actually readOnly — a FormatSpec/PlatformComponent renderer has no
47
46
  // editable widget of its own, so applying it to an editable field would
48
- // silently make that field un-editable (fw#2245).
47
+ // silently make that field un-editable.
49
48
  const readOnlyText = valueDisplay === "text" && field.readOnly && field.renderer === undefined;
50
- // Reference-Field rendert eine eigene Component — sie nutzt
51
- // useQuery() für den Live-Lookup, also muss sie als React-
52
- // Komponente gemountet werden (nicht als pure render-Call).
53
- // The list input prints its own list-level issues (empty state included), so
54
- // the Field wrapper must not print them a second time.
49
+ // The reference field renders its own component because it uses useQuery()
50
+ // for the live lookup and must therefore be mounted as a React component,
51
+ // not called as a pure render function.
52
+ // The list input prints its own list-level issues (including the empty state),
53
+ // so the Field wrapper must not print them a second time.
55
54
  const listPrintsOwnIssues = field.type === "embedded" &&
56
55
  field.embeddedListCells !== undefined &&
57
56
  !(field.readOnly && field.renderer !== undefined);
@@ -168,38 +167,35 @@ function WriteOnlyTextInput({ field, id, hasError, onChange, changed, }) {
168
167
  },
169
168
  }) }));
170
169
  }
171
- // Tier 2.7e-3 + 2.1c: Reference-Input rendert eine Searchable Combobox
172
- // gefüllt aus einer Live-Query auf die referenced Entity. Default-
173
- // Limit: 200 — bei größeren Datasets fehlt der Tail im Dropdown
174
- // (Tier 2.7e-Remote: server-side Search-Query mit debounce kommt später).
175
- // - Display = row[refLabelField], Default labelField "id".
176
- // - Loading-State: leeres Dropdown bis die rows da sind. Field
177
- // ist disabled während useQuery läuft.
178
- // - Multi-Mode (Tier 2.7e-Multi via field.refMultiple): value ist
179
- // ein UUID-Array, Combobox rendert Selected-Tags.
170
+ // ReferenceInput renders a searchable combobox populated by a live query for
171
+ // the referenced entity. With the default limit of 200, larger datasets miss
172
+ // the tail in the dropdown; a server-side debounced search query comes later.
173
+ // - Display = row[refLabelField], default labelField "id".
174
+ // - During loading, the dropdown is empty and the field is disabled.
175
+ // - In multiple mode (field.refMultiple), value is a UUID array and the
176
+ // combobox renders tags.
180
177
  //
181
- // Storage: UI-Wert ist UUID (row.id) oder UUID-Array bei multiple.
182
- // Server-Schema: z.uuid() bzw. z.array(z.uuid()).
183
- // REFERENCE_COMBOBOX_LIMIT lebt zentral in hooks/reference-limits.ts
184
- // (siehe dort für Begründung der Default-Werte).
178
+ // Storage: the UI value is a UUID (row.id), or a UUID array in multiple mode.
179
+ // Server schema: z.uuid() or z.array(z.uuid()).
180
+ // REFERENCE_COMBOBOX_LIMIT is centralized in hooks/reference-limits.ts
181
+ // (see there for the rationale behind the default values).
185
182
  function ReferenceInput({ field, id, hasError, onChange, Input, featureName, }) {
186
183
  const { Banner } = usePrimitives();
187
184
  const refEntity = field.refEntity ?? "";
188
185
  const refFeature = field.refFeature ?? featureName;
189
186
  const isMultiple = field.refMultiple === true;
190
- // Tier 2.7e Cross-Feature: refFeature kann ≠ featureName sein
191
- // (z.B. items.assignee → users:query:user:list). Default ist
192
- // same-feature, kommt aus dem ViewModel (parseRefTarget).
193
- // fw#2780: refOptionsQuery replaces that source with an author-supplied
194
- // handler whose rows already carry a composed `label`.
187
+ // refFeature may differ from featureName (for example,
188
+ // items.assignee → users:query:user:list). The default is the same feature,
189
+ // as provided by the ViewModel (parseRefTarget).
190
+ // refOptionsQuery replaces that source with an author-supplied handler whose
191
+ // rows already carry a composed `label`.
195
192
  const { queryQn, labelKey } = referenceOptionSource(field, refFeature, refEntity);
196
- // Issue #1681: "+ Neu" in der Combobox öffnet den Create-Screen der
197
- // referenced entity als Dialog, statt die aktuelle Form zu verlassen.
198
- // refFeature kann ein anderes Feature als das aktuell gerenderte sein
199
- // — appFeatures (createKumikoApp) kennt alle Feature-Schemas, nicht
200
- // nur das der aktiven Screen. Kein Match (Feature/Screen/Entity nicht
201
- // registriert, oder allowCreate:false) → onCreate bleibt undefined,
202
- // Combobox rendert dann ohne den Footer.
193
+ // "+ Neu" in the combobox opens the referenced entity's create screen in a
194
+ // dialog instead of leaving the current form.
195
+ // refFeature may differ from the currently rendered feature; appFeatures
196
+ // (createKumikoApp) knows all feature schemas, not only the active screen.
197
+ // If the feature, screen, or entity is not registered, or allowCreate:false,
198
+ // onCreate remains undefined and the combobox renders without the footer.
203
199
  const appFeatures = useAppFeatures();
204
200
  const userRoles = useUserRoles();
205
201
  const t = useTranslation();
@@ -210,10 +206,10 @@ function ReferenceInput({ field, id, hasError, onChange, Input, featureName, })
210
206
  s.allowCreate !== false &&
211
207
  screenAccessAllows(s.access, userRoles));
212
208
  const refEntityDef = refTargetSchema?.entities[refEntity];
213
- // Tier 2.7e Remote-Search: User tippt im Combobox → Server filtert
214
- // via existing list-payload `search`-Param (Tier 2.6c). Combobox
215
- // debounced den keystroke selbst (300ms) und ruft onSearchChange.
216
- // Initial-State leer → erste 50 Items vom Server (default-sortiert).
209
+ // When the user types in the combobox, the server filters through the
210
+ // existing list-payload `search` parameter. The combobox debounces the
211
+ // keystroke itself (300ms) and calls onSearchChange.
212
+ // An empty initial state requests the first 50 items in default order.
217
213
  const [searchTerm, setSearchTerm] = useState("");
218
214
  const queryPayload = useMemo(() => searchTerm === ""
219
215
  ? { limit: REFERENCE_COMBOBOX_LIMIT }
@@ -254,13 +250,14 @@ function ReferenceInput({ field, id, hasError, onChange, Input, featureName, })
254
250
  return { value: idVal, label };
255
251
  });
256
252
  }, [queryResult.data, labelKey]);
257
- // Single: value ist String/null; Multi: Array. Coerce auf das was
258
- // der Combobox-Mode erwartet, damit Storage-Drift (Server liefert
259
- // alten String wo jetzt Array erwartet wird) keine Crash auslöst.
253
+ // Single mode uses a String/null value; multiple mode uses an array. Coerce
254
+ // to the shape expected by the combobox so storage drift (for example, an
255
+ // old String from the server where an array is now expected) cannot crash it.
260
256
  const baseInputProps = {
261
257
  id,
262
258
  name: field.field,
263
- // Initial-Load disabled — danach loading-Indicator im Popover.
259
+ // Disable the input during the initial load; subsequent loads show a
260
+ // loading indicator in the popover.
264
261
  disabled: field.readOnly || (queryResult.loading && options.length === 0),
265
262
  required: field.required,
266
263
  hasError,
@@ -295,7 +292,7 @@ function FieldRendererOutput({ field, renderer, row, appLocale, }) {
295
292
  if (isFormatSpec(renderer)) {
296
293
  // format:"json" wants the raw value (JsonView stringifies + highlights
297
294
  // itself) — applyFormatSpec's already-indented string is only the
298
- // fallback for primitives-providers without JsonView (fw#2312).
295
+ // fallback for primitives-providers without JsonView.
299
296
  if (renderer.format === "json" && JsonView !== undefined) {
300
297
  return (_jsx(JsonView, { value: field.value, indent: renderer.indent, testId: `field-value-${field.field}` }));
301
298
  }
@@ -308,8 +305,8 @@ function FieldRendererOutput({ field, renderer, row, appLocale, }) {
308
305
  // App locale as default when the FormatSpec declares none of its own —
309
306
  // otherwise locale-sensitive formats (timestamp/date/number/decimal/
310
307
  // bigInt/unit) fell back to Intl's runtime default instead of the app
311
- // language chosen via LocaleProvider (fw#2187). Prefer renderer.locale
312
- // when set; coalesce undefined (spread override) back to appLocale (#2332).
308
+ // language chosen via LocaleProvider. Prefer renderer.locale
309
+ // when set; coalesce undefined (spread override) back to appLocale.
313
310
  return (_jsx(Text, { testId: `field-value-${field.field}`, children: applyFormatSpec({
314
311
  ...renderer,
315
312
  locale: stringPropertyOf(renderer, "locale") ?? appLocale,
@@ -357,7 +354,10 @@ function ReadOnlyReferenceValue({ field, featureName, }) {
357
354
  const row = rows.find((r) => String(r["id"] ?? "") === id);
358
355
  if (row === undefined) {
359
356
  const unknownKey = UNRESOLVED_REFERENCE_LABEL_KEYS[`${refFeature}:${refEntity}`];
360
- const label = unknownKey !== undefined && !queryResult.loading && queryResult.error === null
357
+ const label = unknownKey !== undefined &&
358
+ !queryResult.loading &&
359
+ queryResult.error === null &&
360
+ isCompleteRowPage(queryResult.data)
361
361
  ? t(unknownKey)
362
362
  : id;
363
363
  return { id, label, link: undefined };
@@ -417,7 +417,7 @@ function resolveFieldIcon(field) {
417
417
  // from the EditFieldViewModel (computeEditViewModel pulls them from
418
418
  // SelectFieldDef.options). Structural types without a widget (embedded,
419
419
  // jsonb, multiSelect) render read-only instead of a data-destroying text
420
- // fallback (#1834); truly unknown scalar types still fall back to text.
420
+ // fallback; truly unknown scalar types still fall back to text.
421
421
  function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner, Text, JsonView, t, row, fill, }) {
422
422
  const common = {
423
423
  id,
@@ -438,7 +438,7 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
438
438
  // decimal/bigInt are both plain numbers on the wire (fieldToZod:
439
439
  // z.number() / z.number().int().safe()) — the number input's onChange
440
440
  // already emits `number | undefined`, so no extra coercion is needed
441
- // beyond what "number" already does (#1925).
441
+ // beyond what "number" already does.
442
442
  case "number":
443
443
  case "bigInt": {
444
444
  const icon = resolveFieldIcon(field);
@@ -472,11 +472,11 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
472
472
  case "locatedTimestamp":
473
473
  return (_jsx(Input, { kind: "locatedTimestamp", ...common, value: locatedValue(field.value), onChange: (v) => onChange(v), locale: field.dateLocale ?? appLocale, ...(field.min !== undefined && { min: field.min }), ...(field.max !== undefined && { max: field.max }) }));
474
474
  case "select": {
475
- // Translated Option-Labels kommen aus dem ViewModel-Builder
476
- // (computeEditViewModel, Convention-Key
475
+ // Translated option labels come from the ViewModel builder
476
+ // (computeEditViewModel, convention key
477
477
  // `<feature>:entity:<entity>:field:<field>:option:<value>`).
478
- // Wenn keine Translations registriert sind, fallback auf raw
479
- // value als Label — der ComboboxInput zeigt dann unverändert.
478
+ // If no translations are registered, fall back to the raw value as the
479
+ // label, which ComboboxInput then shows unchanged.
480
480
  const selectOptions = field.optionLabels !== undefined ? staticSelectOptions(field) : (field.options ?? []);
481
481
  // `display` is shared with multiSelect's "checkboxes", which is not a
482
482
  // select presentation — narrow to the two a select field can request.
@@ -495,11 +495,11 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
495
495
  case "images":
496
496
  return (_jsx(FileListField, { field: field, id: id, kind: field.type === "images" ? "image" : "file", hasError: hasError, onChange: (ids) => onChange(ids) }));
497
497
  // embedded (without embeddedListCells — that's embeddedList, which has
498
- // had its own EmbeddedListField widget since #1838) and jsonb carry
498
+ // had its own EmbeddedListField widget) and jsonb carry
499
499
  // arbitrary objects. Without a dedicated widget these must NOT fall
500
500
  // through to a text input: stringValue() turns them into
501
501
  // "[object Object]" and saving that overwrites the real data with the
502
- // mangled string (#1834). A `required: true` on either is caught loudly
502
+ // mangled string. A `required: true` on either is caught loudly
503
503
  // at boot (validateNoWidgetRequiredField in the framework package)
504
504
  // instead of silently failing here.
505
505
  case "embedded":
@@ -519,7 +519,7 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
519
519
  // is set (the view-model carries it), the renderer switches to
520
520
  // textarea. longText always renders a textarea — that's the point of
521
521
  // the type — regardless of whether `multiline` is set; `multiline`
522
- // only supplies an optional `{ rows }` override for it (#1925).
522
+ // only supplies an optional `{ rows }` override for it.
523
523
  if (field.type === "longText" || (field.type === "text" && field.multiline)) {
524
524
  const rows = typeof field.multiline === "object" ? field.multiline.rows : undefined;
525
525
  return (_jsx(Input, { kind: "textarea", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), ...(rows !== undefined && { rows }), ...placeholderProp }));
@@ -628,8 +628,9 @@ function moneyPayload(minorUnits, currency) {
628
628
  return undefined;
629
629
  return { amount: minorUnits / 10 ** currencyDecimals(currency), currency };
630
630
  }
631
- // locatedTimestamp-Feldwert: das Read-Wrapper liefert `{ at, tz, utc }`; leer
632
- // (noch nicht gesetzt) → "" als Empty-Sentinel, analog money/timestamp.
631
+ // A locatedTimestamp field value is supplied by the read wrapper as
632
+ // `{ at, tz, utc }`; an unset value becomes "" as the empty sentinel,
633
+ // consistent with money and timestamp fields.
633
634
  function locatedValue(v) {
634
635
  if (v !== null && typeof v === "object" && "at" in v && "tz" in v) {
635
636
  const utc = propertyOf(v, "utc");
@@ -644,7 +645,7 @@ function locatedValue(v) {
644
645
  // Read-only text for a `field.readOnly` field without its own `renderer` —
645
646
  // per-type formatting that reuses the same value-shaping helpers as the
646
647
  // editable widgets above, so a text value and its would-be Input widget stay
647
- // derived from the identical parse (fw#2245). `type: "reference"` isn't
648
+ // derived from the identical parse. `type: "reference"` isn't
648
649
  // covered here — it needs a live label lookup, see ReadOnlyReferenceValue.
649
650
  // `isComplexFieldType` types aren't covered either — callers keep those on
650
651
  // `renderInput`'s existing Banner fallback.
@@ -11,22 +11,21 @@ export type RenderListProps = {
11
11
  readonly featureName: string;
12
12
  readonly translate?: Translate;
13
13
  readonly onRowClick?: (row: ListRowViewModel) => void;
14
- /** Override der Default-Empty-Box. Wenn gesetzt, wird der Auto-CTA
15
- * via `onCreate` ignoriert — Caller-Inhalt gewinnt. */
14
+ /** Overrides the default empty box. When set, the automatic CTA via
15
+ * `onCreate` is ignored; the caller's content takes precedence. */
16
16
  readonly emptyState?: ReactNode;
17
- /** Setzt einen "+ Neu" Button in die Toolbar UND dient als CTA in
18
- * der Default-Empty-Box. Caller liefert die Action selber (z. B.
19
- * navigate auf den Edit-Screen). */
17
+ /** Adds a "+ New" button to the toolbar and uses it as the CTA in the
18
+ * default empty box. The caller provides the action (for example,
19
+ * navigation to the edit screen). */
20
20
  readonly onCreate?: () => void;
21
- /** Label für den + Neu Button. Default kommt aus dem i18n-Bundle
22
- * (`kumiko.actions.create`). Caller kann durch eigenen String
23
- * überschreiben. */
21
+ /** Label for the + New button. Defaults to the i18n bundle key
22
+ * (`kumiko.actions.create`). The caller can override it. */
24
23
  readonly createLabel?: string;
25
- /** Aktiviert ein Search-Input in der Toolbar. Server-side Filter
26
- * via Caller's onSearchChange — RenderList filtert NICHT lokal. */
24
+ /** Enables a search input in the toolbar. Filtering is server-side via
25
+ * the caller's onSearchChange; RenderList does not filter locally. */
27
26
  readonly searchable?: boolean;
28
- /** Placeholder für das Search-Input. Default kommt aus dem i18n-
29
- * Bundle (`kumiko.list.search-placeholder`). */
27
+ /** Placeholder for the search input. Defaults to the i18n bundle key
28
+ * (`kumiko.list.search-placeholder`). */
30
29
  readonly searchPlaceholder?: string;
31
30
  /** Hint at the left of the toolbar (already translated). */
32
31
  readonly description?: string;
@@ -38,35 +37,35 @@ export type RenderListProps = {
38
37
  readonly defaultFilterActive?: boolean;
39
38
  /** Plural-forms key for the footer count noun; default is the entity's `:noun` convention key. */
40
39
  readonly itemNounKey?: string;
41
- /** Aktueller Search-Term (vom URL-State / Parent). RenderList puffert
42
- * Tipps lokal mit 300ms Debounce, bevor onSearchChange gefeuert
43
- * wird — sonst macht jeder Tastendruck einen Server-Roundtrip. */
40
+ /** Current search term (from URL state / the parent). RenderList buffers
41
+ * typing locally with a 300ms debounce before calling onSearchChange;
42
+ * otherwise every keystroke would trigger a server round trip. */
44
43
  readonly searchValue?: string;
45
- /** Wird gerufen wenn der debounced Search-Term sich ändert. Caller
46
- * setzt damit URL-State (?<id>.q=…) und triggert ein refetch. */
44
+ /** Called when the debounced search term changes. The caller uses it to
45
+ * set URL state (?<id>.q=…) and trigger a refetch. */
47
46
  readonly onSearchChange?: (next: string) => void;
48
- /** Aktuelle Sortierung (oder null = Server-Default-Order). */
47
+ /** Current sorting, or null for the server's default order. */
49
48
  readonly sort?: ListSort | null;
50
- /** Wird gerufen mit dem nächsten Sort-State nach einem Header-Klick. */
49
+ /** Called with the next sort state after a header click. */
51
50
  readonly onSortChange?: (next: ListSort | null) => void;
52
- /** Pager-Props für pagination="pages". Wenn undefined, kein Pager-
53
- * UI — Default oder infinite-Mode. KumikoScreen liefert das je nach
54
- * screen.pagination. */
51
+ /** Pager props for pagination="pages". If undefined, no pager UI is
52
+ * rendered; the default or infinite mode is used. KumikoScreen supplies
53
+ * this according to screen.pagination. */
55
54
  readonly pager?: {
56
55
  readonly page: number;
57
56
  readonly limit: number;
58
57
  readonly total: number;
59
58
  readonly onPageChange: (next: number) => void;
60
59
  };
61
- /** Infinite-Scroll-Wiring für pagination="infinite". KumikoScreen
62
- * hält accumulated rows + cursor, RenderList reicht die Callbacks
63
- * einfach durch an DataTable. */
60
+ /** Infinite-scroll wiring for pagination="infinite". KumikoScreen keeps
61
+ * the accumulated rows and cursor; RenderList passes the callbacks
62
+ * through to DataTable. */
64
63
  readonly onReachEnd?: () => void;
65
64
  readonly loadingMore?: boolean;
66
65
  readonly hasMore?: boolean;
67
- /** Pro-Row-Aktionen — Resolved-Form (KumikoScreen baut das aus
68
- * EntityListScreenDefinition.rowActions: handler-QN → dispatcher-Call,
69
- * i18n-Keys → translated Strings). */
66
+ /** Per-row actions in resolved form. KumikoScreen builds them from
67
+ * EntityListScreenDefinition.rowActions: handler-QN becomes a dispatcher
68
+ * call, and i18n keys become translated strings. */
70
69
  readonly rowActions?: readonly DataTableRowAction[];
71
70
  /** How the row-action column renders (see `DataTableProps.rowActionMode`).
72
71
  * KumikoScreen derives it from the actions themselves; without it the
@@ -75,38 +74,38 @@ export type RenderListProps = {
75
74
  /** Forwarded to `DataTableProps.rowGrouping` / `rowTone`. */
76
75
  readonly rowGrouping?: DataTableRowGrouping;
77
76
  readonly rowTone?: DataTableProps["rowTone"];
78
- /** Toolbar-Aktionen im List-Header — Resolved-Form (KumikoScreen baut
79
- * das aus EntityListScreenDefinition.toolbarActions: navigate-target
80
- * → useNav, handler-QN → dispatcher-Call). RenderList rendert die
81
- * Buttons rechts in der Toolbar, vor "+ Neu". */
77
+ /** Toolbar actions in the list header in resolved form. KumikoScreen
78
+ * builds them from EntityListScreenDefinition.toolbarActions:
79
+ * navigate targets use useNav and handler-QN uses a dispatcher call.
80
+ * RenderList renders the buttons on the right side of the toolbar,
81
+ * before "+ New". */
82
82
  readonly toolbarActions?: readonly ToolbarActionButton[];
83
- /** Faceted-Filter-Dropdowns — KumikoScreen baut sie aus den filterable
84
- * select/boolean-Feldern; RenderList reicht sie an die DataTable durch. */
83
+ /** Faceted filter dropdowns. KumikoScreen builds them from filterable
84
+ * select/boolean fields; RenderList passes them through to DataTable. */
85
85
  readonly filterFacets?: readonly DataTableFacet[];
86
- /** Aktuell gewählte Facet-Werte je Feld (vom URL-State). */
86
+ /** Currently selected facet values by field (from URL state). */
87
87
  readonly filterValues?: Readonly<Record<string, readonly string[]>>;
88
- /** Toggle eines Facet-Werts (URL-State setzen + refetch). */
88
+ /** Toggles a facet value (sets URL state and refetches). */
89
89
  readonly onFilterChange?: (field: string, values: readonly string[]) => void;
90
90
  /** Reset aller aktiven Facets. */
91
91
  readonly onFilterReset?: () => void;
92
- /** Time-range filters (fw#3104) — forwarded to the DataTable. */
92
+ /** Time-range filters — forwarded to the DataTable. */
93
93
  readonly dateRangeFacets?: readonly DataTableDateRangeFacet[];
94
94
  /** One bound changed (set URL state + refetch). */
95
95
  readonly onDateRangeChange?: (field: string, bound: "from" | "to", value: string) => void;
96
96
  /** Forwarded to `DataTableProps.chromeless` — drops the table's own card
97
97
  * frame for a host that already provides one (relatedList in a tabs-mode
98
- * section, fw#2722). Default false. */
98
+ * section). Default false. */
99
99
  readonly chromeless?: boolean;
100
100
  /** Forwarded to `DataTableProps.scrollBody` — bounds the table height and
101
101
  * scrolls rows internally instead of growing the page (relatedList in a
102
- * tabs-mode section, fw#2722). Default false. */
102
+ * tabs-mode section). Default false. */
103
103
  readonly scrollBody?: boolean;
104
104
  /** Forwarded to `DataTableProps.screenPadding` — `true`: the table carries
105
105
  * the shared screen padding because it is the screen body (entityList/
106
- * projectionList, fw#2640). Explicit `false`: no padding at all — the
107
- * host already pads its own children (relatedList in a tabs-mode Card,
108
- * fw#3234). Omitted: legacy `p-6` fallback for a standalone table with no
109
- * padded host. */
106
+ * projectionList). Explicit `false`: no padding at all — the host already
107
+ * pads its own children (relatedList in a tabs-mode Card). Omitted: legacy
108
+ * `p-6` fallback for a standalone table with no padded host. */
110
109
  readonly screenPadding?: boolean;
111
110
  /** Forwarded to `DataTableProps.expandedRowIds` / `onToggleRowExpanded` /
112
111
  * `renderExpandedRow` (entityList `expandableRow`). */