@structured-field/widget-editor 1.2.1 → 1.3.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.
- package/README.md +1 -1
- package/dist/structured-widget-editor.css +1 -1
- package/dist/structured-widget-editor.esm.js +757 -235
- package/dist/structured-widget-editor.esm.js.map +1 -1
- package/dist/structured-widget-editor.iife.js +6 -6
- package/dist/structured-widget-editor.js +5 -5
- package/dist/structured-widget-editor.js.map +1 -1
- package/package.json +3 -1
- package/src/SchemaForm.vue +20 -13
- package/src/editors/BooleanEditor.vue +19 -9
- package/src/editors/DateEditor.vue +95 -0
- package/src/editors/JsonEditor.vue +173 -0
- package/src/editors/NullableEditor.vue +1 -1
- package/src/editors/NumberEditor.vue +23 -10
- package/src/editors/ObjectEditor.vue +14 -0
- package/src/editors/RelationEditor.vue +1 -1
- package/src/editors/SchemaEditor.vue +11 -1
- package/src/editors/SelectEditor.vue +46 -10
- package/src/editors/StringEditor.vue +29 -17
- package/src/editors/UnionEditor.vue +1 -1
- package/src/index.js +1 -0
- package/src/scss/components/editors.scss +114 -0
- package/src/utils.js +18 -0
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="sf-field" :class="{ errors: fieldErrors.length }">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
v-if="
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
<span class="sf-label" :class="{ required: isRequired }">
|
|
4
|
+
{{ title }}
|
|
5
|
+
<span v-if="isNullable && isNullValue" class="sf-null-badge">null</span>
|
|
6
|
+
</span>
|
|
7
|
+
<div :class="isNullable ? 'sf-input-row' : null">
|
|
8
|
+
<textarea
|
|
9
|
+
v-if="isLong"
|
|
10
|
+
class="sf-input sf-textarea"
|
|
11
|
+
rows="3"
|
|
12
|
+
:value="isNullValue ? '' : modelValue"
|
|
13
|
+
:placeholder="isNullValue ? 'null' : (schema.placeholder || '')"
|
|
14
|
+
@input="$emit('update:modelValue', $event.target.value)"
|
|
15
|
+
/>
|
|
16
|
+
<input
|
|
17
|
+
v-else
|
|
18
|
+
type="text"
|
|
19
|
+
class="sf-input"
|
|
20
|
+
:value="isNullValue ? '' : (modelValue != null ? String(modelValue) : '')"
|
|
21
|
+
:placeholder="isNullValue ? 'null' : (schema.placeholder || '')"
|
|
22
|
+
@input="$emit('update:modelValue', $event.target.value)"
|
|
23
|
+
/>
|
|
24
|
+
<button v-if="isNullable && !isNullValue" type="button" class="sf-null-clear-btn" title="Set to null" @click="$emit('update:modelValue', null)">✕</button>
|
|
25
|
+
</div>
|
|
20
26
|
<ul v-if="fieldErrors.length" class="errorlist">
|
|
21
27
|
<li v-for="(err, i) in fieldErrors" :key="i">{{ err }}</li>
|
|
22
28
|
</ul>
|
|
@@ -47,6 +53,12 @@ export default {
|
|
|
47
53
|
isLong() {
|
|
48
54
|
return this.schema.maxLength > 255 || this.schema.format === 'textarea';
|
|
49
55
|
},
|
|
56
|
+
isNullable() {
|
|
57
|
+
return !!this.schema._nullable;
|
|
58
|
+
},
|
|
59
|
+
isNullValue() {
|
|
60
|
+
return this.modelValue === null || this.modelValue === undefined;
|
|
61
|
+
},
|
|
50
62
|
fieldErrors() {
|
|
51
63
|
if (!this.form || !this.form.getErrorsForPath) return [];
|
|
52
64
|
return this.form.getErrorsForPath(this.path);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="sf-union">
|
|
3
3
|
<div class="sf-field">
|
|
4
|
-
<
|
|
4
|
+
<span class="sf-label">{{ title }}</span>
|
|
5
5
|
<select class="sf-input sf-select" :value="currentType" @change="onTypeChange">
|
|
6
6
|
<option v-for="key in typeKeys" :key="key" :value="key">{{ humanize(key) }}</option>
|
|
7
7
|
</select>
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export { default as SchemaEditor } from './editors/SchemaEditor.vue';
|
|
|
5
5
|
export { default as StringEditor } from './editors/StringEditor.vue';
|
|
6
6
|
export { default as NumberEditor } from './editors/NumberEditor.vue';
|
|
7
7
|
export { default as BooleanEditor } from './editors/BooleanEditor.vue';
|
|
8
|
+
export { default as DateEditor } from './editors/DateEditor.vue';
|
|
8
9
|
export { default as SelectEditor } from './editors/SelectEditor.vue';
|
|
9
10
|
export { default as HiddenEditor } from './editors/HiddenEditor.vue';
|
|
10
11
|
export { default as ObjectEditor } from './editors/ObjectEditor.vue';
|
|
@@ -206,6 +206,63 @@
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
// --- Inline nullable controls ---
|
|
210
|
+
.sf-null-badge {
|
|
211
|
+
display: inline-block;
|
|
212
|
+
font-size: 0.65rem;
|
|
213
|
+
font-weight: 600;
|
|
214
|
+
text-transform: uppercase;
|
|
215
|
+
letter-spacing: 0.04em;
|
|
216
|
+
color: var(--body-quiet-color, #888);
|
|
217
|
+
background: var(--darkened-bg, #f0f0f0);
|
|
218
|
+
border: 1px solid var(--border-color, #ccc);
|
|
219
|
+
border-radius: 3px;
|
|
220
|
+
padding: 1px 5px;
|
|
221
|
+
vertical-align: middle;
|
|
222
|
+
margin-left: 4px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.sf-input-row {
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: stretch;
|
|
228
|
+
gap: 4px;
|
|
229
|
+
|
|
230
|
+
.sf-input {
|
|
231
|
+
flex: 1;
|
|
232
|
+
min-width: 0;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.sf-boolean-row {
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
gap: 6px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.sf-null-clear-btn {
|
|
243
|
+
flex-shrink: 0;
|
|
244
|
+
display: inline-flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: center;
|
|
247
|
+
width: 26px;
|
|
248
|
+
height: 26px;
|
|
249
|
+
padding: 0;
|
|
250
|
+
border: 1px solid var(--border-color, #ccc);
|
|
251
|
+
border-radius: 4px;
|
|
252
|
+
background: transparent;
|
|
253
|
+
cursor: pointer;
|
|
254
|
+
color: var(--body-quiet-color, #888);
|
|
255
|
+
font-size: 0.75rem;
|
|
256
|
+
line-height: 1;
|
|
257
|
+
align-self: center;
|
|
258
|
+
|
|
259
|
+
&:hover {
|
|
260
|
+
background: var(--error-bg, #fff2f2);
|
|
261
|
+
border-color: var(--error-fg, #ba2121);
|
|
262
|
+
color: var(--error-fg, #ba2121);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
209
266
|
// --- Nullable editor ---
|
|
210
267
|
.sf-nullable {
|
|
211
268
|
margin-bottom: 12px;
|
|
@@ -234,6 +291,63 @@
|
|
|
234
291
|
}
|
|
235
292
|
}
|
|
236
293
|
|
|
294
|
+
// --- JSON editor ---
|
|
295
|
+
.sf-field-json {
|
|
296
|
+
.sf-json-editor {
|
|
297
|
+
position: relative;
|
|
298
|
+
border: 1px solid var(--border-color, #ccc);
|
|
299
|
+
border-radius: 4px;
|
|
300
|
+
overflow: hidden;
|
|
301
|
+
|
|
302
|
+
&.sf-json-error {
|
|
303
|
+
border-color: var(--error-fg, #ba2121);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.sf-json-toolbar {
|
|
308
|
+
display: flex;
|
|
309
|
+
align-items: center;
|
|
310
|
+
justify-content: flex-end;
|
|
311
|
+
gap: 8px;
|
|
312
|
+
padding: 4px 6px;
|
|
313
|
+
background: var(--darkened-bg, #f8f8f8);
|
|
314
|
+
border-bottom: 1px solid var(--border-color, #ccc);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.sf-json-error-msg {
|
|
318
|
+
font-size: 0.72rem;
|
|
319
|
+
color: var(--error-fg, #ba2121);
|
|
320
|
+
flex: 1;
|
|
321
|
+
white-space: nowrap;
|
|
322
|
+
overflow: hidden;
|
|
323
|
+
text-overflow: ellipsis;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.sf-json-format-btn {
|
|
327
|
+
flex-shrink: 0;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.sf-json-ace-container {
|
|
331
|
+
width: 100%;
|
|
332
|
+
font-size: 0.8rem;
|
|
333
|
+
// Ace sets its own height via maxLines/minLines
|
|
334
|
+
.ace_editor {
|
|
335
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace !important;
|
|
336
|
+
font-size: 0.8rem !important;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.sf-json-textarea-fallback {
|
|
341
|
+
width: 100%;
|
|
342
|
+
min-height: 120px;
|
|
343
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
344
|
+
font-size: 0.8rem;
|
|
345
|
+
border: none;
|
|
346
|
+
border-radius: 0;
|
|
347
|
+
resize: vertical;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
237
351
|
// --- Union editor ---
|
|
238
352
|
.sf-union {
|
|
239
353
|
margin-bottom: 12px;
|
package/src/utils.js
CHANGED
|
@@ -16,6 +16,24 @@ export function deepClone(obj) {
|
|
|
16
16
|
return clone;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export function isChoiceOneOf(list) {
|
|
20
|
+
// A "choice list" oneOf: every member is a {const, title?} value option
|
|
21
|
+
// (the shape emitted for enumerated choices, e.g. metaobjects' select
|
|
22
|
+
// kind), as opposed to a oneOf of alternative sub-schemas.
|
|
23
|
+
return (
|
|
24
|
+
Array.isArray(list) &&
|
|
25
|
+
list.length > 0 &&
|
|
26
|
+
list.every(
|
|
27
|
+
(m) =>
|
|
28
|
+
m &&
|
|
29
|
+
typeof m === 'object' &&
|
|
30
|
+
'const' in m &&
|
|
31
|
+
!('properties' in m) &&
|
|
32
|
+
m.type !== 'null'
|
|
33
|
+
)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
export function getDefaultForSchema(schema) {
|
|
20
38
|
if ('default' in schema) return deepClone(schema.default);
|
|
21
39
|
if (schema.type === 'object') {
|