Repository: myliang/xspreadsheet Branch: master Commit: 3075e5007eaa Files: 60 Total size: 204.8 KB Directory structure: gitextract_n0gowviz/ ├── .gitignore ├── LICENSE ├── README.md ├── docs/ │ ├── index.html │ └── xspreadsheet.js ├── index.html ├── package.json ├── src/ │ ├── core/ │ │ ├── alphabet.d.ts │ │ ├── alphabet.ts │ │ ├── cell.d.ts │ │ ├── cell.ts │ │ ├── font.d.ts │ │ ├── font.ts │ │ ├── format.d.ts │ │ ├── format.ts │ │ ├── formula.d.ts │ │ ├── formula.ts │ │ ├── index.d.ts │ │ ├── index.ts │ │ ├── select.d.ts │ │ └── select.ts │ ├── local/ │ │ ├── base/ │ │ │ ├── colorPanel.d.ts │ │ │ ├── colorPanel.ts │ │ │ ├── dropdown.d.ts │ │ │ ├── dropdown.ts │ │ │ ├── element.d.ts │ │ │ ├── element.ts │ │ │ ├── icon.d.ts │ │ │ ├── icon.ts │ │ │ ├── item.d.ts │ │ │ ├── item.ts │ │ │ ├── menu.d.ts │ │ │ ├── menu.ts │ │ │ ├── suggest.d.ts │ │ │ └── suggest.ts │ │ ├── contextmenu.d.ts │ │ ├── contextmenu.ts │ │ ├── editor.d.ts │ │ ├── editor.ts │ │ ├── editorbar.d.ts │ │ ├── editorbar.ts │ │ ├── event.d.ts │ │ ├── event.ts │ │ ├── index.d.ts │ │ ├── index.ts │ │ ├── resizer.d.ts │ │ ├── resizer.ts │ │ ├── selector.d.ts │ │ ├── selector.ts │ │ ├── table.d.ts │ │ ├── table.ts │ │ ├── toolbar.d.ts │ │ └── toolbar.ts │ ├── main.d.ts │ ├── main.ts │ └── style/ │ └── index.less ├── tsconfig.json ├── tslint.json ├── webpack.config.dev.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules/ .vscode/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 myliang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # XSpreadsheet [![npm package](https://img.shields.io/npm/v/xspreadsheet.svg)](https://www.npmjs.org/package/xspreadsheet) [![NPM downloads](http://img.shields.io/npm/dm/xspreadsheet.svg)](https://npmjs.org/package/xspreadsheet) > a javascript spreadsheet for web

## Install ```shell npm install typescript --save-dev npm install awesome-typescript-loader --save-dev npm install xspreadsheet --save-dev ``` ## Quick Start ``` javascript import xspreadsheet from 'xspreadsheet' const x = xspreadsheet(document.getElementById('#id')) x.change = (data) => { console.log('data:', data) } // edit // data is param in the change method xspreadsheet(document.getElementById('#id'), {d: data}) ``` ### in tsconfig.json ``` { "compilerOptions": { .... "types": ["xspreadsheet"], .... } } ``` ## Browser Support Modern browsers and Internet Explorer 9+(no test). ## LICENSE MIT ================================================ FILE: docs/index.html ================================================ XSpreadsheet Demo
================================================ FILE: docs/xspreadsheet.js ================================================ !function(e){var t={};function s(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{}};return e[i].call(n.exports,n,n.exports,s),n.l=!0,n.exports}s.m=e,s.c=t,s.d=function(e,t,i){s.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:i})},s.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,"a",t),t},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.p="",s(s.s=30)}([function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(1);class n{constructor(e="div"){this.tag=e,this._data={},this._clickOutside=null,this.el=document.createElement(e)}data(e,t){return void 0!==t&&(this._data[e]=t),this._data[e]}on(e,t){const[s,...i]=e.split(".");return this.el.addEventListener(s,e=>{for(let t of i){if(console.log("::::::::::",t),"left"===t&&0!==e.button)return;if("right"===t&&2!==e.button)return;"stop"===t&&e.stopPropagation()}t(e)}),this}onClickOutside(e){return this._clickOutside=e,this}parent(){return this.el.parentNode}class(e){return this.el.className=e,this}attrs(e={}){for(let t of Object.keys(e))this.attr(t,e[t]);return this}attr(e,t){return void 0===t?this.el.getAttribute(e):(this.el.setAttribute(e,t),this)}removeAttr(e){return this.el.removeAttribute(e),this}offset(){const{offsetTop:e,offsetLeft:t,offsetHeight:s,offsetWidth:i}=this.el;return{top:e,left:t,height:s,width:i}}clearStyle(){return this.el.style="",this}styles(e={},t=!1){t&&this.clearStyle();for(let t of Object.keys(e))this.style(t,e[t]);return this}style(e,t){return void 0===t?this.el.style.getPropertyValue(e):(this.el.style.setProperty(e,t),this)}contains(e){return this.el.contains(e)}removeStyle(e){this.el.style.removeProperty(e)}children(e){for(let t of e)this.child(t);return this}child(e){return"string"==typeof e?this.el.appendChild(document.createTextNode(e)):e instanceof n?this.el.appendChild(e.el):e instanceof HTMLElement&&this.el.appendChild(e),this}html(e){return void 0===e?this.el.innerHTML:(this.el.innerHTML=e,this)}val(e){return void 0===e?this.el.value:(this.el.value=e,this)}clone(){return this.el.cloneNode()}isHide(){return"none"===this.style("display")}toggle(){this.isHide()?this.show():this.hide()}disabled(){return this.addClass("disabled"),this}able(){return this.removeClass("disabled"),this}active(e=!0){return e?this.addClass("active"):this.deactive(),this}deactive(){return this.removeClass("active")}isActive(){return this.hasClass("active")}addClass(e){return this.el.className=this.el.className.split(" ").concat(e).join(" "),this}removeClass(e){return this.el.className=this.el.className.split(" ").filter(t=>t!==e).join(" "),this}hasClass(e){return-1!==this.el.className.indexOf(e)}show(e=!1){return e?this.removeStyle("display"):this.style("display","block"),this._clickOutside&&(this.data("_outsidehandler",e=>{if(this.contains(e.target))return!1;this.hide(),i.unbind("click",this.data("_outsidehandler")),this._clickOutside&&this._clickOutside()}),setTimeout(()=>{i.bind("click",this.data("_outsidehandler"))},0)),this}hide(){return this.style("display","none"),this._clickOutside&&i.unbind("click",this.data("_outsidehandler")),this}}t.Element=n,t.h=function(e="div"){return new n(e)}},function(e,t,s){"use strict";function i(e,t,s=window){s.addEventListener(e,t)}function n(e,t,s=window){s.removeEventListener(e,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.bind=i,t.unbind=n,t.mouseMoveUp=function(e,t){i("mousemove",e);const s=i=>{n("mousemove",e),n("mouseup",s),t(i)};i("mouseup",s)}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0);class n extends i.Element{constructor(e="vertical"){super(),this.class(`spreadsheet-menu ${e}`)}}t.Menu=n,t.buildMenu=function(e="vertical"){return new n(e)}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0);class n extends i.Element{constructor(e){super(),this.class("spreadsheet-icon").child(this.img=i.h().class(`spreadsheet-icon-img ${e}`))}replace(e){this.img.class(`spreadsheet-icon-img ${e}`)}}t.Icon=n,t.buildIcon=function(e){return new n(e)}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=s(3);class r extends i.Element{constructor(){super(),this.iconEl=null,this.class("spreadsheet-item")}static build(){return new r}icon(e){return this.child(this.iconEl=n.buildIcon(e)),this}replaceIcon(e){this.iconEl&&this.iconEl.replace(e)}}t.Item=r,t.buildItem=function(){return new r}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.defaultCell={font:"Microsoft YaHei",format:"normal",fontSize:14,bold:!1,italic:!1,underline:!1,color:"#333",backgroundColor:"#fff",align:"left",valign:"middle",wordWrap:!1,invisible:!1,rowspan:1,colspan:1,text:""},t.getStyleFromCell=function(e){const t={};return e&&(e.font&&(t["font-family"]=e.font),e.fontSize&&(t["font-size"]=`${e.fontSize}px`),e.bold&&(t["font-weight"]="bold"),e.italic&&(t["font-style"]="italic"),e.underline&&(t["text-decoration"]="underline"),e.color&&(t.color=e.color),e.backgroundColor&&(t["background-color"]=e.backgroundColor),e.align&&(t["text-align"]=e.align),e.valign&&(t["vertical-align"]=e.valign),e.invisible&&(t.display="none"),e.wordWrap&&(t["word-wrap"]="break-word",t["white-space"]="normal")),t}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"];t.alphabet=function(e){const[t,s]=[parseInt(e/i.length+""),e%i.length];return t>0?`${i[t-1]}${i[s]}`:i[s]},t.alphabetIndex=function(e){let t=0;for(let s=0;s{if("="===e[0]){const s=e.substring(1,e.indexOf("("));for(let i of exports.formulas)if(i.key.toLowerCase()===s.toLowerCase())return t(i,e.substring(e.indexOf("(")+1,e.lastIndexOf(")")))}return e}),exports.formulaRender=((e,t)=>exports.formulaFilterKey(e,(e,s)=>e.render(formulaParamToArray(s,t))+"")),exports.formulaReplaceParam=((e,t,s)=>exports.formulaFilterKey(e,(e,i)=>{const n=e=>{if(/^[0-9\-\+\*\/()\s]+$/.test(e.trim()))return e;const i=/\d+/.exec(e);if(i){let n=e.substring(0,i.index).trim(),r=parseInt(e.substring(i.index).trim());return`${alphabet_1.alphabet(alphabet_1.alphabetIndex(n)+s)}${r+t}`}return e};return i=-1!==i.indexOf(":")?i.split(":").map(n).join(":"):i.split(",").map(n).join(","),`=${e.key}(${i})`}));const formulaParamToArray=(param,renderCell)=>{let paramValues=[];try{if(-1!==param.indexOf(":")){const[e,t]=param.split(":"),s=/\d+/.exec(e),i=/\d+/.exec(t);if(s&&i){let n=e.substring(0,s.index).trim(),r=parseInt(e.substring(s.index).trim()),o=t.substring(0,i.index).trim(),l=parseInt(t.substring(i.index).trim());if(o===n)for(let e=r;e<=l;e++)paramValues.push(renderCell(e-1,alphabet_1.alphabetIndex(n)));else for(let e=alphabet_1.alphabetIndex(n);e<=alphabet_1.alphabetIndex(o);e++)paramValues.push(renderCell(r-1,e))}}else paramValues=param.split(",").map(p=>{if(/^[0-9\-\+\*\/()\s]+$/.test(p.trim()))try{return eval(p)}catch(e){return 0}const idx=/\d+/.exec(p);if(idx){const e=p.substring(0,idx.index).trim(),t=p.substring(idx.index).trim();return renderCell(parseInt(t)-1,alphabet_1.alphabetIndex(e))}return 0})}catch(e){console.log("warning:",e)}return paramValues};exports.formulas=[{key:"SUM",title:"求和",render:e=>e.reduce((e,t)=>Number(e)+Number(t),0)},{key:"AVERAGE",title:"平均值",render:e=>e.reduce((e,t)=>Number(e)+Number(t),0)/e.length},{key:"MAX",title:"最大值",render:e=>Math.max(...e.map(e=>Number(e)))},{key:"MIN",title:"最小值",render:e=>Math.min(...e.map(e=>Number(e)))}]},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.formatRenderHtml=((e,s)=>{for(let i=0;i{if(/^(-?\d*.?\d*)$/.test(e)){const t=(e=Number(e).toFixed(2).toString()).split(".");return t[0]=t[0].toString().replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1,"),t.join(".")}return e},n=e=>e;t.formats=[{key:"normal",title:"Normal",render:n},{key:"text",title:"Text",render:n},{key:"number",title:"Number",label:"1,000.12",render:i},{key:"percent",title:"Percent",label:"10.12%",render:e=>`${i(e)}%`},{key:"RMB",title:"RMB",label:"¥10.00",render:e=>`¥${i(e)}`},{key:"USD",title:"USD",label:"$10.00",render:e=>`$${i(e)}`}]},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0);t.Editorbar=class{constructor(){this.value=null,this.change=(e=>{}),this.el=i.h().class("spreadsheet-editor-bar").children([i.h().class("spreadsheet-formula-bar").children([this.label=i.h().class("spreadsheet-formula-label"),this.textarea=i.h("textarea").on("input",e=>this.input(e))])])}set(e,t){this.label.html(e),this.setValue(t)}setValue(e){this.value=e,this.textarea.val(e&&e.text||"")}input(e){const t=e.target.value;this.value?this.value.text=t:this.value={text:t},this.change(this.value)}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=[["#c00000","#ff0000","#ffc003","#ffff00","#91d051","#00af50","#00b0f0","#0070c0","#002060","#70309f"],["#ffffff","#000000","#e7e6e6","#44546a","#4472c4","#ed7d31","#a5a5a5","#ffc003","#5b9bd5","#70ad47"],["#f4f5f8","#848484","#d0cece","#d6dce4","#d9e2f2","#fae5d5","#ededed","#fff2cc","#deebf6","#e2efd9"],["#d8d8d8","#595959","#afabab","#adb9ca","#b4c6e7","#f7cbac","#dbdbdb","#fee598","#bdd7ee","#c5e0b3"],["#bfbfbf","#3f3f3f","#757070","#8496b0","#8eaad8","#f4b183","#c9c9c9","#ffd964","#9dc2e5","#a8d08d"],["#a5a5a5","#262626","#3a3838","#333f4f","#2f5496","#c55b11","#7b7b7b","#bf9001","#2e75b5","#538135"],["#7e7e7e","#0c0c0c","#171616","#232a35","#1e3864","#833d0b","#525252","#7e6000","#1f4e79","#375623"]];class r extends i.Element{constructor(e){super(),this.class("spreadsheet-color-panel").child(i.h("table").child(i.h("tbody").children(n.map(t=>i.h("tr").children(t.map(t=>i.h("td").child(i.h().class("color-cell").on("click",e.bind(null,t)).style("background-color",t))))))))}}t.ColorPanel=r,t.buildColorPanel=function(e){return new r(e)}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=s(3);class r extends i.Element{constructor(e,t,s){super(),this.class("spreadsheet-dropdown spreadsheet-item"),this.content=i.h().class("spreadsheet-dropdown-content").children(s).onClickOutside(()=>this.deactive()).on("click",e=>this.toggleHandler(e)).style("width",t).hide(),this.child(i.h().class("spreadsheet-dropdown-header").children([this.title="string"==typeof e?i.h().class("spreadsheet-dropdown-title").child(e):e,i.h().class("spreadsheet-dropdown-icon").on("click",e=>this.toggleHandler(e)).child(n.buildIcon("arrow-down"))])).child(this.content)}toggleHandler(e){this.content.isHide()?(this.content.show(),this.active()):(this.content.hide(),this.deactive())}}t.Dropdown=r,t.buildDropdown=function(e,t,s){return new r(e,t,s)}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=s(4),r=s(3),o=s(11),l=s(2),h=s(10);t.Toolbar=class{constructor(e){this.ss=e,this.target=null,this.currentCell=null,this.change=((e,t)=>{}),this.redo=(()=>!1),this.undo=(()=>!1),this.defaultCell=e.data.cell,this.el=i.h().class("spreadsheet-toolbar").child(l.buildMenu("horizontal").children([this.elUndo=this.buildUndo(),this.elRedo=this.buildRedo(),this.elPaintformat=this.buildPaintformat(),this.elClearformat=this.buildClearformat(),this.elFormat=this.buildFormats(),this.buildSeparator(),this.elFont=this.buildFonts(),this.elFontSize=this.buildFontSizes(),this.buildSeparator(),this.elFontWeight=this.buildFontWeight(),this.elFontStyle=this.buildFontStyle(),this.elTextDecoration=this.buildTextDecoration(),this.elColor=this.buildColor(),this.buildSeparator(),this.elBackgroundColor=this.buildBackgroundColor(),this.elMerge=this.buildMerge(),this.buildSeparator(),this.elAlign=this.buildAligns(),this.elValign=this.buildValigns(),this.elWordWrap=this.buildWordWrap()]))}set(e,t){this.target=e,this.setCell(t)}setCell(e){this.currentCell=e,this.setCellStyle()}setCellStyle(){const{target:e,currentCell:t,defaultCell:s,ss:i}=this;e&&(this.elFormat.title.html(i.getFormat(null!==t&&t.format||s.format).title),this.elFont.title.html(i.getFont(null!==t&&t.font||s.font).title),this.elFontSize.title.html((null!==t&&t.fontSize||s.fontSize)+""),this.elFontWeight.active(null!==t&&void 0!==t.bold&&t.bold!==s.bold),this.elFontStyle.active(null!==t&&void 0!==t.italic&&t.italic!==s.italic),this.elTextDecoration.active(null!==t&&void 0!==t.underline&&t.underline!==s.underline),this.elColor.title.style("border-bottom-color",null!==t&&t.color||s.color),this.elBackgroundColor.title.style("border-bottom-color",null!==t&&t.backgroundColor||s.backgroundColor),this.elAlign.title.replace(`align-${null!==t&&t.align||s.align}`),this.elValign.title.replace(`valign-${null!==t&&t.valign||s.valign}`),this.elWordWrap.active(null!==t&&void 0!==t.wordWrap&&t.wordWrap!==s.wordWrap),null!==t&&t.rowspan&&t.rowspan>1||null!==t&&t.colspan&&t.colspan>1?this.elMerge.active(!0):this.elMerge.active(!1))}setRedoAble(e){e?this.elRedo.able():this.elRedo.disabled()}setUndoAble(e){e?this.elUndo.able():this.elUndo.disabled()}buildSeparator(){return i.h().class("spreadsheet-item-separator")}buildAligns(){const e=r.buildIcon(`align-${this.defaultCell.align}`),t=t=>{e.replace(`align-${t}`),this.change("align",t)};return o.buildDropdown(e,"60px",[l.buildMenu().children(["left","center","right"].map(e=>n.buildItem().child(r.buildIcon(`align-${e}`).style("text-align","center")).on("click",t.bind(null,e))))])}buildValigns(){const e=r.buildIcon(`valign-${this.defaultCell.valign}`),t=t=>{e.replace(`valign-${t}`),this.change("valign",t)};return o.buildDropdown(e,"60px",[l.buildMenu().children(["top","middle","bottom"].map(e=>n.buildItem().child(r.buildIcon(`valign-${e}`).style("text-align","center")).on("click",t.bind(null,e))))])}buildWordWrap(){return a("textwrap",e=>this.change("wordWrap",e))}buildFontWeight(){return a("bold",e=>this.change("bold",e))}buildFontStyle(){return a("italic",e=>this.change("italic",e))}buildTextDecoration(){return a("underline",e=>this.change("underline",e))}buildMerge(){return a("merge",e=>this.change("merge",e))}buildColor(){return o.buildDropdown(r.buildIcon("text-color").styles({"border-bottom":`3px solid ${this.defaultCell.color}`,"margin-top":"2px",height:"16px"}),"auto",[h.buildColorPanel(e=>{this.elColor.title.style("border-bottom-color",e),this.change("color",e)})])}buildBackgroundColor(){return o.buildDropdown(r.buildIcon("cell-color").styles({"border-bottom":`3px solid ${this.defaultCell.backgroundColor}`,"margin-top":"2px",height:"16px"}),"auto",[h.buildColorPanel(e=>{this.elBackgroundColor.title.style("border-bottom-color",e),this.change("backgroundColor",e)})])}buildUndo(){return n.buildItem().child(r.buildIcon("undo")).on("click",e=>{this.undo()?this.elUndo.able():this.elUndo.disabled()}).disabled()}buildRedo(){return n.buildItem().child(r.buildIcon("redo")).on("click",e=>{this.redo()?this.elRedo.able():this.elRedo.disabled()}).disabled()}buildPaintformat(){return a("paintformat",e=>{this.change("paintformat",!0),this.elPaintformat.deactive()})}buildClearformat(){return a("clearformat",e=>{this.change("clearformat",!0),this.elClearformat.deactive()})}buildFormats(){const e=e=>{this.elFormat.title.html(this.ss.getFormat(e.key).title),this.change("format",e.key)};return o.buildDropdown(this.ss.getFormat(this.defaultCell.format).title,"250px",[l.buildMenu().children(this.ss.formats.map(t=>n.buildItem().children([t.title,i.h().class("label").child(t.label||"")]).on("click",e.bind(null,t))))])}buildFonts(){const e=e=>{this.elFont.title.html(e.title),this.change("font",e.key)};return o.buildDropdown(this.ss.getFont(this.defaultCell.font).title,"170px",[l.buildMenu().children(this.ss.fonts.map(t=>n.buildItem().child(t.title).on("click",e.bind(null,t))))])}buildFontSizes(){const e=e=>{this.elFontSize.title.html(`${e}`),this.change("fontSize",e)};return o.buildDropdown(this.defaultCell.fontSize+"","70px",[l.buildMenu().children([6,8,10,12,14,16,18,20,22,24,30,36].map(t=>n.buildItem().child(`${t}`).on("click",e.bind(null,t))))])}};const a=(e,t)=>{const s=n.buildItem().child(r.buildIcon(e));return s.on("click",e=>{let i=s.isActive();i?s.deactive():s.active(),t(!i)}),s}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=s(4),r=s(2);t.ContextMenu=class{constructor(e){this.table=e,this.el=i.h().class("spreadsheet-contextmenu").style("width","160px").on("click",e=>this.el.hide()).children([r.buildMenu().children([n.buildItem().on("click",t=>e.copy()).children(["copy",i.h().class("label").html("ctrl + c")]),n.buildItem().on("click",t=>e.cut()).children(["cut",i.h().class("label").html("ctrl + x")]),n.buildItem().on("click",t=>e.paste()).children(["paste",i.h().class("label").html("ctrl + v")])])]).onClickOutside(()=>{}).hide()}set(e){const{offsetLeft:t,offsetTop:s}=e.target,i=this.el.el.getBoundingClientRect(),{clientWidth:n,clientHeight:r}=document.documentElement;let o=s+e.offsetY,l=t+e.offsetX;e.clientY>r/1.5&&(o-=i.height),e.clientX>n/1.5&&(l-=i.width),this.el.style("left",`${l}px`).style("top",`${o}px`).show()}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=s(1);t.Resizer=class{constructor(e,t){this.vertical=e,this.change=t,this.moving=!1,this.index=0,this.el=i.h().class("spreadsheet-resizer-wrapper").children([this.resizer=i.h().class(`spreadsheet-resizer ${e?"vertical":"horizontal"}`).on("mousedown",e=>this.mousedown(e)),this.resizerLine=i.h().class(`spreadsheet-resizer-line ${e?"vertical":"horizontal"}`).hide()])}set(e,t,s){if(this.moving)return;this.index=t;const{vertical:i}=this,{offsetLeft:n,offsetTop:r,offsetHeight:o,offsetWidth:l,parentNode:h}=e;this.resizer.styles({left:`${i?n+l-5-s:n}px`,top:`${i?r:r+o-5+24-s}px`,width:`${i?5:l}px`,height:`${i?o:5}px`}),this.resizerLine.styles({left:`${i?n+l-s:n}px`,top:`${i?r:r+o+24-s}px`,width:`${i?0:h.parentNode.parentNode.parentNode.parentNode.nextSibling.offsetWidth-15}px`,height:`${i?h.parentNode.parentNode.parentNode.nextSibling.offsetHeight+h.offsetHeight:0}px`})}mousedown(e){let t=e,s=0;this.resizerLine.show(),n.mouseMoveUp(e=>{if(this.moving=!0,null!==t&&1===e.buttons){if(this.vertical){const i=e.x-t.x;s+=i,this.resizer.style("left",`${this.resizer.offset().left+i}px`),this.resizerLine.style("left",`${this.resizerLine.offset().left+i}px`)}else{const i=e.y-t.y;s+=i,this.resizer.style("top",`${this.resizer.offset().top+i}px`),this.resizerLine.style("top",`${this.resizerLine.offset().top+i}px`)}t=e}},e=>{this.change(this.index,s),t=null,this.resizerLine.hide(),s=0,this.moving=!1})}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=s(1);t.Selector=class{constructor(e,t){this.ss=e,this.table=t,this._offset={left:0,top:0,width:0,height:0},this.change=(()=>{}),this.changeCopy=((e,t,s,i,n,r)=>{}),this.topEl=i.h().class("top-border"),this.rightEl=i.h().class("right-border"),this.bottomEl=i.h().class("bottom-border"),this.leftEl=i.h().class("left-border"),this.areaEl=i.h().class("area-border"),this.cornerEl=i.h().class("corner").on("mousedown",e=>this.cornerMousedown(e)),this.copyEl=i.h().class("copy-border"),this.el=i.h().class("spreadsheet-borders").children([this.topEl,this.rightEl,this.bottomEl,this.leftEl,this.areaEl,this.cornerEl,this.copyEl.hide()]).hide()}mousedown(e){if(1===e.detail&&"cell"===e.target.getAttribute("type")){if(e.shiftKey)return this.endTarget=e.target,void this.setOffset();this.setCurrentTarget(e.target),n.mouseMoveUp(e=>{1===e.buttons&&"cell"===e.target.getAttribute("type")&&(this.endTarget=e.target,this.setOffset())},e=>{this.change()}),this.el.show()}}setCurrentTarget(e){Object.assign(this,{startTarget:e,endTarget:e}),this.setOffset()}cornerMousedown(e){const{select:t}=this.ss;if(null===t)return;const[s,i]=t.stop,[r,o]=t.start;let l=null;n.mouseMoveUp(e=>{const n=e.target.getAttribute("row-index"),h=e.target.getAttribute("col-index");if(n&&h){this.copyEl.show();let e=s-n,a=i-h,d=r-n,c=o-h;const{left:p,top:u,height:f,width:b}=this._offset;if(e<0)this.copyEl.styles({left:`${p-1}px`,top:`${u-1}px`,width:`${b-1}px`,height:`${this.rowsHeight(s-t.rowLen()+1,s+Math.abs(e))-1}px`}),l=["bottom",s+1,o,s+Math.abs(e),i];else if(a<0)this.copyEl.styles({left:`${p-1}px`,top:`${u-1}px`,width:`${this.colsWidth(i-t.colLen()+1,i+Math.abs(a))-1}px`,height:`${f-1}px`}),l=["right",r,i+1,s,i+Math.abs(a)];else if(d>0){const e=this.rowsHeight(r-d,r-1);this.copyEl.styles({left:`${p-1}px`,top:`${u-e-1}px`,width:`${b-1}px`,height:`${e-1}px`}),l=["top",r-d,o,r-1,i]}else if(c>0){const e=this.colsWidth(o-c,o-1);this.copyEl.styles({left:`${p-e-1}px`,top:`${u-1}px`,width:`${e-1}px`,height:`${f-1}px`}),l=["left",r,o-c,s,o-1]}else this.copyEl.styles({left:`${p-1}px`,top:`${u-1}px`,width:`${b-1}px`,height:`${f-1}px`}),l=null}},e=>{if(this.copyEl.hide(),null!==l){const[t,s,i,n,r]=l;this.changeCopy(e,t,s,i,n,r)}})}reload(){this.setOffset()}setOffset(){if(void 0===this.startTarget)return;let{select:e}=this.ss;if(e){const[t,s]=e.start,[i,n]=e.stop;r(t,i,this.table.firsttds,e=>{e.deactive()}),r(s,n,this.table.ths,e=>{e.deactive()})}e=this.ss.buildSelect(this.startTarget,this.endTarget);const[t,s]=e.start,[i,n]=e.stop,o=this.rowsHeight(t,i,e=>e.active()),l=this.colsWidth(s,n,e=>e.active()),h=this.table.td(t,s);if(h){const{left:e,top:t}=h.offset();this._offset={left:e,top:t,width:l,height:o},this.topEl.styles({left:`${e-1}px`,top:`${t-1}px`,width:`${l+1}px`,height:"2px"}),this.rightEl.styles({left:`${e+l-1}px`,top:`${t-1}px`,width:"2px",height:`${o}px`}),this.bottomEl.styles({left:`${e-1}px`,top:`${t+o-1}px`,width:`${l}px`,height:"2px"}),this.leftEl.styles({left:`${e-1}px`,top:`${t-1}px`,width:"2px",height:`${o}px`}),this.areaEl.styles({left:`${e}px`,top:`${t}px`,width:`${l-2}px`,height:`${o-2}px`}),this.cornerEl.styles({left:`${e+l-5}px`,top:`${t+o-5}px`})}}rowsHeight(e,t,s=(e=>{})){let i=0;return r(e,t,this.table.firsttds,e=>{s(e),i+=parseInt(e.offset().height)}),i/=2}colsWidth(e,t,s=(e=>{})){let i=0;return r(e,t,this.table.ths,e=>{s(e),i+=parseInt(e.offset().width)}),i}};const r=(e,t,s,n)=>{for(let r=e;r<=t;r++){const e=s[r+""];e&&(e instanceof i.Element?n(e):e.forEach(e=>n(e)))}};t.DashedSelector=class{constructor(){this.el=i.h().class("spreadsheet-borders dashed").hide()}set(e){if(e._offset){const{left:t,top:s,width:i,height:n}=e._offset;this.el.style("left",`${t-2}px`).style("top",`${s-2}px`).style("width",`${i}px`).style("height",`${n}px`).show()}}hide(){this.el.hide()}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=s(4),r=s(2),o=s(1);t.Suggest=class extends i.Element{constructor(e,t){super(),this.list=e,this.width=t,this.filterList=[],this.currentIndex=0,this.target=null,this.evtTarget=null,this.itemClick=(e=>{}),this.class("spreadsheet-suggest").hide()}documentHandler(e){if(this.el.contains(e.target))return!1;this.hideAndRemoveEvents()}documentKeydownHandler(e){if(console.log("keyCode: ",e),!(this.filterList.length<=0&&"textarea"!==e.target.type)){switch(e.keyCode){case 37:e.returnValue=!1;break;case 38:this.filterList[this.currentIndex].deactive(),this.currentIndex--,this.currentIndex<0&&(this.currentIndex=this.filterList.length-1),this.filterList[this.currentIndex].active(),e.returnValue=!1,e.stopPropagation();break;case 39:e.returnValue=!1;break;case 40:this.filterList[this.currentIndex].deactive(),this.currentIndex++,this.currentIndex>this.filterList.length-1&&(this.currentIndex=0),this.filterList[this.currentIndex].active(),e.returnValue=!1;break;case 13:this.filterList[this.currentIndex].el.click(),e.returnValue=!1}e.stopPropagation()}}hideAndRemoveEvents(){this.hide(),this.removeEvents()}removeEvents(){null!==this.evtTarget&&(o.unbind("click",this.data("_outsidehandler"),this.evtTarget.el),o.unbind("keydown",this.data("_keydownhandler"),this.evtTarget.el))}clickItemHandler(e){this.itemClick(e),this.hideAndRemoveEvents()}search(e,t,s){this.removeEvents(),this.target=e,this.evtTarget=t;const{left:l,top:h,width:a,height:d}=e.offset();this.styles({left:`${l}px`,top:`${h+d+2}px`,width:`${this.width}px`});let c=this.list;/^\s*$/.test(s)||(c=this.list.filter(e=>e[0].startsWith(s.toUpperCase()))),c=c.map(e=>{const t=n.buildItem().on("click",t=>this.clickItemHandler(e)).child(e[0]);return e[1]&&t.child(i.h().class("label").html(e[1])),t}),this.filterList=c,this.currentIndex=0,c.length<=0?c=[n.buildItem().child("No Result")]:(c[0].active(),this.data("_outsidehandler",e=>{this.documentHandler(e)}),this.data("_keydownhandler",e=>this.documentKeydownHandler(e)),setTimeout(()=>{null!==this.evtTarget&&(o.bind("click",this.data("_outsidehandler"),this.evtTarget.el),o.bind("keydown",this.data("_keydownhandler"),this.evtTarget.el))},0)),this.html(""),this.child(r.buildMenu().children(c)).show()}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=s(16),r=s(5);t.Editor=class{constructor(e,t){this.defaultRowHeight=e,this.formulas=t,this.target=null,this.value=null,this.change=(e=>{});const s=t.map(e=>[e.key,e.title]);this.el=i.h().children([this.editor=i.h().class("spreadsheet-editor").children([this.textarea=i.h("textarea").on("keydown",e=>this.inputKeydown(e)).on("input",e=>this.inputChange(e)),this.textline=i.h().styles({visibility:"hidden",overflow:"hidden",position:"fixed",top:"0",left:"0"})]),this.suggest=new n.Suggest(s,180)]).hide(),this.el.on("keydown",e=>{13!==e.keyCode&&9!==e.keyCode&&e.stopPropagation()}),this.suggest.itemClick=(e=>{const t=`=${e[0]}()`;this.value&&(this.value.text=t),this.textarea.val(t),this.textline.html(t),this.setTextareaRange(t.length-1)})}onChange(e){this.change=e}set(e,t){this.target=e;const s=this.setValue(t);this.el.show(),this.setTextareaRange(s.length),this.reload()}setValue(e){if(this.setStyle(e),e){this.value=e;const t=e.text||"";return this.textarea.val(t),this.textline.html(t),t}return""}setStyle(e){let t={width:this.textarea.style("width"),height:this.textarea.style("height")};this.textarea.styles(Object.assign(t,r.getStyleFromCell(e)),!0)}clear(){this.el.hide(),this.target=null,this.value=null,this.textarea.val(""),this.textline.html("")}setTextareaRange(e){setTimeout(()=>{this.textarea.el.setSelectionRange(e,e),this.textarea.el.focus()},10)}inputKeydown(e){13===e.keyCode&&e.preventDefault()}inputChange(e){const t=e.target.value;this.value?this.value.text=t:this.value={text:t},this.change(this.value),this.autocomplete(t),this.textline.html(t),this.reload()}autocomplete(e){if("="===e[0])if(e.includes("("))this.suggest.hide();else{const t=e.substring(1);console.log(":::;search word:",t),this.suggest.search(this.editor,this.textarea,t)}else this.suggest.hide()}reload(){if(this.target){const{offsetTop:e,offsetLeft:t,offsetWidth:s,offsetHeight:i}=this.target;this.editor.styles({left:`${t-1}px`,top:`${e-1}px`}),this.textarea.styles({width:`${s-8}px`,height:`${i-2}px`});let n=this.textline.offset().width+16;if(this.value)if(this.value.wordWrap){const e=(parseInt(n/s+"")+(n%s>0?1:0))*this.defaultRowHeight;e>i&&this.textarea.style("height",`${e}px`)}else{const e=document.documentElement.clientWidth-t-24;if(n>s){if(n>e){const t=(parseInt(n/e+"")+(n%e>0?1:0))*this.defaultRowHeight;t>i?this.textarea.style("height",`${t}px`):this.textarea.style("height",`${i}px`),n=e}this.textarea.style("width",`${n}px`)}}this.el.show()}}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(0),n=s(17),r=s(15),o=s(14),l=s(13),h=s(5),a=s(8),d=s(7),c=s(1);t.Table=class{constructor(e,t){this.options=t,this.cols={},this.firsttds={},this.tds={},this.ths={},this.formulaCellIndexs=new Set,this.fixedLeftBody=null,this.editor=null,this.rowResizer=null,this.colResizer=null,this.contextmenu=null,this.state=null,this.currentIndexs=null,this.focusing=!1,this.change=(()=>{}),this.editorChange=(e=>{}),this.clickCell=((e,t,s)=>{}),this.ss=e,this.ss.change=(e=>{this.change(e)}),"read"!==t.mode&&(this.editor=new n.Editor(e.defaultRowHeight(),e.formulas),this.editor.change=(e=>this.editorChange(e))),"design"===t.mode&&(this.rowResizer=new o.Resizer(!1,(e,t)=>this.changeRowResizer(e,t)),this.colResizer=new o.Resizer(!0,(e,t)=>this.changeColResizer(e,t)),this.contextmenu=new l.ContextMenu(this)),this.selector=new r.Selector(this.ss,this),this.selector.change=(()=>this.selectorChange()),this.selector.changeCopy=((e,t,s,i,n,r)=>{this.selectorChangeCopy(e,t,s,i,n,r)}),this.dashedSelector=new r.DashedSelector,this.el=i.h().class("spreadsheet-table").children([this.colResizer&&this.colResizer.el||"",this.rowResizer&&this.rowResizer.el||"",this.buildFixedLeft(),this.header=this.buildHeader(),this.body=this.buildBody()]).on("contextmenu",e=>{e.returnValue=!1,e.preventDefault()}),c.bind("resize",e=>{this.header.style("width",`${this.options.width()}px`),this.body.style("width",`${this.options.width()}px`),"read"!==this.options.mode&&this.body.style("height",`${this.options.height()}px`)}),c.bind("click",e=>{this.focusing=this.el.parent().contains(e.target)}),c.bind("keydown",e=>{if(this.focusing&&this.focusing)if(e.ctrlKey&&"textarea"!==e.target.type&&"read"!==this.options.mode)67===e.keyCode&&(this.copy(),e.returnValue=!1),88===e.keyCode&&(this.cut(),e.returnValue=!1),86===e.keyCode&&(this.paste(),e.returnValue=!1);else{switch(e.keyCode){case 37:this.moveLeft(),e.returnValue=!1;break;case 38:this.moveUp(),e.returnValue=!1;break;case 39:this.moveRight(),e.returnValue=!1;break;case 40:this.moveDown(),e.returnValue=!1;break;case 9:this.moveRight(),e.returnValue=!1;break;case 13:this.moveDown(),e.returnValue=!1}"read"!==this.options.mode&&(e.keyCode>=65&&e.keyCode<=90||e.keyCode>=48&&e.keyCode<=57||e.keyCode>=96&&e.keyCode<=105||187==e.keyCode)&&"textarea"!==e.target.type&&this.ss.cellText(e.key,(e,t,s)=>{if(this.editor){const i=this.td(e,t);i.html(this.renderCell(e,t,s)),this.editor.set(i.el,this.ss.currentCell())}})}})}reload(){this.firsttds={},this.el.html(""),this.el.children([this.colResizer&&this.colResizer.el||"",this.rowResizer&&this.rowResizer.el||"",this.buildFixedLeft(),this.header=this.buildHeader(),this.body=this.buildBody()])}moveLeft(){this.currentIndexs&&this.currentIndexs[1]>0&&(this.currentIndexs[1]-=1,this.moveSelector("left"))}moveUp(){this.currentIndexs&&this.currentIndexs[0]>0&&(this.currentIndexs[0]-=1,this.moveSelector("up"))}moveDown(){this.currentIndexs&&this.currentIndexs[0]0&&"right"===e&&(this.body.el.scrollLeft=d+15),"left"===e&&this.body.el.scrollLeft+60>o&&(this.body.el.scrollLeft-=this.body.el.scrollLeft+60-o),"up"===e&&this.body.el.scrollTop>l&&(this.body.el.scrollTop-=this.body.el.scrollTop-l),"down"===e&&l+a-r>0&&(this.body.el.scrollTop=l+a-r+15),this.mousedownCell(t,s)}}}setValueWithText(e){this.currentIndexs&&this.ss.cellText(e.text,(e,t,s)=>{this.td(e,t).html(this.renderCell(e,t,s))}),this.editor&&this.editor.setValue(e)}setTdWithCell(e,t,s,i=!0){this.setTdStyles(e,t,s),this.setRowHeight(e,t,i),this.td(e,t).html(this.renderCell(e,t,s))}setCellAttr(e,t){this.ss.cellAttr(e,t,(s,i,n)=>{this.setTdWithCell(s,i,n,"wordWrap"===e&&t)}),this.editor&&this.editor.setStyle(this.ss.currentCell())}undo(){return this.ss.undo((e,t,s)=>{this.setTdStylesAndAttrsAndText(e,t,s)})}redo(){return this.ss.redo((e,t,s)=>{this.setTdStylesAndAttrsAndText(e,t,s)})}setTdStylesAndAttrsAndText(e,t,s){let i=this.td(e,t);this.setTdStyles(e,t,s),this.setTdAttrs(e,t,s),i.html(this.renderCell(e,t,s))}copy(){this.ss.copy(),this.dashedSelector.set(this.selector),this.state="copy"}cut(){this.ss.cut(),this.dashedSelector.set(this.selector),this.state="cut"}copyformat(){this.ss.copy(),this.dashedSelector.set(this.selector),this.state="copyformat"}paste(){null!==this.state&&this.ss.select&&(this.ss.paste((e,t,s)=>{let i=this.td(e,t);this.setTdStyles(e,t,s),this.setTdAttrs(e,t,s),"cut"!==this.state&&"copy"!==this.state||i.html(this.renderCell(e,t,s))},this.state,(e,t,s)=>{let i=this.td(e,t);this.setTdStyles(e,t,s),this.setTdAttrs(e,t,s),i.html("")}),this.selector.reload()),"copyformat"===this.state?this.state=null:"cut"===this.state?this.state=null:this.state,this.dashedSelector.hide()}clearformat(){this.ss.clearformat((e,t,s)=>{this.td(e,t).removeAttr("rowspan").removeAttr("colspan").styles({},!0).show(!0)})}merge(){this.ss.merge((e,t,s)=>{this.setTdAttrs(e,t,s).show(!0)},(e,t,s)=>{this.setTdAttrs(e,t,s).show(!0)},(e,t,s)=>{let i=this.td(e,t);s.invisible?i.hide():i.show(!0)})}insert(e,t){this.ss.insert(e,t,(e,t,s)=>{this.setTdStylesAndAttrsAndText(e,t,s)})}td(e,t){return this.tds[`${e}_${t}`]}selectorChange(){"copyformat"===this.state&&this.paste()}selectorChangeCopy(e,t,s,i,n,r){this.ss.batchPaste(t,s,i,n,r,e.ctrlKey,(e,t,s)=>{this.setTdStyles(e,t,s),this.setTdAttrs(e,t,s),this.td(e,t).html(this.renderCell(e,t,s))})}renderCell(e,t,s){if(s){const i=`${e}_${t}`;return s.text&&"="===s.text[0]?this.formulaCellIndexs.add(i):(this.formulaCellIndexs.has(i)&&this.formulaCellIndexs.delete(i),this.reRenderFormulaCells()),a.formatRenderHtml(s.format,this._renderCell(s))}return""}_renderCell(e){if(e){let t=e.text||"";return d.formulaRender(t,(e,t)=>this._renderCell(this.ss.getCell(e,t)))}return""}reRenderFormulaCells(){this.formulaCellIndexs.forEach(e=>{let t=e.split("_");const s=parseInt(t[0]),i=parseInt(t[1]),n=this.renderCell(s,i,this.ss.getCell(s,i));this.td(s,i).html(n)})}setRowHeight(e,t,s){if(!1===s)return;this.ss.cols();const i=this.td(e,t);let n=i.offset().height;console.log("h:",n);const r=i.attr("rowspan");if(r)for(let t=1;te.attr("height",t)),this.selector.reload(),this.editor&&this.editor.reload()}changeRowResizer(e,t){const s=this.ss.row(e).height+t;this.changeRowHeight(e,s)}changeColResizer(e,t){const s=this.ss.col(e).width+t;if(s<=50)return;this.ss.col(e,s);const i=this.cols[e+""];i&&i.forEach(e=>e.attr("width",s)),this.selector.reload(),this.editor&&this.editor.reload()}buildColGroup(e){const t=this.ss.cols();return i.h("colgroup").children([i.h("col").attr("width","60"),...t.map((e,t)=>{let s=i.h("col").attr("width",e.width);return this.cols[t+""]=this.cols[t+""]||[],this.cols[t+""].push(s),s}),i.h("col").attr("width",e)])}buildFixedLeft(){const e=this.ss.rows("read"===this.options.mode);return i.h().class("spreadsheet-fixed").style("width","60px").children([i.h().class("spreadsheet-fixed-header").child(i.h("table").child(i.h("thead").child(i.h("tr").child(i.h("th").child("-"))))),this.fixedLeftBody=i.h().class("spreadsheet-fixed-body").style("height",`${"read"===this.options.mode?"auto":this.options.height()-18}px`).children([i.h("table").child(i.h("tbody").children(e.map((e,t)=>{let s=i.h("td").attr("height",`${e.height}`).child(`${t+1}`).on("mouseover",e=>this.rowResizer&&this.rowResizer.set(e.target,t,this.body.el.scrollTop));return this.firsttdsPush(t,s),i.h("tr").child(s)})))])])}buildHeader(){const e=this.ss.cols(),t=i.h("thead").child(i.h("tr").children([i.h("th"),...e.map((e,t)=>{let s=i.h("th").child(e.title).on("mouseover",e=>{console.log(e),this.colResizer&&this.colResizer.set(e.target,t,this.body.el.scrollLeft)});return this.ths[t+""]=s,s}),i.h("th")]));return i.h().class("spreadsheet-header").style("width",`${this.options.width()}px`).children([i.h("table").children([this.buildColGroup(15),t])])}mousedownCell(e,t){if(this.editor){const e=this.editor.value;if(this.currentIndexs&&this.editor.target&&e){const t=this.ss.cellText(e.text,(e,t,s)=>{this.td(e,t).html(this.renderCell(e,t,s))});t&&t.wordWrap&&this.setRowHeight(this.currentIndexs[0],this.currentIndexs[1],!0)}this.editor.clear()}this.currentIndexs=[e,t];const s=this.ss.currentCell([e,t]);this.clickCell(e,t,s)}editCell(e,t){const s=this.td(e,t);this.editor&&this.editor.set(s.el,this.ss.currentCell())}buildBody(){const e=this.ss.rows("read"===this.options.mode),t=this.ss.cols(),s=(e,t,s)=>{const{select:i}=this.ss;2===s.button&&(console.log(":::evt:",s),this.contextmenu&&this.contextmenu.set(s),i&&i.contains(e,t))||(this.selector.mousedown(s),this.mousedownCell(e,t),this.focusing=!0)},n=(e,t)=>{this.editCell(e,t)},r=i.h("tbody").children(e.map((e,r)=>{let o=i.h("td").attr("height",`${e.height}`).child(`${r+1}`);return this.firsttdsPush(r,o),i.h("tr").children([o,...t.map((e,t)=>{let o=this.ss.getCell(r,t),l=i.h("td").child(this.renderCell(r,t,o)).attr("type","cell").attr("row-index",r+"").attr("col-index",t+"").attr("rowspan",o&&o.rowspan||1).attr("colspan",o&&o.colspan||1).styles(h.getStyleFromCell(o),!0).on("mousedown",e=>s(r,t,e)).on("dblclick",n.bind(null,r,t));return this.tds[`${r}_${t}`]=l,l}),i.h("td")])}));return i.h().class("spreadsheet-body").on("scroll",e=>{this.header.el.scrollLeft=e.target.scrollLeft,this.fixedLeftBody&&(this.fixedLeftBody.el.scrollTop=e.target.scrollTop)}).style("height",`${"read"===this.options.mode?"auto":this.options.height()}px`).style("width",`${this.options.width()}px`).children([i.h("table").children([this.buildColGroup(0),r]),this.editor&&this.editor.el||"",this.selector.el,this.contextmenu&&this.contextmenu.el||"",this.dashedSelector.el])}addRow(e=1){}firsttdsPush(e,t){this.firsttds[`${e}`]=this.firsttds[`${e}`]||[],this.firsttds[`${e}`].push(t)}}},function(e,t){e.exports=function(e){var t="undefined"!=typeof window&&window.location;if(!t)throw new Error("fixUrls requires window.location");if(!e||"string"!=typeof e)return e;var s=t.protocol+"//"+t.host,i=s+t.pathname.replace(/\/[^\/]*$/,"/");return e.replace(/url\s*\(((?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gi,function(e,t){var n,r=t.trim().replace(/^"(.*)"$/,function(e,t){return t}).replace(/^'(.*)'$/,function(e,t){return t});return/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/|\s*$)/i.test(r)?e:(n=0===r.indexOf("//")?r:0===r.indexOf("/")?s+r:i+r.replace(/^\.\//,""),"url("+JSON.stringify(n)+")")})}},function(e,t,s){var i,n,r={},o=(i=function(){return window&&document&&document.all&&!window.atob},function(){return void 0===n&&(n=i.apply(this,arguments)),n}),l=function(e){var t={};return function(e){if("function"==typeof e)return e();if(void 0===t[e]){var s=function(e){return document.querySelector(e)}.call(this,e);if(window.HTMLIFrameElement&&s instanceof window.HTMLIFrameElement)try{s=s.contentDocument.head}catch(e){s=null}t[e]=s}return t[e]}}(),h=null,a=0,d=[],c=s(19);function p(e,t){for(var s=0;s=0&&d.splice(t,1)}function g(e){var t=document.createElement("style");return e.attrs.type="text/css",m(t,e.attrs),f(e,t),t}function m(e,t){Object.keys(t).forEach(function(s){e.setAttribute(s,t[s])})}function x(e,t){var s,i,n,r;if(t.transform&&e.css){if(!(r=t.transform(e.css)))return function(){};e.css=r}if(t.singleton){var o=a++;s=h||(h=g(t)),i=v.bind(null,s,o,!1),n=v.bind(null,s,o,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(s=function(e){var t=document.createElement("link");return e.attrs.type="text/css",e.attrs.rel="stylesheet",m(t,e.attrs),f(e,t),t}(t),i=function(e,t,s){var i=s.css,n=s.sourceMap,r=void 0===t.convertToAbsoluteUrls&&n;(t.convertToAbsoluteUrls||r)&&(i=c(i));n&&(i+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(n))))+" */");var o=new Blob([i],{type:"text/css"}),l=e.href;e.href=URL.createObjectURL(o),l&&URL.revokeObjectURL(l)}.bind(null,s,t),n=function(){b(s),s.href&&URL.revokeObjectURL(s.href)}):(s=g(t),i=function(e,t){var s=t.css,i=t.media;i&&e.setAttribute("media",i);if(e.styleSheet)e.styleSheet.cssText=s;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(s))}}.bind(null,s),n=function(){b(s)});return i(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;i(e=t)}else n()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=o()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var s=u(e,t);return p(s,t),function(e){for(var i=[],n=0;n .spreadsheet-menu > .spreadsheet-item {\n margin: 7px 1px 0;\n}\n.spreadsheet-bars .spreadsheet-editor-bar {\n width: 100%;\n height: 26px;\n line-height: 26px;\n position: relative;\n background: #fff;\n padding: 0;\n}\n.spreadsheet-formula-bar {\n position: relative;\n height: 100%;\n}\n.spreadsheet-formula-bar .spreadsheet-formula-label {\n width: 60px;\n height: 100%;\n box-sizing: border-box;\n display: inline-block;\n text-align: center;\n border-right: 1px solid #e0e2e4;\n background-color: #fff;\n font-size: 12px;\n color: #777;\n user-select: none;\n float: left;\n vertical-align: middle;\n}\n.spreadsheet-formula-bar textarea {\n width: calc(100% - 60px);\n height: 100%;\n box-sizing: border-box;\n font-family: inherit;\n font-size: inherit;\n padding: 4px 10px;\n position: relative;\n float: left;\n resize: none;\n overflow-y: hidden;\n border: none;\n outline-width: 0;\n margin: 0;\n line-height: 1.2rem;\n}\n.spreadsheet-formula-bar-resizer {\n cursor: ns-resize;\n height: 4px;\n position: absolute;\n width: 100%;\n left: 0;\n bottom: 0;\n}\n.spreadsheet-menu.vertical > .spreadsheet-item-separator {\n background: #e0e2e4;\n height: 1px;\n margin: 5px 0;\n}\n.spreadsheet-menu.vertical > .spreadsheet-item {\n padding: 2px 10px;\n border-radius: 0;\n}\n.spreadsheet-menu.horizontal > .spreadsheet-item {\n display: inline-block;\n}\n.spreadsheet-menu.horizontal > .spreadsheet-item-separator {\n display: inline-block;\n background: #e0e2e4;\n width: 1px;\n vertical-align: middle;\n height: 18px;\n margin: 0 3px;\n}\n.spreadsheet-menu > .spreadsheet-item {\n border-radius: 2px;\n user-select: none;\n background: 0;\n border: 1px solid transparent;\n outline: none;\n height: 24px;\n color: rgba(0, 0, 0, 0.8);\n line-height: 24px;\n list-style: none;\n cursor: default;\n}\n.spreadsheet-menu > .spreadsheet-item.disabled {\n pointer-events: none;\n opacity: 0.5;\n}\n.spreadsheet-menu > .spreadsheet-item:not(.separator):hover,\n.spreadsheet-menu > .spreadsheet-item.active {\n background: rgba(0, 0, 0, 0.08);\n}\n.spreadsheet-menu > .spreadsheet-item:not(.separator):hover .spreadsheet-icon-img,\n.spreadsheet-menu > .spreadsheet-item.active .spreadsheet-icon-img {\n opacity: 0.7;\n}\n.spreadsheet-menu > .spreadsheet-item:not(.separator):hover .spreadsheet-dropdown-icon,\n.spreadsheet-menu > .spreadsheet-item.active .spreadsheet-dropdown-icon {\n background-color: rgba(0, 0, 0, 0.15);\n opacity: 0.6;\n}\n.spreadsheet-menu > .spreadsheet-item > .label {\n float: right;\n opacity: .8;\n}\n.spreadsheet-dropdown {\n position: relative;\n display: inline-block;\n width: auto!important;\n}\n.spreadsheet-dropdown .spreadsheet-dropdown-content {\n position: absolute;\n top: calc(100% + 5px);\n left: 0;\n z-index: 200;\n background: #fff;\n box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);\n width: auto;\n}\n.spreadsheet-dropdown .spreadsheet-dropdown-header .spreadsheet-dropdown-title {\n padding: 0 5px;\n display: inline-block;\n}\n.spreadsheet-dropdown .spreadsheet-dropdown-header .spreadsheet-dropdown-icon {\n display: inline-block;\n vertical-align: top;\n}\n.spreadsheet-dropdown .spreadsheet-dropdown-header .spreadsheet-dropdown-icon .spreadsheet-icon {\n width: 10px;\n}\n.spreadsheet-dropdown .spreadsheet-dropdown-header .spreadsheet-dropdown-icon .spreadsheet-icon .arrow-down {\n left: -130px;\n}\n.spreadsheet-color-panel {\n padding: 10px;\n width: 100%;\n}\n.spreadsheet-color-panel table {\n border-collapse: separate;\n border-spacing: 0;\n border: none;\n}\n.spreadsheet-color-panel table tr td {\n padding: 0;\n border: none;\n}\n.spreadsheet-color-panel .color-cell {\n width: 20px;\n height: 20px;\n margin: 3px;\n}\n.spreadsheet-color-panel .color-cell:hover {\n box-shadow: 0 0 2px rgba(0, 0, 0, 0.8);\n}\n.spreadsheet-icon {\n height: 18px;\n width: 18px;\n margin: 0px 4px 3px 3px;\n direction: ltr;\n text-align: left;\n user-select: none;\n vertical-align: middle;\n overflow: hidden;\n position: relative;\n display: inline-block;\n}\n.spreadsheet-icon-img {\n background-image: url("+i(s(21))+");\n position: absolute;\n width: 262px;\n height: 444px;\n opacity: 0.55;\n}\n.spreadsheet-icon-img.undo {\n left: 0;\n top: 0;\n}\n.spreadsheet-icon-img.redo {\n left: -18px;\n top: 0;\n}\n.spreadsheet-icon-img.print {\n left: -36px;\n top: 0;\n}\n.spreadsheet-icon-img.paintformat {\n left: -54px;\n top: 0;\n}\n.spreadsheet-icon-img.clearformat {\n left: -72px;\n top: 0;\n}\n.spreadsheet-icon-img.bold {\n left: -90px;\n top: 0;\n}\n.spreadsheet-icon-img.italic {\n left: -108px;\n top: 0;\n}\n.spreadsheet-icon-img.underline {\n left: -126px;\n top: 0;\n}\n.spreadsheet-icon-img.strikethrough {\n left: -144px;\n top: 0;\n}\n.spreadsheet-icon-img.text-color {\n left: -162px;\n top: 0;\n}\n.spreadsheet-icon-img.cell-color {\n left: -180px;\n top: 0;\n}\n.spreadsheet-icon-img.merge {\n left: -198px;\n top: 0;\n}\n.spreadsheet-icon-img.align-left {\n left: -216px;\n top: 0;\n}\n.spreadsheet-icon-img.align-center {\n left: -234px;\n top: 0;\n}\n.spreadsheet-icon-img.align-right {\n left: 0;\n top: -18px;\n}\n.spreadsheet-icon-img.valign-top {\n left: -18px;\n top: -18px;\n}\n.spreadsheet-icon-img.valign-middle {\n left: -36px;\n top: -18px;\n}\n.spreadsheet-icon-img.valign-bottom {\n left: -54px;\n top: -18px;\n}\n.spreadsheet-icon-img.textwrap {\n left: -72px;\n top: -18px;\n}\n.spreadsheet-icon-img.autofilter {\n left: -90px;\n top: -18px;\n}\n.spreadsheet-icon-img.formula {\n left: -108px;\n top: -18px;\n}\n.spreadsheet-icon-img.arrow-down {\n left: -126px;\n top: -18px;\n}\n.spreadsheet-icon-img.arrow-right {\n left: -144px;\n top: -18px;\n}\n",""])},function(e,t,s){var i=s(24);"string"==typeof i&&(i=[[e.i,i,""]]);var n={hmr:!0,transform:void 0,insertInto:void 0};s(20)(i,n);i.locals&&(e.exports=i.locals)},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.Select=class{constructor(e,t,s){this.start=e,this.stop=t,this.canMerge=s}forEach(e){const[t,s]=this.start,[i,n]=this.stop;for(let r=t;r<=i;r++)for(let o=s;o<=n;o++)e(r,o,r-t,o-s,i-t+1,n-s+1)}rowIndex(e){return this.start[0]+e%this.rowLen()}colIndex(e){return this.start[1]+e%this.colLen()}rowLen(){return this.stop[0]-this.start[0]+1}colLen(){return this.stop[1]-this.start[1]+1}cellLen(){return this.rowLen()*this.colLen()}contains(e,t){const[s,i]=this.start,[n,r]=this.stop;return s<=e&&n>=e&&i<=t&&r>=t}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.fonts=[{key:"Microsoft YaHei",title:"微软雅黑"},{key:"STFangsong",title:"华文仿宋"},{key:"Comic Sans MS",title:"Comic Sans MS"},{key:"Arial",title:"Arial"},{key:"Courier New",title:"Courier New"},{key:"Verdana",title:"Verdana"}]},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(8),n=s(27),r=s(7),o=s(5),l=s(6),h=s(26);class a{constructor(e){this.type=e,this.values=[]}add(e,t,s){this.values.push([e,t,s])}}t.History=a;t.Spreadsheet=class{constructor(e={}){if(this.histories=[],this.histories2=[],this.currentCellIndexes=[0,0],this.select=null,this.copySelect=null,this.cutSelect=null,this.change=(()=>{}),this.formats=e.formats||i.formats,this.fonts=e.fonts||n.fonts,this.formulas=e.formulas||r.formulas,this.data={rowHeight:22,colWidth:100,cell:o.defaultCell},e.data){const{data:t}=e;for(let e of["rowHeight","colWidth","rows","cols","cells"])t[e]&&(this.data[e]=t[e]);Object.assign(this.data.cell,t.cell||{})}}buildSelect(e,t){const s=f(e),i=f(t);let n=s.row,r=s.col,o=i.row,l=i.col;n>o&&(n=i.row,o=s.row),r>l&&(r=i.col,l=s.col);let[a,d]=g((e,t)=>this.getCell(e,t),n,o,r,l),[c,p]=b((e,t)=>this.getCell(e,t),a,d,r,l);for(;;){const[e,t]=g((e,t)=>this.getCell(e,t),a,d,c,p);let[s,i]=b((e,t)=>this.getCell(e,t),a,d,c,p);if(a===e&&d===t&&c===s&&p===i)break;a=e,d=t,c=s,p=i}const u=this.getCell(a,c);let m=a+(u&&u.rowspan||1)-1===d&&c+(u&&u.colspan||1)-1===p;return this.select=new h.Select([a,c],[d,p],!m),this.select}defaultRowHeight(){return this.data.rowHeight||22}defaultColWidth(){return this.data.colWidth||100}copy(){this.copySelect=this.select}cut(){this.cutSelect=this.select}paste(e,t,s){let i=this.copySelect;if(this.cutSelect&&(i=this.cutSelect,this.cutSelect=null),i&&this.select){const n=new a("cells");"copyformat"===t?this.select.forEach((r,o,l,h,a,d)=>{if(i){const a=i.rowIndex(l),d=i.colIndex(h),[c,p]=this.copyCell(a,d,r,o,t,e,s);n.add([r,o],c,p)}}):i.forEach((i,r,o,l,h,a)=>{if(this.select){const h=this.select.start[0]+o,a=this.select.start[1]+l,[d,c]=this.copyCell(i,r,h,a,t,e,s);n.add([h,a],d,c)}}),this.histories.push(n),this.change(this.data)}}insert(e,t,s){if(this.select){const{cells:t}=this.data,[i,n]=this.select.start;if(!t)return;const r=new a("cells");if("row"===e){const e={};Object.keys(t).forEach(n=>{let o=parseInt(n),l=t[o];i<=o&&Object.keys(l).forEach(e=>{let t=parseInt(e);s(o,t,{}),r.add([o,t],l[t],void 0),s(o+1,t,l[t]||{}),r.add([o+1,t],this.getCell(o+1,t),l[t])}),e[i<=o?o+1:o]=t[o]}),this.data.cells=e}else"col"===e&&Object.keys(t).forEach(e=>{let i=parseInt(e),o=t[i],l={};Object.keys(o).forEach(e=>{let t=parseInt(e);n<=t&&(s(i,t,{}),r.add([i,t],o[t],void 0),s(i,t+1,o[t]||{}),r.add([i,t+1],this.getCell(i,t+1),o[t])),l[n<=t?t+1:t]=o[t]}),t[i]=l});this.histories.push(r)}}batchPaste(e,t,s,i,n,r,o){if(this.select){const e=new a("cells");for(let l=t;l<=i;l++)for(let i=s;i<=n;i++){const n=this.select.rowIndex(l-t),h=this.select.colIndex(i-s),[a,d]=this.copyCell(n,h,l,i,r?"seqCopy":"copy",o,()=>{});e.add([l,i],a,d)}this.histories.push(e),this.change(this.data)}}copyCell(e,t,s,i,n,o,l){const h=this.getCell(e,t),a=s-e,d=i-t;if(h){let c=this.getCell(s,i);const p=Object.assign({},h);if(h.merge){const[e,t]=h.merge;p.merge=[e+a,t+d]}if("cut"===n&&l(e,t,this.cell(e,t,{})),"copyformat"===n)c&&c.text&&(p.text=c.text);else{const o=p.text;o&&!/^\s*$/.test(o)&&(/^\d*$/.test(o)&&"seqCopy"===n?p.text=parseInt(o)+(s-e)+(i-t)+"":-1!==o.indexOf("=")&&(p.text=r.formulaReplaceParam(o,a,d)))}return o(s,i,this.cell(s,i,p)),[c,p]}return[null,null]}isRedo(){return this.histories2.length>0}redo(e){const{histories:t,histories2:s}=this;if(s.length>0){const i=s.pop();i&&(this.resetByHistory(i,e,"redo"),t.push(i),this.change(this.data))}return this.isRedo()}isUndo(){return this.histories.length>0}undo(e){const{histories:t,histories2:s}=this;if(t.length>0){const i=t.pop();i&&(this.resetByHistory(i,e,"undo"),s.push(i),this.change(this.data))}return this.isUndo()}resetByHistory(e,t,s){e.values.forEach(([i,n,r])=>{if("cells"===e.type){const e="undo"===s?n:r,o=this.getCell(i[0],i[1]);if(o)if(3===i.length){const s={};s[i[2]]=e,t(i[0],i[1],e?this.cell(i[0],i[1],s,!0):this.cell(i[0],i[1],p(o,i[2])))}else t(i[0],i[1],this.cell(i[0],i[1],e||{}));else if(3===i.length){if(e){const s={};s[i[2]]=e,t(i[0],i[1],this.cell(i[0],i[1],s))}}else t(i[0],i[1],this.cell(i[0],i[1],e||{}))}})}clearformat(e){const{select:t}=this;if(null!==t){const s=new a("cells");t.forEach((t,i,n,r,o,l)=>{let h=this.getCell(t,i);h&&(s.add([t,i],h,{text:h.text}),h=this.cell(t,i,{text:h.text}),e(t,i,h))}),this.histories.push(s),this.change(this.data)}}merge(e,t,s){const{select:i}=this;if(null!==i&&i.cellLen()>1){const n=new a("cells");let r=0,o=[0,0];i.forEach((l,h,a,d,c,u)=>{if(0==r++){o=[l,h];let s={};if(c>1&&(s.rowspan=c),u>1&&(s.colspan=u),i.canMerge){n.add([l,h,"rowspan"],void 0,c),n.add([l,h,"colspan"],void 0,u);let t=this.cell(l,h,s,!0);e(l,h,t)}else{const e=this.getCell(l,h);if(null!==e){n.add([l,h,"rowspan"],e.rowspan,void 0),n.add([l,h,"colspan"],e.colspan,void 0);let s=this.cell(l,h,p(e,"rowspan","colspan","merge"));t(l,h,s)}}}else{let e={invisible:i.canMerge};if(i.canMerge){n.add([l,h,"invisible"],void 0,i.canMerge),e.merge=o;let t=this.cell(l,h,e,!0);s(l,h,t)}else{const e=this.getCell(l,h);if(null!==e){n.add([l,h,"invisible"],e.invisible,void 0);let t=this.cell(l,h,p(e,"rowspan","colspan","merge","invisible"));s(l,h,t)}}}}),this.histories.push(n),i.canMerge=!i.canMerge,this.change(this.data)}}cellAttr(e,t,s){let i={};i[e]=t;const n=t===this.data.cell[e];if(null!==this.select){const r=new a("cells");this.select.forEach((o,l)=>{const h=this.getCell(o,l);r.add([o,l,e],null!==h?h[e]:void 0,t);let a=this.cell(o,l,n?p(h,e):i,!n);s(o,l,a)}),this.histories.push(r)}this.change(this.data)}cellText(e,t){if(this.currentCellIndexes){const s=new a("cells"),[i,n]=this.currentCellIndexes,r=this.getCell(i,n);s.add([i,n,"text"],null!==r?r.text:void 0,e);const o=this.cell(i,n,{text:e},!0);return t(i,n,o),this.histories.push(s),this.change(this.data),o}return null}currentCell(e){void 0!==e&&(this.currentCellIndexes=e);const[t,s]=this.currentCellIndexes;return this.getCell(t,s)}cell(e,t,s,i=!1){return this.data.cells=this.data.cells||{},this.data.cells[e]=this.data.cells[e]||{},this.data.cells[e][t]=this.data.cells[e][t]||{},i?Object.assign(this.data.cells[e][t],s):s&&(this.data.cells[e][t]=s),this.data.cells[e][t]}getCell(e,t){return this.data.cells&&this.data.cells[e]&&this.data.cells[e][t]?this.data.cells[e][t]:null}getFont(e){return this.fonts.filter(t=>t.key===e)[0]}getFormat(e){return this.formats.filter(t=>t.key===e)[0]}row(e,t){const{data:s}=this;if(void 0!==t){const i=new a("rows");s.rows=s.rows||{},s.rows[e]=s.rows[e]||{},s.rows[e].height=t,i.add([e],null,s.rows[e]),this.histories.push(i)}return Object.assign({height:s.rowHeight},s.rows?s.rows[e]:{})}rows(e){const{data:t}=this;let s;return e?(s=10,this.data.cells&&(s=d(this.data.cells)+2)):s=c(100,t.rows),u(s,e=>this.row(e))}col(e,t){const{data:s}=this;if(void 0!==t){const i=new a("cols");s.cols=s.cols||{},s.cols[e]=s.cols[e]||{},s.cols[e].width=t,i.add([e],null,s.cols[e]),this.histories.push(i)}const i={width:s.colWidth,title:l.alphabet(e)};if(s.cols&&s.cols[e])for(let t in s.cols[e]){const n=s.cols[e];n[t]&&(i[t]=n[t])}return i}cols(){const{data:e}=this;let t=c(52,e.cols);return u(t,e=>this.col(e))}};const d=function(e){return Math.max(...Object.keys(e).map(e=>parseInt(e)))},c=function(e,t){if(t){const s=d(t);if(s>e)return s}return e},p=function(e,...t){const s={};return e&&Object.keys(e).forEach(i=>{-1===t.indexOf(i)&&(s[i]=e[i])}),s},u=function(e,t){const s=[];for(let i=0;i{const{offsetTop:t,offsetLeft:s,offsetHeight:i,offsetWidth:n}=e;return{row:parseInt(e.getAttribute("row-index")),col:parseInt(e.getAttribute("col-index")),rowspan:parseInt(e.getAttribute("rowspan")),colspan:parseInt(e.getAttribute("colspan")),left:s,top:t,width:n,height:i}},b=(e,t,s,i,n)=>{let r=i,o=n;for(let n=t;n<=s;n++){let t=i,s=e(n,t);s&&s.merge&&(t+=s.merge[1]-t),t1)t+=parseInt(l);else if(s&&s.merge){const[i,n]=s.merge;t+=e(i,n).colspan+(n-t)}t-1>o&&(o=t-1)}return[r,o]},g=(e,t,s,i,n)=>{let r=t,o=s;for(let s=i;s<=n;s++){let i=t,n=e(i,s);n&&n.merge&&(i+=n.merge[0]-i),i1)i+=parseInt(l);else if(n&&n.merge){const[t,s]=n.merge;i+=e(t,s).rowspan+(t-i)}i-1>o&&(o=i-1)}return[r,o]}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(28);s(25);const n=s(18),r=s(12),o=s(9),l=s(0);t.LocalSpreadsheet=class{constructor(e,t={}){this.refs={},this.toolbar=null,this.editorbar=null,this._change=(()=>{}),this.bindEl=e,this.options=Object.assign({mode:"design"},t),this.bindEl&&(this.bindEl.innerHTML=""),this.ss=new i.Spreadsheet(t),"design"===this.options.mode&&(this.editorbar=new o.Editorbar,this.editorbar.change=(e=>this.editorbarChange(e)),this.toolbar=new r.Toolbar(this.ss),this.toolbar.change=((e,t)=>this.toolbarChange(e,t)),this.toolbar.undo=(()=>this.table.undo()),this.toolbar.redo=(()=>this.table.redo())),this.table=new n.Table(this.ss,Object.assign({height:()=>this.options.height?this.options.height():document.documentElement.clientHeight-24-41-26,width:()=>this.bindEl.offsetWidth,mode:this.options.mode})),this.table.change=(e=>{this.toolbar&&this.toolbar.setRedoAble(this.ss.isRedo()),this.toolbar&&this.toolbar.setUndoAble(this.ss.isUndo()),this._change(e)}),this.table.editorChange=(e=>this.editorChange(e)),this.table.clickCell=((e,t,s)=>this.clickCell(e,t,s)),this.render()}loadData(e){return setTimeout(()=>{this.ss.data=e,this.table.reload()},1),this}change(e){return this._change=e,this}render(){this.bindEl.appendChild(l.h().class("spreadsheet").children([l.h().class("spreadsheet-bars").children([this.toolbar&&this.toolbar.el||"",this.editorbar&&this.editorbar.el||""]),this.table.el]).el)}toolbarChange(e,t){"merge"!==e?"clearformat"!==e?"paintformat"!==e?this.table.setCellAttr(e,t):this.table.copyformat():this.table.clearformat():this.table.merge()}editorbarChange(e){this.table.setValueWithText(e)}editorChange(e){this.editorbar&&this.editorbar.setValue(e)}clickCell(e,t,s){const i=this.ss.cols();this.editorbar&&this.editorbar.set(`${i[t].title}${e+1}`,s),this.toolbar&&this.toolbar.set(this.table.td(e,t),s)}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=s(29);function n(e,t){return new i.LocalSpreadsheet(e,t)}t.default=n,window.xspreadsheet=n}]); //# sourceMappingURL=xspreadsheet.js.map ================================================ FILE: index.html ================================================ TypeScript with VSCode
xxxxxx
================================================ FILE: package.json ================================================ { "name": "xspreadsheet", "version": "1.0.4", "description": "a javascript spreadsheet", "author": "myliang ", "private": false, "main": "src/main.ts", "types": "src/main.d.ts", "repository": { "type": "git", "url": "https://github.com/myliang/spreadsheet.git" }, "scripts": { "dev": "webpack-dev-server --color --inline --hot --config webpack.config.dev.js --open", "build": "webpack --config webpack.config.js --progress --color" }, "keywords": [ "excel", "js", "component", "ui", "spreadsheet" ], "license": "MIT", "devDependencies": { "awesome-typescript-loader": "^5.2.0", "css-loader": "^0.28.11", "extract-text-webpack-plugin": "^4.0.0-beta.0", "file-loader": "^1.1.11", "less": "^3.0.1", "less-loader": "^4.1.0", "source-map-loader": "^0.2.3", "style-loader": "^0.20.3", "tslint-eslint-rules": "^5.2.0", "typescript": "^2.9.2", "webpack": "^4.5.0", "webpack-cli": "^2.0.14", "webpack-dev-server": "^3.1.3" } } ================================================ FILE: src/core/alphabet.d.ts ================================================ export declare function alphabet(index: number): string; export declare function alphabetIndex(key: string): number; ================================================ FILE: src/core/alphabet.ts ================================================ const _alphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'] export function alphabet(index: number): string { const [a, b] = [parseInt(index / _alphabet.length + ''), index % _alphabet.length] // console.log('a: ', a, '; b: ', b) return a > 0 ? `${_alphabet[a - 1]}${_alphabet[b]}` : _alphabet[b] } export function alphabetIndex (key: string): number { let ret = 0; for (let i = 0; i < key.length; i++) { // console.log(key.charCodeAt(i), key[i]) let cindex = key.charCodeAt(i) - 65; ret += i * _alphabet.length + cindex; } return ret; } ================================================ FILE: src/core/cell.d.ts ================================================ export interface Cell { font?: string; format?: string; fontSize?: number; bold?: boolean; italic?: boolean; underline?: boolean; color?: string; backgroundColor?: string; align?: string; valign?: string; wordWrap?: boolean; visable?: boolean; rowspan?: number; colspan?: number; text?: string; merge?: [number, number]; [key: string]: any; } export declare const defaultCell: Cell; export declare function getStyleFromCell(cell: Cell | null): { [key: string]: string; }; ================================================ FILE: src/core/cell.ts ================================================ export interface Cell { font?: string; format?: string; fontSize?: number; bold?: boolean; italic?: boolean; underline?: boolean; color?: string; backgroundColor?: string; align?: string; valign?: string; wordWrap?: boolean; visable?: boolean; rowspan?: number; colspan?: number; text?: string; merge?: [number, number]; [key: string]: any } export const defaultCell: Cell = { font: 'Microsoft YaHei', format: 'normal', fontSize: 14, bold: false, italic: false, underline: false, color: '#333', backgroundColor: '#fff', align: 'left', valign: 'middle', wordWrap: false, invisible: false, rowspan: 1, colspan: 1, text: '', } export function getStyleFromCell (cell: Cell | null): {[key: string]: string} { const map: {[key: string]: string} = {} if (cell) { if (cell.font) map['font-family'] = cell.font if (cell.fontSize) map['font-size'] = `${cell.fontSize}px` if (cell.bold) map['font-weight'] = 'bold' if (cell.italic) map['font-style'] = 'italic' if (cell.underline) map['text-decoration'] = 'underline' if (cell.color) map['color'] = cell.color if (cell.backgroundColor) map['background-color'] = cell.backgroundColor if (cell.align) map['text-align'] = cell.align if (cell.valign) map['vertical-align'] = cell.valign if (cell.invisible) { map['display'] = 'none' } if (cell.wordWrap) { map['word-wrap'] = 'break-word' map['white-space'] = 'normal' } } return map } ================================================ FILE: src/core/font.d.ts ================================================ export interface Font { key: string; title: string; } export declare const fonts: Array; ================================================ FILE: src/core/font.ts ================================================ export interface Font { key: string; title: string; } export const fonts: Array = [ {key: 'Microsoft YaHei', title: '微软雅黑'}, {key: 'STFangsong', title: '华文仿宋'}, {key: 'Comic Sans MS', title: 'Comic Sans MS'}, {key: 'Arial', title: 'Arial'}, {key: 'Courier New', title: 'Courier New'}, {key: 'Verdana', title: 'Verdana'} ] ================================================ FILE: src/core/format.d.ts ================================================ export interface Format { key: string; title: string; label?: string; render(txt: string): string; } export declare const formatRenderHtml: (key: string | undefined, txt: string | undefined) => string; export declare const formats: Array; ================================================ FILE: src/core/format.ts ================================================ export interface Format { key: string; title: string; label?: string; render(txt: string): string; } export const formatRenderHtml = (key: string | undefined, txt: string | undefined) => { for (let i = 0; i < formats.length; i++) { if (formats[i].key === key) { return formats[i].render(txt || '') } } return txt || '' } const formatNumberRender = (v: string) => { if (/^(-?\d*.?\d*)$/.test(v)) { v = Number(v).toFixed(2).toString() const parts = v.split('.') parts[0] = parts[0].toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + ',') return parts.join('.') } return v } const formatRender = (v: string) => v export const formats: Array = [ {key: 'normal', title: 'Normal', render: formatRender}, {key: 'text', title: 'Text', render: formatRender}, {key: 'number', title: 'Number', label: '1,000.12', render: formatNumberRender}, {key: 'percent', title: 'Percent', label: '10.12%', render: (v) => `${formatNumberRender(v)}%`}, {key: 'RMB', title: 'RMB', label: '¥10.00', render: (v) => `¥${formatNumberRender(v)}`}, {key: 'USD', title: 'USD', label: '$10.00', render: (v) => `$${formatNumberRender(v)}`} ] ================================================ FILE: src/core/formula.d.ts ================================================ export interface Formula { key: string; title: string; render(ary: Array): number; } export declare const formulaFilterKey: (v: string, filter: (formula: Formula, param: string) => string) => string; export declare const formulaRender: (v: string, renderCell: (rindex: number, cindex: number) => any) => string; export declare const formulaReplaceParam: (param: string, rowDiff: number, colDiff: number) => string; export declare const formulas: Array; ================================================ FILE: src/core/formula.ts ================================================ import { alphabetIndex, alphabet } from "./alphabet"; export interface Formula { key: string; title: string; render(ary: Array): number } export const formulaFilterKey = (v: string, filter: (formula: Formula, param: string) => string) => { if (v[0] === '=') { const fx = v.substring(1, v.indexOf('(')) for (let formula of formulas) { if (formula.key.toLowerCase() === fx.toLowerCase()) { return filter(formula, v.substring(v.indexOf('(') + 1, v.lastIndexOf(')'))) } } } return v } export const formulaRender = (v: string, renderCell: (rindex: number, cindex: number) => any) => { return formulaFilterKey(v, (fx, param) => { return fx.render(formulaParamToArray(param, renderCell)) + ''; }) } export const formulaReplaceParam = (param: string, rowDiff: number, colDiff: number): string => { return formulaFilterKey(param, (fx, params) => { const replaceFormula = (_v: string):string => { if (/^[0-9\-\+\*\/()\s]+$/.test(_v.trim())) { return _v } const idx = /\d+/.exec(_v) if (idx) { let vc = _v.substring(0, idx.index).trim() let vr = parseInt(_v.substring(idx.index).trim()) return `${alphabet(alphabetIndex(vc) + colDiff)}${vr + rowDiff}` } return _v; } if (params.indexOf(':') !== -1) { params = params.split(':').map(replaceFormula).join(':') } else { params = params.split(',').map(replaceFormula).join(',') } return `=${fx.key}(${params})` }) } const formulaParamToArray = (param: string, renderCell: (rindex: number, cindex: number) => any) => { let paramValues = [] try { if (param.indexOf(':') !== -1) { const [min, max] = param.split(':'); const idx = /\d+/.exec(min); const maxIdx = /\d+/.exec(max); if (idx && maxIdx) { // idx = idx.index; // maxIdx = maxIdx.index; let minC = min.substring(0, idx.index).trim() let minR = parseInt(min.substring(idx.index).trim()) let maxC = max.substring(0, maxIdx.index).trim() let maxR = parseInt(max.substring(maxIdx.index).trim()) // console.log(min, max, minR, maxR, minC, maxC) if (maxC === minC) { for (let i = minR; i <= maxR; i++) { // console.log('value:::', i-1, alphabetIndex(minC), renderCell(i - 1, alphabetIndex(minC))) paramValues.push(renderCell(i - 1, alphabetIndex(minC))) } } else { for (let i = alphabetIndex(minC); i <= alphabetIndex(maxC); i++) { paramValues.push(renderCell(minR - 1, i)) } } } } else { paramValues = param.split(',').map(p => { // console.log(/^[0-9\-\+\*\/() ]+$/.test(p), p) if (/^[0-9\-\+\*\/()\s]+$/.test(p.trim())) { try { return eval(p) } catch (e) { return 0 } } const idx = /\d+/.exec(p) if (idx) { const c = p.substring(0, idx.index).trim() const r = p.substring(idx.index).trim() return renderCell(parseInt(r) - 1, alphabetIndex(c)) } return 0 }) } } catch (e) { console.log('warning:', e) } return paramValues; } export const formulas: Array = [ {key: 'SUM', title: '求和', render: (vv) => vv.reduce((a, b) => Number(a) + Number(b), 0)}, {key: 'AVERAGE', title: '平均值', render: (vv) => vv.reduce((a, b) => Number(a) + Number(b), 0) / vv.length}, {key: 'MAX', title: '最大值', render: (vv) => Math.max(...vv.map(v => Number(v)))}, {key: 'MIN', title: '最小值', render: (vv) => Math.min(...vv.map(v => Number(v)))} ] ================================================ FILE: src/core/index.d.ts ================================================ import { Format } from './format'; import { Font } from './font'; import { Formula } from './formula'; import { Cell } from './cell'; import { Select } from './select'; export interface Row { height: number; } export interface Col { title: string; width: number; } export interface MapInt { [key: number]: T; } export declare class History { type: 'rows' | 'cols' | 'cells'; values: Array<[Array, any, any]>; constructor(type: 'rows' | 'cols' | 'cells'); add(keys: Array, oldValue: any, value: any): void; } export declare type StandardCallback = (rindex: number, cindex: number, cell: Cell) => void; export interface SpreadsheetData { rowHeight?: number; colWidth?: number; rows?: MapInt; cols?: MapInt; cell: Cell; cells?: MapInt>; [prop: string]: any; } export interface SpreadsheetOptions { formats?: Array; fonts?: Array; formulas?: Array; data?: SpreadsheetData; } export declare class Spreadsheet { formats: Array; fonts: Array; formulas: Array; data: SpreadsheetData; private histories; private histories2; private currentCellIndexes; select: Select | null; private copySelect; private cutSelect; change: (data: SpreadsheetData) => void; constructor(options?: SpreadsheetOptions); buildSelect(startTarget: any, endTarget: any): Select; defaultRowHeight(): number; defaultColWidth(): number; copy(): void; cut(): void; paste(cb: StandardCallback, state: 'copy' | 'cut' | 'copyformat', clear: StandardCallback): void; insert(type: 'row' | 'col', amount: number, cb: StandardCallback): void; batchPaste(arrow: 'bottom' | 'top' | 'left' | 'right', startRow: number, startCol: number, stopRow: number, stopCol: number, seqCopy: boolean, cb: StandardCallback): void; private copyCell; isRedo(): boolean; redo(cb: StandardCallback): boolean; isUndo(): boolean; undo(cb: StandardCallback): boolean; resetByHistory(v: History, cb: StandardCallback, state: 'undo' | 'redo'): void; clearformat(cb: StandardCallback): void; merge(ok: StandardCallback, cancel: StandardCallback, other: StandardCallback): void; cellAttr(key: keyof Cell, value: any, cb: StandardCallback): void; cellText(value: any, cb: StandardCallback): Cell | null; currentCell(indexes?: [number, number]): Cell | null; cell(rindex: number, cindex: number, v: any, isCopy?: boolean): Cell; getCell(rindex: number, cindex: number): Cell | null; getFont(key: string | undefined): Font; getFormat(key: string | undefined): Format; row(index: number, v?: number): Row; rows(isData: boolean): Array; col(index: number, v?: number): Col; cols(): Array; } ================================================ FILE: src/core/index.ts ================================================ import { Format, formats } from './format' import { Font, fonts } from './font' import { Formula, formulas, formulaReplaceParam } from './formula' import { Cell, defaultCell } from './cell' import { alphabet } from './alphabet' import { Select } from './select' import { unbind } from '../local/event'; export interface Row { height: number } export interface Col { title: string width: number } export interface MapInt { [key: number]: T } export class History { values: Array<[Array, any, any]> = []; constructor (public type: 'rows' | 'cols' | 'cells') {} add (keys: Array, oldValue: any, value: any) { this.values.push([keys, oldValue, value]) } } // types export type StandardCallback = (rindex: number, cindex: number, cell: Cell) => void; export interface SpreadsheetData { rowHeight?: number; colWidth?: number; rows?: MapInt; cols?: MapInt; cell: Cell; // global default cell cells?: MapInt>; [prop: string]: any } export interface SpreadsheetOptions { formats?: Array; fonts?: Array; formulas?: Array; data?: SpreadsheetData; } export class Spreadsheet { formats: Array; fonts: Array; formulas: Array; data: SpreadsheetData; private histories: Array = []; private histories2: Array = []; private currentCellIndexes: [number, number] = [0, 0]; select: Select | null = null; private copySelect: Select | null = null; private cutSelect: Select | null = null; change: (data: SpreadsheetData) => void = () => {} constructor (options: SpreadsheetOptions = {}) { this.formats = options.formats || formats this.fonts = options.fonts || fonts this.formulas = options.formulas || formulas // init data this.data = {rowHeight: 22, colWidth: 100, cell: defaultCell} if (options.data) { const { data } = options; for (let prop of ['rowHeight', 'colWidth', 'rows', 'cols', 'cells']) { if (data[prop]) { this.data[prop] = data[prop]; } } (Object).assign(this.data.cell, data.cell || {}); } } // build select buildSelect (startTarget: any, endTarget: any) { const startAttrs = getElementAttrs(startTarget) const endAttrs = getElementAttrs(endTarget) // console.log(':::::::>>>', startAttrs, endAttrs) let sRow = startAttrs.row let sCol = startAttrs.col let eRow = endAttrs.row let eCol = endAttrs.col if (sRow > eRow) { sRow = endAttrs.row eRow = startAttrs.row } if (sCol > eCol) { sCol = endAttrs.col eCol = startAttrs.col } // calc min, max of row // console.log('s: ', sRow, sCol, ', e: ', eRow, eCol) let [minRow, maxRow] = calcMinMaxRow((r: number, c: number) => this.getCell(r, c), sRow, eRow, sCol, eCol) // console.log('minRow: ', minRow, ', maxRow: ', maxRow) // calc min, max of col let [minCol, maxCol] = calcMinMaxCol((r: number, c: number) => this.getCell(r, c), minRow, maxRow, sCol, eCol) while (true) { const [minr, maxr] = calcMinMaxRow((r: number, c: number) => this.getCell(r, c), minRow, maxRow, minCol, maxCol) let [minc, maxc] = calcMinMaxCol((r: number, c: number) => this.getCell(r, c), minRow, maxRow, minCol, maxCol) if (minRow === minr && maxRow === maxr && minCol === minc && maxCol === maxc) { break } minRow = minr maxRow = maxr minCol = minc maxCol = maxc } const firstCell = this.getCell(minRow, minCol) // console.log('first => rowspan: ', firstCell.rowspan, ', colspan: ', firstCell.colspan) let canotMerge = minRow + (firstCell && firstCell.rowspan || 1) - 1 === maxRow && minCol + (firstCell && firstCell.colspan || 1) - 1 === maxCol // console.log('row: ', minRow, maxRow, ', col:', minCol, maxCol, canotMerge) // 计算是否可以merge this.select = new Select([minRow, minCol], [maxRow, maxCol], !canotMerge) return this.select } defaultRowHeight (): number { return this.data.rowHeight || 22 } defaultColWidth (): number { return this.data.colWidth || 100 } copy (): void { this.copySelect = this.select } cut (): void { this.cutSelect = this.select } paste (cb: StandardCallback, state: 'copy' | 'cut' | 'copyformat', clear: StandardCallback): void { let cselect = this.copySelect if (this.cutSelect) { cselect = this.cutSelect this.cutSelect = null } if (cselect && this.select) { const history = new History('cells') if (state === 'copyformat') { this.select.forEach((rindex, cindex, i, j, rowspan, colspan) => { if (cselect) { const srcRowIndex = cselect.rowIndex(i) const srcColIndex = cselect.colIndex(j) const [oldCell, newCell] = this.copyCell(srcRowIndex, srcColIndex, rindex, cindex, state, cb, clear) history.add([rindex, cindex], oldCell, newCell) } }) } else { cselect.forEach((rindex, cindex, i, j, rowspan, colspan) => { if (this.select) { const destRowIndex = this.select.start[0] + i const destColIndex = this.select.start[1] + j const [oldCell, newCell] = this.copyCell(rindex, cindex, destRowIndex, destColIndex, state, cb, clear) history.add([destRowIndex, destColIndex], oldCell, newCell) } }) } this.histories.push(history) this.change(this.data) } } insert (type: 'row' | 'col', amount: number, cb: StandardCallback) { if (this.select) { const { cells } = this.data const [srindex, scindex] = this.select.start if (!cells) return // console.log('insert.before.data:', cells) const history = new History('cells') if (type === 'row') { const newCells: MapInt> = {} Object.keys(cells).forEach(key => { let rindex = parseInt(key) let values = cells[rindex] if (srindex <= rindex) { Object.keys(values).forEach(key1 => { let cindex = parseInt(key1) // clear current cell cb(rindex, cindex, {}) history.add([rindex, cindex], values[cindex], undefined) // set next cell is current celll cb(rindex + 1, cindex, values[cindex] || {}) history.add([rindex + 1, cindex], this.getCell(rindex + 1, cindex), values[cindex]) }) } newCells[srindex <= rindex ? rindex + 1 : rindex] = cells[rindex] }) this.data.cells = newCells } else if (type === 'col') { Object.keys(cells).forEach(key => { let rindex = parseInt(key) let values = cells[rindex] let newCell: MapInt = {} Object.keys(values).forEach(key1 => { let cindex = parseInt(key1) if (scindex <= cindex) { // clear 当前cell cb(rindex, cindex, {}) history.add([rindex, cindex], values[cindex], undefined) // 设置下一个cell 等于当前的cell cb(rindex, cindex + 1, values[cindex] || {}) history.add([rindex, cindex + 1], this.getCell(rindex, cindex + 1), values[cindex]) } newCell[scindex <= cindex ? cindex + 1 : cindex] = values[cindex] }) cells[rindex] = newCell }) } this.histories.push(history) // console.log('insert.after.data:', this.data.cells) } } batchPaste (arrow: 'bottom' | 'top' | 'left' | 'right', startRow: number, startCol: number, stopRow: number, stopCol: number, seqCopy: boolean, cb: StandardCallback) { if (this.select) { const history = new History('cells') for (let i = startRow; i <= stopRow; i++) { for (let j = startCol; j <= stopCol; j++) { const srcRowIndex = this.select.rowIndex(i - startRow) const srcColIndex = this.select.colIndex(j - startCol) const [oldDestCell, destCell] = this.copyCell(srcRowIndex, srcColIndex, i, j, seqCopy ? 'seqCopy' : 'copy', cb, () => {}) history.add([i, j], oldDestCell, destCell) } } this.histories.push(history) this.change(this.data) } } private copyCell (srcRowIndex: number, srcColIndex: number, destRowIndex: number, destColIndex: number, state: 'seqCopy' | 'copy' | 'cut' | 'copyformat', cb: StandardCallback, clear: StandardCallback): [Cell | null, Cell | null] { const srcCell = this.getCell(srcRowIndex, srcColIndex) const rowDiff = destRowIndex - srcRowIndex const colDiff = destColIndex - srcColIndex if (srcCell) { let oldDestCell = this.getCell(destRowIndex, destColIndex) // let destCell = cellCopy(srcCell, destRowIndex - srcRowIndex, destColIndex - srcColIndex, state === 'seqCopy') const destCell = Object.assign({}, srcCell) if (srcCell.merge) { const [m1, m2] = srcCell.merge destCell.merge = [m1 + rowDiff, m2 + colDiff]; } if (state === 'cut') { clear(srcRowIndex, srcColIndex, this.cell(srcRowIndex, srcColIndex, {})) } if (state === 'copyformat') { if (oldDestCell && oldDestCell.text) { destCell.text = oldDestCell.text } } else { const txt = destCell.text if (txt && !/^\s*$/.test(txt)) { if (/^\d*$/.test(txt) && state === 'seqCopy') { destCell.text = (parseInt(txt) + (destRowIndex - srcRowIndex) + (destColIndex - srcColIndex)) + '' } else if (txt.indexOf('=') !== -1) { // 如果text的内容是formula,那么需要需要修改表达式参数 destCell.text = formulaReplaceParam(txt, rowDiff, colDiff) } } } cb(destRowIndex, destColIndex, this.cell(destRowIndex, destColIndex, destCell)) return [oldDestCell, destCell]; } return [null, null]; } isRedo (): boolean { return this.histories2.length > 0 } redo (cb: StandardCallback): boolean { const { histories, histories2 } = this if (histories2.length > 0) { const history = histories2.pop() if (history) { this.resetByHistory(history, cb, 'redo') histories.push(history) this.change(this.data) } } return this.isRedo() } isUndo (): boolean { return this.histories.length > 0 } undo (cb: StandardCallback): boolean { const { histories, histories2 } = this // console.log('histories:', histories, histories2) if (histories.length > 0) { const history = histories.pop() if (history) { this.resetByHistory(history, cb, 'undo') histories2.push(history) this.change(this.data) } } return this.isUndo() } resetByHistory (v: History, cb: StandardCallback, state: 'undo' | 'redo') { // console.log('history: ', history) v.values.forEach(([keys, oldValue, value]) => { if (v.type === 'cells') { const v = state === 'undo' ? oldValue : value const oldCell = this.getCell(keys[0], keys[1]) if (!oldCell) { if (keys.length === 3) { if (v) { const nValue: Cell = {} nValue[keys[2]] = v cb(keys[0], keys[1], this.cell(keys[0], keys[1], nValue)) } } else { cb(keys[0], keys[1], this.cell(keys[0], keys[1], v || {})) } } else { if (keys.length === 3) { const nValue: Cell = {} nValue[keys[2]] = v if (v) { cb(keys[0], keys[1], this.cell(keys[0], keys[1], nValue, true)) } else { cb(keys[0], keys[1], this.cell(keys[0], keys[1], mapIntFilter(oldCell, keys[2]))) } } else { cb(keys[0], keys[1], this.cell(keys[0], keys[1], v || {})) } } } else { // cols, rows // const v = state === 'undo' ? oldValue : value // if (v !== null) { // this.data[v.type] // } } // console.log('keys:', keys, ', oldValue:', oldValue, ', value:', value) }) } clearformat (cb: StandardCallback) { const { select } = this if (select !== null) { const history = new History('cells') select.forEach((rindex, cindex, i, j, rowspan, colspan) => { let c = this.getCell(rindex, cindex); if (c) { history.add([rindex, cindex], c, {text: c.text}) c = this.cell(rindex, cindex, {text: c.text}); cb(rindex, cindex, c); } }); this.histories.push(history) this.change(this.data) } } /** * * @param ok 合并单元格第一个单元格(左上角)的回调函数 * @param cancel 取消合并单元格第一个单元格(左上角)的回调函数 * @param other 其他单元格的回调函数 */ merge (ok: StandardCallback, cancel: StandardCallback, other: StandardCallback): void { const { select } = this // console.log('data.before: ', this.data) if (select !== null && select.cellLen() > 1) { // merge merge: [rows[0], cols[0]] const history = new History('cells') let index = 0 let firstXY: [number, number] = [0, 0] select.forEach((rindex, cindex, i, j, rowspan, colspan) => { if (index++ === 0) { firstXY = [rindex, cindex] let v: Cell = {} if (rowspan > 1) v.rowspan = rowspan if (colspan > 1) v.colspan = colspan // console.log('rowspan:', rowspan, ', colspan:', colspan, select.canMerge) if (select.canMerge) { history.add([rindex, cindex, 'rowspan'], undefined, rowspan) history.add([rindex, cindex, 'colspan'], undefined, colspan) let cell = this.cell(rindex, cindex, v, true) ok(rindex, cindex, cell) } else { const oldCell = this.getCell(rindex, cindex) if (oldCell !== null) { history.add([rindex, cindex, 'rowspan'], oldCell.rowspan, undefined) history.add([rindex, cindex, 'colspan'], oldCell.colspan, undefined) let cell = this.cell(rindex, cindex, mapIntFilter(oldCell, 'rowspan', 'colspan', 'merge')) cancel(rindex, cindex, cell) } } } else { let v: Cell = {invisible: select.canMerge} if (select.canMerge) { history.add([rindex, cindex, 'invisible'], undefined, select.canMerge) v.merge = firstXY let cell = this.cell(rindex, cindex, v, true) other(rindex, cindex, cell) } else { const oldCell = this.getCell(rindex, cindex) if (oldCell !== null) { history.add([rindex, cindex, 'invisible'], oldCell.invisible, undefined) let cell = this.cell(rindex, cindex, mapIntFilter(oldCell, 'rowspan', 'colspan', 'merge', 'invisible')) other(rindex, cindex, cell) } } } }) this.histories.push(history) select.canMerge = !select.canMerge this.change(this.data) } } cellAttr (key: keyof Cell, value: any, cb: StandardCallback): void { let v: Cell= {} v[key] = value const isDefault = value === this.data.cell[key] if (this.select !== null) { const history = new History('cells') this.select.forEach((rindex, cindex) => { const oldCell = this.getCell(rindex, cindex) history.add([rindex, cindex, key], oldCell !== null ? oldCell[key] : undefined, value) let cell = this.cell(rindex, cindex, isDefault ? mapIntFilter(oldCell, key) : v, !isDefault) cb(rindex, cindex, cell) }) this.histories.push(history) } this.change(this.data) } cellText (value: any, cb: StandardCallback): Cell | null { if (this.currentCellIndexes) { // this.addHistoryValues() const history = new History('cells') const [rindex, cindex] = this.currentCellIndexes const oldCell = this.getCell(rindex, cindex) history.add([rindex, cindex, 'text'], oldCell !== null ? oldCell.text : undefined, value) const cell = this.cell(rindex, cindex, {text: value}, true) cb(rindex, cindex, cell) this.histories.push(history) this.change(this.data) return cell; } return null } currentCell (indexes?: [number, number]): Cell | null { if (indexes !== undefined) { this.currentCellIndexes = indexes } const [rindex, cindex] = this.currentCellIndexes return this.getCell(rindex, cindex) } cell (rindex: number, cindex: number, v: any, isCopy = false): Cell { this.data.cells = this.data.cells || {} this.data.cells[rindex] = this.data.cells[rindex] || {} this.data.cells[rindex][cindex] = this.data.cells[rindex][cindex] || {} if (isCopy) { (Object).assign(this.data.cells[rindex][cindex], v) } else if (v) { this.data.cells[rindex][cindex] = v } return this.data.cells[rindex][cindex] } getCell (rindex: number, cindex: number): Cell | null { if (this.data.cells && this.data.cells[rindex] && this.data.cells[rindex][cindex]) { return this.data.cells[rindex][cindex]; } return null; } getFont (key: string | undefined) { return this.fonts.filter(it => it.key === key)[0] } getFormat (key: string | undefined) { return this.formats.filter(it => it.key === key)[0] } row (index: number, v?: number): Row { const { data } = this; if (v !== undefined) { const history = new History('rows') data.rows = data.rows || {} data.rows[index] = data.rows[index] || {} data.rows[index].height = v history.add([index], null, data.rows[index]) this.histories.push(history) } return (Object).assign({height: data.rowHeight}, data.rows ? data.rows[index] : {}) } // isData 是否返回数据的最大行数 rows (isData: boolean): Array { const { data } = this; let maxRow; if (isData) { maxRow = 10 if (this.data.cells) { maxRow = mapIntMaxKey(this.data.cells) + 2 } } else { maxRow = mapIntMaxKeyWithDefault(100, data.rows) } return range(maxRow, (index) => this.row(index)) } col (index: number, v?: number): Col { const { data } = this; if (v !== undefined) { const history = new History('cols') data.cols = data.cols || {} data.cols[index] = data.cols[index] || {} data.cols[index].width = v history.add([index], null, data.cols[index]) this.histories.push(history) } const ret:any = {width: data.colWidth, title: alphabet(index)} if (data.cols && data.cols[index]) { for (let prop in data.cols[index]) { const col:any = data.cols[index] if (col[prop]) { ret[prop] = col[prop] } } } return ret } cols (): Array { const { data } = this; let maxCol = mapIntMaxKeyWithDefault(26 * 2, data.cols); return range(maxCol, (index) => this.col(index)); } } const mapIntMaxKey = function(mapInt: MapInt): number { return Math.max(...Object.keys(mapInt).map(s => parseInt(s))) } // methods const mapIntMaxKeyWithDefault = function(max: number, mapInt: MapInt | undefined): number { if (mapInt) { const m = mapIntMaxKey(mapInt) if (m > max) return m; } return max; } const mapIntFilter = function(obj: any, ...keys: Array): any { const ret: any = {} if (obj){ Object.keys(obj).forEach(e => { if (keys.indexOf(e) === -1) { ret[e] = obj[e] } }) } return ret } const range = function(stop:number, cb: (index: number) => T): Array { const ret = [] for (let i = 0; i < stop; i++) { ret.push(cb(i)) } return ret } const getElementAttrs = (target: any) => { const { offsetTop, offsetLeft, offsetHeight, offsetWidth } = target return { row: parseInt(target.getAttribute('row-index')), col: parseInt(target.getAttribute('col-index')), rowspan: parseInt(target.getAttribute('rowspan')), colspan: parseInt(target.getAttribute('colspan')), left: offsetLeft, top: offsetTop, width: offsetWidth, height: offsetHeight } } const calcMinMaxCol = (cell: any, sRow: number, eRow: number, sCol: number, eCol: number) => { let minCol = sCol let maxCol = eCol // console.log(':::::::;start: ', maxCol, minCol) for (let j = sRow; j <= eRow; j++) { let cCol = sCol let dcell = cell(j, cCol) if (dcell && dcell.merge) { cCol += dcell.merge[1] - cCol } if (cCol < minCol) minCol = cCol cCol = maxCol dcell = cell(j, cCol) // console.log(j, cCol, dcell && dcell.colspan || 1) const cColspan = dcell ? dcell.colspan : 1 if (parseInt(cColspan) > 1) { cCol += parseInt(cColspan) } else { if (dcell && dcell.merge) { // console.log('merge::', maxCol, dcell.merge) const [r, c] = dcell.merge const rc = cell(r, c).colspan cCol += rc + (c - cCol) } } // console.log('cCol: ', cCol, ', maxCol: ', maxCol) // console.log(':::::::;end: ', maxCol, minCol) if (cCol - 1 > maxCol) maxCol = cCol - 1 } return [minCol, maxCol] } const calcMinMaxRow = (cell: any, sRow: number, eRow: number, sCol: number, eCol: number) => { let minRow = sRow let maxRow = eRow for (let j = sCol; j <= eCol; j++) { let cRow = sRow let dcell = cell(cRow, j) if (dcell && dcell.merge) { cRow += dcell.merge[0] - cRow } if (cRow < minRow) minRow = cRow cRow = maxRow dcell = cell(cRow, j) // console.log('row: ', j, cRow, dcell.rowspan) const cRowspan = dcell ? dcell.rowspan : 1 if (parseInt(cRowspan) > 1) { cRow += parseInt(cRowspan) } else { if (dcell && dcell.merge) { const [r, c] = dcell.merge const rs = cell(r, c).rowspan cRow += rs + (r - cRow) } } if (cRow - 1 > maxRow) maxRow = cRow - 1 } return [minRow, maxRow] } ================================================ FILE: src/core/select.d.ts ================================================ export declare class Select { start: [number, number]; stop: [number, number]; canMerge: boolean; constructor(start: [number, number], stop: [number, number], canMerge: boolean); forEach(cb: (r: number, c: number, rindex: number, cindex: number, rowspan: number, colspan: number) => void): void; rowIndex(index: number): number; colIndex(index: number): number; rowLen(): number; colLen(): number; cellLen(): number; contains(rindex: number, cindex: number): boolean; } ================================================ FILE: src/core/select.ts ================================================ export class Select { constructor(public start: [number, number], public stop: [number, number], public canMerge: boolean) {} forEach (cb: (r:number, c: number, rindex: number, cindex: number, rowspan: number, colspan: number) => void): void { const [sx, sy] = this.start const [ex, ey] = this.stop for (let i = sx; i <= ex; i++) { for (let j = sy; j <= ey; j++) { cb(i, j, i - sx, j - sy, ex - sx + 1, ey - sy + 1) } } } rowIndex (index: number) { return this.start[0] + index % this.rowLen() } colIndex (index: number) { return this.start[1] + index % this.colLen() } rowLen () { return this.stop[0] - this.start[0] + 1 } colLen () { return this.stop[1] - this.start[1] + 1 } cellLen () { return this.rowLen() * this.colLen() } contains (rindex: number, cindex: number) { const [sx, sy] = this.start const [ex, ey] = this.stop return sx <= rindex && ex >= rindex && sy <= cindex && ey >= cindex } } // export function buildSelect (start: any, end: ) ================================================ FILE: src/local/base/colorPanel.d.ts ================================================ import { Element } from "./element"; export declare class ColorPanel extends Element { constructor(click: (color: string) => void); } export declare function buildColorPanel(click: (color: string) => void): ColorPanel; ================================================ FILE: src/local/base/colorPanel.ts ================================================ import { Element, h } from "./element"; const colorss = [ ['#c00000', '#ff0000', '#ffc003', '#ffff00','#91d051', '#00af50', '#00b0f0', '#0070c0', '#002060', '#70309f'], ['#ffffff', '#000000', '#e7e6e6', '#44546a', '#4472c4', '#ed7d31', '#a5a5a5', '#ffc003', '#5b9bd5', '#70ad47'], ['#f4f5f8', '#848484', '#d0cece', '#d6dce4', '#d9e2f2', '#fae5d5', '#ededed', '#fff2cc', '#deebf6', '#e2efd9'], ['#d8d8d8', '#595959', '#afabab', '#adb9ca', '#b4c6e7', '#f7cbac', '#dbdbdb', '#fee598', '#bdd7ee', '#c5e0b3'], ['#bfbfbf', '#3f3f3f', '#757070', '#8496b0', '#8eaad8', '#f4b183', '#c9c9c9', '#ffd964', '#9dc2e5', '#a8d08d'], ['#a5a5a5', '#262626', '#3a3838', '#333f4f', '#2f5496', '#c55b11', '#7b7b7b', '#bf9001', '#2e75b5', '#538135'], ['#7e7e7e', '#0c0c0c', '#171616', '#232a35', '#1e3864', '#833d0b', '#525252', '#7e6000', '#1f4e79', '#375623'] ] export class ColorPanel extends Element { constructor (click: (color: string) => void) { super(); this.class('spreadsheet-color-panel') .child( h('table').child( h('tbody').children( colorss.map(colors => { return h('tr').children( colors.map(color => { return h('td').child( h() .class('color-cell') .on('click', click.bind(null, color)) .style('background-color', color) ) }) ) }) ))); } } export function buildColorPanel (click: (color: string) => void) { return new ColorPanel(click); } ================================================ FILE: src/local/base/dropdown.d.ts ================================================ import { Element } from "./element"; export declare class Dropdown extends Element { content: Element; title: Element; constructor(title: string | Element, width: string, contentChildren: Element[]); toggleHandler(evt: Event): void; } export declare function buildDropdown(title: string | Element, width: string, contentChildren: Element[]): Dropdown; ================================================ FILE: src/local/base/dropdown.ts ================================================ import { Element, h } from "./element"; import { buildIcon } from "./icon"; export class Dropdown extends Element { content: Element; title: Element; constructor (title: string | Element, width: string, contentChildren: Element[]) { super(); this.class('spreadsheet-dropdown spreadsheet-item'); this.content = h().class('spreadsheet-dropdown-content') .children(contentChildren) .onClickOutside(() => this.deactive()) .on('click', (evt) => this.toggleHandler(evt)) .style('width', width).hide(); this.child(h().class('spreadsheet-dropdown-header').children([ this.title = typeof title === 'string' ? h().class('spreadsheet-dropdown-title').child(title) : title, h().class('spreadsheet-dropdown-icon').on('click', (evt) => this.toggleHandler(evt)).child(buildIcon('arrow-down')) ])).child(this.content); } toggleHandler (evt: Event) { if (this.content.isHide()){ this.content.show() this.active() } else { this.content.hide() this.deactive() } } } export function buildDropdown(title: string | Element, width: string, contentChildren: Element[]) { return new Dropdown(title, width, contentChildren) } ================================================ FILE: src/local/base/element.d.ts ================================================ export declare class Element { tag: string; el: HTMLElement; _data: { [key: string]: any; }; _clickOutside: any; constructor(tag?: string); data(key: string, value?: any): any; on(eventName: string, handler: (evt: any) => any): Element; onClickOutside(cb: () => void): Element; parent(): any; class(name: string): Element; attrs(map?: { [key: string]: string; }): Element; attr(attr: string, value?: any): any; removeAttr(attr: string): Element; offset(): any; clearStyle(): this; styles(map?: { [key: string]: string; }, isClear?: boolean): Element; style(key: string, value?: any): any; contains(el: any): boolean; removeStyle(key: string): void; children(cs: Array): Element; child(c: HTMLElement | string | Element): Element; html(html?: string): string | this; val(v?: string): any; clone(): any; isHide(): boolean; toggle(): void; disabled(): Element; able(): Element; active(flag?: boolean): Element; deactive(): Element; isActive(): boolean; addClass(cls: string): Element; removeClass(cls: string): this; hasClass(cls: string): boolean; show(isRemove?: boolean): Element; hide(): Element; } export declare function h(tag?: string): Element; ================================================ FILE: src/local/base/element.ts ================================================ import { bind, unbind } from '../event' export class Element { el: HTMLElement; _data: {[key: string]: any} = {}; _clickOutside: any = null; constructor (public tag = 'div') { this.el = document.createElement(tag) } data (key: string, value?: any) { if (value !== undefined) { this._data[key] = value } return this._data[key] } on (eventName: string, handler: (evt: any) => any): Element { const [first, ...others] = eventName.split('.') // console.log('first:', first, ', others:', others) this.el.addEventListener(first, (evt: any) => { // console.log('>>>', others, evt.button) for (let k of others) { console.log('::::::::::', k) if (k === 'left' && evt.button !== 0) { return } else if (k === 'right' && evt.button !== 2) { return } else if (k === 'stop') { evt.stopPropagation() } } // console.log('>>>>>>>>>>>>') handler(evt) }) return this; } onClickOutside (cb: () => void): Element { this._clickOutside = cb return this; } parent(): any { return this.el.parentNode } class (name: string): Element { this.el.className = name return this; } attrs (map: {[key: string]: string} = {}): Element { for (let key of Object.keys(map)) this.attr(key, map[key]); return this; } attr (attr: string, value?: any): any { if (value !== undefined) { this.el.setAttribute(attr, value); } else { return this.el.getAttribute(attr) } return this; } removeAttr(attr: string): Element { this.el.removeAttribute(attr); return this; } offset (): any { const { offsetTop, offsetLeft, offsetHeight, offsetWidth } = this.el return {top: offsetTop, left: offsetLeft, height: offsetHeight, width: offsetWidth} } clearStyle () { (this.el).style = '' return this; } styles (map: {[key: string]: string} = {}, isClear = false): Element { if (isClear) { this.clearStyle() } for (let key of Object.keys(map)) this.style(key, map[key]); return this; } style (key: string, value?: any): any { if (value !== undefined) { this.el.style.setProperty(key, value); } else { return this.el.style.getPropertyValue(key) } return this; } contains (el: any) { return this.el.contains(el) } removeStyle (key: string) { this.el.style.removeProperty(key) return ; } children (cs: Array): Element { for (let c of cs) this.child(c); return this; } child (c: HTMLElement | string | Element): Element { if (typeof c === 'string') { this.el.appendChild(document.createTextNode(c)) } else if (c instanceof Element) { this.el.appendChild(c.el) } else if (c instanceof HTMLElement) { this.el.appendChild(c) } return this; } html (html?: string) { if (html !== undefined) { this.el.innerHTML = html } else { return this.el.innerHTML } return this; } val (v?: string) { if (v !== undefined) { // (this.el).value = v (this.el).value = v } else { return (this.el).value } return this; } clone (): any { return this.el.cloneNode(); } isHide () { return this.style('display') === 'none' } toggle () { if (this.isHide()) { this.show() } else { this.hide() } } disabled (): Element { // this.removeClass('disabled') this.addClass('disabled') return this; } able (): Element { this.removeClass('disabled') return this; } active (flag = true): Element { // this.el.className = this.el.className.split(' ').filter(c => c !== 'disabled').join(' ') + ' active' // this.removeClass('disabled') if (flag) this.addClass('active') else this.deactive() return this; } deactive (): Element { return this.removeClass('active') } isActive (): boolean { return this.hasClass('active'); } addClass (cls: string): Element { this.el.className = this.el.className.split(' ').concat(cls).join(' ') return this; } removeClass (cls: string) { // console.log('before.className: ', this.el.className) this.el.className = this.el.className.split(' ').filter(c => c !== cls).join(' ') // console.log('after.className: ', this.el.className) return this; } hasClass (cls: string) { return this.el.className.indexOf(cls) !== -1 } show (isRemove = false): Element { isRemove ? this.removeStyle('display') : this.style('display', 'block'); // clickoutside if (this._clickOutside) { this.data('_outsidehandler', (evt: Event) => { if (this.contains(evt.target)) { return false } this.hide() unbind('click', this.data('_outsidehandler')) this._clickOutside && this._clickOutside() }) setTimeout(() => { bind('click', this.data('_outsidehandler')) }, 0) } return this; } hide (): Element { this.style('display', 'none'); if (this._clickOutside) { unbind('click', this.data('_outsidehandler')) } return this; } } export function h (tag = 'div'): Element { return new Element(tag) } ================================================ FILE: src/local/base/icon.d.ts ================================================ import { Element } from "./element"; export declare class Icon extends Element { img: Element; constructor(name: string); replace(name: string): void; } export declare function buildIcon(name: string): Icon; ================================================ FILE: src/local/base/icon.ts ================================================ import { Element, h } from "./element"; export class Icon extends Element{ img: Element; constructor (name: string) { super(); this.class('spreadsheet-icon').child(this.img = h().class(`spreadsheet-icon-img ${name}`)); } replace (name: string) { this.img.class(`spreadsheet-icon-img ${name}`) } } export function buildIcon (name: string) { return new Icon(name); } ================================================ FILE: src/local/base/item.d.ts ================================================ import { Element } from "./element"; import { Icon } from "./icon"; export declare class Item extends Element { iconEl: Icon | null; static build(): Item; constructor(); icon(name: string): this; replaceIcon(name: string): void; } export declare function buildItem(): Item; ================================================ FILE: src/local/base/item.ts ================================================ import { Element } from "./element"; import { Icon, buildIcon } from "./icon"; export class Item extends Element { iconEl: Icon | null = null; static build (): Item { return new Item() } constructor () { super(); this.class('spreadsheet-item'); } icon (name: string) { this.child(this.iconEl = buildIcon(name)) return this; } replaceIcon (name: string) { this.iconEl && this.iconEl.replace(name) } } export function buildItem (): Item { return new Item(); } ================================================ FILE: src/local/base/menu.d.ts ================================================ import { Element } from "./element"; export declare class Menu extends Element { constructor(align?: string); } export declare function buildMenu(align?: string): Menu; ================================================ FILE: src/local/base/menu.ts ================================================ import { Element } from "./element"; export class Menu extends Element{ constructor (align = 'vertical') { super(); this.class(`spreadsheet-menu ${align}`) } } export function buildMenu (align = 'vertical') { return new Menu(align); } ================================================ FILE: src/local/base/suggest.d.ts ================================================ import { Element } from "./element"; export declare class Suggest extends Element { list: Array<[string, string]>; width: number; filterList: Array; currentIndex: number; target: Element | null; evtTarget: Element | null; itemClick: (it: [string, string]) => void; constructor(list: Array<[string, string]>, width: number); private documentHandler; private documentKeydownHandler; private hideAndRemoveEvents; private removeEvents; private clickItemHandler; search(target: Element, input: Element, word: string): void; } ================================================ FILE: src/local/base/suggest.ts ================================================ import { Element, h } from "./element"; import { buildItem } from "./item"; import { buildMenu } from "./menu"; import { bind, unbind } from "../event"; export class Suggest extends Element { filterList: Array = []; currentIndex = 0; target: Element | null = null; evtTarget: Element | null = null; itemClick: (it: [string, string]) => void = (it) => {} constructor (public list: Array<[string, string]>, public width: number) { super(); this.class('spreadsheet-suggest').hide() } private documentHandler (e: any) { if (this.el.contains(e.target)) { return false } this.hideAndRemoveEvents() } private documentKeydownHandler (e: any) { console.log('keyCode: ', e) if (this.filterList.length <= 0 && e.target.type !== 'textarea') return ; switch (e.keyCode) { case 37: // left e.returnValue = false break; case 38: // up this.filterList[this.currentIndex].deactive() this.currentIndex-- if (this.currentIndex < 0) { this.currentIndex = this.filterList.length - 1 } this.filterList[this.currentIndex].active() e.returnValue = false e.stopPropagation(); break; case 39: // right e.returnValue = false break; case 40: // down this.filterList[this.currentIndex].deactive() this.currentIndex++ if (this.currentIndex > this.filterList.length - 1) { this.currentIndex = 0 } this.filterList[this.currentIndex].active() e.returnValue = false break; case 13: // enter this.filterList[this.currentIndex].el.click() e.returnValue = false break; } e.stopPropagation(); } private hideAndRemoveEvents () { this.hide() this.removeEvents(); } private removeEvents () { if (this.evtTarget !== null) { unbind('click', this.data('_outsidehandler'), this.evtTarget.el) unbind('keydown', this.data('_keydownhandler'), this.evtTarget.el) } } private clickItemHandler (it: [string, string]) { // console.log('click.it: ', it) this.itemClick(it) this.hideAndRemoveEvents() } search (target: Element, input: Element, word: string) { this.removeEvents() this.target = target; this.evtTarget = input; const { left, top, width, height } = target.offset() this.styles({left: `${left}px`, top: `${top + height + 2}px`, width: `${this.width}px`}) let lis: any = this.list if (!/^\s*$/.test(word)) { lis = this.list.filter(it => it[0].startsWith(word.toUpperCase())) } lis = lis.map((it: [string, string]) => { const item = buildItem().on('click', (evt) => this.clickItemHandler(it)).child(it[0]) if (it[1]) { item.child(h().class('label').html(it[1])) } return item // return `
${it[0]}${it[1] ? '
'+it[1]+'
' : ''}
` }) this.filterList = lis this.currentIndex = 0 if (lis.length <= 0) { lis = [buildItem().child('No Result')] // `
No Result
` } else { lis[0].active() // clickoutside this.data('_outsidehandler', (evt: Event) => { this.documentHandler(evt) }) this.data('_keydownhandler', (evt: any) => this.documentKeydownHandler(evt)) setTimeout(() => { if (this.evtTarget !== null) { bind('click', this.data('_outsidehandler'), this.evtTarget.el) bind('keydown', this.data('_keydownhandler'), this.evtTarget.el) } }, 0) } this.html(``) this.child(buildMenu().children(lis)).show() } } ================================================ FILE: src/local/contextmenu.d.ts ================================================ import { Element } from "./base/element"; import { Table } from "./table"; export declare class ContextMenu { table: Table; el: Element; constructor(table: Table); set(evt: any): void; } ================================================ FILE: src/local/contextmenu.ts ================================================ import { Element, h } from "./base/element"; import { buildItem } from "./base/item"; import { buildMenu } from "./base/menu"; import { Table } from "./table" export class ContextMenu { el: Element; constructor (public table: Table) { this.el = h().class('spreadsheet-contextmenu') .style('width', '160px') .on('click', (evt: any) => this.el.hide()) .children([ buildMenu().children([ buildItem().on('click', (evt) => table.copy()).children(['copy', h().class('label').html('ctrl + c')]), buildItem().on('click', (evt) => table.cut()).children(['cut', h().class('label').html('ctrl + x')]), buildItem().on('click', (evt) => table.paste()).children(['paste', h().class('label').html('ctrl + v')]), // h().class('spreadsheet-item-separator'), // buildItem().on('click', (evt) => table.insert('row', 1)).html('insert row'), // buildItem().on('click', (evt) => table.insert('col', 1)).html('insert col') ]) ]).onClickOutside(() => {}).hide() // clickoutside } set (evt: any) { const { offsetLeft, offsetTop } = evt.target const elRect = this.el.el.getBoundingClientRect() // cal left top const { clientWidth, clientHeight } = document.documentElement let top = offsetTop + evt.offsetY let left = offsetLeft + evt.offsetX if (evt.clientY > clientHeight / 1.5) { top -= elRect.height } if (evt.clientX > clientWidth / 1.5) { left -= elRect.width } this.el.style('left', `${left}px`).style('top', `${top}px`).show() } } ================================================ FILE: src/local/editor.d.ts ================================================ import { Element } from "./base/element"; import { Suggest } from "./base/suggest"; import { Cell } from "../core/cell"; import { Formula } from "../core/formula"; export declare class Editor { defaultRowHeight: number; formulas: Array; el: Element; target: HTMLElement | null; value: Cell | null; editor: Element; textarea: Element; textline: Element; suggest: Suggest; change: (v: Cell) => void; constructor(defaultRowHeight: number, formulas: Array); onChange(change: (v: Cell) => void): void; set(target: HTMLElement, value: Cell | null): void; setValue(value: Cell | null): string; setStyle(value: Cell | null): void; clear(): void; private setTextareaRange; private inputKeydown; private inputChange; private autocomplete; reload(): void; } ================================================ FILE: src/local/editor.ts ================================================ import { Element, h } from "./base/element"; import { Suggest } from "./base/suggest"; import { Cell, getStyleFromCell } from "../core/cell" import { Formula } from "../core/formula"; export class Editor { el: Element; target: HTMLElement | null = null; // 选中的当前的element value: Cell | null = null; // 选中的当前的cell editor: Element; textarea: Element; textline: Element; // 计算输入文本的宽度用的element suggest: Suggest; // autocomplete show change: (v: Cell) => void = (v) => {}; constructor (public defaultRowHeight: number, public formulas : Array) { const suggestList: any = formulas.map(it => [it.key, it.title]) this.el = h().children([this.editor = h().class('spreadsheet-editor').children([ this.textarea = h('textarea') .on('keydown', (evt: any) => this.inputKeydown(evt)) .on('input', (evt: Event) => this.inputChange(evt)), this.textline = h().styles({visibility: 'hidden', overflow: 'hidden', position: 'fixed', top: '0', left: '0'}) ]) , this.suggest = new Suggest(suggestList, 180)]).hide() this.el.on('keydown', (evt: any) => { if (evt.keyCode !== 13 && evt.keyCode !== 9) { evt.stopPropagation(); } }) this.suggest.itemClick = (it) => { // console.log('>>>>>>>>>>>>', it) const text = `=${it[0]}()`; if (this.value) { this.value.text = text } this.textarea.val(text); this.textline.html(text); this.setTextareaRange(text.length - 1) // (this.textarea.el).setSelectionRange(text.length + 1, text.length + 1); // setTimeout(() => (this.textarea.el).focus(), 10) } } onChange (change: (v: Cell) => void) { this.change = change } set (target: HTMLElement, value: Cell | null) { // console.log('set::>>') this.target = target; const text = this.setValue(value) this.el.show(); this.setTextareaRange(text.length) // (this.textarea.el).setSelectionRange(text.length, text.length); // setTimeout(() => (this.textarea.el).focus(), 10) this.reload(); } setValue (value: Cell | null): string { this.setStyle(value); if (value) { this.value = value; const text = value.text || ''; this.textarea.val(text); this.textline.html(text); return text } else { return ''; } } setStyle (value: Cell | null): void { let attrs = {width: this.textarea.style('width'), height: this.textarea.style('height')} this.textarea.styles(Object.assign(attrs, getStyleFromCell(value)), true) } clear () { // console.log('clear:>>>') this.el.hide(); this.target = null; this.value = null; this.textarea.val('') this.textline.html('') } private setTextareaRange (position: number) { setTimeout(() => { (this.textarea.el).setSelectionRange(position, position); (this.textarea.el).focus() }, 10) } private inputKeydown (evt: any) { if (evt.keyCode === 13) { evt.preventDefault() } } private inputChange (evt: any) { const v = evt.target.value if (this.value) { this.value.text = v } else { this.value = {text: v} } this.change(this.value) this.autocomplete(v); this.textline.html(v); this.reload() } private autocomplete (v: string) { if (v[0] === '=') { if (!v.includes('(')) { const search = v.substring(1) console.log(':::;search word:', search) this.suggest.search(this.editor, this.textarea, search); } else { this.suggest.hide() } } else { this.suggest.hide() } } reload () { // setTimeout(() => { if (this.target) { const { offsetTop, offsetLeft, offsetWidth, offsetHeight } = this.target this.editor.styles({left: `${offsetLeft - 1}px`, top: `${offsetTop - 1}px`}) this.textarea.styles({width: `${offsetWidth - 8}px`, height: `${offsetHeight - 2}px`}) let ow = this.textline.offset().width + 16 // console.log(maxWidth, ow, '>>>>') if (this.value) { if (this.value.wordWrap) { // 如果单元格自动换行,那么宽度固定,高度变化 // this.textarea.style('height', 'auto'); const h = (parseInt(ow / offsetWidth + '') + (ow % offsetWidth > 0 ? 1 : 0)) * this.defaultRowHeight; if (h > offsetHeight) { this.textarea.style('height', `${h}px`); } } else { const clientWidth = document.documentElement.clientWidth const maxWidth = clientWidth - offsetLeft - 24 if (ow > offsetWidth) { if (ow > maxWidth) { // console.log(':::::::::', ow, maxWidth) const h = (parseInt(ow / maxWidth + '') + (ow % maxWidth > 0 ? 1 : 0)) * this.defaultRowHeight; if (h > offsetHeight) { this.textarea.style('height', `${h}px`) } else { this.textarea.style('height', `${offsetHeight}px`) } ow = maxWidth } this.textarea.style('width', `${ow}px`) } } } this.el.show() } // }, 0) } } ================================================ FILE: src/local/editorbar.d.ts ================================================ import { Element } from "./base/element"; import { Cell } from "../core/cell"; export declare class Editorbar { el: Element; value: Cell | null; textarea: Element; label: Element; change: (v: Cell) => void; constructor(); set(title: string, value: Cell | null): void; setValue(value: Cell | null): void; input(evt: any): void; } ================================================ FILE: src/local/editorbar.ts ================================================ import { Element, h } from "./base/element"; import { Cell } from "../core/cell"; import { mouseMoveUp } from "./event" export class Editorbar { el: Element; value: Cell | null = null; // 选中的当前的cell textarea: Element; label: Element; change: (v: Cell) => void = (v) => {}; constructor () { this.el = h().class('spreadsheet-editor-bar').children([ h().class('spreadsheet-formula-bar').children([ this.label = h().class('spreadsheet-formula-label'), this.textarea = h('textarea').on('input', (evt) => this.input(evt)) ]), // h().class('spreadsheet-formular-bar-resizer').on('mousedown', this.mousedown) ]) } set (title: string, value: Cell | null) { this.label.html(title) this.setValue(value) } setValue (value: Cell | null) { this.value = value this.textarea.val(value && value.text || '') } input (evt: any) { const v = evt.target.value if (this.value) { this.value.text = v } else { this.value = {text: v} } this.change(this.value) } } ================================================ FILE: src/local/event.d.ts ================================================ export declare function bind(name: string, fn: (evt: T) => void, target?: any): void; export declare function unbind(name: string, fn: (evt: T) => void, target?: any): void; export declare function mouseMoveUp(movefunc: (evt: T) => void, upfunc: (evt: T) => void): void; ================================================ FILE: src/local/event.ts ================================================ export function bind(name: string, fn: (evt: T) => void, target: any = window) { target.addEventListener(name, fn) } export function unbind(name: string, fn: (evt: T) => void, target: any = window) { target.removeEventListener(name, fn) } export function mouseMoveUp (movefunc: (evt: T) => void, upfunc: (evt: T) => void) { bind('mousemove', movefunc) const up = (evt: T) => { unbind('mousemove', movefunc) unbind('mouseup', up) upfunc(evt) } bind('mouseup', up) } ================================================ FILE: src/local/index.d.ts ================================================ import { Spreadsheet, SpreadsheetOptions, SpreadsheetData } from '../core/index'; import '../style/index.less'; import { Table } from './table'; import { Toolbar } from './toolbar'; import { Editorbar } from './editorbar'; export interface Options extends SpreadsheetOptions { height?: () => number; mode?: 'design' | 'write' | 'read'; } export declare class LocalSpreadsheet { ss: Spreadsheet; refs: { [key: string]: HTMLElement; }; table: Table; toolbar: Toolbar | null; editorbar: Editorbar | null; bindEl: HTMLElement; options: Options; _change: (data: SpreadsheetData) => void; constructor(el: HTMLElement, options?: Options); loadData(data: SpreadsheetData): LocalSpreadsheet; change(cb: (data: SpreadsheetData) => void): LocalSpreadsheet; private render; private toolbarChange; private editorbarChange; private editorChange; private clickCell; } ================================================ FILE: src/local/index.ts ================================================ import { Spreadsheet, SpreadsheetOptions, SpreadsheetData } from '../core/index' import '../style/index.less' import { Cell, getStyleFromCell } from '../core/cell'; import { Format } from '../core/format'; import { Font } from '../core/font'; import { Editor } from './editor'; import { Selector } from './selector'; import { Table } from './table'; import { Toolbar } from './toolbar'; import { Editorbar } from './editorbar'; import { h, Element } from './base/element' export interface Options extends SpreadsheetOptions { height?: () => number; mode?: 'design' | 'write' | 'read'; } export class LocalSpreadsheet { ss: Spreadsheet; refs: {[key: string]: HTMLElement} = {}; table: Table; toolbar: Toolbar | null = null; editorbar: Editorbar | null = null; bindEl: HTMLElement options: Options; _change: (data: SpreadsheetData) => void = () => {} constructor (el: HTMLElement, options: Options = {}) { this.bindEl = el this.options = Object.assign({mode: 'design'}, options) // clear content in el this.bindEl && (this.bindEl.innerHTML = '') this.ss = new Spreadsheet(options); // console.log('::::>>>select:', this.ss.select) if (this.options.mode === 'design') { this.editorbar = new Editorbar() this.editorbar.change = (v) => this.editorbarChange(v) this.toolbar = new Toolbar(this.ss); this.toolbar.change = (key, v) => this.toolbarChange(key, v) this.toolbar.undo = () => { // console.log('undo::') return this.table.undo() } this.toolbar.redo = () => { // console.log('redo::') return this.table.redo() } } let bodyHeightFn = (): number => { if (this.options.height) { return this.options.height() } return document.documentElement.clientHeight - 24 - 41 - 26 } let bodyWidthFn = (): number => { return this.bindEl.offsetWidth } this.table = new Table(this.ss, Object.assign({height: bodyHeightFn, width: bodyWidthFn, mode: this.options.mode})); this.table.change = (data) => { this.toolbar && this.toolbar.setRedoAble(this.ss.isRedo()) this.toolbar && this.toolbar.setUndoAble(this.ss.isUndo()) this._change(data) } this.table.editorChange = (v) => this.editorChange(v) this.table.clickCell = (rindex, cindex, cell) => this.clickCell(rindex, cindex, cell) this.render(); } loadData (data: SpreadsheetData): LocalSpreadsheet { // reload until waiting main thread setTimeout(() => { this.ss.data = data this.table.reload() }, 1) return this } change (cb: (data: SpreadsheetData) => void): LocalSpreadsheet { this._change = cb return this; } private render (): void { this.bindEl.appendChild(h().class('spreadsheet').children([ h().class('spreadsheet-bars').children([ this.toolbar && this.toolbar.el || '', this.editorbar && this.editorbar.el || '', ]), this.table.el ]).el); } private toolbarChange (k: keyof Cell, v: any) { if (k === 'merge') { this.table.merge(); return; } else if (k === 'clearformat') { this.table.clearformat(); return ; } else if (k === 'paintformat') { this.table.copyformat(); return ; } this.table.setCellAttr(k, v); } private editorbarChange (v: Cell) { this.table.setValueWithText(v) } private editorChange (v: Cell) { this.editorbar && this.editorbar.setValue(v) } private clickCell (rindex: number, cindex: number, v: Cell | null) { const cols = this.ss.cols() this.editorbar && this.editorbar.set(`${cols[cindex].title}${rindex + 1}`, v) this.toolbar && this.toolbar.set(this.table.td(rindex, cindex), v) } } ================================================ FILE: src/local/resizer.d.ts ================================================ import { Element } from "./base/element"; export declare class Resizer { vertical: boolean; change: (index: number, distance: number) => void; el: Element; resizer: Element; resizerLine: Element; moving: boolean; index: number; constructor(vertical: boolean, change: (index: number, distance: number) => void); set(target: any, index: number, scroll: number): void; mousedown(evt: any): void; } ================================================ FILE: src/local/resizer.ts ================================================ import { Element, h } from "./base/element"; import { mouseMoveUp } from './event'; export class Resizer { el: Element; resizer: Element; resizerLine: Element; moving: boolean = false; index: number = 0; constructor (public vertical: boolean, public change: (index: number, distance: number) => void) { this.el = h().class('spreadsheet-resizer-wrapper').children([ this.resizer = h().class(`spreadsheet-resizer ${vertical ? 'vertical' : 'horizontal'}`) .on('mousedown', (evt: Event) => this.mousedown(evt)), this.resizerLine = h().class(`spreadsheet-resizer-line ${vertical ? 'vertical' : 'horizontal'}`).hide() ]) } set (target: any, index: number, scroll: number) { if (this.moving) return ; this.index = index const { vertical } = this const { offsetLeft, offsetTop, offsetHeight, offsetWidth, parentNode } = target this.resizer.styles({ left: `${vertical ? offsetLeft + offsetWidth - 5 - scroll : offsetLeft}px`, top: `${vertical ? offsetTop : offsetTop + offsetHeight - 5 + 24 - scroll}px`, width: `${vertical ? 5 : offsetWidth}px`, height: `${vertical ? offsetHeight : 5}px` }) this.resizerLine.styles({ left: `${vertical ? offsetLeft + offsetWidth - scroll : offsetLeft}px`, top: `${vertical ? offsetTop : offsetTop + offsetHeight + 24 - scroll}px`, width: `${vertical ? 0 : parentNode.parentNode.parentNode.parentNode.parentNode.nextSibling.offsetWidth - 15}px`, height: `${vertical ? parentNode.parentNode.parentNode.parentNode.nextSibling.offsetHeight + parentNode.offsetHeight : 0}px` }) // this.el.show() } mousedown (evt: any) { let startEvt = evt; let distance = 0; this.resizerLine.show() mouseMoveUp((e: any) => { this.moving = true if (startEvt !== null && e.buttons === 1) { if (this.vertical) { const d = e.x - startEvt.x distance += d this.resizer.style('left', `${this.resizer.offset().left + d}px`) this.resizerLine.style('left', `${this.resizerLine.offset().left + d}px`) } else { const d = e.y - startEvt.y distance += d this.resizer.style('top', `${this.resizer.offset().top + d}px`) this.resizerLine.style('top', `${this.resizerLine.offset().top + d}px`) } startEvt = e } }, (e: any) => { this.change(this.index, distance) startEvt = null this.resizerLine.hide() distance = 0 this.moving = false }) } } ================================================ FILE: src/local/selector.d.ts ================================================ import { Element } from "./base/element"; import { Spreadsheet } from "../core/index"; import { Table } from './table'; export declare class Selector { ss: Spreadsheet; table: Table; topEl: Element; rightEl: Element; bottomEl: Element; leftEl: Element; areaEl: Element; cornerEl: Element; copyEl: Element; el: Element; _offset: { left: number; top: number; width: number; height: number; }; startTarget: any; endTarget: any; change: () => void; changeCopy: (evt: any, arrow: 'bottom' | 'top' | 'left' | 'right', startRow: number, startCol: number, stopRow: number, stopCol: number) => void; constructor(ss: Spreadsheet, table: Table); mousedown(evt: any): void; setCurrentTarget(target: HTMLElement): void; private cornerMousedown; reload(): void; private setOffset; private rowsHeight; private colsWidth; } export declare class DashedSelector { el: Element; constructor(); set(selector: Selector): void; hide(): void; } ================================================ FILE: src/local/selector.ts ================================================ import { Element, h } from "./base/element"; import { bind, mouseMoveUp } from './event'; import { Spreadsheet } from "../core/index"; import { Table } from './table'; export class Selector { topEl: Element; rightEl: Element; bottomEl: Element; leftEl: Element; areaEl: Element; cornerEl: Element; copyEl: Element; el: Element; _offset = {left: 0, top: 0, width: 0, height: 0}; startTarget: any; endTarget: any; change: () => void = () => {}; changeCopy: (evt: any, arrow: 'bottom' | 'top' | 'left' | 'right', startRow: number, startCol: number, stopRow: number, stopCol: number) => void = (evt, arrow, startRow, startCol, stopRow, stopCol) => {}; constructor (public ss: Spreadsheet, public table: Table) { this.topEl = h().class('top-border'); this.rightEl = h().class('right-border'); this.bottomEl = h().class('bottom-border'); this.leftEl = h().class('left-border'); this.areaEl = h().class('area-border'); this.cornerEl = h().class('corner').on('mousedown', (evt) => this.cornerMousedown(evt)); this.copyEl = h().class('copy-border'); this.el = h().class('spreadsheet-borders').children([ this.topEl, this.rightEl, this.bottomEl, this.leftEl, this.areaEl, this.cornerEl, this.copyEl.hide(), ]).hide() } mousedown (evt: any) { // console.log('>>>>>>>>selector>>') // console.log(this, evt, evt.type, evt.detail, evt.buttons, evt.button) if (evt.detail === 1 && evt.target.getAttribute('type') === 'cell') { // console.log(evt.shiftKey) if (evt.shiftKey) { this.endTarget = evt.target this.setOffset() return } // Object.assign(this, {startTarget: evt.target, endTarget: evt.target}) // this.setOffset() this.setCurrentTarget(evt.target) mouseMoveUp((e: any) => { if (e.buttons === 1 && e.target.getAttribute('type') === 'cell') { this.endTarget = e.target this.setOffset() } }, (e) => { this.change() }) // show el this.el.show() } } setCurrentTarget (target: HTMLElement) { Object.assign(this, {startTarget: target, endTarget: target}) this.setOffset() } private cornerMousedown (evt: any) { const { select } = this.ss if (select === null) { return ; } const [stopRow, stopCol] = select.stop; const [startRow, startCol] = select.start; let boxRange:['bottom' | 'top' | 'left' | 'right', number, number, number, number] | null = null; mouseMoveUp((e: any) => { const rowIndex = e.target.getAttribute('row-index') const colIndex = e.target.getAttribute('col-index') if (rowIndex && colIndex) { this.copyEl.show(); let rdiff = stopRow - rowIndex let cdiff = stopCol - colIndex let _rdiff = startRow - rowIndex let _cdiff = startCol - colIndex const {left, top, height, width} = this._offset; // console.log(rdiff, cdiff, ',,,', _rdiff, _cdiff) if (rdiff < 0) { // bottom // console.log('FCK=>bottom', this.rowsHeight(stopRow, stopRow + Math.abs(rdiff)), rdiff) this.copyEl.styles({ left: `${left - 1}px`, top: `${top - 1}px`, width: `${width - 1}px`, height: `${this.rowsHeight(stopRow - select.rowLen() + 1, stopRow + Math.abs(rdiff)) - 1}px`}); boxRange = ['bottom', stopRow + 1, startCol, stopRow + Math.abs(rdiff), stopCol] } else if (cdiff < 0) { // right // console.log('FCK=>right') this.copyEl.styles({ left: `${left - 1}px`, top: `${top - 1}px`, width: `${this.colsWidth(stopCol - select.colLen() + 1, stopCol + Math.abs(cdiff)) - 1}px`, height: `${height - 1}px`}); boxRange = ['right', startRow, stopCol + 1, stopRow, stopCol + Math.abs(cdiff)] } else if (_rdiff > 0) { // top // console.log('FCK=>top') const h = this.rowsHeight(startRow - _rdiff, startRow - 1) this.copyEl.styles({ left: `${left - 1}px`, top: `${top - h - 1}px`, width: `${width - 1}px`, height: `${h - 1}px`}); boxRange = ['top', startRow - _rdiff, startCol, startRow - 1, stopCol] } else if (_cdiff > 0) { // left // console.log('FCK=>left') const w = this.colsWidth(startCol - _cdiff, startCol - 1) this.copyEl.styles({ left: `${left - w - 1}px`, top: `${top - 1}px`, width: `${w - 1}px`, height: `${height - 1}px`}); boxRange = ['left', startRow, startCol - _cdiff, stopRow, startCol - 1] } else { this.copyEl.styles({ left: `${left - 1}px`, top: `${top - 1}px`, width: `${width - 1}px`, height: `${height - 1}px`}); boxRange = null } } }, (e) => { this.copyEl.hide() if (boxRange !== null) { const [arrow, startRow, startCol, stopRow, stopCol] = boxRange this.changeCopy(e, arrow, startRow, startCol, stopRow, stopCol) } }); } reload () { this.setOffset() } private setOffset () { if (this.startTarget === undefined) return ; let { select } = this.ss // console.log('select: ', select, this.table) if (select) { // console.log('clear>>>>>:::') // clear const [minRow, minCol] = select.start const [maxRow, maxCol] = select.stop _forEach(minRow, maxRow, this.table.firsttds, (e) => { e.deactive() }) _forEach(minCol, maxCol, this.table.ths, (e) => { e.deactive() }) } select = this.ss.buildSelect(this.startTarget, this.endTarget) const [minRow, minCol] = select.start const [maxRow, maxCol] = select.stop // let height = 0, width = 0; const height = this.rowsHeight(minRow, maxRow, (e) => e.active()) // _forEach(minRow, maxRow, this.table.firsttds, (e) => { // e.active() // height += parseInt(e.offset().height) // }) // height /= 2 const width = this.colsWidth(minCol, maxCol, (e) => e.active()) // _forEach(minCol, maxCol, this.table.ths, (e) => { // e.active() // width += parseInt(e.offset().width) // }) // console.log('>>', minRow, minCol, maxRow, maxCol, height, width) const td = this.table.td(minRow, minCol) if (td) { // console.log('td:', td) const {left, top} = td.offset() this._offset = {left, top, width, height}; this.topEl.styles({left: `${left - 1}px`, top: `${top - 1}px`, width: `${width + 1}px`, height: '2px'}) this.rightEl.styles({left: `${left + width - 1}px`, top: `${top - 1}px`, width: '2px', height: `${height}px`}) this.bottomEl.styles({left: `${left - 1}px`, top: `${top + height - 1}px`, width: `${width}px`, height: '2px'}) this.leftEl.styles({left: `${left - 1}px`, top: `${top - 1}px`, width: '2px', height: `${height}px`}) this.areaEl.styles({left: `${left}px`, top: `${top}px`, width: `${width - 2}px`, height: `${height - 2}px`}) this.cornerEl.styles({left: `${left + width - 5}px`, top: `${top + height - 5}px`}) } } private rowsHeight (minRow:number, maxRow:number, cb: (e: Element) => void = (e) => {}): number { let height = 0 _forEach(minRow, maxRow, this.table.firsttds, (e) => { cb(e) height += parseInt(e.offset().height) }) height /= 2 return height } private colsWidth (minCol: number, maxCol: number, cb: (e: Element) => void = (e) => {}): number { let width = 0 _forEach(minCol, maxCol, this.table.ths, (e) => { cb(e) width += parseInt(e.offset().width) }) return width } } const _forEach = (start: number, stop: number, elements: {[key: string]: Array | Element}, cb: (e: Element) => void): void => { for (let i = start; i <= stop; i++) { const es = elements[i + '']; if (es) { if (es instanceof Element) { cb(es) } else { es.forEach(e => cb(e)) } } } } export class DashedSelector { el: Element; constructor () { this.el = h().class('spreadsheet-borders dashed').hide(); } set (selector: Selector) { if (selector._offset) { const { left, top, width, height } = selector._offset; this.el .style('left', `${left - 2}px`) .style('top', `${top - 2}px`) .style('width', `${width}px`) .style('height', `${height}px`) .show(); } } hide () { this.el.hide(); } } ================================================ FILE: src/local/table.d.ts ================================================ import { Element } from "./base/element"; import { Spreadsheet, SpreadsheetData } from '../core/index'; import { Editor } from './editor'; import { Selector, DashedSelector } from './selector'; import { Resizer } from './resizer'; import { ContextMenu } from "./contextmenu"; import { Cell } from "../core/cell"; interface Map { [key: string]: T; } export interface TableOption { height: () => number; width: () => number; mode: 'design' | 'write' | 'read'; } export declare class Table { options: TableOption; cols: Map>; firsttds: Map>; tds: Map; ths: Map; ss: Spreadsheet; formulaCellIndexs: Set; el: Element; header: Element; body: Element; fixedLeftBody: Element | null; editor: Editor | null; rowResizer: Resizer | null; colResizer: Resizer | null; contextmenu: ContextMenu | null; selector: Selector; dashedSelector: DashedSelector; state: 'copy' | 'cut' | 'copyformat' | null; currentIndexs: [number, number] | null; focusing: boolean; change: (data: SpreadsheetData) => void; editorChange: (v: Cell) => void; clickCell: (rindex: number, cindex: number, v: Cell | null) => void; constructor(ss: Spreadsheet, options: TableOption); reload(): void; private moveLeft; private moveUp; private moveDown; private moveRight; private moveSelector; setValueWithText(v: Cell): void; setTdWithCell(rindex: number, cindex: number, cell: Cell, autoWordWrap?: boolean): void; setCellAttr(k: keyof Cell, v: any): void; undo(): boolean; redo(): boolean; private setTdStylesAndAttrsAndText; copy(): void; cut(): void; copyformat(): void; paste(): void; clearformat(): void; merge(): void; insert(type: 'row' | 'col', amount: number): void; td(rindex: number, cindex: number): Element; private selectorChange; private selectorChangeCopy; private renderCell; private _renderCell; private reRenderFormulaCells; private setRowHeight; private setTdStyles; private setTdAttrs; private changeRowHeight; private changeRowResizer; private changeColResizer; private buildColGroup; private buildFixedLeft; private buildHeader; private mousedownCell; private editCell; private buildBody; private addRow; private firsttdsPush; } export {}; ================================================ FILE: src/local/table.ts ================================================ import { Element, h } from "./base/element"; import { Spreadsheet, SpreadsheetData } from '../core/index' import { Editor } from './editor'; import { Selector, DashedSelector } from './selector'; import { Resizer } from './resizer'; import { Editorbar } from "./editorbar"; import { Toolbar } from "./toolbar"; import { ContextMenu } from "./contextmenu"; import { Cell, getStyleFromCell } from "../core/cell"; import { formatRenderHtml } from "../core/format"; import { formulaRender } from "../core/formula"; import { bind } from "./event"; interface Map { [key: string]: T } export interface TableOption { height: () => number, width: () => number, mode: 'design' | 'write' | 'read'; } export class Table { cols: Map> = {}; firsttds: Map> = {}; tds: Map = {}; ths: Map = {}; ss: Spreadsheet; formulaCellIndexs: Set = new Set(); // 表达式单元格set el: Element; header: Element; body: Element; fixedLeftBody: Element | null = null; editor: Editor | null = null; rowResizer: Resizer | null = null; colResizer: Resizer | null = null; contextmenu: ContextMenu | null = null; selector: Selector; dashedSelector: DashedSelector; state: 'copy' | 'cut' | 'copyformat' | null = null; currentIndexs: [number, number] | null = null; // 当前用户是否焦点再table上 focusing: boolean = false; // change change: (data: SpreadsheetData) => void = () => {} editorChange: (v: Cell) => void = (v) => {} clickCell: (rindex: number, cindex: number, v: Cell | null) => void = (rindex, cindex, v) => {} constructor (ss: Spreadsheet, public options: TableOption) { this.ss = ss; this.ss.change = (data) => { this.change(data) } if (options.mode !== 'read') { this.editor = new Editor(ss.defaultRowHeight(), ss.formulas) this.editor.change = (v: Cell) => this.editorChange(v) } if (options.mode === 'design') { this.rowResizer = new Resizer(false, (index, distance) => this.changeRowResizer(index, distance)) this.colResizer = new Resizer(true, (index, distance) => this.changeColResizer(index, distance)) this.contextmenu = new ContextMenu(this) } this.selector = new Selector(this.ss, this); this.selector.change = () => this.selectorChange(); this.selector.changeCopy = (e, arrow, startRow, startCol, stopRow, stopCol) => { this.selectorChangeCopy(e, arrow, startRow, startCol, stopRow, stopCol); } this.dashedSelector = new DashedSelector(); this.el = h().class('spreadsheet-table').children([ this.colResizer && this.colResizer.el || '', this.rowResizer && this.rowResizer.el || '', this.buildFixedLeft(), this.header = this.buildHeader(), this.body = this.buildBody() ]).on('contextmenu', (evt) => { evt.returnValue = false evt.preventDefault(); }); bind('resize', (evt: any) => { this.header.style('width', `${this.options.width()}px`) this.body.style('width', `${this.options.width()}px`) if (this.options.mode !== 'read') { this.body.style('height', `${this.options.height()}px`) } }) bind('click', (evt: any) => { // console.log('::::::::', this.el.contains(evt.target)) this.focusing = this.el.parent().contains(evt.target) }) // bind ctrl + c, ctrl + x, ctrl + v bind('keydown', (evt: any) => { if (!this.focusing) { return } // console.log('::::::::', evt) if (!this.focusing) return; // ctrlKey if (evt.ctrlKey && evt.target.type !== 'textarea' && this.options.mode !== 'read') { // ctrl + c if (evt.keyCode === 67) { this.copy(); evt.returnValue = false } // ctrl + x if (evt.keyCode === 88) { this.cut(); evt.returnValue = false } // ctrl + v if (evt.keyCode === 86) { this.paste(); evt.returnValue = false } } else { // console.log('>>>>>>>>>>>>>>', evt) switch (evt.keyCode) { case 37: // left this.moveLeft() evt.returnValue = false break; case 38: // up this.moveUp() evt.returnValue = false break; case 39: // right this.moveRight() evt.returnValue = false break; case 40: // down this.moveDown() evt.returnValue = false break; case 9: // tab this.moveRight(); evt.returnValue = false break; case 13: this.moveDown(); evt.returnValue = false break; } // 输入a-zA-Z1-9 if (this.options.mode !== 'read') { if (evt.keyCode >= 65 && evt.keyCode <= 90 || evt.keyCode >= 48 && evt.keyCode <= 57 || evt.keyCode >= 96 && evt.keyCode <= 105 || evt.keyCode == 187) { // if (this.currentIndexs) { // console.log('::::::::', evt.target.type) if (evt.target.type !== 'textarea') { this.ss.cellText(evt.key, (rindex, cindex, cell) => { if (this.editor) { const td = this.td(rindex, cindex) td.html(this.renderCell(rindex, cindex, cell)) this.editor.set(td.el, this.ss.currentCell()) } }) } } } } }); } reload () { this.firsttds = {} this.el.html('') this.el.children([ this.colResizer && this.colResizer.el || '', this.rowResizer && this.rowResizer.el || '', this.buildFixedLeft(), this.header = this.buildHeader(), this.body = this.buildBody() ]); } private moveLeft () { if (this.currentIndexs && this.currentIndexs[1] > 0) { this.currentIndexs[1] -= 1 this.moveSelector('left') } } private moveUp () { if (this.currentIndexs && this.currentIndexs[0] > 0) { this.currentIndexs[0] -= 1 this.moveSelector('up') } } private moveDown () { if (this.currentIndexs && this.currentIndexs[0] < this.ss.rows(this.options.mode === 'read').length) { this.currentIndexs[0] += 1 this.moveSelector('down') } } private moveRight () { if (this.currentIndexs && this.currentIndexs[1] < this.ss.cols().length) { this.currentIndexs[1] += 1 this.moveSelector('right') } } // 移动选框 private moveSelector (direction: 'right' | 'left' | 'up' | 'down') { if (this.currentIndexs) { const [rindex, cindex] = this.currentIndexs const td = this.td(rindex, cindex) // console.log('move.td:', td) if (td) { this.selector.setCurrentTarget(td.el) const bodyWidth = this.options.width() const bodyHeight = this.options.height() const {left, top, width, height} = td.offset() // console.log(this.body.el.scrollLeft, ', body-width:', bodyWidth, ', left:', left, ', width=', width) const leftDiff = left + width - bodyWidth if (leftDiff > 0 && direction === 'right') { this.body.el.scrollLeft = leftDiff + 15 } if (direction === 'left' && this.body.el.scrollLeft + 60 > left) { this.body.el.scrollLeft -= (this.body.el.scrollLeft + 60 - left) } if (direction === 'up' && this.body.el.scrollTop > top) { this.body.el.scrollTop -= (this.body.el.scrollTop - top) } if (direction === 'down' && top + height - bodyHeight > 0) { this.body.el.scrollTop = top + height - bodyHeight + 15; } this.mousedownCell(rindex, cindex) } } } setValueWithText (v: Cell) { // console.log('setValueWithText: v = ', v) if (this.currentIndexs) { this.ss.cellText(v.text, (rindex, cindex, cell) => { this.td(rindex, cindex).html(this.renderCell(rindex, cindex, cell)) }) } this.editor && this.editor.setValue(v) } setTdWithCell (rindex: number, cindex: number, cell: Cell, autoWordWrap = true) { this.setTdStyles(rindex, cindex, cell); this.setRowHeight(rindex, cindex, autoWordWrap); this.td(rindex, cindex).html(this.renderCell(rindex, cindex, cell)); } setCellAttr (k: keyof Cell, v: any) { // console.log('::k:', k, '::v:', v) this.ss.cellAttr(k, v, (rindex, cindex, cell) => { // console.log(':rindex:', rindex, '; cindex:', cindex, '; cell: ', cell) this.setTdWithCell(rindex, cindex, cell, k === 'wordWrap' && v); }) this.editor && this.editor.setStyle(this.ss.currentCell()) } undo (): boolean { return this.ss.undo((rindex, cindex, cell) => { // console.log('>', rindex, ',', cindex, '::', cell) this.setTdStylesAndAttrsAndText(rindex, cindex, cell) }) } redo (): boolean { return this.ss.redo((rindex, cindex, cell) => { this.setTdStylesAndAttrsAndText(rindex, cindex, cell) }) } private setTdStylesAndAttrsAndText (rindex: number, cindex: number, cell: Cell) { let td = this.td(rindex, cindex); this.setTdStyles(rindex, cindex, cell); this.setTdAttrs(rindex, cindex, cell); // console.log('txt>>>:', this.renderCell(rindex, cindex, cell)) td.html(this.renderCell(rindex, cindex, cell)); } copy () { this.ss.copy(); this.dashedSelector.set(this.selector); this.state = 'copy'; } cut () { this.ss.cut(); this.dashedSelector.set(this.selector); this.state = 'cut'; } copyformat () { this.ss.copy(); this.dashedSelector.set(this.selector); this.state = 'copyformat'; } paste () { // console.log('state: ', this.state, this.ss.select) if (this.state !== null && this.ss.select) { this.ss.paste((rindex, cindex, cell) => { // console.log('rindex: ', rindex, ', cindex: ', cindex); let td = this.td(rindex, cindex); this.setTdStyles(rindex, cindex, cell); this.setTdAttrs(rindex, cindex, cell); if (this.state === 'cut' || this.state === 'copy') { td.html(this.renderCell(rindex, cindex, cell)); } }, this.state, (rindex, cindex, cell) => { let td = this.td(rindex, cindex); this.setTdStyles(rindex, cindex, cell); this.setTdAttrs(rindex, cindex, cell); td.html(''); }); this.selector.reload(); } if (this.state === 'copyformat') { this.state = null; } else if (this.state === 'cut') { this.state = null; } else if (this.state === 'copy') { // this.ss.paste() } this.dashedSelector.hide(); } clearformat () { this.ss.clearformat((rindex, cindex, cell) => { this.td(rindex, cindex) .removeAttr('rowspan') .removeAttr('colspan') .styles({}, true) .show(true); }) } merge () { this.ss.merge((rindex, cindex, cell) => { // console.log(rindex, cindex, '>>>', this.table.td(rindex, cindex)) this.setTdAttrs(rindex, cindex, cell).show(true) }, (rindex, cindex, cell) => { this.setTdAttrs(rindex, cindex, cell).show(true) }, (rindex, cindex, cell) => { let td = this.td(rindex, cindex) !cell.invisible ? td.show(true) : td.hide() }) } // insert insert (type: 'row' | 'col', amount: number) { if (type === 'col') { // insert col } else if (type === 'row') { // insert row } this.ss.insert(type, amount, (rindex, cindex, cell) => { this.setTdStylesAndAttrsAndText(rindex, cindex, cell) }) } td (rindex: number, cindex: number): Element { const td = this.tds[`${rindex}_${cindex}`] return td } private selectorChange () { if (this.state === 'copyformat') { this.paste(); } } private selectorChangeCopy (evt: any, arrow: 'bottom' | 'top' | 'left' | 'right', startRow: number, startCol: number, stopRow: number, stopCol: number) { this.ss.batchPaste(arrow, startRow, startCol, stopRow, stopCol, evt.ctrlKey, (rindex, cindex, cell) => { this.setTdStyles(rindex, cindex, cell); this.setTdAttrs(rindex, cindex, cell); this.td(rindex, cindex).html(this.renderCell(rindex, cindex, cell)); }) } private renderCell (rindex: number, cindex: number, cell: Cell | null): string { if (cell) { const setKey = `${rindex}_${cindex}` // console.log('text:', setKey, cell.text && cell.text) if (cell.text && cell.text[0] === '=') { this.formulaCellIndexs.add(setKey) } else { if (this.formulaCellIndexs.has(setKey)) { this.formulaCellIndexs.delete(setKey) } this.reRenderFormulaCells() } return formatRenderHtml(cell.format, this._renderCell(cell)) } return ''; } private _renderCell (cell: Cell | null): string { if (cell) { let text = cell.text || ''; return formulaRender(text, (rindex, cindex) => this._renderCell(this.ss.getCell(rindex, cindex))) } return ''; } private reRenderFormulaCells () { // console.log('formulaCellIndex: ', this.formulaCellIndexs) this.formulaCellIndexs.forEach(it => { let rcindexes = it.split('_') const rindex = parseInt(rcindexes[0]) const cindex = parseInt(rcindexes[1]) // console.log('>>>', this.ss.data, this.ss.getCell(rindex, cindex)) const text = this.renderCell(rindex, cindex, this.ss.getCell(rindex, cindex)) this.td(rindex, cindex).html(text); }) } private setRowHeight (rindex: number, cindex: number, autoWordWrap: boolean) { // console.log('rowHeight: ', this.td(rindex, cindex).offset().height, ', autoWordWrap:', autoWordWrap) // 遍历rindex行的所有单元格,计算最大高度 if (autoWordWrap === false) { return ; } const cols = this.ss.cols() const td = this.td(rindex, cindex) let h = td.offset().height console.log('h:', h) const tdRowspan = td.attr('rowspan') if (tdRowspan) { for (let i = 1; i < parseInt(tdRowspan); i++) { let firsttds = this.firsttds[(rindex + i) +''] firsttds && (h -= parseInt(firsttds[0].attr('height') || 0) + 1) } } // console.log('after.h:', h) this.changeRowHeight(rindex, h - 1); } private setTdStyles (rindex: number, cindex: number, cell: Cell): Element { return this.td(rindex, cindex).styles(getStyleFromCell(cell), true) } private setTdAttrs (rindex: number, cindex: number, cell: Cell): Element { return this.td(rindex, cindex) .attr('rowspan', cell.rowspan || 1) .attr('colspan', cell.colspan || 1); } private changeRowHeight (index: number, h: number) { if (h <= this.ss.defaultRowHeight()) return this.ss.row(index, h) const firstTds = this.firsttds[index+''] if (firstTds) { firstTds.forEach(td => td.attr('height', h)) } this.selector.reload() this.editor && this.editor.reload() } private changeRowResizer (index: number, distance: number) { const h = this.ss.row(index).height + distance this.changeRowHeight(index, h); } private changeColResizer (index: number, distance: number) { const w = this.ss.col(index).width + distance if (w <= 50) return this.ss.col(index, w) const cols = this.cols[index+''] if (cols) { cols.forEach(col => col.attr('width', w)) } this.selector.reload() this.editor && this.editor.reload() } private buildColGroup (lastColWidth: number): Element { const cols = this.ss.cols(); return h('colgroup').children([ h('col').attr('width', '60'), ...cols.map((col, index) => { let c = h('col').attr('width', col.width) this.cols[index+''] = this.cols[index+''] || [] this.cols[index+''].push(c) return c; }), h('col').attr('width', lastColWidth) ]) } private buildFixedLeft (): Element { const rows = this.ss.rows(this.options.mode === 'read'); return h().class('spreadsheet-fixed') .style('width', '60px') .children([ h().class('spreadsheet-fixed-header').child(h('table').child( h('thead').child( h('tr').child( h('th').child('-') ) ), )), this.fixedLeftBody = h().class('spreadsheet-fixed-body') .style('height', `${this.options.mode === 'read' ? 'auto' : this.options.height() - 18}px`) .children([ h('table').child( h('tbody').children( rows.map((row, rindex) => { let firstTd = h('td').attr('height', `${row.height}`).child(`${rindex + 1}`) .on('mouseover', (evt: Event) => this.rowResizer && this.rowResizer.set(evt.target, rindex, this.body.el.scrollTop)); this.firsttdsPush(rindex, firstTd) return h('tr').child(firstTd) }) ) ) ]) ]) } private buildHeader (): Element { const cols = this.ss.cols(); const thead = h('thead').child( h('tr').children([ h('th'), ...cols.map((col, index) => { let th = h('th').child(col.title).on('mouseover', (evt: Event) => { console.log(evt) this.colResizer && this.colResizer.set(evt.target, index, this.body.el.scrollLeft) }); this.ths[index + ''] = th; return th; }), h('th') ] )) return h().class('spreadsheet-header').style('width', `${this.options.width()}px`).children([ h('table').children([this.buildColGroup(15), thead]) ]) } private mousedownCell (rindex: number, cindex: number) { if (this.editor) { const editorValue = this.editor.value if (this.currentIndexs && this.editor.target && editorValue) { // console.log(':::editorValue:', editorValue) const oldCell = this.ss.cellText(editorValue.text, (_rindex, _cindex, _cell: Cell) => { this.td(_rindex, _cindex).html(this.renderCell(_rindex, _cindex, _cell)) }); // const oldTd = this.td(this.currentIndexs[0], this.currentIndexs[1]); // oldTd.html(this.renderCell(editorValue)) if (oldCell) { // 设置内容之后,获取高度设置行高 if (oldCell.wordWrap) { this.setRowHeight(this.currentIndexs[0], this.currentIndexs[1], true) } // console.log('old.td.offset:', oldCell) // this.editorChange(oldCell) } } this.editor.clear() } this.currentIndexs = [rindex, cindex] const cCell = this.ss.currentCell([rindex, cindex]) this.clickCell(rindex, cindex, cCell) } private editCell(rindex: number, cindex: number) { const td = this.td(rindex, cindex) this.editor && this.editor.set(td.el, this.ss.currentCell()) } private buildBody () { const rows = this.ss.rows(this.options.mode === 'read'); const cols = this.ss.cols(); const mousedown = (rindex: number, cindex: number, evt: any) => { const {select} = this.ss if (evt.button === 2) { // show contextmenu console.log(':::evt:', evt) this.contextmenu && this.contextmenu.set(evt) if (select && select.contains(rindex, cindex)) { return } } // left key this.selector.mousedown(evt) this.mousedownCell(rindex, cindex) this.focusing = true } const dblclick = (rindex: number, cindex: number) => { this.editCell(rindex, cindex) } const scrollFn = (evt: any) => { this.header.el.scrollLeft = evt.target.scrollLeft this.fixedLeftBody && (this.fixedLeftBody.el.scrollTop = evt.target.scrollTop) // console.log('>>>>>>>>scroll...', this.header, evt.target.scrollLeft, evt.target.scrollHeight) } const tbody = h('tbody').children(rows.map((row, rindex) => { let firstTd = h('td').attr('height', `${row.height}`).child(`${rindex + 1}`); this.firsttdsPush(rindex, firstTd) return h('tr').children([ firstTd, ...cols.map((col, cindex) => { let cell = this.ss.getCell(rindex, cindex) let td = h('td') .child(this.renderCell(rindex, cindex, cell)) .attr('type', 'cell') .attr('row-index', rindex + '') .attr('col-index', cindex + '') .attr('rowspan', cell && cell.rowspan || 1) .attr('colspan', cell && cell.colspan || 1) .styles(getStyleFromCell(cell), true) .on('mousedown', (evt: any) => mousedown(rindex, cindex, evt)) .on('dblclick', dblclick.bind(null, rindex, cindex)); this.tds[`${rindex}_${cindex}`] = td return td; }), h('td') ]) })); return h().class('spreadsheet-body') .on('scroll', scrollFn) .style('height', `${this.options.mode === 'read' ? 'auto' : this.options.height()}px`) .style('width', `${this.options.width()}px`) .children([ h('table').children([this.buildColGroup(0), tbody]), this.editor && this.editor.el || '', this.selector.el, this.contextmenu && this.contextmenu.el || '', this.dashedSelector.el ] ) } // 向尾部添加行 private addRow (num = 1) { if (num > 0) { } } private firsttdsPush (index: number, el: Element) { this.firsttds[`${index}`] = this.firsttds[`${index}`] || [] this.firsttds[`${index}`].push(el) } } ================================================ FILE: src/local/toolbar.d.ts ================================================ import { Element } from "./base/element"; import { Spreadsheet } from "../core/index"; import { Cell } from '../core/cell'; import { Dropdown } from './base/dropdown'; export declare class Toolbar { ss: Spreadsheet; el: Element; defaultCell: Cell; target: Element | null; currentCell: Cell | null; elUndo: Element; elRedo: Element; elPaintformat: Element; elClearformat: Element; elFormat: Dropdown; elFont: Dropdown; elFontSize: Dropdown; elFontWeight: Element; elFontStyle: Element; elTextDecoration: Element; elColor: Dropdown; elBackgroundColor: Dropdown; elMerge: Element; elAlign: Dropdown; elValign: Dropdown; elWordWrap: Element; change: (key: keyof Cell, v: any) => void; redo: () => boolean; undo: () => boolean; constructor(ss: Spreadsheet); set(target: Element, cell: Cell | null): void; private setCell; private setCellStyle; setRedoAble(flag: boolean): void; setUndoAble(flag: boolean): void; private buildSeparator; private buildAligns; private buildValigns; private buildWordWrap; private buildFontWeight; private buildFontStyle; private buildTextDecoration; private buildMerge; private buildColor; private buildBackgroundColor; private buildUndo; private buildRedo; private buildPaintformat; private buildClearformat; private buildFormats; private buildFonts; private buildFontSizes; } ================================================ FILE: src/local/toolbar.ts ================================================ import { Element, h } from "./base/element"; import { Spreadsheet } from "../core/index"; import { Cell, getStyleFromCell, defaultCell } from '../core/cell'; import { Table } from './table'; import { buildItem, Item } from './base/item'; import { buildIcon } from './base/icon'; import { buildDropdown, Dropdown } from './base/dropdown'; import { buildMenu } from './base/menu'; import { buildColorPanel } from './base/colorPanel'; import { Font } from "../core/font"; import { Format } from "../core/format"; export class Toolbar { el: Element; defaultCell: Cell; target: Element | null = null; currentCell: Cell | null = null; elUndo: Element; elRedo: Element; elPaintformat: Element; elClearformat: Element; elFormat: Dropdown; elFont: Dropdown; elFontSize: Dropdown; elFontWeight: Element; elFontStyle: Element; elTextDecoration: Element; elColor: Dropdown; elBackgroundColor: Dropdown; elMerge: Element; elAlign: Dropdown; elValign: Dropdown; elWordWrap: Element; change: (key: keyof Cell, v: any) => void = (key, v) => {} redo: () => boolean = () => false undo: () => boolean = () => false constructor (public ss: Spreadsheet) { this.defaultCell = ss.data.cell this.el = h().class('spreadsheet-toolbar').child( buildMenu('horizontal').children([ this.elUndo = this.buildUndo(), this.elRedo = this.buildRedo(), this.elPaintformat = this.buildPaintformat(), this.elClearformat = this.buildClearformat(), this.elFormat = this.buildFormats(), this.buildSeparator(), this.elFont = this.buildFonts(), this.elFontSize = this.buildFontSizes(), this.buildSeparator(), this.elFontWeight = this.buildFontWeight(), this.elFontStyle = this.buildFontStyle(), this.elTextDecoration = this.buildTextDecoration(), this.elColor = this.buildColor(), this.buildSeparator(), this.elBackgroundColor = this.buildBackgroundColor(), this.elMerge = this.buildMerge(), this.buildSeparator(), this.elAlign = this.buildAligns(), this.elValign = this.buildValigns(), this.elWordWrap = this.buildWordWrap() ]) ) ; } set (target: Element, cell: Cell | null) { this.target = target this.setCell(cell) } private setCell (cell: Cell | null) { this.currentCell = cell this.setCellStyle() } private setCellStyle () { const { target, currentCell, defaultCell, ss } = this // console.log(':::', currentCell) if (target) { // target.clearStyle() // target.styles(getStyleFromCell(currentCell)) this.elFormat.title.html(ss.getFormat(currentCell !== null && currentCell.format || defaultCell.format).title); this.elFont.title.html(ss.getFont(currentCell !== null && currentCell.font || defaultCell.font).title); this.elFontSize.title.html((currentCell !== null && currentCell.fontSize || defaultCell.fontSize) + ''); this.elFontWeight.active(currentCell !== null && currentCell.bold !== undefined && currentCell.bold !== defaultCell.bold); this.elFontStyle.active(currentCell !== null && currentCell.italic !== undefined && currentCell.italic !== defaultCell.italic); this.elTextDecoration.active(currentCell !== null && currentCell.underline !== undefined && currentCell.underline !== defaultCell.underline); this.elColor.title.style('border-bottom-color', currentCell !== null && currentCell.color || defaultCell.color); this.elBackgroundColor.title.style('border-bottom-color', currentCell !== null && currentCell.backgroundColor || defaultCell.backgroundColor); (this.elAlign.title).replace(`align-${currentCell !== null && currentCell.align || defaultCell.align}`); (this.elValign.title).replace(`valign-${currentCell !== null && currentCell.valign || defaultCell.valign}`); this.elWordWrap.active(currentCell !== null && currentCell.wordWrap !== undefined && currentCell.wordWrap !== defaultCell.wordWrap); // console.log('select:', currentCell) if ((currentCell !== null && currentCell.rowspan && currentCell.rowspan > 1) || (currentCell !== null && currentCell.colspan && currentCell.colspan > 1)) { this.elMerge.active(true); } else { this.elMerge.active(false); } } } setRedoAble (flag: boolean) { flag ? this.elRedo.able() : this.elRedo.disabled() } setUndoAble (flag: boolean) { flag ? this.elUndo.able() : this.elUndo.disabled() } private buildSeparator (): Element { return h().class('spreadsheet-item-separator') } private buildAligns (): Dropdown { const titleIcon = buildIcon(`align-${this.defaultCell.align}`) const clickHandler = (it: string) => { titleIcon.replace(`align-${it}`) this.change('align', it) } return buildDropdown(titleIcon, '60px', [buildMenu().children( ['left', 'center', 'right'].map(it => buildItem() .child(buildIcon(`align-${it}`).style('text-align', 'center')) .on('click', clickHandler.bind(null, it)) ) )]) } private buildValigns (): Dropdown { const titleIcon = buildIcon(`valign-${this.defaultCell.valign}`) const clickHandler = (it: string) => { titleIcon.replace(`valign-${it}`) this.change('valign', it) } return buildDropdown(titleIcon, '60px', [buildMenu().children( ['top', 'middle', 'bottom'].map(it => buildItem() .child(buildIcon(`valign-${it}`).style('text-align', 'center')) .on('click', clickHandler.bind(null, it)) ) )]) } private buildWordWrap (): Element { return buildIconItem('textwrap', (is) => this.change('wordWrap', is)) } private buildFontWeight (): Element { return buildIconItem('bold', (is) => this.change('bold', is)) } private buildFontStyle (): Element { return buildIconItem('italic', (is) => this.change('italic', is)) } private buildTextDecoration (): Element { return buildIconItem('underline', (is) => this.change('underline', is)) } private buildMerge (): Element { return buildIconItem('merge', (is) => this.change('merge', is)) } private buildColor (): Dropdown { const clickHandler = (color: string) => { this.elColor.title.style('border-bottom-color', color) this.change('color', color) } return buildDropdown( buildIcon('text-color').styles({'border-bottom': `3px solid ${this.defaultCell.color}`, 'margin-top': '2px', height: '16px'}), 'auto', [buildColorPanel(clickHandler)]) } private buildBackgroundColor (): Dropdown { const clickHandler = (color: string) => { this.elBackgroundColor.title.style('border-bottom-color', color) this.change('backgroundColor', color) } return buildDropdown( buildIcon('cell-color').styles({'border-bottom': `3px solid ${this.defaultCell.backgroundColor}`, 'margin-top': '2px', height: '16px'}), 'auto', [buildColorPanel(clickHandler)]) } private buildUndo (): Element { return buildItem().child(buildIcon('undo')) .on('click', (evt) => { this.undo() ? this.elUndo.able() : this.elUndo.disabled() }) .disabled() } private buildRedo (): Element { return buildItem().child(buildIcon('redo')) .on('click', (evt) => { this.redo() ? this.elRedo.able() : this.elRedo.disabled() }) .disabled() } private buildPaintformat (): Element { return buildIconItem('paintformat', (is) => { this.change('paintformat', true); this.elPaintformat.deactive(); }) } private buildClearformat (): Element { return buildIconItem('clearformat', (is) => { this.change('clearformat', true); this.elClearformat.deactive(); }); } private buildFormats (): Dropdown { const clickHandler = (it: Format) => { this.elFormat.title.html(this.ss.getFormat(it.key).title); this.change('format', it.key) } return buildDropdown(this.ss.getFormat(this.defaultCell.format).title, '250px', [buildMenu().children( this.ss.formats.map(it => buildItem() .children([it.title, h().class('label').child(it.label||'')]) .on('click', clickHandler.bind(null, it)) ) )]) } private buildFonts (): Dropdown { const clickHandler = (it: Font) => { this.elFont.title.html(it.title) this.change('font', it.key) } return buildDropdown(this.ss.getFont(this.defaultCell.font).title, '170px', [buildMenu().children( this.ss.fonts.map(it => { return buildItem() .child(it.title) .on('click', clickHandler.bind(null, it)) }) )]) } private buildFontSizes (): Dropdown { const clickHandler = (it: number) => { this.elFontSize.title.html(`${it}`) this.change('fontSize', it) } return buildDropdown(this.defaultCell.fontSize + '', '70px', [buildMenu().children( [6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 30, 36].map(it => { return buildItem() .child(`${it}`) .on('click', clickHandler.bind(null, it)) }) )]) } } const buildIconItem = (iconName: string, change: (flag: boolean) => void) => { const el = buildItem().child(buildIcon(iconName)) el.on('click', (evt) => { let is = el.isActive() is ? el.deactive() : el.active() change(!is) }) return el; } ================================================ FILE: src/main.d.ts ================================================ import { LocalSpreadsheet, Options } from './local/index'; export default function xspreadsheet(el: HTMLElement, options?: Options): LocalSpreadsheet; declare global { interface Window { xspreadsheet: any; } } ================================================ FILE: src/main.ts ================================================ import { LocalSpreadsheet, Options } from './local/index'; export default function xspreadsheet (el: HTMLElement, options?: Options) { return new LocalSpreadsheet(el, options) } declare global { interface Window { xspreadsheet: any; } } window.xspreadsheet = xspreadsheet ================================================ FILE: src/style/index.less ================================================ @border-style: 1px solid #e0e2e4; @icon-size: 18px; body { margin: 0; } .spreadsheet { font-size: 14px; line-height: normal; user-select: none; -moz-user-select: none; font-family: Roboto, Helvetica, Arial, sans-serif; box-sizing: content-box; background: #fff; .spreadsheet-table { position: relative; background: #fff; } .spreadsheet-fixed { position: absolute; top: 0; left: 0; z-index: 10; background: #fff; .spreadsheet-fixed-body { overflow: hidden; } .spreadsheet-fixed-header { overflow: hidden; } } .spreadsheet-body { overflow: scroll; position: relative; } .spreadsheet-header { overflow: hidden; width: 100%; } .spreadsheet-header, .spreadsheet-body, .spreadsheet-fixed { table { table-layout: fixed; text-align: left; width: 100%; border-collapse: separate; border-spacing: 0; color: #000; td, th { transition: background .1s ease,color .1s ease; border-bottom: @border-style; border-right: @border-style; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; padding: 0 4px; // height: 22px; line-height: 22px; } td.active, th.active { background: rgba(75, 137, 255, .05)!important; } th { border-top: @border-style; text-align: center; } th, td:first-child { font-size: 12px; background: #f4f5f8; font-weight: normal; color: #666; } td:first-child, th:first-child { border-left: @border-style; text-align: center; } } } } .spreadsheet-editor { position: absolute; text-align: left; // z-index: 10; border: 2px solid rgb(75, 137, 255); line-height: 0; z-index: 10; textarea { box-sizing: content-box; border: none; padding: 0 3px; outline-width: 0; resize: none; text-align: start; // max-width: 500px; overflow-y: hidden; font-family: inherit; font-size: inherit; color: inherit; white-space: normal; word-wrap: break-word; line-height: 22px; margin: 0; } } .spreadsheet-suggest, .spreadsheet-contextmenu { position: absolute; box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15); background: #fff; z-index: 100; } .spreadsheet-resizer { position: absolute; z-index: 11; &.horizontal { cursor: row-resize; } &.vertical { cursor: col-resize; } } .spreadsheet-resizer-line { position: absolute; z-index: 100; &.horizontal { border-bottom: 2px dashed rgb(75, 137, 255); } &.vertical { border-right: 2px dashed rgb(75, 137, 255); } } .spreadsheet-borders { box-sizing: content-box; &.dashed { border: 2px dashed rgb(75, 137, 255); position: absolute; background: rgba(75, 137, 255, 0.03); } .left-border, .right-border, .bottom-border, .top-border, .area-border { position: absolute; font-size: 0; pointer-events: none; background: rgb(75, 137, 255); } .area-border { background: rgba(75, 137, 255, 0.03); } .corner { cursor: crosshair; font-size: 0; height: 5px; width: 5px; border: 2px solid rgb(255, 255, 255); position: absolute; bottom: -6px; right: -6px; background: rgb(75, 137, 255); // z-index: 12; } .copy-border { position: absolute; pointer-events: none; border: 1px dashed rgb(75, 137, 255); background: rgba(75, 137, 255, .03); } } .spreadsheet-paint-border { position: absolute; pointer-events: none; border: 1px dashed rgb(75, 137, 255); background: rgba(75, 137, 255, .03); } .spreadsheet-bars { .spreadsheet-toolbar { // width: 100%; height: 40px; text-align: left; padding: 0 60px; border-bottom: @border-style; background: #f5f6f7; >.spreadsheet-menu > .spreadsheet-item { margin: 7px 1px 0; } } .spreadsheet-editor-bar { width: 100%; height: 26px; line-height: 26px; position: relative; background: #fff; padding: 0; } } .spreadsheet-formula-bar { position: relative; height: 100%; .spreadsheet-formula-label { width: 60px; height: 100%; box-sizing: border-box; display: inline-block; text-align: center; // line-height: inherit; border-right: 1px solid #e0e2e4; background-color: #fff; font-size: 12px; color: #777; user-select: none; float: left; vertical-align: middle; } textarea { width: calc(~'100% - 60px'); height: 100%; box-sizing: border-box; font-family: inherit; font-size: inherit; padding: 4px 10px; position: relative; float: left; resize: none; overflow-y: hidden; border: none; outline-width: 0; margin: 0; line-height: 1.2rem; } } .spreadsheet-formula-bar-resizer { cursor: ns-resize; // border-bottom: 1px solid #c0c0c0; height: 4px; position: absolute; width: 100%; left: 0; bottom: 0; } .spreadsheet-menu { &.vertical { > .spreadsheet-item-separator { background: #e0e2e4; height: 1px; margin: 5px 0; } > .spreadsheet-item { padding: 2px 10px; border-radius: 0; // border-top: 1px solid #fafafa; } } &.horizontal { > .spreadsheet-item { display: inline-block; } > .spreadsheet-item-separator { display: inline-block; background: #e0e2e4; width: 1px; vertical-align: middle; height: 18px; margin: 0 3px; } } > .spreadsheet-item { border-radius: 2px; user-select: none; background: 0; border: 1px solid transparent; outline: none; height: 24px; color: rgba(0, 0, 0, .8); line-height: 24px; list-style: none; // font-weight: bold; cursor: default; &.disabled { pointer-events: none; opacity: 0.5; } &:not(.separator):hover, &.active { background: rgba(0, 0, 0, .08); // color: rgba(0, 0, 0, 0.7); .spreadsheet-icon-img { opacity: 0.7; } .spreadsheet-dropdown-icon { background-color: rgba(0, 0, 0, .15); opacity: 0.6; } } > .label { float: right; opacity: .8; } } } .spreadsheet-dropdown { position: relative; display: inline-block; width: auto!important; .spreadsheet-dropdown-content { position: absolute; top: calc(~'100% + 5px'); left: 0; z-index: 200; background: #fff; box-shadow: 1px 2px 5px 2px rgba(51,51,51,.15); width: auto; } .spreadsheet-dropdown-header { .spreadsheet-dropdown-title { padding: 0 5px; display: inline-block; } // > .spreadsheet-icon { // width: 18px; // height: 16px; // } .spreadsheet-dropdown-icon { display: inline-block; vertical-align: top; // border: 1px solid transparent; .spreadsheet-icon { width: 10px; .arrow-down { left: -7 * @icon-size - 4px; } } } } } .spreadsheet-color-panel { padding: 10px; width: 100%; table { border-collapse: separate; border-spacing: 0; border: none; } table tr td { padding: 0; border: none; } .color-cell { width: 20px; height: 20px; margin: 3px; &:hover { box-shadow: 0 0 2px rgba(0,0,0,.8); } } } .spreadsheet-icon { height: 18px; width: 18px; margin: 0px 4px 3px 3px; direction: ltr; text-align: left; user-select: none; vertical-align: middle; overflow: hidden; position: relative; display: inline-block; } .spreadsheet-icon-img { background-image: url('../assets/sprite.svg'); position: absolute; width: 262px; height: 444px; opacity: 0.55; &.undo { left: 0; top: 0; } &.redo { left: -1 * @icon-size; top: 0; } &.print { left: -2 * @icon-size; top: 0; } &.paintformat { left: -3 * @icon-size; top: 0; } &.clearformat { left: -4 * @icon-size; top: 0; } &.bold { left: -5 * @icon-size; top: 0; } &.italic { left: -6 * @icon-size; top: 0; } &.underline { left: -7 * @icon-size; top: 0; } &.strikethrough { left: -8 * @icon-size; top: 0; } &.text-color { left: -9 * @icon-size; top: 0; } &.cell-color { left: -10 * @icon-size; top: 0; } &.merge { left: -11 * @icon-size; top: 0; } &.align-left { left: -12 * @icon-size; top: 0; } &.align-center { left: -13 * @icon-size; top: 0; } &.align-right { left: 0; top: -1 * @icon-size; } &.valign-top { left: -1 * @icon-size; top: -1 * @icon-size; } &.valign-middle { left: -2 * @icon-size; top: -1 * @icon-size; } &.valign-bottom { left: -3 * @icon-size; top: -1 * @icon-size; } &.textwrap { left: -4 * @icon-size; top: -1 * @icon-size; } &.autofilter { left: -5 * @icon-size; top: -1 * @icon-size; } &.formula { left: -6 * @icon-size; top: -1 * @icon-size; } &.arrow-down { left: -7 * @icon-size; top: -1 * @icon-size; } &.arrow-right { left: -8 * @icon-size; top: -1 * @icon-size; } } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { /* Basic Options */ "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": ["es6"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./xpreadsheet", /* Concatenate and emit output to single file. */ // "outDir": "./distjs/", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ // "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ /* Source Map Options */ // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ }, "include": [ "./src/**/*" ] } ================================================ FILE: tslint.json ================================================ { "defaultSeverity": "error", "extends": [ "tslint:recommended", "tslint-eslint-rules" ], "jsRules": {}, "rules": { "ter-indent": [true, 2] }, "rulesDirectory": [] } ================================================ FILE: webpack.config.dev.js ================================================ var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: "./src/main.ts", output: { filename: "bundle.js", path: __dirname + "/dist" }, // Enable sourcemaps for debugging webpack's output. devtool: "source-map", devServer: { clientLogLevel: 'warning', hot: true, publicPath: '/' }, resolve: { // Add '.ts' and '.tsx' as resolvable extensions. extensions: [".ts", ".tsx", ".js", ".json"] }, module: { rules: [ {test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader","css-loader")}, { test: /\.less$/, use:ExtractTextPlugin.extract({ fallback:'style-loader', use:['css-loader','less-loader'] }) }, {test: /\.(eot|woff|woff2|ttf|svg)([\\?]?.*)$/, loader: "file-loader"}, // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. { test: /\.tsx?$/, loader: "awesome-typescript-loader" }, // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. { enforce: "pre", test: /\.js$/, loader: "source-map-loader" } ] }, plugins: [ new ExtractTextPlugin("spreadsheet.css") ] // When importing a module whose path matches one of the following, just // assume a corresponding global variable exists and use that instead. // This is important because it allows us to avoid bundling all of our // dependencies, which allows browsers to cache those libraries between builds. // externals: { // "react": "React", // "react-dom": "ReactDOM" // }, }; ================================================ FILE: webpack.config.js ================================================ var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: "./src/main.ts", output: { filename: "xspreadsheet.js", path: __dirname + "/docs" }, // Enable sourcemaps for debugging webpack's output. devtool: "source-map", resolve: { // Add '.ts' and '.tsx' as resolvable extensions. extensions: [".ts", ".tsx", ".js", ".json"] }, module: { rules: [ {test: /\.less$/, loader: 'style-loader!css-loader!less-loader'}, // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. { test: /\.tsx?$/, loader: "awesome-typescript-loader" }, {test: /\.(eot|woff|woff2|ttf|svg)([\\?]?.*)$/, loader: "file-loader"}, // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. { enforce: "pre", test: /\.js$/, loader: "source-map-loader" } ] }, plugins: [ new ExtractTextPlugin("xspreadsheet.css") ] // When importing a module whose path matches one of the following, just // assume a corresponding global variable exists and use that instead. // This is important because it allows us to avoid bundling all of our // dependencies, which allows browsers to cache those libraries between builds. // externals: { // "react": "React", // "react-dom": "ReactDOM" // }, };