@json-editor/json-editor 2.13.2 → 2.14.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 (149) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +2 -1
  3. package/dist/jsoneditor.js +1 -1
  4. package/dist/jsoneditor.js.LICENSE.txt +1 -1
  5. package/dist/nonmin/jsoneditor.js +369 -195
  6. package/dist/nonmin/jsoneditor.js.map +1 -1
  7. package/docs/imask.html +1 -1
  8. package/docs/meta_schema.json +3 -0
  9. package/package.json +1 -1
  10. package/src/defaults.js +5 -1
  11. package/src/editor.js +14 -1
  12. package/src/editors/array/selectize.js +13 -0
  13. package/src/editors/array.js +9 -1
  14. package/src/editors/info.js +1 -1
  15. package/src/editors/multiple.js +6 -1
  16. package/src/editors/multiselect.js +3 -2
  17. package/src/editors/object.js +20 -3
  18. package/src/editors/select.js +1 -1
  19. package/src/editors/starrating.js +5 -1
  20. package/src/editors/string.js +2 -2
  21. package/src/editors/table.js +3 -2
  22. package/src/theme.js +56 -4
  23. package/src/themes/barebones.js +1 -0
  24. package/src/themes/bootstrap3.js +49 -3
  25. package/src/themes/bootstrap4.js +38 -13
  26. package/src/themes/bootstrap5.js +37 -7
  27. package/src/themes/html.js +1 -0
  28. package/src/themes/spectre.js +15 -7
  29. package/src/validator.js +4 -3
  30. package/tests/codeceptjs/core_test.js +11 -0
  31. package/tests/codeceptjs/editors/array_test.js +7 -0
  32. package/tests/codeceptjs/editors/button_test.js +1 -1
  33. package/tests/codeceptjs/editors/checkbox_test.js +1 -1
  34. package/tests/codeceptjs/editors/integer_test.js +1 -1
  35. package/tests/codeceptjs/editors/multiselect_test.js +1 -1
  36. package/tests/codeceptjs/editors/number_test.js +1 -1
  37. package/tests/codeceptjs/editors/object_test.js +7 -0
  38. package/tests/codeceptjs/editors/radio_test.js +1 -2
  39. package/tests/codeceptjs/editors/rating_test.js +1 -2
  40. package/tests/codeceptjs/editors/select_test.js +1 -1
  41. package/tests/codeceptjs/editors/string_test.js +1 -1
  42. package/tests/codeceptjs/issues/issue-gh-1471_test.js +17 -0
  43. package/tests/codeceptjs/issues/issue-gh-812_test.js +2 -2
  44. package/tests/codeceptjs/meta-schema_test.js +1 -1
  45. package/tests/codeceptjs/schemaloader_test.js +1 -1
  46. package/tests/pages/advanced.html +1 -1
  47. package/tests/pages/anyof-2.html +1 -0
  48. package/tests/pages/anyof.html +4 -2
  49. package/tests/pages/array-anyof.html +5 -2
  50. package/tests/pages/array-checkboxes-infotext.html +5 -2
  51. package/tests/pages/array-checkboxes.html +5 -2
  52. package/tests/pages/array-choices.html +5 -2
  53. package/tests/pages/array-events-table.html +5 -2
  54. package/tests/pages/array-events.html +7 -3
  55. package/tests/pages/array-header-template.html +1 -0
  56. package/tests/pages/array-integers.html +5 -2
  57. package/tests/pages/array-multiselects.html +5 -2
  58. package/tests/pages/array-nested-arrays.html +5 -2
  59. package/tests/pages/array-numbers.html +5 -2
  60. package/tests/pages/array-objects.html +5 -2
  61. package/tests/pages/array-ratings.html +5 -2
  62. package/tests/pages/array-selectize-create.html +1 -0
  63. package/tests/pages/array-selectize.html +5 -2
  64. package/tests/pages/array-selects.html +5 -2
  65. package/tests/pages/array-strings.html +5 -2
  66. package/tests/pages/array-table-responsive.html +1 -0
  67. package/tests/pages/array-unique-items-sort.html +5 -2
  68. package/tests/pages/array.html +5 -2
  69. package/tests/pages/autocomplete.html +3 -1
  70. package/tests/pages/button-callbacks.html +4 -2
  71. package/tests/pages/button-icons.html +2 -1
  72. package/tests/pages/button_state_mode_1.html +1 -0
  73. package/tests/pages/button_state_mode_2.html +1 -0
  74. package/tests/pages/checkbox-labels.html +4 -2
  75. package/tests/pages/colorpicker-no-3rd-party.html +4 -2
  76. package/tests/pages/colorpicker-use-vanilla-picker.html +4 -2
  77. package/tests/pages/container-attributes.html +1 -0
  78. package/tests/pages/contains.html +1 -0
  79. package/tests/pages/core.html +5 -3
  80. package/tests/pages/datetime.html +2 -0
  81. package/tests/pages/dependentRequired.html +1 -0
  82. package/tests/pages/dependentSchemas.html +1 -0
  83. package/tests/pages/disable-button-in-object-editors.html +1 -0
  84. package/tests/pages/error-messages.html +1 -0
  85. package/tests/pages/form-name.html +4 -1
  86. package/tests/pages/grid-strict.html +3 -2
  87. package/tests/pages/grid.html +3 -2
  88. package/tests/pages/if-else.html +1 -0
  89. package/tests/pages/if-then-else-allOf.html +1 -0
  90. package/tests/pages/if-then-else-disable-fields.html +1 -0
  91. package/tests/pages/if-then-else.html +1 -0
  92. package/tests/pages/if-then.html +1 -0
  93. package/tests/pages/inheritance.html +6 -2
  94. package/tests/pages/integer.html +4 -2
  95. package/tests/pages/issues/_template.html +1 -1
  96. package/tests/pages/issues/issue-gh-1158-2.html +1 -1
  97. package/tests/pages/issues/issue-gh-1158.html +1 -1
  98. package/tests/pages/issues/issue-gh-1364.html +1 -1
  99. package/tests/pages/issues/issue-gh-1367.html +1 -1
  100. package/tests/pages/issues/issue-gh-1471.html +49 -0
  101. package/tests/pages/issues/issue-gh-812.html +3 -2
  102. package/tests/pages/issues/issue-gh-823-meta-schema.html +1 -1
  103. package/tests/pages/issues/issue-gh-848.html +1 -1
  104. package/tests/pages/keep_only_existing_values.html +1 -0
  105. package/tests/pages/load-events.html +1 -0
  106. package/tests/pages/maxContains.html +1 -0
  107. package/tests/pages/meta-schema.html +4 -0
  108. package/tests/pages/meta_schema.json +3 -0
  109. package/tests/pages/minContains.html +1 -0
  110. package/tests/pages/number.html +4 -2
  111. package/tests/pages/object-case-sensitive-property-search-false.html +2 -1
  112. package/tests/pages/object-case-sensitive-property-search-true.html +2 -1
  113. package/tests/pages/object-no-additional-properties.html +5 -2
  114. package/tests/pages/object-no-duplicated-id.html +2 -0
  115. package/tests/pages/object-required-properties.html +5 -3
  116. package/tests/pages/object-show-opt-in.html +3 -2
  117. package/tests/pages/object-with-dependencies-array.html +4 -2
  118. package/tests/pages/object-with-dependencies.html +2 -0
  119. package/tests/pages/object.html +5 -3
  120. package/tests/pages/oneof-2.html +1 -0
  121. package/tests/pages/oneof.html +4 -2
  122. package/tests/pages/option-dependencies.html +1 -0
  123. package/tests/pages/option-no_default_values.html +4 -2
  124. package/tests/pages/per-editor-options.html +1 -1
  125. package/tests/pages/programmatic-changes.html +4 -3
  126. package/tests/pages/range.html +4 -2
  127. package/tests/pages/read-only.html +36 -5
  128. package/tests/pages/ready.html +2 -1
  129. package/tests/pages/references.html +1 -1
  130. package/tests/pages/select.html +4 -3
  131. package/tests/pages/stepper-manual.html +4 -2
  132. package/tests/pages/stepper.html +4 -2
  133. package/tests/pages/string-ace-editor.html +4 -2
  134. package/tests/pages/string-cleave.html +4 -2
  135. package/tests/pages/string-custom-attributes.html +4 -2
  136. package/tests/pages/string-formats.html +4 -2
  137. package/tests/pages/string-formats2.html +4 -2
  138. package/tests/pages/string-jodit-editor.html +4 -2
  139. package/tests/pages/string-sceditor.html +4 -2
  140. package/tests/pages/string-simplemde-editor.html +4 -2
  141. package/tests/pages/table.html +4 -2
  142. package/tests/pages/tabs.html +1 -1
  143. package/tests/pages/themes.html +38 -52
  144. package/tests/pages/title-hidden.html +75 -0
  145. package/tests/pages/translate-property.html +2 -1
  146. package/tests/pages/urn.html +4 -2
  147. package/tests/pages/use-name-attributes.html +2 -1
  148. package/tests/pages/uuid.html +2 -0
  149. package/tests/pages/validation.html +2 -1
package/docs/imask.html CHANGED
@@ -54,7 +54,7 @@ var jseditor, jedata = {schema:{
54
54
  },
55
55
  "properties": {
56
56
  "_header": {
57
- "type": "info",
57
+ "format": "info",
58
58
  "title": "Example showing how to use Imask.js to format your <input/> content when you are typing.",
59
59
  "description": "<p>For documentation on the Imask.js options, look at <a href=\"https://imask.js.org/\" target=\"_blank\" title=\"Imask.js Homepage\">IMask.js</a> homepage.</p>",
60
60
  "options": {
@@ -657,6 +657,9 @@
657
657
  "default": {},
658
658
  "defaultProperties": {},
659
659
  "properties": {
660
+ "titleHidden": {
661
+ "type": "boolean"
662
+ },
660
663
  "enum_titles": {
661
664
  "type": "array",
662
665
  "uniqueItems": true,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@json-editor/json-editor",
3
3
  "title": "JSONEditor",
4
4
  "description": "JSON Schema based editor",
5
- "version": "2.13.2",
5
+ "version": "2.14.0",
6
6
  "main": "dist/jsoneditor.js",
7
7
  "author": {
8
8
  "name": "Jeremy Dorn",
package/src/defaults.js CHANGED
@@ -359,7 +359,11 @@ languages.en = {
359
359
  /**
360
360
  * Warning when deleting a node
361
361
  */
362
- button_delete_node_warning: 'Are you sure you want to remove this item?'
362
+ button_delete_node_warning: 'Are you sure you want to remove this item?',
363
+ /**
364
+ * Warning when deleting a node
365
+ */
366
+ table_controls: 'Controls'
363
367
  }
364
368
 
365
369
  /* Default per-editor options */
package/src/editor.js CHANGED
@@ -55,6 +55,10 @@ export class AbstractEditor {
55
55
 
56
56
  register () {
57
57
  this.jsoneditor.registerEditor(this)
58
+ if (this.input && !this.label) {
59
+ const ariaLabel = this.getTitle() || this.formname
60
+ this.input.setAttribute('aria-label', ariaLabel)
61
+ }
58
62
  this.onChange()
59
63
  }
60
64
 
@@ -211,9 +215,12 @@ export class AbstractEditor {
211
215
  setOptInCheckbox (header) {
212
216
  /* the active/deactive checbox control. */
213
217
 
218
+ this.optInLabel = this.theme.getHiddenLabel(this.formname + ' opt-in')
219
+ this.optInLabel.setAttribute('for', this.formname + '-opt-in')
214
220
  this.optInCheckbox = document.createElement('input')
215
221
  this.optInCheckbox.setAttribute('type', 'checkbox')
216
222
  this.optInCheckbox.setAttribute('style', 'margin: 0 10px 0 0;')
223
+ this.optInCheckbox.setAttribute('id', this.formname + '-opt-in')
217
224
  this.optInCheckbox.classList.add('json-editor-opt-in')
218
225
 
219
226
  this.optInCheckbox.addEventListener('click', () => {
@@ -232,6 +239,7 @@ export class AbstractEditor {
232
239
  if (parentOptInEnabled || (!parentOptInDisabled && globalOptIn) || (!parentOptInDefined && globalOptIn)) {
233
240
  /* and control to type object editors if they are not required */
234
241
  if (this.parent && this.parent.schema.type === 'object' && !this.isRequired() && this.header) {
242
+ this.header.appendChild(this.optInLabel)
235
243
  this.header.appendChild(this.optInCheckbox)
236
244
  this.header.insertBefore(this.optInCheckbox, this.header.firstChild)
237
245
  }
@@ -253,6 +261,11 @@ export class AbstractEditor {
253
261
  this.setValue(this.getDefault(), true)
254
262
  this.updateHeaderText()
255
263
  this.onWatchedFieldChange()
264
+
265
+ if (this.options.titleHidden) {
266
+ this.theme.visuallyHidden(this.label)
267
+ this.theme.visuallyHidden(this.header)
268
+ }
256
269
  }
257
270
 
258
271
  setupWatchListeners () {
@@ -627,7 +640,7 @@ export class AbstractEditor {
627
640
  }
628
641
 
629
642
  getTitle () {
630
- return this.translateProperty(this.schema.title || this.key)
643
+ return this.translateProperty(this.schema.title || this.key || this.formname)
631
644
  }
632
645
 
633
646
  enable () {
@@ -46,6 +46,19 @@ export class ArraySelectizeEditor extends MultiSelectEditor {
46
46
  /* Add new event handler. */
47
47
  /* Note: Must use the "on()" method and not addEventListener() */
48
48
  this.selectize_instance.on('change', this.multiselectChangeHandler)
49
+
50
+ const label = this.theme.getHiddenLabel(this.formname)
51
+ this.input.setAttribute('id', this.formname + '-hidden-input')
52
+ label.setAttribute('for', this.formname + '-hidden-input')
53
+ this.input.parentNode.insertBefore(label, this.input)
54
+
55
+ const selectizeControl = this.selectize_instance.$control[0]
56
+
57
+ if (selectizeControl) {
58
+ const selectizeLabel = this.theme.getHiddenLabel(this.formname)
59
+ selectizeLabel.setAttribute('for', this.formname + '-selectized')
60
+ selectizeControl.appendChild(selectizeLabel)
61
+ }
49
62
  }
50
63
  super.afterInputReady()
51
64
  }
@@ -93,7 +93,7 @@ export class ArrayEditor extends AbstractEditor {
93
93
 
94
94
  build () {
95
95
  if (!this.options.compact) {
96
- this.header = document.createElement('label')
96
+ this.header = document.createElement('span')
97
97
  this.header.textContent = this.getTitle()
98
98
  this.title = this.theme.getHeader(this.header, this.getPathDepth())
99
99
  this.container.appendChild(this.title)
@@ -156,6 +156,14 @@ export class ArrayEditor extends AbstractEditor {
156
156
  this.addControls()
157
157
  }
158
158
 
159
+ postBuild () {
160
+ super.postBuild()
161
+
162
+ if (this.schema.readOnly || this.schema.readonly) {
163
+ this.disable()
164
+ }
165
+ }
166
+
159
167
  onChildEditorChange (editor) {
160
168
  this.refreshValue()
161
169
  this.refreshTabs(true)
@@ -4,7 +4,7 @@ import { ButtonEditor } from './button.js'
4
4
  export class InfoEditor extends ButtonEditor {
5
5
  build () {
6
6
  this.options.compact = false
7
- this.header = this.label = this.theme.getFormInputLabel(this.getTitle())
7
+ this.header = this.label = this.theme.getLabelLike(this.getTitle())
8
8
  this.description = this.theme.getDescription(this.schema.description || '')
9
9
  this.control = this.theme.getFormControl(this.label, this.description, null)
10
10
  this.container.appendChild(this.control)
@@ -208,11 +208,16 @@ export class MultipleEditor extends AbstractEditor {
208
208
 
209
209
  build () {
210
210
  const { container } = this
211
- this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
211
+ this.header = this.label = this.theme.getLabelLike(this.getTitle(), this.isRequired())
212
212
  this.switcher = this.theme.getSwitcher(this.display_text)
213
+ this.switcher.setAttribute('id', this.formname + 'switcher')
214
+
215
+ this.switcherLabel = this.theme.getHiddenLabel(this.formname + ' switcher')
216
+ this.switcherLabel.setAttribute('for', this.formname + 'switcher')
213
217
 
214
218
  if (!this.if) {
215
219
  this.container.appendChild(this.header)
220
+ container.appendChild(this.switcherLabel)
216
221
  container.appendChild(this.switcher)
217
222
  }
218
223
 
@@ -58,7 +58,7 @@ export class MultiSelectEditor extends AbstractEditor {
58
58
 
59
59
  build () {
60
60
  let i
61
- if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
61
+ if (!this.options.compact) this.header = this.label = this.theme.getLabelLike(this.getTitle(), this.isRequired())
62
62
  if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
63
63
  if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
64
64
  if (this.options.compact) this.container.classList.add('compact')
@@ -85,6 +85,7 @@ export class MultiSelectEditor extends AbstractEditor {
85
85
  this.control = this.theme.getMultiCheckboxHolder(this.controls, this.label, this.description, this.infoButton)
86
86
  this.inputs.controlgroup = this.inputs.controls = this.control /* Enable error messages for checkboxes */
87
87
  } else {
88
+ if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
88
89
  this.input_type = 'select'
89
90
  this.input = this.theme.getSelectInput(this.option_keys, true)
90
91
  this.theme.setSelectOptions(this.input, this.option_keys, this.option_enum.map(e => e.title))
@@ -95,7 +96,7 @@ export class MultiSelectEditor extends AbstractEditor {
95
96
  this.select_options[this.option_keys[i]] = this.input.children[i]
96
97
  }
97
98
 
98
- this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton)
99
+ this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton, this.formname)
99
100
  }
100
101
 
101
102
  if (this.schema.readOnly || this.schema.readonly) {
@@ -554,7 +554,7 @@ export class ObjectEditor extends AbstractEditor {
554
554
  } else {
555
555
  this.header = ''
556
556
  if (!this.options.compact) {
557
- this.header = document.createElement('label')
557
+ this.header = document.createElement('span')
558
558
  this.header.textContent = this.getTitle()
559
559
  }
560
560
  this.title = this.theme.getHeader(this.header, this.getPathDepth())
@@ -568,7 +568,11 @@ export class ObjectEditor extends AbstractEditor {
568
568
 
569
569
  /* Edit JSON modal */
570
570
  this.editjson_holder = this.theme.getModal()
571
+ this.editjson_textarea_label = this.theme.getHiddenLabel(this.translate('button_edit_json'))
572
+ this.editjson_textarea_label.setAttribute('for', this.path + '-' + 'edit-json-textarea')
571
573
  this.editjson_textarea = this.theme.getTextareaInput()
574
+ this.editjson_textarea.setAttribute('id', this.path + '-' + 'edit-json-textarea')
575
+ this.editjson_textarea.setAttribute('aria-labelledby', this.path + '-' + 'edit-json-textarea')
572
576
  this.editjson_textarea.classList.add('je-edit-json--textarea')
573
577
  this.editjson_save = this.getButton('button_save', 'save', 'button_save')
574
578
  this.editjson_save.classList.add('json-editor-btntype-save')
@@ -591,6 +595,7 @@ export class ObjectEditor extends AbstractEditor {
591
595
  e.stopPropagation()
592
596
  this.hideEditJSON()
593
597
  })
598
+ this.editjson_holder.appendChild(this.editjson_textarea_label)
594
599
  this.editjson_holder.appendChild(this.editjson_textarea)
595
600
  this.editjson_holder.appendChild(this.editjson_save)
596
601
  this.editjson_holder.appendChild(this.editjson_copy)
@@ -605,7 +610,14 @@ export class ObjectEditor extends AbstractEditor {
605
610
 
606
611
  this.addproperty_input = this.theme.getFormInputField('text')
607
612
  this.addproperty_input.setAttribute('placeholder', 'Property name...')
613
+
614
+ this.addproperty_input_label = this.theme.getHiddenLabel(this.translate('button_properties'))
615
+ this.addproperty_input_label.setAttribute('for', this.path + '-' + 'property-selector')
616
+
608
617
  this.addproperty_input.classList.add('property-selector-input')
618
+ this.addproperty_input.setAttribute('id', this.path + '-' + 'property-selector')
619
+ this.addproperty_input.setAttribute('aria-labelledby', this.path + '-' + 'property-selector')
620
+
609
621
  this.addproperty_add.addEventListener('click', (e) => {
610
622
  e.preventDefault()
611
623
  e.stopPropagation()
@@ -623,7 +635,7 @@ export class ObjectEditor extends AbstractEditor {
623
635
  }
624
636
  })
625
637
  this.addproperty_input.addEventListener('input', (e) => {
626
- e.target.previousSibling.childNodes.forEach((value) => {
638
+ e.target.previousSibling.previousSibling.childNodes.forEach((value) => {
627
639
  let searchTerm = value.innerText
628
640
  let propertyTitle = e.target.value
629
641
 
@@ -642,6 +654,7 @@ export class ObjectEditor extends AbstractEditor {
642
654
  })
643
655
  })
644
656
  this.addproperty_holder.appendChild(this.addproperty_list)
657
+ this.addproperty_holder.appendChild(this.addproperty_input_label)
645
658
  this.addproperty_holder.appendChild(this.addproperty_input)
646
659
  this.addproperty_holder.appendChild(this.addproperty_add)
647
660
  const spacer = document.createElement('div')
@@ -802,6 +815,10 @@ export class ObjectEditor extends AbstractEditor {
802
815
  /* Do it again now that we know the approximate heights of elements */
803
816
  this.layoutEditors()
804
817
  }
818
+
819
+ if (this.schema.readOnly || this.schema.readonly) {
820
+ this.disable()
821
+ }
805
822
  }
806
823
 
807
824
  deactivateNonRequiredProperties () {
@@ -901,7 +918,7 @@ export class ObjectEditor extends AbstractEditor {
901
918
 
902
919
  const label = this.theme.getCheckboxLabel(labelText)
903
920
 
904
- const control = this.theme.getFormControl(label, checkbox)
921
+ const control = this.theme.getFormControl(label, checkbox, null, null, this.path + '-' + key)
905
922
  control.style.paddingBottom = control.style.marginBottom = control.style.paddingTop = control.style.marginTop = 0
906
923
  control.style.height = 'auto'
907
924
  /* control.style.overflowY = 'hidden'; */
@@ -181,7 +181,7 @@ export class SelectEditor extends AbstractEditor {
181
181
  this.onInputChange()
182
182
  })
183
183
 
184
- this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton)
184
+ this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton, this.formname)
185
185
  this.container.appendChild(this.control)
186
186
 
187
187
  this.value = this.enum_values[0]
@@ -3,7 +3,7 @@ import rules from './starrating.css.js'
3
3
 
4
4
  export class StarratingEditor extends StringEditor {
5
5
  build () {
6
- if (!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
6
+ if (!this.options.compact) this.header = this.label = this.theme.getLabelLike(this.getTitle(), this.isRequired())
7
7
  if (this.schema.description) this.description = this.theme.getFormInputDescription(this.translateProperty(this.schema.description))
8
8
  if (this.options.infoText) this.infoButton = this.theme.getInfoButton(this.translateProperty(this.options.infoText))
9
9
  if (this.options.compact) this.container.classList.add('compact')
@@ -47,6 +47,10 @@ export class StarratingEditor extends StringEditor {
47
47
  radioLabel.classList.add('starrating-display-enabled')
48
48
  }
49
49
 
50
+ const radioText = this.theme.getHiddenText('label')
51
+ radioText.textContent = i
52
+
53
+ radioLabel.appendChild(radioText)
50
54
  this.ratingContainer.appendChild(radioInput)
51
55
  this.ratingContainer.appendChild(radioLabel)
52
56
  }
@@ -8,7 +8,6 @@ export class StringEditor extends AbstractEditor {
8
8
  if (this.jsoneditor.options.use_name_attributes) {
9
9
  this.input.setAttribute('name', this.formname)
10
10
  }
11
- this.input.setAttribute('aria-label', this.formname)
12
11
  }
13
12
 
14
13
  unregister () {
@@ -106,7 +105,8 @@ export class StringEditor extends AbstractEditor {
106
105
  step = this.schema.multipleOf
107
106
  }
108
107
 
109
- this.input = this.theme.getRangeInput(min, max, step)
108
+ this.input = this.theme.getRangeInput(min, max, step, this.description, this.formname)
109
+ this.input.setAttribute('id', this.formname)
110
110
  /* HTML5 Input type */
111
111
  } else {
112
112
  this.input_type = 'text'
@@ -54,7 +54,7 @@ export class TableEditor extends ArrayEditor {
54
54
  this.width = tmp.getNumColumns() + 2
55
55
 
56
56
  if (!this.options.compact) {
57
- this.header = document.createElement('label')
57
+ this.header = document.createElement('span')
58
58
  this.header.textContent = this.getTitle()
59
59
  this.title = this.theme.getHeader(this.header, this.getPathDepth())
60
60
  this.container.appendChild(this.title)
@@ -101,8 +101,9 @@ export class TableEditor extends ArrayEditor {
101
101
  this.row_holder.innerHTML = ''
102
102
 
103
103
  /* Row Controls column */
104
- this.controls_header_cell = this.theme.getTableHeaderCell(' ')
104
+ this.controls_header_cell = this.theme.getTableHeaderCell(this.translate('table_controls'))
105
105
  this.controls_header_cell.setAttribute('aria-hidden', 'true')
106
+ this.controls_header_cell.style.visibility = 'hidden'
106
107
  this.header_row.appendChild(this.controls_header_cell)
107
108
 
108
109
  /* Add controls */
package/src/theme.js CHANGED
@@ -105,8 +105,15 @@ export class AbstractTheme {
105
105
  return el
106
106
  }
107
107
 
108
+ getLabelLike (text, req) {
109
+ const el = document.createElement('b')
110
+ el.appendChild(document.createTextNode(text))
111
+ if (req) el.classList.add('required')
112
+ return el
113
+ }
114
+
108
115
  getHeader (text, pathDepth) {
109
- const el = document.createElement('h3')
116
+ const el = document.createElement('span')
110
117
  if (typeof text === 'string') {
111
118
  el.textContent = text
112
119
  } else {
@@ -177,7 +184,7 @@ export class AbstractTheme {
177
184
  return el
178
185
  }
179
186
 
180
- getFormRadioControl (label, input, compact) {
187
+ getFormRadioControl (label, input, compact, formName) {
181
188
  const el = document.createElement('div')
182
189
  el.appendChild(label)
183
190
  input.style.width = 'auto'
@@ -186,6 +193,12 @@ export class AbstractTheme {
186
193
  el.classList.add('je-radio-control--compact')
187
194
  }
188
195
 
196
+ if (input.tagName.toLowerCase() !== 'div' && formName && label && input) {
197
+ input.setAttribute('id', formName)
198
+ input.setAttribute('aria-labelledby', formName)
199
+ label.setAttribute('for', formName)
200
+ }
201
+
189
202
  return el
190
203
  }
191
204
 
@@ -225,11 +238,39 @@ export class AbstractTheme {
225
238
  return el
226
239
  }
227
240
 
228
- getRangeInput (min, max, step) {
241
+ getHiddenLabel (text) {
242
+ const el = document.createElement('label')
243
+ el.textContent = text
244
+ el.setAttribute('style', 'position: absolute;width: 1px;height: 1px;padding: 0;margin: -1px;overflow: hidden;clip: rect(0,0,0,0);border: 0;')
245
+ return el
246
+ }
247
+
248
+ visuallyHidden (element) {
249
+ if (!element) {
250
+ return
251
+ }
252
+
253
+ element.setAttribute('style', 'position: absolute;width: 1px;height: 1px;padding: 0;margin: -1px;overflow: hidden;clip: rect(0,0,0,0);border: 0;')
254
+ }
255
+
256
+ getHiddenText (text) {
257
+ const el = document.createElement('span')
258
+ el.textContent = text
259
+ el.setAttribute('style', 'position: absolute;width: 1px;height: 1px;padding: 0;margin: -1px;overflow: hidden;clip: rect(0,0,0,0);border: 0;')
260
+ return el
261
+ }
262
+
263
+ getRangeInput (min, max, step, description, formName) {
229
264
  const el = this.getFormInputField('range')
230
265
  el.setAttribute('min', min)
231
266
  el.setAttribute('max', max)
232
267
  el.setAttribute('step', step)
268
+
269
+ if (description) {
270
+ description.setAttribute('id', formName + '-description')
271
+ el.setAttribute('aria-describedby', formName + '-description')
272
+ }
273
+
233
274
  return el
234
275
  }
235
276
 
@@ -297,7 +338,7 @@ export class AbstractTheme {
297
338
  return div
298
339
  }
299
340
 
300
- getRangeOutput (input, startvalue) {
341
+ getRangeOutput (input) {
301
342
  const output = document.createElement('output')
302
343
  const updateOutput = e => { output.value = e.currentTarget.value }
303
344
  input.addEventListener('change', updateOutput, false)
@@ -339,6 +380,16 @@ export class AbstractTheme {
339
380
  el.appendChild(input)
340
381
  }
341
382
 
383
+ if (input.tagName.toLowerCase() !== 'div' && input && label && formName) {
384
+ label.setAttribute('for', formName)
385
+ input.setAttribute('id', formName)
386
+ }
387
+
388
+ if (input.tagName.toLowerCase() !== 'div' && input && description) {
389
+ description.setAttribute('id', formName + '-description')
390
+ input.setAttribute('aria-describedby', formName + '-description')
391
+ }
392
+
342
393
  if (description) el.appendChild(description)
343
394
  return el
344
395
  }
@@ -455,6 +506,7 @@ export class AbstractTheme {
455
506
  }
456
507
 
457
508
  addInputError (input, text) {
509
+ input.errmsg.setAttribute('role', 'alert')
458
510
  }
459
511
 
460
512
  removeInputError (input) {
@@ -16,6 +16,7 @@ export class barebonesTheme extends AbstractTheme {
16
16
 
17
17
  input.errmsg.innerHTML = ''
18
18
  input.errmsg.appendChild(document.createTextNode(text))
19
+ input.errmsg.setAttribute('role', 'alert')
19
20
  }
20
21
 
21
22
  removeInputError (input) {
@@ -37,9 +37,9 @@ export class bootstrap3Theme extends AbstractTheme {
37
37
  return el
38
38
  }
39
39
 
40
- getRangeInput (min, max, step) {
40
+ getRangeInput (min, max, step, description, formName) {
41
41
  /* TODO: use better slider */
42
- return super.getRangeInput(min, max, step)
42
+ return super.getRangeInput(min, max, step, description, formName)
43
43
  }
44
44
 
45
45
  getFormInputField (type) {
@@ -50,7 +50,29 @@ export class bootstrap3Theme extends AbstractTheme {
50
50
  return el
51
51
  }
52
52
 
53
- getFormControl (label, input, description, infoText) {
53
+ getHiddenLabel (text) {
54
+ const el = document.createElement('label')
55
+ el.textContent = text
56
+ el.classList.add('sr-only')
57
+ return el
58
+ }
59
+
60
+ visuallyHidden (element) {
61
+ if (!element) {
62
+ return
63
+ }
64
+
65
+ element.classList.add('sr-only')
66
+ }
67
+
68
+ getHiddenText (text) {
69
+ const el = document.createElement('span')
70
+ el.textContent = text
71
+ el.classList.add('sr-only')
72
+ return el
73
+ }
74
+
75
+ getFormControl (label, input, description, infoText, formName) {
54
76
  const group = document.createElement('div')
55
77
 
56
78
  if (label && (input.type === 'checkbox' || input.type === 'radio')) {
@@ -70,6 +92,16 @@ export class bootstrap3Theme extends AbstractTheme {
70
92
 
71
93
  if (description) group.appendChild(description)
72
94
 
95
+ if (input.tagName.toLowerCase() !== 'div' && input && label && formName) {
96
+ label.setAttribute('for', formName)
97
+ input.setAttribute('id', formName)
98
+ }
99
+
100
+ if (input.tagName.toLowerCase() !== 'div' && input && description) {
101
+ description.setAttribute('id', formName + '-description')
102
+ input.setAttribute('aria-describedby', formName + '-description')
103
+ }
104
+
73
105
  return group
74
106
  }
75
107
 
@@ -118,6 +150,19 @@ export class bootstrap3Theme extends AbstractTheme {
118
150
  return el
119
151
  }
120
152
 
153
+ getHeader (text, pathDepth) {
154
+ const el = document.createElement('span')
155
+ el.classList.add('h3')
156
+
157
+ if (typeof text === 'string') {
158
+ el.textContent = text
159
+ } else {
160
+ el.appendChild(text)
161
+ }
162
+
163
+ return el
164
+ }
165
+
121
166
  getHeaderButtonHolder () {
122
167
  const el = this.getButtonHolder()
123
168
  el.style.marginLeft = '10px'
@@ -165,6 +210,7 @@ export class bootstrap3Theme extends AbstractTheme {
165
210
  }
166
211
 
167
212
  input.errmsg.textContent = text
213
+ input.errmsg.setAttribute('role', 'alert')
168
214
  }
169
215
 
170
216
  removeInputError (input) {
@@ -75,8 +75,8 @@ export class bootstrap4Theme extends AbstractTheme {
75
75
  return el
76
76
  }
77
77
 
78
- getRangeInput (min, max, step) {
79
- const el = super.getRangeInput(min, max, step)
78
+ getRangeInput (min, max, step, description, formName) {
79
+ const el = super.getRangeInput(min, max, step, description, formName)
80
80
 
81
81
  if (this.options.custom_forms === true) {
82
82
  el.classList.remove('form-control')
@@ -188,7 +188,29 @@ export class bootstrap4Theme extends AbstractTheme {
188
188
  return el
189
189
  }
190
190
 
191
- getFormControl (label, input, description, infoText) {
191
+ getHiddenLabel (text) {
192
+ const el = document.createElement('label')
193
+ el.textContent = text
194
+ el.classList.add('sr-only')
195
+ return el
196
+ }
197
+
198
+ visuallyHidden (element) {
199
+ if (!element) {
200
+ return
201
+ }
202
+
203
+ element.classList.add('sr-only')
204
+ }
205
+
206
+ getHiddenText (text) {
207
+ const el = document.createElement('span')
208
+ el.textContent = text
209
+ el.classList.add('sr-only')
210
+ return el
211
+ }
212
+
213
+ getFormControl (label, input, description, infoText, formName) {
192
214
  const group = document.createElement('div')
193
215
  group.classList.add('form-group')
194
216
 
@@ -211,10 +233,6 @@ export class bootstrap4Theme extends AbstractTheme {
211
233
  }
212
234
  }
213
235
 
214
- const unique = (Date.now() * Math.random()).toFixed(0)
215
- input.setAttribute('id', unique)
216
- label.setAttribute('for', unique)
217
-
218
236
  check.appendChild(input)
219
237
  check.appendChild(label)
220
238
  if (infoText) check.appendChild(infoText)
@@ -234,6 +252,16 @@ export class bootstrap4Theme extends AbstractTheme {
234
252
  group.appendChild(description)
235
253
  }
236
254
 
255
+ if (input.tagName.toLowerCase() !== 'div' && input && label && formName) {
256
+ label.setAttribute('for', formName)
257
+ input.setAttribute('id', formName)
258
+ }
259
+
260
+ if (input.tagName.toLowerCase() !== 'div' && input && description) {
261
+ description.setAttribute('id', formName + '-description')
262
+ input.setAttribute('aria-describedby', formName + '-description')
263
+ }
264
+
237
265
  return group
238
266
  }
239
267
 
@@ -406,10 +434,8 @@ export class bootstrap4Theme extends AbstractTheme {
406
434
  }
407
435
 
408
436
  getHeader (text, pathDepth) {
409
- /* var cardHeader = document.createElement('div') */
410
- /* cardHeader.classList.add('card-header') */
411
-
412
- const el = document.createElement('h3')
437
+ const el = document.createElement('span')
438
+ el.classList.add('h3')
413
439
  el.classList.add('card-title')
414
440
  el.classList.add('level-' + pathDepth)
415
441
 
@@ -421,8 +447,6 @@ export class bootstrap4Theme extends AbstractTheme {
421
447
 
422
448
  el.style.display = 'inline-block'
423
449
 
424
- /* cardHeader.appendChild(el) */
425
-
426
450
  return el
427
451
  }
428
452
 
@@ -500,6 +524,7 @@ export class bootstrap4Theme extends AbstractTheme {
500
524
 
501
525
  input.errmsg.style.display = 'block'
502
526
  input.errmsg.textContent = text
527
+ input.errmsg.setAttribute('role', 'alert')
503
528
  }
504
529
 
505
530
  removeInputError (input) {