diff --git a/documentUtils.js b/documentUtils.js new file mode 100644 index 0000000..f2714d3 --- /dev/null +++ b/documentUtils.js @@ -0,0 +1,42 @@ +/** + * Checks the item is not missed or messed + * @param {object|string[]|Element[]|HTMLElement|string} elem - element + * @returns {boolean} true if element is correct + * @private + */ +function _isNotMissed(elem) { + return (!(elem === undefined || elem === null)); +} + +/** + * Create DOM element with set parameters + * @param {string} tagName - Html tag of the element to be created + * @param {string[]} cssClasses - Css classes that must be applied to an element + * @param {object} attrs - Attributes that must be applied to the element + * @param {Element[]} children - child elements of creating element + * @returns {HTMLElement} the new element + */ +export function create(tagName, cssClasses = null, attrs = null, children = null) { + const elem = document.createElement(tagName); + + if (_isNotMissed(cssClasses)) { + for (let i = 0; i < cssClasses.length; i++) { + if (_isNotMissed(cssClasses[i])) { + elem.classList.add(cssClasses[i]); + } + } + } + if (_isNotMissed(attrs)) { + for (let key in attrs) { + elem.setAttribute(key, attrs[key]); + } + } + if (_isNotMissed(children)) { + for (let i = 0; i < children.length; i++) { + if (_isNotMissed(children[i])) { + elem.appendChild(children[i]); + } + } + } + return elem; +} diff --git a/img/deleteColIcon.svg b/img/deleteColIcon.svg new file mode 100644 index 0000000..c0b4f84 --- /dev/null +++ b/img/deleteColIcon.svg @@ -0,0 +1 @@ + diff --git a/img/deleteRowIcon.svg b/img/deleteRowIcon.svg new file mode 100644 index 0000000..b4f331d --- /dev/null +++ b/img/deleteRowIcon.svg @@ -0,0 +1 @@ + diff --git a/img/indertColAfterIcon.svg b/img/indertColAfterIcon.svg new file mode 100644 index 0000000..c06d8e1 --- /dev/null +++ b/img/indertColAfterIcon.svg @@ -0,0 +1 @@ + diff --git a/img/insertColBeforeIcon.svg b/img/insertColBeforeIcon.svg new file mode 100644 index 0000000..b1155b8 --- /dev/null +++ b/img/insertColBeforeIcon.svg @@ -0,0 +1 @@ + diff --git a/img/insertRowAfter.svg b/img/insertRowAfter.svg new file mode 100644 index 0000000..bcb6a4c --- /dev/null +++ b/img/insertRowAfter.svg @@ -0,0 +1 @@ + diff --git a/img/insertRowBeforeIcon.svg b/img/insertRowBeforeIcon.svg new file mode 100644 index 0000000..885ae46 --- /dev/null +++ b/img/insertRowBeforeIcon.svg @@ -0,0 +1 @@ + diff --git a/img/mergeCellIcon.svg b/img/mergeCellIcon.svg new file mode 100644 index 0000000..808be67 --- /dev/null +++ b/img/mergeCellIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/toolboxIcon.svg b/img/toolboxIcon.svg new file mode 100644 index 0000000..0db0ad5 --- /dev/null +++ b/img/toolboxIcon.svg @@ -0,0 +1 @@ + diff --git a/img/unmergeCellIcon.svg b/img/unmergeCellIcon.svg new file mode 100644 index 0000000..9b5eaf1 --- /dev/null +++ b/img/unmergeCellIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugin.js b/plugin.js new file mode 100644 index 0000000..a2163b9 --- /dev/null +++ b/plugin.js @@ -0,0 +1,323 @@ +const { TableConstructor } = require("./tableConstructor"); +const toolboxIcon = require("./img/toolboxIcon.svg"); +const insertColBefore = require("./img/insertColBeforeIcon.svg"); +const insertColAfter = require("./img/indertColAfterIcon.svg"); +const insertRowBefore = require("./img/insertRowBeforeIcon.svg"); +const insertRowAfter = require("./img/insertRowAfter.svg"); +const deleteRow = require("./img/deleteRowIcon.svg"); +const deleteCol = require("./img/deleteColIcon.svg"); + +const Icons = { + Toolbox: toolboxIcon, + InsertColBefore: insertColBefore, + InsertColAfter: insertColAfter, + InsertRowBefore: insertRowBefore, + InsertRowAfter: insertRowAfter, + DeleteRow: deleteRow, + DeleteCol: deleteCol, +}; + +const CSS = { + input: "tc-table__inp", +}; + +/** + * Tool for table's creating + * @typedef {object} TableData - object with the data transferred to form a table + * @property {string[][]} content - two-dimensional array which contains table content + */ +class Table { + /** + * Allow to press Enter inside the CodeTool textarea + * @returns {boolean} + * @public + */ + static get enableLineBreaks() { + return true; + } + + /** + * Get Tool toolbox settings + * icon - Tool icon's SVG + * title - title to show in toolbox + * + * @return {{icon: string, title: string}} + */ + static get toolbox() { + return { + icon: Icons.Toolbox, + title: "Table", + }; + } + + /** + * Render plugin`s main Element and fill it with saved data + * @param {TableData} data — previously saved data + * @param {object} config - user config for Tool + * @param {object} api - Editor.js API + */ + constructor({ data, config, api }) { + this.api = api; + this.wrapper = undefined; + this.config = config; + this.data = data; + this._tableConstructor = new TableConstructor(data, config, api); + + this.actions = [ + { + actionName: "InsertColBefore", + icon: Icons.InsertColBefore, + label: this.api.i18n.t("Insert column before"), + }, + { + actionName: "InsertColAfter", + icon: Icons.InsertColAfter, + label: this.api.i18n.t("Insert column after"), + }, + { + actionName: "InsertRowBefore", + icon: Icons.InsertRowBefore, + label: this.api.i18n.t("Insert row before"), + }, + { + actionName: "InsertRowAfter", + icon: Icons.InsertRowAfter, + label: this.api.i18n.t("Insert row after"), + }, + { + actionName: "DeleteRow", + icon: Icons.DeleteRow, + label: this.api.i18n.t("Delete row"), + }, + { + actionName: "DeleteCol", + icon: Icons.DeleteCol, + label: this.api.i18n.t("Delete column"), + }, + ]; + } + + /** + * perform selected action + * @param actionName {string} - action name + * @return {undefined} + */ + performAction(actionName) { + switch (actionName) { + case "InsertColBefore": + this._tableConstructor.table.insertColumnBefore(); + break; + case "InsertColAfter": + this._tableConstructor.table.insertColumnAfter(); + break; + case "InsertRowBefore": + this._tableConstructor.table.insertRowBefore(); + break; + case "InsertRowAfter": + this._tableConstructor.table.insertRowAfter(); + break; + case "DeleteRow": + this._tableConstructor.table.deleteRow(); + break; + case "DeleteCol": + this._tableConstructor.table.deleteColumn(); + break; + } + } + + /** + * render actions toolbar + * @returns {HTMLDivElement} + */ + renderSettings() { + const wrapper = document.createElement("div"); + + this.actions.forEach(({ actionName, label, icon }) => { + const title = this.api.i18n.t(label); + const button = document.createElement("div"); + + button.classList.add("cdx-settings-button"); + button.innerHTML = icon; + button.title = actionName; + + this.api.tooltip.onHover(button, title, { + placement: "top", + }); + button.addEventListener( + "click", + this.performAction.bind(this, actionName) + ); + wrapper.appendChild(button); + if(this._tableConstructor.table.selectedCell) { + this._tableConstructor.table.focusCellOnSelectedCell(); + } + }); + return wrapper; + } + + /** + * Return Tool's view + * @returns {HTMLDivElement} + * @public + */ + render() { + this.wrapper = document.createElement("div"); + + if ((this.data && this.data.content)) { + //Creates table if Data is Present + this._createTableConfiguration(); + } else { + // Create table preview if New table is initialised + this.wrapper.classList.add("table-selector"); + this.wrapper.setAttribute("data-hoveredClass", "m,n"); + const rows = 6; + this.createCells(rows); + //Hover to select cells + if (this.wrapper.className === "table-selector") { + this.wrapper.addEventListener("mouseover", (event) => { + const selectedCell = event.target.id; + if (selectedCell.length) { + const selectedRow = event.target.attributes.row.value; + const selectedColumn = event.target.attributes.column.value; + this.wrapper.setAttribute( + "data-hoveredClass", + `${selectedRow},${selectedColumn}` + ); + } + }); + } + //set the select cell to load table config + this.wrapper.addEventListener("click", (event) => { + const selectedCell = event.target.id; + if (selectedCell.length) { + const selectedRow = event.target.attributes.row.value; + const selectedColumn = event.target.attributes.column.value; + this.wrapper.removeEventListener("mouseover", () => {}); + this.config.rows = selectedRow; + this.config.cols = selectedColumn; + this._createTableConfiguration(); + } + }); + } + return this.wrapper; + } + + createCells(rows) { + if (rows !== 0) { + for (let i = 0; i < rows; i++) { + let rowDiv = document.createElement("div"); + rowDiv.setAttribute("class", "table-row"); + for (let j = 0; j < rows; j++) { + let columnDivContainer = document.createElement("div"); + let columnDiv = document.createElement("div"); + columnDivContainer.setAttribute("class", "table-cell-container"); + columnDiv.setAttribute("class", "table-cell"); + columnDivContainer.setAttribute("id", `row_${i + 1}_cell_${j + 1}`); + columnDivContainer.setAttribute("column", j + 1); + columnDivContainer.setAttribute("row", i + 1); + columnDiv.setAttribute("id", `cell_${j + 1}`); + columnDiv.setAttribute("column", j + 1); + columnDiv.setAttribute("row", i + 1); + columnDivContainer.appendChild(columnDiv); + rowDiv.appendChild(columnDivContainer); + } + this.wrapper.appendChild(rowDiv); + } + } + const hiddenEl = document.createElement('input'); + hiddenEl.classList.add('hidden-element'); + hiddenEl.setAttribute('tabindex', 0); + this.wrapper.appendChild(hiddenEl); + } + + _createTableConfiguration() { + this.wrapper.innerHTML = ""; + this._tableConstructor = new TableConstructor( + this.data, + this.config, + this.api + ); + this.wrapper.appendChild(this._tableConstructor.htmlElement); + } + /** + * Extract Tool's data from the view + * @returns {TableData} - saved data + * @public + */ + save(toolsContent) { + const table = toolsContent.querySelector("table"); + const data = []; + const rows = table ? table.rows : 0; + if(rows.length) { + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + const cols = Array.from(row.cells); + const inputs = cols.map((cell) => cell.querySelector("." + CSS.input)); + const isWorthless = inputs.every(this._isEmpty); + + if (isWorthless) { + continue; + } + data.push(inputs.map((input) => input.innerHTML)); + } + return { + content: data, + }; + } +} + + /** + * @private + * + * Check input field is empty + * @param {HTMLElement} input - input field + * @return {boolean} + */ + _isEmpty(input) { + return !input.textContent.trim(); + } + + static get pasteConfig() { + return { + tags: ['TABLE', 'TR', 'TD', 'TBODY', 'TH'], + }; + } + + async onPaste(event) { + const table = event.detail.data; + this.data = this.pasteHandler(table); + this._createTableConfiguration(); + } + + pasteHandler(element) { + const {tagName: tag} = element; + const data = { + content: [], + config: { + rows: 0, + cols: 0 + } + } + if(tag ==='TABLE') { + let tableBody = Array.from(element.childNodes); + tableBody = tableBody.find(el => el.nodeName === 'TBODY'); + let tableRows = Array.from(tableBody.childNodes); + tableRows = [tableRows].map(obj => { + return obj.filter((tr) => tr.nodeName === 'TR'); + }); + data.config.rows = tableRows[0].length; + data.content = tableRows[0].map((tr) => { + let tableData = tr.childNodes; + data.config.cols = tableData.length; + tableData = [...tableData].map((td) => { + return td.innerHTML; + }); + return tableData; + }) + } + return data; + } + +} + +module.exports = Table; diff --git a/src/img/mergeCellIcon.svg b/src/img/mergeCellIcon.svg new file mode 100644 index 0000000..808be67 --- /dev/null +++ b/src/img/mergeCellIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/img/unmergeCellIcon.svg b/src/img/unmergeCellIcon.svg new file mode 100644 index 0000000..9b5eaf1 --- /dev/null +++ b/src/img/unmergeCellIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/styles/table-constructor.pcss b/styles/table-constructor.pcss new file mode 100644 index 0000000..e6c8ec5 --- /dev/null +++ b/styles/table-constructor.pcss @@ -0,0 +1,9 @@ +/* tcm - project's prefix*/ + +.tc-editor { + padding: 10px; + position: relative; + box-sizing: content-box; + width: 100%; + left: -10px; +} \ No newline at end of file diff --git a/styles/table.pcss b/styles/table.pcss new file mode 100644 index 0000000..d4dd5bf --- /dev/null +++ b/styles/table.pcss @@ -0,0 +1,522 @@ +/* tc- project's prefix*/ + +.tc-table { + width: 100%; + height: 100%; + border-collapse: collapse; + table-layout: fixed; + + &__wrap { + border: 1px solid #DBDBE2; + border-radius: 3px; + position: relative; + height: 100%; + width: 100%; + box-sizing: border-box; + } + + &__cell { + border: 1px solid #DBDBE2; + padding: 0; + vertical-align: top; + } + + &__area { + padding: 10px; + height: 100%; + } + + &__inp { + outline: none; + flex-grow: 100; + min-height: 1.5em; + height: 100%; + overflow: hidden; + } + + &__highlight:focus-within { + background-color: rgba(173, 164, 176, 0.1); + } + + tbody tr:first-child td { + border-top: none; + } + + tbody tr:last-child td { + border-bottom: none; + } + + tbody tr td:last-child { + border-right: none; + } + + tbody tr td:first-child { + border-left: none; + } +} +.table-selector { + display: flex; + flex-direction: column; + .hidden-element { + display: none; + } +} +.table-row { + display: flex; + flex-direction: row; +} +.table-cell-container { + width: 30px; + height: 30px; +} +.table-cell { + width: 25px; + height: 25px; + background: rgba(246, 246, 246); + border: 1px solid rgba(228, 228, 228); + cursor: pointer; +} +[data-hoveredClass="1,1"] #row_1_cell_1 #cell_1, +[data-hoveredClass="1,2"] #row_1_cell_1 #cell_1, +[data-hoveredClass="1,2"] #row_1_cell_2 #cell_2, +[data-hoveredClass="1,3"] #row_1_cell_1 #cell_1, +[data-hoveredClass="1,3"] #row_1_cell_2 #cell_2, +[data-hoveredClass="1,3"] #row_1_cell_3 #cell_3, +[data-hoveredClass="1,4"] #row_1_cell_1 #cell_1, +[data-hoveredClass="1,4"] #row_1_cell_2 #cell_2, +[data-hoveredClass="1,4"] #row_1_cell_3 #cell_3, +[data-hoveredClass="1,4"] #row_1_cell_4 #cell_4, +[data-hoveredClass="1,5"] #row_1_cell_1 #cell_1, +[data-hoveredClass="1,5"] #row_1_cell_2 #cell_2, +[data-hoveredClass="1,5"] #row_1_cell_3 #cell_3, +[data-hoveredClass="1,5"] #row_1_cell_4 #cell_4, +[data-hoveredClass="1,5"] #row_1_cell_5 #cell_5, +[data-hoveredClass="1,6"] #row_1_cell_1 #cell_1, +[data-hoveredClass="1,6"] #row_1_cell_2 #cell_2, +[data-hoveredClass="1,6"] #row_1_cell_3 #cell_3, +[data-hoveredClass="1,6"] #row_1_cell_4 #cell_4, +[data-hoveredClass="1,6"] #row_1_cell_5 #cell_5, +[data-hoveredClass="1,6"] #row_1_cell_6 #cell_6, +[data-hoveredClass="2,1"] #row_1_cell_1 #cell_1, +[data-hoveredClass="2,1"] #row_2_cell_1 #cell_1, +[data-hoveredClass="2,2"] #row_1_cell_1 #cell_1, +[data-hoveredClass="2,2"] #row_1_cell_2 #cell_2, +[data-hoveredClass="2,2"] #row_2_cell_1 #cell_1, +[data-hoveredClass="2,2"] #row_2_cell_2 #cell_2, +[data-hoveredClass="2,3"] #row_1_cell_1 #cell_1, +[data-hoveredClass="2,3"] #row_1_cell_2 #cell_2, +[data-hoveredClass="2,3"] #row_1_cell_3 #cell_3, +[data-hoveredClass="2,3"] #row_2_cell_1 #cell_1, +[data-hoveredClass="2,3"] #row_2_cell_2 #cell_2, +[data-hoveredClass="2,3"] #row_2_cell_3 #cell_3, +[data-hoveredClass="2,4"] #row_1_cell_1 #cell_1, +[data-hoveredClass="2,4"] #row_1_cell_2 #cell_2, +[data-hoveredClass="2,4"] #row_1_cell_3 #cell_3, +[data-hoveredClass="2,4"] #row_1_cell_4 #cell_4, +[data-hoveredClass="2,4"] #row_2_cell_1 #cell_1, +[data-hoveredClass="2,4"] #row_2_cell_2 #cell_2, +[data-hoveredClass="2,4"] #row_2_cell_3 #cell_3, +[data-hoveredClass="2,4"] #row_2_cell_4 #cell_4, +[data-hoveredClass="2,5"] #row_1_cell_1 #cell_1, +[data-hoveredClass="2,5"] #row_1_cell_2 #cell_2, +[data-hoveredClass="2,5"] #row_1_cell_3 #cell_3, +[data-hoveredClass="2,5"] #row_1_cell_4 #cell_4, +[data-hoveredClass="2,5"] #row_1_cell_5 #cell_5, +[data-hoveredClass="2,5"] #row_2_cell_1 #cell_1, +[data-hoveredClass="2,5"] #row_2_cell_2 #cell_2, +[data-hoveredClass="2,5"] #row_2_cell_3 #cell_3, +[data-hoveredClass="2,5"] #row_2_cell_4 #cell_4, +[data-hoveredClass="2,5"] #row_2_cell_5 #cell_5, +[data-hoveredClass="2,6"] #row_1_cell_1 #cell_1, +[data-hoveredClass="2,6"] #row_1_cell_2 #cell_2, +[data-hoveredClass="2,6"] #row_1_cell_3 #cell_3, +[data-hoveredClass="2,6"] #row_1_cell_4 #cell_4, +[data-hoveredClass="2,6"] #row_1_cell_5 #cell_5, +[data-hoveredClass="2,6"] #row_1_cell_6 #cell_6, +[data-hoveredClass="2,6"] #row_2_cell_1 #cell_1, +[data-hoveredClass="2,6"] #row_2_cell_2 #cell_2, +[data-hoveredClass="2,6"] #row_2_cell_3 #cell_3, +[data-hoveredClass="2,6"] #row_2_cell_4 #cell_4, +[data-hoveredClass="2,6"] #row_2_cell_5 #cell_5, +[data-hoveredClass="2,6"] #row_2_cell_6 #cell_6, +[data-hoveredClass="3,1"] #row_1_cell_1 #cell_1, +[data-hoveredClass="3,1"] #row_2_cell_1 #cell_1, +[data-hoveredClass="3,1"] #row_3_cell_1 #cell_1, +[data-hoveredClass="3,2"] #row_1_cell_1 #cell_1, +[data-hoveredClass="3,2"] #row_1_cell_2 #cell_2, +[data-hoveredClass="3,2"] #row_2_cell_1 #cell_1, +[data-hoveredClass="3,2"] #row_2_cell_2 #cell_2, +[data-hoveredClass="3,2"] #row_3_cell_1 #cell_1, +[data-hoveredClass="3,2"] #row_3_cell_2 #cell_2, +[data-hoveredClass="3,3"] #row_1_cell_1 #cell_1, +[data-hoveredClass="3,3"] #row_1_cell_2 #cell_2, +[data-hoveredClass="3,3"] #row_1_cell_3 #cell_3, +[data-hoveredClass="3,3"] #row_2_cell_1 #cell_1, +[data-hoveredClass="3,3"] #row_2_cell_2 #cell_2, +[data-hoveredClass="3,3"] #row_2_cell_3 #cell_3, +[data-hoveredClass="3,3"] #row_3_cell_1 #cell_1, +[data-hoveredClass="3,3"] #row_3_cell_2 #cell_2, +[data-hoveredClass="3,3"] #row_3_cell_3 #cell_3, +[data-hoveredClass="3,4"] #row_1_cell_1 #cell_1, +[data-hoveredClass="3,4"] #row_1_cell_2 #cell_2, +[data-hoveredClass="3,4"] #row_1_cell_3 #cell_3, +[data-hoveredClass="3,4"] #row_1_cell_4 #cell_4, +[data-hoveredClass="3,4"] #row_2_cell_1 #cell_1, +[data-hoveredClass="3,4"] #row_2_cell_2 #cell_2, +[data-hoveredClass="3,4"] #row_2_cell_3 #cell_3, +[data-hoveredClass="3,4"] #row_2_cell_4 #cell_4, +[data-hoveredClass="3,4"] #row_3_cell_1 #cell_1, +[data-hoveredClass="3,4"] #row_3_cell_2 #cell_2, +[data-hoveredClass="3,4"] #row_3_cell_3 #cell_3, +[data-hoveredClass="3,4"] #row_3_cell_4 #cell_4, +[data-hoveredClass="3,5"] #row_1_cell_1 #cell_1, +[data-hoveredClass="3,5"] #row_1_cell_2 #cell_2, +[data-hoveredClass="3,5"] #row_1_cell_3 #cell_3, +[data-hoveredClass="3,5"] #row_1_cell_4 #cell_4, +[data-hoveredClass="3,5"] #row_1_cell_5 #cell_5, +[data-hoveredClass="3,5"] #row_2_cell_1 #cell_1, +[data-hoveredClass="3,5"] #row_2_cell_2 #cell_2, +[data-hoveredClass="3,5"] #row_2_cell_3 #cell_3, +[data-hoveredClass="3,5"] #row_2_cell_4 #cell_4, +[data-hoveredClass="3,5"] #row_2_cell_5 #cell_5, +[data-hoveredClass="3,5"] #row_3_cell_1 #cell_1, +[data-hoveredClass="3,5"] #row_3_cell_2 #cell_2, +[data-hoveredClass="3,5"] #row_3_cell_3 #cell_3, +[data-hoveredClass="3,5"] #row_3_cell_4 #cell_4, +[data-hoveredClass="3,5"] #row_3_cell_5 #cell_5, +[data-hoveredClass="3,6"] #row_1_cell_1 #cell_1, +[data-hoveredClass="3,6"] #row_1_cell_2 #cell_2, +[data-hoveredClass="3,6"] #row_1_cell_3 #cell_3, +[data-hoveredClass="3,6"] #row_1_cell_4 #cell_4, +[data-hoveredClass="3,6"] #row_1_cell_5 #cell_5, +[data-hoveredClass="3,6"] #row_1_cell_6 #cell_6, +[data-hoveredClass="3,6"] #row_2_cell_1 #cell_1, +[data-hoveredClass="3,6"] #row_2_cell_2 #cell_2, +[data-hoveredClass="3,6"] #row_2_cell_3 #cell_3, +[data-hoveredClass="3,6"] #row_2_cell_4 #cell_4, +[data-hoveredClass="3,6"] #row_2_cell_5 #cell_5, +[data-hoveredClass="3,6"] #row_2_cell_6 #cell_6, +[data-hoveredClass="3,6"] #row_3_cell_1 #cell_1, +[data-hoveredClass="3,6"] #row_3_cell_2 #cell_2, +[data-hoveredClass="3,6"] #row_3_cell_3 #cell_3, +[data-hoveredClass="3,6"] #row_3_cell_4 #cell_4, +[data-hoveredClass="3,6"] #row_3_cell_5 #cell_5, +[data-hoveredClass="3,6"] #row_3_cell_6 #cell_6, +[data-hoveredClass="4,1"] #row_1_cell_1 #cell_1, +[data-hoveredClass="4,1"] #row_2_cell_1 #cell_1, +[data-hoveredClass="4,1"] #row_3_cell_1 #cell_1, +[data-hoveredClass="4,1"] #row_4_cell_1 #cell_1, +[data-hoveredClass="4,2"] #row_1_cell_1 #cell_1, +[data-hoveredClass="4,2"] #row_1_cell_2 #cell_2, +[data-hoveredClass="4,2"] #row_2_cell_1 #cell_1, +[data-hoveredClass="4,2"] #row_2_cell_2 #cell_2, +[data-hoveredClass="4,2"] #row_3_cell_1 #cell_1, +[data-hoveredClass="4,2"] #row_3_cell_2 #cell_2, +[data-hoveredClass="4,2"] #row_4_cell_1 #cell_1, +[data-hoveredClass="4,2"] #row_4_cell_2 #cell_2, +[data-hoveredClass="4,3"] #row_1_cell_1 #cell_1, +[data-hoveredClass="4,3"] #row_1_cell_2 #cell_2, +[data-hoveredClass="4,3"] #row_1_cell_3 #cell_3, +[data-hoveredClass="4,3"] #row_2_cell_1 #cell_1, +[data-hoveredClass="4,3"] #row_2_cell_2 #cell_2, +[data-hoveredClass="4,3"] #row_2_cell_3 #cell_3, +[data-hoveredClass="4,3"] #row_3_cell_1 #cell_1, +[data-hoveredClass="4,3"] #row_3_cell_2 #cell_2, +[data-hoveredClass="4,3"] #row_3_cell_3 #cell_3, +[data-hoveredClass="4,3"] #row_4_cell_1 #cell_1, +[data-hoveredClass="4,3"] #row_4_cell_2 #cell_2, +[data-hoveredClass="4,3"] #row_4_cell_3 #cell_3, +[data-hoveredClass="4,4"] #row_1_cell_1 #cell_1, +[data-hoveredClass="4,4"] #row_1_cell_2 #cell_2, +[data-hoveredClass="4,4"] #row_1_cell_3 #cell_3, +[data-hoveredClass="4,4"] #row_1_cell_4 #cell_4, +[data-hoveredClass="4,4"] #row_2_cell_1 #cell_1, +[data-hoveredClass="4,4"] #row_2_cell_2 #cell_2, +[data-hoveredClass="4,4"] #row_2_cell_3 #cell_3, +[data-hoveredClass="4,4"] #row_2_cell_4 #cell_4, +[data-hoveredClass="4,4"] #row_3_cell_1 #cell_1, +[data-hoveredClass="4,4"] #row_3_cell_2 #cell_2, +[data-hoveredClass="4,4"] #row_3_cell_3 #cell_3, +[data-hoveredClass="4,4"] #row_3_cell_4 #cell_4, +[data-hoveredClass="4,4"] #row_4_cell_1 #cell_1, +[data-hoveredClass="4,4"] #row_4_cell_2 #cell_2, +[data-hoveredClass="4,4"] #row_4_cell_3 #cell_3, +[data-hoveredClass="4,4"] #row_4_cell_4 #cell_4, +[data-hoveredClass="4,5"] #row_1_cell_1 #cell_1, +[data-hoveredClass="4,5"] #row_1_cell_2 #cell_2, +[data-hoveredClass="4,5"] #row_1_cell_3 #cell_3, +[data-hoveredClass="4,5"] #row_1_cell_4 #cell_4, +[data-hoveredClass="4,5"] #row_1_cell_5 #cell_5, +[data-hoveredClass="4,5"] #row_2_cell_1 #cell_1, +[data-hoveredClass="4,5"] #row_2_cell_2 #cell_2, +[data-hoveredClass="4,5"] #row_2_cell_3 #cell_3, +[data-hoveredClass="4,5"] #row_2_cell_4 #cell_4, +[data-hoveredClass="4,5"] #row_2_cell_5 #cell_5, +[data-hoveredClass="4,5"] #row_3_cell_1 #cell_1, +[data-hoveredClass="4,5"] #row_3_cell_2 #cell_2, +[data-hoveredClass="4,5"] #row_3_cell_3 #cell_3, +[data-hoveredClass="4,5"] #row_3_cell_4 #cell_4, +[data-hoveredClass="4,5"] #row_3_cell_5 #cell_5, +[data-hoveredClass="4,5"] #row_4_cell_1 #cell_1, +[data-hoveredClass="4,5"] #row_4_cell_2 #cell_2, +[data-hoveredClass="4,5"] #row_4_cell_3 #cell_3, +[data-hoveredClass="4,5"] #row_4_cell_4 #cell_4, +[data-hoveredClass="4,5"] #row_4_cell_5 #cell_5, +[data-hoveredClass="4,6"] #row_1_cell_1 #cell_1, +[data-hoveredClass="4,6"] #row_1_cell_2 #cell_2, +[data-hoveredClass="4,6"] #row_1_cell_3 #cell_3, +[data-hoveredClass="4,6"] #row_1_cell_4 #cell_4, +[data-hoveredClass="4,6"] #row_1_cell_5 #cell_5, +[data-hoveredClass="4,6"] #row_1_cell_6 #cell_6, +[data-hoveredClass="4,6"] #row_2_cell_1 #cell_1, +[data-hoveredClass="4,6"] #row_2_cell_2 #cell_2, +[data-hoveredClass="4,6"] #row_2_cell_3 #cell_3, +[data-hoveredClass="4,6"] #row_2_cell_4 #cell_4, +[data-hoveredClass="4,6"] #row_2_cell_5 #cell_5, +[data-hoveredClass="4,6"] #row_2_cell_6 #cell_6, +[data-hoveredClass="4,6"] #row_3_cell_1 #cell_1, +[data-hoveredClass="4,6"] #row_3_cell_2 #cell_2, +[data-hoveredClass="4,6"] #row_3_cell_3 #cell_3, +[data-hoveredClass="4,6"] #row_3_cell_4 #cell_4, +[data-hoveredClass="4,6"] #row_3_cell_5 #cell_5, +[data-hoveredClass="4,6"] #row_3_cell_6 #cell_6, +[data-hoveredClass="4,6"] #row_4_cell_1 #cell_1, +[data-hoveredClass="4,6"] #row_4_cell_2 #cell_2, +[data-hoveredClass="4,6"] #row_4_cell_3 #cell_3, +[data-hoveredClass="4,6"] #row_4_cell_4 #cell_4, +[data-hoveredClass="4,6"] #row_4_cell_5 #cell_5, +[data-hoveredClass="4,6"] #row_4_cell_6 #cell_6, +[data-hoveredClass="5,1"] #row_1_cell_1 #cell_1, +[data-hoveredClass="5,1"] #row_2_cell_1 #cell_1, +[data-hoveredClass="5,1"] #row_3_cell_1 #cell_1, +[data-hoveredClass="5,1"] #row_4_cell_1 #cell_1, +[data-hoveredClass="5,1"] #row_5_cell_1 #cell_1, +[data-hoveredClass="5,2"] #row_1_cell_1 #cell_1, +[data-hoveredClass="5,2"] #row_1_cell_2 #cell_2, +[data-hoveredClass="5,2"] #row_2_cell_1 #cell_1, +[data-hoveredClass="5,2"] #row_2_cell_2 #cell_2, +[data-hoveredClass="5,2"] #row_3_cell_1 #cell_1, +[data-hoveredClass="5,2"] #row_3_cell_2 #cell_2, +[data-hoveredClass="5,2"] #row_4_cell_1 #cell_1, +[data-hoveredClass="5,2"] #row_4_cell_2 #cell_2, +[data-hoveredClass="5,2"] #row_5_cell_1 #cell_1, +[data-hoveredClass="5,2"] #row_5_cell_2 #cell_2, +[data-hoveredClass="5,3"] #row_1_cell_1 #cell_1, +[data-hoveredClass="5,3"] #row_1_cell_2 #cell_2, +[data-hoveredClass="5,3"] #row_1_cell_3 #cell_3, +[data-hoveredClass="5,3"] #row_2_cell_1 #cell_1, +[data-hoveredClass="5,3"] #row_2_cell_2 #cell_2, +[data-hoveredClass="5,3"] #row_2_cell_3 #cell_3, +[data-hoveredClass="5,3"] #row_3_cell_1 #cell_1, +[data-hoveredClass="5,3"] #row_3_cell_2 #cell_2, +[data-hoveredClass="5,3"] #row_3_cell_3 #cell_3, +[data-hoveredClass="5,3"] #row_4_cell_1 #cell_1, +[data-hoveredClass="5,3"] #row_4_cell_2 #cell_2, +[data-hoveredClass="5,3"] #row_4_cell_3 #cell_3, +[data-hoveredClass="5,3"] #row_5_cell_1 #cell_1, +[data-hoveredClass="5,3"] #row_5_cell_2 #cell_2, +[data-hoveredClass="5,3"] #row_5_cell_3 #cell_3, +[data-hoveredClass="5,4"] #row_1_cell_1 #cell_1, +[data-hoveredClass="5,4"] #row_1_cell_2 #cell_2, +[data-hoveredClass="5,4"] #row_1_cell_3 #cell_3, +[data-hoveredClass="5,4"] #row_1_cell_4 #cell_4, +[data-hoveredClass="5,4"] #row_2_cell_1 #cell_1, +[data-hoveredClass="5,4"] #row_2_cell_2 #cell_2, +[data-hoveredClass="5,4"] #row_2_cell_3 #cell_3, +[data-hoveredClass="5,4"] #row_2_cell_4 #cell_4, +[data-hoveredClass="5,4"] #row_3_cell_1 #cell_1, +[data-hoveredClass="5,4"] #row_3_cell_2 #cell_2, +[data-hoveredClass="5,4"] #row_3_cell_3 #cell_3, +[data-hoveredClass="5,4"] #row_3_cell_4 #cell_4, +[data-hoveredClass="5,4"] #row_4_cell_1 #cell_1, +[data-hoveredClass="5,4"] #row_4_cell_2 #cell_2, +[data-hoveredClass="5,4"] #row_4_cell_3 #cell_3, +[data-hoveredClass="5,4"] #row_4_cell_4 #cell_4, +[data-hoveredClass="5,4"] #row_5_cell_1 #cell_1, +[data-hoveredClass="5,4"] #row_5_cell_2 #cell_2, +[data-hoveredClass="5,4"] #row_5_cell_3 #cell_3, +[data-hoveredClass="5,4"] #row_5_cell_4 #cell_4, +[data-hoveredClass="5,5"] #row_1_cell_1 #cell_1, +[data-hoveredClass="5,5"] #row_1_cell_2 #cell_2, +[data-hoveredClass="5,5"] #row_1_cell_3 #cell_3, +[data-hoveredClass="5,5"] #row_1_cell_4 #cell_4, +[data-hoveredClass="5,5"] #row_1_cell_5 #cell_5, +[data-hoveredClass="5,5"] #row_2_cell_1 #cell_1, +[data-hoveredClass="5,5"] #row_2_cell_2 #cell_2, +[data-hoveredClass="5,5"] #row_2_cell_3 #cell_3, +[data-hoveredClass="5,5"] #row_2_cell_4 #cell_4, +[data-hoveredClass="5,5"] #row_2_cell_5 #cell_5, +[data-hoveredClass="5,5"] #row_3_cell_1 #cell_1, +[data-hoveredClass="5,5"] #row_3_cell_2 #cell_2, +[data-hoveredClass="5,5"] #row_3_cell_3 #cell_3, +[data-hoveredClass="5,5"] #row_3_cell_4 #cell_4, +[data-hoveredClass="5,5"] #row_3_cell_5 #cell_5, +[data-hoveredClass="5,5"] #row_4_cell_1 #cell_1, +[data-hoveredClass="5,5"] #row_4_cell_2 #cell_2, +[data-hoveredClass="5,5"] #row_4_cell_3 #cell_3, +[data-hoveredClass="5,5"] #row_4_cell_4 #cell_4, +[data-hoveredClass="5,5"] #row_4_cell_5 #cell_5, +[data-hoveredClass="5,5"] #row_5_cell_1 #cell_1, +[data-hoveredClass="5,5"] #row_5_cell_2 #cell_2, +[data-hoveredClass="5,5"] #row_5_cell_3 #cell_3, +[data-hoveredClass="5,5"] #row_5_cell_4 #cell_4, +[data-hoveredClass="5,5"] #row_5_cell_5 #cell_5, +[data-hoveredClass="5,6"] #row_1_cell_1 #cell_1, +[data-hoveredClass="5,6"] #row_1_cell_2 #cell_2, +[data-hoveredClass="5,6"] #row_1_cell_3 #cell_3, +[data-hoveredClass="5,6"] #row_1_cell_4 #cell_4, +[data-hoveredClass="5,6"] #row_1_cell_5 #cell_5, +[data-hoveredClass="5,6"] #row_1_cell_6 #cell_6, +[data-hoveredClass="5,6"] #row_2_cell_1 #cell_1, +[data-hoveredClass="5,6"] #row_2_cell_2 #cell_2, +[data-hoveredClass="5,6"] #row_2_cell_3 #cell_3, +[data-hoveredClass="5,6"] #row_2_cell_4 #cell_4, +[data-hoveredClass="5,6"] #row_2_cell_5 #cell_5, +[data-hoveredClass="5,6"] #row_2_cell_6 #cell_6, +[data-hoveredClass="5,6"] #row_3_cell_1 #cell_1, +[data-hoveredClass="5,6"] #row_3_cell_2 #cell_2, +[data-hoveredClass="5,6"] #row_3_cell_3 #cell_3, +[data-hoveredClass="5,6"] #row_3_cell_4 #cell_4, +[data-hoveredClass="5,6"] #row_3_cell_5 #cell_5, +[data-hoveredClass="5,6"] #row_3_cell_6 #cell_6, +[data-hoveredClass="5,6"] #row_4_cell_1 #cell_1, +[data-hoveredClass="5,6"] #row_4_cell_2 #cell_2, +[data-hoveredClass="5,6"] #row_4_cell_3 #cell_3, +[data-hoveredClass="5,6"] #row_4_cell_4 #cell_4, +[data-hoveredClass="5,6"] #row_4_cell_5 #cell_5, +[data-hoveredClass="5,6"] #row_4_cell_6 #cell_6, +[data-hoveredClass="5,6"] #row_5_cell_1 #cell_1, +[data-hoveredClass="5,6"] #row_5_cell_2 #cell_2, +[data-hoveredClass="5,6"] #row_5_cell_3 #cell_3, +[data-hoveredClass="5,6"] #row_5_cell_4 #cell_4, +[data-hoveredClass="5,6"] #row_5_cell_5 #cell_5, +[data-hoveredClass="5,6"] #row_5_cell_6 #cell_6, +[data-hoveredClass="6,1"] #row_1_cell_1 #cell_1, +[data-hoveredClass="6,1"] #row_2_cell_1 #cell_1, +[data-hoveredClass="6,1"] #row_3_cell_1 #cell_1, +[data-hoveredClass="6,1"] #row_4_cell_1 #cell_1, +[data-hoveredClass="6,1"] #row_5_cell_1 #cell_1, +[data-hoveredClass="6,1"] #row_6_cell_1 #cell_1, +[data-hoveredClass="6,2"] #row_1_cell_1 #cell_1, +[data-hoveredClass="6,2"] #row_1_cell_2 #cell_2, +[data-hoveredClass="6,2"] #row_2_cell_1 #cell_1, +[data-hoveredClass="6,2"] #row_2_cell_2 #cell_2, +[data-hoveredClass="6,2"] #row_3_cell_1 #cell_1, +[data-hoveredClass="6,2"] #row_3_cell_2 #cell_2, +[data-hoveredClass="6,2"] #row_4_cell_1 #cell_1, +[data-hoveredClass="6,2"] #row_4_cell_2 #cell_2, +[data-hoveredClass="6,2"] #row_5_cell_1 #cell_1, +[data-hoveredClass="6,2"] #row_5_cell_2 #cell_2, +[data-hoveredClass="6,2"] #row_6_cell_1 #cell_1, +[data-hoveredClass="6,2"] #row_6_cell_2 #cell_2, +[data-hoveredClass="6,3"] #row_1_cell_1 #cell_1, +[data-hoveredClass="6,3"] #row_1_cell_2 #cell_2, +[data-hoveredClass="6,3"] #row_1_cell_3 #cell_3, +[data-hoveredClass="6,3"] #row_2_cell_1 #cell_1, +[data-hoveredClass="6,3"] #row_2_cell_2 #cell_2, +[data-hoveredClass="6,3"] #row_2_cell_3 #cell_3, +[data-hoveredClass="6,3"] #row_3_cell_1 #cell_1, +[data-hoveredClass="6,3"] #row_3_cell_2 #cell_2, +[data-hoveredClass="6,3"] #row_3_cell_3 #cell_3, +[data-hoveredClass="6,3"] #row_4_cell_1 #cell_1, +[data-hoveredClass="6,3"] #row_4_cell_2 #cell_2, +[data-hoveredClass="6,3"] #row_4_cell_3 #cell_3, +[data-hoveredClass="6,3"] #row_5_cell_1 #cell_1, +[data-hoveredClass="6,3"] #row_5_cell_2 #cell_2, +[data-hoveredClass="6,3"] #row_5_cell_3 #cell_3, +[data-hoveredClass="6,3"] #row_6_cell_1 #cell_1, +[data-hoveredClass="6,3"] #row_6_cell_2 #cell_2, +[data-hoveredClass="6,3"] #row_6_cell_3 #cell_3, +[data-hoveredClass="6,4"] #row_1_cell_1 #cell_1, +[data-hoveredClass="6,4"] #row_1_cell_2 #cell_2, +[data-hoveredClass="6,4"] #row_1_cell_3 #cell_3, +[data-hoveredClass="6,4"] #row_1_cell_4 #cell_4, +[data-hoveredClass="6,4"] #row_2_cell_1 #cell_1, +[data-hoveredClass="6,4"] #row_2_cell_2 #cell_2, +[data-hoveredClass="6,4"] #row_2_cell_3 #cell_3, +[data-hoveredClass="6,4"] #row_2_cell_4 #cell_4, +[data-hoveredClass="6,4"] #row_3_cell_1 #cell_1, +[data-hoveredClass="6,4"] #row_3_cell_2 #cell_2, +[data-hoveredClass="6,4"] #row_3_cell_3 #cell_3, +[data-hoveredClass="6,4"] #row_3_cell_4 #cell_4, +[data-hoveredClass="6,4"] #row_4_cell_1 #cell_1, +[data-hoveredClass="6,4"] #row_4_cell_2 #cell_2, +[data-hoveredClass="6,4"] #row_4_cell_3 #cell_3, +[data-hoveredClass="6,4"] #row_4_cell_4 #cell_4, +[data-hoveredClass="6,4"] #row_5_cell_1 #cell_1, +[data-hoveredClass="6,4"] #row_5_cell_2 #cell_2, +[data-hoveredClass="6,4"] #row_5_cell_3 #cell_3, +[data-hoveredClass="6,4"] #row_5_cell_4 #cell_4, +[data-hoveredClass="6,4"] #row_6_cell_1 #cell_1, +[data-hoveredClass="6,4"] #row_6_cell_2 #cell_2, +[data-hoveredClass="6,4"] #row_6_cell_3 #cell_3, +[data-hoveredClass="6,4"] #row_6_cell_4 #cell_4, +[data-hoveredClass="6,5"] #row_1_cell_1 #cell_1, +[data-hoveredClass="6,5"] #row_1_cell_2 #cell_2, +[data-hoveredClass="6,5"] #row_1_cell_3 #cell_3, +[data-hoveredClass="6,5"] #row_1_cell_4 #cell_4, +[data-hoveredClass="6,5"] #row_1_cell_5 #cell_5, +[data-hoveredClass="6,5"] #row_2_cell_1 #cell_1, +[data-hoveredClass="6,5"] #row_2_cell_2 #cell_2, +[data-hoveredClass="6,5"] #row_2_cell_3 #cell_3, +[data-hoveredClass="6,5"] #row_2_cell_4 #cell_4, +[data-hoveredClass="6,5"] #row_2_cell_5 #cell_5, +[data-hoveredClass="6,5"] #row_3_cell_1 #cell_1, +[data-hoveredClass="6,5"] #row_3_cell_2 #cell_2, +[data-hoveredClass="6,5"] #row_3_cell_3 #cell_3, +[data-hoveredClass="6,5"] #row_3_cell_4 #cell_4, +[data-hoveredClass="6,5"] #row_3_cell_5 #cell_5, +[data-hoveredClass="6,5"] #row_4_cell_1 #cell_1, +[data-hoveredClass="6,5"] #row_4_cell_2 #cell_2, +[data-hoveredClass="6,5"] #row_4_cell_3 #cell_3, +[data-hoveredClass="6,5"] #row_4_cell_4 #cell_4, +[data-hoveredClass="6,5"] #row_4_cell_5 #cell_5, +[data-hoveredClass="6,5"] #row_5_cell_1 #cell_1, +[data-hoveredClass="6,5"] #row_5_cell_2 #cell_2, +[data-hoveredClass="6,5"] #row_5_cell_3 #cell_3, +[data-hoveredClass="6,5"] #row_5_cell_4 #cell_4, +[data-hoveredClass="6,5"] #row_5_cell_5 #cell_5, +[data-hoveredClass="6,5"] #row_6_cell_1 #cell_1, +[data-hoveredClass="6,5"] #row_6_cell_2 #cell_2, +[data-hoveredClass="6,5"] #row_6_cell_3 #cell_3, +[data-hoveredClass="6,5"] #row_6_cell_4 #cell_4, +[data-hoveredClass="6,5"] #row_6_cell_5 #cell_5, +[data-hoveredClass="6,6"] #row_1_cell_1 #cell_1, +[data-hoveredClass="6,6"] #row_1_cell_2 #cell_2, +[data-hoveredClass="6,6"] #row_1_cell_3 #cell_3, +[data-hoveredClass="6,6"] #row_1_cell_4 #cell_4, +[data-hoveredClass="6,6"] #row_1_cell_5 #cell_5, +[data-hoveredClass="6,6"] #row_1_cell_6 #cell_6, +[data-hoveredClass="6,6"] #row_2_cell_1 #cell_1, +[data-hoveredClass="6,6"] #row_2_cell_2 #cell_2, +[data-hoveredClass="6,6"] #row_2_cell_3 #cell_3, +[data-hoveredClass="6,6"] #row_2_cell_4 #cell_4, +[data-hoveredClass="6,6"] #row_2_cell_5 #cell_5, +[data-hoveredClass="6,6"] #row_2_cell_6 #cell_6, +[data-hoveredClass="6,6"] #row_3_cell_1 #cell_1, +[data-hoveredClass="6,6"] #row_3_cell_2 #cell_2, +[data-hoveredClass="6,6"] #row_3_cell_3 #cell_3, +[data-hoveredClass="6,6"] #row_3_cell_4 #cell_4, +[data-hoveredClass="6,6"] #row_3_cell_5 #cell_5, +[data-hoveredClass="6,6"] #row_3_cell_6 #cell_6, +[data-hoveredClass="6,6"] #row_4_cell_1 #cell_1, +[data-hoveredClass="6,6"] #row_4_cell_2 #cell_2, +[data-hoveredClass="6,6"] #row_4_cell_3 #cell_3, +[data-hoveredClass="6,6"] #row_4_cell_4 #cell_4, +[data-hoveredClass="6,6"] #row_4_cell_5 #cell_5, +[data-hoveredClass="6,6"] #row_4_cell_6 #cell_6, +[data-hoveredClass="6,6"] #row_5_cell_1 #cell_1, +[data-hoveredClass="6,6"] #row_5_cell_2 #cell_2, +[data-hoveredClass="6,6"] #row_5_cell_3 #cell_3, +[data-hoveredClass="6,6"] #row_5_cell_4 #cell_4, +[data-hoveredClass="6,6"] #row_5_cell_5 #cell_5, +[data-hoveredClass="6,6"] #row_5_cell_6 #cell_6, +[data-hoveredClass="6,6"] #row_6_cell_1 #cell_1, +[data-hoveredClass="6,6"] #row_6_cell_2 #cell_2, +[data-hoveredClass="6,6"] #row_6_cell_3 #cell_3, +[data-hoveredClass="6,6"] #row_6_cell_4 #cell_4, +[data-hoveredClass="6,6"] #row_6_cell_5 #cell_5, +[data-hoveredClass="6,6"] #row_6_cell_6 #cell_6 { + background: #d5e4f9; + border: 1px solid #c0cffd; +} \ No newline at end of file diff --git a/table.js b/table.js new file mode 100644 index 0000000..5730465 --- /dev/null +++ b/table.js @@ -0,0 +1,324 @@ +import {create} from './documentUtils'; +import './styles/table.pcss'; + +const CSS = { + table: 'tc-table', + inputField: 'tc-table__inp', + cell: 'tc-table__cell', + wrapper: 'tc-table__wrap', + area: 'tc-table__area', + highlight: 'tc-table__highlight' +}; + +/** + * Generates and manages _table contents. + */ +export class Table { + /** + * Creates + */ + constructor() { + this._numberOfColumns = 0; + this._numberOfRows = 0; + this._element = this._createTableWrapper(); + this._table = this._element.querySelector('table'); + this._selectedCell = null; + this._attachEvents(); + } + + /** + * returns selected/editable cell or null if row is not selected + * @return {HTMLElement|null} + */ + get selectedCell() { + return this._selectedCell; + } + + /** + * sets a selected cell and highlights it + * @param cell - new current cell + */ + set selectedCell(cell) { + if (this._selectedCell) { + this._selectedCell.classList.remove(CSS.highlight); + } + + this._selectedCell = cell; + + if (this._selectedCell) { + this._selectedCell.classList.add(CSS.highlight); + } + } + + /** + * returns current a row that contains current cell + * or null if no cell selected + * @returns {HTMLElement|null} + */ + get selectedRow() { + if (!this.selectedCell) return null; + + return this.selectedCell.closest('tr'); + } + + /** + * Inserts column to the right from currently selected cell + */ + insertColumnAfter() { + this.insertColumn(1); + this.focusCellOnSelectedCell(); + } + + /** + * Inserts column to the left from currently selected cell + */ + insertColumnBefore() { + this.insertColumn(); + this.focusCellOnSelectedCell(); + } + + /** + * Inserts new row below a current row + */ + insertRowBefore() { + this.insertRow(); + this.focusCellOnSelectedCell(); + } + + /** + * Inserts row above a current row + */ + insertRowAfter() { + this.insertRow(1); + this.focusCellOnSelectedCell(); + } + + /** + * Insert a column into table relatively to a current cell + * @param {number} direction - direction of insertion. 0 is insertion before, 1 is insertion after + */ + insertColumn(direction = 0) { + direction = Math.min(Math.max(direction, 0), 1); + + const insertionIndex = this.selectedCell + ? this.selectedCell.cellIndex + direction + : 0; + + this._numberOfColumns++; + /** Add cell in each row */ + const rows = this._table.rows; + + for (let i = 0; i < rows.length; i++) { + const cell = rows[i].insertCell(insertionIndex); + + this._fillCell(cell); + } + }; + + /** + * Remove column that includes currently selected cell + * Do nothing if there's no current cell + */ + deleteColumn() { + if (!this.selectedCell) return; + + const removalIndex = this.selectedCell.cellIndex; + + this._numberOfColumns--; + /** Delete cell in each row */ + const rows = this._table.rows; + + for (let i = 0; i < rows.length; i++) { + rows[i].deleteCell(removalIndex); + } + }; + + /** + * Insert a row into table relatively to a current cell + * @param {number} direction - direction of insertion. 0 is insertion before, 1 is insertion after + * @return {HTMLElement} row + */ + insertRow(direction = 0) { + direction = Math.min(Math.max(direction, 0), 1); + + const insertionIndex = this.selectedRow + ? this.selectedRow.rowIndex + direction + : 0; + + const row = this._table.insertRow(insertionIndex); + + this._numberOfRows++; + + this._fillRow(row); + return row; + }; + + /** + * Remove row in table on index place + * @param {number} index - number in the array of columns, where new column to insert,-1 if insert at the end + */ + deleteRow(index = -1) { + if (!this.selectedRow) return; + + const removalIndex = this.selectedRow.rowIndex; + + this._table.deleteRow(removalIndex); + this._numberOfRows--; + }; + + /** + * get html table wrapper + * @return {HTMLElement} + */ + get htmlElement() { + return this._element; + } + + /** + * get real table tag + * @return {HTMLElement} + */ + get body() { + return this._table; + } + + /** + * @private + * + * Creates table structure + * @return {HTMLElement} tbody - where rows will be + */ + _createTableWrapper() { + return create('div', [ CSS.wrapper ], null, [ + create('table', [ CSS.table ]) + // This function can be updated so that it will render the table with the give config instead of 3x3 + ]); + } + + /** + * @private + * + * Create editable area of cell + * @return {HTMLElement} - the area + */ + _createContenteditableArea() { + return create('div', [ CSS.inputField ], { contenteditable: 'true' }); + } + + /** + * @private + * + * Fills the empty cell of the editable area + * @param {HTMLElement} cell - empty cell + */ + _fillCell(cell) { + cell.classList.add(CSS.cell); + const content = this._createContenteditableArea(); + cell.appendChild(create('div', [ CSS.area ], null, [ content ])); + } + + /** + * @private + * + * Fills the empty row with cells in the size of numberOfColumns + * @param row = the empty row + */ + _fillRow(row) { + for (let i = 0; i < this._numberOfColumns; i++) { + const cell = row.insertCell(); + + this._fillCell(cell); + } + } + + /** + * @private + * + * hang necessary events + */ + _attachEvents() { + this._table.addEventListener('focus', (event) => { + this._focusEditField(event); + }, true); + + this._table.addEventListener('keydown', (event) => { + this._pressedEnterInEditField(event); + }); + + this._table.addEventListener('click', (event) => { + this._clickedOnCell(event); + }); + + this.htmlElement.addEventListener('keydown', (event) => { + this._containerKeydown(event); + }); + } + + /** + * @private + * + * When you focus on an editable field, remembers the cell + * @param {FocusEvent} event + */ + _focusEditField(event) { + this.selectedCell = event.target.tagName === 'TD' + ? event.target + : event.target.closest('td'); + } + focusCellOnSelectedCell() { + this.selectedCell.childNodes[0].childNodes[0].focus(); + } + /** + * @private + * + * When enter is pressed when editing a field + * @param {KeyboardEvent} event + */ + _pressedEnterInEditField(event) { + if (!event.target.classList.contains(CSS.inputField)) { + return; + } + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + } + } + + /** + * @private + * + * When clicking on a cell + * @param {MouseEvent} event + */ + _clickedOnCell(event) { + if (!event.target.classList.contains(CSS.cell)) { + return; + } + const content = event.target.querySelector('.' + CSS.inputField); + + content.focus(); + } + + /** + * @private + * + * detects button presses when editing a table's content + * @param {KeyboardEvent} event + */ + _containerKeydown(event) { + if (event.key === 'Enter' && event.ctrlKey) { + this._containerEnterPressed(event); + } + } + + /** + * @private + * + * if "Ctrl + Enter" is pressed then create new line under current and focus it + * @param {KeyboardEvent} event + */ + _containerEnterPressed(event) { + const newRow = this.insertRow(1); + + newRow.cells[0].click(); + } +} diff --git a/tableConstructor.js b/tableConstructor.js new file mode 100644 index 0000000..57349e2 --- /dev/null +++ b/tableConstructor.js @@ -0,0 +1,108 @@ +import './styles/table-constructor.pcss'; +import {create} from './documentUtils'; +import {Table} from './table'; + +const CSS = { + editor: 'tc-editor', + toolBarHor: 'tc-toolbar--hor', + toolBarVer: 'tc-toolbar--ver', + inputField: 'tc-table__inp' +}; + +/** + * Entry point. Controls table and give API to user + */ +export class TableConstructor { + /** + * Creates + * @param {TableData} data - previously saved data for insert in table + * @param {object} config - configuration of table + * @param {object} api - Editor.js API + */ + constructor(data, config, api) { + /** creating table */ + this._table = new Table(); + const size = this._resizeTable(data, config); + + this._fillTable(data, size); + + /** creating container around table */ + this._container = create('div', [CSS.editor, api.styles && api.styles.block], null, [ + this._table.htmlElement + ]); + } + + /** + * returns html element of TableConstructor; + * @return {HTMLElement} + */ + get htmlElement() { + return this._container; + } + + /** + * Returns instance of Table + * @returns {Table} + */ + get table() { + return this._table; + } + + /** + * @private + * + * Fill table data passed to the constructor + * @param {TableData} data - data for insert in table + * @param {{rows: number, cols: number}} size - contains number of rows and cols + */ + _fillTable(data, size) { + if (data.content !== undefined) { + for (let i = 0; i < size.rows && i < data.content.length; i++) { + for (let j = 0; j < size.cols && j < data.content[i].length; j++) { + // get current cell and her editable part + const input = this._table.body.rows[i].cells[j].querySelector('.' + CSS.inputField); + + input.innerHTML = data.content[i][j]; + } + } + } + } + + /** + * @private + * + * resize to match config or transmitted data + * @param {TableData} data - data for inserting to the table + * @param {object} config - configuration of table + * @param {number|string} config.rows - number of rows in configuration + * @param {number|string} config.cols - number of cols in configuration + * @return {{rows: number, cols: number}} - number of cols and rows + */ + _resizeTable(data, config) { + const isValidArray = Array.isArray(data.content); + const isNotEmptyArray = isValidArray ? data.content.length : false; + const contentRows = isValidArray ? data.content.length : undefined; + const contentCols = isNotEmptyArray ? data.content[0].length : undefined; + const parsedRows = Number.parseInt(config.rows); + const parsedCols = Number.parseInt(config.cols); + // value of config have to be positive number + const configRows = !isNaN(parsedRows) && parsedRows > 0 ? parsedRows : undefined; + const configCols = !isNaN(parsedCols) && parsedCols > 0 ? parsedCols : undefined; + const defaultRows = 1; + const defaultCols = 1; + const rows = contentRows || configRows || defaultRows; + const cols = contentCols || configCols || defaultCols; + + for (let i = 0; i < rows; i++) { + this._table.insertRow(); + } + for (let i = 0; i < cols; i++) { + this._table.insertColumn(); + } + + return { + rows: rows, + cols: cols + }; + } +}