@itfin/components 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/ITFComponents.common.js +87 -0
  2. package/dist/ITFComponents.common.js.map +1 -0
  3. package/dist/ITFComponents.umd.js +98 -0
  4. package/dist/ITFComponents.umd.js.map +1 -0
  5. package/dist/ITFComponents.umd.min.js +2 -0
  6. package/dist/ITFComponents.umd.min.js.map +1 -0
  7. package/dist/demo.html +1 -0
  8. package/package.json +1 -1
  9. package/src/assets/scss/_css_variables.scss +7 -2
  10. package/src/assets/scss/_dark-theme.scss +12 -2
  11. package/src/assets/scss/_variables.scss +9 -34
  12. package/src/assets/scss/components/_button.scss +10 -29
  13. package/src/assets/scss/components/_checkbox.scss +0 -9
  14. package/src/assets/scss/components/_datepicker.scss +3 -3
  15. package/src/assets/scss/components/_pagination.scss +1 -4
  16. package/src/assets/scss/components/_popover.scss +0 -22
  17. package/src/assets/scss/components/_segmeneted-control.scss +8 -19
  18. package/src/assets/scss/components/_select.scss +8 -6
  19. package/src/assets/scss/components/_text-field.scss +7 -9
  20. package/src/assets/scss/components/select/_dropdown-menu.scss +0 -1
  21. package/src/assets/scss/components/select/_dropdown-toggle.scss +1 -0
  22. package/src/assets/scss/directives/tooltip.scss +5 -10
  23. package/src/assets/scss/main.scss +0 -48
  24. package/src/components/alert/AlertBanner.vue +70 -14
  25. package/src/components/card/BentoGrid.vue +2 -0
  26. package/src/components/checkbox/NestedCheckboxGroup.vue +109 -0
  27. package/src/components/editor/plugins.js +1012 -0
  28. package/src/components/filter/FilterBadge.vue +3 -3
  29. package/src/components/filter/FilterFacetsList.vue +13 -7
  30. package/src/components/icon/components/nomi-.DS_Store +0 -0
  31. package/src/components/icon/components/nomi-approval-chain.vue +5 -0
  32. package/src/components/icon/components/nomi-budget.vue +4 -0
  33. package/src/components/icon/components/nomi-close-alt.vue +5 -0
  34. package/src/components/icon/components/nomi-cog-lightning.vue +5 -0
  35. package/src/components/icon/components/nomi-comment-add.vue +5 -0
  36. package/src/components/icon/components/nomi-comment.vue +4 -0
  37. package/src/components/icon/components/nomi-comments.vue +5 -0
  38. package/src/components/icon/components/nomi-copy.vue +5 -0
  39. package/src/components/icon/components/nomi-delta.vue +7 -0
  40. package/src/components/icon/components/nomi-dollar.vue +4 -0
  41. package/src/components/icon/components/nomi-expense-requests.vue +5 -0
  42. package/src/components/icon/components/nomi-file-doc.vue +7 -0
  43. package/src/components/icon/components/nomi-file-excel.vue +9 -0
  44. package/src/components/icon/components/nomi-file-image.vue +6 -0
  45. package/src/components/icon/components/nomi-file-pdf.vue +5 -0
  46. package/src/components/icon/components/nomi-light-bulb.vue +4 -0
  47. package/src/components/icon/components/nomi-money-alt.vue +4 -0
  48. package/src/components/icon/components/nomi-money-requests.vue +12 -0
  49. package/src/components/icon/components/nomi-pending.vue +4 -0
  50. package/src/components/icon/components/nomi-plus.vue +5 -0
  51. package/src/components/icon/components/nomi-secure.vue +4 -0
  52. package/src/components/icon/components/nomi-stop.vue +4 -0
  53. package/src/components/icon/components/nomi-thumbs-down.vue +4 -0
  54. package/src/components/icon/components/nomi-thumbs-up.vue +4 -0
  55. package/src/components/icon/components/nomi-undo.vue +4 -0
  56. package/src/components/icon/components/nomi-user-settings.vue +5 -0
  57. package/src/components/icon/icons.js +397 -371
  58. package/src/components/icon/new-icons/approval-chain.svg +4 -0
  59. package/src/components/icon/new-icons/budget.svg +3 -0
  60. package/src/components/icon/new-icons/close-alt.svg +4 -0
  61. package/src/components/icon/new-icons/cog-lightning.svg +4 -0
  62. package/src/components/icon/new-icons/comment-add.svg +4 -0
  63. package/src/components/icon/new-icons/comment.svg +3 -0
  64. package/src/components/icon/new-icons/comments.svg +4 -0
  65. package/src/components/icon/new-icons/copy.svg +4 -0
  66. package/src/components/icon/new-icons/delta.svg +6 -0
  67. package/src/components/icon/new-icons/dollar.svg +3 -0
  68. package/src/components/icon/new-icons/expense-requests.svg +4 -0
  69. package/src/components/icon/new-icons/file-doc.svg +6 -0
  70. package/src/components/icon/new-icons/file-excel.svg +8 -0
  71. package/src/components/icon/new-icons/file-image.svg +5 -0
  72. package/src/components/icon/new-icons/file-pdf.svg +4 -0
  73. package/src/components/icon/new-icons/light-bulb.svg +3 -0
  74. package/src/components/icon/new-icons/money-alt.svg +3 -0
  75. package/src/components/icon/new-icons/money-requests.svg +11 -0
  76. package/src/components/icon/new-icons/pending.svg +3 -0
  77. package/src/components/icon/new-icons/plus.svg +4 -0
  78. package/src/components/icon/new-icons/secure.svg +3 -0
  79. package/src/components/icon/new-icons/stop.svg +3 -0
  80. package/src/components/icon/new-icons/thumbs-down.svg +3 -0
  81. package/src/components/icon/new-icons/thumbs-up.svg +3 -0
  82. package/src/components/icon/new-icons/undo.svg +3 -0
  83. package/src/components/icon/new-icons/user-settings.svg +4 -0
  84. package/src/components/modal/Modal.vue +6 -1
  85. package/src/components/pagination/Pagination2.vue +4 -3
  86. package/src/components/panels/Panel.vue +2 -1
  87. package/src/components/panels/PanelItemEdit.vue +91 -10
  88. package/src/components/panels/PanelList.vue +3 -0
  89. package/src/components/panels/helpers.ts +14 -4
  90. package/src/components/text-field/TextField.vue +8 -0
  91. package/src/components/view/View.vue +14 -0
  92. package/src/locales/en.js +7 -1
  93. package/src/locales/pl.js +2 -1
  94. package/src/locales/uk.js +7 -1
@@ -0,0 +1,1012 @@
1
+ /**
2
+ * De-minified (beautified + renamed) single-file version of the provided code.
3
+ *
4
+ * Notes:
5
+ * 1) Original snippet references a helper `ct(this, 'name', fn)` (likely from bundler/Babel).
6
+ * Here we implement `ct` as a simple property assignment: obj[name] = fn.
7
+ * 2) This file is designed to keep behavior equivalent to your snippet, but with readable names.
8
+ * 3) This looks like an Editor.js tool (toolbox/render/save/renderSettings + sanitize flags).
9
+ */
10
+
11
+ // -----------------------------------------------------------------------------
12
+ // Helpers
13
+ // -----------------------------------------------------------------------------
14
+
15
+ function isNotNull(value) {
16
+ return value != null;
17
+ }
18
+
19
+ /**
20
+ * Create DOM element with classes, attributes, children.
21
+ * @param {string} tag
22
+ * @param {string[] | null} classes
23
+ * @param {Record<string,string> | null} attrs
24
+ * @param {Node[] | null} children
25
+ */
26
+ function createElement(tag, classes = null, attrs = null, children = null) {
27
+ const el = document.createElement(tag);
28
+
29
+ if (isNotNull(classes)) {
30
+ for (let i = 0; i < classes.length; i++) {
31
+ if (isNotNull(classes[i])) el.classList.add(classes[i]);
32
+ }
33
+ }
34
+
35
+ if (isNotNull(attrs)) {
36
+ for (const key in attrs) {
37
+ el.setAttribute(key, attrs[key]);
38
+ }
39
+ }
40
+
41
+ if (isNotNull(children)) {
42
+ for (let i = 0; i < children.length; i++) {
43
+ if (isNotNull(children[i])) el.appendChild(children[i]);
44
+ }
45
+ }
46
+
47
+ return el;
48
+ }
49
+
50
+ /**
51
+ * Helper used in original minified build to assign methods.
52
+ * Equivalent to: this[name] = fn;
53
+ */
54
+ function ct(obj, name, fn) {
55
+ obj[name] = fn;
56
+ return fn;
57
+ }
58
+
59
+ /**
60
+ * Get absolute page coordinates of an element
61
+ */
62
+ function getAbsRect(el) {
63
+ const r = el.getBoundingClientRect();
64
+ return {
65
+ y1: Math.floor(r.top + window.pageYOffset),
66
+ x1: Math.floor(r.left + window.pageXOffset),
67
+ x2: Math.floor(r.right + window.pageXOffset),
68
+ y2: Math.floor(r.bottom + window.pageYOffset),
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Detect near-edge side based on pointer coordinates
74
+ * (within about 11px tolerance as in original)
75
+ */
76
+ function detectNearSide(rect, pageX, pageY) {
77
+ let side;
78
+
79
+ if (pageX - rect.x1 >= -1 && pageX - rect.x1 <= 11) side = "left";
80
+ if (rect.x2 - pageX >= -1 && rect.x2 - pageX <= 11) side = "right";
81
+ if (pageY - rect.y1 >= -1 && pageY - rect.y1 <= 11) side = "top";
82
+ if (rect.y2 - pageY >= -1 && rect.y2 - pageY <= 11) side = "bottom";
83
+
84
+ return side;
85
+ }
86
+
87
+ // -----------------------------------------------------------------------------
88
+ // Resize columns feature
89
+ // -----------------------------------------------------------------------------
90
+
91
+ const RESIZE_CSS = {
92
+ resizedColumnHandle: "tc-table__resize_column",
93
+ };
94
+
95
+ class ColumnResizer {
96
+ /**
97
+ * @param {TableUI} table
98
+ */
99
+ constructor(table) {
100
+ this.table = table;
101
+
102
+ this.active = null;
103
+ this.activeIndex = 0;
104
+ this.startX = 0;
105
+ this.width = 0;
106
+
107
+ this.widthFirst = 0;
108
+ this.widthSecond = 0;
109
+
110
+ ct(this, "getIndex", (evt) => {
111
+ const handles = this.table.body.querySelectorAll(`.${RESIZE_CSS.resizedColumnHandle}`);
112
+ for (let i = 0; i < handles.length; i += 1) {
113
+ if (handles[i] === evt.target) return i;
114
+ }
115
+ return -1;
116
+ });
117
+
118
+ ct(this, "onDragStart", (evt) => {
119
+ const index = this.getIndex(evt);
120
+
121
+ this.startX = evt.pageX;
122
+ this.active = evt.target;
123
+ this.activeIndex = index;
124
+
125
+ this.width = this.table.body.offsetWidth;
126
+
127
+ const [w1, w2] = this.getWidthCols();
128
+ this.widthFirst = w1;
129
+ this.widthSecond = w2;
130
+
131
+ document.body.style.cursor = "col-resize";
132
+
133
+ if (evt.preventDefault) evt.preventDefault();
134
+ if (evt.stopPropagation) evt.stopPropagation();
135
+ });
136
+
137
+ ct(this, "onDrag", (evt) => {
138
+ if (this.active) this.move(evt.pageX - this.startX);
139
+ });
140
+
141
+ ct(this, "onDragEnd", () => {
142
+ this.active = null;
143
+ document.body.style.cursor = "auto";
144
+ });
145
+
146
+ ct(this, "move", (deltaX) => {
147
+ const [col1, col2] = this.getCols();
148
+
149
+ let w1Percent = (this.widthFirst / this.width + deltaX / this.width) * 100;
150
+ let w2Percent = (this.widthSecond / this.width - deltaX / this.width) * 100;
151
+
152
+ // minimum 5% each as in original
153
+ if (w1Percent >= 5 && w2Percent >= 5) {
154
+ col1.style.width = `${w1Percent}%`;
155
+ col2.style.width = `${w2Percent}%`;
156
+ }
157
+ });
158
+
159
+ ct(this, "getCols", () => {
160
+ const cols = this.table.colgroup.children;
161
+ const col1 = cols[this.activeIndex];
162
+ const col2 = cols[this.activeIndex + 1];
163
+ return [col1, col2];
164
+ });
165
+
166
+ ct(this, "parseWidth", (col) => (Number.parseFloat(col.style.width) / 100) * this.width);
167
+
168
+ ct(this, "getWidthCols", () => {
169
+ const [c1, c2] = this.getCols();
170
+ return [this.parseWidth(c1), this.parseWidth(c2)];
171
+ });
172
+
173
+ this.init();
174
+ }
175
+
176
+ init() {
177
+ document.addEventListener("mousemove", this.onDrag, false);
178
+ document.addEventListener("mouseup", this.onDragEnd, false);
179
+ }
180
+
181
+ /**
182
+ * Adds resize handle into a cell element if absent.
183
+ */
184
+ createElem(cellEl) {
185
+ if (!cellEl.querySelector(`.${RESIZE_CSS.resizedColumnHandle}`)) {
186
+ const handle = createElement("div", [RESIZE_CSS.resizedColumnHandle]);
187
+ handle.addEventListener("mousedown", this.onDragStart, false);
188
+ cellEl.appendChild(handle);
189
+ }
190
+ }
191
+ }
192
+
193
+ // -----------------------------------------------------------------------------
194
+ // Select line (remove row/column) feature
195
+ // -----------------------------------------------------------------------------
196
+
197
+ const SELECT_LINE_CSS = {
198
+ selectLineCol: "tc-table__select_line_col",
199
+ selectLineRow: "tc-table__select_line_row",
200
+ trRemove: "tc-table__tr_remove",
201
+ tdRemove: "tc-table__td_remove",
202
+ };
203
+
204
+ class SelectLineRemover {
205
+ /**
206
+ * @param {TableUI} table
207
+ */
208
+ constructor(table) {
209
+ this.table = table;
210
+
211
+ ct(this, "getDirection", (evt) => (evt.target.classList.contains(SELECT_LINE_CSS.selectLineCol) ? 0 : 1));
212
+
213
+ ct(this, "getIndex", (evt) => {
214
+ // original matched by exact className
215
+ const candidates = this.table.body.querySelectorAll(`.${evt.target.className}`);
216
+ for (let i = 0; i < candidates.length; i += 1) {
217
+ if (candidates[i] === evt.target) return i;
218
+ }
219
+ return -1;
220
+ });
221
+
222
+ ct(this, "onMouseEnter", (evt) => {
223
+ if (this.getDirection(evt) === 0) {
224
+ // column
225
+ const colIndex = this.getIndex(evt);
226
+ for (let r = 0; r < this.table.body.rows.length; r += 1) {
227
+ const cell = this.table.body.rows[r].children[colIndex];
228
+ if (cell) cell.classList.add(SELECT_LINE_CSS.tdRemove);
229
+ }
230
+ } else {
231
+ // row
232
+ evt.target.closest("tr").classList.add(SELECT_LINE_CSS.trRemove);
233
+ }
234
+ });
235
+
236
+ ct(this, "onMouseLeave", (evt) => {
237
+ if (!evt.target) return;
238
+
239
+ if (this.getDirection(evt) === 0) {
240
+ this.table.body.querySelectorAll(`.${SELECT_LINE_CSS.tdRemove}`).forEach((n) => n.classList.remove(SELECT_LINE_CSS.tdRemove));
241
+ } else {
242
+ evt.target.closest("tr").classList.remove(SELECT_LINE_CSS.trRemove);
243
+ }
244
+ });
245
+
246
+ ct(this, "onClick", (evt) => {
247
+ const idx = this.getIndex(evt);
248
+ if (this.getDirection(evt) === 0) this.table.removeColumn(idx);
249
+ else this.table.removeRow(idx);
250
+ });
251
+ }
252
+
253
+ /**
254
+ * @param {HTMLElement} cellEl
255
+ * @param {0|1} direction 0=col, 1=row
256
+ */
257
+ createElem(cellEl, direction = 0) {
258
+ const cls = direction === 0 ? SELECT_LINE_CSS.selectLineCol : SELECT_LINE_CSS.selectLineRow;
259
+
260
+ if (!cellEl.querySelector(`.${cls}`)) {
261
+ const btn = createElement("div", [cls]);
262
+ btn.addEventListener("click", this.onClick, false);
263
+ btn.addEventListener("mouseenter", this.onMouseEnter, false);
264
+ btn.addEventListener("mouseleave", this.onMouseLeave, false);
265
+ cellEl.appendChild(btn);
266
+ }
267
+ }
268
+ }
269
+
270
+ // -----------------------------------------------------------------------------
271
+ // Add row/column feature
272
+ // -----------------------------------------------------------------------------
273
+
274
+ const ADD_LINE_CSS = {
275
+ addColumn: "tc-table__add_column",
276
+ addRow: "tc-table__add_row",
277
+ };
278
+
279
+ class AddLineCreator {
280
+ /**
281
+ * @param {TableUI} table
282
+ */
283
+ constructor(table) {
284
+ this.table = table;
285
+
286
+ ct(this, "createElem", (cellEl, direction = 0) => {
287
+ const cls = [ADD_LINE_CSS.addColumn, ADD_LINE_CSS.addRow][direction];
288
+
289
+ if (!cellEl.querySelector(`.${cls}`)) {
290
+ const clickable = createElement("div");
291
+ clickable.addEventListener("click", this.onClick);
292
+
293
+ // structure like original: <div class="..."><div></div><div></div></div>
294
+ const wrapper = createElement("div", [cls], null, [clickable, createElement("div")]);
295
+ cellEl.appendChild(wrapper);
296
+ return wrapper;
297
+ }
298
+ });
299
+
300
+ ct(this, "getDirection", (el) => (el.classList.contains(ADD_LINE_CSS.addColumn) ? 0 : 1));
301
+
302
+ ct(this, "getIndex", (el) => {
303
+ const nodes = this.table.body.querySelectorAll(`.${el.className}`);
304
+ for (let i = 0; i < nodes.length; i += 1) {
305
+ if (nodes[i] === el) return i;
306
+ }
307
+ return -1;
308
+ });
309
+
310
+ ct(this, "onClick", (evt) => {
311
+ const container = evt.target.parentNode; // parent of clickable inner div
312
+ const direction = this.getDirection(container);
313
+ const index = this.getIndex(container);
314
+
315
+ if (direction === 0) this.table.addColumn(index);
316
+ else this.table.addRow(index);
317
+ });
318
+ }
319
+ }
320
+
321
+ // -----------------------------------------------------------------------------
322
+ // Image upload feature
323
+ // -----------------------------------------------------------------------------
324
+
325
+ const SVG_IMAGE_ADD = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
326
+ <path d="M19 5V19H5V5H19ZM19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3ZM14.14 11.86L11.14 15.73L9 13.14L6 17H18L14.14 11.86Z" />
327
+ </svg>`;
328
+
329
+ const SVG_IMAGE_DELETE = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
330
+ <path d="M19 5V16.17L21 18.17V5C21 3.9 20.1 3 19 3H5.83L7.83 5H19ZM2.81 2.81L1.39 4.22L3 5.83V19C3 20.1 3.9 21 5 21H18.17L19.78 22.61L21.19 21.2L2.81 2.81ZM5 19V7.83L12.07 14.9L11.25 16L9 13L6 17H14.17L16.17 19H5Z"/>
331
+ </svg>`;
332
+
333
+ const DEFAULT_UPLOAD_ENDPOINT = "/upload_image";
334
+
335
+ const IMAGE_CSS = {
336
+ uploadButton: "tc-table__image_upload_button",
337
+ uploadButtonVisible: "tc-table__image_upload_button_visible",
338
+ image: "tc-table__image",
339
+ wrapper: "tc-table__wrapper_image",
340
+ buttonDelete: "tc-table__wrapper_image_button",
341
+ };
342
+
343
+ class ImageUploader {
344
+ /**
345
+ * @param {TableUI} table
346
+ */
347
+ constructor(table) {
348
+ this.table = table;
349
+
350
+ this.buttonText = "Добавить изображение"; // original had broken encoding
351
+ this.visible = false;
352
+
353
+ this.cell = null; // selected cell (td)
354
+ this.Button = null; // label element
355
+ this.image = null; // File
356
+
357
+ ct(this, "createElem", (container) => {
358
+ if (!container.querySelector(`.${IMAGE_CSS.uploadButton}`)) {
359
+ const fileInput = createElement("input", [], { type: "file" });
360
+ const icon = createElement("span");
361
+ icon.innerHTML = SVG_IMAGE_ADD;
362
+
363
+ this.Button = createElement(
364
+ "label",
365
+ [IMAGE_CSS.uploadButton],
366
+ { title: this.buttonText },
367
+ [fileInput, icon]
368
+ );
369
+
370
+ fileInput.addEventListener("change", this.onChange);
371
+
372
+ container.appendChild(this.Button);
373
+ return this.Button;
374
+ }
375
+ });
376
+
377
+ ct(this, "onToggle", (isVisible) => {
378
+ this.visible = isVisible;
379
+
380
+ if (isVisible) {
381
+ this.Button.classList.add(IMAGE_CSS.uploadButtonVisible);
382
+
383
+ if (this.table._selectedCell) {
384
+ this.cell = this.table._selectedCell;
385
+
386
+ // position button under the selected cell
387
+ this.Button.style.top = `${this.cell.offsetTop + this.cell.offsetHeight}px`;
388
+ this.Button.style.left = `${this.cell.offsetLeft}px`;
389
+ }
390
+ } else {
391
+ setTimeout(() => {
392
+ if (!this.table._selectedCell) {
393
+ this.Button.classList.remove(IMAGE_CSS.uploadButtonVisible);
394
+ }
395
+ }, 200);
396
+ }
397
+ });
398
+
399
+ ct(this, "createImage", (cell, src) => {
400
+ // original expects: td -> div.tc-table__area -> (contentEditable div)
401
+ const [editable] = cell.children[0].children;
402
+
403
+ const img = createElement("img", [IMAGE_CSS.image], { src });
404
+
405
+ if (this.table.readOnly) {
406
+ editable.replaceWith(img);
407
+ return;
408
+ }
409
+
410
+ const deleteIcon = createElement("div");
411
+ deleteIcon.innerHTML = SVG_IMAGE_DELETE;
412
+
413
+ const deleteBtn = createElement("div", [IMAGE_CSS.buttonDelete], null, [deleteIcon]);
414
+
415
+ const wrapper = createElement("div", [IMAGE_CSS.wrapper], null, [img, deleteBtn]);
416
+ deleteBtn.addEventListener("click", this.removeImage);
417
+
418
+ editable.replaceWith(wrapper);
419
+ });
420
+
421
+ ct(this, "removeImage", (evt) => {
422
+ evt.target.closest(`.${IMAGE_CSS.wrapper}`).replaceWith(this.table._createContentEditableArea());
423
+ });
424
+
425
+ ct(this, "onChange", async (evt) => {
426
+ this.image = evt.target.files[0];
427
+ const { url } = await this.onUploadImage();
428
+ if (url) this.createImage(this.cell, url);
429
+ });
430
+
431
+ ct(this, "onUploadImage", async () => {
432
+ const data = new FormData();
433
+ data.append("upfile", this.image);
434
+
435
+ const res = await fetch(DEFAULT_UPLOAD_ENDPOINT, { method: "POST", body: data });
436
+ return res.json();
437
+ });
438
+ }
439
+ }
440
+
441
+ // -----------------------------------------------------------------------------
442
+ // Table UI (DOM + interactions)
443
+ // -----------------------------------------------------------------------------
444
+
445
+ const TABLE_CSS = {
446
+ table: "tc-table",
447
+ inputField: "tc-table__inp",
448
+ cell: "tc-table__cell",
449
+ container: "tc-table__container",
450
+ containerReadOnly: "tc-table__container_readonly",
451
+ wrapper: "tc-table__wrap",
452
+ area: "tc-table__area",
453
+
454
+ addColumn: "tc-table__add_column",
455
+ addRow: "tc-table__add_row",
456
+ addColumnButton: "tc-table__add_column_button",
457
+ addRowButton: "tc-table__add_row_button",
458
+ };
459
+
460
+ class TableUI {
461
+ /**
462
+ * @param {boolean} readOnly
463
+ */
464
+ constructor(readOnly) {
465
+ this.readOnly = readOnly;
466
+
467
+ this._numberOfColumns = 0;
468
+ this._numberOfRows = 0;
469
+
470
+ this.resize = new ColumnResizer(this);
471
+ this.selectLine = new SelectLineRemover(this);
472
+ this.createLine = new AddLineCreator(this);
473
+ this.imageUpload = new ImageUploader(this);
474
+
475
+ this._element = this._createTableWrapper();
476
+ this._table = this._element.querySelector("table");
477
+ this.colgroup = this._table.querySelector("colgroup");
478
+
479
+ this._selectedCell = null;
480
+
481
+ if (!this.readOnly) this._hangEvents();
482
+ }
483
+
484
+ // ----- layout helpers -----
485
+
486
+ columnSizeReCalc() {
487
+ const cols = this.colgroup.children;
488
+ for (let i = 0; i < cols.length; i += 1) {
489
+ cols[i].style.width = `${100 / cols.length}%`;
490
+ }
491
+ }
492
+
493
+ insertCol(index) {
494
+ this.colgroup.insertBefore(createElement("col", [], { span: 1 }), this.colgroup.children[index]);
495
+ }
496
+
497
+ removeCol(index) {
498
+ // original did: this.body.querySelector('colgroup').children[t].remove();
499
+ this.body.querySelector("colgroup").children[index].remove();
500
+ }
501
+
502
+ // ----- public API -----
503
+
504
+ addColumn(index = -1) {
505
+ this._numberOfColumns++;
506
+
507
+ const rows = this._table.rows;
508
+ if (index === 0) this.removeButtons(1);
509
+
510
+ this.insertCol(index);
511
+
512
+ for (let r = 0; r < rows.length; r++) {
513
+ const cell = rows[r].insertCell(index);
514
+ this._fillCell(cell);
515
+ }
516
+
517
+ if (!this.readOnly) {
518
+ this.columnSizeReCalc();
519
+ this.updateButtons();
520
+ }
521
+ }
522
+
523
+ removeColumn(index) {
524
+ this._numberOfColumns--;
525
+
526
+ for (let r = 0; r < this._table.rows.length; r += 1) {
527
+ this._table.rows[r].deleteCell(index);
528
+ }
529
+
530
+ if (!this.readOnly) {
531
+ this.removeCol(index);
532
+ this.columnSizeReCalc();
533
+ this.updateButtons();
534
+ }
535
+ }
536
+
537
+ addRow(index = -1) {
538
+ this._numberOfRows++;
539
+
540
+ const tr = this._table.insertRow(index);
541
+ if (index === 0) this.removeButtons(0);
542
+
543
+ this._fillRow(tr);
544
+ this.updateButtons();
545
+
546
+ return tr;
547
+ }
548
+
549
+ removeRow(index) {
550
+ this._numberOfRows--;
551
+ this._table.rows[index].remove();
552
+ this.updateButtons();
553
+ }
554
+
555
+ get htmlElement() {
556
+ return this._element;
557
+ }
558
+
559
+ get body() {
560
+ return this._table;
561
+ }
562
+
563
+ get selectedCell() {
564
+ return this._selectedCell;
565
+ }
566
+
567
+ // (kept as in original: getter exists but empty)
568
+ get withBorder() {}
569
+
570
+ // ----- buttons (resizer / add / remove) management -----
571
+
572
+ fillButtons(cellEl, colIndex, rowIndex) {
573
+ // rowIndex === 0 => header row style behavior in original
574
+ if (rowIndex === 0) {
575
+ this.createLine.createElem(cellEl);
576
+ if (colIndex !== 0) this.resize.createElem(cellEl);
577
+ }
578
+
579
+ // add select line controls on first row/first col zones
580
+ if (colIndex === 0 || rowIndex === 0) {
581
+ this.selectLine.createElem(cellEl, +(colIndex === 0));
582
+ if (colIndex === 0 && rowIndex === 0) this.selectLine.createElem(cellEl);
583
+ }
584
+
585
+ // row add control on first column cells
586
+ if (colIndex === 0) {
587
+ this.createLine.createElem(cellEl, 1);
588
+ }
589
+ }
590
+
591
+ updateButtons() {
592
+ for (let r = 0; r < this._table.rows.length; r += 1) {
593
+ const row = this._table.rows[r];
594
+ for (let c = 0; c < row.children.length; c += 1) {
595
+ const cell = row.children[c];
596
+ this.fillButtons(cell, c, r);
597
+ }
598
+ }
599
+ }
600
+
601
+ /**
602
+ * @param {0|1} axis 0=columns UI, 1=rows UI (as in original)
603
+ */
604
+ removeButtons(axis = 0) {
605
+ const groups = [
606
+ [TABLE_CSS.addColumn, SELECT_LINE_CSS.selectLineCol],
607
+ [TABLE_CSS.addRow, SELECT_LINE_CSS.selectLineRow],
608
+ ][axis];
609
+
610
+ groups.forEach((cls) => {
611
+ const nodes = this._table.querySelectorAll(`.${cls}`);
612
+ for (let i = 0; i < nodes.length; i += 1) nodes[i].remove();
613
+ });
614
+ }
615
+
616
+ // ----- DOM build -----
617
+
618
+ _createTableWrapper() {
619
+ const containerClass = this.readOnly ? TABLE_CSS.containerReadOnly : TABLE_CSS.container;
620
+
621
+ const container = createElement("div", [containerClass], null, [
622
+ createElement("div", [TABLE_CSS.wrapper], null, [
623
+ createElement("table", [TABLE_CSS.table], null, [
624
+ createElement("colgroup"),
625
+ createElement("tbody"),
626
+ ]),
627
+ ]),
628
+ ]);
629
+
630
+ if (!this.readOnly) {
631
+ const addRowBtn = createElement("div", [TABLE_CSS.addRowButton]);
632
+ const addColBtn = createElement("div", [TABLE_CSS.addColumnButton]);
633
+
634
+ // NOTE: In original these were swapped: addRowBtn click -> addColumn, addColBtn -> addRow
635
+ // Keeping original behavior:
636
+ addRowBtn.addEventListener("click", () => this.addColumn(this._numberOfColumns), true);
637
+ addColBtn.addEventListener("click", () => this.addRow(this._numberOfRows), true);
638
+
639
+ container.appendChild(addRowBtn);
640
+ container.appendChild(addColBtn);
641
+ }
642
+
643
+ return container;
644
+ }
645
+
646
+ _createContentEditableArea() {
647
+ return createElement("div", [TABLE_CSS.inputField], { contenteditable: !this.readOnly });
648
+ }
649
+
650
+ _fillCell(td) {
651
+ td.classList.add(TABLE_CSS.cell);
652
+ const editable = this._createContentEditableArea();
653
+ td.appendChild(createElement("div", [TABLE_CSS.area], null, [editable]));
654
+ }
655
+
656
+ _fillRow(tr) {
657
+ for (let c = 0; c < this._numberOfColumns; c++) {
658
+ const td = tr.insertCell();
659
+ this._fillCell(td);
660
+ }
661
+ }
662
+
663
+ // ----- events -----
664
+
665
+ _hangEvents() {
666
+ this._table.addEventListener(
667
+ "focus",
668
+ (evt) => this._focusEditField(evt),
669
+ true
670
+ );
671
+
672
+ this._table.addEventListener(
673
+ "blur",
674
+ (evt) => this._blurEditField(evt),
675
+ true
676
+ );
677
+
678
+ this._table.addEventListener("keydown", (evt) => this._pressedEnterInEditField(evt));
679
+ this._table.addEventListener("click", (evt) => this._clickedOnCell(evt));
680
+
681
+ this._table.addEventListener(
682
+ "mouseover",
683
+ (evt) => {
684
+ this._mouseEnterInDetectArea(evt);
685
+ evt.stopPropagation();
686
+ },
687
+ true
688
+ );
689
+ }
690
+
691
+ _focusEditField(evt) {
692
+ if (evt.target.classList.contains(TABLE_CSS.inputField)) {
693
+ this._selectedCell = evt.target.closest("." + TABLE_CSS.cell);
694
+ this.imageUpload.onToggle(true);
695
+ }
696
+ }
697
+
698
+ _blurEditField(evt) {
699
+ if (evt.target.classList.contains(TABLE_CSS.inputField)) {
700
+ this._selectedCell = null;
701
+ this.imageUpload.onToggle(false);
702
+ }
703
+ }
704
+
705
+ _pressedEnterInEditField(evt) {
706
+ if (evt.target.classList.contains(TABLE_CSS.inputField) && evt.keyCode === 13 && !evt.shiftKey) {
707
+ evt.preventDefault();
708
+ }
709
+ }
710
+
711
+ _clickedOnCell(evt) {
712
+ // ignore clicks on delete button wrapper
713
+ if (evt.target.classList.contains(IMAGE_CSS.buttonDelete) === false) {
714
+ if (!evt.target.classList.contains(TABLE_CSS.cell)) return;
715
+ evt.target.querySelector("." + TABLE_CSS.inputField).focus();
716
+ }
717
+ }
718
+
719
+ _mouseEnterInDetectArea(evt) {
720
+ if (!evt.target.classList.contains(TABLE_CSS.area)) return;
721
+
722
+ const rect = getAbsRect(evt.target.closest("TD"));
723
+ const side = detectNearSide(rect, evt.pageX, evt.pageY);
724
+
725
+ evt.target.dispatchEvent(
726
+ new CustomEvent("mouseInActivatingArea", {
727
+ detail: { side },
728
+ bubbles: true,
729
+ })
730
+ );
731
+ }
732
+ }
733
+
734
+ // -----------------------------------------------------------------------------
735
+ // Table constructor used by the Editor.js tool wrapper
736
+ // -----------------------------------------------------------------------------
737
+
738
+ class TableConstructor {
739
+ /**
740
+ * @param {{content?: any[][], settings?: any}} data
741
+ * @param {{rows: string|number, cols: string|number}} config
742
+ * @param {any} api
743
+ * @param {boolean} readOnly
744
+ */
745
+ constructor(data, config, api, readOnly) {
746
+ this.readOnly = readOnly;
747
+
748
+ this._CSS = {
749
+ editor: "tc-editor",
750
+ inputField: "tc-table__inp",
751
+ withBorder: "tc-table__with_border",
752
+ };
753
+
754
+ try {
755
+ this._table = new TableUI(readOnly);
756
+ const { rows, cols } = this._resizeTable(data, config);
757
+ this._fillTable(data, { rows, cols });
758
+ } catch (e) {
759
+ console.log(e);
760
+ }
761
+
762
+ // container: <div class="tc-editor {api.styles.block}"> {tableUI} </div>
763
+ this._container = createElement("div", [this._CSS.editor, api.styles.block], null, [
764
+ this._table.htmlElement,
765
+ ]);
766
+
767
+ // add image upload floating button
768
+ this._table.imageUpload.createElem(this._container);
769
+
770
+ this._hoveredCell = null;
771
+ this._hoveredCellSide = null;
772
+
773
+ if (!this.readOnly) this._hangEvents();
774
+ }
775
+
776
+ get htmlElement() {
777
+ return this._container;
778
+ }
779
+
780
+ _fillTable(data, tableSize) {
781
+ if (data.content !== void 0) {
782
+ for (let r = 0; r < tableSize.rows && r < data.content.length; r++) {
783
+ for (let c = 0; c < tableSize.cols && c < data.content[r].length; c++) {
784
+ const cellData = data.content[r][c];
785
+ const td = this._table.body.rows[r].cells[c];
786
+
787
+ if (typeof cellData === "string") {
788
+ const editable = td.querySelector("." + this._CSS.inputField);
789
+ editable.innerHTML = cellData;
790
+ } else {
791
+ if ((cellData?.type) === "image") {
792
+ this._table.imageUpload.createImage(td, cellData.src);
793
+ }
794
+ }
795
+ }
796
+ }
797
+ }
798
+ }
799
+
800
+ _resizeTable(data, config) {
801
+ const hasContentArray = Array.isArray(data.content);
802
+ const contentRows = hasContentArray ? data.content.length : false;
803
+ const contentRowsCount = hasContentArray ? data.content.length : void 0;
804
+ const contentColsCount = contentRows ? data.content[0].length : void 0;
805
+
806
+ const rowsCfg = Number.parseInt(config.rows);
807
+ const colsCfg = Number.parseInt(config.cols);
808
+
809
+ const rowsFromCfg = !isNaN(rowsCfg) && rowsCfg > 0 ? rowsCfg : void 0;
810
+ const colsFromCfg = !isNaN(colsCfg) && colsCfg > 0 ? colsCfg : void 0;
811
+
812
+ const settings = data.settings;
813
+
814
+ // defaults from original
815
+ const defaultRows = 3;
816
+ const defaultCols = 2;
817
+
818
+ const rows = contentRowsCount || rowsFromCfg || defaultRows;
819
+ const cols = contentColsCount || colsFromCfg || defaultCols;
820
+
821
+ for (let r = 0; r < rows; r++) this._table.addRow(r);
822
+ for (let c = 0; c < cols; c++) this._table.addColumn(c);
823
+
824
+ // restore col widths if exist (values are ratios)
825
+ if (settings && settings.sizes) {
826
+ settings.sizes.forEach((ratio, idx) => {
827
+ if (this._table.colgroup.children[idx]) {
828
+ this._table.colgroup.children[idx].style.width = `${ratio * 100}%`;
829
+ }
830
+ });
831
+ }
832
+
833
+ // border toggle (default true if undefined)
834
+ const withBorder =
835
+ (settings?.withBorder) === void 0 ? true : settings?.withBorder;
836
+
837
+ this._table.htmlElement.classList.toggle(this._CSS.withBorder, withBorder);
838
+
839
+ return { rows, cols };
840
+ }
841
+
842
+ _hangEvents() {
843
+ this._container.addEventListener("keydown", (evt) => this._containerKeydown(evt));
844
+ }
845
+
846
+ _containerKeydown(evt) {
847
+ if (evt.keyCode === 13) this._containerEnterPressed(evt);
848
+ }
849
+
850
+ _getHoveredSideOfContainer() {
851
+ return this._hoveredCell === this._container ? (this._isBottomOrRight() ? 0 : -1) : 1;
852
+ }
853
+
854
+ _isBottomOrRight() {
855
+ return this._hoveredCellSide === "bottom" || this._hoveredCellSide === "right";
856
+ }
857
+
858
+ _containerEnterPressed(evt) {
859
+ if (!(this._table.selectedCell !== null && !evt.shiftKey)) return;
860
+
861
+ const tr = this._table.selectedCell.closest("TR");
862
+ let insertIndex = this._getHoveredSideOfContainer();
863
+
864
+ if (insertIndex === 1) insertIndex = tr.sectionRowIndex + 1;
865
+
866
+ this._table.addRow(insertIndex).cells[0].click();
867
+ }
868
+ }
869
+
870
+ // -----------------------------------------------------------------------------
871
+ // Editor.js tool wrapper class (Table tool)
872
+ // -----------------------------------------------------------------------------
873
+
874
+ const TOOLBOX_ICON = `<svg width="18" height="14"><path d="M2.833 8v1.95a1.7 1.7 0 0 0 1.7 1.7h3.45V8h-5.15zm0-2h5.15V2.35h-3.45a1.7 1.7 0 0 0-1.7 1.7V6zm12.3 2h-5.15v3.65h3.45a1.7 1.7 0 0 0 1.7-1.7V8zm0-2V4.05a1.7 1.7 0 0 0-1.7-1.7h-3.45V6h5.15zM4.533.1h8.9a3.95 3.95 0 0 1 3.95 3.95v5.9a3.95 3.95 0 0 1-3.95 3.95h-8.9a3.95 3.95 0 0 1-3.95-3.95v-5.9A3.95 3.95 0 0 1 4.533.1z"/></svg>`;
875
+
876
+ const BORDER_ICON = `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
877
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M0 2V8V10V16V18H2H8H10H16H18V16V10V8V2V0H16H10H8H2H0V2ZM8 2L2 2V8H8L8 2ZM8 10H2V16H8L8 10ZM10 16V10H16V16H10ZM10 8V2L16 2V8H10Z"/>
878
+ </svg>`;
879
+
880
+ class TableTool {
881
+ /**
882
+ * Editor.js Tool constructor signature
883
+ * @param {{data:any, config:any, api:any, readOnly:boolean}} param0
884
+ */
885
+ constructor({ data, config, api, readOnly }) {
886
+ this.api = api;
887
+ this.readOnly = readOnly;
888
+
889
+ this._tableConstructor = new TableConstructor(data, config, api, readOnly);
890
+
891
+ this._CSS = {
892
+ input: "tc-table__inp",
893
+ settingsButton: this.api.styles.settingsButton,
894
+ settingsButtonActive: this.api.styles.settingsButtonActive,
895
+ };
896
+
897
+ this.borderActive = data.settings?.withBorder;
898
+ this.toggleBorderButton = null;
899
+
900
+ ct(this, "toggleBorder", () => {
901
+ this.borderActive = !this.borderActive;
902
+ this.toggleBorderButton.classList.toggle(this._CSS.settingsButtonActive, this.borderActive);
903
+ this._tableConstructor._table._element.classList.toggle(
904
+ this._tableConstructor._CSS.withBorder,
905
+ this.borderActive
906
+ );
907
+ });
908
+ }
909
+
910
+ static get isReadOnlySupported() {
911
+ return true;
912
+ }
913
+
914
+ static get enableLineBreaks() {
915
+ return true;
916
+ }
917
+
918
+ static get sanitize() {
919
+ return { br: true, mark: true };
920
+ }
921
+
922
+ static get toolbox() {
923
+ return { icon: TOOLBOX_ICON, title: "Table" };
924
+ }
925
+
926
+ render() {
927
+ return this._tableConstructor.htmlElement;
928
+ }
929
+
930
+ /**
931
+ * Editor.js save(output)
932
+ */
933
+ save(container) {
934
+ const tableEl = container.querySelector("table");
935
+
936
+ const content = [];
937
+ const rows = tableEl.rows;
938
+
939
+ const colSizeRatios = [];
940
+ const tableWidth = tableEl.offsetWidth;
941
+
942
+ for (let r = 0; r < rows.length; r++) {
943
+ const row = rows[r];
944
+ const tds = Array.from(row.cells);
945
+
946
+ const editableAreas = tds.map((td) => td.querySelector("." + this._CSS.input));
947
+ const images = tds.map((td) => td.querySelector("." + IMAGE_CSS.image));
948
+
949
+ const rowCells = tds.map((td, idx) => {
950
+ if (editableAreas[idx]) {
951
+ return { type: "input", text: editableAreas[idx].innerHTML };
952
+ }
953
+ if (images[idx]) {
954
+ return { type: "image", src: images[idx].getAttribute("src") };
955
+ }
956
+ return undefined;
957
+ });
958
+
959
+ if (r === 0) {
960
+ tds.forEach((td) => colSizeRatios.push(td.offsetWidth / tableWidth));
961
+ }
962
+
963
+ content.push(
964
+ rowCells.map((cell) => {
965
+ if (!cell) return "";
966
+
967
+ switch (cell.type) {
968
+ case "input":
969
+ return cell.text;
970
+ case "image":
971
+ return cell;
972
+ default:
973
+ return "";
974
+ }
975
+ })
976
+ );
977
+ }
978
+
979
+ return {
980
+ settings: {
981
+ sizes: colSizeRatios,
982
+ withBorder: this.borderActive === void 0 ? true : this.borderActive,
983
+ },
984
+ content,
985
+ };
986
+ }
987
+
988
+ _isEmpty(container) {
989
+ return !container.textContent.trim();
990
+ }
991
+
992
+ renderSettings() {
993
+ const wrapper = document.createElement("DIV");
994
+ const btn = document.createElement("SPAN");
995
+
996
+ btn.classList.add(this._CSS.settingsButton);
997
+ if (this.borderActive) btn.classList.add(this._CSS.settingsButtonActive);
998
+
999
+ btn.innerHTML = BORDER_ICON;
1000
+ btn.dataset.active = this.borderActive;
1001
+
1002
+ btn.addEventListener("click", () => this.toggleBorder());
1003
+
1004
+ wrapper.appendChild(btn);
1005
+ this.toggleBorderButton = btn;
1006
+
1007
+ return wrapper;
1008
+ }
1009
+ }
1010
+
1011
+ // If you want it as default export (optional):
1012
+ export default TableTool;