├── .DS_Store ├── .gitignore ├── Readme.md ├── docs ├── .nojekyll ├── _app │ ├── assets │ │ └── pages │ │ │ └── index.svelte-f7e4ec4a.css │ ├── chunks │ │ └── index-54ee1bf0.js │ ├── error.svelte-1f93263e.js │ ├── layout.svelte-da045ec5.js │ ├── manifest.json │ ├── pages │ │ └── index.svelte-d17e7ba4.js │ ├── start-869b4509.js │ └── version.json ├── favicon.png ├── github.png ├── global.css └── index.html ├── package-lock.json ├── package.json ├── package ├── .gitignore ├── Menu.svelte ├── Menu.svelte.d.ts ├── Open.svelte ├── Open.svelte.d.ts ├── Readme.md ├── Sheet.svelte ├── Sheet.svelte.d.ts ├── Toolbar.svelte ├── Toolbar.svelte.d.ts ├── actions │ ├── copypaste.d.ts │ ├── copypaste.js │ ├── draggable.d.ts │ ├── draggable.js │ ├── index.d.ts │ ├── index.js │ ├── resizable.d.ts │ ├── resizable.js │ ├── rightclick.d.ts │ ├── rightclick.js │ ├── selected.d.ts │ └── selected.js ├── convert.d.ts ├── convert.js ├── defaultconfig.d.ts ├── defaultconfig.js ├── index.d.ts ├── index.js ├── package.json ├── utilities.d.ts └── utilities.js ├── src ├── app.d.ts ├── app.html ├── lib │ ├── Menu.svelte │ ├── Open.svelte │ ├── Sheet.svelte │ ├── Toolbar.svelte │ ├── actions │ │ ├── copypaste.ts │ │ ├── draggable.ts │ │ ├── index.ts │ │ ├── resizable.ts │ │ ├── rightclick.ts │ │ └── selected.ts │ ├── convert.ts │ ├── defaultconfig.ts │ ├── index.ts │ └── utilities.ts └── routes │ ├── _example.json │ └── index.svelte ├── static ├── .nojekyll ├── favicon.png ├── github.png └── global.css ├── svelte.config.js └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ticruz38/svelte-sheets/b126f663574e991f2e6a102047ee57f96c65fb7a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .svelte-kit 3 | .DS_Store -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Svelte Spreadsheets 2 | 3 | Ultra fast excel sheets in the browser. Hugely inspired by JExcel, built on XLSX shoulders. 4 | 5 | => Find a live example [Here](https://ticruz38.github.io/svelte-sheets/) 6 | 7 | ### Motivation 8 | 9 | Making excel sheets a reality in the browser can be incredibly difficult, keeping good performance while drawing and editing large amount of data in the DOM is the ultimate challenge for a web developper. 10 | The best implementation I could find was the awesome vanillajs [jexcel](https://github.com/jspreadsheet/jexcel) by Paul Hodel.
11 | However, opening really big spreadsheet would still block the JS thread for a minute or two. 12 | Following Rich Harris talk about reactivity, I decided to take the idea behind Jexcel and adapt it to Svelte, making use of a Virtual List to keep the DOM small and light at all times. 13 | 14 | ### Known limitation 15 | 16 | You will need to have typescript svelte-preprocess enabled in your webpack/rollup configuration 17 | 18 | ### Installation 19 | 20 | `npm i -S svelte-sheets` 21 | 22 | ### Example 23 | 24 | ```html 25 | 39 | 40 | 41 | ``` 42 | 43 | Alternatively you can use the toolbar to open any kind of excel files 44 | 45 | ```html 46 | 64 | 65 | 66 | 67 | ``` 68 | 69 | You can configure the table such as height and many other things with the options props: 70 | 71 | ```js 72 | let options = { 73 | tableHeight: "90vh", 74 | defaultColWidth: "50px", 75 | }; 76 | ``` 77 | 78 | Many of this options will be implemented later, so expect most of them to be unresponsible. 79 | 80 | ### Things yet to be done 81 | 82 | - Make a svelte REPL demonstrating the library (awaiting repl typescript support) 83 | - ✅ Undo/Redo (mapping keyboard shortcuts) 84 | - ✅ shift+click should extend the selection 85 | - ✅ Resizing rows/columns 86 | - Filtering 87 | - ✅ Copy/Paste 88 | - Comments on cells 89 | - Support more that number, string or boolean in cells. let's say charts, datepickers etc... 90 | - ✅ Implement a tooltip when right clicking a cell with a list of actions 91 | - All other excel features you can think of 92 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ticruz38/svelte-sheets/b126f663574e991f2e6a102047ee57f96c65fb7a/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_app/assets/pages/index.svelte-f7e4ec4a.css: -------------------------------------------------------------------------------- 1 | .menu.svelte-194m42e{padding:.5rem .25rem;position:fixed;z-index:20;min-width:10rem;background:#ddd;border-radius:10px}.disabled.svelte-194m42e{color:#aaa}.divider.svelte-194m42e{padding-top:.5rem;margin-bottom:.5rem;border-bottom:1px solid #aaa}.hidden.svelte-194m42e{display:none}.flex.svelte-194m42e{display:flex}.justify-between.svelte-194m42e{justify-content:space-between}.item.svelte-194m42e{cursor:pointer;padding:.25rem .5rem;border-radius:.25rem}.item.svelte-194m42e:hover{background-color:#4fc2c2}.hidden.svelte-74gt44{height:0;width:0;opacity:0}.svelte-10iujif.svelte-10iujif.svelte-10iujif,.svelte-10iujif.svelte-10iujif.svelte-10iujif:before,.svelte-10iujif.svelte-10iujif.svelte-10iujif:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e0e0e0}:root{tab-size:4}.jexcel_content.svelte-10iujif.svelte-10iujif.svelte-10iujif{overflow-x:auto;overflow-y:auto;max-width:100vw;max-height:100vh}.sheet_container.svelte-10iujif.svelte-10iujif.svelte-10iujif{display:block;padding-right:2px;box-sizing:border-box;overscroll-behavior:contain;outline:none;position:relative;user-select:none}table.svelte-10iujif.svelte-10iujif.svelte-10iujif{border-collapse:separate;table-layout:fixed;white-space:nowrap;empty-cells:show;border:0px;background-color:#fff;width:0;border-top:1px solid transparent;border-left:1px solid transparent;border-right:1px solid #ccc;border-bottom:1px solid #ccc;text-indent:0}thead.svelte-10iujif>tr.svelte-10iujif>td.selected.svelte-10iujif{background-color:#dcdcdc;color:teal}thead.svelte-10iujif>tr.svelte-10iujif>td.svelte-10iujif{background-color:#f3f3f3;padding:2px;cursor:s-resize;box-sizing:border-box;overflow:hidden;position:sticky;top:0;z-index:2}td.svelte-10iujif.svelte-10iujif.svelte-10iujif{outline:none;cursor:default;line-height:14px;font-size:14px;border-top:1px solid #ccc;border-left:1px solid #ccc;border-right:1px solid transparent;border-bottom:1px solid transparent}tbody.svelte-10iujif>tr.svelte-10iujif>td.svelte-10iujif{padding:4px;white-space:nowrap;box-sizing:border-box;line-height:1em;text-align:end;cursor:cell}tbody.svelte-10iujif>tr.svelte-10iujif>td.selected.svelte-10iujif{background-color:#ddd;transition:all .1s linear}tbody.svelte-10iujif>tr.svelte-10iujif>th.svelte-10iujif,thead.svelte-10iujif>tr.svelte-10iujif>th.svelte-10iujif{position:sticky;left:0;cursor:e-resize;top:auto;background:#f3f3f3;border-top:1px solid #ccc;border-left:1px solid #ccc;border-right:1px solid #ccc;z-index:10;font-weight:400;height:27px}tbody.svelte-10iujif>tr.svelte-10iujif>th.selected.svelte-10iujif{background-color:#dcdcdc!important;color:teal}div.col-resize.svelte-10iujif.svelte-10iujif.svelte-10iujif{position:absolute;top:0;cursor:col-resize;width:1rem;height:100%}div.col-resize.right.svelte-10iujif.svelte-10iujif.svelte-10iujif{right:0}div.col-resize.left.svelte-10iujif.svelte-10iujif.svelte-10iujif{left:0}div.row-resize.svelte-10iujif.svelte-10iujif.svelte-10iujif{position:absolute;left:0;cursor:row-resize;width:100%;height:.5rem}div.row-resize.top.svelte-10iujif.svelte-10iujif.svelte-10iujif{top:0}div.row-resize.bottom.svelte-10iujif.svelte-10iujif.svelte-10iujif{bottom:0}input.svelte-10iujif.svelte-10iujif.svelte-10iujif{background:none;margin:-4px 0;outline:none}.absolute.svelte-10iujif.svelte-10iujif.svelte-10iujif{position:absolute;z-index:10;transition:all .1s linear}.top-select.svelte-10iujif.svelte-10iujif.svelte-10iujif,.bottom-select.svelte-10iujif.svelte-10iujif.svelte-10iujif,.col-line.svelte-10iujif.svelte-10iujif.svelte-10iujif{border-bottom:2px solid teal}.left-select.svelte-10iujif.svelte-10iujif.svelte-10iujif,.right-select.svelte-10iujif.svelte-10iujif.svelte-10iujif{border-left:2px solid teal}.top-extend.svelte-10iujif.svelte-10iujif.svelte-10iujif,.bottom-extend.svelte-10iujif.svelte-10iujif.svelte-10iujif{border-bottom:2px solid #aaa}.left-extend.svelte-10iujif.svelte-10iujif.svelte-10iujif,.right-extend.svelte-10iujif.svelte-10iujif.svelte-10iujif{border-left:2px solid #aaa}.row-line.svelte-10iujif.svelte-10iujif.svelte-10iujif{border-right:1px solid teal}.square.svelte-10iujif.svelte-10iujif.svelte-10iujif{height:8px;width:8px;cursor:crosshair;border:1px solid white;background:teal;transform:translate3D(-40%,-40%,0)}.hidden.svelte-10iujif.svelte-10iujif.svelte-10iujif{display:none}.hidden.svelte-1oyjtb6{height:0;width:0;opacity:0}.active.svelte-1oyjtb6{border-bottom:1px solid teal}.flex.svelte-1oyjtb6{display:flex;justify-content:center;align-items:center}.ml-4.svelte-1oyjtb6{margin-left:1rem}.cursor-pointer.svelte-1oyjtb6{cursor:pointer}.sheet-names.svelte-57i5ub{text-align:center;position:fixed;bottom:0;width:100%;padding:1rem}.github-link.svelte-57i5ub{position:fixed;top:.5rem;right:.5rem;background-image:url(/github.png);height:2rem;width:2rem;background-position:center;background-repeat:none;background-size:cover}.selected.svelte-57i5ub{text-decoration:underline} 2 | -------------------------------------------------------------------------------- /docs/_app/chunks/index-54ee1bf0.js: -------------------------------------------------------------------------------- 1 | function P(){}function nt(t,e){for(const n in e)t[n]=e[n];return t}function K(t){return t()}function H(){return Object.create(null)}function $(t){t.forEach(K)}function Q(t){return typeof t=="function"}function xt(t,e){return t!=t?e==e:t!==e||t&&typeof t=="object"||typeof t=="function"}function it(t){return Object.keys(t).length===0}function wt(t,e,n,i){if(t){const o=R(t,e,n,i);return t[0](o)}}function R(t,e,n,i){return t[1]&&i?nt(n.ctx.slice(),t[1](i(e))):n.ctx}function bt(t,e,n,i){if(t[2]&&i){const o=t[2](i(n));if(e.dirty===void 0)return o;if(typeof o=="object"){const l=[],r=Math.max(e.dirty.length,o.length);for(let c=0;c32){const e=[],n=t.ctx.length/32;for(let i=0;i>1);n(o)<=i?t=o+1:e=o}return t}function ct(t){if(t.hydrate_init)return;t.hydrate_init=!0;let e=t.childNodes;if(t.nodeName==="HEAD"){const s=[];for(let a=0;a0&&e[n[o]].claim_order<=a?o+1:st(1,o,d=>e[n[d]].claim_order,a))-1;i[s]=n[f]+1;const _=f+1;n[_]=s,o=Math.max(_,o)}const l=[],r=[];let c=e.length-1;for(let s=n[o]+1;s!=0;s=i[s-1]){for(l.push(e[s-1]);c>=s;c--)r.push(e[c]);c--}for(;c>=0;c--)r.push(e[c]);l.reverse(),r.sort((s,a)=>s.claim_order-a.claim_order);for(let s=0,a=0;s=l[a].claim_order;)a++;const f=at.removeEventListener(e,n,i)}function Mt(t,e,n){n==null?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function ut(t){return Array.from(t.childNodes)}function ft(t){t.claim_info===void 0&&(t.claim_info={last_index:0,total_claimed:0})}function X(t,e,n,i,o=!1){ft(t);const l=(()=>{for(let r=t.claim_info.last_index;r=0;r--){const c=t[r];if(e(c)){const s=n(c);return s===void 0?t.splice(r,1):t[r]=s,o?s===void 0&&t.claim_info.last_index--:t.claim_info.last_index=r,c}}return i()})();return l.claim_order=t.claim_info.total_claimed,t.claim_info.total_claimed+=1,l}function dt(t,e,n,i){return X(t,o=>o.nodeName===e,o=>{const l=[];for(let r=0;ro.removeAttribute(r))},()=>i(e))}function Nt(t,e,n){return dt(t,e,n,V)}function _t(t,e){return X(t,n=>n.nodeType===3,n=>{const i=""+e;if(n.data.startsWith(i)){if(n.data.length!==i.length)return n.splitText(i.length)}else n.data=i},()=>I(e),!0)}function Ct(t){return _t(t," ")}function zt(t,e){e=""+e,t.wholeText!==e&&(t.data=e)}function Lt(t,e){t.value=e==null?"":e}function Tt(t,e,n,i){n===null?t.style.removeProperty(e):t.style.setProperty(e,n,i?"important":"")}let E;function ht(){if(E===void 0){E=!1;try{typeof window!="undefined"&&window.parent&&window.parent.document}catch{E=!0}}return E}function Wt(t,e){getComputedStyle(t).position==="static"&&(t.style.position="relative");const i=V("iframe");i.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;"),i.setAttribute("aria-hidden","true"),i.tabIndex=-1;const o=ht();let l;return o?(i.src="data:text/html, 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-sheets", 3 | "version": "1.0.0", 4 | "description": "Run your excel sheet in the browser!", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "build": "svelte-kit build", 9 | "dev": "svelte-kit dev", 10 | "package": "svelte-kit package", 11 | "preview": "svelte-kit preview" 12 | }, 13 | "keywords": [ 14 | "svelte", 15 | "xlsx", 16 | "excel", 17 | "spreadsheets" 18 | ], 19 | "author": "Thibaut Duchêne", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "@sveltejs/adapter-auto": "next", 23 | "@sveltejs/adapter-static": "^1.0.0-next.30", 24 | "@sveltejs/kit": "next", 25 | "@tsconfig/svelte": "^3.0.0", 26 | "prettier": "^2.5.1", 27 | "prettier-plugin-svelte": "^2.5.0", 28 | "svelte": "^3.44.0", 29 | "svelte-check": "^2.2.6", 30 | "svelte-preprocess": "^4.10.1", 31 | "svelte2tsx": "^0.5.10", 32 | "tslib": "^2.3.1", 33 | "typescript": "~4.6.2" 34 | }, 35 | "peerDependencies": { 36 | "xlsx": "^0.16.9", 37 | "b64-to-blob": "^1.2.19", 38 | "hotkeys-js": "^3.8.1" 39 | }, 40 | "dependencies": { 41 | "b64-to-blob": "^1.2.19", 42 | "hotkeys-js": "^3.8.1", 43 | "xlsx": "^0.16.9" 44 | }, 45 | "type": "module" 46 | } -------------------------------------------------------------------------------- /package/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .svelte-kit 3 | .DS_Store -------------------------------------------------------------------------------- /package/Menu.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |