diff --git a/src/calendar/calendar.en-US.md b/src/calendar/calendar.en-US.md index 268799433..eea818ada 100644 --- a/src/calendar/calendar.en-US.md +++ b/src/calendar/calendar.en-US.md @@ -11,7 +11,7 @@ firstDayOfWeek | Number | 0 | \- | N format | Function | - | Typescript:`CalendarFormatType ` `type CalendarFormatType = (day: TDate) => TDate` `type TDateType = 'selected' \| 'disabled' \| 'start' \| 'centre' \| 'end' \| ''` `interface TDate { date: Date; day: number; type: TDateType; className?: string; prefix?: string; suffix?: string;}`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/calendar/type.ts) | N maxDate | Number / Date | - | Typescript:` number \| Date` | N minDate | Number / Date | - | Typescript:` number \| Date` | N -readonly | Boolean | - | `1.9.3` | N +readonly | Boolean | undefined | `1.9.3` | N switchMode | String | none | `1.8.1`。options: none/month/year-month | N title | String / Slot / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N type | String | 'single' | options: single/multiple/range | N @@ -23,7 +23,7 @@ onChange | Function | | Typescript:`(value: Date) => void`
| N onClose | Function | | Typescript:`(trigger: CalendarTrigger) => void`
[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/calendar/type.ts)。
`type CalendarTrigger = 'close-btn' \| 'confirm-btn' \| 'overlay'`
| N onConfirm | Function | | Typescript:`(value: Date) => void`
| N onPanelChange | Function | | Typescript:`(context: { year: number, month: number }) => void`
`1.8.1` | N -onScroll | Function | | Typescript:`(context: {e: Event}) => void`
triggered when scrolling | N +onScroll | Function | | Typescript:`(context: {e: Event}) => void`
`1.8.1`。triggered when scrolling | N onSelect | Function | | Typescript:`(value: Date) => void`
| N ### Calendar Events @@ -34,7 +34,7 @@ change | `(value: Date)` | \- close | `(trigger: CalendarTrigger)` | [see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/calendar/type.ts)。
`type CalendarTrigger = 'close-btn' \| 'confirm-btn' \| 'overlay'`
confirm | `(value: Date)` | \- panel-change | `(context: { year: number, month: number })` | `1.8.1` -scroll | `(context: {e: Event})` | triggered when scrolling +scroll | `(context: {e: Event})` | `1.8.1`。triggered when scrolling select | `(value: Date)` | \- ### CSS Variables @@ -54,4 +54,4 @@ Name | Default Value | Description --td-calendar-switch-mode-icon-color | @text-color-secondary | - --td-calendar-switch-mode-icon-disabled-color | @text-color-disabled | - --td-calendar-title-color | @text-color-primary | - ---td-calendar-title-font-size | 18px | - +--td-calendar-title-font-size | 18px | - \ No newline at end of file diff --git a/src/calendar/calendar.md b/src/calendar/calendar.md index 4185db5c8..7973f9fd7 100644 --- a/src/calendar/calendar.md +++ b/src/calendar/calendar.md @@ -11,7 +11,7 @@ firstDayOfWeek | Number | 0 | 第一天从星期几开始,默认 0 = 周日 | format | Function | - | 用于格式化日期的函数。TS 类型:`CalendarFormatType ` `type CalendarFormatType = (day: TDate) => TDate` `type TDateType = 'selected' \| 'disabled' \| 'start' \| 'centre' \| 'end' \| ''` `interface TDate { date: Date; day: number; type: TDateType; className?: string; prefix?: string; suffix?: string;}`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/calendar/type.ts) | N maxDate | Number / Date | - | 最大可选的日期,不传则默认半年后。TS 类型:` number \| Date` | N minDate | Number / Date | - | 最小可选的日期,不传则默认今天。TS 类型:` number \| Date` | N -readonly | Boolean | - | `1.9.3`。是否只读,只读状态下不能选择日期 | N +readonly | Boolean | undefined | `1.9.3`。是否只读,只读状态下不能选择日期 | N switchMode | String | none | `1.8.1`。切换模式。 `none` 表示平铺展示所有月份; `month` 表示支持按月切换, `year-month` 表示既按年切换,也支持按月切换。可选项:none/month/year-month | N title | String / Slot / Function | - | 标题,不传默认为“请选择日期”。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N type | String | 'single' | 日历的选择类型,single = 单选;multiple = 多选; range = 区间选择。可选项:single/multiple/range | N @@ -23,7 +23,7 @@ onChange | Function | | TS 类型:`(value: Date) => void`
不显示 confi onClose | Function | | TS 类型:`(trigger: CalendarTrigger) => void`
关闭按钮时触发。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/calendar/type.ts)。
`type CalendarTrigger = 'close-btn' \| 'confirm-btn' \| 'overlay'`
| N onConfirm | Function | | TS 类型:`(value: Date) => void`
点击确认按钮时触发 | N onPanelChange | Function | | TS 类型:`(context: { year: number, month: number }) => void`
`1.8.1`。切换月或年时触发(switch-mode 不为 none 时有效) | N -onScroll | Function | | TS 类型:`(context: {e: Event}) => void`
滚动时触发 | N +onScroll | Function | | TS 类型:`(context: {e: Event}) => void`
`1.8.1`。滚动时触发 | N onSelect | Function | | TS 类型:`(value: Date) => void`
点击日期时触发 | N ### Calendar Events @@ -34,7 +34,7 @@ change | `(value: Date)` | 不显示 confirm-btn 时,完成选择时触发( close | `(trigger: CalendarTrigger)` | 关闭按钮时触发。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/calendar/type.ts)。
`type CalendarTrigger = 'close-btn' \| 'confirm-btn' \| 'overlay'`
confirm | `(value: Date)` | 点击确认按钮时触发 panel-change | `(context: { year: number, month: number })` | `1.8.1`。切换月或年时触发(switch-mode 不为 none 时有效) -scroll | `(context: {e: Event})` | 滚动时触发 +scroll | `(context: {e: Event})` | `1.8.1`。滚动时触发 select | `(value: Date)` | 点击日期时触发 ### CSS Variables @@ -54,4 +54,4 @@ select | `(value: Date)` | 点击日期时触发 --td-calendar-switch-mode-icon-color | @text-color-secondary | - --td-calendar-switch-mode-icon-disabled-color | @text-color-disabled | - --td-calendar-title-color | @text-color-primary | - ---td-calendar-title-font-size | 18px | - +--td-calendar-title-font-size | 18px | - \ No newline at end of file diff --git a/src/calendar/calendar.tsx b/src/calendar/calendar.tsx index e9dc31ac2..0f82a8706 100644 --- a/src/calendar/calendar.tsx +++ b/src/calendar/calendar.tsx @@ -5,6 +5,7 @@ import props from './props'; import { useTNodeJSX } from '../hooks/tnode'; import TCalendarTemplate from './template'; import { usePrefixClass } from '../hooks/useClass'; +import { useFormReadonly } from '../form/hooks'; const { prefix } = config; @@ -17,8 +18,9 @@ export default defineComponent({ const calendarTemplateRef = ref(); const renderTNodeJSX = useTNodeJSX(); + const isReadonly = useFormReadonly(); - provide('templateProps', reactive(props)); + provide('templateProps', { ...reactive(props), readonly: isReadonly.value }); const selectedValueIntoView = () => { const type = props.type === 'range' ? 'start' : 'selected'; const { templateRef } = calendarTemplateRef.value; diff --git a/src/calendar/props.ts b/src/calendar/props.ts index 29eba7bee..98be8ff71 100644 --- a/src/calendar/props.ts +++ b/src/calendar/props.ts @@ -31,7 +31,10 @@ export default { type: [Number, Date] as PropType, }, /** 是否只读,只读状态下不能选择日期 */ - readonly: Boolean, + readonly: { + type: Boolean, + default: undefined, + }, /** 切换模式。 `none` 表示平铺展示所有月份; `month` 表示支持按月切换, `year-month` 表示既按年切换,也支持按月切换 */ switchMode: { type: String as PropType, diff --git a/src/checkbox/checkbox.tsx b/src/checkbox/checkbox.tsx index 4b3216b14..435230cfb 100644 --- a/src/checkbox/checkbox.tsx +++ b/src/checkbox/checkbox.tsx @@ -14,7 +14,7 @@ import { TNode } from '../shared'; import useVModel from '../hooks/useVModel'; import { TdCheckboxProps } from '../checkbox/type'; import { useTNodeJSX, useContent } from '../hooks/tnode'; -import { useFormDisabled } from '../form/hooks'; +import { useFormDisabled, useFormReadonly } from '../form/hooks'; import { usePrefixClass } from '../hooks/useClass'; const { prefix } = config; @@ -40,6 +40,8 @@ export default defineComponent({ const checkboxGroup: any = inject('checkboxGroup', undefined); const disabled = useFormDisabled(checkboxGroup?.disabled); + const isReadonly = useFormReadonly(checkboxGroup?.readonly); + const indeterminate = computed(() => { if (props.checkAll && checkboxGroup != null) return checkboxGroup.checkAllStatus.value === 'indeterminate'; return props.indeterminate; @@ -83,10 +85,8 @@ export default defineComponent({ return disabled.value; }); - const finalReadonly = computed(() => Boolean(props.readonly || checkboxGroup?.readonly.value)); - const handleChange = (e: Event, source?: string) => { - if (isDisabled.value || finalReadonly.value) return; + if (isDisabled.value || isReadonly.value) return; if (source === 'text' && props.contentDisabled) return; const value = !isChecked.value; diff --git a/src/form/form.en-US.md b/src/form/form.en-US.md index f7418dc26..264e1c46a 100644 --- a/src/form/form.en-US.md +++ b/src/form/form.en-US.md @@ -14,6 +14,7 @@ errorMessage | Object | - | Typescript:`FormErrorMessage` | N labelAlign | String | right | options: left/right/top | N labelWidth | String / Number | '81px' | \- | N preventSubmitDefault | Boolean | true | \- | N +readonly | Boolean | undefined | \- | N requiredMark | Boolean | undefined | \- | N requiredMarkPosition | String | left | Display position of required symbols。options: left/right | N resetType | String | empty | options: empty/initial | N diff --git a/src/form/form.md b/src/form/form.md index 2a2e29d4d..798fc2389 100644 --- a/src/form/form.md +++ b/src/form/form.md @@ -14,6 +14,7 @@ errorMessage | Object | - | 表单错误信息配置,示例:`{ idcard: '请 labelAlign | String | right | 表单字段标签对齐方式:左对齐、右对齐、顶部对齐。可选项:left/right/top | N labelWidth | String / Number | '81px' | 可以整体设置label标签宽度,默认为81px | N preventSubmitDefault | Boolean | true | 是否阻止表单提交默认事件(表单提交默认事件会刷新页面),设置为 `true` 可以避免刷新 | N +readonly | Boolean | undefined | 是否整个表单只读 | N requiredMark | Boolean | undefined | 是否显示必填符号(*),默认显示 | N requiredMarkPosition | String | left | 表单必填符号(*)显示位置。可选项:left/right | N resetType | String | empty | 重置表单的方式,值为 empty 表示重置表单为空,值为 initial 表示重置表单数据为初始值。可选项:empty/initial | N diff --git a/src/form/form.tsx b/src/form/form.tsx index 8f416650b..bae05ee85 100644 --- a/src/form/form.tsx +++ b/src/form/form.tsx @@ -11,7 +11,7 @@ import { } from './type'; import props from './props'; import { FormInjectionKey, FormItemContext } from './const'; -import { FormDisabledProvider } from './hooks'; +import { FormDisabledProvider, FormReadonlyProvider } from './hooks'; import config from '../config'; import { renderContent } from '../shared'; import { preventDefault } from '../shared/dom'; @@ -45,6 +45,7 @@ export default defineComponent({ const renderTNodeJSX = useTNodeJSX(); const { disabled, + readonly, showErrorMessage, labelWidth, labelAlign, @@ -64,6 +65,9 @@ export default defineComponent({ provide('formDisabled', { disabled, }); + provide('formReadonly', { + readonly, + }); provide( FormInjectionKey, diff --git a/src/form/hooks.ts b/src/form/hooks.ts index 3cc1431fe..e98a6bc74 100644 --- a/src/form/hooks.ts +++ b/src/form/hooks.ts @@ -31,3 +31,33 @@ export function useFormDisabled(extend?: Ref) { return false; }); } + +export interface FormReadonlyProvider { + readonly: Ref; +} + +/** + * 用于实现 form 的全局只读状态hook + * 只读优先级: 组件 > 组件组 > 表单(propsReadonly.value > extend?.value > readonly?.value) + * @returns + */ +export function useFormReadonly(extend?: Ref) { + const ctx = getCurrentInstance(); + const propsReadonly = computed(() => ctx?.props.readonly as boolean); + const { readonly } = inject('formReadonly', Object.create(null)); + return computed(() => { + // 组件 + if (isBoolean(propsReadonly.value)) { + return propsReadonly.value; + } + // 组件组 + if (isBoolean(extend?.value)) { + return extend.value; + } + // 表单 + if (isBoolean(readonly?.value)) { + return readonly.value; + } + return false; + }); +} diff --git a/src/form/props.ts b/src/form/props.ts index e2870a5a5..e4671d8ff 100644 --- a/src/form/props.ts +++ b/src/form/props.ts @@ -52,6 +52,11 @@ export default { type: Boolean, default: true, }, + /** 是否整个表单只读 */ + readonly: { + type: Boolean, + default: undefined, + }, /** 是否显示必填符号(*),默认显示 */ requiredMark: { type: Boolean, diff --git a/src/form/type.ts b/src/form/type.ts index 62ae8ed5f..e612096eb 100644 --- a/src/form/type.ts +++ b/src/form/type.ts @@ -47,6 +47,10 @@ export interface TdFormProps { * @default true */ preventSubmitDefault?: boolean; + /** + * 是否整个表单只读 + */ + readonly?: boolean; /** * 是否显示必填符号(*),默认显示 */ diff --git a/src/input/input.tsx b/src/input/input.tsx index 28a82c30e..604cfc199 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -10,7 +10,7 @@ import InputProps from './props'; import { InputValue, TdInputProps } from './type'; import { extendAPI } from '../shared'; import { FormItemInjectionKey } from '../form/const'; -import { useFormDisabled } from '../form/hooks'; +import { useFormDisabled, useFormReadonly } from '../form/hooks'; import useVModel from '../hooks/useVModel'; import { usePrefixClass } from '../hooks/useClass'; import { useTNodeJSX } from '../hooks/tnode'; @@ -41,6 +41,7 @@ export default defineComponent({ const renderTNodeJSX = useTNodeJSX(); const inputClass = usePrefixClass('input'); const isDisabled = useFormDisabled(); + const isReadonly = useFormReadonly(); const inputRef = ref(); @@ -70,7 +71,7 @@ export default defineComponent({ }, ]); const showClear = computed(() => { - if (isDisabled.value || props.readonly === true) return false; + if (isDisabled.value || isReadonly.value) return false; if (props.clearable && innerValue.value && String(innerValue.value).length > 0) { return props.clearTrigger === 'always' || (props.clearTrigger === 'focus' && focused.value); @@ -257,7 +258,7 @@ export default defineComponent({ disabled: isDisabled.value, autocomplete: props.autocomplete ? 'On' : 'Off', placeholder: props.placeholder, - readonly: props.readonly, + readonly: isReadonly.value, // maxlength: props.maxlength, pattern: props.pattern, inputmode: props.inputmode, diff --git a/src/radio/radio.tsx b/src/radio/radio.tsx index 37537d48e..e3a1f73b0 100644 --- a/src/radio/radio.tsx +++ b/src/radio/radio.tsx @@ -4,7 +4,7 @@ import config from '../config'; import props from './props'; import { TdRadioGroupProps, TdRadioProps } from './type'; import useVModel from '../hooks/useVModel'; -import { useFormDisabled } from '../form/hooks'; +import { useFormDisabled, useFormReadonly } from '../form/hooks'; import { usePrefixClass } from '../hooks/useClass'; import { useContent, useTNodeJSX } from '../hooks/tnode'; @@ -14,7 +14,7 @@ export default defineComponent({ name: `${prefix}-radio`, props, emits: ['update:checked', 'update:modelValue', 'change'], - setup(props, context) { + setup(props) { const renderTNodeContent = useContent(); const renderTNodeJSX = useTNodeJSX(); const radioClass = usePrefixClass('radio'); @@ -35,6 +35,9 @@ export default defineComponent({ const groupDisabled = computed(() => rootGroupProps?.disabled); const isDisabled = useFormDisabled(groupDisabled); + const groupReadonly = computed(() => rootGroupProps?.readonly); + const isReadonly = useFormReadonly(groupReadonly); + const radioChecked = computed(() => rootGroupValue?.value !== undefined ? props.value === rootGroupValue?.value : innerChecked.value, ); @@ -50,14 +53,12 @@ export default defineComponent({ const finalAllowUncheck = computed(() => Boolean(props.allowUncheck || rootGroupProps?.allowUncheck)); - const finalReadonly = computed(() => Boolean(props.readonly || rootGroupProps?.readonly)); - // input props attribute const inputProps = computed(() => ({ name: rootGroupProps.name || props.name, checked: radioChecked.value, disabled: isDisabled.value, - readonly: finalReadonly.value, + readonly: isReadonly.value, value: props.value, })); @@ -102,7 +103,7 @@ export default defineComponent({ }; const radioOrgChange = (e: Event) => { - if (isDisabled.value || finalReadonly.value) { + if (isDisabled.value || isReadonly.value) { return; } if (rootGroupChange) { diff --git a/src/search/search.tsx b/src/search/search.tsx index cb4d9f3e4..f16a2295d 100644 --- a/src/search/search.tsx +++ b/src/search/search.tsx @@ -11,6 +11,7 @@ import useLengthLimit from '../hooks/useLengthLimit'; import { TdSearchProps } from './type'; import { ENTER_REG } from '../_common/js/common'; import TCell from '../cell/cell'; +import { useFormReadonly, useFormDisabled } from '../form/hooks'; const { prefix } = config; @@ -21,6 +22,8 @@ export default defineComponent({ const renderTNodeJSX = useTNodeJSX(); const classPrefix = usePrefixClass(); const searchClass = usePrefixClass('search'); + const isDisabled = useFormDisabled(); + const isReadonly = useFormReadonly(); const isShowResultList = ref(false); const inputRef = ref(); @@ -191,8 +194,8 @@ export default defineComponent({ class={inputClasses.value} autofocus={props.focus} placeholder={props.placeholder} - readonly={props.readonly} - disabled={props.disabled} + readonly={isReadonly.value} + disabled={isDisabled.value} onKeypress={handleSearch} onFocus={handleFocus} onBlur={handleBlur} diff --git a/src/steps/steps.tsx b/src/steps/steps.tsx index 86d8f9dea..f0cdc2b12 100644 --- a/src/steps/steps.tsx +++ b/src/steps/steps.tsx @@ -5,6 +5,7 @@ import { TdStepsProps } from './type'; import useVModel from '../hooks/useVModel'; import { useTNodeJSX } from '../hooks/tnode'; import { usePrefixClass } from '../hooks/useClass'; +import { useFormReadonly } from '../form/hooks'; const { prefix } = config; @@ -14,11 +15,12 @@ export default defineComponent({ emits: ['update:current', 'update:modelValue', 'change'], setup(props, context) { const stepsClass = usePrefixClass('steps'); + const isReadonly = useFormReadonly(); const baseClass = computed(() => [ stepsClass.value, `${stepsClass.value}--${props.layout}`, `${stepsClass.value}--${props.sequence}`, - { [`${stepsClass.value}--readonly`]: props.readonly }, + { [`${stepsClass.value}--readonly`]: isReadonly.value }, ]); const renderTNodeJSX = useTNodeJSX(); diff --git a/src/textarea/textarea.tsx b/src/textarea/textarea.tsx index 93b0f7acc..eb905b70d 100644 --- a/src/textarea/textarea.tsx +++ b/src/textarea/textarea.tsx @@ -5,7 +5,7 @@ import props from './props'; import { TextareaValue } from './type'; import calcTextareaHeight from '../_common/js/utils/calcTextareaHeight'; import { FormItemInjectionKey } from '../form/const'; -import { useFormDisabled } from '../form/hooks'; +import { useFormDisabled, useFormReadonly } from '../form/hooks'; import { usePrefixClass } from '../hooks/useClass'; import { useTNodeJSX } from '../hooks/tnode'; import useVModel from '../hooks/useVModel'; @@ -18,6 +18,7 @@ export default defineComponent({ setup(props, context) { const renderTNodeJSX = useTNodeJSX(); const isDisabled = useFormDisabled(); + const isReadonly = useFormReadonly(); const formItem = inject(FormItemInjectionKey, undefined); const textareaClass = usePrefixClass('textarea'); @@ -33,7 +34,7 @@ export default defineComponent({ `${textareaClass.value}__wrapper-inner`, { [`${textareaClass.value}--disabled`]: isDisabled.value, - [`${textareaClass.value}--readonly`]: props.readonly, + [`${textareaClass.value}--readonly`]: isReadonly.value, }, ]); @@ -145,7 +146,7 @@ export default defineComponent({ // maxlength: props.maxlength, disabled: isDisabled.value, placeholder: props.placeholder, - readonly: props.readonly, + readonly: isReadonly.value, onFocus: handleFocus, onBlur: handleBlur, onInput: handleInput,