[{"data":1,"prerenderedAt":499},["ShallowReactive",2],{"content-\u002Fdocs\u002Frecipes\u002Ftransforms":3},{"id":4,"title":5,"body":6,"description":16,"extension":493,"meta":494,"navigation":60,"path":495,"seo":496,"stem":497,"__hash__":498},"docs\u002Fdocs\u002Frecipes\u002Ftransforms.md","Register transforms",{"type":7,"value":8,"toc":483},"minimark",[9,13,17,22,213,251,264,268,276,326,333,337,340,368,374,381,384,388,395,424,427,434,441,476,479],[10,11,5],"h1",{"id":12},"register-transforms",[14,15,16],"p",{},"A pipeline of pure sync functions that runs over user input before\nthe assigner writes to form state. Use it for trim \u002F lowercase \u002F\nmask \u002F clamp normalisations — anything that should always apply\nno matter how the user typed.",[18,19,21],"h2",{"id":20},"basic-example","Basic example",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-ts shiki shiki-themes github-light github-dark","import type { RegisterTransform } from 'attaform'\n\nconst trim: RegisterTransform = (v) => (typeof v === 'string' ? v.trim() : v)\n\nconst lowercase: RegisterTransform = (v) => (typeof v === 'string' ? v.toLowerCase() : v)\n\n\u002F\u002F In setup\nconst rv = form.register('email', { transforms: [trim, lowercase] })\n","ts","",[30,31,32,55,62,125,130,175,180,187],"code",{"__ignoreMap":28},[33,34,37,41,44,48,51],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"szBVR","import",[33,42,43],{"class":39}," type",[33,45,47],{"class":46},"sVt8B"," { RegisterTransform } ",[33,49,50],{"class":39},"from",[33,52,54],{"class":53},"sZZnC"," 'attaform'\n",[33,56,58],{"class":35,"line":57},2,[33,59,61],{"emptyLinePlaceholder":60},true,"\n",[33,63,65,68,72,75,78,81,84,88,91,94,96,99,102,105,108,111,114,117,120,122],{"class":35,"line":64},3,[33,66,67],{"class":39},"const",[33,69,71],{"class":70},"sScJk"," trim",[33,73,74],{"class":39},":",[33,76,77],{"class":70}," RegisterTransform",[33,79,80],{"class":39}," =",[33,82,83],{"class":46}," (",[33,85,87],{"class":86},"s4XuR","v",[33,89,90],{"class":46},") ",[33,92,93],{"class":39},"=>",[33,95,83],{"class":46},[33,97,98],{"class":39},"typeof",[33,100,101],{"class":46}," v ",[33,103,104],{"class":39},"===",[33,106,107],{"class":53}," 'string'",[33,109,110],{"class":39}," ?",[33,112,113],{"class":46}," v.",[33,115,116],{"class":70},"trim",[33,118,119],{"class":46},"() ",[33,121,74],{"class":39},[33,123,124],{"class":46}," v)\n",[33,126,128],{"class":35,"line":127},4,[33,129,61],{"emptyLinePlaceholder":60},[33,131,133,135,138,140,142,144,146,148,150,152,154,156,158,160,162,164,166,169,171,173],{"class":35,"line":132},5,[33,134,67],{"class":39},[33,136,137],{"class":70}," lowercase",[33,139,74],{"class":39},[33,141,77],{"class":70},[33,143,80],{"class":39},[33,145,83],{"class":46},[33,147,87],{"class":86},[33,149,90],{"class":46},[33,151,93],{"class":39},[33,153,83],{"class":46},[33,155,98],{"class":39},[33,157,101],{"class":46},[33,159,104],{"class":39},[33,161,107],{"class":53},[33,163,110],{"class":39},[33,165,113],{"class":46},[33,167,168],{"class":70},"toLowerCase",[33,170,119],{"class":46},[33,172,74],{"class":39},[33,174,124],{"class":46},[33,176,178],{"class":35,"line":177},6,[33,179,61],{"emptyLinePlaceholder":60},[33,181,183],{"class":35,"line":182},7,[33,184,186],{"class":185},"sJ8bj","\u002F\u002F In setup\n",[33,188,190,192,196,198,201,204,207,210],{"class":35,"line":189},8,[33,191,67],{"class":39},[33,193,195],{"class":194},"sj4cs"," rv",[33,197,80],{"class":39},[33,199,200],{"class":46}," form.",[33,202,203],{"class":70},"register",[33,205,206],{"class":46},"(",[33,208,209],{"class":53},"'email'",[33,211,212],{"class":46},", { transforms: [trim, lowercase] })\n",[23,214,218],{"className":215,"code":216,"language":217,"meta":28,"style":28},"language-vue shiki shiki-themes github-light github-dark","\u003Cinput v-register=\"rv\" \u002F>\n\u003C!-- type \"  Foo@BAR.com  \", form.values.email === \"foo@bar.com\" -->\n","vue",[30,219,220,246],{"__ignoreMap":28},[33,221,222,225,229,232,235,238,241,243],{"class":35,"line":36},[33,223,224],{"class":46},"\u003C",[33,226,228],{"class":227},"s9eBZ","input",[33,230,231],{"class":70}," v-register",[33,233,234],{"class":46},"=",[33,236,237],{"class":53},"\"",[33,239,240],{"class":46},"rv",[33,242,237],{"class":53},[33,244,245],{"class":46}," \u002F>\n",[33,247,248],{"class":35,"line":57},[33,249,250],{"class":185},"\u003C!-- type \"  Foo@BAR.com  \", form.values.email === \"foo@bar.com\" -->\n",[14,252,253,256,257,260,261,263],{},[30,254,255],{},"RegisterTransform"," is ",[30,258,259],{},"(value: unknown) => unknown"," —\ngeneric-erased so the same ",[30,262,116],{}," works for every string path.\nType-safety at the call site is delegated to attaform's slim-primitive\ngate at write time.",[18,265,267],{"id":266},"pipeline-ordering","Pipeline ordering",[23,269,274],{"className":270,"code":272,"language":273},[271],"language-text","DOM event → modifier cast → coerce → transforms[0] → … → transforms[n] → assigner\n","text",[30,275,272],{"__ignoreMap":28},[277,278,279,298,310,316],"ul",{},[280,281,282,286,287,290,291,290,294,297],"li",{},[283,284,285],"strong",{},"Modifier cast"," — ",[30,288,289],{},".lazy"," \u002F ",[30,292,293],{},".trim",[30,295,296],{},".number"," from the\ndirective itself.",[280,299,300,303,304,309],{},[283,301,302],{},"Coerce"," — schema-driven type coercion (see\n",[305,306,308],"a",{"href":307},".\u002Fcoerce","coerce recipe",").",[280,311,312,315],{},[283,313,314],{},"Transforms"," — your sync pipeline, left-to-right.",[280,317,318,321,322,325],{},[283,319,320],{},"Assigner"," — the default writer, or ",[30,323,324],{},"@update:registerValue"," if\nyou've attached one.",[14,327,328,329,332],{},"Combine freely:\n",[30,330,331],{},"\u003Cinput v-register.lazy.number=\"register('age', { transforms: [clamp(0, 99)] })\">","\ncasts to a number on blur, clamps, then writes.",[18,334,336],{"id":335},"what-transforms-dont-apply-to","What transforms DON'T apply to",[14,338,339],{},"This is deliberately narrow — transforms are user-input\nnormalisation, not storage middleware:",[277,341,342,356,362],{},[280,343,344,347,348,351,352,355],{},[30,345,346],{},"form.setValue(path, value)"," and\n",[30,349,350],{},"rv.setValueWithInternalPath(value)"," — programmatic writes.\nCompose transforms yourself at the call site if you want the\nsame normalisation:\n",[30,353,354],{},"form.setValue('email', lowercase(trim(rawValue)))",".",[280,357,358,361],{},[30,359,360],{},"form.reset()"," \u002F hydration \u002F SSR replay — those write canonical\nstate that's already been validated; running normalisation over\nit would be redundant or destructive.",[280,363,364,367],{},[30,365,366],{},"markBlank()"," — already writes the slim default.",[18,369,371,372],{"id":370},"composing-with-updateregistervalue","Composing with ",[30,373,324],{},[14,375,376,377,380],{},"The override receives the ",[283,378,379],{},"post-transform"," value as its first\narg. A consumer who declared transforms intended \"always\nnormalise\"; a silent bypass when an override is attached would\nbe the surprise.",[14,382,383],{},"If you want the raw extracted value, don't register transforms\n— use the override exclusively. If you want both pre- and\npost-transform inspection inside the override, register\ntransforms and read the first arg.",[18,385,387],{"id":386},"failure-mode","Failure mode",[14,389,390,391,394],{},"Transforms must be ",[283,392,393],{},"sync",". Attaform wraps each call in try\u002Fcatch; on\nthrow OR Promise return:",[277,396,397,400,406,413],{},[280,398,399],{},"The pipeline aborts (subsequent transforms don't run).",[280,401,402,403,355],{},"Form state is NOT updated; the assigner returns ",[30,404,405],{},"false",[280,407,408,409,412],{},"The DOM's ",[30,410,411],{},":value"," reactive binding round-trips form state\nback, snapping the input to the prior value (same UX as the\ndocumented \"rejection\" pattern).",[280,414,415,416,419,420,423],{},"A ",[30,417,418],{},"console.error"," is logged. In dev the message includes the\npath, transform index, transform ",[30,421,422],{},".name",", the original error,\nand a remediation hint. In prod the message is a single fixed\nstring with NONE of those — transform bodies can construct\nerror messages from user-typed values, throw with sensitive\nstack frames, or originate inside deeply-nested call chains\nwe don't control.",[14,425,426],{},"A throw on one keystroke doesn't poison subsequent keystrokes\n(the next event runs the pipeline fresh) and doesn't affect\nother fields' assigners (each field has its own pipeline).",[18,428,430,431,433],{"id":429},"when-to-reach-for-updateregistervalue-instead","When to reach for ",[30,432,324],{}," instead",[14,435,436,437,440],{},"Three patterns where the override pulls weight that ",[30,438,439],{},"transforms","\ndoesn't:",[277,442,443,457,463],{},[280,444,445,448,449,452,453,456],{},[283,446,447],{},"Rejection with side effect."," The override receives the\n",[30,450,451],{},"RegisterValue","; you can inspect, log to telemetry, then\nconditionally call ",[30,454,455],{},"rv.setValueWithInternalPath"," or skip.",[280,458,459,462],{},[283,460,461],{},"Redirection."," Write to a different field, multiple fields,\nor an external store using the form API.",[280,464,465,468,469,472,473,475],{},[283,466,467],{},"Custom DOM mutation."," The override has access to the event\nflow; you can synchronously rewrite ",[30,470,471],{},"event.target.value"," if\nyour use case can't rely on the ",[30,474,411],{}," round-trip.",[14,477,478],{},"Transforms cover normalisation. The override covers control.",[480,481,482],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":28,"searchDepth":57,"depth":57,"links":484},[485,486,487,488,490,491],{"id":20,"depth":57,"text":21},{"id":266,"depth":57,"text":267},{"id":335,"depth":57,"text":336},{"id":370,"depth":57,"text":489},"Composing with @update:registerValue",{"id":386,"depth":57,"text":387},{"id":429,"depth":57,"text":492},"When to reach for @update:registerValue instead","md",{},"\u002Fdocs\u002Frecipes\u002Ftransforms",{"title":5,"description":16},"docs\u002Frecipes\u002Ftransforms","5N4GZcBLTbqp4ZHgBzOgd_fhMRN0wQKodjJfeW2KEy8",1777934136741]