attaform 0.18.2 → 0.19.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.
- package/README.md +3 -0
- package/dist/chunks/devtools.cjs +1 -1
- package/dist/chunks/devtools.mjs +1 -1
- package/dist/chunks/indexeddb.cjs +1 -1
- package/dist/chunks/indexeddb.mjs +1 -1
- package/dist/chunks/local-storage.cjs +1 -1
- package/dist/chunks/local-storage.mjs +1 -1
- package/dist/chunks/session-storage.cjs +1 -1
- package/dist/chunks/session-storage.mjs +1 -1
- package/dist/index.cjs +4 -4
- package/dist/index.d.cts +68 -75
- package/dist/index.d.mts +68 -75
- package/dist/index.d.ts +68 -75
- package/dist/index.mjs +5 -5
- package/dist/nuxt.d.cts +1 -1
- package/dist/nuxt.d.mts +1 -1
- package/dist/nuxt.d.ts +1 -1
- package/dist/runtime/plugins/attaform.cjs +2 -2
- package/dist/runtime/plugins/attaform.mjs +2 -2
- package/dist/shared/{attaform.CZ-XtZt_.mjs → attaform.BTi-PsHr.mjs} +544 -134
- package/dist/shared/attaform.BTi-PsHr.mjs.map +1 -0
- package/dist/shared/{attaform.CuN7ZhBy.d.cts → attaform.BTpuvGec.d.ts} +44 -11
- package/dist/shared/{attaform.BqK_L4gK.cjs → attaform.BqEfHpVB.cjs} +119 -1
- package/dist/shared/attaform.BqEfHpVB.cjs.map +1 -0
- package/dist/shared/{attaform.D1gzu2GL.d.mts → attaform.BtBmfLQN.d.mts} +44 -11
- package/dist/shared/{attaform.Ca5_6Ky-.d.cts → attaform.C0uGZQ4M.d.cts} +365 -86
- package/dist/shared/{attaform.Ca5_6Ky-.d.mts → attaform.C0uGZQ4M.d.mts} +365 -86
- package/dist/shared/{attaform.Ca5_6Ky-.d.ts → attaform.C0uGZQ4M.d.ts} +365 -86
- package/dist/shared/{attaform.II89Pcf4.cjs → attaform.C1msmO2v.cjs} +544 -134
- package/dist/shared/attaform.C1msmO2v.cjs.map +1 -0
- package/dist/shared/{attaform.CRmmNAYp.d.cts → attaform.CBjmobqk.d.cts} +1 -1
- package/dist/shared/{attaform.B957T6NU.d.ts → attaform.CJ-e9gYI.d.ts} +1 -1
- package/dist/shared/{attaform.XDjA7sRz.d.cts → attaform.CRNA0vrd.d.mts} +1 -1
- package/dist/shared/{attaform.Dl161U6E.mjs → attaform.Cghpuav8.mjs} +2 -2
- package/dist/shared/{attaform.Dl161U6E.mjs.map → attaform.Cghpuav8.mjs.map} +1 -1
- package/dist/shared/{attaform.Df0tU0Ut.mjs → attaform.CiMqJHDm.mjs} +3 -3
- package/dist/shared/{attaform.Df0tU0Ut.mjs.map → attaform.CiMqJHDm.mjs.map} +1 -1
- package/dist/shared/{attaform.5UhpSVFI.cjs → attaform.CoxJ8Qm8.cjs} +2 -2
- package/dist/shared/{attaform.5UhpSVFI.cjs.map → attaform.CoxJ8Qm8.cjs.map} +1 -1
- package/dist/shared/{attaform.CDmaxrt2.mjs → attaform.CrpjyXdO.mjs} +119 -1
- package/dist/shared/attaform.CrpjyXdO.mjs.map +1 -0
- package/dist/shared/{attaform.FnEwjhvX.d.ts → attaform.D4I63aBV.d.ts} +1 -1
- package/dist/shared/{attaform.D9wuTGu9.d.mts → attaform.DXYHL99q.d.mts} +1 -1
- package/dist/shared/{attaform.Dlk1jMuv.cjs → attaform.JBx8cfMA.cjs} +3 -3
- package/dist/shared/{attaform.Dlk1jMuv.cjs.map → attaform.JBx8cfMA.cjs.map} +1 -1
- package/dist/shared/{attaform.DUHru0OF.cjs → attaform.OznWyOPy.cjs} +3 -3
- package/dist/shared/{attaform.DUHru0OF.cjs.map → attaform.OznWyOPy.cjs.map} +1 -1
- package/dist/shared/{attaform.M-RanbyV.d.mts → attaform.QvygsFGh.d.cts} +1 -1
- package/dist/shared/{attaform.-1GQTX2T.mjs → attaform.a3uBo-gw.mjs} +3 -3
- package/dist/shared/{attaform.-1GQTX2T.mjs.map → attaform.a3uBo-gw.mjs.map} +1 -1
- package/dist/shared/{attaform.CGX1CNpz.d.ts → attaform.ePUcKxId.d.cts} +44 -11
- package/dist/zod-v3.cjs +3 -3
- package/dist/zod-v3.d.cts +4 -4
- package/dist/zod-v3.d.mts +4 -4
- package/dist/zod-v3.d.ts +4 -4
- package/dist/zod-v3.mjs +3 -3
- package/dist/zod-v4.cjs +3 -3
- package/dist/zod-v4.d.cts +4 -4
- package/dist/zod-v4.d.mts +4 -4
- package/dist/zod-v4.d.ts +4 -4
- package/dist/zod-v4.mjs +3 -3
- package/dist/zod.cjs +4 -4
- package/dist/zod.d.cts +6 -6
- package/dist/zod.d.mts +6 -6
- package/dist/zod.d.ts +6 -6
- package/dist/zod.mjs +5 -5
- package/package.json +2 -2
- package/dist/shared/attaform.BqK_L4gK.cjs.map +0 -1
- package/dist/shared/attaform.CDmaxrt2.mjs.map +0 -1
- package/dist/shared/attaform.CZ-XtZt_.mjs.map +0 -1
- package/dist/shared/attaform.II89Pcf4.cjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const paths = require('./attaform.
|
|
3
|
+
const paths = require('./attaform.BqEfHpVB.cjs');
|
|
4
4
|
|
|
5
5
|
function renderAttaformState(app) {
|
|
6
6
|
const registry = paths.getRegistryFromApp(app);
|
|
@@ -60,4 +60,4 @@ exports.REDACTED = REDACTED;
|
|
|
60
60
|
exports.hydrateAttaformState = hydrateAttaformState;
|
|
61
61
|
exports.redactSensitiveLeaves = redactSensitiveLeaves;
|
|
62
62
|
exports.renderAttaformState = renderAttaformState;
|
|
63
|
-
//# sourceMappingURL=attaform.
|
|
63
|
+
//# sourceMappingURL=attaform.CoxJ8Qm8.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attaform.5UhpSVFI.cjs","sources":["../../src/runtime/core/serialize.ts","../../src/runtime/core/devtools-shared.ts"],"sourcesContent":["import type { App } from 'vue'\nimport type { FormKey } from '../types/types-api'\nimport { pathKeyToDotted, type PathKey } from './paths'\nimport { getRegistryFromApp, type SerializedFormData } from './registry'\n\n/**\n * Serialised snapshot of every form in a Vue app, produced by\n * `renderAttaformState` and consumed by `hydrateAttaformState`.\n *\n * JSON-safe — pass to `JSON.stringify`, `devalue`, or any other\n * serialiser before embedding in your SSR payload.\n */\nexport type SerializedAttaformState = {\n /** Tuples of `[formKey, snapshot]` for every form in the app. */\n readonly forms: ReadonlyArray<readonly [FormKey, SerializedFormData]>\n}\n\n/**\n * Snapshot every form on a Vue app for SSR. Call from your server\n * entry after rendering the app:\n *\n * ```ts\n * import { renderToString } from '@vue/server-renderer'\n * import { renderAttaformState, escapeForInlineScript } from 'attaform'\n *\n * const html = await renderToString(app)\n * const state = renderAttaformState(app)\n * const payload = escapeForInlineScript(JSON.stringify(state))\n *\n * return `\n * ${html}\n * <script>window.__ATTAFORM_STATE__ = ${payload}</script>\n * `\n * ```\n *\n * Pair with `hydrateAttaformState` on the client to restore the\n * forms in their server-rendered state. Nuxt users don't need this —\n * `attaform/nuxt` wires SSR automatically.\n */\nexport function renderAttaformState(app: App): SerializedAttaformState {\n const registry = getRegistryFromApp(app)\n const forms: Array<readonly [FormKey, SerializedFormData]> = []\n for (const [key, state] of registry.forms) {\n // Skip the blank field when the set is empty so the\n // wire payload stays minimal for forms that don't use it. The\n // optional shape on the consuming side handles the absence\n // cleanly (defaults to \"no blank paths\"). PathKey → dotted at\n // the boundary so the wire shape matches the rest of the\n // public path notation.\n const transientList: string[] = []\n for (const pk of state.blankPaths) {\n const d = pathKeyToDotted(pk as PathKey)\n if (d !== null) transientList.push(d)\n }\n forms.push([\n key,\n {\n form: state.form.value,\n schemaErrors: Array.from(state.schemaErrors.entries()),\n userErrors: Array.from(state.userErrors.entries()),\n fields: Array.from(state.fields.entries()),\n ...(transientList.length > 0 ? { blankPaths: transientList } : {}),\n },\n ])\n }\n return { forms }\n}\n\n/**\n * Restore forms from a server-rendered snapshot on the client. Call\n * from your client entry before mounting:\n *\n * ```ts\n * import { createApp } from 'vue'\n * import { createAttaform, hydrateAttaformState } from 'attaform'\n *\n * const app = createApp(App).use(createAttaform())\n * hydrateAttaformState(app, window.__ATTAFORM_STATE__)\n * app.mount('#app')\n * ```\n *\n * The next `useForm({ key })` call for each serialised form picks up\n * the snapshot transparently — no further action is required.\n */\nexport function hydrateAttaformState(app: App, payload: SerializedAttaformState): void {\n const registry = getRegistryFromApp(app)\n for (const [key, data] of payload.forms) {\n registry.pendingHydration.set(key, data)\n }\n}\n","/**\n * Shared building blocks for Attaform's two devtools surfaces — the Vue\n * DevTools (Chrome-extension) inspector wired up in `./devtools.ts`, and\n * the Nuxt DevTools (overlay) panel wired up via `../../nuxt.ts` +\n * `../pages/_attaform_devtools.vue`.\n *\n * Centralizing the redaction policy and the window-bridge contract here\n * keeps both surfaces aligned: a future tightening of the sensitive-name\n * heuristic, or a new field added to the bridge, lands in one file.\n */\nimport type { AttaformRegistry } from './registry'\nimport type { Segment } from './paths'\n\nexport const REDACTED = '[redacted]'\n\n/**\n * Walk `value` and replace any leaf whose enclosing path matches the\n * sensitive-name heuristic with the string `'[redacted]'`. Returns a\n * new tree (no mutation of the input). Object keys + array indices are\n * preserved; only the leaf payloads change.\n *\n * Applied to BOTH devtools surfaces' Form-value rendering AND every\n * timeline event payload — leaks via either surface are treatable as\n * \"any developer with the panel open during user testing can read a\n * customer's password,\" which is exactly the failure mode the\n * sensitive-name guard exists to prevent on the storage side.\n *\n * Leaves whose path doesn't match a pattern pass through untouched.\n * `acknowledgeSensitive: true` on persistence does NOT bypass this — if\n * the consumer opted into persisting the value, they still shouldn't\n * see it in DevTools timelines that grow unbounded.\n *\n * Implementation note: tracks an `inSensitiveSubtree` flag through the\n * recursion instead of allocating a fresh path array per node + calling\n * `isSensitivePath` per leaf. Once any ancestor segment matches the\n * heuristic, the flag stays set for every descendant — the leaf simply\n * returns `REDACTED` without re-scanning the path. For a 100-leaf form:\n * ~100 path allocations + ~100 full-path regex sweeps → 0 path\n * allocations + ~100 single-segment regex sweeps, with whole-subtree\n * short-circuit when sensitive ancestors are found early.\n */\nexport function redactSensitiveLeaves(\n value: unknown,\n matchSensitive: (segment: Segment) => boolean\n): unknown {\n return redactImpl(value, false, matchSensitive)\n}\n\nfunction redactImpl(\n value: unknown,\n inSensitiveSubtree: boolean,\n matchSensitive: (segment: Segment) => boolean\n): unknown {\n if (value === null || value === undefined) return value\n if (typeof value !== 'object') {\n return inSensitiveSubtree ? REDACTED : value\n }\n if (Array.isArray(value)) {\n // Numeric segments never match the sensitive-name heuristic\n // (segmentMatchesSensitive rejects non-string segments), so the\n // flag passes through unchanged when descending into arrays.\n return value.map((item) => redactImpl(item, inSensitiveSubtree, matchSensitive))\n }\n // Non-plain object (Map / Set / Date / class instance) — redact\n // wholesale if we're already in a sensitive subtree; otherwise pass\n // through. DevTools rendering of these is already heuristic, so we\n // don't try to descend into them.\n //\n // Use `Object.prototype.toString.call(value)` rather than a\n // `getPrototypeOf` comparison because `Object.prototype` is\n // realm-scoped — the Nuxt DevTools overlay panel runs in an iframe\n // whose Vue runtime is separate from the host's, so the host's\n // reactive proxies have a prototype that doesn't equal the panel's\n // `Object.prototype`. The `toString` tag check is realm-aware via\n // `@@toStringTag` and returns `'[object Object]'` for plain objects\n // (including Vue reactive proxies of plain objects) regardless of\n // which iframe they were created in.\n if (Object.prototype.toString.call(value) !== '[object Object]') {\n return inSensitiveSubtree ? REDACTED : value\n }\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const childSensitive = inSensitiveSubtree || matchSensitive(key)\n out[key] = redactImpl((value as Record<string, unknown>)[key], childSensitive, matchSensitive)\n }\n return out\n}\n\n/**\n * Property key on `window` that the Nuxt-side dev plugin attaches the\n * bridge object to. The iframe-mounted overlay panel reads\n * `window.parent[DEVTOOLS_WINDOW_KEY]` to reach the host app's registry.\n *\n * Underscored + namespaced to make accidental collision with consumer\n * globals vanishingly unlikely. Stable across versions — bumping it\n * would silently disconnect older library builds from newer overlay\n * panels in the same browser tab during a library upgrade.\n */\nexport const DEVTOOLS_WINDOW_KEY = '__attaform_devtools__'\n\n/**\n * Shape of the object the host plugin attaches to `window` in dev mode.\n * The iframe overlay panel reads this to discover the live registry and\n * render its forms.\n *\n * Single-registry assumption: the latest `createAttaform()` install\n * wins. Multi-app pages (rare; typically only seen in micro-frontend\n * setups) will only see one app's forms in the panel. Documented but\n * not actively supported — the alternative (a Set of registries with\n * union-rendering) is a future call if a real consumer hits it.\n */\nexport interface AttaformDevtoolsBridge {\n registry: AttaformRegistry\n /**\n * The library version, surfaced in the panel's footer for support /\n * bug-report context. Read from `package.json` at host-plugin init.\n */\n version: string\n}\n\ndeclare global {\n interface Window {\n [DEVTOOLS_WINDOW_KEY]?: AttaformDevtoolsBridge\n }\n}\n"],"names":["getRegistryFromApp","pathKeyToDotted"],"mappings":";;;;AAuCO,SAAS,oBAAoB,GAAA,EAAmC;AACrE,EAAA,MAAM,QAAA,GAAWA,yBAAmB,GAAG,CAAA;AACvC,EAAA,MAAM,QAAuD,EAAC;AAC9D,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,SAAS,KAAA,EAAO;AAOzC,IAAA,MAAM,gBAA0B,EAAC;AACjC,IAAA,KAAA,MAAW,EAAA,IAAM,MAAM,UAAA,EAAY;AACjC,MAAA,MAAM,CAAA,GAAIC,sBAAgB,EAAa,CAAA;AACvC,MAAA,IAAI,CAAA,KAAM,IAAA,EAAM,aAAA,CAAc,IAAA,CAAK,CAAC,CAAA;AAAA,IACtC;AACA,IAAA,KAAA,CAAM,IAAA,CAAK;AAAA,MACT,GAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAM,MAAM,IAAA,CAAK,KAAA;AAAA,QACjB,cAAc,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,YAAA,CAAa,SAAS,CAAA;AAAA,QACrD,YAAY,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,UAAA,CAAW,SAAS,CAAA;AAAA,QACjD,QAAQ,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,SAAS,CAAA;AAAA,QACzC,GAAI,cAAc,MAAA,GAAS,CAAA,GAAI,EAAE,UAAA,EAAY,aAAA,KAAkB;AAAC;AAClE,KACD,CAAA;AAAA,EACH;AACA,EAAA,OAAO,EAAE,KAAA,EAAM;AACjB;AAkBO,SAAS,oBAAA,CAAqB,KAAU,OAAA,EAAwC;AACrF,EAAA,MAAM,QAAA,GAAWD,yBAAmB,GAAG,CAAA;AACvC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,CAAA,IAAK,QAAQ,KAAA,EAAO;AACvC,IAAA,QAAA,CAAS,gBAAA,CAAiB,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA;AAAA,EACzC;AACF;;AC5EO,MAAM,QAAA,GAAW;AA4BjB,SAAS,qBAAA,CACd,OACA,cAAA,EACS;AACT,EAAA,OAAO,UAAA,CAAW,KAAA,EAAO,KAAA,EAAO,cAAc,CAAA;AAChD;AAEA,SAAS,UAAA,CACP,KAAA,EACA,kBAAA,EACA,cAAA,EACS;AACT,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW,OAAO,KAAA;AAClD,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,qBAAqB,QAAA,GAAW,KAAA;AAAA,EACzC;AACA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAIxB,IAAA,OAAO,KAAA,CAAM,IAAI,CAAC,IAAA,KAAS,WAAW,IAAA,EAAM,kBAAA,EAAoB,cAAc,CAAC,CAAA;AAAA,EACjF;AAeA,EAAA,IAAI,OAAO,SAAA,CAAU,QAAA,CAAS,IAAA,CAAK,KAAK,MAAM,iBAAA,EAAmB;AAC/D,IAAA,OAAO,qBAAqB,QAAA,GAAW,KAAA;AAAA,EACzC;AACA,EAAA,MAAM,MAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,KAAgC,CAAA,EAAG;AAC/D,IAAA,MAAM,cAAA,GAAiB,kBAAA,IAAsB,cAAA,CAAe,GAAG,CAAA;AAC/D,IAAA,GAAA,CAAI,GAAG,CAAA,GAAI,UAAA,CAAY,MAAkC,GAAG,CAAA,EAAG,gBAAgB,cAAc,CAAA;AAAA,EAC/F;AACA,EAAA,OAAO,GAAA;AACT;AAYO,MAAM,mBAAA,GAAsB;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"attaform.CoxJ8Qm8.cjs","sources":["../../src/runtime/core/serialize.ts","../../src/runtime/core/devtools-shared.ts"],"sourcesContent":["import type { App } from 'vue'\nimport type { FormKey } from '../types/types-api'\nimport { pathKeyToDotted, type PathKey } from './paths'\nimport { getRegistryFromApp, type SerializedFormData } from './registry'\n\n/**\n * Serialised snapshot of every form in a Vue app, produced by\n * `renderAttaformState` and consumed by `hydrateAttaformState`.\n *\n * JSON-safe — pass to `JSON.stringify`, `devalue`, or any other\n * serialiser before embedding in your SSR payload.\n */\nexport type SerializedAttaformState = {\n /** Tuples of `[formKey, snapshot]` for every form in the app. */\n readonly forms: ReadonlyArray<readonly [FormKey, SerializedFormData]>\n}\n\n/**\n * Snapshot every form on a Vue app for SSR. Call from your server\n * entry after rendering the app:\n *\n * ```ts\n * import { renderToString } from '@vue/server-renderer'\n * import { renderAttaformState, escapeForInlineScript } from 'attaform'\n *\n * const html = await renderToString(app)\n * const state = renderAttaformState(app)\n * const payload = escapeForInlineScript(JSON.stringify(state))\n *\n * return `\n * ${html}\n * <script>window.__ATTAFORM_STATE__ = ${payload}</script>\n * `\n * ```\n *\n * Pair with `hydrateAttaformState` on the client to restore the\n * forms in their server-rendered state. Nuxt users don't need this —\n * `attaform/nuxt` wires SSR automatically.\n */\nexport function renderAttaformState(app: App): SerializedAttaformState {\n const registry = getRegistryFromApp(app)\n const forms: Array<readonly [FormKey, SerializedFormData]> = []\n for (const [key, state] of registry.forms) {\n // Skip the blank field when the set is empty so the\n // wire payload stays minimal for forms that don't use it. The\n // optional shape on the consuming side handles the absence\n // cleanly (defaults to \"no blank paths\"). PathKey → dotted at\n // the boundary so the wire shape matches the rest of the\n // public path notation.\n const transientList: string[] = []\n for (const pk of state.blankPaths) {\n const d = pathKeyToDotted(pk as PathKey)\n if (d !== null) transientList.push(d)\n }\n forms.push([\n key,\n {\n form: state.form.value,\n schemaErrors: Array.from(state.schemaErrors.entries()),\n userErrors: Array.from(state.userErrors.entries()),\n fields: Array.from(state.fields.entries()),\n ...(transientList.length > 0 ? { blankPaths: transientList } : {}),\n },\n ])\n }\n return { forms }\n}\n\n/**\n * Restore forms from a server-rendered snapshot on the client. Call\n * from your client entry before mounting:\n *\n * ```ts\n * import { createApp } from 'vue'\n * import { createAttaform, hydrateAttaformState } from 'attaform'\n *\n * const app = createApp(App).use(createAttaform())\n * hydrateAttaformState(app, window.__ATTAFORM_STATE__)\n * app.mount('#app')\n * ```\n *\n * The next `useForm({ key })` call for each serialised form picks up\n * the snapshot transparently — no further action is required.\n */\nexport function hydrateAttaformState(app: App, payload: SerializedAttaformState): void {\n const registry = getRegistryFromApp(app)\n for (const [key, data] of payload.forms) {\n registry.pendingHydration.set(key, data)\n }\n}\n","/**\n * Shared building blocks for Attaform's two devtools surfaces — the Vue\n * DevTools (Chrome-extension) inspector wired up in `./devtools.ts`, and\n * the Nuxt DevTools (overlay) panel wired up via `../../nuxt.ts` +\n * `../pages/_attaform_devtools.vue`.\n *\n * Centralizing the redaction policy and the window-bridge contract here\n * keeps both surfaces aligned: a future tightening of the sensitive-name\n * heuristic, or a new field added to the bridge, lands in one file.\n */\nimport type { AttaformRegistry } from './registry'\nimport type { Segment } from './paths'\n\nexport const REDACTED = '[redacted]'\n\n/**\n * Walk `value` and replace any leaf whose enclosing path matches the\n * sensitive-name heuristic with the string `'[redacted]'`. Returns a\n * new tree (no mutation of the input). Object keys + array indices are\n * preserved; only the leaf payloads change.\n *\n * Applied to BOTH devtools surfaces' Form-value rendering AND every\n * timeline event payload — leaks via either surface are treatable as\n * \"any developer with the panel open during user testing can read a\n * customer's password,\" which is exactly the failure mode the\n * sensitive-name guard exists to prevent on the storage side.\n *\n * Leaves whose path doesn't match a pattern pass through untouched.\n * `acknowledgeSensitive: true` on persistence does NOT bypass this — if\n * the consumer opted into persisting the value, they still shouldn't\n * see it in DevTools timelines that grow unbounded.\n *\n * Implementation note: tracks an `inSensitiveSubtree` flag through the\n * recursion instead of allocating a fresh path array per node + calling\n * `isSensitivePath` per leaf. Once any ancestor segment matches the\n * heuristic, the flag stays set for every descendant — the leaf simply\n * returns `REDACTED` without re-scanning the path. For a 100-leaf form:\n * ~100 path allocations + ~100 full-path regex sweeps → 0 path\n * allocations + ~100 single-segment regex sweeps, with whole-subtree\n * short-circuit when sensitive ancestors are found early.\n */\nexport function redactSensitiveLeaves(\n value: unknown,\n matchSensitive: (segment: Segment) => boolean\n): unknown {\n return redactImpl(value, false, matchSensitive)\n}\n\nfunction redactImpl(\n value: unknown,\n inSensitiveSubtree: boolean,\n matchSensitive: (segment: Segment) => boolean\n): unknown {\n if (value === null || value === undefined) return value\n if (typeof value !== 'object') {\n return inSensitiveSubtree ? REDACTED : value\n }\n if (Array.isArray(value)) {\n // Numeric segments never match the sensitive-name heuristic\n // (segmentMatchesSensitive rejects non-string segments), so the\n // flag passes through unchanged when descending into arrays.\n return value.map((item) => redactImpl(item, inSensitiveSubtree, matchSensitive))\n }\n // Non-plain object (Map / Set / Date / class instance) — redact\n // wholesale if we're already in a sensitive subtree; otherwise pass\n // through. DevTools rendering of these is already heuristic, so we\n // don't try to descend into them.\n //\n // Use `Object.prototype.toString.call(value)` rather than a\n // `getPrototypeOf` comparison because `Object.prototype` is\n // realm-scoped — the Nuxt DevTools overlay panel runs in an iframe\n // whose Vue runtime is separate from the host's, so the host's\n // reactive proxies have a prototype that doesn't equal the panel's\n // `Object.prototype`. The `toString` tag check is realm-aware via\n // `@@toStringTag` and returns `'[object Object]'` for plain objects\n // (including Vue reactive proxies of plain objects) regardless of\n // which iframe they were created in.\n if (Object.prototype.toString.call(value) !== '[object Object]') {\n return inSensitiveSubtree ? REDACTED : value\n }\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const childSensitive = inSensitiveSubtree || matchSensitive(key)\n out[key] = redactImpl((value as Record<string, unknown>)[key], childSensitive, matchSensitive)\n }\n return out\n}\n\n/**\n * Property key on `window` that the Nuxt-side dev plugin attaches the\n * bridge object to. The iframe-mounted overlay panel reads\n * `window.parent[DEVTOOLS_WINDOW_KEY]` to reach the host app's registry.\n *\n * Underscored + namespaced to make accidental collision with consumer\n * globals vanishingly unlikely. Stable across versions — bumping it\n * would silently disconnect older library builds from newer overlay\n * panels in the same browser tab during a library upgrade.\n */\nexport const DEVTOOLS_WINDOW_KEY = '__attaform_devtools__'\n\n/**\n * Shape of the object the host plugin attaches to `window` in dev mode.\n * The iframe overlay panel reads this to discover the live registry and\n * render its forms.\n *\n * Single-registry assumption: the latest `createAttaform()` install\n * wins. Multi-app pages (rare; typically only seen in micro-frontend\n * setups) will only see one app's forms in the panel. Documented but\n * not actively supported — the alternative (a Set of registries with\n * union-rendering) is a future call if a real consumer hits it.\n */\nexport interface AttaformDevtoolsBridge {\n registry: AttaformRegistry\n /**\n * The library version, surfaced in the panel's footer for support /\n * bug-report context. Read from `package.json` at host-plugin init.\n */\n version: string\n}\n\ndeclare global {\n interface Window {\n [DEVTOOLS_WINDOW_KEY]?: AttaformDevtoolsBridge\n }\n}\n"],"names":["getRegistryFromApp","pathKeyToDotted"],"mappings":";;;;AAuCO,SAAS,oBAAoB,GAAA,EAAmC;AACrE,EAAA,MAAM,QAAA,GAAWA,yBAAmB,GAAG,CAAA;AACvC,EAAA,MAAM,QAAuD,EAAC;AAC9D,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,SAAS,KAAA,EAAO;AAOzC,IAAA,MAAM,gBAA0B,EAAC;AACjC,IAAA,KAAA,MAAW,EAAA,IAAM,MAAM,UAAA,EAAY;AACjC,MAAA,MAAM,CAAA,GAAIC,sBAAgB,EAAa,CAAA;AACvC,MAAA,IAAI,CAAA,KAAM,IAAA,EAAM,aAAA,CAAc,IAAA,CAAK,CAAC,CAAA;AAAA,IACtC;AACA,IAAA,KAAA,CAAM,IAAA,CAAK;AAAA,MACT,GAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAM,MAAM,IAAA,CAAK,KAAA;AAAA,QACjB,cAAc,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,YAAA,CAAa,SAAS,CAAA;AAAA,QACrD,YAAY,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,UAAA,CAAW,SAAS,CAAA;AAAA,QACjD,QAAQ,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,SAAS,CAAA;AAAA,QACzC,GAAI,cAAc,MAAA,GAAS,CAAA,GAAI,EAAE,UAAA,EAAY,aAAA,KAAkB;AAAC;AAClE,KACD,CAAA;AAAA,EACH;AACA,EAAA,OAAO,EAAE,KAAA,EAAM;AACjB;AAkBO,SAAS,oBAAA,CAAqB,KAAU,OAAA,EAAwC;AACrF,EAAA,MAAM,QAAA,GAAWD,yBAAmB,GAAG,CAAA;AACvC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,CAAA,IAAK,QAAQ,KAAA,EAAO;AACvC,IAAA,QAAA,CAAS,gBAAA,CAAiB,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA;AAAA,EACzC;AACF;;AC5EO,MAAM,QAAA,GAAW;AA4BjB,SAAS,qBAAA,CACd,OACA,cAAA,EACS;AACT,EAAA,OAAO,UAAA,CAAW,KAAA,EAAO,KAAA,EAAO,cAAc,CAAA;AAChD;AAEA,SAAS,UAAA,CACP,KAAA,EACA,kBAAA,EACA,cAAA,EACS;AACT,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW,OAAO,KAAA;AAClD,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,qBAAqB,QAAA,GAAW,KAAA;AAAA,EACzC;AACA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAIxB,IAAA,OAAO,KAAA,CAAM,IAAI,CAAC,IAAA,KAAS,WAAW,IAAA,EAAM,kBAAA,EAAoB,cAAc,CAAC,CAAA;AAAA,EACjF;AAeA,EAAA,IAAI,OAAO,SAAA,CAAU,QAAA,CAAS,IAAA,CAAK,KAAK,MAAM,iBAAA,EAAmB;AAC/D,IAAA,OAAO,qBAAqB,QAAA,GAAW,KAAA;AAAA,EACzC;AACA,EAAA,MAAM,MAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,KAAgC,CAAA,EAAG;AAC/D,IAAA,MAAM,cAAA,GAAiB,kBAAA,IAAsB,cAAA,CAAe,GAAG,CAAA;AAC/D,IAAA,GAAA,CAAI,GAAG,CAAA,GAAI,UAAA,CAAY,MAAkC,GAAG,CAAA,EAAG,gBAAgB,cAAc,CAAA;AAAA,EAC/F;AACA,EAAA,OAAO,GAAA;AACT;AAYO,MAAM,mBAAA,GAAsB;;;;;;;;"}
|
|
@@ -816,6 +816,9 @@ function setAssignFunction(el, vnode, value) {
|
|
|
816
816
|
}
|
|
817
817
|
el[assignKey] = getModelAssigner(el, vnode, value);
|
|
818
818
|
}
|
|
819
|
+
function noteInteraction(value) {
|
|
820
|
+
if (isRegisterValue(value)) value.markInteracted();
|
|
821
|
+
}
|
|
819
822
|
const vRegisterText = {
|
|
820
823
|
created(el, { value, modifiers: { lazy, trim, number } }, vnode) {
|
|
821
824
|
const castToNumber = number === true || vnode.props?.["type"] === "number";
|
|
@@ -827,6 +830,7 @@ const vRegisterText = {
|
|
|
827
830
|
if (shouldBailListener(el)) return;
|
|
828
831
|
const target = e.target;
|
|
829
832
|
if (target === null || target.composing) return;
|
|
833
|
+
noteInteraction(value);
|
|
830
834
|
let domValue = el.value;
|
|
831
835
|
if (trim === true && lazy === true) {
|
|
832
836
|
domValue = domValue.trim();
|
|
@@ -958,6 +962,7 @@ const vRegisterCheckbox = {
|
|
|
958
962
|
setAssignFunction(el, vnode, value);
|
|
959
963
|
addEventListener(el, "change", () => {
|
|
960
964
|
if (shouldBailListener(el)) return;
|
|
965
|
+
noteInteraction(value);
|
|
961
966
|
const modelValue = value.innerRef.value ?? [];
|
|
962
967
|
const explicitValueRequired = true;
|
|
963
968
|
const rawElementValue = getValue(el, explicitValueRequired);
|
|
@@ -1055,6 +1060,7 @@ const vRegisterRadio = {
|
|
|
1055
1060
|
setAssignFunction(el, vnode, value);
|
|
1056
1061
|
addEventListener(el, "change", () => {
|
|
1057
1062
|
if (shouldBailListener(el)) return;
|
|
1063
|
+
noteInteraction(value);
|
|
1058
1064
|
el[assignKey]?.(getValue(el));
|
|
1059
1065
|
if (isRegisterValue(value) && isDefaultAssigner(el[assignKey])) {
|
|
1060
1066
|
const currentModel = value.innerRef.value;
|
|
@@ -1103,6 +1109,7 @@ const vRegisterSelect = {
|
|
|
1103
1109
|
const isSetModel = isSet(value.innerRef.value);
|
|
1104
1110
|
addEventListener(el, "change", () => {
|
|
1105
1111
|
if (shouldBailListener(el)) return;
|
|
1112
|
+
noteInteraction(value);
|
|
1106
1113
|
const selectedVal = Array.prototype.filter.call(el.options, (o) => o.selected).map((o) => number === true ? looseToNumber(getValue(o)) : getValue(o));
|
|
1107
1114
|
const wrote = el[assignKey]?.(
|
|
1108
1115
|
el.multiple ? isSetModel ? new Set(selectedVal) : selectedVal : selectedVal[0]
|
|
@@ -1217,11 +1224,83 @@ function getCheckboxValue(el, checked) {
|
|
|
1217
1224
|
}
|
|
1218
1225
|
const SUPPORTED_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
|
|
1219
1226
|
const warnedUnsupportedElements = __DEV__ ? /* @__PURE__ */ new WeakSet() : null;
|
|
1227
|
+
const MANAGED_ARIA_ATTRS = [
|
|
1228
|
+
"aria-invalid",
|
|
1229
|
+
"aria-busy",
|
|
1230
|
+
"aria-required",
|
|
1231
|
+
"aria-describedby"
|
|
1232
|
+
];
|
|
1233
|
+
const ariaLockKey = Symbol.for("attaform:aria-locks");
|
|
1234
|
+
const ariaScopeKey = Symbol.for("attaform:aria-scope");
|
|
1235
|
+
const EMPTY_ARIA_LOCKS = /* @__PURE__ */ new Set();
|
|
1236
|
+
function mergeAriaLocks(el, vnode) {
|
|
1237
|
+
let locks = el[ariaLockKey];
|
|
1238
|
+
if (locks === void 0) {
|
|
1239
|
+
locks = /* @__PURE__ */ new Set();
|
|
1240
|
+
el[ariaLockKey] = locks;
|
|
1241
|
+
}
|
|
1242
|
+
const props = vnode.props;
|
|
1243
|
+
if (props !== null) {
|
|
1244
|
+
for (const attr of MANAGED_ARIA_ATTRS) {
|
|
1245
|
+
if (attr in props) locks.add(attr);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return locks;
|
|
1249
|
+
}
|
|
1250
|
+
function setAriaAttr(el, attr, value) {
|
|
1251
|
+
if (value === null) el.removeAttribute(attr);
|
|
1252
|
+
else el.setAttribute(attr, value);
|
|
1253
|
+
}
|
|
1254
|
+
function resolveAriaValue(attr, rv, ds) {
|
|
1255
|
+
switch (attr) {
|
|
1256
|
+
case "aria-invalid":
|
|
1257
|
+
return ds === "error" ? "true" : null;
|
|
1258
|
+
case "aria-busy":
|
|
1259
|
+
return ds === "pending" ? "true" : null;
|
|
1260
|
+
case "aria-required":
|
|
1261
|
+
return rv.isRequired === true ? "true" : null;
|
|
1262
|
+
case "aria-describedby":
|
|
1263
|
+
return ds === "error" && rv.aria?.errorId !== void 0 ? rv.aria.errorId : null;
|
|
1264
|
+
default:
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
function applyAria(el, rv) {
|
|
1269
|
+
if (rv.ariaEnabled !== true || rv.ariaDisplayState === void 0) return;
|
|
1270
|
+
const locks = el[ariaLockKey] ?? EMPTY_ARIA_LOCKS;
|
|
1271
|
+
const ds = rv.ariaDisplayState.value;
|
|
1272
|
+
for (const attr of MANAGED_ARIA_ATTRS) {
|
|
1273
|
+
if (!locks.has(attr)) setAriaAttr(el, attr, resolveAriaValue(attr, rv, ds));
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
function setupAria(el, rv, vnode) {
|
|
1277
|
+
if (rv.ariaEnabled !== true || rv.ariaDisplayState === void 0) return;
|
|
1278
|
+
mergeAriaLocks(el, vnode);
|
|
1279
|
+
applyAria(el, rv);
|
|
1280
|
+
const displayState = rv.ariaDisplayState;
|
|
1281
|
+
const scope = effectScope(true);
|
|
1282
|
+
scope.run(() => {
|
|
1283
|
+
watch(displayState, () => applyAria(el, rv), { flush: "post" });
|
|
1284
|
+
});
|
|
1285
|
+
el[ariaScopeKey] = () => scope.stop();
|
|
1286
|
+
}
|
|
1287
|
+
function teardownAria(el) {
|
|
1288
|
+
const stop = el[ariaScopeKey];
|
|
1289
|
+
if (stop === void 0) return;
|
|
1290
|
+
stop();
|
|
1291
|
+
delete el[ariaScopeKey];
|
|
1292
|
+
const locks = el[ariaLockKey] ?? EMPTY_ARIA_LOCKS;
|
|
1293
|
+
for (const attr of MANAGED_ARIA_ATTRS) {
|
|
1294
|
+
if (!locks.has(attr)) el.removeAttribute(attr);
|
|
1295
|
+
}
|
|
1296
|
+
delete el[ariaLockKey];
|
|
1297
|
+
}
|
|
1220
1298
|
const vRegisterDynamic = {
|
|
1221
1299
|
created(el, binding, vnode) {
|
|
1222
1300
|
syncPersistOptIn(el, binding.value, void 0, vnode.props?.["type"]);
|
|
1223
1301
|
syncMultiTabOptOut(binding.value, void 0);
|
|
1224
1302
|
callModelHook(el, binding, vnode, null, "created");
|
|
1303
|
+
if (isRegisterValue(binding.value)) setupAria(el, binding.value, vnode);
|
|
1225
1304
|
if (__DEV__ && warnedUnsupportedElements !== null && !SUPPORTED_TAGS.has(el.tagName) && !warnedUnsupportedElements.has(el)) {
|
|
1226
1305
|
void nextTick(() => {
|
|
1227
1306
|
if (warnedUnsupportedElements.has(el)) return;
|
|
@@ -1245,12 +1324,28 @@ const vRegisterDynamic = {
|
|
|
1245
1324
|
syncMultiTabOptOut(binding.value, binding.oldValue);
|
|
1246
1325
|
syncElementRegistration(el, binding.value, binding.oldValue);
|
|
1247
1326
|
callModelHook(el, binding, vnode, prevVNode, "beforeUpdate");
|
|
1327
|
+
const ariaEl = el;
|
|
1328
|
+
const value = binding.value;
|
|
1329
|
+
if (!isRegisterValue(value) || value.ariaEnabled !== true || value.ariaDisplayState === void 0) {
|
|
1330
|
+
teardownAria(ariaEl);
|
|
1331
|
+
} else {
|
|
1332
|
+
const old = binding.oldValue;
|
|
1333
|
+
const pathChanged = !isRegisterValue(old) || old.path !== value.path;
|
|
1334
|
+
if (pathChanged) {
|
|
1335
|
+
teardownAria(ariaEl);
|
|
1336
|
+
setupAria(ariaEl, value, vnode);
|
|
1337
|
+
} else {
|
|
1338
|
+
mergeAriaLocks(ariaEl, vnode);
|
|
1339
|
+
applyAria(ariaEl, value);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1248
1342
|
},
|
|
1249
1343
|
updated(el, binding, vnode, prevVNode) {
|
|
1250
1344
|
callModelHook(el, binding, vnode, prevVNode, "updated");
|
|
1251
1345
|
},
|
|
1252
1346
|
beforeUnmount(el, { value }) {
|
|
1253
1347
|
removeTrackedListeners(el);
|
|
1348
|
+
teardownAria(el);
|
|
1254
1349
|
if (isRegisterValue(value)) {
|
|
1255
1350
|
value.persistOptIns.removeAllFor(getOrAssignElementId(el));
|
|
1256
1351
|
value.unmarkNoSync?.();
|
|
@@ -1260,6 +1355,28 @@ const vRegisterDynamic = {
|
|
|
1260
1355
|
delete el.composing;
|
|
1261
1356
|
delete el._assigning;
|
|
1262
1357
|
delete el[assignKey];
|
|
1358
|
+
},
|
|
1359
|
+
// The lifecycle hooks above don't run on the server (Vue skips
|
|
1360
|
+
// directive lifecycle during SSR), so emit the same aria attributes
|
|
1361
|
+
// here from the SSR-time gated display state. Honors authored attrs
|
|
1362
|
+
// (vnode-level lockout) and the ariaEnabled gate, touches no DOM, and
|
|
1363
|
+
// shares `resolveAriaValue` with the client path. Ids are SSR-stable
|
|
1364
|
+
// (formInstanceId derives from Vue's useId), so a server-rendered
|
|
1365
|
+
// describedby matches the client after hydration.
|
|
1366
|
+
getSSRProps(binding, vnode) {
|
|
1367
|
+
const rv = binding.value;
|
|
1368
|
+
if (!isRegisterValue(rv) || rv.ariaEnabled !== true || rv.ariaDisplayState === void 0) {
|
|
1369
|
+
return void 0;
|
|
1370
|
+
}
|
|
1371
|
+
const props = vnode?.props ?? null;
|
|
1372
|
+
const ds = rv.ariaDisplayState.value;
|
|
1373
|
+
const out = {};
|
|
1374
|
+
for (const attr of MANAGED_ARIA_ATTRS) {
|
|
1375
|
+
if (props !== null && attr in props) continue;
|
|
1376
|
+
const value = resolveAriaValue(attr, rv, ds);
|
|
1377
|
+
if (value !== null) out[attr] = value;
|
|
1378
|
+
}
|
|
1379
|
+
return out;
|
|
1263
1380
|
}
|
|
1264
1381
|
};
|
|
1265
1382
|
function isBlankFileValue(value) {
|
|
@@ -1305,6 +1422,7 @@ const vRegisterFile = {
|
|
|
1305
1422
|
value.setValueWithInternalPath(blankShape, { blank: true });
|
|
1306
1423
|
}
|
|
1307
1424
|
addEventListener(input, "change", () => {
|
|
1425
|
+
noteInteraction(value);
|
|
1308
1426
|
const next = readFilesFromInput(input);
|
|
1309
1427
|
const blank = isBlankFileValue(next);
|
|
1310
1428
|
value.setValueWithInternalPath(next, blank ? { blank: true } : void 0);
|
|
@@ -1505,4 +1623,4 @@ function isPathPrefix(prefix, path) {
|
|
|
1505
1623
|
}
|
|
1506
1624
|
|
|
1507
1625
|
export { AnonPersistError as A, kFormInstanceId as B, parseDottedPath as C, DEFAULT_SENSITIVE_NAMES as D, pathKeyToDotted as E, FORM_ERRORS_PATH as F, segmentMatchesSensitive as G, segmentsForPathKey as H, InvalidPathError as I, useRegister as J, useRegistry as K, vRegister as L, OutsideSetupError as O, ROOT_PATH as R, SensitivePersistFieldError as S, __DEV__ as _, AttaformError as a, FORM_ERRORS_PATH_KEY as b, InvalidUseFormConfigError as c, ROOT_PATH_KEY as d, RegistryNotInstalledError as e, ReservedFormKeyError as f, SubmitErrorHandlerError as g, assignKey as h, canonicalizePath as i, captureUserCallSite as j, coerceToPathKey as k, createAttaform as l, createIsSensitivePath as m, createPersistOptInRegistry as n, createRegistry as o, createSegmentMatchesSensitive as p, enforceSensitiveCheck as q, ensureAttaformInstalled as r, getRegistryFromApp as s, isPathPrefix as t, isRegisterValue as u, isSensitivePath as v, kAttaformAncestorWizard as w, kAttaformRegistry as x, kAttaformWizardActiveStepResolver as y, kFormContext as z };
|
|
1508
|
-
//# sourceMappingURL=attaform.
|
|
1626
|
+
//# sourceMappingURL=attaform.CrpjyXdO.mjs.map
|