Skip to content

Commit

Permalink
[lexical-table] Feature: TableCellNode add verticalAlign attribute (#…
Browse files Browse the repository at this point in the history
…7077)

Co-authored-by: Ivaylo Pavlov <[email protected]>
Co-authored-by: Bob Ippolito <[email protected]>
  • Loading branch information
3 people authored Feb 10, 2025
1 parent c82e7bb commit fcc3740
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,18 @@ test.describe('HTML Tables CopyAndPaste', () => {
<col style="width: 100px" />
</colgroup>
<tr style="height: 21px">
<td class="PlaygroundEditorTheme__tableCell">
<td
class="PlaygroundEditorTheme__tableCell"
style="vertical-align: bottom">
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">a</span>
</p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<td
class="PlaygroundEditorTheme__tableCell"
style="vertical-align: bottom">
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
Expand All @@ -300,7 +304,9 @@ test.describe('HTML Tables CopyAndPaste', () => {
<span data-lexical-text="true">b</span>
</p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<td
class="PlaygroundEditorTheme__tableCell"
style="vertical-align: bottom">
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
Expand All @@ -309,21 +315,27 @@ test.describe('HTML Tables CopyAndPaste', () => {
</td>
</tr>
<tr style="height: 21px">
<td class="PlaygroundEditorTheme__tableCell">
<td
class="PlaygroundEditorTheme__tableCell"
style="vertical-align: bottom">
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">d</span>
</p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<td
class="PlaygroundEditorTheme__tableCell"
style="vertical-align: bottom">
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">e</span>
</p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<td
class="PlaygroundEditorTheme__tableCell"
style="vertical-align: bottom">
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions packages/lexical-playground/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,21 @@ i.justify-align {
background-image: url(images/icons/justify.svg);
}

.icon.vertical-top,
i.left-align {
background-image: url(images/icons/vertical-top.svg);
}

.icon.vertical-middle,
i.center-align {
background-image: url(images/icons/vertical-middle.svg);
}

.icon.vertical-bottom,
i.right-align {
background-image: url(images/icons/vertical-bottom.svg);
}

i.indent {
background-image: url(images/icons/indent.svg);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import invariant from 'shared/invariant';

import useModal from '../../hooks/useModal';
import ColorPicker from '../../ui/ColorPicker';
import DropDown, {DropDownItem} from '../../ui/DropDown';

function computeSelectionCount(selection: TableSelection): {
columns: number;
Expand Down Expand Up @@ -473,6 +474,29 @@ function TableActionMenu({
[editor],
);

const formatVerticalAlign = (value: string) => {
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection) || $isTableSelection(selection)) {
const [cell] = $getNodeTriplet(selection.anchor);
if ($isTableCellNode(cell)) {
cell.setVerticalAlign(value);
}

if ($isTableSelection(selection)) {
const nodes = selection.getNodes();

for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if ($isTableCellNode(node)) {
node.setVerticalAlign(value);
}
}
}
}
});
};

let mergeCellButton: null | JSX.Element = null;
if (cellMerge) {
if (canMergeCells) {
Expand Down Expand Up @@ -528,6 +552,41 @@ function TableActionMenu({
data-test-id="table-row-striping">
<span className="text">Toggle Row Striping</span>
</button>
<DropDown
buttonLabel="Vertical Align"
buttonClassName="item"
buttonAriaLabel="Formatting options for vertical alignment">
<DropDownItem
onClick={() => {
formatVerticalAlign('top');
}}
className="item wide">
<div className="icon-text-container">
<i className="icon vertical-top" />
<span className="text">Top Align</span>
</div>
</DropDownItem>
<DropDownItem
onClick={() => {
formatVerticalAlign('middle');
}}
className="item wide">
<div className="icon-text-container">
<i className="icon vertical-middle" />
<span className="text">Middle Align</span>
</div>
</DropDownItem>
<DropDownItem
onClick={() => {
formatVerticalAlign('bottom');
}}
className="item wide">
<div className="icon-text-container">
<i className="icon vertical-bottom" />
<span className="text">Bottom Align</span>
</div>
</DropDownItem>
</DropDown>
<hr />
<button
type="button"
Expand Down
38 changes: 35 additions & 3 deletions packages/lexical-table/src/LexicalTableCellNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type SerializedTableCellNode = Spread<
headerState: TableCellHeaderState;
width?: number;
backgroundColor?: null | string;
verticalAlign?: string;
},
SerializedElementNode
>;
Expand All @@ -65,6 +66,8 @@ export class TableCellNode extends ElementNode {
__width?: number | undefined;
/** @internal */
__backgroundColor: null | string;
/** @internal */
__verticalAlign?: undefined | string;

static getType(): string {
return 'tablecell';
Expand All @@ -83,6 +86,7 @@ export class TableCellNode extends ElementNode {
super.afterCloneFrom(node);
this.__rowSpan = node.__rowSpan;
this.__backgroundColor = node.__backgroundColor;
this.__verticalAlign = node.__verticalAlign;
}

static importDOM(): DOMConversionMap | null {
Expand Down Expand Up @@ -111,7 +115,8 @@ export class TableCellNode extends ElementNode {
.setColSpan(serializedNode.colSpan || 1)
.setRowSpan(serializedNode.rowSpan || 1)
.setWidth(serializedNode.width || undefined)
.setBackgroundColor(serializedNode.backgroundColor || null);
.setBackgroundColor(serializedNode.backgroundColor || null)
.setVerticalAlign(serializedNode.verticalAlign || undefined);
}

constructor(
Expand Down Expand Up @@ -143,6 +148,9 @@ export class TableCellNode extends ElementNode {
if (this.__backgroundColor !== null) {
element.style.backgroundColor = this.__backgroundColor;
}
if (isValidVerticalAlign(this.__verticalAlign)) {
element.style.verticalAlign = this.__verticalAlign;
}

addClassNamesToElement(
element,
Expand Down Expand Up @@ -171,7 +179,7 @@ export class TableCellNode extends ElementNode {
}
element.style.width = `${this.getWidth() || COLUMN_WIDTH}px`;

element.style.verticalAlign = 'top';
element.style.verticalAlign = this.getVerticalAlign() || 'top';
element.style.textAlign = 'start';
if (this.__backgroundColor === null && this.hasHeader()) {
element.style.backgroundColor = '#f2f3f5';
Expand All @@ -184,6 +192,9 @@ export class TableCellNode extends ElementNode {
exportJSON(): SerializedTableCellNode {
return {
...super.exportJSON(),
...(isValidVerticalAlign(this.__verticalAlign) && {
verticalAlign: this.__verticalAlign,
}),
backgroundColor: this.getBackgroundColor(),
colSpan: this.__colSpan,
headerState: this.__headerState,
Expand Down Expand Up @@ -249,6 +260,16 @@ export class TableCellNode extends ElementNode {
return self;
}

getVerticalAlign(): undefined | string {
return this.getLatest().__verticalAlign;
}

setVerticalAlign(newVerticalAlign: null | undefined | string): this {
const self = this.getWritable();
self.__verticalAlign = newVerticalAlign || undefined;
return self;
}

toggleHeaderStyle(headerStateToToggle: TableCellHeaderState): this {
const self = this.getWritable();

Expand All @@ -275,7 +296,8 @@ export class TableCellNode extends ElementNode {
prevNode.__width !== this.__width ||
prevNode.__colSpan !== this.__colSpan ||
prevNode.__rowSpan !== this.__rowSpan ||
prevNode.__backgroundColor !== this.__backgroundColor
prevNode.__backgroundColor !== this.__backgroundColor ||
prevNode.__verticalAlign !== this.__verticalAlign
);
}

Expand All @@ -296,6 +318,12 @@ export class TableCellNode extends ElementNode {
}
}

function isValidVerticalAlign(
verticalAlign?: null | string,
): verticalAlign is 'middle' | 'bottom' {
return verticalAlign === 'middle' || verticalAlign === 'bottom';
}

export function $convertTableCellNodeElement(
domNode: Node,
): DOMConversionOutput {
Expand All @@ -321,6 +349,10 @@ export function $convertTableCellNodeElement(
if (backgroundColor !== '') {
tableCellNode.__backgroundColor = backgroundColor;
}
const verticalAlign = domNode_.style.verticalAlign;
if (isValidVerticalAlign(verticalAlign)) {
tableCellNode.__verticalAlign = verticalAlign;
}

const style = domNode_.style;
const textDecoration = ((style && style.textDecoration) || '').split(' ');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ describe('LexicalTableCellNode tests', () => {
expect(cellWithCustomWidthNode.createDOM(editorConfig).outerHTML).toBe(
`<td style="width: ${cellWidth}px;" class="${editorConfig.theme.tableCell}"></td>`,
);
const ignoredVerticalAlign = 'top';
const cellWithIgnoredVerticalAlignNode = $createTableCellNode(
TableCellHeaderStates.NO_STATUS,
);
cellWithIgnoredVerticalAlignNode.setVerticalAlign(ignoredVerticalAlign);
expect(
cellWithIgnoredVerticalAlignNode.createDOM(editorConfig).outerHTML,
).toBe(`<td class="${editorConfig.theme.tableCell}"></td>`);
const validVerticalAlign = 'middle';
const cellWithValidVerticalAlignNode = $createTableCellNode(
TableCellHeaderStates.NO_STATUS,
);
cellWithValidVerticalAlignNode.setVerticalAlign(validVerticalAlign);
expect(
cellWithValidVerticalAlignNode.createDOM(editorConfig).outerHTML,
).toBe(
`<td style="vertical-align: ${validVerticalAlign};" class="${editorConfig.theme.tableCell}"></td>`,
);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -659,34 +659,34 @@ describe('LexicalTableNode tests', () => {
<col style="width: 171px" />
</colgroup>
<tr style="height: 21px;">
<td>
<td style="vertical-align: bottom">
<p dir="ltr">
<strong data-lexical-text="true">Surface</strong>
</p>
</td>
<td>
<td style="vertical-align: bottom">
<p dir="ltr">
<em data-lexical-text="true">MWP_WORK_LS_COMPOSER</em>
</p>
</td>
<td>
<td style="vertical-align: bottom">
<p style="text-align: right;">
<span data-lexical-text="true">77349</span>
</p>
</td>
</tr>
<tr style="height: 21px;">
<td>
<td style="vertical-align: bottom">
<p dir="ltr">
<span data-lexical-text="true">Lexical</span>
</p>
</td>
<td>
<td style="vertical-align: bottom">
<p dir="ltr">
<span data-lexical-text="true">XDS_RICH_TEXT_AREA</span>
</p>
</td>
<td>
<td style="vertical-align: bottom">
<p dir="ltr">
<span data-lexical-text="true">sdvd</span>
<strong data-lexical-text="true">sdfvsfs</strong>
Expand Down

0 comments on commit fcc3740

Please sign in to comment.