feat: add checkbox list #385
This commit is contained in:
@@ -185,6 +185,12 @@
|
||||
<li>________</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li data-list-type="ul" data-list-style='checkbox'>
|
||||
<label>复选框列表:</label>
|
||||
<ul style="list-style-type: '☑️ ';">
|
||||
<li>________</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-type="ul" data-list-style='disc'>
|
||||
<label>实心圆点列表:</label>
|
||||
<ul style="list-style-type: disc;">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ListStyle, ListType, UlStyle } from '../../../dataset/enum/List'
|
||||
import { DeepRequired } from '../../../interface/Common'
|
||||
import { IEditorOption } from '../../../interface/Editor'
|
||||
import { IElement, IElementPosition } from '../../../interface/Element'
|
||||
import { IRow } from '../../../interface/Row'
|
||||
import { IRow, IRowElement } from '../../../interface/Row'
|
||||
import { getUUID } from '../../../utils'
|
||||
import { RangeManager } from '../../range/RangeManager'
|
||||
import { Draw } from '../Draw'
|
||||
@@ -174,15 +174,7 @@ export class ListParticle {
|
||||
if (element?.type !== ElementType.TAB) break
|
||||
tabWidth += defaultTabWidth * scale
|
||||
}
|
||||
let text = ''
|
||||
if (startElement.listType === ListType.UL) {
|
||||
text =
|
||||
ulStyleMapping[<UlStyle>(<unknown>startElement.listStyle)] ||
|
||||
ulStyleMapping[UlStyle.DISC]
|
||||
} else {
|
||||
text = `${listIndex! + 1}${KeyMap.PERIOD}`
|
||||
}
|
||||
if (!text) return
|
||||
// 列表样式渲染
|
||||
const {
|
||||
coordinate: {
|
||||
leftTop: [startX, startY]
|
||||
@@ -190,9 +182,37 @@ export class ListParticle {
|
||||
} = position
|
||||
const x = startX - offsetX! + tabWidth
|
||||
const y = startY + ascent
|
||||
ctx.save()
|
||||
ctx.font = `${defaultSize * scale}px ${defaultFont}`
|
||||
ctx.fillText(text, x, y)
|
||||
ctx.restore()
|
||||
// 复选框样式特殊处理
|
||||
if (startElement.listStyle === ListStyle.CHECKBOX) {
|
||||
const { width, height, gap } = this.options.checkbox
|
||||
const checkboxRowElement: IRowElement = {
|
||||
...startElement,
|
||||
checkbox: {
|
||||
value: !!startElement.checkbox?.value
|
||||
},
|
||||
metrics: {
|
||||
...startElement.metrics,
|
||||
width: (width + gap * 2) * scale,
|
||||
height: height * scale
|
||||
}
|
||||
}
|
||||
this.draw
|
||||
.getCheckboxParticle()
|
||||
.render(ctx, checkboxRowElement, x - gap * scale, y)
|
||||
} else {
|
||||
let text = ''
|
||||
if (startElement.listType === ListType.UL) {
|
||||
text =
|
||||
ulStyleMapping[<UlStyle>(<unknown>startElement.listStyle)] ||
|
||||
ulStyleMapping[UlStyle.DISC]
|
||||
} else {
|
||||
text = `${listIndex! + 1}${KeyMap.PERIOD}`
|
||||
}
|
||||
if (!text) return
|
||||
ctx.save()
|
||||
ctx.font = `${defaultSize * scale}px ${defaultFont}`
|
||||
ctx.fillText(text, x, y)
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ElementType, RowFlex, VerticalAlign } from '../..'
|
||||
import { ElementType, ListStyle, RowFlex, VerticalAlign } from '../..'
|
||||
import { ZERO } from '../../dataset/constant/Common'
|
||||
import { ControlComponent } from '../../dataset/enum/Control'
|
||||
import {
|
||||
@@ -358,6 +358,7 @@ export class Position {
|
||||
coordinate: { leftTop, rightTop, leftBottom }
|
||||
} = positionList[j]
|
||||
if (positionNo !== pageNo) continue
|
||||
if (pageNo > positionNo) break
|
||||
// 命中元素
|
||||
if (
|
||||
leftTop[0] - left <= x &&
|
||||
@@ -482,17 +483,15 @@ export class Position {
|
||||
for (let j = 0; j < lastLetterList.length; j++) {
|
||||
const {
|
||||
index,
|
||||
pageNo,
|
||||
rowNo,
|
||||
coordinate: { leftTop, leftBottom }
|
||||
} = lastLetterList[j]
|
||||
if (positionNo !== pageNo) continue
|
||||
if (y > leftTop[1] && y <= leftBottom[1]) {
|
||||
const isHead = x < this.options.margins[3]
|
||||
const headIndex = positionList.findIndex(
|
||||
p => p.pageNo === positionNo && p.rowNo === rowNo
|
||||
)
|
||||
// 是否在头部
|
||||
if (isHead) {
|
||||
const headIndex = positionList.findIndex(
|
||||
p => p.pageNo === positionNo && p.rowNo === lastLetterList[j].rowNo
|
||||
)
|
||||
if (x < this.options.margins[3]) {
|
||||
// 头部元素为空元素时无需选中
|
||||
if (~headIndex) {
|
||||
if (positionList[headIndex].value === ZERO) {
|
||||
@@ -505,6 +504,17 @@ export class Position {
|
||||
curPositionIndex = index
|
||||
}
|
||||
} else {
|
||||
// 是否是复选框列表
|
||||
if (
|
||||
elementList[headIndex].listStyle === ListStyle.CHECKBOX &&
|
||||
x < leftTop[0]
|
||||
) {
|
||||
return {
|
||||
index: headIndex,
|
||||
isDirectHit: true,
|
||||
isCheckbox: true
|
||||
}
|
||||
}
|
||||
curPositionIndex = index
|
||||
}
|
||||
isLastArea = true
|
||||
|
||||
@@ -3,7 +3,8 @@ import { ListStyle, ListType, UlStyle } from '../enum/List'
|
||||
export const ulStyleMapping: Record<UlStyle, string> = {
|
||||
[UlStyle.DISC]: '•',
|
||||
[UlStyle.CIRCLE]: '◦',
|
||||
[UlStyle.SQUARE]: '▫︎'
|
||||
[UlStyle.SQUARE]: '▫︎',
|
||||
[UlStyle.CHECKBOX]: '☑️'
|
||||
}
|
||||
|
||||
export const listTypeElementMapping: Record<ListType, string> = {
|
||||
@@ -15,5 +16,6 @@ export const listStyleCSSMapping: Record<ListStyle, string> = {
|
||||
[ListStyle.DISC]: 'disc',
|
||||
[ListStyle.CIRCLE]: 'circle',
|
||||
[ListStyle.SQUARE]: 'square',
|
||||
[ListStyle.DECIMAL]: 'decimal'
|
||||
[ListStyle.DECIMAL]: 'decimal',
|
||||
[ListStyle.CHECKBOX]: 'checkbox'
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ZERO } from './Common'
|
||||
|
||||
export const NUMBER_REG = /[0-9]/
|
||||
export const NUMBER_LIKE_REG = /[0-9.]/
|
||||
export const CHINESE_REG = /[\u4e00-\u9fa5]/
|
||||
@@ -13,3 +15,5 @@ export const UNICODE_SYMBOL_REG = new RegExp(
|
||||
|
||||
export const PUNCTUATION_REG =
|
||||
/[、,。?!;:……「」“”‘’*()【】〔〕〖〗〘〙〚〛《》———﹝﹞–—\\/·.,!?;:`~<>()[\]{}'"|]/
|
||||
|
||||
export const START_LINE_BREAK_REG = new RegExp(`^[${ZERO}\n]`)
|
||||
|
||||
@@ -6,7 +6,8 @@ export enum ListType {
|
||||
export enum UlStyle {
|
||||
DISC = 'disc', // 实心圆点
|
||||
CIRCLE = 'circle', // 空心圆点
|
||||
SQUARE = 'square' // 实心方块
|
||||
SQUARE = 'square', // 实心方块
|
||||
CHECKBOX = 'checkbox' // 复选框
|
||||
}
|
||||
|
||||
export enum OlStyle {
|
||||
@@ -17,5 +18,6 @@ export enum ListStyle {
|
||||
DISC = UlStyle.DISC,
|
||||
CIRCLE = UlStyle.CIRCLE,
|
||||
SQUARE = UlStyle.SQUARE,
|
||||
DECIMAL = OlStyle.DECIMAL
|
||||
DECIMAL = OlStyle.DECIMAL,
|
||||
CHECKBOX = UlStyle.CHECKBOX
|
||||
}
|
||||
|
||||
+110
-93
@@ -30,6 +30,7 @@ import {
|
||||
listStyleCSSMapping,
|
||||
listTypeElementMapping
|
||||
} from '../dataset/constant/List'
|
||||
import { START_LINE_BREAK_REG } from '../dataset/constant/Regular'
|
||||
import {
|
||||
titleNodeNameMapping,
|
||||
titleOrderNumberMapping,
|
||||
@@ -71,7 +72,7 @@ export function formatElementList(
|
||||
if (
|
||||
isHandleFirstElement &&
|
||||
((startElement?.type && startElement.type !== ElementType.TEXT) ||
|
||||
(startElement?.value !== ZERO && startElement?.value !== '\n'))
|
||||
!START_LINE_BREAK_REG.test(startElement?.value))
|
||||
) {
|
||||
elementList.unshift({
|
||||
value: ZERO
|
||||
@@ -431,10 +432,11 @@ export function zipElementList(payload: IElement[]): IElement[] {
|
||||
let e = 0
|
||||
while (e < elementList.length) {
|
||||
let element = elementList[e]
|
||||
// 上下文首字符(占位符)
|
||||
// 上下文首字符(占位符)-列表首字符要保留避免是复选框
|
||||
if (
|
||||
e === 0 &&
|
||||
element.value === ZERO &&
|
||||
!element.listId &&
|
||||
(!element.type || element.type === ElementType.TEXT)
|
||||
) {
|
||||
e++
|
||||
@@ -444,51 +446,55 @@ export function zipElementList(payload: IElement[]): IElement[] {
|
||||
if (element.titleId && element.level) {
|
||||
// 标题处理
|
||||
const titleId = element.titleId
|
||||
const level = element.level
|
||||
const titleElement: IElement = {
|
||||
type: ElementType.TITLE,
|
||||
value: '',
|
||||
level
|
||||
}
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const titleE = elementList[e]
|
||||
if (titleId !== titleE.titleId) {
|
||||
e--
|
||||
break
|
||||
if (titleId) {
|
||||
const level = element.level
|
||||
const titleElement: IElement = {
|
||||
type: ElementType.TITLE,
|
||||
value: '',
|
||||
level
|
||||
}
|
||||
delete titleE.level
|
||||
valueList.push(titleE)
|
||||
e++
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const titleE = elementList[e]
|
||||
if (titleId !== titleE.titleId) {
|
||||
e--
|
||||
break
|
||||
}
|
||||
delete titleE.level
|
||||
valueList.push(titleE)
|
||||
e++
|
||||
}
|
||||
titleElement.valueList = zipElementList(valueList)
|
||||
element = titleElement
|
||||
}
|
||||
titleElement.valueList = zipElementList(valueList)
|
||||
element = titleElement
|
||||
} else if (element.listId && element.listType) {
|
||||
// 列表处理
|
||||
const listId = element.listId
|
||||
const listType = element.listType
|
||||
const listStyle = element.listStyle
|
||||
const listElement: IElement = {
|
||||
type: ElementType.LIST,
|
||||
value: '',
|
||||
listId,
|
||||
listType,
|
||||
listStyle
|
||||
}
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const listE = elementList[e]
|
||||
if (listId !== listE.listId) {
|
||||
e--
|
||||
break
|
||||
if (listId) {
|
||||
const listType = element.listType
|
||||
const listStyle = element.listStyle
|
||||
const listElement: IElement = {
|
||||
type: ElementType.LIST,
|
||||
value: '',
|
||||
listId,
|
||||
listType,
|
||||
listStyle
|
||||
}
|
||||
delete listE.listType
|
||||
delete listE.listStyle
|
||||
valueList.push(listE)
|
||||
e++
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const listE = elementList[e]
|
||||
if (listId !== listE.listId) {
|
||||
e--
|
||||
break
|
||||
}
|
||||
delete listE.listType
|
||||
delete listE.listStyle
|
||||
valueList.push(listE)
|
||||
e++
|
||||
}
|
||||
listElement.valueList = zipElementList(valueList)
|
||||
element = listElement
|
||||
}
|
||||
listElement.valueList = zipElementList(valueList)
|
||||
element = listElement
|
||||
} else if (element.type === ElementType.TABLE) {
|
||||
// 分页表格先进行合并
|
||||
if (element.pagingId) {
|
||||
@@ -532,71 +538,77 @@ export function zipElementList(payload: IElement[]): IElement[] {
|
||||
} else if (element.type === ElementType.HYPERLINK) {
|
||||
// 超链接处理
|
||||
const hyperlinkId = element.hyperlinkId
|
||||
const hyperlinkElement: IElement = {
|
||||
type: ElementType.HYPERLINK,
|
||||
value: '',
|
||||
url: element.url
|
||||
}
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const hyperlinkE = elementList[e]
|
||||
if (hyperlinkId !== hyperlinkE.hyperlinkId) {
|
||||
e--
|
||||
break
|
||||
if (hyperlinkId) {
|
||||
const hyperlinkElement: IElement = {
|
||||
type: ElementType.HYPERLINK,
|
||||
value: '',
|
||||
url: element.url
|
||||
}
|
||||
delete hyperlinkE.type
|
||||
delete hyperlinkE.url
|
||||
valueList.push(hyperlinkE)
|
||||
e++
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const hyperlinkE = elementList[e]
|
||||
if (hyperlinkId !== hyperlinkE.hyperlinkId) {
|
||||
e--
|
||||
break
|
||||
}
|
||||
delete hyperlinkE.type
|
||||
delete hyperlinkE.url
|
||||
valueList.push(hyperlinkE)
|
||||
e++
|
||||
}
|
||||
hyperlinkElement.valueList = zipElementList(valueList)
|
||||
element = hyperlinkElement
|
||||
}
|
||||
hyperlinkElement.valueList = zipElementList(valueList)
|
||||
element = hyperlinkElement
|
||||
} else if (element.type === ElementType.DATE) {
|
||||
const dateId = element.dateId
|
||||
const dateElement: IElement = {
|
||||
type: ElementType.DATE,
|
||||
value: '',
|
||||
dateFormat: element.dateFormat
|
||||
}
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const dateE = elementList[e]
|
||||
if (dateId !== dateE.dateId) {
|
||||
e--
|
||||
break
|
||||
if (dateId) {
|
||||
const dateElement: IElement = {
|
||||
type: ElementType.DATE,
|
||||
value: '',
|
||||
dateFormat: element.dateFormat
|
||||
}
|
||||
delete dateE.type
|
||||
delete dateE.dateFormat
|
||||
valueList.push(dateE)
|
||||
e++
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const dateE = elementList[e]
|
||||
if (dateId !== dateE.dateId) {
|
||||
e--
|
||||
break
|
||||
}
|
||||
delete dateE.type
|
||||
delete dateE.dateFormat
|
||||
valueList.push(dateE)
|
||||
e++
|
||||
}
|
||||
dateElement.valueList = zipElementList(valueList)
|
||||
element = dateElement
|
||||
}
|
||||
dateElement.valueList = zipElementList(valueList)
|
||||
element = dateElement
|
||||
} else if (element.controlId) {
|
||||
// 控件处理
|
||||
const controlId = element.controlId
|
||||
const control = element.control!
|
||||
const controlElement: IElement = {
|
||||
type: ElementType.CONTROL,
|
||||
value: '',
|
||||
control
|
||||
}
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const controlE = elementList[e]
|
||||
if (controlId !== controlE.controlId) {
|
||||
e--
|
||||
break
|
||||
if (controlId) {
|
||||
const control = element.control!
|
||||
const controlElement: IElement = {
|
||||
type: ElementType.CONTROL,
|
||||
value: '',
|
||||
control
|
||||
}
|
||||
if (controlE.controlComponent === ControlComponent.VALUE) {
|
||||
delete controlE.control
|
||||
delete controlE.controlId
|
||||
valueList.push(controlE)
|
||||
const valueList: IElement[] = []
|
||||
while (e < elementList.length) {
|
||||
const controlE = elementList[e]
|
||||
if (controlId !== controlE.controlId) {
|
||||
e--
|
||||
break
|
||||
}
|
||||
if (controlE.controlComponent === ControlComponent.VALUE) {
|
||||
delete controlE.control
|
||||
delete controlE.controlId
|
||||
valueList.push(controlE)
|
||||
}
|
||||
e++
|
||||
}
|
||||
e++
|
||||
controlElement.control!.value = zipElementList(valueList)
|
||||
element = controlElement
|
||||
}
|
||||
controlElement.control!.value = zipElementList(valueList)
|
||||
element = controlElement
|
||||
}
|
||||
// 组合元素
|
||||
const pickElement = pickElementAttr(element)
|
||||
@@ -768,6 +780,11 @@ export function splitListElement(
|
||||
const listElementListMap: Map<number, IElement[]> = new Map()
|
||||
for (let e = 0; e < elementList.length; e++) {
|
||||
const element = elementList[e]
|
||||
// 移除列表首行换行字符-如果是复选框直接忽略
|
||||
if (e === 0) {
|
||||
if (element.checkbox) continue
|
||||
element.value = element.value.replace(START_LINE_BREAK_REG, '')
|
||||
}
|
||||
if (element.listWrap) {
|
||||
const listElementList = listElementListMap.get(curListIndex) || []
|
||||
listElementList.push(element)
|
||||
|
||||
@@ -367,6 +367,10 @@ ul {
|
||||
margin-left: 18px;
|
||||
}
|
||||
|
||||
.menu-item__list .options>ul>li[data-list-style='checkbox'] li::marker {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.menu-item__image i {
|
||||
background-image: url('./assets/images/image.svg');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user