@kozou/svelte-ui 0.2.1 → 1.1.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 (167) hide show
  1. package/README.md +28 -8
  2. package/build/client/_app/immutable/assets/0._vgqT-Ja.css +2 -0
  3. package/build/client/_app/immutable/assets/0._vgqT-Ja.css.br +0 -0
  4. package/build/client/_app/immutable/assets/{0.B45CfnXC.css.gz → 0._vgqT-Ja.css.gz} +0 -0
  5. package/build/client/_app/immutable/chunks/BgT5WiOF.js +2 -0
  6. package/build/client/_app/immutable/chunks/BgT5WiOF.js.br +0 -0
  7. package/build/client/_app/immutable/chunks/BgT5WiOF.js.gz +0 -0
  8. package/build/client/_app/immutable/chunks/CCUVvlTa.js +214 -0
  9. package/build/client/_app/immutable/chunks/CCUVvlTa.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/CCUVvlTa.js.gz +0 -0
  11. package/build/client/_app/immutable/chunks/D5sfkRNZ.js +1 -0
  12. package/build/client/_app/immutable/chunks/D5sfkRNZ.js.br +0 -0
  13. package/build/client/_app/immutable/chunks/D5sfkRNZ.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/{nlrTAAdT.js → DdJtfEcD.js} +3 -3
  15. package/build/client/_app/immutable/chunks/DdJtfEcD.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/DdJtfEcD.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/{CNJ9qNKc.js → De8foa1z.js} +1 -1
  18. package/build/client/_app/immutable/chunks/De8foa1z.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/De8foa1z.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/DxikiVqj.js +1 -0
  21. package/build/client/_app/immutable/chunks/DxikiVqj.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/DxikiVqj.js.gz +0 -0
  23. package/build/client/_app/immutable/entry/app.BkKlOvdn.js +2 -0
  24. package/build/client/_app/immutable/entry/app.BkKlOvdn.js.br +0 -0
  25. package/build/client/_app/immutable/entry/app.BkKlOvdn.js.gz +0 -0
  26. package/build/client/_app/immutable/entry/start.Cjh4JaF7.js +1 -0
  27. package/build/client/_app/immutable/entry/start.Cjh4JaF7.js.br +0 -0
  28. package/build/client/_app/immutable/entry/start.Cjh4JaF7.js.gz +0 -0
  29. package/build/client/_app/immutable/nodes/0.BTi1ueXu.js +1 -0
  30. package/build/client/_app/immutable/nodes/0.BTi1ueXu.js.br +0 -0
  31. package/build/client/_app/immutable/nodes/0.BTi1ueXu.js.gz +0 -0
  32. package/build/client/_app/immutable/nodes/1.CrygsmaA.js +1 -0
  33. package/build/client/_app/immutable/nodes/1.CrygsmaA.js.br +0 -0
  34. package/build/client/_app/immutable/nodes/1.CrygsmaA.js.gz +0 -0
  35. package/build/client/_app/immutable/nodes/2.CWOXCYvQ.js +1 -0
  36. package/build/client/_app/immutable/nodes/2.CWOXCYvQ.js.br +0 -0
  37. package/build/client/_app/immutable/nodes/2.CWOXCYvQ.js.gz +0 -0
  38. package/build/client/_app/immutable/nodes/3.BfuWEZdX.js +1 -0
  39. package/build/client/_app/immutable/nodes/3.BfuWEZdX.js.br +0 -0
  40. package/build/client/_app/immutable/nodes/3.BfuWEZdX.js.gz +0 -0
  41. package/build/client/_app/immutable/nodes/4.TqsoTNiI.js +1 -0
  42. package/build/client/_app/immutable/nodes/4.TqsoTNiI.js.br +0 -0
  43. package/build/client/_app/immutable/nodes/4.TqsoTNiI.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/5.6savDm4D.js +1 -0
  45. package/build/client/_app/immutable/nodes/5.6savDm4D.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/5.6savDm4D.js.gz +0 -0
  47. package/build/client/_app/immutable/nodes/6.B6xlqPuj.js +1 -0
  48. package/build/client/_app/immutable/nodes/6.B6xlqPuj.js.br +0 -0
  49. package/build/client/_app/immutable/nodes/6.B6xlqPuj.js.gz +0 -0
  50. package/build/client/_app/immutable/nodes/{7.zir-jrfl.js → 7.CjEmXoAF.js} +1 -1
  51. package/build/client/_app/immutable/nodes/7.CjEmXoAF.js.br +1 -0
  52. package/build/client/_app/immutable/nodes/7.CjEmXoAF.js.gz +0 -0
  53. package/build/client/_app/version.json +1 -1
  54. package/build/client/_app/version.json.br +0 -0
  55. package/build/client/_app/version.json.gz +0 -0
  56. package/build/server/chunks/{0-D3i8OEah.js → 0-C4CgvBlc.js} +3 -3
  57. package/build/server/chunks/{0-D3i8OEah.js.map → 0-C4CgvBlc.js.map} +1 -1
  58. package/build/server/chunks/{1-BYbo_Ryk.js → 1-B5tolhxt.js} +3 -3
  59. package/build/server/chunks/{1-BYbo_Ryk.js.map → 1-B5tolhxt.js.map} +1 -1
  60. package/build/server/chunks/{2-CE6m9qid.js → 2-D9il_3ty.js} +2 -2
  61. package/build/server/chunks/{2-CE6m9qid.js.map → 2-D9il_3ty.js.map} +1 -1
  62. package/build/server/chunks/{3-C50aWcyD.js → 3-DfrqlU53.js} +5 -5
  63. package/build/server/chunks/3-DfrqlU53.js.map +1 -0
  64. package/build/server/chunks/{4-C8kXW-9J.js → 4-D6Y076hP.js} +53 -8
  65. package/build/server/chunks/4-D6Y076hP.js.map +1 -0
  66. package/build/server/chunks/5-DOvvx6kj.js +85 -0
  67. package/build/server/chunks/5-DOvvx6kj.js.map +1 -0
  68. package/build/server/chunks/6-BJn_aKxy.js +79 -0
  69. package/build/server/chunks/6-BJn_aKxy.js.map +1 -0
  70. package/build/server/chunks/{7-BAhFrC8Z.js → 7-CKkRKfs8.js} +5 -5
  71. package/build/server/chunks/7-CKkRKfs8.js.map +1 -0
  72. package/build/server/chunks/{ListTable-BxIw_RVF.js → ListTable-CkfkQNdN.js} +3 -7
  73. package/build/server/chunks/ListTable-CkfkQNdN.js.map +1 -0
  74. package/build/server/chunks/{_page.svelte-hNlfE_-R.js → _page.svelte-BWAo_bI_.js} +3 -3
  75. package/build/server/chunks/{_page.svelte-hNlfE_-R.js.map → _page.svelte-BWAo_bI_.js.map} +1 -1
  76. package/build/server/chunks/_page.svelte-D_706hfC.js +167 -0
  77. package/build/server/chunks/_page.svelte-D_706hfC.js.map +1 -0
  78. package/build/server/chunks/{_page.svelte-CUhqt56V.js → _page.svelte-Dabtpyl9.js} +3 -2
  79. package/build/server/chunks/{_page.svelte-CUhqt56V.js.map → _page.svelte-Dabtpyl9.js.map} +1 -1
  80. package/build/server/chunks/_page.svelte-DpoimUgp.js +167 -0
  81. package/build/server/chunks/_page.svelte-DpoimUgp.js.map +1 -0
  82. package/build/server/chunks/{_page.svelte-CRH5Ib1y.js → _page.svelte-j2Tqrjlb.js} +3 -2
  83. package/build/server/chunks/{_page.svelte-CRH5Ib1y.js.map → _page.svelte-j2Tqrjlb.js.map} +1 -1
  84. package/build/server/chunks/_server.ts-C-Wy675T.js +19 -0
  85. package/build/server/chunks/_server.ts-C-Wy675T.js.map +1 -0
  86. package/build/server/chunks/{adapter-BGid8Q1v.js → adapter-CXNsjV1V.js} +97 -14
  87. package/build/server/chunks/adapter-CXNsjV1V.js.map +1 -0
  88. package/build/server/chunks/{client-PK7K0DFj.js → client-_87vxA9R.js} +2 -2
  89. package/build/server/chunks/{client-PK7K0DFj.js.map → client-_87vxA9R.js.map} +1 -1
  90. package/build/server/chunks/{zod-from-table-CCy3rZQi.js → composite-form-C6ci57iZ.js} +248 -6
  91. package/build/server/chunks/composite-form-C6ci57iZ.js.map +1 -0
  92. package/build/server/chunks/{error.svelte-CKswtdNj.js → error.svelte-CsN316oS.js} +3 -3
  93. package/build/server/chunks/{error.svelte-CKswtdNj.js.map → error.svelte-CsN316oS.js.map} +1 -1
  94. package/build/server/chunks/{hooks.server-_7e7MfY9.js → hooks.server-O5zJccZI.js} +36 -14
  95. package/build/server/chunks/hooks.server-O5zJccZI.js.map +1 -0
  96. package/build/server/chunks/{internal-BERZdLJL.js → internal-Cuea68jL.js} +3 -3
  97. package/build/server/chunks/{internal-BERZdLJL.js.map → internal-Cuea68jL.js.map} +1 -1
  98. package/build/server/chunks/relation-options-Dzy9DaQG.js +87 -0
  99. package/build/server/chunks/relation-options-Dzy9DaQG.js.map +1 -0
  100. package/build/server/chunks/resource-id-PDcQeAnc.js +41 -0
  101. package/build/server/chunks/resource-id-PDcQeAnc.js.map +1 -0
  102. package/build/server/chunks/{widget-registry-CYJ6dCZP.js → widget-registry-CohQ23di.js} +282 -42
  103. package/build/server/chunks/widget-registry-CohQ23di.js.map +1 -0
  104. package/build/server/index.js +1 -1
  105. package/build/server/manifest.js +16 -9
  106. package/build/server/manifest.js.map +1 -1
  107. package/package.json +5 -5
  108. package/build/client/_app/immutable/assets/0.B45CfnXC.css +0 -2
  109. package/build/client/_app/immutable/assets/0.B45CfnXC.css.br +0 -0
  110. package/build/client/_app/immutable/chunks/1PNfJiP5.js +0 -2
  111. package/build/client/_app/immutable/chunks/1PNfJiP5.js.br +0 -0
  112. package/build/client/_app/immutable/chunks/1PNfJiP5.js.gz +0 -0
  113. package/build/client/_app/immutable/chunks/CLOztVuN.js +0 -214
  114. package/build/client/_app/immutable/chunks/CLOztVuN.js.br +0 -0
  115. package/build/client/_app/immutable/chunks/CLOztVuN.js.gz +0 -0
  116. package/build/client/_app/immutable/chunks/CNJ9qNKc.js.br +0 -0
  117. package/build/client/_app/immutable/chunks/CNJ9qNKc.js.gz +0 -0
  118. package/build/client/_app/immutable/chunks/Cj8LiudX.js +0 -1
  119. package/build/client/_app/immutable/chunks/Cj8LiudX.js.br +0 -0
  120. package/build/client/_app/immutable/chunks/Cj8LiudX.js.gz +0 -0
  121. package/build/client/_app/immutable/chunks/nlrTAAdT.js.br +0 -0
  122. package/build/client/_app/immutable/chunks/nlrTAAdT.js.gz +0 -0
  123. package/build/client/_app/immutable/entry/app.Bv8sjgxu.js +0 -2
  124. package/build/client/_app/immutable/entry/app.Bv8sjgxu.js.br +0 -0
  125. package/build/client/_app/immutable/entry/app.Bv8sjgxu.js.gz +0 -0
  126. package/build/client/_app/immutable/entry/start.mcTncYkV.js +0 -1
  127. package/build/client/_app/immutable/entry/start.mcTncYkV.js.br +0 -0
  128. package/build/client/_app/immutable/entry/start.mcTncYkV.js.gz +0 -0
  129. package/build/client/_app/immutable/nodes/0.DAriStmJ.js +0 -1
  130. package/build/client/_app/immutable/nodes/0.DAriStmJ.js.br +0 -0
  131. package/build/client/_app/immutable/nodes/0.DAriStmJ.js.gz +0 -0
  132. package/build/client/_app/immutable/nodes/1.B_iETXYV.js +0 -1
  133. package/build/client/_app/immutable/nodes/1.B_iETXYV.js.br +0 -2
  134. package/build/client/_app/immutable/nodes/1.B_iETXYV.js.gz +0 -0
  135. package/build/client/_app/immutable/nodes/2.C3ChRVrO.js +0 -1
  136. package/build/client/_app/immutable/nodes/2.C3ChRVrO.js.br +0 -0
  137. package/build/client/_app/immutable/nodes/2.C3ChRVrO.js.gz +0 -0
  138. package/build/client/_app/immutable/nodes/3.CL9ksnmY.js +0 -1
  139. package/build/client/_app/immutable/nodes/3.CL9ksnmY.js.br +0 -0
  140. package/build/client/_app/immutable/nodes/3.CL9ksnmY.js.gz +0 -0
  141. package/build/client/_app/immutable/nodes/4.wRTS68Ip.js +0 -1
  142. package/build/client/_app/immutable/nodes/4.wRTS68Ip.js.br +0 -0
  143. package/build/client/_app/immutable/nodes/4.wRTS68Ip.js.gz +0 -0
  144. package/build/client/_app/immutable/nodes/5.sOvoAy14.js +0 -1
  145. package/build/client/_app/immutable/nodes/5.sOvoAy14.js.br +0 -0
  146. package/build/client/_app/immutable/nodes/5.sOvoAy14.js.gz +0 -0
  147. package/build/client/_app/immutable/nodes/6.QhxyVDh4.js +0 -1
  148. package/build/client/_app/immutable/nodes/6.QhxyVDh4.js.br +0 -0
  149. package/build/client/_app/immutable/nodes/6.QhxyVDh4.js.gz +0 -0
  150. package/build/client/_app/immutable/nodes/7.zir-jrfl.js.br +0 -4
  151. package/build/client/_app/immutable/nodes/7.zir-jrfl.js.gz +0 -0
  152. package/build/server/chunks/3-C50aWcyD.js.map +0 -1
  153. package/build/server/chunks/4-C8kXW-9J.js.map +0 -1
  154. package/build/server/chunks/5-D-0L-X8y.js +0 -69
  155. package/build/server/chunks/5-D-0L-X8y.js.map +0 -1
  156. package/build/server/chunks/6-D-SbXeTH.js +0 -70
  157. package/build/server/chunks/6-D-SbXeTH.js.map +0 -1
  158. package/build/server/chunks/7-BAhFrC8Z.js.map +0 -1
  159. package/build/server/chunks/ListTable-BxIw_RVF.js.map +0 -1
  160. package/build/server/chunks/_page.svelte-6neDahmr.js +0 -70
  161. package/build/server/chunks/_page.svelte-6neDahmr.js.map +0 -1
  162. package/build/server/chunks/_page.svelte-rW7yzEet.js +0 -70
  163. package/build/server/chunks/_page.svelte-rW7yzEet.js.map +0 -1
  164. package/build/server/chunks/adapter-BGid8Q1v.js.map +0 -1
  165. package/build/server/chunks/hooks.server-_7e7MfY9.js.map +0 -1
  166. package/build/server/chunks/widget-registry-CYJ6dCZP.js.map +0 -1
  167. package/build/server/chunks/zod-from-table-CCy3rZQi.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { y as noop, G as parse, C as onDestroy, X as tick, S as stringify$1, c as __toESM, _ as __commonJSMin, a as __exportAll } from './internal-BERZdLJL.js';
1
+ import { y as noop, G as parse, C as onDestroy, X as tick, S as stringify$1, c as __toESM, _ as __commonJSMin, a as __exportAll } from './internal-Cuea68jL.js';
2
2
  import { u as get$1, ad as writable, n as derived$1, Y as readonly, x as getContext } from './dev-yFiAtg5l.js';
3
- import { i as invalidateAll, b as applyAction, a as afterNavigate } from './client-PK7K0DFj.js';
3
+ import { i as invalidateAll, b as applyAction, a as afterNavigate } from './client-_87vxA9R.js';
4
4
  import { a as app } from './app-DST8OmzT.js';
5
5
  import './index-5kYmxIr9.js';
6
6
  import { toJSONSchema, config, safeParseAsync } from 'zod/v4/core';
@@ -2388,6 +2388,191 @@ async function superValidate(data, adapter, options) {
2388
2388
  }
2389
2389
  return output;
2390
2390
  }
2391
+ //#endregion
2392
+ //#region src/lib/form/relation-field-config.ts
2393
+ /**
2394
+ * Whether an option can round-trip through the picker contract. A key
2395
+ * containing an EMPTY-STRING component collides with the contract's ''
2396
+ * unselected sentinel — picking it would corrupt the write (the component
2397
+ * would be normalized to null) — so such options are not offered (a
2398
+ * documented limitation, see {@link promoteCompositeMemberWidgets}).
2399
+ */
2400
+ function isPickableOption(option) {
2401
+ return (Array.isArray(option.id) ? option.id : [option.id]).every((part) => part !== "");
2402
+ }
2403
+ /**
2404
+ * The composite picker's explicit clear value. The empty string cannot mean
2405
+ * "clear" on the native path: an unselected (e.g. partial-null) current
2406
+ * value renders the select at '' too, and an untouched no-JS save must keep
2407
+ * the baseline values rather than erase them. A real composite option value
2408
+ * always contains a comma (two or more components), so this marker cannot
2409
+ * collide with one.
2410
+ */
2411
+ var COMPOSITE_CLEAR_VALUE = "__clear__";
2412
+ /**
2413
+ * Name of the single form control a composite picker submits. A no-JS
2414
+ * (non-enhanced) submission cannot fan the selection out to the component
2415
+ * fields itself, so the picker's `<select>` carries the canonical encoded id
2416
+ * under this synthetic name and the server decodes it into the component
2417
+ * fields ahead of validation (see `readFormWithCompositePicks`). The enhanced
2418
+ * path ignores it (the form store travels in superforms' JSON envelope).
2419
+ */
2420
+ function compositeParamName(field) {
2421
+ return `__composite__${field}`;
2422
+ }
2423
+ var TEXT_LIKE_BASE_TYPES = new Set([
2424
+ "text",
2425
+ "character varying",
2426
+ "varchar",
2427
+ "character",
2428
+ "char",
2429
+ "bpchar",
2430
+ "citext",
2431
+ "name"
2432
+ ]);
2433
+ function isTextSearchable(dataType) {
2434
+ const lower = dataType.trim().toLowerCase();
2435
+ if (lower.includes("[")) return false;
2436
+ const paren = lower.indexOf("(");
2437
+ const base = (paren === -1 ? lower : lower.slice(0, paren)).trim();
2438
+ return TEXT_LIKE_BASE_TYPES.has(base);
2439
+ }
2440
+ /** Resolve the searchable label column of a picker target, or `null` when the
2441
+ * picker would be unusable (no text-searchable label). */
2442
+ function searchableLabel(target) {
2443
+ const labelField = target.displayField ?? target.primaryKey[0];
2444
+ const labelColumn = target.columns.find((c) => c.name === labelField);
2445
+ if (labelColumn === void 0 || !isTextSearchable(labelColumn.dataType)) return null;
2446
+ return {
2447
+ labelField,
2448
+ searchFields: [labelField]
2449
+ };
2450
+ }
2451
+ function relationFieldConfigs(table, schema) {
2452
+ const configs = [];
2453
+ for (const relation of table.relations) {
2454
+ const fields = relation.fields ?? [relation.field];
2455
+ const referencedColumns = relation.references.columns ?? [relation.references.column];
2456
+ if (referencedColumns.length !== fields.length) continue;
2457
+ const resource = `${relation.references.schema}.${relation.references.table}`;
2458
+ const target = schema.tables.find((t) => t.qualifiedName === resource);
2459
+ if (target === void 0) continue;
2460
+ const primaryKey = target.primaryKey;
2461
+ if (primaryKey.length !== referencedColumns.length || !primaryKey.every((column) => referencedColumns.includes(column))) continue;
2462
+ const label = searchableLabel(target);
2463
+ if (label === null) continue;
2464
+ if (fields.length === 1) {
2465
+ configs.push({
2466
+ field: fields[0],
2467
+ resource,
2468
+ ...label
2469
+ });
2470
+ continue;
2471
+ }
2472
+ if (table.columns.some((c) => c.name === compositeParamName(fields[0]))) continue;
2473
+ const keyFields = primaryKey.map((column) => fields[referencedColumns.indexOf(column)]);
2474
+ configs.push({
2475
+ field: fields[0],
2476
+ fields: [...fields],
2477
+ keyFields,
2478
+ resource,
2479
+ ...label
2480
+ });
2481
+ }
2482
+ const claims = /* @__PURE__ */ new Map();
2483
+ for (const relation of table.relations) for (const field of relation.fields ?? [relation.field]) claims.set(field, (claims.get(field) ?? 0) + 1);
2484
+ return configs.filter((config) => {
2485
+ const fields = config.fields ?? [config.field];
2486
+ if (fields.length === 1) return true;
2487
+ return fields.every((field) => claims.get(field) === 1);
2488
+ });
2489
+ }
2490
+ /**
2491
+ * Pick a scalar input widget for a column from its `dataType`.
2492
+ *
2493
+ * Used to demote a single-column foreign key that core marked `relation-select`
2494
+ * but that has no usable picker config (it references a non-PK unique column).
2495
+ * Without this it would render an empty, unselectable relation dropdown; the
2496
+ * scalar input lets the operator type the raw value instead. Mirrors the
2497
+ * type-based arm of core's widget inference, but keyed on the formatted
2498
+ * `dataType` exposed on `ColumnContext`.
2499
+ */
2500
+ function scalarWidgetForDataType(dataType) {
2501
+ const type = dataType.toLowerCase();
2502
+ if (type === "uuid") return "uuid";
2503
+ if (type === "boolean" || type === "bool") return "boolean";
2504
+ if (type === "date") return "date";
2505
+ if (type.includes("timestamp") || type.startsWith("time")) return "datetime";
2506
+ if (type === "json" || type === "jsonb") return "json";
2507
+ if (type.includes("int") || type.includes("numeric") || type.includes("decimal") || type.includes("double") || type === "real") return "number";
2508
+ return "text";
2509
+ }
2510
+ /**
2511
+ * Return a copy of `table` with each unpickable single-column relation-select
2512
+ * column demoted to a scalar widget (see {@link scalarWidgetForDataType}).
2513
+ *
2514
+ * The create / edit routes build BOTH the rendered widget and the zod
2515
+ * validation schema from this, so a demoted field validates as the scalar type
2516
+ * the operator actually sees rather than the loose relation-select union.
2517
+ * Composite foreign-key columns are untouched: core never marks them
2518
+ * `relation-select` (they carry type-based scalar widgets), and the composite
2519
+ * picker writes through those scalar fields rather than replacing them.
2520
+ */
2521
+ function demoteUnpickableRelations(table, relations) {
2522
+ const pickable = new Set(relations.filter((r) => (r.fields ?? [r.field]).length === 1).map((r) => r.field));
2523
+ return {
2524
+ ...table,
2525
+ columns: table.columns.map((c) => c.widget === "relation-select" && !pickable.has(c.name) ? {
2526
+ ...c,
2527
+ widget: scalarWidgetForDataType(c.dataType)
2528
+ } : c)
2529
+ };
2530
+ }
2531
+ /**
2532
+ * Return a copy of `table` with every composite-picker component column
2533
+ * switched to the `relation-select` widget, so the form layer treats it as a
2534
+ * picker-held value rather than a typed scalar input:
2535
+ *
2536
+ * - zodFromColumn applies the picker semantics: the unselected default is
2537
+ * `''` and a required component rejects an empty submission — without
2538
+ * this, superforms initializes an absent required numeric component to
2539
+ * `0`, and the suppressed (never-rendered) member columns would silently
2540
+ * submit a fabricated `(0, 0, ...)` reference;
2541
+ * - buildMutationPayload normalizes `''` to null (or drops it for a
2542
+ * DB-supplied column) instead of letting it coerce.
2543
+ *
2544
+ * The columns themselves are never rendered — the composite picker replaces
2545
+ * them — so the widget change is purely a validation / payload contract.
2546
+ * Apply AFTER {@link demoteUnpickableRelations} (which would demote the
2547
+ * promoted members straight back).
2548
+ *
2549
+ * Known limitation: the contract reserves '' as the unselected sentinel
2550
+ * (matching the single-column picker), so a key that contains an
2551
+ * EMPTY-STRING component cannot round-trip through the form — a required
2552
+ * component rejects it and an optional one normalizes it to null. The
2553
+ * picker therefore does not offer such options ({@link isPickableOption}),
2554
+ * the native decoder treats them as malformed, and the current-value seeding
2555
+ * skips them; an untouched edit save of a row already holding such a key
2556
+ * still normalizes the '' component (this predates composite support — the
2557
+ * single-column picker has the same property). Such keys are pathological
2558
+ * schema design and remain manageable through the API.
2559
+ */
2560
+ function promoteCompositeMemberWidgets(table, relations) {
2561
+ const members = /* @__PURE__ */ new Set();
2562
+ for (const config of relations) {
2563
+ const fields = config.fields ?? [config.field];
2564
+ if (fields.length < 2) continue;
2565
+ for (const field of fields) members.add(field);
2566
+ }
2567
+ if (members.size === 0) return table;
2568
+ return {
2569
+ ...table,
2570
+ columns: table.columns.map((c) => members.has(c.name) ? {
2571
+ ...c,
2572
+ widget: "relation-select"
2573
+ } : c)
2574
+ };
2575
+ }
2391
2576
 
2392
2577
  //#region ../../node_modules/.pnpm/memoize-weak@1.0.2/node_modules/memoize-weak/lib/memoize.js
2393
2578
  var require_memoize = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -6869,6 +7054,11 @@ function dbCanSupplyColumn(column) {
6869
7054
  return column.defaultExpr !== null || column.readonly;
6870
7055
  }
6871
7056
  function zodFromColumn(column) {
7057
+ if (column.widget === "relation-select") {
7058
+ const required = !column.nullable && !dbCanSupplyColumn(column);
7059
+ const id = z.union([required ? z.string().min(1) : z.string(), z.number()]);
7060
+ return (column.nullable ? id.nullable() : id).default("");
7061
+ }
6872
7062
  const base = pickBase(column);
6873
7063
  if (dbCanSupplyColumn(column)) {
6874
7064
  const inner = column.nullable ? base.nullable() : base;
@@ -6887,7 +7077,6 @@ function pickBase(column) {
6887
7077
  case "enum-select":
6888
7078
  if (column.enumValues && column.enumValues.length > 0) return z.enum(column.enumValues);
6889
7079
  return z.string();
6890
- case "relation-select": return z.union([z.string(), z.number()]);
6891
7080
  case "uuid": return z.guid();
6892
7081
  case "json": return z.unknown();
6893
7082
  case "image-url":
@@ -6901,11 +7090,17 @@ function pickBase(column) {
6901
7090
  function isEmpty(value) {
6902
7091
  return value === "" || value === void 0;
6903
7092
  }
6904
- function buildMutationPayload(table, data) {
7093
+ function buildMutationPayload(table, data, mode = "create") {
6905
7094
  const byName = new Map(table.columns.map((c) => [c.name, c]));
6906
7095
  const payload = {};
6907
7096
  for (const [key, value] of Object.entries(data)) {
6908
7097
  const column = byName.get(key);
7098
+ if (column?.widget === "relation-select" && value === "") {
7099
+ if (!(dbCanSupplyColumn(column) && (mode === "create" || column.readonly))) {
7100
+ payload[key] = null;
7101
+ continue;
7102
+ }
7103
+ }
6909
7104
  if (column && dbCanSupplyColumn(column) && isEmpty(value)) continue;
6910
7105
  payload[key] = value;
6911
7106
  }
@@ -6918,6 +7113,53 @@ function zodFromTable(table) {
6918
7113
  for (const column of table.columns) shape[column.name] = zodFromColumn(column);
6919
7114
  return z.object(shape);
6920
7115
  }
7116
+ //#endregion
7117
+ //#region src/lib/server/composite-form.ts
7118
+ /**
7119
+ * Return what the route action should hand to `superValidate`: the request
7120
+ * itself when the table has no pickers, the raw FormData when it carries the
7121
+ * enhanced JSON envelope, or a plain object holding the native submission
7122
+ * with each composite pick decoded into its component fields. Returns `null`
7123
+ * for a malformed composite control — wrong arity, invalid percent-encoding,
7124
+ * or an empty-string component — so the action can reject the submission:
7125
+ * letting it fall through to the schema defaults would silently CLEAR an
7126
+ * optional relation instead of reporting the bad value (a required one
7127
+ * already fails validation through the '' defaults).
7128
+ */
7129
+ async function readFormWithCompositePicks(request, relations) {
7130
+ if (relations.length === 0) return request;
7131
+ const contentType = request.headers.get("content-type") ?? "";
7132
+ if (!contentType.includes("application/x-www-form-urlencoded") && !contentType.includes("multipart/form-data")) return request;
7133
+ const data = await request.formData();
7134
+ if (data.has("__superform_json")) return data;
7135
+ const fields = {};
7136
+ for (const [key, value] of data.entries()) if (typeof value === "string" && value !== "") fields[key] = value;
7137
+ for (const config of relations) {
7138
+ const keyFields = config.keyFields ?? [config.field];
7139
+ if (keyFields.length < 2) continue;
7140
+ const param = compositeParamName(config.field);
7141
+ const raw = fields[param];
7142
+ if (typeof raw !== "string") continue;
7143
+ delete fields[param];
7144
+ if (raw === "") continue;
7145
+ if (raw === "__clear__") {
7146
+ for (const field of keyFields) delete fields[field];
7147
+ continue;
7148
+ }
7149
+ let components;
7150
+ try {
7151
+ components = raw.split(",").map((part) => decodeURIComponent(part));
7152
+ } catch {
7153
+ return null;
7154
+ }
7155
+ if (components.length !== keyFields.length) return null;
7156
+ if (components.some((part) => part === "")) return null;
7157
+ keyFields.forEach((field, i) => {
7158
+ fields[field] = components[i];
7159
+ });
7160
+ }
7161
+ return fields;
7162
+ }
6921
7163
 
6922
- export { superValidate as a, buildMutationPayload as b, zodFromTable as c, superForm as s, zod as z };
6923
- //# sourceMappingURL=zod-from-table-CCy3rZQi.js.map
7164
+ export { COMPOSITE_CLEAR_VALUE as C, relationFieldConfigs as a, buildMutationPayload as b, compositeParamName as c, demoteUnpickableRelations as d, superValidate as e, zodFromTable as f, isPickableOption as i, promoteCompositeMemberWidgets as p, readFormWithCompositePicks as r, superForm as s, zod as z };
7165
+ //# sourceMappingURL=composite-form-C6ci57iZ.js.map