Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0930c93ac5 | ||
|
|
55a58cdfb2 | ||
|
|
d288c489b2 | ||
|
|
51b0edb2ef | ||
|
|
473b22a96d | ||
|
|
861dd48b0a | ||
|
|
35103ca69c | ||
|
|
5ad8c3e6f2 | ||
|
|
4b18e7dfac | ||
|
|
cab3d24165 | ||
|
|
eb7a87b4a8 | ||
|
|
1afd0450b9 | ||
|
|
b9f2b92cde |
@@ -14,7 +14,7 @@ new Editor(container, IEditorData | IElement[], {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface IEditorOption {
|
interface IEditorOption {
|
||||||
mode?: EditorMode // Editor mode: Edit, Clean (Visual aids are not displayed, For example: page break), ReadOnly, Form (Only editable within the control), Print (Visual aids are not displayed, Unwritten content control). default: Edit
|
mode?: EditorMode // Editor mode: Edit, Clean (Visual aids are not displayed, For example: page break), ReadOnly, Form (Only editable within the control), Print (Visual aids are not displayed, Unwritten content control), Design (Do not handle configurations such as non deletable and read-only). default: Edit
|
||||||
defaultType?: string // Default element type. default: TEXT
|
defaultType?: string // Default element type. default: TEXT
|
||||||
defaultColor?: string // Default color. default: #000000
|
defaultColor?: string // Default color. default: #000000
|
||||||
defaultFont?: string // Default font. default: Microsoft YaHei
|
defaultFont?: string // Default font. default: Microsoft YaHei
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ new Editor(container, IEditorData | IElement[], {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface IEditorOption {
|
interface IEditorOption {
|
||||||
mode?: EditorMode // 编辑器模式:编辑、清洁(不显示视觉辅助元素。如:分页符)、只读、表单(仅控件内可编辑)、打印(不显示辅助元素、未书写控件及前后括号)。默认:编辑
|
mode?: EditorMode // 编辑器模式:编辑、清洁(不显示视觉辅助元素。如:分页符)、只读、表单(仅控件内可编辑)、打印(不显示辅助元素、未书写控件及前后括号)、设计模式(不可删除、只读等配置不控制)。默认:编辑
|
||||||
defaultType?: string // 默认元素类型。默认:TEXT
|
defaultType?: string // 默认元素类型。默认:TEXT
|
||||||
defaultColor?: string // 默认字体颜色。默认:#000000
|
defaultColor?: string // 默认字体颜色。默认:#000000
|
||||||
defaultFont?: string // 默认字体。默认:Microsoft YaHei
|
defaultFont?: string // 默认字体。默认:Microsoft YaHei
|
||||||
|
|||||||
+1
-1
@@ -365,7 +365,7 @@
|
|||||||
<span>页面:<span class="page-no">1</span>/<span class="page-size">1</span></span>
|
<span>页面:<span class="page-no">1</span>/<span class="page-size">1</span></span>
|
||||||
<span>字数:<span class="word-count">0</span></span>
|
<span>字数:<span class="word-count">0</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-mode" title="编辑模式(编辑、清洁、只读、表单)">编辑模式</div>
|
<div class="editor-mode" title="编辑模式(编辑、清洁、只读、表单、设计)">编辑模式</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="page-scale-minus" title="缩小(Ctrl+-)">
|
<div class="page-scale-minus" title="缩小(Ctrl+-)">
|
||||||
<i></i>
|
<i></i>
|
||||||
|
|||||||
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@hufe921/canvas-editor",
|
"name": "@liushuai05/canvas-editor",
|
||||||
"author": "Hufe",
|
"author": "Hufe",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "0.9.91",
|
"version": "0.9.89",
|
||||||
"description": "rich text editor by canvas/svg",
|
"description": "rich text editor by canvas/svg",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://registry.npmjs.org/",
|
"registry": "https://registry.yarnpkg.com/",
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -827,7 +827,9 @@ export class CommandAdapt {
|
|||||||
formatElementList([element], {
|
formatElementList([element], {
|
||||||
editorOptions: this.options
|
editorOptions: this.options
|
||||||
})
|
})
|
||||||
formatElementContext(elementList, [element], startIndex)
|
formatElementContext(elementList, [element], startIndex, {
|
||||||
|
editorOptions: this.options
|
||||||
|
})
|
||||||
const curIndex = startIndex + 1
|
const curIndex = startIndex + 1
|
||||||
this.draw.spliceElementList(
|
this.draw.spliceElementList(
|
||||||
elementList,
|
elementList,
|
||||||
@@ -1588,7 +1590,9 @@ export class CommandAdapt {
|
|||||||
}))
|
}))
|
||||||
if (!newElementList) return
|
if (!newElementList) return
|
||||||
const start = startIndex + 1
|
const start = startIndex + 1
|
||||||
formatElementContext(elementList, newElementList, startIndex)
|
formatElementContext(elementList, newElementList, startIndex, {
|
||||||
|
editorOptions: this.options
|
||||||
|
})
|
||||||
this.draw.spliceElementList(
|
this.draw.spliceElementList(
|
||||||
elementList,
|
elementList,
|
||||||
start,
|
start,
|
||||||
@@ -1733,7 +1737,9 @@ export class CommandAdapt {
|
|||||||
dashArray: payload
|
dashArray: payload
|
||||||
}
|
}
|
||||||
// 从行头增加分割线
|
// 从行头增加分割线
|
||||||
formatElementContext(elementList, [newElement], startIndex)
|
formatElementContext(elementList, [newElement], startIndex, {
|
||||||
|
editorOptions: this.options
|
||||||
|
})
|
||||||
if (startIndex !== 0 && elementList[startIndex].value === ZERO) {
|
if (startIndex !== 0 && elementList[startIndex].value === ZERO) {
|
||||||
this.draw.spliceElementList(elementList, startIndex, 1, newElement)
|
this.draw.spliceElementList(elementList, startIndex, 1, newElement)
|
||||||
curIndex = startIndex - 1
|
curIndex = startIndex - 1
|
||||||
@@ -2238,7 +2244,8 @@ export class CommandAdapt {
|
|||||||
const { startIndex } = this.range.getRange()
|
const { startIndex } = this.range.getRange()
|
||||||
const elementList = this.draw.getElementList()
|
const elementList = this.draw.getElementList()
|
||||||
formatElementContext(elementList, cloneElementList, startIndex, {
|
formatElementContext(elementList, cloneElementList, startIndex, {
|
||||||
isBreakWhenWrap: true
|
isBreakWhenWrap: true,
|
||||||
|
editorOptions: this.options
|
||||||
})
|
})
|
||||||
this.draw.insertElementList(cloneElementList)
|
this.draw.insertElementList(cloneElementList)
|
||||||
}
|
}
|
||||||
|
|||||||
+385
-62
@@ -102,6 +102,7 @@ import { PUNCTUATION_REG } from '../../dataset/constant/Regular'
|
|||||||
import { LineBreakParticle } from './particle/LineBreakParticle'
|
import { LineBreakParticle } from './particle/LineBreakParticle'
|
||||||
import { MouseObserver } from '../observer/MouseObserver'
|
import { MouseObserver } from '../observer/MouseObserver'
|
||||||
import { LineNumber } from './frame/LineNumber'
|
import { LineNumber } from './frame/LineNumber'
|
||||||
|
import { ITd } from '../../interface/table/Td'
|
||||||
import { PageBorder } from './frame/PageBorder'
|
import { PageBorder } from './frame/PageBorder'
|
||||||
|
|
||||||
export class Draw {
|
export class Draw {
|
||||||
@@ -314,6 +315,8 @@ export class Draw {
|
|||||||
|
|
||||||
public isReadonly() {
|
public isReadonly() {
|
||||||
switch (this.mode) {
|
switch (this.mode) {
|
||||||
|
case EditorMode.DESIGN:
|
||||||
|
return false
|
||||||
case EditorMode.READONLY:
|
case EditorMode.READONLY:
|
||||||
case EditorMode.PRINT:
|
case EditorMode.PRINT:
|
||||||
return true
|
return true
|
||||||
@@ -325,6 +328,7 @@ export class Draw {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isDisabled() {
|
public isDisabled() {
|
||||||
|
if (this.mode === EditorMode.DESIGN) return false
|
||||||
const { startIndex, endIndex } = this.range.getRange()
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
const elementList = this.getElementList()
|
const elementList = this.getElementList()
|
||||||
if (startIndex === endIndex) {
|
if (startIndex === endIndex) {
|
||||||
@@ -341,6 +345,10 @@ export class Draw {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isDesignMode() {
|
||||||
|
return this.mode === EditorMode.DESIGN
|
||||||
|
}
|
||||||
|
|
||||||
public getOriginalWidth(): number {
|
public getOriginalWidth(): number {
|
||||||
const { paperDirection, width, height } = this.options
|
const { paperDirection, width, height } = this.options
|
||||||
return paperDirection === PaperDirection.VERTICAL ? width : height
|
return paperDirection === PaperDirection.VERTICAL ? width : height
|
||||||
@@ -698,6 +706,7 @@ export class Draw {
|
|||||||
deleteCount: number,
|
deleteCount: number,
|
||||||
...items: IElement[]
|
...items: IElement[]
|
||||||
) {
|
) {
|
||||||
|
const isDesignMode = this.isDesignMode()
|
||||||
if (deleteCount > 0) {
|
if (deleteCount > 0) {
|
||||||
// 当最后元素与开始元素列表信息不一致时:清除当前列表信息
|
// 当最后元素与开始元素列表信息不一致时:清除当前列表信息
|
||||||
const endIndex = start + deleteCount
|
const endIndex = start + deleteCount
|
||||||
@@ -728,8 +737,9 @@ export class Draw {
|
|||||||
while (deleteIndex >= start) {
|
while (deleteIndex >= start) {
|
||||||
const deleteElement = elementList[deleteIndex]
|
const deleteElement = elementList[deleteIndex]
|
||||||
if (
|
if (
|
||||||
deleteElement?.control?.deletable !== false &&
|
isDesignMode ||
|
||||||
deleteElement?.title?.deletable !== false
|
(deleteElement?.control?.deletable !== false &&
|
||||||
|
deleteElement?.title?.deletable !== false)
|
||||||
) {
|
) {
|
||||||
elementList.splice(deleteIndex, 1)
|
elementList.splice(deleteIndex, 1)
|
||||||
}
|
}
|
||||||
@@ -1205,6 +1215,7 @@ export class Draw {
|
|||||||
table: { tdPadding },
|
table: { tdPadding },
|
||||||
defaultTabWidth
|
defaultTabWidth
|
||||||
} = this.options
|
} = this.options
|
||||||
|
let curIndex = payload.curIndex
|
||||||
const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight()
|
const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight()
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
|
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
|
||||||
@@ -1278,9 +1289,30 @@ export class Draw {
|
|||||||
} else if (element.type === ElementType.TABLE) {
|
} else if (element.type === ElementType.TABLE) {
|
||||||
const tdPaddingWidth = tdPadding[1] + tdPadding[3]
|
const tdPaddingWidth = tdPadding[1] + tdPadding[3]
|
||||||
const tdPaddingHeight = tdPadding[0] + tdPadding[2]
|
const tdPaddingHeight = tdPadding[0] + tdPadding[2]
|
||||||
|
const height = this.getHeight()
|
||||||
|
const marginHeight = this.getMainOuterHeight()
|
||||||
|
const emptyMainHeight = (height - marginHeight - rowMargin * 2) / scale
|
||||||
// 表格分页处理进度:https://github.com/Hufe921/canvas-editor/issues/41
|
// 表格分页处理进度:https://github.com/Hufe921/canvas-editor/issues/41
|
||||||
// 查看后续表格是否属于同一个源表格-存在即合并
|
// 查看后续表格是否属于同一个源表格-存在即合并
|
||||||
if (element.pagingId) {
|
if (element.pagingId) {
|
||||||
|
// 为当前表格构建一个虚拟表格
|
||||||
|
const virtualTable = Array.from(
|
||||||
|
{ length: element.trList!.length },
|
||||||
|
() => new Array(element.colgroup!.length)
|
||||||
|
) as Array<Array<ITd | undefined | null>>
|
||||||
|
element.trList!.forEach((tr, trIndex) => {
|
||||||
|
let tdIndex = 0
|
||||||
|
tr.tdList.forEach(td => {
|
||||||
|
while (virtualTable[trIndex][tdIndex] === null) {
|
||||||
|
tdIndex++
|
||||||
|
}
|
||||||
|
virtualTable[trIndex][tdIndex] = td
|
||||||
|
for (let i = 1; i < td.rowspan; i++) {
|
||||||
|
virtualTable[trIndex + i][tdIndex] = null
|
||||||
|
}
|
||||||
|
tdIndex += td.colspan
|
||||||
|
})
|
||||||
|
})
|
||||||
let tableIndex = i + 1
|
let tableIndex = i + 1
|
||||||
let combineCount = 0
|
let combineCount = 0
|
||||||
while (tableIndex < elementList.length) {
|
while (tableIndex < elementList.length) {
|
||||||
@@ -1289,8 +1321,65 @@ export class Draw {
|
|||||||
const nexTrList = nextElement.trList!.filter(
|
const nexTrList = nextElement.trList!.filter(
|
||||||
tr => !tr.pagingRepeat
|
tr => !tr.pagingRepeat
|
||||||
)
|
)
|
||||||
element.trList!.push(...nexTrList)
|
// 判断后续表格第一行是拆分出来的还是从原表格挪到下一页的
|
||||||
element.height! += nextElement.height!
|
const isNextTrSplit =
|
||||||
|
element.trList![element.trList!.length - 1].id ===
|
||||||
|
nexTrList[0].pagingOriginId
|
||||||
|
let tdIndex = 0
|
||||||
|
const mergedTds: ITd[] = []
|
||||||
|
nexTrList[0].tdList.forEach(td => {
|
||||||
|
let targetTd
|
||||||
|
// 如果虚拟表格最后一行对应位置有单元格,则其就为目标单元格,否则向上查找
|
||||||
|
if (virtualTable[virtualTable.length - 1][tdIndex]) {
|
||||||
|
targetTd = virtualTable[virtualTable.length - 1][tdIndex]
|
||||||
|
} else {
|
||||||
|
for (let i = virtualTable.length - 2; i >= 0; i--) {
|
||||||
|
if (virtualTable[i][tdIndex]) {
|
||||||
|
targetTd = virtualTable[i][tdIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targetTd) {
|
||||||
|
if (targetTd.id === td.pagingOriginId) {
|
||||||
|
targetTd.value.push(...td.value)
|
||||||
|
if (isNextTrSplit) {
|
||||||
|
targetTd.rowspan = targetTd.rowspan + td.rowspan - 1
|
||||||
|
} else {
|
||||||
|
targetTd.rowspan = targetTd.rowspan + td.rowspan
|
||||||
|
mergedTds.push(td)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tdIndex += targetTd.colspan
|
||||||
|
}
|
||||||
|
})
|
||||||
|
nexTrList[0].tdList = nexTrList[0].tdList.filter(td => {
|
||||||
|
const isNotMerged = mergedTds.every(
|
||||||
|
mergedTd => mergedTd.id !== td.id
|
||||||
|
)
|
||||||
|
delete td.pagingOriginId
|
||||||
|
return isNotMerged
|
||||||
|
})
|
||||||
|
while (nexTrList.length > 0) {
|
||||||
|
const lastTr = element.trList![element.trList!.length - 1]
|
||||||
|
const nextTr = nexTrList.shift()!
|
||||||
|
if (lastTr.id === nextTr.pagingOriginId) {
|
||||||
|
lastTr.height += nextTr.pagingOriginHeight || 0
|
||||||
|
// 更新id关联
|
||||||
|
lastTr.tdList.forEach(td => {
|
||||||
|
td.value.forEach(v => {
|
||||||
|
v.tdId = td.id
|
||||||
|
v.trId = lastTr.id
|
||||||
|
v.tableId = element.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
nextTr.height = nextTr.pagingOriginHeight || nextTr.height
|
||||||
|
element.trList!.push(nextTr)
|
||||||
|
}
|
||||||
|
delete nextTr.pagingOriginHeight
|
||||||
|
delete nextTr.pagingOriginId
|
||||||
|
}
|
||||||
tableIndex++
|
tableIndex++
|
||||||
combineCount++
|
combineCount++
|
||||||
} else {
|
} else {
|
||||||
@@ -1308,9 +1397,17 @@ export class Draw {
|
|||||||
const trList = element.trList!
|
const trList = element.trList!
|
||||||
for (let t = 0; t < trList.length; t++) {
|
for (let t = 0; t < trList.length; t++) {
|
||||||
const tr = trList[t]
|
const tr = trList[t]
|
||||||
|
// 行的minHeight最大只能为页面主内容区域高度(如果表格前第一行是空行,由于此时表格不能换页,因此需减掉空行的高度)
|
||||||
|
if (tr.minHeight && tr.minHeight > emptyMainHeight) {
|
||||||
|
if (i === 1 && elementList[0].value === ZERO) {
|
||||||
|
tr.minHeight = emptyMainHeight - rowList[0].height
|
||||||
|
} else {
|
||||||
|
tr.minHeight = emptyMainHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
for (let d = 0; d < tr.tdList.length; d++) {
|
for (let d = 0; d < tr.tdList.length; d++) {
|
||||||
const td = tr.tdList[d]
|
const td = tr.tdList[d]
|
||||||
const rowList = this.computeRowList({
|
const { rowList } = this.computeRowList({
|
||||||
innerWidth: (td.width! - tdPaddingWidth) * scale,
|
innerWidth: (td.width! - tdPaddingWidth) * scale,
|
||||||
elementList: td.value,
|
elementList: td.value,
|
||||||
isPagingMode
|
isPagingMode
|
||||||
@@ -1385,8 +1482,6 @@ export class Draw {
|
|||||||
metrics.boundingBoxAscent = -rowMargin
|
metrics.boundingBoxAscent = -rowMargin
|
||||||
// 表格分页处理(拆分表格)
|
// 表格分页处理(拆分表格)
|
||||||
if (isPagingMode) {
|
if (isPagingMode) {
|
||||||
const height = this.getHeight()
|
|
||||||
const marginHeight = this.getMainOuterHeight()
|
|
||||||
let curPagePreHeight = marginHeight
|
let curPagePreHeight = marginHeight
|
||||||
for (let r = 0; r < rowList.length; r++) {
|
for (let r = 0; r < rowList.length; r++) {
|
||||||
const row = rowList[r]
|
const row = rowList[r]
|
||||||
@@ -1400,9 +1495,11 @@ export class Draw {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 当前剩余高度是否能容下当前表格第一行(可拆分)的高度,排除掉表头类型
|
// 当前剩余高度是否能容下当前表格第一行(可拆分)的高度,排除掉表头类型
|
||||||
const rowMarginHeight = rowMargin * 2 * scale
|
const rowMarginHeight = rowMargin * 2
|
||||||
if (
|
if (
|
||||||
curPagePreHeight + element.trList![0].height! + rowMarginHeight >
|
curPagePreHeight +
|
||||||
|
element.trList![0].height! * scale +
|
||||||
|
rowMarginHeight >
|
||||||
height ||
|
height ||
|
||||||
(element.pagingIndex !== 0 && element.trList![0].pagingRepeat)
|
(element.pagingIndex !== 0 && element.trList![0].pagingRepeat)
|
||||||
) {
|
) {
|
||||||
@@ -1412,50 +1509,262 @@ export class Draw {
|
|||||||
// 表格高度超过页面高度开始截断行
|
// 表格高度超过页面高度开始截断行
|
||||||
if (curPagePreHeight + rowMarginHeight + elementHeight > height) {
|
if (curPagePreHeight + rowMarginHeight + elementHeight > height) {
|
||||||
const trList = element.trList!
|
const trList = element.trList!
|
||||||
// 计算需要移除的行数
|
/** 在哪一行截断表格 */
|
||||||
let deleteStart = 0
|
let splitTrIndex = -1
|
||||||
let deleteCount = 0
|
/** 被截断的行在截断线之前的高度 */
|
||||||
let preTrHeight = 0
|
let splitTrPreHeight = 0
|
||||||
// 大于一行时再拆分避免循环
|
// 根据表格行列数构造出一个虚拟表格
|
||||||
if (trList.length > 1) {
|
const virtualTable = Array.from({ length: trList.length }, () =>
|
||||||
for (let r = 0; r < trList.length; r++) {
|
new Array(element.colgroup?.length).fill(undefined)
|
||||||
const tr = trList[r]
|
) as Array<Array<ITd | undefined>>
|
||||||
const trHeight = tr.height * scale
|
trList.forEach(tr => {
|
||||||
|
tr.tdList.forEach(td => {
|
||||||
|
virtualTable[td.rowIndex!][td.colIndex!] = td
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// 加上表格上方的行间距
|
||||||
|
curPagePreHeight += rowMargin
|
||||||
|
for (const [trIndex, tr] of trList.entries()) {
|
||||||
|
// 找到需要截断的行,以及该行在截断线之前的高度
|
||||||
if (
|
if (
|
||||||
curPagePreHeight + rowMarginHeight + preTrHeight + trHeight >
|
!tr.pagingRepeat && // 标题行不可截断
|
||||||
height
|
curPagePreHeight + rowMargin + tr.height * scale > height
|
||||||
) {
|
) {
|
||||||
// 当前行存在跨行中断-暂时忽略分页
|
splitTrIndex = trIndex
|
||||||
const rowColCount = tr.tdList.reduce(
|
splitTrPreHeight = height - curPagePreHeight - rowMargin // 额外减去被拆分的表格到下方的行间距
|
||||||
(pre, cur) => pre + cur.colspan,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
if (element.colgroup?.length !== rowColCount) {
|
|
||||||
deleteCount = 0
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
deleteStart = r + 1
|
curPagePreHeight += tr.height * scale
|
||||||
deleteCount = trList.length - deleteStart
|
}
|
||||||
preTrHeight += trHeight
|
}
|
||||||
|
if (splitTrIndex > -1) {
|
||||||
|
// 构建出一个行作为被截断出来的新行
|
||||||
|
const cloneTr = new Array(element.colgroup!.length) as Array<
|
||||||
|
ITd | undefined
|
||||||
|
>
|
||||||
|
// 判断目标行是否可截断
|
||||||
|
const allowSplitTr =
|
||||||
|
splitTrPreHeight >= trList[splitTrIndex].minHeight! * scale && // 最小行高区间内不可截断
|
||||||
|
trList[splitTrIndex].tdList.every(
|
||||||
|
// 如果截断线穿过该行所有单元格中第一个排版行,此时该行也不可截断
|
||||||
|
td => td.rowList![0].height < splitTrPreHeight
|
||||||
|
)
|
||||||
|
if (!allowSplitTr) {
|
||||||
|
splitTrPreHeight = 0
|
||||||
|
}
|
||||||
|
// 遍历虚拟表格中的截断行,如果某个列的位置有td,表明截断的是这个td;如果该位置没有td,且该位置不是被列合并单元格所占,此时向上查找实际被截断的td
|
||||||
|
let preMergedEndIndex = -1
|
||||||
|
let splitTrReduceHeight =
|
||||||
|
trList[splitTrIndex].height - splitTrPreHeight / scale // 被拆分的行减少的高度
|
||||||
|
virtualTable[splitTrIndex].forEach((td, tdIndex) => {
|
||||||
|
if (tdIndex <= preMergedEndIndex) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 虚拟表格中截断行的当前索引位置是否有td
|
||||||
|
let hasTdAtCurIndex = false
|
||||||
|
// 找到被截断的td
|
||||||
|
let splitTd
|
||||||
|
if (td) {
|
||||||
|
splitTd = td
|
||||||
|
hasTdAtCurIndex = true
|
||||||
|
preMergedEndIndex = td.colIndex! + td.colspan - 1
|
||||||
|
} else {
|
||||||
|
for (let i = splitTrIndex; i >= 0; i--) {
|
||||||
|
if (virtualTable[i][tdIndex]) {
|
||||||
|
splitTd = virtualTable[i][tdIndex]
|
||||||
|
preMergedEndIndex =
|
||||||
|
splitTd!.colIndex! + splitTd!.colspan - 1
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (deleteCount) {
|
if (splitTd) {
|
||||||
const cloneTrList = trList.splice(deleteStart, deleteCount)
|
cloneTr[tdIndex] = deepClone(splitTd)
|
||||||
const cloneTrHeight = cloneTrList.reduce(
|
// 如果tr可拆分,根据截断位置,将td中的内容拆分到新行中
|
||||||
(pre, cur) => pre + cur.height,
|
// 如果tr不可拆分,但当前位置td是跨行单元格,同样需要拆分
|
||||||
|
if (allowSplitTr || splitTd.rowspan > 1) {
|
||||||
|
cloneTr[tdIndex]!.pagingOriginId =
|
||||||
|
splitTd.pagingOriginId || splitTd.id
|
||||||
|
cloneTr[tdIndex]!.id = getUUID()
|
||||||
|
// 计算当前td在截断线之前的高度(默认为被截断行在截断线之前的高度,若td跨行,则加上其他行高度)
|
||||||
|
let splitTdPreHeight = splitTrPreHeight
|
||||||
|
for (let i = splitTd.rowIndex!; i < splitTrIndex; i++) {
|
||||||
|
splitTdPreHeight += trList[i].height * scale
|
||||||
|
}
|
||||||
|
// 根据td中已排版的内容的高度,计算出哪些内容需要拆分到新行中
|
||||||
|
let splitTdPreRowHeight = 0
|
||||||
|
let splitTdRowIndex = -1
|
||||||
|
for (const [rowIndex, row] of splitTd.rowList!.entries()) {
|
||||||
|
if (
|
||||||
|
row.height +
|
||||||
|
splitTdPreRowHeight +
|
||||||
|
tdPaddingHeight * scale >
|
||||||
|
splitTdPreHeight
|
||||||
|
) {
|
||||||
|
splitTdRowIndex = rowIndex
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
splitTdPreRowHeight += row.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (splitTdRowIndex > -1) {
|
||||||
|
// 拆分td中的内容
|
||||||
|
cloneTr[tdIndex]!.rowList =
|
||||||
|
splitTd.rowList!.splice(splitTdRowIndex)
|
||||||
|
cloneTr[tdIndex]!.value = cloneTr[tdIndex]!.rowList!.map(
|
||||||
|
row => row.elementList
|
||||||
|
).flat()
|
||||||
|
splitTd.value = splitTd
|
||||||
|
.rowList!.map(row => row.elementList)
|
||||||
|
.flat()
|
||||||
|
// 计算被拆分的单元格高度减少了多少
|
||||||
|
const splitTdReduceMainHeight = cloneTr[
|
||||||
|
tdIndex
|
||||||
|
]!.rowList!.reduce((ret, row) => {
|
||||||
|
return ret + row.height / scale
|
||||||
|
}, 0)
|
||||||
|
splitTd.mainHeight! -= splitTdReduceMainHeight
|
||||||
|
// 如果目标行可拆分,计算出行减小的高度
|
||||||
|
if (
|
||||||
|
allowSplitTr &&
|
||||||
|
splitTd.rowspan === 1 &&
|
||||||
|
splitTdRowIndex > 0
|
||||||
|
) {
|
||||||
|
splitTrReduceHeight = Math.min(
|
||||||
|
splitTd.height! - splitTdPreRowHeight / scale,
|
||||||
|
splitTrReduceHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 执行到这里表示当前td虽然被截断,但是内容高度未达到截断线位置
|
||||||
|
// FIXME: 此时拆分出来的单元格为空,value中不应有元素,否则会影响单元格可编辑性以及单元格尺寸
|
||||||
|
cloneTr[tdIndex]!.value = [{ value: ZERO }]
|
||||||
|
if (allowSplitTr && splitTd.rowspan === 1) {
|
||||||
|
splitTrReduceHeight = Math.min(
|
||||||
|
splitTd.height! - splitTdPreHeight / scale,
|
||||||
|
splitTrReduceHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 更新td的rowspan
|
||||||
|
if (hasTdAtCurIndex) {
|
||||||
|
cloneTr[tdIndex]!.rowspan = splitTd.rowspan
|
||||||
|
splitTd.rowspan = 1
|
||||||
|
} else {
|
||||||
|
cloneTr[tdIndex]!.rowspan =
|
||||||
|
splitTd.rowspan - splitTrIndex + splitTd.rowIndex!
|
||||||
|
splitTd.rowspan = splitTrIndex - splitTd.rowIndex! + 1
|
||||||
|
// 如果tr不可截断,且当前td是跨行单元格,由于tr会被挪到后续表格,因此需要将跨行单元格的rowspan减1
|
||||||
|
if (!allowSplitTr && splitTd.rowspan > 1) {
|
||||||
|
splitTd.rowspan--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 构造新行
|
||||||
|
const newTr = deepClone(trList[splitTrIndex])
|
||||||
|
newTr.tdList = cloneTr.filter(td => td !== undefined) as ITd[]
|
||||||
|
if (allowSplitTr) {
|
||||||
|
newTr.pagingOriginId = newTr.pagingOriginId || newTr.id
|
||||||
|
newTr.id = getUUID()
|
||||||
|
trList[splitTrIndex].height -= splitTrReduceHeight
|
||||||
|
trList[splitTrIndex].tdList.forEach(td => {
|
||||||
|
td.realHeight! -= splitTrReduceHeight
|
||||||
|
td.height! -= splitTrReduceHeight
|
||||||
|
})
|
||||||
|
// 记录拆分出来的新行的原始高度
|
||||||
|
newTr.pagingOriginHeight = splitTrReduceHeight
|
||||||
|
newTr.height = Math.max(
|
||||||
|
splitTrReduceHeight + tdPaddingHeight,
|
||||||
|
newTr.minHeight!
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 记录被挪到下一页的行的原始高度
|
||||||
|
newTr.pagingOriginHeight = newTr.height
|
||||||
|
}
|
||||||
|
// 构造出后续表格的行
|
||||||
|
const cloneTrList = trList.splice(splitTrIndex + 1)
|
||||||
|
cloneTrList.unshift(newTr)
|
||||||
|
// 如果当前表格行不可从中间拆分,此行将被挪至后续表格,因此将其从当前表格中移除
|
||||||
|
if (!allowSplitTr) {
|
||||||
|
trList.splice(splitTrIndex, 1)
|
||||||
|
}
|
||||||
|
let totalHeight = 0
|
||||||
|
// 更新后续表格首行行高
|
||||||
|
const crossRowTds: ITd[] = []
|
||||||
|
newTr.tdList.forEach(td => {
|
||||||
|
if (td.rowspan === 1) {
|
||||||
|
const mainHeight =
|
||||||
|
td.rowList!.reduce(
|
||||||
|
(ret, row) => ret + row.height / scale,
|
||||||
|
0
|
||||||
|
) + tdPaddingHeight
|
||||||
|
newTr.height = Math.max(mainHeight, newTr.height!)
|
||||||
|
} else {
|
||||||
|
crossRowTds.push(td)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
totalHeight += newTr.height
|
||||||
|
const groupTds = crossRowTds.reduce((ret, td) => {
|
||||||
|
const key = td.rowspan
|
||||||
|
if (ret[key]) {
|
||||||
|
ret[key].push(td)
|
||||||
|
} else {
|
||||||
|
ret[key] = [td]
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}, {} as { [key: number]: ITd[] })
|
||||||
|
const maxRowspan = Math.max(
|
||||||
|
...Object.keys(groupTds).map(parseInt)
|
||||||
|
)
|
||||||
|
// 计算出跨行单元格最大内容高度,如果高于其rowspan区间的行高之和,增加rowspan区间中最后一行的行高
|
||||||
|
for (let i = 2; i <= maxRowspan; i++) {
|
||||||
|
const tds = groupTds[i]
|
||||||
|
if (tds) {
|
||||||
|
let maxMainHeight = 0
|
||||||
|
tds.forEach(td => {
|
||||||
|
const mainHeight =
|
||||||
|
td.rowList!.reduce(
|
||||||
|
(ret, row) => ret + row.height / scale,
|
||||||
|
0
|
||||||
|
) + tdPaddingHeight
|
||||||
|
maxMainHeight = Math.max(mainHeight, maxMainHeight)
|
||||||
|
})
|
||||||
|
if (maxMainHeight > totalHeight) {
|
||||||
|
// 记录此行原始高度,便于合并时还原行高
|
||||||
|
cloneTrList[i - 1].pagingOriginHeight =
|
||||||
|
cloneTrList[i - 1].height
|
||||||
|
cloneTrList[i - 1].height += maxMainHeight - totalHeight
|
||||||
|
}
|
||||||
|
totalHeight += maxMainHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const totalOriginHeight = cloneTrList.reduce(
|
||||||
|
(ret, tr) => ret + (tr.pagingOriginHeight || tr.height),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
// 追加拆分表格
|
||||||
const pagingId = element.pagingId || getUUID()
|
const pagingId = element.pagingId || getUUID()
|
||||||
element.pagingId = pagingId
|
element.pagingId = pagingId
|
||||||
element.height -= cloneTrHeight
|
element.height -= totalOriginHeight
|
||||||
metrics.height -= cloneTrHeight
|
metrics.height -= totalOriginHeight * scale
|
||||||
metrics.boundingBoxDescent -= cloneTrHeight
|
metrics.boundingBoxDescent -= totalOriginHeight * scale
|
||||||
// 追加拆分表格
|
|
||||||
const cloneElement = deepClone(element)
|
const cloneElement = deepClone(element)
|
||||||
|
cloneElement.id = getUUID()
|
||||||
cloneElement.pagingId = pagingId
|
cloneElement.pagingId = pagingId
|
||||||
cloneElement.pagingIndex = element.pagingIndex! + 1
|
cloneElement.pagingIndex = element.pagingIndex! + 1
|
||||||
|
this.tableParticle.computeRowColInfo(element)
|
||||||
|
// 更新拆分出来的新表格中的id关联信息
|
||||||
|
cloneTrList.forEach(tr => {
|
||||||
|
tr.tdList.forEach(td => {
|
||||||
|
td.value.forEach(v => {
|
||||||
|
v.tdId = td.id
|
||||||
|
v.trId = tr.id
|
||||||
|
v.tableId = cloneElement.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
// 处理分页重复表头
|
// 处理分页重复表头
|
||||||
const repeatTrList = trList.filter(tr => tr.pagingRepeat)
|
const repeatTrList = trList.filter(tr => tr.pagingRepeat)
|
||||||
if (repeatTrList.length) {
|
if (repeatTrList.length) {
|
||||||
@@ -1464,35 +1773,46 @@ export class Draw {
|
|||||||
cloneTrList.unshift(...cloneRepeatTrList)
|
cloneTrList.unshift(...cloneRepeatTrList)
|
||||||
}
|
}
|
||||||
cloneElement.trList = cloneTrList
|
cloneElement.trList = cloneTrList
|
||||||
cloneElement.id = getUUID()
|
|
||||||
this.spliceElementList(elementList, i + 1, 0, cloneElement)
|
this.spliceElementList(elementList, i + 1, 0, cloneElement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 表格经过分页处理-需要处理上下文
|
// 表格经过分页处理-需要处理上下文和选区
|
||||||
if (element.pagingId) {
|
if (element.pagingId) {
|
||||||
const positionContext = this.position.getPositionContext()
|
const positionContext = this.position.getPositionContext()
|
||||||
if (positionContext.isTable) {
|
if (
|
||||||
// 查找光标所在表格索引(根据trId搜索)
|
positionContext.isTable &&
|
||||||
let newPositionContextIndex = -1
|
positionContext.tableId === element.id
|
||||||
let newPositionContextTrIndex = -1
|
) {
|
||||||
let tableIndex = i
|
const trIndex = element.trList!.findIndex(
|
||||||
while (tableIndex < elementList.length) {
|
r =>
|
||||||
const curElement = elementList[tableIndex]
|
r.pagingOriginId === positionContext.trId ||
|
||||||
if (curElement.pagingId !== element.pagingId) break
|
r.id === positionContext.trId
|
||||||
const trIndex = curElement.trList!.findIndex(
|
|
||||||
r => r.id === positionContext.trId
|
|
||||||
)
|
)
|
||||||
if (~trIndex) {
|
if (~trIndex) {
|
||||||
newPositionContextIndex = tableIndex
|
const tr = element.trList![trIndex]
|
||||||
newPositionContextTrIndex = trIndex
|
const tdIndex = tr.tdList!.findIndex(
|
||||||
break
|
d =>
|
||||||
|
d.pagingOriginId === positionContext.tdId ||
|
||||||
|
d.id === positionContext.tdId
|
||||||
|
)
|
||||||
|
if (~tdIndex) {
|
||||||
|
const td = tr.tdList![tdIndex]
|
||||||
|
if (curIndex !== undefined && curIndex > -1) {
|
||||||
|
if (td.value[curIndex]) {
|
||||||
|
positionContext.index = i
|
||||||
|
positionContext.trIndex = trIndex
|
||||||
|
positionContext.tdIndex = tdIndex
|
||||||
|
positionContext.trId = tr.id
|
||||||
|
positionContext.tdId = td.id
|
||||||
|
this.range.setRange(curIndex, curIndex)
|
||||||
|
} else {
|
||||||
|
positionContext.tableId = elementList[i + 1].id
|
||||||
|
curIndex -= td.value.length
|
||||||
}
|
}
|
||||||
tableIndex++
|
|
||||||
}
|
}
|
||||||
if (~newPositionContextIndex) {
|
}
|
||||||
positionContext.index = newPositionContextIndex
|
} else {
|
||||||
positionContext.trIndex = newPositionContextTrIndex
|
positionContext.tableId = elementList[i + 1].id
|
||||||
this.position.setPositionContext(positionContext)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1739,7 +2059,7 @@ export class Draw {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rowList
|
return { rowList, curIndex }
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computePageList(): IRow[][] {
|
private _computePageList(): IRow[][] {
|
||||||
@@ -2372,11 +2692,14 @@ export class Draw {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 行信息
|
// 行信息
|
||||||
this.rowList = this.computeRowList({
|
const { rowList, curIndex: newIndex } = this.computeRowList({
|
||||||
isPagingMode,
|
isPagingMode,
|
||||||
innerWidth,
|
innerWidth,
|
||||||
elementList: this.elementList
|
elementList: this.elementList,
|
||||||
|
curIndex
|
||||||
})
|
})
|
||||||
|
this.rowList = rowList
|
||||||
|
curIndex = newIndex
|
||||||
// 页面信息
|
// 页面信息
|
||||||
this.pageRowList = this._computePageList()
|
this.pageRowList = this._computePageList()
|
||||||
// 位置信息
|
// 位置信息
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export class Control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getIsDisabledControl(context: IControlContext = {}): boolean {
|
public getIsDisabledControl(context: IControlContext = {}): boolean {
|
||||||
if (!this.activeControl) return false
|
if (this.draw.isDesignMode() || !this.activeControl) return false
|
||||||
const { startIndex, endIndex } = context.range || this.range.getRange()
|
const { startIndex, endIndex } = context.range || this.range.getRange()
|
||||||
if (startIndex === endIndex && ~startIndex && ~endIndex) {
|
if (startIndex === endIndex && ~startIndex && ~endIndex) {
|
||||||
const elementList = context.elementList || this.getElementList()
|
const elementList = context.elementList || this.getElementList()
|
||||||
@@ -452,8 +452,11 @@ export class Control {
|
|||||||
): number | null {
|
): number | null {
|
||||||
const elementList = context.elementList || this.getElementList()
|
const elementList = context.elementList || this.getElementList()
|
||||||
const startElement = elementList[startIndex]
|
const startElement = elementList[startIndex]
|
||||||
|
// 设计模式不验证删除权限
|
||||||
|
if (!this.draw.isDesignMode()) {
|
||||||
const { deletable = true } = startElement.control!
|
const { deletable = true } = startElement.control!
|
||||||
if (!deletable) return null
|
if (!deletable) return null
|
||||||
|
}
|
||||||
let leftIndex = -1
|
let leftIndex = -1
|
||||||
let rightIndex = -1
|
let rightIndex = -1
|
||||||
// 向左查找
|
// 向左查找
|
||||||
@@ -538,7 +541,9 @@ export class Control {
|
|||||||
controlComponent: ControlComponent.PLACEHOLDER,
|
controlComponent: ControlComponent.PLACEHOLDER,
|
||||||
color: this.controlOptions.placeholderColor
|
color: this.controlOptions.placeholderColor
|
||||||
}
|
}
|
||||||
formatElementContext(elementList, [newElement], startIndex)
|
formatElementContext(elementList, [newElement], startIndex, {
|
||||||
|
editorOptions: this.options
|
||||||
|
})
|
||||||
this.draw.spliceElementList(
|
this.draw.spliceElementList(
|
||||||
elementList,
|
elementList,
|
||||||
startIndex + p + 1,
|
startIndex + p + 1,
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import {
|
|||||||
import { ControlComponent } from '../../../../dataset/enum/Control'
|
import { ControlComponent } from '../../../../dataset/enum/Control'
|
||||||
import { ElementType } from '../../../../dataset/enum/Element'
|
import { ElementType } from '../../../../dataset/enum/Element'
|
||||||
import { KeyMap } from '../../../../dataset/enum/KeyMap'
|
import { KeyMap } from '../../../../dataset/enum/KeyMap'
|
||||||
|
import { DeepRequired } from '../../../../interface/Common'
|
||||||
import {
|
import {
|
||||||
IControlContext,
|
IControlContext,
|
||||||
IControlInstance,
|
IControlInstance,
|
||||||
IControlRuleOption
|
IControlRuleOption
|
||||||
} from '../../../../interface/Control'
|
} from '../../../../interface/Control'
|
||||||
|
import { IEditorOption } from '../../../../interface/Editor'
|
||||||
import { IElement } from '../../../../interface/Element'
|
import { IElement } from '../../../../interface/Element'
|
||||||
import { omitObject, pickObject } from '../../../../utils'
|
import { omitObject, pickObject } from '../../../../utils'
|
||||||
import { formatElementContext } from '../../../../utils/element'
|
import { formatElementContext } from '../../../../utils/element'
|
||||||
@@ -24,10 +26,12 @@ export class DateControl implements IControlInstance {
|
|||||||
private control: Control
|
private control: Control
|
||||||
private isPopup: boolean
|
private isPopup: boolean
|
||||||
private datePicker: DatePicker | null
|
private datePicker: DatePicker | null
|
||||||
|
private options: DeepRequired<IEditorOption>
|
||||||
|
|
||||||
constructor(element: IElement, control: Control) {
|
constructor(element: IElement, control: Control) {
|
||||||
const draw = control.getDraw()
|
const draw = control.getDraw()
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
|
this.options = draw.getOptions()
|
||||||
this.element = element
|
this.element = element
|
||||||
this.control = control
|
this.control = control
|
||||||
this.isPopup = false
|
this.isPopup = false
|
||||||
@@ -138,7 +142,9 @@ export class DateControl implements IControlInstance {
|
|||||||
...data[i],
|
...data[i],
|
||||||
controlComponent: ControlComponent.VALUE
|
controlComponent: ControlComponent.VALUE
|
||||||
}
|
}
|
||||||
formatElementContext(elementList, [newElement], startIndex)
|
formatElementContext(elementList, [newElement], startIndex, {
|
||||||
|
editorOptions: this.options
|
||||||
|
})
|
||||||
draw.spliceElementList(elementList, start + i, 0, newElement)
|
draw.spliceElementList(elementList, start + i, 0, newElement)
|
||||||
}
|
}
|
||||||
return start + data.length - 1
|
return start + data.length - 1
|
||||||
@@ -207,7 +213,9 @@ export class DateControl implements IControlInstance {
|
|||||||
value: date[i],
|
value: date[i],
|
||||||
controlComponent: ControlComponent.VALUE
|
controlComponent: ControlComponent.VALUE
|
||||||
}
|
}
|
||||||
formatElementContext(elementList, [newElement], prefixIndex)
|
formatElementContext(elementList, [newElement], prefixIndex, {
|
||||||
|
editorOptions: this.options
|
||||||
|
})
|
||||||
draw.spliceElementList(elementList, start + i, 0, newElement)
|
draw.spliceElementList(elementList, start + i, 0, newElement)
|
||||||
}
|
}
|
||||||
// 重新渲染控件
|
// 重新渲染控件
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import { ControlComponent } from '../../../../dataset/enum/Control'
|
|||||||
import { EditorComponent } from '../../../../dataset/enum/Editor'
|
import { EditorComponent } from '../../../../dataset/enum/Editor'
|
||||||
import { ElementType } from '../../../../dataset/enum/Element'
|
import { ElementType } from '../../../../dataset/enum/Element'
|
||||||
import { KeyMap } from '../../../../dataset/enum/KeyMap'
|
import { KeyMap } from '../../../../dataset/enum/KeyMap'
|
||||||
|
import { DeepRequired } from '../../../../interface/Common'
|
||||||
import {
|
import {
|
||||||
IControlContext,
|
IControlContext,
|
||||||
IControlInstance,
|
IControlInstance,
|
||||||
IControlRuleOption
|
IControlRuleOption
|
||||||
} from '../../../../interface/Control'
|
} from '../../../../interface/Control'
|
||||||
|
import { IEditorOption } from '../../../../interface/Editor'
|
||||||
import { IElement } from '../../../../interface/Element'
|
import { IElement } from '../../../../interface/Element'
|
||||||
import { omitObject, pickObject, splitText } from '../../../../utils'
|
import { omitObject, pickObject, splitText } from '../../../../utils'
|
||||||
import { formatElementContext } from '../../../../utils/element'
|
import { formatElementContext } from '../../../../utils/element'
|
||||||
@@ -25,8 +27,10 @@ export class SelectControl implements IControlInstance {
|
|||||||
private control: Control
|
private control: Control
|
||||||
private isPopup: boolean
|
private isPopup: boolean
|
||||||
private selectDom: HTMLDivElement | null
|
private selectDom: HTMLDivElement | null
|
||||||
|
private options: DeepRequired<IEditorOption>
|
||||||
|
|
||||||
constructor(element: IElement, control: Control) {
|
constructor(element: IElement, control: Control) {
|
||||||
|
this.options = control.getDraw().getOptions()
|
||||||
this.element = element
|
this.element = element
|
||||||
this.control = control
|
this.control = control
|
||||||
this.isPopup = false
|
this.isPopup = false
|
||||||
@@ -270,7 +274,9 @@ export class SelectControl implements IControlInstance {
|
|||||||
value: data[i],
|
value: data[i],
|
||||||
controlComponent: ControlComponent.VALUE
|
controlComponent: ControlComponent.VALUE
|
||||||
}
|
}
|
||||||
formatElementContext(elementList, [newElement], prefixIndex)
|
formatElementContext(elementList, [newElement], prefixIndex, {
|
||||||
|
editorOptions: this.options
|
||||||
|
})
|
||||||
draw.spliceElementList(elementList, start + i, 0, newElement)
|
draw.spliceElementList(elementList, start + i, 0, newElement)
|
||||||
}
|
}
|
||||||
// 设置状态
|
// 设置状态
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import {
|
|||||||
} from '../../../../dataset/constant/Element'
|
} from '../../../../dataset/constant/Element'
|
||||||
import { ControlComponent } from '../../../../dataset/enum/Control'
|
import { ControlComponent } from '../../../../dataset/enum/Control'
|
||||||
import { KeyMap } from '../../../../dataset/enum/KeyMap'
|
import { KeyMap } from '../../../../dataset/enum/KeyMap'
|
||||||
|
import { DeepRequired } from '../../../../interface/Common'
|
||||||
import {
|
import {
|
||||||
IControlContext,
|
IControlContext,
|
||||||
IControlInstance,
|
IControlInstance,
|
||||||
IControlRuleOption
|
IControlRuleOption
|
||||||
} from '../../../../interface/Control'
|
} from '../../../../interface/Control'
|
||||||
|
import { IEditorOption } from '../../../../interface/Editor'
|
||||||
import { IElement } from '../../../../interface/Element'
|
import { IElement } from '../../../../interface/Element'
|
||||||
import { omitObject, pickObject } from '../../../../utils'
|
import { omitObject, pickObject } from '../../../../utils'
|
||||||
import { formatElementContext } from '../../../../utils/element'
|
import { formatElementContext } from '../../../../utils/element'
|
||||||
@@ -17,8 +19,10 @@ import { Control } from '../Control'
|
|||||||
export class TextControl implements IControlInstance {
|
export class TextControl implements IControlInstance {
|
||||||
private element: IElement
|
private element: IElement
|
||||||
private control: Control
|
private control: Control
|
||||||
|
private options: DeepRequired<IEditorOption>
|
||||||
|
|
||||||
constructor(element: IElement, control: Control) {
|
constructor(element: IElement, control: Control) {
|
||||||
|
this.options = control.getDraw().getOptions()
|
||||||
this.element = element
|
this.element = element
|
||||||
this.control = control
|
this.control = control
|
||||||
}
|
}
|
||||||
@@ -114,7 +118,9 @@ export class TextControl implements IControlInstance {
|
|||||||
...data[i],
|
...data[i],
|
||||||
controlComponent: ControlComponent.VALUE
|
controlComponent: ControlComponent.VALUE
|
||||||
}
|
}
|
||||||
formatElementContext(elementList, [newElement], startIndex)
|
formatElementContext(elementList, [newElement], startIndex, {
|
||||||
|
editorOptions: this.options
|
||||||
|
})
|
||||||
draw.spliceElementList(elementList, start + i, 0, newElement)
|
draw.spliceElementList(elementList, start + i, 0, newElement)
|
||||||
}
|
}
|
||||||
return start + data.length - 1
|
return start + data.length - 1
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class Footer {
|
|||||||
this.rowList = this.draw.computeRowList({
|
this.rowList = this.draw.computeRowList({
|
||||||
innerWidth,
|
innerWidth,
|
||||||
elementList: this.elementList
|
elementList: this.elementList
|
||||||
})
|
}).rowList
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computePositionList() {
|
private _computePositionList() {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class Header {
|
|||||||
this.rowList = this.draw.computeRowList({
|
this.rowList = this.draw.computeRowList({
|
||||||
innerWidth,
|
innerWidth,
|
||||||
elementList: this.elementList
|
elementList: this.elementList
|
||||||
})
|
}).rowList
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computePositionList() {
|
private _computePositionList() {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class Placeholder {
|
|||||||
this.rowList = this.draw.computeRowList({
|
this.rowList = this.draw.computeRowList({
|
||||||
innerWidth,
|
innerWidth,
|
||||||
elementList: this.elementList
|
elementList: this.elementList
|
||||||
})
|
}).rowList
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computePositionList() {
|
private _computePositionList() {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { ElementType } from '../../../../dataset/enum/Element'
|
import { ElementType } from '../../../../dataset/enum/Element'
|
||||||
|
import { DeepRequired } from '../../../../interface/Common'
|
||||||
|
import { IEditorOption } from '../../../../interface/Editor'
|
||||||
import { IElement, IElementPosition } from '../../../../interface/Element'
|
import { IElement, IElementPosition } from '../../../../interface/Element'
|
||||||
import { formatElementContext } from '../../../../utils/element'
|
import { formatElementContext } from '../../../../utils/element'
|
||||||
import { RangeManager } from '../../../range/RangeManager'
|
import { RangeManager } from '../../../range/RangeManager'
|
||||||
@@ -9,9 +11,11 @@ export class DateParticle {
|
|||||||
private draw: Draw
|
private draw: Draw
|
||||||
private range: RangeManager
|
private range: RangeManager
|
||||||
private datePicker: DatePicker
|
private datePicker: DatePicker
|
||||||
|
private options: DeepRequired<IEditorOption>
|
||||||
|
|
||||||
constructor(draw: Draw) {
|
constructor(draw: Draw) {
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
|
this.options = draw.getOptions()
|
||||||
this.range = draw.getRange()
|
this.range = draw.getRange()
|
||||||
this.datePicker = new DatePicker(draw, {
|
this.datePicker = new DatePicker(draw, {
|
||||||
onSubmit: this._setValue.bind(this)
|
onSubmit: this._setValue.bind(this)
|
||||||
@@ -43,7 +47,9 @@ export class DateParticle {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
formatElementContext(elementList, [dateElement], leftIndex)
|
formatElementContext(elementList, [dateElement], leftIndex, {
|
||||||
|
editorOptions: this.options
|
||||||
|
})
|
||||||
this.draw.insertElementList([dateElement])
|
this.draw.insertElementList([dateElement])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,15 @@ export function input(data: string, host: CanvasEvent) {
|
|||||||
const elementList = draw.getElementList()
|
const elementList = draw.getElementList()
|
||||||
const copyElement = getAnchorElement(elementList, endIndex)
|
const copyElement = getAnchorElement(elementList, endIndex)
|
||||||
if (!copyElement) return
|
if (!copyElement) return
|
||||||
|
const isDesignMode = draw.isDesignMode()
|
||||||
const inputData: IElement[] = splitText(text).map(value => {
|
const inputData: IElement[] = splitText(text).map(value => {
|
||||||
const newElement: IElement = {
|
const newElement: IElement = {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
if (!copyElement.title?.disabled && !copyElement.control?.disabled) {
|
if (
|
||||||
|
isDesignMode ||
|
||||||
|
(!copyElement.title?.disabled && !copyElement.control?.disabled)
|
||||||
|
) {
|
||||||
const nextElement = elementList[endIndex + 1]
|
const nextElement = elementList[endIndex + 1]
|
||||||
if (
|
if (
|
||||||
!copyElement.type ||
|
!copyElement.type ||
|
||||||
@@ -69,7 +73,9 @@ export function input(data: string, host: CanvasEvent) {
|
|||||||
if (startIndex !== endIndex) {
|
if (startIndex !== endIndex) {
|
||||||
draw.spliceElementList(elementList, start, endIndex - startIndex)
|
draw.spliceElementList(elementList, start, endIndex - startIndex)
|
||||||
}
|
}
|
||||||
formatElementContext(elementList, inputData, startIndex)
|
formatElementContext(elementList, inputData, startIndex, {
|
||||||
|
editorOptions: draw.getOptions()
|
||||||
|
})
|
||||||
draw.spliceElementList(elementList, start, 0, ...inputData)
|
draw.spliceElementList(elementList, start, 0, ...inputData)
|
||||||
curIndex = startIndex + inputData.length
|
curIndex = startIndex + inputData.length
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export function enter(evt: KeyboardEvent, host: CanvasEvent) {
|
|||||||
}
|
}
|
||||||
// 格式化上下文
|
// 格式化上下文
|
||||||
formatElementContext(elementList, [enterText], startIndex, {
|
formatElementContext(elementList, [enterText], startIndex, {
|
||||||
isBreakWhenWrap: true
|
isBreakWhenWrap: true,
|
||||||
|
editorOptions: draw.getOptions()
|
||||||
})
|
})
|
||||||
// 标题结尾处回车无需格式化及样式复制
|
// 标题结尾处回车无需格式化及样式复制
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ export function tab(evt: KeyboardEvent, host: CanvasEvent) {
|
|||||||
const rangeManager = draw.getRange()
|
const rangeManager = draw.getRange()
|
||||||
const { startIndex } = rangeManager.getRange()
|
const { startIndex } = rangeManager.getRange()
|
||||||
const elementList = draw.getElementList()
|
const elementList = draw.getElementList()
|
||||||
formatElementContext(elementList, [tabElement], startIndex)
|
formatElementContext(elementList, [tabElement], startIndex, {
|
||||||
|
editorOptions: draw.getOptions()
|
||||||
|
})
|
||||||
draw.insertElementList([tabElement])
|
draw.insertElementList([tabElement])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,9 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
|
|||||||
return newElement
|
return newElement
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
formatElementContext(elementList, replaceElementList, range.startIndex)
|
formatElementContext(elementList, replaceElementList, range.startIndex, {
|
||||||
|
editorOptions: draw.getOptions()
|
||||||
|
})
|
||||||
// 缓存拖拽选区开始元素、位置、开始结束id
|
// 缓存拖拽选区开始元素、位置、开始结束id
|
||||||
const cacheStartElement = cacheElementList[cacheStartIndex]
|
const cacheStartElement = cacheElementList[cacheStartIndex]
|
||||||
const cacheStartPosition = cachePositionList[cacheStartIndex]
|
const cacheStartPosition = cachePositionList[cacheStartIndex]
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ export function pasteElement(host: CanvasEvent, elementList: IElement[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
formatElementContext(originalElementList, elementList, startIndex, {
|
formatElementContext(originalElementList, elementList, startIndex, {
|
||||||
isBreakWhenWrap: true
|
isBreakWhenWrap: true,
|
||||||
|
editorOptions: draw.getOptions()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
draw.insertElementList(elementList)
|
draw.insertElementList(elementList)
|
||||||
@@ -87,7 +88,9 @@ export function pasteImage(host: CanvasEvent, file: File | Blob) {
|
|||||||
height: image.height
|
height: image.height
|
||||||
}
|
}
|
||||||
if (~startIndex) {
|
if (~startIndex) {
|
||||||
formatElementContext(elementList, [imageElement], startIndex)
|
formatElementContext(elementList, [imageElement], startIndex, {
|
||||||
|
editorOptions: draw.getOptions()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
draw.insertElementList([imageElement])
|
draw.insertElementList([imageElement])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export enum EditorMode {
|
|||||||
CLEAN = 'clean', // 清洁模式(隐藏辅助元素)
|
CLEAN = 'clean', // 清洁模式(隐藏辅助元素)
|
||||||
READONLY = 'readonly', // 只读模式(文档不可编辑)
|
READONLY = 'readonly', // 只读模式(文档不可编辑)
|
||||||
FORM = 'form', // 表单模式(仅控件内可编辑)
|
FORM = 'form', // 表单模式(仅控件内可编辑)
|
||||||
PRINT = 'print' // 打印模式(文档不可编辑、隐藏辅助元素、选区、未书写控件及边框)
|
PRINT = 'print', // 打印模式(文档不可编辑、隐藏辅助元素、选区、未书写控件及边框)
|
||||||
|
DESIGN = 'design' // 设计模式(不可删除、只读等配置不控制)
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EditorZone {
|
export enum EditorZone {
|
||||||
|
|||||||
@@ -70,4 +70,5 @@ export interface IComputeRowListPayload {
|
|||||||
innerWidth: number
|
innerWidth: number
|
||||||
elementList: IElement[]
|
elementList: IElement[]
|
||||||
isPagingMode?: boolean
|
isPagingMode?: boolean
|
||||||
|
curIndex?: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,4 +29,5 @@ export interface ITd {
|
|||||||
mainHeight?: number // 内容 + 内边距高度
|
mainHeight?: number // 内容 + 内边距高度
|
||||||
realHeight?: number // 真实高度(包含跨列)
|
realHeight?: number // 真实高度(包含跨列)
|
||||||
realMinHeight?: number // 真实最小高度(包含跨列)
|
realMinHeight?: number // 真实最小高度(包含跨列)
|
||||||
|
pagingOriginId?: string // 被拆分到下一页的单元格的原始id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ export interface ITr {
|
|||||||
tdList: ITd[]
|
tdList: ITd[]
|
||||||
minHeight?: number
|
minHeight?: number
|
||||||
pagingRepeat?: boolean // 在各页顶端以标题行的形式重复出现
|
pagingRepeat?: boolean // 在各页顶端以标题行的形式重复出现
|
||||||
|
pagingOriginHeight?: number // 被拆分到下一页的行的原始高度
|
||||||
|
pagingOriginId?: string // 被拆分到下一页的行的原始id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
splitText
|
splitText
|
||||||
} from '.'
|
} from '.'
|
||||||
import {
|
import {
|
||||||
|
EditorMode,
|
||||||
ElementType,
|
ElementType,
|
||||||
IEditorOption,
|
IEditorOption,
|
||||||
IElement,
|
IElement,
|
||||||
@@ -606,13 +607,85 @@ export function zipElementList(
|
|||||||
} else if (element.type === ElementType.TABLE) {
|
} else if (element.type === ElementType.TABLE) {
|
||||||
// 分页表格先进行合并
|
// 分页表格先进行合并
|
||||||
if (element.pagingId) {
|
if (element.pagingId) {
|
||||||
|
// 为当前表格构建一个虚拟表格
|
||||||
|
const virtualTable = Array.from(
|
||||||
|
{ length: element.trList!.length },
|
||||||
|
() => new Array(element.colgroup!.length)
|
||||||
|
) as Array<Array<ITd | undefined | null>>
|
||||||
|
element.trList!.forEach((tr, trIndex) => {
|
||||||
|
let tdIndex = 0
|
||||||
|
tr.tdList.forEach(td => {
|
||||||
|
while (virtualTable[trIndex][tdIndex] === null) {
|
||||||
|
tdIndex++
|
||||||
|
}
|
||||||
|
virtualTable[trIndex][tdIndex] = td
|
||||||
|
for (let i = 1; i < td.rowspan; i++) {
|
||||||
|
virtualTable[trIndex + i][tdIndex] = null
|
||||||
|
}
|
||||||
|
tdIndex += td.colspan
|
||||||
|
})
|
||||||
|
})
|
||||||
let tableIndex = e + 1
|
let tableIndex = e + 1
|
||||||
let combineCount = 0
|
let combineCount = 0
|
||||||
while (tableIndex < elementList.length) {
|
while (tableIndex < elementList.length) {
|
||||||
const nextElement = elementList[tableIndex]
|
const nextElement = elementList[tableIndex]
|
||||||
if (nextElement.pagingId === element.pagingId) {
|
if (nextElement.pagingId === element.pagingId) {
|
||||||
element.height! += nextElement.height!
|
const nexTrList = nextElement.trList!.filter(
|
||||||
element.trList!.push(...nextElement.trList!)
|
tr => !tr.pagingRepeat
|
||||||
|
)
|
||||||
|
// 判断后续表格第一行是拆分出来的还是从原表格挪到下一页的
|
||||||
|
const isNextTrSplit =
|
||||||
|
element.trList![element.trList!.length - 1].id ===
|
||||||
|
nexTrList[0].pagingOriginId
|
||||||
|
// 遍历后续表格首行中的单元格,在虚拟表格中找到其对应单元格
|
||||||
|
let tdIndex = 0
|
||||||
|
const mergedTds: ITd[] = []
|
||||||
|
nexTrList[0].tdList.forEach(td => {
|
||||||
|
let targetTd
|
||||||
|
// 如果虚拟表格最后一行对应位置有单元格,则其就为目标单元格,否则向上查找
|
||||||
|
if (virtualTable[virtualTable.length - 1][tdIndex]) {
|
||||||
|
targetTd = virtualTable[virtualTable.length - 1][tdIndex]
|
||||||
|
} else {
|
||||||
|
for (let i = virtualTable.length - 2; i >= 0; i--) {
|
||||||
|
if (virtualTable[i][tdIndex]) {
|
||||||
|
targetTd = virtualTable[i][tdIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targetTd) {
|
||||||
|
if (targetTd.id === td.pagingOriginId) {
|
||||||
|
targetTd.value.push(...td.value)
|
||||||
|
if (isNextTrSplit) {
|
||||||
|
targetTd.rowspan = targetTd.rowspan + td.rowspan - 1
|
||||||
|
} else {
|
||||||
|
targetTd.rowspan = targetTd.rowspan + td.rowspan
|
||||||
|
mergedTds.push(td)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tdIndex += targetTd.colspan
|
||||||
|
}
|
||||||
|
})
|
||||||
|
nexTrList[0].tdList = nexTrList[0].tdList.filter(td => {
|
||||||
|
const isNotMerged = mergedTds.every(
|
||||||
|
mergedTd => mergedTd.id !== td.id
|
||||||
|
)
|
||||||
|
delete td.pagingOriginId
|
||||||
|
return isNotMerged
|
||||||
|
})
|
||||||
|
// 更新行高,逐行合并
|
||||||
|
while (nexTrList.length > 0) {
|
||||||
|
const lastTr = element.trList![element.trList!.length - 1]
|
||||||
|
const nextTr = nexTrList.shift()!
|
||||||
|
if (lastTr.id === nextTr.pagingOriginId) {
|
||||||
|
lastTr.height += nextTr.pagingOriginHeight || 0
|
||||||
|
} else {
|
||||||
|
nextTr.height = nextTr.pagingOriginHeight || nextTr.height
|
||||||
|
element.trList!.push(nextTr)
|
||||||
|
}
|
||||||
|
delete nextTr.pagingOriginHeight
|
||||||
|
delete nextTr.pagingOriginId
|
||||||
|
}
|
||||||
tableIndex++
|
tableIndex++
|
||||||
combineCount++
|
combineCount++
|
||||||
} else {
|
} else {
|
||||||
@@ -821,7 +894,8 @@ export function getAnchorElement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IFormatElementContextOption {
|
export interface IFormatElementContextOption {
|
||||||
isBreakWhenWrap: boolean
|
isBreakWhenWrap?: boolean
|
||||||
|
editorOptions?: DeepRequired<IEditorOption>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatElementContext(
|
export function formatElementContext(
|
||||||
@@ -832,11 +906,12 @@ export function formatElementContext(
|
|||||||
) {
|
) {
|
||||||
let copyElement = getAnchorElement(sourceElementList, anchorIndex)
|
let copyElement = getAnchorElement(sourceElementList, anchorIndex)
|
||||||
if (!copyElement) return
|
if (!copyElement) return
|
||||||
// 标题元素禁用时不复制标题属性
|
const { isBreakWhenWrap = false, editorOptions } = options || {}
|
||||||
if (copyElement.title?.disabled) {
|
const { mode } = editorOptions || {}
|
||||||
|
// 非设计模式时:标题元素禁用时不复制标题属性
|
||||||
|
if (mode !== EditorMode.DESIGN && copyElement.title?.disabled) {
|
||||||
copyElement = omitObject(copyElement, TITLE_CONTEXT_ATTR)
|
copyElement = omitObject(copyElement, TITLE_CONTEXT_ATTR)
|
||||||
}
|
}
|
||||||
const { isBreakWhenWrap = false } = options || {}
|
|
||||||
// 是否已经换行
|
// 是否已经换行
|
||||||
let isBreakWarped = false
|
let isBreakWarped = false
|
||||||
for (let e = 0; e < formatElementList.length; e++) {
|
for (let e = 0; e < formatElementList.length; e++) {
|
||||||
@@ -865,7 +940,8 @@ export function formatElementContext(
|
|||||||
formatElementContext(
|
formatElementContext(
|
||||||
sourceElementList,
|
sourceElementList,
|
||||||
targetElement.valueList,
|
targetElement.valueList,
|
||||||
anchorIndex
|
anchorIndex,
|
||||||
|
options
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// 非块类元素,需处理行属性
|
// 非块类元素,需处理行属性
|
||||||
|
|||||||
@@ -1412,6 +1412,10 @@ window.onload = function () {
|
|||||||
{
|
{
|
||||||
mode: EditorMode.PRINT,
|
mode: EditorMode.PRINT,
|
||||||
name: '打印模式'
|
name: '打印模式'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: EditorMode.DESIGN,
|
||||||
|
name: '设计模式'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
const modeElement = document.querySelector<HTMLDivElement>('.editor-mode')!
|
const modeElement = document.querySelector<HTMLDivElement>('.editor-mode')!
|
||||||
|
|||||||
Reference in New Issue
Block a user