@json-editor/json-editor 2.13.1 → 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 (158) hide show
  1. package/CHANGELOG.md +17 -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 +418 -220
  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 +7 -2
  11. package/src/editor.js +15 -1
  12. package/src/editors/array/selectize.js +13 -0
  13. package/src/editors/array.js +16 -3
  14. package/src/editors/button.js +1 -0
  15. package/src/editors/checkbox.js +17 -1
  16. package/src/editors/info.js +1 -1
  17. package/src/editors/multiple.js +6 -1
  18. package/src/editors/multiselect.js +3 -2
  19. package/src/editors/object.js +24 -14
  20. package/src/editors/select.js +17 -5
  21. package/src/editors/starrating.js +5 -1
  22. package/src/editors/string.js +2 -2
  23. package/src/editors/table.js +3 -2
  24. package/src/theme.js +56 -4
  25. package/src/themes/barebones.js +1 -0
  26. package/src/themes/bootstrap3.js +49 -3
  27. package/src/themes/bootstrap4.js +38 -13
  28. package/src/themes/bootstrap5.js +37 -7
  29. package/src/themes/html.js +1 -0
  30. package/src/themes/spectre.js +15 -7
  31. package/src/validator.js +6 -4
  32. package/tests/codeceptjs/core_test.js +50 -0
  33. package/tests/codeceptjs/editors/array_test.js +7 -0
  34. package/tests/codeceptjs/editors/button_test.js +7 -1
  35. package/tests/codeceptjs/editors/checkbox_test.js +1 -1
  36. package/tests/codeceptjs/editors/integer_test.js +1 -1
  37. package/tests/codeceptjs/editors/multiselect_test.js +1 -1
  38. package/tests/codeceptjs/editors/number_test.js +1 -1
  39. package/tests/codeceptjs/editors/object_test.js +7 -0
  40. package/tests/codeceptjs/editors/radio_test.js +1 -2
  41. package/tests/codeceptjs/editors/rating_test.js +1 -2
  42. package/tests/codeceptjs/editors/select_test.js +1 -1
  43. package/tests/codeceptjs/editors/string_test.js +1 -1
  44. package/tests/codeceptjs/issues/issue-gh-1453_test.js +18 -0
  45. package/tests/codeceptjs/issues/issue-gh-1461_test.js +14 -0
  46. package/tests/codeceptjs/issues/issue-gh-1463_test.js +9 -0
  47. package/tests/codeceptjs/issues/issue-gh-1471_test.js +17 -0
  48. package/tests/codeceptjs/issues/issue-gh-812_test.js +2 -2
  49. package/tests/codeceptjs/meta-schema_test.js +1 -1
  50. package/tests/codeceptjs/schemaloader_test.js +1 -1
  51. package/tests/pages/advanced.html +1 -1
  52. package/tests/pages/anyof-2.html +1 -0
  53. package/tests/pages/anyof.html +4 -2
  54. package/tests/pages/array-anyof.html +5 -2
  55. package/tests/pages/array-checkboxes-infotext.html +5 -2
  56. package/tests/pages/array-checkboxes.html +5 -2
  57. package/tests/pages/array-choices.html +5 -2
  58. package/tests/pages/array-events-table.html +5 -2
  59. package/tests/pages/array-events.html +7 -3
  60. package/tests/pages/array-header-template.html +1 -0
  61. package/tests/pages/array-integers.html +5 -2
  62. package/tests/pages/array-multiselects.html +5 -2
  63. package/tests/pages/array-nested-arrays.html +5 -2
  64. package/tests/pages/array-numbers.html +5 -2
  65. package/tests/pages/array-objects.html +5 -2
  66. package/tests/pages/array-ratings.html +5 -2
  67. package/tests/pages/array-selectize-create.html +1 -0
  68. package/tests/pages/array-selectize.html +5 -2
  69. package/tests/pages/array-selects.html +5 -2
  70. package/tests/pages/array-strings.html +5 -2
  71. package/tests/pages/array-table-responsive.html +1 -0
  72. package/tests/pages/array-unique-items-sort.html +5 -2
  73. package/tests/pages/array.html +5 -2
  74. package/tests/pages/autocomplete.html +3 -1
  75. package/tests/pages/button-callbacks.html +4 -2
  76. package/tests/pages/button-icons.html +2 -1
  77. package/tests/pages/button_state_mode_1.html +1 -0
  78. package/tests/pages/button_state_mode_2.html +1 -0
  79. package/tests/pages/checkbox-labels.html +4 -2
  80. package/tests/pages/colorpicker-no-3rd-party.html +4 -2
  81. package/tests/pages/colorpicker-use-vanilla-picker.html +4 -2
  82. package/tests/pages/container-attributes.html +1 -0
  83. package/tests/pages/contains.html +1 -0
  84. package/tests/pages/core.html +5 -3
  85. package/tests/pages/datetime.html +2 -0
  86. package/tests/pages/dependentRequired.html +1 -0
  87. package/tests/pages/dependentSchemas.html +1 -0
  88. package/tests/pages/disable-button-in-object-editors.html +57 -0
  89. package/tests/pages/error-messages.html +1 -0
  90. package/tests/pages/form-name.html +4 -1
  91. package/tests/pages/grid-strict.html +3 -2
  92. package/tests/pages/grid.html +3 -2
  93. package/tests/pages/if-else.html +1 -0
  94. package/tests/pages/if-then-else-allOf.html +1 -0
  95. package/tests/pages/if-then-else-disable-fields.html +1 -0
  96. package/tests/pages/if-then-else.html +1 -0
  97. package/tests/pages/if-then.html +1 -0
  98. package/tests/pages/inheritance.html +6 -2
  99. package/tests/pages/integer.html +4 -2
  100. package/tests/pages/issues/_template.html +1 -1
  101. package/tests/pages/issues/issue-gh-1158-2.html +1 -1
  102. package/tests/pages/issues/issue-gh-1158.html +1 -1
  103. package/tests/pages/issues/issue-gh-1364.html +1 -1
  104. package/tests/pages/issues/issue-gh-1367.html +1 -1
  105. package/tests/pages/issues/issue-gh-1453.html +45 -0
  106. package/tests/pages/issues/issue-gh-1461.html +55 -0
  107. package/tests/pages/issues/issue-gh-1463.html +41 -0
  108. package/tests/pages/issues/issue-gh-1471.html +49 -0
  109. package/tests/pages/issues/issue-gh-812.html +3 -2
  110. package/tests/pages/issues/issue-gh-823-meta-schema.html +1 -1
  111. package/tests/pages/issues/issue-gh-848.html +1 -1
  112. package/tests/pages/keep_only_existing_values.html +1 -0
  113. package/tests/pages/load-events.html +1 -0
  114. package/tests/pages/maxContains.html +1 -0
  115. package/tests/pages/meta-schema.html +4 -0
  116. package/tests/pages/meta_schema.json +3 -0
  117. package/tests/pages/minContains.html +1 -0
  118. package/tests/pages/number.html +4 -2
  119. package/tests/pages/object-case-sensitive-property-search-false.html +2 -1
  120. package/tests/pages/object-case-sensitive-property-search-true.html +2 -1
  121. package/tests/pages/object-no-additional-properties.html +5 -2
  122. package/tests/pages/object-no-duplicated-id.html +2 -0
  123. package/tests/pages/object-required-properties.html +5 -3
  124. package/tests/pages/object-show-opt-in.html +3 -2
  125. package/tests/pages/object-with-dependencies-array.html +4 -2
  126. package/tests/pages/object-with-dependencies.html +2 -0
  127. package/tests/pages/object.html +5 -3
  128. package/tests/pages/oneof-2.html +1 -0
  129. package/tests/pages/oneof.html +4 -2
  130. package/tests/pages/option-dependencies.html +1 -0
  131. package/tests/pages/option-no_default_values.html +4 -2
  132. package/tests/pages/per-editor-options.html +1 -1
  133. package/tests/pages/programmatic-changes.html +4 -3
  134. package/tests/pages/range.html +4 -2
  135. package/tests/pages/read-only.html +36 -5
  136. package/tests/pages/ready.html +2 -1
  137. package/tests/pages/references.html +1 -1
  138. package/tests/pages/select.html +4 -3
  139. package/tests/pages/show-validation-errors.html +73 -0
  140. package/tests/pages/stepper-manual.html +4 -2
  141. package/tests/pages/stepper.html +4 -2
  142. package/tests/pages/string-ace-editor.html +4 -2
  143. package/tests/pages/string-cleave.html +4 -2
  144. package/tests/pages/string-custom-attributes.html +4 -2
  145. package/tests/pages/string-formats.html +4 -2
  146. package/tests/pages/string-formats2.html +4 -2
  147. package/tests/pages/string-jodit-editor.html +4 -2
  148. package/tests/pages/string-sceditor.html +4 -2
  149. package/tests/pages/string-simplemde-editor.html +4 -2
  150. package/tests/pages/table.html +4 -2
  151. package/tests/pages/tabs.html +1 -1
  152. package/tests/pages/themes.html +38 -52
  153. package/tests/pages/title-hidden.html +75 -0
  154. package/tests/pages/translate-property.html +2 -1
  155. package/tests/pages/urn.html +4 -2
  156. package/tests/pages/use-name-attributes.html +2 -1
  157. package/tests/pages/uuid.html +2 -0
  158. 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.1",
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 */
@@ -408,7 +412,8 @@ const options = {
408
412
  use_default_values: true,
409
413
  max_depth: 0,
410
414
  button_state_mode: 1,
411
- case_sensitive_property_search: true
415
+ case_sensitive_property_search: true,
416
+ show_errors: 'interaction'
412
417
  }
413
418
 
414
419
  /* This assignment was previously in index.js but makes more sense here */
package/src/editor.js CHANGED
@@ -15,6 +15,7 @@ export class AbstractEditor {
15
15
  this.original_schema = options.schema
16
16
  this.schema = this.jsoneditor.expandSchema(this.original_schema)
17
17
  this.active = true
18
+ this.isUiOnly = false
18
19
  this.options = extend({}, (this.options || {}), (this.schema.options || {}), (options.schema.options || {}), options)
19
20
 
20
21
  this.formname = this.jsoneditor.options.form_name_root || 'root'
@@ -54,6 +55,10 @@ export class AbstractEditor {
54
55
 
55
56
  register () {
56
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
+ }
57
62
  this.onChange()
58
63
  }
59
64
 
@@ -210,9 +215,12 @@ export class AbstractEditor {
210
215
  setOptInCheckbox (header) {
211
216
  /* the active/deactive checbox control. */
212
217
 
218
+ this.optInLabel = this.theme.getHiddenLabel(this.formname + ' opt-in')
219
+ this.optInLabel.setAttribute('for', this.formname + '-opt-in')
213
220
  this.optInCheckbox = document.createElement('input')
214
221
  this.optInCheckbox.setAttribute('type', 'checkbox')
215
222
  this.optInCheckbox.setAttribute('style', 'margin: 0 10px 0 0;')
223
+ this.optInCheckbox.setAttribute('id', this.formname + '-opt-in')
216
224
  this.optInCheckbox.classList.add('json-editor-opt-in')
217
225
 
218
226
  this.optInCheckbox.addEventListener('click', () => {
@@ -231,6 +239,7 @@ export class AbstractEditor {
231
239
  if (parentOptInEnabled || (!parentOptInDisabled && globalOptIn) || (!parentOptInDefined && globalOptIn)) {
232
240
  /* and control to type object editors if they are not required */
233
241
  if (this.parent && this.parent.schema.type === 'object' && !this.isRequired() && this.header) {
242
+ this.header.appendChild(this.optInLabel)
234
243
  this.header.appendChild(this.optInCheckbox)
235
244
  this.header.insertBefore(this.optInCheckbox, this.header.firstChild)
236
245
  }
@@ -252,6 +261,11 @@ export class AbstractEditor {
252
261
  this.setValue(this.getDefault(), true)
253
262
  this.updateHeaderText()
254
263
  this.onWatchedFieldChange()
264
+
265
+ if (this.options.titleHidden) {
266
+ this.theme.visuallyHidden(this.label)
267
+ this.theme.visuallyHidden(this.header)
268
+ }
255
269
  }
256
270
 
257
271
  setupWatchListeners () {
@@ -626,7 +640,7 @@ export class AbstractEditor {
626
640
  }
627
641
 
628
642
  getTitle () {
629
- return this.translateProperty(this.schema.title || this.key)
643
+ return this.translateProperty(this.schema.title || this.key || this.formname)
630
644
  }
631
645
 
632
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)
@@ -342,7 +350,12 @@ export class ArrayEditor extends AbstractEditor {
342
350
  value = this.ensureArraySize(value)
343
351
 
344
352
  const serialized = JSON.stringify(value)
345
- if (serialized === this.serialized) return
353
+ if (serialized === this.serialized) {
354
+ if (initial) {
355
+ this.refreshValue(initial)
356
+ }
357
+ return
358
+ }
346
359
 
347
360
  value.forEach((val, i) => {
348
361
  if (this.rows[i]) {
@@ -454,7 +467,7 @@ export class ArrayEditor extends AbstractEditor {
454
467
  this.value[i] = editor.getValue()
455
468
  })
456
469
 
457
- if (!this.collapsed && this.setupButtons(minItems)) {
470
+ if (this.setupButtons(minItems) && !this.collapsed) {
458
471
  this.controls.style.display = 'inline-block'
459
472
  } else {
460
473
  this.controls.style.display = 'none'
@@ -6,6 +6,7 @@ export class ButtonEditor extends AbstractEditor {
6
6
  constructor (options, defaults) {
7
7
  super(options, defaults)
8
8
  this.active = false
9
+ this.isUiOnly = true
9
10
 
10
11
  /* Set field to required in schema otherwise it will not be displayed */
11
12
  if (this.parent && this.parent.schema) {
@@ -6,6 +6,11 @@ export class CheckboxEditor extends AbstractEditor {
6
6
  const changed = this.getValue() !== value
7
7
  this.value = value
8
8
  this.input.checked = this.value
9
+
10
+ if (!initial) {
11
+ this.is_dirty = true
12
+ }
13
+
9
14
  this.onChange(changed)
10
15
  }
11
16
 
@@ -50,6 +55,7 @@ export class CheckboxEditor extends AbstractEditor {
50
55
  e.preventDefault()
51
56
  e.stopPropagation()
52
57
  this.value = e.currentTarget.checked
58
+ this.is_dirty = true
53
59
  this.onChange(true)
54
60
  })
55
61
 
@@ -77,7 +83,17 @@ export class CheckboxEditor extends AbstractEditor {
77
83
  }
78
84
 
79
85
  showValidationErrors (errors) {
80
- this.previous_error_setting = this.jsoneditor.options.show_errors
86
+ const showErrors = this.jsoneditor.options.show_errors
87
+ const changeOrInteraction = showErrors === 'change' || showErrors === 'interaction'
88
+ const never = showErrors === 'never'
89
+
90
+ if (never) {
91
+ return
92
+ }
93
+
94
+ if (changeOrInteraction && !this.is_dirty) {
95
+ return
96
+ }
81
97
 
82
98
  const addMessage = (messages, error) => {
83
99
  if (error.path === this.path) {
@@ -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) {
@@ -42,7 +42,7 @@ export class ObjectEditor extends AbstractEditor {
42
42
  super.enable()
43
43
  if (this.editors) {
44
44
  Object.values(this.editors).forEach(e => {
45
- if (e.isActive()) {
45
+ if (e.isActive() || e.isUiOnly) {
46
46
  e.enable()
47
47
  }
48
48
  e.optInCheckbox.disabled = false
@@ -60,7 +60,7 @@ export class ObjectEditor extends AbstractEditor {
60
60
  super.disable()
61
61
  if (this.editors) {
62
62
  Object.values(this.editors).forEach(e => {
63
- if (e.isActive()) {
63
+ if (e.isActive() || e.isUiOnly) {
64
64
  e.disable(alwaysDisabled)
65
65
  }
66
66
  e.optInCheckbox.disabled = true
@@ -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 () {
@@ -850,15 +867,8 @@ export class ObjectEditor extends AbstractEditor {
850
867
 
851
868
  copyJSON () {
852
869
  if (!this.editjson_holder) return
853
- const ta = document.createElement('textarea')
854
- ta.value = this.editjson_textarea.value
855
- ta.setAttribute('readonly', '')
856
- ta.style.position = 'absolute'
857
- ta.style.left = '-9999px'
858
- document.body.appendChild(ta)
859
- ta.select()
860
- document.execCommand('copy')
861
- document.body.removeChild(ta)
870
+ navigator.clipboard.writeText(this.editjson_textarea.value)
871
+ .catch((e) => window.alert(e))
862
872
  }
863
873
 
864
874
  saveJSON () {
@@ -908,7 +918,7 @@ export class ObjectEditor extends AbstractEditor {
908
918
 
909
919
  const label = this.theme.getCheckboxLabel(labelText)
910
920
 
911
- const control = this.theme.getFormControl(label, checkbox)
921
+ const control = this.theme.getFormControl(label, checkbox, null, null, this.path + '-' + key)
912
922
  control.style.paddingBottom = control.style.marginBottom = control.style.paddingTop = control.style.marginTop = 0
913
923
  control.style.height = 'auto'
914
924
  /* control.style.overflowY = 'hidden'; */
@@ -17,12 +17,14 @@ export class SelectEditor extends AbstractEditor {
17
17
 
18
18
  if (this.value === sanitized) return
19
19
 
20
- if (initial) this.is_dirty = false
21
- else if (this.jsoneditor.options.show_errors === 'change') this.is_dirty = true
22
-
23
20
  this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)]
24
21
 
25
22
  this.value = sanitized
23
+
24
+ if (!initial) {
25
+ this.is_dirty = true
26
+ }
27
+
26
28
  this.onChange()
27
29
  this.change()
28
30
  }
@@ -179,7 +181,7 @@ export class SelectEditor extends AbstractEditor {
179
181
  this.onInputChange()
180
182
  })
181
183
 
182
- 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)
183
185
  this.container.appendChild(this.control)
184
186
 
185
187
  this.value = this.enum_values[0]
@@ -345,7 +347,17 @@ export class SelectEditor extends AbstractEditor {
345
347
  }
346
348
 
347
349
  showValidationErrors (errors) {
348
- this.previous_error_setting = this.jsoneditor.options.show_errors
350
+ const showErrors = this.jsoneditor.options.show_errors
351
+ const changeOrInteraction = showErrors === 'change' || showErrors === 'interaction'
352
+ const never = showErrors === 'never'
353
+
354
+ if (never) {
355
+ return
356
+ }
357
+
358
+ if (changeOrInteraction && !this.is_dirty) {
359
+ return
360
+ }
349
361
 
350
362
  const addMessage = (messages, error) => {
351
363
  if (error.path === this.path) {
@@ -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) {