@qooxdoo/framework 7.0.0-beta.2 → 7.0.0-beta.6

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 (143) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/Manifest.json +2 -3
  3. package/README.md +7 -3
  4. package/bin/deploy/qx +0 -0
  5. package/lib/compiler/compile-info.json +44 -45
  6. package/lib/compiler/index.js +1971 -8371
  7. package/lib/resource/qx/tool/cli/templates/loader/loader-node.tmpl.js +3 -1
  8. package/lib/resource/qx/tool/cli/templates/skeleton/mobile/source/theme/custom/css/custom.css +1 -1
  9. package/lib/resource/qx/tool/cli/templates/skeleton/mobile/source/theme/custom/css/custom.css.map +1 -1
  10. package/lib/resource/qx/tool/cli/templates/template_vars.js +1 -1
  11. package/lib/resource/qx/tool/loadsass.js +6 -12
  12. package/lib/resource/qx/tool/schema/Manifest-1-0-0.json +1 -2
  13. package/lib/resource/qx/tool/schema/Manifest-2-0-0.json +1 -2
  14. package/lib/resource/qx/tool/schema/compile-1-0-0.json +3 -7
  15. package/package.json +25 -12
  16. package/source/class/qx/io/__init__.js +5 -3
  17. package/source/class/qx/io/exception/Cancel.js +34 -0
  18. package/source/class/qx/io/exception/Exception.js +38 -0
  19. package/source/class/qx/{tool/compiler/Version.js → io/exception/Protocol.js} +13 -7
  20. package/source/class/qx/io/exception/Transport.js +39 -0
  21. package/source/class/qx/io/exception/__init__.js +4 -0
  22. package/source/class/qx/io/graphql/Client.js +112 -0
  23. package/source/class/qx/io/graphql/__init__.js +9 -0
  24. package/source/class/qx/io/graphql/protocol/Message.js +65 -0
  25. package/source/class/qx/io/graphql/protocol/Request.js +95 -0
  26. package/source/class/qx/io/graphql/protocol/Response.js +61 -0
  27. package/source/class/qx/io/graphql/protocol/__init__.js +6 -0
  28. package/source/class/qx/io/jsonrpc/Client.js +323 -0
  29. package/source/class/qx/io/jsonrpc/__init__.js +15 -0
  30. package/source/class/qx/io/jsonrpc/protocol/Batch.js +97 -0
  31. package/source/class/qx/io/jsonrpc/protocol/Error.js +63 -0
  32. package/source/class/qx/io/jsonrpc/protocol/Message.js +48 -0
  33. package/source/class/qx/io/jsonrpc/protocol/Notification.js +45 -0
  34. package/source/class/qx/io/jsonrpc/protocol/Parser.js +81 -0
  35. package/source/class/qx/io/jsonrpc/protocol/Request.js +93 -0
  36. package/source/class/qx/io/jsonrpc/protocol/Result.js +48 -0
  37. package/source/class/qx/io/jsonrpc/protocol/__init__.js +5 -0
  38. package/source/class/qx/io/request/authentication/Bearer.js +52 -0
  39. package/source/class/qx/io/transport/AbstractClient.js +100 -0
  40. package/source/class/qx/io/transport/AbstractTransport.js +41 -0
  41. package/source/class/qx/io/transport/Fetch.js +95 -0
  42. package/source/class/qx/io/transport/ITransport.js +40 -0
  43. package/source/class/qx/io/transport/PostMessage.js +55 -0
  44. package/source/class/qx/io/transport/Websocket.js +97 -0
  45. package/source/class/qx/io/transport/Xhr.js +139 -0
  46. package/source/class/qx/io/transport/__init__.js +18 -0
  47. package/source/class/qx/test/io/MAssert.js +46 -0
  48. package/source/class/qx/test/io/graphql/Client.js +169 -0
  49. package/source/class/qx/test/io/graphql/ClientFetch.js +34 -0
  50. package/source/class/qx/test/io/graphql/Request.js +42 -0
  51. package/source/class/qx/test/io/jsonrpc/Client.js +267 -0
  52. package/source/class/qx/test/io/jsonrpc/Protocol.js +80 -0
  53. package/source/class/qx/test/io/transport/PostMessage.js +56 -0
  54. package/source/class/qx/test/io/transport/Websocket.js +63 -0
  55. package/source/class/qx/test/ui/embed/Iframe.js +1 -0
  56. package/source/class/qx/test/ui/form/ComboBox.js +0 -42
  57. package/source/class/qx/test/util/DateFormat.js +45 -6
  58. package/source/class/qx/theme/manager/Decoration.js +0 -0
  59. package/source/class/qx/theme/tangible/ColorDark.js +0 -0
  60. package/source/class/qx/tool/cli/Cli.js +5 -3
  61. package/source/class/qx/tool/cli/api/CompilerApi.js +15 -5
  62. package/source/class/qx/tool/cli/commands/Command.js +7 -0
  63. package/source/class/qx/tool/cli/commands/Compile.js +4 -4
  64. package/source/class/qx/tool/cli/commands/Lint.js +30 -11
  65. package/source/class/qx/tool/cli/commands/Package.js +1 -2
  66. package/source/class/qx/tool/cli/commands/package/Publish.js +33 -10
  67. package/source/class/qx/tool/compiler/Analyser.js +21 -22
  68. package/source/class/qx/tool/compiler/app/WebFont.js +1 -1
  69. package/source/class/qx/tool/compiler/makers/AppMaker.js +15 -14
  70. package/source/class/qx/tool/compiler/targets/Target.js +2 -1
  71. package/source/class/qx/tool/compiler/targets/TypeScriptWriter.js +1 -2
  72. package/source/class/qx/tool/compiler/targets/meta/PolyfillJs.js +17 -9
  73. package/source/class/qx/tool/config/Abstract.js +3 -3
  74. package/source/class/qx/tool/config/Utils.js +10 -1
  75. package/source/class/qx/tool/utils/Json.js +1 -1
  76. package/source/class/qx/ui/container/SlideBar.js +3 -0
  77. package/source/class/qx/ui/core/Widget.js +70 -0
  78. package/source/class/qx/ui/core/scroll/NativeScrollBar.js +3 -0
  79. package/source/class/qx/ui/core/scroll/ScrollBar.js +3 -0
  80. package/source/class/qx/ui/form/AbstractSelectBox.js +38 -6
  81. package/source/class/qx/ui/form/Button.js +3 -0
  82. package/source/class/qx/ui/form/CheckBox.js +25 -1
  83. package/source/class/qx/ui/form/ComboBox.js +41 -27
  84. package/source/class/qx/ui/form/DateField.js +16 -1
  85. package/source/class/qx/ui/form/List.js +3 -0
  86. package/source/class/qx/ui/form/MenuButton.js +28 -2
  87. package/source/class/qx/ui/form/RadioButton.js +7 -0
  88. package/source/class/qx/ui/form/RadioButtonGroup.js +3 -0
  89. package/source/class/qx/ui/form/RadioGroup.js +19 -0
  90. package/source/class/qx/ui/form/SelectBox.js +28 -1
  91. package/source/class/qx/ui/form/Slider.js +15 -0
  92. package/source/class/qx/ui/form/SplitButton.js +3 -0
  93. package/source/class/qx/ui/form/ToggleButton.js +8 -0
  94. package/source/class/qx/ui/menu/AbstractButton.js +28 -0
  95. package/source/class/qx/ui/menu/Button.js +3 -0
  96. package/source/class/qx/ui/menu/CheckBox.js +8 -0
  97. package/source/class/qx/ui/menu/Manager.js +2 -0
  98. package/source/class/qx/ui/menu/Menu.js +74 -2
  99. package/source/class/qx/ui/menu/RadioButton.js +10 -1
  100. package/source/class/qx/ui/menubar/Button.js +0 -27
  101. package/source/class/qx/ui/menubar/MenuBar.js +12 -0
  102. package/source/class/qx/ui/splitpane/Blocker.js +3 -0
  103. package/source/class/qx/ui/splitpane/Pane.js +3 -0
  104. package/source/class/qx/ui/table/Table.js +24 -2
  105. package/source/class/qx/ui/table/cellrenderer/Abstract.js +3 -1
  106. package/source/class/qx/ui/table/cellrenderer/AbstractImage.js +7 -3
  107. package/source/class/qx/ui/table/headerrenderer/HeaderCell.js +3 -0
  108. package/source/class/qx/ui/table/pane/Header.js +3 -0
  109. package/source/class/qx/ui/table/pane/Model.js +10 -4
  110. package/source/class/qx/ui/table/pane/Scroller.js +3 -7
  111. package/source/class/qx/ui/table/rowrenderer/Default.js +1 -1
  112. package/source/class/qx/ui/tabview/Page.js +26 -0
  113. package/source/class/qx/ui/tabview/TabView.js +3 -0
  114. package/source/class/qx/ui/toolbar/Button.js +2 -27
  115. package/source/class/qx/ui/toolbar/CheckBox.js +0 -27
  116. package/source/class/qx/ui/toolbar/RadioButton.js +21 -0
  117. package/source/class/qx/ui/toolbar/SplitButton.js +0 -28
  118. package/source/class/qx/ui/toolbar/ToolBar.js +3 -0
  119. package/source/class/qx/ui/window/Window.js +8 -0
  120. package/source/class/qx/util/format/DateFormat.js +44 -17
  121. package/source/class/qxWeb.js +2 -0
  122. package/source/resource/qx/decoration/Indigo/font/JosefinSlab-SemiBold.ttf +0 -0
  123. package/source/resource/qx/decoration/Indigo/font/SIL Open Font License 1.1.txt +0 -0
  124. package/source/resource/qx/iconfont/MaterialIcons/fetch-fonts.sh +0 -0
  125. package/source/resource/qx/tool/bin/build-devtools +0 -0
  126. package/source/resource/qx/tool/bin/build-website +0 -0
  127. package/source/resource/qx/tool/bin/download-assets +0 -0
  128. package/source/resource/qx/tool/cli/templates/loader/loader-node.tmpl.js +3 -1
  129. package/source/resource/qx/tool/cli/templates/template_vars.js +1 -1
  130. package/source/resource/qx/tool/loadsass.js +6 -12
  131. package/source/resource/qx/tool/schema/Manifest-1-0-0.json +1 -2
  132. package/source/resource/qx/tool/schema/Manifest-2-0-0.json +1 -2
  133. package/source/resource/qx/tool/schema/compile-1-0-0.json +3 -7
  134. package/source/translation/hr.po +297 -0
  135. package/lib/resource/qx/tool/website/.gitignore +0 -2
  136. package/source/class/qx/io/request/auth/.gitignore +0 -0
  137. package/source/class/qx/test/bom/client/.gitignore +0 -0
  138. package/source/class/qx/test/ui/control/.gitignore +0 -0
  139. package/source/resource/qx/decoration/Modern/treevirtual/.gitignore +0 -0
  140. package/source/resource/qx/mobile/css/.gitignore +0 -3
  141. package/source/resource/qx/tool/website/.gitignore +0 -2
  142. package/source/resource/qx/website/.gitignore +0 -1
  143. package/source/resource/qx/website/scss/.gitignore +0 -1
@@ -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,25 +245,24 @@ 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
- }
261
- else if (popup.isVisible())
262
- {
263
- this.base(arguments, e);
264
266
  }
265
267
  },
266
268
 
@@ -333,6 +335,18 @@ qx.Class.define("qx.ui.form.ComboBox",
333
335
  this.__preSelectedItem = null;
334
336
  }
335
337
  }
338
+
339
+ // Set aria-activedescendant
340
+ const textFieldContentEl = this.getChildControl("textfield").getContentElement();
341
+ if (!textFieldContentEl) {
342
+ return;
343
+ }
344
+ const currentContentEl = current && current[0] ? current[0].getContentElement() : null;
345
+ if (currentContentEl) {
346
+ textFieldContentEl.setAttribute("aria-activedescendant", currentContentEl.getAttribute("id"));
347
+ } else {
348
+ textFieldContentEl.removeAttribute("aria-activedescendant");
349
+ }
336
350
  },
337
351
 
338
352
 
@@ -363,16 +377,6 @@ qx.Class.define("qx.ui.form.ComboBox",
363
377
  list.resetSelection();
364
378
  }
365
379
  }
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
380
 
377
381
  // In all cases: Remove focused state from button
378
382
  this.getChildControl("button").removeState("selected");
@@ -390,11 +394,12 @@ qx.Class.define("qx.ui.form.ComboBox",
390
394
  var value = e.getData();
391
395
 
392
396
  var list = this.getChildControl("list");
397
+ let current = null;
393
398
  if (value != null) {
394
399
  // Select item when possible
395
- var item = list.findItem(value, false);
396
- if (item) {
397
- list.setSelection([item]);
400
+ current = list.findItem(value, false);
401
+ if (current) {
402
+ list.setSelection([current]);
398
403
  } else {
399
404
  list.resetSelection();
400
405
  }
@@ -402,6 +407,15 @@ qx.Class.define("qx.ui.form.ComboBox",
402
407
  list.resetSelection();
403
408
  }
404
409
 
410
+ // ARIA attrs
411
+ const old = e.getOldData() ? list.findItem(e.getOldData(), false) : null;
412
+ if (old && old !== current) {
413
+ old.getContentElement().setAttribute("aria-selected", false);
414
+ }
415
+ if (current) {
416
+ current.getContentElement().setAttribute("aria-selected", true);
417
+ }
418
+
405
419
  // Fire event
406
420
  this.fireDataEvent("changeValue", value, e.getOldData());
407
421
  },
@@ -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
 
@@ -120,6 +123,21 @@ qx.Class.define("qx.ui.form.MenuButton",
120
123
  value.removeState("submenu");
121
124
  value.removeState("contextmenu");
122
125
  }
126
+
127
+ // ARIA attrs
128
+ const contentEl = this.getContentElement();
129
+ if (!contentEl) {
130
+ return;
131
+ }
132
+ if (value) {
133
+ contentEl.setAttribute("aria-haspopup", "menu");
134
+ contentEl.setAttribute("aria-expanded", value.isVisible());
135
+ contentEl.setAttribute("aria-controls", value.getContentElement().getAttribute("id"));
136
+ } else {
137
+ contentEl.removeAttribute("aria-haspopup");
138
+ contentEl.removeAttribute("aria-expanded");
139
+ contentEl.removeAttribute("aria-controls");
140
+ }
123
141
  },
124
142
 
125
143
 
@@ -142,6 +160,10 @@ qx.Class.define("qx.ui.form.MenuButton",
142
160
 
143
161
  if (menu)
144
162
  {
163
+ // Focus this button when the menu opens
164
+ if (this.isFocusable() && !qx.ui.core.FocusHandler.getInstance().isFocused(this)) {
165
+ this.focus();
166
+ }
145
167
  // Hide all menus first
146
168
  qx.ui.menu.Manager.getInstance().hideAll();
147
169
 
@@ -177,12 +199,15 @@ qx.Class.define("qx.ui.form.MenuButton",
177
199
  _onMenuChange : function(e)
178
200
  {
179
201
  var menu = this.getMenu();
180
-
181
- if (menu.isVisible()) {
202
+ const menuVisible = menu.isVisible()
203
+ if (menuVisible) {
182
204
  this.addState("pressed");
183
205
  } else {
184
206
  this.removeState("pressed");
185
207
  }
208
+
209
+ // ARIA attrs
210
+ this.getContentElement().setAttribute("aria-expanded", menuVisible);
186
211
  },
187
212
 
188
213
 
@@ -241,6 +266,7 @@ qx.Class.define("qx.ui.form.MenuButton",
241
266
  {
242
267
  switch(e.getKeyIdentifier())
243
268
  {
269
+ case "Space":
244
270
  case "Enter":
245
271
  this.removeState("abandoned");
246
272
  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,18 @@ qx.Class.define("qx.ui.form.SelectBox",
413
428
  {
414
429
  this.resetSelection();
415
430
  }
431
+
432
+ // Set aria-activedescendant
433
+ const contentEl = this.getContentElement();
434
+ if (!contentEl) {
435
+ return;
436
+ }
437
+ const currentContentEl = current && current[0] ? current[0].getContentElement() : null;
438
+ if (currentContentEl) {
439
+ contentEl.setAttribute("aria-activedescendant", currentContentEl.getAttribute("id"));
440
+ } else {
441
+ contentEl.removeAttribute("aria-activedescendant");
442
+ }
416
443
  },
417
444
 
418
445
  // overridden
@@ -82,6 +82,9 @@ qx.Class.define("qx.ui.form.Slider",
82
82
  // Force canvas layout
83
83
  this._setLayout(new qx.ui.layout.Canvas());
84
84
 
85
+ // ARIA attrs
86
+ this.getContentElement().setAttribute("role", "slider");
87
+
85
88
  // Add listeners
86
89
  this.addListener("keypress", this._onKeyPress);
87
90
  this.addListener("roll", this._onRoll);
@@ -1024,6 +1027,9 @@ qx.Class.define("qx.ui.form.Slider",
1024
1027
  // property apply
1025
1028
  _applyOrientation : function(value, old)
1026
1029
  {
1030
+ // ARIA attrs
1031
+ this.getContentElement().setAttribute("aria-orientation", value);
1032
+
1027
1033
  var knob = this.getChildControl("knob");
1028
1034
 
1029
1035
  // Update private flag for faster access
@@ -1077,6 +1083,9 @@ qx.Class.define("qx.ui.form.Slider",
1077
1083
  // property apply
1078
1084
  _applyValue : function(value, old) {
1079
1085
  if (value != null) {
1086
+ // ARIA attrs
1087
+ this.getContentElement().setAttribute("aria-valuenow", value);
1088
+
1080
1089
  this._updateKnobPosition();
1081
1090
  if (this.__dragMode) {
1082
1091
  this.__dragValue = [value,old];
@@ -1105,6 +1114,9 @@ qx.Class.define("qx.ui.form.Slider",
1105
1114
  // property apply
1106
1115
  _applyMinimum : function(value, old)
1107
1116
  {
1117
+ // ARIA attrs
1118
+ this.getContentElement().setAttribute("aria-valuemin", value);
1119
+
1108
1120
  if (this.getValue() < value) {
1109
1121
  this.setValue(value);
1110
1122
  }
@@ -1116,6 +1128,9 @@ qx.Class.define("qx.ui.form.Slider",
1116
1128
  // property apply
1117
1129
  _applyMaximum : function(value, old)
1118
1130
  {
1131
+ // ARIA attrs
1132
+ this.getContentElement().setAttribute("aria-valuemax", value);
1133
+
1119
1134
  if (this.getValue() > value) {
1120
1135
  this.setValue(value);
1121
1136
  }
@@ -48,6 +48,9 @@ qx.Class.define("qx.ui.form.SplitButton",
48
48
  {
49
49
  this.base(arguments);
50
50
 
51
+ // ARIA attrs
52
+ this.getContentElement().setAttribute("role", "button");
53
+
51
54
  this._setLayout(new qx.ui.layout.HBox);
52
55
 
53
56
  // Force arrow creation
@@ -65,6 +65,10 @@ qx.Class.define("qx.ui.form.ToggleButton",
65
65
  // register execute event
66
66
  this.addListener("execute", this._onExecute, this);
67
67
 
68
+ // ARIA attrs
69
+ const contentEl = this.getContentElement();
70
+ contentEl.setAttribute("role", "button");
71
+ contentEl.setAttribute("aria-pressed", false);
68
72
  },
69
73
 
70
74
 
@@ -159,13 +163,17 @@ qx.Class.define("qx.ui.form.ToggleButton",
159
163
  _applyValue : function(value, old) {
160
164
  value ? this.addState("checked") : this.removeState("checked");
161
165
 
166
+ let ariaPressed = Boolean(value);
162
167
  if (this.isTriState()) {
163
168
  if (value === null) {
169
+ ariaPressed = "mixed";
164
170
  this.addState("undetermined");
165
171
  } else if (old === null) {
166
172
  this.removeState("undetermined");
167
173
  }
168
174
  }
175
+
176
+ this.getContentElement().setAttribute("aria-pressed", ariaPressed);
169
177
  },
170
178
 
171
179
  /**