feat(richtext-lexical): upgrade lexical from 0.21.0 to 0.27.1 (#11564)
Fixes https://github.com/payloadcms/payload/issues/10628 This upgrades lexical from 0.21.0 to 0.27.1. This will allow us to use the new node state API to implement custom text formats (e.g. text colors), [thanks to Germán](https://x.com/GermanJablo/status/1897345631821222292). ## Notable changes ported over from lexical playground: ### Table column freezing https://github.com/user-attachments/assets/febdd7dd-6fa0-40d7-811c-9a38de04bfa7 ### Block cursors We now render a block cursor, which is a custom cursor that gets rendered when the browser doesn't render the native one. An example would be this this horizontal cursor above block nodes, if there is no space above:  Previously, those cursors were unstyled and not visible ### Table Alignment Tables can now be aligned 
This commit is contained in:
@@ -265,11 +265,6 @@
|
||||
"types": "./src/lexical-proxy/@lexical-react/LexicalTabIndentationPlugin.ts",
|
||||
"default": "./src/lexical-proxy/@lexical-react/LexicalTabIndentationPlugin.ts"
|
||||
},
|
||||
"./lexical/react/LexicalTableOfContents": {
|
||||
"import": "./src/lexical-proxy/@lexical-react/LexicalTableOfContents.ts",
|
||||
"types": "./src/lexical-proxy/@lexical-react/LexicalTableOfContents.ts",
|
||||
"default": "./src/lexical-proxy/@lexical-react/LexicalTableOfContents.ts"
|
||||
},
|
||||
"./lexical/react/LexicalTableOfContentsPlugin": {
|
||||
"import": "./src/lexical-proxy/@lexical-react/LexicalTableOfContentsPlugin.ts",
|
||||
"types": "./src/lexical-proxy/@lexical-react/LexicalTableOfContentsPlugin.ts",
|
||||
@@ -356,16 +351,16 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@lexical/headless": "0.21.0",
|
||||
"@lexical/html": "0.21.0",
|
||||
"@lexical/link": "0.21.0",
|
||||
"@lexical/list": "0.21.0",
|
||||
"@lexical/mark": "0.21.0",
|
||||
"@lexical/react": "0.21.0",
|
||||
"@lexical/rich-text": "0.21.0",
|
||||
"@lexical/selection": "0.21.0",
|
||||
"@lexical/table": "0.21.0",
|
||||
"@lexical/utils": "0.21.0",
|
||||
"@lexical/headless": "0.27.1",
|
||||
"@lexical/html": "0.27.1",
|
||||
"@lexical/link": "0.27.1",
|
||||
"@lexical/list": "0.27.1",
|
||||
"@lexical/mark": "0.27.1",
|
||||
"@lexical/react": "0.27.1",
|
||||
"@lexical/rich-text": "0.27.1",
|
||||
"@lexical/selection": "0.27.1",
|
||||
"@lexical/table": "0.27.1",
|
||||
"@lexical/utils": "0.27.1",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"@types/uuid": "10.0.0",
|
||||
@@ -374,7 +369,7 @@
|
||||
"dequal": "2.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
"jsox": "1.2.121",
|
||||
"lexical": "0.21.0",
|
||||
"lexical": "0.27.1",
|
||||
"mdast-util-from-markdown": "2.0.2",
|
||||
"mdast-util-mdx-jsx": "3.1.3",
|
||||
"micromark-extension-mdx-jsx": "3.0.1",
|
||||
@@ -389,7 +384,7 @@
|
||||
"@babel/preset-env": "7.26.7",
|
||||
"@babel/preset-react": "7.26.3",
|
||||
"@babel/preset-typescript": "7.26.0",
|
||||
"@lexical/eslint-plugin": "0.21.0",
|
||||
"@lexical/eslint-plugin": "0.27.1",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/escape-html": "1.0.4",
|
||||
"@types/json-schema": "7.0.15",
|
||||
@@ -662,11 +657,6 @@
|
||||
"types": "./dist/lexical-proxy/@lexical-react/LexicalTabIndentationPlugin.d.ts",
|
||||
"default": "./dist/lexical-proxy/@lexical-react/LexicalTabIndentationPlugin.js"
|
||||
},
|
||||
"./lexical/react/LexicalTableOfContents": {
|
||||
"import": "./dist/lexical-proxy/@lexical-react/LexicalTableOfContents.js",
|
||||
"types": "./dist/lexical-proxy/@lexical-react/LexicalTableOfContents.d.ts",
|
||||
"default": "./dist/lexical-proxy/@lexical-react/LexicalTableOfContents.js"
|
||||
},
|
||||
"./lexical/react/LexicalTableOfContentsPlugin": {
|
||||
"import": "./dist/lexical-proxy/@lexical-react/LexicalTableOfContentsPlugin.js",
|
||||
"types": "./dist/lexical-proxy/@lexical-react/LexicalTableOfContentsPlugin.d.ts",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { $createQuoteNode, $isQuoteNode, QuoteNode } from '@lexical/rich-text'
|
||||
import { $createLineBreakNode } from 'lexical'
|
||||
|
||||
import type { ElementTransformer } from '../../packages/@lexical/markdown/index.js'
|
||||
|
||||
@@ -23,10 +22,7 @@ export const MarkdownTransformer: ElementTransformer = {
|
||||
if (isImport) {
|
||||
const previousNode = parentNode.getPreviousSibling()
|
||||
if ($isQuoteNode(previousNode)) {
|
||||
previousNode.splice(previousNode.getChildrenSize(), 0, [
|
||||
$createLineBreakNode(),
|
||||
...children,
|
||||
])
|
||||
previousNode.splice(previousNode.getChildrenSize(), 0, [...children])
|
||||
previousNode.select(0, 0)
|
||||
parentNode.remove()
|
||||
return
|
||||
|
||||
@@ -95,8 +95,8 @@ export class ServerBlockNode extends DecoratorBlockNode {
|
||||
return false
|
||||
}
|
||||
|
||||
override decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element | null {
|
||||
return null
|
||||
override decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element {
|
||||
return null as unknown as JSX.Element
|
||||
}
|
||||
|
||||
override exportDOM(): DOMExportOutput {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@layer payload-default {
|
||||
.table-cell-action-button-container {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
left: 0;
|
||||
will-change: transform;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import type { TableObserver, TableRowNode, TableSelection } from '@lexical/table'
|
||||
import type { TableObserver, TableSelection } from '@lexical/table'
|
||||
import type { ElementNode } from 'lexical'
|
||||
import type { JSX } from 'react'
|
||||
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { useLexicalEditable } from '@lexical/react/useLexicalEditable'
|
||||
import {
|
||||
$computeTableMapSkipCellCheck,
|
||||
$deleteTableColumn__EXPERIMENTAL,
|
||||
$deleteTableRow__EXPERIMENTAL,
|
||||
$getNodeTriplet,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
$insertTableColumn__EXPERIMENTAL,
|
||||
$insertTableRow__EXPERIMENTAL,
|
||||
$isTableCellNode,
|
||||
$isTableRowNode,
|
||||
$isTableSelection,
|
||||
$unmergeCell,
|
||||
getTableElement,
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
$isTextNode,
|
||||
COMMAND_PRIORITY_CRITICAL,
|
||||
getDOMSelection,
|
||||
isDOMNode,
|
||||
SELECTION_CHANGE_COMMAND,
|
||||
} from 'lexical'
|
||||
import * as React from 'react'
|
||||
@@ -186,8 +187,9 @@ function TableActionMenu({
|
||||
if (
|
||||
dropDownRef.current != null &&
|
||||
contextRef.current != null &&
|
||||
!dropDownRef.current.contains(event.target as Node) &&
|
||||
!contextRef.current.contains(event.target as Node)
|
||||
isDOMNode(event.target) &&
|
||||
!dropDownRef.current.contains(event.target) &&
|
||||
!contextRef.current.contains(event.target)
|
||||
) {
|
||||
setIsMenuOpen(false)
|
||||
}
|
||||
@@ -226,35 +228,105 @@ function TableActionMenu({
|
||||
editor.update(() => {
|
||||
const selection = $getSelection()
|
||||
if ($isTableSelection(selection)) {
|
||||
const { columns, rows } = computeSelectionCount(selection)
|
||||
// Get all selected cells and compute the total area
|
||||
const nodes = selection.getNodes()
|
||||
let firstCell: null | TableCellNode = null
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
if ($isTableCellNode(node)) {
|
||||
if (firstCell === null) {
|
||||
node.setColSpan(columns).setRowSpan(rows)
|
||||
firstCell = node
|
||||
const isEmpty = $cellContainsEmptyParagraph(node)
|
||||
let firstChild
|
||||
if (isEmpty && $isParagraphNode((firstChild = node.getFirstChild()))) {
|
||||
firstChild.remove()
|
||||
}
|
||||
} else if ($isTableCellNode(firstCell)) {
|
||||
const isEmpty = $cellContainsEmptyParagraph(node)
|
||||
if (!isEmpty) {
|
||||
firstCell.append(...node.getChildren())
|
||||
}
|
||||
node.remove()
|
||||
const tableCells = nodes.filter($isTableCellNode)
|
||||
|
||||
if (tableCells.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Find the table node
|
||||
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCells[0] as TableCellNode)
|
||||
const [gridMap] = $computeTableMapSkipCellCheck(tableNode, null, null)
|
||||
|
||||
// Find the boundaries of the selection including merged cells
|
||||
let minRow = Infinity
|
||||
let maxRow = -Infinity
|
||||
let minCol = Infinity
|
||||
let maxCol = -Infinity
|
||||
|
||||
// First pass: find the actual boundaries considering merged cells
|
||||
const processedCells = new Set()
|
||||
for (const row of gridMap) {
|
||||
for (const mapCell of row) {
|
||||
if (!mapCell || !mapCell.cell) {
|
||||
continue
|
||||
}
|
||||
|
||||
const cellKey = mapCell.cell.getKey()
|
||||
if (processedCells.has(cellKey)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (tableCells.some((cell) => cell.is(mapCell.cell))) {
|
||||
processedCells.add(cellKey)
|
||||
// Get the actual position of this cell in the grid
|
||||
const cellStartRow = mapCell.startRow
|
||||
const cellStartCol = mapCell.startColumn
|
||||
const cellRowSpan = mapCell.cell.__rowSpan || 1
|
||||
const cellColSpan = mapCell.cell.__colSpan || 1
|
||||
|
||||
// Update boundaries considering the cell's actual position and span
|
||||
minRow = Math.min(minRow, cellStartRow)
|
||||
maxRow = Math.max(maxRow, cellStartRow + cellRowSpan - 1)
|
||||
minCol = Math.min(minCol, cellStartCol)
|
||||
maxCol = Math.max(maxCol, cellStartCol + cellColSpan - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (firstCell !== null) {
|
||||
if (firstCell.getChildrenSize() === 0) {
|
||||
firstCell.append($createParagraphNode())
|
||||
}
|
||||
$selectLastDescendant(firstCell)
|
||||
|
||||
// Validate boundaries
|
||||
if (minRow === Infinity || minCol === Infinity) {
|
||||
return
|
||||
}
|
||||
|
||||
// The total span of the merged cell
|
||||
const totalRowSpan = maxRow - minRow + 1
|
||||
const totalColSpan = maxCol - minCol + 1
|
||||
|
||||
// Use the top-left cell as the target cell
|
||||
const targetCellMap = gridMap?.[minRow]?.[minCol]
|
||||
if (!targetCellMap?.cell) {
|
||||
return
|
||||
}
|
||||
const targetCell = targetCellMap.cell
|
||||
|
||||
// Set the spans for the target cell
|
||||
targetCell.setColSpan(totalColSpan)
|
||||
targetCell.setRowSpan(totalRowSpan)
|
||||
|
||||
// Move content from other cells to the target cell
|
||||
const seenCells = new Set([targetCell.getKey()])
|
||||
|
||||
// Second pass: merge content and remove other cells
|
||||
for (let row = minRow; row <= maxRow; row++) {
|
||||
for (let col = minCol; col <= maxCol; col++) {
|
||||
const mapCell = gridMap?.[row]?.[col]
|
||||
if (!mapCell?.cell) {
|
||||
continue
|
||||
}
|
||||
|
||||
const currentCell = mapCell.cell
|
||||
const key = currentCell.getKey()
|
||||
|
||||
if (!seenCells.has(key)) {
|
||||
seenCells.add(key)
|
||||
const isEmpty = $cellContainsEmptyParagraph(currentCell)
|
||||
if (!isEmpty) {
|
||||
targetCell.append(...currentCell.getChildren())
|
||||
}
|
||||
currentCell.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure target cell has content
|
||||
if (targetCell.getChildrenSize() === 0) {
|
||||
targetCell.append($createParagraphNode())
|
||||
}
|
||||
|
||||
$selectLastDescendant(targetCell)
|
||||
onClose()
|
||||
}
|
||||
})
|
||||
@@ -269,11 +341,13 @@ function TableActionMenu({
|
||||
const insertTableRowAtSelection = useCallback(
|
||||
(shouldInsertAfter: boolean) => {
|
||||
editor.update(() => {
|
||||
$insertTableRow__EXPERIMENTAL(shouldInsertAfter)
|
||||
for (let i = 0; i < selectionCounts.rows; i++) {
|
||||
$insertTableRow__EXPERIMENTAL(shouldInsertAfter)
|
||||
}
|
||||
onClose()
|
||||
})
|
||||
},
|
||||
[editor, onClose],
|
||||
[editor, onClose, selectionCounts.rows],
|
||||
)
|
||||
|
||||
const insertTableColumnAtSelection = useCallback(
|
||||
@@ -318,26 +392,25 @@ function TableActionMenu({
|
||||
|
||||
const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode)
|
||||
|
||||
const tableRows = tableNode.getChildren()
|
||||
const [gridMap] = $computeTableMapSkipCellCheck(tableNode, null, null)
|
||||
|
||||
if (tableRowIndex >= tableRows.length || tableRowIndex < 0) {
|
||||
throw new Error('Expected table cell to be inside of table row.')
|
||||
}
|
||||
|
||||
const tableRow = tableRows[tableRowIndex]
|
||||
|
||||
if (!$isTableRowNode(tableRow)) {
|
||||
throw new Error('Expected table row')
|
||||
}
|
||||
const rowCells = new Set<TableCellNode>()
|
||||
|
||||
const newStyle = tableCellNode.getHeaderStyles() ^ TableCellHeaderStates.ROW
|
||||
tableRow.getChildren().forEach((tableCell) => {
|
||||
if (!$isTableCellNode(tableCell)) {
|
||||
throw new Error('Expected table cell')
|
||||
}
|
||||
if (gridMap[tableRowIndex]) {
|
||||
for (let col = 0; col < gridMap[tableRowIndex].length; col++) {
|
||||
const mapCell = gridMap[tableRowIndex][col]
|
||||
|
||||
tableCell.setHeaderStyles(newStyle, TableCellHeaderStates.ROW)
|
||||
})
|
||||
if (!mapCell?.cell) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!rowCells.has(mapCell.cell)) {
|
||||
rowCells.add(mapCell.cell)
|
||||
mapCell.cell.setHeaderStyles(newStyle, TableCellHeaderStates.ROW)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearTableSelection()
|
||||
onClose()
|
||||
@@ -350,35 +423,26 @@ function TableActionMenu({
|
||||
|
||||
const tableColumnIndex = $getTableColumnIndexFromTableCellNode(tableCellNode)
|
||||
|
||||
const tableRows = tableNode.getChildren<TableRowNode>()
|
||||
const maxRowsLength = Math.max(...tableRows.map((row) => row.getChildren().length))
|
||||
const [gridMap] = $computeTableMapSkipCellCheck(tableNode, null, null)
|
||||
|
||||
if (tableColumnIndex >= maxRowsLength || tableColumnIndex < 0) {
|
||||
throw new Error('Expected table cell to be inside of table row.')
|
||||
}
|
||||
const columnCells = new Set<TableCellNode>()
|
||||
|
||||
const newStyle = tableCellNode.getHeaderStyles() ^ TableCellHeaderStates.COLUMN
|
||||
for (let r = 0; r < tableRows.length; r++) {
|
||||
const tableRow = tableRows[r]
|
||||
if (gridMap) {
|
||||
for (let row = 0; row < gridMap.length; row++) {
|
||||
const mapCell = gridMap?.[row]?.[tableColumnIndex]
|
||||
|
||||
if (!$isTableRowNode(tableRow)) {
|
||||
throw new Error('Expected table row')
|
||||
if (!mapCell?.cell) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!columnCells.has(mapCell.cell)) {
|
||||
columnCells.add(mapCell.cell)
|
||||
mapCell.cell.setHeaderStyles(newStyle, TableCellHeaderStates.COLUMN)
|
||||
}
|
||||
}
|
||||
|
||||
const tableCells = tableRow.getChildren()
|
||||
if (tableColumnIndex >= tableCells.length) {
|
||||
// if cell is outside of bounds for the current row (for example various merge cell cases) we shouldn't highlight it
|
||||
continue
|
||||
}
|
||||
|
||||
const tableCell = tableCells[tableColumnIndex]
|
||||
|
||||
if (!$isTableCellNode(tableCell)) {
|
||||
throw new Error('Expected table cell')
|
||||
}
|
||||
|
||||
tableCell.setHeaderStyles(newStyle, TableCellHeaderStates.COLUMN)
|
||||
}
|
||||
|
||||
clearTableSelection()
|
||||
onClose()
|
||||
})
|
||||
@@ -398,6 +462,19 @@ function TableActionMenu({
|
||||
})
|
||||
}, [editor, tableCellNode, clearTableSelection, onClose])
|
||||
|
||||
const toggleFirstColumnFreeze = useCallback(() => {
|
||||
editor.update(() => {
|
||||
if (tableCellNode.isAttached()) {
|
||||
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode)
|
||||
if (tableNode) {
|
||||
tableNode.setFrozenColumns(tableNode.getFrozenColumns() === 0 ? 1 : 0)
|
||||
}
|
||||
}
|
||||
clearTableSelection()
|
||||
onClose()
|
||||
})
|
||||
}, [editor, tableCellNode, clearTableSelection, onClose])
|
||||
|
||||
let mergeCellButton: JSX.Element | null = null
|
||||
if (cellMerge) {
|
||||
if (canMergeCells) {
|
||||
@@ -449,6 +526,14 @@ function TableActionMenu({
|
||||
>
|
||||
<span className="text">Toggle Row Striping</span>
|
||||
</button>
|
||||
<button
|
||||
className="item"
|
||||
data-test-id="table-freeze-first-column"
|
||||
onClick={() => toggleFirstColumnFreeze()}
|
||||
type="button"
|
||||
>
|
||||
<span className="text">Toggle First Column Freeze</span>
|
||||
</button>
|
||||
<button
|
||||
className="item"
|
||||
data-test-id="table-insert-row-above"
|
||||
@@ -518,7 +603,12 @@ function TableActionMenu({
|
||||
<span className="text">Delete table</span>
|
||||
</button>
|
||||
<hr />
|
||||
<button className="item" onClick={() => toggleTableRowIsHeader()} type="button">
|
||||
<button
|
||||
className="item"
|
||||
data-test-id="table-row-header"
|
||||
onClick={() => toggleTableRowIsHeader()}
|
||||
type="button"
|
||||
>
|
||||
<span className="text">
|
||||
{(tableCellNode.__headerState & TableCellHeaderStates.ROW) === TableCellHeaderStates.ROW
|
||||
? 'Remove'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { TableCellNode, TableDOMCell, TableMapType } from '@lexical/table'
|
||||
import type { LexicalEditor } from 'lexical'
|
||||
import type { LexicalEditor, NodeKey } from 'lexical'
|
||||
import type { JSX, MouseEventHandler } from 'react'
|
||||
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
getTableElement,
|
||||
TableNode,
|
||||
} from '@lexical/table'
|
||||
import { calculateZoomLevel } from '@lexical/utils'
|
||||
import { $getNearestNodeFromDOMNode } from 'lexical'
|
||||
import { calculateZoomLevel, mergeRegister } from '@lexical/utils'
|
||||
import { $getNearestNodeFromDOMNode, isHTMLElement } from 'lexical'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
@@ -40,6 +40,7 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
|
||||
const targetRef = useRef<HTMLElement | null>(null)
|
||||
const resizerRef = useRef<HTMLDivElement | null>(null)
|
||||
const tableRectRef = useRef<ClientRect | null>(null)
|
||||
const [hasTable, setHasTable] = useState(false)
|
||||
const editorConfig = useEditorConfigContext()
|
||||
|
||||
const mouseStartPosRef = useRef<MousePosition | null>(null)
|
||||
@@ -62,22 +63,42 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return editor.registerNodeTransform(TableNode, (tableNode) => {
|
||||
if (tableNode.getColWidths()) {
|
||||
const tableKeys = new Set<NodeKey>()
|
||||
return mergeRegister(
|
||||
editor.registerMutationListener(TableNode, (nodeMutations) => {
|
||||
for (const [nodeKey, mutation] of nodeMutations) {
|
||||
if (mutation === 'destroyed') {
|
||||
tableKeys.delete(nodeKey)
|
||||
} else {
|
||||
tableKeys.add(nodeKey)
|
||||
}
|
||||
}
|
||||
setHasTable(tableKeys.size > 0)
|
||||
}),
|
||||
editor.registerNodeTransform(TableNode, (tableNode) => {
|
||||
if (tableNode.getColWidths()) {
|
||||
return tableNode
|
||||
}
|
||||
|
||||
const numColumns = tableNode.getColumnCount()
|
||||
const columnWidth = MIN_COLUMN_WIDTH
|
||||
|
||||
tableNode.setColWidths(Array(numColumns).fill(columnWidth))
|
||||
return tableNode
|
||||
}
|
||||
|
||||
const numColumns = tableNode.getColumnCount()
|
||||
const columnWidth = MIN_COLUMN_WIDTH
|
||||
|
||||
tableNode.setColWidths(Array(numColumns).fill(columnWidth))
|
||||
return tableNode
|
||||
})
|
||||
}),
|
||||
)
|
||||
}, [editor])
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasTable) {
|
||||
return
|
||||
}
|
||||
|
||||
const onMouseMove = (event: MouseEvent) => {
|
||||
const target = event.target
|
||||
if (!isHTMLElement(target)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (draggingDirection) {
|
||||
updateMouseCurrentPos({
|
||||
@@ -87,13 +108,13 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
|
||||
return
|
||||
}
|
||||
updateIsMouseDown(isMouseDownOnEvent(event))
|
||||
if (resizerRef.current && resizerRef.current.contains(target as Node)) {
|
||||
if (resizerRef.current && resizerRef.current.contains(target)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (targetRef.current !== target) {
|
||||
targetRef.current = target as HTMLElement
|
||||
const cell = getDOMCellFromTarget(target as HTMLElement)
|
||||
targetRef.current = target
|
||||
const cell = getDOMCellFromTarget(target)
|
||||
|
||||
if (cell && activeCell !== cell) {
|
||||
editor.getEditorState().read(
|
||||
@@ -113,7 +134,7 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
|
||||
throw new Error('TableCellResizer: Table element not found.')
|
||||
}
|
||||
|
||||
targetRef.current = target as HTMLElement
|
||||
targetRef.current = target
|
||||
tableRectRef.current = tableElement.getBoundingClientRect()
|
||||
updateActiveCell(cell)
|
||||
},
|
||||
@@ -145,7 +166,7 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
|
||||
return () => {
|
||||
removeRootListener()
|
||||
}
|
||||
}, [activeCell, draggingDirection, editor, resetState])
|
||||
}, [activeCell, draggingDirection, editor, hasTable, resetState])
|
||||
|
||||
const isHeightChanging = (direction: MouseDraggingDirection) => {
|
||||
if (direction === 'bottom') {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
TableNode,
|
||||
} from '@lexical/table'
|
||||
import { $findMatchingParent, mergeRegister } from '@lexical/utils'
|
||||
import { $getNearestNodeFromDOMNode } from 'lexical'
|
||||
import { $getNearestNodeFromDOMNode, isHTMLElement } from 'lexical'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import * as React from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
@@ -109,7 +109,15 @@ function TableHoverActionsContainer({
|
||||
right: tableElemRight,
|
||||
width: tableElemWidth,
|
||||
y: tableElemY,
|
||||
} = tableContainerElement.getBoundingClientRect()
|
||||
} = (tableDOMElement as HTMLTableElement).getBoundingClientRect()
|
||||
|
||||
let tableHasScroll = false
|
||||
if (
|
||||
tableContainerElement &&
|
||||
tableContainerElement.classList.contains('LexicalEditorTheme__tableScrollableWrapper')
|
||||
) {
|
||||
tableHasScroll = tableContainerElement.scrollWidth > tableContainerElement.clientWidth
|
||||
}
|
||||
|
||||
const { left: editorElemLeft, y: editorElemY } = anchorElem.getBoundingClientRect()
|
||||
|
||||
@@ -118,9 +126,15 @@ function TableHoverActionsContainer({
|
||||
setShownRow(true)
|
||||
setPosition({
|
||||
height: BUTTON_WIDTH_PX,
|
||||
left: tableElemLeft - editorElemLeft,
|
||||
left:
|
||||
tableHasScroll && tableContainerElement
|
||||
? tableContainerElement.offsetLeft
|
||||
: tableElemLeft - editorElemLeft,
|
||||
top: tableElemBottom - editorElemY + 5,
|
||||
width: tableElemWidth,
|
||||
width:
|
||||
tableHasScroll && tableContainerElement
|
||||
? tableContainerElement.offsetWidth
|
||||
: tableElemWidth,
|
||||
})
|
||||
} else if (hoveredColumnNode) {
|
||||
setShownColumn(true)
|
||||
@@ -256,7 +270,7 @@ function getMouseInfo(
|
||||
} {
|
||||
const target = event.target
|
||||
|
||||
if (target && target instanceof HTMLElement) {
|
||||
if (isHTMLElement(target)) {
|
||||
const tableDOMNode = target.closest<HTMLElement>(
|
||||
`td.${editorConfig.theme.tableCell}, th.${editorConfig.theme.tableCell}`,
|
||||
)
|
||||
|
||||
@@ -7,8 +7,17 @@
|
||||
margin: 0px 25px 30px 0px;
|
||||
}
|
||||
&__tableScrollableWrapper > .LexicalEditorTheme__table {
|
||||
/* Remove the table's margin and put it on the wrapper */
|
||||
margin: 0;
|
||||
/* Remove the table's vertical margin and put it on the wrapper */
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__tableAlignmentCenter {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
&__tableAlignmentRight {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&__tableSelection *::selection {
|
||||
@@ -23,7 +32,8 @@
|
||||
overflow-x: scroll;
|
||||
table-layout: fixed;
|
||||
width: fit-content;
|
||||
margin: 0 25px 30px 0;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
::selection {
|
||||
background: rgba(172, 206, 247);
|
||||
@@ -34,6 +44,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__tableFrozenColumn tr > td:first-child {
|
||||
background-color: var(--theme-bg);
|
||||
position: sticky;
|
||||
z-index: 2;
|
||||
left: 0;
|
||||
}
|
||||
&__tableFrozenColumn tr > th:first-child {
|
||||
background-color: var(--theme-elevation-50);
|
||||
position: sticky;
|
||||
z-index: 2;
|
||||
left: 0;
|
||||
}
|
||||
&__tableFrozenColumn tr > :first-child::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--theme-elevation-400);
|
||||
}
|
||||
|
||||
&__tableRowStriping tr:nth-child(even) {
|
||||
background-color: var(--theme-elevation-100);
|
||||
}
|
||||
@@ -52,6 +84,14 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* A firefox workaround to allow scrolling of overflowing table cell
|
||||
* ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1904159
|
||||
*/
|
||||
&__tableCell > * {
|
||||
overflow: inherit;
|
||||
}
|
||||
|
||||
&__tableCellResizer {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { JSX } from 'react'
|
||||
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { TablePlugin as LexicalReactTablePlugin } from '@lexical/react/LexicalTablePlugin'
|
||||
import { INSERT_TABLE_COMMAND, TableNode } from '@lexical/table'
|
||||
import { INSERT_TABLE_COMMAND, TableCellNode, TableNode, TableRowNode } from '@lexical/table'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { formatDrawerSlug, useEditDepth } from '@payloadcms/ui'
|
||||
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
@@ -97,8 +97,10 @@ export const TablePlugin: PluginComponent = () => {
|
||||
const { toggleDrawer } = useLexicalDrawer(drawerSlug, true)
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([TableNode])) {
|
||||
throw new Error('TablePlugin: TableNode is not registered on editor')
|
||||
if (!editor.hasNodes([TableNode, TableRowNode, TableCellNode])) {
|
||||
throw new Error(
|
||||
'TablePlugin: TableNode, TableRowNode, or TableCellNode is not registered on editor',
|
||||
)
|
||||
}
|
||||
|
||||
return mergeRegister(
|
||||
|
||||
@@ -48,7 +48,10 @@ export const TableMarkdownTransformer: (props: {
|
||||
for (const cell of row.getChildren()) {
|
||||
// It's TableCellNode, so it's just to make flow happy
|
||||
if ($isTableCellNode(cell)) {
|
||||
rowOutput.push($convertToMarkdownString(allTransformers, cell).replace(/\n/g, '\\n'))
|
||||
rowOutput.push(
|
||||
$convertToMarkdownString(allTransformers, cell).replace(/\n/g, '\\n').trim(),
|
||||
)
|
||||
|
||||
if (cell.__headerState === TableCellHeaderStates.ROW) {
|
||||
isHeaderRow = true
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
background-color: var(--theme-elevation-250);
|
||||
}
|
||||
|
||||
.LexicalEditorTheme__hr.selected {
|
||||
.LexicalEditorTheme__hr.LexicalEditorTheme__hrSelected {
|
||||
outline: 2px solid var(--theme-success-250);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from 'lexical'
|
||||
|
||||
import type { ToolbarGroup } from '../../toolbars/types.js'
|
||||
import type { PluginComponent } from '../../typesClient.js'
|
||||
|
||||
import { IndentDecreaseIcon } from '../../../lexical/ui/icons/IndentDecrease/index.js'
|
||||
import { IndentIncreaseIcon } from '../../../lexical/ui/icons/IndentIncrease/index.js'
|
||||
@@ -83,7 +84,7 @@ const toolbarGroups: ToolbarGroup[] = [
|
||||
export const IndentFeatureClient = createClientFeature({
|
||||
plugins: [
|
||||
{
|
||||
Component: TabIndentationPlugin,
|
||||
Component: TabIndentationPlugin as PluginComponent,
|
||||
position: 'normal',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ElementNode, LexicalNode, RangeSelection } from 'lexical'
|
||||
import type { ElementNode, LexicalNode, LexicalUpdateJSON, RangeSelection } from 'lexical'
|
||||
|
||||
import { $applyNodeReplacement, $isElementNode } from 'lexical'
|
||||
|
||||
@@ -24,6 +24,11 @@ export class AutoLinkNode extends LinkNode {
|
||||
}
|
||||
|
||||
static override importJSON(serializedNode: SerializedAutoLinkNode): AutoLinkNode {
|
||||
const node = $createAutoLinkNode({}).updateFromJSON(serializedNode)
|
||||
|
||||
/**
|
||||
* @todo remove in 4.0
|
||||
*/
|
||||
if (
|
||||
serializedNode.version === 1 &&
|
||||
typeof serializedNode.fields?.doc?.value === 'object' &&
|
||||
@@ -33,11 +38,6 @@ export class AutoLinkNode extends LinkNode {
|
||||
serializedNode.version = 2
|
||||
}
|
||||
|
||||
const node = $createAutoLinkNode({ fields: serializedNode.fields })
|
||||
|
||||
node.setFormat(serializedNode.format)
|
||||
node.setIndent(serializedNode.indent)
|
||||
node.setDirection(serializedNode.direction)
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -64,9 +64,13 @@ export class AutoLinkNode extends LinkNode {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedAutoLinkNode>): this {
|
||||
return super.updateFromJSON(serializedNode).setFields(serializedNode.fields)
|
||||
}
|
||||
}
|
||||
|
||||
export function $createAutoLinkNode({ fields }: { fields: LinkFields }): AutoLinkNode {
|
||||
export function $createAutoLinkNode({ fields }: { fields?: LinkFields }): AutoLinkNode {
|
||||
return $applyNodeReplacement(new AutoLinkNode({ id: '', fields }))
|
||||
}
|
||||
export function $isAutoLinkNode(node: LexicalNode | null | undefined): node is AutoLinkNode {
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
ElementNode as ElementNodeType,
|
||||
LexicalCommand,
|
||||
LexicalNode,
|
||||
LexicalUpdateJSON,
|
||||
NodeKey,
|
||||
RangeSelection,
|
||||
} from 'lexical'
|
||||
@@ -40,7 +41,7 @@ export class LinkNode extends ElementNode {
|
||||
},
|
||||
key,
|
||||
}: {
|
||||
fields: LinkFields
|
||||
fields?: LinkFields
|
||||
id: string
|
||||
key?: NodeKey
|
||||
}) {
|
||||
@@ -71,6 +72,11 @@ export class LinkNode extends ElementNode {
|
||||
}
|
||||
|
||||
static override importJSON(serializedNode: SerializedLinkNode): LinkNode {
|
||||
const node = $createLinkNode({}).updateFromJSON(serializedNode)
|
||||
|
||||
/**
|
||||
* @todo remove this in 4.0
|
||||
*/
|
||||
if (
|
||||
serializedNode.version === 1 &&
|
||||
typeof serializedNode.fields?.doc?.value === 'object' &&
|
||||
@@ -84,14 +90,6 @@ export class LinkNode extends ElementNode {
|
||||
serializedNode.id = new ObjectID.default().toHexString()
|
||||
serializedNode.version = 3
|
||||
}
|
||||
|
||||
const node = $createLinkNode({
|
||||
id: serializedNode.id,
|
||||
fields: serializedNode.fields,
|
||||
})
|
||||
node.setFormat(serializedNode.format)
|
||||
node.setIndent(serializedNode.indent)
|
||||
node.setDirection(serializedNode.direction)
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -203,12 +201,19 @@ export class LinkNode extends ElementNode {
|
||||
return url
|
||||
}
|
||||
|
||||
setFields(fields: LinkFields): void {
|
||||
setFields(fields: LinkFields): this {
|
||||
const writable = this.getWritable()
|
||||
writable.__fields = fields
|
||||
return writable
|
||||
}
|
||||
|
||||
override updateDOM(prevNode: LinkNode, anchor: HTMLAnchorElement, config: EditorConfig): boolean {
|
||||
setID(id: string): this {
|
||||
const writable = this.getWritable()
|
||||
writable.__id = id
|
||||
return writable
|
||||
}
|
||||
|
||||
override updateDOM(prevNode: this, anchor: HTMLAnchorElement, config: EditorConfig): boolean {
|
||||
const url = this.__fields?.url
|
||||
const newTab = this.__fields?.newTab
|
||||
if (url != null && url !== prevNode.__fields?.url && this.__fields?.linkType === 'custom') {
|
||||
@@ -238,6 +243,13 @@ export class LinkNode extends ElementNode {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedLinkNode>): this {
|
||||
return super
|
||||
.updateFromJSON(serializedNode)
|
||||
.setFields(serializedNode.fields)
|
||||
.setID(serializedNode.id as string)
|
||||
}
|
||||
}
|
||||
|
||||
function $convertAnchorElement(domNode: Node): DOMConversionOutput {
|
||||
@@ -259,7 +271,7 @@ function $convertAnchorElement(domNode: Node): DOMConversionOutput {
|
||||
return { node }
|
||||
}
|
||||
|
||||
export function $createLinkNode({ id, fields }: { fields: LinkFields; id?: string }): LinkNode {
|
||||
export function $createLinkNode({ id, fields }: { fields?: LinkFields; id?: string }): LinkNode {
|
||||
return $applyNodeReplacement(
|
||||
new LinkNode({
|
||||
id: id ?? new ObjectID.default().toHexString(),
|
||||
|
||||
@@ -21,6 +21,9 @@ export type LinkFields = {
|
||||
export type SerializedLinkNode<T extends SerializedLexicalNode = SerializedLexicalNode> = Spread<
|
||||
{
|
||||
fields: LinkFields
|
||||
/**
|
||||
* @todo make required in 4.0 and type AutoLinkNode differently
|
||||
*/
|
||||
id?: string // optional if AutoLinkNode
|
||||
type: 'link'
|
||||
},
|
||||
|
||||
@@ -80,7 +80,7 @@ export class UnknownConvertedNode extends DecoratorNode<JSX.Element> {
|
||||
return true
|
||||
}
|
||||
|
||||
override updateDOM(prevNode: UnknownConvertedNode, dom: HTMLElement): boolean {
|
||||
override updateDOM(prevNode: this, dom: HTMLElement): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export class UnknownConvertedNode extends DecoratorNode<JSX.Element> {
|
||||
return true
|
||||
}
|
||||
|
||||
override updateDOM(prevNode: UnknownConvertedNode, dom: HTMLElement): boolean {
|
||||
override updateDOM(prevNode: this, dom: HTMLElement): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ export class RelationshipServerNode extends DecoratorBlockNode {
|
||||
return false
|
||||
}
|
||||
|
||||
override decorate(_editor: LexicalEditor, _config: EditorConfig): JSX.Element | null {
|
||||
return null
|
||||
override decorate(_editor: LexicalEditor, _config: EditorConfig): JSX.Element {
|
||||
return null as unknown as JSX.Element
|
||||
}
|
||||
|
||||
override exportDOM(): DOMExportOutput {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import { Button } from '@payloadcms/ui'
|
||||
import { $addUpdateTag, type LexicalEditor } from 'lexical'
|
||||
import { $addUpdateTag, isDOMNode, type LexicalEditor } from 'lexical'
|
||||
import React, { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
@@ -229,13 +229,16 @@ export function DropDown({
|
||||
|
||||
if (button !== null && showDropDown) {
|
||||
const handle = (event: MouseEvent): void => {
|
||||
const { target } = event
|
||||
if (stopCloseOnClickSelf != null) {
|
||||
if (dropDownRef.current != null && dropDownRef.current.contains(target as Node)) {
|
||||
const target = event.target
|
||||
if (!isDOMNode(target)) {
|
||||
return
|
||||
}
|
||||
if (stopCloseOnClickSelf) {
|
||||
if (dropDownRef.current && dropDownRef.current.contains(target)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!button.contains(target as Node)) {
|
||||
if (!button.contains(target)) {
|
||||
setShowDropDown(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import { richTextValidateHOC } from './validate/index.js'
|
||||
|
||||
let checkedDependencies = false
|
||||
|
||||
export const lexicalTargetVersion = '0.21.0'
|
||||
export const lexicalTargetVersion = '0.27.1'
|
||||
|
||||
export function lexicalEditor(args?: LexicalEditorProps): LexicalRichTextAdapterProvider {
|
||||
if (
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@lexical/react/LexicalTableOfContents'
|
||||
@@ -451,6 +451,16 @@ export function LexicalMenu({
|
||||
)
|
||||
}
|
||||
|
||||
function setContainerDivAttributes(containerDiv: HTMLElement, className?: string) {
|
||||
if (className != null) {
|
||||
containerDiv.className = className
|
||||
}
|
||||
containerDiv.setAttribute('aria-label', 'Slash menu')
|
||||
containerDiv.setAttribute('role', 'listbox')
|
||||
containerDiv.style.display = 'block'
|
||||
containerDiv.style.position = 'absolute'
|
||||
}
|
||||
|
||||
export function useMenuAnchorRef(
|
||||
anchorElem: HTMLElement,
|
||||
resolution: MenuResolution | null,
|
||||
@@ -508,16 +518,10 @@ export function useMenuAnchorRef(
|
||||
}
|
||||
|
||||
if (!containerDiv.isConnected) {
|
||||
if (className != null) {
|
||||
containerDiv.className = className
|
||||
}
|
||||
containerDiv.setAttribute('aria-label', 'Slash menu')
|
||||
containerDiv.setAttribute('id', 'slash-menu')
|
||||
containerDiv.setAttribute('role', 'listbox')
|
||||
containerDiv.style.display = 'block'
|
||||
containerDiv.style.position = 'absolute'
|
||||
setContainerDivAttributes(containerDiv, className)
|
||||
anchorElem.append(containerDiv)
|
||||
}
|
||||
containerDiv.setAttribute('id', 'slash-menu')
|
||||
anchorElementRef.current = containerDiv
|
||||
rootElement.setAttribute('aria-controls', 'slash-menu')
|
||||
}
|
||||
@@ -535,6 +539,7 @@ export function useMenuAnchorRef(
|
||||
const containerDiv = anchorElementRef.current
|
||||
if (containerDiv !== null && containerDiv.isConnected) {
|
||||
containerDiv.remove()
|
||||
containerDiv.removeAttribute('id')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
import type { LexicalEditor, LexicalNode, ParagraphNode } from 'lexical'
|
||||
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
|
||||
import { $createParagraphNode } from 'lexical'
|
||||
import { $createParagraphNode, isHTMLElement } from 'lexical'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import { useEditorConfigContext } from '../../../config/client/EditorConfigProvider.js'
|
||||
import { isHTMLElement } from '../../../utils/guard.js'
|
||||
import { Point } from '../../../utils/point.js'
|
||||
import { ENABLE_SLASH_MENU_COMMAND } from '../../SlashMenu/LexicalTypeaheadMenuPlugin/index.js'
|
||||
import { calculateDistanceFromScrollerElem } from '../utils/calculateDistanceFromScrollerElem.js'
|
||||
|
||||
@@ -4,13 +4,12 @@ import type { DragEvent as ReactDragEvent } from 'react'
|
||||
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
|
||||
import { eventFiles } from '@lexical/rich-text'
|
||||
import { $getNearestNodeFromDOMNode, $getNodeByKey } from 'lexical'
|
||||
import { $getNearestNodeFromDOMNode, $getNodeByKey, isHTMLElement } from 'lexical'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import { useEditorConfigContext } from '../../../config/client/EditorConfigProvider.js'
|
||||
import { isHTMLElement } from '../../../utils/guard.js'
|
||||
import { Point } from '../../../utils/point.js'
|
||||
import { calculateDistanceFromScrollerElem } from '../utils/calculateDistanceFromScrollerElem.js'
|
||||
import { getNodeCloseToPoint } from '../utils/getNodeCloseToPoint.js'
|
||||
|
||||
@@ -106,6 +106,47 @@
|
||||
text-decoration: underline line-through;
|
||||
}
|
||||
|
||||
&__tabNode {
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&__tabNode.LexicalEditorTheme__textUnderline::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0.15em;
|
||||
border-bottom: 0.1em solid currentColor;
|
||||
}
|
||||
|
||||
&__tabNode.LexicalEditorTheme__textStrikethrough::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0.69em;
|
||||
border-top: 0.1em solid currentColor;
|
||||
}
|
||||
|
||||
&__tabNode.LexicalEditorTheme__textUnderlineStrikethrough::before,
|
||||
&__tabNode.LexicalEditorTheme__textUnderlineStrikethrough::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&__tabNode.LexicalEditorTheme__textUnderlineStrikethrough::before {
|
||||
top: 0.69em;
|
||||
border-top: 0.1em solid currentColor;
|
||||
}
|
||||
|
||||
&__tabNode.LexicalEditorTheme__textUnderlineStrikethrough::after {
|
||||
bottom: 0.05em;
|
||||
border-bottom: 0.1em solid currentColor;
|
||||
}
|
||||
|
||||
&__textSubscript {
|
||||
font-size: 0.8em;
|
||||
vertical-align: sub !important;
|
||||
@@ -143,6 +184,27 @@
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
// Renders cursor when native browser does not. See https://github.com/facebook/lexical/issues/3417
|
||||
&__blockCursor {
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
}
|
||||
&__blockCursor:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
width: 20px;
|
||||
border-top: 1px solid var(--theme-text);
|
||||
animation: CursorBlink 1.1s steps(2, start) infinite;
|
||||
}
|
||||
@keyframes CursorBlink {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&__code {
|
||||
/*background-color: rgb(240, 242, 245);*/
|
||||
font-family: Menlo, Consolas, Monaco, monospace;
|
||||
|
||||
@@ -51,6 +51,7 @@ export const LexicalEditorTheme: EditorThemeClasses = {
|
||||
h6: 'LexicalEditorTheme__h6',
|
||||
},
|
||||
hr: 'LexicalEditorTheme__hr',
|
||||
hrSelected: 'LexicalEditorTheme__hrSelected',
|
||||
indent: 'LexicalEditorTheme__indent',
|
||||
inlineImage: 'LexicalEditor__inline-image',
|
||||
link: 'LexicalEditorTheme__link',
|
||||
@@ -78,15 +79,21 @@ export const LexicalEditorTheme: EditorThemeClasses = {
|
||||
quote: 'LexicalEditorTheme__quote',
|
||||
relationship: 'LexicalEditorTheme__relationship',
|
||||
rtl: 'LexicalEditorTheme__rtl',
|
||||
tab: 'LexicalEditorTheme__tabNode',
|
||||
table: 'LexicalEditorTheme__table',
|
||||
tableAddColumns: 'LexicalEditorTheme__tableAddColumns',
|
||||
tableAddRows: 'LexicalEditorTheme__tableAddRows',
|
||||
tableAlignment: {
|
||||
center: 'LexicalEditorTheme__tableAlignmentCenter',
|
||||
right: 'LexicalEditorTheme__tableAlignmentRight',
|
||||
},
|
||||
tableCell: 'LexicalEditorTheme__tableCell',
|
||||
tableCellActionButton: 'LexicalEditorTheme__tableCellActionButton',
|
||||
tableCellActionButtonContainer: 'LexicalEditorTheme__tableCellActionButtonContainer',
|
||||
tableCellHeader: 'LexicalEditorTheme__tableCellHeader',
|
||||
tableCellResizer: 'LexicalEditorTheme__tableCellResizer',
|
||||
tableCellSelected: 'LexicalEditorTheme__tableCellSelected',
|
||||
tableFrozenColumn: 'LexicalEditorTheme__tableFrozenColumn',
|
||||
tableRowStriping: 'LexicalEditorTheme__tableRowStriping',
|
||||
tableScrollableWrapper: 'LexicalEditorTheme__tableScrollableWrapper',
|
||||
tableSelected: 'LexicalEditorTheme__tableSelected',
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* @deprecated - remove in 4.0. lexical already exports an isHTMLElement utility
|
||||
*/
|
||||
export function isHTMLElement(x: unknown): x is HTMLElement {
|
||||
return x instanceof HTMLElement
|
||||
}
|
||||
|
||||
1129
pnpm-lock.yaml
generated
1129
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user