@uwdata/mosaic-inputs 0.0.1
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/README.md +3 -0
- package/dist/mosaic-inputs.js +1299 -0
- package/dist/mosaic-inputs.min.js +1 -0
- package/package.json +32 -0
- package/src/Menu.js +104 -0
- package/src/Search.js +104 -0
- package/src/Slider.js +70 -0
- package/src/Table.js +210 -0
- package/src/index.js +4 -0
- package/src/util/format.js +47 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var $=class{constructor(t){this._filterBy=t}get filterBy(){return this._filterBy}get filterIndexable(){return!0}fields(){return null}fieldStats(){return this}query(){return null}queryPending(){return this}queryResult(){return this}queryError(t){return console.error(t),this}update(){return this}};var y=class{constructor(t,e){t&&(this.table=String(t)),e&&(this.column=e)}get columns(){return this.column?[this.column]:[]}toString(){let{table:t,column:e}=this;if(e){let r=e==="*"?e:`"${e}"`;return(t?`"${t}".`:"")+r}else return`"${t}"`}};function mt(s,t){return s instanceof y&&s.column===t}function h(s){return typeof s=="string"?A(s):s}function M(s){return typeof s=="string"?dt(s):s}function dt(s){return new y(s)}function A(s,t){return arguments.length===1?new y(null,s):new y(s,t)}function V(s){return s=h(s),{expr:s,desc:!0,toString:()=>`${s} DESC NULLS LAST`,get columns(){return s.columns?.()||[]}}}function X(s,t,e){return{expr:s,label:e,toString:()=>`${s}`,get columns(){return t||[]}}}function Q(s,t){return e=>X(s(e),h(e).columns,t)}function d(s){return typeof s=="string"?`"${s}"`:U(s)}function U(s){switch(typeof s){case"boolean":return s?"TRUE":"FALSE";case"string":return`'${s}'`;default:return s==null?"NULL":s instanceof Date?`MAKE_DATE(${s.getUTCFullYear()}, ${s.getUTCMonth()+1}, ${s.getUTCDate()})`:s instanceof RegExp?`'${s.source}'`:String(s)}}var H=class{constructor(t){this.value=t}toString(){return U(this.value)}},L=s=>new H(s);function k(...s){return s.flat().flatMap(t=>t?.columns||[])}var W=class{constructor(t,e){this.op=t,this.a=h(e)}get columns(){return k(this.a)}visit(t){t(this.op,this)}toString(){let{op:t,a:e}=this;return`(${d(e)} ${t})`}};function Z(s){return t=>new W(s,t)}var Tt=Z("NOT"),gt=Z("IS NULL"),At=Z("IS NOT NULL"),z=class{constructor(t,e,r){this.op=t,this.a=h(e),this.b=h(r)}get columns(){return k(this.a,this.b)}visit(t){t(this.op,this)}toString(){let{op:t,a:e,b:r}=this;return`(${d(e)} ${t} ${d(r)})`}};function N(s){return(t,e)=>new z(s,t,e)}var j=N("="),Nt=N("<>"),Ct=N("<"),vt=N(">"),It=N("<="),Lt=N(">="),Rt=N("IS DISTINCT FROM"),Ot=N("IS NOT DISTINCT FROM"),J=class{constructor(t,e,r){this.op=t,this.expr=h(e),this.value=r?.map(h)}get columns(){return k(this.expr,this.value)}visit(t){t(this.op,this)}toString(){let{op:t,expr:e,value:r}=this;if(!r)return"";let[n,i]=r;return`(${d(e)} ${t} ${d(n)} AND ${d(i)})`}};function xt(s){return(t,e)=>new J(s,t,e)}var yt=xt("BETWEEN"),Dt=xt("NOT BETWEEN"),K=class{constructor(t,e){this.op=t,this.value=e.map(h)}get columns(){return k(this.value)}visit(t){t(this.op,this),this.value?.forEach(e=>e.visit(t))}toString(){let{op:t,value:e}=this;return!e||e.length===0?"":e.length===1?d(e[0]):`(${e.map(d).filter(r=>r).join(` ${t} `)})`}};function tt(...s){return new K("OR",s.flat())}var et=class{constructor(t,e){this.func=t,this.args=(e||[]).map(h)}get column(){return this.columns[0]}get columns(){return this.args.flatMap(t=>t.columns||[])}toString(){let{func:t,args:e}=this;return`${t}(${e.map(d).join(", ")})`}};function b(s){return(...t)=>new et(s,t)}var st=b("regexp_matches"),rt=b("contains"),nt=b("prefix"),it=b("suffix"),Mt=b("lower"),Ut=b("upper"),jt=b("length"),kt=b("isnan"),_t=b("isfinite"),Ft=b("isinf");var ot=class{constructor(t,e){this.aggregate=t,this.args=(e||[]).map(h)}get label(){return this.aggregate.toLowerCase()+(this.args.length?` ${this.columns.join(", ")}`:"")}get column(){return this.columns[0]}get columns(){return this.args.flatMap(t=>t.columns||[])}distinct(){return this.isDistinct=!0,this}where(t){return this.filter=t,this}toString(){let{aggregate:t,args:e,isDistinct:r,filter:n}=this,i=e.length===0?"*":e.map(d).join(", "),o=r?"DISTINCT ":"",c=n?` FILTER (WHERE ${d(n)})`:"",u=t==="COUNT"?"::INTEGER":"";return c&&u?`(${t}(${o}${i})${c})${u}`:`${t}(${o}${i})${c}${u}`}};function l(s){return(...t)=>new ot(s,t)}var bt=l("COUNT"),Bt=l("AVG"),Gt=l("AVG"),Pt=l("MAD"),wt=l("MAX"),Et=l("MIN"),Yt=l("SUM"),Vt=l("PRODUCT"),Xt=l("MEDIAN"),Qt=l("QUANTILE"),Ht=l("MODE"),Wt=l("VARIANCE"),zt=l("STDDEV"),Jt=l("SKEWNESS"),Kt=l("KURTOSIS"),Zt=l("ENTROPY"),te=l("VAR_POP"),ee=l("STDDEV_POP"),se=l("CORR"),re=l("COVAR_POP"),ne=l("REGR_INTERCEPT"),ie=l("REGR_SLOPE"),oe=l("REGR_COUNT"),ce=l("REGR_R2"),le=l("REGR_SYY"),ue=l("REGR_SXX"),ae=l("REGR_SXY"),he=l("REGR_AVGX"),fe=l("REGR_AVGY"),pe=l("FIRST"),me=l("LAST"),de=l("ARG_MIN"),ge=l("ARG_MAX"),xe=l("STRING_AGG"),ye=l("ARRAY_AGG");var St=Q(s=>`(1000 * (epoch(${s}) - second(${s})) + millisecond(${s}))::DOUBLE`);var p=class{static select(...t){return new p().select(...t)}static from(...t){return new p().from(...t)}static with(...t){return new p().with(...t)}static union(...t){return new q("UNION",t.flat())}static unionAll(...t){return new q("UNION ALL",t.flat())}static intersect(...t){return new q("INTERSECT",t.flat())}static except(...t){return new q("EXCEPT",t.flat())}constructor(){this.query={with:[],select:[],from:[],where:[],groupby:[],having:[],window:[],qualify:[],orderby:[]}}clone(){let t=new p;return t.query={...this.query},t}with(...t){let{query:e}=this;if(t.length===0)return e.with;{let r=[],n=(i,o)=>{let c=o.clone();c.cteFor=this,r.push({as:i,query:c})};return t.flat().forEach(i=>{if(i!=null)if(i.as&&i.query)n(i.as,i.query);else for(let o in i)n(o,i[o])}),e.with=e.with.concat(r),this}}select(...t){let{query:e}=this;if(t.length===0)return e.select;{let r=[];return t.flat().forEach(n=>{if(n!=null)if(typeof n=="string")r.push({as:n,expr:h(n)});else if(n instanceof y)r.push({as:n.column,expr:n});else if(Array.isArray(n))r.push({as:n[0],expr:n[1]});else for(let i in n)r.push({as:_(i),expr:h(n[i])})}),e.select=e.select.concat(r),this}}$select(...t){return this.query.select=[],this.select(...t)}distinct(t=!0){return this.query.distinct=!!t,this}from(...t){let{query:e}=this;if(t.length===0)return e.from;{let r=[];return t.flat().forEach(n=>{if(n!=null)if(typeof n=="string")r.push({as:n,from:M(n)});else if(n instanceof y)r.push({as:n.table,from:n});else if(F(n)||Object.hasOwn(n,"toString"))r.push({from:n});else if(Array.isArray(n))r.push({as:_(n[0]),from:M(n[1])});else for(let i in n)r.push({as:_(i),from:M(n[i])})}),e.from=e.from.concat(r),this}}$from(...t){return this.query.from=[],this.from(...t)}sample(t){let{query:e}=this;if(arguments.length===0)return e.sample;{let r=t;return typeof t=="number"&&(r=t>0&&t<1?{perc:100*t}:{rows:Math.round(t)}),e.sample=r,this}}where(...t){let{query:e}=this;return t.length===0?e.where:(e.where=e.where.concat(t.flat().filter(r=>r)),this)}$where(...t){return this.query.where=[],this.where(...t)}groupby(...t){let{query:e}=this;return t.length===0?e.groupby:(e.groupby=e.groupby.concat(t.flat().filter(r=>r).map(h)),this)}having(...t){let{query:e}=this;return t.length===0?e.having:(e.having=e.having.concat(t.flat().filter(r=>r)),this)}window(...t){let{query:e}=this;if(t.length===0)return e.window;{let r=[];return t.flat().forEach(n=>{if(n!=null)for(let i in n)r.push({as:_(i),expr:n[i]})}),e.window=e.window.concat(r),this}}qualify(...t){let{query:e}=this;return t.length===0?e.qualify:(e.qualify=e.qualify.concat(t.flat().filter(r=>r)),this)}orderby(...t){let{query:e}=this;return t.length===0?e.orderby:(e.orderby=e.orderby.concat(t.flat().filter(r=>r).map(h)),this)}limit(t){let{query:e}=this;return arguments.length===0?e.limit:(e.limit=Number.isFinite(t)?t:void 0,this)}offset(t){let{query:e}=this;return arguments.length===0?e.offset:(e.offset=Number.isFinite(t)?t:void 0,this)}get subqueries(){let{query:t,cteFor:e}=this,n=(e?.query||t).with?.reduce((o,{as:c,query:u})=>(o[c]=u,o),{}),i=[];return t.from.forEach(({from:o})=>{if(F(o))i.push(o);else if(n[o.table]){let c=n[o.table];i.push(c)}}),i}toString(){let{select:t,distinct:e,from:r,sample:n,where:i,groupby:o,having:c,window:u,qualify:g,orderby:S,limit:v,offset:I,with:O}=this.query,m=[];if(O.length){let a=O.map(({as:f,query:x})=>`"${f}" AS (${x})`);m.push(`WITH ${a.join(", ")}`)}let Y=t.map(({as:a,expr:f})=>mt(f,a)&&!f.table?`${f}`:`${f} AS "${a}"`);if(m.push(`SELECT${e?" DISTINCT":""} ${Y.join(", ")}`),r.length){let a=r.map(({as:f,from:x})=>{let D=F(x)?`(${x})`:`${x}`;return!f||f===x.table?D:`${D} AS "${f}"`});m.push(`FROM ${a.join(", ")}`)}if(n){let{rows:a,perc:f,method:x,seed:D}=n,$t=a?`${a} ROWS`:`${f} PERCENT`,qt=x?` (${x}${D!=null?`, ${D}`:""})`:"";m.push(`USING SAMPLE ${$t}${qt}`)}if(i.length){let a=i.map(String).filter(f=>f).join(" AND ");a&&m.push(`WHERE ${a}`)}if(o.length&&m.push(`GROUP BY ${o.join(", ")}`),c.length){let a=c.map(String).filter(f=>f).join(" AND ");a&&m.push(`HAVING ${a}`)}if(u.length){let a=u.map(({as:f,expr:x})=>`"${f}" AS (${x})`);m.push(`WINDOW ${a.join(", ")}`)}if(g.length){let a=g.map(String).filter(f=>f).join(" AND ");a&&m.push(`QUALIFY ${a}`)}return S.length&&m.push(`ORDER BY ${S.join(", ")}`),Number.isFinite(v)&&m.push(`LIMIT ${v}`),Number.isFinite(I)&&m.push(`OFFSET ${I}`),m.join(" ")}},q=class{constructor(t,e){this.op=t,this.queries=e.map(r=>r.clone()),this.query={orderby:[]}}clone(){let t=new q(this.op,this.queries);return t.query={...this.query},t}orderby(...t){let{query:e}=this;return t.length===0?e.orderby:(e.orderby=e.orderby.concat(t.flat().filter(r=>r).map(h)),this)}limit(t){let{query:e}=this;return arguments.length===0?e.limit:(e.limit=Number.isFinite(t)?t:void 0,this)}offset(t){let{query:e}=this;return arguments.length===0?e.offset:(e.offset=Number.isFinite(t)?t:void 0,this)}get subqueries(){let{queries:t,cteFor:e}=this;return e&&t.forEach(r=>r.cteFor=e),t}toString(){let{op:t,queries:e,query:{orderby:r,limit:n,offset:i}}=this,o=[e.join(` ${t} `)];return r.length&&o.push(`ORDER BY ${r.join(", ")}`),Number.isFinite(n)&&o.push(`LIMIT ${n}`),Number.isFinite(i)&&o.push(`OFFSET ${i}`),o.join(" ")}};function F(s){return s instanceof p||s instanceof q}function _(s){return be(s)?s.slice(1,-1):s}function be(s){return s[0]==='"'&&s[s.length-1]==='"'}function B(s,t){return t?.clients?.has(s)}function R(s){return s instanceof C}var C=class{constructor(t){this._value=t,this._listeners=new Map}get value(){return this._value}update(t,{force:e}={}){let r=this._value!==t;return r&&(this._value=t),(r||e)&&this.emit("value",this.value),this}addEventListener(t,e){let r=this._listeners.get(t)||[];r.indexOf(e)<0&&(r=r.concat(e)),this._listeners.set(t,r)}removeEventListener(t,e){let r=this._listeners.get(t);r?.length&&this._listeners.set(t,r.filter(n=>n!==e))}emit(t,e){this._listeners.get(t)?.forEach(r=>r(e))}};function E(s){return s instanceof w}var w=class extends C{static intersect(){return new w}static crossfilter(){return new w({cross:!0})}static union(){return new w({union:!0})}static single(){return new w({single:!0})}constructor({union:t,cross:e,single:r}={}){super([]),this.active=null,this.union=!!t,this.cross=!!e,this.single=!!r}clone(){let t=new w;return t.active=this.active,t.union=this.union,t.cross=this.cross,t._value=this._value,t}get value(){let{clauses:t}=this;return t[t.length-1]?.value}get clauses(){return super.value}activate(t){this.emit("activate",t)}update(t){let{source:e,predicate:r}=t;this.active=t;let n=this.single?[]:this.clauses.filter(i=>e!==i.source);return r&&n.push(t),super.update(n)}predicate(t){let{active:e,clauses:r,cross:n,union:i}=this;if(n&&B(t,e))return;let o=(n?r.filter(c=>!B(t,c)):r).map(c=>c.predicate);return i&&o.length>1?tt(o):o}};var $e=s=>s&&typeof s=="object"&&!Array.isArray(s),ct=class extends ${constructor({filterBy:t,from:e,column:r,label:n=r,options:i,value:o,as:c}={}){super(t),this.from=e,this.column=r,this.selection=c,this.element=document.createElement("div"),this.element.setAttribute("class","input"),this.element.value=this;let u=document.createElement("label");u.innerText=n||r,this.element.appendChild(u),this.select=document.createElement("select"),i&&(this.data=i.map(g=>$e(g)?g:{value:g}),this.update()),o=o??this.selection?.value??this.data?.[0]?.value,this.select.value=o,this.selection?.value===void 0&&this.publish(o),this.element.appendChild(this.select),this.selection&&(this.select.addEventListener("input",()=>{this.publish(this.select.value||null)}),E(this.selection)||this.selection.addEventListener("value",g=>{g!==this.select.value&&(this.select.value=g)}))}publish(t){let{selection:e,column:r}=this;E(e)?e.update({source:this,schema:{type:"point"},value:t,predicate:t?j(r,L(t)):null}):R(e)&&e.update(t)}fields(){let{from:t,column:e}=this;return t?[A(t,e)]:null}query(t=[]){let{from:e,column:r}=this;return e?p.from(e).select({value:r}).distinct().where(t).orderby(r):null}queryResult(t){return this.data=[{value:"",label:"All"},...t],this}update(){let{data:t,select:e}=this;e.replaceChildren();for(let{value:r,label:n=r}of t){let i=document.createElement("option");i.setAttribute("value",r),i.innerText=n,this.select.appendChild(i)}return this.selection&&(this.select.value=this.selection?.value||""),this}};var qe={contains:rt,prefix:nt,suffix:it,regexp:st},Te=0,lt=class extends ${constructor({filterBy:t,from:e,column:r,label:n,type:i,as:o}={}){if(super(t),this.id="search_"+ ++Te,this.type=i,this.from=e,this.column=r,this.selection=o,this.element=document.createElement("div"),this.element.setAttribute("class","input"),this.element.value=this,n){let c=document.createElement("label");c.setAttribute("for",this.id),c.innerText=n,this.element.appendChild(c)}this.searchbox=document.createElement("input"),this.searchbox.setAttribute("id",this.id),this.searchbox.setAttribute("type","text"),this.searchbox.setAttribute("placeholder","Query"),this.element.appendChild(this.searchbox),this.selection&&(this.searchbox.addEventListener("input",()=>{this.publish(this.searchbox.value||null)}),E(this.selection)||this.selection.addEventListener("value",c=>{c!==this.searchbox.value&&(this.searchbox.value=c)}))}publish(t){let{selection:e,column:r,type:n}=this;E(e)?e.update({source:this,schema:{type:n},value:t,predicate:t?qe[n](r,L(t)):null}):R(e)&&e.update(t)}fields(){let{from:t,column:e}=this;return t?[A(t,e)]:null}query(t=[]){let{from:e,column:r}=this;return e?p.from(e).select({list:r}).distinct().where(t):null}queryResult(t){return this.data=t,this}update(){let t=document.createElement("datalist"),e=`${this.id}_list`;t.setAttribute("id",e);for(let r of this.data){let n=document.createElement("option");n.setAttribute("value",r.list),t.append(n)}return this.datalist&&this.datalist.remove(),this.element.appendChild(this.datalist=t),this.searchbox.setAttribute("list",e),this}};var Ae=0,ut=class{constructor({as:t,min:e,max:r,step:n,column:i,label:o=i,value:c=t?.value}={}){if(this.id="slider_"+ ++Ae,this.column=i||"value",this.selection=t,this.element=document.createElement("div"),this.element.setAttribute("class","input"),this.element.value=this,o){let u=document.createElement("label");u.setAttribute("for",this.id),u.innerText=o,this.element.appendChild(u)}this.slider=document.createElement("input"),this.slider.setAttribute("id",this.id),this.slider.setAttribute("type","range"),e!=null&&this.slider.setAttribute("min",e),r!=null&&this.slider.setAttribute("max",r),n!=null&&this.slider.setAttribute("step",n),c!=null&&(this.slider.setAttribute("value",c),this.selection?.value===void 0&&this.publish(c)),this.element.appendChild(this.slider),this.selection&&(this.slider.addEventListener("input",()=>{this.publish(+this.slider.value)}),E(this.selection)||this.selection.addEventListener("value",u=>{u!==+this.slider.value&&(this.slider.value=u)}))}publish(t){let{selection:e,column:r}=this;E(e)?e.update({source:this,schema:{type:"point"},value:t,predicate:j(r,L(t))}):R(this.selection)&&e.update(t)}};function G(s,t){if(s instanceof Date||(s=new Date(+s)),isNaN(s))return typeof t=="function"?t(s):t;let e=s.getUTCHours(),r=s.getUTCMinutes(),n=s.getUTCSeconds(),i=s.getUTCMilliseconds();return`${Ne(s.getUTCFullYear(),4)}-${T(s.getUTCMonth()+1,2)}-${T(s.getUTCDate(),2)}${e||r||n||i?`T${T(e,2)}:${T(r,2)}${n||i?`:${T(n,2)}${i?`.${T(i,3)}`:""}`:""}Z`:""}`}function Ne(s){return s<0?`-${T(-s,6)}`:s>9999?`+${T(s,6)}`:T(s,4)}function T(s,t){return`${s}`.padStart(t,"0")}var at=ft(s=>{let t=P(s);return e=>e==null?"":typeof e=="number"?t(e):e instanceof Date?ht(e):`${e}`}),P=ft(s=>t=>t===0?"0":t.toLocaleString(s)),br=at(),wr=P();function ht(s){return G(s,"Invalid Date")}function ft(s){let t=ft,e;return(r="en")=>r===t?e:e=s(t=r)}var Ce=-1,pt=class extends ${constructor({filterBy:t,from:e,columns:r=["*"],format:n,rowBatch:i=100,width:o,height:c=500}={}){super(t),this.id=`table-${++Ce}`,this.from=e,this.columns=r,this.format=n,this.offset=0,this.limit=+i,this.request=new C,this.pending=!1,this.sortHeader=null,this.sortColumn=null,this.sortDesc=!1,this.element=document.createElement("div"),this.element.setAttribute("id",this.id),this.element.value=this,o&&(this.element.style.maxWidth=`${o}px`),this.element.style.maxHeight=`${c}px`,this.element.style.overflow="auto";let u=-1;this.element.addEventListener("scroll",g=>{let{pending:S,loaded:v}=this,{scrollHeight:I,scrollTop:O,clientHeight:m}=g.target,Y=O<u;if(u=O,!(Y||S||v)&&I-O<2*m){this.pending=!0,this.offset+=this.limit;let a=this.queryInternal(this.filterBy?.predicate(this));this.request.update(a)}}),this.tbl=document.createElement("table"),this.element.appendChild(this.tbl),this.head=document.createElement("thead"),this.tbl.appendChild(this.head),this.body=document.createElement("tbody"),this.tbl.appendChild(this.body),this.style=document.createElement("style"),this.element.appendChild(this.style)}fields(){let{from:t,columns:e}=this;return e.map(r=>A(t,r))}fieldStats(t){this.stats=t;let e=this.head;e.innerHTML="";let r=document.createElement("tr");for(let{column:n}of t){let i=document.createElement("th");i.addEventListener("click",o=>this.sort(o,n)),i.appendChild(document.createElement("span")),i.appendChild(document.createTextNode(n)),r.appendChild(i)}return e.appendChild(r),this.formats=ve(this.format,t),this.style.innerText=Le(this.id,Ie({},t)),this}query(t){return this.offset=0,this.queryInternal(t)}queryInternal(t=[]){let{from:e,limit:r,offset:n,stats:i,sortColumn:o,sortDesc:c}=this;return p.from(e).select(i.map(u=>u.column)).where(t).orderby(o?c?V(o):o:[]).limit(r).offset(n)}queryResult(t){return this.pending||(this.loaded=!1,this.body.replaceChildren()),this.data=t,this}update(){let{body:t,formats:e,data:r,stats:n,limit:i}=this,o=n.length,c=0;for(let u of r){++c;let g=document.createElement("tr");for(let S=0;S<o;++S){let v=u[n[S].column],I=document.createElement("td");I.innerText=v==null?"":e[S](v),g.appendChild(I)}t.appendChild(g)}return c<i&&(this.loaded=!0),this.pending=!1,this}sort(t,e){e===this.sortColumn?this.sortDesc=!this.sortDesc:(this.sortColumn=e,this.sortDesc=!1);let r=t.currentTarget,n=this.sortHeader;n===r&&t.metaKey?(n.firstChild.textContent="",this.sortHeader=null,this.sortColumn=null):(n&&(n.firstChild.textContent=""),this.sortHeader=r,r.firstChild.textContent=this.sortDesc?"\u25BE":"\u25B4");let i=this.query(this.filterBy?.predicate(this));this.request.update(i)}};function ve(s={},t,e){return t.map(({column:r,type:n})=>{if(r in s)return s[r];switch(n){case"number":return P(e);case"date":return ht;default:return at(e)}})}function Ie(s={},t){return t.map(({column:e,type:r})=>e in s?s[e]:r==="number"?"right":"left")}function Le(s,t){let e=[];return t.forEach((r,n)=>{r!=="left"&&e.push(`#${s} tr>:nth-child(${n+1}) {text-align:${r}}`)}),e.join(" ")}export{ct as Menu,lt as Search,ut as Slider,pt as Table};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uwdata/mosaic-inputs",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Mosaic input components.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"inputs",
|
|
7
|
+
"mosaic"
|
|
8
|
+
],
|
|
9
|
+
"license": "BSD-3-Clause",
|
|
10
|
+
"author": "Jeffrey Heer (http://idl.cs.washington.edu)",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "src/index.js",
|
|
13
|
+
"module": "src/index.js",
|
|
14
|
+
"jsdelivr": "dist/mosaic-inputs.min.js",
|
|
15
|
+
"unpkg": "dist/mosaic-inputs.min.js",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/uwdata/mosaic.git"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"prebuild": "rimraf dist && mkdir dist",
|
|
22
|
+
"build": "node ../../esbuild.js mosaic-inputs",
|
|
23
|
+
"lint": "eslint src test --ext .js",
|
|
24
|
+
"test": "mocha 'test/**/*-test.js'",
|
|
25
|
+
"prepublishOnly": "npm run test && npm run lint && npm run build"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@uwdata/mosaic-core": "^0.0.1",
|
|
29
|
+
"@uwdata/mosaic-sql": "^0.0.1",
|
|
30
|
+
"isoformat": "^0.2.1"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/Menu.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isSelection, isSignal, MosaicClient } from '@uwdata/mosaic-core';
|
|
2
|
+
import { Query, column as columnRef, eq, literal } from '@uwdata/mosaic-sql';
|
|
3
|
+
|
|
4
|
+
const isObject = v => {
|
|
5
|
+
return v && typeof v === 'object' && !Array.isArray(v);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export class Menu extends MosaicClient {
|
|
9
|
+
constructor({
|
|
10
|
+
filterBy,
|
|
11
|
+
from,
|
|
12
|
+
column,
|
|
13
|
+
label = column,
|
|
14
|
+
options,
|
|
15
|
+
value,
|
|
16
|
+
as
|
|
17
|
+
} = {}) {
|
|
18
|
+
super(filterBy);
|
|
19
|
+
this.from = from;
|
|
20
|
+
this.column = column;
|
|
21
|
+
this.selection = as;
|
|
22
|
+
|
|
23
|
+
this.element = document.createElement('div');
|
|
24
|
+
this.element.setAttribute('class', 'input');
|
|
25
|
+
this.element.value = this;
|
|
26
|
+
|
|
27
|
+
const lab = document.createElement('label');
|
|
28
|
+
lab.innerText = label || column;
|
|
29
|
+
this.element.appendChild(lab);
|
|
30
|
+
|
|
31
|
+
this.select = document.createElement('select');
|
|
32
|
+
if (options) {
|
|
33
|
+
this.data = options.map(value => isObject(value) ? value : { value });
|
|
34
|
+
this.update();
|
|
35
|
+
}
|
|
36
|
+
value = value ?? this.selection?.value ?? this.data?.[0]?.value;
|
|
37
|
+
this.select.value = value;
|
|
38
|
+
if (this.selection?.value === undefined) this.publish(value);
|
|
39
|
+
this.element.appendChild(this.select);
|
|
40
|
+
|
|
41
|
+
if (this.selection) {
|
|
42
|
+
this.select.addEventListener('input', () => {
|
|
43
|
+
this.publish(this.select.value || null);
|
|
44
|
+
});
|
|
45
|
+
if (!isSelection(this.selection)) {
|
|
46
|
+
this.selection.addEventListener('value', value => {
|
|
47
|
+
if (value !== this.select.value) {
|
|
48
|
+
this.select.value = value;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
publish(value) {
|
|
56
|
+
const { selection, column } = this;
|
|
57
|
+
if (isSelection(selection)) {
|
|
58
|
+
selection.update({
|
|
59
|
+
source: this,
|
|
60
|
+
schema: { type: 'point' },
|
|
61
|
+
value,
|
|
62
|
+
predicate: value ? eq(column, literal(value)) : null
|
|
63
|
+
});
|
|
64
|
+
} else if (isSignal(selection)) {
|
|
65
|
+
selection.update(value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fields() {
|
|
70
|
+
const { from, column } = this;
|
|
71
|
+
return from ? [ columnRef(from, column) ] : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
query(filter = []) {
|
|
75
|
+
const { from, column } = this;
|
|
76
|
+
if (!from) return null;
|
|
77
|
+
return Query
|
|
78
|
+
.from(from)
|
|
79
|
+
.select({ value: column })
|
|
80
|
+
.distinct()
|
|
81
|
+
.where(filter)
|
|
82
|
+
.orderby(column)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
queryResult(data) {
|
|
86
|
+
this.data = [{ value: '', label: 'All' }, ...data];
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
update() {
|
|
91
|
+
const { data, select } = this;
|
|
92
|
+
select.replaceChildren();
|
|
93
|
+
for (const { value, label = value } of data) {
|
|
94
|
+
const opt = document.createElement('option');
|
|
95
|
+
opt.setAttribute('value', value);
|
|
96
|
+
opt.innerText = label; // TODO: label formatting?
|
|
97
|
+
this.select.appendChild(opt);
|
|
98
|
+
}
|
|
99
|
+
if (this.selection) {
|
|
100
|
+
this.select.value = this.selection?.value || '';
|
|
101
|
+
}
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/Search.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isSelection, isSignal, MosaicClient } from '@uwdata/mosaic-core';
|
|
2
|
+
import {
|
|
3
|
+
Query, column as columnRef, regexp_matches, contains, prefix, suffix, literal
|
|
4
|
+
} from '@uwdata/mosaic-sql';
|
|
5
|
+
|
|
6
|
+
const FUNCTIONS = { contains, prefix, suffix, regexp: regexp_matches };
|
|
7
|
+
let _id = 0;
|
|
8
|
+
|
|
9
|
+
export class Search extends MosaicClient {
|
|
10
|
+
constructor({
|
|
11
|
+
filterBy,
|
|
12
|
+
from,
|
|
13
|
+
column,
|
|
14
|
+
label,
|
|
15
|
+
type,
|
|
16
|
+
as
|
|
17
|
+
} = {}) {
|
|
18
|
+
super(filterBy);
|
|
19
|
+
this.id = 'search_' + (++_id);
|
|
20
|
+
this.type = type;
|
|
21
|
+
this.from = from;
|
|
22
|
+
this.column = column;
|
|
23
|
+
this.selection = as;
|
|
24
|
+
|
|
25
|
+
this.element = document.createElement('div');
|
|
26
|
+
this.element.setAttribute('class', 'input');
|
|
27
|
+
this.element.value = this;
|
|
28
|
+
|
|
29
|
+
if (label) {
|
|
30
|
+
const lab = document.createElement('label');
|
|
31
|
+
lab.setAttribute('for', this.id);
|
|
32
|
+
lab.innerText = label;
|
|
33
|
+
this.element.appendChild(lab);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.searchbox = document.createElement('input');
|
|
37
|
+
this.searchbox.setAttribute('id', this.id);
|
|
38
|
+
this.searchbox.setAttribute('type', 'text');
|
|
39
|
+
this.searchbox.setAttribute('placeholder', 'Query');
|
|
40
|
+
this.element.appendChild(this.searchbox);
|
|
41
|
+
|
|
42
|
+
if (this.selection) {
|
|
43
|
+
this.searchbox.addEventListener('input', () => {
|
|
44
|
+
this.publish(this.searchbox.value || null);
|
|
45
|
+
});
|
|
46
|
+
if (!isSelection(this.selection)) {
|
|
47
|
+
this.selection.addEventListener('value', value => {
|
|
48
|
+
if (value !== this.searchbox.value) {
|
|
49
|
+
this.searchbox.value = value;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
publish(value) {
|
|
57
|
+
const { selection, column, type } = this;
|
|
58
|
+
if (isSelection(selection)) {
|
|
59
|
+
selection.update({
|
|
60
|
+
source: this,
|
|
61
|
+
schema: { type },
|
|
62
|
+
value,
|
|
63
|
+
predicate: value ? FUNCTIONS[type](column, literal(value)) : null
|
|
64
|
+
});
|
|
65
|
+
} else if (isSignal(selection)) {
|
|
66
|
+
selection.update(value);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fields() {
|
|
71
|
+
const { from, column } = this;
|
|
72
|
+
return from ? [ columnRef(from, column) ] : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
query(filter = []) {
|
|
76
|
+
const { from, column } = this;
|
|
77
|
+
if (!from) return null;
|
|
78
|
+
return Query
|
|
79
|
+
.from(from)
|
|
80
|
+
.select({ list: column })
|
|
81
|
+
.distinct()
|
|
82
|
+
.where(filter);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
queryResult(data) {
|
|
86
|
+
this.data = data;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
update() {
|
|
91
|
+
const list = document.createElement('datalist');
|
|
92
|
+
const id = `${this.id}_list`;
|
|
93
|
+
list.setAttribute('id', id);
|
|
94
|
+
for (const d of this.data) {
|
|
95
|
+
const opt = document.createElement('option');
|
|
96
|
+
opt.setAttribute('value', d.list);
|
|
97
|
+
list.append(opt);
|
|
98
|
+
}
|
|
99
|
+
if (this.datalist) this.datalist.remove();
|
|
100
|
+
this.element.appendChild(this.datalist = list);
|
|
101
|
+
this.searchbox.setAttribute('list', id);
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/Slider.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isSelection, isSignal } from '@uwdata/mosaic-core';
|
|
2
|
+
import { eq, literal } from '@uwdata/mosaic-sql';
|
|
3
|
+
|
|
4
|
+
let _id = 0;
|
|
5
|
+
|
|
6
|
+
export class Slider {
|
|
7
|
+
constructor({
|
|
8
|
+
as,
|
|
9
|
+
min,
|
|
10
|
+
max,
|
|
11
|
+
step,
|
|
12
|
+
column,
|
|
13
|
+
label = column,
|
|
14
|
+
value = as?.value
|
|
15
|
+
} = {}) {
|
|
16
|
+
this.id = 'slider_' + (++_id);
|
|
17
|
+
this.column = column || 'value';
|
|
18
|
+
this.selection = as;
|
|
19
|
+
|
|
20
|
+
this.element = document.createElement('div');
|
|
21
|
+
this.element.setAttribute('class', 'input');
|
|
22
|
+
this.element.value = this;
|
|
23
|
+
|
|
24
|
+
if (label) {
|
|
25
|
+
const lab = document.createElement('label');
|
|
26
|
+
lab.setAttribute('for', this.id);
|
|
27
|
+
lab.innerText = label;
|
|
28
|
+
this.element.appendChild(lab);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.slider = document.createElement('input');
|
|
32
|
+
this.slider.setAttribute('id', this.id);
|
|
33
|
+
this.slider.setAttribute('type', 'range');
|
|
34
|
+
if (min != null) this.slider.setAttribute('min', min);
|
|
35
|
+
if (max != null) this.slider.setAttribute('max', max);
|
|
36
|
+
if (step != null) this.slider.setAttribute('step', step);
|
|
37
|
+
if (value != null) {
|
|
38
|
+
this.slider.setAttribute('value', value);
|
|
39
|
+
if (this.selection?.value === undefined) this.publish(value);
|
|
40
|
+
}
|
|
41
|
+
this.element.appendChild(this.slider);
|
|
42
|
+
|
|
43
|
+
if (this.selection) {
|
|
44
|
+
this.slider.addEventListener('input', () => {
|
|
45
|
+
this.publish(+this.slider.value);
|
|
46
|
+
});
|
|
47
|
+
if (!isSelection(this.selection)) {
|
|
48
|
+
this.selection.addEventListener('value', value => {
|
|
49
|
+
if (value !== +this.slider.value) {
|
|
50
|
+
this.slider.value = value;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
publish(value) {
|
|
58
|
+
const { selection, column } = this;
|
|
59
|
+
if (isSelection(selection)) {
|
|
60
|
+
selection.update({
|
|
61
|
+
source: this,
|
|
62
|
+
schema: { type: 'point' },
|
|
63
|
+
value,
|
|
64
|
+
predicate: eq(column, literal(value))
|
|
65
|
+
});
|
|
66
|
+
} else if (isSignal(this.selection)) {
|
|
67
|
+
selection.update(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/Table.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { MosaicClient, Signal } from '@uwdata/mosaic-core';
|
|
2
|
+
import { Query, column, desc } from '@uwdata/mosaic-sql';
|
|
3
|
+
import { formatDate, formatLocaleAuto, formatLocaleNumber } from './util/format.js';
|
|
4
|
+
|
|
5
|
+
let _id = -1;
|
|
6
|
+
|
|
7
|
+
export class Table extends MosaicClient {
|
|
8
|
+
constructor({
|
|
9
|
+
filterBy,
|
|
10
|
+
from,
|
|
11
|
+
columns = ['*'],
|
|
12
|
+
format,
|
|
13
|
+
rowBatch = 100,
|
|
14
|
+
width,
|
|
15
|
+
height = 500
|
|
16
|
+
} = {}) {
|
|
17
|
+
super(filterBy);
|
|
18
|
+
this.id = `table-${++_id}`;
|
|
19
|
+
this.from = from;
|
|
20
|
+
this.columns = columns;
|
|
21
|
+
this.format = format;
|
|
22
|
+
this.offset = 0;
|
|
23
|
+
this.limit = +rowBatch;
|
|
24
|
+
this.request = new Signal();
|
|
25
|
+
this.pending = false;
|
|
26
|
+
|
|
27
|
+
this.sortHeader = null;
|
|
28
|
+
this.sortColumn = null;
|
|
29
|
+
this.sortDesc = false;
|
|
30
|
+
|
|
31
|
+
this.element = document.createElement('div');
|
|
32
|
+
this.element.setAttribute('id', this.id);
|
|
33
|
+
this.element.value = this;
|
|
34
|
+
if (width) {
|
|
35
|
+
this.element.style.maxWidth = `${width}px`;
|
|
36
|
+
}
|
|
37
|
+
this.element.style.maxHeight = `${height}px`;
|
|
38
|
+
this.element.style.overflow = 'auto';
|
|
39
|
+
|
|
40
|
+
let prevScrollTop = -1;
|
|
41
|
+
this.element.addEventListener('scroll', evt => {
|
|
42
|
+
const { pending, loaded } = this;
|
|
43
|
+
const { scrollHeight, scrollTop, clientHeight } = evt.target;
|
|
44
|
+
|
|
45
|
+
const back = scrollTop < prevScrollTop;
|
|
46
|
+
prevScrollTop = scrollTop;
|
|
47
|
+
if (back || pending || loaded) return;
|
|
48
|
+
|
|
49
|
+
if (scrollHeight - scrollTop < 2 * clientHeight) {
|
|
50
|
+
this.pending = true;
|
|
51
|
+
this.offset += this.limit;
|
|
52
|
+
const query = this.queryInternal(this.filterBy?.predicate(this));
|
|
53
|
+
this.request.update(query);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.tbl = document.createElement('table');
|
|
58
|
+
this.element.appendChild(this.tbl);
|
|
59
|
+
|
|
60
|
+
this.head = document.createElement('thead');
|
|
61
|
+
this.tbl.appendChild(this.head);
|
|
62
|
+
|
|
63
|
+
this.body = document.createElement('tbody');
|
|
64
|
+
this.tbl.appendChild(this.body);
|
|
65
|
+
|
|
66
|
+
this.style = document.createElement('style');
|
|
67
|
+
this.element.appendChild(this.style);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fields() {
|
|
71
|
+
const { from, columns } = this;
|
|
72
|
+
return columns.map(name => column(from, name));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fieldStats(stats) {
|
|
76
|
+
this.stats = stats;
|
|
77
|
+
|
|
78
|
+
const thead = this.head;
|
|
79
|
+
thead.innerHTML = '';
|
|
80
|
+
const tr = document.createElement('tr');
|
|
81
|
+
for (const { column } of stats) {
|
|
82
|
+
const th = document.createElement('th');
|
|
83
|
+
th.addEventListener('click', evt => this.sort(evt, column));
|
|
84
|
+
th.appendChild(document.createElement('span'));
|
|
85
|
+
th.appendChild(document.createTextNode(column));
|
|
86
|
+
tr.appendChild(th);
|
|
87
|
+
}
|
|
88
|
+
thead.appendChild(tr);
|
|
89
|
+
|
|
90
|
+
// get column formatters
|
|
91
|
+
this.formats = formatof(this.format, stats);
|
|
92
|
+
|
|
93
|
+
// get column alignment style
|
|
94
|
+
this.style.innerText = tableCSS(this.id, alignof({}, stats));
|
|
95
|
+
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
query(filter) {
|
|
100
|
+
this.offset = 0;
|
|
101
|
+
return this.queryInternal(filter);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
queryInternal(filter = []) {
|
|
105
|
+
const { from, limit, offset, stats, sortColumn, sortDesc } = this;
|
|
106
|
+
return Query.from(from)
|
|
107
|
+
.select(stats.map(s => s.column))
|
|
108
|
+
.where(filter)
|
|
109
|
+
.orderby(sortColumn ? (sortDesc ? desc(sortColumn) : sortColumn) : [])
|
|
110
|
+
.limit(limit)
|
|
111
|
+
.offset(offset);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
queryResult(data) {
|
|
115
|
+
if (!this.pending) {
|
|
116
|
+
// data is not from an internal request, so reset table
|
|
117
|
+
this.loaded = false;
|
|
118
|
+
this.body.replaceChildren();
|
|
119
|
+
}
|
|
120
|
+
this.data = data;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
update() {
|
|
125
|
+
const { body, formats, data, stats, limit } = this;
|
|
126
|
+
const nf = stats.length;
|
|
127
|
+
|
|
128
|
+
let count = 0;
|
|
129
|
+
for (const row of data) {
|
|
130
|
+
++count;
|
|
131
|
+
const tr = document.createElement('tr');
|
|
132
|
+
for (let i = 0; i < nf; ++i) {
|
|
133
|
+
const value = row[stats[i].column];
|
|
134
|
+
const td = document.createElement('td');
|
|
135
|
+
td.innerText = value == null ? '' : formats[i](value);
|
|
136
|
+
tr.appendChild(td);
|
|
137
|
+
}
|
|
138
|
+
body.appendChild(tr);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (count < limit) {
|
|
142
|
+
// data table has been fully loaded
|
|
143
|
+
this.loaded = true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.pending = false;
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
sort(event, column) {
|
|
151
|
+
if (column === this.sortColumn) {
|
|
152
|
+
this.sortDesc = !this.sortDesc;
|
|
153
|
+
} else {
|
|
154
|
+
this.sortColumn = column;
|
|
155
|
+
this.sortDesc = false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const th = event.currentTarget;
|
|
159
|
+
const currentHeader = this.sortHeader;
|
|
160
|
+
if (currentHeader === th && event.metaKey) {
|
|
161
|
+
currentHeader.firstChild.textContent = '';
|
|
162
|
+
this.sortHeader = null;
|
|
163
|
+
this.sortColumn = null;
|
|
164
|
+
} else {
|
|
165
|
+
if (currentHeader) currentHeader.firstChild.textContent = '';
|
|
166
|
+
this.sortHeader = th;
|
|
167
|
+
th.firstChild.textContent = this.sortDesc ? "▾" : "▴";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// issue query for sorted data
|
|
171
|
+
const query = this.query(this.filterBy?.predicate(this));
|
|
172
|
+
this.request.update(query);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function formatof(base = {}, stats, locale) {
|
|
177
|
+
return stats.map(({ column, type }) => {
|
|
178
|
+
if (column in base) {
|
|
179
|
+
return base[column];
|
|
180
|
+
} else {
|
|
181
|
+
switch (type) {
|
|
182
|
+
case 'number': return formatLocaleNumber(locale);
|
|
183
|
+
case 'date': return formatDate;
|
|
184
|
+
default: return formatLocaleAuto(locale);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function alignof(base = {}, stats) {
|
|
191
|
+
return stats.map(({ column, type }) => {
|
|
192
|
+
if (column in base) {
|
|
193
|
+
return base[column];
|
|
194
|
+
} else if (type === 'number') {
|
|
195
|
+
return 'right';
|
|
196
|
+
} else {
|
|
197
|
+
return 'left';
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function tableCSS(id, align) {
|
|
203
|
+
const styles = [];
|
|
204
|
+
align.forEach((a, i) => {
|
|
205
|
+
if (a !== 'left') {
|
|
206
|
+
styles.push(`#${id} tr>:nth-child(${i+1}) {text-align:${a}}`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
return styles.join(' ');
|
|
210
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { format as isoformat } from 'isoformat';
|
|
2
|
+
|
|
3
|
+
// Note: use formatAuto (or any other localized format) to present values to the
|
|
4
|
+
// user; stringify is only intended for machine values.
|
|
5
|
+
export function stringify(x) {
|
|
6
|
+
return x == null ? '' : `${x}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const formatLocaleAuto = localize(locale => {
|
|
10
|
+
const formatNumber = formatLocaleNumber(locale);
|
|
11
|
+
return value => value == null ? ''
|
|
12
|
+
: typeof value === 'number' ? formatNumber(value)
|
|
13
|
+
: value instanceof Date ? formatDate(value)
|
|
14
|
+
: `${value}`;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const formatLocaleNumber = localize(locale => {
|
|
18
|
+
return value => value === 0 ? '0' : value.toLocaleString(locale); // handle negative zero
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const formatAuto = formatLocaleAuto();
|
|
22
|
+
|
|
23
|
+
export const formatNumber = formatLocaleNumber();
|
|
24
|
+
|
|
25
|
+
export function formatTrim(value) {
|
|
26
|
+
const s = value.toString();
|
|
27
|
+
const n = s.length;
|
|
28
|
+
let i0 = -1, i1;
|
|
29
|
+
out: for (let i = 1; i < n; ++i) {
|
|
30
|
+
switch (s[i]) {
|
|
31
|
+
case '.': i0 = i1 = i; break;
|
|
32
|
+
case '0': if (i0 === 0) i0 = i; i1 = i; break;
|
|
33
|
+
default: if (!+s[i]) break out; if (i0 > 0) i0 = 0; break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function formatDate(date) {
|
|
40
|
+
return isoformat(date, 'Invalid Date');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Memoize the last-returned locale.
|
|
44
|
+
export function localize(f) {
|
|
45
|
+
let key = localize, value;
|
|
46
|
+
return (locale = 'en') => locale === key ? value : (value = f(key = locale));
|
|
47
|
+
}
|