x4js 1.4.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 (176) hide show
  1. package/lib/application.d.ts +95 -0
  2. package/lib/application.js +137 -0
  3. package/lib/base64.d.ts +31 -0
  4. package/lib/base64.js +135 -0
  5. package/lib/base_component.d.ts +64 -0
  6. package/lib/base_component.js +77 -0
  7. package/lib/button.d.ts +145 -0
  8. package/lib/button.js +235 -0
  9. package/lib/calendar.d.ts +77 -0
  10. package/lib/calendar.js +236 -0
  11. package/lib/canvas.d.ts +88 -0
  12. package/lib/canvas.js +354 -0
  13. package/lib/cardview.d.ts +83 -0
  14. package/lib/cardview.js +152 -0
  15. package/lib/checkbox.d.ts +72 -0
  16. package/lib/checkbox.js +126 -0
  17. package/lib/color.d.ts +144 -0
  18. package/lib/color.js +584 -0
  19. package/lib/colorpicker.d.ts +98 -0
  20. package/lib/colorpicker.js +1457 -0
  21. package/lib/combobox.d.ts +97 -0
  22. package/lib/combobox.js +246 -0
  23. package/lib/component.d.ts +572 -0
  24. package/lib/component.js +1712 -0
  25. package/lib/datastore.d.ts +392 -0
  26. package/lib/datastore.js +986 -0
  27. package/lib/dialog.d.ts +171 -0
  28. package/lib/dialog.js +468 -0
  29. package/lib/dom_events.d.ts +284 -0
  30. package/lib/dom_events.js +13 -0
  31. package/lib/drag_manager.d.ts +26 -0
  32. package/lib/drag_manager.js +118 -0
  33. package/lib/drawtext.d.ts +43 -0
  34. package/lib/drawtext.js +261 -0
  35. package/lib/fileupload.d.ts +60 -0
  36. package/lib/fileupload.js +158 -0
  37. package/lib/form.d.ts +122 -0
  38. package/lib/form.js +293 -0
  39. package/lib/formatters.d.ts +31 -0
  40. package/lib/formatters.js +75 -0
  41. package/lib/gridview.d.ts +171 -0
  42. package/lib/gridview.js +786 -0
  43. package/lib/hosts/host.d.ts +44 -0
  44. package/lib/hosts/host.js +69 -0
  45. package/lib/i18n.d.ts +67 -0
  46. package/lib/i18n.js +169 -0
  47. package/lib/icon.d.ts +56 -0
  48. package/lib/icon.js +173 -0
  49. package/lib/image.d.ts +51 -0
  50. package/lib/image.js +149 -0
  51. package/lib/index.js +1 -0
  52. package/lib/input.d.ts +86 -0
  53. package/lib/input.js +172 -0
  54. package/lib/label.d.ts +54 -0
  55. package/lib/label.js +86 -0
  56. package/lib/layout.d.ts +77 -0
  57. package/lib/layout.js +261 -0
  58. package/lib/link.d.ts +46 -0
  59. package/lib/link.js +55 -0
  60. package/lib/listview.d.ts +173 -0
  61. package/lib/listview.js +532 -0
  62. package/lib/md5.d.ts +56 -0
  63. package/lib/md5.js +397 -0
  64. package/lib/menu.d.ts +122 -0
  65. package/lib/menu.js +276 -0
  66. package/lib/messagebox.d.ts +64 -0
  67. package/lib/messagebox.js +141 -0
  68. package/lib/panel.d.ts +42 -0
  69. package/lib/panel.js +61 -0
  70. package/lib/popup.d.ts +71 -0
  71. package/lib/popup.js +373 -0
  72. package/lib/property_editor.d.ts +67 -0
  73. package/lib/property_editor.js +247 -0
  74. package/lib/radiobtn.d.ts +68 -0
  75. package/lib/radiobtn.js +131 -0
  76. package/lib/rating.d.ts +49 -0
  77. package/lib/rating.js +93 -0
  78. package/lib/request.d.ts +48 -0
  79. package/lib/request.js +220 -0
  80. package/lib/router.d.ts +13 -0
  81. package/lib/router.js +27 -0
  82. package/lib/settings.d.ts +33 -0
  83. package/lib/settings.js +63 -0
  84. package/lib/sidebarview.d.ts +44 -0
  85. package/lib/sidebarview.js +73 -0
  86. package/lib/smartedit.d.ts +103 -0
  87. package/lib/smartedit.js +381 -0
  88. package/lib/spreadsheet.d.ts +214 -0
  89. package/lib/spreadsheet.js +1073 -0
  90. package/lib/styles.d.ts +81 -0
  91. package/lib/styles.js +262 -0
  92. package/lib/svgcomponent.d.ts +165 -0
  93. package/lib/svgcomponent.js +350 -0
  94. package/lib/tabbar.d.ts +41 -0
  95. package/lib/tabbar.js +66 -0
  96. package/lib/tabview.d.ts +45 -0
  97. package/lib/tabview.js +79 -0
  98. package/lib/textarea.d.ts +59 -0
  99. package/lib/textarea.js +119 -0
  100. package/lib/textedit.d.ts +118 -0
  101. package/lib/textedit.js +406 -0
  102. package/lib/texthiliter.d.ts +56 -0
  103. package/lib/texthiliter.js +219 -0
  104. package/lib/toaster.d.ts +38 -0
  105. package/lib/toaster.js +58 -0
  106. package/lib/tools.d.ts +382 -0
  107. package/lib/tools.js +1096 -0
  108. package/lib/tooltips.d.ts +42 -0
  109. package/lib/tooltips.js +148 -0
  110. package/lib/treeview.d.ts +128 -0
  111. package/lib/treeview.js +490 -0
  112. package/lib/x4_events.d.ts +253 -0
  113. package/lib/x4_events.js +363 -0
  114. package/package.json +21 -0
  115. package/src/README.md +2 -0
  116. package/src/application.ts +191 -0
  117. package/src/base64.ts +162 -0
  118. package/src/base_component.ts +118 -0
  119. package/src/button.ts +327 -0
  120. package/src/calendar.ts +312 -0
  121. package/src/canvas.ts +501 -0
  122. package/src/cardview.ts +220 -0
  123. package/src/checkbox.ts +178 -0
  124. package/src/color.ts +748 -0
  125. package/src/colorpicker.ts +1618 -0
  126. package/src/combobox.ts +348 -0
  127. package/src/component.ts +2330 -0
  128. package/src/datastore.ts +1318 -0
  129. package/src/dialog.ts +631 -0
  130. package/src/dom_events.ts +297 -0
  131. package/src/drag_manager.ts +168 -0
  132. package/src/drawtext.ts +342 -0
  133. package/src/fileupload.ts +208 -0
  134. package/src/form.ts +362 -0
  135. package/src/formatters.ts +96 -0
  136. package/src/gridview.ts +1051 -0
  137. package/src/hosts/electron.ts +161 -0
  138. package/src/hosts/host.ts +100 -0
  139. package/src/hosts/nwjs.ts +141 -0
  140. package/src/hosts/nwjs_types.ts +339 -0
  141. package/src/i18n.ts +205 -0
  142. package/src/icon.ts +237 -0
  143. package/src/image.ts +198 -0
  144. package/src/input.ts +236 -0
  145. package/src/label.ts +124 -0
  146. package/src/layout.ts +366 -0
  147. package/src/link.ts +82 -0
  148. package/src/listview.ts +749 -0
  149. package/src/md5.ts +432 -0
  150. package/src/menu.ts +394 -0
  151. package/src/messagebox.ts +199 -0
  152. package/src/panel.ts +81 -0
  153. package/src/popup.ts +488 -0
  154. package/src/property_editor.ts +333 -0
  155. package/src/radiobtn.ts +190 -0
  156. package/src/rating.ts +131 -0
  157. package/src/request.ts +296 -0
  158. package/src/router.ts +43 -0
  159. package/src/settings.ts +75 -0
  160. package/src/sidebarview.ts +97 -0
  161. package/src/smartedit.ts +532 -0
  162. package/src/spreadsheet.ts +1423 -0
  163. package/src/styles.ts +332 -0
  164. package/src/svgcomponent.ts +440 -0
  165. package/src/tabbar.ts +105 -0
  166. package/src/tabview.ts +106 -0
  167. package/src/textarea.ts +183 -0
  168. package/src/textedit.ts +535 -0
  169. package/src/texthiliter.ts +284 -0
  170. package/src/toaster.ts +76 -0
  171. package/src/tools.ts +1391 -0
  172. package/src/tooltips.ts +185 -0
  173. package/src/treeview.ts +670 -0
  174. package/src/x4.less +1940 -0
  175. package/src/x4_events.ts +558 -0
  176. package/tsconfig.json +14 -0
@@ -0,0 +1,1073 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \_/ / / _
4
+ * \ / /_| |_
5
+ * / _ \____ _|
6
+ * /__/ \__\ |_|
7
+ *
8
+ * @file spreadsheet.ts
9
+ * @author Etienne Cochard
10
+ * @license
11
+ * Copyright (c) 2019-2021 R-libre ingenierie
12
+ *
13
+ * This program is free software; you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License as published by
15
+ * the Free Software Foundation; either version 3 of the License, or
16
+ * (at your option) any later version.
17
+ *
18
+ * This program is distributed in the hope that it will be useful,
19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
+ * GNU General Public License for more details.
22
+ *
23
+ * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
24
+ **/
25
+ import { Component, SizerOverlay, EvDblClick } from './component';
26
+ import { HLayout, VLayout } from './layout';
27
+ import { TextEdit } from './textedit';
28
+ import { asap, parseIntlFloat } from './tools';
29
+ import { deferCall } from './tools';
30
+ import { EvContextMenu, EvChange, EvSelectionChange } from './x4_events';
31
+ import { ComboBox } from './combobox';
32
+ class CellData {
33
+ text;
34
+ cls;
35
+ static empty_cell = {
36
+ text: ''
37
+ };
38
+ }
39
+ /**
40
+ *
41
+ */
42
+ export class Spreadsheet extends VLayout {
43
+ m_columns;
44
+ m_row_limit;
45
+ m_cells_data;
46
+ m_rows_data;
47
+ m_view;
48
+ m_container;
49
+ m_header;
50
+ m_itemHeight;
51
+ m_topIndex;
52
+ m_visible_cells;
53
+ m_row_count; // visible row count
54
+ m_selection;
55
+ m_editor;
56
+ m_autoedit;
57
+ m_lockupdate;
58
+ m_auto_row_count;
59
+ m_recycler;
60
+ m_used_cells;
61
+ constructor(props) {
62
+ super(props);
63
+ this.m_columns = props.columns;
64
+ this.m_autoedit = props.autoedit;
65
+ this.m_lockupdate = 0;
66
+ this.m_cells_data = new Map();
67
+ this.m_rows_data = new Map();
68
+ this.m_itemHeight = 0;
69
+ this.m_selection = { row: 0, col: 0 };
70
+ this.m_row_count = 0;
71
+ this.m_auto_row_count = false;
72
+ this.m_recycler = [];
73
+ this.m_used_cells = [];
74
+ if (props.maxrows === undefined) {
75
+ this.m_row_limit = 0;
76
+ this.m_auto_row_count = true;
77
+ }
78
+ else if (props.maxrows < 0) {
79
+ this.m_row_limit = 0;
80
+ this.m_auto_row_count = true;
81
+ }
82
+ else {
83
+ this.m_row_limit = props.maxrows;
84
+ }
85
+ this.setAttribute('tabIndex', 0);
86
+ this.setDomEvent('click', (e) => this._itemClick(e));
87
+ this.setDomEvent('dblclick', (e) => this._itemDblClick(e));
88
+ this.setDomEvent('keydown', (e) => this._handleKey(e));
89
+ this.setDomEvent('keypress', (e) => this._keyPress(e));
90
+ this.setDomEvent('focus', () => this._focus(true));
91
+ this.setDomEvent('focusout', () => this._focus(false));
92
+ this.setDomEvent('contextmenu', (e) => this._ctxMenu(e));
93
+ this.mapPropEvents(props, 'dblClick', 'selectionChange', 'contextMenu', 'change');
94
+ }
95
+ componentCreated() {
96
+ super.componentCreated();
97
+ this._updateScroll(true);
98
+ }
99
+ setColWidth(col, width) {
100
+ this._on_col_resize(col, width);
101
+ this.update(10);
102
+ }
103
+ getColWidth(col) {
104
+ if (!this.m_columns[col]) {
105
+ return;
106
+ }
107
+ return this.m_columns[col].width;
108
+ }
109
+ setColTitle(col, title) {
110
+ console.assert(this.m_columns[col] !== undefined); // what ?
111
+ this.m_columns[col].title = title;
112
+ this.update(10);
113
+ }
114
+ reset(columns) {
115
+ this.m_columns = columns;
116
+ this.m_cells_data = new Map();
117
+ this.m_rows_data = new Map();
118
+ this.update(10);
119
+ }
120
+ /**
121
+ * insert a row
122
+ * @param before row number before wich insert the new row
123
+ */
124
+ insertRow(before) {
125
+ let new_cells_data = new Map();
126
+ this.m_cells_data.forEach((celldata, key) => {
127
+ let { row, col } = _getid(key);
128
+ if (row >= before) {
129
+ new_cells_data.set(_mkid(row + 1, col), celldata);
130
+ }
131
+ else {
132
+ new_cells_data.set(key, celldata);
133
+ }
134
+ });
135
+ let new_rows_data = new Map();
136
+ this.m_rows_data.forEach((rowdata, row) => {
137
+ if (row >= before) {
138
+ new_rows_data.set(row + 1, rowdata);
139
+ }
140
+ else {
141
+ new_rows_data.set(row, rowdata);
142
+ }
143
+ });
144
+ this.m_cells_data = new_cells_data;
145
+ this.m_rows_data = new_rows_data;
146
+ this._buildItems();
147
+ }
148
+ /**
149
+ * remove a row
150
+ * @param rowtodel row number to remove
151
+ */
152
+ deleteRow(rowtodel) {
153
+ let new_cells_data = new Map();
154
+ let new_rows_data = new Map();
155
+ this.m_cells_data.forEach((celldata, key) => {
156
+ let { row, col } = _getid(key);
157
+ if (row > rowtodel) {
158
+ new_cells_data.set(_mkid(row - 1, col), celldata);
159
+ }
160
+ else if (row < rowtodel) {
161
+ new_cells_data.set(key, celldata);
162
+ }
163
+ });
164
+ this.m_rows_data.forEach((rowdata, row) => {
165
+ if (row > rowtodel) {
166
+ new_rows_data.set(row - 1, rowdata);
167
+ }
168
+ else if (row < rowtodel) {
169
+ new_rows_data.set(row, rowdata);
170
+ }
171
+ });
172
+ this.m_cells_data = new_cells_data;
173
+ this.m_rows_data = new_rows_data;
174
+ this._buildItems();
175
+ }
176
+ /**
177
+ * insert a new column
178
+ * @param before column index before to insert the new column or <0 to append
179
+ */
180
+ insertCol(before, column) {
181
+ let inspos = before;
182
+ if (inspos < 0) {
183
+ inspos = this.m_columns.length + 1;
184
+ }
185
+ // insert the col at the right place
186
+ this.m_columns.splice(inspos, 0, column);
187
+ if (before >= 0) {
188
+ let new_cells_data = new Map();
189
+ this.m_cells_data.forEach((celldata, key) => {
190
+ let { row, col } = _getid(key);
191
+ if (col >= before) {
192
+ new_cells_data.set(_mkid(row, col + 1), celldata);
193
+ }
194
+ else {
195
+ new_cells_data.set(key, celldata);
196
+ }
197
+ });
198
+ this.m_cells_data = new_cells_data;
199
+ }
200
+ this.update();
201
+ }
202
+ /**
203
+ * remove a column
204
+ * @param coltodel
205
+ */
206
+ deleteCol(coltodel) {
207
+ // insert the col at the right place
208
+ this.m_columns.splice(coltodel, 1);
209
+ let new_cells_data = new Map();
210
+ this.m_cells_data.forEach((celldata, key) => {
211
+ let { row, col } = _getid(key);
212
+ if (col > coltodel) {
213
+ new_cells_data.set(_mkid(row, col - 1), celldata);
214
+ }
215
+ else if (col < coltodel) {
216
+ new_cells_data.set(key, celldata);
217
+ }
218
+ });
219
+ this.m_cells_data = new_cells_data;
220
+ this.update();
221
+ }
222
+ /**
223
+ *
224
+ * @param row
225
+ * @param col
226
+ */
227
+ _getCellData(row, col, raw = false) {
228
+ let value = this.m_cells_data.get(_mkid(row, col));
229
+ if (value === undefined) {
230
+ return raw ? null : CellData.empty_cell;
231
+ }
232
+ return value;
233
+ }
234
+ _focus(focus) {
235
+ this.setClass('@focus', focus);
236
+ }
237
+ _ctxMenu(e) {
238
+ let dom = e.target, self = this.dom;
239
+ while (dom && dom != self) {
240
+ let itm = Component.getElement(dom), row = itm.getData('row-id'), col = itm.getData('col-id');
241
+ if (row !== undefined) {
242
+ this._selectItem(row, col);
243
+ this.emit('contextMenu', EvContextMenu(e, { row, col, item: itm }));
244
+ e.preventDefault();
245
+ return;
246
+ }
247
+ dom = dom.parentElement;
248
+ }
249
+ }
250
+ /** @ignore */
251
+ render() {
252
+ this.m_recycler = [];
253
+ this.m_container = new Component({
254
+ cls: 'content',
255
+ });
256
+ this.m_view = new Component({
257
+ cls: '@scroll-view',
258
+ flex: 1,
259
+ dom_events: {
260
+ sizechange: () => this._updateScroll(true),
261
+ scroll: () => this._updateScroll(false)
262
+ },
263
+ content: this.m_container
264
+ });
265
+ let cols = this.m_columns.map((col, index) => {
266
+ let comp = new Component({
267
+ cls: '@cell c' + index,
268
+ content: col.title ? col.title : '&nbsp',
269
+ flex: col.width < 0 ? -col.width : undefined,
270
+ attrs: {
271
+ title: col.title
272
+ },
273
+ style: {
274
+ width: col.width >= 0 ? col.width : undefined,
275
+ minWidth: col.min_width
276
+ },
277
+ });
278
+ new SizerOverlay({
279
+ target: comp,
280
+ sens: 'right',
281
+ resize: (ev) => {
282
+ this._on_col_resize(index, ev.size.width);
283
+ }
284
+ });
285
+ col.$col = comp;
286
+ return comp;
287
+ });
288
+ this.m_header = new HLayout({
289
+ cls: '@header',
290
+ content: cols,
291
+ });
292
+ this.setContent([
293
+ this.m_header,
294
+ this.m_view
295
+ ]);
296
+ }
297
+ /**
298
+ *
299
+ */
300
+ _on_col_resize(col, width) {
301
+ if (!this.m_columns[col]) {
302
+ return;
303
+ }
304
+ // -> flex
305
+ if (width <= 0) {
306
+ this.m_columns[col].width = -1; // flex default
307
+ }
308
+ else {
309
+ this.m_columns[col].width = width;
310
+ }
311
+ this._updateScroll(true);
312
+ }
313
+ /**
314
+ * compute misc dimensions
315
+ * - item height
316
+ * - scroll width
317
+ */
318
+ _computeItemHeight() {
319
+ let g1 = document.createElement('div');
320
+ g1.classList.add('x-spreadsheet');
321
+ let g2 = document.createElement('div');
322
+ g2.classList.add('content');
323
+ let g3 = document.createElement('div');
324
+ g3.classList.add('x-cell');
325
+ g3.append('&nbsp;');
326
+ g2.appendChild(g3);
327
+ g1.appendChild(g2);
328
+ this.dom.appendChild(g1);
329
+ let rc = g3.getBoundingClientRect();
330
+ this.dom.removeChild(g1);
331
+ this.m_itemHeight = rc.height;
332
+ }
333
+ /**
334
+ * compute columns widths
335
+ * use col.width for fixed size columns
336
+ * if col.width < 0 that mean that this is a proportion of the remaining space
337
+ */
338
+ _calcColWidths(width) {
339
+ let fullw = 0;
340
+ let nwide = 0;
341
+ let calcw = new Int32Array(this.m_columns.length);
342
+ let calcz = new Int32Array(this.m_columns.length);
343
+ let calcm = new Int32Array(this.m_columns.length);
344
+ this.m_columns.forEach((col, colIdx) => {
345
+ let minw = Math.max(10, col.min_width ?? 0);
346
+ if (col.width > 0) {
347
+ let cw = Math.max(col.width, minw);
348
+ fullw += cw;
349
+ calcw[colIdx] = cw;
350
+ }
351
+ else {
352
+ let z = -col.width;
353
+ calcz[colIdx] = z;
354
+ nwide += z;
355
+ }
356
+ calcm[colIdx] = minw;
357
+ });
358
+ if (nwide) {
359
+ let restw = width - fullw;
360
+ for (let i = 0; i < this.m_columns.length && nwide; i++) {
361
+ if (!calcw[i]) {
362
+ let rest = Math.round(restw / nwide) * calcz[i];
363
+ if (rest < calcm[i]) {
364
+ rest = calcm[i];
365
+ }
366
+ calcw[i] = rest;
367
+ restw -= rest;
368
+ nwide -= calcz[i];
369
+ }
370
+ }
371
+ }
372
+ return calcw;
373
+ }
374
+ /**
375
+ * create a cell (component)
376
+ * and append it to the parent view
377
+ * if a cell was reviously recyled, use it
378
+ */
379
+ _createCell() {
380
+ let cell;
381
+ if (this.m_recycler.length) {
382
+ cell = this.m_recycler.pop();
383
+ cell.clearClasses();
384
+ }
385
+ else {
386
+ cell = new Component({
387
+ cls: '@cell'
388
+ });
389
+ }
390
+ if (!cell.dom) {
391
+ this.m_container.appendChild(cell);
392
+ }
393
+ return cell;
394
+ }
395
+ /**
396
+ * build cells of the spreadsheet
397
+ * cells are recycled when scrolling,
398
+ * only visibles cells exists
399
+ */
400
+ _buildItems() {
401
+ let rc = this.getBoundingRect();
402
+ let rh = this.m_header.getBoundingRect();
403
+ let height = rc.height - rh.height;
404
+ if (this.m_itemHeight == 0) {
405
+ this._computeItemHeight();
406
+ }
407
+ let top = this.m_topIndex * this.m_itemHeight;
408
+ let y = 0;
409
+ let cidx = 0;
410
+ let rowIdx = this.m_topIndex;
411
+ let count = this.m_row_limit;
412
+ if (this.m_auto_row_count) {
413
+ //@review should be evaluated only when row count change
414
+ this.m_row_limit = count = this.getMaxRowCount();
415
+ }
416
+ let right_pos = 0;
417
+ if ((count * this.m_itemHeight) > height) {
418
+ let w = Component.getScrollbarSize();
419
+ rc.width -= w;
420
+ right_pos = w;
421
+ }
422
+ let even = this.m_topIndex & 1 ? true : false;
423
+ this.m_visible_cells = new Map();
424
+ // passe 0 - all created cells are moved to the recycler
425
+ this.m_used_cells.forEach((c) => {
426
+ this.m_recycler.push(c);
427
+ });
428
+ this.m_used_cells = [];
429
+ // pass 1 - compute column widths
430
+ let calcw = this._calcColWidths(rc.width);
431
+ //
432
+ let full_width = 0;
433
+ for (let i = 0; i < calcw.length; i++) {
434
+ full_width += calcw[i];
435
+ }
436
+ if (full_width <= rc.width) {
437
+ this.m_view.setStyleValue('overflow-x', 'hidden');
438
+ this.m_header.setStyleValue('width', rc.width);
439
+ this.m_container.setStyleValue('width', rc.width);
440
+ this.m_container.setStyle({
441
+ height: count * this.m_itemHeight,
442
+ });
443
+ }
444
+ else {
445
+ this.m_header.setStyleValue('width', full_width);
446
+ this.m_container.setStyleValue('width', full_width);
447
+ this.m_view.setStyleValue('overflow-x', 'visible');
448
+ this.m_container.setStyle({
449
+ height: count * this.m_itemHeight,
450
+ width: full_width
451
+ });
452
+ }
453
+ this.m_view.addClass('@hidden');
454
+ // pass 2 - build cells
455
+ let limit = 100;
456
+ while (y < height && rowIdx < count && --limit > 0) {
457
+ let rowdata = this.m_rows_data.get(rowIdx);
458
+ let x = 0;
459
+ let cols = this.m_columns.map((col, colIdx) => {
460
+ let cls = '@cell c' + colIdx;
461
+ if (col.align) {
462
+ cls += ' ' + col.align;
463
+ }
464
+ if (col.cls) {
465
+ cls += ' ' + col.cls;
466
+ }
467
+ let cell;
468
+ let celldata = this._getCellData(rowIdx, colIdx);
469
+ let text = celldata.text;
470
+ if (col.renderer && text.length) {
471
+ text = col.renderer(text, { row: rowIdx, col: colIdx });
472
+ }
473
+ //if( text.length==0 ) {
474
+ // text = '&nbsp;'
475
+ //}
476
+ cls += (even ? ' even' : ' odd');
477
+ if (rowdata) {
478
+ cls += ' ' + rowdata;
479
+ }
480
+ cell = this._createCell();
481
+ this.m_used_cells.push(cell);
482
+ cell.setContent(text); // always because cell reuse
483
+ cell.addClass(cls);
484
+ cell.setStyle({
485
+ left: x,
486
+ top: top + y,
487
+ width: calcw[colIdx]
488
+ });
489
+ if (this.m_selection.row == rowIdx && this.m_selection.col == colIdx) {
490
+ cell.addClass('@selected');
491
+ }
492
+ cell.setData('row-id', rowIdx);
493
+ cell.setData('col-id', colIdx);
494
+ if (celldata.cls) {
495
+ cell.addClass(celldata.cls);
496
+ }
497
+ this.m_visible_cells.set(_mkid(rowIdx, colIdx), cell);
498
+ x += calcw[colIdx];
499
+ return cell;
500
+ });
501
+ even = !even;
502
+ y += this.m_itemHeight;
503
+ rowIdx++;
504
+ cidx++;
505
+ //rows.splice( rows.length, 0, ...cols );
506
+ }
507
+ // if some cells are still in cache, hide them
508
+ this.m_recycler.forEach((c) => {
509
+ c.addClass('@hidden');
510
+ });
511
+ this.m_row_count = cidx;
512
+ //this.m_container.setContent( <ComponentContent>rows);
513
+ this.m_view.removeClass('@hidden');
514
+ this.setClass('empty', count == 0);
515
+ }
516
+ /** @ignore */
517
+ _itemClick(e) {
518
+ let dom = e.target;
519
+ if (this.m_editor && this.m_editor.dom.contains(dom)) {
520
+ return;
521
+ }
522
+ let itm = Component.getElement(dom, Component);
523
+ if (!itm) {
524
+ return;
525
+ }
526
+ let rowIdx = itm.getData('row-id'), colIdx = itm.getData('col-id');
527
+ if (rowIdx === undefined || colIdx === undefined) {
528
+ return;
529
+ }
530
+ this._selectItem(rowIdx, colIdx);
531
+ }
532
+ _itemDblClick(e) {
533
+ let dom = e.target;
534
+ if (this.m_editor && this.m_editor.dom.contains(dom)) {
535
+ return;
536
+ }
537
+ let itm = Component.getElement(dom), rowIdx = itm.getData('row-id'), colIdx = itm.getData('col-id');
538
+ if (rowIdx === undefined || colIdx === undefined) {
539
+ return;
540
+ }
541
+ this.emit('dblClick', EvDblClick({ row: rowIdx, col: colIdx }));
542
+ this.editCell(rowIdx, colIdx);
543
+ }
544
+ /**
545
+ *
546
+ * @param rowIdx
547
+ * @param colIdx
548
+ * @param scrollIntoView
549
+ */
550
+ _selectItem(rowIdx, colIdx, scrollIntoView) {
551
+ if (rowIdx < 0) {
552
+ rowIdx = 0;
553
+ }
554
+ if (rowIdx > this.m_row_limit - 1) {
555
+ rowIdx = this.m_row_limit - 1;
556
+ }
557
+ if (colIdx < 0) {
558
+ colIdx = 0;
559
+ }
560
+ let lastcol = this.m_columns.length - 1;
561
+ if (colIdx > lastcol) {
562
+ colIdx = lastcol;
563
+ }
564
+ if (this.m_selection.row == rowIdx && this.m_selection.col == colIdx) {
565
+ return;
566
+ }
567
+ this.select(rowIdx, colIdx, scrollIntoView);
568
+ }
569
+ _scrollIntoView(row, col) {
570
+ let doscroll = (itm, mode = 'nearest') => {
571
+ itm.scrollIntoView({
572
+ block: mode //<ScrollLogicalPosition>sens ?? 'nearest'
573
+ });
574
+ };
575
+ let last = this.m_topIndex + this.m_row_count - 1;
576
+ if (row < this.m_topIndex) {
577
+ this.m_topIndex = row;
578
+ this.m_view.dom.scrollTop = this.m_topIndex * this.m_itemHeight;
579
+ this._buildItems();
580
+ doscroll(this._findItem(row, col), 'start');
581
+ }
582
+ else if (row > last) {
583
+ this.m_topIndex = row - this.m_row_count + 1;
584
+ this.m_view.dom.scrollTop = this.m_topIndex * this.m_itemHeight;
585
+ this._buildItems();
586
+ doscroll(this._findItem(row, col), 'end');
587
+ }
588
+ else {
589
+ doscroll(this._findItem(row, col));
590
+ }
591
+ }
592
+ /**
593
+ *
594
+ * @param row
595
+ * @param col
596
+ */
597
+ _findItem(row, col) {
598
+ if (!this.m_visible_cells) {
599
+ return null;
600
+ }
601
+ return this.m_visible_cells.get(_mkid(row, col));
602
+ }
603
+ /**
604
+ *
605
+ */
606
+ _updateScroll(forceUpdate) {
607
+ if (!this?.m_view?.dom) {
608
+ return;
609
+ }
610
+ let newTop = Math.floor(this.m_view.dom.scrollTop / (this.m_itemHeight || 1));
611
+ if (newTop != this.m_topIndex || forceUpdate) {
612
+ this.m_topIndex = newTop;
613
+ this._buildItems();
614
+ }
615
+ let newLeft = this.m_view.dom.scrollLeft;
616
+ this.m_header.setStyleValue('left', -newLeft);
617
+ }
618
+ /**
619
+ *
620
+ * @param event
621
+ * @param t
622
+ */
623
+ _moveSel(sensy, sensx) {
624
+ let sel = this.m_selection;
625
+ let newRow = sel.row ?? 0;
626
+ let newCol = sel.col ?? 0;
627
+ if (sensy == 1) {
628
+ newRow++;
629
+ }
630
+ else if (sensy == -1) {
631
+ newRow--;
632
+ }
633
+ else if (sensy == 2) {
634
+ newRow += this.m_row_count - 1;
635
+ }
636
+ else if (sensy == -2) {
637
+ newRow -= this.m_row_count - 1;
638
+ }
639
+ else if (sensy == 3) {
640
+ newRow = this.m_row_limit - 1;
641
+ }
642
+ else if (sensy == -3) {
643
+ newRow = 0;
644
+ }
645
+ if (sensx == 1) {
646
+ newCol++;
647
+ }
648
+ else if (sensx == -1) {
649
+ newCol--;
650
+ }
651
+ else if (sensx == 2) {
652
+ newCol = this.m_columns.length - 1;
653
+ }
654
+ else if (sensx == -2) {
655
+ newCol = 0;
656
+ }
657
+ else if (sensx == 3) { // new editable cell skip line if needed
658
+ newCol++;
659
+ let lastcol = this.m_columns.length - 1;
660
+ l1: for (let trys = 0; trys < 2; trys++) {
661
+ while (newCol < lastcol) {
662
+ if (this.m_columns[newCol].createEditor !== null) {
663
+ break l1;
664
+ }
665
+ newCol++;
666
+ }
667
+ if (newCol > lastcol) {
668
+ newRow++;
669
+ newCol = 0;
670
+ }
671
+ }
672
+ }
673
+ else if (sensx == -3) {
674
+ newCol--;
675
+ let lastcol = this.m_columns.length - 1;
676
+ l2: for (let trys = 0; trys < 2; trys++) {
677
+ while (newCol >= 0) {
678
+ if (this.m_columns[newCol].createEditor !== null) {
679
+ break l2;
680
+ }
681
+ newCol--;
682
+ }
683
+ if (newCol < 0) {
684
+ newRow--;
685
+ newCol = lastcol;
686
+ }
687
+ }
688
+ }
689
+ this._selectItem(newRow, newCol, true);
690
+ }
691
+ _handleKey(event) {
692
+ let dom = event.target;
693
+ if (this.m_editor && this.m_editor.dom.contains(dom)) {
694
+ return;
695
+ }
696
+ switch (event.key) {
697
+ case 'ArrowDown':
698
+ case 'Down': {
699
+ this._moveSel(1, 0);
700
+ break;
701
+ }
702
+ case 'ArrowUp':
703
+ case 'Up': {
704
+ this._moveSel(-1, 0);
705
+ break;
706
+ }
707
+ case 'PageUp': {
708
+ this._moveSel(-2, 0);
709
+ break;
710
+ }
711
+ case 'PageDown': {
712
+ this._moveSel(2, 0);
713
+ break;
714
+ }
715
+ case 'ArrowLeft':
716
+ case 'Left': {
717
+ this._moveSel(0, -1);
718
+ break;
719
+ }
720
+ case 'ArrowRight':
721
+ case 'Right': {
722
+ this._moveSel(0, 1);
723
+ break;
724
+ }
725
+ case 'Home': {
726
+ if (event.ctrlKey) {
727
+ this._moveSel(-3, 0);
728
+ }
729
+ else {
730
+ this._moveSel(0, -2);
731
+ }
732
+ break;
733
+ }
734
+ case 'End': {
735
+ if (event.ctrlKey) {
736
+ this._moveSel(3, 0);
737
+ }
738
+ else {
739
+ this._moveSel(0, 2);
740
+ }
741
+ break;
742
+ }
743
+ case 'Enter': {
744
+ this.editCurCell();
745
+ event.stopPropagation();
746
+ break;
747
+ }
748
+ case 'Delete': {
749
+ this.clearCell(this.m_selection.row, this.m_selection.col);
750
+ break;
751
+ }
752
+ default: {
753
+ //console.log( "unknown key: ", event.key);
754
+ break;
755
+ }
756
+ }
757
+ }
758
+ _keyPress(event) {
759
+ let dom = event.target;
760
+ if (this.m_editor && this.m_editor.dom.contains(dom)) {
761
+ return;
762
+ }
763
+ if (event.ctrlKey || event.altKey) {
764
+ return;
765
+ }
766
+ this.editCurCell(event.key);
767
+ }
768
+ /**
769
+ * return the selection
770
+ * { row, col }
771
+ */
772
+ getSelection() {
773
+ return this.m_selection;
774
+ }
775
+ select(row, col, scrollIntoView = true) {
776
+ if (this.m_selection.row == row && this.m_selection.col == col) {
777
+ return;
778
+ }
779
+ let oldSel = this._findItem(this.m_selection.row, this.m_selection.col);
780
+ if (oldSel) {
781
+ oldSel.removeClass('@selected');
782
+ }
783
+ this.m_selection = { row, col };
784
+ if (scrollIntoView) {
785
+ this._scrollIntoView(row, col);
786
+ }
787
+ let newSel = this._findItem(row, col);
788
+ if (newSel) {
789
+ newSel.addClass('@selected');
790
+ }
791
+ this.emit('selectionChange', EvSelectionChange({ row, col }));
792
+ }
793
+ /**
794
+ * return the row count
795
+ */
796
+ rowCount() {
797
+ return this.m_row_limit;
798
+ }
799
+ /**
800
+ * return the maximum row index filled with something
801
+ */
802
+ getMaxRowCount() {
803
+ let max_row = 0;
804
+ this.m_cells_data.forEach((c, uid) => {
805
+ let row = Math.round(uid / 1000) + 1;
806
+ if (max_row < row) {
807
+ max_row = row;
808
+ }
809
+ });
810
+ return max_row;
811
+ }
812
+ getColCount() {
813
+ return this.m_columns.length;
814
+ }
815
+ setRowStyle(row, cls) {
816
+ this.m_rows_data.set(row, cls);
817
+ if (this.m_lockupdate == 0) {
818
+ this._buildItems();
819
+ }
820
+ }
821
+ getRowStyle(row) {
822
+ return this.m_rows_data.get(row);
823
+ }
824
+ setCellStyle(row, col, cls) {
825
+ let cell = this._getCellData(row, col, true);
826
+ if (!cell) {
827
+ cell = { text: '' };
828
+ this.m_cells_data.set(_mkid(row, col), cell);
829
+ }
830
+ cell.cls = cls;
831
+ if (this.m_lockupdate == 0 && this.m_visible_cells) {
832
+ let itm = this._findItem(row, col);
833
+ if (itm) {
834
+ itm.setClass(cls, true); //todo: pb when changing classes
835
+ }
836
+ else {
837
+ this._buildItems();
838
+ }
839
+ }
840
+ }
841
+ getCellText(row, col) {
842
+ return this._getCellData(row, col).text;
843
+ }
844
+ getCellNumber(row, col) {
845
+ let text = this._getCellData(row, col).text;
846
+ return parseIntlFloat(text);
847
+ }
848
+ clearRow(row) {
849
+ for (let c = 0; c < this.m_columns.length; c++) {
850
+ this.clearCell(row, c);
851
+ }
852
+ this.update(10);
853
+ }
854
+ clearCell(row, col) {
855
+ this.setCellText(row, col, null);
856
+ }
857
+ editCurCell(forceText) {
858
+ this.editCell(this.m_selection.row, this.m_selection.col, forceText);
859
+ }
860
+ editCell(row, col, forcedText) {
861
+ if (!this.m_autoedit) {
862
+ return;
863
+ }
864
+ // disable edition
865
+ if (this.m_columns[col].createEditor === null) {
866
+ return;
867
+ }
868
+ this._scrollIntoView(row, col);
869
+ let item = this._findItem(row, col);
870
+ let place = item.dom;
871
+ let parent = place.parentElement;
872
+ let rc = place.getBoundingClientRect();
873
+ let prc = parent.getBoundingClientRect();
874
+ let cell = this._getCellData(row, col, true);
875
+ let edtBuilder = (props, col, row) => {
876
+ return new TextEdit(props);
877
+ };
878
+ if (this.m_columns[col].createEditor) {
879
+ edtBuilder = this.m_columns[col].createEditor;
880
+ }
881
+ let cellvalue = forcedText ? forcedText : (cell ? cell.text : '');
882
+ this.m_editor = edtBuilder({
883
+ cls: '@editor',
884
+ style: {
885
+ left: rc.left - prc.left,
886
+ top: rc.top - prc.top,
887
+ width: rc.width,
888
+ height: rc.height
889
+ },
890
+ tabIndex: false,
891
+ value: cellvalue,
892
+ data: {
893
+ row,
894
+ col
895
+ },
896
+ }, row, col);
897
+ if (!this.m_editor) {
898
+ return;
899
+ }
900
+ parent.appendChild(this.m_editor._build());
901
+ this._setupEditor();
902
+ this.m_editor.setData('old-value', cellvalue);
903
+ this.m_editor.focus();
904
+ if (this.m_editor instanceof TextEdit) {
905
+ this.m_editor.selectAll();
906
+ }
907
+ }
908
+ _setupEditor() {
909
+ let movesel = (sensy, sensx) => {
910
+ deferCall(() => {
911
+ this.killEditor(true);
912
+ this._moveSel(sensy, sensx);
913
+ this.editCurCell();
914
+ });
915
+ };
916
+ // todo: better
917
+ if (this.m_editor instanceof TextEdit) {
918
+ let editor = this.m_editor;
919
+ let input = editor.input;
920
+ input.setDomEvent('blur', () => {
921
+ this.killEditor(true);
922
+ });
923
+ input.setDomEvent('keydown', (e) => {
924
+ switch (e.key) {
925
+ case 'Escape': {
926
+ this.killEditor(false);
927
+ e.stopPropagation();
928
+ break;
929
+ }
930
+ case 'Enter':
931
+ case 'Tab': {
932
+ let sens = 3;
933
+ if (e.shiftKey) {
934
+ sens = -3;
935
+ }
936
+ movesel(0, sens);
937
+ e.stopPropagation();
938
+ break;
939
+ }
940
+ case 'ArrowUp':
941
+ case 'Up': {
942
+ movesel(-1, 0);
943
+ e.stopPropagation();
944
+ break;
945
+ }
946
+ case 'ArrowDown':
947
+ case 'Down': {
948
+ movesel(1, 0);
949
+ e.stopPropagation();
950
+ break;
951
+ }
952
+ }
953
+ });
954
+ }
955
+ else if (this.m_editor instanceof ComboBox) {
956
+ let input = this.m_editor.input;
957
+ input.setDomEvent('blur', () => {
958
+ this.killEditor(true);
959
+ });
960
+ input.setDomEvent('keydown', (e) => {
961
+ switch (e.key) {
962
+ case 'Escape': {
963
+ this.killEditor(false);
964
+ e.stopPropagation();
965
+ break;
966
+ }
967
+ case 'Enter':
968
+ case 'Tab': {
969
+ let sens = 3;
970
+ if (e.shiftKey) {
971
+ sens = -3;
972
+ }
973
+ movesel(0, sens);
974
+ e.stopPropagation();
975
+ break;
976
+ }
977
+ }
978
+ });
979
+ this.m_editor.showPopup();
980
+ this.m_editor.on('change', (ev) => {
981
+ this.killEditor(true);
982
+ });
983
+ this.m_editor.on('cancel', (ev) => {
984
+ this.killEditor(false);
985
+ });
986
+ }
987
+ }
988
+ killEditor(save) {
989
+ if (this.m_editor) {
990
+ if (save) {
991
+ let text, id;
992
+ if (this.m_editor instanceof TextEdit) {
993
+ text = this.m_editor.value;
994
+ }
995
+ else if (this.m_editor instanceof ComboBox) {
996
+ id = this.m_editor.value;
997
+ text = this.m_editor.valueText;
998
+ }
999
+ let row = this.m_editor.getData('row');
1000
+ let col = this.m_editor.getData('col');
1001
+ let old = this.m_editor.getData('old-value');
1002
+ this.setCellText(row, col, text);
1003
+ const ev = EvChange(text, { row, col, oldValue: old, id });
1004
+ this.emit('change', ev);
1005
+ if (ev.defaultPrevented) {
1006
+ this.setCellText(row, col, old);
1007
+ }
1008
+ }
1009
+ // cannot dipose while handling blur event, so we defer...
1010
+ let t = this.m_editor;
1011
+ asap(() => {
1012
+ t.dispose();
1013
+ });
1014
+ this.m_editor = null;
1015
+ this.focus();
1016
+ }
1017
+ }
1018
+ clearData() {
1019
+ this.m_cells_data = new Map();
1020
+ this.m_rows_data = new Map();
1021
+ }
1022
+ setCellText(row, col, value) {
1023
+ if (value == null || value.length == 0) {
1024
+ this.m_cells_data.delete(_mkid(row, col));
1025
+ value = ''; //'&nbsp';
1026
+ }
1027
+ else {
1028
+ let cell = this._getCellData(row, col, true);
1029
+ if (!cell) {
1030
+ cell = {};
1031
+ }
1032
+ cell.text = value;
1033
+ this.m_cells_data.set(_mkid(row, col), cell);
1034
+ }
1035
+ if (this.m_lockupdate == 0 && this.m_visible_cells) {
1036
+ let itm = this._findItem(row, col);
1037
+ if (itm) {
1038
+ if (this.m_columns[col].renderer) {
1039
+ value = this.m_columns[col].renderer(value, { row, col });
1040
+ }
1041
+ itm.setContent(value);
1042
+ }
1043
+ else {
1044
+ this._buildItems();
1045
+ }
1046
+ }
1047
+ }
1048
+ lockUpdate(start) {
1049
+ if (start) {
1050
+ this.m_lockupdate++;
1051
+ }
1052
+ else {
1053
+ if (--this.m_lockupdate == 0) {
1054
+ this._updateScroll(true);
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+ /**
1060
+ * @ignore
1061
+ */
1062
+ function _mkid(row, col) {
1063
+ return row * 1000 + col;
1064
+ }
1065
+ /**
1066
+ * @ignore
1067
+ */
1068
+ function _getid(key) {
1069
+ return {
1070
+ row: Math.floor(key / 1000) | 0,
1071
+ col: (key % 1000) | 0
1072
+ };
1073
+ }