FormGrid
FormGrid component, a thin wrapper around
@silver-formily/grid.
Tip
When using this component in Vue, import createGrid from @silver-formily/grid. It returns a markRaw FormGrid instance. Without markRaw, listeners such as shouldVisible can fall into infinite reactive loops.
Markup Schema Example
<script setup lang="ts">
import { createForm } from '@silver-formily/core'
import { FormGrid, FormItem, Input, Submit } from '@silver-formily/element-plus'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
const form = createForm()
const { SchemaField, SchemaVoidField, SchemaStringField } = createSchemaField({
components: {
FormItem,
Input,
FormGrid,
},
})
async function onSubmit(value: Record<string, any>) {
console.log(value)
}
</script>
<template>
<FormProvider :form="form">
<SchemaField>
<SchemaVoidField
x-component="FormGrid"
:x-component-props="{
maxColumns: 3,
minColumns: 2,
}"
>
<SchemaStringField
name="aaa"
title="aaa"
x-decorator="FormItem"
:x-decorator-props="{ 'data-grid-span': '2' }"
x-component="Input"
/>
<SchemaStringField
name="bbb"
title="bbb"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaStringField
name="ccc"
title="ccc"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaStringField
name="ddd"
title="ddd"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaStringField
name="eee"
title="eee"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaStringField
name="fff"
title="fff"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaStringField
name="ggg"
title="ggg"
x-decorator="FormItem"
x-component="Input"
/>
</SchemaVoidField>
</SchemaField>
<Submit @submit="onSubmit">
Submit
</Submit>
</FormProvider>
</template>aaa:
bbb:
ccc:
ddd:
eee:
fff:
ggg:
查看源码
JSON Schema Example
<script setup lang="ts">
import { createForm } from '@silver-formily/core'
import { FormGrid, FormItem, Input, Submit } from '@silver-formily/element-plus'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
const schema = {
type: 'object',
properties: {
grid: {
'type': 'void',
'x-component': 'FormGrid',
'x-component-props': {
minColumns: [4, 6, 10],
},
'properties': {
aaa: {
'type': 'string',
'title': 'AAA',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
bbb: {
'type': 'string',
'title': 'BBB',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
ccc: {
'type': 'string',
'title': 'CCC',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
ddd: {
'type': 'string',
'title': 'DDD',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
eee: {
'type': 'string',
'title': 'EEE',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
fff: {
'type': 'string',
'title': 'FFF',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
ggg: {
'type': 'string',
'title': 'GGG',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
}
const form = createForm()
const { SchemaField } = createSchemaField({
components: {
FormItem,
Input,
FormGrid,
},
})
async function onSubmit(value: Record<string, any>) {
console.log(value)
}
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
<Submit @submit="onSubmit">
Submit
</Submit>
</FormProvider>
</template>AAA:
BBB:
CCC:
DDD:
EEE:
FFF:
GGG:
查看源码
Native Example
<script setup lang="tsx">
import { FormGrid } from '@silver-formily/element-plus'
function Cell(_props, { slots }) {
return (
<div
style={{
backgroundColor: '#AAA',
color: '#FFF',
height: '30px',
display: 'flex',
alignItems: 'center',
padding: '0 10px',
}}
>
{slots?.default()}
</div>
)
}
const FormGridColumn = FormGrid.GridColumn
</script>
<template>
<div>
<p>maxColumns 3 + minColumns 2 (stable)</p>
<FormGrid :max-columns="3" :min-columns="2" :column-gap="4">
<FormGridColumn :grid-span="3">
<Cell>1</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>2</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>3</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>4</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>5</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>6</Cell>
</FormGridColumn>
</FormGrid>
<p>maxColumns 3 (stable)</p>
<FormGrid :max-columns="3" :column-gap="4">
<FormGridColumn :grid-span="2">
<Cell>1</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>2</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>3</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>4</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>5</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>6</Cell>
</FormGridColumn>
</FormGrid>
<p>minColumns 2 + maxColumns 2 (stable)</p>
<FormGrid :min-columns="2" :max-columns="2" :column-gap="4">
<FormGridColumn :grid-span="2">
<Cell>1</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>2</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>3</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>4</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>5</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>6</Cell>
</FormGridColumn>
</FormGrid>
<p>Null</p>
<FormGrid :column-gap="4">
<FormGridColumn :grid-span="2">
<Cell>1</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>2</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>3</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>4</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>5</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>6</Cell>
</FormGridColumn>
</FormGrid>
<p>minWidth 150 +maxColumns 3</p>
<FormGrid :min-width="150" :max-columns="3" :column-gap="4">
<FormGridColumn :grid-span="2">
<Cell>1</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>2</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>3</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>4</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>5</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>6</Cell>
</FormGridColumn>
</FormGrid>
<p>maxWidth 120+minColumns 2</p>
<FormGrid :max-width="120" :min-columns="2" :column-gap="4">
<FormGridColumn :grid-span="2">
<Cell>1</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>2</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>3</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>4</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>5</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>6</Cell>
</FormGridColumn>
</FormGrid>
<p>maxWidth 120 + gridSpan -1</p>
<FormGrid :max-width="120" :column-gap="4">
<FormGridColumn :grid-span="2">
<Cell>1</Cell>
</FormGridColumn>
<FormGridColumn>
<Cell>2</Cell>
</FormGridColumn>
<FormGridColumn :grid-span="-1">
<Cell>3</Cell>
</FormGridColumn>
</FormGrid>
</div>
</template>maxColumns 3 + minColumns 2 (stable)
1
2
3
4
5
6
maxColumns 3 (stable)
1
2
3
4
5
6
minColumns 2 + maxColumns 2 (stable)
1
2
3
4
5
6
Null
1
2
3
4
5
6
minWidth 150 +maxColumns 3
1
2
3
4
5
6
maxWidth 120+minColumns 2
1
2
3
4
5
6
maxWidth 120 + gridSpan -1
1
2
3
查看源码
Query Form Implementation Example
<script setup lang="tsx">
import { createForm } from '@silver-formily/core'
import {
DatePicker,
Form,
FormButtonGroup,
FormGrid,
FormItem,
Input,
Reset,
Select,
Submit,
} from '@silver-formily/element-plus'
import { Grid } from '@silver-formily/grid'
import { autorun } from '@silver-formily/reactive'
import { observer } from '@silver-formily/reactive-vue'
import {
createSchemaField,
FormProvider,
} from '@silver-formily/vue'
import { defineComponent, onUnmounted, ref } from 'vue'
function useCollapseGrid(maxRows: number) {
const grid = new Grid({
maxColumns: 4,
maxWidth: 240,
maxRows,
shouldVisible: (node, grid) => {
if (node.index === grid.childSize - 1)
return true
if (grid.maxRows === Infinity)
return true
return node.shadowRow ? node.shadowRow < maxRows + 1 : true
},
})
const expanded = ref(false)
const type = ref('')
const takeType = (realRows: number, computeRows: number) => {
if (realRows < maxRows + 1)
return 'incomplete-wrap'
if (computeRows > maxRows)
return 'collapsible'
return 'complete-wrap'
}
const dispose = autorun(() => {
expanded.value = grid.maxRows === Infinity
const realRows = grid.shadowRows
const computeRows = grid.fullnessLastColumn
? grid.shadowRows - 1
: grid.shadowRows
type.value = takeType(realRows, computeRows)
})
onUnmounted(dispose)
const toggle = () => {
grid.maxRows = grid.maxRows === Infinity ? maxRows : Infinity
}
return {
grid,
expanded,
toggle,
type,
}
}
const QueryForm = observer(
defineComponent({
setup(props, { slots }) {
const { grid, expanded, toggle, type } = useCollapseGrid(1)
const renderActions = () => {
return (
<>
<Submit onSubmit={console.log}>Search</Submit>
<Reset>Reset</Reset>
</>
)
}
const renderButtonGroup = () => {
if (type.value === 'incomplete-wrap') {
return (
<FormButtonGroup.FormItem>
<FormButtonGroup>{renderActions()}</FormButtonGroup>
</FormButtonGroup.FormItem>
)
}
if (type.value === 'collapsible') {
return (
<>
<FormButtonGroup>
<a
href=""
onClick={(e) => {
e.preventDefault()
toggle()
}}
>
{expanded.value ? 'Collapse' : 'Expand'}
</a>
</FormButtonGroup>
<FormButtonGroup align="right">{renderActions()}</FormButtonGroup>
</>
)
}
return (
<FormButtonGroup
align="right"
style={{ display: 'flex', width: '100%' }}
>
{renderActions()}
</FormButtonGroup>
)
}
return () => {
return (
<Form {...props}>
<FormGrid grid={grid}>
{slots.default?.()}
<FormGrid.GridColumn
gridSpan={-1}
style={{ display: 'flex', justifyContent: 'space-between' }}
>
{renderButtonGroup()}
</FormGrid.GridColumn>
</FormGrid>
</Form>
)
}
},
}),
)
const form = createForm()
const { SchemaField, SchemaObjectField, SchemaStringField } = createSchemaField(
{
components: {
QueryForm,
Input,
Select,
DatePicker,
FormItem,
},
},
)
</script>
<template>
<FormProvider :form="form">
<SchemaField>
<SchemaObjectField x-component="QueryForm">
<SchemaStringField
name="input1"
title="Input 1"
x-component="Input"
x-decorator="FormItem"
/>
<SchemaStringField
name="input2"
title="Input 2"
x-component="Input"
x-decorator="FormItem"
/>
<SchemaStringField
name="select1"
title="Select 1"
x-component="Select"
x-decorator="FormItem"
/>
<SchemaStringField
name="select2"
title="Select 2"
x-component="Select"
x-decorator="FormItem"
/>
<SchemaStringField
name="date"
title="DatePicker"
x-component="DatePicker"
x-decorator="FormItem"
/>
<SchemaStringField
name="dateRange"
title="DatePicker"
x-component="DatePicker"
x-decorator="FormItem"
:x-decorator-props="{
gridSpan: 'span 2',
}"
:x-component-props="{
type: 'daterange',
}"
/>
<SchemaStringField
name="select3"
title="Select 3"
x-component="Select"
x-decorator="FormItem"
/>
</SchemaObjectField>
</SchemaField>
</FormProvider>
</template>This is a simple example. For a higher-level abstraction, prefer the `QueryForm` component now.
查看源码
SSR / Hydration Stability Recommendations
To reduce layout jumps between the initial SSR render and hydration, follow these rules where possible:
- If you need a stable number of columns, set both
minColumnsandmaxColumns, and keep them equal. - When using array breakpoints (
minWidth/maxWidth/minColumns/maxColumns), make sure the last item matches the desired large-screen first paint. Beforeconnect,Infinityis used for breakpoint calculation, so the last breakpoint bucket is usually matched. gridSpan = -1means "fill the remaining columns in the current row", not "always occupy a full row". If preceding cells occupy different spans before and after hydration, the placement of-1can also change.- If you only configure
minWidth/maxWidth, or only one side of the column constraints (minColumnsormaxColumns), layout depends more heavily on the real container width, so SSR and hydrated layout may differ. If you need stronger consistency, prefer a fixed-column strategy.
API
FormGrid
The component inherits all Grid options. For the full prop list, see the Grid API documentation for @silver-formily/grid.
| Prop | Type | Description | Default |
|---|---|---|---|
grid | Grid | Externally provided Grid instance for advanced layout logic | - |
Note
minWidthtakes priority overminColumns.maxWidthtakes priority overmaxColumns.- Array forms of
minWidth/maxWidth/minColumns/maxColumnsmap one-to-one to thebreakpointsarray.
FormGrid.GridColumn
| Prop | Type | Description | Default |
|---|---|---|---|
gridSpan | number | Number of columns spanned by the element. When set to -1, it back-fills the remaining space in the current row. | 1 |
FormGrid.useFormGrid
Reads the Grid instance from context.
ts
interface useFormGrid {
(): Grid
}- For Grid instance methods and properties, see the Grid documentation.