@africanies/angular-web-sdk 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/.angular/cache/22.0.9/playground/.tsbuildinfo +1 -1
  2. package/.angular/cache/22.0.9/playground/angular-compiler.db +0 -0
  3. package/.angular/cache/22.0.9/playground/angular-compiler.db-lock +0 -0
  4. package/.github/workflows/deploy-demo.yml +63 -0
  5. package/.nx/cache/12276756491762927325/dist/libs/africanies-theme/README.md +46 -0
  6. package/.nx/cache/12276756491762927325/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +215 -0
  7. package/.nx/cache/12276756491762927325/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -0
  8. package/.nx/cache/12276756491762927325/dist/libs/africanies-theme/package.json +35 -0
  9. package/.nx/cache/12276756491762927325/dist/libs/africanies-theme/tailwind-preset.cjs +245 -0
  10. package/.nx/cache/12276756491762927325/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +157 -0
  11. package/.nx/cache/17491371155400585077/dist/libs/africanies-core/README.md +85 -0
  12. package/.nx/cache/17491371155400585077/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +4546 -0
  13. package/.nx/cache/17491371155400585077/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -0
  14. package/.nx/cache/17491371155400585077/dist/libs/africanies-core/package.json +31 -0
  15. package/.nx/cache/17491371155400585077/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +2740 -0
  16. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/README.md +68 -0
  17. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  18. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  19. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +5 -0
  20. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/assets/carriers/dhl.svg +7 -0
  21. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +19291 -0
  22. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  23. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/package.json +35 -0
  24. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +5483 -0
  25. package/.nx/cache/run.json +38 -38
  26. package/.nx/cache/terminalOutputs/12276756491762927325 +21 -0
  27. package/.nx/cache/terminalOutputs/13524438527920092277 +196 -21
  28. package/.nx/cache/terminalOutputs/14153928176747262656 +45 -21
  29. package/.nx/cache/terminalOutputs/14416694322692463866 +108 -21
  30. package/.nx/cache/terminalOutputs/15255861479705594703 +51 -21
  31. package/.nx/cache/terminalOutputs/17491371155400585077 +21 -0
  32. package/.nx/cache/terminalOutputs/18132311043100376919 +315 -21
  33. package/.nx/cache/terminalOutputs/2436781291243085315 +8 -0
  34. package/.nx/cache/terminalOutputs/3017748574492080228 +28 -0
  35. package/.nx/cache/terminalOutputs/3314955264390318442 +21 -0
  36. package/.nx/cache/terminalOutputs/6664915487114632770 +44 -21
  37. package/.nx/cache/terminalOutputs/8648260256613557249 +173 -0
  38. package/.nx/cache/terminalOutputs/8767335728598176888 +37 -0
  39. package/.nx/cache/terminalOutputs/9408529484792607601 +6 -0
  40. package/.nx/cache/terminalOutputs/9739655320223921719 +47 -0
  41. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db +0 -0
  42. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-shm +0 -0
  43. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-wal +0 -0
  44. package/.nx/workspace-data/d/daemon.log +3026 -0
  45. package/.nx/workspace-data/d/server-process.json +2 -2
  46. package/.nx/workspace-data/file-map.json +3268 -3262
  47. package/.nx/workspace-data/nx_files.nxt +0 -0
  48. package/.nx/workspace-data/project-graph.json +9 -9
  49. package/.nx/workspace-data/project-graph.lock +0 -0
  50. package/.pnpm-store/v11/index.db +0 -0
  51. package/README.md +13 -1
  52. package/apps/playground/src/app/app.config.ts +28 -3
  53. package/apps/playground/src/app/snippets/action-menu.snippets.ts +1 -0
  54. package/dist/apps/playground/3rdpartylicenses.txt +415 -0
  55. package/dist/apps/playground/browser/assets/africanies-icons/icons.sprite.svg +1844 -0
  56. package/dist/apps/playground/browser/assets/africanies-ui/brand/africanies-logo-mini.png +0 -0
  57. package/dist/apps/playground/browser/assets/africanies-ui/brand/africanies-logo.png +0 -0
  58. package/dist/apps/playground/browser/assets/africanies-ui/brand/africanies-logo.svg +5 -0
  59. package/dist/apps/playground/browser/assets/africanies-ui/carriers/dhl.svg +7 -0
  60. package/dist/apps/playground/browser/chunk--VJ6U9Ev.js +2 -0
  61. package/dist/apps/playground/browser/chunk-B1QVY0hA.js +1 -0
  62. package/dist/apps/playground/browser/chunk-BHI7MG2Y.js +1 -0
  63. package/dist/apps/playground/browser/chunk-BJ5IK8Re.js +1 -0
  64. package/dist/apps/playground/browser/chunk-BOSfyWK2.js +1 -0
  65. package/dist/apps/playground/browser/chunk-BYPboEAg.js +1 -0
  66. package/dist/apps/playground/browser/chunk-BZBRtbFJ.js +1 -0
  67. package/dist/apps/playground/browser/chunk-BdrGkq40.js +1 -0
  68. package/dist/apps/playground/browser/chunk-Bej5a2xR.js +1 -0
  69. package/dist/apps/playground/browser/chunk-Bg3QC1eL.js +1 -0
  70. package/dist/apps/playground/browser/chunk-BsnTz1m5.js +1 -0
  71. package/dist/apps/playground/browser/chunk-C5kLunTi.js +1 -0
  72. package/dist/apps/playground/browser/chunk-C6tu3y81.js +1 -0
  73. package/dist/apps/playground/browser/chunk-C96WSpFp.js +1 -0
  74. package/dist/apps/playground/browser/chunk-CCnOVzbz.js +2 -0
  75. package/dist/apps/playground/browser/chunk-CERmZ9We.js +1 -0
  76. package/dist/apps/playground/browser/chunk-CZBTrY_H.js +68 -0
  77. package/dist/apps/playground/browser/chunk-Cg7brw6x.js +4070 -0
  78. package/dist/apps/playground/browser/chunk-CjTyStrr.js +1 -0
  79. package/dist/apps/playground/browser/chunk-CjYlnYC7.js +1 -0
  80. package/dist/apps/playground/browser/chunk-ClSUoZp8.js +292 -0
  81. package/dist/apps/playground/browser/chunk-CsrxfMap.js +1 -0
  82. package/dist/apps/playground/browser/chunk-CtndMAe2.js +1 -0
  83. package/dist/apps/playground/browser/chunk-Cy-ZOZI8.js +1 -0
  84. package/dist/apps/playground/browser/chunk-D3-jrtyv.js +1 -0
  85. package/dist/apps/playground/browser/chunk-D3vgrlDF.js +1 -0
  86. package/dist/apps/playground/browser/chunk-D48aVVTv.js +1 -0
  87. package/dist/apps/playground/browser/chunk-D4V3eSCv.js +1 -0
  88. package/dist/apps/playground/browser/chunk-DK715t6D.js +1 -0
  89. package/dist/apps/playground/browser/chunk-DUkct7WP.js +1 -0
  90. package/dist/apps/playground/browser/chunk-Y9ccR-I6.js +1 -0
  91. package/dist/apps/playground/browser/chunk-lfUHaltX.js +1 -0
  92. package/dist/apps/playground/browser/favicon.ico +0 -0
  93. package/dist/apps/playground/browser/index.html +14 -0
  94. package/dist/apps/playground/browser/main-WEJC3BLT.js +148 -0
  95. package/dist/apps/playground/browser/styles-4FSELHYD.css +1 -0
  96. package/dist/apps/playground/prerendered-routes.json +3 -0
  97. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +1 -1
  98. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -1
  99. package/dist/libs/africanies-core/package.json +3 -3
  100. package/dist/libs/africanies-theme/package.json +3 -3
  101. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +318 -115
  102. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -1
  103. package/dist/libs/africanies-ui/package.json +5 -5
  104. package/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +92 -19
  105. package/libs/africanies-core/package.json +3 -3
  106. package/libs/africanies-core/src/lib/payment-method/payment-method.mapper.spec.ts +15 -0
  107. package/libs/africanies-core/src/lib/payment-method/payment-method.mapper.ts +1 -2
  108. package/libs/africanies-theme/package.json +3 -3
  109. package/libs/africanies-ui/package.json +5 -5
  110. package/libs/africanies-ui/src/index.ts +6 -0
  111. package/libs/africanies-ui/src/lib/accordion/accordion.component.ts +135 -0
  112. package/libs/africanies-ui/src/lib/accordion/index.ts +7 -0
  113. package/libs/africanies-ui/src/lib/action-menu/action-menu.component.ts +84 -17
  114. package/libs/africanies-ui/src/lib/action-menu/menu-item.ts +19 -2
  115. package/libs/africanies-ui/src/lib/modules/africanies-feedback.module.ts +3 -1
  116. package/package.json +31 -8
@@ -0,0 +1,4070 @@
1
+ import {o}from'./chunk-CZBTrY_H.js';import {T,bA as Eu,aO as pt,k as TP,bB as RP,C as Co$1,u as QI,I as IE,Z,g,h as br$1,l as tI,e as ei$1,M as MI,a as gc,L as LE,o as nI,b9 as lp,J as zf,Q as Qy,f as gp,P as PE,m as mI,t as tp,X as XE,i as Ic,G as GE,c as Zf,d as Qf,bC as gD,bD as hD,p as Gl,z as zl,bE as oy}from'./main-WEJC3BLT.js';var Nt=hD((pi,It)=>{"use strict";function yt(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw new Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw new Error("set is read-only")}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach(t=>{let n=e[t],i=typeof n;(i==="object"||i==="function")&&!Object.isFrozen(n)&&yt(n);}),e}var be=class{constructor(t){t.data===void 0&&(t.data={}),this.data=t.data,this.isMatchIgnored=!1;}ignoreMatch(){this.isMatchIgnored=!0;}};function vt(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")}function q(e,...t){let n=Object.create(null);for(let i in e)n[i]=e[i];return t.forEach(function(i){for(let m in i)n[m]=i[m];}),n}var en="</span>",mt=e=>!!e.scope,tn=(e,{prefix:t})=>{if(e.startsWith("language:"))return e.replace("language:","language-");if(e.includes(".")){let n=e.split(".");return [`${t}${n.shift()}`,...n.map((i,m)=>`${i}${"_".repeat(m+1)}`)].join(" ")}return `${t}${e}`},Pe=class{constructor(t,n){this.buffer="",this.classPrefix=n.classPrefix,t.walk(this);}addText(t){this.buffer+=vt(t);}openNode(t){if(!mt(t))return;let n=tn(t.scope,{prefix:this.classPrefix});this.span(n);}closeNode(t){mt(t)&&(this.buffer+=en);}value(){return this.buffer}span(t){this.buffer+=`<span class="${t}">`;}},ut=(e={})=>{let t={children:[]};return Object.assign(t,e),t},Le=class e{constructor(){this.rootNode=ut(),this.stack=[this.rootNode];}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(t){this.top.children.push(t);}openNode(t){let n=ut({scope:t});this.add(n),this.stack.push(n);}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(t){return this.constructor._walk(t,this.rootNode)}static _walk(t,n){return typeof n=="string"?t.addText(n):n.children&&(t.openNode(n),n.children.forEach(i=>this._walk(t,i)),t.closeNode(n)),t}static _collapse(t){typeof t!="string"&&t.children&&(t.children.every(n=>typeof n=="string")?t.children=[t.children.join("")]:t.children.forEach(n=>{e._collapse(n);}));}},Fe=class extends Le{constructor(t){super(),this.options=t;}addText(t){t!==""&&this.add(t);}startScope(t){this.openNode(t);}endScope(){this.closeNode();}__addSublanguage(t,n){let i=t.root;n&&(i.scope=`language:${n}`),this.add(i);}toHTML(){return new Pe(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}};function se(e){return e?typeof e=="string"?e:e.source:null}function St(e){return J("(?=",e,")")}function nn(e){return J("(?:",e,")*")}function rn(e){return J("(?:",e,")?")}function J(...e){return e.map(n=>se(n)).join("")}function on(e){let t=e[e.length-1];return typeof t=="object"&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function ve(...e){return "("+(on(e).capture?"":"?:")+e.map(i=>se(i)).join("|")+")"}function Ct(e){return new RegExp(e.toString()+"|").exec("").length-1}function an(e,t){let n=e&&e.exec(t);return n&&n.index===0}var sn=new RegExp(ve(/\[(?:[^\\\]]|\\.)*\]/,/\(\?<(?![=!])[^>]+>/,/\(\?'[^']+'/,/\(\??/,/\\([1-9][0-9]*)/,/\\./));function Ue(e,{joinWith:t}){let n=0;return e.map(i=>{n+=1;let m=n,g=se(i),a="";for(;g.length>0;){let o=sn.exec(g);if(!o){a+=g;break}a+=g.substring(0,o.index),g=g.substring(o.index+o[0].length),o[0][0]==="\\"&&o[1]?a+="\\"+String(Number(o[1])+m):(a+=o[0],(o[0]==="("||/^\(\?[<']/.test(o[0]))&&n++);}return a}).map(i=>`(${i})`).join(t)}var ln=/\b\B/,xt="[a-zA-Z]\\w*",je="[a-zA-Z_]\\w*",wt="\\b\\d+(\\.\\d+)?",Et="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",At="\\b(0b[01]+)",cn="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",pn=(e={})=>{let t=/^#![ ]*\//;return e.binary&&(e.begin=J(t,/.*\b/,e.binary,/\b.*/)),q({scope:"meta",begin:t,end:/$/,relevance:0,"on:begin":(n,i)=>{n.index!==0&&i.ignoreMatch();}},e)},le={begin:"\\\\[\\s\\S]",relevance:0},dn={scope:"string",begin:"'",end:"'",illegal:"\\n",contains:[le]},mn={scope:"string",begin:'"',end:'"',illegal:"\\n",contains:[le]},un={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},Se=function(e,t,n={}){let i=q({scope:"comment",begin:e,end:t,contains:[]},n);i.contains.push({scope:"doctag",begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});let m=ve("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return i.contains.push({begin:J(/[ ]+/,"(",m,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i},fn=Se("//","$"),gn=Se("/\\*","\\*/"),hn=Se("#","$"),bn={scope:"number",begin:wt,relevance:0},yn={scope:"number",begin:Et,relevance:0},vn={scope:"number",begin:At,relevance:0},Sn={scope:"regexp",begin:/\/(?=[^/\n]*\/)/,end:/\/[gimuy]*/,contains:[le,{begin:/\[/,end:/\]/,relevance:0,contains:[le]}]},Cn={scope:"title",begin:xt,relevance:0},xn={scope:"title",begin:je,relevance:0},wn={begin:"\\.\\s*"+je,relevance:0},En=function(e){return Object.assign(e,{"on:begin":(t,n)=>{n.data._beginMatch=t[1];},"on:end":(t,n)=>{n.data._beginMatch!==t[1]&&n.ignoreMatch();}})},he=Object.freeze({__proto__:null,APOS_STRING_MODE:dn,BACKSLASH_ESCAPE:le,BINARY_NUMBER_MODE:vn,BINARY_NUMBER_RE:At,COMMENT:Se,C_BLOCK_COMMENT_MODE:gn,C_LINE_COMMENT_MODE:fn,C_NUMBER_MODE:yn,C_NUMBER_RE:Et,END_SAME_AS_BEGIN:En,HASH_COMMENT_MODE:hn,IDENT_RE:xt,MATCH_NOTHING_RE:ln,METHOD_GUARD:wn,NUMBER_MODE:bn,NUMBER_RE:wt,PHRASAL_WORDS_MODE:un,QUOTE_STRING_MODE:mn,REGEXP_MODE:Sn,RE_STARTERS_RE:cn,SHEBANG:pn,TITLE_MODE:Cn,UNDERSCORE_IDENT_RE:je,UNDERSCORE_TITLE_MODE:xn});function An(e,t){e.input[e.index-1]==="."&&t.ignoreMatch();}function kn(e,t){e.className!==void 0&&(e.scope=e.className,delete e.className);}function _n(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=An,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,e.relevance===void 0&&(e.relevance=0));}function Tn(e,t){Array.isArray(e.illegal)&&(e.illegal=ve(...e.illegal));}function Mn(e,t){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match;}}function In(e,t){e.relevance===void 0&&(e.relevance=1);}var Nn=(e,t)=>{if(!e.beforeMatch)return;if(e.starts)throw new Error("beforeMatch cannot be used with starts");let n=Object.assign({},e);Object.keys(e).forEach(i=>{delete e[i];}),e.keywords=n.keywords,e.begin=J(n.beforeMatch,St(n.begin)),e.starts={relevance:0,contains:[Object.assign(n,{endsParent:!0})]},e.relevance=0,delete n.beforeMatch;},Rn=["of","and","for","in","not","or","if","then","parent","list","value"],On="keyword";function kt(e,t,n=On){let i=Object.create(null);return typeof e=="string"?m(n,e.split(" ")):Array.isArray(e)?m(n,e):Object.keys(e).forEach(function(g){Object.assign(i,kt(e[g],t,g));}),i;function m(g,a){t&&(a=a.map(o=>o.toLowerCase())),a.forEach(function(o){let c=o.split("|");i[c[0]]=[g,Dn(c[0],c[1])];});}}function Dn(e,t){return t?Number(t):Pn(e)?0:1}function Pn(e){return Rn.includes(e.toLowerCase())}var ft={},X=e=>{console.error(e);},gt=(e,...t)=>{console.log(`WARN: ${e}`,...t);},re=(e,t)=>{ft[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),ft[`${e}/${t}`]=!0);},ye=new Error;function _t(e,t,{key:n}){let i=0,m=e[n],g={},a={};for(let o=1;o<=t.length;o++)a[o+i]=m[o],g[o+i]=!0,i+=Ct(t[o-1]);e[n]=a,e[n]._emit=g,e[n]._multi=!0;}function Ln(e){if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw X("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),ye;if(typeof e.beginScope!="object"||e.beginScope===null)throw X("beginScope must be object"),ye;_t(e,e.begin,{key:"beginScope"}),e.begin=Ue(e.begin,{joinWith:""});}}function Fn(e){if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw X("skip, excludeEnd, returnEnd not compatible with endScope: {}"),ye;if(typeof e.endScope!="object"||e.endScope===null)throw X("endScope must be object"),ye;_t(e,e.end,{key:"endScope"}),e.end=Ue(e.end,{joinWith:""});}}function Bn(e){e.scope&&typeof e.scope=="object"&&e.scope!==null&&(e.beginScope=e.scope,delete e.scope);}function Un(e){Bn(e),typeof e.beginScope=="string"&&(e.beginScope={_wrap:e.beginScope}),typeof e.endScope=="string"&&(e.endScope={_wrap:e.endScope}),Ln(e),Fn(e);}function jn(e){function t(a,o){return new RegExp(se(a),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(o?"g":""))}class n{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0;}addRule(o,c){c.position=this.position++,this.matchIndexes[this.matchAt]=c,this.regexes.push([c,o]),this.matchAt+=Ct(o)+1;}compile(){this.regexes.length===0&&(this.exec=()=>null);let o=this.regexes.map(c=>c[1]);this.matcherRe=t(Ue(o,{joinWith:"|"}),!0),this.lastIndex=0;}exec(o){this.matcherRe.lastIndex=this.lastIndex;let c=this.matcherRe.exec(o);if(!c)return null;let b=c.findIndex((M,A)=>A>0&&M!==void 0),v=this.matchIndexes[b];return c.splice(0,b),Object.assign(c,v)}}class i{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0;}getMatcher(o){if(this.multiRegexes[o])return this.multiRegexes[o];let c=new n;return this.rules.slice(o).forEach(([b,v])=>c.addRule(b,v)),c.compile(),this.multiRegexes[o]=c,c}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0;}addRule(o,c){this.rules.push([o,c]),c.type==="begin"&&this.count++;}exec(o){let c=this.getMatcher(this.regexIndex);c.lastIndex=this.lastIndex;let b=c.exec(o);if(this.resumingScanAtSamePosition()&&!(b&&b.index===this.lastIndex)){let v=this.getMatcher(0);v.lastIndex=this.lastIndex+1,b=v.exec(o);}return b&&(this.regexIndex+=b.position+1,this.regexIndex===this.count&&this.considerAll()),b}}function m(a){let o=new i;return a.contains.forEach(c=>o.addRule(c.begin,{rule:c,type:"begin"})),a.terminatorEnd&&o.addRule(a.terminatorEnd,{type:"end"}),a.illegal&&o.addRule(a.illegal,{type:"illegal"}),o}function g(a,o){let c=a;if(a.isCompiled)return c;[kn,Mn,Un,Nn].forEach(v=>v(a,o)),e.compilerExtensions.forEach(v=>v(a,o)),a.__beforeBegin=null,[_n,Tn,In].forEach(v=>v(a,o)),a.isCompiled=!0;let b=null;return typeof a.keywords=="object"&&a.keywords.$pattern&&(a.keywords=Object.assign({},a.keywords),b=a.keywords.$pattern,delete a.keywords.$pattern),b=b||/\w+/,a.keywords&&(a.keywords=kt(a.keywords,e.case_insensitive)),c.keywordPatternRe=t(b,!0),o&&(a.begin||(a.begin=/\B|\b/),c.beginRe=t(c.begin),!a.end&&!a.endsWithParent&&(a.end=/\B|\b/),a.end&&(c.endRe=t(c.end)),c.terminatorEnd=se(c.end)||"",a.endsWithParent&&o.terminatorEnd&&(c.terminatorEnd+=(a.end?"|":"")+o.terminatorEnd)),a.illegal&&(c.illegalRe=t(a.illegal)),a.contains||(a.contains=[]),a.contains=[].concat(...a.contains.map(function(v){return zn(v==="self"?a:v)})),a.contains.forEach(function(v){g(v,c);}),a.starts&&g(a.starts,o),c.matcher=m(c),c}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=q(e.classNameAliases||{}),g(e)}function Tt(e){return e?e.endsWithParent||Tt(e.starts):!1}function zn(e){return e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map(function(t){return q(e,{variants:null},t)})),e.cachedVariants?e.cachedVariants:Tt(e)?q(e,{starts:e.starts?q(e.starts):null}):Object.isFrozen(e)?q(e):e}var Gn="11.12.0",Be=class extends Error{constructor(t,n){super(t),this.name="HTMLInjectionError",this.html=n;}},De=vt,ht=q,bt=Symbol("nomatch"),Vn=7,Mt=function(e){let t=Object.create(null),n=Object.create(null),i=[],m=!0,g="Could not find the language '{}', did you forget to load/include a language module?",a={disableAutodetect:!0,name:"Plain text",contains:[]},o={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",cssSelector:"pre code",languages:null,__emitter:Fe};function c(r){return o.noHighlightRe.test(r)}function b(r){let p=r.className+" ";p+=r.parentNode?r.parentNode.className:"";let d=o.languageDetectRe.exec(p);if(d){let h=D(d[1]);return h||(gt(g.replace("{}",d[1])),gt("Falling back to no-highlight mode for this block.",r)),h?d[1]:"no-highlight"}return p.split(/\s+/).find(h=>c(h)||D(h))}function v(r,p,d){let h="",C="";typeof p=="object"?(h=r,d=p.ignoreIllegals,C=p.language):(re("10.7.0","highlight(lang, code, ...args) has been deprecated."),re("10.7.0",`Please use highlight(code, options) instead.
2
+ https://github.com/highlightjs/highlight.js/issues/2277`),C=r,h=p),d===void 0&&(d=!0);let w={code:h,language:C};te("before:highlight",w);let N=w.result?w.result:M(w.language,w.code,d);return N.code=w.code,te("after:highlight",N),N}function M(r,p,d,h){let C=Object.create(null);function w(s,l){return s.keywords[l]}function N(){if(!u.keywords){x.addText(S);return}let s=0;u.keywordPatternRe.lastIndex=0;let l=u.keywordPatternRe.exec(S),f="";for(;l;){f+=S.substring(s,l.index);let y=F.case_insensitive?l[0].toLowerCase():l[0],E=w(u,y);if(E){let[z,Zt]=E;if(x.addText(f),f="",C[y]=(C[y]||0)+1,C[y]<=Vn&&(ue+=Zt),z.startsWith("_"))f+=l[0];else {let Qt=F.classNameAliases[z]||z;L(l[0],Qt);}}else f+=l[0];s=u.keywordPatternRe.lastIndex,l=u.keywordPatternRe.exec(S);}f+=S.substring(s),x.addText(f);}function Y(){if(S==="")return;let s=null;if(typeof u.subLanguage=="string"){if(!t[u.subLanguage]){x.addText(S);return}s=M(u.subLanguage,S,!0,$e[u.subLanguage]),$e[u.subLanguage]=s._top;}else s=O(S,u.subLanguage.length?u.subLanguage:null);u.relevance>0&&(ue+=s.relevance),x.__addSublanguage(s._emitter,s.language);}function I(){u.subLanguage!=null?Y():N(),S="";}function L(s,l){s!==""&&(x.startScope(l),x.addText(s),x.endScope());}function Ve(s,l){let f=1,y=l.length-1;for(;f<=y;){if(!s._emit[f]){f++;continue}let E=F.classNameAliases[s[f]]||s[f],z=l[f];E?L(z,E):(S=z,N(),S=""),f++;}}function He(s,l){return s.scope&&typeof s.scope=="string"&&x.openNode(F.classNameAliases[s.scope]||s.scope),s.beginScope&&(s.beginScope._wrap?(L(S,F.classNameAliases[s.beginScope._wrap]||s.beginScope._wrap),S=""):s.beginScope._multi&&(Ve(s.beginScope,l),S="")),u=Object.create(s,{parent:{value:u}}),u}function We(s,l,f){let y=an(s.endRe,f);if(y){if(s["on:end"]){let E=new be(s);s["on:end"](l,E),E.isMatchIgnored&&(y=!1);}if(y){for(;s.endsParent&&s.parent;)s=s.parent;return s}}if(s.endsWithParent)return We(s.parent,l,f)}function Wt(s){return u.matcher.regexIndex===0?(S+=s[0],1):(Me=!0,0)}function qt(s){let l=s[0],f=s.rule,y=new be(f),E=[f.__beforeBegin,f["on:begin"]];for(let z of E)if(z&&(z(s,y),y.isMatchIgnored))return Wt(l);return f.skip?S+=l:(f.excludeBegin&&(S+=l),I(),!f.returnBegin&&!f.excludeBegin&&(S=l)),He(f,s),f.returnBegin?0:l.length}function $t(s){let l=s[0],f=p.substring(s.index),y=We(u,s,f);if(!y)return bt;let E=u;u.endScope&&u.endScope._wrap?(I(),L(l,u.endScope._wrap)):u.endScope&&u.endScope._multi?(I(),Ve(u.endScope,s)):E.skip?S+=l:(E.returnEnd||E.excludeEnd||(S+=l),I(),E.excludeEnd&&(S=l));do u.scope&&x.closeNode(),!u.skip&&!u.subLanguage&&(ue+=u.relevance),u=u.parent;while(u!==y.parent);return y.starts&&He(y.starts,s),E.returnEnd?0:l.length}function Kt(){let s=[];for(let l=u;l!==F;l=l.parent)l.scope&&s.unshift(l.scope);s.forEach(l=>x.openNode(l));}let me={};function qe(s,l){let f=l&&l[0];if(S+=s,f==null)return I(),0;if(me.type==="begin"&&l.type==="end"&&me.index===l.index&&f===""){if(S+=p.slice(l.index,l.index+1),!m){let y=new Error(`0 width match regex (${r})`);throw y.languageName=r,y.badRule=me.rule,y}return 1}if(me=l,l.type==="begin")return qt(l);if(l.type==="illegal"&&!d){let y=new Error('Illegal lexeme "'+f+'" for mode "'+(u.scope||"<unnamed>")+'"');throw y.mode=u,y}else if(l.type==="end"){let y=$t(l);if(y!==bt)return y}if(l.type==="illegal"&&f==="")return l.index===p.length||(S+=`
3
+ `),1;if(Te>1e5&&Te>l.index*3)throw new Error("potential infinite loop, way more iterations than matches");return S+=f,f.length}let F=D(r);if(!F)throw X(g.replace("{}",r)),new Error('Unknown language: "'+r+'"');let Yt=jn(F),_e="",u=h||Yt,$e={},x=new o.__emitter(o);Kt();let S="",ue=0,Z=0,Te=0,Me=!1;try{if(F.__emitTokens)F.__emitTokens(p,x);else {for(u.matcher.considerAll();;){Te++,Me?Me=!1:u.matcher.considerAll(),u.matcher.lastIndex=Z;let s=u.matcher.exec(p);if(!s)break;let l=p.substring(Z,s.index),f=qe(l,s);Z=s.index+f;}qe(p.substring(Z));}return x.finalize(),_e=x.toHTML(),{language:r,value:_e,relevance:ue,illegal:!1,_emitter:x,_top:u}}catch(s){if(s.message&&s.message.includes("Illegal"))return {language:r,value:De(p),illegal:!0,relevance:0,_illegalBy:{message:s.message,index:Z,context:p.slice(Z-100,Z+100),mode:s.mode,resultSoFar:_e},_emitter:x};if(m)return {language:r,value:De(p),illegal:!1,relevance:0,errorRaised:s,_emitter:x,_top:u};throw s}}function A(r){let p={value:De(r),illegal:!1,relevance:0,_top:a,_emitter:new o.__emitter(o)};return p._emitter.addText(r),p}function O(r,p){p=p||o.languages||Object.keys(t);let d=A(r),h=p.filter(D).filter(de).map(I=>M(I,r,!1));h.unshift(d);let C=h.sort((I,L)=>{if(I.relevance!==L.relevance)return L.relevance-I.relevance;if(I.language&&L.language){if(D(I.language).supersetOf===L.language)return 1;if(D(L.language).supersetOf===I.language)return -1}return 0}),[w,N]=C,Y=w;return Y.secondBest=N,Y}function G(r,p,d){let h=p&&n[p]||d;r.classList.add("hljs"),r.classList.add(`language-${h}`);}function k(r){let p=null,d=b(r);if(c(d))return;if(te("before:highlightElement",{el:r,language:d}),r.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",r);return}if(r.children.length>0&&(o.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),console.warn("The element with unescaped HTML:"),console.warn(r)),o.throwUnescapedHTML))throw new Be("One of your code blocks includes unescaped HTML.",r.innerHTML);p=r;let h=p.textContent,C=d?v(h,{language:d,ignoreIllegals:!0}):O(h);r.innerHTML=C.value,r.dataset.highlighted="yes",G(r,d,C.language),r.result={language:C.language,re:C.relevance,relevance:C.relevance},C.secondBest&&(r.secondBest={language:C.secondBest.language,relevance:C.secondBest.relevance}),te("after:highlightElement",{el:r,result:C,text:h});}function V(r){o=ht(o,r);}let ee=()=>{j(),re("10.6.0","initHighlighting() deprecated. Use highlightAll() now.");};function P(){j(),re("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.");}let U=!1;function j(){function r(){j();}if(document.readyState==="loading"){U||window.addEventListener("DOMContentLoaded",r,!1),U=!0;return}document.querySelectorAll(o.cssSelector).forEach(k);}function $(r,p){let d=null;try{d=p(e);}catch(h){if(X("Language definition for '{}' could not be registered.".replace("{}",r)),m)X(h);else throw h;d=a;}d.name||(d.name=r),t[r]=d,d.rawDefinition=p.bind(null,e),d.aliases&&pe(d.aliases,{languageName:r});}function K(r){delete t[r];for(let p of Object.keys(n))n[p]===r&&delete n[p];}function xe(){return Object.keys(t)}function D(r){return r=(r||"").toLowerCase(),t[r]||t[n[r]]}function pe(r,{languageName:p}){typeof r=="string"&&(r=[r]),r.forEach(d=>{n[d.toLowerCase()]=p;});}function de(r){let p=D(r);return p&&!p.disableAutodetect}function we(r){r["before:highlightBlock"]&&!r["before:highlightElement"]&&(r["before:highlightElement"]=p=>{r["before:highlightBlock"](Object.assign({block:p.el},p));}),r["after:highlightBlock"]&&!r["after:highlightElement"]&&(r["after:highlightElement"]=p=>{r["after:highlightBlock"](Object.assign({block:p.el},p));});}function Ee(r){we(r),i.push(r);}function Ae(r){let p=i.indexOf(r);p!==-1&&i.splice(p,1);}function te(r,p){let d=r;i.forEach(function(h){h[d]&&h[d](p);});}function ke(r){return re("10.7.0","highlightBlock will be removed entirely in v12.0"),re("10.7.0","Please use highlightElement now."),k(r)}Object.assign(e,{highlight:v,highlightAuto:O,highlightAll:j,highlightElement:k,highlightBlock:ke,configure:V,initHighlighting:ee,initHighlightingOnLoad:P,registerLanguage:$,unregisterLanguage:K,listLanguages:xe,getLanguage:D,registerAliases:pe,autoDetection:de,inherit:ht,addPlugin:Ee,removePlugin:Ae}),e.debugMode=function(){m=!1;},e.safeMode=function(){m=!0;},e.versionString=Gn,e.regex={concat:J,lookahead:St,either:ve,optional:rn,anyNumberOfTimes:nn};for(let r in he)typeof he[r]=="object"&&yt(he[r]);return Object.assign(e,he),e},oe=Mt({});oe.newInstance=()=>Mt({});It.exports=oe;oe.HighlightJS=oe;oe.default=oe;});var Rt=gD(Nt());var R=Rt.default;function ze(e){let t=e.regex,n={},i={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[n]}]};Object.assign(n,{className:"variable",variants:[{begin:t.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},i]});let m={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},g=e.inherit(e.COMMENT(),{match:[/(^|\s)/,/#.*$/],scope:{2:"comment"}}),a={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},o={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n,m]};m.contains.push(o);let c={match:/\\"/},b={className:"string",begin:/'/,end:/'/},v={match:/\\'/},M={begin:/\$?\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,n]},A=["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"],O=e.SHEBANG({binary:`(${A.join("|")})`,relevance:10}),G={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:true,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},k=["if","then","else","elif","fi","time","for","while","until","in","do","done","case","esac","coproc","function","select"],V=["true","false"],ee={match:/(\/[a-z._-]+)+/},P=["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset"],U=["alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","sudo","type","typeset","ulimit","unalias"],j=["autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp"],$=["chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"];return {name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/,keyword:k,literal:V,built_in:[...P,...U,"set","shopt",...j,...$]},contains:[O,e.SHEBANG(),G,M,g,a,ee,o,c,b,v,n]}}var Hn=e=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},UNICODE_RANGE:{scope:"number",begin:/\b[Uu]\+[0-9A-Fa-f][0-9A-Fa-f?]{0,5}(-[0-9A-Fa-f][0-9A-Fa-f]{0,5})?/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),Wn=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","optgroup","option","p","picture","q","quote","samp","section","select","source","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],qn=["defs","g","marker","mask","pattern","svg","switch","symbol","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feGaussianBlur","feImage","feMerge","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence","linearGradient","radialGradient","stop","circle","ellipse","image","line","path","polygon","polyline","rect","text","use","textPath","tspan","foreignObject","clipPath"],$n=[...Wn,...qn],Kn=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"].sort().reverse(),Yn=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"].sort().reverse(),Zn=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"].sort().reverse(),Qn=["accent-color","align-content","align-items","align-self","alignment-baseline","all","anchor-name","animation","animation-composition","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-range","animation-range-end","animation-range-start","animation-timeline","animation-timing-function","appearance","aspect-ratio","backdrop-filter","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-position-x","background-position-y","background-repeat","background-size","baseline-shift","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-end-end-radius","border-end-start-radius","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-start-end-radius","border-start-start-radius","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-align","box-decoration-break","box-direction","box-flex","box-flex-group","box-lines","box-ordinal-group","box-orient","box-pack","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","color-scheme","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","contain-intrinsic-block-size","contain-intrinsic-height","contain-intrinsic-inline-size","contain-intrinsic-size","contain-intrinsic-width","container","container-name","container-type","content","content-visibility","corner-bottom-left-shape","corner-bottom-right-shape","corner-shape","corner-top-left-shape","corner-top-right-shape","counter-increment","counter-reset","counter-set","cue","cue-after","cue-before","cursor","cx","cy","direction","display","dominant-baseline","empty-cells","enable-background","field-sizing","fill","fill-opacity","fill-rule","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flood-color","flood-opacity","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-optical-sizing","font-palette","font-size","font-size-adjust","font-smooth","font-smoothing","font-stretch","font-style","font-synthesis","font-synthesis-position","font-synthesis-small-caps","font-synthesis-style","font-synthesis-weight","font-variant","font-variant-alternates","font-variant-caps","font-variant-east-asian","font-variant-emoji","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","forced-color-adjust","gap","glyph-orientation-horizontal","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphenate-character","hyphenate-limit-chars","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","initial-letter","initial-letter-align","inline-size","inset","inset-area","inset-block","inset-block-end","inset-block-start","inset-inline","inset-inline-end","inset-inline-start","isolation","justify-content","justify-items","justify-self","kerning","left","letter-spacing","lighting-color","line-break","line-height","line-height-step","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","margin-trim","marker","marker-end","marker-mid","marker-start","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","masonry-auto-flow","math-depth","math-shift","math-style","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","offset","offset-anchor","offset-distance","offset-path","offset-position","offset-rotate","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-anchor","overflow-block","overflow-clip-margin","overflow-inline","overflow-wrap","overflow-x","overflow-y","overlay","overscroll-behavior","overscroll-behavior-block","overscroll-behavior-inline","overscroll-behavior-x","overscroll-behavior-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page","page-break-after","page-break-before","page-break-inside","paint-order","pause","pause-after","pause-before","perspective","perspective-origin","place-content","place-items","place-self","pointer-events","position","position-anchor","position-visibility","print-color-adjust","quotes","r","resize","rest","rest-after","rest-before","right","rotate","row-gap","ruby-align","ruby-position","scale","scroll-behavior","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scroll-timeline","scroll-timeline-axis","scroll-timeline-name","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","shape-rendering","speak","speak-as","src","stop-color","stop-opacity","stroke","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","tab-size","table-layout","text-align","text-align-all","text-align-last","text-anchor","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-skip","text-decoration-skip-ink","text-decoration-style","text-decoration-thickness","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-size-adjust","text-transform","text-underline-offset","text-underline-position","text-wrap","text-wrap-mode","text-wrap-style","timeline-scope","top","touch-action","transform","transform-box","transform-origin","transform-style","transition","transition-behavior","transition-delay","transition-duration","transition-property","transition-timing-function","translate","unicode-bidi","unicode-range","user-modify","user-select","vector-effect","vertical-align","view-timeline","view-timeline-axis","view-timeline-inset","view-timeline-name","view-transition-name","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","white-space-collapse","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","x","y","z-index","zoom"].sort().reverse();function Ot(e){let t=e.regex,n=Hn(e),i={begin:/-(webkit|moz|ms|o)-(?=[a-z])/},m="and or not only",g=/@-?\w[\w]*(-\w+)*/,a="[a-zA-Z-][a-zA-Z0-9_-]*",o=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE];return {name:"CSS",case_insensitive:true,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"},classNameAliases:{keyframePosition:"selector-tag"},contains:[n.BLOCK_COMMENT,i,n.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0},{className:"selector-class",begin:"\\."+a,relevance:0},n.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{begin:":("+Yn.join("|")+")"},{begin:":(:)?("+Zn.join("|")+")"}]},n.CSS_VARIABLE,{className:"attribute",begin:"\\b("+Qn.join("|")+")\\b"},{begin:/:/,end:/[;}{]/,contains:[n.BLOCK_COMMENT,n.HEXCOLOR,n.IMPORTANT,n.CSS_NUMBER_MODE,n.UNICODE_RANGE,...o,{begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri"},contains:[...o,{className:"string",begin:/[^)]/,endsWithParent:true,excludeEnd:true}]},n.FUNCTION_DISPATCH]},{begin:t.lookahead(/@/),end:"[{;]",relevance:0,illegal:/:/,contains:[{className:"keyword",begin:g},{begin:/\s/,endsWithParent:true,excludeEnd:true,relevance:0,keywords:{$pattern:/[a-z-]+/,keyword:m,attribute:Kn.join(" ")},contains:[{begin:/[a-z-]+(?=:)/,className:"attribute"},...o,n.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"\\b("+$n.join("|")+")\\b"}]}}var Xn="([-+]?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)|NaN|[-+]?Infinity",Jn={scope:"number",match:Xn,relevance:0};function Dt(e){let t={className:"attr",begin:/(("(\\.|[^\\"\r\n])*")|('(\\.|[^\\'\r\n])*'))(?=\s*:)/,relevance:1.01},n={match:/[{}[\],:]/,className:"punctuation",relevance:0},i=["true","false","null"],m={scope:"literal",beginKeywords:i.join(" ")};return {name:"JSON",aliases:["jsonc","json5"],keywords:{literal:i},contains:[t,n,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,m,Jn,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],illegal:"\\S"}}var Ce="[A-Za-z$_][0-9A-Za-z$_]*",Pt=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends","using"],Lt=["true","false","null","undefined","NaN","Infinity"],Ft=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],Bt=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],Ut=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],jt=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","self","global"],zt=[].concat(Ut,Ft,Bt);function ei(e){let t=e.regex,n=(d,{after:h})=>{let C="</"+d[0].slice(1);return d.input.indexOf(C,h)!==-1},i=Ce,m={begin:"<>",end:"</>"},g=/<[A-Za-z0-9\\._:-]+\s*\/>/,a={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(d,h)=>{let C=d[0].length+d.index,w=d.input[C];if(w==="<"||w===","){h.ignoreMatch();return}w===">"&&(n(d,{after:C})||h.ignoreMatch());let N,Y=d.input.substring(C);if(N=Y.match(/^\s*=/)){h.ignoreMatch();return}if((N=Y.match(/^\s+extends\s+/))&&N.index===0){h.ignoreMatch();return}}},o={$pattern:Ce,keyword:Pt,literal:Lt,built_in:zt,"variable.language":jt},c="[0-9](_?[0-9])*",b=`\\.(${c})`,v="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",M={className:"number",variants:[{begin:`(\\b(${v})((${b})|\\.)?|(${b}))[eE][+-]?(${c})\\b`},{begin:`\\b(${v})\\b((${b})\\b|\\.)?|(${b})\\b`},{begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{begin:"\\b0[0-7]+n?\\b"}],relevance:0},A={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},O={begin:".?html`",end:"",starts:{end:"`",returnEnd:false,contains:[e.BACKSLASH_ESCAPE,A],subLanguage:"xml"}},G={begin:".?css`",end:"",starts:{end:"`",returnEnd:false,contains:[e.BACKSLASH_ESCAPE,A],subLanguage:"css"}},k={begin:".?gql`",end:"",starts:{end:"`",returnEnd:false,contains:[e.BACKSLASH_ESCAPE,A],subLanguage:"graphql"}},V={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,A]},P={className:"comment",variants:[e.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{begin:"(?=@[A-Za-z]+)",relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"},{className:"type",begin:"\\{",end:"\\}",excludeEnd:true,excludeBegin:true,relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:true,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]},U=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,O,G,k,V,{match:/\$\d+/},M];A.contains=U.concat({begin:/\{/,end:/\}/,keywords:o,contains:["self"].concat(U)});let j=[].concat(P,A.contains),$=j.concat([{begin:/(\s*)\(/,end:/\)/,keywords:o,contains:["self"].concat(j)}]),K={className:"params",begin:/(\s*)\(/,end:/\)/,excludeBegin:true,excludeEnd:true,keywords:o,contains:$},xe={variants:[{match:[/class/,/\s+/,i,/\s+/,/extends/,/\s+/,t.concat(i,"(",t.concat(/\./,i),")*")],scope:{1:"keyword",3:"title.class",5:"keyword",7:"title.class.inherited"}},{match:[/class/,/\s+/,i],scope:{1:"keyword",3:"title.class"}}]},D={relevance:0,match:t.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),className:"title.class",keywords:{_:[...Ft,...Bt]}},pe={label:"use_strict",className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},de={variants:[{match:[/function/,/\s+/,i,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],className:{1:"keyword",3:"title.function"},label:"func.def",contains:[K],illegal:/%/},we={relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:"variable.constant"};function Ee(d){return t.concat("(?!",d.join("|"),")")}let Ae={match:t.concat(/\b/,Ee([...Ut,"super","import","await"].map(d=>`${d}\\s*\\(`)),i,t.lookahead(/\s*\(/)),className:"title.function",relevance:0},te={begin:t.concat(/\./,t.lookahead(t.concat(i,/(?![0-9A-Za-z$_(])/))),end:i,excludeBegin:true,keywords:"prototype",className:"property",relevance:0},ke={match:[/get|set/,/\s+/,i,/(?=\()/],className:{1:"keyword",3:"title.function"},contains:[{begin:/\(\)/},K]},r="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+e.UNDERSCORE_IDENT_RE+")\\s*=>",p={match:[/const|var|let/,/\s+/,i,/\s*/,/=\s*/,/(async\s*)?/,t.lookahead(r)],keywords:"async",className:{1:"keyword",3:"title.function"},contains:[K]};return {name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,exports:{PARAMS_CONTAINS:$,CLASS_REFERENCE:D},illegal:/#(?![$_A-Za-z])/,contains:[e.SHEBANG({label:"shebang",binary:"node",relevance:5}),pe,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,O,G,k,V,P,{match:/\$\d+/},M,D,{scope:"attr",match:i+t.lookahead(":"),relevance:0},p,{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",relevance:0,contains:[P,e.REGEXP_MODE,{className:"function",begin:r,returnBegin:true,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:true},{begin:/(\s*)\(/,end:/\)/,excludeBegin:true,excludeEnd:true,keywords:o,contains:$}]}]},{begin:/,/,relevance:0},{match:/\s+/,relevance:0},{variants:[{begin:m.begin,end:m.end},{match:g},{begin:a.begin,"on:begin":a.isTrulyOpeningTag,end:a.end}],subLanguage:"xml",contains:[{begin:a.begin,end:a.end,skip:true,contains:["self"]}]}]},de,{beginKeywords:"while if switch catch for"},{begin:"\\b(?!function)"+e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:true,label:"func.def",contains:[K,e.inherit(e.TITLE_MODE,{begin:i,className:"title.function"})]},{match:/\.\.\./,relevance:0},te,{match:"\\$"+i,relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},contains:[K]},Ae,we,xe,ke,{match:/\$[(.]/}]}}function ce(e){let t=e.regex,n=ei(e),i=Ce,m=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],g={begin:[/namespace/,/\s+/,e.IDENT_RE],beginScope:{1:"keyword",3:"title.class"}},a={beginKeywords:"interface",end:/\{/,excludeEnd:true,keywords:{keyword:"interface extends",built_in:m},contains:[n.exports.CLASS_REFERENCE]},o={className:"meta",relevance:10,begin:/^\s*['"]use strict['"]/},c=["type","interface","public","private","protected","implements","declare","abstract","readonly","enum","override","satisfies"],b={$pattern:Ce,keyword:Pt.concat(c),literal:Lt,built_in:zt.concat(m),"variable.language":jt},v={className:"meta",begin:"@"+i},M=(k,V,ee)=>{let P=k.contains.findIndex(U=>U.label===V);if(P===-1)throw new Error("can not find mode to replace");k.contains.splice(P,1,ee);};Object.assign(n.keywords,b),n.exports.PARAMS_CONTAINS.push(v);let A=n.contains.find(k=>k.scope==="attr"),O=Object.assign({},A,{match:t.concat(i,t.lookahead(/\s*\?:/))});n.exports.PARAMS_CONTAINS.push([n.exports.CLASS_REFERENCE,A,O]),n.contains=n.contains.concat([v,g,a,O]),M(n,"shebang",e.SHEBANG()),M(n,"use_strict",o);let G=n.contains.find(k=>k.label==="func.def");return G.relevance=0,Object.assign(n,{name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),n}function Ge(e){let t=e.regex,n=t.concat(/[\p{L}_]/u,t.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),i=/[\p{L}0-9._:-]+/u,m={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},g={begin:/\s/,contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},a=e.inherit(g,{begin:/\(/,end:/\)/}),o=e.inherit(e.APOS_STRING_MODE,{className:"string"}),c=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),b={endsWithParent:true,illegal:/</,relevance:0,contains:[{className:"attr",begin:i,relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:true,variants:[{begin:/"/,end:/"/,contains:[m]},{begin:/'/,end:/'/,contains:[m]},{begin:/[^\s"'=<>`]+/}]}]}]};return {name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:true,unicodeRegex:true,contains:[{className:"meta",begin:/<![a-z]/,end:/>/,relevance:10,contains:[g,c,o,a,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin:/<![a-z]/,end:/>/,contains:[g,a,c,o]}]}]},e.COMMENT(/<!--/,/-->/,{relevance:10}),{begin:/<!\[CDATA\[/,end:/\]\]>/,relevance:10},m,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/,relevance:10,contains:[c]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag",begin:/<style(?=\s|>)/,end:/>/,keywords:{name:"style"},contains:[b],starts:{end:/<\/style>/,returnEnd:true,subLanguage:"css"}},{className:"tag",begin:/<script(?=\s|>)/,end:/>/,keywords:{name:"script"},contains:[b],starts:{end:/<\/script>/,returnEnd:true,subLanguage:"javascript"}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:t.concat(/</,t.lookahead(t.concat(n,t.either(/\/>/,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:n,relevance:0,starts:b}]},{className:"tag",begin:t.concat(/<\//,t.lookahead(t.concat(n,/>/))),contains:[{className:"name",begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:true}]}]}}R.registerLanguage("typescript",ce);R.registerLanguage("ts",ce);R.registerLanguage("javascript",ce);R.registerLanguage("js",ce);R.registerLanguage("xml",Ge);R.registerLanguage("html",Ge);R.registerLanguage("css",Ot);R.registerLanguage("bash",ze);R.registerLanguage("shell",ze);R.registerLanguage("json",Dt);function Gt(e,t="typescript"){try{return R.highlight(e,{language:t,ignoreIllegals:!0}).value}catch{return ti(e)}}function ti(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function ni(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function ii(e){return e.split(/(<[^>]+>)/g).map(t=>{if(t.startsWith("<"))return t;let n=t;for(let{id:i,match:m}of o){let g=new RegExp(`\\b${ni(m)}\\b`,"g");n=n.replace(g,`<a href="/lecture#${i}" class="pg-glossary-link">${m}</a>`);}return n}).join("")}function Vt(e,t="typescript"){return ii(Gt(e,t))}var ri=["*"];function oi(e,t){if(e&1&&(ei$1(0,"span",5),MI(1),gc()),e&2){let n=XE();Qy(),Ic(" ",n.badge()," ");}}function ai(e,t){if(e&1&&(ei$1(0,"p",6),MI(1),gc()),e&2){let n=XE();Qy(),Ic(" ",n.hint()," ");}}function si(e,t){if(e&1){let n=GE();ei$1(0,"button",10),tp("click",function(){Gl(n);let m=XE();return zl(m.toggleCode())}),Zf(1,"africanies-icon",11),MI(2),gc();}if(e&2){let n=XE();zf("aria-expanded",n.codeOpen()),Qy(),Qf("name",n.codeOpen()?"close":"code")("size",14),Qy(),Ic(" ",n.codeOpen()?"Hide code":"Show code"," ");}}function li(e,t){if(e&1&&(ei$1(0,"p",8),MI(1),gc()),e&2){let n=XE();Qy(),Ic(" ",n.subtext()," ");}}function ci(e,t){if(e&1&&(ei$1(0,"div",9)(1,"div",12)(2,"span",13),MI(3," Implementation "),ei$1(4,"span",14),MI(5,"TypeScript"),gc()(),Zf(6,"africanies-copy",15),gc(),ei$1(7,"pre",16),Zf(8,"code",17),gc()()),e&2){let n=XE();Qy(6),Qf("value",n.code()),Qy(2),Qf("innerHTML",n.highlightedCode(),oy);}}var Ht=class e{sanitizer=T(Eu);router=T(pt);title=TP.required();anchorId=TP(null);hint=TP(null);subtext=TP(null);badge=TP(null);muted=TP(false,{transform:RP});code=TP(null);codeOpen=Co$1(false);hasCode=QI(()=>{let t=this.code();return t!=null&&t.trim().length>0});highlightedCode=QI(()=>{let t=this.code()??"",n=Vt(t,"typescript");return this.sanitizer.bypassSecurityTrustHtml(n)});toggleCode(){this.codeOpen.update(t=>!t);}onGlossaryLinkClick(t){let n=t.target.closest("a.pg-glossary-link");if(!(n instanceof HTMLAnchorElement))return;t.preventDefault();let i=new URL(n.href,window.location.origin);this.router.navigate([i.pathname],{fragment:i.hash.replace(/^#/,"")||void 0});}static \u0275fac=function(n){return new(n||e)};static \u0275cmp=IE({type:e,selectors:[["app-demo-section"]],hostBindings:function(n,i){n&1&&tp("click",function(g){return i.onGlossaryLinkClick(g)});},inputs:{title:[1,"title"],anchorId:[1,"anchorId"],hint:[1,"hint"],subtext:[1,"subtext"],badge:[1,"badge"],muted:[1,"muted"],code:[1,"code"]},ngContentSelectors:ri,decls:13,vars:11,consts:[[1,"flex","flex-col","gap-4"],[1,"flex","flex-col","gap-2","sm:flex-row","sm:items-start","sm:justify-between"],[1,"flex","min-w-0","flex-col","gap-1"],[1,"flex","flex-wrap","items-center","gap-2"],[1,"m-0","text-heading-3","text-ink","dark:text-white"],[1,"rounded-md","bg-border","px-2","py-0.5","text-caption","text-neutral-600","dark:bg-white/10","dark:text-neutral-400"],[1,"m-0","text-body-sm","text-neutral-600","dark:text-neutral-400"],["africanies-button","","type","button","variant","ghost","size","sm",1,"shrink-0","self-start"],[1,"m-0","text-caption","text-neutral-500","dark:text-neutral-400"],[1,"overflow-hidden","rounded-xl","border","border-border","bg-[#1e1e1e]","text-white","dark:border-white/10"],["africanies-button","","type","button","variant","ghost","size","sm",1,"shrink-0","self-start",3,"click"],[3,"name","size"],[1,"flex","items-center","justify-between","gap-2","border-b","border-white/10","px-4","py-2"],[1,"text-caption","font-medium","uppercase","tracking-wide","text-white/50"],[1,"ml-2","normal-case","tracking-normal","text-white/35"],["label","Copy","buttonClass","!text-white/80 hover:!bg-white/10 hover:!text-white",3,"value"],[1,"pg-code-block","m-0","max-h-[min(70vh,40rem)]","overflow-auto","p-4","font-mono","text-caption","leading-relaxed"],[1,"hljs","language-typescript",3,"innerHTML"]],template:function(n,i){n&1&&(tI(),ei$1(0,"section",0)(1,"div",1)(2,"div",2)(3,"div",3)(4,"h2",4),MI(5),gc(),LE(6,oi,2,1,"span",5),gc(),LE(7,ai,2,1,"p",6),gc(),LE(8,si,3,4,"button",7),gc(),ei$1(9,"div"),nI(10),gc(),LE(11,li,2,1,"p",8),LE(12,ci,9,2,"div",9),gc()),n&2&&(lp("scroll-mt-24",i.anchorId()!==null),zf("id",i.anchorId()||null),Qy(5),gp(i.title()),Qy(),PE(i.badge()?6:-1),Qy(),PE(i.hint()?7:-1),Qy(),PE(i.hasCode()?8:-1),Qy(),mI(i.muted()?"pg-demo-muted":"pg-demo"),Qy(2),PE(i.subtext()?11:-1),Qy(),PE(i.hasCode()&&i.codeOpen()?12:-1));},dependencies:[Z,g,br$1],encapsulation:2})};var Bi=`// Default ellipsis trigger \u2014 good for table rows.
4
+ // Put side effects on each item's onClick; the menu closes after it runs.
5
+ // Panel renders in a CDK overlay so it won't get clipped by overflow:hidden.
6
+
7
+ import { Component, signal } from '@angular/core';
8
+ import { ActionMenuComponent, type AfricaniesMenuItem } from '@africanies/africanies-ui';
9
+
10
+ @Component({
11
+ selector: 'app-shipment-row-actions-demo',
12
+ standalone: true,
13
+ imports: [ActionMenuComponent],
14
+ template: \`
15
+ <div class="flex flex-wrap items-center gap-4">
16
+ <africanies-action-menu [items]="items" ariaLabel="Shipment actions" />
17
+
18
+ <p class="m-0 text-body-sm">
19
+ Last action:
20
+ <span class="font-medium">{{ lastAction() ?? '\u2014' }}</span>
21
+ </p>
22
+ </div>
23
+ \`,
24
+ })
25
+ export class ShipmentRowActionsDemoComponent {
26
+ protected readonly lastAction = signal<string | null>(null);
27
+
28
+ protected readonly items: AfricaniesMenuItem[] = [
29
+ {
30
+ label: 'Open',
31
+ icon: 'eye',
32
+ onClick: () => this.lastAction.set('Open'),
33
+ },
34
+ {
35
+ label: 'Edit',
36
+ icon: 'edit',
37
+ onClick: () => this.lastAction.set('Edit'),
38
+ },
39
+ {
40
+ label: 'Copy reference',
41
+ icon: 'copy',
42
+ onClick: () => this.lastAction.set('Copy reference'),
43
+ },
44
+ {
45
+ label: 'Delete',
46
+ icon: 'trash',
47
+ danger: true,
48
+ dividerBefore: true,
49
+ onClick: () => this.lastAction.set('Delete'),
50
+ },
51
+ ];
52
+ }
53
+ `,Ui=`// Swap the ellipsis for your own trigger \u2014 project it inside and add africaniesActionMenuTrigger.
54
+
55
+ import { Component } from '@angular/core';
56
+ import {
57
+ ActionMenuComponent,
58
+ ActionMenuTriggerDirective,
59
+ ButtonComponent,
60
+ type AfricaniesMenuItem,
61
+ } from '@africanies/africanies-ui';
62
+
63
+ @Component({
64
+ selector: 'app-shipment-toolbar-actions',
65
+ standalone: true,
66
+ imports: [ActionMenuComponent, ActionMenuTriggerDirective, ButtonComponent],
67
+ template: \`
68
+ <africanies-action-menu [items]="items" ariaLabel="Shipment actions">
69
+ <button
70
+ type="button"
71
+ africanies-button
72
+ africaniesActionMenuTrigger
73
+ variant="secondary"
74
+ size="sm"
75
+ >
76
+ Actions
77
+ </button>
78
+ </africanies-action-menu>
79
+ \`,
80
+ })
81
+ export class ShipmentToolbarActionsComponent {
82
+ protected readonly items: AfricaniesMenuItem[] = [
83
+ {
84
+ label: 'Open',
85
+ icon: 'eye',
86
+ onClick: () => this.open(),
87
+ },
88
+ {
89
+ label: 'Edit',
90
+ icon: 'edit',
91
+ onClick: () => this.edit(),
92
+ },
93
+ {
94
+ label: 'Delete',
95
+ icon: 'trash',
96
+ danger: true,
97
+ dividerBefore: true,
98
+ onClick: () => this.delete(),
99
+ },
100
+ ];
101
+
102
+ private open(): void {
103
+ /* navigate or open drawer */
104
+ }
105
+
106
+ private edit(): void {
107
+ /* \u2026 */
108
+ }
109
+
110
+ private delete(): void {
111
+ /* \u2026 */
112
+ }
113
+ }
114
+ `,ji=`// Item flags: danger for destructive, disabled when unavailable, dividerBefore to separate groups.
115
+
116
+ import { Component } from '@angular/core';
117
+ import { ActionMenuComponent, type AfricaniesMenuItem } from '@africanies/africanies-ui';
118
+
119
+ @Component({
120
+ selector: 'app-shipment-action-variants',
121
+ standalone: true,
122
+ imports: [ActionMenuComponent],
123
+ template: \`
124
+ <africanies-action-menu [items]="items" />
125
+ \`,
126
+ })
127
+ export class ShipmentActionVariantsComponent {
128
+ protected readonly items: AfricaniesMenuItem[] = [
129
+ {
130
+ label: 'Download PDF',
131
+ icon: 'download',
132
+ onClick: () => this.download(),
133
+ },
134
+ {
135
+ label: 'Archive',
136
+ disabled: true,
137
+ onClick: () => undefined,
138
+ },
139
+ {
140
+ label: 'Void shipment',
141
+ icon: 'trash',
142
+ danger: true,
143
+ dividerBefore: true,
144
+ onClick: () => this.voidShipment(),
145
+ },
146
+ ];
147
+
148
+ private download(): void {
149
+ /* \u2026 */
150
+ }
151
+
152
+ private voidShipment(): void {
153
+ /* \u2026 */
154
+ }
155
+ }
156
+ `,zi=`// [disabled] locks the whole menu (won't open). Prefer this over an empty items array.
157
+
158
+ import { Component, input } from '@angular/core';
159
+ import { ActionMenuComponent, type AfricaniesMenuItem } from '@africanies/africanies-ui';
160
+
161
+ @Component({
162
+ selector: 'app-shipment-action-menu-disabled',
163
+ standalone: true,
164
+ imports: [ActionMenuComponent],
165
+ template: \`
166
+ <africanies-action-menu [items]="items" [disabled]="menuDisabled()" />
167
+ \`,
168
+ })
169
+ export class ShipmentActionMenuDisabledComponent {
170
+ // Parent passes true when the row is locked or the user lacks edit rights.
171
+ readonly menuDisabled = input(false);
172
+
173
+ protected readonly items: AfricaniesMenuItem[] = [
174
+ {
175
+ label: 'Open',
176
+ icon: 'eye',
177
+ onClick: () => this.open(),
178
+ },
179
+ {
180
+ label: 'Edit',
181
+ icon: 'edit',
182
+ onClick: () => this.edit(),
183
+ },
184
+ {
185
+ label: 'Delete',
186
+ icon: 'trash',
187
+ danger: true,
188
+ dividerBefore: true,
189
+ onClick: () => this.delete(),
190
+ },
191
+ ];
192
+
193
+ private open(): void {
194
+ /* not reachable while [disabled]="true" */
195
+ }
196
+
197
+ private edit(): void {
198
+ /* \u2026 */
199
+ }
200
+
201
+ private delete(): void {
202
+ /* \u2026 */
203
+ }
204
+ }
205
+ `;var Vi=`
206
+ // Inline banners for info / success / warning / danger.
207
+ // Parent owns visibility. Not a toast, and not for full-page fetch failures.
208
+
209
+ import { Component, signal } from '@angular/core';
210
+ import { AlertComponent, ButtonComponent, type AlertVariant } from '@africanies/africanies-ui';
211
+
212
+ @Component({
213
+ selector: 'app-alert-demo',
214
+ standalone: true,
215
+ imports: [AlertComponent, ButtonComponent],
216
+ template: \`
217
+ <div class="flex flex-col gap-3">
218
+ @for (v of variants; track v) {
219
+ <africanies-alert
220
+ [variant]="v"
221
+ [title]="titles[v]"
222
+ [message]="messages[v]"
223
+ />
224
+ }
225
+ </div>
226
+ \`,
227
+ })
228
+ export class AlertDemo {
229
+ readonly variants: AlertVariant[] = ['info', 'success', 'warning', 'danger'];
230
+
231
+ readonly titles: Record<AlertVariant, string> = {
232
+ info: 'Document checklist',
233
+ success: 'Submitted',
234
+ warning: 'Rates may be stale',
235
+ danger: 'Cannot release',
236
+ };
237
+
238
+ readonly messages: Record<AlertVariant, string> = {
239
+ info: 'Commercial invoice and packing list are still required.',
240
+ success: 'Shipment SFN-1042 was submitted to the carrier.',
241
+ warning: 'Last rate pull was more than 4 hours ago.',
242
+ danger: 'Customs hold blocks release until documents are approved.',
243
+ };
244
+ }
245
+ `,Hi=`
246
+ // Dismissible by default \u2014 listen to (dismissed) and unmount with @if.
247
+ // Set [dismissible]="false" when the notice has to stay.
248
+
249
+ import { Component, signal } from '@angular/core';
250
+ import { AlertComponent, ButtonComponent } from '@africanies/africanies-ui';
251
+
252
+ @Component({
253
+ selector: 'app-alert-dismiss-demo',
254
+ standalone: true,
255
+ imports: [AlertComponent, ButtonComponent],
256
+ template: \`
257
+ @if (showBanner()) {
258
+ <africanies-alert
259
+ variant="warning"
260
+ title="Rates outdated"
261
+ message="Refresh to pull the latest carrier rates before quoting."
262
+ (dismissed)="showBanner.set(false)"
263
+ >
264
+ <button
265
+ africanies-button
266
+ type="button"
267
+ size="sm"
268
+ class="mt-2"
269
+ (click)="refresh()"
270
+ >
271
+ Refresh rates
272
+ </button>
273
+ </africanies-alert>
274
+ } @else {
275
+ <button africanies-button type="button" variant="secondary" size="sm" (click)="showBanner.set(true)">
276
+ Show banner again
277
+ </button>
278
+ }
279
+
280
+ <!-- Permanent notice \u2014 no close control -->
281
+ <africanies-alert
282
+ class="mt-4 block"
283
+ variant="info"
284
+ message="This environment is connected to the staging API."
285
+ [dismissible]="false"
286
+ />
287
+ \`,
288
+ })
289
+ export class AlertDismissDemo {
290
+ readonly showBanner = signal(true);
291
+
292
+ refresh(): void {
293
+ // refetch rates\u2026
294
+ this.showBanner.set(false);
295
+ }
296
+ }
297
+ `;var qi=`// Domain services in @africanies/africanies-core own paths and mapping.
298
+ // Apps call inject(CountryService) / CurrencyService / PaymentMethodService / ProductService / \u2026 \u2014 or ApiClient for custom routes.
299
+ //
300
+ // List GET convention (ResourceId) \u2014 built-in services AND custom calls:
301
+ // null \u2192 paginated page GET {base} (+ page/size/order)
302
+ // 'all' \u2192 full dump GET {base}/all
303
+ // 42 \u2192 single record GET {base}/42
304
+ //
305
+ // Custom endpoints \u2014 IDE helpers on ApiClient:
306
+ // api.getResourcePage<T>('shipments', { page: 1 })
307
+ // api.getResourceAll<T>('shipments')
308
+ // api.getResourceById<T>('shipments', 42)
309
+ // // or overloads: getResource(base, null | 'all' | number, query?, options?)
310
+ // // Per-request mode override (tab mode unchanged):
311
+ // api.post('/claim', body, { shippingMode: 'stn' })
312
+ // // path helper: buildResourcePath('/my/read', id)
313
+
314
+ import { ApplicationConfig } from '@angular/core';
315
+ import {
316
+ provideAfricaniesSdk,
317
+ provideAfricaniesHttpClient,
318
+ } from '@africanies/africanies-core';
319
+
320
+ export const appConfig: ApplicationConfig = {
321
+ providers: [
322
+ provideAfricaniesSdk({
323
+ baseUrl: 'https://test-api-export.africaniestest.com/api',
324
+ // loadModeConfig: true by default \u2192 GET /public/mode/config on startup
325
+ }),
326
+ provideAfricaniesHttpClient(),
327
+ ],
328
+ };
329
+ `,$i=`// GET /public/country/read/{id?} \u2014 ResourceId: null | 'all' | number.
330
+ // Flag images: countryFlagUrl(iso2) or mapCountrySelectOptions(rows) \u2192 prefixImageUrl
331
+
332
+ import { Component, inject, signal } from '@angular/core';
333
+ import { CountryService, mapCountrySelectOptions } from '@africanies/africanies-core';
334
+ import type { SelectOption } from '@africanies/africanies-ui';
335
+ import { firstValueFrom } from 'rxjs';
336
+
337
+ @Component({
338
+ selector: 'app-country-loader',
339
+ standalone: true,
340
+ template: \`
341
+ <africanies-select
342
+ label="Destination country"
343
+ [options]="countryOptions()"
344
+ searchable
345
+ />
346
+ \`,
347
+ })
348
+ export class CountryLoaderComponent {
349
+ private readonly countriesApi = inject(CountryService);
350
+ protected readonly countryOptions = signal<SelectOption<number>[]>([]);
351
+
352
+ async ngOnInit(): Promise<void> {
353
+ const res = await firstValueFrom(this.countriesApi.readAll());
354
+ if (res.success && res.data) {
355
+ this.countryOptions.set(
356
+ mapCountrySelectOptions(res.data, { width: 40 }).map((row) => ({
357
+ label: row.label,
358
+ value: row.value,
359
+ prefixImageUrl: row.prefixImageUrl,
360
+ })),
361
+ );
362
+ }
363
+ }
364
+ }
365
+ `,Ki=`// GET /public/service/read/{id?} \u2014 ResourceId: null | 'all' | number.
366
+ // Public catalog \u2014 no auth required. App Settings Services / Plans boards.
367
+ // readPage({ page, order, search, size, from, to }) \u2192 ServiceModel[]
368
+
369
+ import { Component, inject, signal } from '@angular/core';
370
+ import { ServiceService } from '@africanies/africanies-core';
371
+ import type { ServiceModel } from '@africanies/africanies-models';
372
+ import { firstValueFrom } from 'rxjs';
373
+
374
+ @Component({
375
+ selector: 'app-service-loader',
376
+ standalone: true,
377
+ template: \`
378
+ <p>{{ services().length }} services</p>
379
+ @if (services()[0]; as first) {
380
+ <p>{{ first.name }}</p>
381
+ }
382
+ \`,
383
+ })
384
+ export class ServiceLoaderComponent {
385
+ private readonly servicesApi = inject(ServiceService);
386
+ protected readonly services = signal<ServiceModel[]>([]);
387
+
388
+ async ngOnInit(): Promise<void> {
389
+ const res = await firstValueFrom(
390
+ this.servicesApi.readPage({ page: 1, order: 'desc' }),
391
+ );
392
+ if (res.success && res.data) {
393
+ this.services.set(res.data);
394
+ }
395
+ }
396
+ }
397
+ `,Yi=`// GET /public/document/read/{id?} \u2014 catalog preview by document id.
398
+ // readById(n) \u2192 bind doc.file_ref.base_64 in <img> / PDF viewer.
399
+ // For raw file_ref on shipments/KYC/waybills \u2192 FileService.read(ref).
400
+
401
+ import { Component, inject, signal } from '@angular/core';
402
+ import { DocumentService } from '@africanies/africanies-core';
403
+ import type { DocumentModel } from '@africanies/africanies-models';
404
+ import { firstValueFrom } from 'rxjs';
405
+
406
+ @Component({
407
+ selector: 'app-document-loader',
408
+ standalone: true,
409
+ template: \`
410
+ @if (doc(); as d) {
411
+ <p>{{ d.name }}</p>
412
+ @if (d.file_ref?.base_64) {
413
+ <img [src]="d.file_ref.base_64" alt="" />
414
+ }
415
+ }
416
+ \`,
417
+ })
418
+ export class DocumentLoaderComponent {
419
+ private readonly documentsApi = inject(DocumentService);
420
+ protected readonly doc = signal<DocumentModel | null>(null);
421
+
422
+ async load(id: number): Promise<void> {
423
+ const res = await firstValueFrom(this.documentsApi.readById(id));
424
+ if (res.success && res.data) {
425
+ this.doc.set(res.data);
426
+ }
427
+ }
428
+ }
429
+ `,Zi=`// GET /public/plan/read/{id?} \u2014 ResourceId: null | 'all' | number.
430
+ // Public catalog \u2014 no auth required. App Settings Plans / Plan Packages.
431
+ // readPage({ page, order, search, size, from, to }) \u2192 PlanModel[] (nested packages)
432
+
433
+ import { Component, inject, signal } from '@angular/core';
434
+ import { PlanService } from '@africanies/africanies-core';
435
+ import type { PlanModel } from '@africanies/africanies-models';
436
+ import { firstValueFrom } from 'rxjs';
437
+
438
+ @Component({
439
+ selector: 'app-plan-loader',
440
+ standalone: true,
441
+ template: \`
442
+ <p>{{ plans().length }} plans</p>
443
+ @if (plans()[0]; as first) {
444
+ <p>{{ first.name }} \u2014 {{ first.packages.length }} packages</p>
445
+ }
446
+ \`,
447
+ })
448
+ export class PlanLoaderComponent {
449
+ private readonly plansApi = inject(PlanService);
450
+ protected readonly plans = signal<PlanModel[]>([]);
451
+
452
+ async ngOnInit(): Promise<void> {
453
+ const res = await firstValueFrom(this.plansApi.readPage({ page: 1 }));
454
+ if (res.success && res.data) {
455
+ this.plans.set(res.data);
456
+ }
457
+ }
458
+ }
459
+ `,Qi=`// GET /currency/read/{id?} \u2014 ResourceId: null | 'all' | number.
460
+ // POST /currency/create \xB7 PUT /currency/update \xB7 DELETE /currency/delete ({ id } body).
461
+ // Laravel paginator in data is flattened to data[] + pagination.
462
+ // readPage({ page, order, search, from, to }) \u2192 CurrencyModel[]
463
+ // create / update serialize active + is_naira_greater to "1" / "0".
464
+ // After each write: show res.message, then readPage again.
465
+
466
+ import { Component, inject, signal } from '@angular/core';
467
+ import { CurrencyService, PaymentMethodService } from '@africanies/africanies-core';
468
+ import type { CurrencyModel, PaginationMetaModel } from '@africanies/africanies-models';
469
+ import { firstValueFrom } from 'rxjs';
470
+
471
+ @Component({
472
+ selector: 'app-currency-loader',
473
+ standalone: true,
474
+ template: \`
475
+ <p>{{ currencies().length }} currencies</p>
476
+ @if (currencies()[0]; as first) {
477
+ <p>{{ first.name }} ({{ first.short_code }})</p>
478
+ }
479
+ \`,
480
+ })
481
+ export class CurrencyLoaderComponent {
482
+ private readonly currenciesApi = inject(CurrencyService);
483
+ private readonly paymentMethodsApi = inject(PaymentMethodService);
484
+ protected readonly currencies = signal<CurrencyModel[]>([]);
485
+ protected readonly meta = signal<PaginationMetaModel | null>(null);
486
+
487
+ async ngOnInit(): Promise<void> {
488
+ const methods = await firstValueFrom(
489
+ this.paymentMethodsApi.readPage({ page: 1 }),
490
+ );
491
+ const page = await firstValueFrom(
492
+ this.currenciesApi.readPage({
493
+ page: 1,
494
+ order: 'desc',
495
+ size: 15,
496
+ search: '',
497
+ from: '',
498
+ to: '',
499
+ }),
500
+ );
501
+ if (page.success && page.data) {
502
+ this.currencies.set(page.data);
503
+ this.meta.set(page.pagination);
504
+ }
505
+ void methods;
506
+ }
507
+
508
+ async saveUsd(methodIds: number[]): Promise<void> {
509
+ const res = await firstValueFrom(
510
+ this.currenciesApi.create({
511
+ name: 'United States Dollar',
512
+ short_code: 'USD',
513
+ multiplication_rate: '1600',
514
+ division_rate: '1400',
515
+ active: true,
516
+ is_naira_greater: false,
517
+ payment_method_ids: methodIds,
518
+ }),
519
+ );
520
+ if (res.success) {
521
+ // toast res.message, close modal, then readPage again
522
+ await firstValueFrom(
523
+ this.currenciesApi.readPage({ page: 1, order: 'desc', size: 15 }),
524
+ );
525
+ }
526
+ }
527
+ }
528
+ `,Xi=`// GET /payment_method/read/{id?} \u2014 ResourceId: null | 'all' | number.
529
+ // PUT /payment_method/update \u2014 active toggle only (resend name + model from the row).
530
+ // No create/delete on this board. After update: toast message + patch updated_at locally.
531
+ // Laravel paginator in data is flattened to data[] + pagination.
532
+ // readPage({ page, size, order, search, from, to }) \u2192 PaymentMethodModel[]
533
+ // readAll() \u2192 full PaymentMethodModel[]
534
+ // readById(4) \u2192 single PaymentMethodModel
535
+
536
+ import { Component, inject, signal } from '@angular/core';
537
+ import { PaymentMethodService } from '@africanies/africanies-core';
538
+ import type { PaginationMetaModel, PaymentMethodModel } from '@africanies/africanies-models';
539
+ import { firstValueFrom } from 'rxjs';
540
+
541
+ @Component({
542
+ selector: 'app-payment-method-loader',
543
+ standalone: true,
544
+ template: \`
545
+ <p>{{ methods().length }} payment methods</p>
546
+ @if (methods()[0]; as first) {
547
+ <p>{{ first.name }} \u2014 {{ first.currencies.length }} currencies</p>
548
+ }
549
+ \`,
550
+ })
551
+ export class PaymentMethodLoaderComponent {
552
+ private readonly methodsApi = inject(PaymentMethodService);
553
+ protected readonly methods = signal<PaymentMethodModel[]>([]);
554
+ protected readonly meta = signal<PaginationMetaModel | null>(null);
555
+
556
+ async ngOnInit(): Promise<void> {
557
+ const page = await firstValueFrom(
558
+ this.methodsApi.readPage({ page: 1, order: 'desc' }),
559
+ );
560
+ if (page.success && page.data) {
561
+ this.methods.set(page.data);
562
+ this.meta.set(page.pagination);
563
+ }
564
+ }
565
+
566
+ async toggleActive(row: PaymentMethodModel, next: boolean): Promise<void> {
567
+ const res = await firstValueFrom(
568
+ this.methodsApi.update({
569
+ id: row.id,
570
+ name: row.name,
571
+ model: row.model,
572
+ active: next,
573
+ }),
574
+ );
575
+ if (res.success) {
576
+ // toast res.message; patch row.active + row.updated_at in place \u2014 do not readPage()
577
+ this.methods.update((list) =>
578
+ list.map((m) =>
579
+ m.id === row.id
580
+ ? { ...m, active: next, updated_at: new Date().toISOString() }
581
+ : m,
582
+ ),
583
+ );
584
+ }
585
+ }
586
+ }
587
+ `,Ji=`// GET /public/mode/config \u2014 usually loaded at bootstrap via provideAfricaniesSdk.
588
+ // ModeConfigService hydrates storage; getRegionConfig(country) reads STN/SFN units.
589
+
590
+ import { Component, inject } from '@angular/core';
591
+ import { ModeConfigService } from '@africanies/africanies-core';
592
+
593
+ @Component({
594
+ selector: 'app-region-units',
595
+ standalone: true,
596
+ template: \`
597
+ @if (region(); as r) {
598
+ <p>{{ r.currency_symbol }} \xB7 {{ r.mass_unit }} \xB7 {{ r.dimension_unit }}</p>
599
+ }
600
+ \`,
601
+ })
602
+ export class RegionUnitsComponent {
603
+ private readonly modeConfig = inject(ModeConfigService);
604
+
605
+ region() {
606
+ return this.modeConfig.getRegionConfig('ng');
607
+ }
608
+
609
+ reload() {
610
+ this.modeConfig.loadConfig().subscribe();
611
+ }
612
+ }
613
+ `,er=`// GET /shipment_method/read/{id?} \u2014 ResourceId: null | 'all' | number.
614
+ // readPage() \u2192 paginated \xB7 readAll() \u2192 full \xB7 readById(n) \u2192 single ShipmentMethodModel
615
+
616
+ import { Component, inject, signal } from '@angular/core';
617
+ import { ShipmentMethodService } from '@africanies/africanies-core';
618
+ import type { ShipmentMethodModel } from '@africanies/africanies-models';
619
+ import { firstValueFrom } from 'rxjs';
620
+
621
+ @Component({
622
+ selector: 'app-carrier-loader',
623
+ standalone: true,
624
+ template: \`
625
+ <p>{{ methods().length }} methods</p>
626
+ @if (methods()[0]; as first) {
627
+ <p>{{ first.name }} \xB7 {{ first.mode }} \xB7 {{ first.zone_values.total }} zones</p>
628
+ }
629
+ \`,
630
+ })
631
+ export class CarrierLoaderComponent {
632
+ private readonly methodsApi = inject(ShipmentMethodService);
633
+ protected readonly methods = signal<ShipmentMethodModel[]>([]);
634
+
635
+ async ngOnInit(): Promise<void> {
636
+ const res = await firstValueFrom(this.methodsApi.readAll());
637
+ if (res.success && res.data) {
638
+ this.methods.set(res.data);
639
+ }
640
+ }
641
+ }
642
+ `,tr=`// GET /warehouse/read/{id?} \u2014 ResourceId: null | 'all' | number.
643
+ // readPage() \u2192 paginated \xB7 readAll() \u2192 full \xB7 readById(n) \u2192 single WarehouseModel
644
+
645
+ import { Component, inject, signal } from '@angular/core';
646
+ import { WarehouseService } from '@africanies/africanies-core';
647
+ import type { WarehouseModel } from '@africanies/africanies-models';
648
+ import { firstValueFrom } from 'rxjs';
649
+
650
+ @Component({
651
+ selector: 'app-warehouse-loader',
652
+ standalone: true,
653
+ template: \`
654
+ <p>{{ warehouses().length }} warehouses</p>
655
+ @if (warehouses()[0]; as first) {
656
+ <p>{{ first.name }} \xB7 {{ first.city }} \xB7 {{ first.country?.iso2 }}</p>
657
+ }
658
+ \`,
659
+ })
660
+ export class WarehouseLoaderComponent {
661
+ private readonly warehousesApi = inject(WarehouseService);
662
+ protected readonly warehouses = signal<WarehouseModel[]>([]);
663
+
664
+ async ngOnInit(): Promise<void> {
665
+ const res = await firstValueFrom(this.warehousesApi.readAll());
666
+ if (res.success && res.data) {
667
+ this.warehouses.set(res.data);
668
+ }
669
+ }
670
+ }
671
+ `,nr=`// GET /zone/read/records/{id?} \u2014 ResourceId: null | 'all' | number.
672
+ // readPage() \u2192 paginated \xB7 readAll() \u2192 full \xB7 readById(n) \u2192 single ZoneModel
673
+
674
+ import { Component, inject, signal } from '@angular/core';
675
+ import { ZoneService } from '@africanies/africanies-core';
676
+ import type { ZoneModel } from '@africanies/africanies-models';
677
+ import { firstValueFrom } from 'rxjs';
678
+
679
+ @Component({
680
+ selector: 'app-zone-loader',
681
+ standalone: true,
682
+ template: \`
683
+ <p>{{ zones().length }} zones</p>
684
+ @if (zones()[0]; as first) {
685
+ <p>{{ first.name }} \xB7 {{ first.type }}</p>
686
+ }
687
+ \`,
688
+ })
689
+ export class ZoneLoaderComponent {
690
+ private readonly zonesApi = inject(ZoneService);
691
+ protected readonly zones = signal<ZoneModel[]>([]);
692
+
693
+ async ngOnInit(): Promise<void> {
694
+ const res = await firstValueFrom(this.zonesApi.readAll());
695
+ if (res.success && res.data) {
696
+ this.zones.set(res.data);
697
+ }
698
+ }
699
+ }
700
+ `,ir=`// POST /auth/forgot/password \u2014 email-only, unauthenticated. Body { email }.
701
+ // The backend emails a reset link. This frontend never shows a new-password
702
+ // form for that link (no token in the route).
703
+ // /onboarding/reset-password is a different flow (default_password first login).
704
+ // Admins can call the same forgot(email) from user/partner screens.
705
+
706
+ import { Component, computed, inject, signal } from '@angular/core';
707
+ import { RouterLink } from '@angular/router';
708
+ import { AuthService, isValidEmail } from '@africanies/africanies-core';
709
+ import { ButtonComponent, TextInputComponent } from '@africanies/africanies-ui';
710
+ import { firstValueFrom } from 'rxjs';
711
+
712
+ @Component({
713
+ selector: 'app-forgot-password',
714
+ standalone: true,
715
+ imports: [ButtonComponent, TextInputComponent, RouterLink],
716
+ template: \`
717
+ <p>We will send you a password reset link.</p>
718
+ <africanies-text-input
719
+ type="email"
720
+ label="Email"
721
+ autocomplete="email"
722
+ [(value)]="email"
723
+ />
724
+ <button
725
+ africanies-button
726
+ type="button"
727
+ variant="primary"
728
+ [loading]="sending()"
729
+ [disabled]="!canSubmit()"
730
+ (click)="forgot()"
731
+ >
732
+ Send reset link
733
+ </button>
734
+ @if (status(); as copy) {
735
+ <p>{{ copy }}</p>
736
+ <a routerLink="/onboarding/login">Reset completed? Login here</a>
737
+ }
738
+ \`,
739
+ })
740
+ export class ForgotPasswordComponent {
741
+ private readonly authApi = inject(AuthService);
742
+
743
+ protected readonly email = signal('');
744
+ protected readonly sending = signal(false);
745
+ protected readonly status = signal<string | null>(null);
746
+ protected readonly canSubmit = computed(() => isValidEmail(this.email()));
747
+
748
+ protected async forgot(): Promise<void> {
749
+ if (!this.canSubmit()) {
750
+ return;
751
+ }
752
+ this.sending.set(true);
753
+ this.status.set(null);
754
+ try {
755
+ const res = await firstValueFrom(this.authApi.forgot(this.email()));
756
+ if (res.success) {
757
+ this.status.set(res.message?.trim() || 'We have emailed your reset password.');
758
+ } else {
759
+ this.status.set(res.message?.trim() || 'Could not send a reset email.');
760
+ }
761
+ } finally {
762
+ this.sending.set(false);
763
+ }
764
+ }
765
+ }
766
+ `,rr=`// GET /user \u2014 bare user object (no { success, data } wrapper).
767
+ // POST /user/change/password \u2014 first-login default password.
768
+ // POST /user/logout-from-all-sessions \u2014 while the token is still set, then clear().
769
+ // After login/register in your app:
770
+ // inject(AuthTokenService).set(access_token);
771
+ // UserService.me() then sends Authorization: Bearer \u2026
772
+
773
+ import { Component, inject, signal } from '@angular/core';
774
+ import { AuthTokenService, UserService } from '@africanies/africanies-core';
775
+ import type { UserModel } from '@africanies/africanies-models';
776
+ import { firstValueFrom } from 'rxjs';
777
+
778
+ @Component({
779
+ selector: 'app-profile',
780
+ standalone: true,
781
+ template: \`
782
+ @if (user(); as u) {
783
+ <p>{{ u.name }} \xB7 {{ u.email }} \xB7 {{ u.main_region }}</p>
784
+ }
785
+ \`,
786
+ })
787
+ export class ProfileComponent {
788
+ private readonly users = inject(UserService);
789
+ private readonly auth = inject(AuthTokenService);
790
+ protected readonly user = signal<UserModel | null>(null);
791
+
792
+ async ngOnInit(): Promise<void> {
793
+ // typically called right after login elsewhere:
794
+ // this.auth.set(loginResponse.access_token);
795
+ const res = await firstValueFrom(this.users.me());
796
+ if (res.success && res.data) {
797
+ this.user.set(res.data);
798
+ }
799
+ }
800
+
801
+ logout(): void {
802
+ this.users.logoutFromAllSessions().subscribe({
803
+ next: () => this.auth.clear(),
804
+ error: () => this.auth.clear(),
805
+ });
806
+ }
807
+ }
808
+ `,or=`// GET /user/notifications/read/{id?} \u2014 auth required (ResourceId + UUID by id).
809
+ // PUT /user/notifications/update \u2014 markRead(id) or markRead() for all
810
+ //
811
+ // Header badge: first page via readPage(). Drawer: infinite scroll via onNotificationLoadPage.
812
+
813
+ import { Component, inject, signal } from '@angular/core';
814
+ import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
815
+ import {
816
+ AuthTokenService,
817
+ mapNotificationInboxItem,
818
+ NotificationService,
819
+ } from '@africanies/africanies-core';
820
+ import type { NotificationModel } from '@africanies/africanies-models';
821
+ import { NOTIFICATION_PAGE_SIZE } from '@africanies/africanies-models';
822
+ import type { AfricaniesNotification, NotificationPageResult } from '@africanies/africanies-ui';
823
+ import { catchError, map, of, switchMap, tap } from 'rxjs';
824
+
825
+ @Component({
826
+ selector: 'app-header-notifications',
827
+ standalone: true,
828
+ template: \`
829
+ <africanies-app-shell-header
830
+ title="Dashboard"
831
+ [notifications]="notifications()"
832
+ [onNotificationLoadPage]="loadNotificationPage"
833
+ [onNotificationMarkRead]="markNotificationRead"
834
+ [onNotificationMarkAllRead]="markAllNotificationsRead"
835
+ />
836
+ \`,
837
+ })
838
+ export class HeaderNotificationsComponent {
839
+ private readonly auth = inject(AuthTokenService);
840
+ private readonly notificationsApi = inject(NotificationService);
841
+
842
+ protected readonly notifications = signal<AfricaniesNotification[]>([]);
843
+
844
+ protected loadNotificationPage = (page: number) =>
845
+ this.notificationsApi
846
+ .readPage({ page, size: NOTIFICATION_PAGE_SIZE, order: 'desc' })
847
+ .pipe(
848
+ map(
849
+ (res): NotificationPageResult => ({
850
+ items:
851
+ res.success && res.data
852
+ ? res.data.map((row) => this.toHeaderItem(row))
853
+ : [],
854
+ pagination: res.pagination ?? null,
855
+ }),
856
+ ),
857
+ );
858
+
859
+ protected markNotificationRead = (id: string) =>
860
+ this.notificationsApi.markRead(id).pipe(
861
+ tap(() => this.patchRead(id)),
862
+ );
863
+
864
+ protected markAllNotificationsRead = () =>
865
+ this.notificationsApi.markRead().pipe(
866
+ tap(() =>
867
+ this.notifications.update((items) =>
868
+ items.map((item) => ({ ...item, read: true })),
869
+ ),
870
+ ),
871
+ );
872
+
873
+ constructor() {
874
+ toObservable(this.auth.token)
875
+ .pipe(
876
+ switchMap((token) => {
877
+ if (!token) {
878
+ return of([] as AfricaniesNotification[]);
879
+ }
880
+ return this.notificationsApi
881
+ .readPage({
882
+ page: 1,
883
+ size: NOTIFICATION_PAGE_SIZE,
884
+ order: 'desc',
885
+ })
886
+ .pipe(
887
+ map((res) =>
888
+ res.success && res.data
889
+ ? res.data.map((row) => this.toHeaderItem(row))
890
+ : [],
891
+ ),
892
+ catchError(() => of([] as AfricaniesNotification[])),
893
+ );
894
+ }),
895
+ takeUntilDestroyed(),
896
+ )
897
+ .subscribe((items) => this.notifications.set(items));
898
+ }
899
+
900
+ private patchRead(id: string): void {
901
+ this.notifications.update((items) =>
902
+ items.map((item) => (item.id === id ? { ...item, read: true } : item)),
903
+ );
904
+ }
905
+
906
+ private toHeaderItem(row: NotificationModel): AfricaniesNotification {
907
+ const item = mapNotificationInboxItem(row);
908
+ return {
909
+ id: item.id,
910
+ title: item.title,
911
+ body: item.body,
912
+ timestamp: item.timestamp,
913
+ read: item.read,
914
+ link: item.link,
915
+ externalLink: item.external_link,
916
+ };
917
+ }
918
+ }
919
+ `,ar=`// GET /product/read/{id?} \u2014 ResourceId: null | 'all' | number.
920
+ // readPage() \u2192 paginated \xB7 readAll() \u2192 full \xB7 readById(n) \u2192 single ProductModel
921
+
922
+ import { Component, inject, OnInit, signal } from '@angular/core';
923
+ import { ProductService } from '@africanies/africanies-core';
924
+ import type { ProductModel } from '@africanies/africanies-models';
925
+ import { firstValueFrom } from 'rxjs';
926
+
927
+ @Component({
928
+ selector: 'app-product-loader',
929
+ standalone: true,
930
+ template: \`
931
+ <p>{{ products().length }} products</p>
932
+ @if (products()[0]; as first) {
933
+ <p>{{ first.name }} \u2014 {{ first.hs_code }}</p>
934
+ }
935
+ \`,
936
+ })
937
+ export class ProductLoaderComponent implements OnInit {
938
+ private readonly productsApi = inject(ProductService);
939
+ protected readonly products = signal<ProductModel[]>([]);
940
+
941
+ async ngOnInit(): Promise<void> {
942
+ const res = await firstValueFrom(this.productsApi.readAll());
943
+ if (res.success && res.data) {
944
+ this.products.set(res.data);
945
+ }
946
+ }
947
+ }
948
+ `,sr=`// POST /file/read \u2014 body { ref } when a record stores file_ref.
949
+ // data.mime_type + data.base_64 for preview. Waybills: readMultiple(ref).
950
+
951
+ import { Component, inject, signal } from '@angular/core';
952
+ import { FileService } from '@africanies/africanies-core';
953
+ import type { FileReadModel } from '@africanies/africanies-models';
954
+ import { firstValueFrom } from 'rxjs';
955
+
956
+ @Component({
957
+ selector: 'app-file-loader',
958
+ standalone: true,
959
+ template: \`
960
+ @if (file(); as f) {
961
+ <p>{{ f.mime_type }}</p>
962
+ @if (f.base_64) {
963
+ <img [src]="f.base_64" alt="" />
964
+ } @else if (f.url) {
965
+ <a [href]="f.url" target="_blank" rel="noopener">Open signed URL</a>
966
+ }
967
+ }
968
+ \`,
969
+ })
970
+ export class FileLoaderComponent {
971
+ private readonly filesApi = inject(FileService);
972
+ protected readonly file = signal<FileReadModel | null>(null);
973
+
974
+ async load(ref: string): Promise<void> {
975
+ const res = await firstValueFrom(this.filesApi.read(ref));
976
+ if (res.success && res.data) {
977
+ this.file.set(res.data);
978
+ }
979
+ }
980
+ }
981
+ `;var cr=`// Five variants. Primary and ghost-primary pick up SFN green / STN orange
982
+ // from ModeColorService \u2014 don't hard-code those colors yourself.
983
+ // ghost stays neutral (Cancel); ghost-primary is a mode-colored text button.
984
+
985
+ import { Component } from '@angular/core';
986
+ import {
987
+ ButtonComponent,
988
+ type ButtonVariant,
989
+ } from '@africanies/africanies-ui';
990
+
991
+ @Component({
992
+ selector: 'app-shipment-actions',
993
+ standalone: true,
994
+ imports: [ButtonComponent],
995
+ template: \`
996
+ <div class="flex flex-wrap items-end gap-6">
997
+ @for (v of variants; track v) {
998
+ <div class="flex flex-col gap-2">
999
+ <span class="text-caption text-neutral-600">{{ v }}</span>
1000
+ <button africanies-button type="button" [variant]="v">
1001
+ {{ labels[v] }}
1002
+ </button>
1003
+ </div>
1004
+ }
1005
+ </div>
1006
+ \`,
1007
+ })
1008
+ export class ShipmentActionsComponent {
1009
+ protected readonly variants: ButtonVariant[] = [
1010
+ 'primary',
1011
+ 'secondary',
1012
+ 'ghost',
1013
+ 'ghost-primary',
1014
+ 'ghost-danger',
1015
+ 'underline',
1016
+ 'danger',
1017
+ ];
1018
+
1019
+ protected readonly labels: Record<ButtonVariant, string> = {
1020
+ primary: 'Save shipment',
1021
+ secondary: 'Save draft',
1022
+ ghost: 'Cancel',
1023
+ 'ghost-primary': 'Add row',
1024
+ 'ghost-danger': 'Remove',
1025
+ underline: 'Learn more',
1026
+ danger: 'Delete',
1027
+ };
1028
+ }`,pr=`// sm | md | lg. Default is md. Use sm in toolbars/dialogs; save lg for hero CTAs.
1029
+
1030
+ import { Component } from '@angular/core';
1031
+ import {
1032
+ ButtonComponent,
1033
+ type ButtonSize,
1034
+ } from '@africanies/africanies-ui';
1035
+
1036
+ @Component({
1037
+ selector: 'app-button-sizes',
1038
+ standalone: true,
1039
+ imports: [ButtonComponent],
1040
+ template: \`
1041
+ <div class="flex flex-wrap items-end gap-6">
1042
+ @for (s of sizes; track s) {
1043
+ <div class="flex flex-col gap-2">
1044
+ <span class="text-caption text-neutral-600">size={{ s }}</span>
1045
+ <button africanies-button type="button" [size]="s">
1046
+ Continue
1047
+ </button>
1048
+ </div>
1049
+ }
1050
+ </div>
1051
+ \`,
1052
+ })
1053
+ export class ButtonSizesComponent {
1054
+ protected readonly sizes: ButtonSize[] = ['sm', 'md', 'lg'];
1055
+ }`,dr=`// Full variant \xD7 size grid \u2014 handy for QA, not something you'd ship on a product page.
1056
+
1057
+ import { Component } from '@angular/core';
1058
+ import {
1059
+ ButtonComponent,
1060
+ type ButtonSize,
1061
+ type ButtonVariant,
1062
+ } from '@africanies/africanies-ui';
1063
+
1064
+ @Component({
1065
+ selector: 'app-button-matrix',
1066
+ standalone: true,
1067
+ imports: [ButtonComponent],
1068
+ template: \`
1069
+ <table class="w-full border-collapse text-left">
1070
+ <thead>
1071
+ <tr class="text-caption text-neutral-600">
1072
+ <th class="pb-3 pr-4 font-medium">Variant</th>
1073
+ @for (s of sizes; track s) {
1074
+ <th class="pb-3 pr-4 font-medium">{{ s }}</th>
1075
+ }
1076
+ </tr>
1077
+ </thead>
1078
+ <tbody>
1079
+ @for (v of variants; track v) {
1080
+ <tr class="border-t border-border dark:border-white/10">
1081
+ <td class="py-3 pr-4 text-body-sm text-neutral-600">{{ v }}</td>
1082
+ @for (s of sizes; track s) {
1083
+ <td class="py-3 pr-4">
1084
+ <button africanies-button type="button" [variant]="v" [size]="s">
1085
+ Action
1086
+ </button>
1087
+ </td>
1088
+ }
1089
+ </tr>
1090
+ }
1091
+ </tbody>
1092
+ </table>
1093
+ \`,
1094
+ })
1095
+ export class ButtonMatrixComponent {
1096
+ protected readonly variants: ButtonVariant[] = [
1097
+ 'primary',
1098
+ 'secondary',
1099
+ 'ghost',
1100
+ 'danger',
1101
+ ];
1102
+ protected readonly sizes: ButtonSize[] = ['sm', 'md', 'lg'];
1103
+ }`,mr=`// Drop <africanies-icon> inside the button \u2014 same focus ring as the label.
1104
+ // Icon-only? Put aria-label on the host.
1105
+
1106
+ import { Component } from '@angular/core';
1107
+ import { AfricaniesIconComponent } from '@africanies/africanies-icons';
1108
+ import { ButtonComponent } from '@africanies/africanies-ui';
1109
+
1110
+ @Component({
1111
+ selector: 'app-button-icons',
1112
+ standalone: true,
1113
+ imports: [ButtonComponent, AfricaniesIconComponent],
1114
+ template: \`
1115
+ <div class="flex flex-wrap gap-3">
1116
+ <button africanies-button type="button" variant="primary">
1117
+ <africanies-icon name="airplane" [size]="16" />
1118
+ Create shipment
1119
+ </button>
1120
+
1121
+ <button africanies-button type="button" variant="secondary">
1122
+ <africanies-icon name="adjust" [size]="16" />
1123
+ Filters
1124
+ </button>
1125
+
1126
+ <button africanies-button type="button" variant="ghost" size="sm">
1127
+ <africanies-icon name="warehouse" [size]="14" />
1128
+ Warehouse
1129
+ </button>
1130
+
1131
+ <button africanies-button type="button" variant="danger" size="sm">
1132
+ <africanies-icon name="warning" [size]="14" />
1133
+ Void
1134
+ </button>
1135
+ </div>
1136
+ \`,
1137
+ })
1138
+ export class ButtonIconsComponent {}`,ur=`// Same look on <a> \u2014 use routerLink for in-app, href for external.
1139
+
1140
+ import { Component } from '@angular/core';
1141
+ import { RouterLink } from '@angular/router';
1142
+ import { ButtonComponent } from '@africanies/africanies-ui';
1143
+
1144
+ @Component({
1145
+ selector: 'app-button-links',
1146
+ standalone: true,
1147
+ imports: [ButtonComponent, RouterLink],
1148
+ template: \`
1149
+ <div class="flex flex-wrap gap-3">
1150
+ <a africanies-button routerLink="/shipments/new" variant="primary">
1151
+ Primary link
1152
+ </a>
1153
+
1154
+ <a africanies-button routerLink="/drafts" variant="secondary" size="sm">
1155
+ Secondary link
1156
+ </a>
1157
+
1158
+ <a africanies-button href="/help/getting-started" variant="ghost">
1159
+ Ghost link
1160
+ </a>
1161
+ </div>
1162
+ \`,
1163
+ })
1164
+ export class ButtonLinksComponent {}`,fr=`// [disabled] works on buttons and anchors (anchors get aria-disabled + tabindex=-1).
1165
+ // Use this for unavailable actions \u2014 in-flight work belongs on [loading].
1166
+
1167
+ import { Component, signal } from '@angular/core';
1168
+ import { ButtonComponent } from '@africanies/africanies-ui';
1169
+
1170
+ @Component({
1171
+ selector: 'app-button-disabled',
1172
+ standalone: true,
1173
+ imports: [ButtonComponent],
1174
+ template: \`
1175
+ <div class="flex flex-wrap gap-3">
1176
+ <button africanies-button type="button" variant="primary" [disabled]="saving()">
1177
+ {{ saving() ? 'Saving\u2026' : 'Save shipment' }}
1178
+ </button>
1179
+
1180
+ <button africanies-button type="button" variant="secondary" disabled>
1181
+ Secondary
1182
+ </button>
1183
+
1184
+ <button africanies-button type="button" variant="ghost" disabled>
1185
+ Ghost
1186
+ </button>
1187
+
1188
+ <button africanies-button type="button" variant="danger" disabled>
1189
+ Danger
1190
+ </button>
1191
+
1192
+ <a africanies-button href="/shipments" variant="ghost" [disabled]="saving()">
1193
+ Ghost link
1194
+ </a>
1195
+ </div>
1196
+ \`,
1197
+ })
1198
+ export class ButtonDisabledComponent {
1199
+ protected readonly saving = signal(false);
1200
+ }`,gr=`// Default loading state is the circular spinner. Label stays in the layout
1201
+ // (invisible) so the button does not shrink while the request is in flight.
1202
+
1203
+ import { Component, signal } from '@angular/core';
1204
+ import { ButtonComponent } from '@africanies/africanies-ui';
1205
+
1206
+ @Component({
1207
+ selector: 'app-button-loading',
1208
+ standalone: true,
1209
+ imports: [ButtonComponent],
1210
+ template: \`
1211
+ <div class="flex flex-wrap items-center gap-3">
1212
+ <button
1213
+ africanies-button
1214
+ type="button"
1215
+ variant="primary"
1216
+ [loading]="saving()"
1217
+ (click)="save()"
1218
+ >
1219
+ Save shipment
1220
+ </button>
1221
+
1222
+ <button africanies-button type="button" variant="secondary" [loading]="true">
1223
+ Saving draft
1224
+ </button>
1225
+
1226
+ <button
1227
+ africanies-button
1228
+ type="button"
1229
+ variant="danger"
1230
+ size="sm"
1231
+ [loading]="true"
1232
+ >
1233
+ Deleting
1234
+ </button>
1235
+ </div>
1236
+ \`,
1237
+ })
1238
+ export class ButtonLoadingComponent {
1239
+ protected readonly saving = signal(false);
1240
+
1241
+ protected save(): void {
1242
+ this.saving.set(true);
1243
+ // POST \u2192 finalize in subscribe/finalize and set saving false.
1244
+ }
1245
+ }`,hr=`// Typical toolbar: ghost cancel \u2192 secondary draft \u2192 primary submit.
1246
+ // Toggle SFN/STN in the sidebar to see primary shift color.
1247
+
1248
+ import { Component, inject, signal } from '@angular/core';
1249
+ import { ModeColorService } from '@africanies/africanies-theme';
1250
+ import { ButtonComponent } from '@africanies/africanies-ui';
1251
+
1252
+ @Component({
1253
+ selector: 'app-shipment-toolbar',
1254
+ standalone: true,
1255
+ imports: [ButtonComponent],
1256
+ template: \`
1257
+ <div
1258
+ class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border bg-background-welcome px-4 py-3 dark:border-white/10 dark:bg-ink-950"
1259
+ >
1260
+ <div>
1261
+ <p class="m-0 text-body font-medium text-ink dark:text-white">
1262
+ Shipment {{ ref() }}
1263
+ </p>
1264
+ <p class="m-0 text-caption text-neutral-600">
1265
+ Draft \xB7 {{ route() }}
1266
+ </p>
1267
+ </div>
1268
+
1269
+ <div class="flex flex-wrap gap-2">
1270
+ <button africanies-button type="button" variant="ghost" size="sm" (click)="cancel()">
1271
+ Cancel
1272
+ </button>
1273
+
1274
+ <button
1275
+ africanies-button
1276
+ type="button"
1277
+ variant="secondary"
1278
+ size="sm"
1279
+ [loading]="saving()"
1280
+ (click)="saveDraft()"
1281
+ >
1282
+ Save draft
1283
+ </button>
1284
+
1285
+ <button
1286
+ africanies-button
1287
+ type="button"
1288
+ variant="primary"
1289
+ size="sm"
1290
+ [loading]="saving()"
1291
+ (click)="submit()"
1292
+ >
1293
+ Submit
1294
+ </button>
1295
+ </div>
1296
+ </div>
1297
+ \`,
1298
+ })
1299
+ export class ShipmentToolbarComponent {
1300
+ protected readonly modeColor = inject(ModeColorService);
1301
+
1302
+ protected readonly ref = signal('SFN-1042');
1303
+ protected readonly route = signal('Lagos \u2192 London');
1304
+ protected readonly saving = signal(false);
1305
+
1306
+ protected cancel(): void {
1307
+ // Navigate away or close parent overlay ref.
1308
+ }
1309
+
1310
+ protected saveDraft(): void {
1311
+ this.saving.set(true);
1312
+ // POST draft \u2192 finalize in subscribe/finalize and set saving false.
1313
+ }
1314
+
1315
+ protected submit(): void {
1316
+ this.saving.set(true);
1317
+ // POST submit \u2192 on success close overlay or navigate to detail.
1318
+ }
1319
+ }`,br=`// Clipboard control \u2014 icon flips to check on success.
1320
+ // Optional [announce]="true" also fires a toast when ToastService is provided.
1321
+
1322
+ import { Component, signal } from '@angular/core';
1323
+ import { CopyButtonComponent } from '@africanies/africanies-ui';
1324
+
1325
+ @Component({
1326
+ selector: 'app-copy-demo',
1327
+ standalone: true,
1328
+ imports: [CopyButtonComponent],
1329
+ template: \`
1330
+ <div class="flex flex-wrap items-center gap-4">
1331
+ <code class="rounded-md bg-border/60 px-2 py-1 font-mono text-caption">
1332
+ {{ reference() }}
1333
+ </code>
1334
+ <africanies-copy [value]="reference()" ariaLabel="Copy reference" />
1335
+ <africanies-copy
1336
+ [value]="snippet()"
1337
+ label="Copy snippet"
1338
+ [announce]="true"
1339
+ />
1340
+ </div>
1341
+ \`,
1342
+ })
1343
+ export class CopyDemoComponent {
1344
+ protected readonly reference = signal('SFN-1042');
1345
+ protected readonly snippet = signal('<africanies-icon name="airplane" />');
1346
+ }
1347
+ `;var vr=`
1348
+ <africanies-chip variant="neutral">Neutral</africanies-chip>
1349
+ <africanies-chip variant="success">Delivered</africanies-chip>
1350
+ <africanies-chip variant="warning">Pending</africanies-chip>
1351
+ <africanies-chip variant="danger">Exception</africanies-chip>
1352
+ <africanies-chip variant="export">SFN</africanies-chip>
1353
+ <africanies-chip variant="import">STN</africanies-chip>
1354
+ `.trim(),Sr=`
1355
+ <africanies-chip variant="warning" icon="clock">Pending</africanies-chip>
1356
+
1357
+ <africanies-chip
1358
+ variant="danger"
1359
+ [removable]="true"
1360
+ (removed)="clearFilter()"
1361
+ >
1362
+ Exception
1363
+ </africanies-chip>
1364
+ `.trim(),Cr=`
1365
+ <ng-template africaniesCellDef="status" let-row>
1366
+ <africanies-chip [variant]="statusVariant(row.status)">{{ row.status }}</africanies-chip>
1367
+ </ng-template>
1368
+ `.trim();var wr=`
1369
+ // Block for first load; inline for a quiet refresh in a toolbar or header.
1370
+ // Spinner color follows ModeColorService. Don\u2019t drop this inside africanies-table \u2014 use AsyncState.
1371
+
1372
+ import { Component } from '@angular/core';
1373
+ import { LoadingStateComponent } from '@africanies/africanies-ui';
1374
+
1375
+ @Component({
1376
+ selector: 'app-loading-demo',
1377
+ standalone: true,
1378
+ imports: [LoadingStateComponent],
1379
+ template: \`
1380
+ <div class="grid gap-6 md:grid-cols-2">
1381
+ <div class="flex flex-col gap-2">
1382
+ <p class="m-0 text-caption font-medium text-neutral-600">Block</p>
1383
+ <div
1384
+ class="flex min-h-[10rem] items-center justify-center rounded-lg border border-dashed border-border bg-background-welcome dark:border-white/10 dark:bg-ink-950"
1385
+ >
1386
+ <africanies-loading-state message="Loading shipments\u2026" />
1387
+ </div>
1388
+ </div>
1389
+
1390
+ <div class="flex flex-col gap-2">
1391
+ <p class="m-0 text-caption font-medium text-neutral-600">Inline</p>
1392
+ <div
1393
+ class="flex min-h-[10rem] items-center rounded-lg border border-dashed border-border bg-background-welcome px-5 dark:border-white/10 dark:bg-ink-950"
1394
+ >
1395
+ <africanies-loading-state mode="inline" message="Refreshing rates\u2026" />
1396
+ </div>
1397
+ </div>
1398
+ </div>
1399
+ \`,
1400
+ })
1401
+ export class LoadingDemoComponent {}
1402
+ `,Er=`
1403
+ // Fetch failed \u2014 always wire (retry) to your refetch. Message from the API layer is fine.
1404
+ // Section/page failures only; not field validation or a single missing row.
1405
+
1406
+ import { Component, inject, signal } from '@angular/core';
1407
+ import { ErrorStateComponent } from '@africanies/africanies-ui';
1408
+
1409
+ @Component({
1410
+ selector: 'app-error-demo',
1411
+ standalone: true,
1412
+ imports: [ErrorStateComponent],
1413
+ template: \`
1414
+ <div
1415
+ class="rounded-lg border border-border bg-background-welcome dark:border-white/10 dark:bg-ink-950"
1416
+ >
1417
+ <africanies-error-state
1418
+ [message]="errorMessage()"
1419
+ (retry)="refetch()"
1420
+ />
1421
+ </div>
1422
+ \`,
1423
+ })
1424
+ export class ErrorDemoComponent {
1425
+ // In production: map injectQuery() / getResource() error signal to this string.
1426
+ protected readonly errorMessage = signal(
1427
+ 'Failed to load shipments. Check your connection and try again.',
1428
+ );
1429
+
1430
+ protected refetch(): void {
1431
+ // query.refetch() or resource.reload() \u2014 same handler AsyncState uses via (retry).
1432
+ }
1433
+ }
1434
+ `,Ar=`
1435
+ // Load succeeded but nothing matched. Retry often means resetFilters() or refetch.
1436
+ // Prefer AsyncState on list pages \u2014 it picks EmptyState when data is [].
1437
+
1438
+ import { Component, signal } from '@angular/core';
1439
+ import { EmptyStateComponent } from '@africanies/africanies-ui';
1440
+
1441
+ @Component({
1442
+ selector: 'app-empty-demo',
1443
+ standalone: true,
1444
+ imports: [EmptyStateComponent],
1445
+ template: \`
1446
+ <africanies-empty-state
1447
+ [message]="message()"
1448
+ (retry)="resetFilters()"
1449
+ />
1450
+ \`,
1451
+ })
1452
+ export class EmptyDemoComponent {
1453
+ protected readonly message = signal('No shipments match your filters.');
1454
+
1455
+ protected resetFilters(): void {
1456
+ // Clear facet signals \u2192 triggers list refetch with default params.
1457
+ }
1458
+ }
1459
+ `,kr=`
1460
+ // Non-blocking retry pill \u2014 stale data stays visible underneath.
1461
+ // AsyncState uses this automatically when data exists but refresh failed.
1462
+
1463
+ import { Component, signal } from '@angular/core';
1464
+ import { ErrorIndicatorComponent } from '@africanies/africanies-ui';
1465
+
1466
+ @Component({
1467
+ selector: 'app-stale-data-panel',
1468
+ standalone: true,
1469
+ imports: [ErrorIndicatorComponent],
1470
+ template: \`
1471
+ <div class="flex flex-col gap-3 rounded-xl border border-border p-4 dark:border-white/10">
1472
+ <div class="flex justify-end">
1473
+ <africanies-error-indicator
1474
+ class="max-w-[min(100%,20rem)]"
1475
+ error="Failed to fetch the most recent data."
1476
+ retryText="Refresh"
1477
+ [refreshing]="isRefreshing()"
1478
+ [refreshingText]="isRefreshing() ? 'Refreshing...' : 'Refresh'"
1479
+ (retry)="refetch()"
1480
+ />
1481
+ </div>
1482
+
1483
+ <!-- Existing table / cards stay mounted -->
1484
+ <ng-content />
1485
+ </div>
1486
+ \`,
1487
+ })
1488
+ export class StaleDataPanelComponent {
1489
+ protected readonly isRefreshing = signal(false);
1490
+
1491
+ protected refetch(): void {
1492
+ this.isRefreshing.set(true);
1493
+ // query.refetch().finally(() => this.isRefreshing.set(false));
1494
+ }
1495
+ }
1496
+
1497
+ // Standalone call sites:
1498
+ // Socket: error="Connection lost" retryText="Reconnect" refreshingText="Connecting..."
1499
+ // Currencies: error="Error fetching currencies" (retryText defaults to "Retry")
1500
+ `,_r=`
1501
+ // Wrap the list (table + pagination). Maps loading / error / empty / success for you.
1502
+ // Background refetch keeps content on screen. Wire your own in-flight cue (e.g. table Refresh). One (retry) \u2192 refetch.
1503
+
1504
+ import { Component, computed, inject, signal } from '@angular/core';
1505
+ import type { AsyncQueryStateModel } from '@africanies/africanies-models';
1506
+ import {
1507
+ AsyncStateComponent,
1508
+ TableComponent,
1509
+ type TableColumn,
1510
+ } from '@africanies/africanies-ui';
1511
+
1512
+ interface Shipment {
1513
+ id: string;
1514
+ route: string;
1515
+ status: string;
1516
+ }
1517
+
1518
+ @Component({
1519
+ selector: 'app-shipment-list',
1520
+ standalone: true,
1521
+ imports: [AsyncStateComponent, TableComponent],
1522
+ template: \`
1523
+ <africanies-async-state [state]="state()" (retry)="refetch()">
1524
+ <africanies-table
1525
+ [columns]="columns"
1526
+ [rows]="state().data ?? []"
1527
+ [refreshing]="state().isFetching"
1528
+ />
1529
+ </africanies-async-state>
1530
+ \`,
1531
+ })
1532
+ export class ShipmentListComponent {
1533
+ // Playground: manual signal. Production: computed from injectQuery().
1534
+ private readonly queryData = signal<Shipment[] | undefined>(undefined);
1535
+ private readonly queryLoading = signal(true);
1536
+ private readonly queryFetching = signal(false);
1537
+ private readonly queryError = signal<string | null>(null);
1538
+
1539
+ protected readonly state = computed((): AsyncQueryStateModel<Shipment[]> => ({
1540
+ data: this.queryData(),
1541
+ isLoading: this.queryLoading(),
1542
+ isFetching: this.queryFetching(),
1543
+ isError: this.queryError() !== null,
1544
+ error: this.queryError(),
1545
+ }));
1546
+
1547
+ protected readonly columns: TableColumn<Shipment>[] = [
1548
+ { key: 'id', header: 'Reference', sortable: true },
1549
+ { key: 'route', header: 'Route' },
1550
+ { key: 'status', header: 'Status', sortable: true },
1551
+ ];
1552
+
1553
+ protected refetch(): void {
1554
+ // Production one-liner: this.shipmentsQuery.refetch();
1555
+ this.queryLoading.set(this.queryData() === undefined);
1556
+ this.queryFetching.set(true);
1557
+ this.queryError.set(null);
1558
+ // \u2026 HTTP GET \u2192 on success set queryData + clear flags; on error set queryError.
1559
+ }
1560
+ }
1561
+
1562
+ // Production mapping (TanStack Query) \u2014 drop into the same component:
1563
+ //
1564
+ // readonly query = injectQuery(() => ({
1565
+ // queryKey: ['shipments', this.filters()],
1566
+ // queryFn: () => this.api.getShipments(this.filters()),
1567
+ // }));
1568
+ //
1569
+ // readonly state = computed<AsyncQueryStateModel<Shipment[]>>(() => ({
1570
+ // data: this.query.data(),
1571
+ // isLoading: this.query.isLoading(),
1572
+ // isFetching: this.query.isFetching(),
1573
+ // isError: this.query.isError(),
1574
+ // error: this.query.error()?.message ?? null,
1575
+ // }));
1576
+ `;var Mr=`
1577
+ // Open the drawer, await the list refetch, then commit state on success.
1578
+ // Host owns FilterStateModel; drawer edits a clone. Needs provideAfricaniesUiOverlays().
1579
+ // Apply writes the query bag to the URL (page resets to 1). Seed from the URL
1580
+ // on first paint with FilterQueryService.read() when those queries are present.
1581
+
1582
+ import { Component, inject, signal } from '@angular/core';
1583
+ import {
1584
+ ButtonComponent,
1585
+ FilterDrawerService,
1586
+ FilterQueryService,
1587
+ emptyFilterState,
1588
+ trackShipmentsFilterConfig,
1589
+ type FilterStateModel,
1590
+ } from '@africanies/africanies-ui';
1591
+
1592
+ @Component({
1593
+ selector: 'app-shipment-list-filters',
1594
+ standalone: true,
1595
+ imports: [ButtonComponent],
1596
+ template: \`
1597
+ <button africanies-button type="button" variant="secondary" (click)="openFilters()">
1598
+ Filters
1599
+ </button>
1600
+ \`,
1601
+ })
1602
+ export class ShipmentListFiltersComponent {
1603
+ private readonly filterDrawer = inject(FilterDrawerService);
1604
+ private readonly filterQuery = inject(FilterQueryService);
1605
+ private readonly http = inject(/* your HttpClient or list service */);
1606
+ private readonly config = trackShipmentsFilterConfig;
1607
+
1608
+ protected readonly state = signal<FilterStateModel>(
1609
+ this.filterQuery.hasParams(this.config)
1610
+ ? this.filterQuery.read(this.config)
1611
+ : emptyFilterState(),
1612
+ );
1613
+
1614
+ protected openFilters(): void {
1615
+ this.filterDrawer
1616
+ .open({
1617
+ config: this.config,
1618
+ state: this.state(),
1619
+ title: 'Track shipments',
1620
+ // Stays open until this settles; errors keep it open + show a tip.
1621
+ onApply: ({ params }) => this.http.get('/api/shipments', { params }),
1622
+ })
1623
+ .afterClosed()
1624
+ .subscribe((result) => {
1625
+ if (!result?.applied) {
1626
+ return; // Cancel / backdrop \u2014 leave host state alone
1627
+ }
1628
+ this.state.set(result.state);
1629
+ });
1630
+ }
1631
+ }
1632
+ `,Ir=`
1633
+ // Custom ModuleFilterConfigModel for your list screen.
1634
+ // Omit transport \u2192 defaults to FilterTransport.LegacyParallel (filterColumn CSV).
1635
+ // Set transport: FilterTransport.Named when each field is its own query param.
1636
+
1637
+ import {
1638
+ FilterTransport,
1639
+ type ModuleFilterConfigModel,
1640
+ } from '@africanies/africanies-models';
1641
+
1642
+ export const myOrdersFilterConfig: ModuleFilterConfigModel = {
1643
+ id: 'my-orders',
1644
+ search: {
1645
+ param: 'search',
1646
+ label: 'Order ref',
1647
+ placeholder: 'Search\u2026',
1648
+ },
1649
+ sort: {
1650
+ param: 'order',
1651
+ options: [
1652
+ { value: 'desc', label: 'Newest first' },
1653
+ { value: 'asc', label: 'Oldest first' },
1654
+ ],
1655
+ },
1656
+ pagination: { pageParam: 'page', sizeParam: 'size' },
1657
+ fields: [
1658
+ {
1659
+ key: 'status',
1660
+ label: 'Status',
1661
+ type: 'enum',
1662
+ options: [
1663
+ { value: 'open', label: 'Open', color: '#3B82F6' },
1664
+ { value: 'closed', label: 'Closed', color: '#25945c' },
1665
+ ],
1666
+ },
1667
+ {
1668
+ key: 'warehouse_id',
1669
+ label: 'Warehouse',
1670
+ type: 'select',
1671
+ optionsSource: 'warehouses',
1672
+ placeholder: 'All warehouses',
1673
+ },
1674
+ ],
1675
+ };
1676
+
1677
+ // Newer API \u2014 explicit named transport:
1678
+ export const myClaimsFilterConfig: ModuleFilterConfigModel = {
1679
+ id: 'claims',
1680
+ transport: FilterTransport.Named,
1681
+ search: { param: 'search', label: 'Search' },
1682
+ fields: [
1683
+ {
1684
+ key: 'claim_status',
1685
+ label: 'Claim status',
1686
+ type: 'enum',
1687
+ options: [{ value: 'open', label: 'Open' }],
1688
+ },
1689
+ ],
1690
+ };
1691
+
1692
+ // Pass to the drawer (same UI for both transports):
1693
+ // filterDrawer.open({ config: myOrdersFilterConfig, state, onApply: \u2026 });
1694
+ `,Nr=`
1695
+ // Full seed example (update-shipments) \u2014 field \`type\` picks the control.
1696
+ // See FILTERS_CUSTOM_CONFIG for a minimal custom module.
1697
+
1698
+ import { FilterTransport, type ModuleFilterConfigModel } from '@africanies/africanies-models';
1699
+
1700
+ export const updateShipmentsFilterConfig: ModuleFilterConfigModel = {
1701
+ id: 'update-shipments',
1702
+ route: ['portal', 'shipment', 'update-shipments'],
1703
+ // transport omitted \u2192 FilterTransport.LegacyParallel
1704
+ pagination: { pageParam: 'page', sizeParam: 'size' },
1705
+ search: {
1706
+ param: 'search',
1707
+ label: 'Shipment ID',
1708
+ placeholder: 'Search',
1709
+ },
1710
+ date: {
1711
+ rangeParams: { from: 'from', to: 'to' },
1712
+ fieldParam: 'date',
1713
+ fields: [
1714
+ { value: 'created_at', label: 'Date Created' },
1715
+ { value: 'paid_at', label: 'Date of Payment' },
1716
+ { value: 'packaged_at', label: 'Date Packaged' },
1717
+ { value: 'shipment_processed_at', label: 'Date Processed' },
1718
+ ],
1719
+ },
1720
+ sort: {
1721
+ param: 'order',
1722
+ options: [
1723
+ { value: 'asc', label: 'Ascending' },
1724
+ { value: 'desc', label: 'Descending' },
1725
+ ],
1726
+ },
1727
+ fields: [
1728
+ {
1729
+ key: 'payment_status',
1730
+ label: 'Payment Status',
1731
+ type: 'enum',
1732
+ exclusive: true,
1733
+ options: [
1734
+ { value: 'paid', label: 'Paid', color: '#25945c' },
1735
+ { value: 'unpaid', label: 'Unpaid', color: '#f48220' },
1736
+ ],
1737
+ },
1738
+ {
1739
+ key: 'shipment_status',
1740
+ label: 'Shipment Status',
1741
+ type: 'enum',
1742
+ exclusive: true,
1743
+ options: [
1744
+ { value: 'pending', label: 'Pending', color: '#DBB316' },
1745
+ { value: 'in-process', label: 'In Process', color: '#3B82F6' },
1746
+ { value: 'completed', label: 'Completed', color: '#25945c' },
1747
+ ],
1748
+ },
1749
+ {
1750
+ key: 'type',
1751
+ label: 'Shipment Type',
1752
+ type: 'enum',
1753
+ exclusive: true,
1754
+ options: [
1755
+ { value: 'shipment', label: 'Shipment', color: '#3B82F6' },
1756
+ { value: 'etw_shipment', label: 'ETW Shipment', color: '#8B5CF6' },
1757
+ ],
1758
+ },
1759
+ {
1760
+ key: 'shipment_method_id',
1761
+ label: 'Shipment Carrier',
1762
+ type: 'select',
1763
+ optionsSource: 'shipmentMethods',
1764
+ placeholder: 'Shipment Methods',
1765
+ },
1766
+ {
1767
+ key: 'type_of_user',
1768
+ label: 'User Type',
1769
+ type: 'select',
1770
+ optionsSource: 'static',
1771
+ options: [
1772
+ { value: 'individual', label: 'Individual' },
1773
+ { value: 'business', label: 'Business' },
1774
+ ],
1775
+ placeholder: 'User Type',
1776
+ },
1777
+ {
1778
+ key: 'warehouse_id',
1779
+ label: 'Warehouse',
1780
+ type: 'select',
1781
+ optionsSource: 'warehouses',
1782
+ placeholder: 'Warehouse',
1783
+ },
1784
+ {
1785
+ key: 'shipment_manifest_id',
1786
+ label: 'Shipment Manifest',
1787
+ type: 'select',
1788
+ optionsSource: 'shipmentManifests',
1789
+ placeholder: 'Shipment Manifest',
1790
+ },
1791
+ {
1792
+ key: 'api_request',
1793
+ label: 'API Request',
1794
+ type: 'boolean',
1795
+ options: [
1796
+ { value: '1', label: 'Yes' },
1797
+ { value: '0', label: 'No' },
1798
+ ],
1799
+ },
1800
+ {
1801
+ key: 'is_insured',
1802
+ label: 'Insured',
1803
+ type: 'boolean',
1804
+ options: [
1805
+ { value: '1', label: 'Yes' },
1806
+ { value: '0', label: 'No' },
1807
+ ],
1808
+ },
1809
+ ],
1810
+ };
1811
+ `,Rr=`
1812
+ // Status chips. Optional \`color\` tints the selected chip; idle stays neutral.
1813
+ // color is UI-only \u2014 never sent in params.
1814
+
1815
+ {
1816
+ key: 'shipment_status',
1817
+ label: 'Shipment Status',
1818
+ type: 'enum',
1819
+ exclusive: true, // one value at a time
1820
+ options: [
1821
+ { value: 'pending', label: 'Pending', color: '#DBB316' },
1822
+ { value: 'in-process', label: 'In Process', color: '#3B82F6' },
1823
+ { value: 'completed', label: 'Completed', color: '#25945c' },
1824
+ ],
1825
+ }
1826
+ `,Or=`
1827
+ // \u201CFilter by\u201D lists every config.fields entry; picking one reveals its control.
1828
+ // Deselect clears that section\u2019s value on Apply. Keep field.key stable.
1829
+
1830
+ // User picks \u201CPayment Status\u201D + \u201CWarehouse\u201D in Filter by \u2192
1831
+ // draft.values = { payment_status: 'paid', warehouse_id: '12' }
1832
+ `,Dr=`
1833
+ // Date column select + From / To. Clear resets only those three.
1834
+ // To can\u2019t be before From \u2014 moving From past To clears To.
1835
+
1836
+ date: {
1837
+ rangeParams: { from: 'from', to: 'to' },
1838
+ fieldParam: 'date',
1839
+ fields: [
1840
+ { value: 'created_at', label: 'Date Created' },
1841
+ { value: 'paid_at', label: 'Date of Payment' },
1842
+ ],
1843
+ },
1844
+
1845
+ // Example state \u2192 params
1846
+ // { date: 'created_at', from: '2026-01-01', to: '2026-01-31' }
1847
+ `,Pr=`
1848
+ // Laravel-style: filterColumn + filterValue CSVs by index, plus search/date/sort/page.
1849
+ // Keep values keyed by field.key; only call toFilterParams at the URL/HTTP boundary.
1850
+
1851
+ import {
1852
+ fromFilterParams,
1853
+ toFilterParams,
1854
+ trackShipmentsFilterConfig,
1855
+ type FilterStateModel,
1856
+ } from '@africanies/africanies-models';
1857
+
1858
+ const state: FilterStateModel = {
1859
+ search: 'SFN-1042',
1860
+ date: 'created_at',
1861
+ from: '2026-01-01',
1862
+ to: '2026-01-31',
1863
+ order: 'desc',
1864
+ page: 1,
1865
+ size: 15,
1866
+ values: {
1867
+ payment_status: 'paid',
1868
+ shipment_status: 'pending',
1869
+ tracking_number: 'TN-9',
1870
+ },
1871
+ };
1872
+
1873
+ const params = toFilterParams(state, trackShipmentsFilterConfig);
1874
+ // \u2192 {
1875
+ // search: 'SFN-1042',
1876
+ // date: 'created_at',
1877
+ // from: '2026-01-01',
1878
+ // to: '2026-01-31',
1879
+ // order: 'desc',
1880
+ // page: 1,
1881
+ // size: 15,
1882
+ // filterColumn: 'payment_status,shipment_status,tracking_number',
1883
+ // filterValue: 'paid,pending,TN-9',
1884
+ // }
1885
+
1886
+ // Cold load from router queryParams:
1887
+ const restored = fromFilterParams(params, trackShipmentsFilterConfig);
1888
+ // restored.values.payment_status === 'paid'
1889
+ `,Lr=`
1890
+ // Newer endpoints: each filter is its own query key (no filterColumn CSVs).
1891
+ // Same drawer + FilterStateModel; set transport: FilterTransport.Named.
1892
+
1893
+ import {
1894
+ FilterTransport,
1895
+ shipmentTrackingItemFilterConfig,
1896
+ toFilterParams,
1897
+ type FilterStateModel,
1898
+ } from '@africanies/africanies-models';
1899
+
1900
+ const state: FilterStateModel = {
1901
+ search: 'AWB',
1902
+ values: { claim_status: 'open' },
1903
+ };
1904
+
1905
+ toFilterParams(state, shipmentTrackingItemFilterConfig);
1906
+ // \u2192 { search: 'AWB', claim_status: 'open' }
1907
+ // (no filterColumn / filterValue)
1908
+ `,Fr=`// optionsSource catalogs load lazily inside the filter drawer when the user
1909
+ // adds the field via Filter by. HTTP errors toast via httpToasts; the select
1910
+ // shows a field error. Pass host optionLists for sources without an SDK service.
1911
+
1912
+ import { Component, inject, signal } from '@angular/core';
1913
+ import {
1914
+ emptyFilterState,
1915
+ updateShipmentsFilterConfig,
1916
+ type FilterStateModel,
1917
+ } from '@africanies/africanies-models';
1918
+ import { FilterDrawerService } from '@africanies/africanies-ui';
1919
+
1920
+ @Component({
1921
+ selector: 'app-update-shipment-filters',
1922
+ standalone: true,
1923
+ template: \`<button type="button" (click)="open()">Filters</button>\`,
1924
+ })
1925
+ export class UpdateShipmentFiltersComponent {
1926
+ private readonly filterDrawer = inject(FilterDrawerService);
1927
+
1928
+ protected readonly state = signal<FilterStateModel>(emptyFilterState());
1929
+
1930
+ protected open(): void {
1931
+ this.filterDrawer
1932
+ .open({
1933
+ config: updateShipmentsFilterConfig,
1934
+ state: this.state(),
1935
+ optionLists: {
1936
+ // No built-in SDK service yet \u2014 host supplies manifests
1937
+ shipment_manifest_id: [{ value: '100', label: 'Manifest A' }],
1938
+ },
1939
+ onApply: ({ params }) => this.listApi.fetch(params),
1940
+ })
1941
+ .afterClosed()
1942
+ .subscribe((result) => {
1943
+ if (result?.applied) {
1944
+ this.state.set(result.state);
1945
+ }
1946
+ });
1947
+ }
1948
+ }
1949
+ `,Br=`
1950
+ // First paint: rebuild FilterStateModel from the URL so shared links restore filters.
1951
+ // FilterQueryService.read() is a no-op into empty state when those queries are absent.
1952
+ // Apply and africanies-pagination write the same keys back (\`page\`, \`size\`, filters).
1953
+
1954
+ import { inject, signal } from '@angular/core';
1955
+ import {
1956
+ FilterQueryService,
1957
+ emptyFilterState,
1958
+ trackShipmentsFilterConfig,
1959
+ type FilterStateModel,
1960
+ } from '@africanies/africanies-ui';
1961
+
1962
+ export class ListPage {
1963
+ private readonly filterQuery = inject(FilterQueryService);
1964
+ private readonly config = trackShipmentsFilterConfig;
1965
+
1966
+ protected readonly state = signal<FilterStateModel>(
1967
+ this.filterQuery.hasParams(this.config)
1968
+ ? this.filterQuery.read(this.config)
1969
+ : emptyFilterState(),
1970
+ );
1971
+
1972
+ protected readonly page = signal(this.state().page ?? 1);
1973
+ protected readonly size = signal(this.state().size ?? 15);
1974
+ }
1975
+ `;var jr=`
1976
+ // Text fields: label + optional hint/error. Bind with [(value)].
1977
+ // Prefix/suffix slots use attribute selectors (prefix, suffix).
1978
+
1979
+ import { Component, signal } from '@angular/core';
1980
+ import { AfricaniesIconComponent } from '@africanies/africanies-icons';
1981
+ import { TextInputComponent } from '@africanies/africanies-ui';
1982
+
1983
+ @Component({
1984
+ selector: 'app-shipment-reference-form',
1985
+ standalone: true,
1986
+ imports: [TextInputComponent, AfricaniesIconComponent],
1987
+ template: \`
1988
+ <div class="grid gap-5 md:grid-cols-2">
1989
+ <africanies-text-input
1990
+ label="Tracking number"
1991
+ hint="As printed on the airway bill"
1992
+ placeholder="e.g. AWB-12345"
1993
+ [(value)]="tracking"
1994
+ />
1995
+
1996
+ <africanies-text-input label="Reference" placeholder="Optional internal ref" [(value)]="reference">
1997
+ <africanies-icon prefix name="anchor" [size]="16" />
1998
+ </africanies-text-input>
1999
+
2000
+ <africanies-text-input
2001
+ label="Consignee email"
2002
+ error="Enter a valid email address"
2003
+ [(value)]="email"
2004
+ />
2005
+
2006
+ <africanies-text-input label="Locked field" [disabled]="true" [(value)]="locked" />
2007
+ </div>
2008
+ \`,
2009
+ })
2010
+ export class ShipmentReferenceFormComponent {
2011
+ protected readonly tracking = signal('');
2012
+ protected readonly reference = signal('');
2013
+ protected readonly email = signal('not-an-email');
2014
+ protected readonly locked = signal('WH-LOCKED-01');
2015
+ }
2016
+ `,zr=`
2017
+ // Same label / hint / error pattern as text inputs, just multi-line.
2018
+
2019
+ import { Component, signal } from '@angular/core';
2020
+ import { TextareaComponent } from '@africanies/africanies-ui';
2021
+
2022
+ @Component({
2023
+ selector: 'app-shipment-instructions-form',
2024
+ standalone: true,
2025
+ imports: [TextareaComponent],
2026
+ template: \`
2027
+ <div class="grid gap-5 md:grid-cols-2">
2028
+ <africanies-textarea
2029
+ label="Special instructions"
2030
+ hint="Visible to warehouse staff"
2031
+ placeholder="Fragile \u2014 keep upright"
2032
+ [(value)]="instructions"
2033
+ />
2034
+
2035
+ <africanies-textarea
2036
+ label="Notes"
2037
+ error="Notes cannot exceed 500 characters"
2038
+ [(value)]="notesError"
2039
+ />
2040
+ </div>
2041
+ \`,
2042
+ })
2043
+ export class ShipmentInstructionsFormComponent {
2044
+ protected readonly instructions = signal('');
2045
+ protected readonly notesError = signal('');
2046
+ }
2047
+ `,Gr=`
2048
+ // Bound value is number | null. Prefix/suffix are display chrome only \u2014
2049
+ // commas in the UI are cosmetic; send the numeric value to the API.
2050
+
2051
+ import { DecimalPipe } from '@angular/common';
2052
+ import { Component, signal } from '@angular/core';
2053
+ import { NumberInputComponent } from '@africanies/africanies-ui';
2054
+
2055
+ @Component({
2056
+ selector: 'app-shipment-value-form',
2057
+ standalone: true,
2058
+ imports: [DecimalPipe, NumberInputComponent],
2059
+ template: \`
2060
+ <div class="grid gap-5 md:grid-cols-2">
2061
+ <africanies-number-input label="Declared value (USD)" [(value)]="amount">
2062
+ <span prefix>$</span>
2063
+ </africanies-number-input>
2064
+
2065
+ <africanies-number-input label="Weight" hint="Kilograms" [(value)]="weight">
2066
+ <span suffix>kg</span>
2067
+ </africanies-number-input>
2068
+ </div>
2069
+
2070
+ <p class="text-body-sm">
2071
+ Stored amount:
2072
+ {{ amount() == null ? '\u2014' : (amount()! | number: '1.0-6') }}
2073
+ </p>
2074
+ \`,
2075
+ })
2076
+ export class ShipmentValueFormComponent {
2077
+ protected readonly amount = signal<number | null>(12500);
2078
+ protected readonly weight = signal<number | null>(24.5);
2079
+ }
2080
+ `,Vr=`
2081
+ // Single/multi select binds [(selected)]. [searchable] for long lists.
2082
+ // allowFreeText = inline tags; [create] opens a modal (needs provideAfricaniesUiOverlays()).
2083
+
2084
+ import { Component, signal } from '@angular/core';
2085
+ import { AfricaniesIconComponent } from '@africanies/africanies-icons';
2086
+ import {
2087
+ SelectComponent,
2088
+ type SelectCreateConfig,
2089
+ type SelectOption,
2090
+ } from '@africanies/africanies-ui';
2091
+
2092
+ // Example modal close payload \u2014 your modal component returns this shape.
2093
+ interface WarehouseCreateResult {
2094
+ id: string;
2095
+ name: string;
2096
+ }
2097
+
2098
+ @Component({
2099
+ selector: 'app-shipment-routing-form',
2100
+ standalone: true,
2101
+ imports: [SelectComponent, AfricaniesIconComponent],
2102
+ template: \`
2103
+ <div class="grid gap-5 md:grid-cols-2">
2104
+ <africanies-select
2105
+ label="Warehouse"
2106
+ [options]="warehouses()"
2107
+ [searchable]="true"
2108
+ [(selected)]="selectedWarehouse"
2109
+ >
2110
+ <africanies-icon prefix name="warehouse" [size]="16" />
2111
+ </africanies-select>
2112
+
2113
+ <africanies-select
2114
+ label="Tags"
2115
+ [searchable]="true"
2116
+ [allowFreeText]="true"
2117
+ [multiple]="true"
2118
+ [maxSelected]="4"
2119
+ [(options)]="tags"
2120
+ [(selected)]="selectedTags"
2121
+ />
2122
+
2123
+ <africanies-select
2124
+ label="Incoterm"
2125
+ hint="Plain list \u2014 searchable off"
2126
+ [options]="incoterms"
2127
+ [(selected)]="selectedIncoterm"
2128
+ />
2129
+
2130
+ <africanies-select
2131
+ label="Carrier"
2132
+ hint="Create opens a modal \u2014 requires provideAfricaniesUiOverlays()"
2133
+ [options]="carriers()"
2134
+ [searchable]="true"
2135
+ [create]="warehouseCreate"
2136
+ [(options)]="carriers"
2137
+ [(selected)]="selectedCarrier"
2138
+ >
2139
+ <africanies-icon prefix name="airplane" [size]="16" />
2140
+ </africanies-select>
2141
+ </div>
2142
+ \`,
2143
+ })
2144
+ export class ShipmentRoutingFormComponent {
2145
+ protected readonly warehouses = signal<SelectOption<string>[]>([
2146
+ { label: 'Lagos Hub', value: 'los', prefix: 'warehouse', suffix: 'globe' },
2147
+ { label: 'Accra Depot', value: 'acc', prefix: 'warehouse' },
2148
+ ]);
2149
+
2150
+ protected readonly selectedWarehouse = signal<SelectOption<string> | null>(null);
2151
+
2152
+ protected readonly tags = signal<SelectOption<string>[]>([
2153
+ { label: 'Fragile', value: 'fragile', prefix: 'warning' },
2154
+ { label: 'Priority', value: 'priority', prefix: 'alarm' },
2155
+ ]);
2156
+ protected readonly selectedTags = signal<SelectOption<string>[]>([]);
2157
+
2158
+ protected readonly incoterms: SelectOption<string>[] = [
2159
+ { label: 'DDP', value: 'ddp' },
2160
+ { label: 'DAP', value: 'dap' },
2161
+ { label: 'EXW', value: 'exw' },
2162
+ ];
2163
+ protected readonly selectedIncoterm = signal<SelectOption<string> | null>(null);
2164
+
2165
+ protected readonly carriers = signal<SelectOption<string>[]>([
2166
+ { label: 'DHL Express', value: 'dhl', prefix: 'airplane' },
2167
+ { label: 'FedEx', value: 'fedex', prefix: 'airplane' },
2168
+ ]);
2169
+ protected readonly selectedCarrier = signal<SelectOption<string> | null>(null);
2170
+
2171
+ // Modal-backed create \u2014 component is your standalone creation form.
2172
+ protected readonly warehouseCreate: SelectCreateConfig<WarehouseCreateResult, string> = {
2173
+ label: 'Add warehouse',
2174
+ component: WarehouseCreateModalComponent,
2175
+ data: { region: 'west-africa' },
2176
+ mapResult: (result) => ({
2177
+ label: result.name,
2178
+ value: result.id,
2179
+ prefix: 'warehouse',
2180
+ }),
2181
+ };
2182
+ }
2183
+
2184
+ // Stub \u2014 replace with your real modal panel opened via ModalService.
2185
+ @Component({ selector: 'app-warehouse-create-modal', standalone: true, template: '' })
2186
+ export class WarehouseCreateModalComponent {}
2187
+
2188
+ // app.config.ts \u2014 required once per app when any select uses [create]:
2189
+ //
2190
+ // import { provideAfricaniesUiOverlays } from '@africanies/africanies-ui';
2191
+ //
2192
+ // export const appConfig: ApplicationConfig = {
2193
+ // providers: [provideAfricaniesUiOverlays(), /* \u2026 */],
2194
+ // };
2195
+ `,Hr=`
2196
+ // Checkbox / toggle for booleans; radio for exactly-one-of. All use [(value)].
2197
+
2198
+ import { Component, signal } from '@angular/core';
2199
+ import {
2200
+ CheckboxComponent,
2201
+ RadioComponent,
2202
+ ToggleComponent,
2203
+ type RadioOption,
2204
+ } from '@africanies/africanies-ui';
2205
+
2206
+ @Component({
2207
+ selector: 'app-shipment-options-form',
2208
+ standalone: true,
2209
+ imports: [CheckboxComponent, ToggleComponent, RadioComponent],
2210
+ template: \`
2211
+ <div class="grid gap-6 md:grid-cols-2">
2212
+ <div class="flex flex-col gap-4">
2213
+ <africanies-checkbox label="Require signature on delivery" [(value)]="signature" />
2214
+
2215
+ <africanies-checkbox
2216
+ label="Insure this shipment"
2217
+ hint="Adds 0.8% of declared value"
2218
+ [(value)]="insured"
2219
+ />
2220
+
2221
+ <africanies-checkbox
2222
+ label="Accept terms"
2223
+ error="You must accept the terms"
2224
+ [(value)]="terms"
2225
+ />
2226
+
2227
+ <africanies-toggle label="Notify consignee by SMS" [(value)]="smsNotify" />
2228
+ <africanies-toggle label="Hold at depot" [(value)]="holdAtDepot" />
2229
+ <africanies-toggle
2230
+ label="Require customs hold"
2231
+ hint="Waits for the API \u2014 the switch does not flip until the save succeeds."
2232
+ [value]="customsHold()"
2233
+ [loading]="customsHoldSaving()"
2234
+ (valueChange)="saveCustomsHold($event)"
2235
+ />
2236
+ </div>
2237
+
2238
+ <africanies-radio
2239
+ label="Service level"
2240
+ [options]="serviceLevels"
2241
+ [(value)]="serviceLevel"
2242
+ />
2243
+ </div>
2244
+ \`,
2245
+ })
2246
+ export class ShipmentOptionsFormComponent {
2247
+ protected readonly signature = signal(true);
2248
+ protected readonly insured = signal(false);
2249
+ protected readonly terms = signal(false);
2250
+ protected readonly smsNotify = signal(true);
2251
+ protected readonly holdAtDepot = signal(false);
2252
+ protected readonly customsHold = signal(false);
2253
+ protected readonly customsHoldSaving = signal(false);
2254
+
2255
+ protected readonly serviceLevels: RadioOption<string>[] = [
2256
+ { label: 'Economy', value: 'economy' },
2257
+ { label: 'Standard', value: 'standard' },
2258
+ { label: 'Express', value: 'express' },
2259
+ ];
2260
+ protected readonly serviceLevel = signal<string | null>('standard');
2261
+
2262
+ protected saveCustomsHold(next: boolean): void {
2263
+ this.customsHoldSaving.set(true);
2264
+ window.setTimeout(() => {
2265
+ this.customsHold.set(next);
2266
+ this.customsHoldSaving.set(false);
2267
+ }, 1200);
2268
+ }
2269
+ }
2270
+ `,Wr=`
2271
+ // Date picker emits YYYY-MM-DD (string | null). Keep that ISO string as-is.
2272
+
2273
+ import { Component, signal } from '@angular/core';
2274
+ import { DatePickerComponent } from '@africanies/africanies-ui';
2275
+
2276
+ @Component({
2277
+ selector: 'app-shipment-schedule-form',
2278
+ standalone: true,
2279
+ imports: [DatePickerComponent],
2280
+ template: \`
2281
+ <div class="grid gap-5 md:grid-cols-2">
2282
+ <africanies-date-picker label="Ready date" hint="Local pickup day" [(value)]="readyDate" />
2283
+ <africanies-date-picker
2284
+ label="Cutoff"
2285
+ error="Cutoff must be after ready date"
2286
+ [(value)]="cutoffDate"
2287
+ />
2288
+ </div>
2289
+ \`,
2290
+ })
2291
+ export class ShipmentScheduleFormComponent {
2292
+ protected readonly readyDate = signal<string | null>('2026-08-15');
2293
+ protected readonly cutoffDate = signal<string | null>(null);
2294
+ }
2295
+ `,qr=`
2296
+ // Google Places address field. Bootstrap with provideGooglePlaces({ apiKey }).
2297
+ // [(value)] is AddressPlace | null; (placeSelected) fires when a suggestion is chosen.
2298
+
2299
+ import { Component, signal } from '@angular/core';
2300
+ import { AfricaniesIconComponent } from '@africanies/africanies-icons';
2301
+ import {
2302
+ AddressInputComponent,
2303
+ type AddressPlace,
2304
+ provideGooglePlaces,
2305
+ } from '@africanies/africanies-ui';
2306
+
2307
+ // app.config.ts
2308
+ export const appConfig = {
2309
+ providers: [
2310
+ provideGooglePlaces({ apiKey: import.meta.env['NG_APP_GOOGLE_MAPS_KEY'] }),
2311
+ ],
2312
+ };
2313
+
2314
+ @Component({
2315
+ selector: 'app-pickup-address-form',
2316
+ standalone: true,
2317
+ imports: [AddressInputComponent, AfricaniesIconComponent],
2318
+ template: \`
2319
+ <africanies-address-input
2320
+ label="Pickup address"
2321
+ hint="Start typing \u2014 pick a Google suggestion"
2322
+ placeholder="Street, city, or landmark"
2323
+ [countries]="['ng', 'gh', 'za']"
2324
+ [(value)]="pickup"
2325
+ (placeSelected)="onPlace($event)"
2326
+ >
2327
+ <africanies-icon prefix name="map-marker" [size]="16" />
2328
+ </africanies-address-input>
2329
+
2330
+ <pre>{{ pickup() | json }}</pre>
2331
+ \`,
2332
+ })
2333
+ export class PickupAddressFormComponent {
2334
+ protected readonly pickup = signal<AddressPlace | null>(null);
2335
+
2336
+ protected onPlace(place: AddressPlace): void {
2337
+ console.log('selected', place);
2338
+ }
2339
+ }
2340
+ `,$r=`
2341
+ // Browse, drag-drop, or camera. You get FileUploadResult[] on (filesSelected) \u2014
2342
+ // upload them yourself. accept filters client-side; mismatches are skipped.
2343
+ // Added files include an underlined View action for a larger preview.
2344
+
2345
+ import { Component, signal } from '@angular/core';
2346
+ import {
2347
+ FileUploadComponent,
2348
+ type FileUploadResult,
2349
+ } from '@africanies/africanies-ui';
2350
+
2351
+ @Component({
2352
+ selector: 'app-kyc-upload-form',
2353
+ standalone: true,
2354
+ imports: [FileUploadComponent],
2355
+ template: \`
2356
+ <africanies-file-upload
2357
+ variant="dropzone"
2358
+ label="Identity document"
2359
+ accept="image/*,.pdf"
2360
+ [allowCamera]="true"
2361
+ [multiple]="true"
2362
+ (filesSelected)="onFiles($event)"
2363
+ />
2364
+ \`,
2365
+ })
2366
+ export class KycUploadFormComponent {
2367
+ protected readonly files = signal<FileUploadResult[]>([]);
2368
+
2369
+ protected onFiles(next: FileUploadResult[]): void {
2370
+ this.files.set(next);
2371
+ }
2372
+ }
2373
+ `,Kr=`
2374
+ // OTP is one string, not an array. (resend) is your cue to call the API \u2014
2375
+ // the control only runs the cooldown UI. (completed) fires when all digits are in.
2376
+ // variant="masked" hides each digit (PIN-style); the model is still the real code.
2377
+
2378
+ import { Component, signal } from '@angular/core';
2379
+ import { OtpInputComponent } from '@africanies/africanies-ui';
2380
+
2381
+ @Component({
2382
+ selector: 'app-verify-otp-form',
2383
+ standalone: true,
2384
+ imports: [OtpInputComponent],
2385
+ template: \`
2386
+ <africanies-otp-input
2387
+ label="Verification code"
2388
+ hint="Enter the 6-digit code we sent"
2389
+ [resendCooldown]="60"
2390
+ [(value)]="code"
2391
+ (completed)="verify($event)"
2392
+ (resend)="sendAgain()"
2393
+ />
2394
+
2395
+ <africanies-otp-input
2396
+ label="Transaction PIN"
2397
+ variant="masked"
2398
+ [length]="4"
2399
+ [showResend]="false"
2400
+ [(value)]="pin"
2401
+ (completed)="confirmPin($event)"
2402
+ />
2403
+ \`,
2404
+ })
2405
+ export class VerifyOtpFormComponent {
2406
+ protected readonly code = signal('');
2407
+ protected readonly pin = signal('');
2408
+
2409
+ protected verify(code: string): void {
2410
+ // POST /auth/verify { code }
2411
+ console.log('OTP complete', code);
2412
+ }
2413
+
2414
+ protected confirmPin(pin: string): void {
2415
+ // POST /wallet/confirm { pin }
2416
+ console.log('PIN complete', pin);
2417
+ }
2418
+
2419
+ protected sendAgain(): void {
2420
+ // POST /auth/resend-otp
2421
+ console.log('Resend OTP');
2422
+ }
2423
+ }
2424
+ `,Yr=`
2425
+ // Same signals you bound with [(value)] / [(selected)] \u2014 read them anywhere.
2426
+ // Call them as functions: tracking(), amount(). Don't forget the ().
2427
+
2428
+ import { DecimalPipe } from '@angular/common';
2429
+ import { Component, signal } from '@angular/core';
2430
+ import { type FileUploadResult, type SelectOption } from '@africanies/africanies-ui';
2431
+
2432
+ @Component({
2433
+ selector: 'app-shipment-summary',
2434
+ standalone: true,
2435
+ imports: [DecimalPipe],
2436
+ template: \`
2437
+ <dl class="m-0 grid gap-2 text-body-sm sm:grid-cols-2">
2438
+ <div>
2439
+ <dt class="text-neutral-600">Tracking</dt>
2440
+ <dd class="m-0 font-medium">{{ tracking() || '\u2014' }}</dd>
2441
+ </div>
2442
+
2443
+ <div>
2444
+ <dt class="text-neutral-600">Amount</dt>
2445
+ <dd class="m-0 font-medium">
2446
+ {{ amount() == null ? '\u2014' : (amount()! | number: '1.0-6') }}
2447
+ </dd>
2448
+ </div>
2449
+
2450
+ <div>
2451
+ <dt class="text-neutral-600">Warehouse</dt>
2452
+ <dd class="m-0 font-medium">{{ selectedWarehouse()?.label ?? '\u2014' }}</dd>
2453
+ </div>
2454
+
2455
+ <div>
2456
+ <dt class="text-neutral-600">Tags</dt>
2457
+ <dd class="m-0 font-medium">{{ tagSummary() }}</dd>
2458
+ </div>
2459
+
2460
+ <div>
2461
+ <dt class="text-neutral-600">Service</dt>
2462
+ <dd class="m-0 font-medium">{{ serviceLevel() ?? '\u2014' }}</dd>
2463
+ </div>
2464
+
2465
+ <div>
2466
+ <dt class="text-neutral-600">Files</dt>
2467
+ <dd class="m-0 font-medium">
2468
+ {{
2469
+ files().length
2470
+ ? files()
2471
+ .map((f) => f.file.name)
2472
+ .join(', ')
2473
+ : '\u2014'
2474
+ }}
2475
+ </dd>
2476
+ </div>
2477
+ </dl>
2478
+ \`,
2479
+ })
2480
+ export class ShipmentSummaryComponent {
2481
+ // These signals are the same instances bound in sibling form controls.
2482
+ protected readonly tracking = signal('');
2483
+ protected readonly amount = signal<number | null>(12500);
2484
+ protected readonly selectedWarehouse = signal<SelectOption<string> | null>(null);
2485
+ protected readonly selectedTags = signal<SelectOption<string>[]>([]);
2486
+ protected readonly serviceLevel = signal<string | null>('standard');
2487
+ protected readonly files = signal<FileUploadResult[]>([]);
2488
+
2489
+ protected tagSummary(): string {
2490
+ const selected = this.selectedTags();
2491
+ if (!selected.length) {
2492
+ return '\u2014';
2493
+ }
2494
+ return selected.map((t) => t.label).join(', ');
2495
+ }
2496
+ }
2497
+ `;var Qr=`// Point Tailwind at the africanies-theme preset and scan @africanies/africanies-ui so its classes aren't purged.
2498
+ // Don't re-declare token colors locally \u2014 extend the preset.
2499
+
2500
+ // tailwind.config.js \u2014 monorepo paths (adjust __dirname to your app root)
2501
+ const path = require('node:path');
2502
+
2503
+ module.exports = {
2504
+ presets: [require('@africanies/africanies-theme/tailwind-preset')],
2505
+ content: [
2506
+ path.join(__dirname, 'src/**/*.{html,ts}'),
2507
+ // Required: scan the UI library so its literal Tailwind classes survive purge.
2508
+ path.join(__dirname, '../../libs/africanies-ui/src/**/*.{html,ts}'),
2509
+ path.join(__dirname, '../../libs/africanies-theme/src/**/*.{html,ts}'),
2510
+ // Published-app equivalent:
2511
+ // './src/**/*.{html,ts}',
2512
+ // './node_modules/@africanies/africanies-ui/**/*.{js,mjs}',
2513
+ ],
2514
+ };
2515
+ `,Xr=`// Ink / surface utilities \u2014 use literal class strings (Tailwind can't see dynamic names).
2516
+
2517
+ <div class="bg-ink text-white">Ink surface</div>
2518
+ <div class="bg-ink-brand text-white">Brand ink panel</div>
2519
+ <div class="bg-ink-950 text-white">Near-black panel</div>
2520
+ <div class="bg-black text-white">Pure black</div>
2521
+ <div class="bg-white text-ink">White surface</div>
2522
+ `,Jr=`// Borders, muted copy, page chrome \u2014 stay on the token scale, skip raw hex.
2523
+
2524
+ <div class="rounded-lg border border-border bg-background-welcome p-4">
2525
+ <p class="text-neutral-600">Muted supporting copy</p>
2526
+ <p class="text-ink dark:text-white">Primary body on welcome background</p>
2527
+ </div>
2528
+
2529
+ <span class="text-neutral-400">Disabled / placeholder tone</span>
2530
+ <span class="text-neutral-300">Light divider accent</span>
2531
+ `,eo=`// SFN / export green \u2014 fine for static marketing. Mode-aware chrome? Use ModeColorService.
2532
+
2533
+ <span class="rounded-md bg-export px-2 py-1 text-white">Primary</span>
2534
+ <span class="rounded-md bg-export-subtle px-2 py-1 text-export">Subtle</span>
2535
+ <span class="text-export-light">Light accent text</span>
2536
+ <div class="rounded-xl bg-export-tint p-5">Tinted panel</div>
2537
+ `,to=`// STN / import orange \u2014 same idea as export; prefer ModeColorService when mode can flip.
2538
+
2539
+ <span class="rounded-md bg-import px-2 py-1 text-white">Primary</span>
2540
+ <span class="rounded-md bg-import-subtle px-2 py-1 text-import">Subtle</span>
2541
+ <span class="text-import-light">Light accent text</span>
2542
+ <div class="rounded-xl bg-import-tint p-5">Tinted panel</div>
2543
+ `,no=`// Status colors \u2014 mode-agnostic. Don't map danger/warning to export/import.
2544
+
2545
+ <span class="rounded-md bg-danger-subtle px-2 py-1 text-danger">Danger</span>
2546
+ <span class="rounded-md bg-danger px-2 py-1 text-white">Danger solid</span>
2547
+ <span class="rounded-md bg-warning-subtle px-2 py-1 text-warning-dark">Warning</span>
2548
+ <span class="text-danger-dark">Inline error copy</span>
2549
+ `,io=`// Typography scale from the theme preset. Prefer these over arbitrary text-[Npx].
2550
+
2551
+ <h1 class="text-heading-1 text-ink dark:text-white">Ship across continents</h1>
2552
+ <h2 class="text-heading-2 text-ink dark:text-white">Shipment details</h2>
2553
+ <h3 class="text-heading-3 text-ink dark:text-white">Cargo summary</h3>
2554
+ <p class="text-body-lg text-ink dark:text-white">
2555
+ Supporting sentence for denser marketing or empty states.
2556
+ </p>
2557
+ <p class="text-body text-ink dark:text-white">
2558
+ Default reading size for forms, tables, and dialogs.
2559
+ </p>
2560
+ <p class="text-body-sm text-neutral-600">Secondary copy and dense metadata.</p>
2561
+ <span class="text-caption text-neutral-600">Hints, timestamps, and chip labels.</span>
2562
+ `,ro=`// Primary chrome that follows SFN/STN. Bind [class] to modeColor.classes() literals \u2014
2563
+ // don't interpolate Tailwind token names yourself.
2564
+
2565
+ import { Component, inject } from '@angular/core';
2566
+ import { ModeColorService } from '@africanies/africanies-theme';
2567
+ import { ButtonComponent } from '@africanies/africanies-ui';
2568
+
2569
+ @Component({
2570
+ selector: 'app-mode-accent-demo',
2571
+ standalone: true,
2572
+ imports: [ButtonComponent],
2573
+ template: \`
2574
+ @let colors = modeColor.classes();
2575
+
2576
+ <button africanies-button type="button" [class]="colors.primary">
2577
+ Mode-aware primary CTA
2578
+ </button>
2579
+
2580
+ <span [class]="colors.text">Accent label</span>
2581
+
2582
+ <div class="rounded-xl p-5" [class]="colors.bgSubtle">
2583
+ <p class="m-0 text-caption font-medium uppercase tracking-wide" [class]="colors.text">
2584
+ SFN export / STN import
2585
+ </p>
2586
+ <p class="mt-2 m-0 text-heading-3 text-ink dark:text-white">Shipment headline</p>
2587
+ </div>
2588
+
2589
+ <div
2590
+ class="rounded-lg px-3 py-2 transition"
2591
+ [class]="colors.soft + ' ' + colors.softHover"
2592
+ >
2593
+ Selected row highlight
2594
+ </div>
2595
+ \`,
2596
+ })
2597
+ export class ModeAccentDemoComponent {
2598
+ protected readonly modeColor = inject(ModeColorService);
2599
+ }
2600
+ `,oo=`// Shared SVG sprite via <africanies-icon>. Type dynamic names as IconName for autocomplete.
2601
+
2602
+ import { Component } from '@angular/core';
2603
+ import { AfricaniesIconComponent, type IconName } from '@africanies/africanies-icons';
2604
+
2605
+ @Component({
2606
+ selector: 'app-icon-demo',
2607
+ standalone: true,
2608
+ imports: [AfricaniesIconComponent],
2609
+ template: \`
2610
+ <africanies-icon name="airplane" [size]="24" />
2611
+ <africanies-icon name="warehouse" [size]="24" />
2612
+ <africanies-icon name="warning" [size]="24" />
2613
+ <africanies-icon [name]="dynamicIcon" [size]="16" />
2614
+ \`,
2615
+ })
2616
+ export class IconDemoComponent {
2617
+ protected readonly dynamicIcon: IconName = 'anchor';
2618
+ }
2619
+ `;var so=`// Shared API/UI shapes \u2014 type-only, no providers. Import from here instead of reinventing.
2620
+
2621
+ import type {
2622
+ AsyncQueryStateModel,
2623
+ ApiResponseModel,
2624
+ CountryModel,
2625
+ CurrencyModel,
2626
+ PaginationMetaModel,
2627
+ PaginationQueryParamsModel,
2628
+ PaymentMethodModel,
2629
+ ResourceId,
2630
+ ShippingMode,
2631
+ ModeConfigDataModel,
2632
+ } from '@africanies/africanies-models';
2633
+ `;var co=`// You own the items array \u2014 breadcrumb won't walk the route tree for you.
2634
+ // Link every ancestor; leave routerLink off the last crumb (current page).
2635
+
2636
+ import { Component, computed, inject, signal } from '@angular/core';
2637
+ import { toSignal } from '@angular/core/rxjs-interop';
2638
+ import { NavigationEnd, Router } from '@angular/router';
2639
+ import { BreadcrumbComponent, type AfricaniesNavItem } from '@africanies/africanies-ui';
2640
+ import { filter, map, startWith } from 'rxjs';
2641
+
2642
+ @Component({
2643
+ selector: 'app-shipment-detail-chrome',
2644
+ standalone: true,
2645
+ imports: [BreadcrumbComponent],
2646
+ template: \`
2647
+ <africanies-breadcrumb [items]="crumbs()" />
2648
+ \`,
2649
+ })
2650
+ export class ShipmentDetailChromeComponent {
2651
+ private readonly router = inject(Router);
2652
+
2653
+ private readonly url = toSignal(
2654
+ this.router.events.pipe(
2655
+ filter((e): e is NavigationEnd => e instanceof NavigationEnd),
2656
+ map(() => this.router.url),
2657
+ startWith(this.router.url),
2658
+ ),
2659
+ { initialValue: this.router.url },
2660
+ );
2661
+
2662
+ protected readonly shipmentRef = signal('SFN-1042');
2663
+
2664
+ protected readonly crumbs = computed((): AfricaniesNavItem[] => {
2665
+ const ref = this.shipmentRef();
2666
+ const path = this.url().split('?')[0] ?? '';
2667
+ const section = path.includes('/documents')
2668
+ ? 'Documents'
2669
+ : path.includes('/events')
2670
+ ? 'Events'
2671
+ : 'Overview';
2672
+
2673
+ return [
2674
+ { id: 'home', label: 'Home', routerLink: '/overview', icon: 'home' },
2675
+ { id: 'shipments', label: 'Shipments', routerLink: '/shipments' },
2676
+ { id: 'detail', label: ref, routerLink: '/shipments/' + ref + '/overview' },
2677
+ { id: 'section', label: section }, // current page \u2014 no routerLink
2678
+ ];
2679
+ });
2680
+ }
2681
+ `,po=`// URL-driven tabs + child routes. Active tab follows Router.isActive (survives refresh).
2682
+ // Put routerLink on each item, render <router-outlet /> below. Don't mix with africaniesTabDef.
2683
+ // keepScroll defaults to true \u2014 navigates with { scroll: 'manual' } so the page does not jump to the top.
2684
+ // Pass [keepScroll]="false" if you want the app's normal scrollPositionRestoration behaviour.
2685
+
2686
+ import { Component, signal } from '@angular/core';
2687
+ import { RouterOutlet } from '@angular/router';
2688
+ import { TabsComponent, type AfricaniesNavItem } from '@africanies/africanies-ui';
2689
+
2690
+ @Component({
2691
+ selector: 'app-shipment-detail-tabs',
2692
+ standalone: true,
2693
+ imports: [TabsComponent, RouterOutlet],
2694
+ template: \`
2695
+ <africanies-tabs
2696
+ [items]="routeTabs"
2697
+ [(activeId)]="routeTabId"
2698
+ ariaLabel="Shipment sections"
2699
+ />
2700
+
2701
+ <div class="mt-4">
2702
+ <router-outlet />
2703
+ </div>
2704
+ \`,
2705
+ })
2706
+ export class ShipmentDetailTabsComponent {
2707
+ protected readonly routeTabId = signal<string | null>(null);
2708
+
2709
+ protected readonly routeTabs: AfricaniesNavItem[] = [
2710
+ {
2711
+ id: 'overview',
2712
+ label: 'Overview',
2713
+ icon: 'inbox',
2714
+ routerLink: '/shipments/SFN-1042/overview',
2715
+ },
2716
+ {
2717
+ id: 'documents',
2718
+ label: 'Documents',
2719
+ icon: 'file-add',
2720
+ routerLink: '/shipments/SFN-1042/documents',
2721
+ },
2722
+ {
2723
+ id: 'events',
2724
+ label: 'Events',
2725
+ icon: 'alarm',
2726
+ routerLink: '/shipments/SFN-1042/events',
2727
+ },
2728
+ ];
2729
+ }
2730
+
2731
+ // Example child route config (app.routes.ts excerpt):
2732
+ //
2733
+ // {
2734
+ // path: 'shipments/:ref',
2735
+ // component: ShipmentDetailTabsComponent,
2736
+ // children: [
2737
+ // { path: 'overview', loadComponent: () => import('./overview').then(m => m.OverviewPage) },
2738
+ // { path: 'documents', loadComponent: () => import('./documents').then(m => m.DocumentsPage) },
2739
+ // { path: 'events', loadComponent: () => import('./events').then(m => m.EventsPage) },
2740
+ // { path: '', pathMatch: 'full', redirectTo: 'overview' },
2741
+ // ],
2742
+ // }
2743
+ `,mo=`// Pill switcher on one path \u2014 same routerLink, different queryParams per segment.
2744
+ // Active pill matches the query on cold load.
2745
+ // keepScroll defaults to true \u2014 no jump to top when the query changes (same as routed tabs).
2746
+
2747
+ import { Component, inject, signal } from '@angular/core';
2748
+ import { toSignal } from '@angular/core/rxjs-interop';
2749
+ import { ActivatedRoute } from '@angular/router';
2750
+ import { SegmentComponent, type AfricaniesNavItem } from '@africanies/africanies-ui';
2751
+ import { map } from 'rxjs';
2752
+
2753
+ @Component({
2754
+ selector: 'app-shipment-list-density',
2755
+ standalone: true,
2756
+ imports: [SegmentComponent],
2757
+ template: \`
2758
+ <africanies-segment
2759
+ [items]="densitySegments"
2760
+ [(activeId)]="densityId"
2761
+ ariaLabel="List density"
2762
+ />
2763
+
2764
+ <p class="mt-3 text-body-sm">
2765
+ Active density from URL:
2766
+ <span class="font-medium">{{ densityId() ?? '\u2014' }}</span>
2767
+ </p>
2768
+ \`,
2769
+ })
2770
+ export class ShipmentListDensityComponent {
2771
+ private readonly route = inject(ActivatedRoute);
2772
+
2773
+ protected readonly densityFromUrl = toSignal(
2774
+ this.route.queryParamMap.pipe(map((p) => p.get('density') ?? 'comfortable')),
2775
+ { initialValue: 'comfortable' },
2776
+ );
2777
+
2778
+ protected readonly densityId = signal<string | null>(null);
2779
+
2780
+ protected readonly densitySegments: AfricaniesNavItem[] = [
2781
+ {
2782
+ id: 'comfortable',
2783
+ label: 'Comfortable',
2784
+ routerLink: '/shipments',
2785
+ queryParams: { density: 'comfortable' },
2786
+ },
2787
+ {
2788
+ id: 'compact',
2789
+ label: 'Compact',
2790
+ routerLink: '/shipments',
2791
+ queryParams: { density: 'compact' },
2792
+ },
2793
+ {
2794
+ id: 'dense',
2795
+ label: 'Dense',
2796
+ routerLink: '/shipments',
2797
+ queryParams: { density: 'dense' },
2798
+ },
2799
+ ];
2800
+ }
2801
+ `,uo=`// In-page tabs \u2014 no router. Selection is [(activeId)] only; panels live in africaniesTabDef.
2802
+ // Match template ids to AfricaniesNavItem.id. Want bookmarkable tabs? Use routed tabs instead.
2803
+
2804
+ import { Component, signal } from '@angular/core';
2805
+ import { TabDefDirective, TabsComponent, type AfricaniesNavItem } from '@africanies/africanies-ui';
2806
+
2807
+ @Component({
2808
+ selector: 'app-shipment-editor-local-tabs',
2809
+ standalone: true,
2810
+ imports: [TabsComponent, TabDefDirective],
2811
+ template: \`
2812
+ <africanies-tabs
2813
+ [items]="localTabs"
2814
+ [(activeId)]="localTabId"
2815
+ ariaLabel="Editor sections"
2816
+ >
2817
+ <ng-template africaniesTabDef="general">
2818
+ <p class="m-0 text-body">General fields \u2014 not tied to the URL.</p>
2819
+ </ng-template>
2820
+
2821
+ <ng-template africaniesTabDef="parties">
2822
+ <p class="m-0 text-body">Shipper and consignee \u2014 local panel only.</p>
2823
+ </ng-template>
2824
+
2825
+ <ng-template africaniesTabDef="charges">
2826
+ <p class="m-0 text-body">Charges and duties \u2014 local panel only.</p>
2827
+ </ng-template>
2828
+ </africanies-tabs>
2829
+ \`,
2830
+ })
2831
+ export class ShipmentEditorLocalTabsComponent {
2832
+ protected readonly localTabId = signal('general');
2833
+
2834
+ protected readonly localTabs: AfricaniesNavItem[] = [
2835
+ { id: 'general', label: 'General', icon: 'edit' },
2836
+ { id: 'parties', label: 'Parties', icon: 'user' },
2837
+ { id: 'charges', label: 'Charges', icon: 'wallet' },
2838
+ ];
2839
+ }
2840
+ `,fo=`// Ink-spine side nav \u2014 icons, nested children, collapse to icon rail.
2841
+ // Soft active highlight; collapsed: hover an icon for a label blade.
2842
+ // Collapse all sits above the list (top right) when any section is open.
2843
+
2844
+ import { Component, signal } from '@angular/core';
2845
+ import {
2846
+ SideNavComponent,
2847
+ type AfricaniesSideNavItem,
2848
+ } from '@africanies/africanies-ui';
2849
+
2850
+ @Component({
2851
+ selector: 'app-shell-nav',
2852
+ standalone: true,
2853
+ imports: [SideNavComponent],
2854
+ template: \`
2855
+ <div class="h-[28rem] overflow-visible rounded-xl border border-border dark:border-white/10">
2856
+ <africanies-side-nav
2857
+ [items]="nav"
2858
+ [(collapsed)]="collapsed"
2859
+ [(activeId)]="activeId"
2860
+ ariaLabel="Portal"
2861
+ >
2862
+ </africanies-side-nav>
2863
+ </div>
2864
+ \`,
2865
+ })
2866
+ export class ShellNavComponent {
2867
+ protected readonly collapsed = signal(false);
2868
+ protected readonly activeId = signal('shipments');
2869
+
2870
+ protected readonly nav: AfricaniesSideNavItem[] = [
2871
+ { id: 'home', label: 'Home', icon: 'home' },
2872
+ {
2873
+ id: 'shipments',
2874
+ label: 'Shipments',
2875
+ icon: 'truck',
2876
+ badge: true,
2877
+ children: [
2878
+ { id: 'track', label: 'Track', icon: 'map-marker', badge: true },
2879
+ { id: 'create', label: 'Create', icon: 'plus' },
2880
+ ],
2881
+ },
2882
+ { id: 'warehouse', label: 'Warehouse', icon: 'warehouse' },
2883
+ { id: 'settings', label: 'Settings', icon: 'cog' },
2884
+ ];
2885
+ }
2886
+ `,go=`// Dedicated app shell \u2014 breadcrumbs + Back in the content column;
2887
+ // header holds clock, notifications, and avatar.
2888
+ // Needs provideAfricaniesUiOverlays() for the notification drawer.
2889
+ // Back is built into africanies-app-shell \u2014 nest child routes under a parent and it appears.
2890
+ // Log out: confirm \u2192 UserService.logoutFromAllSessions() \u2192 AuthTokenService.clear().
2891
+
2892
+ import { Component, inject, signal } from '@angular/core';
2893
+
2894
+ import { finalize } from 'rxjs';
2895
+
2896
+ import { AuthTokenService, UserService } from '@africanies/africanies-core';
2897
+ import {
2898
+ AppShellComponent,
2899
+ ConfirmService,
2900
+ SideNavComponent,
2901
+ type AfricaniesMenuItem,
2902
+ type AfricaniesNavItem,
2903
+ type AfricaniesNotification,
2904
+ type AfricaniesSideNavItem,
2905
+ } from '@africanies/africanies-ui';
2906
+
2907
+ @Component({
2908
+ selector: 'app-product-shell',
2909
+ standalone: true,
2910
+ imports: [AppShellComponent, SideNavComponent],
2911
+ template: \`
2912
+ <africanies-app-shell
2913
+ contentWidth="5xl"
2914
+ [headerTitle]="pageTitle"
2915
+ [breadcrumbs]="crumbs"
2916
+ [catalogNav]="nav"
2917
+ userName="Jane Doe"
2918
+ [userMenuItems]="accountMenu"
2919
+ [notifications]="notifications"
2920
+ >
2921
+ <africanies-side-nav
2922
+ sidenav
2923
+ [items]="nav"
2924
+ [(collapsed)]="collapsed"
2925
+ [(activeId)]="activeId"
2926
+ >
2927
+ </africanies-side-nav>
2928
+
2929
+ <router-outlet />
2930
+ </africanies-app-shell>
2931
+ \`,
2932
+ })
2933
+ export class ProductShellComponent {
2934
+ private readonly confirm = inject(ConfirmService);
2935
+ private readonly users = inject(UserService);
2936
+ private readonly auth = inject(AuthTokenService);
2937
+
2938
+ protected readonly collapsed = signal(false);
2939
+ protected readonly activeId = signal('home');
2940
+ protected readonly pageTitle = 'STN-1042';
2941
+
2942
+ protected readonly crumbs: AfricaniesNavItem[] = [
2943
+ { id: 'home', label: 'Home', icon: 'home' },
2944
+ { id: 'shipments', label: 'Shipments' },
2945
+ { id: 'track', label: 'STN-1042' },
2946
+ ];
2947
+
2948
+ protected readonly accountMenu: AfricaniesMenuItem[] = [
2949
+ { label: 'Profile', icon: 'user', onClick: () => {} },
2950
+ { label: 'Settings', icon: 'cog', onClick: () => {} },
2951
+ {
2952
+ label: 'Log out',
2953
+ icon: 'sign-out',
2954
+ danger: true,
2955
+ dividerBefore: true,
2956
+ onClick: () => this.confirmLogout(),
2957
+ },
2958
+ ];
2959
+
2960
+ protected readonly notifications: AfricaniesNotification[] = [
2961
+ {
2962
+ id: 'n1',
2963
+ title: 'Shipment delivered',
2964
+ timestamp: new Date().toISOString(),
2965
+ },
2966
+ ];
2967
+
2968
+ protected readonly nav: AfricaniesSideNavItem[] = [
2969
+ { id: 'home', label: 'Home', icon: 'home' },
2970
+ { id: 'shipments', label: 'Shipments', icon: 'truck' },
2971
+ ];
2972
+
2973
+ protected confirmLogout(): void {
2974
+ this.confirm
2975
+ .confirm({
2976
+ title: 'Log out?',
2977
+ message:
2978
+ 'This signs you out of this device and every other session.',
2979
+ confirmLabel: 'Log out',
2980
+ danger: true,
2981
+ })
2982
+ .subscribe((ok) => {
2983
+ if (!ok) {
2984
+ return;
2985
+ }
2986
+ this.users
2987
+ .logoutFromAllSessions()
2988
+ .pipe(finalize(() => this.auth.clear()))
2989
+ .subscribe();
2990
+ });
2991
+ }
2992
+ }
2993
+ `;var bo=`// Centered modal via ModalService. Needs provideAfricaniesUiOverlays() in app.config.
2994
+ // Built on Angular CDK Overlay (panels attach to body \u2014 not clipped by overflow).
2995
+ // Inject OVERLAY_DATA in the panel, close with AfricaniesOverlayRef.close(result).
2996
+ // dismissible defaults to false \u2014 backdrop/Escape only work when you opt in.
2997
+ // Close X: put it in a shrink-0 header and scroll the body (host overflow-hidden
2998
+ // + africanies-overlay-scroll). The pane reserves a scrollbar gutter so the track
2999
+ // cannot cover the button.
3000
+
3001
+ // app.config.ts
3002
+ import { ApplicationConfig } from '@angular/core';
3003
+ import { provideAfricaniesUiOverlays } from '@africanies/africanies-ui';
3004
+
3005
+ export const appConfig: ApplicationConfig = {
3006
+ providers: [
3007
+ provideAfricaniesUiOverlays(),
3008
+ ],
3009
+ };
3010
+
3011
+ // --- Hosted panel ---
3012
+ import { Component, inject } from '@angular/core';
3013
+ import {
3014
+ AfricaniesOverlayRef,
3015
+ ButtonComponent,
3016
+ OVERLAY_DATA,
3017
+ } from '@africanies/africanies-ui';
3018
+
3019
+ export interface EditShipmentData {
3020
+ shipmentRef: string;
3021
+ }
3022
+
3023
+ export interface EditShipmentResult {
3024
+ saved: boolean;
3025
+ note: string;
3026
+ }
3027
+
3028
+ @Component({
3029
+ selector: 'app-edit-shipment-modal',
3030
+ standalone: true,
3031
+ imports: [ButtonComponent],
3032
+ template: \`
3033
+ <div class="flex flex-col gap-5">
3034
+ <h2 class="m-0 text-heading-3 text-ink dark:text-white">
3035
+ Edit {{ data.shipmentRef }}
3036
+ </h2>
3037
+
3038
+ <label class="flex flex-col gap-1.5">
3039
+ <span class="text-caption font-medium text-neutral-600">Note</span>
3040
+ <input
3041
+ class="rounded-md border border-border px-3 py-2 text-body"
3042
+ type="text"
3043
+ [value]="note"
3044
+ (input)="note = $any($event.target).value"
3045
+ />
3046
+ </label>
3047
+
3048
+ <div class="flex justify-end gap-2">
3049
+ <button africanies-button type="button" variant="ghost" (click)="ref.close()">
3050
+ Cancel
3051
+ </button>
3052
+ <button
3053
+ africanies-button
3054
+ type="button"
3055
+ variant="primary"
3056
+ (click)="ref.close({ saved: true, note })"
3057
+ >
3058
+ Save
3059
+ </button>
3060
+ </div>
3061
+ </div>
3062
+ \`,
3063
+ })
3064
+ export class EditShipmentModalComponent {
3065
+ protected readonly data = inject<EditShipmentData>(OVERLAY_DATA);
3066
+ protected readonly ref = inject(AfricaniesOverlayRef<EditShipmentResult>);
3067
+ protected note = '';
3068
+ }
3069
+
3070
+ // --- Caller ---
3071
+ import { Component, inject } from '@angular/core';
3072
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3073
+ import { ButtonComponent, ModalService } from '@africanies/africanies-ui';
3074
+
3075
+ @Component({
3076
+ selector: 'app-open-modal-demo',
3077
+ standalone: true,
3078
+ imports: [ButtonComponent],
3079
+ template: \`
3080
+ <button africanies-button type="button" variant="primary" (click)="openLocked()">
3081
+ Edit shipment
3082
+ </button>
3083
+ <button africanies-button type="button" variant="secondary" (click)="openDismissible()">
3084
+ Quick look
3085
+ </button>
3086
+ \`,
3087
+ })
3088
+ export class OpenModalDemoComponent {
3089
+ private readonly modal = inject(ModalService);
3090
+
3091
+ protected openLocked(): void {
3092
+ this.modal
3093
+ .open<EditShipmentData, EditShipmentResult>(EditShipmentModalComponent, {
3094
+ data: { shipmentRef: 'SFN-1042' },
3095
+ // dismissible defaults to false \u2014 backdrop / Escape do nothing.
3096
+ // size defaults to 'md'. Use 'lg' for dense forms, 'xl' for media.
3097
+ })
3098
+ .afterClosed()
3099
+ .pipe(takeUntilDestroyed())
3100
+ .subscribe((result) => {
3101
+ if (result?.saved) {
3102
+ // Apply note locally or invalidate list query \u2014 no second GET needed.
3103
+ }
3104
+ });
3105
+ }
3106
+
3107
+ protected openDismissible(): void {
3108
+ this.modal
3109
+ .open<EditShipmentData, EditShipmentResult>(EditShipmentModalComponent, {
3110
+ data: { shipmentRef: 'SFN-1042' },
3111
+ dismissible: true,
3112
+ size: 'lg',
3113
+ })
3114
+ .afterClosed()
3115
+ .pipe(takeUntilDestroyed())
3116
+ .subscribe((result) => {
3117
+ // result undefined when dismissed via backdrop / Escape / Cancel without save.
3118
+ });
3119
+ }
3120
+ }`,yo=`// Right-edge drawer \u2014 same OVERLAY_DATA / AfricaniesOverlayRef contract as modals.
3121
+ // Good for filters and detail chrome. Default is locked; set dismissible: true for abandonable panels.
3122
+
3123
+ import { Component, inject } from '@angular/core';
3124
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3125
+ import {
3126
+ AfricaniesOverlayRef,
3127
+ ButtonComponent,
3128
+ DrawerService,
3129
+ OVERLAY_DATA,
3130
+ } from '@africanies/africanies-ui';
3131
+
3132
+ export interface FiltersDrawerData {
3133
+ facet: string;
3134
+ }
3135
+
3136
+ export interface FiltersDrawerResult {
3137
+ applied: boolean;
3138
+ }
3139
+
3140
+ @Component({
3141
+ selector: 'app-filters-drawer',
3142
+ standalone: true,
3143
+ imports: [ButtonComponent],
3144
+ template: \`
3145
+ <div class="flex h-full flex-col gap-5">
3146
+ <h2 class="m-0 text-heading-3 text-ink dark:text-white">
3147
+ Filters \u2014 {{ data.facet }}
3148
+ </h2>
3149
+
3150
+ <ul class="m-0 flex list-none flex-col gap-2 p-0">
3151
+ @for (opt of options; track opt) {
3152
+ <li>
3153
+ <label class="flex items-center gap-2 rounded-md border border-border px-3 py-2">
3154
+ <input type="checkbox" />
3155
+ {{ opt }}
3156
+ </label>
3157
+ </li>
3158
+ }
3159
+ </ul>
3160
+
3161
+ <div class="mt-auto flex justify-end gap-2 pt-4">
3162
+ <button africanies-button type="button" variant="ghost" (click)="ref.close()">
3163
+ Cancel
3164
+ </button>
3165
+ <button
3166
+ africanies-button
3167
+ type="button"
3168
+ variant="primary"
3169
+ (click)="ref.close({ applied: true })"
3170
+ >
3171
+ Apply
3172
+ </button>
3173
+ </div>
3174
+ </div>
3175
+ \`,
3176
+ })
3177
+ export class FiltersDrawerComponent {
3178
+ protected readonly data = inject<FiltersDrawerData>(OVERLAY_DATA);
3179
+ protected readonly ref = inject(AfricaniesOverlayRef<FiltersDrawerResult>);
3180
+ protected readonly options = ['In transit', 'Delivered', 'Pending', 'Exception'] as const;
3181
+ }
3182
+
3183
+ @Component({
3184
+ selector: 'app-open-drawer-demo',
3185
+ standalone: true,
3186
+ imports: [ButtonComponent],
3187
+ template: \`
3188
+ <button africanies-button type="button" variant="secondary" (click)="open(false)">
3189
+ Open (locked)
3190
+ </button>
3191
+ <button africanies-button type="button" variant="primary" (click)="open(true)">
3192
+ Open (dismissible)
3193
+ </button>
3194
+ \`,
3195
+ })
3196
+ export class OpenDrawerDemoComponent {
3197
+ private readonly drawer = inject(DrawerService);
3198
+
3199
+ protected open(dismissible: boolean): void {
3200
+ this.drawer
3201
+ .open<FiltersDrawerData, FiltersDrawerResult>(FiltersDrawerComponent, {
3202
+ data: { facet: 'Status' },
3203
+ dismissible,
3204
+ })
3205
+ .afterClosed()
3206
+ .pipe(takeUntilDestroyed())
3207
+ .subscribe((result) => {
3208
+ if (result?.applied) {
3209
+ // Push facet selections into list query params \u2192 refetch.
3210
+ }
3211
+ });
3212
+ }
3213
+ }`,vo=`// One-liner confirm via ConfirmService \u2014 returns Observable<boolean>.
3214
+ // Set danger: true for delete/void. Don't open ConfirmDialogComponent yourself.
3215
+
3216
+ import { Component, inject } from '@angular/core';
3217
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3218
+ import { ButtonComponent, ConfirmService } from '@africanies/africanies-ui';
3219
+
3220
+ @Component({
3221
+ selector: 'app-confirm-demo',
3222
+ standalone: true,
3223
+ imports: [ButtonComponent],
3224
+ template: \`
3225
+ <button africanies-button type="button" variant="secondary" (click)="confirmNeutral()">
3226
+ Mark delivered
3227
+ </button>
3228
+
3229
+ <button africanies-button type="button" variant="danger" (click)="confirmDestructive()">
3230
+ Delete shipment
3231
+ </button>
3232
+
3233
+ <button africanies-button type="button" variant="ghost" (click)="confirmDismissible()">
3234
+ Dismissible confirm
3235
+ </button>
3236
+ \`,
3237
+ })
3238
+ export class ConfirmDemoComponent {
3239
+ private readonly confirm = inject(ConfirmService);
3240
+
3241
+ protected confirmNeutral(): void {
3242
+ this.confirm
3243
+ .confirm({
3244
+ title: 'Mark as delivered?',
3245
+ message: 'The consignee will be notified that the shipment is complete.',
3246
+ confirmLabel: 'Mark delivered',
3247
+ cancelLabel: 'Cancel',
3248
+ danger: false,
3249
+ })
3250
+ .pipe(takeUntilDestroyed())
3251
+ .subscribe((ok) => {
3252
+ if (ok) {
3253
+ this.markDelivered();
3254
+ }
3255
+ });
3256
+ }
3257
+
3258
+ protected confirmDestructive(): void {
3259
+ this.confirm
3260
+ .confirm({
3261
+ title: 'Delete shipment?',
3262
+ message: 'This cannot be undone. Related documents stay in archive.',
3263
+ confirmLabel: 'Delete',
3264
+ cancelLabel: 'Cancel',
3265
+ danger: true,
3266
+ // Optional: keep the dialog open with a loading Confirm until this
3267
+ // settles. Errors leave it open so the user can retry or cancel.
3268
+ // onConfirm: () => this.shipments.delete(id),
3269
+ })
3270
+ .pipe(takeUntilDestroyed())
3271
+ .subscribe((ok) => {
3272
+ if (ok) {
3273
+ this.deleteShipment();
3274
+ }
3275
+ });
3276
+ }
3277
+
3278
+ protected confirmDismissible(): void {
3279
+ this.confirm
3280
+ .confirm({
3281
+ title: 'Discard unsaved changes?',
3282
+ message: 'Your draft note will be lost.',
3283
+ confirmLabel: 'Discard',
3284
+ cancelLabel: 'Keep editing',
3285
+ danger: true,
3286
+ dismissible: true,
3287
+ // backdrop / Escape \u2192 false (same as Cancel) via ConfirmService map.
3288
+ })
3289
+ .pipe(takeUntilDestroyed())
3290
+ .subscribe((ok) => {
3291
+ if (ok) {
3292
+ this.discardDraft();
3293
+ }
3294
+ });
3295
+ }
3296
+
3297
+ private markDelivered(): void {
3298
+ // mutation.mutate({ id, status: 'Delivered' })
3299
+ }
3300
+
3301
+ private deleteShipment(): void {
3302
+ // mutation.mutate({ id })
3303
+ }
3304
+
3305
+ private discardDraft(): void {
3306
+ // navigate away or ref.close() on parent overlay
3307
+ }
3308
+ }`;var Co=`// Multi-step wizard with forward-blocking. Match africaniesStepDef keys to StepDefinition.key.
3309
+ // Drive isValid from real form state; wire your own Back/Next around canGoNext().
3310
+ // This is wizard nav \u2014 not a shipment status timeline.
3311
+
3312
+ import { Component, computed, signal } from '@angular/core';
3313
+ import {
3314
+ ButtonComponent,
3315
+ StepDefDirective,
3316
+ StepperComponent,
3317
+ TextInputComponent,
3318
+ type StepDefinition,
3319
+ } from '@africanies/africanies-ui';
3320
+
3321
+ @Component({
3322
+ selector: 'app-shipment-wizard',
3323
+ standalone: true,
3324
+ imports: [
3325
+ StepperComponent,
3326
+ StepDefDirective,
3327
+ ButtonComponent,
3328
+ TextInputComponent,
3329
+ ],
3330
+ template: \`
3331
+ <africanies-stepper
3332
+ [steps]="steps()"
3333
+ [activeIndex]="activeIndex()"
3334
+ [linear]="true"
3335
+ (activeIndexChange)="activeIndex.set($event)"
3336
+ >
3337
+ <ng-template africaniesStepDef="route">
3338
+ <div class="flex flex-col gap-4">
3339
+ <p class="m-0 text-body-sm text-neutral-600">
3340
+ Origin and destination for this shipment.
3341
+ </p>
3342
+ <div class="grid gap-4 md:grid-cols-2">
3343
+ <africanies-text-input label="Origin city" [(value)]="origin" />
3344
+ <africanies-text-input label="Destination city" [(value)]="destination" />
3345
+ </div>
3346
+ </div>
3347
+ </ng-template>
3348
+
3349
+ <ng-template africaniesStepDef="cargo">
3350
+ <div class="flex flex-col gap-4">
3351
+ <p class="m-0 text-body-sm text-neutral-600">Describe what you are shipping.</p>
3352
+ <africanies-text-input label="Commodity" [(value)]="commodity" />
3353
+ </div>
3354
+ </ng-template>
3355
+
3356
+ <ng-template africaniesStepDef="review">
3357
+ <div
3358
+ class="rounded-lg border border-border bg-background-welcome p-4 text-body-sm dark:border-white/10 dark:bg-ink-950"
3359
+ >
3360
+ <p class="m-0 font-medium text-ink dark:text-white">Review</p>
3361
+ <ul class="mt-2 mb-0 list-disc pl-5 text-neutral-600">
3362
+ <li>{{ origin() || '\u2014' }} \u2192 {{ destination() || '\u2014' }}</li>
3363
+ <li>{{ commodity() || 'No commodity set' }}</li>
3364
+ </ul>
3365
+ </div>
3366
+ </ng-template>
3367
+ </africanies-stepper>
3368
+
3369
+ <div class="mt-5 flex gap-2">
3370
+ <button
3371
+ africanies-button
3372
+ type="button"
3373
+ variant="ghost"
3374
+ [disabled]="activeIndex() === 0"
3375
+ (click)="activeIndex.set(activeIndex() - 1)"
3376
+ >
3377
+ Back
3378
+ </button>
3379
+ <button
3380
+ africanies-button
3381
+ type="button"
3382
+ [disabled]="activeIndex() >= steps().length - 1 || !canGoNext()"
3383
+ (click)="activeIndex.set(activeIndex() + 1)"
3384
+ >
3385
+ Next
3386
+ </button>
3387
+ </div>
3388
+ \`,
3389
+ })
3390
+ export class ShipmentWizardComponent {
3391
+ protected readonly activeIndex = signal(0);
3392
+ protected readonly routeValid = signal(false);
3393
+
3394
+ protected readonly origin = signal('Lagos');
3395
+ protected readonly destination = signal('');
3396
+ protected readonly commodity = signal('');
3397
+
3398
+ protected readonly steps = computed<StepDefinition[]>(() => [
3399
+ { key: 'route', label: 'Route', isValid: this.routeValid() },
3400
+ { key: 'cargo', label: 'Cargo', isValid: true },
3401
+ { key: 'review', label: 'Review' },
3402
+ ]);
3403
+
3404
+ protected canGoNext(): boolean {
3405
+ const current = this.steps()[this.activeIndex()];
3406
+ return current?.isValid !== false;
3407
+ }
3408
+ }
3409
+ `,xo=`// Non-linear \u2014 users can jump to any step via the header.
3410
+ // isValid won't gate navigation here; use linear=true when order matters.
3411
+
3412
+ import { Component, signal } from '@angular/core';
3413
+ import {
3414
+ StepDefDirective,
3415
+ StepperComponent,
3416
+ type StepDefinition,
3417
+ } from '@africanies/africanies-ui';
3418
+
3419
+ @Component({
3420
+ selector: 'app-account-setup',
3421
+ standalone: true,
3422
+ imports: [StepperComponent, StepDefDirective],
3423
+ template: \`
3424
+ <africanies-stepper
3425
+ [steps]="steps"
3426
+ [activeIndex]="activeIndex()"
3427
+ [linear]="false"
3428
+ (activeIndexChange)="activeIndex.set($event)"
3429
+ >
3430
+ <ng-template africaniesStepDef="account">
3431
+ <p class="m-0 text-body text-ink dark:text-white">Account details step body.</p>
3432
+ </ng-template>
3433
+
3434
+ <ng-template africaniesStepDef="billing">
3435
+ <p class="m-0 text-body text-ink dark:text-white">Billing step body.</p>
3436
+ </ng-template>
3437
+
3438
+ <ng-template africaniesStepDef="confirm">
3439
+ <p class="m-0 text-body text-ink dark:text-white">Confirmation step body.</p>
3440
+ </ng-template>
3441
+ </africanies-stepper>
3442
+ \`,
3443
+ })
3444
+ export class AccountSetupComponent {
3445
+ protected readonly activeIndex = signal(0);
3446
+
3447
+ protected readonly steps: StepDefinition[] = [
3448
+ { key: 'account', label: 'Account' },
3449
+ { key: 'billing', label: 'Billing' },
3450
+ { key: 'confirm', label: 'Confirm' },
3451
+ ];
3452
+ }
3453
+ `;var Eo=`// Server-driven list: keep the table mounted; loading / empty / error render in the body.
3454
+ // Toolbar: Refresh (left); Filters + Export (right). Pass [meta] for the built-in pager.
3455
+ // Hydrate page/size/filters from FilterQueryService when the URL has those queries.
3456
+ // Pager + Apply write the same keys back. Refetch on sortChange / pageChange / sizeChange.
3457
+ // Refresh / tab focus \u2192 [refreshing] (rows stay). Page/size \u2192 pager spinner.
3458
+ // First load and STN\u2194SFN (listFetchKind reason 'mode') \u2192 clear rows + body loader.
3459
+ // Empty and hard errors stay in-grid so Filters / Export / pager remain available.
3460
+ // Expandable rows: africaniesRowDetail="Label" let-row for label / component value pairs.
3461
+ // Column width: omit to size to content; set width (e.g. '3.5rem') to pin actions.
3462
+ // The actions column sticks to the right while the table scrolls horizontally.
3463
+
3464
+ import { Component, computed, effect, inject, signal, untracked } from '@angular/core';
3465
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3466
+ import { ApiClient, downloadCsv, listFetchKind, ShippingModeService, type ListFetchReason } from '@africanies/africanies-core';
3467
+ import type { AsyncQueryStateModel, PaginationMetaModel } from '@africanies/africanies-models';
3468
+ import {
3469
+ ActionMenuComponent,
3470
+ CellDefDirective,
3471
+ ChipComponent,
3472
+ CopyButtonComponent,
3473
+ DEFAULT_PAGE_SIZE,
3474
+ FilterQueryService,
3475
+ RowDetailDefDirective,
3476
+ TableComponent,
3477
+ trackShipmentsFilterConfig,
3478
+ type AfricaniesMenuItem,
3479
+ type TableColumn,
3480
+ type TableSortChange,
3481
+ } from '@africanies/africanies-ui';
3482
+ import { filter, firstValueFrom, fromEvent } from 'rxjs';
3483
+
3484
+ interface Shipment {
3485
+ reference: string;
3486
+ status: 'In transit' | 'Delivered' | 'Pending' | 'Exception';
3487
+ mode: 'sfn' | 'stn';
3488
+ destination: string;
3489
+ valueUsd: number;
3490
+ }
3491
+
3492
+ const PAGE_SIZE = DEFAULT_PAGE_SIZE;
3493
+
3494
+ @Component({
3495
+ selector: 'app-shipment-list-page',
3496
+ standalone: true,
3497
+ imports: [
3498
+ TableComponent,
3499
+ CellDefDirective,
3500
+ RowDetailDefDirective,
3501
+ ActionMenuComponent,
3502
+ ChipComponent,
3503
+ CopyButtonComponent,
3504
+ ],
3505
+ template: \`
3506
+ <africanies-table
3507
+ [columns]="columns"
3508
+ [rows]="rows()"
3509
+ [meta]="meta()"
3510
+ [sort]="sort()"
3511
+ [rowTrackBy]="rowTrackBy"
3512
+ [showRefresh]="true"
3513
+ [refreshing]="isRefreshing()"
3514
+ [loading]="isLoading() || isPageLoading()"
3515
+ [error]="listError()"
3516
+ emptyMessage="No shipments match these filters."
3517
+ [showFilter]="true"
3518
+ [showExport]="true"
3519
+ (sortChange)="onSort($event)"
3520
+ (refreshClick)="refetch({ reason: 'refresh' })"
3521
+ (filterClick)="openFilters()"
3522
+ (exportClick)="exportCsv()"
3523
+ (pageChange)="onPageChange($event)"
3524
+ (sizeChange)="onSizeChange($event)"
3525
+ >
3526
+ <ng-template africaniesCellDef="reference" let-row>
3527
+ <div class="flex items-center gap-1">
3528
+ <span class="font-medium">{{ row.reference }}</span>
3529
+ <africanies-copy
3530
+ [value]="row.reference"
3531
+ [ariaLabel]="'Copy ' + row.reference"
3532
+ />
3533
+ </div>
3534
+ </ng-template>
3535
+
3536
+ <ng-template africaniesCellDef="status" let-row>
3537
+ <africanies-chip [variant]="statusVariant(row.status)">{{ row.status }}</africanies-chip>
3538
+ </ng-template>
3539
+
3540
+ <ng-template africaniesCellDef="valueUsd" let-row>
3541
+ <span class="tabular-nums">{{ formatUsd(row.valueUsd) }}</span>
3542
+ </ng-template>
3543
+
3544
+ <ng-template africaniesCellDef="actions" let-row>
3545
+ <africanies-action-menu
3546
+ [items]="rowActions(row)"
3547
+ [ariaLabel]="'Actions for ' + row.reference"
3548
+ />
3549
+ </ng-template>
3550
+
3551
+ <ng-template africaniesRowDetail="Destination" let-row>
3552
+ <span class="font-medium">{{ row.destination }}</span>
3553
+ </ng-template>
3554
+
3555
+ <ng-template africaniesRowDetail="Declared value" let-row>
3556
+ <span class="tabular-nums">{{ formatUsd(row.valueUsd) }}</span>
3557
+ </ng-template>
3558
+
3559
+ <ng-template africaniesRowDetail="Status" let-row>
3560
+ <africanies-chip [variant]="statusVariant(row.status)">{{ row.status }}</africanies-chip>
3561
+ </ng-template>
3562
+ </africanies-table>
3563
+ \`,
3564
+ })
3565
+ export class ShipmentListPageComponent {
3566
+ private readonly api = inject(ApiClient);
3567
+ private readonly shipping = inject(ShippingModeService);
3568
+ private readonly filterQuery = inject(FilterQueryService);
3569
+ private readonly urlState = this.filterQuery.read(trackShipmentsFilterConfig);
3570
+
3571
+ protected readonly page = signal(this.urlState.page ?? 1);
3572
+ protected readonly size = signal(this.urlState.size ?? PAGE_SIZE);
3573
+ protected readonly sort = signal<TableSortChange | null>(null);
3574
+ protected readonly rows = signal<Shipment[]>([]);
3575
+ protected readonly meta = signal<PaginationMetaModel | null>(null);
3576
+ protected readonly fetchError = signal<string | null>(null);
3577
+ protected readonly isLoading = signal(false);
3578
+ protected readonly isRefreshing = signal(false);
3579
+ protected readonly isPageLoading = signal(false);
3580
+
3581
+ protected readonly columns: TableColumn<Shipment>[] = [
3582
+ { key: 'reference', header: 'Reference', sortable: true },
3583
+ { key: 'status', header: 'Status', sortable: true },
3584
+ { key: 'mode', header: 'Mode' },
3585
+ { key: 'destination', header: 'Destination', sortable: true },
3586
+ { key: 'valueUsd', header: 'Value', sortable: true },
3587
+ { key: 'actions', header: '', width: '3.5rem' },
3588
+ ];
3589
+
3590
+ protected readonly rowTrackBy = (row: Shipment) => row.reference;
3591
+
3592
+ protected rowActions(row: Shipment): AfricaniesMenuItem[] {
3593
+ return [
3594
+ {
3595
+ label: 'Open',
3596
+ icon: 'eye',
3597
+ onClick: () => this.open(row),
3598
+ },
3599
+ {
3600
+ label: 'Edit',
3601
+ icon: 'edit',
3602
+ onClick: () => this.edit(row),
3603
+ },
3604
+ {
3605
+ label: 'Copy reference',
3606
+ icon: 'copy',
3607
+ onClick: () => this.copyReference(row),
3608
+ },
3609
+ {
3610
+ label: 'Delete',
3611
+ icon: 'trash',
3612
+ danger: true,
3613
+ dividerBefore: true,
3614
+ onClick: () => this.delete(row),
3615
+ },
3616
+ ];
3617
+ }
3618
+
3619
+ protected readonly listState = computed((): AsyncQueryStateModel<Shipment[]> => ({
3620
+ data: this.rows(),
3621
+ isLoading: this.isLoading(),
3622
+ isFetching: this.isRefreshing() || this.isPageLoading(),
3623
+ isError: this.fetchError() != null,
3624
+ error: this.fetchError(),
3625
+ }));
3626
+
3627
+ constructor() {
3628
+ effect(() => {
3629
+ this.shipping.mode();
3630
+ untracked(() =>
3631
+ void this.refetch({
3632
+ reason: this.rows().length > 0 ? 'mode' : 'initial',
3633
+ }),
3634
+ );
3635
+ });
3636
+
3637
+ fromEvent(document, 'visibilitychange')
3638
+ .pipe(
3639
+ filter(() => document.visibilityState === 'visible'),
3640
+ takeUntilDestroyed(),
3641
+ )
3642
+ .subscribe(() => void this.refetch({ reason: 'focus' }));
3643
+ }
3644
+
3645
+ protected async refetch(
3646
+ opts: { reason: ListFetchReason } = { reason: 'initial' },
3647
+ ): Promise<void> {
3648
+ const kind = listFetchKind({
3649
+ hasData: this.rows().length > 0,
3650
+ reason: opts.reason,
3651
+ });
3652
+ this.isLoading.set(kind === 'loading');
3653
+ this.isRefreshing.set(kind === 'refreshing');
3654
+ this.isPageLoading.set(kind === 'pagination');
3655
+ if (kind === 'loading') {
3656
+ this.rows.set([]);
3657
+ }
3658
+ this.fetchError.set(null);
3659
+ try {
3660
+ const currentSort = this.sort();
3661
+ const res = await firstValueFrom(
3662
+ this.api.getResource<Shipment>('shipments', null, {
3663
+ page: this.page(),
3664
+ size: this.size(),
3665
+ // Map sort key/direction to your API query params when supported.
3666
+ orderBy: currentSort?.key,
3667
+ order: currentSort?.direction,
3668
+ }),
3669
+ );
3670
+ this.rows.set(res.data ?? []);
3671
+ this.meta.set(res.pagination ?? null);
3672
+ } catch {
3673
+ this.fetchError.set('Could not load shipments.');
3674
+ } finally {
3675
+ this.isLoading.set(false);
3676
+ this.isRefreshing.set(false);
3677
+ this.isPageLoading.set(false);
3678
+ }
3679
+ }
3680
+
3681
+ protected onSort(change: TableSortChange): void {
3682
+ this.sort.set(change);
3683
+ this.page.set(1);
3684
+ void this.refetch({ reason: 'page' });
3685
+ }
3686
+
3687
+ protected onPageChange(next: number): void {
3688
+ this.page.set(next);
3689
+ void this.refetch({ reason: 'page' });
3690
+ }
3691
+
3692
+ protected onSizeChange(next: number): void {
3693
+ this.size.set(next);
3694
+ this.page.set(1);
3695
+ void this.refetch({ reason: 'page' });
3696
+ }
3697
+
3698
+ private open(row: Shipment): void {
3699
+ /* navigate to detail */
3700
+ }
3701
+
3702
+ private edit(row: Shipment): void {
3703
+ /* open edit flow */
3704
+ }
3705
+
3706
+ private copyReference(row: Shipment): void {
3707
+ /* clipboard.writeText(row.reference) */
3708
+ }
3709
+
3710
+ private delete(row: Shipment): void {
3711
+ /* confirm + API delete */
3712
+ }
3713
+
3714
+ protected exportCsv(): void {
3715
+ downloadCsv({
3716
+ filename: 'shipments.csv',
3717
+ headers: ['Reference', 'Status', 'Destination', 'Value (USD)'],
3718
+ rows: this.rows().map((row) => [
3719
+ row.reference,
3720
+ row.status,
3721
+ row.destination,
3722
+ row.valueUsd,
3723
+ ]),
3724
+ });
3725
+ }
3726
+
3727
+ protected formatUsd(value: number): string {
3728
+ return new Intl.NumberFormat('en-US', {
3729
+ style: 'currency',
3730
+ currency: 'USD',
3731
+ maximumFractionDigits: 0,
3732
+ }).format(value);
3733
+ }
3734
+ }
3735
+
3736
+ // --- Playground-only demo (in-memory slice \u2014 NOT for production) ---
3737
+ //
3738
+ // const ALL_ROWS: Shipment[] = [/* static fixture */];
3739
+ //
3740
+ // pageRows = computed(() => {
3741
+ // const start = (this.page() - 1) * PAGE_SIZE;
3742
+ // return sortedRows().slice(start, start + PAGE_SIZE);
3743
+ // });
3744
+ //
3745
+ // meta = computed((): PaginationMetaModel => ({ /* derived from ALL_ROWS.length */ }));
3746
+ `,Ao=`// Plain text columns only \u2014 no africaniesCellDef needed; cells render String(row[key]).
3747
+ // Fine for small previews; skip AsyncState/pagination if the set is static.
3748
+
3749
+ import { Component } from '@angular/core';
3750
+ import { TableComponent, type TableColumn } from '@africanies/africanies-ui';
3751
+
3752
+ interface Shipment {
3753
+ reference: string;
3754
+ destination: string;
3755
+ status: string;
3756
+ }
3757
+
3758
+ @Component({
3759
+ selector: 'app-shipment-compact-preview',
3760
+ standalone: true,
3761
+ imports: [TableComponent],
3762
+ template: \`
3763
+ <africanies-table [columns]="columns" [rows]="rows" />
3764
+ \`,
3765
+ })
3766
+ export class ShipmentCompactPreviewComponent {
3767
+ protected readonly columns: TableColumn<Shipment>[] = [
3768
+ { key: 'reference', header: 'Reference' },
3769
+ { key: 'destination', header: 'Destination' },
3770
+ { key: 'status', header: 'Status' },
3771
+ ];
3772
+
3773
+ protected readonly rows: Shipment[] = [
3774
+ { reference: 'SFN-1042', destination: 'Lagos', status: 'In transit' },
3775
+ { reference: 'STN-2088', destination: 'Accra', status: 'Delivered' },
3776
+ { reference: 'SFN-1099', destination: 'Nairobi', status: 'Pending' },
3777
+ ];
3778
+ }
3779
+ `,ko=`// Stacked title / subtitle / extra line. Drop into a table cell (or a detail panel).
3780
+ // The table row grows to fit \u2014 no extra layout work.
3781
+
3782
+ import { Component } from '@angular/core';
3783
+ import { ContentStackComponent } from '@africanies/africanies-ui';
3784
+
3785
+ @Component({
3786
+ selector: 'app-packaged-by',
3787
+ standalone: true,
3788
+ imports: [ContentStackComponent],
3789
+ template: \`
3790
+ <africanies-content-stack
3791
+ title="Oladotun Adedeji"
3792
+ subtitle="oladotun.a@africanies.com"
3793
+ extraLine="Aug 14, 2026, 2:04:22 PM"
3794
+ />
3795
+ \`,
3796
+ })
3797
+ export class PackagedByComponent {}
3798
+ `,_o=`// Customer list: switch columns by account type (individual vs business).
3799
+ // User: date created, customer, attempts, identifier, status, performed by, action.
3800
+ // Business: date created, business, attempts, company type, incorp no, incorp date, website, status, action.
3801
+
3802
+ protected readonly userColumns: TableColumn<CustomerRow>[] = [
3803
+ { key: 'createdAt', header: 'Date created', sortable: true },
3804
+ { key: 'customer', header: 'Customer' },
3805
+ { key: 'attempts', header: 'Attempts', sortable: true },
3806
+ { key: 'identifier', header: 'Identifier' },
3807
+ { key: 'status', header: 'Status', sortable: true },
3808
+ { key: 'performedBy', header: 'Performed by' },
3809
+ { key: 'actions', header: 'Action', width: '3.5rem' },
3810
+ ];
3811
+
3812
+ protected readonly businessColumns: TableColumn<CustomerRow>[] = [
3813
+ { key: 'createdAt', header: 'Date created', sortable: true },
3814
+ { key: 'business', header: 'Business' },
3815
+ { key: 'attempts', header: 'Attempts', sortable: true },
3816
+ { key: 'companyType', header: 'Company type' },
3817
+ { key: 'incorpNo', header: 'Incorp no' },
3818
+ { key: 'incorpDate', header: 'Incorp date' },
3819
+ { key: 'website', header: 'Website' },
3820
+ { key: 'status', header: 'Status', sortable: true },
3821
+ { key: 'actions', header: 'Action', width: '3.5rem' },
3822
+ ];
3823
+ `;var Mo=`
3824
+ // Inject ToastService and fire away.
3825
+ // Toasts mount via CDK Overlay in the corner (provideAfricaniesToasts() once).
3826
+ // Error stays until closed; warning ~8s; info/success ~4.5s. Hover pauses.
3827
+
3828
+ import { Component, inject } from '@angular/core';
3829
+ import { ButtonComponent, ToastService } from '@africanies/africanies-ui';
3830
+
3831
+ @Component({
3832
+ selector: 'app-toast-demo',
3833
+ standalone: true,
3834
+ imports: [ButtonComponent],
3835
+ template: \`
3836
+ <button africanies-button type="button" (click)="ok()">Saved</button>
3837
+ <button africanies-button type="button" variant="secondary" (click)="warn()">Warn</button>
3838
+ <button africanies-button type="button" variant="danger" (click)="fail()">Error</button>
3839
+ \`,
3840
+ })
3841
+ export class ToastDemoComponent {
3842
+ private readonly toast = inject(ToastService);
3843
+
3844
+ protected ok(): void {
3845
+ this.toast.success('Shipment saved', 'Done');
3846
+ }
3847
+
3848
+ protected warn(): void {
3849
+ this.toast.warning('Rates may be outdated. Refresh before quoting.');
3850
+ }
3851
+
3852
+ protected fail(): void {
3853
+ this.toast.error('Could not reach the carrier API.', 'Request failed');
3854
+ }
3855
+ }
3856
+ `,Io=`
3857
+ // Same message twice? They collapse into one with \xD7N.
3858
+ // X peels one copy; Expand shows all; Close all clears the group.
3859
+
3860
+ this.toast.error('Warehouse offline');
3861
+ this.toast.error('Warehouse offline'); // \xD72
3862
+ `,No=`
3863
+ // Wrap HttpClient calls with withToast(). Success + error on by default.
3864
+ // Needs provideAfricaniesHttpClient() + provideAfricaniesToasts() in app.config.
3865
+
3866
+ import { HttpClient } from '@angular/common/http';
3867
+ import { inject } from '@angular/core';
3868
+ import { withToast } from '@africanies/africanies-core';
3869
+
3870
+ const http = inject(HttpClient);
3871
+
3872
+ // Success toast + persistent error toast
3873
+ http.post('/shipments', body, { context: withToast() }).subscribe();
3874
+
3875
+ // Errors only
3876
+ http.post('/shipments', body, {
3877
+ context: withToast({ success: false }),
3878
+ }).subscribe();
3879
+
3880
+ // Custom copy
3881
+ http.post('/shipments', body, {
3882
+ context: withToast({
3883
+ successMessage: 'Shipment created',
3884
+ errorMessage: 'Could not create shipment',
3885
+ }),
3886
+ }).subscribe();
3887
+ `;var Oo=`
3888
+ // Help next to a label. Ghost info icon; opens on hover, focus, or tap.
3889
+ // No overlay provider needed \u2014 uses CDK like select / action-menu.
3890
+
3891
+ import { TooltipComponent } from '@africanies/africanies-ui';
3892
+
3893
+ @Component({
3894
+ imports: [TooltipComponent],
3895
+ template: \`
3896
+ <div class="inline-flex items-center gap-1.5">
3897
+ Insured
3898
+ <africanies-tooltip
3899
+ text="Coverage against loss or damage while the shipment is in transit."
3900
+ />
3901
+ </div>
3902
+ \`,
3903
+ })
3904
+ export class Example {}
3905
+ `,Do=`
3906
+ // Wrap any control and mark it with africaniesTooltipTrigger \u2014 default info icon hides.
3907
+
3908
+ import { ButtonComponent, TooltipComponent, TooltipTriggerDirective } from '@africanies/africanies-ui';
3909
+
3910
+ @Component({
3911
+ imports: [ButtonComponent, TooltipComponent, TooltipTriggerDirective],
3912
+ template: \`
3913
+ <africanies-tooltip text="Required when the declared value exceeds $2,000.">
3914
+ <button
3915
+ type="button"
3916
+ africanies-button
3917
+ africaniesTooltipTrigger
3918
+ variant="ghost"
3919
+ size="sm"
3920
+ >
3921
+ Why is this required?
3922
+ </button>
3923
+ </africanies-tooltip>
3924
+ \`,
3925
+ })
3926
+ export class Example {}
3927
+ `,Po=`
3928
+ // placement prefers a side; CDK flips if there\u2019s no room.
3929
+ // 'top' | 'bottom' | 'left' | 'right' (default 'top').
3930
+
3931
+ <africanies-tooltip text="Opens above when possible." placement="top" />
3932
+ <africanies-tooltip text="Opens below when possible." placement="bottom" />
3933
+ <africanies-tooltip text="Opens to the left." placement="left" />
3934
+ <africanies-tooltip text="Opens to the right." placement="right" />
3935
+ `,Lo=`
3936
+ // Label + tip beside it \u2014 don\u2019t wrap the input itself.
3937
+
3938
+ import { TextInputComponent, TooltipComponent } from '@africanies/africanies-ui';
3939
+
3940
+ @Component({
3941
+ imports: [TextInputComponent, TooltipComponent],
3942
+ template: \`
3943
+ <div class="flex flex-col gap-1.5">
3944
+ <div class="flex items-center gap-1.5">
3945
+ <span class="text-body-sm font-medium text-ink dark:text-white">
3946
+ Tracking number
3947
+ </span>
3948
+ <africanies-tooltip
3949
+ text="Carrier AWB or internal shipment reference used on the warehouse floor."
3950
+ icon="question-circle"
3951
+ ariaLabel="About tracking number"
3952
+ />
3953
+ </div>
3954
+ <africanies-text-input label="" placeholder="SFN-\u2026" [(value)]="tracking" />
3955
+ </div>
3956
+ \`,
3957
+ })
3958
+ export class Example {
3959
+ tracking = '';
3960
+ }
3961
+ `;var Bo=`// No Back button or breadcrumbs to build. africanies-app-shell shows both in the
3962
+ // content chrome. Back appears only on child routes (and still does after a
3963
+ // pasted / reloaded URL). Nested children under a parent is all the app needs.
3964
+ //
3965
+ // List filters + pagination live on the query string (FilterQueryService).
3966
+ // Open detail with queryParamsHandling: 'preserve' so Back restores page/filters.
3967
+
3968
+ import { FilterQueryService, trackShipmentsFilterConfig } from '@africanies/africanies-ui';
3969
+
3970
+ const routes: Route[] = [
3971
+ {
3972
+ path: 'usecases/shipment',
3973
+ loadComponent: () =>
3974
+ import('./shipment-usecase.page').then((m) => m.ShipmentUsecasePage),
3975
+ children: [
3976
+ {
3977
+ path: '',
3978
+ loadComponent: () =>
3979
+ import('./shipment-list.page').then((m) => m.ShipmentListPage),
3980
+ },
3981
+ {
3982
+ path: ':id',
3983
+ loadComponent: () =>
3984
+ import('./shipment-detail.page').then((m) => m.ShipmentDetailPage),
3985
+ },
3986
+ ],
3987
+ },
3988
+ ];
3989
+
3990
+ // List page \u2014 seed from the URL, then let Apply / africanies-pagination write back:
3991
+ const filterQuery = inject(FilterQueryService);
3992
+ const config = trackShipmentsFilterConfig;
3993
+ const state = filterQuery.hasParams(config)
3994
+ ? filterQuery.read(config)
3995
+ : emptyFilterState();
3996
+
3997
+ // Detail navigation keeps the list queries on the URL:
3998
+ this.router.navigate(['/usecases/shipment', row.reference], {
3999
+ queryParamsHandling: 'preserve',
4000
+ });
4001
+ `,Uo=`// Forgot password is email-only. Login \u2192 /onboarding/forgot-password.
4002
+ // Submit POSTs { email } to /auth/forgot/password. This app does not handle
4003
+ // the emailed link (no token in the route). /onboarding/reset-password is a
4004
+ // different flow: first login when user.default_password is set.
4005
+
4006
+ import { Component, computed, inject, signal } from '@angular/core';
4007
+ import { RouterLink } from '@angular/router';
4008
+ import { AuthService, isValidEmail } from '@africanies/africanies-core';
4009
+ import { firstValueFrom } from 'rxjs';
4010
+
4011
+ const routes: Route[] = [
4012
+ { path: 'onboarding/login', loadComponent: () => import('./login.page') },
4013
+ {
4014
+ path: 'onboarding/forgot-password',
4015
+ loadComponent: () => import('./forgot-password.page'),
4016
+ },
4017
+ // NOT part of forgot-password \u2014 first login with a default password:
4018
+ {
4019
+ path: 'onboarding/reset-password',
4020
+ loadComponent: () => import('./reset-password.page'),
4021
+ },
4022
+ ];
4023
+
4024
+ @Component({
4025
+ selector: 'app-forgot-password',
4026
+ standalone: true,
4027
+ template: \`
4028
+ <p>We will send you a password reset link.</p>
4029
+ <africanies-text-input type="email" label="Email" [(value)]="email" />
4030
+ <button
4031
+ africanies-button
4032
+ type="button"
4033
+ [disabled]="!canSubmit()"
4034
+ [loading]="sending()"
4035
+ (click)="forgot()"
4036
+ >
4037
+ Send reset link
4038
+ </button>
4039
+ @if (message(); as copy) {
4040
+ <p>{{ copy }}</p>
4041
+ <a routerLink="/onboarding/login">Reset completed? Login here</a>
4042
+ }
4043
+ \`,
4044
+ })
4045
+ export class ForgotPasswordPage {
4046
+ private readonly authApi = inject(AuthService);
4047
+ protected readonly email = signal('');
4048
+ protected readonly sending = signal(false);
4049
+ protected readonly message = signal<string | null>(null);
4050
+ protected readonly canSubmit = computed(() => isValidEmail(this.email()));
4051
+
4052
+ protected async forgot(): Promise<void> {
4053
+ if (!this.canSubmit()) return;
4054
+ this.sending.set(true);
4055
+ try {
4056
+ const res = await firstValueFrom(this.authApi.forgot(this.email()));
4057
+ if (res.success) {
4058
+ this.message.set(res.message?.trim() ?? null);
4059
+ }
4060
+ } finally {
4061
+ this.sending.set(false);
4062
+ }
4063
+ }
4064
+ }
4065
+
4066
+ // After login:
4067
+ // if (user.default_password) router.navigate(['/onboarding/reset-password']);
4068
+ // reset-password calls UserService.changePassword({ current_password, password, password_confirmation })
4069
+ // Admins can also call authApi.forgot(user.email) from user/partner screens.
4070
+ `;export{$r as $,Ar as A,Bi as B,Cr as C,Dr as D,Er as E,Fr as F,Gr as G,Ht as H,Ir as I,Po as J,Kr as K,Lr as L,Mr as M,Nr as N,Or as O,Pr as P,Lo as Q,Rr as R,Sr as S,Mo as T,Ui as U,Vi as V,Wr as W,Io as X,Yr as Y,No as Z,_r as _,Hi as a,_o as a0,Eo as a1,Ao as a2,ko as a3,Co as a4,xo as a5,co as a6,po as a7,mo as a8,uo as a9,rr as aA,or as aB,Ji as aC,sr as aD,fo as aa,go as ab,Bo as ac,Uo as ad,oo as ae,Qr as af,io as ag,ro as ah,no as ai,to as aj,eo as ak,Jr as al,Xr as am,so as an,qi as ao,$i as ap,Ki as aq,Yi as ar,Zi as as,Qi as at,Xi as au,er as av,tr as aw,nr as ax,ar as ay,ir as az,br as b,cr as c,dr as d,bo as e,fr as f,gr as g,hr as h,vo as i,ji as j,kr as k,jr as l,mr as m,zr as n,Vr as o,pr as p,qr as q,Hr as r,Br as s,Oo as t,ur as u,vr as v,wr as w,Do as x,yo as y,zi as z};