From 886e8b98cbd6ab33849b6b028325c61ddb349f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Sko=C4=8D=C3=ADk?= Date: Tue, 11 Jul 2023 14:01:46 +0200 Subject: [PATCH] move to svelte-kit --- .gitignore | 19 +- dist/svelecte-element.js | 1 - dist/svelecte.css | 3 - dist/svelecte.js | 1 - dist/svelecte.mjs | 1 - index.js | 5 - jsconfig.json | 11 + package.json | 99 +- pnpm-lock.yaml | 1630 +++++++++++++++++++++ src/app.d.ts | 12 + src/app.html | 13 + src/{ => lib}/Svelecte.svelte | 1626 ++++++++++---------- src/{ => lib}/actions.js | 70 +- component.js => src/lib/component.js | 2 +- src/{ => lib}/components/Control.svelte | 461 +++--- src/{ => lib}/components/Dropdown.svelte | 682 ++++----- src/{ => lib}/components/Input.svelte | 204 +-- src/{ => lib}/components/Item.svelte | 85 +- src/{ => lib}/components/ItemClose.svelte | 48 +- src/lib/index.js | 5 + item.js => src/lib/item.js | 8 +- src/lib/{ => lib}/custom-element.js | 0 src/lib/{ => lib}/list.js | 346 ++--- src/lib/{ => lib}/sifter.js | 964 ++++++------ src/lib/{ => lib}/utils.js | 388 ++--- src/{ => lib}/settings.js | 122 +- src/routes/+page.svelte | 20 + static/favicon.png | Bin 0 -> 1571 bytes static/normalize.css | 349 +++++ svelte.config.js | 13 + vite.config.js | 9 + 31 files changed, 4642 insertions(+), 2555 deletions(-) delete mode 100644 dist/svelecte-element.js delete mode 100644 dist/svelecte.css delete mode 100644 dist/svelecte.js delete mode 100644 dist/svelecte.mjs delete mode 100644 index.js create mode 100644 jsconfig.json create mode 100644 pnpm-lock.yaml create mode 100644 src/app.d.ts create mode 100644 src/app.html rename src/{ => lib}/Svelecte.svelte (97%) rename src/{ => lib}/actions.js (96%) rename component.js => src/lib/component.js (93%) rename src/{ => lib}/components/Control.svelte (96%) rename src/{ => lib}/components/Dropdown.svelte (96%) rename src/{ => lib}/components/Input.svelte (96%) rename src/{ => lib}/components/Item.svelte (94%) rename src/{ => lib}/components/ItemClose.svelte (97%) create mode 100644 src/lib/index.js rename item.js => src/lib/item.js (53%) rename src/lib/{ => lib}/custom-element.js (100%) rename src/lib/{ => lib}/list.js (96%) rename src/lib/{ => lib}/sifter.js (96%) rename src/lib/{ => lib}/utils.js (96%) rename src/{ => lib}/settings.js (96%) create mode 100644 src/routes/+page.svelte create mode 100644 static/favicon.png create mode 100644 static/normalize.css create mode 100644 svelte.config.js create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore index 9acbfda..0555656 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,15 @@ -/node_modules/ -/public/build/ -package-lock.json - .DS_Store -*.todo +node_modules +/build +/dist +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +/_dist +/_package.json +/.npmrc \ No newline at end of file diff --git a/dist/svelecte-element.js b/dist/svelecte-element.js deleted file mode 100644 index 82189d9..0000000 --- a/dist/svelecte-element.js +++ /dev/null @@ -1 +0,0 @@ -var Svelecte=function(e){"use strict";function t(){}const n=e=>e;function i(e){return e()}function l(){return Object.create(null)}function r(e){e.forEach(i)}function o(e){return"function"==typeof e}function s(e,t){return e!=e?t==t:e!==t||e&&"object"==typeof e||"function"==typeof e}function a(e,...n){if(null==e)return t;const i=e.subscribe(...n);return i.unsubscribe?()=>i.unsubscribe():i}function c(e,t,n){e.$$.on_destroy.push(a(t,n))}function d(e,t,n,i){if(e){const l=u(e,t,n,i);return e[0](l)}}function u(e,t,n,i){return e[1]&&i?function(e,t){for(const n in t)e[n]=t[n];return e}(n.ctx.slice(),e[1](i(t))):n.ctx}function h(e,t,n,i){if(e[2]&&i){const l=e[2](i(n));if(void 0===t.dirty)return l;if("object"==typeof l){const e=[],n=Math.max(t.dirty.length,l.length);for(let i=0;i32){const t=[],n=e.ctx.length/32;for(let e=0;ewindow.performance.now():()=>Date.now(),y=b?e=>requestAnimationFrame(e):t;const x=new Set;function w(e){x.forEach((t=>{t.c(e)||(x.delete(t),t.f())})),0!==x.size&&y(w)}function S(e,t){e.appendChild(t)}function A(e,t,n){const i=I(e);if(!i.getElementById(t)){const e=E("style");e.id=t,e.textContent=n,k(i,e)}}function I(e){if(!e)return document;const t=e.getRootNode?e.getRootNode():e.ownerDocument;return t&&t.host?t:e.ownerDocument}function z(e){const t=E("style");return k(I(e),t),t.sheet}function k(e,t){return S(e.head||e,t),t.sheet}function C(e,t,n){e.insertBefore(t,n||null)}function O(e){e.parentNode.removeChild(e)}function F(e,t){for(let n=0;ne.removeEventListener(t,n,i)}function M(e){return function(t){return t.preventDefault(),e.call(this,t)}}function L(e,t,n){null==n?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function H(e,t){t=""+t,e.wholeText!==t&&(e.data=t)}function P(e,t){e.value=null==t?"":t}let V;function R(){if(void 0===V){V=!1;try{"undefined"!=typeof window&&window.parent&&window.parent.document}catch(e){V=!0}}return V}function _(e,t,n){e.classList[n?"add":"remove"](t)}class B{constructor(e=!1){this.is_svg=!1,this.is_svg=e,this.e=this.n=null}c(e){this.h(e)}m(e,t,n=null){var i;this.e||(this.is_svg?this.e=(i=t.nodeName,document.createElementNS("http://www.w3.org/2000/svg",i)):this.e=E(t.nodeName),this.t=t,this.c(e)),this.i(n)}h(e){this.e.innerHTML=e,this.n=Array.from(this.e.childNodes)}i(e){for(let t=0;t>>0}(d)}_${s}`,h=I(e),{stylesheet:p,rules:f}=N.get(h)||function(e,t){const n={stylesheet:z(t),rules:{}};return N.set(e,n),n}(h,e);f[u]||(f[u]=!0,p.insertRule(`@keyframes ${u} ${d}`,p.cssRules.length));const m=e.style.animation||"";return e.style.animation=`${m?`${m}, `:""}${u} ${i}ms linear ${l}ms 1 both`,K+=1,u}function Q(e,t){const n=(e.style.animation||"").split(", "),i=n.filter(t?e=>e.indexOf(t)<0:e=>-1===e.indexOf("__svelte")),l=n.length-i.length;l&&(e.style.animation=i.join(", "),K-=l,K||y((()=>{K||(N.forEach((e=>{const{ownerNode:t}=e.stylesheet;t&&O(t)})),N.clear())})))}function U(e,i,l,r){if(!i)return t;const o=e.getBoundingClientRect();if(i.left===o.left&&i.right===o.right&&i.top===o.top&&i.bottom===o.bottom)return t;const{delay:s=0,duration:a=300,easing:c=n,start:d=$()+s,end:u=d+a,tick:h=t,css:p}=l(e,{from:i,to:o},r);let f,m=!0,g=!1;function v(){p&&Q(e,f),m=!1}return function(e){let t;0===x.size&&y(w),new Promise((n=>{x.add(t={c:e,f:n})}))}((e=>{if(!g&&e>=d&&(g=!0),g&&e>=u&&(h(1,0),v()),!m)return!1;if(g){const t=0+1*c((e-d)/a);h(t,1-t)}return!0})),p&&(f=W(e,0,1,a,s,c,p)),s||(g=!0),h(0,1),v}function J(e){const t=getComputedStyle(e);if("absolute"!==t.position&&"fixed"!==t.position){const{width:n,height:i}=t,l=e.getBoundingClientRect();e.style.position="absolute",e.style.width=n,e.style.height=i,function(e,t){const n=e.getBoundingClientRect();if(t.left!==n.left||t.top!==n.top){const i=getComputedStyle(e),l="none"===i.transform?"":i.transform;e.style.transform=`${l} translate(${t.left-n.left}px, ${t.top-n.top}px)`}}(e,l)}}function Z(e){G=e}function X(){if(!G)throw new Error("Function called outside component initialization");return G}function Y(e){X().$$.on_mount.push(e)}function ee(e){X().$$.on_destroy.push(e)}function te(){const e=X();return(t,n,{cancelable:i=!1}={})=>{const l=e.$$.callbacks[t];if(l){const r=function(e,t,{bubbles:n=!1,cancelable:i=!1}={}){const l=document.createEvent("CustomEvent");return l.initCustomEvent(e,n,i,t),l}(t,n,{cancelable:i});return l.slice().forEach((t=>{t.call(e,r)})),!r.defaultPrevented}return!0}}function ne(e,t){const n=e.$$.callbacks[t.type];n&&n.slice().forEach((e=>e.call(this,t)))}const ie=[],le=[],re=[],oe=[],se=Promise.resolve();let ae=!1;function ce(){ae||(ae=!0,se.then(fe))}function de(){return ce(),se}function ue(e){re.push(e)}const he=new Set;let pe=0;function fe(){const e=G;do{for(;pe{ge.delete(e),i&&(n&&e.d(1),i())})),e.o(t)}else i&&i()}function we(e,t){xe(e,1,1,(()=>{t.delete(e.key)}))}function Se(e,t){e.f(),we(e,t)}function Ae(e,t,n,i,l,r,o,s,a,c,d,u){let h=e.length,p=r.length,f=h;const m={};for(;f--;)m[e[f].key]=f;const g=[],v=new Map,b=new Map;for(f=p;f--;){const e=u(l,r,f),s=n(e);let a=o.get(s);a?i&&a.p(e,t):(a=c(s,e),a.c()),v.set(s,g[f]=a),s in m&&b.set(s,Math.abs(f-m[s]))}const $=new Set,y=new Set;function x(e){ye(e,1),e.m(s,d),o.set(e.key,e),d=e.first,p--}for(;h&&p;){const t=g[p-1],n=e[h-1],i=t.key,l=n.key;t===n?(d=t.first,h--,p--):v.has(l)?!o.has(i)||$.has(i)?x(t):y.has(l)?h--:b.get(i)>b.get(l)?(y.add(i),x(t)):($.add(l),h--):(a(n,o),h--)}for(;h--;){const t=e[h];v.has(t.key)||a(t,o)}for(;p;)x(g[p-1]);return g}function Ie(e){e&&e.c()}function ze(e,t,n,l){const{fragment:s,on_mount:a,on_destroy:c,after_update:d}=e.$$;s&&s.m(t,n),l||ue((()=>{const t=a.map(i).filter(o);c?c.push(...t):r(t),e.$$.on_mount=[]})),d.forEach(ue)}function ke(e,t){const n=e.$$;null!==n.fragment&&(r(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function Ce(e,n,i,o,s,a,c,d=[-1]){const u=G;Z(e);const h=e.$$={fragment:null,ctx:null,props:a,update:t,not_equal:s,bound:l(),on_mount:[],on_destroy:[],on_disconnect:[],before_update:[],after_update:[],context:new Map(n.context||(u?u.$$.context:[])),callbacks:l(),dirty:d,skip_bound:!1,root:n.target||u.$$.root};c&&c(h.root);let p=!1;if(h.ctx=i?i(e,n.props||{},((t,n,...i)=>{const l=i.length?i[0]:n;return h.ctx&&s(h.ctx[t],h.ctx[t]=l)&&(!h.skip_bound&&h.bound[t]&&h.bound[t](l),p&&function(e,t){-1===e.$$.dirty[0]&&(ie.push(e),ce(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const e=n.indexOf(t);-1!==e&&n.splice(e,1)}}$set(e){var t;this.$$set&&(t=e,0!==Object.keys(t).length)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}}var Fe=function(e,t){this.items=e,this.settings=t||{diacritics:!0}};Fe.prototype.tokenize=function(e,t){if(!(e=De(String(e||"").toLowerCase()))||!e.length)return[];var n,i,l,r,o=[],s=e.split(/ +/);for(n=0,i=s.length;n0)&&i.items.push({score:n,id:l})})):o.iterator(o.items,(function(e,t){i.items.push({score:1,id:t})})),(l=o.getSortFunction(i,t))&&i.items.sort(l),i.total=i.items.length,"number"==typeof t.limit&&(i.items=i.items.slice(0,t.limit)),i};var Ee=function(e,t){return"number"==typeof e&&"number"==typeof t?e>t?1:e(t=Le(String(t||"")))?1:t>e?-1:0},Te=function(e,t){var n,i,l,r;for(n=1,i=arguments.length;n'+r+"";He||(He=document.createElement("div"),He.className="sv-item-content"),He.innerHTML=r;return Le(n).split(" ").filter((e=>e)).forEach((e=>{Re(He,e)})),He.outerHTML}const Re=function(e,t){let n=0;if(3===e.nodeType){const i=Le(e.data);let l=i.indexOf(t);if(l-=i.substr(0,l).toUpperCase().length-i.substr(0,l).length,l>=0){const i=document.createElement("span");i.className="highlight";const r=e.splitText(l);r.splitText(t.length);const o=r.cloneNode(!0);i.appendChild(o),r.parentNode.replaceChild(i,r),n=1}}else if(1===e.nodeType&&e.childNodes&&!/(script|style)/i.test(e.tagName)&&("highlight"!==e.className||"SPAN"!==e.tagName))for(var i=0;io.includes(e))).concat([Object.keys(e)[r]]).shift()}return l}function Be(e,t){return(e||"").replace(/\t/g," ").trim().split(" ").filter((e=>e)).join(" ")}function Ne(e,t,n,i){return{[n]:e,[i]:t+e}}const Ge={disabled:!1,valueField:null,labelField:null,groupLabelField:"label",groupItemsField:"options",disabledField:"$disabled",placeholder:"Select",valueAsObject:!1,searchable:!0,clearable:!1,selectOnTab:!1,resetOnBlur:!0,resetOnSelect:!0,fetchResetOnBlur:!0,multiple:!1,closeAfterSelect:!1,max:0,collapseSelection:!1,creatable:!1,creatablePrefix:"*",keepCreated:!0,allowEditing:!1,delimiter:",",fetchCallback:null,minQuery:1,lazyDropdown:!0,virtualList:!1,vlItemSize:null,vlHeight:null,i18n:{empty:"No options",nomatch:"No matching options",max:e=>`Maximum items ${e} selected`,fetchBefore:"Type to start searching",fetchQuery:(e,t)=>`Type ${e>1&&e>t?`at least ${e-t} characters `:""}to start searching`,fetchInit:"Fetching data, please wait...",fetchEmpty:"No data related to your search",collapsedSelection:e=>`${e} selected`,createRowLabel:e=>`Create '${e}'`},collapseSelectionFn:function(e,t){return this.collapsedSelection(e)}},Ke=[];function We(e,n=t){let i;const l=new Set;function r(t){if(s(e,t)&&(e=t,i)){const t=!Ke.length;for(const t of l)t[1](),Ke.push(t,e);if(t){for(let e=0;e{l.delete(a),0===l.size&&(i(),i=null)}}}}function Qe(e,t,n){if(t)return Array.isArray(e)?e:[e];const i=Array.isArray(e)?e:[e],l=n.labelAsValue?n.labelField:n.valueField;return this.reduce(((e,t,r)=>{if(t[n.optItems]&&t[n.optItems].length){const r=t[n.optItems].reduce(((e,t)=>(i.includes(t[l])&&e.push(t),e)),[]);if(r.length)return e.push(...r),e}return i.includes("object"==typeof t?t[l]:n.labelAsValue?t:r)&&(n.isOptionArray&&(t={[n.valueField]:r,[n.labelField]:t}),e.push(t)),e}),[]).sort(((e,t)=>i.indexOf(e[l])t.isOptionArray?(e.push({[t.valueField]:i,[t.labelField]:n}),e):n[t.optItems]&&n[t.optItems].length?(t.optionsWithGroups=!0,e.push({label:n[t.optLabel],$isGroupHeader:!0}),e.push(...n[t.optItems].map((e=>(e.$isGroupItem=!0,e)))),e):(e.push(n),e)),[]);return function(e,t){t.isOptionArray&&(t.optionProps||(t.optionProps=["value","label"]));e.some((e=>!e.$isGroupHeader&&(t.optionProps=Je(e),!0)))}(n,t),n}function Je(e){e.options&&(e=e.options[0]);const t=["$disabled","$isGroupHeader","$isGroupItem"];return Object.keys(e).filter((e=>!t.includes(e)))}function Ze(e){const t=e-1;return t*t*t+1}function Xe(e,{from:t,to:n},i={}){const l=getComputedStyle(e),r="none"===l.transform?"":l.transform,[s,a]=l.transformOrigin.split(" ").map(parseFloat),c=t.left+t.width*s/n.width-(n.left+s),d=t.top+t.height*a/n.height-(n.top+a),{delay:u=0,duration:h=(e=>120*Math.sqrt(e)),easing:p=Ze}=i;return{delay:u,duration:o(h)?h(Math.sqrt(c*c+d*d)):h,easing:p,css:(e,i)=>{const l=i*c,o=i*d,s=e+i*t.width/n.width,a=e+i*t.height/n.height;return`transform: ${r} translate(${l}px, ${o}px) scale(${s}, ${a});`}}}function Ye(e){A(e,"svelte-x1t6fd",".inputBox.svelte-x1t6fd{box-sizing:content-box;width:19px;background:rgba(0, 0, 0, 0) none repeat scroll 0px center;border:0px none;font-size:inherit;font-family:inherit;opacity:1;outline:currentcolor none 0px;padding:0px;color:inherit;margin:-2px 0 0;height:20px}.inputBox.svelte-x1t6fd::placeholder{color:var(--sv-placeholder-color, #ccccd6)}.inputBox.svelte-x1t6fd:read-only{width:100%}.shadow-text.svelte-x1t6fd{opacity:0;position:absolute;left:100%;z-index:-100;min-width:24px;white-space:nowrap;top:0;left:0}")}function et(e){let n,i,l,o,s,a,c,d;return{c(){n=E("input"),l=q(),o=E("div"),s=T(e[11]),L(n,"type","text"),L(n,"class","inputBox svelte-x1t6fd"),n.disabled=e[2],n.readOnly=i=!e[1],L(n,"id",e[0]),L(n,"style",e[10]),L(n,"placeholder",e[6]),L(n,"enterkeyhint",e[9]),L(o,"class","shadow-text svelte-x1t6fd"),ue((()=>e[28].call(o)))},m(t,i){var r;C(t,n,i),e[26](n),P(n,e[7]),C(t,l,i),C(t,o,i),S(o,s),a=function(e,t){"static"===getComputedStyle(e).position&&(e.style.position="relative");const n=E("iframe");n.setAttribute("style","display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: -1;"),n.setAttribute("aria-hidden","true"),n.tabIndex=-1;const i=R();let l;return i?(n.src="data:text/html, - - - - -
- -
-
- - - - - {#if selectedOptions.length} - - {/if} - -
- - {#if name && !hasAnchor} - - {/if} -
- - + + + + + +
+ +
+
+ + + + + {#if selectedOptions.length} + + {/if} + +
+ + {#if name && !hasAnchor} + + {/if} +
+ + diff --git a/src/actions.js b/src/lib/actions.js similarity index 96% rename from src/actions.js rename to src/lib/actions.js index 633a900..40a06cf 100644 --- a/src/actions.js +++ b/src/lib/actions.js @@ -1,35 +1,35 @@ -const mouseDownAction = e => e.preventDefault(); - -export default function(node, {item, index}) { - - function selectAction(e) { - const eventType = e.target.closest('[data-action="deselect"]') ? 'deselect' : 'select'; - node.dispatchEvent(new CustomEvent(eventType, { - bubble: true, - detail: item - })); - } - - function hoverAction() { - node.dispatchEvent(new CustomEvent('hover', { - detail: index - })); - } - node.onmousedown = mouseDownAction; - node.onclick = selectAction; - // !item.isSelected && - node.addEventListener('mouseenter', hoverAction); - - return { - update(updated) { - item = updated.item; - index = updated.index; - }, - destroy() { - node.removeEventListener('mousedown', mouseDownAction); - node.removeEventListener('click', selectAction); - // !item.isSelected && - node.removeEventListener('mouseenter', hoverAction); - } - } -} +const mouseDownAction = e => e.preventDefault(); + +export default function(node, {item, index}) { + + function selectAction(e) { + const eventType = e.target.closest('[data-action="deselect"]') ? 'deselect' : 'select'; + node.dispatchEvent(new CustomEvent(eventType, { + bubble: true, + detail: item + })); + } + + function hoverAction() { + node.dispatchEvent(new CustomEvent('hover', { + detail: index + })); + } + node.onmousedown = mouseDownAction; + node.onclick = selectAction; + // !item.isSelected && + node.addEventListener('mouseenter', hoverAction); + + return { + update(updated) { + item = updated.item; + index = updated.index; + }, + destroy() { + node.removeEventListener('mousedown', mouseDownAction); + node.removeEventListener('click', selectAction); + // !item.isSelected && + node.removeEventListener('mouseenter', hoverAction); + } + } +} diff --git a/component.js b/src/lib/component.js similarity index 93% rename from component.js rename to src/lib/component.js index 2cf85af..2bac948 100644 --- a/component.js +++ b/src/lib/component.js @@ -1,4 +1,4 @@ -import Svelecte, { addFormatter, config, registerSvelecte } from './index'; +import Svelecte, { addFormatter, config, registerSvelecte } from './index.js'; export { addFormatter, config }; export const registerAsCustomElement = (name) => registerSvelecte(name || 'el-svelecte', Svelecte, config); \ No newline at end of file diff --git a/src/components/Control.svelte b/src/lib/components/Control.svelte similarity index 96% rename from src/components/Control.svelte rename to src/lib/components/Control.svelte index ff03d06..020d1e9 100644 --- a/src/components/Control.svelte +++ b/src/lib/components/Control.svelte @@ -1,231 +1,232 @@ - - -
- - -
- {#if selectedOptions.length } - {#if multiple && collapseSelection && doCollapse} - {@html collapseSelection(selectedOptions.length, selectedOptions) } - {:else} - {#each selectedOptions as opt (opt[currentValueField])} -
- -
- {/each} - {/if} - {/if} - - -
- -
- {#if clearable && !disabled} - - {/if} - {#if clearable} - - {/if} - -
- -
- - \ No newline at end of file diff --git a/src/components/Dropdown.svelte b/src/lib/components/Dropdown.svelte similarity index 96% rename from src/components/Dropdown.svelte rename to src/lib/components/Dropdown.svelte index 3c73f6b..2b74f0c 100644 --- a/src/components/Dropdown.svelte +++ b/src/lib/components/Dropdown.svelte @@ -1,341 +1,343 @@ - - -{#if isMounted && renderDropdown} -
- {#if selection} -
- {#each selection as opt, i (i)} - - {/each} -
- {/if} -
-
- {#if items.length} - {#if virtualList} - -
- -
-
- {:else} - {#each items as opt, i} -
- -
- {/each} - {/if} - {/if} - {#if hasEmptyList || maxReached} -
{listMessage}
- {/if} -
-
- {#if inputValue && creatable && !maxReached} -
-
- {@html createLabel(inputValue)} - {#if currentListLength != dropdownIndex} - {metaKey}+Enter - {/if} -
-
- {/if} -
-{/if} - - \ No newline at end of file diff --git a/src/components/Input.svelte b/src/lib/components/Input.svelte similarity index 96% rename from src/components/Input.svelte rename to src/lib/components/Input.svelte index c06e45e..cee1f17 100644 --- a/src/components/Input.svelte +++ b/src/lib/components/Input.svelte @@ -1,103 +1,103 @@ - - - - -
{shadowText}
- - - \ No newline at end of file diff --git a/src/components/Item.svelte b/src/lib/components/Item.svelte similarity index 94% rename from src/components/Item.svelte rename to src/lib/components/Item.svelte index 5be1fef..eb50544 100644 --- a/src/components/Item.svelte +++ b/src/lib/components/Item.svelte @@ -1,42 +1,43 @@ - - -{#if item.$isGroupHeader} -
{item.label}
-{:else} -
- {@html (isSelected - ? `
${formatter(item, isSelected, inputValue)}
` - : highlightSearch(item, isSelected, inputValue, formatter, disableHighlight)) - } - {#if isSelected && isMultiple}{/if} -
-{/if} - - + + +{#if item.$isGroupHeader} + +
{item.label}
+{:else} +
+ {@html (isSelected + ? `
${formatter(item, isSelected, inputValue)}
` + : highlightSearch(item, isSelected, inputValue, formatter, disableHighlight)) + } + {#if isSelected && isMultiple}{/if} +
+{/if} + + diff --git a/src/components/ItemClose.svelte b/src/lib/components/ItemClose.svelte similarity index 97% rename from src/components/ItemClose.svelte rename to src/lib/components/ItemClose.svelte index b9f307e..db6bc0a 100644 --- a/src/components/ItemClose.svelte +++ b/src/lib/components/ItemClose.svelte @@ -1,25 +1,25 @@ - - - \ No newline at end of file diff --git a/src/lib/index.js b/src/lib/index.js new file mode 100644 index 0000000..1aa5837 --- /dev/null +++ b/src/lib/index.js @@ -0,0 +1,5 @@ +import Svelecte from './Svelecte.svelte'; + +export default Svelecte; +export { addFormatter, config, TAB_SELECT_NAVIGATE } from './Svelecte.svelte'; +export { registerSvelecte } from './lib/custom-element.js'; diff --git a/item.js b/src/lib/item.js similarity index 53% rename from item.js rename to src/lib/item.js index c4bd363..108aef0 100644 --- a/item.js +++ b/src/lib/item.js @@ -1,5 +1,5 @@ -import CloseButton from './src/components/ItemClose.svelte'; -import { highlightSearch } from './src/lib/utils'; -import itemActions from './src/actions'; - +import CloseButton from './src/components/ItemClose.svelte'; +import { highlightSearch } from './src/lib/utils.js'; +import itemActions from './src/actions.js'; + export { CloseButton, itemActions, highlightSearch } \ No newline at end of file diff --git a/src/lib/custom-element.js b/src/lib/lib/custom-element.js similarity index 100% rename from src/lib/custom-element.js rename to src/lib/lib/custom-element.js diff --git a/src/lib/list.js b/src/lib/lib/list.js similarity index 96% rename from src/lib/list.js rename to src/lib/lib/list.js index 910413d..a00ceda 100644 --- a/src/lib/list.js +++ b/src/lib/lib/list.js @@ -1,173 +1,173 @@ -import Sifter from './sifter'; - -export function initSelection(initialValue, valueAsObject, config) { - if (valueAsObject) return Array.isArray(initialValue) ? initialValue : [initialValue]; - - const _initialValue = Array.isArray(initialValue) ? initialValue : [initialValue]; - const valueField = config.labelAsValue ? config.labelField : config.valueField; - - const initialSelection = this/** options */.reduce((res, val, i) => { - if (val[config.optItems] && val[config.optItems].length) { // handle groups - const selected = val[config.optItems].reduce((res, groupVal) => { - if (_initialValue.includes(groupVal[valueField])) res.push(groupVal); - return res; - }, []); - if (selected.length) { - res.push(...selected); - return res; - } - } - if (_initialValue.includes(typeof val === 'object' ? val[valueField] : (config.labelAsValue ? val : i))) { - if (config.isOptionArray) { - // initial options are not transformed, therefore we need to create object from given option - val = { - [config.valueField]: i, - [config.labelField]: val - } - } - res.push(val); - }; - return res; - }, []); - - return initialSelection - .sort((a, b) => _initialValue.indexOf(a[valueField]) < _initialValue.indexOf(b[valueField]) ? -1 : 1) -} - -export function flatList(options, config) { - const flatOpts = options.reduce((res, opt, i) => { - if (config.isOptionArray) { - res.push({ - [config.valueField]: i, - [config.labelField]: opt - }); - return res; - } - if (opt[config.optItems] && opt[config.optItems].length) { - config.optionsWithGroups = true; - res.push({ label: opt[config.optLabel], $isGroupHeader: true }); - res.push(...opt[config.optItems].map(_opt => { - _opt.$isGroupItem = true; - return _opt; - })); - return res; - } - res.push(opt); - return res; - }, []); - updateOptionProps(flatOpts, config); - return flatOpts; -} - -function updateOptionProps(options, config) { - if (config.isOptionArray) { - if (!config.optionProps) { - config.optionProps = ['value', 'label']; - } - } - options.some(opt => { - if (opt.$isGroupHeader) return false; - config.optionProps = getFilterProps(opt); - return true; - }) -} - -export function getFilterProps(object) { - if (object.options) object = object.options[0]; - const exclude = ['$disabled', '$isGroupHeader', '$isGroupItem']; - return Object.keys(object).filter(prop => !exclude.includes(prop)); -} - -export function filterList(options, inputValue, excludeSelected, sifterSearchField, sifterSortField, config) { - if (excludeSelected) { - options = options - .filter(opt => !excludeSelected.has(opt[config.valueField])) - .filter((opt, idx, self) => { - if (opt.$isGroupHeader && - ( - (self[idx + 1] && self[idx + 1].$isGroupHeader) - || self.length <= 1 - || self.length - 1 === idx - ) - ) return false; - return true; - }) - } - if (!inputValue) return options; - - const sifter = new Sifter(options); - /** - * Sifter is used for searching to provide rich filter functionality. - * But it degradate nicely, when optgroups are present - */ - if (config.optionsWithGroups) { // disable sorting - sifter.getSortFunction = () => null; - } - let conjunction = 'and'; - if (inputValue.startsWith('|| ')) { - conjunction = 'or'; - inputValue = inputValue.substr(2); - } - - const result = sifter.search(inputValue, { - fields: sifterSearchField || config.optionProps, - sort: createSifterSortField(sifterSortField || config.labelField), - conjunction: conjunction - }); - - const mapped = config.optionsWithGroups - ? result.items.reduce((res, item) => { - const opt = options[item.id]; - if (excludeSelected && opt.isSelected) return res; - const lastPos = res.push(opt); - if (opt.$isGroupItem) { - const prevItems = options.slice(0, item.id); - let prev = null; - do { - prev = prevItems.pop(); - prev && prev.$isGroupHeader && !res.includes(prev) && res.splice(lastPos - 1, 0, prev); - } while (prev && !prev.$isGroupHeader); - } - return res; - }, []) - : result.items.map(item => options[item.id]) - return mapped; -} - -function createSifterSortField(prop) { - return [{ field: prop, direction: 'asc'}]; -} - -export function indexList(options, includeCreateRow, config) { - const map = config.optionsWithGroups - ? options.reduce((res, opt, index) => { - res.push(opt.$isGroupHeader ? '' : index); - return res; - }, []) - : Object.keys(options); - - return { - map: map, - first: map[0] !== '' ? 0 : 1, - last: map.length ? map.length - (includeCreateRow ? 0 : 1) : 0, - hasCreateRow: !!includeCreateRow, - next(curr, prevOnUndefined) { - const val = this.map[++curr]; - if (this.hasCreateRow && curr === this.last) return this.last; - if (val === '') return this.next(curr); - if (val === undefined) { - if (!this.map.length) return 0; // ref #26 - if (curr > this.map.length) curr = this.first - 1; - return prevOnUndefined === true ? this.prev(curr) : this.next(curr); - } - return val; - }, - prev(curr) { - const val = this.map[--curr]; - if (this.hasCreateRow && curr === this.first) return this.first; - if (val === '') return this.prev(curr); - if (!val) return this.last; - return val; - } - }; -} +import Sifter from './sifter.js'; + +export function initSelection(initialValue, valueAsObject, config) { + if (valueAsObject) return Array.isArray(initialValue) ? initialValue : [initialValue]; + + const _initialValue = Array.isArray(initialValue) ? initialValue : [initialValue]; + const valueField = config.labelAsValue ? config.labelField : config.valueField; + + const initialSelection = this/** options */.reduce((res, val, i) => { + if (val[config.optItems] && val[config.optItems].length) { // handle groups + const selected = val[config.optItems].reduce((res, groupVal) => { + if (_initialValue.includes(groupVal[valueField])) res.push(groupVal); + return res; + }, []); + if (selected.length) { + res.push(...selected); + return res; + } + } + if (_initialValue.includes(typeof val === 'object' ? val[valueField] : (config.labelAsValue ? val : i))) { + if (config.isOptionArray) { + // initial options are not transformed, therefore we need to create object from given option + val = { + [config.valueField]: i, + [config.labelField]: val + } + } + res.push(val); + }; + return res; + }, []); + + return initialSelection + .sort((a, b) => _initialValue.indexOf(a[valueField]) < _initialValue.indexOf(b[valueField]) ? -1 : 1) +} + +export function flatList(options, config) { + const flatOpts = options.reduce((res, opt, i) => { + if (config.isOptionArray) { + res.push({ + [config.valueField]: i, + [config.labelField]: opt + }); + return res; + } + if (opt[config.optItems] && opt[config.optItems].length) { + config.optionsWithGroups = true; + res.push({ label: opt[config.optLabel], $isGroupHeader: true }); + res.push(...opt[config.optItems].map(_opt => { + _opt.$isGroupItem = true; + return _opt; + })); + return res; + } + res.push(opt); + return res; + }, []); + updateOptionProps(flatOpts, config); + return flatOpts; +} + +function updateOptionProps(options, config) { + if (config.isOptionArray) { + if (!config.optionProps) { + config.optionProps = ['value', 'label']; + } + } + options.some(opt => { + if (opt.$isGroupHeader) return false; + config.optionProps = getFilterProps(opt); + return true; + }) +} + +export function getFilterProps(object) { + if (object.options) object = object.options[0]; + const exclude = ['$disabled', '$isGroupHeader', '$isGroupItem']; + return Object.keys(object).filter(prop => !exclude.includes(prop)); +} + +export function filterList(options, inputValue, excludeSelected, sifterSearchField, sifterSortField, config) { + if (excludeSelected) { + options = options + .filter(opt => !excludeSelected.has(opt[config.valueField])) + .filter((opt, idx, self) => { + if (opt.$isGroupHeader && + ( + (self[idx + 1] && self[idx + 1].$isGroupHeader) + || self.length <= 1 + || self.length - 1 === idx + ) + ) return false; + return true; + }) + } + if (!inputValue) return options; + + const sifter = new Sifter(options); + /** + * Sifter is used for searching to provide rich filter functionality. + * But it degradate nicely, when optgroups are present + */ + if (config.optionsWithGroups) { // disable sorting + sifter.getSortFunction = () => null; + } + let conjunction = 'and'; + if (inputValue.startsWith('|| ')) { + conjunction = 'or'; + inputValue = inputValue.substr(2); + } + + const result = sifter.search(inputValue, { + fields: sifterSearchField || config.optionProps, + sort: createSifterSortField(sifterSortField || config.labelField), + conjunction: conjunction + }); + + const mapped = config.optionsWithGroups + ? result.items.reduce((res, item) => { + const opt = options[item.id]; + if (excludeSelected && opt.isSelected) return res; + const lastPos = res.push(opt); + if (opt.$isGroupItem) { + const prevItems = options.slice(0, item.id); + let prev = null; + do { + prev = prevItems.pop(); + prev && prev.$isGroupHeader && !res.includes(prev) && res.splice(lastPos - 1, 0, prev); + } while (prev && !prev.$isGroupHeader); + } + return res; + }, []) + : result.items.map(item => options[item.id]) + return mapped; +} + +function createSifterSortField(prop) { + return [{ field: prop, direction: 'asc'}]; +} + +export function indexList(options, includeCreateRow, config) { + const map = config.optionsWithGroups + ? options.reduce((res, opt, index) => { + res.push(opt.$isGroupHeader ? '' : index); + return res; + }, []) + : Object.keys(options); + + return { + map: map, + first: map[0] !== '' ? 0 : 1, + last: map.length ? map.length - (includeCreateRow ? 0 : 1) : 0, + hasCreateRow: !!includeCreateRow, + next(curr, prevOnUndefined) { + const val = this.map[++curr]; + if (this.hasCreateRow && curr === this.last) return this.last; + if (val === '') return this.next(curr); + if (val === undefined) { + if (!this.map.length) return 0; // ref #26 + if (curr > this.map.length) curr = this.first - 1; + return prevOnUndefined === true ? this.prev(curr) : this.next(curr); + } + return val; + }, + prev(curr) { + const val = this.map[--curr]; + if (this.hasCreateRow && curr === this.first) return this.first; + if (val === '') return this.prev(curr); + if (!val) return this.last; + return val; + } + }; +} diff --git a/src/lib/sifter.js b/src/lib/lib/sifter.js similarity index 96% rename from src/lib/sifter.js rename to src/lib/lib/sifter.js index ef64421..f6c6ef9 100644 --- a/src/lib/sifter.js +++ b/src/lib/lib/sifter.js @@ -1,482 +1,482 @@ -/** - * sifter.js - * Copyright (c) 2013–2020 Brian Reavis & contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this - * file except in compliance with the License. You may obtain a copy of the License at: - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF - * ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - * - * @author Brian Reavis - */ - -/** - * Textually searches arrays and hashes of objects - * by property (or multiple properties). Designed - * specifically for autocomplete. - * - * @constructor - * @param {array|object} items - * @param {object} items - */ -var Sifter = function(items, settings) { - this.items = items; - this.settings = settings || {diacritics: true}; -}; - -/** - * Splits a search string into an array of individual - * regexps to be used to match results. - * - * @param {string} query - * @returns {array} - */ -Sifter.prototype.tokenize = function(query, respect_word_boundaries) { - query = trim(String(query || '').toLowerCase()); - if (!query || !query.length) return []; - - var i, n, regex, letter; - var tokens = []; - var words = query.split(/ +/); - - for (i = 0, n = words.length; i < n; i++) { - regex = escape_regex(words[i]); - if (this.settings.diacritics) { - for (letter in DIACRITICS) { - if (DIACRITICS.hasOwnProperty(letter)) { - regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]); - } - } - } - if (respect_word_boundaries) regex = "\\b"+regex - tokens.push({ - string : words[i], - regex : new RegExp(regex, 'i') - }); - } - - return tokens; -}; - -/** - * Iterates over arrays and hashes. - * - * ``` - * this.iterator(this.items, function(item, id) { - * // invoked for each item - * }); - * ``` - * - * @param {array|object} object - */ -Sifter.prototype.iterator = function(object, callback) { - var iterator; - if (Array.isArray(object)) { - iterator = Array.prototype.forEach || function(callback) { - for (var i = 0, n = this.length; i < n; i++) { - callback(this[i], i, this); - } - }; - } else { - iterator = function(callback) { - for (var key in this) { - if (this.hasOwnProperty(key)) { - callback(this[key], key, this); - } - } - }; - } - - iterator.apply(object, [callback]); -}; - -/** - * Returns a function to be used to score individual results. - * - * Good matches will have a higher score than poor matches. - * If an item is not a match, 0 will be returned by the function. - * - * @param {object|string} search - * @param {object} options (optional) - * @returns {function} - */ -Sifter.prototype.getScoreFunction = function(search, options) { - var self, fields, tokens, token_count, nesting; - - self = this; - search = self.prepareSearch(search, options); - tokens = search.tokens; - fields = search.options.fields; - token_count = tokens.length; - nesting = search.options.nesting; - - /** - * Calculates how close of a match the - * given value is against a search token. - * - * @param {string | number} value - * @param {object} token - * @return {number} - */ - var scoreValue = function(value, token) { - var score, pos; - - if (!value) return 0; - value = String(value || ''); - pos = value.search(token.regex); - if (pos === -1) return 0; - score = token.string.length / value.length; - if (pos === 0) score += 0.5; - return score; - }; - - /** - * Calculates the score of an object - * against the search query. - * - * @param {object} token - * @param {object} data - * @return {number} - */ - var scoreObject = (function() { - var field_count = fields.length; - if (!field_count) { - return function() { return 0; }; - } - if (field_count === 1) { - return function(token, data) { - return scoreValue(getattr(data, fields[0], nesting), token); - }; - } - return function(token, data) { - for (var i = 0, sum = 0; i < field_count; i++) { - sum += scoreValue(getattr(data, fields[i], nesting), token); - } - return sum / field_count; - }; - })(); - - if (!token_count) { - return function() { return 0; }; - } - if (token_count === 1) { - return function(data) { - return scoreObject(tokens[0], data); - }; - } - - if (search.options.conjunction === 'and') { - return function(data) { - var score; - for (var i = 0, sum = 0; i < token_count; i++) { - score = scoreObject(tokens[i], data); - if (score <= 0) return 0; - sum += score; - } - return sum / token_count; - }; - } else { - return function(data) { - for (var i = 0, sum = 0; i < token_count; i++) { - sum += scoreObject(tokens[i], data); - } - return sum / token_count; - }; - } -}; - -/** - * Returns a function that can be used to compare two - * results, for sorting purposes. If no sorting should - * be performed, `null` will be returned. - * - * @param {string|object} search - * @param {object} options - * @return function(a,b) - */ -Sifter.prototype.getSortFunction = function(search, options) { - var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort; - - self = this; - search = self.prepareSearch(search, options); - sort = (!search.query && options.sort_empty) || options.sort; - - /** - * Fetches the specified sort field value - * from a search result item. - * - * @param {string} name - * @param {object} result - */ - get_field = function(name, result) { - if (name === '$score') return result.score; - return getattr(self.items[result.id], name, options.nesting); - }; - - // parse options - fields = []; - if (sort) { - for (i = 0, n = sort.length; i < n; i++) { - if (search.query || sort[i].field !== '$score') { - fields.push(sort[i]); - } - } - } - - // the "$score" field is implied to be the primary - // sort field, unless it's manually specified - if (search.query) { - implicit_score = true; - for (i = 0, n = fields.length; i < n; i++) { - if (fields[i].field === '$score') { - implicit_score = false; - break; - } - } - if (implicit_score) { - fields.unshift({field: '$score', direction: 'desc'}); - } - } else { - for (i = 0, n = fields.length; i < n; i++) { - if (fields[i].field === '$score') { - fields.splice(i, 1); - break; - } - } - } - - multipliers = []; - for (i = 0, n = fields.length; i < n; i++) { - multipliers.push(fields[i].direction === 'desc' ? -1 : 1); - } - - // build function - fields_count = fields.length; - if (!fields_count) { - return null; - } else if (fields_count === 1) { - field = fields[0].field; - multiplier = multipliers[0]; - return function(a, b) { - return multiplier * cmp( - get_field(field, a), - get_field(field, b) - ); - }; - } else { - return function(a, b) { - var i, result, a_value, b_value, field; - for (i = 0; i < fields_count; i++) { - field = fields[i].field; - result = multipliers[i] * cmp( - get_field(field, a), - get_field(field, b) - ); - if (result) return result; - } - return 0; - }; - } -}; - -/** - * Parses a search query and returns an object - * with tokens and fields ready to be populated - * with results. - * - * @param {string} query - * @param {object} options - * @returns {object} - */ -Sifter.prototype.prepareSearch = function(query, options) { - if (typeof query === 'object') return query; - - options = extend({}, options); - - var option_fields = options.fields; - var option_sort = options.sort; - var option_sort_empty = options.sort_empty; - - if (option_fields && !Array.isArray(option_fields)) options.fields = [option_fields]; - if (option_sort && !Array.isArray(option_sort)) options.sort = [option_sort]; - if (option_sort_empty && !Array.isArray(option_sort_empty)) options.sort_empty = [option_sort_empty]; - - return { - options : options, - query : String(query || '').toLowerCase(), - tokens : this.tokenize(query, options.respect_word_boundaries), - total : 0, - items : [] - }; -}; - -/** - * Searches through all items and returns a sorted array of matches. - * - * The `options` parameter can contain: - * - * - fields {string|array} - * - sort {array} - * - score {function} - * - filter {bool} - * - limit {integer} - * - * Returns an object containing: - * - * - options {object} - * - query {string} - * - tokens {array} - * - total {int} - * - items {array} - * - * @param {string} query - * @param {object} options - * @returns {object} - */ -Sifter.prototype.search = function(query, options) { - var self = this, value, score, search, calculateScore; - var fn_sort; - var fn_score; - - search = this.prepareSearch(query, options); - options = search.options; - query = search.query; - - // generate result scoring function - fn_score = options.score || self.getScoreFunction(search); - - // perform search and sort - if (query.length) { - self.iterator(self.items, function(item, id) { - score = fn_score(item); - if (options.filter === false || score > 0) { - search.items.push({'score': score, 'id': id}); - } - }); - } else { - self.iterator(self.items, function(item, id) { - search.items.push({'score': 1, 'id': id}); - }); - } - - fn_sort = self.getSortFunction(search, options); - if (fn_sort) search.items.sort(fn_sort); - - // apply limits - search.total = search.items.length; - if (typeof options.limit === 'number') { - search.items = search.items.slice(0, options.limit); - } - - return search; -}; - -// utilities -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -var cmp = function(a, b) { - if (typeof a === 'number' && typeof b === 'number') { - return a > b ? 1 : (a < b ? -1 : 0); - } - a = asciifold(String(a || '')); - b = asciifold(String(b || '')); - if (a > b) return 1; - if (b > a) return -1; - return 0; -}; - -var extend = function(a, b) { - var i, n, k, object; - for (i = 1, n = arguments.length; i < n; i++) { - object = arguments[i]; - if (!object) continue; - for (k in object) { - if (object.hasOwnProperty(k)) { - a[k] = object[k]; - } - } - } - return a; -}; - -/** - * A property getter resolving dot-notation - * @param {Object} obj The root object to fetch property on - * @param {String} name The optionally dotted property name to fetch - * @param {Boolean} nesting Handle nesting or not - * @return {Object} The resolved property value - */ -var getattr = function(obj, name, nesting) { - if (!obj || !name) return; - if (!nesting) return obj[name]; - var names = name.split("."); - while(names.length && (obj = obj[names.shift()])); - return obj; -}; - -var trim = function(str) { - return (str + '').replace(/^\s+|\s+$|/g, ''); -}; - -var escape_regex = function(str) { - return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); -}; - -var DIACRITICS = { - 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]', - 'b': '[b␢βΒB฿𐌁ᛒ]', - 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]', - 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]', - 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]', - 'f': '[fƑƒḞḟ]', - 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]', - 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]', - 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]', - 'j': '[jȷĴĵɈɉʝɟʲ]', - 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]', - 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]', - 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]', - 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]', - 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]', - 'q': '[qꝖꝗʠɊɋꝘꝙq̃]', - 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]', - 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]', - 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]', - 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]', - 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]', - 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]', - 'x': '[xẌẍẊẋχ]', - 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]', - 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]' -}; - -export const asciifold = (function() { - var i, n, k, chunk; - var foreignletters = ''; - var lookup = {}; - for (k in DIACRITICS) { - if (DIACRITICS.hasOwnProperty(k)) { - chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1); - foreignletters += chunk; - for (i = 0, n = chunk.length; i < n; i++) { - lookup[chunk.charAt(i)] = k; - } - } - } - var regexp = new RegExp('[' + foreignletters + ']', 'g'); - return function(str) { - return str.replace(regexp, function(foreignletter) { - return lookup[foreignletter]; - }).toLowerCase(); - }; -})(); - - -// export -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -export default Sifter; - +/** + * sifter.js + * Copyright (c) 2013–2020 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + +/** + * Textually searches arrays and hashes of objects + * by property (or multiple properties). Designed + * specifically for autocomplete. + * + * @constructor + * @param {array|object} items + * @param {object} items + */ +var Sifter = function(items, settings) { + this.items = items; + this.settings = settings || {diacritics: true}; +}; + +/** + * Splits a search string into an array of individual + * regexps to be used to match results. + * + * @param {string} query + * @returns {array} + */ +Sifter.prototype.tokenize = function(query, respect_word_boundaries) { + query = trim(String(query || '').toLowerCase()); + if (!query || !query.length) return []; + + var i, n, regex, letter; + var tokens = []; + var words = query.split(/ +/); + + for (i = 0, n = words.length; i < n; i++) { + regex = escape_regex(words[i]); + if (this.settings.diacritics) { + for (letter in DIACRITICS) { + if (DIACRITICS.hasOwnProperty(letter)) { + regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]); + } + } + } + if (respect_word_boundaries) regex = "\\b"+regex + tokens.push({ + string : words[i], + regex : new RegExp(regex, 'i') + }); + } + + return tokens; +}; + +/** + * Iterates over arrays and hashes. + * + * ``` + * this.iterator(this.items, function(item, id) { + * // invoked for each item + * }); + * ``` + * + * @param {array|object} object + */ +Sifter.prototype.iterator = function(object, callback) { + var iterator; + if (Array.isArray(object)) { + iterator = Array.prototype.forEach || function(callback) { + for (var i = 0, n = this.length; i < n; i++) { + callback(this[i], i, this); + } + }; + } else { + iterator = function(callback) { + for (var key in this) { + if (this.hasOwnProperty(key)) { + callback(this[key], key, this); + } + } + }; + } + + iterator.apply(object, [callback]); +}; + +/** + * Returns a function to be used to score individual results. + * + * Good matches will have a higher score than poor matches. + * If an item is not a match, 0 will be returned by the function. + * + * @param {object|string} search + * @param {object} options (optional) + * @returns {function} + */ +Sifter.prototype.getScoreFunction = function(search, options) { + var self, fields, tokens, token_count, nesting; + + self = this; + search = self.prepareSearch(search, options); + tokens = search.tokens; + fields = search.options.fields; + token_count = tokens.length; + nesting = search.options.nesting; + + /** + * Calculates how close of a match the + * given value is against a search token. + * + * @param {string | number} value + * @param {object} token + * @return {number} + */ + var scoreValue = function(value, token) { + var score, pos; + + if (!value) return 0; + value = String(value || ''); + pos = value.search(token.regex); + if (pos === -1) return 0; + score = token.string.length / value.length; + if (pos === 0) score += 0.5; + return score; + }; + + /** + * Calculates the score of an object + * against the search query. + * + * @param {object} token + * @param {object} data + * @return {number} + */ + var scoreObject = (function() { + var field_count = fields.length; + if (!field_count) { + return function() { return 0; }; + } + if (field_count === 1) { + return function(token, data) { + return scoreValue(getattr(data, fields[0], nesting), token); + }; + } + return function(token, data) { + for (var i = 0, sum = 0; i < field_count; i++) { + sum += scoreValue(getattr(data, fields[i], nesting), token); + } + return sum / field_count; + }; + })(); + + if (!token_count) { + return function() { return 0; }; + } + if (token_count === 1) { + return function(data) { + return scoreObject(tokens[0], data); + }; + } + + if (search.options.conjunction === 'and') { + return function(data) { + var score; + for (var i = 0, sum = 0; i < token_count; i++) { + score = scoreObject(tokens[i], data); + if (score <= 0) return 0; + sum += score; + } + return sum / token_count; + }; + } else { + return function(data) { + for (var i = 0, sum = 0; i < token_count; i++) { + sum += scoreObject(tokens[i], data); + } + return sum / token_count; + }; + } +}; + +/** + * Returns a function that can be used to compare two + * results, for sorting purposes. If no sorting should + * be performed, `null` will be returned. + * + * @param {string|object} search + * @param {object} options + * @return function(a,b) + */ +Sifter.prototype.getSortFunction = function(search, options) { + var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort; + + self = this; + search = self.prepareSearch(search, options); + sort = (!search.query && options.sort_empty) || options.sort; + + /** + * Fetches the specified sort field value + * from a search result item. + * + * @param {string} name + * @param {object} result + */ + get_field = function(name, result) { + if (name === '$score') return result.score; + return getattr(self.items[result.id], name, options.nesting); + }; + + // parse options + fields = []; + if (sort) { + for (i = 0, n = sort.length; i < n; i++) { + if (search.query || sort[i].field !== '$score') { + fields.push(sort[i]); + } + } + } + + // the "$score" field is implied to be the primary + // sort field, unless it's manually specified + if (search.query) { + implicit_score = true; + for (i = 0, n = fields.length; i < n; i++) { + if (fields[i].field === '$score') { + implicit_score = false; + break; + } + } + if (implicit_score) { + fields.unshift({field: '$score', direction: 'desc'}); + } + } else { + for (i = 0, n = fields.length; i < n; i++) { + if (fields[i].field === '$score') { + fields.splice(i, 1); + break; + } + } + } + + multipliers = []; + for (i = 0, n = fields.length; i < n; i++) { + multipliers.push(fields[i].direction === 'desc' ? -1 : 1); + } + + // build function + fields_count = fields.length; + if (!fields_count) { + return null; + } else if (fields_count === 1) { + field = fields[0].field; + multiplier = multipliers[0]; + return function(a, b) { + return multiplier * cmp( + get_field(field, a), + get_field(field, b) + ); + }; + } else { + return function(a, b) { + var i, result, a_value, b_value, field; + for (i = 0; i < fields_count; i++) { + field = fields[i].field; + result = multipliers[i] * cmp( + get_field(field, a), + get_field(field, b) + ); + if (result) return result; + } + return 0; + }; + } +}; + +/** + * Parses a search query and returns an object + * with tokens and fields ready to be populated + * with results. + * + * @param {string} query + * @param {object} options + * @returns {object} + */ +Sifter.prototype.prepareSearch = function(query, options) { + if (typeof query === 'object') return query; + + options = extend({}, options); + + var option_fields = options.fields; + var option_sort = options.sort; + var option_sort_empty = options.sort_empty; + + if (option_fields && !Array.isArray(option_fields)) options.fields = [option_fields]; + if (option_sort && !Array.isArray(option_sort)) options.sort = [option_sort]; + if (option_sort_empty && !Array.isArray(option_sort_empty)) options.sort_empty = [option_sort_empty]; + + return { + options : options, + query : String(query || '').toLowerCase(), + tokens : this.tokenize(query, options.respect_word_boundaries), + total : 0, + items : [] + }; +}; + +/** + * Searches through all items and returns a sorted array of matches. + * + * The `options` parameter can contain: + * + * - fields {string|array} + * - sort {array} + * - score {function} + * - filter {bool} + * - limit {integer} + * + * Returns an object containing: + * + * - options {object} + * - query {string} + * - tokens {array} + * - total {int} + * - items {array} + * + * @param {string} query + * @param {object} options + * @returns {object} + */ +Sifter.prototype.search = function(query, options) { + var self = this, value, score, search, calculateScore; + var fn_sort; + var fn_score; + + search = this.prepareSearch(query, options); + options = search.options; + query = search.query; + + // generate result scoring function + fn_score = options.score || self.getScoreFunction(search); + + // perform search and sort + if (query.length) { + self.iterator(self.items, function(item, id) { + score = fn_score(item); + if (options.filter === false || score > 0) { + search.items.push({'score': score, 'id': id}); + } + }); + } else { + self.iterator(self.items, function(item, id) { + search.items.push({'score': 1, 'id': id}); + }); + } + + fn_sort = self.getSortFunction(search, options); + if (fn_sort) search.items.sort(fn_sort); + + // apply limits + search.total = search.items.length; + if (typeof options.limit === 'number') { + search.items = search.items.slice(0, options.limit); + } + + return search; +}; + +// utilities +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +var cmp = function(a, b) { + if (typeof a === 'number' && typeof b === 'number') { + return a > b ? 1 : (a < b ? -1 : 0); + } + a = asciifold(String(a || '')); + b = asciifold(String(b || '')); + if (a > b) return 1; + if (b > a) return -1; + return 0; +}; + +var extend = function(a, b) { + var i, n, k, object; + for (i = 1, n = arguments.length; i < n; i++) { + object = arguments[i]; + if (!object) continue; + for (k in object) { + if (object.hasOwnProperty(k)) { + a[k] = object[k]; + } + } + } + return a; +}; + +/** + * A property getter resolving dot-notation + * @param {Object} obj The root object to fetch property on + * @param {String} name The optionally dotted property name to fetch + * @param {Boolean} nesting Handle nesting or not + * @return {Object} The resolved property value + */ +var getattr = function(obj, name, nesting) { + if (!obj || !name) return; + if (!nesting) return obj[name]; + var names = name.split("."); + while(names.length && (obj = obj[names.shift()])); + return obj; +}; + +var trim = function(str) { + return (str + '').replace(/^\s+|\s+$|/g, ''); +}; + +var escape_regex = function(str) { + return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); +}; + +var DIACRITICS = { + 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]', + 'b': '[b␢βΒB฿𐌁ᛒ]', + 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]', + 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]', + 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]', + 'f': '[fƑƒḞḟ]', + 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]', + 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]', + 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]', + 'j': '[jȷĴĵɈɉʝɟʲ]', + 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]', + 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]', + 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]', + 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]', + 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]', + 'q': '[qꝖꝗʠɊɋꝘꝙq̃]', + 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]', + 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]', + 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]', + 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]', + 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]', + 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]', + 'x': '[xẌẍẊẋχ]', + 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]', + 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]' +}; + +export const asciifold = (function() { + var i, n, k, chunk; + var foreignletters = ''; + var lookup = {}; + for (k in DIACRITICS) { + if (DIACRITICS.hasOwnProperty(k)) { + chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1); + foreignletters += chunk; + for (i = 0, n = chunk.length; i < n; i++) { + lookup[chunk.charAt(i)] = k; + } + } + } + var regexp = new RegExp('[' + foreignletters + ']', 'g'); + return function(str) { + return str.replace(regexp, function(foreignletter) { + return lookup[foreignletter]; + }).toLowerCase(); + }; +})(); + + +// export +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +export default Sifter; + diff --git a/src/lib/utils.js b/src/lib/lib/utils.js similarity index 96% rename from src/lib/utils.js rename to src/lib/lib/utils.js index dd29998..903c63b 100644 --- a/src/lib/utils.js +++ b/src/lib/lib/utils.js @@ -1,194 +1,194 @@ -import { asciifold } from './sifter'; - -// source: https://github.com/rob-balfre/svelte-select/blob/master/src/utils/isOutOfViewport.js -export function isOutOfViewport(elem) { - if (!elem) return false; - const parentBounding = elem - .parentElement // dropdown container - .parentElement // component container - .getBoundingClientRect(); - const bounding = elem.getBoundingClientRect(); - const out = {}; - - out.top = parentBounding.top < 0; - out.left = parentBounding.left < 0; - out.bottom = parentBounding.bottom + bounding.height > (window.innerHeight || document.documentElement.clientHeight); - out.right = parentBounding.right > (window.innerWidth || document.documentElement.clientWidth); - out.any = out.top || out.left || out.bottom || out.right; - - return out; -}; - -export let xhr = null; - -export function fetchRemote(url) { - return function(query, parentValue, cb) { - return new Promise((resolve, reject) => { - xhr = new XMLHttpRequest(); - if (parentValue) { - url = url.replace('[parent]', encodeURIComponent(parentValue)); - } - xhr.open('GET', `${url.replace('[query]', encodeURIComponent(query))}`); - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - xhr.send(); - - xhr.onreadystatechange = function() { - if (this.readyState === 4) { - if (this.status === 200) { - try { - const resp = JSON.parse(this.response); - resolve(cb ? cb(resp) : (resp.data || resp.items || resp.options || resp)); - } catch (e) { - console.warn('[Svelecte]:Fetch - error handling fetch response', e); - reject(); - } - } else { - reject(); - } - } - }; - }); - } -} - -export function debounce(fn, delay) { - let timeout; - return function() { - const self = this; - const args = arguments; - clearTimeout(timeout); - timeout = setTimeout(function() { - fn.apply(self, args) - }, delay); - }; -}; - -let itemHtml; - -export function highlightSearch(item, isSelected, $inputValue, formatter, disableHighlight) { - const itemHtmlText = formatter ? formatter(item, isSelected, $inputValue) : item; - - if ($inputValue == '' || item.isSelected || disableHighlight) { - return '
' + itemHtmlText + '
'; - } - - if (!itemHtml) { - itemHtml = document.createElement('div'); - itemHtml.className = 'sv-item-content'; - } - itemHtml.innerHTML = itemHtmlText; - - // const regex = new RegExp(`(${asciifold($inputValue)})`, 'ig'); - const pattern = asciifold($inputValue); - pattern.split(' ').filter(e => e).forEach(pat => { - highlight(itemHtml, pat); - }); - - return itemHtml.outerHTML; -} - -/** - * highlight function code from selectize itself. We pass raw html through @html svelte tag - * base from https://github.com/selectize/selectize.js/blob/master/src/contrib/highlight.js & edited - */ -const highlight = function(node, regex) { - let skip = 0; - // Wrap matching part of text node with highlighting , e.g. - // Soccer -> Soccer for pattern 'soc' - if (node.nodeType === 3) { - const folded = asciifold(node.data); - let pos = folded.indexOf(regex); - pos -= (folded.substr(0, pos).toUpperCase().length - folded.substr(0, pos).length); - if (pos >= 0 ) { - const spannode = document.createElement('span'); - spannode.className = 'highlight'; - const middlebit = node.splitText(pos); - const endbit = middlebit.splitText(regex.length); - const middleclone = middlebit.cloneNode(true); - spannode.appendChild(middleclone); - middlebit.parentNode.replaceChild(spannode, middlebit); - skip = 1; - } - } - // Recurse element node, looking for child text nodes to highlight, unless element - // is childless, + +
+ +
+ + \ No newline at end of file diff --git a/static/favicon.png b/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH