diff --git a/.gitignore b/.gitignore
index 2fd83a6..7a1537b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
.idea
node_modules
-dist
diff --git a/dist/index.d.ts b/dist/index.d.ts
new file mode 100644
index 0000000..48a9a9c
--- /dev/null
+++ b/dist/index.d.ts
@@ -0,0 +1,2 @@
+import { default as Plugin } from './plugin';
+export default Plugin;
diff --git a/dist/plugin.d.ts b/dist/plugin.d.ts
new file mode 100644
index 0000000..2409c83
--- /dev/null
+++ b/dist/plugin.d.ts
@@ -0,0 +1,224 @@
+import { default as Table } from './table';
+/**
+ * @typedef {object} TableData - configuration that the user can set for the table
+ * @property {number} rows - number of rows in the table
+ * @property {number} cols - number of columns in the table
+ */
+/**
+ * @typedef {object} Tune - setting for the table
+ * @property {string} name - tune name
+ * @property {HTMLElement} icon - icon for the tune
+ * @property {boolean} isActive - default state of the tune
+ * @property {void} setTune - set tune state to the table data
+ */
+/**
+ * @typedef {object} TableConfig - object with the data transferred to form a table
+ * @property {boolean} withHeading - setting to use cells of the first row as headings
+ * @property {string[][]} content - two-dimensional array which contains table content
+ */
+/**
+ * @typedef {object} TableConstructor
+ * @property {TableConfig} data — previously saved data
+ * @property {TableConfig} config - user config for Tool
+ * @property {object} api - Editor.js API
+ * @property {boolean} readOnly - read-only mode flag
+ */
+/**
+ * @typedef {import('@editorjs/editorjs').PasteEvent} PasteEvent
+ */
+/**
+ * Table block for Editor.js
+ */
+export default class TableBlock {
+ /**
+ * Notify core that read-only mode is supported
+ *
+ * @returns {boolean}
+ */
+ static get isReadOnlySupported(): boolean;
+ /**
+ * Allow to press Enter inside the CodeTool textarea
+ *
+ * @returns {boolean}
+ * @public
+ */
+ public static get enableLineBreaks(): boolean;
+ /**
+ * Get Tool toolbox settings
+ * icon - Tool icon's SVG
+ * title - title to show in toolbox
+ *
+ * @returns {{icon: string, title: string}}
+ */
+ static get toolbox(): {
+ icon: string;
+ title: string;
+ };
+ /**
+ * Table onPaste configuration
+ *
+ * @public
+ */
+ public static get pasteConfig(): {
+ tags: string[];
+ };
+ /**
+ * Render plugin`s main Element and fill it with saved data
+ *
+ * @param {TableConstructor} init
+ */
+ constructor({ data, config, api, readOnly, block }: TableConstructor);
+ api: any;
+ readOnly: boolean;
+ config: TableConfig;
+ data: {
+ withHeadings: any;
+ stretched: any;
+ content: string[][];
+ };
+ table: Table;
+ block: any;
+ /**
+ * Return Tool's view
+ *
+ * @returns {HTMLDivElement}
+ */
+ render(): HTMLDivElement;
+ /** creating container around table */
+ container: Element;
+ /**
+ * Returns plugin settings
+ *
+ * @returns {Array}
+ */
+ renderSettings(): any[];
+ /**
+ * Extract table data from the view
+ *
+ * @returns {TableData} - saved data
+ */
+ save(): TableData;
+ /**
+ * Plugin destroyer
+ *
+ * @returns {void}
+ */
+ destroy(): void;
+ /**
+ * A helper to get config value.
+ *
+ * @param {string} configName - the key to get from the config.
+ * @param {any} defaultValue - default value if config doesn't have passed key
+ * @param {object} savedData - previously saved data. If passed, the key will be got from there, otherwise from the config
+ * @returns {any} - config value.
+ */
+ getConfig(configName: string, defaultValue?: any, savedData?: object): any;
+ /**
+ * On paste callback that is fired from Editor
+ *
+ * @param {PasteEvent} event - event with pasted data
+ */
+ onPaste(event: PasteEvent): void;
+}
+/**
+ * - configuration that the user can set for the table
+ */
+export type TableData = {
+ /**
+ * - number of rows in the table
+ */
+ /**
+ * - number of rows in the table
+ */
+ rows: number;
+ /**
+ * - number of columns in the table
+ */
+ /**
+ * - number of columns in the table
+ */
+ cols: number;
+};
+/**
+ * - setting for the table
+ */
+export type Tune = {
+ /**
+ * - tune name
+ */
+ /**
+ * - tune name
+ */
+ name: string;
+ /**
+ * - icon for the tune
+ */
+ /**
+ * - icon for the tune
+ */
+ icon: HTMLElement;
+ /**
+ * - default state of the tune
+ */
+ /**
+ * - default state of the tune
+ */
+ isActive: boolean;
+ /**
+ * - set tune state to the table data
+ */
+ /**
+ * - set tune state to the table data
+ */
+ setTune: void;
+};
+/**
+ * - object with the data transferred to form a table
+ */
+export type TableConfig = {
+ /**
+ * - setting to use cells of the first row as headings
+ */
+ /**
+ * - setting to use cells of the first row as headings
+ */
+ withHeading: boolean;
+ /**
+ * - two-dimensional array which contains table content
+ */
+ /**
+ * - two-dimensional array which contains table content
+ */
+ content: string[][];
+};
+export type TableConstructor = {
+ /**
+ * — previously saved data
+ */
+ /**
+ * — previously saved data
+ */
+ data: TableConfig;
+ /**
+ * - user config for Tool
+ */
+ /**
+ * - user config for Tool
+ */
+ config: TableConfig;
+ /**
+ * - Editor.js API
+ */
+ /**
+ * - Editor.js API
+ */
+ api: object;
+ /**
+ * - read-only mode flag
+ */
+ /**
+ * - read-only mode flag
+ */
+ readOnly: boolean;
+};
+export type PasteEvent = any;
diff --git a/dist/table.d.ts b/dist/table.d.ts
new file mode 100644
index 0000000..dac362e
--- /dev/null
+++ b/dist/table.d.ts
@@ -0,0 +1,368 @@
+import { default as Toolbox } from './toolbox';
+/**
+ * @typedef {object} TableConfig
+ * @description Tool's config from Editor
+ * @property {boolean} withHeadings — Uses the first line as headings
+ * @property {string[][]} withHeadings — two-dimensional array with table contents
+ */
+/**
+ * @typedef {object} TableData - object with the data transferred to form a table
+ * @property {number} rows - number of rows in the table
+ * @property {number} cols - number of columns in the table
+ */
+/**
+ * Generates and manages table contents.
+ */
+export default class Table {
+ /**
+ * Creates
+ *
+ * @constructor
+ * @param {boolean} readOnly - read-only mode flag
+ * @param {object} api - Editor.js API
+ * @param {TableData} data - Editor.js API
+ * @param {TableConfig} config - Editor.js API
+ */
+ constructor(readOnly: boolean, api: object, data: TableData, config: TableConfig);
+ readOnly: boolean;
+ api: any;
+ data: TableData;
+ config: any;
+ /**
+ * DOM nodes
+ */
+ wrapper: Element;
+ table: Element;
+ /**
+ * Toolbox for managing of columns
+ */
+ toolboxColumn: Toolbox;
+ toolboxRow: Toolbox;
+ hoveredRow: number;
+ hoveredColumn: number;
+ selectedRow: number;
+ selectedColumn: number;
+ tunes: {
+ withHeadings: boolean;
+ };
+ /**
+ * The cell in which the focus is currently located, if 0 and 0 then there is no focus
+ * Uses to switch between cells with buttons
+ */
+ focusedCell: {
+ row: number;
+ column: number;
+ };
+ /**
+ * Global click listener allows to delegate clicks on some elements
+ */
+ documentClicked: (event: any) => void;
+ /**
+ * Returns the rendered table wrapper
+ *
+ * @returns {Element}
+ */
+ getWrapper(): Element;
+ /**
+ * Hangs the necessary handlers to events
+ */
+ bindEvents(): void;
+ /**
+ * Configures and creates the toolbox for manipulating with columns
+ *
+ * @returns {Toolbox}
+ */
+ createColumnToolbox(): Toolbox;
+ /**
+ * Configures and creates the toolbox for manipulating with rows
+ *
+ * @returns {Toolbox}
+ */
+ createRowToolbox(): Toolbox;
+ /**
+ * When you press enter it moves the cursor down to the next row
+ * or creates it if the click occurred on the last one
+ */
+ moveCursorToNextRow(): void;
+ /**
+ * Get table cell by row and col index
+ *
+ * @param {number} row - cell row coordinate
+ * @param {number} column - cell column coordinate
+ * @returns {HTMLElement}
+ */
+ getCell(row: number, column: number): HTMLElement;
+ /**
+ * Get table row by index
+ *
+ * @param {number} row - row coordinate
+ * @returns {HTMLElement}
+ */
+ getRow(row: number): HTMLElement;
+ /**
+ * The parent of the cell which is the row
+ *
+ * @param {HTMLElement} cell - cell element
+ * @returns {HTMLElement}
+ */
+ getRowByCell(cell: HTMLElement): HTMLElement;
+ /**
+ * Ger row's first cell
+ *
+ * @param {Element} row - row to find its first cell
+ * @returns {Element}
+ */
+ getRowFirstCell(row: Element): Element;
+ /**
+ * Set the sell's content by row and column numbers
+ *
+ * @param {number} row - cell row coordinate
+ * @param {number} column - cell column coordinate
+ * @param {string} content - cell HTML content
+ */
+ setCellContent(row: number, column: number, content: string): void;
+ /**
+ * Add column in table on index place
+ * Add cells in each row
+ *
+ * @param {number} columnIndex - number in the array of columns, where new column to insert, -1 if insert at the end
+ * @param {boolean} [setFocus] - pass true to focus the first cell
+ */
+ addColumn(columnIndex?: number, setFocus?: boolean): void;
+ /**
+ * Add row in table on index place
+ *
+ * @param {number} index - number in the array of rows, where new column to insert, -1 if insert at the end
+ * @param {boolean} [setFocus] - pass true to focus the inserted row
+ * @returns {HTMLElement} row
+ */
+ addRow(index?: number, setFocus?: boolean): HTMLElement;
+ /**
+ * Delete a column by index
+ *
+ * @param {number} index
+ */
+ deleteColumn(index: number): void;
+ /**
+ * Delete a row by index
+ *
+ * @param {number} index
+ */
+ deleteRow(index: number): void;
+ /**
+ * Create a wrapper containing a table, toolboxes
+ * and buttons for adding rows and columns
+ *
+ * @returns {HTMLElement} wrapper - where all buttons for a table and the table itself will be
+ */
+ createTableWrapper(): HTMLElement;
+ /**
+ * Returns the size of the table based on initial data or config "size" property
+ *
+ * @return {{rows: number, cols: number}} - number of cols and rows
+ */
+ computeInitialSize(): {
+ rows: number;
+ cols: number;
+ };
+ /**
+ * Resize table to match config size or transmitted data size
+ *
+ * @return {{rows: number, cols: number}} - number of cols and rows
+ */
+ resize(): {
+ rows: number;
+ cols: number;
+ };
+ /**
+ * Fills the table with data passed to the constructor
+ *
+ * @returns {void}
+ */
+ fill(): void;
+ /**
+ * Fills a row with cells
+ *
+ * @param {HTMLElement} row - row to fill
+ * @param {number} numberOfColumns - how many cells should be in a row
+ */
+ fillRow(row: HTMLElement, numberOfColumns: number): void;
+ /**
+ * Creating a cell element
+ *
+ * @return {Element}
+ */
+ createCell(): Element;
+ /**
+ * Get number of rows in the table
+ */
+ get numberOfRows(): number;
+ /**
+ * Get number of columns in the table
+ */
+ get numberOfColumns(): number;
+ /**
+ * Is the column toolbox menu displayed or not
+ *
+ * @returns {boolean}
+ */
+ get isColumnMenuShowing(): boolean;
+ /**
+ * Is the row toolbox menu displayed or not
+ *
+ * @returns {boolean}
+ */
+ get isRowMenuShowing(): boolean;
+ /**
+ * Recalculate position of toolbox icons
+ *
+ * @param {Event} event - mouse move event
+ */
+ onMouseMoveInTable(event: Event): void;
+ /**
+ * Prevents default Enter behaviors
+ * Adds Shift+Enter processing
+ *
+ * @param {KeyboardEvent} event - keypress event
+ */
+ onKeyPressListener(event: KeyboardEvent): boolean;
+ /**
+ * Prevents tab keydown event from bubbling
+ * so that it only works inside the table
+ *
+ * @param {KeyboardEvent} event - keydown event
+ */
+ onKeyDownListener(event: KeyboardEvent): void;
+ /**
+ * Set the coordinates of the cell that the focus has moved to
+ *
+ * @param {FocusEvent} event - focusin event
+ */
+ focusInTableListener(event: FocusEvent): void;
+ /**
+ * Unselect row/column
+ * Close toolbox menu
+ * Hide toolboxes
+ *
+ * @returns {void}
+ */
+ hideToolboxes(): void;
+ /**
+ * Unselect row, close toolbox
+ *
+ * @returns {void}
+ */
+ hideRowToolbox(): void;
+ /**
+ * Unselect column, close toolbox
+ *
+ * @returns {void}
+ */
+ hideColumnToolbox(): void;
+ /**
+ * Set the cursor focus to the focused cell
+ *
+ * @returns {void}
+ */
+ focusCell(): void;
+ /**
+ * Get current focused element
+ *
+ * @returns {HTMLElement} - focused cell
+ */
+ get focusedCellElem(): HTMLElement;
+ /**
+ * Update toolboxes position
+ *
+ * @param {number} row - hovered row
+ * @param {number} column - hovered column
+ */
+ updateToolboxesPosition(row?: number, column?: number): void;
+ /**
+ * Makes the first row headings
+ *
+ * @param {boolean} withHeadings - use headings row or not
+ */
+ setHeadingsSetting(withHeadings: boolean): void;
+ /**
+ * Adds an attribute for displaying the placeholder in the cell
+ */
+ addHeadingAttrToFirstRow(): void;
+ /**
+ * Removes an attribute for displaying the placeholder in the cell
+ */
+ removeHeadingAttrFromFirstRow(): void;
+ /**
+ * Add effect of a selected row
+ *
+ * @param {number} index
+ */
+ selectRow(index: number): void;
+ /**
+ * Remove effect of a selected row
+ */
+ unselectRow(): void;
+ /**
+ * Add effect of a selected column
+ *
+ * @param {number} index
+ */
+ selectColumn(index: number): void;
+ /**
+ * Remove effect of a selected column
+ */
+ unselectColumn(): void;
+ /**
+ * Calculates the row and column that the cursor is currently hovering over
+ * The search was optimized from O(n) to O (log n) via bin search to reduce the number of calculations
+ *
+ * @param {Event} event - mousemove event
+ * @returns hovered cell coordinates as an integer row and column
+ */
+ getHoveredCell(event: Event): {
+ row: number;
+ column: number;
+ };
+ /**
+ * Looks for the index of the cell the mouse is hovering over.
+ * Cells can be represented as ordered intervals with left and
+ * right (upper and lower for rows) borders inside the table, if the mouse enters it, then this is our index
+ *
+ * @param {number} numberOfCells - upper bound of binary search
+ * @param {function} getCell - function to take the currently viewed cell
+ * @param {function} beforeTheLeftBorder - determines the cursor position, to the left of the cell or not
+ * @param {function} afterTheRightBorder - determines the cursor position, to the right of the cell or not
+ * @returns {number}
+ */
+ binSearch(numberOfCells: number, getCell: Function, beforeTheLeftBorder: Function, afterTheRightBorder: Function): number;
+ /**
+ * Collects data from cells into a two-dimensional array
+ *
+ * @returns {string[][]}
+ */
+ getData(): string[][];
+ /**
+ * Remove listeners on the document
+ */
+ destroy(): void;
+}
+export type TableConfig = object;
+/**
+ * - object with the data transferred to form a table
+ */
+export type TableData = {
+ /**
+ * - number of rows in the table
+ */
+ /**
+ * - number of rows in the table
+ */
+ rows: number;
+ /**
+ * - number of columns in the table
+ */
+ /**
+ * - number of columns in the table
+ */
+ cols: number;
+};
diff --git a/dist/table.mjs b/dist/table.mjs
new file mode 100644
index 0000000..0ad96e6
--- /dev/null
+++ b/dist/table.mjs
@@ -0,0 +1,1024 @@
+(function(){var r;"use strict";try{if(typeof document<"u"){var o=document.createElement("style");o.nonce=(r=document.head.querySelector("meta[property=csp-nonce]"))==null?void 0:r.content,o.appendChild(document.createTextNode('.tc-wrap{--color-background:#f9f9fb;--color-text-secondary:#7b7e89;--color-border:#e8e8eb;--cell-size:34px;--toolbox-icon-size:18px;--toolbox-padding:6px;--toolbox-aiming-field-size:calc(var(--toolbox-icon-size) + var(--toolbox-padding)*2);border-left:0;box-sizing:border-box;display:grid;grid-template-columns:calc(100% - var(--cell-size)) var(--cell-size);height:100%;margin-top:var(--toolbox-icon-size);position:relative;width:100%}.tc-wrap--readonly{grid-template-columns:100% var(--cell-size)}.tc-wrap svg{vertical-align:top}@media print{.tc-wrap{border-left:1px solid var(--color-border);grid-template-columns:100% var(--cell-size)}.tc-wrap .tc-row:after{display:none}}.tc-table{border-top:1px solid var(--color-border);display:grid;font-size:14px;height:100%;line-height:1.4;position:relative;width:100%}.tc-table:after{content:"";height:100%;left:calc(var(--cell-size)*-1);position:absolute;top:0;width:calc(var(--cell-size))}.tc-table:before{content:"";height:var(--toolbox-aiming-field-size);left:0;position:absolute;top:calc(var(--toolbox-aiming-field-size)*-1);width:100%}.tc-table--heading .tc-row:first-child{border-bottom:2px solid var(--color-border);font-weight:600}.tc-table--heading .tc-row:first-child [contenteditable]:empty:before{color:var(--color-text-secondary);content:attr(heading)}.tc-table--heading .tc-row:first-child:after{border-bottom:2px solid var(--color-border);bottom:-2px}.tc-add-column,.tc-add-row{color:var(--color-text-secondary);display:flex}@media print{.tc-add{display:none}}.tc-add-column{border-top:1px solid var(--color-border);justify-content:center;padding:4px 0}.tc-add-column--disabled{visibility:hidden}@media print{.tc-add-column{display:none}}.tc-add-row{align-items:center;height:var(--cell-size);padding-left:4px;position:relative}.tc-add-row--disabled{display:none}.tc-add-row:before{content:"";height:100%;position:absolute;right:calc(var(--cell-size)*-1);width:var(--cell-size)}@media print{.tc-add-row{display:none}}.tc-add-column,.tc-add-row{cursor:pointer;transition:0s;will-change:background-color}.tc-add-column:hover,.tc-add-row:hover{background-color:var(--color-background);transition:background-color .1s ease}.tc-add-row{margin-top:1px}.tc-add-row:hover:before{background-color:var(--color-background);transition:.1s}.tc-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(10px,1fr));position:relative}.tc-row,.tc-row:after{border-bottom:1px solid var(--color-border)}.tc-row:after{bottom:-1px;content:"";height:100%;pointer-events:none;position:absolute;right:calc(var(--cell-size)*-1);width:var(--cell-size)}.tc-row--selected,.tc-row--selected:after{background:var(--color-background)}.tc-cell{border-right:1px solid var(--color-border);line-break:normal;outline:none;overflow:hidden;padding:6px 12px}.tc-cell--selected{background:var(--color-background)}.tc-wrap--readonly .tc-row:after{display:none}.tc-toolbox{--toolbox-padding:6px;--popover-margin:30px;--toggler-click-zone-size:30px;--toggler-dots-color:#7b7e89;--toggler-dots-color-hovered:#1d202b;cursor:pointer;opacity:0;position:absolute;transition:opacity .1s;will-change:left,opacity;z-index:1}.tc-toolbox--column{top:calc((var(--toggler-click-zone-size))*-1);transform:translate(calc(var(--toggler-click-zone-size)*-1/2));will-change:left,opacity}.tc-toolbox--row{left:calc(var(--popover-margin)*-1);margin-top:-1px;transform:translateY(calc(var(--toggler-click-zone-size)*-1/2));will-change:top,opacity}.tc-toolbox--showed{opacity:1}.tc-toolbox .tc-popover{position:absolute}.tc-toolbox__toggler{align-items:center;color:var(--toggler-dots-color);display:flex;height:var(--toggler-click-zone-size);justify-content:center;opacity:0;transition:opacity .15s ease;width:var(--toggler-click-zone-size);will-change:opacity}.tc-toolbox__toggler:hover{color:var(--toggler-dots-color-hovered)}.tc-toolbox__toggler svg{fill:currentColor}.tc-wrap:hover .tc-toolbox__toggler{opacity:1}.tc-settings .cdx-settings-button{margin:0;width:50%}.tc-popover{--color-border:#eaeaea;--color-background:#fff;--color-background-hover:hsla(240,7%,92%,.49);--color-background-confirm:#e24a4a;--color-background-confirm-hover:#d54040;--color-text-confirm:#fff;background:var(--color-background);border:1px solid var(--color-border);border-radius:6px;box-shadow:0 3px 15px -3px #0d142121;display:none;padding:6px;will-change:opacity,transform}.tc-popover--opened{animation:menuShowing .1s cubic-bezier(.215,.61,.355,1) forwards;display:block}.tc-popover__item{align-items:center;border-radius:5px;cursor:pointer;display:flex;padding:2px 14px 2px 2px;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.tc-popover__item:hover{background:var(--color-background-hover)}.tc-popover__item:not(:last-of-type){margin-bottom:2px}.tc-popover__item-icon{align-items:center;background:var(--color-background);border:1px solid var(--color-border);border-radius:5px;display:inline-flex;height:26px;justify-content:center;margin-right:8px;width:26px}.tc-popover__item-label{font-size:14px;font-weight:500;line-height:22px}.tc-popover__item--confirm{background:var(--color-background-confirm);color:var(--color-text-confirm)}.tc-popover__item--confirm:hover{background-color:var(--color-background-confirm-hover)}.tc-popover__item--confirm .tc-popover__item-icon{background:var(--color-background-confirm);border-color:#0000001a}.tc-popover__item--confirm .tc-popover__item-icon svg{transform:rotate(90deg) scale(1.2);transition:transform .2s ease-in}.tc-popover__item--hidden{display:none}@keyframes menuShowing{0%{opacity:0;transform:translateY(-8px) scale(.9)}70%{opacity:1;transform:translateY(2px)}to{transform:translateY(0)}}')),document.head.appendChild(o)}}catch(e){console.error("vite-plugin-css-injected-by-js",e)}})();
+function c(d, t, e = {}) {
+ const o = document.createElement(d);
+ Array.isArray(t) ? o.classList.add(...t) : t && o.classList.add(t);
+ for (const i in e)
+ Object.prototype.hasOwnProperty.call(e, i) && (o[i] = e[i]);
+ return o;
+}
+function f(d) {
+ const t = d.getBoundingClientRect();
+ return {
+ y1: Math.floor(t.top + window.pageYOffset),
+ x1: Math.floor(t.left + window.pageXOffset),
+ x2: Math.floor(t.right + window.pageXOffset),
+ y2: Math.floor(t.bottom + window.pageYOffset)
+ };
+}
+function m(d, t) {
+ const e = f(d), o = f(t);
+ return {
+ fromTopBorder: o.y1 - e.y1,
+ fromLeftBorder: o.x1 - e.x1,
+ fromRightBorder: e.x2 - o.x2,
+ fromBottomBorder: e.y2 - o.y2
+ };
+}
+function R(d, t) {
+ const e = d.getBoundingClientRect(), { width: o, height: i, x: n, y: r } = e, { clientX: h, clientY: l } = t;
+ return {
+ width: o,
+ height: i,
+ x: h - n,
+ y: l - r
+ };
+}
+function g(d, t) {
+ return t.parentNode.insertBefore(d, t);
+}
+function C(d, t = !0) {
+ const e = document.createRange(), o = window.getSelection();
+ e.selectNodeContents(d), e.collapse(t), o.removeAllRanges(), o.addRange(e);
+}
+class a {
+ /**
+ * @param {object} options - constructor options
+ * @param {PopoverItem[]} options.items - constructor options
+ */
+ constructor({ items: t }) {
+ this.items = t, this.wrapper = void 0, this.itemEls = [];
+ }
+ /**
+ * Set of CSS classnames used in popover
+ *
+ * @returns {object}
+ */
+ static get CSS() {
+ return {
+ popover: "tc-popover",
+ popoverOpened: "tc-popover--opened",
+ item: "tc-popover__item",
+ itemHidden: "tc-popover__item--hidden",
+ itemConfirmState: "tc-popover__item--confirm",
+ itemIcon: "tc-popover__item-icon",
+ itemLabel: "tc-popover__item-label"
+ };
+ }
+ /**
+ * Returns the popover element
+ *
+ * @returns {Element}
+ */
+ render() {
+ return this.wrapper = c("div", a.CSS.popover), this.items.forEach((t, e) => {
+ const o = c("div", a.CSS.item), i = c("div", a.CSS.itemIcon, {
+ innerHTML: t.icon
+ }), n = c("div", a.CSS.itemLabel, {
+ textContent: t.label
+ });
+ o.dataset.index = e, o.appendChild(i), o.appendChild(n), this.wrapper.appendChild(o), this.itemEls.push(o);
+ }), this.wrapper.addEventListener("click", (t) => {
+ this.popoverClicked(t);
+ }), this.wrapper;
+ }
+ /**
+ * Popover wrapper click listener
+ * Used to delegate clicks in items
+ *
+ * @returns {void}
+ */
+ popoverClicked(t) {
+ const e = t.target.closest(`.${a.CSS.item}`);
+ if (!e)
+ return;
+ const o = e.dataset.index, i = this.items[o];
+ if (i.confirmationRequired && !this.hasConfirmationState(e)) {
+ this.setConfirmationState(e);
+ return;
+ }
+ i.onClick();
+ }
+ /**
+ * Enable the confirmation state on passed item
+ *
+ * @returns {void}
+ */
+ setConfirmationState(t) {
+ t.classList.add(a.CSS.itemConfirmState);
+ }
+ /**
+ * Disable the confirmation state on passed item
+ *
+ * @returns {void}
+ */
+ clearConfirmationState(t) {
+ t.classList.remove(a.CSS.itemConfirmState);
+ }
+ /**
+ * Check if passed item has the confirmation state
+ *
+ * @returns {boolean}
+ */
+ hasConfirmationState(t) {
+ return t.classList.contains(a.CSS.itemConfirmState);
+ }
+ /**
+ * Return an opening state
+ *
+ * @returns {boolean}
+ */
+ get opened() {
+ return this.wrapper.classList.contains(a.CSS.popoverOpened);
+ }
+ /**
+ * Opens the popover
+ *
+ * @returns {void}
+ */
+ open() {
+ this.items.forEach((t, e) => {
+ typeof t.hideIf == "function" && this.itemEls[e].classList.toggle(a.CSS.itemHidden, t.hideIf());
+ }), this.wrapper.classList.add(a.CSS.popoverOpened);
+ }
+ /**
+ * Closes the popover
+ *
+ * @returns {void}
+ */
+ close() {
+ this.wrapper.classList.remove(a.CSS.popoverOpened), this.itemEls.forEach((t) => {
+ this.clearConfirmationState(t);
+ });
+ }
+}
+const k = '', b = '', x = '', S = '', y = '', O = '', M = '', v = '', L = '', T = '', H = '', A = '';
+class w {
+ /**
+ * Creates toolbox buttons and toolbox menus
+ *
+ * @param {Object} config
+ * @param {any} config.api - Editor.js api
+ * @param {PopoverItem[]} config.items - Editor.js api
+ * @param {function} config.onOpen - callback fired when the Popover is opening
+ * @param {function} config.onClose - callback fired when the Popover is closing
+ * @param {string} config.cssModifier - the modifier for the Toolbox. Allows to add some specific styles.
+ */
+ constructor({ api: t, items: e, onOpen: o, onClose: i, cssModifier: n = "" }) {
+ this.api = t, this.items = e, this.onOpen = o, this.onClose = i, this.cssModifier = n, this.popover = null, this.wrapper = this.createToolbox(), this.numberOfColumns = 0, this.numberOfRows = 0, this.currentColumn = 0, this.currentRow = 0;
+ }
+ /**
+ * Style classes
+ */
+ static get CSS() {
+ return {
+ toolbox: "tc-toolbox",
+ toolboxShowed: "tc-toolbox--showed",
+ toggler: "tc-toolbox__toggler"
+ };
+ }
+ /**
+ * Returns rendered Toolbox element
+ */
+ get element() {
+ return this.wrapper;
+ }
+ /**
+ * Creating a toolbox to open menu for a manipulating columns
+ *
+ * @returns {Element}
+ */
+ createToolbox() {
+ const t = c("div", [
+ w.CSS.toolbox,
+ this.cssModifier ? `${w.CSS.toolbox}--${this.cssModifier}` : ""
+ ]);
+ t.dataset.mutationFree = "true";
+ const e = this.createPopover(), o = this.createToggler();
+ return t.appendChild(o), t.appendChild(e), t;
+ }
+ /**
+ * Creates the Toggler
+ *
+ * @returns {Element}
+ */
+ createToggler() {
+ const t = c("div", w.CSS.toggler, {
+ innerHTML: M
+ });
+ return t.addEventListener("click", () => {
+ this.togglerClicked();
+ }), t;
+ }
+ /**
+ * Creates the Popover instance and render it
+ *
+ * @returns {Element}
+ */
+ createPopover() {
+ return this.popover = new a({
+ items: this.items
+ }), this.popover.render();
+ }
+ /**
+ * Toggler click handler. Opens/Closes the popover
+ *
+ * @returns {void}
+ */
+ togglerClicked() {
+ let t = {};
+ console.log(this.currentColumn, Math.ceil(this.numberOfColumns / 2)), this.currentColumn > Math.ceil(this.numberOfColumns / 2) ? (t.right = "var(--popover-margin)", t.left = "auto") : (t.left = "var(--popover-margin)", t.right = "auto"), this.currentRow > Math.ceil(this.numberOfRows / 2) ? (t.bottom = 0, t.top = "auto") : (t.top = 0, t.bottom = "auto"), Object.entries(t).forEach(([e, o]) => {
+ this.popover.wrapper.style[e] = o;
+ }), this.popover.opened ? (this.popover.close(), this.onClose()) : (this.popover.open(), this.onOpen());
+ }
+ /**
+ * Shows the Toolbox
+ *
+ * @param {function} computePositionMethod - method that returns the position coordinate
+ * @returns {void}
+ */
+ show(t) {
+ const e = t();
+ Object.entries(e.style).forEach(([o, i]) => {
+ this.wrapper.style[o] = i;
+ }), console.log(e, this.cssModifier), this.cssModifier == "row" ? (this.numberOfRows = e.numberOfRows, this.currentRow = e.currentRow) : this.cssModifier == "column" && (this.numberOfColumns = e.numberOfColumns, this.currentColumn = e.currentColumn), this.wrapper.classList.add(w.CSS.toolboxShowed);
+ }
+ /**
+ * Hides the Toolbox
+ *
+ * @returns {void}
+ */
+ hide() {
+ this.popover.close(), this.wrapper.classList.remove(w.CSS.toolboxShowed);
+ }
+}
+function B(d, t) {
+ let e = 0;
+ return function(...o) {
+ const i = (/* @__PURE__ */ new Date()).getTime();
+ if (!(i - e < d))
+ return e = i, t(...o);
+ };
+}
+const s = {
+ wrapper: "tc-wrap",
+ wrapperReadOnly: "tc-wrap--readonly",
+ table: "tc-table",
+ row: "tc-row",
+ withHeadings: "tc-table--heading",
+ rowSelected: "tc-row--selected",
+ cell: "tc-cell",
+ cellSelected: "tc-cell--selected",
+ addRow: "tc-add-row",
+ addRowDisabled: "tc-add-row--disabled",
+ addColumn: "tc-add-column",
+ addColumnDisabled: "tc-add-column--disabled"
+};
+class E {
+ /**
+ * Creates
+ *
+ * @constructor
+ * @param {boolean} readOnly - read-only mode flag
+ * @param {object} api - Editor.js API
+ * @param {TableData} data - Editor.js API
+ * @param {TableConfig} config - Editor.js API
+ */
+ constructor(t, e, o, i) {
+ this.readOnly = t, this.api = e, this.data = o, this.config = i, this.wrapper = null, this.table = null, this.toolboxColumn = this.createColumnToolbox(), this.toolboxRow = this.createRowToolbox(), this.createTableWrapper(), this.hoveredRow = 0, this.hoveredColumn = 0, this.selectedRow = 0, this.selectedColumn = 0, this.tunes = {
+ withHeadings: !1
+ }, this.resize(), this.fill(), this.focusedCell = {
+ row: 0,
+ column: 0
+ }, this.documentClicked = (n) => {
+ const r = n.target.closest(`.${s.table}`) !== null, h = n.target.closest(`.${s.wrapper}`) === null;
+ (r || h) && this.hideToolboxes();
+ const u = n.target.closest(`.${s.addRow}`), p = n.target.closest(`.${s.addColumn}`);
+ u && u.parentNode === this.wrapper ? (this.addRow(void 0, !0), this.hideToolboxes()) : p && p.parentNode === this.wrapper && (this.addColumn(void 0, !0), this.hideToolboxes());
+ }, this.readOnly || this.bindEvents();
+ }
+ /**
+ * Returns the rendered table wrapper
+ *
+ * @returns {Element}
+ */
+ getWrapper() {
+ return this.wrapper;
+ }
+ /**
+ * Hangs the necessary handlers to events
+ */
+ bindEvents() {
+ document.addEventListener("click", this.documentClicked), this.table.addEventListener("mousemove", B(150, (t) => this.onMouseMoveInTable(t)), { passive: !0 }), this.table.onkeypress = (t) => this.onKeyPressListener(t), this.table.addEventListener("keydown", (t) => this.onKeyDownListener(t)), this.table.addEventListener("focusin", (t) => this.focusInTableListener(t));
+ }
+ /**
+ * Configures and creates the toolbox for manipulating with columns
+ *
+ * @returns {Toolbox}
+ */
+ createColumnToolbox() {
+ return new w({
+ api: this.api,
+ cssModifier: "column",
+ items: [
+ {
+ label: this.api.i18n.t("Add column to left"),
+ icon: S,
+ hideIf: () => this.numberOfColumns === this.config.maxcols,
+ onClick: () => {
+ this.addColumn(this.selectedColumn, !0), this.hideToolboxes();
+ }
+ },
+ {
+ label: this.api.i18n.t("Add column to right"),
+ icon: y,
+ hideIf: () => this.numberOfColumns === this.config.maxcols,
+ onClick: () => {
+ this.addColumn(this.selectedColumn + 1, !0), this.hideToolboxes();
+ }
+ },
+ {
+ label: this.api.i18n.t("Delete column"),
+ icon: b,
+ hideIf: () => this.numberOfColumns === 1,
+ confirmationRequired: !0,
+ onClick: () => {
+ this.deleteColumn(this.selectedColumn), this.hideToolboxes();
+ }
+ }
+ ],
+ onOpen: () => {
+ this.selectColumn(this.hoveredColumn), this.hideRowToolbox();
+ },
+ onClose: () => {
+ this.unselectColumn();
+ }
+ });
+ }
+ /**
+ * Configures and creates the toolbox for manipulating with rows
+ *
+ * @returns {Toolbox}
+ */
+ createRowToolbox() {
+ return new w({
+ api: this.api,
+ cssModifier: "row",
+ items: [
+ {
+ label: this.api.i18n.t("Add row above"),
+ icon: O,
+ hideIf: () => this.numberOfRows === this.config.maxrows,
+ onClick: () => {
+ this.addRow(this.selectedRow, !0), this.hideToolboxes();
+ }
+ },
+ {
+ label: this.api.i18n.t("Add row below"),
+ icon: x,
+ hideIf: () => this.numberOfRows === this.config.maxrows,
+ onClick: () => {
+ this.addRow(this.selectedRow + 1, !0), this.hideToolboxes();
+ }
+ },
+ {
+ label: this.api.i18n.t("Delete row"),
+ icon: b,
+ hideIf: () => this.numberOfRows === 1,
+ confirmationRequired: !0,
+ onClick: () => {
+ this.deleteRow(this.selectedRow), this.hideToolboxes();
+ }
+ }
+ ],
+ onOpen: () => {
+ this.selectRow(this.hoveredRow), this.hideColumnToolbox();
+ },
+ onClose: () => {
+ this.unselectRow();
+ }
+ });
+ }
+ /**
+ * When you press enter it moves the cursor down to the next row
+ * or creates it if the click occurred on the last one
+ */
+ moveCursorToNextRow() {
+ this.focusedCell.row !== this.numberOfRows ? (this.focusedCell.row += 1, this.focusCell(this.focusedCell)) : (this.addRow(), this.focusedCell.row += 1, this.focusCell(this.focusedCell), this.updateToolboxesPosition(0, 0));
+ }
+ /**
+ * Get table cell by row and col index
+ *
+ * @param {number} row - cell row coordinate
+ * @param {number} column - cell column coordinate
+ * @returns {HTMLElement}
+ */
+ getCell(t, e) {
+ return this.table.querySelectorAll(`.${s.row}:nth-child(${t}) .${s.cell}`)[e - 1];
+ }
+ /**
+ * Get table row by index
+ *
+ * @param {number} row - row coordinate
+ * @returns {HTMLElement}
+ */
+ getRow(t) {
+ return this.table.querySelector(`.${s.row}:nth-child(${t})`);
+ }
+ /**
+ * The parent of the cell which is the row
+ *
+ * @param {HTMLElement} cell - cell element
+ * @returns {HTMLElement}
+ */
+ getRowByCell(t) {
+ return t.parentElement;
+ }
+ /**
+ * Ger row's first cell
+ *
+ * @param {Element} row - row to find its first cell
+ * @returns {Element}
+ */
+ getRowFirstCell(t) {
+ return t.querySelector(`.${s.cell}:first-child`);
+ }
+ /**
+ * Set the sell's content by row and column numbers
+ *
+ * @param {number} row - cell row coordinate
+ * @param {number} column - cell column coordinate
+ * @param {string} content - cell HTML content
+ */
+ setCellContent(t, e, o) {
+ const i = this.getCell(t, e);
+ i.innerHTML = o;
+ }
+ /**
+ * Add column in table on index place
+ * Add cells in each row
+ *
+ * @param {number} columnIndex - number in the array of columns, where new column to insert, -1 if insert at the end
+ * @param {boolean} [setFocus] - pass true to focus the first cell
+ */
+ addColumn(t = -1, e = !1) {
+ var n;
+ let o = this.numberOfColumns;
+ if (this.config && this.config.maxcols && this.numberOfColumns >= this.config.maxcols)
+ return;
+ for (let r = 1; r <= this.numberOfRows; r++) {
+ let h;
+ const l = this.createCell();
+ if (t > 0 && t <= o ? (h = this.getCell(r, t), g(l, h)) : h = this.getRow(r).appendChild(l), r === 1) {
+ const u = this.getCell(r, t > 0 ? t : o + 1);
+ u && e && C(u);
+ }
+ }
+ const i = this.wrapper.querySelector(`.${s.addColumn}`);
+ (n = this.config) != null && n.maxcols && this.numberOfColumns > this.config.maxcols - 1 && i && i.classList.add(s.addColumnDisabled), this.addHeadingAttrToFirstRow();
+ }
+ /**
+ * Add row in table on index place
+ *
+ * @param {number} index - number in the array of rows, where new column to insert, -1 if insert at the end
+ * @param {boolean} [setFocus] - pass true to focus the inserted row
+ * @returns {HTMLElement} row
+ */
+ addRow(t = -1, e = !1) {
+ let o, i = c("div", s.row);
+ this.tunes.withHeadings && this.removeHeadingAttrFromFirstRow();
+ let n = this.numberOfColumns;
+ if (this.config && this.config.maxrows && this.numberOfRows >= this.config.maxrows && h)
+ return;
+ if (t > 0 && t <= this.numberOfRows) {
+ let l = this.getRow(t);
+ o = g(i, l);
+ } else
+ o = this.table.appendChild(i);
+ this.fillRow(o, n), this.tunes.withHeadings && this.addHeadingAttrToFirstRow();
+ const r = this.getRowFirstCell(o);
+ r && e && C(r);
+ const h = this.wrapper.querySelector(`.${s.addRow}`);
+ return this.config && this.config.maxrows && this.numberOfRows >= this.config.maxrows && h && h.classList.add(s.addRowDisabled), o;
+ }
+ /**
+ * Delete a column by index
+ *
+ * @param {number} index
+ */
+ deleteColumn(t) {
+ for (let o = 1; o <= this.numberOfRows; o++) {
+ const i = this.getCell(o, t);
+ if (!i)
+ return;
+ i.remove();
+ }
+ const e = this.wrapper.querySelector(`.${s.addColumn}`);
+ e && e.classList.remove(s.addColumnDisabled);
+ }
+ /**
+ * Delete a row by index
+ *
+ * @param {number} index
+ */
+ deleteRow(t) {
+ this.getRow(t).remove();
+ const e = this.wrapper.querySelector(`.${s.addRow}`);
+ e && e.classList.remove(s.addRowDisabled), this.addHeadingAttrToFirstRow();
+ }
+ /**
+ * Create a wrapper containing a table, toolboxes
+ * and buttons for adding rows and columns
+ *
+ * @returns {HTMLElement} wrapper - where all buttons for a table and the table itself will be
+ */
+ createTableWrapper() {
+ if (this.wrapper = c("div", s.wrapper), this.table = c("div", s.table), this.readOnly && this.wrapper.classList.add(s.wrapperReadOnly), this.wrapper.appendChild(this.toolboxRow.element), this.wrapper.appendChild(this.toolboxColumn.element), this.wrapper.appendChild(this.table), !this.readOnly) {
+ const t = c("div", s.addColumn, {
+ innerHTML: v
+ }), e = c("div", s.addRow, {
+ innerHTML: v
+ });
+ this.wrapper.appendChild(t), this.wrapper.appendChild(e);
+ }
+ }
+ /**
+ * Returns the size of the table based on initial data or config "size" property
+ *
+ * @return {{rows: number, cols: number}} - number of cols and rows
+ */
+ computeInitialSize() {
+ const t = this.data && this.data.content, e = Array.isArray(t), o = e ? t.length : !1, i = e ? t.length : void 0, n = o ? t[0].length : void 0, r = Number.parseInt(this.config && this.config.rows), h = Number.parseInt(this.config && this.config.cols), l = !isNaN(r) && r > 0 ? r : void 0, u = !isNaN(h) && h > 0 ? h : void 0;
+ return {
+ rows: i || l || 2,
+ cols: n || u || 2
+ };
+ }
+ /**
+ * Resize table to match config size or transmitted data size
+ *
+ * @return {{rows: number, cols: number}} - number of cols and rows
+ */
+ resize() {
+ const { rows: t, cols: e } = this.computeInitialSize();
+ for (let o = 0; o < t; o++)
+ this.addRow();
+ for (let o = 0; o < e; o++)
+ this.addColumn();
+ }
+ /**
+ * Fills the table with data passed to the constructor
+ *
+ * @returns {void}
+ */
+ fill() {
+ const t = this.data;
+ if (t && t.content)
+ for (let e = 0; e < t.content.length; e++)
+ for (let o = 0; o < t.content[e].length; o++)
+ this.setCellContent(e + 1, o + 1, t.content[e][o]);
+ }
+ /**
+ * Fills a row with cells
+ *
+ * @param {HTMLElement} row - row to fill
+ * @param {number} numberOfColumns - how many cells should be in a row
+ */
+ fillRow(t, e) {
+ for (let o = 1; o <= e; o++) {
+ const i = this.createCell();
+ t.appendChild(i);
+ }
+ }
+ /**
+ * Creating a cell element
+ *
+ * @return {Element}
+ */
+ createCell() {
+ return c("div", s.cell, {
+ contentEditable: !this.readOnly
+ });
+ }
+ /**
+ * Get number of rows in the table
+ */
+ get numberOfRows() {
+ return this.table.childElementCount;
+ }
+ /**
+ * Get number of columns in the table
+ */
+ get numberOfColumns() {
+ return this.numberOfRows ? this.table.querySelectorAll(`.${s.row}:first-child .${s.cell}`).length : 0;
+ }
+ /**
+ * Is the column toolbox menu displayed or not
+ *
+ * @returns {boolean}
+ */
+ get isColumnMenuShowing() {
+ return this.selectedColumn !== 0;
+ }
+ /**
+ * Is the row toolbox menu displayed or not
+ *
+ * @returns {boolean}
+ */
+ get isRowMenuShowing() {
+ return this.selectedRow !== 0;
+ }
+ /**
+ * Recalculate position of toolbox icons
+ *
+ * @param {Event} event - mouse move event
+ */
+ onMouseMoveInTable(t) {
+ const { row: e, column: o } = this.getHoveredCell(t);
+ this.hoveredColumn = o, this.hoveredRow = e, this.updateToolboxesPosition();
+ }
+ /**
+ * Prevents default Enter behaviors
+ * Adds Shift+Enter processing
+ *
+ * @param {KeyboardEvent} event - keypress event
+ */
+ onKeyPressListener(t) {
+ if (t.key === "Enter") {
+ if (t.shiftKey)
+ return !0;
+ this.moveCursorToNextRow();
+ }
+ return t.key !== "Enter";
+ }
+ /**
+ * Prevents tab keydown event from bubbling
+ * so that it only works inside the table
+ *
+ * @param {KeyboardEvent} event - keydown event
+ */
+ onKeyDownListener(t) {
+ t.key === "Tab" && t.stopPropagation();
+ }
+ /**
+ * Set the coordinates of the cell that the focus has moved to
+ *
+ * @param {FocusEvent} event - focusin event
+ */
+ focusInTableListener(t) {
+ const e = t.target, o = this.getRowByCell(e);
+ this.focusedCell = {
+ row: Array.from(this.table.querySelectorAll(`.${s.row}`)).indexOf(o) + 1,
+ column: Array.from(o.querySelectorAll(`.${s.cell}`)).indexOf(e) + 1
+ };
+ }
+ /**
+ * Unselect row/column
+ * Close toolbox menu
+ * Hide toolboxes
+ *
+ * @returns {void}
+ */
+ hideToolboxes() {
+ this.hideRowToolbox(), this.hideColumnToolbox(), this.updateToolboxesPosition();
+ }
+ /**
+ * Unselect row, close toolbox
+ *
+ * @returns {void}
+ */
+ hideRowToolbox() {
+ this.unselectRow(), this.toolboxRow.hide();
+ }
+ /**
+ * Unselect column, close toolbox
+ *
+ * @returns {void}
+ */
+ hideColumnToolbox() {
+ this.unselectColumn(), this.toolboxColumn.hide();
+ }
+ /**
+ * Set the cursor focus to the focused cell
+ *
+ * @returns {void}
+ */
+ focusCell() {
+ this.focusedCellElem.focus();
+ }
+ /**
+ * Get current focused element
+ *
+ * @returns {HTMLElement} - focused cell
+ */
+ get focusedCellElem() {
+ const { row: t, column: e } = this.focusedCell;
+ return this.getCell(t, e);
+ }
+ /**
+ * Update toolboxes position
+ *
+ * @param {number} row - hovered row
+ * @param {number} column - hovered column
+ */
+ updateToolboxesPosition(t = this.hoveredRow, e = this.hoveredColumn) {
+ this.isColumnMenuShowing || e > 0 && e <= this.numberOfColumns && this.toolboxColumn.show(() => ({
+ style: {
+ left: `calc((100% - var(--cell-size)) / (${this.numberOfColumns} * 2) * (1 + (${e} - 1) * 2))`
+ },
+ numberOfColumns: this.numberOfColumns,
+ currentColumn: e
+ })), this.isRowMenuShowing || t > 0 && t <= this.numberOfRows && this.toolboxRow.show(() => {
+ const o = this.getRow(t), { fromTopBorder: i } = m(this.table, o), { height: n } = o.getBoundingClientRect();
+ return {
+ style: {
+ top: `${Math.ceil(i + n / 2)}px`
+ },
+ numberOfRows: this.numberOfRows,
+ currentRow: t
+ };
+ });
+ }
+ /**
+ * Makes the first row headings
+ *
+ * @param {boolean} withHeadings - use headings row or not
+ */
+ setHeadingsSetting(t) {
+ this.tunes.withHeadings = t, t ? (this.table.classList.add(s.withHeadings), this.addHeadingAttrToFirstRow()) : (this.table.classList.remove(s.withHeadings), this.removeHeadingAttrFromFirstRow());
+ }
+ /**
+ * Adds an attribute for displaying the placeholder in the cell
+ */
+ addHeadingAttrToFirstRow() {
+ for (let t = 1; t <= this.numberOfColumns; t++) {
+ let e = this.getCell(1, t);
+ e && e.setAttribute("heading", this.api.i18n.t("Heading"));
+ }
+ }
+ /**
+ * Removes an attribute for displaying the placeholder in the cell
+ */
+ removeHeadingAttrFromFirstRow() {
+ for (let t = 1; t <= this.numberOfColumns; t++) {
+ let e = this.getCell(1, t);
+ e && e.removeAttribute("heading");
+ }
+ }
+ /**
+ * Add effect of a selected row
+ *
+ * @param {number} index
+ */
+ selectRow(t) {
+ const e = this.getRow(t);
+ e && (this.selectedRow = t, e.classList.add(s.rowSelected));
+ }
+ /**
+ * Remove effect of a selected row
+ */
+ unselectRow() {
+ if (this.selectedRow <= 0)
+ return;
+ const t = this.table.querySelector(`.${s.rowSelected}`);
+ t && t.classList.remove(s.rowSelected), this.selectedRow = 0;
+ }
+ /**
+ * Add effect of a selected column
+ *
+ * @param {number} index
+ */
+ selectColumn(t) {
+ for (let e = 1; e <= this.numberOfRows; e++) {
+ const o = this.getCell(e, t);
+ o && o.classList.add(s.cellSelected);
+ }
+ this.selectedColumn = t;
+ }
+ /**
+ * Remove effect of a selected column
+ */
+ unselectColumn() {
+ if (this.selectedColumn <= 0)
+ return;
+ let t = this.table.querySelectorAll(`.${s.cellSelected}`);
+ Array.from(t).forEach((e) => {
+ e.classList.remove(s.cellSelected);
+ }), this.selectedColumn = 0;
+ }
+ /**
+ * Calculates the row and column that the cursor is currently hovering over
+ * The search was optimized from O(n) to O (log n) via bin search to reduce the number of calculations
+ *
+ * @param {Event} event - mousemove event
+ * @returns hovered cell coordinates as an integer row and column
+ */
+ getHoveredCell(t) {
+ let e = this.hoveredRow, o = this.hoveredColumn;
+ const { width: i, height: n, x: r, y: h } = R(this.table, t);
+ return r >= 0 && (o = this.binSearch(
+ this.numberOfColumns,
+ (l) => this.getCell(1, l),
+ ({ fromLeftBorder: l }) => r < l,
+ ({ fromRightBorder: l }) => r > i - l
+ )), h >= 0 && (e = this.binSearch(
+ this.numberOfRows,
+ (l) => this.getCell(l, 1),
+ ({ fromTopBorder: l }) => h < l,
+ ({ fromBottomBorder: l }) => h > n - l
+ )), {
+ row: e || this.hoveredRow,
+ column: o || this.hoveredColumn
+ };
+ }
+ /**
+ * Looks for the index of the cell the mouse is hovering over.
+ * Cells can be represented as ordered intervals with left and
+ * right (upper and lower for rows) borders inside the table, if the mouse enters it, then this is our index
+ *
+ * @param {number} numberOfCells - upper bound of binary search
+ * @param {function} getCell - function to take the currently viewed cell
+ * @param {function} beforeTheLeftBorder - determines the cursor position, to the left of the cell or not
+ * @param {function} afterTheRightBorder - determines the cursor position, to the right of the cell or not
+ * @returns {number}
+ */
+ binSearch(t, e, o, i) {
+ let n = 0, r = t + 1, h = 0, l;
+ for (; n < r - 1 && h < 10; ) {
+ l = Math.ceil((n + r) / 2);
+ const u = e(l), p = m(this.table, u);
+ if (o(p))
+ r = l;
+ else if (i(p))
+ n = l;
+ else
+ break;
+ h++;
+ }
+ return l;
+ }
+ /**
+ * Collects data from cells into a two-dimensional array
+ *
+ * @returns {string[][]}
+ */
+ getData() {
+ const t = [];
+ for (let e = 1; e <= this.numberOfRows; e++) {
+ const o = this.table.querySelector(`.${s.row}:nth-child(${e})`), i = Array.from(o.querySelectorAll(`.${s.cell}`));
+ i.every((r) => !r.textContent.trim()) || t.push(i.map((r) => r.innerHTML));
+ }
+ return t;
+ }
+ /**
+ * Remove listeners on the document
+ */
+ destroy() {
+ document.removeEventListener("click", this.documentClicked);
+ }
+}
+class j {
+ /**
+ * Notify core that read-only mode is supported
+ *
+ * @returns {boolean}
+ */
+ static get isReadOnlySupported() {
+ return !0;
+ }
+ /**
+ * Allow to press Enter inside the CodeTool textarea
+ *
+ * @returns {boolean}
+ * @public
+ */
+ static get enableLineBreaks() {
+ return !0;
+ }
+ /**
+ * Render plugin`s main Element and fill it with saved data
+ *
+ * @param {TableConstructor} init
+ */
+ constructor({ data: t, config: e, api: o, readOnly: i, block: n }) {
+ this.api = o, this.readOnly = i, this.config = e, this.data = {
+ withHeadings: this.getConfig("withHeadings", !1, t),
+ stretched: this.getConfig("stretched", !1, t),
+ content: t && t.content ? t.content : []
+ }, this.table = null, this.block = n;
+ }
+ /**
+ * Get Tool toolbox settings
+ * icon - Tool icon's SVG
+ * title - title to show in toolbox
+ *
+ * @returns {{icon: string, title: string}}
+ */
+ static get toolbox() {
+ return {
+ icon: A,
+ title: "Table"
+ };
+ }
+ /**
+ * Return Tool's view
+ *
+ * @returns {HTMLDivElement}
+ */
+ render() {
+ return this.table = new E(this.readOnly, this.api, this.data, this.config), this.container = c("div", this.api.styles.block), this.container.appendChild(this.table.getWrapper()), this.table.setHeadingsSetting(this.data.withHeadings), this.container;
+ }
+ /**
+ * Returns plugin settings
+ *
+ * @returns {Array}
+ */
+ renderSettings() {
+ return [
+ {
+ label: this.api.i18n.t("With headings"),
+ icon: T,
+ isActive: this.data.withHeadings,
+ closeOnActivate: !0,
+ toggle: !0,
+ onActivate: () => {
+ this.data.withHeadings = !0, this.table.setHeadingsSetting(this.data.withHeadings);
+ }
+ },
+ {
+ label: this.api.i18n.t("Without headings"),
+ icon: H,
+ isActive: !this.data.withHeadings,
+ closeOnActivate: !0,
+ toggle: !0,
+ onActivate: () => {
+ this.data.withHeadings = !1, this.table.setHeadingsSetting(this.data.withHeadings);
+ }
+ },
+ {
+ label: this.data.stretched ? this.api.i18n.t("Collapse") : this.api.i18n.t("Stretch"),
+ icon: this.data.stretched ? k : L,
+ closeOnActivate: !0,
+ toggle: !0,
+ onActivate: () => {
+ this.data.stretched = !this.data.stretched, this.block.stretched = this.data.stretched;
+ }
+ }
+ ];
+ }
+ /**
+ * Extract table data from the view
+ *
+ * @returns {TableData} - saved data
+ */
+ save() {
+ const t = this.table.getData();
+ return {
+ withHeadings: this.data.withHeadings,
+ stretched: this.data.stretched,
+ content: t
+ };
+ }
+ /**
+ * Plugin destroyer
+ *
+ * @returns {void}
+ */
+ destroy() {
+ this.table.destroy();
+ }
+ /**
+ * A helper to get config value.
+ *
+ * @param {string} configName - the key to get from the config.
+ * @param {any} defaultValue - default value if config doesn't have passed key
+ * @param {object} savedData - previously saved data. If passed, the key will be got from there, otherwise from the config
+ * @returns {any} - config value.
+ */
+ getConfig(t, e = void 0, o = void 0) {
+ const i = this.data || o;
+ return i ? i[t] ? i[t] : e : this.config && this.config[t] ? this.config[t] : e;
+ }
+ /**
+ * Table onPaste configuration
+ *
+ * @public
+ */
+ static get pasteConfig() {
+ return { tags: ["TABLE", "TR", "TH", "TD"] };
+ }
+ /**
+ * On paste callback that is fired from Editor
+ *
+ * @param {PasteEvent} event - event with pasted data
+ */
+ onPaste(t) {
+ const e = t.detail.data, o = e.querySelector(":scope > thead, tr:first-of-type th"), n = Array.from(e.querySelectorAll("tr")).map((r) => Array.from(r.querySelectorAll("th, td")).map((l) => l.innerHTML));
+ this.data = {
+ withHeadings: o !== null,
+ content: n
+ }, this.table.wrapper && this.table.wrapper.replaceWith(this.render());
+ }
+}
+export {
+ j as default
+};
diff --git a/dist/table.umd.js b/dist/table.umd.js
new file mode 100644
index 0000000..0092a65
--- /dev/null
+++ b/dist/table.umd.js
@@ -0,0 +1,2 @@
+(function(){var r;"use strict";try{if(typeof document<"u"){var o=document.createElement("style");o.nonce=(r=document.head.querySelector("meta[property=csp-nonce]"))==null?void 0:r.content,o.appendChild(document.createTextNode('.tc-wrap{--color-background:#f9f9fb;--color-text-secondary:#7b7e89;--color-border:#e8e8eb;--cell-size:34px;--toolbox-icon-size:18px;--toolbox-padding:6px;--toolbox-aiming-field-size:calc(var(--toolbox-icon-size) + var(--toolbox-padding)*2);border-left:0;box-sizing:border-box;display:grid;grid-template-columns:calc(100% - var(--cell-size)) var(--cell-size);height:100%;margin-top:var(--toolbox-icon-size);position:relative;width:100%}.tc-wrap--readonly{grid-template-columns:100% var(--cell-size)}.tc-wrap svg{vertical-align:top}@media print{.tc-wrap{border-left:1px solid var(--color-border);grid-template-columns:100% var(--cell-size)}.tc-wrap .tc-row:after{display:none}}.tc-table{border-top:1px solid var(--color-border);display:grid;font-size:14px;height:100%;line-height:1.4;position:relative;width:100%}.tc-table:after{content:"";height:100%;left:calc(var(--cell-size)*-1);position:absolute;top:0;width:calc(var(--cell-size))}.tc-table:before{content:"";height:var(--toolbox-aiming-field-size);left:0;position:absolute;top:calc(var(--toolbox-aiming-field-size)*-1);width:100%}.tc-table--heading .tc-row:first-child{border-bottom:2px solid var(--color-border);font-weight:600}.tc-table--heading .tc-row:first-child [contenteditable]:empty:before{color:var(--color-text-secondary);content:attr(heading)}.tc-table--heading .tc-row:first-child:after{border-bottom:2px solid var(--color-border);bottom:-2px}.tc-add-column,.tc-add-row{color:var(--color-text-secondary);display:flex}@media print{.tc-add{display:none}}.tc-add-column{border-top:1px solid var(--color-border);justify-content:center;padding:4px 0}.tc-add-column--disabled{visibility:hidden}@media print{.tc-add-column{display:none}}.tc-add-row{align-items:center;height:var(--cell-size);padding-left:4px;position:relative}.tc-add-row--disabled{display:none}.tc-add-row:before{content:"";height:100%;position:absolute;right:calc(var(--cell-size)*-1);width:var(--cell-size)}@media print{.tc-add-row{display:none}}.tc-add-column,.tc-add-row{cursor:pointer;transition:0s;will-change:background-color}.tc-add-column:hover,.tc-add-row:hover{background-color:var(--color-background);transition:background-color .1s ease}.tc-add-row{margin-top:1px}.tc-add-row:hover:before{background-color:var(--color-background);transition:.1s}.tc-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(10px,1fr));position:relative}.tc-row,.tc-row:after{border-bottom:1px solid var(--color-border)}.tc-row:after{bottom:-1px;content:"";height:100%;pointer-events:none;position:absolute;right:calc(var(--cell-size)*-1);width:var(--cell-size)}.tc-row--selected,.tc-row--selected:after{background:var(--color-background)}.tc-cell{border-right:1px solid var(--color-border);line-break:normal;outline:none;overflow:hidden;padding:6px 12px}.tc-cell--selected{background:var(--color-background)}.tc-wrap--readonly .tc-row:after{display:none}.tc-toolbox{--toolbox-padding:6px;--popover-margin:30px;--toggler-click-zone-size:30px;--toggler-dots-color:#7b7e89;--toggler-dots-color-hovered:#1d202b;cursor:pointer;opacity:0;position:absolute;transition:opacity .1s;will-change:left,opacity;z-index:1}.tc-toolbox--column{top:calc((var(--toggler-click-zone-size))*-1);transform:translate(calc(var(--toggler-click-zone-size)*-1/2));will-change:left,opacity}.tc-toolbox--row{left:calc(var(--popover-margin)*-1);margin-top:-1px;transform:translateY(calc(var(--toggler-click-zone-size)*-1/2));will-change:top,opacity}.tc-toolbox--showed{opacity:1}.tc-toolbox .tc-popover{position:absolute}.tc-toolbox__toggler{align-items:center;color:var(--toggler-dots-color);display:flex;height:var(--toggler-click-zone-size);justify-content:center;opacity:0;transition:opacity .15s ease;width:var(--toggler-click-zone-size);will-change:opacity}.tc-toolbox__toggler:hover{color:var(--toggler-dots-color-hovered)}.tc-toolbox__toggler svg{fill:currentColor}.tc-wrap:hover .tc-toolbox__toggler{opacity:1}.tc-settings .cdx-settings-button{margin:0;width:50%}.tc-popover{--color-border:#eaeaea;--color-background:#fff;--color-background-hover:hsla(240,7%,92%,.49);--color-background-confirm:#e24a4a;--color-background-confirm-hover:#d54040;--color-text-confirm:#fff;background:var(--color-background);border:1px solid var(--color-border);border-radius:6px;box-shadow:0 3px 15px -3px #0d142121;display:none;padding:6px;will-change:opacity,transform}.tc-popover--opened{animation:menuShowing .1s cubic-bezier(.215,.61,.355,1) forwards;display:block}.tc-popover__item{align-items:center;border-radius:5px;cursor:pointer;display:flex;padding:2px 14px 2px 2px;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.tc-popover__item:hover{background:var(--color-background-hover)}.tc-popover__item:not(:last-of-type){margin-bottom:2px}.tc-popover__item-icon{align-items:center;background:var(--color-background);border:1px solid var(--color-border);border-radius:5px;display:inline-flex;height:26px;justify-content:center;margin-right:8px;width:26px}.tc-popover__item-label{font-size:14px;font-weight:500;line-height:22px}.tc-popover__item--confirm{background:var(--color-background-confirm);color:var(--color-text-confirm)}.tc-popover__item--confirm:hover{background-color:var(--color-background-confirm-hover)}.tc-popover__item--confirm .tc-popover__item-icon{background:var(--color-background-confirm);border-color:#0000001a}.tc-popover__item--confirm .tc-popover__item-icon svg{transform:rotate(90deg) scale(1.2);transition:transform .2s ease-in}.tc-popover__item--hidden{display:none}@keyframes menuShowing{0%{opacity:0;transform:translateY(-8px) scale(.9)}70%{opacity:1;transform:translateY(2px)}to{transform:translateY(0)}}')),document.head.appendChild(o)}}catch(e){console.error("vite-plugin-css-injected-by-js",e)}})();
+(function(d,p){typeof exports=="object"&&typeof module<"u"?module.exports=p():typeof define=="function"&&define.amd?define(p):(d=typeof globalThis<"u"?globalThis:d||self,d.Table=p())})(this,function(){"use strict";function d(a,t,e={}){const o=document.createElement(a);Array.isArray(t)?o.classList.add(...t):t&&o.classList.add(t);for(const i in e)Object.prototype.hasOwnProperty.call(e,i)&&(o[i]=e[i]);return o}function p(a){const t=a.getBoundingClientRect();return{y1:Math.floor(t.top+window.pageYOffset),x1:Math.floor(t.left+window.pageXOffset),x2:Math.floor(t.right+window.pageXOffset),y2:Math.floor(t.bottom+window.pageYOffset)}}function m(a,t){const e=p(a),o=p(t);return{fromTopBorder:o.y1-e.y1,fromLeftBorder:o.x1-e.x1,fromRightBorder:e.x2-o.x2,fromBottomBorder:e.y2-o.y2}}function R(a,t){const e=a.getBoundingClientRect(),{width:o,height:i,x:n,y:r}=e,{clientX:h,clientY:l}=t;return{width:o,height:i,x:h-n,y:l-r}}function g(a,t){return t.parentNode.insertBefore(a,t)}function C(a,t=!0){const e=document.createRange(),o=window.getSelection();e.selectNodeContents(a),e.collapse(t),o.removeAllRanges(),o.addRange(e)}class c{constructor({items:t}){this.items=t,this.wrapper=void 0,this.itemEls=[]}static get CSS(){return{popover:"tc-popover",popoverOpened:"tc-popover--opened",item:"tc-popover__item",itemHidden:"tc-popover__item--hidden",itemConfirmState:"tc-popover__item--confirm",itemIcon:"tc-popover__item-icon",itemLabel:"tc-popover__item-label"}}render(){return this.wrapper=d("div",c.CSS.popover),this.items.forEach((t,e)=>{const o=d("div",c.CSS.item),i=d("div",c.CSS.itemIcon,{innerHTML:t.icon}),n=d("div",c.CSS.itemLabel,{textContent:t.label});o.dataset.index=e,o.appendChild(i),o.appendChild(n),this.wrapper.appendChild(o),this.itemEls.push(o)}),this.wrapper.addEventListener("click",t=>{this.popoverClicked(t)}),this.wrapper}popoverClicked(t){const e=t.target.closest(`.${c.CSS.item}`);if(!e)return;const o=e.dataset.index,i=this.items[o];if(i.confirmationRequired&&!this.hasConfirmationState(e)){this.setConfirmationState(e);return}i.onClick()}setConfirmationState(t){t.classList.add(c.CSS.itemConfirmState)}clearConfirmationState(t){t.classList.remove(c.CSS.itemConfirmState)}hasConfirmationState(t){return t.classList.contains(c.CSS.itemConfirmState)}get opened(){return this.wrapper.classList.contains(c.CSS.popoverOpened)}open(){this.items.forEach((t,e)=>{typeof t.hideIf=="function"&&this.itemEls[e].classList.toggle(c.CSS.itemHidden,t.hideIf())}),this.wrapper.classList.add(c.CSS.popoverOpened)}close(){this.wrapper.classList.remove(c.CSS.popoverOpened),this.itemEls.forEach(t=>{this.clearConfirmationState(t)})}}const k='',b='',x='',S='',y='',O='',M='',v='',L='',T='',H='',A='';class w{constructor({api:t,items:e,onOpen:o,onClose:i,cssModifier:n=""}){this.api=t,this.items=e,this.onOpen=o,this.onClose=i,this.cssModifier=n,this.popover=null,this.wrapper=this.createToolbox(),this.numberOfColumns=0,this.numberOfRows=0,this.currentColumn=0,this.currentRow=0}static get CSS(){return{toolbox:"tc-toolbox",toolboxShowed:"tc-toolbox--showed",toggler:"tc-toolbox__toggler"}}get element(){return this.wrapper}createToolbox(){const t=d("div",[w.CSS.toolbox,this.cssModifier?`${w.CSS.toolbox}--${this.cssModifier}`:""]);t.dataset.mutationFree="true";const e=this.createPopover(),o=this.createToggler();return t.appendChild(o),t.appendChild(e),t}createToggler(){const t=d("div",w.CSS.toggler,{innerHTML:M});return t.addEventListener("click",()=>{this.togglerClicked()}),t}createPopover(){return this.popover=new c({items:this.items}),this.popover.render()}togglerClicked(){let t={};console.log(this.currentColumn,Math.ceil(this.numberOfColumns/2)),this.currentColumn>Math.ceil(this.numberOfColumns/2)?(t.right="var(--popover-margin)",t.left="auto"):(t.left="var(--popover-margin)",t.right="auto"),this.currentRow>Math.ceil(this.numberOfRows/2)?(t.bottom=0,t.top="auto"):(t.top=0,t.bottom="auto"),Object.entries(t).forEach(([e,o])=>{this.popover.wrapper.style[e]=o}),this.popover.opened?(this.popover.close(),this.onClose()):(this.popover.open(),this.onOpen())}show(t){const e=t();Object.entries(e.style).forEach(([o,i])=>{this.wrapper.style[o]=i}),console.log(e,this.cssModifier),this.cssModifier=="row"?(this.numberOfRows=e.numberOfRows,this.currentRow=e.currentRow):this.cssModifier=="column"&&(this.numberOfColumns=e.numberOfColumns,this.currentColumn=e.currentColumn),this.wrapper.classList.add(w.CSS.toolboxShowed)}hide(){this.popover.close(),this.wrapper.classList.remove(w.CSS.toolboxShowed)}}function B(a,t){let e=0;return function(...o){const i=new Date().getTime();if(!(i-e{const r=n.target.closest(`.${s.table}`)!==null,h=n.target.closest(`.${s.wrapper}`)===null;(r||h)&&this.hideToolboxes();const u=n.target.closest(`.${s.addRow}`),f=n.target.closest(`.${s.addColumn}`);u&&u.parentNode===this.wrapper?(this.addRow(void 0,!0),this.hideToolboxes()):f&&f.parentNode===this.wrapper&&(this.addColumn(void 0,!0),this.hideToolboxes())},this.readOnly||this.bindEvents()}getWrapper(){return this.wrapper}bindEvents(){document.addEventListener("click",this.documentClicked),this.table.addEventListener("mousemove",B(150,t=>this.onMouseMoveInTable(t)),{passive:!0}),this.table.onkeypress=t=>this.onKeyPressListener(t),this.table.addEventListener("keydown",t=>this.onKeyDownListener(t)),this.table.addEventListener("focusin",t=>this.focusInTableListener(t))}createColumnToolbox(){return new w({api:this.api,cssModifier:"column",items:[{label:this.api.i18n.t("Add column to left"),icon:S,hideIf:()=>this.numberOfColumns===this.config.maxcols,onClick:()=>{this.addColumn(this.selectedColumn,!0),this.hideToolboxes()}},{label:this.api.i18n.t("Add column to right"),icon:y,hideIf:()=>this.numberOfColumns===this.config.maxcols,onClick:()=>{this.addColumn(this.selectedColumn+1,!0),this.hideToolboxes()}},{label:this.api.i18n.t("Delete column"),icon:b,hideIf:()=>this.numberOfColumns===1,confirmationRequired:!0,onClick:()=>{this.deleteColumn(this.selectedColumn),this.hideToolboxes()}}],onOpen:()=>{this.selectColumn(this.hoveredColumn),this.hideRowToolbox()},onClose:()=>{this.unselectColumn()}})}createRowToolbox(){return new w({api:this.api,cssModifier:"row",items:[{label:this.api.i18n.t("Add row above"),icon:O,hideIf:()=>this.numberOfRows===this.config.maxrows,onClick:()=>{this.addRow(this.selectedRow,!0),this.hideToolboxes()}},{label:this.api.i18n.t("Add row below"),icon:x,hideIf:()=>this.numberOfRows===this.config.maxrows,onClick:()=>{this.addRow(this.selectedRow+1,!0),this.hideToolboxes()}},{label:this.api.i18n.t("Delete row"),icon:b,hideIf:()=>this.numberOfRows===1,confirmationRequired:!0,onClick:()=>{this.deleteRow(this.selectedRow),this.hideToolboxes()}}],onOpen:()=>{this.selectRow(this.hoveredRow),this.hideColumnToolbox()},onClose:()=>{this.unselectRow()}})}moveCursorToNextRow(){this.focusedCell.row!==this.numberOfRows?(this.focusedCell.row+=1,this.focusCell(this.focusedCell)):(this.addRow(),this.focusedCell.row+=1,this.focusCell(this.focusedCell),this.updateToolboxesPosition(0,0))}getCell(t,e){return this.table.querySelectorAll(`.${s.row}:nth-child(${t}) .${s.cell}`)[e-1]}getRow(t){return this.table.querySelector(`.${s.row}:nth-child(${t})`)}getRowByCell(t){return t.parentElement}getRowFirstCell(t){return t.querySelector(`.${s.cell}:first-child`)}setCellContent(t,e,o){const i=this.getCell(t,e);i.innerHTML=o}addColumn(t=-1,e=!1){var n;let o=this.numberOfColumns;if(this.config&&this.config.maxcols&&this.numberOfColumns>=this.config.maxcols)return;for(let r=1;r<=this.numberOfRows;r++){let h;const l=this.createCell();if(t>0&&t<=o?(h=this.getCell(r,t),g(l,h)):h=this.getRow(r).appendChild(l),r===1){const u=this.getCell(r,t>0?t:o+1);u&&e&&C(u)}}const i=this.wrapper.querySelector(`.${s.addColumn}`);(n=this.config)!=null&&n.maxcols&&this.numberOfColumns>this.config.maxcols-1&&i&&i.classList.add(s.addColumnDisabled),this.addHeadingAttrToFirstRow()}addRow(t=-1,e=!1){let o,i=d("div",s.row);this.tunes.withHeadings&&this.removeHeadingAttrFromFirstRow();let n=this.numberOfColumns;if(this.config&&this.config.maxrows&&this.numberOfRows>=this.config.maxrows&&h)return;if(t>0&&t<=this.numberOfRows){let l=this.getRow(t);o=g(i,l)}else o=this.table.appendChild(i);this.fillRow(o,n),this.tunes.withHeadings&&this.addHeadingAttrToFirstRow();const r=this.getRowFirstCell(o);r&&e&&C(r);const h=this.wrapper.querySelector(`.${s.addRow}`);return this.config&&this.config.maxrows&&this.numberOfRows>=this.config.maxrows&&h&&h.classList.add(s.addRowDisabled),o}deleteColumn(t){for(let o=1;o<=this.numberOfRows;o++){const i=this.getCell(o,t);if(!i)return;i.remove()}const e=this.wrapper.querySelector(`.${s.addColumn}`);e&&e.classList.remove(s.addColumnDisabled)}deleteRow(t){this.getRow(t).remove();const e=this.wrapper.querySelector(`.${s.addRow}`);e&&e.classList.remove(s.addRowDisabled),this.addHeadingAttrToFirstRow()}createTableWrapper(){if(this.wrapper=d("div",s.wrapper),this.table=d("div",s.table),this.readOnly&&this.wrapper.classList.add(s.wrapperReadOnly),this.wrapper.appendChild(this.toolboxRow.element),this.wrapper.appendChild(this.toolboxColumn.element),this.wrapper.appendChild(this.table),!this.readOnly){const t=d("div",s.addColumn,{innerHTML:v}),e=d("div",s.addRow,{innerHTML:v});this.wrapper.appendChild(t),this.wrapper.appendChild(e)}}computeInitialSize(){const t=this.data&&this.data.content,e=Array.isArray(t),o=e?t.length:!1,i=e?t.length:void 0,n=o?t[0].length:void 0,r=Number.parseInt(this.config&&this.config.rows),h=Number.parseInt(this.config&&this.config.cols),l=!isNaN(r)&&r>0?r:void 0,u=!isNaN(h)&&h>0?h:void 0;return{rows:i||l||2,cols:n||u||2}}resize(){const{rows:t,cols:e}=this.computeInitialSize();for(let o=0;o0&&e<=this.numberOfColumns&&this.toolboxColumn.show(()=>({style:{left:`calc((100% - var(--cell-size)) / (${this.numberOfColumns} * 2) * (1 + (${e} - 1) * 2))`},numberOfColumns:this.numberOfColumns,currentColumn:e})),this.isRowMenuShowing||t>0&&t<=this.numberOfRows&&this.toolboxRow.show(()=>{const o=this.getRow(t),{fromTopBorder:i}=m(this.table,o),{height:n}=o.getBoundingClientRect();return{style:{top:`${Math.ceil(i+n/2)}px`},numberOfRows:this.numberOfRows,currentRow:t}})}setHeadingsSetting(t){this.tunes.withHeadings=t,t?(this.table.classList.add(s.withHeadings),this.addHeadingAttrToFirstRow()):(this.table.classList.remove(s.withHeadings),this.removeHeadingAttrFromFirstRow())}addHeadingAttrToFirstRow(){for(let t=1;t<=this.numberOfColumns;t++){let e=this.getCell(1,t);e&&e.setAttribute("heading",this.api.i18n.t("Heading"))}}removeHeadingAttrFromFirstRow(){for(let t=1;t<=this.numberOfColumns;t++){let e=this.getCell(1,t);e&&e.removeAttribute("heading")}}selectRow(t){const e=this.getRow(t);e&&(this.selectedRow=t,e.classList.add(s.rowSelected))}unselectRow(){if(this.selectedRow<=0)return;const t=this.table.querySelector(`.${s.rowSelected}`);t&&t.classList.remove(s.rowSelected),this.selectedRow=0}selectColumn(t){for(let e=1;e<=this.numberOfRows;e++){const o=this.getCell(e,t);o&&o.classList.add(s.cellSelected)}this.selectedColumn=t}unselectColumn(){if(this.selectedColumn<=0)return;let t=this.table.querySelectorAll(`.${s.cellSelected}`);Array.from(t).forEach(e=>{e.classList.remove(s.cellSelected)}),this.selectedColumn=0}getHoveredCell(t){let e=this.hoveredRow,o=this.hoveredColumn;const{width:i,height:n,x:r,y:h}=R(this.table,t);return r>=0&&(o=this.binSearch(this.numberOfColumns,l=>this.getCell(1,l),({fromLeftBorder:l})=>rr>i-l)),h>=0&&(e=this.binSearch(this.numberOfRows,l=>this.getCell(l,1),({fromTopBorder:l})=>hh>n-l)),{row:e||this.hoveredRow,column:o||this.hoveredColumn}}binSearch(t,e,o,i){let n=0,r=t+1,h=0,l;for(;n!r.textContent.trim())||t.push(i.map(r=>r.innerHTML))}return t}destroy(){document.removeEventListener("click",this.documentClicked)}}class ${static get isReadOnlySupported(){return!0}static get enableLineBreaks(){return!0}constructor({data:t,config:e,api:o,readOnly:i,block:n}){this.api=o,this.readOnly=i,this.config=e,this.data={withHeadings:this.getConfig("withHeadings",!1,t),stretched:this.getConfig("stretched",!1,t),content:t&&t.content?t.content:[]},this.table=null,this.block=n}static get toolbox(){return{icon:A,title:"Table"}}render(){return this.table=new E(this.readOnly,this.api,this.data,this.config),this.container=d("div",this.api.styles.block),this.container.appendChild(this.table.getWrapper()),this.table.setHeadingsSetting(this.data.withHeadings),this.container}renderSettings(){return[{label:this.api.i18n.t("With headings"),icon:T,isActive:this.data.withHeadings,closeOnActivate:!0,toggle:!0,onActivate:()=>{this.data.withHeadings=!0,this.table.setHeadingsSetting(this.data.withHeadings)}},{label:this.api.i18n.t("Without headings"),icon:H,isActive:!this.data.withHeadings,closeOnActivate:!0,toggle:!0,onActivate:()=>{this.data.withHeadings=!1,this.table.setHeadingsSetting(this.data.withHeadings)}},{label:this.data.stretched?this.api.i18n.t("Collapse"):this.api.i18n.t("Stretch"),icon:this.data.stretched?k:L,closeOnActivate:!0,toggle:!0,onActivate:()=>{this.data.stretched=!this.data.stretched,this.block.stretched=this.data.stretched}}]}save(){const t=this.table.getData();return{withHeadings:this.data.withHeadings,stretched:this.data.stretched,content:t}}destroy(){this.table.destroy()}getConfig(t,e=void 0,o=void 0){const i=this.data||o;return i?i[t]?i[t]:e:this.config&&this.config[t]?this.config[t]:e}static get pasteConfig(){return{tags:["TABLE","TR","TH","TD"]}}onPaste(t){const e=t.detail.data,o=e.querySelector(":scope > thead, tr:first-of-type th"),n=Array.from(e.querySelectorAll("tr")).map(r=>Array.from(r.querySelectorAll("th, td")).map(l=>l.innerHTML));this.data={withHeadings:o!==null,content:n},this.table.wrapper&&this.table.wrapper.replaceWith(this.render())}}const I="";return $});
diff --git a/dist/toolbox.d.ts b/dist/toolbox.d.ts
new file mode 100644
index 0000000..63da96a
--- /dev/null
+++ b/dist/toolbox.d.ts
@@ -0,0 +1,134 @@
+import { default as Popover } from './utils/popover';
+/**
+ * @typedef {object} PopoverItem
+ * @property {string} label - button text
+ * @property {string} icon - button icon
+ * @property {boolean} confirmationRequired - if true, a confirmation state will be applied on the first click
+ * @property {function} hideIf - if provided, item will be hid, if this method returns true
+ * @property {function} onClick - click callback
+ */
+/**
+ * Toolbox is a menu for manipulation of rows/cols
+ *
+ * It contains toggler and Popover:
+ *
+ *
+ *
+ *
+ */
+export default class Toolbox {
+ /**
+ * Style classes
+ */
+ static get CSS(): {
+ toolbox: string;
+ toolboxShowed: string;
+ toggler: string;
+ };
+ /**
+ * Creates toolbox buttons and toolbox menus
+ *
+ * @param {Object} config
+ * @param {any} config.api - Editor.js api
+ * @param {PopoverItem[]} config.items - Editor.js api
+ * @param {function} config.onOpen - callback fired when the Popover is opening
+ * @param {function} config.onClose - callback fired when the Popover is closing
+ * @param {string} config.cssModifier - the modifier for the Toolbox. Allows to add some specific styles.
+ */
+ constructor({ api, items, onOpen, onClose, cssModifier }: {
+ api: any;
+ items: PopoverItem[];
+ onOpen: Function;
+ onClose: Function;
+ cssModifier: string;
+ });
+ api: any;
+ items: PopoverItem[];
+ onOpen: Function;
+ onClose: Function;
+ cssModifier: string;
+ popover: Popover;
+ wrapper: Element;
+ numberOfColumns: number;
+ numberOfRows: number;
+ currentColumn: number;
+ currentRow: number;
+ /**
+ * Returns rendered Toolbox element
+ */
+ get element(): Element;
+ /**
+ * Creating a toolbox to open menu for a manipulating columns
+ *
+ * @returns {Element}
+ */
+ createToolbox(): Element;
+ /**
+ * Creates the Toggler
+ *
+ * @returns {Element}
+ */
+ createToggler(): Element;
+ /**
+ * Creates the Popover instance and render it
+ *
+ * @returns {Element}
+ */
+ createPopover(): Element;
+ /**
+ * Toggler click handler. Opens/Closes the popover
+ *
+ * @returns {void}
+ */
+ togglerClicked(): void;
+ /**
+ * Shows the Toolbox
+ *
+ * @param {function} computePositionMethod - method that returns the position coordinate
+ * @returns {void}
+ */
+ show(computePositionMethod: Function): void;
+ /**
+ * Hides the Toolbox
+ *
+ * @returns {void}
+ */
+ hide(): void;
+}
+export type PopoverItem = {
+ /**
+ * - button text
+ */
+ /**
+ * - button text
+ */
+ label: string;
+ /**
+ * - button icon
+ */
+ /**
+ * - button icon
+ */
+ icon: string;
+ /**
+ * - if true, a confirmation state will be applied on the first click
+ */
+ /**
+ * - if true, a confirmation state will be applied on the first click
+ */
+ confirmationRequired: boolean;
+ /**
+ * - if provided, item will be hid, if this method returns true
+ */
+ /**
+ * - if provided, item will be hid, if this method returns true
+ */
+ hideIf: Function;
+ /**
+ * - click callback
+ */
+ /**
+ * - click callback
+ */
+ onClick: Function;
+};
diff --git a/dist/utils/dom.d.ts b/dist/utils/dom.d.ts
new file mode 100644
index 0000000..0200a84
--- /dev/null
+++ b/dist/utils/dom.d.ts
@@ -0,0 +1,71 @@
+/**
+ * Helper for making Elements with attributes
+ *
+ * @param {string} tagName - new Element tag name
+ * @param {string|string[]} classNames - list or name of CSS classname(s)
+ * @param {object} attributes - any attributes
+ * @returns {Element}
+ */
+export function make(tagName: string, classNames: string | string[], attributes?: object): Element;
+/**
+ * Get item position relative to document
+ *
+ * @param {HTMLElement} elem - item
+ * @returns {{x1: number, y1: number, x2: number, y2: number}} coordinates of the upper left (x1,y1) and lower right(x2,y2) corners
+ */
+export function getCoords(elem: HTMLElement): {
+ x1: number;
+ y1: number;
+ x2: number;
+ y2: number;
+};
+/**
+ * Calculate paddings of the first element relative to the second
+ *
+ * @param {HTMLElement} firstElem - outer element, if the second element is inside it, then all padding will be positive
+ * @param {HTMLElement} secondElem - inner element, if its borders go beyond the first, then the paddings will be considered negative
+ * @returns {{fromTopBorder: number, fromLeftBorder: number, fromRightBorder: number, fromBottomBorder: number}}
+ */
+export function getRelativeCoordsOfTwoElems(firstElem: HTMLElement, secondElem: HTMLElement): {
+ fromTopBorder: number;
+ fromLeftBorder: number;
+ fromRightBorder: number;
+ fromBottomBorder: number;
+};
+/**
+ * Get the width and height of an element and the position of the cursor relative to it
+ *
+ * @param {HTMLElement} elem - element relative to which the coordinates will be calculated
+ * @param {Event} event - mouse event
+ */
+export function getCursorPositionRelativeToElement(elem: HTMLElement, event: Event): {
+ width: number;
+ height: number;
+ x: number;
+ y: number;
+};
+/**
+ * Insert element after the referenced
+ *
+ * @param {HTMLElement} newNode
+ * @param {HTMLElement} referenceNode
+ * @returns {HTMLElement}
+ */
+export function insertAfter(newNode: HTMLElement, referenceNode: HTMLElement): HTMLElement;
+/**
+ * Insert element after the referenced
+ *
+ * @param {HTMLElement} newNode
+ * @param {HTMLElement} referenceNode
+ * @returns {HTMLElement}
+ */
+export function insertBefore(newNode: HTMLElement, referenceNode: HTMLElement): HTMLElement;
+/**
+ * Set focus to contenteditable or native input element
+ *
+ * @param {Element} element - element where to set focus
+ * @param {boolean} atStart - where to set focus: at the start or at the end
+ *
+ * @returns {void}
+ */
+export function focus(element: Element, atStart?: boolean): void;
diff --git a/dist/utils/popover.d.ts b/dist/utils/popover.d.ts
new file mode 100644
index 0000000..3732acc
--- /dev/null
+++ b/dist/utils/popover.d.ts
@@ -0,0 +1,115 @@
+/**
+ * @typedef {object} PopoverItem
+ * @property {string} label - button text
+ * @property {string} icon - button icon
+ * @property {boolean} confirmationRequired - if true, a confirmation state will be applied on the first click
+ * @property {function} hideIf - if provided, item will be hid, if this method returns true
+ * @property {function} onClick - click callback
+ */
+/**
+ * This cass provides a popover rendering
+ */
+export default class Popover {
+ /**
+ * Set of CSS classnames used in popover
+ *
+ * @returns {object}
+ */
+ static get CSS(): any;
+ /**
+ * @param {object} options - constructor options
+ * @param {PopoverItem[]} options.items - constructor options
+ */
+ constructor({ items }: {
+ items: PopoverItem[];
+ });
+ items: PopoverItem[];
+ wrapper: Element;
+ itemEls: any[];
+ /**
+ * Returns the popover element
+ *
+ * @returns {Element}
+ */
+ render(): Element;
+ /**
+ * Popover wrapper click listener
+ * Used to delegate clicks in items
+ *
+ * @returns {void}
+ */
+ popoverClicked(event: any): void;
+ /**
+ * Enable the confirmation state on passed item
+ *
+ * @returns {void}
+ */
+ setConfirmationState(itemEl: any): void;
+ /**
+ * Disable the confirmation state on passed item
+ *
+ * @returns {void}
+ */
+ clearConfirmationState(itemEl: any): void;
+ /**
+ * Check if passed item has the confirmation state
+ *
+ * @returns {boolean}
+ */
+ hasConfirmationState(itemEl: any): boolean;
+ /**
+ * Return an opening state
+ *
+ * @returns {boolean}
+ */
+ get opened(): boolean;
+ /**
+ * Opens the popover
+ *
+ * @returns {void}
+ */
+ open(): void;
+ /**
+ * Closes the popover
+ *
+ * @returns {void}
+ */
+ close(): void;
+}
+export type PopoverItem = {
+ /**
+ * - button text
+ */
+ /**
+ * - button text
+ */
+ label: string;
+ /**
+ * - button icon
+ */
+ /**
+ * - button icon
+ */
+ icon: string;
+ /**
+ * - if true, a confirmation state will be applied on the first click
+ */
+ /**
+ * - if true, a confirmation state will be applied on the first click
+ */
+ confirmationRequired: boolean;
+ /**
+ * - if provided, item will be hid, if this method returns true
+ */
+ /**
+ * - if provided, item will be hid, if this method returns true
+ */
+ hideIf: Function;
+ /**
+ * - click callback
+ */
+ /**
+ * - click callback
+ */
+ onClick: Function;
+};
diff --git a/dist/utils/throttled.d.ts b/dist/utils/throttled.d.ts
new file mode 100644
index 0000000..c758ba0
--- /dev/null
+++ b/dist/utils/throttled.d.ts
@@ -0,0 +1,7 @@
+/**
+ * Limits the frequency of calling a function
+ *
+ * @param {number} delay - delay between calls in milliseconds
+ * @param {function} fn - function to be throttled
+ */
+export default function throttled(delay: number, fn: Function): (...args: any[]) => any;
diff --git a/src/styles/toolboxes.pcss b/src/styles/toolboxes.pcss
index 038bab3..f82d9b4 100644
--- a/src/styles/toolboxes.pcss
+++ b/src/styles/toolboxes.pcss
@@ -2,8 +2,8 @@
--toolbox-padding: 6px;
--popover-margin: 30px;
--toggler-click-zone-size: 30px;
- --toggler-dots-color: #7B7E89;
- --toggler-dots-color-hovered: #1D202B;
+ --toggler-dots-color: #7b7e89;
+ --toggler-dots-color-hovered: #1d202b;
position: absolute;
cursor: pointer;
@@ -32,8 +32,6 @@
.tc-popover {
position: absolute;
- top: 0;
- left: var(--popover-margin)
}
&__toggler {
diff --git a/src/table.js b/src/table.js
index 7dc31fd..a48001b 100644
--- a/src/table.js
+++ b/src/table.js
@@ -775,7 +775,11 @@ export default class Table {
if (column > 0 && column <= this.numberOfColumns) { // not sure this statement is needed. Maybe it should be fixed in getHoveredCell()
this.toolboxColumn.show(() => {
return {
- left: `calc((100% - var(--cell-size)) / (${this.numberOfColumns} * 2) * (1 + (${column} - 1) * 2))`
+ style: {
+ left: `calc((100% - var(--cell-size)) / (${this.numberOfColumns} * 2) * (1 + (${column} - 1) * 2))`
+ },
+ numberOfColumns: this.numberOfColumns,
+ currentColumn: column
};
});
}
@@ -789,7 +793,11 @@ export default class Table {
const { height } = hoveredRowElement.getBoundingClientRect();
return {
- top: `${Math.ceil(fromTopBorder + height / 2)}px`
+ style: {
+ top: `${Math.ceil(fromTopBorder + height / 2)}px`
+ },
+ numberOfRows: this.numberOfRows,
+ currentRow: row
};
});
}
diff --git a/src/toolbox.js b/src/toolbox.js
index 453cd14..04f5785 100644
--- a/src/toolbox.js
+++ b/src/toolbox.js
@@ -41,6 +41,11 @@ export default class Toolbox {
this.popover = null;
this.wrapper = this.createToolbox();
+
+ this.numberOfColumns = 0;
+ this.numberOfRows = 0;
+ this.currentColumn = 0;
+ this.currentRow = 0;
}
/**
@@ -118,6 +123,35 @@ export default class Toolbox {
* @returns {void}
*/
togglerClicked() {
+ // default:
+ // left: var(--popover-margin)
+ // top: 0
+ let styles = {};
+
+ if (this.currentColumn > Math.ceil(this.numberOfColumns / 2)) {
+ styles.right = "var(--popover-margin)";
+ styles.left = "auto";
+ } else {
+ styles.left = "var(--popover-margin)";
+ styles.right = "auto";
+ }
+
+ if (this.currentRow > Math.ceil(this.numberOfRows / 2)) {
+ styles.bottom = 0;
+ styles.top = 'auto';
+ } else {
+ styles.top = 0;
+ styles.bottom = 'auto';
+ }
+
+ /**
+ * Set 'top','bottom' style
+ * Set 'left','right' style
+ */
+ Object.entries(styles).forEach(([prop, value]) => {
+ this.popover.wrapper.style[prop] = value;
+ });
+
if (this.popover.opened) {
this.popover.close();
this.onClose();
@@ -135,14 +169,24 @@ export default class Toolbox {
*/
show(computePositionMethod) {
const position = computePositionMethod();
-
/**
* Set 'top' or 'left' style
*/
- Object.entries(position).forEach(([prop, value]) => {
+ Object.entries(position.style).forEach(([prop, value]) => {
this.wrapper.style[prop] = value;
});
+ console.log(position, this.cssModifier)
+ if (this.cssModifier == 'row') {
+ // row
+ this.numberOfRows = position.numberOfRows;
+ this.currentRow = position.currentRow;
+ } else if (this.cssModifier == 'column') {
+ // column
+ this.numberOfColumns = position.numberOfColumns;
+ this.currentColumn = position.currentColumn;
+ }
+
this.wrapper.classList.add(Toolbox.CSS.toolboxShowed);
}