[{"data":1,"prerenderedAt":2522},["ShallowReactive",2],{"page-\u002Fvue\u002F15-custom-directives":3},{"id":4,"title":5,"body":6,"description":2515,"extension":2516,"meta":2517,"navigation":69,"path":2518,"seo":2519,"stem":2520,"__hash__":2521},"content\u002Fvue\u002F15-custom-directives.md","Vue 3 Engineering Reference — Custom Directives",{"type":7,"value":8,"toc":2506},"minimark",[9,14,19,472,476,948,952,1558,1562,1787,1791,1890,1894,2022,2026,2030,2194,2502],[10,11,13],"h1",{"id":12},"_15-custom-directives","15 — Custom Directives",[15,16,18],"h2",{"id":17},"directive-hook-lifecycle","Directive Hook Lifecycle",[20,21,24],"code-wrapper",{"filename":22,"language":23},"directive-hooks.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","import { type Directive, type DirectiveBinding } from 'vue'\n\n\u002F\u002F ── Directive hooks fire in parallel with component lifecycle ──\n\u002F\u002F The directive object can implement any subset of these hooks.\n\nconst myDirective: Directive = {\n  \u002F\u002F ── created: before the element's attributes\u002Fevent listeners are applied ──\n  \u002F\u002F Useful for setting up something that must exist before Vue patches attrs.\n  created(el, binding, vnode, prevVnode) {\n    \u002F\u002F el: the DOM element (always a real element, even for components)\n    \u002F\u002F binding.value: the value passed to v-directive=\"value\"\n    \u002F\u002F binding.oldValue: previous value (only in beforeUpdate\u002Fupdated)\n    \u002F\u002F binding.arg: the argument (v-directive:foo → \"foo\")\n    \u002F\u002F binding.modifiers: object of modifiers (v-directive.bar → { bar: true })\n    \u002F\u002F binding.instance: the component instance using the directive\n    \u002F\u002F vnode: the virtual node of the element\n  },\n\n  \u002F\u002F ── beforeMount: before the element is inserted into the DOM ──\n  \u002F\u002F Element exists in memory but not in document yet. Can't measure size.\n  beforeMount(el, binding) {},\n\n  \u002F\u002F ── mounted: element is in the DOM ──\n  \u002F\u002F Safe to measure (getBoundingClientRect), attach event listeners, init libraries.\n  mounted(el, binding) {},\n\n  \u002F\u002F ── beforeUpdate: before the element's VNode updates ──\n  \u002F\u002F binding.oldValue is available here (the value before the update).\n  beforeUpdate(el, binding, vnode, oldVnode) {},\n\n  \u002F\u002F ── updated: after the element's VNode (and children) have updated ──\n  updated(el, binding) {},\n\n  \u002F\u002F ── beforeUnmount: before the element is removed from DOM ──\n  \u002F\u002F Element is still in the DOM — cleanup that needs the element can run here.\n  beforeUnmount(el) {},\n\n  \u002F\u002F ── unmounted: element removed from DOM ──\n  \u002F\u002F Final cleanup — remove event listeners, destroy library instances.\n  unmounted(el) {},\n}\n\n\u002F\u002F ── Shorthand: if you only need mounted + updated, pass a function ──\n\u002F\u002F This function runs in both mounted and updated hooks.\nconst vFocus: Directive = (el, binding) => {\n  if (binding.value) el.focus()\n}\n","",[31,32,33,64,71,78,84,89,112,118,124,156,162,168,174,180,186,192,198,204,209,215,221,238,243,249,255,271,276,282,288,313,318,324,340,345,351,357,369,374,380,386,398,404,409,415,421,452,467],"code",{"__ignoreMap":29},[34,35,38,42,46,49,52,54,57,60],"span",{"class":36,"line":37},"line",1,[34,39,41],{"class":40},"svdQ7","import",[34,43,45],{"class":44},"ssxIu"," { ",[34,47,48],{"class":40},"type",[34,50,51],{"class":44}," Directive, ",[34,53,48],{"class":40},[34,55,56],{"class":44}," DirectiveBinding } ",[34,58,59],{"class":40},"from",[34,61,63],{"class":62},"sJ6F3"," 'vue'\n",[34,65,67],{"class":36,"line":66},2,[34,68,70],{"emptyLinePlaceholder":69},true,"\n",[34,72,74],{"class":36,"line":73},3,[34,75,77],{"class":76},"sdCPZ","\u002F\u002F ── Directive hooks fire in parallel with component lifecycle ──\n",[34,79,81],{"class":36,"line":80},4,[34,82,83],{"class":76},"\u002F\u002F The directive object can implement any subset of these hooks.\n",[34,85,87],{"class":36,"line":86},5,[34,88,70],{"emptyLinePlaceholder":69},[34,90,92,95,99,102,106,109],{"class":36,"line":91},6,[34,93,94],{"class":40},"const",[34,96,98],{"class":97},"snvgF"," myDirective",[34,100,101],{"class":40},":",[34,103,105],{"class":104},"sIsaT"," Directive",[34,107,108],{"class":40}," =",[34,110,111],{"class":44}," {\n",[34,113,115],{"class":36,"line":114},7,[34,116,117],{"class":76},"  \u002F\u002F ── created: before the element's attributes\u002Fevent listeners are applied ──\n",[34,119,121],{"class":36,"line":120},8,[34,122,123],{"class":76},"  \u002F\u002F Useful for setting up something that must exist before Vue patches attrs.\n",[34,125,127,130,133,137,140,143,145,148,150,153],{"class":36,"line":126},9,[34,128,129],{"class":104},"  created",[34,131,132],{"class":44},"(",[34,134,136],{"class":135},"sCrzJ","el",[34,138,139],{"class":44},", ",[34,141,142],{"class":135},"binding",[34,144,139],{"class":44},[34,146,147],{"class":135},"vnode",[34,149,139],{"class":44},[34,151,152],{"class":135},"prevVnode",[34,154,155],{"class":44},") {\n",[34,157,159],{"class":36,"line":158},10,[34,160,161],{"class":76},"    \u002F\u002F el: the DOM element (always a real element, even for components)\n",[34,163,165],{"class":36,"line":164},11,[34,166,167],{"class":76},"    \u002F\u002F binding.value: the value passed to v-directive=\"value\"\n",[34,169,171],{"class":36,"line":170},12,[34,172,173],{"class":76},"    \u002F\u002F binding.oldValue: previous value (only in beforeUpdate\u002Fupdated)\n",[34,175,177],{"class":36,"line":176},13,[34,178,179],{"class":76},"    \u002F\u002F binding.arg: the argument (v-directive:foo → \"foo\")\n",[34,181,183],{"class":36,"line":182},14,[34,184,185],{"class":76},"    \u002F\u002F binding.modifiers: object of modifiers (v-directive.bar → { bar: true })\n",[34,187,189],{"class":36,"line":188},15,[34,190,191],{"class":76},"    \u002F\u002F binding.instance: the component instance using the directive\n",[34,193,195],{"class":36,"line":194},16,[34,196,197],{"class":76},"    \u002F\u002F vnode: the virtual node of the element\n",[34,199,201],{"class":36,"line":200},17,[34,202,203],{"class":44},"  },\n",[34,205,207],{"class":36,"line":206},18,[34,208,70],{"emptyLinePlaceholder":69},[34,210,212],{"class":36,"line":211},19,[34,213,214],{"class":76},"  \u002F\u002F ── beforeMount: before the element is inserted into the DOM ──\n",[34,216,218],{"class":36,"line":217},20,[34,219,220],{"class":76},"  \u002F\u002F Element exists in memory but not in document yet. Can't measure size.\n",[34,222,224,227,229,231,233,235],{"class":36,"line":223},21,[34,225,226],{"class":104},"  beforeMount",[34,228,132],{"class":44},[34,230,136],{"class":135},[34,232,139],{"class":44},[34,234,142],{"class":135},[34,236,237],{"class":44},") {},\n",[34,239,241],{"class":36,"line":240},22,[34,242,70],{"emptyLinePlaceholder":69},[34,244,246],{"class":36,"line":245},23,[34,247,248],{"class":76},"  \u002F\u002F ── mounted: element is in the DOM ──\n",[34,250,252],{"class":36,"line":251},24,[34,253,254],{"class":76},"  \u002F\u002F Safe to measure (getBoundingClientRect), attach event listeners, init libraries.\n",[34,256,258,261,263,265,267,269],{"class":36,"line":257},25,[34,259,260],{"class":104},"  mounted",[34,262,132],{"class":44},[34,264,136],{"class":135},[34,266,139],{"class":44},[34,268,142],{"class":135},[34,270,237],{"class":44},[34,272,274],{"class":36,"line":273},26,[34,275,70],{"emptyLinePlaceholder":69},[34,277,279],{"class":36,"line":278},27,[34,280,281],{"class":76},"  \u002F\u002F ── beforeUpdate: before the element's VNode updates ──\n",[34,283,285],{"class":36,"line":284},28,[34,286,287],{"class":76},"  \u002F\u002F binding.oldValue is available here (the value before the update).\n",[34,289,291,294,296,298,300,302,304,306,308,311],{"class":36,"line":290},29,[34,292,293],{"class":104},"  beforeUpdate",[34,295,132],{"class":44},[34,297,136],{"class":135},[34,299,139],{"class":44},[34,301,142],{"class":135},[34,303,139],{"class":44},[34,305,147],{"class":135},[34,307,139],{"class":44},[34,309,310],{"class":135},"oldVnode",[34,312,237],{"class":44},[34,314,316],{"class":36,"line":315},30,[34,317,70],{"emptyLinePlaceholder":69},[34,319,321],{"class":36,"line":320},31,[34,322,323],{"class":76},"  \u002F\u002F ── updated: after the element's VNode (and children) have updated ──\n",[34,325,327,330,332,334,336,338],{"class":36,"line":326},32,[34,328,329],{"class":104},"  updated",[34,331,132],{"class":44},[34,333,136],{"class":135},[34,335,139],{"class":44},[34,337,142],{"class":135},[34,339,237],{"class":44},[34,341,343],{"class":36,"line":342},33,[34,344,70],{"emptyLinePlaceholder":69},[34,346,348],{"class":36,"line":347},34,[34,349,350],{"class":76},"  \u002F\u002F ── beforeUnmount: before the element is removed from DOM ──\n",[34,352,354],{"class":36,"line":353},35,[34,355,356],{"class":76},"  \u002F\u002F Element is still in the DOM — cleanup that needs the element can run here.\n",[34,358,360,363,365,367],{"class":36,"line":359},36,[34,361,362],{"class":104},"  beforeUnmount",[34,364,132],{"class":44},[34,366,136],{"class":135},[34,368,237],{"class":44},[34,370,372],{"class":36,"line":371},37,[34,373,70],{"emptyLinePlaceholder":69},[34,375,377],{"class":36,"line":376},38,[34,378,379],{"class":76},"  \u002F\u002F ── unmounted: element removed from DOM ──\n",[34,381,383],{"class":36,"line":382},39,[34,384,385],{"class":76},"  \u002F\u002F Final cleanup — remove event listeners, destroy library instances.\n",[34,387,389,392,394,396],{"class":36,"line":388},40,[34,390,391],{"class":104},"  unmounted",[34,393,132],{"class":44},[34,395,136],{"class":135},[34,397,237],{"class":44},[34,399,401],{"class":36,"line":400},41,[34,402,403],{"class":44},"}\n",[34,405,407],{"class":36,"line":406},42,[34,408,70],{"emptyLinePlaceholder":69},[34,410,412],{"class":36,"line":411},43,[34,413,414],{"class":76},"\u002F\u002F ── Shorthand: if you only need mounted + updated, pass a function ──\n",[34,416,418],{"class":36,"line":417},44,[34,419,420],{"class":76},"\u002F\u002F This function runs in both mounted and updated hooks.\n",[34,422,424,426,429,431,433,435,438,440,442,444,447,450],{"class":36,"line":423},45,[34,425,94],{"class":40},[34,427,428],{"class":104}," vFocus",[34,430,101],{"class":40},[34,432,105],{"class":104},[34,434,108],{"class":40},[34,436,437],{"class":44}," (",[34,439,136],{"class":135},[34,441,139],{"class":44},[34,443,142],{"class":135},[34,445,446],{"class":44},") ",[34,448,449],{"class":40},"=>",[34,451,111],{"class":44},[34,453,455,458,461,464],{"class":36,"line":454},46,[34,456,457],{"class":40},"  if",[34,459,460],{"class":44}," (binding.value) el.",[34,462,463],{"class":104},"focus",[34,465,466],{"class":44},"()\n",[34,468,470],{"class":36,"line":469},47,[34,471,403],{"class":44},[15,473,475],{"id":474},"production-directive-v-lazy-with-intersectionobserver","Production Directive — v-lazy with IntersectionObserver",[20,477,479],{"filename":478,"language":23},"vLazy.ts",[25,480,482],{"className":27,"code":481,"language":23,"meta":29,"style":29},"import { type Directive } from 'vue'\n\n\u002F\u002F ── v-lazy: lazy-load images when they scroll into view ──\n\u002F\u002F Usage: \u003Cimg v-lazy=\"imageUrl\" \u002F>\n\u002F\u002F Only sets src when the element intersects the viewport, reducing initial load.\n\nexport const vLazy: Directive\u003CHTMLImageElement, string> = {\n  mounted(el, binding) {\n    \u002F\u002F ── Store the target src, don't set it yet ──\n    el.dataset.src = binding.value\n\n    \u002F\u002F ── IntersectionObserver: fires callback when element enters viewport ──\n    const observer = new IntersectionObserver(\n      (entries) => {\n        entries.forEach((entry) => {\n          if (entry.isIntersecting) {\n            \u002F\u002F Element is visible — set the src to trigger the image load\n            el.src = el.dataset.src!\n            \u002F\u002F Stop observing once loaded (one-shot, not continuous)\n            observer.unobserve(el)\n          }\n        })\n      },\n      {\n        \u002F\u002F ── rootMargin: start loading 50px before element enters viewport ──\n        \u002F\u002F Reduces visible \"pop-in\" — image is loading before user sees the empty slot.\n        rootMargin: '50px',\n        threshold: 0.01,  \u002F\u002F trigger when 1% of element is visible\n      }\n    )\n\n    observer.observe(el)\n    \u002F\u002F ── Store observer for cleanup in unmounted ──\n    el._lazyObserver = observer\n  },\n\n  updated(el, binding) {\n    \u002F\u002F ── Handle dynamic src changes ──\n    if (binding.value !== binding.oldValue) {\n      el.dataset.src = binding.value\n      el.src = binding.value  \u002F\u002F already in view, update immediately\n    }\n  },\n\n  unmounted(el) {\n    \u002F\u002F ── Cleanup: disconnect observer to prevent memory leaks ──\n    el._lazyObserver?.disconnect()\n  },\n}\n\n\u002F\u002F ── Augment HTMLElement for TypeScript ──\ndeclare global {\n  interface HTMLElement {\n    _lazyObserver?: IntersectionObserver\n  }\n}\n",[31,483,484,499,503,508,513,518,522,556,570,575,585,589,594,613,627,647,655,660,673,678,689,694,699,704,709,714,719,730,744,749,754,758,768,773,783,787,791,805,810,824,833,846,851,855,859,869,874,884,889,894,899,905,914,925,937,943],{"__ignoreMap":29},[34,485,486,488,490,492,495,497],{"class":36,"line":37},[34,487,41],{"class":40},[34,489,45],{"class":44},[34,491,48],{"class":40},[34,493,494],{"class":44}," Directive } ",[34,496,59],{"class":40},[34,498,63],{"class":62},[34,500,501],{"class":36,"line":66},[34,502,70],{"emptyLinePlaceholder":69},[34,504,505],{"class":36,"line":73},[34,506,507],{"class":76},"\u002F\u002F ── v-lazy: lazy-load images when they scroll into view ──\n",[34,509,510],{"class":36,"line":80},[34,511,512],{"class":76},"\u002F\u002F Usage: \u003Cimg v-lazy=\"imageUrl\" \u002F>\n",[34,514,515],{"class":36,"line":86},[34,516,517],{"class":76},"\u002F\u002F Only sets src when the element intersects the viewport, reducing initial load.\n",[34,519,520],{"class":36,"line":91},[34,521,70],{"emptyLinePlaceholder":69},[34,523,524,527,530,533,535,537,540,543,545,548,551,554],{"class":36,"line":114},[34,525,526],{"class":40},"export",[34,528,529],{"class":40}," const",[34,531,532],{"class":97}," vLazy",[34,534,101],{"class":40},[34,536,105],{"class":104},[34,538,539],{"class":44},"\u003C",[34,541,542],{"class":104},"HTMLImageElement",[34,544,139],{"class":44},[34,546,547],{"class":97},"string",[34,549,550],{"class":44},"> ",[34,552,553],{"class":40},"=",[34,555,111],{"class":44},[34,557,558,560,562,564,566,568],{"class":36,"line":120},[34,559,260],{"class":104},[34,561,132],{"class":44},[34,563,136],{"class":135},[34,565,139],{"class":44},[34,567,142],{"class":135},[34,569,155],{"class":44},[34,571,572],{"class":36,"line":126},[34,573,574],{"class":76},"    \u002F\u002F ── Store the target src, don't set it yet ──\n",[34,576,577,580,582],{"class":36,"line":158},[34,578,579],{"class":44},"    el.dataset.src ",[34,581,553],{"class":40},[34,583,584],{"class":44}," binding.value\n",[34,586,587],{"class":36,"line":164},[34,588,70],{"emptyLinePlaceholder":69},[34,590,591],{"class":36,"line":170},[34,592,593],{"class":76},"    \u002F\u002F ── IntersectionObserver: fires callback when element enters viewport ──\n",[34,595,596,599,602,604,607,610],{"class":36,"line":176},[34,597,598],{"class":40},"    const",[34,600,601],{"class":97}," observer",[34,603,108],{"class":40},[34,605,606],{"class":40}," new",[34,608,609],{"class":104}," IntersectionObserver",[34,611,612],{"class":44},"(\n",[34,614,615,618,621,623,625],{"class":36,"line":182},[34,616,617],{"class":44},"      (",[34,619,620],{"class":135},"entries",[34,622,446],{"class":44},[34,624,449],{"class":40},[34,626,111],{"class":44},[34,628,629,632,635,638,641,643,645],{"class":36,"line":188},[34,630,631],{"class":44},"        entries.",[34,633,634],{"class":104},"forEach",[34,636,637],{"class":44},"((",[34,639,640],{"class":135},"entry",[34,642,446],{"class":44},[34,644,449],{"class":40},[34,646,111],{"class":44},[34,648,649,652],{"class":36,"line":194},[34,650,651],{"class":40},"          if",[34,653,654],{"class":44}," (entry.isIntersecting) {\n",[34,656,657],{"class":36,"line":200},[34,658,659],{"class":76},"            \u002F\u002F Element is visible — set the src to trigger the image load\n",[34,661,662,665,667,670],{"class":36,"line":206},[34,663,664],{"class":44},"            el.src ",[34,666,553],{"class":40},[34,668,669],{"class":44}," el.dataset.src",[34,671,672],{"class":40},"!\n",[34,674,675],{"class":36,"line":211},[34,676,677],{"class":76},"            \u002F\u002F Stop observing once loaded (one-shot, not continuous)\n",[34,679,680,683,686],{"class":36,"line":217},[34,681,682],{"class":44},"            observer.",[34,684,685],{"class":104},"unobserve",[34,687,688],{"class":44},"(el)\n",[34,690,691],{"class":36,"line":223},[34,692,693],{"class":44},"          }\n",[34,695,696],{"class":36,"line":240},[34,697,698],{"class":44},"        })\n",[34,700,701],{"class":36,"line":245},[34,702,703],{"class":44},"      },\n",[34,705,706],{"class":36,"line":251},[34,707,708],{"class":44},"      {\n",[34,710,711],{"class":36,"line":257},[34,712,713],{"class":76},"        \u002F\u002F ── rootMargin: start loading 50px before element enters viewport ──\n",[34,715,716],{"class":36,"line":273},[34,717,718],{"class":76},"        \u002F\u002F Reduces visible \"pop-in\" — image is loading before user sees the empty slot.\n",[34,720,721,724,727],{"class":36,"line":278},[34,722,723],{"class":44},"        rootMargin: ",[34,725,726],{"class":62},"'50px'",[34,728,729],{"class":44},",\n",[34,731,732,735,738,741],{"class":36,"line":284},[34,733,734],{"class":44},"        threshold: ",[34,736,737],{"class":97},"0.01",[34,739,740],{"class":44},",  ",[34,742,743],{"class":76},"\u002F\u002F trigger when 1% of element is visible\n",[34,745,746],{"class":36,"line":290},[34,747,748],{"class":44},"      }\n",[34,750,751],{"class":36,"line":315},[34,752,753],{"class":44},"    )\n",[34,755,756],{"class":36,"line":320},[34,757,70],{"emptyLinePlaceholder":69},[34,759,760,763,766],{"class":36,"line":326},[34,761,762],{"class":44},"    observer.",[34,764,765],{"class":104},"observe",[34,767,688],{"class":44},[34,769,770],{"class":36,"line":342},[34,771,772],{"class":76},"    \u002F\u002F ── Store observer for cleanup in unmounted ──\n",[34,774,775,778,780],{"class":36,"line":347},[34,776,777],{"class":44},"    el._lazyObserver ",[34,779,553],{"class":40},[34,781,782],{"class":44}," observer\n",[34,784,785],{"class":36,"line":353},[34,786,203],{"class":44},[34,788,789],{"class":36,"line":359},[34,790,70],{"emptyLinePlaceholder":69},[34,792,793,795,797,799,801,803],{"class":36,"line":371},[34,794,329],{"class":104},[34,796,132],{"class":44},[34,798,136],{"class":135},[34,800,139],{"class":44},[34,802,142],{"class":135},[34,804,155],{"class":44},[34,806,807],{"class":36,"line":376},[34,808,809],{"class":76},"    \u002F\u002F ── Handle dynamic src changes ──\n",[34,811,812,815,818,821],{"class":36,"line":382},[34,813,814],{"class":40},"    if",[34,816,817],{"class":44}," (binding.value ",[34,819,820],{"class":40},"!==",[34,822,823],{"class":44}," binding.oldValue) {\n",[34,825,826,829,831],{"class":36,"line":388},[34,827,828],{"class":44},"      el.dataset.src ",[34,830,553],{"class":40},[34,832,584],{"class":44},[34,834,835,838,840,843],{"class":36,"line":400},[34,836,837],{"class":44},"      el.src ",[34,839,553],{"class":40},[34,841,842],{"class":44}," binding.value  ",[34,844,845],{"class":76},"\u002F\u002F already in view, update immediately\n",[34,847,848],{"class":36,"line":406},[34,849,850],{"class":44},"    }\n",[34,852,853],{"class":36,"line":411},[34,854,203],{"class":44},[34,856,857],{"class":36,"line":417},[34,858,70],{"emptyLinePlaceholder":69},[34,860,861,863,865,867],{"class":36,"line":423},[34,862,391],{"class":104},[34,864,132],{"class":44},[34,866,136],{"class":135},[34,868,155],{"class":44},[34,870,871],{"class":36,"line":454},[34,872,873],{"class":76},"    \u002F\u002F ── Cleanup: disconnect observer to prevent memory leaks ──\n",[34,875,876,879,882],{"class":36,"line":469},[34,877,878],{"class":44},"    el._lazyObserver?.",[34,880,881],{"class":104},"disconnect",[34,883,466],{"class":44},[34,885,887],{"class":36,"line":886},48,[34,888,203],{"class":44},[34,890,892],{"class":36,"line":891},49,[34,893,403],{"class":44},[34,895,897],{"class":36,"line":896},50,[34,898,70],{"emptyLinePlaceholder":69},[34,900,902],{"class":36,"line":901},51,[34,903,904],{"class":76},"\u002F\u002F ── Augment HTMLElement for TypeScript ──\n",[34,906,908,911],{"class":36,"line":907},52,[34,909,910],{"class":40},"declare",[34,912,913],{"class":44}," global {\n",[34,915,917,920,923],{"class":36,"line":916},53,[34,918,919],{"class":40},"  interface",[34,921,922],{"class":104}," HTMLElement",[34,924,111],{"class":44},[34,926,928,931,934],{"class":36,"line":927},54,[34,929,930],{"class":135},"    _lazyObserver",[34,932,933],{"class":40},"?:",[34,935,936],{"class":104}," IntersectionObserver\n",[34,938,940],{"class":36,"line":939},55,[34,941,942],{"class":44},"  }\n",[34,944,946],{"class":36,"line":945},56,[34,947,403],{"class":44},[15,949,951],{"id":950},"production-directive-v-copy-clipboard","Production Directive — v-copy Clipboard",[20,953,955],{"filename":954,"language":23},"vCopy.ts",[25,956,958],{"className":27,"code":957,"language":23,"meta":29,"style":29},"import { type Directive } from 'vue'\n\n\u002F\u002F ── v-copy: copy text to clipboard on click ──\n\u002F\u002F Usage: \u003Cbutton v-copy=\"textToCopy\">Copy\u003C\u002Fbutton>\n\u002F\u002F Supports modifier: v-copy.toast to show a toast on success.\n\nexport const vCopy: Directive\u003CHTMLElement, string> = {\n  mounted(el, binding) {\n    \u002F\u002F ── Store the click handler so we can remove it on unmount ──\n    el._copyHandler = async () => {\n      const text = binding.value\n      try {\n        \u002F\u002F ── Clipboard API (modern, secure context only) ──\n        await navigator.clipboard.writeText(text)\n\n        \u002F\u002F ── Optional: emit a custom event for the component to listen to ──\n        el.dispatchEvent(new CustomEvent('copied', { detail: { text } }))\n\n        \u002F\u002F ── Modifier: show toast if v-copy.toast ──\n        if (binding.modifiers.toast) {\n          showToast('Copied to clipboard')\n        }\n      } catch (e) {\n        \u002F\u002F ── Fallback: execCommand (deprecated but works in non-secure contexts) ──\n        const textarea = document.createElement('textarea')\n        textarea.value = text\n        textarea.style.position = 'fixed'\n        textarea.style.opacity = '0'\n        document.body.appendChild(textarea)\n        textarea.select()\n        document.execCommand('copy')\n        document.body.removeChild(textarea)\n      }\n    }\n\n    el.addEventListener('click', el._copyHandler)\n  },\n\n  \u002F\u002F ── updated: handle dynamic value changes (binding.value updated) ──\n  \u002F\u002F The click handler reads binding.value at click time, so if we re-create\n  \u002F\u002F the handler on every update, we always have the latest value.\n  updated(el, binding) {\n    if (binding.value !== binding.oldValue) {\n      el.removeEventListener('click', el._copyHandler!)\n      el._copyHandler = createHandler(binding)\n      el.addEventListener('click', el._copyHandler)\n    }\n  },\n\n  unmounted(el) {\n    el.removeEventListener('click', el._copyHandler!)\n  },\n}\n\nfunction createHandler(binding: DirectiveBinding\u003Cstring>) {\n  return async () => {\n    await navigator.clipboard.writeText(binding.value)\n  }\n}\n\ndeclare global {\n  interface HTMLElement {\n    _copyHandler?: () => void\n  }\n}\n",[31,959,960,974,978,983,988,993,997,1025,1039,1044,1064,1076,1083,1088,1102,1106,1111,1135,1139,1144,1152,1165,1170,1181,1186,1209,1219,1229,1239,1250,1260,1275,1284,1288,1292,1296,1311,1315,1319,1324,1329,1334,1348,1358,1378,1391,1403,1407,1411,1415,1425,1441,1445,1449,1453,1476,1489,1502,1507,1512,1517,1524,1533,1548,1553],{"__ignoreMap":29},[34,961,962,964,966,968,970,972],{"class":36,"line":37},[34,963,41],{"class":40},[34,965,45],{"class":44},[34,967,48],{"class":40},[34,969,494],{"class":44},[34,971,59],{"class":40},[34,973,63],{"class":62},[34,975,976],{"class":36,"line":66},[34,977,70],{"emptyLinePlaceholder":69},[34,979,980],{"class":36,"line":73},[34,981,982],{"class":76},"\u002F\u002F ── v-copy: copy text to clipboard on click ──\n",[34,984,985],{"class":36,"line":80},[34,986,987],{"class":76},"\u002F\u002F Usage: \u003Cbutton v-copy=\"textToCopy\">Copy\u003C\u002Fbutton>\n",[34,989,990],{"class":36,"line":86},[34,991,992],{"class":76},"\u002F\u002F Supports modifier: v-copy.toast to show a toast on success.\n",[34,994,995],{"class":36,"line":91},[34,996,70],{"emptyLinePlaceholder":69},[34,998,999,1001,1003,1006,1008,1010,1012,1015,1017,1019,1021,1023],{"class":36,"line":114},[34,1000,526],{"class":40},[34,1002,529],{"class":40},[34,1004,1005],{"class":97}," vCopy",[34,1007,101],{"class":40},[34,1009,105],{"class":104},[34,1011,539],{"class":44},[34,1013,1014],{"class":104},"HTMLElement",[34,1016,139],{"class":44},[34,1018,547],{"class":97},[34,1020,550],{"class":44},[34,1022,553],{"class":40},[34,1024,111],{"class":44},[34,1026,1027,1029,1031,1033,1035,1037],{"class":36,"line":120},[34,1028,260],{"class":104},[34,1030,132],{"class":44},[34,1032,136],{"class":135},[34,1034,139],{"class":44},[34,1036,142],{"class":135},[34,1038,155],{"class":44},[34,1040,1041],{"class":36,"line":126},[34,1042,1043],{"class":76},"    \u002F\u002F ── Store the click handler so we can remove it on unmount ──\n",[34,1045,1046,1049,1052,1054,1057,1060,1062],{"class":36,"line":158},[34,1047,1048],{"class":44},"    el.",[34,1050,1051],{"class":104},"_copyHandler",[34,1053,108],{"class":40},[34,1055,1056],{"class":40}," async",[34,1058,1059],{"class":44}," () ",[34,1061,449],{"class":40},[34,1063,111],{"class":44},[34,1065,1066,1069,1072,1074],{"class":36,"line":164},[34,1067,1068],{"class":40},"      const",[34,1070,1071],{"class":97}," text",[34,1073,108],{"class":40},[34,1075,584],{"class":44},[34,1077,1078,1081],{"class":36,"line":170},[34,1079,1080],{"class":40},"      try",[34,1082,111],{"class":44},[34,1084,1085],{"class":36,"line":176},[34,1086,1087],{"class":76},"        \u002F\u002F ── Clipboard API (modern, secure context only) ──\n",[34,1089,1090,1093,1096,1099],{"class":36,"line":182},[34,1091,1092],{"class":40},"        await",[34,1094,1095],{"class":44}," navigator.clipboard.",[34,1097,1098],{"class":104},"writeText",[34,1100,1101],{"class":44},"(text)\n",[34,1103,1104],{"class":36,"line":188},[34,1105,70],{"emptyLinePlaceholder":69},[34,1107,1108],{"class":36,"line":194},[34,1109,1110],{"class":76},"        \u002F\u002F ── Optional: emit a custom event for the component to listen to ──\n",[34,1112,1113,1116,1119,1121,1124,1127,1129,1132],{"class":36,"line":200},[34,1114,1115],{"class":44},"        el.",[34,1117,1118],{"class":104},"dispatchEvent",[34,1120,132],{"class":44},[34,1122,1123],{"class":40},"new",[34,1125,1126],{"class":104}," CustomEvent",[34,1128,132],{"class":44},[34,1130,1131],{"class":62},"'copied'",[34,1133,1134],{"class":44},", { detail: { text } }))\n",[34,1136,1137],{"class":36,"line":206},[34,1138,70],{"emptyLinePlaceholder":69},[34,1140,1141],{"class":36,"line":211},[34,1142,1143],{"class":76},"        \u002F\u002F ── Modifier: show toast if v-copy.toast ──\n",[34,1145,1146,1149],{"class":36,"line":217},[34,1147,1148],{"class":40},"        if",[34,1150,1151],{"class":44}," (binding.modifiers.toast) {\n",[34,1153,1154,1157,1159,1162],{"class":36,"line":223},[34,1155,1156],{"class":104},"          showToast",[34,1158,132],{"class":44},[34,1160,1161],{"class":62},"'Copied to clipboard'",[34,1163,1164],{"class":44},")\n",[34,1166,1167],{"class":36,"line":240},[34,1168,1169],{"class":44},"        }\n",[34,1171,1172,1175,1178],{"class":36,"line":245},[34,1173,1174],{"class":44},"      } ",[34,1176,1177],{"class":40},"catch",[34,1179,1180],{"class":44}," (e) {\n",[34,1182,1183],{"class":36,"line":251},[34,1184,1185],{"class":76},"        \u002F\u002F ── Fallback: execCommand (deprecated but works in non-secure contexts) ──\n",[34,1187,1188,1191,1194,1196,1199,1202,1204,1207],{"class":36,"line":257},[34,1189,1190],{"class":40},"        const",[34,1192,1193],{"class":97}," textarea",[34,1195,108],{"class":40},[34,1197,1198],{"class":44}," document.",[34,1200,1201],{"class":104},"createElement",[34,1203,132],{"class":44},[34,1205,1206],{"class":62},"'textarea'",[34,1208,1164],{"class":44},[34,1210,1211,1214,1216],{"class":36,"line":273},[34,1212,1213],{"class":44},"        textarea.value ",[34,1215,553],{"class":40},[34,1217,1218],{"class":44}," text\n",[34,1220,1221,1224,1226],{"class":36,"line":278},[34,1222,1223],{"class":44},"        textarea.style.position ",[34,1225,553],{"class":40},[34,1227,1228],{"class":62}," 'fixed'\n",[34,1230,1231,1234,1236],{"class":36,"line":284},[34,1232,1233],{"class":44},"        textarea.style.opacity ",[34,1235,553],{"class":40},[34,1237,1238],{"class":62}," '0'\n",[34,1240,1241,1244,1247],{"class":36,"line":290},[34,1242,1243],{"class":44},"        document.body.",[34,1245,1246],{"class":104},"appendChild",[34,1248,1249],{"class":44},"(textarea)\n",[34,1251,1252,1255,1258],{"class":36,"line":315},[34,1253,1254],{"class":44},"        textarea.",[34,1256,1257],{"class":104},"select",[34,1259,466],{"class":44},[34,1261,1262,1265,1268,1270,1273],{"class":36,"line":320},[34,1263,1264],{"class":44},"        document.",[34,1266,1267],{"class":104},"execCommand",[34,1269,132],{"class":44},[34,1271,1272],{"class":62},"'copy'",[34,1274,1164],{"class":44},[34,1276,1277,1279,1282],{"class":36,"line":326},[34,1278,1243],{"class":44},[34,1280,1281],{"class":104},"removeChild",[34,1283,1249],{"class":44},[34,1285,1286],{"class":36,"line":342},[34,1287,748],{"class":44},[34,1289,1290],{"class":36,"line":347},[34,1291,850],{"class":44},[34,1293,1294],{"class":36,"line":353},[34,1295,70],{"emptyLinePlaceholder":69},[34,1297,1298,1300,1303,1305,1308],{"class":36,"line":359},[34,1299,1048],{"class":44},[34,1301,1302],{"class":104},"addEventListener",[34,1304,132],{"class":44},[34,1306,1307],{"class":62},"'click'",[34,1309,1310],{"class":44},", el._copyHandler)\n",[34,1312,1313],{"class":36,"line":371},[34,1314,203],{"class":44},[34,1316,1317],{"class":36,"line":376},[34,1318,70],{"emptyLinePlaceholder":69},[34,1320,1321],{"class":36,"line":382},[34,1322,1323],{"class":76},"  \u002F\u002F ── updated: handle dynamic value changes (binding.value updated) ──\n",[34,1325,1326],{"class":36,"line":388},[34,1327,1328],{"class":76},"  \u002F\u002F The click handler reads binding.value at click time, so if we re-create\n",[34,1330,1331],{"class":36,"line":400},[34,1332,1333],{"class":76},"  \u002F\u002F the handler on every update, we always have the latest value.\n",[34,1335,1336,1338,1340,1342,1344,1346],{"class":36,"line":406},[34,1337,329],{"class":104},[34,1339,132],{"class":44},[34,1341,136],{"class":135},[34,1343,139],{"class":44},[34,1345,142],{"class":135},[34,1347,155],{"class":44},[34,1349,1350,1352,1354,1356],{"class":36,"line":411},[34,1351,814],{"class":40},[34,1353,817],{"class":44},[34,1355,820],{"class":40},[34,1357,823],{"class":44},[34,1359,1360,1363,1366,1368,1370,1373,1376],{"class":36,"line":417},[34,1361,1362],{"class":44},"      el.",[34,1364,1365],{"class":104},"removeEventListener",[34,1367,132],{"class":44},[34,1369,1307],{"class":62},[34,1371,1372],{"class":44},", el._copyHandler",[34,1374,1375],{"class":40},"!",[34,1377,1164],{"class":44},[34,1379,1380,1383,1385,1388],{"class":36,"line":423},[34,1381,1382],{"class":44},"      el._copyHandler ",[34,1384,553],{"class":40},[34,1386,1387],{"class":104}," createHandler",[34,1389,1390],{"class":44},"(binding)\n",[34,1392,1393,1395,1397,1399,1401],{"class":36,"line":454},[34,1394,1362],{"class":44},[34,1396,1302],{"class":104},[34,1398,132],{"class":44},[34,1400,1307],{"class":62},[34,1402,1310],{"class":44},[34,1404,1405],{"class":36,"line":469},[34,1406,850],{"class":44},[34,1408,1409],{"class":36,"line":886},[34,1410,203],{"class":44},[34,1412,1413],{"class":36,"line":891},[34,1414,70],{"emptyLinePlaceholder":69},[34,1416,1417,1419,1421,1423],{"class":36,"line":896},[34,1418,391],{"class":104},[34,1420,132],{"class":44},[34,1422,136],{"class":135},[34,1424,155],{"class":44},[34,1426,1427,1429,1431,1433,1435,1437,1439],{"class":36,"line":901},[34,1428,1048],{"class":44},[34,1430,1365],{"class":104},[34,1432,132],{"class":44},[34,1434,1307],{"class":62},[34,1436,1372],{"class":44},[34,1438,1375],{"class":40},[34,1440,1164],{"class":44},[34,1442,1443],{"class":36,"line":907},[34,1444,203],{"class":44},[34,1446,1447],{"class":36,"line":916},[34,1448,403],{"class":44},[34,1450,1451],{"class":36,"line":927},[34,1452,70],{"emptyLinePlaceholder":69},[34,1454,1455,1458,1460,1462,1464,1466,1469,1471,1473],{"class":36,"line":939},[34,1456,1457],{"class":40},"function",[34,1459,1387],{"class":104},[34,1461,132],{"class":44},[34,1463,142],{"class":135},[34,1465,101],{"class":40},[34,1467,1468],{"class":104}," DirectiveBinding",[34,1470,539],{"class":44},[34,1472,547],{"class":97},[34,1474,1475],{"class":44},">) {\n",[34,1477,1478,1481,1483,1485,1487],{"class":36,"line":945},[34,1479,1480],{"class":40},"  return",[34,1482,1056],{"class":40},[34,1484,1059],{"class":44},[34,1486,449],{"class":40},[34,1488,111],{"class":44},[34,1490,1492,1495,1497,1499],{"class":36,"line":1491},57,[34,1493,1494],{"class":40},"    await",[34,1496,1095],{"class":44},[34,1498,1098],{"class":104},[34,1500,1501],{"class":44},"(binding.value)\n",[34,1503,1505],{"class":36,"line":1504},58,[34,1506,942],{"class":44},[34,1508,1510],{"class":36,"line":1509},59,[34,1511,403],{"class":44},[34,1513,1515],{"class":36,"line":1514},60,[34,1516,70],{"emptyLinePlaceholder":69},[34,1518,1520,1522],{"class":36,"line":1519},61,[34,1521,910],{"class":40},[34,1523,913],{"class":44},[34,1525,1527,1529,1531],{"class":36,"line":1526},62,[34,1528,919],{"class":40},[34,1530,922],{"class":104},[34,1532,111],{"class":44},[34,1534,1536,1539,1541,1543,1545],{"class":36,"line":1535},63,[34,1537,1538],{"class":104},"    _copyHandler",[34,1540,933],{"class":40},[34,1542,1059],{"class":44},[34,1544,449],{"class":40},[34,1546,1547],{"class":97}," void\n",[34,1549,1551],{"class":36,"line":1550},64,[34,1552,942],{"class":44},[34,1554,1556],{"class":36,"line":1555},65,[34,1557,403],{"class":44},[15,1559,1561],{"id":1560},"directive-arguments-and-modifiers","Directive Arguments and Modifiers",[20,1563,1566],{"filename":1564,"language":1565},"DirectiveArgs.vue","vue",[25,1567,1570],{"className":1568,"code":1569,"language":1565,"meta":29,"style":29},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup>\n\u002F\u002F ── v-permission:role.admin=\"user\" ────────────────────\n\u002F\u002F binding.arg = \"role\" (the part after the colon)\n\u002F\u002F binding.modifiers = { admin: true } (the dots)\n\u002F\u002F binding.value = user (the expression result)\n\nconst vPermission = {\n  mounted(el, binding) {\n    const { arg, modifiers, value } = binding\n    \u002F\u002F arg = 'role', modifiers = { admin: true }, value = user object\n    if (arg === 'role' && modifiers.admin && value.role !== 'admin') {\n      el.remove()  \u002F\u002F remove element if user is not admin\n    }\n    \u002F\u002F Or: el.style.display = 'none' for hide instead of remove\n  },\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv v-permission:role.admin=\"user\">Admin only content\u003C\u002Fdiv>\n  \u003C!-- arg: 'role', modifiers: { admin: true }, value: user -->\n\u003C\u002Ftemplate>\n",[31,1571,1572,1586,1591,1596,1601,1606,1610,1621,1635,1662,1667,1699,1712,1716,1721,1725,1729,1738,1742,1751,1774,1779],{"__ignoreMap":29},[34,1573,1574,1576,1580,1583],{"class":36,"line":37},[34,1575,539],{"class":44},[34,1577,1579],{"class":1578},"sk71V","script",[34,1581,1582],{"class":104}," setup",[34,1584,1585],{"class":44},">\n",[34,1587,1588],{"class":36,"line":66},[34,1589,1590],{"class":76},"\u002F\u002F ── v-permission:role.admin=\"user\" ────────────────────\n",[34,1592,1593],{"class":36,"line":73},[34,1594,1595],{"class":76},"\u002F\u002F binding.arg = \"role\" (the part after the colon)\n",[34,1597,1598],{"class":36,"line":80},[34,1599,1600],{"class":76},"\u002F\u002F binding.modifiers = { admin: true } (the dots)\n",[34,1602,1603],{"class":36,"line":86},[34,1604,1605],{"class":76},"\u002F\u002F binding.value = user (the expression result)\n",[34,1607,1608],{"class":36,"line":91},[34,1609,70],{"emptyLinePlaceholder":69},[34,1611,1612,1614,1617,1619],{"class":36,"line":114},[34,1613,94],{"class":40},[34,1615,1616],{"class":97}," vPermission",[34,1618,108],{"class":40},[34,1620,111],{"class":44},[34,1622,1623,1625,1627,1629,1631,1633],{"class":36,"line":120},[34,1624,260],{"class":104},[34,1626,132],{"class":44},[34,1628,136],{"class":135},[34,1630,139],{"class":44},[34,1632,142],{"class":135},[34,1634,155],{"class":44},[34,1636,1637,1639,1641,1644,1646,1649,1651,1654,1657,1659],{"class":36,"line":126},[34,1638,598],{"class":40},[34,1640,45],{"class":44},[34,1642,1643],{"class":97},"arg",[34,1645,139],{"class":44},[34,1647,1648],{"class":97},"modifiers",[34,1650,139],{"class":44},[34,1652,1653],{"class":97},"value",[34,1655,1656],{"class":44}," } ",[34,1658,553],{"class":40},[34,1660,1661],{"class":44}," binding\n",[34,1663,1664],{"class":36,"line":158},[34,1665,1666],{"class":76},"    \u002F\u002F arg = 'role', modifiers = { admin: true }, value = user object\n",[34,1668,1669,1671,1674,1677,1680,1683,1686,1689,1692,1694,1697],{"class":36,"line":164},[34,1670,814],{"class":40},[34,1672,1673],{"class":44}," (arg ",[34,1675,1676],{"class":40},"===",[34,1678,1679],{"class":62}," 'role'",[34,1681,1682],{"class":40}," &&",[34,1684,1685],{"class":44}," modifiers.admin ",[34,1687,1688],{"class":40},"&&",[34,1690,1691],{"class":44}," value.role ",[34,1693,820],{"class":40},[34,1695,1696],{"class":62}," 'admin'",[34,1698,155],{"class":44},[34,1700,1701,1703,1706,1709],{"class":36,"line":170},[34,1702,1362],{"class":44},[34,1704,1705],{"class":104},"remove",[34,1707,1708],{"class":44},"()  ",[34,1710,1711],{"class":76},"\u002F\u002F remove element if user is not admin\n",[34,1713,1714],{"class":36,"line":176},[34,1715,850],{"class":44},[34,1717,1718],{"class":36,"line":182},[34,1719,1720],{"class":76},"    \u002F\u002F Or: el.style.display = 'none' for hide instead of remove\n",[34,1722,1723],{"class":36,"line":188},[34,1724,203],{"class":44},[34,1726,1727],{"class":36,"line":194},[34,1728,403],{"class":44},[34,1730,1731,1734,1736],{"class":36,"line":200},[34,1732,1733],{"class":44},"\u003C\u002F",[34,1735,1579],{"class":1578},[34,1737,1585],{"class":44},[34,1739,1740],{"class":36,"line":206},[34,1741,70],{"emptyLinePlaceholder":69},[34,1743,1744,1746,1749],{"class":36,"line":211},[34,1745,539],{"class":44},[34,1747,1748],{"class":1578},"template",[34,1750,1585],{"class":44},[34,1752,1753,1756,1759,1762,1764,1767,1770,1772],{"class":36,"line":217},[34,1754,1755],{"class":44},"  \u003C",[34,1757,1758],{"class":1578},"div",[34,1760,1761],{"class":104}," v-permission:role.admin",[34,1763,553],{"class":44},[34,1765,1766],{"class":62},"\"user\"",[34,1768,1769],{"class":44},">Admin only content\u003C\u002F",[34,1771,1758],{"class":1578},[34,1773,1585],{"class":44},[34,1775,1776],{"class":36,"line":223},[34,1777,1778],{"class":76},"  \u003C!-- arg: 'role', modifiers: { admin: true }, value: user -->\n",[34,1780,1781,1783,1785],{"class":36,"line":240},[34,1782,1733],{"class":44},[34,1784,1748],{"class":1578},[34,1786,1585],{"class":44},[15,1788,1790],{"id":1789},"tips-tricks","💡 Tips & Tricks",[20,1792,1794],{"filename":1793,"language":23},"tips.ts",[25,1795,1797],{"className":27,"code":1796,"language":23,"meta":29,"style":29},"\u002F\u002F ── 1. Directive naming: v- prefix in templates, vXxx in script ──\n\u002F\u002F export const vFocus = { ... } → used as \u003Cinput v-focus \u002F>\n\u002F\u002F The 'v' prefix is automatic in templates; the variable must start with 'v'.\n\n\u002F\u002F ── 2. Directives on components: applied to the root element ──\n\u002F\u002F \u003CMyComp v-focus \u002F> → v-focus runs on MyComp's root DOM element.\n\u002F\u002F With fragments (multiple roots), a warning is issued (ambiguous target).\n\n\u002F\u002F ── 3. Function shorthand: mounted + updated only ──\n\u002F\u002F const vColor = (el, binding) => { el.style.color = binding.value }\n\u002F\u002F Equivalent to { mounted(el, b) {...}, updated(el, b) {...} }\n\n\u002F\u002F ── 4. Custom events from directives ──\n\u002F\u002F el.dispatchEvent(new CustomEvent('my-event', { detail: payload }))\n\u002F\u002F Parent listens: \u003Cdiv v-my @my-event=\"handler\" \u002F>\n\n\u002F\u002F ── 5. Global registration: app.directive() ──\n\u002F\u002F app.directive('lazy', vLazy) → available in ALL components without import.\n\u002F\u002F Use for app-wide directives (v-focus, v-permission); local for one-off.\n",[31,1798,1799,1804,1809,1814,1818,1823,1828,1833,1837,1842,1847,1852,1856,1861,1866,1871,1875,1880,1885],{"__ignoreMap":29},[34,1800,1801],{"class":36,"line":37},[34,1802,1803],{"class":76},"\u002F\u002F ── 1. Directive naming: v- prefix in templates, vXxx in script ──\n",[34,1805,1806],{"class":36,"line":66},[34,1807,1808],{"class":76},"\u002F\u002F export const vFocus = { ... } → used as \u003Cinput v-focus \u002F>\n",[34,1810,1811],{"class":36,"line":73},[34,1812,1813],{"class":76},"\u002F\u002F The 'v' prefix is automatic in templates; the variable must start with 'v'.\n",[34,1815,1816],{"class":36,"line":80},[34,1817,70],{"emptyLinePlaceholder":69},[34,1819,1820],{"class":36,"line":86},[34,1821,1822],{"class":76},"\u002F\u002F ── 2. Directives on components: applied to the root element ──\n",[34,1824,1825],{"class":36,"line":91},[34,1826,1827],{"class":76},"\u002F\u002F \u003CMyComp v-focus \u002F> → v-focus runs on MyComp's root DOM element.\n",[34,1829,1830],{"class":36,"line":114},[34,1831,1832],{"class":76},"\u002F\u002F With fragments (multiple roots), a warning is issued (ambiguous target).\n",[34,1834,1835],{"class":36,"line":120},[34,1836,70],{"emptyLinePlaceholder":69},[34,1838,1839],{"class":36,"line":126},[34,1840,1841],{"class":76},"\u002F\u002F ── 3. Function shorthand: mounted + updated only ──\n",[34,1843,1844],{"class":36,"line":158},[34,1845,1846],{"class":76},"\u002F\u002F const vColor = (el, binding) => { el.style.color = binding.value }\n",[34,1848,1849],{"class":36,"line":164},[34,1850,1851],{"class":76},"\u002F\u002F Equivalent to { mounted(el, b) {...}, updated(el, b) {...} }\n",[34,1853,1854],{"class":36,"line":170},[34,1855,70],{"emptyLinePlaceholder":69},[34,1857,1858],{"class":36,"line":176},[34,1859,1860],{"class":76},"\u002F\u002F ── 4. Custom events from directives ──\n",[34,1862,1863],{"class":36,"line":182},[34,1864,1865],{"class":76},"\u002F\u002F el.dispatchEvent(new CustomEvent('my-event', { detail: payload }))\n",[34,1867,1868],{"class":36,"line":188},[34,1869,1870],{"class":76},"\u002F\u002F Parent listens: \u003Cdiv v-my @my-event=\"handler\" \u002F>\n",[34,1872,1873],{"class":36,"line":194},[34,1874,70],{"emptyLinePlaceholder":69},[34,1876,1877],{"class":36,"line":200},[34,1878,1879],{"class":76},"\u002F\u002F ── 5. Global registration: app.directive() ──\n",[34,1881,1882],{"class":36,"line":206},[34,1883,1884],{"class":76},"\u002F\u002F app.directive('lazy', vLazy) → available in ALL components without import.\n",[34,1886,1887],{"class":36,"line":211},[34,1888,1889],{"class":76},"\u002F\u002F Use for app-wide directives (v-focus, v-permission); local for one-off.\n",[15,1891,1893],{"id":1892},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[20,1895,1897],{"filename":1896,"language":23},"edge-cases.ts",[25,1898,1900],{"className":27,"code":1899,"language":23,"meta":29,"style":29},"\u002F\u002F ── 1. Directives don't receive the component instance via `this` ──\n\u002F\u002F Use binding.instance to access the component that uses the directive.\n\u002F\u002F `this` inside a directive hook is undefined — not the component instance.\n\n\u002F\u002F ── 2. Directives on components with fragments (multiple roots) warn ──\n\u002F\u002F Vue can't know which root to apply the directive to.\n\u002F\u002F Use a single root element, or apply the directive to a specific element inside.\n\n\u002F\u002F ── 3. Cleanup in unmounted is MANDATORY for observers\u002Flisteners ──\n\u002F\u002F Without cleanup: memory leaks, stale callbacks firing on detached elements.\n\u002F\u002F Always store references (observer, handler) on el and remove in unmounted.\n\n\u002F\u002F ── 4. binding.value can be any type — check before using ──\n\u002F\u002F v-my=\"42\" → binding.value = 42 (number)\n\u002F\u002F v-my=\"user.name\" → binding.value = string\n\u002F\u002F v-my=\"{ a: 1 }\" → binding.value = object\n\u002F\u002F Validate the type inside the directive to avoid runtime errors.\n\n\u002F\u002F ── 5. Directive hooks fire AFTER component lifecycle hooks ──\n\u002F\u002F mounted hook: component's onMounted fires first, then directive's mounted.\n\u002F\u002F If the directive needs component state initialized in onMounted, it's ready.\n\n\u002F\u002F ── 6. v-bind='$attrs' can conflict with directives on the same element ──\n\u002F\u002F If parent passes an attribute that the directive also modifies, order matters.\n\u002F\u002F Directives run after attribute patching — directive wins.\n",[31,1901,1902,1907,1912,1917,1921,1926,1931,1936,1940,1945,1950,1955,1959,1964,1969,1974,1979,1984,1988,1993,1998,2003,2007,2012,2017],{"__ignoreMap":29},[34,1903,1904],{"class":36,"line":37},[34,1905,1906],{"class":76},"\u002F\u002F ── 1. Directives don't receive the component instance via `this` ──\n",[34,1908,1909],{"class":36,"line":66},[34,1910,1911],{"class":76},"\u002F\u002F Use binding.instance to access the component that uses the directive.\n",[34,1913,1914],{"class":36,"line":73},[34,1915,1916],{"class":76},"\u002F\u002F `this` inside a directive hook is undefined — not the component instance.\n",[34,1918,1919],{"class":36,"line":80},[34,1920,70],{"emptyLinePlaceholder":69},[34,1922,1923],{"class":36,"line":86},[34,1924,1925],{"class":76},"\u002F\u002F ── 2. Directives on components with fragments (multiple roots) warn ──\n",[34,1927,1928],{"class":36,"line":91},[34,1929,1930],{"class":76},"\u002F\u002F Vue can't know which root to apply the directive to.\n",[34,1932,1933],{"class":36,"line":114},[34,1934,1935],{"class":76},"\u002F\u002F Use a single root element, or apply the directive to a specific element inside.\n",[34,1937,1938],{"class":36,"line":120},[34,1939,70],{"emptyLinePlaceholder":69},[34,1941,1942],{"class":36,"line":126},[34,1943,1944],{"class":76},"\u002F\u002F ── 3. Cleanup in unmounted is MANDATORY for observers\u002Flisteners ──\n",[34,1946,1947],{"class":36,"line":158},[34,1948,1949],{"class":76},"\u002F\u002F Without cleanup: memory leaks, stale callbacks firing on detached elements.\n",[34,1951,1952],{"class":36,"line":164},[34,1953,1954],{"class":76},"\u002F\u002F Always store references (observer, handler) on el and remove in unmounted.\n",[34,1956,1957],{"class":36,"line":170},[34,1958,70],{"emptyLinePlaceholder":69},[34,1960,1961],{"class":36,"line":176},[34,1962,1963],{"class":76},"\u002F\u002F ── 4. binding.value can be any type — check before using ──\n",[34,1965,1966],{"class":36,"line":182},[34,1967,1968],{"class":76},"\u002F\u002F v-my=\"42\" → binding.value = 42 (number)\n",[34,1970,1971],{"class":36,"line":188},[34,1972,1973],{"class":76},"\u002F\u002F v-my=\"user.name\" → binding.value = string\n",[34,1975,1976],{"class":36,"line":194},[34,1977,1978],{"class":76},"\u002F\u002F v-my=\"{ a: 1 }\" → binding.value = object\n",[34,1980,1981],{"class":36,"line":200},[34,1982,1983],{"class":76},"\u002F\u002F Validate the type inside the directive to avoid runtime errors.\n",[34,1985,1986],{"class":36,"line":206},[34,1987,70],{"emptyLinePlaceholder":69},[34,1989,1990],{"class":36,"line":211},[34,1991,1992],{"class":76},"\u002F\u002F ── 5. Directive hooks fire AFTER component lifecycle hooks ──\n",[34,1994,1995],{"class":36,"line":217},[34,1996,1997],{"class":76},"\u002F\u002F mounted hook: component's onMounted fires first, then directive's mounted.\n",[34,1999,2000],{"class":36,"line":223},[34,2001,2002],{"class":76},"\u002F\u002F If the directive needs component state initialized in onMounted, it's ready.\n",[34,2004,2005],{"class":36,"line":240},[34,2006,70],{"emptyLinePlaceholder":69},[34,2008,2009],{"class":36,"line":245},[34,2010,2011],{"class":76},"\u002F\u002F ── 6. v-bind='$attrs' can conflict with directives on the same element ──\n",[34,2013,2014],{"class":36,"line":251},[34,2015,2016],{"class":76},"\u002F\u002F If parent passes an attribute that the directive also modifies, order matters.\n",[34,2018,2019],{"class":36,"line":257},[34,2020,2021],{"class":76},"\u002F\u002F Directives run after attribute patching — directive wins.\n",[15,2023,2025],{"id":2024},"spot-the-bug","🧠 Spot the Bug",[2027,2028,2029],"p",{},"A v-tooltip directive works initially but leaks memory — tooltips accumulate in the DOM.",[20,2031,2033],{"filename":2032,"language":23},"TooltipBug.ts",[25,2034,2036],{"className":27,"code":2035,"language":23,"meta":29,"style":29},"export const vTooltip = {\n  mounted(el, binding) {\n    const tooltip = document.createElement('div')\n    tooltip.textContent = binding.value\n    tooltip.className = 'tooltip'\n    document.body.appendChild(tooltip)\n\n    el.addEventListener('mouseenter', () => {\n      tooltip.style.display = 'block'\n    })\n    el.addEventListener('mouseleave', () => {\n      tooltip.style.display = 'none'\n    })\n    \u002F\u002F ❌ No cleanup — tooltip div and event listeners persist after unmount\n  },\n}\n",[31,2037,2038,2051,2065,2085,2094,2104,2114,2118,2136,2146,2151,2168,2177,2181,2186,2190],{"__ignoreMap":29},[34,2039,2040,2042,2044,2047,2049],{"class":36,"line":37},[34,2041,526],{"class":40},[34,2043,529],{"class":40},[34,2045,2046],{"class":97}," vTooltip",[34,2048,108],{"class":40},[34,2050,111],{"class":44},[34,2052,2053,2055,2057,2059,2061,2063],{"class":36,"line":66},[34,2054,260],{"class":104},[34,2056,132],{"class":44},[34,2058,136],{"class":135},[34,2060,139],{"class":44},[34,2062,142],{"class":135},[34,2064,155],{"class":44},[34,2066,2067,2069,2072,2074,2076,2078,2080,2083],{"class":36,"line":73},[34,2068,598],{"class":40},[34,2070,2071],{"class":97}," tooltip",[34,2073,108],{"class":40},[34,2075,1198],{"class":44},[34,2077,1201],{"class":104},[34,2079,132],{"class":44},[34,2081,2082],{"class":62},"'div'",[34,2084,1164],{"class":44},[34,2086,2087,2090,2092],{"class":36,"line":80},[34,2088,2089],{"class":44},"    tooltip.textContent ",[34,2091,553],{"class":40},[34,2093,584],{"class":44},[34,2095,2096,2099,2101],{"class":36,"line":86},[34,2097,2098],{"class":44},"    tooltip.className ",[34,2100,553],{"class":40},[34,2102,2103],{"class":62}," 'tooltip'\n",[34,2105,2106,2109,2111],{"class":36,"line":91},[34,2107,2108],{"class":44},"    document.body.",[34,2110,1246],{"class":104},[34,2112,2113],{"class":44},"(tooltip)\n",[34,2115,2116],{"class":36,"line":114},[34,2117,70],{"emptyLinePlaceholder":69},[34,2119,2120,2122,2124,2126,2129,2132,2134],{"class":36,"line":120},[34,2121,1048],{"class":44},[34,2123,1302],{"class":104},[34,2125,132],{"class":44},[34,2127,2128],{"class":62},"'mouseenter'",[34,2130,2131],{"class":44},", () ",[34,2133,449],{"class":40},[34,2135,111],{"class":44},[34,2137,2138,2141,2143],{"class":36,"line":126},[34,2139,2140],{"class":44},"      tooltip.style.display ",[34,2142,553],{"class":40},[34,2144,2145],{"class":62}," 'block'\n",[34,2147,2148],{"class":36,"line":158},[34,2149,2150],{"class":44},"    })\n",[34,2152,2153,2155,2157,2159,2162,2164,2166],{"class":36,"line":164},[34,2154,1048],{"class":44},[34,2156,1302],{"class":104},[34,2158,132],{"class":44},[34,2160,2161],{"class":62},"'mouseleave'",[34,2163,2131],{"class":44},[34,2165,449],{"class":40},[34,2167,111],{"class":44},[34,2169,2170,2172,2174],{"class":36,"line":170},[34,2171,2140],{"class":44},[34,2173,553],{"class":40},[34,2175,2176],{"class":62}," 'none'\n",[34,2178,2179],{"class":36,"line":176},[34,2180,2150],{"class":44},[34,2182,2183],{"class":36,"line":182},[34,2184,2185],{"class":76},"    \u002F\u002F ❌ No cleanup — tooltip div and event listeners persist after unmount\n",[34,2187,2188],{"class":36,"line":188},[34,2189,203],{"class":44},[34,2191,2192],{"class":36,"line":194},[34,2193,403],{"class":44},[2195,2196,2197,2201,2212,2222,2493],"details",{},[2198,2199,2200],"summary",{},"Answer",[2027,2202,2203,2204,2207,2208,2211],{},"The ",[31,2205,2206],{},"tooltip"," div is appended to ",[31,2209,2210],{},"document.body"," and never removed. Event listeners are added but never removed. When the element unmounts, the tooltip div and listeners persist — a memory leak that accumulates across navigations.",[2027,2213,2214,2218,2219,101],{},[2215,2216,2217],"strong",{},"Fix"," — store references and clean up in ",[31,2220,2221],{},"unmounted",[20,2223,2225],{"filename":2224,"language":23},"TooltipFixed.ts",[25,2226,2228],{"className":27,"code":2227,"language":23,"meta":29,"style":29},"export const vTooltip = {\n  mounted(el, binding) {\n    const tooltip = document.createElement('div')\n    tooltip.textContent = binding.value\n    tooltip.className = 'tooltip'\n    tooltip.style.display = 'none'\n    document.body.appendChild(tooltip)\n\n    const show = () => { tooltip.style.display = 'block' }\n    const hide = () => { tooltip.style.display = 'none' }\n\n    el.addEventListener('mouseenter', show)\n    el.addEventListener('mouseleave', hide)\n\n    \u002F\u002F Store for cleanup\n    el._tooltip = { tooltip, show, hide }\n  },\n\n  unmounted(el) {\n    const { tooltip, show, hide } = el._tooltip\n    el.removeEventListener('mouseenter', show)\n    el.removeEventListener('mouseleave', hide)\n    document.body.removeChild(tooltip)  \u002F\u002F remove the DOM element\n  },\n}\n",[31,2229,2230,2242,2256,2274,2282,2290,2299,2307,2311,2335,2357,2361,2374,2387,2391,2396,2406,2410,2414,2424,2449,2461,2473,2485,2489],{"__ignoreMap":29},[34,2231,2232,2234,2236,2238,2240],{"class":36,"line":37},[34,2233,526],{"class":40},[34,2235,529],{"class":40},[34,2237,2046],{"class":97},[34,2239,108],{"class":40},[34,2241,111],{"class":44},[34,2243,2244,2246,2248,2250,2252,2254],{"class":36,"line":66},[34,2245,260],{"class":104},[34,2247,132],{"class":44},[34,2249,136],{"class":135},[34,2251,139],{"class":44},[34,2253,142],{"class":135},[34,2255,155],{"class":44},[34,2257,2258,2260,2262,2264,2266,2268,2270,2272],{"class":36,"line":73},[34,2259,598],{"class":40},[34,2261,2071],{"class":97},[34,2263,108],{"class":40},[34,2265,1198],{"class":44},[34,2267,1201],{"class":104},[34,2269,132],{"class":44},[34,2271,2082],{"class":62},[34,2273,1164],{"class":44},[34,2275,2276,2278,2280],{"class":36,"line":80},[34,2277,2089],{"class":44},[34,2279,553],{"class":40},[34,2281,584],{"class":44},[34,2283,2284,2286,2288],{"class":36,"line":86},[34,2285,2098],{"class":44},[34,2287,553],{"class":40},[34,2289,2103],{"class":62},[34,2291,2292,2295,2297],{"class":36,"line":91},[34,2293,2294],{"class":44},"    tooltip.style.display ",[34,2296,553],{"class":40},[34,2298,2176],{"class":62},[34,2300,2301,2303,2305],{"class":36,"line":114},[34,2302,2108],{"class":44},[34,2304,1246],{"class":104},[34,2306,2113],{"class":44},[34,2308,2309],{"class":36,"line":120},[34,2310,70],{"emptyLinePlaceholder":69},[34,2312,2313,2315,2318,2320,2322,2324,2327,2329,2332],{"class":36,"line":126},[34,2314,598],{"class":40},[34,2316,2317],{"class":104}," show",[34,2319,108],{"class":40},[34,2321,1059],{"class":44},[34,2323,449],{"class":40},[34,2325,2326],{"class":44}," { tooltip.style.display ",[34,2328,553],{"class":40},[34,2330,2331],{"class":62}," 'block'",[34,2333,2334],{"class":44}," }\n",[34,2336,2337,2339,2342,2344,2346,2348,2350,2352,2355],{"class":36,"line":158},[34,2338,598],{"class":40},[34,2340,2341],{"class":104}," hide",[34,2343,108],{"class":40},[34,2345,1059],{"class":44},[34,2347,449],{"class":40},[34,2349,2326],{"class":44},[34,2351,553],{"class":40},[34,2353,2354],{"class":62}," 'none'",[34,2356,2334],{"class":44},[34,2358,2359],{"class":36,"line":164},[34,2360,70],{"emptyLinePlaceholder":69},[34,2362,2363,2365,2367,2369,2371],{"class":36,"line":170},[34,2364,1048],{"class":44},[34,2366,1302],{"class":104},[34,2368,132],{"class":44},[34,2370,2128],{"class":62},[34,2372,2373],{"class":44},", show)\n",[34,2375,2376,2378,2380,2382,2384],{"class":36,"line":176},[34,2377,1048],{"class":44},[34,2379,1302],{"class":104},[34,2381,132],{"class":44},[34,2383,2161],{"class":62},[34,2385,2386],{"class":44},", hide)\n",[34,2388,2389],{"class":36,"line":182},[34,2390,70],{"emptyLinePlaceholder":69},[34,2392,2393],{"class":36,"line":188},[34,2394,2395],{"class":76},"    \u002F\u002F Store for cleanup\n",[34,2397,2398,2401,2403],{"class":36,"line":194},[34,2399,2400],{"class":44},"    el._tooltip ",[34,2402,553],{"class":40},[34,2404,2405],{"class":44}," { tooltip, show, hide }\n",[34,2407,2408],{"class":36,"line":200},[34,2409,203],{"class":44},[34,2411,2412],{"class":36,"line":206},[34,2413,70],{"emptyLinePlaceholder":69},[34,2415,2416,2418,2420,2422],{"class":36,"line":211},[34,2417,391],{"class":104},[34,2419,132],{"class":44},[34,2421,136],{"class":135},[34,2423,155],{"class":44},[34,2425,2426,2428,2430,2432,2434,2437,2439,2442,2444,2446],{"class":36,"line":217},[34,2427,598],{"class":40},[34,2429,45],{"class":44},[34,2431,2206],{"class":97},[34,2433,139],{"class":44},[34,2435,2436],{"class":97},"show",[34,2438,139],{"class":44},[34,2440,2441],{"class":97},"hide",[34,2443,1656],{"class":44},[34,2445,553],{"class":40},[34,2447,2448],{"class":44}," el._tooltip\n",[34,2450,2451,2453,2455,2457,2459],{"class":36,"line":223},[34,2452,1048],{"class":44},[34,2454,1365],{"class":104},[34,2456,132],{"class":44},[34,2458,2128],{"class":62},[34,2460,2373],{"class":44},[34,2462,2463,2465,2467,2469,2471],{"class":36,"line":240},[34,2464,1048],{"class":44},[34,2466,1365],{"class":104},[34,2468,132],{"class":44},[34,2470,2161],{"class":62},[34,2472,2386],{"class":44},[34,2474,2475,2477,2479,2482],{"class":36,"line":245},[34,2476,2108],{"class":44},[34,2478,1281],{"class":104},[34,2480,2481],{"class":44},"(tooltip)  ",[34,2483,2484],{"class":76},"\u002F\u002F remove the DOM element\n",[34,2486,2487],{"class":36,"line":251},[34,2488,203],{"class":44},[34,2490,2491],{"class":36,"line":257},[34,2492,403],{"class":44},[2027,2494,2495,2498,2499,2501],{},[2215,2496,2497],{},"The lesson",": every DOM element created, event listener added, or observer created in a directive must be cleaned up in ",[31,2500,2221],{},". Without cleanup, the resources outlive the element, causing memory leaks.",[2503,2504,2505],"style",{},"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 .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}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 .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":66,"depth":66,"links":2507},[2508,2509,2510,2511,2512,2513,2514],{"id":17,"depth":66,"text":18},{"id":474,"depth":66,"text":475},{"id":950,"depth":66,"text":951},{"id":1560,"depth":66,"text":1561},{"id":1789,"depth":66,"text":1790},{"id":1892,"depth":66,"text":1893},{"id":2024,"depth":66,"text":2025},"Directive hook lifecycle (created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted), hook argument shape, directive function shorthand, and production patterns for intersection observer and copy directives.","md",{},"\u002Fvue\u002F15-custom-directives",{"title":5,"description":2515},"vue\u002F15-custom-directives","l2z8M8S-SN73W3SdKFDXHuaWbt1H95b-6gTVGq5CyKk",1789924655722]