@qooxdoo/framework 7.9.1 → 7.9.3

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 (34) hide show
  1. package/Manifest.json +1 -1
  2. package/lib/compiler/compile-info.json +54 -54
  3. package/lib/compiler/index.js +249 -216
  4. package/lib/resource/qx/tool/cli/templates/skeleton/mobile/source/theme/custom/css/custom.css.map +1 -1
  5. package/package.json +2 -2
  6. package/source/class/qx/locale/Manager.js +13 -0
  7. package/source/class/qx/test/compiler/ClassFile.js +13 -0
  8. package/source/class/qx/test/theme/manager/Color.js +23 -15
  9. package/source/class/qx/test/theme/manager/Decoration.js +23 -15
  10. package/source/class/qx/test/theme/manager/Font.js +23 -15
  11. package/source/class/qx/test/theme/manager/Icon.js +23 -15
  12. package/source/class/qx/test/theme/manager/Meta.js +26 -15
  13. package/source/class/qx/test/ui/basic/Label.js +106 -0
  14. package/source/class/qx/test/ui/core/Blocker.js +121 -0
  15. package/source/class/qx/test/ui/tree/virtual/Tree.js +36 -0
  16. package/source/class/qx/test/util/DateFormat.js +1 -1
  17. package/source/class/qx/theme/classic/Appearance.js +21 -0
  18. package/source/class/qx/theme/indigo/ColorDark.js +2 -0
  19. package/source/class/qx/theme/modern/Appearance.js +21 -0
  20. package/source/class/qx/theme/simple/Appearance.js +27 -5
  21. package/source/class/qx/theme/tangible/Appearance.js +2 -0
  22. package/source/class/qx/tool/compiler/ClassFile.js +18 -6
  23. package/source/class/qx/tool/compiler/resources/Asset.js +1 -1
  24. package/source/class/qx/tool/compiler/targets/meta/PackageJavascript.js +6 -2
  25. package/source/class/qx/ui/core/Blocker.js +16 -3
  26. package/source/class/qx/ui/core/MExecutable.js +14 -12
  27. package/source/class/qx/ui/form/validation/Manager.js +1 -1
  28. package/source/class/qx/ui/mobile/dialog/Popup.js +13 -1
  29. package/source/class/qx/ui/table/Table.js +5 -4
  30. package/source/class/qx/ui/table/pane/Scroller.js +6 -8
  31. package/source/class/qx/ui/tree/VirtualTree.js +4 -1
  32. package/source/class/qx/util/format/DateFormat.js +3 -2
  33. package/source/resource/qx/decoration/Modern/table/boolean-false.png +0 -0
  34. package/source/resource/qx/decoration/Modern/table/boolean-true.png +0 -0
@@ -235,6 +235,127 @@ qx.Class.define("qx.test.ui.core.Blocker", {
235
235
 
236
236
  txt.destroy();
237
237
  this.flush();
238
+ },
239
+
240
+ testBlockContent() {
241
+ // Create a container widget with specific position
242
+ var container = new qx.ui.container.Composite(new qx.ui.layout.Canvas());
243
+ this.getRoot().add(container, { left: 100, top: 50 });
244
+ container.setWidth(200);
245
+ container.setHeight(150);
246
+ this.flush();
247
+
248
+ // Create blocker for the container
249
+ var blocker = new qx.ui.core.Blocker(container);
250
+ var blockerElement = blocker.getBlockerElement();
251
+
252
+ // Block content
253
+ blocker.blockContent(10);
254
+ this.flush();
255
+
256
+ this.assertTrue(blocker.isBlocked(), "blocker should be blocked");
257
+ this.assertTrue(blockerElement.isIncluded(), "blocker element should be included");
258
+
259
+ // Verify blocker dimensions match container
260
+ var styles = blockerElement.getAllStyles();
261
+ this.assertEquals("200px", styles.width, "blocker width should match container width");
262
+ this.assertEquals("150px", styles.height, "blocker height should match container height");
263
+
264
+ // Verify blocker position is 0,0 (relative to container, not layout parent)
265
+ // This is the fix for issue #10411
266
+ this.assertEquals("0px", styles.left, "blocker left should be 0 when blocking content");
267
+ this.assertEquals("0px", styles.top, "blocker top should be 0 when blocking content");
268
+
269
+ // Cleanup
270
+ blocker.unblock();
271
+ blocker.dispose();
272
+ container.destroy();
273
+ this.flush();
274
+ },
275
+
276
+ testBlockContentPositioning() {
277
+ // Create a container at a non-zero position
278
+ var container = new qx.ui.container.Composite(new qx.ui.layout.Canvas());
279
+ this.getRoot().add(container, { left: 150, top: 100 });
280
+ container.setWidth(300);
281
+ container.setHeight(200);
282
+ this.flush();
283
+
284
+ var blocker = new qx.ui.core.Blocker(container);
285
+ var blockerElement = blocker.getBlockerElement();
286
+
287
+ // Block the content
288
+ blocker.blockContent(5);
289
+ this.flush();
290
+
291
+ var styles = blockerElement.getAllStyles();
292
+
293
+ // The blocker should be positioned at 0,0 relative to the container
294
+ // NOT at the container's position (150, 100)
295
+ this.assertEquals("0px", styles.left, "blocker should be at left: 0");
296
+ this.assertEquals("0px", styles.top, "blocker should be at top: 0");
297
+ this.assertEquals("300px", styles.width, "blocker width should match container");
298
+ this.assertEquals("200px", styles.height, "blocker height should match container");
299
+
300
+ // Cleanup
301
+ blocker.unblock();
302
+ blocker.dispose();
303
+ container.destroy();
304
+ this.flush();
305
+ },
306
+
307
+ testNormalBlockVsBlockContent() {
308
+ // Create two containers to compare normal block vs blockContent
309
+ var container1 = new qx.ui.container.Composite(new qx.ui.layout.Canvas());
310
+ var container2 = new qx.ui.container.Composite(new qx.ui.layout.Canvas());
311
+
312
+ this.getRoot().add(container1, { left: 50, top: 50 });
313
+ this.getRoot().add(container2, { left: 300, top: 50 });
314
+
315
+ container1.setWidth(100);
316
+ container1.setHeight(100);
317
+ container2.setWidth(100);
318
+ container2.setHeight(100);
319
+
320
+ this.flush();
321
+
322
+ var blocker1 = new qx.ui.core.Blocker(container1);
323
+ var blocker2 = new qx.ui.core.Blocker(container2);
324
+
325
+ // Normal block - blocker is added to layout parent
326
+ blocker1.block();
327
+ this.flush();
328
+
329
+ var styles1 = blocker1.getBlockerElement().getAllStyles();
330
+
331
+ // BlockContent - blocker is added to the widget itself
332
+ blocker2.blockContent(5);
333
+ this.flush();
334
+
335
+ var styles2 = blocker2.getBlockerElement().getAllStyles();
336
+
337
+ // Normal block: blocker positioned at container's position in layout parent
338
+ this.assertEquals("50px", styles1.left, "normal block uses container's left position");
339
+ this.assertEquals("50px", styles1.top, "normal block uses container's top position");
340
+
341
+ // BlockContent: blocker positioned at 0,0 relative to container
342
+ this.assertEquals("0px", styles2.left, "blockContent uses 0 for left position");
343
+ this.assertEquals("0px", styles2.top, "blockContent uses 0 for top position");
344
+
345
+ // Both should have same dimensions as their containers
346
+ this.assertEquals("100px", styles1.width);
347
+ this.assertEquals("100px", styles1.height);
348
+ this.assertEquals("100px", styles2.width);
349
+ this.assertEquals("100px", styles2.height);
350
+
351
+ // Cleanup
352
+ blocker1.unblock();
353
+ blocker2.unblock();
354
+ blocker1.dispose();
355
+ blocker2.dispose();
356
+ container1.destroy();
357
+ container2.destroy();
358
+ this.flush();
238
359
  }
239
360
  }
240
361
  });
@@ -260,6 +260,42 @@ qx.Class.define("qx.test.ui.tree.virtual.Tree", {
260
260
  this.__testBuildLookupTable(expected);
261
261
  },
262
262
 
263
+ /**
264
+ * Test for issue #9390: VirtualTree doesn't render if hideRoot is true
265
+ * when adding nodes dynamically
266
+ */
267
+ testDynamicNodesWithHiddenRoot() {
268
+ // Create initial model
269
+ var root = this.createModelAndSetModel(1);
270
+
271
+ // Set hideRoot before adding nodes
272
+ this.tree.setHideRoot(true);
273
+
274
+ // Flush to ensure initial rendering
275
+ this.flush();
276
+
277
+ // Get initial visible items (should be root's children)
278
+ var expectedBefore = this.getVisibleItemsFrom(root, [root]);
279
+ this.__testBuildLookupTable(expectedBefore);
280
+
281
+ // Dynamically add a new branch to the root
282
+ var newBranch = new qx.test.ui.tree.virtual.Node("Dynamic Branch");
283
+ this._createNodes(newBranch, 1);
284
+ root.getChildren().push(newBranch);
285
+
286
+ // The tree should automatically update without calling refresh()
287
+ // Expected items should now include the new branch
288
+ var expectedAfter = this.getVisibleItemsFrom(root, [root]);
289
+ this.__testBuildLookupTable(expectedAfter);
290
+
291
+ // Verify the new branch is in the lookup table
292
+ var lookupTable = this.tree.getLookupTable();
293
+ this.assertTrue(
294
+ lookupTable.contains(newBranch),
295
+ "New dynamically added branch should be visible in tree with hidden root"
296
+ );
297
+ },
298
+
263
299
  testBuildLookupWithoutLeafs() {
264
300
  var root = this.createModelAndSetModel(2);
265
301
 
@@ -969,7 +969,7 @@ qx.Class.define("qx.test.util.DateFormat", {
969
969
  var timezoneOffset = date.getTimezoneOffset();
970
970
  var timezoneSign = timezoneOffset > 0 ? 1 : -1;
971
971
  var timezoneHours = Math.floor(Math.abs(timezoneOffset) / 60);
972
- var timezoneMinutes = Math.abs(timezoneOffset) % 60;
972
+ var timezoneMinutes = Math.trunc(Math.abs(timezoneOffset)) % 60;
973
973
 
974
974
  var localTimeZone =
975
975
  "GMT" +
@@ -1465,6 +1465,27 @@ qx.Theme.define("qx.theme.classic.Appearance", {
1465
1465
  }
1466
1466
  },
1467
1467
 
1468
+ "selectbox-arrow-button": "widget",
1469
+
1470
+ /*
1471
+ ---------------------------------------------------------------------------
1472
+ CHECKED SELECT BOX
1473
+ ---------------------------------------------------------------------------
1474
+ */
1475
+
1476
+ "checked-selectbox": "selectbox",
1477
+
1478
+ "checked-selectbox/allNone": {
1479
+ include: "button"
1480
+ },
1481
+
1482
+ "checked-selectbox/tag": "tag",
1483
+
1484
+ tag: {
1485
+ alias: "button",
1486
+ include: "button"
1487
+ },
1488
+
1468
1489
  /*
1469
1490
  ---------------------------------------------------------------------------
1470
1491
  DATE CHOOSER
@@ -28,6 +28,8 @@ qx.Theme.define("qx.theme.indigo.ColorDark", {
28
28
  "highlight-shade": "#dddddd",
29
29
 
30
30
  // backgrounds
31
+ "datechooser-background-selected-dark": "#333",
32
+ "datechooser-background": "#666",
31
33
  "background-selected": "#666666",
32
34
  "background-selected-disabled": "#777777",
33
35
  "background-selected-dark": "#333333",
@@ -1728,6 +1728,27 @@ qx.Theme.define("qx.theme.modern.Appearance", {
1728
1728
  }
1729
1729
  },
1730
1730
 
1731
+ "selectbox-arrow-button": "widget",
1732
+
1733
+ /*
1734
+ ---------------------------------------------------------------------------
1735
+ CHECKED SELECT BOX
1736
+ ---------------------------------------------------------------------------
1737
+ */
1738
+
1739
+ "checked-selectbox": "selectbox",
1740
+
1741
+ "checked-selectbox/allNone": {
1742
+ include: "button"
1743
+ },
1744
+
1745
+ "checked-selectbox/tag": "tag",
1746
+
1747
+ tag: {
1748
+ alias: "button",
1749
+ include: "button"
1750
+ },
1751
+
1731
1752
  /*
1732
1753
  ---------------------------------------------------------------------------
1733
1754
  DATE CHOOSER
@@ -1238,6 +1238,27 @@ qx.Theme.define("qx.theme.simple.Appearance", {
1238
1238
  }
1239
1239
  },
1240
1240
 
1241
+ "selectbox-arrow-button": "widget",
1242
+
1243
+ /*
1244
+ ---------------------------------------------------------------------------
1245
+ CHECKED SELECT BOX
1246
+ ---------------------------------------------------------------------------
1247
+ */
1248
+
1249
+ "checked-selectbox": "selectbox",
1250
+
1251
+ "checked-selectbox/allNone": {
1252
+ include: "button"
1253
+ },
1254
+
1255
+ "checked-selectbox/tag": "tag",
1256
+
1257
+ tag: {
1258
+ alias: "button",
1259
+ include: "button"
1260
+ },
1261
+
1241
1262
  /*
1242
1263
  ---------------------------------------------------------------------------
1243
1264
  COMBO BOX
@@ -1911,11 +1932,11 @@ qx.Theme.define("qx.theme.simple.Appearance", {
1911
1932
  textColor: states.disabled
1912
1933
  ? "text-disabled"
1913
1934
  : states.weekend
1914
- ? "background-selected-dark"
1915
- : "background",
1935
+ ? "datechooser-background-selected-dark"
1936
+ : "datechooser-background",
1916
1937
  backgroundColor: states.weekend
1917
- ? "background"
1918
- : "background-selected-dark",
1938
+ ? "datechooser-background"
1939
+ : "datechooser-background-selected-dark",
1919
1940
  paddingTop: 2
1920
1941
  };
1921
1942
  }
@@ -1947,7 +1968,8 @@ qx.Theme.define("qx.theme.simple.Appearance", {
1947
1968
  style(states) {
1948
1969
  return {
1949
1970
  textAlign: "center",
1950
- textColor: "background-selected-dark",
1971
+ textColor: "datechooser-background",
1972
+ backgroundColor: "datechooser-background-selected-dark",
1951
1973
  padding: [2, 4],
1952
1974
  decorator: states.header
1953
1975
  ? "datechooser-week-header"
@@ -1313,6 +1313,8 @@ qx.Theme.define("qx.theme.tangible.Appearance", {
1313
1313
  }
1314
1314
  },
1315
1315
 
1316
+ "selectbox-arrow-button": "widget",
1317
+
1316
1318
  /*
1317
1319
  ---------------------------------------------------------------------------
1318
1320
  CHECKED SELECT BOX
@@ -810,7 +810,13 @@ qx.Class.define("qx.tool.compiler.ClassFile", {
810
810
  } else if (param.type == "ArrayPattern") {
811
811
  param.elements.forEach(elem => addDecl(elem));
812
812
  } else if (param.type == "ObjectPattern") {
813
- param.properties.forEach(prop => addDecl(prop.value));
813
+ param.properties.forEach(prop => {
814
+ if (prop.type == "RestElement") {
815
+ addDecl(prop);
816
+ } else {
817
+ addDecl(prop.value);
818
+ }
819
+ });
814
820
  } else {
815
821
  t.addMarker("testForFunctionParameterType", node.loc, param.type);
816
822
  }
@@ -1516,6 +1522,9 @@ qx.Class.define("qx.tool.compiler.ClassFile", {
1516
1522
  });
1517
1523
  }
1518
1524
  path.traverse(VISITOR);
1525
+ } else if (keyName == "delegate") {
1526
+ path.skip();
1527
+ path.traverse(VISITOR);
1519
1528
  } else if (keyName == "aliases") {
1520
1529
  path.skip();
1521
1530
  if (!prop.value.properties) {
@@ -1703,7 +1712,8 @@ qx.Class.define("qx.tool.compiler.ClassFile", {
1703
1712
  ClassDeclaration: 1,
1704
1713
  ClassMethod: 1,
1705
1714
  LabeledStatement: 1,
1706
- BreakStatement: 1
1715
+ BreakStatement: 1,
1716
+ ContinueStatement: 1
1707
1717
  };
1708
1718
 
1709
1719
  // These are AST node types we expect to find at the root of the identifier, and which will
@@ -2149,7 +2159,7 @@ qx.Class.define("qx.tool.compiler.ClassFile", {
2149
2159
  t.addMarker(
2150
2160
  "translate.invalidMessageId",
2151
2161
  path.node.loc,
2152
- arg0
2162
+ arg0 ?? ""
2153
2163
  );
2154
2164
  } else {
2155
2165
  addTranslation({ msgid: arg0 });
@@ -2164,8 +2174,8 @@ qx.Class.define("qx.tool.compiler.ClassFile", {
2164
2174
  t.addMarker(
2165
2175
  "translate.invalidMessageIds",
2166
2176
  path.node.loc,
2167
- arg0,
2168
- arg1
2177
+ arg0 ?? "",
2178
+ arg1 ?? ""
2169
2179
  );
2170
2180
  } else {
2171
2181
  addTranslation({ msgid: arg0, msgid_plural: arg1 });
@@ -2387,7 +2397,9 @@ qx.Class.define("qx.tool.compiler.ClassFile", {
2387
2397
  // Object destructuring `var {a,b} = {...}`
2388
2398
  } else if (decl.id.type == "ObjectPattern") {
2389
2399
  decl.id.properties.forEach(prop => {
2390
- if (prop.value.type == "AssignmentPattern") {
2400
+ if (prop.type == "RestElement") {
2401
+ t.addDeclaration(prop.argument.name);
2402
+ } else if (prop.value.type == "AssignmentPattern") {
2391
2403
  t.addDeclaration(prop.value.left.name);
2392
2404
  } else {
2393
2405
  t.addDeclaration(prop.value.name);
@@ -167,7 +167,7 @@ qx.Class.define("qx.tool.compiler.resources.Asset", {
167
167
 
168
168
  async load() {
169
169
  if (this.__loaders) {
170
- this.__loaders.forEach(loader => loader.load(this));
170
+ await Promise.all(this.__loaders.map(loader => loader.load(this)));
171
171
  }
172
172
  },
173
173
 
@@ -85,12 +85,16 @@ qx.Class.define("qx.tool.compiler.targets.meta.PackageJavascript", {
85
85
 
86
86
  if (pkg.isEmbedAllJavascript()) {
87
87
  this.__sourceMapOffsets = [];
88
+ let packageWs = new qx.tool.utils.Utils.LineCountingTransform();
88
89
  let strip = new qx.tool.utils.Utils.StripSourceMapTransform();
89
- strip.pipe(ws);
90
+ strip.pipe(packageWs);
91
+ packageWs.pipe(ws, {
92
+ end: false
93
+ });
90
94
  await new Promise(async resolve => {
91
95
  for (let i = 0; i < pkg.getJavascriptMetas().length; i++) {
92
96
  let js = pkg.getJavascriptMetas()[i];
93
- this.__sourceMapOffsets.push(ws.getLineNumber());
97
+ this.__sourceMapOffsets.push(packageWs.getLineNumber());
94
98
  await js.unwrap().writeSourceCodeToStream(strip);
95
99
  strip.write("\n");
96
100
  }
@@ -132,6 +132,7 @@ qx.Class.define("qx.ui.core.Blocker", {
132
132
  members: {
133
133
  __blocker: null,
134
134
  __blockerCount: 0,
135
+ __blockingContent: false,
135
136
 
136
137
  __activeElements: null,
137
138
  __focusElements: null,
@@ -186,11 +187,17 @@ qx.Class.define("qx.ui.core.Blocker", {
186
187
  * @param bounds {Map} Map with the new width, height, left and top values
187
188
  */
188
189
  _updateBlockerBounds(bounds) {
190
+ // When blocking content, the blocker is a child of the widget itself,
191
+ // so it should be positioned at 0,0 relative to the widget.
192
+ // Otherwise, it's positioned relative to the layout parent.
193
+ var left = this.__blockingContent ? 0 : bounds.left;
194
+ var top = this.__blockingContent ? 0 : bounds.top;
195
+
189
196
  this.getBlockerElement().setStyles({
190
197
  width: bounds.width + "px",
191
198
  height: bounds.height + "px",
192
- left: bounds.left + "px",
193
- top: bounds.top + "px"
199
+ left: left + "px",
200
+ top: top + "px"
194
201
  });
195
202
  },
196
203
 
@@ -338,7 +345,7 @@ qx.Class.define("qx.ui.core.Blocker", {
338
345
  if (!this.__appearListener) {
339
346
  this.__appearListener = this._widget.addListenerOnce(
340
347
  "appear",
341
- this._block.bind(this, zIndex)
348
+ this._block.bind(this, zIndex, blockContent)
342
349
  );
343
350
  }
344
351
  return;
@@ -358,6 +365,9 @@ qx.Class.define("qx.ui.core.Blocker", {
358
365
 
359
366
  this.__blockerCount++;
360
367
  if (this.__blockerCount < 2) {
368
+ // Track if we're blocking content (blocker is child of widget)
369
+ this.__blockingContent = blockContent === true;
370
+
361
371
  this._backupActiveWidget();
362
372
 
363
373
  var bounds = this._widget.getBounds();
@@ -442,6 +452,9 @@ qx.Class.define("qx.ui.core.Blocker", {
442
452
  blocker.removeListener("keyup", this.__stopTabEvent, this);
443
453
  blocker.exclude();
444
454
 
455
+ // Reset blocking content state
456
+ this.__blockingContent = false;
457
+
445
458
  this.fireEvent("unblocked", qx.event.type.Event);
446
459
  },
447
460
 
@@ -61,7 +61,11 @@ qx.Mixin.define("qx.ui.core.MExecutable", {
61
61
 
62
62
  members: {
63
63
  __executableBindingIds: null,
64
- __semaphore: false,
64
+
65
+ /**
66
+ * @type {Boolean} Whether the command already executed. The protection flag to stop the second command execution.
67
+ */
68
+ __commandExecuted: false,
65
69
  __executeListenerId: null,
66
70
 
67
71
  /**
@@ -83,10 +87,10 @@ qx.Mixin.define("qx.ui.core.MExecutable", {
83
87
  var cmd = this.getCommand();
84
88
 
85
89
  if (cmd) {
86
- if (this.__semaphore) {
87
- this.__semaphore = false;
90
+ if (this.__commandExecuted) {
91
+ this.__commandExecuted = false;
88
92
  } else {
89
- this.__semaphore = true;
93
+ this.__commandExecuted = true;
90
94
  cmd.execute(this);
91
95
  }
92
96
  }
@@ -100,15 +104,13 @@ qx.Mixin.define("qx.ui.core.MExecutable", {
100
104
  * @param e {qx.event.type.Event} The execute event of the command.
101
105
  */
102
106
  __onCommandExecute(e) {
107
+ if (this.__commandExecuted) {
108
+ this.__commandExecuted = false;
109
+ return;
110
+ }
111
+ this.__commandExecuted = true;
103
112
  if (this.isEnabled()) {
104
- if (this.__semaphore) {
105
- this.__semaphore = false;
106
- return;
107
- }
108
- if (this.isEnabled()) {
109
- this.__semaphore = true;
110
- this.execute();
111
- }
113
+ this.execute();
112
114
  }
113
115
  },
114
116
 
@@ -484,7 +484,7 @@ qx.Class.define("qx.ui.form.validation.Manager", {
484
484
 
485
485
  let msg = item.getInvalidMessage();
486
486
  if (
487
- msg &&
487
+ !msg &&
488
488
  qx.core.Environment.get(
489
489
  "qx.ui.form.validation.Manager.allowDefaultInvalidMessage"
490
490
  )
@@ -140,11 +140,16 @@ qx.Class.define("qx.ui.mobile.dialog.Popup", {
140
140
  __widget: null,
141
141
  __titleWidget: null,
142
142
  __lastPopupDimension: null,
143
+ /**
144
+ * Flag which ignores event domupdated caused by this widget and stopps infinite recursive `_updatePosition` calls
145
+ */
146
+ __updatePositionStarted: false,
143
147
 
144
148
  /**
145
149
  * Event handler. Called whenever the position of the popup should be updated.
146
150
  */
147
151
  _updatePosition() {
152
+ this.__updatePositionStarted = true;
148
153
  // Traverse single anchor classes for removal, for preventing 'domupdated' event if no CSS classes changed.
149
154
  var anchorClasses = ["top", "bottom", "left", "right", "anchor"];
150
155
  for (var i = 0; i < anchorClasses.length; i++) {
@@ -446,7 +451,14 @@ qx.Class.define("qx.ui.mobile.dialog.Popup", {
446
451
  flex: 1
447
452
  });
448
453
 
449
- widget.addListener("domupdated", this._updatePosition, this);
454
+ widget.addListener("domupdated", () => {
455
+ if (!this.__updatePositionStarted){
456
+ this._updatePosition();
457
+ }
458
+ else {
459
+ this.__updatePositionStarted = false;
460
+ }
461
+ }, this);
450
462
 
451
463
  this.__widget = widget;
452
464
  },
@@ -1570,10 +1570,11 @@ qx.Class.define("qx.ui.table.Table", {
1570
1570
  * visible.
1571
1571
  */
1572
1572
  setFocusedCell(col, row, scrollVisible) {
1573
- if (
1574
- !this.isEditing() &&
1575
- (col != this.__focusedCol || row != this.__focusedRow)
1576
- ) {
1573
+ let cellChanged = col != this.__focusedCol || row != this.__focusedRow;
1574
+ if (this.isEditing() && cellChanged) {
1575
+ this.stopEditing();
1576
+ }
1577
+ if (!this.isEditing() && cellChanged) {
1577
1578
  if (col === null) {
1578
1579
  col = 0;
1579
1580
  }
@@ -1973,16 +1973,14 @@ qx.Class.define("qx.ui.table.pane.Scroller", {
1973
1973
  _onFocusinCellEditorAddBlurListener(e) {
1974
1974
  this.debug("executed FOCUSIN event listener for hash: " + e.getTarget().$$hash);
1975
1975
  qx.event.Timer.once(function() {
1976
- this._cellEditor.addListenerOnce('blur', this._onBlurCellEditorStopEditing, this);
1977
- this.debug('added BLUR listener to hash: ' + this._cellEditor.$$hash);
1976
+ this._cellEditor.addListener('focusout', this._onFocusoutCellEditorStopEditing, this);
1977
+ this.debug('added FOCUSOUT listener to hash: ' + this._cellEditor.$$hash);
1978
1978
  }, this, 1);
1979
1979
  },
1980
1980
 
1981
- /**
1982
- * Stop editing whenever the cell editor blurs.
1983
- */
1984
- _onBlurCellEditorStopEditing(e) {
1985
- this.debug("executed BLUR listener for hash " + e.getTarget().$$hash);
1981
+
1982
+ _onFocusoutCellEditorStopEditing(e) {
1983
+ this.debug("executed FOCUSOUT listener for hash " + e.getTarget().$$hash);
1986
1984
  if (this._cellEditor === e.getTarget()) {
1987
1985
  this.debug('hash: ' + this._cellEditor.$$hash);
1988
1986
  switch (this.getTable().getCellEditorBlurAction()) {
@@ -2408,4 +2406,4 @@ qx.Class.define("qx.ui.table.pane.Scroller", {
2408
2406
  "__clipperContainer"
2409
2407
  );
2410
2408
  }
2411
- });
2409
+ });
@@ -761,7 +761,10 @@ qx.Class.define("qx.ui.tree.VirtualTree", {
761
761
  }
762
762
  }
763
763
 
764
- if (this.__lookupTable.indexOf(item) != -1) {
764
+ // Issue #9390: When hideRoot is true, the root is not in the lookup table,
765
+ // but we still need to update when its children change
766
+ var isRootAndHidden = this.isHideRoot() && item === this.getModel();
767
+ if (this.__lookupTable.indexOf(item) != -1 || isRootAndHidden) {
765
768
  this.__applyModelChanges();
766
769
  }
767
770
  }
@@ -476,8 +476,9 @@ qx.Class.define("qx.util.format.DateFormat", {
476
476
 
477
477
  var timezoneOffset = date.getTimezoneOffset();
478
478
  var timezoneSign = timezoneOffset > 0 ? 1 : -1;
479
- var timezoneHours = Math.floor(Math.abs(timezoneOffset) / 60);
480
- var timezoneMinutes = Math.abs(timezoneOffset) % 60;
479
+ var absTimezoneOffset = Math.abs(timezoneOffset);
480
+ var timezoneHours = Math.floor(absTimezoneOffset / 60);
481
+ var timezoneMinutes = Math.trunc(absTimezoneOffset) % 60;
481
482
 
482
483
  // Create the output
483
484
  this.__initFormatTree();