[{"data":1,"prerenderedAt":10532},["ShallowReactive",2],{"writesAr-\u002Fwrites\u002Finside-a-skeleton-self-generating-skeleton-loaders":3,"related-writesAr-\u002Fwrites\u002Finside-a-skeleton-self-generating-skeleton-loaders":1854},{"id":4,"title":5,"body":6,"date":1833,"description":1834,"description_ar":1835,"draft":1836,"extension":1837,"image":1835,"keywords":1838,"lang":1848,"meta":1849,"navigation":323,"path":1850,"seo":1851,"stem":1852,"title_ar":1835,"updatedAt":1835,"__hash__":1853},"writesAr\u002Fwrites-ar\u002Finside-a-skeleton-self-generating-skeleton-loaders.md","داخل @alikhalilll\u002Fa-skeleton، مُحمِّلات skeleton تولّد نفسها بنفسها",{"type":7,"value":8,"toc":1822},"minimark",[9,13,21,24,29,32,38,101,106,137,166,173,187,190,194,201,223,230,241,244,286,289,388,416,422,426,433,478,503,509,512,516,527,943,980,983,1053,1056,1173,1176,1199,1202,1206,1209,1215,1251,1270,1290,1299,1303,1351,1354,1361,1437,1450,1454,1496,1503,1514,1603,1610,1719,1741,1744,1748,1767,1785,1792,1796,1807,1813,1816,1819],[10,11,12],"p",{},"مُحمِّلات الـ skeleton في النهاية واجهة مكرّرة. تبني بطاقة، ثم تبني بطاقة أخرى من مستطيلات رمادية تشبهها، وكلما عدّلت البطاقة الحقيقية نسيتَ أن تعدّل الـ placeholder معها. بعد ثلاثة أسابيع تجد العنوان محاذيًا لليسار في المكوّن الفعلي وموسّطًا في الـ skeleton، ولا ينتبه أحد حتى يمرّ مصمّم بالصفحة على بيئة الـ staging.",[10,14,15,16,20],{},"بنيتُ ",[17,18,19],"code",{},"@alikhalilll\u002Fa-skeleton"," على فرضية مختلفة تمامًا: الـ skeleton يُشتقّ من المكوّن الحقيقي نفسه. تلفّ بطاقتك مرة واحدة فقط، وتتكفّل المكتبة باستنتاج الـ placeholder من قراءة البطاقة. لا قالب موازٍ. لا مستطيلات رمادية تلاحقها بالتحديث. أيّ تعديل تجريه على البطاقة يلتقطه الـ skeleton في العرض التالي دون تدخّل منك.",[10,22,23],{},"ما أتحدّث عنه هنا هو ما يقع تحت السطح: عقد التأليف الذي يجعل هذا الأمر ممكنًا، واستراتيجيات العرض الثلاث وما تدفعه كلٌّ منها من مقايضات، وخط أنابيب الالتقاط خلف وضع clone تحديدًا. هذا الأخير هو ما سأحمله معي إلى أيّ مشكلة مستقبلية من نوع \"أريد نسخة من شكل هذا الشيء الآن\".",[25,26,28],"h2",{"id":27},"العقد-أبقِ-الوسوم-غير-مشروطة-وقيّد-المحتوى-وحده","العقد: أبقِ الوسوم غير مشروطة، وقيّد المحتوى وحده",[10,30,31],{},"قبل أيّ شيء، هناك قاعدة واحدة. هي الأساس الذي تستند إليه كل استراتيجية أذكرها لاحقًا، ولذلك أضعها في الصدارة.",[10,33,34],{},[35,36,37],"strong",{},"خطأ:",[39,40,45],"pre",{"className":41,"code":42,"language":43,"meta":44,"style":44},"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","",[17,46,47,79,85,91],{"__ignoreMap":44},[48,49,52,56,60,64,67,71,74,76],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sVt8B","\u003C",[48,57,59],{"class":58},"s9eBZ","div",[48,61,63],{"class":62},"szBVR"," v-if",[48,65,66],{"class":54},"=",[48,68,70],{"class":69},"sZZnC","\"",[48,72,73],{"class":54},"user",[48,75,70],{"class":69},[48,77,78],{"class":54},">\n",[48,80,82],{"class":50,"line":81},2,[48,83,84],{"class":54},"  \u003Ch3>{{ user.name }}\u003C\u002Fh3>\n",[48,86,88],{"class":50,"line":87},3,[48,89,90],{"class":54},"  \u003Cp>{{ user.bio }}\u003C\u002Fp>\n",[48,92,94,97,99],{"class":50,"line":93},4,[48,95,96],{"class":54},"\u003C\u002F",[48,98,59],{"class":58},[48,100,78],{"class":54},[10,102,103],{},[35,104,105],{},"صحيح:",[39,107,109],{"className":41,"code":108,"language":43,"meta":44,"style":44},"\u003Cdiv>\n  \u003Ch3>{{ user?.name }}\u003C\u002Fh3>\n  \u003Cp>{{ user?.bio }}\u003C\u002Fp>\n\u003C\u002Fdiv>\n",[17,110,111,119,124,129],{"__ignoreMap":44},[48,112,113,115,117],{"class":50,"line":51},[48,114,55],{"class":54},[48,116,59],{"class":58},[48,118,78],{"class":54},[48,120,121],{"class":50,"line":81},[48,122,123],{"class":54},"  \u003Ch3>{{ user?.name }}\u003C\u002Fh3>\n",[48,125,126],{"class":50,"line":87},[48,127,128],{"class":54},"  \u003Cp>{{ user?.bio }}\u003C\u002Fp>\n",[48,130,131,133,135],{"class":50,"line":93},[48,132,96],{"class":54},[48,134,59],{"class":58},[48,136,78],{"class":54},[10,138,139,140,143,144,147,148,150,151,154,155,158,159,158,162,165],{},"الـ walker، سواء كان يقرأ الـ DOM الحيّ أو شجرة الـ vnode، يحتاج أن يرى الشكل ذاته أثناء التحميل وبعد وصول البيانات. لو غلّفتَ العنوان بـ ",[17,141,142],{},"v-if=\"user\"","، فسيرى الـ walker عقدة تعليق أثناء التحميل ثم ",[17,145,146],{},"\u003Ch3>"," بعد ذلك، ولن يستطيع أن يتنبّأ بهندسة الـ placeholder. لكن مع استخدام الاستيفاء الاختياري (optional chaining) يبقى ",[17,149,146],{}," موجودًا دائمًا؛ يكون نصّه فارغًا أثناء التحميل، ويصنّف الـ walker الوسوم الحاملة للنص الفارغة (",[17,152,153],{},"\u003Ch1>","–",[17,156,157],{},"\u003Ch6>",", ",[17,160,161],{},"\u003Cp>",[17,163,164],{},"\u003Cspan>",", وغيرها) على أنها أشرطة نص shimmer بارتفاع العنوان الطبيعي.",[10,167,168,169,172],{},"الوسوم هي المخطط. المحتوى هو الحمولة. تستطيع أن تُبدّل الحمولة بـ ",[17,170,171],{},"null"," دون أن يلاحظ المخطط شيئًا. هذا الانضباط البسيط وحده هو ما يفتح الباب لكل ما يأتي بعده.",[10,174,175,176,179,180,182,183,186],{},"النصف الآخر من العقد هو مخرج الطوارئ ",[17,177,178],{},"data-skeleton-ignore",". لو كان جزء من المكوّن مجرد زخرفة بصرية (خلفية تدرّج، شارة علامة تجارية ثابتة، لمسة SVG) ولا ترغب باستبداله بكتلة رمادية، ضع عليه ",[17,181,178],{}," فيتجاهله الـ walker تمامًا؛ ويُعرض العنصر الحقيقي كما هو في الحالتين. أما ",[17,184,185],{},"data-skeleton-stop"," فيقول للـ walker: \"لا تدخل داخل هذا العنصر؛ اعرضه ككتلة واحدة\". أستخدمه عادةً مع الـ widgets الذرّية حين تكون البنية الداخلية مجرد ضجيج بصري.",[10,188,189],{},"قاعدتان اثنتان وسِمَتان اثنتان. هذا هو كل ما يلزمك تعلّمه من سطح التأليف.",[25,191,193],{"id":192},"وضع-clone-التقاط-الرسم-النهائي-وإعادة-عرض-البكسلات","وضع Clone: التقاط الرسم النهائي وإعادة عرض البكسلات",[10,195,196,197,200],{},"Clone هو الوضع الافتراضي، وهو أهمّ ما في المكتبة برأيي. أغلب مكتبات الـ skeleton تتعثّر لأنها تحاول التفكير على مستوى الـ CSS cascade. تنظر إلى أسماء الفئات. تنظر إلى الأنماط المضمّنة. تفترض قيمة الـ border radius بناءً على بادئة ",[17,198,199],{},"rounded-*",". باختصار، تخمّن.",[10,202,203,204,207,208,211,212,215,216,219,220,222],{},"وضع clone يتخلّى عن التخمين كليًا. يركّب الـ slot خارج الشاشة داخل capture host مضبوط على ",[17,205,206],{},"visibility: hidden","، ويترك المتصفح ينهي كامل الـ layout وحلّ الأنماط، ثم يقرأ ",[35,209,210],{},"النتيجة النهائية المحسوبة"," لكل عنصر عبر ",[17,213,214],{},"getComputedStyle()",". مهما كان نظام التنسيق لديك (أدوات Tailwind، أنماط scoped بأسماء فئات مُجزّأة، CSS-in-JS وقت التشغيل، رموز DaisyUI الدلالية، أو حتى سمات ",[17,217,218],{},"style"," مضمّنة يمرّرها مكوّن أب)، فبحلول اللحظة التي يعمل فيها ",[17,221,214],{}," تكون كلّها قد انهارت إلى إجابة قانونية واحدة لكل خاصية. يأخذ المحرك تلك الإجابة الجاهزة ويعيد عرضها.",[10,224,225,226,229],{},"السطح الذي يلتقطه المحرك شامل فعلًا. حدود مستقلة لكل حافة (فبطاقة لها ",[17,227,228],{},"border-bottom"," مختلف تُقرأ كما ينبغي). أنصاف أقطار مستقلة لكل زاوية (فشكل pill غير المتماثل لا يتراجع إلى مجرد مستطيل مدوّر). لون الخلفية وصورتها. Box-shadow. Opacity. Filter. Transform. Typography. Mix-blend-mode. كل عقدة ملتقطة تحمل حمولة أنماط مضمّنة مجمّدة خاصة بها؛ حلقة العرض لا تخصّص شيئًا لأيّ عقدة، بل تقرأ ما هو مجهّز مسبقًا.",[10,231,232,233,236,237,240],{},"النص هو أكثر ما يخطئ فيه الآخرون، وأكثر ما تصيبه ",[17,234,235],{},"a-skeleton"," بدقّة. بدلًا من تخمين نقاط الالتفاف (\"لنفترض أن النص يلتف كل N حرفًا، ولنفترض أن السطر الأخير عرضه 70%\")، يستخدم المحرك ",[17,238,239],{},"Range.getClientRects()"," ليلتقط المستطيل الفعلي المعروض لكل سطر نصّي بدقّة: left, top, width, height. عنوان موسّط من ثلاثة أسطر على سطح المكتب يصبح عنوانًا موسّطًا من سطرين على الجوّال، وتتقلّص المستطيلات الملتقطة معه. مواضع السطر الأخير في اتجاه RTL، والنص المضبوط (justified)، وفواصل الأسطر اليدوية، كلّها تُعاد بنسبة 1:1 لأنها قِيست فعلًا ولم تُخمَّن.",[10,242,243],{},"واجهة الـ wrapper لكل هذا هي prop واحدة فقط:",[39,245,247],{"className":41,"code":246,"language":43,"meta":44,"style":44},"\u003CASkeleton :loading=\"loading\">\n  \u003CSomeRichComponent \u002F>\n\u003C\u002FASkeleton>\n",[17,248,249,273,278],{"__ignoreMap":44},[48,250,251,253,256,259,263,265,267,269,271],{"class":50,"line":51},[48,252,55],{"class":54},[48,254,255],{"class":58},"ASkeleton",[48,257,258],{"class":54}," :",[48,260,262],{"class":261},"sScJk","loading",[48,264,66],{"class":54},[48,266,70],{"class":69},[48,268,262],{"class":54},[48,270,70],{"class":69},[48,272,78],{"class":54},[48,274,275],{"class":50,"line":81},[48,276,277],{"class":54},"  \u003CSomeRichComponent \u002F>\n",[48,279,280,282,284],{"class":50,"line":87},[48,281,96],{"class":54},[48,283,255],{"class":58},[48,285,78],{"class":54},[10,287,288],{},"ولو أردتَ بناء تدفّق التقاط وإعادة عرض خاص بك، فالدالة الأساسية معرّضة كتصدير عام:",[39,290,294],{"className":291,"code":292,"language":293,"meta":44,"style":44},"language-ts shiki shiki-themes github-light github-dark","import { captureSnapshot, type CaptureSnapshot } from '@alikhalilll\u002Fa-skeleton';\n\nconst snap: CaptureSnapshot = captureSnapshot(myRootEl, {\n  maxDepth: 12,\n  maxNodes: 800,\n  minSize: 4,\n});\n","ts",[17,295,296,319,325,349,360,371,382],{"__ignoreMap":44},[48,297,298,301,304,307,310,313,316],{"class":50,"line":51},[48,299,300],{"class":62},"import",[48,302,303],{"class":54}," { captureSnapshot, ",[48,305,306],{"class":62},"type",[48,308,309],{"class":54}," CaptureSnapshot } ",[48,311,312],{"class":62},"from",[48,314,315],{"class":69}," '@alikhalilll\u002Fa-skeleton'",[48,317,318],{"class":54},";\n",[48,320,321],{"class":50,"line":81},[48,322,324],{"emptyLinePlaceholder":323},true,"\n",[48,326,327,330,334,337,340,343,346],{"class":50,"line":87},[48,328,329],{"class":62},"const",[48,331,333],{"class":332},"sj4cs"," snap",[48,335,336],{"class":62},":",[48,338,339],{"class":261}," CaptureSnapshot",[48,341,342],{"class":62}," =",[48,344,345],{"class":261}," captureSnapshot",[48,347,348],{"class":54},"(myRootEl, {\n",[48,350,351,354,357],{"class":50,"line":93},[48,352,353],{"class":54},"  maxDepth: ",[48,355,356],{"class":332},"12",[48,358,359],{"class":54},",\n",[48,361,363,366,369],{"class":50,"line":362},5,[48,364,365],{"class":54},"  maxNodes: ",[48,367,368],{"class":332},"800",[48,370,359],{"class":54},[48,372,374,377,380],{"class":50,"line":373},6,[48,375,376],{"class":54},"  minSize: ",[48,378,379],{"class":332},"4",[48,381,359],{"class":54},[48,383,385],{"class":50,"line":384},7,[48,386,387],{"class":54},"});\n",[10,389,390,393,394,393,397,400,401,404,405,408,409,412,413,415],{},[17,391,392],{},"maxDepth"," و",[17,395,396],{},"maxNodes",[17,398,399],{},"minSize"," هي حدود الميزانية. بفضلها لا يتوقف الخيط الرئيسي عند جدول من 5000 صفّ. عندما يبلغ الـ walk الحدّ يتوقّف، ويضبط ",[17,402,403],{},"truncated: true","، ثم يسجّل الـ wrapper ",[17,406,407],{},"console.warn"," لمرة واحدة لكل ",[17,410,411],{},"cacheKey"," كي تنتبه للأمر أثناء التطوير لا في الإنتاج. أما ",[17,414,399],{}," فيُسقط أيّ شيء أصغر من أربعة بكسلات CSS على أيّ محور، وهو ما يحذف الخيوط الرفيعة ونقاط الفراغ التي كانت ستظهر لولا ذلك كقصاصات ملوّنة داخل الـ skeleton.",[10,417,418,419,421],{},"المقايضة التي يدفعها وضع clone هي أنه يعمل على العميل فقط. تحتاج إلى DOM حقيقي، وإلى ",[17,420,214],{},"، وإلى متصفح يقرأ الرسم النهائي. على الخادم يُعرض markup الـ slot الحقيقي كالمعتاد، ولا يبدأ الالتقاط وإعادة العرض إلا بعد الـ hydration. لو احتجتَ إلى placeholder جاهز من الخادم قبل انتهاء الـ hydration، فذلك بالضبط ما وُجد وضع mirror من أجله.",[25,423,425],{"id":424},"وضع-mirror-امشِ-على-الـ-vnodes-وسلّمها-إلى-الخادم","وضع Mirror: امشِ على الـ vnodes وسلّمها إلى الخادم",[10,427,428,429,432],{},"Mirror يبدّل المحرك من أساسه. بدلًا من تركيب الـ slot وقراءة الـ DOM، يمشي على شجرة الـ vnode مباشرةً وقت العرض. لا وصول إلى ",[17,430,431],{},"window",". متوافق مع الـ hydration. قابل للعرض من الخادم.",[39,434,436],{"className":41,"code":435,"language":43,"meta":44,"style":44},"\u003CASkeleton mode=\"mirror\" :loading=\"loading\">\n  \u003CSomeRichComponent \u002F>\n\u003C\u002FASkeleton>\n",[17,437,438,466,470],{"__ignoreMap":44},[48,439,440,442,444,447,449,452,454,456,458,460,462,464],{"class":50,"line":51},[48,441,55],{"class":54},[48,443,255],{"class":58},[48,445,446],{"class":261}," mode",[48,448,66],{"class":54},[48,450,451],{"class":69},"\"mirror\"",[48,453,258],{"class":54},[48,455,262],{"class":261},[48,457,66],{"class":54},[48,459,70],{"class":69},[48,461,262],{"class":54},[48,463,70],{"class":69},[48,465,78],{"class":54},[48,467,468],{"class":50,"line":81},[48,469,277],{"class":54},[48,471,472,474,476],{"class":50,"line":87},[48,473,96],{"class":54},[48,475,255],{"class":58},[48,477,78],{"class":54},[10,479,480,481,484,485,488,489,158,492,158,495,498,499,502],{},"يحافظ الـ walker على وسم كل عنصر وعلى فئته الأصلية. الأوراق الحاملة للنص تتحوّل إلى ",[17,482,483],{},"\u003Cspan class=\"a-skel-text-content\">"," (نص شفاف فوق خلفية skeleton) مع ",[17,486,487],{},"box-decoration-break: clone"," كي يلتفّ الـ shimmer حول عرض النص المعروض بدقّة سطرًا سطرًا. أما الوسوم الذرّية والتفاعلية (",[17,490,491],{},"\u003Cimg>",[17,493,494],{},"\u003Cbutton>",[17,496,497],{},"\u003Csvg>",") فتصبح ",[17,500,501],{},"\u003Cdiv class=\"a-skel-block\">"," بأبعاد مستمدّة من فئتها.",[10,504,505,506,508],{},"مكسب Mirror الأساسي هو دعم الـ SSR. ومقايضته هي الدقّة. لأن الـ walker لا يملك ",[17,507,214],{},"، فهو لا يرى إلا ما هو مؤلَّف صراحةً: أسماء الفئات الثابتة والأنماط المضمّنة. لو كانت هوية مكوّنك تأتي من قاعدة CSS-in-JS مُجزّأة أو من كتلة نمط scoped، فلن يلتقطها الـ mirror كما يفعل clone. لكن في معظم التخطيطات تكفي الفئة الثابتة، وأمان الـ SSR يستحق أكثر من آخر 5% من التطابق البكسلي. أما المكوّنات المعقّدة بصريًا فمكانها الطبيعي هو clone.",[10,510,511],{},"كلا الوضعين يتشاركان خط أنابيب الحركة نفسه، والـ cache نفسه، وقاعدة الـ a11y نفسها. التبديل بينهما لا يكلّفك أكثر من prop واحدة.",[25,513,515],{"id":514},"وضع-structural-أشجار-في-الـ-flow-الطبيعي-محفوظة-بين-الجلسات","وضع Structural: أشجار في الـ flow الطبيعي، محفوظة بين الجلسات",[10,517,518,519,522,523,526],{},"الاستراتيجية الثالثة ألجأ إليها حين لا يكون الـ wrapper هو وحدة التنسيق المناسبة لديّ: حين أريد لشكل ملتقط أن يعيش عبر عدة نُسخ، أو أن يستمرّ بين الجلسات، أو أن يعيد التدفّق مع العنصر الأب عند تغيير حجم الـ viewport بدل تثبيته على إحداثيات مطلقة. لتلك الحالة أستخدم ",[17,520,521],{},"useSkeleton()"," مع ",[17,524,525],{},"\u003CASkeletonLayer>",".",[39,528,530],{"className":41,"code":529,"language":43,"meta":44,"style":44},"\u003Cscript setup lang=\"ts\">\nimport { computed, ref } from 'vue';\nimport { useSkeleton } from '@alikhalilll\u002Fa-skeleton';\n\nconst props = defineProps\u003C{ userId: string }>();\nconst user = ref(null);\nconst loading = computed(() => user.value === null);\nconst containerRef = ref\u003CHTMLElement | null>(null);\n\nconst { shape, clear } = useSkeleton({\n  cacheKey: `user-card:${props.userId}`,\n  target: () => (loading.value ? null : containerRef.value),\n  persist: true,\n});\n\nfetchUser(props.userId).then((u) => (user.value = u));\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv ref=\"containerRef\">\n    \u003CASkeletonLayer v-if=\"loading && shape\" :shape=\"shape\" \u002F>\n    \u003CColdStartFallback v-else-if=\"loading\" \u002F>\n    \u003CUserCard v-else :data=\"user\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[17,531,532,552,566,579,583,610,630,659,688,693,720,741,765,776,781,786,817,826,831,841,858,885,903,924,934],{"__ignoreMap":44},[48,533,534,536,539,542,545,547,550],{"class":50,"line":51},[48,535,55],{"class":54},[48,537,538],{"class":58},"script",[48,540,541],{"class":261}," setup",[48,543,544],{"class":261}," lang",[48,546,66],{"class":54},[48,548,549],{"class":69},"\"ts\"",[48,551,78],{"class":54},[48,553,554,556,559,561,564],{"class":50,"line":81},[48,555,300],{"class":62},[48,557,558],{"class":54}," { computed, ref } ",[48,560,312],{"class":62},[48,562,563],{"class":69}," 'vue'",[48,565,318],{"class":54},[48,567,568,570,573,575,577],{"class":50,"line":87},[48,569,300],{"class":62},[48,571,572],{"class":54}," { useSkeleton } ",[48,574,312],{"class":62},[48,576,315],{"class":69},[48,578,318],{"class":54},[48,580,581],{"class":50,"line":93},[48,582,324],{"emptyLinePlaceholder":323},[48,584,585,587,590,592,595,598,602,604,607],{"class":50,"line":362},[48,586,329],{"class":62},[48,588,589],{"class":332}," props",[48,591,342],{"class":62},[48,593,594],{"class":261}," defineProps",[48,596,597],{"class":54},"\u003C{ ",[48,599,601],{"class":600},"s4XuR","userId",[48,603,336],{"class":62},[48,605,606],{"class":332}," string",[48,608,609],{"class":54}," }>();\n",[48,611,612,614,617,619,622,625,627],{"class":50,"line":373},[48,613,329],{"class":62},[48,615,616],{"class":332}," user",[48,618,342],{"class":62},[48,620,621],{"class":261}," ref",[48,623,624],{"class":54},"(",[48,626,171],{"class":332},[48,628,629],{"class":54},");\n",[48,631,632,634,637,639,642,645,648,651,654,657],{"class":50,"line":384},[48,633,329],{"class":62},[48,635,636],{"class":332}," loading",[48,638,342],{"class":62},[48,640,641],{"class":261}," computed",[48,643,644],{"class":54},"(() ",[48,646,647],{"class":62},"=>",[48,649,650],{"class":54}," user.value ",[48,652,653],{"class":62},"===",[48,655,656],{"class":332}," null",[48,658,629],{"class":54},[48,660,662,664,667,669,671,673,676,679,681,684,686],{"class":50,"line":661},8,[48,663,329],{"class":62},[48,665,666],{"class":332}," containerRef",[48,668,342],{"class":62},[48,670,621],{"class":261},[48,672,55],{"class":54},[48,674,675],{"class":261},"HTMLElement",[48,677,678],{"class":62}," |",[48,680,656],{"class":332},[48,682,683],{"class":54},">(",[48,685,171],{"class":332},[48,687,629],{"class":54},[48,689,691],{"class":50,"line":690},9,[48,692,324],{"emptyLinePlaceholder":323},[48,694,696,698,701,704,706,709,712,714,717],{"class":50,"line":695},10,[48,697,329],{"class":62},[48,699,700],{"class":54}," { ",[48,702,703],{"class":332},"shape",[48,705,158],{"class":54},[48,707,708],{"class":332},"clear",[48,710,711],{"class":54}," } ",[48,713,66],{"class":62},[48,715,716],{"class":261}," useSkeleton",[48,718,719],{"class":54},"({\n",[48,721,723,726,729,732,734,736,739],{"class":50,"line":722},11,[48,724,725],{"class":54},"  cacheKey: ",[48,727,728],{"class":69},"`user-card:${",[48,730,731],{"class":54},"props",[48,733,526],{"class":69},[48,735,601],{"class":54},[48,737,738],{"class":69},"}`",[48,740,359],{"class":54},[48,742,744,747,750,752,755,758,760,762],{"class":50,"line":743},12,[48,745,746],{"class":261},"  target",[48,748,749],{"class":54},": () ",[48,751,647],{"class":62},[48,753,754],{"class":54}," (loading.value ",[48,756,757],{"class":62},"?",[48,759,656],{"class":332},[48,761,258],{"class":62},[48,763,764],{"class":54}," containerRef.value),\n",[48,766,768,771,774],{"class":50,"line":767},13,[48,769,770],{"class":54},"  persist: ",[48,772,773],{"class":332},"true",[48,775,359],{"class":54},[48,777,779],{"class":50,"line":778},14,[48,780,387],{"class":54},[48,782,784],{"class":50,"line":783},15,[48,785,324],{"emptyLinePlaceholder":323},[48,787,789,792,795,798,801,804,807,809,812,814],{"class":50,"line":788},16,[48,790,791],{"class":261},"fetchUser",[48,793,794],{"class":54},"(props.userId).",[48,796,797],{"class":261},"then",[48,799,800],{"class":54},"((",[48,802,803],{"class":600},"u",[48,805,806],{"class":54},") ",[48,808,647],{"class":62},[48,810,811],{"class":54}," (user.value ",[48,813,66],{"class":62},[48,815,816],{"class":54}," u));\n",[48,818,820,822,824],{"class":50,"line":819},17,[48,821,96],{"class":54},[48,823,538],{"class":58},[48,825,78],{"class":54},[48,827,829],{"class":50,"line":828},18,[48,830,324],{"emptyLinePlaceholder":323},[48,832,834,836,839],{"class":50,"line":833},19,[48,835,55],{"class":54},[48,837,838],{"class":58},"template",[48,840,78],{"class":54},[48,842,844,847,849,851,853,856],{"class":50,"line":843},20,[48,845,846],{"class":54},"  \u003C",[48,848,59],{"class":58},[48,850,621],{"class":261},[48,852,66],{"class":54},[48,854,855],{"class":69},"\"containerRef\"",[48,857,78],{"class":54},[48,859,861,864,867,869,871,874,877,879,882],{"class":50,"line":860},21,[48,862,863],{"class":54},"    \u003C",[48,865,866],{"class":58},"ASkeletonLayer",[48,868,63],{"class":261},[48,870,66],{"class":54},[48,872,873],{"class":69},"\"loading && shape\"",[48,875,876],{"class":261}," :shape",[48,878,66],{"class":54},[48,880,881],{"class":69},"\"shape\"",[48,883,884],{"class":54}," \u002F>\n",[48,886,888,890,893,896,898,901],{"class":50,"line":887},22,[48,889,863],{"class":54},[48,891,892],{"class":58},"ColdStartFallback",[48,894,895],{"class":261}," v-else-if",[48,897,66],{"class":54},[48,899,900],{"class":69},"\"loading\"",[48,902,884],{"class":54},[48,904,906,908,911,914,917,919,922],{"class":50,"line":905},23,[48,907,863],{"class":54},[48,909,910],{"class":58},"UserCard",[48,912,913],{"class":261}," v-else",[48,915,916],{"class":261}," :data",[48,918,66],{"class":54},[48,920,921],{"class":69},"\"user\"",[48,923,884],{"class":54},[48,925,927,930,932],{"class":50,"line":926},24,[48,928,929],{"class":54},"  \u003C\u002F",[48,931,59],{"class":58},[48,933,78],{"class":54},[48,935,937,939,941],{"class":50,"line":936},25,[48,938,96],{"class":54},[48,940,838],{"class":58},[48,942,78],{"class":54},[10,944,945,946,949,950,158,953,158,956,158,959,158,962,158,965,968,969,971,972,975,976,979],{},"يلتقط ",[17,947,948],{},"walkStructural()"," الشكل، وهو walker شجريّ يحافظ على وسوم الحاويات وفئاتها الأصلية وعلى CSS الـ layout المحلول (",[17,951,952],{},"display",[17,954,955],{},"flex-*",[17,957,958],{},"gap",[17,960,961],{},"padding",[17,963,964],{},"grid-*",[17,966,967],{},"box-sizing","). أما الأوراق فتحمل عرضًا وارتفاعًا مضمّنَين إضافةً إلى الإشارات البصرية (background, border, radius). عندما يعيد ",[17,970,525],{}," عرض الشجرة، فإنه يفعل ذلك في ",[35,973,974],{},"الـ flow الطبيعي",": الـ ",[17,977,978],{},"\u003Cdiv class=\"flex flex-col gap-4 p-4\">"," الملتقط يُعرض كحاوية flex فعلية، وكتل الـ shimmer تجلس داخلها كأبناء flex. تغيّر حجم الـ viewport فيعيد الـ layout التدفّق، ولا يتأخّر الـ skeleton خلفه كما يحدث في إعادة عرض clone ذات المواضع المطلقة.",[10,981,982],{},"فلو كان لديك هذا الـ markup الفعلي:",[39,984,988],{"className":985,"code":986,"language":987,"meta":44,"style":44},"language-html shiki shiki-themes github-light github-dark","\u003Cdiv class=\"flex flex-col gap-4 p-4\">\n  \u003Ch3>...\u003C\u002Fh3>\n  \u003Cp>...\u003C\u002Fp>\n  \u003Cbutton>...\u003C\u002Fbutton>\n\u003C\u002Fdiv>\n","html",[17,989,990,1006,1020,1032,1045],{"__ignoreMap":44},[48,991,992,994,996,999,1001,1004],{"class":50,"line":51},[48,993,55],{"class":54},[48,995,59],{"class":58},[48,997,998],{"class":261}," class",[48,1000,66],{"class":54},[48,1002,1003],{"class":69},"\"flex flex-col gap-4 p-4\"",[48,1005,78],{"class":54},[48,1007,1008,1010,1013,1016,1018],{"class":50,"line":81},[48,1009,846],{"class":54},[48,1011,1012],{"class":58},"h3",[48,1014,1015],{"class":54},">...\u003C\u002F",[48,1017,1012],{"class":58},[48,1019,78],{"class":54},[48,1021,1022,1024,1026,1028,1030],{"class":50,"line":87},[48,1023,846],{"class":54},[48,1025,10],{"class":58},[48,1027,1015],{"class":54},[48,1029,10],{"class":58},[48,1031,78],{"class":54},[48,1033,1034,1036,1039,1041,1043],{"class":50,"line":93},[48,1035,846],{"class":54},[48,1037,1038],{"class":58},"button",[48,1040,1015],{"class":54},[48,1042,1038],{"class":58},[48,1044,78],{"class":54},[48,1046,1047,1049,1051],{"class":50,"line":362},[48,1048,96],{"class":54},[48,1050,59],{"class":58},[48,1052,78],{"class":54},[10,1054,1055],{},"فإن الـ layer يعيد عرضه هكذا:",[39,1057,1059],{"className":985,"code":1058,"language":987,"meta":44,"style":44},"\u003Cdiv\n  class=\"flex flex-col gap-4 p-4\"\n  style=\"display: flex; flex-direction: column; gap: 16px; padding: 16px; box-sizing: border-box\"\n>\n  \u003Cdiv class=\"a-skel\" style=\"width: 200px; height: 24px; ...\" \u002F>\n  \u003Cdiv class=\"a-skel\" style=\"width: 280px; height: 16px; ...\" \u002F>\n  \u003Cdiv class=\"a-skel\" style=\"width: 120px; height: 36px; ...\" \u002F>\n\u003C\u002Fdiv>\n",[17,1060,1061,1068,1078,1088,1092,1119,1142,1165],{"__ignoreMap":44},[48,1062,1063,1065],{"class":50,"line":51},[48,1064,55],{"class":54},[48,1066,1067],{"class":58},"div\n",[48,1069,1070,1073,1075],{"class":50,"line":81},[48,1071,1072],{"class":261},"  class",[48,1074,66],{"class":54},[48,1076,1077],{"class":69},"\"flex flex-col gap-4 p-4\"\n",[48,1079,1080,1083,1085],{"class":50,"line":87},[48,1081,1082],{"class":261},"  style",[48,1084,66],{"class":54},[48,1086,1087],{"class":69},"\"display: flex; flex-direction: column; gap: 16px; padding: 16px; box-sizing: border-box\"\n",[48,1089,1090],{"class":50,"line":93},[48,1091,78],{"class":54},[48,1093,1094,1096,1098,1100,1102,1105,1108,1110,1113,1117],{"class":50,"line":362},[48,1095,846],{"class":54},[48,1097,59],{"class":58},[48,1099,998],{"class":261},[48,1101,66],{"class":54},[48,1103,1104],{"class":69},"\"a-skel\"",[48,1106,1107],{"class":261}," style",[48,1109,66],{"class":54},[48,1111,1112],{"class":69},"\"width: 200px; height: 24px; ...\"",[48,1114,1116],{"class":1115},"s7hpK"," \u002F",[48,1118,78],{"class":54},[48,1120,1121,1123,1125,1127,1129,1131,1133,1135,1138,1140],{"class":50,"line":373},[48,1122,846],{"class":54},[48,1124,59],{"class":58},[48,1126,998],{"class":261},[48,1128,66],{"class":54},[48,1130,1104],{"class":69},[48,1132,1107],{"class":261},[48,1134,66],{"class":54},[48,1136,1137],{"class":69},"\"width: 280px; height: 16px; ...\"",[48,1139,1116],{"class":1115},[48,1141,78],{"class":54},[48,1143,1144,1146,1148,1150,1152,1154,1156,1158,1161,1163],{"class":50,"line":384},[48,1145,846],{"class":54},[48,1147,59],{"class":58},[48,1149,998],{"class":261},[48,1151,66],{"class":54},[48,1153,1104],{"class":69},[48,1155,1107],{"class":261},[48,1157,66],{"class":54},[48,1159,1160],{"class":69},"\"width: 120px; height: 36px; ...\"",[48,1162,1116],{"class":1115},[48,1164,78],{"class":54},[48,1166,1167,1169,1171],{"class":50,"line":661},[48,1168,96],{"class":54},[48,1170,59],{"class":58},[48,1172,78],{"class":54},[10,1174,1175],{},"تبقى الفئة الأصلية موجودة كي يظلّ نظام الـ CSS الـ utility-first ساريًا، ويُضمَّن CSS الـ layout المحلول كنسخة احتياطية كي يبدو الـ skeleton صحيحًا حتى لو لم تُحمَّل ورقة الأنماط عند نقطة التركيب. يهمّ هذا كثيرًا لو كنت تعرض الشكل الملتقط داخل سياق CSS مختلف، مثل نافذة معاينة على هيئة بريد إلكتروني، أو shell معروض من الخادم لا يجلب Tailwind أصلًا.",[10,1177,1178,1179,1182,1183,1186,1187,1190,1191,1194,1195,1198],{},"الميزة الأخرى التي يمنحك إياها وضع structural هي الاستمرارية. تجعل ",[17,1180,1181],{},"persist: true"," الشكل الملتقط ينعكس إلى ",[17,1184,1185],{},"localStorage"," تحت البادئة ",[17,1188,1189],{},"a-skeleton:s:",". عند البدء البارد (cold start) في زيارة عائدة، يقرأ الـ cache الشكل المحفوظ ويعيد عرض الـ skeleton في أول رسم مباشرةً، دون وميض \"shimmer عام\" بينما يُركَّب المكوّن الحقيقي ويقيس نفسه. الـ cache مُصدَّر مخططه (",[17,1192,1193],{},"v: 3"," لـ structural، و",[17,1196,1197],{},"v: 2"," للشكل المسطّح القديم)، ولذلك تُطهَّر الإصدارات غير المتطابقة تلقائيًا عند القراءة. لا يمكنك إعادة عرض هندسة الأمس عبر مُعارض اليوم لو تغيّر شكل البيانات.",[10,1200,1201],{},"الاستراتيجيات الثلاث تختار سلوكيات افتراضية مختلفة انطلاقًا من البدائيات نفسها. Clone يعيد عرض بكسلات بمواضع مطلقة. Mirror يعيد عرض vnodes داخل الشجرة الأصلية. Structural يعيد عرض الحاويات في الـ flow الطبيعي. وحدة cache واحدة، وسطح theming واحد، وقاعدة a11y واحدة، وثلاثة محرّكات مختلفة.",[25,1203,1205],{"id":1204},"خط-أنابيب-الالتقاط-والتخزين-المؤقت-وإعادة-العرض","خط أنابيب الالتقاط والتخزين المؤقت وإعادة العرض",[10,1207,1208],{},"القصة المعمارية واحدة في الأوضاع الثلاثة، حتى وإن تباينت التنفيذات. إليك دورة تحميل واحدة من أوّلها إلى آخرها.",[10,1210,1211,1214],{},[35,1212,1213],{},"الرسم الأول، وفشل الـ cache."," يُركَّب الـ wrapper، ولا يوجد شكل مخزّن بعد. في وضعَي clone وmirror يمشي المحرك على بنية الـ slot (vnodes في mirror، وتركيب DOM خارج الشاشة في clone) وينتج placeholder يعكس بنية وسوم الـ slot. تحتفظ الحاويات بوسومها الدلالية وبـ CSS الـ layout الخاص بها. أما الأوراق الذرّية والعناصر الحاملة للنص فتتحوّل إلى placeholders من نوع shimmer. يرى المستخدم skeleton بنيويًا يطابق حدود المكوّن الحقيقي على الفور، قبل أن تصل أيّ بيانات.",[10,1216,1217,1220,1221,522,1224,1227,1228,1231,1232,1234,1235,1237,1238,1241,1242,1244,1245,1247,1248,1250],{},[35,1218,1219],{},"وصول البيانات."," يُعرض المحتوى الحقيقي داخل الـ slot. يجدول ",[17,1222,1223],{},"ResizeObserver",[17,1225,1226],{},"requestAnimationFrame"," تمريرة قياس. في وضع clone تقرأ ",[17,1229,1230],{},"captureSnapshot()"," قيمة ",[17,1233,214],{}," لكل عنصر ومستطيلات النص لكل سطر عبر ",[17,1236,239],{},"، ثم تجمّد النتيجة داخل شجرة ",[17,1239,1240],{},"CaptureSnapshot",". في وضع structural تنتج ",[17,1243,948],{}," شجرة مجمّدة تحمل فيها الحاويات CSS الـ layout الخاص بها، وتحمل الأوراق أبعادها وإشاراتها البصرية. في الحالتين تصبح اللقطة الملتقطة في الذاكرة الآن (وفي ",[17,1246,1185],{}," أيضًا لو كانت ",[17,1249,1181],{},").",[10,1252,1253,1256,1257,1259,1260,1262,1263,1266,1267,1269],{},[35,1254,1255],{},"التقلّب التالي إلى حالة التحميل."," يعود المستخدم إلى الصفحة، أو يطلق إعادة جلب، أو يزور المسار في جلسة لاحقة. تنقلب ",[17,1258,262],{}," إلى ",[17,1261,773],{},". يُعاد عرض الشكل المخزّن فورًا، بلا خطوة وسيطة من نوع \"خمّن الشكل\". في وضع clone يعرض ",[17,1264,1265],{},"\u003CASkeletonClone>"," شجرة من الـ divs ذات المواضع المطلقة، يحمل كلٌّ منها نمطه المضمّن المجمّد مسبقًا؛ وحلقة العرض خالية تمامًا من التخصيص لأن كل حمولة نمط حُسبت وقت الالتقاط. في وضع structural يعرض ",[17,1268,525],{}," الشجرة في الـ flow الطبيعي، مع تشغيل CSS الـ layout الملتقط لسلوك حاوية flex أو grid.",[10,1271,1272,1273,1275,1276,1278,1279,1281,1282,1284,1285,408,1287,1289],{},"للميزانيات دور في كل خطوة. ",[17,1274,396],{}," (افتراضيًا 500–800 بحسب الـ walker) هو حدّ حجم الشجرة. ",[17,1277,392],{}," (افتراضيًا 12–16) هو حدّ الاستدعاء العودي. ",[17,1280,399],{}," (افتراضيًا 4 بكسلات CSS) هو مرشّح لكل ورقة. هذه الأرقام ليست اعتباطية: اخترتُها بحيث تبقى لوحة تحكم مزدحمة بمئات الأوراق قابلة للمشي داخل rAF واحد، وبحيث يتّسع إدخال ",[17,1283,1185],{}," للشجرة الملتقطة دون تجاوز الحصّة البالغة 5 ميغابايت. وحين يُقتطع الـ walk، يسجّل الـ wrapper ",[17,1286,407],{},[17,1288,411],{}," كي تكتشف الأمر في التطوير، لا في الإنتاج.",[10,1291,1292,1293,393,1296,1298],{},"قراءة layout واحدة في البداية، ثم قيم مخزّنة في بقية المشي. تحدث استدعاءات ",[17,1294,1295],{},"getBoundingClientRect()",[17,1297,214],{}," في تمريرة واحدة من أعلى إلى أسفل، دون كتابات تتخلّلها، ودون قذف تخطيط: reflow قسري واحد، ثم تصبح اللقطة الملتقطة دائمة بعده.",[25,1300,1302],{"id":1301},"البدائيات-الـ-15-المسمّاة","البدائيات الـ 15 المسمّاة",[10,1304,1305,1306,158,1309,158,1312,158,1315,158,1318,158,1321,158,1324,158,1327,158,1330,158,1333,158,1336,158,1339,158,1342,158,1345,158,1348,526],{},"التوليد الذاتي لا يناسب كل حالة. لو لم يكن لحالة التحميل عندك مكوّن حقيقي يستحق أن يُلَفّ (كأن تعرض الـ skeleton قبل بدء أيّ جلب بيانات، أو يكون الـ placeholder حقًا مختلف الـ markup عن العرض النهائي)، فلا يجد الالتقاط التلقائي شيئًا يقرأه أصلًا. لهذه الحالات تشحن الحزمة 15 متغيّرًا مسمّى: ",[17,1307,1308],{},"\u003CASkeletonCard>",[17,1310,1311],{},"\u003CASkeletonText>",[17,1313,1314],{},"\u003CASkeletonHeading>",[17,1316,1317],{},"\u003CASkeletonAvatar>",[17,1319,1320],{},"\u003CASkeletonImage>",[17,1322,1323],{},"\u003CASkeletonVideo>",[17,1325,1326],{},"\u003CASkeletonButton>",[17,1328,1329],{},"\u003CASkeletonInput>",[17,1331,1332],{},"\u003CASkeletonChip>",[17,1334,1335],{},"\u003CASkeletonListItem>",[17,1337,1338],{},"\u003CASkeletonTable>",[17,1340,1341],{},"\u003CASkeletonChart>",[17,1343,1344],{},"\u003CASkeletonForm>",[17,1346,1347],{},"\u003CASkeletonArticle>",[17,1349,1350],{},"\u003CASkeletonDivider>",[10,1352,1353],{},"هذه ليست بديلًا عن مسار الالتقاط التلقائي، بل مخرجًا جانبيًا حين تحتاجه. تشترك في خط أنابيب الحركة نفسه، وفي رموز الـ theming نفسها، وفي قاعدة الـ a11y نفسها. تؤلّفها يدويًا عند الحاجة، وإلا فدع الـ wrapper يقوم بالعمل.",[10,1355,1356,1357,1360],{},"يوجد أيضًا ",[17,1358,1359],{},"\u003CASkeletonBlock>"," للحالة التي لا يناسبها أيّ من المتغيّرات وتريد تركيب نسختك الخاصة:",[39,1362,1364],{"className":41,"code":1363,"language":43,"meta":44,"style":44},"\u003Cdiv v-if=\"loading\" class=\"flex items-start gap-4 p-4\">\n  \u003CASkeletonBlock type=\"circle\" :w=\"64\" :h=\"64\" \u002F>\n  \u003Cdiv class=\"flex-1 space-y-2\">\n    \u003CASkeletonBlock type=\"text\" :w=\"160\" :h=\"18\" \u002F>\n    \u003CASkeletonBlock type=\"text\" :w=\"100\" :h=\"12\" \u002F>\n    \u003CASkeletonBlock type=\"text\" :lines=\"3\" :h=\"14\" class=\"!mt-3\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003CUserCard v-else :data=\"user\" \u002F>\n",[17,1365,1366,1391,1396,1401,1406,1411,1416,1424,1432],{"__ignoreMap":44},[48,1367,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1389],{"class":50,"line":51},[48,1369,55],{"class":54},[48,1371,59],{"class":58},[48,1373,63],{"class":62},[48,1375,66],{"class":54},[48,1377,70],{"class":69},[48,1379,262],{"class":54},[48,1381,70],{"class":69},[48,1383,998],{"class":261},[48,1385,66],{"class":54},[48,1387,1388],{"class":69},"\"flex items-start gap-4 p-4\"",[48,1390,78],{"class":54},[48,1392,1393],{"class":50,"line":81},[48,1394,1395],{"class":54},"  \u003CASkeletonBlock type=\"circle\" :w=\"64\" :h=\"64\" \u002F>\n",[48,1397,1398],{"class":50,"line":87},[48,1399,1400],{"class":54},"  \u003Cdiv class=\"flex-1 space-y-2\">\n",[48,1402,1403],{"class":50,"line":93},[48,1404,1405],{"class":54},"    \u003CASkeletonBlock type=\"text\" :w=\"160\" :h=\"18\" \u002F>\n",[48,1407,1408],{"class":50,"line":362},[48,1409,1410],{"class":54},"    \u003CASkeletonBlock type=\"text\" :w=\"100\" :h=\"12\" \u002F>\n",[48,1412,1413],{"class":50,"line":373},[48,1414,1415],{"class":54},"    \u003CASkeletonBlock type=\"text\" :lines=\"3\" :h=\"14\" class=\"!mt-3\" \u002F>\n",[48,1417,1418,1420,1422],{"class":50,"line":384},[48,1419,929],{"class":54},[48,1421,59],{"class":58},[48,1423,78],{"class":54},[48,1425,1426,1428,1430],{"class":50,"line":661},[48,1427,96],{"class":54},[48,1429,59],{"class":58},[48,1431,78],{"class":54},[48,1433,1434],{"class":50,"line":690},[48,1435,1436],{"class":54},"\u003CUserCard v-else :data=\"user\" \u002F>\n",[10,1438,1439,1441,1442,1445,1446,1449],{},[17,1440,1359],{}," صديق للـ flow. يتركّب بسلاسة مع تخطيطات flex وgrid والـ stack. تكدّس prop ",[17,1443,1444],{},"lines"," على ",[17,1447,1448],{},"type=\"text\""," عددًا N من الأشرطة مع جعل الأخير بعرض 70%، وهو استدلال يكون صحيحًا بدرجة كافية في أغلب الحالات حين لا يتوفّر مكوّن حقيقي تقيسه.",[25,1451,1453],{"id":1452},"الحركات-والـ-theming-بوصفها-استراتيجيات","الحركات والـ theming بوصفها استراتيجيات",[10,1455,1456,1457,1460,1461,1464,1465,1468,1469,1472,1473,1476,1477,1480,1481,1468,1484,1487,1488,1491,1492,1495],{},"تُشحن معنا أربعة أوضاع للحركة: ",[17,1458,1459],{},"shimmer"," (تدرّج ماسح على عنصر زائف ",[17,1462,1463],{},"::after","، محتوى داخل كل كتلة عبر ",[17,1466,1467],{},"overflow: hidden",")، و",[17,1470,1471],{},"pulse"," (تدوير opacity من 1 إلى ",[17,1474,1475],{},"--ak-skel-pulse-min"," والعودة)، و",[17,1478,1479],{},"wave"," (تدرّج منزلق عبر ",[17,1482,1483],{},"background-position",[17,1485,1486],{},"none"," (كتل ثابتة). كلٌّ منها استراتيجية قائمة بذاتها، وليست فرعًا شرطيًا. تمرّر ",[17,1489,1490],{},"animation=\"pulse\""," فتنخرط فئة الـ CSS المناسبة تلقائيًا. لا سلسلة ",[17,1493,1494],{},"if (animation === 'pulse')"," داخل المكوّن.",[10,1497,1498,1499,1502],{},"يعطّل ",[17,1500,1501],{},"prefers-reduced-motion: reduce"," كل حركة تلقائيًا. يهبط عنصر shimmer الزائف إلى طبقة ثابتة بشفافية منخفضة، ويتوقّف الـ pulse عن التدوير. لا شيفرة مكوّن معنيّة بذلك أصلًا؛ الأمر مجرد استعلام media في ورقة الأنماط.",[10,1504,1505,1506,1509,1510,1513],{},"الـ theming يقوم على متغيرات CSS. الرموز منطاقة تحت ",[17,1507,1508],{},"--ak-skel-*"," (مع أسماء بديلة متوافقة رجعيًا تحت ",[17,1511,1512],{},"--ak-skeleton-*"," لمستخدمي الإصدار الأول):",[1515,1516,1517,1530],"table",{},[1518,1519,1520],"thead",{},[1521,1522,1523,1527],"tr",{},[1524,1525,1526],"th",{},"الرمز",[1524,1528,1529],{},"يُستخدم لأجل",[1531,1532,1533,1544,1554,1564,1574,1583,1593],"tbody",{},[1521,1534,1535,1541],{},[1536,1537,1538],"td",{},[17,1539,1540],{},"--ak-skel-base",[1536,1542,1543],{},"تعبئة الكتلة",[1521,1545,1546,1551],{},[1536,1547,1548],{},[17,1549,1550],{},"--ak-skel-highlight",[1536,1552,1553],{},"لون مسح shimmer \u002F wave",[1521,1555,1556,1561],{},[1536,1557,1558],{},[17,1559,1560],{},"--ak-skel-radius",[1536,1562,1563],{},"نصف قطر حدود الكتلة الافتراضي",[1521,1565,1566,1571],{},[1536,1567,1568],{},[17,1569,1570],{},"--ak-skel-duration",[1536,1572,1573],{},"طول دورة الحركة",[1521,1575,1576,1580],{},[1536,1577,1578],{},[17,1579,1475],{},[1536,1581,1582],{},"قيمة opacity عند قاع دورة الـ pulse",[1521,1584,1585,1590],{},[1536,1586,1587],{},[17,1588,1589],{},"--ak-skel-ring",[1536,1591,1592],{},"لون حلقة داخلية خفيفة بعرض 1 بكسل",[1521,1594,1595,1600],{},[1536,1596,1597],{},[17,1598,1599],{},"--ak-skel-icon",[1536,1601,1602],{},"لون أيقونة placeholder (متغيرات image \u002F video)",[10,1604,1605,1606,1609],{},"تستطيع أن تكتب فوق هذه الرموز على ",[17,1607,1608],{},":root","، أو على فئة wrapper لأوضاع multi-tenant، أو مضمّنة على نسخة واحدة بعينها:",[39,1611,1615],{"className":1612,"code":1613,"language":1614,"meta":44,"style":44},"language-css shiki shiki-themes github-light github-dark",".tenant-acme {\n  --ak-skel-base: hsl(220 30% 18%);\n  --ak-skel-highlight: hsl(220 60% 60% \u002F 0.35);\n  --ak-skel-radius: 0.5rem;\n  --ak-skel-duration: 2s;\n}\n","css",[17,1616,1617,1625,1654,1684,1699,1714],{"__ignoreMap":44},[48,1618,1619,1622],{"class":50,"line":51},[48,1620,1621],{"class":261},".tenant-acme",[48,1623,1624],{"class":54}," {\n",[48,1626,1627,1630,1633,1636,1638,1641,1644,1647,1650,1652],{"class":50,"line":81},[48,1628,1629],{"class":600},"  --ak-skel-base",[48,1631,1632],{"class":54},": ",[48,1634,1635],{"class":332},"hsl",[48,1637,624],{"class":54},[48,1639,1640],{"class":332},"220",[48,1642,1643],{"class":332}," 30",[48,1645,1646],{"class":62},"%",[48,1648,1649],{"class":332}," 18",[48,1651,1646],{"class":62},[48,1653,629],{"class":54},[48,1655,1656,1659,1661,1663,1665,1667,1670,1672,1674,1676,1679,1682],{"class":50,"line":87},[48,1657,1658],{"class":600},"  --ak-skel-highlight",[48,1660,1632],{"class":54},[48,1662,1635],{"class":332},[48,1664,624],{"class":54},[48,1666,1640],{"class":332},[48,1668,1669],{"class":332}," 60",[48,1671,1646],{"class":62},[48,1673,1669],{"class":332},[48,1675,1646],{"class":62},[48,1677,1678],{"class":54}," \u002F ",[48,1680,1681],{"class":332},"0.35",[48,1683,629],{"class":54},[48,1685,1686,1689,1691,1694,1697],{"class":50,"line":93},[48,1687,1688],{"class":600},"  --ak-skel-radius",[48,1690,1632],{"class":54},[48,1692,1693],{"class":332},"0.5",[48,1695,1696],{"class":62},"rem",[48,1698,318],{"class":54},[48,1700,1701,1704,1706,1709,1712],{"class":50,"line":362},[48,1702,1703],{"class":600},"  --ak-skel-duration",[48,1705,1632],{"class":54},[48,1707,1708],{"class":332},"2",[48,1710,1711],{"class":62},"s",[48,1713,318],{"class":54},[48,1715,1716],{"class":50,"line":373},[48,1717,1718],{"class":54},"}\n",[10,1720,1721,1722,1725,1726,1729,1730,1733,1734,1736,1737,1740],{},"الوضع الداكن يعمل تلقائيًا عبر محدّدات ",[17,1723,1724],{},":where(.dark)"," التي تعيد تلوين الرموز (فئة ",[17,1727,1728],{},".dark"," الخاصة بـ Tailwind، وفئة shadcn، وفئة ",[17,1731,1732],{},"nuxt-color-mode","، وأيّ شيء يضع سلفًا يحمل ",[17,1735,1728],{}," في الشجرة)، إضافةً إلى نسخة احتياطية عبر ",[17,1738,1739],{},"@media (prefers-color-scheme: dark)"," للحالة التي لا توجد فيها فئة صريحة على الشجرة.",[10,1742,1743],{},"المقصد من هذا التصميم أن لا يضطر مستهلك المكتبة يومًا إلى الكتابة فوق فئة داخلية. كل قرار بصري هو رمز؛ والرموز موروثة عبر الـ cascade؛ والكتابة فوقها لنسخة بعينها تكون بأنماط مضمّنة. المكوّن يملك البنية والحركة، وأنت تملك المظهر.",[25,1745,1747],{"id":1746},"a11y-لا-تحتاج-إلى-تكوينها","a11y لا تحتاج إلى تكوينها",[10,1749,1750,1751,1754,1755,1758,1759,1762,1763,1766],{},"الإعدادات الافتراضية هي ذاتها التي كنتَ ستريدها على أيّ حال. يحمل كل wrapper وكل جذر متغيّر السمة ",[17,1752,1753],{},"role=\"status\""," أثناء التحميل، وسمة ",[17,1756,1757],{},"aria-busy=\"true\""," تعكس حالة التحميل، وسمة ",[17,1760,1761],{},"aria-live=\"polite\""," كي تُعلن قارئات الشاشة حالة التحميل دون مقاطعة المستخدم. يُشحن ",[17,1764,1765],{},"\u003Cspan class=\"a-skel-sr-only\">Loading...\u003C\u002Fspan>"," مخفيّ بصريًا مع كل متغيّر ليكون للإعلان نصّ يقرأه.",[10,1768,1769,1770,1773,1774,393,1777,1780,1781,1784],{},"يحمل كل سطح shimmer مُصدَر السمة ",[17,1771,1772],{},"aria-hidden=\"true\"","، لأن الـ placeholder زخرفة والـ wrapper هو ما يُعلَن. تعطّل skeletons وضع mirror الخصائص ",[17,1775,1776],{},"user-select",[17,1778,1779],{},"pointer-events"," على شجرة الـ slot كي لا يستطيع أحد التفاعل مع الـ placeholder في منتصف التحميل. ويزيل ",[17,1782,1783],{},"prefers-reduced-motion"," الحركة كلها.",[10,1786,1787,1788,1791],{},"لا شيء من هذا قابل للتكوين. ولا ينبغي أن يكون. الإعدادات الافتراضية هي الإجابة الصحيحة. لو احتجتَ إلى إيقافها، فأنت إما تختبر شيئًا محدّدًا (وفي هذه الحالة ",[17,1789,1790],{},"animation=\"none\""," هو المفتاح المناسب)، أو تحاول شيئًا على مستوى الـ a11y لا أرغب أنا في أن أجعله سهلًا.",[25,1793,1795],{"id":1794},"ما-علّمني-إياه-وضع-clone","ما علّمني إياه وضع Clone",[10,1797,1798,1799,1802,1803,1806],{},"الشيء الذي سأحمله معي إلى المشكلة التالية ليس الـ API. بل هي البصيرة التي يقوم عليها وضع clone. تستطيع أن تتوقف عن التفكير على مستوى الـ cascade. تستطيع أن تتوقف عن محاولة التنبّؤ بما تنتجه فعلًا قيمة ",[17,1800,1801],{},"border-radius: 0.75rem"," مخلوطة بـ ",[17,1804,1805],{},"rounded-2xl"," مخلوطة بكتابة فوقية scoped. ركّب الشيء. دع المتصفح يطوي كل المدخلات. اقرأ الإجابة النهائية. أعِد عرض الإجابة النهائية.",[10,1808,1809,1810,1812],{},"هذا المبدأ ينطبق في كل موضع لديك فيه مشكلة \"أحتاج نسخة من هذا العنصر\". معاينات الطباعة. معاينات السحب. معارض المكوّنات التي تعرض الـ widget نفسه في خمسة أوضاع مختلفة. نهج \"انظر إلى ما هو موجود فعلًا على الشاشة، لا إلى القواعد التي أنتجته\" يمتدّ أبعد مما يُعطى من فضل. كل خط أنابيب CSS، وكل متغيّر، وكل أولوية cascade، وكل إطار حركة، ينتهي إلى رقم واحد لكل خاصية لكل عنصر، وذلك الرقم هو ما سيخبرك به ",[17,1811,214],{}," متى ما سألته.",[10,1814,1815],{},"الشيء الآخر الذي أثبت جدواه فعلًا هو عقد التأليف. وسوم غير مشروطة، ومحتوى عبر الاستيفاء. بمجرد أن ترسخ هذه الفكرة في ذهنك، يصبح كل مكوّن تبنيه جاهزًا للـ skeleton تلقائيًا: لا قالب إضافي، ولا حالة موازية، ولا عبء صيانة. المكتبة تقرأ مكوّنك الحقيقي، وأنت تكتبه بالطريقة التي كنتَ تريدها أصلًا.",[10,1817,1818],{},"فكرتان اثنتان، وحزمة واحدة. المستودع مرخّص برخصة MIT، وستجد في الـ README المرجع الطويل لكل prop وslot لو أردتَ التعمّق أكثر. لو رأيتَ شيئًا يمكن أن يكون أكثر حدّة، افتح issue من فضلك. أفضّل بصدق أن أُصحَّح على أن أبقى مرتاحًا.",[218,1820,1821],{},"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":44,"searchDepth":81,"depth":81,"links":1823},[1824,1825,1826,1827,1828,1829,1830,1831,1832],{"id":27,"depth":81,"text":28},{"id":192,"depth":81,"text":193},{"id":424,"depth":81,"text":425},{"id":514,"depth":81,"text":515},{"id":1204,"depth":81,"text":1205},{"id":1301,"depth":81,"text":1302},{"id":1452,"depth":81,"text":1453},{"id":1746,"depth":81,"text":1747},{"id":1794,"depth":81,"text":1795},"2026-06-13","كيف تستخرج a-skeleton واجهة الانتظار من المكوّن نفسه. وضع Clone، ووضع Structural، ووضع Mirror، ومسار العمل الذي يلتقط ثم يُخزّن مؤقّتاً ثم يُعيد العرض خلفها.",null,false,"md",[1839,1840,1841,1842,1843,1844,1223,1845,1846,1847],"Vue 3","Nuxt","مُحمِّل Skeleton","حالات التحميل","getComputedStyle","Range API","TypeScript","مكتبة مكوّنات","إمكانية الوصول","ar",{},"\u002Fwrites-ar\u002Finside-a-skeleton-self-generating-skeleton-loaders",{"title":5,"description":1834},"writes-ar\u002Finside-a-skeleton-self-generating-skeleton-loaders","YF9r0lB5uhmEc4wHqor5kRgfbA9lR_dGeOudu2h0SQg",[1855,6431],{"id":1856,"title":1857,"body":1858,"date":6417,"description":6418,"description_ar":1835,"draft":1836,"extension":1837,"image":1835,"keywords":6419,"lang":1848,"meta":6426,"navigation":323,"path":6427,"seo":6428,"stem":6429,"title_ar":1835,"updatedAt":6417,"__hash__":6430},"writesAr\u002Fwrites-ar\u002Fatell-input-the-library-rewrite.md","ملاحظات من إعادة كتابة ATellInput كمكتبة مُنشَرة",{"type":7,"value":1859,"toc":6407},[1860,1871,1874,1881,1884,1888,1891,1954,1969,1972,1989,1992,2079,2102,2252,2269,2280,2283,2287,2290,2378,2388,2403,2409,2972,2975,3003,3026,3044,3047,3050,3226,3240,3312,3315,3319,3326,3332,3666,3669,3740,3743,4072,4101,4108,4151,4162,4165,4249,4255,4259,4262,4273,4384,4406,4409,4413,4416,4419,4986,5028,5035,5175,5189,5192,5322,5325,5328,5465,5488,5491,5495,5524,5527,5760,5767,5993,5996,6170,6177,6180,6184,6190,6195,6317,6327,6330,6334,6345,6348,6351,6394,6397,6404],[10,1861,1862,1863,1866,1867,1870],{},"وُلد ",[17,1864,1865],{},"ATellInput"," داخل تطبيق واحد فقط: قيمة احتياطية تميل إلى السعودية أو مصر، ونداء داخلي لـ ",[17,1868,1869],{},"RestCountries","، وتحقّق يظهر عبر v-model لا غير، وقائمة منسدلة ظاهرة على الشاشة طوال الوقت. حلّت تلك النسخة مسألة الصحّة: اضطراب موضع المؤشّر، وأسباب التحقّق، ودعم RTL\u002FLTR. وأنجزت معظم ما يحتاجه مستهلك واحد.",[10,1872,1873],{},"هذه التدوينة تحكي ما جرى حين حاولتُ نشر المكوّن.",[10,1875,1876,1877,1880],{},"يعيش المكوّن الآن في ",[17,1878,1879],{},"@alikhalilll\u002Fui\u002Ftell-input","، ومهمّة المكتبة المُنشَرة تختلف كلياً. لا تستطيع أن تُشفِّر السوق الذي تخدمه داخل الكود. ولا أن تفترض شكل نظام تصميم المستهلك. ولا أن تُلزم كلّ نقطة تركيب بانتظار واجهة برمجية طرف ثالث. ولا حتى أن تعرف مسبقاً إن كان المستخدم على هاتف أم على حاسوب. حافظت إعادة الكتابة على صحّة النسخة الأصلية وأضافت ما يحتاجه مستهلك المكتبة فعلياً، وكلّ تغيير تقريباً وراءه علّة محدّدة أو قصور واضح في النسخة القديمة.",[10,1882,1883],{},"جمعتُ التغييرات حول السبعة التي استحقّت مكانها.",[25,1885,1887],{"id":1886},"نموذجان-بشكلين-مختلفين-الرقم-للخارج-وiso2-في-الداخل","نموذجان بشكلين مختلفين: الرقم للخارج، وISO2 في الداخل",[10,1889,1890],{},"بدت الواجهة القديمة أنيقة من بعيد:",[39,1892,1896],{"className":1893,"code":1894,"language":1895,"meta":44,"style":44},"language-typescript shiki shiki-themes github-light github-dark","const phoneModelValue = defineModel\u003Cstring>('phone', { required: true });\nconst countryModelValue = defineModel\u003Cstring>('country', { required: true });\n","typescript",[17,1897,1898,1928],{"__ignoreMap":44},[48,1899,1900,1902,1905,1907,1910,1912,1915,1917,1920,1923,1925],{"class":50,"line":51},[48,1901,329],{"class":62},[48,1903,1904],{"class":332}," phoneModelValue",[48,1906,342],{"class":62},[48,1908,1909],{"class":261}," defineModel",[48,1911,55],{"class":54},[48,1913,1914],{"class":332},"string",[48,1916,683],{"class":54},[48,1918,1919],{"class":69},"'phone'",[48,1921,1922],{"class":54},", { required: ",[48,1924,773],{"class":332},[48,1926,1927],{"class":54}," });\n",[48,1929,1930,1932,1935,1937,1939,1941,1943,1945,1948,1950,1952],{"class":50,"line":81},[48,1931,329],{"class":62},[48,1933,1934],{"class":332}," countryModelValue",[48,1936,342],{"class":62},[48,1938,1909],{"class":261},[48,1940,55],{"class":54},[48,1942,1914],{"class":332},[48,1944,683],{"class":54},[48,1946,1947],{"class":69},"'country'",[48,1949,1922],{"class":54},[48,1951,773],{"class":332},[48,1953,1927],{"class":54},[10,1955,1956,1957,1960,1961,1964,1965,1968],{},"كان ",[17,1958,1959],{},"country"," سلسلة نصية تحوي أرقام الاتصال: ",[17,1962,1963],{},"\"20\""," لمصر، و",[17,1966,1967],{},"\"44\""," للمملكة المتحدة. سهل التحويل إلى JSON، وسهل ترميزه في URL، وسهل إسكانه في عمود قاعدة بيانات.",[10,1970,1971],{},"المشكلة أنّه عجز عن التفريق بين الولايات المتحدة وكندا.",[10,1973,1974,1975,1978,1979,1982,1983,1985,1986,1988],{},"يتقاسم الرمز ",[17,1976,1977],{},"+1"," أكثر من 25 دولة ضمن NANP. لو اختار مستخدم كندي \"كندا\" من القائمة وأصدر v-model القيمة ",[17,1980,1981],{},"\"1\"",", فلن يعرف الأب أيّ ",[17,1984,1977],{}," هو المقصود. حدّث الصفحة، واستعد الحالة من URL، ستعود إليك ",[17,1987,1981],{}," بينما يعرض المنتقي أوّل دولة صادف أن ترتّبها القائمة. النموذج الذي يختصر الدولة في \"سلسلة رموز اتصال\" كان يُفقد نصف المعلومة عن شيء من قطعتين.",[10,1990,1991],{},"النموذج الجديد يفصل بين القطعتين:",[39,1993,1995],{"className":1893,"code":1994,"language":1895,"meta":44,"style":44},"const phone = defineModel\u003Cstring>('phone', { default: '' });\nconst country = defineModel\u003Cnumber | null>('country', { default: null });\n\nconst selectedIso2 = ref\u003Cstring>('');\n",[17,1996,1997,2024,2054,2058],{"__ignoreMap":44},[48,1998,1999,2001,2004,2006,2008,2010,2012,2014,2016,2019,2022],{"class":50,"line":51},[48,2000,329],{"class":62},[48,2002,2003],{"class":332}," phone",[48,2005,342],{"class":62},[48,2007,1909],{"class":261},[48,2009,55],{"class":54},[48,2011,1914],{"class":332},[48,2013,683],{"class":54},[48,2015,1919],{"class":69},[48,2017,2018],{"class":54},", { default: ",[48,2020,2021],{"class":69},"''",[48,2023,1927],{"class":54},[48,2025,2026,2028,2031,2033,2035,2037,2040,2042,2044,2046,2048,2050,2052],{"class":50,"line":81},[48,2027,329],{"class":62},[48,2029,2030],{"class":332}," country",[48,2032,342],{"class":62},[48,2034,1909],{"class":261},[48,2036,55],{"class":54},[48,2038,2039],{"class":332},"number",[48,2041,678],{"class":62},[48,2043,656],{"class":332},[48,2045,683],{"class":54},[48,2047,1947],{"class":69},[48,2049,2018],{"class":54},[48,2051,171],{"class":332},[48,2053,1927],{"class":54},[48,2055,2056],{"class":50,"line":87},[48,2057,324],{"emptyLinePlaceholder":323},[48,2059,2060,2062,2065,2067,2069,2071,2073,2075,2077],{"class":50,"line":93},[48,2061,329],{"class":62},[48,2063,2064],{"class":332}," selectedIso2",[48,2066,342],{"class":62},[48,2068,621],{"class":261},[48,2070,55],{"class":54},[48,2072,1914],{"class":332},[48,2074,683],{"class":54},[48,2076,2021],{"class":69},[48,2078,629],{"class":54},[10,2080,2081,2082,2084,2085,158,2088,158,2091,2093,2094,2097,2098,2101],{},"صار ",[17,2083,1959],{}," رقماً (",[17,2086,2087],{},"20",[17,2089,2090],{},"1",[17,2092,171],{},") لأنّ رموز الاتصال أرقامٌ في جوهرها، ولأنّ ",[17,2095,2096],{},"Number"," ينجو من رحلات URL وDB وJSON بلا أيّ التباس ناتج عن اقتطاع الأحرف. غير أنّ المكوّن يتتبّع داخلياً أيضاً ",[17,2099,2100],{},"selectedIso2"," يملكه المنتقي وحده. تبقى القيمتان متزامنتين عبر watcher-ين، أحدهما للخارج والآخر للداخل، والداخلي منهما فيه حارس صغير لكنّه حاسم:",[39,2103,2105],{"className":1893,"code":2104,"language":1895,"meta":44,"style":44},"watch(\n  country,\n  (next) => {\n    if (next == null) {\n      if (selectedIso2.value) selectedIso2.value = '';\n      return;\n    }\n    if (dialNumberFor(selectedIso2.value) === next) return;\n    const iso2 = resolveCountryIdentifier(String(next));\n    if (iso2) selectedIso2.value = iso2;\n  },\n  { immediate: true }\n);\n",[17,2106,2107,2115,2120,2134,2150,2165,2172,2177,2200,2221,2233,2238,2248],{"__ignoreMap":44},[48,2108,2109,2112],{"class":50,"line":51},[48,2110,2111],{"class":261},"watch",[48,2113,2114],{"class":54},"(\n",[48,2116,2117],{"class":50,"line":81},[48,2118,2119],{"class":54},"  country,\n",[48,2121,2122,2125,2128,2130,2132],{"class":50,"line":87},[48,2123,2124],{"class":54},"  (",[48,2126,2127],{"class":600},"next",[48,2129,806],{"class":54},[48,2131,647],{"class":62},[48,2133,1624],{"class":54},[48,2135,2136,2139,2142,2145,2147],{"class":50,"line":93},[48,2137,2138],{"class":62},"    if",[48,2140,2141],{"class":54}," (next ",[48,2143,2144],{"class":62},"==",[48,2146,656],{"class":332},[48,2148,2149],{"class":54},") {\n",[48,2151,2152,2155,2158,2160,2163],{"class":50,"line":362},[48,2153,2154],{"class":62},"      if",[48,2156,2157],{"class":54}," (selectedIso2.value) selectedIso2.value ",[48,2159,66],{"class":62},[48,2161,2162],{"class":69}," ''",[48,2164,318],{"class":54},[48,2166,2167,2170],{"class":50,"line":373},[48,2168,2169],{"class":62},"      return",[48,2171,318],{"class":54},[48,2173,2174],{"class":50,"line":384},[48,2175,2176],{"class":54},"    }\n",[48,2178,2179,2181,2184,2187,2190,2192,2195,2198],{"class":50,"line":661},[48,2180,2138],{"class":62},[48,2182,2183],{"class":54}," (",[48,2185,2186],{"class":261},"dialNumberFor",[48,2188,2189],{"class":54},"(selectedIso2.value) ",[48,2191,653],{"class":62},[48,2193,2194],{"class":54}," next) ",[48,2196,2197],{"class":62},"return",[48,2199,318],{"class":54},[48,2201,2202,2205,2208,2210,2213,2215,2218],{"class":50,"line":690},[48,2203,2204],{"class":62},"    const",[48,2206,2207],{"class":332}," iso2",[48,2209,342],{"class":62},[48,2211,2212],{"class":261}," resolveCountryIdentifier",[48,2214,624],{"class":54},[48,2216,2217],{"class":261},"String",[48,2219,2220],{"class":54},"(next));\n",[48,2222,2223,2225,2228,2230],{"class":50,"line":695},[48,2224,2138],{"class":62},[48,2226,2227],{"class":54}," (iso2) selectedIso2.value ",[48,2229,66],{"class":62},[48,2231,2232],{"class":54}," iso2;\n",[48,2234,2235],{"class":50,"line":722},[48,2236,2237],{"class":54},"  },\n",[48,2239,2240,2243,2245],{"class":50,"line":743},[48,2241,2242],{"class":54},"  { immediate: ",[48,2244,773],{"class":332},[48,2246,2247],{"class":54}," }\n",[48,2249,2250],{"class":50,"line":767},[48,2251,629],{"class":54},[10,2253,2254,2255,2258,2259,2262,2263,2265,2266,2268],{},"السطر ",[17,2256,2257],{},"if (dialNumberFor(selectedIso2.value) === next) return;"," هو ما أودّ التوقّف عنده. حين يكتب الأب ",[17,2260,2261],{},"country = 1"," من جديد إلى النموذج والمنتقي يعرض كندا مختارة أصلاً، ينطلق الـ watch, لكنّ رمز الاتصال متطابق مسبقاً، فيعود دون أن يمسّ ",[17,2264,2100],{},".\nلولا هذا الحارس، لكان الـ watcher حلّ ",[17,2267,1981],{}," إلى أوّل دولة NANP أبجدياً وقلب كندا بصمت إلى أيّاً كانت تلك الدولة. الحارس يحفظ الحالة الأغنى.",[10,2270,2271,2272,2275,2276,2279],{},"الـ watch الخارجي يعمل بوضع ",[17,2273,2274],{},"flush: 'sync'"," كي يبقى العلَم ",[17,2277,2278],{},"autoSettingCountry"," متّسقاً. لأنّه حين يختار المطابِق دولةً تلقائياً بناءً على ما كُتب، لا نريد اعتبار ذلك \"اختياراً يدوياً\" يُقفل الاكتشاف التلقائي لاحقاً.",[10,2281,2282],{},"نصفان من بيانات الدولة، ونموذجان مستقلّان، ومصدر حقيقة واحد لكلٍّ منهما.",[25,2284,2286],{"id":2285},"تجربة-تقوم-على-الاكتشاف-أولاً-المنتقي-مخفي-افتراضياً","تجربة تقوم على الاكتشاف أولاً: المنتقي مخفي افتراضياً",[10,2288,2289],{},"كان المكوّن القديم يرسم القائمة دائماً: العلَم يساراً، وحقل الإدخال يميناً. أمّا المكوّن الجديد فيكتفي بحقل الإدخال وحده افتراضياً. تنزلق القائمة إلى الظهور فقط حين يكتب المستخدم شيئاً يتعرّف عليه المطابِق:",[39,2291,2293],{"className":41,"code":2292,"language":43,"meta":44,"style":44},"\u003CTransition\n  enter-active-class=\"transition-all duration-200 ease-out overflow-hidden\"\n  leave-active-class=\"transition-all duration-150 ease-in overflow-hidden\"\n  enter-from-class=\"opacity-0 -translate-x-1 max-w-0\"\n  leave-to-class=\"opacity-0 -translate-x-1 max-w-0\"\n  enter-to-class=\"max-w-[12rem]\"\n  leave-from-class=\"max-w-[12rem]\"\n>\n  \u003CACountrySelect v-if=\"!props.detectFromInput || selectedIso2\" ... \u002F>\n\u003C\u002FTransition>\n",[17,2294,2295,2302,2312,2322,2332,2341,2351,2360,2364,2369],{"__ignoreMap":44},[48,2296,2297,2299],{"class":50,"line":51},[48,2298,55],{"class":54},[48,2300,2301],{"class":58},"Transition\n",[48,2303,2304,2307,2309],{"class":50,"line":81},[48,2305,2306],{"class":261},"  enter-active-class",[48,2308,66],{"class":54},[48,2310,2311],{"class":69},"\"transition-all duration-200 ease-out overflow-hidden\"\n",[48,2313,2314,2317,2319],{"class":50,"line":87},[48,2315,2316],{"class":261},"  leave-active-class",[48,2318,66],{"class":54},[48,2320,2321],{"class":69},"\"transition-all duration-150 ease-in overflow-hidden\"\n",[48,2323,2324,2327,2329],{"class":50,"line":93},[48,2325,2326],{"class":261},"  enter-from-class",[48,2328,66],{"class":54},[48,2330,2331],{"class":69},"\"opacity-0 -translate-x-1 max-w-0\"\n",[48,2333,2334,2337,2339],{"class":50,"line":362},[48,2335,2336],{"class":261},"  leave-to-class",[48,2338,66],{"class":54},[48,2340,2331],{"class":69},[48,2342,2343,2346,2348],{"class":50,"line":373},[48,2344,2345],{"class":261},"  enter-to-class",[48,2347,66],{"class":54},[48,2349,2350],{"class":69},"\"max-w-[12rem]\"\n",[48,2352,2353,2356,2358],{"class":50,"line":384},[48,2354,2355],{"class":261},"  leave-from-class",[48,2357,66],{"class":54},[48,2359,2350],{"class":69},[48,2361,2362],{"class":50,"line":661},[48,2363,78],{"class":54},[48,2365,2366],{"class":50,"line":690},[48,2367,2368],{"class":54},"  \u003CACountrySelect v-if=\"!props.detectFromInput || selectedIso2\" ... \u002F>\n",[48,2370,2371,2373,2376],{"class":50,"line":695},[48,2372,96],{"class":54},[48,2374,2375],{"class":58},"Transition",[48,2377,78],{"class":54},[10,2379,2380,2381,2384,2385,526],{},"يتقلّص ",[17,2382,2383],{},"max-w"," إلى صفر ما لم يكن هناك اختيار، ثم يتحرّك ليتفتّح عند أوّل تطابق ناجح. ومن يريد الشكل القديم يستطيع إعادة المنتقي الظاهر دائماً عبر ",[17,2386,2387],{},"detectFromInput=\"false\"",[10,2389,2390,2391,2394,2395,2398,2399,2402],{},"هذا سلوك ينحاز إلى رأي واضح. الأغلبية العظمى من المستخدمين في معظم الدول لا يكتبون بادئة ",[17,2392,2393],{},"+"," أبداً. بل يكتبون الرقم كما يكتبونه في هاتفهم: ",[17,2396,2397],{},"01066105963"," في القاهرة، و",[17,2400,2401],{},"07911 123456"," في مانشستر. القائمة التي \"يُجبَرون على التعامل معها\" في كلّ حقل هاتف آخر خطوة زائدة بالنسبة لهم. أخفِها إذاً. وراقب ما يكتبونه. ولا تُظهرها إلا حين يقع الاكتشاف على نتيجة.",[10,2404,2405,2406,336],{},"الجزء الوحيد الذي فيه بعض التعقيد هو \"مراقبة ما يكتبونه\". يمرّ عبر ثلاث طبقات في ",[17,2407,2408],{},"matchLeadingDialCode",[39,2410,2412],{"className":1893,"code":2411,"language":1895,"meta":44,"style":44},"function matchLeadingDialCode(digits: string): DialMatch | null {\n  if (!digits) return null;\n\n  \u002F\u002F Tier 1: international parse, disambiguates NANP and strips the calling code.\n  try {\n    const parsed = parsePhoneNumberFromString(`+${digits}`);\n    if (parsed?.country && parsed.countryCallingCode) {\n      const parsedCountry = getCountryByValue(parsed.country);\n      if (parsedCountry) {\n        return { country: parsedCountry, nationalNumber: String(parsed.nationalNumber ?? '') };\n      }\n    }\n  } catch {\n    \u002F* libphonenumber throws on partial input, fall through *\u002F\n  }\n\n  \u002F\u002F Tier 2: national-format parse using the silently-inferred country as a hint.\n  const hint = inferredCountry.value;\n  if (hint && digits.length >= 4) {\n    try {\n      const parsed = parsePhoneNumberFromString(digits, hint as CountryCode);\n      if (parsed?.isValid()) {\n        const matched = getCountryByValue(parsed.country || hint);\n        if (matched) {\n          return { country: matched, nationalNumber: String(parsed.nationalNumber ?? '') };\n        }\n      }\n    } catch {\n      \u002F* fall through *\u002F\n    }\n  }\n\n  \u002F\u002F Tier 3: longest-prefix match over our own dial-digits index.\n  for (let len = Math.min(3, digits.length); len >= 1; len--) {\n    const prefix = digits.slice(0, len);\n    const group = getCountriesByDial(prefix);\n    if (!group.length) continue;\n    const nationalNumber = digits.slice(prefix.length);\n    if (group.length === 1) return { country: group[0], nationalNumber };\n    \u002F\u002F ambiguity: prefer current selection, then recents, then first\n    \u002F\u002F (see \"Recents as the tiebreaker\" below)\n    return { country: group[0], nationalNumber };\n  }\n  return null;\n}\n",[17,2413,2414,2445,2464,2468,2474,2481,2504,2517,2533,2540,2561,2566,2570,2580,2585,2590,2594,2599,2612,2635,2642,2663,2676,2697,2705,2723,2729,2734,2744,2750,2755,2760,2765,2771,2820,2843,2859,2880,2901,2928,2934,2940,2952,2957,2967],{"__ignoreMap":44},[48,2415,2416,2419,2422,2424,2427,2429,2431,2434,2436,2439,2441,2443],{"class":50,"line":51},[48,2417,2418],{"class":62},"function",[48,2420,2421],{"class":261}," matchLeadingDialCode",[48,2423,624],{"class":54},[48,2425,2426],{"class":600},"digits",[48,2428,336],{"class":62},[48,2430,606],{"class":332},[48,2432,2433],{"class":54},")",[48,2435,336],{"class":62},[48,2437,2438],{"class":261}," DialMatch",[48,2440,678],{"class":62},[48,2442,656],{"class":332},[48,2444,1624],{"class":54},[48,2446,2447,2450,2452,2455,2458,2460,2462],{"class":50,"line":81},[48,2448,2449],{"class":62},"  if",[48,2451,2183],{"class":54},[48,2453,2454],{"class":62},"!",[48,2456,2457],{"class":54},"digits) ",[48,2459,2197],{"class":62},[48,2461,656],{"class":332},[48,2463,318],{"class":54},[48,2465,2466],{"class":50,"line":87},[48,2467,324],{"emptyLinePlaceholder":323},[48,2469,2470],{"class":50,"line":93},[48,2471,2473],{"class":2472},"sJ8bj","  \u002F\u002F Tier 1: international parse, disambiguates NANP and strips the calling code.\n",[48,2475,2476,2479],{"class":50,"line":362},[48,2477,2478],{"class":62},"  try",[48,2480,1624],{"class":54},[48,2482,2483,2485,2488,2490,2493,2495,2498,2500,2502],{"class":50,"line":373},[48,2484,2204],{"class":62},[48,2486,2487],{"class":332}," parsed",[48,2489,342],{"class":62},[48,2491,2492],{"class":261}," parsePhoneNumberFromString",[48,2494,624],{"class":54},[48,2496,2497],{"class":69},"`+${",[48,2499,2426],{"class":54},[48,2501,738],{"class":69},[48,2503,629],{"class":54},[48,2505,2506,2508,2511,2514],{"class":50,"line":384},[48,2507,2138],{"class":62},[48,2509,2510],{"class":54}," (parsed?.country ",[48,2512,2513],{"class":62},"&&",[48,2515,2516],{"class":54}," parsed.countryCallingCode) {\n",[48,2518,2519,2522,2525,2527,2530],{"class":50,"line":661},[48,2520,2521],{"class":62},"      const",[48,2523,2524],{"class":332}," parsedCountry",[48,2526,342],{"class":62},[48,2528,2529],{"class":261}," getCountryByValue",[48,2531,2532],{"class":54},"(parsed.country);\n",[48,2534,2535,2537],{"class":50,"line":690},[48,2536,2154],{"class":62},[48,2538,2539],{"class":54}," (parsedCountry) {\n",[48,2541,2542,2545,2548,2550,2553,2556,2558],{"class":50,"line":695},[48,2543,2544],{"class":62},"        return",[48,2546,2547],{"class":54}," { country: parsedCountry, nationalNumber: ",[48,2549,2217],{"class":261},[48,2551,2552],{"class":54},"(parsed.nationalNumber ",[48,2554,2555],{"class":62},"??",[48,2557,2162],{"class":69},[48,2559,2560],{"class":54},") };\n",[48,2562,2563],{"class":50,"line":722},[48,2564,2565],{"class":54},"      }\n",[48,2567,2568],{"class":50,"line":743},[48,2569,2176],{"class":54},[48,2571,2572,2575,2578],{"class":50,"line":767},[48,2573,2574],{"class":54},"  } ",[48,2576,2577],{"class":62},"catch",[48,2579,1624],{"class":54},[48,2581,2582],{"class":50,"line":778},[48,2583,2584],{"class":2472},"    \u002F* libphonenumber throws on partial input, fall through *\u002F\n",[48,2586,2587],{"class":50,"line":783},[48,2588,2589],{"class":54},"  }\n",[48,2591,2592],{"class":50,"line":788},[48,2593,324],{"emptyLinePlaceholder":323},[48,2595,2596],{"class":50,"line":819},[48,2597,2598],{"class":2472},"  \u002F\u002F Tier 2: national-format parse using the silently-inferred country as a hint.\n",[48,2600,2601,2604,2607,2609],{"class":50,"line":828},[48,2602,2603],{"class":62},"  const",[48,2605,2606],{"class":332}," hint",[48,2608,342],{"class":62},[48,2610,2611],{"class":54}," inferredCountry.value;\n",[48,2613,2614,2616,2619,2621,2624,2627,2630,2633],{"class":50,"line":833},[48,2615,2449],{"class":62},[48,2617,2618],{"class":54}," (hint ",[48,2620,2513],{"class":62},[48,2622,2623],{"class":54}," digits.",[48,2625,2626],{"class":332},"length",[48,2628,2629],{"class":62}," >=",[48,2631,2632],{"class":332}," 4",[48,2634,2149],{"class":54},[48,2636,2637,2640],{"class":50,"line":843},[48,2638,2639],{"class":62},"    try",[48,2641,1624],{"class":54},[48,2643,2644,2646,2648,2650,2652,2655,2658,2661],{"class":50,"line":860},[48,2645,2521],{"class":62},[48,2647,2487],{"class":332},[48,2649,342],{"class":62},[48,2651,2492],{"class":261},[48,2653,2654],{"class":54},"(digits, hint ",[48,2656,2657],{"class":62},"as",[48,2659,2660],{"class":261}," CountryCode",[48,2662,629],{"class":54},[48,2664,2665,2667,2670,2673],{"class":50,"line":887},[48,2666,2154],{"class":62},[48,2668,2669],{"class":54}," (parsed?.",[48,2671,2672],{"class":261},"isValid",[48,2674,2675],{"class":54},"()) {\n",[48,2677,2678,2681,2684,2686,2688,2691,2694],{"class":50,"line":905},[48,2679,2680],{"class":62},"        const",[48,2682,2683],{"class":332}," matched",[48,2685,342],{"class":62},[48,2687,2529],{"class":261},[48,2689,2690],{"class":54},"(parsed.country ",[48,2692,2693],{"class":62},"||",[48,2695,2696],{"class":54}," hint);\n",[48,2698,2699,2702],{"class":50,"line":926},[48,2700,2701],{"class":62},"        if",[48,2703,2704],{"class":54}," (matched) {\n",[48,2706,2707,2710,2713,2715,2717,2719,2721],{"class":50,"line":936},[48,2708,2709],{"class":62},"          return",[48,2711,2712],{"class":54}," { country: matched, nationalNumber: ",[48,2714,2217],{"class":261},[48,2716,2552],{"class":54},[48,2718,2555],{"class":62},[48,2720,2162],{"class":69},[48,2722,2560],{"class":54},[48,2724,2726],{"class":50,"line":2725},26,[48,2727,2728],{"class":54},"        }\n",[48,2730,2732],{"class":50,"line":2731},27,[48,2733,2565],{"class":54},[48,2735,2737,2740,2742],{"class":50,"line":2736},28,[48,2738,2739],{"class":54},"    } ",[48,2741,2577],{"class":62},[48,2743,1624],{"class":54},[48,2745,2747],{"class":50,"line":2746},29,[48,2748,2749],{"class":2472},"      \u002F* fall through *\u002F\n",[48,2751,2753],{"class":50,"line":2752},30,[48,2754,2176],{"class":54},[48,2756,2758],{"class":50,"line":2757},31,[48,2759,2589],{"class":54},[48,2761,2763],{"class":50,"line":2762},32,[48,2764,324],{"emptyLinePlaceholder":323},[48,2766,2768],{"class":50,"line":2767},33,[48,2769,2770],{"class":2472},"  \u002F\u002F Tier 3: longest-prefix match over our own dial-digits index.\n",[48,2772,2774,2777,2779,2782,2785,2787,2790,2793,2795,2798,2801,2803,2806,2809,2812,2815,2818],{"class":50,"line":2773},34,[48,2775,2776],{"class":62},"  for",[48,2778,2183],{"class":54},[48,2780,2781],{"class":62},"let",[48,2783,2784],{"class":54}," len ",[48,2786,66],{"class":62},[48,2788,2789],{"class":54}," Math.",[48,2791,2792],{"class":261},"min",[48,2794,624],{"class":54},[48,2796,2797],{"class":332},"3",[48,2799,2800],{"class":54},", digits.",[48,2802,2626],{"class":332},[48,2804,2805],{"class":54},"); len ",[48,2807,2808],{"class":62},">=",[48,2810,2811],{"class":332}," 1",[48,2813,2814],{"class":54},"; len",[48,2816,2817],{"class":62},"--",[48,2819,2149],{"class":54},[48,2821,2823,2825,2828,2830,2832,2835,2837,2840],{"class":50,"line":2822},35,[48,2824,2204],{"class":62},[48,2826,2827],{"class":332}," prefix",[48,2829,342],{"class":62},[48,2831,2623],{"class":54},[48,2833,2834],{"class":261},"slice",[48,2836,624],{"class":54},[48,2838,2839],{"class":332},"0",[48,2841,2842],{"class":54},", len);\n",[48,2844,2846,2848,2851,2853,2856],{"class":50,"line":2845},36,[48,2847,2204],{"class":62},[48,2849,2850],{"class":332}," group",[48,2852,342],{"class":62},[48,2854,2855],{"class":261}," getCountriesByDial",[48,2857,2858],{"class":54},"(prefix);\n",[48,2860,2862,2864,2866,2868,2871,2873,2875,2878],{"class":50,"line":2861},37,[48,2863,2138],{"class":62},[48,2865,2183],{"class":54},[48,2867,2454],{"class":62},[48,2869,2870],{"class":54},"group.",[48,2872,2626],{"class":332},[48,2874,806],{"class":54},[48,2876,2877],{"class":62},"continue",[48,2879,318],{"class":54},[48,2881,2883,2885,2888,2890,2892,2894,2897,2899],{"class":50,"line":2882},38,[48,2884,2204],{"class":62},[48,2886,2887],{"class":332}," nationalNumber",[48,2889,342],{"class":62},[48,2891,2623],{"class":54},[48,2893,2834],{"class":261},[48,2895,2896],{"class":54},"(prefix.",[48,2898,2626],{"class":332},[48,2900,629],{"class":54},[48,2902,2904,2906,2909,2911,2914,2916,2918,2920,2923,2925],{"class":50,"line":2903},39,[48,2905,2138],{"class":62},[48,2907,2908],{"class":54}," (group.",[48,2910,2626],{"class":332},[48,2912,2913],{"class":62}," ===",[48,2915,2811],{"class":332},[48,2917,806],{"class":54},[48,2919,2197],{"class":62},[48,2921,2922],{"class":54}," { country: group[",[48,2924,2839],{"class":332},[48,2926,2927],{"class":54},"], nationalNumber };\n",[48,2929,2931],{"class":50,"line":2930},40,[48,2932,2933],{"class":2472},"    \u002F\u002F ambiguity: prefer current selection, then recents, then first\n",[48,2935,2937],{"class":50,"line":2936},41,[48,2938,2939],{"class":2472},"    \u002F\u002F (see \"Recents as the tiebreaker\" below)\n",[48,2941,2943,2946,2948,2950],{"class":50,"line":2942},42,[48,2944,2945],{"class":62},"    return",[48,2947,2922],{"class":54},[48,2949,2839],{"class":332},[48,2951,2927],{"class":54},[48,2953,2955],{"class":50,"line":2954},43,[48,2956,2589],{"class":54},[48,2958,2960,2963,2965],{"class":50,"line":2959},44,[48,2961,2962],{"class":62},"  return",[48,2964,656],{"class":332},[48,2966,318],{"class":54},[48,2968,2970],{"class":50,"line":2969},45,[48,2971,1718],{"class":54},[10,2973,2974],{},"توجد الطبقات لأنّ لكلّ واحدة منها فئة من المدخلات تعجز الطبقتان الأخريان عن معالجتها.",[10,2976,2977,2978,2980,2981,2984,2985,2988,2989,2992,2993,2995,2996,2998,2999,3002],{},"تأخذ الطبقة الأولى ما كتبه المستخدم، وتُلحق قبله ",[17,2979,2393],{},", ثم تطلب من ",[17,2982,2983],{},"libphonenumber"," تحليله بوصفه رقماً دولياً. إن كتب المستخدم ",[17,2986,2987],{},"447911123456"," سيتطابق مع المملكة المتحدة. وإن كتب ",[17,2990,2991],{},"1416..."," سيتطابق مع كندا تحديداً (لا مع NANP العام)، لأنّ ",[17,2994,2983],{}," يحمل قواعد رموز المناطق مدمجة أصلاً. توجد كتلة catch لأنّ ",[17,2997,2983],{}," يرمي استثناءً على الإدخال الجزئي. ",[17,3000,3001],{},"447"," رقم ناقص؛ يرفع التحليل استثناءً، فننتقل إلى الطبقة التالية.",[10,3004,3005,3006,3008,3009,3012,3013,1445,3015,3017,3018,3021,3022,3025],{},"الطبقة الثانية هي المسار المخصّص لمن يكتب الرقم كما يستعمله في بلده. ",[17,3007,2397],{}," لا يبدأ برمز اتصال، وبالتالي لا نفع من الطبقة الأولى. لكن إن كنّا نعرف بصمت أنّ المستخدم في مصر على الأرجح (اعتماداً على IP أو timezone, والمزيد عن هذا لاحقاً)، فبإمكاننا تمرير ",[17,3010,3011],{},"\"EG\""," تلميحاً للمحلِّل، وسيتعرّف ",[17,3014,2983],{},[17,3016,2397],{}," بوصفه رقم هاتف محمول مصري صحيح، ويجرّد الصفر الأول (البادئة الوطنية لمصر)، ويعيد الرقم الوطني المعياري ",[17,3019,3020],{},"1066105963",". الحارس ",[17,3023,3024],{},"digits.length >= 4"," يمنع المحلِّل من التعامل مع بدايات رموز المناطق ذات الرقمين بوصفها أرقاماً كاملة.",[10,3027,3028,3029,3032,3033,3035,3036,3039,3040,3043],{},"الطبقة الثالثة هي الاحتياطي لحالة \"كتبتُ ",[17,3030,3031],{},"44"," فقط ولا شيء غير ذلك\". في هذه الحالة لا مسار في ",[17,3034,2983],{}," يستطيع المطابقة، فالمدخل قصير جداً. لكنّ فهرس أرقام الاتصال الخاصّ بنا يكفي: ",[17,3037,3038],{},"byDialDigits.get('44')"," يعيد مجموعة المملكة المتحدة بطول 1, وينتهي الأمر. هنا تُثبت بنية ",[17,3041,3042],{},"Map\u003Cstring, CountryOption[]>"," الموروثة من النسخة القديمة قيمتها.",[10,3045,3046],{},"قد تبدو ثلاث طبقات كثيرة. في الحقيقة هي الحدّ الأدنى: كلّ طبقة تعالج مدخلات ترفضها الاثنتان الأخريان.",[10,3048,3049],{},"المُعالِج الذي يستدعيها يبقى صغيراً:",[39,3051,3053],{"className":1893,"code":3052,"language":1895,"meta":44,"style":44},"const detectAndApply = useDebounceFn(\n  () => {\n    if (!props.detectFromInput) return;\n    if (userPickedCountry.value || selectedIso2.value) return;\n    const current = phone.value;\n    if (!current) return;\n    const match = matchLeadingDialCode(current);\n    if (!match) return;\n    autoSettingCountry.value = true;\n    selectedIso2.value = match.country.value;\n    phone.value = match.nationalNumber;\n  },\n  computed(() => Math.max(0, props.detectDebounceMs))\n);\n",[17,3054,3055,3069,3078,3093,3109,3121,3136,3150,3165,3177,3187,3197,3201,3222],{"__ignoreMap":44},[48,3056,3057,3059,3062,3064,3067],{"class":50,"line":51},[48,3058,329],{"class":62},[48,3060,3061],{"class":332}," detectAndApply",[48,3063,342],{"class":62},[48,3065,3066],{"class":261}," useDebounceFn",[48,3068,2114],{"class":54},[48,3070,3071,3074,3076],{"class":50,"line":81},[48,3072,3073],{"class":54},"  () ",[48,3075,647],{"class":62},[48,3077,1624],{"class":54},[48,3079,3080,3082,3084,3086,3089,3091],{"class":50,"line":87},[48,3081,2138],{"class":62},[48,3083,2183],{"class":54},[48,3085,2454],{"class":62},[48,3087,3088],{"class":54},"props.detectFromInput) ",[48,3090,2197],{"class":62},[48,3092,318],{"class":54},[48,3094,3095,3097,3100,3102,3105,3107],{"class":50,"line":93},[48,3096,2138],{"class":62},[48,3098,3099],{"class":54}," (userPickedCountry.value ",[48,3101,2693],{"class":62},[48,3103,3104],{"class":54}," selectedIso2.value) ",[48,3106,2197],{"class":62},[48,3108,318],{"class":54},[48,3110,3111,3113,3116,3118],{"class":50,"line":362},[48,3112,2204],{"class":62},[48,3114,3115],{"class":332}," current",[48,3117,342],{"class":62},[48,3119,3120],{"class":54}," phone.value;\n",[48,3122,3123,3125,3127,3129,3132,3134],{"class":50,"line":373},[48,3124,2138],{"class":62},[48,3126,2183],{"class":54},[48,3128,2454],{"class":62},[48,3130,3131],{"class":54},"current) ",[48,3133,2197],{"class":62},[48,3135,318],{"class":54},[48,3137,3138,3140,3143,3145,3147],{"class":50,"line":384},[48,3139,2204],{"class":62},[48,3141,3142],{"class":332}," match",[48,3144,342],{"class":62},[48,3146,2421],{"class":261},[48,3148,3149],{"class":54},"(current);\n",[48,3151,3152,3154,3156,3158,3161,3163],{"class":50,"line":661},[48,3153,2138],{"class":62},[48,3155,2183],{"class":54},[48,3157,2454],{"class":62},[48,3159,3160],{"class":54},"match) ",[48,3162,2197],{"class":62},[48,3164,318],{"class":54},[48,3166,3167,3170,3172,3175],{"class":50,"line":690},[48,3168,3169],{"class":54},"    autoSettingCountry.value ",[48,3171,66],{"class":62},[48,3173,3174],{"class":332}," true",[48,3176,318],{"class":54},[48,3178,3179,3182,3184],{"class":50,"line":695},[48,3180,3181],{"class":54},"    selectedIso2.value ",[48,3183,66],{"class":62},[48,3185,3186],{"class":54}," match.country.value;\n",[48,3188,3189,3192,3194],{"class":50,"line":722},[48,3190,3191],{"class":54},"    phone.value ",[48,3193,66],{"class":62},[48,3195,3196],{"class":54}," match.nationalNumber;\n",[48,3198,3199],{"class":50,"line":743},[48,3200,2237],{"class":54},[48,3202,3203,3206,3208,3210,3212,3215,3217,3219],{"class":50,"line":767},[48,3204,3205],{"class":261},"  computed",[48,3207,644],{"class":54},[48,3209,647],{"class":62},[48,3211,2789],{"class":54},[48,3213,3214],{"class":261},"max",[48,3216,624],{"class":54},[48,3218,2839],{"class":332},[48,3220,3221],{"class":54},", props.detectDebounceMs))\n",[48,3223,3224],{"class":50,"line":778},[48,3225,629],{"class":54},[10,3227,3228,3229,1231,3232,3235,3236,3239],{},"يقرأ ",[17,3230,3231],{},"useDebounceFn",[17,3233,3234],{},"phone.value"," لحظة إطلاق المؤقّت، لا لحظة جدولته، فتذوب دفعة من الضغطات على المفاتيح في تحليل واحد. ويمنع ",[17,3237,3238],{},"userPickedCountry.value"," المكتشفَ التلقائيّ من الدهس على اختيار يدوي. متى استعمل المستخدم القائمة، ينسحب المطابِق مباشرة. أمّا مسح الحقل فلا يخضع لأيّ debounce, بل يعيد المنتقي إلى الاختفاء فوراً:",[39,3241,3243],{"className":1893,"code":3242,"language":1895,"meta":44,"style":44},"if (!cleaned) {\n  autoSettingCountry.value = true;\n  selectedIso2.value = '';\n  phone.value = '';\n  userPickedCountry.value = false;\n  return;\n}\n",[17,3244,3245,3257,3268,3279,3290,3302,3308],{"__ignoreMap":44},[48,3246,3247,3250,3252,3254],{"class":50,"line":51},[48,3248,3249],{"class":62},"if",[48,3251,2183],{"class":54},[48,3253,2454],{"class":62},[48,3255,3256],{"class":54},"cleaned) {\n",[48,3258,3259,3262,3264,3266],{"class":50,"line":81},[48,3260,3261],{"class":54},"  autoSettingCountry.value ",[48,3263,66],{"class":62},[48,3265,3174],{"class":332},[48,3267,318],{"class":54},[48,3269,3270,3273,3275,3277],{"class":50,"line":87},[48,3271,3272],{"class":54},"  selectedIso2.value ",[48,3274,66],{"class":62},[48,3276,2162],{"class":69},[48,3278,318],{"class":54},[48,3280,3281,3284,3286,3288],{"class":50,"line":93},[48,3282,3283],{"class":54},"  phone.value ",[48,3285,66],{"class":62},[48,3287,2162],{"class":69},[48,3289,318],{"class":54},[48,3291,3292,3295,3297,3300],{"class":50,"line":362},[48,3293,3294],{"class":54},"  userPickedCountry.value ",[48,3296,66],{"class":62},[48,3298,3299],{"class":332}," false",[48,3301,318],{"class":54},[48,3303,3304,3306],{"class":50,"line":373},[48,3305,2962],{"class":62},[48,3307,318],{"class":54},[48,3309,3310],{"class":50,"line":384},[48,3311,1718],{"class":54},[10,3313,3314],{},"تأخير المسح يعني بقاء القائمة معلّقة 150 ميلي ثانية بعد إفراغ الحقل، وهو ما يبدو خاطئاً في كلّ مرّة.",[25,3316,3318],{"id":3317},"سلسلة-اكتشاف-البيئة-ولماذا-هي-قابلة-للاستبدال","سلسلة اكتشاف البيئة، ولماذا هي قابلة للاستبدال",[10,3320,3321,3322,3325],{},"تعتمد الطبقة الثانية أعلاه على ",[17,3323,3324],{},"inferredCountry.value","، أي أفضل تخمين للمكوّن عن موقع المستخدم، يُضبط بصمت قبل أن يكتب حرفاً واحداً. كان \"أفضل تخمين\" في النسخة القديمة قيمة احتياطية مثبّتة على السعودية أو مصر، لأنّهما السوقان اللذان استهدفتهما النسخة الداخلية. المكتبة لا تملك ترف هذا الافتراض.",[10,3327,3328,3329,336],{},"السلسلة الجديدة هي ",[17,3330,3331],{},"detectCountry()",[39,3333,3335],{"className":1893,"code":3334,"language":1895,"meta":44,"style":44},"export async function detectCountry(opts: DetectCountryOptions = {}): Promise\u003Cstring> {\n  const {\n    strategy = 'auto',\n    ipEndpoint = 'https:\u002F\u002Fipapi.co\u002Fjson\u002F',\n    defaultCountry = 'US',\n    timeoutMs = 2000,\n    cache = true,\n  } = opts;\n\n  if (cache) {\n    const cached = readCache();\n    if (cached) return cached;\n  }\n\n  if (strategy === 'none') {\n    return defaultCountry.toUpperCase();\n  }\n\n  if (strategy === 'auto') {\n    const ipResult = await tryIp(ipEndpoint, timeoutMs);\n    if (ipResult) {\n      if (cache) writeCache(ipResult);\n      return ipResult;\n    }\n  }\n\n  const localResult = tryTimezone() ?? tryLocale();\n  const final = (localResult ?? defaultCountry).toUpperCase();\n  if (cache) writeCache(final);\n  return final;\n}\n",[17,3336,3337,3378,3384,3396,3408,3420,3432,3443,3452,3456,3463,3478,3490,3494,3498,3512,3524,3528,3532,3544,3562,3569,3582,3589,3593,3597,3601,3623,3644,3655,3662],{"__ignoreMap":44},[48,3338,3339,3342,3345,3348,3351,3353,3356,3358,3361,3363,3366,3368,3371,3373,3375],{"class":50,"line":51},[48,3340,3341],{"class":62},"export",[48,3343,3344],{"class":62}," async",[48,3346,3347],{"class":62}," function",[48,3349,3350],{"class":261}," detectCountry",[48,3352,624],{"class":54},[48,3354,3355],{"class":600},"opts",[48,3357,336],{"class":62},[48,3359,3360],{"class":261}," DetectCountryOptions",[48,3362,342],{"class":62},[48,3364,3365],{"class":54}," {})",[48,3367,336],{"class":62},[48,3369,3370],{"class":261}," Promise",[48,3372,55],{"class":54},[48,3374,1914],{"class":332},[48,3376,3377],{"class":54},"> {\n",[48,3379,3380,3382],{"class":50,"line":81},[48,3381,2603],{"class":62},[48,3383,1624],{"class":54},[48,3385,3386,3389,3391,3394],{"class":50,"line":87},[48,3387,3388],{"class":332},"    strategy",[48,3390,342],{"class":62},[48,3392,3393],{"class":69}," 'auto'",[48,3395,359],{"class":54},[48,3397,3398,3401,3403,3406],{"class":50,"line":93},[48,3399,3400],{"class":332},"    ipEndpoint",[48,3402,342],{"class":62},[48,3404,3405],{"class":69}," 'https:\u002F\u002Fipapi.co\u002Fjson\u002F'",[48,3407,359],{"class":54},[48,3409,3410,3413,3415,3418],{"class":50,"line":362},[48,3411,3412],{"class":332},"    defaultCountry",[48,3414,342],{"class":62},[48,3416,3417],{"class":69}," 'US'",[48,3419,359],{"class":54},[48,3421,3422,3425,3427,3430],{"class":50,"line":373},[48,3423,3424],{"class":332},"    timeoutMs",[48,3426,342],{"class":62},[48,3428,3429],{"class":332}," 2000",[48,3431,359],{"class":54},[48,3433,3434,3437,3439,3441],{"class":50,"line":384},[48,3435,3436],{"class":332},"    cache",[48,3438,342],{"class":62},[48,3440,3174],{"class":332},[48,3442,359],{"class":54},[48,3444,3445,3447,3449],{"class":50,"line":661},[48,3446,2574],{"class":54},[48,3448,66],{"class":62},[48,3450,3451],{"class":54}," opts;\n",[48,3453,3454],{"class":50,"line":690},[48,3455,324],{"emptyLinePlaceholder":323},[48,3457,3458,3460],{"class":50,"line":695},[48,3459,2449],{"class":62},[48,3461,3462],{"class":54}," (cache) {\n",[48,3464,3465,3467,3470,3472,3475],{"class":50,"line":722},[48,3466,2204],{"class":62},[48,3468,3469],{"class":332}," cached",[48,3471,342],{"class":62},[48,3473,3474],{"class":261}," readCache",[48,3476,3477],{"class":54},"();\n",[48,3479,3480,3482,3485,3487],{"class":50,"line":743},[48,3481,2138],{"class":62},[48,3483,3484],{"class":54}," (cached) ",[48,3486,2197],{"class":62},[48,3488,3489],{"class":54}," cached;\n",[48,3491,3492],{"class":50,"line":767},[48,3493,2589],{"class":54},[48,3495,3496],{"class":50,"line":778},[48,3497,324],{"emptyLinePlaceholder":323},[48,3499,3500,3502,3505,3507,3510],{"class":50,"line":783},[48,3501,2449],{"class":62},[48,3503,3504],{"class":54}," (strategy ",[48,3506,653],{"class":62},[48,3508,3509],{"class":69}," 'none'",[48,3511,2149],{"class":54},[48,3513,3514,3516,3519,3522],{"class":50,"line":788},[48,3515,2945],{"class":62},[48,3517,3518],{"class":54}," defaultCountry.",[48,3520,3521],{"class":261},"toUpperCase",[48,3523,3477],{"class":54},[48,3525,3526],{"class":50,"line":819},[48,3527,2589],{"class":54},[48,3529,3530],{"class":50,"line":828},[48,3531,324],{"emptyLinePlaceholder":323},[48,3533,3534,3536,3538,3540,3542],{"class":50,"line":833},[48,3535,2449],{"class":62},[48,3537,3504],{"class":54},[48,3539,653],{"class":62},[48,3541,3393],{"class":69},[48,3543,2149],{"class":54},[48,3545,3546,3548,3551,3553,3556,3559],{"class":50,"line":843},[48,3547,2204],{"class":62},[48,3549,3550],{"class":332}," ipResult",[48,3552,342],{"class":62},[48,3554,3555],{"class":62}," await",[48,3557,3558],{"class":261}," tryIp",[48,3560,3561],{"class":54},"(ipEndpoint, timeoutMs);\n",[48,3563,3564,3566],{"class":50,"line":860},[48,3565,2138],{"class":62},[48,3567,3568],{"class":54}," (ipResult) {\n",[48,3570,3571,3573,3576,3579],{"class":50,"line":887},[48,3572,2154],{"class":62},[48,3574,3575],{"class":54}," (cache) ",[48,3577,3578],{"class":261},"writeCache",[48,3580,3581],{"class":54},"(ipResult);\n",[48,3583,3584,3586],{"class":50,"line":905},[48,3585,2169],{"class":62},[48,3587,3588],{"class":54}," ipResult;\n",[48,3590,3591],{"class":50,"line":926},[48,3592,2176],{"class":54},[48,3594,3595],{"class":50,"line":936},[48,3596,2589],{"class":54},[48,3598,3599],{"class":50,"line":2725},[48,3600,324],{"emptyLinePlaceholder":323},[48,3602,3603,3605,3608,3610,3613,3616,3618,3621],{"class":50,"line":2731},[48,3604,2603],{"class":62},[48,3606,3607],{"class":332}," localResult",[48,3609,342],{"class":62},[48,3611,3612],{"class":261}," tryTimezone",[48,3614,3615],{"class":54},"() ",[48,3617,2555],{"class":62},[48,3619,3620],{"class":261}," tryLocale",[48,3622,3477],{"class":54},[48,3624,3625,3627,3630,3632,3635,3637,3640,3642],{"class":50,"line":2736},[48,3626,2603],{"class":62},[48,3628,3629],{"class":332}," final",[48,3631,342],{"class":62},[48,3633,3634],{"class":54}," (localResult ",[48,3636,2555],{"class":62},[48,3638,3639],{"class":54}," defaultCountry).",[48,3641,3521],{"class":261},[48,3643,3477],{"class":54},[48,3645,3646,3648,3650,3652],{"class":50,"line":2746},[48,3647,2449],{"class":62},[48,3649,3575],{"class":54},[48,3651,3578],{"class":261},[48,3653,3654],{"class":54},"(final);\n",[48,3656,3657,3659],{"class":50,"line":2752},[48,3658,2962],{"class":62},[48,3660,3661],{"class":54}," final;\n",[48,3663,3664],{"class":50,"line":2757},[48,3665,1718],{"class":54},[10,3667,3668],{},"أربع إشارات تُقيَّم بترتيب الكلفة: الأرخص المخبَّأ أولاً، والأغلى الذي يمرّ عبر الشبكة أخيراً.",[3670,3671,3672,3680,3691,3719,3733],"ol",{},[3673,3674,3675,3676,3679],"li",{},"تخزين مؤقّت في ",[17,3677,3678],{},"sessionStorage",". المستخدم حسم دولته أصلاً ضمن هذه الجلسة، فلا داعي لتكرار العمل.",[3673,3681,3682,3683,3686,3687,3690],{},"تحديد الموقع الجغرافي عبر IP باستخدام ",[17,3684,3685],{},"ipapi.co"," (قابل للتكوين). أدقّ إشارة، لكنّها تكلّف نداء شبكة وقدراً يسيراً من الخصوصية. من لا يريد هذا الطلب يمرّر ",[17,3688,3689],{},"strategy: 'locale'"," ليتخطّى الخطوة كلياً.",[3673,3692,3693,3694,3697,3698,3701,3702,3705,3706,3701,3709,3705,3712,3701,3715,3718],{},"المنطقة الزمنية عبر ",[17,3695,3696],{},"Intl.DateTimeFormat().resolvedOptions().timeZone",", يُبحث عنها في جدول صنعتُه يدوياً يربط ",[17,3699,3700],{},"Africa\u002FCairo"," بـ ",[17,3703,3704],{},"EG",", و",[17,3707,3708],{},"Asia\u002FRiyadh",[17,3710,3711],{},"SA",[17,3713,3714],{},"Europe\u002FLondon",[17,3716,3717],{},"GB",", وهكذا. يغطّي الجدول أكثف المناطق سكاناً، وهو صريح بأنّه لا يغطّي كلّ منطقة. عدم التطابق يمرّ إلى ما بعده.",[3673,3720,3721,3724,3725,3728,3729,3732],{},[17,3722,3723],{},"navigator.language"," الذي يعطي ",[17,3726,3727],{},"en-EG"," أو ",[17,3730,3731],{},"ar-SA"," وأشباهها. لاحقة المنطقة هي الدولة. أقلّ موثوقية (فالمستخدمون يتنقّلون بينما locale المتصفح لا)، لكنّها بلا كلفة.",[3673,3734,3735,3736,3739],{},"القيمة الافتراضية التي يمرّرها المستدعي. ",[17,3737,3738],{},"defaultCountry: 'US'"," هو الافتراضي في المكتبة، وللمستهلك حرية اختيار غيره.",[10,3741,3742],{},"خطوة IP هي التي احتاجت عناية حقيقية، لأنّ الطلب قد يتعلّق:",[39,3744,3746],{"className":1893,"code":3745,"language":1895,"meta":44,"style":44},"async function tryIp(endpoint: string, timeoutMs: number): Promise\u003Cstring | null> {\n  if (!isBrowser() || typeof fetch !== 'function') return null;\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(), timeoutMs);\n  try {\n    const res = await fetch(endpoint, { signal: controller.signal, credentials: 'omit' });\n    if (!res.ok) return null;\n    const data = (await res.json()) as { country_code?: string; country?: string };\n    const code = (data.country_code ?? data.country ?? '').toString().toUpperCase();\n    return \u002F^[A-Z]{2}$\u002F.test(code) ? code : null;\n  } catch {\n    return null;\n  } finally {\n    clearTimeout(timer);\n  }\n}\n",[17,3747,3748,3792,3827,3844,3869,3875,3897,3914,3961,3994,4031,4039,4047,4056,4064,4068],{"__ignoreMap":44},[48,3749,3750,3753,3755,3757,3759,3762,3764,3766,3768,3771,3773,3776,3778,3780,3782,3784,3786,3788,3790],{"class":50,"line":51},[48,3751,3752],{"class":62},"async",[48,3754,3347],{"class":62},[48,3756,3558],{"class":261},[48,3758,624],{"class":54},[48,3760,3761],{"class":600},"endpoint",[48,3763,336],{"class":62},[48,3765,606],{"class":332},[48,3767,158],{"class":54},[48,3769,3770],{"class":600},"timeoutMs",[48,3772,336],{"class":62},[48,3774,3775],{"class":332}," number",[48,3777,2433],{"class":54},[48,3779,336],{"class":62},[48,3781,3370],{"class":261},[48,3783,55],{"class":54},[48,3785,1914],{"class":332},[48,3787,678],{"class":62},[48,3789,656],{"class":332},[48,3791,3377],{"class":54},[48,3793,3794,3796,3798,3800,3803,3805,3807,3810,3813,3816,3819,3821,3823,3825],{"class":50,"line":81},[48,3795,2449],{"class":62},[48,3797,2183],{"class":54},[48,3799,2454],{"class":62},[48,3801,3802],{"class":261},"isBrowser",[48,3804,3615],{"class":54},[48,3806,2693],{"class":62},[48,3808,3809],{"class":62}," typeof",[48,3811,3812],{"class":54}," fetch ",[48,3814,3815],{"class":62},"!==",[48,3817,3818],{"class":69}," 'function'",[48,3820,806],{"class":54},[48,3822,2197],{"class":62},[48,3824,656],{"class":332},[48,3826,318],{"class":54},[48,3828,3829,3831,3834,3836,3839,3842],{"class":50,"line":87},[48,3830,2603],{"class":62},[48,3832,3833],{"class":332}," controller",[48,3835,342],{"class":62},[48,3837,3838],{"class":62}," new",[48,3840,3841],{"class":261}," AbortController",[48,3843,3477],{"class":54},[48,3845,3846,3848,3851,3853,3856,3858,3860,3863,3866],{"class":50,"line":93},[48,3847,2603],{"class":62},[48,3849,3850],{"class":332}," timer",[48,3852,342],{"class":62},[48,3854,3855],{"class":261}," setTimeout",[48,3857,644],{"class":54},[48,3859,647],{"class":62},[48,3861,3862],{"class":54}," controller.",[48,3864,3865],{"class":261},"abort",[48,3867,3868],{"class":54},"(), timeoutMs);\n",[48,3870,3871,3873],{"class":50,"line":362},[48,3872,2478],{"class":62},[48,3874,1624],{"class":54},[48,3876,3877,3879,3882,3884,3886,3889,3892,3895],{"class":50,"line":373},[48,3878,2204],{"class":62},[48,3880,3881],{"class":332}," res",[48,3883,342],{"class":62},[48,3885,3555],{"class":62},[48,3887,3888],{"class":261}," fetch",[48,3890,3891],{"class":54},"(endpoint, { signal: controller.signal, credentials: ",[48,3893,3894],{"class":69},"'omit'",[48,3896,1927],{"class":54},[48,3898,3899,3901,3903,3905,3908,3910,3912],{"class":50,"line":384},[48,3900,2138],{"class":62},[48,3902,2183],{"class":54},[48,3904,2454],{"class":62},[48,3906,3907],{"class":54},"res.ok) ",[48,3909,2197],{"class":62},[48,3911,656],{"class":332},[48,3913,318],{"class":54},[48,3915,3916,3918,3921,3923,3925,3928,3931,3934,3937,3939,3941,3944,3947,3949,3952,3954,3956,3958],{"class":50,"line":661},[48,3917,2204],{"class":62},[48,3919,3920],{"class":332}," data",[48,3922,342],{"class":62},[48,3924,2183],{"class":54},[48,3926,3927],{"class":62},"await",[48,3929,3930],{"class":54}," res.",[48,3932,3933],{"class":261},"json",[48,3935,3936],{"class":54},"()) ",[48,3938,2657],{"class":62},[48,3940,700],{"class":54},[48,3942,3943],{"class":600},"country_code",[48,3945,3946],{"class":62},"?:",[48,3948,606],{"class":332},[48,3950,3951],{"class":54},"; ",[48,3953,1959],{"class":600},[48,3955,3946],{"class":62},[48,3957,606],{"class":332},[48,3959,3960],{"class":54}," };\n",[48,3962,3963,3965,3968,3970,3973,3975,3978,3980,3982,3984,3987,3990,3992],{"class":50,"line":690},[48,3964,2204],{"class":62},[48,3966,3967],{"class":332}," code",[48,3969,342],{"class":62},[48,3971,3972],{"class":54}," (data.country_code ",[48,3974,2555],{"class":62},[48,3976,3977],{"class":54}," data.country ",[48,3979,2555],{"class":62},[48,3981,2162],{"class":69},[48,3983,1250],{"class":54},[48,3985,3986],{"class":261},"toString",[48,3988,3989],{"class":54},"().",[48,3991,3521],{"class":261},[48,3993,3477],{"class":54},[48,3995,3996,3998,4000,4003,4006,4009,4012,4014,4017,4020,4022,4025,4027,4029],{"class":50,"line":695},[48,3997,2945],{"class":62},[48,3999,1116],{"class":69},[48,4001,4002],{"class":62},"^",[48,4004,4005],{"class":332},"[A-Z]",[48,4007,4008],{"class":62},"{2}$",[48,4010,4011],{"class":69},"\u002F",[48,4013,526],{"class":54},[48,4015,4016],{"class":261},"test",[48,4018,4019],{"class":54},"(code) ",[48,4021,757],{"class":62},[48,4023,4024],{"class":54}," code ",[48,4026,336],{"class":62},[48,4028,656],{"class":332},[48,4030,318],{"class":54},[48,4032,4033,4035,4037],{"class":50,"line":722},[48,4034,2574],{"class":54},[48,4036,2577],{"class":62},[48,4038,1624],{"class":54},[48,4040,4041,4043,4045],{"class":50,"line":743},[48,4042,2945],{"class":62},[48,4044,656],{"class":332},[48,4046,318],{"class":54},[48,4048,4049,4051,4054],{"class":50,"line":767},[48,4050,2574],{"class":54},[48,4052,4053],{"class":62},"finally",[48,4055,1624],{"class":54},[48,4057,4058,4061],{"class":50,"line":778},[48,4059,4060],{"class":261},"    clearTimeout",[48,4062,4063],{"class":54},"(timer);\n",[48,4065,4066],{"class":50,"line":783},[48,4067,2589],{"class":54},[48,4069,4070],{"class":50,"line":788},[48,4071,1718],{"class":54},[10,4073,4074,4075,4078,4079,4081,4082,4085,4086,4089,4090,4093,4094,4097,4098,4100],{},"يعني ",[17,4076,4077],{},"AbortController"," مع مؤقّت من ثانيتين أنّ خدمة IP بطيئة لا تستطيع تعليق سلسلة الاكتشاف لأكثر من ",[17,4080,3770],{},". وتُبقي ",[17,4083,4084],{},"credentials: 'omit'"," الكوكيز خارج الطلب المرسَل إلى الطرف الثالث الذي ضبطتَه. أمّا شكل الاستجابة فيُتحقّق منه بالتعبير النمطي ",[17,4087,4088],{},"\u002F^[A-Z]{2}$\u002F",", لأنّ بعض واجهات IP تعيد ",[17,4091,4092],{},"\"GB\"",", وبعضها الآخر ",[17,4095,4096],{},"\"United Kingdom\"",", وبعضها ",[17,4099,171],{}," حين لا تعرف. التعبير النمطي هو البوّابة.",[10,4102,4103,4104,4107],{},"يعيش ",[17,4105,4106],{},"detectCountry"," خارج المكوّن عن قصد:",[39,4109,4111],{"className":1893,"code":4110,"language":1895,"meta":44,"style":44},"import { detectCountry } from '@alikhalilll\u002Fui\u002Ftell-input';\n\nconst country = await detectCountry({ strategy: 'locale' });\n",[17,4112,4113,4127,4131],{"__ignoreMap":44},[48,4114,4115,4117,4120,4122,4125],{"class":50,"line":51},[48,4116,300],{"class":62},[48,4118,4119],{"class":54}," { detectCountry } ",[48,4121,312],{"class":62},[48,4123,4124],{"class":69}," '@alikhalilll\u002Fui\u002Ftell-input'",[48,4126,318],{"class":54},[48,4128,4129],{"class":50,"line":81},[48,4130,324],{"emptyLinePlaceholder":323},[48,4132,4133,4135,4137,4139,4141,4143,4146,4149],{"class":50,"line":87},[48,4134,329],{"class":62},[48,4136,2030],{"class":332},[48,4138,342],{"class":62},[48,4140,3555],{"class":62},[48,4142,3350],{"class":261},[48,4144,4145],{"class":54},"({ strategy: ",[48,4147,4148],{"class":69},"'locale'",[48,4150,1927],{"class":54},[10,4152,4153,4154,4157,4158,4161],{},"بلا اعتماد على Vue, ولا refs تفاعلية، ولا ",[17,4155,4156],{},"onMounted",". يعمل داخل server middleware يريد ضبط كوكي، وفي CLI يريد طباعة موقع المستخدم المرجّح، وفي unit test يريد التحكّم بالاستراتيجية. أمّا الغلاف التفاعلي (",[17,4159,4160],{},"useCountryDetection",") فقشرة رقيقة فوقه للحالة داخل المكوّن. سطحان، وتنفيذ واحد.",[10,4163,4164],{},"والسلسلة كلّها قابلة للاستبدال. إن كنتَ تكتشف الموقع الجغرافي في الخادم أصلاً وتمرّره عبر SSR, فلن ترغب في أن يُعيد المكوّن نداء IP:",[39,4166,4168],{"className":41,"code":4167,"language":43,"meta":44,"style":44},"\u003CATellInput\n  v-model:phone=\"phone\"\n  v-model:country=\"country\"\n  :detector=\"(opts) => Promise.resolve(serverDetectedCountry.value)\"\n\u002F>\n",[17,4169,4170,4177,4196,4212,4244],{"__ignoreMap":44},[48,4171,4172,4174],{"class":50,"line":51},[48,4173,55],{"class":54},[48,4175,4176],{"class":58},"ATellInput\n",[48,4178,4179,4182,4184,4187,4189,4191,4193],{"class":50,"line":81},[48,4180,4181],{"class":261},"  v-model",[48,4183,336],{"class":54},[48,4185,4186],{"class":261},"phone",[48,4188,66],{"class":54},[48,4190,70],{"class":69},[48,4192,4186],{"class":54},[48,4194,4195],{"class":69},"\"\n",[48,4197,4198,4200,4202,4204,4206,4208,4210],{"class":50,"line":87},[48,4199,4181],{"class":261},[48,4201,336],{"class":54},[48,4203,1959],{"class":261},[48,4205,66],{"class":54},[48,4207,70],{"class":69},[48,4209,1959],{"class":54},[48,4211,4195],{"class":69},[48,4213,4214,4217,4220,4222,4224,4226,4228,4230,4232,4234,4236,4239,4242],{"class":50,"line":93},[48,4215,4216],{"class":54},"  :",[48,4218,4219],{"class":261},"detector",[48,4221,66],{"class":54},[48,4223,70],{"class":69},[48,4225,624],{"class":54},[48,4227,3355],{"class":600},[48,4229,806],{"class":54},[48,4231,647],{"class":62},[48,4233,3370],{"class":332},[48,4235,526],{"class":54},[48,4237,4238],{"class":261},"resolve",[48,4240,4241],{"class":54},"(serverDetectedCountry.value)",[48,4243,4195],{"class":69},[48,4245,4246],{"class":50,"line":362},[48,4247,4248],{"class":54},"\u002F>\n",[10,4250,4251,4252,4254],{},"يستبدل الـ prop المسمّى ",[17,4253,4219],{}," السلسلةَ المدمجةَ بأكملها. يواصل المكوّن تشغيل بقية منطق الاكتشاف (يضبط الدولة المستنتجة، ويستخدمها تلميحاً للطبقة الثانية، وهكذا)، لكنّ مصدر الإجابة يصبح ما حدّدتَه أنت.",[25,4256,4258],{"id":4257},"منتقٍ-متجاوب-مكوّن-واحد-بتقديمَين","منتقٍ متجاوب: مكوّن واحد بتقديمَين",[10,4260,4261],{},"كانت قائمة الدولة القديمة popover في كلّ مقاسات الشاشة. على الهاتف كان ذلك يعني قائمة قابلة للتمرير من 250 صفّاً مثبّتة على زرّ عرضه 38 بكسل، مع حقل بحث تحجبه لوحة المفاتيح باستمرار. كان يعمل. لكنّه يعمل بشكل رديء.",[10,4263,4264,4265,4268,4269,4272],{},"يعتمد المنتقي الجديد على العنصر الأساسي ",[17,4266,4267],{},"AResponsivePopover"," من المكتبة نفسها، وهو popover على الحاسوب وdrawer عبر ",[17,4270,4271],{},"vaul-vue"," على الجوّال:",[39,4274,4276],{"className":41,"code":4275,"language":43,"meta":44,"style":44},"\u003CAResponsivePopover v-model:open=\"open\">\n  \u003CAResponsivePopoverTrigger as-child>\n    \u003C!-- trigger button -->\n  \u003C\u002FAResponsivePopoverTrigger>\n\n  \u003CAResponsivePopoverContent\n    :popover-class=\"\n      cn(\n        'w-[min(20rem,calc(100vw-2rem))] max-h-[min(22rem,var(--reka-popover-content-available-height))]',\n        props.popoverClass\n      )\n    \"\n    :drawer-class=\"cn('max-h-[80vh] pb-4', props.drawerClass)\"\n  >\n    \u003C!-- search + list -->\n  \u003C\u002FAResponsivePopoverContent>\n\u003C\u002FAResponsivePopover>\n",[17,4277,4278,4302,4307,4312,4317,4321,4326,4331,4336,4341,4346,4351,4356,4361,4366,4371,4376],{"__ignoreMap":44},[48,4279,4280,4282,4284,4287,4289,4292,4294,4296,4298,4300],{"class":50,"line":51},[48,4281,55],{"class":54},[48,4283,4267],{"class":58},[48,4285,4286],{"class":261}," v-model",[48,4288,336],{"class":54},[48,4290,4291],{"class":261},"open",[48,4293,66],{"class":54},[48,4295,70],{"class":69},[48,4297,4291],{"class":54},[48,4299,70],{"class":69},[48,4301,78],{"class":54},[48,4303,4304],{"class":50,"line":81},[48,4305,4306],{"class":54},"  \u003CAResponsivePopoverTrigger as-child>\n",[48,4308,4309],{"class":50,"line":87},[48,4310,4311],{"class":54},"    \u003C!-- trigger button -->\n",[48,4313,4314],{"class":50,"line":93},[48,4315,4316],{"class":54},"  \u003C\u002FAResponsivePopoverTrigger>\n",[48,4318,4319],{"class":50,"line":362},[48,4320,324],{"emptyLinePlaceholder":323},[48,4322,4323],{"class":50,"line":373},[48,4324,4325],{"class":54},"  \u003CAResponsivePopoverContent\n",[48,4327,4328],{"class":50,"line":384},[48,4329,4330],{"class":54},"    :popover-class=\"\n",[48,4332,4333],{"class":50,"line":661},[48,4334,4335],{"class":54},"      cn(\n",[48,4337,4338],{"class":50,"line":690},[48,4339,4340],{"class":54},"        'w-[min(20rem,calc(100vw-2rem))] max-h-[min(22rem,var(--reka-popover-content-available-height))]',\n",[48,4342,4343],{"class":50,"line":695},[48,4344,4345],{"class":54},"        props.popoverClass\n",[48,4347,4348],{"class":50,"line":722},[48,4349,4350],{"class":54},"      )\n",[48,4352,4353],{"class":50,"line":743},[48,4354,4355],{"class":54},"    \"\n",[48,4357,4358],{"class":50,"line":767},[48,4359,4360],{"class":54},"    :drawer-class=\"cn('max-h-[80vh] pb-4', props.drawerClass)\"\n",[48,4362,4363],{"class":50,"line":778},[48,4364,4365],{"class":54},"  >\n",[48,4367,4368],{"class":50,"line":783},[48,4369,4370],{"class":54},"    \u003C!-- search + list -->\n",[48,4372,4373],{"class":50,"line":788},[48,4374,4375],{"class":54},"  \u003C\u002FAResponsivePopoverContent>\n",[48,4377,4378,4380,4382],{"class":50,"line":819},[48,4379,96],{"class":54},[48,4381,4267],{"class":58},[48,4383,78],{"class":54},[10,4385,4386,4387,393,4390,4393,4394,4397,4398,4401,4402,4405],{},"يُمرَّر prop-ان للأصناف (",[17,4388,4389],{},"popoverClass",[17,4391,4392],{},"drawerClass",") كلّ على حدة، فيتمكّن المستهلك من تنسيق كلّ تقديم دون أن يرث الآخر شيئاً منه. الـ popover يريد ",[17,4395,4396],{},"max-h-[min(22rem,var(--reka-popover-content-available-height))]"," لأنّ ",[17,4399,4400],{},"reka-ui"," يُتيح الارتفاع المتاح كمتغيّر CSS. أمّا الـ drawer فيريد ",[17,4403,4404],{},"max-h-[80vh]"," لأنّه ورقة سفلية و\"80% من نافذة العرض\" سقفٌ منطقي. فرض مجموعة أصناف واحدة على الحالتين سيكون خطأً على إحداهما، لكنّ السماح للمستهلك بتجاوز كلّ منهما مستقلاً هو تحديداً نقطة الفصل التي تحتاجها المكتبة.",[10,4407,4408],{},"الفائدة الجانبية الأخرى تخصّ البحث. في وضع الـ drawer يرسو حقل البحث في أعلى ورقة تغطّي معظم الشاشة، ولوحة المفاتيح تدفع الورقة لأعلى بدل أن تدفنها. الشيفرة نفسها، والمكوّن نفسه، والشكل المناسب للجهاز. ليس media-query داخل CSS المستهلك، بل عنصر أساسي يعرف الفرق بنفسه.",[25,4410,4412],{"id":4411},"slots-لكلّ-شيء-أربعة-عشر-slot","Slots لكلّ شيء (أربعة عشر Slot)",[10,4414,4415],{},"كانت النسخة القديمة كتلة واحدة صمّاء. تنسيق رسالة الخطأ يعني ترقيع المكوّن. تغيير أيقونة السهم يعني ترقيع المكوّن. استبدال علَم الدولة بواحد من CDN خاصّ بك يعني ترقيع المكوّن.",[10,4417,4418],{},"النسخة الجديدة تملك السلوك وتترك للمستهلك الشكل:",[39,4420,4422],{"className":1893,"code":4421,"language":1895,"meta":44,"style":44},"defineSlots\u003C{\n  prefix?: () => unknown;\n  suffix?: (props: { validationState: 'idle' | 'valid' | 'error'; validation: PhoneValidationResult }) => unknown;\n  'valid-icon'?: () => unknown;\n  'error-icon'?: (props: { reason: string }) => unknown;\n  hint?: (props: { country: string; formatHint: string; example: string | null }) => unknown;\n  error?: (props: { message: string; reason: string; validation: PhoneValidationResult }) => unknown;\n  trigger?: (props: { selectedCountry: CountryOption | null; open: boolean; sizeClasses: string }) => unknown;\n  chevron?: (props: { open: boolean }) => unknown;\n  flag?: (props: { country: CountryOption; context: 'trigger' | 'item' }) => unknown;\n  item?: (props: { country: CountryOption; selected: boolean; disabled: boolean; select: () => void }) => unknown;\n  'group-header'?: (props: { label: string; group: 'suggested' | 'all' }) => unknown;\n  search?: (props: { value: string; setValue: (v: string) => void; isSearching: boolean }) => unknown;\n  loading?: () => unknown;\n  empty?: (props: { query: string }) => unknown;\n}>();\n",[17,4423,4424,4432,4449,4501,4516,4546,4597,4643,4696,4725,4769,4830,4875,4936,4951,4981],{"__ignoreMap":44},[48,4425,4426,4429],{"class":50,"line":51},[48,4427,4428],{"class":261},"defineSlots",[48,4430,4431],{"class":54},"\u003C{\n",[48,4433,4434,4437,4439,4442,4444,4447],{"class":50,"line":81},[48,4435,4436],{"class":261},"  prefix",[48,4438,3946],{"class":62},[48,4440,4441],{"class":54}," () ",[48,4443,647],{"class":62},[48,4445,4446],{"class":332}," unknown",[48,4448,318],{"class":54},[48,4450,4451,4454,4456,4458,4460,4462,4464,4467,4469,4472,4474,4477,4479,4482,4484,4487,4489,4492,4495,4497,4499],{"class":50,"line":87},[48,4452,4453],{"class":261},"  suffix",[48,4455,3946],{"class":62},[48,4457,2183],{"class":54},[48,4459,731],{"class":600},[48,4461,336],{"class":62},[48,4463,700],{"class":54},[48,4465,4466],{"class":600},"validationState",[48,4468,336],{"class":62},[48,4470,4471],{"class":69}," 'idle'",[48,4473,678],{"class":62},[48,4475,4476],{"class":69}," 'valid'",[48,4478,678],{"class":62},[48,4480,4481],{"class":69}," 'error'",[48,4483,3951],{"class":54},[48,4485,4486],{"class":600},"validation",[48,4488,336],{"class":62},[48,4490,4491],{"class":261}," PhoneValidationResult",[48,4493,4494],{"class":54}," }) ",[48,4496,647],{"class":62},[48,4498,4446],{"class":332},[48,4500,318],{"class":54},[48,4502,4503,4506,4508,4510,4512,4514],{"class":50,"line":93},[48,4504,4505],{"class":69},"  'valid-icon'",[48,4507,3946],{"class":62},[48,4509,4441],{"class":54},[48,4511,647],{"class":62},[48,4513,4446],{"class":332},[48,4515,318],{"class":54},[48,4517,4518,4521,4523,4525,4527,4529,4531,4534,4536,4538,4540,4542,4544],{"class":50,"line":362},[48,4519,4520],{"class":69},"  'error-icon'",[48,4522,3946],{"class":62},[48,4524,2183],{"class":54},[48,4526,731],{"class":600},[48,4528,336],{"class":62},[48,4530,700],{"class":54},[48,4532,4533],{"class":600},"reason",[48,4535,336],{"class":62},[48,4537,606],{"class":332},[48,4539,4494],{"class":54},[48,4541,647],{"class":62},[48,4543,4446],{"class":332},[48,4545,318],{"class":54},[48,4547,4548,4551,4553,4555,4557,4559,4561,4563,4565,4567,4569,4572,4574,4576,4578,4581,4583,4585,4587,4589,4591,4593,4595],{"class":50,"line":373},[48,4549,4550],{"class":261},"  hint",[48,4552,3946],{"class":62},[48,4554,2183],{"class":54},[48,4556,731],{"class":600},[48,4558,336],{"class":62},[48,4560,700],{"class":54},[48,4562,1959],{"class":600},[48,4564,336],{"class":62},[48,4566,606],{"class":332},[48,4568,3951],{"class":54},[48,4570,4571],{"class":600},"formatHint",[48,4573,336],{"class":62},[48,4575,606],{"class":332},[48,4577,3951],{"class":54},[48,4579,4580],{"class":600},"example",[48,4582,336],{"class":62},[48,4584,606],{"class":332},[48,4586,678],{"class":62},[48,4588,656],{"class":332},[48,4590,4494],{"class":54},[48,4592,647],{"class":62},[48,4594,4446],{"class":332},[48,4596,318],{"class":54},[48,4598,4599,4602,4604,4606,4608,4610,4612,4615,4617,4619,4621,4623,4625,4627,4629,4631,4633,4635,4637,4639,4641],{"class":50,"line":384},[48,4600,4601],{"class":261},"  error",[48,4603,3946],{"class":62},[48,4605,2183],{"class":54},[48,4607,731],{"class":600},[48,4609,336],{"class":62},[48,4611,700],{"class":54},[48,4613,4614],{"class":600},"message",[48,4616,336],{"class":62},[48,4618,606],{"class":332},[48,4620,3951],{"class":54},[48,4622,4533],{"class":600},[48,4624,336],{"class":62},[48,4626,606],{"class":332},[48,4628,3951],{"class":54},[48,4630,4486],{"class":600},[48,4632,336],{"class":62},[48,4634,4491],{"class":261},[48,4636,4494],{"class":54},[48,4638,647],{"class":62},[48,4640,4446],{"class":332},[48,4642,318],{"class":54},[48,4644,4645,4648,4650,4652,4654,4656,4658,4661,4663,4666,4668,4670,4672,4674,4676,4679,4681,4684,4686,4688,4690,4692,4694],{"class":50,"line":661},[48,4646,4647],{"class":261},"  trigger",[48,4649,3946],{"class":62},[48,4651,2183],{"class":54},[48,4653,731],{"class":600},[48,4655,336],{"class":62},[48,4657,700],{"class":54},[48,4659,4660],{"class":600},"selectedCountry",[48,4662,336],{"class":62},[48,4664,4665],{"class":261}," CountryOption",[48,4667,678],{"class":62},[48,4669,656],{"class":332},[48,4671,3951],{"class":54},[48,4673,4291],{"class":600},[48,4675,336],{"class":62},[48,4677,4678],{"class":332}," boolean",[48,4680,3951],{"class":54},[48,4682,4683],{"class":600},"sizeClasses",[48,4685,336],{"class":62},[48,4687,606],{"class":332},[48,4689,4494],{"class":54},[48,4691,647],{"class":62},[48,4693,4446],{"class":332},[48,4695,318],{"class":54},[48,4697,4698,4701,4703,4705,4707,4709,4711,4713,4715,4717,4719,4721,4723],{"class":50,"line":690},[48,4699,4700],{"class":261},"  chevron",[48,4702,3946],{"class":62},[48,4704,2183],{"class":54},[48,4706,731],{"class":600},[48,4708,336],{"class":62},[48,4710,700],{"class":54},[48,4712,4291],{"class":600},[48,4714,336],{"class":62},[48,4716,4678],{"class":332},[48,4718,4494],{"class":54},[48,4720,647],{"class":62},[48,4722,4446],{"class":332},[48,4724,318],{"class":54},[48,4726,4727,4730,4732,4734,4736,4738,4740,4742,4744,4746,4748,4751,4753,4756,4758,4761,4763,4765,4767],{"class":50,"line":695},[48,4728,4729],{"class":261},"  flag",[48,4731,3946],{"class":62},[48,4733,2183],{"class":54},[48,4735,731],{"class":600},[48,4737,336],{"class":62},[48,4739,700],{"class":54},[48,4741,1959],{"class":600},[48,4743,336],{"class":62},[48,4745,4665],{"class":261},[48,4747,3951],{"class":54},[48,4749,4750],{"class":600},"context",[48,4752,336],{"class":62},[48,4754,4755],{"class":69}," 'trigger'",[48,4757,678],{"class":62},[48,4759,4760],{"class":69}," 'item'",[48,4762,4494],{"class":54},[48,4764,647],{"class":62},[48,4766,4446],{"class":332},[48,4768,318],{"class":54},[48,4770,4771,4774,4776,4778,4780,4782,4784,4786,4788,4790,4792,4795,4797,4799,4801,4804,4806,4808,4810,4813,4815,4817,4819,4822,4824,4826,4828],{"class":50,"line":722},[48,4772,4773],{"class":261},"  item",[48,4775,3946],{"class":62},[48,4777,2183],{"class":54},[48,4779,731],{"class":600},[48,4781,336],{"class":62},[48,4783,700],{"class":54},[48,4785,1959],{"class":600},[48,4787,336],{"class":62},[48,4789,4665],{"class":261},[48,4791,3951],{"class":54},[48,4793,4794],{"class":600},"selected",[48,4796,336],{"class":62},[48,4798,4678],{"class":332},[48,4800,3951],{"class":54},[48,4802,4803],{"class":600},"disabled",[48,4805,336],{"class":62},[48,4807,4678],{"class":332},[48,4809,3951],{"class":54},[48,4811,4812],{"class":261},"select",[48,4814,336],{"class":62},[48,4816,4441],{"class":54},[48,4818,647],{"class":62},[48,4820,4821],{"class":332}," void",[48,4823,4494],{"class":54},[48,4825,647],{"class":62},[48,4827,4446],{"class":332},[48,4829,318],{"class":54},[48,4831,4832,4835,4837,4839,4841,4843,4845,4848,4850,4852,4854,4857,4859,4862,4864,4867,4869,4871,4873],{"class":50,"line":743},[48,4833,4834],{"class":69},"  'group-header'",[48,4836,3946],{"class":62},[48,4838,2183],{"class":54},[48,4840,731],{"class":600},[48,4842,336],{"class":62},[48,4844,700],{"class":54},[48,4846,4847],{"class":600},"label",[48,4849,336],{"class":62},[48,4851,606],{"class":332},[48,4853,3951],{"class":54},[48,4855,4856],{"class":600},"group",[48,4858,336],{"class":62},[48,4860,4861],{"class":69}," 'suggested'",[48,4863,678],{"class":62},[48,4865,4866],{"class":69}," 'all'",[48,4868,4494],{"class":54},[48,4870,647],{"class":62},[48,4872,4446],{"class":332},[48,4874,318],{"class":54},[48,4876,4877,4880,4882,4884,4886,4888,4890,4893,4895,4897,4899,4902,4904,4906,4909,4911,4913,4915,4917,4919,4921,4924,4926,4928,4930,4932,4934],{"class":50,"line":767},[48,4878,4879],{"class":261},"  search",[48,4881,3946],{"class":62},[48,4883,2183],{"class":54},[48,4885,731],{"class":600},[48,4887,336],{"class":62},[48,4889,700],{"class":54},[48,4891,4892],{"class":600},"value",[48,4894,336],{"class":62},[48,4896,606],{"class":332},[48,4898,3951],{"class":54},[48,4900,4901],{"class":261},"setValue",[48,4903,336],{"class":62},[48,4905,2183],{"class":54},[48,4907,4908],{"class":600},"v",[48,4910,336],{"class":62},[48,4912,606],{"class":332},[48,4914,806],{"class":54},[48,4916,647],{"class":62},[48,4918,4821],{"class":332},[48,4920,3951],{"class":54},[48,4922,4923],{"class":600},"isSearching",[48,4925,336],{"class":62},[48,4927,4678],{"class":332},[48,4929,4494],{"class":54},[48,4931,647],{"class":62},[48,4933,4446],{"class":332},[48,4935,318],{"class":54},[48,4937,4938,4941,4943,4945,4947,4949],{"class":50,"line":778},[48,4939,4940],{"class":261},"  loading",[48,4942,3946],{"class":62},[48,4944,4441],{"class":54},[48,4946,647],{"class":62},[48,4948,4446],{"class":332},[48,4950,318],{"class":54},[48,4952,4953,4956,4958,4960,4962,4964,4966,4969,4971,4973,4975,4977,4979],{"class":50,"line":783},[48,4954,4955],{"class":261},"  empty",[48,4957,3946],{"class":62},[48,4959,2183],{"class":54},[48,4961,731],{"class":600},[48,4963,336],{"class":62},[48,4965,700],{"class":54},[48,4967,4968],{"class":600},"query",[48,4970,336],{"class":62},[48,4972,606],{"class":332},[48,4974,4494],{"class":54},[48,4976,647],{"class":62},[48,4978,4446],{"class":332},[48,4980,318],{"class":54},[48,4982,4983],{"class":50,"line":788},[48,4984,4985],{"class":54},"}>();\n",[10,4987,4988,4989,158,4992,4995,4996,3705,4999,5002,5003,158,5006,158,5009,5012,5013,158,5016,158,5018,158,5021,158,5024,5027],{},"أربعة عشر slot موزّعة على ثلاث مجموعات. المجموعة البصرية (",[17,4990,4991],{},"prefix",[17,4993,4994],{},"suffix",", وslot-ا الأيقونتين، و",[17,4997,4998],{},"hint",[17,5000,5001],{},"error",") تزيّن الحقل نفسه. مجموعة مُشغِّل المنتقي (",[17,5004,5005],{},"trigger",[17,5007,5008],{},"chevron",[17,5010,5011],{},"flag",") تستبدل الزرّ الذي ينقر عليه المستخدم. ومجموعة محتوى المنتقي (",[17,5014,5015],{},"search",[17,5017,262],{},[17,5019,5020],{},"empty",[17,5022,5023],{},"group-header",[17,5025,5026],{},"item",") تستبدل أيّ شيء داخل الـ popover أو الـ drawer.",[10,5029,5030,5031,5034],{},"معظم slot-ات المنتقي تُمرَّر مباشرة إلى ",[17,5032,5033],{},"ACountrySelect",". المكوّن نفسه يتولّى التوجيه:",[39,5036,5038],{"className":41,"code":5037,"language":43,"meta":44,"style":44},"\u003Ctemplate v-if=\"$slots.trigger\" #trigger=\"slotProps\">\n  \u003Cslot name=\"trigger\" v-bind=\"slotProps\" \u002F>\n\u003C\u002Ftemplate>\n\u003Ctemplate v-if=\"$slots.chevron\" #chevron=\"slotProps\">\n  \u003Cslot name=\"chevron\" v-bind=\"slotProps\" \u002F>\n\u003C\u002Ftemplate>\n\u003C!-- ... and so on for each forwarded slot -->\n",[17,5039,5040,5073,5100,5108,5139,5162,5170],{"__ignoreMap":44},[48,5041,5042,5044,5046,5048,5050,5052,5055,5057,5060,5062,5064,5066,5069,5071],{"class":50,"line":51},[48,5043,55],{"class":54},[48,5045,838],{"class":58},[48,5047,63],{"class":62},[48,5049,66],{"class":54},[48,5051,70],{"class":69},[48,5053,5054],{"class":54},"$slots.trigger",[48,5056,70],{"class":69},[48,5058,5059],{"class":54}," #",[48,5061,5005],{"class":261},[48,5063,66],{"class":54},[48,5065,70],{"class":69},[48,5067,5068],{"class":54},"slotProps",[48,5070,70],{"class":69},[48,5072,78],{"class":54},[48,5074,5075,5077,5080,5083,5085,5088,5091,5093,5096,5098],{"class":50,"line":81},[48,5076,846],{"class":54},[48,5078,5079],{"class":58},"slot",[48,5081,5082],{"class":261}," name",[48,5084,66],{"class":54},[48,5086,5087],{"class":69},"\"trigger\"",[48,5089,5090],{"class":261}," v-bind",[48,5092,66],{"class":54},[48,5094,5095],{"class":69},"\"slotProps\"",[48,5097,1116],{"class":1115},[48,5099,78],{"class":54},[48,5101,5102,5104,5106],{"class":50,"line":87},[48,5103,96],{"class":54},[48,5105,838],{"class":58},[48,5107,78],{"class":54},[48,5109,5110,5112,5114,5116,5118,5120,5123,5125,5127,5129,5131,5133,5135,5137],{"class":50,"line":93},[48,5111,55],{"class":54},[48,5113,838],{"class":58},[48,5115,63],{"class":62},[48,5117,66],{"class":54},[48,5119,70],{"class":69},[48,5121,5122],{"class":54},"$slots.chevron",[48,5124,70],{"class":69},[48,5126,5059],{"class":54},[48,5128,5008],{"class":261},[48,5130,66],{"class":54},[48,5132,70],{"class":69},[48,5134,5068],{"class":54},[48,5136,70],{"class":69},[48,5138,78],{"class":54},[48,5140,5141,5143,5145,5147,5149,5152,5154,5156,5158,5160],{"class":50,"line":362},[48,5142,846],{"class":54},[48,5144,5079],{"class":58},[48,5146,5082],{"class":261},[48,5148,66],{"class":54},[48,5150,5151],{"class":69},"\"chevron\"",[48,5153,5090],{"class":261},[48,5155,66],{"class":54},[48,5157,5095],{"class":69},[48,5159,1116],{"class":1115},[48,5161,78],{"class":54},[48,5163,5164,5166,5168],{"class":50,"line":373},[48,5165,96],{"class":54},[48,5167,838],{"class":58},[48,5169,78],{"class":54},[48,5171,5172],{"class":50,"line":384},[48,5173,5174],{"class":2472},"\u003C!-- ... and so on for each forwarded slot -->\n",[10,5176,5177,5178,5181,5182,5185,5186,5188],{},"الشرط ",[17,5179,5180],{},"v-if=\"$slots.x\""," هنا مهم. بدونه سيبدو الـ ",[17,5183,5184],{},"\u003Ctemplate #x>"," المُمرَّر موجوداً دائماً من منظور الأب، فلا يحصل الرسم الاحتياطي داخل ",[17,5187,5033],{}," على أيّ فرصة للعمل. الحارس يقول ببساطة: \"لا تُمرّر إلا إذا قدّم المستهلك هذا الـ slot فعلاً\".",[10,5190,5191],{},"مثال تخصيص واقعي:",[39,5193,5195],{"className":41,"code":5194,"language":43,"meta":44,"style":44},"\u003CATellInput v-model:phone=\"phone\" v-model:country=\"country\" show-validation>\n  \u003Ctemplate #suffix=\"{ validationState }\">\n    \u003CSparkle v-if=\"validationState === 'valid'\" class=\"size-4 text-amber-400\" \u002F>\n  \u003C\u002Ftemplate>\n  \u003Ctemplate #error=\"{ message, reason }\">\n    \u003Cp class=\"text-destructive text-xs\">\n      \u003Ccode class=\"bg-muted rounded px-1 py-0.5\">{{ reason }}\u003C\u002Fcode> {{ message }}\n    \u003C\u002Fp>\n  \u003C\u002Ftemplate>\n  \u003Ctemplate #empty=\"{ query }\">\n    \u003Cdiv class=\"px-3 py-6 text-center text-sm\">\n      Nothing matched \"\u003Cstrong>{{ query }}\u003C\u002Fstrong>\".\n      \u003Cbutton type=\"button\" class=\"underline\" @click=\"suggestCountry(query)\">\n        Suggest a country\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  \u003C\u002Ftemplate>\n\u003C\u002FATellInput>\n",[17,5196,5197,5236,5241,5246,5251,5256,5261,5266,5271,5275,5280,5285,5290,5295,5300,5305,5310,5314],{"__ignoreMap":44},[48,5198,5199,5201,5203,5205,5207,5209,5211,5213,5215,5217,5219,5221,5223,5225,5227,5229,5231,5234],{"class":50,"line":51},[48,5200,55],{"class":54},[48,5202,1865],{"class":58},[48,5204,4286],{"class":261},[48,5206,336],{"class":54},[48,5208,4186],{"class":261},[48,5210,66],{"class":54},[48,5212,70],{"class":69},[48,5214,4186],{"class":54},[48,5216,70],{"class":69},[48,5218,4286],{"class":261},[48,5220,336],{"class":54},[48,5222,1959],{"class":261},[48,5224,66],{"class":54},[48,5226,70],{"class":69},[48,5228,1959],{"class":54},[48,5230,70],{"class":69},[48,5232,5233],{"class":261}," show-validation",[48,5235,78],{"class":54},[48,5237,5238],{"class":50,"line":81},[48,5239,5240],{"class":54},"  \u003Ctemplate #suffix=\"{ validationState }\">\n",[48,5242,5243],{"class":50,"line":87},[48,5244,5245],{"class":54},"    \u003CSparkle v-if=\"validationState === 'valid'\" class=\"size-4 text-amber-400\" \u002F>\n",[48,5247,5248],{"class":50,"line":93},[48,5249,5250],{"class":54},"  \u003C\u002Ftemplate>\n",[48,5252,5253],{"class":50,"line":362},[48,5254,5255],{"class":54},"  \u003Ctemplate #error=\"{ message, reason }\">\n",[48,5257,5258],{"class":50,"line":373},[48,5259,5260],{"class":54},"    \u003Cp class=\"text-destructive text-xs\">\n",[48,5262,5263],{"class":50,"line":384},[48,5264,5265],{"class":54},"      \u003Ccode class=\"bg-muted rounded px-1 py-0.5\">{{ reason }}\u003C\u002Fcode> {{ message }}\n",[48,5267,5268],{"class":50,"line":661},[48,5269,5270],{"class":54},"    \u003C\u002Fp>\n",[48,5272,5273],{"class":50,"line":690},[48,5274,5250],{"class":54},[48,5276,5277],{"class":50,"line":695},[48,5278,5279],{"class":54},"  \u003Ctemplate #empty=\"{ query }\">\n",[48,5281,5282],{"class":50,"line":722},[48,5283,5284],{"class":54},"    \u003Cdiv class=\"px-3 py-6 text-center text-sm\">\n",[48,5286,5287],{"class":50,"line":743},[48,5288,5289],{"class":54},"      Nothing matched \"\u003Cstrong>{{ query }}\u003C\u002Fstrong>\".\n",[48,5291,5292],{"class":50,"line":767},[48,5293,5294],{"class":54},"      \u003Cbutton type=\"button\" class=\"underline\" @click=\"suggestCountry(query)\">\n",[48,5296,5297],{"class":50,"line":778},[48,5298,5299],{"class":54},"        Suggest a country\n",[48,5301,5302],{"class":50,"line":783},[48,5303,5304],{"class":54},"      \u003C\u002Fbutton>\n",[48,5306,5307],{"class":50,"line":788},[48,5308,5309],{"class":54},"    \u003C\u002Fdiv>\n",[48,5311,5312],{"class":50,"line":819},[48,5313,5250],{"class":54},[48,5315,5316,5318,5320],{"class":50,"line":828},[48,5317,96],{"class":54},[48,5319,1865],{"class":58},[48,5321,78],{"class":54},[10,5323,5324],{},"وظيفة مكوّن المكتبة أن يملك السلوك (التحليل، والتحقّق، والاكتشاف، وإدارة الحالة)، وأن يترك للمستهلك التحكّم بشكل هذه الأشياء. أربعة عشر slot هي كلفة هذه الصفقة، وهي كلفة تستحقّ.",[10,5326,5327],{},"المبدأ نفسه ينطبق على الـ props غير البصرية:",[39,5329,5331],{"className":1893,"code":5330,"language":1895,"meta":44,"style":44},"flagUrl?: (iso2: string, width: number) => string;\nsearcher?: (query: string, country: CountryOption) => boolean;\ncountries?: CountryOption[];\ndetector?: (options: DetectCountryOptions) => Promise\u003Cstring | null | undefined>;\nerrorMessages?: Record\u003CPhoneValidationReason, string>;\n",[17,5332,5333,5365,5395,5405,5446],{"__ignoreMap":44},[48,5334,5335,5338,5340,5342,5345,5347,5349,5351,5354,5356,5358,5360,5362],{"class":50,"line":51},[48,5336,5337],{"class":54},"flagUrl",[48,5339,3946],{"class":62},[48,5341,2183],{"class":54},[48,5343,5344],{"class":600},"iso2",[48,5346,336],{"class":62},[48,5348,606],{"class":332},[48,5350,158],{"class":54},[48,5352,5353],{"class":600},"width",[48,5355,336],{"class":62},[48,5357,3775],{"class":332},[48,5359,806],{"class":54},[48,5361,647],{"class":62},[48,5363,5364],{"class":54}," string;\n",[48,5366,5367,5370,5372,5374,5376,5378,5380,5382,5384,5386,5388,5390,5392],{"class":50,"line":81},[48,5368,5369],{"class":54},"searcher",[48,5371,3946],{"class":62},[48,5373,2183],{"class":54},[48,5375,4968],{"class":600},[48,5377,336],{"class":62},[48,5379,606],{"class":332},[48,5381,158],{"class":54},[48,5383,1959],{"class":600},[48,5385,336],{"class":62},[48,5387,4665],{"class":261},[48,5389,806],{"class":54},[48,5391,647],{"class":62},[48,5393,5394],{"class":54}," boolean;\n",[48,5396,5397,5400,5402],{"class":50,"line":87},[48,5398,5399],{"class":54},"countries",[48,5401,3946],{"class":62},[48,5403,5404],{"class":54}," CountryOption[];\n",[48,5406,5407,5409,5411,5413,5416,5418,5420,5422,5424,5426,5428,5431,5434,5436,5438,5441,5444],{"class":50,"line":93},[48,5408,4219],{"class":54},[48,5410,3946],{"class":62},[48,5412,2183],{"class":54},[48,5414,5415],{"class":600},"options",[48,5417,336],{"class":62},[48,5419,3360],{"class":261},[48,5421,806],{"class":54},[48,5423,647],{"class":62},[48,5425,3370],{"class":332},[48,5427,55],{"class":62},[48,5429,5430],{"class":54},"string ",[48,5432,5433],{"class":62},"|",[48,5435,656],{"class":332},[48,5437,678],{"class":62},[48,5439,5440],{"class":332}," undefined",[48,5442,5443],{"class":62},">",[48,5445,318],{"class":54},[48,5447,5448,5451,5453,5456,5458,5461,5463],{"class":50,"line":362},[48,5449,5450],{"class":54},"errorMessages",[48,5452,3946],{"class":62},[48,5454,5455],{"class":54}," Record",[48,5457,55],{"class":62},[48,5459,5460],{"class":54},"PhoneValidationReason, string",[48,5462,5443],{"class":62},[48,5464,318],{"class":54},[10,5466,5467,5468,5470,5471,5474,5475,5477,5478,5480,5481,5484,5485,5487],{},"يُتيح لك ",[17,5469,5337],{}," استبدال ",[17,5472,5473],{},"flagcdn.com"," بـ CDN خاصّ بك، وهو مفيد إن كان لديك CSP لا يسمح بصور من طرف ثالث. ويُتيح لك ",[17,5476,5369],{}," استبدال مطابقة السلاسل الفرعية بمطابقة بادئة، أو مطابقة ضبابية، أو مطابقة بالاسم العربي، أو أيّ شيء يحتاجه مستخدموك فعلاً. ويُتيح لك ",[17,5479,5399],{}," شحن قائمتك المُنتقاة والاستغناء كلياً عن نداء ",[17,5482,5483],{},"REST Countries",", وهو مفيد إن كنتَ حسمتَ أنّك تخدم دول الخليج مثلاً. أمّا ",[17,5486,5450],{}," فمعنيّ بالتوطين: مرّر جدولاً عربياً وستُعرض أسباب التحقّق بالعربية.",[10,5489,5490],{},"كلّ prop له افتراضي منطقي. النداء الذي لا يحتاج تكويناً ما زال يعمل كما هو.",[25,5492,5494],{"id":5493},"جدول-رموز-الاتصال-المتزامن-ولماذا-ليس-اختيارياً","جدول رموز الاتصال المتزامن، ولماذا ليس اختيارياً",[10,5496,5497,5498,5500,5501,2183,5504,158,5507,5510,5511,5514,5515,5518,5519,1259,5521,5523],{},"عند أوّل تركيب للمكوّن في تبويب جديد، لا يكون نداء ",[17,5499,5483],{}," قد اكتمل. أيّ فهارس يبنيها ",[17,5502,5503],{},"usePhoneValidation",[17,5505,5506],{},"byValue",[17,5508,5509],{},"byDialDigits",") تكون خرائط فارغة. لو كتب المستهلك ",[17,5512,5513],{},"default-country=\"20\""," في قالبه متوقّعاً أن يظهر المنتقي وقد اختار مصر، فما سيحصل عليه بلا مساعدة هو منتقٍ فارغ. فهرس أرقام الاتصال يعيد ",[17,5516,5517],{},"[]",", ويضبط الـ watcher قيمة ",[17,5520,2100],{},[17,5522,2021],{},", ولن تظهر مصر إلا حين يعود نداء الشبكة.",[10,5525,5526],{},"لذلك يُدرِج المكوّن جدولاً احتياطياً متزامناً في داخله:",[39,5528,5530],{"className":1893,"code":5529,"language":1895,"meta":44,"style":44},"const DIAL_TO_ISO2_FALLBACK: Record\u003Cstring, string> = {\n  '1': 'US',\n  '7': 'RU',\n  '20': 'EG',\n  '27': 'ZA',\n  '33': 'FR',\n  '44': 'GB',\n  '49': 'DE',\n  '52': 'MX',\n  '55': 'BR',\n  '61': 'AU',\n  '81': 'JP',\n  '86': 'CN',\n  '91': 'IN',\n  '212': 'MA',\n  '966': 'SA',\n  '971': 'AE',\n  \u002F\u002F ... ~50 entries\n};\n",[17,5531,5532,5558,5570,5582,5594,5606,5618,5630,5642,5654,5666,5678,5690,5702,5714,5726,5738,5750,5755],{"__ignoreMap":44},[48,5533,5534,5536,5539,5541,5543,5545,5547,5549,5551,5554,5556],{"class":50,"line":51},[48,5535,329],{"class":62},[48,5537,5538],{"class":332}," DIAL_TO_ISO2_FALLBACK",[48,5540,336],{"class":62},[48,5542,5455],{"class":261},[48,5544,55],{"class":54},[48,5546,1914],{"class":332},[48,5548,158],{"class":54},[48,5550,1914],{"class":332},[48,5552,5553],{"class":54},"> ",[48,5555,66],{"class":62},[48,5557,1624],{"class":54},[48,5559,5560,5563,5565,5568],{"class":50,"line":81},[48,5561,5562],{"class":69},"  '1'",[48,5564,1632],{"class":54},[48,5566,5567],{"class":69},"'US'",[48,5569,359],{"class":54},[48,5571,5572,5575,5577,5580],{"class":50,"line":87},[48,5573,5574],{"class":69},"  '7'",[48,5576,1632],{"class":54},[48,5578,5579],{"class":69},"'RU'",[48,5581,359],{"class":54},[48,5583,5584,5587,5589,5592],{"class":50,"line":93},[48,5585,5586],{"class":69},"  '20'",[48,5588,1632],{"class":54},[48,5590,5591],{"class":69},"'EG'",[48,5593,359],{"class":54},[48,5595,5596,5599,5601,5604],{"class":50,"line":362},[48,5597,5598],{"class":69},"  '27'",[48,5600,1632],{"class":54},[48,5602,5603],{"class":69},"'ZA'",[48,5605,359],{"class":54},[48,5607,5608,5611,5613,5616],{"class":50,"line":373},[48,5609,5610],{"class":69},"  '33'",[48,5612,1632],{"class":54},[48,5614,5615],{"class":69},"'FR'",[48,5617,359],{"class":54},[48,5619,5620,5623,5625,5628],{"class":50,"line":384},[48,5621,5622],{"class":69},"  '44'",[48,5624,1632],{"class":54},[48,5626,5627],{"class":69},"'GB'",[48,5629,359],{"class":54},[48,5631,5632,5635,5637,5640],{"class":50,"line":661},[48,5633,5634],{"class":69},"  '49'",[48,5636,1632],{"class":54},[48,5638,5639],{"class":69},"'DE'",[48,5641,359],{"class":54},[48,5643,5644,5647,5649,5652],{"class":50,"line":690},[48,5645,5646],{"class":69},"  '52'",[48,5648,1632],{"class":54},[48,5650,5651],{"class":69},"'MX'",[48,5653,359],{"class":54},[48,5655,5656,5659,5661,5664],{"class":50,"line":695},[48,5657,5658],{"class":69},"  '55'",[48,5660,1632],{"class":54},[48,5662,5663],{"class":69},"'BR'",[48,5665,359],{"class":54},[48,5667,5668,5671,5673,5676],{"class":50,"line":722},[48,5669,5670],{"class":69},"  '61'",[48,5672,1632],{"class":54},[48,5674,5675],{"class":69},"'AU'",[48,5677,359],{"class":54},[48,5679,5680,5683,5685,5688],{"class":50,"line":743},[48,5681,5682],{"class":69},"  '81'",[48,5684,1632],{"class":54},[48,5686,5687],{"class":69},"'JP'",[48,5689,359],{"class":54},[48,5691,5692,5695,5697,5700],{"class":50,"line":767},[48,5693,5694],{"class":69},"  '86'",[48,5696,1632],{"class":54},[48,5698,5699],{"class":69},"'CN'",[48,5701,359],{"class":54},[48,5703,5704,5707,5709,5712],{"class":50,"line":778},[48,5705,5706],{"class":69},"  '91'",[48,5708,1632],{"class":54},[48,5710,5711],{"class":69},"'IN'",[48,5713,359],{"class":54},[48,5715,5716,5719,5721,5724],{"class":50,"line":783},[48,5717,5718],{"class":69},"  '212'",[48,5720,1632],{"class":54},[48,5722,5723],{"class":69},"'MA'",[48,5725,359],{"class":54},[48,5727,5728,5731,5733,5736],{"class":50,"line":788},[48,5729,5730],{"class":69},"  '966'",[48,5732,1632],{"class":54},[48,5734,5735],{"class":69},"'SA'",[48,5737,359],{"class":54},[48,5739,5740,5743,5745,5748],{"class":50,"line":819},[48,5741,5742],{"class":69},"  '971'",[48,5744,1632],{"class":54},[48,5746,5747],{"class":69},"'AE'",[48,5749,359],{"class":54},[48,5751,5752],{"class":50,"line":828},[48,5753,5754],{"class":2472},"  \u002F\u002F ... ~50 entries\n",[48,5756,5757],{"class":50,"line":833},[48,5758,5759],{"class":54},"};\n",[10,5761,5762,5763,5766],{},"ويتحقّق ",[17,5764,5765],{},"resolveCountryIdentifier"," أولاً من الفهرس المحمَّل، ثم من هذا الجدول:",[39,5768,5770],{"className":1893,"code":5769,"language":1895,"meta":44,"style":44},"function resolveCountryIdentifier(raw: string | undefined | null): string {\n  const v = String(raw ?? '').trim();\n  if (!v) return '';\n  if (\u002F^[A-Za-z]{2}$\u002F.test(v)) return v.toUpperCase();\n  const dial = v.replace(\u002F^\\+\u002F, '');\n  if (!\u002F^\\d+$\u002F.test(dial)) return '';\n  const match = getCountriesByDial(dial)[0];\n  if (match) return match.value;\n  return DIAL_TO_ISO2_FALLBACK[dial] ?? '';\n}\n",[17,5771,5772,5803,5829,5846,5879,5911,5944,5962,5974,5989],{"__ignoreMap":44},[48,5773,5774,5776,5778,5780,5783,5785,5787,5789,5791,5793,5795,5797,5799,5801],{"class":50,"line":51},[48,5775,2418],{"class":62},[48,5777,2212],{"class":261},[48,5779,624],{"class":54},[48,5781,5782],{"class":600},"raw",[48,5784,336],{"class":62},[48,5786,606],{"class":332},[48,5788,678],{"class":62},[48,5790,5440],{"class":332},[48,5792,678],{"class":62},[48,5794,656],{"class":332},[48,5796,2433],{"class":54},[48,5798,336],{"class":62},[48,5800,606],{"class":332},[48,5802,1624],{"class":54},[48,5804,5805,5807,5810,5812,5815,5818,5820,5822,5824,5827],{"class":50,"line":81},[48,5806,2603],{"class":62},[48,5808,5809],{"class":332}," v",[48,5811,342],{"class":62},[48,5813,5814],{"class":261}," String",[48,5816,5817],{"class":54},"(raw ",[48,5819,2555],{"class":62},[48,5821,2162],{"class":69},[48,5823,1250],{"class":54},[48,5825,5826],{"class":261},"trim",[48,5828,3477],{"class":54},[48,5830,5831,5833,5835,5837,5840,5842,5844],{"class":50,"line":87},[48,5832,2449],{"class":62},[48,5834,2183],{"class":54},[48,5836,2454],{"class":62},[48,5838,5839],{"class":54},"v) ",[48,5841,2197],{"class":62},[48,5843,2162],{"class":69},[48,5845,318],{"class":54},[48,5847,5848,5850,5852,5854,5856,5859,5861,5863,5865,5867,5870,5872,5875,5877],{"class":50,"line":93},[48,5849,2449],{"class":62},[48,5851,2183],{"class":54},[48,5853,4011],{"class":69},[48,5855,4002],{"class":62},[48,5857,5858],{"class":332},"[A-Za-z]",[48,5860,4008],{"class":62},[48,5862,4011],{"class":69},[48,5864,526],{"class":54},[48,5866,4016],{"class":261},[48,5868,5869],{"class":54},"(v)) ",[48,5871,2197],{"class":62},[48,5873,5874],{"class":54}," v.",[48,5876,3521],{"class":261},[48,5878,3477],{"class":54},[48,5880,5881,5883,5886,5888,5890,5893,5895,5897,5899,5903,5905,5907,5909],{"class":50,"line":362},[48,5882,2603],{"class":62},[48,5884,5885],{"class":332}," dial",[48,5887,342],{"class":62},[48,5889,5874],{"class":54},[48,5891,5892],{"class":261},"replace",[48,5894,624],{"class":54},[48,5896,4011],{"class":69},[48,5898,4002],{"class":62},[48,5900,5902],{"class":5901},"snhLl","\\+",[48,5904,4011],{"class":69},[48,5906,158],{"class":54},[48,5908,2021],{"class":69},[48,5910,629],{"class":54},[48,5912,5913,5915,5917,5919,5921,5923,5926,5929,5931,5933,5935,5938,5940,5942],{"class":50,"line":373},[48,5914,2449],{"class":62},[48,5916,2183],{"class":54},[48,5918,2454],{"class":62},[48,5920,4011],{"class":69},[48,5922,4002],{"class":62},[48,5924,5925],{"class":332},"\\d",[48,5927,5928],{"class":62},"+$",[48,5930,4011],{"class":69},[48,5932,526],{"class":54},[48,5934,4016],{"class":261},[48,5936,5937],{"class":54},"(dial)) ",[48,5939,2197],{"class":62},[48,5941,2162],{"class":69},[48,5943,318],{"class":54},[48,5945,5946,5948,5950,5952,5954,5957,5959],{"class":50,"line":384},[48,5947,2603],{"class":62},[48,5949,3142],{"class":332},[48,5951,342],{"class":62},[48,5953,2855],{"class":261},[48,5955,5956],{"class":54},"(dial)[",[48,5958,2839],{"class":332},[48,5960,5961],{"class":54},"];\n",[48,5963,5964,5966,5969,5971],{"class":50,"line":661},[48,5965,2449],{"class":62},[48,5967,5968],{"class":54}," (match) ",[48,5970,2197],{"class":62},[48,5972,5973],{"class":54}," match.value;\n",[48,5975,5976,5978,5980,5983,5985,5987],{"class":50,"line":690},[48,5977,2962],{"class":62},[48,5979,5538],{"class":332},[48,5981,5982],{"class":54},"[dial] ",[48,5984,2555],{"class":62},[48,5986,2162],{"class":69},[48,5988,318],{"class":54},[48,5990,5991],{"class":50,"line":695},[48,5992,1718],{"class":54},[10,5994,5995],{},"الاتجاه المعاكس، أي التحويل من ISO2 إلى رقم اتصال من أجل v-model, يُجري الفحص نفسه:",[39,5997,5999],{"className":1893,"code":5998,"language":1895,"meta":44,"style":44},"function dialNumberFor(iso2: string): number | null {\n  if (!iso2) return null;\n  const fromIndex = getCountryByValue(iso2)?.raw_data?.dial_digits;\n  const digits = fromIndex ?? Object.entries(DIAL_TO_ISO2_FALLBACK).find(([, v]) => v === iso2)?.[0];\n  if (!digits) return null;\n  const n = Number(digits);\n  return Number.isFinite(n) ? n : null;\n}\n",[17,6000,6001,6028,6045,6059,6111,6127,6142,6166],{"__ignoreMap":44},[48,6002,6003,6005,6008,6010,6012,6014,6016,6018,6020,6022,6024,6026],{"class":50,"line":51},[48,6004,2418],{"class":62},[48,6006,6007],{"class":261}," dialNumberFor",[48,6009,624],{"class":54},[48,6011,5344],{"class":600},[48,6013,336],{"class":62},[48,6015,606],{"class":332},[48,6017,2433],{"class":54},[48,6019,336],{"class":62},[48,6021,3775],{"class":332},[48,6023,678],{"class":62},[48,6025,656],{"class":332},[48,6027,1624],{"class":54},[48,6029,6030,6032,6034,6036,6039,6041,6043],{"class":50,"line":81},[48,6031,2449],{"class":62},[48,6033,2183],{"class":54},[48,6035,2454],{"class":62},[48,6037,6038],{"class":54},"iso2) ",[48,6040,2197],{"class":62},[48,6042,656],{"class":332},[48,6044,318],{"class":54},[48,6046,6047,6049,6052,6054,6056],{"class":50,"line":87},[48,6048,2603],{"class":62},[48,6050,6051],{"class":332}," fromIndex",[48,6053,342],{"class":62},[48,6055,2529],{"class":261},[48,6057,6058],{"class":54},"(iso2)?.raw_data?.dial_digits;\n",[48,6060,6061,6063,6066,6068,6071,6073,6076,6079,6081,6084,6086,6089,6092,6094,6097,6099,6102,6104,6107,6109],{"class":50,"line":93},[48,6062,2603],{"class":62},[48,6064,6065],{"class":332}," digits",[48,6067,342],{"class":62},[48,6069,6070],{"class":54}," fromIndex ",[48,6072,2555],{"class":62},[48,6074,6075],{"class":54}," Object.",[48,6077,6078],{"class":261},"entries",[48,6080,624],{"class":54},[48,6082,6083],{"class":332},"DIAL_TO_ISO2_FALLBACK",[48,6085,1250],{"class":54},[48,6087,6088],{"class":261},"find",[48,6090,6091],{"class":54},"(([, ",[48,6093,4908],{"class":600},[48,6095,6096],{"class":54},"]) ",[48,6098,647],{"class":62},[48,6100,6101],{"class":54}," v ",[48,6103,653],{"class":62},[48,6105,6106],{"class":54}," iso2)?.[",[48,6108,2839],{"class":332},[48,6110,5961],{"class":54},[48,6112,6113,6115,6117,6119,6121,6123,6125],{"class":50,"line":362},[48,6114,2449],{"class":62},[48,6116,2183],{"class":54},[48,6118,2454],{"class":62},[48,6120,2457],{"class":54},[48,6122,2197],{"class":62},[48,6124,656],{"class":332},[48,6126,318],{"class":54},[48,6128,6129,6131,6134,6136,6139],{"class":50,"line":373},[48,6130,2603],{"class":62},[48,6132,6133],{"class":332}," n",[48,6135,342],{"class":62},[48,6137,6138],{"class":261}," Number",[48,6140,6141],{"class":54},"(digits);\n",[48,6143,6144,6146,6149,6152,6155,6157,6160,6162,6164],{"class":50,"line":384},[48,6145,2962],{"class":62},[48,6147,6148],{"class":54}," Number.",[48,6150,6151],{"class":261},"isFinite",[48,6153,6154],{"class":54},"(n) ",[48,6156,757],{"class":62},[48,6158,6159],{"class":54}," n ",[48,6161,336],{"class":62},[48,6163,656],{"class":332},[48,6165,318],{"class":54},[48,6167,6168],{"class":50,"line":661},[48,6169,1718],{"class":54},[10,6171,6172,6173,6176],{},"الجدول تسوية معلومة سلفاً. خمسون مدخلاً لا تكفي لتغطية كلّ رمز اتصال، واختيار الخمسين ينحاز إلى رأي: أكثر الدول سكاناً بالإضافة إلى تلك التي أحتاجها شخصياً. المستهلك الذي يمرّر ",[17,6174,6175],{},"default-country=\"678\""," (فانواتو) عند التركيب دون أيّ بيانات دولة مخبّأة لن يحصل على شيء من هذا البحث. بعد ثوانٍ قليلة، حين يعود نداء الشبكة، سيحسم المنتقي القيمة بشكل صحيح. غاية الاحتياطي أن تعمل الحالة الشائعة (95%) دون وميض، والذيل الطويل ينتهي إلى الصحيح.",[10,6178,6179],{},"داخل التطبيق تستطيع انتظار النداء. أمّا في المكتبة فلا تقدر على إلزام كلّ مستهلك بتعليق تركيب مكوّنه انتظاراً لواجهة برمجية طرف ثالث، ولا تستطيع في المقابل شحن 80 كيلوبايت من بيانات الدول بشكل فوري. الجدول المتزامن هو المخرج الثالث.",[25,6181,6183],{"id":6182},"آخر-الاختيارات-كفاصل-عند-التعادل","آخر الاختيارات كفاصل عند التعادل",[10,6185,6186,6187,6189],{},"مطابقة أطول بادئة في الطبقة الثالثة ليست دائماً واضحة بلا لبس. الرمز ",[17,6188,2090],{}," يطابق كتلة NANP كاملة، أكثر من 25 دولة. الأبجدي الأوّل يعني \"ساموا الأمريكية\" أو أيّاً كانت الدولة الأسبق أبجدياً في كلّ مرّة. هذا ليس افتراضاً مفيداً لأيّ أحد.",[10,6191,6192,6193,336],{},"لذلك تقرأ الطبقة الثالثة من ",[17,6194,1185],{},[39,6196,6198],{"className":1893,"code":6197,"language":1895,"meta":44,"style":44},"const recents = readRecents();\nconst recentHit = recents\n  .map((iso2) => group.find((c) => c.value === iso2))\n  .find((c): c is CountryOption => Boolean(c));\nif (recentHit) return { country: recentHit, nationalNumber };\nreturn { country: group[0], nationalNumber };\n",[17,6199,6200,6214,6226,6264,6295,6307],{"__ignoreMap":44},[48,6201,6202,6204,6207,6209,6212],{"class":50,"line":51},[48,6203,329],{"class":62},[48,6205,6206],{"class":332}," recents",[48,6208,342],{"class":62},[48,6210,6211],{"class":261}," readRecents",[48,6213,3477],{"class":54},[48,6215,6216,6218,6221,6223],{"class":50,"line":81},[48,6217,329],{"class":62},[48,6219,6220],{"class":332}," recentHit",[48,6222,342],{"class":62},[48,6224,6225],{"class":54}," recents\n",[48,6227,6228,6231,6234,6236,6238,6240,6242,6245,6247,6249,6252,6254,6256,6259,6261],{"class":50,"line":87},[48,6229,6230],{"class":54},"  .",[48,6232,6233],{"class":261},"map",[48,6235,800],{"class":54},[48,6237,5344],{"class":600},[48,6239,806],{"class":54},[48,6241,647],{"class":62},[48,6243,6244],{"class":54}," group.",[48,6246,6088],{"class":261},[48,6248,800],{"class":54},[48,6250,6251],{"class":600},"c",[48,6253,806],{"class":54},[48,6255,647],{"class":62},[48,6257,6258],{"class":54}," c.value ",[48,6260,653],{"class":62},[48,6262,6263],{"class":54}," iso2))\n",[48,6265,6266,6268,6270,6272,6274,6276,6278,6281,6284,6286,6289,6292],{"class":50,"line":93},[48,6267,6230],{"class":54},[48,6269,6088],{"class":261},[48,6271,800],{"class":54},[48,6273,6251],{"class":600},[48,6275,2433],{"class":54},[48,6277,336],{"class":62},[48,6279,6280],{"class":600}," c",[48,6282,6283],{"class":62}," is",[48,6285,4665],{"class":261},[48,6287,6288],{"class":62}," =>",[48,6290,6291],{"class":261}," Boolean",[48,6293,6294],{"class":54},"(c));\n",[48,6296,6297,6299,6302,6304],{"class":50,"line":362},[48,6298,3249],{"class":62},[48,6300,6301],{"class":54}," (recentHit) ",[48,6303,2197],{"class":62},[48,6305,6306],{"class":54}," { country: recentHit, nationalNumber };\n",[48,6308,6309,6311,6313,6315],{"class":50,"line":373},[48,6310,2197],{"class":62},[48,6312,2922],{"class":54},[48,6314,2839],{"class":332},[48,6316,2927],{"class":54},[10,6318,6319,6320,6323,6324,6326],{},"يكتب المنتقي في ",[17,6321,6322],{},"ali_ui_country_recents_v1"," كلّما اختار المستخدم دولةً صراحةً، ويقرأ المطابِق منها عند حصول الالتباس. إن كنتَ تعيش في كندا وسبق أن استعملتَ الحقل، فأوّل ",[17,6325,1977],{}," تكتبه سيُحلّ إلى كندا. وإن لم تستعمله من قبل، ستحصل على الاحتياطي الأبجدي، وهو خطأ نعم، لكنّه يقع مرّة واحدة فقط.",[10,6328,6329],{},"لمسة صغيرة، لكنّها من ذلك النوع الذي يحوّل \"هذا يعمل تقنياً\" إلى \"هذا مبنيّ بيدِ من استعمله فعلاً\".",[25,6331,6333],{"id":6332},"عن-ماذا-كانت-إعادة-كتابة-المكتبة-حقيقةً","عن ماذا كانت إعادة كتابة المكتبة حقيقةً",[10,6335,6336,6337,6340,6341,6344],{},"كانت النسخة الأصلية معنيّة بالصحّة: اضطراب موضع المؤشّر، وأسباب التحقّق، ومعالجة RTL\u002FLTR, والحالات السبع المحدّدة التي على حقل الهاتف أن يميّز بينها. لم يختفِ أيّ من ذلك. كلّه لا يزال حاضراً في هذه النسخة: الدالّة المسنِدة ",[17,6338,6339],{},"dropLeadingZeros",", والقيم السبع لـ enum المسمّى ",[17,6342,6343],{},"PhoneValidationReason",", ونمط التنسيق ثنائي الاتجاه القائم على غلاف LTR فوق مُدخل RTL. تلك الأشياء لم تكن خاطئة، بل كانت الأساس الذي يُبنى عليه.",[10,6346,6347],{},"ما أضافته إعادة كتابة المكتبة كلّه يتعلّق بحقيقة أنّها لم تعد تخدم تطبيقاً واحداً. فصل بين ISO2 ورمز الاتصال يصمد أمام رموز الاتصال الملتبسة. سلسلة اكتشاف بيئية قابلة للاستبدال وصالحة للعمل دون اتصال. منتقٍ يعمل popover على الحاسوب وdrawer على الهاتف، ولكلّ حالة صنفها الخاصّ. أربعة عشر slot كي لا يشتبك نظام تصميم المستهلك مع المكوّن. جدول رموز اتصال متزامن لأنّ مستهلك المكتبة لا يستطيع انتظار الشبكة. آخر الاختيارات فاصلاً عند التعادل لأنّ \"الأبجدي الأوّل\" هو مظهر الكسل بعينه.",[10,6349,6350],{},"تضاعف عدد الأسطر تقريباً. النسخة الأصلية كانت 160 سطراً للمكوّن مع 540 سطراً للـ composable, بينما النسخة الجديدة عدّة مئات من الأسطر موزّعة على مكوّنَين واثنين من الـ composables. تضاعف السطح البرمجي أكثر من مرّة. ومع ذلك يظلّ النداء بلا تكوين قابلاً للاختصار في ثلاثة أسطر:",[39,6352,6354],{"className":41,"code":6353,"language":43,"meta":44,"style":44},"\u003CATellInput v-model:phone=\"phone\" v-model:country=\"country\" show-validation \u002F>\n",[17,6355,6356],{"__ignoreMap":44},[48,6357,6358,6360,6362,6364,6366,6368,6370,6372,6374,6376,6378,6380,6382,6384,6386,6388,6390,6392],{"class":50,"line":51},[48,6359,55],{"class":54},[48,6361,1865],{"class":58},[48,6363,4286],{"class":261},[48,6365,336],{"class":54},[48,6367,4186],{"class":261},[48,6369,66],{"class":54},[48,6371,70],{"class":69},[48,6373,4186],{"class":54},[48,6375,70],{"class":69},[48,6377,4286],{"class":261},[48,6379,336],{"class":54},[48,6381,1959],{"class":261},[48,6383,66],{"class":54},[48,6385,70],{"class":69},[48,6387,1959],{"class":54},[48,6389,70],{"class":69},[48,6391,5233],{"class":261},[48,6393,884],{"class":54},[10,6395,6396],{},"لكلّ prop جديد افتراضي يعكس ما كانت النسخة الداخلية تفعله تلقائياً. من يريد \"حقل هاتف فحسب\" يحصل عليه. ومن يريد \"حقل هاتف بأعلامنا، وقائمة دولنا، ومسند بحثنا، ورسائل أخطائنا\" يجد كلّ نقاط الفصل التي يحتاجها.",[10,6398,6399,6400,6403],{},"الدرس الذي سأحمله معي إلى المرّة القادمة: جعل مكوّن قابلاً للنشر يتعلّق أساساً برصد الآراء التي فرضتَها على نفسك، وتحويل كلّ واحد منها إلى نقطة فصل. كلّ ",[17,6401,6402],{},"restcountries.com"," مثبّت، وكلّ احتياطي \"السعودية ومصر\"، وكلّ قائمة منسدلة ظاهرة بلا انقطاع: تلك الأمور لم تكن خطأً بالنسبة للتطبيق، لكنّها الأمور التي لن تنجو مع المستهلك التالي. ابحث عنها، وسمِّها، واستبدل كلاً منها بافتراضي مع إمكانية تجاوز. الافتراضي يُبقي مستهلكك القديم راضياً، والتجاوز يفتح الباب للجديد.",[218,6405,6406],{},"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":44,"searchDepth":81,"depth":81,"links":6408},[6409,6410,6411,6412,6413,6414,6415,6416],{"id":1886,"depth":81,"text":1887},{"id":2285,"depth":81,"text":2286},{"id":3317,"depth":81,"text":3318},{"id":4257,"depth":81,"text":4258},{"id":4411,"depth":81,"text":4412},{"id":5493,"depth":81,"text":5494},{"id":6182,"depth":81,"text":6183},{"id":6332,"depth":81,"text":6333},"2026-05-21","تحويل حقل هاتف من داخل تطبيق واحد إلى مكوّن Vue مُنشَر استوجب تغييرات جوهرية. تجربة استخدام تكتشف الدولة قبل أيّ تدخّل، وفصل بين رمز ISO2 ورقم الاتصال، ومنتقي متجاوب عبر Popover وDrawer، وSlots لكل شيء.",[1839,1846,6420,6421,6422,6423,4271,6424,6425,1845],"حقل هاتف","libphonenumber-js","اكتشاف الدولة","Popover متجاوب","defineModel","Slots",{},"\u002Fwrites-ar\u002Fatell-input-the-library-rewrite",{"title":1857,"description":6418},"writes-ar\u002Fatell-input-the-library-rewrite","8wJPO0VSknxZ5ZTju80YmwbagcwC1H2lmP5kn104P24",{"id":6432,"title":6433,"body":6434,"date":10518,"description":10519,"description_ar":1835,"draft":1836,"extension":1837,"image":1835,"keywords":10520,"lang":1848,"meta":10527,"navigation":323,"path":10528,"seo":10529,"stem":10530,"title_ar":1835,"updatedAt":10518,"__hash__":10531},"writesAr\u002Fwrites-ar\u002Fanatomy-of-adatatable.md","تشريح ADataTable، جدول بيانات مُعمَّم بأنواع في Vue",{"type":7,"value":6435,"toc":10495},[6436,6443,6450,6453,6460,6463,6775,6785,6788,7066,7069,7081,7099,7114,7121,7134,7152,7158,7304,7336,7342,7423,7448,7469,7475,7484,7501,7701,7704,7749,7752,7757,7857,7863,7885,7892,7918,7925,7934,8139,8144,8288,8302,8319,8323,8334,8337,8404,8407,8670,8673,8724,8742,8749,8760,8848,8851,8950,8953,8975,8982,8989,9060,9073,9081,9088,9208,9214,9230,9244,9257,9273,9276,9336,9339,9343,9346,9382,9389,9392,9514,9517,9520,9771,9778,9781,9792,9809,9911,9923,9933,9938,10054,10057,10064,10149,10156,10160,10163,10169,10220,10226,10229,10300,10314,10320,10393,10406,10410,10413,10480,10492],[10,6437,6438,6439,6442],{},"كتبتُ جدول البيانات نفسه ثلاث مرات. مرةً على هيئة كتلة JSX، ومرةً على شكل كائن إعدادات مبني على class اسمه ",[17,6440,6441],{},"IColumn","، والمرة الثالثة، وهي التي بقيت، مكوّن Vue مُعمَّم ومكتوب بأنواع، مع نوع عمود على هيئة سكيمة. في تلك المرة الثالثة تحديدًا، توقّف الجدول عن أن يكون الشيء الذي أخاف من الاقتراب منه.",[10,6444,6445,6446,6449],{},"هذه التدوينة جولة في شكله الحالي. أضعه في حزمة الـ UI الخاصة بي باسم ",[17,6447,6448],{},"ADataTable","، والمثير فيه ليس حيلة بعينها، بل أن المكوّن كفّ عن التضخم. كل طلب ميزة على مدار السنة الأخيرة استوعبته التجريدات الموجودة أصلًا، دون أن أضيف ولو prop جديد. هذه هي صيغة \"الاكتمال\" التي كنتُ أطاردها.",[10,6451,6452],{},"سأمرّ على الملفات بترتيب قريب من ترتيب تدفّق الطلب داخلها، متوقفًا عند ما يستحق الشرح.",[25,6454,6456,6457,6459],{"id":6455},"من-كلاس-icolumn-إلى-interface-مكتوب-بأنواع","من كلاس ",[17,6458,6441],{}," إلى interface مكتوب بأنواع",[10,6461,6462],{},"قبل عامين كان لديّ class بهذا الشكل:",[39,6464,6468],{"className":6465,"code":6466,"language":6467,"meta":44,"style":44},"language-javascript shiki shiki-themes github-light github-dark","export class IColumn {\n  constructor(options) {\n    const { title, rowKey, type = 'default', sortable = false, \u002F* ... *\u002F } = options;\n    this.title = title;\n    this.rowKey = rowKey;\n    this.sorter = sorter || ((a, b) => a[this.rowKey] > b[this.rowKey]);\n    this.headerComponent = this.processComponent(headerComponent);\n    this.rowComponent = this.processComponent(rowComponent);\n    \u002F\u002F ...\n  }\n\n  processComponent(component) {\n    if (!component) return undefined;\n    return {\n      is: component.is || '',\n      props: typeof component.props === 'function' ? component.props : () => component.props,\n      events: typeof component.events === 'function' ? component.events : () => component.events,\n    };\n  }\n}\n","javascript",[17,6469,6470,6481,6492,6536,6549,6561,6609,6629,6647,6652,6656,6660,6672,6689,6695,6706,6735,6762,6767,6771],{"__ignoreMap":44},[48,6471,6472,6474,6476,6479],{"class":50,"line":51},[48,6473,3341],{"class":62},[48,6475,998],{"class":62},[48,6477,6478],{"class":261}," IColumn",[48,6480,1624],{"class":54},[48,6482,6483,6486,6488,6490],{"class":50,"line":81},[48,6484,6485],{"class":62},"  constructor",[48,6487,624],{"class":54},[48,6489,5415],{"class":600},[48,6491,2149],{"class":54},[48,6493,6494,6496,6498,6501,6503,6506,6508,6510,6512,6515,6517,6520,6522,6524,6526,6529,6531,6533],{"class":50,"line":87},[48,6495,2204],{"class":62},[48,6497,700],{"class":54},[48,6499,6500],{"class":332},"title",[48,6502,158],{"class":54},[48,6504,6505],{"class":332},"rowKey",[48,6507,158],{"class":54},[48,6509,306],{"class":332},[48,6511,342],{"class":62},[48,6513,6514],{"class":69}," 'default'",[48,6516,158],{"class":54},[48,6518,6519],{"class":332},"sortable",[48,6521,342],{"class":62},[48,6523,3299],{"class":332},[48,6525,158],{"class":54},[48,6527,6528],{"class":2472},"\u002F* ... *\u002F",[48,6530,711],{"class":54},[48,6532,66],{"class":62},[48,6534,6535],{"class":54}," options;\n",[48,6537,6538,6541,6544,6546],{"class":50,"line":93},[48,6539,6540],{"class":332},"    this",[48,6542,6543],{"class":54},".title ",[48,6545,66],{"class":62},[48,6547,6548],{"class":54}," title;\n",[48,6550,6551,6553,6556,6558],{"class":50,"line":362},[48,6552,6540],{"class":332},[48,6554,6555],{"class":54},".rowKey ",[48,6557,66],{"class":62},[48,6559,6560],{"class":54}," rowKey;\n",[48,6562,6563,6565,6568,6570,6573,6575,6578,6581,6583,6586,6588,6590,6593,6596,6599,6601,6604,6606],{"class":50,"line":373},[48,6564,6540],{"class":332},[48,6566,6567],{"class":54},".sorter ",[48,6569,66],{"class":62},[48,6571,6572],{"class":54}," sorter ",[48,6574,2693],{"class":62},[48,6576,6577],{"class":54}," ((",[48,6579,6580],{"class":600},"a",[48,6582,158],{"class":54},[48,6584,6585],{"class":600},"b",[48,6587,806],{"class":54},[48,6589,647],{"class":62},[48,6591,6592],{"class":54}," a[",[48,6594,6595],{"class":332},"this",[48,6597,6598],{"class":54},".rowKey] ",[48,6600,5443],{"class":62},[48,6602,6603],{"class":54}," b[",[48,6605,6595],{"class":332},[48,6607,6608],{"class":54},".rowKey]);\n",[48,6610,6611,6613,6616,6618,6621,6623,6626],{"class":50,"line":384},[48,6612,6540],{"class":332},[48,6614,6615],{"class":54},".headerComponent ",[48,6617,66],{"class":62},[48,6619,6620],{"class":332}," this",[48,6622,526],{"class":54},[48,6624,6625],{"class":261},"processComponent",[48,6627,6628],{"class":54},"(headerComponent);\n",[48,6630,6631,6633,6636,6638,6640,6642,6644],{"class":50,"line":661},[48,6632,6540],{"class":332},[48,6634,6635],{"class":54},".rowComponent ",[48,6637,66],{"class":62},[48,6639,6620],{"class":332},[48,6641,526],{"class":54},[48,6643,6625],{"class":261},[48,6645,6646],{"class":54},"(rowComponent);\n",[48,6648,6649],{"class":50,"line":690},[48,6650,6651],{"class":2472},"    \u002F\u002F ...\n",[48,6653,6654],{"class":50,"line":695},[48,6655,2589],{"class":54},[48,6657,6658],{"class":50,"line":722},[48,6659,324],{"emptyLinePlaceholder":323},[48,6661,6662,6665,6667,6670],{"class":50,"line":743},[48,6663,6664],{"class":261},"  processComponent",[48,6666,624],{"class":54},[48,6668,6669],{"class":600},"component",[48,6671,2149],{"class":54},[48,6673,6674,6676,6678,6680,6683,6685,6687],{"class":50,"line":767},[48,6675,2138],{"class":62},[48,6677,2183],{"class":54},[48,6679,2454],{"class":62},[48,6681,6682],{"class":54},"component) ",[48,6684,2197],{"class":62},[48,6686,5440],{"class":332},[48,6688,318],{"class":54},[48,6690,6691,6693],{"class":50,"line":778},[48,6692,2945],{"class":62},[48,6694,1624],{"class":54},[48,6696,6697,6700,6702,6704],{"class":50,"line":783},[48,6698,6699],{"class":54},"      is: component.is ",[48,6701,2693],{"class":62},[48,6703,2162],{"class":69},[48,6705,359],{"class":54},[48,6707,6708,6711,6714,6717,6719,6721,6724,6726,6728,6730,6732],{"class":50,"line":788},[48,6709,6710],{"class":54},"      props: ",[48,6712,6713],{"class":62},"typeof",[48,6715,6716],{"class":54}," component.props ",[48,6718,653],{"class":62},[48,6720,3818],{"class":69},[48,6722,6723],{"class":62}," ?",[48,6725,6716],{"class":54},[48,6727,336],{"class":62},[48,6729,4441],{"class":54},[48,6731,647],{"class":62},[48,6733,6734],{"class":54}," component.props,\n",[48,6736,6737,6740,6742,6745,6747,6749,6751,6753,6755,6757,6759],{"class":50,"line":819},[48,6738,6739],{"class":54},"      events: ",[48,6741,6713],{"class":62},[48,6743,6744],{"class":54}," component.events ",[48,6746,653],{"class":62},[48,6748,3818],{"class":69},[48,6750,6723],{"class":62},[48,6752,6744],{"class":54},[48,6754,336],{"class":62},[48,6756,4441],{"class":54},[48,6758,647],{"class":62},[48,6760,6761],{"class":54}," component.events,\n",[48,6763,6764],{"class":50,"line":828},[48,6765,6766],{"class":54},"    };\n",[48,6768,6769],{"class":50,"line":833},[48,6770,2589],{"class":54},[48,6772,6773],{"class":50,"line":843},[48,6774,1718],{"class":54},[10,6776,6777,6778,6781,6782,526],{},"مهمّة هذا الكلاس كانت توحيد شكل الـ props والـ events. لو مررتَ كائنًا ثابتًا، فإنه يُلفّه داخل ",[17,6779,6780],{},"() => object"," كي يستطيع الجدول استدعاءه دائمًا كدالة. الفكرة أنيقة على الورق، ومقبولة تمامًا في JavaScript وحده. المشكلة بدأت حين دخل TypeScript إلى الصورة: أنواع الصفوف المُعمَّمة لا تتدفق بنظافة عبر class يعمل وقت التشغيل، والمستهلك يفقد استنتاج الأنواع بالضبط عند اللحظة التي يريد فيها الوصول إلى ",[17,6783,6784],{},"row.user.name",[10,6786,6787],{},"الشكل الحالي مجرد interface بسيط ومكتوب بأنواع:",[39,6789,6791],{"className":1893,"code":6790,"language":1895,"meta":44,"style":44},"export interface IDataTableColumn\u003CT = unknown> {\n  title: string | IDataTableRowFunction\u003CT>;\n  key: string | IDataTableRowFunction\u003CT>;\n  props?: Record\u003Cstring, unknown> | IDataTableRowFunction\u003CT>;\n  type?: 'default' | 'actions' | 'selection';\n  disabled?: boolean | IDataTableRowFunction\u003CT>;\n\n  headRender?: () => Array\u003CIDataTableVNodeChild> | IDataTableVNodeChild;\n  bodyRender?: IDataTableRenderFunction\u003CT>;\n\n  sortable?: boolean;\n  sorter?: (row1: T, row2: T) => number;\n  sortValue?: ISortValue;\n  extras?: Record\u003Cstring, unknown> | IDataTableRowFunction\u003CT>;\n}\n",[17,6792,6793,6814,6835,6854,6884,6905,6924,6928,6956,6972,6976,6987,7021,7033,7062],{"__ignoreMap":44},[48,6794,6795,6797,6800,6803,6805,6808,6810,6812],{"class":50,"line":51},[48,6796,3341],{"class":62},[48,6798,6799],{"class":62}," interface",[48,6801,6802],{"class":261}," IDataTableColumn",[48,6804,55],{"class":54},[48,6806,6807],{"class":261},"T",[48,6809,342],{"class":62},[48,6811,4446],{"class":332},[48,6813,3377],{"class":54},[48,6815,6816,6819,6821,6823,6825,6828,6830,6832],{"class":50,"line":81},[48,6817,6818],{"class":600},"  title",[48,6820,336],{"class":62},[48,6822,606],{"class":332},[48,6824,678],{"class":62},[48,6826,6827],{"class":261}," IDataTableRowFunction",[48,6829,55],{"class":54},[48,6831,6807],{"class":261},[48,6833,6834],{"class":54},">;\n",[48,6836,6837,6840,6842,6844,6846,6848,6850,6852],{"class":50,"line":87},[48,6838,6839],{"class":600},"  key",[48,6841,336],{"class":62},[48,6843,606],{"class":332},[48,6845,678],{"class":62},[48,6847,6827],{"class":261},[48,6849,55],{"class":54},[48,6851,6807],{"class":261},[48,6853,6834],{"class":54},[48,6855,6856,6859,6861,6863,6865,6867,6869,6872,6874,6876,6878,6880,6882],{"class":50,"line":93},[48,6857,6858],{"class":600},"  props",[48,6860,3946],{"class":62},[48,6862,5455],{"class":261},[48,6864,55],{"class":54},[48,6866,1914],{"class":332},[48,6868,158],{"class":54},[48,6870,6871],{"class":332},"unknown",[48,6873,5553],{"class":54},[48,6875,5433],{"class":62},[48,6877,6827],{"class":261},[48,6879,55],{"class":54},[48,6881,6807],{"class":261},[48,6883,6834],{"class":54},[48,6885,6886,6889,6891,6893,6895,6898,6900,6903],{"class":50,"line":362},[48,6887,6888],{"class":600},"  type",[48,6890,3946],{"class":62},[48,6892,6514],{"class":69},[48,6894,678],{"class":62},[48,6896,6897],{"class":69}," 'actions'",[48,6899,678],{"class":62},[48,6901,6902],{"class":69}," 'selection'",[48,6904,318],{"class":54},[48,6906,6907,6910,6912,6914,6916,6918,6920,6922],{"class":50,"line":373},[48,6908,6909],{"class":600},"  disabled",[48,6911,3946],{"class":62},[48,6913,4678],{"class":332},[48,6915,678],{"class":62},[48,6917,6827],{"class":261},[48,6919,55],{"class":54},[48,6921,6807],{"class":261},[48,6923,6834],{"class":54},[48,6925,6926],{"class":50,"line":384},[48,6927,324],{"emptyLinePlaceholder":323},[48,6929,6930,6933,6935,6937,6939,6942,6944,6947,6949,6951,6954],{"class":50,"line":661},[48,6931,6932],{"class":261},"  headRender",[48,6934,3946],{"class":62},[48,6936,4441],{"class":54},[48,6938,647],{"class":62},[48,6940,6941],{"class":261}," Array",[48,6943,55],{"class":54},[48,6945,6946],{"class":261},"IDataTableVNodeChild",[48,6948,5553],{"class":54},[48,6950,5433],{"class":62},[48,6952,6953],{"class":261}," IDataTableVNodeChild",[48,6955,318],{"class":54},[48,6957,6958,6961,6963,6966,6968,6970],{"class":50,"line":690},[48,6959,6960],{"class":600},"  bodyRender",[48,6962,3946],{"class":62},[48,6964,6965],{"class":261}," IDataTableRenderFunction",[48,6967,55],{"class":54},[48,6969,6807],{"class":261},[48,6971,6834],{"class":54},[48,6973,6974],{"class":50,"line":695},[48,6975,324],{"emptyLinePlaceholder":323},[48,6977,6978,6981,6983,6985],{"class":50,"line":722},[48,6979,6980],{"class":600},"  sortable",[48,6982,3946],{"class":62},[48,6984,4678],{"class":332},[48,6986,318],{"class":54},[48,6988,6989,6992,6994,6996,6999,7001,7004,7006,7009,7011,7013,7015,7017,7019],{"class":50,"line":743},[48,6990,6991],{"class":261},"  sorter",[48,6993,3946],{"class":62},[48,6995,2183],{"class":54},[48,6997,6998],{"class":600},"row1",[48,7000,336],{"class":62},[48,7002,7003],{"class":261}," T",[48,7005,158],{"class":54},[48,7007,7008],{"class":600},"row2",[48,7010,336],{"class":62},[48,7012,7003],{"class":261},[48,7014,806],{"class":54},[48,7016,647],{"class":62},[48,7018,3775],{"class":332},[48,7020,318],{"class":54},[48,7022,7023,7026,7028,7031],{"class":50,"line":767},[48,7024,7025],{"class":600},"  sortValue",[48,7027,3946],{"class":62},[48,7029,7030],{"class":261}," ISortValue",[48,7032,318],{"class":54},[48,7034,7035,7038,7040,7042,7044,7046,7048,7050,7052,7054,7056,7058,7060],{"class":50,"line":778},[48,7036,7037],{"class":600},"  extras",[48,7039,3946],{"class":62},[48,7041,5455],{"class":261},[48,7043,55],{"class":54},[48,7045,1914],{"class":332},[48,7047,158],{"class":54},[48,7049,6871],{"class":332},[48,7051,5553],{"class":54},[48,7053,5433],{"class":62},[48,7055,6827],{"class":261},[48,7057,55],{"class":54},[48,7059,6807],{"class":261},[48,7061,6834],{"class":54},[48,7063,7064],{"class":50,"line":783},[48,7065,1718],{"class":54},[10,7067,7068],{},"تغيّرت ثلاثة أشياء، وكل واحد منها ترك أثرًا واضحًا.",[10,7070,7071,7072,7075,7076,7080],{},"أولًا، لا class. العمود صار كائنًا عاديًا. TypeScript يُضيّق نوعه، والمحرر يقدّم autocomplete جاهزًا، ويتسلسل نظيفًا لو أردتُ يومًا أن أُغذّي الجدول من JSON. أما مهمة التوحيد التي كان يتولّاها الكلاس وقت الإنشاء، فقد نقلتُها إلى دالة مساعدة صغيرة في مسار الـ render اسمها ",[17,7073,7074],{},"handleProps","، وسنمرّ عليها بعد قليل. الكلفة صارت فرعًا واحدًا لكل render لخلية بدل فرع واحد لكل ",[7077,7078,7079],"em",{},"تعريف"," عمود، والمكسب أن العمود يظل قيمة لا كائنًا حيًّا وقت التشغيل.",[10,7082,7083,7084,7086,7087,7090,7091,7094,7095,7098],{},"ثانيًا، صار العمود مُعمَّمًا على ",[17,7085,6807],{},". حين تكتب ",[17,7088,7089],{},"IDataTableColumn\u003CUser>","، فإن ",[17,7092,7093],{},"key"," يتحوّل إلى ",[17,7096,7097],{},"string | (row: User) => ...","، وهذا النوع يتدفّق إلى كل callback لاحق. في اليوم الذي أضفتُ فيه هذا التعميم، كشف ثلاثة مواضع استدعاء في العمل أخطاء في أنواع أعمدة كانت مكسورة بصمت.",[10,7100,7101,7102,7105,7106,7109,7110,7113],{},"ثالثًا، انقسمت مسؤوليات الـ render انقسامًا نظيفًا. ",[17,7103,7104],{},"headRender"," لخلية الرأس فقط، و",[17,7107,7108],{},"bodyRender"," لخلية الجسم. نسخة الكلاس كانت تجمع الاثنين في ",[17,7111,7112],{},"rowComponent"," واحد يتفرّع داخليًا، مما يعني أن حالة \"رأس مخصّص وجسم افتراضي\" كانت تتطلب توليفة غير موثّقة من الحقول الفارغة.",[10,7115,7116,7117,7120],{},"لا شيء من هذا فكرة جديدة. الانتقال من class يعمل وقت التشغيل إلى نوع على هيئة سكيمة هو قصة معظم مشاريع الـ UI في السنوات الخمس الأخيرة. أذكره فقط لأنني كتبتُ نسخة الكلاس ",[7077,7118,7119],{},"أولًا","، وواصلتُ إطلاقها لفترة طويلة بعد أن أصبحت نسخة الأنواع أرخص بكثير. تكلفة الانتقال لم تكن في إعادة الكتابة نفسها، بل في الاعتراف بأن التصميم الأول قد استُنفد.",[25,7122,7124,7125,393,7128,393,7131],{"id":7123},"ثلاثي-الـ-render-athead-وatbody-وatcell","ثلاثي الـ render: ",[17,7126,7127],{},"ATHead",[17,7129,7130],{},"ATBody",[17,7132,7133],{},"ATCell",[10,7135,7136,7137,393,7140,7143,7144,7147,7148,7151],{},"مكوّن الجدول لا يعرض الصفوف بشكل مباشر. هو يعرض ",[17,7138,7139],{},"\u003Cthead>",[17,7141,7142],{},"\u003Ctbody>","، وهذان بدورهما يعرضان عناصر ",[17,7145,7146],{},"\u003Ctr>"," التي تُركّب مكوّنات ",[17,7149,7150],{},"\u003CATCell>",". الفصل محكم بحيث يبقى لكل ملف عمل واحد فقط.",[10,7153,7154,7157],{},[17,7155,7156],{},"ATHead.vue"," هو الأبسط:",[39,7159,7161],{"className":41,"code":7160,"language":43,"meta":44,"style":44},"\u003Ctemplate>\n  \u003Cthead>\n    \u003Ctr>\n      \u003CATCell\n        v-for=\"(column, collIndex) in props.columns\"\n        :key=\"collIndex\"\n        :binder=\"column.props\"\n        :cell=\"column\"\n        :head=\"true\"\n        :record=\"{} as S\"\n        :row-index=\"collIndex\"\n        @update-sorter=\"emit('updateSorter', collIndex)\"\n      \u002F>\n    \u003C\u002Ftr>\n  \u003C\u002Fthead>\n\u003C\u002Ftemplate>\n",[17,7162,7163,7171,7179,7187,7195,7205,7215,7225,7235,7245,7255,7264,7274,7279,7288,7296],{"__ignoreMap":44},[48,7164,7165,7167,7169],{"class":50,"line":51},[48,7166,55],{"class":54},[48,7168,838],{"class":58},[48,7170,78],{"class":54},[48,7172,7173,7175,7177],{"class":50,"line":81},[48,7174,846],{"class":54},[48,7176,1518],{"class":58},[48,7178,78],{"class":54},[48,7180,7181,7183,7185],{"class":50,"line":87},[48,7182,863],{"class":54},[48,7184,1521],{"class":58},[48,7186,78],{"class":54},[48,7188,7189,7192],{"class":50,"line":93},[48,7190,7191],{"class":54},"      \u003C",[48,7193,7194],{"class":58},"ATCell\n",[48,7196,7197,7200,7202],{"class":50,"line":362},[48,7198,7199],{"class":261},"        v-for",[48,7201,66],{"class":54},[48,7203,7204],{"class":69},"\"(column, collIndex) in props.columns\"\n",[48,7206,7207,7210,7212],{"class":50,"line":373},[48,7208,7209],{"class":261},"        :key",[48,7211,66],{"class":54},[48,7213,7214],{"class":69},"\"collIndex\"\n",[48,7216,7217,7220,7222],{"class":50,"line":384},[48,7218,7219],{"class":261},"        :binder",[48,7221,66],{"class":54},[48,7223,7224],{"class":69},"\"column.props\"\n",[48,7226,7227,7230,7232],{"class":50,"line":661},[48,7228,7229],{"class":261},"        :cell",[48,7231,66],{"class":54},[48,7233,7234],{"class":69},"\"column\"\n",[48,7236,7237,7240,7242],{"class":50,"line":690},[48,7238,7239],{"class":261},"        :head",[48,7241,66],{"class":54},[48,7243,7244],{"class":69},"\"true\"\n",[48,7246,7247,7250,7252],{"class":50,"line":695},[48,7248,7249],{"class":261},"        :record",[48,7251,66],{"class":54},[48,7253,7254],{"class":69},"\"{} as S\"\n",[48,7256,7257,7260,7262],{"class":50,"line":722},[48,7258,7259],{"class":261},"        :row-index",[48,7261,66],{"class":54},[48,7263,7214],{"class":69},[48,7265,7266,7269,7271],{"class":50,"line":743},[48,7267,7268],{"class":261},"        @update-sorter",[48,7270,66],{"class":54},[48,7272,7273],{"class":69},"\"emit('updateSorter', collIndex)\"\n",[48,7275,7276],{"class":50,"line":767},[48,7277,7278],{"class":54},"      \u002F>\n",[48,7280,7281,7284,7286],{"class":50,"line":778},[48,7282,7283],{"class":54},"    \u003C\u002F",[48,7285,1521],{"class":58},[48,7287,78],{"class":54},[48,7289,7290,7292,7294],{"class":50,"line":783},[48,7291,929],{"class":54},[48,7293,1518],{"class":58},[48,7295,78],{"class":54},[48,7297,7298,7300,7302],{"class":50,"line":788},[48,7299,96],{"class":54},[48,7301,838],{"class":58},[48,7303,78],{"class":54},[10,7305,7306,7307,7309,7310,7312,7313,7316,7317,7320,7321,7324,7325,7327,7328,7331,7332,7335],{},"عنصر ",[17,7308,7146],{}," واحد، ومكوّن ",[17,7311,7150],{}," واحد لكل عمود مع ",[17,7314,7315],{},"head={true}",". أما الـ ",[17,7318,7319],{},"record"," فهو كائن فارغ اصطناعي مُحوَّل بالنوع إلى ",[17,7322,7323],{},"S",". الرأس لا يمتلك صفًا حقيقيًا، لكن ",[17,7326,7133],{}," مُعمَّم على السجل، فلا بدّ من شيء يشغل هذا الموضع. تحويل ",[17,7329,7330],{},"{} as S"," هو أقل الخيارات سوءًا. لو مرّرنا ",[17,7333,7334],{},"undefined"," فعلية، لأجبرنا كل خلية على فحص العدم عند كل render.",[10,7337,7338,7341],{},[17,7339,7340],{},"ATBody.vue"," يأخذ الشكل نفسه لكن بمستوى واحد أعلى:",[39,7343,7345],{"className":41,"code":7344,"language":43,"meta":44,"style":44},"\u003Ctbody>\n  \u003Ctr v-for=\"(dataItem, $index) in props.items\" :key=\"$index + '_dataItem'\">\n    \u003CATCell\n      v-for=\"(column, collIndex) in props.columns\"\n      :key=\"collIndex + '_dataItem_' + $index\"\n      :record=\"dataItem\"\n      :head=\"false\"\n      :row-index=\"$index\"\n      :cell=\"column\"\n      :binder=\"column.props\"\n    \u002F>\n  \u003C\u002Ftr>\n  \u003Cslot \u002F>\n\u003C\u002Ftbody>\n",[17,7346,7347,7355,7360,7365,7370,7375,7380,7385,7390,7395,7400,7405,7410,7415],{"__ignoreMap":44},[48,7348,7349,7351,7353],{"class":50,"line":51},[48,7350,55],{"class":54},[48,7352,1531],{"class":58},[48,7354,78],{"class":54},[48,7356,7357],{"class":50,"line":81},[48,7358,7359],{"class":54},"  \u003Ctr v-for=\"(dataItem, $index) in props.items\" :key=\"$index + '_dataItem'\">\n",[48,7361,7362],{"class":50,"line":87},[48,7363,7364],{"class":54},"    \u003CATCell\n",[48,7366,7367],{"class":50,"line":93},[48,7368,7369],{"class":54},"      v-for=\"(column, collIndex) in props.columns\"\n",[48,7371,7372],{"class":50,"line":362},[48,7373,7374],{"class":54},"      :key=\"collIndex + '_dataItem_' + $index\"\n",[48,7376,7377],{"class":50,"line":373},[48,7378,7379],{"class":54},"      :record=\"dataItem\"\n",[48,7381,7382],{"class":50,"line":384},[48,7383,7384],{"class":54},"      :head=\"false\"\n",[48,7386,7387],{"class":50,"line":661},[48,7388,7389],{"class":54},"      :row-index=\"$index\"\n",[48,7391,7392],{"class":50,"line":690},[48,7393,7394],{"class":54},"      :cell=\"column\"\n",[48,7396,7397],{"class":50,"line":695},[48,7398,7399],{"class":54},"      :binder=\"column.props\"\n",[48,7401,7402],{"class":50,"line":722},[48,7403,7404],{"class":54},"    \u002F>\n",[48,7406,7407],{"class":50,"line":743},[48,7408,7409],{"class":54},"  \u003C\u002Ftr>\n",[48,7411,7412],{"class":50,"line":767},[48,7413,7414],{"class":54},"  \u003Cslot \u002F>\n",[48,7416,7417,7419,7421],{"class":50,"line":778},[48,7418,96],{"class":54},[48,7420,1531],{"class":58},[48,7422,78],{"class":54},[10,7424,7425,7426,393,7429,7432,7433,7436,7437,7440,7441,7444,7445,7447],{},"المفاتيح المركّبة (",[17,7427,7428],{},"$index + '_dataItem'",[17,7430,7431],{},"collIndex + '_dataItem_' + $index",") ليست وسواسًا. مُطابِق Vue يهتم فقط بأن يكون المفتاح فريدًا ",[7077,7434,7435],{},"داخل قائمة الأب","، ولذلك ",[17,7438,7439],{},"$index"," وحده كافٍ. لكن حين أتصفح Vue DevTools وأحاول أن أعرف أيّ صف هو أيّ، فإن ظهور مفتاح بصيغة ",[17,7442,7443],{},"3_dataItem"," بدل مجرد ",[17,7446,2797],{}," يوفّر عليّ ثانية. هذا هو السبب الوحيد للاحقة.",[10,7449,7450,7451,7454,7455,7457,7458,7461,7462,7465,7466,7468],{},"الـ ",[17,7452,7453],{},"\u003Cslot \u002F>"," داخل ",[17,7456,7142],{}," هو الموضع الذي يحلّ فيه صفّ \"لا توجد بيانات\". المكوّن الخارجي يمرّر عبره ",[17,7459,7460],{},"\u003Ctr>\u003Ctd>...\u003C\u002Ftd>\u003C\u002Ftr>",". إبقاء حالة الفراغ صفًّا يمرّ عبر slot، بدل جعله عنصرًا منفصلًا، يعني أن حسابات ",[17,7463,7464],{},"colspan"," تعمل ضمن شبكة الـ ",[17,7467,7146],{}," نفسها، وأنماط انطباق الحدود لا تنكسر.",[10,7470,7471,7472,526],{},"العمل الحقيقي كلّه يجري داخل ",[17,7473,7474],{},"ATCell.vue",[25,7476,7478,7480,7481],{"id":7477},"atcell-مكوّن-واحد-بشكلَين-مبنيّ-عبر-h",[17,7479,7133],{},": مكوّن واحد بشكلَين، مبنيّ عبر ",[17,7482,7483],{},"h()",[10,7485,7486,7488,7489,7492,7493,7496,7497,7500],{},[17,7487,7133],{}," يعرض إما ",[17,7490,7491],{},"\u003Cth>"," وإما ",[17,7494,7495],{},"\u003Ctd>"," بحسب قيمة ",[17,7498,7499],{},"head",". بدل كتابة قالبَين، يبني الـ vnode ديناميكيًا:",[39,7502,7504],{"className":1893,"code":7503,"language":1895,"meta":44,"style":44},"const Cell = computed(() => {\n  return h(\n    props.head ? 'th' : 'td',\n    {\n      class: 'p-[16px] text-md gap-2 relative',\n      colspan: '1',\n      rowspan: '1',\n      ...handleProps\u003CT>(props.cell.props, props.record),\n      ...handleProps\u003CT>(props.binder, props.record),\n    },\n    h('div', { class: 'flex items-center gap-2 w-full' },\n      props.head\n        ? props.cell.sortable\n          ? [children(props.cell, props.record), sortButton(props.cell.sortValue?.applied)]\n          : children(props.cell, props.record)\n        : children(props.cell, props.record)\n    )\n  );\n});\n",[17,7505,7506,7523,7532,7549,7554,7564,7574,7583,7597,7610,7615,7634,7639,7647,7667,7678,7687,7692,7697],{"__ignoreMap":44},[48,7507,7508,7510,7513,7515,7517,7519,7521],{"class":50,"line":51},[48,7509,329],{"class":62},[48,7511,7512],{"class":332}," Cell",[48,7514,342],{"class":62},[48,7516,641],{"class":261},[48,7518,644],{"class":54},[48,7520,647],{"class":62},[48,7522,1624],{"class":54},[48,7524,7525,7527,7530],{"class":50,"line":81},[48,7526,2962],{"class":62},[48,7528,7529],{"class":261}," h",[48,7531,2114],{"class":54},[48,7533,7534,7537,7539,7542,7544,7547],{"class":50,"line":87},[48,7535,7536],{"class":54},"    props.head ",[48,7538,757],{"class":62},[48,7540,7541],{"class":69}," 'th'",[48,7543,258],{"class":62},[48,7545,7546],{"class":69}," 'td'",[48,7548,359],{"class":54},[48,7550,7551],{"class":50,"line":93},[48,7552,7553],{"class":54},"    {\n",[48,7555,7556,7559,7562],{"class":50,"line":362},[48,7557,7558],{"class":54},"      class: ",[48,7560,7561],{"class":69},"'p-[16px] text-md gap-2 relative'",[48,7563,359],{"class":54},[48,7565,7566,7569,7572],{"class":50,"line":373},[48,7567,7568],{"class":54},"      colspan: ",[48,7570,7571],{"class":69},"'1'",[48,7573,359],{"class":54},[48,7575,7576,7579,7581],{"class":50,"line":384},[48,7577,7578],{"class":54},"      rowspan: ",[48,7580,7571],{"class":69},[48,7582,359],{"class":54},[48,7584,7585,7588,7590,7592,7594],{"class":50,"line":661},[48,7586,7587],{"class":62},"      ...",[48,7589,7074],{"class":261},[48,7591,55],{"class":54},[48,7593,6807],{"class":261},[48,7595,7596],{"class":54},">(props.cell.props, props.record),\n",[48,7598,7599,7601,7603,7605,7607],{"class":50,"line":690},[48,7600,7587],{"class":62},[48,7602,7074],{"class":261},[48,7604,55],{"class":54},[48,7606,6807],{"class":261},[48,7608,7609],{"class":54},">(props.binder, props.record),\n",[48,7611,7612],{"class":50,"line":695},[48,7613,7614],{"class":54},"    },\n",[48,7616,7617,7620,7622,7625,7628,7631],{"class":50,"line":722},[48,7618,7619],{"class":261},"    h",[48,7621,624],{"class":54},[48,7623,7624],{"class":69},"'div'",[48,7626,7627],{"class":54},", { class: ",[48,7629,7630],{"class":69},"'flex items-center gap-2 w-full'",[48,7632,7633],{"class":54}," },\n",[48,7635,7636],{"class":50,"line":743},[48,7637,7638],{"class":54},"      props.head\n",[48,7640,7641,7644],{"class":50,"line":767},[48,7642,7643],{"class":62},"        ?",[48,7645,7646],{"class":54}," props.cell.sortable\n",[48,7648,7649,7652,7655,7658,7661,7664],{"class":50,"line":778},[48,7650,7651],{"class":62},"          ?",[48,7653,7654],{"class":54}," [",[48,7656,7657],{"class":261},"children",[48,7659,7660],{"class":54},"(props.cell, props.record), ",[48,7662,7663],{"class":261},"sortButton",[48,7665,7666],{"class":54},"(props.cell.sortValue?.applied)]\n",[48,7668,7669,7672,7675],{"class":50,"line":783},[48,7670,7671],{"class":62},"          :",[48,7673,7674],{"class":261}," children",[48,7676,7677],{"class":54},"(props.cell, props.record)\n",[48,7679,7680,7683,7685],{"class":50,"line":788},[48,7681,7682],{"class":62},"        :",[48,7684,7674],{"class":261},[48,7686,7677],{"class":54},[48,7688,7689],{"class":50,"line":819},[48,7690,7691],{"class":54},"    )\n",[48,7693,7694],{"class":50,"line":828},[48,7695,7696],{"class":54},"  );\n",[48,7698,7699],{"class":50,"line":833},[48,7700,387],{"class":54},[10,7702,7703],{},"ثم يكتفي القالب بتركيب الـ vnode المحسوب:",[39,7705,7707],{"className":41,"code":7706,"language":43,"meta":44,"style":44},"\u003Ctemplate>\n  \u003Ccomponent :is=\"Cell\" :key=\"props.cell.sortValue?.applied || 'ATCell'\" \u002F>\n\u003C\u002Ftemplate>\n",[17,7708,7709,7717,7741],{"__ignoreMap":44},[48,7710,7711,7713,7715],{"class":50,"line":51},[48,7712,55],{"class":54},[48,7714,838],{"class":58},[48,7716,78],{"class":54},[48,7718,7719,7721,7723,7726,7728,7731,7734,7736,7739],{"class":50,"line":81},[48,7720,846],{"class":54},[48,7722,6669],{"class":58},[48,7724,7725],{"class":261}," :is",[48,7727,66],{"class":54},[48,7729,7730],{"class":69},"\"Cell\"",[48,7732,7733],{"class":261}," :key",[48,7735,66],{"class":54},[48,7737,7738],{"class":69},"\"props.cell.sortValue?.applied || 'ATCell'\"",[48,7740,884],{"class":54},[48,7742,7743,7745,7747],{"class":50,"line":87},[48,7744,96],{"class":54},[48,7746,838],{"class":58},[48,7748,78],{"class":54},[10,7750,7751],{},"هنا شيئان يعملان بهدوء.",[10,7753,7754,7756],{},[17,7755,7074],{}," يقبل إما كائنًا عاديًا وإما دالة تأخذ الصف، ويُعيد كائنًا دائمًا:",[39,7758,7760],{"className":1893,"code":7759,"language":1895,"meta":44,"style":44},"const handleProps = \u003CT,>(val: IDataTableColumn\u003CT>['props'], record: T) => {\n  if (!val) return {};\n  return typeof val === 'function' ? val(record) : val;\n};\n",[17,7761,7762,7811,7827,7853],{"__ignoreMap":44},[48,7763,7764,7766,7769,7771,7774,7776,7779,7782,7784,7786,7788,7790,7793,7796,7799,7801,7803,7805,7807,7809],{"class":50,"line":51},[48,7765,329],{"class":62},[48,7767,7768],{"class":261}," handleProps",[48,7770,342],{"class":62},[48,7772,7773],{"class":54}," \u003C",[48,7775,6807],{"class":261},[48,7777,7778],{"class":54},",>(",[48,7780,7781],{"class":600},"val",[48,7783,336],{"class":62},[48,7785,6802],{"class":261},[48,7787,55],{"class":54},[48,7789,6807],{"class":261},[48,7791,7792],{"class":54},">[",[48,7794,7795],{"class":69},"'props'",[48,7797,7798],{"class":54},"], ",[48,7800,7319],{"class":600},[48,7802,336],{"class":62},[48,7804,7003],{"class":261},[48,7806,806],{"class":54},[48,7808,647],{"class":62},[48,7810,1624],{"class":54},[48,7812,7813,7815,7817,7819,7822,7824],{"class":50,"line":81},[48,7814,2449],{"class":62},[48,7816,2183],{"class":54},[48,7818,2454],{"class":62},[48,7820,7821],{"class":54},"val) ",[48,7823,2197],{"class":62},[48,7825,7826],{"class":54}," {};\n",[48,7828,7829,7831,7833,7836,7838,7840,7842,7845,7848,7850],{"class":50,"line":87},[48,7830,2962],{"class":62},[48,7832,3809],{"class":62},[48,7834,7835],{"class":54}," val ",[48,7837,653],{"class":62},[48,7839,3818],{"class":69},[48,7841,6723],{"class":62},[48,7843,7844],{"class":261}," val",[48,7846,7847],{"class":54},"(record) ",[48,7849,336],{"class":62},[48,7851,7852],{"class":54}," val;\n",[48,7854,7855],{"class":50,"line":93},[48,7856,5759],{"class":54},[10,7858,7859,7860,7862],{},"هذا هو نفس عقد \"قابل للاستدعاء دائمًا\" الذي كان كلاس ",[17,7861,6441],{}," القديم يفرضه وقت الإنشاء، لكنه انتقل الآن إلى مسار الـ render، حيث يكون أرخص في الحالة الشائعة (لا تُغلَّف الـ props الثابتة بلا داعٍ)، ويترك للمستهلك حرية الاختيار.",[10,7864,7865,7866,7869,7870,7873,7874,7877,7878,7881,7882,7884],{},"ترتيب الـ spread مقصود. ",[17,7867,7868],{},"props.cell.props"," يُنشر أولًا، ثم ",[17,7871,7872],{},"props.binder"," بعده. في الواقع، ",[17,7875,7876],{},"binder"," هو نفسه ",[17,7879,7880],{},"cell.props","، ولا يُمرَّر عبر prop منفصل إلا لأسباب تاريخية سأدمجها لو أعدتُ الكتابة. لكن حاليًا يفوز ",[17,7883,7876],{}," عند التعارض، وهذا هو ما يتوقعه المستدعون.",[1012,7886,7450,7888,7891],{"id":7887},"الـ-keysortvalueapplied-الذي-يبدو-خطأً-مطبعيًا",[17,7889,7890],{},":key=\"sortValue?.applied\""," الذي يبدو خطأً مطبعيًا",[10,7893,7894,7895,7898,7899,7902,7903,7906,7907,7909,7910,7913,7914,7917],{},"الشيء الثاني هو المفتاح الغريب ",[17,7896,7897],{},":key=\"props.cell.sortValue?.applied || 'ATCell'\"",". الـ ",[17,7900,7901],{},"\u003Ccomponent :is>"," يتفاعل أصلًا مع تغيّر الـ vnode، لكن الـ ",[17,7904,7905],{},":key"," هنا موجود لإجبار إعادة تركيب كاملة حين تنقلب حالة الفرز. وهذا بالضبط دوره. آلية التحديث الجزئي في Vue ذكية بما يكفي لعمل diff بين خرجَي ",[17,7908,7483],{},"، ولكن حين تأتي props الخلية من دالة على السجل ",[7077,7911,7912],{},"ويتغيّر"," السجل في اللحظة نفسها التي ينقلب فيها الفرز، فقد يتشبّث ",[17,7915,7916],{},"patcher"," أحيانًا بسمة قديمة. ربط المفتاح بحالة الفرز يفرض unmount ثم remount نظيفَين عند الانتقال الوحيد الذي قد يهمّ فيه ذلك. سطر واحد فقط. حذفه لم يعضّني حتى الآن، وهذا يعني إما أنه احتياط مضاعف بلا داعٍ، وإما أنه يمنع خطأً بهدوء. لستُ متأكدًا، وقد كففتُ عن محاولة الحسم في أي من الاحتمالين. الكلفة render واحد لكل تبديل فرز.",[25,7919,7921,7924],{"id":7920},"handlevaluebasedonkey-مسارات-بالنقاط-داخل-الصفوف",[17,7922,7923],{},"handleValueBasedOnKey",": مسارات بالنقاط داخل الصفوف",[10,7926,7927,7928,7930,7931,7933],{},"حين لا يوفّر العمود لا ",[17,7929,7104],{}," ولا ",[17,7932,7108],{},"، تعود الخلية إلى renderer افتراضي:",[39,7935,7937],{"className":1893,"code":7936,"language":1895,"meta":44,"style":44},"const children = (cell: IDataTableColumn\u003CT>, record: T) => {\n  const defaultHeadCell = h('span', {\n    class: 'text-[16px] text-secondary font-medium capitalize',\n    innerHTML: typeof cell.title === 'function' ? cell.title(record) : cell.title,\n  });\n  const defaultBodyCell = h('span', {\n    class: 'text-[16px] text-foreground capitalize',\n    innerHTML: handleValueBasedOnKey(cell.key as string, record),\n  });\n  if (props.head) {\n    return cell.headRender ? cell.headRender() : defaultHeadCell;\n  }\n  return cell.bodyRender ? cell.bodyRender(record, props.rowIndex) : defaultBodyCell;\n};\n",[17,7938,7939,7975,7994,8004,8032,8037,8054,8063,8079,8083,8090,8110,8114,8135],{"__ignoreMap":44},[48,7940,7941,7943,7945,7947,7949,7952,7954,7956,7958,7960,7963,7965,7967,7969,7971,7973],{"class":50,"line":51},[48,7942,329],{"class":62},[48,7944,7674],{"class":261},[48,7946,342],{"class":62},[48,7948,2183],{"class":54},[48,7950,7951],{"class":600},"cell",[48,7953,336],{"class":62},[48,7955,6802],{"class":261},[48,7957,55],{"class":54},[48,7959,6807],{"class":261},[48,7961,7962],{"class":54},">, ",[48,7964,7319],{"class":600},[48,7966,336],{"class":62},[48,7968,7003],{"class":261},[48,7970,806],{"class":54},[48,7972,647],{"class":62},[48,7974,1624],{"class":54},[48,7976,7977,7979,7982,7984,7986,7988,7991],{"class":50,"line":81},[48,7978,2603],{"class":62},[48,7980,7981],{"class":332}," defaultHeadCell",[48,7983,342],{"class":62},[48,7985,7529],{"class":261},[48,7987,624],{"class":54},[48,7989,7990],{"class":69},"'span'",[48,7992,7993],{"class":54},", {\n",[48,7995,7996,7999,8002],{"class":50,"line":87},[48,7997,7998],{"class":54},"    class: ",[48,8000,8001],{"class":69},"'text-[16px] text-secondary font-medium capitalize'",[48,8003,359],{"class":54},[48,8005,8006,8009,8011,8014,8016,8018,8020,8023,8025,8027,8029],{"class":50,"line":93},[48,8007,8008],{"class":54},"    innerHTML: ",[48,8010,6713],{"class":62},[48,8012,8013],{"class":54}," cell.title ",[48,8015,653],{"class":62},[48,8017,3818],{"class":69},[48,8019,6723],{"class":62},[48,8021,8022],{"class":54}," cell.",[48,8024,6500],{"class":261},[48,8026,7847],{"class":54},[48,8028,336],{"class":62},[48,8030,8031],{"class":54}," cell.title,\n",[48,8033,8034],{"class":50,"line":362},[48,8035,8036],{"class":54},"  });\n",[48,8038,8039,8041,8044,8046,8048,8050,8052],{"class":50,"line":373},[48,8040,2603],{"class":62},[48,8042,8043],{"class":332}," defaultBodyCell",[48,8045,342],{"class":62},[48,8047,7529],{"class":261},[48,8049,624],{"class":54},[48,8051,7990],{"class":69},[48,8053,7993],{"class":54},[48,8055,8056,8058,8061],{"class":50,"line":384},[48,8057,7998],{"class":54},[48,8059,8060],{"class":69},"'text-[16px] text-foreground capitalize'",[48,8062,359],{"class":54},[48,8064,8065,8067,8069,8072,8074,8076],{"class":50,"line":661},[48,8066,8008],{"class":54},[48,8068,7923],{"class":261},[48,8070,8071],{"class":54},"(cell.key ",[48,8073,2657],{"class":62},[48,8075,606],{"class":332},[48,8077,8078],{"class":54},", record),\n",[48,8080,8081],{"class":50,"line":690},[48,8082,8036],{"class":54},[48,8084,8085,8087],{"class":50,"line":695},[48,8086,2449],{"class":62},[48,8088,8089],{"class":54}," (props.head) {\n",[48,8091,8092,8094,8097,8099,8101,8103,8105,8107],{"class":50,"line":722},[48,8093,2945],{"class":62},[48,8095,8096],{"class":54}," cell.headRender ",[48,8098,757],{"class":62},[48,8100,8022],{"class":54},[48,8102,7104],{"class":261},[48,8104,3615],{"class":54},[48,8106,336],{"class":62},[48,8108,8109],{"class":54}," defaultHeadCell;\n",[48,8111,8112],{"class":50,"line":743},[48,8113,2589],{"class":54},[48,8115,8116,8118,8121,8123,8125,8127,8130,8132],{"class":50,"line":767},[48,8117,2962],{"class":62},[48,8119,8120],{"class":54}," cell.bodyRender ",[48,8122,757],{"class":62},[48,8124,8022],{"class":54},[48,8126,7108],{"class":261},[48,8128,8129],{"class":54},"(record, props.rowIndex) ",[48,8131,336],{"class":62},[48,8133,8134],{"class":54}," defaultBodyCell;\n",[48,8136,8137],{"class":50,"line":778},[48,8138,5759],{"class":54},[10,8140,8141,8142,336],{},"الجزء الذي يستحق التكبير هو ",[17,8143,7923],{},[39,8145,8147],{"className":1893,"code":8146,"language":1895,"meta":44,"style":44},"const handleValueBasedOnKey = (key: string, receivedData: T): unknown => {\n  return key.split('.').reduce((acc: unknown, curr) => {\n    if (acc && typeof acc === 'object') {\n      return (acc as Record\u003Cstring, unknown>)[curr];\n    }\n    return undefined;\n  }, receivedData);\n};\n",[17,8148,8149,8185,8225,8246,8267,8271,8279,8284],{"__ignoreMap":44},[48,8150,8151,8153,8156,8158,8160,8162,8164,8166,8168,8171,8173,8175,8177,8179,8181,8183],{"class":50,"line":51},[48,8152,329],{"class":62},[48,8154,8155],{"class":261}," handleValueBasedOnKey",[48,8157,342],{"class":62},[48,8159,2183],{"class":54},[48,8161,7093],{"class":600},[48,8163,336],{"class":62},[48,8165,606],{"class":332},[48,8167,158],{"class":54},[48,8169,8170],{"class":600},"receivedData",[48,8172,336],{"class":62},[48,8174,7003],{"class":261},[48,8176,2433],{"class":54},[48,8178,336],{"class":62},[48,8180,4446],{"class":332},[48,8182,6288],{"class":62},[48,8184,1624],{"class":54},[48,8186,8187,8189,8192,8195,8197,8200,8202,8205,8207,8210,8212,8214,8216,8219,8221,8223],{"class":50,"line":81},[48,8188,2962],{"class":62},[48,8190,8191],{"class":54}," key.",[48,8193,8194],{"class":261},"split",[48,8196,624],{"class":54},[48,8198,8199],{"class":69},"'.'",[48,8201,1250],{"class":54},[48,8203,8204],{"class":261},"reduce",[48,8206,800],{"class":54},[48,8208,8209],{"class":600},"acc",[48,8211,336],{"class":62},[48,8213,4446],{"class":332},[48,8215,158],{"class":54},[48,8217,8218],{"class":600},"curr",[48,8220,806],{"class":54},[48,8222,647],{"class":62},[48,8224,1624],{"class":54},[48,8226,8227,8229,8232,8234,8236,8239,8241,8244],{"class":50,"line":87},[48,8228,2138],{"class":62},[48,8230,8231],{"class":54}," (acc ",[48,8233,2513],{"class":62},[48,8235,3809],{"class":62},[48,8237,8238],{"class":54}," acc ",[48,8240,653],{"class":62},[48,8242,8243],{"class":69}," 'object'",[48,8245,2149],{"class":54},[48,8247,8248,8250,8252,8254,8256,8258,8260,8262,8264],{"class":50,"line":93},[48,8249,2169],{"class":62},[48,8251,8231],{"class":54},[48,8253,2657],{"class":62},[48,8255,5455],{"class":261},[48,8257,55],{"class":54},[48,8259,1914],{"class":332},[48,8261,158],{"class":54},[48,8263,6871],{"class":332},[48,8265,8266],{"class":54},">)[curr];\n",[48,8268,8269],{"class":50,"line":362},[48,8270,2176],{"class":54},[48,8272,8273,8275,8277],{"class":50,"line":373},[48,8274,2945],{"class":62},[48,8276,5440],{"class":332},[48,8278,318],{"class":54},[48,8280,8281],{"class":50,"line":384},[48,8282,8283],{"class":54},"  }, receivedData);\n",[48,8285,8286],{"class":50,"line":661},[48,8287,5759],{"class":54},[10,8289,8290,8291,8294,8295,8297,8298,8301],{},"عمود بمفتاح ",[17,8292,8293],{},"key: 'user.profile.displayName'"," يسير على النقاط ويسحب القيمة المتشعّبة. هذه الميزة وحدها هي التي تُغني المستهلك عن كتابة دالة ",[17,8296,7108],{}," في 80% من الحالات. معظم الجداول لديها ثلاثة أو أربعة أعمدة كل واحد منها ليس أكثر من ",[17,8299,8300],{},"row.foo.bar","، ومنحُها مسارًا بالنقاط يجعل نهج \"السكيمة كبيانات\" يكسب جولة أخرى.",[10,8303,8304,8305,8308,8309,8312,8313,8315,8316,8318],{},"أستخدم ",[17,8306,8307],{},"innerHTML"," في الـ renderer الافتراضي، وهذا مقصود من أجل العناوين التي تتضمن ",[17,8310,8311],{},"\u003Cbr>"," أو قليلًا من التنسيق، وهو في الوقت نفسه فخّ متوقّع مع أي بيانات يتحكم بها المستخدم. الاتفاق الذي استقرّ عليه فريقي في مراجعات الكود هو: استخدم الـ renderer الافتراضي مع العناوين الثابتة والبيانات المُنقّاة، وأما ما يأتي من المستخدم فاكتب له ",[17,8314,7108],{}," يمرّر البيانات عبر مكوّن فرعي. لم أجد طريقة أنظف للتعبير عن هذه القاعدة داخل نظام الأنواع، ولستُ متأكدًا أنني أريد أصلًا أن أُجبر الجميع على ",[17,8317,7108],{}," لكل خلية.",[25,8320,8322],{"id":8321},"مفاتيح-الصفوف-البديل-حين-لا-يمرّر-أحد-مفتاحًا","مفاتيح الصفوف: البديل حين لا يمرّر أحد مفتاحًا",[10,8324,7450,8325,8327,8328,8330,8331,8333],{},[17,8326,7905],{}," الافتراضي في Vue على الصفوف هو ",[17,8329,7439],{},"، وهو إجابة خاطئة تمامًا لأي شيء يتغير ترتيبه. لتفعل أفضل من ذلك، تحتاج معرّفًا مستقرًّا لكل صف، وبما أن الجدول مُعمَّم على ",[17,8332,6807],{},"، فلا يوجد حقل يمكنك الاعتماد عليه في كل الحالات.",[10,8335,8336],{},"الـ interface واضح:",[39,8338,8340],{"className":1893,"code":8339,"language":1895,"meta":44,"style":44},"type DataTablePropsWithKey\u003CT> = IDataTableProps\u003CT> & {\n  \u002F** Optional stable key extractor; recommended *\u002F\n  rowKey?: (row: T) => string;\n};\n",[17,8341,8342,8371,8376,8400],{"__ignoreMap":44},[48,8343,8344,8346,8349,8351,8353,8355,8357,8360,8362,8364,8366,8369],{"class":50,"line":51},[48,8345,306],{"class":62},[48,8347,8348],{"class":261}," DataTablePropsWithKey",[48,8350,55],{"class":54},[48,8352,6807],{"class":261},[48,8354,5553],{"class":54},[48,8356,66],{"class":62},[48,8358,8359],{"class":261}," IDataTableProps",[48,8361,55],{"class":54},[48,8363,6807],{"class":261},[48,8365,5553],{"class":54},[48,8367,8368],{"class":62},"&",[48,8370,1624],{"class":54},[48,8372,8373],{"class":50,"line":81},[48,8374,8375],{"class":2472},"  \u002F** Optional stable key extractor; recommended *\u002F\n",[48,8377,8378,8381,8383,8385,8388,8390,8392,8394,8396,8398],{"class":50,"line":87},[48,8379,8380],{"class":261},"  rowKey",[48,8382,3946],{"class":62},[48,8384,2183],{"class":54},[48,8386,8387],{"class":600},"row",[48,8389,336],{"class":62},[48,8391,7003],{"class":261},[48,8393,806],{"class":54},[48,8395,647],{"class":62},[48,8397,606],{"class":332},[48,8399,318],{"class":54},[48,8401,8402],{"class":50,"line":93},[48,8403,5759],{"class":54},[10,8405,8406],{},"لكن عمليًا، لا أحد يمرّره. لذلك كان لا بدّ أن يعمل البديل مع صفوف اعتباطية:",[39,8408,8410],{"className":1893,"code":8409,"language":1895,"meta":44,"style":44},"function stableKeyFromObject(obj: Record\u003Cstring, unknown>): string {\n  if ('id' in obj && typeof obj.id !== 'undefined') {\n    return String(obj.id as unknown);\n  }\n  const keys = Object.keys(obj).sort();\n  return keys\n    .map((k) => `${k}:${String(obj[k])}`)\n    .join('|');\n}\n\nfunction getRowKey(row: T): string {\n  if (typeof globalProps.rowKey === 'function') return globalProps.rowKey(row);\n  return stableKeyFromObject(row as unknown as Record\u003Cstring, unknown>);\n}\n",[17,8411,8412,8445,8474,8489,8493,8515,8522,8565,8579,8583,8587,8610,8637,8666],{"__ignoreMap":44},[48,8413,8414,8416,8419,8421,8424,8426,8428,8430,8432,8434,8436,8439,8441,8443],{"class":50,"line":51},[48,8415,2418],{"class":62},[48,8417,8418],{"class":261}," stableKeyFromObject",[48,8420,624],{"class":54},[48,8422,8423],{"class":600},"obj",[48,8425,336],{"class":62},[48,8427,5455],{"class":261},[48,8429,55],{"class":54},[48,8431,1914],{"class":332},[48,8433,158],{"class":54},[48,8435,6871],{"class":332},[48,8437,8438],{"class":54},">)",[48,8440,336],{"class":62},[48,8442,606],{"class":332},[48,8444,1624],{"class":54},[48,8446,8447,8449,8451,8454,8457,8460,8462,8464,8467,8469,8472],{"class":50,"line":81},[48,8448,2449],{"class":62},[48,8450,2183],{"class":54},[48,8452,8453],{"class":69},"'id'",[48,8455,8456],{"class":62}," in",[48,8458,8459],{"class":54}," obj ",[48,8461,2513],{"class":62},[48,8463,3809],{"class":62},[48,8465,8466],{"class":54}," obj.id ",[48,8468,3815],{"class":62},[48,8470,8471],{"class":69}," 'undefined'",[48,8473,2149],{"class":54},[48,8475,8476,8478,8480,8483,8485,8487],{"class":50,"line":87},[48,8477,2945],{"class":62},[48,8479,5814],{"class":261},[48,8481,8482],{"class":54},"(obj.id ",[48,8484,2657],{"class":62},[48,8486,4446],{"class":332},[48,8488,629],{"class":54},[48,8490,8491],{"class":50,"line":93},[48,8492,2589],{"class":54},[48,8494,8495,8497,8500,8502,8504,8507,8510,8513],{"class":50,"line":362},[48,8496,2603],{"class":62},[48,8498,8499],{"class":332}," keys",[48,8501,342],{"class":62},[48,8503,6075],{"class":54},[48,8505,8506],{"class":261},"keys",[48,8508,8509],{"class":54},"(obj).",[48,8511,8512],{"class":261},"sort",[48,8514,3477],{"class":54},[48,8516,8517,8519],{"class":50,"line":373},[48,8518,2962],{"class":62},[48,8520,8521],{"class":54}," keys\n",[48,8523,8524,8527,8529,8531,8534,8536,8538,8541,8543,8546,8548,8550,8552,8555,8557,8560,8562],{"class":50,"line":384},[48,8525,8526],{"class":54},"    .",[48,8528,6233],{"class":261},[48,8530,800],{"class":54},[48,8532,8533],{"class":600},"k",[48,8535,806],{"class":54},[48,8537,647],{"class":62},[48,8539,8540],{"class":69}," `${",[48,8542,8533],{"class":54},[48,8544,8545],{"class":69},"}:${",[48,8547,2217],{"class":261},[48,8549,624],{"class":69},[48,8551,8423],{"class":54},[48,8553,8554],{"class":69},"[",[48,8556,8533],{"class":54},[48,8558,8559],{"class":69},"])",[48,8561,738],{"class":69},[48,8563,8564],{"class":54},")\n",[48,8566,8567,8569,8572,8574,8577],{"class":50,"line":661},[48,8568,8526],{"class":54},[48,8570,8571],{"class":261},"join",[48,8573,624],{"class":54},[48,8575,8576],{"class":69},"'|'",[48,8578,629],{"class":54},[48,8580,8581],{"class":50,"line":690},[48,8582,1718],{"class":54},[48,8584,8585],{"class":50,"line":695},[48,8586,324],{"emptyLinePlaceholder":323},[48,8588,8589,8591,8594,8596,8598,8600,8602,8604,8606,8608],{"class":50,"line":722},[48,8590,2418],{"class":62},[48,8592,8593],{"class":261}," getRowKey",[48,8595,624],{"class":54},[48,8597,8387],{"class":600},[48,8599,336],{"class":62},[48,8601,7003],{"class":261},[48,8603,2433],{"class":54},[48,8605,336],{"class":62},[48,8607,606],{"class":332},[48,8609,1624],{"class":54},[48,8611,8612,8614,8616,8618,8621,8623,8625,8627,8629,8632,8634],{"class":50,"line":743},[48,8613,2449],{"class":62},[48,8615,2183],{"class":54},[48,8617,6713],{"class":62},[48,8619,8620],{"class":54}," globalProps.rowKey ",[48,8622,653],{"class":62},[48,8624,3818],{"class":69},[48,8626,806],{"class":54},[48,8628,2197],{"class":62},[48,8630,8631],{"class":54}," globalProps.",[48,8633,6505],{"class":261},[48,8635,8636],{"class":54},"(row);\n",[48,8638,8639,8641,8643,8646,8648,8650,8653,8655,8657,8659,8661,8663],{"class":50,"line":767},[48,8640,2962],{"class":62},[48,8642,8418],{"class":261},[48,8644,8645],{"class":54},"(row ",[48,8647,2657],{"class":62},[48,8649,4446],{"class":332},[48,8651,8652],{"class":62}," as",[48,8654,5455],{"class":261},[48,8656,55],{"class":54},[48,8658,1914],{"class":332},[48,8660,158],{"class":54},[48,8662,6871],{"class":332},[48,8664,8665],{"class":54},">);\n",[48,8667,8668],{"class":50,"line":778},[48,8669,1718],{"class":54},[10,8671,8672],{},"ثلاثة تفاصيل صغيرة، وكل واحد منها يستحق وزنه:",[8674,8675,8676,8693,8707],"ul",{},[3673,8677,8678,8685,8686,8688,8689,8692],{},[35,8679,8680,8681,8684],{},"اختصار ",[17,8682,8683],{},"id"," أولًا."," أغلب سجلات الـ backend فيها حقل ",[17,8687,8683],{},". ",[17,8690,8691],{},"String(obj.id)"," يتعامل مع الأرقام والسلاسل و BigInt دون تفرّع. التحويل بالنوع كذبة في نظام الأنواع وصدق وقت التشغيل.",[3673,8694,8695,8698,8699,8702,8703,8706],{},[35,8696,8697],{},"رتّب المفاتيح قبل اشتقاق البصمة."," ",[17,8700,8701],{},"Object.keys()"," يعيد ترتيب الإدخال. في أغلب الكائنات يكون هذا الترتيب هو نفسه في كل مرة، لكن \"أغلب\" ليست \"كل\". كائن أعيد بناؤه من ",[17,8704,8705],{},"JSON.parse"," لن يتفق بالضرورة مع كائن كُتب مباشرة كـ object literal بالمفاتيح نفسها. الترتيب يقضي على هذا الصنف من الأخطاء دفعة واحدة.",[3673,8708,8709,8698,8718,8720,8721,8723],{},[35,8710,8711,8714,8715,526],{},[17,8712,8713],{},"String()"," بدل ",[17,8716,8717],{},"JSON.stringify",[17,8719,8713],{}," لا يرمي استثناءً أبدًا، بينما ",[17,8722,8717],{}," يرمي على المراجع الدائرية (وهي تحدث حين تكون مُستهترًا مع Pinia). الهدف هنا مفتاح ثابت، لا مُسلسِل كامل.",[10,8725,8726,8727,8730,8731,8734,8735,8737,8738,8741],{},"هذا المفتاح يُستخدم في مطابقة Vue ",[7077,8728,8729],{},"و"," في التحديد معًا. وهذه هي الثابتة التي تهمّ. لو استخدم الاثنان مفهومَي هوية مختلفَين (",[17,8732,8733],{},":key=\"i\""," وتحديد يستخدم ",[17,8736,653],{},")، فقد تُسقط عملية فرز واحدة مربعات الاختيار مع بقاء البيانات نفسها. استخدام ",[17,8739,8740],{},"getRowKey"," في الموضعَين يضمن أن الاثنين لا يفترقان.",[25,8743,8745,8746],{"id":8744},"التحديد-على-شكل-mapstring-t","التحديد على شكل ",[17,8747,8748],{},"Map\u003Cstring, T>",[10,8750,8751,8752,8755,8756,8759],{},"لا توجد حالة تحديد منفصلة. المكوّن يحتفظ فقط بـ ",[17,8753,8754],{},"localSelectedItems"," (وهي ",[17,8757,8758],{},"shallowRef\u003CT[]>",")، ويشتق منها Map مفهرسة بالمفتاح:",[39,8761,8763],{"className":1893,"code":8762,"language":1895,"meta":44,"style":44},"const hashedSelectedItems = computed(() => {\n  const map = new Map\u003Cstring, T>();\n  localSelectedItems.value.forEach((x) => map.set(getRowKey(x), x));\n  return map;\n});\n",[17,8764,8765,8782,8807,8837,8844],{"__ignoreMap":44},[48,8766,8767,8769,8772,8774,8776,8778,8780],{"class":50,"line":51},[48,8768,329],{"class":62},[48,8770,8771],{"class":332}," hashedSelectedItems",[48,8773,342],{"class":62},[48,8775,641],{"class":261},[48,8777,644],{"class":54},[48,8779,647],{"class":62},[48,8781,1624],{"class":54},[48,8783,8784,8786,8789,8791,8793,8796,8798,8800,8802,8804],{"class":50,"line":81},[48,8785,2603],{"class":62},[48,8787,8788],{"class":332}," map",[48,8790,342],{"class":62},[48,8792,3838],{"class":62},[48,8794,8795],{"class":261}," Map",[48,8797,55],{"class":54},[48,8799,1914],{"class":332},[48,8801,158],{"class":54},[48,8803,6807],{"class":261},[48,8805,8806],{"class":54},">();\n",[48,8808,8809,8812,8815,8817,8820,8822,8824,8827,8830,8832,8834],{"class":50,"line":87},[48,8810,8811],{"class":54},"  localSelectedItems.value.",[48,8813,8814],{"class":261},"forEach",[48,8816,800],{"class":54},[48,8818,8819],{"class":600},"x",[48,8821,806],{"class":54},[48,8823,647],{"class":62},[48,8825,8826],{"class":54}," map.",[48,8828,8829],{"class":261},"set",[48,8831,624],{"class":54},[48,8833,8740],{"class":261},[48,8835,8836],{"class":54},"(x), x));\n",[48,8838,8839,8841],{"class":50,"line":93},[48,8840,2962],{"class":62},[48,8842,8843],{"class":54}," map;\n",[48,8845,8846],{"class":50,"line":362},[48,8847,387],{"class":54},[10,8849,8850],{},"مربع اختيار رأس عمود التحديد يفحص \"هل كل العناصر محدَّدة\" بمسح واحد للمصفوفة:",[39,8852,8854],{"className":1893,"code":8853,"language":1895,"meta":44,"style":44},"const isAllDataSelected = computed(() => {\n  const atData = localItems.value.filter((item) =>\n    hashedSelectedItems.value.has(getRowKey(item))\n  );\n  return atData.length === localItems.value.length && localItems.value.length > 0;\n});\n",[17,8855,8856,8873,8897,8912,8916,8946],{"__ignoreMap":44},[48,8857,8858,8860,8863,8865,8867,8869,8871],{"class":50,"line":51},[48,8859,329],{"class":62},[48,8861,8862],{"class":332}," isAllDataSelected",[48,8864,342],{"class":62},[48,8866,641],{"class":261},[48,8868,644],{"class":54},[48,8870,647],{"class":62},[48,8872,1624],{"class":54},[48,8874,8875,8877,8880,8882,8885,8888,8890,8892,8894],{"class":50,"line":81},[48,8876,2603],{"class":62},[48,8878,8879],{"class":332}," atData",[48,8881,342],{"class":62},[48,8883,8884],{"class":54}," localItems.value.",[48,8886,8887],{"class":261},"filter",[48,8889,800],{"class":54},[48,8891,5026],{"class":600},[48,8893,806],{"class":54},[48,8895,8896],{"class":62},"=>\n",[48,8898,8899,8902,8905,8907,8909],{"class":50,"line":87},[48,8900,8901],{"class":54},"    hashedSelectedItems.value.",[48,8903,8904],{"class":261},"has",[48,8906,624],{"class":54},[48,8908,8740],{"class":261},[48,8910,8911],{"class":54},"(item))\n",[48,8913,8914],{"class":50,"line":93},[48,8915,7696],{"class":54},[48,8917,8918,8920,8923,8925,8927,8929,8931,8934,8936,8938,8941,8944],{"class":50,"line":362},[48,8919,2962],{"class":62},[48,8921,8922],{"class":54}," atData.",[48,8924,2626],{"class":332},[48,8926,2913],{"class":62},[48,8928,8884],{"class":54},[48,8930,2626],{"class":332},[48,8932,8933],{"class":62}," &&",[48,8935,8884],{"class":54},[48,8937,2626],{"class":332},[48,8939,8940],{"class":62}," >",[48,8942,8943],{"class":332}," 0",[48,8945,318],{"class":54},[48,8947,8948],{"class":50,"line":373},[48,8949,387],{"class":54},[10,8951,8952],{},"هذا O(n) على العناصر المرئية، وهي أصلًا O(n) بالنسبة إلى الـ render نفسه. لا شيء يمكن فعله لتحسين هذا. أما الفحص لكل صف على حدة فهو O(1):",[39,8954,8956],{"className":1893,"code":8955,"language":1895,"meta":44,"style":44},"modelValue: hashedSelectedItems.value.has(getRowKey(rowData)),\n",[17,8957,8958],{"__ignoreMap":44},[48,8959,8960,8963,8966,8968,8970,8972],{"class":50,"line":51},[48,8961,8962],{"class":261},"modelValue",[48,8964,8965],{"class":54},": hashedSelectedItems.value.",[48,8967,8904],{"class":261},[48,8969,624],{"class":54},[48,8971,8740],{"class":261},[48,8973,8974],{"class":54},"(rowData)),\n",[10,8976,8977,8978,8981],{},"بدون هذه الـ Map المحسوبة، سيقوم كل مربع اختيار بـ ",[17,8979,8980],{},"localSelectedItems.value.find(...)"," عند كل render. جدول من 100 صف مع 100 عنصر محدَّد كان سينفّذ 10,000 مقارنة في كل إعادة render. الـ Map تحوّل ذلك إلى 100 بحث بزمن ثابت. لا تلاحظ الفرق قبل أن تُفجّر جدولًا ببيانات حقيقية، وعند تلك اللحظة تحفظ النمط وتتوقف عن كتابة النسخة الساذجة.",[10,8983,8984,8985,8988],{},"عمود التحديد نفسه يُبنى عند الطلب. المستهلك إما يصرّح به عمودًا (",[17,8986,8987],{},"type: 'selection'",") وإما يتركه:",[39,8990,8992],{"className":1893,"code":8991,"language":1895,"meta":44,"style":44},"if (hasSelectColumn) {\n  const selectionColumn = handleSelectionColumn();\n  _columns = _columns.filter((col) => col.type !== 'selection');\n  _columns.unshift(selectionColumn);\n}\n",[17,8993,8994,9001,9015,9045,9056],{"__ignoreMap":44},[48,8995,8996,8998],{"class":50,"line":51},[48,8997,3249],{"class":62},[48,8999,9000],{"class":54}," (hasSelectColumn) {\n",[48,9002,9003,9005,9008,9010,9013],{"class":50,"line":81},[48,9004,2603],{"class":62},[48,9006,9007],{"class":332}," selectionColumn",[48,9009,342],{"class":62},[48,9011,9012],{"class":261}," handleSelectionColumn",[48,9014,3477],{"class":54},[48,9016,9017,9020,9022,9025,9027,9029,9032,9034,9036,9039,9041,9043],{"class":50,"line":87},[48,9018,9019],{"class":54},"  _columns ",[48,9021,66],{"class":62},[48,9023,9024],{"class":54}," _columns.",[48,9026,8887],{"class":261},[48,9028,800],{"class":54},[48,9030,9031],{"class":600},"col",[48,9033,806],{"class":54},[48,9035,647],{"class":62},[48,9037,9038],{"class":54}," col.type ",[48,9040,3815],{"class":62},[48,9042,6902],{"class":69},[48,9044,629],{"class":54},[48,9046,9047,9050,9053],{"class":50,"line":93},[48,9048,9049],{"class":54},"  _columns.",[48,9051,9052],{"class":261},"unshift",[48,9054,9055],{"class":54},"(selectionColumn);\n",[48,9057,9058],{"class":50,"line":362},[48,9059,1718],{"class":54},[10,9061,9062,9063,9065,9066,9069,9070,526],{},"عمود ",[17,9064,8987],{}," القادم من المستهلك، إن وُجد، يُستبدَل بواحد يبنيه المكوّن داخليًا ويُثبَّت في الموضع 0. نسخة المستهلك مجرد ",[7077,9067,9068],{},"علامة"," تعني \"أريد تحديدًا في هذا الجدول\"، والمكوّن نفسه يتولى كتابة الـ renderer الفعلي. بهذا يحصل المستهلك على واجهة \"التحديد عمود\" دون أن يُطلب منه أن يعرف كيف يُبنى مربع الاختيار أو كيف تُوصَّل حالة ",[17,9071,9072],{},"indeterminate",[25,9074,9076,9077,9080],{"id":9075},"الإجراءات-وmarkraw-الذي-أثبت-جدواه","الإجراءات، و",[17,9078,9079],{},"markRaw"," الذي أثبت جدواه",[10,9082,9083,9084,9087],{},"الإجراءات تعمل بالطريقة نفسها تمامًا. المستهلك يصرّح بعمود إجراءات (أو يُلحقه المكوّن تلقائيًا إذا كان ",[17,9085,9086],{},"actions.length > 0",")، والـ renderer يُبنى داخليًا:",[39,9089,9091],{"className":1893,"code":9090,"language":1895,"meta":44,"style":44},"const actionsComponent: IDataTableRenderFunction\u003CT> = (rowData, index) =>\n  h(markRaw(AActionGroup), {\n    actions: localActions.value as IAction\u003Cunknown>[],\n    record: rowData,\n    popover: globalProps.popover,\n    maxMainCount: getVisibleLength(rowData),\n    onUpdateAction: (action) =>\n      emit('updateAction', { action, row: rowData, rowIndex: index }),\n  });\n",[17,9092,9093,9126,9138,9155,9160,9165,9176,9191,9204],{"__ignoreMap":44},[48,9094,9095,9097,9100,9102,9104,9106,9108,9110,9112,9114,9117,9119,9122,9124],{"class":50,"line":51},[48,9096,329],{"class":62},[48,9098,9099],{"class":261}," actionsComponent",[48,9101,336],{"class":62},[48,9103,6965],{"class":261},[48,9105,55],{"class":54},[48,9107,6807],{"class":261},[48,9109,5553],{"class":54},[48,9111,66],{"class":62},[48,9113,2183],{"class":54},[48,9115,9116],{"class":600},"rowData",[48,9118,158],{"class":54},[48,9120,9121],{"class":600},"index",[48,9123,806],{"class":54},[48,9125,8896],{"class":62},[48,9127,9128,9131,9133,9135],{"class":50,"line":81},[48,9129,9130],{"class":261},"  h",[48,9132,624],{"class":54},[48,9134,9079],{"class":261},[48,9136,9137],{"class":54},"(AActionGroup), {\n",[48,9139,9140,9143,9145,9148,9150,9152],{"class":50,"line":87},[48,9141,9142],{"class":54},"    actions: localActions.value ",[48,9144,2657],{"class":62},[48,9146,9147],{"class":261}," IAction",[48,9149,55],{"class":54},[48,9151,6871],{"class":332},[48,9153,9154],{"class":54},">[],\n",[48,9156,9157],{"class":50,"line":93},[48,9158,9159],{"class":54},"    record: rowData,\n",[48,9161,9162],{"class":50,"line":362},[48,9163,9164],{"class":54},"    popover: globalProps.popover,\n",[48,9166,9167,9170,9173],{"class":50,"line":373},[48,9168,9169],{"class":54},"    maxMainCount: ",[48,9171,9172],{"class":261},"getVisibleLength",[48,9174,9175],{"class":54},"(rowData),\n",[48,9177,9178,9181,9184,9187,9189],{"class":50,"line":384},[48,9179,9180],{"class":261},"    onUpdateAction",[48,9182,9183],{"class":54},": (",[48,9185,9186],{"class":600},"action",[48,9188,806],{"class":54},[48,9190,8896],{"class":62},[48,9192,9193,9196,9198,9201],{"class":50,"line":661},[48,9194,9195],{"class":261},"      emit",[48,9197,624],{"class":54},[48,9199,9200],{"class":69},"'updateAction'",[48,9202,9203],{"class":54},", { action, row: rowData, rowIndex: index }),\n",[48,9205,9206],{"class":50,"line":690},[48,9207,8036],{"class":54},[10,9209,9210,9211,526],{},"السطر الذي أريد أن أتوقف عنده هو ",[17,9212,9213],{},"markRaw(AActionGroup)",[10,9215,9216,9217,9219,9220,9222,9223,3728,9226,9229],{},"حين تمرّر تعريف مكوّن إلى ",[17,9218,7483],{},"، فإن Vue يلمسه أثناء الـ render. وبدون ",[17,9221,9079],{},"، لو مرّ هذا التعريف يومًا عبر نظام التفاعلية في Vue، سواء عبر ",[17,9224,9225],{},"ref",[17,9227,9228],{},"reactive"," أو حتى prop، فإنه يُلفّ في Proxy. تعريفات المكوّنات ليست مصمَّمة أصلًا لتكون تفاعلية، هي بيانات وصفية ثابتة. تغليفها لا يكسر شيئًا، لكنه يجعل كل قراءة لخاصية تمرّ عبر handler يقوم بفحص تتبّع تفاعلية لا يُثمر أبدًا.",[10,9231,9232,9233,9236,9237,9239,9240,9243],{},"دالة الـ render أعلاه تعمل مرة لكل صف. في جدول من 500 صف، يلمس أول render تعريف ",[17,9234,9235],{},"AActionGroup"," خمسمئة مرة. ",[17,9238,9079],{}," يضع علامة ",[17,9241,9242],{},"__v_skip"," على الكائن مرة واحدة، وكل render لاحق يتخطى مسار الـ Proxy تمامًا.",[10,9245,9246,9247,9249,9250,9253,9254,9256],{},"كلفة ",[17,9248,9079],{}," دائمة، لا يمكن التراجع عنها. وهذا مقبول تمامًا مع تعريف مكوّن، فهو import ثابت في نهاية المطاف. لو أردتَ تبديل مكوّن الإجراءات وقت التشغيل، فالتبديل يحصل في طبقة مختلفة، أي في اختيار ",[7077,9251,9252],{},"أي"," مكوّن تمرّره إلى ",[17,9255,9079],{},"، لا بأن تجعل المرجع نفسه تفاعليًا.",[10,9258,9259,9260,9262,9263,9265,9266,9269,9270,9272],{},"هناك أيضًا تنبيه في وضع التطوير نصّه \"Vue received a Component that was made a reactive object\"، يظهر حين يتسلل مكوّن تفاعلي إلى ",[17,9261,7483],{},". اصطدمتُ به في جدول من 1200 صف كان يمرَّر فيه ",[17,9264,9235],{}," عبر ",[17,9267,9268],{},"shallowRef"," لأغراض الـ theming. إضافة ",[17,9271,9079],{}," أسكتت التنبيه وخفّضت زمن أول رسم بشكل ملحوظ.",[10,9274,9275],{},"عدد الإجراءات الظاهرة يمكن أن يكون رقمًا أو دالة على الصف:",[39,9277,9279],{"className":1893,"code":9278,"language":1895,"meta":44,"style":44},"const getVisibleLength = (rowData: T) =>\n  typeof globalProps.visibleActionLength === 'function'\n    ? globalProps.visibleActionLength(rowData)\n    : globalProps.visibleActionLength;\n",[17,9280,9281,9302,9315,9328],{"__ignoreMap":44},[48,9282,9283,9285,9288,9290,9292,9294,9296,9298,9300],{"class":50,"line":51},[48,9284,329],{"class":62},[48,9286,9287],{"class":261}," getVisibleLength",[48,9289,342],{"class":62},[48,9291,2183],{"class":54},[48,9293,9116],{"class":600},[48,9295,336],{"class":62},[48,9297,7003],{"class":261},[48,9299,806],{"class":54},[48,9301,8896],{"class":62},[48,9303,9304,9307,9310,9312],{"class":50,"line":81},[48,9305,9306],{"class":62},"  typeof",[48,9308,9309],{"class":54}," globalProps.visibleActionLength ",[48,9311,653],{"class":62},[48,9313,9314],{"class":69}," 'function'\n",[48,9316,9317,9320,9322,9325],{"class":50,"line":87},[48,9318,9319],{"class":62},"    ?",[48,9321,8631],{"class":54},[48,9323,9324],{"class":261},"visibleActionLength",[48,9326,9327],{"class":54},"(rowData)\n",[48,9329,9330,9333],{"class":50,"line":93},[48,9331,9332],{"class":62},"    :",[48,9334,9335],{"class":54}," globalProps.visibleActionLength;\n",[10,9337,9338],{},"وهذا هو السبيل الوحيد لتنفيذ \"اعرض 3 إجراءات للـ admin وإجراءً واحدًا للمستخدم العادي\" دون أن نبني عمود إجراءات ثانيًا.",[25,9340,9342],{"id":9341},"الفرز-دورة-من-ثلاث-نقرات-محلية-أو-عن-بُعد","الفرز: دورة من ثلاث نقرات، محلية أو عن بُعد",[10,9344,9345],{},"حالة الفرز كائن ذو قيمتَين منطقيتَين:",[39,9347,9349],{"className":1893,"code":9348,"language":1895,"meta":44,"style":44},"export type ISortValue = { applied: boolean; revert: boolean };\n",[17,9350,9351],{"__ignoreMap":44},[48,9352,9353,9355,9358,9360,9362,9364,9367,9369,9371,9373,9376,9378,9380],{"class":50,"line":51},[48,9354,3341],{"class":62},[48,9356,9357],{"class":62}," type",[48,9359,7030],{"class":261},[48,9361,342],{"class":62},[48,9363,700],{"class":54},[48,9365,9366],{"class":600},"applied",[48,9368,336],{"class":62},[48,9370,4678],{"class":332},[48,9372,3951],{"class":54},[48,9374,9375],{"class":600},"revert",[48,9377,336],{"class":62},[48,9379,4678],{"class":332},[48,9381,3960],{"class":54},[10,9383,9384,9385,9388],{},"وليست ",[17,9386,9387],{},"'asc' | 'desc' | null",". جرّبتُ النصوص أولًا، لكنها كانت تفقد باستمرار حالة \"لم يُطبَّق\"، لأن القيم الزائفة من النوع الخاطئ لا تتعايش بلطف مع TypeScript الصارم.",[10,9390,9391],{},"التبديل يمرّ بثلاث حالات:",[39,9393,9395],{"className":1893,"code":9394,"language":1895,"meta":44,"style":44},"function handleToggleSortButton(sorterValue: ISortValue): ISortValue {\n  if (!sorterValue.applied) {\n    sorterValue.applied = true;\n  } else if (!sorterValue.revert) {\n    sorterValue.revert = true;\n  } else {\n    sorterValue.applied = false;\n    sorterValue.revert = false;\n  }\n  return sorterValue;\n}\n",[17,9396,9397,9421,9432,9443,9460,9471,9479,9489,9499,9503,9510],{"__ignoreMap":44},[48,9398,9399,9401,9404,9406,9409,9411,9413,9415,9417,9419],{"class":50,"line":51},[48,9400,2418],{"class":62},[48,9402,9403],{"class":261}," handleToggleSortButton",[48,9405,624],{"class":54},[48,9407,9408],{"class":600},"sorterValue",[48,9410,336],{"class":62},[48,9412,7030],{"class":261},[48,9414,2433],{"class":54},[48,9416,336],{"class":62},[48,9418,7030],{"class":261},[48,9420,1624],{"class":54},[48,9422,9423,9425,9427,9429],{"class":50,"line":81},[48,9424,2449],{"class":62},[48,9426,2183],{"class":54},[48,9428,2454],{"class":62},[48,9430,9431],{"class":54},"sorterValue.applied) {\n",[48,9433,9434,9437,9439,9441],{"class":50,"line":87},[48,9435,9436],{"class":54},"    sorterValue.applied ",[48,9438,66],{"class":62},[48,9440,3174],{"class":332},[48,9442,318],{"class":54},[48,9444,9445,9447,9450,9453,9455,9457],{"class":50,"line":93},[48,9446,2574],{"class":54},[48,9448,9449],{"class":62},"else",[48,9451,9452],{"class":62}," if",[48,9454,2183],{"class":54},[48,9456,2454],{"class":62},[48,9458,9459],{"class":54},"sorterValue.revert) {\n",[48,9461,9462,9465,9467,9469],{"class":50,"line":362},[48,9463,9464],{"class":54},"    sorterValue.revert ",[48,9466,66],{"class":62},[48,9468,3174],{"class":332},[48,9470,318],{"class":54},[48,9472,9473,9475,9477],{"class":50,"line":373},[48,9474,2574],{"class":54},[48,9476,9449],{"class":62},[48,9478,1624],{"class":54},[48,9480,9481,9483,9485,9487],{"class":50,"line":384},[48,9482,9436],{"class":54},[48,9484,66],{"class":62},[48,9486,3299],{"class":332},[48,9488,318],{"class":54},[48,9490,9491,9493,9495,9497],{"class":50,"line":661},[48,9492,9464],{"class":54},[48,9494,66],{"class":62},[48,9496,3299],{"class":332},[48,9498,318],{"class":54},[48,9500,9501],{"class":50,"line":690},[48,9502,2589],{"class":54},[48,9504,9505,9507],{"class":50,"line":695},[48,9506,2962],{"class":62},[48,9508,9509],{"class":54}," sorterValue;\n",[48,9511,9512],{"class":50,"line":722},[48,9513,1718],{"class":54},[10,9515,9516],{},"نقرة أولى: تصاعدي. نقرة ثانية: تنازلي. نقرة ثالثة: عودة إلى الترتيب الأصلي. المستخدمون يتوقّعون هذا في كل جدول تعاملوا معه، ويُفاجَؤون دائمًا حين لا يجدون نقرة \"إلغاء الفرز\".",[10,9518,9519],{},"الفرز الفعلي له مساران: عن بُعد ومحلي.",[39,9521,9523],{"className":1893,"code":9522,"language":1895,"meta":44,"style":44},"function sortCell(columnIndex: number) {\n  const column = localColumns.value[columnIndex]!;\n  const sorterFunction = column.sorter;\n  const sorterValue = handleToggleSortButton(column.sortValue!);\n\n  if (globalProps.remote) {\n    emit('updateSorter', { column, sorterValue, rowIndex: columnIndex });\n    return;\n  }\n\n  if (!sorterValue.applied && !sorterValue.revert) {\n    localItems.value = _.cloneDeep(globalProps.items);\n    emit('updateSorter', { column, sorterValue, rowIndex: columnIndex });\n    return;\n  }\n\n  localItems.value = [...localItems.value].sort((a, b) => {\n    const result = sorterFunction!(a, b);\n    return sorterValue.revert ? -result : result;\n  });\n  emit('updateSorter', { column, sorterValue, rowIndex: columnIndex });\n}\n",[17,9524,9525,9543,9559,9571,9589,9593,9600,9613,9619,9623,9627,9645,9661,9671,9677,9681,9685,9716,9732,9752,9756,9767],{"__ignoreMap":44},[48,9526,9527,9529,9532,9534,9537,9539,9541],{"class":50,"line":51},[48,9528,2418],{"class":62},[48,9530,9531],{"class":261}," sortCell",[48,9533,624],{"class":54},[48,9535,9536],{"class":600},"columnIndex",[48,9538,336],{"class":62},[48,9540,3775],{"class":332},[48,9542,2149],{"class":54},[48,9544,9545,9547,9550,9552,9555,9557],{"class":50,"line":81},[48,9546,2603],{"class":62},[48,9548,9549],{"class":332}," column",[48,9551,342],{"class":62},[48,9553,9554],{"class":54}," localColumns.value[columnIndex]",[48,9556,2454],{"class":62},[48,9558,318],{"class":54},[48,9560,9561,9563,9566,9568],{"class":50,"line":87},[48,9562,2603],{"class":62},[48,9564,9565],{"class":332}," sorterFunction",[48,9567,342],{"class":62},[48,9569,9570],{"class":54}," column.sorter;\n",[48,9572,9573,9575,9578,9580,9582,9585,9587],{"class":50,"line":93},[48,9574,2603],{"class":62},[48,9576,9577],{"class":332}," sorterValue",[48,9579,342],{"class":62},[48,9581,9403],{"class":261},[48,9583,9584],{"class":54},"(column.sortValue",[48,9586,2454],{"class":62},[48,9588,629],{"class":54},[48,9590,9591],{"class":50,"line":362},[48,9592,324],{"emptyLinePlaceholder":323},[48,9594,9595,9597],{"class":50,"line":373},[48,9596,2449],{"class":62},[48,9598,9599],{"class":54}," (globalProps.remote) {\n",[48,9601,9602,9605,9607,9610],{"class":50,"line":384},[48,9603,9604],{"class":261},"    emit",[48,9606,624],{"class":54},[48,9608,9609],{"class":69},"'updateSorter'",[48,9611,9612],{"class":54},", { column, sorterValue, rowIndex: columnIndex });\n",[48,9614,9615,9617],{"class":50,"line":661},[48,9616,2945],{"class":62},[48,9618,318],{"class":54},[48,9620,9621],{"class":50,"line":690},[48,9622,2589],{"class":54},[48,9624,9625],{"class":50,"line":695},[48,9626,324],{"emptyLinePlaceholder":323},[48,9628,9629,9631,9633,9635,9638,9640,9643],{"class":50,"line":722},[48,9630,2449],{"class":62},[48,9632,2183],{"class":54},[48,9634,2454],{"class":62},[48,9636,9637],{"class":54},"sorterValue.applied ",[48,9639,2513],{"class":62},[48,9641,9642],{"class":62}," !",[48,9644,9459],{"class":54},[48,9646,9647,9650,9652,9655,9658],{"class":50,"line":743},[48,9648,9649],{"class":54},"    localItems.value ",[48,9651,66],{"class":62},[48,9653,9654],{"class":54}," _.",[48,9656,9657],{"class":261},"cloneDeep",[48,9659,9660],{"class":54},"(globalProps.items);\n",[48,9662,9663,9665,9667,9669],{"class":50,"line":767},[48,9664,9604],{"class":261},[48,9666,624],{"class":54},[48,9668,9609],{"class":69},[48,9670,9612],{"class":54},[48,9672,9673,9675],{"class":50,"line":778},[48,9674,2945],{"class":62},[48,9676,318],{"class":54},[48,9678,9679],{"class":50,"line":783},[48,9680,2589],{"class":54},[48,9682,9683],{"class":50,"line":788},[48,9684,324],{"emptyLinePlaceholder":323},[48,9686,9687,9690,9692,9694,9697,9700,9702,9704,9706,9708,9710,9712,9714],{"class":50,"line":819},[48,9688,9689],{"class":54},"  localItems.value ",[48,9691,66],{"class":62},[48,9693,7654],{"class":54},[48,9695,9696],{"class":62},"...",[48,9698,9699],{"class":54},"localItems.value].",[48,9701,8512],{"class":261},[48,9703,800],{"class":54},[48,9705,6580],{"class":600},[48,9707,158],{"class":54},[48,9709,6585],{"class":600},[48,9711,806],{"class":54},[48,9713,647],{"class":62},[48,9715,1624],{"class":54},[48,9717,9718,9720,9723,9725,9727,9729],{"class":50,"line":828},[48,9719,2204],{"class":62},[48,9721,9722],{"class":332}," result",[48,9724,342],{"class":62},[48,9726,9565],{"class":261},[48,9728,2454],{"class":62},[48,9730,9731],{"class":54},"(a, b);\n",[48,9733,9734,9736,9739,9741,9744,9747,9749],{"class":50,"line":833},[48,9735,2945],{"class":62},[48,9737,9738],{"class":54}," sorterValue.revert ",[48,9740,757],{"class":62},[48,9742,9743],{"class":62}," -",[48,9745,9746],{"class":54},"result ",[48,9748,336],{"class":62},[48,9750,9751],{"class":54}," result;\n",[48,9753,9754],{"class":50,"line":843},[48,9755,8036],{"class":54},[48,9757,9758,9761,9763,9765],{"class":50,"line":860},[48,9759,9760],{"class":261},"  emit",[48,9762,624],{"class":54},[48,9764,9609],{"class":69},[48,9766,9612],{"class":54},[48,9768,9769],{"class":50,"line":887},[48,9770,1718],{"class":54},[10,9772,9773,9774,9777],{},"الوضع البعيد يُصدر emit ويترك للأب تولّي الترقيم والفرز على مستوى الـ backend. الوضع المحلي يُشغّل الـ sorter في الذاكرة، مع تفصيلة واحدة: حين يعود المستخدم إلى حالة \"غير مفروز\"، يُعاد استنساخ العناصر المحلية من ",[17,9775,9776],{},"globalProps.items",". هذا يستعيد الترتيب الأصلي دون أن نحتاج إلى تذكّره أصلًا.",[10,9779,9780],{},"الـ emit يُطلَق في الحالات الثلاث. الـ backend البعيدة تحتاجه لإعادة الجلب، والمستهلك المحلي يستطيع استخدامه لحفظ اختيار المستخدم في query param داخل الـ URL. إطلاق الـ emit بلا شروط أرخص من التفرّع، والمستهلك يستطيع تجاهله ببساطة.",[25,9782,9784,9785,393,9787,393,9789],{"id":9783},"التفاعلية-shallowref-وclonedeep-وdebouncedwatch","التفاعلية: ",[17,9786,9268],{},[17,9788,9657],{},[17,9790,9791],{},"debouncedWatch",[10,9793,9794,9795,393,9798,393,9801,393,9804,9806,9807,336],{},"المكوّن لا يراقب بيانات المستهلك مراقبة عميقة. ",[17,9796,9797],{},"localItems",[17,9799,9800],{},"localColumns",[17,9802,9803],{},"localActions",[17,9805,8754],{},"، جميعها ",[17,9808,9268],{},[39,9810,9812],{"className":1893,"code":9811,"language":1895,"meta":44,"style":44},"const localItems = shallowRef\u003CT[]>(_.cloneDeep(globalProps.items));\nconst localColumns = shallowRef\u003CIDataTableColumn\u003CT>[]>([]);\nconst localActions = shallowRef\u003CIAction\u003CT>[]>(_.cloneDeep(globalProps.actions));\nconst localSelectedItems = shallowRef\u003CT[]>(_.cloneDeep(globalProps.selectedItems));\n",[17,9813,9814,9838,9861,9889],{"__ignoreMap":44},[48,9815,9816,9818,9821,9823,9826,9828,9830,9833,9835],{"class":50,"line":51},[48,9817,329],{"class":62},[48,9819,9820],{"class":332}," localItems",[48,9822,342],{"class":62},[48,9824,9825],{"class":261}," shallowRef",[48,9827,55],{"class":54},[48,9829,6807],{"class":261},[48,9831,9832],{"class":54},"[]>(_.",[48,9834,9657],{"class":261},[48,9836,9837],{"class":54},"(globalProps.items));\n",[48,9839,9840,9842,9845,9847,9849,9851,9854,9856,9858],{"class":50,"line":81},[48,9841,329],{"class":62},[48,9843,9844],{"class":332}," localColumns",[48,9846,342],{"class":62},[48,9848,9825],{"class":261},[48,9850,55],{"class":54},[48,9852,9853],{"class":261},"IDataTableColumn",[48,9855,55],{"class":54},[48,9857,6807],{"class":261},[48,9859,9860],{"class":54},">[]>([]);\n",[48,9862,9863,9865,9868,9870,9872,9874,9877,9879,9881,9884,9886],{"class":50,"line":87},[48,9864,329],{"class":62},[48,9866,9867],{"class":332}," localActions",[48,9869,342],{"class":62},[48,9871,9825],{"class":261},[48,9873,55],{"class":54},[48,9875,9876],{"class":261},"IAction",[48,9878,55],{"class":54},[48,9880,6807],{"class":261},[48,9882,9883],{"class":54},">[]>(_.",[48,9885,9657],{"class":261},[48,9887,9888],{"class":54},"(globalProps.actions));\n",[48,9890,9891,9893,9896,9898,9900,9902,9904,9906,9908],{"class":50,"line":93},[48,9892,329],{"class":62},[48,9894,9895],{"class":332}," localSelectedItems",[48,9897,342],{"class":62},[48,9899,9825],{"class":261},[48,9901,55],{"class":54},[48,9903,6807],{"class":261},[48,9905,9832],{"class":54},[48,9907,9657],{"class":261},[48,9909,9910],{"class":54},"(globalProps.selectedItems));\n",[10,9912,7450,9913,9915,9916,9919,9920,9922],{},[17,9914,9225],{}," يلفّ المصفوفات في proxy تفاعلي يتتبّع كل تعديل صغير. في جدول من 1000 صف، يعني هذا 1000 proxy على الأقل، مع ما يتبعها من proxies متشعّبة داخل كل صف. معظم هذا الحمل ضائع. المكوّن نفسه يعيد إسناد ",[17,9917,9918],{},".value"," بأكمله كل مرة، ولا يعدّل أي صف داخليًا في مكانه. الـ ",[17,9921,9268],{}," يقول: تتبّع تغيّر المرجع فقط ولا تتعمّق. وهذا هو العقد المطلوب بالضبط.",[10,9924,9925,9926,9929,9930,9932],{},"الاستنساخ هو النصف الثاني من القصة. ",[17,9927,9928],{},"_.cloneDeep(globalProps.items)"," يأخذ لقطة من بيانات الأب لحظة الإسناد، ثم يعمل الجدول على نسخته الخاصة. هذا يهمّ في الفرز تحديدًا: حين يفرز المستخدم، يعدّل الجدول ",[17,9931,9797],{}," عنده دون أن يمسّ مصفوفة الأب. لو حذفنا الاستنساخ، لتحوّل الفرز البصري إلى تعديل مباشر على حالة الأب، وهنا يظهر أسوأ صنف من الأخطاء المعروفة بـ \"التصدير من الخادم لا يطابق ما كان على الشاشة\".",[10,9934,9935,9936,336],{},"المراقبة المتجدّدة تستعمل ",[17,9937,9791],{},[39,9939,9941],{"className":1893,"code":9940,"language":1895,"meta":44,"style":44},"debouncedWatch(\n  () => globalProps.items,\n  () => (localItems.value = _.cloneDeep(globalProps.items)),\n  { deep: true, debounce: 100 }\n);\n\ndebouncedWatch(\n  () => globalProps.selectedItems,\n  () => (localSelectedItems.value = _.cloneDeep(globalProps.selectedItems)),\n  { deep: true, immediate: true, debounce: 50 }\n);\n",[17,9942,9943,9949,9958,9976,9991,9995,9999,10005,10014,10032,10050],{"__ignoreMap":44},[48,9944,9945,9947],{"class":50,"line":51},[48,9946,9791],{"class":261},[48,9948,2114],{"class":54},[48,9950,9951,9953,9955],{"class":50,"line":81},[48,9952,3073],{"class":54},[48,9954,647],{"class":62},[48,9956,9957],{"class":54}," globalProps.items,\n",[48,9959,9960,9962,9964,9967,9969,9971,9973],{"class":50,"line":87},[48,9961,3073],{"class":54},[48,9963,647],{"class":62},[48,9965,9966],{"class":54}," (localItems.value ",[48,9968,66],{"class":62},[48,9970,9654],{"class":54},[48,9972,9657],{"class":261},[48,9974,9975],{"class":54},"(globalProps.items)),\n",[48,9977,9978,9981,9983,9986,9989],{"class":50,"line":93},[48,9979,9980],{"class":54},"  { deep: ",[48,9982,773],{"class":332},[48,9984,9985],{"class":54},", debounce: ",[48,9987,9988],{"class":332},"100",[48,9990,2247],{"class":54},[48,9992,9993],{"class":50,"line":362},[48,9994,629],{"class":54},[48,9996,9997],{"class":50,"line":373},[48,9998,324],{"emptyLinePlaceholder":323},[48,10000,10001,10003],{"class":50,"line":384},[48,10002,9791],{"class":261},[48,10004,2114],{"class":54},[48,10006,10007,10009,10011],{"class":50,"line":661},[48,10008,3073],{"class":54},[48,10010,647],{"class":62},[48,10012,10013],{"class":54}," globalProps.selectedItems,\n",[48,10015,10016,10018,10020,10023,10025,10027,10029],{"class":50,"line":690},[48,10017,3073],{"class":54},[48,10019,647],{"class":62},[48,10021,10022],{"class":54}," (localSelectedItems.value ",[48,10024,66],{"class":62},[48,10026,9654],{"class":54},[48,10028,9657],{"class":261},[48,10030,10031],{"class":54},"(globalProps.selectedItems)),\n",[48,10033,10034,10036,10038,10041,10043,10045,10048],{"class":50,"line":695},[48,10035,9980],{"class":54},[48,10037,773],{"class":332},[48,10039,10040],{"class":54},", immediate: ",[48,10042,773],{"class":332},[48,10044,9985],{"class":54},[48,10046,10047],{"class":332},"50",[48,10049,2247],{"class":54},[48,10051,10052],{"class":50,"line":722},[48,10053,629],{"class":54},[10,10055,10056],{},"لماذا debounce؟ لأن حالة الأب كثيرًا ما تتخبط. تغيير مرشِّح واحد على مستوى الأب قد يُحدث ثلاث إعادات render متتالية: تحديث الفورم، ثم تحديث سلسلة الاستعلام، ثم استجابة الجلب التي تُبدّل بيانات النائب. دون debounce، سيستنسخ الجدول عناصره ثلاث مرات في نفس الإطار. الـ 100ms للعناصر و50ms للتحديد أرقام استقرّيت عليها من مراقبة السلوك على الإنتاج، وليست مقدّسة. لو كانت لديّ الشهية لجعلتها props بحدّ ذاتها.",[10,10058,10059,10060,10063],{},"مراقب الأعمدة والإجراءات ",[7077,10061,10062],{},"ليس"," مُخفَّفًا بـ debounce:",[39,10065,10067],{"className":1893,"code":10066,"language":1895,"meta":44,"style":44},"localColumns.value = handleColumns();\nwatch(\n  () => [globalProps.columns, globalProps.actions],\n  () => {\n    localActions.value = _.cloneDeep(globalProps.actions);\n    localColumns.value = handleColumns();\n  },\n  { deep: true, immediate: true }\n);\n",[17,10068,10069,10081,10087,10096,10104,10118,10129,10133,10145],{"__ignoreMap":44},[48,10070,10071,10074,10076,10079],{"class":50,"line":51},[48,10072,10073],{"class":54},"localColumns.value ",[48,10075,66],{"class":62},[48,10077,10078],{"class":261}," handleColumns",[48,10080,3477],{"class":54},[48,10082,10083,10085],{"class":50,"line":81},[48,10084,2111],{"class":261},[48,10086,2114],{"class":54},[48,10088,10089,10091,10093],{"class":50,"line":87},[48,10090,3073],{"class":54},[48,10092,647],{"class":62},[48,10094,10095],{"class":54}," [globalProps.columns, globalProps.actions],\n",[48,10097,10098,10100,10102],{"class":50,"line":93},[48,10099,3073],{"class":54},[48,10101,647],{"class":62},[48,10103,1624],{"class":54},[48,10105,10106,10109,10111,10113,10115],{"class":50,"line":362},[48,10107,10108],{"class":54},"    localActions.value ",[48,10110,66],{"class":62},[48,10112,9654],{"class":54},[48,10114,9657],{"class":261},[48,10116,10117],{"class":54},"(globalProps.actions);\n",[48,10119,10120,10123,10125,10127],{"class":50,"line":373},[48,10121,10122],{"class":54},"    localColumns.value ",[48,10124,66],{"class":62},[48,10126,10078],{"class":261},[48,10128,3477],{"class":54},[48,10130,10131],{"class":50,"line":384},[48,10132,2237],{"class":54},[48,10134,10135,10137,10139,10141,10143],{"class":50,"line":661},[48,10136,9980],{"class":54},[48,10138,773],{"class":332},[48,10140,10040],{"class":54},[48,10142,773],{"class":332},[48,10144,2247],{"class":54},[48,10146,10147],{"class":50,"line":690},[48,10148,629],{"class":54},[10,10150,10151,10152,10155],{},"تغييرات الأعمدة والإجراءات نادرة. عادةً ما تُصرَّح مرة واحدة في أعلى ",[17,10153,10154],{},"\u003Cscript setup>"," ولا تُلمس بعدها أبدًا. الـ debounce سيُدخل تأخيرًا محسوسًا في الحالة النادرة التي تتغير فيها فعلًا، مثل admin يبدّل ظهور عمود. عدم التماثل (debounce على العناصر ولا debounce على الأعمدة) يعكس بدقة الطريقة التي يستخدم بها الناس المكوّن.",[25,10157,10159],{"id":10158},"التحميل-والفراغ-والترقيم-الزخارف-المحيطة","التحميل والفراغ والترقيم: الزخارف المحيطة",[10,10161,10162],{},"الزخارف المحيطة قصة صغيرة لا يكتبها أحد.",[10,10164,10165,10166,336],{},"حالة التحميل عبارة عن طبقة موضوعة بشكل مطلق مع ",[17,10167,10168],{},"pointer-events-none",[39,10170,10172],{"className":41,"code":10171,"language":43,"meta":44,"style":44},"\u003Cdiv\n  v-if=\"globalProps.loading\"\n  class=\"bg-white\u002F70 flex pointer-events-none items-center inset-0 justify-center absolute z-10 backdrop-blur-[1px]\"\n>\n  \u003CLoadingComp \u002F>\n\u003C\u002Fdiv>\n",[17,10173,10174,10180,10194,10203,10207,10212],{"__ignoreMap":44},[48,10175,10176,10178],{"class":50,"line":51},[48,10177,55],{"class":54},[48,10179,1067],{"class":58},[48,10181,10182,10185,10187,10189,10192],{"class":50,"line":81},[48,10183,10184],{"class":62},"  v-if",[48,10186,66],{"class":54},[48,10188,70],{"class":69},[48,10190,10191],{"class":54},"globalProps.loading",[48,10193,4195],{"class":69},[48,10195,10196,10198,10200],{"class":50,"line":87},[48,10197,1072],{"class":261},[48,10199,66],{"class":54},[48,10201,10202],{"class":69},"\"bg-white\u002F70 flex pointer-events-none items-center inset-0 justify-center absolute z-10 backdrop-blur-[1px]\"\n",[48,10204,10205],{"class":50,"line":93},[48,10206,78],{"class":54},[48,10208,10209],{"class":50,"line":362},[48,10210,10211],{"class":54},"  \u003CLoadingComp \u002F>\n",[48,10213,10214,10216,10218],{"class":50,"line":373},[48,10215,96],{"class":54},[48,10217,59],{"class":58},[48,10219,78],{"class":54},[10,10221,10222,10223,10225],{},"التفصيلة المهمّة هي ",[17,10224,10168],{},". بدونها، تعترض الطبقة النقرات على الصفوف تحتها. الطبقة شفافة بصريًا، ومع ذلك لن يستطيع المستخدم التفاعل مع الجدول المشوّش. النيّة هنا \"أظهِر أن شيئًا يجري، لا تحرم المستخدم من تحديد صف لا يزال يراه\". الطبقة مجرد إشارة، لا حاجز.",[10,10227,10228],{},"حالة الفراغ صف داخل الجدول نفسه، لا شقيقًا له:",[39,10230,10232],{"className":41,"code":10231,"language":43,"meta":44,"style":44},"\u003CATBody :columns=\"localColumns\" :items=\"localItems\">\n  \u003Ctr v-if=\"localItems.length === 0 && !globalProps.loading\">\n    \u003Ctd :colspan=\"localColumns.length\" class=\"p-4\">\n      \u003CNoData :title=\"globalProps.noDataTitle\" \u002F>\n    \u003C\u002Ftd>\n  \u003C\u002Ftr>\n\u003C\u002FATBody>\n",[17,10233,10234,10268,10273,10278,10283,10288,10292],{"__ignoreMap":44},[48,10235,10236,10238,10240,10242,10245,10247,10249,10251,10253,10255,10258,10260,10262,10264,10266],{"class":50,"line":51},[48,10237,55],{"class":54},[48,10239,7130],{"class":58},[48,10241,258],{"class":54},[48,10243,10244],{"class":261},"columns",[48,10246,66],{"class":54},[48,10248,70],{"class":69},[48,10250,9800],{"class":54},[48,10252,70],{"class":69},[48,10254,258],{"class":54},[48,10256,10257],{"class":261},"items",[48,10259,66],{"class":54},[48,10261,70],{"class":69},[48,10263,9797],{"class":54},[48,10265,70],{"class":69},[48,10267,78],{"class":54},[48,10269,10270],{"class":50,"line":81},[48,10271,10272],{"class":54},"  \u003Ctr v-if=\"localItems.length === 0 && !globalProps.loading\">\n",[48,10274,10275],{"class":50,"line":87},[48,10276,10277],{"class":54},"    \u003Ctd :colspan=\"localColumns.length\" class=\"p-4\">\n",[48,10279,10280],{"class":50,"line":93},[48,10281,10282],{"class":54},"      \u003CNoData :title=\"globalProps.noDataTitle\" \u002F>\n",[48,10284,10285],{"class":50,"line":362},[48,10286,10287],{"class":54},"    \u003C\u002Ftd>\n",[48,10289,10290],{"class":50,"line":373},[48,10291,7409],{"class":54},[48,10293,10294,10296,10298],{"class":50,"line":384},[48,10295,96],{"class":54},[48,10297,7130],{"class":58},[48,10299,78],{"class":54},[10,10301,10302,10303,10305,10306,10309,10310,10313],{},"تمريرها عبر slot الـ ",[17,10304,7142],{}," هو ما يجعلها ترث حدّ الجدول ومسافاته. لو وضعناها في ",[17,10307,10308],{},"\u003Cdiv>"," شقيق، لاحتجنا أنماطًا منفصلة لضبط محاذاتها، أما ",[17,10311,10312],{},"\u003Ctr colspan>"," فيحصل على التخطيط مجانًا.",[10,10315,10316,10317,336],{},"الترقيم اختياري ويعمل عبر prop اسمه ",[17,10318,10319],{},"pagination",[39,10321,10323],{"className":41,"code":10322,"language":43,"meta":44,"style":44},"\u003Cdiv v-if=\"globalProps.pagination && !globalProps.loading\">\n  \u003CAPagination\n    :model-value=\"globalProps.pagination.modelValue\"\n    :page-size=\"globalProps.pagination.pageSize\"\n    :total-items=\"globalProps.pagination.totalItems\"\n    @update:page-size=\"globalProps.pagination.onPageSizeChange\"\n    @update:model-value=\"globalProps.pagination.onUpdateCurrentPage\"\n  \u002F>\n\u003C\u002Fdiv>\n",[17,10324,10325,10350,10355,10360,10365,10370,10375,10380,10385],{"__ignoreMap":44},[48,10326,10327,10329,10331,10333,10335,10337,10340,10342,10344,10346,10348],{"class":50,"line":51},[48,10328,55],{"class":54},[48,10330,59],{"class":58},[48,10332,63],{"class":62},[48,10334,66],{"class":54},[48,10336,70],{"class":69},[48,10338,10339],{"class":54},"globalProps.pagination ",[48,10341,2513],{"class":62},[48,10343,9642],{"class":62},[48,10345,10191],{"class":54},[48,10347,70],{"class":69},[48,10349,78],{"class":54},[48,10351,10352],{"class":50,"line":81},[48,10353,10354],{"class":54},"  \u003CAPagination\n",[48,10356,10357],{"class":50,"line":87},[48,10358,10359],{"class":54},"    :model-value=\"globalProps.pagination.modelValue\"\n",[48,10361,10362],{"class":50,"line":93},[48,10363,10364],{"class":54},"    :page-size=\"globalProps.pagination.pageSize\"\n",[48,10366,10367],{"class":50,"line":362},[48,10368,10369],{"class":54},"    :total-items=\"globalProps.pagination.totalItems\"\n",[48,10371,10372],{"class":50,"line":373},[48,10373,10374],{"class":54},"    @update:page-size=\"globalProps.pagination.onPageSizeChange\"\n",[48,10376,10377],{"class":50,"line":384},[48,10378,10379],{"class":54},"    @update:model-value=\"globalProps.pagination.onUpdateCurrentPage\"\n",[48,10381,10382],{"class":50,"line":661},[48,10383,10384],{"class":54},"  \u002F>\n",[48,10386,10387,10389,10391],{"class":50,"line":690},[48,10388,96],{"class":54},[48,10390,59],{"class":58},[48,10392,78],{"class":54},[10,10394,10395,10396,10398,10399,393,10402,10405],{},"هناك تفصيلتان تستحقّان الإشارة. أولًا، مكوّن الترقيم مخفيّ أثناء التحميل. لو تركناه ظاهرًا، لبقيت قائمة حجم الصفحة نشطة أثناء الجلب، وقد تُطلق نقرة سريعة ثانية طلب صفحة إضافيًا. وثانيًا، تُمرَّر callbacks داخل كائن ",[17,10397,10319],{}," كحقول (",[17,10400,10401],{},"onPageSizeChange",[17,10403,10404],{},"onUpdateCurrentPage",") لا كـ props منفصلة. لستُ متأكدًا أن هذا كان القرار الصائب، وإصدار emit كان سيبدو أقرب إلى روح Vue. لكنه يحافظ على قصة \"الترقيم كتلة إعداد واحدة\" نظيفة.",[25,10407,10409],{"id":10408},"ما-كنتُ-سأغيّره-في-إعادة-كتابة-رابعة","ما كنتُ سأغيّره في إعادة كتابة رابعة",[10,10411,10412],{},"بضعة مواضع لا يمثّل كود اليوم فيها ما كنتُ سأكتبه لو بدأت من الصفر:",[8674,10414,10415,10427,10443,10459,10465],{},[3673,10416,10417,10426],{},[35,10418,10419,10420,393,10422,7454,10424,526],{},"ازدواج ",[17,10421,7876],{},[17,10423,7880],{},[17,10425,7133],{}," prop منفصلان يشيران إلى البيانات نفسها. دمجهما في prop واحد كان سيوفّر spread إضافيًا وفرعًا زائدًا. تركتُهما لأن مواقع الاستدعاء القديمة تعتمد على الاسم.",[3673,10428,10429,10434,10435,10438,10439,10442],{},[35,10430,10431,10433],{},[17,10432,8307],{}," في الخلايا الافتراضية."," الفخّ حقيقي، لكن الأرغونومية تستحقّ. لو أعدتُ الكتابة، فسأذهب إما إلى ",[17,10436,10437],{},"textContent"," افتراضيًا مع جعل الـ HTML خيارًا صريحًا عبر حقل في العمود (",[17,10440,10441],{},"unsafe: true",")، وإما إلى نهج مبني بالكامل على slots. كلاهما يحتاج إعادة كتابة، ولا أحدهما مستعجل.",[3673,10444,10445,10451,10452,10455,10456,10458],{},[35,10446,10447,10448,10450],{},"تحويل ",[17,10449,7330],{}," داخل الرأس."," يعمل، لكن جعل ",[17,10453,10454],{},"record?: S"," اختياريًا داخل ",[17,10457,7133],{}," سيكون أكثر أمانة. الرأس لا يمتلك سجلًا، فليقل النوع ذلك صراحة.",[3673,10460,10461,10464],{},[35,10462,10463],{},"قيم الـ debounce المكتوبة يدويًا (50ms و100ms)."," يجب أن تكون props. ثلاث دقائق عمل، ومع ذلك أؤجّلها كل مرة.",[3673,10466,10467,10476,10477,10479],{},[35,10468,7450,10469,10472,10473,10475],{},[17,10470,10471],{},"Cell"," المحسوب مع ",[17,10474,7901],{}," المفتاح فيه معلَّق على حالة الفرز."," ما زلتُ أُحدّق فيها. إما أن المفتاح غير ضروري (احذفه وانتظر ماذا يحصل)، وإما أنه يُخفي خطأ توقيت في ",[17,10478,7916],{}," يجب أن أستطيع إعادة إنتاجه. أنا مدين للكود بذلك التحقيق.",[10,10481,10482,10483,10485,10486,10488,10489,10491],{},"لا شيء من هذه هو السبب في أنني أنصح بهذا النمط. السبب أبسط وأملّ من هذا كلّه: سكيمة العمود كبيانات، وإيقاع ",[17,10484,9268],{}," مع الاستنساخ مع الـ debounce، والتحديد المفهرَس بـ Map، و",[17,10487,9079],{}," على مكوّن الصف. كل واحد منها من الأشياء التي تكتبها في المرة الثانية، بعد أن تكون النسخة الأولى قد عضّتك بالفعل. وهذه هي قصة ",[17,10490,6448],{}," كلها. الشكل الذي استقرّ عليه هو شكل الشيء الذي كففتُ عن إعادة كتابته.",[218,10493,10494],{},"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 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}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":44,"searchDepth":81,"depth":81,"links":10496},[10497,10499,10501,10506,10508,10509,10511,10513,10514,10516,10517],{"id":6455,"depth":81,"text":10498},"من كلاس IColumn إلى interface مكتوب بأنواع",{"id":7123,"depth":81,"text":10500},"ثلاثي الـ render: ATHead وATBody وATCell",{"id":7477,"depth":81,"text":10502,"children":10503},"ATCell: مكوّن واحد بشكلَين، مبنيّ عبر h()",[10504],{"id":7887,"depth":87,"text":10505},"الـ :key=\"sortValue?.applied\" الذي يبدو خطأً مطبعيًا",{"id":7920,"depth":81,"text":10507},"handleValueBasedOnKey: مسارات بالنقاط داخل الصفوف",{"id":8321,"depth":81,"text":8322},{"id":8744,"depth":81,"text":10510},"التحديد على شكل Map\u003Cstring, T>",{"id":9075,"depth":81,"text":10512},"الإجراءات، وmarkRaw الذي أثبت جدواه",{"id":9341,"depth":81,"text":9342},{"id":9783,"depth":81,"text":10515},"التفاعلية: shallowRef وcloneDeep وdebouncedWatch",{"id":10158,"depth":81,"text":10159},{"id":10408,"depth":81,"text":10409},"2026-04-19","جولة كاملة في جدول بيانات Vue مُعمَّم بأنواع صارمة، بأعمدة مُعرَّفة كسكيمة، وتحديد صفوف قائم على Map، وأزرار إجراءات لكل صف عبر markRaw، ودورة فرز من ثلاث نقرات.",[10521,10522,9079,9268,10523,10524,10525,9791,10526,1840],"جدول بيانات Vue 3","مكوّن TypeScript مُعمَّم","تحديد الصفوف","فرز الأعمدة","تحليل مسارات بالنقاط","مكوّن واجهة",{},"\u002Fwrites-ar\u002Fanatomy-of-adatatable",{"title":6433,"description":10519},"writes-ar\u002Fanatomy-of-adatatable","7-nOCz0mwXbkHvNV5ZA2fsV31ZK1i0hzHSGTY2Z5KFI",1793901269930]