@ekzo-dev/bootstrap-addons 5.2.4 → 5.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ekzo-dev/bootstrap-addons",
3
3
  "description": "Aurelia Bootstrap additional component",
4
- "version": "5.2.4",
4
+ "version": "5.2.6",
5
5
  "homepage": "https://github.com/ekzo-dev/aurelia-components/tree/main/packages/bootstrap-addons",
6
6
  "repository": {
7
7
  "type": "git",
@@ -10,7 +10,7 @@
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@ekzo-dev/bootstrap": "^5.2.11",
13
- "@ekzo-dev/vanilla-jsoneditor": "^0.23.6",
13
+ "@ekzo-dev/vanilla-jsoneditor": "^0.23.7",
14
14
  "@ekzo-dev/toolkit": "^1.2.4",
15
15
  "@fortawesome/free-solid-svg-icons": "^6.5.2",
16
16
  "@types/json-schema": "^7.0.14",
@@ -1,12 +1,14 @@
1
1
  <template>
2
2
  <input ref="input" required.bind="inputRequired" />
3
3
  <json-editor
4
- ref="editor"
5
- json.bind="value"
4
+ ref="editorElement"
5
+ component.ref="editorComponent"
6
+ content.to-view="content"
6
7
  validator.bind="validator"
7
8
  read-only.bind="disabled"
8
9
  on-render-value.one-time="onRenderValue"
9
10
  on-render-menu.one-time="onRenderMenu"
11
+ on-change.one-time="onChange"
10
12
  mode="text"
11
13
  ...$bindables="jsonEditorOptions"
12
14
  ></json-editor>
@@ -2,27 +2,31 @@ import template from './json-input.html';
2
2
 
3
3
  import './json-input.scss';
4
4
 
5
- import { coerceBoolean } from '@ekzo-dev/toolkit';
6
- import { JsonEditor } from '@ekzo-dev/vanilla-jsoneditor';
7
- import { faUpRightAndDownLeftFromCenter } from '@fortawesome/free-solid-svg-icons/faUpRightAndDownLeftFromCenter';
8
- import Ajv, { ErrorObject, Options } from 'ajv';
9
- import Ajv2019 from 'ajv/dist/2019';
10
- import Ajv2020 from 'ajv/dist/2020';
11
- import addFormats from 'ajv-formats';
12
- import { bindable, BindingMode, customElement } from 'aurelia';
13
- import { JSONValue, parsePath } from 'immutable-json-patch';
14
- import {
5
+ import type {
6
+ Content,
7
+ JSONContent,
15
8
  JSONEditorPropsOptional,
16
9
  JSONSchema,
17
10
  JSONSchemaDefinitions,
18
11
  MenuItem,
19
12
  RenderValueComponentDescription,
20
13
  RenderValueProps,
14
+ TextContent,
21
15
  ValidationError,
22
16
  ValidationSeverity,
23
17
  Validator,
24
18
  } from 'vanilla-jsoneditor';
25
19
 
20
+ import { coerceBoolean } from '@ekzo-dev/toolkit';
21
+ import { JsonEditor } from '@ekzo-dev/vanilla-jsoneditor';
22
+ import { faUpRightAndDownLeftFromCenter } from '@fortawesome/free-solid-svg-icons/faUpRightAndDownLeftFromCenter';
23
+ import Ajv, { ErrorObject, Options } from 'ajv';
24
+ import Ajv2019 from 'ajv/dist/2019';
25
+ import Ajv2020 from 'ajv/dist/2020';
26
+ import addFormats from 'ajv-formats';
27
+ import { bindable, BindingMode, customElement } from 'aurelia';
28
+ import { JSONValue, parsePath } from 'immutable-json-patch';
29
+
26
30
  @customElement({
27
31
  name: 'bs-json-input',
28
32
  template,
@@ -47,36 +51,34 @@ export class BsJsonInput {
47
51
  @bindable()
48
52
  jsonEditorOptions: JSONEditorPropsOptional = {};
49
53
 
50
- editorModule?: typeof import('vanilla-jsoneditor');
54
+ valueCache?: unknown;
51
55
 
52
- schemaVersion?: string;
56
+ content?: JSONContent;
53
57
 
54
58
  readonly input!: HTMLInputElement;
55
59
 
56
- readonly editor!: HTMLElement;
60
+ readonly editorElement!: HTMLElement;
61
+
62
+ readonly editorComponent!: JsonEditor;
57
63
 
58
- async binding() {
59
- this.schemaVersion = this.#getSchemaVersion(this.value);
60
- this.editorModule = await import('vanilla-jsoneditor');
64
+ binding() {
65
+ this.#setContent(this.value);
61
66
  }
62
67
 
63
68
  valueChanged(value: unknown) {
64
- this.schemaVersion = this.jsonSchema === true ? this.#getSchemaVersion(value) : undefined;
65
-
66
- if (value == null || value === '') {
67
- this.input.setCustomValidity('');
68
- }
69
+ this.#setContent(value);
69
70
  }
70
71
 
71
72
  onRenderValue = (props: RenderValueProps): RenderValueComponentDescription[] => {
72
73
  let result: RenderValueComponentDescription[] | null;
73
74
  const { jsonSchema } = this;
75
+ const { editorModule } = this.editorComponent;
74
76
 
75
77
  if (jsonSchema && typeof jsonSchema === 'object') {
76
- result = this.editorModule.renderJSONSchemaEnum(props, jsonSchema, this.#getSchemaDefinitions(jsonSchema));
78
+ result = editorModule.renderJSONSchemaEnum(props, jsonSchema, this.#getSchemaDefinitions(jsonSchema));
77
79
  }
78
80
 
79
- return result ?? this.editorModule.renderValue(props);
81
+ return result ?? editorModule.renderValue(props);
80
82
  };
81
83
 
82
84
  onRenderMenu = (items: MenuItem[]): MenuItem[] | undefined => {
@@ -91,7 +93,7 @@ export class BsJsonInput {
91
93
  if (document.fullscreenElement) {
92
94
  void document.exitFullscreen();
93
95
  } else {
94
- void this.editor.requestFullscreen();
96
+ void this.editorElement.requestFullscreen();
95
97
  }
96
98
  },
97
99
  icon: faUpRightAndDownLeftFromCenter,
@@ -100,34 +102,76 @@ export class BsJsonInput {
100
102
  ];
101
103
  };
102
104
 
105
+ onChange = (content: Content): void => {
106
+ const { json, text } = content as JSONContent & TextContent;
107
+
108
+ if (json !== undefined) {
109
+ this.valueCache = json;
110
+ } else {
111
+ const message = 'Please enter a valid JSON string';
112
+
113
+ try {
114
+ this.valueCache = text === '' ? '' : (this.jsonEditorOptions?.parser ?? JSON).parse(text);
115
+
116
+ if (this.input.validationMessage === message || text === '') {
117
+ this.input.setCustomValidity('');
118
+ }
119
+ } catch (e) {
120
+ if (e instanceof SyntaxError) {
121
+ this.input.setCustomValidity(message);
122
+ } else {
123
+ throw e;
124
+ }
125
+ }
126
+ }
127
+
128
+ this.value = this.valueCache;
129
+ };
130
+
103
131
  get inputRequired(): boolean {
104
132
  return this.required && (this.value == null || this.value === '');
105
133
  }
106
134
 
135
+ get schemaVersion(): string {
136
+ if (this.jsonSchema !== true) return undefined;
137
+
138
+ const { value } = this;
139
+
140
+ return value == null || value['$schema'] == null ? '' : (value['$schema'] as string);
141
+ }
142
+
107
143
  get validator(): Validator | undefined {
108
- const { jsonSchema, schemaVersion, disabled } = this;
144
+ const { schemaVersion, disabled, ajvOptions } = this;
145
+ // use raw object because proxies don't work with private properties
109
146
  const rawThis = this['__raw__'] as BsJsonInput;
147
+ // use jsonSchema from raw object to pass original (non-proxied) object to AJV
148
+ const { jsonSchema } = rawThis;
110
149
 
111
150
  if (jsonSchema && typeof jsonSchema === 'object' && !disabled) {
112
- const ajv = rawThis.#initAjv(jsonSchema.$schema as string);
151
+ const ajv = rawThis.#initAjv(jsonSchema.$schema as string, ajvOptions);
113
152
 
114
153
  addFormats(ajv);
115
- const clonedSchema = JSON.parse(JSON.stringify(jsonSchema)) as JSONSchema;
116
- const validate = ajv.compile(clonedSchema);
154
+ const validate = ajv.compile(jsonSchema);
117
155
 
118
156
  if (validate.errors) {
119
157
  throw validate.errors[0];
120
158
  }
121
159
 
122
160
  return (json: unknown): ValidationError[] => {
161
+ // do not validate empty documents
162
+ if (json === undefined) return [];
163
+
123
164
  validate(json);
124
165
 
125
166
  return rawThis.#processErrors(validate.errors, json);
126
167
  };
127
168
  } else if (schemaVersion != null && !disabled) {
128
- const ajv = rawThis.#initAjv(schemaVersion);
169
+ const ajv = rawThis.#initAjv(schemaVersion, ajvOptions);
129
170
 
130
171
  return (json: unknown): ValidationError[] => {
172
+ // do not validate empty documents
173
+ if (json === undefined) return [];
174
+
131
175
  void ajv.validateSchema(json);
132
176
 
133
177
  return rawThis.#processErrors(ajv.errors, json);
@@ -135,11 +179,21 @@ export class BsJsonInput {
135
179
  }
136
180
  }
137
181
 
138
- #initAjv($schema: string): Ajv {
182
+ #setContent(value: unknown): void {
183
+ if (value !== this.valueCache) {
184
+ // reset validation state before assigning new content
185
+ this.input?.setCustomValidity('');
186
+
187
+ this.content = { json: value };
188
+ this.valueCache = value;
189
+ }
190
+ }
191
+
192
+ #initAjv($schema: string, ajvOptions: Options): Ajv {
139
193
  const options: Options = {
140
194
  strict: false,
141
195
  multipleOfPrecision: 2,
142
- ...this.ajvOptions,
196
+ ...ajvOptions,
143
197
  };
144
198
  let ajv: Ajv;
145
199
 
@@ -163,10 +217,6 @@ export class BsJsonInput {
163
217
  return (schema.$defs ?? schema.definitions) as JSONSchemaDefinitions;
164
218
  }
165
219
 
166
- #getSchemaVersion(value: unknown): string {
167
- return value === Object(value) && value['$schema'] ? (value['$schema'] as string) : '';
168
- }
169
-
170
220
  #processErrors(errors: ErrorObject[] | null, json: unknown): ValidationError[] {
171
221
  const message = this.jsonSchema === true ? 'JSON is not a valid JSONSchema' : 'JSON does not match schema';
172
222