[{"data":1,"prerenderedAt":11367},["ShallowReactive",2],{"writesAr-\u002Fwrites\u002Fupload-and-download-progress-in-the-browser":3,"related-writesAr-\u002Fwrites\u002Fupload-and-download-progress-in-the-browser":5148},{"id":4,"title":5,"body":6,"date":5127,"description":5128,"description_ar":5129,"draft":5130,"extension":5131,"image":5129,"keywords":5132,"lang":5142,"meta":5143,"navigation":546,"path":5144,"seo":5145,"stem":5146,"title_ar":5129,"updatedAt":5127,"__hash__":5147},"writesAr\u002Fwrites-ar\u002Fupload-and-download-progress-in-the-browser.md","غلاف مخصَّص لـ fetch، وXHR لتقدُّم الرفع",{"type":7,"value":8,"toc":5103},"minimark",[9,30,50,61,64,85,92,100,103,129,145,168,192,225,238,246,250,265,268,273,276,445,448,451,738,752,759,764,808,811,1012,1028,1032,1035,1616,1623,1626,1700,1703,1777,1781,1784,2004,2011,2015,2018,2155,2160,2163,2170,2176,2342,2348,2368,2371,2375,2378,2526,2529,2550,2563,2576,2587,2596,2657,2691,2694,2804,2813,2817,2820,3221,3224,3268,3272,3278,3617,3641,3659,3665,3674,3686,3689,4508,4511,4524,4544,4762,4786,4820,4824,4839,4925,4938,4956,4960,4963,4976,4990,5001,5016,5033,5037,5040,5096,5099],[10,11,12,13,17,18,21,22,25,26,29],"p",{},"كلما احتجتُ إلى التعامل مع HTTP API من المتصفح، أبدأ بـ ",[14,15,16],"code",{},"fetch"," مباشرة. أراه الأداة الصحيحة في هذا المستوى: مبني على Promise منذ البداية، ويتركَّب بسلاسة مع ",[14,19,20],{},"AbortController","، ويتشارك ",[14,23,24],{},"Request"," و",[14,27,28],{},"Response"," مع Service Workers وواجهة Cache API، ثم إن التوقيع نفسه يعمل في Node 18 وما بعده، وفي Bun وDeno وCloudflare Workers دون الحاجة إلى محوِّل. وأغلب النقاشات التي تدور حول \"أيّ عميل HTTP أختار؟\" تنتهي عند هذه النقطة.",[10,31,32,33,35,36,38,39,41,42,45,46,49],{},"لكن في ",[14,34,16],{}," ثغرة واحدة لا يذكرها أحد، وليست من النوع الذي يتوقَّع أن يُصلَح لاحقاً، لأنها ثغرة بنيوية. ",[14,37,16],{}," لا يُطلق أحداث تقدُّم الرفع أصلاً. صحيح أن جسم ",[14,40,24],{}," يقبل ",[14,43,44],{},"ReadableStream","، لكن المتصفح لا يُطلق حدث ",[14,47,48],{},"progress"," وهو يستنزف البايتات منه إلى الشبكة. تُسلِّم الجسم إلى طبقة الشبكة، ثم يمضي في طريقه، ولا تسمع منه شيئاً بعد ذلك.",[10,51,52,53,56,57,60],{},"ولو أردتَ شريط تقدُّم رفع حقيقياً لا مجرَّد spinner وهمي، فليس أمامك سوى ",[14,54,55],{},"XMLHttpRequest",". هو الواجهة الأقدم، لكنه الوحيد الذي يكشف عن ",[14,58,59],{},"xhr.upload.onprogress",".",[10,62,63],{},"سأتحدَّث في هذه التدوينة عن ثلاثة أمور بالترتيب:",[65,66,67,74,79],"ol",{},[68,69,70,71,73],"li",{},"لماذا يستحق ",[14,72,16],{}," الأصلي أن يكون الخيار الافتراضي، وما الفوائد التي تحصل عليها بالضبط من ورائه.",[68,75,76,77,60],{},"كيف تبني غلاف fetch مخصَّصاً (افتراضات، ومهلات، وinterceptors، وإعادة محاولة) دون أن تُضيِّع شكل ",[14,78,16],{},[68,80,81,82,84],{},"الثغرة في XHR، وخريطة أحداث التقدُّم، وغلاف صغير مبني على XHR يُعيد ",[14,83,28],{}," كي تبقى بقية شيفرتك بشكل fetch.",[10,86,87,88,91],{},"إذا سبق أن تساءلتَ عن سرِّ وجود مصنع باسم ",[14,89,90],{},"createClient()"," في كثير من مشاريع الإنتاج، فإجابتك تكمن في القسمين الأول والثاني. أما القسم الثالث فهو الشيء الوحيد الذي يعجز عنه هذا العميل من تلقاء نفسه.",[93,94,96,97,99],"h2",{"id":95},"لماذا-fetch-الأصلي-هو-البدائي-الافتراضي","لماذا ",[14,98,16],{}," الأصلي هو البدائي الافتراضي",[10,101,102],{},"كل نقاش خضتُه حول عملاء HTTP انتهى في نهاية المطاف إلى \"استخدم fetch\". والأسباب تستحق أن تُذكر صراحة، لأنها تحديداً ما ستتنازل عنه لو اخترتَ مكتبة أخرى، ولأنها تتراكم فوق بعضها بمجرَّد أن تبدأ بتغليفه.",[10,104,105,109,110,113,114,117,118,121,122,125,126,128],{},[106,107,108],"strong",{},"Promise واحدة، وResponse واحدة."," يُعيد ",[14,111,112],{},"fetch(url, init)"," قيمة من نوع ",[14,115,116],{},"Promise\u003CResponse>"," مباشرة. لا آلة حالات، ولا ",[14,119,120],{},"readyState","، ولا حاجة لربط معالجات أحداث في المسار السليم. تقرأ ",[14,123,124],{},"await fetch(...)"," كما تقرأ أي استدعاء غير متزامن آخر في مشروعك. XHR لا يزال مدفوعاً بالأحداث خلف الكواليس، بينما ",[14,127,16],{}," هو الواجهة المصمَّمة أصلاً على شكل Promise.",[10,130,131,134,135,137,138,140,141,144],{},[106,132,133],{},"بدائيات موحَّدة لـ Request وResponse."," ",[14,136,28],{}," الذي يصلك من ",[14,139,16],{}," هو النوع نفسه الذي يُعيده Service Worker، وتخزِّنه Cache API، وتقبله بيئات الحافة. تستطيع أن تستدعي ",[14,142,143],{},".clone()"," عليه، أو أن تُعيده إلى المتصفح، أو تخزِّنه، أو تدفعه إلى مكان آخر، دون أي طبقة ترجمة بينهما.",[10,146,147,152,153,156,157,160,161,25,164,167],{},[106,148,149,150,60],{},"الإلغاء عبر ",[14,151,20],{}," إشارة واحدة كافية لإلغاء سلسلة عمل كاملة: fetch، وحساب معتمد عليه، وإعادة محاولة لم تنطلق بعد. تُجمِّع ",[14,154,155],{},"AbortSignal.any([...])"," عدة إشارات في إشارة واحدة، وتمنحك ",[14,158,159],{},"AbortSignal.timeout(ms)"," مهلة زمنية دون الحاجة إلى رقصة ",[14,162,163],{},"setTimeout",[14,165,166],{},"clearTimeout",". الإلغاء صار ميزة أصيلة في المنصَّة، لا شأناً تُعالجه كل مكتبة على حدة.",[10,169,170,134,173,176,177,179,180,183,184,187,188,191],{},[106,171,172],{},"أجسام على شكل streams.",[14,174,175],{},"Response.body"," هو ",[14,178,44],{},". عند التنزيل، تستطيع أن تقرأ القطع فور وصولها، وهذا يكفي لعدَّاد بايتات، أو محلِّل حي، أو لتمرير الـ stream إلى شيء آخر مثل ",[14,181,182],{},"createImageBitmap"," أو ",[14,185,186],{},"new Response(stream)"," أو Cache API. ويقبل ",[14,189,190],{},"Request.body"," هو الآخر streams، غير أن تقدُّم الرفع هو الحالة الوحيدة التي لا يترجم فيها هذا القبول إلى نتيجة عملية.",[10,193,194,197,198,25,201,25,204,25,207,25,210,25,213,216,217,220,221,224],{},[106,195,196],{},"خيارات طلب من الدرجة الأولى."," حقول مثل ",[14,199,200],{},"credentials",[14,202,203],{},"cache",[14,205,206],{},"redirect",[14,208,209],{},"mode",[14,211,212],{},"referrer",[14,214,215],{},"integrity"," كلها حقول عليا في ",[14,218,219],{},"RequestInit"," مباشرة. لا آثار جانبية على شاكلة ",[14,222,223],{},"xhr.withCredentials = true","، ولا حيل للالتفاف حول headers ممنوعة. صُمِّمت الواجهة أصلاً وفي ذهنها دلالات الأمن والتخزين المؤقَّت الحديثة.",[10,226,227,230,231,233,234,237],{},[106,228,229],{},"اتساق عبر بيئات التشغيل."," التوقيع نفسه لـ ",[14,232,16],{}," يعمل في المتصفحات، وNode 18 وما بعده، وBun وDeno وCloudflare Workers وService Workers. لا يحتاج العميل الذي يعمل في SSR إلى تفريع بـ ",[14,235,236],{},"typeof window","، وتستطيع الشيفرة المُصيَّرة على الخادم أن تتشارك مع تلك المُصيَّرة على العميل طبقة HTTP واحدة.",[10,239,240,134,243,245],{},[106,241,242],{},"قابلية التوسيع دون تسلسلات أصناف.",[14,244,16],{}," مجرَّد دالة عادية، وعميلك المخصَّص أيضاً دالة عادية بنفس التوقيع. التغليف، وCurrying، والوسائط، والاستبدال: كل هذه التقنيات القياسية تنطبق كما هي، وهذا بالضبط ما يجعل القسم التالي ممكناً.",[93,247,249],{"id":248},"بناء-غلاف-fetch-مخصَّص","بناء غلاف fetch مخصَّص",[10,251,252,253,255,256,183,258,261,262,264],{},"قلَّما تجد قاعدة شيفرة إنتاجية تستدعي ",[14,254,16],{}," مباشرة. الغالب أن يكون لديها ",[14,257,90],{},[14,259,260],{},"apiClient"," يضع فوقه طبقات من الافتراضات، وترويسات المصادقة، والمهلات، وإعادة المحاولة، وتوحيد الأخطاء، وربما interceptors أيضاً. والحيلة كلها في أن تبنيه دون أن يفقد شكل ",[14,263,16],{},"، حتى إذا احتجتَ لاحقاً أن تُبدِّل الناقل (بناقل مبني على XHR مثلاً كما سنرى) لا تضطر إلى إعادة كتابة كل موضع استدعاء.",[10,266,267],{},"سأمرُّ على أربع طبقات، كل واحدة منها صغيرة، وكل واحدة تستحق مكانها.",[269,270,272],"h3",{"id":271},"الطبقة-الأولى-رابط-أساسي-وترويسات-افتراضية-وjson-مُحلَّل","الطبقة الأولى: رابط أساسي، وترويسات افتراضية، وJSON مُحلَّل",[10,274,275],{},"ابدأ بأقصر غلاف مفيد يمكن تصوُّره:",[277,278,283],"pre",{"className":279,"code":280,"language":281,"meta":282,"style":282},"language-typescript shiki shiki-themes github-light github-dark","export async function request\u003CT>(path: string, init: RequestInit = {}): Promise\u003CT> {\n  const res = await fetch(path, init);\n  if (!res.ok) throw new ApiError(res.status, await res.text());\n  return res.json() as Promise\u003CT>;\n}\n","typescript","",[14,284,285,354,374,413,439],{"__ignoreMap":282},[286,287,290,294,297,300,304,308,311,314,318,321,325,328,331,333,336,339,342,344,347,349,351],"span",{"class":288,"line":289},"line",1,[286,291,293],{"class":292},"szBVR","export",[286,295,296],{"class":292}," async",[286,298,299],{"class":292}," function",[286,301,303],{"class":302},"sScJk"," request",[286,305,307],{"class":306},"sVt8B","\u003C",[286,309,310],{"class":302},"T",[286,312,313],{"class":306},">(",[286,315,317],{"class":316},"s4XuR","path",[286,319,320],{"class":292},":",[286,322,324],{"class":323},"sj4cs"," string",[286,326,327],{"class":306},", ",[286,329,330],{"class":316},"init",[286,332,320],{"class":292},[286,334,335],{"class":302}," RequestInit",[286,337,338],{"class":292}," =",[286,340,341],{"class":306}," {})",[286,343,320],{"class":292},[286,345,346],{"class":302}," Promise",[286,348,307],{"class":306},[286,350,310],{"class":302},[286,352,353],{"class":306},"> {\n",[286,355,357,360,363,365,368,371],{"class":288,"line":356},2,[286,358,359],{"class":292},"  const",[286,361,362],{"class":323}," res",[286,364,338],{"class":292},[286,366,367],{"class":292}," await",[286,369,370],{"class":302}," fetch",[286,372,373],{"class":306},"(path, init);\n",[286,375,377,380,383,386,389,392,395,398,401,404,407,410],{"class":288,"line":376},3,[286,378,379],{"class":292},"  if",[286,381,382],{"class":306}," (",[286,384,385],{"class":292},"!",[286,387,388],{"class":306},"res.ok) ",[286,390,391],{"class":292},"throw",[286,393,394],{"class":292}," new",[286,396,397],{"class":302}," ApiError",[286,399,400],{"class":306},"(res.status, ",[286,402,403],{"class":292},"await",[286,405,406],{"class":306}," res.",[286,408,409],{"class":302},"text",[286,411,412],{"class":306},"());\n",[286,414,416,419,421,424,427,430,432,434,436],{"class":288,"line":415},4,[286,417,418],{"class":292},"  return",[286,420,406],{"class":306},[286,422,423],{"class":302},"json",[286,425,426],{"class":306},"() ",[286,428,429],{"class":292},"as",[286,431,346],{"class":302},[286,433,307],{"class":306},[286,435,310],{"class":302},[286,437,438],{"class":306},">;\n",[286,440,442],{"class":288,"line":441},5,[286,443,444],{"class":306},"}\n",[10,446,447],{},"ثلاثة أسطر تشتري لك سلوكين: رمي الخطأ عند أي استجابة غير 2xx، وتحليل JSON عند النجاح. وبمجرَّد أن تلاحظ نفسك تُكرِّر هذين السلوكين في كل موقع استدعاء، يكون الغلاف قد استحقَّ وجوده.",[10,449,450],{},"وشكل أنفع قليلاً يقبل إعداداً:",[277,452,454],{"className":279,"code":453,"language":281,"meta":282,"style":282},"export function createClient(options: {\n  baseURL: string;\n  defaultHeaders?: Record\u003Cstring, string>;\n}) {\n  const { baseURL, defaultHeaders = {} } = options;\n\n  return async function request\u003CT>(path: string, init: RequestInit = {}): Promise\u003CT> {\n    const url = new URL(path, baseURL).toString();\n    const headers = { ...defaultHeaders, ...(init.headers as Record\u003Cstring, string>) };\n    const res = await fetch(url, { ...init, headers });\n    if (!res.ok) throw new ApiError(res.status, await res.text());\n    return res.json() as Promise\u003CT>;\n  };\n}\n",[14,455,456,476,488,510,515,541,548,593,618,656,677,705,727,733],{"__ignoreMap":282},[286,457,458,460,462,465,468,471,473],{"class":288,"line":289},[286,459,293],{"class":292},[286,461,299],{"class":292},[286,463,464],{"class":302}," createClient",[286,466,467],{"class":306},"(",[286,469,470],{"class":316},"options",[286,472,320],{"class":292},[286,474,475],{"class":306}," {\n",[286,477,478,481,483,485],{"class":288,"line":356},[286,479,480],{"class":316},"  baseURL",[286,482,320],{"class":292},[286,484,324],{"class":323},[286,486,487],{"class":306},";\n",[286,489,490,493,496,499,501,504,506,508],{"class":288,"line":376},[286,491,492],{"class":316},"  defaultHeaders",[286,494,495],{"class":292},"?:",[286,497,498],{"class":302}," Record",[286,500,307],{"class":306},[286,502,503],{"class":323},"string",[286,505,327],{"class":306},[286,507,503],{"class":323},[286,509,438],{"class":306},[286,511,512],{"class":288,"line":415},[286,513,514],{"class":306},"}) {\n",[286,516,517,519,522,525,527,530,532,535,538],{"class":288,"line":441},[286,518,359],{"class":292},[286,520,521],{"class":306}," { ",[286,523,524],{"class":323},"baseURL",[286,526,327],{"class":306},[286,528,529],{"class":323},"defaultHeaders",[286,531,338],{"class":292},[286,533,534],{"class":306}," {} } ",[286,536,537],{"class":292},"=",[286,539,540],{"class":306}," options;\n",[286,542,544],{"class":288,"line":543},6,[286,545,547],{"emptyLinePlaceholder":546},true,"\n",[286,549,551,553,555,557,559,561,563,565,567,569,571,573,575,577,579,581,583,585,587,589,591],{"class":288,"line":550},7,[286,552,418],{"class":292},[286,554,296],{"class":292},[286,556,299],{"class":292},[286,558,303],{"class":302},[286,560,307],{"class":306},[286,562,310],{"class":302},[286,564,313],{"class":306},[286,566,317],{"class":316},[286,568,320],{"class":292},[286,570,324],{"class":323},[286,572,327],{"class":306},[286,574,330],{"class":316},[286,576,320],{"class":292},[286,578,335],{"class":302},[286,580,338],{"class":292},[286,582,341],{"class":306},[286,584,320],{"class":292},[286,586,346],{"class":302},[286,588,307],{"class":306},[286,590,310],{"class":302},[286,592,353],{"class":306},[286,594,596,599,602,604,606,609,612,615],{"class":288,"line":595},8,[286,597,598],{"class":292},"    const",[286,600,601],{"class":323}," url",[286,603,338],{"class":292},[286,605,394],{"class":292},[286,607,608],{"class":302}," URL",[286,610,611],{"class":306},"(path, baseURL).",[286,613,614],{"class":302},"toString",[286,616,617],{"class":306},"();\n",[286,619,621,623,626,628,630,633,636,638,641,643,645,647,649,651,653],{"class":288,"line":620},9,[286,622,598],{"class":292},[286,624,625],{"class":323}," headers",[286,627,338],{"class":292},[286,629,521],{"class":306},[286,631,632],{"class":292},"...",[286,634,635],{"class":306},"defaultHeaders, ",[286,637,632],{"class":292},[286,639,640],{"class":306},"(init.headers ",[286,642,429],{"class":292},[286,644,498],{"class":302},[286,646,307],{"class":306},[286,648,503],{"class":323},[286,650,327],{"class":306},[286,652,503],{"class":323},[286,654,655],{"class":306},">) };\n",[286,657,659,661,663,665,667,669,672,674],{"class":288,"line":658},10,[286,660,598],{"class":292},[286,662,362],{"class":323},[286,664,338],{"class":292},[286,666,367],{"class":292},[286,668,370],{"class":302},[286,670,671],{"class":306},"(url, { ",[286,673,632],{"class":292},[286,675,676],{"class":306},"init, headers });\n",[286,678,680,683,685,687,689,691,693,695,697,699,701,703],{"class":288,"line":679},11,[286,681,682],{"class":292},"    if",[286,684,382],{"class":306},[286,686,385],{"class":292},[286,688,388],{"class":306},[286,690,391],{"class":292},[286,692,394],{"class":292},[286,694,397],{"class":302},[286,696,400],{"class":306},[286,698,403],{"class":292},[286,700,406],{"class":306},[286,702,409],{"class":302},[286,704,412],{"class":306},[286,706,708,711,713,715,717,719,721,723,725],{"class":288,"line":707},12,[286,709,710],{"class":292},"    return",[286,712,406],{"class":306},[286,714,423],{"class":302},[286,716,426],{"class":306},[286,718,429],{"class":292},[286,720,346],{"class":302},[286,722,307],{"class":306},[286,724,310],{"class":302},[286,726,438],{"class":306},[286,728,730],{"class":288,"line":729},13,[286,731,732],{"class":306},"  };\n",[286,734,736],{"class":288,"line":735},14,[286,737,444],{"class":306},[10,739,740,743,744,747,748,751],{},[14,741,742],{},"new URL(path, baseURL)"," هو الجزء الذي يستحق أن تسرقه. يتعامل مع الشرطات المائلة في البداية والنهاية بشكل صحيح ومنذ اللحظة الأولى، فلا تحتاج إلى تلك اللعبة اليدوية ",[14,745,746],{},"${baseURL}\u002F${path}.replace(\u002F\\\u002F+\u002Fg, '\u002F')"," التي ينتهي إليها كل عميل مرتجل مع الوقت. مرِّر مساراً نسبياً فسيُحلَّل مقابل الرابط الأساسي، ومرِّر رابطاً مطلقاً فسيستخدمه ",[14,749,750],{},"URL"," كما هو.",[269,753,755,756],{"id":754},"الطبقة-الثانية-المهلات-عبر-abortsignal","الطبقة الثانية: المهلات عبر ",[14,757,758],{},"AbortSignal",[10,760,761,762,320],{},"الطريقة الصحيحة لتنفيذ المهلات هي ",[14,763,159],{},[277,765,767],{"className":279,"code":766,"language":281,"meta":282,"style":282},"const signal = AbortSignal.timeout(30_000);\nconst res = await fetch(url, { signal });\n",[14,768,769,793],{"__ignoreMap":282},[286,770,771,774,777,779,782,785,787,790],{"class":288,"line":289},[286,772,773],{"class":292},"const",[286,775,776],{"class":323}," signal",[286,778,338],{"class":292},[286,780,781],{"class":306}," AbortSignal.",[286,783,784],{"class":302},"timeout",[286,786,467],{"class":306},[286,788,789],{"class":323},"30_000",[286,791,792],{"class":306},");\n",[286,794,795,797,799,801,803,805],{"class":288,"line":356},[286,796,773],{"class":292},[286,798,362],{"class":323},[286,800,338],{"class":292},[286,802,367],{"class":292},[286,804,370],{"class":302},[286,806,807],{"class":306},"(url, { signal });\n",[10,809,810],{},"وإن مرَّر المستدعي إشارته الخاصة إلى جانب مهلتك، فادمج الاثنتين بحيث تستطيع أيٌّ منهما إلغاء الطلب:",[277,812,814],{"className":279,"code":813,"language":281,"meta":282,"style":282},"function mergeSignals(a?: AbortSignal, b?: AbortSignal): AbortSignal | undefined {\n  if (!a) return b;\n  if (!b) return a;\n  if ('any' in AbortSignal) return AbortSignal.any([a, b]);\n  \u002F\u002F fallback for older runtimes\n  const controller = new AbortController();\n  const onAbort = () => controller.abort();\n  a.addEventListener('abort', onAbort, { once: true });\n  b.addEventListener('abort', onAbort, { once: true });\n  return controller.signal;\n}\n",[14,815,816,858,875,891,917,923,939,962,984,1001,1008],{"__ignoreMap":282},[286,817,818,821,824,826,829,831,834,836,839,841,843,846,848,850,853,856],{"class":288,"line":289},[286,819,820],{"class":292},"function",[286,822,823],{"class":302}," mergeSignals",[286,825,467],{"class":306},[286,827,828],{"class":316},"a",[286,830,495],{"class":292},[286,832,833],{"class":302}," AbortSignal",[286,835,327],{"class":306},[286,837,838],{"class":316},"b",[286,840,495],{"class":292},[286,842,833],{"class":302},[286,844,845],{"class":306},")",[286,847,320],{"class":292},[286,849,833],{"class":302},[286,851,852],{"class":292}," |",[286,854,855],{"class":323}," undefined",[286,857,475],{"class":306},[286,859,860,862,864,866,869,872],{"class":288,"line":356},[286,861,379],{"class":292},[286,863,382],{"class":306},[286,865,385],{"class":292},[286,867,868],{"class":306},"a) ",[286,870,871],{"class":292},"return",[286,873,874],{"class":306}," b;\n",[286,876,877,879,881,883,886,888],{"class":288,"line":376},[286,878,379],{"class":292},[286,880,382],{"class":306},[286,882,385],{"class":292},[286,884,885],{"class":306},"b) ",[286,887,871],{"class":292},[286,889,890],{"class":306}," a;\n",[286,892,893,895,897,901,904,907,909,911,914],{"class":288,"line":415},[286,894,379],{"class":292},[286,896,382],{"class":306},[286,898,900],{"class":899},"sZZnC","'any'",[286,902,903],{"class":292}," in",[286,905,906],{"class":306}," AbortSignal) ",[286,908,871],{"class":292},[286,910,781],{"class":306},[286,912,913],{"class":302},"any",[286,915,916],{"class":306},"([a, b]);\n",[286,918,919],{"class":288,"line":441},[286,920,922],{"class":921},"sJ8bj","  \u002F\u002F fallback for older runtimes\n",[286,924,925,927,930,932,934,937],{"class":288,"line":543},[286,926,359],{"class":292},[286,928,929],{"class":323}," controller",[286,931,338],{"class":292},[286,933,394],{"class":292},[286,935,936],{"class":302}," AbortController",[286,938,617],{"class":306},[286,940,941,943,946,948,951,954,957,960],{"class":288,"line":550},[286,942,359],{"class":292},[286,944,945],{"class":302}," onAbort",[286,947,338],{"class":292},[286,949,950],{"class":306}," () ",[286,952,953],{"class":292},"=>",[286,955,956],{"class":306}," controller.",[286,958,959],{"class":302},"abort",[286,961,617],{"class":306},[286,963,964,967,970,972,975,978,981],{"class":288,"line":595},[286,965,966],{"class":306},"  a.",[286,968,969],{"class":302},"addEventListener",[286,971,467],{"class":306},[286,973,974],{"class":899},"'abort'",[286,976,977],{"class":306},", onAbort, { once: ",[286,979,980],{"class":323},"true",[286,982,983],{"class":306}," });\n",[286,985,986,989,991,993,995,997,999],{"class":288,"line":620},[286,987,988],{"class":306},"  b.",[286,990,969],{"class":302},[286,992,467],{"class":306},[286,994,974],{"class":899},[286,996,977],{"class":306},[286,998,980],{"class":323},[286,1000,983],{"class":306},[286,1002,1003,1005],{"class":288,"line":658},[286,1004,418],{"class":292},[286,1006,1007],{"class":306}," controller.signal;\n",[286,1009,1010],{"class":288,"line":679},[286,1011,444],{"class":306},[10,1013,1014,1016,1017,1019,1020,1023,1024,1027],{},[14,1015,155],{}," تقوم بالدمج أصلياً في المتصفحات الحديثة، أما البديل الاحتياطي فيمرِّر الإشارتين يدوياً إلى متحكِّم جديد. في الحالتين، تصل إشارة واحدة إلى ",[14,1018,16],{},"، وينتهي انقضاء المهلة أو إلغاء المستدعي إلى مسار الرفض نفسه. وهذا مهم، لأن ",[14,1021,1022],{},"try\u002Fcatch"," يستطيع أن يفحص ",[14,1025,1026],{},"err.name === 'AbortError'"," بغضِّ النظر عن الجهة التي أطلقت الحدث.",[269,1029,1031],{"id":1030},"الطبقة-الثالثة-interceptors","الطبقة الثالثة: Interceptors",[10,1033,1034],{},"ثلاث سلاسل (طلب، واستجابة، وخطأ) تكفي لتحوِّل عميلاً جامداً إلى عميل قابل للتوسيع:",[277,1036,1038],{"className":279,"code":1037,"language":281,"meta":282,"style":282},"type RequestInterceptor = (ctx: { url: string; init: RequestInit }) => void | Promise\u003Cvoid>;\ntype ResponseInterceptor = (res: Response) => Response | Promise\u003CResponse>;\n\nexport function createClient(options: { baseURL: string; defaultHeaders?: Record\u003Cstring, string> }) {\n  const requestInterceptors: RequestInterceptor[] = [];\n  const responseInterceptors: ResponseInterceptor[] = [];\n\n  async function request\u003CT>(path: string, init: RequestInit = {}): Promise\u003CT> {\n    const ctx = {\n      url: new URL(path, options.baseURL).toString(),\n      init: { ...init, headers: { ...(options.defaultHeaders ?? {}), ...(init.headers as Record\u003Cstring, string>) } },\n    };\n    for (const fn of requestInterceptors) await fn(ctx);\n    let res = await fetch(ctx.url, ctx.init);\n    for (const fn of responseInterceptors) res = await fn(res);\n    if (!res.ok) throw new ApiError(res.status, await res.text());\n    return res.json() as Promise\u003CT>;\n  }\n\n  request.useRequest = (fn: RequestInterceptor) => {\n    requestInterceptors.push(fn);\n    return () => void requestInterceptors.splice(requestInterceptors.indexOf(fn), 1);\n  };\n  request.useResponse = (fn: ResponseInterceptor) => {\n    responseInterceptors.push(fn);\n    return () => void responseInterceptors.splice(responseInterceptors.indexOf(fn), 1);\n  };\n\n  return request;\n}\n",[14,1039,1040,1094,1130,1134,1175,1194,1211,1215,1258,1269,1287,1327,1332,1357,1374,1399,1426,1447,1453,1458,1484,1496,1527,1532,1556,1566,1593,1598,1603,1611],{"__ignoreMap":282},[286,1041,1042,1045,1048,1050,1052,1055,1057,1059,1062,1064,1066,1069,1071,1073,1075,1078,1080,1083,1085,1087,1089,1092],{"class":288,"line":289},[286,1043,1044],{"class":292},"type",[286,1046,1047],{"class":302}," RequestInterceptor",[286,1049,338],{"class":292},[286,1051,382],{"class":306},[286,1053,1054],{"class":316},"ctx",[286,1056,320],{"class":292},[286,1058,521],{"class":306},[286,1060,1061],{"class":316},"url",[286,1063,320],{"class":292},[286,1065,324],{"class":323},[286,1067,1068],{"class":306},"; ",[286,1070,330],{"class":316},[286,1072,320],{"class":292},[286,1074,335],{"class":302},[286,1076,1077],{"class":306}," }) ",[286,1079,953],{"class":292},[286,1081,1082],{"class":323}," void",[286,1084,852],{"class":292},[286,1086,346],{"class":302},[286,1088,307],{"class":306},[286,1090,1091],{"class":323},"void",[286,1093,438],{"class":306},[286,1095,1096,1098,1101,1103,1105,1108,1110,1113,1116,1118,1120,1122,1124,1126,1128],{"class":288,"line":356},[286,1097,1044],{"class":292},[286,1099,1100],{"class":302}," ResponseInterceptor",[286,1102,338],{"class":292},[286,1104,382],{"class":306},[286,1106,1107],{"class":316},"res",[286,1109,320],{"class":292},[286,1111,1112],{"class":302}," Response",[286,1114,1115],{"class":306},") ",[286,1117,953],{"class":292},[286,1119,1112],{"class":302},[286,1121,852],{"class":292},[286,1123,346],{"class":302},[286,1125,307],{"class":306},[286,1127,28],{"class":302},[286,1129,438],{"class":306},[286,1131,1132],{"class":288,"line":376},[286,1133,547],{"emptyLinePlaceholder":546},[286,1135,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162,1164,1166,1168,1170,1172],{"class":288,"line":415},[286,1137,293],{"class":292},[286,1139,299],{"class":292},[286,1141,464],{"class":302},[286,1143,467],{"class":306},[286,1145,470],{"class":316},[286,1147,320],{"class":292},[286,1149,521],{"class":306},[286,1151,524],{"class":316},[286,1153,320],{"class":292},[286,1155,324],{"class":323},[286,1157,1068],{"class":306},[286,1159,529],{"class":316},[286,1161,495],{"class":292},[286,1163,498],{"class":302},[286,1165,307],{"class":306},[286,1167,503],{"class":323},[286,1169,327],{"class":306},[286,1171,503],{"class":323},[286,1173,1174],{"class":306},"> }) {\n",[286,1176,1177,1179,1182,1184,1186,1189,1191],{"class":288,"line":441},[286,1178,359],{"class":292},[286,1180,1181],{"class":323}," requestInterceptors",[286,1183,320],{"class":292},[286,1185,1047],{"class":302},[286,1187,1188],{"class":306},"[] ",[286,1190,537],{"class":292},[286,1192,1193],{"class":306}," [];\n",[286,1195,1196,1198,1201,1203,1205,1207,1209],{"class":288,"line":543},[286,1197,359],{"class":292},[286,1199,1200],{"class":323}," responseInterceptors",[286,1202,320],{"class":292},[286,1204,1100],{"class":302},[286,1206,1188],{"class":306},[286,1208,537],{"class":292},[286,1210,1193],{"class":306},[286,1212,1213],{"class":288,"line":550},[286,1214,547],{"emptyLinePlaceholder":546},[286,1216,1217,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248,1250,1252,1254,1256],{"class":288,"line":595},[286,1218,1219],{"class":292},"  async",[286,1221,299],{"class":292},[286,1223,303],{"class":302},[286,1225,307],{"class":306},[286,1227,310],{"class":302},[286,1229,313],{"class":306},[286,1231,317],{"class":316},[286,1233,320],{"class":292},[286,1235,324],{"class":323},[286,1237,327],{"class":306},[286,1239,330],{"class":316},[286,1241,320],{"class":292},[286,1243,335],{"class":302},[286,1245,338],{"class":292},[286,1247,341],{"class":306},[286,1249,320],{"class":292},[286,1251,346],{"class":302},[286,1253,307],{"class":306},[286,1255,310],{"class":302},[286,1257,353],{"class":306},[286,1259,1260,1262,1265,1267],{"class":288,"line":620},[286,1261,598],{"class":292},[286,1263,1264],{"class":323}," ctx",[286,1266,338],{"class":292},[286,1268,475],{"class":306},[286,1270,1271,1274,1277,1279,1282,1284],{"class":288,"line":658},[286,1272,1273],{"class":306},"      url: ",[286,1275,1276],{"class":292},"new",[286,1278,608],{"class":302},[286,1280,1281],{"class":306},"(path, options.baseURL).",[286,1283,614],{"class":302},[286,1285,1286],{"class":306},"(),\n",[286,1288,1289,1292,1294,1297,1299,1302,1305,1308,1310,1312,1314,1316,1318,1320,1322,1324],{"class":288,"line":679},[286,1290,1291],{"class":306},"      init: { ",[286,1293,632],{"class":292},[286,1295,1296],{"class":306},"init, headers: { ",[286,1298,632],{"class":292},[286,1300,1301],{"class":306},"(options.defaultHeaders ",[286,1303,1304],{"class":292},"??",[286,1306,1307],{"class":306}," {}), ",[286,1309,632],{"class":292},[286,1311,640],{"class":306},[286,1313,429],{"class":292},[286,1315,498],{"class":302},[286,1317,307],{"class":306},[286,1319,503],{"class":323},[286,1321,327],{"class":306},[286,1323,503],{"class":323},[286,1325,1326],{"class":306},">) } },\n",[286,1328,1329],{"class":288,"line":707},[286,1330,1331],{"class":306},"    };\n",[286,1333,1334,1337,1339,1341,1344,1347,1350,1352,1354],{"class":288,"line":729},[286,1335,1336],{"class":292},"    for",[286,1338,382],{"class":306},[286,1340,773],{"class":292},[286,1342,1343],{"class":323}," fn",[286,1345,1346],{"class":292}," of",[286,1348,1349],{"class":306}," requestInterceptors) ",[286,1351,403],{"class":292},[286,1353,1343],{"class":302},[286,1355,1356],{"class":306},"(ctx);\n",[286,1358,1359,1362,1365,1367,1369,1371],{"class":288,"line":735},[286,1360,1361],{"class":292},"    let",[286,1363,1364],{"class":306}," res ",[286,1366,537],{"class":292},[286,1368,367],{"class":292},[286,1370,370],{"class":302},[286,1372,1373],{"class":306},"(ctx.url, ctx.init);\n",[286,1375,1377,1379,1381,1383,1385,1387,1390,1392,1394,1396],{"class":288,"line":1376},15,[286,1378,1336],{"class":292},[286,1380,382],{"class":306},[286,1382,773],{"class":292},[286,1384,1343],{"class":323},[286,1386,1346],{"class":292},[286,1388,1389],{"class":306}," responseInterceptors) res ",[286,1391,537],{"class":292},[286,1393,367],{"class":292},[286,1395,1343],{"class":302},[286,1397,1398],{"class":306},"(res);\n",[286,1400,1402,1404,1406,1408,1410,1412,1414,1416,1418,1420,1422,1424],{"class":288,"line":1401},16,[286,1403,682],{"class":292},[286,1405,382],{"class":306},[286,1407,385],{"class":292},[286,1409,388],{"class":306},[286,1411,391],{"class":292},[286,1413,394],{"class":292},[286,1415,397],{"class":302},[286,1417,400],{"class":306},[286,1419,403],{"class":292},[286,1421,406],{"class":306},[286,1423,409],{"class":302},[286,1425,412],{"class":306},[286,1427,1429,1431,1433,1435,1437,1439,1441,1443,1445],{"class":288,"line":1428},17,[286,1430,710],{"class":292},[286,1432,406],{"class":306},[286,1434,423],{"class":302},[286,1436,426],{"class":306},[286,1438,429],{"class":292},[286,1440,346],{"class":302},[286,1442,307],{"class":306},[286,1444,310],{"class":302},[286,1446,438],{"class":306},[286,1448,1450],{"class":288,"line":1449},18,[286,1451,1452],{"class":306},"  }\n",[286,1454,1456],{"class":288,"line":1455},19,[286,1457,547],{"emptyLinePlaceholder":546},[286,1459,1461,1464,1467,1469,1471,1474,1476,1478,1480,1482],{"class":288,"line":1460},20,[286,1462,1463],{"class":306},"  request.",[286,1465,1466],{"class":302},"useRequest",[286,1468,338],{"class":292},[286,1470,382],{"class":306},[286,1472,1473],{"class":316},"fn",[286,1475,320],{"class":292},[286,1477,1047],{"class":302},[286,1479,1115],{"class":306},[286,1481,953],{"class":292},[286,1483,475],{"class":306},[286,1485,1487,1490,1493],{"class":288,"line":1486},21,[286,1488,1489],{"class":306},"    requestInterceptors.",[286,1491,1492],{"class":302},"push",[286,1494,1495],{"class":306},"(fn);\n",[286,1497,1499,1501,1503,1505,1507,1510,1513,1516,1519,1522,1525],{"class":288,"line":1498},22,[286,1500,710],{"class":292},[286,1502,950],{"class":306},[286,1504,953],{"class":292},[286,1506,1082],{"class":292},[286,1508,1509],{"class":306}," requestInterceptors.",[286,1511,1512],{"class":302},"splice",[286,1514,1515],{"class":306},"(requestInterceptors.",[286,1517,1518],{"class":302},"indexOf",[286,1520,1521],{"class":306},"(fn), ",[286,1523,1524],{"class":323},"1",[286,1526,792],{"class":306},[286,1528,1530],{"class":288,"line":1529},23,[286,1531,732],{"class":306},[286,1533,1535,1537,1540,1542,1544,1546,1548,1550,1552,1554],{"class":288,"line":1534},24,[286,1536,1463],{"class":306},[286,1538,1539],{"class":302},"useResponse",[286,1541,338],{"class":292},[286,1543,382],{"class":306},[286,1545,1473],{"class":316},[286,1547,320],{"class":292},[286,1549,1100],{"class":302},[286,1551,1115],{"class":306},[286,1553,953],{"class":292},[286,1555,475],{"class":306},[286,1557,1559,1562,1564],{"class":288,"line":1558},25,[286,1560,1561],{"class":306},"    responseInterceptors.",[286,1563,1492],{"class":302},[286,1565,1495],{"class":306},[286,1567,1569,1571,1573,1575,1577,1580,1582,1585,1587,1589,1591],{"class":288,"line":1568},26,[286,1570,710],{"class":292},[286,1572,950],{"class":306},[286,1574,953],{"class":292},[286,1576,1082],{"class":292},[286,1578,1579],{"class":306}," responseInterceptors.",[286,1581,1512],{"class":302},[286,1583,1584],{"class":306},"(responseInterceptors.",[286,1586,1518],{"class":302},[286,1588,1521],{"class":306},[286,1590,1524],{"class":323},[286,1592,792],{"class":306},[286,1594,1596],{"class":288,"line":1595},27,[286,1597,732],{"class":306},[286,1599,1601],{"class":288,"line":1600},28,[286,1602,547],{"emptyLinePlaceholder":546},[286,1604,1606,1608],{"class":288,"line":1605},29,[286,1607,418],{"class":292},[286,1609,1610],{"class":306}," request;\n",[286,1612,1614],{"class":288,"line":1613},30,[286,1615,444],{"class":306},[10,1617,1618,1619,1622],{},"كل تابع ",[14,1620,1621],{},"use*"," يُعيد دالة لإلغاء التسجيل، وهذه فائدة عملية حين تسجِّل interceptor داخل مكوِّن وتريد التنظيف عند إزالته.",[10,1624,1625],{},"المصادقة تتحوَّل بذلك إلى سطر واحد:",[277,1627,1629],{"className":279,"code":1628,"language":281,"meta":282,"style":282},"api.useRequest((ctx) => {\n  ctx.init.headers = { ...(ctx.init.headers as Record\u003Cstring, string>), Authorization: `Bearer ${token.value}` };\n});\n",[14,1630,1631,1649,1695],{"__ignoreMap":282},[286,1632,1633,1636,1638,1641,1643,1645,1647],{"class":288,"line":289},[286,1634,1635],{"class":306},"api.",[286,1637,1466],{"class":302},[286,1639,1640],{"class":306},"((",[286,1642,1054],{"class":316},[286,1644,1115],{"class":306},[286,1646,953],{"class":292},[286,1648,475],{"class":306},[286,1650,1651,1654,1656,1658,1660,1663,1665,1667,1669,1671,1673,1675,1678,1681,1684,1686,1689,1692],{"class":288,"line":356},[286,1652,1653],{"class":306},"  ctx.init.headers ",[286,1655,537],{"class":292},[286,1657,521],{"class":306},[286,1659,632],{"class":292},[286,1661,1662],{"class":306},"(ctx.init.headers ",[286,1664,429],{"class":292},[286,1666,498],{"class":302},[286,1668,307],{"class":306},[286,1670,503],{"class":323},[286,1672,327],{"class":306},[286,1674,503],{"class":323},[286,1676,1677],{"class":306},">), Authorization: ",[286,1679,1680],{"class":899},"`Bearer ${",[286,1682,1683],{"class":306},"token",[286,1685,60],{"class":899},[286,1687,1688],{"class":306},"value",[286,1690,1691],{"class":899},"}`",[286,1693,1694],{"class":306}," };\n",[286,1696,1697],{"class":288,"line":376},[286,1698,1699],{"class":306},"});\n",[10,1701,1702],{},"وكذلك سيناريو \"تجديد التوكن عند 401\":",[277,1704,1706],{"className":279,"code":1705,"language":281,"meta":282,"style":282},"api.useResponse(async (res) => {\n  if (res.status !== 401) return res;\n  await refreshAuth();\n  return fetch(res.url, { \u002F* re-run options *\u002F });\n});\n",[14,1707,1708,1729,1749,1759,1773],{"__ignoreMap":282},[286,1709,1710,1712,1714,1716,1719,1721,1723,1725,1727],{"class":288,"line":289},[286,1711,1635],{"class":306},[286,1713,1539],{"class":302},[286,1715,467],{"class":306},[286,1717,1718],{"class":292},"async",[286,1720,382],{"class":306},[286,1722,1107],{"class":316},[286,1724,1115],{"class":306},[286,1726,953],{"class":292},[286,1728,475],{"class":306},[286,1730,1731,1733,1736,1739,1742,1744,1746],{"class":288,"line":356},[286,1732,379],{"class":292},[286,1734,1735],{"class":306}," (res.status ",[286,1737,1738],{"class":292},"!==",[286,1740,1741],{"class":323}," 401",[286,1743,1115],{"class":306},[286,1745,871],{"class":292},[286,1747,1748],{"class":306}," res;\n",[286,1750,1751,1754,1757],{"class":288,"line":376},[286,1752,1753],{"class":292},"  await",[286,1755,1756],{"class":302}," refreshAuth",[286,1758,617],{"class":306},[286,1760,1761,1763,1765,1768,1771],{"class":288,"line":415},[286,1762,418],{"class":292},[286,1764,370],{"class":302},[286,1766,1767],{"class":306},"(res.url, { ",[286,1769,1770],{"class":921},"\u002F* re-run options *\u002F",[286,1772,983],{"class":306},[286,1774,1775],{"class":288,"line":441},[286,1776,1699],{"class":306},[269,1778,1780],{"id":1779},"الطبقة-الرابعة-إعادة-المحاولة-مع-exponential-backoff","الطبقة الرابعة: إعادة المحاولة مع Exponential Backoff",[10,1782,1783],{},"في الطلبات المُكافئة (GET وHEAD ومعظم PUT وDELETE)، يمتصُّ مساعد retry صغير أغلب الأخطاء الشبكية العابرة:",[277,1785,1787],{"className":279,"code":1786,"language":281,"meta":282,"style":282},"async function withRetry\u003CT>(fn: () => Promise\u003CT>, attempts = 2, baseDelayMs = 200): Promise\u003CT> {\n  let lastError: unknown;\n  for (let i = 0; i \u003C= attempts; i++) {\n    try {\n      return await fn();\n    } catch (e) {\n      lastError = e;\n      if (i \u003C attempts) await new Promise((r) => setTimeout(r, baseDelayMs * 2 ** i));\n    }\n  }\n  throw lastError;\n}\n",[14,1788,1789,1851,1866,1899,1906,1917,1928,1938,1983,1988,1992,2000],{"__ignoreMap":282},[286,1790,1791,1793,1795,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818,1821,1824,1826,1829,1831,1834,1836,1839,1841,1843,1845,1847,1849],{"class":288,"line":289},[286,1792,1718],{"class":292},[286,1794,299],{"class":292},[286,1796,1797],{"class":302}," withRetry",[286,1799,307],{"class":306},[286,1801,310],{"class":302},[286,1803,313],{"class":306},[286,1805,1473],{"class":302},[286,1807,320],{"class":292},[286,1809,950],{"class":306},[286,1811,953],{"class":292},[286,1813,346],{"class":302},[286,1815,307],{"class":306},[286,1817,310],{"class":302},[286,1819,1820],{"class":306},">, ",[286,1822,1823],{"class":316},"attempts",[286,1825,338],{"class":292},[286,1827,1828],{"class":323}," 2",[286,1830,327],{"class":306},[286,1832,1833],{"class":316},"baseDelayMs",[286,1835,338],{"class":292},[286,1837,1838],{"class":323}," 200",[286,1840,845],{"class":306},[286,1842,320],{"class":292},[286,1844,346],{"class":302},[286,1846,307],{"class":306},[286,1848,310],{"class":302},[286,1850,353],{"class":306},[286,1852,1853,1856,1859,1861,1864],{"class":288,"line":356},[286,1854,1855],{"class":292},"  let",[286,1857,1858],{"class":306}," lastError",[286,1860,320],{"class":292},[286,1862,1863],{"class":323}," unknown",[286,1865,487],{"class":306},[286,1867,1868,1871,1873,1876,1879,1881,1884,1887,1890,1893,1896],{"class":288,"line":376},[286,1869,1870],{"class":292},"  for",[286,1872,382],{"class":306},[286,1874,1875],{"class":292},"let",[286,1877,1878],{"class":306}," i ",[286,1880,537],{"class":292},[286,1882,1883],{"class":323}," 0",[286,1885,1886],{"class":306},"; i ",[286,1888,1889],{"class":292},"\u003C=",[286,1891,1892],{"class":306}," attempts; i",[286,1894,1895],{"class":292},"++",[286,1897,1898],{"class":306},") {\n",[286,1900,1901,1904],{"class":288,"line":415},[286,1902,1903],{"class":292},"    try",[286,1905,475],{"class":306},[286,1907,1908,1911,1913,1915],{"class":288,"line":441},[286,1909,1910],{"class":292},"      return",[286,1912,367],{"class":292},[286,1914,1343],{"class":302},[286,1916,617],{"class":306},[286,1918,1919,1922,1925],{"class":288,"line":543},[286,1920,1921],{"class":306},"    } ",[286,1923,1924],{"class":292},"catch",[286,1926,1927],{"class":306}," (e) {\n",[286,1929,1930,1933,1935],{"class":288,"line":550},[286,1931,1932],{"class":306},"      lastError ",[286,1934,537],{"class":292},[286,1936,1937],{"class":306}," e;\n",[286,1939,1940,1943,1946,1948,1951,1953,1955,1957,1959,1962,1964,1966,1969,1972,1975,1977,1980],{"class":288,"line":595},[286,1941,1942],{"class":292},"      if",[286,1944,1945],{"class":306}," (i ",[286,1947,307],{"class":292},[286,1949,1950],{"class":306}," attempts) ",[286,1952,403],{"class":292},[286,1954,394],{"class":292},[286,1956,346],{"class":323},[286,1958,1640],{"class":306},[286,1960,1961],{"class":316},"r",[286,1963,1115],{"class":306},[286,1965,953],{"class":292},[286,1967,1968],{"class":302}," setTimeout",[286,1970,1971],{"class":306},"(r, baseDelayMs ",[286,1973,1974],{"class":292},"*",[286,1976,1828],{"class":323},[286,1978,1979],{"class":292}," **",[286,1981,1982],{"class":306}," i));\n",[286,1984,1985],{"class":288,"line":620},[286,1986,1987],{"class":306},"    }\n",[286,1989,1990],{"class":288,"line":658},[286,1991,1452],{"class":306},[286,1993,1994,1997],{"class":288,"line":679},[286,1995,1996],{"class":292},"  throw",[286,1998,1999],{"class":306}," lastError;\n",[286,2001,2002],{"class":288,"line":707},[286,2003,444],{"class":306},[10,2005,2006,2007,2010],{},"ثلاث محاولات مع تأخير 200ms ثم 400ms ثم 800ms تُغطِّي معظم أخطاء 502 العابرة وتقطُّعات DNS. أما إعادة محاولة الطلبات غير المُكافئة فعلى مسؤوليتك، لأن إعادة إرسال POST ثبت في قاعدة البيانات فعلاً أسوأ بكثير من الفشل بوضوح. وأسهل طريقة لضبط ذلك هي قائمة سماح صغيرة داخل الغلاف نفسه (",[14,2008,2009],{},"['GET', 'HEAD', 'PUT', 'DELETE']",").",[269,2012,2014],{"id":2013},"النتيجة-النهائية","النتيجة النهائية",[10,2016,2017],{},"بعد الطبقات الأربع، تصبح واجهة الاستهلاك بهذا الشكل:",[277,2019,2021],{"className":279,"code":2020,"language":281,"meta":282,"style":282},"const api = createClient({\n  baseURL: 'https:\u002F\u002Fapi.example.com',\n  defaultHeaders: { Accept: 'application\u002Fjson' },\n});\n\napi.useRequest((ctx) => {\n  ctx.init.headers = { ...(ctx.init.headers as Record\u003Cstring, string>), Authorization: `Bearer ${token.value}` };\n});\n\nconst users = await api\u003CUser[]>('\u002Fusers');\n",[14,2022,2023,2037,2048,2059,2063,2067,2083,2121,2125,2129],{"__ignoreMap":282},[286,2024,2025,2027,2030,2032,2034],{"class":288,"line":289},[286,2026,773],{"class":292},[286,2028,2029],{"class":323}," api",[286,2031,338],{"class":292},[286,2033,464],{"class":302},[286,2035,2036],{"class":306},"({\n",[286,2038,2039,2042,2045],{"class":288,"line":356},[286,2040,2041],{"class":306},"  baseURL: ",[286,2043,2044],{"class":899},"'https:\u002F\u002Fapi.example.com'",[286,2046,2047],{"class":306},",\n",[286,2049,2050,2053,2056],{"class":288,"line":376},[286,2051,2052],{"class":306},"  defaultHeaders: { Accept: ",[286,2054,2055],{"class":899},"'application\u002Fjson'",[286,2057,2058],{"class":306}," },\n",[286,2060,2061],{"class":288,"line":415},[286,2062,1699],{"class":306},[286,2064,2065],{"class":288,"line":441},[286,2066,547],{"emptyLinePlaceholder":546},[286,2068,2069,2071,2073,2075,2077,2079,2081],{"class":288,"line":543},[286,2070,1635],{"class":306},[286,2072,1466],{"class":302},[286,2074,1640],{"class":306},[286,2076,1054],{"class":316},[286,2078,1115],{"class":306},[286,2080,953],{"class":292},[286,2082,475],{"class":306},[286,2084,2085,2087,2089,2091,2093,2095,2097,2099,2101,2103,2105,2107,2109,2111,2113,2115,2117,2119],{"class":288,"line":550},[286,2086,1653],{"class":306},[286,2088,537],{"class":292},[286,2090,521],{"class":306},[286,2092,632],{"class":292},[286,2094,1662],{"class":306},[286,2096,429],{"class":292},[286,2098,498],{"class":302},[286,2100,307],{"class":306},[286,2102,503],{"class":323},[286,2104,327],{"class":306},[286,2106,503],{"class":323},[286,2108,1677],{"class":306},[286,2110,1680],{"class":899},[286,2112,1683],{"class":306},[286,2114,60],{"class":899},[286,2116,1688],{"class":306},[286,2118,1691],{"class":899},[286,2120,1694],{"class":306},[286,2122,2123],{"class":288,"line":595},[286,2124,1699],{"class":306},[286,2126,2127],{"class":288,"line":620},[286,2128,547],{"emptyLinePlaceholder":546},[286,2130,2131,2133,2136,2138,2140,2142,2144,2147,2150,2153],{"class":288,"line":658},[286,2132,773],{"class":292},[286,2134,2135],{"class":323}," users",[286,2137,338],{"class":292},[286,2139,367],{"class":292},[286,2141,2029],{"class":302},[286,2143,307],{"class":306},[286,2145,2146],{"class":302},"User",[286,2148,2149],{"class":306},"[]>(",[286,2151,2152],{"class":899},"'\u002Fusers'",[286,2154,792],{"class":306},[10,2156,2157,2158,60],{},"استدعاء مسطَّح، ومصادقة تُدار مرَّة واحدة، وأخطاء تُرمى بنوع معروف، ومهلات وإعادة محاولة تُطبَّق حيث ينبغي. كل طبقة دالة عادية تلفُّ التي تحتها، وفي القاع لا يزال ",[14,2159,16],{},[10,2161,2162],{},"وهنا بالضبط تظهر ثغرة تقدُّم الرفع.",[93,2164,2166,2167,2169],{"id":2165},"لماذا-يعجز-fetch-عن-إظهار-تقدُّم-الرفع","لماذا يعجز ",[14,2168,16],{}," عن إظهار تقدُّم الرفع",[10,2171,2172,2173,2175],{},"تقدُّم التنزيل مع ",[14,2174,16],{}," يعمل، لكن بطريقة فيها بعض الحرج. تقرأ جسم الاستجابة كـ stream:",[277,2177,2179],{"className":279,"code":2178,"language":281,"meta":282,"style":282},"const res = await fetch('\u002Fbig.zip');\nconst total = Number(res.headers.get('content-length')) || null;\nlet loaded = 0;\nconst reader = res.body!.getReader();\nwhile (true) {\n  const { value, done } = await reader.read();\n  if (done) break;\n  loaded += value.byteLength;\n  onProgress(loaded, total);\n}\n",[14,2180,2181,2200,2234,2247,2268,2279,2307,2319,2330,2338],{"__ignoreMap":282},[286,2182,2183,2185,2187,2189,2191,2193,2195,2198],{"class":288,"line":289},[286,2184,773],{"class":292},[286,2186,362],{"class":323},[286,2188,338],{"class":292},[286,2190,367],{"class":292},[286,2192,370],{"class":302},[286,2194,467],{"class":306},[286,2196,2197],{"class":899},"'\u002Fbig.zip'",[286,2199,792],{"class":306},[286,2201,2202,2204,2207,2209,2212,2215,2218,2220,2223,2226,2229,2232],{"class":288,"line":356},[286,2203,773],{"class":292},[286,2205,2206],{"class":323}," total",[286,2208,338],{"class":292},[286,2210,2211],{"class":302}," Number",[286,2213,2214],{"class":306},"(res.headers.",[286,2216,2217],{"class":302},"get",[286,2219,467],{"class":306},[286,2221,2222],{"class":899},"'content-length'",[286,2224,2225],{"class":306},")) ",[286,2227,2228],{"class":292},"||",[286,2230,2231],{"class":323}," null",[286,2233,487],{"class":306},[286,2235,2236,2238,2241,2243,2245],{"class":288,"line":376},[286,2237,1875],{"class":292},[286,2239,2240],{"class":306}," loaded ",[286,2242,537],{"class":292},[286,2244,1883],{"class":323},[286,2246,487],{"class":306},[286,2248,2249,2251,2254,2256,2259,2261,2263,2266],{"class":288,"line":415},[286,2250,773],{"class":292},[286,2252,2253],{"class":323}," reader",[286,2255,338],{"class":292},[286,2257,2258],{"class":306}," res.body",[286,2260,385],{"class":292},[286,2262,60],{"class":306},[286,2264,2265],{"class":302},"getReader",[286,2267,617],{"class":306},[286,2269,2270,2273,2275,2277],{"class":288,"line":441},[286,2271,2272],{"class":292},"while",[286,2274,382],{"class":306},[286,2276,980],{"class":323},[286,2278,1898],{"class":306},[286,2280,2281,2283,2285,2287,2289,2292,2295,2297,2299,2302,2305],{"class":288,"line":543},[286,2282,359],{"class":292},[286,2284,521],{"class":306},[286,2286,1688],{"class":323},[286,2288,327],{"class":306},[286,2290,2291],{"class":323},"done",[286,2293,2294],{"class":306}," } ",[286,2296,537],{"class":292},[286,2298,367],{"class":292},[286,2300,2301],{"class":306}," reader.",[286,2303,2304],{"class":302},"read",[286,2306,617],{"class":306},[286,2308,2309,2311,2314,2317],{"class":288,"line":550},[286,2310,379],{"class":292},[286,2312,2313],{"class":306}," (done) ",[286,2315,2316],{"class":292},"break",[286,2318,487],{"class":306},[286,2320,2321,2324,2327],{"class":288,"line":595},[286,2322,2323],{"class":306},"  loaded ",[286,2325,2326],{"class":292},"+=",[286,2328,2329],{"class":306}," value.byteLength;\n",[286,2331,2332,2335],{"class":288,"line":620},[286,2333,2334],{"class":302},"  onProgress",[286,2336,2337],{"class":306},"(loaded, total);\n",[286,2339,2340],{"class":288,"line":658},[286,2341,444],{"class":306},[10,2343,2344,2345,2347],{},"هذا مقبول. صحيح أن عليك أن تحتفظ بالقطع بنفسك (أو أن تدفعها إلى ",[14,2346,28],{}," جديد إن أردتَ تمريرها إلى شيء آخر)، لكن البدائيات موجودة على الأقل.",[10,2349,2350,2351,2353,2354,2356,2357,2360,2361,2363,2364,2367],{},"أما في الرفع، فالقطعة المفقودة أن جسم ",[14,2352,24],{}," تستهلكه شبكة المتصفح، لا شيفرتك. تستطيع أن تمرِّر ",[14,2355,44],{},"، لكن لا يوجد أي حدث يُطلَق أثناء استنزاف المتصفح له. بعض إصدارات Chromium تدعم ",[14,2358,2359],{},"Request.duplex: 'half'"," مما يفتح باب streaming الرفع، غير أن حدث ",[14,2362,48],{}," لا يزال غائباً. سيتوجَّب عليك أن تُجهِّز ",[14,2365,2366],{},"pull()"," الخاص بالـ stream يدوياً لعدِّ البايتات، وحتى إذا فعلتَ ذلك فأنت تقيس ما يُطلقه الـ stream، لا ما أرسله الـ socket فعلاً.",[10,2369,2370],{},"عملياً، إن أردتَ شريط تقدُّم رفع في سنة 2026، فستستخدم XHR.",[93,2372,2374],{"id":2373},"الأحداث-الخمسة-في-xhr-التي-تهمُّك-حقاً","الأحداث الخمسة في XHR التي تهمُّك حقاً",[10,2376,2377],{},"يُطلق كل XHR مجموعة أحداث ثابتة ومتوقَّعة. والذي يهم منها لواجهات التقدُّم هو:",[277,2379,2381],{"className":279,"code":2380,"language":281,"meta":282,"style":282},"xhr.upload.onprogress = (e: ProgressEvent) => { \u002F* upload bytes *\u002F };\nxhr.onprogress        = (e: ProgressEvent) => { \u002F* download bytes *\u002F };\nxhr.onload            = () => { \u002F* request completed successfully *\u002F };\nxhr.onerror           = () => { \u002F* network error *\u002F };\nxhr.onabort           = () => { \u002F* xhr.abort() was called *\u002F };\nxhr.ontimeout         = () => { \u002F* xhr.timeout exceeded *\u002F };\n",[14,2382,2383,2414,2443,2464,2485,2505],{"__ignoreMap":282},[286,2384,2385,2388,2391,2393,2395,2398,2400,2403,2405,2407,2409,2412],{"class":288,"line":289},[286,2386,2387],{"class":306},"xhr.upload.",[286,2389,2390],{"class":302},"onprogress",[286,2392,338],{"class":292},[286,2394,382],{"class":306},[286,2396,2397],{"class":316},"e",[286,2399,320],{"class":292},[286,2401,2402],{"class":302}," ProgressEvent",[286,2404,1115],{"class":306},[286,2406,953],{"class":292},[286,2408,521],{"class":306},[286,2410,2411],{"class":921},"\u002F* upload bytes *\u002F",[286,2413,1694],{"class":306},[286,2415,2416,2419,2421,2424,2426,2428,2430,2432,2434,2436,2438,2441],{"class":288,"line":356},[286,2417,2418],{"class":306},"xhr.",[286,2420,2390],{"class":302},[286,2422,2423],{"class":292},"        =",[286,2425,382],{"class":306},[286,2427,2397],{"class":316},[286,2429,320],{"class":292},[286,2431,2402],{"class":302},[286,2433,1115],{"class":306},[286,2435,953],{"class":292},[286,2437,521],{"class":306},[286,2439,2440],{"class":921},"\u002F* download bytes *\u002F",[286,2442,1694],{"class":306},[286,2444,2445,2447,2450,2453,2455,2457,2459,2462],{"class":288,"line":376},[286,2446,2418],{"class":306},[286,2448,2449],{"class":302},"onload",[286,2451,2452],{"class":292},"            =",[286,2454,950],{"class":306},[286,2456,953],{"class":292},[286,2458,521],{"class":306},[286,2460,2461],{"class":921},"\u002F* request completed successfully *\u002F",[286,2463,1694],{"class":306},[286,2465,2466,2468,2471,2474,2476,2478,2480,2483],{"class":288,"line":415},[286,2467,2418],{"class":306},[286,2469,2470],{"class":302},"onerror",[286,2472,2473],{"class":292},"           =",[286,2475,950],{"class":306},[286,2477,953],{"class":292},[286,2479,521],{"class":306},[286,2481,2482],{"class":921},"\u002F* network error *\u002F",[286,2484,1694],{"class":306},[286,2486,2487,2489,2492,2494,2496,2498,2500,2503],{"class":288,"line":441},[286,2488,2418],{"class":306},[286,2490,2491],{"class":302},"onabort",[286,2493,2473],{"class":292},[286,2495,950],{"class":306},[286,2497,953],{"class":292},[286,2499,521],{"class":306},[286,2501,2502],{"class":921},"\u002F* xhr.abort() was called *\u002F",[286,2504,1694],{"class":306},[286,2506,2507,2509,2512,2515,2517,2519,2521,2524],{"class":288,"line":543},[286,2508,2418],{"class":306},[286,2510,2511],{"class":302},"ontimeout",[286,2513,2514],{"class":292},"         =",[286,2516,950],{"class":306},[286,2518,953],{"class":292},[286,2520,521],{"class":306},[286,2522,2523],{"class":921},"\u002F* xhr.timeout exceeded *\u002F",[286,2525,1694],{"class":306},[10,2527,2528],{},"ثمَّة أمران يسهل أن يفوتك الانتباه إليهما.",[10,2530,2531,2532,2535,2536,2539,2540,2542,2543,2546,2547,2549],{},"الأوَّل أن ",[14,2533,2534],{},"xhr.upload"," كائن مستقل من نوع ",[14,2537,2538],{},"XMLHttpRequestUpload","، وله هدف أحداث خاص به. هو الذي يُطلق ",[14,2541,48],{}," أثناء إرسال جسم الطلب، بينما ",[14,2544,2545],{},"xhr"," نفسه يُطلق ",[14,2548,48],{}," أثناء استقبال جسم الاستجابة. إنهما مرحلتان في الطلب نفسه، ويشتركان في شكل الحدث ذاته.",[10,2551,2552,2553,2556,2557,2560,2561,60],{},"الثاني أن ",[14,2554,2555],{},"xhr.onload"," يُطلَق مع أي طلب مكتمل، حتى لو أرجع HTTP 500. معنى \"اكتمل الطلب\" هنا هو \"ردَّ الخادم\"، لا أكثر. فإن أردتَ التحقُّق من أن \"الطلب نجح\" فعلاً، فافحص ",[14,2558,2559],{},"xhr.status"," بنفسك داخل ",[14,2562,2449],{},[10,2564,2565,2566,25,2569,25,2572,2575],{},"هناك أحداث أخرى (",[14,2567,2568],{},"loadstart",[14,2570,2571],{},"loadend",[14,2573,2574],{},"readystatechange",")، لكن الأحداث الخمسة أعلاه تكفي واجهة التقدُّم تماماً.",[93,2577,2579,2580,2583,2584],{"id":2578},"شكل-progressevent-وحكاية-lengthcomputable","شكل ",[14,2581,2582],{},"ProgressEvent","، وحكاية ",[14,2585,2586],{},"lengthComputable",[10,2588,2589,2590,2592,2593,2595],{},"يستقبل كلا الـ callback في ",[14,2591,2390],{}," كائن ",[14,2594,2582],{}," يحوي ثلاثة حقول:",[277,2597,2599],{"className":279,"code":2598,"language":281,"meta":282,"style":282},"interface ProgressEvent {\n  lengthComputable: boolean;\n  loaded: number;   \u002F\u002F bytes transferred so far\n  total: number;    \u002F\u002F bytes expected, ONLY valid when lengthComputable is true\n}\n",[14,2600,2601,2610,2622,2638,2653],{"__ignoreMap":282},[286,2602,2603,2606,2608],{"class":288,"line":289},[286,2604,2605],{"class":292},"interface",[286,2607,2402],{"class":302},[286,2609,475],{"class":306},[286,2611,2612,2615,2617,2620],{"class":288,"line":356},[286,2613,2614],{"class":316},"  lengthComputable",[286,2616,320],{"class":292},[286,2618,2619],{"class":323}," boolean",[286,2621,487],{"class":306},[286,2623,2624,2627,2629,2632,2635],{"class":288,"line":376},[286,2625,2626],{"class":316},"  loaded",[286,2628,320],{"class":292},[286,2630,2631],{"class":323}," number",[286,2633,2634],{"class":306},";   ",[286,2636,2637],{"class":921},"\u002F\u002F bytes transferred so far\n",[286,2639,2640,2643,2645,2647,2650],{"class":288,"line":415},[286,2641,2642],{"class":316},"  total",[286,2644,320],{"class":292},[286,2646,2631],{"class":323},[286,2648,2649],{"class":306},";    ",[286,2651,2652],{"class":921},"\u002F\u002F bytes expected, ONLY valid when lengthComputable is true\n",[286,2654,2655],{"class":288,"line":441},[286,2656,444],{"class":306},[10,2658,2659,2661,2662,2665,2666,2669,2670,2672,2673,2676,2677,2680,2681,2684,2685,183,2688,60],{},[14,2660,2586],{}," هو الحقل الذي يتجاهله أكثر من ينفِّذ الأمر لأول مرَّة. تصبح قيمته ",[14,2663,2664],{},"false"," حين يُرسل الخادم استجابة مُقطَّعة دون ترويسة ",[14,2667,2668],{},"Content-Length","، وتصبح ",[14,2671,2664],{}," كذلك أثناء عمليات الرفع التي يكون فيها الجسم stream مجهول الطول. وفي هذه الحالة تكون ",[14,2674,2675],{},"total"," تساوي ",[14,2678,2679],{},"0"," بالضبط، لا \"غير موجودة\" ولا ",[14,2682,2683],{},"undefined","، بل الرقم صفر، وحين تقسم عليه في شريط التقدُّم يخرج لك ",[14,2686,2687],{},"NaN",[14,2689,2690],{},"Infinity",[10,2692,2693],{},"لذلك ضع التطبيع في مكان واحد:",[277,2695,2697],{"className":279,"code":2696,"language":281,"meta":282,"style":282},"function normaliseProgress(phase: 'upload' | 'download', e: ProgressEvent) {\n  const total = e.lengthComputable ? e.total : null;\n  const ratio = total && total > 0 ? e.loaded \u002F total : null;\n  return { phase, loaded: e.loaded, total, ratio };\n}\n",[14,2698,2699,2731,2754,2793,2800],{"__ignoreMap":282},[286,2700,2701,2703,2706,2708,2711,2713,2716,2718,2721,2723,2725,2727,2729],{"class":288,"line":289},[286,2702,820],{"class":292},[286,2704,2705],{"class":302}," normaliseProgress",[286,2707,467],{"class":306},[286,2709,2710],{"class":316},"phase",[286,2712,320],{"class":292},[286,2714,2715],{"class":899}," 'upload'",[286,2717,852],{"class":292},[286,2719,2720],{"class":899}," 'download'",[286,2722,327],{"class":306},[286,2724,2397],{"class":316},[286,2726,320],{"class":292},[286,2728,2402],{"class":302},[286,2730,1898],{"class":306},[286,2732,2733,2735,2737,2739,2742,2745,2748,2750,2752],{"class":288,"line":356},[286,2734,359],{"class":292},[286,2736,2206],{"class":323},[286,2738,338],{"class":292},[286,2740,2741],{"class":306}," e.lengthComputable ",[286,2743,2744],{"class":292},"?",[286,2746,2747],{"class":306}," e.total ",[286,2749,320],{"class":292},[286,2751,2231],{"class":323},[286,2753,487],{"class":306},[286,2755,2756,2758,2761,2763,2766,2769,2771,2774,2776,2779,2782,2785,2787,2789,2791],{"class":288,"line":376},[286,2757,359],{"class":292},[286,2759,2760],{"class":323}," ratio",[286,2762,338],{"class":292},[286,2764,2765],{"class":306}," total ",[286,2767,2768],{"class":292},"&&",[286,2770,2765],{"class":306},[286,2772,2773],{"class":292},">",[286,2775,1883],{"class":323},[286,2777,2778],{"class":292}," ?",[286,2780,2781],{"class":306}," e.loaded ",[286,2783,2784],{"class":292},"\u002F",[286,2786,2765],{"class":306},[286,2788,320],{"class":292},[286,2790,2231],{"class":323},[286,2792,487],{"class":306},[286,2794,2795,2797],{"class":288,"line":415},[286,2796,418],{"class":292},[286,2798,2799],{"class":306}," { phase, loaded: e.loaded, total, ratio };\n",[286,2801,2802],{"class":288,"line":441},[286,2803,444],{"class":306},[10,2805,2806,2809,2810,2812],{},[14,2807,2808],{},"null"," هو الجواب الأمين حين لا تعرف المجموع، وطبقة الواجهة هي التي تقرِّر ما تعرضه: عدَّاد بايتات بلا نسبة مئوية، أو شريط غير محدَّد، أو مؤشِّر دوران. المهم ألا تسمح بأن يتسرَّب ",[14,2811,2687],{}," من طبقة النقل.",[93,2814,2816],{"id":2815},"أبسط-مثال-لتقدُّم-الرفع","أبسط مثال لتقدُّم الرفع",[10,2818,2819],{},"هذا أصغر مثال مفيد لرفع مع تقدُّم، يُظهر واجهة الاستخدام دون أي غلاف:",[277,2821,2823],{"className":279,"code":2822,"language":281,"meta":282,"style":282},"async function uploadWithProgress(\n  file: File,\n  onProgress: (loaded: number, total: number | null) => void\n): Promise\u003Cvoid> {\n  await new Promise\u003Cvoid>((resolve, reject) => {\n    const xhr = new XMLHttpRequest();\n    xhr.open('POST', '\u002Fupload', true);\n\n    xhr.upload.onprogress = (e) => {\n      const total = e.lengthComputable ? e.total : null;\n      onProgress(e.loaded, total);\n    };\n\n    xhr.onload = () => {\n      if (xhr.status >= 200 && xhr.status \u003C 300) resolve();\n      else reject(new Error(`HTTP ${xhr.status}`));\n    };\n    xhr.onerror = () => reject(new TypeError('Network error'));\n    xhr.onabort = () => reject(new DOMException('Aborted', 'AbortError'));\n\n    const form = new FormData();\n    form.append('file', file);\n    xhr.send(form);\n  });\n}\n",[14,2824,2825,2837,2849,2883,2897,2926,2942,2966,2970,2989,3010,3018,3022,3026,3040,3069,3101,3105,3133,3166,3170,3186,3202,3212,3217],{"__ignoreMap":282},[286,2826,2827,2829,2831,2834],{"class":288,"line":289},[286,2828,1718],{"class":292},[286,2830,299],{"class":292},[286,2832,2833],{"class":302}," uploadWithProgress",[286,2835,2836],{"class":306},"(\n",[286,2838,2839,2842,2844,2847],{"class":288,"line":356},[286,2840,2841],{"class":316},"  file",[286,2843,320],{"class":292},[286,2845,2846],{"class":302}," File",[286,2848,2047],{"class":306},[286,2850,2851,2853,2855,2857,2860,2862,2864,2866,2868,2870,2872,2874,2876,2878,2880],{"class":288,"line":376},[286,2852,2334],{"class":302},[286,2854,320],{"class":292},[286,2856,382],{"class":306},[286,2858,2859],{"class":316},"loaded",[286,2861,320],{"class":292},[286,2863,2631],{"class":323},[286,2865,327],{"class":306},[286,2867,2675],{"class":316},[286,2869,320],{"class":292},[286,2871,2631],{"class":323},[286,2873,852],{"class":292},[286,2875,2231],{"class":323},[286,2877,1115],{"class":306},[286,2879,953],{"class":292},[286,2881,2882],{"class":323}," void\n",[286,2884,2885,2887,2889,2891,2893,2895],{"class":288,"line":415},[286,2886,845],{"class":306},[286,2888,320],{"class":292},[286,2890,346],{"class":302},[286,2892,307],{"class":306},[286,2894,1091],{"class":323},[286,2896,353],{"class":306},[286,2898,2899,2901,2903,2905,2907,2909,2912,2915,2917,2920,2922,2924],{"class":288,"line":441},[286,2900,1753],{"class":292},[286,2902,394],{"class":292},[286,2904,346],{"class":323},[286,2906,307],{"class":306},[286,2908,1091],{"class":323},[286,2910,2911],{"class":306},">((",[286,2913,2914],{"class":316},"resolve",[286,2916,327],{"class":306},[286,2918,2919],{"class":316},"reject",[286,2921,1115],{"class":306},[286,2923,953],{"class":292},[286,2925,475],{"class":306},[286,2927,2928,2930,2933,2935,2937,2940],{"class":288,"line":543},[286,2929,598],{"class":292},[286,2931,2932],{"class":323}," xhr",[286,2934,338],{"class":292},[286,2936,394],{"class":292},[286,2938,2939],{"class":302}," XMLHttpRequest",[286,2941,617],{"class":306},[286,2943,2944,2947,2950,2952,2955,2957,2960,2962,2964],{"class":288,"line":550},[286,2945,2946],{"class":306},"    xhr.",[286,2948,2949],{"class":302},"open",[286,2951,467],{"class":306},[286,2953,2954],{"class":899},"'POST'",[286,2956,327],{"class":306},[286,2958,2959],{"class":899},"'\u002Fupload'",[286,2961,327],{"class":306},[286,2963,980],{"class":323},[286,2965,792],{"class":306},[286,2967,2968],{"class":288,"line":595},[286,2969,547],{"emptyLinePlaceholder":546},[286,2971,2972,2975,2977,2979,2981,2983,2985,2987],{"class":288,"line":620},[286,2973,2974],{"class":306},"    xhr.upload.",[286,2976,2390],{"class":302},[286,2978,338],{"class":292},[286,2980,382],{"class":306},[286,2982,2397],{"class":316},[286,2984,1115],{"class":306},[286,2986,953],{"class":292},[286,2988,475],{"class":306},[286,2990,2991,2994,2996,2998,3000,3002,3004,3006,3008],{"class":288,"line":658},[286,2992,2993],{"class":292},"      const",[286,2995,2206],{"class":323},[286,2997,338],{"class":292},[286,2999,2741],{"class":306},[286,3001,2744],{"class":292},[286,3003,2747],{"class":306},[286,3005,320],{"class":292},[286,3007,2231],{"class":323},[286,3009,487],{"class":306},[286,3011,3012,3015],{"class":288,"line":679},[286,3013,3014],{"class":302},"      onProgress",[286,3016,3017],{"class":306},"(e.loaded, total);\n",[286,3019,3020],{"class":288,"line":707},[286,3021,1331],{"class":306},[286,3023,3024],{"class":288,"line":729},[286,3025,547],{"emptyLinePlaceholder":546},[286,3027,3028,3030,3032,3034,3036,3038],{"class":288,"line":735},[286,3029,2946],{"class":306},[286,3031,2449],{"class":302},[286,3033,338],{"class":292},[286,3035,950],{"class":306},[286,3037,953],{"class":292},[286,3039,475],{"class":306},[286,3041,3042,3044,3047,3050,3052,3055,3058,3060,3063,3065,3067],{"class":288,"line":1376},[286,3043,1942],{"class":292},[286,3045,3046],{"class":306}," (xhr.status ",[286,3048,3049],{"class":292},">=",[286,3051,1838],{"class":323},[286,3053,3054],{"class":292}," &&",[286,3056,3057],{"class":306}," xhr.status ",[286,3059,307],{"class":292},[286,3061,3062],{"class":323}," 300",[286,3064,1115],{"class":306},[286,3066,2914],{"class":302},[286,3068,617],{"class":306},[286,3070,3071,3074,3077,3079,3081,3084,3086,3089,3091,3093,3096,3098],{"class":288,"line":1401},[286,3072,3073],{"class":292},"      else",[286,3075,3076],{"class":302}," reject",[286,3078,467],{"class":306},[286,3080,1276],{"class":292},[286,3082,3083],{"class":302}," Error",[286,3085,467],{"class":306},[286,3087,3088],{"class":899},"`HTTP ${",[286,3090,2545],{"class":306},[286,3092,60],{"class":899},[286,3094,3095],{"class":306},"status",[286,3097,1691],{"class":899},[286,3099,3100],{"class":306},"));\n",[286,3102,3103],{"class":288,"line":1428},[286,3104,1331],{"class":306},[286,3106,3107,3109,3111,3113,3115,3117,3119,3121,3123,3126,3128,3131],{"class":288,"line":1449},[286,3108,2946],{"class":306},[286,3110,2470],{"class":302},[286,3112,338],{"class":292},[286,3114,950],{"class":306},[286,3116,953],{"class":292},[286,3118,3076],{"class":302},[286,3120,467],{"class":306},[286,3122,1276],{"class":292},[286,3124,3125],{"class":302}," TypeError",[286,3127,467],{"class":306},[286,3129,3130],{"class":899},"'Network error'",[286,3132,3100],{"class":306},[286,3134,3135,3137,3139,3141,3143,3145,3147,3149,3151,3154,3156,3159,3161,3164],{"class":288,"line":1455},[286,3136,2946],{"class":306},[286,3138,2491],{"class":302},[286,3140,338],{"class":292},[286,3142,950],{"class":306},[286,3144,953],{"class":292},[286,3146,3076],{"class":302},[286,3148,467],{"class":306},[286,3150,1276],{"class":292},[286,3152,3153],{"class":302}," DOMException",[286,3155,467],{"class":306},[286,3157,3158],{"class":899},"'Aborted'",[286,3160,327],{"class":306},[286,3162,3163],{"class":899},"'AbortError'",[286,3165,3100],{"class":306},[286,3167,3168],{"class":288,"line":1460},[286,3169,547],{"emptyLinePlaceholder":546},[286,3171,3172,3174,3177,3179,3181,3184],{"class":288,"line":1486},[286,3173,598],{"class":292},[286,3175,3176],{"class":323}," form",[286,3178,338],{"class":292},[286,3180,394],{"class":292},[286,3182,3183],{"class":302}," FormData",[286,3185,617],{"class":306},[286,3187,3188,3191,3194,3196,3199],{"class":288,"line":1498},[286,3189,3190],{"class":306},"    form.",[286,3192,3193],{"class":302},"append",[286,3195,467],{"class":306},[286,3197,3198],{"class":899},"'file'",[286,3200,3201],{"class":306},", file);\n",[286,3203,3204,3206,3209],{"class":288,"line":1529},[286,3205,2946],{"class":306},[286,3207,3208],{"class":302},"send",[286,3210,3211],{"class":306},"(form);\n",[286,3213,3214],{"class":288,"line":1534},[286,3215,3216],{"class":306},"  });\n",[286,3218,3219],{"class":288,"line":1558},[286,3220,444],{"class":306},[10,3222,3223],{},"ثلاث ملاحظات تستحق التوقُّف عندها:",[3225,3226,3227,3244,3255],"ul",{},[68,3228,3229,3239,3240,3243],{},[106,3230,3231,3232,3235,3236,60],{},"لا تُعيِّن ",[14,3233,3234],{},"Content-Type"," عند إرسال ",[14,3237,3238],{},"FormData"," المتصفح يحتاج إلى أن يختار سلسلة boundary الخاصة به لترميز multipart. لو ضبطتَ ",[14,3241,3242],{},"Content-Type: multipart\u002Fform-data"," يدوياً، فستُلغي القيمة التي يضعها المتصفح ومعها الـ boundary، وسيفشل الخادم في تحليل الجسم.",[68,3245,3246,134,3252,3254],{},[106,3247,3248,3249,3251],{},"فحص ",[14,3250,2559],{}," يجب أن يكون صريحاً.",[14,3253,2449],{}," يُطلَق حتى مع 500. الخلط بين \"اكتمل الطلب\" و\"نجح الطلب\" هو المصدر الأوَّل والأكثر شيوعاً لعلل الرفع الصامتة.",[68,3256,3257,3263,3264,3267],{},[106,3258,3259,3262],{},[14,3260,3261],{},"new Promise"," هو المحوِّل."," XHR مبني على الأحداث، وبقية شيفرتك بشكل ",[14,3265,3266],{},"async\u002Fawait",". تغليفه مرَّة واحدة داخل Promise يُبقي هذا الحرج محصوراً في مكانه.",[93,3269,3271],{"id":3270},"أبسط-مثال-لتقدُّم-التنزيل","أبسط مثال لتقدُّم التنزيل",[10,3273,3274,3275,320],{},"الشكل نفسه بالنسبة إلى التنزيل، لكن على ",[14,3276,3277],{},"xhr.onprogress",[277,3279,3281],{"className":279,"code":3280,"language":281,"meta":282,"style":282},"async function downloadWithProgress(\n  url: string,\n  onProgress: (loaded: number, total: number | null) => void\n): Promise\u003CBlob> {\n  return new Promise\u003CBlob>((resolve, reject) => {\n    const xhr = new XMLHttpRequest();\n    xhr.open('GET', url, true);\n    xhr.responseType = 'blob';\n\n    xhr.onprogress = (e) => {\n      const total = e.lengthComputable ? e.total : null;\n      onProgress(e.loaded, total);\n    };\n\n    xhr.onload = () => {\n      if (xhr.status >= 200 && xhr.status \u003C 300) {\n        resolve(xhr.response as Blob);\n      } else {\n        reject(new Error(`HTTP ${xhr.status}`));\n      }\n    };\n    xhr.onerror = () => reject(new TypeError('Network error'));\n\n    xhr.send();\n  });\n}\n",[14,3282,3283,3294,3305,3337,3352,3378,3392,3410,3422,3426,3444,3464,3470,3474,3478,3492,3512,3527,3537,3562,3567,3571,3597,3601,3609,3613],{"__ignoreMap":282},[286,3284,3285,3287,3289,3292],{"class":288,"line":289},[286,3286,1718],{"class":292},[286,3288,299],{"class":292},[286,3290,3291],{"class":302}," downloadWithProgress",[286,3293,2836],{"class":306},[286,3295,3296,3299,3301,3303],{"class":288,"line":356},[286,3297,3298],{"class":316},"  url",[286,3300,320],{"class":292},[286,3302,324],{"class":323},[286,3304,2047],{"class":306},[286,3306,3307,3309,3311,3313,3315,3317,3319,3321,3323,3325,3327,3329,3331,3333,3335],{"class":288,"line":376},[286,3308,2334],{"class":302},[286,3310,320],{"class":292},[286,3312,382],{"class":306},[286,3314,2859],{"class":316},[286,3316,320],{"class":292},[286,3318,2631],{"class":323},[286,3320,327],{"class":306},[286,3322,2675],{"class":316},[286,3324,320],{"class":292},[286,3326,2631],{"class":323},[286,3328,852],{"class":292},[286,3330,2231],{"class":323},[286,3332,1115],{"class":306},[286,3334,953],{"class":292},[286,3336,2882],{"class":323},[286,3338,3339,3341,3343,3345,3347,3350],{"class":288,"line":415},[286,3340,845],{"class":306},[286,3342,320],{"class":292},[286,3344,346],{"class":302},[286,3346,307],{"class":306},[286,3348,3349],{"class":302},"Blob",[286,3351,353],{"class":306},[286,3353,3354,3356,3358,3360,3362,3364,3366,3368,3370,3372,3374,3376],{"class":288,"line":441},[286,3355,418],{"class":292},[286,3357,394],{"class":292},[286,3359,346],{"class":323},[286,3361,307],{"class":306},[286,3363,3349],{"class":302},[286,3365,2911],{"class":306},[286,3367,2914],{"class":316},[286,3369,327],{"class":306},[286,3371,2919],{"class":316},[286,3373,1115],{"class":306},[286,3375,953],{"class":292},[286,3377,475],{"class":306},[286,3379,3380,3382,3384,3386,3388,3390],{"class":288,"line":543},[286,3381,598],{"class":292},[286,3383,2932],{"class":323},[286,3385,338],{"class":292},[286,3387,394],{"class":292},[286,3389,2939],{"class":302},[286,3391,617],{"class":306},[286,3393,3394,3396,3398,3400,3403,3406,3408],{"class":288,"line":550},[286,3395,2946],{"class":306},[286,3397,2949],{"class":302},[286,3399,467],{"class":306},[286,3401,3402],{"class":899},"'GET'",[286,3404,3405],{"class":306},", url, ",[286,3407,980],{"class":323},[286,3409,792],{"class":306},[286,3411,3412,3415,3417,3420],{"class":288,"line":595},[286,3413,3414],{"class":306},"    xhr.responseType ",[286,3416,537],{"class":292},[286,3418,3419],{"class":899}," 'blob'",[286,3421,487],{"class":306},[286,3423,3424],{"class":288,"line":620},[286,3425,547],{"emptyLinePlaceholder":546},[286,3427,3428,3430,3432,3434,3436,3438,3440,3442],{"class":288,"line":658},[286,3429,2946],{"class":306},[286,3431,2390],{"class":302},[286,3433,338],{"class":292},[286,3435,382],{"class":306},[286,3437,2397],{"class":316},[286,3439,1115],{"class":306},[286,3441,953],{"class":292},[286,3443,475],{"class":306},[286,3445,3446,3448,3450,3452,3454,3456,3458,3460,3462],{"class":288,"line":679},[286,3447,2993],{"class":292},[286,3449,2206],{"class":323},[286,3451,338],{"class":292},[286,3453,2741],{"class":306},[286,3455,2744],{"class":292},[286,3457,2747],{"class":306},[286,3459,320],{"class":292},[286,3461,2231],{"class":323},[286,3463,487],{"class":306},[286,3465,3466,3468],{"class":288,"line":707},[286,3467,3014],{"class":302},[286,3469,3017],{"class":306},[286,3471,3472],{"class":288,"line":729},[286,3473,1331],{"class":306},[286,3475,3476],{"class":288,"line":735},[286,3477,547],{"emptyLinePlaceholder":546},[286,3479,3480,3482,3484,3486,3488,3490],{"class":288,"line":1376},[286,3481,2946],{"class":306},[286,3483,2449],{"class":302},[286,3485,338],{"class":292},[286,3487,950],{"class":306},[286,3489,953],{"class":292},[286,3491,475],{"class":306},[286,3493,3494,3496,3498,3500,3502,3504,3506,3508,3510],{"class":288,"line":1401},[286,3495,1942],{"class":292},[286,3497,3046],{"class":306},[286,3499,3049],{"class":292},[286,3501,1838],{"class":323},[286,3503,3054],{"class":292},[286,3505,3057],{"class":306},[286,3507,307],{"class":292},[286,3509,3062],{"class":323},[286,3511,1898],{"class":306},[286,3513,3514,3517,3520,3522,3525],{"class":288,"line":1428},[286,3515,3516],{"class":302},"        resolve",[286,3518,3519],{"class":306},"(xhr.response ",[286,3521,429],{"class":292},[286,3523,3524],{"class":302}," Blob",[286,3526,792],{"class":306},[286,3528,3529,3532,3535],{"class":288,"line":1449},[286,3530,3531],{"class":306},"      } ",[286,3533,3534],{"class":292},"else",[286,3536,475],{"class":306},[286,3538,3539,3542,3544,3546,3548,3550,3552,3554,3556,3558,3560],{"class":288,"line":1455},[286,3540,3541],{"class":302},"        reject",[286,3543,467],{"class":306},[286,3545,1276],{"class":292},[286,3547,3083],{"class":302},[286,3549,467],{"class":306},[286,3551,3088],{"class":899},[286,3553,2545],{"class":306},[286,3555,60],{"class":899},[286,3557,3095],{"class":306},[286,3559,1691],{"class":899},[286,3561,3100],{"class":306},[286,3563,3564],{"class":288,"line":1460},[286,3565,3566],{"class":306},"      }\n",[286,3568,3569],{"class":288,"line":1486},[286,3570,1331],{"class":306},[286,3572,3573,3575,3577,3579,3581,3583,3585,3587,3589,3591,3593,3595],{"class":288,"line":1498},[286,3574,2946],{"class":306},[286,3576,2470],{"class":302},[286,3578,338],{"class":292},[286,3580,950],{"class":306},[286,3582,953],{"class":292},[286,3584,3076],{"class":302},[286,3586,467],{"class":306},[286,3588,1276],{"class":292},[286,3590,3125],{"class":302},[286,3592,467],{"class":306},[286,3594,3130],{"class":899},[286,3596,3100],{"class":306},[286,3598,3599],{"class":288,"line":1529},[286,3600,547],{"emptyLinePlaceholder":546},[286,3602,3603,3605,3607],{"class":288,"line":1534},[286,3604,2946],{"class":306},[286,3606,3208],{"class":302},[286,3608,617],{"class":306},[286,3610,3611],{"class":288,"line":1558},[286,3612,3216],{"class":306},[286,3614,3615],{"class":288,"line":1568},[286,3616,444],{"class":306},[10,3618,3619,3622,3623,3626,3627,183,3630,3633,3634,134,3638,60],{},[14,3620,3621],{},"xhr.responseType = 'blob'"," هو السطر الحاسم عند تنزيل الملفات. بدونه ستحصل على نص، وفي المحتوى الثنائي يعني ذلك أن المتصفح سيفكُّ ترميز البايتات بـ UTF-8 ويسلِّمك محتوى فاسداً. مع JSON تستخدم ",[14,3624,3625],{},"'json'","، ومع البايتات العامة ",[14,3628,3629],{},"'arraybuffer'",[14,3631,3632],{},"'blob'",". المهم أن تضبطه ",[3635,3636,3637],"em",{},"قبل",[14,3639,3640],{},"send()",[10,3642,3643,3644,3646,3647,3649,3650,3652,3653,3655,3656,3658],{},"كما أن على الخادم أن يُرسل ترويسة ",[14,3645,2668],{}," إن كنت تريد أن تكون قيمة ",[14,3648,2675],{}," ذات معنى. ترميز النقل المُقطَّع لا يتضمَّنها، فتخرج ",[14,3651,2586],{}," بقيمة ",[14,3654,2664],{},". وشبكات CDN التي تُطبِّق gzip أثناء التسليم تُسقط أحياناً ",[14,3657,2668],{}," بدورها. حين يحدث هذا، سترضى بعدَّاد بايتات بلا نسبة مئوية، وهذا الحل الأمين على أي حال.",[93,3660,3662,3663],{"id":3661},"تغليف-xhr-ليبدو-مثل-fetch","تغليف XHR ليبدو مثل ",[14,3664,16],{},[10,3666,3667,3668,3670,3671,3673],{},"معظم قاعدة الشيفرة الحديثة مبنية على شكل ",[14,3669,16],{},". وإذا نفَّذتَ التقدُّم الحقيقي عبر XHR وتركتَ كل شيء آخر على ",[14,3672,16],{},"، فسينتهي بك الأمر إلى خطَّي طلب متوازيين بأنواع أخطاء مختلفة، ودلالات إلغاء مختلفة، وتطبيع headers مختلف.",[10,3675,3676,3677,3679,3680,3685],{},"والحل أن تُبقي ",[14,3678,16],{}," هو الخيار الافتراضي، وأن تُبدِّله بدالة مبنية على XHR ",[3635,3681,3682,3683],{},"تُعيد ",[14,3684,28],{}," في الحالات التي يُطلَب فيها التقدُّم فقط. بهذا لا تتغيَّر الشيفرة المستدعية إطلاقاً.",[10,3687,3688],{},"هذا غلاف يفعل ذلك تماماً (وهو التطبيق الفعلي المستخدم في حزمة api-provider خاصتي):",[277,3690,3692],{"className":279,"code":3691,"language":281,"meta":282,"style":282},"export function createXhrFetch(\n  onProgress: (progress: RequestProgress) => void\n): (input: string, init: RequestInit) => Promise\u003CResponse> {\n  return function xhrFetch(input, init) {\n    const method = (init.method ?? 'GET').toUpperCase();\n    const body = (init.body ?? null) as XMLHttpRequestBodyInit | null;\n    const headers = init.headers as Record\u003Cstring, string> | undefined;\n    const signal = init.signal as AbortSignal | null | undefined;\n\n    return new Promise\u003CResponse>((resolve, reject) => {\n      if (typeof XMLHttpRequest === 'undefined') {\n        reject(new Error('XHR unavailable in this runtime'));\n        return;\n      }\n\n      const xhr = new XMLHttpRequest();\n      xhr.open(method, input, true);\n      xhr.responseType = 'blob';\n      if (init.credentials === 'include') xhr.withCredentials = true;\n\n      if (headers) {\n        for (const [name, value] of Object.entries(headers)) {\n          try { xhr.setRequestHeader(name, value); }\n          catch { \u002F* forbidden headers are silently skipped *\u002F }\n        }\n      }\n\n      xhr.upload.onprogress = (e) => onProgress(normaliseProgress('upload', e));\n      xhr.onprogress        = (e) => onProgress(normaliseProgress('download', e));\n\n      const onAbort = () => xhr.abort();\n      if (signal) {\n        if (signal.aborted) {\n          reject(new DOMException('Aborted', 'AbortError'));\n          return;\n        }\n        signal.addEventListener('abort', onAbort, { once: true });\n      }\n\n      xhr.onload = () => {\n        if (signal) signal.removeEventListener('abort', onAbort);\n        resolve(new Response(xhr.response as BodyInit | null, {\n          status: xhr.status,\n          statusText: xhr.statusText,\n          headers: parseHeaders(xhr.getAllResponseHeaders()),\n        }));\n      };\n      xhr.onerror   = () => reject(new TypeError('Network error'));\n      xhr.onabort   = () => reject(new DOMException('Aborted', 'AbortError'));\n      xhr.ontimeout = () => reject(new DOMException('Request timeout', 'TimeoutError'));\n\n      xhr.send(body);\n    });\n  };\n}\n",[14,3693,3694,3705,3726,3761,3780,3804,3833,3866,3891,3895,3921,3941,3958,3965,3969,3973,3987,4001,4012,4034,4038,4045,4079,4093,4106,4111,4115,4119,4152,4181,4185,4205,4213,4222,4244,4252,4257,4275,4280,4285,4300,4318,4343,4349,4355,4373,4379,4385,4413,4444,4477,4482,4492,4498,4503],{"__ignoreMap":282},[286,3695,3696,3698,3700,3703],{"class":288,"line":289},[286,3697,293],{"class":292},[286,3699,299],{"class":292},[286,3701,3702],{"class":302}," createXhrFetch",[286,3704,2836],{"class":306},[286,3706,3707,3709,3711,3713,3715,3717,3720,3722,3724],{"class":288,"line":356},[286,3708,2334],{"class":302},[286,3710,320],{"class":292},[286,3712,382],{"class":306},[286,3714,48],{"class":316},[286,3716,320],{"class":292},[286,3718,3719],{"class":302}," RequestProgress",[286,3721,1115],{"class":306},[286,3723,953],{"class":292},[286,3725,2882],{"class":323},[286,3727,3728,3730,3732,3734,3737,3739,3741,3743,3745,3747,3749,3751,3753,3755,3757,3759],{"class":288,"line":376},[286,3729,845],{"class":306},[286,3731,320],{"class":292},[286,3733,382],{"class":306},[286,3735,3736],{"class":316},"input",[286,3738,320],{"class":292},[286,3740,324],{"class":323},[286,3742,327],{"class":306},[286,3744,330],{"class":316},[286,3746,320],{"class":292},[286,3748,335],{"class":302},[286,3750,1115],{"class":306},[286,3752,953],{"class":292},[286,3754,346],{"class":302},[286,3756,307],{"class":306},[286,3758,28],{"class":302},[286,3760,353],{"class":306},[286,3762,3763,3765,3767,3770,3772,3774,3776,3778],{"class":288,"line":415},[286,3764,418],{"class":292},[286,3766,299],{"class":292},[286,3768,3769],{"class":302}," xhrFetch",[286,3771,467],{"class":306},[286,3773,3736],{"class":316},[286,3775,327],{"class":306},[286,3777,330],{"class":316},[286,3779,1898],{"class":306},[286,3781,3782,3784,3787,3789,3792,3794,3797,3799,3802],{"class":288,"line":441},[286,3783,598],{"class":292},[286,3785,3786],{"class":323}," method",[286,3788,338],{"class":292},[286,3790,3791],{"class":306}," (init.method ",[286,3793,1304],{"class":292},[286,3795,3796],{"class":899}," 'GET'",[286,3798,2010],{"class":306},[286,3800,3801],{"class":302},"toUpperCase",[286,3803,617],{"class":306},[286,3805,3806,3808,3811,3813,3816,3818,3820,3822,3824,3827,3829,3831],{"class":288,"line":543},[286,3807,598],{"class":292},[286,3809,3810],{"class":323}," body",[286,3812,338],{"class":292},[286,3814,3815],{"class":306}," (init.body ",[286,3817,1304],{"class":292},[286,3819,2231],{"class":323},[286,3821,1115],{"class":306},[286,3823,429],{"class":292},[286,3825,3826],{"class":302}," XMLHttpRequestBodyInit",[286,3828,852],{"class":292},[286,3830,2231],{"class":323},[286,3832,487],{"class":306},[286,3834,3835,3837,3839,3841,3844,3846,3848,3850,3852,3854,3856,3859,3862,3864],{"class":288,"line":550},[286,3836,598],{"class":292},[286,3838,625],{"class":323},[286,3840,338],{"class":292},[286,3842,3843],{"class":306}," init.headers ",[286,3845,429],{"class":292},[286,3847,498],{"class":302},[286,3849,307],{"class":306},[286,3851,503],{"class":323},[286,3853,327],{"class":306},[286,3855,503],{"class":323},[286,3857,3858],{"class":306},"> ",[286,3860,3861],{"class":292},"|",[286,3863,855],{"class":323},[286,3865,487],{"class":306},[286,3867,3868,3870,3872,3874,3877,3879,3881,3883,3885,3887,3889],{"class":288,"line":595},[286,3869,598],{"class":292},[286,3871,776],{"class":323},[286,3873,338],{"class":292},[286,3875,3876],{"class":306}," init.signal ",[286,3878,429],{"class":292},[286,3880,833],{"class":302},[286,3882,852],{"class":292},[286,3884,2231],{"class":323},[286,3886,852],{"class":292},[286,3888,855],{"class":323},[286,3890,487],{"class":306},[286,3892,3893],{"class":288,"line":620},[286,3894,547],{"emptyLinePlaceholder":546},[286,3896,3897,3899,3901,3903,3905,3907,3909,3911,3913,3915,3917,3919],{"class":288,"line":658},[286,3898,710],{"class":292},[286,3900,394],{"class":292},[286,3902,346],{"class":323},[286,3904,307],{"class":306},[286,3906,28],{"class":302},[286,3908,2911],{"class":306},[286,3910,2914],{"class":316},[286,3912,327],{"class":306},[286,3914,2919],{"class":316},[286,3916,1115],{"class":306},[286,3918,953],{"class":292},[286,3920,475],{"class":306},[286,3922,3923,3925,3927,3930,3933,3936,3939],{"class":288,"line":679},[286,3924,1942],{"class":292},[286,3926,382],{"class":306},[286,3928,3929],{"class":292},"typeof",[286,3931,3932],{"class":306}," XMLHttpRequest ",[286,3934,3935],{"class":292},"===",[286,3937,3938],{"class":899}," 'undefined'",[286,3940,1898],{"class":306},[286,3942,3943,3945,3947,3949,3951,3953,3956],{"class":288,"line":707},[286,3944,3541],{"class":302},[286,3946,467],{"class":306},[286,3948,1276],{"class":292},[286,3950,3083],{"class":302},[286,3952,467],{"class":306},[286,3954,3955],{"class":899},"'XHR unavailable in this runtime'",[286,3957,3100],{"class":306},[286,3959,3960,3963],{"class":288,"line":729},[286,3961,3962],{"class":292},"        return",[286,3964,487],{"class":306},[286,3966,3967],{"class":288,"line":735},[286,3968,3566],{"class":306},[286,3970,3971],{"class":288,"line":1376},[286,3972,547],{"emptyLinePlaceholder":546},[286,3974,3975,3977,3979,3981,3983,3985],{"class":288,"line":1401},[286,3976,2993],{"class":292},[286,3978,2932],{"class":323},[286,3980,338],{"class":292},[286,3982,394],{"class":292},[286,3984,2939],{"class":302},[286,3986,617],{"class":306},[286,3988,3989,3992,3994,3997,3999],{"class":288,"line":1428},[286,3990,3991],{"class":306},"      xhr.",[286,3993,2949],{"class":302},[286,3995,3996],{"class":306},"(method, input, ",[286,3998,980],{"class":323},[286,4000,792],{"class":306},[286,4002,4003,4006,4008,4010],{"class":288,"line":1449},[286,4004,4005],{"class":306},"      xhr.responseType ",[286,4007,537],{"class":292},[286,4009,3419],{"class":899},[286,4011,487],{"class":306},[286,4013,4014,4016,4019,4021,4024,4027,4029,4032],{"class":288,"line":1455},[286,4015,1942],{"class":292},[286,4017,4018],{"class":306}," (init.credentials ",[286,4020,3935],{"class":292},[286,4022,4023],{"class":899}," 'include'",[286,4025,4026],{"class":306},") xhr.withCredentials ",[286,4028,537],{"class":292},[286,4030,4031],{"class":323}," true",[286,4033,487],{"class":306},[286,4035,4036],{"class":288,"line":1460},[286,4037,547],{"emptyLinePlaceholder":546},[286,4039,4040,4042],{"class":288,"line":1486},[286,4041,1942],{"class":292},[286,4043,4044],{"class":306}," (headers) {\n",[286,4046,4047,4050,4052,4054,4057,4060,4062,4064,4067,4070,4073,4076],{"class":288,"line":1498},[286,4048,4049],{"class":292},"        for",[286,4051,382],{"class":306},[286,4053,773],{"class":292},[286,4055,4056],{"class":306}," [",[286,4058,4059],{"class":323},"name",[286,4061,327],{"class":306},[286,4063,1688],{"class":323},[286,4065,4066],{"class":306},"] ",[286,4068,4069],{"class":292},"of",[286,4071,4072],{"class":306}," Object.",[286,4074,4075],{"class":302},"entries",[286,4077,4078],{"class":306},"(headers)) {\n",[286,4080,4081,4084,4087,4090],{"class":288,"line":1529},[286,4082,4083],{"class":292},"          try",[286,4085,4086],{"class":306}," { xhr.",[286,4088,4089],{"class":302},"setRequestHeader",[286,4091,4092],{"class":306},"(name, value); }\n",[286,4094,4095,4098,4100,4103],{"class":288,"line":1534},[286,4096,4097],{"class":292},"          catch",[286,4099,521],{"class":306},[286,4101,4102],{"class":921},"\u002F* forbidden headers are silently skipped *\u002F",[286,4104,4105],{"class":306}," }\n",[286,4107,4108],{"class":288,"line":1558},[286,4109,4110],{"class":306},"        }\n",[286,4112,4113],{"class":288,"line":1568},[286,4114,3566],{"class":306},[286,4116,4117],{"class":288,"line":1595},[286,4118,547],{"emptyLinePlaceholder":546},[286,4120,4121,4124,4126,4128,4130,4132,4134,4136,4139,4141,4144,4146,4149],{"class":288,"line":1600},[286,4122,4123],{"class":306},"      xhr.upload.",[286,4125,2390],{"class":302},[286,4127,338],{"class":292},[286,4129,382],{"class":306},[286,4131,2397],{"class":316},[286,4133,1115],{"class":306},[286,4135,953],{"class":292},[286,4137,4138],{"class":302}," onProgress",[286,4140,467],{"class":306},[286,4142,4143],{"class":302},"normaliseProgress",[286,4145,467],{"class":306},[286,4147,4148],{"class":899},"'upload'",[286,4150,4151],{"class":306},", e));\n",[286,4153,4154,4156,4158,4160,4162,4164,4166,4168,4170,4172,4174,4176,4179],{"class":288,"line":1605},[286,4155,3991],{"class":306},[286,4157,2390],{"class":302},[286,4159,2423],{"class":292},[286,4161,382],{"class":306},[286,4163,2397],{"class":316},[286,4165,1115],{"class":306},[286,4167,953],{"class":292},[286,4169,4138],{"class":302},[286,4171,467],{"class":306},[286,4173,4143],{"class":302},[286,4175,467],{"class":306},[286,4177,4178],{"class":899},"'download'",[286,4180,4151],{"class":306},[286,4182,4183],{"class":288,"line":1613},[286,4184,547],{"emptyLinePlaceholder":546},[286,4186,4188,4190,4192,4194,4196,4198,4201,4203],{"class":288,"line":4187},31,[286,4189,2993],{"class":292},[286,4191,945],{"class":302},[286,4193,338],{"class":292},[286,4195,950],{"class":306},[286,4197,953],{"class":292},[286,4199,4200],{"class":306}," xhr.",[286,4202,959],{"class":302},[286,4204,617],{"class":306},[286,4206,4208,4210],{"class":288,"line":4207},32,[286,4209,1942],{"class":292},[286,4211,4212],{"class":306}," (signal) {\n",[286,4214,4216,4219],{"class":288,"line":4215},33,[286,4217,4218],{"class":292},"        if",[286,4220,4221],{"class":306}," (signal.aborted) {\n",[286,4223,4225,4228,4230,4232,4234,4236,4238,4240,4242],{"class":288,"line":4224},34,[286,4226,4227],{"class":302},"          reject",[286,4229,467],{"class":306},[286,4231,1276],{"class":292},[286,4233,3153],{"class":302},[286,4235,467],{"class":306},[286,4237,3158],{"class":899},[286,4239,327],{"class":306},[286,4241,3163],{"class":899},[286,4243,3100],{"class":306},[286,4245,4247,4250],{"class":288,"line":4246},35,[286,4248,4249],{"class":292},"          return",[286,4251,487],{"class":306},[286,4253,4255],{"class":288,"line":4254},36,[286,4256,4110],{"class":306},[286,4258,4260,4263,4265,4267,4269,4271,4273],{"class":288,"line":4259},37,[286,4261,4262],{"class":306},"        signal.",[286,4264,969],{"class":302},[286,4266,467],{"class":306},[286,4268,974],{"class":899},[286,4270,977],{"class":306},[286,4272,980],{"class":323},[286,4274,983],{"class":306},[286,4276,4278],{"class":288,"line":4277},38,[286,4279,3566],{"class":306},[286,4281,4283],{"class":288,"line":4282},39,[286,4284,547],{"emptyLinePlaceholder":546},[286,4286,4288,4290,4292,4294,4296,4298],{"class":288,"line":4287},40,[286,4289,3991],{"class":306},[286,4291,2449],{"class":302},[286,4293,338],{"class":292},[286,4295,950],{"class":306},[286,4297,953],{"class":292},[286,4299,475],{"class":306},[286,4301,4303,4305,4308,4311,4313,4315],{"class":288,"line":4302},41,[286,4304,4218],{"class":292},[286,4306,4307],{"class":306}," (signal) signal.",[286,4309,4310],{"class":302},"removeEventListener",[286,4312,467],{"class":306},[286,4314,974],{"class":899},[286,4316,4317],{"class":306},", onAbort);\n",[286,4319,4321,4323,4325,4327,4329,4331,4333,4336,4338,4340],{"class":288,"line":4320},42,[286,4322,3516],{"class":302},[286,4324,467],{"class":306},[286,4326,1276],{"class":292},[286,4328,1112],{"class":302},[286,4330,3519],{"class":306},[286,4332,429],{"class":292},[286,4334,4335],{"class":302}," BodyInit",[286,4337,852],{"class":292},[286,4339,2231],{"class":323},[286,4341,4342],{"class":306},", {\n",[286,4344,4346],{"class":288,"line":4345},43,[286,4347,4348],{"class":306},"          status: xhr.status,\n",[286,4350,4352],{"class":288,"line":4351},44,[286,4353,4354],{"class":306},"          statusText: xhr.statusText,\n",[286,4356,4358,4361,4364,4367,4370],{"class":288,"line":4357},45,[286,4359,4360],{"class":306},"          headers: ",[286,4362,4363],{"class":302},"parseHeaders",[286,4365,4366],{"class":306},"(xhr.",[286,4368,4369],{"class":302},"getAllResponseHeaders",[286,4371,4372],{"class":306},"()),\n",[286,4374,4376],{"class":288,"line":4375},46,[286,4377,4378],{"class":306},"        }));\n",[286,4380,4382],{"class":288,"line":4381},47,[286,4383,4384],{"class":306},"      };\n",[286,4386,4388,4390,4392,4395,4397,4399,4401,4403,4405,4407,4409,4411],{"class":288,"line":4387},48,[286,4389,3991],{"class":306},[286,4391,2470],{"class":302},[286,4393,4394],{"class":292},"   =",[286,4396,950],{"class":306},[286,4398,953],{"class":292},[286,4400,3076],{"class":302},[286,4402,467],{"class":306},[286,4404,1276],{"class":292},[286,4406,3125],{"class":302},[286,4408,467],{"class":306},[286,4410,3130],{"class":899},[286,4412,3100],{"class":306},[286,4414,4416,4418,4420,4422,4424,4426,4428,4430,4432,4434,4436,4438,4440,4442],{"class":288,"line":4415},49,[286,4417,3991],{"class":306},[286,4419,2491],{"class":302},[286,4421,4394],{"class":292},[286,4423,950],{"class":306},[286,4425,953],{"class":292},[286,4427,3076],{"class":302},[286,4429,467],{"class":306},[286,4431,1276],{"class":292},[286,4433,3153],{"class":302},[286,4435,467],{"class":306},[286,4437,3158],{"class":899},[286,4439,327],{"class":306},[286,4441,3163],{"class":899},[286,4443,3100],{"class":306},[286,4445,4447,4449,4451,4453,4455,4457,4459,4461,4463,4465,4467,4470,4472,4475],{"class":288,"line":4446},50,[286,4448,3991],{"class":306},[286,4450,2511],{"class":302},[286,4452,338],{"class":292},[286,4454,950],{"class":306},[286,4456,953],{"class":292},[286,4458,3076],{"class":302},[286,4460,467],{"class":306},[286,4462,1276],{"class":292},[286,4464,3153],{"class":302},[286,4466,467],{"class":306},[286,4468,4469],{"class":899},"'Request timeout'",[286,4471,327],{"class":306},[286,4473,4474],{"class":899},"'TimeoutError'",[286,4476,3100],{"class":306},[286,4478,4480],{"class":288,"line":4479},51,[286,4481,547],{"emptyLinePlaceholder":546},[286,4483,4485,4487,4489],{"class":288,"line":4484},52,[286,4486,3991],{"class":306},[286,4488,3208],{"class":302},[286,4490,4491],{"class":306},"(body);\n",[286,4493,4495],{"class":288,"line":4494},53,[286,4496,4497],{"class":306},"    });\n",[286,4499,4501],{"class":288,"line":4500},54,[286,4502,732],{"class":306},[286,4504,4506],{"class":288,"line":4505},55,[286,4507,444],{"class":306},[10,4509,4510],{},"أربعة أمور تقوم بالعمل الحقيقي في هذا الشكل:",[10,4512,4513,4514,4519,4520,4523],{},"أوَّلها أن ",[106,4515,4516,4517],{},"نوع الإرجاع ",[14,4518,116],{},". وهذا هو المقصد كله: كل ما يقبل ",[14,4521,4522],{},"typeof fetch"," سيقبل هذه الدالة، وتبقى بقية قاعدة الشيفرة (interceptors وretry وتحليل JSON وتحويل الأخطاء) لا تُبالي بالناقل المستخدم.",[10,4525,4526,4527,4530,4531,4534,4535,4538,4539,4541,4542,320],{},"وثانيها ",[106,4528,4529],{},"مطبِّع الترويسات في الأسفل","، إذ يُترجم ",[14,4532,4533],{},"xhr.getAllResponseHeaders()"," (سلسلة خام مفصولة بـ CRLF) إلى كائن ",[14,4536,4537],{},"Headers","، بحيث يسلك ",[14,4540,28],{}," سلوكه المعتاد كأنه قادم من ",[14,4543,16],{},[277,4545,4547],{"className":279,"code":4546,"language":281,"meta":282,"style":282},"function parseHeaders(raw: string): Headers {\n  const out = new Headers();\n  if (!raw) return out;\n  for (const line of raw.trim().split(\u002F[\\r\\n]+\u002F)) {\n    const idx = line.indexOf(':');\n    if (idx \u003C 0) continue;\n    const name = line.slice(0, idx).trim();\n    const value = line.slice(idx + 1).trim();\n    if (name) out.append(name, value);\n  }\n  return out;\n}\n",[14,4548,4549,4574,4589,4605,4645,4666,4684,4709,4736,4748,4752,4758],{"__ignoreMap":282},[286,4550,4551,4553,4556,4558,4561,4563,4565,4567,4569,4572],{"class":288,"line":289},[286,4552,820],{"class":292},[286,4554,4555],{"class":302}," parseHeaders",[286,4557,467],{"class":306},[286,4559,4560],{"class":316},"raw",[286,4562,320],{"class":292},[286,4564,324],{"class":323},[286,4566,845],{"class":306},[286,4568,320],{"class":292},[286,4570,4571],{"class":302}," Headers",[286,4573,475],{"class":306},[286,4575,4576,4578,4581,4583,4585,4587],{"class":288,"line":356},[286,4577,359],{"class":292},[286,4579,4580],{"class":323}," out",[286,4582,338],{"class":292},[286,4584,394],{"class":292},[286,4586,4571],{"class":302},[286,4588,617],{"class":306},[286,4590,4591,4593,4595,4597,4600,4602],{"class":288,"line":376},[286,4592,379],{"class":292},[286,4594,382],{"class":306},[286,4596,385],{"class":292},[286,4598,4599],{"class":306},"raw) ",[286,4601,871],{"class":292},[286,4603,4604],{"class":306}," out;\n",[286,4606,4607,4609,4611,4613,4616,4618,4621,4624,4627,4630,4632,4634,4637,4640,4642],{"class":288,"line":415},[286,4608,1870],{"class":292},[286,4610,382],{"class":306},[286,4612,773],{"class":292},[286,4614,4615],{"class":323}," line",[286,4617,1346],{"class":292},[286,4619,4620],{"class":306}," raw.",[286,4622,4623],{"class":302},"trim",[286,4625,4626],{"class":306},"().",[286,4628,4629],{"class":302},"split",[286,4631,467],{"class":306},[286,4633,2784],{"class":899},[286,4635,4636],{"class":323},"[\\r\\n]",[286,4638,4639],{"class":292},"+",[286,4641,2784],{"class":899},[286,4643,4644],{"class":306},")) {\n",[286,4646,4647,4649,4652,4654,4657,4659,4661,4664],{"class":288,"line":441},[286,4648,598],{"class":292},[286,4650,4651],{"class":323}," idx",[286,4653,338],{"class":292},[286,4655,4656],{"class":306}," line.",[286,4658,1518],{"class":302},[286,4660,467],{"class":306},[286,4662,4663],{"class":899},"':'",[286,4665,792],{"class":306},[286,4667,4668,4670,4673,4675,4677,4679,4682],{"class":288,"line":543},[286,4669,682],{"class":292},[286,4671,4672],{"class":306}," (idx ",[286,4674,307],{"class":292},[286,4676,1883],{"class":323},[286,4678,1115],{"class":306},[286,4680,4681],{"class":292},"continue",[286,4683,487],{"class":306},[286,4685,4686,4688,4691,4693,4695,4698,4700,4702,4705,4707],{"class":288,"line":550},[286,4687,598],{"class":292},[286,4689,4690],{"class":323}," name",[286,4692,338],{"class":292},[286,4694,4656],{"class":306},[286,4696,4697],{"class":302},"slice",[286,4699,467],{"class":306},[286,4701,2679],{"class":323},[286,4703,4704],{"class":306},", idx).",[286,4706,4623],{"class":302},[286,4708,617],{"class":306},[286,4710,4711,4713,4716,4718,4720,4722,4725,4727,4730,4732,4734],{"class":288,"line":595},[286,4712,598],{"class":292},[286,4714,4715],{"class":323}," value",[286,4717,338],{"class":292},[286,4719,4656],{"class":306},[286,4721,4697],{"class":302},[286,4723,4724],{"class":306},"(idx ",[286,4726,4639],{"class":292},[286,4728,4729],{"class":323}," 1",[286,4731,2010],{"class":306},[286,4733,4623],{"class":302},[286,4735,617],{"class":306},[286,4737,4738,4740,4743,4745],{"class":288,"line":620},[286,4739,682],{"class":292},[286,4741,4742],{"class":306}," (name) out.",[286,4744,3193],{"class":302},[286,4746,4747],{"class":306},"(name, value);\n",[286,4749,4750],{"class":288,"line":658},[286,4751,1452],{"class":306},[286,4753,4754,4756],{"class":288,"line":679},[286,4755,418],{"class":292},[286,4757,4604],{"class":306},[286,4759,4760],{"class":288,"line":707},[286,4761,444],{"class":306},[10,4763,4764,4765,4770,4771,4773,4774,4777,4778,4781,4782,4785],{},"وثالثها ",[106,4766,4767,4768],{},"جسر ",[14,4769,758],{},"، فهو الذي يترجم بين بدائيّ الإلغاء في ",[14,4772,16],{}," وتابع ",[14,4775,4776],{},"abort()"," في XHR. استدعاء ",[14,4779,4780],{},"AbortSignal.addEventListener('abort', xhr.abort)"," يجعل الواجهتين تتحدَّثان اللغة نفسها، وفحص ",[14,4783,4784],{},"signal.aborted"," منذ البداية يُعالج حالة أن يمرِّر المستدعي إشارة مُلغاة سلفاً (وتحدث مثلاً حين تُلغي طلباً قبل أن يُرسَل).",[10,4787,4788,4789,4796,4797,25,4800,25,4803,4806,4807,25,4810,4813,4814,4816,4817,4819],{},"ورابعها ",[106,4790,4791,4793,4794],{},[14,4792,1022],{}," حول ",[14,4795,4089],{},"، وهو مقصود. المتصفحات تمنع ترويسات معيَّنة مثل ",[14,4798,4799],{},"User-Agent",[14,4801,4802],{},"Cookie",[14,4804,4805],{},"Host"," ومعظم ",[14,4808,4809],{},"Sec-*",[14,4811,4812],{},"Proxy-*",". ",[14,4815,16],{}," يتجاهلها بصمت هو الآخر، و",[14,4818,1022],{}," هنا يجعل سلوك XHR مطابقاً له.",[93,4821,4823],{"id":4822},"الإلغاء-وانقضاء-المهلة-وحدوث-الخطأ","الإلغاء، وانقضاء المهلة، وحدوث الخطأ",[10,4825,4826,4827,25,4829,25,4831,25,4833,4835,4836,4838],{},"نهايات XHR الأربع الحصرية هي ",[14,4828,2449],{},[14,4830,2470],{},[14,4832,2491],{},[14,4834,2511],{},". وهي متنافية تماماً، بمعنى أن حدثاً واحداً بالضبط سيُطلَق لكل طلب. أما الترجمة إلى عالم ",[14,4837,16],{}," فهي:",[4840,4841,4842,4858],"table",{},[4843,4844,4845],"thead",{},[4846,4847,4848,4852,4855],"tr",{},[4849,4850,4851],"th",{},"حدث XHR",[4849,4853,4854],{},"المعنى",[4849,4856,4857],{},"شكل الـ Promise",[4859,4860,4861,4876,4890,4907],"tbody",{},[4846,4862,4863,4868,4871],{},[4864,4865,4866],"td",{},[14,4867,2449],{},[4864,4869,4870],{},"ردَّ الخادم (بأي حالة)",[4864,4872,4873],{},[14,4874,4875],{},"resolve(new Response(...))",[4846,4877,4878,4882,4885],{},[4864,4879,4880],{},[14,4881,2470],{},[4864,4883,4884],{},"فشل شبكي، أو حجب CORS، أو DNS",[4864,4886,4887],{},[14,4888,4889],{},"reject(new TypeError('Network error'))",[4846,4891,4892,4896,4902],{},[4864,4893,4894],{},[14,4895,2491],{},[4864,4897,4898,4899],{},"استُدعي ",[14,4900,4901],{},"xhr.abort()",[4864,4903,4904],{},[14,4905,4906],{},"reject(new DOMException('...', 'AbortError'))",[4846,4908,4909,4913,4920],{},[4864,4910,4911],{},[14,4912,2511],{},[4864,4914,4915,4916,4919],{},"انقضت المدة المحددة في ",[14,4917,4918],{},"xhr.timeout"," بالمللي ثانية",[4864,4921,4922],{},[14,4923,4924],{},"reject(new DOMException('...', 'TimeoutError'))",[10,4926,4927,4929,4930,4933,4934,4937],{},[14,4928,16],{}," يستخدم ",[14,4931,4932],{},"TypeError"," للأخطاء الشبكية و",[14,4935,4936],{},"DOMException"," للإلغاء. الحفاظ على هذه الأنواع بعينها يعني أن معالجات الأخطاء عندك ستستمر في العمل حين تُبدِّل الناقل.",[10,4939,4940,4941,4944,4945,4947,4948,4950,4951,25,4953,4955],{},"وحين يضبط المستدعي ",[14,4942,4943],{},"xhr.timeout = 30_000","، سيُطلَق ",[14,4946,2511],{}," تلقائياً دون أي شيفرة إضافية. وهذه من المكاسب الصغيرة لـ XHR على ",[14,4949,16],{}," مع ",[14,4952,20],{},[14,4954,163],{},": آلية المهلة مدمجة أصلاً.",[93,4957,4959],{"id":4958},"مطبَّات-تستحق-أن-تُسمَّى","مطبَّات تستحق أن تُسمَّى",[10,4961,4962],{},"مجموعة صغيرة من الفخاخ كلَّفتني وقتاً في أول مرَّة وقعتُ فيها.",[10,4964,4965,4971,4972,4975],{},[106,4966,4967,4968,4970],{},"تعيين ",[14,4969,3234],{}," مع FormData يكسر multipart."," ترويسة ",[14,4973,4974],{},"multipart\u002Fform-data; boundary=..."," التي يُنشئها المتصفح تلقائياً هي الوحيدة التي يستطيع الخادم أن يحلِّلها. لا تتجاوزها.",[10,4977,4978,4981,4982,4985,4986,4989],{},[106,4979,4980],{},"تقدُّم CORS مقيَّد."," في الطلبات عبر الأصول، لن تُطلَق أحداث تقدُّم الرفع إلا إذا شملت الاستجابة ترويسة ",[14,4983,4984],{},"Access-Control-Allow-Origin"," المناسبة. فإن كنت ترى ",[14,4987,4988],{},"loaded = 0"," بلا نهاية في رفع عبر أصل مختلف، فالسبب غالباً CORS لا شيفرتك.",[10,4991,4992,5000],{},[106,4993,4994,4997,4998,60],{},[14,4995,4996],{},"responseType"," يجب أن يُضبط قبل ",[14,4999,3640],{}," ضبطه بعد ذلك لا يفعل شيئاً. المتصفح يحتاج إلى معرفة كيفية تخزين الاستجابة منذ أول بايت.",[10,5002,5003,5008,5009,5011,5012,5015],{},[106,5004,5005,5006,60],{},"gzip وترويسة ",[14,5007,2668],{}," كثير من شبكات CDN تُسقط ",[14,5010,2668],{}," حين تُطبِّق gzip أثناء التسليم، لأن الطول المضغوط يختلف عن غير المضغوط. فيرى عميلك ",[14,5013,5014],{},"lengthComputable: false"," رغم أن الخادم يعرف الحجم الحقيقي. لا يوجد حلٌّ من جانب العميل هنا. فإن كان الخادم بيدك، فقدِّم محتوى مضغوطاً مسبقاً، أو تجاوز الضغط في الأصول التي يهمُّ فيها التقدُّم.",[10,5017,5018,134,5021,5024,5025,5028,5029,5032],{},[106,5019,5020],{},"لا تُبقي الاستجابة كلها في الذاكرة دون داعٍ.",[14,5022,5023],{},"responseType: 'blob'"," يترك للمتصفح إدارة العازلة، بينما ",[14,5026,5027],{},"responseType: 'arraybuffer'"," يُجبرها إلى heap الخاصة بـ JS. لأي شيء يتجاوز بضعة MB، ",[14,5030,5031],{},"blob"," هو الافتراضي الصحيح.",[93,5034,5036],{"id":5035},"ما-سألجأ-إليه-أولاً","ما سألجأ إليه أولاً",[10,5038,5039],{},"لأي مشروع يحتاج واجهات تقدُّم حقيقية، هذا هو الشكل الذي سأبنيه في كل مرَّة:",[3225,5041,5042,5050,5060,5068,5081],{},[68,5043,5044,5049],{},[106,5045,5046,5047,60],{},"الناقل الافتراضي هو ",[14,5048,16],{}," فهو البدائي الحديث، وهو الأسرع، ويتحوَّل إلى أنماط ودودة لـ SSR بنظافة.",[68,5051,5052,5055,5056,5059],{},[106,5053,5054],{},"لا تلجأ إلى XHR إلا حين يطلب المستدعي التقدُّم صراحة."," خيار واحد باسم ",[14,5057,5058],{},"onRequestProgress"," كافٍ لتبديل الناقل لهذا الاستدعاء وحده.",[68,5061,5062,5067],{},[106,5063,5064,5065],{},"مسار XHR يُعيد ",[14,5066,116],{},"، حتى يستمر كل ما بعده في العمل بلا تغيير: interceptors وretry وتحويل الأخطاء وتحليل JSON.",[68,5069,5070,5077,5078,5080],{},[106,5071,5072,5073,5076],{},"طبِّع ",[14,5074,5075],{},"{ loaded, total, ratio }"," في طبقة النقل."," ومرِّر ",[14,5079,2808],{}," إلى الأعلى حين يكون المجموع مجهولاً. ودع الواجهة تقرِّر كيف تعرض هذه الحالة.",[68,5082,5083,5089,5090,5092,5093,5095],{},[106,5084,5085,5086,5088],{},"اجسر ",[14,5087,758],{}," مرَّة واحدة"," في الغلاف، لا في كل موقع استدعاء. ",[14,5091,4776],{}," في XHR و",[14,5094,20],{}," في fetch يشغلان الفتحة المنطقية نفسها، والغلاف هو الذي يُترجم بينهما.",[10,5097,5098],{},"هذه هي الصورة كاملة. لم يعد XHR الواجهة اللامعة، لكنه لا يزال يعمل في حالة محدَّدة يعجز عنها fetch، والقدر الصغير من شيفرة الغراء أعلاه هو ما يمنعه من أن يُلوِّث بقية قاعدة الشيفرة.",[5100,5101,5102],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":282,"searchDepth":356,"depth":356,"links":5104},[5105,5107,5115,5117,5118,5120,5121,5122,5124,5125,5126],{"id":95,"depth":356,"text":5106},"لماذا fetch الأصلي هو البدائي الافتراضي",{"id":248,"depth":356,"text":249,"children":5108},[5109,5110,5112,5113,5114],{"id":271,"depth":376,"text":272},{"id":754,"depth":376,"text":5111},"الطبقة الثانية: المهلات عبر AbortSignal",{"id":1030,"depth":376,"text":1031},{"id":1779,"depth":376,"text":1780},{"id":2013,"depth":376,"text":2014},{"id":2165,"depth":356,"text":5116},"لماذا يعجز fetch عن إظهار تقدُّم الرفع",{"id":2373,"depth":356,"text":2374},{"id":2578,"depth":356,"text":5119},"شكل ProgressEvent، وحكاية lengthComputable",{"id":2815,"depth":356,"text":2816},{"id":3270,"depth":356,"text":3271},{"id":3661,"depth":356,"text":5123},"تغليف XHR ليبدو مثل fetch",{"id":4822,"depth":356,"text":4823},{"id":4958,"depth":356,"text":4959},{"id":5035,"depth":356,"text":5036},"2025-09-22","لماذا يبقى fetch الأصلي الخيار الافتراضي الصحيح، وكيف تبني حوله غلافاً متعدّد الطبقات (رابط أساسي، ومهلات، واعتراضات، وإعادة محاولات)، ومتى تظل بحاجة إلى XHR.",null,false,"md",[5133,55,5134,5135,5136,20,758,5137,5138,5139,5140,5141],"fetch API","XHR","تقدُّم الرفع","تقدُّم التنزيل","عميل HTTP مخصَّص","Interceptors","إعادة المحاولة","Exponential Backoff","TypeScript","ar",{},"\u002Fwrites-ar\u002Fupload-and-download-progress-in-the-browser",{"title":5,"description":5128},"writes-ar\u002Fupload-and-download-progress-in-the-browser","Rd7oX4pZes3Jt-NRZKY-28fuNjP69dDsvFWM37kHHgk",[5149,6912],{"id":5150,"title":5151,"body":5152,"date":6896,"description":6897,"description_ar":5129,"draft":5130,"extension":5131,"image":5129,"keywords":6898,"lang":5142,"meta":6907,"navigation":546,"path":6908,"seo":6909,"stem":6910,"title_ar":5129,"updatedAt":5129,"__hash__":6911},"writesAr\u002Fwrites-ar\u002Finside-a-skeleton-self-generating-skeleton-loaders.md","داخل @alikhalilll\u002Fa-skeleton، مُحمِّلات skeleton تولّد نفسها بنفسها",{"type":7,"value":5153,"toc":6885},[5154,5157,5164,5167,5171,5174,5179,5229,5234,5265,5293,5299,5313,5316,5320,5327,5348,5355,5366,5369,5410,5413,5499,5526,5532,5536,5543,5588,5613,5619,5622,5626,5635,6016,6053,6056,6125,6128,6245,6248,6271,6274,6278,6281,6287,6322,6341,6361,6370,6374,6422,6425,6432,6508,6521,6525,6567,6574,6585,6668,6675,6782,6804,6807,6811,6830,6848,6855,6859,6870,6876,6879,6882],[10,5155,5156],{},"مُحمِّلات الـ skeleton في النهاية واجهة مكرّرة. تبني بطاقة، ثم تبني بطاقة أخرى من مستطيلات رمادية تشبهها، وكلما عدّلت البطاقة الحقيقية نسيتَ أن تعدّل الـ placeholder معها. بعد ثلاثة أسابيع تجد العنوان محاذيًا لليسار في المكوّن الفعلي وموسّطًا في الـ skeleton، ولا ينتبه أحد حتى يمرّ مصمّم بالصفحة على بيئة الـ staging.",[10,5158,5159,5160,5163],{},"بنيتُ ",[14,5161,5162],{},"@alikhalilll\u002Fa-skeleton"," على فرضية مختلفة تمامًا: الـ skeleton يُشتقّ من المكوّن الحقيقي نفسه. تلفّ بطاقتك مرة واحدة فقط، وتتكفّل المكتبة باستنتاج الـ placeholder من قراءة البطاقة. لا قالب موازٍ. لا مستطيلات رمادية تلاحقها بالتحديث. أيّ تعديل تجريه على البطاقة يلتقطه الـ skeleton في العرض التالي دون تدخّل منك.",[10,5165,5166],{},"ما أتحدّث عنه هنا هو ما يقع تحت السطح: عقد التأليف الذي يجعل هذا الأمر ممكنًا، واستراتيجيات العرض الثلاث وما تدفعه كلٌّ منها من مقايضات، وخط أنابيب الالتقاط خلف وضع clone تحديدًا. هذا الأخير هو ما سأحمله معي إلى أيّ مشكلة مستقبلية من نوع \"أريد نسخة من شكل هذا الشيء الآن\".",[93,5168,5170],{"id":5169},"العقد-أبقِ-الوسوم-غير-مشروطة-وقيّد-المحتوى-وحده","العقد: أبقِ الوسوم غير مشروطة، وقيّد المحتوى وحده",[10,5172,5173],{},"قبل أيّ شيء، هناك قاعدة واحدة. هي الأساس الذي تستند إليه كل استراتيجية أذكرها لاحقًا، ولذلك أضعها في الصدارة.",[10,5175,5176],{},[106,5177,5178],{},"خطأ:",[277,5180,5184],{"className":5181,"code":5182,"language":5183,"meta":282,"style":282},"language-vue shiki shiki-themes github-light github-dark","\u003Cdiv v-if=\"user\">\n  \u003Ch3>{{ user.name }}\u003C\u002Fh3>\n  \u003Cp>{{ user.bio }}\u003C\u002Fp>\n\u003C\u002Fdiv>\n","vue",[14,5185,5186,5210,5215,5220],{"__ignoreMap":282},[286,5187,5188,5190,5194,5197,5199,5202,5205,5207],{"class":288,"line":289},[286,5189,307],{"class":306},[286,5191,5193],{"class":5192},"s9eBZ","div",[286,5195,5196],{"class":292}," v-if",[286,5198,537],{"class":306},[286,5200,5201],{"class":899},"\"",[286,5203,5204],{"class":306},"user",[286,5206,5201],{"class":899},[286,5208,5209],{"class":306},">\n",[286,5211,5212],{"class":288,"line":356},[286,5213,5214],{"class":306},"  \u003Ch3>{{ user.name }}\u003C\u002Fh3>\n",[286,5216,5217],{"class":288,"line":376},[286,5218,5219],{"class":306},"  \u003Cp>{{ user.bio }}\u003C\u002Fp>\n",[286,5221,5222,5225,5227],{"class":288,"line":415},[286,5223,5224],{"class":306},"\u003C\u002F",[286,5226,5193],{"class":5192},[286,5228,5209],{"class":306},[10,5230,5231],{},[106,5232,5233],{},"صحيح:",[277,5235,5237],{"className":5181,"code":5236,"language":5183,"meta":282,"style":282},"\u003Cdiv>\n  \u003Ch3>{{ user?.name }}\u003C\u002Fh3>\n  \u003Cp>{{ user?.bio }}\u003C\u002Fp>\n\u003C\u002Fdiv>\n",[14,5238,5239,5247,5252,5257],{"__ignoreMap":282},[286,5240,5241,5243,5245],{"class":288,"line":289},[286,5242,307],{"class":306},[286,5244,5193],{"class":5192},[286,5246,5209],{"class":306},[286,5248,5249],{"class":288,"line":356},[286,5250,5251],{"class":306},"  \u003Ch3>{{ user?.name }}\u003C\u002Fh3>\n",[286,5253,5254],{"class":288,"line":376},[286,5255,5256],{"class":306},"  \u003Cp>{{ user?.bio }}\u003C\u002Fp>\n",[286,5258,5259,5261,5263],{"class":288,"line":415},[286,5260,5224],{"class":306},[286,5262,5193],{"class":5192},[286,5264,5209],{"class":306},[10,5266,5267,5268,5271,5272,5275,5276,5278,5279,5282,5283,327,5286,327,5289,5292],{},"الـ walker، سواء كان يقرأ الـ DOM الحيّ أو شجرة الـ vnode، يحتاج أن يرى الشكل ذاته أثناء التحميل وبعد وصول البيانات. لو غلّفتَ العنوان بـ ",[14,5269,5270],{},"v-if=\"user\"","، فسيرى الـ walker عقدة تعليق أثناء التحميل ثم ",[14,5273,5274],{},"\u003Ch3>"," بعد ذلك، ولن يستطيع أن يتنبّأ بهندسة الـ placeholder. لكن مع استخدام الاستيفاء الاختياري (optional chaining) يبقى ",[14,5277,5274],{}," موجودًا دائمًا؛ يكون نصّه فارغًا أثناء التحميل، ويصنّف الـ walker الوسوم الحاملة للنص الفارغة (",[14,5280,5281],{},"\u003Ch1>","–",[14,5284,5285],{},"\u003Ch6>",[14,5287,5288],{},"\u003Cp>",[14,5290,5291],{},"\u003Cspan>",", وغيرها) على أنها أشرطة نص shimmer بارتفاع العنوان الطبيعي.",[10,5294,5295,5296,5298],{},"الوسوم هي المخطط. المحتوى هو الحمولة. تستطيع أن تُبدّل الحمولة بـ ",[14,5297,2808],{}," دون أن يلاحظ المخطط شيئًا. هذا الانضباط البسيط وحده هو ما يفتح الباب لكل ما يأتي بعده.",[10,5300,5301,5302,5305,5306,5308,5309,5312],{},"النصف الآخر من العقد هو مخرج الطوارئ ",[14,5303,5304],{},"data-skeleton-ignore",". لو كان جزء من المكوّن مجرد زخرفة بصرية (خلفية تدرّج، شارة علامة تجارية ثابتة، لمسة SVG) ولا ترغب باستبداله بكتلة رمادية، ضع عليه ",[14,5307,5304],{}," فيتجاهله الـ walker تمامًا؛ ويُعرض العنصر الحقيقي كما هو في الحالتين. أما ",[14,5310,5311],{},"data-skeleton-stop"," فيقول للـ walker: \"لا تدخل داخل هذا العنصر؛ اعرضه ككتلة واحدة\". أستخدمه عادةً مع الـ widgets الذرّية حين تكون البنية الداخلية مجرد ضجيج بصري.",[10,5314,5315],{},"قاعدتان اثنتان وسِمَتان اثنتان. هذا هو كل ما يلزمك تعلّمه من سطح التأليف.",[93,5317,5319],{"id":5318},"وضع-clone-التقاط-الرسم-النهائي-وإعادة-عرض-البكسلات","وضع Clone: التقاط الرسم النهائي وإعادة عرض البكسلات",[10,5321,5322,5323,5326],{},"Clone هو الوضع الافتراضي، وهو أهمّ ما في المكتبة برأيي. أغلب مكتبات الـ skeleton تتعثّر لأنها تحاول التفكير على مستوى الـ CSS cascade. تنظر إلى أسماء الفئات. تنظر إلى الأنماط المضمّنة. تفترض قيمة الـ border radius بناءً على بادئة ",[14,5324,5325],{},"rounded-*",". باختصار، تخمّن.",[10,5328,5329,5330,5333,5334,5337,5338,5341,5342,5344,5345,5347],{},"وضع clone يتخلّى عن التخمين كليًا. يركّب الـ slot خارج الشاشة داخل capture host مضبوط على ",[14,5331,5332],{},"visibility: hidden","، ويترك المتصفح ينهي كامل الـ layout وحلّ الأنماط، ثم يقرأ ",[106,5335,5336],{},"النتيجة النهائية المحسوبة"," لكل عنصر عبر ",[14,5339,5340],{},"getComputedStyle()",". مهما كان نظام التنسيق لديك (أدوات Tailwind، أنماط scoped بأسماء فئات مُجزّأة، CSS-in-JS وقت التشغيل، رموز DaisyUI الدلالية، أو حتى سمات ",[14,5343,5100],{}," مضمّنة يمرّرها مكوّن أب)، فبحلول اللحظة التي يعمل فيها ",[14,5346,5340],{}," تكون كلّها قد انهارت إلى إجابة قانونية واحدة لكل خاصية. يأخذ المحرك تلك الإجابة الجاهزة ويعيد عرضها.",[10,5349,5350,5351,5354],{},"السطح الذي يلتقطه المحرك شامل فعلًا. حدود مستقلة لكل حافة (فبطاقة لها ",[14,5352,5353],{},"border-bottom"," مختلف تُقرأ كما ينبغي). أنصاف أقطار مستقلة لكل زاوية (فشكل pill غير المتماثل لا يتراجع إلى مجرد مستطيل مدوّر). لون الخلفية وصورتها. Box-shadow. Opacity. Filter. Transform. Typography. Mix-blend-mode. كل عقدة ملتقطة تحمل حمولة أنماط مضمّنة مجمّدة خاصة بها؛ حلقة العرض لا تخصّص شيئًا لأيّ عقدة، بل تقرأ ما هو مجهّز مسبقًا.",[10,5356,5357,5358,5361,5362,5365],{},"النص هو أكثر ما يخطئ فيه الآخرون، وأكثر ما تصيبه ",[14,5359,5360],{},"a-skeleton"," بدقّة. بدلًا من تخمين نقاط الالتفاف (\"لنفترض أن النص يلتف كل N حرفًا، ولنفترض أن السطر الأخير عرضه 70%\")، يستخدم المحرك ",[14,5363,5364],{},"Range.getClientRects()"," ليلتقط المستطيل الفعلي المعروض لكل سطر نصّي بدقّة: left, top, width, height. عنوان موسّط من ثلاثة أسطر على سطح المكتب يصبح عنوانًا موسّطًا من سطرين على الجوّال، وتتقلّص المستطيلات الملتقطة معه. مواضع السطر الأخير في اتجاه RTL، والنص المضبوط (justified)، وفواصل الأسطر اليدوية، كلّها تُعاد بنسبة 1:1 لأنها قِيست فعلًا ولم تُخمَّن.",[10,5367,5368],{},"واجهة الـ wrapper لكل هذا هي prop واحدة فقط:",[277,5370,5372],{"className":5181,"code":5371,"language":5183,"meta":282,"style":282},"\u003CASkeleton :loading=\"loading\">\n  \u003CSomeRichComponent \u002F>\n\u003C\u002FASkeleton>\n",[14,5373,5374,5397,5402],{"__ignoreMap":282},[286,5375,5376,5378,5381,5384,5387,5389,5391,5393,5395],{"class":288,"line":289},[286,5377,307],{"class":306},[286,5379,5380],{"class":5192},"ASkeleton",[286,5382,5383],{"class":306}," :",[286,5385,5386],{"class":302},"loading",[286,5388,537],{"class":306},[286,5390,5201],{"class":899},[286,5392,5386],{"class":306},[286,5394,5201],{"class":899},[286,5396,5209],{"class":306},[286,5398,5399],{"class":288,"line":356},[286,5400,5401],{"class":306},"  \u003CSomeRichComponent \u002F>\n",[286,5403,5404,5406,5408],{"class":288,"line":376},[286,5405,5224],{"class":306},[286,5407,5380],{"class":5192},[286,5409,5209],{"class":306},[10,5411,5412],{},"ولو أردتَ بناء تدفّق التقاط وإعادة عرض خاص بك، فالدالة الأساسية معرّضة كتصدير عام:",[277,5414,5418],{"className":5415,"code":5416,"language":5417,"meta":282,"style":282},"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",[14,5419,5420,5441,5445,5465,5475,5485,5495],{"__ignoreMap":282},[286,5421,5422,5425,5428,5430,5433,5436,5439],{"class":288,"line":289},[286,5423,5424],{"class":292},"import",[286,5426,5427],{"class":306}," { captureSnapshot, ",[286,5429,1044],{"class":292},[286,5431,5432],{"class":306}," CaptureSnapshot } ",[286,5434,5435],{"class":292},"from",[286,5437,5438],{"class":899}," '@alikhalilll\u002Fa-skeleton'",[286,5440,487],{"class":306},[286,5442,5443],{"class":288,"line":356},[286,5444,547],{"emptyLinePlaceholder":546},[286,5446,5447,5449,5452,5454,5457,5459,5462],{"class":288,"line":376},[286,5448,773],{"class":292},[286,5450,5451],{"class":323}," snap",[286,5453,320],{"class":292},[286,5455,5456],{"class":302}," CaptureSnapshot",[286,5458,338],{"class":292},[286,5460,5461],{"class":302}," captureSnapshot",[286,5463,5464],{"class":306},"(myRootEl, {\n",[286,5466,5467,5470,5473],{"class":288,"line":415},[286,5468,5469],{"class":306},"  maxDepth: ",[286,5471,5472],{"class":323},"12",[286,5474,2047],{"class":306},[286,5476,5477,5480,5483],{"class":288,"line":441},[286,5478,5479],{"class":306},"  maxNodes: ",[286,5481,5482],{"class":323},"800",[286,5484,2047],{"class":306},[286,5486,5487,5490,5493],{"class":288,"line":543},[286,5488,5489],{"class":306},"  minSize: ",[286,5491,5492],{"class":323},"4",[286,5494,2047],{"class":306},[286,5496,5497],{"class":288,"line":550},[286,5498,1699],{"class":306},[10,5500,5501,25,5504,25,5507,5510,5511,5514,5515,5518,5519,5522,5523,5525],{},[14,5502,5503],{},"maxDepth",[14,5505,5506],{},"maxNodes",[14,5508,5509],{},"minSize"," هي حدود الميزانية. بفضلها لا يتوقف الخيط الرئيسي عند جدول من 5000 صفّ. عندما يبلغ الـ walk الحدّ يتوقّف، ويضبط ",[14,5512,5513],{},"truncated: true","، ثم يسجّل الـ wrapper ",[14,5516,5517],{},"console.warn"," لمرة واحدة لكل ",[14,5520,5521],{},"cacheKey"," كي تنتبه للأمر أثناء التطوير لا في الإنتاج. أما ",[14,5524,5509],{}," فيُسقط أيّ شيء أصغر من أربعة بكسلات CSS على أيّ محور، وهو ما يحذف الخيوط الرفيعة ونقاط الفراغ التي كانت ستظهر لولا ذلك كقصاصات ملوّنة داخل الـ skeleton.",[10,5527,5528,5529,5531],{},"المقايضة التي يدفعها وضع clone هي أنه يعمل على العميل فقط. تحتاج إلى DOM حقيقي، وإلى ",[14,5530,5340],{},"، وإلى متصفح يقرأ الرسم النهائي. على الخادم يُعرض markup الـ slot الحقيقي كالمعتاد، ولا يبدأ الالتقاط وإعادة العرض إلا بعد الـ hydration. لو احتجتَ إلى placeholder جاهز من الخادم قبل انتهاء الـ hydration، فذلك بالضبط ما وُجد وضع mirror من أجله.",[93,5533,5535],{"id":5534},"وضع-mirror-امشِ-على-الـ-vnodes-وسلّمها-إلى-الخادم","وضع Mirror: امشِ على الـ vnodes وسلّمها إلى الخادم",[10,5537,5538,5539,5542],{},"Mirror يبدّل المحرك من أساسه. بدلًا من تركيب الـ slot وقراءة الـ DOM، يمشي على شجرة الـ vnode مباشرةً وقت العرض. لا وصول إلى ",[14,5540,5541],{},"window",". متوافق مع الـ hydration. قابل للعرض من الخادم.",[277,5544,5546],{"className":5181,"code":5545,"language":5183,"meta":282,"style":282},"\u003CASkeleton mode=\"mirror\" :loading=\"loading\">\n  \u003CSomeRichComponent \u002F>\n\u003C\u002FASkeleton>\n",[14,5547,5548,5576,5580],{"__ignoreMap":282},[286,5549,5550,5552,5554,5557,5559,5562,5564,5566,5568,5570,5572,5574],{"class":288,"line":289},[286,5551,307],{"class":306},[286,5553,5380],{"class":5192},[286,5555,5556],{"class":302}," mode",[286,5558,537],{"class":306},[286,5560,5561],{"class":899},"\"mirror\"",[286,5563,5383],{"class":306},[286,5565,5386],{"class":302},[286,5567,537],{"class":306},[286,5569,5201],{"class":899},[286,5571,5386],{"class":306},[286,5573,5201],{"class":899},[286,5575,5209],{"class":306},[286,5577,5578],{"class":288,"line":356},[286,5579,5401],{"class":306},[286,5581,5582,5584,5586],{"class":288,"line":376},[286,5583,5224],{"class":306},[286,5585,5380],{"class":5192},[286,5587,5209],{"class":306},[10,5589,5590,5591,5594,5595,5598,5599,327,5602,327,5605,5608,5609,5612],{},"يحافظ الـ walker على وسم كل عنصر وعلى فئته الأصلية. الأوراق الحاملة للنص تتحوّل إلى ",[14,5592,5593],{},"\u003Cspan class=\"a-skel-text-content\">"," (نص شفاف فوق خلفية skeleton) مع ",[14,5596,5597],{},"box-decoration-break: clone"," كي يلتفّ الـ shimmer حول عرض النص المعروض بدقّة سطرًا سطرًا. أما الوسوم الذرّية والتفاعلية (",[14,5600,5601],{},"\u003Cimg>",[14,5603,5604],{},"\u003Cbutton>",[14,5606,5607],{},"\u003Csvg>",") فتصبح ",[14,5610,5611],{},"\u003Cdiv class=\"a-skel-block\">"," بأبعاد مستمدّة من فئتها.",[10,5614,5615,5616,5618],{},"مكسب Mirror الأساسي هو دعم الـ SSR. ومقايضته هي الدقّة. لأن الـ walker لا يملك ",[14,5617,5340],{},"، فهو لا يرى إلا ما هو مؤلَّف صراحةً: أسماء الفئات الثابتة والأنماط المضمّنة. لو كانت هوية مكوّنك تأتي من قاعدة CSS-in-JS مُجزّأة أو من كتلة نمط scoped، فلن يلتقطها الـ mirror كما يفعل clone. لكن في معظم التخطيطات تكفي الفئة الثابتة، وأمان الـ SSR يستحق أكثر من آخر 5% من التطابق البكسلي. أما المكوّنات المعقّدة بصريًا فمكانها الطبيعي هو clone.",[10,5620,5621],{},"كلا الوضعين يتشاركان خط أنابيب الحركة نفسه، والـ cache نفسه، وقاعدة الـ a11y نفسها. التبديل بينهما لا يكلّفك أكثر من prop واحدة.",[93,5623,5625],{"id":5624},"وضع-structural-أشجار-في-الـ-flow-الطبيعي-محفوظة-بين-الجلسات","وضع Structural: أشجار في الـ flow الطبيعي، محفوظة بين الجلسات",[10,5627,5628,5629,4950,5632,60],{},"الاستراتيجية الثالثة ألجأ إليها حين لا يكون الـ wrapper هو وحدة التنسيق المناسبة لديّ: حين أريد لشكل ملتقط أن يعيش عبر عدة نُسخ، أو أن يستمرّ بين الجلسات، أو أن يعيد التدفّق مع العنصر الأب عند تغيير حجم الـ viewport بدل تثبيته على إحداثيات مطلقة. لتلك الحالة أستخدم ",[14,5630,5631],{},"useSkeleton()",[14,5633,5634],{},"\u003CASkeletonLayer>",[277,5636,5638],{"className":5181,"code":5637,"language":5183,"meta":282,"style":282},"\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",[14,5639,5640,5660,5674,5687,5691,5716,5734,5760,5786,5790,5813,5832,5854,5863,5867,5871,5899,5907,5911,5920,5936,5962,5979,5999,6008],{"__ignoreMap":282},[286,5641,5642,5644,5647,5650,5653,5655,5658],{"class":288,"line":289},[286,5643,307],{"class":306},[286,5645,5646],{"class":5192},"script",[286,5648,5649],{"class":302}," setup",[286,5651,5652],{"class":302}," lang",[286,5654,537],{"class":306},[286,5656,5657],{"class":899},"\"ts\"",[286,5659,5209],{"class":306},[286,5661,5662,5664,5667,5669,5672],{"class":288,"line":356},[286,5663,5424],{"class":292},[286,5665,5666],{"class":306}," { computed, ref } ",[286,5668,5435],{"class":292},[286,5670,5671],{"class":899}," 'vue'",[286,5673,487],{"class":306},[286,5675,5676,5678,5681,5683,5685],{"class":288,"line":376},[286,5677,5424],{"class":292},[286,5679,5680],{"class":306}," { useSkeleton } ",[286,5682,5435],{"class":292},[286,5684,5438],{"class":899},[286,5686,487],{"class":306},[286,5688,5689],{"class":288,"line":415},[286,5690,547],{"emptyLinePlaceholder":546},[286,5692,5693,5695,5698,5700,5703,5706,5709,5711,5713],{"class":288,"line":441},[286,5694,773],{"class":292},[286,5696,5697],{"class":323}," props",[286,5699,338],{"class":292},[286,5701,5702],{"class":302}," defineProps",[286,5704,5705],{"class":306},"\u003C{ ",[286,5707,5708],{"class":316},"userId",[286,5710,320],{"class":292},[286,5712,324],{"class":323},[286,5714,5715],{"class":306}," }>();\n",[286,5717,5718,5720,5723,5725,5728,5730,5732],{"class":288,"line":543},[286,5719,773],{"class":292},[286,5721,5722],{"class":323}," user",[286,5724,338],{"class":292},[286,5726,5727],{"class":302}," ref",[286,5729,467],{"class":306},[286,5731,2808],{"class":323},[286,5733,792],{"class":306},[286,5735,5736,5738,5741,5743,5746,5749,5751,5754,5756,5758],{"class":288,"line":550},[286,5737,773],{"class":292},[286,5739,5740],{"class":323}," loading",[286,5742,338],{"class":292},[286,5744,5745],{"class":302}," computed",[286,5747,5748],{"class":306},"(() ",[286,5750,953],{"class":292},[286,5752,5753],{"class":306}," user.value ",[286,5755,3935],{"class":292},[286,5757,2231],{"class":323},[286,5759,792],{"class":306},[286,5761,5762,5764,5767,5769,5771,5773,5776,5778,5780,5782,5784],{"class":288,"line":595},[286,5763,773],{"class":292},[286,5765,5766],{"class":323}," containerRef",[286,5768,338],{"class":292},[286,5770,5727],{"class":302},[286,5772,307],{"class":306},[286,5774,5775],{"class":302},"HTMLElement",[286,5777,852],{"class":292},[286,5779,2231],{"class":323},[286,5781,313],{"class":306},[286,5783,2808],{"class":323},[286,5785,792],{"class":306},[286,5787,5788],{"class":288,"line":620},[286,5789,547],{"emptyLinePlaceholder":546},[286,5791,5792,5794,5796,5799,5801,5804,5806,5808,5811],{"class":288,"line":658},[286,5793,773],{"class":292},[286,5795,521],{"class":306},[286,5797,5798],{"class":323},"shape",[286,5800,327],{"class":306},[286,5802,5803],{"class":323},"clear",[286,5805,2294],{"class":306},[286,5807,537],{"class":292},[286,5809,5810],{"class":302}," useSkeleton",[286,5812,2036],{"class":306},[286,5814,5815,5818,5821,5824,5826,5828,5830],{"class":288,"line":679},[286,5816,5817],{"class":306},"  cacheKey: ",[286,5819,5820],{"class":899},"`user-card:${",[286,5822,5823],{"class":306},"props",[286,5825,60],{"class":899},[286,5827,5708],{"class":306},[286,5829,1691],{"class":899},[286,5831,2047],{"class":306},[286,5833,5834,5837,5840,5842,5845,5847,5849,5851],{"class":288,"line":707},[286,5835,5836],{"class":302},"  target",[286,5838,5839],{"class":306},": () ",[286,5841,953],{"class":292},[286,5843,5844],{"class":306}," (loading.value ",[286,5846,2744],{"class":292},[286,5848,2231],{"class":323},[286,5850,5383],{"class":292},[286,5852,5853],{"class":306}," containerRef.value),\n",[286,5855,5856,5859,5861],{"class":288,"line":729},[286,5857,5858],{"class":306},"  persist: ",[286,5860,980],{"class":323},[286,5862,2047],{"class":306},[286,5864,5865],{"class":288,"line":735},[286,5866,1699],{"class":306},[286,5868,5869],{"class":288,"line":1376},[286,5870,547],{"emptyLinePlaceholder":546},[286,5872,5873,5876,5879,5882,5884,5887,5889,5891,5894,5896],{"class":288,"line":1401},[286,5874,5875],{"class":302},"fetchUser",[286,5877,5878],{"class":306},"(props.userId).",[286,5880,5881],{"class":302},"then",[286,5883,1640],{"class":306},[286,5885,5886],{"class":316},"u",[286,5888,1115],{"class":306},[286,5890,953],{"class":292},[286,5892,5893],{"class":306}," (user.value ",[286,5895,537],{"class":292},[286,5897,5898],{"class":306}," u));\n",[286,5900,5901,5903,5905],{"class":288,"line":1428},[286,5902,5224],{"class":306},[286,5904,5646],{"class":5192},[286,5906,5209],{"class":306},[286,5908,5909],{"class":288,"line":1449},[286,5910,547],{"emptyLinePlaceholder":546},[286,5912,5913,5915,5918],{"class":288,"line":1455},[286,5914,307],{"class":306},[286,5916,5917],{"class":5192},"template",[286,5919,5209],{"class":306},[286,5921,5922,5925,5927,5929,5931,5934],{"class":288,"line":1460},[286,5923,5924],{"class":306},"  \u003C",[286,5926,5193],{"class":5192},[286,5928,5727],{"class":302},[286,5930,537],{"class":306},[286,5932,5933],{"class":899},"\"containerRef\"",[286,5935,5209],{"class":306},[286,5937,5938,5941,5944,5946,5948,5951,5954,5956,5959],{"class":288,"line":1486},[286,5939,5940],{"class":306},"    \u003C",[286,5942,5943],{"class":5192},"ASkeletonLayer",[286,5945,5196],{"class":302},[286,5947,537],{"class":306},[286,5949,5950],{"class":899},"\"loading && shape\"",[286,5952,5953],{"class":302}," :shape",[286,5955,537],{"class":306},[286,5957,5958],{"class":899},"\"shape\"",[286,5960,5961],{"class":306}," \u002F>\n",[286,5963,5964,5966,5969,5972,5974,5977],{"class":288,"line":1498},[286,5965,5940],{"class":306},[286,5967,5968],{"class":5192},"ColdStartFallback",[286,5970,5971],{"class":302}," v-else-if",[286,5973,537],{"class":306},[286,5975,5976],{"class":899},"\"loading\"",[286,5978,5961],{"class":306},[286,5980,5981,5983,5986,5989,5992,5994,5997],{"class":288,"line":1529},[286,5982,5940],{"class":306},[286,5984,5985],{"class":5192},"UserCard",[286,5987,5988],{"class":302}," v-else",[286,5990,5991],{"class":302}," :data",[286,5993,537],{"class":306},[286,5995,5996],{"class":899},"\"user\"",[286,5998,5961],{"class":306},[286,6000,6001,6004,6006],{"class":288,"line":1534},[286,6002,6003],{"class":306},"  \u003C\u002F",[286,6005,5193],{"class":5192},[286,6007,5209],{"class":306},[286,6009,6010,6012,6014],{"class":288,"line":1558},[286,6011,5224],{"class":306},[286,6013,5917],{"class":5192},[286,6015,5209],{"class":306},[10,6017,6018,6019,6022,6023,327,6026,327,6029,327,6032,327,6035,327,6038,6041,6042,6044,6045,6048,6049,6052],{},"يلتقط ",[14,6020,6021],{},"walkStructural()"," الشكل، وهو walker شجريّ يحافظ على وسوم الحاويات وفئاتها الأصلية وعلى CSS الـ layout المحلول (",[14,6024,6025],{},"display",[14,6027,6028],{},"flex-*",[14,6030,6031],{},"gap",[14,6033,6034],{},"padding",[14,6036,6037],{},"grid-*",[14,6039,6040],{},"box-sizing","). أما الأوراق فتحمل عرضًا وارتفاعًا مضمّنَين إضافةً إلى الإشارات البصرية (background, border, radius). عندما يعيد ",[14,6043,5634],{}," عرض الشجرة، فإنه يفعل ذلك في ",[106,6046,6047],{},"الـ flow الطبيعي",": الـ ",[14,6050,6051],{},"\u003Cdiv class=\"flex flex-col gap-4 p-4\">"," الملتقط يُعرض كحاوية flex فعلية، وكتل الـ shimmer تجلس داخلها كأبناء flex. تغيّر حجم الـ viewport فيعيد الـ layout التدفّق، ولا يتأخّر الـ skeleton خلفه كما يحدث في إعادة عرض clone ذات المواضع المطلقة.",[10,6054,6055],{},"فلو كان لديك هذا الـ markup الفعلي:",[277,6057,6061],{"className":6058,"code":6059,"language":6060,"meta":282,"style":282},"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",[14,6062,6063,6079,6092,6104,6117],{"__ignoreMap":282},[286,6064,6065,6067,6069,6072,6074,6077],{"class":288,"line":289},[286,6066,307],{"class":306},[286,6068,5193],{"class":5192},[286,6070,6071],{"class":302}," class",[286,6073,537],{"class":306},[286,6075,6076],{"class":899},"\"flex flex-col gap-4 p-4\"",[286,6078,5209],{"class":306},[286,6080,6081,6083,6085,6088,6090],{"class":288,"line":356},[286,6082,5924],{"class":306},[286,6084,269],{"class":5192},[286,6086,6087],{"class":306},">...\u003C\u002F",[286,6089,269],{"class":5192},[286,6091,5209],{"class":306},[286,6093,6094,6096,6098,6100,6102],{"class":288,"line":376},[286,6095,5924],{"class":306},[286,6097,10],{"class":5192},[286,6099,6087],{"class":306},[286,6101,10],{"class":5192},[286,6103,5209],{"class":306},[286,6105,6106,6108,6111,6113,6115],{"class":288,"line":415},[286,6107,5924],{"class":306},[286,6109,6110],{"class":5192},"button",[286,6112,6087],{"class":306},[286,6114,6110],{"class":5192},[286,6116,5209],{"class":306},[286,6118,6119,6121,6123],{"class":288,"line":441},[286,6120,5224],{"class":306},[286,6122,5193],{"class":5192},[286,6124,5209],{"class":306},[10,6126,6127],{},"فإن الـ layer يعيد عرضه هكذا:",[277,6129,6131],{"className":6058,"code":6130,"language":6060,"meta":282,"style":282},"\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",[14,6132,6133,6140,6150,6160,6164,6191,6214,6237],{"__ignoreMap":282},[286,6134,6135,6137],{"class":288,"line":289},[286,6136,307],{"class":306},[286,6138,6139],{"class":5192},"div\n",[286,6141,6142,6145,6147],{"class":288,"line":356},[286,6143,6144],{"class":302},"  class",[286,6146,537],{"class":306},[286,6148,6149],{"class":899},"\"flex flex-col gap-4 p-4\"\n",[286,6151,6152,6155,6157],{"class":288,"line":376},[286,6153,6154],{"class":302},"  style",[286,6156,537],{"class":306},[286,6158,6159],{"class":899},"\"display: flex; flex-direction: column; gap: 16px; padding: 16px; box-sizing: border-box\"\n",[286,6161,6162],{"class":288,"line":415},[286,6163,5209],{"class":306},[286,6165,6166,6168,6170,6172,6174,6177,6180,6182,6185,6189],{"class":288,"line":441},[286,6167,5924],{"class":306},[286,6169,5193],{"class":5192},[286,6171,6071],{"class":302},[286,6173,537],{"class":306},[286,6175,6176],{"class":899},"\"a-skel\"",[286,6178,6179],{"class":302}," style",[286,6181,537],{"class":306},[286,6183,6184],{"class":899},"\"width: 200px; height: 24px; ...\"",[286,6186,6188],{"class":6187},"s7hpK"," \u002F",[286,6190,5209],{"class":306},[286,6192,6193,6195,6197,6199,6201,6203,6205,6207,6210,6212],{"class":288,"line":543},[286,6194,5924],{"class":306},[286,6196,5193],{"class":5192},[286,6198,6071],{"class":302},[286,6200,537],{"class":306},[286,6202,6176],{"class":899},[286,6204,6179],{"class":302},[286,6206,537],{"class":306},[286,6208,6209],{"class":899},"\"width: 280px; height: 16px; ...\"",[286,6211,6188],{"class":6187},[286,6213,5209],{"class":306},[286,6215,6216,6218,6220,6222,6224,6226,6228,6230,6233,6235],{"class":288,"line":550},[286,6217,5924],{"class":306},[286,6219,5193],{"class":5192},[286,6221,6071],{"class":302},[286,6223,537],{"class":306},[286,6225,6176],{"class":899},[286,6227,6179],{"class":302},[286,6229,537],{"class":306},[286,6231,6232],{"class":899},"\"width: 120px; height: 36px; ...\"",[286,6234,6188],{"class":6187},[286,6236,5209],{"class":306},[286,6238,6239,6241,6243],{"class":288,"line":595},[286,6240,5224],{"class":306},[286,6242,5193],{"class":5192},[286,6244,5209],{"class":306},[10,6246,6247],{},"تبقى الفئة الأصلية موجودة كي يظلّ نظام الـ CSS الـ utility-first ساريًا، ويُضمَّن CSS الـ layout المحلول كنسخة احتياطية كي يبدو الـ skeleton صحيحًا حتى لو لم تُحمَّل ورقة الأنماط عند نقطة التركيب. يهمّ هذا كثيرًا لو كنت تعرض الشكل الملتقط داخل سياق CSS مختلف، مثل نافذة معاينة على هيئة بريد إلكتروني، أو shell معروض من الخادم لا يجلب Tailwind أصلًا.",[10,6249,6250,6251,6254,6255,6258,6259,6262,6263,6266,6267,6270],{},"الميزة الأخرى التي يمنحك إياها وضع structural هي الاستمرارية. تجعل ",[14,6252,6253],{},"persist: true"," الشكل الملتقط ينعكس إلى ",[14,6256,6257],{},"localStorage"," تحت البادئة ",[14,6260,6261],{},"a-skeleton:s:",". عند البدء البارد (cold start) في زيارة عائدة، يقرأ الـ cache الشكل المحفوظ ويعيد عرض الـ skeleton في أول رسم مباشرةً، دون وميض \"shimmer عام\" بينما يُركَّب المكوّن الحقيقي ويقيس نفسه. الـ cache مُصدَّر مخططه (",[14,6264,6265],{},"v: 3"," لـ structural، و",[14,6268,6269],{},"v: 2"," للشكل المسطّح القديم)، ولذلك تُطهَّر الإصدارات غير المتطابقة تلقائيًا عند القراءة. لا يمكنك إعادة عرض هندسة الأمس عبر مُعارض اليوم لو تغيّر شكل البيانات.",[10,6272,6273],{},"الاستراتيجيات الثلاث تختار سلوكيات افتراضية مختلفة انطلاقًا من البدائيات نفسها. Clone يعيد عرض بكسلات بمواضع مطلقة. Mirror يعيد عرض vnodes داخل الشجرة الأصلية. Structural يعيد عرض الحاويات في الـ flow الطبيعي. وحدة cache واحدة، وسطح theming واحد، وقاعدة a11y واحدة، وثلاثة محرّكات مختلفة.",[93,6275,6277],{"id":6276},"خط-أنابيب-الالتقاط-والتخزين-المؤقت-وإعادة-العرض","خط أنابيب الالتقاط والتخزين المؤقت وإعادة العرض",[10,6279,6280],{},"القصة المعمارية واحدة في الأوضاع الثلاثة، حتى وإن تباينت التنفيذات. إليك دورة تحميل واحدة من أوّلها إلى آخرها.",[10,6282,6283,6286],{},[106,6284,6285],{},"الرسم الأول، وفشل الـ cache."," يُركَّب الـ wrapper، ولا يوجد شكل مخزّن بعد. في وضعَي clone وmirror يمشي المحرك على بنية الـ slot (vnodes في mirror، وتركيب DOM خارج الشاشة في clone) وينتج placeholder يعكس بنية وسوم الـ slot. تحتفظ الحاويات بوسومها الدلالية وبـ CSS الـ layout الخاص بها. أما الأوراق الذرّية والعناصر الحاملة للنص فتتحوّل إلى placeholders من نوع shimmer. يرى المستخدم skeleton بنيويًا يطابق حدود المكوّن الحقيقي على الفور، قبل أن تصل أيّ بيانات.",[10,6288,6289,6292,6293,4950,6296,6299,6300,6303,6304,6306,6307,6309,6310,6313,6314,6316,6317,6319,6320,2010],{},[106,6290,6291],{},"وصول البيانات."," يُعرض المحتوى الحقيقي داخل الـ slot. يجدول ",[14,6294,6295],{},"ResizeObserver",[14,6297,6298],{},"requestAnimationFrame"," تمريرة قياس. في وضع clone تقرأ ",[14,6301,6302],{},"captureSnapshot()"," قيمة ",[14,6305,5340],{}," لكل عنصر ومستطيلات النص لكل سطر عبر ",[14,6308,5364],{},"، ثم تجمّد النتيجة داخل شجرة ",[14,6311,6312],{},"CaptureSnapshot",". في وضع structural تنتج ",[14,6315,6021],{}," شجرة مجمّدة تحمل فيها الحاويات CSS الـ layout الخاص بها، وتحمل الأوراق أبعادها وإشاراتها البصرية. في الحالتين تصبح اللقطة الملتقطة في الذاكرة الآن (وفي ",[14,6318,6257],{}," أيضًا لو كانت ",[14,6321,6253],{},[10,6323,6324,6327,6328,6330,6331,6333,6334,6337,6338,6340],{},[106,6325,6326],{},"التقلّب التالي إلى حالة التحميل."," يعود المستخدم إلى الصفحة، أو يطلق إعادة جلب، أو يزور المسار في جلسة لاحقة. تنقلب ",[14,6329,5386],{}," إلى ",[14,6332,980],{},". يُعاد عرض الشكل المخزّن فورًا، بلا خطوة وسيطة من نوع \"خمّن الشكل\". في وضع clone يعرض ",[14,6335,6336],{},"\u003CASkeletonClone>"," شجرة من الـ divs ذات المواضع المطلقة، يحمل كلٌّ منها نمطه المضمّن المجمّد مسبقًا؛ وحلقة العرض خالية تمامًا من التخصيص لأن كل حمولة نمط حُسبت وقت الالتقاط. في وضع structural يعرض ",[14,6339,5634],{}," الشجرة في الـ flow الطبيعي، مع تشغيل CSS الـ layout الملتقط لسلوك حاوية flex أو grid.",[10,6342,6343,6344,6346,6347,6349,6350,6352,6353,6355,6356,5518,6358,6360],{},"للميزانيات دور في كل خطوة. ",[14,6345,5506],{}," (افتراضيًا 500–800 بحسب الـ walker) هو حدّ حجم الشجرة. ",[14,6348,5503],{}," (افتراضيًا 12–16) هو حدّ الاستدعاء العودي. ",[14,6351,5509],{}," (افتراضيًا 4 بكسلات CSS) هو مرشّح لكل ورقة. هذه الأرقام ليست اعتباطية: اخترتُها بحيث تبقى لوحة تحكم مزدحمة بمئات الأوراق قابلة للمشي داخل rAF واحد، وبحيث يتّسع إدخال ",[14,6354,6257],{}," للشجرة الملتقطة دون تجاوز الحصّة البالغة 5 ميغابايت. وحين يُقتطع الـ walk، يسجّل الـ wrapper ",[14,6357,5517],{},[14,6359,5521],{}," كي تكتشف الأمر في التطوير، لا في الإنتاج.",[10,6362,6363,6364,25,6367,6369],{},"قراءة layout واحدة في البداية، ثم قيم مخزّنة في بقية المشي. تحدث استدعاءات ",[14,6365,6366],{},"getBoundingClientRect()",[14,6368,5340],{}," في تمريرة واحدة من أعلى إلى أسفل، دون كتابات تتخلّلها، ودون قذف تخطيط: reflow قسري واحد، ثم تصبح اللقطة الملتقطة دائمة بعده.",[93,6371,6373],{"id":6372},"البدائيات-الـ-15-المسمّاة","البدائيات الـ 15 المسمّاة",[10,6375,6376,6377,327,6380,327,6383,327,6386,327,6389,327,6392,327,6395,327,6398,327,6401,327,6404,327,6407,327,6410,327,6413,327,6416,327,6419,60],{},"التوليد الذاتي لا يناسب كل حالة. لو لم يكن لحالة التحميل عندك مكوّن حقيقي يستحق أن يُلَفّ (كأن تعرض الـ skeleton قبل بدء أيّ جلب بيانات، أو يكون الـ placeholder حقًا مختلف الـ markup عن العرض النهائي)، فلا يجد الالتقاط التلقائي شيئًا يقرأه أصلًا. لهذه الحالات تشحن الحزمة 15 متغيّرًا مسمّى: ",[14,6378,6379],{},"\u003CASkeletonCard>",[14,6381,6382],{},"\u003CASkeletonText>",[14,6384,6385],{},"\u003CASkeletonHeading>",[14,6387,6388],{},"\u003CASkeletonAvatar>",[14,6390,6391],{},"\u003CASkeletonImage>",[14,6393,6394],{},"\u003CASkeletonVideo>",[14,6396,6397],{},"\u003CASkeletonButton>",[14,6399,6400],{},"\u003CASkeletonInput>",[14,6402,6403],{},"\u003CASkeletonChip>",[14,6405,6406],{},"\u003CASkeletonListItem>",[14,6408,6409],{},"\u003CASkeletonTable>",[14,6411,6412],{},"\u003CASkeletonChart>",[14,6414,6415],{},"\u003CASkeletonForm>",[14,6417,6418],{},"\u003CASkeletonArticle>",[14,6420,6421],{},"\u003CASkeletonDivider>",[10,6423,6424],{},"هذه ليست بديلًا عن مسار الالتقاط التلقائي، بل مخرجًا جانبيًا حين تحتاجه. تشترك في خط أنابيب الحركة نفسه، وفي رموز الـ theming نفسها، وفي قاعدة الـ a11y نفسها. تؤلّفها يدويًا عند الحاجة، وإلا فدع الـ wrapper يقوم بالعمل.",[10,6426,6427,6428,6431],{},"يوجد أيضًا ",[14,6429,6430],{},"\u003CASkeletonBlock>"," للحالة التي لا يناسبها أيّ من المتغيّرات وتريد تركيب نسختك الخاصة:",[277,6433,6435],{"className":5181,"code":6434,"language":5183,"meta":282,"style":282},"\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",[14,6436,6437,6462,6467,6472,6477,6482,6487,6495,6503],{"__ignoreMap":282},[286,6438,6439,6441,6443,6445,6447,6449,6451,6453,6455,6457,6460],{"class":288,"line":289},[286,6440,307],{"class":306},[286,6442,5193],{"class":5192},[286,6444,5196],{"class":292},[286,6446,537],{"class":306},[286,6448,5201],{"class":899},[286,6450,5386],{"class":306},[286,6452,5201],{"class":899},[286,6454,6071],{"class":302},[286,6456,537],{"class":306},[286,6458,6459],{"class":899},"\"flex items-start gap-4 p-4\"",[286,6461,5209],{"class":306},[286,6463,6464],{"class":288,"line":356},[286,6465,6466],{"class":306},"  \u003CASkeletonBlock type=\"circle\" :w=\"64\" :h=\"64\" \u002F>\n",[286,6468,6469],{"class":288,"line":376},[286,6470,6471],{"class":306},"  \u003Cdiv class=\"flex-1 space-y-2\">\n",[286,6473,6474],{"class":288,"line":415},[286,6475,6476],{"class":306},"    \u003CASkeletonBlock type=\"text\" :w=\"160\" :h=\"18\" \u002F>\n",[286,6478,6479],{"class":288,"line":441},[286,6480,6481],{"class":306},"    \u003CASkeletonBlock type=\"text\" :w=\"100\" :h=\"12\" \u002F>\n",[286,6483,6484],{"class":288,"line":543},[286,6485,6486],{"class":306},"    \u003CASkeletonBlock type=\"text\" :lines=\"3\" :h=\"14\" class=\"!mt-3\" \u002F>\n",[286,6488,6489,6491,6493],{"class":288,"line":550},[286,6490,6003],{"class":306},[286,6492,5193],{"class":5192},[286,6494,5209],{"class":306},[286,6496,6497,6499,6501],{"class":288,"line":595},[286,6498,5224],{"class":306},[286,6500,5193],{"class":5192},[286,6502,5209],{"class":306},[286,6504,6505],{"class":288,"line":620},[286,6506,6507],{"class":306},"\u003CUserCard v-else :data=\"user\" \u002F>\n",[10,6509,6510,6512,6513,6516,6517,6520],{},[14,6511,6430],{}," صديق للـ flow. يتركّب بسلاسة مع تخطيطات flex وgrid والـ stack. تكدّس prop ",[14,6514,6515],{},"lines"," على ",[14,6518,6519],{},"type=\"text\""," عددًا N من الأشرطة مع جعل الأخير بعرض 70%، وهو استدلال يكون صحيحًا بدرجة كافية في أغلب الحالات حين لا يتوفّر مكوّن حقيقي تقيسه.",[93,6522,6524],{"id":6523},"الحركات-والـ-theming-بوصفها-استراتيجيات","الحركات والـ theming بوصفها استراتيجيات",[10,6526,6527,6528,6531,6532,6535,6536,6539,6540,6543,6544,6547,6548,6551,6552,6539,6555,6558,6559,6562,6563,6566],{},"تُشحن معنا أربعة أوضاع للحركة: ",[14,6529,6530],{},"shimmer"," (تدرّج ماسح على عنصر زائف ",[14,6533,6534],{},"::after","، محتوى داخل كل كتلة عبر ",[14,6537,6538],{},"overflow: hidden",")، و",[14,6541,6542],{},"pulse"," (تدوير opacity من 1 إلى ",[14,6545,6546],{},"--ak-skel-pulse-min"," والعودة)، و",[14,6549,6550],{},"wave"," (تدرّج منزلق عبر ",[14,6553,6554],{},"background-position",[14,6556,6557],{},"none"," (كتل ثابتة). كلٌّ منها استراتيجية قائمة بذاتها، وليست فرعًا شرطيًا. تمرّر ",[14,6560,6561],{},"animation=\"pulse\""," فتنخرط فئة الـ CSS المناسبة تلقائيًا. لا سلسلة ",[14,6564,6565],{},"if (animation === 'pulse')"," داخل المكوّن.",[10,6568,6569,6570,6573],{},"يعطّل ",[14,6571,6572],{},"prefers-reduced-motion: reduce"," كل حركة تلقائيًا. يهبط عنصر shimmer الزائف إلى طبقة ثابتة بشفافية منخفضة، ويتوقّف الـ pulse عن التدوير. لا شيفرة مكوّن معنيّة بذلك أصلًا؛ الأمر مجرد استعلام media في ورقة الأنماط.",[10,6575,6576,6577,6580,6581,6584],{},"الـ theming يقوم على متغيرات CSS. الرموز منطاقة تحت ",[14,6578,6579],{},"--ak-skel-*"," (مع أسماء بديلة متوافقة رجعيًا تحت ",[14,6582,6583],{},"--ak-skeleton-*"," لمستخدمي الإصدار الأول):",[4840,6586,6587,6597],{},[4843,6588,6589],{},[4846,6590,6591,6594],{},[4849,6592,6593],{},"الرمز",[4849,6595,6596],{},"يُستخدم لأجل",[4859,6598,6599,6609,6619,6629,6639,6648,6658],{},[4846,6600,6601,6606],{},[4864,6602,6603],{},[14,6604,6605],{},"--ak-skel-base",[4864,6607,6608],{},"تعبئة الكتلة",[4846,6610,6611,6616],{},[4864,6612,6613],{},[14,6614,6615],{},"--ak-skel-highlight",[4864,6617,6618],{},"لون مسح shimmer \u002F wave",[4846,6620,6621,6626],{},[4864,6622,6623],{},[14,6624,6625],{},"--ak-skel-radius",[4864,6627,6628],{},"نصف قطر حدود الكتلة الافتراضي",[4846,6630,6631,6636],{},[4864,6632,6633],{},[14,6634,6635],{},"--ak-skel-duration",[4864,6637,6638],{},"طول دورة الحركة",[4846,6640,6641,6645],{},[4864,6642,6643],{},[14,6644,6546],{},[4864,6646,6647],{},"قيمة opacity عند قاع دورة الـ pulse",[4846,6649,6650,6655],{},[4864,6651,6652],{},[14,6653,6654],{},"--ak-skel-ring",[4864,6656,6657],{},"لون حلقة داخلية خفيفة بعرض 1 بكسل",[4846,6659,6660,6665],{},[4864,6661,6662],{},[14,6663,6664],{},"--ak-skel-icon",[4864,6666,6667],{},"لون أيقونة placeholder (متغيرات image \u002F video)",[10,6669,6670,6671,6674],{},"تستطيع أن تكتب فوق هذه الرموز على ",[14,6672,6673],{},":root","، أو على فئة wrapper لأوضاع multi-tenant، أو مضمّنة على نسخة واحدة بعينها:",[277,6676,6680],{"className":6677,"code":6678,"language":6679,"meta":282,"style":282},"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",[14,6681,6682,6689,6718,6748,6763,6778],{"__ignoreMap":282},[286,6683,6684,6687],{"class":288,"line":289},[286,6685,6686],{"class":302},".tenant-acme",[286,6688,475],{"class":306},[286,6690,6691,6694,6697,6700,6702,6705,6708,6711,6714,6716],{"class":288,"line":356},[286,6692,6693],{"class":316},"  --ak-skel-base",[286,6695,6696],{"class":306},": ",[286,6698,6699],{"class":323},"hsl",[286,6701,467],{"class":306},[286,6703,6704],{"class":323},"220",[286,6706,6707],{"class":323}," 30",[286,6709,6710],{"class":292},"%",[286,6712,6713],{"class":323}," 18",[286,6715,6710],{"class":292},[286,6717,792],{"class":306},[286,6719,6720,6723,6725,6727,6729,6731,6734,6736,6738,6740,6743,6746],{"class":288,"line":376},[286,6721,6722],{"class":316},"  --ak-skel-highlight",[286,6724,6696],{"class":306},[286,6726,6699],{"class":323},[286,6728,467],{"class":306},[286,6730,6704],{"class":323},[286,6732,6733],{"class":323}," 60",[286,6735,6710],{"class":292},[286,6737,6733],{"class":323},[286,6739,6710],{"class":292},[286,6741,6742],{"class":306}," \u002F ",[286,6744,6745],{"class":323},"0.35",[286,6747,792],{"class":306},[286,6749,6750,6753,6755,6758,6761],{"class":288,"line":415},[286,6751,6752],{"class":316},"  --ak-skel-radius",[286,6754,6696],{"class":306},[286,6756,6757],{"class":323},"0.5",[286,6759,6760],{"class":292},"rem",[286,6762,487],{"class":306},[286,6764,6765,6768,6770,6773,6776],{"class":288,"line":441},[286,6766,6767],{"class":316},"  --ak-skel-duration",[286,6769,6696],{"class":306},[286,6771,6772],{"class":323},"2",[286,6774,6775],{"class":292},"s",[286,6777,487],{"class":306},[286,6779,6780],{"class":288,"line":543},[286,6781,444],{"class":306},[10,6783,6784,6785,6788,6789,6792,6793,6796,6797,6799,6800,6803],{},"الوضع الداكن يعمل تلقائيًا عبر محدّدات ",[14,6786,6787],{},":where(.dark)"," التي تعيد تلوين الرموز (فئة ",[14,6790,6791],{},".dark"," الخاصة بـ Tailwind، وفئة shadcn، وفئة ",[14,6794,6795],{},"nuxt-color-mode","، وأيّ شيء يضع سلفًا يحمل ",[14,6798,6791],{}," في الشجرة)، إضافةً إلى نسخة احتياطية عبر ",[14,6801,6802],{},"@media (prefers-color-scheme: dark)"," للحالة التي لا توجد فيها فئة صريحة على الشجرة.",[10,6805,6806],{},"المقصد من هذا التصميم أن لا يضطر مستهلك المكتبة يومًا إلى الكتابة فوق فئة داخلية. كل قرار بصري هو رمز؛ والرموز موروثة عبر الـ cascade؛ والكتابة فوقها لنسخة بعينها تكون بأنماط مضمّنة. المكوّن يملك البنية والحركة، وأنت تملك المظهر.",[93,6808,6810],{"id":6809},"a11y-لا-تحتاج-إلى-تكوينها","a11y لا تحتاج إلى تكوينها",[10,6812,6813,6814,6817,6818,6821,6822,6825,6826,6829],{},"الإعدادات الافتراضية هي ذاتها التي كنتَ ستريدها على أيّ حال. يحمل كل wrapper وكل جذر متغيّر السمة ",[14,6815,6816],{},"role=\"status\""," أثناء التحميل، وسمة ",[14,6819,6820],{},"aria-busy=\"true\""," تعكس حالة التحميل، وسمة ",[14,6823,6824],{},"aria-live=\"polite\""," كي تُعلن قارئات الشاشة حالة التحميل دون مقاطعة المستخدم. يُشحن ",[14,6827,6828],{},"\u003Cspan class=\"a-skel-sr-only\">Loading...\u003C\u002Fspan>"," مخفيّ بصريًا مع كل متغيّر ليكون للإعلان نصّ يقرأه.",[10,6831,6832,6833,6836,6837,25,6840,6843,6844,6847],{},"يحمل كل سطح shimmer مُصدَر السمة ",[14,6834,6835],{},"aria-hidden=\"true\"","، لأن الـ placeholder زخرفة والـ wrapper هو ما يُعلَن. تعطّل skeletons وضع mirror الخصائص ",[14,6838,6839],{},"user-select",[14,6841,6842],{},"pointer-events"," على شجرة الـ slot كي لا يستطيع أحد التفاعل مع الـ placeholder في منتصف التحميل. ويزيل ",[14,6845,6846],{},"prefers-reduced-motion"," الحركة كلها.",[10,6849,6850,6851,6854],{},"لا شيء من هذا قابل للتكوين. ولا ينبغي أن يكون. الإعدادات الافتراضية هي الإجابة الصحيحة. لو احتجتَ إلى إيقافها، فأنت إما تختبر شيئًا محدّدًا (وفي هذه الحالة ",[14,6852,6853],{},"animation=\"none\""," هو المفتاح المناسب)، أو تحاول شيئًا على مستوى الـ a11y لا أرغب أنا في أن أجعله سهلًا.",[93,6856,6858],{"id":6857},"ما-علّمني-إياه-وضع-clone","ما علّمني إياه وضع Clone",[10,6860,6861,6862,6865,6866,6869],{},"الشيء الذي سأحمله معي إلى المشكلة التالية ليس الـ API. بل هي البصيرة التي يقوم عليها وضع clone. تستطيع أن تتوقف عن التفكير على مستوى الـ cascade. تستطيع أن تتوقف عن محاولة التنبّؤ بما تنتجه فعلًا قيمة ",[14,6863,6864],{},"border-radius: 0.75rem"," مخلوطة بـ ",[14,6867,6868],{},"rounded-2xl"," مخلوطة بكتابة فوقية scoped. ركّب الشيء. دع المتصفح يطوي كل المدخلات. اقرأ الإجابة النهائية. أعِد عرض الإجابة النهائية.",[10,6871,6872,6873,6875],{},"هذا المبدأ ينطبق في كل موضع لديك فيه مشكلة \"أحتاج نسخة من هذا العنصر\". معاينات الطباعة. معاينات السحب. معارض المكوّنات التي تعرض الـ widget نفسه في خمسة أوضاع مختلفة. نهج \"انظر إلى ما هو موجود فعلًا على الشاشة، لا إلى القواعد التي أنتجته\" يمتدّ أبعد مما يُعطى من فضل. كل خط أنابيب CSS، وكل متغيّر، وكل أولوية cascade، وكل إطار حركة، ينتهي إلى رقم واحد لكل خاصية لكل عنصر، وذلك الرقم هو ما سيخبرك به ",[14,6874,5340],{}," متى ما سألته.",[10,6877,6878],{},"الشيء الآخر الذي أثبت جدواه فعلًا هو عقد التأليف. وسوم غير مشروطة، ومحتوى عبر الاستيفاء. بمجرد أن ترسخ هذه الفكرة في ذهنك، يصبح كل مكوّن تبنيه جاهزًا للـ skeleton تلقائيًا: لا قالب إضافي، ولا حالة موازية، ولا عبء صيانة. المكتبة تقرأ مكوّنك الحقيقي، وأنت تكتبه بالطريقة التي كنتَ تريدها أصلًا.",[10,6880,6881],{},"فكرتان اثنتان، وحزمة واحدة. المستودع مرخّص برخصة MIT، وستجد في الـ README المرجع الطويل لكل prop وslot لو أردتَ التعمّق أكثر. لو رأيتَ شيئًا يمكن أن يكون أكثر حدّة، افتح issue من فضلك. أفضّل بصدق أن أُصحَّح على أن أبقى مرتاحًا.",[5100,6883,6884],{},"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":282,"searchDepth":356,"depth":356,"links":6886},[6887,6888,6889,6890,6891,6892,6893,6894,6895],{"id":5169,"depth":356,"text":5170},{"id":5318,"depth":356,"text":5319},{"id":5534,"depth":356,"text":5535},{"id":5624,"depth":356,"text":5625},{"id":6276,"depth":356,"text":6277},{"id":6372,"depth":356,"text":6373},{"id":6523,"depth":356,"text":6524},{"id":6809,"depth":356,"text":6810},{"id":6857,"depth":356,"text":6858},"2026-06-13","كيف تستخرج a-skeleton واجهة الانتظار من المكوّن نفسه. وضع Clone، ووضع Structural، ووضع Mirror، ومسار العمل الذي يلتقط ثم يُخزّن مؤقّتاً ثم يُعيد العرض خلفها.",[6899,6900,6901,6902,6903,6904,6295,5141,6905,6906],"Vue 3","Nuxt","مُحمِّل Skeleton","حالات التحميل","getComputedStyle","Range API","مكتبة مكوّنات","إمكانية الوصول",{},"\u002Fwrites-ar\u002Finside-a-skeleton-self-generating-skeleton-loaders",{"title":5151,"description":6897},"writes-ar\u002Finside-a-skeleton-self-generating-skeleton-loaders","YF9r0lB5uhmEc4wHqor5kRgfbA9lR_dGeOudu2h0SQg",{"id":6913,"title":6914,"body":6915,"date":11353,"description":11354,"description_ar":5129,"draft":5130,"extension":5131,"image":5129,"keywords":11355,"lang":5142,"meta":11362,"navigation":546,"path":11363,"seo":11364,"stem":11365,"title_ar":5129,"updatedAt":11353,"__hash__":11366},"writesAr\u002Fwrites-ar\u002Fatell-input-the-library-rewrite.md","ملاحظات من إعادة كتابة ATellInput كمكتبة مُنشَرة",{"type":7,"value":6916,"toc":11343},[6917,6928,6931,6938,6941,6945,6948,7007,7022,7025,7042,7045,7132,7154,7294,7311,7322,7325,7329,7332,7420,7430,7444,7450,7964,7967,7995,8018,8036,8039,8042,8217,8231,8303,8306,8310,8317,8323,8647,8650,8718,8721,9029,9057,9064,9107,9118,9121,9204,9210,9214,9217,9228,9338,9360,9363,9367,9370,9373,9934,9976,9983,10122,10136,10139,10269,10272,10275,10407,10430,10433,10437,10466,10469,10701,10708,10932,10935,11106,11113,11116,11120,11126,11131,11253,11263,11266,11270,11281,11284,11287,11330,11333,11340],[10,6918,6919,6920,6923,6924,6927],{},"وُلد ",[14,6921,6922],{},"ATellInput"," داخل تطبيق واحد فقط: قيمة احتياطية تميل إلى السعودية أو مصر، ونداء داخلي لـ ",[14,6925,6926],{},"RestCountries","، وتحقّق يظهر عبر v-model لا غير، وقائمة منسدلة ظاهرة على الشاشة طوال الوقت. حلّت تلك النسخة مسألة الصحّة: اضطراب موضع المؤشّر، وأسباب التحقّق، ودعم RTL\u002FLTR. وأنجزت معظم ما يحتاجه مستهلك واحد.",[10,6929,6930],{},"هذه التدوينة تحكي ما جرى حين حاولتُ نشر المكوّن.",[10,6932,6933,6934,6937],{},"يعيش المكوّن الآن في ",[14,6935,6936],{},"@alikhalilll\u002Fui\u002Ftell-input","، ومهمّة المكتبة المُنشَرة تختلف كلياً. لا تستطيع أن تُشفِّر السوق الذي تخدمه داخل الكود. ولا أن تفترض شكل نظام تصميم المستهلك. ولا أن تُلزم كلّ نقطة تركيب بانتظار واجهة برمجية طرف ثالث. ولا حتى أن تعرف مسبقاً إن كان المستخدم على هاتف أم على حاسوب. حافظت إعادة الكتابة على صحّة النسخة الأصلية وأضافت ما يحتاجه مستهلك المكتبة فعلياً، وكلّ تغيير تقريباً وراءه علّة محدّدة أو قصور واضح في النسخة القديمة.",[10,6939,6940],{},"جمعتُ التغييرات حول السبعة التي استحقّت مكانها.",[93,6942,6944],{"id":6943},"نموذجان-بشكلين-مختلفين-الرقم-للخارج-وiso2-في-الداخل","نموذجان بشكلين مختلفين: الرقم للخارج، وISO2 في الداخل",[10,6946,6947],{},"بدت الواجهة القديمة أنيقة من بعيد:",[277,6949,6951],{"className":279,"code":6950,"language":281,"meta":282,"style":282},"const phoneModelValue = defineModel\u003Cstring>('phone', { required: true });\nconst countryModelValue = defineModel\u003Cstring>('country', { required: true });\n",[14,6952,6953,6981],{"__ignoreMap":282},[286,6954,6955,6957,6960,6962,6965,6967,6969,6971,6974,6977,6979],{"class":288,"line":289},[286,6956,773],{"class":292},[286,6958,6959],{"class":323}," phoneModelValue",[286,6961,338],{"class":292},[286,6963,6964],{"class":302}," defineModel",[286,6966,307],{"class":306},[286,6968,503],{"class":323},[286,6970,313],{"class":306},[286,6972,6973],{"class":899},"'phone'",[286,6975,6976],{"class":306},", { required: ",[286,6978,980],{"class":323},[286,6980,983],{"class":306},[286,6982,6983,6985,6988,6990,6992,6994,6996,6998,7001,7003,7005],{"class":288,"line":356},[286,6984,773],{"class":292},[286,6986,6987],{"class":323}," countryModelValue",[286,6989,338],{"class":292},[286,6991,6964],{"class":302},[286,6993,307],{"class":306},[286,6995,503],{"class":323},[286,6997,313],{"class":306},[286,6999,7000],{"class":899},"'country'",[286,7002,6976],{"class":306},[286,7004,980],{"class":323},[286,7006,983],{"class":306},[10,7008,7009,7010,7013,7014,7017,7018,7021],{},"كان ",[14,7011,7012],{},"country"," سلسلة نصية تحوي أرقام الاتصال: ",[14,7015,7016],{},"\"20\""," لمصر، و",[14,7019,7020],{},"\"44\""," للمملكة المتحدة. سهل التحويل إلى JSON، وسهل ترميزه في URL، وسهل إسكانه في عمود قاعدة بيانات.",[10,7023,7024],{},"المشكلة أنّه عجز عن التفريق بين الولايات المتحدة وكندا.",[10,7026,7027,7028,7031,7032,7035,7036,7038,7039,7041],{},"يتقاسم الرمز ",[14,7029,7030],{},"+1"," أكثر من 25 دولة ضمن NANP. لو اختار مستخدم كندي \"كندا\" من القائمة وأصدر v-model القيمة ",[14,7033,7034],{},"\"1\"",", فلن يعرف الأب أيّ ",[14,7037,7030],{}," هو المقصود. حدّث الصفحة، واستعد الحالة من URL، ستعود إليك ",[14,7040,7034],{}," بينما يعرض المنتقي أوّل دولة صادف أن ترتّبها القائمة. النموذج الذي يختصر الدولة في \"سلسلة رموز اتصال\" كان يُفقد نصف المعلومة عن شيء من قطعتين.",[10,7043,7044],{},"النموذج الجديد يفصل بين القطعتين:",[277,7046,7048],{"className":279,"code":7047,"language":281,"meta":282,"style":282},"const phone = defineModel\u003Cstring>('phone', { default: '' });\nconst country = defineModel\u003Cnumber | null>('country', { default: null });\n\nconst selectedIso2 = ref\u003Cstring>('');\n",[14,7049,7050,7077,7107,7111],{"__ignoreMap":282},[286,7051,7052,7054,7057,7059,7061,7063,7065,7067,7069,7072,7075],{"class":288,"line":289},[286,7053,773],{"class":292},[286,7055,7056],{"class":323}," phone",[286,7058,338],{"class":292},[286,7060,6964],{"class":302},[286,7062,307],{"class":306},[286,7064,503],{"class":323},[286,7066,313],{"class":306},[286,7068,6973],{"class":899},[286,7070,7071],{"class":306},", { default: ",[286,7073,7074],{"class":899},"''",[286,7076,983],{"class":306},[286,7078,7079,7081,7084,7086,7088,7090,7093,7095,7097,7099,7101,7103,7105],{"class":288,"line":356},[286,7080,773],{"class":292},[286,7082,7083],{"class":323}," country",[286,7085,338],{"class":292},[286,7087,6964],{"class":302},[286,7089,307],{"class":306},[286,7091,7092],{"class":323},"number",[286,7094,852],{"class":292},[286,7096,2231],{"class":323},[286,7098,313],{"class":306},[286,7100,7000],{"class":899},[286,7102,7071],{"class":306},[286,7104,2808],{"class":323},[286,7106,983],{"class":306},[286,7108,7109],{"class":288,"line":376},[286,7110,547],{"emptyLinePlaceholder":546},[286,7112,7113,7115,7118,7120,7122,7124,7126,7128,7130],{"class":288,"line":415},[286,7114,773],{"class":292},[286,7116,7117],{"class":323}," selectedIso2",[286,7119,338],{"class":292},[286,7121,5727],{"class":302},[286,7123,307],{"class":306},[286,7125,503],{"class":323},[286,7127,313],{"class":306},[286,7129,7074],{"class":899},[286,7131,792],{"class":306},[10,7133,7134,7135,7137,7138,327,7141,327,7143,7145,7146,7149,7150,7153],{},"صار ",[14,7136,7012],{}," رقماً (",[14,7139,7140],{},"20",[14,7142,1524],{},[14,7144,2808],{},") لأنّ رموز الاتصال أرقامٌ في جوهرها، ولأنّ ",[14,7147,7148],{},"Number"," ينجو من رحلات URL وDB وJSON بلا أيّ التباس ناتج عن اقتطاع الأحرف. غير أنّ المكوّن يتتبّع داخلياً أيضاً ",[14,7151,7152],{},"selectedIso2"," يملكه المنتقي وحده. تبقى القيمتان متزامنتين عبر watcher-ين، أحدهما للخارج والآخر للداخل، والداخلي منهما فيه حارس صغير لكنّه حاسم:",[277,7155,7157],{"className":279,"code":7156,"language":281,"meta":282,"style":282},"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",[14,7158,7159,7166,7171,7185,7199,7213,7219,7223,7244,7264,7276,7281,7290],{"__ignoreMap":282},[286,7160,7161,7164],{"class":288,"line":289},[286,7162,7163],{"class":302},"watch",[286,7165,2836],{"class":306},[286,7167,7168],{"class":288,"line":356},[286,7169,7170],{"class":306},"  country,\n",[286,7172,7173,7176,7179,7181,7183],{"class":288,"line":376},[286,7174,7175],{"class":306},"  (",[286,7177,7178],{"class":316},"next",[286,7180,1115],{"class":306},[286,7182,953],{"class":292},[286,7184,475],{"class":306},[286,7186,7187,7189,7192,7195,7197],{"class":288,"line":415},[286,7188,682],{"class":292},[286,7190,7191],{"class":306}," (next ",[286,7193,7194],{"class":292},"==",[286,7196,2231],{"class":323},[286,7198,1898],{"class":306},[286,7200,7201,7203,7206,7208,7211],{"class":288,"line":441},[286,7202,1942],{"class":292},[286,7204,7205],{"class":306}," (selectedIso2.value) selectedIso2.value ",[286,7207,537],{"class":292},[286,7209,7210],{"class":899}," ''",[286,7212,487],{"class":306},[286,7214,7215,7217],{"class":288,"line":543},[286,7216,1910],{"class":292},[286,7218,487],{"class":306},[286,7220,7221],{"class":288,"line":550},[286,7222,1987],{"class":306},[286,7224,7225,7227,7229,7232,7235,7237,7240,7242],{"class":288,"line":595},[286,7226,682],{"class":292},[286,7228,382],{"class":306},[286,7230,7231],{"class":302},"dialNumberFor",[286,7233,7234],{"class":306},"(selectedIso2.value) ",[286,7236,3935],{"class":292},[286,7238,7239],{"class":306}," next) ",[286,7241,871],{"class":292},[286,7243,487],{"class":306},[286,7245,7246,7248,7251,7253,7256,7258,7261],{"class":288,"line":620},[286,7247,598],{"class":292},[286,7249,7250],{"class":323}," iso2",[286,7252,338],{"class":292},[286,7254,7255],{"class":302}," resolveCountryIdentifier",[286,7257,467],{"class":306},[286,7259,7260],{"class":302},"String",[286,7262,7263],{"class":306},"(next));\n",[286,7265,7266,7268,7271,7273],{"class":288,"line":658},[286,7267,682],{"class":292},[286,7269,7270],{"class":306}," (iso2) selectedIso2.value ",[286,7272,537],{"class":292},[286,7274,7275],{"class":306}," iso2;\n",[286,7277,7278],{"class":288,"line":679},[286,7279,7280],{"class":306},"  },\n",[286,7282,7283,7286,7288],{"class":288,"line":707},[286,7284,7285],{"class":306},"  { immediate: ",[286,7287,980],{"class":323},[286,7289,4105],{"class":306},[286,7291,7292],{"class":288,"line":729},[286,7293,792],{"class":306},[10,7295,7296,7297,7300,7301,7304,7305,7307,7308,7310],{},"السطر ",[14,7298,7299],{},"if (dialNumberFor(selectedIso2.value) === next) return;"," هو ما أودّ التوقّف عنده. حين يكتب الأب ",[14,7302,7303],{},"country = 1"," من جديد إلى النموذج والمنتقي يعرض كندا مختارة أصلاً، ينطلق الـ watch, لكنّ رمز الاتصال متطابق مسبقاً، فيعود دون أن يمسّ ",[14,7306,7152],{},".\nلولا هذا الحارس، لكان الـ watcher حلّ ",[14,7309,7034],{}," إلى أوّل دولة NANP أبجدياً وقلب كندا بصمت إلى أيّاً كانت تلك الدولة. الحارس يحفظ الحالة الأغنى.",[10,7312,7313,7314,7317,7318,7321],{},"الـ watch الخارجي يعمل بوضع ",[14,7315,7316],{},"flush: 'sync'"," كي يبقى العلَم ",[14,7319,7320],{},"autoSettingCountry"," متّسقاً. لأنّه حين يختار المطابِق دولةً تلقائياً بناءً على ما كُتب، لا نريد اعتبار ذلك \"اختياراً يدوياً\" يُقفل الاكتشاف التلقائي لاحقاً.",[10,7323,7324],{},"نصفان من بيانات الدولة، ونموذجان مستقلّان، ومصدر حقيقة واحد لكلٍّ منهما.",[93,7326,7328],{"id":7327},"تجربة-تقوم-على-الاكتشاف-أولاً-المنتقي-مخفي-افتراضياً","تجربة تقوم على الاكتشاف أولاً: المنتقي مخفي افتراضياً",[10,7330,7331],{},"كان المكوّن القديم يرسم القائمة دائماً: العلَم يساراً، وحقل الإدخال يميناً. أمّا المكوّن الجديد فيكتفي بحقل الإدخال وحده افتراضياً. تنزلق القائمة إلى الظهور فقط حين يكتب المستخدم شيئاً يتعرّف عليه المطابِق:",[277,7333,7335],{"className":5181,"code":7334,"language":5183,"meta":282,"style":282},"\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",[14,7336,7337,7344,7354,7364,7374,7383,7393,7402,7406,7411],{"__ignoreMap":282},[286,7338,7339,7341],{"class":288,"line":289},[286,7340,307],{"class":306},[286,7342,7343],{"class":5192},"Transition\n",[286,7345,7346,7349,7351],{"class":288,"line":356},[286,7347,7348],{"class":302},"  enter-active-class",[286,7350,537],{"class":306},[286,7352,7353],{"class":899},"\"transition-all duration-200 ease-out overflow-hidden\"\n",[286,7355,7356,7359,7361],{"class":288,"line":376},[286,7357,7358],{"class":302},"  leave-active-class",[286,7360,537],{"class":306},[286,7362,7363],{"class":899},"\"transition-all duration-150 ease-in overflow-hidden\"\n",[286,7365,7366,7369,7371],{"class":288,"line":415},[286,7367,7368],{"class":302},"  enter-from-class",[286,7370,537],{"class":306},[286,7372,7373],{"class":899},"\"opacity-0 -translate-x-1 max-w-0\"\n",[286,7375,7376,7379,7381],{"class":288,"line":441},[286,7377,7378],{"class":302},"  leave-to-class",[286,7380,537],{"class":306},[286,7382,7373],{"class":899},[286,7384,7385,7388,7390],{"class":288,"line":543},[286,7386,7387],{"class":302},"  enter-to-class",[286,7389,537],{"class":306},[286,7391,7392],{"class":899},"\"max-w-[12rem]\"\n",[286,7394,7395,7398,7400],{"class":288,"line":550},[286,7396,7397],{"class":302},"  leave-from-class",[286,7399,537],{"class":306},[286,7401,7392],{"class":899},[286,7403,7404],{"class":288,"line":595},[286,7405,5209],{"class":306},[286,7407,7408],{"class":288,"line":620},[286,7409,7410],{"class":306},"  \u003CACountrySelect v-if=\"!props.detectFromInput || selectedIso2\" ... \u002F>\n",[286,7412,7413,7415,7418],{"class":288,"line":658},[286,7414,5224],{"class":306},[286,7416,7417],{"class":5192},"Transition",[286,7419,5209],{"class":306},[10,7421,7422,7423,7426,7427,60],{},"يتقلّص ",[14,7424,7425],{},"max-w"," إلى صفر ما لم يكن هناك اختيار، ثم يتحرّك ليتفتّح عند أوّل تطابق ناجح. ومن يريد الشكل القديم يستطيع إعادة المنتقي الظاهر دائماً عبر ",[14,7428,7429],{},"detectFromInput=\"false\"",[10,7431,7432,7433,7435,7436,7439,7440,7443],{},"هذا سلوك ينحاز إلى رأي واضح. الأغلبية العظمى من المستخدمين في معظم الدول لا يكتبون بادئة ",[14,7434,4639],{}," أبداً. بل يكتبون الرقم كما يكتبونه في هاتفهم: ",[14,7437,7438],{},"01066105963"," في القاهرة، و",[14,7441,7442],{},"07911 123456"," في مانشستر. القائمة التي \"يُجبَرون على التعامل معها\" في كلّ حقل هاتف آخر خطوة زائدة بالنسبة لهم. أخفِها إذاً. وراقب ما يكتبونه. ولا تُظهرها إلا حين يقع الاكتشاف على نتيجة.",[10,7445,7446,7447,320],{},"الجزء الوحيد الذي فيه بعض التعقيد هو \"مراقبة ما يكتبونه\". يمرّ عبر ثلاث طبقات في ",[14,7448,7449],{},"matchLeadingDialCode",[277,7451,7453],{"className":279,"code":7452,"language":281,"meta":282,"style":282},"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",[14,7454,7455,7484,7501,7505,7510,7517,7540,7552,7567,7574,7593,7597,7601,7610,7615,7619,7623,7628,7640,7663,7669,7689,7702,7722,7729,7746,7750,7754,7762,7767,7771,7775,7779,7784,7828,7848,7863,7882,7902,7928,7933,7938,7948,7952,7960],{"__ignoreMap":282},[286,7456,7457,7459,7462,7464,7467,7469,7471,7473,7475,7478,7480,7482],{"class":288,"line":289},[286,7458,820],{"class":292},[286,7460,7461],{"class":302}," matchLeadingDialCode",[286,7463,467],{"class":306},[286,7465,7466],{"class":316},"digits",[286,7468,320],{"class":292},[286,7470,324],{"class":323},[286,7472,845],{"class":306},[286,7474,320],{"class":292},[286,7476,7477],{"class":302}," DialMatch",[286,7479,852],{"class":292},[286,7481,2231],{"class":323},[286,7483,475],{"class":306},[286,7485,7486,7488,7490,7492,7495,7497,7499],{"class":288,"line":356},[286,7487,379],{"class":292},[286,7489,382],{"class":306},[286,7491,385],{"class":292},[286,7493,7494],{"class":306},"digits) ",[286,7496,871],{"class":292},[286,7498,2231],{"class":323},[286,7500,487],{"class":306},[286,7502,7503],{"class":288,"line":376},[286,7504,547],{"emptyLinePlaceholder":546},[286,7506,7507],{"class":288,"line":415},[286,7508,7509],{"class":921},"  \u002F\u002F Tier 1: international parse, disambiguates NANP and strips the calling code.\n",[286,7511,7512,7515],{"class":288,"line":441},[286,7513,7514],{"class":292},"  try",[286,7516,475],{"class":306},[286,7518,7519,7521,7524,7526,7529,7531,7534,7536,7538],{"class":288,"line":543},[286,7520,598],{"class":292},[286,7522,7523],{"class":323}," parsed",[286,7525,338],{"class":292},[286,7527,7528],{"class":302}," parsePhoneNumberFromString",[286,7530,467],{"class":306},[286,7532,7533],{"class":899},"`+${",[286,7535,7466],{"class":306},[286,7537,1691],{"class":899},[286,7539,792],{"class":306},[286,7541,7542,7544,7547,7549],{"class":288,"line":550},[286,7543,682],{"class":292},[286,7545,7546],{"class":306}," (parsed?.country ",[286,7548,2768],{"class":292},[286,7550,7551],{"class":306}," parsed.countryCallingCode) {\n",[286,7553,7554,7556,7559,7561,7564],{"class":288,"line":595},[286,7555,2993],{"class":292},[286,7557,7558],{"class":323}," parsedCountry",[286,7560,338],{"class":292},[286,7562,7563],{"class":302}," getCountryByValue",[286,7565,7566],{"class":306},"(parsed.country);\n",[286,7568,7569,7571],{"class":288,"line":620},[286,7570,1942],{"class":292},[286,7572,7573],{"class":306}," (parsedCountry) {\n",[286,7575,7576,7578,7581,7583,7586,7588,7590],{"class":288,"line":658},[286,7577,3962],{"class":292},[286,7579,7580],{"class":306}," { country: parsedCountry, nationalNumber: ",[286,7582,7260],{"class":302},[286,7584,7585],{"class":306},"(parsed.nationalNumber ",[286,7587,1304],{"class":292},[286,7589,7210],{"class":899},[286,7591,7592],{"class":306},") };\n",[286,7594,7595],{"class":288,"line":679},[286,7596,3566],{"class":306},[286,7598,7599],{"class":288,"line":707},[286,7600,1987],{"class":306},[286,7602,7603,7606,7608],{"class":288,"line":729},[286,7604,7605],{"class":306},"  } ",[286,7607,1924],{"class":292},[286,7609,475],{"class":306},[286,7611,7612],{"class":288,"line":735},[286,7613,7614],{"class":921},"    \u002F* libphonenumber throws on partial input, fall through *\u002F\n",[286,7616,7617],{"class":288,"line":1376},[286,7618,1452],{"class":306},[286,7620,7621],{"class":288,"line":1401},[286,7622,547],{"emptyLinePlaceholder":546},[286,7624,7625],{"class":288,"line":1428},[286,7626,7627],{"class":921},"  \u002F\u002F Tier 2: national-format parse using the silently-inferred country as a hint.\n",[286,7629,7630,7632,7635,7637],{"class":288,"line":1449},[286,7631,359],{"class":292},[286,7633,7634],{"class":323}," hint",[286,7636,338],{"class":292},[286,7638,7639],{"class":306}," inferredCountry.value;\n",[286,7641,7642,7644,7647,7649,7652,7655,7658,7661],{"class":288,"line":1455},[286,7643,379],{"class":292},[286,7645,7646],{"class":306}," (hint ",[286,7648,2768],{"class":292},[286,7650,7651],{"class":306}," digits.",[286,7653,7654],{"class":323},"length",[286,7656,7657],{"class":292}," >=",[286,7659,7660],{"class":323}," 4",[286,7662,1898],{"class":306},[286,7664,7665,7667],{"class":288,"line":1460},[286,7666,1903],{"class":292},[286,7668,475],{"class":306},[286,7670,7671,7673,7675,7677,7679,7682,7684,7687],{"class":288,"line":1486},[286,7672,2993],{"class":292},[286,7674,7523],{"class":323},[286,7676,338],{"class":292},[286,7678,7528],{"class":302},[286,7680,7681],{"class":306},"(digits, hint ",[286,7683,429],{"class":292},[286,7685,7686],{"class":302}," CountryCode",[286,7688,792],{"class":306},[286,7690,7691,7693,7696,7699],{"class":288,"line":1498},[286,7692,1942],{"class":292},[286,7694,7695],{"class":306}," (parsed?.",[286,7697,7698],{"class":302},"isValid",[286,7700,7701],{"class":306},"()) {\n",[286,7703,7704,7707,7710,7712,7714,7717,7719],{"class":288,"line":1529},[286,7705,7706],{"class":292},"        const",[286,7708,7709],{"class":323}," matched",[286,7711,338],{"class":292},[286,7713,7563],{"class":302},[286,7715,7716],{"class":306},"(parsed.country ",[286,7718,2228],{"class":292},[286,7720,7721],{"class":306}," hint);\n",[286,7723,7724,7726],{"class":288,"line":1534},[286,7725,4218],{"class":292},[286,7727,7728],{"class":306}," (matched) {\n",[286,7730,7731,7733,7736,7738,7740,7742,7744],{"class":288,"line":1558},[286,7732,4249],{"class":292},[286,7734,7735],{"class":306}," { country: matched, nationalNumber: ",[286,7737,7260],{"class":302},[286,7739,7585],{"class":306},[286,7741,1304],{"class":292},[286,7743,7210],{"class":899},[286,7745,7592],{"class":306},[286,7747,7748],{"class":288,"line":1568},[286,7749,4110],{"class":306},[286,7751,7752],{"class":288,"line":1595},[286,7753,3566],{"class":306},[286,7755,7756,7758,7760],{"class":288,"line":1600},[286,7757,1921],{"class":306},[286,7759,1924],{"class":292},[286,7761,475],{"class":306},[286,7763,7764],{"class":288,"line":1605},[286,7765,7766],{"class":921},"      \u002F* fall through *\u002F\n",[286,7768,7769],{"class":288,"line":1613},[286,7770,1987],{"class":306},[286,7772,7773],{"class":288,"line":4187},[286,7774,1452],{"class":306},[286,7776,7777],{"class":288,"line":4207},[286,7778,547],{"emptyLinePlaceholder":546},[286,7780,7781],{"class":288,"line":4215},[286,7782,7783],{"class":921},"  \u002F\u002F Tier 3: longest-prefix match over our own dial-digits index.\n",[286,7785,7786,7788,7790,7792,7795,7797,7800,7803,7805,7808,7811,7813,7816,7818,7820,7823,7826],{"class":288,"line":4224},[286,7787,1870],{"class":292},[286,7789,382],{"class":306},[286,7791,1875],{"class":292},[286,7793,7794],{"class":306}," len ",[286,7796,537],{"class":292},[286,7798,7799],{"class":306}," Math.",[286,7801,7802],{"class":302},"min",[286,7804,467],{"class":306},[286,7806,7807],{"class":323},"3",[286,7809,7810],{"class":306},", digits.",[286,7812,7654],{"class":323},[286,7814,7815],{"class":306},"); len ",[286,7817,3049],{"class":292},[286,7819,4729],{"class":323},[286,7821,7822],{"class":306},"; len",[286,7824,7825],{"class":292},"--",[286,7827,1898],{"class":306},[286,7829,7830,7832,7835,7837,7839,7841,7843,7845],{"class":288,"line":4246},[286,7831,598],{"class":292},[286,7833,7834],{"class":323}," prefix",[286,7836,338],{"class":292},[286,7838,7651],{"class":306},[286,7840,4697],{"class":302},[286,7842,467],{"class":306},[286,7844,2679],{"class":323},[286,7846,7847],{"class":306},", len);\n",[286,7849,7850,7852,7855,7857,7860],{"class":288,"line":4254},[286,7851,598],{"class":292},[286,7853,7854],{"class":323}," group",[286,7856,338],{"class":292},[286,7858,7859],{"class":302}," getCountriesByDial",[286,7861,7862],{"class":306},"(prefix);\n",[286,7864,7865,7867,7869,7871,7874,7876,7878,7880],{"class":288,"line":4259},[286,7866,682],{"class":292},[286,7868,382],{"class":306},[286,7870,385],{"class":292},[286,7872,7873],{"class":306},"group.",[286,7875,7654],{"class":323},[286,7877,1115],{"class":306},[286,7879,4681],{"class":292},[286,7881,487],{"class":306},[286,7883,7884,7886,7889,7891,7893,7895,7898,7900],{"class":288,"line":4277},[286,7885,598],{"class":292},[286,7887,7888],{"class":323}," nationalNumber",[286,7890,338],{"class":292},[286,7892,7651],{"class":306},[286,7894,4697],{"class":302},[286,7896,7897],{"class":306},"(prefix.",[286,7899,7654],{"class":323},[286,7901,792],{"class":306},[286,7903,7904,7906,7909,7911,7914,7916,7918,7920,7923,7925],{"class":288,"line":4282},[286,7905,682],{"class":292},[286,7907,7908],{"class":306}," (group.",[286,7910,7654],{"class":323},[286,7912,7913],{"class":292}," ===",[286,7915,4729],{"class":323},[286,7917,1115],{"class":306},[286,7919,871],{"class":292},[286,7921,7922],{"class":306}," { country: group[",[286,7924,2679],{"class":323},[286,7926,7927],{"class":306},"], nationalNumber };\n",[286,7929,7930],{"class":288,"line":4287},[286,7931,7932],{"class":921},"    \u002F\u002F ambiguity: prefer current selection, then recents, then first\n",[286,7934,7935],{"class":288,"line":4302},[286,7936,7937],{"class":921},"    \u002F\u002F (see \"Recents as the tiebreaker\" below)\n",[286,7939,7940,7942,7944,7946],{"class":288,"line":4320},[286,7941,710],{"class":292},[286,7943,7922],{"class":306},[286,7945,2679],{"class":323},[286,7947,7927],{"class":306},[286,7949,7950],{"class":288,"line":4345},[286,7951,1452],{"class":306},[286,7953,7954,7956,7958],{"class":288,"line":4351},[286,7955,418],{"class":292},[286,7957,2231],{"class":323},[286,7959,487],{"class":306},[286,7961,7962],{"class":288,"line":4357},[286,7963,444],{"class":306},[10,7965,7966],{},"توجد الطبقات لأنّ لكلّ واحدة منها فئة من المدخلات تعجز الطبقتان الأخريان عن معالجتها.",[10,7968,7969,7970,7972,7973,7976,7977,7980,7981,7984,7985,7987,7988,7990,7991,7994],{},"تأخذ الطبقة الأولى ما كتبه المستخدم، وتُلحق قبله ",[14,7971,4639],{},", ثم تطلب من ",[14,7974,7975],{},"libphonenumber"," تحليله بوصفه رقماً دولياً. إن كتب المستخدم ",[14,7978,7979],{},"447911123456"," سيتطابق مع المملكة المتحدة. وإن كتب ",[14,7982,7983],{},"1416..."," سيتطابق مع كندا تحديداً (لا مع NANP العام)، لأنّ ",[14,7986,7975],{}," يحمل قواعد رموز المناطق مدمجة أصلاً. توجد كتلة catch لأنّ ",[14,7989,7975],{}," يرمي استثناءً على الإدخال الجزئي. ",[14,7992,7993],{},"447"," رقم ناقص؛ يرفع التحليل استثناءً، فننتقل إلى الطبقة التالية.",[10,7996,7997,7998,8000,8001,8004,8005,6516,8007,8009,8010,8013,8014,8017],{},"الطبقة الثانية هي المسار المخصّص لمن يكتب الرقم كما يستعمله في بلده. ",[14,7999,7438],{}," لا يبدأ برمز اتصال، وبالتالي لا نفع من الطبقة الأولى. لكن إن كنّا نعرف بصمت أنّ المستخدم في مصر على الأرجح (اعتماداً على IP أو timezone, والمزيد عن هذا لاحقاً)، فبإمكاننا تمرير ",[14,8002,8003],{},"\"EG\""," تلميحاً للمحلِّل، وسيتعرّف ",[14,8006,7975],{},[14,8008,7438],{}," بوصفه رقم هاتف محمول مصري صحيح، ويجرّد الصفر الأول (البادئة الوطنية لمصر)، ويعيد الرقم الوطني المعياري ",[14,8011,8012],{},"1066105963",". الحارس ",[14,8015,8016],{},"digits.length >= 4"," يمنع المحلِّل من التعامل مع بدايات رموز المناطق ذات الرقمين بوصفها أرقاماً كاملة.",[10,8019,8020,8021,8024,8025,8027,8028,8031,8032,8035],{},"الطبقة الثالثة هي الاحتياطي لحالة \"كتبتُ ",[14,8022,8023],{},"44"," فقط ولا شيء غير ذلك\". في هذه الحالة لا مسار في ",[14,8026,7975],{}," يستطيع المطابقة، فالمدخل قصير جداً. لكنّ فهرس أرقام الاتصال الخاصّ بنا يكفي: ",[14,8029,8030],{},"byDialDigits.get('44')"," يعيد مجموعة المملكة المتحدة بطول 1, وينتهي الأمر. هنا تُثبت بنية ",[14,8033,8034],{},"Map\u003Cstring, CountryOption[]>"," الموروثة من النسخة القديمة قيمتها.",[10,8037,8038],{},"قد تبدو ثلاث طبقات كثيرة. في الحقيقة هي الحدّ الأدنى: كلّ طبقة تعالج مدخلات ترفضها الاثنتان الأخريان.",[10,8040,8041],{},"المُعالِج الذي يستدعيها يبقى صغيراً:",[277,8043,8045],{"className":279,"code":8044,"language":281,"meta":282,"style":282},"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",[14,8046,8047,8061,8070,8085,8101,8113,8128,8142,8157,8168,8178,8188,8192,8213],{"__ignoreMap":282},[286,8048,8049,8051,8054,8056,8059],{"class":288,"line":289},[286,8050,773],{"class":292},[286,8052,8053],{"class":323}," detectAndApply",[286,8055,338],{"class":292},[286,8057,8058],{"class":302}," useDebounceFn",[286,8060,2836],{"class":306},[286,8062,8063,8066,8068],{"class":288,"line":356},[286,8064,8065],{"class":306},"  () ",[286,8067,953],{"class":292},[286,8069,475],{"class":306},[286,8071,8072,8074,8076,8078,8081,8083],{"class":288,"line":376},[286,8073,682],{"class":292},[286,8075,382],{"class":306},[286,8077,385],{"class":292},[286,8079,8080],{"class":306},"props.detectFromInput) ",[286,8082,871],{"class":292},[286,8084,487],{"class":306},[286,8086,8087,8089,8092,8094,8097,8099],{"class":288,"line":415},[286,8088,682],{"class":292},[286,8090,8091],{"class":306}," (userPickedCountry.value ",[286,8093,2228],{"class":292},[286,8095,8096],{"class":306}," selectedIso2.value) ",[286,8098,871],{"class":292},[286,8100,487],{"class":306},[286,8102,8103,8105,8108,8110],{"class":288,"line":441},[286,8104,598],{"class":292},[286,8106,8107],{"class":323}," current",[286,8109,338],{"class":292},[286,8111,8112],{"class":306}," phone.value;\n",[286,8114,8115,8117,8119,8121,8124,8126],{"class":288,"line":543},[286,8116,682],{"class":292},[286,8118,382],{"class":306},[286,8120,385],{"class":292},[286,8122,8123],{"class":306},"current) ",[286,8125,871],{"class":292},[286,8127,487],{"class":306},[286,8129,8130,8132,8135,8137,8139],{"class":288,"line":550},[286,8131,598],{"class":292},[286,8133,8134],{"class":323}," match",[286,8136,338],{"class":292},[286,8138,7461],{"class":302},[286,8140,8141],{"class":306},"(current);\n",[286,8143,8144,8146,8148,8150,8153,8155],{"class":288,"line":595},[286,8145,682],{"class":292},[286,8147,382],{"class":306},[286,8149,385],{"class":292},[286,8151,8152],{"class":306},"match) ",[286,8154,871],{"class":292},[286,8156,487],{"class":306},[286,8158,8159,8162,8164,8166],{"class":288,"line":620},[286,8160,8161],{"class":306},"    autoSettingCountry.value ",[286,8163,537],{"class":292},[286,8165,4031],{"class":323},[286,8167,487],{"class":306},[286,8169,8170,8173,8175],{"class":288,"line":658},[286,8171,8172],{"class":306},"    selectedIso2.value ",[286,8174,537],{"class":292},[286,8176,8177],{"class":306}," match.country.value;\n",[286,8179,8180,8183,8185],{"class":288,"line":679},[286,8181,8182],{"class":306},"    phone.value ",[286,8184,537],{"class":292},[286,8186,8187],{"class":306}," match.nationalNumber;\n",[286,8189,8190],{"class":288,"line":707},[286,8191,7280],{"class":306},[286,8193,8194,8197,8199,8201,8203,8206,8208,8210],{"class":288,"line":729},[286,8195,8196],{"class":302},"  computed",[286,8198,5748],{"class":306},[286,8200,953],{"class":292},[286,8202,7799],{"class":306},[286,8204,8205],{"class":302},"max",[286,8207,467],{"class":306},[286,8209,2679],{"class":323},[286,8211,8212],{"class":306},", props.detectDebounceMs))\n",[286,8214,8215],{"class":288,"line":735},[286,8216,792],{"class":306},[10,8218,8219,8220,6303,8223,8226,8227,8230],{},"يقرأ ",[14,8221,8222],{},"useDebounceFn",[14,8224,8225],{},"phone.value"," لحظة إطلاق المؤقّت، لا لحظة جدولته، فتذوب دفعة من الضغطات على المفاتيح في تحليل واحد. ويمنع ",[14,8228,8229],{},"userPickedCountry.value"," المكتشفَ التلقائيّ من الدهس على اختيار يدوي. متى استعمل المستخدم القائمة، ينسحب المطابِق مباشرة. أمّا مسح الحقل فلا يخضع لأيّ debounce, بل يعيد المنتقي إلى الاختفاء فوراً:",[277,8232,8234],{"className":279,"code":8233,"language":281,"meta":282,"style":282},"if (!cleaned) {\n  autoSettingCountry.value = true;\n  selectedIso2.value = '';\n  phone.value = '';\n  userPickedCountry.value = false;\n  return;\n}\n",[14,8235,8236,8248,8259,8270,8281,8293,8299],{"__ignoreMap":282},[286,8237,8238,8241,8243,8245],{"class":288,"line":289},[286,8239,8240],{"class":292},"if",[286,8242,382],{"class":306},[286,8244,385],{"class":292},[286,8246,8247],{"class":306},"cleaned) {\n",[286,8249,8250,8253,8255,8257],{"class":288,"line":356},[286,8251,8252],{"class":306},"  autoSettingCountry.value ",[286,8254,537],{"class":292},[286,8256,4031],{"class":323},[286,8258,487],{"class":306},[286,8260,8261,8264,8266,8268],{"class":288,"line":376},[286,8262,8263],{"class":306},"  selectedIso2.value ",[286,8265,537],{"class":292},[286,8267,7210],{"class":899},[286,8269,487],{"class":306},[286,8271,8272,8275,8277,8279],{"class":288,"line":415},[286,8273,8274],{"class":306},"  phone.value ",[286,8276,537],{"class":292},[286,8278,7210],{"class":899},[286,8280,487],{"class":306},[286,8282,8283,8286,8288,8291],{"class":288,"line":441},[286,8284,8285],{"class":306},"  userPickedCountry.value ",[286,8287,537],{"class":292},[286,8289,8290],{"class":323}," false",[286,8292,487],{"class":306},[286,8294,8295,8297],{"class":288,"line":543},[286,8296,418],{"class":292},[286,8298,487],{"class":306},[286,8300,8301],{"class":288,"line":550},[286,8302,444],{"class":306},[10,8304,8305],{},"تأخير المسح يعني بقاء القائمة معلّقة 150 ميلي ثانية بعد إفراغ الحقل، وهو ما يبدو خاطئاً في كلّ مرّة.",[93,8307,8309],{"id":8308},"سلسلة-اكتشاف-البيئة-ولماذا-هي-قابلة-للاستبدال","سلسلة اكتشاف البيئة، ولماذا هي قابلة للاستبدال",[10,8311,8312,8313,8316],{},"تعتمد الطبقة الثانية أعلاه على ",[14,8314,8315],{},"inferredCountry.value","، أي أفضل تخمين للمكوّن عن موقع المستخدم، يُضبط بصمت قبل أن يكتب حرفاً واحداً. كان \"أفضل تخمين\" في النسخة القديمة قيمة احتياطية مثبّتة على السعودية أو مصر، لأنّهما السوقان اللذان استهدفتهما النسخة الداخلية. المكتبة لا تملك ترف هذا الافتراض.",[10,8318,8319,8320,320],{},"السلسلة الجديدة هي ",[14,8321,8322],{},"detectCountry()",[277,8324,8326],{"className":279,"code":8325,"language":281,"meta":282,"style":282},"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",[14,8327,8328,8363,8369,8381,8393,8405,8417,8428,8437,8441,8448,8462,8474,8478,8482,8496,8507,8511,8515,8527,8544,8551,8564,8571,8575,8579,8583,8604,8625,8636,8643],{"__ignoreMap":282},[286,8329,8330,8332,8334,8336,8339,8341,8344,8346,8349,8351,8353,8355,8357,8359,8361],{"class":288,"line":289},[286,8331,293],{"class":292},[286,8333,296],{"class":292},[286,8335,299],{"class":292},[286,8337,8338],{"class":302}," detectCountry",[286,8340,467],{"class":306},[286,8342,8343],{"class":316},"opts",[286,8345,320],{"class":292},[286,8347,8348],{"class":302}," DetectCountryOptions",[286,8350,338],{"class":292},[286,8352,341],{"class":306},[286,8354,320],{"class":292},[286,8356,346],{"class":302},[286,8358,307],{"class":306},[286,8360,503],{"class":323},[286,8362,353],{"class":306},[286,8364,8365,8367],{"class":288,"line":356},[286,8366,359],{"class":292},[286,8368,475],{"class":306},[286,8370,8371,8374,8376,8379],{"class":288,"line":376},[286,8372,8373],{"class":323},"    strategy",[286,8375,338],{"class":292},[286,8377,8378],{"class":899}," 'auto'",[286,8380,2047],{"class":306},[286,8382,8383,8386,8388,8391],{"class":288,"line":415},[286,8384,8385],{"class":323},"    ipEndpoint",[286,8387,338],{"class":292},[286,8389,8390],{"class":899}," 'https:\u002F\u002Fipapi.co\u002Fjson\u002F'",[286,8392,2047],{"class":306},[286,8394,8395,8398,8400,8403],{"class":288,"line":441},[286,8396,8397],{"class":323},"    defaultCountry",[286,8399,338],{"class":292},[286,8401,8402],{"class":899}," 'US'",[286,8404,2047],{"class":306},[286,8406,8407,8410,8412,8415],{"class":288,"line":543},[286,8408,8409],{"class":323},"    timeoutMs",[286,8411,338],{"class":292},[286,8413,8414],{"class":323}," 2000",[286,8416,2047],{"class":306},[286,8418,8419,8422,8424,8426],{"class":288,"line":550},[286,8420,8421],{"class":323},"    cache",[286,8423,338],{"class":292},[286,8425,4031],{"class":323},[286,8427,2047],{"class":306},[286,8429,8430,8432,8434],{"class":288,"line":595},[286,8431,7605],{"class":306},[286,8433,537],{"class":292},[286,8435,8436],{"class":306}," opts;\n",[286,8438,8439],{"class":288,"line":620},[286,8440,547],{"emptyLinePlaceholder":546},[286,8442,8443,8445],{"class":288,"line":658},[286,8444,379],{"class":292},[286,8446,8447],{"class":306}," (cache) {\n",[286,8449,8450,8452,8455,8457,8460],{"class":288,"line":679},[286,8451,598],{"class":292},[286,8453,8454],{"class":323}," cached",[286,8456,338],{"class":292},[286,8458,8459],{"class":302}," readCache",[286,8461,617],{"class":306},[286,8463,8464,8466,8469,8471],{"class":288,"line":707},[286,8465,682],{"class":292},[286,8467,8468],{"class":306}," (cached) ",[286,8470,871],{"class":292},[286,8472,8473],{"class":306}," cached;\n",[286,8475,8476],{"class":288,"line":729},[286,8477,1452],{"class":306},[286,8479,8480],{"class":288,"line":735},[286,8481,547],{"emptyLinePlaceholder":546},[286,8483,8484,8486,8489,8491,8494],{"class":288,"line":1376},[286,8485,379],{"class":292},[286,8487,8488],{"class":306}," (strategy ",[286,8490,3935],{"class":292},[286,8492,8493],{"class":899}," 'none'",[286,8495,1898],{"class":306},[286,8497,8498,8500,8503,8505],{"class":288,"line":1401},[286,8499,710],{"class":292},[286,8501,8502],{"class":306}," defaultCountry.",[286,8504,3801],{"class":302},[286,8506,617],{"class":306},[286,8508,8509],{"class":288,"line":1428},[286,8510,1452],{"class":306},[286,8512,8513],{"class":288,"line":1449},[286,8514,547],{"emptyLinePlaceholder":546},[286,8516,8517,8519,8521,8523,8525],{"class":288,"line":1455},[286,8518,379],{"class":292},[286,8520,8488],{"class":306},[286,8522,3935],{"class":292},[286,8524,8378],{"class":899},[286,8526,1898],{"class":306},[286,8528,8529,8531,8534,8536,8538,8541],{"class":288,"line":1460},[286,8530,598],{"class":292},[286,8532,8533],{"class":323}," ipResult",[286,8535,338],{"class":292},[286,8537,367],{"class":292},[286,8539,8540],{"class":302}," tryIp",[286,8542,8543],{"class":306},"(ipEndpoint, timeoutMs);\n",[286,8545,8546,8548],{"class":288,"line":1486},[286,8547,682],{"class":292},[286,8549,8550],{"class":306}," (ipResult) {\n",[286,8552,8553,8555,8558,8561],{"class":288,"line":1498},[286,8554,1942],{"class":292},[286,8556,8557],{"class":306}," (cache) ",[286,8559,8560],{"class":302},"writeCache",[286,8562,8563],{"class":306},"(ipResult);\n",[286,8565,8566,8568],{"class":288,"line":1529},[286,8567,1910],{"class":292},[286,8569,8570],{"class":306}," ipResult;\n",[286,8572,8573],{"class":288,"line":1534},[286,8574,1987],{"class":306},[286,8576,8577],{"class":288,"line":1558},[286,8578,1452],{"class":306},[286,8580,8581],{"class":288,"line":1568},[286,8582,547],{"emptyLinePlaceholder":546},[286,8584,8585,8587,8590,8592,8595,8597,8599,8602],{"class":288,"line":1595},[286,8586,359],{"class":292},[286,8588,8589],{"class":323}," localResult",[286,8591,338],{"class":292},[286,8593,8594],{"class":302}," tryTimezone",[286,8596,426],{"class":306},[286,8598,1304],{"class":292},[286,8600,8601],{"class":302}," tryLocale",[286,8603,617],{"class":306},[286,8605,8606,8608,8611,8613,8616,8618,8621,8623],{"class":288,"line":1600},[286,8607,359],{"class":292},[286,8609,8610],{"class":323}," final",[286,8612,338],{"class":292},[286,8614,8615],{"class":306}," (localResult ",[286,8617,1304],{"class":292},[286,8619,8620],{"class":306}," defaultCountry).",[286,8622,3801],{"class":302},[286,8624,617],{"class":306},[286,8626,8627,8629,8631,8633],{"class":288,"line":1605},[286,8628,379],{"class":292},[286,8630,8557],{"class":306},[286,8632,8560],{"class":302},[286,8634,8635],{"class":306},"(final);\n",[286,8637,8638,8640],{"class":288,"line":1613},[286,8639,418],{"class":292},[286,8641,8642],{"class":306}," final;\n",[286,8644,8645],{"class":288,"line":4187},[286,8646,444],{"class":306},[10,8648,8649],{},"أربع إشارات تُقيَّم بترتيب الكلفة: الأرخص المخبَّأ أولاً، والأغلى الذي يمرّ عبر الشبكة أخيراً.",[65,8651,8652,8659,8670,8698,8711],{},[68,8653,8654,8655,8658],{},"تخزين مؤقّت في ",[14,8656,8657],{},"sessionStorage",". المستخدم حسم دولته أصلاً ضمن هذه الجلسة، فلا داعي لتكرار العمل.",[68,8660,8661,8662,8665,8666,8669],{},"تحديد الموقع الجغرافي عبر IP باستخدام ",[14,8663,8664],{},"ipapi.co"," (قابل للتكوين). أدقّ إشارة، لكنّها تكلّف نداء شبكة وقدراً يسيراً من الخصوصية. من لا يريد هذا الطلب يمرّر ",[14,8667,8668],{},"strategy: 'locale'"," ليتخطّى الخطوة كلياً.",[68,8671,8672,8673,8676,8677,8680,8681,8684,8685,8680,8688,8684,8691,8680,8694,8697],{},"المنطقة الزمنية عبر ",[14,8674,8675],{},"Intl.DateTimeFormat().resolvedOptions().timeZone",", يُبحث عنها في جدول صنعتُه يدوياً يربط ",[14,8678,8679],{},"Africa\u002FCairo"," بـ ",[14,8682,8683],{},"EG",", و",[14,8686,8687],{},"Asia\u002FRiyadh",[14,8689,8690],{},"SA",[14,8692,8693],{},"Europe\u002FLondon",[14,8695,8696],{},"GB",", وهكذا. يغطّي الجدول أكثف المناطق سكاناً، وهو صريح بأنّه لا يغطّي كلّ منطقة. عدم التطابق يمرّ إلى ما بعده.",[68,8699,8700,8703,8704,183,8707,8710],{},[14,8701,8702],{},"navigator.language"," الذي يعطي ",[14,8705,8706],{},"en-EG",[14,8708,8709],{},"ar-SA"," وأشباهها. لاحقة المنطقة هي الدولة. أقلّ موثوقية (فالمستخدمون يتنقّلون بينما locale المتصفح لا)، لكنّها بلا كلفة.",[68,8712,8713,8714,8717],{},"القيمة الافتراضية التي يمرّرها المستدعي. ",[14,8715,8716],{},"defaultCountry: 'US'"," هو الافتراضي في المكتبة، وللمستهلك حرية اختيار غيره.",[10,8719,8720],{},"خطوة IP هي التي احتاجت عناية حقيقية، لأنّ الطلب قد يتعلّق:",[277,8722,8724],{"className":279,"code":8723,"language":281,"meta":282,"style":282},"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",[14,8725,8726,8768,8802,8816,8838,8844,8864,8880,8921,8952,8988,8996,9004,9013,9021,9025],{"__ignoreMap":282},[286,8727,8728,8730,8732,8734,8736,8739,8741,8743,8745,8748,8750,8752,8754,8756,8758,8760,8762,8764,8766],{"class":288,"line":289},[286,8729,1718],{"class":292},[286,8731,299],{"class":292},[286,8733,8540],{"class":302},[286,8735,467],{"class":306},[286,8737,8738],{"class":316},"endpoint",[286,8740,320],{"class":292},[286,8742,324],{"class":323},[286,8744,327],{"class":306},[286,8746,8747],{"class":316},"timeoutMs",[286,8749,320],{"class":292},[286,8751,2631],{"class":323},[286,8753,845],{"class":306},[286,8755,320],{"class":292},[286,8757,346],{"class":302},[286,8759,307],{"class":306},[286,8761,503],{"class":323},[286,8763,852],{"class":292},[286,8765,2231],{"class":323},[286,8767,353],{"class":306},[286,8769,8770,8772,8774,8776,8779,8781,8783,8786,8789,8791,8794,8796,8798,8800],{"class":288,"line":356},[286,8771,379],{"class":292},[286,8773,382],{"class":306},[286,8775,385],{"class":292},[286,8777,8778],{"class":302},"isBrowser",[286,8780,426],{"class":306},[286,8782,2228],{"class":292},[286,8784,8785],{"class":292}," typeof",[286,8787,8788],{"class":306}," fetch ",[286,8790,1738],{"class":292},[286,8792,8793],{"class":899}," 'function'",[286,8795,1115],{"class":306},[286,8797,871],{"class":292},[286,8799,2231],{"class":323},[286,8801,487],{"class":306},[286,8803,8804,8806,8808,8810,8812,8814],{"class":288,"line":376},[286,8805,359],{"class":292},[286,8807,929],{"class":323},[286,8809,338],{"class":292},[286,8811,394],{"class":292},[286,8813,936],{"class":302},[286,8815,617],{"class":306},[286,8817,8818,8820,8823,8825,8827,8829,8831,8833,8835],{"class":288,"line":415},[286,8819,359],{"class":292},[286,8821,8822],{"class":323}," timer",[286,8824,338],{"class":292},[286,8826,1968],{"class":302},[286,8828,5748],{"class":306},[286,8830,953],{"class":292},[286,8832,956],{"class":306},[286,8834,959],{"class":302},[286,8836,8837],{"class":306},"(), timeoutMs);\n",[286,8839,8840,8842],{"class":288,"line":441},[286,8841,7514],{"class":292},[286,8843,475],{"class":306},[286,8845,8846,8848,8850,8852,8854,8856,8859,8862],{"class":288,"line":543},[286,8847,598],{"class":292},[286,8849,362],{"class":323},[286,8851,338],{"class":292},[286,8853,367],{"class":292},[286,8855,370],{"class":302},[286,8857,8858],{"class":306},"(endpoint, { signal: controller.signal, credentials: ",[286,8860,8861],{"class":899},"'omit'",[286,8863,983],{"class":306},[286,8865,8866,8868,8870,8872,8874,8876,8878],{"class":288,"line":550},[286,8867,682],{"class":292},[286,8869,382],{"class":306},[286,8871,385],{"class":292},[286,8873,388],{"class":306},[286,8875,871],{"class":292},[286,8877,2231],{"class":323},[286,8879,487],{"class":306},[286,8881,8882,8884,8887,8889,8891,8893,8895,8897,8900,8902,8904,8907,8909,8911,8913,8915,8917,8919],{"class":288,"line":595},[286,8883,598],{"class":292},[286,8885,8886],{"class":323}," data",[286,8888,338],{"class":292},[286,8890,382],{"class":306},[286,8892,403],{"class":292},[286,8894,406],{"class":306},[286,8896,423],{"class":302},[286,8898,8899],{"class":306},"()) ",[286,8901,429],{"class":292},[286,8903,521],{"class":306},[286,8905,8906],{"class":316},"country_code",[286,8908,495],{"class":292},[286,8910,324],{"class":323},[286,8912,1068],{"class":306},[286,8914,7012],{"class":316},[286,8916,495],{"class":292},[286,8918,324],{"class":323},[286,8920,1694],{"class":306},[286,8922,8923,8925,8928,8930,8933,8935,8938,8940,8942,8944,8946,8948,8950],{"class":288,"line":620},[286,8924,598],{"class":292},[286,8926,8927],{"class":323}," code",[286,8929,338],{"class":292},[286,8931,8932],{"class":306}," (data.country_code ",[286,8934,1304],{"class":292},[286,8936,8937],{"class":306}," data.country ",[286,8939,1304],{"class":292},[286,8941,7210],{"class":899},[286,8943,2010],{"class":306},[286,8945,614],{"class":302},[286,8947,4626],{"class":306},[286,8949,3801],{"class":302},[286,8951,617],{"class":306},[286,8953,8954,8956,8958,8961,8964,8967,8969,8971,8974,8977,8979,8982,8984,8986],{"class":288,"line":658},[286,8955,710],{"class":292},[286,8957,6188],{"class":899},[286,8959,8960],{"class":292},"^",[286,8962,8963],{"class":323},"[A-Z]",[286,8965,8966],{"class":292},"{2}$",[286,8968,2784],{"class":899},[286,8970,60],{"class":306},[286,8972,8973],{"class":302},"test",[286,8975,8976],{"class":306},"(code) ",[286,8978,2744],{"class":292},[286,8980,8981],{"class":306}," code ",[286,8983,320],{"class":292},[286,8985,2231],{"class":323},[286,8987,487],{"class":306},[286,8989,8990,8992,8994],{"class":288,"line":679},[286,8991,7605],{"class":306},[286,8993,1924],{"class":292},[286,8995,475],{"class":306},[286,8997,8998,9000,9002],{"class":288,"line":707},[286,8999,710],{"class":292},[286,9001,2231],{"class":323},[286,9003,487],{"class":306},[286,9005,9006,9008,9011],{"class":288,"line":729},[286,9007,7605],{"class":306},[286,9009,9010],{"class":292},"finally",[286,9012,475],{"class":306},[286,9014,9015,9018],{"class":288,"line":735},[286,9016,9017],{"class":302},"    clearTimeout",[286,9019,9020],{"class":306},"(timer);\n",[286,9022,9023],{"class":288,"line":1376},[286,9024,1452],{"class":306},[286,9026,9027],{"class":288,"line":1401},[286,9028,444],{"class":306},[10,9030,9031,9032,9034,9035,9037,9038,9041,9042,9045,9046,9049,9050,9053,9054,9056],{},"يعني ",[14,9033,20],{}," مع مؤقّت من ثانيتين أنّ خدمة IP بطيئة لا تستطيع تعليق سلسلة الاكتشاف لأكثر من ",[14,9036,8747],{},". وتُبقي ",[14,9039,9040],{},"credentials: 'omit'"," الكوكيز خارج الطلب المرسَل إلى الطرف الثالث الذي ضبطتَه. أمّا شكل الاستجابة فيُتحقّق منه بالتعبير النمطي ",[14,9043,9044],{},"\u002F^[A-Z]{2}$\u002F",", لأنّ بعض واجهات IP تعيد ",[14,9047,9048],{},"\"GB\"",", وبعضها الآخر ",[14,9051,9052],{},"\"United Kingdom\"",", وبعضها ",[14,9055,2808],{}," حين لا تعرف. التعبير النمطي هو البوّابة.",[10,9058,9059,9060,9063],{},"يعيش ",[14,9061,9062],{},"detectCountry"," خارج المكوّن عن قصد:",[277,9065,9067],{"className":279,"code":9066,"language":281,"meta":282,"style":282},"import { detectCountry } from '@alikhalilll\u002Fui\u002Ftell-input';\n\nconst country = await detectCountry({ strategy: 'locale' });\n",[14,9068,9069,9083,9087],{"__ignoreMap":282},[286,9070,9071,9073,9076,9078,9081],{"class":288,"line":289},[286,9072,5424],{"class":292},[286,9074,9075],{"class":306}," { detectCountry } ",[286,9077,5435],{"class":292},[286,9079,9080],{"class":899}," '@alikhalilll\u002Fui\u002Ftell-input'",[286,9082,487],{"class":306},[286,9084,9085],{"class":288,"line":356},[286,9086,547],{"emptyLinePlaceholder":546},[286,9088,9089,9091,9093,9095,9097,9099,9102,9105],{"class":288,"line":376},[286,9090,773],{"class":292},[286,9092,7083],{"class":323},[286,9094,338],{"class":292},[286,9096,367],{"class":292},[286,9098,8338],{"class":302},[286,9100,9101],{"class":306},"({ strategy: ",[286,9103,9104],{"class":899},"'locale'",[286,9106,983],{"class":306},[10,9108,9109,9110,9113,9114,9117],{},"بلا اعتماد على Vue, ولا refs تفاعلية، ولا ",[14,9111,9112],{},"onMounted",". يعمل داخل server middleware يريد ضبط كوكي، وفي CLI يريد طباعة موقع المستخدم المرجّح، وفي unit test يريد التحكّم بالاستراتيجية. أمّا الغلاف التفاعلي (",[14,9115,9116],{},"useCountryDetection",") فقشرة رقيقة فوقه للحالة داخل المكوّن. سطحان، وتنفيذ واحد.",[10,9119,9120],{},"والسلسلة كلّها قابلة للاستبدال. إن كنتَ تكتشف الموقع الجغرافي في الخادم أصلاً وتمرّره عبر SSR, فلن ترغب في أن يُعيد المكوّن نداء IP:",[277,9122,9124],{"className":5181,"code":9123,"language":5183,"meta":282,"style":282},"\u003CATellInput\n  v-model:phone=\"phone\"\n  v-model:country=\"country\"\n  :detector=\"(opts) => Promise.resolve(serverDetectedCountry.value)\"\n\u002F>\n",[14,9125,9126,9133,9152,9168,9199],{"__ignoreMap":282},[286,9127,9128,9130],{"class":288,"line":289},[286,9129,307],{"class":306},[286,9131,9132],{"class":5192},"ATellInput\n",[286,9134,9135,9138,9140,9143,9145,9147,9149],{"class":288,"line":356},[286,9136,9137],{"class":302},"  v-model",[286,9139,320],{"class":306},[286,9141,9142],{"class":302},"phone",[286,9144,537],{"class":306},[286,9146,5201],{"class":899},[286,9148,9142],{"class":306},[286,9150,9151],{"class":899},"\"\n",[286,9153,9154,9156,9158,9160,9162,9164,9166],{"class":288,"line":376},[286,9155,9137],{"class":302},[286,9157,320],{"class":306},[286,9159,7012],{"class":302},[286,9161,537],{"class":306},[286,9163,5201],{"class":899},[286,9165,7012],{"class":306},[286,9167,9151],{"class":899},[286,9169,9170,9173,9176,9178,9180,9182,9184,9186,9188,9190,9192,9194,9197],{"class":288,"line":415},[286,9171,9172],{"class":306},"  :",[286,9174,9175],{"class":302},"detector",[286,9177,537],{"class":306},[286,9179,5201],{"class":899},[286,9181,467],{"class":306},[286,9183,8343],{"class":316},[286,9185,1115],{"class":306},[286,9187,953],{"class":292},[286,9189,346],{"class":323},[286,9191,60],{"class":306},[286,9193,2914],{"class":302},[286,9195,9196],{"class":306},"(serverDetectedCountry.value)",[286,9198,9151],{"class":899},[286,9200,9201],{"class":288,"line":441},[286,9202,9203],{"class":306},"\u002F>\n",[10,9205,9206,9207,9209],{},"يستبدل الـ prop المسمّى ",[14,9208,9175],{}," السلسلةَ المدمجةَ بأكملها. يواصل المكوّن تشغيل بقية منطق الاكتشاف (يضبط الدولة المستنتجة، ويستخدمها تلميحاً للطبقة الثانية، وهكذا)، لكنّ مصدر الإجابة يصبح ما حدّدتَه أنت.",[93,9211,9213],{"id":9212},"منتقٍ-متجاوب-مكوّن-واحد-بتقديمَين","منتقٍ متجاوب: مكوّن واحد بتقديمَين",[10,9215,9216],{},"كانت قائمة الدولة القديمة popover في كلّ مقاسات الشاشة. على الهاتف كان ذلك يعني قائمة قابلة للتمرير من 250 صفّاً مثبّتة على زرّ عرضه 38 بكسل، مع حقل بحث تحجبه لوحة المفاتيح باستمرار. كان يعمل. لكنّه يعمل بشكل رديء.",[10,9218,9219,9220,9223,9224,9227],{},"يعتمد المنتقي الجديد على العنصر الأساسي ",[14,9221,9222],{},"AResponsivePopover"," من المكتبة نفسها، وهو popover على الحاسوب وdrawer عبر ",[14,9225,9226],{},"vaul-vue"," على الجوّال:",[277,9229,9231],{"className":5181,"code":9230,"language":5183,"meta":282,"style":282},"\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",[14,9232,9233,9256,9261,9266,9271,9275,9280,9285,9290,9295,9300,9305,9310,9315,9320,9325,9330],{"__ignoreMap":282},[286,9234,9235,9237,9239,9242,9244,9246,9248,9250,9252,9254],{"class":288,"line":289},[286,9236,307],{"class":306},[286,9238,9222],{"class":5192},[286,9240,9241],{"class":302}," v-model",[286,9243,320],{"class":306},[286,9245,2949],{"class":302},[286,9247,537],{"class":306},[286,9249,5201],{"class":899},[286,9251,2949],{"class":306},[286,9253,5201],{"class":899},[286,9255,5209],{"class":306},[286,9257,9258],{"class":288,"line":356},[286,9259,9260],{"class":306},"  \u003CAResponsivePopoverTrigger as-child>\n",[286,9262,9263],{"class":288,"line":376},[286,9264,9265],{"class":306},"    \u003C!-- trigger button -->\n",[286,9267,9268],{"class":288,"line":415},[286,9269,9270],{"class":306},"  \u003C\u002FAResponsivePopoverTrigger>\n",[286,9272,9273],{"class":288,"line":441},[286,9274,547],{"emptyLinePlaceholder":546},[286,9276,9277],{"class":288,"line":543},[286,9278,9279],{"class":306},"  \u003CAResponsivePopoverContent\n",[286,9281,9282],{"class":288,"line":550},[286,9283,9284],{"class":306},"    :popover-class=\"\n",[286,9286,9287],{"class":288,"line":595},[286,9288,9289],{"class":306},"      cn(\n",[286,9291,9292],{"class":288,"line":620},[286,9293,9294],{"class":306},"        'w-[min(20rem,calc(100vw-2rem))] max-h-[min(22rem,var(--reka-popover-content-available-height))]',\n",[286,9296,9297],{"class":288,"line":658},[286,9298,9299],{"class":306},"        props.popoverClass\n",[286,9301,9302],{"class":288,"line":679},[286,9303,9304],{"class":306},"      )\n",[286,9306,9307],{"class":288,"line":707},[286,9308,9309],{"class":306},"    \"\n",[286,9311,9312],{"class":288,"line":729},[286,9313,9314],{"class":306},"    :drawer-class=\"cn('max-h-[80vh] pb-4', props.drawerClass)\"\n",[286,9316,9317],{"class":288,"line":735},[286,9318,9319],{"class":306},"  >\n",[286,9321,9322],{"class":288,"line":1376},[286,9323,9324],{"class":306},"    \u003C!-- search + list -->\n",[286,9326,9327],{"class":288,"line":1401},[286,9328,9329],{"class":306},"  \u003C\u002FAResponsivePopoverContent>\n",[286,9331,9332,9334,9336],{"class":288,"line":1428},[286,9333,5224],{"class":306},[286,9335,9222],{"class":5192},[286,9337,5209],{"class":306},[10,9339,9340,9341,25,9344,9347,9348,9351,9352,9355,9356,9359],{},"يُمرَّر prop-ان للأصناف (",[14,9342,9343],{},"popoverClass",[14,9345,9346],{},"drawerClass",") كلّ على حدة، فيتمكّن المستهلك من تنسيق كلّ تقديم دون أن يرث الآخر شيئاً منه. الـ popover يريد ",[14,9349,9350],{},"max-h-[min(22rem,var(--reka-popover-content-available-height))]"," لأنّ ",[14,9353,9354],{},"reka-ui"," يُتيح الارتفاع المتاح كمتغيّر CSS. أمّا الـ drawer فيريد ",[14,9357,9358],{},"max-h-[80vh]"," لأنّه ورقة سفلية و\"80% من نافذة العرض\" سقفٌ منطقي. فرض مجموعة أصناف واحدة على الحالتين سيكون خطأً على إحداهما، لكنّ السماح للمستهلك بتجاوز كلّ منهما مستقلاً هو تحديداً نقطة الفصل التي تحتاجها المكتبة.",[10,9361,9362],{},"الفائدة الجانبية الأخرى تخصّ البحث. في وضع الـ drawer يرسو حقل البحث في أعلى ورقة تغطّي معظم الشاشة، ولوحة المفاتيح تدفع الورقة لأعلى بدل أن تدفنها. الشيفرة نفسها، والمكوّن نفسه، والشكل المناسب للجهاز. ليس media-query داخل CSS المستهلك، بل عنصر أساسي يعرف الفرق بنفسه.",[93,9364,9366],{"id":9365},"slots-لكلّ-شيء-أربعة-عشر-slot","Slots لكلّ شيء (أربعة عشر Slot)",[10,9368,9369],{},"كانت النسخة القديمة كتلة واحدة صمّاء. تنسيق رسالة الخطأ يعني ترقيع المكوّن. تغيير أيقونة السهم يعني ترقيع المكوّن. استبدال علَم الدولة بواحد من CDN خاصّ بك يعني ترقيع المكوّن.",[10,9371,9372],{},"النسخة الجديدة تملك السلوك وتترك للمستهلك الشكل:",[277,9374,9376],{"className":279,"code":9375,"language":281,"meta":282,"style":282},"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",[14,9377,9378,9386,9401,9452,9467,9497,9548,9594,9646,9675,9719,9779,9824,9884,9899,9929],{"__ignoreMap":282},[286,9379,9380,9383],{"class":288,"line":289},[286,9381,9382],{"class":302},"defineSlots",[286,9384,9385],{"class":306},"\u003C{\n",[286,9387,9388,9391,9393,9395,9397,9399],{"class":288,"line":356},[286,9389,9390],{"class":302},"  prefix",[286,9392,495],{"class":292},[286,9394,950],{"class":306},[286,9396,953],{"class":292},[286,9398,1863],{"class":323},[286,9400,487],{"class":306},[286,9402,9403,9406,9408,9410,9412,9414,9416,9419,9421,9424,9426,9429,9431,9434,9436,9439,9441,9444,9446,9448,9450],{"class":288,"line":376},[286,9404,9405],{"class":302},"  suffix",[286,9407,495],{"class":292},[286,9409,382],{"class":306},[286,9411,5823],{"class":316},[286,9413,320],{"class":292},[286,9415,521],{"class":306},[286,9417,9418],{"class":316},"validationState",[286,9420,320],{"class":292},[286,9422,9423],{"class":899}," 'idle'",[286,9425,852],{"class":292},[286,9427,9428],{"class":899}," 'valid'",[286,9430,852],{"class":292},[286,9432,9433],{"class":899}," 'error'",[286,9435,1068],{"class":306},[286,9437,9438],{"class":316},"validation",[286,9440,320],{"class":292},[286,9442,9443],{"class":302}," PhoneValidationResult",[286,9445,1077],{"class":306},[286,9447,953],{"class":292},[286,9449,1863],{"class":323},[286,9451,487],{"class":306},[286,9453,9454,9457,9459,9461,9463,9465],{"class":288,"line":415},[286,9455,9456],{"class":899},"  'valid-icon'",[286,9458,495],{"class":292},[286,9460,950],{"class":306},[286,9462,953],{"class":292},[286,9464,1863],{"class":323},[286,9466,487],{"class":306},[286,9468,9469,9472,9474,9476,9478,9480,9482,9485,9487,9489,9491,9493,9495],{"class":288,"line":441},[286,9470,9471],{"class":899},"  'error-icon'",[286,9473,495],{"class":292},[286,9475,382],{"class":306},[286,9477,5823],{"class":316},[286,9479,320],{"class":292},[286,9481,521],{"class":306},[286,9483,9484],{"class":316},"reason",[286,9486,320],{"class":292},[286,9488,324],{"class":323},[286,9490,1077],{"class":306},[286,9492,953],{"class":292},[286,9494,1863],{"class":323},[286,9496,487],{"class":306},[286,9498,9499,9502,9504,9506,9508,9510,9512,9514,9516,9518,9520,9523,9525,9527,9529,9532,9534,9536,9538,9540,9542,9544,9546],{"class":288,"line":543},[286,9500,9501],{"class":302},"  hint",[286,9503,495],{"class":292},[286,9505,382],{"class":306},[286,9507,5823],{"class":316},[286,9509,320],{"class":292},[286,9511,521],{"class":306},[286,9513,7012],{"class":316},[286,9515,320],{"class":292},[286,9517,324],{"class":323},[286,9519,1068],{"class":306},[286,9521,9522],{"class":316},"formatHint",[286,9524,320],{"class":292},[286,9526,324],{"class":323},[286,9528,1068],{"class":306},[286,9530,9531],{"class":316},"example",[286,9533,320],{"class":292},[286,9535,324],{"class":323},[286,9537,852],{"class":292},[286,9539,2231],{"class":323},[286,9541,1077],{"class":306},[286,9543,953],{"class":292},[286,9545,1863],{"class":323},[286,9547,487],{"class":306},[286,9549,9550,9553,9555,9557,9559,9561,9563,9566,9568,9570,9572,9574,9576,9578,9580,9582,9584,9586,9588,9590,9592],{"class":288,"line":550},[286,9551,9552],{"class":302},"  error",[286,9554,495],{"class":292},[286,9556,382],{"class":306},[286,9558,5823],{"class":316},[286,9560,320],{"class":292},[286,9562,521],{"class":306},[286,9564,9565],{"class":316},"message",[286,9567,320],{"class":292},[286,9569,324],{"class":323},[286,9571,1068],{"class":306},[286,9573,9484],{"class":316},[286,9575,320],{"class":292},[286,9577,324],{"class":323},[286,9579,1068],{"class":306},[286,9581,9438],{"class":316},[286,9583,320],{"class":292},[286,9585,9443],{"class":302},[286,9587,1077],{"class":306},[286,9589,953],{"class":292},[286,9591,1863],{"class":323},[286,9593,487],{"class":306},[286,9595,9596,9599,9601,9603,9605,9607,9609,9612,9614,9617,9619,9621,9623,9625,9627,9629,9631,9634,9636,9638,9640,9642,9644],{"class":288,"line":595},[286,9597,9598],{"class":302},"  trigger",[286,9600,495],{"class":292},[286,9602,382],{"class":306},[286,9604,5823],{"class":316},[286,9606,320],{"class":292},[286,9608,521],{"class":306},[286,9610,9611],{"class":316},"selectedCountry",[286,9613,320],{"class":292},[286,9615,9616],{"class":302}," CountryOption",[286,9618,852],{"class":292},[286,9620,2231],{"class":323},[286,9622,1068],{"class":306},[286,9624,2949],{"class":316},[286,9626,320],{"class":292},[286,9628,2619],{"class":323},[286,9630,1068],{"class":306},[286,9632,9633],{"class":316},"sizeClasses",[286,9635,320],{"class":292},[286,9637,324],{"class":323},[286,9639,1077],{"class":306},[286,9641,953],{"class":292},[286,9643,1863],{"class":323},[286,9645,487],{"class":306},[286,9647,9648,9651,9653,9655,9657,9659,9661,9663,9665,9667,9669,9671,9673],{"class":288,"line":620},[286,9649,9650],{"class":302},"  chevron",[286,9652,495],{"class":292},[286,9654,382],{"class":306},[286,9656,5823],{"class":316},[286,9658,320],{"class":292},[286,9660,521],{"class":306},[286,9662,2949],{"class":316},[286,9664,320],{"class":292},[286,9666,2619],{"class":323},[286,9668,1077],{"class":306},[286,9670,953],{"class":292},[286,9672,1863],{"class":323},[286,9674,487],{"class":306},[286,9676,9677,9680,9682,9684,9686,9688,9690,9692,9694,9696,9698,9701,9703,9706,9708,9711,9713,9715,9717],{"class":288,"line":658},[286,9678,9679],{"class":302},"  flag",[286,9681,495],{"class":292},[286,9683,382],{"class":306},[286,9685,5823],{"class":316},[286,9687,320],{"class":292},[286,9689,521],{"class":306},[286,9691,7012],{"class":316},[286,9693,320],{"class":292},[286,9695,9616],{"class":302},[286,9697,1068],{"class":306},[286,9699,9700],{"class":316},"context",[286,9702,320],{"class":292},[286,9704,9705],{"class":899}," 'trigger'",[286,9707,852],{"class":292},[286,9709,9710],{"class":899}," 'item'",[286,9712,1077],{"class":306},[286,9714,953],{"class":292},[286,9716,1863],{"class":323},[286,9718,487],{"class":306},[286,9720,9721,9724,9726,9728,9730,9732,9734,9736,9738,9740,9742,9745,9747,9749,9751,9754,9756,9758,9760,9763,9765,9767,9769,9771,9773,9775,9777],{"class":288,"line":679},[286,9722,9723],{"class":302},"  item",[286,9725,495],{"class":292},[286,9727,382],{"class":306},[286,9729,5823],{"class":316},[286,9731,320],{"class":292},[286,9733,521],{"class":306},[286,9735,7012],{"class":316},[286,9737,320],{"class":292},[286,9739,9616],{"class":302},[286,9741,1068],{"class":306},[286,9743,9744],{"class":316},"selected",[286,9746,320],{"class":292},[286,9748,2619],{"class":323},[286,9750,1068],{"class":306},[286,9752,9753],{"class":316},"disabled",[286,9755,320],{"class":292},[286,9757,2619],{"class":323},[286,9759,1068],{"class":306},[286,9761,9762],{"class":302},"select",[286,9764,320],{"class":292},[286,9766,950],{"class":306},[286,9768,953],{"class":292},[286,9770,1082],{"class":323},[286,9772,1077],{"class":306},[286,9774,953],{"class":292},[286,9776,1863],{"class":323},[286,9778,487],{"class":306},[286,9780,9781,9784,9786,9788,9790,9792,9794,9797,9799,9801,9803,9806,9808,9811,9813,9816,9818,9820,9822],{"class":288,"line":707},[286,9782,9783],{"class":899},"  'group-header'",[286,9785,495],{"class":292},[286,9787,382],{"class":306},[286,9789,5823],{"class":316},[286,9791,320],{"class":292},[286,9793,521],{"class":306},[286,9795,9796],{"class":316},"label",[286,9798,320],{"class":292},[286,9800,324],{"class":323},[286,9802,1068],{"class":306},[286,9804,9805],{"class":316},"group",[286,9807,320],{"class":292},[286,9809,9810],{"class":899}," 'suggested'",[286,9812,852],{"class":292},[286,9814,9815],{"class":899}," 'all'",[286,9817,1077],{"class":306},[286,9819,953],{"class":292},[286,9821,1863],{"class":323},[286,9823,487],{"class":306},[286,9825,9826,9829,9831,9833,9835,9837,9839,9841,9843,9845,9847,9850,9852,9854,9857,9859,9861,9863,9865,9867,9869,9872,9874,9876,9878,9880,9882],{"class":288,"line":729},[286,9827,9828],{"class":302},"  search",[286,9830,495],{"class":292},[286,9832,382],{"class":306},[286,9834,5823],{"class":316},[286,9836,320],{"class":292},[286,9838,521],{"class":306},[286,9840,1688],{"class":316},[286,9842,320],{"class":292},[286,9844,324],{"class":323},[286,9846,1068],{"class":306},[286,9848,9849],{"class":302},"setValue",[286,9851,320],{"class":292},[286,9853,382],{"class":306},[286,9855,9856],{"class":316},"v",[286,9858,320],{"class":292},[286,9860,324],{"class":323},[286,9862,1115],{"class":306},[286,9864,953],{"class":292},[286,9866,1082],{"class":323},[286,9868,1068],{"class":306},[286,9870,9871],{"class":316},"isSearching",[286,9873,320],{"class":292},[286,9875,2619],{"class":323},[286,9877,1077],{"class":306},[286,9879,953],{"class":292},[286,9881,1863],{"class":323},[286,9883,487],{"class":306},[286,9885,9886,9889,9891,9893,9895,9897],{"class":288,"line":735},[286,9887,9888],{"class":302},"  loading",[286,9890,495],{"class":292},[286,9892,950],{"class":306},[286,9894,953],{"class":292},[286,9896,1863],{"class":323},[286,9898,487],{"class":306},[286,9900,9901,9904,9906,9908,9910,9912,9914,9917,9919,9921,9923,9925,9927],{"class":288,"line":1376},[286,9902,9903],{"class":302},"  empty",[286,9905,495],{"class":292},[286,9907,382],{"class":306},[286,9909,5823],{"class":316},[286,9911,320],{"class":292},[286,9913,521],{"class":306},[286,9915,9916],{"class":316},"query",[286,9918,320],{"class":292},[286,9920,324],{"class":323},[286,9922,1077],{"class":306},[286,9924,953],{"class":292},[286,9926,1863],{"class":323},[286,9928,487],{"class":306},[286,9930,9931],{"class":288,"line":1401},[286,9932,9933],{"class":306},"}>();\n",[10,9935,9936,9937,327,9940,9943,9944,8684,9947,9950,9951,327,9954,327,9957,9960,9961,327,9964,327,9966,327,9969,327,9972,9975],{},"أربعة عشر slot موزّعة على ثلاث مجموعات. المجموعة البصرية (",[14,9938,9939],{},"prefix",[14,9941,9942],{},"suffix",", وslot-ا الأيقونتين، و",[14,9945,9946],{},"hint",[14,9948,9949],{},"error",") تزيّن الحقل نفسه. مجموعة مُشغِّل المنتقي (",[14,9952,9953],{},"trigger",[14,9955,9956],{},"chevron",[14,9958,9959],{},"flag",") تستبدل الزرّ الذي ينقر عليه المستخدم. ومجموعة محتوى المنتقي (",[14,9962,9963],{},"search",[14,9965,5386],{},[14,9967,9968],{},"empty",[14,9970,9971],{},"group-header",[14,9973,9974],{},"item",") تستبدل أيّ شيء داخل الـ popover أو الـ drawer.",[10,9977,9978,9979,9982],{},"معظم slot-ات المنتقي تُمرَّر مباشرة إلى ",[14,9980,9981],{},"ACountrySelect",". المكوّن نفسه يتولّى التوجيه:",[277,9984,9986],{"className":5181,"code":9985,"language":5183,"meta":282,"style":282},"\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",[14,9987,9988,10021,10047,10055,10086,10109,10117],{"__ignoreMap":282},[286,9989,9990,9992,9994,9996,9998,10000,10003,10005,10008,10010,10012,10014,10017,10019],{"class":288,"line":289},[286,9991,307],{"class":306},[286,9993,5917],{"class":5192},[286,9995,5196],{"class":292},[286,9997,537],{"class":306},[286,9999,5201],{"class":899},[286,10001,10002],{"class":306},"$slots.trigger",[286,10004,5201],{"class":899},[286,10006,10007],{"class":306}," #",[286,10009,9953],{"class":302},[286,10011,537],{"class":306},[286,10013,5201],{"class":899},[286,10015,10016],{"class":306},"slotProps",[286,10018,5201],{"class":899},[286,10020,5209],{"class":306},[286,10022,10023,10025,10028,10030,10032,10035,10038,10040,10043,10045],{"class":288,"line":356},[286,10024,5924],{"class":306},[286,10026,10027],{"class":5192},"slot",[286,10029,4690],{"class":302},[286,10031,537],{"class":306},[286,10033,10034],{"class":899},"\"trigger\"",[286,10036,10037],{"class":302}," v-bind",[286,10039,537],{"class":306},[286,10041,10042],{"class":899},"\"slotProps\"",[286,10044,6188],{"class":6187},[286,10046,5209],{"class":306},[286,10048,10049,10051,10053],{"class":288,"line":376},[286,10050,5224],{"class":306},[286,10052,5917],{"class":5192},[286,10054,5209],{"class":306},[286,10056,10057,10059,10061,10063,10065,10067,10070,10072,10074,10076,10078,10080,10082,10084],{"class":288,"line":415},[286,10058,307],{"class":306},[286,10060,5917],{"class":5192},[286,10062,5196],{"class":292},[286,10064,537],{"class":306},[286,10066,5201],{"class":899},[286,10068,10069],{"class":306},"$slots.chevron",[286,10071,5201],{"class":899},[286,10073,10007],{"class":306},[286,10075,9956],{"class":302},[286,10077,537],{"class":306},[286,10079,5201],{"class":899},[286,10081,10016],{"class":306},[286,10083,5201],{"class":899},[286,10085,5209],{"class":306},[286,10087,10088,10090,10092,10094,10096,10099,10101,10103,10105,10107],{"class":288,"line":441},[286,10089,5924],{"class":306},[286,10091,10027],{"class":5192},[286,10093,4690],{"class":302},[286,10095,537],{"class":306},[286,10097,10098],{"class":899},"\"chevron\"",[286,10100,10037],{"class":302},[286,10102,537],{"class":306},[286,10104,10042],{"class":899},[286,10106,6188],{"class":6187},[286,10108,5209],{"class":306},[286,10110,10111,10113,10115],{"class":288,"line":543},[286,10112,5224],{"class":306},[286,10114,5917],{"class":5192},[286,10116,5209],{"class":306},[286,10118,10119],{"class":288,"line":550},[286,10120,10121],{"class":921},"\u003C!-- ... and so on for each forwarded slot -->\n",[10,10123,10124,10125,10128,10129,10132,10133,10135],{},"الشرط ",[14,10126,10127],{},"v-if=\"$slots.x\""," هنا مهم. بدونه سيبدو الـ ",[14,10130,10131],{},"\u003Ctemplate #x>"," المُمرَّر موجوداً دائماً من منظور الأب، فلا يحصل الرسم الاحتياطي داخل ",[14,10134,9981],{}," على أيّ فرصة للعمل. الحارس يقول ببساطة: \"لا تُمرّر إلا إذا قدّم المستهلك هذا الـ slot فعلاً\".",[10,10137,10138],{},"مثال تخصيص واقعي:",[277,10140,10142],{"className":5181,"code":10141,"language":5183,"meta":282,"style":282},"\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",[14,10143,10144,10183,10188,10193,10198,10203,10208,10213,10218,10222,10227,10232,10237,10242,10247,10252,10257,10261],{"__ignoreMap":282},[286,10145,10146,10148,10150,10152,10154,10156,10158,10160,10162,10164,10166,10168,10170,10172,10174,10176,10178,10181],{"class":288,"line":289},[286,10147,307],{"class":306},[286,10149,6922],{"class":5192},[286,10151,9241],{"class":302},[286,10153,320],{"class":306},[286,10155,9142],{"class":302},[286,10157,537],{"class":306},[286,10159,5201],{"class":899},[286,10161,9142],{"class":306},[286,10163,5201],{"class":899},[286,10165,9241],{"class":302},[286,10167,320],{"class":306},[286,10169,7012],{"class":302},[286,10171,537],{"class":306},[286,10173,5201],{"class":899},[286,10175,7012],{"class":306},[286,10177,5201],{"class":899},[286,10179,10180],{"class":302}," show-validation",[286,10182,5209],{"class":306},[286,10184,10185],{"class":288,"line":356},[286,10186,10187],{"class":306},"  \u003Ctemplate #suffix=\"{ validationState }\">\n",[286,10189,10190],{"class":288,"line":376},[286,10191,10192],{"class":306},"    \u003CSparkle v-if=\"validationState === 'valid'\" class=\"size-4 text-amber-400\" \u002F>\n",[286,10194,10195],{"class":288,"line":415},[286,10196,10197],{"class":306},"  \u003C\u002Ftemplate>\n",[286,10199,10200],{"class":288,"line":441},[286,10201,10202],{"class":306},"  \u003Ctemplate #error=\"{ message, reason }\">\n",[286,10204,10205],{"class":288,"line":543},[286,10206,10207],{"class":306},"    \u003Cp class=\"text-destructive text-xs\">\n",[286,10209,10210],{"class":288,"line":550},[286,10211,10212],{"class":306},"      \u003Ccode class=\"bg-muted rounded px-1 py-0.5\">{{ reason }}\u003C\u002Fcode> {{ message }}\n",[286,10214,10215],{"class":288,"line":595},[286,10216,10217],{"class":306},"    \u003C\u002Fp>\n",[286,10219,10220],{"class":288,"line":620},[286,10221,10197],{"class":306},[286,10223,10224],{"class":288,"line":658},[286,10225,10226],{"class":306},"  \u003Ctemplate #empty=\"{ query }\">\n",[286,10228,10229],{"class":288,"line":679},[286,10230,10231],{"class":306},"    \u003Cdiv class=\"px-3 py-6 text-center text-sm\">\n",[286,10233,10234],{"class":288,"line":707},[286,10235,10236],{"class":306},"      Nothing matched \"\u003Cstrong>{{ query }}\u003C\u002Fstrong>\".\n",[286,10238,10239],{"class":288,"line":729},[286,10240,10241],{"class":306},"      \u003Cbutton type=\"button\" class=\"underline\" @click=\"suggestCountry(query)\">\n",[286,10243,10244],{"class":288,"line":735},[286,10245,10246],{"class":306},"        Suggest a country\n",[286,10248,10249],{"class":288,"line":1376},[286,10250,10251],{"class":306},"      \u003C\u002Fbutton>\n",[286,10253,10254],{"class":288,"line":1401},[286,10255,10256],{"class":306},"    \u003C\u002Fdiv>\n",[286,10258,10259],{"class":288,"line":1428},[286,10260,10197],{"class":306},[286,10262,10263,10265,10267],{"class":288,"line":1449},[286,10264,5224],{"class":306},[286,10266,6922],{"class":5192},[286,10268,5209],{"class":306},[10,10270,10271],{},"وظيفة مكوّن المكتبة أن يملك السلوك (التحليل، والتحقّق، والاكتشاف، وإدارة الحالة)، وأن يترك للمستهلك التحكّم بشكل هذه الأشياء. أربعة عشر slot هي كلفة هذه الصفقة، وهي كلفة تستحقّ.",[10,10273,10274],{},"المبدأ نفسه ينطبق على الـ props غير البصرية:",[277,10276,10278],{"className":279,"code":10277,"language":281,"meta":282,"style":282},"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",[14,10279,10280,10312,10342,10352,10389],{"__ignoreMap":282},[286,10281,10282,10285,10287,10289,10292,10294,10296,10298,10301,10303,10305,10307,10309],{"class":288,"line":289},[286,10283,10284],{"class":306},"flagUrl",[286,10286,495],{"class":292},[286,10288,382],{"class":306},[286,10290,10291],{"class":316},"iso2",[286,10293,320],{"class":292},[286,10295,324],{"class":323},[286,10297,327],{"class":306},[286,10299,10300],{"class":316},"width",[286,10302,320],{"class":292},[286,10304,2631],{"class":323},[286,10306,1115],{"class":306},[286,10308,953],{"class":292},[286,10310,10311],{"class":306}," string;\n",[286,10313,10314,10317,10319,10321,10323,10325,10327,10329,10331,10333,10335,10337,10339],{"class":288,"line":356},[286,10315,10316],{"class":306},"searcher",[286,10318,495],{"class":292},[286,10320,382],{"class":306},[286,10322,9916],{"class":316},[286,10324,320],{"class":292},[286,10326,324],{"class":323},[286,10328,327],{"class":306},[286,10330,7012],{"class":316},[286,10332,320],{"class":292},[286,10334,9616],{"class":302},[286,10336,1115],{"class":306},[286,10338,953],{"class":292},[286,10340,10341],{"class":306}," boolean;\n",[286,10343,10344,10347,10349],{"class":288,"line":376},[286,10345,10346],{"class":306},"countries",[286,10348,495],{"class":292},[286,10350,10351],{"class":306}," CountryOption[];\n",[286,10353,10354,10356,10358,10360,10362,10364,10366,10368,10370,10372,10374,10377,10379,10381,10383,10385,10387],{"class":288,"line":415},[286,10355,9175],{"class":306},[286,10357,495],{"class":292},[286,10359,382],{"class":306},[286,10361,470],{"class":316},[286,10363,320],{"class":292},[286,10365,8348],{"class":302},[286,10367,1115],{"class":306},[286,10369,953],{"class":292},[286,10371,346],{"class":323},[286,10373,307],{"class":292},[286,10375,10376],{"class":306},"string ",[286,10378,3861],{"class":292},[286,10380,2231],{"class":323},[286,10382,852],{"class":292},[286,10384,855],{"class":323},[286,10386,2773],{"class":292},[286,10388,487],{"class":306},[286,10390,10391,10394,10396,10398,10400,10403,10405],{"class":288,"line":441},[286,10392,10393],{"class":306},"errorMessages",[286,10395,495],{"class":292},[286,10397,498],{"class":306},[286,10399,307],{"class":292},[286,10401,10402],{"class":306},"PhoneValidationReason, string",[286,10404,2773],{"class":292},[286,10406,487],{"class":306},[10,10408,10409,10410,10412,10413,10416,10417,10419,10420,10422,10423,10426,10427,10429],{},"يُتيح لك ",[14,10411,10284],{}," استبدال ",[14,10414,10415],{},"flagcdn.com"," بـ CDN خاصّ بك، وهو مفيد إن كان لديك CSP لا يسمح بصور من طرف ثالث. ويُتيح لك ",[14,10418,10316],{}," استبدال مطابقة السلاسل الفرعية بمطابقة بادئة، أو مطابقة ضبابية، أو مطابقة بالاسم العربي، أو أيّ شيء يحتاجه مستخدموك فعلاً. ويُتيح لك ",[14,10421,10346],{}," شحن قائمتك المُنتقاة والاستغناء كلياً عن نداء ",[14,10424,10425],{},"REST Countries",", وهو مفيد إن كنتَ حسمتَ أنّك تخدم دول الخليج مثلاً. أمّا ",[14,10428,10393],{}," فمعنيّ بالتوطين: مرّر جدولاً عربياً وستُعرض أسباب التحقّق بالعربية.",[10,10431,10432],{},"كلّ prop له افتراضي منطقي. النداء الذي لا يحتاج تكويناً ما زال يعمل كما هو.",[93,10434,10436],{"id":10435},"جدول-رموز-الاتصال-المتزامن-ولماذا-ليس-اختيارياً","جدول رموز الاتصال المتزامن، ولماذا ليس اختيارياً",[10,10438,10439,10440,10442,10443,382,10446,327,10449,10452,10453,10456,10457,10460,10461,6330,10463,10465],{},"عند أوّل تركيب للمكوّن في تبويب جديد، لا يكون نداء ",[14,10441,10425],{}," قد اكتمل. أيّ فهارس يبنيها ",[14,10444,10445],{},"usePhoneValidation",[14,10447,10448],{},"byValue",[14,10450,10451],{},"byDialDigits",") تكون خرائط فارغة. لو كتب المستهلك ",[14,10454,10455],{},"default-country=\"20\""," في قالبه متوقّعاً أن يظهر المنتقي وقد اختار مصر، فما سيحصل عليه بلا مساعدة هو منتقٍ فارغ. فهرس أرقام الاتصال يعيد ",[14,10458,10459],{},"[]",", ويضبط الـ watcher قيمة ",[14,10462,7152],{},[14,10464,7074],{},", ولن تظهر مصر إلا حين يعود نداء الشبكة.",[10,10467,10468],{},"لذلك يُدرِج المكوّن جدولاً احتياطياً متزامناً في داخله:",[277,10470,10472],{"className":279,"code":10471,"language":281,"meta":282,"style":282},"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",[14,10473,10474,10499,10511,10523,10535,10547,10559,10571,10583,10595,10607,10619,10631,10643,10655,10667,10679,10691,10696],{"__ignoreMap":282},[286,10475,10476,10478,10481,10483,10485,10487,10489,10491,10493,10495,10497],{"class":288,"line":289},[286,10477,773],{"class":292},[286,10479,10480],{"class":323}," DIAL_TO_ISO2_FALLBACK",[286,10482,320],{"class":292},[286,10484,498],{"class":302},[286,10486,307],{"class":306},[286,10488,503],{"class":323},[286,10490,327],{"class":306},[286,10492,503],{"class":323},[286,10494,3858],{"class":306},[286,10496,537],{"class":292},[286,10498,475],{"class":306},[286,10500,10501,10504,10506,10509],{"class":288,"line":356},[286,10502,10503],{"class":899},"  '1'",[286,10505,6696],{"class":306},[286,10507,10508],{"class":899},"'US'",[286,10510,2047],{"class":306},[286,10512,10513,10516,10518,10521],{"class":288,"line":376},[286,10514,10515],{"class":899},"  '7'",[286,10517,6696],{"class":306},[286,10519,10520],{"class":899},"'RU'",[286,10522,2047],{"class":306},[286,10524,10525,10528,10530,10533],{"class":288,"line":415},[286,10526,10527],{"class":899},"  '20'",[286,10529,6696],{"class":306},[286,10531,10532],{"class":899},"'EG'",[286,10534,2047],{"class":306},[286,10536,10537,10540,10542,10545],{"class":288,"line":441},[286,10538,10539],{"class":899},"  '27'",[286,10541,6696],{"class":306},[286,10543,10544],{"class":899},"'ZA'",[286,10546,2047],{"class":306},[286,10548,10549,10552,10554,10557],{"class":288,"line":543},[286,10550,10551],{"class":899},"  '33'",[286,10553,6696],{"class":306},[286,10555,10556],{"class":899},"'FR'",[286,10558,2047],{"class":306},[286,10560,10561,10564,10566,10569],{"class":288,"line":550},[286,10562,10563],{"class":899},"  '44'",[286,10565,6696],{"class":306},[286,10567,10568],{"class":899},"'GB'",[286,10570,2047],{"class":306},[286,10572,10573,10576,10578,10581],{"class":288,"line":595},[286,10574,10575],{"class":899},"  '49'",[286,10577,6696],{"class":306},[286,10579,10580],{"class":899},"'DE'",[286,10582,2047],{"class":306},[286,10584,10585,10588,10590,10593],{"class":288,"line":620},[286,10586,10587],{"class":899},"  '52'",[286,10589,6696],{"class":306},[286,10591,10592],{"class":899},"'MX'",[286,10594,2047],{"class":306},[286,10596,10597,10600,10602,10605],{"class":288,"line":658},[286,10598,10599],{"class":899},"  '55'",[286,10601,6696],{"class":306},[286,10603,10604],{"class":899},"'BR'",[286,10606,2047],{"class":306},[286,10608,10609,10612,10614,10617],{"class":288,"line":679},[286,10610,10611],{"class":899},"  '61'",[286,10613,6696],{"class":306},[286,10615,10616],{"class":899},"'AU'",[286,10618,2047],{"class":306},[286,10620,10621,10624,10626,10629],{"class":288,"line":707},[286,10622,10623],{"class":899},"  '81'",[286,10625,6696],{"class":306},[286,10627,10628],{"class":899},"'JP'",[286,10630,2047],{"class":306},[286,10632,10633,10636,10638,10641],{"class":288,"line":729},[286,10634,10635],{"class":899},"  '86'",[286,10637,6696],{"class":306},[286,10639,10640],{"class":899},"'CN'",[286,10642,2047],{"class":306},[286,10644,10645,10648,10650,10653],{"class":288,"line":735},[286,10646,10647],{"class":899},"  '91'",[286,10649,6696],{"class":306},[286,10651,10652],{"class":899},"'IN'",[286,10654,2047],{"class":306},[286,10656,10657,10660,10662,10665],{"class":288,"line":1376},[286,10658,10659],{"class":899},"  '212'",[286,10661,6696],{"class":306},[286,10663,10664],{"class":899},"'MA'",[286,10666,2047],{"class":306},[286,10668,10669,10672,10674,10677],{"class":288,"line":1401},[286,10670,10671],{"class":899},"  '966'",[286,10673,6696],{"class":306},[286,10675,10676],{"class":899},"'SA'",[286,10678,2047],{"class":306},[286,10680,10681,10684,10686,10689],{"class":288,"line":1428},[286,10682,10683],{"class":899},"  '971'",[286,10685,6696],{"class":306},[286,10687,10688],{"class":899},"'AE'",[286,10690,2047],{"class":306},[286,10692,10693],{"class":288,"line":1449},[286,10694,10695],{"class":921},"  \u002F\u002F ... ~50 entries\n",[286,10697,10698],{"class":288,"line":1455},[286,10699,10700],{"class":306},"};\n",[10,10702,10703,10704,10707],{},"ويتحقّق ",[14,10705,10706],{},"resolveCountryIdentifier"," أولاً من الفهرس المحمَّل، ثم من هذا الجدول:",[277,10709,10711],{"className":279,"code":10710,"language":281,"meta":282,"style":282},"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",[14,10712,10713,10743,10768,10785,10818,10850,10883,10901,10913,10928],{"__ignoreMap":282},[286,10714,10715,10717,10719,10721,10723,10725,10727,10729,10731,10733,10735,10737,10739,10741],{"class":288,"line":289},[286,10716,820],{"class":292},[286,10718,7255],{"class":302},[286,10720,467],{"class":306},[286,10722,4560],{"class":316},[286,10724,320],{"class":292},[286,10726,324],{"class":323},[286,10728,852],{"class":292},[286,10730,855],{"class":323},[286,10732,852],{"class":292},[286,10734,2231],{"class":323},[286,10736,845],{"class":306},[286,10738,320],{"class":292},[286,10740,324],{"class":323},[286,10742,475],{"class":306},[286,10744,10745,10747,10750,10752,10755,10758,10760,10762,10764,10766],{"class":288,"line":356},[286,10746,359],{"class":292},[286,10748,10749],{"class":323}," v",[286,10751,338],{"class":292},[286,10753,10754],{"class":302}," String",[286,10756,10757],{"class":306},"(raw ",[286,10759,1304],{"class":292},[286,10761,7210],{"class":899},[286,10763,2010],{"class":306},[286,10765,4623],{"class":302},[286,10767,617],{"class":306},[286,10769,10770,10772,10774,10776,10779,10781,10783],{"class":288,"line":376},[286,10771,379],{"class":292},[286,10773,382],{"class":306},[286,10775,385],{"class":292},[286,10777,10778],{"class":306},"v) ",[286,10780,871],{"class":292},[286,10782,7210],{"class":899},[286,10784,487],{"class":306},[286,10786,10787,10789,10791,10793,10795,10798,10800,10802,10804,10806,10809,10811,10814,10816],{"class":288,"line":415},[286,10788,379],{"class":292},[286,10790,382],{"class":306},[286,10792,2784],{"class":899},[286,10794,8960],{"class":292},[286,10796,10797],{"class":323},"[A-Za-z]",[286,10799,8966],{"class":292},[286,10801,2784],{"class":899},[286,10803,60],{"class":306},[286,10805,8973],{"class":302},[286,10807,10808],{"class":306},"(v)) ",[286,10810,871],{"class":292},[286,10812,10813],{"class":306}," v.",[286,10815,3801],{"class":302},[286,10817,617],{"class":306},[286,10819,10820,10822,10825,10827,10829,10832,10834,10836,10838,10842,10844,10846,10848],{"class":288,"line":441},[286,10821,359],{"class":292},[286,10823,10824],{"class":323}," dial",[286,10826,338],{"class":292},[286,10828,10813],{"class":306},[286,10830,10831],{"class":302},"replace",[286,10833,467],{"class":306},[286,10835,2784],{"class":899},[286,10837,8960],{"class":292},[286,10839,10841],{"class":10840},"snhLl","\\+",[286,10843,2784],{"class":899},[286,10845,327],{"class":306},[286,10847,7074],{"class":899},[286,10849,792],{"class":306},[286,10851,10852,10854,10856,10858,10860,10862,10865,10868,10870,10872,10874,10877,10879,10881],{"class":288,"line":543},[286,10853,379],{"class":292},[286,10855,382],{"class":306},[286,10857,385],{"class":292},[286,10859,2784],{"class":899},[286,10861,8960],{"class":292},[286,10863,10864],{"class":323},"\\d",[286,10866,10867],{"class":292},"+$",[286,10869,2784],{"class":899},[286,10871,60],{"class":306},[286,10873,8973],{"class":302},[286,10875,10876],{"class":306},"(dial)) ",[286,10878,871],{"class":292},[286,10880,7210],{"class":899},[286,10882,487],{"class":306},[286,10884,10885,10887,10889,10891,10893,10896,10898],{"class":288,"line":550},[286,10886,359],{"class":292},[286,10888,8134],{"class":323},[286,10890,338],{"class":292},[286,10892,7859],{"class":302},[286,10894,10895],{"class":306},"(dial)[",[286,10897,2679],{"class":323},[286,10899,10900],{"class":306},"];\n",[286,10902,10903,10905,10908,10910],{"class":288,"line":595},[286,10904,379],{"class":292},[286,10906,10907],{"class":306}," (match) ",[286,10909,871],{"class":292},[286,10911,10912],{"class":306}," match.value;\n",[286,10914,10915,10917,10919,10922,10924,10926],{"class":288,"line":620},[286,10916,418],{"class":292},[286,10918,10480],{"class":323},[286,10920,10921],{"class":306},"[dial] ",[286,10923,1304],{"class":292},[286,10925,7210],{"class":899},[286,10927,487],{"class":306},[286,10929,10930],{"class":288,"line":658},[286,10931,444],{"class":306},[10,10933,10934],{},"الاتجاه المعاكس، أي التحويل من ISO2 إلى رقم اتصال من أجل v-model, يُجري الفحص نفسه:",[277,10936,10938],{"className":279,"code":10937,"language":281,"meta":282,"style":282},"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",[14,10939,10940,10967,10984,10998,11048,11064,11078,11102],{"__ignoreMap":282},[286,10941,10942,10944,10947,10949,10951,10953,10955,10957,10959,10961,10963,10965],{"class":288,"line":289},[286,10943,820],{"class":292},[286,10945,10946],{"class":302}," dialNumberFor",[286,10948,467],{"class":306},[286,10950,10291],{"class":316},[286,10952,320],{"class":292},[286,10954,324],{"class":323},[286,10956,845],{"class":306},[286,10958,320],{"class":292},[286,10960,2631],{"class":323},[286,10962,852],{"class":292},[286,10964,2231],{"class":323},[286,10966,475],{"class":306},[286,10968,10969,10971,10973,10975,10978,10980,10982],{"class":288,"line":356},[286,10970,379],{"class":292},[286,10972,382],{"class":306},[286,10974,385],{"class":292},[286,10976,10977],{"class":306},"iso2) ",[286,10979,871],{"class":292},[286,10981,2231],{"class":323},[286,10983,487],{"class":306},[286,10985,10986,10988,10991,10993,10995],{"class":288,"line":376},[286,10987,359],{"class":292},[286,10989,10990],{"class":323}," fromIndex",[286,10992,338],{"class":292},[286,10994,7563],{"class":302},[286,10996,10997],{"class":306},"(iso2)?.raw_data?.dial_digits;\n",[286,10999,11000,11002,11005,11007,11010,11012,11014,11016,11018,11021,11023,11026,11029,11031,11034,11036,11039,11041,11044,11046],{"class":288,"line":415},[286,11001,359],{"class":292},[286,11003,11004],{"class":323}," digits",[286,11006,338],{"class":292},[286,11008,11009],{"class":306}," fromIndex ",[286,11011,1304],{"class":292},[286,11013,4072],{"class":306},[286,11015,4075],{"class":302},[286,11017,467],{"class":306},[286,11019,11020],{"class":323},"DIAL_TO_ISO2_FALLBACK",[286,11022,2010],{"class":306},[286,11024,11025],{"class":302},"find",[286,11027,11028],{"class":306},"(([, ",[286,11030,9856],{"class":316},[286,11032,11033],{"class":306},"]) ",[286,11035,953],{"class":292},[286,11037,11038],{"class":306}," v ",[286,11040,3935],{"class":292},[286,11042,11043],{"class":306}," iso2)?.[",[286,11045,2679],{"class":323},[286,11047,10900],{"class":306},[286,11049,11050,11052,11054,11056,11058,11060,11062],{"class":288,"line":441},[286,11051,379],{"class":292},[286,11053,382],{"class":306},[286,11055,385],{"class":292},[286,11057,7494],{"class":306},[286,11059,871],{"class":292},[286,11061,2231],{"class":323},[286,11063,487],{"class":306},[286,11065,11066,11068,11071,11073,11075],{"class":288,"line":543},[286,11067,359],{"class":292},[286,11069,11070],{"class":323}," n",[286,11072,338],{"class":292},[286,11074,2211],{"class":302},[286,11076,11077],{"class":306},"(digits);\n",[286,11079,11080,11082,11085,11088,11091,11093,11096,11098,11100],{"class":288,"line":550},[286,11081,418],{"class":292},[286,11083,11084],{"class":306}," Number.",[286,11086,11087],{"class":302},"isFinite",[286,11089,11090],{"class":306},"(n) ",[286,11092,2744],{"class":292},[286,11094,11095],{"class":306}," n ",[286,11097,320],{"class":292},[286,11099,2231],{"class":323},[286,11101,487],{"class":306},[286,11103,11104],{"class":288,"line":595},[286,11105,444],{"class":306},[10,11107,11108,11109,11112],{},"الجدول تسوية معلومة سلفاً. خمسون مدخلاً لا تكفي لتغطية كلّ رمز اتصال، واختيار الخمسين ينحاز إلى رأي: أكثر الدول سكاناً بالإضافة إلى تلك التي أحتاجها شخصياً. المستهلك الذي يمرّر ",[14,11110,11111],{},"default-country=\"678\""," (فانواتو) عند التركيب دون أيّ بيانات دولة مخبّأة لن يحصل على شيء من هذا البحث. بعد ثوانٍ قليلة، حين يعود نداء الشبكة، سيحسم المنتقي القيمة بشكل صحيح. غاية الاحتياطي أن تعمل الحالة الشائعة (95%) دون وميض، والذيل الطويل ينتهي إلى الصحيح.",[10,11114,11115],{},"داخل التطبيق تستطيع انتظار النداء. أمّا في المكتبة فلا تقدر على إلزام كلّ مستهلك بتعليق تركيب مكوّنه انتظاراً لواجهة برمجية طرف ثالث، ولا تستطيع في المقابل شحن 80 كيلوبايت من بيانات الدول بشكل فوري. الجدول المتزامن هو المخرج الثالث.",[93,11117,11119],{"id":11118},"آخر-الاختيارات-كفاصل-عند-التعادل","آخر الاختيارات كفاصل عند التعادل",[10,11121,11122,11123,11125],{},"مطابقة أطول بادئة في الطبقة الثالثة ليست دائماً واضحة بلا لبس. الرمز ",[14,11124,1524],{}," يطابق كتلة NANP كاملة، أكثر من 25 دولة. الأبجدي الأوّل يعني \"ساموا الأمريكية\" أو أيّاً كانت الدولة الأسبق أبجدياً في كلّ مرّة. هذا ليس افتراضاً مفيداً لأيّ أحد.",[10,11127,11128,11129,320],{},"لذلك تقرأ الطبقة الثالثة من ",[14,11130,6257],{},[277,11132,11134],{"className":279,"code":11133,"language":281,"meta":282,"style":282},"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",[14,11135,11136,11150,11162,11200,11231,11243],{"__ignoreMap":282},[286,11137,11138,11140,11143,11145,11148],{"class":288,"line":289},[286,11139,773],{"class":292},[286,11141,11142],{"class":323}," recents",[286,11144,338],{"class":292},[286,11146,11147],{"class":302}," readRecents",[286,11149,617],{"class":306},[286,11151,11152,11154,11157,11159],{"class":288,"line":356},[286,11153,773],{"class":292},[286,11155,11156],{"class":323}," recentHit",[286,11158,338],{"class":292},[286,11160,11161],{"class":306}," recents\n",[286,11163,11164,11167,11170,11172,11174,11176,11178,11181,11183,11185,11188,11190,11192,11195,11197],{"class":288,"line":376},[286,11165,11166],{"class":306},"  .",[286,11168,11169],{"class":302},"map",[286,11171,1640],{"class":306},[286,11173,10291],{"class":316},[286,11175,1115],{"class":306},[286,11177,953],{"class":292},[286,11179,11180],{"class":306}," group.",[286,11182,11025],{"class":302},[286,11184,1640],{"class":306},[286,11186,11187],{"class":316},"c",[286,11189,1115],{"class":306},[286,11191,953],{"class":292},[286,11193,11194],{"class":306}," c.value ",[286,11196,3935],{"class":292},[286,11198,11199],{"class":306}," iso2))\n",[286,11201,11202,11204,11206,11208,11210,11212,11214,11217,11220,11222,11225,11228],{"class":288,"line":415},[286,11203,11166],{"class":306},[286,11205,11025],{"class":302},[286,11207,1640],{"class":306},[286,11209,11187],{"class":316},[286,11211,845],{"class":306},[286,11213,320],{"class":292},[286,11215,11216],{"class":316}," c",[286,11218,11219],{"class":292}," is",[286,11221,9616],{"class":302},[286,11223,11224],{"class":292}," =>",[286,11226,11227],{"class":302}," Boolean",[286,11229,11230],{"class":306},"(c));\n",[286,11232,11233,11235,11238,11240],{"class":288,"line":441},[286,11234,8240],{"class":292},[286,11236,11237],{"class":306}," (recentHit) ",[286,11239,871],{"class":292},[286,11241,11242],{"class":306}," { country: recentHit, nationalNumber };\n",[286,11244,11245,11247,11249,11251],{"class":288,"line":543},[286,11246,871],{"class":292},[286,11248,7922],{"class":306},[286,11250,2679],{"class":323},[286,11252,7927],{"class":306},[10,11254,11255,11256,11259,11260,11262],{},"يكتب المنتقي في ",[14,11257,11258],{},"ali_ui_country_recents_v1"," كلّما اختار المستخدم دولةً صراحةً، ويقرأ المطابِق منها عند حصول الالتباس. إن كنتَ تعيش في كندا وسبق أن استعملتَ الحقل، فأوّل ",[14,11261,7030],{}," تكتبه سيُحلّ إلى كندا. وإن لم تستعمله من قبل، ستحصل على الاحتياطي الأبجدي، وهو خطأ نعم، لكنّه يقع مرّة واحدة فقط.",[10,11264,11265],{},"لمسة صغيرة، لكنّها من ذلك النوع الذي يحوّل \"هذا يعمل تقنياً\" إلى \"هذا مبنيّ بيدِ من استعمله فعلاً\".",[93,11267,11269],{"id":11268},"عن-ماذا-كانت-إعادة-كتابة-المكتبة-حقيقةً","عن ماذا كانت إعادة كتابة المكتبة حقيقةً",[10,11271,11272,11273,11276,11277,11280],{},"كانت النسخة الأصلية معنيّة بالصحّة: اضطراب موضع المؤشّر، وأسباب التحقّق، ومعالجة RTL\u002FLTR, والحالات السبع المحدّدة التي على حقل الهاتف أن يميّز بينها. لم يختفِ أيّ من ذلك. كلّه لا يزال حاضراً في هذه النسخة: الدالّة المسنِدة ",[14,11274,11275],{},"dropLeadingZeros",", والقيم السبع لـ enum المسمّى ",[14,11278,11279],{},"PhoneValidationReason",", ونمط التنسيق ثنائي الاتجاه القائم على غلاف LTR فوق مُدخل RTL. تلك الأشياء لم تكن خاطئة، بل كانت الأساس الذي يُبنى عليه.",[10,11282,11283],{},"ما أضافته إعادة كتابة المكتبة كلّه يتعلّق بحقيقة أنّها لم تعد تخدم تطبيقاً واحداً. فصل بين ISO2 ورمز الاتصال يصمد أمام رموز الاتصال الملتبسة. سلسلة اكتشاف بيئية قابلة للاستبدال وصالحة للعمل دون اتصال. منتقٍ يعمل popover على الحاسوب وdrawer على الهاتف، ولكلّ حالة صنفها الخاصّ. أربعة عشر slot كي لا يشتبك نظام تصميم المستهلك مع المكوّن. جدول رموز اتصال متزامن لأنّ مستهلك المكتبة لا يستطيع انتظار الشبكة. آخر الاختيارات فاصلاً عند التعادل لأنّ \"الأبجدي الأوّل\" هو مظهر الكسل بعينه.",[10,11285,11286],{},"تضاعف عدد الأسطر تقريباً. النسخة الأصلية كانت 160 سطراً للمكوّن مع 540 سطراً للـ composable, بينما النسخة الجديدة عدّة مئات من الأسطر موزّعة على مكوّنَين واثنين من الـ composables. تضاعف السطح البرمجي أكثر من مرّة. ومع ذلك يظلّ النداء بلا تكوين قابلاً للاختصار في ثلاثة أسطر:",[277,11288,11290],{"className":5181,"code":11289,"language":5183,"meta":282,"style":282},"\u003CATellInput v-model:phone=\"phone\" v-model:country=\"country\" show-validation \u002F>\n",[14,11291,11292],{"__ignoreMap":282},[286,11293,11294,11296,11298,11300,11302,11304,11306,11308,11310,11312,11314,11316,11318,11320,11322,11324,11326,11328],{"class":288,"line":289},[286,11295,307],{"class":306},[286,11297,6922],{"class":5192},[286,11299,9241],{"class":302},[286,11301,320],{"class":306},[286,11303,9142],{"class":302},[286,11305,537],{"class":306},[286,11307,5201],{"class":899},[286,11309,9142],{"class":306},[286,11311,5201],{"class":899},[286,11313,9241],{"class":302},[286,11315,320],{"class":306},[286,11317,7012],{"class":302},[286,11319,537],{"class":306},[286,11321,5201],{"class":899},[286,11323,7012],{"class":306},[286,11325,5201],{"class":899},[286,11327,10180],{"class":302},[286,11329,5961],{"class":306},[10,11331,11332],{},"لكلّ prop جديد افتراضي يعكس ما كانت النسخة الداخلية تفعله تلقائياً. من يريد \"حقل هاتف فحسب\" يحصل عليه. ومن يريد \"حقل هاتف بأعلامنا، وقائمة دولنا، ومسند بحثنا، ورسائل أخطائنا\" يجد كلّ نقاط الفصل التي يحتاجها.",[10,11334,11335,11336,11339],{},"الدرس الذي سأحمله معي إلى المرّة القادمة: جعل مكوّن قابلاً للنشر يتعلّق أساساً برصد الآراء التي فرضتَها على نفسك، وتحويل كلّ واحد منها إلى نقطة فصل. كلّ ",[14,11337,11338],{},"restcountries.com"," مثبّت، وكلّ احتياطي \"السعودية ومصر\"، وكلّ قائمة منسدلة ظاهرة بلا انقطاع: تلك الأمور لم تكن خطأً بالنسبة للتطبيق، لكنّها الأمور التي لن تنجو مع المستهلك التالي. ابحث عنها، وسمِّها، واستبدل كلاً منها بافتراضي مع إمكانية تجاوز. الافتراضي يُبقي مستهلكك القديم راضياً، والتجاوز يفتح الباب للجديد.",[5100,11341,11342],{},"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":282,"searchDepth":356,"depth":356,"links":11344},[11345,11346,11347,11348,11349,11350,11351,11352],{"id":6943,"depth":356,"text":6944},{"id":7327,"depth":356,"text":7328},{"id":8308,"depth":356,"text":8309},{"id":9212,"depth":356,"text":9213},{"id":9365,"depth":356,"text":9366},{"id":10435,"depth":356,"text":10436},{"id":11118,"depth":356,"text":11119},{"id":11268,"depth":356,"text":11269},"2026-05-21","تحويل حقل هاتف من داخل تطبيق واحد إلى مكوّن Vue مُنشَر استوجب تغييرات جوهرية. تجربة استخدام تكتشف الدولة قبل أيّ تدخّل، وفصل بين رمز ISO2 ورقم الاتصال، ومنتقي متجاوب عبر Popover وDrawer، وSlots لكل شيء.",[6899,6905,11356,11357,11358,11359,9226,11360,11361,5141],"حقل هاتف","libphonenumber-js","اكتشاف الدولة","Popover متجاوب","defineModel","Slots",{},"\u002Fwrites-ar\u002Fatell-input-the-library-rewrite",{"title":6914,"description":11354},"writes-ar\u002Fatell-input-the-library-rewrite","8wJPO0VSknxZ5ZTju80YmwbagcwC1H2lmP5kn104P24",1793901269473]