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,