@qooxdoo/framework 7.0.0-beta.8 → 7.1.0

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 (33) hide show
  1. package/CHANGELOG.md +8 -3
  2. package/Manifest.json +2 -2
  3. package/README.md +2 -4
  4. package/lib/compiler/compile-info.json +72 -70
  5. package/lib/compiler/index.js +1907 -1639
  6. package/npm-shrinkwrap.json +950 -42
  7. package/package.json +5 -3
  8. package/source/class/qx/Class.js +0 -28
  9. package/source/class/qx/Mixin.js +6 -0
  10. package/source/class/qx/data/Array.js +1 -1
  11. package/source/class/qx/test/Mixin.js +206 -1
  12. package/source/class/qx/tool/cli/api/AbstractApi.js +4 -1
  13. package/source/class/qx/tool/cli/commands/Compile.js +1 -0
  14. package/source/class/qx/tool/cli/commands/Lint.js +19 -8
  15. package/source/class/qx/tool/compiler/ClassFile.js +73 -5
  16. package/source/class/qx/tool/compiler/TargetError.js +27 -0
  17. package/source/class/qx/tool/compiler/targets/Target.js +6 -0
  18. package/source/class/qx/tool/compiler/targets/meta/AbstractJavascriptMeta.js +1 -1
  19. package/source/class/qx/tool/compiler/targets/meta/Browserify.js +143 -0
  20. package/source/class/qx/tool/compiler/targets/meta/Uglify.js +4 -25
  21. package/source/class/qx/ui/form/AbstractSelectBox.js +4 -1
  22. package/source/class/qx/ui/form/DateField.js +4 -1
  23. package/source/class/qx/ui/progressive/renderer/table/Row.js +2 -1
  24. package/source/class/qx/ui/progressive/renderer/table/cell/Boolean.js +24 -26
  25. package/source/class/qx/ui/progressive/renderer/table/cell/Icon.js +9 -7
  26. package/source/class/qx/ui/progressive/renderer/table/cell/Image.js +16 -13
  27. package/source/class/qx/ui/table/Table.js +0 -1
  28. package/source/class/qx/ui/table/model/Abstract.js +31 -1
  29. package/source/class/qx/ui/table/model/Remote.js +1 -0
  30. package/source/class/qx/ui/table/model/Simple.js +14 -3
  31. package/source/class/qx/ui/table/pane/Pane.js +3 -1
  32. package/source/class/qx/ui/treevirtual/MNode.js +60 -5
  33. package/source/class/qx/ui/treevirtual/SimpleTreeDataModel.js +11 -3
@@ -0,0 +1,143 @@
1
+ /* ************************************************************************
2
+ *
3
+ * qooxdoo-compiler - node.js based replacement for the Qooxdoo python
4
+ * toolchain
5
+ *
6
+ * https://github.com/qooxdoo/qooxdoo-compiler
7
+ *
8
+ * Copyright:
9
+ * 2022 Derrell Lipman
10
+ *
11
+ * License:
12
+ * MIT: https://opensource.org/licenses/MIT
13
+ *
14
+ * This software is provided under the same licensing terms as Qooxdoo,
15
+ * please see the LICENSE file in the Qooxdoo project's top-level directory
16
+ * for details.
17
+ *
18
+ * Authors:
19
+ * * Derrell Lipman (@derrell)
20
+ *
21
+ * ************************************************************************/
22
+
23
+ const fs = qx.tool.utils.Promisify.fs;
24
+ const path = require("upath");
25
+
26
+ /**
27
+ *
28
+ */
29
+ qx.Class.define("qx.tool.compiler.targets.meta.Browserify", {
30
+ extend: qx.tool.compiler.targets.meta.AbstractJavascriptMeta,
31
+
32
+ construct(appMeta) {
33
+ super(appMeta, `${appMeta.getApplicationRoot()}commonjs-browserify.js`);
34
+ this.__appMeta = appMeta;
35
+ this.setNeedsWriteToDisk(true);
36
+ },
37
+
38
+ members: {
39
+ __appMeta : null,
40
+
41
+ /**
42
+ * @Override
43
+ */
44
+ async writeSourceCodeToStream(ws) {
45
+ let hasCommonjsModules = false;
46
+ let commonjsModules = new Set();
47
+ let references = {};
48
+ const db = this.__appMeta.getAnalyser().getDatabase();
49
+
50
+ // Get a Set of unique `require`d CommonJS module names from all classes
51
+ for (let className in db.classInfo) {
52
+ let classInfo = db.classInfo[className];
53
+ if (classInfo.commonjsModules) {
54
+ Object.keys(classInfo.commonjsModules).forEach(
55
+ moduleName =>
56
+ {
57
+ // Add this module name to the set of module names
58
+ commonjsModules.add(moduleName);
59
+
60
+ // Add the list of references from which this module was require()d
61
+ if (! references[moduleName]) {
62
+ references[moduleName] = new Set();
63
+ }
64
+ references[moduleName].add([ ...classInfo.commonjsModules[moduleName] ]);
65
+
66
+ // There is at least one module
67
+ hasCommonjsModules = true;
68
+ });
69
+ }
70
+ }
71
+
72
+ // If there are any CommonJS modules required, browserify them
73
+ if (hasCommonjsModules) {
74
+ await this.__browserify(
75
+ commonjsModules,
76
+ references,
77
+ ws
78
+ );
79
+ }
80
+
81
+ await new Promise(resolve => {
82
+ ws.write("\n", resolve);
83
+ });
84
+ },
85
+
86
+ async __browserify(commonjsModules, references, ws) {
87
+ let b;
88
+ const browserify = require("browserify");
89
+ const builtins = require("browserify/lib/builtins.js");
90
+
91
+ // For some reason, `process` is not require()able, but `_process` is.
92
+ // Make them equivalent.
93
+ builtins.process = builtins._process;
94
+
95
+ // Convert the Set of CommonJS module names to an array
96
+ commonjsModules = [...commonjsModules];
97
+
98
+ return new Promise(resolve =>
99
+ {
100
+ b = browserify(
101
+ [],
102
+ {
103
+ builtins : builtins,
104
+ ignoreMissing : true,
105
+ insertGlobals : true,
106
+ detectGlobals : true
107
+ });
108
+ b._mdeps.on("missing", (id, parent) => {
109
+ let message = [];
110
+
111
+ message.push(`ERROR: could not locate require()d module: "${id}"`);
112
+ message.push(" required from:");
113
+ [ ...references[id] ].forEach(refs => {
114
+ refs.forEach(ref =>
115
+ {
116
+ message.push(` ${ref}`);
117
+ });
118
+ });
119
+
120
+ qx.tool.compiler.Console.error(message.join("\n"));
121
+ });
122
+ b.require(commonjsModules);
123
+ b.bundle((e, output) => {
124
+ if (e) {
125
+ // We've already handled the case of missing module. This is something else.
126
+ qx.tool.compiler.Console.error(`Failed: ${e}`);
127
+ throw(e);
128
+ }
129
+
130
+ ws.write(output);
131
+ resolve(null);
132
+ });
133
+ });
134
+ },
135
+
136
+ /**
137
+ * @Override
138
+ */
139
+ async getSourceMap() {
140
+ return null;
141
+ }
142
+ }
143
+ });
@@ -96,22 +96,8 @@ qx.Class.define("qx.tool.compiler.targets.meta.Uglify", {
96
96
  });
97
97
  return ss.toString();
98
98
  })();
99
- await qx.tool.utils.files.Utils.safeUnlink(outJsFilename + ".unminified");
100
- await qx.tool.utils.files.Utils.safeRename(
101
- outJsFilename,
102
- outJsFilename + ".unminified"
103
- );
104
99
 
105
100
  let inSourceMap = await this.__jsMeta.getSourceMap();
106
- await qx.tool.utils.files.Utils.safeUnlink(
107
- outJsFilename + ".unminified.map"
108
- );
109
-
110
- await qx.tool.utils.files.Utils.safeRename(
111
- outJsFilename + ".map",
112
- outJsFilename + ".unminified.map"
113
- );
114
-
115
101
  this.fireDataEvent("minifyingApplication", {
116
102
  application: application,
117
103
  filename: baseJsFilename
@@ -138,18 +124,11 @@ qx.Class.define("qx.tool.compiler.targets.meta.Uglify", {
138
124
  throw new Error("UglifyJS failed to minimise: " + (err.message || err));
139
125
  }
140
126
  await fs.writeFileAsync(outJsFilename, result.code, { encoding: "utf8" });
141
- if (!this._appMeta.getTarget().isSaveUnminified()) {
142
- await qx.tool.utils.files.Utils.safeUnlink(
143
- outJsFilename + ".unminified"
144
- );
145
-
146
- await qx.tool.utils.files.Utils.safeUnlink(
147
- outJsFilename + ".unminified.map"
148
- );
127
+ await fs.writeFileAsync(outJsFilename + ".map", result.map, { encoding: "utf8" });
128
+ if (this._appMeta.getTarget().isSaveUnminified()) {
129
+ await fs.writeFileAsync(outJsFilename + ".unminified", inSourceCode, { encoding: "utf8" });
130
+ await fs.writeFileAsync(outJsFilename + ".unminified.map", JSON.stringify(inSourceMap, null, 2), { encoding: "utf8" });
149
131
  }
150
- await fs.writeFileAsync(outJsFilename + ".map", result.map, {
151
- encoding: "utf8"
152
- });
153
132
 
154
133
  this.fireDataEvent("minifiedApplication", {
155
134
  application: application,
@@ -210,7 +210,10 @@ qx.Class.define("qx.ui.form.AbstractSelectBox", {
210
210
  * Hides the list popup.
211
211
  */
212
212
  close() {
213
- this.getChildControl("popup").hide();
213
+ var popup = this.getChildControl("popup", true);
214
+ if (popup && popup.isVisible()) {
215
+ popup.hide();
216
+ }
214
217
  },
215
218
 
216
219
  /**
@@ -312,7 +312,10 @@ qx.Class.define("qx.ui.form.DateField", {
312
312
  * Hides the date chooser popup.
313
313
  */
314
314
  close() {
315
- this.getChildControl("popup").hide();
315
+ var popup = this.getChildControl("popup", true);
316
+ if (popup && popup.isVisible()) {
317
+ popup.hide();
318
+ }
316
319
  },
317
320
 
318
321
  /**
@@ -284,7 +284,8 @@ qx.Class.define("qx.ui.progressive.renderer.table.Row", {
284
284
  element: element,
285
285
  dataIndex: i,
286
286
  cellData: data[i],
287
- height: height
287
+ height: height,
288
+ rowRenderer: this // useful, e.g., for getting default row height
288
289
  };
289
290
 
290
291
  // Render this cell
@@ -28,13 +28,13 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Boolean", {
28
28
  construct() {
29
29
  super();
30
30
 
31
- this.__resolveImages();
31
+ this._resolveImages();
32
32
 
33
33
  // dynamic theme switch
34
34
  if (qx.core.Environment.get("qx.dyntheme")) {
35
35
  qx.theme.manager.Meta.getInstance().addListener(
36
36
  "changeTheme",
37
- this.__resolveImages,
37
+ this._resolveImages,
38
38
  this
39
39
  );
40
40
  }
@@ -53,19 +53,13 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Boolean", {
53
53
  },
54
54
 
55
55
  members: {
56
- __iconUrlTrue: null,
57
- __iconUrlFalse: null,
58
- __numericAllowed: null,
59
- __conditions: null,
60
- __defaultTextAlign: null,
61
- __defaultColor: null,
62
- __defaultFontStyle: null,
63
- __defaultFontWeight: null,
56
+ _iconUrlTrue: null,
57
+ _iconUrlFalse: null,
64
58
 
65
59
  /**
66
60
  * Resolve the boolean images using the alias and resource manager.
67
61
  */
68
- __resolveImages() {
62
+ _resolveImages() {
69
63
  var aliasManager = qx.util.AliasManager.getInstance();
70
64
  var resourceManager = qx.util.ResourceManager.getInstance();
71
65
  var boolTrueImg = aliasManager.resolve(
@@ -76,25 +70,29 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Boolean", {
76
70
  "decoration/table/boolean-false.png"
77
71
  );
78
72
 
79
- this.__iconUrlTrue = resourceManager.toUri(boolTrueImg);
80
- this.__iconUrlFalse = resourceManager.toUri(boolFalseImg);
73
+ this._iconUrlTrue = resourceManager.toUri(boolTrueImg);
74
+ this._iconUrlFalse = resourceManager.toUri(boolFalseImg);
81
75
  },
82
76
 
83
- // overridden
84
- _identifyImage(cellInfo) {
85
- var imageData = {
77
+ _getDefaultImageData(cellInfo) {
78
+ return {
86
79
  imageWidth: 11,
87
80
  imageHeight: 11
88
81
  };
82
+ },
83
+
84
+ // overridden
85
+ _identifyImage(cellInfo) {
86
+ var imageData = this._getDefaultImageData(cellInfo);
89
87
 
90
88
  switch (cellInfo.cellData) {
91
89
  case true:
92
- imageData.url = this.__iconUrlTrue;
90
+ imageData.url = this._iconUrlTrue;
93
91
  imageData.extras = "celldata='1' ";
94
92
  break;
95
93
 
96
94
  case false:
97
- imageData.url = this.__iconUrlFalse;
95
+ imageData.url = this._iconUrlFalse;
98
96
  imageData.extras = "celldata='0' ";
99
97
  break;
100
98
 
@@ -115,7 +113,7 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Boolean", {
115
113
 
116
114
  if (
117
115
  qx.core.Environment.get("css.alphaimageloaderneeded") &&
118
- /\.png$/i.test(this.__iconUrlTrue)
116
+ /\.png$/i.test(this._iconUrlTrue)
119
117
  ) {
120
118
  imageData.extras +=
121
119
  " this.src='" +
@@ -124,18 +122,18 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Boolean", {
124
122
  " var loader = 'DXImageTransform.Microsoft.AlphaImageLoader'; " +
125
123
  " var filters = this.filters.item(loader); " +
126
124
  " filters.src='" +
127
- this.__iconUrlTrue +
125
+ this._iconUrlTrue +
128
126
  "'; " +
129
127
  " filters.sizingMethod = 'scale'; ";
130
128
  } else {
131
- imageData.extras += " this.src='" + this.__iconUrlTrue + "'; ";
129
+ imageData.extras += " this.src='" + this._iconUrlTrue + "'; ";
132
130
  }
133
131
 
134
132
  imageData.extras += " node.nodeValue='1'; " + "} " + "else " + "{";
135
133
 
136
134
  if (
137
135
  qx.core.Environment.get("css.alphaimageloaderneeded") &&
138
- /\.png$/i.test(this.__iconUrlFalse)
136
+ /\.png$/i.test(this._iconUrlFalse)
139
137
  ) {
140
138
  imageData.extras +=
141
139
  " this.src='" +
@@ -144,11 +142,11 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Boolean", {
144
142
  " var loader = 'DXImageTransform.Microsoft.AlphaImageLoader'; " +
145
143
  " var filters = this.filters.item(loader); " +
146
144
  " filters.src='" +
147
- this.__iconUrlFalse +
145
+ this._iconUrlFalse +
148
146
  "'; " +
149
147
  " filters.sizingMethod = 'scale'; ";
150
148
  } else {
151
- imageData.extras += " this.src='" + this.__iconUrlFalse + "'; ";
149
+ imageData.extras += " this.src='" + this._iconUrlFalse + "'; ";
152
150
  }
153
151
 
154
152
  imageData.extras += " node.nodeValue='0'; " + "}";
@@ -176,13 +174,13 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Boolean", {
176
174
  },
177
175
 
178
176
  destruct() {
179
- this.__iconUrlTrue = this.__iconUrlFalse = null;
177
+ this._iconUrlTrue = this._iconUrlFalse = null;
180
178
 
181
179
  // remove dynamic theme listener
182
180
  if (qx.core.Environment.get("qx.dyntheme")) {
183
181
  qx.theme.manager.Meta.getInstance().removeListener(
184
182
  "changeTheme",
185
- this.__resolveImages,
183
+ this._resolveImages,
186
184
  this
187
185
  );
188
186
  }
@@ -21,6 +21,8 @@
21
21
 
22
22
  /**
23
23
  * Abstract Icon cell renderer.
24
+ *
25
+ * @asset(qx/static/blank.gif)
24
26
  */
25
27
  qx.Class.define("qx.ui.progressive.renderer.table.cell.Icon", {
26
28
  type: "abstract",
@@ -34,14 +36,14 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Icon", {
34
36
  var resourceManager = qx.util.ResourceManager.getInstance();
35
37
  var blankImg = aliasManager.resolve("qx/static/blank.gif");
36
38
 
37
- this.__imageBlank = resourceManager.toUri(blankImg);
39
+ this._imageBlank = resourceManager.toUri(blankImg);
38
40
  },
39
41
 
40
42
  members: {
41
43
  /**
42
44
  * A blank image for use as a spacer in place of another image
43
45
  */
44
- __imageBlank: null,
46
+ _imageBlank: null,
45
47
 
46
48
  /**
47
49
  * Retrieve the URI for a blank image
@@ -50,7 +52,7 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Icon", {
50
52
  * The URI of the blank image.
51
53
  */
52
54
  getBlankImage() {
53
- return this.__imageBlank;
55
+ return this._imageBlank;
54
56
  },
55
57
 
56
58
  /**
@@ -118,7 +120,7 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Icon", {
118
120
  // overridden
119
121
  _getContentHtml(cellInfo) {
120
122
  var html = [];
121
- var imageData = this.__getImageData(cellInfo);
123
+ var imageData = this._getImageData(cellInfo);
122
124
 
123
125
  // Start the image tag
124
126
  html.push("<img ");
@@ -130,7 +132,7 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Icon", {
130
132
  ) {
131
133
  html.push(
132
134
  'src="',
133
- this.__imageBlank,
135
+ this._imageBlank,
134
136
  '" style="filter:',
135
137
  "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='",
136
138
  imageData.url,
@@ -192,7 +194,7 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Icon", {
192
194
  * @return {Map}
193
195
  * See {@link #_identifyImage}
194
196
  */
195
- __getImageData(cellInfo) {
197
+ _getImageData(cellInfo) {
196
198
  // Query the subclass about image and tooltip
197
199
  var imageData = this._identifyImage(cellInfo);
198
200
 
@@ -206,7 +208,7 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Icon", {
206
208
 
207
209
  // If subclass gave null as url, replace with url to empty image
208
210
  if (imageData.url == null) {
209
- imageData.url = this.__imageBlank;
211
+ imageData.url = this._imageBlank;
210
212
  }
211
213
 
212
214
  return imageData;
@@ -34,31 +34,34 @@ qx.Class.define("qx.ui.progressive.renderer.table.cell.Image", {
34
34
  construct(width, height) {
35
35
  super();
36
36
 
37
- if (width === undefined) {
38
- this.__imageWidth = width;
37
+ if (width !== undefined) {
38
+ this._imageWidth = width;
39
39
  } else {
40
- this.__imageWidth = 16;
40
+ this._imageWidth = 16;
41
41
  }
42
42
 
43
- if (height === undefined) {
44
- this.__imageHeight = height;
43
+ if (height !== undefined) {
44
+ this._imageHeight = height;
45
45
  } else {
46
- this.__imageHeight = 16;
46
+ this._imageHeight = 16;
47
47
  }
48
48
  },
49
49
 
50
50
  members: {
51
- __imageWidth: null,
52
- __imageHeight: null,
51
+ _imageWidth: null,
52
+ _imageHeight: null,
53
53
 
54
- // overridden
55
- _identifyImage(cellInfo) {
56
- var imageData = {
57
- imageWidth: this.__imageWidth,
58
- imageHeight: this.__imageHeight
54
+ _getDefaultImageData(cellInfo) {
55
+ return {
56
+ imageWidth: this._imageWidth,
57
+ imageHeight: this._imageHeight
59
58
  };
59
+ },
60
60
 
61
+ // overridden
62
+ _identifyImage(cellInfo) {
61
63
  var height;
64
+ var imageData = this._getDefaultImageData(cellInfo);
62
65
 
63
66
  // String data is the unresolved url for the image.
64
67
  // Object data is a map containing the url, tooltip, and a height
@@ -2193,7 +2193,6 @@ qx.Class.define("qx.ui.table.Table", {
2193
2193
  );
2194
2194
  }
2195
2195
 
2196
- this._cleanUpMetaColumns(0);
2197
2196
  this.getTableColumnModel().dispose();
2198
2197
  this._disposeObjects(
2199
2198
  "__selectionManager",
@@ -66,11 +66,22 @@ qx.Class.define("qx.ui.table.model.Abstract", {
66
66
  this.__columnIndexMap = {};
67
67
  },
68
68
 
69
+ statics: {
70
+ /**
71
+ * Member to control if a table should throw an error when you try to change the
72
+ * data model data whilst there is an incomplete edit. It could possibly break
73
+ * current implementations so only introduce the change from QX v8.
74
+ * Ref: https://github.com/qooxdoo/qooxdoo/pull/10377#discussion_r818697343
75
+ */
76
+ THROW_ON_MODEL_CHANGE_DURING_EDIT: parseInt(qx.core.Environment.get("qx.version"), 10) >= 8
77
+ },
78
+
69
79
  members: {
70
80
  __columnIdArr: null,
71
81
  __columnNameArr: null,
72
82
  __columnIndexMap: null,
73
83
  __internalChange: null,
84
+ __table: null,
74
85
 
75
86
  /**
76
87
  * Initialize the table model <--> table interaction. The table model is
@@ -84,7 +95,17 @@ qx.Class.define("qx.ui.table.model.Abstract", {
84
95
  * The table to which this model is attached
85
96
  */
86
97
  init(table) {
87
- // default implementation has nothing to do
98
+ // store a reference back to the table
99
+ this.__table = table;
100
+ },
101
+
102
+ /**
103
+ *
104
+ *
105
+ * @returns table {qx.ui.table.Table}
106
+ */
107
+ getTable() {
108
+ return this.__table;
88
109
  },
89
110
 
90
111
  /**
@@ -292,6 +313,15 @@ qx.Class.define("qx.ui.table.model.Abstract", {
292
313
  }
293
314
 
294
315
  this.setColumnNamesByIndex(columnNameArr);
316
+ },
317
+
318
+ _checkEditing() {
319
+ if (!qx.ui.table.model.Abstract.THROW_ON_MODEL_CHANGE_DURING_EDIT) {
320
+ return;
321
+ }
322
+ if (this.getTable() && this.getTable().isEditing()) {
323
+ throw new Error("A cell is currently being edited. Commit or cancel the edit before setting the table data");
324
+ }
295
325
  }
296
326
  },
297
327
 
@@ -548,6 +548,7 @@ qx.Class.define("qx.ui.table.model.Remote", {
548
548
  * @param rowIndex {Integer} the index of the row to remove.
549
549
  */
550
550
  removeRow(rowIndex) {
551
+ this._checkEditing();
551
552
  if (this.getClearCacheOnRemove()) {
552
553
  this.clearCache();
553
554
 
@@ -531,6 +531,7 @@ qx.Class.define("qx.ui.table.model.Simple", {
531
531
  * @param clearSorting {Boolean ? true} Whether to clear the sort state.
532
532
  */
533
533
  setData(rowArr, clearSorting) {
534
+ this._checkEditing();
534
535
  this._rowArr = rowArr;
535
536
 
536
537
  // Inform the listeners
@@ -650,6 +651,7 @@ qx.Class.define("qx.ui.table.model.Simple", {
650
651
  * @param clearSorting {Boolean ? true} Whether to clear the sort state.
651
652
  */
652
653
  setRows(rowArr, startIndex, clearSorting) {
654
+ this._checkEditing();
653
655
  if (startIndex == null) {
654
656
  startIndex = 0;
655
657
  }
@@ -708,9 +710,14 @@ qx.Class.define("qx.ui.table.model.Simple", {
708
710
  * @param clearSorting {Boolean ? true} Whether to clear the sort state.
709
711
  */
710
712
  removeRows(startIndex, howMany, clearSorting) {
711
- this._rowArr.splice(startIndex, howMany);
712
-
713
- // Inform the listeners
713
+ this._checkEditing();
714
+ // In the case of `removeRows`, specifically, we must create the
715
+ // listeners' event data before actually removing the rows from
716
+ // the row data, so that the `lastRow` calculation is correct.
717
+ // If we do the delete operation first, as is done in other
718
+ // methods, the final rows of the table can escape being
719
+ // updated, thus leaving hanging old data on the rendered table.
720
+ // This reordering (deleting after creating event data) fixes #10365.
714
721
  var data = {
715
722
  firstRow: startIndex,
716
723
  lastRow: this._rowArr.length - 1,
@@ -720,7 +727,11 @@ qx.Class.define("qx.ui.table.model.Simple", {
720
727
  removeCount: howMany
721
728
  };
722
729
 
730
+ this._rowArr.splice(startIndex, howMany);
731
+
732
+ // Inform the listeners
723
733
  this.fireDataEvent("dataChanged", data);
734
+
724
735
  if (clearSorting !== false) {
725
736
  this.clearSorting();
726
737
  }
@@ -632,7 +632,9 @@ qx.Class.define("qx.ui.table.pane.Pane", {
632
632
  var offset = row - firstRow;
633
633
  var rowElem = tableChildNodes[offset];
634
634
 
635
- if (row > modelRowCount || typeof rowElem == "undefined") {
635
+ // `row` can be too big if rows were deleted. In that case, we
636
+ // can't update the current single row
637
+ if (row >= modelRowCount || typeof rowElem == "undefined") {
636
638
  this._updateAllRows();
637
639
  return;
638
640
  }