@canopypay/checkout-sdk 0.3.0 → 0.4.0
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.
- package/dist/canopy.global.js +1 -1
- package/dist/index.cjs +465 -1
- package/dist/index.d.cts +59 -1
- package/dist/index.d.ts +59 -1
- package/dist/index.js +465 -1
- package/package.json +2 -2
package/dist/canopy.global.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";(()=>{(function(){"use strict";if(typeof window=="undefined")return;let f="width=460,height=640",y=2500,m="Pay with Canopy",h=500,g=420,v="canopy",b="cnpy_pk_live_";function k(){let e=document.currentScript;if(e&&e.src)try{return new URL(e.src).origin}catch(o){}let n=document.getElementsByTagName("script");for(let o=0;o<n.length;o+=1){let r=n[o].src;if(r&&/\/v1\/(?:[^/]+\/)?canopy\.js(?:[?#].*)?$/.test(r))try{return new URL(r).origin}catch(t){}}return null}function _(e){let n;try{n=new URL(e)}catch(t){return{ok:!1,code:"canopy_origin_invalid"}}if(!((n.pathname===""||n.pathname==="/")&&n.search===""&&n.hash===""))return{ok:!1,code:"canopy_origin_not_bare"};let r=n.hostname==="localhost"||n.hostname==="127.0.0.1";return n.protocol!=="https:"&&!r?{ok:!1,code:"canopy_origin_insecure"}:{ok:!0,origin:n.origin}}function E(e){if(e!==void 0)return _(e);let n=k();return n?{ok:!0,origin:n}:{ok:!1,code:"canopy_origin_undiscoverable"}}function c(e){let n=new URL("/customer-pay",e.origin);return n.searchParams.set("merchant",e.merchant),n.hash="cap="+encodeURIComponent(e.capability||""),n.toString()}function i(e,n,o){if(!e.destroyed)try{e.onEvent(Object.assign({type:n},o))}catch(r){}}function l(e){e.handshakeTimer&&(clearTimeout(e.handshakeTimer),e.handshakeTimer=null),e.resizeObserver&&(e.resizeObserver.disconnect(),e.resizeObserver=null),e.frame&&(e.frame.parentNode&&e.frame.parentNode.removeChild(e.frame),e.frame=null),e.lastHeight=null}function T(e){e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=setInterval(function(){try{e.popupWin&&e.popupWin.closed&&(e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=null,e.popupWin=null,i(e,"close",{via:"popup"}))}catch(n){e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=null}},h)}function p(e,n){if(!e.destroyed){if(!n){i(e,"error",{code:"popup_blocked"});return}if(!e.capability){try{n.close()}catch(o){}i(e,"error",{code:"capability_unavailable"});return}try{n.location.href=c(e)}catch(o){i(e,"error",{code:"popup_navigation_failed"});return}e.popupWin=n,T(e)}}function w(e){let n=document.createElement("button");n.type="button",n.textContent=e.fallbackLabel;let o=n.style;o.background="#1A7A4C",o.color="#FFFFFF",o.border="none",o.borderRadius="8px",o.height="48px",o.padding="0 20px",o.fontSize="16px",o.fontFamily="inherit",o.cursor="pointer",o.maxWidth="100%",o.overflow="hidden",o.textOverflow="ellipsis",o.whiteSpace="nowrap",o.display="inline-block",n.addEventListener("click",function(){let r=window.open("","canopy_checkout",f);p(e,r)}),e.node.appendChild(n),e.fallbackButton=n,i(e,"fallback_offered",{surface:"popup",reason:"iframe_unavailable"})}function O(e){let n=document.createElement("iframe");n.src=c(e),n.style.width="100%",n.style.border="none",n.setAttribute("allow","clipboard-write"),n.setAttribute("title","Canopy checkout"),n.style.height=g+"px",e.node.appendChild(n),e.frame=n,typeof ResizeObserver!="undefined"&&(e.resizeObserver=new ResizeObserver(function(o){for(let r=0;r<o.length;r+=1){let t=Math.round(o[r].contentRect.height);t>0&&t!==e.lastHeight&&(e.lastHeight=t,i(e,"resize",{height:t}))}}),e.resizeObserver.observe(n)),e.handshakeTimer=setTimeout(function(){e.handshakeTimer=null,i(e,"surface_unavailable",{attempted:"iframe"}),l(e),e.surface==="auto"?w(e):i(e,"error",{code:"iframe_blocked"})},e.handshakeTimeoutMs)}function L(e,n){if(n.origin!==e.origin)return;let o=n.data;if(!(!o||o.source!==v)){if(o.type==="ready"){e.handshakeTimer&&(clearTimeout(e.handshakeTimer),e.handshakeTimer=null),i(e,"ready");return}if(o.type==="resize"&&typeof o.height=="number"&&e.frame){e.frame.style.height=o.height+"px";return}if(o.type==="paid"){i(e,"paid",o);return}if(o.type==="close"){i(e,"close");return}}}let s=new WeakMap;function R(e){if(typeof e.merchant!="string"||e.merchant.indexOf(b)!==0){try{(e.onEvent||function(){})({type:"error",code:"invalid_merchant_key"})}catch(a){}return{openNow:function(){},destroy:function(){}}}let n=E(e.canopyOrigin);if(!n.ok){try{(e.onEvent||function(){})({type:"error",code:n.code})}catch(a){}return{openNow:function(){},destroy:function(){}}}let o=typeof e.target=="string"?document.querySelector(e.target):e.target;if(!o)return{openNow:function(){},destroy:function(){}};let r=s.get(o);if(r)return r;let t={node:o,capability:e.capability,merchant:e.merchant,origin:n.origin,surface:e.surface||"popup",handshakeTimeoutMs:e.handshakeTimeoutMs||y,fallbackLabel:e.fallbackLabel||m,onEvent:e.onEvent||function(){},destroyed:!1,messageListener:function(){},handshakeTimer:null,resizeObserver:null,frame:null,fallbackButton:null,lastHeight:null,popupWin:null,popupPollTimer:null};t.messageListener=function(a){L(t,a)},window.addEventListener("message",t.messageListener),(t.surface==="iframe"||t.surface==="auto")&&O(t);let d={openNow:function(a){p(t,a)},destroy:function(){t.destroyed||(t.destroyed=!0,window.removeEventListener("message",t.messageListener),l(t),t.popupPollTimer&&(clearInterval(t.popupPollTimer),t.popupPollTimer=null),t.fallbackButton&&(t.fallbackButton.parentNode&&t.fallbackButton.parentNode.removeChild(t.fallbackButton),t.fallbackButton=null),s.delete(o))}};return s.set(o,d),d}let u=window;u.Canopy&&u.Canopy.__version==="3"||(u.Canopy={mount:R,__version:"3"})})();})();
|
|
1
|
+
"use strict";(()=>{var S="theme";var T=["accent","background","surface","text","muted","border","radius","font"],B=new Map([["accent",{kind:"colour",targets:["--leaf","--primary","--accent","--ring"]}],["background",{kind:"colour",targets:["--paper","--background","background"]}],["surface",{kind:"colour",targets:["--surface","--card","--popover","--secondary"]}],["text",{kind:"colour",targets:["--ink","--foreground","--card-foreground","--popover-foreground","--secondary-foreground"]}],["muted",{kind:"colour",targets:["--ink-muted","--muted-foreground"]}],["border",{kind:"colour",targets:["--line","--border","--input"]}],["radius",{kind:"length",targets:["--radius"]}],["font",{kind:"font",targets:["--font-sans"]}]]),Y=/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/,q=/^(rgb|rgba|hsl|hsla)\(([0-9a-z.,%/ +-]*)\)$/,Z=/^[+-]?(?:[0-9]{1,4}(?:\.[0-9]{1,4})?|\.[0-9]{1,4})$/,J=/^[+-]?(?:[0-9]{1,3}(?:\.[0-9]{1,4})?|\.[0-9]{1,4})%$/,Q=/^(?:0|(?:[0-9]{1,4}(?:\.[0-9]{1,4})?|\.[0-9]{1,4})(?:px|rem|em|%))$/,ee=/^[A-Za-z0-9 ,_-]+$/,ne=new Map([["transparent",{r:0,g:0,b:0,a:0}],["black",{r:0,g:0,b:0,a:1}],["white",{r:255,g:255,b:255,a:1}],["silver",{r:192,g:192,b:192,a:1}],["gray",{r:128,g:128,b:128,a:1}],["grey",{r:128,g:128,b:128,a:1}],["maroon",{r:128,g:0,b:0,a:1}],["red",{r:255,g:0,b:0,a:1}],["purple",{r:128,g:0,b:128,a:1}],["fuchsia",{r:255,g:0,b:255,a:1}],["magenta",{r:255,g:0,b:255,a:1}],["green",{r:0,g:128,b:0,a:1}],["lime",{r:0,g:255,b:0,a:1}],["olive",{r:128,g:128,b:0,a:1}],["yellow",{r:255,g:255,b:0,a:1}],["navy",{r:0,g:0,b:128,a:1}],["blue",{r:0,g:0,b:255,a:1}],["teal",{r:0,g:128,b:128,a:1}],["aqua",{r:0,g:255,b:255,a:1}],["cyan",{r:0,g:255,b:255,a:1}],["orange",{r:255,g:165,b:0,a:1}],["pink",{r:255,g:192,b:203,a:1}],["brown",{r:165,g:42,b:42,a:1}],["beige",{r:245,g:245,b:220,a:1}],["ivory",{r:255,g:255,b:240,a:1}],["indigo",{r:75,g:0,b:130,a:1}],["violet",{r:238,g:130,b:238,a:1}],["gold",{r:255,g:215,b:0,a:1}],["crimson",{r:220,g:20,b:60,a:1}],["salmon",{r:250,g:128,b:114,a:1}],["khaki",{r:240,g:230,b:140,a:1}],["lavender",{r:230,g:230,b:250,a:1}],["turquoise",{r:64,g:224,b:208,a:1}],["tan",{r:210,g:180,b:140,a:1}],["plum",{r:221,g:160,b:221,a:1}],["orchid",{r:218,g:112,b:214,a:1}],["coral",{r:255,g:127,b:80,a:1}],["tomato",{r:255,g:99,b:71,a:1}],["chocolate",{r:210,g:105,b:30,a:1}],["snow",{r:255,g:250,b:250,a:1}],["linen",{r:250,g:240,b:230,a:1}],["azure",{r:240,g:255,b:255,a:1}],["wheat",{r:245,g:222,b:179,a:1}],["thistle",{r:216,g:191,b:216,a:1}]]),te=new Set(["transparent","currentcolor","inherit","initial","unset","revert","revert-layer"]),re=new Set(["text","muted","accent"]);function y(t,n,o){return t<n?n:t>o?o:t}function x(t){if(!Z.test(t))return null;let n=Number(t);return Number.isFinite(n)?n:null}function v(t){if(!J.test(t))return null;let n=Number(t.slice(0,t.length-1));return Number.isFinite(n)?n/100:null}function A(t){let n=v(t);if(n!==null)return y(n,0,1)*255;let o=x(t);return o===null?null:y(o,0,255)}function oe(t){let n=v(t);if(n!==null)return y(n,0,1);let o=x(t);return o===null?null:y(o,0,1)}function ae(t){let n=t,o=1;n.endsWith("deg")?n=n.slice(0,n.length-3):n.endsWith("grad")?(n=n.slice(0,n.length-4),o=360/400):n.endsWith("turn")?(n=n.slice(0,n.length-4),o=360):n.endsWith("rad")&&(n=n.slice(0,n.length-3),o=180/Math.PI);let r=x(n);return r===null?null:(r*o%360+360)%360}function C(t,n,o){let r=o;return r<0&&(r+=1),r>1&&(r-=1),r<1/6?t+(n-t)*6*r:r<1/2?n:r<2/3?t+(n-t)*(2/3-r)*6:t}function ie(t,n,o,r){if(n===0){let l=o*255;return{r:l,g:l,b:l,a:r}}let u=o<.5?o*(1+n):o+n-o*n,d=2*o-u,s=t/360;return{r:C(d,u,s+1/3)*255,g:C(d,u,s)*255,b:C(d,u,s-1/3)*255,a:r}}function le(t){let n=t.trim();if(n==="")return null;if(n.indexOf(",")>=0){if(n.indexOf("/")>=0)return null;let d=n.split(",").map(function(s){return s.trim()});return d.length!==3&&d.length!==4?null:d}let o=n.split("/");if(o.length>2)return null;let r=o[0].trim().split(/ +/);if(r.length!==3)return null;if(o.length===1)return r;let u=o[1].trim();return u===""||u.indexOf(" ")>=0?null:r.concat([u])}function se(t){let n=q.exec(t);if(n===null)return null;let o=n[1],r=le(n[2]);if(r===null)return null;let u=1;if(r.length===4){let c=oe(r[3]);if(c===null)return null;u=c}if(o==="rgb"||o==="rgba"){let c=A(r[0]),f=A(r[1]),h=A(r[2]);return c===null||f===null||h===null?null:{r:c,g:f,b:h,a:u}}let d=ae(r[0]),s=v(r[1]),l=v(r[2]);return d===null||s===null||l===null?null:ie(d,y(s,0,1),y(l,0,1),u)}function ue(t){if(!Y.test(t))return null;let n=t.slice(1),o=n.length===3||n.length===4,r=o?1:2,u=function(s){let l=n.slice(s*r,s*r+r);return parseInt(o?l+l:l,16)},d=n.length===4||n.length===8;return{r:u(0),g:u(1),b:u(2),a:d?u(3)/255:1}}function R(t){let n=ue(t);if(n!==null)return n;let o=se(t);if(o!==null)return o;let r=ne.get(t.toLowerCase());return r===void 0?null:r}function O(t){let n=t/255;return n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4)}function F(t){return .2126*O(t.r)+.7152*O(t.g)+.0722*O(t.b)}function L(t,n){let o=F(t),r=F(n),u=o>r?o:r,d=o>r?r:o;return(u+.05)/(d+.05)}function w(t,n){return t.a>=1?t:{r:t.r*t.a+n.r*(1-t.a),g:t.g*t.a+n.g*(1-t.a),b:t.b*t.a+n.b*(1-t.a),a:1}}var N=3,ce={light:[{r:243,g:250,b:246,a:1},{r:255,g:255,b:255,a:1}],dark:[{r:10,g:22,b:40,a:1},{r:26,g:40,b:60,a:1}]},H={r:255,g:255,b:255,a:1};function de(t,n){let o=n===null?["light","dark"]:[n],r=[];for(let l=0;l<o.length;l+=1){let c=ce[o[l]];for(let f=0;f<c.length;f+=1)r.push(c[f])}let u=[],d=["surface","background"];for(let l=0;l<d.length;l+=1){let c=t[d[l]];if(c===void 0)continue;let f=R(c);f!==null&&u.push(f)}if(u.length===0)return r;let s=[];for(let l=0;l<u.length;l+=1){let c=u[l];if(c.a>=1){s.push(c);continue}for(let f=0;f<r.length;f+=1)s.push(w(c,r[f]))}return s}function fe(t,n,o){let r=de(t,o),u=t.text;if(u!==void 0){let s=R(u),l=1/0;if(s===null)l=0;else for(let c=0;c<r.length;c+=1){let f=r[c],h=L(w(s,f),f);h<l&&(l=h)}l<N&&(delete t.text,n.push({key:"text",code:"variable_contrast_too_low"}))}let d=t.accent;if(d!==void 0){let s=R(d),l=1/0;if(s===null)l=0;else if(s.a>=1)l=L(s,H);else for(let c=0;c<r.length;c+=1){let f=L(w(s,r[c]),H);f<l&&(l=f)}l<N&&(delete t.accent,n.push({key:"accent",code:"variable_contrast_too_low"}))}}function pe(t,n){return t==="colour"?R(n)!==null:t==="length"?Q.test(n):ee.test(n)}function ge(t,n){let o=B.get(t);return o===void 0?{code:"unknown_variable",value:""}:typeof n!="string"?{code:"variable_not_a_string",value:""}:n.length>64?{code:"variable_value_too_long",value:""}:o.kind==="colour"&&re.has(t)&&te.has(n.toLowerCase())?{code:"variable_value_erases_content",value:""}:pe(o.kind,n)?{code:null,value:n}:{code:"variable_value_malformed",value:""}}function U(t,n=null){let o={},r=[];if(t===null||typeof t!="object"||Array.isArray(t))return{accepted:{},declarations:{},rejections:[]};let u=Object.keys(t);for(let s=0;s<u.length;s+=1){let l=u[s];if(s>=12){r.push({key:l,code:"too_many_variables"});break}let c=ge(l,t[l]);if(c.code!==null){r.push({key:l,code:c.code});continue}o[l]=c.value}fe(o,r,n);let d={};for(let s of T){let l=o[s];if(l===void 0)continue;let c=B.get(s).targets;for(let f=0;f<c.length;f+=1)d[c[f]]=l}return{accepted:o,declarations:d,rejections:r}}function j(t){return t==="light"||t==="dark"?t:null}var z="4";(function(){"use strict";if(typeof window=="undefined")return;let t="width=460,height=640",n=2500,o="Pay with Canopy",r=500,u=420,d="canopy",s="cnpy_pk_live_";function l(){let e=document.currentScript;if(e&&e.src)try{return new URL(e.src).origin}catch(i){}let a=document.getElementsByTagName("script");for(let i=0;i<a.length;i+=1){let p=a[i].src;if(p&&/\/v1\/(?:[^/]+\/)?canopy\.js(?:[?#].*)?$/.test(p))try{return new URL(p).origin}catch(b){}}return null}function c(e){let a;try{a=new URL(e)}catch(b){return{ok:!1,code:"canopy_origin_invalid"}}if(!((a.pathname===""||a.pathname==="/")&&a.search===""&&a.hash===""))return{ok:!1,code:"canopy_origin_not_bare"};let p=a.hostname==="localhost"||a.hostname==="127.0.0.1";return a.protocol!=="https:"&&!p?{ok:!1,code:"canopy_origin_insecure"}:{ok:!0,origin:a.origin}}function f(e){if(e!==void 0)return c(e);let a=l();return a?{ok:!0,origin:a}:{ok:!1,code:"canopy_origin_undiscoverable"}}function h(e){let a=new URL("/customer-pay",e.origin);a.searchParams.set("merchant",e.merchant),e.theme!==null&&a.searchParams.set(S,e.theme);for(let i=0;i<T.length;i+=1){let p=T[i],b=e.themeVariables[p];b!==void 0&&a.searchParams.set("v."+p,b)}return a.hash="cap="+encodeURIComponent(e.capability||""),a.toString()}function m(e,a,i){if(!e.destroyed)try{e.onEvent(Object.assign({type:a},i))}catch(p){}}function W(e,a){for(let i=0;i<a.length;i+=1){let p=a[i];m(e,"error",{code:p.code,variable:p.key})}}function I(e){e.handshakeTimer&&(clearTimeout(e.handshakeTimer),e.handshakeTimer=null),e.resizeObserver&&(e.resizeObserver.disconnect(),e.resizeObserver=null),e.frame&&(e.frame.parentNode&&e.frame.parentNode.removeChild(e.frame),e.frame=null),e.lastHeight=null}function K(e){e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=setInterval(function(){try{e.popupWin&&e.popupWin.closed&&(e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=null,e.popupWin=null,m(e,"close",{via:"popup"}))}catch(a){e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=null}},r)}function M(e,a){if(!e.destroyed){if(!a){m(e,"error",{code:"popup_blocked"});return}if(!e.capability){try{a.close()}catch(i){}m(e,"error",{code:"capability_unavailable"});return}try{a.location.href=h(e)}catch(i){m(e,"error",{code:"popup_navigation_failed"});return}e.popupWin=a,K(e)}}function G(e){let a=document.createElement("button");a.type="button",a.textContent=e.fallbackLabel;let i=a.style;i.background="#1A7A4C",i.color="#FFFFFF",i.border="none",i.borderRadius="8px",i.height="48px",i.padding="0 20px",i.fontSize="16px",i.fontFamily="inherit",i.cursor="pointer",i.maxWidth="100%",i.overflow="hidden",i.textOverflow="ellipsis",i.whiteSpace="nowrap",i.display="inline-block",a.addEventListener("click",function(){let p=window.open("","canopy_checkout",t);M(e,p)}),e.node.appendChild(a),e.fallbackButton=a,m(e,"fallback_offered",{surface:"popup",reason:"iframe_unavailable"})}function X(e){let a=document.createElement("iframe");a.src=h(e),a.style.width="100%",a.style.border="none",a.setAttribute("allow","clipboard-write"),a.setAttribute("title","Canopy checkout"),a.style.height=u+"px",e.node.appendChild(a),e.frame=a,typeof ResizeObserver!="undefined"&&(e.resizeObserver=new ResizeObserver(function(i){for(let p=0;p<i.length;p+=1){let b=Math.round(i[p].contentRect.height);b>0&&b!==e.lastHeight&&(e.lastHeight=b,m(e,"resize",{height:b}))}}),e.resizeObserver.observe(a)),e.handshakeTimer=setTimeout(function(){e.handshakeTimer=null,m(e,"surface_unavailable",{attempted:"iframe"}),I(e),e.surface==="auto"?G(e):m(e,"error",{code:"iframe_blocked"})},e.handshakeTimeoutMs)}function D(e,a){if(a.origin!==e.origin)return;let i=a.data;if(!(!i||i.source!==d)){if(i.type==="ready"){e.handshakeTimer&&(clearTimeout(e.handshakeTimer),e.handshakeTimer=null),m(e,"ready");return}if(i.type==="resize"&&typeof i.height=="number"&&e.frame){e.frame.style.height=i.height+"px";return}if(i.type==="paid"){m(e,"paid",i);return}if(i.type==="close"){m(e,"close");return}}}let E=new WeakMap;function $(e){if(typeof e.merchant!="string"||e.merchant.indexOf(s)!==0){try{(e.onEvent||function(){})({type:"error",code:"invalid_merchant_key"})}catch(_){}return{openNow:function(){},destroy:function(){}}}let a=f(e.canopyOrigin);if(!a.ok){try{(e.onEvent||function(){})({type:"error",code:a.code})}catch(_){}return{openNow:function(){},destroy:function(){}}}let i=j(e.theme),p=U(e.variables,i),b=typeof e.target=="string"?document.querySelector(e.target):e.target;if(!b)return{openNow:function(){},destroy:function(){}};let V=E.get(b);if(V)return V;let g={node:b,capability:e.capability,merchant:e.merchant,origin:a.origin,theme:i,themeVariables:p.accepted,surface:e.surface||"popup",handshakeTimeoutMs:e.handshakeTimeoutMs||n,fallbackLabel:e.fallbackLabel||o,onEvent:e.onEvent||function(){},destroyed:!1,messageListener:function(){},handshakeTimer:null,resizeObserver:null,frame:null,fallbackButton:null,lastHeight:null,popupWin:null,popupPollTimer:null};g.messageListener=function(_){D(g,_)},window.addEventListener("message",g.messageListener),W(g,p.rejections),(g.surface==="iframe"||g.surface==="auto")&&X(g);let P={openNow:function(_){M(g,_)},destroy:function(){g.destroyed||(g.destroyed=!0,window.removeEventListener("message",g.messageListener),I(g),g.popupPollTimer&&(clearInterval(g.popupPollTimer),g.popupPollTimer=null),g.fallbackButton&&(g.fallbackButton.parentNode&&g.fallbackButton.parentNode.removeChild(g.fallbackButton),g.fallbackButton=null),E.delete(b))}};return E.set(b,P),P}let k=window;k.Canopy&&k.Canopy.__version===z||(k.Canopy={mount:$,__version:z})})();})();
|
package/dist/index.cjs
CHANGED
|
@@ -25,8 +25,449 @@ __export(src_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(src_exports);
|
|
27
27
|
|
|
28
|
+
// src/theme.ts
|
|
29
|
+
var THEME_PARAM = "theme";
|
|
30
|
+
var THEME_VARIABLE_PARAM_PREFIX = "v.";
|
|
31
|
+
var MAX_THEME_VARIABLE_VALUE_LENGTH = 64;
|
|
32
|
+
var MAX_THEME_VARIABLE_ENTRIES = 12;
|
|
33
|
+
var CANOPY_THEME_VARIABLES = [
|
|
34
|
+
"accent",
|
|
35
|
+
"background",
|
|
36
|
+
"surface",
|
|
37
|
+
"text",
|
|
38
|
+
"muted",
|
|
39
|
+
"border",
|
|
40
|
+
"radius",
|
|
41
|
+
"font"
|
|
42
|
+
];
|
|
43
|
+
var VARIABLE_TARGETS = /* @__PURE__ */ new Map([
|
|
44
|
+
[
|
|
45
|
+
"accent",
|
|
46
|
+
{
|
|
47
|
+
kind: "colour",
|
|
48
|
+
targets: ["--leaf", "--primary", "--accent", "--ring"]
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
"background",
|
|
53
|
+
{
|
|
54
|
+
kind: "colour",
|
|
55
|
+
targets: ["--paper", "--background", "background"]
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
"surface",
|
|
60
|
+
{
|
|
61
|
+
kind: "colour",
|
|
62
|
+
targets: ["--surface", "--card", "--popover", "--secondary"]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"text",
|
|
67
|
+
{
|
|
68
|
+
kind: "colour",
|
|
69
|
+
targets: [
|
|
70
|
+
"--ink",
|
|
71
|
+
"--foreground",
|
|
72
|
+
"--card-foreground",
|
|
73
|
+
"--popover-foreground",
|
|
74
|
+
"--secondary-foreground"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
"muted",
|
|
80
|
+
{
|
|
81
|
+
kind: "colour",
|
|
82
|
+
targets: ["--ink-muted", "--muted-foreground"]
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
"border",
|
|
87
|
+
{ kind: "colour", targets: ["--line", "--border", "--input"] }
|
|
88
|
+
],
|
|
89
|
+
["radius", { kind: "length", targets: ["--radius"] }],
|
|
90
|
+
["font", { kind: "font", targets: ["--font-sans"] }]
|
|
91
|
+
]);
|
|
92
|
+
var COLOUR_HEX = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
93
|
+
var COLOUR_FUNCTION = /^(rgb|rgba|hsl|hsla)\(([0-9a-z.,%/ +-]*)\)$/;
|
|
94
|
+
var NUMBER = /^[+-]?(?:[0-9]{1,4}(?:\.[0-9]{1,4})?|\.[0-9]{1,4})$/;
|
|
95
|
+
var PERCENT = /^[+-]?(?:[0-9]{1,3}(?:\.[0-9]{1,4})?|\.[0-9]{1,4})%$/;
|
|
96
|
+
var LENGTH = /^(?:0|(?:[0-9]{1,4}(?:\.[0-9]{1,4})?|\.[0-9]{1,4})(?:px|rem|em|%))$/;
|
|
97
|
+
var FONT_FAMILY_LIST = /^[A-Za-z0-9 ,_-]+$/;
|
|
98
|
+
var NAMED_COLOURS = /* @__PURE__ */ new Map([
|
|
99
|
+
["transparent", { r: 0, g: 0, b: 0, a: 0 }],
|
|
100
|
+
["black", { r: 0, g: 0, b: 0, a: 1 }],
|
|
101
|
+
["white", { r: 255, g: 255, b: 255, a: 1 }],
|
|
102
|
+
["silver", { r: 192, g: 192, b: 192, a: 1 }],
|
|
103
|
+
["gray", { r: 128, g: 128, b: 128, a: 1 }],
|
|
104
|
+
["grey", { r: 128, g: 128, b: 128, a: 1 }],
|
|
105
|
+
["maroon", { r: 128, g: 0, b: 0, a: 1 }],
|
|
106
|
+
["red", { r: 255, g: 0, b: 0, a: 1 }],
|
|
107
|
+
["purple", { r: 128, g: 0, b: 128, a: 1 }],
|
|
108
|
+
["fuchsia", { r: 255, g: 0, b: 255, a: 1 }],
|
|
109
|
+
["magenta", { r: 255, g: 0, b: 255, a: 1 }],
|
|
110
|
+
["green", { r: 0, g: 128, b: 0, a: 1 }],
|
|
111
|
+
["lime", { r: 0, g: 255, b: 0, a: 1 }],
|
|
112
|
+
["olive", { r: 128, g: 128, b: 0, a: 1 }],
|
|
113
|
+
["yellow", { r: 255, g: 255, b: 0, a: 1 }],
|
|
114
|
+
["navy", { r: 0, g: 0, b: 128, a: 1 }],
|
|
115
|
+
["blue", { r: 0, g: 0, b: 255, a: 1 }],
|
|
116
|
+
["teal", { r: 0, g: 128, b: 128, a: 1 }],
|
|
117
|
+
["aqua", { r: 0, g: 255, b: 255, a: 1 }],
|
|
118
|
+
["cyan", { r: 0, g: 255, b: 255, a: 1 }],
|
|
119
|
+
["orange", { r: 255, g: 165, b: 0, a: 1 }],
|
|
120
|
+
["pink", { r: 255, g: 192, b: 203, a: 1 }],
|
|
121
|
+
["brown", { r: 165, g: 42, b: 42, a: 1 }],
|
|
122
|
+
["beige", { r: 245, g: 245, b: 220, a: 1 }],
|
|
123
|
+
["ivory", { r: 255, g: 255, b: 240, a: 1 }],
|
|
124
|
+
["indigo", { r: 75, g: 0, b: 130, a: 1 }],
|
|
125
|
+
["violet", { r: 238, g: 130, b: 238, a: 1 }],
|
|
126
|
+
["gold", { r: 255, g: 215, b: 0, a: 1 }],
|
|
127
|
+
["crimson", { r: 220, g: 20, b: 60, a: 1 }],
|
|
128
|
+
["salmon", { r: 250, g: 128, b: 114, a: 1 }],
|
|
129
|
+
["khaki", { r: 240, g: 230, b: 140, a: 1 }],
|
|
130
|
+
["lavender", { r: 230, g: 230, b: 250, a: 1 }],
|
|
131
|
+
["turquoise", { r: 64, g: 224, b: 208, a: 1 }],
|
|
132
|
+
["tan", { r: 210, g: 180, b: 140, a: 1 }],
|
|
133
|
+
["plum", { r: 221, g: 160, b: 221, a: 1 }],
|
|
134
|
+
["orchid", { r: 218, g: 112, b: 214, a: 1 }],
|
|
135
|
+
["coral", { r: 255, g: 127, b: 80, a: 1 }],
|
|
136
|
+
["tomato", { r: 255, g: 99, b: 71, a: 1 }],
|
|
137
|
+
["chocolate", { r: 210, g: 105, b: 30, a: 1 }],
|
|
138
|
+
["snow", { r: 255, g: 250, b: 250, a: 1 }],
|
|
139
|
+
["linen", { r: 250, g: 240, b: 230, a: 1 }],
|
|
140
|
+
["azure", { r: 240, g: 255, b: 255, a: 1 }],
|
|
141
|
+
["wheat", { r: 245, g: 222, b: 179, a: 1 }],
|
|
142
|
+
["thistle", { r: 216, g: 191, b: 216, a: 1 }]
|
|
143
|
+
]);
|
|
144
|
+
var CONTENT_ERASING_COLOURS = /* @__PURE__ */ new Set([
|
|
145
|
+
"transparent",
|
|
146
|
+
"currentcolor",
|
|
147
|
+
"inherit",
|
|
148
|
+
"initial",
|
|
149
|
+
"unset",
|
|
150
|
+
"revert",
|
|
151
|
+
"revert-layer"
|
|
152
|
+
]);
|
|
153
|
+
var FOREGROUND_VARIABLES = /* @__PURE__ */ new Set([
|
|
154
|
+
"text",
|
|
155
|
+
"muted",
|
|
156
|
+
"accent"
|
|
157
|
+
]);
|
|
158
|
+
function clamp(value, low, high) {
|
|
159
|
+
if (value < low) return low;
|
|
160
|
+
if (value > high) return high;
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
function parseNumber(token) {
|
|
164
|
+
if (!NUMBER.test(token)) return null;
|
|
165
|
+
const value = Number(token);
|
|
166
|
+
return Number.isFinite(value) ? value : null;
|
|
167
|
+
}
|
|
168
|
+
function parsePercent(token) {
|
|
169
|
+
if (!PERCENT.test(token)) return null;
|
|
170
|
+
const value = Number(token.slice(0, token.length - 1));
|
|
171
|
+
return Number.isFinite(value) ? value / 100 : null;
|
|
172
|
+
}
|
|
173
|
+
function parseRgbComponent(token) {
|
|
174
|
+
const percent = parsePercent(token);
|
|
175
|
+
if (percent !== null) return clamp(percent, 0, 1) * 255;
|
|
176
|
+
const number = parseNumber(token);
|
|
177
|
+
if (number === null) return null;
|
|
178
|
+
return clamp(number, 0, 255);
|
|
179
|
+
}
|
|
180
|
+
function parseAlpha(token) {
|
|
181
|
+
const percent = parsePercent(token);
|
|
182
|
+
if (percent !== null) return clamp(percent, 0, 1);
|
|
183
|
+
const number = parseNumber(token);
|
|
184
|
+
if (number === null) return null;
|
|
185
|
+
return clamp(number, 0, 1);
|
|
186
|
+
}
|
|
187
|
+
function parseHue(token) {
|
|
188
|
+
let text = token;
|
|
189
|
+
let scale = 1;
|
|
190
|
+
if (text.endsWith("deg")) text = text.slice(0, text.length - 3);
|
|
191
|
+
else if (text.endsWith("grad")) {
|
|
192
|
+
text = text.slice(0, text.length - 4);
|
|
193
|
+
scale = 360 / 400;
|
|
194
|
+
} else if (text.endsWith("turn")) {
|
|
195
|
+
text = text.slice(0, text.length - 4);
|
|
196
|
+
scale = 360;
|
|
197
|
+
} else if (text.endsWith("rad")) {
|
|
198
|
+
text = text.slice(0, text.length - 3);
|
|
199
|
+
scale = 180 / Math.PI;
|
|
200
|
+
}
|
|
201
|
+
const number = parseNumber(text);
|
|
202
|
+
if (number === null) return null;
|
|
203
|
+
return (number * scale % 360 + 360) % 360;
|
|
204
|
+
}
|
|
205
|
+
function hueToChannel(p, q, tRaw) {
|
|
206
|
+
let t = tRaw;
|
|
207
|
+
if (t < 0) t += 1;
|
|
208
|
+
if (t > 1) t -= 1;
|
|
209
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
210
|
+
if (t < 1 / 2) return q;
|
|
211
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
212
|
+
return p;
|
|
213
|
+
}
|
|
214
|
+
function hslToRgb(h, s, l, a) {
|
|
215
|
+
if (s === 0) {
|
|
216
|
+
const grey = l * 255;
|
|
217
|
+
return { r: grey, g: grey, b: grey, a };
|
|
218
|
+
}
|
|
219
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
220
|
+
const p = 2 * l - q;
|
|
221
|
+
const hk = h / 360;
|
|
222
|
+
return {
|
|
223
|
+
r: hueToChannel(p, q, hk + 1 / 3) * 255,
|
|
224
|
+
g: hueToChannel(p, q, hk) * 255,
|
|
225
|
+
b: hueToChannel(p, q, hk - 1 / 3) * 255,
|
|
226
|
+
a
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function splitColourComponents(body) {
|
|
230
|
+
const trimmed = body.trim();
|
|
231
|
+
if (trimmed === "") return null;
|
|
232
|
+
if (trimmed.indexOf(",") >= 0) {
|
|
233
|
+
if (trimmed.indexOf("/") >= 0) return null;
|
|
234
|
+
const parts = trimmed.split(",").map(function(part) {
|
|
235
|
+
return part.trim();
|
|
236
|
+
});
|
|
237
|
+
if (parts.length !== 3 && parts.length !== 4) return null;
|
|
238
|
+
return parts;
|
|
239
|
+
}
|
|
240
|
+
const slashParts = trimmed.split("/");
|
|
241
|
+
if (slashParts.length > 2) return null;
|
|
242
|
+
const main = slashParts[0].trim().split(/ +/);
|
|
243
|
+
if (main.length !== 3) return null;
|
|
244
|
+
if (slashParts.length === 1) return main;
|
|
245
|
+
const alpha = slashParts[1].trim();
|
|
246
|
+
if (alpha === "" || alpha.indexOf(" ") >= 0) return null;
|
|
247
|
+
return main.concat([alpha]);
|
|
248
|
+
}
|
|
249
|
+
function parseColourFunction(value) {
|
|
250
|
+
const match = COLOUR_FUNCTION.exec(value);
|
|
251
|
+
if (match === null) return null;
|
|
252
|
+
const fn = match[1];
|
|
253
|
+
const parts = splitColourComponents(match[2]);
|
|
254
|
+
if (parts === null) return null;
|
|
255
|
+
let alpha = 1;
|
|
256
|
+
if (parts.length === 4) {
|
|
257
|
+
const parsed = parseAlpha(parts[3]);
|
|
258
|
+
if (parsed === null) return null;
|
|
259
|
+
alpha = parsed;
|
|
260
|
+
}
|
|
261
|
+
if (fn === "rgb" || fn === "rgba") {
|
|
262
|
+
const r = parseRgbComponent(parts[0]);
|
|
263
|
+
const g = parseRgbComponent(parts[1]);
|
|
264
|
+
const b = parseRgbComponent(parts[2]);
|
|
265
|
+
if (r === null || g === null || b === null) return null;
|
|
266
|
+
return { r, g, b, a: alpha };
|
|
267
|
+
}
|
|
268
|
+
const h = parseHue(parts[0]);
|
|
269
|
+
const s = parsePercent(parts[1]);
|
|
270
|
+
const l = parsePercent(parts[2]);
|
|
271
|
+
if (h === null || s === null || l === null) return null;
|
|
272
|
+
return hslToRgb(h, clamp(s, 0, 1), clamp(l, 0, 1), alpha);
|
|
273
|
+
}
|
|
274
|
+
function parseHexColour(value) {
|
|
275
|
+
if (!COLOUR_HEX.test(value)) return null;
|
|
276
|
+
const digits = value.slice(1);
|
|
277
|
+
const short = digits.length === 3 || digits.length === 4;
|
|
278
|
+
const size = short ? 1 : 2;
|
|
279
|
+
const channel = function(index) {
|
|
280
|
+
const part = digits.slice(index * size, index * size + size);
|
|
281
|
+
return parseInt(short ? part + part : part, 16);
|
|
282
|
+
};
|
|
283
|
+
const hasAlpha = digits.length === 4 || digits.length === 8;
|
|
284
|
+
return {
|
|
285
|
+
r: channel(0),
|
|
286
|
+
g: channel(1),
|
|
287
|
+
b: channel(2),
|
|
288
|
+
a: hasAlpha ? channel(3) / 255 : 1
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function parseColour(value) {
|
|
292
|
+
const hex = parseHexColour(value);
|
|
293
|
+
if (hex !== null) return hex;
|
|
294
|
+
const fn = parseColourFunction(value);
|
|
295
|
+
if (fn !== null) return fn;
|
|
296
|
+
const named = NAMED_COLOURS.get(value.toLowerCase());
|
|
297
|
+
return named === void 0 ? null : named;
|
|
298
|
+
}
|
|
299
|
+
function channelLuminance(component) {
|
|
300
|
+
const s = component / 255;
|
|
301
|
+
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
302
|
+
}
|
|
303
|
+
function relativeLuminance(colour) {
|
|
304
|
+
return 0.2126 * channelLuminance(colour.r) + 0.7152 * channelLuminance(colour.g) + 0.0722 * channelLuminance(colour.b);
|
|
305
|
+
}
|
|
306
|
+
function contrastRatio(a, b) {
|
|
307
|
+
const la = relativeLuminance(a);
|
|
308
|
+
const lb = relativeLuminance(b);
|
|
309
|
+
const light = la > lb ? la : lb;
|
|
310
|
+
const dark = la > lb ? lb : la;
|
|
311
|
+
return (light + 0.05) / (dark + 0.05);
|
|
312
|
+
}
|
|
313
|
+
function composite(fg, bg) {
|
|
314
|
+
if (fg.a >= 1) return fg;
|
|
315
|
+
return {
|
|
316
|
+
r: fg.r * fg.a + bg.r * (1 - fg.a),
|
|
317
|
+
g: fg.g * fg.a + bg.g * (1 - fg.a),
|
|
318
|
+
b: fg.b * fg.a + bg.b * (1 - fg.a),
|
|
319
|
+
a: 1
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
var MIN_THEME_CONTRAST_RATIO = 3;
|
|
323
|
+
var DEFAULT_BACKDROPS = {
|
|
324
|
+
light: [
|
|
325
|
+
{ r: 243, g: 250, b: 246, a: 1 },
|
|
326
|
+
// --paper
|
|
327
|
+
{ r: 255, g: 255, b: 255, a: 1 }
|
|
328
|
+
// --surface (--white)
|
|
329
|
+
],
|
|
330
|
+
dark: [
|
|
331
|
+
{ r: 10, g: 22, b: 40, a: 1 },
|
|
332
|
+
// --background (--navy)
|
|
333
|
+
{ r: 26, g: 40, b: 60, a: 1 }
|
|
334
|
+
// --surface (--navy-mid)
|
|
335
|
+
]
|
|
336
|
+
};
|
|
337
|
+
var ACCENT_FOREGROUND = { r: 255, g: 255, b: 255, a: 1 };
|
|
338
|
+
function backdropCandidates(accepted, forcedTheme) {
|
|
339
|
+
const schemes = forcedTheme === null ? ["light", "dark"] : [forcedTheme];
|
|
340
|
+
const bases = [];
|
|
341
|
+
for (let i = 0; i < schemes.length; i += 1) {
|
|
342
|
+
const scheme = DEFAULT_BACKDROPS[schemes[i]];
|
|
343
|
+
for (let j = 0; j < scheme.length; j += 1) bases.push(scheme[j]);
|
|
344
|
+
}
|
|
345
|
+
const supplied = [];
|
|
346
|
+
const names = ["surface", "background"];
|
|
347
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
348
|
+
const raw = accepted[names[i]];
|
|
349
|
+
if (raw === void 0) continue;
|
|
350
|
+
const parsed = parseColour(raw);
|
|
351
|
+
if (parsed === null) continue;
|
|
352
|
+
supplied.push(parsed);
|
|
353
|
+
}
|
|
354
|
+
if (supplied.length === 0) return bases;
|
|
355
|
+
const out = [];
|
|
356
|
+
for (let i = 0; i < supplied.length; i += 1) {
|
|
357
|
+
const value = supplied[i];
|
|
358
|
+
if (value.a >= 1) {
|
|
359
|
+
out.push(value);
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
for (let j = 0; j < bases.length; j += 1) {
|
|
363
|
+
out.push(composite(value, bases[j]));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
}
|
|
368
|
+
function enforceLegibilityFloor(accepted, rejections, forcedTheme) {
|
|
369
|
+
const backdrops = backdropCandidates(accepted, forcedTheme);
|
|
370
|
+
const text = accepted["text"];
|
|
371
|
+
if (text !== void 0) {
|
|
372
|
+
const parsed = parseColour(text);
|
|
373
|
+
let worst = Infinity;
|
|
374
|
+
if (parsed === null) worst = 0;
|
|
375
|
+
else {
|
|
376
|
+
for (let i = 0; i < backdrops.length; i += 1) {
|
|
377
|
+
const backdrop = backdrops[i];
|
|
378
|
+
const ratio = contrastRatio(composite(parsed, backdrop), backdrop);
|
|
379
|
+
if (ratio < worst) worst = ratio;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (worst < MIN_THEME_CONTRAST_RATIO) {
|
|
383
|
+
delete accepted["text"];
|
|
384
|
+
rejections.push({ key: "text", code: "variable_contrast_too_low" });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const accent = accepted["accent"];
|
|
388
|
+
if (accent !== void 0) {
|
|
389
|
+
const parsed = parseColour(accent);
|
|
390
|
+
let worst = Infinity;
|
|
391
|
+
if (parsed === null) worst = 0;
|
|
392
|
+
else if (parsed.a >= 1) {
|
|
393
|
+
worst = contrastRatio(parsed, ACCENT_FOREGROUND);
|
|
394
|
+
} else {
|
|
395
|
+
for (let i = 0; i < backdrops.length; i += 1) {
|
|
396
|
+
const ratio = contrastRatio(
|
|
397
|
+
composite(parsed, backdrops[i]),
|
|
398
|
+
ACCENT_FOREGROUND
|
|
399
|
+
);
|
|
400
|
+
if (ratio < worst) worst = ratio;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (worst < MIN_THEME_CONTRAST_RATIO) {
|
|
404
|
+
delete accepted["accent"];
|
|
405
|
+
rejections.push({ key: "accent", code: "variable_contrast_too_low" });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function matchesKind(kind, value) {
|
|
410
|
+
if (kind === "colour") return parseColour(value) !== null;
|
|
411
|
+
if (kind === "length") return LENGTH.test(value);
|
|
412
|
+
return FONT_FAMILY_LIST.test(value);
|
|
413
|
+
}
|
|
414
|
+
function classifyEntry(key, raw) {
|
|
415
|
+
const spec = VARIABLE_TARGETS.get(key);
|
|
416
|
+
if (spec === void 0) return { code: "unknown_variable", value: "" };
|
|
417
|
+
if (typeof raw !== "string")
|
|
418
|
+
return { code: "variable_not_a_string", value: "" };
|
|
419
|
+
if (raw.length > MAX_THEME_VARIABLE_VALUE_LENGTH)
|
|
420
|
+
return { code: "variable_value_too_long", value: "" };
|
|
421
|
+
if (spec.kind === "colour" && FOREGROUND_VARIABLES.has(key) && CONTENT_ERASING_COLOURS.has(raw.toLowerCase())) {
|
|
422
|
+
return { code: "variable_value_erases_content", value: "" };
|
|
423
|
+
}
|
|
424
|
+
if (!matchesKind(spec.kind, raw))
|
|
425
|
+
return { code: "variable_value_malformed", value: "" };
|
|
426
|
+
return { code: null, value: raw };
|
|
427
|
+
}
|
|
428
|
+
function resolveThemeVariables(raw, forcedTheme = null) {
|
|
429
|
+
const accepted = {};
|
|
430
|
+
const rejections = [];
|
|
431
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
432
|
+
return { accepted: {}, declarations: {}, rejections: [] };
|
|
433
|
+
}
|
|
434
|
+
const entries = Object.keys(raw);
|
|
435
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
436
|
+
const key = entries[i];
|
|
437
|
+
if (i >= MAX_THEME_VARIABLE_ENTRIES) {
|
|
438
|
+
rejections.push({ key, code: "too_many_variables" });
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
const outcome = classifyEntry(key, raw[key]);
|
|
442
|
+
if (outcome.code !== null) {
|
|
443
|
+
rejections.push({ key, code: outcome.code });
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
accepted[key] = outcome.value;
|
|
447
|
+
}
|
|
448
|
+
enforceLegibilityFloor(accepted, rejections, forcedTheme);
|
|
449
|
+
const declarations = {};
|
|
450
|
+
for (const name of CANOPY_THEME_VARIABLES) {
|
|
451
|
+
const value = accepted[name];
|
|
452
|
+
if (value === void 0) continue;
|
|
453
|
+
const targets = VARIABLE_TARGETS.get(name).targets;
|
|
454
|
+
for (let i = 0; i < targets.length; i += 1) {
|
|
455
|
+
declarations[targets[i]] = value;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
accepted,
|
|
460
|
+
declarations,
|
|
461
|
+
rejections
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function normalizeForcedTheme(raw) {
|
|
465
|
+
if (raw === "light" || raw === "dark") return raw;
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
|
|
28
469
|
// src/loader.ts
|
|
29
|
-
var LOADER_VERSION = "
|
|
470
|
+
var LOADER_VERSION = "4";
|
|
30
471
|
(function() {
|
|
31
472
|
"use strict";
|
|
32
473
|
if (typeof window === "undefined") return;
|
|
@@ -87,6 +528,15 @@ var LOADER_VERSION = "3";
|
|
|
87
528
|
function buildCheckoutUrl(internal) {
|
|
88
529
|
const url = new URL("/customer-pay", internal.origin);
|
|
89
530
|
url.searchParams.set("merchant", internal.merchant);
|
|
531
|
+
if (internal.theme !== null) {
|
|
532
|
+
url.searchParams.set(THEME_PARAM, internal.theme);
|
|
533
|
+
}
|
|
534
|
+
for (let i = 0; i < CANOPY_THEME_VARIABLES.length; i += 1) {
|
|
535
|
+
const name = CANOPY_THEME_VARIABLES[i];
|
|
536
|
+
const value = internal.themeVariables[name];
|
|
537
|
+
if (value === void 0) continue;
|
|
538
|
+
url.searchParams.set(THEME_VARIABLE_PARAM_PREFIX + name, value);
|
|
539
|
+
}
|
|
90
540
|
url.hash = "cap=" + encodeURIComponent(internal.capability || "");
|
|
91
541
|
return url.toString();
|
|
92
542
|
}
|
|
@@ -97,6 +547,15 @@ var LOADER_VERSION = "3";
|
|
|
97
547
|
} catch {
|
|
98
548
|
}
|
|
99
549
|
}
|
|
550
|
+
function emitThemeRejections(internal, rejections) {
|
|
551
|
+
for (let i = 0; i < rejections.length; i += 1) {
|
|
552
|
+
const rejection = rejections[i];
|
|
553
|
+
emit(internal, "error", {
|
|
554
|
+
code: rejection.code,
|
|
555
|
+
variable: rejection.key
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
100
559
|
function teardownFrame(internal) {
|
|
101
560
|
if (internal.handshakeTimer) {
|
|
102
561
|
clearTimeout(internal.handshakeTimer);
|
|
@@ -269,6 +728,8 @@ var LOADER_VERSION = "3";
|
|
|
269
728
|
}, destroy: function() {
|
|
270
729
|
} };
|
|
271
730
|
}
|
|
731
|
+
const forcedTheme = normalizeForcedTheme(opts.theme);
|
|
732
|
+
const themeVariables = resolveThemeVariables(opts.variables, forcedTheme);
|
|
272
733
|
const node = typeof opts.target === "string" ? document.querySelector(opts.target) : opts.target;
|
|
273
734
|
if (!node) {
|
|
274
735
|
return { openNow: function() {
|
|
@@ -282,6 +743,8 @@ var LOADER_VERSION = "3";
|
|
|
282
743
|
capability: opts.capability,
|
|
283
744
|
merchant: opts.merchant,
|
|
284
745
|
origin: originResolution.origin,
|
|
746
|
+
theme: forcedTheme,
|
|
747
|
+
themeVariables: themeVariables.accepted,
|
|
285
748
|
surface: opts.surface || "popup",
|
|
286
749
|
handshakeTimeoutMs: opts.handshakeTimeoutMs || DEFAULT_HANDSHAKE_TIMEOUT_MS,
|
|
287
750
|
fallbackLabel: opts.fallbackLabel || DEFAULT_FALLBACK_LABEL,
|
|
@@ -302,6 +765,7 @@ var LOADER_VERSION = "3";
|
|
|
302
765
|
handleMessage(internal, event);
|
|
303
766
|
};
|
|
304
767
|
window.addEventListener("message", internal.messageListener);
|
|
768
|
+
emitThemeRejections(internal, themeVariables.rejections);
|
|
305
769
|
if (internal.surface === "iframe" || internal.surface === "auto") {
|
|
306
770
|
createFrame(internal);
|
|
307
771
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canopy checkout theming -- the ONE validator behind
|
|
3
|
+
* `Canopy.mount({ theme, variables })`.
|
|
4
|
+
*
|
|
5
|
+
* Imported by BOTH `packages/checkout-sdk/src/loader.ts` (tsup bundles this
|
|
6
|
+
* relative sibling into the IIFE) and `app/customer-pay/page.tsx` (the app
|
|
7
|
+
* already imports across this package boundary; `tsconfig.json` maps `@/*`
|
|
8
|
+
* to the repo root and includes `packages/**\/*.ts`). Deliberately NOT
|
|
9
|
+
* duplicated the way `MESSAGE_SOURCE` is -- that duplication exists only
|
|
10
|
+
* because a React component cannot reach into the tsup entry graph, which
|
|
11
|
+
* is not the case here. Two copies of a grammar are two grammars.
|
|
12
|
+
*
|
|
13
|
+
* The two passes are not equals. The SERVER pass in `app/customer-pay/
|
|
14
|
+
* page.tsx` is the security boundary: those values arrive as query
|
|
15
|
+
* parameters that anyone can hand-craft, with no loader involved. The
|
|
16
|
+
* client-side pass inside `mount()` is a developer-experience courtesy --
|
|
17
|
+
* it turns a typo into an `error` event on the merchant's own console
|
|
18
|
+
* instead of a value that silently vanishes server-side.
|
|
19
|
+
*
|
|
20
|
+
* Threat register (quick 260906-qql): T-QQL-01 tampering (a value escaping
|
|
21
|
+
* its declaration), T-QQL-02 elevation (a prototype-chain key), T-QQL-03
|
|
22
|
+
* denial of service (unbounded input).
|
|
23
|
+
*
|
|
24
|
+
* Pure and browser-free: no `window`, no `document`, no DOM types. It runs
|
|
25
|
+
* inside a React Server Component, inside a `node --test` process, and
|
|
26
|
+
* inside an es2018 IIFE in a merchant's page.
|
|
27
|
+
*/
|
|
28
|
+
/** What a merchant may pass as `theme`. */
|
|
29
|
+
type CanopyThemeOption = "light" | "dark" | "auto";
|
|
30
|
+
/** The eight public names, in the order their declarations are emitted. */
|
|
31
|
+
declare const CANOPY_THEME_VARIABLES: readonly ["accent", "background", "surface", "text", "muted", "border", "radius", "font"];
|
|
32
|
+
type CanopyThemeVariable = (typeof CANOPY_THEME_VARIABLES)[number];
|
|
33
|
+
/** What a merchant may pass as `variables`. */
|
|
34
|
+
type CanopyThemeVariables = Partial<Record<CanopyThemeVariable, string>>;
|
|
35
|
+
|
|
1
36
|
/**
|
|
2
37
|
* Canopy checkout SDK loader.
|
|
3
38
|
*
|
|
@@ -31,6 +66,7 @@
|
|
|
31
66
|
* | "auto"`, default `"popup"`), seven events (`ready`, `resize`, `paid`,
|
|
32
67
|
* `error`, `close`, `surface_unavailable`, `fallback_offered`).
|
|
33
68
|
*/
|
|
69
|
+
|
|
34
70
|
/**
|
|
35
71
|
* `close` is POPUP-ONLY. It reports that the payer closed the checkout
|
|
36
72
|
* window, which the loader detects by polling `popupWin.closed`. An inline
|
|
@@ -68,6 +104,28 @@ type MountOptions = {
|
|
|
68
104
|
* script tag when both are available.
|
|
69
105
|
*/
|
|
70
106
|
canopyOrigin?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Forces the checkout's colour scheme for THIS embed only, overriding the
|
|
109
|
+
* visitor's own stored preference inside the frame and nowhere else.
|
|
110
|
+
* `"auto"` and omitting it are the same thing and are the default: the
|
|
111
|
+
* visitor's preference (and their OS setting) keeps deciding. Nothing is
|
|
112
|
+
* written to the visitor's storage either way.
|
|
113
|
+
*/
|
|
114
|
+
theme?: CanopyThemeOption;
|
|
115
|
+
/**
|
|
116
|
+
* Repaints the checkout through eight allowlisted names: `accent`,
|
|
117
|
+
* `background`, `surface`, `text`, `muted`, `border`, `radius` and
|
|
118
|
+
* `font`. Values are CSS -- a colour (hex, `rgb()`/`rgba()`/`hsl()`, or a
|
|
119
|
+
* bare keyword such as `transparent`) for the six colour names, a length
|
|
120
|
+
* for `radius`, a plain family list for `font`.
|
|
121
|
+
*
|
|
122
|
+
* Rejection is PER ENTRY and never fatal: an unknown name or a malformed
|
|
123
|
+
* value drops that one entry, emits an `error` event naming it, and lets
|
|
124
|
+
* every other entry through. A cosmetic option can never block a checkout.
|
|
125
|
+
* Validated again server-side, which is the authoritative pass -- this one
|
|
126
|
+
* exists so a typo reaches your console instead of vanishing silently.
|
|
127
|
+
*/
|
|
128
|
+
variables?: CanopyThemeVariables;
|
|
71
129
|
};
|
|
72
130
|
type CanopyHandle = {
|
|
73
131
|
openNow: (win: Window | null) => void;
|
|
@@ -97,4 +155,4 @@ declare function mount(opts: MountOptions): CanopyHandle;
|
|
|
97
155
|
* `undefined` server-side / before the loader has installed the global. */
|
|
98
156
|
declare function version(): string | undefined;
|
|
99
157
|
|
|
100
|
-
export { type CanopyEvent, type CanopyEventType, type CanopyHandle, type MountOptions, type Surface, mount, version };
|
|
158
|
+
export { type CanopyEvent, type CanopyEventType, type CanopyHandle, type CanopyThemeOption, type CanopyThemeVariable, type CanopyThemeVariables, type MountOptions, type Surface, mount, version };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canopy checkout theming -- the ONE validator behind
|
|
3
|
+
* `Canopy.mount({ theme, variables })`.
|
|
4
|
+
*
|
|
5
|
+
* Imported by BOTH `packages/checkout-sdk/src/loader.ts` (tsup bundles this
|
|
6
|
+
* relative sibling into the IIFE) and `app/customer-pay/page.tsx` (the app
|
|
7
|
+
* already imports across this package boundary; `tsconfig.json` maps `@/*`
|
|
8
|
+
* to the repo root and includes `packages/**\/*.ts`). Deliberately NOT
|
|
9
|
+
* duplicated the way `MESSAGE_SOURCE` is -- that duplication exists only
|
|
10
|
+
* because a React component cannot reach into the tsup entry graph, which
|
|
11
|
+
* is not the case here. Two copies of a grammar are two grammars.
|
|
12
|
+
*
|
|
13
|
+
* The two passes are not equals. The SERVER pass in `app/customer-pay/
|
|
14
|
+
* page.tsx` is the security boundary: those values arrive as query
|
|
15
|
+
* parameters that anyone can hand-craft, with no loader involved. The
|
|
16
|
+
* client-side pass inside `mount()` is a developer-experience courtesy --
|
|
17
|
+
* it turns a typo into an `error` event on the merchant's own console
|
|
18
|
+
* instead of a value that silently vanishes server-side.
|
|
19
|
+
*
|
|
20
|
+
* Threat register (quick 260906-qql): T-QQL-01 tampering (a value escaping
|
|
21
|
+
* its declaration), T-QQL-02 elevation (a prototype-chain key), T-QQL-03
|
|
22
|
+
* denial of service (unbounded input).
|
|
23
|
+
*
|
|
24
|
+
* Pure and browser-free: no `window`, no `document`, no DOM types. It runs
|
|
25
|
+
* inside a React Server Component, inside a `node --test` process, and
|
|
26
|
+
* inside an es2018 IIFE in a merchant's page.
|
|
27
|
+
*/
|
|
28
|
+
/** What a merchant may pass as `theme`. */
|
|
29
|
+
type CanopyThemeOption = "light" | "dark" | "auto";
|
|
30
|
+
/** The eight public names, in the order their declarations are emitted. */
|
|
31
|
+
declare const CANOPY_THEME_VARIABLES: readonly ["accent", "background", "surface", "text", "muted", "border", "radius", "font"];
|
|
32
|
+
type CanopyThemeVariable = (typeof CANOPY_THEME_VARIABLES)[number];
|
|
33
|
+
/** What a merchant may pass as `variables`. */
|
|
34
|
+
type CanopyThemeVariables = Partial<Record<CanopyThemeVariable, string>>;
|
|
35
|
+
|
|
1
36
|
/**
|
|
2
37
|
* Canopy checkout SDK loader.
|
|
3
38
|
*
|
|
@@ -31,6 +66,7 @@
|
|
|
31
66
|
* | "auto"`, default `"popup"`), seven events (`ready`, `resize`, `paid`,
|
|
32
67
|
* `error`, `close`, `surface_unavailable`, `fallback_offered`).
|
|
33
68
|
*/
|
|
69
|
+
|
|
34
70
|
/**
|
|
35
71
|
* `close` is POPUP-ONLY. It reports that the payer closed the checkout
|
|
36
72
|
* window, which the loader detects by polling `popupWin.closed`. An inline
|
|
@@ -68,6 +104,28 @@ type MountOptions = {
|
|
|
68
104
|
* script tag when both are available.
|
|
69
105
|
*/
|
|
70
106
|
canopyOrigin?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Forces the checkout's colour scheme for THIS embed only, overriding the
|
|
109
|
+
* visitor's own stored preference inside the frame and nowhere else.
|
|
110
|
+
* `"auto"` and omitting it are the same thing and are the default: the
|
|
111
|
+
* visitor's preference (and their OS setting) keeps deciding. Nothing is
|
|
112
|
+
* written to the visitor's storage either way.
|
|
113
|
+
*/
|
|
114
|
+
theme?: CanopyThemeOption;
|
|
115
|
+
/**
|
|
116
|
+
* Repaints the checkout through eight allowlisted names: `accent`,
|
|
117
|
+
* `background`, `surface`, `text`, `muted`, `border`, `radius` and
|
|
118
|
+
* `font`. Values are CSS -- a colour (hex, `rgb()`/`rgba()`/`hsl()`, or a
|
|
119
|
+
* bare keyword such as `transparent`) for the six colour names, a length
|
|
120
|
+
* for `radius`, a plain family list for `font`.
|
|
121
|
+
*
|
|
122
|
+
* Rejection is PER ENTRY and never fatal: an unknown name or a malformed
|
|
123
|
+
* value drops that one entry, emits an `error` event naming it, and lets
|
|
124
|
+
* every other entry through. A cosmetic option can never block a checkout.
|
|
125
|
+
* Validated again server-side, which is the authoritative pass -- this one
|
|
126
|
+
* exists so a typo reaches your console instead of vanishing silently.
|
|
127
|
+
*/
|
|
128
|
+
variables?: CanopyThemeVariables;
|
|
71
129
|
};
|
|
72
130
|
type CanopyHandle = {
|
|
73
131
|
openNow: (win: Window | null) => void;
|
|
@@ -97,4 +155,4 @@ declare function mount(opts: MountOptions): CanopyHandle;
|
|
|
97
155
|
* `undefined` server-side / before the loader has installed the global. */
|
|
98
156
|
declare function version(): string | undefined;
|
|
99
157
|
|
|
100
|
-
export { type CanopyEvent, type CanopyEventType, type CanopyHandle, type MountOptions, type Surface, mount, version };
|
|
158
|
+
export { type CanopyEvent, type CanopyEventType, type CanopyHandle, type CanopyThemeOption, type CanopyThemeVariable, type CanopyThemeVariables, type MountOptions, type Surface, mount, version };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,446 @@
|
|
|
1
|
+
// src/theme.ts
|
|
2
|
+
var THEME_PARAM = "theme";
|
|
3
|
+
var THEME_VARIABLE_PARAM_PREFIX = "v.";
|
|
4
|
+
var MAX_THEME_VARIABLE_VALUE_LENGTH = 64;
|
|
5
|
+
var MAX_THEME_VARIABLE_ENTRIES = 12;
|
|
6
|
+
var CANOPY_THEME_VARIABLES = [
|
|
7
|
+
"accent",
|
|
8
|
+
"background",
|
|
9
|
+
"surface",
|
|
10
|
+
"text",
|
|
11
|
+
"muted",
|
|
12
|
+
"border",
|
|
13
|
+
"radius",
|
|
14
|
+
"font"
|
|
15
|
+
];
|
|
16
|
+
var VARIABLE_TARGETS = /* @__PURE__ */ new Map([
|
|
17
|
+
[
|
|
18
|
+
"accent",
|
|
19
|
+
{
|
|
20
|
+
kind: "colour",
|
|
21
|
+
targets: ["--leaf", "--primary", "--accent", "--ring"]
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
"background",
|
|
26
|
+
{
|
|
27
|
+
kind: "colour",
|
|
28
|
+
targets: ["--paper", "--background", "background"]
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
"surface",
|
|
33
|
+
{
|
|
34
|
+
kind: "colour",
|
|
35
|
+
targets: ["--surface", "--card", "--popover", "--secondary"]
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
[
|
|
39
|
+
"text",
|
|
40
|
+
{
|
|
41
|
+
kind: "colour",
|
|
42
|
+
targets: [
|
|
43
|
+
"--ink",
|
|
44
|
+
"--foreground",
|
|
45
|
+
"--card-foreground",
|
|
46
|
+
"--popover-foreground",
|
|
47
|
+
"--secondary-foreground"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
"muted",
|
|
53
|
+
{
|
|
54
|
+
kind: "colour",
|
|
55
|
+
targets: ["--ink-muted", "--muted-foreground"]
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
"border",
|
|
60
|
+
{ kind: "colour", targets: ["--line", "--border", "--input"] }
|
|
61
|
+
],
|
|
62
|
+
["radius", { kind: "length", targets: ["--radius"] }],
|
|
63
|
+
["font", { kind: "font", targets: ["--font-sans"] }]
|
|
64
|
+
]);
|
|
65
|
+
var COLOUR_HEX = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
66
|
+
var COLOUR_FUNCTION = /^(rgb|rgba|hsl|hsla)\(([0-9a-z.,%/ +-]*)\)$/;
|
|
67
|
+
var NUMBER = /^[+-]?(?:[0-9]{1,4}(?:\.[0-9]{1,4})?|\.[0-9]{1,4})$/;
|
|
68
|
+
var PERCENT = /^[+-]?(?:[0-9]{1,3}(?:\.[0-9]{1,4})?|\.[0-9]{1,4})%$/;
|
|
69
|
+
var LENGTH = /^(?:0|(?:[0-9]{1,4}(?:\.[0-9]{1,4})?|\.[0-9]{1,4})(?:px|rem|em|%))$/;
|
|
70
|
+
var FONT_FAMILY_LIST = /^[A-Za-z0-9 ,_-]+$/;
|
|
71
|
+
var NAMED_COLOURS = /* @__PURE__ */ new Map([
|
|
72
|
+
["transparent", { r: 0, g: 0, b: 0, a: 0 }],
|
|
73
|
+
["black", { r: 0, g: 0, b: 0, a: 1 }],
|
|
74
|
+
["white", { r: 255, g: 255, b: 255, a: 1 }],
|
|
75
|
+
["silver", { r: 192, g: 192, b: 192, a: 1 }],
|
|
76
|
+
["gray", { r: 128, g: 128, b: 128, a: 1 }],
|
|
77
|
+
["grey", { r: 128, g: 128, b: 128, a: 1 }],
|
|
78
|
+
["maroon", { r: 128, g: 0, b: 0, a: 1 }],
|
|
79
|
+
["red", { r: 255, g: 0, b: 0, a: 1 }],
|
|
80
|
+
["purple", { r: 128, g: 0, b: 128, a: 1 }],
|
|
81
|
+
["fuchsia", { r: 255, g: 0, b: 255, a: 1 }],
|
|
82
|
+
["magenta", { r: 255, g: 0, b: 255, a: 1 }],
|
|
83
|
+
["green", { r: 0, g: 128, b: 0, a: 1 }],
|
|
84
|
+
["lime", { r: 0, g: 255, b: 0, a: 1 }],
|
|
85
|
+
["olive", { r: 128, g: 128, b: 0, a: 1 }],
|
|
86
|
+
["yellow", { r: 255, g: 255, b: 0, a: 1 }],
|
|
87
|
+
["navy", { r: 0, g: 0, b: 128, a: 1 }],
|
|
88
|
+
["blue", { r: 0, g: 0, b: 255, a: 1 }],
|
|
89
|
+
["teal", { r: 0, g: 128, b: 128, a: 1 }],
|
|
90
|
+
["aqua", { r: 0, g: 255, b: 255, a: 1 }],
|
|
91
|
+
["cyan", { r: 0, g: 255, b: 255, a: 1 }],
|
|
92
|
+
["orange", { r: 255, g: 165, b: 0, a: 1 }],
|
|
93
|
+
["pink", { r: 255, g: 192, b: 203, a: 1 }],
|
|
94
|
+
["brown", { r: 165, g: 42, b: 42, a: 1 }],
|
|
95
|
+
["beige", { r: 245, g: 245, b: 220, a: 1 }],
|
|
96
|
+
["ivory", { r: 255, g: 255, b: 240, a: 1 }],
|
|
97
|
+
["indigo", { r: 75, g: 0, b: 130, a: 1 }],
|
|
98
|
+
["violet", { r: 238, g: 130, b: 238, a: 1 }],
|
|
99
|
+
["gold", { r: 255, g: 215, b: 0, a: 1 }],
|
|
100
|
+
["crimson", { r: 220, g: 20, b: 60, a: 1 }],
|
|
101
|
+
["salmon", { r: 250, g: 128, b: 114, a: 1 }],
|
|
102
|
+
["khaki", { r: 240, g: 230, b: 140, a: 1 }],
|
|
103
|
+
["lavender", { r: 230, g: 230, b: 250, a: 1 }],
|
|
104
|
+
["turquoise", { r: 64, g: 224, b: 208, a: 1 }],
|
|
105
|
+
["tan", { r: 210, g: 180, b: 140, a: 1 }],
|
|
106
|
+
["plum", { r: 221, g: 160, b: 221, a: 1 }],
|
|
107
|
+
["orchid", { r: 218, g: 112, b: 214, a: 1 }],
|
|
108
|
+
["coral", { r: 255, g: 127, b: 80, a: 1 }],
|
|
109
|
+
["tomato", { r: 255, g: 99, b: 71, a: 1 }],
|
|
110
|
+
["chocolate", { r: 210, g: 105, b: 30, a: 1 }],
|
|
111
|
+
["snow", { r: 255, g: 250, b: 250, a: 1 }],
|
|
112
|
+
["linen", { r: 250, g: 240, b: 230, a: 1 }],
|
|
113
|
+
["azure", { r: 240, g: 255, b: 255, a: 1 }],
|
|
114
|
+
["wheat", { r: 245, g: 222, b: 179, a: 1 }],
|
|
115
|
+
["thistle", { r: 216, g: 191, b: 216, a: 1 }]
|
|
116
|
+
]);
|
|
117
|
+
var CONTENT_ERASING_COLOURS = /* @__PURE__ */ new Set([
|
|
118
|
+
"transparent",
|
|
119
|
+
"currentcolor",
|
|
120
|
+
"inherit",
|
|
121
|
+
"initial",
|
|
122
|
+
"unset",
|
|
123
|
+
"revert",
|
|
124
|
+
"revert-layer"
|
|
125
|
+
]);
|
|
126
|
+
var FOREGROUND_VARIABLES = /* @__PURE__ */ new Set([
|
|
127
|
+
"text",
|
|
128
|
+
"muted",
|
|
129
|
+
"accent"
|
|
130
|
+
]);
|
|
131
|
+
function clamp(value, low, high) {
|
|
132
|
+
if (value < low) return low;
|
|
133
|
+
if (value > high) return high;
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
function parseNumber(token) {
|
|
137
|
+
if (!NUMBER.test(token)) return null;
|
|
138
|
+
const value = Number(token);
|
|
139
|
+
return Number.isFinite(value) ? value : null;
|
|
140
|
+
}
|
|
141
|
+
function parsePercent(token) {
|
|
142
|
+
if (!PERCENT.test(token)) return null;
|
|
143
|
+
const value = Number(token.slice(0, token.length - 1));
|
|
144
|
+
return Number.isFinite(value) ? value / 100 : null;
|
|
145
|
+
}
|
|
146
|
+
function parseRgbComponent(token) {
|
|
147
|
+
const percent = parsePercent(token);
|
|
148
|
+
if (percent !== null) return clamp(percent, 0, 1) * 255;
|
|
149
|
+
const number = parseNumber(token);
|
|
150
|
+
if (number === null) return null;
|
|
151
|
+
return clamp(number, 0, 255);
|
|
152
|
+
}
|
|
153
|
+
function parseAlpha(token) {
|
|
154
|
+
const percent = parsePercent(token);
|
|
155
|
+
if (percent !== null) return clamp(percent, 0, 1);
|
|
156
|
+
const number = parseNumber(token);
|
|
157
|
+
if (number === null) return null;
|
|
158
|
+
return clamp(number, 0, 1);
|
|
159
|
+
}
|
|
160
|
+
function parseHue(token) {
|
|
161
|
+
let text = token;
|
|
162
|
+
let scale = 1;
|
|
163
|
+
if (text.endsWith("deg")) text = text.slice(0, text.length - 3);
|
|
164
|
+
else if (text.endsWith("grad")) {
|
|
165
|
+
text = text.slice(0, text.length - 4);
|
|
166
|
+
scale = 360 / 400;
|
|
167
|
+
} else if (text.endsWith("turn")) {
|
|
168
|
+
text = text.slice(0, text.length - 4);
|
|
169
|
+
scale = 360;
|
|
170
|
+
} else if (text.endsWith("rad")) {
|
|
171
|
+
text = text.slice(0, text.length - 3);
|
|
172
|
+
scale = 180 / Math.PI;
|
|
173
|
+
}
|
|
174
|
+
const number = parseNumber(text);
|
|
175
|
+
if (number === null) return null;
|
|
176
|
+
return (number * scale % 360 + 360) % 360;
|
|
177
|
+
}
|
|
178
|
+
function hueToChannel(p, q, tRaw) {
|
|
179
|
+
let t = tRaw;
|
|
180
|
+
if (t < 0) t += 1;
|
|
181
|
+
if (t > 1) t -= 1;
|
|
182
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
183
|
+
if (t < 1 / 2) return q;
|
|
184
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
185
|
+
return p;
|
|
186
|
+
}
|
|
187
|
+
function hslToRgb(h, s, l, a) {
|
|
188
|
+
if (s === 0) {
|
|
189
|
+
const grey = l * 255;
|
|
190
|
+
return { r: grey, g: grey, b: grey, a };
|
|
191
|
+
}
|
|
192
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
193
|
+
const p = 2 * l - q;
|
|
194
|
+
const hk = h / 360;
|
|
195
|
+
return {
|
|
196
|
+
r: hueToChannel(p, q, hk + 1 / 3) * 255,
|
|
197
|
+
g: hueToChannel(p, q, hk) * 255,
|
|
198
|
+
b: hueToChannel(p, q, hk - 1 / 3) * 255,
|
|
199
|
+
a
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function splitColourComponents(body) {
|
|
203
|
+
const trimmed = body.trim();
|
|
204
|
+
if (trimmed === "") return null;
|
|
205
|
+
if (trimmed.indexOf(",") >= 0) {
|
|
206
|
+
if (trimmed.indexOf("/") >= 0) return null;
|
|
207
|
+
const parts = trimmed.split(",").map(function(part) {
|
|
208
|
+
return part.trim();
|
|
209
|
+
});
|
|
210
|
+
if (parts.length !== 3 && parts.length !== 4) return null;
|
|
211
|
+
return parts;
|
|
212
|
+
}
|
|
213
|
+
const slashParts = trimmed.split("/");
|
|
214
|
+
if (slashParts.length > 2) return null;
|
|
215
|
+
const main = slashParts[0].trim().split(/ +/);
|
|
216
|
+
if (main.length !== 3) return null;
|
|
217
|
+
if (slashParts.length === 1) return main;
|
|
218
|
+
const alpha = slashParts[1].trim();
|
|
219
|
+
if (alpha === "" || alpha.indexOf(" ") >= 0) return null;
|
|
220
|
+
return main.concat([alpha]);
|
|
221
|
+
}
|
|
222
|
+
function parseColourFunction(value) {
|
|
223
|
+
const match = COLOUR_FUNCTION.exec(value);
|
|
224
|
+
if (match === null) return null;
|
|
225
|
+
const fn = match[1];
|
|
226
|
+
const parts = splitColourComponents(match[2]);
|
|
227
|
+
if (parts === null) return null;
|
|
228
|
+
let alpha = 1;
|
|
229
|
+
if (parts.length === 4) {
|
|
230
|
+
const parsed = parseAlpha(parts[3]);
|
|
231
|
+
if (parsed === null) return null;
|
|
232
|
+
alpha = parsed;
|
|
233
|
+
}
|
|
234
|
+
if (fn === "rgb" || fn === "rgba") {
|
|
235
|
+
const r = parseRgbComponent(parts[0]);
|
|
236
|
+
const g = parseRgbComponent(parts[1]);
|
|
237
|
+
const b = parseRgbComponent(parts[2]);
|
|
238
|
+
if (r === null || g === null || b === null) return null;
|
|
239
|
+
return { r, g, b, a: alpha };
|
|
240
|
+
}
|
|
241
|
+
const h = parseHue(parts[0]);
|
|
242
|
+
const s = parsePercent(parts[1]);
|
|
243
|
+
const l = parsePercent(parts[2]);
|
|
244
|
+
if (h === null || s === null || l === null) return null;
|
|
245
|
+
return hslToRgb(h, clamp(s, 0, 1), clamp(l, 0, 1), alpha);
|
|
246
|
+
}
|
|
247
|
+
function parseHexColour(value) {
|
|
248
|
+
if (!COLOUR_HEX.test(value)) return null;
|
|
249
|
+
const digits = value.slice(1);
|
|
250
|
+
const short = digits.length === 3 || digits.length === 4;
|
|
251
|
+
const size = short ? 1 : 2;
|
|
252
|
+
const channel = function(index) {
|
|
253
|
+
const part = digits.slice(index * size, index * size + size);
|
|
254
|
+
return parseInt(short ? part + part : part, 16);
|
|
255
|
+
};
|
|
256
|
+
const hasAlpha = digits.length === 4 || digits.length === 8;
|
|
257
|
+
return {
|
|
258
|
+
r: channel(0),
|
|
259
|
+
g: channel(1),
|
|
260
|
+
b: channel(2),
|
|
261
|
+
a: hasAlpha ? channel(3) / 255 : 1
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function parseColour(value) {
|
|
265
|
+
const hex = parseHexColour(value);
|
|
266
|
+
if (hex !== null) return hex;
|
|
267
|
+
const fn = parseColourFunction(value);
|
|
268
|
+
if (fn !== null) return fn;
|
|
269
|
+
const named = NAMED_COLOURS.get(value.toLowerCase());
|
|
270
|
+
return named === void 0 ? null : named;
|
|
271
|
+
}
|
|
272
|
+
function channelLuminance(component) {
|
|
273
|
+
const s = component / 255;
|
|
274
|
+
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
275
|
+
}
|
|
276
|
+
function relativeLuminance(colour) {
|
|
277
|
+
return 0.2126 * channelLuminance(colour.r) + 0.7152 * channelLuminance(colour.g) + 0.0722 * channelLuminance(colour.b);
|
|
278
|
+
}
|
|
279
|
+
function contrastRatio(a, b) {
|
|
280
|
+
const la = relativeLuminance(a);
|
|
281
|
+
const lb = relativeLuminance(b);
|
|
282
|
+
const light = la > lb ? la : lb;
|
|
283
|
+
const dark = la > lb ? lb : la;
|
|
284
|
+
return (light + 0.05) / (dark + 0.05);
|
|
285
|
+
}
|
|
286
|
+
function composite(fg, bg) {
|
|
287
|
+
if (fg.a >= 1) return fg;
|
|
288
|
+
return {
|
|
289
|
+
r: fg.r * fg.a + bg.r * (1 - fg.a),
|
|
290
|
+
g: fg.g * fg.a + bg.g * (1 - fg.a),
|
|
291
|
+
b: fg.b * fg.a + bg.b * (1 - fg.a),
|
|
292
|
+
a: 1
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
var MIN_THEME_CONTRAST_RATIO = 3;
|
|
296
|
+
var DEFAULT_BACKDROPS = {
|
|
297
|
+
light: [
|
|
298
|
+
{ r: 243, g: 250, b: 246, a: 1 },
|
|
299
|
+
// --paper
|
|
300
|
+
{ r: 255, g: 255, b: 255, a: 1 }
|
|
301
|
+
// --surface (--white)
|
|
302
|
+
],
|
|
303
|
+
dark: [
|
|
304
|
+
{ r: 10, g: 22, b: 40, a: 1 },
|
|
305
|
+
// --background (--navy)
|
|
306
|
+
{ r: 26, g: 40, b: 60, a: 1 }
|
|
307
|
+
// --surface (--navy-mid)
|
|
308
|
+
]
|
|
309
|
+
};
|
|
310
|
+
var ACCENT_FOREGROUND = { r: 255, g: 255, b: 255, a: 1 };
|
|
311
|
+
function backdropCandidates(accepted, forcedTheme) {
|
|
312
|
+
const schemes = forcedTheme === null ? ["light", "dark"] : [forcedTheme];
|
|
313
|
+
const bases = [];
|
|
314
|
+
for (let i = 0; i < schemes.length; i += 1) {
|
|
315
|
+
const scheme = DEFAULT_BACKDROPS[schemes[i]];
|
|
316
|
+
for (let j = 0; j < scheme.length; j += 1) bases.push(scheme[j]);
|
|
317
|
+
}
|
|
318
|
+
const supplied = [];
|
|
319
|
+
const names = ["surface", "background"];
|
|
320
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
321
|
+
const raw = accepted[names[i]];
|
|
322
|
+
if (raw === void 0) continue;
|
|
323
|
+
const parsed = parseColour(raw);
|
|
324
|
+
if (parsed === null) continue;
|
|
325
|
+
supplied.push(parsed);
|
|
326
|
+
}
|
|
327
|
+
if (supplied.length === 0) return bases;
|
|
328
|
+
const out = [];
|
|
329
|
+
for (let i = 0; i < supplied.length; i += 1) {
|
|
330
|
+
const value = supplied[i];
|
|
331
|
+
if (value.a >= 1) {
|
|
332
|
+
out.push(value);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
for (let j = 0; j < bases.length; j += 1) {
|
|
336
|
+
out.push(composite(value, bases[j]));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
function enforceLegibilityFloor(accepted, rejections, forcedTheme) {
|
|
342
|
+
const backdrops = backdropCandidates(accepted, forcedTheme);
|
|
343
|
+
const text = accepted["text"];
|
|
344
|
+
if (text !== void 0) {
|
|
345
|
+
const parsed = parseColour(text);
|
|
346
|
+
let worst = Infinity;
|
|
347
|
+
if (parsed === null) worst = 0;
|
|
348
|
+
else {
|
|
349
|
+
for (let i = 0; i < backdrops.length; i += 1) {
|
|
350
|
+
const backdrop = backdrops[i];
|
|
351
|
+
const ratio = contrastRatio(composite(parsed, backdrop), backdrop);
|
|
352
|
+
if (ratio < worst) worst = ratio;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (worst < MIN_THEME_CONTRAST_RATIO) {
|
|
356
|
+
delete accepted["text"];
|
|
357
|
+
rejections.push({ key: "text", code: "variable_contrast_too_low" });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const accent = accepted["accent"];
|
|
361
|
+
if (accent !== void 0) {
|
|
362
|
+
const parsed = parseColour(accent);
|
|
363
|
+
let worst = Infinity;
|
|
364
|
+
if (parsed === null) worst = 0;
|
|
365
|
+
else if (parsed.a >= 1) {
|
|
366
|
+
worst = contrastRatio(parsed, ACCENT_FOREGROUND);
|
|
367
|
+
} else {
|
|
368
|
+
for (let i = 0; i < backdrops.length; i += 1) {
|
|
369
|
+
const ratio = contrastRatio(
|
|
370
|
+
composite(parsed, backdrops[i]),
|
|
371
|
+
ACCENT_FOREGROUND
|
|
372
|
+
);
|
|
373
|
+
if (ratio < worst) worst = ratio;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (worst < MIN_THEME_CONTRAST_RATIO) {
|
|
377
|
+
delete accepted["accent"];
|
|
378
|
+
rejections.push({ key: "accent", code: "variable_contrast_too_low" });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function matchesKind(kind, value) {
|
|
383
|
+
if (kind === "colour") return parseColour(value) !== null;
|
|
384
|
+
if (kind === "length") return LENGTH.test(value);
|
|
385
|
+
return FONT_FAMILY_LIST.test(value);
|
|
386
|
+
}
|
|
387
|
+
function classifyEntry(key, raw) {
|
|
388
|
+
const spec = VARIABLE_TARGETS.get(key);
|
|
389
|
+
if (spec === void 0) return { code: "unknown_variable", value: "" };
|
|
390
|
+
if (typeof raw !== "string")
|
|
391
|
+
return { code: "variable_not_a_string", value: "" };
|
|
392
|
+
if (raw.length > MAX_THEME_VARIABLE_VALUE_LENGTH)
|
|
393
|
+
return { code: "variable_value_too_long", value: "" };
|
|
394
|
+
if (spec.kind === "colour" && FOREGROUND_VARIABLES.has(key) && CONTENT_ERASING_COLOURS.has(raw.toLowerCase())) {
|
|
395
|
+
return { code: "variable_value_erases_content", value: "" };
|
|
396
|
+
}
|
|
397
|
+
if (!matchesKind(spec.kind, raw))
|
|
398
|
+
return { code: "variable_value_malformed", value: "" };
|
|
399
|
+
return { code: null, value: raw };
|
|
400
|
+
}
|
|
401
|
+
function resolveThemeVariables(raw, forcedTheme = null) {
|
|
402
|
+
const accepted = {};
|
|
403
|
+
const rejections = [];
|
|
404
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
405
|
+
return { accepted: {}, declarations: {}, rejections: [] };
|
|
406
|
+
}
|
|
407
|
+
const entries = Object.keys(raw);
|
|
408
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
409
|
+
const key = entries[i];
|
|
410
|
+
if (i >= MAX_THEME_VARIABLE_ENTRIES) {
|
|
411
|
+
rejections.push({ key, code: "too_many_variables" });
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
const outcome = classifyEntry(key, raw[key]);
|
|
415
|
+
if (outcome.code !== null) {
|
|
416
|
+
rejections.push({ key, code: outcome.code });
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
accepted[key] = outcome.value;
|
|
420
|
+
}
|
|
421
|
+
enforceLegibilityFloor(accepted, rejections, forcedTheme);
|
|
422
|
+
const declarations = {};
|
|
423
|
+
for (const name of CANOPY_THEME_VARIABLES) {
|
|
424
|
+
const value = accepted[name];
|
|
425
|
+
if (value === void 0) continue;
|
|
426
|
+
const targets = VARIABLE_TARGETS.get(name).targets;
|
|
427
|
+
for (let i = 0; i < targets.length; i += 1) {
|
|
428
|
+
declarations[targets[i]] = value;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
accepted,
|
|
433
|
+
declarations,
|
|
434
|
+
rejections
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function normalizeForcedTheme(raw) {
|
|
438
|
+
if (raw === "light" || raw === "dark") return raw;
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
1
442
|
// src/loader.ts
|
|
2
|
-
var LOADER_VERSION = "
|
|
443
|
+
var LOADER_VERSION = "4";
|
|
3
444
|
(function() {
|
|
4
445
|
"use strict";
|
|
5
446
|
if (typeof window === "undefined") return;
|
|
@@ -60,6 +501,15 @@ var LOADER_VERSION = "3";
|
|
|
60
501
|
function buildCheckoutUrl(internal) {
|
|
61
502
|
const url = new URL("/customer-pay", internal.origin);
|
|
62
503
|
url.searchParams.set("merchant", internal.merchant);
|
|
504
|
+
if (internal.theme !== null) {
|
|
505
|
+
url.searchParams.set(THEME_PARAM, internal.theme);
|
|
506
|
+
}
|
|
507
|
+
for (let i = 0; i < CANOPY_THEME_VARIABLES.length; i += 1) {
|
|
508
|
+
const name = CANOPY_THEME_VARIABLES[i];
|
|
509
|
+
const value = internal.themeVariables[name];
|
|
510
|
+
if (value === void 0) continue;
|
|
511
|
+
url.searchParams.set(THEME_VARIABLE_PARAM_PREFIX + name, value);
|
|
512
|
+
}
|
|
63
513
|
url.hash = "cap=" + encodeURIComponent(internal.capability || "");
|
|
64
514
|
return url.toString();
|
|
65
515
|
}
|
|
@@ -70,6 +520,15 @@ var LOADER_VERSION = "3";
|
|
|
70
520
|
} catch {
|
|
71
521
|
}
|
|
72
522
|
}
|
|
523
|
+
function emitThemeRejections(internal, rejections) {
|
|
524
|
+
for (let i = 0; i < rejections.length; i += 1) {
|
|
525
|
+
const rejection = rejections[i];
|
|
526
|
+
emit(internal, "error", {
|
|
527
|
+
code: rejection.code,
|
|
528
|
+
variable: rejection.key
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
73
532
|
function teardownFrame(internal) {
|
|
74
533
|
if (internal.handshakeTimer) {
|
|
75
534
|
clearTimeout(internal.handshakeTimer);
|
|
@@ -242,6 +701,8 @@ var LOADER_VERSION = "3";
|
|
|
242
701
|
}, destroy: function() {
|
|
243
702
|
} };
|
|
244
703
|
}
|
|
704
|
+
const forcedTheme = normalizeForcedTheme(opts.theme);
|
|
705
|
+
const themeVariables = resolveThemeVariables(opts.variables, forcedTheme);
|
|
245
706
|
const node = typeof opts.target === "string" ? document.querySelector(opts.target) : opts.target;
|
|
246
707
|
if (!node) {
|
|
247
708
|
return { openNow: function() {
|
|
@@ -255,6 +716,8 @@ var LOADER_VERSION = "3";
|
|
|
255
716
|
capability: opts.capability,
|
|
256
717
|
merchant: opts.merchant,
|
|
257
718
|
origin: originResolution.origin,
|
|
719
|
+
theme: forcedTheme,
|
|
720
|
+
themeVariables: themeVariables.accepted,
|
|
258
721
|
surface: opts.surface || "popup",
|
|
259
722
|
handshakeTimeoutMs: opts.handshakeTimeoutMs || DEFAULT_HANDSHAKE_TIMEOUT_MS,
|
|
260
723
|
fallbackLabel: opts.fallbackLabel || DEFAULT_FALLBACK_LABEL,
|
|
@@ -275,6 +738,7 @@ var LOADER_VERSION = "3";
|
|
|
275
738
|
handleMessage(internal, event);
|
|
276
739
|
};
|
|
277
740
|
window.addEventListener("message", internal.messageListener);
|
|
741
|
+
emitThemeRejections(internal, themeVariables.rejections);
|
|
278
742
|
if (internal.surface === "iframe" || internal.surface === "auto") {
|
|
279
743
|
createFrame(internal);
|
|
280
744
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canopypay/checkout-sdk",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"prepublishOnly": "npm --prefix ../.. run sdk:build && npm run build:npm"
|
|
31
31
|
},
|
|
32
32
|
"canopy": {
|
|
33
|
-
"loaderVersion": "
|
|
33
|
+
"loaderVersion": "4",
|
|
34
34
|
"apiVersion": "2026-09-01"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|