@cosmicdrift/kumiko-headless 0.328.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 (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,274 +0,0 @@
1
- import { describe, expect, mock, test } from "bun:test";
2
- import * as z from "zod";
3
- import { createFormController } from "../form-controller";
4
- import { groupIssuesByPath, zodErrorToFieldIssues } from "../zod-bridge";
5
-
6
- describe("zodErrorToFieldIssues", () => {
7
- test("flattens zod issues to FieldIssue with dotted paths", () => {
8
- const schema = z.object({
9
- title: z.string().min(1),
10
- address: z.object({ city: z.string().min(1) }),
11
- tags: z.array(z.string().min(1)),
12
- });
13
- const result = schema.safeParse({ title: "", address: { city: "" }, tags: ["ok", ""] });
14
-
15
- expect(result.success).toBe(false);
16
- if (result.success) return;
17
- const issues = zodErrorToFieldIssues(result.error);
18
-
19
- const paths = issues.map((i) => i.path).sort();
20
- expect(paths).toContain("title");
21
- expect(paths).toContain("address.city");
22
- expect(paths).toContain("tags.1");
23
- });
24
-
25
- test("top-level issues get path='(root)' — matches server zod-bridge", () => {
26
- const schema = z.object({ foo: z.string() });
27
- // Pass a non-object → zod raises an issue with path=[].
28
- const result = schema.safeParse("not-an-object");
29
-
30
- expect(result.success).toBe(false);
31
- if (result.success) return;
32
- const issues = zodErrorToFieldIssues(result.error);
33
-
34
- expect(issues[0]?.path).toBe("(root)");
35
- });
36
-
37
- test("surfaces zod params (minimum/maximum/expected) under issue.params", () => {
38
- const schema = z.object({ count: z.number().min(10).max(100) });
39
- const result = schema.safeParse({ count: 3 });
40
-
41
- expect(result.success).toBe(false);
42
- if (result.success) return;
43
- const issues = zodErrorToFieldIssues(result.error);
44
-
45
- expect(issues[0]?.params).toBeDefined();
46
- expect(issues[0]?.params?.["minimum"]).toBe(10);
47
- });
48
-
49
- test("custom issue with params.i18nKey overrides the mechanical errors.validation.custom key", () => {
50
- const schema = z.object({ name: z.string() }).superRefine((values, ctx) => {
51
- if (values.name === "") {
52
- ctx.addIssue({
53
- code: "custom",
54
- path: ["name"],
55
- message: '"name" is required.',
56
- params: { i18nKey: "kumiko.validation.required" },
57
- });
58
- }
59
- });
60
- const result = schema.safeParse({ name: "" });
61
-
62
- expect(result.success).toBe(false);
63
- if (result.success) return;
64
- const issues = zodErrorToFieldIssues(result.error);
65
-
66
- expect(issues[0]?.i18nKey).toBe("kumiko.validation.required");
67
- });
68
-
69
- test("custom issue without params.i18nKey still falls back to errors.validation.custom", () => {
70
- const schema = z.object({ name: z.string() }).superRefine((values, ctx) => {
71
- if (values.name === "bad") {
72
- ctx.addIssue({ code: "custom", path: ["name"], message: "not allowed" });
73
- }
74
- });
75
- const result = schema.safeParse({ name: "bad" });
76
-
77
- expect(result.success).toBe(false);
78
- if (result.success) return;
79
- const issues = zodErrorToFieldIssues(result.error);
80
-
81
- expect(issues[0]?.i18nKey).toBe("errors.validation.custom");
82
- });
83
- });
84
-
85
- describe("groupIssuesByPath", () => {
86
- test("groups multiple issues on the same path into one bucket", () => {
87
- const grouped = groupIssuesByPath([
88
- { path: "title", code: "too_small", i18nKey: "x" },
89
- { path: "title", code: "invalid_format", i18nKey: "x" },
90
- { path: "age", code: "invalid_type", i18nKey: "x" },
91
- ]);
92
-
93
- expect(grouped["title"]).toHaveLength(2);
94
- expect(grouped["age"]).toHaveLength(1);
95
- });
96
- });
97
-
98
- describe("createFormController — validate()", () => {
99
- test("without a schema: validate() is a no-op that returns true", () => {
100
- const form = createFormController({ initial: { title: "" } });
101
- const listener = mock();
102
- form.subscribe(listener);
103
-
104
- const ok = form.validate();
105
-
106
- expect(ok).toBe(true);
107
- expect(form.getSnapshot().errors).toEqual({});
108
- expect(listener).not.toHaveBeenCalled(); // no-op
109
- });
110
-
111
- test("with a schema: validate() runs it and populates errors on failure", () => {
112
- const schema = z.object({ title: z.string().min(3), age: z.number().int() });
113
- const form = createFormController({
114
- initial: { title: "a", age: 1.5 },
115
- schema,
116
- });
117
-
118
- const ok = form.validate();
119
- const snap = form.getSnapshot();
120
-
121
- expect(ok).toBe(false);
122
- expect(snap.errors["title"]).toBeDefined();
123
- expect(snap.errors["age"]).toBeDefined();
124
- });
125
-
126
- test("validate() returns true when values match the schema", () => {
127
- const schema = z.object({ title: z.string().min(1) });
128
- const form = createFormController({ initial: { title: "hello" }, schema });
129
-
130
- expect(form.validate()).toBe(true);
131
- expect(form.getSnapshot().errors).toEqual({});
132
- });
133
-
134
- test("validate() clears previous errors on subsequent success", () => {
135
- // Common flow: user submits, sees errors, fixes fields, hits validate
136
- // again — old errors must disappear.
137
- const schema = z.object({ title: z.string().min(3) });
138
- const form = createFormController({ initial: { title: "a" }, schema });
139
-
140
- form.validate();
141
- expect(form.getSnapshot().errors["title"]).toBeDefined();
142
-
143
- form.setField("title", "hello");
144
- form.validate();
145
-
146
- expect(form.getSnapshot().errors).toEqual({});
147
- });
148
-
149
- test("validate() with nested values: errors keyed by dotted path", () => {
150
- const schema = z.object({
151
- address: z.object({ city: z.string().min(1) }),
152
- });
153
- const form = createFormController({ initial: { address: { city: "" } }, schema });
154
-
155
- form.validate();
156
-
157
- expect(form.getSnapshot().errors["address.city"]).toBeDefined();
158
- });
159
- });
160
-
161
- describe("createFormController — validate(scope)", () => {
162
- test("scoped run reports only fields inside scope, drops issues outside it", () => {
163
- const schema = z.object({
164
- a: z.string().min(3),
165
- b: z.string().min(3),
166
- });
167
- const form = createFormController({ initial: { a: "x", b: "y" }, schema });
168
-
169
- const ok = form.validate(["a"]);
170
- const snap = form.getSnapshot();
171
-
172
- expect(ok).toBe(false);
173
- expect(snap.errors["a"]).toBeDefined();
174
- expect(snap.errors["b"]).toBeUndefined();
175
- });
176
-
177
- test("scoped run returns true when every field in scope is valid, even if fields outside scope are not", () => {
178
- const schema = z.object({
179
- a: z.string().min(1),
180
- b: z.string().min(3),
181
- });
182
- const form = createFormController({ initial: { a: "ok", b: "y" }, schema });
183
-
184
- const ok = form.validate(["a"]);
185
-
186
- expect(ok).toBe(true);
187
- expect(form.getSnapshot().errors).toEqual({});
188
- });
189
-
190
- test("nested scope path matches issue root segment (#1898)", () => {
191
- const schema = z.object({
192
- address: z.object({ city: z.string().min(1) }),
193
- });
194
- const form = createFormController({
195
- initial: { address: { city: "" } },
196
- schema,
197
- });
198
- expect(form.validate(["address.city"])).toBe(false);
199
- expect(form.getSnapshot().errors["address.city"]).toBeDefined();
200
- });
201
-
202
- // Hard rule (kumiko-framework#1885): an object-level .refine() issue has
203
- // path "(root)", which never matches a field name — every scoped
204
- // validate() call must ignore it, and only the unscoped final-submit
205
- // validate() may surface it. Otherwise a step-boundary check would block
206
- // a wizard step forever on an invariant no single step owns.
207
- describe("root-path .refine() issues", () => {
208
- const schema = z
209
- .object({
210
- start: z.string(),
211
- end: z.string(),
212
- })
213
- .refine((v) => v.end >= v.start, { message: "end must be >= start" });
214
-
215
- test("scoped validate() ignores the root issue regardless of which fields are in scope", () => {
216
- const form = createFormController({ initial: { start: "b", end: "a" }, schema });
217
-
218
- expect(form.validate(["start"])).toBe(true);
219
- expect(form.validate(["end"])).toBe(true);
220
- expect(form.validate(["start", "end"])).toBe(true);
221
- expect(form.getSnapshot().errors).toEqual({});
222
- });
223
-
224
- test("unscoped validate() (final submit) reports the root issue under '(root)'", () => {
225
- const form = createFormController({ initial: { start: "b", end: "a" }, schema });
226
-
227
- const ok = form.validate();
228
-
229
- expect(ok).toBe(false);
230
- expect(form.getSnapshot().errors["(root)"]).toBeDefined();
231
- });
232
- });
233
-
234
- test("hidden fields stay filtered in scoped mode even when the hidden field is itself in scope", () => {
235
- // Discriminating case: field `b` is both hidden AND inside `scope`, so
236
- // the assertion actually exercises the hidden-field filter — not just
237
- // the scope filter dropping an out-of-scope field.
238
- const schema = z.object({
239
- a: z.string().min(3),
240
- b: z.string().min(3),
241
- });
242
- const form = createFormController({
243
- initial: { a: "x", b: "y", showB: false },
244
- schema,
245
- fields: { b: { visible: (values) => values.showB } },
246
- });
247
-
248
- const ok = form.validate(["a", "b"]);
249
- const snap = form.getSnapshot();
250
-
251
- expect(ok).toBe(false);
252
- expect(snap.errors["a"]).toBeDefined();
253
- expect(snap.errors["b"]).toBeUndefined();
254
- });
255
-
256
- test("hidden fields stay filtered in unscoped mode (regression guard alongside the scoped case)", () => {
257
- const schema = z.object({
258
- a: z.string().min(3),
259
- b: z.string().min(3),
260
- });
261
- const form = createFormController({
262
- initial: { a: "x", b: "y", showB: false },
263
- schema,
264
- fields: { b: { visible: (values) => values.showB } },
265
- });
266
-
267
- const ok = form.validate();
268
- const snap = form.getSnapshot();
269
-
270
- expect(ok).toBe(false);
271
- expect(snap.errors["a"]).toBeDefined();
272
- expect(snap.errors["b"]).toBeUndefined();
273
- });
274
- });