@livepreso/react-plugin-textfield 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.rush/temp/shrinkwrap-deps.json +2 -2
- package/CHANGELOG.json +20 -0
- package/CHANGELOG.md +8 -1
- package/components/BorderEdgeToggle.js +72 -0
- package/components/BubbleMenu.js +107 -19
- package/components/BubbleMenu.module.scss +0 -1
- package/components/InsertTokenDialog.js +1 -1
- package/components/InsertTokenDialog.module.scss +1 -1
- package/components/LinkEditDialog.js +2 -2
- package/components/LinkEditDialog.module.scss +1 -1
- package/components/TableDensityPicker.js +48 -0
- package/components/{color-picker/ColorPicker.module.scss → TableDensityPicker.module.scss} +4 -3
- package/components/TableToolbar.js +4 -0
- package/components/VerticalAlignToggle.js +8 -8
- package/components/color-picker/ColorPickerCombo.js +5 -5
- package/components/color-picker/ColorPickerCombo.module.scss +1 -1
- package/components/editor-toolbars/EditorMenu.js +2 -1
- package/components/editor-toolbars/EditorMenu.module.scss +1 -1
- package/components/editor-toolbars/EditorToolbar.js +10 -7
- package/components/editor-toolbars/EditorToolbar.module.scss +11 -68
- package/components/editor-toolbars/SimpleButton.module.scss +1 -1
- package/components/editor-toolbars/SubMenu.js +59 -3
- package/components/table-creator/TableCreatorCombo.js +4 -4
- package/components/table-creator/TableCreatorCombo.module.scss +1 -1
- package/components/tiptap/Table.js +172 -5
- package/components/tiptap/TableCells.js +33 -0
- package/components/tiptap/token/Token.module.scss +1 -1
- package/components/tiptap/token/TokenList.module.scss +1 -1
- package/components/utils.js +0 -49
- package/configs/table-toolbar-configuration.js +87 -8
- package/configs/toolbar-configuration.js +3 -3
- package/constants.js +16 -1
- package/index.js +7 -1
- package/index.module.scss +63 -22
- package/package.json +3 -4
- package/utils/generate-border-icon.js +22 -0
- package/utils/generate-vertical-alignment-icon.js +1 -1
- package/components/Popover.js +0 -43
- package/components/Popover.module.scss +0 -80
- package/components/Select/Select.js +0 -71
- package/components/Select/Select.module.scss +0 -100
- package/components/Select/SelectGroup.js +0 -37
- package/components/Select/index.js +0 -1
- package/components/Tooltip.js +0 -34
- package/components/Tooltip.module.scss +0 -87
- package/components/color-picker/ColorPicker.js +0 -28
- package/components/color-picker/ColorPickerChip.js +0 -22
- package/components/color-picker/ColorPickerChip.module.scss +0 -25
- package/components/editor-toolbars/ToolbarButton.js +0 -26
- package/components/editor-toolbars/ToolbarToggle.js +0 -35
- package/components/editor-toolbars/ToolbarToggleGroup.js +0 -43
- package/components/style.module.scss +0 -63
- package/icons/AddColumnLeft.js +0 -15
- package/icons/AddColumnRight.js +0 -15
- package/icons/AddRowAbove.js +0 -15
- package/icons/AddRowBelow.js +0 -15
- package/icons/AlignHorizontalCenter.js +0 -13
- package/icons/AlignHorizontalLeft.js +0 -13
- package/icons/AlignHorizontalRight.js +0 -13
- package/icons/AlignVerticalBottom.js +0 -13
- package/icons/AlignVerticalCenter.js +0 -13
- package/icons/AlignVerticalTop.js +0 -13
- package/icons/Backspace.js +0 -13
- package/icons/Bold.js +0 -14
- package/icons/BorderAll.js +0 -13
- package/icons/BorderBottom.js +0 -13
- package/icons/BorderClear.js +0 -13
- package/icons/BorderHorizontal.js +0 -13
- package/icons/BorderInner.js +0 -13
- package/icons/BorderLeft.js +0 -13
- package/icons/BorderOuter.js +0 -13
- package/icons/BorderRight.js +0 -13
- package/icons/BorderTop.js +0 -13
- package/icons/BorderVertical.js +0 -13
- package/icons/Close.js +0 -13
- package/icons/Delete.js +0 -13
- package/icons/EvenlyDistribute.js +0 -14
- package/icons/FitWidth.js +0 -13
- package/icons/FitWidthArrows.js +0 -21
- package/icons/FormatAlignCenter.js +0 -13
- package/icons/FormatAlignJustify.js +0 -13
- package/icons/FormatAlignLeft.js +0 -13
- package/icons/FormatAlignRight.js +0 -13
- package/icons/FormatBold.js +0 -13
- package/icons/FormatClear.js +0 -13
- package/icons/FormatColorFill.js +0 -13
- package/icons/FormatColorText.js +0 -13
- package/icons/FormatItalic.js +0 -13
- package/icons/FormatLineSpacing.js +0 -13
- package/icons/FormatListBulleted.js +0 -13
- package/icons/FormatListNumbered.js +0 -13
- package/icons/FormatStrikethrough.js +0 -13
- package/icons/FormatUnderlined.js +0 -13
- package/icons/HelpCenter.js +0 -13
- package/icons/HorizontalRule.js +0 -13
- package/icons/Italic.js +0 -14
- package/icons/ItalicIcon.js +0 -18
- package/icons/Link.js +0 -13
- package/icons/LinkOff.js +0 -13
- package/icons/MergeCells.js +0 -14
- package/icons/MoreVert.js +0 -13
- package/icons/Redo.js +0 -13
- package/icons/RemoveColumnOutline.js +0 -28
- package/icons/RemoveRowOutline.js +0 -25
- package/icons/SplitCells.js +0 -14
- package/icons/SplitScene.js +0 -13
- package/icons/Subscript.js +0 -13
- package/icons/Superscript.js +0 -13
- package/icons/Underline.js +0 -14
- package/icons/Undo.js +0 -13
- package/icons/VerticalAlignBottom.js +0 -13
- package/icons/VerticalAlignCenter.js +0 -13
- package/icons/VerticalAlignTop.js +0 -13
- package/icons/WandStars.js +0 -13
- package/icons/add_column_left.svg +0 -6
- package/icons/add_column_right.svg +0 -6
- package/icons/add_row_above.svg +0 -6
- package/icons/add_row_below.svg +0 -6
- package/icons/align_horizontal_center.svg +0 -1
- package/icons/align_horizontal_left.svg +0 -1
- package/icons/align_horizontal_right.svg +0 -1
- package/icons/align_vertical_bottom.svg +0 -1
- package/icons/align_vertical_center.svg +0 -1
- package/icons/align_vertical_top.svg +0 -1
- package/icons/backspace.svg +0 -1
- package/icons/bold.svg +0 -1
- package/icons/border_all.svg +0 -1
- package/icons/border_bottom.svg +0 -1
- package/icons/border_clear.svg +0 -1
- package/icons/border_horizontal.svg +0 -1
- package/icons/border_inner.svg +0 -1
- package/icons/border_left.svg +0 -1
- package/icons/border_outer.svg +0 -1
- package/icons/border_right.svg +0 -1
- package/icons/border_top.svg +0 -1
- package/icons/border_vertical.svg +0 -1
- package/icons/close.svg +0 -1
- package/icons/delete.svg +0 -1
- package/icons/evenly_distribute.svg +0 -5
- package/icons/fit_width.svg +0 -1
- package/icons/fit_width_arrows.svg +0 -12
- package/icons/format_align_center.svg +0 -1
- package/icons/format_align_justify.svg +0 -1
- package/icons/format_align_left.svg +0 -1
- package/icons/format_align_right.svg +0 -1
- package/icons/format_bold.svg +0 -1
- package/icons/format_clear.svg +0 -1
- package/icons/format_color_fill.svg +0 -1
- package/icons/format_italic.svg +0 -1
- package/icons/format_line_spacing.svg +0 -1
- package/icons/format_list_bulleted.svg +0 -1
- package/icons/format_list_numbered.svg +0 -1
- package/icons/format_strikethrough.svg +0 -1
- package/icons/format_underlined.svg +0 -1
- package/icons/help_center.svg +0 -1
- package/icons/horizontal_rule.svg +0 -1
- package/icons/index.js +0 -199
- package/icons/italic.svg +0 -1
- package/icons/link.svg +0 -1
- package/icons/link_off.svg +0 -1
- package/icons/merge_cells.svg +0 -5
- package/icons/more_vert.svg +0 -1
- package/icons/redo.svg +0 -1
- package/icons/remove_column_outline.svg +0 -20
- package/icons/remove_row_outline.svg +0 -17
- package/icons/split_cells.svg +0 -5
- package/icons/split_scene.svg +0 -1
- package/icons/subscript.svg +0 -1
- package/icons/superscript.svg +0 -1
- package/icons/underline.svg +0 -1
- package/icons/undo.svg +0 -1
- package/icons/vertical_align_bottom.svg +0 -1
- package/icons/vertical_align_center.svg +0 -1
- package/icons/vertical_align_top.svg +0 -1
- package/icons/wand_stars.svg +0 -1
- package/scripts/extract-svg.js +0 -290
- package/variables.scss +0 -6
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
Fragment,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
2
8
|
import classNames from "classnames";
|
|
3
9
|
import { DropdownMenu } from "radix-ui";
|
|
4
10
|
|
|
@@ -6,18 +12,67 @@ import { ChevronRightIcon } from "@radix-ui/react-icons";
|
|
|
6
12
|
import { MenuItem } from "./MenuItem";
|
|
7
13
|
import style from "./EditorMenu.module.scss";
|
|
8
14
|
|
|
15
|
+
// Delay to prevent the submenu closing just from the user
|
|
16
|
+
// rolling between the trigger and the content.
|
|
17
|
+
const POINTER_LEAVE_CLOSE_DELAY = 200;
|
|
18
|
+
|
|
9
19
|
export function SubMenu({
|
|
10
20
|
label,
|
|
11
21
|
icon,
|
|
12
22
|
items = [],
|
|
13
23
|
disabled = false,
|
|
14
24
|
renderItem = null,
|
|
25
|
+
// For submenus whose contents run editor commands.
|
|
26
|
+
// Prevents focus jumping back to ProseMirror and closing
|
|
27
|
+
// a submenu that we want to keep open (eg. border cell selector).
|
|
28
|
+
// Submenu stays open until the user rolls off the trigger.
|
|
29
|
+
keepOpenOnInteract = false,
|
|
15
30
|
}) {
|
|
16
31
|
const div = window.Bridge.UI.overlay;
|
|
17
32
|
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
const closeTimerRef = useRef(null);
|
|
35
|
+
|
|
36
|
+
const cancelClose = useCallback(() => {
|
|
37
|
+
if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);
|
|
38
|
+
closeTimerRef.current = null;
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const scheduleClose = useCallback(() => {
|
|
42
|
+
cancelClose();
|
|
43
|
+
closeTimerRef.current = window.setTimeout(() => {
|
|
44
|
+
closeTimerRef.current = null;
|
|
45
|
+
setOpen(false);
|
|
46
|
+
}, POINTER_LEAVE_CLOSE_DELAY);
|
|
47
|
+
}, [cancelClose]);
|
|
48
|
+
|
|
49
|
+
// Mirror every Radix-initiated change (hover-open, arrow keys, Escape,
|
|
50
|
+
// click-outside) so the controlled value never fights the primitive.
|
|
51
|
+
const handleOpenChange = useCallback(
|
|
52
|
+
(nextOpen) => {
|
|
53
|
+
cancelClose();
|
|
54
|
+
setOpen(nextOpen);
|
|
55
|
+
},
|
|
56
|
+
[cancelClose],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
useEffect(() => cancelClose, [cancelClose]);
|
|
60
|
+
|
|
61
|
+
// Spread conditionally so submenus that don't opt in stay uncontrolled, ie.
|
|
62
|
+
// exactly as they behaved before.
|
|
63
|
+
const subProps = keepOpenOnInteract
|
|
64
|
+
? { open, onOpenChange: handleOpenChange }
|
|
65
|
+
: {};
|
|
66
|
+
const hoverProps = keepOpenOnInteract
|
|
67
|
+
? { onPointerEnter: cancelClose, onPointerLeave: scheduleClose }
|
|
68
|
+
: {};
|
|
69
|
+
const contentProps = keepOpenOnInteract
|
|
70
|
+
? { ...hoverProps, onFocusOutside: (event) => event.preventDefault() }
|
|
71
|
+
: {};
|
|
72
|
+
|
|
18
73
|
return (
|
|
19
|
-
<DropdownMenu.Sub>
|
|
20
|
-
<DropdownMenu.SubTrigger className={style.item}>
|
|
74
|
+
<DropdownMenu.Sub {...subProps}>
|
|
75
|
+
<DropdownMenu.SubTrigger className={style.item} {...hoverProps}>
|
|
21
76
|
<div className={style.itemIcon}>{icon}</div>
|
|
22
77
|
<div className={style.itemLabel}>{label}</div>
|
|
23
78
|
<div className={classNames(style.itemIcon, style.itemIconRight)}>
|
|
@@ -31,6 +86,7 @@ export function SubMenu({
|
|
|
31
86
|
sideOffset={-30}
|
|
32
87
|
alignOffset={-5}
|
|
33
88
|
collisionBoundary={div}
|
|
89
|
+
{...contentProps}
|
|
34
90
|
>
|
|
35
91
|
{items.map((item) => {
|
|
36
92
|
if (typeof renderItem === "function") {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
import { TableCreator } from "./TableCreator";
|
|
4
|
-
import { Popover } from "
|
|
5
|
-
import toolbarStyle from "
|
|
6
|
-
import { BorderAll } from "
|
|
7
|
-
import { ToolbarButton } from "
|
|
4
|
+
import { Popover } from "@livepreso/react-plugin-ui/popover";
|
|
5
|
+
import toolbarStyle from "@livepreso/react-plugin-ui/toolbar/toolbar.module.scss";
|
|
6
|
+
import { BorderAll } from "@livepreso/react-plugin-ui/icons";
|
|
7
|
+
import { ToolbarButton } from "@livepreso/react-plugin-ui/toolbar";
|
|
8
8
|
|
|
9
9
|
export function TableCreatorCombo({ disabled, onChange }) {
|
|
10
10
|
return (
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { CellSelection, TableMap } from "@tiptap/pm/tables";
|
|
2
|
-
import { Table as TableBase } from "@tiptap/extension-table";
|
|
2
|
+
import { Table as TableBase, TableView } from "@tiptap/extension-table";
|
|
3
|
+
import {
|
|
4
|
+
getAttributesFromExtensions,
|
|
5
|
+
getRenderedAttributes,
|
|
6
|
+
} from "@tiptap/core";
|
|
3
7
|
import { getDomWidth } from "../utils";
|
|
4
|
-
import { CELL_MIN_WIDTH } from "../../constants";
|
|
8
|
+
import { CELL_MIN_WIDTH, BORDER_SIDES } from "../../constants";
|
|
5
9
|
|
|
6
10
|
export const CELL_VALIGN_TOP = "top";
|
|
7
11
|
export const CELL_VALIGN_MIDDLE = "middle";
|
|
8
12
|
export const CELL_VALIGN_BOTTOM = "bottom";
|
|
9
13
|
export const CELL_VALIGN_MIXED = "mixed";
|
|
10
14
|
|
|
15
|
+
export const TABLE_DENSITY_SPACIOUS = "spacious";
|
|
16
|
+
export const TABLE_DENSITY_NORMAL = "normal";
|
|
17
|
+
export const TABLE_DENSITY_DENSE = "dense";
|
|
18
|
+
|
|
11
19
|
/**
|
|
12
20
|
* Finds the table node and its position if the current selection is inside a table
|
|
13
21
|
* @param state The current editor state
|
|
@@ -48,7 +56,7 @@ export function findCellsInSelection(state) {
|
|
|
48
56
|
|
|
49
57
|
if (!table) return result;
|
|
50
58
|
const cells = [];
|
|
51
|
-
selection.forEachCell((node
|
|
59
|
+
selection.forEachCell((node) => {
|
|
52
60
|
cells.push(node);
|
|
53
61
|
});
|
|
54
62
|
return cells;
|
|
@@ -199,7 +207,7 @@ function setColumnWidths({
|
|
|
199
207
|
let rowPos = tablePos + 1; // Start at the first row
|
|
200
208
|
|
|
201
209
|
// Process each row in the table
|
|
202
|
-
tableNode.forEach((row
|
|
210
|
+
tableNode.forEach((row) => {
|
|
203
211
|
if (row.type.name !== "tableRow") return;
|
|
204
212
|
|
|
205
213
|
let cellPos = rowPos + 1; // Start at the first cell in this row
|
|
@@ -255,6 +263,84 @@ export function getCellAlignment(editor) {
|
|
|
255
263
|
return CELL_VALIGN_MIXED;
|
|
256
264
|
}
|
|
257
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Summarises which border sides are active across the currently selected cells.
|
|
268
|
+
* A side counts as active only if every selected cell has it active (intersection),
|
|
269
|
+
* so the toggle group reflects a side as "on" only when the whole selection agrees.
|
|
270
|
+
*/
|
|
271
|
+
export function getCellBorderSides(editor) {
|
|
272
|
+
const values = getCellAttributes(editor, "borderSides");
|
|
273
|
+
const mappedValues = values.map((v) => v || BORDER_SIDES);
|
|
274
|
+
if (mappedValues.length === 0) return BORDER_SIDES;
|
|
275
|
+
|
|
276
|
+
return BORDER_SIDES.filter((side) =>
|
|
277
|
+
mappedValues.every((sides) => sides.includes(side)),
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Returns the density of the table the selection is currently in, defaulting
|
|
283
|
+
* to normal if there's no table in the selection or it predates this attribute.
|
|
284
|
+
*/
|
|
285
|
+
export function getTableDensity(editor) {
|
|
286
|
+
const table = findTable(editor.state);
|
|
287
|
+
return table?.node?.attrs?.density || TABLE_DENSITY_NORMAL;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Returns whether the table the selection is currently in is set to full width.
|
|
292
|
+
*/
|
|
293
|
+
export function getTableFullWidth(editor) {
|
|
294
|
+
const table = findTable(editor.state);
|
|
295
|
+
return Boolean(table?.node?.attrs?.fullWidth);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* When resizable, the table extension's columnResizing plugin builds the
|
|
300
|
+
* <table> DOM element itself via a NodeView (TableView) rather than the node's
|
|
301
|
+
* renderHTML - and TableView#update only ever re-syncs colgroup/width, never
|
|
302
|
+
* any other attribute. Any table-level attribute change (eg. density) is
|
|
303
|
+
* saved correctly but never reaches the live DOM without this override
|
|
304
|
+
* re-applying it ourselves.
|
|
305
|
+
*
|
|
306
|
+
* getRenderedAttributes/getAttributesFromExtensions are the same helpers
|
|
307
|
+
* Tiptap uses internally to build the non-NodeView renderHTML output, so any
|
|
308
|
+
* attribute added via addAttributes() on the table node flows through here
|
|
309
|
+
* automatically.
|
|
310
|
+
*/
|
|
311
|
+
class TableViewWithAttrs extends TableView {
|
|
312
|
+
constructor(node, cellMinWidth, view) {
|
|
313
|
+
super(node, cellMinWidth);
|
|
314
|
+
// Cached once - the extension list doesn't change during the editor's lifetime.
|
|
315
|
+
this.extensionAttributes = getAttributesFromExtensions(
|
|
316
|
+
view?.dom?.editor?.extensionManager.extensions || [],
|
|
317
|
+
);
|
|
318
|
+
this.syncAttrs(node);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
update(node) {
|
|
322
|
+
const updated = super.update(node);
|
|
323
|
+
if (updated) this.syncAttrs(node);
|
|
324
|
+
return updated;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
syncAttrs(node) {
|
|
328
|
+
const attrs = getRenderedAttributes(node, this.extensionAttributes);
|
|
329
|
+
|
|
330
|
+
Object.entries(attrs).forEach(([key, value]) => {
|
|
331
|
+
// width/min-width is already recalculated more precisely by
|
|
332
|
+
// updateColumns() above, from live column widths - don't clobber it.
|
|
333
|
+
if (key === "style") return;
|
|
334
|
+
|
|
335
|
+
if (value === null || value === undefined) {
|
|
336
|
+
this.table.removeAttribute(key);
|
|
337
|
+
} else {
|
|
338
|
+
this.table.setAttribute(key, value);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
258
344
|
/**
|
|
259
345
|
* Extension that adds equal column width functionality to tables
|
|
260
346
|
*/
|
|
@@ -265,6 +351,37 @@ export const TableExtended = TableBase.extend({
|
|
|
265
351
|
cellMinWidth: CELL_MIN_WIDTH,
|
|
266
352
|
lastColumnResizable: true,
|
|
267
353
|
resizable: true,
|
|
354
|
+
View: TableViewWithAttrs,
|
|
355
|
+
};
|
|
356
|
+
},
|
|
357
|
+
addAttributes() {
|
|
358
|
+
return {
|
|
359
|
+
...this.parent?.(),
|
|
360
|
+
// Rendered as a class rather than inline styles so a deck importing this
|
|
361
|
+
// component can override the padding values per density state via CSS.
|
|
362
|
+
density: {
|
|
363
|
+
default: TABLE_DENSITY_NORMAL,
|
|
364
|
+
parseHTML: (element) =>
|
|
365
|
+
element.getAttribute("data-density") || TABLE_DENSITY_NORMAL,
|
|
366
|
+
renderHTML: ({ density }) => ({
|
|
367
|
+
"data-density": density,
|
|
368
|
+
class: `tiptap-table-density-${density}`,
|
|
369
|
+
}),
|
|
370
|
+
},
|
|
371
|
+
// !FEATURE CURRENTLY NOT IN USE!
|
|
372
|
+
// Rendered as a class (rather than a `width: 100%` style) because the
|
|
373
|
+
// resizable table's own colgroup logic (updateColumns) reasserts an
|
|
374
|
+
// inline width/min-width on every update, based on `node.attrs.style`
|
|
375
|
+
// specifically - a style-based width here would be silently clobbered.
|
|
376
|
+
// See setFullWidthLegacy below for the previous, column-width-based approach.
|
|
377
|
+
fullWidth: {
|
|
378
|
+
default: false,
|
|
379
|
+
parseHTML: (element) => element.hasAttribute("data-full-width"),
|
|
380
|
+
renderHTML: ({ fullWidth }) =>
|
|
381
|
+
fullWidth
|
|
382
|
+
? { "data-full-width": "true", class: "tiptap-table-full-width" }
|
|
383
|
+
: {},
|
|
384
|
+
},
|
|
268
385
|
};
|
|
269
386
|
},
|
|
270
387
|
addCommands() {
|
|
@@ -296,7 +413,12 @@ export const TableExtended = TableBase.extend({
|
|
|
296
413
|
|
|
297
414
|
return true;
|
|
298
415
|
},
|
|
299
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Original full-width implementation - redistributes column widths to
|
|
418
|
+
* fill the available space. Plan is to replace this with the simpler
|
|
419
|
+
* `setFullWidth` (CSS-class-based) approach below, when it's ready.
|
|
420
|
+
*/
|
|
421
|
+
setFullWidthLegacy:
|
|
300
422
|
() =>
|
|
301
423
|
({ state, tr, dispatch, editor }) => {
|
|
302
424
|
const table = findTable(state);
|
|
@@ -317,6 +439,30 @@ export const TableExtended = TableBase.extend({
|
|
|
317
439
|
dispatch(newTr);
|
|
318
440
|
}
|
|
319
441
|
|
|
442
|
+
return true;
|
|
443
|
+
},
|
|
444
|
+
/**
|
|
445
|
+
* !FEATURE CURRENTLY NOT IN USE!
|
|
446
|
+
* Toggle full width for the current table.
|
|
447
|
+
* Plan was to replace legacy in the future, but there
|
|
448
|
+
* is some bugginess that needs ironing out first.
|
|
449
|
+
*/
|
|
450
|
+
setFullWidth:
|
|
451
|
+
(fullWidth) =>
|
|
452
|
+
({ state, tr, dispatch }) => {
|
|
453
|
+
const table = findTable(state);
|
|
454
|
+
|
|
455
|
+
if (!table) return false;
|
|
456
|
+
|
|
457
|
+
if (dispatch) {
|
|
458
|
+
dispatch(
|
|
459
|
+
tr.setNodeMarkup(table.pos, null, {
|
|
460
|
+
...table.node.attrs,
|
|
461
|
+
fullWidth,
|
|
462
|
+
}),
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
320
466
|
return true;
|
|
321
467
|
},
|
|
322
468
|
cleanWidth:
|
|
@@ -350,6 +496,27 @@ export const TableExtended = TableBase.extend({
|
|
|
350
496
|
dispatch(newTr);
|
|
351
497
|
}
|
|
352
498
|
|
|
499
|
+
return true;
|
|
500
|
+
},
|
|
501
|
+
/**
|
|
502
|
+
* Set the padding density for the current table
|
|
503
|
+
*/
|
|
504
|
+
setTableDensity:
|
|
505
|
+
(density) =>
|
|
506
|
+
({ state, tr, dispatch }) => {
|
|
507
|
+
const table = findTable(state);
|
|
508
|
+
|
|
509
|
+
if (!table) return false;
|
|
510
|
+
|
|
511
|
+
if (dispatch) {
|
|
512
|
+
dispatch(
|
|
513
|
+
tr.setNodeMarkup(table.pos, null, {
|
|
514
|
+
...table.node.attrs,
|
|
515
|
+
density,
|
|
516
|
+
}),
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
353
520
|
return true;
|
|
354
521
|
},
|
|
355
522
|
applyStyleToCells:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TableCell as TableCellBase } from "@tiptap/extension-table";
|
|
2
2
|
import { TableHeader as TableHeaderBase } from "@tiptap/extension-table";
|
|
3
3
|
import { hexIsDark } from "../../utils/color-utils";
|
|
4
|
+
import { BORDER_SIDES, BORDER_SIDES_NONE } from "../../constants";
|
|
4
5
|
|
|
5
6
|
const tableCellAttributes = {
|
|
6
7
|
colwidth: {
|
|
@@ -78,6 +79,38 @@ const tableCellAttributes = {
|
|
|
78
79
|
return attrs;
|
|
79
80
|
},
|
|
80
81
|
},
|
|
82
|
+
// Sides with a visible border. An absent attribute means "all sides" (default),
|
|
83
|
+
// `"none"` means no sides at all. Only sides missing from the list get an explicit
|
|
84
|
+
// `border-<side>: hidden`, remaining sides inherit default CSS border styling.
|
|
85
|
+
// Note: `hidden` is used instead of `none`, because the table is `border-collapse: collapse`,
|
|
86
|
+
// otherwise the neighbouring cell's border would keep painting the shared edge.
|
|
87
|
+
borderSides: {
|
|
88
|
+
default: null,
|
|
89
|
+
parseHTML: (element) => {
|
|
90
|
+
const value = element.getAttribute("data-border-sides");
|
|
91
|
+
if (value === null) return null;
|
|
92
|
+
// `""` is only produced by builds predating BORDER_SIDES_NONE.
|
|
93
|
+
if (value === BORDER_SIDES_NONE || value === "") return [];
|
|
94
|
+
return value.split(",").filter((side) => BORDER_SIDES.includes(side));
|
|
95
|
+
},
|
|
96
|
+
renderHTML: ({ borderSides }) => {
|
|
97
|
+
if (!borderSides) return { "data-border-sides": null };
|
|
98
|
+
|
|
99
|
+
const hiddenSides = BORDER_SIDES.filter(
|
|
100
|
+
(side) => !borderSides.includes(side),
|
|
101
|
+
);
|
|
102
|
+
const style = hiddenSides
|
|
103
|
+
.map((side) => `border-${side}: hidden;`)
|
|
104
|
+
.join(" ");
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
"data-border-sides": borderSides.length
|
|
108
|
+
? borderSides.join(",")
|
|
109
|
+
: BORDER_SIDES_NONE,
|
|
110
|
+
style,
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
},
|
|
81
114
|
};
|
|
82
115
|
|
|
83
116
|
export const TableCell = TableCellBase.extend({
|
package/components/utils.js
CHANGED
|
@@ -33,52 +33,3 @@ export function getDomWidth(dom) {
|
|
|
33
33
|
const bounds = dom?.getBoundingClientRect() || { width: 0 };
|
|
34
34
|
return bounds.width ? bounds.width / getContentRatio() : null;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Tests if the element is contained within the bounds of a mask element
|
|
39
|
-
* and returns appropriate rect combining the element and mask rects.
|
|
40
|
-
*
|
|
41
|
-
* If the test is outside of the vertical limits of the mask, the mask's
|
|
42
|
-
* top, bottom and height will replace the testRect's, while textRect
|
|
43
|
-
* maintains its width, left and right.
|
|
44
|
-
*
|
|
45
|
-
* @param {Object} element - return of getBoundingClientRect()
|
|
46
|
-
* @param {Object} parent - return of getBoundingClientRect()
|
|
47
|
-
* @returns {Object}
|
|
48
|
-
*/
|
|
49
|
-
export const getTestRect = (testRect, maskRect) => {
|
|
50
|
-
const newRect = {
|
|
51
|
-
y: testRect.y,
|
|
52
|
-
x: testRect.x,
|
|
53
|
-
width: testRect.width,
|
|
54
|
-
height: testRect.height,
|
|
55
|
-
top: testRect.top,
|
|
56
|
-
bottom: testRect.bottom,
|
|
57
|
-
left: testRect.left,
|
|
58
|
-
right: testRect.right,
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
if (testRect.top < maskRect.top) {
|
|
62
|
-
newRect.y = maskRect.y;
|
|
63
|
-
newRect.top = maskRect.top;
|
|
64
|
-
newRect.height = testRect.height - (maskRect.top - testRect.top);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (testRect.bottom > maskRect.bottom) {
|
|
68
|
-
newRect.bottom = maskRect.bottom;
|
|
69
|
-
newRect.height = testRect.height - (testRect.bottom - maskRect.bottom);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (testRect.left < maskRect.left) {
|
|
73
|
-
newRect.x = maskRect.x;
|
|
74
|
-
newRect.left = maskRect.left;
|
|
75
|
-
newRect.width = testRect.width - (maskRect.left - testRect.left);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (testRect.right > maskRect.right) {
|
|
79
|
-
newRect.right = maskRect.right;
|
|
80
|
-
newRect.width = testRect.width - (testRect.right - maskRect.right);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return newRect;
|
|
84
|
-
};
|
|
@@ -1,21 +1,69 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import icons from "
|
|
2
|
+
import icons from "@livepreso/react-plugin-ui/icons";
|
|
3
3
|
import {
|
|
4
4
|
TOOLBAR_COMPONENT_TYPES,
|
|
5
5
|
TABLE_TOOLBAR_IDS,
|
|
6
6
|
TOOLBAR_SCOPES,
|
|
7
|
+
BORDER_SIDES,
|
|
7
8
|
} from "../constants";
|
|
8
|
-
import {
|
|
9
|
+
import { ColorPickerBasic } from "@livepreso/react-plugin-ui/color-picker";
|
|
9
10
|
import { VerticalAlignToggle } from "../components/VerticalAlignToggle";
|
|
10
|
-
import {
|
|
11
|
+
import { BorderEdgeToggle } from "../components/BorderEdgeToggle";
|
|
12
|
+
import { TableDensityPicker } from "../components/TableDensityPicker";
|
|
13
|
+
import {
|
|
14
|
+
findCellsInSelection,
|
|
15
|
+
getCellBorderSides,
|
|
16
|
+
getTableDensity,
|
|
17
|
+
getTableFullWidth,
|
|
18
|
+
} from "../components/tiptap/Table";
|
|
11
19
|
import { generateVerticalAlignmentIcon } from "../utils/generate-vertical-alignment-icon";
|
|
20
|
+
import { generateBorderIcon } from "../utils/generate-border-icon";
|
|
12
21
|
|
|
13
22
|
// Table-wide options
|
|
14
|
-
const
|
|
23
|
+
const tableDensityPicker = () => ({
|
|
24
|
+
id: TABLE_TOOLBAR_IDS.TABLE_DENSITY,
|
|
25
|
+
label: "Table density",
|
|
26
|
+
type: TOOLBAR_COMPONENT_TYPES.CUSTOM,
|
|
27
|
+
scope: TOOLBAR_SCOPES.TABLE,
|
|
28
|
+
value: (editor) => getTableDensity(editor),
|
|
29
|
+
renderContent: ({ editor, value }) => (
|
|
30
|
+
<TableDensityPicker
|
|
31
|
+
value={value}
|
|
32
|
+
onChange={(density) =>
|
|
33
|
+
editor.chain().focus().setTableDensity(density).run()
|
|
34
|
+
}
|
|
35
|
+
/>
|
|
36
|
+
),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Original "Fit width" control - a one-shot button that redistributes column
|
|
41
|
+
* widths to fill the available space. Plan is to replace with the `fullWidthToggle`
|
|
42
|
+
* below, but the new function needs more work and testing.
|
|
43
|
+
*/
|
|
44
|
+
const fullWidthToggleLegacy = () => ({
|
|
15
45
|
id: TABLE_TOOLBAR_IDS.FULL_WIDTH,
|
|
16
46
|
label: "Fit width",
|
|
17
47
|
type: TOOLBAR_COMPONENT_TYPES.BUTTON,
|
|
18
|
-
command: (editor) => editor.chain().focus().
|
|
48
|
+
command: (editor) => editor.chain().focus().setFullWidthLegacy().run(),
|
|
49
|
+
icon: <icons.FitWidthArrows />,
|
|
50
|
+
scope: TOOLBAR_SCOPES.TABLE,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* !FEATURE CURRENTLY NOT IN USE!
|
|
55
|
+
* Trialled full-width toggle, worked well in initial tests, but proved problematic
|
|
56
|
+
* in preview / present modes, and resizing of columns in certain situations.
|
|
57
|
+
* Will revisit in the future.
|
|
58
|
+
*/
|
|
59
|
+
/* eslint-disable-next-line no-unused-vars */
|
|
60
|
+
const fullWidthToggle = () => ({
|
|
61
|
+
id: TABLE_TOOLBAR_IDS.FULL_WIDTH,
|
|
62
|
+
label: "Fit width",
|
|
63
|
+
type: TOOLBAR_COMPONENT_TYPES.TOGGLE,
|
|
64
|
+
active: (editor) => getTableFullWidth(editor),
|
|
65
|
+
command: (editor) =>
|
|
66
|
+
editor.chain().focus().setFullWidth(!getTableFullWidth(editor)).run(),
|
|
19
67
|
icon: <icons.FitWidthArrows />,
|
|
20
68
|
scope: TOOLBAR_SCOPES.TABLE,
|
|
21
69
|
});
|
|
@@ -132,7 +180,7 @@ const cellBgColorPicker = ({ colors }) => ({
|
|
|
132
180
|
type: TOOLBAR_COMPONENT_TYPES.SUB,
|
|
133
181
|
items: [{ id: "color-picker" }],
|
|
134
182
|
renderItem: (item, { editor } = {}) => (
|
|
135
|
-
<
|
|
183
|
+
<ColorPickerBasic
|
|
136
184
|
colors={colors}
|
|
137
185
|
onChange={(color) =>
|
|
138
186
|
editor.chain().focus().setCellAttribute("backgroundColor", color).run()
|
|
@@ -153,7 +201,7 @@ const cellValignPicker = () => ({
|
|
|
153
201
|
if (!cells) return null;
|
|
154
202
|
return cells[0]?.attrs?.verticalAlign;
|
|
155
203
|
},
|
|
156
|
-
renderItem: (item, { editor,
|
|
204
|
+
renderItem: (item, { editor, value } = {}) => (
|
|
157
205
|
<VerticalAlignToggle
|
|
158
206
|
id={TABLE_TOOLBAR_IDS.CELL_VALIGN}
|
|
159
207
|
value={value}
|
|
@@ -167,9 +215,39 @@ const cellValignPicker = () => ({
|
|
|
167
215
|
/>
|
|
168
216
|
),
|
|
169
217
|
});
|
|
218
|
+
|
|
219
|
+
const cellBorderPicker = () => ({
|
|
220
|
+
id: TABLE_TOOLBAR_IDS.CELL_BORDER,
|
|
221
|
+
label: "Border",
|
|
222
|
+
renderIcon: generateBorderIcon,
|
|
223
|
+
scope: TOOLBAR_SCOPES.TABLE_CELL,
|
|
224
|
+
type: TOOLBAR_COMPONENT_TYPES.SUB,
|
|
225
|
+
items: [{ id: "border-sides" }],
|
|
226
|
+
// Four independent toggles - the submenu has to survive repeated clicks.
|
|
227
|
+
keepOpenOnInteract: true,
|
|
228
|
+
value: (editor) => getCellBorderSides(editor),
|
|
229
|
+
renderItem: (item, { editor, value } = {}) => (
|
|
230
|
+
<BorderEdgeToggle
|
|
231
|
+
id={TABLE_TOOLBAR_IDS.CELL_BORDER}
|
|
232
|
+
value={value}
|
|
233
|
+
onChange={(sides) =>
|
|
234
|
+
editor
|
|
235
|
+
.chain()
|
|
236
|
+
.focus()
|
|
237
|
+
.setCellAttribute(
|
|
238
|
+
"borderSides",
|
|
239
|
+
sides.length === BORDER_SIDES.length ? null : sides,
|
|
240
|
+
)
|
|
241
|
+
.run()
|
|
242
|
+
}
|
|
243
|
+
/>
|
|
244
|
+
),
|
|
245
|
+
});
|
|
246
|
+
|
|
170
247
|
export const tableItemGenerators = {
|
|
171
248
|
// Table-wide options
|
|
172
|
-
[TABLE_TOOLBAR_IDS.
|
|
249
|
+
[TABLE_TOOLBAR_IDS.TABLE_DENSITY]: tableDensityPicker,
|
|
250
|
+
[TABLE_TOOLBAR_IDS.FULL_WIDTH]: fullWidthToggleLegacy,
|
|
173
251
|
[TABLE_TOOLBAR_IDS.EQUAL_WIDTH_COLUMNS]: distributeColumnsButton,
|
|
174
252
|
[TABLE_TOOLBAR_IDS.DELETE_TABLE]: deleteTableButton,
|
|
175
253
|
// Internal table options
|
|
@@ -184,4 +262,5 @@ export const tableItemGenerators = {
|
|
|
184
262
|
[TABLE_TOOLBAR_IDS.SPLIT_CELLS]: splitCellsButton,
|
|
185
263
|
[TABLE_TOOLBAR_IDS.CELL_BG_COLOR]: cellBgColorPicker,
|
|
186
264
|
[TABLE_TOOLBAR_IDS.CELL_VALIGN]: cellValignPicker,
|
|
265
|
+
[TABLE_TOOLBAR_IDS.CELL_BORDER]: cellBorderPicker,
|
|
187
266
|
};
|
|
@@ -12,12 +12,12 @@ import { Heading } from "@tiptap/extension-heading";
|
|
|
12
12
|
import Underline from "@tiptap/extension-underline";
|
|
13
13
|
import { UndoRedo } from "@tiptap/extensions";
|
|
14
14
|
|
|
15
|
-
import icons from "
|
|
15
|
+
import icons from "@livepreso/react-plugin-ui/icons";
|
|
16
16
|
import { TableRow } from "@tiptap/extension-table";
|
|
17
17
|
import { TextStyle, Color } from "@tiptap/extension-text-style";
|
|
18
18
|
import { TableCell, TableHeader } from "../components/tiptap/TableCells";
|
|
19
19
|
import { TableExtended } from "../components/tiptap/Table";
|
|
20
|
-
import { Select } from "
|
|
20
|
+
import { Select } from "@livepreso/react-plugin-ui/select";
|
|
21
21
|
import { PRIMARY_TOOLBAR_IDS, TOOLBAR_COMPONENT_TYPES } from "../constants";
|
|
22
22
|
import LinkEditDialog from "../components/LinkEditDialog";
|
|
23
23
|
import { Gapcursor } from "@tiptap/extensions";
|
|
@@ -343,7 +343,7 @@ const insertTableButton = () => ({
|
|
|
343
343
|
cols,
|
|
344
344
|
withHeaderRow: true,
|
|
345
345
|
})
|
|
346
|
-
.
|
|
346
|
+
.setFullWidthLegacy()
|
|
347
347
|
.run();
|
|
348
348
|
}}
|
|
349
349
|
/>
|
package/constants.js
CHANGED
|
@@ -26,6 +26,14 @@ export const TOOLBAR_SCOPES = Object.freeze({
|
|
|
26
26
|
TABLE_CELL: "table_cell",
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
export const BORDER_SIDES = ["top", "right", "bottom", "left"];
|
|
30
|
+
|
|
31
|
+
// An empty list can't be written as an empty `data-border-sides` attribute,
|
|
32
|
+
// we needed null/empty to represent default cell border style (for backwards
|
|
33
|
+
// compatibility) - as such, a specific "none" was needed to prevent "none"
|
|
34
|
+
// looking like default (generally "all").
|
|
35
|
+
export const BORDER_SIDES_NONE = "none";
|
|
36
|
+
|
|
29
37
|
export const EDITABLE_OUTER_SELECTOR = "sp-textfield";
|
|
30
38
|
|
|
31
39
|
// Primary groups
|
|
@@ -61,6 +69,7 @@ export const PRIMARY_TOOLBAR_IDS = Object.freeze({
|
|
|
61
69
|
});
|
|
62
70
|
|
|
63
71
|
// Table editing & formatting groups
|
|
72
|
+
export const TABLE_DENSITY = "tableDensity";
|
|
64
73
|
export const TABLE_SIZE = "tableSize";
|
|
65
74
|
export const DELETE_TABLE = "deleteTable";
|
|
66
75
|
export const COLUMN = "column";
|
|
@@ -68,6 +77,7 @@ export const ROW = "row";
|
|
|
68
77
|
export const CELL = "cell";
|
|
69
78
|
|
|
70
79
|
export const TABLE_TOOLBAR_IDS = Object.freeze({
|
|
80
|
+
TABLE_DENSITY: TABLE_DENSITY,
|
|
71
81
|
FULL_WIDTH: "fullWidthTable",
|
|
72
82
|
EQUAL_WIDTH_COLUMNS: "equalWidthColumns",
|
|
73
83
|
DELETE_TABLE: DELETE_TABLE,
|
|
@@ -82,6 +92,7 @@ export const TABLE_TOOLBAR_IDS = Object.freeze({
|
|
|
82
92
|
SPLIT_CELLS: "splitCells",
|
|
83
93
|
CELL_BG_COLOR: "cellBackgroundColor",
|
|
84
94
|
CELL_VALIGN: "cellVerticalAlignment",
|
|
95
|
+
CELL_BORDER: "cellBorderSides",
|
|
85
96
|
});
|
|
86
97
|
|
|
87
98
|
// Default group configurations
|
|
@@ -125,6 +136,9 @@ const PRIMARY_TOOLBAR_GROUPS = Object.freeze({
|
|
|
125
136
|
});
|
|
126
137
|
|
|
127
138
|
const TABLE_TOOLBAR_GROUPS = Object.freeze({
|
|
139
|
+
[TABLE_DENSITY]: {
|
|
140
|
+
items: [TABLE_TOOLBAR_IDS.TABLE_DENSITY],
|
|
141
|
+
},
|
|
128
142
|
[TABLE_SIZE]: {
|
|
129
143
|
items: [
|
|
130
144
|
TABLE_TOOLBAR_IDS.FULL_WIDTH,
|
|
@@ -158,6 +172,7 @@ const TABLE_MENU_GROUPS = Object.freeze({
|
|
|
158
172
|
TABLE_TOOLBAR_IDS.SPLIT_CELLS,
|
|
159
173
|
TABLE_TOOLBAR_IDS.CELL_BG_COLOR,
|
|
160
174
|
TABLE_TOOLBAR_IDS.CELL_VALIGN,
|
|
175
|
+
TABLE_TOOLBAR_IDS.CELL_BORDER,
|
|
161
176
|
],
|
|
162
177
|
},
|
|
163
178
|
});
|
|
@@ -169,7 +184,7 @@ export const DEFAULT_TOOLBAR_GROUPS = {
|
|
|
169
184
|
};
|
|
170
185
|
|
|
171
186
|
// Default group order of table-wide and table-cell editors
|
|
172
|
-
const TABLE_OPTIONS = [TABLE_SIZE, DELETE_TABLE];
|
|
187
|
+
const TABLE_OPTIONS = [TABLE_DENSITY, TABLE_SIZE, DELETE_TABLE];
|
|
173
188
|
const TABLE_CELL_OPTIONS = [COLUMN, ROW, CELL];
|
|
174
189
|
|
|
175
190
|
// Complete set of toolbar configurations
|