attaform 0.30.1 → 0.31.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 (55) hide show
  1. package/dist/abstract.d.mts +5 -5
  2. package/dist/abstract.mjs +4 -4
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/devtools.mjs +3 -3
  5. package/dist/dev/directive.mjs +1 -1
  6. package/dist/dev/history.mjs +15 -2
  7. package/dist/dev/index.mjs +5 -5
  8. package/dist/dev/runtime/plugins/attaform.mjs +1 -1
  9. package/dist/dev/shared/{attaform-dev.zlvDr0WB.mjs → attaform-dev.04aupFBW.mjs} +3 -2
  10. package/dist/dev/shared/{attaform-dev.DxHdkkPG.mjs → attaform-dev.3uTCYwy8.mjs} +770 -254
  11. package/dist/dev/shared/{attaform-dev.BJK1kqHY.mjs → attaform-dev.BpVGFSFa.mjs} +88 -115
  12. package/dist/dev/shared/{attaform-dev.Chm4D6SJ.mjs → attaform-dev.Bu6wgrA5.mjs} +3 -3
  13. package/dist/dev/shared/{attaform-dev.BMQ3GcgD.mjs → attaform-dev.Cbx0AiQF.mjs} +476 -337
  14. package/dist/dev/shared/attaform-dev.CwYTWyPX.mjs +120 -0
  15. package/dist/dev/shared/{attaform-dev.DuEE4GU1.mjs → attaform-dev.D7lKyM_7.mjs} +4 -4
  16. package/dist/dev/shared/{attaform-dev.CT7MDjPy.mjs → attaform-dev.OceE0X3v.mjs} +154 -229
  17. package/dist/{shared/attaform.DRsANMok.mjs → dev/shared/attaform-dev.PTGM9KE1.mjs} +16 -15
  18. package/dist/{shared/attaform.8mfKZCwH.mjs → dev/shared/attaform-dev.n6n00owJ.mjs} +49 -128
  19. package/dist/dev/zod-v3.mjs +6 -6
  20. package/dist/dev/zod-v4.mjs +6 -6
  21. package/dist/dev/zod.mjs +5 -5
  22. package/dist/directive.d.mts +2 -2
  23. package/dist/directive.mjs +1 -1
  24. package/dist/history.d.mts +1 -1
  25. package/dist/history.mjs +15 -2
  26. package/dist/index.d.mts +16 -55
  27. package/dist/index.mjs +5 -5
  28. package/dist/runtime/components/AttaformDevtoolsPanel.vue +3 -3
  29. package/dist/runtime/plugins/attaform.mjs +1 -1
  30. package/dist/shared/{attaform.CO918TdU.mjs → attaform.B5b-HmhH.mjs} +88 -115
  31. package/dist/shared/{attaform.2FslhB9R.d.mts → attaform.B9PYKlhB.d.mts} +43 -19
  32. package/dist/shared/{attaform.D_f1WgYf.mjs → attaform.BPMOl562.mjs} +458 -319
  33. package/dist/{dev/shared/attaform-dev.CZz4GYZ6.mjs → shared/attaform.BPSqtU7a.mjs} +44 -156
  34. package/dist/shared/{attaform.CdZIEf7_.mjs → attaform.BhtAZysa.mjs} +3 -2
  35. package/dist/shared/{attaform.BcQwgWaq.d.mts → attaform.CYlF4P6X.d.mts} +58 -17
  36. package/dist/shared/{attaform.WiGdIqgx.mjs → attaform.CdA9bfRv.mjs} +3 -3
  37. package/dist/shared/{attaform.BIWicztt.mjs → attaform.DAf4ehIe.mjs} +765 -251
  38. package/dist/shared/{attaform.ZYn2ahzJ.mjs → attaform.DBCbxlyk.mjs} +59 -1
  39. package/dist/shared/{attaform.L0kUn3vL.d.mts → attaform.DZOynI6c.d.mts} +1 -1
  40. package/dist/shared/{attaform.B2iebkPA.mjs → attaform.KEfNRDot.mjs} +4 -4
  41. package/dist/shared/attaform._1Zp3mDG.mjs +96 -0
  42. package/dist/shared/{attaform.Br8EBn5B.d.mts → attaform.i13OEHJF.d.mts} +195 -38
  43. package/dist/{dev/shared/attaform-dev.BUIFcTYf.mjs → shared/attaform.j9xI_ObC.mjs} +16 -15
  44. package/dist/shared/{attaform.D_pt1gOj.mjs → attaform.u6jfHoed.mjs} +154 -229
  45. package/dist/transforms.mjs +1 -1
  46. package/dist/vite.mjs +1 -1
  47. package/dist/zod-v3.d.mts +4 -4
  48. package/dist/zod-v3.mjs +6 -6
  49. package/dist/zod-v4.d.mts +22 -16
  50. package/dist/zod-v4.mjs +6 -6
  51. package/dist/zod.d.mts +4 -4
  52. package/dist/zod.mjs +5 -5
  53. package/package.json +2 -1
  54. package/skills/attaform/SKILL.md +2 -1
  55. package/skills/attaform/references/custom-components.md +4 -0
@@ -119,6 +119,20 @@ const NATIVE_FORM_TAGS = /* @__PURE__ */ new Set([
119
119
  "label",
120
120
  "button"
121
121
  ]);
122
+ const HOST_MODIFIERS = ["lazy", "trim", "number"];
123
+ function hostModifiersLiteral(registerDirective) {
124
+ const present = HOST_MODIFIERS.filter(
125
+ (name) => registerDirective?.modifiers.some((m) => m.content === name) === true
126
+ );
127
+ return present.length === 0 ? "{}" : `{ ${present.map((name) => `${name}: true`).join(", ")} }`;
128
+ }
129
+ function isInputListenerSlot(prop) {
130
+ if (prop.type !== NodeTypes.DIRECTIVE || prop.name !== "on" && prop.name !== "bind") {
131
+ return false;
132
+ }
133
+ if (prop.arg === void 0) return true;
134
+ return prop.arg.type === NodeTypes.SIMPLE_EXPRESSION && prop.arg.content === (prop.name === "on" ? "input" : "onInput");
135
+ }
122
136
  function inferOptionValueFromChildren(node) {
123
137
  if (!("children" in node)) return null;
124
138
  const children = node.children;
@@ -275,6 +289,11 @@ const componentBridgeTransform = (node, context) => {
275
289
  node.props.push(valueProp);
276
290
  }
277
291
  if (isPlainComponentHost) {
292
+ const hostModifiers = hostModifiersLiteral(
293
+ node.props.find(
294
+ (p) => p.type === NodeTypes.DIRECTIVE && p.name === "register"
295
+ )
296
+ );
278
297
  removePropsByName(node.props, [
279
298
  "model",
280
299
  "modelValue",
@@ -317,7 +336,7 @@ const componentBridgeTransform = (node, context) => {
317
336
  const updateInitExpression = createCompoundExpression([
318
337
  "$event => (",
319
338
  ...modelValuePropExpArray,
320
- ")?.setValueFromHost?.($event)"
339
+ `)?.setValueFromHost?.($event, ${hostModifiers})`
321
340
  ]);
322
341
  const updateSimpleExpression = createSimpleExpression(
323
342
  flattenExpression(updateInitExpression),
@@ -346,6 +365,45 @@ const componentBridgeTransform = (node, context) => {
346
365
  loc: selectLoc
347
366
  };
348
367
  node.props.push(updateModelValueProp);
368
+ const inputAlreadyInjected = node.props.some(
369
+ (p) => p.type === NodeTypes.DIRECTIVE && p.name === "bind" && p.arg?.type === NodeTypes.SIMPLE_EXPRESSION && p.arg.content === "onInput" && p.exp !== void 0 && flattenExpression(p.exp).includes("setValueFromHostInput")
370
+ );
371
+ if (!inputAlreadyInjected) {
372
+ const inputInitExpression = createCompoundExpression([
373
+ "$event => (",
374
+ ...modelValuePropExpArray,
375
+ `)?.setValueFromHostInput?.($event, ${hostModifiers})`
376
+ ]);
377
+ const inputSimpleExpression = createSimpleExpression(
378
+ flattenExpression(inputInitExpression),
379
+ false
380
+ );
381
+ let inputOutputExp;
382
+ try {
383
+ inputOutputExp = processExpression(inputSimpleExpression, {
384
+ ...context,
385
+ prefixIdentifiers: false
386
+ });
387
+ } catch (err) {
388
+ console.error(
389
+ "[attaform] component-bridge transform: processExpression failed for onInput; falling back to the unprocessed expression.",
390
+ err
391
+ );
392
+ inputOutputExp = inputSimpleExpression;
393
+ }
394
+ const inputProp = {
395
+ rawName: "@input",
396
+ arg: createSimpleExpression("onInput", true),
397
+ exp: inputOutputExp,
398
+ name: "bind",
399
+ modifiers: [],
400
+ type: NodeTypes.DIRECTIVE,
401
+ loc: selectLoc
402
+ };
403
+ const authorListenerIndex = node.props.findIndex(isInputListenerSlot);
404
+ if (authorListenerIndex === -1) node.props.push(inputProp);
405
+ else node.props.splice(authorListenerIndex, 0, inputProp);
406
+ }
349
407
  }
350
408
  const registerExprArray = Array.isArray(registerSummarizedProp?.value) ? registerSummarizedProp.value : [registerSummarizedProp?.value ?? "undefined"];
351
409
  const hasAuthorDisabled = selectSummarizedProps.findIndex((p) => isExactKey(p.key, "disabled")) !== -1;
@@ -1,5 +1,5 @@
1
1
  import { App } from 'vue';
2
- import { ao as RegisterModelDynamicCustomDirective } from './attaform.Br8EBn5B.mjs';
2
+ import { an as RegisterModelDynamicCustomDirective } from './attaform.i13OEHJF.mjs';
3
3
 
4
4
  /**
5
5
  * The `v-register` directive. Bind a form field to a native input, select,
@@ -1,7 +1,7 @@
1
- import { a as InvalidUseFormConfigError } from './attaform.CdZIEf7_.mjs';
2
- import { u as useForm$2 } from './attaform.D_pt1gOj.mjs';
3
- import { u as useForm$1 } from './attaform.CO918TdU.mjs';
4
- import { i as installingFieldMetaStore, g as getFieldMetaForSchema } from './attaform.DRsANMok.mjs';
1
+ import { a as InvalidUseFormConfigError } from './attaform.BhtAZysa.mjs';
2
+ import { u as useForm$2 } from './attaform.u6jfHoed.mjs';
3
+ import { u as useForm$1 } from './attaform.B5b-HmhH.mjs';
4
+ import { i as installingFieldMetaStore, g as getFieldMetaForSchema } from './attaform.j9xI_ObC.mjs';
5
5
 
6
6
  function isZodV4SchemaShape(value) {
7
7
  if (typeof value !== "object" || value === null) return false;
@@ -0,0 +1,96 @@
1
+ function callConsumerFn(fn, fallback, site, detail) {
2
+ try {
3
+ return fn();
4
+ } catch (err) {
5
+ return fallback;
6
+ }
7
+ }
8
+ function readConsumerProp(obj, key) {
9
+ try {
10
+ return obj[key];
11
+ } catch {
12
+ return void 0;
13
+ }
14
+ }
15
+ function consumerKeys(obj) {
16
+ try {
17
+ return Object.keys(obj);
18
+ } catch {
19
+ return [];
20
+ }
21
+ }
22
+ function readConsumerIndex(arr, i) {
23
+ try {
24
+ return arr[i];
25
+ } catch {
26
+ return void 0;
27
+ }
28
+ }
29
+ function consumerSymbolKeys(obj) {
30
+ try {
31
+ return Object.getOwnPropertySymbols(obj);
32
+ } catch {
33
+ return [];
34
+ }
35
+ }
36
+ function consumerHas(obj, key) {
37
+ try {
38
+ return key in obj;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ function safeAssign(target, key, value) {
45
+ if (key === "__proto__") {
46
+ Object.defineProperty(target, key, {
47
+ value,
48
+ writable: true,
49
+ enumerable: true,
50
+ configurable: true
51
+ });
52
+ return;
53
+ }
54
+ target[key] = value;
55
+ }
56
+ const SHADOWED_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));
57
+ function isShadowedKey(key) {
58
+ return SHADOWED_KEYS.has(key);
59
+ }
60
+ function safeOwnRead(target, key) {
61
+ if (isShadowedKey(key)) {
62
+ const desc = Object.getOwnPropertyDescriptor(target, key);
63
+ if (desc === void 0) return void 0;
64
+ return "value" in desc ? desc.value : readConsumerProp(target, key);
65
+ }
66
+ return readConsumerProp(target, key);
67
+ }
68
+ function safeOwnHas(target, key) {
69
+ try {
70
+ return Object.prototype.hasOwnProperty.call(target, key);
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+ function spreadConsumerRecord(src) {
76
+ try {
77
+ return { ...src };
78
+ } catch {
79
+ const out = {};
80
+ for (const key of Object.keys(src)) {
81
+ safeAssign(out, key, readConsumerProp(src, key));
82
+ }
83
+ return out;
84
+ }
85
+ }
86
+ function copyConsumerArray(src) {
87
+ try {
88
+ return src.slice();
89
+ } catch {
90
+ const out = new Array(src.length);
91
+ for (let i = 0; i < src.length; i++) out[i] = readConsumerIndex(src, i);
92
+ return out;
93
+ }
94
+ }
95
+
96
+ export { safeOwnRead as a, readConsumerProp as b, consumerKeys as c, callConsumerFn as d, safeOwnHas as e, consumerHas as f, spreadConsumerRecord as g, copyConsumerArray as h, isShadowedKey as i, consumerSymbolKeys as j, readConsumerIndex as r, safeAssign as s };
@@ -215,6 +215,25 @@ type OriginalsRecord = {
215
215
  readonly segments: Path;
216
216
  readonly value: unknown;
217
217
  };
218
+ /** Every tracked array's identity tokens and baseline order, by array key. */
219
+ type IdentityOrders = {
220
+ readonly tokens: ReadonlyMap<PathKey, readonly string[]>;
221
+ readonly baselines: ReadonlyMap<PathKey, readonly string[]>;
222
+ };
223
+ /**
224
+ * The per-element bookkeeping a history position puts back with its value.
225
+ * A typed array op moves each element's baseline with its identity and drops
226
+ * a removed element's, as it does the element's blank membership, so a value
227
+ * alone, restored by position, would compare each row against the baseline of
228
+ * whichever row sits there now. With them come the identity orders and the
229
+ * removed-baseline set those baselines are read against. Opaque to the history module, which keeps it and hands it back.
230
+ */
231
+ type StructureSnapshot = {
232
+ readonly identity: IdentityOrders;
233
+ readonly originals: ReadonlyArray<readonly [PathKey, OriginalsRecord]>;
234
+ readonly originalBlank: ReadonlyArray<PathKey>;
235
+ readonly removed: ReadonlyArray<readonly [PathKey, Path]>;
236
+ };
218
237
 
219
238
  /** Internal brand for the `Unset` type. Never exposed at runtime. */
220
239
  declare const _unsetBrand: unique symbol;
@@ -888,8 +907,6 @@ type SchemaParseErrorWithData<TData> = {
888
907
  };
889
908
  /** Settled schema-level verdict. Discriminate on `success`. */
890
909
  type SchemaParseResult<TData> = SchemaParseSuccess<TData> | SchemaParseErrorWithData<TData> | SchemaParseErrorWithoutData;
891
- /** Schema-level `getDefaultValues` verdict: defaults always come back. */
892
- type SchemaDefaultsResult<TData> = SchemaParseSuccess<TData> | SchemaParseErrorWithData<TData>;
893
910
  /** Settled validation result when the form or subtree parsed successfully. */
894
911
  type ValidationResponseSuccess<TData> = SchemaParseSuccess<TData> & {
895
912
  /** The form this verdict belongs to. Stamped by the store, not the schema. */
@@ -977,7 +994,31 @@ type GetDefaultValuesConfig<Form> = {
977
994
  * library.
978
995
  */
979
996
  type AbstractSchema<Form, GetValueFormType> = {
980
- getDefaultValues(config: GetDefaultValuesConfig<Form>): SchemaDefaultsResult<Form>;
997
+ /**
998
+ * The form's starting value: the schema's defaults, with
999
+ * `config.constraints` merged over them and any shape the merge broke
1000
+ * repaired. Derivation only. Nothing is validated here, so no
1001
+ * refinement, check or transform runs; `validateSync` is the verdict.
1002
+ */
1003
+ getDefaultValues(config: GetDefaultValuesConfig<Form>): Form;
1004
+ /**
1005
+ * The schema's synchronous verdict on `data` as a whole form, as a
1006
+ * list of failures: empty when the data passes.
1007
+ *
1008
+ * The runtime seeds the schema error channel with it whenever it
1009
+ * establishes a baseline: on the starting value, the first time
1010
+ * anything reads or writes the form's errors, and on `reset()`. A form
1011
+ * starting from invalid defaults therefore reads invalid from its
1012
+ * first render.
1013
+ * A hydrating client never calls it, since the server's verdict
1014
+ * arrives with the payload.
1015
+ *
1016
+ * Adapters MUST NOT throw: a refinement that throws, or a schema whose
1017
+ * verdict resolves only through a Promise, returns `[]` and leaves the
1018
+ * verdict to the async pass that `needsAsyncValidation` queues. An
1019
+ * adapter with no synchronous parse returns `[]` unconditionally.
1020
+ */
1021
+ validateSync(data: unknown): ValidationError[];
981
1022
  /**
982
1023
  * The schema-prescribed default at `path`. The runtime uses it to
983
1024
  * fill structural gaps, so every `setValue` write leaves the form
@@ -1325,8 +1366,8 @@ type AbstractSchema<Form, GetValueFormType> = {
1325
1366
  /**
1326
1367
  * Whether `validateAtPath` MAY have to run asynchronously to surface
1327
1368
  * every error this schema can produce. The runtime asks at
1328
- * construction: `false` means the construction-time sync seed is
1329
- * authoritative and no extra microtask is spent, `true` queues a
1369
+ * construction: `false` means the synchronous verdict on the starting
1370
+ * value is authoritative and no extra microtask is spent, `true` queues a
1330
1371
  * one-shot full-form async pass so async-only verdicts (refinements,
1331
1372
  * transforms or pipes that resolve only through a Promise) surface
1332
1373
  * without waiting for a user mutation.
@@ -1338,7 +1379,10 @@ type AbstractSchema<Form, GetValueFormType> = {
1338
1379
  * For a per-path query, compose with `getSchemasAtPath(path)`: each
1339
1380
  * candidate sub-schema exposes its own `needsAsyncValidation`, so
1340
1381
  * "does the cargo subtree contain async work?" is the union of the
1341
- * per-candidate answers, with no separate top-level overload.
1382
+ * per-candidate answers, with no separate top-level overload. A root
1383
+ * that answers `false` answers for every path, and the runtime then
1384
+ * asks no sub-schema, so never answer `false` at the root of a tree
1385
+ * where a sub-schema would answer `true`.
1342
1386
  */
1343
1387
  needsAsyncValidation?(): boolean;
1344
1388
  /**
@@ -1378,6 +1422,20 @@ type AbstractSchema<Form, GetValueFormType> = {
1378
1422
  * that disables variant reshape and stub correction for the form.
1379
1423
  */
1380
1424
  hasDiscriminatedUnions?(): boolean;
1425
+ /**
1426
+ * Whether `isPreprocessOrCoerceLeaf` can answer `true` at any path:
1427
+ * the schema holds a preprocess-style normalizer or a coerce
1428
+ * primitive somewhere. The store reads it once at construction.
1429
+ * `false` lets it skip recording which paths the consumer authored,
1430
+ * a set that is only read at such leaves.
1431
+ *
1432
+ * Optional, and an adapter that omits it is treated as holding one,
1433
+ * which keeps that bookkeeping on. Never return `false` for a schema
1434
+ * where `isPreprocessOrCoerceLeaf` answers `true` anywhere: verdicts
1435
+ * at a path the consumer authored with `undefined` would then stay
1436
+ * hidden.
1437
+ */
1438
+ hasPreprocessOrCoerce?(): boolean;
1381
1439
  };
1382
1440
  /**
1383
1441
  * A discriminated union as the adapter reports it: the discriminator
@@ -1583,10 +1641,13 @@ type WriteMeta = {
1583
1641
  type HistoryKernel = {
1584
1642
  readonly form: Ref<unknown>;
1585
1643
  readonly blankPaths: Set<PathKey>;
1586
- readonly errorCells: Map<PathKey, ErrorCell>;
1644
+ readonly errorCells: () => Map<PathKey, ErrorCell>;
1587
1645
  onFormChange(listener: (next: unknown, meta?: WriteMeta) => void): () => void;
1646
+ onReset(listener: () => void): () => void;
1588
1647
  applyFormReplacement(next: unknown): void;
1589
1648
  restoreErrorCells(entries: ReadonlyArray<readonly [PathKey, ErrorCell]>): void;
1649
+ captureStructure(): StructureSnapshot;
1650
+ restoreStructure(snapshot: StructureSnapshot): void;
1590
1651
  };
1591
1652
  /**
1592
1653
  * The live undo/redo runtime a {@link HistoryPlugin} attaches to one
@@ -2033,7 +2094,13 @@ type HandleSubmit<Form extends GenericForm> = (onSubmit: OnSubmit<Form>, onError
2033
2094
  * fields appear in friendlier shape on `FieldState`.
2034
2095
  */
2035
2096
  type MetaTrackerValue = {
2036
- /** ISO timestamp of the most recent write at this path. `null` if never written. */
2097
+ /**
2098
+ * ISO timestamp of the most recent write at this path or under it,
2099
+ * counting the form's creation. A removal is a write too, so the stamp
2100
+ * never moves backwards, and every write reads later than the one before
2101
+ * it, whatever the device's clock says. `null` only for a path never
2102
+ * written or no longer present.
2103
+ */
2037
2104
  updatedAt: string | null;
2038
2105
  /** Value as it arrived, before any transforms. Distinguishes a parse-coerced read from raw user input. */
2039
2106
  rawValue: unknown;
@@ -2184,6 +2251,32 @@ type AttaformDomBinding = {
2184
2251
  path: Path;
2185
2252
  element: HTMLElement;
2186
2253
  } | null;
2254
+ readonly hostChannel: HostChannel;
2255
+ };
2256
+ /**
2257
+ * The write policy for a `v-register` component host, held on the DOM
2258
+ * binding so it stays in the directive cluster's lazy graph. Applies the
2259
+ * binding site's modifiers, runs register `transforms` and schema
2260
+ * coercion, and buffers `.lazy` writes while focus is inside the host.
2261
+ *
2262
+ * - `write` commits a value the host emitted through
2263
+ * `update:modelValue`.
2264
+ * - `writeInput` takes an `input` emit and commits it only when the
2265
+ * payload carries the live value as an own `value` property.
2266
+ * - `markEditing` records focus entering or leaving the host. Leaving
2267
+ * commits a buffered `.lazy` value, or the trimmed form under `.trim`.
2268
+ * - `holdsDraft` reports a buffered `.lazy` value that storage has not
2269
+ * moved past, the edit a re-render must leave on screen.
2270
+ * - `release` drops any buffered value when the host unmounts.
2271
+ *
2272
+ * @internal
2273
+ */
2274
+ type HostChannel = {
2275
+ readonly write: (rv: RegisterValue, value: unknown, modifiers: HostModifiers) => boolean;
2276
+ readonly writeInput: (rv: RegisterValue, payload: unknown, modifiers: HostModifiers) => boolean;
2277
+ readonly markEditing: (rv: RegisterValue, editing: boolean, modifiers: HostModifiers) => void;
2278
+ readonly holdsDraft: (rv: RegisterValue) => boolean;
2279
+ readonly release: (rv: RegisterValue) => void;
2187
2280
  };
2188
2281
  /**
2189
2282
  * Factory the directive and `useRegister` inject through
@@ -2244,16 +2337,39 @@ type RegisterValue<Value = unknown> = Readonly<{
2244
2337
  setValueWithInternalPath: (value: unknown, meta?: WriteMeta) => boolean;
2245
2338
  /**
2246
2339
  * Commit a value emitted by a third-party component bound through
2247
- * `v-register`'s compile-time v-model desugar. Writes the component's
2248
- * typed model output as authoritative, with no coercion, AND marks
2249
- * the field interacted, since a v-model host has no DOM input
2250
- * listener to flip the sticky `interacted` bit. The injected
2251
- * `onUpdate:modelValue` handler is the only caller. `true` when the
2252
- * write was accepted.
2340
+ * `v-register`'s compile-time v-model desugar, and mark the field
2341
+ * interacted, since a v-model host has no DOM input listener to flip
2342
+ * the sticky `interacted` bit. The write takes the same path a native
2343
+ * input's does: the binding site's `.number` / `.trim` / `.lazy`
2344
+ * modifiers, then register `transforms`, then schema coercion. The
2345
+ * injected `onUpdate:modelValue` handler is the caller, passing the
2346
+ * modifiers as a static literal. `true` when the write was accepted
2347
+ * or buffered.
2348
+ *
2349
+ * @internal
2350
+ */
2351
+ setValueFromHost: (value: unknown, modifiers: HostModifiers) => boolean;
2352
+ /**
2353
+ * Commit the live value from a component host's `input` emit. A
2354
+ * payload that is a non-Event object owning a `value` property (the
2355
+ * shape PrimeVue's InputNumber reports per keystroke) writes that
2356
+ * value through `setValueFromHost`; anything else, a native `Event`
2357
+ * reaching a fallthrough listener included, is ignored and returns
2358
+ * `false`. The injected `onInput` handler is the only caller.
2359
+ *
2360
+ * @internal
2361
+ */
2362
+ setValueFromHostInput: (payload: unknown, modifiers: HostModifiers) => boolean;
2363
+ /**
2364
+ * The final step of a component-host write, after modifiers,
2365
+ * transforms and coercion: an empty signal (`''`, `null` or
2366
+ * `undefined`) the field's schema does not admit marks the field
2367
+ * blank, and anything else writes through. Called by the host write
2368
+ * channel.
2253
2369
  *
2254
2370
  * @internal
2255
2371
  */
2256
- setValueFromHost: (value: unknown) => boolean;
2372
+ commitFromHost: (value: unknown) => boolean;
2257
2373
  /**
2258
2374
  * Mark this field DOM-connected during SSR, so a server-rendered
2259
2375
  * template reading `form.fields.<path>.connected` does not flicker on
@@ -2312,11 +2428,12 @@ type RegisterValue<Value = unknown> = Readonly<{
2312
2428
  * every later registration.
2313
2429
  *
2314
2430
  * Optional, so a hand-rolled RegisterValue owning its own element
2315
- * handling need not declare it.
2431
+ * handling need not declare it. Returns the armed binding, which is how
2432
+ * the directive reaches the host write channel.
2316
2433
  *
2317
2434
  * @internal
2318
2435
  */
2319
- ensureDomBinding?: (factory: DomBindingFactory) => void;
2436
+ ensureDomBinding?: (factory: DomBindingFactory) => AttaformDomBinding;
2320
2437
  /**
2321
2438
  * Canonical JSON-encoded path key for this binding, such as
2322
2439
  * `'["items",0,"name"]'`. Useful for stable Map and Set keys, log
@@ -2576,9 +2693,10 @@ type CustomRegisterDirective<T, Modifiers extends string = string> = ObjectDirec
2576
2693
  /**
2577
2694
  * Snapshot of the last `value.innerRef.value` reference the
2578
2695
  * directive's DOM-sync (setSelected / setChecked / radio
2579
- * `el.checked = …`) was applied for. Used by every input
2580
- * directive's `updated` / `beforeUpdate` to skip the per-render
2581
- * DOM sync when the model is identity-unchanged, so a parent
2696
+ * `el.checked = …` / text `el.value = …`) was applied for. Used by
2697
+ * every input directive's `updated` / `beforeUpdate` to skip the
2698
+ * per-render DOM sync when the model is identity-unchanged (for
2699
+ * text, a focused `.lazy` edit is held through it), so a parent
2582
2700
  * re-render (a typed character in a sibling, an async-validation
2583
2701
  * tick, any reactive read) cannot clobber an in-progress user
2584
2702
  * interaction. Identity comparison is sound because every form
@@ -2604,16 +2722,18 @@ type CustomRegisterDirective<T, Modifiers extends string = string> = ObjectDirec
2604
2722
  }, RegisterValue | undefined, Modifiers, string>;
2605
2723
  /**
2606
2724
  * Modifier names supported by `v-register` on `<input type="text">`,
2607
- * `<input type="number">`, and `<textarea>`. Mirrors Vue's
2608
- * `v-model` modifier semantics on the same elements; combine freely
2609
- * (`<input v-register.lazy.trim.number="..." />`).
2725
+ * `<input type="number">`, `<textarea>`, and component hosts. Mirrors
2726
+ * Vue's `v-model` modifier semantics on the same elements; combine
2727
+ * freely (`<input v-register.lazy.trim.number="..." />`).
2610
2728
  */
2611
2729
  type RegisterTextModifier =
2612
2730
  /**
2613
2731
  * Write on `change` (blur) instead of `input`. The reactive
2614
2732
  * model only updates after the user tabs/clicks out of the
2615
2733
  * field. IME composition handlers are skipped under `.lazy`, since
2616
- * composition events do not gate writes.
2734
+ * composition events do not gate writes. On a component host the
2735
+ * last value emitted while focus is inside the host commits when
2736
+ * focus leaves it.
2617
2737
  */
2618
2738
  'lazy'
2619
2739
  /**
@@ -2621,18 +2741,30 @@ type RegisterTextModifier =
2621
2741
  * the user's raw input (whitespace included) while they're
2622
2742
  * typing; on `change` (blur / commit) the value is trimmed
2623
2743
  * once and written back to both the model and the visible DOM.
2624
- * Combine with `.lazy` to skip the mid-typing writes entirely.
2744
+ * Combine with `.lazy` to skip the mid-typing writes entirely. On a
2745
+ * component host the trimmed value commits when focus leaves the
2746
+ * host.
2625
2747
  */
2626
2748
  | 'trim'
2627
2749
  /**
2628
- * Cast the value with `parseFloat` before writing. A value that
2629
- * does not parse as a number (`'abc'`) passes through unchanged,
2630
- * and the slim-primitive gate then sees a string heading for a
2631
- * numeric slot and rejects the write. Auto-applied for
2632
- * `<input type="number">`, where an explicit `.number` is
2633
- * redundant.
2750
+ * Cast the value with `parseFloat` before writing. An empty field,
2751
+ * or text that does not parse as a number (`'abc'`), marks the field
2752
+ * blank instead of writing a string. On a component host the cast
2753
+ * applies to string emits, and a value the component already typed
2754
+ * passes through. Auto-applied for `<input type="number">`, where an
2755
+ * explicit `.number` is redundant.
2634
2756
  */
2635
2757
  | 'number';
2758
+ /**
2759
+ * The `v-register` modifiers a component host honours, carried from the
2760
+ * binding site to the host write channel as a static literal that the
2761
+ * compile-time bridge transform emits. Shaped like Vue's directive
2762
+ * modifiers, so the directive passes a binding's modifiers to the channel
2763
+ * unchanged.
2764
+ *
2765
+ * @internal
2766
+ */
2767
+ type HostModifiers = Readonly<Partial<Record<RegisterTextModifier, boolean>>>;
2636
2768
  /**
2637
2769
  * v-register directive variant for `<input type="text">`,
2638
2770
  * `<input type="number">` and `<textarea>`. Supports the `.lazy`,
@@ -2855,6 +2987,13 @@ type FieldState<Value = unknown> = {
2855
2987
  readonly dirty: boolean;
2856
2988
  readonly focused: boolean | null;
2857
2989
  readonly blurred: boolean | null;
2990
+ /**
2991
+ * `true` once this field has been blurred, with or without an edit.
2992
+ * Sticky thereafter, including through a value round trip; cleared by
2993
+ * `form.reset()` / `form.resetField(path)`. Containers roll it up as
2994
+ * a disjunction, and keep it after the descendant that set it is
2995
+ * removed.
2996
+ */
2858
2997
  readonly touched: boolean;
2859
2998
  /**
2860
2999
  * `true` once the user has issued at least one value edit on this
@@ -2866,7 +3005,8 @@ type FieldState<Value = unknown> = {
2866
3005
  * flips `touched` but never `interacted`. Set only by user input,
2867
3006
  * never by hydration or programmatic `setValue`; cleared by
2868
3007
  * `form.reset()` / `form.resetField(path)`. Containers roll it up as
2869
- * a disjunction (any descendant interacted).
3008
+ * a disjunction (any descendant interacted), and keep it after the
3009
+ * descendant that set it is removed.
2870
3010
  */
2871
3011
  readonly interacted: boolean;
2872
3012
  /**
@@ -2879,7 +3019,8 @@ type FieldState<Value = unknown> = {
2879
3019
  * visible through a re-focus to be fixed live. Set only by user
2880
3020
  * input/blur, never by hydration or programmatic writes; cleared by
2881
3021
  * `form.reset()` / `form.resetField(path)`. Containers roll it up as a
2882
- * disjunction.
3022
+ * disjunction, and keep it after the descendant that set it is
3023
+ * removed.
2883
3024
  */
2884
3025
  readonly blurredAfterInteraction: boolean;
2885
3026
  readonly connected: boolean;
@@ -2915,6 +3056,18 @@ type FieldState<Value = unknown> = {
2915
3056
  * `elements[0] ?? null`.
2916
3057
  */
2917
3058
  readonly elements: readonly HTMLElement[];
3059
+ /**
3060
+ * ISO timestamp of the most recent write at this path or anywhere
3061
+ * under it, counting the form's creation. A removal is a write too, so
3062
+ * a container's stamp moves forward when a row leaves and never moves
3063
+ * backwards. A survivor keeps its own stamp when a removal or a reorder
3064
+ * moves it to a new index, and a write that changes nothing stamps
3065
+ * nothing. Every write that changes a value reads later than the one
3066
+ * before it: two writes in one millisecond, or a write on a device whose
3067
+ * clock runs behind the server that rendered the page, take one
3068
+ * millisecond past the latest stamp the form holds. `null` only for a
3069
+ * path never written or no longer present.
3070
+ */
2918
3071
  readonly updatedAt: string | null;
2919
3072
  /**
2920
3073
  * Every `ValidationError` in this path's SUBTREE: the node's own
@@ -4252,7 +4405,9 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4252
4405
  * - the form value back to defaults;
4253
4406
  * - the dirty baseline (so the next edit flips `dirty` correctly);
4254
4407
  * - field errors;
4255
- * - touched / focused / blurred per-field flags;
4408
+ * - the interaction history (`touched` / `interacted` /
4409
+ * `blurredAfterInteraction`) at every path, containers included;
4410
+ * `focused`, `blurred` and `connected` follow the DOM and stay;
4256
4411
  * - submission state (`submitting` / `submissionAttempts` /
4257
4412
  * `submitted` / `submitError`).
4258
4413
  *
@@ -4279,8 +4434,10 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4279
4434
  reset: (nextDefaultValues?: DefaultValuesInput<Form>) => void;
4280
4435
  /**
4281
4436
  * Restore a single field (or a sub-tree like `'user'`) to its
4282
- * initial value. Clears errors and touched flags for the field
4283
- * and its descendants; leaves siblings and submission state alone.
4437
+ * initial value. Clears errors and the interaction history
4438
+ * (`touched` / `interacted` / `blurredAfterInteraction`) for the
4439
+ * field and its descendants; leaves siblings, ancestors and
4440
+ * submission state alone.
4284
4441
  *
4285
4442
  * No-op when the path doesn't exist on the form (e.g. a typo'd
4286
4443
  * dynamic key).
@@ -4549,5 +4706,5 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4549
4706
  blankPaths: ComputedRef<BlankPathsView>;
4550
4707
  };
4551
4708
 
4552
- export { ROOT_PATH as R, canonicalizePath as aj, isPathPrefix as ak, isUnset as al, parseDottedPath as am, unset as an, ROOT_PATH_KEY as z };
4553
- export type { SchemaDefaultsResult as $, AbstractSchema as A, ReactiveValidationStatus as B, CustomDirectiveRegisterAssignerFn as C, DefaultValuesInput as D, ErrorInput as E, FormKey as F, GenericForm as G, HandleSubmit as H, IsTuple as I, JoinSegments as J, KeyofUnion as K, LiftedValueShape as L, MetaTrackerValue as M, NestedReadType as N, OnError as O, ParseOptions as P, RecordPath as Q, RecordValue as S, RegisterDirective as T, UseFormConfiguration as U, RegisterFlatPath as V, RegisterOptions as W, RegisterSelectModifier as X, RegisterTextModifier as Y, RegisterTransform as Z, RegisterValue as _, UseFormReturnType as a, SchemaParseErrorWithData as a0, SchemaParseErrorWithoutData as a1, SchemaParseResult as a2, SchemaParseSuccess as a3, Segment as a4, SetValueCallback as a5, SetValuePayload as a6, SettledValidationStatus as a7, SlimPrimitiveKind as a8, SubmitHandler as a9, Unset as aa, ValidateOn as ab, ValidateOnConfig as ac, ValidationError as ad, ValidationResponse as ae, ValidationResponseWithoutValue as af, ValueOfUnion as ag, WriteMeta as ah, WriteShape as ai, RegisterModelDynamicCustomDirective as ao, SchemaFactoryOptions as ap, AcceptableDefaults as aq, DisplayCtx as ar, GetDisplayState as as, DisplayMachine as at, FieldRecord as au, ErrorCell as av, OriginalsRecord as aw, AttaformDomBinding as ax, TransformAbortHolder as ay, ArrayItem as b, ArrayPath as c, DeepPartial as d, DefaultValuesShape as e, DisplayState as f, ErrorsProxyShape as g, FieldMetaPayload as h, FieldState as i, FieldStateMap as j, FieldStateMapEntry as k, FlatPath as l, FormErrorRecord as m, FormErrorsSurface as n, FormMeta as o, HistoryPlugin as p, IsUnion as q, Json as r, NestedType as s, OnSubmit as t, PartialFlatPath as u, Path as v, PathKey as w, PendingValidationStatus as x, Primitive as y };
4709
+ export { ROOT_PATH as R, canonicalizePath as ai, isPathPrefix as aj, isUnset as ak, parseDottedPath as al, unset as am, ROOT_PATH_KEY as z };
4710
+ export type { SchemaParseErrorWithData as $, AbstractSchema as A, ReactiveValidationStatus as B, CustomDirectiveRegisterAssignerFn as C, DefaultValuesInput as D, ErrorInput as E, FormKey as F, GenericForm as G, HandleSubmit as H, IsTuple as I, JoinSegments as J, KeyofUnion as K, LiftedValueShape as L, MetaTrackerValue as M, NestedReadType as N, OnError as O, ParseOptions as P, RecordPath as Q, RecordValue as S, RegisterDirective as T, UseFormConfiguration as U, RegisterFlatPath as V, RegisterOptions as W, RegisterSelectModifier as X, RegisterTextModifier as Y, RegisterTransform as Z, RegisterValue as _, UseFormReturnType as a, SchemaParseErrorWithoutData as a0, SchemaParseResult as a1, SchemaParseSuccess as a2, Segment as a3, SetValueCallback as a4, SetValuePayload as a5, SettledValidationStatus as a6, SlimPrimitiveKind as a7, SubmitHandler as a8, Unset as a9, ValidateOn as aa, ValidateOnConfig as ab, ValidationError as ac, ValidationResponse as ad, ValidationResponseWithoutValue as ae, ValueOfUnion as af, WriteMeta as ag, WriteShape as ah, RegisterModelDynamicCustomDirective as an, SchemaFactoryOptions as ao, AcceptableDefaults as ap, DisplayCtx as aq, GetDisplayState as ar, DisplayMachine as as, FieldRecord as at, ErrorCell as au, OriginalsRecord as av, StructureSnapshot as aw, AttaformDomBinding as ax, TransformAbortHolder as ay, ArrayItem as b, ArrayPath as c, DeepPartial as d, DefaultValuesShape as e, DisplayState as f, ErrorsProxyShape as g, FieldMetaPayload as h, FieldState as i, FieldStateMap as j, FieldStateMapEntry as k, FlatPath as l, FormErrorRecord as m, FormErrorsSurface as n, FormMeta as o, HistoryPlugin as p, IsUnion as q, Json as r, NestedType as s, OnSubmit as t, PartialFlatPath as u, Path as v, PathKey as w, PendingValidationStatus as x, Primitive as y };