[{"data":1,"prerenderedAt":3955},["ShallowReactive",2],{"page-\u002Fvue\u002F02-template-syntax-and-directives":3},{"id":4,"title":5,"body":6,"description":3948,"extension":3949,"meta":3950,"navigation":83,"path":3951,"seo":3952,"stem":3953,"__hash__":3954},"content\u002Fvue\u002F02-template-syntax-and-directives.md","Vue 3 Engineering Reference — Template Syntax & Directives",{"type":7,"value":8,"toc":3934},"minimark",[9,14,19,233,237,785,790,1072,1173,1177,1497,1501,1981,1985,2376,2380,2747,2751,2908,3154,3158,3415,3419,3548,3552,3556,3784,3930],[10,11,13],"h1",{"id":12},"_02-template-syntax-directives","02 — Template Syntax & Directives",[15,16,18],"h2",{"id":17},"compiler-output-what-templates-become","Compiler Output — What Templates Become",[20,21,24],"code-wrapper",{"filename":22,"language":23},"compiler-output.ts","typescript",[25,26,30],"pre",{"className":27,"code":28,"language":23,"meta":29,"style":29},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F ── Template ──────────────────────────────────────────\n\u002F\u002F \u003Cdiv :class=\"{ active: isActive }\" @click=\"onClick\">{{ msg }}\u003C\u002Fdiv>\n\u002F\u002F\n\u002F\u002F ── Compiled render function (simplified) ─────────────\nimport { h, withDirectives, vModelText } from 'vue'\n\nfunction render(_ctx) {\n  \u002F\u002F h(type, props, children) — returns a VNode object\n  \u002F\u002F PatchFlag 2 = CLASS (compiler knows ONLY class may change → targeted patch)\n  \u002F\u002F PatchFlag 8 = PROPS (only listed props may change)\n  \u002F\u002F PatchFlag -1 = FULL_PATCH (no hint, diff everything)\n  \u002F\u002F PatchFlag -2 = HOISTED (static subtree, skipped entirely)\n  return h('div', {\n    class: { active: _ctx.isActive },       \u002F\u002F bound — tracked by flag\n    onClick: _ctx.onClick,                   \u002F\u002F event handler — always reactive\n    \u002F\u002F __v_internal__ flag: tells runtime \"only diff class, skip everything else\"\n  }, _ctx.msg, 2 \u002F* CLASS *\u002F)\n}\n\n\u002F\u002F ── The optimization: ──────────────────────────────────\n\u002F\u002F The compiler analyzes the template at BUILD time and tags each vnode\n\u002F\u002F with a PatchFlag. At runtime, the patcher only checks the flagged\n\u002F\u002F properties — skipping full prop diffing on static subtrees.\n\u002F\u002F This is why compiled templates outperform hand-written render functions.\n","",[31,32,33,42,48,54,60,78,85,105,111,117,123,129,135,152,161,170,176,192,198,203,209,215,221,227],"code",{"__ignoreMap":29},[34,35,38],"span",{"class":36,"line":37},"line",1,[34,39,41],{"class":40},"sdCPZ","\u002F\u002F ── Template ──────────────────────────────────────────\n",[34,43,45],{"class":36,"line":44},2,[34,46,47],{"class":40},"\u002F\u002F \u003Cdiv :class=\"{ active: isActive }\" @click=\"onClick\">{{ msg }}\u003C\u002Fdiv>\n",[34,49,51],{"class":36,"line":50},3,[34,52,53],{"class":40},"\u002F\u002F\n",[34,55,57],{"class":36,"line":56},4,[34,58,59],{"class":40},"\u002F\u002F ── Compiled render function (simplified) ─────────────\n",[34,61,63,67,71,74],{"class":36,"line":62},5,[34,64,66],{"class":65},"svdQ7","import",[34,68,70],{"class":69},"ssxIu"," { h, withDirectives, vModelText } ",[34,72,73],{"class":65},"from",[34,75,77],{"class":76},"sJ6F3"," 'vue'\n",[34,79,81],{"class":36,"line":80},6,[34,82,84],{"emptyLinePlaceholder":83},true,"\n",[34,86,88,91,95,98,102],{"class":36,"line":87},7,[34,89,90],{"class":65},"function",[34,92,94],{"class":93},"sIsaT"," render",[34,96,97],{"class":69},"(",[34,99,101],{"class":100},"sCrzJ","_ctx",[34,103,104],{"class":69},") {\n",[34,106,108],{"class":36,"line":107},8,[34,109,110],{"class":40},"  \u002F\u002F h(type, props, children) — returns a VNode object\n",[34,112,114],{"class":36,"line":113},9,[34,115,116],{"class":40},"  \u002F\u002F PatchFlag 2 = CLASS (compiler knows ONLY class may change → targeted patch)\n",[34,118,120],{"class":36,"line":119},10,[34,121,122],{"class":40},"  \u002F\u002F PatchFlag 8 = PROPS (only listed props may change)\n",[34,124,126],{"class":36,"line":125},11,[34,127,128],{"class":40},"  \u002F\u002F PatchFlag -1 = FULL_PATCH (no hint, diff everything)\n",[34,130,132],{"class":36,"line":131},12,[34,133,134],{"class":40},"  \u002F\u002F PatchFlag -2 = HOISTED (static subtree, skipped entirely)\n",[34,136,138,141,144,146,149],{"class":36,"line":137},13,[34,139,140],{"class":65},"  return",[34,142,143],{"class":93}," h",[34,145,97],{"class":69},[34,147,148],{"class":76},"'div'",[34,150,151],{"class":69},", {\n",[34,153,155,158],{"class":36,"line":154},14,[34,156,157],{"class":69},"    class: { active: _ctx.isActive },       ",[34,159,160],{"class":40},"\u002F\u002F bound — tracked by flag\n",[34,162,164,167],{"class":36,"line":163},15,[34,165,166],{"class":69},"    onClick: _ctx.onClick,                   ",[34,168,169],{"class":40},"\u002F\u002F event handler — always reactive\n",[34,171,173],{"class":36,"line":172},16,[34,174,175],{"class":40},"    \u002F\u002F __v_internal__ flag: tells runtime \"only diff class, skip everything else\"\n",[34,177,179,182,186,189],{"class":36,"line":178},17,[34,180,181],{"class":69},"  }, _ctx.msg, ",[34,183,185],{"class":184},"snvgF","2",[34,187,188],{"class":40}," \u002F* CLASS *\u002F",[34,190,191],{"class":69},")\n",[34,193,195],{"class":36,"line":194},18,[34,196,197],{"class":69},"}\n",[34,199,201],{"class":36,"line":200},19,[34,202,84],{"emptyLinePlaceholder":83},[34,204,206],{"class":36,"line":205},20,[34,207,208],{"class":40},"\u002F\u002F ── The optimization: ──────────────────────────────────\n",[34,210,212],{"class":36,"line":211},21,[34,213,214],{"class":40},"\u002F\u002F The compiler analyzes the template at BUILD time and tags each vnode\n",[34,216,218],{"class":36,"line":217},22,[34,219,220],{"class":40},"\u002F\u002F with a PatchFlag. At runtime, the patcher only checks the flagged\n",[34,222,224],{"class":36,"line":223},23,[34,225,226],{"class":40},"\u002F\u002F properties — skipping full prop diffing on static subtrees.\n",[34,228,230],{"class":36,"line":229},24,[34,231,232],{"class":40},"\u002F\u002F This is why compiled templates outperform hand-written render functions.\n",[15,234,236],{"id":235},"production-list-rendering-keyed-virtual-dom-diffing","Production List Rendering — Keyed Virtual DOM Diffing",[20,238,241],{"filename":239,"language":240},"DataTable.vue","vue",[25,242,245],{"className":243,"code":244,"language":240,"meta":29,"style":29},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\nimport { ref, computed, type ShallowRef } from 'vue'\n\ninterface Row {\n  id: string\n  name: string\n  status: 'active' | 'inactive' | 'pending'\n  updatedAt: number\n}\n\n\u002F\u002F ── Simulated server data — mutable in place ────────────\nconst rows = ref\u003CRow[]>([])\n\n\u002F\u002F ── Derived view: filtered + sorted, memoized via computed ──\n\u002F\u002F computed() caches the result; only re-evaluates when `rows` changes.\n\u002F\u002F Avoids re-filtering on every re-render (which happens on ANY reactive read).\nconst visibleRows = computed(() =>\n  rows.value\n    .filter(r => r.status !== 'inactive')\n    .sort((a, b) => b.updatedAt - a.updatedAt)\n)\n\n\u002F\u002F ── Update a single row WITHOUT replacing the array ────\n\u002F\u002F Direct index mutation works in Vue 3 (Proxy catches it), but\n\u002F\u002F replacing the array is clearer for diffing and time-travel debugging.\nfunction patchRow(id: string, patch: Partial\u003CRow>) {\n  rows.value = rows.value.map(r =>\n    r.id === id ? { ...r, ...patch, updatedAt: Date.now() } : r\n  )\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003C!-- :key MUST be a stable identity — never the array index.\n       With id keys, Vue's diff algorithm:\n       1. Builds a Map of old vnodes keyed by id\n       2. Iterates new list, patches matched vnodes in place\n       3. Moves unmatched vnodes to new positions (no re-creation)\n       4. Destroys vnodes that no longer exist (runs onUnmounted)\n       Without stable keys, Vue patches by INDEX — reusing DOM nodes\n       for the wrong data, corrupting per-row state (focus, transitions). -->\n  \u003Ctr v-for=\"row in visibleRows\" :key=\"row.id\">\n    \u003Ctd>{{ row.name }}\u003C\u002Ftd>\n    \u003Ctd>{{ row.status }}\u003C\u002Ftd>\n    \u003Ctd>{{ new Date(row.updatedAt).toISOString() }}\u003C\u002Ftd>\n  \u003C\u002Ftr>\n\u003C\u002Ftemplate>\n",[31,246,247,271,288,292,303,314,323,344,354,358,362,367,389,393,398,403,408,426,431,457,491,495,499,504,509,515,550,571,611,617,622,632,637,647,653,659,665,671,677,683,689,695,722,738,752,766,776],{"__ignoreMap":29},[34,248,249,252,256,259,262,265,268],{"class":36,"line":37},[34,250,251],{"class":69},"\u003C",[34,253,255],{"class":254},"sk71V","script",[34,257,258],{"class":93}," setup",[34,260,261],{"class":93}," lang",[34,263,264],{"class":69},"=",[34,266,267],{"class":76},"\"ts\"",[34,269,270],{"class":69},">\n",[34,272,273,275,278,281,284,286],{"class":36,"line":44},[34,274,66],{"class":65},[34,276,277],{"class":69}," { ref, computed, ",[34,279,280],{"class":65},"type",[34,282,283],{"class":69}," ShallowRef } ",[34,285,73],{"class":65},[34,287,77],{"class":76},[34,289,290],{"class":36,"line":50},[34,291,84],{"emptyLinePlaceholder":83},[34,293,294,297,300],{"class":36,"line":56},[34,295,296],{"class":65},"interface",[34,298,299],{"class":93}," Row",[34,301,302],{"class":69}," {\n",[34,304,305,308,311],{"class":36,"line":62},[34,306,307],{"class":100},"  id",[34,309,310],{"class":65},":",[34,312,313],{"class":184}," string\n",[34,315,316,319,321],{"class":36,"line":80},[34,317,318],{"class":100},"  name",[34,320,310],{"class":65},[34,322,313],{"class":184},[34,324,325,328,330,333,336,339,341],{"class":36,"line":87},[34,326,327],{"class":100},"  status",[34,329,310],{"class":65},[34,331,332],{"class":76}," 'active'",[34,334,335],{"class":65}," |",[34,337,338],{"class":76}," 'inactive'",[34,340,335],{"class":65},[34,342,343],{"class":76}," 'pending'\n",[34,345,346,349,351],{"class":36,"line":107},[34,347,348],{"class":100},"  updatedAt",[34,350,310],{"class":65},[34,352,353],{"class":184}," number\n",[34,355,356],{"class":36,"line":113},[34,357,197],{"class":69},[34,359,360],{"class":36,"line":119},[34,361,84],{"emptyLinePlaceholder":83},[34,363,364],{"class":36,"line":125},[34,365,366],{"class":40},"\u002F\u002F ── Simulated server data — mutable in place ────────────\n",[34,368,369,372,375,378,381,383,386],{"class":36,"line":131},[34,370,371],{"class":65},"const",[34,373,374],{"class":184}," rows",[34,376,377],{"class":65}," =",[34,379,380],{"class":93}," ref",[34,382,251],{"class":69},[34,384,385],{"class":93},"Row",[34,387,388],{"class":69},"[]>([])\n",[34,390,391],{"class":36,"line":137},[34,392,84],{"emptyLinePlaceholder":83},[34,394,395],{"class":36,"line":154},[34,396,397],{"class":40},"\u002F\u002F ── Derived view: filtered + sorted, memoized via computed ──\n",[34,399,400],{"class":36,"line":163},[34,401,402],{"class":40},"\u002F\u002F computed() caches the result; only re-evaluates when `rows` changes.\n",[34,404,405],{"class":36,"line":172},[34,406,407],{"class":40},"\u002F\u002F Avoids re-filtering on every re-render (which happens on ANY reactive read).\n",[34,409,410,412,415,417,420,423],{"class":36,"line":178},[34,411,371],{"class":65},[34,413,414],{"class":184}," visibleRows",[34,416,377],{"class":65},[34,418,419],{"class":93}," computed",[34,421,422],{"class":69},"(() ",[34,424,425],{"class":65},"=>\n",[34,427,428],{"class":36,"line":194},[34,429,430],{"class":69},"  rows.value\n",[34,432,433,436,439,441,444,447,450,453,455],{"class":36,"line":200},[34,434,435],{"class":69},"    .",[34,437,438],{"class":93},"filter",[34,440,97],{"class":69},[34,442,443],{"class":100},"r",[34,445,446],{"class":65}," =>",[34,448,449],{"class":69}," r.status ",[34,451,452],{"class":65},"!==",[34,454,338],{"class":76},[34,456,191],{"class":69},[34,458,459,461,464,467,470,473,476,479,482,485,488],{"class":36,"line":205},[34,460,435],{"class":69},[34,462,463],{"class":93},"sort",[34,465,466],{"class":69},"((",[34,468,469],{"class":100},"a",[34,471,472],{"class":69},", ",[34,474,475],{"class":100},"b",[34,477,478],{"class":69},") ",[34,480,481],{"class":65},"=>",[34,483,484],{"class":69}," b.updatedAt ",[34,486,487],{"class":65},"-",[34,489,490],{"class":69}," a.updatedAt)\n",[34,492,493],{"class":36,"line":211},[34,494,191],{"class":69},[34,496,497],{"class":36,"line":217},[34,498,84],{"emptyLinePlaceholder":83},[34,500,501],{"class":36,"line":223},[34,502,503],{"class":40},"\u002F\u002F ── Update a single row WITHOUT replacing the array ────\n",[34,505,506],{"class":36,"line":229},[34,507,508],{"class":40},"\u002F\u002F Direct index mutation works in Vue 3 (Proxy catches it), but\n",[34,510,512],{"class":36,"line":511},25,[34,513,514],{"class":40},"\u002F\u002F replacing the array is clearer for diffing and time-travel debugging.\n",[34,516,518,520,523,525,528,530,533,535,538,540,543,545,547],{"class":36,"line":517},26,[34,519,90],{"class":65},[34,521,522],{"class":93}," patchRow",[34,524,97],{"class":69},[34,526,527],{"class":100},"id",[34,529,310],{"class":65},[34,531,532],{"class":184}," string",[34,534,472],{"class":69},[34,536,537],{"class":100},"patch",[34,539,310],{"class":65},[34,541,542],{"class":93}," Partial",[34,544,251],{"class":69},[34,546,385],{"class":93},[34,548,549],{"class":69},">) {\n",[34,551,553,556,558,561,564,566,568],{"class":36,"line":552},27,[34,554,555],{"class":69},"  rows.value ",[34,557,264],{"class":65},[34,559,560],{"class":69}," rows.value.",[34,562,563],{"class":93},"map",[34,565,97],{"class":69},[34,567,443],{"class":100},[34,569,570],{"class":65}," =>\n",[34,572,574,577,580,583,586,589,592,595,597,600,603,606,608],{"class":36,"line":573},28,[34,575,576],{"class":69},"    r.id ",[34,578,579],{"class":65},"===",[34,581,582],{"class":69}," id ",[34,584,585],{"class":65},"?",[34,587,588],{"class":69}," { ",[34,590,591],{"class":65},"...",[34,593,594],{"class":69},"r, ",[34,596,591],{"class":65},[34,598,599],{"class":69},"patch, updatedAt: Date.",[34,601,602],{"class":93},"now",[34,604,605],{"class":69},"() } ",[34,607,310],{"class":65},[34,609,610],{"class":69}," r\n",[34,612,614],{"class":36,"line":613},29,[34,615,616],{"class":69},"  )\n",[34,618,620],{"class":36,"line":619},30,[34,621,197],{"class":69},[34,623,625,628,630],{"class":36,"line":624},31,[34,626,627],{"class":69},"\u003C\u002F",[34,629,255],{"class":254},[34,631,270],{"class":69},[34,633,635],{"class":36,"line":634},32,[34,636,84],{"emptyLinePlaceholder":83},[34,638,640,642,645],{"class":36,"line":639},33,[34,641,251],{"class":69},[34,643,644],{"class":254},"template",[34,646,270],{"class":69},[34,648,650],{"class":36,"line":649},34,[34,651,652],{"class":40},"  \u003C!-- :key MUST be a stable identity — never the array index.\n",[34,654,656],{"class":36,"line":655},35,[34,657,658],{"class":40},"       With id keys, Vue's diff algorithm:\n",[34,660,662],{"class":36,"line":661},36,[34,663,664],{"class":40},"       1. Builds a Map of old vnodes keyed by id\n",[34,666,668],{"class":36,"line":667},37,[34,669,670],{"class":40},"       2. Iterates new list, patches matched vnodes in place\n",[34,672,674],{"class":36,"line":673},38,[34,675,676],{"class":40},"       3. Moves unmatched vnodes to new positions (no re-creation)\n",[34,678,680],{"class":36,"line":679},39,[34,681,682],{"class":40},"       4. Destroys vnodes that no longer exist (runs onUnmounted)\n",[34,684,686],{"class":36,"line":685},40,[34,687,688],{"class":40},"       Without stable keys, Vue patches by INDEX — reusing DOM nodes\n",[34,690,692],{"class":36,"line":691},41,[34,693,694],{"class":40},"       for the wrong data, corrupting per-row state (focus, transitions). -->\n",[34,696,698,701,704,707,709,712,715,717,720],{"class":36,"line":697},42,[34,699,700],{"class":69},"  \u003C",[34,702,703],{"class":254},"tr",[34,705,706],{"class":93}," v-for",[34,708,264],{"class":69},[34,710,711],{"class":76},"\"row in visibleRows\"",[34,713,714],{"class":93}," :key",[34,716,264],{"class":69},[34,718,719],{"class":76},"\"row.id\"",[34,721,270],{"class":69},[34,723,725,728,731,734,736],{"class":36,"line":724},43,[34,726,727],{"class":69},"    \u003C",[34,729,730],{"class":254},"td",[34,732,733],{"class":69},">{{ row.name }}\u003C\u002F",[34,735,730],{"class":254},[34,737,270],{"class":69},[34,739,741,743,745,748,750],{"class":36,"line":740},44,[34,742,727],{"class":69},[34,744,730],{"class":254},[34,746,747],{"class":69},">{{ row.status }}\u003C\u002F",[34,749,730],{"class":254},[34,751,270],{"class":69},[34,753,755,757,759,762,764],{"class":36,"line":754},45,[34,756,727],{"class":69},[34,758,730],{"class":254},[34,760,761],{"class":69},">{{ new Date(row.updatedAt).toISOString() }}\u003C\u002F",[34,763,730],{"class":254},[34,765,270],{"class":69},[34,767,769,772,774],{"class":36,"line":768},46,[34,770,771],{"class":69},"  \u003C\u002F",[34,773,703],{"class":254},[34,775,270],{"class":69},[34,777,779,781,783],{"class":36,"line":778},47,[34,780,627],{"class":69},[34,782,644],{"class":254},[34,784,270],{"class":69},[786,787,789],"h3",{"id":788},"anti-pattern-index-as-key","Anti-Pattern: Index as Key",[20,791,793],{"filename":792,"language":240},"AntiPatternKey.vue",[25,794,796],{"className":243,"code":795,"language":240,"meta":29,"style":29},"\u003Cscript setup>\nimport { ref } from 'vue'\n\nconst items = ref([\n  { id: 1, text: 'Alpha', checked: false },\n  { id: 2, text: 'Beta', checked: true },\n  { id: 3, text: 'Gamma', checked: false },\n])\n\nfunction removeFirst() { items.value.shift() }\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003C!-- ❌ WRONG: index key — DOM nodes reused by position, not identity -->\n  \u003Cli v-for=\"(item, i) in items\" :key=\"i\">\n    \u003Cinput type=\"checkbox\" v-model=\"item.checked\" \u002F>\n    {{ item.text }}\n  \u003C\u002Fli>\n  \u003Cbutton @click=\"removeFirst\">Remove first\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n\n\u003C!-- After clicking \"Remove first\":\n     - items[0] is now \"Beta\" (was \"Alpha\")\n     - The \u003Cinput> at DOM position 0 is REUSED (not destroyed)\n     - That input's checked state is \"false\" (Alpha's state, not Beta's)\n     - The checkbox appears unchecked even though item.checked is true\n     The DOM state (checked) is detached from the data state. -->\n",[31,797,798,808,819,823,837,860,878,896,901,905,921,929,933,941,946,969,995,1000,1008,1030,1038,1042,1047,1052,1057,1062,1067],{"__ignoreMap":29},[34,799,800,802,804,806],{"class":36,"line":37},[34,801,251],{"class":69},[34,803,255],{"class":254},[34,805,258],{"class":93},[34,807,270],{"class":69},[34,809,810,812,815,817],{"class":36,"line":44},[34,811,66],{"class":65},[34,813,814],{"class":69}," { ref } ",[34,816,73],{"class":65},[34,818,77],{"class":76},[34,820,821],{"class":36,"line":50},[34,822,84],{"emptyLinePlaceholder":83},[34,824,825,827,830,832,834],{"class":36,"line":56},[34,826,371],{"class":65},[34,828,829],{"class":184}," items",[34,831,377],{"class":65},[34,833,380],{"class":93},[34,835,836],{"class":69},"([\n",[34,838,839,842,845,848,851,854,857],{"class":36,"line":62},[34,840,841],{"class":69},"  { id: ",[34,843,844],{"class":184},"1",[34,846,847],{"class":69},", text: ",[34,849,850],{"class":76},"'Alpha'",[34,852,853],{"class":69},", checked: ",[34,855,856],{"class":184},"false",[34,858,859],{"class":69}," },\n",[34,861,862,864,866,868,871,873,876],{"class":36,"line":80},[34,863,841],{"class":69},[34,865,185],{"class":184},[34,867,847],{"class":69},[34,869,870],{"class":76},"'Beta'",[34,872,853],{"class":69},[34,874,875],{"class":184},"true",[34,877,859],{"class":69},[34,879,880,882,885,887,890,892,894],{"class":36,"line":87},[34,881,841],{"class":69},[34,883,884],{"class":184},"3",[34,886,847],{"class":69},[34,888,889],{"class":76},"'Gamma'",[34,891,853],{"class":69},[34,893,856],{"class":184},[34,895,859],{"class":69},[34,897,898],{"class":36,"line":107},[34,899,900],{"class":69},"])\n",[34,902,903],{"class":36,"line":113},[34,904,84],{"emptyLinePlaceholder":83},[34,906,907,909,912,915,918],{"class":36,"line":119},[34,908,90],{"class":65},[34,910,911],{"class":93}," removeFirst",[34,913,914],{"class":69},"() { items.value.",[34,916,917],{"class":93},"shift",[34,919,920],{"class":69},"() }\n",[34,922,923,925,927],{"class":36,"line":125},[34,924,627],{"class":69},[34,926,255],{"class":254},[34,928,270],{"class":69},[34,930,931],{"class":36,"line":131},[34,932,84],{"emptyLinePlaceholder":83},[34,934,935,937,939],{"class":36,"line":137},[34,936,251],{"class":69},[34,938,644],{"class":254},[34,940,270],{"class":69},[34,942,943],{"class":36,"line":154},[34,944,945],{"class":40},"  \u003C!-- ❌ WRONG: index key — DOM nodes reused by position, not identity -->\n",[34,947,948,950,953,955,957,960,962,964,967],{"class":36,"line":163},[34,949,700],{"class":69},[34,951,952],{"class":254},"li",[34,954,706],{"class":93},[34,956,264],{"class":69},[34,958,959],{"class":76},"\"(item, i) in items\"",[34,961,714],{"class":93},[34,963,264],{"class":69},[34,965,966],{"class":76},"\"i\"",[34,968,270],{"class":69},[34,970,971,973,976,979,981,984,987,989,992],{"class":36,"line":172},[34,972,727],{"class":69},[34,974,975],{"class":254},"input",[34,977,978],{"class":93}," type",[34,980,264],{"class":69},[34,982,983],{"class":76},"\"checkbox\"",[34,985,986],{"class":93}," v-model",[34,988,264],{"class":69},[34,990,991],{"class":76},"\"item.checked\"",[34,993,994],{"class":69}," \u002F>\n",[34,996,997],{"class":36,"line":178},[34,998,999],{"class":69},"    {{ item.text }}\n",[34,1001,1002,1004,1006],{"class":36,"line":194},[34,1003,771],{"class":69},[34,1005,952],{"class":254},[34,1007,270],{"class":69},[34,1009,1010,1012,1015,1018,1020,1023,1026,1028],{"class":36,"line":200},[34,1011,700],{"class":69},[34,1013,1014],{"class":254},"button",[34,1016,1017],{"class":93}," @click",[34,1019,264],{"class":69},[34,1021,1022],{"class":76},"\"removeFirst\"",[34,1024,1025],{"class":69},">Remove first\u003C\u002F",[34,1027,1014],{"class":254},[34,1029,270],{"class":69},[34,1031,1032,1034,1036],{"class":36,"line":205},[34,1033,627],{"class":69},[34,1035,644],{"class":254},[34,1037,270],{"class":69},[34,1039,1040],{"class":36,"line":211},[34,1041,84],{"emptyLinePlaceholder":83},[34,1043,1044],{"class":36,"line":217},[34,1045,1046],{"class":40},"\u003C!-- After clicking \"Remove first\":\n",[34,1048,1049],{"class":36,"line":223},[34,1050,1051],{"class":40},"     - items[0] is now \"Beta\" (was \"Alpha\")\n",[34,1053,1054],{"class":36,"line":229},[34,1055,1056],{"class":40},"     - The \u003Cinput> at DOM position 0 is REUSED (not destroyed)\n",[34,1058,1059],{"class":36,"line":511},[34,1060,1061],{"class":40},"     - That input's checked state is \"false\" (Alpha's state, not Beta's)\n",[34,1063,1064],{"class":36,"line":517},[34,1065,1066],{"class":40},"     - The checkbox appears unchecked even though item.checked is true\n",[34,1068,1069],{"class":36,"line":552},[34,1070,1071],{"class":40},"     The DOM state (checked) is detached from the data state. -->\n",[20,1073,1075],{"filename":1074,"language":240},"CorrectKey.vue",[25,1076,1078],{"className":243,"code":1077,"language":240,"meta":29,"style":29},"\u003Ctemplate>\n  \u003C!-- ✅ CORRECT: stable id key — Vue tracks each row by identity -->\n  \u003Cli v-for=\"item in items\" :key=\"item.id\">\n    \u003Cinput type=\"checkbox\" v-model=\"item.checked\" \u002F>\n    {{ item.text }}\n  \u003C\u002Fli>\n  \u003Cbutton @click=\"removeFirst\">Remove first\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[31,1079,1080,1088,1093,1115,1135,1139,1147,1165],{"__ignoreMap":29},[34,1081,1082,1084,1086],{"class":36,"line":37},[34,1083,251],{"class":69},[34,1085,644],{"class":254},[34,1087,270],{"class":69},[34,1089,1090],{"class":36,"line":44},[34,1091,1092],{"class":40},"  \u003C!-- ✅ CORRECT: stable id key — Vue tracks each row by identity -->\n",[34,1094,1095,1097,1099,1101,1103,1106,1108,1110,1113],{"class":36,"line":50},[34,1096,700],{"class":69},[34,1098,952],{"class":254},[34,1100,706],{"class":93},[34,1102,264],{"class":69},[34,1104,1105],{"class":76},"\"item in items\"",[34,1107,714],{"class":93},[34,1109,264],{"class":69},[34,1111,1112],{"class":76},"\"item.id\"",[34,1114,270],{"class":69},[34,1116,1117,1119,1121,1123,1125,1127,1129,1131,1133],{"class":36,"line":56},[34,1118,727],{"class":69},[34,1120,975],{"class":254},[34,1122,978],{"class":93},[34,1124,264],{"class":69},[34,1126,983],{"class":76},[34,1128,986],{"class":93},[34,1130,264],{"class":69},[34,1132,991],{"class":76},[34,1134,994],{"class":69},[34,1136,1137],{"class":36,"line":62},[34,1138,999],{"class":69},[34,1140,1141,1143,1145],{"class":36,"line":80},[34,1142,771],{"class":69},[34,1144,952],{"class":254},[34,1146,270],{"class":69},[34,1148,1149,1151,1153,1155,1157,1159,1161,1163],{"class":36,"line":87},[34,1150,700],{"class":69},[34,1152,1014],{"class":254},[34,1154,1017],{"class":93},[34,1156,264],{"class":69},[34,1158,1022],{"class":76},[34,1160,1025],{"class":69},[34,1162,1014],{"class":254},[34,1164,270],{"class":69},[34,1166,1167,1169,1171],{"class":36,"line":107},[34,1168,627],{"class":69},[34,1170,644],{"class":254},[34,1172,270],{"class":69},[15,1174,1176],{"id":1175},"conditional-rendering-v-if-vs-v-show-decision-matrix","Conditional Rendering — v-if vs v-show Decision Matrix",[20,1178,1180],{"filename":1179,"language":240},"ConditionalStrategy.vue",[25,1181,1183],{"className":243,"code":1182,"language":240,"meta":29,"style":29},"\u003Cscript setup>\nimport { ref, shallowRef, onActivated, onDeactivated } from 'vue'\nimport HeavyChart from '.\u002FHeavyChart.vue'\nimport Tooltip from '.\u002FTooltip.vue'\n\nconst showChart = ref(false)\nconst tooltipVisible = ref(false)\nconst tab = ref('overview')\n\n\u002F\u002F ── v-if: destroys and recreates the component ──────\n\u002F\u002F Runs onMounted\u002FonUnmounted every toggle.\n\u002F\u002F Cost: full teardown + recreation. Zero cost when hidden.\n\u002F\u002F Use for: rarely toggled, expensive to keep mounted.\n\n\u002F\u002F ── v-show: toggles display:none ─────────────────────\n\u002F\u002F Component stays mounted, lifecycle hooks run ONCE.\n\u002F\u002F Cost: initial render happens even when hidden.\n\u002F\u002F Use for: frequently toggled, cheap to keep alive.\n\n\u002F\u002F ── \u003CKeepAlive>: caches component instances ─────────\n\u002F\u002F v-if + KeepAlive = best of both: unmounts from DOM tree\n\u002F\u002F but preserves instance state in memory, reuses on re-mount.\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003C!-- Heavy chart: v-if — don't pay render cost when hidden -->\n  \u003CKeepAlive>\n    \u003CHeavyChart v-if=\"showChart\" \u002F>\n  \u003C\u002FKeepAlive>\n\n  \u003C!-- Tooltip: v-show — toggled dozens of times per session -->\n  \u003CTooltip v-show=\"tooltipVisible\" \u002F>\n\n  \u003C!-- Tab content: v-if inside KeepAlive — switchable, cached -->\n  \u003CKeepAlive>\n    \u003Ccomponent :is=\"tab\" \u002F>\n  \u003C\u002FKeepAlive>\n\u003C\u002Ftemplate>\n",[31,1184,1185,1195,1206,1218,1230,1234,1251,1268,1286,1290,1295,1300,1305,1310,1314,1319,1324,1329,1334,1338,1343,1348,1353,1361,1365,1373,1378,1387,1409,1417,1421,1426,1447,1451,1456,1464,1481,1489],{"__ignoreMap":29},[34,1186,1187,1189,1191,1193],{"class":36,"line":37},[34,1188,251],{"class":69},[34,1190,255],{"class":254},[34,1192,258],{"class":93},[34,1194,270],{"class":69},[34,1196,1197,1199,1202,1204],{"class":36,"line":44},[34,1198,66],{"class":65},[34,1200,1201],{"class":69}," { ref, shallowRef, onActivated, onDeactivated } ",[34,1203,73],{"class":65},[34,1205,77],{"class":76},[34,1207,1208,1210,1213,1215],{"class":36,"line":50},[34,1209,66],{"class":65},[34,1211,1212],{"class":69}," HeavyChart ",[34,1214,73],{"class":65},[34,1216,1217],{"class":76}," '.\u002FHeavyChart.vue'\n",[34,1219,1220,1222,1225,1227],{"class":36,"line":56},[34,1221,66],{"class":65},[34,1223,1224],{"class":69}," Tooltip ",[34,1226,73],{"class":65},[34,1228,1229],{"class":76}," '.\u002FTooltip.vue'\n",[34,1231,1232],{"class":36,"line":62},[34,1233,84],{"emptyLinePlaceholder":83},[34,1235,1236,1238,1241,1243,1245,1247,1249],{"class":36,"line":80},[34,1237,371],{"class":65},[34,1239,1240],{"class":184}," showChart",[34,1242,377],{"class":65},[34,1244,380],{"class":93},[34,1246,97],{"class":69},[34,1248,856],{"class":184},[34,1250,191],{"class":69},[34,1252,1253,1255,1258,1260,1262,1264,1266],{"class":36,"line":87},[34,1254,371],{"class":65},[34,1256,1257],{"class":184}," tooltipVisible",[34,1259,377],{"class":65},[34,1261,380],{"class":93},[34,1263,97],{"class":69},[34,1265,856],{"class":184},[34,1267,191],{"class":69},[34,1269,1270,1272,1275,1277,1279,1281,1284],{"class":36,"line":107},[34,1271,371],{"class":65},[34,1273,1274],{"class":184}," tab",[34,1276,377],{"class":65},[34,1278,380],{"class":93},[34,1280,97],{"class":69},[34,1282,1283],{"class":76},"'overview'",[34,1285,191],{"class":69},[34,1287,1288],{"class":36,"line":113},[34,1289,84],{"emptyLinePlaceholder":83},[34,1291,1292],{"class":36,"line":119},[34,1293,1294],{"class":40},"\u002F\u002F ── v-if: destroys and recreates the component ──────\n",[34,1296,1297],{"class":36,"line":125},[34,1298,1299],{"class":40},"\u002F\u002F Runs onMounted\u002FonUnmounted every toggle.\n",[34,1301,1302],{"class":36,"line":131},[34,1303,1304],{"class":40},"\u002F\u002F Cost: full teardown + recreation. Zero cost when hidden.\n",[34,1306,1307],{"class":36,"line":137},[34,1308,1309],{"class":40},"\u002F\u002F Use for: rarely toggled, expensive to keep mounted.\n",[34,1311,1312],{"class":36,"line":154},[34,1313,84],{"emptyLinePlaceholder":83},[34,1315,1316],{"class":36,"line":163},[34,1317,1318],{"class":40},"\u002F\u002F ── v-show: toggles display:none ─────────────────────\n",[34,1320,1321],{"class":36,"line":172},[34,1322,1323],{"class":40},"\u002F\u002F Component stays mounted, lifecycle hooks run ONCE.\n",[34,1325,1326],{"class":36,"line":178},[34,1327,1328],{"class":40},"\u002F\u002F Cost: initial render happens even when hidden.\n",[34,1330,1331],{"class":36,"line":194},[34,1332,1333],{"class":40},"\u002F\u002F Use for: frequently toggled, cheap to keep alive.\n",[34,1335,1336],{"class":36,"line":200},[34,1337,84],{"emptyLinePlaceholder":83},[34,1339,1340],{"class":36,"line":205},[34,1341,1342],{"class":40},"\u002F\u002F ── \u003CKeepAlive>: caches component instances ─────────\n",[34,1344,1345],{"class":36,"line":211},[34,1346,1347],{"class":40},"\u002F\u002F v-if + KeepAlive = best of both: unmounts from DOM tree\n",[34,1349,1350],{"class":36,"line":217},[34,1351,1352],{"class":40},"\u002F\u002F but preserves instance state in memory, reuses on re-mount.\n",[34,1354,1355,1357,1359],{"class":36,"line":223},[34,1356,627],{"class":69},[34,1358,255],{"class":254},[34,1360,270],{"class":69},[34,1362,1363],{"class":36,"line":229},[34,1364,84],{"emptyLinePlaceholder":83},[34,1366,1367,1369,1371],{"class":36,"line":511},[34,1368,251],{"class":69},[34,1370,644],{"class":254},[34,1372,270],{"class":69},[34,1374,1375],{"class":36,"line":517},[34,1376,1377],{"class":40},"  \u003C!-- Heavy chart: v-if — don't pay render cost when hidden -->\n",[34,1379,1380,1382,1385],{"class":36,"line":552},[34,1381,700],{"class":69},[34,1383,1384],{"class":254},"KeepAlive",[34,1386,270],{"class":69},[34,1388,1389,1391,1394,1397,1399,1402,1405,1407],{"class":36,"line":573},[34,1390,727],{"class":69},[34,1392,1393],{"class":254},"HeavyChart",[34,1395,1396],{"class":65}," v-if",[34,1398,264],{"class":69},[34,1400,1401],{"class":76},"\"",[34,1403,1404],{"class":69},"showChart",[34,1406,1401],{"class":76},[34,1408,994],{"class":69},[34,1410,1411,1413,1415],{"class":36,"line":613},[34,1412,771],{"class":69},[34,1414,1384],{"class":254},[34,1416,270],{"class":69},[34,1418,1419],{"class":36,"line":619},[34,1420,84],{"emptyLinePlaceholder":83},[34,1422,1423],{"class":36,"line":624},[34,1424,1425],{"class":40},"  \u003C!-- Tooltip: v-show — toggled dozens of times per session -->\n",[34,1427,1428,1430,1433,1436,1438,1440,1443,1445],{"class":36,"line":634},[34,1429,700],{"class":69},[34,1431,1432],{"class":254},"Tooltip",[34,1434,1435],{"class":93}," v-show",[34,1437,264],{"class":69},[34,1439,1401],{"class":76},[34,1441,1442],{"class":69},"tooltipVisible",[34,1444,1401],{"class":76},[34,1446,994],{"class":69},[34,1448,1449],{"class":36,"line":639},[34,1450,84],{"emptyLinePlaceholder":83},[34,1452,1453],{"class":36,"line":649},[34,1454,1455],{"class":40},"  \u003C!-- Tab content: v-if inside KeepAlive — switchable, cached -->\n",[34,1457,1458,1460,1462],{"class":36,"line":655},[34,1459,700],{"class":69},[34,1461,1384],{"class":254},[34,1463,270],{"class":69},[34,1465,1466,1468,1471,1474,1476,1479],{"class":36,"line":661},[34,1467,727],{"class":69},[34,1469,1470],{"class":254},"component",[34,1472,1473],{"class":93}," :is",[34,1475,264],{"class":69},[34,1477,1478],{"class":76},"\"tab\"",[34,1480,994],{"class":69},[34,1482,1483,1485,1487],{"class":36,"line":667},[34,1484,771],{"class":69},[34,1486,1384],{"class":254},[34,1488,270],{"class":69},[34,1490,1491,1493,1495],{"class":36,"line":673},[34,1492,627],{"class":69},[34,1494,644],{"class":254},[34,1496,270],{"class":69},[15,1498,1500],{"id":1499},"event-modifiers-chained-handler-pipeline","Event Modifiers — Chained Handler Pipeline",[20,1502,1504],{"filename":1503,"language":240},"EventModifiers.vue",[25,1505,1507],{"className":243,"code":1506,"language":240,"meta":29,"style":29},"\u003Cscript setup>\nimport { ref } from 'vue'\n\nconst dragged = ref(0)\n\nfunction onDrop(e) {\n  \u002F\u002F .prevent already called preventDefault() — safe to access dataTransfer\n  const data = e.dataTransfer?.getData('text\u002Fplain') ?? ''\n  console.log('dropped:', data)\n}\n\nfunction onKeyNav(e) {\n  \u002F\u002F .enter filters to Enter key only — handler never fires for other keys\n  console.log('Enter pressed, value:', e.target.value)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003C!-- Modifier chain: left→right execution order\n       .stop   → event.stopPropagation()\n       .prevent → event.preventDefault()\n       .self   → only fire if event.target === currentTarget\n       .once   → remove listener after first call -->\n  \u003Cbutton @click.stop.prevent.once=\"onSave\">Save (once, no bubble, no default)\u003C\u002Fbutton>\n\n  \u003C!-- .self: ignores clicks from child elements (e.g., icon inside button) -->\n  \u003Cdiv @click.self=\"onBackdropClick\" class=\"modal-backdrop\">\n    \u003Cdiv class=\"modal\">...\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\n  \u003C!-- .exact: ONLY fire when no modifier keys held -->\n  \u003Cbutton @click.exact=\"onSimpleClick\">Plain click only\u003C\u002Fbutton>\n  \u003Cbutton @click.ctrl.exact=\"onCtrlClick\">Ctrl+click only\u003C\u002Fbutton>\n\n  \u003C!-- Key modifiers: .enter, .tab, .delete, .esc, .space, .up, .down, etc. -->\n  \u003Cinput @keydown.enter=\"onKeyNav\" @keydown.esc=\"onEscape\" \u002F>\n\n  \u003C!-- System modifiers: .ctrl, .alt, .shift, .meta (Cmd on Mac, Win on Windows) -->\n  \u003Cinput @keydown.meta.enter=\"onCmdEnter\" \u002F>\n\n  \u003C!-- .passive: tells browser \"I won't call preventDefault\" → enables scroll optimization -->\n  \u003C!-- Don't combine .passive with .prevent — browser warns, .prevent is ignored -->\n  \u003Cdiv @scroll.passive=\"onScroll\">Scrollable\u003C\u002Fdiv>\n\n  \u003C!-- Mouse button modifiers: .left, .middle, .right -->\n  \u003Cdiv @contextmenu.prevent=\"onRightClick\" @mousedown.middle=\"onMiddleClick\">Drag me\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[31,1508,1509,1519,1529,1533,1551,1555,1569,1574,1603,1619,1623,1627,1640,1645,1659,1663,1671,1675,1683,1688,1693,1698,1703,1708,1729,1733,1738,1763,1783,1791,1795,1800,1821,1842,1846,1851,1875,1879,1884,1900,1904,1909,1914,1935,1939,1944,1973],{"__ignoreMap":29},[34,1510,1511,1513,1515,1517],{"class":36,"line":37},[34,1512,251],{"class":69},[34,1514,255],{"class":254},[34,1516,258],{"class":93},[34,1518,270],{"class":69},[34,1520,1521,1523,1525,1527],{"class":36,"line":44},[34,1522,66],{"class":65},[34,1524,814],{"class":69},[34,1526,73],{"class":65},[34,1528,77],{"class":76},[34,1530,1531],{"class":36,"line":50},[34,1532,84],{"emptyLinePlaceholder":83},[34,1534,1535,1537,1540,1542,1544,1546,1549],{"class":36,"line":56},[34,1536,371],{"class":65},[34,1538,1539],{"class":184}," dragged",[34,1541,377],{"class":65},[34,1543,380],{"class":93},[34,1545,97],{"class":69},[34,1547,1548],{"class":184},"0",[34,1550,191],{"class":69},[34,1552,1553],{"class":36,"line":62},[34,1554,84],{"emptyLinePlaceholder":83},[34,1556,1557,1559,1562,1564,1567],{"class":36,"line":80},[34,1558,90],{"class":65},[34,1560,1561],{"class":93}," onDrop",[34,1563,97],{"class":69},[34,1565,1566],{"class":100},"e",[34,1568,104],{"class":69},[34,1570,1571],{"class":36,"line":87},[34,1572,1573],{"class":40},"  \u002F\u002F .prevent already called preventDefault() — safe to access dataTransfer\n",[34,1575,1576,1579,1582,1584,1587,1590,1592,1595,1597,1600],{"class":36,"line":107},[34,1577,1578],{"class":65},"  const",[34,1580,1581],{"class":184}," data",[34,1583,377],{"class":65},[34,1585,1586],{"class":69}," e.dataTransfer?.",[34,1588,1589],{"class":93},"getData",[34,1591,97],{"class":69},[34,1593,1594],{"class":76},"'text\u002Fplain'",[34,1596,478],{"class":69},[34,1598,1599],{"class":65},"??",[34,1601,1602],{"class":76}," ''\n",[34,1604,1605,1608,1611,1613,1616],{"class":36,"line":113},[34,1606,1607],{"class":69},"  console.",[34,1609,1610],{"class":93},"log",[34,1612,97],{"class":69},[34,1614,1615],{"class":76},"'dropped:'",[34,1617,1618],{"class":69},", data)\n",[34,1620,1621],{"class":36,"line":119},[34,1622,197],{"class":69},[34,1624,1625],{"class":36,"line":125},[34,1626,84],{"emptyLinePlaceholder":83},[34,1628,1629,1631,1634,1636,1638],{"class":36,"line":131},[34,1630,90],{"class":65},[34,1632,1633],{"class":93}," onKeyNav",[34,1635,97],{"class":69},[34,1637,1566],{"class":100},[34,1639,104],{"class":69},[34,1641,1642],{"class":36,"line":137},[34,1643,1644],{"class":40},"  \u002F\u002F .enter filters to Enter key only — handler never fires for other keys\n",[34,1646,1647,1649,1651,1653,1656],{"class":36,"line":154},[34,1648,1607],{"class":69},[34,1650,1610],{"class":93},[34,1652,97],{"class":69},[34,1654,1655],{"class":76},"'Enter pressed, value:'",[34,1657,1658],{"class":69},", e.target.value)\n",[34,1660,1661],{"class":36,"line":163},[34,1662,197],{"class":69},[34,1664,1665,1667,1669],{"class":36,"line":172},[34,1666,627],{"class":69},[34,1668,255],{"class":254},[34,1670,270],{"class":69},[34,1672,1673],{"class":36,"line":178},[34,1674,84],{"emptyLinePlaceholder":83},[34,1676,1677,1679,1681],{"class":36,"line":194},[34,1678,251],{"class":69},[34,1680,644],{"class":254},[34,1682,270],{"class":69},[34,1684,1685],{"class":36,"line":200},[34,1686,1687],{"class":40},"  \u003C!-- Modifier chain: left→right execution order\n",[34,1689,1690],{"class":36,"line":205},[34,1691,1692],{"class":40},"       .stop   → event.stopPropagation()\n",[34,1694,1695],{"class":36,"line":211},[34,1696,1697],{"class":40},"       .prevent → event.preventDefault()\n",[34,1699,1700],{"class":36,"line":217},[34,1701,1702],{"class":40},"       .self   → only fire if event.target === currentTarget\n",[34,1704,1705],{"class":36,"line":223},[34,1706,1707],{"class":40},"       .once   → remove listener after first call -->\n",[34,1709,1710,1712,1714,1717,1719,1722,1725,1727],{"class":36,"line":229},[34,1711,700],{"class":69},[34,1713,1014],{"class":254},[34,1715,1716],{"class":93}," @click.stop.prevent.once",[34,1718,264],{"class":69},[34,1720,1721],{"class":76},"\"onSave\"",[34,1723,1724],{"class":69},">Save (once, no bubble, no default)\u003C\u002F",[34,1726,1014],{"class":254},[34,1728,270],{"class":69},[34,1730,1731],{"class":36,"line":511},[34,1732,84],{"emptyLinePlaceholder":83},[34,1734,1735],{"class":36,"line":517},[34,1736,1737],{"class":40},"  \u003C!-- .self: ignores clicks from child elements (e.g., icon inside button) -->\n",[34,1739,1740,1742,1745,1748,1750,1753,1756,1758,1761],{"class":36,"line":552},[34,1741,700],{"class":69},[34,1743,1744],{"class":254},"div",[34,1746,1747],{"class":93}," @click.self",[34,1749,264],{"class":69},[34,1751,1752],{"class":76},"\"onBackdropClick\"",[34,1754,1755],{"class":93}," class",[34,1757,264],{"class":69},[34,1759,1760],{"class":76},"\"modal-backdrop\"",[34,1762,270],{"class":69},[34,1764,1765,1767,1769,1771,1773,1776,1779,1781],{"class":36,"line":573},[34,1766,727],{"class":69},[34,1768,1744],{"class":254},[34,1770,1755],{"class":93},[34,1772,264],{"class":69},[34,1774,1775],{"class":76},"\"modal\"",[34,1777,1778],{"class":69},">...\u003C\u002F",[34,1780,1744],{"class":254},[34,1782,270],{"class":69},[34,1784,1785,1787,1789],{"class":36,"line":613},[34,1786,771],{"class":69},[34,1788,1744],{"class":254},[34,1790,270],{"class":69},[34,1792,1793],{"class":36,"line":619},[34,1794,84],{"emptyLinePlaceholder":83},[34,1796,1797],{"class":36,"line":624},[34,1798,1799],{"class":40},"  \u003C!-- .exact: ONLY fire when no modifier keys held -->\n",[34,1801,1802,1804,1806,1809,1811,1814,1817,1819],{"class":36,"line":634},[34,1803,700],{"class":69},[34,1805,1014],{"class":254},[34,1807,1808],{"class":93}," @click.exact",[34,1810,264],{"class":69},[34,1812,1813],{"class":76},"\"onSimpleClick\"",[34,1815,1816],{"class":69},">Plain click only\u003C\u002F",[34,1818,1014],{"class":254},[34,1820,270],{"class":69},[34,1822,1823,1825,1827,1830,1832,1835,1838,1840],{"class":36,"line":639},[34,1824,700],{"class":69},[34,1826,1014],{"class":254},[34,1828,1829],{"class":93}," @click.ctrl.exact",[34,1831,264],{"class":69},[34,1833,1834],{"class":76},"\"onCtrlClick\"",[34,1836,1837],{"class":69},">Ctrl+click only\u003C\u002F",[34,1839,1014],{"class":254},[34,1841,270],{"class":69},[34,1843,1844],{"class":36,"line":649},[34,1845,84],{"emptyLinePlaceholder":83},[34,1847,1848],{"class":36,"line":655},[34,1849,1850],{"class":40},"  \u003C!-- Key modifiers: .enter, .tab, .delete, .esc, .space, .up, .down, etc. -->\n",[34,1852,1853,1855,1857,1860,1862,1865,1868,1870,1873],{"class":36,"line":661},[34,1854,700],{"class":69},[34,1856,975],{"class":254},[34,1858,1859],{"class":93}," @keydown.enter",[34,1861,264],{"class":69},[34,1863,1864],{"class":76},"\"onKeyNav\"",[34,1866,1867],{"class":93}," @keydown.esc",[34,1869,264],{"class":69},[34,1871,1872],{"class":76},"\"onEscape\"",[34,1874,994],{"class":69},[34,1876,1877],{"class":36,"line":667},[34,1878,84],{"emptyLinePlaceholder":83},[34,1880,1881],{"class":36,"line":673},[34,1882,1883],{"class":40},"  \u003C!-- System modifiers: .ctrl, .alt, .shift, .meta (Cmd on Mac, Win on Windows) -->\n",[34,1885,1886,1888,1890,1893,1895,1898],{"class":36,"line":679},[34,1887,700],{"class":69},[34,1889,975],{"class":254},[34,1891,1892],{"class":93}," @keydown.meta.enter",[34,1894,264],{"class":69},[34,1896,1897],{"class":76},"\"onCmdEnter\"",[34,1899,994],{"class":69},[34,1901,1902],{"class":36,"line":685},[34,1903,84],{"emptyLinePlaceholder":83},[34,1905,1906],{"class":36,"line":691},[34,1907,1908],{"class":40},"  \u003C!-- .passive: tells browser \"I won't call preventDefault\" → enables scroll optimization -->\n",[34,1910,1911],{"class":36,"line":697},[34,1912,1913],{"class":40},"  \u003C!-- Don't combine .passive with .prevent — browser warns, .prevent is ignored -->\n",[34,1915,1916,1918,1920,1923,1925,1928,1931,1933],{"class":36,"line":724},[34,1917,700],{"class":69},[34,1919,1744],{"class":254},[34,1921,1922],{"class":93}," @scroll.passive",[34,1924,264],{"class":69},[34,1926,1927],{"class":76},"\"onScroll\"",[34,1929,1930],{"class":69},">Scrollable\u003C\u002F",[34,1932,1744],{"class":254},[34,1934,270],{"class":69},[34,1936,1937],{"class":36,"line":740},[34,1938,84],{"emptyLinePlaceholder":83},[34,1940,1941],{"class":36,"line":754},[34,1942,1943],{"class":40},"  \u003C!-- Mouse button modifiers: .left, .middle, .right -->\n",[34,1945,1946,1948,1950,1953,1955,1958,1961,1963,1966,1969,1971],{"class":36,"line":768},[34,1947,700],{"class":69},[34,1949,1744],{"class":254},[34,1951,1952],{"class":93}," @contextmenu.prevent",[34,1954,264],{"class":69},[34,1956,1957],{"class":76},"\"onRightClick\"",[34,1959,1960],{"class":93}," @mousedown.middle",[34,1962,264],{"class":69},[34,1964,1965],{"class":76},"\"onMiddleClick\"",[34,1967,1968],{"class":69},">Drag me\u003C\u002F",[34,1970,1744],{"class":254},[34,1972,270],{"class":69},[34,1974,1975,1977,1979],{"class":36,"line":778},[34,1976,627],{"class":69},[34,1978,644],{"class":254},[34,1980,270],{"class":69},[15,1982,1984],{"id":1983},"class-style-binding-merging-strategies","Class & Style Binding — Merging Strategies",[20,1986,1988],{"filename":1987,"language":240},"ClassBinding.vue",[25,1989,1991],{"className":243,"code":1990,"language":240,"meta":29,"style":29},"\u003Cscript setup>\nimport { ref, computed } from 'vue'\n\nconst isActive = ref(true)\nconst error = ref(null)\nconst size = ref('md')\n\n\u002F\u002F ── Computed class object — cleaner than complex template ternaries ──\nconst classes = computed(() => ({\n  btn: true,\n  [`btn-${size.value}`]: true,      \u002F\u002F dynamic class name via computed key\n  'btn-active': isActive.value,\n  'btn-error': !!error.value,\n  'btn-disabled': !isActive.value,\n}))\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003C!-- Static class + dynamic :class MERGE automatically (no clobbering) -->\n  \u003Cbutton class=\"rounded shadow\" :class=\"classes\">Click\u003C\u002Fbutton>\n\n  \u003C!-- Array syntax: multiple sources, arrays, objects can be mixed -->\n  \u003Cdiv :class=\"[\n    'card',                          \u002F\u002F static string\n    { 'card-elevated': isActive },   \u002F\u002F conditional object\n    size === 'lg' ? 'card-lg' : '',  \u002F\u002F ternary\n  ]\">Content\u003C\u002Fdiv>\n\n  \u003C!-- Style binding: camelCase keys, auto-vendored, auto-merged -->\n  \u003Cdiv :style=\"{\n    color: error ? 'red' : 'inherit',\n    \u002F\u002F Vue auto-adds vendor prefixes via @vue\u002Fruntime-dom\n    \u002F\u002F Multi-value: uses last supported value in the list\n    display: ['flex', '-webkit-flex'],\n  }\">Styled\u003C\u002Fdiv>\n\n  \u003C!-- Multiple style objects merged (later wins conflicts) -->\n  \u003Cdiv :style=\"[baseStyles, dynamicStyles]\">Merged\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[31,1992,1993,2003,2014,2018,2035,2053,2071,2075,2080,2098,2108,2139,2147,2161,2174,2179,2187,2191,2199,2204,2232,2236,2241,2254,2262,2267,2272,2284,2288,2293,2307,2312,2317,2322,2327,2339,2343,2348,2368],{"__ignoreMap":29},[34,1994,1995,1997,1999,2001],{"class":36,"line":37},[34,1996,251],{"class":69},[34,1998,255],{"class":254},[34,2000,258],{"class":93},[34,2002,270],{"class":69},[34,2004,2005,2007,2010,2012],{"class":36,"line":44},[34,2006,66],{"class":65},[34,2008,2009],{"class":69}," { ref, computed } ",[34,2011,73],{"class":65},[34,2013,77],{"class":76},[34,2015,2016],{"class":36,"line":50},[34,2017,84],{"emptyLinePlaceholder":83},[34,2019,2020,2022,2025,2027,2029,2031,2033],{"class":36,"line":56},[34,2021,371],{"class":65},[34,2023,2024],{"class":184}," isActive",[34,2026,377],{"class":65},[34,2028,380],{"class":93},[34,2030,97],{"class":69},[34,2032,875],{"class":184},[34,2034,191],{"class":69},[34,2036,2037,2039,2042,2044,2046,2048,2051],{"class":36,"line":62},[34,2038,371],{"class":65},[34,2040,2041],{"class":184}," error",[34,2043,377],{"class":65},[34,2045,380],{"class":93},[34,2047,97],{"class":69},[34,2049,2050],{"class":184},"null",[34,2052,191],{"class":69},[34,2054,2055,2057,2060,2062,2064,2066,2069],{"class":36,"line":80},[34,2056,371],{"class":65},[34,2058,2059],{"class":184}," size",[34,2061,377],{"class":65},[34,2063,380],{"class":93},[34,2065,97],{"class":69},[34,2067,2068],{"class":76},"'md'",[34,2070,191],{"class":69},[34,2072,2073],{"class":36,"line":87},[34,2074,84],{"emptyLinePlaceholder":83},[34,2076,2077],{"class":36,"line":107},[34,2078,2079],{"class":40},"\u002F\u002F ── Computed class object — cleaner than complex template ternaries ──\n",[34,2081,2082,2084,2087,2089,2091,2093,2095],{"class":36,"line":113},[34,2083,371],{"class":65},[34,2085,2086],{"class":184}," classes",[34,2088,377],{"class":65},[34,2090,419],{"class":93},[34,2092,422],{"class":69},[34,2094,481],{"class":65},[34,2096,2097],{"class":69}," ({\n",[34,2099,2100,2103,2105],{"class":36,"line":119},[34,2101,2102],{"class":69},"  btn: ",[34,2104,875],{"class":184},[34,2106,2107],{"class":69},",\n",[34,2109,2110,2113,2116,2119,2122,2125,2128,2131,2133,2136],{"class":36,"line":125},[34,2111,2112],{"class":69},"  [",[34,2114,2115],{"class":76},"`btn-${",[34,2117,2118],{"class":69},"size",[34,2120,2121],{"class":76},".",[34,2123,2124],{"class":69},"value",[34,2126,2127],{"class":76},"}`",[34,2129,2130],{"class":69},"]: ",[34,2132,875],{"class":184},[34,2134,2135],{"class":69},",      ",[34,2137,2138],{"class":40},"\u002F\u002F dynamic class name via computed key\n",[34,2140,2141,2144],{"class":36,"line":131},[34,2142,2143],{"class":76},"  'btn-active'",[34,2145,2146],{"class":69},": isActive.value,\n",[34,2148,2149,2152,2155,2158],{"class":36,"line":137},[34,2150,2151],{"class":76},"  'btn-error'",[34,2153,2154],{"class":69},": ",[34,2156,2157],{"class":65},"!!",[34,2159,2160],{"class":69},"error.value,\n",[34,2162,2163,2166,2168,2171],{"class":36,"line":154},[34,2164,2165],{"class":76},"  'btn-disabled'",[34,2167,2154],{"class":69},[34,2169,2170],{"class":65},"!",[34,2172,2173],{"class":69},"isActive.value,\n",[34,2175,2176],{"class":36,"line":163},[34,2177,2178],{"class":69},"}))\n",[34,2180,2181,2183,2185],{"class":36,"line":172},[34,2182,627],{"class":69},[34,2184,255],{"class":254},[34,2186,270],{"class":69},[34,2188,2189],{"class":36,"line":178},[34,2190,84],{"emptyLinePlaceholder":83},[34,2192,2193,2195,2197],{"class":36,"line":194},[34,2194,251],{"class":69},[34,2196,644],{"class":254},[34,2198,270],{"class":69},[34,2200,2201],{"class":36,"line":200},[34,2202,2203],{"class":40},"  \u003C!-- Static class + dynamic :class MERGE automatically (no clobbering) -->\n",[34,2205,2206,2208,2210,2212,2214,2217,2220,2222,2225,2228,2230],{"class":36,"line":205},[34,2207,700],{"class":69},[34,2209,1014],{"class":254},[34,2211,1755],{"class":93},[34,2213,264],{"class":69},[34,2215,2216],{"class":76},"\"rounded shadow\"",[34,2218,2219],{"class":93}," :class",[34,2221,264],{"class":69},[34,2223,2224],{"class":76},"\"classes\"",[34,2226,2227],{"class":69},">Click\u003C\u002F",[34,2229,1014],{"class":254},[34,2231,270],{"class":69},[34,2233,2234],{"class":36,"line":211},[34,2235,84],{"emptyLinePlaceholder":83},[34,2237,2238],{"class":36,"line":217},[34,2239,2240],{"class":40},"  \u003C!-- Array syntax: multiple sources, arrays, objects can be mixed -->\n",[34,2242,2243,2245,2247,2249,2251],{"class":36,"line":223},[34,2244,700],{"class":69},[34,2246,1744],{"class":254},[34,2248,2219],{"class":93},[34,2250,264],{"class":69},[34,2252,2253],{"class":76},"\"[\n",[34,2255,2256,2259],{"class":36,"line":229},[34,2257,2258],{"class":76},"    'card',",[34,2260,2261],{"class":76},"                          \u002F\u002F static string\n",[34,2263,2264],{"class":36,"line":511},[34,2265,2266],{"class":76},"    { 'card-elevated': isActive },   \u002F\u002F conditional object\n",[34,2268,2269],{"class":36,"line":517},[34,2270,2271],{"class":76},"    size === 'lg' ? 'card-lg' : '',  \u002F\u002F ternary\n",[34,2273,2274,2277,2280,2282],{"class":36,"line":552},[34,2275,2276],{"class":76},"  ]\"",[34,2278,2279],{"class":69},">Content\u003C\u002F",[34,2281,1744],{"class":254},[34,2283,270],{"class":69},[34,2285,2286],{"class":36,"line":573},[34,2287,84],{"emptyLinePlaceholder":83},[34,2289,2290],{"class":36,"line":613},[34,2291,2292],{"class":40},"  \u003C!-- Style binding: camelCase keys, auto-vendored, auto-merged -->\n",[34,2294,2295,2297,2299,2302,2304],{"class":36,"line":619},[34,2296,700],{"class":69},[34,2298,1744],{"class":254},[34,2300,2301],{"class":93}," :style",[34,2303,264],{"class":69},[34,2305,2306],{"class":76},"\"{\n",[34,2308,2309],{"class":36,"line":624},[34,2310,2311],{"class":76},"    color: error ? 'red' : 'inherit',\n",[34,2313,2314],{"class":36,"line":634},[34,2315,2316],{"class":76},"    \u002F\u002F Vue auto-adds vendor prefixes via @vue\u002Fruntime-dom\n",[34,2318,2319],{"class":36,"line":639},[34,2320,2321],{"class":76},"    \u002F\u002F Multi-value: uses last supported value in the list\n",[34,2323,2324],{"class":36,"line":649},[34,2325,2326],{"class":76},"    display: ['flex', '-webkit-flex'],\n",[34,2328,2329,2332,2335,2337],{"class":36,"line":655},[34,2330,2331],{"class":76},"  }\"",[34,2333,2334],{"class":69},">Styled\u003C\u002F",[34,2336,1744],{"class":254},[34,2338,270],{"class":69},[34,2340,2341],{"class":36,"line":661},[34,2342,84],{"emptyLinePlaceholder":83},[34,2344,2345],{"class":36,"line":667},[34,2346,2347],{"class":40},"  \u003C!-- Multiple style objects merged (later wins conflicts) -->\n",[34,2349,2350,2352,2354,2356,2358,2361,2364,2366],{"class":36,"line":673},[34,2351,700],{"class":69},[34,2353,1744],{"class":254},[34,2355,2301],{"class":93},[34,2357,264],{"class":69},[34,2359,2360],{"class":76},"\"[baseStyles, dynamicStyles]\"",[34,2362,2363],{"class":69},">Merged\u003C\u002F",[34,2365,1744],{"class":254},[34,2367,270],{"class":69},[34,2369,2370,2372,2374],{"class":36,"line":679},[34,2371,627],{"class":69},[34,2373,644],{"class":254},[34,2375,270],{"class":69},[15,2377,2379],{"id":2378},"v-model-two-way-binding-expansion","v-model — Two-Way Binding Expansion",[20,2381,2383],{"filename":2382,"language":240},"VModelInternals.vue",[25,2384,2386],{"className":243,"code":2385,"language":240,"meta":29,"style":29},"\u003Cscript setup>\nimport { ref, vModelText, vModelCheckbox, vModelSelect } from 'vue'\n\nconst text = ref('')\nconst checked = ref(false)\nconst multi = ref([])\nconst selected = ref('a')\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003C!-- v-model on \u003Cinput type=\"text\"> expands to: -->\n  \u003C!-- :value=\"text\" @input=\"text = $event.target.value\" -->\n  \u003C!-- BUT Vue uses a directive (vModelText) not just attribute binding:\n       it handles composition events (IME), edge cases with type=number, etc. -->\n  \u003Cinput v-model=\"text\" \u002F>\n\n  \u003C!-- .lazy: listen to 'change' (on blur) instead of 'input' (every keystroke) -->\n  \u003C!-- .number: cast to Number (empty string → '' not 0; invalid → original string) -->\n  \u003C!-- .trim: strip whitespace from both ends -->\n  \u003Cinput v-model.lazy.number.trim=\"text\" type=\"number\" \u002F>\n\n  \u003C!-- Checkbox: single → boolean; multiple → array of checked values -->\n  \u003Cinput v-model=\"checked\" type=\"checkbox\" \u002F>\n  \u003Cinput v-model=\"multi\" type=\"checkbox\" value=\"a\" \u002F>\n  \u003Cinput v-model=\"multi\" type=\"checkbox\" value=\"b\" \u002F>\n\n  \u003C!-- Select: binds to selected \u003Coption>'s value attribute -->\n  \u003C!-- If no value attr, uses the option's text content -->\n  \u003Cselect v-model=\"selected\">\n    \u003Coption value=\"a\">Option A\u003C\u002Foption>\n    \u003Coption value=\"b\">Option B\u003C\u002Foption>\n  \u003C\u002Fselect>\n\u003C\u002Ftemplate>\n",[31,2387,2388,2398,2409,2413,2431,2448,2462,2480,2488,2492,2500,2505,2510,2515,2520,2535,2539,2544,2549,2554,2576,2580,2585,2606,2635,2662,2666,2671,2676,2692,2712,2731,2739],{"__ignoreMap":29},[34,2389,2390,2392,2394,2396],{"class":36,"line":37},[34,2391,251],{"class":69},[34,2393,255],{"class":254},[34,2395,258],{"class":93},[34,2397,270],{"class":69},[34,2399,2400,2402,2405,2407],{"class":36,"line":44},[34,2401,66],{"class":65},[34,2403,2404],{"class":69}," { ref, vModelText, vModelCheckbox, vModelSelect } ",[34,2406,73],{"class":65},[34,2408,77],{"class":76},[34,2410,2411],{"class":36,"line":50},[34,2412,84],{"emptyLinePlaceholder":83},[34,2414,2415,2417,2420,2422,2424,2426,2429],{"class":36,"line":56},[34,2416,371],{"class":65},[34,2418,2419],{"class":184}," text",[34,2421,377],{"class":65},[34,2423,380],{"class":93},[34,2425,97],{"class":69},[34,2427,2428],{"class":76},"''",[34,2430,191],{"class":69},[34,2432,2433,2435,2438,2440,2442,2444,2446],{"class":36,"line":62},[34,2434,371],{"class":65},[34,2436,2437],{"class":184}," checked",[34,2439,377],{"class":65},[34,2441,380],{"class":93},[34,2443,97],{"class":69},[34,2445,856],{"class":184},[34,2447,191],{"class":69},[34,2449,2450,2452,2455,2457,2459],{"class":36,"line":80},[34,2451,371],{"class":65},[34,2453,2454],{"class":184}," multi",[34,2456,377],{"class":65},[34,2458,380],{"class":93},[34,2460,2461],{"class":69},"([])\n",[34,2463,2464,2466,2469,2471,2473,2475,2478],{"class":36,"line":87},[34,2465,371],{"class":65},[34,2467,2468],{"class":184}," selected",[34,2470,377],{"class":65},[34,2472,380],{"class":93},[34,2474,97],{"class":69},[34,2476,2477],{"class":76},"'a'",[34,2479,191],{"class":69},[34,2481,2482,2484,2486],{"class":36,"line":107},[34,2483,627],{"class":69},[34,2485,255],{"class":254},[34,2487,270],{"class":69},[34,2489,2490],{"class":36,"line":113},[34,2491,84],{"emptyLinePlaceholder":83},[34,2493,2494,2496,2498],{"class":36,"line":119},[34,2495,251],{"class":69},[34,2497,644],{"class":254},[34,2499,270],{"class":69},[34,2501,2502],{"class":36,"line":125},[34,2503,2504],{"class":40},"  \u003C!-- v-model on \u003Cinput type=\"text\"> expands to: -->\n",[34,2506,2507],{"class":36,"line":131},[34,2508,2509],{"class":40},"  \u003C!-- :value=\"text\" @input=\"text = $event.target.value\" -->\n",[34,2511,2512],{"class":36,"line":137},[34,2513,2514],{"class":40},"  \u003C!-- BUT Vue uses a directive (vModelText) not just attribute binding:\n",[34,2516,2517],{"class":36,"line":154},[34,2518,2519],{"class":40},"       it handles composition events (IME), edge cases with type=number, etc. -->\n",[34,2521,2522,2524,2526,2528,2530,2533],{"class":36,"line":163},[34,2523,700],{"class":69},[34,2525,975],{"class":254},[34,2527,986],{"class":93},[34,2529,264],{"class":69},[34,2531,2532],{"class":76},"\"text\"",[34,2534,994],{"class":69},[34,2536,2537],{"class":36,"line":172},[34,2538,84],{"emptyLinePlaceholder":83},[34,2540,2541],{"class":36,"line":178},[34,2542,2543],{"class":40},"  \u003C!-- .lazy: listen to 'change' (on blur) instead of 'input' (every keystroke) -->\n",[34,2545,2546],{"class":36,"line":194},[34,2547,2548],{"class":40},"  \u003C!-- .number: cast to Number (empty string → '' not 0; invalid → original string) -->\n",[34,2550,2551],{"class":36,"line":200},[34,2552,2553],{"class":40},"  \u003C!-- .trim: strip whitespace from both ends -->\n",[34,2555,2556,2558,2560,2563,2565,2567,2569,2571,2574],{"class":36,"line":205},[34,2557,700],{"class":69},[34,2559,975],{"class":254},[34,2561,2562],{"class":93}," v-model.lazy.number.trim",[34,2564,264],{"class":69},[34,2566,2532],{"class":76},[34,2568,978],{"class":93},[34,2570,264],{"class":69},[34,2572,2573],{"class":76},"\"number\"",[34,2575,994],{"class":69},[34,2577,2578],{"class":36,"line":211},[34,2579,84],{"emptyLinePlaceholder":83},[34,2581,2582],{"class":36,"line":217},[34,2583,2584],{"class":40},"  \u003C!-- Checkbox: single → boolean; multiple → array of checked values -->\n",[34,2586,2587,2589,2591,2593,2595,2598,2600,2602,2604],{"class":36,"line":223},[34,2588,700],{"class":69},[34,2590,975],{"class":254},[34,2592,986],{"class":93},[34,2594,264],{"class":69},[34,2596,2597],{"class":76},"\"checked\"",[34,2599,978],{"class":93},[34,2601,264],{"class":69},[34,2603,983],{"class":76},[34,2605,994],{"class":69},[34,2607,2608,2610,2612,2614,2616,2619,2621,2623,2625,2628,2630,2633],{"class":36,"line":229},[34,2609,700],{"class":69},[34,2611,975],{"class":254},[34,2613,986],{"class":93},[34,2615,264],{"class":69},[34,2617,2618],{"class":76},"\"multi\"",[34,2620,978],{"class":93},[34,2622,264],{"class":69},[34,2624,983],{"class":76},[34,2626,2627],{"class":93}," value",[34,2629,264],{"class":69},[34,2631,2632],{"class":76},"\"a\"",[34,2634,994],{"class":69},[34,2636,2637,2639,2641,2643,2645,2647,2649,2651,2653,2655,2657,2660],{"class":36,"line":511},[34,2638,700],{"class":69},[34,2640,975],{"class":254},[34,2642,986],{"class":93},[34,2644,264],{"class":69},[34,2646,2618],{"class":76},[34,2648,978],{"class":93},[34,2650,264],{"class":69},[34,2652,983],{"class":76},[34,2654,2627],{"class":93},[34,2656,264],{"class":69},[34,2658,2659],{"class":76},"\"b\"",[34,2661,994],{"class":69},[34,2663,2664],{"class":36,"line":517},[34,2665,84],{"emptyLinePlaceholder":83},[34,2667,2668],{"class":36,"line":552},[34,2669,2670],{"class":40},"  \u003C!-- Select: binds to selected \u003Coption>'s value attribute -->\n",[34,2672,2673],{"class":36,"line":573},[34,2674,2675],{"class":40},"  \u003C!-- If no value attr, uses the option's text content -->\n",[34,2677,2678,2680,2683,2685,2687,2690],{"class":36,"line":613},[34,2679,700],{"class":69},[34,2681,2682],{"class":254},"select",[34,2684,986],{"class":93},[34,2686,264],{"class":69},[34,2688,2689],{"class":76},"\"selected\"",[34,2691,270],{"class":69},[34,2693,2694,2696,2699,2701,2703,2705,2708,2710],{"class":36,"line":619},[34,2695,727],{"class":69},[34,2697,2698],{"class":254},"option",[34,2700,2627],{"class":93},[34,2702,264],{"class":69},[34,2704,2632],{"class":76},[34,2706,2707],{"class":69},">Option A\u003C\u002F",[34,2709,2698],{"class":254},[34,2711,270],{"class":69},[34,2713,2714,2716,2718,2720,2722,2724,2727,2729],{"class":36,"line":624},[34,2715,727],{"class":69},[34,2717,2698],{"class":254},[34,2719,2627],{"class":93},[34,2721,264],{"class":69},[34,2723,2659],{"class":76},[34,2725,2726],{"class":69},">Option B\u003C\u002F",[34,2728,2698],{"class":254},[34,2730,270],{"class":69},[34,2732,2733,2735,2737],{"class":36,"line":634},[34,2734,771],{"class":69},[34,2736,2682],{"class":254},[34,2738,270],{"class":69},[34,2740,2741,2743,2745],{"class":36,"line":639},[34,2742,627],{"class":69},[34,2744,644],{"class":254},[34,2746,270],{"class":69},[15,2748,2750],{"id":2749},"v-for-with-v-if-the-vue-3-precedence-trap","v-for with v-if — The Vue 3 Precedence Trap",[20,2752,2754],{"filename":2753,"language":240},"VForVIfTrap.vue",[25,2755,2757],{"className":243,"code":2756,"language":240,"meta":29,"style":29},"\u003Cscript setup>\nimport { ref, computed } from 'vue'\nconst todos = ref([\n  { id: 1, text: 'A', done: true },\n  { id: 2, text: 'B', done: false },\n])\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003C!-- ❌ WRONG in Vue 3: v-if has HIGHER precedence than v-for\n       → v-if evaluates FIRST, before the loop variable exists\n       → `todo` is undefined → ReferenceError or silent skip -->\n  \u003Cli v-for=\"todo in todos\" v-if=\"!todo.done\" :key=\"todo.id\">{{ todo.text }}\u003C\u002Fli>\n\u003C\u002Ftemplate>\n",[31,2758,2759,2769,2779,2792,2810,2827,2831,2839,2843,2851,2856,2861,2866,2900],{"__ignoreMap":29},[34,2760,2761,2763,2765,2767],{"class":36,"line":37},[34,2762,251],{"class":69},[34,2764,255],{"class":254},[34,2766,258],{"class":93},[34,2768,270],{"class":69},[34,2770,2771,2773,2775,2777],{"class":36,"line":44},[34,2772,66],{"class":65},[34,2774,2009],{"class":69},[34,2776,73],{"class":65},[34,2778,77],{"class":76},[34,2780,2781,2783,2786,2788,2790],{"class":36,"line":50},[34,2782,371],{"class":65},[34,2784,2785],{"class":184}," todos",[34,2787,377],{"class":65},[34,2789,380],{"class":93},[34,2791,836],{"class":69},[34,2793,2794,2796,2798,2800,2803,2806,2808],{"class":36,"line":56},[34,2795,841],{"class":69},[34,2797,844],{"class":184},[34,2799,847],{"class":69},[34,2801,2802],{"class":76},"'A'",[34,2804,2805],{"class":69},", done: ",[34,2807,875],{"class":184},[34,2809,859],{"class":69},[34,2811,2812,2814,2816,2818,2821,2823,2825],{"class":36,"line":62},[34,2813,841],{"class":69},[34,2815,185],{"class":184},[34,2817,847],{"class":69},[34,2819,2820],{"class":76},"'B'",[34,2822,2805],{"class":69},[34,2824,856],{"class":184},[34,2826,859],{"class":69},[34,2828,2829],{"class":36,"line":80},[34,2830,900],{"class":69},[34,2832,2833,2835,2837],{"class":36,"line":87},[34,2834,627],{"class":69},[34,2836,255],{"class":254},[34,2838,270],{"class":69},[34,2840,2841],{"class":36,"line":107},[34,2842,84],{"emptyLinePlaceholder":83},[34,2844,2845,2847,2849],{"class":36,"line":113},[34,2846,251],{"class":69},[34,2848,644],{"class":254},[34,2850,270],{"class":69},[34,2852,2853],{"class":36,"line":119},[34,2854,2855],{"class":40},"  \u003C!-- ❌ WRONG in Vue 3: v-if has HIGHER precedence than v-for\n",[34,2857,2858],{"class":36,"line":125},[34,2859,2860],{"class":40},"       → v-if evaluates FIRST, before the loop variable exists\n",[34,2862,2863],{"class":36,"line":131},[34,2864,2865],{"class":40},"       → `todo` is undefined → ReferenceError or silent skip -->\n",[34,2867,2868,2870,2872,2874,2876,2879,2881,2883,2886,2888,2890,2893,2896,2898],{"class":36,"line":137},[34,2869,700],{"class":69},[34,2871,952],{"class":254},[34,2873,706],{"class":93},[34,2875,264],{"class":69},[34,2877,2878],{"class":76},"\"todo in todos\"",[34,2880,1396],{"class":93},[34,2882,264],{"class":69},[34,2884,2885],{"class":76},"\"!todo.done\"",[34,2887,714],{"class":93},[34,2889,264],{"class":69},[34,2891,2892],{"class":76},"\"todo.id\"",[34,2894,2895],{"class":69},">{{ todo.text }}\u003C\u002F",[34,2897,952],{"class":254},[34,2899,270],{"class":69},[34,2901,2902,2904,2906],{"class":36,"line":154},[34,2903,627],{"class":69},[34,2905,644],{"class":254},[34,2907,270],{"class":69},[20,2909,2911],{"filename":2910,"language":240},"VForVIfFixed.vue",[25,2912,2914],{"className":243,"code":2913,"language":240,"meta":29,"style":29},"\u003Cscript setup>\nimport { computed } from 'vue'\nconst todos = ref([\n  { id: 1, text: 'A', done: true },\n  { id: 2, text: 'B', done: false },\n])\n\n\u002F\u002F ✅ Option 1: filter via computed — re-evaluates only when todos changes\nconst incomplete = computed(() => todos.value.filter(t => !t.done))\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cli v-for=\"todo in incomplete\" :key=\"todo.id\">{{ todo.text }}\u003C\u002Fli>\n\n  \u003C!-- ✅ Option 2: \u003Ctemplate v-for> wraps, inner v-if filters per item -->\n  \u003Ctemplate v-for=\"todo in todos\" :key=\"todo.id\">\n    \u003Cli v-if=\"!todo.done\">{{ todo.text }}\u003C\u002Fli>\n  \u003C\u002Ftemplate>\n\u003C\u002Ftemplate>\n",[31,2915,2916,2926,2937,2949,2965,2981,2985,2989,2994,3027,3035,3039,3047,3072,3076,3081,3120,3138,3146],{"__ignoreMap":29},[34,2917,2918,2920,2922,2924],{"class":36,"line":37},[34,2919,251],{"class":69},[34,2921,255],{"class":254},[34,2923,258],{"class":93},[34,2925,270],{"class":69},[34,2927,2928,2930,2933,2935],{"class":36,"line":44},[34,2929,66],{"class":65},[34,2931,2932],{"class":69}," { computed } ",[34,2934,73],{"class":65},[34,2936,77],{"class":76},[34,2938,2939,2941,2943,2945,2947],{"class":36,"line":50},[34,2940,371],{"class":65},[34,2942,2785],{"class":184},[34,2944,377],{"class":65},[34,2946,380],{"class":93},[34,2948,836],{"class":69},[34,2950,2951,2953,2955,2957,2959,2961,2963],{"class":36,"line":56},[34,2952,841],{"class":69},[34,2954,844],{"class":184},[34,2956,847],{"class":69},[34,2958,2802],{"class":76},[34,2960,2805],{"class":69},[34,2962,875],{"class":184},[34,2964,859],{"class":69},[34,2966,2967,2969,2971,2973,2975,2977,2979],{"class":36,"line":62},[34,2968,841],{"class":69},[34,2970,185],{"class":184},[34,2972,847],{"class":69},[34,2974,2820],{"class":76},[34,2976,2805],{"class":69},[34,2978,856],{"class":184},[34,2980,859],{"class":69},[34,2982,2983],{"class":36,"line":80},[34,2984,900],{"class":69},[34,2986,2987],{"class":36,"line":87},[34,2988,84],{"emptyLinePlaceholder":83},[34,2990,2991],{"class":36,"line":107},[34,2992,2993],{"class":40},"\u002F\u002F ✅ Option 1: filter via computed — re-evaluates only when todos changes\n",[34,2995,2996,2998,3001,3003,3005,3007,3009,3012,3014,3016,3019,3021,3024],{"class":36,"line":113},[34,2997,371],{"class":65},[34,2999,3000],{"class":184}," incomplete",[34,3002,377],{"class":65},[34,3004,419],{"class":93},[34,3006,422],{"class":69},[34,3008,481],{"class":65},[34,3010,3011],{"class":69}," todos.value.",[34,3013,438],{"class":93},[34,3015,97],{"class":69},[34,3017,3018],{"class":100},"t",[34,3020,446],{"class":65},[34,3022,3023],{"class":65}," !",[34,3025,3026],{"class":69},"t.done))\n",[34,3028,3029,3031,3033],{"class":36,"line":119},[34,3030,627],{"class":69},[34,3032,255],{"class":254},[34,3034,270],{"class":69},[34,3036,3037],{"class":36,"line":125},[34,3038,84],{"emptyLinePlaceholder":83},[34,3040,3041,3043,3045],{"class":36,"line":131},[34,3042,251],{"class":69},[34,3044,644],{"class":254},[34,3046,270],{"class":69},[34,3048,3049,3051,3053,3055,3057,3060,3062,3064,3066,3068,3070],{"class":36,"line":137},[34,3050,700],{"class":69},[34,3052,952],{"class":254},[34,3054,706],{"class":93},[34,3056,264],{"class":69},[34,3058,3059],{"class":76},"\"todo in incomplete\"",[34,3061,714],{"class":93},[34,3063,264],{"class":69},[34,3065,2892],{"class":76},[34,3067,2895],{"class":69},[34,3069,952],{"class":254},[34,3071,270],{"class":69},[34,3073,3074],{"class":36,"line":154},[34,3075,84],{"emptyLinePlaceholder":83},[34,3077,3078],{"class":36,"line":163},[34,3079,3080],{"class":40},"  \u003C!-- ✅ Option 2: \u003Ctemplate v-for> wraps, inner v-if filters per item -->\n",[34,3082,3083,3085,3087,3089,3091,3093,3096,3099,3101,3103,3106,3109,3111,3113,3116,3118],{"class":36,"line":172},[34,3084,700],{"class":69},[34,3086,644],{"class":254},[34,3088,706],{"class":65},[34,3090,264],{"class":69},[34,3092,1401],{"class":76},[34,3094,3095],{"class":69},"todo ",[34,3097,3098],{"class":65},"in",[34,3100,2785],{"class":69},[34,3102,1401],{"class":76},[34,3104,3105],{"class":69}," :",[34,3107,3108],{"class":93},"key",[34,3110,264],{"class":69},[34,3112,1401],{"class":76},[34,3114,3115],{"class":69},"todo.id",[34,3117,1401],{"class":76},[34,3119,270],{"class":69},[34,3121,3122,3124,3126,3128,3130,3132,3134,3136],{"class":36,"line":178},[34,3123,727],{"class":69},[34,3125,952],{"class":254},[34,3127,1396],{"class":93},[34,3129,264],{"class":69},[34,3131,2885],{"class":76},[34,3133,2895],{"class":69},[34,3135,952],{"class":254},[34,3137,270],{"class":69},[34,3139,3140,3142,3144],{"class":36,"line":194},[34,3141,771],{"class":69},[34,3143,644],{"class":254},[34,3145,270],{"class":69},[34,3147,3148,3150,3152],{"class":36,"line":200},[34,3149,627],{"class":69},[34,3151,644],{"class":254},[34,3153,270],{"class":69},[15,3155,3157],{"id":3156},"tips-tricks","💡 Tips & Tricks",[20,3159,3161],{"filename":3160,"language":240},"TipsTricks.vue",[25,3162,3164],{"className":243,"code":3163,"language":240,"meta":29,"style":29},"\u003Cscript setup>\nimport { ref } from 'vue'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003C!-- ── 1. v-bind spread — forward a bag of attrs to a native element ── -->\n  \u003Cinput v-bind=\"{ type: 'email', placeholder: 'Email', required: true, autocomplete: 'email' }\" \u002F>\n\n  \u003C!-- ── 2. v-once — render once, skip forever (static content optimization) ── -->\n  \u003Cheader v-once>{{ heavyMarkdownRenderedOnce }}\u003C\u002Fheader>\n\n  \u003C!-- ── 3. v-memo — skip re-render unless deps change (for expensive list items) ── -->\n  \u003Cdiv v-memo=\"[item.id, item.status]\" v-for=\"item in hugeList\" :key=\"item.id\">\n    {{ item.name }}\n  \u003C\u002Fdiv>\n\n  \u003C!-- ── 4. v-for range starts at 1, not 0 ── -->\n  \u003Coption v-for=\"n in 12\" :key=\"n\" :value=\"n\">{{ n }}\u003C\u002Foption>\n\n  \u003C!-- ── 5. Dynamic event name — useful for programmatically bound events ── -->\n  \u003Cbutton @[eventName]=\"handler\">Dynamic event\u003C\u002Fbutton>\n\n  \u003C!-- ── 6. Dynamic argument — any expression that evaluates to a string ── -->\n  \u003Ca :[attrName]=\"url\">Dynamic attribute\u003C\u002Fa>\n\u003C\u002Ftemplate>\n",[31,3165,3166,3176,3186,3194,3198,3206,3211,3227,3231,3236,3253,3257,3262,3291,3296,3304,3308,3313,3347,3351,3356,3377,3381,3386,3407],{"__ignoreMap":29},[34,3167,3168,3170,3172,3174],{"class":36,"line":37},[34,3169,251],{"class":69},[34,3171,255],{"class":254},[34,3173,258],{"class":93},[34,3175,270],{"class":69},[34,3177,3178,3180,3182,3184],{"class":36,"line":44},[34,3179,66],{"class":65},[34,3181,814],{"class":69},[34,3183,73],{"class":65},[34,3185,77],{"class":76},[34,3187,3188,3190,3192],{"class":36,"line":50},[34,3189,627],{"class":69},[34,3191,255],{"class":254},[34,3193,270],{"class":69},[34,3195,3196],{"class":36,"line":56},[34,3197,84],{"emptyLinePlaceholder":83},[34,3199,3200,3202,3204],{"class":36,"line":62},[34,3201,251],{"class":69},[34,3203,644],{"class":254},[34,3205,270],{"class":69},[34,3207,3208],{"class":36,"line":80},[34,3209,3210],{"class":40},"  \u003C!-- ── 1. v-bind spread — forward a bag of attrs to a native element ── -->\n",[34,3212,3213,3215,3217,3220,3222,3225],{"class":36,"line":87},[34,3214,700],{"class":69},[34,3216,975],{"class":254},[34,3218,3219],{"class":93}," v-bind",[34,3221,264],{"class":69},[34,3223,3224],{"class":76},"\"{ type: 'email', placeholder: 'Email', required: true, autocomplete: 'email' }\"",[34,3226,994],{"class":69},[34,3228,3229],{"class":36,"line":107},[34,3230,84],{"emptyLinePlaceholder":83},[34,3232,3233],{"class":36,"line":113},[34,3234,3235],{"class":40},"  \u003C!-- ── 2. v-once — render once, skip forever (static content optimization) ── -->\n",[34,3237,3238,3240,3243,3246,3249,3251],{"class":36,"line":119},[34,3239,700],{"class":69},[34,3241,3242],{"class":254},"header",[34,3244,3245],{"class":93}," v-once",[34,3247,3248],{"class":69},">{{ heavyMarkdownRenderedOnce }}\u003C\u002F",[34,3250,3242],{"class":254},[34,3252,270],{"class":69},[34,3254,3255],{"class":36,"line":125},[34,3256,84],{"emptyLinePlaceholder":83},[34,3258,3259],{"class":36,"line":131},[34,3260,3261],{"class":40},"  \u003C!-- ── 3. v-memo — skip re-render unless deps change (for expensive list items) ── -->\n",[34,3263,3264,3266,3268,3271,3273,3276,3278,3280,3283,3285,3287,3289],{"class":36,"line":137},[34,3265,700],{"class":69},[34,3267,1744],{"class":254},[34,3269,3270],{"class":93}," v-memo",[34,3272,264],{"class":69},[34,3274,3275],{"class":76},"\"[item.id, item.status]\"",[34,3277,706],{"class":93},[34,3279,264],{"class":69},[34,3281,3282],{"class":76},"\"item in hugeList\"",[34,3284,714],{"class":93},[34,3286,264],{"class":69},[34,3288,1112],{"class":76},[34,3290,270],{"class":69},[34,3292,3293],{"class":36,"line":154},[34,3294,3295],{"class":69},"    {{ item.name }}\n",[34,3297,3298,3300,3302],{"class":36,"line":163},[34,3299,771],{"class":69},[34,3301,1744],{"class":254},[34,3303,270],{"class":69},[34,3305,3306],{"class":36,"line":172},[34,3307,84],{"emptyLinePlaceholder":83},[34,3309,3310],{"class":36,"line":178},[34,3311,3312],{"class":40},"  \u003C!-- ── 4. v-for range starts at 1, not 0 ── -->\n",[34,3314,3315,3317,3319,3321,3323,3326,3328,3330,3333,3336,3338,3340,3343,3345],{"class":36,"line":194},[34,3316,700],{"class":69},[34,3318,2698],{"class":254},[34,3320,706],{"class":93},[34,3322,264],{"class":69},[34,3324,3325],{"class":76},"\"n in 12\"",[34,3327,714],{"class":93},[34,3329,264],{"class":69},[34,3331,3332],{"class":76},"\"n\"",[34,3334,3335],{"class":93}," :value",[34,3337,264],{"class":69},[34,3339,3332],{"class":76},[34,3341,3342],{"class":69},">{{ n }}\u003C\u002F",[34,3344,2698],{"class":254},[34,3346,270],{"class":69},[34,3348,3349],{"class":36,"line":200},[34,3350,84],{"emptyLinePlaceholder":83},[34,3352,3353],{"class":36,"line":205},[34,3354,3355],{"class":40},"  \u003C!-- ── 5. Dynamic event name — useful for programmatically bound events ── -->\n",[34,3357,3358,3360,3362,3365,3367,3370,3373,3375],{"class":36,"line":211},[34,3359,700],{"class":69},[34,3361,1014],{"class":254},[34,3363,3364],{"class":93}," @[eventName]",[34,3366,264],{"class":69},[34,3368,3369],{"class":76},"\"handler\"",[34,3371,3372],{"class":69},">Dynamic event\u003C\u002F",[34,3374,1014],{"class":254},[34,3376,270],{"class":69},[34,3378,3379],{"class":36,"line":217},[34,3380,84],{"emptyLinePlaceholder":83},[34,3382,3383],{"class":36,"line":223},[34,3384,3385],{"class":40},"  \u003C!-- ── 6. Dynamic argument — any expression that evaluates to a string ── -->\n",[34,3387,3388,3390,3392,3395,3397,3400,3403,3405],{"class":36,"line":229},[34,3389,700],{"class":69},[34,3391,469],{"class":254},[34,3393,3394],{"class":93}," :[attrName]",[34,3396,264],{"class":69},[34,3398,3399],{"class":76},"\"url\"",[34,3401,3402],{"class":69},">Dynamic attribute\u003C\u002F",[34,3404,469],{"class":254},[34,3406,270],{"class":69},[34,3408,3409,3411,3413],{"class":36,"line":511},[34,3410,627],{"class":69},[34,3412,644],{"class":254},[34,3414,270],{"class":69},[15,3416,3418],{"id":3417},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[20,3420,3422],{"filename":3421,"language":23},"edge-cases.ts",[25,3423,3425],{"className":27,"code":3424,"language":23,"meta":29,"style":29},"\u002F\u002F ── 1. Boolean attribute string \"false\" is still truthy in HTML ──\n\u002F\u002F :disabled=\"false\" → attribute removed ✅\n\u002F\u002F disabled=\"false\"   → attribute present (string \"false\" is truthy in HTML) ❌\n\u002F\u002F HTML spec: presence of boolean attr = true, regardless of value.\n\n\u002F\u002F ── 2. v-html bypasses Vue's XSS escaping ──\n\u002F\u002F {{ userComment }} → escaped (safe)\n\u002F\u002F \u003Cp v-html=\"userComment\" \u002F> → raw HTML injected (XSS if unsanitized)\n\u002F\u002F Always sanitize with DOMPurify before v-html on user content.\n\n\u002F\u002F ── 3. Object key iteration order is NOT alphabetical ──\n\u002F\u002F v-for=\"(v, k) in obj\" follows JS engine for-in order:\n\u002F\u002F   1. Integer-index keys (ascending numeric)\n\u002F\u002F   2. String keys (insertion order)\n\u002F\u002F   3. Symbol keys (insertion order)\n\u002F\u002F { b: 1, a: 2, 2: 3, 1: 4 } → iterates: 1, 2, b, a (NOT a, b, 1, 2)\n\n\u002F\u002F ── 4. v-model on component requires modelValue prop + update:modelValue emit ──\n\u002F\u002F Vue 3 renamed value→modelValue and input→update:modelValue (breaking change from Vue 2)\n\u002F\u002F Multiple v-models: v-model:firstName + v-model:lastName → two model props\n\n\u002F\u002F ── 5. Template expressions are sandboxed — limited global access ──\n\u002F\u002F {{ window.location }} → undefined (window not in sandbox whitelist)\n\u002F\u002F Only: Math, Date, parseInt, JSON, undefined, etc.\n\u002F\u002F Access component methods via: {{ myMethod() }}\n",[31,3426,3427,3432,3437,3442,3447,3451,3456,3461,3466,3471,3475,3480,3485,3490,3495,3500,3505,3509,3514,3519,3524,3528,3533,3538,3543],{"__ignoreMap":29},[34,3428,3429],{"class":36,"line":37},[34,3430,3431],{"class":40},"\u002F\u002F ── 1. Boolean attribute string \"false\" is still truthy in HTML ──\n",[34,3433,3434],{"class":36,"line":44},[34,3435,3436],{"class":40},"\u002F\u002F :disabled=\"false\" → attribute removed ✅\n",[34,3438,3439],{"class":36,"line":50},[34,3440,3441],{"class":40},"\u002F\u002F disabled=\"false\"   → attribute present (string \"false\" is truthy in HTML) ❌\n",[34,3443,3444],{"class":36,"line":56},[34,3445,3446],{"class":40},"\u002F\u002F HTML spec: presence of boolean attr = true, regardless of value.\n",[34,3448,3449],{"class":36,"line":62},[34,3450,84],{"emptyLinePlaceholder":83},[34,3452,3453],{"class":36,"line":80},[34,3454,3455],{"class":40},"\u002F\u002F ── 2. v-html bypasses Vue's XSS escaping ──\n",[34,3457,3458],{"class":36,"line":87},[34,3459,3460],{"class":40},"\u002F\u002F {{ userComment }} → escaped (safe)\n",[34,3462,3463],{"class":36,"line":107},[34,3464,3465],{"class":40},"\u002F\u002F \u003Cp v-html=\"userComment\" \u002F> → raw HTML injected (XSS if unsanitized)\n",[34,3467,3468],{"class":36,"line":113},[34,3469,3470],{"class":40},"\u002F\u002F Always sanitize with DOMPurify before v-html on user content.\n",[34,3472,3473],{"class":36,"line":119},[34,3474,84],{"emptyLinePlaceholder":83},[34,3476,3477],{"class":36,"line":125},[34,3478,3479],{"class":40},"\u002F\u002F ── 3. Object key iteration order is NOT alphabetical ──\n",[34,3481,3482],{"class":36,"line":131},[34,3483,3484],{"class":40},"\u002F\u002F v-for=\"(v, k) in obj\" follows JS engine for-in order:\n",[34,3486,3487],{"class":36,"line":137},[34,3488,3489],{"class":40},"\u002F\u002F   1. Integer-index keys (ascending numeric)\n",[34,3491,3492],{"class":36,"line":154},[34,3493,3494],{"class":40},"\u002F\u002F   2. String keys (insertion order)\n",[34,3496,3497],{"class":36,"line":163},[34,3498,3499],{"class":40},"\u002F\u002F   3. Symbol keys (insertion order)\n",[34,3501,3502],{"class":36,"line":172},[34,3503,3504],{"class":40},"\u002F\u002F { b: 1, a: 2, 2: 3, 1: 4 } → iterates: 1, 2, b, a (NOT a, b, 1, 2)\n",[34,3506,3507],{"class":36,"line":178},[34,3508,84],{"emptyLinePlaceholder":83},[34,3510,3511],{"class":36,"line":194},[34,3512,3513],{"class":40},"\u002F\u002F ── 4. v-model on component requires modelValue prop + update:modelValue emit ──\n",[34,3515,3516],{"class":36,"line":200},[34,3517,3518],{"class":40},"\u002F\u002F Vue 3 renamed value→modelValue and input→update:modelValue (breaking change from Vue 2)\n",[34,3520,3521],{"class":36,"line":205},[34,3522,3523],{"class":40},"\u002F\u002F Multiple v-models: v-model:firstName + v-model:lastName → two model props\n",[34,3525,3526],{"class":36,"line":211},[34,3527,84],{"emptyLinePlaceholder":83},[34,3529,3530],{"class":36,"line":217},[34,3531,3532],{"class":40},"\u002F\u002F ── 5. Template expressions are sandboxed — limited global access ──\n",[34,3534,3535],{"class":36,"line":223},[34,3536,3537],{"class":40},"\u002F\u002F {{ window.location }} → undefined (window not in sandbox whitelist)\n",[34,3539,3540],{"class":36,"line":229},[34,3541,3542],{"class":40},"\u002F\u002F Only: Math, Date, parseInt, JSON, undefined, etc.\n",[34,3544,3545],{"class":36,"line":511},[34,3546,3547],{"class":40},"\u002F\u002F Access component methods via: {{ myMethod() }}\n",[15,3549,3551],{"id":3550},"spot-the-bug","🧠 Spot the Bug",[3553,3554,3555],"p",{},"After removing the second cart item, the \"On Sale\" badge appears on the wrong product.",[20,3557,3559],{"filename":3558,"language":240},"CartBug.vue",[25,3560,3562],{"className":243,"code":3561,"language":240,"meta":29,"style":29},"\u003Cscript setup>\nimport { ref } from 'vue'\nconst cart = ref([\n  { id: 101, name: 'Keyboard', onSale: false },\n  { id: 102, name: 'Mouse', onSale: true },\n  { id: 103, name: 'Monitor', onSale: false },\n])\nfunction remove(i) { cart.value.splice(i, 1) }\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv v-for=\"(item, i) in cart\" :key=\"i\">\n    {{ item.name }}\n    \u003Cspan v-if=\"item.onSale\">On Sale!\u003C\u002Fspan>\n    \u003Cbutton @click=\"remove(i)\">Remove\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[31,3563,3564,3574,3584,3597,3617,3635,3653,3657,3683,3691,3695,3703,3724,3728,3748,3768,3776],{"__ignoreMap":29},[34,3565,3566,3568,3570,3572],{"class":36,"line":37},[34,3567,251],{"class":69},[34,3569,255],{"class":254},[34,3571,258],{"class":93},[34,3573,270],{"class":69},[34,3575,3576,3578,3580,3582],{"class":36,"line":44},[34,3577,66],{"class":65},[34,3579,814],{"class":69},[34,3581,73],{"class":65},[34,3583,77],{"class":76},[34,3585,3586,3588,3591,3593,3595],{"class":36,"line":50},[34,3587,371],{"class":65},[34,3589,3590],{"class":184}," cart",[34,3592,377],{"class":65},[34,3594,380],{"class":93},[34,3596,836],{"class":69},[34,3598,3599,3601,3604,3607,3610,3613,3615],{"class":36,"line":56},[34,3600,841],{"class":69},[34,3602,3603],{"class":184},"101",[34,3605,3606],{"class":69},", name: ",[34,3608,3609],{"class":76},"'Keyboard'",[34,3611,3612],{"class":69},", onSale: ",[34,3614,856],{"class":184},[34,3616,859],{"class":69},[34,3618,3619,3621,3624,3626,3629,3631,3633],{"class":36,"line":62},[34,3620,841],{"class":69},[34,3622,3623],{"class":184},"102",[34,3625,3606],{"class":69},[34,3627,3628],{"class":76},"'Mouse'",[34,3630,3612],{"class":69},[34,3632,875],{"class":184},[34,3634,859],{"class":69},[34,3636,3637,3639,3642,3644,3647,3649,3651],{"class":36,"line":80},[34,3638,841],{"class":69},[34,3640,3641],{"class":184},"103",[34,3643,3606],{"class":69},[34,3645,3646],{"class":76},"'Monitor'",[34,3648,3612],{"class":69},[34,3650,856],{"class":184},[34,3652,859],{"class":69},[34,3654,3655],{"class":36,"line":87},[34,3656,900],{"class":69},[34,3658,3659,3661,3664,3666,3669,3672,3675,3678,3680],{"class":36,"line":107},[34,3660,90],{"class":65},[34,3662,3663],{"class":93}," remove",[34,3665,97],{"class":69},[34,3667,3668],{"class":100},"i",[34,3670,3671],{"class":69},") { cart.value.",[34,3673,3674],{"class":93},"splice",[34,3676,3677],{"class":69},"(i, ",[34,3679,844],{"class":184},[34,3681,3682],{"class":69},") }\n",[34,3684,3685,3687,3689],{"class":36,"line":113},[34,3686,627],{"class":69},[34,3688,255],{"class":254},[34,3690,270],{"class":69},[34,3692,3693],{"class":36,"line":119},[34,3694,84],{"emptyLinePlaceholder":83},[34,3696,3697,3699,3701],{"class":36,"line":125},[34,3698,251],{"class":69},[34,3700,644],{"class":254},[34,3702,270],{"class":69},[34,3704,3705,3707,3709,3711,3713,3716,3718,3720,3722],{"class":36,"line":131},[34,3706,700],{"class":69},[34,3708,1744],{"class":254},[34,3710,706],{"class":93},[34,3712,264],{"class":69},[34,3714,3715],{"class":76},"\"(item, i) in cart\"",[34,3717,714],{"class":93},[34,3719,264],{"class":69},[34,3721,966],{"class":76},[34,3723,270],{"class":69},[34,3725,3726],{"class":36,"line":137},[34,3727,3295],{"class":69},[34,3729,3730,3732,3734,3736,3738,3741,3744,3746],{"class":36,"line":154},[34,3731,727],{"class":69},[34,3733,34],{"class":254},[34,3735,1396],{"class":93},[34,3737,264],{"class":69},[34,3739,3740],{"class":76},"\"item.onSale\"",[34,3742,3743],{"class":69},">On Sale!\u003C\u002F",[34,3745,34],{"class":254},[34,3747,270],{"class":69},[34,3749,3750,3752,3754,3756,3758,3761,3764,3766],{"class":36,"line":163},[34,3751,727],{"class":69},[34,3753,1014],{"class":254},[34,3755,1017],{"class":93},[34,3757,264],{"class":69},[34,3759,3760],{"class":76},"\"remove(i)\"",[34,3762,3763],{"class":69},">Remove\u003C\u002F",[34,3765,1014],{"class":254},[34,3767,270],{"class":69},[34,3769,3770,3772,3774],{"class":36,"line":172},[34,3771,771],{"class":69},[34,3773,1744],{"class":254},[34,3775,270],{"class":69},[34,3777,3778,3780,3782],{"class":36,"line":178},[34,3779,627],{"class":69},[34,3781,644],{"class":254},[34,3783,270],{"class":69},[3785,3786,3787,3791,3808,3811,3818,3912],"details",{},[3788,3789,3790],"summary",{},"Answer",[3553,3792,3793,3796,3797,3799,3800,3803,3804,3807],{},[31,3794,3795],{},":key=\"i\""," keys by position. When \"Mouse\" (index 1) is removed, \"Monitor\" shifts to index 1. Vue sees the vnode keyed ",[31,3798,844],{}," still exists, patches it in place with \"Monitor\" data — but the ",[31,3801,3802],{},"v-if=\"item.onSale\""," re-evaluates against the NEW data (Monitor, ",[31,3805,3806],{},"onSale: false","), so the badge disappears. Meanwhile the vnode at index 0 (\"Keyboard\") was unkeyed-correct but if there were transient render states (CSS transitions, focus), they'd be attached to the wrong product.",[3553,3809,3810],{},"The root cause: index keys make Vue treat positions as identity, not the actual data items.",[3553,3812,3813,3817],{},[3814,3815,3816],"strong",{},"Fix"," — key by stable id:",[20,3819,3821],{"filename":3820,"language":240},"CartFixed.vue",[25,3822,3824],{"className":243,"code":3823,"language":240,"meta":29,"style":29},"\u003Ctemplate>\n  \u003Cdiv v-for=\"item in cart\" :key=\"item.id\">\n    {{ item.name }}\n    \u003Cspan v-if=\"item.onSale\">On Sale!\u003C\u002Fspan>\n    \u003Cbutton @click=\"cart.splice(cart.indexOf(item), 1)\">Remove\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[31,3825,3826,3834,3855,3859,3877,3896,3904],{"__ignoreMap":29},[34,3827,3828,3830,3832],{"class":36,"line":37},[34,3829,251],{"class":69},[34,3831,644],{"class":254},[34,3833,270],{"class":69},[34,3835,3836,3838,3840,3842,3844,3847,3849,3851,3853],{"class":36,"line":44},[34,3837,700],{"class":69},[34,3839,1744],{"class":254},[34,3841,706],{"class":93},[34,3843,264],{"class":69},[34,3845,3846],{"class":76},"\"item in cart\"",[34,3848,714],{"class":93},[34,3850,264],{"class":69},[34,3852,1112],{"class":76},[34,3854,270],{"class":69},[34,3856,3857],{"class":36,"line":50},[34,3858,3295],{"class":69},[34,3860,3861,3863,3865,3867,3869,3871,3873,3875],{"class":36,"line":56},[34,3862,727],{"class":69},[34,3864,34],{"class":254},[34,3866,1396],{"class":93},[34,3868,264],{"class":69},[34,3870,3740],{"class":76},[34,3872,3743],{"class":69},[34,3874,34],{"class":254},[34,3876,270],{"class":69},[34,3878,3879,3881,3883,3885,3887,3890,3892,3894],{"class":36,"line":62},[34,3880,727],{"class":69},[34,3882,1014],{"class":254},[34,3884,1017],{"class":93},[34,3886,264],{"class":69},[34,3888,3889],{"class":76},"\"cart.splice(cart.indexOf(item), 1)\"",[34,3891,3763],{"class":69},[34,3893,1014],{"class":254},[34,3895,270],{"class":69},[34,3897,3898,3900,3902],{"class":36,"line":80},[34,3899,771],{"class":69},[34,3901,1744],{"class":254},[34,3903,270],{"class":69},[34,3905,3906,3908,3910],{"class":36,"line":87},[34,3907,627],{"class":69},[34,3909,644],{"class":254},[34,3911,270],{"class":69},[3553,3913,3914,2154,3917,3920,3921,3925,3926,3929],{},[3814,3915,3916],{},"The lesson",[31,3918,3919],{},":key"," must identify ",[3922,3923,3924],"em",{},"what the data represents",", never ",[3922,3927,3928],{},"where it currently sits in the array",". Index keys are only safe for lists that are never reordered, filtered, or spliced.",[3931,3932,3933],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}",{"title":29,"searchDepth":44,"depth":44,"links":3935},[3936,3937,3940,3941,3942,3943,3944,3945,3946,3947],{"id":17,"depth":44,"text":18},{"id":235,"depth":44,"text":236,"children":3938},[3939],{"id":788,"depth":50,"text":789},{"id":1175,"depth":44,"text":1176},{"id":1499,"depth":44,"text":1500},{"id":1983,"depth":44,"text":1984},{"id":2378,"depth":44,"text":2379},{"id":2749,"depth":44,"text":2750},{"id":3156,"depth":44,"text":3157},{"id":3417,"depth":44,"text":3418},{"id":3550,"depth":44,"text":3551},"Production-grade template compilation — vnode diffing with keyed lists, compiler-informed patch flags, modifier chains, v-model expansion, attribute fallthrough, and the v-if\u002Fv-for precedence trap.","md",{},"\u002Fvue\u002F02-template-syntax-and-directives",{"title":5,"description":3948},"vue\u002F02-template-syntax-and-directives","zcaZ-_dUCwIFba6qRDIwfecdCyc7P2qtfXBzIYCQ8e0",1789924655358]