From df758bd4e048c666cd7b61fb25b84487e9eb0573 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Tue, 26 Nov 2024 04:44:23 -0500 Subject: [PATCH 1/7] update demo app, fix oneOf, allow passing slots to the dispatcher renderer, use vuetify widgets for integer and number inputs --- packages/core/src/util/resolvers.ts | 3 +- .../dev/components/ExampleAppBar.vue | 2 +- .../dev/components/ExampleDrawer.vue | 18 +- .../dev/components/ExampleForm.vue | 4 +- .../dev/components/ExampleSettings.vue | 31 +- packages/vue-vuetify/dev/store/index.ts | 103 +- .../vue-vuetify/dev/views/ExampleView.vue | 218 +- packages/vue-vuetify/package.json | 2 + .../src/complex/EnumArrayRenderer.vue | 19 +- .../vue-vuetify/src/complex/OneOfRenderer.vue | 66 +- .../src/complex/OneOfTabRenderer.vue | 42 +- .../src/controls/DateTimeControlRenderer.vue | 3 +- .../src/controls/IntegerControlRenderer.vue | 60 +- .../src/controls/NumberControlRenderer.vue | 77 +- .../src/controls/TimeControlRenderer.vue | 3 +- .../src/layouts/ArrayLayoutRenderer.vue | 2 +- .../tests/unit/complex/OneOfRenderer.spec.ts | 2 +- .../__snapshots__/OneOfRenderer.spec.ts.snap | 11 +- .../IntegerControlRenderer.spec.ts.snap | 16 +- .../NumberControlRenderer.spec.ts.snap | 16 +- packages/vue/src/components/DispatchCell.vue | 7 +- .../vue/src/components/DispatchRenderer.vue | 7 +- packages/vue/src/components/JsonForms.vue | 3 +- pnpm-lock.yaml | 18789 ++++------------ 24 files changed, 5270 insertions(+), 14234 deletions(-) diff --git a/packages/core/src/util/resolvers.ts b/packages/core/src/util/resolvers.ts index a415e252df..92880f6a23 100644 --- a/packages/core/src/util/resolvers.ts +++ b/packages/core/src/util/resolvers.ts @@ -127,7 +127,8 @@ const resolveSchemaWithSegments = ( return undefined; } - if (schema.$ref) { + // use typeof because schema can by of any type - check singleSegmentResolveSchema below + if (typeof schema.$ref === 'string') { schema = resolveSchema(rootSchema, schema.$ref, rootSchema); } diff --git a/packages/vue-vuetify/dev/components/ExampleAppBar.vue b/packages/vue-vuetify/dev/components/ExampleAppBar.vue index fe05ff7e1c..8cf4612c24 100644 --- a/packages/vue-vuetify/dev/components/ExampleAppBar.vue +++ b/packages/vue-vuetify/dev/components/ExampleAppBar.vue @@ -1,6 +1,6 @@ @@ -35,19 +35,18 @@ import { useJsonFormsControl, type RendererProps, } from '@jsonforms/vue'; -import { computed, defineComponent, ref } from 'vue'; -import { VTextField } from 'vuetify/components'; +import { defineComponent } from 'vue'; +import { VNumberInput } from 'vuetify/labs/VNumberInput'; + import { useVuetifyControl } from '../util'; import { default as ControlWrapper } from './ControlWrapper.vue'; import { DisabledIconFocus } from './directives'; -const NUMBER_REGEX_TEST = /^[+-]?\d+([.]\d+)?([eE][+-]?\d+)?$/; - const controlRenderer = defineComponent({ name: 'number-control-renderer', components: { ControlWrapper, - VTextField, + VNumberInput, }, directives: { DisabledIconFocus, @@ -56,28 +55,10 @@ const controlRenderer = defineComponent({ ...rendererProps(), }, setup(props: RendererProps) { - const adaptValue = (value: any) => - typeof value === 'number' ? value : value || undefined; + const adaptValue = (value: any) => (value == null ? undefined : value); const input = useVuetifyControl(useJsonFormsControl(props), adaptValue); - const toNumberOrString = (value: string): number | string => { - // have a regex test before parseFloat to make sure that invalid input won't be ignored and will lead to errors, parseFloat will parse invalid input such 7.22m6 as 7.22 - if (NUMBER_REGEX_TEST.test(value)) { - const num = Number.parseFloat(value); - if (Number.isFinite(num)) { - // return the parsed number only if it is not NaN or Infinite - return num; - } - } - return value; - }; - - // preserve the value as it was typed by the user - for example when the user type very long number if we rely on the control.data to return back the actual data then the string could appear with exponent form and etc. - // otherwise while typing the string in the input can suddenly change - const inputValue = ref((input.control.value.data as string) || ''); - const dataValue = computed(() => toNumberOrString(inputValue.value)); - - return { ...input, adaptValue, inputValue, dataValue, toNumberOrString }; + return { ...input, adaptValue }; }, computed: { step(): number { @@ -85,42 +66,6 @@ const controlRenderer = defineComponent({ return options.step ?? 0.1; }, }, - watch: { - 'control.data': { - handler(newData) { - if (newData !== this.dataValue) { - // data was change from outside then synch our control - this.inputValue = newData; - } - }, - }, - }, - methods: { - onInputChange(value: string): void { - this.inputValue = value; - const result = this.toNumberOrString(value); - if (typeof result === 'number') { - // if user entered 5675.4444444444444444444444444444444 but the actual data is 5675.444444444444 then sync the input with what the data represents and try to preserve the format - const inputStringIsInExponentForm = - this.inputValue.includes('E') || this.inputValue.includes('e'); - - const numberAsString = inputStringIsInExponentForm - ? result.toExponential() - : result.toPrecision(); - - const numberIsInExponentForm = - numberAsString.includes('E') || numberAsString.includes('e'); - - if ( - this.inputValue !== numberAsString && - inputStringIsInExponentForm === numberIsInExponentForm // only change the input if both the user input and the string representation of the number are in the same form - ) { - this.$nextTick(() => (this.inputValue = numberAsString)); - } - } - this.onChange(result); - }, - }, }); export default controlRenderer; diff --git a/packages/vue-vuetify/src/controls/TimeControlRenderer.vue b/packages/vue-vuetify/src/controls/TimeControlRenderer.vue index b0be761d6b..fdaf707368 100644 --- a/packages/vue-vuetify/src/controls/TimeControlRenderer.vue +++ b/packages/vue-vuetify/src/controls/TimeControlRenderer.vue @@ -100,6 +100,7 @@ import { import { VTimePicker } from 'vuetify/labs/VTimePicker'; import { useLocale } from 'vuetify'; +import type { IconValue } from '../icons'; import { convertDayjsToMaskaFormat, expandLocaleFormat, @@ -196,7 +197,7 @@ const controlRenderer = defineComponent({ }; }, computed: { - pickerIcon(): string { + pickerIcon(): IconValue { return typeof this.appliedOptions.pickerIcon == 'string' ? this.appliedOptions.pickerIcon : this.icons.current.value.clock; diff --git a/packages/vue-vuetify/src/layouts/ArrayLayoutRenderer.vue b/packages/vue-vuetify/src/layouts/ArrayLayoutRenderer.vue index 81884eb137..58e0d6ef93 100644 --- a/packages/vue-vuetify/src/layouts/ArrayLayoutRenderer.vue +++ b/packages/vue-vuetify/src/layouts/ArrayLayoutRenderer.vue @@ -193,7 +193,7 @@ - No data + {{ control.translations.noDataMessage }} diff --git a/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts b/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts index 9cae6a50ec..30027fdb25 100644 --- a/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts @@ -55,7 +55,7 @@ describe('OneOfRenderer.vue', () => { it('respects translations', async () => { // trigger the selection change - wrapper.getComponent(OneOfControlRenderer).vm.handleSelectChange(); + wrapper.getComponent(OneOfControlRenderer).vm.handleSelectChange(0); // wait until the dialog is rendered await wrapper.vm.$nextTick(); // dialog is rendered in document outside of the wrapper diff --git a/packages/vue-vuetify/tests/unit/complex/__snapshots__/OneOfRenderer.spec.ts.snap b/packages/vue-vuetify/tests/unit/complex/__snapshots__/OneOfRenderer.spec.ts.snap index 608d18009d..88e49d65d6 100644 --- a/packages/vue-vuetify/tests/unit/complex/__snapshots__/OneOfRenderer.spec.ts.snap +++ b/packages/vue-vuetify/tests/unit/complex/__snapshots__/OneOfRenderer.spec.ts.snap @@ -5,10 +5,10 @@ exports[`OneOfRenderer.vue > should render component and match snapshot 1`] = `
-
+
-