@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.
@@ -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,4 @@
1
+ export { Menu } from './Menu.js';
2
+ export { Search } from './Search.js';
3
+ export { Slider } from './Slider.js';
4
+ export { Table } from './Table.js';
@@ -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
+ }