@happyvertical/smrt-fields 0.40.60 → 0.40.62

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 (218) hide show
  1. package/AGENTS.md +58 -12
  2. package/README.md +143 -42
  3. package/dist/__smrt-register__.d.ts +2 -0
  4. package/dist/__smrt-register__.d.ts.map +1 -0
  5. package/dist/cache.d.ts +18 -0
  6. package/dist/cache.d.ts.map +1 -0
  7. package/dist/chunks/FieldPolicyCollection--RxukfCX.js +1218 -0
  8. package/dist/chunks/FieldPolicyCollection--RxukfCX.js.map +1 -0
  9. package/dist/collections/FieldPolicyCollection.d.ts +95 -0
  10. package/dist/collections/FieldPolicyCollection.d.ts.map +1 -0
  11. package/dist/collections/FieldPolicySuggestionCollection.d.ts +160 -0
  12. package/dist/collections/FieldPolicySuggestionCollection.d.ts.map +1 -0
  13. package/dist/collections/FieldUsageCounterCollection.d.ts +143 -0
  14. package/dist/collections/FieldUsageCounterCollection.d.ts.map +1 -0
  15. package/dist/deterministic-id.d.ts +18 -0
  16. package/dist/deterministic-id.d.ts.map +1 -0
  17. package/dist/field-definitions.d.ts +85 -0
  18. package/dist/field-definitions.d.ts.map +1 -0
  19. package/dist/field-policy-resolver.d.ts +46 -0
  20. package/dist/field-policy-resolver.d.ts.map +1 -0
  21. package/dist/index.d.ts +15 -532
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +1813 -628
  24. package/dist/index.js.map +1 -1
  25. package/dist/manifest.json +1487 -191
  26. package/dist/models/FieldPolicy.d.ts +163 -0
  27. package/dist/models/FieldPolicy.d.ts.map +1 -0
  28. package/dist/models/FieldPolicySuggestion.d.ts +132 -0
  29. package/dist/models/FieldPolicySuggestion.d.ts.map +1 -0
  30. package/dist/models/FieldUsageCounter.d.ts +178 -0
  31. package/dist/models/FieldUsageCounter.d.ts.map +1 -0
  32. package/dist/models/FieldUsageReportReceipt.d.ts +25 -0
  33. package/dist/models/FieldUsageReportReceipt.d.ts.map +1 -0
  34. package/dist/permissions.d.ts +9 -0
  35. package/dist/permissions.d.ts.map +1 -0
  36. package/dist/playground.d.ts +2 -0
  37. package/dist/playground.d.ts.map +1 -0
  38. package/dist/playground.js +39 -0
  39. package/dist/playground.js.map +1 -0
  40. package/dist/settings-catalog.d.ts +73 -0
  41. package/dist/settings-catalog.d.ts.map +1 -0
  42. package/dist/smrt-knowledge.json +449 -9
  43. package/dist/svelte/__tests__/FieldPolicyControlPanel.test.js +277 -0
  44. package/dist/svelte/__tests__/FieldPolicyGear.test.js +503 -0
  45. package/dist/svelte/__tests__/FormHelp.test.js +138 -0
  46. package/dist/svelte/__tests__/ModeUX.test.js +109 -0
  47. package/dist/svelte/__tests__/ObjectForm.test.js +687 -0
  48. package/dist/svelte/__tests__/ObjectFormGeneratedApi.integration.test.js +436 -0
  49. package/dist/svelte/__tests__/PolicyField.test.js +373 -0
  50. package/dist/svelte/__tests__/UsageLearning.test.js +140 -0
  51. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte +30 -0
  52. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts +9 -0
  53. package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts.map +1 -0
  54. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte +33 -0
  55. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts +10 -0
  56. package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts.map +1 -0
  57. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte +19 -0
  58. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts +10 -0
  59. package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts.map +1 -0
  60. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte +18 -0
  61. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts +11 -0
  62. package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts.map +1 -0
  63. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte +11 -0
  64. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts +19 -0
  65. package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts.map +1 -0
  66. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte +18 -0
  67. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts +8 -0
  68. package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts.map +1 -0
  69. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte +24 -0
  70. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts +13 -0
  71. package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts.map +1 -0
  72. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte +37 -0
  73. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts +15 -0
  74. package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts.map +1 -0
  75. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte +17 -0
  76. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts +10 -0
  77. package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts.map +1 -0
  78. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte +25 -0
  79. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts +10 -0
  80. package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts.map +1 -0
  81. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte +34 -0
  82. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts +10 -0
  83. package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts.map +1 -0
  84. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte +29 -0
  85. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts +11 -0
  86. package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts.map +1 -0
  87. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte +33 -0
  88. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts +9 -0
  89. package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts.map +1 -0
  90. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte +40 -0
  91. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts +8 -0
  92. package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts.map +1 -0
  93. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte +26 -0
  94. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts +13 -0
  95. package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts.map +1 -0
  96. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte +39 -0
  97. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts +17 -0
  98. package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts.map +1 -0
  99. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte +15 -0
  100. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts +5 -0
  101. package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts.map +1 -0
  102. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte +29 -0
  103. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts +14 -0
  104. package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts.map +1 -0
  105. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte +24 -0
  106. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts +14 -0
  107. package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts.map +1 -0
  108. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte +28 -0
  109. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts +13 -0
  110. package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts.map +1 -0
  111. package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte +23 -0
  112. package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts +10 -0
  113. package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts.map +1 -0
  114. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte +14 -0
  115. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts +19 -0
  116. package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts.map +1 -0
  117. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte +25 -0
  118. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts +14 -0
  119. package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts.map +1 -0
  120. package/dist/svelte/__tests__/settings-catalog.test.js +161 -0
  121. package/dist/svelte/components/AdvancedFields.svelte +49 -0
  122. package/dist/svelte/components/AdvancedFields.svelte.d.ts +17 -0
  123. package/dist/svelte/components/AdvancedFields.svelte.d.ts.map +1 -0
  124. package/dist/svelte/components/FieldInput.svelte +159 -0
  125. package/dist/svelte/components/FieldInput.svelte.d.ts +5 -0
  126. package/dist/svelte/components/FieldInput.svelte.d.ts.map +1 -0
  127. package/dist/svelte/components/FieldPolicyControlPanel.svelte +288 -0
  128. package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts +29 -0
  129. package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts.map +1 -0
  130. package/dist/svelte/components/FieldPolicyEditor.svelte +417 -0
  131. package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts +18 -0
  132. package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts.map +1 -0
  133. package/dist/svelte/components/FieldPolicyGearButton.svelte +24 -0
  134. package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts +8 -0
  135. package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts.map +1 -0
  136. package/dist/svelte/components/FieldPolicyGearProvider.svelte +169 -0
  137. package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts +20 -0
  138. package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts.map +1 -0
  139. package/dist/svelte/components/FieldPolicyProvider.svelte +89 -0
  140. package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts +35 -0
  141. package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts.map +1 -0
  142. package/dist/svelte/components/FieldPolicySuggestionQueue.svelte +135 -0
  143. package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts +11 -0
  144. package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts.map +1 -0
  145. package/dist/svelte/components/FormHelp.svelte +157 -0
  146. package/dist/svelte/components/FormHelp.svelte.d.ts +15 -0
  147. package/dist/svelte/components/FormHelp.svelte.d.ts.map +1 -0
  148. package/dist/svelte/components/ModeSwitch.svelte +47 -0
  149. package/dist/svelte/components/ModeSwitch.svelte.d.ts +10 -0
  150. package/dist/svelte/components/ModeSwitch.svelte.d.ts.map +1 -0
  151. package/dist/svelte/components/ObjectForm.svelte +440 -0
  152. package/dist/svelte/components/ObjectForm.svelte.d.ts +50 -0
  153. package/dist/svelte/components/ObjectForm.svelte.d.ts.map +1 -0
  154. package/dist/svelte/components/ObjectFormSourceProvider.svelte +18 -0
  155. package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts +11 -0
  156. package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts.map +1 -0
  157. package/dist/svelte/components/PolicyField.svelte +338 -0
  158. package/dist/svelte/components/PolicyField.svelte.d.ts +80 -0
  159. package/dist/svelte/components/PolicyField.svelte.d.ts.map +1 -0
  160. package/dist/svelte/context.svelte.d.ts +54 -0
  161. package/dist/svelte/context.svelte.d.ts.map +1 -0
  162. package/dist/svelte/context.svelte.js +55 -0
  163. package/dist/svelte/data-table.d.ts +17 -0
  164. package/dist/svelte/data-table.d.ts.map +1 -0
  165. package/dist/svelte/data-table.js +19 -0
  166. package/dist/svelte/field-policy-editor.d.ts +81 -0
  167. package/dist/svelte/field-policy-editor.d.ts.map +1 -0
  168. package/dist/svelte/field-policy-editor.js +120 -0
  169. package/dist/svelte/gear-context.svelte.d.ts +16 -0
  170. package/dist/svelte/gear-context.svelte.d.ts.map +1 -0
  171. package/dist/svelte/gear-context.svelte.js +15 -0
  172. package/dist/svelte/index.d.ts +63 -0
  173. package/dist/svelte/index.d.ts.map +1 -0
  174. package/dist/svelte/index.js +41 -0
  175. package/dist/svelte/input-registry.d.ts +21 -0
  176. package/dist/svelte/input-registry.d.ts.map +1 -0
  177. package/dist/svelte/input-registry.js +26 -0
  178. package/dist/svelte/object-form-source-context.svelte.d.ts +4 -0
  179. package/dist/svelte/object-form-source-context.svelte.d.ts.map +1 -0
  180. package/dist/svelte/object-form-source-context.svelte.js +8 -0
  181. package/dist/svelte/object-form-source.svelte.d.ts +18 -0
  182. package/dist/svelte/object-form-source.svelte.d.ts.map +1 -0
  183. package/dist/svelte/object-form-source.svelte.js +99 -0
  184. package/dist/svelte/object-form.d.ts +6 -0
  185. package/dist/svelte/object-form.d.ts.map +1 -0
  186. package/dist/svelte/object-form.js +38 -0
  187. package/dist/svelte/playground/FieldPolicyFormPreview.svelte +212 -0
  188. package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts +4 -0
  189. package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts.map +1 -0
  190. package/dist/svelte/playground/ObjectFormPreview.svelte +129 -0
  191. package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts +4 -0
  192. package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts.map +1 -0
  193. package/dist/svelte/playground.d.ts +20 -0
  194. package/dist/svelte/playground.d.ts.map +1 -0
  195. package/dist/svelte/playground.js +38 -0
  196. package/dist/svelte/settings-catalog.d.ts +54 -0
  197. package/dist/svelte/settings-catalog.d.ts.map +1 -0
  198. package/dist/svelte/settings-catalog.js +116 -0
  199. package/dist/svelte/suggestions.d.ts +38 -0
  200. package/dist/svelte/suggestions.d.ts.map +1 -0
  201. package/dist/svelte/suggestions.js +68 -0
  202. package/dist/svelte/types.d.ts +101 -0
  203. package/dist/svelte/types.d.ts.map +1 -0
  204. package/dist/svelte/types.js +4 -0
  205. package/dist/svelte/usage-capture.d.ts +63 -0
  206. package/dist/svelte/usage-capture.d.ts.map +1 -0
  207. package/dist/svelte/usage-capture.js +61 -0
  208. package/dist/types.d.ts +316 -203
  209. package/dist/types.d.ts.map +1 -0
  210. package/dist/types.js +7 -1
  211. package/dist/types.js.map +1 -1
  212. package/dist/usage-learning.d.ts +201 -0
  213. package/dist/usage-learning.d.ts.map +1 -0
  214. package/dist/usage-schedules.d.ts +110 -0
  215. package/dist/usage-schedules.d.ts.map +1 -0
  216. package/dist/users-module.d.ts +30 -0
  217. package/dist/users-module.d.ts.map +1 -0
  218. package/package.json +33 -8
@@ -0,0 +1,440 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ObjectForm renders the safe intersection of a generated web definition and
4
+ * its resolved field policy. It remains transport-neutral: callers own the
5
+ * record and submission, while this component owns ordering, policy modes,
6
+ * built-in wire-type adapters, and accessible field layout.
7
+ */
8
+ import { Form } from '@happyvertical/smrt-ui/forms';
9
+ import type { Component, Snippet } from 'svelte';
10
+ import type { ResolvedObjectFieldPolicy } from '../../types.js';
11
+ import { tryGetFieldPolicyGearContext } from '../gear-context.svelte.js';
12
+ import type {
13
+ FieldInputComponent,
14
+ FieldInputRegistry,
15
+ } from '../input-registry.js';
16
+ import { resolveObjectFormFields } from '../object-form.js';
17
+ import { tryGetObjectFormSource } from '../object-form-source-context.svelte.js';
18
+ import {
19
+ type FieldInputProps,
20
+ type ObjectFormField,
21
+ type ObjectFormFieldDefinition,
22
+ type ObjectFormFieldSnippetProps,
23
+ } from '../types.js';
24
+ import {
25
+ collectFieldUsageEntries,
26
+ type FieldUsageReporter,
27
+ reportFieldUsage,
28
+ } from '../usage-capture.js';
29
+ import AdvancedFields from './AdvancedFields.svelte';
30
+ import FieldInput from './FieldInput.svelte';
31
+ import FieldPolicyGearButton from './FieldPolicyGearButton.svelte';
32
+ import FieldPolicyProvider from './FieldPolicyProvider.svelte';
33
+ import ModeSwitch from './ModeSwitch.svelte';
34
+ import PolicyField from './PolicyField.svelte';
35
+
36
+ export interface ObjectFormProps {
37
+ objectRef: string;
38
+ /**
39
+ * Generated browser-safe definitions — never pass raw server registry fields.
40
+ * Omit with `policy` to resolve both through ObjectFormSourceProvider.
41
+ */
42
+ fields?: Readonly<Record<string, ObjectFormFieldDefinition>>;
43
+ /** Pre-resolved policy for the same objectRef. Omit with `fields` for a source. */
44
+ policy?: ResolvedObjectFieldPolicy;
45
+ /** Mutable create/edit record. Bind this prop to retain values in the host. */
46
+ value?: Record<string, unknown>;
47
+ /** True for creates (defaults may prefill), false for loaded edits. */
48
+ isNewRecord?: boolean;
49
+ /**
50
+ * Optional identity for a create session. Change this when reusing a mounted
51
+ * form for another new record; defaults then apply once to missing values.
52
+ * With no key, replacing the bound record with an empty object starts a new
53
+ * create session automatically.
54
+ */
55
+ createSessionKey?: string | number;
56
+ disabled?: boolean;
57
+ showModeSwitch?: boolean;
58
+ /** Render a provider-registered policy gear next to the mode control. */
59
+ showPolicyGear?: boolean;
60
+ inputRegistry?: FieldInputRegistry;
61
+ /** Per-field custom widget snippets, keyed by field name. */
62
+ renderers?: Readonly<Record<string, Snippet<[ObjectFormFieldSnippetProps]>>>;
63
+ /** Optional controls rendered after the policy-ordered fields inside the form. */
64
+ actions?: Snippet;
65
+ /**
66
+ * Optional telemetry transport. It runs only when `onsubmit` explicitly
67
+ * resolves `true` after persistence; identity and field sensitivity remain
68
+ * server-derived.
69
+ */
70
+ usageReporter?: FieldUsageReporter;
71
+ class?: string;
72
+ /** Return true only after the host has persisted this record successfully. */
73
+ onsubmit?: ObjectFormSubmitHandler;
74
+ }
75
+
76
+ // biome-ignore lint/suspicious/noConfusingVoidType: void handlers remain a supported legacy integration and never acknowledge persistence.
77
+ export type ObjectFormSubmitAcknowledgement = boolean | void;
78
+ export type ObjectFormSubmitHandler = (
79
+ event: SubmitEvent,
80
+ ) => ObjectFormSubmitAcknowledgement | Promise<ObjectFormSubmitAcknowledgement>;
81
+
82
+ let {
83
+ objectRef,
84
+ fields = undefined,
85
+ policy = undefined,
86
+ value = $bindable<Record<string, unknown>>({}),
87
+ isNewRecord = true,
88
+ createSessionKey,
89
+ disabled = false,
90
+ showModeSwitch = true,
91
+ showPolicyGear = false,
92
+ inputRegistry,
93
+ renderers = {},
94
+ actions,
95
+ usageReporter,
96
+ class: className = '',
97
+ onsubmit,
98
+ }: ObjectFormProps = $props();
99
+
100
+ const fieldPolicyGear = tryGetFieldPolicyGearContext();
101
+ const objectFormSource = tryGetObjectFormSource();
102
+ const formInstanceId = $props.id();
103
+ let invalidFields = $state<Set<string>>(new Set());
104
+
105
+ let sourceResult = $state<{
106
+ fields: Readonly<Record<string, ObjectFormFieldDefinition>>;
107
+ policy: ResolvedObjectFieldPolicy;
108
+ } | null>(null);
109
+ let sourceError = $state<string | null>(null);
110
+ let sourceLoading = $state(false);
111
+
112
+ // Direct props preserve SSR and existing integrations. The provider path makes
113
+ // an app's generated definitions + resolveBatch transport reusable across all
114
+ // objects without per-form glue.
115
+ $effect(() => {
116
+ if (fields !== undefined || policy !== undefined) {
117
+ sourceLoading = false;
118
+ sourceResult = null;
119
+ if (fields === undefined || policy === undefined) {
120
+ sourceError =
121
+ 'ObjectForm requires both fields and policy when direct props are used.';
122
+ } else if (policy.objectRef !== objectRef) {
123
+ sourceError = `ObjectForm policy does not match '${objectRef}'.`;
124
+ } else {
125
+ sourceError = null;
126
+ }
127
+ return;
128
+ }
129
+ if (!objectFormSource) {
130
+ sourceLoading = false;
131
+ sourceResult = null;
132
+ sourceError = `No ObjectForm source is available for '${objectRef}'.`;
133
+ return;
134
+ }
135
+
136
+ let active = true;
137
+ sourceLoading = true;
138
+ sourceResult = null;
139
+ sourceError = null;
140
+ void objectFormSource
141
+ .load(objectRef)
142
+ .then((resolved) => {
143
+ if (!active) return;
144
+ if (
145
+ resolved.definition.objectRef !== objectRef ||
146
+ resolved.policy.objectRef !== objectRef
147
+ ) {
148
+ throw new Error(
149
+ `ObjectForm source returned a mismatched objectRef for '${objectRef}'.`,
150
+ );
151
+ }
152
+ sourceResult = {
153
+ fields: resolved.definition.fields,
154
+ policy: resolved.policy,
155
+ };
156
+ })
157
+ .catch((error: unknown) => {
158
+ if (active) {
159
+ sourceError =
160
+ error instanceof Error ? error.message : 'Unable to load this form.';
161
+ }
162
+ })
163
+ .finally(() => {
164
+ if (active) sourceLoading = false;
165
+ });
166
+ return () => {
167
+ active = false;
168
+ };
169
+ });
170
+
171
+ const resolvedFields = $derived(fields ?? sourceResult?.fields ?? {});
172
+ const resolvedPolicy = $derived(policy ?? sourceResult?.policy ?? null);
173
+
174
+ const formFields = $derived(
175
+ resolvedPolicy ? resolveObjectFormFields(resolvedFields, resolvedPolicy) : [],
176
+ );
177
+ const basicGroups = $derived(groupFields('basic'));
178
+ const advancedGroups = $derived(groupFields('advanced'));
179
+
180
+ function groupFields(
181
+ visibility: 'basic' | 'advanced',
182
+ ): Array<[string | null, ObjectFormField[]]> {
183
+ const result = new Map<string | null, ObjectFormField[]>();
184
+ if (!resolvedPolicy) return [];
185
+ for (const field of formFields) {
186
+ if (resolvedPolicy.fields[field.name].visibility !== visibility) continue;
187
+ const group = result.get(field.group) ?? [];
188
+ group.push(field);
189
+ result.set(field.group, group);
190
+ }
191
+ return [...result.entries()];
192
+ }
193
+
194
+ function setValue(name: string, next: unknown): void {
195
+ const updated = { ...value, [name]: next };
196
+ lastInternalValue = updated;
197
+ lastObservedValue = updated;
198
+ value = updated;
199
+ }
200
+
201
+ /** Generated fields need a stable accessible label even when policy omits one. */
202
+ function fieldLabel(field: ObjectFormField): string {
203
+ return (
204
+ resolvedPolicy?.fields[field.name].label ??
205
+ field.name
206
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
207
+ .replace(/[-_]+/g, ' ')
208
+ .replace(/^./, (character) => character.toUpperCase())
209
+ );
210
+ }
211
+
212
+ function setValidity(name: string, valid: boolean): void {
213
+ const next = new Set(invalidFields);
214
+ if (valid) next.delete(name);
215
+ else next.add(name);
216
+ invalidFields = next;
217
+ }
218
+
219
+ function fieldId(name: string): string {
220
+ return `${formInstanceId}-${name}`;
221
+ }
222
+
223
+ function captureUsage(values: Readonly<Record<string, unknown>>): void {
224
+ if (!resolvedPolicy) return;
225
+ reportFieldUsage(
226
+ usageReporter,
227
+ collectFieldUsageEntries({
228
+ objectRef,
229
+ values,
230
+ // Hidden fields are not submitted user inputs. Basic and advanced fields
231
+ // are included even when a value matches the default so the server's
232
+ // dominance calculation has a complete denominator.
233
+ fields: Object.fromEntries(
234
+ formFields
235
+ .filter(
236
+ (field) =>
237
+ resolvedPolicy.fields[field.name].visibility !== 'hidden',
238
+ )
239
+ .map((field) => [field.name, field.definition.type]),
240
+ ),
241
+ defaults: Object.fromEntries(
242
+ formFields
243
+ .filter(
244
+ (field) =>
245
+ resolvedPolicy.fields[field.name].visibility !== 'hidden',
246
+ )
247
+ .map((field) => {
248
+ const resolved = resolvedPolicy.fields[field.name];
249
+ return [
250
+ field.name,
251
+ {
252
+ hasDefault: resolved.hasDefault,
253
+ defaultValue: resolved.defaultValue,
254
+ },
255
+ ];
256
+ }),
257
+ ),
258
+ }),
259
+ );
260
+ }
261
+
262
+ async function handleSubmit(event: SubmitEvent): Promise<void> {
263
+ if (invalidFields.size > 0) {
264
+ event.preventDefault();
265
+ return;
266
+ }
267
+ if (!onsubmit) return;
268
+ // A successful create commonly replaces the bound record before its
269
+ // persistence promise resolves. Snapshot before awaiting so usage describes
270
+ // the fields that were submitted, not the newly cleared next-record state.
271
+ const submittedValues = snapshotUsageValues(value);
272
+ try {
273
+ // Form prevents native navigation itself, so persistence acknowledgement
274
+ // must be explicit rather than inferred from event.defaultPrevented.
275
+ if ((await onsubmit(event)) === true) captureUsage(submittedValues);
276
+ } catch {
277
+ // The host owns persistence errors; rejected submissions never emit usage.
278
+ }
279
+ }
280
+
281
+ function snapshotUsageValues(
282
+ record: Readonly<Record<string, unknown>>,
283
+ ): Record<string, unknown> {
284
+ try {
285
+ return structuredClone(record);
286
+ } catch {
287
+ // Generated form values are wire-safe, but retain a shallow fallback for
288
+ // host extensions that add an uncloneable value outside telemetry fields.
289
+ return { ...record };
290
+ }
291
+ }
292
+
293
+ // Apply policy defaults to a create record once, before a user can interact
294
+ // with any control. This preserves JSON and boolean defaults (which cannot be
295
+ // faithfully inferred from generic DOM string assignment) and means callers
296
+ // receive a complete create payload. Loaded edit records never enter this path.
297
+ let initializedCreateSession = false;
298
+ let previousCreateSessionKey: string | number | undefined;
299
+ let previousIsNewRecord = false;
300
+ let lastObservedValue: Record<string, unknown> | undefined;
301
+ let lastInternalValue: Record<string, unknown> | undefined;
302
+
303
+ function isEmptyRecord(record: Record<string, unknown>): boolean {
304
+ return Object.keys(record).length === 0;
305
+ }
306
+
307
+ $effect(() => {
308
+ const currentValue = value;
309
+ if (!resolvedPolicy || !isNewRecord) {
310
+ previousIsNewRecord = isNewRecord;
311
+ lastObservedValue = currentValue;
312
+ return;
313
+ }
314
+
315
+ const explicitNewSession =
316
+ createSessionKey !== undefined &&
317
+ createSessionKey !== previousCreateSessionKey;
318
+ const resetCreateSession =
319
+ initializedCreateSession &&
320
+ !previousIsNewRecord &&
321
+ isEmptyRecord(currentValue);
322
+ const externalEmptyReset =
323
+ initializedCreateSession &&
324
+ currentValue !== lastObservedValue &&
325
+ currentValue !== lastInternalValue &&
326
+ isEmptyRecord(currentValue);
327
+ const shouldApplyDefaults =
328
+ !initializedCreateSession ||
329
+ explicitNewSession ||
330
+ resetCreateSession ||
331
+ (createSessionKey === undefined && externalEmptyReset);
332
+
333
+ if (!shouldApplyDefaults) {
334
+ previousIsNewRecord = true;
335
+ previousCreateSessionKey = createSessionKey;
336
+ lastObservedValue = currentValue;
337
+ return;
338
+ }
339
+
340
+ const next = { ...currentValue };
341
+ let changed = false;
342
+ for (const field of formFields) {
343
+ const policyField = resolvedPolicy.fields[field.name];
344
+ if (policyField.hasDefault && next[field.name] === undefined) {
345
+ next[field.name] = policyField.defaultValue;
346
+ changed = true;
347
+ }
348
+ }
349
+ initializedCreateSession = true;
350
+ previousIsNewRecord = true;
351
+ previousCreateSessionKey = createSessionKey;
352
+ if (changed) {
353
+ lastInternalValue = next;
354
+ lastObservedValue = next;
355
+ value = next;
356
+ } else {
357
+ lastObservedValue = currentValue;
358
+ }
359
+ });
360
+
361
+ function inputFor(field: ObjectFormField): FieldInputComponent {
362
+ return (
363
+ inputRegistry?.resolve(objectRef, field.name, field.definition.type) ??
364
+ (FieldInput as unknown as Component<FieldInputProps>)
365
+ );
366
+ }
367
+ </script>
368
+
369
+ {#snippet renderField(field: ObjectFormField)}
370
+ {@const renderer = renderers[field.name]}
371
+ <PolicyField name={field.name} label={fieldLabel(field)} id={fieldId(field.name)} helpId={`${fieldId(field.name)}-help`} {isNewRecord}>
372
+ {#if renderer}
373
+ {@render renderer({ field, id: fieldId(field.name), helpId: `${fieldId(field.name)}-help`, value: value[field.name], required: resolvedPolicy!.fields[field.name].required, disabled, setValue: (next) => setValue(field.name, next), setValidity: (valid) => setValidity(field.name, valid) })}
374
+ {:else}
375
+ {@const InputComponent = inputFor(field)}
376
+ <InputComponent
377
+ id={fieldId(field.name)}
378
+ name={field.name}
379
+ label={fieldLabel(field)}
380
+ {field}
381
+ value={value[field.name]}
382
+ required={resolvedPolicy!.fields[field.name].required}
383
+ {disabled}
384
+ onvaluechange={(next) => setValue(field.name, next)}
385
+ onvaliditychange={(valid) => setValidity(field.name, valid)}
386
+ />
387
+ {/if}
388
+ </PolicyField>
389
+ {/snippet}
390
+
391
+ {#snippet renderGroups(groups: Array<[string | null, ObjectFormField[]]>)}
392
+ {#each groups as [groupName, groupFields] (groupName ?? '__ungrouped__')}
393
+ {#if groupName}
394
+ <fieldset class="object-form__group">
395
+ <legend>{groupName}</legend>
396
+ {#each groupFields as field (field.name)}
397
+ {@render renderField(field)}
398
+ {/each}
399
+ </fieldset>
400
+ {:else}
401
+ {#each groupFields as field (field.name)}
402
+ {@render renderField(field)}
403
+ {/each}
404
+ {/if}
405
+ {/each}
406
+ {/snippet}
407
+
408
+ {#if sourceLoading}
409
+ <div class="object-form__status" role="status" aria-live="polite">Loading form…</div>
410
+ {:else if sourceError}
411
+ <div class="object-form__status" role="alert">{sourceError}</div>
412
+ {:else if resolvedPolicy}
413
+ <FieldPolicyProvider policy={resolvedPolicy}>
414
+ {#if showModeSwitch || (showPolicyGear && fieldPolicyGear)}
415
+ {#if showModeSwitch}
416
+ <div class="object-form__mode"><ModeSwitch /></div>
417
+ {/if}
418
+ {#if showPolicyGear && fieldPolicyGear}
419
+ <div class="object-form__gear"><FieldPolicyGearButton /></div>
420
+ {/if}
421
+ {/if}
422
+ <Form class="object-form {className}" onsubmit={handleSubmit}>
423
+ {@render renderGroups(basicGroups)}
424
+ {#if advancedGroups.length > 0}
425
+ <AdvancedFields open>
426
+ {@render renderGroups(advancedGroups)}
427
+ </AdvancedFields>
428
+ {/if}
429
+ {@render actions?.()}
430
+ </Form>
431
+ </FieldPolicyProvider>
432
+ {/if}
433
+
434
+ <style>
435
+ :global(.object-form) { display: grid; gap: var(--smrt-spacing-4, 1rem); }
436
+ .object-form__mode, .object-form__gear { display: inline-flex; justify-content: flex-end; margin-bottom: var(--smrt-spacing-3, 0.75rem); }
437
+ .object-form__group { display: grid; gap: var(--smrt-spacing-3, 0.75rem); border: 0; margin: 0; min-inline-size: 0; padding: 0; }
438
+ .object-form__group legend { font: var(--smrt-typography-title-small-font, inherit); margin-bottom: var(--smrt-spacing-2, 0.5rem); }
439
+ .object-form__status { color: var(--smrt-color-on-surface-variant, currentColor); }
440
+ </style>
@@ -0,0 +1,50 @@
1
+ import type { Component, Snippet } from 'svelte';
2
+ import type { ResolvedObjectFieldPolicy } from '../../types.js';
3
+ import type { FieldInputRegistry } from '../input-registry.js';
4
+ import { type ObjectFormFieldDefinition, type ObjectFormFieldSnippetProps } from '../types.js';
5
+ import { type FieldUsageReporter } from '../usage-capture.js';
6
+ export type ObjectFormSubmitAcknowledgement = boolean | void;
7
+ export type ObjectFormSubmitHandler = (event: SubmitEvent) => ObjectFormSubmitAcknowledgement | Promise<ObjectFormSubmitAcknowledgement>;
8
+ export interface ObjectFormProps {
9
+ objectRef: string;
10
+ /**
11
+ * Generated browser-safe definitions — never pass raw server registry fields.
12
+ * Omit with `policy` to resolve both through ObjectFormSourceProvider.
13
+ */
14
+ fields?: Readonly<Record<string, ObjectFormFieldDefinition>>;
15
+ /** Pre-resolved policy for the same objectRef. Omit with `fields` for a source. */
16
+ policy?: ResolvedObjectFieldPolicy;
17
+ /** Mutable create/edit record. Bind this prop to retain values in the host. */
18
+ value?: Record<string, unknown>;
19
+ /** True for creates (defaults may prefill), false for loaded edits. */
20
+ isNewRecord?: boolean;
21
+ /**
22
+ * Optional identity for a create session. Change this when reusing a mounted
23
+ * form for another new record; defaults then apply once to missing values.
24
+ * With no key, replacing the bound record with an empty object starts a new
25
+ * create session automatically.
26
+ */
27
+ createSessionKey?: string | number;
28
+ disabled?: boolean;
29
+ showModeSwitch?: boolean;
30
+ /** Render a provider-registered policy gear next to the mode control. */
31
+ showPolicyGear?: boolean;
32
+ inputRegistry?: FieldInputRegistry;
33
+ /** Per-field custom widget snippets, keyed by field name. */
34
+ renderers?: Readonly<Record<string, Snippet<[ObjectFormFieldSnippetProps]>>>;
35
+ /** Optional controls rendered after the policy-ordered fields inside the form. */
36
+ actions?: Snippet;
37
+ /**
38
+ * Optional telemetry transport. It runs only when `onsubmit` explicitly
39
+ * resolves `true` after persistence; identity and field sensitivity remain
40
+ * server-derived.
41
+ */
42
+ usageReporter?: FieldUsageReporter;
43
+ class?: string;
44
+ /** Return true only after the host has persisted this record successfully. */
45
+ onsubmit?: ObjectFormSubmitHandler;
46
+ }
47
+ declare const ObjectForm: Component<ObjectFormProps, {}, "value">;
48
+ type ObjectForm = ReturnType<typeof ObjectForm>;
49
+ export default ObjectForm;
50
+ //# sourceMappingURL=ObjectForm.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ObjectForm.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EACjC,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,qBAAqB,CAAC;AAU7B,MAAM,MAAM,+BAA+B,GAAG,OAAO,GAAG,IAAI,CAAC;AAC7D,MAAM,MAAM,uBAAuB,GAAG,CACpC,KAAK,EAAE,WAAW,KACf,+BAA+B,GAAG,OAAO,CAAC,+BAA+B,CAAC,CAAC;AAEhF,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAC7D,mFAAmF;IACnF,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,yEAAyE;IACzE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,6DAA6D;IAC7D,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7E,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,uBAAuB,CAAC;CACpC;AAgXD,QAAA,MAAM,UAAU,yCAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ /** Provides one app-local generated ObjectForm source to descendant forms. */
3
+ import type { Snippet } from 'svelte';
4
+ import { setObjectFormSource } from '../object-form-source-context.svelte.js';
5
+ import type { ObjectFormSource } from '../types.js';
6
+
7
+ interface Props {
8
+ source: ObjectFormSource;
9
+ children?: Snippet;
10
+ }
11
+
12
+ let { source, children }: Props = $props();
13
+ // Context is established once, but the wrapper reads the current app source
14
+ // when a descendant loads so a host may replace its transport configuration.
15
+ setObjectFormSource({ load: (objectRef) => source.load(objectRef) });
16
+ </script>
17
+
18
+ {@render children?.()}
@@ -0,0 +1,11 @@
1
+ /** Provides one app-local generated ObjectForm source to descendant forms. */
2
+ import type { Snippet } from 'svelte';
3
+ import type { ObjectFormSource } from '../types.js';
4
+ interface Props {
5
+ source: ObjectFormSource;
6
+ children?: Snippet;
7
+ }
8
+ declare const ObjectFormSourceProvider: import("svelte").Component<Props, {}, "">;
9
+ type ObjectFormSourceProvider = ReturnType<typeof ObjectFormSourceProvider>;
10
+ export default ObjectFormSourceProvider;
11
+ //# sourceMappingURL=ObjectFormSourceProvider.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectFormSourceProvider.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ObjectFormSourceProvider.svelte.ts"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,UAAU,KAAK;IACb,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAkBD,QAAA,MAAM,wBAAwB,2CAAwC,CAAC;AACvE,KAAK,wBAAwB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC5E,eAAe,wBAAwB,CAAC"}