attaform 0.30.0 → 0.31.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 (71) hide show
  1. package/dist/abstract.d.mts +15 -21
  2. package/dist/abstract.mjs +3 -3
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/devtools.mjs +4 -4
  5. package/dist/dev/directive.mjs +1 -1
  6. package/dist/dev/history.mjs +2 -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.DUZckyV8.mjs → attaform-dev.B1q-x9R9.mjs} +4 -4
  10. package/dist/dev/shared/{attaform-dev.BcYT6tFh.mjs → attaform-dev.CZz4GYZ6.mjs} +21 -9
  11. package/dist/dev/shared/{attaform-dev.BisFstxh.mjs → attaform-dev.Caibt7sb.mjs} +16 -15
  12. package/dist/dev/shared/{attaform-dev.DTrHonpc.mjs → attaform-dev.CtyqGia7.mjs} +176 -260
  13. package/dist/dev/shared/{attaform-dev.BK6Ge2qR.mjs → attaform-dev.DJ3nsWw6.mjs} +86 -122
  14. package/dist/dev/shared/{attaform-dev.BcNtyOmf.mjs → attaform-dev.DlaT4xGd.mjs} +4 -4
  15. package/dist/dev/shared/{attaform-dev.D6CLvaQT.mjs → attaform-dev.k5X47GMD.mjs} +520 -395
  16. package/dist/dev/shared/{attaform-dev.CQ2rJiAX.mjs → attaform-dev.y9x9Vp9F.mjs} +324 -234
  17. package/dist/dev/shared/{attaform-dev.BQc5QV3Y.mjs → attaform-dev.zlvDr0WB.mjs} +2 -2
  18. package/dist/dev/zod-v3.mjs +6 -6
  19. package/dist/dev/zod-v4.mjs +6 -6
  20. package/dist/dev/zod.mjs +5 -5
  21. package/dist/directive.d.mts +4 -4
  22. package/dist/directive.mjs +1 -1
  23. package/dist/esbuild.d.mts +11 -0
  24. package/dist/esbuild.mjs +1 -1
  25. package/dist/history.d.mts +3 -32
  26. package/dist/history.mjs +2 -2
  27. package/dist/index.d.mts +67 -70
  28. package/dist/index.mjs +4 -4
  29. package/dist/nuxt.d.mts +4 -3
  30. package/dist/nuxt.mjs +3 -6
  31. package/dist/rollup.d.mts +11 -0
  32. package/dist/rollup.mjs +1 -1
  33. package/dist/rspack.d.mts +19 -11
  34. package/dist/rspack.mjs +1 -1
  35. package/dist/runtime/components/AttaformDevtoolsPanel.vue +15 -15
  36. package/dist/runtime/components/DevtoolsValueTree.vue +2 -2
  37. package/dist/runtime/plugins/attaform.mjs +1 -1
  38. package/dist/shared/{attaform.BRDDQeHV.mjs → attaform.8mfKZCwH.mjs} +2 -2
  39. package/dist/shared/{attaform.DRUT703x.d.mts → attaform.B-Tb62qV.d.mts} +12 -12
  40. package/dist/shared/{attaform.v0fQtWyA.mjs → attaform.B1rH9ht6.mjs} +1 -1
  41. package/dist/shared/{attaform.DhnPb6nK.mjs → attaform.BAf3UNn4.mjs} +1 -1
  42. package/dist/shared/{attaform.BXneBxEj.mjs → attaform.BWUn8Kjy.mjs} +85 -121
  43. package/dist/shared/{attaform.WiGdIqgx.mjs → attaform.C4shmwyJ.mjs} +2 -2
  44. package/dist/shared/{attaform.Rc9u4Vfw.d.mts → attaform.CRVe8s82.d.mts} +83 -95
  45. package/dist/shared/attaform.D9RqPgGu.d.mts +42 -0
  46. package/dist/shared/attaform.DAo0QBmS.d.mts +2173 -0
  47. package/dist/shared/{attaform.BK0cgu2K.mjs → attaform.DBCbxlyk.mjs} +62 -4
  48. package/dist/shared/{attaform.CUfrMK7t.mjs → attaform.DL2nl_QR.mjs} +15 -14
  49. package/dist/shared/{attaform.DNJOWjQw.mjs → attaform.DNPJPOO7.mjs} +299 -218
  50. package/dist/shared/attaform.DZnrDrNb.d.mts +4657 -0
  51. package/dist/shared/{attaform.B3resMQ1.mjs → attaform.DxKQsqFZ.mjs} +496 -371
  52. package/dist/shared/{attaform.ByWP6kSx.mjs → attaform.L011tHgJ.mjs} +3 -3
  53. package/dist/shared/{attaform.CJ8DpRGj.mjs → attaform.LUPC-2Qg.mjs} +175 -257
  54. package/dist/transforms.d.mts +153 -26
  55. package/dist/transforms.mjs +1 -1
  56. package/dist/vite.d.mts +29 -52
  57. package/dist/vite.mjs +3 -3
  58. package/dist/webpack.d.mts +20 -12
  59. package/dist/webpack.mjs +1 -1
  60. package/dist/zod-v3.d.mts +90 -96
  61. package/dist/zod-v3.mjs +5 -5
  62. package/dist/zod-v4.d.mts +120 -118
  63. package/dist/zod-v4.mjs +5 -5
  64. package/dist/zod.d.mts +5 -5
  65. package/dist/zod.mjs +4 -4
  66. package/package.json +7 -6
  67. package/skills/attaform/SKILL.md +1 -1
  68. package/skills/attaform/references/custom-components.md +4 -0
  69. package/dist/shared/attaform.Clz0ArIi.d.mts +0 -44
  70. package/dist/shared/attaform.UGyOK83T.d.mts +0 -2366
  71. package/dist/shared/attaform.bKLzMU0o.d.mts +0 -4761
@@ -1,8 +1,373 @@
1
- import { reactive, markRaw, isRef, effectScope, watch, warn, nextTick } from 'vue';
2
- import { e as canonicalizePath } from './attaform-dev.BQc5QV3Y.mjs';
1
+ import { isRef, warn, toRaw, reactive, markRaw, effectScope, watch, nextTick } from 'vue';
2
+ import { e as canonicalizePath } from './attaform-dev.zlvDr0WB.mjs';
3
+
4
+ function activeElementOf(node) {
5
+ const rootNode = node.getRootNode();
6
+ return rootNode instanceof Document || rootNode instanceof ShadowRoot ? rootNode.activeElement : null;
7
+ }
3
8
 
4
9
  const INTERACTIVE_TAG_NAMES = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
5
10
 
11
+ const isArray = Array.isArray;
12
+ function isFunction(value) {
13
+ return typeof value === "function";
14
+ }
15
+ function toTypeString(value) {
16
+ return Object.prototype.toString.call(value);
17
+ }
18
+ function isSet(value) {
19
+ return toTypeString(value) === "[object Set]";
20
+ }
21
+ function isDate(value) {
22
+ return toTypeString(value) === "[object Date]";
23
+ }
24
+ function isSymbol(value) {
25
+ return typeof value === "symbol";
26
+ }
27
+ function isObject(value) {
28
+ return value !== null && typeof value === "object";
29
+ }
30
+ function looseToNumber(val) {
31
+ const n = parseFloat(val);
32
+ return isNaN(n) ? val : n;
33
+ }
34
+ function looseCompareArrays(a, b) {
35
+ if (a.length !== b.length) return false;
36
+ let equal = true;
37
+ for (let i = 0; equal && i < a.length; i++) {
38
+ equal = looseEqual(a[i], b[i]);
39
+ }
40
+ return equal;
41
+ }
42
+ function looseEqual(a, b) {
43
+ if (a === b) return true;
44
+ const aValidType = isDate(a);
45
+ const bValidType = isDate(b);
46
+ if (aValidType || bValidType) {
47
+ return aValidType && bValidType ? a.getTime() === b.getTime() : false;
48
+ }
49
+ const aSymbol = isSymbol(a);
50
+ const bSymbol = isSymbol(b);
51
+ if (aSymbol || bSymbol) return a === b;
52
+ const aIsArray = isArray(a);
53
+ const bIsArray = isArray(b);
54
+ if (aIsArray || bIsArray) {
55
+ return aIsArray && bIsArray ? looseCompareArrays(a, b) : false;
56
+ }
57
+ const aIsObject = isObject(a);
58
+ const bIsObject = isObject(b);
59
+ if (aIsObject !== bIsObject) return false;
60
+ if (aIsObject && bIsObject) {
61
+ const keysA = Object.keys(a);
62
+ if (keysA.length !== Object.keys(b).length) return false;
63
+ for (const key of keysA) {
64
+ const hasA = Object.prototype.hasOwnProperty.call(a, key);
65
+ const hasB = Object.prototype.hasOwnProperty.call(b, key);
66
+ if (!hasA || !hasB || !looseEqual(a[key], b[key])) return false;
67
+ }
68
+ return true;
69
+ }
70
+ return String(a) === String(b);
71
+ }
72
+ function looseIndexOf(arr, val) {
73
+ return arr.findIndex((item) => looseEqual(item, val));
74
+ }
75
+ function invokeArrayFns(fns, ...args) {
76
+ for (let i = 0; i < fns.length; i++) {
77
+ const fn = fns[i];
78
+ if (fn) fn(...args);
79
+ }
80
+ }
81
+
82
+ const V_REGISTER_MARKER = /* @__PURE__ */ Symbol.for("attaform:v-register-directive");
83
+ const REGISTER_OWNER_MARKER = /* @__PURE__ */ Symbol.for("attaform:register-owner-marker");
84
+ const SSR_COMPONENT_HOST_MODIFIER = "attaformComponentHost";
85
+ const V_REGISTER_COMPILED_MODIFIER = "attaformCompiled";
86
+ function isRegisterValue(val) {
87
+ if (typeof val !== "object" || val === null) return false;
88
+ if (!("innerRef" in val)) return false;
89
+ if (!isRef(val.innerRef)) return false;
90
+ if (!("registerElement" in val)) return false;
91
+ if (typeof val.registerElement !== "function") return false;
92
+ if (!("setValueWithInternalPath" in val)) return false;
93
+ if (typeof val.setValueWithInternalPath !== "function") return false;
94
+ return true;
95
+ }
96
+ function isTransforming(value) {
97
+ return isRegisterValue(value) && value.transforming;
98
+ }
99
+
100
+ const assignKey = /* @__PURE__ */ Symbol.for("attaform:assign-key");
101
+ const DEFAULT_ASSIGNER_TAG = /* @__PURE__ */ Symbol.for("attaform:default-assigner-tag");
102
+ function isDefaultAssigner(fn) {
103
+ return typeof fn === "function" && fn[DEFAULT_ASSIGNER_TAG] === true;
104
+ }
105
+ const CONSUMER_WRAPPED_TAG = /* @__PURE__ */ Symbol.for("attaform:consumer-wrapped-assigner");
106
+ function isConsumerWrapped(fn) {
107
+ return typeof fn === "function" && fn[CONSUMER_WRAPPED_TAG] === true;
108
+ }
109
+ function fireAssigner(el, registerValue, value) {
110
+ const fn = el[assignKey];
111
+ if (fn === void 0) return void 0;
112
+ if (isDefaultAssigner(fn) || isConsumerWrapped(fn)) {
113
+ return fn(value);
114
+ }
115
+ if (!isRegisterValue(registerValue)) {
116
+ return fn(value, void 0);
117
+ }
118
+ return wrapWithTransforms(
119
+ value,
120
+ registerValue,
121
+ (coerced) => fn(coerced, registerValue),
122
+ void 0
123
+ );
124
+ }
125
+ function isThenable(x) {
126
+ return x !== null && (typeof x === "object" || typeof x === "function") && typeof x.then === "function";
127
+ }
128
+ function makeTransformContext() {
129
+ const holder = { controller: null, aborted: false };
130
+ const ctx = {
131
+ get signal() {
132
+ if (holder.controller === null) {
133
+ holder.controller = new AbortController();
134
+ if (holder.aborted) holder.controller.abort();
135
+ }
136
+ return holder.controller.signal;
137
+ }
138
+ };
139
+ return { ctx, holder };
140
+ }
141
+ function runTransforms(initial, registerValue) {
142
+ const transforms = registerValue.transforms;
143
+ if (transforms === void 0 || transforms.length === 0) {
144
+ return { kind: "sync", ok: true, value: initial };
145
+ }
146
+ const { ctx, holder } = makeTransformContext();
147
+ let v = initial;
148
+ for (let i = 0; i < transforms.length; i++) {
149
+ const fn = transforms[i];
150
+ let out;
151
+ try {
152
+ out = fn(v, ctx);
153
+ } catch (err) {
154
+ logTransformFailure(registerValue.path, i, fn, err);
155
+ return { kind: "sync", ok: false };
156
+ }
157
+ if (isThenable(out)) {
158
+ const rest = transforms.slice(i + 1);
159
+ const seed = out;
160
+ const run = () => rest.reduce(
161
+ (acc, next) => acc.then((value) => next(value, ctx)),
162
+ Promise.resolve(seed)
163
+ );
164
+ return { kind: "async", run, holder };
165
+ }
166
+ v = out;
167
+ }
168
+ return { kind: "sync", ok: true, value: v };
169
+ }
170
+ function kickoffAsyncTransform(rv, holder, run, commit, syncDom) {
171
+ const token = rv.beginTransform(holder);
172
+ void run().then(
173
+ (value) => {
174
+ const live = rv.isCurrentTransform(token);
175
+ rv.endTransform(token);
176
+ if (!live) return;
177
+ const coerced = applyCoerce(value, rv);
178
+ const wrote = commit(coerced);
179
+ if (wrote === false) rv.setTransformError(transformGateRejectedError(rv.path));
180
+ else syncDom?.();
181
+ },
182
+ (err) => {
183
+ if (rv.isCurrentTransform(token)) rv.setTransformError(toTransformError(err));
184
+ rv.endTransform(token);
185
+ }
186
+ );
187
+ }
188
+ function logTransformFailure(path, index, fn, err) {
189
+ {
190
+ const namePart = fn.name !== "" ? `, '${fn.name}'` : "";
191
+ console.error(
192
+ `[attaform] transform threw for path '${path}' (index ${index}${namePart}). Write aborted. Transforms must not throw; wrap your own try/catch if the throw is recoverable. Original error:`,
193
+ err
194
+ );
195
+ }
196
+ }
197
+ function applyCoerce(value, registerValue) {
198
+ return registerValue.coerce !== void 0 ? registerValue.coerce(value) : value;
199
+ }
200
+ function wrapWithTransforms(value, registerValue, commit, syncDom) {
201
+ const r = runTransforms(value, registerValue);
202
+ if (r.kind === "async") {
203
+ kickoffAsyncTransform(registerValue, r.holder, r.run, commit, syncDom);
204
+ return true;
205
+ }
206
+ if (!r.ok) return false;
207
+ const coerced = applyCoerce(r.value, registerValue);
208
+ return commit(coerced);
209
+ }
210
+ function toTransformError(value) {
211
+ if (value instanceof Error) return value;
212
+ const message = typeof value === "string" && value.length > 0 ? value : `Transform rejected with a non-Error value (${typeof value})`;
213
+ return new Error(message, { cause: value });
214
+ }
215
+ function transformGateRejectedError(path) {
216
+ return new Error(
217
+ `[attaform] transform result for path '${path}' was rejected by the field's type gate (the resolved value did not fit the schema slot).`
218
+ );
219
+ }
220
+ const getModelAssigner = (el, vnode, registerValue) => {
221
+ const fn = vnode.props?.["onUpdate:registerValue"] ?? vnode.props?.["on:update:registerValue"];
222
+ if (isArray(fn)) {
223
+ const fnArr = fn.filter((x) => isFunction(x));
224
+ const wrapped = (value) => {
225
+ return wrapWithTransforms(
226
+ value,
227
+ registerValue,
228
+ (coerced) => {
229
+ invokeArrayFns(fnArr, coerced, registerValue);
230
+ return void 0;
231
+ },
232
+ void 0
233
+ );
234
+ };
235
+ wrapped[CONSUMER_WRAPPED_TAG] = true;
236
+ return wrapped;
237
+ }
238
+ if (isFunction(fn)) {
239
+ const handler = fn;
240
+ const wrapped = (value) => {
241
+ return wrapWithTransforms(
242
+ value,
243
+ registerValue,
244
+ (coerced) => handler(coerced, registerValue),
245
+ void 0
246
+ );
247
+ };
248
+ wrapped[CONSUMER_WRAPPED_TAG] = true;
249
+ return wrapped;
250
+ }
251
+ const defaultAssigner = (value) => {
252
+ if (value === void 0 && registerValue.acceptsUndefined) {
253
+ return registerValue.setValueWithInternalPath(void 0);
254
+ }
255
+ return wrapWithTransforms(
256
+ value,
257
+ registerValue,
258
+ (coerced) => registerValue.setValueWithInternalPath(coerced),
259
+ el._syncFromStorage
260
+ );
261
+ };
262
+ defaultAssigner[DEFAULT_ASSIGNER_TAG] = true;
263
+ return defaultAssigner;
264
+ };
265
+ function makeNoopAssigner() {
266
+ const noop = (_) => void 0;
267
+ noop[DEFAULT_ASSIGNER_TAG] = true;
268
+ return noop;
269
+ }
270
+ function setAssignFunction(el, vnode, value) {
271
+ const current = el[assignKey];
272
+ if (current !== void 0 && !isDefaultAssigner(current) && !isConsumerWrapped(current)) {
273
+ return;
274
+ }
275
+ if (value === void 0) {
276
+ el[assignKey] = makeNoopAssigner();
277
+ return;
278
+ }
279
+ if (!isRegisterValue(value)) {
280
+ {
281
+ warn(
282
+ `v-register expected a RegisterValue, got '${typeof value}'. Bind to form.register('field'), not the field's ref, value, or path string.`
283
+ );
284
+ }
285
+ el[assignKey] = makeNoopAssigner();
286
+ return;
287
+ }
288
+ el[assignKey] = getModelAssigner(el, vnode, value);
289
+ }
290
+
291
+ const inherited = /* @__PURE__ */ new WeakMap();
292
+ function recordInheritedModifiers(rv, modifiers) {
293
+ const lazy = modifiers.lazy === true;
294
+ const trim = modifiers.trim === true;
295
+ const number = modifiers.number === true;
296
+ if ((lazy || trim || number) && typeof rv === "object" && rv !== null) {
297
+ inherited.set(toRaw(rv), { lazy, trim, number });
298
+ }
299
+ }
300
+ function withInheritedModifiers(rv, own) {
301
+ const found = typeof rv === "object" && rv !== null ? inherited.get(toRaw(rv)) : void 0;
302
+ return found === void 0 ? own : { ...found, ...own };
303
+ }
304
+
305
+ function commit(rv, raw, modifiers) {
306
+ let value = raw;
307
+ if (modifiers.number === true && typeof value === "string") {
308
+ const cast = looseToNumber(value);
309
+ if (typeof cast !== "number") return rv.markBlank();
310
+ if (!Number.isFinite(cast)) return false;
311
+ value = cast;
312
+ }
313
+ if (value == null || value === "" && !rv.acceptsString) return rv.commitFromHost(value);
314
+ return wrapWithTransforms(value, rv, (coerced) => rv.commitFromHost(coerced), void 0) !== false;
315
+ }
316
+ function createHostChannel() {
317
+ const editing = /* @__PURE__ */ new Set();
318
+ const pending = /* @__PURE__ */ new Map();
319
+ const heldDraft = (rv) => {
320
+ const draft = pending.get(rv.path);
321
+ return draft?.basis === rv.innerRef.value ? draft : void 0;
322
+ };
323
+ return {
324
+ write(rv, value, ownModifiers) {
325
+ const modifiers = withInheritedModifiers(rv, ownModifiers);
326
+ if (modifiers.lazy === true && editing.has(rv.path)) {
327
+ pending.set(rv.path, { value, basis: rv.innerRef.value });
328
+ return true;
329
+ }
330
+ return commit(rv, value, modifiers);
331
+ },
332
+ writeInput(rv, payload, modifiers) {
333
+ if (typeof payload !== "object" || payload === null || payload instanceof Event || !("value" in payload) || !Object.hasOwn(payload, "value")) {
334
+ return false;
335
+ }
336
+ return rv.setValueFromHost(payload.value, modifiers);
337
+ },
338
+ markEditing(rv, isEditing, modifiers) {
339
+ if (isEditing) {
340
+ editing.add(rv.path);
341
+ return;
342
+ }
343
+ editing.delete(rv.path);
344
+ const draft = heldDraft(rv);
345
+ if (pending.delete(rv.path)) {
346
+ if (draft !== void 0) {
347
+ const buffered = draft.value;
348
+ commit(
349
+ rv,
350
+ modifiers.trim === true && typeof buffered === "string" ? buffered.trim() : buffered,
351
+ modifiers
352
+ );
353
+ }
354
+ return;
355
+ }
356
+ if (modifiers.trim === true) {
357
+ const current = rv.innerRef.value;
358
+ if (typeof current === "string" && current !== current.trim()) {
359
+ commit(rv, current.trim(), modifiers);
360
+ }
361
+ }
362
+ },
363
+ holdsDraft: (rv) => heldDraft(rv) !== void 0,
364
+ release(rv) {
365
+ editing.delete(rv.path);
366
+ pending.delete(rv.path);
367
+ }
368
+ };
369
+ }
370
+
6
371
  const attaformListenersSymbol = /* @__PURE__ */ Symbol.for("attaform:focus-listeners");
7
372
  const HOST_FOCUS_TARGET_SELECTOR = [
8
373
  'input:not([type="hidden"])',
@@ -32,9 +397,7 @@ function attachFocusListeners(kernel, segments, element, instanceMeta) {
32
397
  element.addEventListener("focus", handleFocus);
33
398
  element.addEventListener("blur", handleBlur);
34
399
  target[attaformListenersSymbol] = { handleFocus, handleBlur };
35
- const rootNode = element.getRootNode();
36
- const activeElement = rootNode instanceof Document || rootNode instanceof ShadowRoot ? rootNode.activeElement : null;
37
- if (activeElement === element) {
400
+ if (activeElementOf(element) === element) {
38
401
  kernel.markFocused(segments, true, focusMeta);
39
402
  }
40
403
  }
@@ -107,135 +470,47 @@ function createDomBinding(kernel) {
107
470
  path: record.path,
108
471
  element: el,
109
472
  host: false,
110
- formInstanceId: elementToFormInstance.get(el) ?? ""
111
- });
112
- }
113
- }
114
- for (const [key, target] of hostTargets) {
115
- if (elements.has(key)) continue;
116
- flat.push({
117
- path: target.path,
118
- element: target.hostRoot,
119
- host: true,
120
- formInstanceId: target.formInstanceId
121
- });
122
- }
123
- flat.sort(
124
- (a, b) => a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
125
- );
126
- return flat;
127
- }
128
- function getFirstErrorElement(formInstanceId) {
129
- sortedRegistrationsCache ?? (sortedRegistrationsCache = rebuildSortedRegistrations());
130
- for (const entry of sortedRegistrationsCache) {
131
- if (entry.formInstanceId !== formInstanceId) continue;
132
- if (!entry.element.isConnected) continue;
133
- if (entry.element.offsetParent === null) continue;
134
- if (kernel.getErrorsForPath(entry.path).length === 0) continue;
135
- const element = entry.host ? resolveHostFocusTarget(entry.element) : entry.element;
136
- return { path: entry.path, element };
137
- }
138
- return null;
139
- }
140
- return {
141
- elements,
142
- attach,
143
- detach,
144
- markHostConnected,
145
- getFirstErrorElement
146
- };
147
- }
148
- function armDomBinding(value) {
149
- value.ensureDomBinding?.(createDomBinding);
150
- }
151
-
152
- const V_REGISTER_MARKER = /* @__PURE__ */ Symbol.for("attaform:v-register-directive");
153
- const REGISTER_OWNER_MARKER = /* @__PURE__ */ Symbol.for("attaform:register-owner-marker");
154
- const SSR_COMPONENT_HOST_MODIFIER = "attaformComponentHost";
155
- const V_REGISTER_COMPILED_MODIFIER = "attaformCompiled";
156
- function isRegisterValue(val) {
157
- if (typeof val !== "object" || val === null) return false;
158
- if (!("innerRef" in val)) return false;
159
- if (!isRef(val.innerRef)) return false;
160
- if (!("registerElement" in val)) return false;
161
- if (typeof val.registerElement !== "function") return false;
162
- if (!("setValueWithInternalPath" in val)) return false;
163
- if (typeof val.setValueWithInternalPath !== "function") return false;
164
- return true;
165
- }
166
- function isTransforming(value) {
167
- return isRegisterValue(value) && value.transforming;
168
- }
169
-
170
- const isArray = Array.isArray;
171
- function isFunction(value) {
172
- return typeof value === "function";
173
- }
174
- function toTypeString(value) {
175
- return Object.prototype.toString.call(value);
176
- }
177
- function isSet(value) {
178
- return toTypeString(value) === "[object Set]";
179
- }
180
- function isDate(value) {
181
- return toTypeString(value) === "[object Date]";
182
- }
183
- function isSymbol(value) {
184
- return typeof value === "symbol";
185
- }
186
- function isObject(value) {
187
- return value !== null && typeof value === "object";
188
- }
189
- function looseToNumber(val) {
190
- const n = parseFloat(val);
191
- return isNaN(n) ? val : n;
192
- }
193
- function looseCompareArrays(a, b) {
194
- if (a.length !== b.length) return false;
195
- let equal = true;
196
- for (let i = 0; equal && i < a.length; i++) {
197
- equal = looseEqual(a[i], b[i]);
198
- }
199
- return equal;
200
- }
201
- function looseEqual(a, b) {
202
- if (a === b) return true;
203
- const aValidType = isDate(a);
204
- const bValidType = isDate(b);
205
- if (aValidType || bValidType) {
206
- return aValidType && bValidType ? a.getTime() === b.getTime() : false;
207
- }
208
- const aSymbol = isSymbol(a);
209
- const bSymbol = isSymbol(b);
210
- if (aSymbol || bSymbol) return a === b;
211
- const aIsArray = isArray(a);
212
- const bIsArray = isArray(b);
213
- if (aIsArray || bIsArray) {
214
- return aIsArray && bIsArray ? looseCompareArrays(a, b) : false;
473
+ formInstanceId: elementToFormInstance.get(el) ?? ""
474
+ });
475
+ }
476
+ }
477
+ for (const [key, target] of hostTargets) {
478
+ if (elements.has(key)) continue;
479
+ flat.push({
480
+ path: target.path,
481
+ element: target.hostRoot,
482
+ host: true,
483
+ formInstanceId: target.formInstanceId
484
+ });
485
+ }
486
+ flat.sort(
487
+ (a, b) => a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
488
+ );
489
+ return flat;
215
490
  }
216
- const aIsObject = isObject(a);
217
- const bIsObject = isObject(b);
218
- if (aIsObject !== bIsObject) return false;
219
- if (aIsObject && bIsObject) {
220
- const keysA = Object.keys(a);
221
- if (keysA.length !== Object.keys(b).length) return false;
222
- for (const key of keysA) {
223
- const hasA = Object.prototype.hasOwnProperty.call(a, key);
224
- const hasB = Object.prototype.hasOwnProperty.call(b, key);
225
- if (!hasA || !hasB || !looseEqual(a[key], b[key])) return false;
491
+ function getFirstErrorElement(formInstanceId) {
492
+ sortedRegistrationsCache ?? (sortedRegistrationsCache = rebuildSortedRegistrations());
493
+ for (const entry of sortedRegistrationsCache) {
494
+ if (entry.formInstanceId !== formInstanceId) continue;
495
+ if (!entry.element.isConnected) continue;
496
+ if (entry.element.offsetParent === null) continue;
497
+ if (kernel.getErrorsForPath(entry.path).length === 0) continue;
498
+ const element = entry.host ? resolveHostFocusTarget(entry.element) : entry.element;
499
+ return { path: entry.path, element };
226
500
  }
227
- return true;
501
+ return null;
228
502
  }
229
- return String(a) === String(b);
230
- }
231
- function looseIndexOf(arr, val) {
232
- return arr.findIndex((item) => looseEqual(item, val));
503
+ return {
504
+ elements,
505
+ attach,
506
+ detach,
507
+ markHostConnected,
508
+ getFirstErrorElement,
509
+ hostChannel: createHostChannel()
510
+ };
233
511
  }
234
- function invokeArrayFns(fns, ...args) {
235
- for (let i = 0; i < fns.length; i++) {
236
- const fn = fns[i];
237
- if (fn) fn(...args);
238
- }
512
+ function armDomBinding(value) {
513
+ return value.ensureDomBinding?.(createDomBinding);
239
514
  }
240
515
 
241
516
  const MANAGED_ARIA_ATTRS = [
@@ -386,197 +661,6 @@ function isRegisterValueLike(val) {
386
661
  return typeof val === "object" && val !== null && "markInteracted" in val && typeof val.markInteracted === "function";
387
662
  }
388
663
 
389
- const assignKey = /* @__PURE__ */ Symbol.for("attaform:assign-key");
390
- const DEFAULT_ASSIGNER_TAG = /* @__PURE__ */ Symbol.for("attaform:default-assigner-tag");
391
- function isDefaultAssigner(fn) {
392
- return typeof fn === "function" && fn[DEFAULT_ASSIGNER_TAG] === true;
393
- }
394
- const CONSUMER_WRAPPED_TAG = /* @__PURE__ */ Symbol.for("attaform:consumer-wrapped-assigner");
395
- function isConsumerWrapped(fn) {
396
- return typeof fn === "function" && fn[CONSUMER_WRAPPED_TAG] === true;
397
- }
398
- function fireAssigner(el, registerValue, value) {
399
- const fn = el[assignKey];
400
- if (fn === void 0) return void 0;
401
- if (isDefaultAssigner(fn) || isConsumerWrapped(fn)) {
402
- return fn(value);
403
- }
404
- if (!isRegisterValue(registerValue)) {
405
- return fn(value, void 0);
406
- }
407
- return wrapWithTransforms(
408
- value,
409
- registerValue,
410
- (coerced) => fn(coerced, registerValue),
411
- void 0
412
- );
413
- }
414
- function isThenable(x) {
415
- return x !== null && (typeof x === "object" || typeof x === "function") && typeof x.then === "function";
416
- }
417
- function makeTransformContext() {
418
- const holder = { controller: null, aborted: false };
419
- const ctx = {
420
- get signal() {
421
- if (holder.controller === null) {
422
- holder.controller = new AbortController();
423
- if (holder.aborted) holder.controller.abort();
424
- }
425
- return holder.controller.signal;
426
- }
427
- };
428
- return { ctx, holder };
429
- }
430
- function runTransforms(initial, registerValue) {
431
- const transforms = registerValue.transforms;
432
- if (transforms === void 0 || transforms.length === 0) {
433
- return { kind: "sync", ok: true, value: initial };
434
- }
435
- const { ctx, holder } = makeTransformContext();
436
- let v = initial;
437
- for (let i = 0; i < transforms.length; i++) {
438
- const fn = transforms[i];
439
- let out;
440
- try {
441
- out = fn(v, ctx);
442
- } catch (err) {
443
- logTransformFailure(registerValue.path, i, fn, err);
444
- return { kind: "sync", ok: false };
445
- }
446
- if (isThenable(out)) {
447
- const rest = transforms.slice(i + 1);
448
- const seed = out;
449
- const run = () => rest.reduce(
450
- (acc, next) => acc.then((value) => next(value, ctx)),
451
- Promise.resolve(seed)
452
- );
453
- return { kind: "async", run, holder };
454
- }
455
- v = out;
456
- }
457
- return { kind: "sync", ok: true, value: v };
458
- }
459
- function kickoffAsyncTransform(rv, holder, run, commit, syncDom) {
460
- const token = rv.beginTransform(holder);
461
- void run().then(
462
- (value) => {
463
- const live = rv.isCurrentTransform(token);
464
- rv.endTransform(token);
465
- if (!live) return;
466
- const coerced = applyCoerce(value, rv);
467
- const wrote = commit(coerced);
468
- if (wrote === false) rv.setTransformError(transformGateRejectedError(rv.path));
469
- else syncDom?.();
470
- },
471
- (err) => {
472
- if (rv.isCurrentTransform(token)) rv.setTransformError(toTransformError(err));
473
- rv.endTransform(token);
474
- }
475
- );
476
- }
477
- function logTransformFailure(path, index, fn, err) {
478
- {
479
- const namePart = fn.name !== "" ? `, '${fn.name}'` : "";
480
- console.error(
481
- `[attaform] transform threw for path '${path}' (index ${index}${namePart}) \u2014 write aborted. Transforms must not throw; wrap your own try/catch if the throw is recoverable. Original error:`,
482
- err
483
- );
484
- }
485
- }
486
- function applyCoerce(value, registerValue) {
487
- return registerValue.coerce !== void 0 ? registerValue.coerce(value) : value;
488
- }
489
- function wrapWithTransforms(value, registerValue, commit, syncDom) {
490
- const r = runTransforms(value, registerValue);
491
- if (r.kind === "async") {
492
- kickoffAsyncTransform(registerValue, r.holder, r.run, commit, syncDom);
493
- return true;
494
- }
495
- if (!r.ok) return false;
496
- const coerced = applyCoerce(r.value, registerValue);
497
- return commit(coerced);
498
- }
499
- function toTransformError(value) {
500
- if (value instanceof Error) return value;
501
- const message = typeof value === "string" && value.length > 0 ? value : `Transform rejected with a non-Error value (${typeof value})`;
502
- return new Error(message, { cause: value });
503
- }
504
- function transformGateRejectedError(path) {
505
- return new Error(
506
- `[attaform] transform result for path '${path}' was rejected by the field's type gate (the resolved value did not fit the schema slot).`
507
- );
508
- }
509
- const getModelAssigner = (el, vnode, registerValue) => {
510
- const fn = vnode.props?.["onUpdate:registerValue"] ?? vnode.props?.["on:update:registerValue"];
511
- if (isArray(fn)) {
512
- const fnArr = fn.filter((x) => isFunction(x));
513
- const wrapped = (value) => {
514
- return wrapWithTransforms(
515
- value,
516
- registerValue,
517
- (coerced) => {
518
- invokeArrayFns(fnArr, coerced, registerValue);
519
- return void 0;
520
- },
521
- void 0
522
- );
523
- };
524
- wrapped[CONSUMER_WRAPPED_TAG] = true;
525
- return wrapped;
526
- }
527
- if (isFunction(fn)) {
528
- const handler = fn;
529
- const wrapped = (value) => {
530
- return wrapWithTransforms(
531
- value,
532
- registerValue,
533
- (coerced) => handler(coerced, registerValue),
534
- void 0
535
- );
536
- };
537
- wrapped[CONSUMER_WRAPPED_TAG] = true;
538
- return wrapped;
539
- }
540
- const defaultAssigner = (value) => {
541
- if (value === void 0 && registerValue.acceptsUndefined) {
542
- return registerValue.setValueWithInternalPath(void 0);
543
- }
544
- return wrapWithTransforms(
545
- value,
546
- registerValue,
547
- (coerced) => registerValue.setValueWithInternalPath(coerced),
548
- el._syncFromStorage
549
- );
550
- };
551
- defaultAssigner[DEFAULT_ASSIGNER_TAG] = true;
552
- return defaultAssigner;
553
- };
554
- function makeNoopAssigner() {
555
- const noop = (_) => void 0;
556
- noop[DEFAULT_ASSIGNER_TAG] = true;
557
- return noop;
558
- }
559
- function setAssignFunction(el, vnode, value) {
560
- const current = el[assignKey];
561
- if (current !== void 0 && !isDefaultAssigner(current) && !isConsumerWrapped(current)) {
562
- return;
563
- }
564
- if (value === void 0) {
565
- el[assignKey] = makeNoopAssigner();
566
- return;
567
- }
568
- if (!isRegisterValue(value)) {
569
- {
570
- warn(
571
- `v-register expected a RegisterValue, got '${typeof value}'. Bind to form.register('field') \u2014 not the field's ref, value, or path string.`
572
- );
573
- }
574
- el[assignKey] = makeNoopAssigner();
575
- return;
576
- }
577
- el[assignKey] = getModelAssigner(el, vnode, value);
578
- }
579
-
580
664
  function isBlankFileValue(value) {
581
665
  if (value === null || value === void 0) return true;
582
666
  if (Array.isArray(value) && value.length === 0) return true;
@@ -668,11 +752,6 @@ function syncElementRegistration(el, value, oldValue) {
668
752
  }
669
753
 
670
754
  const valueSyncScopeKey = /* @__PURE__ */ Symbol.for("attaform:value-sync-scope");
671
- function isElementFocused(el) {
672
- const rootNode = el.getRootNode();
673
- const activeElement = rootNode instanceof Document || rootNode instanceof ShadowRoot ? rootNode.activeElement : null;
674
- return activeElement === el;
675
- }
676
755
  function setupValueSync(el, source, apply, options = {}) {
677
756
  const skipWhileFocused = options.skipWhileFocused === true;
678
757
  const scope = effectScope(true);
@@ -681,7 +760,7 @@ function setupValueSync(el, source, apply, options = {}) {
681
760
  source,
682
761
  () => {
683
762
  if (el.composing === true) return;
684
- if (skipWhileFocused && isElementFocused(el)) return;
763
+ if (skipWhileFocused && activeElementOf(el) === el) return;
685
764
  apply();
686
765
  },
687
766
  { flush: "post" }
@@ -740,6 +819,28 @@ function onCompositionEnd(e) {
740
819
  target.dispatchEvent(new Event("input"));
741
820
  }
742
821
  }
822
+ const heldDrafts = /* @__PURE__ */ new WeakMap();
823
+ function holdDraft(owner, control, stale) {
824
+ heldDrafts.set(owner, {
825
+ control,
826
+ text: control.value,
827
+ stale,
828
+ start: control.selectionStart,
829
+ end: control.selectionEnd
830
+ });
831
+ }
832
+ function restoreDraft(owner) {
833
+ const held = heldDrafts.get(owner);
834
+ if (held === void 0) return;
835
+ heldDrafts.delete(owner);
836
+ const { control, text, stale, start, end } = held;
837
+ if (control.value !== stale || stale === text) return;
838
+ control.value = text;
839
+ if (start !== null && end !== null) control.setSelectionRange(start, end);
840
+ }
841
+ function textControl(node) {
842
+ return node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement ? node : null;
843
+ }
743
844
  const vRegisterText = {
744
845
  created(el, { value, modifiers: { lazy, trim, number } }, vnode) {
745
846
  const castToNumberAtCreated = number === true || vnode.props?.["type"] === "number";
@@ -862,12 +963,14 @@ const vRegisterText = {
862
963
  mounted(el, { value }) {
863
964
  if (!isRegisterValue(value)) return;
864
965
  el.value = value.displayValue.value;
966
+ el._lastAppliedModel = value.innerRef.value;
865
967
  setupValueSync(
866
968
  el,
867
969
  value.displayValue,
868
970
  () => {
869
971
  const next = value.displayValue.value;
870
972
  if (el.value !== next) el.value = next;
973
+ el._lastAppliedModel = value.innerRef.value;
871
974
  },
872
975
  { skipWhileFocused: true }
873
976
  );
@@ -876,17 +979,16 @@ const vRegisterText = {
876
979
  setAssignFunction(el, vnode, value);
877
980
  if (el.composing === true) return;
878
981
  if (!isRegisterValue(value)) return;
982
+ const model = value.innerRef.value;
983
+ const modelMoved = model !== el._lastAppliedModel || !isRegisterValue(oldValue) || oldValue.path !== value.path;
984
+ el._lastAppliedModel = model;
879
985
  const target = value.displayValue.value;
880
986
  if (el.value === target) {
881
987
  return;
882
988
  }
883
- const rootNode = el.getRootNode();
884
- const activeElement = rootNode instanceof Document || rootNode instanceof ShadowRoot ? rootNode.activeElement : null;
885
- if (activeElement === el && el.type !== "range") {
886
- if (lazy === true && value.innerRef.value === oldValue) {
887
- return;
888
- }
889
- if (trim === true && el.value.trim() === target) {
989
+ if (activeElementOf(el) === el && el.type !== "range") {
990
+ if (lazy === true && !modelMoved || trim === true && el.value.trim() === target) {
991
+ holdDraft(el, el, target);
890
992
  return;
891
993
  }
892
994
  }
@@ -916,7 +1018,7 @@ const vRegisterCheckbox = {
916
1018
  if (rawElementValue === void 0) {
917
1019
  {
918
1020
  warn(
919
- 'Checkbox bound to an array model is missing a `value` attribute \u2014 cannot determine which item to add or remove. Add value="..." to each <input type="checkbox">.'
1021
+ 'Checkbox bound to an array model is missing a `value` attribute, so Attaform cannot determine which item to add or remove. Add value="..." to each <input type="checkbox">.'
920
1022
  );
921
1023
  }
922
1024
  return;
@@ -935,7 +1037,7 @@ const vRegisterCheckbox = {
935
1037
  if (rawElementValue === void 0) {
936
1038
  {
937
1039
  warn(
938
- 'Checkbox bound to a Set model is missing a `value` attribute \u2014 cannot determine which item to add or remove. Add value="..." to each <input type="checkbox">.'
1040
+ 'Checkbox bound to a Set model is missing a `value` attribute, so Attaform cannot determine which item to add or remove. Add value="..." to each <input type="checkbox">.'
939
1041
  );
940
1042
  }
941
1043
  return;
@@ -967,20 +1069,15 @@ const vRegisterCheckbox = {
967
1069
  el._lastAppliedModel = value.innerRef.value;
968
1070
  });
969
1071
  },
970
- // Skip the DOM sync when the model is identity-unchanged from the
971
- // last application. Pre-fix the scalar branch in `setChecked`
972
- // gated on `originalValue === oldValue`, comparing a primitive
973
- // scalar against the wrapper RegisterValue object — always !==,
974
- // so the guard was a silent no-op. Array / Set branches lacked
975
- // any guard. The per-render re-apply mirrors the just-fixed
976
- // `vRegisterSelect` shape: a sibling's reactive write triggers
977
- // `beforeUpdate` mid-click, `setChecked` re-applies the prior
978
- // model state, and the in-flight user toggle is clobbered before
979
- // the browser fires `change`. Identity comparison on
980
- // `innerRef.value` is sound for the same reason as multi-select —
981
- // every form write produces a fresh value at the path (new
982
- // primitives; new array/Set references along the spine), so
983
- // reference equality tracks "did the model move" exactly.
1072
+ // Skip the DOM sync when the model is identity-unchanged since the last
1073
+ // application. Without the guard, a sibling's reactive write triggers
1074
+ // `beforeUpdate` mid-click, `setChecked` re-applies the prior model state,
1075
+ // and the in-flight toggle is clobbered before the browser fires `change`.
1076
+ // Compare `innerRef.value` by identity: every form write produces a fresh
1077
+ // value at the path, new primitives and new array or Set references along
1078
+ // the spine, so reference equality tracks "did the model move" exactly. The
1079
+ // guard must NOT compare against `oldValue`, which is the previous binding's
1080
+ // wrapper RegisterValue and never equals a model scalar.
984
1081
  beforeUpdate(el, binding, vnode) {
985
1082
  setAssignFunction(el, vnode, binding.value);
986
1083
  if (!isRegisterValue(binding.value)) return;
@@ -1030,13 +1127,11 @@ const vRegisterRadio = {
1030
1127
  }
1031
1128
  });
1032
1129
  },
1033
- // Initial checked-state sync runs in `mounted`, NOT `created` —
1034
- // Vue's directive lifecycle fires `created` BEFORE the element's
1035
- // attributes are patched (`type`, `value`, `_value` etc. aren't on
1036
- // the element yet), so `getValue(el)` would return `undefined` and
1037
- // every radio in a group would mount unchecked regardless of the
1038
- // model. Checkbox already uses `mounted: setChecked` for the same
1039
- // reason.
1130
+ // The initial checked sync runs in `mounted`, NOT `created`: Vue fires
1131
+ // `created` before the element's attributes are patched, so `type`, `value`
1132
+ // and `_value` are absent, `getValue(el)` returns undefined, and every radio
1133
+ // in a group mounts unchecked whatever the model says. Checkbox uses
1134
+ // `mounted` for the same reason.
1040
1135
  mounted(el, { value }) {
1041
1136
  if (!isRegisterValue(value)) return;
1042
1137
  el.checked = looseEqual(value.innerRef.value, applyCoerce(getValue(el), value));
@@ -1046,15 +1141,12 @@ const vRegisterRadio = {
1046
1141
  el._lastAppliedModel = value.innerRef.value;
1047
1142
  });
1048
1143
  },
1049
- // Skip the DOM sync when the model is identity-unchanged from the
1050
- // last application. Pre-fix the guard read `value.innerRef.value
1051
- // !== oldValue`, comparing a primitive scalar against the previous
1052
- // binding's wrapper RegisterValue object — always !==, so the
1053
- // guard was a silent no-op and `el.checked = …` re-applied on
1054
- // every parent re-render. Same shape as the just-fixed
1055
- // `vRegisterSelect` and `setChecked` bugs: a sibling's reactive
1056
- // write triggers `beforeUpdate` mid-click and writes back the
1057
- // prior model state, clobbering the in-flight selection.
1144
+ // Skip the DOM sync when the model is identity-unchanged since the last
1145
+ // application, tracked on `_lastAppliedModel`. Comparing against `oldValue`
1146
+ // instead compares a primitive scalar to the previous binding's wrapper
1147
+ // RegisterValue, never equal, so `el.checked` re-applies on every parent
1148
+ // re-render and a sibling's reactive write clobbers an in-flight selection
1149
+ // mid-click.
1058
1150
  beforeUpdate(el, { value }, vnode) {
1059
1151
  if (!isRegisterValue(value)) return;
1060
1152
  setAssignFunction(el, vnode, value);
@@ -1113,22 +1205,20 @@ const vRegisterSelect = {
1113
1205
  beforeUpdate(el, binding, vnode) {
1114
1206
  setAssignFunction(el, vnode, binding.value);
1115
1207
  },
1116
- // Skip the DOM sync when the model is identity-unchanged from the
1117
- // last application. Parent re-renders fire `updated` whether or not
1118
- // the bound model actually moved (a typed character in a sibling,
1119
- // an async-validation tick, any reactive read elsewhere on the
1120
- // page). Without this guard, every such render unconditionally re-
1121
- // applies `setSelected` against the prior model, which on a
1122
- // `<select multiple>` clobbers any in-progress user selection
1123
- // between mousedown and the browser's change-event decision — the
1124
- // browser then sees no net change, never fires `change`, and the
1125
- // model never updates. Identity comparison is sound: every form
1126
- // write produces a new array/Set reference at the path (the diff-
1127
- // apply replacement of `form.value` rolls forward fresh structures
1128
- // along the spine), so reference equality on `innerRef.value`
1129
- // tracks "did the model move" exactly. The `_assigning` gate stays
1130
- // — it short-circuits the immediate post-write render where the
1131
- // DOM is already in sync from the user's click.
1208
+ // Skip the DOM sync when the model is identity-unchanged since the last
1209
+ // application. A parent re-render fires `updated` whether or not the bound
1210
+ // model moved: a typed character in a sibling, an async-validation tick, any
1211
+ // reactive read elsewhere on the page. Re-applying `setSelected` against the
1212
+ // prior model on each of those clobbers an in-progress `<select multiple>`
1213
+ // selection between mousedown and the browser's change decision, after which
1214
+ // the browser sees no net change, never fires `change`, and the model never
1215
+ // updates.
1216
+ //
1217
+ // Identity comparison is sound: every form write produces a new array or Set
1218
+ // reference at the path, the replacement of `form.value` rolling fresh
1219
+ // structures forward along the spine. The `_assigning` gate stays as well,
1220
+ // short-circuiting the immediate post-write render where the DOM is already
1221
+ // in sync from the user's click.
1132
1222
  updated(el, { value }) {
1133
1223
  if (el._assigning === true) return;
1134
1224
  if (!isRegisterValue(value)) return;
@@ -1277,6 +1367,29 @@ function observeForLateHostControl(el, rv) {
1277
1367
  observer.observe(el, { childList: true, subtree: true });
1278
1368
  hostHealObservers.set(el, observer);
1279
1369
  }
1370
+ const hostEdits = /* @__PURE__ */ new WeakMap();
1371
+ function trackHostEdits(el, rv, modifiers) {
1372
+ if (modifiers.lazy !== true && modifiers.trim !== true) return;
1373
+ if (!INTERACTIVE_TAG_NAMES.has(el.tagName) && rv.hasRegisteredDescendant(el)) return;
1374
+ const channel = armDomBinding(rv)?.hostChannel;
1375
+ if (channel === void 0) return;
1376
+ const edits = { channel, rv, base: void 0 };
1377
+ const edge = (editing) => (event) => {
1378
+ if (editing && !channel.holdsDraft(rv)) edits.base = textControl(event.target)?.value;
1379
+ const other = event.relatedTarget;
1380
+ if (other instanceof Node && el.contains(other)) return;
1381
+ channel.markEditing(rv, editing, modifiers);
1382
+ };
1383
+ addTrackedListener(el, "focus", edge(true), { capture: true });
1384
+ addTrackedListener(el, "blur", edge(false), { capture: true });
1385
+ hostEdits.set(el, edits);
1386
+ }
1387
+ function holdHostDraft(el) {
1388
+ const edits = hostEdits.get(el);
1389
+ if (edits?.base === void 0 || !edits.channel.holdsDraft(edits.rv)) return;
1390
+ const control = textControl(activeElementOf(el));
1391
+ if (control !== null && el.contains(control)) holdDraft(el, control, edits.base);
1392
+ }
1280
1393
  function activateComponentHost(el, rv) {
1281
1394
  if (rv.hasRegisteredDescendant(el)) return;
1282
1395
  el[REGISTER_OWNER_MARKER] = true;
@@ -1291,19 +1404,26 @@ function activateComponentHost(el, rv) {
1291
1404
  setupNoLatchHost(el, rv);
1292
1405
  scheduleHostSelfHeal(el, rv);
1293
1406
  }
1407
+ function inheritModifiers(binding) {
1408
+ const modifiers = withInheritedModifiers(binding.value, binding.modifiers);
1409
+ return modifiers === binding.modifiers ? binding : { ...binding, modifiers };
1410
+ }
1294
1411
  const vRegisterDynamic = {
1295
- created(el, binding, vnode) {
1412
+ created(el, ownBinding, vnode) {
1413
+ const binding = inheritModifiers(ownBinding);
1296
1414
  if (isRegisterValue(binding.value)) armDomBinding(binding.value);
1297
1415
  callModelHook(el, binding, vnode, null, "created");
1298
1416
  if (isRegisterValue(binding.value)) setupAria(el, binding.value, vnode);
1299
1417
  warnRedundantStateBinding(el, binding, vnode);
1300
1418
  },
1301
- mounted(el, binding, vnode) {
1419
+ mounted(el, ownBinding, vnode) {
1420
+ const binding = inheritModifiers(ownBinding);
1302
1421
  callModelHook(el, binding, vnode, null, "mounted");
1303
1422
  if (isRegisterValue(binding.value) && INTERACTIVE_TAG_NAMES.has(el.tagName) && binding.modifiers[SSR_COMPONENT_HOST_MODIFIER] !== true && vnode.props?.["disabled"] === void 0) {
1304
1423
  setupDisabledSync(el, binding.value.disabled);
1305
1424
  }
1306
1425
  if (binding.modifiers[SSR_COMPONENT_HOST_MODIFIER] === true && isRegisterValue(binding.value)) {
1426
+ trackHostEdits(el, binding.value, binding.modifiers);
1307
1427
  activateComponentHost(el, binding.value);
1308
1428
  }
1309
1429
  if (warnedUnsupportedElements !== null && !INTERACTIVE_TAG_NAMES.has(el.tagName) && !warnedUnsupportedElements.has(el)) {
@@ -1316,15 +1436,17 @@ const vRegisterDynamic = {
1316
1436
  if (hasMarker || hasUserAssigner) return;
1317
1437
  warnedUnsupportedElements.add(el);
1318
1438
  warn(
1319
- `[attaform] v-register on <${el.tagName.toLowerCase()}> is a no-op \u2014 non-input roots aren't bound to text-input semantics. For custom components: call \`useRegister()\` in the child's setup and re-bind v-register to an inner native element. Lower-level: install a custom assigner via the \`assignKey\` symbol on the element.`
1439
+ `[attaform] v-register on <${el.tagName.toLowerCase()}> is a no-op: non-input roots aren't bound to text-input semantics. For custom components: call \`useRegister()\` in the child's setup and re-bind v-register to an inner native element. Lower-level: install a custom assigner via the \`assignKey\` symbol on the element.`
1320
1440
  );
1321
1441
  });
1322
1442
  }
1323
1443
  },
1324
- beforeUpdate(el, binding, vnode, prevVNode) {
1444
+ beforeUpdate(el, ownBinding, vnode, prevVNode) {
1445
+ const binding = inheritModifiers(ownBinding);
1325
1446
  if (isRegisterValue(binding.value)) armDomBinding(binding.value);
1326
1447
  syncElementRegistration(el, binding.value, binding.oldValue);
1327
1448
  callModelHook(el, binding, vnode, prevVNode, "beforeUpdate");
1449
+ holdHostDraft(el);
1328
1450
  const ariaEl = el;
1329
1451
  const value = binding.value;
1330
1452
  if (!isRegisterValue(value) || value.ariaDisplayState === void 0) {
@@ -1342,13 +1464,17 @@ const vRegisterDynamic = {
1342
1464
  }
1343
1465
  },
1344
1466
  updated(el, binding, vnode, prevVNode) {
1345
- callModelHook(el, binding, vnode, prevVNode, "updated");
1467
+ callModelHook(el, inheritModifiers(binding), vnode, prevVNode, "updated");
1468
+ restoreDraft(el);
1346
1469
  },
1347
1470
  beforeUnmount(el, { value }) {
1348
1471
  removeTrackedListeners(el);
1349
1472
  teardownAria(el);
1350
1473
  teardownValueSync(el);
1351
1474
  teardownDisabledSync(el);
1475
+ const edits = hostEdits.get(el);
1476
+ edits?.channel.release(edits.rv);
1477
+ hostEdits.delete(el);
1352
1478
  if (!isRegisterValue(value)) return;
1353
1479
  value.deregisterElement(el);
1354
1480
  if (componentHostLatch.has(el)) {
@@ -1369,13 +1495,12 @@ const vRegisterDynamic = {
1369
1495
  delete el._syncFromStorage;
1370
1496
  delete el[assignKey];
1371
1497
  },
1372
- // The lifecycle hooks above don't run on the server (Vue skips
1373
- // directive lifecycle during SSR), so emit the same aria attributes
1374
- // here from the SSR-time gated display state. Honors authored attrs
1375
- // (vnode-level lockout), touches no DOM, and shares
1376
- // `resolveAriaValue` with the client path. Ids are SSR-stable
1377
- // (formInstanceId derives from Vue's useId), so a server-rendered
1378
- // describedby matches the client after hydration.
1498
+ // Vue skips directive lifecycle during SSR, so the hooks above never run on
1499
+ // the server and the same aria attributes are emitted here from the SSR-time
1500
+ // gated display state. Honours authored attrs through the vnode-level
1501
+ // lockout, touches no DOM, and shares `resolveAriaValue` with the client
1502
+ // path. Ids are SSR-stable, `formInstanceId` deriving from Vue's `useId`, so
1503
+ // a server-rendered describedby matches the client after hydration.
1379
1504
  getSSRProps(binding, vnode) {
1380
1505
  const rv = binding.value;
1381
1506
  if (!isRegisterValue(rv)) return void 0;
@@ -1462,4 +1587,4 @@ function installVRegister(app) {
1462
1587
  }
1463
1588
  }
1464
1589
 
1465
- export { REGISTER_OWNER_MARKER as R, V_REGISTER_MARKER as V, assignKey as a, installVRegister as b, vRegisterFile as c, armDomBinding as d, isRegisterValue as i, vRegister as v };
1590
+ export { REGISTER_OWNER_MARKER as R, V_REGISTER_MARKER as V, assignKey as a, installVRegister as b, vRegisterFile as c, armDomBinding as d, isRegisterValue as i, recordInheritedModifiers as r, vRegister as v, withInheritedModifiers as w };