@json-editor/json-editor 2.14.0 → 2.15.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 (83) hide show
  1. package/.env +4 -0
  2. package/.eslintrc +4 -1
  3. package/.github/workflows/build.yml +5 -4
  4. package/CHANGELOG.md +31 -0
  5. package/README.md +32 -1
  6. package/config/webpack.common.js +2 -6
  7. package/dist/jsoneditor.js +1 -1
  8. package/dist/jsoneditor.js.LICENSE.txt +1 -1
  9. package/dist/nonmin/jsoneditor.js +4152 -3955
  10. package/dist/nonmin/jsoneditor.js.map +1 -1
  11. package/docs/meta-schema.html +793 -0
  12. package/package.json +13 -13
  13. package/src/core.js +5 -1
  14. package/src/defaults.js +9 -2
  15. package/src/editor.js +34 -15
  16. package/src/editors/array.js +10 -7
  17. package/src/editors/base64.js +3 -0
  18. package/src/editors/describedby.js +2 -2
  19. package/src/editors/enum.js +9 -1
  20. package/src/editors/info.js +8 -0
  21. package/src/editors/multiple.js +16 -3
  22. package/src/editors/object.js +26 -7
  23. package/src/editors/radio.js +9 -2
  24. package/src/editors/select.js +19 -8
  25. package/src/editors/select2.js +1 -1
  26. package/src/editors/starrating.js +5 -4
  27. package/src/editors/string.js +17 -1
  28. package/src/editors/table.js +2 -2
  29. package/src/iconlib.js +0 -1
  30. package/src/schemaloader.js +2 -2
  31. package/src/style.css +4 -0
  32. package/src/style.css.js +1 -1
  33. package/src/templates/default.js +2 -2
  34. package/src/theme.js +13 -3
  35. package/src/themes/bootstrap3.js +0 -9
  36. package/src/themes/index.js +0 -1
  37. package/src/validator.js +4 -4
  38. package/tests/Dockerfile +1 -1
  39. package/tests/codeceptjs/core_test.js +8 -2
  40. package/tests/codeceptjs/editors/array_test.js +11 -6
  41. package/tests/codeceptjs/editors/autocomplete_test.js +0 -1
  42. package/tests/codeceptjs/editors/integer_test.js +0 -4
  43. package/tests/codeceptjs/editors/object_test.js +8 -0
  44. package/tests/codeceptjs/editors/rating_test.js +1 -1
  45. package/tests/codeceptjs/editors/select_test.js +18 -0
  46. package/tests/codeceptjs/editors/starrating_test.js +15 -0
  47. package/tests/codeceptjs/editors/string_test.js +7 -0
  48. package/tests/codeceptjs/issues/issue-gh-1158_test.js +1 -1
  49. package/tests/codeceptjs/issues/issue-gh-1164_test.js +0 -1
  50. package/tests/codeceptjs/issues/issue-gh-1171_test.js +11 -0
  51. package/tests/codeceptjs/issues/issue-gh-1272_test.js +21 -0
  52. package/tests/codeceptjs/issues/issue-gh-1383_test.js +1 -1
  53. package/tests/codeceptjs/issues/issue-gh-1452_test.js +10 -0
  54. package/tests/codeceptjs/issues/issue-gh-1485_test.js +13 -0
  55. package/tests/codeceptjs/issues/issue-gh-1491_test.js +9 -0
  56. package/tests/codeceptjs/issues/issue-gh-1525_test.js +9 -0
  57. package/tests/codeceptjs/issues/issue-gh-1536_test.js +12 -0
  58. package/tests/codeceptjs/issues/issue-gh-1538_test.js +10 -0
  59. package/tests/codeceptjs/issues/issue-gh-1541_test.js +8 -0
  60. package/tests/docker-compose-local.yml +1 -2
  61. package/tests/docker-compose.yml +0 -1
  62. package/tests/pages/array-events-table.html +39 -31
  63. package/tests/pages/array-events.html +39 -31
  64. package/tests/pages/assets/autocomplete.css +1 -0
  65. package/tests/pages/assets/autocomplete.min.js +1 -0
  66. package/tests/pages/autocomplete.html +4 -4
  67. package/tests/pages/enforce-const.html +176 -0
  68. package/tests/pages/issues/issue-gh-1171.html +39 -0
  69. package/tests/pages/issues/issue-gh-1272.html +167 -0
  70. package/tests/pages/issues/issue-gh-1452.html +98 -0
  71. package/tests/pages/issues/issue-gh-1466.html +63 -0
  72. package/tests/pages/issues/issue-gh-1485.html +59 -0
  73. package/tests/pages/issues/issue-gh-1491.html +59 -0
  74. package/tests/pages/issues/issue-gh-1525.html +62 -0
  75. package/tests/pages/issues/issue-gh-1536.html +55 -0
  76. package/tests/pages/issues/issue-gh-1538.html +56 -0
  77. package/tests/pages/issues/issue-gh-1541.html +51 -0
  78. package/tests/pages/issues/issue-gh-1541.json +9 -0
  79. package/tests/pages/placeholder-options.html +57 -0
  80. package/tests/pages/prompt-paste-max-length-reached.html +51 -0
  81. package/tests/pages/remove-false-properties.html +85 -0
  82. package/tests/pages/starrating.html +86 -0
  83. package/tests/unit/editor.spec.js +1 -1
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.14.0",
5
+ "version": "2.15.0",
6
6
  "main": "dist/jsoneditor.js",
7
7
  "author": {
8
8
  "name": "Jeremy Dorn",
@@ -38,7 +38,7 @@
38
38
  "test-headless": "karma start config/karma.conf.js --singleRun true --browsers ChromeHeadless",
39
39
  "serve-test": "http-server --p 9001",
40
40
  "docker-prepare-codeceptjs": "docker-compose run --rm node npm install && docker-compose run --rm node npm run build && docker-compose up -d && docker-compose ps",
41
- "docker-codeceptjs": "docker-compose exec node codeceptjs -c /repo/tests/codeceptjs/codecept.json run-multiple basic:chrome --invert --grep '@optional'",
41
+ "docker-codeceptjs": "docker-compose exec node codeceptjs run-multiple --config /repo/tests/codeceptjs/codecept.json basic:chrome --invert --grep '@optional'",
42
42
  "docker-test": "npm run docker-prepare-codeceptjs && npm run docker-codeceptjs",
43
43
  "preversion": "npm run test-headless && npm run docker-test",
44
44
  "postversion": "git push && git push --tags && npm publish ./ --access public",
@@ -61,15 +61,15 @@
61
61
  "@babel/runtime": "^7.20.13",
62
62
  "@webpack-cli/serve": "^2.0.1",
63
63
  "ace-builds": "^1.15.0",
64
- "babel-loader": "^8.3.0",
64
+ "babel-loader": "^9.1.3",
65
65
  "clean-webpack-plugin": "^3.0.0",
66
66
  "cleave.js": "^1.6.0",
67
- "codeceptjs": "^3.3.7",
68
- "css-loader": "^3.6.0",
67
+ "codeceptjs": "^3.6.1",
68
+ "css-loader": "^7.1.1",
69
69
  "css2json": "^1.1.1",
70
- "cssnano": "^4.1.11",
71
- "eslint": "^6.8.0",
72
- "eslint-loader": "^2.2.1",
70
+ "cssnano": "^6.1.2",
71
+ "eslint": "^8.0.0",
72
+ "eslint-webpack-plugin": "^4.1.0",
73
73
  "fast-deep-equal": "^3.1.3",
74
74
  "http-server": "^14.1.1",
75
75
  "jasmine": "^3.99.0",
@@ -83,19 +83,19 @@
83
83
  "karma-sourcemap-loader": "^0.3.8",
84
84
  "karma-webpack": "^5.0.0",
85
85
  "mathjs": "^7.5.1",
86
- "mini-css-extract-plugin": "^0.8.2",
86
+ "mini-css-extract-plugin": "^2.8.1",
87
87
  "mocha": "^10.2.0",
88
- "mochawesome": "^4.1.0",
88
+ "mochawesome": "^7.1.3",
89
89
  "postcss-loader": "^5.3.0",
90
90
  "puppeteer": "^1.20.0",
91
91
  "remove-strict-webpack-plugin": "^0.1.2",
92
- "sceditor": "^2.1.3",
92
+ "sceditor": "^3.2.0",
93
93
  "simplemde": "^1.11.2",
94
94
  "sinon": "^8.1.1",
95
- "standard": "^14.3.4",
95
+ "standard": "^17.1.0",
96
96
  "style-loader": "^1.3.0",
97
97
  "webdriverio": "^6.12.1",
98
- "webpack": "^5.75.0",
98
+ "webpack": "^5.91.0",
99
99
  "webpack-cli": "^5.0.1",
100
100
  "webpack-dev-server": "^4.11.1",
101
101
  "webpack-merge": "^4.2.2"
package/src/core.js CHANGED
@@ -234,9 +234,13 @@ export class JSONEditor {
234
234
  return new editorClass(options, JSONEditor.defaults, depthCounter)
235
235
  }
236
236
 
237
- onChange () {
237
+ onChange (eventData) {
238
238
  if (!this.ready) return
239
239
 
240
+ if (eventData) {
241
+ this.trigger(eventData.event, eventData.data)
242
+ }
243
+
240
244
  if (this.firing_change) return
241
245
  this.firing_change = true
242
246
 
package/src/defaults.js CHANGED
@@ -363,7 +363,11 @@ languages.en = {
363
363
  /**
364
364
  * Warning when deleting a node
365
365
  */
366
- table_controls: 'Controls'
366
+ table_controls: 'Controls',
367
+ /**
368
+ * Warning when paste and length exceeded maxLength
369
+ */
370
+ paste_max_length_reached: 'Pasted text exceeded maximum length of {{0}} and will be clipped.'
367
371
  }
368
372
 
369
373
  /* Default per-editor options */
@@ -413,7 +417,10 @@ const options = {
413
417
  max_depth: 0,
414
418
  button_state_mode: 1,
415
419
  case_sensitive_property_search: true,
416
- show_errors: 'interaction'
420
+ show_errors: 'interaction',
421
+ prompt_paste_max_length_reached: false,
422
+ remove_false_properties: false,
423
+ enforce_const: false
417
424
  }
418
425
 
419
426
  /* This assignment was previously in index.js but makes more sense here */
package/src/editor.js CHANGED
@@ -17,7 +17,7 @@ export class AbstractEditor {
17
17
  this.active = true
18
18
  this.isUiOnly = false
19
19
  this.options = extend({}, (this.options || {}), (this.schema.options || {}), (options.schema.options || {}), options)
20
-
20
+ this.enforceConst = this.options.enforce_const ?? this.jsoneditor.options.enforce_const
21
21
  this.formname = this.jsoneditor.options.form_name_root || 'root'
22
22
 
23
23
  if (!options.path && !this.schema.id) this.schema.id = this.formname
@@ -34,23 +34,27 @@ export class AbstractEditor {
34
34
  this.registerDependencies()
35
35
  }
36
36
 
37
- onChildEditorChange (editor) {
38
- this.onChange(true)
37
+ onChildEditorChange (editor, eventData) {
38
+ this.onChange(true, false, eventData)
39
39
  }
40
40
 
41
41
  notify () {
42
42
  if (this.path) this.jsoneditor.notifyWatchers(this.path)
43
43
  }
44
44
 
45
- change () {
46
- if (this.parent) this.parent.onChildEditorChange(this)
47
- else if (this.jsoneditor) this.jsoneditor.onChange()
45
+ change (eventData) {
46
+ if (this.parent) this.parent.onChildEditorChange(this, eventData)
47
+ else if (this.jsoneditor) this.jsoneditor.onChange(eventData)
48
48
  }
49
49
 
50
- onChange (bubble) {
50
+ onChange (bubble, fromTemplate, eventData) {
51
51
  this.notify()
52
- if (this.watch_listener) this.watch_listener()
53
- if (bubble) this.change()
52
+
53
+ if (!fromTemplate) {
54
+ if (this.watch_listener) this.watch_listener()
55
+ }
56
+
57
+ if (bubble) this.change(eventData)
54
58
  }
55
59
 
56
60
  register () {
@@ -171,7 +175,7 @@ export class AbstractEditor {
171
175
  const editor = this.jsoneditor.getEditor(path)
172
176
  const value = editor ? editor.getValue() : undefined
173
177
 
174
- if (!editor || !editor.dependenciesFulfilled) {
178
+ if (!editor || !editor.dependenciesFulfilled || !value) {
175
179
  this.dependenciesFulfilled = false
176
180
  } else if (Array.isArray(choices)) {
177
181
  this.dependenciesFulfilled = choices.some(choice => {
@@ -266,6 +270,10 @@ export class AbstractEditor {
266
270
  this.theme.visuallyHidden(this.label)
267
271
  this.theme.visuallyHidden(this.header)
268
272
  }
273
+
274
+ if (this.enforceConst && this.schema.const) {
275
+ this.disable()
276
+ }
269
277
  }
270
278
 
271
279
  setupWatchListeners () {
@@ -455,7 +463,13 @@ export class AbstractEditor {
455
463
  }
456
464
  }
457
465
 
458
- if (data.class) link.classList.add(data.class)
466
+ if (data.class) {
467
+ const classNames = data.class.split(' ')
468
+
469
+ classNames.forEach((className) => {
470
+ link.classList.add(className)
471
+ })
472
+ }
459
473
 
460
474
  return holder
461
475
  }
@@ -535,7 +549,7 @@ export class AbstractEditor {
535
549
  })
536
550
 
537
551
  // object properties
538
- if (Object.keys(this.editors).length) {
552
+ if (this.editors && Object.keys(this.editors).length) {
539
553
  vars.properties = {}
540
554
 
541
555
  Object.keys(this.editors).forEach((key) => {
@@ -545,7 +559,7 @@ export class AbstractEditor {
545
559
  const enumIndex = editor.schema.enum.indexOf(editor.value)
546
560
  const enumTitle = editor.options.enum_titles[enumIndex]
547
561
  vars.properties[key] = {
548
- enumTitle: enumTitle
562
+ enumTitle
549
563
  }
550
564
  }
551
565
  })
@@ -613,6 +627,10 @@ export class AbstractEditor {
613
627
  }
614
628
 
615
629
  getDefault () {
630
+ if (this.enforceConst && this.schema.const) {
631
+ return this.schema.const
632
+ }
633
+
616
634
  if (typeof this.schema.default !== 'undefined') {
617
635
  return this.schema.default
618
636
  }
@@ -726,13 +744,14 @@ export class AbstractEditor {
726
744
  return id.replace(/\s+/g, '-')
727
745
  }
728
746
 
729
- setInputAttributes (inputAttribute) {
747
+ setInputAttributes (inputAttribute, input) {
730
748
  if (this.schema.options && this.schema.options.inputAttributes) {
731
749
  const inputAttributes = this.schema.options.inputAttributes
732
750
  const protectedAttributes = ['name', 'type'].concat(inputAttribute)
751
+ const workingInput = input || this.input
733
752
  Object.keys(inputAttributes).forEach(key => {
734
753
  if (!protectedAttributes.includes(key.toLowerCase())) {
735
- this.input.setAttribute(key, inputAttributes[key])
754
+ workingInput.setAttribute(key, inputAttributes[key])
736
755
  }
737
756
  })
738
757
  }
@@ -12,10 +12,6 @@ export class ArrayEditor extends AbstractEditor {
12
12
  return true
13
13
  }
14
14
 
15
- getDefault () {
16
- return this.schema.default || []
17
- }
18
-
19
15
  register () {
20
16
  super.register()
21
17
  if (this.rows) {
@@ -164,10 +160,10 @@ export class ArrayEditor extends AbstractEditor {
164
160
  }
165
161
  }
166
162
 
167
- onChildEditorChange (editor) {
163
+ onChildEditorChange (editor, eventData) {
168
164
  this.refreshValue()
169
165
  this.refreshTabs(true)
170
- super.onChildEditorChange(editor)
166
+ super.onChildEditorChange(editor, eventData)
171
167
  }
172
168
 
173
169
  getItemTitle () {
@@ -283,7 +279,7 @@ export class ArrayEditor extends AbstractEditor {
283
279
  }
284
280
 
285
281
  empty (hard) {
286
- if (!this.rows) return
282
+ if (this.rows === null) return
287
283
 
288
284
  this.rows.forEach((row, i) => {
289
285
  if (hard) {
@@ -293,6 +289,12 @@ export class ArrayEditor extends AbstractEditor {
293
289
  }
294
290
  this.rows[i] = null
295
291
  })
292
+ if (hard) {
293
+ for (let j = this.rows.length; j < this.row_cache.length; j++) {
294
+ this.destroyRow(this.row_cache[j], true)
295
+ this.row_cache[j] = null
296
+ }
297
+ }
296
298
  this.rows = []
297
299
  if (hard) this.row_cache = []
298
300
  }
@@ -598,6 +600,7 @@ export class ArrayEditor extends AbstractEditor {
598
600
  this.setValue(value)
599
601
  this.refreshValue(true)
600
602
  this.onChange(true)
603
+ this.jsoneditor.trigger('copyRow', this.rows[i - 1])
601
604
  })
602
605
 
603
606
  holder.appendChild(button)
@@ -104,6 +104,9 @@ export class Base64Editor extends AbstractEditor {
104
104
  })
105
105
 
106
106
  this.control.appendChild(uploadButton)
107
+
108
+ /* Set custom attributes on input element. Parameter is array of protected keys. Empty array if none. */
109
+ this.setInputAttributes(['multiple'], uploadButton)
107
110
  }
108
111
 
109
112
  refreshPreview () {
@@ -145,12 +145,12 @@ export class DescribedByEditor extends AbstractEditor {
145
145
  this.switchEditor()
146
146
  }
147
147
 
148
- onChildEditorChange (editor) {
148
+ onChildEditorChange (editor, eventData) {
149
149
  if (this.editors[this.currentEditor]) {
150
150
  this.refreshValue()
151
151
  }
152
152
 
153
- super.onChildEditorChange(editor)
153
+ super.onChildEditorChange(editor, eventData)
154
154
  }
155
155
 
156
156
  refreshValue () {
@@ -12,7 +12,12 @@ export class EnumEditor extends AbstractEditor {
12
12
 
13
13
  this.options.enum_titles = this.options.enum_titles || []
14
14
 
15
- this.enum = this.schema.enum
15
+ if (this.enforceConst && this.schema.const) {
16
+ this.enum = [this.schema.const]
17
+ } else {
18
+ this.enum = this.schema.enum
19
+ }
20
+
16
21
  this.selected = 0
17
22
  this.select_options = []
18
23
  this.html_values = []
@@ -45,6 +50,9 @@ export class EnumEditor extends AbstractEditor {
45
50
  }
46
51
 
47
52
  refreshValue () {
53
+ if (!this.enum) {
54
+ return
55
+ }
48
56
  this.selected = -1
49
57
  const stringified = JSON.stringify(this.value)
50
58
  this.enum.forEach((el, i) => {
@@ -17,4 +17,12 @@ export class InfoEditor extends ButtonEditor {
17
17
  getNumColumns () {
18
18
  return 12
19
19
  }
20
+
21
+ disable () {
22
+ return false
23
+ }
24
+
25
+ enable () {
26
+ return false
27
+ }
20
28
  }
@@ -83,6 +83,13 @@ export class MultipleEditor extends AbstractEditor {
83
83
  }
84
84
  })
85
85
 
86
+ this.onChange(true, false, {
87
+ event: 'switch',
88
+ data: {
89
+ type: this.lastType,
90
+ path: this.editors[i].path
91
+ }
92
+ })
86
93
  this.refreshValue()
87
94
  this.refreshHeaderText()
88
95
  }
@@ -107,6 +114,10 @@ export class MultipleEditor extends AbstractEditor {
107
114
  }
108
115
  }
109
116
 
117
+ if (schema?.options?.dependencies) {
118
+ delete schema.options.dependencies
119
+ }
120
+
110
121
  const editor = this.jsoneditor.getEditorClass(schema)
111
122
 
112
123
  this.editors[i] = this.jsoneditor.createEditor(editor, {
@@ -121,7 +132,9 @@ export class MultipleEditor extends AbstractEditor {
121
132
  this.editors[i].build()
122
133
  this.editors[i].postBuild()
123
134
 
124
- if (this.editors[i].header) this.editors[i].header.style.display = 'none'
135
+ if (this.editors[i].header) {
136
+ this.theme.visuallyHidden(this.editors[i].header)
137
+ }
125
138
 
126
139
  this.editors[i].option = this.switcher_options[i]
127
140
 
@@ -264,13 +277,13 @@ export class MultipleEditor extends AbstractEditor {
264
277
  this.switchEditor(0)
265
278
  }
266
279
 
267
- onChildEditorChange (editor) {
280
+ onChildEditorChange (editor, eventData) {
268
281
  if (this.editors[this.type]) {
269
282
  this.refreshValue()
270
283
  this.refreshHeaderText()
271
284
  }
272
285
 
273
- super.onChildEditorChange()
286
+ super.onChildEditorChange(editor, eventData)
274
287
  }
275
288
 
276
289
  refreshHeaderText () {
@@ -8,10 +8,6 @@ export class ObjectEditor extends AbstractEditor {
8
8
  this.currentDepth = depth
9
9
  }
10
10
 
11
- getDefault () {
12
- return extend({}, this.schema.default || {})
13
- }
14
-
15
11
  getChildEditors () {
16
12
  return this.editors
17
13
  }
@@ -631,7 +627,14 @@ export class ObjectEditor extends AbstractEditor {
631
627
  if (this.editors[this.addproperty_input.value]) {
632
628
  this.editors[this.addproperty_input.value].disable()
633
629
  }
634
- this.onChange(true)
630
+ const key = this.editors[this.addproperty_input.value].key
631
+ const type = this.editors[this.addproperty_input.value].type
632
+ const path = this.editors[this.addproperty_input.value].path
633
+
634
+ this.onChange(true, false, {
635
+ event: 'add',
636
+ data: { key, type, path }
637
+ })
635
638
  }
636
639
  })
637
640
  this.addproperty_input.addEventListener('input', (e) => {
@@ -974,6 +977,11 @@ export class ObjectEditor extends AbstractEditor {
974
977
 
975
978
  removeObjectProperty (property) {
976
979
  if (this.editors[property]) {
980
+ // do not destroy dependent editors
981
+ if (this.editors[property].schema?.options?.dependencies) {
982
+ return
983
+ }
984
+
977
985
  this.editors[property].unregister()
978
986
  delete this.editors[property]
979
987
 
@@ -1072,9 +1080,9 @@ export class ObjectEditor extends AbstractEditor {
1072
1080
  }
1073
1081
  }
1074
1082
 
1075
- onChildEditorChange (editor) {
1083
+ onChildEditorChange (editor, eventData) {
1076
1084
  this.refreshValue()
1077
- super.onChildEditorChange(editor)
1085
+ super.onChildEditorChange(editor, eventData)
1078
1086
  }
1079
1087
 
1080
1088
  canHaveAdditionalProperties () {
@@ -1138,6 +1146,14 @@ export class ObjectEditor extends AbstractEditor {
1138
1146
  }
1139
1147
  })
1140
1148
  }
1149
+
1150
+ if (result && (this.jsoneditor.options.remove_false_properties || this.options.remove_false_properties)) {
1151
+ Object.keys(result).forEach(key => {
1152
+ if (result[key] === false) {
1153
+ delete result[key]
1154
+ }
1155
+ })
1156
+ }
1141
1157
  return result
1142
1158
  }
1143
1159
 
@@ -1286,6 +1302,9 @@ export class ObjectEditor extends AbstractEditor {
1286
1302
  this.addObjectProperty(i)
1287
1303
  editor.setValue(value[i], initial)
1288
1304
  editor.activate()
1305
+ if (this.disabled) {
1306
+ editor.disable()
1307
+ }
1289
1308
  /* Otherwise, remove value unless this is the initial set or it's required */
1290
1309
  } else if (!initial && !this.isRequiredObject(editor)) {
1291
1310
  if (this.jsoneditor.options.show_opt_in || this.options.show_opt_in) {
@@ -19,6 +19,10 @@ export class RadioEditor extends SelectEditor {
19
19
  const radioInputEventhandler = e => {
20
20
  this.setValue(e.currentTarget.value)
21
21
  this.onChange(true)
22
+
23
+ this.radioGroup.forEach((radio) => {
24
+ radio.checked = (radio.value === this.getValue())
25
+ })
22
26
  }
23
27
 
24
28
  for (let i = 0; i < this.enum_values.length; i++) {
@@ -110,10 +114,13 @@ export class RadioEditor extends SelectEditor {
110
114
  for (let i = 0; i < this.radioGroup.length; i++) {
111
115
  if (this.radioGroup[i].value === val) {
112
116
  this.radioGroup[i].checked = true
113
- this.value = val
114
- this.onChange()
115
117
  break
118
+ } else {
119
+ this.radioGroup[i].checked = false
116
120
  }
117
121
  }
122
+
123
+ this.value = val
124
+ this.onChange()
118
125
  }
119
126
  }
@@ -5,19 +5,21 @@ export class SelectEditor extends AbstractEditor {
5
5
  setValue (value, initial) {
6
6
  /* Sanitize value before setting it */
7
7
  let sanitized = this.typecast(value)
8
+ const inEnum = (this.enum_options.length > 0 && this.enum_values.includes(sanitized))
8
9
 
9
10
  const haveToUseDefaultValue = !!this.jsoneditor.options.use_default_values || typeof this.schema.default !== 'undefined'
10
11
 
11
- if (
12
- (this.enum_options.length > 0 && !this.enum_values.includes(sanitized)) ||
13
- (initial && !this.isRequired() && !haveToUseDefaultValue)
14
- ) {
12
+ if (!this.hasPlaceholderOption && (!inEnum || (initial && !this.isRequired() && !haveToUseDefaultValue))) {
15
13
  sanitized = this.enum_values[0]
16
14
  }
17
15
 
18
16
  if (this.value === sanitized) return
19
17
 
20
- this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)]
18
+ if (inEnum && this.hasPlaceholderOption) {
19
+ this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)]
20
+ } else {
21
+ this.input.value = '_placeholder_'
22
+ }
21
23
 
22
24
  this.value = sanitized
23
25
 
@@ -75,8 +77,17 @@ export class SelectEditor extends AbstractEditor {
75
77
  let i
76
78
  let callback
77
79
 
78
- /* Enum options enumerated */
79
- if (this.schema.enum) {
80
+ this.hasPlaceholderOption = this.schema?.options?.has_placeholder_option || false
81
+ this.placeholderOptionText = this.schema?.options?.placeholder_option_text || ' '
82
+
83
+ /* Const value */
84
+ if (this.enforceConst && this.schema.const) {
85
+ const value = this.schema.const
86
+ this.enum_options = [`${value}`]
87
+ this.enum_display = [`${this.translateProperty(value) || value}`]
88
+ this.enum_values = [this.typecast(value)]
89
+ /* Enum options enumerated */
90
+ } else if (this.schema.enum) {
80
91
  const display = (this.schema.options && this.schema.options.enum_titles) || []
81
92
 
82
93
  this.schema.enum.forEach((option, i) => {
@@ -165,7 +176,7 @@ export class SelectEditor extends AbstractEditor {
165
176
  if (this.options.compact) this.container.classList.add('compact')
166
177
 
167
178
  this.input = this.theme.getSelectInput(this.enum_options, false)
168
- this.theme.setSelectOptions(this.input, this.enum_options, this.enum_display)
179
+ this.theme.setSelectOptions(this.input, this.enum_options, this.enum_display, this.hasPlaceholderOption, this.placeholderOptionText)
169
180
 
170
181
  if (this.schema.readOnly || this.schema.readonly) {
171
182
  this.disable(true)
@@ -74,7 +74,7 @@ export class Select2Editor extends SelectEditor {
74
74
  /* Remove data attribute to make option tag permanent. */
75
75
  optionTag.removeAttribute('data-select2-tag')
76
76
  } else {
77
- this.input.appendChild(new Option(sanitized, sanitized, false, false)).trigger('change')
77
+ this.select2_instance.append(new Option(sanitized, sanitized, false, false)).trigger('change')
78
78
  }
79
79
 
80
80
  res = true
@@ -116,23 +116,24 @@ export class StarratingEditor extends StringEditor {
116
116
  return undefined
117
117
  }
118
118
  if (this.schema.type === 'integer') {
119
- return this.value === '' ? undefined : this.value * 1
119
+ return this.value === '' ? 0 : parseInt(this.value)
120
120
  }
121
121
  return this.value
122
122
  }
123
123
 
124
124
  setValue (value) {
125
+ this.value = value
126
+
125
127
  for (let i = 0; i < this.radioGroup.length; i++) {
126
128
  if (this.radioGroup[i].value === `${value}`) {
127
129
  this.radioGroup[i].checked = true
128
130
  this.value = value
129
-
130
131
  if (this.options.displayValue) this.displayRating.innerHTML = this.value
131
-
132
- this.onChange(true)
133
132
  break
134
133
  }
135
134
  }
135
+
136
+ super.setValue(this.value)
136
137
  }
137
138
  }
138
139
 
@@ -50,7 +50,9 @@ export class StringEditor extends AbstractEditor {
50
50
  if (this.adjust_height) this.adjust_height(this.input)
51
51
 
52
52
  /* Bubble this setValue to parents if the value changed */
53
- this.onChange(changed)
53
+ if (changed) {
54
+ this.onChange(true, fromTemplate)
55
+ }
54
56
 
55
57
  /* Return object with changed state and sanitized value for use in editors that extend this */
56
58
  return { changed, value: sanitized }
@@ -198,6 +200,20 @@ export class StringEditor extends AbstractEditor {
198
200
  this.adjust_height()
199
201
  }
200
202
 
203
+ const promptPasteMaxLengthReached = this.options.prompt_paste_max_length_reached ?? this.jsoneditor.options.prompt_paste_max_length_reached
204
+ const hasMaxLength = typeof this.schema.maxLength !== 'undefined'
205
+
206
+ if (promptPasteMaxLengthReached && hasMaxLength) {
207
+ this.input.addEventListener('paste', (event) => {
208
+ const paste = (event.clipboardData || window.clipboardData).getData('text')
209
+ const length = (paste.length + this.input.value.length)
210
+
211
+ if (length > this.schema.maxLength) {
212
+ alert(this.translate('paste_max_length_reached', [this.schema.maxLength]))
213
+ }
214
+ })
215
+ }
216
+
201
217
  if (this.format) this.input.setAttribute('data-schemaformat', this.format)
202
218
 
203
219
  let { input } = this
@@ -110,9 +110,9 @@ export class TableEditor extends ArrayEditor {
110
110
  this.addControls()
111
111
  }
112
112
 
113
- onChildEditorChange (editor) {
113
+ onChildEditorChange (editor, eventData) {
114
114
  this.refreshValue()
115
- super.onChildEditorChange()
115
+ super.onChildEditorChange(editor, eventData)
116
116
  }
117
117
 
118
118
  getItemDefault () {
package/src/iconlib.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  const defaultMapping = { collapse: '', expand: '', delete: '', edit: '', add: '', cancel: '', save: '', moveup: '', movedown: '' }
3
2
 
4
3
  export class AbstractIconLib {
@@ -261,7 +261,7 @@ export class SchemaLoader {
261
261
  */
262
262
  _manageRecursivePointer (schema, path) {
263
263
  Object.keys(schema).forEach(i => {
264
- if (schema[i].$ref && schema[i].$ref.indexOf('#') === 0) {
264
+ if (schema[i] !== null && schema[i].$ref && schema[i].$ref.indexOf('#') === 0) {
265
265
  schema[i].$ref = path + schema[i].$ref
266
266
  }
267
267
  })
@@ -334,7 +334,7 @@ export class SchemaLoader {
334
334
  }
335
335
 
336
336
  _joinUrl (url, fileBase) {
337
- var fetchUrl = url
337
+ let fetchUrl = url
338
338
 
339
339
  if (url.substr(0, 7) !== 'http://' &&
340
340
  url.substr(0, 8) !== 'https://' &&
package/src/style.css CHANGED
@@ -1,3 +1,7 @@
1
+ .table-responsive .autocomplete-result-list {
2
+ position: relative !important;
3
+ }
4
+
1
5
  .je-float-right-linkholder {
2
6
  float: right;
3
7
  margin-left: 10px;