x4js 2.3.16 → 2.3.18

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.
@@ -80,6 +80,12 @@ export interface GridviewProps extends ComponentProps {
80
80
  columns: GridColumn[];
81
81
  emptyMsg?: string;
82
82
 
83
+ /**
84
+ * several rows can be selected: Ctrl (Cmd on a Mac) + click adds a row or
85
+ * removes it, Shift + click selects up to the row, Ctrl + A selects all
86
+ */
87
+ multisel?: boolean;
88
+
83
89
  click?: EventCallback<EvClick>;
84
90
  dblClick?: EventCallback<EvDblClick>;
85
91
  contextMenu?: EventCallback<EvContextMenu>;
@@ -155,7 +161,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
155
161
  private _start: number;
156
162
  private _end: number;
157
163
 
158
- private _selection: Set<number>;
164
+ private _selection: Set<any>; // the ids of the selected records: a row changes place when the view is sorted
159
165
 
160
166
 
161
167
  // TODO: that
@@ -195,11 +201,26 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
195
201
  this.setAttribute("tabindex", 0);
196
202
 
197
203
  this.addDOMEvent("created", () => {
204
+ // back in the document: the view follows its store again (see "removed")
205
+ if (this._dataview) {
206
+ this._dataview.open( );
207
+ }
208
+
198
209
  this._init();
199
210
  this._dirty = 1;
200
211
  this.lock(false);
201
212
  });
202
213
 
214
+ // out of the document: its store must not keep the grid alive, nor redraw it
215
+ this.addDOMEvent("removed", () => {
216
+ if (this._dataview) {
217
+ this._dataview.close( );
218
+ }
219
+
220
+ // as before its first insertion: nothing is drawn out of the document
221
+ this.lock( true );
222
+ });
223
+
203
224
  this.addDOMEvent("resized", () => {
204
225
  this._updateFlexs( );
205
226
  this._computeFullSize( );
@@ -210,6 +231,200 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
210
231
  this._on_key( e );
211
232
  })
212
233
 
234
+ // the handlers of the grid itself are put once, here. _init builds what is
235
+ // inside the grid, each time it comes in the document
236
+
237
+ // WHEEL
238
+ this.addDOMEvent("wheel", (ev: WheelEvent) => {
239
+ // many rows: one pixel of the scroll bar is one row, wherever the pointer is
240
+ if (ev.deltaY && this._dataview && this._dataview.getCount() >= SCROLL_LIMIT) {
241
+ this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -1 : 1);
242
+ ev.stopPropagation();
243
+ ev.preventDefault();
244
+ }
245
+ else if (this._has_fixed && ev.deltaY) {
246
+ // wheel on fixed part
247
+ // fixed part do not have scrollbar, so we need to handle it by hand
248
+ let t = ev.target as Node;
249
+ while (t != this.dom) {
250
+ if (t == this._vheader.dom) {
251
+ this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -this._row_height : this._row_height);
252
+ ev.stopPropagation();
253
+ ev.preventDefault();
254
+ break;
255
+ }
256
+
257
+ t = t.parentNode;
258
+ }
259
+ }
260
+ })
261
+
262
+ const targetRow = ( e: MouseEvent ) => {
263
+ let el = Component.parentElement(e.target as HTMLElement, Component);
264
+ while (el && !el.hasClass("row")) {
265
+ el = el.parentElement();
266
+ }
267
+
268
+ if (el) {
269
+ return el.getIntData("row");
270
+ }
271
+
272
+ return undefined;
273
+ }
274
+
275
+ const inColHeader = ( e: MouseEvent ) => {
276
+ let el = Component.parentElement(e.target as HTMLElement, Component);
277
+ while (el && !el.hasClass("col-header")) {
278
+ el = el.parentElement();
279
+ }
280
+
281
+ return !!el;
282
+ }
283
+
284
+ // multisel, Ctrl + click: one row more, or one row less
285
+ const toggleRow = ( row: number ) => {
286
+ const id = this._dataview.getIdByIndex( row );
287
+ if( !this._selection.has( id ) ) {
288
+ this._addSelection( row );
289
+ return;
290
+ }
291
+
292
+ this._selection.delete( id );
293
+ this.queryAll(`.row[data-row="${row}"]`).forEach(el => {
294
+ el.removeClass("selected");
295
+ });
296
+
297
+ this.fire("selectionChange", { selection: this.getSelection( ) ?? [], empty: this._selection.size==0 } );
298
+ }
299
+
300
+ // multisel, Shift + click: from the row that was selected last to this one
301
+ const selectTo = ( row: number ) => {
302
+ let last: any;
303
+ for (const id of this._selection) {
304
+ last = id;
305
+ }
306
+
307
+ const from = this._dataview.indexOfId( last );
308
+
309
+ this._clearSelection( false );
310
+
311
+ // the row to start from is added last: the next Shift + click starts from it too
312
+ if( from<0 ) {
313
+ this._addSelection( row, false );
314
+ }
315
+ else if( from<=row ) {
316
+ for( let r=row; r>=from; r-- ) {
317
+ this._addSelection( r, false );
318
+ }
319
+ }
320
+ else {
321
+ for( let r=row; r<=from; r++ ) {
322
+ this._addSelection( r, false );
323
+ }
324
+ }
325
+
326
+ this.fire("selectionChange", { selection: this.getSelection( ), empty: false } );
327
+ }
328
+
329
+
330
+ // CLICK
331
+ this.addDOMEvent("click", (e) => {
332
+ const row = targetRow( e );
333
+ if (row!==undefined ) {
334
+ if( this.props.multisel && ( e.ctrlKey || e.metaKey ) ) {
335
+ toggleRow( row );
336
+ }
337
+ else if( this.props.multisel && e.shiftKey && this._selection.size ) {
338
+ selectTo( row );
339
+ }
340
+ // this row alone
341
+ else if( this._selection.size!=1 || !this._selection.has( this._dataview.getIdByIndex( row ) ) ) {
342
+ this._clearSelection( false );
343
+ this._addSelection(row);
344
+ }
345
+
346
+ // once the selection is what the click made it
347
+ const rec = this._dataview.getByIndex( row );
348
+ this.fire( "click", { context: rec } );
349
+ }
350
+ // a click in a column header sorts or sizes it: what is selected stays
351
+ else if( !inColHeader( e ) ) {
352
+ this._clearSelection( true );
353
+ }
354
+ });
355
+
356
+ // DBLCLICK
357
+ this.addDOMEvent("dblclick", (e) => {
358
+ const row = targetRow( e );
359
+ if (row!==undefined ) {
360
+ if( !this._selection.has( this._dataview.getIdByIndex( row ) ) ) {
361
+ this._clearSelection( false );
362
+ this._addSelection(row);
363
+ }
364
+
365
+ this._on_dblclk( e, row );
366
+
367
+ const rec = this._dataview.getByIndex( row );
368
+ this.fire( "dblClick", { context: rec } );
369
+ }
370
+ });
371
+
372
+ // CONTEXT
373
+ this.addDOMEvent("contextmenu", (e) => {
374
+ const row = targetRow( e );
375
+ if (row!==undefined ) {
376
+ // on a selected row, the menu is for all that is selected
377
+ if( !this._selection.has( this._dataview.getIdByIndex( row ) ) ) {
378
+ this._clearSelection( false );
379
+ this._addSelection(row);
380
+ }
381
+
382
+ const rec = this._dataview.getByIndex( row );
383
+ this.fire( "contextMenu", { uievent: e, context: rec } );
384
+ }
385
+
386
+ e.preventDefault( );
387
+ e.stopPropagation( );
388
+ });
389
+
390
+ // MOUSE OVER
391
+ this.addDOMEvent("mouseover", (e) => {
392
+
393
+ if (!this._has_fixed) {
394
+ return;
395
+ }
396
+
397
+ let el = Component.parentElement(e.target as HTMLElement, Component);
398
+ while (el && !el.hasClass("row")) {
399
+ el = el.parentElement();
400
+ }
401
+
402
+ if (el) {
403
+ const data = el.getData("row");
404
+
405
+ this.queryAll(".hover").forEach(x => x.removeClass("hover"));
406
+
407
+ if (data) {
408
+ const rows = this.queryAll(`.row[data-row="${data}"]`);
409
+ rows.forEach(x => x.addClass("hover"));
410
+ }
411
+ }
412
+ });
413
+
414
+ // MOUSE LEAVE
415
+ this.addDOMEvent("mouseleave", (e) => {
416
+
417
+ if (!this._has_fixed) {
418
+ return;
419
+ }
420
+
421
+ this.queryAll(".hover").forEach(x => x.removeClass("hover"));
422
+ });
423
+
424
+ if( props.multisel ) {
425
+ this.addShortcut( "Mod+A", ( ) => this.selectAll( ) );
426
+ }
427
+
213
428
  if (props.store) {
214
429
  this.setStore(props.store);
215
430
 
@@ -272,7 +487,18 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
272
487
  */
273
488
 
274
489
  navigate( sens: kbNav ) {
275
- if( !this._selection.size ) {
490
+ // no row to go to
491
+ if( !this._dataview || !this._dataview.getCount( ) ) {
492
+ return false;
493
+ }
494
+
495
+ // the row to start from: the one of the first selected record
496
+ let fsel = -1;
497
+ if( this._selection.size ) {
498
+ fsel = this._dataview.indexOfId( this._selection.values().next().value );
499
+ }
500
+
501
+ if( fsel<0 ) {
276
502
  if( sens==kbNav.next || sens==kbNav.pgdn ) {
277
503
  sens = kbNav.first;
278
504
  }
@@ -289,7 +515,6 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
289
515
  return true;
290
516
  }
291
517
  else if( sens==kbNav.prev || sens==kbNav.next ) {
292
- const fsel = this._selection.values().next().value;
293
518
  let nel = sens==kbNav.next ? fsel+1 : fsel-1;
294
519
  if( nel>=0 && nel<this._dataview.getCount() ) {
295
520
  this._clearSelection( false );
@@ -301,8 +526,6 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
301
526
  else if( sens==kbNav.pgdn || sens==kbNav.pgup ) {
302
527
  const pgh = this._vis_rows.size;
303
528
 
304
- const fsel = this._selection.values().next().value;
305
-
306
529
  let sby = sens==kbNav.pgdn ? pgh : -pgh;
307
530
  let nel = fsel+sby;
308
531
 
@@ -366,8 +589,20 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
366
589
  return;
367
590
  }
368
591
 
369
- if (ev.change_type == 'change') {
370
- this._selection.clear();
592
+ // the data or the filter changed: a record that left the view leaves the selection
593
+ if (ev.change_type != 'sort') {
594
+ let lost = false;
595
+
596
+ for (const id of this._selection) {
597
+ if (this._dataview.indexOfId( id ) < 0) {
598
+ this._selection.delete( id );
599
+ lost = true;
600
+ }
601
+ }
602
+
603
+ if (lost) {
604
+ this.fire("selectionChange", { selection: this.getSelection( ) ?? [], empty: this._selection.size==0 } );
605
+ }
371
606
  }
372
607
 
373
608
  this._updateFlexs( );
@@ -375,9 +610,9 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
375
610
  this._update(true);
376
611
  }
377
612
 
378
- // unlink previous observer
613
+ // the previous view stops following its store
379
614
  if (this._dataview) {
380
- this._dataview.off('view_change', on_change);
615
+ this._dataview.close( );
381
616
  }
382
617
 
383
618
  if (store) {
@@ -497,7 +732,9 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
497
732
  const sizer = new CSizer("right");
498
733
 
499
734
  sizer.on("stop", ( ) => {
735
+ // the flex columns took what was left: the body gets the width of its columns
500
736
  this._updateFlexs( );
737
+ this._computeFullSize( );
501
738
  })
502
739
 
503
740
  sizer.on("resize", (ev) => {
@@ -519,6 +756,24 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
519
756
  }
520
757
  })
521
758
 
759
+ // a double click on the sizer: the column takes its share of the place
760
+ // that is left (flex 1). To stop taking it, the sizer is dragged
761
+ sizer.addDOMEvent("dblclick", (ev) => {
762
+ // both: the cell under the sizer sorts its column on a double click
763
+ ev.stopPropagation( );
764
+ ev.preventDefault( );
765
+
766
+ // a fixed column is not in the place that is shared
767
+ if (fixed || cdata.flex) {
768
+ return;
769
+ }
770
+
771
+ cdata.flex = 1;
772
+
773
+ this._updateFlexs( );
774
+ this._computeFullSize( );
775
+ });
776
+
522
777
  const cell = new Component({
523
778
  cls: `cell`,
524
779
  attrs: { "data-col": col },
@@ -530,6 +785,11 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
530
785
  ]
531
786
  });
532
787
 
788
+ // the column the grid is sorted by
789
+ if (cdata.sens) {
790
+ cell.addClass( cdata.sens==="up" ? "sorted" : "sorted desc" );
791
+ }
792
+
533
793
  cell.addDOMEvent("touchend", () => {
534
794
  const last = cell.getInternalData("touchend");
535
795
  const now = Date.now();
@@ -614,34 +874,29 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
614
874
  this.setTimeout("sort", 50, () => {
615
875
  let asc = true;
616
876
 
617
- // already sorted ?
618
- const scol = this.query(`.col-header .cell[data-col="${col}"]`);
619
- if( !scol ) {
620
- return;
621
- }
877
+ const cdata = this._getCol(col);
622
878
 
623
879
  if( ascending===undefined ) {
624
- if (scol.hasClass("sorted")) {
625
- if (scol.hasClass("desc")) {
626
- asc = true;
627
- }
628
- else {
629
- asc = false;
630
- }
631
- }
632
- else {
633
- const sorted = this.queryAll(".sorted");
634
- sorted.forEach(x => x.removeClass("sorted asc desc"));
635
- }
880
+ // a double click on the sorted column reverses it
881
+ asc = cdata.sens!=="up";
636
882
  }
637
883
  else {
638
884
  asc = ascending;
639
885
  }
640
886
 
641
- scol.setClass("sorted");
642
- scol.setClass("desc", !asc);
887
+ // one column is sorted. It is kept in the columns, not in the header: the header
888
+ // may not be built yet, and is built again each time the grid comes in the document
889
+ this._columns.forEach( c => { c.sens = undefined; } );
890
+ cdata.sens = asc ? "up" : "dn";
643
891
 
644
- const cdata = this._getCol(col);
892
+ const sorted = this.queryAll(".sorted");
893
+ sorted.forEach(x => x.removeClass("sorted asc desc"));
894
+
895
+ const scol = this.query(`.col-header .cell[data-col="${col}"]`);
896
+ if( scol ) {
897
+ scol.setClass("sorted");
898
+ scol.setClass("desc", !asc);
899
+ }
645
900
 
646
901
  let num = false;
647
902
  switch (cdata.type) {
@@ -664,6 +919,11 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
664
919
 
665
920
  this._update(true);
666
921
 
922
+ // sorted by a double click on a header: what is selected is shown where it went
923
+ if( ascending===undefined && this._selection.size ) {
924
+ this._scrollToIndex( this._dataview.indexOfId( this._selection.values().next().value ) );
925
+ }
926
+
667
927
  setWaitCursor(false);
668
928
  });
669
929
  }
@@ -689,6 +949,11 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
689
949
  const col = column.id;
690
950
  const type = column.type;
691
951
 
952
+ // a column that is not a field of the model (actions...) has only its renderer to draw it
953
+ if( column.renderer && isString( col ) && this._datamodel.getFieldIndex( col )<0 ) {
954
+ return column.renderer( rec, col );
955
+ }
956
+
692
957
  let data = this._datamodel.getRaw(col, rec);
693
958
  if (data === undefined || data === null) {
694
959
  return null;
@@ -837,7 +1102,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
837
1102
  const rowel = new Box({ cls: "row", style: { top: top.toFixed(2) + "px" }, content: els });
838
1103
  rowel.setData("row", rowid + "");
839
1104
 
840
- if (this._selection.has(rowid)) {
1105
+ if (this._selection.has( this._dataview.getRecId( rec ) )) {
841
1106
  rowel.addClass("selected");
842
1107
  }
843
1108
 
@@ -894,7 +1159,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
894
1159
  const rowel = new Box({ cls: "row", style: { top: top + "px" }, content: cols });
895
1160
  rowel.setData("row", rowid + "");
896
1161
 
897
- if (this._selection.has(rowid)) {
1162
+ if (this._selection.has( this._dataview.getRecId( rec ) )) {
898
1163
  rowel.addClass("selected");
899
1164
  }
900
1165
 
@@ -913,8 +1178,13 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
913
1178
 
914
1179
  for (let x = 0; x < ccount; x++) {
915
1180
  const cdata = this._getCol(x);
916
-
917
- if( !cdata.fixed && cdata.flex ) {
1181
+
1182
+ // the fixed columns are not in the viewport: their width is not taken from its own
1183
+ if( cdata.fixed ) {
1184
+ continue;
1185
+ }
1186
+
1187
+ if( cdata.flex ) {
918
1188
  flexc += cdata.flex;
919
1189
  }
920
1190
  else {
@@ -1016,129 +1286,6 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1016
1286
  this._update()
1017
1287
  });
1018
1288
 
1019
- // WHEEL
1020
- this.addDOMEvent("wheel", (ev: WheelEvent) => {
1021
- if (ev.deltaY && this._dataview && this._dataview.getCount() >= SCROLL_LIMIT) {
1022
- this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -1 : 1);
1023
- ev.stopPropagation();
1024
- ev.preventDefault();
1025
- }
1026
-
1027
- if (this._has_fixed && ev.deltaY) {
1028
- // wheel on fixed part
1029
- // fixed part do not have scrollbar, so we need to handle it by hand
1030
- let t = ev.target as Node;
1031
- while (t != this.dom) {
1032
- if (t == this._vheader.dom) {
1033
- this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -this._row_height : this._row_height);
1034
- ev.stopPropagation();
1035
- ev.preventDefault();
1036
- break;
1037
- }
1038
-
1039
- t = t.parentNode;
1040
- }
1041
- }
1042
- })
1043
-
1044
- const targetRow = ( e: MouseEvent ) => {
1045
- let el = Component.parentElement(e.target as HTMLElement, Component);
1046
- while (el && !el.hasClass("row")) {
1047
- el = el.parentElement();
1048
- }
1049
-
1050
- if (el) {
1051
- return el.getIntData("row");
1052
- }
1053
-
1054
- return undefined;
1055
- }
1056
-
1057
-
1058
- // CLICK
1059
- this.addDOMEvent("click", (e) => {
1060
- const row = targetRow( e );
1061
- if (row!==undefined ) {
1062
- //TODO: multiselection
1063
- if( !this._selection.has(row) ) {
1064
- this._clearSelection( false );
1065
- this._addSelection(row);
1066
- }
1067
- }
1068
- else {
1069
- this._clearSelection( true );
1070
- }
1071
- });
1072
-
1073
- // DBLCLICK
1074
- this.addDOMEvent("dblclick", (e) => {
1075
- const row = targetRow( e );
1076
- if (row!==undefined ) {
1077
- //TODO: multiselection
1078
- if( !this._selection.has(row) ) {
1079
- this._clearSelection( false );
1080
- this._addSelection(row);
1081
- }
1082
-
1083
- this._on_dblclk( e, row );
1084
-
1085
- const rec = this._dataview.getByIndex( row );
1086
- this.fire( "dblClick", { context: rec } );
1087
- }
1088
- });
1089
-
1090
- // CONTEXT
1091
- this.addDOMEvent("contextmenu", (e) => {
1092
- const row = targetRow( e );
1093
- if (row!==undefined ) {
1094
- //TODO: multiselection
1095
- if( !this._selection.has(row) ) {
1096
- this._clearSelection( false );
1097
- this._addSelection(row);
1098
- }
1099
-
1100
- const rec = this._dataview.getByIndex( row );
1101
- this.fire( "contextMenu", { uievent: e, context: rec } );
1102
- }
1103
-
1104
- e.preventDefault( );
1105
- e.stopPropagation( );
1106
- });
1107
-
1108
- // MOUSE OVER
1109
- this.addDOMEvent("mouseover", (e) => {
1110
-
1111
- if (!this._has_fixed) {
1112
- return;
1113
- }
1114
-
1115
- let el = Component.parentElement(e.target as HTMLElement, Component);
1116
- while (el && !el.hasClass("row")) {
1117
- el = el.parentElement();
1118
- }
1119
-
1120
- if (el) {
1121
- const data = el.getData("row");
1122
-
1123
- this.queryAll(".hover").forEach(x => x.removeClass("hover"));
1124
-
1125
- if (data) {
1126
- const rows = this.queryAll(`.row[data-row="${data}"]`);
1127
- rows.forEach(x => x.addClass("hover"));
1128
- }
1129
- }
1130
- });
1131
-
1132
- // MOUSE LEAVE
1133
- this.addDOMEvent("mouseleave", (e) => {
1134
-
1135
- if (!this._has_fixed) {
1136
- return;
1137
- }
1138
-
1139
- this.queryAll(".hover").forEach(x => x.removeClass("hover"));
1140
- });
1141
-
1142
1289
  this._updateFlexs( );
1143
1290
 
1144
1291
  this._fheader = this._buildColHeader(true);
@@ -1271,17 +1418,37 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1271
1418
  this._clearSelection( false );
1272
1419
  }
1273
1420
 
1421
+ /**
1422
+ * Select every record of the view (with multisel only).
1423
+ * The id of each record is kept, and selectionChange gives them all:
1424
+ * on a very large view this costs memory.
1425
+ */
1426
+
1427
+ selectAll( ) {
1428
+ const count = this._dataview ? this._dataview.getCount( ) : 0;
1429
+ if( !this.props.multisel || !count ) {
1430
+ return;
1431
+ }
1432
+
1433
+ for( let row=0; row<count; row++ ) {
1434
+ this._selection.add( this._dataview.getIdByIndex( row ) );
1435
+ }
1436
+
1437
+ this.queryAll(".row").forEach(el => {
1438
+ el.addClass("selected");
1439
+ });
1440
+
1441
+ this.fire("selectionChange", { selection: this.getSelection( ), empty: false } );
1442
+ }
1443
+
1274
1444
  private _clearSelection( notify: boolean ) {
1275
1445
  if( !this._selection.size ) {
1276
1446
  return;
1277
1447
  }
1278
1448
 
1279
- for (const ref of this._selection.keys()) {
1280
- const els = this.queryAll(`.row[data-row="${ref}"]`)
1281
- els.forEach(el => {
1282
- el.removeClass("selected");
1283
- })
1284
- }
1449
+ this.queryAll(".row.selected").forEach(el => {
1450
+ el.removeClass("selected");
1451
+ });
1285
1452
 
1286
1453
  this._selection.clear();
1287
1454
  if( notify ) {
@@ -1293,16 +1460,18 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1293
1460
  *
1294
1461
  */
1295
1462
 
1296
- private _addSelection(rowid: number) {
1297
- this._selection.add(rowid)
1463
+ private _addSelection(rowid: number, notify = true) {
1464
+ this._selection.add( this._dataview.getIdByIndex( rowid ) );
1298
1465
 
1299
1466
  const els = this.queryAll(`.row[data-row="${rowid}"]`)
1300
1467
  els.forEach(el => {
1301
1468
  el.addClass("selected");
1302
1469
  });
1303
1470
 
1304
- const rec = this._dataview.getByIndex( rowid );
1305
- this.fire("selectionChange", { selection: [rec], empty: false } );
1471
+ // all that is selected, not this row only
1472
+ if( notify ) {
1473
+ this.fire("selectionChange", { selection: this.getSelection( ), empty: false } );
1474
+ }
1306
1475
  }
1307
1476
 
1308
1477
  /**
@@ -1310,25 +1479,31 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1310
1479
  */
1311
1480
 
1312
1481
  getSelection( ) {
1313
- if( this._selection.size==0 ) {
1314
- return null;
1482
+ // a record may be gone: out of the document, the grid does not follow its store
1483
+ const recs: DataRecord[] = [];
1484
+ for (const id of this._selection) {
1485
+ const rec = this._dataview.getById( id );
1486
+ if (rec) {
1487
+ recs.push( rec );
1488
+ }
1315
1489
  }
1316
1490
 
1317
- const ids = [...this._selection.values() ];
1318
- return ids.map( id => this._dataview.getByIndex(id) );
1491
+ return recs.length ? recs : null;
1319
1492
  }
1320
1493
 
1321
1494
  /**
1322
- *
1495
+ *
1323
1496
  */
1324
1497
 
1325
1498
  getFirstSel( ) {
1326
- if( this._selection.size==0 ) {
1327
- return null;
1499
+ for (const id of this._selection) {
1500
+ const rec = this._dataview.getById( id );
1501
+ if (rec) {
1502
+ return rec;
1503
+ }
1328
1504
  }
1329
1505
 
1330
- const id = this._selection.values().next().value;
1331
- return this._dataview.getByIndex( id );
1506
+ return null;
1332
1507
  }
1333
1508
 
1334
1509
  /**