[{"data":1,"prerenderedAt":833},["ShallowReactive",2],{"content-\u002Fdocs\u002Fcross-cutting-state\u002Fundo-redo":3},{"id":4,"title":5,"body":6,"description":812,"extension":813,"meta":814,"metaRows":815,"navigation":560,"path":828,"seo":829,"source":830,"stem":831,"__hash__":832},"docs\u002Fdocs\u002Fcross-cutting-state\u002Fundo-redo.md","Undo & redo",{"type":7,"value":8,"toc":801},"minimark",[9,13,32,35,56,60,65,116,119,137,140,158,174,178,185,297,301,333,350,353,391,404,408,411,616,619,625,631,685,700,704,744,748,763,767,797],[10,11,5],"h1",{"id":12},"undo-redo",[14,15,16],"blockquote",{},[17,18,19,20,24,25,24,28,31],"p",{},"Opt into a per-form history chain with one option. Every value mutation records a position; the namespace exposes ",[21,22,23],"code",{},"undo()"," \u002F ",[21,26,27],{},"redo()",[21,29,30],{},"clear()"," and the reactive flags that gate your UI.",[33,34],"docs-meta-table",{},[17,36,37,38,24,41,44,45,48,49,52,53,55],{},"Type into any field, append a few tags, then hit ",[21,39,40],{},"⌘Z",[21,42,43],{},"⌘⇧Z"," (or click the buttons) to walk the chain. ",[21,46,47],{},"canUndo"," and ",[21,50,51],{},"canRedo"," gate the buttons reactively; ",[21,54,30],{}," reseeds the chain at the current state, the move you'd make after a \"Save successful\" milestone.",[57,58],"docs-demo",{"label":59,"slug":12},"Undo & Redo Demo",[61,62,64],"h2",{"id":63},"the-option","The option",[66,67,72],"pre",{"className":68,"code":69,"language":70,"meta":71,"style":71},"language-ts shiki shiki-themes github-light github-dark","useForm({\n  schema,\n  history: true, \u002F\u002F default 128-position bounded chain\n})\n","ts","",[21,73,74,87,93,110],{"__ignoreMap":71},[75,76,79,83],"span",{"class":77,"line":78},"line",1,[75,80,82],{"class":81},"sScJk","useForm",[75,84,86],{"class":85},"sVt8B","({\n",[75,88,90],{"class":77,"line":89},2,[75,91,92],{"class":85},"  schema,\n",[75,94,96,99,103,106],{"class":77,"line":95},3,[75,97,98],{"class":85},"  history: ",[75,100,102],{"class":101},"sj4cs","true",[75,104,105],{"class":85},", ",[75,107,109],{"class":108},"sJ8bj","\u002F\u002F default 128-position bounded chain\n",[75,111,113],{"class":77,"line":112},4,[75,114,115],{"class":85},"})\n",[17,117,118],{},"Tune the depth:",[66,120,122],{"className":68,"code":121,"language":70,"meta":71,"style":71},"useForm({ schema, history: { max: 200 } })\n",[21,123,124],{"__ignoreMap":71},[75,125,126,128,131,134],{"class":77,"line":78},[75,127,82],{"class":81},[75,129,130],{"class":85},"({ schema, history: { max: ",[75,132,133],{"class":101},"200",[75,135,136],{"class":85}," } })\n",[17,138,139],{},"Disable explicitly:",[66,141,143],{"className":68,"code":142,"language":70,"meta":71,"style":71},"useForm({ schema, history: false })\n",[21,144,145],{"__ignoreMap":71},[75,146,147,149,152,155],{"class":77,"line":78},[75,148,82],{"class":81},[75,150,151],{"class":85},"({ schema, history: ",[75,153,154],{"class":101},"false",[75,156,157],{"class":85}," })\n",[17,159,160,161,164,165,167,168,24,170,173],{},"When omitted, ",[21,162,163],{},"history"," defaults to ",[21,166,154],{},". The namespace is still present on the form return so templates don't need conditional logic, but every method is a no-op and the flags read ",[21,169,154],{},[21,171,172],{},"0",".",[61,175,177],{"id":176},"the-namespace","The namespace",[17,179,180,181,184],{},"All undo\u002Fredo surface lives under ",[21,182,183],{},"form.history",":",[186,187,188,204],"table",{},[189,190,191],"thead",{},[192,193,194,198,201],"tr",{},[195,196,197],"th",{},"Member",[195,199,200],{},"Type",[195,202,203],{},"What it does",[205,206,207,225,241,255,269,282],"tbody",{},[192,208,209,214,219],{},[210,211,212],"td",{},[21,213,23],{},[210,215,216],{},[21,217,218],{},"() => boolean",[210,220,221,222,224],{},"Step back to the previous state. ",[21,223,154],{}," at baseline.",[192,226,227,231,235],{},[210,228,229],{},[21,230,27],{},[210,232,233],{},[21,234,218],{},[210,236,237,238,240],{},"Replay the next state after an undo. ",[21,239,154],{}," when nothing's queued.",[192,242,243,247,252],{},[210,244,245],{},[21,246,30],{},[210,248,249],{},[21,250,251],{},"() => void",[210,253,254],{},"Wipe the chain; reseed at the current state as the new baseline.",[192,256,257,261,266],{},[210,258,259],{},[21,260,47],{},[210,262,263],{},[21,264,265],{},"boolean",[210,267,268],{},"Gate an \"Undo\" button reactively.",[192,270,271,275,279],{},[210,272,273],{},[21,274,51],{},[210,276,277],{},[21,278,265],{},[210,280,281],{},"Gate a \"Redo\" button reactively.",[192,283,284,289,294],{},[210,285,286],{},[21,287,288],{},"size",[210,290,291],{},[21,292,293],{},"number",[210,295,296],{},"Reachable positions across the chain (useful for debug overlays).",[61,298,300],{"id":299},"what-gets-captured","What gets captured",[17,302,303,304,105,307,310,311,105,314,105,317,105,320,105,323,105,326,105,329,332],{},"Every form value mutation: ",[21,305,306],{},"setValue",[21,308,309],{},"register","-backed input edits, any array helper (",[21,312,313],{},"append",[21,315,316],{},"prepend",[21,318,319],{},"insert",[21,321,322],{},"remove",[21,324,325],{},"swap",[21,327,328],{},"move",[21,330,331],{},"replace","), or a programmatic write. Each recorded position carries:",[334,335,336,340,343],"ul",{},[337,338,339],"li",{},"The form value.",[337,341,342],{},"The error map at the time of the captured position.",[337,344,345,346,349],{},"The ",[21,347,348],{},"blankPaths"," set (so cleared-but-defaulted numeric fields keep showing as empty after an undo, instead of resurrecting their slim default).",[17,351,352],{},"What's NOT captured:",[334,354,355,375,386],{},[337,356,357,361,362,24,365,24,368,24,371,374],{},[358,359,360],"strong",{},"Field interaction state",": ",[21,363,364],{},"touched",[21,366,367],{},"focused",[21,369,370],{},"blurred",[21,372,373],{},"connected",". UI interaction history; it shouldn't rewind. A field that was touched stays touched.",[337,376,377,361,380,105,383,173],{},[358,378,379],{},"Submission lifecycle",[21,381,382],{},"meta.submissionAttempts",[21,384,385],{},"meta.submitError",[337,387,388,173],{},[358,389,390],{},"Validation in-flight state",[17,392,393,394,24,397,24,400,403],{},"Calling ",[21,395,396],{},"setFieldErrors",[21,398,399],{},"addFieldErrors",[21,401,402],{},"clearFieldErrors"," does NOT record a position; those only touch the error map. Whatever errors are live when the next mutation lands go into that mutation's delta.",[61,405,407],{"id":406},"keyboard-shortcuts","Keyboard shortcuts",[17,409,410],{},"Not wired by default; wire them in a few lines:",[66,412,416],{"className":413,"code":414,"language":415,"meta":71,"style":71},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\n  function onKeydown(event: KeyboardEvent) {\n    if ((event.metaKey || event.ctrlKey) && event.key === 'z') {\n      event.preventDefault()\n      event.shiftKey ? form.history.redo() : form.history.undo()\n    }\n  }\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cform @keydown=\"onKeydown\">\n    \u003C!-- … -->\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n","vue",[21,417,418,443,467,495,506,533,539,545,555,562,572,591,597,607],{"__ignoreMap":71},[75,419,420,423,427,430,433,436,440],{"class":77,"line":78},[75,421,422],{"class":85},"\u003C",[75,424,426],{"class":425},"s9eBZ","script",[75,428,429],{"class":81}," setup",[75,431,432],{"class":81}," lang",[75,434,435],{"class":85},"=",[75,437,439],{"class":438},"sZZnC","\"ts\"",[75,441,442],{"class":85},">\n",[75,444,445,449,452,455,459,461,464],{"class":77,"line":89},[75,446,448],{"class":447},"szBVR","  function",[75,450,451],{"class":81}," onKeydown",[75,453,454],{"class":85},"(",[75,456,458],{"class":457},"s4XuR","event",[75,460,184],{"class":447},[75,462,463],{"class":81}," KeyboardEvent",[75,465,466],{"class":85},") {\n",[75,468,469,472,475,478,481,484,487,490,493],{"class":77,"line":95},[75,470,471],{"class":447},"    if",[75,473,474],{"class":85}," ((event.metaKey ",[75,476,477],{"class":447},"||",[75,479,480],{"class":85}," event.ctrlKey) ",[75,482,483],{"class":447},"&&",[75,485,486],{"class":85}," event.key ",[75,488,489],{"class":447},"===",[75,491,492],{"class":438}," 'z'",[75,494,466],{"class":85},[75,496,497,500,503],{"class":77,"line":112},[75,498,499],{"class":85},"      event.",[75,501,502],{"class":81},"preventDefault",[75,504,505],{"class":85},"()\n",[75,507,509,512,515,518,521,524,526,528,531],{"class":77,"line":508},5,[75,510,511],{"class":85},"      event.shiftKey ",[75,513,514],{"class":447},"?",[75,516,517],{"class":85}," form.history.",[75,519,520],{"class":81},"redo",[75,522,523],{"class":85},"() ",[75,525,184],{"class":447},[75,527,517],{"class":85},[75,529,530],{"class":81},"undo",[75,532,505],{"class":85},[75,534,536],{"class":77,"line":535},6,[75,537,538],{"class":85},"    }\n",[75,540,542],{"class":77,"line":541},7,[75,543,544],{"class":85},"  }\n",[75,546,548,551,553],{"class":77,"line":547},8,[75,549,550],{"class":85},"\u003C\u002F",[75,552,426],{"class":425},[75,554,442],{"class":85},[75,556,558],{"class":77,"line":557},9,[75,559,561],{"emptyLinePlaceholder":560},true,"\n",[75,563,565,567,570],{"class":77,"line":564},10,[75,566,422],{"class":85},[75,568,569],{"class":425},"template",[75,571,442],{"class":85},[75,573,575,578,581,584,586,589],{"class":77,"line":574},11,[75,576,577],{"class":85},"  \u003C",[75,579,580],{"class":425},"form",[75,582,583],{"class":81}," @keydown",[75,585,435],{"class":85},[75,587,588],{"class":438},"\"onKeydown\"",[75,590,442],{"class":85},[75,592,594],{"class":77,"line":593},12,[75,595,596],{"class":108},"    \u003C!-- … -->\n",[75,598,600,603,605],{"class":77,"line":599},13,[75,601,602],{"class":85},"  \u003C\u002F",[75,604,580],{"class":425},[75,606,442],{"class":85},[75,608,610,612,614],{"class":77,"line":609},14,[75,611,550],{"class":85},[75,613,569],{"class":425},[75,615,442],{"class":85},[17,617,618],{},"Attaform stays out of the global keydown business so you can layer shortcuts at the right scope (per-form, per-route, global), with the modifier convention that fits your platform.",[61,620,622,624],{"id":621},"clear-at-a-milestone",[21,623,30],{}," at a milestone",[17,626,627,628,630],{},"After a \"save successful\" moment, or any point where consumers should lose access to the prior chain without disturbing the rendered form, call ",[21,629,30],{},". The form value, errors, and blank-paths stay exactly where they are; only the past and future history reset.",[66,632,634],{"className":68,"code":633,"language":70,"meta":71,"style":71},"async function onSaveSuccess() {\n  await api.commit(form.values())\n  form.history.clear()\n}\n",[21,635,636,650,670,680],{"__ignoreMap":71},[75,637,638,641,644,647],{"class":77,"line":78},[75,639,640],{"class":447},"async",[75,642,643],{"class":447}," function",[75,645,646],{"class":81}," onSaveSuccess",[75,648,649],{"class":85},"() {\n",[75,651,652,655,658,661,664,667],{"class":77,"line":89},[75,653,654],{"class":447},"  await",[75,656,657],{"class":85}," api.",[75,659,660],{"class":81},"commit",[75,662,663],{"class":85},"(form.",[75,665,666],{"class":81},"values",[75,668,669],{"class":85},"())\n",[75,671,672,675,678],{"class":77,"line":95},[75,673,674],{"class":85},"  form.history.",[75,676,677],{"class":81},"clear",[75,679,505],{"class":85},[75,681,682],{"class":77,"line":112},[75,683,684],{"class":85},"}\n",[17,686,687,688,361,690,105,693,105,696,699],{},"After ",[21,689,30],{},[21,691,692],{},"canUndo === false",[21,694,695],{},"canRedo === false",[21,697,698],{},"size === 1",". The current position is still reachable; there's just nothing on either side of it.",[61,701,703],{"id":702},"interactions","Interactions",[334,705,706,723,729,735],{},[337,707,708,713,714,717,718,720,721,173],{},[358,709,710],{},[21,711,712],{},"reset()"," is itself a mutation; the pre-reset state stays one undo away. Consumers who want a hard wipe call ",[21,715,716],{},"form.history.clear()"," after ",[21,719,712],{},", or pop a confirmation dialog before calling ",[21,722,712],{},[337,724,725,728],{},[358,726,727],{},"Live field validation"," still runs on undo \u002F redo; the restored state validates like any other.",[337,730,731,734],{},[358,732,733],{},"Persistence"," picks up each undo \u002F redo as a normal mutation and writes the restored state to the configured backend.",[337,736,737,740,741,743],{},[358,738,739],{},"Persistence hydration"," is the floor: once the hydrated value applies, the chain reseeds and ",[21,742,23],{}," can't reach back into the transient pre-hydration default.",[61,745,747],{"id":746},"memory","Memory",[17,749,750,751,754,755,758,759,762],{},"The default ",[21,752,753],{},"max: 128"," keeps at most 128 reachable positions across the undo + redo halves combined. Bump it for editors with long histories; drop it for memory-constrained targets. Internally history stores one base snapshot plus a chain of forward deltas (per-mutation ",[21,756,757],{},"Patch[]"," from the diff machinery), so each additional position costs ",[21,760,761],{},"O(changed-leaf-count)",". Typing one character into one field allocates a single patch, not a clone of the whole form.",[61,764,766],{"id":765},"where-to-next","Where to next",[334,768,769,777,790],{},[337,770,771,776],{},[772,773,775],"a",{"href":774},"\u002Fdocs\u002Fcross-cutting-state\u002Fmulti-tab-sync","Multi-tab sync",": values converge across tabs; the history chain stays tab-local (each tab walks its own user's intent).",[337,778,779,789],{},[772,780,782,785,786],{"href":781},"\u002Fdocs\u002Fwriting-and-mutating\u002Freset",[21,783,784],{},"reset"," & ",[21,787,788],{},"resetField",": recorded as positions; the pre-reset state stays one undo away.",[337,791,792,796],{},[772,793,795],{"href":794},"\u002Fdocs\u002Fpersistence\u002Foverview","Persistence overview",": picks up undo \u002F redo as normal mutations.",[798,799,800],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":71,"searchDepth":89,"depth":89,"links":802},[803,804,805,806,807,809,810,811],{"id":63,"depth":89,"text":64},{"id":176,"depth":89,"text":177},{"id":299,"depth":89,"text":300},{"id":406,"depth":89,"text":407},{"id":621,"depth":89,"text":808},"clear() at a milestone",{"id":702,"depth":89,"text":703},{"id":746,"depth":89,"text":747},{"id":765,"depth":89,"text":766},"history opt-in unlocks a per-form undo\u002Fredo chain. Every mutation records a position, undo() and redo() walk the timeline, clear() reseeds at a milestone.","md",{},[816,819,823,826],{"label":817,"value":818},"Category","Module",{"label":820,"value":821},"Opt in",{"useForm({ history":822,"kind":21},"true })",{"label":824,"value":825},"Default depth","128 positions",{"label":827,"value":183,"kind":21},"Namespace","\u002Fdocs\u002Fcross-cutting-state\u002Fundo-redo",{"title":5,"description":812},null,"docs\u002Fcross-cutting-state\u002Fundo-redo","QCtD0GWYTR7_dI_xv3wAMUYSkG7Iv85m-hA-znda1r0",1780949761220]