@cosmicdrift/kumiko-headless 0.328.0 → 0.329.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 (108) hide show
  1. package/dist/apex/css.d.ts +3 -0
  2. package/{src/apex/css.ts → dist/apex/css.js} +11 -24
  3. package/dist/apex/index.d.ts +229 -0
  4. package/dist/apex/index.js +360 -0
  5. package/dist/apex/lightbox.d.ts +4 -0
  6. package/{src/apex/lightbox.ts → dist/apex/lightbox.js} +0 -4
  7. package/dist/contracts/asset.d.ts +15 -0
  8. package/dist/contracts/asset.js +17 -0
  9. package/dist/contracts/index.d.ts +3 -0
  10. package/dist/contracts/index.js +1 -0
  11. package/dist/contracts/locale.d.ts +7 -0
  12. package/dist/contracts/locale.js +11 -0
  13. package/dist/contracts/primitives.d.ts +93 -0
  14. package/dist/contracts/primitives.js +21 -0
  15. package/dist/dispatcher/index.d.ts +2 -0
  16. package/dist/dispatcher/index.js +1 -0
  17. package/dist/dispatcher/types.d.ts +86 -0
  18. package/dist/dispatcher/types.js +34 -0
  19. package/dist/form/form-controller.d.ts +2 -0
  20. package/dist/form/form-controller.js +404 -0
  21. package/dist/form/index.d.ts +3 -0
  22. package/dist/form/index.js +2 -0
  23. package/dist/form/types.d.ts +71 -0
  24. package/dist/form/types.js +1 -0
  25. package/dist/form/zod-bridge.d.ts +5 -0
  26. package/{src/form/zod-bridge.ts → dist/form/zod-bridge.js} +52 -58
  27. package/dist/format/escape.d.ts +5 -0
  28. package/{src/format/escape.ts → dist/format/escape.js} +43 -47
  29. package/dist/format/html-template.d.ts +9 -0
  30. package/dist/format/html-template.js +39 -0
  31. package/dist/format/index.d.ts +10 -0
  32. package/dist/format/index.js +192 -0
  33. package/dist/format/money.d.ts +1 -0
  34. package/{src/format/money.ts → dist/format/money.js} +6 -5
  35. package/dist/index.d.ts +14 -0
  36. package/dist/index.js +9 -0
  37. package/dist/locale-routing/index.d.ts +39 -0
  38. package/dist/locale-routing/index.js +143 -0
  39. package/dist/nav/index.d.ts +2 -0
  40. package/dist/nav/index.js +1 -0
  41. package/dist/nav/resolve.d.ts +2 -0
  42. package/dist/nav/resolve.js +58 -0
  43. package/dist/nav/types.d.ts +26 -0
  44. package/dist/nav/types.js +1 -0
  45. package/dist/store/create-store.d.ts +2 -0
  46. package/{src/store/create-store.ts → dist/store/create-store.js} +21 -23
  47. package/dist/store/equality.d.ts +1 -0
  48. package/{src/store/equality.ts → dist/store/equality.js} +19 -17
  49. package/dist/store/index.d.ts +3 -0
  50. package/dist/store/index.js +2 -0
  51. package/dist/store/types.d.ts +7 -0
  52. package/dist/store/types.js +10 -0
  53. package/dist/view-model/edit.d.ts +10 -0
  54. package/dist/view-model/edit.js +392 -0
  55. package/dist/view-model/embedded-list.d.ts +27 -0
  56. package/dist/view-model/embedded-list.js +63 -0
  57. package/dist/view-model/index.d.ts +7 -0
  58. package/dist/view-model/index.js +3 -0
  59. package/dist/view-model/list.d.ts +16 -0
  60. package/dist/view-model/list.js +201 -0
  61. package/dist/view-model/types.d.ts +300 -0
  62. package/dist/view-model/types.js +1 -0
  63. package/package.json +31 -12
  64. package/src/apex/__tests__/lightbox.test.ts +0 -20
  65. package/src/apex/__tests__/page-head.test.ts +0 -63
  66. package/src/apex/__tests__/render.test.ts +0 -401
  67. package/src/apex/index.ts +0 -667
  68. package/src/contracts/__tests__/contracts.test.ts +0 -123
  69. package/src/contracts/__tests__/field-issue-compat.test.ts +0 -19
  70. package/src/contracts/asset.ts +0 -48
  71. package/src/contracts/index.ts +0 -23
  72. package/src/contracts/locale.ts +0 -33
  73. package/src/contracts/primitives.ts +0 -158
  74. package/src/dispatcher/__tests__/contract.test.ts +0 -163
  75. package/src/dispatcher/index.ts +0 -16
  76. package/src/dispatcher/types.ts +0 -217
  77. package/src/form/__tests__/conditional-fields.test.ts +0 -177
  78. package/src/form/__tests__/form-controller.test.ts +0 -231
  79. package/src/form/__tests__/submit.test.ts +0 -575
  80. package/src/form/__tests__/validation.test.ts +0 -274
  81. package/src/form/form-controller.ts +0 -458
  82. package/src/form/index.ts +0 -15
  83. package/src/form/types.ts +0 -297
  84. package/src/format/__tests__/escape.test.ts +0 -79
  85. package/src/format/__tests__/format.test.ts +0 -285
  86. package/src/format/__tests__/html-template.test.ts +0 -47
  87. package/src/format/html-template.ts +0 -49
  88. package/src/format/index.ts +0 -223
  89. package/src/index.ts +0 -118
  90. package/src/locale-routing/__tests__/locale-routing.test.ts +0 -276
  91. package/src/locale-routing/index.ts +0 -218
  92. package/src/nav/__tests__/resolve.test.ts +0 -279
  93. package/src/nav/index.ts +0 -8
  94. package/src/nav/resolve.ts +0 -60
  95. package/src/nav/types.ts +0 -70
  96. package/src/store/__tests__/create-store.test.ts +0 -139
  97. package/src/store/__tests__/equality.test.ts +0 -66
  98. package/src/store/index.ts +0 -3
  99. package/src/store/types.ts +0 -27
  100. package/src/view-model/__tests__/edit.test.ts +0 -1072
  101. package/src/view-model/__tests__/embedded-list.test.ts +0 -96
  102. package/src/view-model/__tests__/field-labels.test.ts +0 -62
  103. package/src/view-model/__tests__/list.test.ts +0 -439
  104. package/src/view-model/edit.ts +0 -645
  105. package/src/view-model/embedded-list.ts +0 -89
  106. package/src/view-model/index.ts +0 -43
  107. package/src/view-model/list.ts +0 -280
  108. package/src/view-model/types.ts +0 -400
@@ -1,47 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { html, RawHtml, raw } from "../html-template";
3
-
4
- const XSS = `<script>alert("1")</script>`;
5
-
6
- describe("html tagged template", () => {
7
- test("escapes string interpolations", () => {
8
- expect(html`<p>${XSS}</p>`.toString()).toBe(
9
- "<p>&lt;script&gt;alert(&quot;1&quot;)&lt;/script&gt;</p>",
10
- );
11
- });
12
-
13
- test("escapes attribute breakouts (double quotes)", () => {
14
- const href = `"><img src=x onerror=alert(1)>`;
15
- const out = html`<a href="${href}">x</a>`.toString();
16
- expect(out).not.toContain('"><img');
17
- expect(out).toContain("&quot;&gt;&lt;img");
18
- });
19
-
20
- test("raw() passes prerendered markup through unchanged", () => {
21
- expect(html`<div>${raw("<b>ok</b>")}</div>`.toString()).toBe("<div><b>ok</b></div>");
22
- });
23
-
24
- test("nested html`...` fragments are not double-escaped", () => {
25
- const item = html`<li>${"a & b"}</li>`;
26
- expect(html`<ul>${item}</ul>`.toString()).toBe("<ul><li>a &amp; b</li></ul>");
27
- });
28
-
29
- test("arrays are joined with each element escaped", () => {
30
- const items = ["<x>", "y"].map((v) => html`<li>${v}</li>`);
31
- expect(html`<ul>${items}</ul>`.toString()).toBe("<ul><li>&lt;x&gt;</li><li>y</li></ul>");
32
- });
33
-
34
- test("null and undefined render as empty string", () => {
35
- expect(html`<p>${null}${undefined}</p>`.toString()).toBe("<p></p>");
36
- });
37
-
38
- test("numbers and booleans render via String()", () => {
39
- expect(html`<td>${42}${false}</td>`.toString()).toBe("<td>42false</td>");
40
- });
41
-
42
- test("toString() makes fragments usable in plain string contexts", () => {
43
- const fragment = html`<p>${"<i>"}</p>`;
44
- expect(`${fragment}`).toBe("<p>&lt;i&gt;</p>");
45
- expect(fragment).toBeInstanceOf(RawHtml);
46
- });
47
- });
@@ -1,49 +0,0 @@
1
- // html`...` — Tagged-Template, das jede Interpolation automatisch HTML-escaped.
2
- // Macht Escaping strukturell statt per-Callsite-Konvention: vergessen ist
3
- // unmöglich, bewusst rohes HTML braucht ein explizites raw(). Der
4
- // HTML-Escape-Guard (kumiko-guards) akzeptiert html`...` als safe.
5
-
6
- import { escapeHtml } from "./escape";
7
-
8
- export class RawHtml {
9
- readonly html: string;
10
- constructor(html: string) {
11
- this.html = html;
12
- }
13
- toString(): string {
14
- return this.html;
15
- }
16
- }
17
-
18
- /** Markiert bereits escaptes/vertrauenswürdiges Markup für html`...`. */
19
- export function raw(html: string): RawHtml {
20
- return new RawHtml(html);
21
- }
22
-
23
- export type HtmlValue =
24
- | string
25
- | number
26
- | boolean
27
- | null
28
- | undefined
29
- | RawHtml
30
- | ReadonlyArray<HtmlValue>;
31
-
32
- function renderValue(value: HtmlValue): string {
33
- if (value === null || value === undefined) return "";
34
- if (value instanceof RawHtml) return value.html;
35
- if (Array.isArray(value)) return value.map(renderValue).join("");
36
- if (typeof value === "string") return escapeHtml(value);
37
- return String(value);
38
- }
39
-
40
- // Rückgabe ist RawHtml, damit Fragmente verschachtelbar sind ohne doppelt zu
41
- // escapen: `html`<div>${item}</div>`` innerhalb eines äußeren html`...`
42
- // passiert unverändert durch. toString() liefert das fertige Markup.
43
- export function html(strings: TemplateStringsArray, ...values: ReadonlyArray<HtmlValue>): RawHtml {
44
- let out = strings[0] ?? "";
45
- values.forEach((value, i) => {
46
- out += renderValue(value) + (strings[i + 1] ?? "");
47
- });
48
- return new RawHtml(out);
49
- }
@@ -1,223 +0,0 @@
1
- // Pure format utilities — no web or platform dependencies.
2
- // Shared between renderer-web, renderer-native, and server-side tests.
3
-
4
- import { Temporal } from "temporal-polyfill";
5
-
6
- function toPlainDate(raw: string): Temporal.PlainDate {
7
- try {
8
- return Temporal.PlainDate.from(raw);
9
- } catch {
10
- // "date"-typed field stored as a full instant (day-boundary timestamp) —
11
- // take the calendar date in the local zone rather than fail.
12
- return toInstant(raw).toZonedDateTimeISO(Temporal.Now.timeZoneId()).toPlainDate();
13
- }
14
- }
15
-
16
- // Temporal.Instant.from is stricter than the `new Date(raw)` this replaced:
17
- // it requires a UTC designator/offset (Z/+hh:mm). Timestamps without one
18
- // (e.g. "2026-07-18T12:00:00", still valid input to `new Date`) throw here
19
- // instead of parsing — fall back to reading them as a local wall-clock time,
20
- // same posture as toPlainDate's own fallback above.
21
- export function toInstant(raw: string): Temporal.Instant {
22
- try {
23
- return Temporal.Instant.from(raw);
24
- } catch {
25
- return Temporal.PlainDateTime.from(raw).toZonedDateTime(Temporal.Now.timeZoneId()).toInstant();
26
- }
27
- }
28
-
29
- function formatDateCell(
30
- value: unknown,
31
- type: string,
32
- opts?: {
33
- locale?: string;
34
- dateStyle?: Intl.DateTimeFormatOptions["dateStyle"];
35
- timeStyle?: Intl.DateTimeFormatOptions["timeStyle"];
36
- },
37
- ): string {
38
- try {
39
- const raw = typeof value === "string" ? value : String(value);
40
- const locale = opts?.locale;
41
- if (opts?.dateStyle || opts?.timeStyle) {
42
- if (type === "date") {
43
- // ponytail: timeStyle is ignored here on purpose — a PlainDate has no
44
- // time component to format, so a timeStyle-only "date" field falls
45
- // back to PlainDate's default dateStyle instead of rendering a time.
46
- return toPlainDate(raw).toLocaleString(locale, {
47
- dateStyle: opts.dateStyle,
48
- });
49
- }
50
- return toInstant(raw).toLocaleString(locale, {
51
- dateStyle: opts.dateStyle,
52
- timeStyle: opts.timeStyle,
53
- });
54
- }
55
- if (type === "date") {
56
- return toPlainDate(raw).toLocaleString(locale, {
57
- year: "numeric",
58
- month: "2-digit",
59
- day: "2-digit",
60
- });
61
- }
62
- return toInstant(raw).toLocaleString(locale, {
63
- year: "numeric",
64
- month: "short",
65
- day: "numeric",
66
- hour: "2-digit",
67
- minute: "2-digit",
68
- });
69
- } catch {
70
- return String(value);
71
- }
72
- }
73
-
74
- // No `locale` fallback to a browser API here (headless has none) — passing
75
- // `undefined` to Intl.NumberFormat resolves the runtime's default locale,
76
- // the same value a browser's navigator.language-based guess would produce.
77
- function formatNumberCell(value: unknown, locale?: string, grouping = true): string {
78
- if (typeof value !== "number" || !Number.isFinite(value)) return String(value);
79
- try {
80
- return new Intl.NumberFormat(locale, { useGrouping: grouping }).format(value);
81
- } catch {
82
- // Malformed BCP-47 locale tag — mirrors formatDateCell's fallback above.
83
- return String(value);
84
- }
85
- }
86
-
87
- // Two-tier unit table for `{ format: "unit" }`. Keys mirror
88
- // @cosmicdrift/kumiko-types' UnitKey (kept as a local literal union instead
89
- // of an import — headless has no build-time dependency on kumiko-types
90
- // today, and applyFormatSpec's spec param is already an untyped
91
- // Record<string, unknown>, cast per-case like every other format below).
92
- const UNIT_INTL_IDS = {
93
- km: "kilometer",
94
- m: "meter",
95
- kg: "kilogram",
96
- percent: "percent",
97
- mi: "mile",
98
- } as const;
99
-
100
- // "m2" (square meter) has no ECMA-402 sanctioned unit — Intl.NumberFormat
101
- // throws RangeError for "square-meter". Locale-format the number and append
102
- // the literal suffix instead of going through `style: "unit"`.
103
- const UNIT_SUFFIXES = { m2: "m²" } as const;
104
-
105
- function isIntlUnit(unit: string): unit is keyof typeof UNIT_INTL_IDS {
106
- return Object.hasOwn(UNIT_INTL_IDS, unit);
107
- }
108
-
109
- function isSuffixUnit(unit: string): unit is keyof typeof UNIT_SUFFIXES {
110
- return Object.hasOwn(UNIT_SUFFIXES, unit);
111
- }
112
-
113
- function formatUnitCell(
114
- value: unknown,
115
- unit: string | undefined,
116
- locale: string | undefined,
117
- unitDisplay: Intl.NumberFormatOptions["unitDisplay"],
118
- ): string {
119
- const n = typeof value === "number" ? value : Number(value);
120
- if (unit === undefined || !Number.isFinite(n)) return String(value);
121
- if (isIntlUnit(unit)) {
122
- try {
123
- return new Intl.NumberFormat(locale, {
124
- style: "unit",
125
- unit: UNIT_INTL_IDS[unit],
126
- unitDisplay,
127
- }).format(n);
128
- } catch {
129
- // Malformed BCP-47 locale tag — mirrors formatNumberCell's fallback below.
130
- return formatNumberCell(n, locale);
131
- }
132
- }
133
- if (isSuffixUnit(unit)) {
134
- return `${formatNumberCell(n, locale)} ${UNIT_SUFFIXES[unit]}`;
135
- }
136
- return String(value);
137
- }
138
-
139
- /** Exported for UnitKey parity tests against @cosmicdrift/kumiko-types/screen. */
140
- export const UNIT_FORMAT_KEYS = [
141
- ...Object.keys(UNIT_INTL_IDS),
142
- ...Object.keys(UNIT_SUFFIXES),
143
- ] as const;
144
-
145
- export { escapeHtml, escapeHtmlAttr, escapeXml, isSafeHref, stripControlChars } from "./escape";
146
- export { type HtmlValue, html, RawHtml, raw } from "./html-template";
147
- export { currencyDecimals } from "./money";
148
- export function applyFormatSpec(
149
- spec: { format: string } & Record<string, unknown>,
150
- value: unknown,
151
- translate?: (key: string) => string,
152
- ): string {
153
- const isEmpty = value === null || value === undefined || value === "";
154
- // priority renders its emptyLabel for empty values — every other format
155
- // collapses empty to "".
156
- if (isEmpty && spec.format !== "priority") return "";
157
- switch (spec.format) {
158
- case "timestamp":
159
- case "date":
160
- return formatDateCell(value, spec.format, {
161
- locale: spec["locale"] as string | undefined,
162
- dateStyle: spec["dateStyle"] as Intl.DateTimeFormatOptions["dateStyle"],
163
- timeStyle:
164
- spec.format === "timestamp"
165
- ? (spec["timeStyle"] as Intl.DateTimeFormatOptions["timeStyle"])
166
- : undefined,
167
- });
168
- case "boolean": {
169
- if (value === true) {
170
- const trueLabel = spec["trueLabel"] as string | undefined;
171
- return trueLabel === undefined ? "✓" : (translate?.(trueLabel) ?? trueLabel);
172
- }
173
- if (value === false) {
174
- const falseLabel = spec["falseLabel"] as string | undefined;
175
- return falseLabel === undefined ? "" : (translate?.(falseLabel) ?? falseLabel);
176
- }
177
- return "";
178
- }
179
- case "currency": {
180
- const sym = (spec["symbol"] as string | undefined) ?? "";
181
- return sym.length > 0 ? `${value} ${sym}` : String(value);
182
- }
183
- case "number":
184
- case "decimal":
185
- case "bigInt":
186
- return formatNumberCell(
187
- value,
188
- spec["locale"] as string | undefined,
189
- (spec["grouping"] as boolean | undefined) ?? true,
190
- );
191
- case "unit":
192
- return formatUnitCell(
193
- value,
194
- spec["unit"] as string | undefined,
195
- spec["locale"] as string | undefined,
196
- (spec["unitDisplay"] as Intl.NumberFormatOptions["unitDisplay"]) ?? "short",
197
- );
198
- case "priority": {
199
- const emptyLabel = (spec["emptyLabel"] as string | undefined) ?? "—";
200
- const prefix = (spec["prefix"] as string | undefined) ?? "";
201
- if (isEmpty || value === 0) return emptyLabel;
202
- return `${prefix}${value}`;
203
- }
204
- case "enumOption": {
205
- const raw = typeof value === "string" ? value : String(value);
206
- const keyPrefix = spec["keyPrefix"] as string | undefined;
207
- if (keyPrefix === undefined || translate === undefined) return raw;
208
- const key = `${keyPrefix}${raw}`;
209
- const translated = translate(key);
210
- return translated === key ? raw : translated;
211
- }
212
- case "json":
213
- return JSON.stringify(value, null, (spec["indent"] as number | undefined) ?? 2);
214
- default:
215
- if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
216
- // biome-ignore lint/suspicious/noConsole: dev-only warning
217
- console.warn(
218
- `[kumiko] applyFormatSpec: unknown format key "${spec.format}" — registered via FieldFormatRegistry module augmentation?`,
219
- );
220
- }
221
- return typeof value === "string" ? value : String(value);
222
- }
223
- }
package/src/index.ts DELETED
@@ -1,118 +0,0 @@
1
- export type { ParsedRefTarget } from "@cosmicdrift/kumiko-framework/ui-types";
2
- // Re-Export aus framework/ui-types damit Renderer-Code denselben
3
- // Parser nutzt wie der Server-Boot-Validator (Cross-Feature-Refs).
4
- export { parseRefTarget } from "@cosmicdrift/kumiko-framework/ui-types";
5
- export type {
6
- AssetResolution,
7
- AssetResolveContext,
8
- AssetResolver,
9
- BadgeProps,
10
- ButtonProps,
11
- CardProps,
12
- DatePickerProps,
13
- IconProps,
14
- LocaleResolver,
15
- ModalProps,
16
- NumberInputProps,
17
- PrimitiveCommonProps,
18
- PrimitivesContract,
19
- SelectOption,
20
- SelectProps,
21
- TextInputProps,
22
- ToastIntent,
23
- ToastProps,
24
- ToggleProps,
25
- } from "./contracts";
26
- export type {
27
- BatchResult,
28
- Command,
29
- Dispatcher,
30
- DispatcherError,
31
- DispatcherStatus,
32
- FieldIssue,
33
- PendingFile,
34
- PendingWrite,
35
- QueryOpts,
36
- QueryResult,
37
- StreamOpts,
38
- WriteOpts,
39
- WriteResult,
40
- } from "./dispatcher";
41
- export { StreamFrame, type StreamFrameEvent } from "./dispatcher";
42
- export type {
43
- FieldConditionPredicate,
44
- FieldConditions,
45
- FieldConditionValue,
46
- FieldState,
47
- FormController,
48
- FormControllerOptions,
49
- FormSnapshot,
50
- FormValues,
51
- SubmitConfig,
52
- SubmitPayloadMode,
53
- SubmitResult,
54
- } from "./form";
55
- export { createFormController, I18N_KEY_PARAM } from "./form";
56
- export {
57
- applyFormatSpec,
58
- currencyDecimals,
59
- escapeHtml,
60
- escapeHtmlAttr,
61
- escapeXml,
62
- type HtmlValue,
63
- html,
64
- isSafeHref,
65
- RawHtml,
66
- raw,
67
- stripControlChars,
68
- toInstant,
69
- } from "./format";
70
- export type {
71
- NavDefinition,
72
- NavNode,
73
- NavRegistrySlice,
74
- NavTree,
75
- ResolveNavigationOptions,
76
- } from "./nav";
77
- export { resolveNavigation } from "./nav";
78
- export type { Store, WritableStore } from "./store";
79
- export { createStore, shallowEqual } from "./store";
80
- export type {
81
- ComputeEditViewModelInput,
82
- ComputeListViewModelInput,
83
- DerivedCellRoundingTarget,
84
- EditExtensionSectionViewModel,
85
- EditFieldSpec,
86
- EditFieldsSectionViewModel,
87
- EditFieldViewModel,
88
- EditRelatedListSectionViewModel,
89
- EditSectionSpec,
90
- EditSectionViewModel,
91
- EditViewModel,
92
- EditWriteFormSectionViewModel,
93
- EmbeddedDerivedOp,
94
- EmbeddedListCellViewModel,
95
- EmbeddedListIssueGroups,
96
- FieldConditionCtx,
97
- FieldRenderer,
98
- ListColumnSpec,
99
- ListColumnViewModel,
100
- ListRowViewModel,
101
- ListViewModel,
102
- RuntimeRenderer,
103
- ScreenSlots,
104
- Translate,
105
- } from "./view-model";
106
- export {
107
- computeDerivedCellValue,
108
- computeEditViewModel,
109
- computeListViewModel,
110
- embeddedCellLabelKey,
111
- embeddedCellOptionLabelKey,
112
- fieldLabelKey,
113
- fieldOptionLabelKey,
114
- fieldOptionLabelKeyPrefix,
115
- groupEmbeddedListIssues,
116
- roundDerivedCellValue,
117
- sumEmbeddedListColumn,
118
- } from "./view-model";