@cosmicdrift/kumiko-renderer 0.327.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.
Files changed (257) hide show
  1. package/dist/app/action-form-shim.d.ts +14 -0
  2. package/{src/app/action-form-shim.ts → dist/app/action-form-shim.js} +26 -44
  3. package/dist/app/app-features-context.d.ts +8 -0
  4. package/dist/app/app-features-context.js +16 -0
  5. package/dist/app/column-renderers.d.ts +30 -0
  6. package/dist/app/column-renderers.js +34 -0
  7. package/dist/app/config-edit-shim.d.ts +11 -0
  8. package/{src/app/config-edit-shim.ts → dist/app/config-edit-shim.js} +12 -25
  9. package/dist/app/content-editors.d.ts +38 -0
  10. package/dist/app/content-editors.js +26 -0
  11. package/dist/app/content-preview.d.ts +11 -0
  12. package/dist/app/content-preview.js +30 -0
  13. package/dist/app/custom-screens.d.ts +11 -0
  14. package/dist/app/custom-screens.js +20 -0
  15. package/dist/app/dashboard-body.d.ts +16 -0
  16. package/dist/app/dashboard-body.js +11 -0
  17. package/dist/app/extension-form-submit.d.ts +32 -0
  18. package/dist/app/extension-form-submit.js +73 -0
  19. package/dist/app/extension-sections.d.ts +72 -0
  20. package/dist/app/extension-sections.js +34 -0
  21. package/dist/app/feature-schema.d.ts +4 -0
  22. package/{src/app/feature-schema.ts → dist/app/feature-schema.js} +12 -22
  23. package/dist/app/form-schema.d.ts +4 -0
  24. package/dist/app/form-schema.js +85 -0
  25. package/dist/app/kumiko-screen.d.ts +43 -0
  26. package/dist/app/kumiko-screen.js +2206 -0
  27. package/dist/app/layout-fields.d.ts +3 -0
  28. package/dist/app/layout-fields.js +20 -0
  29. package/dist/app/list-facets.d.ts +48 -0
  30. package/dist/app/list-facets.js +185 -0
  31. package/dist/app/nav.d.ts +61 -0
  32. package/dist/app/nav.js +139 -0
  33. package/dist/app/projection-detail-shim.d.ts +8 -0
  34. package/dist/app/projection-detail-shim.js +71 -0
  35. package/dist/app/projection-list-shim.d.ts +13 -0
  36. package/{src/app/projection-list-shim.ts → dist/app/projection-list-shim.js} +26 -38
  37. package/dist/app/qn.d.ts +7 -0
  38. package/{src/app/qn.ts → dist/app/qn.js} +9 -11
  39. package/dist/app/qualify-screen-id.d.ts +2 -0
  40. package/{src/app/qualify-screen-id.ts → dist/app/qualify-screen-id.js} +5 -6
  41. package/dist/app/reference-facet-bridge.d.ts +10 -0
  42. package/dist/app/reference-facet-bridge.js +22 -0
  43. package/dist/app/return-to.d.ts +31 -0
  44. package/dist/app/return-to.js +188 -0
  45. package/dist/app/row-actions.d.ts +77 -0
  46. package/dist/app/row-actions.js +416 -0
  47. package/dist/app/screen-access.d.ts +8 -0
  48. package/dist/app/screen-access.js +48 -0
  49. package/dist/app/secret-mint-body.d.ts +10 -0
  50. package/dist/app/secret-mint-body.js +140 -0
  51. package/dist/app/secrets-edit-body.d.ts +8 -0
  52. package/dist/app/secrets-edit-body.js +76 -0
  53. package/dist/app/use-embedded-screen.d.ts +6 -0
  54. package/dist/app/use-embedded-screen.js +19 -0
  55. package/dist/app/variable-chips.d.ts +7 -0
  56. package/dist/app/variable-chips.js +16 -0
  57. package/dist/app/write-failed-error.d.ts +6 -0
  58. package/dist/app/write-failed-error.js +21 -0
  59. package/dist/components/embedded-list-field.d.ts +10 -0
  60. package/dist/components/embedded-list-field.js +259 -0
  61. package/dist/components/grid-cell-for-field.d.ts +25 -0
  62. package/dist/components/grid-cell-for-field.js +31 -0
  63. package/dist/components/multi-select-checkboxes.d.ts +14 -0
  64. package/dist/components/multi-select-checkboxes.js +36 -0
  65. package/dist/components/reference-create-dialog.d.ts +13 -0
  66. package/dist/components/reference-create-dialog.js +46 -0
  67. package/dist/components/related-list-section.d.ts +25 -0
  68. package/dist/components/related-list-section.js +273 -0
  69. package/dist/components/render-edit-action-button.d.ts +22 -0
  70. package/dist/components/render-edit-action-button.js +43 -0
  71. package/dist/components/render-edit-logic.d.ts +6 -0
  72. package/dist/components/render-edit-logic.js +63 -0
  73. package/dist/components/render-edit-types.d.ts +209 -0
  74. package/dist/components/render-edit-types.js +1 -0
  75. package/dist/components/render-edit.d.ts +6 -0
  76. package/dist/components/render-edit.js +1169 -0
  77. package/dist/components/render-field.d.ts +38 -0
  78. package/dist/components/render-field.js +544 -0
  79. package/dist/components/render-list.d.ts +117 -0
  80. package/dist/components/render-list.js +232 -0
  81. package/dist/components/write-form-section.d.ts +17 -0
  82. package/dist/components/write-form-section.js +104 -0
  83. package/dist/context/dispatcher-context.d.ts +10 -0
  84. package/{src/context/dispatcher-context.tsx → dist/context/dispatcher-context.js} +16 -29
  85. package/dist/context/draft-storage-context.d.ts +12 -0
  86. package/dist/context/draft-storage-context.js +18 -0
  87. package/dist/context/user-roles-context.d.ts +7 -0
  88. package/{src/context/user-roles-context.tsx → dist/context/user-roles-context.js} +7 -15
  89. package/dist/format-when.d.ts +1 -0
  90. package/{src/format-when.ts → dist/format-when.js} +7 -7
  91. package/dist/hooks/reference-limits.d.ts +3 -0
  92. package/{src/hooks/reference-limits.ts → dist/hooks/reference-limits.js} +0 -1
  93. package/dist/hooks/use-ai-text.d.ts +52 -0
  94. package/dist/hooks/use-ai-text.js +90 -0
  95. package/dist/hooks/use-disclosure.d.ts +7 -0
  96. package/dist/hooks/use-disclosure.js +8 -0
  97. package/dist/hooks/use-form.d.ts +7 -0
  98. package/dist/hooks/use-form.js +36 -0
  99. package/dist/hooks/use-list-url-state.d.ts +43 -0
  100. package/dist/hooks/use-list-url-state.js +131 -0
  101. package/dist/hooks/use-mutation.d.ts +9 -0
  102. package/dist/hooks/use-mutation.js +37 -0
  103. package/dist/hooks/use-query.d.ts +14 -0
  104. package/dist/hooks/use-query.js +118 -0
  105. package/dist/hooks/use-reference-lookup.d.ts +14 -0
  106. package/dist/hooks/use-reference-lookup.js +63 -0
  107. package/dist/hooks/use-store.d.ts +3 -0
  108. package/dist/hooks/use-store.js +38 -0
  109. package/dist/hooks/use-stream-handler.d.ts +14 -0
  110. package/dist/hooks/use-stream-handler.js +93 -0
  111. package/dist/i18n-defaults.d.ts +2 -0
  112. package/dist/i18n-defaults.js +201 -0
  113. package/dist/i18n.d.ts +64 -0
  114. package/dist/i18n.js +166 -0
  115. package/dist/index.d.ts +77 -0
  116. package/dist/index.js +55 -0
  117. package/dist/inside-drawer.d.ts +2 -0
  118. package/dist/inside-drawer.js +9 -0
  119. package/dist/page-header-slot.d.ts +2 -0
  120. package/dist/page-header-slot.js +9 -0
  121. package/dist/primitives.d.ts +1351 -0
  122. package/dist/primitives.js +73 -0
  123. package/dist/screen-fills-height.d.ts +3 -0
  124. package/dist/screen-fills-height.js +3 -0
  125. package/dist/share-links.d.ts +4 -0
  126. package/dist/share-links.js +11 -0
  127. package/dist/sort-by-accessor.d.ts +5 -0
  128. package/dist/sort-by-accessor.js +16 -0
  129. package/dist/sse/live-events.d.ts +23 -0
  130. package/dist/sse/live-events.js +28 -0
  131. package/dist/tokens.d.ts +83 -0
  132. package/dist/tokens.js +80 -0
  133. package/package.json +16 -9
  134. package/src/changes.json +52 -0
  135. package/src/__tests__/error-i18n-defaults.test.ts +0 -82
  136. package/src/__tests__/format-when.test.ts +0 -12
  137. package/src/__tests__/i18n.test.tsx +0 -283
  138. package/src/__tests__/merge-search-params-into-initial.test.ts +0 -525
  139. package/src/__tests__/qn.test.ts +0 -83
  140. package/src/__tests__/share-links.test.ts +0 -26
  141. package/src/__tests__/sort-by-accessor.test.ts +0 -48
  142. package/src/__tests__/stringify-nav-params.test.ts +0 -33
  143. package/src/__tests__/use-list-url-state.test.tsx +0 -250
  144. package/src/app/__tests__/action-form-field-labels.test.tsx +0 -92
  145. package/src/app/__tests__/action-form-shim.test.ts +0 -137
  146. package/src/app/__tests__/build-list-query-payload.test.ts +0 -107
  147. package/src/app/__tests__/config-edit-shim.test.ts +0 -57
  148. package/src/app/__tests__/content-editors.test.tsx +0 -128
  149. package/src/app/__tests__/content-preview.test.tsx +0 -184
  150. package/src/app/__tests__/entity-edit-create-search-params.test.tsx +0 -236
  151. package/src/app/__tests__/entity-edit-jsonb-embedded-submit.test.tsx +0 -239
  152. package/src/app/__tests__/entity-list-create-singleton.test.tsx +0 -128
  153. package/src/app/__tests__/entity-list-row-action-entity-target.test.tsx +0 -424
  154. package/src/app/__tests__/entity-list-row-action-icon.test.tsx +0 -248
  155. package/src/app/__tests__/entity-list-row-action-kumiko-actions-view.test.tsx +0 -162
  156. package/src/app/__tests__/entity-list-row-action-refetch.test.tsx +0 -401
  157. package/src/app/__tests__/entity-list-screen-padding.test.tsx +0 -111
  158. package/src/app/__tests__/feature-schema.test.ts +0 -40
  159. package/src/app/__tests__/form-schema.test.ts +0 -247
  160. package/src/app/__tests__/list-daterange-facet.test.ts +0 -165
  161. package/src/app/__tests__/list-filter-facets.test.tsx +0 -521
  162. package/src/app/__tests__/nav.test.ts +0 -198
  163. package/src/app/__tests__/projection-detail-actions.test.tsx +0 -719
  164. package/src/app/__tests__/projection-detail-reference-field.test.tsx +0 -165
  165. package/src/app/__tests__/projection-detail-shim.test.ts +0 -188
  166. package/src/app/__tests__/projection-detail-singleton.test.tsx +0 -191
  167. package/src/app/__tests__/projection-list-bad-shape.test.tsx +0 -152
  168. package/src/app/__tests__/projection-list-reference-column.test.tsx +0 -219
  169. package/src/app/__tests__/projection-list-search-sort.test.tsx +0 -212
  170. package/src/app/__tests__/qualified-names.test.ts +0 -37
  171. package/src/app/__tests__/return-to.test.ts +0 -396
  172. package/src/app/__tests__/row-actions-navigate-tab.test.ts +0 -223
  173. package/src/app/__tests__/row-default-edit-action.test.tsx +0 -324
  174. package/src/app/__tests__/screen-access-allows.test.ts +0 -34
  175. package/src/app/__tests__/secret-mint-body.test.tsx +0 -404
  176. package/src/app/__tests__/variable-chips.test.tsx +0 -70
  177. package/src/app/app-features-context.tsx +0 -25
  178. package/src/app/column-renderers.tsx +0 -68
  179. package/src/app/content-editors.tsx +0 -85
  180. package/src/app/content-preview.tsx +0 -67
  181. package/src/app/custom-screens.tsx +0 -29
  182. package/src/app/dashboard-body.tsx +0 -33
  183. package/src/app/extension-form-submit.tsx +0 -123
  184. package/src/app/extension-sections.tsx +0 -111
  185. package/src/app/form-schema.ts +0 -91
  186. package/src/app/kumiko-screen.tsx +0 -3614
  187. package/src/app/layout-fields.ts +0 -31
  188. package/src/app/list-facets.ts +0 -259
  189. package/src/app/nav.tsx +0 -258
  190. package/src/app/projection-detail-shim.ts +0 -85
  191. package/src/app/reference-facet-bridge.tsx +0 -58
  192. package/src/app/return-to.tsx +0 -234
  193. package/src/app/row-actions.ts +0 -639
  194. package/src/app/screen-access.ts +0 -63
  195. package/src/app/secret-mint-body.tsx +0 -259
  196. package/src/app/secrets-edit-body.tsx +0 -194
  197. package/src/app/use-embedded-screen.ts +0 -27
  198. package/src/app/variable-chips.tsx +0 -39
  199. package/src/app/write-failed-error.ts +0 -27
  200. package/src/components/__tests__/embedded-list-field.test.tsx +0 -531
  201. package/src/components/__tests__/multi-select-checkboxes.test.tsx +0 -162
  202. package/src/components/__tests__/related-list-section-empty-state.test.tsx +0 -143
  203. package/src/components/__tests__/related-list-section.test.tsx +0 -1669
  204. package/src/components/__tests__/render-edit-action-button.test.tsx +0 -205
  205. package/src/components/__tests__/render-edit-hidden-section.test.tsx +0 -128
  206. package/src/components/__tests__/render-edit-logic.test.ts +0 -314
  207. package/src/components/__tests__/render-edit-screen-description.test.tsx +0 -152
  208. package/src/components/__tests__/render-edit-submit-actions.test.tsx +0 -460
  209. package/src/components/__tests__/render-field-app-locale.test.tsx +0 -142
  210. package/src/components/__tests__/render-field-enum-option.test.tsx +0 -99
  211. package/src/components/__tests__/render-field-icon-derivation.test.tsx +0 -119
  212. package/src/components/__tests__/render-field-json-format.test.tsx +0 -164
  213. package/src/components/__tests__/render-field-money-embedded-list-consistency.test.tsx +0 -212
  214. package/src/components/__tests__/render-field-money-roundtrip.test.tsx +0 -217
  215. package/src/components/__tests__/render-field-number-grouping.test.tsx +0 -76
  216. package/src/components/__tests__/render-field-number-unit.test.tsx +0 -227
  217. package/src/components/__tests__/render-field-password-format.test.tsx +0 -101
  218. package/src/components/__tests__/render-field-select-display.test.tsx +0 -134
  219. package/src/components/__tests__/render-field-unit-format.test.tsx +0 -107
  220. package/src/components/__tests__/render-field-unsupported-types.test.tsx +0 -195
  221. package/src/components/__tests__/render-list-toolbar-icon-only.test.tsx +0 -146
  222. package/src/components/__tests__/write-form-section.test.tsx +0 -375
  223. package/src/components/embedded-list-field.tsx +0 -354
  224. package/src/components/grid-cell-for-field.tsx +0 -60
  225. package/src/components/multi-select-checkboxes.tsx +0 -93
  226. package/src/components/reference-create-dialog.tsx +0 -91
  227. package/src/components/related-list-section.tsx +0 -467
  228. package/src/components/render-edit-action-button.tsx +0 -106
  229. package/src/components/render-edit-logic.ts +0 -85
  230. package/src/components/render-edit-types.ts +0 -216
  231. package/src/components/render-edit.tsx +0 -1700
  232. package/src/components/render-field.tsx +0 -942
  233. package/src/components/render-list.tsx +0 -561
  234. package/src/components/write-form-section.tsx +0 -199
  235. package/src/context/draft-storage-context.tsx +0 -39
  236. package/src/hooks/__tests__/use-ai-text.test.tsx +0 -177
  237. package/src/hooks/__tests__/use-disclosure.test.tsx +0 -25
  238. package/src/hooks/__tests__/use-form.test.tsx +0 -65
  239. package/src/hooks/__tests__/use-mutation.test.tsx +0 -77
  240. package/src/hooks/__tests__/use-stream-handler.test.tsx +0 -107
  241. package/src/hooks/use-ai-text.ts +0 -172
  242. package/src/hooks/use-disclosure.ts +0 -20
  243. package/src/hooks/use-form.ts +0 -78
  244. package/src/hooks/use-list-url-state.ts +0 -171
  245. package/src/hooks/use-mutation.ts +0 -61
  246. package/src/hooks/use-query.ts +0 -132
  247. package/src/hooks/use-reference-lookup.ts +0 -76
  248. package/src/hooks/use-store.ts +0 -48
  249. package/src/hooks/use-stream-handler.ts +0 -134
  250. package/src/i18n-defaults.ts +0 -220
  251. package/src/i18n.tsx +0 -286
  252. package/src/index.ts +0 -272
  253. package/src/primitives.tsx +0 -1446
  254. package/src/share-links.ts +0 -12
  255. package/src/sort-by-accessor.ts +0 -20
  256. package/src/sse/live-events.tsx +0 -56
  257. package/src/tokens.tsx +0 -170
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, } from "react";
3
+ /** Structural marker a top-level `actions` node can carry so DefaultForm's
4
+ * sticky-footer split (fw#1918) treats it as the pinned primary action even
5
+ * though it isn't a `Button` with `type="submit"` — e.g. RenderEdit's
6
+ * footer-slot mount, whose actual rendered button is opaque app code the
7
+ * split can't inspect (see `ScreenSlots.footerPrimary`). */
8
+ export const STICKY_PRIMARY_ACTION_PROP = "kumikoStickyPrimaryAction";
9
+ /** Teil-C action-icon collapse rule: a group of more than two actions where
10
+ * every member carries an icon renders icon-only instead of wall-to-wall
11
+ * text buttons; any icon-less member keeps the whole group on text so it
12
+ * doesn't fall apart visually mid-group. */
13
+ export function shouldRenderActionsIconOnly(actions) {
14
+ return actions.length > 2 && actions.every((a) => a.icon !== undefined);
15
+ }
16
+ // Status value -> StatusTone heuristic, shared by every surface that shows a
17
+ // raw status value without a tone of its own (projectionDetail header badge,
18
+ // list cells). Covers the common status vocabularies; unknown values stay
19
+ // undefined so the caller keeps its own neutral default.
20
+ const STATUS_TONE_BY_VALUE = {
21
+ ok: "ok",
22
+ active: "ok",
23
+ done: "ok",
24
+ complete: "ok",
25
+ completed: "ok",
26
+ paid: "ok",
27
+ approved: "ok",
28
+ published: "ok",
29
+ success: "ok",
30
+ pending: "warn",
31
+ processing: "warn",
32
+ review: "warn",
33
+ "in-review": "warn",
34
+ waiting: "warn",
35
+ open: "warn",
36
+ draft: "warn",
37
+ failed: "bad",
38
+ error: "bad",
39
+ overdue: "bad",
40
+ rejected: "bad",
41
+ blocked: "bad",
42
+ critical: "bad",
43
+ };
44
+ const STATUS_TONE_BY_OPTION_TONE = {
45
+ ok: "ok",
46
+ warn: "warn",
47
+ bad: "bad",
48
+ neutral: "muted",
49
+ };
50
+ export function statusToneForOptionTone(tone) {
51
+ return STATUS_TONE_BY_OPTION_TONE[tone];
52
+ }
53
+ export function statusToneForValue(value) {
54
+ const slug = value
55
+ .trim()
56
+ .toLowerCase()
57
+ .replace(/[\s_]+/g, "-");
58
+ // Own-key check: the value comes from row data, and a plain object lookup
59
+ // would hand back `Object.prototype.constructor` & friends as a "tone".
60
+ return Object.hasOwn(STATUS_TONE_BY_VALUE, slug) ? STATUS_TONE_BY_VALUE[slug] : undefined;
61
+ }
62
+ // ---- Context + Provider + Hook ----
63
+ const PrimitivesContext = createContext(undefined);
64
+ export function PrimitivesProvider({ children, value }) {
65
+ return _jsx(PrimitivesContext.Provider, { value: value, children: children });
66
+ }
67
+ export function usePrimitives() {
68
+ const registry = useContext(PrimitivesContext);
69
+ if (registry === undefined) {
70
+ throw new Error("usePrimitives: no <PrimitivesProvider> mounted. Wrap your app in one (createKumikoApp does this for you with defaultPrimitives from @cosmicdrift/kumiko-renderer-web).");
71
+ }
72
+ return registry;
73
+ }
@@ -0,0 +1,3 @@
1
+ export declare function screenFillsHeight(screen: {
2
+ readonly fillHeight?: boolean;
3
+ }): boolean;
@@ -0,0 +1,3 @@
1
+ export function screenFillsHeight(screen) {
2
+ return screen.fillHeight !== false;
3
+ }
@@ -0,0 +1,4 @@
1
+ /** wa.me link that opens WhatsApp with `text` prefilled — addressed to
2
+ * `phone` when given (any formatting; international form required, a
3
+ * leading "+" or "00" is accepted), otherwise WhatsApp's contact picker. */
4
+ export declare function buildWhatsAppShareUrl(text: string, phone?: string): string;
@@ -0,0 +1,11 @@
1
+ /** wa.me link that opens WhatsApp with `text` prefilled — addressed to
2
+ * `phone` when given (any formatting; international form required, a
3
+ * leading "+" or "00" is accepted), otherwise WhatsApp's contact picker. */
4
+ export function buildWhatsAppShareUrl(text, phone) {
5
+ const recipient = phone === undefined ? "" : whatsAppRecipientDigits(phone);
6
+ return `https://wa.me/${recipient}?text=${encodeURIComponent(text)}`;
7
+ }
8
+ function whatsAppRecipientDigits(phone) {
9
+ const digits = phone.replace(/\D/g, "");
10
+ return digits.startsWith("00") ? digits.slice(2) : digits;
11
+ }
@@ -0,0 +1,5 @@
1
+ import type { DataTableSort } from "./primitives.js";
2
+ /** Sorts `rows` by a `DataTableSort` against a field->accessor map. Unknown
3
+ * field or `sort === null` returns `rows` unchanged (no-op, not an error —
4
+ * callers pass whatever the DataTable reports). */
5
+ export declare function sortByAccessor<TRow>(rows: readonly TRow[], sort: DataTableSort | null, accessors: Readonly<Record<string, (row: TRow) => string | number>>): readonly TRow[];
@@ -0,0 +1,16 @@
1
+ /** Sorts `rows` by a `DataTableSort` against a field->accessor map. Unknown
2
+ * field or `sort === null` returns `rows` unchanged (no-op, not an error —
3
+ * callers pass whatever the DataTable reports). */
4
+ export function sortByAccessor(rows, sort, accessors) {
5
+ if (sort === null)
6
+ return rows;
7
+ const accessor = accessors[sort.field];
8
+ if (accessor === undefined)
9
+ return rows;
10
+ const factor = sort.dir === "asc" ? 1 : -1;
11
+ return [...rows].sort((a, b) => {
12
+ const av = accessor(a);
13
+ const bv = accessor(b);
14
+ return av < bv ? -factor : av > bv ? factor : 0;
15
+ });
16
+ }
@@ -0,0 +1,23 @@
1
+ import { type ReactNode } from "react";
2
+ export type LiveEvent = {
3
+ readonly type: string;
4
+ readonly data: {
5
+ readonly id: string;
6
+ readonly aggregateType: string;
7
+ readonly version: number;
8
+ readonly payload: unknown;
9
+ readonly createdAt: string;
10
+ };
11
+ };
12
+ /** Abonniert Live-Events für eine Entity (aggregateType). Returnt die
13
+ * Unsubscribe-Funktion. Mehrere Subscriptions parallel auf dieselbe
14
+ * Entity sind erlaubt — jede bekommt ihr eigenes Event. */
15
+ export type LiveEventSubscriber = (entityName: string, listener: (event: LiveEvent) => void) => () => void;
16
+ export type LiveEventsProviderProps = {
17
+ readonly children: ReactNode;
18
+ readonly value: LiveEventSubscriber;
19
+ };
20
+ export declare function LiveEventsProvider({ children, value }: LiveEventsProviderProps): ReactNode;
21
+ /** Hook für useQuery-like Consumer. Liefert die Subscribe-Function
22
+ * aus dem Context. Wenn kein Provider da ist, ist's ein No-op. */
23
+ export declare function useLiveEvents(): LiveEventSubscriber;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Live-Events-Contract, plattform-neutral. Die Plattform (Web mit
3
+ // EventSource, Native mit polyfill) liefert einen `LiveEventSubscriber`
4
+ // via `<LiveEventsProvider value={...}>`; der shared-Layer konsumiert
5
+ // nur das Interface.
6
+ //
7
+ // Warum ein Context statt Module-Singleton wie vor dem Split: ein
8
+ // Module-Singleton koppelt an eine globale Verbindung. Mit Context
9
+ // kann der Caller pro Tree eine andere Subscribe-Quelle durchreichen
10
+ // — nützlich in Tests (Fake-Feed), bei Multi-Tenant-Bridges, und
11
+ // beim Native-Renderer wo die Verbindungs-Lifecycle oft nicht dem
12
+ // App-Lifecycle entspricht.
13
+ import { createContext, useContext } from "react";
14
+ // Wenn kein Provider da ist, liefern wir einen No-op-Subscriber statt
15
+ // zu crashen. Grund: useQuery({ live: true }) kann optimistisch
16
+ // ausgehakt werden ohne den ganzen Baum abzureißen, wenn die Plattform
17
+ // z.B. SSE temporär deaktiviert hat. Fehler-Signale würden stumm
18
+ // geschluckt — das ist explizit dokumentiert.
19
+ const noopSubscriber = () => () => { };
20
+ const LiveEventsContext = createContext(noopSubscriber);
21
+ export function LiveEventsProvider({ children, value }) {
22
+ return _jsx(LiveEventsContext.Provider, { value: value, children: children });
23
+ }
24
+ /** Hook für useQuery-like Consumer. Liefert die Subscribe-Function
25
+ * aus dem Context. Wenn kein Provider da ist, ist's ein No-op. */
26
+ export function useLiveEvents() {
27
+ return useContext(LiveEventsContext);
28
+ }
@@ -0,0 +1,83 @@
1
+ import { type ReactNode } from "react";
2
+ export type ColorTokens = {
3
+ readonly background: string;
4
+ readonly foreground: string;
5
+ readonly card: string;
6
+ readonly cardForeground: string;
7
+ readonly popover: string;
8
+ readonly popoverForeground: string;
9
+ readonly primary: string;
10
+ readonly primaryForeground: string;
11
+ readonly secondary: string;
12
+ readonly secondaryForeground: string;
13
+ readonly muted: string;
14
+ readonly mutedForeground: string;
15
+ readonly accent: string;
16
+ readonly accentForeground: string;
17
+ readonly destructive: string;
18
+ readonly destructiveForeground: string;
19
+ readonly border: string;
20
+ readonly input: string;
21
+ readonly ring: string;
22
+ };
23
+ export type RadiusTokens = {
24
+ readonly sm: string;
25
+ readonly md: string;
26
+ readonly lg: string;
27
+ readonly xl: string;
28
+ };
29
+ export type FontTokens = {
30
+ readonly sans: string;
31
+ readonly heading: string;
32
+ readonly mono: string;
33
+ };
34
+ export type ShadowTokens = {
35
+ readonly card: string;
36
+ };
37
+ export type SpacingTokens = {
38
+ readonly card: string;
39
+ };
40
+ export type CoreTokens = {
41
+ readonly color: ColorTokens;
42
+ readonly radius: RadiusTokens;
43
+ readonly font: FontTokens;
44
+ readonly shadow: ShadowTokens;
45
+ readonly spacing: SpacingTokens;
46
+ };
47
+ /** Erweiterung für App-eigene Token-Kategorien. Devs augmentieren:
48
+ *
49
+ * declare module "@cosmicdrift/kumiko-renderer" {
50
+ * interface AppTokens {
51
+ * chart: { gridline: string; line: string };
52
+ * }
53
+ * }
54
+ *
55
+ * Und reichen im App-CSS die passenden `--chart-*` Variablen rein.
56
+ * Kumikos Default-Primitives nutzen nur CoreTokens. */
57
+ export interface AppTokens {
58
+ }
59
+ export type Tokens = CoreTokens & AppTokens;
60
+ export type ThemeMode = "light" | "dark";
61
+ export type TokensApi = {
62
+ readonly tokens: Tokens;
63
+ readonly mode: ThemeMode;
64
+ readonly setMode: (mode: ThemeMode) => void;
65
+ readonly toggleMode: () => void;
66
+ };
67
+ export type TokensProviderProps = {
68
+ readonly children: ReactNode;
69
+ readonly value: TokensApi;
70
+ };
71
+ export declare function TokensProvider({ children, value }: TokensProviderProps): ReactNode;
72
+ /** Read-only Tokens-Hook. Liefert CSS-var-Strings — nutzbar in
73
+ * style-Props oder Tailwind-arbitrary-values wie
74
+ * `bg-[var(--color-primary)]` (selten, die shadcn-Tailwind-Klassen
75
+ * `bg-primary` sind idiomatischer). */
76
+ export declare function useTokens(): Tokens;
77
+ /** Volle API inkl. Theme-Toggle. Für den Dark/Light-Switch-Button. */
78
+ export declare function useTokenController(): TokensApi;
79
+ /** Tokens mit var-string-Werten. Konstant — die "Werte" sind
80
+ * Referenzen, die echten Farben ändern sich per CSS ohne dass das
81
+ * Objekt mutiert werden muss. Plattform-Packages (renderer-web,
82
+ * -native) importieren das in ihre TokensApi-Impl. */
83
+ export declare const cssVarTokens: Tokens;
package/dist/tokens.js ADDED
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Design-Tokens im shadcn-Namensschema. Werte sind CSS-Variable-
3
+ // Referenzen (`var(--color-primary)`) — die echten Zahlen leben in
4
+ // styles.css (Web) bzw. in einer StyleSheet.create-Config (Native,
5
+ // später). Der Hook ist vor allem für Custom-Components die mal
6
+ // einen Token-Wert in eine JS-style-prop schieben müssen; die
7
+ // Default-Primitives nutzen direkt Tailwind-Klassen und brauchen
8
+ // ihn nicht.
9
+ //
10
+ // Theme-Toggle läuft über `.dark`-Class auf `<html>` — kein JS-State
11
+ // nötig, CSS rendert sofort neu. `useTokenController().toggleTheme()`
12
+ // ist nur ein DOM-Wrapper, kein eigener React-State.
13
+ //
14
+ // Erweiterbar: AppTokens-Interface wie bei Primitives. Dev kann
15
+ // eigene Token-Kategorien deklarieren und mit var-Strings befüllen.
16
+ import { createContext, useContext } from "react";
17
+ // ---- Context + Provider + Hooks ----
18
+ const TokensContext = createContext(undefined);
19
+ export function TokensProvider({ children, value }) {
20
+ return _jsx(TokensContext.Provider, { value: value, children: children });
21
+ }
22
+ /** Read-only Tokens-Hook. Liefert CSS-var-Strings — nutzbar in
23
+ * style-Props oder Tailwind-arbitrary-values wie
24
+ * `bg-[var(--color-primary)]` (selten, die shadcn-Tailwind-Klassen
25
+ * `bg-primary` sind idiomatischer). */
26
+ export function useTokens() {
27
+ return useTokenController().tokens;
28
+ }
29
+ /** Volle API inkl. Theme-Toggle. Für den Dark/Light-Switch-Button. */
30
+ export function useTokenController() {
31
+ const api = useContext(TokensContext);
32
+ if (api === undefined) {
33
+ throw new Error("useTokens/useTokenController: no <TokensProvider> mounted. createKumikoApp verdrahtet den Provider automatisch mit @cosmicdrift/kumiko-renderer-web defaults.");
34
+ }
35
+ return api;
36
+ }
37
+ /** Tokens mit var-string-Werten. Konstant — die "Werte" sind
38
+ * Referenzen, die echten Farben ändern sich per CSS ohne dass das
39
+ * Objekt mutiert werden muss. Plattform-Packages (renderer-web,
40
+ * -native) importieren das in ihre TokensApi-Impl. */
41
+ export const cssVarTokens = {
42
+ color: {
43
+ background: "var(--color-background)",
44
+ foreground: "var(--color-foreground)",
45
+ card: "var(--color-card)",
46
+ cardForeground: "var(--color-card-foreground)",
47
+ popover: "var(--color-popover)",
48
+ popoverForeground: "var(--color-popover-foreground)",
49
+ primary: "var(--color-primary)",
50
+ primaryForeground: "var(--color-primary-foreground)",
51
+ secondary: "var(--color-secondary)",
52
+ secondaryForeground: "var(--color-secondary-foreground)",
53
+ muted: "var(--color-muted)",
54
+ mutedForeground: "var(--color-muted-foreground)",
55
+ accent: "var(--color-accent)",
56
+ accentForeground: "var(--color-accent-foreground)",
57
+ destructive: "var(--color-destructive)",
58
+ destructiveForeground: "var(--color-destructive-foreground)",
59
+ border: "var(--color-border)",
60
+ input: "var(--color-input)",
61
+ ring: "var(--color-ring)",
62
+ },
63
+ radius: {
64
+ sm: "var(--radius-sm)",
65
+ md: "var(--radius-md)",
66
+ lg: "var(--radius-lg)",
67
+ xl: "var(--radius-xl)",
68
+ },
69
+ font: {
70
+ sans: "var(--font-sans)",
71
+ heading: "var(--font-heading)",
72
+ mono: "var(--font-mono)",
73
+ },
74
+ shadow: {
75
+ card: "var(--card-shadow)",
76
+ },
77
+ spacing: {
78
+ card: "var(--card-padding)",
79
+ },
80
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer",
3
- "version": "0.327.0",
3
+ "version": "0.328.1",
4
4
  "description": "Platform-agnostic React renderer for Kumiko screens. Contains the shared logic — primitives-contract, hooks, KumikoScreen, navigation & SSE abstractions — that any platform-specific renderer (web, native) composes. No DOM, no EventSource, no react-dom.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -10,14 +10,14 @@
10
10
  },
11
11
  "exports": {
12
12
  ".": {
13
- "types": "./src/index.ts",
14
- "default": "./src/index.ts"
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@cosmicdrift/kumiko-framework": "0.327.0",
19
- "@cosmicdrift/kumiko-headless": "0.327.0",
20
- "@cosmicdrift/kumiko-types": "0.327.0",
18
+ "@cosmicdrift/kumiko-framework": "0.328.1",
19
+ "@cosmicdrift/kumiko-headless": "0.328.1",
20
+ "@cosmicdrift/kumiko-types": "0.328.1",
21
21
  "react": "^19.2.6",
22
22
  "temporal-polyfill": "^0.3.2",
23
23
  "zod": "^4.4.3"
@@ -28,7 +28,7 @@
28
28
  "@types/react-dom": "^19.2.3",
29
29
  "jsdom": "^29.1.1",
30
30
  "react-dom": "^19.2.6",
31
- "@cosmicdrift/kumiko-locale-de": "0.327.0"
31
+ "@cosmicdrift/kumiko-locale-de": "0.328.1"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
@@ -41,10 +41,17 @@
41
41
  },
42
42
  "publishConfig": {
43
43
  "registry": "https://registry.npmjs.org",
44
- "access": "public"
44
+ "access": "public",
45
+ "exports": {
46
+ ".": {
47
+ "types": "./dist/index.d.ts",
48
+ "default": "./dist/index.js"
49
+ }
50
+ }
45
51
  },
46
52
  "files": [
47
- "src",
53
+ "dist",
54
+ "src/changes.json",
48
55
  "README.md",
49
56
  "LICENSE"
50
57
  ]
package/src/changes.json CHANGED
@@ -1,4 +1,56 @@
1
1
  [
2
+ {
3
+ "version": "0.328.0",
4
+ "type": "breaking",
5
+ "title": "Screens fill the shell height by default",
6
+ "detail": "entityList, projectionList, projectionDetail, entityEdit, actionForm and wizard screens now fill the height of the shell content. The table or the form body scrolls inside, the toolbar stays on top and the pager or action bar stays pinned at the bottom (`scrollBody`, `stickyActions`). The shell header is 56px. Custom screens and direct DataTable or Form usage are unchanged.",
7
+ "migration": "Set `fillHeight: false` on a screen to restore the old page scrolling (table grows with its rows, pager below the last row, form footer in the flow). Apps with e2e or screenshot tests that scroll the page to reach the pager or the form footer need to adjust them or opt out per screen."
8
+ },
9
+ {
10
+ "version": "0.328.0",
11
+ "type": "breaking",
12
+ "title": "Pager shows a status line, page size select and Page X of Y instead of page numbers",
13
+ "detail": "The numbered page list is gone. The list footer shows \"1-19 of 19 <entities>\" (with the entity plural label when known), \"Page X of Y\", previous and next buttons and a page size select (25, 50, 100). Lists without a pager show a plain entry count. The chosen size is kept in the URL under `<screen>.size`.",
14
+ "migration": "Remove code and tests that click numbered page buttons (`-page-N` test ids); use the next and previous buttons. The screen's declared `pageSize` stays the default, `<screen>.size` in the URL overrides it. The new keys `kumiko.pager.pageOf`, `kumiko.pager.status.noun`, `kumiko.pager.pageSize`, `kumiko.pager.pageSizeLabel`, `kumiko.list.count.*` exist in en, de and es; apps with their own locale need them too."
15
+ },
16
+ {
17
+ "version": "0.328.0",
18
+ "type": "breaking",
19
+ "title": "rowActionMode adaptive has new semantics and mobile rows lose the kebab for plain navigation",
20
+ "detail": "With `rowActionMode: \"adaptive\"` (the default), a table with `onRowClick` renders the first cell as a keyboard-operable link and puts all row actions into the kebab. Without `onRowClick` the primary row action is a link-button and the remaining actions go into the kebab (a single action gets no kebab). Below the md breakpoint a row that only navigates shows a chevron and no kebab.",
21
+ "migration": "Code or tests that expect up to two inline row action buttons must open the kebab or use `rowActionMode: \"inline\"`, which is unchanged. To keep a visible action next to the row link, drop `onRowClick` and declare the action as the primary row action."
22
+ },
23
+ {
24
+ "version": "0.328.0",
25
+ "type": "breaking",
26
+ "title": "Form, wizard and detail screens use the board layout",
27
+ "detail": "Screen forms have no card, sections are separated by lines, fields flow in rows with widths by type (text 240px, number 96px, money 160px, date and select 200px, textarea full row) and the page title lives in the shell header. Delete and Copy link move into the header kebab, the footer reads status, Cancel, Save, and the wizard footer has no Cancel. Wizard steps sit in a vertical rail from the lg breakpoint. The first detail header action is a button, further ones sit in the kebab. Detail screens have no header card and use the full width: metrics render as a dl band, tabs are underlined only when active, related lists are full-bleed with a count footer. Required-field asterisks are omitted when every field is required and \"All fields are required.\" is shown instead.",
28
+ "migration": "Adjust screenshots and DOM tests that pin the old card layout. `fillHeight: false` restores page scrolling only; the footer order, the moved actions and the new field layout apply in both modes. Empty list cells render a dash."
29
+ },
30
+ {
31
+ "version": "0.328.0",
32
+ "type": "improvement",
33
+ "title": "PageHeader slot, MetricBand, wizard Save and close, drawer row actions with unsaved-input guard",
34
+ "detail": "Core primitives `PageHeader` (status and actions portalled into the shell header when a shell provides the slot, otherwise the caller keeps its placement) and `MetricBand` (borderless dl of metrics) are new and optional. Wizards on an existing record show \"Save and close\". Row actions and related list row actions with `kind: \"drawer\"` open the action form in a flush drawer. Closing it with unsaved input (X, Escape, overlay, Cancel) asks for confirmation first; nothing is asked after a successful submit. Row action drawers are prefilled from the row through `params`. The action form `summary` renders a context box above the fields."
35
+ },
36
+ {
37
+ "version": "0.328.0",
38
+ "type": "fix",
39
+ "title": "Related list no longer claims \"showing the first N entries\" for a complete list",
40
+ "detail": "The hint \"Zeigt die ersten 1 Einträge. Es gibt weitere, die hier nicht angezeigt werden.\" appeared for a single row even when nothing else existed. It now appears only when more rows exist than were loaded."
41
+ },
42
+ {
43
+ "version": "0.328.0",
44
+ "type": "improvement",
45
+ "title": "writeForm section Save button without icon, parity with entityEdit",
46
+ "detail": "The Save button of a writeForm section no longer shows a check icon; it matches the entityEdit submit button. Tests that look for the icon inside the section Save button need updating."
47
+ },
48
+ {
49
+ "version": "0.328.0",
50
+ "type": "improvement",
51
+ "title": "useQuery gains a refetchIntervalMs option for polling",
52
+ "detail": "With `refetchIntervalMs` set, an enabled query re-runs on a timer. Background ticks keep `loading` false, are skipped while a fetch is still in flight, and stop on unmount or when enabled, the interval, type or payload change. Only timers are used, so it works on web and React Native."
53
+ },
2
54
  {
3
55
  "version": "0.325.2",
4
56
  "type": "fix",
@@ -1,82 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import {
3
- AccessDeniedError,
4
- ConflictError,
5
- FeatureDisabledError,
6
- InternalError,
7
- NotFoundError,
8
- RateLimitError,
9
- UnconfiguredError,
10
- UniqueViolationError,
11
- UnprocessableError,
12
- ValidationError,
13
- VersionConflictError,
14
- } from "@cosmicdrift/kumiko-framework/errors";
15
- import { localeDeBundle } from "@cosmicdrift/kumiko-locale-de";
16
- import { REQUIRED_FIELD_I18N_KEY } from "../app/form-schema";
17
- import { kumikoDefaultTranslations } from "../i18n-defaults";
18
-
19
- // Keys are read off LIVE error instances, not a hardcoded list — a copied list
20
- // would mirror the bundle and never catch a renamed i18nKey or a new error
21
- // class shipped without a default translation. Ship an 11th error class (or
22
- // rename a key) without adding the default → this fails.
23
- const errorInstances = [
24
- new InternalError(),
25
- new AccessDeniedError(),
26
- new NotFoundError("Thing"),
27
- new ConflictError(),
28
- new VersionConflictError({ entityId: 1, expectedVersion: 1, currentVersion: 2 }),
29
- new UniqueViolationError({ entityName: "Thing" }),
30
- new UnprocessableError("reason"),
31
- new UnconfiguredError({ feature: "f", key: "k" }),
32
- new FeatureDisabledError("f", "h"),
33
- new RateLimitError({
34
- bucket: "b",
35
- limit: 1,
36
- windowSeconds: 1,
37
- remaining: 0,
38
- retryAfterSeconds: 1,
39
- resetAt: "1970-01-01T00:00:00Z",
40
- }),
41
- new ValidationError({ fields: [] }),
42
- ];
43
-
44
- const de = localeDeBundle;
45
- const en = kumikoDefaultTranslations["en"];
46
-
47
- describe("kumikoDefaultTranslations covers every error i18nKey", () => {
48
- for (const e of errorInstances) {
49
- test(`${e.code} → ${e.i18nKey} has en default and de locale-package copy`, () => {
50
- expect(de[e.i18nKey]).toBeTruthy();
51
- expect(en?.[e.i18nKey]).toBeTruthy();
52
- });
53
- }
54
-
55
- // Client-emitted (renderer-web download helper) — not thrown by a class, but
56
- // rendered through the same last-resort bundle. Gap #5 named it explicitly.
57
- test("errors.download.urlMissing has de+en default", () => {
58
- expect(de?.["errors.download.urlMissing"]).toBeTruthy();
59
- expect(en?.["errors.download.urlMissing"]).toBeTruthy();
60
- });
61
-
62
- // Client-emitted (dispatcher-live error-mapping) — network/abort never
63
- // reach the server, so no error class mints these; still rendered through
64
- // this last-resort bundle whenever a feature reads res.error.i18nKey raw.
65
- test("dispatcher.errors.network has de+en default", () => {
66
- expect(de?.["dispatcher.errors.network"]).toBeTruthy();
67
- expect(en?.["dispatcher.errors.network"]).toBeTruthy();
68
- });
69
-
70
- test("dispatcher.errors.aborted has de+en default", () => {
71
- expect(de?.["dispatcher.errors.aborted"]).toBeTruthy();
72
- expect(en?.["dispatcher.errors.aborted"]).toBeTruthy();
73
- });
74
-
75
- // Client-emitted (form-schema.ts required-field presence check) — not
76
- // thrown by an error class, applied as a params.i18nKey override on a zod
77
- // issue instead; still rendered through this last-resort bundle.
78
- test("form-schema's required-field i18nKey has de+en default", () => {
79
- expect(de[REQUIRED_FIELD_I18N_KEY]).toBeTruthy();
80
- expect(en?.[REQUIRED_FIELD_I18N_KEY]).toBeTruthy();
81
- });
82
- });
@@ -1,12 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { formatWhen } from "../format-when";
3
-
4
- describe("formatWhen", () => {
5
- test("formats a parseable ISO timestamp", () => {
6
- expect(formatWhen("2024-01-01T00:00:00.000Z")).not.toBe("2024-01-01T00:00:00.000Z");
7
- });
8
-
9
- test("falls back to the raw value on an unparseable input", () => {
10
- expect(formatWhen("garbage")).toBe("garbage");
11
- });
12
- });