Skip to content

Commit

Permalink
feat(element): support breakpoints for FormLayout (#2340)
Browse files Browse the repository at this point in the history
* feat(element): support breakpoints for FormLayout

* docs(element): update docs
  • Loading branch information
muuyao authored Oct 23, 2021
1 parent 9c2ebc3 commit 345d5f1
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const schema = {
'x-component-props': {
labelCol: 6,
wrapperCol: 10,
layout: 'vertical',
},
properties: {
input: {
Expand Down
8 changes: 7 additions & 1 deletion packages/element/docs/demos/guide/form-layout/template.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<template>
<FormProvider :form="form">
<FormLayout :labelCol="6" :wrapperCol="10">
<FormLayout
:breakpoints="[680]"
:layout="['vertical', 'horizontal']"
:labelAlign="['left', 'right']"
:labelCol="[24, 6]"
:wrapperCol="[24, 10]"
>
<Field
name="input"
title="输入框"
Expand Down
10 changes: 5 additions & 5 deletions packages/element/docs/guide/array-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
扩展属性

| 属性名 | 类型 | 描述 | 默认值 |
| ------ | ---- | ---- | ------ ||
| title | string | 文案 | |
| method | `'push' | 'unshift'` | 添加方式 | `'push'` |
| defaultValue | any | 默认值 | |
| 属性名 | 类型 | 描述 | 默认值 |
| ------------ | ------- | ---------- | -------- | -------- |
| title | string | 文案 | |
| method | `'push' | 'unshift'` | 添加方式 | `'push'` |
| defaultValue | any | 默认值 | |

其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)

Expand Down
6 changes: 3 additions & 3 deletions packages/element/docs/guide/array-table.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
扩展属性

| 属性名 | 类型 | 描述 | 默认值 |
| ------ | ---- | ---- | ------ ||
| title | string | 文案 | |
| 属性名 | 类型 | 描述 | 默认值 |
| ------ | ------- | ---------- | -------- | -------- |
| title | string | 文案 | |
| method | `'push' | 'unshift'` | 添加方式 | `'push'` |

其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
Expand Down
51 changes: 26 additions & 25 deletions packages/element/docs/guide/form-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,29 @@

## API

| 属性名 | 类型 | 描述 | 默认值 |
| -------------- | -------------------- | ----------------------- | -------------- | -------- | ---------- | ---- |
| style | CSSProperties | 样式 | - |
| className | string | 类名 | - |
| colon | boolean | 是否有冒号 | true |
| labelAlign | `right` \| `left` | 标签内容对齐 | - |
| wrapperAlign | `right` \| `left` | 组件容器内容对齐 | - |
| labelWrap | boolean | 标签内容换行 | false |
| labelWidth | number | 标签宽度(px) | - |
| wrapperWidth | number | 组件容器宽度(px) | - |
| wrapperWrap | boolean | 组件容器换行 | false |
| labelCol | number | 标签宽度(24 column) | - |
| wrapperCol | number | 组件容器宽度(24 column) | - |
| fullness | boolean | 组件容器宽度 100% | false |
| size | `'small' | 'default' | 'large'` | 组件尺寸 | default |
| layout | `'vertical' | 'horizontal' | 'inline'` | 布局模式 | horizontal |
| direction | `'rtl' | 'ltr'` | 方向(暂不支持) | ltr |
| inset | boolean | 内联布局 | false |
| shallow | boolean | 上下文浅层传递 | true |
| feedbackLayout | `'loose' | 'terse' | 'popover' | 'none'` | 反馈布局 | true |
| tooltipLayout | `'icon'` \| `'text'` | 问提示布局 | `"icon"` |
| bordered | boolean | 是否有边框 | true |
| gridColumnGap | number | 网格布局列间距 | 8 |
| gridRowGap | number | 网格布局行间距 | 4 |
| spaceGap | number | 弹性间距 | 8 |
| 属性名 | 类型 | 描述 | 默认值 |
| -------------- | ------------- | ----------------- | ----------------------- | ----------- | ---------------- | ------------ | -------- | ---------- |
| style | CSSProperties | 样式 | - |
| className | string | 类名 | - |
| colon | boolean | 是否有冒号 | true |
| labelAlign | `'right' | 'left' | ('right' | 'left')[]` | 标签内容对齐 | - |
| wrapperAlign | `'right' | 'left' | ('right' | 'left')[]` | 组件容器内容对齐 | - |
| labelWrap | boolean | 标签内容换行 | false |
| labelWidth | number | 标签宽度(px) | - |
| wrapperWidth | number | 组件容器宽度(px) | - |
| wrapperWrap | boolean | 组件容器换行 | false |
| labelCol | `number | number[]` | 标签宽度(24 column) | - |
| wrapperCol | `number | number[]` | 组件容器宽度(24 column) | - |
| fullness | boolean | 组件容器宽度 100% | false |
| size | `'small' | 'default' | 'large'` | 组件尺寸 | default |
| layout | `'vertical' | 'horizontal' | 'inline' | ('vertical' | 'horizontal' | 'inline')[]` | 布局模式 | horizontal |
| direction | `'rtl' | 'ltr'` | 方向(暂不支持) | ltr |
| inset | boolean | 内联布局 | false |
| shallow | boolean | 上下文浅层传递 | true |
| feedbackLayout | `'loose' | 'terse' | 'popover' | 'none'` | 反馈布局 | true |
| tooltipLayout | `'icon'` | `'text'` | 问提示布局 | `"icon"` |
| bordered | boolean | 是否有边框 | true |
| breakpoints | number[] | 容器尺寸断点 | - |
| gridColumnGap | number | 网格布局列间距 | 8 |
| gridRowGap | number | 网格布局行间距 | 4 |
| spaceGap | number | 弹性间距 | 8 |
66 changes: 45 additions & 21 deletions packages/element/src/form-layout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,35 @@ import {
Ref,
ref,
watch,
computed,
} from '@vue/composition-api'
import { h } from '@formily/vue'
import { stylePrefix } from '../__builtins__/configs'
import { useResponsiveFormLayout } from './useResponsiveFormLayout'

export type FormLayoutProps = {
className?: string
colon?: boolean
labelAlign?: 'right' | 'left'
wrapperAlign?: 'right' | 'left'
labelAlign?: 'right' | 'left' | ('right' | 'left')[]
wrapperAlign?: 'right' | 'left' | ('right' | 'left')[]
labelWrap?: boolean
labelWidth?: number
wrapperWidth?: number
wrapperWrap?: boolean
labelCol?: number
wrapperCol?: number
labelCol?: number | number[]
wrapperCol?: number | number[]
fullness?: boolean
size?: 'small' | 'default' | 'large'
layout?: 'vertical' | 'horizontal' | 'inline'
layout?:
| 'vertical'
| 'horizontal'
| 'inline'
| ('vertical' | 'horizontal' | 'inline')[]
direction?: 'rtl' | 'ltr'
shallow?: boolean
feedbackLayout?: 'loose' | 'terse' | 'popover'
tooltipLayout?: 'icon' | 'text'
bordered?: boolean
breakpoints?: number[]
inset?: boolean
spaceGap?: number
gridColumnGap?: number
Expand All @@ -50,10 +55,23 @@ export const useFormShallowLayout = (): Ref<FormLayoutProps> =>
inject(FormLayoutShallowContext, ref(null))

export const useFormLayout = (): Ref<FormLayoutProps> => {
return ref({
...useFormDeepLayout().value,
...useFormShallowLayout().value,
})
const shallowLayout = useFormShallowLayout()
const deepLayout = useFormDeepLayout()
const formLayout = ref({})

watch(
[shallowLayout],
() => {
formLayout.value = {
...deepLayout.value,
...shallowLayout.value,
}
},
{
immediate: true,
}
)
return formLayout
}

export const FormLayout = defineComponent<FormLayoutProps>({
Expand All @@ -78,28 +96,32 @@ export const FormLayout = defineComponent<FormLayoutProps>({
tooltipLayout: {},
bordered: { default: true },
inset: { default: false },
breakpoints: {},
spaceGap: {},
gridColumnGap: {},
gridRowGap: {},
},
setup(props, { slots }) {
setup(customProps, { slots, refs }) {
const { props } = useResponsiveFormLayout(customProps, refs)

const deepLayout = useFormDeepLayout()
const newDeepLayout = ref({
...deepLayout,
})
const shallowProps = computed(() => (props.shallow ? props : undefined))
const shallowProps = ref({})

watch(
[props, deepLayout],
() => {
if (!props.shallow) {
shallowProps.value = props.value.shallow ? props.value : undefined
if (!props.value.shallow) {
Object.assign(newDeepLayout.value, props)
} else {
if (props.size) {
newDeepLayout.value.size = props.size
if (props.value.size) {
newDeepLayout.value.size = props.value.size
}
if (props.colon) {
newDeepLayout.value.colon = props.colon
if (props.value.colon) {
newDeepLayout.value.colon = props.value.colon
}
}
},
Expand All @@ -112,14 +134,16 @@ export const FormLayout = defineComponent<FormLayoutProps>({
const formPrefixCls = `${stylePrefix}-form`
return () => {
const classNames = {
[`${formPrefixCls}-${props.layout}`]: true,
[`${formPrefixCls}-rtl`]: props.direction === 'rtl',
[`${formPrefixCls}-${props.size}`]: props.size !== undefined,
[`${props.className}`]: props.className !== undefined,
[`${formPrefixCls}-${props.value.layout}`]: true,
[`${formPrefixCls}-rtl`]: props.value.direction === 'rtl',
[`${formPrefixCls}-${props.value.size}`]:
props.value.size !== undefined,
[`${props.value.className}`]: props.value.className !== undefined,
}
return h(
'div',
{
ref: 'root',
class: classNames,
},
slots
Expand Down
115 changes: 115 additions & 0 deletions packages/element/src/form-layout/useResponsiveFormLayout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { isArr, isValid } from '@formily/shared'
import { onMounted, Ref, ref } from 'vue-demi'

interface IProps {
breakpoints?: number[]
layout?:
| 'vertical'
| 'horizontal'
| 'inline'
| ('vertical' | 'horizontal' | 'inline')[]
labelCol?: number | number[]
wrapperCol?: number | number[]
labelAlign?: 'right' | 'left' | ('right' | 'left')[]
wrapperAlign?: 'right' | 'left' | ('right' | 'left')[]
[props: string]: any
}

interface ICalcBreakpointIndex {
(originalBreakpoints: number[], width: number): number
}

interface ICalculateProps {
(target: Element, props: IProps): IProps
}

interface IUseResponsiveFormLayout {
(
props: IProps,
refs: {
[key: string]: Vue | Element | Vue[] | Element[]
}
): {
props: Ref<IProps>
}
}

const calcBreakpointIndex: ICalcBreakpointIndex = (breakpoints, width) => {
for (let i = 0; i < breakpoints.length; i++) {
if (width <= breakpoints[i]) {
return i
}
}
}

const calcFactor = <T>(value: T | T[], breakpointIndex: number): T => {
if (Array.isArray(value)) {
if (breakpointIndex === -1) return value[0]
return value[breakpointIndex] ?? value[value.length - 1]
} else {
return value
}
}

const factor = <T>(value: T | T[], breakpointIndex: number): T =>
isValid(value) ? calcFactor(value as any, breakpointIndex) : value

const calculateProps: ICalculateProps = (target, props) => {
const { clientWidth } = target
const {
breakpoints,
layout,
labelAlign,
wrapperAlign,
labelCol,
wrapperCol,
...otherProps
} = props
const breakpointIndex = calcBreakpointIndex(breakpoints, clientWidth)

return {
layout: factor(layout, breakpointIndex),
labelAlign: factor(labelAlign, breakpointIndex),
wrapperAlign: factor(wrapperAlign, breakpointIndex),
labelCol: factor(labelCol, breakpointIndex),
wrapperCol: factor(wrapperCol, breakpointIndex),
...otherProps,
}
}

export const useResponsiveFormLayout: IUseResponsiveFormLayout = (
props,
refs
) => {
const root = ref<Element>(null)
const { breakpoints } = props
if (!isArr(breakpoints)) {
return { props: ref(props) }
}
const layoutProps = ref<IProps>({})

const updateUI = () => {
layoutProps.value = calculateProps(root.value, props)
}

onMounted(() => {
root.value = refs.root as Element
const observer = () => {
updateUI()
}
const resizeObserver = new ResizeObserver(observer)
if (root.value) {
resizeObserver.observe(root.value)
}

updateUI()

return () => {
resizeObserver.disconnect()
}
})

return {
props: layoutProps,
}
}

0 comments on commit 345d5f1

Please sign in to comment.