Skip to content

Commit

Permalink
[lexical-table] Freeze top row using pure CSS (#7190)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivailop7 authored Feb 17, 2025
1 parent 0e98db1 commit adaaf21
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,19 @@ function TableActionMenu({
});
}, [editor, tableCellNode, clearTableSelection, onClose]);

const toggleFirstRowFreeze = useCallback(() => {
editor.update(() => {
if (tableCellNode.isAttached()) {
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
if (tableNode) {
tableNode.setFrozenRows(tableNode.getFrozenRows() === 0 ? 1 : 0);
}
}
clearTableSelection();
onClose();
});
}, [editor, tableCellNode, clearTableSelection, onClose]);

const toggleFirstColumnFreeze = useCallback(() => {
editor.update(() => {
if (tableCellNode.isAttached()) {
Expand Down Expand Up @@ -670,6 +683,13 @@ function TableActionMenu({
</div>
</DropDownItem>
</DropDown>
<button
type="button"
className="item"
onClick={() => toggleFirstRowFreeze()}
data-test-id="table-freeze-first-row">
<span className="text">Toggle First Row Freeze</span>
</button>
<button
type="button"
className="item"
Expand Down
28 changes: 28 additions & 0 deletions packages/lexical-playground/src/themes/PlaygroundEditorTheme.css
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,34 @@
margin-top: 25px;
margin-bottom: 30px;
}
.PlaygroundEditorTheme__tableFrozenRow {
/* position:sticky needs overflow:clip or visible
https://github.com/w3c/csswg-drafts/issues/865#issuecomment-350585274 */
overflow-x: clip;
}
.PlaygroundEditorTheme__tableFrozenRow tr:nth-of-type(1) > td {
overflow: clip;
background-color: #ffffff;
position: sticky;
z-index: 2;
top: 44px;
}
.PlaygroundEditorTheme__tableFrozenRow tr:nth-of-type(1) > th {
overflow: clip;
background-color: #f2f3f5;
position: sticky;
z-index: 2;
top: 44px;
}
.PlaygroundEditorTheme__tableFrozenRow tr:nth-of-type(1) > th:after,
.PlaygroundEditorTheme__tableFrozenRow tr:nth-of-type(1) > td:after {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 100%;
border-bottom: 1px solid #bbb;
}
.PlaygroundEditorTheme__tableFrozenColumn tr > td:first-child {
background-color: #ffffff;
position: sticky;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const theme: EditorThemeClasses = {
tableCellResizer: 'PlaygroundEditorTheme__tableCellResizer',
tableCellSelected: 'PlaygroundEditorTheme__tableCellSelected',
tableFrozenColumn: 'PlaygroundEditorTheme__tableFrozenColumn',
tableFrozenRow: 'PlaygroundEditorTheme__tableFrozenRow',
tableRowStriping: 'PlaygroundEditorTheme__tableRowStriping',
tableScrollableWrapper: 'PlaygroundEditorTheme__tableScrollableWrapper',
tableSelected: 'PlaygroundEditorTheme__tableSelected',
Expand Down
36 changes: 36 additions & 0 deletions packages/lexical-table/src/LexicalTableNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type SerializedTableNode = Spread<
colWidths?: readonly number[];
rowStriping?: boolean;
frozenColumnCount?: number;
frozenRowCount?: number;
},
SerializedElementNode
>;
Expand Down Expand Up @@ -103,6 +104,20 @@ function setFrozenColumns(
}
}

function setFrozenRows(
dom: HTMLElement,
config: EditorConfig,
frozenRowCount: number,
): void {
if (frozenRowCount > 0) {
addClassNamesToElement(dom, config.theme.tableFrozenRow);
dom.setAttribute('data-lexical-frozen-row', 'true');
} else {
removeClassNamesFromElement(dom, config.theme.tableFrozenRow);
dom.removeAttribute('data-lexical-frozen-row');
}
}

function alignTableElement(
dom: HTMLElement,
config: EditorConfig,
Expand Down Expand Up @@ -153,6 +168,7 @@ export class TableNode extends ElementNode {
/** @internal */
__rowStriping: boolean;
__frozenColumnCount: number;
__frozenRowCount: number;
__colWidths?: readonly number[];

static getType(): string {
Expand Down Expand Up @@ -181,6 +197,7 @@ export class TableNode extends ElementNode {
this.__colWidths = prevNode.__colWidths;
this.__rowStriping = prevNode.__rowStriping;
this.__frozenColumnCount = prevNode.__frozenColumnCount;
this.__frozenRowCount = prevNode.__frozenRowCount;
}

static importDOM(): DOMConversionMap | null {
Expand All @@ -201,13 +218,15 @@ export class TableNode extends ElementNode {
.updateFromJSON(serializedNode)
.setRowStriping(serializedNode.rowStriping || false)
.setFrozenColumns(serializedNode.frozenColumnCount || 0)
.setFrozenRows(serializedNode.frozenRowCount || 0)
.setColWidths(serializedNode.colWidths);
}

constructor(key?: NodeKey) {
super(key);
this.__rowStriping = false;
this.__frozenColumnCount = 0;
this.__frozenRowCount = 0;
}

exportJSON(): SerializedTableNode {
Expand All @@ -217,6 +236,7 @@ export class TableNode extends ElementNode {
frozenColumnCount: this.__frozenColumnCount
? this.__frozenColumnCount
: undefined,
frozenRowCount: this.__frozenRowCount ? this.__frozenRowCount : undefined,
rowStriping: this.__rowStriping ? this.__rowStriping : undefined,
};
}
Expand Down Expand Up @@ -259,6 +279,9 @@ export class TableNode extends ElementNode {
if (this.__frozenColumnCount) {
setFrozenColumns(tableElement, config, this.__frozenColumnCount);
}
if (this.__frozenRowCount) {
setFrozenRows(tableElement, config, this.__frozenRowCount);
}
if (this.__rowStriping) {
setRowStriping(tableElement, config, true);
}
Expand All @@ -284,6 +307,9 @@ export class TableNode extends ElementNode {
if (prevNode.__frozenColumnCount !== this.__frozenColumnCount) {
setFrozenColumns(dom, config, this.__frozenColumnCount);
}
if (prevNode.__frozenRowCount !== this.__frozenRowCount) {
setFrozenRows(dom, config, this.__frozenRowCount);
}
updateColgroup(dom, config, this.getColumnCount(), this.getColWidths());
alignTableElement(
this.getDOMSlot(dom).element,
Expand Down Expand Up @@ -510,6 +536,16 @@ export class TableNode extends ElementNode {
return this.getLatest().__frozenColumnCount;
}

setFrozenRows(rowCount: number): this {
const self = this.getWritable();
self.__frozenRowCount = rowCount;
return self;
}

getFrozenRows(): number {
return this.getLatest().__frozenRowCount;
}

canSelectBefore(): true {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const editorConfig = Object.freeze({
right: 'test-table-alignment-right',
},
tableFrozenColumn: 'test-table-frozen-column-class',
tableFrozenRow: 'test-table-frozen-row-class',
tableRowStriping: 'test-table-row-striping-class',
tableScrollableWrapper: 'table-scrollable-wrapper',
},
Expand Down

0 comments on commit adaaf21

Please sign in to comment.