[{"data":1,"prerenderedAt":8293},["ShallowReactive",2],{"writesAr-\u002Fwrites\u002Finside-asentinel-pagination":3,"related-writesAr-\u002Fwrites\u002Finside-asentinel-pagination":2011},{"id":4,"title":5,"body":6,"date":1990,"description":1991,"description_ar":1992,"draft":1993,"extension":1994,"image":1992,"keywords":1995,"lang":2005,"meta":2006,"navigation":334,"path":2007,"seo":2008,"stem":2009,"title_ar":1992,"updatedAt":1990,"__hash__":2010},"writesAr\u002Fwrites-ar\u002Finside-asentinel-pagination.md","أربع مشكلات في أيّ قائمة infinite-scroll، وطريقة إصلاحها",{"type":7,"value":8,"toc":1973},"minimark",[9,13,25,35,38,43,46,167,174,181,188,195,198,383,398,401,467,470,551,558,565,568,589,596,599,677,682,973,991,996,1002,1040,1043,1182,1197,1201,1208,1211,1214,1361,1364,1375,1382,1389,1392,1407,1414,1418,1435,1438,1441,1483,1510,1517,1520,1645,1663,1673,1680,1683,1686,1920,1945,1956,1960,1966,1969],[10,11,12],"p",{},"الـ infinite scroll من الميزات التي تبدو بسيطة وأنت ترسمها على السبورة، ثم تتحوّل إلى شيء غريب حين تصطدم بالاستخدام الحقيقي. المسار السعيد يتّسع لشريحة واحدة: تضع IntersectionObserver في أسفل القائمة، تُطلق fetch عند التقاطع، وتُلحق النتائج. اشحنها وانتهِ.",[10,14,15,16,20,21,24],{},"بعدها تبدأ المشكلات في العثور عليك. يغيّر المستخدم فلتراً فيرجع لك استدعاء الصفحة التالية ببيانات الفلتر القديم. يخرج طلبَا \"load more\" في نفس الـ tick لأن المستخدم مرّر بسرعة. يُفرَّغ المكوّن بينما لا يزال observer نشِطاً، ويحتفظ closure الخاص بالـ callback بالصفحة كلها في مأمن من الـ GC. يعمل الـ handler عندك على dev ثم ينكسر على prod لأن أحدهم استدعى ",[17,18,19],"code",{},"fetcher()"," بدلاً من ",[17,22,23],{},"fetcher(page)",".",[10,26,27,30,31,34],{},[17,28,29],{},"ASentinelPagination"," هو جوابي عن هذا كله. بنيتُه في نحو 230 سطراً داخل ملف ",[17,32,33],{},".vue"," واحد، وليس فيه أي ذكاء استثنائي. ما يستحق الكتابة عنه ليس المعمار، بل قائمة المشكلات المحددة التي يتفاداها، والأسطر التي تتفاداها بالضبط. وهذا محور التدوينة.",[10,36,37],{},"سأبني كل شيء حول أربع مشكلات شحنتُ كل واحدة منها مرة على الأقل في محاولات سابقة.",[39,40,42],"h2",{"id":41},"الـ-api-الذي-يكتبه-المستهلك","الـ API الذي يكتبه المستهلك",[10,44,45],{},"قبل أن أدخل في المشكلات، هذا ما يكتبه المستهلك فعلاً:",[47,48,53],"pre",{"className":49,"code":50,"language":51,"meta":52,"style":52},"language-vue shiki shiki-themes github-light github-dark","\u003CASentinelPagination :fetch-handler=\"loadProducts\">\n  \u003Ctemplate #card=\"{ item }\">\n    \u003CProductCard :product=\"item\" \u002F>\n  \u003C\u002Ftemplate>\n  \u003Ctemplate #initialLoading=\"{ count }\">\n    \u003CProductCardSkeleton v-for=\"i in count\" :key=\"i\" \u002F>\n  \u003C\u002Ftemplate>\n  \u003Ctemplate #loadingMore=\"{ count }\">\n    \u003CProductCardSkeleton v-for=\"i in count\" :key=\"i\" \u002F>\n  \u003C\u002Ftemplate>\n  \u003Ctemplate #emptyState>\n    \u003CNoResults \u002F>\n  \u003C\u002Ftemplate>\n\u003C\u002FASentinelPagination>\n","vue","",[17,54,55,89,95,101,107,113,119,124,130,135,140,146,152,157],{"__ignoreMap":52},[56,57,60,64,67,70,74,77,81,84,86],"span",{"class":58,"line":59},"line",1,[56,61,63],{"class":62},"sVt8B","\u003C",[56,65,29],{"class":66},"s9eBZ",[56,68,69],{"class":62}," :",[56,71,73],{"class":72},"sScJk","fetch-handler",[56,75,76],{"class":62},"=",[56,78,80],{"class":79},"sZZnC","\"",[56,82,83],{"class":62},"loadProducts",[56,85,80],{"class":79},[56,87,88],{"class":62},">\n",[56,90,92],{"class":58,"line":91},2,[56,93,94],{"class":62},"  \u003Ctemplate #card=\"{ item }\">\n",[56,96,98],{"class":58,"line":97},3,[56,99,100],{"class":62},"    \u003CProductCard :product=\"item\" \u002F>\n",[56,102,104],{"class":58,"line":103},4,[56,105,106],{"class":62},"  \u003C\u002Ftemplate>\n",[56,108,110],{"class":58,"line":109},5,[56,111,112],{"class":62},"  \u003Ctemplate #initialLoading=\"{ count }\">\n",[56,114,116],{"class":58,"line":115},6,[56,117,118],{"class":62},"    \u003CProductCardSkeleton v-for=\"i in count\" :key=\"i\" \u002F>\n",[56,120,122],{"class":58,"line":121},7,[56,123,106],{"class":62},[56,125,127],{"class":58,"line":126},8,[56,128,129],{"class":62},"  \u003Ctemplate #loadingMore=\"{ count }\">\n",[56,131,133],{"class":58,"line":132},9,[56,134,118],{"class":62},[56,136,138],{"class":58,"line":137},10,[56,139,106],{"class":62},[56,141,143],{"class":58,"line":142},11,[56,144,145],{"class":62},"  \u003Ctemplate #emptyState>\n",[56,147,149],{"class":58,"line":148},12,[56,150,151],{"class":62},"    \u003CNoResults \u002F>\n",[56,153,155],{"class":58,"line":154},13,[56,156,106],{"class":62},[56,158,160,163,165],{"class":58,"line":159},14,[56,161,162],{"class":62},"\u003C\u002F",[56,164,29],{"class":66},[56,166,88],{"class":62},[10,168,169,170,173],{},"أربع slots، وprop واحد يهم فعلاً. الـ handler يرجع ",[17,171,172],{},"{ items: T[], pagination?: { current_page, per_page, total, last_page } }",". الـ pagination اختياري، فإن لم يرجعه الـ backend تُحمَّل القائمة مرة واحدة وتتوقف. سلوك متواضع، لكنه صادق.",[39,175,177,180],{"id":176},"fetchhandlerlength-توزيع-بحسب-الـ-arity",[17,178,179],{},"fetchHandler.length",": توزيع بحسب الـ arity",[10,182,183,184,187],{},"بعض واجهات الـ API يحتاج رقم صفحة، وبعضها لا يحتاجه. بعض الـ endpoints يعمل pagination على الـ server، وبعضها يرجع كل شيء دفعة واحدة. لو فرضتُ على المكوّن شكلاً واحداً، لاضطرّ المستهلكون إلى كتابة closures وسيطة تُجبر الـ fetcher على التوقيع المتوقع. ولو فرضتُ الشكل الآخر، لكتب من لا يهتم بالـ pagination شيئاً مثل ",[17,185,186],{},"(_page) => ..."," في كل مكان مع تجاهل الوسيط.",[10,189,190,191,194],{},"الحل الخاطئ هو إضافة prop باسم ",[17,192,193],{},"mode: 'paged' | 'stateless'",". هذا إعداد لشيء يعبّر عنه توقيع الدالة نفسها.",[10,196,197],{},"الحل الصحيح، كما تبيّن، هو فحص الدالة في وقت التشغيل:",[47,199,203],{"className":200,"code":201,"language":202,"meta":52,"style":52},"language-typescript shiki shiki-themes github-light github-dark","async function runFetchHandler(): Promise\u003CISPPaginationHandlerResult\u003CT>> {\n  if (props.fetchHandler.length >= 1) {\n    const fn = props.fetchHandler as (\n      p: ISPPaginationMeta\n    ) => Promise\u003CISPPaginationHandlerResult\u003CT>>;\n    return await fn(pagination.value);\n  }\n\n  const fn = props.fetchHandler as () => Promise\u003CISPPaginationHandlerResult\u003CT>>;\n  return await fn();\n}\n","typescript",[17,204,205,239,260,280,291,312,325,330,336,366,378],{"__ignoreMap":52},[56,206,207,211,214,217,220,223,226,228,231,233,236],{"class":58,"line":59},[56,208,210],{"class":209},"szBVR","async",[56,212,213],{"class":209}," function",[56,215,216],{"class":72}," runFetchHandler",[56,218,219],{"class":62},"()",[56,221,222],{"class":209},":",[56,224,225],{"class":72}," Promise",[56,227,63],{"class":62},[56,229,230],{"class":72},"ISPPaginationHandlerResult",[56,232,63],{"class":62},[56,234,235],{"class":72},"T",[56,237,238],{"class":62},">> {\n",[56,240,241,244,247,251,254,257],{"class":58,"line":91},[56,242,243],{"class":209},"  if",[56,245,246],{"class":62}," (props.fetchHandler.",[56,248,250],{"class":249},"sj4cs","length",[56,252,253],{"class":209}," >=",[56,255,256],{"class":249}," 1",[56,258,259],{"class":62},") {\n",[56,261,262,265,268,271,274,277],{"class":58,"line":97},[56,263,264],{"class":209},"    const",[56,266,267],{"class":249}," fn",[56,269,270],{"class":209}," =",[56,272,273],{"class":62}," props.fetchHandler ",[56,275,276],{"class":209},"as",[56,278,279],{"class":62}," (\n",[56,281,282,286,288],{"class":58,"line":103},[56,283,285],{"class":284},"s4XuR","      p",[56,287,222],{"class":209},[56,289,290],{"class":72}," ISPPaginationMeta\n",[56,292,293,296,299,301,303,305,307,309],{"class":58,"line":109},[56,294,295],{"class":62},"    ) ",[56,297,298],{"class":209},"=>",[56,300,225],{"class":72},[56,302,63],{"class":62},[56,304,230],{"class":72},[56,306,63],{"class":62},[56,308,235],{"class":72},[56,310,311],{"class":62},">>;\n",[56,313,314,317,320,322],{"class":58,"line":115},[56,315,316],{"class":209},"    return",[56,318,319],{"class":209}," await",[56,321,267],{"class":72},[56,323,324],{"class":62},"(pagination.value);\n",[56,326,327],{"class":58,"line":121},[56,328,329],{"class":62},"  }\n",[56,331,332],{"class":58,"line":126},[56,333,335],{"emptyLinePlaceholder":334},true,"\n",[56,337,338,341,343,345,347,349,352,354,356,358,360,362,364],{"class":58,"line":132},[56,339,340],{"class":209},"  const",[56,342,267],{"class":249},[56,344,270],{"class":209},[56,346,273],{"class":62},[56,348,276],{"class":209},[56,350,351],{"class":62}," () ",[56,353,298],{"class":209},[56,355,225],{"class":72},[56,357,63],{"class":62},[56,359,230],{"class":72},[56,361,63],{"class":62},[56,363,235],{"class":72},[56,365,311],{"class":62},[56,367,368,371,373,375],{"class":58,"line":137},[56,369,370],{"class":209},"  return",[56,372,319],{"class":209},[56,374,267],{"class":72},[56,376,377],{"class":62},"();\n",[56,379,380],{"class":58,"line":142},[56,381,382],{"class":62},"}\n",[10,384,385,386,389,390,393,394,397],{},"الـ ",[17,387,388],{},"Function.prototype.length"," يرجع عدد الوسائط الشكلية التي تُصرِّح بها الدالة. لو كتب المستهلك ",[17,391,392],{},"loadProducts(p)"," فالقيمة 1، ولو كتب ",[17,395,396],{},"loadProducts()"," فالقيمة 0. المكوّن يتفرّع بناءً على ذلك.",[10,399,400],{},"من ناحية المستهلك، كلا الشكلين مقبول:",[47,402,404],{"className":200,"code":403,"language":202,"meta":52,"style":52},"const loadAllProducts = () => $api('\u002Fproducts');                                   \u002F\u002F stateless\nconst loadProductsPage = (p) => $api('\u002Fproducts', { query: { page: p.current_page } }); \u002F\u002F paged\n",[17,405,406,436],{"__ignoreMap":52},[56,407,408,411,414,416,418,420,423,426,429,432],{"class":58,"line":59},[56,409,410],{"class":209},"const",[56,412,413],{"class":72}," loadAllProducts",[56,415,270],{"class":209},[56,417,351],{"class":62},[56,419,298],{"class":209},[56,421,422],{"class":72}," $api",[56,424,425],{"class":62},"(",[56,427,428],{"class":79},"'\u002Fproducts'",[56,430,431],{"class":62},");                                   ",[56,433,435],{"class":434},"sJ8bj","\u002F\u002F stateless\n",[56,437,438,440,443,445,448,450,453,455,457,459,461,464],{"class":58,"line":91},[56,439,410],{"class":209},[56,441,442],{"class":72}," loadProductsPage",[56,444,270],{"class":209},[56,446,447],{"class":62}," (",[56,449,10],{"class":284},[56,451,452],{"class":62},") ",[56,454,298],{"class":209},[56,456,422],{"class":72},[56,458,425],{"class":62},[56,460,428],{"class":79},[56,462,463],{"class":62},", { query: { page: p.current_page } }); ",[56,465,466],{"class":434},"\u002F\u002F paged\n",[10,468,469],{},"الاتحاد في TypeScript على نوع الـ prop يُبقي التوقيعَين متسقَين:",[47,471,473],{"className":200,"code":472,"language":202,"meta":52,"style":52},"export type ISPFetchHandler\u003CT> =\n  | ((pagination: ISPPaginationMeta) => Promise\u003CISPPaginationHandlerResult\u003CT>>)\n  | (() => Promise\u003CISPPaginationHandlerResult\u003CT>>);\n",[17,474,475,496,529],{"__ignoreMap":52},[56,476,477,480,483,486,488,490,493],{"class":58,"line":59},[56,478,479],{"class":209},"export",[56,481,482],{"class":209}," type",[56,484,485],{"class":72}," ISPFetchHandler",[56,487,63],{"class":62},[56,489,235],{"class":72},[56,491,492],{"class":62},"> ",[56,494,495],{"class":209},"=\n",[56,497,498,501,504,507,509,512,514,516,518,520,522,524,526],{"class":58,"line":91},[56,499,500],{"class":209},"  |",[56,502,503],{"class":62}," ((",[56,505,506],{"class":284},"pagination",[56,508,222],{"class":209},[56,510,511],{"class":72}," ISPPaginationMeta",[56,513,452],{"class":62},[56,515,298],{"class":209},[56,517,225],{"class":72},[56,519,63],{"class":62},[56,521,230],{"class":72},[56,523,63],{"class":62},[56,525,235],{"class":72},[56,527,528],{"class":62},">>)\n",[56,530,531,533,536,538,540,542,544,546,548],{"class":58,"line":97},[56,532,500],{"class":209},[56,534,535],{"class":62}," (() ",[56,537,298],{"class":209},[56,539,225],{"class":72},[56,541,63],{"class":62},[56,543,230],{"class":72},[56,545,63],{"class":62},[56,547,235],{"class":72},[56,549,550],{"class":62},">>);\n",[10,552,553,554,557],{},"يبقى فخ واحد جدير بالذكر. الدوال السهمية التي تُصرِّح بوسيط دون أن تستعمله تُحسب أيضاً على أنها arity 1. لو كتب المستهلك ",[17,555,556],{},"(_p) => $api('\u002Fproducts')"," فسيقع على فرع الـ paged. لا مشكلة عملية في هذا لأن الـ handler يتجاهل الوسيط، لكنه من النوع الذي يُدوَّن مرة واحدة كي يجد الشخص التالي إجابة جاهزة لسؤال \"لماذا يُمرَّر pagination أصلاً؟\"",[39,559,561,564],{"id":560},"inflight-بوابة-واحدة-وثلاث-قراءات-مشتقة",[17,562,563],{},"inFlight",": بوابة واحدة، وثلاث قراءات مشتقة",[10,566,567],{},"هذه المشكلة كلاسيكية. يصل المستخدم إلى الأسفل، فينطلق observer ويبدأ fetch. يمرّر بكسلاً واحداً، فينطلق observer مرة أخرى لأن الـ sentinel لا يزال ظاهراً، ويبدأ fetch ثانٍ قبل أن يُحلّ الأول. بحسب التوقيت تحصل على:",[569,570,571,575,586],"ul",{},[572,573,574],"li",{},"كلا الطلبين يرجعان الصفحة 2، فتتكرّر العناصر.",[572,576,577,578,581,582,585],{},"طلب يخرج بـ ",[17,579,580],{},"current_page = 2"," وآخر بـ ",[17,583,584],{},"current_page = 3","، فتتشابك العناصر أو تُتخطّى الصفحة 2 بصمت.",[572,587,588],{},"كلاهما يرجع الصفحة نفسها، فينكسر منطق de-dup عند المستهلك، ويقفز الـ layout.",[10,590,591,592,595],{},"الحل الساذج هو boolean واحد باسم ",[17,593,594],{},"isLoading",". لكنه لا يكفي، لأن لدينا نوعين مختلفين منطقياً من التحميل: الأولي بـ skeleton يملأ الشاشة، والصفحة التالية بـ skeleton صغير في الأسفل. أي boolean-ين قد يختلفان، وحين يختلفان يبدأ منطق البوابة في السؤال \"هل هذا النوع تحديداً جارٍ الآن؟\"، وهو سؤال خاطئ من الأساس.",[10,597,598],{},"السؤال الصحيح هو: \"هل يوجد أي شيء جارٍ الآن؟\" مصدر واحد للحقيقة، وقراءتان مشتقتان منه:",[47,600,602],{"className":200,"code":601,"language":202,"meta":52,"style":52},"const initialLoading = ref(true);\nconst isFetchingMore = ref(false);\nconst inFlight = ref\u003C'init' | 'next' | null>(null);\n",[17,603,604,624,642],{"__ignoreMap":52},[56,605,606,608,611,613,616,618,621],{"class":58,"line":59},[56,607,410],{"class":209},[56,609,610],{"class":249}," initialLoading",[56,612,270],{"class":209},[56,614,615],{"class":72}," ref",[56,617,425],{"class":62},[56,619,620],{"class":249},"true",[56,622,623],{"class":62},");\n",[56,625,626,628,631,633,635,637,640],{"class":58,"line":91},[56,627,410],{"class":209},[56,629,630],{"class":249}," isFetchingMore",[56,632,270],{"class":209},[56,634,615],{"class":72},[56,636,425],{"class":62},[56,638,639],{"class":249},"false",[56,641,623],{"class":62},[56,643,644,646,649,651,653,655,658,661,664,666,669,672,675],{"class":58,"line":97},[56,645,410],{"class":209},[56,647,648],{"class":249}," inFlight",[56,650,270],{"class":209},[56,652,615],{"class":72},[56,654,63],{"class":62},[56,656,657],{"class":79},"'init'",[56,659,660],{"class":209}," |",[56,662,663],{"class":79}," 'next'",[56,665,660],{"class":209},[56,667,668],{"class":249}," null",[56,670,671],{"class":62},">(",[56,673,674],{"class":249},"null",[56,676,623],{"class":62},[10,678,385,679,681],{},[17,680,563],{}," هو البوابة. أما الاثنان الآخران فيتبدّل الـ template عليهما ليحدّد أي skeleton يعرضه. وتُضبط الثلاثة معاً:",[47,683,685],{"className":200,"code":684,"language":202,"meta":52,"style":52},"const fetchData = async (mode: 'init' | 'next') => {\n  if (inFlight.value !== null) return;\n  if (mode === 'next' && (!hasNextPage.value || isFetchingMore.value)) return;\n\n  inFlight.value = mode;\n\n  try {\n    if (mode === 'init') initialLoading.value = true;\n    if (mode === 'next') isFetchingMore.value = true;\n\n    const result = await runFetchHandler();\n    applyPaginationFromHandlerResult(result);\n    items.value = mode === 'init' ? result.items : [...items.value, ...result.items];\n  } finally {\n    if (mode === 'init') initialLoading.value = false;\n    if (mode === 'next') isFetchingMore.value = false;\n    inFlight.value = null;\n  }\n};\n",[17,686,687,720,740,773,777,787,791,798,819,838,842,857,865,901,911,931,950,962,967],{"__ignoreMap":52},[56,688,689,691,694,696,699,701,704,706,709,711,713,715,717],{"class":58,"line":59},[56,690,410],{"class":209},[56,692,693],{"class":72}," fetchData",[56,695,270],{"class":209},[56,697,698],{"class":209}," async",[56,700,447],{"class":62},[56,702,703],{"class":284},"mode",[56,705,222],{"class":209},[56,707,708],{"class":79}," 'init'",[56,710,660],{"class":209},[56,712,663],{"class":79},[56,714,452],{"class":62},[56,716,298],{"class":209},[56,718,719],{"class":62}," {\n",[56,721,722,724,727,730,732,734,737],{"class":58,"line":91},[56,723,243],{"class":209},[56,725,726],{"class":62}," (inFlight.value ",[56,728,729],{"class":209},"!==",[56,731,668],{"class":249},[56,733,452],{"class":62},[56,735,736],{"class":209},"return",[56,738,739],{"class":62},";\n",[56,741,742,744,747,750,752,755,757,760,763,766,769,771],{"class":58,"line":97},[56,743,243],{"class":209},[56,745,746],{"class":62}," (mode ",[56,748,749],{"class":209},"===",[56,751,663],{"class":79},[56,753,754],{"class":209}," &&",[56,756,447],{"class":62},[56,758,759],{"class":209},"!",[56,761,762],{"class":62},"hasNextPage.value ",[56,764,765],{"class":209},"||",[56,767,768],{"class":62}," isFetchingMore.value)) ",[56,770,736],{"class":209},[56,772,739],{"class":62},[56,774,775],{"class":58,"line":103},[56,776,335],{"emptyLinePlaceholder":334},[56,778,779,782,784],{"class":58,"line":109},[56,780,781],{"class":62},"  inFlight.value ",[56,783,76],{"class":209},[56,785,786],{"class":62}," mode;\n",[56,788,789],{"class":58,"line":115},[56,790,335],{"emptyLinePlaceholder":334},[56,792,793,796],{"class":58,"line":121},[56,794,795],{"class":209},"  try",[56,797,719],{"class":62},[56,799,800,803,805,807,809,812,814,817],{"class":58,"line":126},[56,801,802],{"class":209},"    if",[56,804,746],{"class":62},[56,806,749],{"class":209},[56,808,708],{"class":79},[56,810,811],{"class":62},") initialLoading.value ",[56,813,76],{"class":209},[56,815,816],{"class":249}," true",[56,818,739],{"class":62},[56,820,821,823,825,827,829,832,834,836],{"class":58,"line":132},[56,822,802],{"class":209},[56,824,746],{"class":62},[56,826,749],{"class":209},[56,828,663],{"class":79},[56,830,831],{"class":62},") isFetchingMore.value ",[56,833,76],{"class":209},[56,835,816],{"class":249},[56,837,739],{"class":62},[56,839,840],{"class":58,"line":137},[56,841,335],{"emptyLinePlaceholder":334},[56,843,844,846,849,851,853,855],{"class":58,"line":142},[56,845,264],{"class":209},[56,847,848],{"class":249}," result",[56,850,270],{"class":209},[56,852,319],{"class":209},[56,854,216],{"class":72},[56,856,377],{"class":62},[56,858,859,862],{"class":58,"line":148},[56,860,861],{"class":72},"    applyPaginationFromHandlerResult",[56,863,864],{"class":62},"(result);\n",[56,866,867,870,872,875,877,879,882,885,887,890,893,896,898],{"class":58,"line":154},[56,868,869],{"class":62},"    items.value ",[56,871,76],{"class":209},[56,873,874],{"class":62}," mode ",[56,876,749],{"class":209},[56,878,708],{"class":79},[56,880,881],{"class":209}," ?",[56,883,884],{"class":62}," result.items ",[56,886,222],{"class":209},[56,888,889],{"class":62}," [",[56,891,892],{"class":209},"...",[56,894,895],{"class":62},"items.value, ",[56,897,892],{"class":209},[56,899,900],{"class":62},"result.items];\n",[56,902,903,906,909],{"class":58,"line":159},[56,904,905],{"class":62},"  } ",[56,907,908],{"class":209},"finally",[56,910,719],{"class":62},[56,912,914,916,918,920,922,924,926,929],{"class":58,"line":913},15,[56,915,802],{"class":209},[56,917,746],{"class":62},[56,919,749],{"class":209},[56,921,708],{"class":79},[56,923,811],{"class":62},[56,925,76],{"class":209},[56,927,928],{"class":249}," false",[56,930,739],{"class":62},[56,932,934,936,938,940,942,944,946,948],{"class":58,"line":933},16,[56,935,802],{"class":209},[56,937,746],{"class":62},[56,939,749],{"class":209},[56,941,663],{"class":79},[56,943,831],{"class":62},[56,945,76],{"class":209},[56,947,928],{"class":249},[56,949,739],{"class":62},[56,951,953,956,958,960],{"class":58,"line":952},17,[56,954,955],{"class":62},"    inFlight.value ",[56,957,76],{"class":209},[56,959,668],{"class":249},[56,961,739],{"class":62},[56,963,965],{"class":58,"line":964},18,[56,966,329],{"class":62},[56,968,970],{"class":58,"line":969},19,[56,971,972],{"class":62},"};\n",[10,974,975,976,979,980,983,984,986,987,990],{},"قاعدتان صغيرتان تُبقيان هذا مستقيماً. الـ early return يفحص ",[17,977,978],{},"inFlight.value !== null"," لا أي boolean بعينه، فإن كان init جارياً وأُطلق trigger للصفحة التالية، يُرفض الـ fetch الثاني حتى لو كان ",[17,981,982],{},"isFetchingMore"," لا يزال false. والإفراج عن البوابة يقع في ",[17,985,908],{}," لا في ",[17,988,989],{},"try","، فحين يرمي fetch استثناءً تُحرَّر البوابة أيضاً، ولا يُقفل المكوّن نفسه بصمت في وضع \"أرفض تحميل أي شيء\" بعد أول تعثّر شبكي.",[992,993,995],"h3",{"id":994},"الـ-sentinel-وعتبة-الـ-06","الـ sentinel، وعتبة الـ 0.6",[10,997,998,999,222],{},"الـ sentinel نفسه عبارة عن div بارتفاع 1px مع ",[17,1000,1001],{},"aria-hidden",[47,1003,1005],{"className":49,"code":1004,"language":51,"meta":52,"style":52},"\u003Cdiv ref=\"sentinelRef\" class=\"h-1 min-w-[1px] w-full\" aria-hidden=\"true\" \u002F>\n",[17,1006,1007],{"__ignoreMap":52},[56,1008,1009,1011,1014,1016,1018,1021,1024,1026,1029,1032,1034,1037],{"class":58,"line":59},[56,1010,63],{"class":62},[56,1012,1013],{"class":66},"div",[56,1015,615],{"class":72},[56,1017,76],{"class":62},[56,1019,1020],{"class":79},"\"sentinelRef\"",[56,1022,1023],{"class":72}," class",[56,1025,76],{"class":62},[56,1027,1028],{"class":79},"\"h-1 min-w-[1px] w-full\"",[56,1030,1031],{"class":72}," aria-hidden",[56,1033,76],{"class":62},[56,1035,1036],{"class":79},"\"true\"",[56,1038,1039],{"class":62}," \u002F>\n",[10,1041,1042],{},"وفي الـ observer ضمانتان أحبّ أن أعزلهما:",[47,1044,1046],{"className":200,"code":1045,"language":202,"meta":52,"style":52},"observer = new IntersectionObserver(\n  (entries) => {\n    const entry = entries[0];\n    if (!entry?.isIntersecting) return;\n    if (!initialLoading.value && !isFetchingMore.value && hasNextPage.value) {\n      void fetchNextPage();\n    }\n  },\n  { root: null, rootMargin: '0px', threshold: 0.6 }\n);\n",[17,1047,1048,1064,1078,1096,1111,1136,1146,1151,1156,1178],{"__ignoreMap":52},[56,1049,1050,1053,1055,1058,1061],{"class":58,"line":59},[56,1051,1052],{"class":62},"observer ",[56,1054,76],{"class":209},[56,1056,1057],{"class":209}," new",[56,1059,1060],{"class":72}," IntersectionObserver",[56,1062,1063],{"class":62},"(\n",[56,1065,1066,1069,1072,1074,1076],{"class":58,"line":91},[56,1067,1068],{"class":62},"  (",[56,1070,1071],{"class":284},"entries",[56,1073,452],{"class":62},[56,1075,298],{"class":209},[56,1077,719],{"class":62},[56,1079,1080,1082,1085,1087,1090,1093],{"class":58,"line":97},[56,1081,264],{"class":209},[56,1083,1084],{"class":249}," entry",[56,1086,270],{"class":209},[56,1088,1089],{"class":62}," entries[",[56,1091,1092],{"class":249},"0",[56,1094,1095],{"class":62},"];\n",[56,1097,1098,1100,1102,1104,1107,1109],{"class":58,"line":103},[56,1099,802],{"class":209},[56,1101,447],{"class":62},[56,1103,759],{"class":209},[56,1105,1106],{"class":62},"entry?.isIntersecting) ",[56,1108,736],{"class":209},[56,1110,739],{"class":62},[56,1112,1113,1115,1117,1119,1122,1125,1128,1131,1133],{"class":58,"line":109},[56,1114,802],{"class":209},[56,1116,447],{"class":62},[56,1118,759],{"class":209},[56,1120,1121],{"class":62},"initialLoading.value ",[56,1123,1124],{"class":209},"&&",[56,1126,1127],{"class":209}," !",[56,1129,1130],{"class":62},"isFetchingMore.value ",[56,1132,1124],{"class":209},[56,1134,1135],{"class":62}," hasNextPage.value) {\n",[56,1137,1138,1141,1144],{"class":58,"line":115},[56,1139,1140],{"class":209},"      void",[56,1142,1143],{"class":72}," fetchNextPage",[56,1145,377],{"class":62},[56,1147,1148],{"class":58,"line":121},[56,1149,1150],{"class":62},"    }\n",[56,1152,1153],{"class":58,"line":126},[56,1154,1155],{"class":62},"  },\n",[56,1157,1158,1161,1163,1166,1169,1172,1175],{"class":58,"line":132},[56,1159,1160],{"class":62},"  { root: ",[56,1162,674],{"class":249},[56,1164,1165],{"class":62},", rootMargin: ",[56,1167,1168],{"class":79},"'0px'",[56,1170,1171],{"class":62},", threshold: ",[56,1173,1174],{"class":249},"0.6",[56,1176,1177],{"class":62}," }\n",[56,1179,1180],{"class":58,"line":137},[56,1181,623],{"class":62},[10,1183,1184,1187,1188,1190,1191,1194,1195,24],{},[17,1185,1186],{},"threshold: 0.6"," بدل ",[17,1189,1092],{},". الـ sentinel بارتفاع 1px، وعند ",[17,1192,1193],{},"threshold: 0"," يكفي أن يهتز بكسلاً واحداً داخل الـ viewport وخارجه أثناء التمرير حتى يُطلق الـ callback عدة مرات. عند 0.6 يصبح التقاطع أكثر معنى، فـ 60% من هدف بحجم 1px ليست كبيرة لكنها مستقرة أمام هذا الاهتزاز. وفحص الحالة داخل الـ callback يعيد تأكيد البوابة، لأنه بين لحظة إطلاق التقاطع ولحظة تشغيل الـ callback ربما بدأ تحميل آخر. الحارس ثلاثي الشروط هو خط الدفاع الثاني الـ idempotent خلف ",[17,1196,563],{},[39,1198,1200],{"id":1199},"إعادة-الضبط-عند-تغيّر-هوية-الـ-fetcher","إعادة الضبط عند تغيّر هوية الـ fetcher",[10,1202,1203,1204,1207],{},"يضغط المستخدم على \"Category: Shoes\". يمرّر المكوّن الأب ",[17,1205,1206],{},"fetchHandler"," جديداً يحمل الفلتر الجديد. ما ينبغي أن يحدث: مسح القائمة، جلب الصفحة الأولى بالـ handler الجديد، وتركيب observer جديد.",[10,1209,1210],{},"أما إن لم تتعامل مع الموقف، فتبقى القائمة القديمة على الشاشة، وتُلحَق الصفحة الثانية للفلتر الجديد بالصفحة الأولى للفلتر القديم. يرى المستخدم مزيجاً من الأحذية وعناصر لا علاقة لها، وتخرج طلبات \"الصفحة 3\" مقابل handler بينما تُفسَّر مقابل حالة pagination تخص handler آخر.",[10,1212,1213],{},"المُشغِّل هو watch على الـ handler نفسه:",[47,1215,1217],{"className":200,"code":1216,"language":202,"meta":52,"style":52},"watch(\n  () => props.fetchHandler,\n  async () => {\n    observer?.disconnect();\n\n    items.value = [];\n    hasNextPage.value = false;\n    pagination.value = {\n      current_page: 1,\n      per_page: pagination.value.per_page,\n      total: 0,\n      last_page: 1,\n    };\n\n    await fetchData('init');\n    setupObserver();\n  }\n);\n",[17,1218,1219,1226,1236,1247,1257,1261,1270,1281,1290,1301,1306,1315,1324,1329,1333,1346,1353,1357],{"__ignoreMap":52},[56,1220,1221,1224],{"class":58,"line":59},[56,1222,1223],{"class":72},"watch",[56,1225,1063],{"class":62},[56,1227,1228,1231,1233],{"class":58,"line":91},[56,1229,1230],{"class":62},"  () ",[56,1232,298],{"class":209},[56,1234,1235],{"class":62}," props.fetchHandler,\n",[56,1237,1238,1241,1243,1245],{"class":58,"line":97},[56,1239,1240],{"class":209},"  async",[56,1242,351],{"class":62},[56,1244,298],{"class":209},[56,1246,719],{"class":62},[56,1248,1249,1252,1255],{"class":58,"line":103},[56,1250,1251],{"class":62},"    observer?.",[56,1253,1254],{"class":72},"disconnect",[56,1256,377],{"class":62},[56,1258,1259],{"class":58,"line":109},[56,1260,335],{"emptyLinePlaceholder":334},[56,1262,1263,1265,1267],{"class":58,"line":115},[56,1264,869],{"class":62},[56,1266,76],{"class":209},[56,1268,1269],{"class":62}," [];\n",[56,1271,1272,1275,1277,1279],{"class":58,"line":121},[56,1273,1274],{"class":62},"    hasNextPage.value ",[56,1276,76],{"class":209},[56,1278,928],{"class":249},[56,1280,739],{"class":62},[56,1282,1283,1286,1288],{"class":58,"line":126},[56,1284,1285],{"class":62},"    pagination.value ",[56,1287,76],{"class":209},[56,1289,719],{"class":62},[56,1291,1292,1295,1298],{"class":58,"line":132},[56,1293,1294],{"class":62},"      current_page: ",[56,1296,1297],{"class":249},"1",[56,1299,1300],{"class":62},",\n",[56,1302,1303],{"class":58,"line":137},[56,1304,1305],{"class":62},"      per_page: pagination.value.per_page,\n",[56,1307,1308,1311,1313],{"class":58,"line":142},[56,1309,1310],{"class":62},"      total: ",[56,1312,1092],{"class":249},[56,1314,1300],{"class":62},[56,1316,1317,1320,1322],{"class":58,"line":148},[56,1318,1319],{"class":62},"      last_page: ",[56,1321,1297],{"class":249},[56,1323,1300],{"class":62},[56,1325,1326],{"class":58,"line":154},[56,1327,1328],{"class":62},"    };\n",[56,1330,1331],{"class":58,"line":159},[56,1332,335],{"emptyLinePlaceholder":334},[56,1334,1335,1338,1340,1342,1344],{"class":58,"line":913},[56,1336,1337],{"class":209},"    await",[56,1339,693],{"class":72},[56,1341,425],{"class":62},[56,1343,657],{"class":79},[56,1345,623],{"class":62},[56,1347,1348,1351],{"class":58,"line":933},[56,1349,1350],{"class":72},"    setupObserver",[56,1352,377],{"class":62},[56,1354,1355],{"class":58,"line":952},[56,1356,329],{"class":62},[56,1358,1359],{"class":58,"line":964},[56,1360,623],{"class":62},[10,1362,1363],{},"تحدث أربعة أشياء بالترتيب، وكل واحدة منها تحمل وزناً حقيقياً.",[10,1365,1366,1367,1370,1371,1374],{},"الفصل أولاً. لو لم أفصل الـ observer القديم لظلّ حياً أثناء إعادة الضبط، والـ sentinel لا يزال يتقاطع مع الـ viewport لأنه لا شيء تمرّر. حينها يستطيع الـ observer القديم إطلاق ",[17,1368,1369],{},"fetchNextPage()"," مقابل حالة pagination قديمة في منتصف إعادة الضبط، فيخرج طلب بـ ",[17,1372,1373],{},"current_page: 2"," مقابل handler يظن نفسه يرجع الصفحة الأولى. هذا الطلب خاطئ جوهرياً، ويجب أن يُمنع لا أن يُصحَّح.",[10,1376,1377,1378,1381],{},"بعدها أمسح items والـ pagination. أُبقي على ",[17,1379,1380],{},"per_page"," لأنها في الغالب تفضيل مستخدم لا ينبغي للفلتر أن يعبث به. أما الحقول الثلاثة الأخرى فتعود إلى قيمها الافتراضية.",[10,1383,1384,1385,1388],{},"ثم أُطلق ",[17,1386,1387],{},"fetchData('init')",". يظهر الـ skeleton، ويعمل الـ fetch بالـ handler الجديد، وتصل العناصر.",[10,1390,1391],{},"وأخيراً أُعيد ربط الـ observer. عقدة الـ DOM نفسها لم تتغيّر، لكنني أربط نسخة observer جديدة بحالة ما بعد إعادة الضبط.",[10,1393,1394,1395,1398,1399,1402,1403,1406],{},"ثمة تفصيلة دقيقة تستحق التدوين هنا. الـ watch يجري على ",[17,1396,1397],{},"props.fetchHandler"," بهوية المرجع. فإن مرّر المستهلك ",[17,1400,1401],{},"() => $api('\u002Fproducts', { query: { category } })"," بشكل inline داخل الـ template، تُنشأ دالة جديدة في كل re-render للأب، وينطلق الـ watch في كل re-render. غالباً هذا ليس ما تريده. على المستهلكين أن ينضبطوا: memoize الـ handler عبر ",[17,1404,1405],{},"computed","، أو أخذه من composable هويته مستقرة، أو القبول بأن القائمة ستعيد الجلب مع كل render للأب.",[10,1408,1409,1410,1413],{},"فكّرتُ في أن أُضيف prop مستقلاً باسم ",[17,1411,1412],{},"key"," لإطلاق إعادة الضبط صراحةً بدلاً من الاعتماد على هوية المرجع، لكن هذا يدفع بالتعقيد إلى كل مستهلك. عقد watch القائم على هوية المرجع أبسط بكثير إن كنت مستعداً لتوضيح فكرة هوية الدالة للفريق مرة واحدة.",[39,1415,1417],{"id":1416},"تنظيف-الـ-observer-والتسريب-الذي-يمنعه","تنظيف الـ observer، والتسريب الذي يمنعه",[10,1419,1420,1421,1423,1424,1427,1428,1431,1432,1434],{},"هذه هي المشكلة الهادئة. ينتقل المستخدم من مسار كان يحتوي على ",[17,1422,29],{},". تُفرِّغ Vue المكوّن. لكن الـ IntersectionObserver يبقى حياً إن لم تنظّفه، محتفظاً بمرجع لعقدة الـ sentinel في الـ DOM، وبـ closure الـ callback الذي التقط نطاق ",[17,1425,1426],{},"\u003Cscript setup>"," كاملاً: ",[17,1429,1430],{},"items"," و",[17,1433,506],{}," وكل الـ refs ومثيل المكوّن نفسه.",[10,1436,1437],{},"لا شيء من هذا يذهب إلى الـ GC حتى يُفلته الـ observer. وعلى مسار فيه حركة كثيفة دخولاً وخروجاً، يتراكم التسريب. لاحظتُ واحداً كهذا في تطبيق سابق بعد أن أبلغ مستخدم بأن الصفحة تصير أبطأ كلما طال استخدامه لها. جلسته كانت تُجمِّع حالة عشرات المكوّنات الميتة.",[10,1439,1440],{},"الحل استدعاء واحد:",[47,1442,1444],{"className":200,"code":1443,"language":202,"meta":52,"style":52},"onBeforeUnmount(() => {\n  observer?.disconnect();\n  observer = null;\n});\n",[17,1445,1446,1458,1467,1478],{"__ignoreMap":52},[56,1447,1448,1451,1454,1456],{"class":58,"line":59},[56,1449,1450],{"class":72},"onBeforeUnmount",[56,1452,1453],{"class":62},"(() ",[56,1455,298],{"class":209},[56,1457,719],{"class":62},[56,1459,1460,1463,1465],{"class":58,"line":91},[56,1461,1462],{"class":62},"  observer?.",[56,1464,1254],{"class":72},[56,1466,377],{"class":62},[56,1468,1469,1472,1474,1476],{"class":58,"line":97},[56,1470,1471],{"class":62},"  observer ",[56,1473,76],{"class":209},[56,1475,668],{"class":249},[56,1477,739],{"class":62},[56,1479,1480],{"class":58,"line":103},[56,1481,1482],{"class":62},"});\n",[10,1484,1485,1486,1489,1490,1489,1493,1489,1496,1489,1499,1502,1503,1505,1506,1509],{},"يسهل كتابته، ويسهل نسيانه بنفس القدر. ما يمسك هذا في code review هو قائمة تحقق ذهنية: كل ",[17,1487,1488],{},"new IntersectionObserver","، ",[17,1491,1492],{},"new MutationObserver",[17,1494,1495],{},"addEventListener",[17,1497,1498],{},"setInterval",[17,1500,1501],{},"requestAnimationFrame"," يستدعي teardown مقابلاً في ",[17,1504,1450],{}," أو ",[17,1507,1508],{},"onUnmounted",". إن لم تستطع الإشارة إلى الـ teardown، فأنت تُسرّب. في كل مرة.",[992,1511,1513,1516],{"id":1512},"v-scroll-reveal-observer-لكل-بطاقة",[17,1514,1515],{},"v-scroll-reveal",": observer لكل بطاقة",[10,1518,1519],{},"توجيه scroll-reveal الذي يعمل على مستوى كل بطاقة داخل المكوّن نفسه له نسخته الخاصة من القصة:",[47,1521,1523],{"className":200,"code":1522,"language":202,"meta":52,"style":52},"const vScrollReveal = {\n  mounted(el: HTMLElement) {\n    \u002F\u002F ... setup observer, stash on el._scrollObserver\n    observer.observe(el);\n  },\n  unmounted(el: HTMLElement) {\n    const observer = (el as HTMLElement & { _scrollObserver?: IntersectionObserver })._scrollObserver;\n    observer?.disconnect();\n  },\n} as const;\n",[17,1524,1525,1536,1553,1558,1569,1573,1588,1621,1629,1633],{"__ignoreMap":52},[56,1526,1527,1529,1532,1534],{"class":58,"line":59},[56,1528,410],{"class":209},[56,1530,1531],{"class":249}," vScrollReveal",[56,1533,270],{"class":209},[56,1535,719],{"class":62},[56,1537,1538,1541,1543,1546,1548,1551],{"class":58,"line":91},[56,1539,1540],{"class":72},"  mounted",[56,1542,425],{"class":62},[56,1544,1545],{"class":284},"el",[56,1547,222],{"class":209},[56,1549,1550],{"class":72}," HTMLElement",[56,1552,259],{"class":62},[56,1554,1555],{"class":58,"line":97},[56,1556,1557],{"class":434},"    \u002F\u002F ... setup observer, stash on el._scrollObserver\n",[56,1559,1560,1563,1566],{"class":58,"line":103},[56,1561,1562],{"class":62},"    observer.",[56,1564,1565],{"class":72},"observe",[56,1567,1568],{"class":62},"(el);\n",[56,1570,1571],{"class":58,"line":109},[56,1572,1155],{"class":62},[56,1574,1575,1578,1580,1582,1584,1586],{"class":58,"line":115},[56,1576,1577],{"class":72},"  unmounted",[56,1579,425],{"class":62},[56,1581,1545],{"class":284},[56,1583,222],{"class":209},[56,1585,1550],{"class":72},[56,1587,259],{"class":62},[56,1589,1590,1592,1595,1597,1600,1602,1604,1607,1610,1613,1616,1618],{"class":58,"line":121},[56,1591,264],{"class":209},[56,1593,1594],{"class":249}," observer",[56,1596,270],{"class":209},[56,1598,1599],{"class":62}," (el ",[56,1601,276],{"class":209},[56,1603,1550],{"class":72},[56,1605,1606],{"class":209}," &",[56,1608,1609],{"class":62}," { ",[56,1611,1612],{"class":284},"_scrollObserver",[56,1614,1615],{"class":209},"?:",[56,1617,1060],{"class":72},[56,1619,1620],{"class":62}," })._scrollObserver;\n",[56,1622,1623,1625,1627],{"class":58,"line":126},[56,1624,1251],{"class":62},[56,1626,1254],{"class":72},[56,1628,377],{"class":62},[56,1630,1631],{"class":58,"line":132},[56,1632,1155],{"class":62},[56,1634,1635,1638,1640,1643],{"class":58,"line":137},[56,1636,1637],{"class":62},"} ",[56,1639,276],{"class":209},[56,1641,1642],{"class":209}," const",[56,1644,739],{"class":62},[10,1646,1647,1648,1651,1652,1655,1656,1659,1660,1662],{},"يخبّئ التوجيه observer الخاص به على العنصر نفسه في ",[17,1649,1650],{},"el._scrollObserver"," كي يجده ",[17,1653,1654],{},"unmounted"," لاحقاً. يوجد أيضاً استدعاء ",[17,1657,1658],{},"observer.unobserve(el)"," داخل callback الـ mount. متى ظهرت البطاقة بالـ fade، لا يبقى شيء يستحق المراقبة، وإبقاء الـ observer مرتبطاً يعني عملاً إضافياً على كل حدث تمرير بلا عائد. أما ",[17,1661,1654],{}," فهو احتياط مضاعف للبطاقات التي أُفرِّغت قبل أن تُرى أصلاً، سواء لأن المستخدم مرّ عليها بسرعة، أو غادر المسار، أو أزالها فلتر.",[10,1664,1665,1666,1668,1669,1672],{},"الـ type assertion على ",[17,1667,1612],{}," أقبح مما أرغب. مفتاح ",[17,1670,1671],{},"Symbol"," كان سيوفّر الـ cast، لكنه سيكسر أيضاً استعراضه في DevTools. اخترتُ النسخة التي أستطيع قراءتها فعلاً.",[39,1674,1676,1677],{"id":1675},"type-guards-من-دون-zod","Type guards من دون ",[17,1678,1679],{},"zod",[10,1681,1682],{},"يبقى شيئان خارج إطار المشكلات الأربع، لكنهما استحقّا مكانهما داخل المكوّن.",[10,1684,1685],{},"أولاً، الـ type guards لبيانات الـ pagination الوصفية:",[47,1687,1689],{"className":200,"code":1688,"language":202,"meta":52,"style":52},"function isRecord(value: unknown): value is Record\u003Cstring, unknown> {\n  return typeof value === 'object' && value !== null;\n}\nfunction isNumber(value: unknown): value is number {\n  return typeof value === 'number' && Number.isFinite(value);\n}\nfunction isPaginationMeta(value: unknown): value is ISPPaginationMeta {\n  if (!isRecord(value)) return false;\n  return (\n    isNumber(value.current_page) &&\n    isNumber(value.per_page) &&\n    isNumber(value.total) &&\n    isNumber(value.last_page)\n  );\n}\n",[17,1690,1691,1737,1762,1766,1794,1818,1822,1849,1869,1875,1886,1895,1904,1911,1916],{"__ignoreMap":52},[56,1692,1693,1696,1699,1701,1704,1706,1709,1712,1714,1717,1720,1723,1725,1728,1731,1734],{"class":58,"line":59},[56,1694,1695],{"class":209},"function",[56,1697,1698],{"class":72}," isRecord",[56,1700,425],{"class":62},[56,1702,1703],{"class":284},"value",[56,1705,222],{"class":209},[56,1707,1708],{"class":249}," unknown",[56,1710,1711],{"class":62},")",[56,1713,222],{"class":209},[56,1715,1716],{"class":284}," value",[56,1718,1719],{"class":209}," is",[56,1721,1722],{"class":72}," Record",[56,1724,63],{"class":62},[56,1726,1727],{"class":249},"string",[56,1729,1730],{"class":62},", ",[56,1732,1733],{"class":249},"unknown",[56,1735,1736],{"class":62},"> {\n",[56,1738,1739,1741,1744,1747,1749,1752,1754,1756,1758,1760],{"class":58,"line":91},[56,1740,370],{"class":209},[56,1742,1743],{"class":209}," typeof",[56,1745,1746],{"class":62}," value ",[56,1748,749],{"class":209},[56,1750,1751],{"class":79}," 'object'",[56,1753,754],{"class":209},[56,1755,1746],{"class":62},[56,1757,729],{"class":209},[56,1759,668],{"class":249},[56,1761,739],{"class":62},[56,1763,1764],{"class":58,"line":97},[56,1765,382],{"class":62},[56,1767,1768,1770,1773,1775,1777,1779,1781,1783,1785,1787,1789,1792],{"class":58,"line":103},[56,1769,1695],{"class":209},[56,1771,1772],{"class":72}," isNumber",[56,1774,425],{"class":62},[56,1776,1703],{"class":284},[56,1778,222],{"class":209},[56,1780,1708],{"class":249},[56,1782,1711],{"class":62},[56,1784,222],{"class":209},[56,1786,1716],{"class":284},[56,1788,1719],{"class":209},[56,1790,1791],{"class":249}," number",[56,1793,719],{"class":62},[56,1795,1796,1798,1800,1802,1804,1807,1809,1812,1815],{"class":58,"line":109},[56,1797,370],{"class":209},[56,1799,1743],{"class":209},[56,1801,1746],{"class":62},[56,1803,749],{"class":209},[56,1805,1806],{"class":79}," 'number'",[56,1808,754],{"class":209},[56,1810,1811],{"class":62}," Number.",[56,1813,1814],{"class":72},"isFinite",[56,1816,1817],{"class":62},"(value);\n",[56,1819,1820],{"class":58,"line":115},[56,1821,382],{"class":62},[56,1823,1824,1826,1829,1831,1833,1835,1837,1839,1841,1843,1845,1847],{"class":58,"line":121},[56,1825,1695],{"class":209},[56,1827,1828],{"class":72}," isPaginationMeta",[56,1830,425],{"class":62},[56,1832,1703],{"class":284},[56,1834,222],{"class":209},[56,1836,1708],{"class":249},[56,1838,1711],{"class":62},[56,1840,222],{"class":209},[56,1842,1716],{"class":284},[56,1844,1719],{"class":209},[56,1846,511],{"class":72},[56,1848,719],{"class":62},[56,1850,1851,1853,1855,1857,1860,1863,1865,1867],{"class":58,"line":126},[56,1852,243],{"class":209},[56,1854,447],{"class":62},[56,1856,759],{"class":209},[56,1858,1859],{"class":72},"isRecord",[56,1861,1862],{"class":62},"(value)) ",[56,1864,736],{"class":209},[56,1866,928],{"class":249},[56,1868,739],{"class":62},[56,1870,1871,1873],{"class":58,"line":132},[56,1872,370],{"class":209},[56,1874,279],{"class":62},[56,1876,1877,1880,1883],{"class":58,"line":137},[56,1878,1879],{"class":72},"    isNumber",[56,1881,1882],{"class":62},"(value.current_page) ",[56,1884,1885],{"class":209},"&&\n",[56,1887,1888,1890,1893],{"class":58,"line":142},[56,1889,1879],{"class":72},[56,1891,1892],{"class":62},"(value.per_page) ",[56,1894,1885],{"class":209},[56,1896,1897,1899,1902],{"class":58,"line":148},[56,1898,1879],{"class":72},[56,1900,1901],{"class":62},"(value.total) ",[56,1903,1885],{"class":209},[56,1905,1906,1908],{"class":58,"line":154},[56,1907,1879],{"class":72},[56,1909,1910],{"class":62},"(value.last_page)\n",[56,1912,1913],{"class":58,"line":159},[56,1914,1915],{"class":62},"  );\n",[56,1917,1918],{"class":58,"line":913},[56,1919,382],{"class":62},[10,1921,1922,1923,1925,1926,1929,1930,1933,1934,1431,1937,1940,1941,1944],{},"لا ",[17,1924,1679],{},"، ولا ",[17,1927,1928],{},"yup",". ثلاث دوال، وثلاثون سطراً، دون إضافة أي اعتمادية جديدة إلى مكوّن UI. القضية ليست الأداء، بل أن أي مكوّن UI يعتمد على مكتبة validator يُسرِّب هذا الاختيار إلى كل مستهلك. الـ guards المكتوبة يدوياً تُبقي رسم الاعتماديات نظيفاً، ولهذا الشكل من الفحص (أربعة حقول كلها أرقام) فهي أقصر من أي schema. فحص ",[17,1931,1932],{},"Number.isFinite"," مهم هنا. من دونه يجتاز ",[17,1935,1936],{},"NaN",[17,1938,1939],{},"Infinity"," فحص ",[17,1942,1943],{},"typeof === 'number'","، فتنتهي بسلوك \"الصفحة 3 من Infinity\" في مراحل لاحقة.",[10,1946,1947,1948,1951,1952,1955],{},"ثانياً، الـ ",[17,1949,1950],{},"void fetchNextPage()"," داخل callback الـ observer صغير ومقصود. الـ callback ليس async، فالانتظار بـ await لا يفعل شيئاً. والـ floating promises تُوقع أغلب الـ linters. أما ",[17,1953,1954],{},"void"," فهو إشارة صادقة: \"أعرف أن هذا async، وقصدي ألا أنتظره، وقد فكّرت في الأمر.\" كلمة واحدة، والفرق بينها وبين تعليق suppress هو الفرق بين lint نظيف وآخر مقيّد.",[39,1957,1959],{"id":1958},"ملخّص-الدفاعات-الأربع","ملخّص الدفاعات الأربع",[10,1961,1962,1963,1965],{},"على نحو 230 سطراً، هذا المكوّن من النوع الذي تُعيد كتابته في بعد ظهر واحد. ما يجعله يعمل ليس أي قطعة منفصلة (توزيع الـ arity، ",[17,1964,563],{}," بوصفه حالة discriminated، تنظيف الـ observer، إعادة الضبط بهوية المرجع، الـ type guards المكتوبة يدوياً). كل واحدة منها صغيرة لدرجة أنك لو سمعتها منفصلة لهززتَ رأسك وتابعتَ.",[10,1967,1968],{},"ما جعل هذا المكوّن يتوقف عن كونه مصنعاً للمشكلات هو أنني رأيتُ أنماط الفشل الأربعة وكتبتُ حارساً محدداً لكل واحدة منها. لا مكتبة ولا تجريد، بل حفنة من الدفاعات الصغيرة، دفعتُ ثمن كل واحدة منها بمشكلة شحنتُها في نسخة سابقة. هذا في الغالب هو ما يعنيه \"التقسية\" عملياً: ليست ذكاءً، بل ذاكرة أطول.",[1970,1971,1972],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":52,"searchDepth":91,"depth":91,"links":1974},[1975,1976,1978,1982,1983,1987,1989],{"id":41,"depth":91,"text":42},{"id":176,"depth":91,"text":1977},"fetchHandler.length: توزيع بحسب الـ arity",{"id":560,"depth":91,"text":1979,"children":1980},"inFlight: بوابة واحدة، وثلاث قراءات مشتقة",[1981],{"id":994,"depth":97,"text":995},{"id":1199,"depth":91,"text":1200},{"id":1416,"depth":91,"text":1417,"children":1984},[1985],{"id":1512,"depth":97,"text":1986},"v-scroll-reveal: observer لكل بطاقة",{"id":1675,"depth":91,"text":1988},"Type guards من دون zod",{"id":1958,"depth":91,"text":1959},"2026-02-28","أربع مشكلات تظهر عاجلاً أم آجلاً في أيّ قائمة infinite-scroll، والأسطر المحدَّدة داخل مكوّن Vue من 230 سطراً تُعالج كلاً منها.",null,false,"md",[1996,1997,1998,1999,2000,2001,2002,2003,2004],"infinite scroll","Vue 3","IntersectionObserver","AbortController","Pagination","arity dispatch","memory leak","TypeScript","Nuxt","ar",{},"\u002Fwrites-ar\u002Finside-asentinel-pagination",{"title":5,"description":1991},"writes-ar\u002Finside-asentinel-pagination","VTqiNOPf4SYFIKakGRY0kuC2OO6tUjqtUZOBX36xYQk",[2012,3782],{"id":2013,"title":2014,"body":2015,"date":3768,"description":3769,"description_ar":1992,"draft":1993,"extension":1994,"image":1992,"keywords":3770,"lang":2005,"meta":3777,"navigation":334,"path":3778,"seo":3779,"stem":3780,"title_ar":1992,"updatedAt":1992,"__hash__":3781},"writesAr\u002Fwrites-ar\u002Finside-a-skeleton-self-generating-skeleton-loaders.md","داخل @alikhalilll\u002Fa-skeleton، مُحمِّلات skeleton تولّد نفسها بنفسها",{"type":7,"value":2016,"toc":3757},[2017,2020,2027,2030,2034,2037,2043,2086,2091,2122,2150,2156,2170,2173,2177,2184,2205,2212,2223,2226,2266,2269,2356,2383,2389,2393,2400,2445,2470,2476,2479,2483,2493,2883,2920,2923,2991,2994,3111,3114,3137,3140,3144,3147,3153,3188,3207,3227,3236,3240,3288,3291,3298,3374,3387,3391,3433,3440,3451,3540,3547,3654,3676,3679,3683,3702,3720,3727,3731,3742,3748,3751,3754],[10,2018,2019],{},"مُحمِّلات الـ skeleton في النهاية واجهة مكرّرة. تبني بطاقة، ثم تبني بطاقة أخرى من مستطيلات رمادية تشبهها، وكلما عدّلت البطاقة الحقيقية نسيتَ أن تعدّل الـ placeholder معها. بعد ثلاثة أسابيع تجد العنوان محاذيًا لليسار في المكوّن الفعلي وموسّطًا في الـ skeleton، ولا ينتبه أحد حتى يمرّ مصمّم بالصفحة على بيئة الـ staging.",[10,2021,2022,2023,2026],{},"بنيتُ ",[17,2024,2025],{},"@alikhalilll\u002Fa-skeleton"," على فرضية مختلفة تمامًا: الـ skeleton يُشتقّ من المكوّن الحقيقي نفسه. تلفّ بطاقتك مرة واحدة فقط، وتتكفّل المكتبة باستنتاج الـ placeholder من قراءة البطاقة. لا قالب موازٍ. لا مستطيلات رمادية تلاحقها بالتحديث. أيّ تعديل تجريه على البطاقة يلتقطه الـ skeleton في العرض التالي دون تدخّل منك.",[10,2028,2029],{},"ما أتحدّث عنه هنا هو ما يقع تحت السطح: عقد التأليف الذي يجعل هذا الأمر ممكنًا، واستراتيجيات العرض الثلاث وما تدفعه كلٌّ منها من مقايضات، وخط أنابيب الالتقاط خلف وضع clone تحديدًا. هذا الأخير هو ما سأحمله معي إلى أيّ مشكلة مستقبلية من نوع \"أريد نسخة من شكل هذا الشيء الآن\".",[39,2031,2033],{"id":2032},"العقد-أبقِ-الوسوم-غير-مشروطة-وقيّد-المحتوى-وحده","العقد: أبقِ الوسوم غير مشروطة، وقيّد المحتوى وحده",[10,2035,2036],{},"قبل أيّ شيء، هناك قاعدة واحدة. هي الأساس الذي تستند إليه كل استراتيجية أذكرها لاحقًا، ولذلك أضعها في الصدارة.",[10,2038,2039],{},[2040,2041,2042],"strong",{},"خطأ:",[47,2044,2046],{"className":49,"code":2045,"language":51,"meta":52,"style":52},"\u003Cdiv v-if=\"user\">\n  \u003Ch3>{{ user.name }}\u003C\u002Fh3>\n  \u003Cp>{{ user.bio }}\u003C\u002Fp>\n\u003C\u002Fdiv>\n",[17,2047,2048,2068,2073,2078],{"__ignoreMap":52},[56,2049,2050,2052,2054,2057,2059,2061,2064,2066],{"class":58,"line":59},[56,2051,63],{"class":62},[56,2053,1013],{"class":66},[56,2055,2056],{"class":209}," v-if",[56,2058,76],{"class":62},[56,2060,80],{"class":79},[56,2062,2063],{"class":62},"user",[56,2065,80],{"class":79},[56,2067,88],{"class":62},[56,2069,2070],{"class":58,"line":91},[56,2071,2072],{"class":62},"  \u003Ch3>{{ user.name }}\u003C\u002Fh3>\n",[56,2074,2075],{"class":58,"line":97},[56,2076,2077],{"class":62},"  \u003Cp>{{ user.bio }}\u003C\u002Fp>\n",[56,2079,2080,2082,2084],{"class":58,"line":103},[56,2081,162],{"class":62},[56,2083,1013],{"class":66},[56,2085,88],{"class":62},[10,2087,2088],{},[2040,2089,2090],{},"صحيح:",[47,2092,2094],{"className":49,"code":2093,"language":51,"meta":52,"style":52},"\u003Cdiv>\n  \u003Ch3>{{ user?.name }}\u003C\u002Fh3>\n  \u003Cp>{{ user?.bio }}\u003C\u002Fp>\n\u003C\u002Fdiv>\n",[17,2095,2096,2104,2109,2114],{"__ignoreMap":52},[56,2097,2098,2100,2102],{"class":58,"line":59},[56,2099,63],{"class":62},[56,2101,1013],{"class":66},[56,2103,88],{"class":62},[56,2105,2106],{"class":58,"line":91},[56,2107,2108],{"class":62},"  \u003Ch3>{{ user?.name }}\u003C\u002Fh3>\n",[56,2110,2111],{"class":58,"line":97},[56,2112,2113],{"class":62},"  \u003Cp>{{ user?.bio }}\u003C\u002Fp>\n",[56,2115,2116,2118,2120],{"class":58,"line":103},[56,2117,162],{"class":62},[56,2119,1013],{"class":66},[56,2121,88],{"class":62},[10,2123,2124,2125,2128,2129,2132,2133,2135,2136,2139,2140,1730,2143,1730,2146,2149],{},"الـ walker، سواء كان يقرأ الـ DOM الحيّ أو شجرة الـ vnode، يحتاج أن يرى الشكل ذاته أثناء التحميل وبعد وصول البيانات. لو غلّفتَ العنوان بـ ",[17,2126,2127],{},"v-if=\"user\"","، فسيرى الـ walker عقدة تعليق أثناء التحميل ثم ",[17,2130,2131],{},"\u003Ch3>"," بعد ذلك، ولن يستطيع أن يتنبّأ بهندسة الـ placeholder. لكن مع استخدام الاستيفاء الاختياري (optional chaining) يبقى ",[17,2134,2131],{}," موجودًا دائمًا؛ يكون نصّه فارغًا أثناء التحميل، ويصنّف الـ walker الوسوم الحاملة للنص الفارغة (",[17,2137,2138],{},"\u003Ch1>","–",[17,2141,2142],{},"\u003Ch6>",[17,2144,2145],{},"\u003Cp>",[17,2147,2148],{},"\u003Cspan>",", وغيرها) على أنها أشرطة نص shimmer بارتفاع العنوان الطبيعي.",[10,2151,2152,2153,2155],{},"الوسوم هي المخطط. المحتوى هو الحمولة. تستطيع أن تُبدّل الحمولة بـ ",[17,2154,674],{}," دون أن يلاحظ المخطط شيئًا. هذا الانضباط البسيط وحده هو ما يفتح الباب لكل ما يأتي بعده.",[10,2157,2158,2159,2162,2163,2165,2166,2169],{},"النصف الآخر من العقد هو مخرج الطوارئ ",[17,2160,2161],{},"data-skeleton-ignore",". لو كان جزء من المكوّن مجرد زخرفة بصرية (خلفية تدرّج، شارة علامة تجارية ثابتة، لمسة SVG) ولا ترغب باستبداله بكتلة رمادية، ضع عليه ",[17,2164,2161],{}," فيتجاهله الـ walker تمامًا؛ ويُعرض العنصر الحقيقي كما هو في الحالتين. أما ",[17,2167,2168],{},"data-skeleton-stop"," فيقول للـ walker: \"لا تدخل داخل هذا العنصر؛ اعرضه ككتلة واحدة\". أستخدمه عادةً مع الـ widgets الذرّية حين تكون البنية الداخلية مجرد ضجيج بصري.",[10,2171,2172],{},"قاعدتان اثنتان وسِمَتان اثنتان. هذا هو كل ما يلزمك تعلّمه من سطح التأليف.",[39,2174,2176],{"id":2175},"وضع-clone-التقاط-الرسم-النهائي-وإعادة-عرض-البكسلات","وضع Clone: التقاط الرسم النهائي وإعادة عرض البكسلات",[10,2178,2179,2180,2183],{},"Clone هو الوضع الافتراضي، وهو أهمّ ما في المكتبة برأيي. أغلب مكتبات الـ skeleton تتعثّر لأنها تحاول التفكير على مستوى الـ CSS cascade. تنظر إلى أسماء الفئات. تنظر إلى الأنماط المضمّنة. تفترض قيمة الـ border radius بناءً على بادئة ",[17,2181,2182],{},"rounded-*",". باختصار، تخمّن.",[10,2185,2186,2187,2190,2191,2194,2195,2198,2199,2201,2202,2204],{},"وضع clone يتخلّى عن التخمين كليًا. يركّب الـ slot خارج الشاشة داخل capture host مضبوط على ",[17,2188,2189],{},"visibility: hidden","، ويترك المتصفح ينهي كامل الـ layout وحلّ الأنماط، ثم يقرأ ",[2040,2192,2193],{},"النتيجة النهائية المحسوبة"," لكل عنصر عبر ",[17,2196,2197],{},"getComputedStyle()",". مهما كان نظام التنسيق لديك (أدوات Tailwind، أنماط scoped بأسماء فئات مُجزّأة، CSS-in-JS وقت التشغيل، رموز DaisyUI الدلالية، أو حتى سمات ",[17,2200,1970],{}," مضمّنة يمرّرها مكوّن أب)، فبحلول اللحظة التي يعمل فيها ",[17,2203,2197],{}," تكون كلّها قد انهارت إلى إجابة قانونية واحدة لكل خاصية. يأخذ المحرك تلك الإجابة الجاهزة ويعيد عرضها.",[10,2206,2207,2208,2211],{},"السطح الذي يلتقطه المحرك شامل فعلًا. حدود مستقلة لكل حافة (فبطاقة لها ",[17,2209,2210],{},"border-bottom"," مختلف تُقرأ كما ينبغي). أنصاف أقطار مستقلة لكل زاوية (فشكل pill غير المتماثل لا يتراجع إلى مجرد مستطيل مدوّر). لون الخلفية وصورتها. Box-shadow. Opacity. Filter. Transform. Typography. Mix-blend-mode. كل عقدة ملتقطة تحمل حمولة أنماط مضمّنة مجمّدة خاصة بها؛ حلقة العرض لا تخصّص شيئًا لأيّ عقدة، بل تقرأ ما هو مجهّز مسبقًا.",[10,2213,2214,2215,2218,2219,2222],{},"النص هو أكثر ما يخطئ فيه الآخرون، وأكثر ما تصيبه ",[17,2216,2217],{},"a-skeleton"," بدقّة. بدلًا من تخمين نقاط الالتفاف (\"لنفترض أن النص يلتف كل N حرفًا، ولنفترض أن السطر الأخير عرضه 70%\")، يستخدم المحرك ",[17,2220,2221],{},"Range.getClientRects()"," ليلتقط المستطيل الفعلي المعروض لكل سطر نصّي بدقّة: left, top, width, height. عنوان موسّط من ثلاثة أسطر على سطح المكتب يصبح عنوانًا موسّطًا من سطرين على الجوّال، وتتقلّص المستطيلات الملتقطة معه. مواضع السطر الأخير في اتجاه RTL، والنص المضبوط (justified)، وفواصل الأسطر اليدوية، كلّها تُعاد بنسبة 1:1 لأنها قِيست فعلًا ولم تُخمَّن.",[10,2224,2225],{},"واجهة الـ wrapper لكل هذا هي prop واحدة فقط:",[47,2227,2229],{"className":49,"code":2228,"language":51,"meta":52,"style":52},"\u003CASkeleton :loading=\"loading\">\n  \u003CSomeRichComponent \u002F>\n\u003C\u002FASkeleton>\n",[17,2230,2231,2253,2258],{"__ignoreMap":52},[56,2232,2233,2235,2238,2240,2243,2245,2247,2249,2251],{"class":58,"line":59},[56,2234,63],{"class":62},[56,2236,2237],{"class":66},"ASkeleton",[56,2239,69],{"class":62},[56,2241,2242],{"class":72},"loading",[56,2244,76],{"class":62},[56,2246,80],{"class":79},[56,2248,2242],{"class":62},[56,2250,80],{"class":79},[56,2252,88],{"class":62},[56,2254,2255],{"class":58,"line":91},[56,2256,2257],{"class":62},"  \u003CSomeRichComponent \u002F>\n",[56,2259,2260,2262,2264],{"class":58,"line":97},[56,2261,162],{"class":62},[56,2263,2237],{"class":66},[56,2265,88],{"class":62},[10,2267,2268],{},"ولو أردتَ بناء تدفّق التقاط وإعادة عرض خاص بك، فالدالة الأساسية معرّضة كتصدير عام:",[47,2270,2274],{"className":2271,"code":2272,"language":2273,"meta":52,"style":52},"language-ts shiki shiki-themes github-light github-dark","import { captureSnapshot, type CaptureSnapshot } from '@alikhalilll\u002Fa-skeleton';\n\nconst snap: CaptureSnapshot = captureSnapshot(myRootEl, {\n  maxDepth: 12,\n  maxNodes: 800,\n  minSize: 4,\n});\n","ts",[17,2275,2276,2298,2302,2322,2332,2342,2352],{"__ignoreMap":52},[56,2277,2278,2281,2284,2287,2290,2293,2296],{"class":58,"line":59},[56,2279,2280],{"class":209},"import",[56,2282,2283],{"class":62}," { captureSnapshot, ",[56,2285,2286],{"class":209},"type",[56,2288,2289],{"class":62}," CaptureSnapshot } ",[56,2291,2292],{"class":209},"from",[56,2294,2295],{"class":79}," '@alikhalilll\u002Fa-skeleton'",[56,2297,739],{"class":62},[56,2299,2300],{"class":58,"line":91},[56,2301,335],{"emptyLinePlaceholder":334},[56,2303,2304,2306,2309,2311,2314,2316,2319],{"class":58,"line":97},[56,2305,410],{"class":209},[56,2307,2308],{"class":249}," snap",[56,2310,222],{"class":209},[56,2312,2313],{"class":72}," CaptureSnapshot",[56,2315,270],{"class":209},[56,2317,2318],{"class":72}," captureSnapshot",[56,2320,2321],{"class":62},"(myRootEl, {\n",[56,2323,2324,2327,2330],{"class":58,"line":103},[56,2325,2326],{"class":62},"  maxDepth: ",[56,2328,2329],{"class":249},"12",[56,2331,1300],{"class":62},[56,2333,2334,2337,2340],{"class":58,"line":109},[56,2335,2336],{"class":62},"  maxNodes: ",[56,2338,2339],{"class":249},"800",[56,2341,1300],{"class":62},[56,2343,2344,2347,2350],{"class":58,"line":115},[56,2345,2346],{"class":62},"  minSize: ",[56,2348,2349],{"class":249},"4",[56,2351,1300],{"class":62},[56,2353,2354],{"class":58,"line":121},[56,2355,1482],{"class":62},[10,2357,2358,1431,2361,1431,2364,2367,2368,2371,2372,2375,2376,2379,2380,2382],{},[17,2359,2360],{},"maxDepth",[17,2362,2363],{},"maxNodes",[17,2365,2366],{},"minSize"," هي حدود الميزانية. بفضلها لا يتوقف الخيط الرئيسي عند جدول من 5000 صفّ. عندما يبلغ الـ walk الحدّ يتوقّف، ويضبط ",[17,2369,2370],{},"truncated: true","، ثم يسجّل الـ wrapper ",[17,2373,2374],{},"console.warn"," لمرة واحدة لكل ",[17,2377,2378],{},"cacheKey"," كي تنتبه للأمر أثناء التطوير لا في الإنتاج. أما ",[17,2381,2366],{}," فيُسقط أيّ شيء أصغر من أربعة بكسلات CSS على أيّ محور، وهو ما يحذف الخيوط الرفيعة ونقاط الفراغ التي كانت ستظهر لولا ذلك كقصاصات ملوّنة داخل الـ skeleton.",[10,2384,2385,2386,2388],{},"المقايضة التي يدفعها وضع clone هي أنه يعمل على العميل فقط. تحتاج إلى DOM حقيقي، وإلى ",[17,2387,2197],{},"، وإلى متصفح يقرأ الرسم النهائي. على الخادم يُعرض markup الـ slot الحقيقي كالمعتاد، ولا يبدأ الالتقاط وإعادة العرض إلا بعد الـ hydration. لو احتجتَ إلى placeholder جاهز من الخادم قبل انتهاء الـ hydration، فذلك بالضبط ما وُجد وضع mirror من أجله.",[39,2390,2392],{"id":2391},"وضع-mirror-امشِ-على-الـ-vnodes-وسلّمها-إلى-الخادم","وضع Mirror: امشِ على الـ vnodes وسلّمها إلى الخادم",[10,2394,2395,2396,2399],{},"Mirror يبدّل المحرك من أساسه. بدلًا من تركيب الـ slot وقراءة الـ DOM، يمشي على شجرة الـ vnode مباشرةً وقت العرض. لا وصول إلى ",[17,2397,2398],{},"window",". متوافق مع الـ hydration. قابل للعرض من الخادم.",[47,2401,2403],{"className":49,"code":2402,"language":51,"meta":52,"style":52},"\u003CASkeleton mode=\"mirror\" :loading=\"loading\">\n  \u003CSomeRichComponent \u002F>\n\u003C\u002FASkeleton>\n",[17,2404,2405,2433,2437],{"__ignoreMap":52},[56,2406,2407,2409,2411,2414,2416,2419,2421,2423,2425,2427,2429,2431],{"class":58,"line":59},[56,2408,63],{"class":62},[56,2410,2237],{"class":66},[56,2412,2413],{"class":72}," mode",[56,2415,76],{"class":62},[56,2417,2418],{"class":79},"\"mirror\"",[56,2420,69],{"class":62},[56,2422,2242],{"class":72},[56,2424,76],{"class":62},[56,2426,80],{"class":79},[56,2428,2242],{"class":62},[56,2430,80],{"class":79},[56,2432,88],{"class":62},[56,2434,2435],{"class":58,"line":91},[56,2436,2257],{"class":62},[56,2438,2439,2441,2443],{"class":58,"line":97},[56,2440,162],{"class":62},[56,2442,2237],{"class":66},[56,2444,88],{"class":62},[10,2446,2447,2448,2451,2452,2455,2456,1730,2459,1730,2462,2465,2466,2469],{},"يحافظ الـ walker على وسم كل عنصر وعلى فئته الأصلية. الأوراق الحاملة للنص تتحوّل إلى ",[17,2449,2450],{},"\u003Cspan class=\"a-skel-text-content\">"," (نص شفاف فوق خلفية skeleton) مع ",[17,2453,2454],{},"box-decoration-break: clone"," كي يلتفّ الـ shimmer حول عرض النص المعروض بدقّة سطرًا سطرًا. أما الوسوم الذرّية والتفاعلية (",[17,2457,2458],{},"\u003Cimg>",[17,2460,2461],{},"\u003Cbutton>",[17,2463,2464],{},"\u003Csvg>",") فتصبح ",[17,2467,2468],{},"\u003Cdiv class=\"a-skel-block\">"," بأبعاد مستمدّة من فئتها.",[10,2471,2472,2473,2475],{},"مكسب Mirror الأساسي هو دعم الـ SSR. ومقايضته هي الدقّة. لأن الـ walker لا يملك ",[17,2474,2197],{},"، فهو لا يرى إلا ما هو مؤلَّف صراحةً: أسماء الفئات الثابتة والأنماط المضمّنة. لو كانت هوية مكوّنك تأتي من قاعدة CSS-in-JS مُجزّأة أو من كتلة نمط scoped، فلن يلتقطها الـ mirror كما يفعل clone. لكن في معظم التخطيطات تكفي الفئة الثابتة، وأمان الـ SSR يستحق أكثر من آخر 5% من التطابق البكسلي. أما المكوّنات المعقّدة بصريًا فمكانها الطبيعي هو clone.",[10,2477,2478],{},"كلا الوضعين يتشاركان خط أنابيب الحركة نفسه، والـ cache نفسه، وقاعدة الـ a11y نفسها. التبديل بينهما لا يكلّفك أكثر من prop واحدة.",[39,2480,2482],{"id":2481},"وضع-structural-أشجار-في-الـ-flow-الطبيعي-محفوظة-بين-الجلسات","وضع Structural: أشجار في الـ flow الطبيعي، محفوظة بين الجلسات",[10,2484,2485,2486,2489,2490,24],{},"الاستراتيجية الثالثة ألجأ إليها حين لا يكون الـ wrapper هو وحدة التنسيق المناسبة لديّ: حين أريد لشكل ملتقط أن يعيش عبر عدة نُسخ، أو أن يستمرّ بين الجلسات، أو أن يعيد التدفّق مع العنصر الأب عند تغيير حجم الـ viewport بدل تثبيته على إحداثيات مطلقة. لتلك الحالة أستخدم ",[17,2487,2488],{},"useSkeleton()"," مع ",[17,2491,2492],{},"\u003CASkeletonLayer>",[47,2494,2496],{"className":49,"code":2495,"language":51,"meta":52,"style":52},"\u003Cscript setup lang=\"ts\">\nimport { computed, ref } from 'vue';\nimport { useSkeleton } from '@alikhalilll\u002Fa-skeleton';\n\nconst props = defineProps\u003C{ userId: string }>();\nconst user = ref(null);\nconst loading = computed(() => user.value === null);\nconst containerRef = ref\u003CHTMLElement | null>(null);\n\nconst { shape, clear } = useSkeleton({\n  cacheKey: `user-card:${props.userId}`,\n  target: () => (loading.value ? null : containerRef.value),\n  persist: true,\n});\n\nfetchUser(props.userId).then((u) => (user.value = u));\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv ref=\"containerRef\">\n    \u003CASkeletonLayer v-if=\"loading && shape\" :shape=\"shape\" \u002F>\n    \u003CColdStartFallback v-else-if=\"loading\" \u002F>\n    \u003CUserCard v-else :data=\"user\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[17,2497,2498,2518,2532,2545,2549,2575,2592,2617,2643,2647,2672,2692,2715,2724,2728,2732,2761,2769,2773,2782,2799,2825,2843,2864,2874],{"__ignoreMap":52},[56,2499,2500,2502,2505,2508,2511,2513,2516],{"class":58,"line":59},[56,2501,63],{"class":62},[56,2503,2504],{"class":66},"script",[56,2506,2507],{"class":72}," setup",[56,2509,2510],{"class":72}," lang",[56,2512,76],{"class":62},[56,2514,2515],{"class":79},"\"ts\"",[56,2517,88],{"class":62},[56,2519,2520,2522,2525,2527,2530],{"class":58,"line":91},[56,2521,2280],{"class":209},[56,2523,2524],{"class":62}," { computed, ref } ",[56,2526,2292],{"class":209},[56,2528,2529],{"class":79}," 'vue'",[56,2531,739],{"class":62},[56,2533,2534,2536,2539,2541,2543],{"class":58,"line":97},[56,2535,2280],{"class":209},[56,2537,2538],{"class":62}," { useSkeleton } ",[56,2540,2292],{"class":209},[56,2542,2295],{"class":79},[56,2544,739],{"class":62},[56,2546,2547],{"class":58,"line":103},[56,2548,335],{"emptyLinePlaceholder":334},[56,2550,2551,2553,2556,2558,2561,2564,2567,2569,2572],{"class":58,"line":109},[56,2552,410],{"class":209},[56,2554,2555],{"class":249}," props",[56,2557,270],{"class":209},[56,2559,2560],{"class":72}," defineProps",[56,2562,2563],{"class":62},"\u003C{ ",[56,2565,2566],{"class":284},"userId",[56,2568,222],{"class":209},[56,2570,2571],{"class":249}," string",[56,2573,2574],{"class":62}," }>();\n",[56,2576,2577,2579,2582,2584,2586,2588,2590],{"class":58,"line":115},[56,2578,410],{"class":209},[56,2580,2581],{"class":249}," user",[56,2583,270],{"class":209},[56,2585,615],{"class":72},[56,2587,425],{"class":62},[56,2589,674],{"class":249},[56,2591,623],{"class":62},[56,2593,2594,2596,2599,2601,2604,2606,2608,2611,2613,2615],{"class":58,"line":121},[56,2595,410],{"class":209},[56,2597,2598],{"class":249}," loading",[56,2600,270],{"class":209},[56,2602,2603],{"class":72}," computed",[56,2605,1453],{"class":62},[56,2607,298],{"class":209},[56,2609,2610],{"class":62}," user.value ",[56,2612,749],{"class":209},[56,2614,668],{"class":249},[56,2616,623],{"class":62},[56,2618,2619,2621,2624,2626,2628,2630,2633,2635,2637,2639,2641],{"class":58,"line":126},[56,2620,410],{"class":209},[56,2622,2623],{"class":249}," containerRef",[56,2625,270],{"class":209},[56,2627,615],{"class":72},[56,2629,63],{"class":62},[56,2631,2632],{"class":72},"HTMLElement",[56,2634,660],{"class":209},[56,2636,668],{"class":249},[56,2638,671],{"class":62},[56,2640,674],{"class":249},[56,2642,623],{"class":62},[56,2644,2645],{"class":58,"line":132},[56,2646,335],{"emptyLinePlaceholder":334},[56,2648,2649,2651,2653,2656,2658,2661,2664,2666,2669],{"class":58,"line":137},[56,2650,410],{"class":209},[56,2652,1609],{"class":62},[56,2654,2655],{"class":249},"shape",[56,2657,1730],{"class":62},[56,2659,2660],{"class":249},"clear",[56,2662,2663],{"class":62}," } ",[56,2665,76],{"class":209},[56,2667,2668],{"class":72}," useSkeleton",[56,2670,2671],{"class":62},"({\n",[56,2673,2674,2677,2680,2683,2685,2687,2690],{"class":58,"line":142},[56,2675,2676],{"class":62},"  cacheKey: ",[56,2678,2679],{"class":79},"`user-card:${",[56,2681,2682],{"class":62},"props",[56,2684,24],{"class":79},[56,2686,2566],{"class":62},[56,2688,2689],{"class":79},"}`",[56,2691,1300],{"class":62},[56,2693,2694,2697,2700,2702,2705,2708,2710,2712],{"class":58,"line":148},[56,2695,2696],{"class":72},"  target",[56,2698,2699],{"class":62},": () ",[56,2701,298],{"class":209},[56,2703,2704],{"class":62}," (loading.value ",[56,2706,2707],{"class":209},"?",[56,2709,668],{"class":249},[56,2711,69],{"class":209},[56,2713,2714],{"class":62}," containerRef.value),\n",[56,2716,2717,2720,2722],{"class":58,"line":154},[56,2718,2719],{"class":62},"  persist: ",[56,2721,620],{"class":249},[56,2723,1300],{"class":62},[56,2725,2726],{"class":58,"line":159},[56,2727,1482],{"class":62},[56,2729,2730],{"class":58,"line":913},[56,2731,335],{"emptyLinePlaceholder":334},[56,2733,2734,2737,2740,2743,2746,2749,2751,2753,2756,2758],{"class":58,"line":933},[56,2735,2736],{"class":72},"fetchUser",[56,2738,2739],{"class":62},"(props.userId).",[56,2741,2742],{"class":72},"then",[56,2744,2745],{"class":62},"((",[56,2747,2748],{"class":284},"u",[56,2750,452],{"class":62},[56,2752,298],{"class":209},[56,2754,2755],{"class":62}," (user.value ",[56,2757,76],{"class":209},[56,2759,2760],{"class":62}," u));\n",[56,2762,2763,2765,2767],{"class":58,"line":952},[56,2764,162],{"class":62},[56,2766,2504],{"class":66},[56,2768,88],{"class":62},[56,2770,2771],{"class":58,"line":964},[56,2772,335],{"emptyLinePlaceholder":334},[56,2774,2775,2777,2780],{"class":58,"line":969},[56,2776,63],{"class":62},[56,2778,2779],{"class":66},"template",[56,2781,88],{"class":62},[56,2783,2785,2788,2790,2792,2794,2797],{"class":58,"line":2784},20,[56,2786,2787],{"class":62},"  \u003C",[56,2789,1013],{"class":66},[56,2791,615],{"class":72},[56,2793,76],{"class":62},[56,2795,2796],{"class":79},"\"containerRef\"",[56,2798,88],{"class":62},[56,2800,2802,2805,2808,2810,2812,2815,2818,2820,2823],{"class":58,"line":2801},21,[56,2803,2804],{"class":62},"    \u003C",[56,2806,2807],{"class":66},"ASkeletonLayer",[56,2809,2056],{"class":72},[56,2811,76],{"class":62},[56,2813,2814],{"class":79},"\"loading && shape\"",[56,2816,2817],{"class":72}," :shape",[56,2819,76],{"class":62},[56,2821,2822],{"class":79},"\"shape\"",[56,2824,1039],{"class":62},[56,2826,2828,2830,2833,2836,2838,2841],{"class":58,"line":2827},22,[56,2829,2804],{"class":62},[56,2831,2832],{"class":66},"ColdStartFallback",[56,2834,2835],{"class":72}," v-else-if",[56,2837,76],{"class":62},[56,2839,2840],{"class":79},"\"loading\"",[56,2842,1039],{"class":62},[56,2844,2846,2848,2851,2854,2857,2859,2862],{"class":58,"line":2845},23,[56,2847,2804],{"class":62},[56,2849,2850],{"class":66},"UserCard",[56,2852,2853],{"class":72}," v-else",[56,2855,2856],{"class":72}," :data",[56,2858,76],{"class":62},[56,2860,2861],{"class":79},"\"user\"",[56,2863,1039],{"class":62},[56,2865,2867,2870,2872],{"class":58,"line":2866},24,[56,2868,2869],{"class":62},"  \u003C\u002F",[56,2871,1013],{"class":66},[56,2873,88],{"class":62},[56,2875,2877,2879,2881],{"class":58,"line":2876},25,[56,2878,162],{"class":62},[56,2880,2779],{"class":66},[56,2882,88],{"class":62},[10,2884,2885,2886,2889,2890,1730,2893,1730,2896,1730,2899,1730,2902,1730,2905,2908,2909,2911,2912,2915,2916,2919],{},"يلتقط ",[17,2887,2888],{},"walkStructural()"," الشكل، وهو walker شجريّ يحافظ على وسوم الحاويات وفئاتها الأصلية وعلى CSS الـ layout المحلول (",[17,2891,2892],{},"display",[17,2894,2895],{},"flex-*",[17,2897,2898],{},"gap",[17,2900,2901],{},"padding",[17,2903,2904],{},"grid-*",[17,2906,2907],{},"box-sizing","). أما الأوراق فتحمل عرضًا وارتفاعًا مضمّنَين إضافةً إلى الإشارات البصرية (background, border, radius). عندما يعيد ",[17,2910,2492],{}," عرض الشجرة، فإنه يفعل ذلك في ",[2040,2913,2914],{},"الـ flow الطبيعي",": الـ ",[17,2917,2918],{},"\u003Cdiv class=\"flex flex-col gap-4 p-4\">"," الملتقط يُعرض كحاوية flex فعلية، وكتل الـ shimmer تجلس داخلها كأبناء flex. تغيّر حجم الـ viewport فيعيد الـ layout التدفّق، ولا يتأخّر الـ skeleton خلفه كما يحدث في إعادة عرض clone ذات المواضع المطلقة.",[10,2921,2922],{},"فلو كان لديك هذا الـ markup الفعلي:",[47,2924,2928],{"className":2925,"code":2926,"language":2927,"meta":52,"style":52},"language-html shiki shiki-themes github-light github-dark","\u003Cdiv class=\"flex flex-col gap-4 p-4\">\n  \u003Ch3>...\u003C\u002Fh3>\n  \u003Cp>...\u003C\u002Fp>\n  \u003Cbutton>...\u003C\u002Fbutton>\n\u003C\u002Fdiv>\n","html",[17,2929,2930,2945,2958,2970,2983],{"__ignoreMap":52},[56,2931,2932,2934,2936,2938,2940,2943],{"class":58,"line":59},[56,2933,63],{"class":62},[56,2935,1013],{"class":66},[56,2937,1023],{"class":72},[56,2939,76],{"class":62},[56,2941,2942],{"class":79},"\"flex flex-col gap-4 p-4\"",[56,2944,88],{"class":62},[56,2946,2947,2949,2951,2954,2956],{"class":58,"line":91},[56,2948,2787],{"class":62},[56,2950,992],{"class":66},[56,2952,2953],{"class":62},">...\u003C\u002F",[56,2955,992],{"class":66},[56,2957,88],{"class":62},[56,2959,2960,2962,2964,2966,2968],{"class":58,"line":97},[56,2961,2787],{"class":62},[56,2963,10],{"class":66},[56,2965,2953],{"class":62},[56,2967,10],{"class":66},[56,2969,88],{"class":62},[56,2971,2972,2974,2977,2979,2981],{"class":58,"line":103},[56,2973,2787],{"class":62},[56,2975,2976],{"class":66},"button",[56,2978,2953],{"class":62},[56,2980,2976],{"class":66},[56,2982,88],{"class":62},[56,2984,2985,2987,2989],{"class":58,"line":109},[56,2986,162],{"class":62},[56,2988,1013],{"class":66},[56,2990,88],{"class":62},[10,2992,2993],{},"فإن الـ layer يعيد عرضه هكذا:",[47,2995,2997],{"className":2925,"code":2996,"language":2927,"meta":52,"style":52},"\u003Cdiv\n  class=\"flex flex-col gap-4 p-4\"\n  style=\"display: flex; flex-direction: column; gap: 16px; padding: 16px; box-sizing: border-box\"\n>\n  \u003Cdiv class=\"a-skel\" style=\"width: 200px; height: 24px; ...\" \u002F>\n  \u003Cdiv class=\"a-skel\" style=\"width: 280px; height: 16px; ...\" \u002F>\n  \u003Cdiv class=\"a-skel\" style=\"width: 120px; height: 36px; ...\" \u002F>\n\u003C\u002Fdiv>\n",[17,2998,2999,3006,3016,3026,3030,3057,3080,3103],{"__ignoreMap":52},[56,3000,3001,3003],{"class":58,"line":59},[56,3002,63],{"class":62},[56,3004,3005],{"class":66},"div\n",[56,3007,3008,3011,3013],{"class":58,"line":91},[56,3009,3010],{"class":72},"  class",[56,3012,76],{"class":62},[56,3014,3015],{"class":79},"\"flex flex-col gap-4 p-4\"\n",[56,3017,3018,3021,3023],{"class":58,"line":97},[56,3019,3020],{"class":72},"  style",[56,3022,76],{"class":62},[56,3024,3025],{"class":79},"\"display: flex; flex-direction: column; gap: 16px; padding: 16px; box-sizing: border-box\"\n",[56,3027,3028],{"class":58,"line":103},[56,3029,88],{"class":62},[56,3031,3032,3034,3036,3038,3040,3043,3046,3048,3051,3055],{"class":58,"line":109},[56,3033,2787],{"class":62},[56,3035,1013],{"class":66},[56,3037,1023],{"class":72},[56,3039,76],{"class":62},[56,3041,3042],{"class":79},"\"a-skel\"",[56,3044,3045],{"class":72}," style",[56,3047,76],{"class":62},[56,3049,3050],{"class":79},"\"width: 200px; height: 24px; ...\"",[56,3052,3054],{"class":3053},"s7hpK"," \u002F",[56,3056,88],{"class":62},[56,3058,3059,3061,3063,3065,3067,3069,3071,3073,3076,3078],{"class":58,"line":115},[56,3060,2787],{"class":62},[56,3062,1013],{"class":66},[56,3064,1023],{"class":72},[56,3066,76],{"class":62},[56,3068,3042],{"class":79},[56,3070,3045],{"class":72},[56,3072,76],{"class":62},[56,3074,3075],{"class":79},"\"width: 280px; height: 16px; ...\"",[56,3077,3054],{"class":3053},[56,3079,88],{"class":62},[56,3081,3082,3084,3086,3088,3090,3092,3094,3096,3099,3101],{"class":58,"line":121},[56,3083,2787],{"class":62},[56,3085,1013],{"class":66},[56,3087,1023],{"class":72},[56,3089,76],{"class":62},[56,3091,3042],{"class":79},[56,3093,3045],{"class":72},[56,3095,76],{"class":62},[56,3097,3098],{"class":79},"\"width: 120px; height: 36px; ...\"",[56,3100,3054],{"class":3053},[56,3102,88],{"class":62},[56,3104,3105,3107,3109],{"class":58,"line":126},[56,3106,162],{"class":62},[56,3108,1013],{"class":66},[56,3110,88],{"class":62},[10,3112,3113],{},"تبقى الفئة الأصلية موجودة كي يظلّ نظام الـ CSS الـ utility-first ساريًا، ويُضمَّن CSS الـ layout المحلول كنسخة احتياطية كي يبدو الـ skeleton صحيحًا حتى لو لم تُحمَّل ورقة الأنماط عند نقطة التركيب. يهمّ هذا كثيرًا لو كنت تعرض الشكل الملتقط داخل سياق CSS مختلف، مثل نافذة معاينة على هيئة بريد إلكتروني، أو shell معروض من الخادم لا يجلب Tailwind أصلًا.",[10,3115,3116,3117,3120,3121,3124,3125,3128,3129,3132,3133,3136],{},"الميزة الأخرى التي يمنحك إياها وضع structural هي الاستمرارية. تجعل ",[17,3118,3119],{},"persist: true"," الشكل الملتقط ينعكس إلى ",[17,3122,3123],{},"localStorage"," تحت البادئة ",[17,3126,3127],{},"a-skeleton:s:",". عند البدء البارد (cold start) في زيارة عائدة، يقرأ الـ cache الشكل المحفوظ ويعيد عرض الـ skeleton في أول رسم مباشرةً، دون وميض \"shimmer عام\" بينما يُركَّب المكوّن الحقيقي ويقيس نفسه. الـ cache مُصدَّر مخططه (",[17,3130,3131],{},"v: 3"," لـ structural، و",[17,3134,3135],{},"v: 2"," للشكل المسطّح القديم)، ولذلك تُطهَّر الإصدارات غير المتطابقة تلقائيًا عند القراءة. لا يمكنك إعادة عرض هندسة الأمس عبر مُعارض اليوم لو تغيّر شكل البيانات.",[10,3138,3139],{},"الاستراتيجيات الثلاث تختار سلوكيات افتراضية مختلفة انطلاقًا من البدائيات نفسها. Clone يعيد عرض بكسلات بمواضع مطلقة. Mirror يعيد عرض vnodes داخل الشجرة الأصلية. Structural يعيد عرض الحاويات في الـ flow الطبيعي. وحدة cache واحدة، وسطح theming واحد، وقاعدة a11y واحدة، وثلاثة محرّكات مختلفة.",[39,3141,3143],{"id":3142},"خط-أنابيب-الالتقاط-والتخزين-المؤقت-وإعادة-العرض","خط أنابيب الالتقاط والتخزين المؤقت وإعادة العرض",[10,3145,3146],{},"القصة المعمارية واحدة في الأوضاع الثلاثة، حتى وإن تباينت التنفيذات. إليك دورة تحميل واحدة من أوّلها إلى آخرها.",[10,3148,3149,3152],{},[2040,3150,3151],{},"الرسم الأول، وفشل الـ cache."," يُركَّب الـ wrapper، ولا يوجد شكل مخزّن بعد. في وضعَي clone وmirror يمشي المحرك على بنية الـ slot (vnodes في mirror، وتركيب DOM خارج الشاشة في clone) وينتج placeholder يعكس بنية وسوم الـ slot. تحتفظ الحاويات بوسومها الدلالية وبـ CSS الـ layout الخاص بها. أما الأوراق الذرّية والعناصر الحاملة للنص فتتحوّل إلى placeholders من نوع shimmer. يرى المستخدم skeleton بنيويًا يطابق حدود المكوّن الحقيقي على الفور، قبل أن تصل أيّ بيانات.",[10,3154,3155,3158,3159,2489,3162,3164,3165,3168,3169,3171,3172,3174,3175,3178,3179,3181,3182,3184,3185,3187],{},[2040,3156,3157],{},"وصول البيانات."," يُعرض المحتوى الحقيقي داخل الـ slot. يجدول ",[17,3160,3161],{},"ResizeObserver",[17,3163,1501],{}," تمريرة قياس. في وضع clone تقرأ ",[17,3166,3167],{},"captureSnapshot()"," قيمة ",[17,3170,2197],{}," لكل عنصر ومستطيلات النص لكل سطر عبر ",[17,3173,2221],{},"، ثم تجمّد النتيجة داخل شجرة ",[17,3176,3177],{},"CaptureSnapshot",". في وضع structural تنتج ",[17,3180,2888],{}," شجرة مجمّدة تحمل فيها الحاويات CSS الـ layout الخاص بها، وتحمل الأوراق أبعادها وإشاراتها البصرية. في الحالتين تصبح اللقطة الملتقطة في الذاكرة الآن (وفي ",[17,3183,3123],{}," أيضًا لو كانت ",[17,3186,3119],{},").",[10,3189,3190,3193,3194,3196,3197,3199,3200,3203,3204,3206],{},[2040,3191,3192],{},"التقلّب التالي إلى حالة التحميل."," يعود المستخدم إلى الصفحة، أو يطلق إعادة جلب، أو يزور المسار في جلسة لاحقة. تنقلب ",[17,3195,2242],{}," إلى ",[17,3198,620],{},". يُعاد عرض الشكل المخزّن فورًا، بلا خطوة وسيطة من نوع \"خمّن الشكل\". في وضع clone يعرض ",[17,3201,3202],{},"\u003CASkeletonClone>"," شجرة من الـ divs ذات المواضع المطلقة، يحمل كلٌّ منها نمطه المضمّن المجمّد مسبقًا؛ وحلقة العرض خالية تمامًا من التخصيص لأن كل حمولة نمط حُسبت وقت الالتقاط. في وضع structural يعرض ",[17,3205,2492],{}," الشجرة في الـ flow الطبيعي، مع تشغيل CSS الـ layout الملتقط لسلوك حاوية flex أو grid.",[10,3208,3209,3210,3212,3213,3215,3216,3218,3219,3221,3222,2375,3224,3226],{},"للميزانيات دور في كل خطوة. ",[17,3211,2363],{}," (افتراضيًا 500–800 بحسب الـ walker) هو حدّ حجم الشجرة. ",[17,3214,2360],{}," (افتراضيًا 12–16) هو حدّ الاستدعاء العودي. ",[17,3217,2366],{}," (افتراضيًا 4 بكسلات CSS) هو مرشّح لكل ورقة. هذه الأرقام ليست اعتباطية: اخترتُها بحيث تبقى لوحة تحكم مزدحمة بمئات الأوراق قابلة للمشي داخل rAF واحد، وبحيث يتّسع إدخال ",[17,3220,3123],{}," للشجرة الملتقطة دون تجاوز الحصّة البالغة 5 ميغابايت. وحين يُقتطع الـ walk، يسجّل الـ wrapper ",[17,3223,2374],{},[17,3225,2378],{}," كي تكتشف الأمر في التطوير، لا في الإنتاج.",[10,3228,3229,3230,1431,3233,3235],{},"قراءة layout واحدة في البداية، ثم قيم مخزّنة في بقية المشي. تحدث استدعاءات ",[17,3231,3232],{},"getBoundingClientRect()",[17,3234,2197],{}," في تمريرة واحدة من أعلى إلى أسفل، دون كتابات تتخلّلها، ودون قذف تخطيط: reflow قسري واحد، ثم تصبح اللقطة الملتقطة دائمة بعده.",[39,3237,3239],{"id":3238},"البدائيات-الـ-15-المسمّاة","البدائيات الـ 15 المسمّاة",[10,3241,3242,3243,1730,3246,1730,3249,1730,3252,1730,3255,1730,3258,1730,3261,1730,3264,1730,3267,1730,3270,1730,3273,1730,3276,1730,3279,1730,3282,1730,3285,24],{},"التوليد الذاتي لا يناسب كل حالة. لو لم يكن لحالة التحميل عندك مكوّن حقيقي يستحق أن يُلَفّ (كأن تعرض الـ skeleton قبل بدء أيّ جلب بيانات، أو يكون الـ placeholder حقًا مختلف الـ markup عن العرض النهائي)، فلا يجد الالتقاط التلقائي شيئًا يقرأه أصلًا. لهذه الحالات تشحن الحزمة 15 متغيّرًا مسمّى: ",[17,3244,3245],{},"\u003CASkeletonCard>",[17,3247,3248],{},"\u003CASkeletonText>",[17,3250,3251],{},"\u003CASkeletonHeading>",[17,3253,3254],{},"\u003CASkeletonAvatar>",[17,3256,3257],{},"\u003CASkeletonImage>",[17,3259,3260],{},"\u003CASkeletonVideo>",[17,3262,3263],{},"\u003CASkeletonButton>",[17,3265,3266],{},"\u003CASkeletonInput>",[17,3268,3269],{},"\u003CASkeletonChip>",[17,3271,3272],{},"\u003CASkeletonListItem>",[17,3274,3275],{},"\u003CASkeletonTable>",[17,3277,3278],{},"\u003CASkeletonChart>",[17,3280,3281],{},"\u003CASkeletonForm>",[17,3283,3284],{},"\u003CASkeletonArticle>",[17,3286,3287],{},"\u003CASkeletonDivider>",[10,3289,3290],{},"هذه ليست بديلًا عن مسار الالتقاط التلقائي، بل مخرجًا جانبيًا حين تحتاجه. تشترك في خط أنابيب الحركة نفسه، وفي رموز الـ theming نفسها، وفي قاعدة الـ a11y نفسها. تؤلّفها يدويًا عند الحاجة، وإلا فدع الـ wrapper يقوم بالعمل.",[10,3292,3293,3294,3297],{},"يوجد أيضًا ",[17,3295,3296],{},"\u003CASkeletonBlock>"," للحالة التي لا يناسبها أيّ من المتغيّرات وتريد تركيب نسختك الخاصة:",[47,3299,3301],{"className":49,"code":3300,"language":51,"meta":52,"style":52},"\u003Cdiv v-if=\"loading\" class=\"flex items-start gap-4 p-4\">\n  \u003CASkeletonBlock type=\"circle\" :w=\"64\" :h=\"64\" \u002F>\n  \u003Cdiv class=\"flex-1 space-y-2\">\n    \u003CASkeletonBlock type=\"text\" :w=\"160\" :h=\"18\" \u002F>\n    \u003CASkeletonBlock type=\"text\" :w=\"100\" :h=\"12\" \u002F>\n    \u003CASkeletonBlock type=\"text\" :lines=\"3\" :h=\"14\" class=\"!mt-3\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003CUserCard v-else :data=\"user\" \u002F>\n",[17,3302,3303,3328,3333,3338,3343,3348,3353,3361,3369],{"__ignoreMap":52},[56,3304,3305,3307,3309,3311,3313,3315,3317,3319,3321,3323,3326],{"class":58,"line":59},[56,3306,63],{"class":62},[56,3308,1013],{"class":66},[56,3310,2056],{"class":209},[56,3312,76],{"class":62},[56,3314,80],{"class":79},[56,3316,2242],{"class":62},[56,3318,80],{"class":79},[56,3320,1023],{"class":72},[56,3322,76],{"class":62},[56,3324,3325],{"class":79},"\"flex items-start gap-4 p-4\"",[56,3327,88],{"class":62},[56,3329,3330],{"class":58,"line":91},[56,3331,3332],{"class":62},"  \u003CASkeletonBlock type=\"circle\" :w=\"64\" :h=\"64\" \u002F>\n",[56,3334,3335],{"class":58,"line":97},[56,3336,3337],{"class":62},"  \u003Cdiv class=\"flex-1 space-y-2\">\n",[56,3339,3340],{"class":58,"line":103},[56,3341,3342],{"class":62},"    \u003CASkeletonBlock type=\"text\" :w=\"160\" :h=\"18\" \u002F>\n",[56,3344,3345],{"class":58,"line":109},[56,3346,3347],{"class":62},"    \u003CASkeletonBlock type=\"text\" :w=\"100\" :h=\"12\" \u002F>\n",[56,3349,3350],{"class":58,"line":115},[56,3351,3352],{"class":62},"    \u003CASkeletonBlock type=\"text\" :lines=\"3\" :h=\"14\" class=\"!mt-3\" \u002F>\n",[56,3354,3355,3357,3359],{"class":58,"line":121},[56,3356,2869],{"class":62},[56,3358,1013],{"class":66},[56,3360,88],{"class":62},[56,3362,3363,3365,3367],{"class":58,"line":126},[56,3364,162],{"class":62},[56,3366,1013],{"class":66},[56,3368,88],{"class":62},[56,3370,3371],{"class":58,"line":132},[56,3372,3373],{"class":62},"\u003CUserCard v-else :data=\"user\" \u002F>\n",[10,3375,3376,3378,3379,3382,3383,3386],{},[17,3377,3296],{}," صديق للـ flow. يتركّب بسلاسة مع تخطيطات flex وgrid والـ stack. تكدّس prop ",[17,3380,3381],{},"lines"," على ",[17,3384,3385],{},"type=\"text\""," عددًا N من الأشرطة مع جعل الأخير بعرض 70%، وهو استدلال يكون صحيحًا بدرجة كافية في أغلب الحالات حين لا يتوفّر مكوّن حقيقي تقيسه.",[39,3388,3390],{"id":3389},"الحركات-والـ-theming-بوصفها-استراتيجيات","الحركات والـ theming بوصفها استراتيجيات",[10,3392,3393,3394,3397,3398,3401,3402,3405,3406,3409,3410,3413,3414,3417,3418,3405,3421,3424,3425,3428,3429,3432],{},"تُشحن معنا أربعة أوضاع للحركة: ",[17,3395,3396],{},"shimmer"," (تدرّج ماسح على عنصر زائف ",[17,3399,3400],{},"::after","، محتوى داخل كل كتلة عبر ",[17,3403,3404],{},"overflow: hidden",")، و",[17,3407,3408],{},"pulse"," (تدوير opacity من 1 إلى ",[17,3411,3412],{},"--ak-skel-pulse-min"," والعودة)، و",[17,3415,3416],{},"wave"," (تدرّج منزلق عبر ",[17,3419,3420],{},"background-position",[17,3422,3423],{},"none"," (كتل ثابتة). كلٌّ منها استراتيجية قائمة بذاتها، وليست فرعًا شرطيًا. تمرّر ",[17,3426,3427],{},"animation=\"pulse\""," فتنخرط فئة الـ CSS المناسبة تلقائيًا. لا سلسلة ",[17,3430,3431],{},"if (animation === 'pulse')"," داخل المكوّن.",[10,3434,3435,3436,3439],{},"يعطّل ",[17,3437,3438],{},"prefers-reduced-motion: reduce"," كل حركة تلقائيًا. يهبط عنصر shimmer الزائف إلى طبقة ثابتة بشفافية منخفضة، ويتوقّف الـ pulse عن التدوير. لا شيفرة مكوّن معنيّة بذلك أصلًا؛ الأمر مجرد استعلام media في ورقة الأنماط.",[10,3441,3442,3443,3446,3447,3450],{},"الـ theming يقوم على متغيرات CSS. الرموز منطاقة تحت ",[17,3444,3445],{},"--ak-skel-*"," (مع أسماء بديلة متوافقة رجعيًا تحت ",[17,3448,3449],{},"--ak-skeleton-*"," لمستخدمي الإصدار الأول):",[3452,3453,3454,3467],"table",{},[3455,3456,3457],"thead",{},[3458,3459,3460,3464],"tr",{},[3461,3462,3463],"th",{},"الرمز",[3461,3465,3466],{},"يُستخدم لأجل",[3468,3469,3470,3481,3491,3501,3511,3520,3530],"tbody",{},[3458,3471,3472,3478],{},[3473,3474,3475],"td",{},[17,3476,3477],{},"--ak-skel-base",[3473,3479,3480],{},"تعبئة الكتلة",[3458,3482,3483,3488],{},[3473,3484,3485],{},[17,3486,3487],{},"--ak-skel-highlight",[3473,3489,3490],{},"لون مسح shimmer \u002F wave",[3458,3492,3493,3498],{},[3473,3494,3495],{},[17,3496,3497],{},"--ak-skel-radius",[3473,3499,3500],{},"نصف قطر حدود الكتلة الافتراضي",[3458,3502,3503,3508],{},[3473,3504,3505],{},[17,3506,3507],{},"--ak-skel-duration",[3473,3509,3510],{},"طول دورة الحركة",[3458,3512,3513,3517],{},[3473,3514,3515],{},[17,3516,3412],{},[3473,3518,3519],{},"قيمة opacity عند قاع دورة الـ pulse",[3458,3521,3522,3527],{},[3473,3523,3524],{},[17,3525,3526],{},"--ak-skel-ring",[3473,3528,3529],{},"لون حلقة داخلية خفيفة بعرض 1 بكسل",[3458,3531,3532,3537],{},[3473,3533,3534],{},[17,3535,3536],{},"--ak-skel-icon",[3473,3538,3539],{},"لون أيقونة placeholder (متغيرات image \u002F video)",[10,3541,3542,3543,3546],{},"تستطيع أن تكتب فوق هذه الرموز على ",[17,3544,3545],{},":root","، أو على فئة wrapper لأوضاع multi-tenant، أو مضمّنة على نسخة واحدة بعينها:",[47,3548,3552],{"className":3549,"code":3550,"language":3551,"meta":52,"style":52},"language-css shiki shiki-themes github-light github-dark",".tenant-acme {\n  --ak-skel-base: hsl(220 30% 18%);\n  --ak-skel-highlight: hsl(220 60% 60% \u002F 0.35);\n  --ak-skel-radius: 0.5rem;\n  --ak-skel-duration: 2s;\n}\n","css",[17,3553,3554,3561,3590,3620,3635,3650],{"__ignoreMap":52},[56,3555,3556,3559],{"class":58,"line":59},[56,3557,3558],{"class":72},".tenant-acme",[56,3560,719],{"class":62},[56,3562,3563,3566,3569,3572,3574,3577,3580,3583,3586,3588],{"class":58,"line":91},[56,3564,3565],{"class":284},"  --ak-skel-base",[56,3567,3568],{"class":62},": ",[56,3570,3571],{"class":249},"hsl",[56,3573,425],{"class":62},[56,3575,3576],{"class":249},"220",[56,3578,3579],{"class":249}," 30",[56,3581,3582],{"class":209},"%",[56,3584,3585],{"class":249}," 18",[56,3587,3582],{"class":209},[56,3589,623],{"class":62},[56,3591,3592,3595,3597,3599,3601,3603,3606,3608,3610,3612,3615,3618],{"class":58,"line":97},[56,3593,3594],{"class":284},"  --ak-skel-highlight",[56,3596,3568],{"class":62},[56,3598,3571],{"class":249},[56,3600,425],{"class":62},[56,3602,3576],{"class":249},[56,3604,3605],{"class":249}," 60",[56,3607,3582],{"class":209},[56,3609,3605],{"class":249},[56,3611,3582],{"class":209},[56,3613,3614],{"class":62}," \u002F ",[56,3616,3617],{"class":249},"0.35",[56,3619,623],{"class":62},[56,3621,3622,3625,3627,3630,3633],{"class":58,"line":103},[56,3623,3624],{"class":284},"  --ak-skel-radius",[56,3626,3568],{"class":62},[56,3628,3629],{"class":249},"0.5",[56,3631,3632],{"class":209},"rem",[56,3634,739],{"class":62},[56,3636,3637,3640,3642,3645,3648],{"class":58,"line":109},[56,3638,3639],{"class":284},"  --ak-skel-duration",[56,3641,3568],{"class":62},[56,3643,3644],{"class":249},"2",[56,3646,3647],{"class":209},"s",[56,3649,739],{"class":62},[56,3651,3652],{"class":58,"line":115},[56,3653,382],{"class":62},[10,3655,3656,3657,3660,3661,3664,3665,3668,3669,3671,3672,3675],{},"الوضع الداكن يعمل تلقائيًا عبر محدّدات ",[17,3658,3659],{},":where(.dark)"," التي تعيد تلوين الرموز (فئة ",[17,3662,3663],{},".dark"," الخاصة بـ Tailwind، وفئة shadcn، وفئة ",[17,3666,3667],{},"nuxt-color-mode","، وأيّ شيء يضع سلفًا يحمل ",[17,3670,3663],{}," في الشجرة)، إضافةً إلى نسخة احتياطية عبر ",[17,3673,3674],{},"@media (prefers-color-scheme: dark)"," للحالة التي لا توجد فيها فئة صريحة على الشجرة.",[10,3677,3678],{},"المقصد من هذا التصميم أن لا يضطر مستهلك المكتبة يومًا إلى الكتابة فوق فئة داخلية. كل قرار بصري هو رمز؛ والرموز موروثة عبر الـ cascade؛ والكتابة فوقها لنسخة بعينها تكون بأنماط مضمّنة. المكوّن يملك البنية والحركة، وأنت تملك المظهر.",[39,3680,3682],{"id":3681},"a11y-لا-تحتاج-إلى-تكوينها","a11y لا تحتاج إلى تكوينها",[10,3684,3685,3686,3689,3690,3693,3694,3697,3698,3701],{},"الإعدادات الافتراضية هي ذاتها التي كنتَ ستريدها على أيّ حال. يحمل كل wrapper وكل جذر متغيّر السمة ",[17,3687,3688],{},"role=\"status\""," أثناء التحميل، وسمة ",[17,3691,3692],{},"aria-busy=\"true\""," تعكس حالة التحميل، وسمة ",[17,3695,3696],{},"aria-live=\"polite\""," كي تُعلن قارئات الشاشة حالة التحميل دون مقاطعة المستخدم. يُشحن ",[17,3699,3700],{},"\u003Cspan class=\"a-skel-sr-only\">Loading...\u003C\u002Fspan>"," مخفيّ بصريًا مع كل متغيّر ليكون للإعلان نصّ يقرأه.",[10,3703,3704,3705,3708,3709,1431,3712,3715,3716,3719],{},"يحمل كل سطح shimmer مُصدَر السمة ",[17,3706,3707],{},"aria-hidden=\"true\"","، لأن الـ placeholder زخرفة والـ wrapper هو ما يُعلَن. تعطّل skeletons وضع mirror الخصائص ",[17,3710,3711],{},"user-select",[17,3713,3714],{},"pointer-events"," على شجرة الـ slot كي لا يستطيع أحد التفاعل مع الـ placeholder في منتصف التحميل. ويزيل ",[17,3717,3718],{},"prefers-reduced-motion"," الحركة كلها.",[10,3721,3722,3723,3726],{},"لا شيء من هذا قابل للتكوين. ولا ينبغي أن يكون. الإعدادات الافتراضية هي الإجابة الصحيحة. لو احتجتَ إلى إيقافها، فأنت إما تختبر شيئًا محدّدًا (وفي هذه الحالة ",[17,3724,3725],{},"animation=\"none\""," هو المفتاح المناسب)، أو تحاول شيئًا على مستوى الـ a11y لا أرغب أنا في أن أجعله سهلًا.",[39,3728,3730],{"id":3729},"ما-علّمني-إياه-وضع-clone","ما علّمني إياه وضع Clone",[10,3732,3733,3734,3737,3738,3741],{},"الشيء الذي سأحمله معي إلى المشكلة التالية ليس الـ API. بل هي البصيرة التي يقوم عليها وضع clone. تستطيع أن تتوقف عن التفكير على مستوى الـ cascade. تستطيع أن تتوقف عن محاولة التنبّؤ بما تنتجه فعلًا قيمة ",[17,3735,3736],{},"border-radius: 0.75rem"," مخلوطة بـ ",[17,3739,3740],{},"rounded-2xl"," مخلوطة بكتابة فوقية scoped. ركّب الشيء. دع المتصفح يطوي كل المدخلات. اقرأ الإجابة النهائية. أعِد عرض الإجابة النهائية.",[10,3743,3744,3745,3747],{},"هذا المبدأ ينطبق في كل موضع لديك فيه مشكلة \"أحتاج نسخة من هذا العنصر\". معاينات الطباعة. معاينات السحب. معارض المكوّنات التي تعرض الـ widget نفسه في خمسة أوضاع مختلفة. نهج \"انظر إلى ما هو موجود فعلًا على الشاشة، لا إلى القواعد التي أنتجته\" يمتدّ أبعد مما يُعطى من فضل. كل خط أنابيب CSS، وكل متغيّر، وكل أولوية cascade، وكل إطار حركة، ينتهي إلى رقم واحد لكل خاصية لكل عنصر، وذلك الرقم هو ما سيخبرك به ",[17,3746,2197],{}," متى ما سألته.",[10,3749,3750],{},"الشيء الآخر الذي أثبت جدواه فعلًا هو عقد التأليف. وسوم غير مشروطة، ومحتوى عبر الاستيفاء. بمجرد أن ترسخ هذه الفكرة في ذهنك، يصبح كل مكوّن تبنيه جاهزًا للـ skeleton تلقائيًا: لا قالب إضافي، ولا حالة موازية، ولا عبء صيانة. المكتبة تقرأ مكوّنك الحقيقي، وأنت تكتبه بالطريقة التي كنتَ تريدها أصلًا.",[10,3752,3753],{},"فكرتان اثنتان، وحزمة واحدة. المستودع مرخّص برخصة MIT، وستجد في الـ README المرجع الطويل لكل prop وslot لو أردتَ التعمّق أكثر. لو رأيتَ شيئًا يمكن أن يكون أكثر حدّة، افتح issue من فضلك. أفضّل بصدق أن أُصحَّح على أن أبقى مرتاحًا.",[1970,3755,3756],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":52,"searchDepth":91,"depth":91,"links":3758},[3759,3760,3761,3762,3763,3764,3765,3766,3767],{"id":2032,"depth":91,"text":2033},{"id":2175,"depth":91,"text":2176},{"id":2391,"depth":91,"text":2392},{"id":2481,"depth":91,"text":2482},{"id":3142,"depth":91,"text":3143},{"id":3238,"depth":91,"text":3239},{"id":3389,"depth":91,"text":3390},{"id":3681,"depth":91,"text":3682},{"id":3729,"depth":91,"text":3730},"2026-06-13","كيف تستخرج a-skeleton واجهة الانتظار من المكوّن نفسه. وضع Clone، ووضع Structural، ووضع Mirror، ومسار العمل الذي يلتقط ثم يُخزّن مؤقّتاً ثم يُعيد العرض خلفها.",[1997,2004,3771,3772,3773,3774,3161,2003,3775,3776],"مُحمِّل Skeleton","حالات التحميل","getComputedStyle","Range API","مكتبة مكوّنات","إمكانية الوصول",{},"\u002Fwrites-ar\u002Finside-a-skeleton-self-generating-skeleton-loaders",{"title":2014,"description":3769},"writes-ar\u002Finside-a-skeleton-self-generating-skeleton-loaders","YF9r0lB5uhmEc4wHqor5kRgfbA9lR_dGeOudu2h0SQg",{"id":3783,"title":3784,"body":3785,"date":8279,"description":8280,"description_ar":1992,"draft":1993,"extension":1994,"image":1992,"keywords":8281,"lang":2005,"meta":8288,"navigation":334,"path":8289,"seo":8290,"stem":8291,"title_ar":1992,"updatedAt":8279,"__hash__":8292},"writesAr\u002Fwrites-ar\u002Fatell-input-the-library-rewrite.md","ملاحظات من إعادة كتابة ATellInput كمكتبة مُنشَرة",{"type":7,"value":3786,"toc":8269},[3787,3798,3801,3808,3811,3815,3818,3878,3893,3896,3913,3916,4003,4025,4164,4181,4192,4195,4199,4202,4290,4300,4315,4321,4866,4869,4897,4920,4938,4941,4944,5118,5132,5203,5206,5210,5217,5223,5550,5553,5622,5625,5947,5975,5982,6025,6036,6039,6123,6129,6133,6136,6147,6258,6280,6283,6287,6290,6293,6857,6899,6906,7046,7060,7063,7192,7195,7198,7334,7357,7360,7364,7393,7396,7627,7634,7859,7862,8033,8040,8043,8047,8053,8058,8179,8189,8192,8196,8207,8210,8213,8256,8259,8266],[10,3788,3789,3790,3793,3794,3797],{},"وُلد ",[17,3791,3792],{},"ATellInput"," داخل تطبيق واحد فقط: قيمة احتياطية تميل إلى السعودية أو مصر، ونداء داخلي لـ ",[17,3795,3796],{},"RestCountries","، وتحقّق يظهر عبر v-model لا غير، وقائمة منسدلة ظاهرة على الشاشة طوال الوقت. حلّت تلك النسخة مسألة الصحّة: اضطراب موضع المؤشّر، وأسباب التحقّق، ودعم RTL\u002FLTR. وأنجزت معظم ما يحتاجه مستهلك واحد.",[10,3799,3800],{},"هذه التدوينة تحكي ما جرى حين حاولتُ نشر المكوّن.",[10,3802,3803,3804,3807],{},"يعيش المكوّن الآن في ",[17,3805,3806],{},"@alikhalilll\u002Fui\u002Ftell-input","، ومهمّة المكتبة المُنشَرة تختلف كلياً. لا تستطيع أن تُشفِّر السوق الذي تخدمه داخل الكود. ولا أن تفترض شكل نظام تصميم المستهلك. ولا أن تُلزم كلّ نقطة تركيب بانتظار واجهة برمجية طرف ثالث. ولا حتى أن تعرف مسبقاً إن كان المستخدم على هاتف أم على حاسوب. حافظت إعادة الكتابة على صحّة النسخة الأصلية وأضافت ما يحتاجه مستهلك المكتبة فعلياً، وكلّ تغيير تقريباً وراءه علّة محدّدة أو قصور واضح في النسخة القديمة.",[10,3809,3810],{},"جمعتُ التغييرات حول السبعة التي استحقّت مكانها.",[39,3812,3814],{"id":3813},"نموذجان-بشكلين-مختلفين-الرقم-للخارج-وiso2-في-الداخل","نموذجان بشكلين مختلفين: الرقم للخارج، وISO2 في الداخل",[10,3816,3817],{},"بدت الواجهة القديمة أنيقة من بعيد:",[47,3819,3821],{"className":200,"code":3820,"language":202,"meta":52,"style":52},"const phoneModelValue = defineModel\u003Cstring>('phone', { required: true });\nconst countryModelValue = defineModel\u003Cstring>('country', { required: true });\n",[17,3822,3823,3852],{"__ignoreMap":52},[56,3824,3825,3827,3830,3832,3835,3837,3839,3841,3844,3847,3849],{"class":58,"line":59},[56,3826,410],{"class":209},[56,3828,3829],{"class":249}," phoneModelValue",[56,3831,270],{"class":209},[56,3833,3834],{"class":72}," defineModel",[56,3836,63],{"class":62},[56,3838,1727],{"class":249},[56,3840,671],{"class":62},[56,3842,3843],{"class":79},"'phone'",[56,3845,3846],{"class":62},", { required: ",[56,3848,620],{"class":249},[56,3850,3851],{"class":62}," });\n",[56,3853,3854,3856,3859,3861,3863,3865,3867,3869,3872,3874,3876],{"class":58,"line":91},[56,3855,410],{"class":209},[56,3857,3858],{"class":249}," countryModelValue",[56,3860,270],{"class":209},[56,3862,3834],{"class":72},[56,3864,63],{"class":62},[56,3866,1727],{"class":249},[56,3868,671],{"class":62},[56,3870,3871],{"class":79},"'country'",[56,3873,3846],{"class":62},[56,3875,620],{"class":249},[56,3877,3851],{"class":62},[10,3879,3880,3881,3884,3885,3888,3889,3892],{},"كان ",[17,3882,3883],{},"country"," سلسلة نصية تحوي أرقام الاتصال: ",[17,3886,3887],{},"\"20\""," لمصر، و",[17,3890,3891],{},"\"44\""," للمملكة المتحدة. سهل التحويل إلى JSON، وسهل ترميزه في URL، وسهل إسكانه في عمود قاعدة بيانات.",[10,3894,3895],{},"المشكلة أنّه عجز عن التفريق بين الولايات المتحدة وكندا.",[10,3897,3898,3899,3902,3903,3906,3907,3909,3910,3912],{},"يتقاسم الرمز ",[17,3900,3901],{},"+1"," أكثر من 25 دولة ضمن NANP. لو اختار مستخدم كندي \"كندا\" من القائمة وأصدر v-model القيمة ",[17,3904,3905],{},"\"1\"",", فلن يعرف الأب أيّ ",[17,3908,3901],{}," هو المقصود. حدّث الصفحة، واستعد الحالة من URL، ستعود إليك ",[17,3911,3905],{}," بينما يعرض المنتقي أوّل دولة صادف أن ترتّبها القائمة. النموذج الذي يختصر الدولة في \"سلسلة رموز اتصال\" كان يُفقد نصف المعلومة عن شيء من قطعتين.",[10,3914,3915],{},"النموذج الجديد يفصل بين القطعتين:",[47,3917,3919],{"className":200,"code":3918,"language":202,"meta":52,"style":52},"const phone = defineModel\u003Cstring>('phone', { default: '' });\nconst country = defineModel\u003Cnumber | null>('country', { default: null });\n\nconst selectedIso2 = ref\u003Cstring>('');\n",[17,3920,3921,3948,3978,3982],{"__ignoreMap":52},[56,3922,3923,3925,3928,3930,3932,3934,3936,3938,3940,3943,3946],{"class":58,"line":59},[56,3924,410],{"class":209},[56,3926,3927],{"class":249}," phone",[56,3929,270],{"class":209},[56,3931,3834],{"class":72},[56,3933,63],{"class":62},[56,3935,1727],{"class":249},[56,3937,671],{"class":62},[56,3939,3843],{"class":79},[56,3941,3942],{"class":62},", { default: ",[56,3944,3945],{"class":79},"''",[56,3947,3851],{"class":62},[56,3949,3950,3952,3955,3957,3959,3961,3964,3966,3968,3970,3972,3974,3976],{"class":58,"line":91},[56,3951,410],{"class":209},[56,3953,3954],{"class":249}," country",[56,3956,270],{"class":209},[56,3958,3834],{"class":72},[56,3960,63],{"class":62},[56,3962,3963],{"class":249},"number",[56,3965,660],{"class":209},[56,3967,668],{"class":249},[56,3969,671],{"class":62},[56,3971,3871],{"class":79},[56,3973,3942],{"class":62},[56,3975,674],{"class":249},[56,3977,3851],{"class":62},[56,3979,3980],{"class":58,"line":97},[56,3981,335],{"emptyLinePlaceholder":334},[56,3983,3984,3986,3989,3991,3993,3995,3997,3999,4001],{"class":58,"line":103},[56,3985,410],{"class":209},[56,3987,3988],{"class":249}," selectedIso2",[56,3990,270],{"class":209},[56,3992,615],{"class":72},[56,3994,63],{"class":62},[56,3996,1727],{"class":249},[56,3998,671],{"class":62},[56,4000,3945],{"class":79},[56,4002,623],{"class":62},[10,4004,4005,4006,4008,4009,1730,4012,1730,4014,4016,4017,4020,4021,4024],{},"صار ",[17,4007,3883],{}," رقماً (",[17,4010,4011],{},"20",[17,4013,1297],{},[17,4015,674],{},") لأنّ رموز الاتصال أرقامٌ في جوهرها، ولأنّ ",[17,4018,4019],{},"Number"," ينجو من رحلات URL وDB وJSON بلا أيّ التباس ناتج عن اقتطاع الأحرف. غير أنّ المكوّن يتتبّع داخلياً أيضاً ",[17,4022,4023],{},"selectedIso2"," يملكه المنتقي وحده. تبقى القيمتان متزامنتين عبر watcher-ين، أحدهما للخارج والآخر للداخل، والداخلي منهما فيه حارس صغير لكنّه حاسم:",[47,4026,4028],{"className":200,"code":4027,"language":202,"meta":52,"style":52},"watch(\n  country,\n  (next) => {\n    if (next == null) {\n      if (selectedIso2.value) selectedIso2.value = '';\n      return;\n    }\n    if (dialNumberFor(selectedIso2.value) === next) return;\n    const iso2 = resolveCountryIdentifier(String(next));\n    if (iso2) selectedIso2.value = iso2;\n  },\n  { immediate: true }\n);\n",[17,4029,4030,4036,4041,4054,4068,4083,4090,4094,4115,4135,4147,4151,4160],{"__ignoreMap":52},[56,4031,4032,4034],{"class":58,"line":59},[56,4033,1223],{"class":72},[56,4035,1063],{"class":62},[56,4037,4038],{"class":58,"line":91},[56,4039,4040],{"class":62},"  country,\n",[56,4042,4043,4045,4048,4050,4052],{"class":58,"line":97},[56,4044,1068],{"class":62},[56,4046,4047],{"class":284},"next",[56,4049,452],{"class":62},[56,4051,298],{"class":209},[56,4053,719],{"class":62},[56,4055,4056,4058,4061,4064,4066],{"class":58,"line":103},[56,4057,802],{"class":209},[56,4059,4060],{"class":62}," (next ",[56,4062,4063],{"class":209},"==",[56,4065,668],{"class":249},[56,4067,259],{"class":62},[56,4069,4070,4073,4076,4078,4081],{"class":58,"line":109},[56,4071,4072],{"class":209},"      if",[56,4074,4075],{"class":62}," (selectedIso2.value) selectedIso2.value ",[56,4077,76],{"class":209},[56,4079,4080],{"class":79}," ''",[56,4082,739],{"class":62},[56,4084,4085,4088],{"class":58,"line":115},[56,4086,4087],{"class":209},"      return",[56,4089,739],{"class":62},[56,4091,4092],{"class":58,"line":121},[56,4093,1150],{"class":62},[56,4095,4096,4098,4100,4103,4106,4108,4111,4113],{"class":58,"line":126},[56,4097,802],{"class":209},[56,4099,447],{"class":62},[56,4101,4102],{"class":72},"dialNumberFor",[56,4104,4105],{"class":62},"(selectedIso2.value) ",[56,4107,749],{"class":209},[56,4109,4110],{"class":62}," next) ",[56,4112,736],{"class":209},[56,4114,739],{"class":62},[56,4116,4117,4119,4122,4124,4127,4129,4132],{"class":58,"line":132},[56,4118,264],{"class":209},[56,4120,4121],{"class":249}," iso2",[56,4123,270],{"class":209},[56,4125,4126],{"class":72}," resolveCountryIdentifier",[56,4128,425],{"class":62},[56,4130,4131],{"class":72},"String",[56,4133,4134],{"class":62},"(next));\n",[56,4136,4137,4139,4142,4144],{"class":58,"line":137},[56,4138,802],{"class":209},[56,4140,4141],{"class":62}," (iso2) selectedIso2.value ",[56,4143,76],{"class":209},[56,4145,4146],{"class":62}," iso2;\n",[56,4148,4149],{"class":58,"line":142},[56,4150,1155],{"class":62},[56,4152,4153,4156,4158],{"class":58,"line":148},[56,4154,4155],{"class":62},"  { immediate: ",[56,4157,620],{"class":249},[56,4159,1177],{"class":62},[56,4161,4162],{"class":58,"line":154},[56,4163,623],{"class":62},[10,4165,4166,4167,4170,4171,4174,4175,4177,4178,4180],{},"السطر ",[17,4168,4169],{},"if (dialNumberFor(selectedIso2.value) === next) return;"," هو ما أودّ التوقّف عنده. حين يكتب الأب ",[17,4172,4173],{},"country = 1"," من جديد إلى النموذج والمنتقي يعرض كندا مختارة أصلاً، ينطلق الـ watch, لكنّ رمز الاتصال متطابق مسبقاً، فيعود دون أن يمسّ ",[17,4176,4023],{},".\nلولا هذا الحارس، لكان الـ watcher حلّ ",[17,4179,3905],{}," إلى أوّل دولة NANP أبجدياً وقلب كندا بصمت إلى أيّاً كانت تلك الدولة. الحارس يحفظ الحالة الأغنى.",[10,4182,4183,4184,4187,4188,4191],{},"الـ watch الخارجي يعمل بوضع ",[17,4185,4186],{},"flush: 'sync'"," كي يبقى العلَم ",[17,4189,4190],{},"autoSettingCountry"," متّسقاً. لأنّه حين يختار المطابِق دولةً تلقائياً بناءً على ما كُتب، لا نريد اعتبار ذلك \"اختياراً يدوياً\" يُقفل الاكتشاف التلقائي لاحقاً.",[10,4193,4194],{},"نصفان من بيانات الدولة، ونموذجان مستقلّان، ومصدر حقيقة واحد لكلٍّ منهما.",[39,4196,4198],{"id":4197},"تجربة-تقوم-على-الاكتشاف-أولاً-المنتقي-مخفي-افتراضياً","تجربة تقوم على الاكتشاف أولاً: المنتقي مخفي افتراضياً",[10,4200,4201],{},"كان المكوّن القديم يرسم القائمة دائماً: العلَم يساراً، وحقل الإدخال يميناً. أمّا المكوّن الجديد فيكتفي بحقل الإدخال وحده افتراضياً. تنزلق القائمة إلى الظهور فقط حين يكتب المستخدم شيئاً يتعرّف عليه المطابِق:",[47,4203,4205],{"className":49,"code":4204,"language":51,"meta":52,"style":52},"\u003CTransition\n  enter-active-class=\"transition-all duration-200 ease-out overflow-hidden\"\n  leave-active-class=\"transition-all duration-150 ease-in overflow-hidden\"\n  enter-from-class=\"opacity-0 -translate-x-1 max-w-0\"\n  leave-to-class=\"opacity-0 -translate-x-1 max-w-0\"\n  enter-to-class=\"max-w-[12rem]\"\n  leave-from-class=\"max-w-[12rem]\"\n>\n  \u003CACountrySelect v-if=\"!props.detectFromInput || selectedIso2\" ... \u002F>\n\u003C\u002FTransition>\n",[17,4206,4207,4214,4224,4234,4244,4253,4263,4272,4276,4281],{"__ignoreMap":52},[56,4208,4209,4211],{"class":58,"line":59},[56,4210,63],{"class":62},[56,4212,4213],{"class":66},"Transition\n",[56,4215,4216,4219,4221],{"class":58,"line":91},[56,4217,4218],{"class":72},"  enter-active-class",[56,4220,76],{"class":62},[56,4222,4223],{"class":79},"\"transition-all duration-200 ease-out overflow-hidden\"\n",[56,4225,4226,4229,4231],{"class":58,"line":97},[56,4227,4228],{"class":72},"  leave-active-class",[56,4230,76],{"class":62},[56,4232,4233],{"class":79},"\"transition-all duration-150 ease-in overflow-hidden\"\n",[56,4235,4236,4239,4241],{"class":58,"line":103},[56,4237,4238],{"class":72},"  enter-from-class",[56,4240,76],{"class":62},[56,4242,4243],{"class":79},"\"opacity-0 -translate-x-1 max-w-0\"\n",[56,4245,4246,4249,4251],{"class":58,"line":109},[56,4247,4248],{"class":72},"  leave-to-class",[56,4250,76],{"class":62},[56,4252,4243],{"class":79},[56,4254,4255,4258,4260],{"class":58,"line":115},[56,4256,4257],{"class":72},"  enter-to-class",[56,4259,76],{"class":62},[56,4261,4262],{"class":79},"\"max-w-[12rem]\"\n",[56,4264,4265,4268,4270],{"class":58,"line":121},[56,4266,4267],{"class":72},"  leave-from-class",[56,4269,76],{"class":62},[56,4271,4262],{"class":79},[56,4273,4274],{"class":58,"line":126},[56,4275,88],{"class":62},[56,4277,4278],{"class":58,"line":132},[56,4279,4280],{"class":62},"  \u003CACountrySelect v-if=\"!props.detectFromInput || selectedIso2\" ... \u002F>\n",[56,4282,4283,4285,4288],{"class":58,"line":137},[56,4284,162],{"class":62},[56,4286,4287],{"class":66},"Transition",[56,4289,88],{"class":62},[10,4291,4292,4293,4296,4297,24],{},"يتقلّص ",[17,4294,4295],{},"max-w"," إلى صفر ما لم يكن هناك اختيار، ثم يتحرّك ليتفتّح عند أوّل تطابق ناجح. ومن يريد الشكل القديم يستطيع إعادة المنتقي الظاهر دائماً عبر ",[17,4298,4299],{},"detectFromInput=\"false\"",[10,4301,4302,4303,4306,4307,4310,4311,4314],{},"هذا سلوك ينحاز إلى رأي واضح. الأغلبية العظمى من المستخدمين في معظم الدول لا يكتبون بادئة ",[17,4304,4305],{},"+"," أبداً. بل يكتبون الرقم كما يكتبونه في هاتفهم: ",[17,4308,4309],{},"01066105963"," في القاهرة، و",[17,4312,4313],{},"07911 123456"," في مانشستر. القائمة التي \"يُجبَرون على التعامل معها\" في كلّ حقل هاتف آخر خطوة زائدة بالنسبة لهم. أخفِها إذاً. وراقب ما يكتبونه. ولا تُظهرها إلا حين يقع الاكتشاف على نتيجة.",[10,4316,4317,4318,222],{},"الجزء الوحيد الذي فيه بعض التعقيد هو \"مراقبة ما يكتبونه\". يمرّ عبر ثلاث طبقات في ",[17,4319,4320],{},"matchLeadingDialCode",[47,4322,4324],{"className":200,"code":4323,"language":202,"meta":52,"style":52},"function matchLeadingDialCode(digits: string): DialMatch | null {\n  if (!digits) return null;\n\n  \u002F\u002F Tier 1: international parse, disambiguates NANP and strips the calling code.\n  try {\n    const parsed = parsePhoneNumberFromString(`+${digits}`);\n    if (parsed?.country && parsed.countryCallingCode) {\n      const parsedCountry = getCountryByValue(parsed.country);\n      if (parsedCountry) {\n        return { country: parsedCountry, nationalNumber: String(parsed.nationalNumber ?? '') };\n      }\n    }\n  } catch {\n    \u002F* libphonenumber throws on partial input, fall through *\u002F\n  }\n\n  \u002F\u002F Tier 2: national-format parse using the silently-inferred country as a hint.\n  const hint = inferredCountry.value;\n  if (hint && digits.length >= 4) {\n    try {\n      const parsed = parsePhoneNumberFromString(digits, hint as CountryCode);\n      if (parsed?.isValid()) {\n        const matched = getCountryByValue(parsed.country || hint);\n        if (matched) {\n          return { country: matched, nationalNumber: String(parsed.nationalNumber ?? '') };\n        }\n      }\n    } catch {\n      \u002F* fall through *\u002F\n    }\n  }\n\n  \u002F\u002F Tier 3: longest-prefix match over our own dial-digits index.\n  for (let len = Math.min(3, digits.length); len >= 1; len--) {\n    const prefix = digits.slice(0, len);\n    const group = getCountriesByDial(prefix);\n    if (!group.length) continue;\n    const nationalNumber = digits.slice(prefix.length);\n    if (group.length === 1) return { country: group[0], nationalNumber };\n    \u002F\u002F ambiguity: prefer current selection, then recents, then first\n    \u002F\u002F (see \"Recents as the tiebreaker\" below)\n    return { country: group[0], nationalNumber };\n  }\n  return null;\n}\n",[17,4325,4326,4355,4372,4376,4381,4387,4410,4422,4438,4445,4466,4471,4475,4484,4489,4493,4497,4502,4514,4535,4542,4562,4575,4595,4603,4621,4627,4632,4642,4648,4653,4658,4663,4669,4717,4739,4755,4776,4797,4824,4830,4836,4847,4852,4861],{"__ignoreMap":52},[56,4327,4328,4330,4333,4335,4338,4340,4342,4344,4346,4349,4351,4353],{"class":58,"line":59},[56,4329,1695],{"class":209},[56,4331,4332],{"class":72}," matchLeadingDialCode",[56,4334,425],{"class":62},[56,4336,4337],{"class":284},"digits",[56,4339,222],{"class":209},[56,4341,2571],{"class":249},[56,4343,1711],{"class":62},[56,4345,222],{"class":209},[56,4347,4348],{"class":72}," DialMatch",[56,4350,660],{"class":209},[56,4352,668],{"class":249},[56,4354,719],{"class":62},[56,4356,4357,4359,4361,4363,4366,4368,4370],{"class":58,"line":91},[56,4358,243],{"class":209},[56,4360,447],{"class":62},[56,4362,759],{"class":209},[56,4364,4365],{"class":62},"digits) ",[56,4367,736],{"class":209},[56,4369,668],{"class":249},[56,4371,739],{"class":62},[56,4373,4374],{"class":58,"line":97},[56,4375,335],{"emptyLinePlaceholder":334},[56,4377,4378],{"class":58,"line":103},[56,4379,4380],{"class":434},"  \u002F\u002F Tier 1: international parse, disambiguates NANP and strips the calling code.\n",[56,4382,4383,4385],{"class":58,"line":109},[56,4384,795],{"class":209},[56,4386,719],{"class":62},[56,4388,4389,4391,4394,4396,4399,4401,4404,4406,4408],{"class":58,"line":115},[56,4390,264],{"class":209},[56,4392,4393],{"class":249}," parsed",[56,4395,270],{"class":209},[56,4397,4398],{"class":72}," parsePhoneNumberFromString",[56,4400,425],{"class":62},[56,4402,4403],{"class":79},"`+${",[56,4405,4337],{"class":62},[56,4407,2689],{"class":79},[56,4409,623],{"class":62},[56,4411,4412,4414,4417,4419],{"class":58,"line":121},[56,4413,802],{"class":209},[56,4415,4416],{"class":62}," (parsed?.country ",[56,4418,1124],{"class":209},[56,4420,4421],{"class":62}," parsed.countryCallingCode) {\n",[56,4423,4424,4427,4430,4432,4435],{"class":58,"line":126},[56,4425,4426],{"class":209},"      const",[56,4428,4429],{"class":249}," parsedCountry",[56,4431,270],{"class":209},[56,4433,4434],{"class":72}," getCountryByValue",[56,4436,4437],{"class":62},"(parsed.country);\n",[56,4439,4440,4442],{"class":58,"line":132},[56,4441,4072],{"class":209},[56,4443,4444],{"class":62}," (parsedCountry) {\n",[56,4446,4447,4450,4453,4455,4458,4461,4463],{"class":58,"line":137},[56,4448,4449],{"class":209},"        return",[56,4451,4452],{"class":62}," { country: parsedCountry, nationalNumber: ",[56,4454,4131],{"class":72},[56,4456,4457],{"class":62},"(parsed.nationalNumber ",[56,4459,4460],{"class":209},"??",[56,4462,4080],{"class":79},[56,4464,4465],{"class":62},") };\n",[56,4467,4468],{"class":58,"line":142},[56,4469,4470],{"class":62},"      }\n",[56,4472,4473],{"class":58,"line":148},[56,4474,1150],{"class":62},[56,4476,4477,4479,4482],{"class":58,"line":154},[56,4478,905],{"class":62},[56,4480,4481],{"class":209},"catch",[56,4483,719],{"class":62},[56,4485,4486],{"class":58,"line":159},[56,4487,4488],{"class":434},"    \u002F* libphonenumber throws on partial input, fall through *\u002F\n",[56,4490,4491],{"class":58,"line":913},[56,4492,329],{"class":62},[56,4494,4495],{"class":58,"line":933},[56,4496,335],{"emptyLinePlaceholder":334},[56,4498,4499],{"class":58,"line":952},[56,4500,4501],{"class":434},"  \u002F\u002F Tier 2: national-format parse using the silently-inferred country as a hint.\n",[56,4503,4504,4506,4509,4511],{"class":58,"line":964},[56,4505,340],{"class":209},[56,4507,4508],{"class":249}," hint",[56,4510,270],{"class":209},[56,4512,4513],{"class":62}," inferredCountry.value;\n",[56,4515,4516,4518,4521,4523,4526,4528,4530,4533],{"class":58,"line":969},[56,4517,243],{"class":209},[56,4519,4520],{"class":62}," (hint ",[56,4522,1124],{"class":209},[56,4524,4525],{"class":62}," digits.",[56,4527,250],{"class":249},[56,4529,253],{"class":209},[56,4531,4532],{"class":249}," 4",[56,4534,259],{"class":62},[56,4536,4537,4540],{"class":58,"line":2784},[56,4538,4539],{"class":209},"    try",[56,4541,719],{"class":62},[56,4543,4544,4546,4548,4550,4552,4555,4557,4560],{"class":58,"line":2801},[56,4545,4426],{"class":209},[56,4547,4393],{"class":249},[56,4549,270],{"class":209},[56,4551,4398],{"class":72},[56,4553,4554],{"class":62},"(digits, hint ",[56,4556,276],{"class":209},[56,4558,4559],{"class":72}," CountryCode",[56,4561,623],{"class":62},[56,4563,4564,4566,4569,4572],{"class":58,"line":2827},[56,4565,4072],{"class":209},[56,4567,4568],{"class":62}," (parsed?.",[56,4570,4571],{"class":72},"isValid",[56,4573,4574],{"class":62},"()) {\n",[56,4576,4577,4580,4583,4585,4587,4590,4592],{"class":58,"line":2845},[56,4578,4579],{"class":209},"        const",[56,4581,4582],{"class":249}," matched",[56,4584,270],{"class":209},[56,4586,4434],{"class":72},[56,4588,4589],{"class":62},"(parsed.country ",[56,4591,765],{"class":209},[56,4593,4594],{"class":62}," hint);\n",[56,4596,4597,4600],{"class":58,"line":2866},[56,4598,4599],{"class":209},"        if",[56,4601,4602],{"class":62}," (matched) {\n",[56,4604,4605,4608,4611,4613,4615,4617,4619],{"class":58,"line":2876},[56,4606,4607],{"class":209},"          return",[56,4609,4610],{"class":62}," { country: matched, nationalNumber: ",[56,4612,4131],{"class":72},[56,4614,4457],{"class":62},[56,4616,4460],{"class":209},[56,4618,4080],{"class":79},[56,4620,4465],{"class":62},[56,4622,4624],{"class":58,"line":4623},26,[56,4625,4626],{"class":62},"        }\n",[56,4628,4630],{"class":58,"line":4629},27,[56,4631,4470],{"class":62},[56,4633,4635,4638,4640],{"class":58,"line":4634},28,[56,4636,4637],{"class":62},"    } ",[56,4639,4481],{"class":209},[56,4641,719],{"class":62},[56,4643,4645],{"class":58,"line":4644},29,[56,4646,4647],{"class":434},"      \u002F* fall through *\u002F\n",[56,4649,4651],{"class":58,"line":4650},30,[56,4652,1150],{"class":62},[56,4654,4656],{"class":58,"line":4655},31,[56,4657,329],{"class":62},[56,4659,4661],{"class":58,"line":4660},32,[56,4662,335],{"emptyLinePlaceholder":334},[56,4664,4666],{"class":58,"line":4665},33,[56,4667,4668],{"class":434},"  \u002F\u002F Tier 3: longest-prefix match over our own dial-digits index.\n",[56,4670,4672,4675,4677,4680,4683,4685,4688,4691,4693,4696,4699,4701,4704,4707,4709,4712,4715],{"class":58,"line":4671},34,[56,4673,4674],{"class":209},"  for",[56,4676,447],{"class":62},[56,4678,4679],{"class":209},"let",[56,4681,4682],{"class":62}," len ",[56,4684,76],{"class":209},[56,4686,4687],{"class":62}," Math.",[56,4689,4690],{"class":72},"min",[56,4692,425],{"class":62},[56,4694,4695],{"class":249},"3",[56,4697,4698],{"class":62},", digits.",[56,4700,250],{"class":249},[56,4702,4703],{"class":62},"); len ",[56,4705,4706],{"class":209},">=",[56,4708,256],{"class":249},[56,4710,4711],{"class":62},"; len",[56,4713,4714],{"class":209},"--",[56,4716,259],{"class":62},[56,4718,4720,4722,4725,4727,4729,4732,4734,4736],{"class":58,"line":4719},35,[56,4721,264],{"class":209},[56,4723,4724],{"class":249}," prefix",[56,4726,270],{"class":209},[56,4728,4525],{"class":62},[56,4730,4731],{"class":72},"slice",[56,4733,425],{"class":62},[56,4735,1092],{"class":249},[56,4737,4738],{"class":62},", len);\n",[56,4740,4742,4744,4747,4749,4752],{"class":58,"line":4741},36,[56,4743,264],{"class":209},[56,4745,4746],{"class":249}," group",[56,4748,270],{"class":209},[56,4750,4751],{"class":72}," getCountriesByDial",[56,4753,4754],{"class":62},"(prefix);\n",[56,4756,4758,4760,4762,4764,4767,4769,4771,4774],{"class":58,"line":4757},37,[56,4759,802],{"class":209},[56,4761,447],{"class":62},[56,4763,759],{"class":209},[56,4765,4766],{"class":62},"group.",[56,4768,250],{"class":249},[56,4770,452],{"class":62},[56,4772,4773],{"class":209},"continue",[56,4775,739],{"class":62},[56,4777,4779,4781,4784,4786,4788,4790,4793,4795],{"class":58,"line":4778},38,[56,4780,264],{"class":209},[56,4782,4783],{"class":249}," nationalNumber",[56,4785,270],{"class":209},[56,4787,4525],{"class":62},[56,4789,4731],{"class":72},[56,4791,4792],{"class":62},"(prefix.",[56,4794,250],{"class":249},[56,4796,623],{"class":62},[56,4798,4800,4802,4805,4807,4810,4812,4814,4816,4819,4821],{"class":58,"line":4799},39,[56,4801,802],{"class":209},[56,4803,4804],{"class":62}," (group.",[56,4806,250],{"class":249},[56,4808,4809],{"class":209}," ===",[56,4811,256],{"class":249},[56,4813,452],{"class":62},[56,4815,736],{"class":209},[56,4817,4818],{"class":62}," { country: group[",[56,4820,1092],{"class":249},[56,4822,4823],{"class":62},"], nationalNumber };\n",[56,4825,4827],{"class":58,"line":4826},40,[56,4828,4829],{"class":434},"    \u002F\u002F ambiguity: prefer current selection, then recents, then first\n",[56,4831,4833],{"class":58,"line":4832},41,[56,4834,4835],{"class":434},"    \u002F\u002F (see \"Recents as the tiebreaker\" below)\n",[56,4837,4839,4841,4843,4845],{"class":58,"line":4838},42,[56,4840,316],{"class":209},[56,4842,4818],{"class":62},[56,4844,1092],{"class":249},[56,4846,4823],{"class":62},[56,4848,4850],{"class":58,"line":4849},43,[56,4851,329],{"class":62},[56,4853,4855,4857,4859],{"class":58,"line":4854},44,[56,4856,370],{"class":209},[56,4858,668],{"class":249},[56,4860,739],{"class":62},[56,4862,4864],{"class":58,"line":4863},45,[56,4865,382],{"class":62},[10,4867,4868],{},"توجد الطبقات لأنّ لكلّ واحدة منها فئة من المدخلات تعجز الطبقتان الأخريان عن معالجتها.",[10,4870,4871,4872,4874,4875,4878,4879,4882,4883,4886,4887,4889,4890,4892,4893,4896],{},"تأخذ الطبقة الأولى ما كتبه المستخدم، وتُلحق قبله ",[17,4873,4305],{},", ثم تطلب من ",[17,4876,4877],{},"libphonenumber"," تحليله بوصفه رقماً دولياً. إن كتب المستخدم ",[17,4880,4881],{},"447911123456"," سيتطابق مع المملكة المتحدة. وإن كتب ",[17,4884,4885],{},"1416..."," سيتطابق مع كندا تحديداً (لا مع NANP العام)، لأنّ ",[17,4888,4877],{}," يحمل قواعد رموز المناطق مدمجة أصلاً. توجد كتلة catch لأنّ ",[17,4891,4877],{}," يرمي استثناءً على الإدخال الجزئي. ",[17,4894,4895],{},"447"," رقم ناقص؛ يرفع التحليل استثناءً، فننتقل إلى الطبقة التالية.",[10,4898,4899,4900,4902,4903,4906,4907,3382,4909,4911,4912,4915,4916,4919],{},"الطبقة الثانية هي المسار المخصّص لمن يكتب الرقم كما يستعمله في بلده. ",[17,4901,4309],{}," لا يبدأ برمز اتصال، وبالتالي لا نفع من الطبقة الأولى. لكن إن كنّا نعرف بصمت أنّ المستخدم في مصر على الأرجح (اعتماداً على IP أو timezone, والمزيد عن هذا لاحقاً)، فبإمكاننا تمرير ",[17,4904,4905],{},"\"EG\""," تلميحاً للمحلِّل، وسيتعرّف ",[17,4908,4877],{},[17,4910,4309],{}," بوصفه رقم هاتف محمول مصري صحيح، ويجرّد الصفر الأول (البادئة الوطنية لمصر)، ويعيد الرقم الوطني المعياري ",[17,4913,4914],{},"1066105963",". الحارس ",[17,4917,4918],{},"digits.length >= 4"," يمنع المحلِّل من التعامل مع بدايات رموز المناطق ذات الرقمين بوصفها أرقاماً كاملة.",[10,4921,4922,4923,4926,4927,4929,4930,4933,4934,4937],{},"الطبقة الثالثة هي الاحتياطي لحالة \"كتبتُ ",[17,4924,4925],{},"44"," فقط ولا شيء غير ذلك\". في هذه الحالة لا مسار في ",[17,4928,4877],{}," يستطيع المطابقة، فالمدخل قصير جداً. لكنّ فهرس أرقام الاتصال الخاصّ بنا يكفي: ",[17,4931,4932],{},"byDialDigits.get('44')"," يعيد مجموعة المملكة المتحدة بطول 1, وينتهي الأمر. هنا تُثبت بنية ",[17,4935,4936],{},"Map\u003Cstring, CountryOption[]>"," الموروثة من النسخة القديمة قيمتها.",[10,4939,4940],{},"قد تبدو ثلاث طبقات كثيرة. في الحقيقة هي الحدّ الأدنى: كلّ طبقة تعالج مدخلات ترفضها الاثنتان الأخريان.",[10,4942,4943],{},"المُعالِج الذي يستدعيها يبقى صغيراً:",[47,4945,4947],{"className":200,"code":4946,"language":202,"meta":52,"style":52},"const detectAndApply = useDebounceFn(\n  () => {\n    if (!props.detectFromInput) return;\n    if (userPickedCountry.value || selectedIso2.value) return;\n    const current = phone.value;\n    if (!current) return;\n    const match = matchLeadingDialCode(current);\n    if (!match) return;\n    autoSettingCountry.value = true;\n    selectedIso2.value = match.country.value;\n    phone.value = match.nationalNumber;\n  },\n  computed(() => Math.max(0, props.detectDebounceMs))\n);\n",[17,4948,4949,4963,4971,4986,5002,5014,5029,5043,5058,5069,5079,5089,5093,5114],{"__ignoreMap":52},[56,4950,4951,4953,4956,4958,4961],{"class":58,"line":59},[56,4952,410],{"class":209},[56,4954,4955],{"class":249}," detectAndApply",[56,4957,270],{"class":209},[56,4959,4960],{"class":72}," useDebounceFn",[56,4962,1063],{"class":62},[56,4964,4965,4967,4969],{"class":58,"line":91},[56,4966,1230],{"class":62},[56,4968,298],{"class":209},[56,4970,719],{"class":62},[56,4972,4973,4975,4977,4979,4982,4984],{"class":58,"line":97},[56,4974,802],{"class":209},[56,4976,447],{"class":62},[56,4978,759],{"class":209},[56,4980,4981],{"class":62},"props.detectFromInput) ",[56,4983,736],{"class":209},[56,4985,739],{"class":62},[56,4987,4988,4990,4993,4995,4998,5000],{"class":58,"line":103},[56,4989,802],{"class":209},[56,4991,4992],{"class":62}," (userPickedCountry.value ",[56,4994,765],{"class":209},[56,4996,4997],{"class":62}," selectedIso2.value) ",[56,4999,736],{"class":209},[56,5001,739],{"class":62},[56,5003,5004,5006,5009,5011],{"class":58,"line":109},[56,5005,264],{"class":209},[56,5007,5008],{"class":249}," current",[56,5010,270],{"class":209},[56,5012,5013],{"class":62}," phone.value;\n",[56,5015,5016,5018,5020,5022,5025,5027],{"class":58,"line":115},[56,5017,802],{"class":209},[56,5019,447],{"class":62},[56,5021,759],{"class":209},[56,5023,5024],{"class":62},"current) ",[56,5026,736],{"class":209},[56,5028,739],{"class":62},[56,5030,5031,5033,5036,5038,5040],{"class":58,"line":121},[56,5032,264],{"class":209},[56,5034,5035],{"class":249}," match",[56,5037,270],{"class":209},[56,5039,4332],{"class":72},[56,5041,5042],{"class":62},"(current);\n",[56,5044,5045,5047,5049,5051,5054,5056],{"class":58,"line":126},[56,5046,802],{"class":209},[56,5048,447],{"class":62},[56,5050,759],{"class":209},[56,5052,5053],{"class":62},"match) ",[56,5055,736],{"class":209},[56,5057,739],{"class":62},[56,5059,5060,5063,5065,5067],{"class":58,"line":132},[56,5061,5062],{"class":62},"    autoSettingCountry.value ",[56,5064,76],{"class":209},[56,5066,816],{"class":249},[56,5068,739],{"class":62},[56,5070,5071,5074,5076],{"class":58,"line":137},[56,5072,5073],{"class":62},"    selectedIso2.value ",[56,5075,76],{"class":209},[56,5077,5078],{"class":62}," match.country.value;\n",[56,5080,5081,5084,5086],{"class":58,"line":142},[56,5082,5083],{"class":62},"    phone.value ",[56,5085,76],{"class":209},[56,5087,5088],{"class":62}," match.nationalNumber;\n",[56,5090,5091],{"class":58,"line":148},[56,5092,1155],{"class":62},[56,5094,5095,5098,5100,5102,5104,5107,5109,5111],{"class":58,"line":154},[56,5096,5097],{"class":72},"  computed",[56,5099,1453],{"class":62},[56,5101,298],{"class":209},[56,5103,4687],{"class":62},[56,5105,5106],{"class":72},"max",[56,5108,425],{"class":62},[56,5110,1092],{"class":249},[56,5112,5113],{"class":62},", props.detectDebounceMs))\n",[56,5115,5116],{"class":58,"line":159},[56,5117,623],{"class":62},[10,5119,5120,5121,3168,5124,5127,5128,5131],{},"يقرأ ",[17,5122,5123],{},"useDebounceFn",[17,5125,5126],{},"phone.value"," لحظة إطلاق المؤقّت، لا لحظة جدولته، فتذوب دفعة من الضغطات على المفاتيح في تحليل واحد. ويمنع ",[17,5129,5130],{},"userPickedCountry.value"," المكتشفَ التلقائيّ من الدهس على اختيار يدوي. متى استعمل المستخدم القائمة، ينسحب المطابِق مباشرة. أمّا مسح الحقل فلا يخضع لأيّ debounce, بل يعيد المنتقي إلى الاختفاء فوراً:",[47,5133,5135],{"className":200,"code":5134,"language":202,"meta":52,"style":52},"if (!cleaned) {\n  autoSettingCountry.value = true;\n  selectedIso2.value = '';\n  phone.value = '';\n  userPickedCountry.value = false;\n  return;\n}\n",[17,5136,5137,5149,5160,5171,5182,5193,5199],{"__ignoreMap":52},[56,5138,5139,5142,5144,5146],{"class":58,"line":59},[56,5140,5141],{"class":209},"if",[56,5143,447],{"class":62},[56,5145,759],{"class":209},[56,5147,5148],{"class":62},"cleaned) {\n",[56,5150,5151,5154,5156,5158],{"class":58,"line":91},[56,5152,5153],{"class":62},"  autoSettingCountry.value ",[56,5155,76],{"class":209},[56,5157,816],{"class":249},[56,5159,739],{"class":62},[56,5161,5162,5165,5167,5169],{"class":58,"line":97},[56,5163,5164],{"class":62},"  selectedIso2.value ",[56,5166,76],{"class":209},[56,5168,4080],{"class":79},[56,5170,739],{"class":62},[56,5172,5173,5176,5178,5180],{"class":58,"line":103},[56,5174,5175],{"class":62},"  phone.value ",[56,5177,76],{"class":209},[56,5179,4080],{"class":79},[56,5181,739],{"class":62},[56,5183,5184,5187,5189,5191],{"class":58,"line":109},[56,5185,5186],{"class":62},"  userPickedCountry.value ",[56,5188,76],{"class":209},[56,5190,928],{"class":249},[56,5192,739],{"class":62},[56,5194,5195,5197],{"class":58,"line":115},[56,5196,370],{"class":209},[56,5198,739],{"class":62},[56,5200,5201],{"class":58,"line":121},[56,5202,382],{"class":62},[10,5204,5205],{},"تأخير المسح يعني بقاء القائمة معلّقة 150 ميلي ثانية بعد إفراغ الحقل، وهو ما يبدو خاطئاً في كلّ مرّة.",[39,5207,5209],{"id":5208},"سلسلة-اكتشاف-البيئة-ولماذا-هي-قابلة-للاستبدال","سلسلة اكتشاف البيئة، ولماذا هي قابلة للاستبدال",[10,5211,5212,5213,5216],{},"تعتمد الطبقة الثانية أعلاه على ",[17,5214,5215],{},"inferredCountry.value","، أي أفضل تخمين للمكوّن عن موقع المستخدم، يُضبط بصمت قبل أن يكتب حرفاً واحداً. كان \"أفضل تخمين\" في النسخة القديمة قيمة احتياطية مثبّتة على السعودية أو مصر، لأنّهما السوقان اللذان استهدفتهما النسخة الداخلية. المكتبة لا تملك ترف هذا الافتراض.",[10,5218,5219,5220,222],{},"السلسلة الجديدة هي ",[17,5221,5222],{},"detectCountry()",[47,5224,5226],{"className":200,"code":5225,"language":202,"meta":52,"style":52},"export async function detectCountry(opts: DetectCountryOptions = {}): Promise\u003Cstring> {\n  const {\n    strategy = 'auto',\n    ipEndpoint = 'https:\u002F\u002Fipapi.co\u002Fjson\u002F',\n    defaultCountry = 'US',\n    timeoutMs = 2000,\n    cache = true,\n  } = opts;\n\n  if (cache) {\n    const cached = readCache();\n    if (cached) return cached;\n  }\n\n  if (strategy === 'none') {\n    return defaultCountry.toUpperCase();\n  }\n\n  if (strategy === 'auto') {\n    const ipResult = await tryIp(ipEndpoint, timeoutMs);\n    if (ipResult) {\n      if (cache) writeCache(ipResult);\n      return ipResult;\n    }\n  }\n\n  const localResult = tryTimezone() ?? tryLocale();\n  const final = (localResult ?? defaultCountry).toUpperCase();\n  if (cache) writeCache(final);\n  return final;\n}\n",[17,5227,5228,5264,5270,5282,5294,5306,5318,5329,5338,5342,5349,5363,5375,5379,5383,5397,5409,5413,5417,5429,5446,5453,5466,5473,5477,5481,5485,5507,5528,5539,5546],{"__ignoreMap":52},[56,5229,5230,5232,5234,5236,5239,5241,5244,5246,5249,5251,5254,5256,5258,5260,5262],{"class":58,"line":59},[56,5231,479],{"class":209},[56,5233,698],{"class":209},[56,5235,213],{"class":209},[56,5237,5238],{"class":72}," detectCountry",[56,5240,425],{"class":62},[56,5242,5243],{"class":284},"opts",[56,5245,222],{"class":209},[56,5247,5248],{"class":72}," DetectCountryOptions",[56,5250,270],{"class":209},[56,5252,5253],{"class":62}," {})",[56,5255,222],{"class":209},[56,5257,225],{"class":72},[56,5259,63],{"class":62},[56,5261,1727],{"class":249},[56,5263,1736],{"class":62},[56,5265,5266,5268],{"class":58,"line":91},[56,5267,340],{"class":209},[56,5269,719],{"class":62},[56,5271,5272,5275,5277,5280],{"class":58,"line":97},[56,5273,5274],{"class":249},"    strategy",[56,5276,270],{"class":209},[56,5278,5279],{"class":79}," 'auto'",[56,5281,1300],{"class":62},[56,5283,5284,5287,5289,5292],{"class":58,"line":103},[56,5285,5286],{"class":249},"    ipEndpoint",[56,5288,270],{"class":209},[56,5290,5291],{"class":79}," 'https:\u002F\u002Fipapi.co\u002Fjson\u002F'",[56,5293,1300],{"class":62},[56,5295,5296,5299,5301,5304],{"class":58,"line":109},[56,5297,5298],{"class":249},"    defaultCountry",[56,5300,270],{"class":209},[56,5302,5303],{"class":79}," 'US'",[56,5305,1300],{"class":62},[56,5307,5308,5311,5313,5316],{"class":58,"line":115},[56,5309,5310],{"class":249},"    timeoutMs",[56,5312,270],{"class":209},[56,5314,5315],{"class":249}," 2000",[56,5317,1300],{"class":62},[56,5319,5320,5323,5325,5327],{"class":58,"line":121},[56,5321,5322],{"class":249},"    cache",[56,5324,270],{"class":209},[56,5326,816],{"class":249},[56,5328,1300],{"class":62},[56,5330,5331,5333,5335],{"class":58,"line":126},[56,5332,905],{"class":62},[56,5334,76],{"class":209},[56,5336,5337],{"class":62}," opts;\n",[56,5339,5340],{"class":58,"line":132},[56,5341,335],{"emptyLinePlaceholder":334},[56,5343,5344,5346],{"class":58,"line":137},[56,5345,243],{"class":209},[56,5347,5348],{"class":62}," (cache) {\n",[56,5350,5351,5353,5356,5358,5361],{"class":58,"line":142},[56,5352,264],{"class":209},[56,5354,5355],{"class":249}," cached",[56,5357,270],{"class":209},[56,5359,5360],{"class":72}," readCache",[56,5362,377],{"class":62},[56,5364,5365,5367,5370,5372],{"class":58,"line":148},[56,5366,802],{"class":209},[56,5368,5369],{"class":62}," (cached) ",[56,5371,736],{"class":209},[56,5373,5374],{"class":62}," cached;\n",[56,5376,5377],{"class":58,"line":154},[56,5378,329],{"class":62},[56,5380,5381],{"class":58,"line":159},[56,5382,335],{"emptyLinePlaceholder":334},[56,5384,5385,5387,5390,5392,5395],{"class":58,"line":913},[56,5386,243],{"class":209},[56,5388,5389],{"class":62}," (strategy ",[56,5391,749],{"class":209},[56,5393,5394],{"class":79}," 'none'",[56,5396,259],{"class":62},[56,5398,5399,5401,5404,5407],{"class":58,"line":933},[56,5400,316],{"class":209},[56,5402,5403],{"class":62}," defaultCountry.",[56,5405,5406],{"class":72},"toUpperCase",[56,5408,377],{"class":62},[56,5410,5411],{"class":58,"line":952},[56,5412,329],{"class":62},[56,5414,5415],{"class":58,"line":964},[56,5416,335],{"emptyLinePlaceholder":334},[56,5418,5419,5421,5423,5425,5427],{"class":58,"line":969},[56,5420,243],{"class":209},[56,5422,5389],{"class":62},[56,5424,749],{"class":209},[56,5426,5279],{"class":79},[56,5428,259],{"class":62},[56,5430,5431,5433,5436,5438,5440,5443],{"class":58,"line":2784},[56,5432,264],{"class":209},[56,5434,5435],{"class":249}," ipResult",[56,5437,270],{"class":209},[56,5439,319],{"class":209},[56,5441,5442],{"class":72}," tryIp",[56,5444,5445],{"class":62},"(ipEndpoint, timeoutMs);\n",[56,5447,5448,5450],{"class":58,"line":2801},[56,5449,802],{"class":209},[56,5451,5452],{"class":62}," (ipResult) {\n",[56,5454,5455,5457,5460,5463],{"class":58,"line":2827},[56,5456,4072],{"class":209},[56,5458,5459],{"class":62}," (cache) ",[56,5461,5462],{"class":72},"writeCache",[56,5464,5465],{"class":62},"(ipResult);\n",[56,5467,5468,5470],{"class":58,"line":2845},[56,5469,4087],{"class":209},[56,5471,5472],{"class":62}," ipResult;\n",[56,5474,5475],{"class":58,"line":2866},[56,5476,1150],{"class":62},[56,5478,5479],{"class":58,"line":2876},[56,5480,329],{"class":62},[56,5482,5483],{"class":58,"line":4623},[56,5484,335],{"emptyLinePlaceholder":334},[56,5486,5487,5489,5492,5494,5497,5500,5502,5505],{"class":58,"line":4629},[56,5488,340],{"class":209},[56,5490,5491],{"class":249}," localResult",[56,5493,270],{"class":209},[56,5495,5496],{"class":72}," tryTimezone",[56,5498,5499],{"class":62},"() ",[56,5501,4460],{"class":209},[56,5503,5504],{"class":72}," tryLocale",[56,5506,377],{"class":62},[56,5508,5509,5511,5514,5516,5519,5521,5524,5526],{"class":58,"line":4634},[56,5510,340],{"class":209},[56,5512,5513],{"class":249}," final",[56,5515,270],{"class":209},[56,5517,5518],{"class":62}," (localResult ",[56,5520,4460],{"class":209},[56,5522,5523],{"class":62}," defaultCountry).",[56,5525,5406],{"class":72},[56,5527,377],{"class":62},[56,5529,5530,5532,5534,5536],{"class":58,"line":4644},[56,5531,243],{"class":209},[56,5533,5459],{"class":62},[56,5535,5462],{"class":72},[56,5537,5538],{"class":62},"(final);\n",[56,5540,5541,5543],{"class":58,"line":4650},[56,5542,370],{"class":209},[56,5544,5545],{"class":62}," final;\n",[56,5547,5548],{"class":58,"line":4655},[56,5549,382],{"class":62},[10,5551,5552],{},"أربع إشارات تُقيَّم بترتيب الكلفة: الأرخص المخبَّأ أولاً، والأغلى الذي يمرّ عبر الشبكة أخيراً.",[5554,5555,5556,5563,5574,5602,5615],"ol",{},[572,5557,5558,5559,5562],{},"تخزين مؤقّت في ",[17,5560,5561],{},"sessionStorage",". المستخدم حسم دولته أصلاً ضمن هذه الجلسة، فلا داعي لتكرار العمل.",[572,5564,5565,5566,5569,5570,5573],{},"تحديد الموقع الجغرافي عبر IP باستخدام ",[17,5567,5568],{},"ipapi.co"," (قابل للتكوين). أدقّ إشارة، لكنّها تكلّف نداء شبكة وقدراً يسيراً من الخصوصية. من لا يريد هذا الطلب يمرّر ",[17,5571,5572],{},"strategy: 'locale'"," ليتخطّى الخطوة كلياً.",[572,5575,5576,5577,5580,5581,5584,5585,5588,5589,5584,5592,5588,5595,5584,5598,5601],{},"المنطقة الزمنية عبر ",[17,5578,5579],{},"Intl.DateTimeFormat().resolvedOptions().timeZone",", يُبحث عنها في جدول صنعتُه يدوياً يربط ",[17,5582,5583],{},"Africa\u002FCairo"," بـ ",[17,5586,5587],{},"EG",", و",[17,5590,5591],{},"Asia\u002FRiyadh",[17,5593,5594],{},"SA",[17,5596,5597],{},"Europe\u002FLondon",[17,5599,5600],{},"GB",", وهكذا. يغطّي الجدول أكثف المناطق سكاناً، وهو صريح بأنّه لا يغطّي كلّ منطقة. عدم التطابق يمرّ إلى ما بعده.",[572,5603,5604,5607,5608,1505,5611,5614],{},[17,5605,5606],{},"navigator.language"," الذي يعطي ",[17,5609,5610],{},"en-EG",[17,5612,5613],{},"ar-SA"," وأشباهها. لاحقة المنطقة هي الدولة. أقلّ موثوقية (فالمستخدمون يتنقّلون بينما locale المتصفح لا)، لكنّها بلا كلفة.",[572,5616,5617,5618,5621],{},"القيمة الافتراضية التي يمرّرها المستدعي. ",[17,5619,5620],{},"defaultCountry: 'US'"," هو الافتراضي في المكتبة، وللمستهلك حرية اختيار غيره.",[10,5623,5624],{},"خطوة IP هي التي احتاجت عناية حقيقية، لأنّ الطلب قد يتعلّق:",[47,5626,5628],{"className":200,"code":5627,"language":202,"meta":52,"style":52},"async function tryIp(endpoint: string, timeoutMs: number): Promise\u003Cstring | null> {\n  if (!isBrowser() || typeof fetch !== 'function') return null;\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(), timeoutMs);\n  try {\n    const res = await fetch(endpoint, { signal: controller.signal, credentials: 'omit' });\n    if (!res.ok) return null;\n    const data = (await res.json()) as { country_code?: string; country?: string };\n    const code = (data.country_code ?? data.country ?? '').toString().toUpperCase();\n    return \u002F^[A-Z]{2}$\u002F.test(code) ? code : null;\n  } catch {\n    return null;\n  } finally {\n    clearTimeout(timer);\n  }\n}\n",[17,5629,5630,5672,5705,5721,5746,5752,5774,5791,5837,5870,5907,5915,5923,5931,5939,5943],{"__ignoreMap":52},[56,5631,5632,5634,5636,5638,5640,5643,5645,5647,5649,5652,5654,5656,5658,5660,5662,5664,5666,5668,5670],{"class":58,"line":59},[56,5633,210],{"class":209},[56,5635,213],{"class":209},[56,5637,5442],{"class":72},[56,5639,425],{"class":62},[56,5641,5642],{"class":284},"endpoint",[56,5644,222],{"class":209},[56,5646,2571],{"class":249},[56,5648,1730],{"class":62},[56,5650,5651],{"class":284},"timeoutMs",[56,5653,222],{"class":209},[56,5655,1791],{"class":249},[56,5657,1711],{"class":62},[56,5659,222],{"class":209},[56,5661,225],{"class":72},[56,5663,63],{"class":62},[56,5665,1727],{"class":249},[56,5667,660],{"class":209},[56,5669,668],{"class":249},[56,5671,1736],{"class":62},[56,5673,5674,5676,5678,5680,5683,5685,5687,5689,5692,5694,5697,5699,5701,5703],{"class":58,"line":91},[56,5675,243],{"class":209},[56,5677,447],{"class":62},[56,5679,759],{"class":209},[56,5681,5682],{"class":72},"isBrowser",[56,5684,5499],{"class":62},[56,5686,765],{"class":209},[56,5688,1743],{"class":209},[56,5690,5691],{"class":62}," fetch ",[56,5693,729],{"class":209},[56,5695,5696],{"class":79}," 'function'",[56,5698,452],{"class":62},[56,5700,736],{"class":209},[56,5702,668],{"class":249},[56,5704,739],{"class":62},[56,5706,5707,5709,5712,5714,5716,5719],{"class":58,"line":97},[56,5708,340],{"class":209},[56,5710,5711],{"class":249}," controller",[56,5713,270],{"class":209},[56,5715,1057],{"class":209},[56,5717,5718],{"class":72}," AbortController",[56,5720,377],{"class":62},[56,5722,5723,5725,5728,5730,5733,5735,5737,5740,5743],{"class":58,"line":103},[56,5724,340],{"class":209},[56,5726,5727],{"class":249}," timer",[56,5729,270],{"class":209},[56,5731,5732],{"class":72}," setTimeout",[56,5734,1453],{"class":62},[56,5736,298],{"class":209},[56,5738,5739],{"class":62}," controller.",[56,5741,5742],{"class":72},"abort",[56,5744,5745],{"class":62},"(), timeoutMs);\n",[56,5747,5748,5750],{"class":58,"line":109},[56,5749,795],{"class":209},[56,5751,719],{"class":62},[56,5753,5754,5756,5759,5761,5763,5766,5769,5772],{"class":58,"line":115},[56,5755,264],{"class":209},[56,5757,5758],{"class":249}," res",[56,5760,270],{"class":209},[56,5762,319],{"class":209},[56,5764,5765],{"class":72}," fetch",[56,5767,5768],{"class":62},"(endpoint, { signal: controller.signal, credentials: ",[56,5770,5771],{"class":79},"'omit'",[56,5773,3851],{"class":62},[56,5775,5776,5778,5780,5782,5785,5787,5789],{"class":58,"line":121},[56,5777,802],{"class":209},[56,5779,447],{"class":62},[56,5781,759],{"class":209},[56,5783,5784],{"class":62},"res.ok) ",[56,5786,736],{"class":209},[56,5788,668],{"class":249},[56,5790,739],{"class":62},[56,5792,5793,5795,5798,5800,5802,5805,5808,5811,5814,5816,5818,5821,5823,5825,5828,5830,5832,5834],{"class":58,"line":126},[56,5794,264],{"class":209},[56,5796,5797],{"class":249}," data",[56,5799,270],{"class":209},[56,5801,447],{"class":62},[56,5803,5804],{"class":209},"await",[56,5806,5807],{"class":62}," res.",[56,5809,5810],{"class":72},"json",[56,5812,5813],{"class":62},"()) ",[56,5815,276],{"class":209},[56,5817,1609],{"class":62},[56,5819,5820],{"class":284},"country_code",[56,5822,1615],{"class":209},[56,5824,2571],{"class":249},[56,5826,5827],{"class":62},"; ",[56,5829,3883],{"class":284},[56,5831,1615],{"class":209},[56,5833,2571],{"class":249},[56,5835,5836],{"class":62}," };\n",[56,5838,5839,5841,5844,5846,5849,5851,5854,5856,5858,5860,5863,5866,5868],{"class":58,"line":132},[56,5840,264],{"class":209},[56,5842,5843],{"class":249}," code",[56,5845,270],{"class":209},[56,5847,5848],{"class":62}," (data.country_code ",[56,5850,4460],{"class":209},[56,5852,5853],{"class":62}," data.country ",[56,5855,4460],{"class":209},[56,5857,4080],{"class":79},[56,5859,3187],{"class":62},[56,5861,5862],{"class":72},"toString",[56,5864,5865],{"class":62},"().",[56,5867,5406],{"class":72},[56,5869,377],{"class":62},[56,5871,5872,5874,5876,5879,5882,5885,5888,5890,5893,5896,5898,5901,5903,5905],{"class":58,"line":137},[56,5873,316],{"class":209},[56,5875,3054],{"class":79},[56,5877,5878],{"class":209},"^",[56,5880,5881],{"class":249},"[A-Z]",[56,5883,5884],{"class":209},"{2}$",[56,5886,5887],{"class":79},"\u002F",[56,5889,24],{"class":62},[56,5891,5892],{"class":72},"test",[56,5894,5895],{"class":62},"(code) ",[56,5897,2707],{"class":209},[56,5899,5900],{"class":62}," code ",[56,5902,222],{"class":209},[56,5904,668],{"class":249},[56,5906,739],{"class":62},[56,5908,5909,5911,5913],{"class":58,"line":142},[56,5910,905],{"class":62},[56,5912,4481],{"class":209},[56,5914,719],{"class":62},[56,5916,5917,5919,5921],{"class":58,"line":148},[56,5918,316],{"class":209},[56,5920,668],{"class":249},[56,5922,739],{"class":62},[56,5924,5925,5927,5929],{"class":58,"line":154},[56,5926,905],{"class":62},[56,5928,908],{"class":209},[56,5930,719],{"class":62},[56,5932,5933,5936],{"class":58,"line":159},[56,5934,5935],{"class":72},"    clearTimeout",[56,5937,5938],{"class":62},"(timer);\n",[56,5940,5941],{"class":58,"line":913},[56,5942,329],{"class":62},[56,5944,5945],{"class":58,"line":933},[56,5946,382],{"class":62},[10,5948,5949,5950,5952,5953,5955,5956,5959,5960,5963,5964,5967,5968,5971,5972,5974],{},"يعني ",[17,5951,1999],{}," مع مؤقّت من ثانيتين أنّ خدمة IP بطيئة لا تستطيع تعليق سلسلة الاكتشاف لأكثر من ",[17,5954,5651],{},". وتُبقي ",[17,5957,5958],{},"credentials: 'omit'"," الكوكيز خارج الطلب المرسَل إلى الطرف الثالث الذي ضبطتَه. أمّا شكل الاستجابة فيُتحقّق منه بالتعبير النمطي ",[17,5961,5962],{},"\u002F^[A-Z]{2}$\u002F",", لأنّ بعض واجهات IP تعيد ",[17,5965,5966],{},"\"GB\"",", وبعضها الآخر ",[17,5969,5970],{},"\"United Kingdom\"",", وبعضها ",[17,5973,674],{}," حين لا تعرف. التعبير النمطي هو البوّابة.",[10,5976,5977,5978,5981],{},"يعيش ",[17,5979,5980],{},"detectCountry"," خارج المكوّن عن قصد:",[47,5983,5985],{"className":200,"code":5984,"language":202,"meta":52,"style":52},"import { detectCountry } from '@alikhalilll\u002Fui\u002Ftell-input';\n\nconst country = await detectCountry({ strategy: 'locale' });\n",[17,5986,5987,6001,6005],{"__ignoreMap":52},[56,5988,5989,5991,5994,5996,5999],{"class":58,"line":59},[56,5990,2280],{"class":209},[56,5992,5993],{"class":62}," { detectCountry } ",[56,5995,2292],{"class":209},[56,5997,5998],{"class":79}," '@alikhalilll\u002Fui\u002Ftell-input'",[56,6000,739],{"class":62},[56,6002,6003],{"class":58,"line":91},[56,6004,335],{"emptyLinePlaceholder":334},[56,6006,6007,6009,6011,6013,6015,6017,6020,6023],{"class":58,"line":97},[56,6008,410],{"class":209},[56,6010,3954],{"class":249},[56,6012,270],{"class":209},[56,6014,319],{"class":209},[56,6016,5238],{"class":72},[56,6018,6019],{"class":62},"({ strategy: ",[56,6021,6022],{"class":79},"'locale'",[56,6024,3851],{"class":62},[10,6026,6027,6028,6031,6032,6035],{},"بلا اعتماد على Vue, ولا refs تفاعلية، ولا ",[17,6029,6030],{},"onMounted",". يعمل داخل server middleware يريد ضبط كوكي، وفي CLI يريد طباعة موقع المستخدم المرجّح، وفي unit test يريد التحكّم بالاستراتيجية. أمّا الغلاف التفاعلي (",[17,6033,6034],{},"useCountryDetection",") فقشرة رقيقة فوقه للحالة داخل المكوّن. سطحان، وتنفيذ واحد.",[10,6037,6038],{},"والسلسلة كلّها قابلة للاستبدال. إن كنتَ تكتشف الموقع الجغرافي في الخادم أصلاً وتمرّره عبر SSR, فلن ترغب في أن يُعيد المكوّن نداء IP:",[47,6040,6042],{"className":49,"code":6041,"language":51,"meta":52,"style":52},"\u003CATellInput\n  v-model:phone=\"phone\"\n  v-model:country=\"country\"\n  :detector=\"(opts) => Promise.resolve(serverDetectedCountry.value)\"\n\u002F>\n",[17,6043,6044,6051,6070,6086,6118],{"__ignoreMap":52},[56,6045,6046,6048],{"class":58,"line":59},[56,6047,63],{"class":62},[56,6049,6050],{"class":66},"ATellInput\n",[56,6052,6053,6056,6058,6061,6063,6065,6067],{"class":58,"line":91},[56,6054,6055],{"class":72},"  v-model",[56,6057,222],{"class":62},[56,6059,6060],{"class":72},"phone",[56,6062,76],{"class":62},[56,6064,80],{"class":79},[56,6066,6060],{"class":62},[56,6068,6069],{"class":79},"\"\n",[56,6071,6072,6074,6076,6078,6080,6082,6084],{"class":58,"line":97},[56,6073,6055],{"class":72},[56,6075,222],{"class":62},[56,6077,3883],{"class":72},[56,6079,76],{"class":62},[56,6081,80],{"class":79},[56,6083,3883],{"class":62},[56,6085,6069],{"class":79},[56,6087,6088,6091,6094,6096,6098,6100,6102,6104,6106,6108,6110,6113,6116],{"class":58,"line":103},[56,6089,6090],{"class":62},"  :",[56,6092,6093],{"class":72},"detector",[56,6095,76],{"class":62},[56,6097,80],{"class":79},[56,6099,425],{"class":62},[56,6101,5243],{"class":284},[56,6103,452],{"class":62},[56,6105,298],{"class":209},[56,6107,225],{"class":249},[56,6109,24],{"class":62},[56,6111,6112],{"class":72},"resolve",[56,6114,6115],{"class":62},"(serverDetectedCountry.value)",[56,6117,6069],{"class":79},[56,6119,6120],{"class":58,"line":109},[56,6121,6122],{"class":62},"\u002F>\n",[10,6124,6125,6126,6128],{},"يستبدل الـ prop المسمّى ",[17,6127,6093],{}," السلسلةَ المدمجةَ بأكملها. يواصل المكوّن تشغيل بقية منطق الاكتشاف (يضبط الدولة المستنتجة، ويستخدمها تلميحاً للطبقة الثانية، وهكذا)، لكنّ مصدر الإجابة يصبح ما حدّدتَه أنت.",[39,6130,6132],{"id":6131},"منتقٍ-متجاوب-مكوّن-واحد-بتقديمَين","منتقٍ متجاوب: مكوّن واحد بتقديمَين",[10,6134,6135],{},"كانت قائمة الدولة القديمة popover في كلّ مقاسات الشاشة. على الهاتف كان ذلك يعني قائمة قابلة للتمرير من 250 صفّاً مثبّتة على زرّ عرضه 38 بكسل، مع حقل بحث تحجبه لوحة المفاتيح باستمرار. كان يعمل. لكنّه يعمل بشكل رديء.",[10,6137,6138,6139,6142,6143,6146],{},"يعتمد المنتقي الجديد على العنصر الأساسي ",[17,6140,6141],{},"AResponsivePopover"," من المكتبة نفسها، وهو popover على الحاسوب وdrawer عبر ",[17,6144,6145],{},"vaul-vue"," على الجوّال:",[47,6148,6150],{"className":49,"code":6149,"language":51,"meta":52,"style":52},"\u003CAResponsivePopover v-model:open=\"open\">\n  \u003CAResponsivePopoverTrigger as-child>\n    \u003C!-- trigger button -->\n  \u003C\u002FAResponsivePopoverTrigger>\n\n  \u003CAResponsivePopoverContent\n    :popover-class=\"\n      cn(\n        'w-[min(20rem,calc(100vw-2rem))] max-h-[min(22rem,var(--reka-popover-content-available-height))]',\n        props.popoverClass\n      )\n    \"\n    :drawer-class=\"cn('max-h-[80vh] pb-4', props.drawerClass)\"\n  >\n    \u003C!-- search + list -->\n  \u003C\u002FAResponsivePopoverContent>\n\u003C\u002FAResponsivePopover>\n",[17,6151,6152,6176,6181,6186,6191,6195,6200,6205,6210,6215,6220,6225,6230,6235,6240,6245,6250],{"__ignoreMap":52},[56,6153,6154,6156,6158,6161,6163,6166,6168,6170,6172,6174],{"class":58,"line":59},[56,6155,63],{"class":62},[56,6157,6141],{"class":66},[56,6159,6160],{"class":72}," v-model",[56,6162,222],{"class":62},[56,6164,6165],{"class":72},"open",[56,6167,76],{"class":62},[56,6169,80],{"class":79},[56,6171,6165],{"class":62},[56,6173,80],{"class":79},[56,6175,88],{"class":62},[56,6177,6178],{"class":58,"line":91},[56,6179,6180],{"class":62},"  \u003CAResponsivePopoverTrigger as-child>\n",[56,6182,6183],{"class":58,"line":97},[56,6184,6185],{"class":62},"    \u003C!-- trigger button -->\n",[56,6187,6188],{"class":58,"line":103},[56,6189,6190],{"class":62},"  \u003C\u002FAResponsivePopoverTrigger>\n",[56,6192,6193],{"class":58,"line":109},[56,6194,335],{"emptyLinePlaceholder":334},[56,6196,6197],{"class":58,"line":115},[56,6198,6199],{"class":62},"  \u003CAResponsivePopoverContent\n",[56,6201,6202],{"class":58,"line":121},[56,6203,6204],{"class":62},"    :popover-class=\"\n",[56,6206,6207],{"class":58,"line":126},[56,6208,6209],{"class":62},"      cn(\n",[56,6211,6212],{"class":58,"line":132},[56,6213,6214],{"class":62},"        'w-[min(20rem,calc(100vw-2rem))] max-h-[min(22rem,var(--reka-popover-content-available-height))]',\n",[56,6216,6217],{"class":58,"line":137},[56,6218,6219],{"class":62},"        props.popoverClass\n",[56,6221,6222],{"class":58,"line":142},[56,6223,6224],{"class":62},"      )\n",[56,6226,6227],{"class":58,"line":148},[56,6228,6229],{"class":62},"    \"\n",[56,6231,6232],{"class":58,"line":154},[56,6233,6234],{"class":62},"    :drawer-class=\"cn('max-h-[80vh] pb-4', props.drawerClass)\"\n",[56,6236,6237],{"class":58,"line":159},[56,6238,6239],{"class":62},"  >\n",[56,6241,6242],{"class":58,"line":913},[56,6243,6244],{"class":62},"    \u003C!-- search + list -->\n",[56,6246,6247],{"class":58,"line":933},[56,6248,6249],{"class":62},"  \u003C\u002FAResponsivePopoverContent>\n",[56,6251,6252,6254,6256],{"class":58,"line":952},[56,6253,162],{"class":62},[56,6255,6141],{"class":66},[56,6257,88],{"class":62},[10,6259,6260,6261,1431,6264,6267,6268,6271,6272,6275,6276,6279],{},"يُمرَّر prop-ان للأصناف (",[17,6262,6263],{},"popoverClass",[17,6265,6266],{},"drawerClass",") كلّ على حدة، فيتمكّن المستهلك من تنسيق كلّ تقديم دون أن يرث الآخر شيئاً منه. الـ popover يريد ",[17,6269,6270],{},"max-h-[min(22rem,var(--reka-popover-content-available-height))]"," لأنّ ",[17,6273,6274],{},"reka-ui"," يُتيح الارتفاع المتاح كمتغيّر CSS. أمّا الـ drawer فيريد ",[17,6277,6278],{},"max-h-[80vh]"," لأنّه ورقة سفلية و\"80% من نافذة العرض\" سقفٌ منطقي. فرض مجموعة أصناف واحدة على الحالتين سيكون خطأً على إحداهما، لكنّ السماح للمستهلك بتجاوز كلّ منهما مستقلاً هو تحديداً نقطة الفصل التي تحتاجها المكتبة.",[10,6281,6282],{},"الفائدة الجانبية الأخرى تخصّ البحث. في وضع الـ drawer يرسو حقل البحث في أعلى ورقة تغطّي معظم الشاشة، ولوحة المفاتيح تدفع الورقة لأعلى بدل أن تدفنها. الشيفرة نفسها، والمكوّن نفسه، والشكل المناسب للجهاز. ليس media-query داخل CSS المستهلك، بل عنصر أساسي يعرف الفرق بنفسه.",[39,6284,6286],{"id":6285},"slots-لكلّ-شيء-أربعة-عشر-slot","Slots لكلّ شيء (أربعة عشر Slot)",[10,6288,6289],{},"كانت النسخة القديمة كتلة واحدة صمّاء. تنسيق رسالة الخطأ يعني ترقيع المكوّن. تغيير أيقونة السهم يعني ترقيع المكوّن. استبدال علَم الدولة بواحد من CDN خاصّ بك يعني ترقيع المكوّن.",[10,6291,6292],{},"النسخة الجديدة تملك السلوك وتترك للمستهلك الشكل:",[47,6294,6296],{"className":200,"code":6295,"language":202,"meta":52,"style":52},"defineSlots\u003C{\n  prefix?: () => unknown;\n  suffix?: (props: { validationState: 'idle' | 'valid' | 'error'; validation: PhoneValidationResult }) => unknown;\n  'valid-icon'?: () => unknown;\n  'error-icon'?: (props: { reason: string }) => unknown;\n  hint?: (props: { country: string; formatHint: string; example: string | null }) => unknown;\n  error?: (props: { message: string; reason: string; validation: PhoneValidationResult }) => unknown;\n  trigger?: (props: { selectedCountry: CountryOption | null; open: boolean; sizeClasses: string }) => unknown;\n  chevron?: (props: { open: boolean }) => unknown;\n  flag?: (props: { country: CountryOption; context: 'trigger' | 'item' }) => unknown;\n  item?: (props: { country: CountryOption; selected: boolean; disabled: boolean; select: () => void }) => unknown;\n  'group-header'?: (props: { label: string; group: 'suggested' | 'all' }) => unknown;\n  search?: (props: { value: string; setValue: (v: string) => void; isSearching: boolean }) => unknown;\n  loading?: () => unknown;\n  empty?: (props: { query: string }) => unknown;\n}>();\n",[17,6297,6298,6306,6321,6373,6388,6418,6469,6515,6568,6597,6641,6702,6747,6807,6822,6852],{"__ignoreMap":52},[56,6299,6300,6303],{"class":58,"line":59},[56,6301,6302],{"class":72},"defineSlots",[56,6304,6305],{"class":62},"\u003C{\n",[56,6307,6308,6311,6313,6315,6317,6319],{"class":58,"line":91},[56,6309,6310],{"class":72},"  prefix",[56,6312,1615],{"class":209},[56,6314,351],{"class":62},[56,6316,298],{"class":209},[56,6318,1708],{"class":249},[56,6320,739],{"class":62},[56,6322,6323,6326,6328,6330,6332,6334,6336,6339,6341,6344,6346,6349,6351,6354,6356,6359,6361,6364,6367,6369,6371],{"class":58,"line":97},[56,6324,6325],{"class":72},"  suffix",[56,6327,1615],{"class":209},[56,6329,447],{"class":62},[56,6331,2682],{"class":284},[56,6333,222],{"class":209},[56,6335,1609],{"class":62},[56,6337,6338],{"class":284},"validationState",[56,6340,222],{"class":209},[56,6342,6343],{"class":79}," 'idle'",[56,6345,660],{"class":209},[56,6347,6348],{"class":79}," 'valid'",[56,6350,660],{"class":209},[56,6352,6353],{"class":79}," 'error'",[56,6355,5827],{"class":62},[56,6357,6358],{"class":284},"validation",[56,6360,222],{"class":209},[56,6362,6363],{"class":72}," PhoneValidationResult",[56,6365,6366],{"class":62}," }) ",[56,6368,298],{"class":209},[56,6370,1708],{"class":249},[56,6372,739],{"class":62},[56,6374,6375,6378,6380,6382,6384,6386],{"class":58,"line":103},[56,6376,6377],{"class":79},"  'valid-icon'",[56,6379,1615],{"class":209},[56,6381,351],{"class":62},[56,6383,298],{"class":209},[56,6385,1708],{"class":249},[56,6387,739],{"class":62},[56,6389,6390,6393,6395,6397,6399,6401,6403,6406,6408,6410,6412,6414,6416],{"class":58,"line":109},[56,6391,6392],{"class":79},"  'error-icon'",[56,6394,1615],{"class":209},[56,6396,447],{"class":62},[56,6398,2682],{"class":284},[56,6400,222],{"class":209},[56,6402,1609],{"class":62},[56,6404,6405],{"class":284},"reason",[56,6407,222],{"class":209},[56,6409,2571],{"class":249},[56,6411,6366],{"class":62},[56,6413,298],{"class":209},[56,6415,1708],{"class":249},[56,6417,739],{"class":62},[56,6419,6420,6423,6425,6427,6429,6431,6433,6435,6437,6439,6441,6444,6446,6448,6450,6453,6455,6457,6459,6461,6463,6465,6467],{"class":58,"line":115},[56,6421,6422],{"class":72},"  hint",[56,6424,1615],{"class":209},[56,6426,447],{"class":62},[56,6428,2682],{"class":284},[56,6430,222],{"class":209},[56,6432,1609],{"class":62},[56,6434,3883],{"class":284},[56,6436,222],{"class":209},[56,6438,2571],{"class":249},[56,6440,5827],{"class":62},[56,6442,6443],{"class":284},"formatHint",[56,6445,222],{"class":209},[56,6447,2571],{"class":249},[56,6449,5827],{"class":62},[56,6451,6452],{"class":284},"example",[56,6454,222],{"class":209},[56,6456,2571],{"class":249},[56,6458,660],{"class":209},[56,6460,668],{"class":249},[56,6462,6366],{"class":62},[56,6464,298],{"class":209},[56,6466,1708],{"class":249},[56,6468,739],{"class":62},[56,6470,6471,6474,6476,6478,6480,6482,6484,6487,6489,6491,6493,6495,6497,6499,6501,6503,6505,6507,6509,6511,6513],{"class":58,"line":121},[56,6472,6473],{"class":72},"  error",[56,6475,1615],{"class":209},[56,6477,447],{"class":62},[56,6479,2682],{"class":284},[56,6481,222],{"class":209},[56,6483,1609],{"class":62},[56,6485,6486],{"class":284},"message",[56,6488,222],{"class":209},[56,6490,2571],{"class":249},[56,6492,5827],{"class":62},[56,6494,6405],{"class":284},[56,6496,222],{"class":209},[56,6498,2571],{"class":249},[56,6500,5827],{"class":62},[56,6502,6358],{"class":284},[56,6504,222],{"class":209},[56,6506,6363],{"class":72},[56,6508,6366],{"class":62},[56,6510,298],{"class":209},[56,6512,1708],{"class":249},[56,6514,739],{"class":62},[56,6516,6517,6520,6522,6524,6526,6528,6530,6533,6535,6538,6540,6542,6544,6546,6548,6551,6553,6556,6558,6560,6562,6564,6566],{"class":58,"line":126},[56,6518,6519],{"class":72},"  trigger",[56,6521,1615],{"class":209},[56,6523,447],{"class":62},[56,6525,2682],{"class":284},[56,6527,222],{"class":209},[56,6529,1609],{"class":62},[56,6531,6532],{"class":284},"selectedCountry",[56,6534,222],{"class":209},[56,6536,6537],{"class":72}," CountryOption",[56,6539,660],{"class":209},[56,6541,668],{"class":249},[56,6543,5827],{"class":62},[56,6545,6165],{"class":284},[56,6547,222],{"class":209},[56,6549,6550],{"class":249}," boolean",[56,6552,5827],{"class":62},[56,6554,6555],{"class":284},"sizeClasses",[56,6557,222],{"class":209},[56,6559,2571],{"class":249},[56,6561,6366],{"class":62},[56,6563,298],{"class":209},[56,6565,1708],{"class":249},[56,6567,739],{"class":62},[56,6569,6570,6573,6575,6577,6579,6581,6583,6585,6587,6589,6591,6593,6595],{"class":58,"line":132},[56,6571,6572],{"class":72},"  chevron",[56,6574,1615],{"class":209},[56,6576,447],{"class":62},[56,6578,2682],{"class":284},[56,6580,222],{"class":209},[56,6582,1609],{"class":62},[56,6584,6165],{"class":284},[56,6586,222],{"class":209},[56,6588,6550],{"class":249},[56,6590,6366],{"class":62},[56,6592,298],{"class":209},[56,6594,1708],{"class":249},[56,6596,739],{"class":62},[56,6598,6599,6602,6604,6606,6608,6610,6612,6614,6616,6618,6620,6623,6625,6628,6630,6633,6635,6637,6639],{"class":58,"line":137},[56,6600,6601],{"class":72},"  flag",[56,6603,1615],{"class":209},[56,6605,447],{"class":62},[56,6607,2682],{"class":284},[56,6609,222],{"class":209},[56,6611,1609],{"class":62},[56,6613,3883],{"class":284},[56,6615,222],{"class":209},[56,6617,6537],{"class":72},[56,6619,5827],{"class":62},[56,6621,6622],{"class":284},"context",[56,6624,222],{"class":209},[56,6626,6627],{"class":79}," 'trigger'",[56,6629,660],{"class":209},[56,6631,6632],{"class":79}," 'item'",[56,6634,6366],{"class":62},[56,6636,298],{"class":209},[56,6638,1708],{"class":249},[56,6640,739],{"class":62},[56,6642,6643,6646,6648,6650,6652,6654,6656,6658,6660,6662,6664,6667,6669,6671,6673,6676,6678,6680,6682,6685,6687,6689,6691,6694,6696,6698,6700],{"class":58,"line":142},[56,6644,6645],{"class":72},"  item",[56,6647,1615],{"class":209},[56,6649,447],{"class":62},[56,6651,2682],{"class":284},[56,6653,222],{"class":209},[56,6655,1609],{"class":62},[56,6657,3883],{"class":284},[56,6659,222],{"class":209},[56,6661,6537],{"class":72},[56,6663,5827],{"class":62},[56,6665,6666],{"class":284},"selected",[56,6668,222],{"class":209},[56,6670,6550],{"class":249},[56,6672,5827],{"class":62},[56,6674,6675],{"class":284},"disabled",[56,6677,222],{"class":209},[56,6679,6550],{"class":249},[56,6681,5827],{"class":62},[56,6683,6684],{"class":72},"select",[56,6686,222],{"class":209},[56,6688,351],{"class":62},[56,6690,298],{"class":209},[56,6692,6693],{"class":249}," void",[56,6695,6366],{"class":62},[56,6697,298],{"class":209},[56,6699,1708],{"class":249},[56,6701,739],{"class":62},[56,6703,6704,6707,6709,6711,6713,6715,6717,6720,6722,6724,6726,6729,6731,6734,6736,6739,6741,6743,6745],{"class":58,"line":148},[56,6705,6706],{"class":79},"  'group-header'",[56,6708,1615],{"class":209},[56,6710,447],{"class":62},[56,6712,2682],{"class":284},[56,6714,222],{"class":209},[56,6716,1609],{"class":62},[56,6718,6719],{"class":284},"label",[56,6721,222],{"class":209},[56,6723,2571],{"class":249},[56,6725,5827],{"class":62},[56,6727,6728],{"class":284},"group",[56,6730,222],{"class":209},[56,6732,6733],{"class":79}," 'suggested'",[56,6735,660],{"class":209},[56,6737,6738],{"class":79}," 'all'",[56,6740,6366],{"class":62},[56,6742,298],{"class":209},[56,6744,1708],{"class":249},[56,6746,739],{"class":62},[56,6748,6749,6752,6754,6756,6758,6760,6762,6764,6766,6768,6770,6773,6775,6777,6780,6782,6784,6786,6788,6790,6792,6795,6797,6799,6801,6803,6805],{"class":58,"line":154},[56,6750,6751],{"class":72},"  search",[56,6753,1615],{"class":209},[56,6755,447],{"class":62},[56,6757,2682],{"class":284},[56,6759,222],{"class":209},[56,6761,1609],{"class":62},[56,6763,1703],{"class":284},[56,6765,222],{"class":209},[56,6767,2571],{"class":249},[56,6769,5827],{"class":62},[56,6771,6772],{"class":72},"setValue",[56,6774,222],{"class":209},[56,6776,447],{"class":62},[56,6778,6779],{"class":284},"v",[56,6781,222],{"class":209},[56,6783,2571],{"class":249},[56,6785,452],{"class":62},[56,6787,298],{"class":209},[56,6789,6693],{"class":249},[56,6791,5827],{"class":62},[56,6793,6794],{"class":284},"isSearching",[56,6796,222],{"class":209},[56,6798,6550],{"class":249},[56,6800,6366],{"class":62},[56,6802,298],{"class":209},[56,6804,1708],{"class":249},[56,6806,739],{"class":62},[56,6808,6809,6812,6814,6816,6818,6820],{"class":58,"line":159},[56,6810,6811],{"class":72},"  loading",[56,6813,1615],{"class":209},[56,6815,351],{"class":62},[56,6817,298],{"class":209},[56,6819,1708],{"class":249},[56,6821,739],{"class":62},[56,6823,6824,6827,6829,6831,6833,6835,6837,6840,6842,6844,6846,6848,6850],{"class":58,"line":913},[56,6825,6826],{"class":72},"  empty",[56,6828,1615],{"class":209},[56,6830,447],{"class":62},[56,6832,2682],{"class":284},[56,6834,222],{"class":209},[56,6836,1609],{"class":62},[56,6838,6839],{"class":284},"query",[56,6841,222],{"class":209},[56,6843,2571],{"class":249},[56,6845,6366],{"class":62},[56,6847,298],{"class":209},[56,6849,1708],{"class":249},[56,6851,739],{"class":62},[56,6853,6854],{"class":58,"line":933},[56,6855,6856],{"class":62},"}>();\n",[10,6858,6859,6860,1730,6863,6866,6867,5588,6870,6873,6874,1730,6877,1730,6880,6883,6884,1730,6887,1730,6889,1730,6892,1730,6895,6898],{},"أربعة عشر slot موزّعة على ثلاث مجموعات. المجموعة البصرية (",[17,6861,6862],{},"prefix",[17,6864,6865],{},"suffix",", وslot-ا الأيقونتين، و",[17,6868,6869],{},"hint",[17,6871,6872],{},"error",") تزيّن الحقل نفسه. مجموعة مُشغِّل المنتقي (",[17,6875,6876],{},"trigger",[17,6878,6879],{},"chevron",[17,6881,6882],{},"flag",") تستبدل الزرّ الذي ينقر عليه المستخدم. ومجموعة محتوى المنتقي (",[17,6885,6886],{},"search",[17,6888,2242],{},[17,6890,6891],{},"empty",[17,6893,6894],{},"group-header",[17,6896,6897],{},"item",") تستبدل أيّ شيء داخل الـ popover أو الـ drawer.",[10,6900,6901,6902,6905],{},"معظم slot-ات المنتقي تُمرَّر مباشرة إلى ",[17,6903,6904],{},"ACountrySelect",". المكوّن نفسه يتولّى التوجيه:",[47,6907,6909],{"className":49,"code":6908,"language":51,"meta":52,"style":52},"\u003Ctemplate v-if=\"$slots.trigger\" #trigger=\"slotProps\">\n  \u003Cslot name=\"trigger\" v-bind=\"slotProps\" \u002F>\n\u003C\u002Ftemplate>\n\u003Ctemplate v-if=\"$slots.chevron\" #chevron=\"slotProps\">\n  \u003Cslot name=\"chevron\" v-bind=\"slotProps\" \u002F>\n\u003C\u002Ftemplate>\n\u003C!-- ... and so on for each forwarded slot -->\n",[17,6910,6911,6944,6971,6979,7010,7033,7041],{"__ignoreMap":52},[56,6912,6913,6915,6917,6919,6921,6923,6926,6928,6931,6933,6935,6937,6940,6942],{"class":58,"line":59},[56,6914,63],{"class":62},[56,6916,2779],{"class":66},[56,6918,2056],{"class":209},[56,6920,76],{"class":62},[56,6922,80],{"class":79},[56,6924,6925],{"class":62},"$slots.trigger",[56,6927,80],{"class":79},[56,6929,6930],{"class":62}," #",[56,6932,6876],{"class":72},[56,6934,76],{"class":62},[56,6936,80],{"class":79},[56,6938,6939],{"class":62},"slotProps",[56,6941,80],{"class":79},[56,6943,88],{"class":62},[56,6945,6946,6948,6951,6954,6956,6959,6962,6964,6967,6969],{"class":58,"line":91},[56,6947,2787],{"class":62},[56,6949,6950],{"class":66},"slot",[56,6952,6953],{"class":72}," name",[56,6955,76],{"class":62},[56,6957,6958],{"class":79},"\"trigger\"",[56,6960,6961],{"class":72}," v-bind",[56,6963,76],{"class":62},[56,6965,6966],{"class":79},"\"slotProps\"",[56,6968,3054],{"class":3053},[56,6970,88],{"class":62},[56,6972,6973,6975,6977],{"class":58,"line":97},[56,6974,162],{"class":62},[56,6976,2779],{"class":66},[56,6978,88],{"class":62},[56,6980,6981,6983,6985,6987,6989,6991,6994,6996,6998,7000,7002,7004,7006,7008],{"class":58,"line":103},[56,6982,63],{"class":62},[56,6984,2779],{"class":66},[56,6986,2056],{"class":209},[56,6988,76],{"class":62},[56,6990,80],{"class":79},[56,6992,6993],{"class":62},"$slots.chevron",[56,6995,80],{"class":79},[56,6997,6930],{"class":62},[56,6999,6879],{"class":72},[56,7001,76],{"class":62},[56,7003,80],{"class":79},[56,7005,6939],{"class":62},[56,7007,80],{"class":79},[56,7009,88],{"class":62},[56,7011,7012,7014,7016,7018,7020,7023,7025,7027,7029,7031],{"class":58,"line":109},[56,7013,2787],{"class":62},[56,7015,6950],{"class":66},[56,7017,6953],{"class":72},[56,7019,76],{"class":62},[56,7021,7022],{"class":79},"\"chevron\"",[56,7024,6961],{"class":72},[56,7026,76],{"class":62},[56,7028,6966],{"class":79},[56,7030,3054],{"class":3053},[56,7032,88],{"class":62},[56,7034,7035,7037,7039],{"class":58,"line":115},[56,7036,162],{"class":62},[56,7038,2779],{"class":66},[56,7040,88],{"class":62},[56,7042,7043],{"class":58,"line":121},[56,7044,7045],{"class":434},"\u003C!-- ... and so on for each forwarded slot -->\n",[10,7047,7048,7049,7052,7053,7056,7057,7059],{},"الشرط ",[17,7050,7051],{},"v-if=\"$slots.x\""," هنا مهم. بدونه سيبدو الـ ",[17,7054,7055],{},"\u003Ctemplate #x>"," المُمرَّر موجوداً دائماً من منظور الأب، فلا يحصل الرسم الاحتياطي داخل ",[17,7058,6904],{}," على أيّ فرصة للعمل. الحارس يقول ببساطة: \"لا تُمرّر إلا إذا قدّم المستهلك هذا الـ slot فعلاً\".",[10,7061,7062],{},"مثال تخصيص واقعي:",[47,7064,7066],{"className":49,"code":7065,"language":51,"meta":52,"style":52},"\u003CATellInput v-model:phone=\"phone\" v-model:country=\"country\" show-validation>\n  \u003Ctemplate #suffix=\"{ validationState }\">\n    \u003CSparkle v-if=\"validationState === 'valid'\" class=\"size-4 text-amber-400\" \u002F>\n  \u003C\u002Ftemplate>\n  \u003Ctemplate #error=\"{ message, reason }\">\n    \u003Cp class=\"text-destructive text-xs\">\n      \u003Ccode class=\"bg-muted rounded px-1 py-0.5\">{{ reason }}\u003C\u002Fcode> {{ message }}\n    \u003C\u002Fp>\n  \u003C\u002Ftemplate>\n  \u003Ctemplate #empty=\"{ query }\">\n    \u003Cdiv class=\"px-3 py-6 text-center text-sm\">\n      Nothing matched \"\u003Cstrong>{{ query }}\u003C\u002Fstrong>\".\n      \u003Cbutton type=\"button\" class=\"underline\" @click=\"suggestCountry(query)\">\n        Suggest a country\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  \u003C\u002Ftemplate>\n\u003C\u002FATellInput>\n",[17,7067,7068,7107,7112,7117,7121,7126,7131,7136,7141,7145,7150,7155,7160,7165,7170,7175,7180,7184],{"__ignoreMap":52},[56,7069,7070,7072,7074,7076,7078,7080,7082,7084,7086,7088,7090,7092,7094,7096,7098,7100,7102,7105],{"class":58,"line":59},[56,7071,63],{"class":62},[56,7073,3792],{"class":66},[56,7075,6160],{"class":72},[56,7077,222],{"class":62},[56,7079,6060],{"class":72},[56,7081,76],{"class":62},[56,7083,80],{"class":79},[56,7085,6060],{"class":62},[56,7087,80],{"class":79},[56,7089,6160],{"class":72},[56,7091,222],{"class":62},[56,7093,3883],{"class":72},[56,7095,76],{"class":62},[56,7097,80],{"class":79},[56,7099,3883],{"class":62},[56,7101,80],{"class":79},[56,7103,7104],{"class":72}," show-validation",[56,7106,88],{"class":62},[56,7108,7109],{"class":58,"line":91},[56,7110,7111],{"class":62},"  \u003Ctemplate #suffix=\"{ validationState }\">\n",[56,7113,7114],{"class":58,"line":97},[56,7115,7116],{"class":62},"    \u003CSparkle v-if=\"validationState === 'valid'\" class=\"size-4 text-amber-400\" \u002F>\n",[56,7118,7119],{"class":58,"line":103},[56,7120,106],{"class":62},[56,7122,7123],{"class":58,"line":109},[56,7124,7125],{"class":62},"  \u003Ctemplate #error=\"{ message, reason }\">\n",[56,7127,7128],{"class":58,"line":115},[56,7129,7130],{"class":62},"    \u003Cp class=\"text-destructive text-xs\">\n",[56,7132,7133],{"class":58,"line":121},[56,7134,7135],{"class":62},"      \u003Ccode class=\"bg-muted rounded px-1 py-0.5\">{{ reason }}\u003C\u002Fcode> {{ message }}\n",[56,7137,7138],{"class":58,"line":126},[56,7139,7140],{"class":62},"    \u003C\u002Fp>\n",[56,7142,7143],{"class":58,"line":132},[56,7144,106],{"class":62},[56,7146,7147],{"class":58,"line":137},[56,7148,7149],{"class":62},"  \u003Ctemplate #empty=\"{ query }\">\n",[56,7151,7152],{"class":58,"line":142},[56,7153,7154],{"class":62},"    \u003Cdiv class=\"px-3 py-6 text-center text-sm\">\n",[56,7156,7157],{"class":58,"line":148},[56,7158,7159],{"class":62},"      Nothing matched \"\u003Cstrong>{{ query }}\u003C\u002Fstrong>\".\n",[56,7161,7162],{"class":58,"line":154},[56,7163,7164],{"class":62},"      \u003Cbutton type=\"button\" class=\"underline\" @click=\"suggestCountry(query)\">\n",[56,7166,7167],{"class":58,"line":159},[56,7168,7169],{"class":62},"        Suggest a country\n",[56,7171,7172],{"class":58,"line":913},[56,7173,7174],{"class":62},"      \u003C\u002Fbutton>\n",[56,7176,7177],{"class":58,"line":933},[56,7178,7179],{"class":62},"    \u003C\u002Fdiv>\n",[56,7181,7182],{"class":58,"line":952},[56,7183,106],{"class":62},[56,7185,7186,7188,7190],{"class":58,"line":964},[56,7187,162],{"class":62},[56,7189,3792],{"class":66},[56,7191,88],{"class":62},[10,7193,7194],{},"وظيفة مكوّن المكتبة أن يملك السلوك (التحليل، والتحقّق، والاكتشاف، وإدارة الحالة)، وأن يترك للمستهلك التحكّم بشكل هذه الأشياء. أربعة عشر slot هي كلفة هذه الصفقة، وهي كلفة تستحقّ.",[10,7196,7197],{},"المبدأ نفسه ينطبق على الـ props غير البصرية:",[47,7199,7201],{"className":200,"code":7200,"language":202,"meta":52,"style":52},"flagUrl?: (iso2: string, width: number) => string;\nsearcher?: (query: string, country: CountryOption) => boolean;\ncountries?: CountryOption[];\ndetector?: (options: DetectCountryOptions) => Promise\u003Cstring | null | undefined>;\nerrorMessages?: Record\u003CPhoneValidationReason, string>;\n",[17,7202,7203,7235,7265,7275,7316],{"__ignoreMap":52},[56,7204,7205,7208,7210,7212,7215,7217,7219,7221,7224,7226,7228,7230,7232],{"class":58,"line":59},[56,7206,7207],{"class":62},"flagUrl",[56,7209,1615],{"class":209},[56,7211,447],{"class":62},[56,7213,7214],{"class":284},"iso2",[56,7216,222],{"class":209},[56,7218,2571],{"class":249},[56,7220,1730],{"class":62},[56,7222,7223],{"class":284},"width",[56,7225,222],{"class":209},[56,7227,1791],{"class":249},[56,7229,452],{"class":62},[56,7231,298],{"class":209},[56,7233,7234],{"class":62}," string;\n",[56,7236,7237,7240,7242,7244,7246,7248,7250,7252,7254,7256,7258,7260,7262],{"class":58,"line":91},[56,7238,7239],{"class":62},"searcher",[56,7241,1615],{"class":209},[56,7243,447],{"class":62},[56,7245,6839],{"class":284},[56,7247,222],{"class":209},[56,7249,2571],{"class":249},[56,7251,1730],{"class":62},[56,7253,3883],{"class":284},[56,7255,222],{"class":209},[56,7257,6537],{"class":72},[56,7259,452],{"class":62},[56,7261,298],{"class":209},[56,7263,7264],{"class":62}," boolean;\n",[56,7266,7267,7270,7272],{"class":58,"line":97},[56,7268,7269],{"class":62},"countries",[56,7271,1615],{"class":209},[56,7273,7274],{"class":62}," CountryOption[];\n",[56,7276,7277,7279,7281,7283,7286,7288,7290,7292,7294,7296,7298,7301,7304,7306,7308,7311,7314],{"class":58,"line":103},[56,7278,6093],{"class":62},[56,7280,1615],{"class":209},[56,7282,447],{"class":62},[56,7284,7285],{"class":284},"options",[56,7287,222],{"class":209},[56,7289,5248],{"class":72},[56,7291,452],{"class":62},[56,7293,298],{"class":209},[56,7295,225],{"class":249},[56,7297,63],{"class":209},[56,7299,7300],{"class":62},"string ",[56,7302,7303],{"class":209},"|",[56,7305,668],{"class":249},[56,7307,660],{"class":209},[56,7309,7310],{"class":249}," undefined",[56,7312,7313],{"class":209},">",[56,7315,739],{"class":62},[56,7317,7318,7321,7323,7325,7327,7330,7332],{"class":58,"line":109},[56,7319,7320],{"class":62},"errorMessages",[56,7322,1615],{"class":209},[56,7324,1722],{"class":62},[56,7326,63],{"class":209},[56,7328,7329],{"class":62},"PhoneValidationReason, string",[56,7331,7313],{"class":209},[56,7333,739],{"class":62},[10,7335,7336,7337,7339,7340,7343,7344,7346,7347,7349,7350,7353,7354,7356],{},"يُتيح لك ",[17,7338,7207],{}," استبدال ",[17,7341,7342],{},"flagcdn.com"," بـ CDN خاصّ بك، وهو مفيد إن كان لديك CSP لا يسمح بصور من طرف ثالث. ويُتيح لك ",[17,7345,7239],{}," استبدال مطابقة السلاسل الفرعية بمطابقة بادئة، أو مطابقة ضبابية، أو مطابقة بالاسم العربي، أو أيّ شيء يحتاجه مستخدموك فعلاً. ويُتيح لك ",[17,7348,7269],{}," شحن قائمتك المُنتقاة والاستغناء كلياً عن نداء ",[17,7351,7352],{},"REST Countries",", وهو مفيد إن كنتَ حسمتَ أنّك تخدم دول الخليج مثلاً. أمّا ",[17,7355,7320],{}," فمعنيّ بالتوطين: مرّر جدولاً عربياً وستُعرض أسباب التحقّق بالعربية.",[10,7358,7359],{},"كلّ prop له افتراضي منطقي. النداء الذي لا يحتاج تكويناً ما زال يعمل كما هو.",[39,7361,7363],{"id":7362},"جدول-رموز-الاتصال-المتزامن-ولماذا-ليس-اختيارياً","جدول رموز الاتصال المتزامن، ولماذا ليس اختيارياً",[10,7365,7366,7367,7369,7370,447,7373,1730,7376,7379,7380,7383,7384,7387,7388,3196,7390,7392],{},"عند أوّل تركيب للمكوّن في تبويب جديد، لا يكون نداء ",[17,7368,7352],{}," قد اكتمل. أيّ فهارس يبنيها ",[17,7371,7372],{},"usePhoneValidation",[17,7374,7375],{},"byValue",[17,7377,7378],{},"byDialDigits",") تكون خرائط فارغة. لو كتب المستهلك ",[17,7381,7382],{},"default-country=\"20\""," في قالبه متوقّعاً أن يظهر المنتقي وقد اختار مصر، فما سيحصل عليه بلا مساعدة هو منتقٍ فارغ. فهرس أرقام الاتصال يعيد ",[17,7385,7386],{},"[]",", ويضبط الـ watcher قيمة ",[17,7389,4023],{},[17,7391,3945],{},", ولن تظهر مصر إلا حين يعود نداء الشبكة.",[10,7394,7395],{},"لذلك يُدرِج المكوّن جدولاً احتياطياً متزامناً في داخله:",[47,7397,7399],{"className":200,"code":7398,"language":202,"meta":52,"style":52},"const DIAL_TO_ISO2_FALLBACK: Record\u003Cstring, string> = {\n  '1': 'US',\n  '7': 'RU',\n  '20': 'EG',\n  '27': 'ZA',\n  '33': 'FR',\n  '44': 'GB',\n  '49': 'DE',\n  '52': 'MX',\n  '55': 'BR',\n  '61': 'AU',\n  '81': 'JP',\n  '86': 'CN',\n  '91': 'IN',\n  '212': 'MA',\n  '966': 'SA',\n  '971': 'AE',\n  \u002F\u002F ... ~50 entries\n};\n",[17,7400,7401,7426,7438,7450,7462,7474,7486,7498,7510,7522,7534,7546,7558,7570,7582,7594,7606,7618,7623],{"__ignoreMap":52},[56,7402,7403,7405,7408,7410,7412,7414,7416,7418,7420,7422,7424],{"class":58,"line":59},[56,7404,410],{"class":209},[56,7406,7407],{"class":249}," DIAL_TO_ISO2_FALLBACK",[56,7409,222],{"class":209},[56,7411,1722],{"class":72},[56,7413,63],{"class":62},[56,7415,1727],{"class":249},[56,7417,1730],{"class":62},[56,7419,1727],{"class":249},[56,7421,492],{"class":62},[56,7423,76],{"class":209},[56,7425,719],{"class":62},[56,7427,7428,7431,7433,7436],{"class":58,"line":91},[56,7429,7430],{"class":79},"  '1'",[56,7432,3568],{"class":62},[56,7434,7435],{"class":79},"'US'",[56,7437,1300],{"class":62},[56,7439,7440,7443,7445,7448],{"class":58,"line":97},[56,7441,7442],{"class":79},"  '7'",[56,7444,3568],{"class":62},[56,7446,7447],{"class":79},"'RU'",[56,7449,1300],{"class":62},[56,7451,7452,7455,7457,7460],{"class":58,"line":103},[56,7453,7454],{"class":79},"  '20'",[56,7456,3568],{"class":62},[56,7458,7459],{"class":79},"'EG'",[56,7461,1300],{"class":62},[56,7463,7464,7467,7469,7472],{"class":58,"line":109},[56,7465,7466],{"class":79},"  '27'",[56,7468,3568],{"class":62},[56,7470,7471],{"class":79},"'ZA'",[56,7473,1300],{"class":62},[56,7475,7476,7479,7481,7484],{"class":58,"line":115},[56,7477,7478],{"class":79},"  '33'",[56,7480,3568],{"class":62},[56,7482,7483],{"class":79},"'FR'",[56,7485,1300],{"class":62},[56,7487,7488,7491,7493,7496],{"class":58,"line":121},[56,7489,7490],{"class":79},"  '44'",[56,7492,3568],{"class":62},[56,7494,7495],{"class":79},"'GB'",[56,7497,1300],{"class":62},[56,7499,7500,7503,7505,7508],{"class":58,"line":126},[56,7501,7502],{"class":79},"  '49'",[56,7504,3568],{"class":62},[56,7506,7507],{"class":79},"'DE'",[56,7509,1300],{"class":62},[56,7511,7512,7515,7517,7520],{"class":58,"line":132},[56,7513,7514],{"class":79},"  '52'",[56,7516,3568],{"class":62},[56,7518,7519],{"class":79},"'MX'",[56,7521,1300],{"class":62},[56,7523,7524,7527,7529,7532],{"class":58,"line":137},[56,7525,7526],{"class":79},"  '55'",[56,7528,3568],{"class":62},[56,7530,7531],{"class":79},"'BR'",[56,7533,1300],{"class":62},[56,7535,7536,7539,7541,7544],{"class":58,"line":142},[56,7537,7538],{"class":79},"  '61'",[56,7540,3568],{"class":62},[56,7542,7543],{"class":79},"'AU'",[56,7545,1300],{"class":62},[56,7547,7548,7551,7553,7556],{"class":58,"line":148},[56,7549,7550],{"class":79},"  '81'",[56,7552,3568],{"class":62},[56,7554,7555],{"class":79},"'JP'",[56,7557,1300],{"class":62},[56,7559,7560,7563,7565,7568],{"class":58,"line":154},[56,7561,7562],{"class":79},"  '86'",[56,7564,3568],{"class":62},[56,7566,7567],{"class":79},"'CN'",[56,7569,1300],{"class":62},[56,7571,7572,7575,7577,7580],{"class":58,"line":159},[56,7573,7574],{"class":79},"  '91'",[56,7576,3568],{"class":62},[56,7578,7579],{"class":79},"'IN'",[56,7581,1300],{"class":62},[56,7583,7584,7587,7589,7592],{"class":58,"line":913},[56,7585,7586],{"class":79},"  '212'",[56,7588,3568],{"class":62},[56,7590,7591],{"class":79},"'MA'",[56,7593,1300],{"class":62},[56,7595,7596,7599,7601,7604],{"class":58,"line":933},[56,7597,7598],{"class":79},"  '966'",[56,7600,3568],{"class":62},[56,7602,7603],{"class":79},"'SA'",[56,7605,1300],{"class":62},[56,7607,7608,7611,7613,7616],{"class":58,"line":952},[56,7609,7610],{"class":79},"  '971'",[56,7612,3568],{"class":62},[56,7614,7615],{"class":79},"'AE'",[56,7617,1300],{"class":62},[56,7619,7620],{"class":58,"line":964},[56,7621,7622],{"class":434},"  \u002F\u002F ... ~50 entries\n",[56,7624,7625],{"class":58,"line":969},[56,7626,972],{"class":62},[10,7628,7629,7630,7633],{},"ويتحقّق ",[17,7631,7632],{},"resolveCountryIdentifier"," أولاً من الفهرس المحمَّل، ثم من هذا الجدول:",[47,7635,7637],{"className":200,"code":7636,"language":202,"meta":52,"style":52},"function resolveCountryIdentifier(raw: string | undefined | null): string {\n  const v = String(raw ?? '').trim();\n  if (!v) return '';\n  if (\u002F^[A-Za-z]{2}$\u002F.test(v)) return v.toUpperCase();\n  const dial = v.replace(\u002F^\\+\u002F, '');\n  if (!\u002F^\\d+$\u002F.test(dial)) return '';\n  const match = getCountriesByDial(dial)[0];\n  if (match) return match.value;\n  return DIAL_TO_ISO2_FALLBACK[dial] ?? '';\n}\n",[17,7638,7639,7670,7696,7713,7746,7778,7811,7828,7840,7855],{"__ignoreMap":52},[56,7640,7641,7643,7645,7647,7650,7652,7654,7656,7658,7660,7662,7664,7666,7668],{"class":58,"line":59},[56,7642,1695],{"class":209},[56,7644,4126],{"class":72},[56,7646,425],{"class":62},[56,7648,7649],{"class":284},"raw",[56,7651,222],{"class":209},[56,7653,2571],{"class":249},[56,7655,660],{"class":209},[56,7657,7310],{"class":249},[56,7659,660],{"class":209},[56,7661,668],{"class":249},[56,7663,1711],{"class":62},[56,7665,222],{"class":209},[56,7667,2571],{"class":249},[56,7669,719],{"class":62},[56,7671,7672,7674,7677,7679,7682,7685,7687,7689,7691,7694],{"class":58,"line":91},[56,7673,340],{"class":209},[56,7675,7676],{"class":249}," v",[56,7678,270],{"class":209},[56,7680,7681],{"class":72}," String",[56,7683,7684],{"class":62},"(raw ",[56,7686,4460],{"class":209},[56,7688,4080],{"class":79},[56,7690,3187],{"class":62},[56,7692,7693],{"class":72},"trim",[56,7695,377],{"class":62},[56,7697,7698,7700,7702,7704,7707,7709,7711],{"class":58,"line":97},[56,7699,243],{"class":209},[56,7701,447],{"class":62},[56,7703,759],{"class":209},[56,7705,7706],{"class":62},"v) ",[56,7708,736],{"class":209},[56,7710,4080],{"class":79},[56,7712,739],{"class":62},[56,7714,7715,7717,7719,7721,7723,7726,7728,7730,7732,7734,7737,7739,7742,7744],{"class":58,"line":103},[56,7716,243],{"class":209},[56,7718,447],{"class":62},[56,7720,5887],{"class":79},[56,7722,5878],{"class":209},[56,7724,7725],{"class":249},"[A-Za-z]",[56,7727,5884],{"class":209},[56,7729,5887],{"class":79},[56,7731,24],{"class":62},[56,7733,5892],{"class":72},[56,7735,7736],{"class":62},"(v)) ",[56,7738,736],{"class":209},[56,7740,7741],{"class":62}," v.",[56,7743,5406],{"class":72},[56,7745,377],{"class":62},[56,7747,7748,7750,7753,7755,7757,7760,7762,7764,7766,7770,7772,7774,7776],{"class":58,"line":109},[56,7749,340],{"class":209},[56,7751,7752],{"class":249}," dial",[56,7754,270],{"class":209},[56,7756,7741],{"class":62},[56,7758,7759],{"class":72},"replace",[56,7761,425],{"class":62},[56,7763,5887],{"class":79},[56,7765,5878],{"class":209},[56,7767,7769],{"class":7768},"snhLl","\\+",[56,7771,5887],{"class":79},[56,7773,1730],{"class":62},[56,7775,3945],{"class":79},[56,7777,623],{"class":62},[56,7779,7780,7782,7784,7786,7788,7790,7793,7796,7798,7800,7802,7805,7807,7809],{"class":58,"line":115},[56,7781,243],{"class":209},[56,7783,447],{"class":62},[56,7785,759],{"class":209},[56,7787,5887],{"class":79},[56,7789,5878],{"class":209},[56,7791,7792],{"class":249},"\\d",[56,7794,7795],{"class":209},"+$",[56,7797,5887],{"class":79},[56,7799,24],{"class":62},[56,7801,5892],{"class":72},[56,7803,7804],{"class":62},"(dial)) ",[56,7806,736],{"class":209},[56,7808,4080],{"class":79},[56,7810,739],{"class":62},[56,7812,7813,7815,7817,7819,7821,7824,7826],{"class":58,"line":121},[56,7814,340],{"class":209},[56,7816,5035],{"class":249},[56,7818,270],{"class":209},[56,7820,4751],{"class":72},[56,7822,7823],{"class":62},"(dial)[",[56,7825,1092],{"class":249},[56,7827,1095],{"class":62},[56,7829,7830,7832,7835,7837],{"class":58,"line":126},[56,7831,243],{"class":209},[56,7833,7834],{"class":62}," (match) ",[56,7836,736],{"class":209},[56,7838,7839],{"class":62}," match.value;\n",[56,7841,7842,7844,7846,7849,7851,7853],{"class":58,"line":132},[56,7843,370],{"class":209},[56,7845,7407],{"class":249},[56,7847,7848],{"class":62},"[dial] ",[56,7850,4460],{"class":209},[56,7852,4080],{"class":79},[56,7854,739],{"class":62},[56,7856,7857],{"class":58,"line":137},[56,7858,382],{"class":62},[10,7860,7861],{},"الاتجاه المعاكس، أي التحويل من ISO2 إلى رقم اتصال من أجل v-model, يُجري الفحص نفسه:",[47,7863,7865],{"className":200,"code":7864,"language":202,"meta":52,"style":52},"function dialNumberFor(iso2: string): number | null {\n  if (!iso2) return null;\n  const fromIndex = getCountryByValue(iso2)?.raw_data?.dial_digits;\n  const digits = fromIndex ?? Object.entries(DIAL_TO_ISO2_FALLBACK).find(([, v]) => v === iso2)?.[0];\n  if (!digits) return null;\n  const n = Number(digits);\n  return Number.isFinite(n) ? n : null;\n}\n",[17,7866,7867,7894,7911,7925,7976,7992,8007,8029],{"__ignoreMap":52},[56,7868,7869,7871,7874,7876,7878,7880,7882,7884,7886,7888,7890,7892],{"class":58,"line":59},[56,7870,1695],{"class":209},[56,7872,7873],{"class":72}," dialNumberFor",[56,7875,425],{"class":62},[56,7877,7214],{"class":284},[56,7879,222],{"class":209},[56,7881,2571],{"class":249},[56,7883,1711],{"class":62},[56,7885,222],{"class":209},[56,7887,1791],{"class":249},[56,7889,660],{"class":209},[56,7891,668],{"class":249},[56,7893,719],{"class":62},[56,7895,7896,7898,7900,7902,7905,7907,7909],{"class":58,"line":91},[56,7897,243],{"class":209},[56,7899,447],{"class":62},[56,7901,759],{"class":209},[56,7903,7904],{"class":62},"iso2) ",[56,7906,736],{"class":209},[56,7908,668],{"class":249},[56,7910,739],{"class":62},[56,7912,7913,7915,7918,7920,7922],{"class":58,"line":97},[56,7914,340],{"class":209},[56,7916,7917],{"class":249}," fromIndex",[56,7919,270],{"class":209},[56,7921,4434],{"class":72},[56,7923,7924],{"class":62},"(iso2)?.raw_data?.dial_digits;\n",[56,7926,7927,7929,7932,7934,7937,7939,7942,7944,7946,7949,7951,7954,7957,7959,7962,7964,7967,7969,7972,7974],{"class":58,"line":103},[56,7928,340],{"class":209},[56,7930,7931],{"class":249}," digits",[56,7933,270],{"class":209},[56,7935,7936],{"class":62}," fromIndex ",[56,7938,4460],{"class":209},[56,7940,7941],{"class":62}," Object.",[56,7943,1071],{"class":72},[56,7945,425],{"class":62},[56,7947,7948],{"class":249},"DIAL_TO_ISO2_FALLBACK",[56,7950,3187],{"class":62},[56,7952,7953],{"class":72},"find",[56,7955,7956],{"class":62},"(([, ",[56,7958,6779],{"class":284},[56,7960,7961],{"class":62},"]) ",[56,7963,298],{"class":209},[56,7965,7966],{"class":62}," v ",[56,7968,749],{"class":209},[56,7970,7971],{"class":62}," iso2)?.[",[56,7973,1092],{"class":249},[56,7975,1095],{"class":62},[56,7977,7978,7980,7982,7984,7986,7988,7990],{"class":58,"line":109},[56,7979,243],{"class":209},[56,7981,447],{"class":62},[56,7983,759],{"class":209},[56,7985,4365],{"class":62},[56,7987,736],{"class":209},[56,7989,668],{"class":249},[56,7991,739],{"class":62},[56,7993,7994,7996,7999,8001,8004],{"class":58,"line":115},[56,7995,340],{"class":209},[56,7997,7998],{"class":249}," n",[56,8000,270],{"class":209},[56,8002,8003],{"class":72}," Number",[56,8005,8006],{"class":62},"(digits);\n",[56,8008,8009,8011,8013,8015,8018,8020,8023,8025,8027],{"class":58,"line":121},[56,8010,370],{"class":209},[56,8012,1811],{"class":62},[56,8014,1814],{"class":72},[56,8016,8017],{"class":62},"(n) ",[56,8019,2707],{"class":209},[56,8021,8022],{"class":62}," n ",[56,8024,222],{"class":209},[56,8026,668],{"class":249},[56,8028,739],{"class":62},[56,8030,8031],{"class":58,"line":126},[56,8032,382],{"class":62},[10,8034,8035,8036,8039],{},"الجدول تسوية معلومة سلفاً. خمسون مدخلاً لا تكفي لتغطية كلّ رمز اتصال، واختيار الخمسين ينحاز إلى رأي: أكثر الدول سكاناً بالإضافة إلى تلك التي أحتاجها شخصياً. المستهلك الذي يمرّر ",[17,8037,8038],{},"default-country=\"678\""," (فانواتو) عند التركيب دون أيّ بيانات دولة مخبّأة لن يحصل على شيء من هذا البحث. بعد ثوانٍ قليلة، حين يعود نداء الشبكة، سيحسم المنتقي القيمة بشكل صحيح. غاية الاحتياطي أن تعمل الحالة الشائعة (95%) دون وميض، والذيل الطويل ينتهي إلى الصحيح.",[10,8041,8042],{},"داخل التطبيق تستطيع انتظار النداء. أمّا في المكتبة فلا تقدر على إلزام كلّ مستهلك بتعليق تركيب مكوّنه انتظاراً لواجهة برمجية طرف ثالث، ولا تستطيع في المقابل شحن 80 كيلوبايت من بيانات الدول بشكل فوري. الجدول المتزامن هو المخرج الثالث.",[39,8044,8046],{"id":8045},"آخر-الاختيارات-كفاصل-عند-التعادل","آخر الاختيارات كفاصل عند التعادل",[10,8048,8049,8050,8052],{},"مطابقة أطول بادئة في الطبقة الثالثة ليست دائماً واضحة بلا لبس. الرمز ",[17,8051,1297],{}," يطابق كتلة NANP كاملة، أكثر من 25 دولة. الأبجدي الأوّل يعني \"ساموا الأمريكية\" أو أيّاً كانت الدولة الأسبق أبجدياً في كلّ مرّة. هذا ليس افتراضاً مفيداً لأيّ أحد.",[10,8054,8055,8056,222],{},"لذلك تقرأ الطبقة الثالثة من ",[17,8057,3123],{},[47,8059,8061],{"className":200,"code":8060,"language":202,"meta":52,"style":52},"const recents = readRecents();\nconst recentHit = recents\n  .map((iso2) => group.find((c) => c.value === iso2))\n  .find((c): c is CountryOption => Boolean(c));\nif (recentHit) return { country: recentHit, nationalNumber };\nreturn { country: group[0], nationalNumber };\n",[17,8062,8063,8077,8089,8127,8157,8169],{"__ignoreMap":52},[56,8064,8065,8067,8070,8072,8075],{"class":58,"line":59},[56,8066,410],{"class":209},[56,8068,8069],{"class":249}," recents",[56,8071,270],{"class":209},[56,8073,8074],{"class":72}," readRecents",[56,8076,377],{"class":62},[56,8078,8079,8081,8084,8086],{"class":58,"line":91},[56,8080,410],{"class":209},[56,8082,8083],{"class":249}," recentHit",[56,8085,270],{"class":209},[56,8087,8088],{"class":62}," recents\n",[56,8090,8091,8094,8097,8099,8101,8103,8105,8108,8110,8112,8115,8117,8119,8122,8124],{"class":58,"line":97},[56,8092,8093],{"class":62},"  .",[56,8095,8096],{"class":72},"map",[56,8098,2745],{"class":62},[56,8100,7214],{"class":284},[56,8102,452],{"class":62},[56,8104,298],{"class":209},[56,8106,8107],{"class":62}," group.",[56,8109,7953],{"class":72},[56,8111,2745],{"class":62},[56,8113,8114],{"class":284},"c",[56,8116,452],{"class":62},[56,8118,298],{"class":209},[56,8120,8121],{"class":62}," c.value ",[56,8123,749],{"class":209},[56,8125,8126],{"class":62}," iso2))\n",[56,8128,8129,8131,8133,8135,8137,8139,8141,8144,8146,8148,8151,8154],{"class":58,"line":103},[56,8130,8093],{"class":62},[56,8132,7953],{"class":72},[56,8134,2745],{"class":62},[56,8136,8114],{"class":284},[56,8138,1711],{"class":62},[56,8140,222],{"class":209},[56,8142,8143],{"class":284}," c",[56,8145,1719],{"class":209},[56,8147,6537],{"class":72},[56,8149,8150],{"class":209}," =>",[56,8152,8153],{"class":72}," Boolean",[56,8155,8156],{"class":62},"(c));\n",[56,8158,8159,8161,8164,8166],{"class":58,"line":109},[56,8160,5141],{"class":209},[56,8162,8163],{"class":62}," (recentHit) ",[56,8165,736],{"class":209},[56,8167,8168],{"class":62}," { country: recentHit, nationalNumber };\n",[56,8170,8171,8173,8175,8177],{"class":58,"line":115},[56,8172,736],{"class":209},[56,8174,4818],{"class":62},[56,8176,1092],{"class":249},[56,8178,4823],{"class":62},[10,8180,8181,8182,8185,8186,8188],{},"يكتب المنتقي في ",[17,8183,8184],{},"ali_ui_country_recents_v1"," كلّما اختار المستخدم دولةً صراحةً، ويقرأ المطابِق منها عند حصول الالتباس. إن كنتَ تعيش في كندا وسبق أن استعملتَ الحقل، فأوّل ",[17,8187,3901],{}," تكتبه سيُحلّ إلى كندا. وإن لم تستعمله من قبل، ستحصل على الاحتياطي الأبجدي، وهو خطأ نعم، لكنّه يقع مرّة واحدة فقط.",[10,8190,8191],{},"لمسة صغيرة، لكنّها من ذلك النوع الذي يحوّل \"هذا يعمل تقنياً\" إلى \"هذا مبنيّ بيدِ من استعمله فعلاً\".",[39,8193,8195],{"id":8194},"عن-ماذا-كانت-إعادة-كتابة-المكتبة-حقيقةً","عن ماذا كانت إعادة كتابة المكتبة حقيقةً",[10,8197,8198,8199,8202,8203,8206],{},"كانت النسخة الأصلية معنيّة بالصحّة: اضطراب موضع المؤشّر، وأسباب التحقّق، ومعالجة RTL\u002FLTR, والحالات السبع المحدّدة التي على حقل الهاتف أن يميّز بينها. لم يختفِ أيّ من ذلك. كلّه لا يزال حاضراً في هذه النسخة: الدالّة المسنِدة ",[17,8200,8201],{},"dropLeadingZeros",", والقيم السبع لـ enum المسمّى ",[17,8204,8205],{},"PhoneValidationReason",", ونمط التنسيق ثنائي الاتجاه القائم على غلاف LTR فوق مُدخل RTL. تلك الأشياء لم تكن خاطئة، بل كانت الأساس الذي يُبنى عليه.",[10,8208,8209],{},"ما أضافته إعادة كتابة المكتبة كلّه يتعلّق بحقيقة أنّها لم تعد تخدم تطبيقاً واحداً. فصل بين ISO2 ورمز الاتصال يصمد أمام رموز الاتصال الملتبسة. سلسلة اكتشاف بيئية قابلة للاستبدال وصالحة للعمل دون اتصال. منتقٍ يعمل popover على الحاسوب وdrawer على الهاتف، ولكلّ حالة صنفها الخاصّ. أربعة عشر slot كي لا يشتبك نظام تصميم المستهلك مع المكوّن. جدول رموز اتصال متزامن لأنّ مستهلك المكتبة لا يستطيع انتظار الشبكة. آخر الاختيارات فاصلاً عند التعادل لأنّ \"الأبجدي الأوّل\" هو مظهر الكسل بعينه.",[10,8211,8212],{},"تضاعف عدد الأسطر تقريباً. النسخة الأصلية كانت 160 سطراً للمكوّن مع 540 سطراً للـ composable, بينما النسخة الجديدة عدّة مئات من الأسطر موزّعة على مكوّنَين واثنين من الـ composables. تضاعف السطح البرمجي أكثر من مرّة. ومع ذلك يظلّ النداء بلا تكوين قابلاً للاختصار في ثلاثة أسطر:",[47,8214,8216],{"className":49,"code":8215,"language":51,"meta":52,"style":52},"\u003CATellInput v-model:phone=\"phone\" v-model:country=\"country\" show-validation \u002F>\n",[17,8217,8218],{"__ignoreMap":52},[56,8219,8220,8222,8224,8226,8228,8230,8232,8234,8236,8238,8240,8242,8244,8246,8248,8250,8252,8254],{"class":58,"line":59},[56,8221,63],{"class":62},[56,8223,3792],{"class":66},[56,8225,6160],{"class":72},[56,8227,222],{"class":62},[56,8229,6060],{"class":72},[56,8231,76],{"class":62},[56,8233,80],{"class":79},[56,8235,6060],{"class":62},[56,8237,80],{"class":79},[56,8239,6160],{"class":72},[56,8241,222],{"class":62},[56,8243,3883],{"class":72},[56,8245,76],{"class":62},[56,8247,80],{"class":79},[56,8249,3883],{"class":62},[56,8251,80],{"class":79},[56,8253,7104],{"class":72},[56,8255,1039],{"class":62},[10,8257,8258],{},"لكلّ prop جديد افتراضي يعكس ما كانت النسخة الداخلية تفعله تلقائياً. من يريد \"حقل هاتف فحسب\" يحصل عليه. ومن يريد \"حقل هاتف بأعلامنا، وقائمة دولنا، ومسند بحثنا، ورسائل أخطائنا\" يجد كلّ نقاط الفصل التي يحتاجها.",[10,8260,8261,8262,8265],{},"الدرس الذي سأحمله معي إلى المرّة القادمة: جعل مكوّن قابلاً للنشر يتعلّق أساساً برصد الآراء التي فرضتَها على نفسك، وتحويل كلّ واحد منها إلى نقطة فصل. كلّ ",[17,8263,8264],{},"restcountries.com"," مثبّت، وكلّ احتياطي \"السعودية ومصر\"، وكلّ قائمة منسدلة ظاهرة بلا انقطاع: تلك الأمور لم تكن خطأً بالنسبة للتطبيق، لكنّها الأمور التي لن تنجو مع المستهلك التالي. ابحث عنها، وسمِّها، واستبدل كلاً منها بافتراضي مع إمكانية تجاوز. الافتراضي يُبقي مستهلكك القديم راضياً، والتجاوز يفتح الباب للجديد.",[1970,8267,8268],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html pre.shiki code .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}",{"title":52,"searchDepth":91,"depth":91,"links":8270},[8271,8272,8273,8274,8275,8276,8277,8278],{"id":3813,"depth":91,"text":3814},{"id":4197,"depth":91,"text":4198},{"id":5208,"depth":91,"text":5209},{"id":6131,"depth":91,"text":6132},{"id":6285,"depth":91,"text":6286},{"id":7362,"depth":91,"text":7363},{"id":8045,"depth":91,"text":8046},{"id":8194,"depth":91,"text":8195},"2026-05-21","تحويل حقل هاتف من داخل تطبيق واحد إلى مكوّن Vue مُنشَر استوجب تغييرات جوهرية. تجربة استخدام تكتشف الدولة قبل أيّ تدخّل، وفصل بين رمز ISO2 ورقم الاتصال، ومنتقي متجاوب عبر Popover وDrawer، وSlots لكل شيء.",[1997,3775,8282,8283,8284,8285,6145,8286,8287,2003],"حقل هاتف","libphonenumber-js","اكتشاف الدولة","Popover متجاوب","defineModel","Slots",{},"\u002Fwrites-ar\u002Fatell-input-the-library-rewrite",{"title":3784,"description":8280},"writes-ar\u002Fatell-input-the-library-rewrite","8wJPO0VSknxZ5ZTju80YmwbagcwC1H2lmP5kn104P24",1793901270204]