@kuzuma/colwork 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +186 -2
- package/dist/colwork.css +1 -0
- package/dist/colwork.js +1341 -0
- package/dist/colwork.umd.cjs +8 -0
- package/dist/index.d.ts +243 -0
- package/package.json +79 -4
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
(function(x,P){typeof exports=="object"&&typeof module<"u"?P(exports,require("yjs"),require("y-protocols/awareness"),require("y-webrtc"),require("y-websocket")):typeof define=="function"&&define.amd?define(["exports","yjs","y-protocols/awareness","y-webrtc","y-websocket"],P):(x=typeof globalThis<"u"?globalThis:x||self,P(x.Colwork={},x.Y,x.YAwareness,x.YWebrtc,x.YWebsocket))})(this,(function(x,P,J,dt,tt){"use strict";var Ht=Object.defineProperty;var Ot=(x,P,J)=>P in x?Ht(x,P,{enumerable:!0,configurable:!0,writable:!0,value:J}):x[P]=J;var h=(x,P,J)=>Ot(x,typeof P!="symbol"?P+"":P,J);function mt(a){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(a){for(const e in a)if(e!=="default"){const s=Object.getOwnPropertyDescriptor(a,e);Object.defineProperty(t,e,s.get?s:{enumerable:!0,get:()=>a[e]})}}return t.default=a,Object.freeze(t)}const V=mt(P),X=6e5,Z=a=>Array.from(a,t=>t.toString(16).padStart(2,"0")).join("");async function et(a,t){var s;if(!((s=globalThis.crypto)!=null&&s.subtle))throw new Error("密码锁定需要 HTTPS 或 localhost 环境");const e=await crypto.subtle.importKey("raw",new TextEncoder().encode(a),"PBKDF2",!1,["deriveBits"]);return new Uint8Array(await crypto.subtle.deriveBits({name:"PBKDF2",hash:"SHA-256",salt:t,iterations:X},e,256))}async function ft(a){var s;if(!a.length)throw new Error("密码不能为空");if(!((s=globalThis.crypto)!=null&&s.subtle))throw new Error("密码锁定需要 HTTPS 或 localhost 环境");const t=crypto.getRandomValues(new Uint8Array(16)),e={version:1,algorithm:"PBKDF2-SHA-256",iterations:X,salt:Z(t),verifier:Z(await et(a,t))};return JSON.stringify(e)}async function gt(a,t){let e;try{e=JSON.parse(a)}catch{throw new Error("锁定数据无效,无法解锁")}if(!e||e.version!==1||e.algorithm!=="PBKDF2-SHA-256"||e.iterations!==X||typeof e.salt!="string"||!/^[0-9a-f]{32}$/.test(e.salt)||typeof e.verifier!="string"||!/^[0-9a-f]{64}$/.test(e.verifier))throw new Error("锁定数据无效或版本不受支持,无法解锁");const s=Uint8Array.from(e.salt.match(/../g),n=>parseInt(n,16)),o=Z(await et(t,s));let r=0;for(let n=0;n<o.length;n+=1)r|=o.charCodeAt(n)^e.verifier.charCodeAt(n);return r===0}function pt(a,t){const e=a.trim().replace("#","");if(/^[\da-f]{3}$/i.test(e)){const[o,r,n]=e.split("").map(i=>Number.parseInt(i+i,16));return`rgba(${o}, ${r}, ${n}, ${t})`}if(/^[\da-f]{6}$/i.test(e)){const o=Number.parseInt(e,16);return`rgba(${o>>16}, ${o>>8&255}, ${o&255}, ${t})`}const s=a.match(/^rgba?\(\s*([\d.]+)[, ]+\s*([\d.]+)[, ]+\s*([\d.]+)/i);return s?`rgba(${s[1]}, ${s[2]}, ${s[3]}, ${t})`:`color-mix(in srgb, ${a} 12%, transparent)`}function st(a,t){return{start:{row:Math.min(a.row,t.row),column:Math.min(a.column,t.column)},end:{row:Math.max(a.row,t.row),column:Math.max(a.column,t.column)}}}function T(a,t){return t.row>=a.start.row&&t.row<=a.end.row&&t.column>=a.start.column&&t.column<=a.end.column}function wt(a){return{rows:a.end.row-a.start.row+1,columns:a.end.column-a.start.column+1}}function W(a){return`${a.row}:${a.column}`}function $(a,t){const e=a??t??{row:0,column:0};return st(e,t??e)}function ot(a,t,e,s){return{row:a==="column"||a==="all"?e-1:t.row,column:a==="row"||a==="all"?s-1:t.column}}function yt(a,t,e){if(!(!a.anchor||!a.focus))return $(a.anchor,ot(a.mode,a.focus,t,e))}const bt=54,St=42,nt=12,Ct=56,vt=150,kt=28,Et=64,Mt=.25,zt=2;function q(a,t){let e=0;for(let s=0;s<a;s+=1)e+=t(s);return e}function G(a,t){return q(a,t)}function Lt(a){const t=(l,u,c,m)=>{let f=0,p=0;for(;p<c&&f+m(p)<=l;)f+=m(p),p+=1;const M=Math.max(0,p-a.overscan);let E=p,N=f;for(;E<c&&N<l+u;)N+=m(E),E+=1;return{start:M,end:Math.min(c-1,E+a.overscan-1)}},e=t(a.scrollTop,a.clientHeight,a.rowCount,a.rowSize),s=t(a.scrollLeft,a.clientWidth,a.columnCount,a.columnSize),o=e.start,r=e.end,n=s.start,i=s.end;return{top:o,bottom:r,left:n,right:i}}function Y(a){let t="";for(let s=0;s<a.length;s+=32768)t+=String.fromCharCode(...a.subarray(s,s+32768));return btoa(t)}function rt(a){const t=atob(a);return Uint8Array.from(t,e=>e.charCodeAt(0))}const it="application/x-colwork-cells",xt=["任务","负责人","状态","优先级","开始日期","备注"],At=[{urls:"stun:stun.qq.com:3478"},{urls:"stun:stun.miwifi.com:3478"},{urls:"stun:stun.chat.bilibili.com:3478"},{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}],Q=[["设计协作表格","小林","进行中","高","2026-08-07","欢迎测试"],["整理组件 API","阿南","已完成","中","2026-08-08","已补充示例"],["补充单元测试","小周","未开始","高","2026-08-09","覆盖边界输入"],["实现虚拟滚动","小林","进行中","高","2026-08-10","先支持行虚拟化"],["接入 WebSocket","阿南","已完成","中","2026-08-11","等待联调"],["增加复制粘贴","小周","进行中","中","2026-08-12","支持 TSV 格式"],["检查 Safari 兼容性","小雨","阻塞","高","2026-08-13","需要补充 ICE 日志"],["编写使用文档","阿南","未开始","低","2026-08-14","准备 API 清单"],["优化移动端布局","小雨","未开始","中","2026-08-15","检查横向滚动"],["发布 v0.1.0","小林","未开始","低","2026-08-18","等待最终验收"]];function _t(a){let t=a+1,e="";for(;t>0;)t-=1,e=String.fromCharCode(65+t%26)+e,t=Math.floor(t/26);return e}class Rt{constructor(t,e){h(this,"doc");h(this,"cells");h(this,"styles");h(this,"rowHeights");h(this,"columnWidths");h(this,"locks");h(this,"messageDialog");h(this,"passwordDialog");h(this,"destroyed",!1);h(this,"frozenRows",0);h(this,"frozenColumns",0);h(this,"merges");h(this,"undoManager");h(this,"provider");h(this,"awareness");h(this,"root");h(this,"options");h(this,"table");h(this,"viewportResizeObserver");h(this,"viewport");h(this,"frozenBoundaryLayer");h(this,"protectionMenuButton");h(this,"contextMenu");h(this,"pendingCellContext");h(this,"rightButtonReleased",!1);h(this,"rightMouseUpPoint");h(this,"rightDrag");h(this,"undoButton");h(this,"redoButton");h(this,"rowCountValue");h(this,"columnCountValue");h(this,"zoomRatio",1);h(this,"showGridLines",!0);h(this,"bandedRows",!1);h(this,"readOnly");h(this,"documentRevision",0);h(this,"updateSequence",0);h(this,"updateLog",[]);h(this,"resizing");h(this,"onCellsChange",()=>this.refreshValues());h(this,"onStylesChange",()=>this.refreshStyles());h(this,"onSizesChange",()=>this.render());h(this,"onMergesChange",()=>this.render());h(this,"onLocksChange",()=>{this.undoManager.clear(),this.editingKey&&this.isCellLocked(this.editingKey)&&this.finishEdit(!1),this.resizing&&this.isAxisLocked(this.resizing.axis,this.resizing.index)&&(this.resizing=void 0,this.viewport.classList.remove("is-resizing")),this.render()});h(this,"onAwarenessChange",()=>{this.table&&(this.expandForRemoteSelections()?this.render():this.refreshCursors())});h(this,"renderFrame");h(this,"onViewportScroll",()=>{this.renderFrame===void 0&&(this.renderFrame=requestAnimationFrame(()=>{this.renderFrame=void 0,this.expandLogicalSpace(),this.render()}))});h(this,"onViewportWheel",t=>{if(!t.ctrlKey)return;t.preventDefault();const e=t.deltaY<0?1.1:.9;this.zoomRatio=Math.max(Mt,Math.min(zt,this.zoomRatio*e)),this.expandLogicalSpace(),this.render()});h(this,"onViewportKeyDown",t=>{if(this.readOnly||this.editor)return;const e=t.ctrlKey||t.metaKey;if(e&&t.key.toLowerCase()==="z"){t.preventDefault(),t.shiftKey?this.redo():this.undo();return}if(e&&t.key.toLowerCase()==="y"){t.preventDefault(),this.redo();return}if(t.key!==" "||!this.selectionFocus)return;const s=`${this.selectionFocus.row}:${this.selectionFocus.column}`,o=this.table.querySelector(`td[data-key="${s}"]`);o&&(t.preventDefault(),this.startEdit(o,s))});h(this,"onTableMouseDown",t=>{t.target.closest("th")&&t.preventDefault()});h(this,"onDocumentMouseDown",t=>{var e;this.contextMenu&&!this.contextMenu.contains(t.target)&&!((e=this.protectionMenuButton)!=null&&e.contains(t.target))&&this.hideContextMenu()});h(this,"onWindowMouseUp",t=>{if(this.resizing){this.resizing.axis==="row"?this.rowHeights.set(String(this.resizing.index),this.resizing.currentSize/this.zoomRatio):this.columnWidths.set(String(this.resizing.index),this.resizing.currentSize/this.zoomRatio),this.resizing=void 0,this.viewport.classList.remove("is-resizing");return}if(this.selecting=!1,this.headerSelecting=void 0,t.button===2&&(this.rightButtonReleased=!0,this.rightMouseUpPoint={x:t.clientX,y:t.clientY},this.pendingCellContext)){const e=this.rightMouseUpPoint;this.pendingCellContext=void 0,this.showCellContextMenu(e.x,e.y)}this.pendingAwarenessCursor&&this.sendAwareness(this.pendingAwarenessCursor)});h(this,"onWindowBlur",()=>{this.headerSelecting&&(this.selecting=!1,this.headerSelecting=void 0,this.pendingAwarenessCursor&&this.sendAwareness(this.pendingAwarenessCursor))});h(this,"onWindowMouseMove",t=>{if(this.headerSelecting&&!(t.buttons&1)&&this.onWindowBlur(),!this.resizing)return;const e=this.resizing.axis==="row"?t.clientY-this.resizing.start:t.clientX-this.resizing.start,s=(this.resizing.axis==="row"?kt:Et)*this.zoomRatio,o=Math.max(s,this.resizing.size+e);this.resizing.currentSize=o,this.render()});h(this,"onHistoryChange",()=>this.refreshHistoryButtons());h(this,"editor");h(this,"editingKey");h(this,"editingOriginal","");h(this,"selectedKey");h(this,"selectionAnchor");h(this,"selectionFocus");h(this,"selectionMode");h(this,"selecting",!1);h(this,"headerSelecting");h(this,"pendingAwarenessCursor");h(this,"loggedPeers",new Set);h(this,"loggedSignals",new Set);h(this,"onDocUpdate",(t,e)=>{this.documentRevision+=1;const s=this.provider,o={seq:++this.updateSequence,update:Y(t),timestamp:Date.now()};this.updateLog.push(o),this.logChunk(`yjs:${e===(s==null?void 0:s.room)?"recv":"send"}`,t),this.log(`yjs:state ${JSON.stringify({cells:Array.from(this.cells.entries()),rowHeights:Array.from(this.rowHeights.entries()),columnWidths:Array.from(this.columnWidths.entries()),merges:Array.from(this.merges.entries())})}`)});h(this,"onAwarenessUpdate",({added:t,updated:e,removed:s},o)=>{const r=this.provider;this.log(`awareness:${o===(r==null?void 0:r.room)?"recv":"send"} added=${t.join(",")} updated=${e.join(",")} removed=${s.join(",")}`),this.log(`awareness:state ${JSON.stringify(Array.from(this.awareness.getStates().entries()))}`)});h(this,"onCopy",t=>{const e=this.selectionToClipboard();if(!e||!t.clipboardData)return;const s=e.values.map(o=>o.join(" ")).join(`
|
|
2
|
+
`);t.clipboardData.setData("text/plain",s),t.clipboardData.setData(it,JSON.stringify(e)),t.preventDefault()});h(this,"onPaste",t=>{var l,u,c;if(t.preventDefault(),t.stopPropagation(),this.readOnly)return;const e=(u=(l=t.clipboardData)==null?void 0:l.getData("text/plain"))==null?void 0:u.replace(/^\uFEFF/,"");if(!e||!this.selectionFocus)return;this.editor&&this.finishEdit(!1);let s=e.replace(/\r\n/g,`
|
|
3
|
+
`).replace(/\r/g,`
|
|
4
|
+
`).split(`
|
|
5
|
+
`).filter((m,f,p)=>!(f===p.length-1&&m==="")).map(m=>m.split(" ")),o;const r=(c=t.clipboardData)==null?void 0:c.getData(it);if(r)try{const m=JSON.parse(r);Array.isArray(m.values)&&Array.isArray(m.styles)&&(s=m.values,o=m.styles)}catch{o=void 0}const n=this.selectionFocus,i=Math.max(...s.map(m=>m.length));s.some((m,f)=>m.some((p,M)=>this.isCellLocked(W({row:n.row+f,column:n.column+M}))))||(this.doc.transact(()=>s.forEach((m,f)=>m.forEach((p,M)=>{var U;const E=n.row+f,N=n.column+M;if(E>=this.rowCount||N>=this.columnCount)return;const H=W({row:E,column:N});if(this.cells.set(H,p),o){const B=(U=o[f])==null?void 0:U[M];B?this.styles.set(H,JSON.stringify(B)):this.styles.delete(H)}}))),this.selectionAnchor=n,this.selectionFocus={row:Math.min(this.rowCount-1,n.row+s.length-1),column:Math.min(this.columnCount-1,n.column+i-1)},this.render())});var _,ct,ht,ut;this.root=t,this.options=e,this.readOnly=e.readOnly===!0,this.doc=new V.Doc,this.cells=this.doc.getMap("cells"),this.styles=this.doc.getMap("styles"),this.rowHeights=this.doc.getMap("rowHeights"),this.columnWidths=this.doc.getMap("columnWidths"),this.merges=this.doc.getMap("merges"),this.locks=this.doc.getMap("locks"),this.rowCountValue=Math.max(e.rowCount??100,((_=e.rows)==null?void 0:_.length)??Q.length),this.columnCountValue=Math.max(e.columnCount??10,xt.length);const s=e.transport==="webrtc",o=new URL(e.websocketUrl??"ws://127.0.0.1:1234"),r=Object.fromEntries(o.searchParams);o.search="",this.readOnly?this.awareness=new J.Awareness(this.doc):(this.provider=s?new dt.WebrtcProvider(e.room,this.doc,{signaling:[e.signalingUrl??"ws://127.0.0.1:4444"],filterBcConns:!1,peerOpts:e.peerOpts??{config:{iceServers:At}}}):new tt.WebsocketProvider(o.toString().replace(/\/$/,""),encodeURIComponent(e.room),this.doc,{params:r,connect:!e.initializeAfterSync}),this.awareness=this.provider.awareness),e.snapshot&&V.applyUpdate(this.doc,e.snapshot),(ct=e.updates)==null||ct.forEach(g=>V.applyUpdate(this.doc,g)),this.log(`room:${e.room}`),this.doc.on("update",this.onDocUpdate),this.awareness.on("update",this.onAwarenessUpdate),this.awareness.on("change",this.onAwarenessChange),this.awareness.setLocalStateField("user",{name:e.user,color:e.userColor}),s&&this.provider&&this.inspectConnections();const n=document.createElement("section");n.className="colwork-table";const i=document.createElement("div");i.className="colwork-table__toolbar",this.readOnly&&(i.hidden=!0),i.setAttribute("aria-label","表格工具栏");const l=document.createElement("div");l.className="colwork-table__toolbar-row";const u=document.createElement("div");u.className="colwork-table__toolbar-row",i.append(l,u);const c=(g,S)=>{const w=document.createElement("div");w.className="colwork-table__toolbar-group",w.setAttribute("role","group"),w.setAttribute("aria-label",S);const k=document.createElement("span");return k.className="colwork-table__group-label",k.textContent=S,w.append(k),g.append(w),w},m=c(l,"历史"),f=c(l,"单元格"),p=c(l,"保护"),M=c(l,"显示"),E=c(u,"文字"),N=c(u,"对齐"),H=c(u,"格式");[["撤销","↶",()=>this.undo()],["重做","↷",()=>this.redo()]].forEach(([g,S,w],k)=>{const R=document.createElement("button");R.type="button",R.className=`colwork-table__history colwork-table__history--${k===0?"undo":"redo"}`,R.title=g,R.textContent=S,R.addEventListener("mousedown",Dt=>Dt.preventDefault()),R.addEventListener("click",w),k===0?this.undoButton=R:this.redoButton=R,m.append(R)}),[["合并单元格","合并",()=>this.mergeSelection()],["拆分单元格","拆分",()=>this.unmergeSelection()]].forEach(([g,S,w])=>{const k=document.createElement("button");k.type="button",k.className="colwork-table__format colwork-table__grid-action",k.title=g,k.textContent=S,k.addEventListener("mousedown",R=>R.preventDefault()),k.addEventListener("click",w),f.append(k)}),(ht=p.querySelector(".colwork-table__group-label"))==null||ht.remove();const z=document.createElement("button");this.protectionMenuButton=z,z.type="button",z.className="colwork-table__format colwork-table__dropdown-trigger",z.textContent="保护",z.title="锁定、密码锁定与解锁",z.setAttribute("aria-label","保护"),z.setAttribute("aria-expanded","false"),z.addEventListener("mousedown",g=>g.preventDefault()),z.addEventListener("click",()=>{if(z.getAttribute("aria-expanded")==="true"){this.hideContextMenu();return}const g=z.getBoundingClientRect();this.showContextMenu(g.left,g.bottom+4,[{label:"锁定与解锁",actions:this.protectionActions()}]),z.setAttribute("aria-expanded","true")}),z.addEventListener("keydown",g=>{g.key==="ArrowDown"&&(g.preventDefault(),z.click())}),p.append(z);const I=document.createElement("div");I.className="colwork-table__view-control";const O=document.createElement("button");O.type="button",O.className="colwork-table__format",O.textContent="视图",O.setAttribute("aria-label","视图"),O.classList.add("colwork-table__dropdown-trigger"),O.title="本地视图选项";const j=document.createElement("div");j.className="colwork-table__view-menu",[["隔行异色",()=>this.bandedRows,g=>{this.bandedRows=g}],["显示网格线",()=>this.showGridLines,g=>{this.showGridLines=g}]].forEach(([g,S,w])=>{const k=document.createElement("label"),R=document.createElement("input");R.type="checkbox",R.checked=S(),R.addEventListener("change",()=>{w(R.checked),this.render()}),k.append(R,document.createTextNode(g)),j.append(k)}),[["固定至选中行",()=>this.freezeSelection("row")],["固定至选中列",()=>this.freezeSelection("column")],["取消固定",()=>this.setFrozenPanes(0,0)]].forEach(([g,S])=>{const w=document.createElement("button");w.type="button",w.textContent=g,w.addEventListener("mousedown",k=>k.preventDefault()),w.addEventListener("click",S),j.append(w)}),I.append(O,j),M.append(I),[["bold","B","加粗"],["italic","I","斜体"],["underline","U","下划线"],["strike","S","删除线"],["wrap","换行","自动换行"],["align-left","左","左对齐"],["align-center","中","居中"],["align-right","右","右对齐"]].forEach(([g,S,w])=>{const k=document.createElement("button");k.type="button",k.className=`colwork-table__format colwork-table__format--${g}`,k.textContent=S,k.title=w,k.addEventListener("mousedown",R=>R.preventDefault()),k.addEventListener("click",()=>g.startsWith("align-")?this.setStyle("align",g.slice(6)):this.toggleFormat(g)),(g.startsWith("align-")||g==="wrap"?N:E).append(k)});const b=document.createElement("select");b.className="colwork-table__select",b.title="字体大小",[10,12,14,16,18,24].forEach(g=>{const S=document.createElement("option");S.value=String(g),S.textContent=`${g}px`,b.append(S)}),b.addEventListener("change",()=>this.setStyle("fontSize",Number(b.value))),E.append(b);const L=document.createElement("select");L.className="colwork-table__select",L.title="单元格格式",[["general","常规"],["number","数字"],["date","日期"],["percent","百分比"]].forEach(([g,S])=>{const w=document.createElement("option");w.value=g,w.textContent=S,L.append(w)}),L.addEventListener("change",()=>this.setStyle("format",L.value)),H.append(L);const C=document.createElement("button");C.type="button",C.className="colwork-table__format",C.title="清除格式",C.textContent="清除",C.addEventListener("mousedown",g=>g.preventDefault()),C.addEventListener("click",()=>this.clearFormatting()),H.append(C);const F=document.createElement("div");F.className="colwork-table__border-control";const v=document.createElement("button");v.type="button",v.className="colwork-table__format",v.title="边框选项",v.textContent="边框",v.setAttribute("aria-label","边框"),v.classList.add("colwork-table__dropdown-trigger");const A=document.createElement("div");A.className="colwork-table__border-menu",[["外围","outer"],["中间","inner"],["横线","horizontal"],["竖线","vertical"]].forEach(([g,S])=>{const w=document.createElement("button");w.type="button",w.textContent=g,w.addEventListener("click",()=>this.toggleBorder(S)),A.append(w)}),F.append(v,A),H.insertBefore(F,C),[["color","文字颜色"],["background","背景颜色"]].forEach(([g,S])=>{const w=document.createElement("input");w.type="color",w.className=`colwork-table__color colwork-table__color--${g}`,w.title=S,w.addEventListener("input",()=>this.setStyle(g,w.value)),(g==="color"?E:H).append(w)}),this.viewport=document.createElement("div"),this.viewport.className="colwork-table__viewport",this.viewport.addEventListener("scroll",this.onViewportScroll),this.viewport.addEventListener("wheel",this.onViewportWheel,{passive:!1}),this.viewport.tabIndex=0,this.viewport.addEventListener("keydown",this.onViewportKeyDown),this.viewport.addEventListener("copy",this.onCopy,!0),this.viewport.addEventListener("paste",this.onPaste,!0),window.addEventListener("mouseup",this.onWindowMouseUp),window.addEventListener("mousemove",this.onWindowMouseMove),window.addEventListener("blur",this.onWindowBlur),document.addEventListener("mousedown",this.onDocumentMouseDown),this.table=document.createElement("table"),this.table.addEventListener("mousedown",this.onTableMouseDown),this.viewport.append(this.table),this.frozenBoundaryLayer=document.createElement("div"),this.frozenBoundaryLayer.className="colwork-table__frozen-boundaries",this.frozenBoundaryLayer.setAttribute("aria-hidden","true"),n.append(i,this.viewport,this.frozenBoundaryLayer),t.replaceChildren(n);const D=!this.readOnly&&!e.snapshot&&!((ut=e.updates)!=null&&ut.length),K=D&&this.cells.size===0&&this.locks.size===0;if(D&&!e.initializeAfterSync&&this.seed(e.rows??Q),K&&!e.initializeAfterSync&&this.seedDemoFormatting(),this.expandLogicalSizeFromDocument(),this.undoManager=new V.UndoManager([this.cells,this.styles,this.rowHeights,this.columnWidths,this.merges],{captureTimeout:500}),this.undoManager.on("stack-item-added",this.onHistoryChange),this.undoManager.on("stack-item-popped",this.onHistoryChange),this.undoManager.on("stack-cleared",this.onHistoryChange),this.render(),this.refreshHistoryButtons(),this.cells.observe(this.onCellsChange),this.styles.observe(this.onStylesChange),this.rowHeights.observe(this.onSizesChange),this.columnWidths.observe(this.onSizesChange),this.merges.observe(this.onMergesChange),this.locks.observe(this.onLocksChange),this.viewportResizeObserver=new ResizeObserver(()=>this.refreshFrozenBoundaries()),this.viewportResizeObserver.observe(this.viewport),this.provider){const g=this.provider;g.on("status",({connected:S,status:w})=>{this.log(S===!0||w==="connected"?"signaling:connected":"signaling:disconnected"),s&&this.inspectConnections()}),s?g.on("synced",({synced:S})=>this.log(S?"synced":"syncing")):g.on("sync",S=>{if(this.log(S?"synced":"syncing"),!S||!e.initializeAfterSync)return;const w=this.doc.getMap("colwork-metadata");w.get("initialized")||(this.doc.transact(()=>{D&&this.cells.size===0&&this.locks.size===0&&(this.seed(e.rows??Q),this.seedDemoFormatting()),w.set("initialized",!0)}),this.undoManager.clear()),this.expandLogicalSizeFromDocument(),this.render()}),e.initializeAfterSync&&this.provider instanceof tt.WebsocketProvider&&this.provider.connect(),s&&g.on("peers",({added:S,removed:w,webrtcPeers:k})=>{S.length&&this.log(`peer:+${S.length}`),w.length&&this.log(`peer:-${w.length}`),this.log(`webrtc:${k.length}`),this.inspectConnections()})}}log(t){var e,s;(s=(e=this.options).onLog)==null||s.call(e,`${new Date().toLocaleTimeString()} ${t}`)}undo(){this.readOnly||!this.undoManager.canUndo()||(this.editor&&this.finishEdit(!0),this.undoManager.undo())}redo(){this.readOnly||!this.undoManager.canRedo()||(this.editor&&this.finishEdit(!0),this.undoManager.redo())}refreshHistoryButtons(){this.undoButton&&(this.undoButton.disabled=this.readOnly||!this.undoManager.canUndo()),this.redoButton&&(this.redoButton.disabled=this.readOnly||!this.undoManager.canRedo())}getYjsSnapshot(){return{format:"colwork.snapshot",version:1,room:this.options.room,sequence:this.updateSequence,snapshot:Y(V.encodeStateAsUpdate(this.doc)),stateVector:Y(V.encodeStateVector(this.doc))}}getYjsUpdateLog(){return{format:"colwork.update-log",version:1,room:this.options.room,updates:this.updateLog.map(t=>({...t}))}}setFrozenPanes(t,e){if(![t,e].every(r=>Number.isInteger(r)&&r>=0))throw new RangeError("Frozen row and column counts must be non-negative integers");const s=Math.min(t,this.rowCount-1),o=Math.min(e,this.columnCount-1);return this.hasPartialMerge("row",0,s-1)||this.hasPartialMerge("column",0,o-1)?(this.showLockMessage("当前选择包含不完整的合并块,无法固定,请选择完整的合并块"),!1):(this.finishEdit(!0),this.frozenRows=s,this.frozenColumns=o,this.closeLockMessage(),this.render(),!0)}hasPartialMerge(t,e,s){return e>s?!1:this.getMergeRanges().some(o=>{const r=o.start[t],n=o.end[t];return r<=s&&n>=e&&(r<e||n>s)})}crossesFrozenBoundary(t){return t.start.row<this.frozenRows&&t.end.row>=this.frozenRows||t.start.column<this.frozenColumns&&t.end.column>=this.frozenColumns}freezeSelection(t){if(!this.selectionFocus)return;const e=$(this.selectionAnchor,this.selectionFocus);if(this.hasPartialMerge(t,e.start[t],e.end[t])){this.showLockMessage("当前选择包含不完整的合并块,无法固定,请选择完整的合并块");return}this.setFrozenPanes(t==="row"?e.end.row+1:this.frozenRows,t==="column"?e.end.column+1:this.frozenColumns)}rangeHasLocks(t){return Array.from(this.locks.keys()).some(e=>{const[s,o]=e.split(":").map(Number);return T(t,{row:s,column:o})})}isCellLocked(t){if(this.locks.has(t))return!0;const[e,s]=t.split(":").map(Number),o=this.getMergeRanges().find(r=>T(r,{row:e,column:s}));return!!o&&this.rangeHasLocks(o)}isCellPasswordLocked(t){const e=n=>this.locks.has(n)&&this.locks.get(n)!=="1";if(e(t))return!0;const[s,o]=t.split(":").map(Number),r=this.getMergeRanges().find(n=>T(n,{row:s,column:o}));return!!r&&Array.from(this.locks.keys()).some(n=>{const[i,l]=n.split(":").map(Number);return e(n)&&T(r,{row:i,column:l})})}selectionHasLocks(){return this.rangeHasLocks($(this.selectionAnchor,this.selectionFocus))}isAxisLocked(t,e,s=!1){return Array.from(this.locks.keys()).some(o=>{const r=Number(o.split(":")[t==="row"?0:1]);return s?r>=e:r===e})}async setSelectionLocked(t,e){if(this.readOnly||this.destroyed||!this.selectionFocus)return;this.finishEdit(!0);const s=this.expandSelectionToMerges(this.selectionAnchor??this.selectionFocus,this.selectionFocus),o=$(s.anchor,s.focus),r=[];for(let u=o.start.row;u<=o.end.row;u+=1)for(let c=o.start.column;c<=o.end.column;c+=1)r.push(W({row:u,column:c}));const n=[...new Set(r.map(u=>this.locks.get(u)).filter(u=>u!==void 0&&u!=="1"))];if(t&&n.length)throw new Error("选区包含密码锁定单元格,请先用原密码解锁");const i=this.documentRevision;let l="1";if(t&&e!==void 0&&(l=await ft(e)),!t&&n.length){if(!e)throw new Error("请输入解锁密码");for(const u of n)if(!await gt(u,e))throw new Error("密码不正确,选区未解锁")}if(!this.destroyed){if(i!==this.documentRevision)throw new Error("表格内容或锁定状态已变化,请重试");this.doc.transact(()=>{r.forEach(u=>{t?this.locks.set(u,l):this.locks.delete(u)})})}}requestSelectionLock(t,e=!1){if(this.readOnly||this.destroyed||!this.selectionFocus||this.passwordDialog)return;this.closeLockMessage();const s=$(this.selectionAnchor,this.selectionFocus);if(!(e||!t&&Array.from(this.locks.entries()).some(([E,N])=>{const[H,U]=E.split(":").map(Number);return N!=="1"&&T(s,{row:H,column:U})}))){this.setSelectionLocked(t).catch(E=>this.showLockMessage(E instanceof Error?E.message:"操作失败"));return}const r=document.createElement("dialog");this.passwordDialog=r,r.className="colwork-table__password-dialog",r.setAttribute("aria-label",t?"密码锁定":"密码解锁");const n=document.createElement("form"),i=document.createElement("h3");i.textContent=t?"设置锁定密码":"输入解锁密码";const l=document.createElement("input");l.type="password",l.required=!0,l.placeholder=t?"设置密码":"输入密码",l.autocomplete=t?"new-password":"current-password",l.setAttribute("aria-label","密码");const u=document.createElement("input");u.type="password",u.required=t,u.placeholder="再次输入密码",u.autocomplete="new-password",u.setAttribute("aria-label","确认密码");const c=document.createElement("p");c.setAttribute("role","alert");const m=document.createElement("button");m.type="submit",m.textContent=t?"确认锁定":"确认解锁";const f=document.createElement("button");f.type="button",f.textContent="取消",f.addEventListener("click",()=>r.close()),r.addEventListener("close",()=>{l.value="",u.value="",r.remove(),this.passwordDialog===r&&(this.passwordDialog=void 0)});const p=this.selectionAnchor&&{...this.selectionAnchor},M={...this.selectionFocus};n.addEventListener("submit",async E=>{if(E.preventDefault(),!m.disabled){if(t&&l.value!==u.value){c.textContent="两次输入的密码不一致";return}m.disabled=!0,f.disabled=!0,r.oncancel=N=>N.preventDefault(),c.textContent="正在处理…";try{this.selectionAnchor=p,this.selectionFocus=M,await this.setSelectionLocked(t,l.value),r.close()}catch(N){c.textContent=N instanceof Error?N.message:"操作失败,请重试",l.value="",u.value="",l.focus()}finally{m.disabled=!1,f.disabled=!1,r.oncancel=null}}}),n.append(i,l),t&&n.append(u),n.append(c,m,f),r.append(n),this.root.append(r),r.showModal(),l.focus()}closeLockMessage(){const t=this.messageDialog;this.messageDialog=void 0,t==null||t.close(),t==null||t.remove()}showLockMessage(t){if(this.destroyed)return;this.closeLockMessage(),this.hideContextMenu();const e=document.createElement("dialog");this.messageDialog=e,e.className="colwork-table__message-dialog",e.setAttribute("role","alertdialog"),e.setAttribute("aria-label","操作提示"),e.setAttribute("aria-description",t);const s=document.createElement("h3");s.textContent="操作提示";const o=document.createElement("p");o.className="colwork-table__lock-status",o.setAttribute("role","status"),o.textContent=t;const r=document.createElement("button");r.type="button",r.textContent="确定",r.addEventListener("click",()=>e.close()),e.addEventListener("close",()=>{e.remove(),this.messageDialog===e&&(this.messageDialog=void 0)}),e.append(s,o,r),this.root.append(e),e.showModal(),r.focus()}get rowCount(){return this.rowCountValue}get columnCount(){return this.columnCountValue}get rowHeaderWidth(){return bt*this.zoomRatio}get columnHeaderHeight(){return St*this.zoomRatio}getRowHeight(t){var e;return((e=this.resizing)==null?void 0:e.axis)==="row"&&this.resizing.index===t?this.resizing.currentSize:(this.rowHeights.get(String(t))??Ct)*this.zoomRatio}getColumnWidth(t){var e;return((e=this.resizing)==null?void 0:e.axis)==="column"&&this.resizing.index===t?this.resizing.currentSize:(this.columnWidths.get(String(t))??vt)*this.zoomRatio}beginResize(t,e,s){s.preventDefault(),s.stopPropagation(),!(s.button!==0||this.readOnly||this.isAxisLocked(t,e))&&(this.finishEdit(!0),this.selecting=!1,this.headerSelecting=void 0,this.resizing={axis:t,index:e,start:t==="row"?s.clientY:s.clientX,size:t==="row"?this.getRowHeight(e):this.getColumnWidth(e),currentSize:t==="row"?this.getRowHeight(e):this.getColumnWidth(e)},this.viewport.classList.add("is-resizing"))}addResizeHandle(t,e,s){const o=document.createElement("span");o.className=`colwork-table__resize-handle colwork-table__resize-handle--${e}`,o.addEventListener("mousedown",r=>this.beginResize(e,s,r)),t.append(o)}selectRange(t,e,s){this.selectionAnchor=t,this.selectionFocus=e,this.selectionMode=s,this.selectedKey=W(t),this.pendingAwarenessCursor=e,this.refreshSelection()}hideContextMenu(){var t,e;(t=this.protectionMenuButton)==null||t.setAttribute("aria-expanded","false"),(e=this.contextMenu)==null||e.remove(),this.contextMenu=void 0}showContextMenu(t,e,s){var n;this.hideContextMenu();const o=document.createElement("div");o.className="colwork-table__context-menu",o.setAttribute("aria-label","表格操作"),s.forEach(({label:i,actions:l})=>{const u=document.createElement("div");u.className="colwork-table__context-group",u.setAttribute("role","group"),u.setAttribute("aria-label",i);const c=document.createElement("div");c.className="colwork-table__context-label",c.textContent=i,u.append(c),l.forEach(([m,f])=>{const p=document.createElement("button");p.type="button",p.textContent=m,(m.startsWith("删除")||m==="清空内容")&&(p.className="is-destructive"),p.addEventListener("click",()=>{this.hideContextMenu(),f()}),u.append(p)}),o.append(u)}),o.addEventListener("keydown",i=>{var m;if(i.key==="Escape"){i.preventDefault(),this.hideContextMenu(),this.viewport.focus({preventScroll:!0});return}const l=Array.from(o.querySelectorAll("button")),u=l.indexOf(document.activeElement);let c;if(i.key==="ArrowDown")c=(u+1)%l.length;else if(i.key==="ArrowUp")c=(u-1+l.length)%l.length;else if(i.key==="Home")c=0;else if(i.key==="End")c=l.length-1;else return;i.preventDefault(),(m=l[c])==null||m.focus()}),o.style.left=`${t}px`,o.style.top=`${e}px`,document.body.append(o);const r=o.getBoundingClientRect();o.style.left=`${Math.max(8,Math.min(t,window.innerWidth-r.width-8))}px`,o.style.top=`${Math.max(8,Math.min(e,window.innerHeight-r.height-8))}px`,this.contextMenu=o,(n=o.querySelector("button"))==null||n.focus({preventScroll:!0})}protectionActions(){return[["锁定选区",()=>this.requestSelectionLock(!0)],["密码锁定选区",()=>this.requestSelectionLock(!0,!0)],["解锁选区",()=>this.requestSelectionLock(!1)]]}hasHeaderSelection(t,e){const s=$(this.selectionAnchor,this.selectionFocus);return this.selectionMode===t&&(t==="row"?e>=s.start.row&&e<=s.end.row:e>=s.start.column&&e<=s.end.column)}openRowContextMenu(t,e){if(this.readOnly)return;t.preventDefault(),t.stopPropagation(),this.hasHeaderSelection("row",e)||this.selectHeaderRange("row",e,!1);const s=$(this.selectionAnchor,this.selectionFocus),o=s.end.row-s.start.row+1;this.showContextMenu(t.clientX,t.clientY,[{label:"行操作",actions:[[`在上方插入 ${o} 行`,()=>this.modifyAxis("row",s.start.row,o)],[`在下方插入 ${o} 行`,()=>this.modifyAxis("row",s.end.row+1,o)],[`删除 ${o} 行`,()=>this.deleteRows()]]},{label:"固定",actions:[["固定至此行",()=>this.freezeSelection("row")],["取消固定",()=>this.setFrozenPanes(0,0)]]},{label:"保护",actions:this.protectionActions()}])}openColumnContextMenu(t,e){if(this.readOnly)return;t.preventDefault(),t.stopPropagation(),this.hasHeaderSelection("column",e)||this.selectHeaderRange("column",e,!1);const s=$(this.selectionAnchor,this.selectionFocus),o=s.end.column-s.start.column+1;this.showContextMenu(t.clientX,t.clientY,[{label:"列操作",actions:[[`在左侧插入 ${o} 列`,()=>this.modifyAxis("column",s.start.column,o)],[`在右侧插入 ${o} 列`,()=>this.modifyAxis("column",s.end.column+1,o)],[`删除 ${o} 列`,()=>this.deleteColumns()]]},{label:"固定",actions:[["固定至此列",()=>this.freezeSelection("column")],["取消固定",()=>this.setFrozenPanes(0,0)]]},{label:"保护",actions:this.protectionActions()}])}mergeKey(t){return`${t.start.row}:${t.start.column}-${t.end.row}:${t.end.column}`}parseMergeKey(t){const e=t.match(/^(\d+):(\d+)-(\d+):(\d+)$/);if(e)return{start:{row:Number(e[1]),column:Number(e[2])},end:{row:Number(e[3]),column:Number(e[4])}}}getMergeRanges(){return Array.from(this.merges.keys()).flatMap(t=>{const e=this.parseMergeKey(t);return e?[e]:[]})}expandSelectionToMerges(t,e){let s=$(t,e);for(const n of this.getMergeRanges())n.start.row<=s.end.row&&n.end.row>=s.start.row&&n.start.column<=s.end.column&&n.end.column>=s.start.column&&(s={start:{row:Math.min(s.start.row,n.start.row),column:Math.min(s.start.column,n.start.column)},end:{row:Math.max(s.end.row,n.end.row),column:Math.max(s.end.column,n.end.column)}});const o=e.row>=t.row,r=e.column>=t.column;return{anchor:{row:o?s.start.row:s.end.row,column:r?s.start.column:s.end.column},focus:{row:o?s.end.row:s.start.row,column:r?s.end.column:s.start.column}}}getMergeStartingAt(t,e){return this.getMergeRanges().find(s=>s.start.row===t&&s.start.column===e)}isMergeCovered(t,e){return this.getMergeRanges().some(s=>T(s,{row:t,column:e})&&(s.start.row!==t||s.start.column!==e))}mergeSelection(){if(this.readOnly||this.selectionHasLocks())return;const t=$(this.selectionAnchor,this.selectionFocus);if(!(t.start.row===t.end.row&&t.start.column===t.end.column)){if(this.crossesFrozenBoundary(t)){this.showLockMessage("合并范围同时包含固定和非固定区域,无法合并,请调整选区或取消固定");return}this.closeLockMessage(),this.doc.transact(()=>{this.getMergeRanges().forEach(e=>{(T(t,e.start)||T(e,t.start))&&this.merges.delete(this.mergeKey(e))}),this.merges.set(this.mergeKey(t),"1")})}}unmergeSelection(){if(this.readOnly||this.selectionHasLocks())return;const t=$(this.selectionAnchor,this.selectionFocus);this.doc.transact(()=>this.getMergeRanges().forEach(e=>{(T(t,e.start)||T(e,t.start))&&this.merges.delete(this.mergeKey(e))}))}modifyAxis(t,e,s,o=0){if(this.readOnly||s===0||this.isAxisLocked(t,e,!0))return;const r=[this.cells,this.styles];this.doc.transact(()=>{r.forEach(i=>{const l=Array.from(i.entries());i.clear(),l.forEach(([u,c])=>{const[m,f]=u.split(":").map(Number),p=t==="row"?m:f;if(o&&p>=e&&p<e+o)return;const M=p>=e+o?p+s:p,E=W(t==="row"?{row:M,column:f}:{row:m,column:M});i.set(E,c)})});const n=this.getMergeRanges();this.merges.clear(),n.forEach(i=>{const l=t==="row"?i.start.row:i.start.column,u=t==="row"?i.end.row:i.end.column;if(o&&l<e+o&&u>=e)return;const c=l>=e+o?s:0,m=t==="row"?{start:{row:i.start.row+c,column:i.start.column},end:{row:i.end.row+c,column:i.end.column}}:{start:{row:i.start.row,column:i.start.column+c},end:{row:i.end.row,column:i.end.column+c}};this.merges.set(this.mergeKey(m),"1")})}),t==="row"?this.rowCountValue=Math.max(1,this.rowCountValue+s):this.columnCountValue=Math.max(1,this.columnCountValue+s),this.render()}deleteRows(){const t=$(this.selectionAnchor,this.selectionFocus),e=Math.min(t.end.row-t.start.row+1,this.rowCount-1);this.modifyAxis("row",t.start.row,-e,e)}deleteColumns(){const t=$(this.selectionAnchor,this.selectionFocus),e=Math.min(t.end.column-t.start.column+1,this.columnCount-1);this.modifyAxis("column",t.start.column,-e,e)}selectHeaderRange(t,e,s){var r,n;let o=e;s&&this.selectionMode===t&&(o=t==="row"?((r=this.selectionAnchor)==null?void 0:r.row)??e:((n=this.selectionAnchor)==null?void 0:n.column)??e),this.selectHeaderSpan(t,o,e)}selectHeaderSpan(t,e,s){t==="row"?this.selectRange({row:e,column:0},{row:s,column:this.columnCount-1},"row"):this.selectRange({row:0,column:e},{row:this.rowCount-1,column:s},"column")}addHeaderSelection(t,e,s){t.dataset.selectionAxis=e,t.dataset.selectionIndex=String(s),t.addEventListener("mousedown",o=>{o.button===0&&(o.preventDefault(),o.stopPropagation(),this.finishEdit(!0),this.viewport.focus({preventScroll:!0}),this.hideContextMenu(),this.selectHeaderRange(e,s,o.shiftKey),this.headerSelecting={axis:e,anchor:this.selectionAnchor[e]},this.selecting=!0)}),t.addEventListener("mouseenter",o=>{if(!(!this.selecting||!this.headerSelecting||this.headerSelecting.axis!==e)){if(!(o.buttons&1)){this.onWindowBlur();return}this.selectHeaderSpan(e,this.headerSelecting.anchor,s)}}),t.addEventListener("contextmenu",o=>e==="row"?this.openRowContextMenu(o,s):this.openColumnContextMenu(o,s))}expandLogicalSpace(){const t=this.viewport.scrollTop+this.viewport.clientHeight>=this.viewport.scrollHeight-280,e=this.viewport.scrollLeft+this.viewport.clientWidth>=this.viewport.scrollWidth-280,s=t,o=e;if(t&&(this.rowCountValue+=100),e&&(this.columnCountValue+=5),this.selectionMode&&(s||o)){const r=this.selectionFocus??{row:0,column:0};this.selectionFocus=ot(this.selectionMode,r,this.rowCount,this.columnCount),this.publishSelection()}}publishSelection(){this.updateAwareness()}updateAwareness(t={}){const e=this.awareness.getLocalState()??{};this.awareness.setLocalState({...e,...t,selection:{anchor:this.selectionAnchor,focus:this.selectionFocus,mode:this.selectionMode}})}expandForRemoteSelections(){let t=this.rowCount,e=this.columnCount;this.awareness.getStates().forEach((o,r)=>{if(r===this.awareness.clientID)return;const n=o.selection;!(n!=null&&n.anchor)||!n.focus||(t=Math.max(t,n.anchor.row+1,n.focus.row+1),e=Math.max(e,n.anchor.column+1,n.focus.column+1))});const s=t!==this.rowCount||e!==this.columnCount;return this.rowCountValue=t,this.columnCountValue=e,s}getSelectionBounds(){const t=this.selectionAnchor??this.selectionFocus??{row:0,column:0},e=this.selectionFocus??t,s=$(t,e);return{top:s.start.row,bottom:s.end.row,left:s.start.column,right:s.end.column}}selectCell(t,e,s=!1){const o={row:t,column:e};this.selectionMode=void 0,(!s||!this.selectionAnchor)&&(this.selectionAnchor=o);const r=this.expandSelectionToMerges(this.selectionAnchor,o);this.selectionAnchor=r.anchor,this.selectionFocus=r.focus,this.selectedKey=`${t}:${e}`,this.pendingAwarenessCursor=o}isPositionSelected(t){return T($(this.selectionAnchor,this.selectionFocus),t)}selectionToClipboard(){if(!this.selectionFocus)return;const t=$(this.selectionAnchor,this.selectionFocus),e=[],s=[];for(let o=t.start.row;o<=t.end.row;o+=1){const r=[],n=[];for(let i=t.start.column;i<=t.end.column;i+=1){const l=W({row:o,column:i});r.push(this.cells.get(l)??"");const u=this.readStyle(l);n.push(Object.keys(u).length?u:null)}e.push(r),s.push(n)}return{values:e,styles:s}}selectionToText(){var t;return((t=this.selectionToClipboard())==null?void 0:t.values.map(e=>e.join(" ")).join(`
|
|
6
|
+
`))??""}copySelection(){var e;const t=this.selectionToText();t&&((e=navigator.clipboard)==null||e.writeText(t))}clearSelectionContents(){if(this.readOnly||this.selectionHasLocks())return;const t=$(this.selectionAnchor,this.selectionFocus);this.doc.transact(()=>{for(let e=t.start.row;e<=t.end.row;e+=1)for(let s=t.start.column;s<=t.end.column;s+=1)this.cells.delete(W({row:e,column:s}))})}showCellContextMenu(t,e){this.readOnly||this.showContextMenu(t,e,[{label:"编辑",actions:[["复制",()=>this.copySelection()],["清空内容",()=>this.clearSelectionContents()]]},{label:"单元格",actions:[["合并单元格",()=>this.mergeSelection()],["取消合并单元格",()=>this.unmergeSelection()]]},{label:"固定",actions:[["固定至选中行",()=>this.freezeSelection("row")],["固定至选中列",()=>this.freezeSelection("column")],["取消固定",()=>this.setFrozenPanes(0,0)]]},{label:"保护",actions:this.protectionActions()}])}openCellContextMenu(t){if(!this.readOnly&&(t.preventDefault(),t.stopPropagation(),this.pendingCellContext={x:t.clientX,y:t.clientY},this.rightButtonReleased)){this.pendingCellContext=void 0;const e=this.rightMouseUpPoint??{x:t.clientX,y:t.clientY};this.showCellContextMenu(e.x,e.y)}}sendAwareness(t){this.pendingAwarenessCursor=void 0,this.updateAwareness({cursor:t,editing:this.editingKey??null})}logChunk(t,e){const s=Array.from(e,o=>o.toString(16).padStart(2,"0")).join(" ");this.log(`${t} bytes=${e.byteLength} hex=${s}`)}inspectConnections(){var e,s;const t=this.provider;(e=t.signalingConns)==null||e.forEach(o=>{if(this.loggedSignals.has(o))return;this.loggedSignals.add(o);const r=o.send.bind(o);o.send=n=>{this.log(`signal:send ${JSON.stringify(n)}`),r(n)},o.on("message",n=>this.log(`signal:recv ${JSON.stringify(n)}`)),o.on("connect",()=>this.log("signal:connect")),o.on("disconnect",()=>this.log("signal:disconnect")),o.on("error",n=>this.log(`signal:error ${String(n)}`))}),(s=t.room)==null||s.webrtcConns.forEach((o,r)=>{const n=o.peer;if(this.loggedPeers.has(n))return;this.loggedPeers.add(n);const i=n.send.bind(n);n.send=l=>{this.logChunk(`webrtc:send peer=${r}`,l),i(l)},n.on("data",l=>this.logChunk(`webrtc:recv peer=${r}`,l)),n.on("connect",()=>this.log(`webrtc:connect peer=${r}`)),n.on("close",()=>this.log(`webrtc:close peer=${r}`)),n.on("error",l=>this.log(`webrtc:error peer=${r} ${String(l)}`))})}seed(t){t.forEach((e,s)=>e.forEach((o,r)=>{const n=`${s}:${r}`;!this.cells.has(n)&&!this.isCellLocked(n)&&this.cells.set(n,o)}))}seedDemoFormatting(){this.doc.transact(()=>{this.cells.set("0:0",`设计协作表格
|
|
7
|
+
多人协作工作区`),this.cells.delete("0:1"),this.cells.delete("0:2"),this.merges.set("0:0-0:2","1"),this.styles.set("0:0",JSON.stringify({bold:!0,fontSize:16,color:"#0f172a",background:"#bae6fd",align:"center",wrap:!0,border:{top:!0,right:!0,bottom:!0,left:!0}})),this.styles.set("1:0",JSON.stringify({bold:!0,underline:!0,background:"#fef3c7",border:{bottom:!0}})),this.styles.set("1:1",JSON.stringify({italic:!0,color:"#7c3aed"})),this.styles.set("1:2",JSON.stringify({strike:!0,color:"#b91c1c"})),this.cells.set("2:0","125000"),this.styles.set("2:0",JSON.stringify({format:"number",fontSize:14,border:{top:!0,bottom:!0}})),this.cells.set("2:1","0.72"),this.styles.set("2:1",JSON.stringify({format:"percent",color:"#047857",align:"right"})),this.cells.set("2:2","2026-08-09"),this.styles.set("2:2",JSON.stringify({format:"date",background:"#dcfce7"})),this.cells.set("3:0",`这是一段较长的演示内容
|
|
8
|
+
可使用自动换行工具查看`),this.styles.set("3:0",JSON.stringify({wrap:!0,background:"#f1f5f9",border:{top:!0,right:!0,bottom:!0,left:!0}})),this.cells.set("4:0","删除线示例"),this.styles.set("4:0",JSON.stringify({strike:!0,color:"#64748b"})),this.cells.set("5:0","合并区域示例"),this.cells.delete("5:1"),this.cells.delete("5:2"),this.merges.set("5:0-5:2","1"),this.styles.set("5:0",JSON.stringify({bold:!0,align:"center",background:"#ede9fe",border:{top:!0,right:!0,bottom:!0,left:!0}})),this.rowHeights.set("0",72),this.rowHeights.set("3",72),this.rowHeights.set("5",44),this.columnWidths.set("0",220),this.columnWidths.set("1",130),this.columnWidths.set("2",130)})}expandLogicalSizeFromDocument(){const t=[...this.cells.keys(),...this.styles.keys(),...this.locks.keys(),...this.rowHeights.keys(),...this.columnWidths.keys()].map(e=>e.split(":").map(Number)).filter(([e,s])=>Number.isInteger(e)&&Number.isInteger(s));t.length&&(this.rowCountValue=Math.max(this.rowCount,...t.map(([e])=>e+1)),this.columnCountValue=Math.max(this.columnCount,...t.map(([,e])=>e+1)))}render(){this.editor&&this.finishEdit(!0);const t=this.columnCount,e=this.rowCount;this.frozenRows=Math.min(this.frozenRows,e-1),this.frozenColumns=Math.min(this.frozenColumns,t-1);const s=this.hasPartialMerge("row",0,this.frozenRows-1),o=this.hasPartialMerge("column",0,this.frozenColumns-1);(s||o)&&(s&&(this.frozenRows=0),o&&(this.frozenColumns=0),this.showLockMessage("合并区域已变化,已取消与合并块冲突的固定行列"));const r=d=>this.getRowHeight(d),n=d=>this.getColumnWidth(d),i=Lt({scrollTop:this.viewport.scrollTop,scrollLeft:this.viewport.scrollLeft,clientHeight:this.viewport.clientHeight||560,clientWidth:this.viewport.clientWidth||900,rowCount:e,columnCount:t,rowSize:r,columnSize:n,overscan:4});let l=!0;const u=this.getMergeRanges();for(;l;){l=!1;for(const d of u){const y=d.start.row<=i.bottom&&(d.end.row>=i.top||d.start.row<this.frozenRows),b=d.start.column<=i.right&&(d.end.column>=i.left||d.start.column<this.frozenColumns);!y||!b||(d.start.row>=this.frozenRows&&d.start.row<i.top&&(i.top=d.start.row,l=!0),d.start.column>=this.frozenColumns&&d.start.column<i.left&&(i.left=d.start.column,l=!0),d.end.row>i.bottom&&(i.bottom=d.end.row,l=!0),d.end.column>i.right&&(i.right=d.end.column,l=!0))}}i.bottom=Math.max(i.bottom,this.frozenRows-1),i.right=Math.max(i.right,this.frozenColumns-1);const c=Math.max(this.frozenRows,i.top),m=Math.max(this.frozenColumns,i.left),f=(d,y,b)=>[...Array.from({length:d},(L,C)=>C),-1,...Array.from({length:Math.max(0,b-y+1)},(L,C)=>y+C)],p=f(this.frozenRows,c,i.bottom),M=f(this.frozenColumns,m,i.right),E=q(m,n)-q(this.frozenColumns,n),N=q(c,r)-q(this.frozenRows,r),H=(d,y,b)=>{const L=y>=0&&y<this.frozenRows,C=b>=0&&b<this.frozenColumns;!L&&!C||(d.classList.add("is-frozen"),d.style.position="sticky",L&&(d.style.top=`${this.columnHeaderHeight+q(y,r)}px`),C&&(d.style.left=`${this.rowHeaderWidth+q(b,n)}px`),d.style.zIndex=String(y<0?10:b<0?8:L&&C?5:L?4:3))},U=document.createElement("thead"),B=document.createElement("tr"),z=document.createElement("th");z.style.width=`${this.rowHeaderWidth}px`,z.style.height=`${this.columnHeaderHeight}px`,z.addEventListener("mousedown",d=>{d.preventDefault(),d.stopPropagation(),this.selectRange({row:0,column:0},{row:e-1,column:t-1},"all")}),B.append(z);const I=d=>{const y=document.createElement("th");return y.style.width=`${d}px`,y.style.padding="0",y.style.border="0",y};for(const d of M){if(d===-1){B.append(I(E));continue}const y=document.createElement("th");y.style.width=`${n(d)}px`,y.style.height=`${this.columnHeaderHeight}px`,y.style.fontSize=`${nt*this.zoomRatio}px`;const b=document.createElement("b");b.className="colwork-table__column-letter",b.textContent=_t(d),y.append(b),this.addResizeHandle(y,"column",d),this.addHeaderSelection(y,"column",d),H(y,-1,d),B.append(y)}B.append(I(G(t,n)-q(i.right+1,n))),U.append(B);const O=document.createElement("tbody"),j=d=>{const y=document.createElement("tr");y.className="colwork-table__spacer",y.style.height=`${d}px`;const b=document.createElement("td");return b.colSpan=M.length+2,b.style.height=`${d}px`,y.append(b),y};for(const d of p){if(d===-1){O.append(j(N));continue}const y=document.createElement("tr");this.bandedRows&&d%2===1&&(y.className="is-banded-row"),y.style.height=`${r(d)}px`;const b=document.createElement("th");b.style.width=`${this.rowHeaderWidth}px`,b.style.height=`${r(d)}px`,b.style.fontSize=`${nt*this.zoomRatio}px`,b.textContent=String(d+1),this.addResizeHandle(b,"row",d),this.addHeaderSelection(b,"row",d),y.append(b),H(b,d,-1);for(const C of M){if(C===-1){const _=document.createElement("td");_.style.width=`${E}px`,_.style.padding="0",_.style.border="0",y.append(_);continue}if(this.isMergeCovered(d,C))continue;const F=W({row:d,column:C}),v=document.createElement("td");v.style.width=`${n(C)}px`,v.style.height=`${r(d)}px`,v.dataset.key=F;const A=this.getMergeStartingAt(d,C);A&&(v.rowSpan=A.end.row-A.start.row+1,v.colSpan=A.end.column-A.start.column+1);const D=document.createElement("span");D.className="colwork-table__cell-value",D.style.height=`${Math.max(0,r(d)-4)}px`,D.style.maxHeight=`${Math.max(0,r(d)-4)}px`,D.textContent=this.displayValue(F),this.applyStyle(D,F),v.append(D),this.applyStyle(v,F),H(v,d,C);const K=this.isCellLocked(F);v.classList.toggle("is-locked",K),v.classList.toggle("is-password-locked",this.isCellPasswordLocked(F)),v.setAttribute("aria-readonly",String(this.readOnly||K)),K&&(v.title=this.isCellPasswordLocked(F)?"密码锁定:验证密码后可编辑":"已锁定:解锁后可编辑"),v.addEventListener("mousedown",_=>{_.target instanceof HTMLTextAreaElement||_.button!==0&&_.button!==2||(_.button===2?(this.rightButtonReleased=!1,this.rightDrag={start:{row:d,column:C},moved:!1}):this.rightDrag=void 0,_.preventDefault(),this.viewport.focus({preventScroll:!0}),this.selecting=!0,(_.button===0||!this.isPositionSelected({row:d,column:C}))&&this.selectCell(d,C,_.shiftKey),this.refreshSelection())}),v.addEventListener("mouseenter",()=>{if(!(!this.selecting||this.headerSelecting)){if(this.rightDrag){if(this.rightDrag.start.row===d&&this.rightDrag.start.column===C)return;this.rightDrag.moved||(this.rightDrag.moved=!0,this.selectionMode=void 0,this.selectionAnchor=this.rightDrag.start,this.selectedKey=W(this.rightDrag.start))}this.selectCell(d,C,!0),this.refreshSelection()}}),this.readOnly||v.addEventListener("dblclick",()=>this.startEdit(v,F,!1)),v.addEventListener("contextmenu",_=>this.openCellContextMenu(_)),y.append(v)}const L=document.createElement("td");L.style.width=`${G(t,n)-q(i.right+1,n)}px`,L.style.padding="0",L.style.border="0",y.append(L),O.append(y)}O.append(j(G(e,r)-q(i.bottom+1,r))),this.table.style.setProperty("--colwork-zoom",String(this.zoomRatio)),this.table.style.width=`${this.rowHeaderWidth+G(t,n)}px`,this.table.classList.toggle("is-grid-hidden",!this.showGridLines),this.table.classList.toggle("is-banded-rows",this.bandedRows),this.table.style.fontSize=`${this.zoomRatio}em`,this.table.replaceChildren(U,O),this.refreshFrozenBoundaries(),this.refreshSelection(),this.refreshCursors()}refreshFrozenBoundaries(){this.frozenBoundaryLayer.replaceChildren(),this.frozenBoundaryLayer.style.width=`${this.viewport.clientWidth}px`,this.frozenBoundaryLayer.style.height=`${this.viewport.clientHeight}px`;const t=(e,s,o,r)=>{if(!s||o>=r)return;const n=document.createElement("span");n.className=`colwork-table__frozen-boundary colwork-table__frozen-boundary--${e}`,n.setAttribute("aria-hidden","true"),n.style.left=`${e==="column"?o-1:0}px`,n.style.top=`${e==="row"?o-1:0}px`,e==="column"?n.style.height=`${this.viewport.clientHeight}px`:n.style.width=`${this.viewport.clientWidth}px`,this.frozenBoundaryLayer.append(n)};t("row",this.frozenRows,this.columnHeaderHeight+q(this.frozenRows,e=>this.getRowHeight(e)),this.viewport.clientHeight),t("column",this.frozenColumns,this.rowHeaderWidth+q(this.frozenColumns,e=>this.getColumnWidth(e)),this.viewport.clientWidth)}refreshValues(){const t=document.activeElement;this.table.querySelectorAll("td[data-key]").forEach(e=>{const s=e.dataset.key;if(s&&e!==t&&s!==this.editingKey){const o=e.querySelector(".colwork-table__cell-value");o?o.textContent=this.displayValue(s):e.textContent=this.displayValue(s)}})}refreshSelection(){if(this.viewport.querySelectorAll(".colwork-table__selection").forEach(e=>e.remove()),!this.selectionFocus)return;const t=this.getSelectionBounds();this.table.querySelectorAll("th[data-selection-axis]").forEach(e=>{const s=e.dataset.selectionAxis,o=Number(e.dataset.selectionIndex),r=this.selectionMode==="all"||this.selectionMode===s&&(s==="row"?o>=t.top&&o<=t.bottom:o>=t.left&&o<=t.right);e.classList.toggle("is-selected-header",r)}),this.addSelectionOverlay($({row:t.top,column:t.left},{row:t.bottom,column:t.right}),"colwork-table__selection",this.options.userColor??"#3b82f6")}refreshCursors(){this.table&&(this.table.querySelectorAll("td[data-key]").forEach(t=>{t.querySelectorAll(".colwork-table__remote-cursor").forEach(e=>e.remove())}),this.viewport.querySelectorAll(".colwork-table__remote-selection").forEach(t=>t.remove()),this.awareness.getStates().forEach((t,e)=>{var o;if(e===this.awareness.clientID)return;const s=t.selection;if(s!=null&&s.anchor&&s.focus){const r=yt(s,this.rowCount,this.columnCount);r&&this.addSelectionOverlay(r,"colwork-table__remote-selection",((o=t.user)==null?void 0:o.color)??"#94a3b8");const n=Math.min(s.anchor.row,s.focus.row),i=Math.min(s.anchor.column,s.focus.column),l=this.table.querySelector(`td[data-key="${n}:${i}"]`);l&&this.addCursorLabel(l,t,e)}else{const r=t.cursor,n=r&&this.table.querySelector(`td[data-key="${r.row}:${r.column}"]`);n&&this.addCursorLabel(n,t,e)}}))}addSelectionOverlay(t,e,s){this.table.querySelectorAll("td[data-key]").forEach(o=>{const[r,n]=o.dataset.key.split(":").map(Number),i=r+o.rowSpan-1,l=n+o.colSpan-1;if(r>t.end.row||i<t.start.row||n>t.end.column||l<t.start.column)return;const u=getComputedStyle(o),c=parseFloat(u.borderTopWidth),m=parseFloat(u.borderRightWidth),f=parseFloat(u.borderBottomWidth),p=parseFloat(u.borderLeftWidth),M=(z,I,O,j,d,y,b)=>{if(z>=O&&I<=j)return[`${-y}px`,`${-b}px`];let L=0,C=0,F=0;for(let A=z;A<=I;A++){const D=this.table.querySelector(`th[data-selection-axis="${d}"][data-selection-index="${A}"]`),K=D==null?void 0:D.getBoundingClientRect(),_=d==="row"?(K==null?void 0:K.height)??this.getRowHeight(A):(K==null?void 0:K.width)??this.getColumnWidth(A);L+=_,A<O&&(C+=_),A>j&&(F+=_)}const v=(A,D)=>A===0?`${-D}px`:`calc(${A/L*100}% + ${(y+b)*A/L-D}px)`;return[v(C,y),v(F,b)]},[E,N]=M(r,i,t.start.row,t.end.row,"row",c,f),[H,U]=M(n,l,t.start.column,t.end.column,"column",p,m),B=document.createElement("span");B.className=e,B.setAttribute("aria-hidden","true"),Object.assign(B.style,{top:E,right:U,bottom:N,left:H,borderTopWidth:r<=t.start.row?"2px":"0",borderRightWidth:l>=t.end.column?"2px":"0",borderBottomWidth:i>=t.end.row?"2px":"0",borderLeftWidth:n<=t.start.column?"2px":"0",borderColor:s,backgroundColor:pt(s,.1)}),o.append(B)})}addCursorLabel(t,e,s){var r,n;const o=document.createElement("span");o.className="colwork-table__remote-cursor",o.style.backgroundColor=((r=e.user)==null?void 0:r.color)??"#64748b",o.textContent=((n=e.user)==null?void 0:n.name)??String(s).slice(0,6),t.append(o)}startEdit(t,e,s=!0){if(this.readOnly||this.isCellLocked(e)||this.editingKey===e)return;this.finishEdit(!0);const o=Array.from(this.table.querySelectorAll("td[data-key]")).find(n=>n.dataset.key===e);o&&(t=o),this.editingKey=e,this.editingOriginal=this.cells.get(e)??"";const r=document.createElement("textarea");r.className="colwork-table__editor",r.value=this.editingOriginal,this.applyStyle(r,e),this.editor=r,t.classList.add("is-editing"),t.append(r),s&&this.sendAwareness({...this.selectionFocus??{row:0,column:0},offset:0}),r.addEventListener("blur",()=>this.finishEdit(!0),{once:!0}),r.addEventListener("keydown",n=>{n.key==="Enter"&&!n.ctrlKey&&!n.shiftKey&&r.blur(),n.key==="Escape"&&this.finishEdit(!1)}),r.focus(),r.select()}readStyle(t){try{return JSON.parse(this.styles.get(t)??"{}")}catch{return{}}}applyStyle(t,e){var o,r,n,i;const s=this.readStyle(e);t.style.fontWeight=s.bold?"700":"",t.style.fontStyle=s.italic?"oblique":"",t.style.textDecoration=[s.underline?"underline":"",s.strike?"line-through":""].filter(Boolean).join(" "),t.style.transform=t.classList.contains("colwork-table__cell-value")&&s.italic?"skewX(-8deg)":"",t.style.fontSize=s.fontSize?`${s.fontSize*this.zoomRatio}px`:"",t.style.color=s.color??"",t.style.backgroundColor=s.background??"",t.style.textAlign=s.align??"",t.classList.contains("colwork-table__cell-value")&&(t.style.whiteSpace=s.wrap?"pre-wrap":"pre"),!t.classList.contains("colwork-table__cell-value")&&!t.classList.contains("colwork-table__editor")&&(t.style.borderTop=(o=s.border)!=null&&o.top?"2px solid #64748b":"",t.style.borderRight=(r=s.border)!=null&&r.right?"2px solid #64748b":"",t.style.borderBottom=(n=s.border)!=null&&n.bottom?"2px solid #64748b":"",t.style.borderLeft=(i=s.border)!=null&&i.left?"2px solid #64748b":"")}displayValue(t){const e=this.cells.get(t)??"",s=this.readStyle(t).format??"general";if(!e||s==="general")return e;if(s==="number"){const r=Number(e);return Number.isFinite(r)?r.toLocaleString():e}if(s==="percent"){const r=Number(e.replace(/%$/,""));return Number.isFinite(r)?`${e.endsWith("%")?r:r*100}%`:e}const o=new Date(e);return Number.isNaN(o.getTime())?e:o.toISOString().slice(0,10)}toggleFormat(t){if(this.readOnly||!this.selectedKey||this.selectionHasLocks())return;const{top:e,bottom:s,left:o,right:r}=this.getSelectionBounds(),n=this.readStyle(this.selectedKey)[t];this.doc.transact(()=>{for(let i=e;i<=s;i+=1)for(let l=o;l<=r;l+=1){const u=`${i}:${l}`,c=this.readStyle(u);c[t]=!n,this.styles.set(u,JSON.stringify(c))}}),this.render()}clearFormatting(){if(this.readOnly||!this.selectedKey||this.selectionHasLocks())return;const{top:t,bottom:e,left:s,right:o}=this.getSelectionBounds();this.doc.transact(()=>{for(let r=t;r<=e;r+=1)for(let n=s;n<=o;n+=1)this.styles.delete(W({row:r,column:n}))}),this.render()}toggleBorder(t){if(this.readOnly||!this.selectedKey||this.selectionHasLocks())return;const{top:e,bottom:s,left:o,right:r}=this.getSelectionBounds(),n=(c,m)=>{const f=[];return t==="outer"&&(c===e&&f.push("top"),c===s&&f.push("bottom"),m===o&&f.push("left"),m===r&&f.push("right")),(t==="inner"||t==="horizontal")&&c<s&&f.push("bottom"),(t==="inner"||t==="vertical")&&m<r&&f.push("right"),f},i=Array.from({length:s-e+1},(c,m)=>Array.from({length:r-o+1},(f,p)=>({row:e+m,column:o+p}))).flat();if(!i.flatMap(({row:c,column:m})=>n(c,m)).length)return;const u=!i.every(({row:c,column:m})=>n(c,m).every(f=>{var p;return(p=this.readStyle(W({row:c,column:m})).border)==null?void 0:p[f]}));this.doc.transact(()=>{for(let c=e;c<=s;c+=1)for(let m=o;m<=r;m+=1){const f=W({row:c,column:m}),p=this.readStyle(f),M={...p.border??{}};n(c,m).forEach(E=>{u?M[E]=!0:delete M[E]}),Object.keys(M).length?p.border=M:delete p.border,this.styles.set(f,JSON.stringify(p))}}),this.render()}setStyle(t,e){if(this.readOnly||!this.selectedKey||this.selectionHasLocks())return;const{top:s,bottom:o,left:r,right:n}=this.getSelectionBounds();this.doc.transact(()=>{for(let i=s;i<=o;i+=1)for(let l=r;l<=n;l+=1){const u=`${i}:${l}`,c=this.readStyle(u);t==="align"?c.align=e:t==="color"?c.color=e:t==="background"?c.background=e:t==="fontSize"?c.fontSize=e:c.format=e,this.styles.set(u,JSON.stringify(c))}}),this.render()}refreshStyles(){this.table.querySelectorAll("td[data-key]").forEach(t=>{if(t.dataset.key){this.applyStyle(t,t.dataset.key);const e=t.querySelector(".colwork-table__cell-value");e&&(this.applyStyle(e,t.dataset.key),e.textContent=this.displayValue(t.dataset.key))}}),this.refreshSelection(),this.refreshCursors()}finishEdit(t){if(!this.editor||!this.editingKey)return;const e=this.editingKey,s=this.editor,o=t?s.value:this.editingOriginal;this.editor=void 0,this.editingKey=void 0,this.editingOriginal="",!this.isCellLocked(e)&&o!==this.cells.get(e)&&this.cells.set(e,o);const r=this.awareness.getLocalState()??{};this.awareness.setLocalState({...r,editing:null}),s.remove();const n=this.table.querySelector(`td[data-key="${e}"]`);if(!n){this.render();return}this.table.querySelectorAll("td.is-editing").forEach(l=>l.classList.remove("is-editing"));const i=n.querySelector(".colwork-table__cell-value")??document.createElement("span");i.className="colwork-table__cell-value",i.textContent=this.displayValue(e),i.parentElement||n.append(i),Array.from(n.childNodes).forEach(l=>{l!==i&&l.nodeType===Node.TEXT_NODE&&l.remove()}),this.applyStyle(n,e),this.applyStyle(i,e),this.refreshSelection(),this.refreshCursors()}destroy(){var t,e;this.destroyed=!0,this.viewportResizeObserver.disconnect(),this.closeLockMessage(),(t=this.passwordDialog)==null||t.close(),this.finishEdit(!0),this.doc.off("update",this.onDocUpdate),this.awareness.off("update",this.onAwarenessUpdate),this.awareness.off("change",this.onAwarenessChange),this.cells.unobserve(this.onCellsChange),this.styles.unobserve(this.onStylesChange),this.rowHeights.unobserve(this.onSizesChange),this.columnWidths.unobserve(this.onSizesChange),this.merges.unobserve(this.onMergesChange),this.locks.unobserve(this.onLocksChange),this.undoManager.off("stack-item-added",this.onHistoryChange),this.undoManager.off("stack-item-popped",this.onHistoryChange),this.undoManager.off("stack-cleared",this.onHistoryChange),this.viewport.removeEventListener("scroll",this.onViewportScroll),this.viewport.removeEventListener("wheel",this.onViewportWheel),this.viewport.removeEventListener("keydown",this.onViewportKeyDown),this.viewport.removeEventListener("copy",this.onCopy,!0),this.viewport.removeEventListener("paste",this.onPaste,!0),this.table.removeEventListener("mousedown",this.onTableMouseDown),window.removeEventListener("mouseup",this.onWindowMouseUp),window.removeEventListener("mousemove",this.onWindowMouseMove),window.removeEventListener("blur",this.onWindowBlur),document.removeEventListener("mousedown",this.onDocumentMouseDown),this.hideContextMenu(),this.renderFrame!==void 0&&cancelAnimationFrame(this.renderFrame),(e=this.provider)==null||e.destroy(),this.undoManager.destroy(),this.doc.destroy(),this.root.replaceChildren()}}function at(a){if(a!=null&&a.trim())return JSON.parse(a)}function $t(a){if(!(!a||a.format==="colwork.update-log"))return a.format==="colwork.archive"?a.snapshot:a}function lt(a){return!a||a.format==="colwork.snapshot"?[]:a.updates??[]}function Nt(a,t){const e=at(a),s=at(t),o=$t(e),r=[...lt(e),...lt(s)],n=new V.Doc;o!=null&&o.snapshot&&V.applyUpdate(n,rt(o.snapshot));const i=Number((o==null?void 0:o.sequence)??0),l=new Set,u=new Set;let c=i;for(const m of r.sort((f,p)=>Number(f.seq)-Number(p.seq))){const f=Number(m.seq);!Number.isInteger(f)||typeof m.update!="string"||f<=i||l.has(f)||u.has(m.update)||(V.applyUpdate(n,rt(m.update)),l.add(f),u.add(m.update),c=Math.max(c,f))}return{format:"colwork.snapshot",version:1,room:o==null?void 0:o.room,sequence:c,snapshot:Y(V.encodeStateAsUpdate(n)),stateVector:Y(V.encodeStateVector(n))}}x.ColworkTable=Rt,x.cellKey=W,x.containsPosition=T,x.normalizeRange=st,x.rangeSize=wt,x.rebuildYjsSnapshot=Nt,Object.defineProperty(x,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
export declare type AwarenessSelection = {
|
|
2
|
+
anchor?: CellPosition;
|
|
3
|
+
focus?: CellPosition;
|
|
4
|
+
mode?: SelectionMode_2;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export declare function cellKey(position: CellPosition): string;
|
|
8
|
+
|
|
9
|
+
export declare type CellPosition = {
|
|
10
|
+
row: number;
|
|
11
|
+
column: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export declare type CellRange = {
|
|
15
|
+
start: CellPosition;
|
|
16
|
+
end: CellPosition;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export declare class ColworkTable {
|
|
20
|
+
private readonly doc;
|
|
21
|
+
private readonly cells;
|
|
22
|
+
private readonly styles;
|
|
23
|
+
private readonly rowHeights;
|
|
24
|
+
private readonly columnWidths;
|
|
25
|
+
private readonly locks;
|
|
26
|
+
private messageDialog?;
|
|
27
|
+
private passwordDialog?;
|
|
28
|
+
private destroyed;
|
|
29
|
+
private frozenRows;
|
|
30
|
+
private frozenColumns;
|
|
31
|
+
private readonly merges;
|
|
32
|
+
private readonly undoManager;
|
|
33
|
+
private readonly provider?;
|
|
34
|
+
private readonly awareness;
|
|
35
|
+
private readonly root;
|
|
36
|
+
private readonly options;
|
|
37
|
+
private readonly table;
|
|
38
|
+
private readonly viewportResizeObserver;
|
|
39
|
+
private readonly viewport;
|
|
40
|
+
private readonly frozenBoundaryLayer;
|
|
41
|
+
private protectionMenuButton?;
|
|
42
|
+
private contextMenu?;
|
|
43
|
+
private pendingCellContext?;
|
|
44
|
+
private rightButtonReleased;
|
|
45
|
+
private rightMouseUpPoint?;
|
|
46
|
+
private rightDrag?;
|
|
47
|
+
private undoButton?;
|
|
48
|
+
private redoButton?;
|
|
49
|
+
private rowCountValue;
|
|
50
|
+
private columnCountValue;
|
|
51
|
+
private zoomRatio;
|
|
52
|
+
private showGridLines;
|
|
53
|
+
private bandedRows;
|
|
54
|
+
private readonly readOnly;
|
|
55
|
+
private documentRevision;
|
|
56
|
+
private updateSequence;
|
|
57
|
+
private readonly updateLog;
|
|
58
|
+
private resizing?;
|
|
59
|
+
private readonly onCellsChange;
|
|
60
|
+
private readonly onStylesChange;
|
|
61
|
+
private readonly onSizesChange;
|
|
62
|
+
private readonly onMergesChange;
|
|
63
|
+
private readonly onLocksChange;
|
|
64
|
+
private readonly onAwarenessChange;
|
|
65
|
+
private renderFrame?;
|
|
66
|
+
private readonly onViewportScroll;
|
|
67
|
+
private readonly onViewportWheel;
|
|
68
|
+
private readonly onViewportKeyDown;
|
|
69
|
+
private readonly onTableMouseDown;
|
|
70
|
+
private readonly onDocumentMouseDown;
|
|
71
|
+
private readonly onWindowMouseUp;
|
|
72
|
+
private readonly onWindowBlur;
|
|
73
|
+
private readonly onWindowMouseMove;
|
|
74
|
+
private readonly onHistoryChange;
|
|
75
|
+
private editor?;
|
|
76
|
+
private editingKey?;
|
|
77
|
+
private editingOriginal;
|
|
78
|
+
private selectedKey?;
|
|
79
|
+
private selectionAnchor?;
|
|
80
|
+
private selectionFocus?;
|
|
81
|
+
private selectionMode?;
|
|
82
|
+
private selecting;
|
|
83
|
+
private headerSelecting?;
|
|
84
|
+
private pendingAwarenessCursor?;
|
|
85
|
+
private readonly loggedPeers;
|
|
86
|
+
private readonly loggedSignals;
|
|
87
|
+
private readonly onDocUpdate;
|
|
88
|
+
private readonly onAwarenessUpdate;
|
|
89
|
+
constructor(root: HTMLElement, options: ColworkTableOptions);
|
|
90
|
+
private log;
|
|
91
|
+
private undo;
|
|
92
|
+
private redo;
|
|
93
|
+
private refreshHistoryButtons;
|
|
94
|
+
getYjsSnapshot(): YjsSnapshotFile;
|
|
95
|
+
getYjsUpdateLog(): YjsUpdateLogFile;
|
|
96
|
+
/** Freeze a leading prefix of rows/columns in this client only. */
|
|
97
|
+
setFrozenPanes(rows: number, columns: number): boolean;
|
|
98
|
+
private hasPartialMerge;
|
|
99
|
+
private crossesFrozenBoundary;
|
|
100
|
+
private freezeSelection;
|
|
101
|
+
private rangeHasLocks;
|
|
102
|
+
private isCellLocked;
|
|
103
|
+
private isCellPasswordLocked;
|
|
104
|
+
private selectionHasLocks;
|
|
105
|
+
private isAxisLocked;
|
|
106
|
+
/** Passwords are optional for legacy/plain locks; protected locks require verification. */
|
|
107
|
+
setSelectionLocked(locked: boolean, password?: string): Promise<void>;
|
|
108
|
+
private requestSelectionLock;
|
|
109
|
+
private closeLockMessage;
|
|
110
|
+
private showLockMessage;
|
|
111
|
+
private get rowCount();
|
|
112
|
+
private get columnCount();
|
|
113
|
+
private get rowHeaderWidth();
|
|
114
|
+
private get columnHeaderHeight();
|
|
115
|
+
private getRowHeight;
|
|
116
|
+
private getColumnWidth;
|
|
117
|
+
private beginResize;
|
|
118
|
+
private addResizeHandle;
|
|
119
|
+
private selectRange;
|
|
120
|
+
private hideContextMenu;
|
|
121
|
+
private showContextMenu;
|
|
122
|
+
private protectionActions;
|
|
123
|
+
private hasHeaderSelection;
|
|
124
|
+
private openRowContextMenu;
|
|
125
|
+
private openColumnContextMenu;
|
|
126
|
+
private mergeKey;
|
|
127
|
+
private parseMergeKey;
|
|
128
|
+
private getMergeRanges;
|
|
129
|
+
private expandSelectionToMerges;
|
|
130
|
+
private getMergeStartingAt;
|
|
131
|
+
private isMergeCovered;
|
|
132
|
+
private mergeSelection;
|
|
133
|
+
private unmergeSelection;
|
|
134
|
+
private modifyAxis;
|
|
135
|
+
private deleteRows;
|
|
136
|
+
private deleteColumns;
|
|
137
|
+
private selectHeaderRange;
|
|
138
|
+
private selectHeaderSpan;
|
|
139
|
+
private addHeaderSelection;
|
|
140
|
+
private expandLogicalSpace;
|
|
141
|
+
private publishSelection;
|
|
142
|
+
private updateAwareness;
|
|
143
|
+
private expandForRemoteSelections;
|
|
144
|
+
private getSelectionBounds;
|
|
145
|
+
private selectCell;
|
|
146
|
+
private isPositionSelected;
|
|
147
|
+
private selectionToClipboard;
|
|
148
|
+
private selectionToText;
|
|
149
|
+
private copySelection;
|
|
150
|
+
private clearSelectionContents;
|
|
151
|
+
private showCellContextMenu;
|
|
152
|
+
private openCellContextMenu;
|
|
153
|
+
private sendAwareness;
|
|
154
|
+
private onCopy;
|
|
155
|
+
private onPaste;
|
|
156
|
+
private logChunk;
|
|
157
|
+
private inspectConnections;
|
|
158
|
+
private seed;
|
|
159
|
+
private seedDemoFormatting;
|
|
160
|
+
private expandLogicalSizeFromDocument;
|
|
161
|
+
private render;
|
|
162
|
+
private refreshFrozenBoundaries;
|
|
163
|
+
private refreshValues;
|
|
164
|
+
private refreshSelection;
|
|
165
|
+
private refreshCursors;
|
|
166
|
+
private addSelectionOverlay;
|
|
167
|
+
private addCursorLabel;
|
|
168
|
+
private startEdit;
|
|
169
|
+
private readStyle;
|
|
170
|
+
private applyStyle;
|
|
171
|
+
private displayValue;
|
|
172
|
+
private toggleFormat;
|
|
173
|
+
private clearFormatting;
|
|
174
|
+
private toggleBorder;
|
|
175
|
+
private setStyle;
|
|
176
|
+
private refreshStyles;
|
|
177
|
+
private finishEdit;
|
|
178
|
+
destroy(): void;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export declare type ColworkTableOptions = {
|
|
182
|
+
room: string;
|
|
183
|
+
signalingUrl?: string;
|
|
184
|
+
user: string;
|
|
185
|
+
rows?: string[][];
|
|
186
|
+
rowCount?: number;
|
|
187
|
+
columnCount?: number;
|
|
188
|
+
onLog?: (message: string) => void;
|
|
189
|
+
userColor?: string;
|
|
190
|
+
peerOpts?: Record<string, unknown>;
|
|
191
|
+
transport?: 'webrtc' | 'websocket';
|
|
192
|
+
websocketUrl?: string;
|
|
193
|
+
initializeAfterSync?: boolean;
|
|
194
|
+
readOnly?: boolean;
|
|
195
|
+
snapshot?: Uint8Array;
|
|
196
|
+
updates?: Uint8Array[];
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export declare function containsPosition(range: CellRange, position: CellPosition): boolean;
|
|
200
|
+
|
|
201
|
+
export declare function normalizeRange(start: CellPosition, end: CellPosition): CellRange;
|
|
202
|
+
|
|
203
|
+
export declare function rangeSize(range: CellRange): {
|
|
204
|
+
rows: number;
|
|
205
|
+
columns: number;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export declare function rebuildYjsSnapshot(snapshotText?: string, updateLogText?: string): YjsSnapshotFile;
|
|
209
|
+
|
|
210
|
+
declare type SelectionMode_2 = 'row' | 'column' | 'all';
|
|
211
|
+
export { SelectionMode_2 as SelectionMode }
|
|
212
|
+
|
|
213
|
+
export declare type YjsArchiveFile = {
|
|
214
|
+
format: 'colwork.archive';
|
|
215
|
+
version: 1;
|
|
216
|
+
room?: string;
|
|
217
|
+
snapshot?: YjsSnapshotFile;
|
|
218
|
+
updates: YjsUpdateRecord[];
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export declare type YjsSnapshotFile = {
|
|
222
|
+
format: 'colwork.snapshot';
|
|
223
|
+
version: 1;
|
|
224
|
+
room?: string;
|
|
225
|
+
sequence: number;
|
|
226
|
+
snapshot: string;
|
|
227
|
+
stateVector: string;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export declare type YjsUpdateLogFile = {
|
|
231
|
+
format: 'colwork.update-log';
|
|
232
|
+
version: 1;
|
|
233
|
+
room?: string;
|
|
234
|
+
updates: YjsUpdateRecord[];
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export declare type YjsUpdateRecord = {
|
|
238
|
+
seq: number;
|
|
239
|
+
update: string;
|
|
240
|
+
timestamp: number;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export { }
|
package/package.json
CHANGED
|
@@ -1,6 +1,81 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kuzuma/colwork",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "npm run dev:test",
|
|
8
|
+
"dev:test": "node server/index.mjs --mode test",
|
|
9
|
+
"server": "node server/index.mjs",
|
|
10
|
+
"server:test": "node server/index.mjs --mode test",
|
|
11
|
+
"rebuild:yjs": "node script/rebuild-yjs.mjs",
|
|
12
|
+
"build": "npm run build:lib",
|
|
13
|
+
"build:lib": "tsc --noEmit && vite build --config vite.lib.config.ts",
|
|
14
|
+
"test:e2e": "playwright test",
|
|
15
|
+
"build:site": "tsc --noEmit && vite build --config vite.site.config.ts",
|
|
16
|
+
"dev:cloudflare": "npm run build:site && wrangler dev --config config/cloudflare/wrangler.dev.json --persist-to .wrangler/state",
|
|
17
|
+
"deploy:check": "node script/deploy-cloudflare.mjs --dry-run",
|
|
18
|
+
"deploy": "node script/deploy-cloudflare.mjs",
|
|
19
|
+
"test:cloudflare": "node --test test/cloudflare/*.test.mjs",
|
|
20
|
+
"prepublishOnly": "npm run build:lib"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"y-protocols": "^1.0.7",
|
|
24
|
+
"y-webrtc": "^10.3.0",
|
|
25
|
+
"y-websocket": "^2.1.0",
|
|
26
|
+
"yjs": "^13.6.27"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@playwright/test": "^1.63.0",
|
|
30
|
+
"@types/react": "^19.1.2",
|
|
31
|
+
"@types/react-dom": "^19.1.2",
|
|
32
|
+
"@vitejs/plugin-react": "^4.4.1",
|
|
33
|
+
"@vitejs/plugin-vue": "^5.2.3",
|
|
34
|
+
"lib0": "^0.2.119",
|
|
35
|
+
"react": "^19.1.0",
|
|
36
|
+
"react-dom": "^19.1.0",
|
|
37
|
+
"typescript": "^5.8.3",
|
|
38
|
+
"vite": "^6.3.5",
|
|
39
|
+
"vite-plugin-dts": "^4.5.4",
|
|
40
|
+
"vue": "^3.5.13",
|
|
41
|
+
"wrangler": "^4.145.0",
|
|
42
|
+
"ws": "^8.21.3"
|
|
43
|
+
},
|
|
44
|
+
"description": "A collaborative spreadsheet library powered by Yjs, with native DOM rendering, frozen panes, and password-protected ranges.",
|
|
45
|
+
"main": "./dist/colwork.umd.cjs",
|
|
46
|
+
"module": "./dist/colwork.js",
|
|
47
|
+
"types": "./dist/index.d.ts",
|
|
48
|
+
"exports": {
|
|
49
|
+
".": {
|
|
50
|
+
"types": "./dist/index.d.ts",
|
|
51
|
+
"import": "./dist/colwork.js",
|
|
52
|
+
"require": "./dist/colwork.umd.cjs"
|
|
53
|
+
},
|
|
54
|
+
"./colwork.css": "./dist/colwork.css"
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dist"
|
|
58
|
+
],
|
|
59
|
+
"sideEffects": [
|
|
60
|
+
"**/*.css"
|
|
61
|
+
],
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "git+https://github.com/dlwm/colwork.git"
|
|
65
|
+
},
|
|
66
|
+
"homepage": "https://github.com/dlwm/colwork#readme",
|
|
67
|
+
"bugs": {
|
|
68
|
+
"url": "https://github.com/dlwm/colwork/issues"
|
|
69
|
+
},
|
|
70
|
+
"keywords": [
|
|
71
|
+
"spreadsheet",
|
|
72
|
+
"collaboration",
|
|
73
|
+
"yjs",
|
|
74
|
+
"vue",
|
|
75
|
+
"react"
|
|
76
|
+
],
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public",
|
|
79
|
+
"registry": "https://registry.npmjs.org/"
|
|
80
|
+
}
|
|
81
|
+
}
|