x4js 2.3.10 → 2.3.11

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.
@@ -15,10 +15,10 @@
15
15
  **/
16
16
 
17
17
 
18
- import { Component, ComponentContent, ComponentEvents, ComponentProps, EvChange, EvClick, EvContextMenu, EvDblClick, EvSelectionChange, componentFromDOM } from '../../core/component';
18
+ import { Component, ComponentContent, ComponentEvent, ComponentEvents, ComponentProps, EvChange, EvClick, EvContextMenu, EvDblClick, EvSelectionChange, componentFromDOM } from '../../core/component';
19
19
  import { GridColumn } from '../gridview/gridview'
20
20
 
21
- import { class_ns, isNumber, isString, UnsafeHtml } from '../../core/core_tools';
21
+ import { class_ns, IFormElement, isFunction, isNumber, isString, UnsafeHtml } from '../../core/core_tools';
22
22
  import { CoreEvent, EventCallback, EventMap } from '../../core/core_events';
23
23
  import { kbNav } from '../../core/core_tools';
24
24
 
@@ -28,6 +28,7 @@ import { Box } from '../boxes/boxes';
28
28
  import { CSizer } from '../sizers/sizer'
29
29
  import { Viewport } from '../viewport/viewport';
30
30
  import { SimpleText } from '../label/label';
31
+ import { Input } from '../input/input';
31
32
  import { CoreElement } from '../../x4.js';
32
33
 
33
34
  import icons from "../assets/icons"
@@ -42,8 +43,39 @@ export type SSCellClassifier = ( row: number, col: number ) => string; // re
42
43
  export type RowClassifier = (row: number ) => string; // return the row computed class
43
44
  //export type CellRenderer = (row: number, col: number, content: any) => Component;
44
45
 
46
+ export type SSCellEditable = ( row: number, col: number ) => boolean; // col is the column index
47
+ export type SSCellEditor = ( row: number, col: number, value: any ) => Component; // col is the column index
48
+
45
49
  export interface SpreadsheetColumn extends Omit<GridColumn,"classifier"> {
46
50
  cellClassifier?: SSCellClassifier;
51
+
52
+ /** allow the inline edition of the cells of this column */
53
+ editable?: boolean | SSCellEditable;
54
+
55
+ /**
56
+ * custom cell editor (default is an input matching the column type)
57
+ * the component must implement the "form-element" interface
58
+ */
59
+ editor?: SSCellEditor;
60
+ }
61
+
62
+ /**
63
+ * fired before an edited value is written to the store
64
+ * call preventDefault to refuse the value
65
+ */
66
+
67
+ export interface EvCellChange extends ComponentEvent {
68
+ row: number;
69
+ col: number; // column index
70
+ colId: any; // column id (the key in the store)
71
+ value: any; // new value, the handler can change it
72
+ oldValue: any;
73
+ }
74
+
75
+ interface CellEdit extends CellRef {
76
+ editor: Component;
77
+ initial?: string; // text of the editor at start
78
+ busy?: boolean; // rows are being rebuilt, focus loss must be ignored
47
79
  }
48
80
 
49
81
 
@@ -181,6 +213,7 @@ export interface SpreadsheetEvents extends ComponentEvents {
181
213
  dblClick?: EvDblClick;
182
214
  contextMenu?: EvContextMenu;
183
215
  selectionChange?: EvSelectionChange;
216
+ cellChange?: EvCellChange;
184
217
  }
185
218
 
186
219
  export interface SpreadsheetProps extends ComponentProps {
@@ -189,7 +222,10 @@ export interface SpreadsheetProps extends ComponentProps {
189
222
  columns: SpreadsheetColumn[];
190
223
  rowClassifier?: RowClassifier;
191
224
 
225
+ /** where the selection goes when an edition is validated with Enter (default "down") */
226
+ enterMove?: "down" | "right" | "none";
192
227
 
228
+ cellChange?: EventCallback<EvCellChange>;
193
229
  click?: EventCallback<EvClick>;
194
230
  dblClick?: EventCallback<EvDblClick>;
195
231
  contextMenu?: EventCallback<EvContextMenu>;
@@ -267,6 +303,8 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
267
303
  private _has_fixed: boolean;
268
304
  private _has_footer: boolean;
269
305
 
306
+ private _edit: CellEdit; // cell being edited
307
+
270
308
  constructor(props: P) {
271
309
  super(props);
272
310
 
@@ -285,7 +323,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
285
323
 
286
324
  this._columns = props.columns.map(x => x);
287
325
 
288
- this.mapPropEvents(props, "click", "dblClick", "contextMenu", "selectionChange");
326
+ this.mapPropEvents(props, "click", "dblClick", "contextMenu", "selectionChange", "cellChange");
289
327
 
290
328
  this.lock(true);
291
329
  this.setAttribute("tabindex", 0);
@@ -322,6 +360,11 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
322
360
  return;
323
361
  }
324
362
 
363
+ // keys of the cell editor are handled by the editor
364
+ if( this._edit ) {
365
+ return;
366
+ }
367
+
325
368
  switch (ev.key) {
326
369
  case "ArrowDown": {
327
370
  this.navigate(kbNav.next);
@@ -363,8 +406,43 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
363
406
  break;
364
407
  }
365
408
 
366
- default:
409
+ case "F2":
410
+ case "Enter": {
411
+ const sel = this._curCell( );
412
+ if( !sel || !this.editCell( sel.row, sel.col ) ) {
413
+ return;
414
+ }
415
+ break;
416
+ }
417
+
418
+ case "Delete": {
419
+ const sel = this._curCell( );
420
+ if( !sel || !this._isEditable( sel.row, sel.col ) ) {
421
+ return;
422
+ }
423
+
424
+ this._setCellValue( sel.row, sel.col, null );
425
+ break;
426
+ }
427
+
428
+ default: {
429
+ const sel = this._curCell( );
430
+ if( !sel ) {
431
+ return;
432
+ }
433
+
434
+ if( ev.key==" " && this._toggleCell( sel.row, sel.col ) ) {
435
+ break;
436
+ }
437
+
438
+ // a typed char starts the edition: the editor takes the focus, so the char goes into it
439
+ const altgr = ev.getModifierState( "AltGraph" );
440
+ if( ev.key.length==1 && ( altgr || ( !ev.ctrlKey && !ev.altKey && !ev.metaKey ) ) ) {
441
+ this.editCell( sel.row, sel.col, true );
442
+ }
443
+
367
444
  return;
445
+ }
368
446
  }
369
447
 
370
448
  ev.preventDefault();
@@ -391,8 +469,8 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
391
469
 
392
470
  this._selection.forEach( x => {
393
471
  const row = x>>12;
394
- if( m===undefined || m>row ) { m = row; col=x&0xff; }
395
- if( M===undefined || M<row ) { M = row; col=x&0xff; }
472
+ if( m===undefined || m>row ) { m = row; col=x&0xfff; }
473
+ if( M===undefined || M<row ) { M = row; col=x&0xfff; }
396
474
  } );
397
475
 
398
476
  return [top ? m : M, col]
@@ -450,7 +528,13 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
450
528
  if (ncol >= 0 && ncol < this._columns.length ) {
451
529
  this._clearSelection(false);
452
530
  this._addSelection( mkid(fline,ncol), true);
453
- //this._scrollToIndex( nline );
531
+
532
+ // fixed columns are always visible
533
+ if( !this._getCol(ncol).fixed ) {
534
+ const cell = this.query( `.cell[data-ref="${mkid(fline,ncol)}"]` );
535
+ cell?.scrollIntoView( { block: "nearest", inline: "nearest" } );
536
+ }
537
+
454
538
  return true;
455
539
  }
456
540
  }
@@ -497,7 +581,9 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
497
581
  if (ev.type == 'changed' && this._selection.size ) {
498
582
  const nsel = new Set<number>();
499
583
  this._selection.forEach(x => {
500
- if( this._store.hasData( x ) ) {
584
+ // selection use column index, store use column id
585
+ const cdata = this._getCol( x & 0xfff );
586
+ if( cdata && this._store.hasData( x >> 12, cdata.id ) ) {
501
587
  nsel.add( x );
502
588
  }
503
589
  });
@@ -857,7 +943,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
857
943
  el.setInternalData("row", rowid);
858
944
  el.setData("ref", mkid(rowid, col) + "");
859
945
 
860
- if (this._selection.has(mkid(col, rowid))) {
946
+ if (this._selection.has(mkid(rowid, col))) {
861
947
  el.addClass("selected");
862
948
  }
863
949
 
@@ -1028,8 +1114,33 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1028
1114
  }
1029
1115
 
1030
1116
 
1117
+ // MOUSEDOWN
1118
+ this.addDOMEvent("mousedown", (e) => {
1119
+ if( !this._edit || this._inEditor(e) ) {
1120
+ return;
1121
+ }
1122
+
1123
+ // click on another cell while editing:
1124
+ // validate now, rows may be rebuilt before the click event (and the click lost)
1125
+ const ref = targetCell(e);
1126
+ if( ref && ref.row!==undefined ) {
1127
+ this._endEdit( true, true );
1128
+
1129
+ if (!this._selection.has(ref.ref)) {
1130
+ this._clearSelection( false );
1131
+ this._addSelection(ref.ref,true);
1132
+ }
1133
+
1134
+ e.preventDefault( );
1135
+ }
1136
+ });
1137
+
1031
1138
  // CLICK
1032
1139
  this.addDOMEvent("click", (e) => {
1140
+ if( this._inEditor(e) ) {
1141
+ return;
1142
+ }
1143
+
1033
1144
  const ref = targetCell(e);
1034
1145
  if (ref) {
1035
1146
  //TODO: multiselection
@@ -1037,11 +1148,17 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1037
1148
  this._clearSelection( false );
1038
1149
  this._addSelection(ref.ref,true);
1039
1150
  }
1151
+
1152
+ this._toggleCell( ref.row, ref.col );
1040
1153
  }
1041
1154
  });
1042
1155
 
1043
1156
  // DBLCLICK
1044
1157
  this.addDOMEvent("dblclick", (e) => {
1158
+ if( this._inEditor(e) ) {
1159
+ return;
1160
+ }
1161
+
1045
1162
  const ref = targetCell(e);
1046
1163
  if (ref) {
1047
1164
  //TODO: multiselection
@@ -1050,12 +1167,22 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1050
1167
  this._addSelection(ref.ref,true);
1051
1168
  }
1052
1169
 
1053
- this.fire( "dblClick", { context: { row: ref.row, col: ref.col } } );
1170
+ const dev: EvDblClick = { context: { row: ref.row, col: ref.col } };
1171
+ this.fire( "dblClick", dev );
1172
+
1173
+ if( !dev.defaultPrevented ) {
1174
+ this.editCell( ref.row, ref.col );
1175
+ }
1054
1176
  }
1055
1177
  });
1056
1178
 
1057
1179
  // CONTEXT
1058
1180
  this.addDOMEvent("contextmenu", (e) => {
1181
+ // keep the native menu of the editor (copy/paste)
1182
+ if( this._inEditor(e) ) {
1183
+ return;
1184
+ }
1185
+
1059
1186
  const ref = targetCell(e);
1060
1187
  if (ref) {
1061
1188
  //TODO: multiselection
@@ -1115,6 +1242,13 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1115
1242
 
1116
1243
  if (this._start != start || this._end != end || force) {
1117
1244
 
1245
+ // rows are rebuilt: the cell editor must survive
1246
+ const edit = this._edit;
1247
+ const edit_focus = edit ? edit.editor.dom.contains( document.activeElement ) : false;
1248
+ if( edit ) {
1249
+ edit.busy = true;
1250
+ }
1251
+
1118
1252
  const rows: Component[] = [];
1119
1253
  const headers: Component[] = [];
1120
1254
 
@@ -1175,6 +1309,10 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1175
1309
  else {
1176
1310
  this._vheader.addClass("@hidden");
1177
1311
  }
1312
+
1313
+ if( edit && this._edit===edit ) {
1314
+ this._restoreEditor( edit, edit_focus );
1315
+ }
1178
1316
  }
1179
1317
  }
1180
1318
  }
@@ -1211,7 +1349,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1211
1349
 
1212
1350
  if (notify) {
1213
1351
  const selection = this.getSelection();
1214
- this.fire("selectionChange", { selection, empty: selection.length != 0 });
1352
+ this.fire("selectionChange", { selection, empty: selection.length == 0 });
1215
1353
  }
1216
1354
  }
1217
1355
 
@@ -1243,5 +1381,378 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
1243
1381
 
1244
1382
  this._addSelection(mkid(row, col), true);
1245
1383
  }
1384
+
1385
+ // :: EDITION ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1386
+
1387
+ /**
1388
+ * @returns the selected cell when there is exactly one
1389
+ */
1390
+
1391
+ private _curCell( ): CellRef {
1392
+ if( this._selection.size!=1 ) {
1393
+ return null;
1394
+ }
1395
+
1396
+ const ref = this._selection.values().next().value as number;
1397
+ return { row: ref >> 12, col: ref & 0xfff };
1398
+ }
1399
+
1400
+ private _inEditor( e: Event ) {
1401
+ return !!this._edit && this._edit.editor.dom.contains( e.target as Node );
1402
+ }
1403
+
1404
+ /**
1405
+ *
1406
+ */
1407
+
1408
+ private _isEditable( row: number, col: number ) {
1409
+ const cdata = this._getCol( col );
1410
+ if( !cdata?.editable || !this._store || this.isDisabled() ) {
1411
+ return false;
1412
+ }
1413
+
1414
+ if( !(row>=0) || row>=this._store.getRowCount() ) {
1415
+ return false;
1416
+ }
1417
+
1418
+ if( isFunction(cdata.editable) && !cdata.editable( row, col ) ) {
1419
+ return false;
1420
+ }
1421
+
1422
+ // html content & images cannot be edited by the default editor
1423
+ if( !cdata.editor ) {
1424
+ if( cdata.type=="image" || cdata.type=="icon" ) {
1425
+ return false;
1426
+ }
1427
+
1428
+ if( this._store.getData( row, cdata.id ) instanceof UnsafeHtml ) {
1429
+ return false;
1430
+ }
1431
+ }
1432
+
1433
+ return true;
1434
+ }
1435
+
1436
+ /**
1437
+ * write a value to the store (after cellChange agreement)
1438
+ * @returns true if the value was written
1439
+ */
1440
+
1441
+ private _setCellValue( row: number, col: number, value: any ) {
1442
+ const cdata = this._getCol( col );
1443
+ if( !cdata || !this._store || row>=this._store.getRowCount() ) {
1444
+ return false;
1445
+ }
1446
+
1447
+ const empty = ( v: any ) => v===undefined || v===null || v==="";
1448
+ const same = ( a: any, b: any ) => {
1449
+ if( a instanceof Date && b instanceof Date ) {
1450
+ return a.getTime()==b.getTime();
1451
+ }
1452
+
1453
+ return a===b || ( empty(a) && empty(b) );
1454
+ }
1455
+
1456
+ const oldValue = this._store.getData( row, cdata.id );
1457
+ if( same( value, oldValue ) ) {
1458
+ return false;
1459
+ }
1460
+
1461
+ const ev: EvCellChange = { row, col, colId: cdata.id, value, oldValue };
1462
+ this.fire( "cellChange", ev );
1463
+
1464
+ if( ev.defaultPrevented ) {
1465
+ return false;
1466
+ }
1467
+
1468
+ this._store.setData( row, cdata.id, ev.value );
1469
+ return true;
1470
+ }
1471
+
1472
+ /**
1473
+ * editable checkboxes are toggled without editor
1474
+ */
1475
+
1476
+ private _toggleCell( row: number, col: number ) {
1477
+ const cdata = this._getCol( col );
1478
+ if( cdata?.type!="checkbox" || cdata.editor || !this._isEditable( row, col ) ) {
1479
+ return false;
1480
+ }
1481
+
1482
+ this._setCellValue( row, col, !this._store.getData( row, cdata.id ) );
1483
+ return true;
1484
+ }
1485
+
1486
+ /**
1487
+ * default editor: an input matching the column type
1488
+ */
1489
+
1490
+ private _createEditor( cdata: SpreadsheetColumn, value: any, clear: boolean ): Component {
1491
+ switch( cdata.type ) {
1492
+ case "number":
1493
+ case "money":
1494
+ case "percent": {
1495
+ const ed = new Input( { type: "number", value: !clear && isNumber(value) ? value : undefined } );
1496
+ ed.setAttribute( "step", "any" );
1497
+ return ed;
1498
+ }
1499
+
1500
+ case "date": {
1501
+ return new Input( { type: "date", value: !clear && value instanceof Date ? value : undefined } );
1502
+ }
1503
+
1504
+ default: {
1505
+ return new Input( { type: "text", value: clear || value===undefined || value===null ? "" : value+"" } );
1506
+ }
1507
+ }
1508
+ }
1509
+
1510
+ /**
1511
+ * @returns the value of the editor, undefined if there is nothing to write
1512
+ */
1513
+
1514
+ private _readEditor( edit: CellEdit ): any {
1515
+ const cdata = this._getCol( edit.col );
1516
+ if( !cdata ) {
1517
+ return undefined;
1518
+ }
1519
+
1520
+ if( cdata.editor ) {
1521
+ const fe = edit.editor.queryInterface<IFormElement>( "form-element" );
1522
+ return fe && fe.isValid() ? fe.getRawValue( ) : undefined;
1523
+ }
1524
+
1525
+ const input = edit.editor as Input;
1526
+ if( (input.dom as HTMLInputElement).validity?.badInput ) {
1527
+ return undefined;
1528
+ }
1529
+
1530
+ const text = input.getValue( );
1531
+ if( text===edit.initial ) {
1532
+ return undefined; // untouched
1533
+ }
1534
+
1535
+ switch( cdata.type ) {
1536
+ case "number":
1537
+ case "money":
1538
+ case "percent": {
1539
+ const v = parseFloat( text );
1540
+ return isNaN(v) ? null : v;
1541
+ }
1542
+
1543
+ case "date": {
1544
+ const [y,m,d] = text.split( "-" ).map( x => parseInt(x) );
1545
+ return y ? new Date( y, m-1, d ) : null;
1546
+ }
1547
+
1548
+ default: {
1549
+ // keep numbers as numbers
1550
+ const old = this._store.getData( edit.row, cdata.id );
1551
+ if( isNumber(old) && text!=="" && isFinite( Number(text) ) ) {
1552
+ return Number( text );
1553
+ }
1554
+
1555
+ return text;
1556
+ }
1557
+ }
1558
+ }
1559
+
1560
+ private _focusEditor( editor: Component ) {
1561
+ const dom = editor.dom as HTMLElement;
1562
+ const el = dom.matches( "input,select,textarea" ) ? dom : dom.querySelector<HTMLElement>( "input,select,textarea,[tabindex]" );
1563
+ ( el ?? dom ).focus( { preventScroll: true } );
1564
+ }
1565
+
1566
+ /**
1567
+ * rows have been rebuilt: put the editor back in its cell
1568
+ */
1569
+
1570
+ private _restoreEditor( edit: CellEdit, focus: boolean ) {
1571
+ const cell = this.query( `.cell[data-ref="${mkid(edit.row,edit.col)}"]` );
1572
+ if( !cell ) {
1573
+ // the cell is no more visible
1574
+ edit.busy = false;
1575
+ this._endEdit( true, focus );
1576
+ return;
1577
+ }
1578
+
1579
+ if( edit.editor.dom.parentElement!=cell.dom ) {
1580
+ cell.addClass( "editing" );
1581
+ cell.appendContent( edit.editor );
1582
+ }
1583
+
1584
+ if( focus ) {
1585
+ this._focusEditor( edit.editor );
1586
+ }
1587
+
1588
+ edit.busy = false;
1589
+ }
1590
+
1591
+ /**
1592
+ *
1593
+ */
1594
+
1595
+ private _on_edit_key( ev: KeyboardEvent ) {
1596
+ // the grid must not handle the editor keys (navigation)
1597
+ ev.stopPropagation( );
1598
+
1599
+ if( ev.isComposing ) {
1600
+ return;
1601
+ }
1602
+
1603
+ switch( ev.key ) {
1604
+ case "Enter": {
1605
+ this._endEdit( true, true );
1606
+
1607
+ const move = this.props.enterMove ?? "down";
1608
+ if( move=="down" ) {
1609
+ this.navigate( ev.shiftKey ? kbNav.prev : kbNav.next );
1610
+ }
1611
+ else if( move=="right" ) {
1612
+ this.navigate( ev.shiftKey ? kbNav.left : kbNav.right );
1613
+ }
1614
+
1615
+ break;
1616
+ }
1617
+
1618
+ case "Tab": {
1619
+ this._endEdit( true, true );
1620
+ this.navigate( ev.shiftKey ? kbNav.left : kbNav.right );
1621
+ break;
1622
+ }
1623
+
1624
+ case "Escape": {
1625
+ this._endEdit( false, true );
1626
+ break;
1627
+ }
1628
+
1629
+ default:
1630
+ return;
1631
+ }
1632
+
1633
+ ev.preventDefault( );
1634
+ }
1635
+
1636
+ /**
1637
+ * @param refocus - give the focus back to the grid (default: only if the editor has it)
1638
+ */
1639
+
1640
+ private _endEdit( commit: boolean, refocus?: boolean ) {
1641
+ const edit = this._edit;
1642
+ if( !edit ) {
1643
+ return;
1644
+ }
1645
+
1646
+ this._edit = null;
1647
+
1648
+ const value = commit ? this._readEditor( edit ) : undefined;
1649
+ const dom = edit.editor.dom;
1650
+
1651
+ if( refocus===undefined ) {
1652
+ refocus = dom.contains( document.activeElement );
1653
+ }
1654
+
1655
+ const cell = componentFromDOM( dom.parentElement );
1656
+ cell?.removeClass( "editing" );
1657
+ dom.remove( );
1658
+
1659
+ if( refocus ) {
1660
+ (this.dom as HTMLElement).focus( { preventScroll: true } );
1661
+ }
1662
+
1663
+ if( value!==undefined ) {
1664
+ this._setCellValue( edit.row, edit.col, value );
1665
+ }
1666
+ }
1667
+
1668
+ /**
1669
+ * start the edition of a cell (the cell must be visible and its column editable)
1670
+ * @param row - row index
1671
+ * @param col - column index
1672
+ * @param clear - start with an empty editor
1673
+ * @returns false if the cell cannot be edited
1674
+ */
1675
+
1676
+ editCell( row: number, col: number, clear = false ): boolean {
1677
+ this._endEdit( true );
1678
+
1679
+ const cdata = this._getCol( col );
1680
+ if( !this._isEditable( row, col ) || ( cdata.type=="checkbox" && !cdata.editor ) ) {
1681
+ return false;
1682
+ }
1683
+
1684
+ const ref = mkid( row, col );
1685
+ const cell = this.query( `.cell[data-ref="${ref}"]` );
1686
+ if( !cell ) {
1687
+ return false;
1688
+ }
1689
+
1690
+ if( this._selection.size!=1 || !this._selection.has(ref) ) {
1691
+ this._clearSelection( false );
1692
+ this._addSelection( ref, true );
1693
+ }
1694
+
1695
+ const value = this._store.getData( row, cdata.id );
1696
+ const editor = cdata.editor ? cdata.editor( row, col, value ) : this._createEditor( cdata, value, clear );
1697
+ if( !editor ) {
1698
+ return false;
1699
+ }
1700
+
1701
+ const edit: CellEdit = { row, col, editor };
1702
+ if( !cdata.editor ) {
1703
+ edit.initial = (editor as Input).getValue( );
1704
+ }
1705
+
1706
+ editor.addClass( "cell-editor" );
1707
+ editor.addDOMEvent( "keydown", ( ev ) => this._on_edit_key( ev ) );
1708
+
1709
+ // avoid a second dispatch of the editor handlers by the grid
1710
+ editor.addDOMEvent( "wheel", ( ev ) => ev.stopPropagation( ) );
1711
+
1712
+ editor.addDOMEvent( "focusout", ( ev ) => {
1713
+ if( this._edit!==edit || edit.busy ) {
1714
+ return;
1715
+ }
1716
+
1717
+ // focus is moving inside the editor
1718
+ const to = ev.relatedTarget as Node;
1719
+ if( to && editor.dom.contains(to) ) {
1720
+ return;
1721
+ }
1722
+
1723
+ this._endEdit( true, false );
1724
+ });
1725
+
1726
+ this._edit = edit;
1727
+
1728
+ cell.addClass( "editing" );
1729
+ cell.appendContent( editor );
1730
+ this._focusEditor( editor );
1731
+
1732
+ if( !clear && !cdata.editor ) {
1733
+ const input = editor as Input;
1734
+ if( cdata.type=="number" || cdata.type=="money" || cdata.type=="percent" ) {
1735
+ input.selectAll( );
1736
+ }
1737
+ else if( cdata.type!="date" ) {
1738
+ input.select( input.getValue().length, 0 );
1739
+ }
1740
+ }
1741
+
1742
+ return true;
1743
+ }
1744
+
1745
+ /**
1746
+ * stop the current edition
1747
+ * @param commit - false to cancel
1748
+ */
1749
+
1750
+ stopEdit( commit = true ) {
1751
+ this._endEdit( commit );
1752
+ }
1753
+
1754
+ isEditing( ) {
1755
+ return !!this._edit;
1756
+ }
1246
1757
  }
1247
1758
 
@@ -15,10 +15,10 @@
15
15
  **/
16
16
 
17
17
  //import { EventCallback } from '../../core/core_events';
18
- import { Component, makeUniqueComponentId } from '../../core/component';
18
+ import { Component, ComponentEvents, makeUniqueComponentId } from '../../core/component';
19
19
  import { class_ns, UnsafeHtml } from '../../core/core_tools';
20
20
 
21
- import { HBox } from '../boxes/boxes';
21
+ import { BoxProps, HBox } from '../boxes/boxes';
22
22
  import { DateProps, Input, InputProps, NumberProps, TextInputProps, TimeProps } from "../input/input"
23
23
  import { Label } from '../label/label';
24
24
 
@@ -32,7 +32,7 @@ import "./textedit.module.scss";
32
32
 
33
33
  type TextEditInputs = TextInputProps | NumberProps | DateProps | TimeProps;
34
34
 
35
- interface TextEditBase {
35
+ export interface TextEditBase {
36
36
  label: string | UnsafeHtml;
37
37
  labelWidth?: number;
38
38
  inputWidth?: number;
@@ -82,7 +82,7 @@ interface TextEditProps extends InputProps {
82
82
  */
83
83
 
84
84
  @class_ns( "x4" )
85
- export class TextEdit extends HBox {
85
+ export class TextEdit<E extends ComponentEvents = ComponentEvents> extends HBox<BoxProps,E> {
86
86
 
87
87
  private input: Input;
88
88