Skip to content

Commit 715feff

Browse files
committed
jsoneditor
1 parent ee4ce87 commit 715feff

8 files changed

Lines changed: 260 additions & 59 deletions

File tree

docs/guide/components.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,105 @@ Renders nested object fields.
305305

306306
---
307307

308+
## JsonField
309+
310+
Renders a JSON editor for freeform object configuration.
311+
312+
### Handles
313+
314+
- `type: 'object'` with `additionalProperties` but no defined `properties`
315+
- Any field with `x-render: 'jsoneditor'`
316+
317+
### Example Schema
318+
319+
**Auto-detected (freeform object):**
320+
```typescript
321+
{
322+
type: 'object',
323+
title: 'Custom Configuration',
324+
description: 'Freeform JSON object',
325+
additionalProperties: {}
326+
}
327+
```
328+
329+
**Explicit via x-render:**
330+
```typescript
331+
{
332+
type: 'object',
333+
title: 'API Settings',
334+
description: 'Configuration in JSON format',
335+
'x-render': 'jsoneditor',
336+
'x-rows': 10 // Control textarea height
337+
}
338+
```
339+
340+
**With custom props (Quasar):**
341+
```typescript
342+
{
343+
type: 'object',
344+
title: 'Metadata',
345+
'x-render': 'jsoneditor',
346+
'x-rows': 8,
347+
'x-quasar-props': {
348+
dense: false,
349+
color: 'secondary'
350+
},
351+
'x-quickforms-quasar': {
352+
prependIcon: 'settings',
353+
iconColor: 'primary',
354+
showFormatHint: false // Hide info icon
355+
}
356+
}
357+
```
358+
359+
**Hide format hint (Vue):**
360+
```typescript
361+
{
362+
type: 'object',
363+
'x-render': 'jsoneditor',
364+
'x-show-format-hint': false // Hide info icon
365+
}
366+
```
367+
368+
### Features
369+
370+
- **Tab indentation**: Press Tab to insert 2 spaces
371+
- **Format shortcut**: Press Ctrl+Space to auto-format JSON
372+
- **Real-time validation**: Shows parse errors as you type
373+
- **Monospace font**: Better readability for JSON
374+
- **Info icon**: Hover tooltip showing format shortcut (can be hidden)
375+
- **No auto-formatting**: Only formats on initial load or manual trigger
376+
377+
### Keyboard Shortcuts
378+
379+
- **Ctrl+Space**: Format JSON with proper indentation
380+
- **Tab**: Insert 2 spaces for indentation
381+
- **Enter**: Insert new line (does not submit form)
382+
383+
### Configuration
384+
385+
**Global defaults (Quasar):**
386+
```typescript
387+
const formOptions = {
388+
componentDefaults: {
389+
jsoneditor: {
390+
dense: true,
391+
color: 'primary',
392+
rows: 12
393+
}
394+
},
395+
quickformsDefaults: {
396+
jsoneditor: {
397+
prependIcon: 'code',
398+
iconColor: 'grey-7',
399+
showFormatHint: true // Default: true
400+
}
401+
}
402+
}
403+
```
404+
405+
---
406+
308407
## ArrayField
309408

310409
Renders dynamic array fields with add/remove buttons.

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@quickflo/quickforms",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "Framework-agnostic core for QuickForms - JSON Schema form generator",
55
"type": "module",
66
"main": "./dist/index.js",

packages/quasar/dev/App.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,15 +362,20 @@ const schema: JSONSchema = {
362362
"x-rows": 8,
363363
},
364364
365-
// === JSON EDITOR (EXPLICIT) ===
365+
// === JSON EDITOR (EXPLICIT WITH CUSTOMIZATION) ===
366366
metadata: {
367367
type: "object",
368368
title: "Metadata",
369-
description: "Explicit JSON editor via x-render extension",
369+
description: "Explicit JSON editor via x-render extension with custom props",
370370
"x-render": "jsoneditor",
371371
"x-rows": 6,
372372
"x-quasar-props": {
373373
dense: false,
374+
color: "secondary",
375+
},
376+
"x-quickforms-quasar": {
377+
prependIcon: "settings",
378+
iconColor: "primary",
374379
},
375380
},
376381

packages/quasar/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@quickflo/quickforms-quasar",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "Quasar UI components for QuickForms - JSON Schema form generator",
55
"type": "module",
66
"main": "./dist/index.js",

packages/quasar/src/components/QuasarJsonField.vue

Lines changed: 104 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<script setup lang="ts">
22
import { computed, ref, watch } from 'vue';
3-
import { QInput } from 'quasar';
4-
import { useFormField, generateFieldId } from '@quickflo/quickforms-vue';
3+
import { QInput, QIcon } from 'quasar';
4+
import { useFormField, generateFieldId, useFormContext } from '@quickflo/quickforms-vue';
55
import type { FieldProps } from '@quickflo/quickforms-vue';
6+
import type { QuasarFormOptions } from '../types';
67
78
const props = withDefaults(defineProps<FieldProps>(), {
89
disabled: false,
@@ -15,6 +16,8 @@ const { value, setValue, label, hint, errorMessage, required } = useFormField(
1516
{ label: props.label }
1617
);
1718
19+
const formContext = useFormContext<QuasarFormOptions>();
20+
1821
const fieldId = generateFieldId(props.path);
1922
2023
// Local text state for JSON editing
@@ -103,11 +106,30 @@ function handleKeyDown(event: KeyboardEvent) {
103106
104107
const displayError = computed(() => parseError.value || errorMessage.value);
105108
106-
// Get Quasar-specific props
109+
// Get Quasar-specific props with defaults
107110
const quasarProps = computed(() => {
111+
const globalDefaults = formContext?.componentDefaults?.global || {};
112+
const jsonEditorDefaults = formContext?.componentDefaults?.jsoneditor || {};
108113
const xQuasarProps = (props.schema as any)['x-quasar-props'] || {};
109114
const xComponentProps = (props.schema as any)['x-component-props'] || {};
110-
return { ...xComponentProps, ...xQuasarProps };
115+
116+
// Merge in order: global defaults < jsoneditor defaults < schema-specific props
117+
return { ...globalDefaults, ...jsonEditorDefaults, ...xComponentProps, ...xQuasarProps };
118+
});
119+
120+
// Get QuickForms-specific features (icons, etc.)
121+
const quickformsFeatures = computed(() => {
122+
const globalFeatures = formContext?.quickformsDefaults?.global || {};
123+
const jsonEditorFeatures = formContext?.quickformsDefaults?.jsoneditor || {};
124+
const xQuickformsQuasar = (props.schema as any)['x-quickforms-quasar'] || {};
125+
126+
// Merge in order: global < jsoneditor < schema-specific
127+
return { ...globalFeatures, ...jsonEditorFeatures, ...xQuickformsQuasar };
128+
});
129+
130+
// Whether to show the format hint icon (default: true)
131+
const showFormatHint = computed(() => {
132+
return quickformsFeatures.value.showFormatHint !== false;
111133
});
112134
113135
// Get rows from schema or default
@@ -116,44 +138,90 @@ const rows = computed(() => {
116138
return xRows !== undefined ? xRows : 8;
117139
});
118140
119-
// Enhanced hint with format shortcut info
120-
const enhancedHint = computed(() => {
121-
const formatHint = 'Press Ctrl+Space to format';
122-
return hint.value ? `${hint.value} • ${formatHint}` : formatHint;
123-
});
124141
</script>
125142

126143
<template>
127-
<QInput
128-
:id="fieldId"
129-
:model-value="jsonText"
130-
:label="label"
131-
:hint="enhancedHint"
132-
:error="!!displayError"
133-
:error-message="displayError || undefined"
134-
:disable="disabled"
135-
:readonly="readonly"
136-
:rules="[() => !displayError || displayError]"
137-
type="textarea"
138-
:rows="rows"
139-
outlined
140-
dense
141-
class="quickform-json-field"
142-
input-class="quickform-json-editor"
143-
input-style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; font-size: 0.813rem; line-height: 1.5;"
144-
placeholder="{}"
145-
v-bind="quasarProps"
146-
@update:model-value="handleInput"
147-
@keydown="handleKeyDown"
148-
>
149-
<template v-if="required" #label>
150-
{{ label }} <span style="color: red; margin-left: 0.25rem">*</span>
151-
</template>
152-
</QInput>
144+
<div class="quickform-json-field-wrapper">
145+
<div v-if="label" class="quickform-label-header">
146+
<span class="quickform-label-text">
147+
{{ label }}
148+
<span v-if="required" style="color: red; margin-left: 0.25rem">*</span>
149+
</span>
150+
<span v-if="showFormatHint" class="quickform-info-icon" title="Press Ctrl+Space to format JSON">ⓘ</span>
151+
</div>
152+
<QInput
153+
:id="fieldId"
154+
:model-value="jsonText"
155+
:hint="hint"
156+
:error="!!displayError"
157+
:error-message="displayError || undefined"
158+
:disable="disabled"
159+
:readonly="readonly"
160+
:rules="[() => !displayError || displayError]"
161+
type="textarea"
162+
:rows="rows"
163+
outlined
164+
dense
165+
class="quickform-json-field"
166+
input-class="quickform-json-editor"
167+
input-style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; font-size: 0.813rem; line-height: 1.5;"
168+
placeholder="{}"
169+
v-bind="quasarProps"
170+
@update:model-value="handleInput"
171+
@keydown="handleKeyDown"
172+
>
173+
<template v-if="quickformsFeatures.prependIcon" #prepend>
174+
<QIcon
175+
:name="quickformsFeatures.prependIcon"
176+
:color="quickformsFeatures.iconColor || 'grey-7'"
177+
:size="quickformsFeatures.iconSize || 'sm'"
178+
/>
179+
</template>
180+
<template v-if="quickformsFeatures.appendIcon" #append>
181+
<QIcon
182+
:name="quickformsFeatures.appendIcon"
183+
:color="quickformsFeatures.iconColor || 'grey-7'"
184+
:size="quickformsFeatures.iconSize || 'sm'"
185+
/>
186+
</template>
187+
</QInput>
188+
</div>
153189
</template>
154190

155191
<style scoped>
156-
.quickform-json-field :deep(.quickform-json-editor) {
192+
.quickform-json-field-wrapper {
193+
width: 100%;
194+
}
195+
196+
.quickform-label-header {
197+
display: flex;
198+
align-items: center;
199+
gap: 0.375rem;
200+
margin-bottom: 0.5rem;
201+
}
202+
203+
.quickform-label-text {
204+
font-size: 0.875rem;
205+
font-weight: 500;
206+
color: rgba(0, 0, 0, 0.87);
207+
}
208+
209+
.quickform-info-icon {
210+
font-size: 0.875rem;
211+
color: rgba(0, 0, 0, 0.54);
212+
cursor: help;
213+
opacity: 0.7;
214+
transition: opacity 0.2s;
215+
line-height: 1;
216+
display: flex;
217+
align-items: center;
218+
}
219+
220+
.quickform-info-icon:hover {
221+
opacity: 1;
222+
}
223+
224+
.quickform-json-field-wrapper :deep(.quickform-json-editor) {
157225
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
158226
font-size: 0.813rem;
159227
line-height: 1.5;

packages/quasar/src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ export interface QuasarComponentDefaults extends VueComponentDefaults {
116116

117117
/** QExpansionItem-specific defaults (for objects) */
118118
expansion?: Partial<QExpansionItemProps>;
119+
120+
/** QInput textarea-specific defaults (for JSON editor) */
121+
jsoneditor?: Partial<QInputProps>;
119122
}
120123

121124
/**
@@ -134,6 +137,11 @@ export interface QuickFormsQuasarDefaults {
134137
datetime?: QuickFormsQuasarFeatures;
135138
/** Array-specific QuickForms features */
136139
array?: QuickFormsQuasarArrayFeatures;
140+
/** JSON editor-specific QuickForms features */
141+
jsoneditor?: QuickFormsQuasarFeatures & {
142+
/** Show the info icon with format shortcut hint. Default: true */
143+
showFormatHint?: boolean;
144+
};
137145
}
138146

139147
/**

packages/vue/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@quickflo/quickforms-vue",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "Vue 3 bindings for QuickForms - JSON Schema form generator",
55
"type": "module",
66
"main": "./dist/index.js",

0 commit comments

Comments
 (0)