@qooxdoo/framework 7.0.0-beta.1 → 7.0.0-beta.5

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 (113) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/Manifest.json +2 -2
  3. package/bin/deploy/qx +0 -0
  4. package/lib/compiler/compile-info.json +59 -60
  5. package/lib/compiler/index.js +636 -7815
  6. package/lib/resource/qx/tool/cli/templates/loader/loader-node.tmpl.js +3 -1
  7. package/lib/resource/qx/tool/cli/templates/skeleton/mobile/source/theme/custom/css/custom.css +1 -1
  8. package/lib/resource/qx/tool/cli/templates/skeleton/mobile/source/theme/custom/css/custom.css.map +1 -1
  9. package/lib/resource/qx/tool/cli/templates/template_vars.js +1 -1
  10. package/lib/resource/qx/tool/loadsass.js +2 -10
  11. package/lib/resource/qx/tool/schema/Manifest-1-0-0.json +1 -2
  12. package/lib/resource/qx/tool/schema/Manifest-2-0-0.json +1 -2
  13. package/lib/resource/qx/tool/schema/compile-1-0-0.json +3 -7
  14. package/package.json +11 -11
  15. package/source/class/qx/test/type/BaseString.js +7 -0
  16. package/source/class/qx/test/ui/embed/Iframe.js +1 -0
  17. package/source/class/qx/test/ui/form/ComboBox.js +0 -42
  18. package/source/class/qx/theme/manager/Decoration.js +0 -0
  19. package/source/class/qx/theme/tangible/ColorDark.js +0 -0
  20. package/source/class/qx/tool/cli/Cli.js +5 -3
  21. package/source/class/qx/tool/cli/api/CompilerApi.js +15 -5
  22. package/source/class/qx/tool/cli/commands/Command.js +7 -0
  23. package/source/class/qx/tool/cli/commands/Compile.js +6 -6
  24. package/source/class/qx/tool/cli/commands/Lint.js +30 -11
  25. package/source/class/qx/tool/cli/commands/Package.js +1 -2
  26. package/source/class/qx/tool/cli/commands/package/Publish.js +19 -10
  27. package/source/class/qx/tool/compiler/Analyser.js +21 -22
  28. package/source/class/qx/tool/compiler/app/WebFont.js +1 -1
  29. package/source/class/qx/tool/compiler/makers/AppMaker.js +13 -13
  30. package/source/class/qx/tool/compiler/targets/TypeScriptWriter.js +1 -2
  31. package/source/class/qx/tool/compiler/targets/meta/PolyfillJs.js +7 -7
  32. package/source/class/qx/tool/config/Abstract.js +3 -3
  33. package/source/class/qx/tool/config/Utils.js +10 -1
  34. package/source/class/qx/tool/utils/Json.js +1 -1
  35. package/source/class/qx/type/BaseString.js +2 -1
  36. package/source/class/qx/ui/container/SlideBar.js +3 -0
  37. package/source/class/qx/ui/core/Widget.js +70 -0
  38. package/source/class/qx/ui/core/scroll/NativeScrollBar.js +3 -0
  39. package/source/class/qx/ui/core/scroll/ScrollBar.js +3 -0
  40. package/source/class/qx/ui/form/AbstractSelectBox.js +38 -6
  41. package/source/class/qx/ui/form/Button.js +3 -0
  42. package/source/class/qx/ui/form/CheckBox.js +25 -1
  43. package/source/class/qx/ui/form/ComboBox.js +36 -27
  44. package/source/class/qx/ui/form/DateField.js +16 -1
  45. package/source/class/qx/ui/form/List.js +3 -0
  46. package/source/class/qx/ui/form/MenuButton.js +24 -2
  47. package/source/class/qx/ui/form/RadioButton.js +7 -0
  48. package/source/class/qx/ui/form/RadioButtonGroup.js +3 -0
  49. package/source/class/qx/ui/form/RadioGroup.js +19 -0
  50. package/source/class/qx/ui/form/SelectBox.js +23 -1
  51. package/source/class/qx/ui/form/Slider.js +15 -0
  52. package/source/class/qx/ui/form/SplitButton.js +3 -0
  53. package/source/class/qx/ui/form/ToggleButton.js +8 -0
  54. package/source/class/qx/ui/menu/AbstractButton.js +24 -0
  55. package/source/class/qx/ui/menu/Button.js +3 -0
  56. package/source/class/qx/ui/menu/CheckBox.js +8 -0
  57. package/source/class/qx/ui/menu/Manager.js +2 -0
  58. package/source/class/qx/ui/menu/Menu.js +63 -1
  59. package/source/class/qx/ui/menu/RadioButton.js +10 -1
  60. package/source/class/qx/ui/menubar/Button.js +0 -27
  61. package/source/class/qx/ui/menubar/MenuBar.js +12 -0
  62. package/source/class/qx/ui/splitpane/Blocker.js +3 -0
  63. package/source/class/qx/ui/splitpane/Pane.js +3 -0
  64. package/source/class/qx/ui/table/Table.js +24 -2
  65. package/source/class/qx/ui/table/cellrenderer/Abstract.js +3 -1
  66. package/source/class/qx/ui/table/cellrenderer/AbstractImage.js +7 -3
  67. package/source/class/qx/ui/table/headerrenderer/HeaderCell.js +3 -0
  68. package/source/class/qx/ui/table/pane/Header.js +3 -0
  69. package/source/class/qx/ui/table/pane/Scroller.js +3 -7
  70. package/source/class/qx/ui/table/rowrenderer/Default.js +1 -1
  71. package/source/class/qx/ui/tabview/Page.js +26 -0
  72. package/source/class/qx/ui/tabview/TabView.js +3 -0
  73. package/source/class/qx/ui/toolbar/Button.js +2 -27
  74. package/source/class/qx/ui/toolbar/CheckBox.js +0 -27
  75. package/source/class/qx/ui/toolbar/RadioButton.js +21 -0
  76. package/source/class/qx/ui/toolbar/SplitButton.js +0 -28
  77. package/source/class/qx/ui/toolbar/ToolBar.js +3 -0
  78. package/source/class/qx/ui/window/Window.js +8 -0
  79. package/source/class/qxWeb.js +2 -0
  80. package/source/resource/qx/decoration/Indigo/font/JosefinSlab-SemiBold.ttf +0 -0
  81. package/source/resource/qx/decoration/Indigo/font/SIL Open Font License 1.1.txt +0 -0
  82. package/source/resource/qx/iconfont/MaterialIcons/fetch-fonts.sh +0 -0
  83. package/source/resource/qx/mobile/scss/common/_gradients.scss +3 -1
  84. package/source/resource/qx/mobile/scss/theme/indigo/_styles.scss +1 -1
  85. package/source/resource/qx/mobile/scss/ui/_carousel.scss +2 -2
  86. package/source/resource/qx/mobile/scss/ui/_checkbox.scss +3 -3
  87. package/source/resource/qx/mobile/scss/ui/_collapsible.scss +2 -2
  88. package/source/resource/qx/mobile/scss/ui/_picker.scss +4 -4
  89. package/source/resource/qx/mobile/scss/ui/_radiobutton.scss +4 -4
  90. package/source/resource/qx/mobile/scss/ui/_slider.scss +7 -7
  91. package/source/resource/qx/scss/_gradients.scss +3 -1
  92. package/source/resource/qx/scss/_mixins.scss +4 -2
  93. package/source/resource/qx/tool/bin/build-devtools +0 -0
  94. package/source/resource/qx/tool/bin/build-website +0 -0
  95. package/source/resource/qx/tool/bin/download-assets +0 -0
  96. package/source/resource/qx/tool/cli/templates/loader/loader-node.tmpl.js +3 -1
  97. package/source/resource/qx/tool/cli/templates/skeleton/mobile/source/theme/custom/scss/_styles.scss +1 -1
  98. package/source/resource/qx/tool/cli/templates/template_vars.js +1 -1
  99. package/source/resource/qx/tool/loadsass.js +2 -10
  100. package/source/resource/qx/tool/schema/Manifest-1-0-0.json +1 -2
  101. package/source/resource/qx/tool/schema/Manifest-2-0-0.json +1 -2
  102. package/source/resource/qx/tool/schema/compile-1-0-0.json +3 -7
  103. package/source/resource/qx/website/scss/ui/_carousel.scss +2 -2
  104. package/lib/resource/qx/tool/website/.gitignore +0 -2
  105. package/source/class/qx/io/request/auth/.gitignore +0 -0
  106. package/source/class/qx/test/bom/client/.gitignore +0 -0
  107. package/source/class/qx/test/ui/control/.gitignore +0 -0
  108. package/source/class/qx/tool/compiler/Version.js +0 -20
  109. package/source/resource/qx/decoration/Modern/treevirtual/.gitignore +0 -0
  110. package/source/resource/qx/mobile/css/.gitignore +0 -3
  111. package/source/resource/qx/tool/website/.gitignore +0 -2
  112. package/source/resource/qx/website/.gitignore +0 -1
  113. package/source/resource/qx/website/scss/.gitignore +0 -1
@@ -332,7 +332,7 @@ qx.Class.define("qx.tool.config.Abstract", {
332
332
  */
333
333
  setValue(prop_path, value, options) {
334
334
  let originalValue = this.getValue(prop_path, options);
335
- set_value(this.getData(), prop_path, value, options);
335
+ set_value(this.getData(), prop_path, value, {preservePaths:false});
336
336
  try {
337
337
  this.validate();
338
338
  } catch (e) {
@@ -340,7 +340,7 @@ qx.Class.define("qx.tool.config.Abstract", {
340
340
  if (originalValue === undefined) {
341
341
  unset_value(this.getData(), prop_path);
342
342
  } else {
343
- set_value(this.getData(), prop_path, originalValue, options);
343
+ set_value(this.getData(), prop_path, originalValue, {preservePaths:false});
344
344
  }
345
345
  // throw
346
346
  throw e;
@@ -362,7 +362,7 @@ qx.Class.define("qx.tool.config.Abstract", {
362
362
  this.validate();
363
363
  } catch (e) {
364
364
  // revert value
365
- set_value(this.getData(), prop_path, originalValue, options);
365
+ set_value(this.getData(), prop_path, originalValue, {preservePaths:false});
366
366
  // throw
367
367
  throw e;
368
368
  }
@@ -191,7 +191,7 @@ qx.Class.define("qx.tool.config.Utils", {
191
191
 
192
192
  throw new qx.tool.utils.Utils.UserError(`Path to the qx library cannot be determined.`);
193
193
  };
194
-
194
+
195
195
  this.__qxPathPromise = getQxPathImpl();
196
196
  return await this.__qxPathPromise;
197
197
  },
@@ -217,6 +217,15 @@ qx.Class.define("qx.tool.config.Utils", {
217
217
  return qx.tool.config.Utils.getLibraryVersion(qxpath);
218
218
  },
219
219
 
220
+ /**
221
+ * returns the compiler version.
222
+ * The version is written during compiler compile into the enviroment
223
+ * @return {String}
224
+ */
225
+ getCompilerVersion() {
226
+ return qx.core.Environment.get("qx.compiler.version");
227
+ },
228
+
220
229
  /**
221
230
  * Returns the qooxdoo version used in the application in the current or given
222
231
  * directory. Throws if no such version can be determined
@@ -59,7 +59,7 @@ qx.Class.define("qx.tool.utils.Json", {
59
59
  validate(json, schema, warnOnly=false) {
60
60
  let ajv = new Ajv({
61
61
  allErrors: true,
62
- jsonPointers: true
62
+ strict: false
63
63
  });
64
64
  if (qx.lang.Type.isArray(schema)) {
65
65
  ajv.addSchema(schema);
@@ -387,7 +387,8 @@ qx.Class.define("qx.type.BaseString",
387
387
  'toUpperCase',
388
388
  'toLocaleLowerCase',
389
389
  'toLocaleUpperCase',
390
- 'trim'
390
+ 'trim',
391
+ 'codePointAt'
391
392
  ];
392
393
 
393
394
  // feature/bug detection:
@@ -272,6 +272,9 @@ qx.Class.define("qx.ui.container.SlideBar",
272
272
  // property apply
273
273
  _applyOrientation : function(value, old)
274
274
  {
275
+ // ARIA attrs
276
+ this.getContentElement().setAttribute("aria-orientation", value);
277
+
275
278
  var oldLayouts = [this.getLayout(), this._getLayout()];
276
279
  var buttonForward = this.getChildControl("button-forward");
277
280
  var buttonBackward = this.getChildControl("button-backward");
@@ -3827,6 +3827,76 @@ qx.Class.define("qx.ui.core.Widget",
3827
3827
 
3828
3828
 
3829
3829
 
3830
+ /*
3831
+ ---------------------------------------------------------------------------
3832
+ ARIA attrs support
3833
+ ---------------------------------------------------------------------------
3834
+ */
3835
+
3836
+ /**
3837
+ * Sets the string which labels this widget. This will be read out by screenreaders. Needed if there is no
3838
+ * readable text/label in this widget which would automatically act as aria-label.
3839
+ * @param label {String} Labelling Text
3840
+ */
3841
+ setAriaLabel: function(label) {
3842
+ this.getContentElement().setAttribute("aria-label", label);
3843
+ },
3844
+
3845
+ /**
3846
+ * Marks that this widget gets labelled by another widget. This will be read out by screenreaders as first
3847
+ * information.
3848
+ * Similiar to aria-label, difference being that the labelling widget is an different widget and multiple
3849
+ * widgets can be added.
3850
+ * @param labelWidgets {qx.ui.core.Widget[]} Indefinite Number of labelling Widgets
3851
+ */
3852
+ addAriaLabelledBy: function(...labelWidgets) {
3853
+ this.__addAriaXBy(labelWidgets, "aria-labelledby");
3854
+ },
3855
+
3856
+ /**
3857
+ * Marks that this widget gets described by another widget. This will be read out by screenreaders as last
3858
+ * information. Multiple Widgets possible.
3859
+ * @param describingWidgets {qx.ui.core.Widget[]} Indefinite Number of describing Widgets
3860
+ */
3861
+ addAriaDescribedBy: function(...describingWidgets) {
3862
+ this.__addAriaXBy(describingWidgets, "aria-describedby");
3863
+ },
3864
+
3865
+ /**
3866
+ * Sets either aria-labelledby or aria-describedby
3867
+ * @param widgets {qx.ui.core.Widget[]} Indefinite Number of widgets
3868
+ * @param ariaAttr {String} aria-labelledby | aria-describedby
3869
+ */
3870
+ __addAriaXBy: function(widgets, ariaAttr) {
3871
+ if (!["aria-labelledby", "aria-describedby"].includes(ariaAttr)) {
3872
+ throw new Error("Only aria-labelledby or aria-describedby allowed!");
3873
+ }
3874
+ let idArr = [];
3875
+ for (const widget of widgets) {
3876
+ if (!(widget instanceof qx.ui.core.Widget)) {
3877
+ throw new Error("Given widget " + widget + " is not an instance of qx.ui.core.Widget!");
3878
+ }
3879
+ const contentEl = widget.getContentElement();
3880
+ let widgetId = contentEl.getAttribute("id");
3881
+ if (!widgetId) {
3882
+ widgetId = `label-${widget.toHashCode()}`;
3883
+ contentEl.setAttribute("id", widgetId);
3884
+ }
3885
+ if (!idArr.includes(widgetId)) {
3886
+ idArr.push(widgetId);
3887
+ }
3888
+ }
3889
+ if (idArr.length === 0) {
3890
+ return;
3891
+ }
3892
+ const idStr = idArr.join(" ");
3893
+ const contentEl = this.getContentElement();
3894
+ let res = contentEl.getAttribute(ariaAttr);
3895
+ res = res ? `${res} ${idStr}` : idStr;
3896
+ contentEl.setAttribute(ariaAttr, res);
3897
+ },
3898
+
3899
+
3830
3900
 
3831
3901
  /*
3832
3902
  ---------------------------------------------------------------------------
@@ -234,6 +234,9 @@ qx.Class.define("qx.ui.core.scroll.NativeScrollBar",
234
234
  // property apply
235
235
  _applyOrientation : function(value, old)
236
236
  {
237
+ // ARIA attrs
238
+ this.getContentElement().setAttribute("aria-orientation", value);
239
+
237
240
  var isHorizontal = this.__isHorizontal = value === "horizontal";
238
241
 
239
242
  this.set({
@@ -301,6 +301,9 @@ qx.Class.define("qx.ui.core.scroll.ScrollBar",
301
301
  // property apply
302
302
  _applyOrientation : function(value, old)
303
303
  {
304
+ // ARIA attrs
305
+ this.getContentElement().setAttribute("aria-orientation", value);
306
+
304
307
  // Dispose old layout
305
308
  var oldLayout = this._getLayout();
306
309
  if (oldLayout) {
@@ -55,6 +55,12 @@ qx.Class.define("qx.ui.form.AbstractSelectBox",
55
55
  this._setLayout(layout);
56
56
  layout.setAlignY("middle");
57
57
 
58
+ // ARIA attrs
59
+ const contentEl = this.getContentElement();
60
+ contentEl.setAttribute("role", "button");
61
+ contentEl.setAttribute("aria-haspopup", "listbox");
62
+ contentEl.setAttribute("aria-expanded", false);
63
+
58
64
  // Register listeners
59
65
  this.addListener("keypress", this._onKeyPress);
60
66
  this.addListener("blur", this._onBlur, this);
@@ -130,7 +136,7 @@ qx.Class.define("qx.ui.form.AbstractSelectBox",
130
136
 
131
137
  switch(id)
132
138
  {
133
- case "list":
139
+ case "list": {
134
140
  control = new qx.ui.form.List().set({
135
141
  focusable: false,
136
142
  keepFocus: true,
@@ -140,12 +146,18 @@ qx.Class.define("qx.ui.form.AbstractSelectBox",
140
146
  selectionMode: "one",
141
147
  quickSelection: true
142
148
  });
149
+ const listId = "list-" + control.toHashCode();
150
+ const childrenContainerEl = control.getChildrenContainer().getContentElement();
151
+ childrenContainerEl.setAttribute("id", listId);
152
+ childrenContainerEl.setAttribute("role", "listbox");
153
+ this.getContentElement().setAttribute("aria-owns", listId);
143
154
 
155
+ control.addListener("addItem", this._onListAddItem, this);
144
156
  control.addListener("changeSelection", this._onListChangeSelection, this);
145
157
  control.addListener("pointerdown", this._onListPointerDown, this);
146
158
  control.getChildControl("pane").addListener("tap", this.close, this);
147
159
  break;
148
-
160
+ }
149
161
  case "popup":
150
162
  control = new qx.ui.popup.Popup(new qx.ui.layout.VBox());
151
163
  control.setAutoHide(false);
@@ -292,8 +304,8 @@ qx.Class.define("qx.ui.form.AbstractSelectBox",
292
304
  e.stopPropagation();
293
305
  }
294
306
 
295
- // hide the list always on escape
296
- else if (!listPopup.isHidden() && identifier == "Escape")
307
+ // hide the list always on escape and tab
308
+ else if (!listPopup.isHidden() && (identifier == "Escape" || identifier == "Tab"))
297
309
  {
298
310
  this.close();
299
311
  e.stop();
@@ -315,7 +327,23 @@ qx.Class.define("qx.ui.form.AbstractSelectBox",
315
327
  _onResize : function(e){
316
328
  this.getChildControl("popup").setMinWidth(e.getData().width);
317
329
  },
318
-
330
+
331
+ /**
332
+ * Sets ARIA attributes on the item
333
+ *
334
+ * @param e {qx.event.type.Data} Data Event
335
+ */
336
+ _onListAddItem : function(e) {
337
+ const item = e.getData();
338
+ const contentEl = item.getContentElement();
339
+ contentEl.setAttribute("id", "list-item-" + item.toHashCode());
340
+ contentEl.setAttribute("role", "option");
341
+ const ariaSelected = contentEl.getAttribute("aria-selected");
342
+ // aria-selected may be already set from changeSelection listener
343
+ if (ariaSelected === null || ariaSelected === undefined) {
344
+ contentEl.setAttribute("aria-selected", false);
345
+ }
346
+ },
319
347
 
320
348
  /**
321
349
  * Syncs the own property from the list change
@@ -343,7 +371,11 @@ qx.Class.define("qx.ui.form.AbstractSelectBox",
343
371
  * @param e {qx.event.type.Data} Property change event
344
372
  */
345
373
  _onPopupChangeVisibility : function(e) {
346
- e.getData() == "visible" ? this.addState("popupOpen") : this.removeState("popupOpen");
374
+ const visible = e.getData() == "visible";
375
+ visible ? this.addState("popupOpen") : this.removeState("popupOpen");
376
+
377
+ // ARIA attrs
378
+ this.getContentElement().setAttribute("aria-expanded", visible);
347
379
  }
348
380
  }
349
381
  });
@@ -75,6 +75,9 @@ qx.Class.define("qx.ui.form.Button",
75
75
  this.setCommand(command);
76
76
  }
77
77
 
78
+ // ARIA attrs
79
+ this.getContentElement().setAttribute("role", "button");
80
+
78
81
  // Add listeners
79
82
  this.addListener("pointerover", this._onPointerOver);
80
83
  this.addListener("pointerout", this._onPointerOut);
@@ -54,6 +54,12 @@ qx.Class.define("qx.ui.form.CheckBox",
54
54
  // Initialize the checkbox to a valid value (the default is null which
55
55
  // is invalid)
56
56
  this.setValue(false);
57
+
58
+ // ARIA attrs
59
+ const contentEl = this.getContentElement();
60
+ contentEl.setAttribute("role", "checkbox");
61
+ contentEl.removeAttribute("aria-pressed");
62
+ contentEl.setAttribute("aria-checked", false);
57
63
  },
58
64
 
59
65
 
@@ -107,6 +113,24 @@ qx.Class.define("qx.ui.form.CheckBox",
107
113
  "toolTipText",
108
114
  "value",
109
115
  "menu"
110
- ]
116
+ ],
117
+
118
+ // overridden
119
+ _applyValue : function(value, old) {
120
+ value ? this.addState("checked") : this.removeState("checked");
121
+
122
+ let ariaChecked = Boolean(value);
123
+ if (this.isTriState()) {
124
+ if (value === null) {
125
+ ariaChecked = "mixed";
126
+ this.addState("undetermined");
127
+ } else if (old === null) {
128
+ this.removeState("undetermined");
129
+ }
130
+ }
131
+
132
+ // ARIA attrs
133
+ this.getContentElement().setAttribute("aria-checked", ariaChecked);
134
+ },
111
135
  }
112
136
  });
@@ -50,6 +50,9 @@ qx.Class.define("qx.ui.form.ComboBox",
50
50
  var textField = this._createChildControl("textfield");
51
51
  this._createChildControl("button");
52
52
 
53
+ // ARIA attrs
54
+ this.getContentElement().setAttribute("role", "combobox");
55
+
53
56
  this.addListener("tap", this._onTap);
54
57
 
55
58
  // forward the focusin and focusout events to the textfield. The textfield
@@ -242,26 +245,25 @@ qx.Class.define("qx.ui.form.ComboBox",
242
245
  var popup = this.getChildControl("popup");
243
246
  var iden = e.getKeyIdentifier();
244
247
 
245
- if (iden == "Down" && e.isAltPressed())
246
- {
247
- this.getChildControl("button").addState("selected");
248
- this.toggle();
249
- e.stopPropagation();
250
- }
251
- else if (iden == "Enter")
252
- {
253
- if (popup.isVisible())
254
- {
248
+ if (popup.isVisible()) {
249
+ const listIdentifier = ["Up", "Down", "PageUp", "PageDown", "Escape", "Tab"];
250
+ if (iden == "Enter") {
255
251
  this._setPreselectedItem();
256
252
  this.resetAllTextSelection();
257
253
  this.close();
258
254
  e.stop();
255
+ } else if (listIdentifier.includes(iden)) {
256
+ this.base(arguments, e);
257
+ } else {
258
+ this.close();
259
+ }
260
+ } else {
261
+ if (iden == "Down") {
262
+ this.getChildControl("button").addState("selected");
263
+ this.open();
264
+ e.stop();
259
265
  }
260
266
  }
261
- else if (popup.isVisible())
262
- {
263
- this.base(arguments, e);
264
- }
265
267
  },
266
268
 
267
269
 
@@ -333,6 +335,13 @@ qx.Class.define("qx.ui.form.ComboBox",
333
335
  this.__preSelectedItem = null;
334
336
  }
335
337
  }
338
+
339
+ // Set aria-activedescendant
340
+ if (current && current[0]) {
341
+ this.getChildControl("textfield").getContentElement().setAttribute("aria-activedescendant", current[0].getContentElement().getAttribute("id"));
342
+ } else {
343
+ this.getChildControl("textfield").getContentElement().removeAttribute("aria-activedescendant");
344
+ }
336
345
  },
337
346
 
338
347
 
@@ -363,16 +372,6 @@ qx.Class.define("qx.ui.form.ComboBox",
363
372
  list.resetSelection();
364
373
  }
365
374
  }
366
- else
367
- {
368
- // When closing the popup text should selected and field should
369
- // have the focus. Identical to when reaching the field using the TAB key.
370
- //
371
- // Only focus if popup was visible before. Fixes [BUG #4453].
372
- if (e.getOldData() == "visible") {
373
- this.tabFocus();
374
- }
375
- }
376
375
 
377
376
  // In all cases: Remove focused state from button
378
377
  this.getChildControl("button").removeState("selected");
@@ -390,11 +389,12 @@ qx.Class.define("qx.ui.form.ComboBox",
390
389
  var value = e.getData();
391
390
 
392
391
  var list = this.getChildControl("list");
392
+ let current = null;
393
393
  if (value != null) {
394
394
  // Select item when possible
395
- var item = list.findItem(value, false);
396
- if (item) {
397
- list.setSelection([item]);
395
+ current = list.findItem(value, false);
396
+ if (current) {
397
+ list.setSelection([current]);
398
398
  } else {
399
399
  list.resetSelection();
400
400
  }
@@ -402,6 +402,15 @@ qx.Class.define("qx.ui.form.ComboBox",
402
402
  list.resetSelection();
403
403
  }
404
404
 
405
+ // ARIA attrs
406
+ const old = e.getOldData() ? list.findItem(e.getOldData(), false) : null;
407
+ if (old && old !== current) {
408
+ old.getContentElement().setAttribute("aria-selected", false);
409
+ }
410
+ if (current) {
411
+ current.getContentElement().setAttribute("aria-selected", true);
412
+ }
413
+
405
414
  // Fire event
406
415
  this.fireDataEvent("changeValue", value, e.getOldData());
407
416
  },
@@ -602,7 +602,22 @@ qx.Class.define("qx.ui.form.DateField",
602
602
 
603
603
  field.getFocusElement().focus();
604
604
  field.selectAllText();
605
- }
605
+ },
606
+
607
+ // overridden
608
+ setAriaLabel: function(label) {
609
+ this.getChildControl("textfield").setAriaLabel(label);
610
+ },
611
+
612
+ // overridden
613
+ addAriaLabelledBy: function(...labelWidgets) {
614
+ this.getChildControl("textfield").addAriaLabelledBy(...labelWidgets);
615
+ },
616
+
617
+ // overridden
618
+ addAriaDescribedBy: function(...describingWidgets) {
619
+ this.getChildControl("textfield").addAriaDescribedBy(...describingWidgets);
620
+ },
606
621
  },
607
622
 
608
623
 
@@ -277,6 +277,9 @@ qx.Class.define("qx.ui.form.List",
277
277
  // property apply
278
278
  _applyOrientation : function(value, old)
279
279
  {
280
+ // ARIA attrs
281
+ this.getContentElement().setAttribute("aria-orientation", value);
282
+
280
283
  var content = this.__content;
281
284
 
282
285
  // save old layout for disposal
@@ -45,6 +45,9 @@ qx.Class.define("qx.ui.form.MenuButton",
45
45
  if (menu != null) {
46
46
  this.setMenu(menu);
47
47
  }
48
+
49
+ // ARIA attrs
50
+ this.getContentElement().setAttribute("role", "button");
48
51
  },
49
52
 
50
53
 
@@ -112,6 +115,7 @@ qx.Class.define("qx.ui.form.MenuButton",
112
115
  old.resetOpener();
113
116
  }
114
117
 
118
+ const contentEl = this.getContentElement();
115
119
  if (value)
116
120
  {
117
121
  value.addListener("changeVisibility", this._onMenuChange, this);
@@ -119,6 +123,16 @@ qx.Class.define("qx.ui.form.MenuButton",
119
123
 
120
124
  value.removeState("submenu");
121
125
  value.removeState("contextmenu");
126
+
127
+ // ARIA attrs
128
+ contentEl.setAttribute("aria-haspopup", "menu");
129
+ contentEl.setAttribute("aria-expanded", value.isVisible());
130
+ contentEl.setAttribute("aria-controls", value.getContentElement().getAttribute("id"));
131
+ } else {
132
+ // ARIA attrs
133
+ contentEl.removeAttribute("aria-haspopup");
134
+ contentEl.removeAttribute("aria-expanded");
135
+ contentEl.removeAttribute("aria-controls");
122
136
  }
123
137
  },
124
138
 
@@ -142,6 +156,10 @@ qx.Class.define("qx.ui.form.MenuButton",
142
156
 
143
157
  if (menu)
144
158
  {
159
+ // Focus this button when the menu opens
160
+ if (this.isFocusable() && !qx.ui.core.FocusHandler.getInstance().isFocused(this)) {
161
+ this.focus();
162
+ }
145
163
  // Hide all menus first
146
164
  qx.ui.menu.Manager.getInstance().hideAll();
147
165
 
@@ -177,12 +195,15 @@ qx.Class.define("qx.ui.form.MenuButton",
177
195
  _onMenuChange : function(e)
178
196
  {
179
197
  var menu = this.getMenu();
180
-
181
- if (menu.isVisible()) {
198
+ const menuVisible = menu.isVisible()
199
+ if (menuVisible) {
182
200
  this.addState("pressed");
183
201
  } else {
184
202
  this.removeState("pressed");
185
203
  }
204
+
205
+ // ARIA attrs
206
+ this.getContentElement().setAttribute("aria-expanded", menuVisible);
186
207
  },
187
208
 
188
209
 
@@ -241,6 +262,7 @@ qx.Class.define("qx.ui.form.MenuButton",
241
262
  {
242
263
  switch(e.getKeyIdentifier())
243
264
  {
265
+ case "Space":
244
266
  case "Enter":
245
267
  this.removeState("abandoned");
246
268
  this.addState("pressed");
@@ -68,6 +68,12 @@ qx.Class.define("qx.ui.form.RadioButton",
68
68
 
69
69
  this.base(arguments, label);
70
70
 
71
+ // ARIA attrs
72
+ // Important: (Grouped) radio btns should be children of a div with role 'radiogroup'
73
+ const contentEl = this.getContentElement();
74
+ contentEl.setAttribute("role", "radio");
75
+ contentEl.setAttribute("aria-checked", false);
76
+
71
77
  // Add listeners
72
78
  this.addListener("execute", this._onExecute);
73
79
  this.addListener("keypress", this._onKeyPress);
@@ -164,6 +170,7 @@ qx.Class.define("qx.ui.form.RadioButton",
164
170
  value ?
165
171
  this.addState("checked") :
166
172
  this.removeState("checked");
173
+ this.getContentElement().setAttribute("aria-checked", Boolean(value));
167
174
  },
168
175
 
169
176
 
@@ -55,6 +55,9 @@ qx.Class.define("qx.ui.form.RadioButtonGroup",
55
55
  this.setLayout(layout);
56
56
  }
57
57
 
58
+ // ARIA attrs
59
+ this.getContentElement().setAttribute("role", "radiogroup");
60
+
58
61
  // create the radio group
59
62
  this.__radioGroup = new qx.ui.form.RadioGroup();
60
63
 
@@ -522,7 +522,26 @@ qx.Class.define("qx.ui.form.RadioGroup",
522
522
 
523
523
  if (value) {
524
524
  value.set(groupedProperty, true);
525
+
526
+ // If Group is focused, the selection was changed by keyboard. Switch focus to new value
527
+ if (this.__isGroupFocused() && value.isFocusable()) {
528
+ value.focus();
529
+ }
530
+ }
531
+ },
532
+
533
+ /**
534
+ * Checks if this group is focused by checking focused state of each item
535
+ * @returns {Boolean} result
536
+ */
537
+ __isGroupFocused: function () {
538
+ const focusHandler = qx.ui.core.FocusHandler.getInstance();
539
+ for (const item of this._getItems()) {
540
+ if (focusHandler.isFocused(item)) {
541
+ return true;
542
+ }
525
543
  }
544
+ return false;
526
545
  }
527
546
  },
528
547
 
@@ -221,6 +221,16 @@ qx.Class.define("qx.ui.form.SelectBox",
221
221
 
222
222
  this.__updateIcon();
223
223
  this.__updateLabel();
224
+
225
+ // ARIA attrs
226
+ const old = e.getOldData() ? e.getOldData()[0] : null;
227
+ const current = this.getSelection()[0];
228
+ if (old && old !== current) {
229
+ old.getContentElement().setAttribute("aria-selected", false);
230
+ }
231
+ if (current) {
232
+ current.getContentElement().setAttribute("aria-selected", true);
233
+ }
224
234
  },
225
235
 
226
236
 
@@ -327,7 +337,11 @@ qx.Class.define("qx.ui.form.SelectBox",
327
337
  _onKeyPress : function(e)
328
338
  {
329
339
  var iden = e.getKeyIdentifier();
330
- if(iden == "Enter" || iden == "Space")
340
+ if ((iden == "Down" || iden == "Up") && e.isAltPressed()) {
341
+ this.toggle();
342
+ e.stop();
343
+ }
344
+ else if (iden == "Enter" || iden == "Space")
331
345
  {
332
346
  // Apply pre-selected item (translate quick selection to real selection)
333
347
  if (this.__preSelectedItem)
@@ -337,6 +351,7 @@ qx.Class.define("qx.ui.form.SelectBox",
337
351
  }
338
352
 
339
353
  this.toggle();
354
+ e.stop();
340
355
  }
341
356
  else
342
357
  {
@@ -413,6 +428,13 @@ qx.Class.define("qx.ui.form.SelectBox",
413
428
  {
414
429
  this.resetSelection();
415
430
  }
431
+
432
+ // Set aria-activedescendant
433
+ if (current && current[0]) {
434
+ this.getContentElement().setAttribute("aria-activedescendant", current[0].getContentElement().getAttribute("id"));
435
+ } else {
436
+ this.getContentElement().removeAttribute("aria-activedescendant");
437
+ }
416
438
  },
417
439
 
418
440
  // overridden