Skip to content

Commit

Permalink
Merge branch 'refactor_schema_expression' of github.com:alibaba/formi…
Browse files Browse the repository at this point in the history
…ly into refactor_schema_expression
  • Loading branch information
janryWang committed Sep 5, 2021
2 parents f38b64c + 77c396d commit 9427f38
Show file tree
Hide file tree
Showing 3 changed files with 338 additions and 14 deletions.
88 changes: 88 additions & 0 deletions packages/vue/docs/demos/questions/scoped-slot.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template>

<script>
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/vue'
import { observer } from '@formily/reactive-vue'
// 带有作用域插槽的普通组件
const TextPreviewer = {
functional: true,
name: 'TextPreviewer',
render(h, context) {
return h('div', {}, [
context.scopedSlots.default({
scopedProp: 'default 作用域插槽',
onScopedFunc: ($event) => {
alert($event)
},
}),
])
},
}
// 响应式组件
const ObservedComponent = observer({
functional: true,
components: {
TextPreviewer,
},
render(h, context) {
return h(TextPreviewer, {
scopedSlots: {
default: (props) => context.scopedSlots.default(props),
},
})
},
})
// 作用域插槽组件
const ScopedSlotComponent = {
functional: true,
render(h, { props }) {
return h(
'div',
{
on: {
click: () => {
props.onScopedFunc('作用域插槽传递事件函数,事件发生后进行值的回传')
},
},
},
[props.scopedProp]
)
},
}
const { SchemaField } = createSchemaField({
components: {
ObservedComponent,
},
})
const schema = {
type: 'object',
properties: {
textPreview: {
type: 'string',
'x-component': 'ObservedComponent',
'x-content': {
default: ScopedSlotComponent,
},
},
},
}
export default {
components: { FormProvider, SchemaField },
data() {
return {
form: createForm(),
schema,
}
},
}
</script>
222 changes: 222 additions & 0 deletions packages/vue/src/__tests__/schema.json.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,44 @@ const Previewer2: FunctionalComponentOptions = {
},
}

const Previewer3: FunctionalComponentOptions = {
functional: true,
render(h, context) {
return h(
'div',
{
attrs: {
'data-testid': 'previewer3',
},
},
[
context.scopedSlots.default({
scopedProp: '123',
}),
]
)
},
}

const Previewer4: FunctionalComponentOptions = {
functional: true,
render(h, context) {
return h(
'div',
{
attrs: {
'data-testid': 'previewer4',
},
},
[
context.scopedSlots.content({
scopedProp: '123',
}),
]
)
},
}

describe('json schema field', () => {
test('string field', () => {
const form = createForm()
Expand Down Expand Up @@ -198,6 +236,38 @@ describe('x-content', () => {
})

test('default slot with name default', () => {
const form = createForm()
const { SchemaField } = createSchemaField({
components: {
Previewer,
},
})
const { queryByTestId } = render({
components: { SchemaField },
data() {
return {
form,
schema: new Schema({
type: 'string',
'x-component': 'Previewer',
'x-content': {
default: '123',
},
}),
}
},
template: `<FormProvider :form="form">
<SchemaField
name="string"
:schema="schema"
/>
</FormProvider>`,
})
expect(queryByTestId('previewer')).toBeVisible()
expect(queryByTestId('previewer').textContent).toEqual('123')
})

test('default slot with name default and component', () => {
const form = createForm()
const Content = {
render(h) {
Expand Down Expand Up @@ -350,6 +420,158 @@ describe('x-content', () => {
expect(queryByTestId('previewer2')).toBeVisible()
expect(queryByTestId('previewer2').textContent).toEqual('123')
})

test('scoped slot', () => {
const form = createForm()
const Content = {
functional: true,
render(h, context) {
return h('span', context.props.scopedProp)
},
}

const { SchemaField } = createSchemaField({
components: {
Previewer3,
},
})
const { queryByTestId } = render({
components: { SchemaField },
data() {
return {
form,
schema: new Schema({
type: 'string',
'x-component': 'Previewer3',
'x-content': Content,
}),
}
},
template: `<FormProvider :form="form">
<SchemaField
name="string"
:schema="schema"
/>
</FormProvider>`,
})
expect(queryByTestId('previewer3')).toBeVisible()
expect(queryByTestId('previewer3').textContent).toEqual('123')
})

test('scoped slot with scope', () => {
const form = createForm()
const Content = {
functional: true,
render(h, context) {
return h('span', context.props.scopedProp)
},
}
const { SchemaField } = createSchemaField({
components: {
Previewer3,
},
scope: {
Content,
},
})
const { queryByTestId } = render({
components: { SchemaField },
data() {
return {
form,
schema: new Schema({
type: 'string',
'x-component': 'Previewer3',
'x-content': '{{Content}}',
}),
}
},
template: `<FormProvider :form="form">
<SchemaField
name="string"
:schema="schema"
/>
</FormProvider>`,
})
expect(queryByTestId('previewer3')).toBeVisible()
expect(queryByTestId('previewer3').textContent).toEqual('123')
})

test('scoped slot with name default', () => {
const form = createForm()
const Content = {
functional: true,
render(h, context) {
return h('span', context.props.scopedProp)
},
}
const { SchemaField } = createSchemaField({
components: {
Previewer3,
},
})
const { queryByTestId } = render({
components: { SchemaField },
data() {
return {
form,
schema: new Schema({
type: 'string',
'x-component': 'Previewer3',
'x-content': {
default: Content,
},
}),
}
},
template: `<FormProvider :form="form">
<SchemaField
name="string"
:schema="schema"
/>
</FormProvider>`,
})
expect(queryByTestId('previewer3')).toBeVisible()
expect(queryByTestId('previewer3').textContent).toEqual('123')
})

test('scoped slot with name other', () => {
const form = createForm()
const Content = {
functional: true,
render(h, context) {
return h('span', context.props.scopedProp)
},
}
const { SchemaField } = createSchemaField({
components: {
Previewer4,
},
})
const { queryByTestId } = render({
components: { SchemaField },
data() {
return {
form,
schema: new Schema({
type: 'string',
'x-component': 'Previewer4',
'x-content': {
content: Content,
},
}),
}
},
template: `<FormProvider :form="form">
<SchemaField
name="string"
:schema="schema"
/>
</FormProvider>`,
})
expect(queryByTestId('previewer4')).toBeVisible()
expect(queryByTestId('previewer4').textContent).toEqual('123')
})
})

describe('scope', () => {
Expand Down
42 changes: 28 additions & 14 deletions packages/vue/src/components/ReactiveField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
IReactiveFieldProps,
VueComponent,
DefineComponent,
VueComponentProps,
} from '../types'

function isVueOptions(options: any) {
Expand All @@ -31,28 +32,41 @@ export default observer(
setup(props: IReactiveFieldProps, { slots }) {
const optionsRef = inject(SchemaOptionsSymbol, ref(null))
const key = Math.floor(Date.now() * Math.random()).toString(16)
const mergeChildren = (slots: Record<string, any>, content: any[]) => {
const mergeChildren = (slots: Record<string, any>, content: any) => {
if (!Object.keys(slots).length && !content) return {}

if (
typeof content === 'string' ||
isVueOptions(content) ||
typeof content === 'function'
) {
const defaultSlot = slots?.default
slots['default'] = () => [
...(defaultSlot ? defaultSlot(props.field, props.field.form) : []),
typeof content === 'string' ? content : h(content, {}, {}),
]
const defaultSlot = slots?.default
? slots?.default(props.field, props.field.form)
: []
if (typeof content === 'string') {
slots['default'] = () => [...defaultSlot, content]
} else if (isVueOptions(content) || typeof content === 'function') {
// scoped slot for class component
if (isVueOptions(content) && content?.render?.length > 1) {
slots['default'] = (scopedProps: VueComponentProps<any>) => [
...defaultSlot,
h(content, { props: scopedProps }, {}),
]
} else {
slots['default'] = () => [...defaultSlot, h(content, {}, {})]
}
} else if (content && typeof content === 'object') {
// for named slots
Object.keys(content).forEach((key) => {
const child = content[key]
const slot = slots?.[key]
const slot = slots?.[key] ? slots?.[key]() : []
if (typeof child === 'string') {
slots[key] = () => [...(slot ? slot() : []), child]
slots[key] = () => [...slot, child]
} else if (isVueOptions(child) || typeof child === 'function') {
slots[key] = () => [...(slot ? slot() : []), h(child, {}, {})]
// scoped slot for class component
if (isVueOptions(child) && child?.render?.length > 1) {
slots[key] = (scopedProps: VueComponentProps<any>) => [
...slot,
h(child, { props: scopedProps }, {}),
]
} else {
slots[key] = () => [...slot, h(child, {}, {})]
}
}
})
}
Expand Down

0 comments on commit 9427f38

Please sign in to comment.