style: configprovider

feat-css-var
tangjinzhou 2022-02-27 11:31:28 +08:00
parent a4b8c68533
commit 5da035d275
16 changed files with 428 additions and 186 deletions

View File

@ -1,9 +0,0 @@
import type { ComputedRef } from 'vue';
import { computed, inject } from 'vue';
import { defaultConfigProvider } from '../../config-provider';
export default (name: string, props: Record<any, any>): ComputedRef<string> => {
const configProvider = inject('configProvider', defaultConfigProvider);
const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls));
return prefixCls;
};

View File

@ -1,5 +1,5 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import { inject, defineComponent, ref } from 'vue';
import { defineComponent, ref } from 'vue';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined';
import ExclamationCircleOutlined from '@ant-design/icons-vue/ExclamationCircleOutlined';
@ -13,10 +13,10 @@ import classNames from '../_util/classNames';
import PropTypes from '../_util/vue-types';
import { getTransitionProps, Transition } from '../_util/transition';
import { isValidElement, getPropsSlot } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
import { tuple, withInstall } from '../_util/type';
import { cloneElement } from '../_util/vnode';
import type { NodeMouseEventHandler } from '../vc-tree/contextTypes';
import useConfigInject from '../_util/hooks/useConfigInject';
function noop() {}
@ -69,7 +69,7 @@ const Alert = defineComponent({
inheritAttrs: false,
props: alertProps(),
setup(props, { slots, emit, attrs, expose }) {
const configProvider = inject('configProvider', defaultConfigProvider);
const { prefixCls, direction } = useConfigInject('alert', props);
const closing = ref(false);
const closed = ref(false);
const alertNode = ref();
@ -97,13 +97,7 @@ const Alert = defineComponent({
expose({ animationEnd });
const motionStyle = ref<CSSProperties>({});
return () => {
const {
prefixCls: customizePrefixCls,
banner,
closeIcon: customCloseIcon = slots.closeIcon?.(),
} = props;
const { getPrefixCls } = configProvider;
const prefixCls = getPrefixCls('alert', customizePrefixCls);
const { banner, closeIcon: customCloseIcon = slots.closeIcon?.() } = props;
let { closable, type, showIcon } = props;
@ -123,20 +117,26 @@ const Alert = defineComponent({
if (closeText) {
closable = true;
}
const alertCls = classNames(prefixCls, {
[`${prefixCls}-${type}`]: true,
[`${prefixCls}-closing`]: closing.value,
[`${prefixCls}-with-description`]: !!description,
[`${prefixCls}-no-icon`]: !showIcon,
[`${prefixCls}-banner`]: !!banner,
[`${prefixCls}-closable`]: closable,
const prefixClsValue = prefixCls.value;
const alertCls = classNames(prefixClsValue, {
[`${prefixClsValue}-${type}`]: true,
[`${prefixClsValue}-closing`]: closing.value,
[`${prefixClsValue}-with-description`]: !!description,
[`${prefixClsValue}-no-icon`]: !showIcon,
[`${prefixClsValue}-banner`]: !!banner,
[`${prefixClsValue}-closable`]: closable,
[`${prefixClsValue}-rtl`]: direction.value === 'rtl',
});
const closeIcon = closable ? (
<button type="button" onClick={handleClose} class={`${prefixCls}-close-icon`} tabindex={0}>
<button
type="button"
onClick={handleClose}
class={`${prefixClsValue}-close-icon`}
tabindex={0}
>
{closeText ? (
<span class={`${prefixCls}-close-text`}>{closeText}</span>
<span class={`${prefixClsValue}-close-text`}>{closeText}</span>
) : customCloseIcon === undefined ? (
<CloseOutlined />
) : (
@ -148,13 +148,13 @@ const Alert = defineComponent({
const iconNode = (icon &&
(isValidElement(icon) ? (
cloneElement(icon, {
class: `${prefixCls}-icon`,
class: `${prefixClsValue}-icon`,
})
) : (
<span class={`${prefixCls}-icon`}>{icon}</span>
))) || <IconType class={`${prefixCls}-icon`} />;
<span class={`${prefixClsValue}-icon`}>{icon}</span>
))) || <IconType class={`${prefixClsValue}-icon`} />;
const transitionProps = getTransitionProps(`${prefixCls}-motion`, {
const transitionProps = getTransitionProps(`${prefixClsValue}-motion`, {
appear: false,
css: true,
onAfterLeave: animationEnd,
@ -177,9 +177,11 @@ const Alert = defineComponent({
ref={alertNode}
>
{showIcon ? iconNode : null}
<div class={`${prefixCls}-content`}>
{message ? <div class={`${prefixCls}-message`}>{message}</div> : null}
{description ? <div class={`${prefixCls}-description`}>{description}</div> : null}
<div class={`${prefixClsValue}-content`}>
{message ? <div class={`${prefixClsValue}-message`}>{message}</div> : null}
{description ? (
<div class={`${prefixClsValue}-description`}>{description}</div>
) : null}
</div>
{closeIcon}
</div>

View File

@ -1,7 +1,6 @@
import type { ExtractPropTypes, PropType } from 'vue';
import {
defineComponent,
inject,
nextTick,
onActivated,
onBeforeUnmount,
@ -10,17 +9,16 @@ import {
ref,
watch,
onDeactivated,
computed,
} from 'vue';
import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined';
import PropTypes from '../_util/vue-types';
import addEventListener from '../vc-util/Dom/addEventListener';
import getScroll from '../_util/getScroll';
import { getTransitionProps, Transition } from '../_util/transition';
import { defaultConfigProvider } from '../config-provider';
import scrollTo from '../_util/scrollTo';
import { withInstall } from '../_util/type';
import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
import useConfigInject from '../_util/hooks/useConfigInject';
export const backTopProps = {
visibilityHeight: PropTypes.number.def(400),
@ -39,7 +37,7 @@ const BackTop = defineComponent({
props: backTopProps,
emits: ['click'],
setup(props, { slots, attrs, emit }) {
const configProvider = inject('configProvider', defaultConfigProvider);
const { prefixCls, direction } = useConfigInject('back-top', props);
const domRef = ref();
const state = reactive({
visible: false,
@ -113,8 +111,6 @@ const BackTop = defineComponent({
scrollRemove();
});
const prefixCls = computed(() => configProvider.getPrefixCls('back-top', props.prefixCls));
return () => {
const defaultElement = (
<div class={`${prefixCls.value}-content`}>
@ -129,7 +125,7 @@ const BackTop = defineComponent({
class: {
[`${prefixCls.value}`]: true,
[`${attrs.class}`]: attrs.class,
[`${prefixCls.value}-rtl`]: configProvider.direction === 'rtl',
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
},
};

View File

@ -0,0 +1,131 @@
import type { ExtractPropTypes, InjectionKey, PropType, Ref } from 'vue';
import { inject, provide } from 'vue';
import type { ValidateMessages } from '../form/interface';
import type { RequiredMark } from '../form/Form';
import type { RenderEmptyHandler } from './renderEmpty';
import type { TransformCellTextProps } from '../table/interface';
import PropTypes from '../_util/vue-types';
import type { Locale } from '../locale-provider';
type GlobalFormCOntextProps = {
validateMessages?: Ref<ValidateMessages>;
};
export const GlobalFormContextKey: InjectionKey<GlobalFormCOntextProps> =
Symbol('GlobalFormContextKey');
export const useProvideGlobalForm = (state: GlobalFormCOntextProps) => {
provide(GlobalFormContextKey, state);
};
export const useInjectGlobalForm = () => {
return inject(GlobalFormContextKey, {});
};
export const GlobalConfigContextKey: InjectionKey<GlobalFormCOntextProps> =
Symbol('GlobalConfigContextKey');
export interface CSPConfig {
nonce?: string;
}
export interface Theme {
primaryColor?: string;
infoColor?: string;
successColor?: string;
processingColor?: string;
errorColor?: string;
warningColor?: string;
}
export type SizeType = 'small' | 'middle' | 'large' | undefined;
export type Direction = 'ltr' | 'rtl';
export interface ConfigConsumerProps {
getTargetContainer?: () => HTMLElement;
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
rootPrefixCls?: string;
getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string;
renderEmpty: RenderEmptyHandler;
transformCellText?: (tableProps: TransformCellTextProps) => any;
csp?: CSPConfig;
autoInsertSpaceInButton?: boolean;
input?: {
autocomplete?: string;
};
locale?: Locale;
pageHeader?: {
ghost: boolean;
};
componentSize?: SizeType;
direction?: 'ltr' | 'rtl';
space?: {
size?: SizeType | number;
};
virtual?: boolean;
dropdownMatchSelectWidth?: boolean | number;
form?: {
requiredMark?: RequiredMark;
colon?: boolean;
};
}
export const configProviderProps = () => ({
getTargetContainer: {
type: Function as PropType<() => HTMLElement>,
},
getPopupContainer: {
type: Function as PropType<(triggerNode?: HTMLElement) => HTMLElement>,
},
prefixCls: String,
getPrefixCls: {
type: Function as PropType<(suffixCls?: string, customizePrefixCls?: string) => string>,
},
renderEmpty: {
type: Function as PropType<RenderEmptyHandler>,
},
transformCellText: {
type: Function as PropType<(tableProps: TransformCellTextProps) => any>,
},
csp: {
type: Object as PropType<CSPConfig>,
default: undefined as CSPConfig,
},
input: {
type: Object as PropType<{ autocomplete: string }>,
},
autoInsertSpaceInButton: PropTypes.looseBool,
locale: {
type: Object as PropType<Locale>,
default: undefined as Locale,
},
pageHeader: {
type: Object as PropType<{ ghost: boolean }>,
},
componentSize: {
type: String as PropType<SizeType>,
},
direction: {
type: String as PropType<'ltr' | 'rtl'>,
},
space: {
type: Object as PropType<{ size: SizeType | number }>,
},
virtual: PropTypes.looseBool,
dropdownMatchSelectWidth: { type: [Number, Boolean], default: true },
form: {
type: Object as PropType<{
validateMessages?: ValidateMessages;
requiredMark?: RequiredMark;
colon?: boolean;
}>,
default: undefined as {
validateMessages?: ValidateMessages;
requiredMark?: RequiredMark;
colon?: boolean;
},
},
// internal use
notUpdateGlobalConfig: Boolean,
});
export type ConfigProviderProps = Partial<ExtractPropTypes<ReturnType<typeof configProviderProps>>>;

View File

@ -0,0 +1,97 @@
/* eslint-disable import/prefer-default-export, prefer-destructuring */
import { TinyColor } from '@ctrl/tinycolor';
import { generate } from '@ant-design/colors';
import type { Theme } from './context';
import { updateCSS } from '../vc-util/Dom/dynamicCSS';
const dynamicStyleMark = `-ant-${Date.now()}-${Math.random()}`;
export function registerTheme(globalPrefixCls: string, theme: Theme) {
const variables: Record<string, string> = {};
const formatColor = (
color: TinyColor,
updater?: (cloneColor: TinyColor) => TinyColor | undefined,
) => {
let clone = color.clone();
clone = updater?.(clone) || clone;
return clone.toRgbString();
};
const fillColor = (colorVal: string, type: string) => {
const baseColor = new TinyColor(colorVal);
const colorPalettes = generate(baseColor.toRgbString());
variables[`${type}-color`] = formatColor(baseColor);
variables[`${type}-color-disabled`] = colorPalettes[1];
variables[`${type}-color-hover`] = colorPalettes[4];
variables[`${type}-color-active`] = colorPalettes[7];
variables[`${type}-color-outline`] = baseColor.clone().setAlpha(0.2).toRgbString();
variables[`${type}-color-deprecated-bg`] = colorPalettes[1];
variables[`${type}-color-deprecated-border`] = colorPalettes[3];
};
// ================ Primary Color ================
if (theme.primaryColor) {
fillColor(theme.primaryColor, 'primary');
const primaryColor = new TinyColor(theme.primaryColor);
const primaryColors = generate(primaryColor.toRgbString());
// Legacy - We should use semantic naming standard
primaryColors.forEach((color, index) => {
variables[`primary-${index + 1}`] = color;
});
// Deprecated
variables['primary-color-deprecated-l-35'] = formatColor(primaryColor, c => c.lighten(35));
variables['primary-color-deprecated-l-20'] = formatColor(primaryColor, c => c.lighten(20));
variables['primary-color-deprecated-t-20'] = formatColor(primaryColor, c => c.tint(20));
variables['primary-color-deprecated-t-50'] = formatColor(primaryColor, c => c.tint(50));
variables['primary-color-deprecated-f-12'] = formatColor(primaryColor, c =>
c.setAlpha(c.getAlpha() * 0.12),
);
const primaryActiveColor = new TinyColor(primaryColors[0]);
variables['primary-color-active-deprecated-f-30'] = formatColor(primaryActiveColor, c =>
c.setAlpha(c.getAlpha() * 0.3),
);
variables['primary-color-active-deprecated-d-02'] = formatColor(primaryActiveColor, c =>
c.darken(2),
);
}
// ================ Success Color ================
if (theme.successColor) {
fillColor(theme.successColor, 'success');
}
// ================ Warning Color ================
if (theme.warningColor) {
fillColor(theme.warningColor, 'warning');
}
// ================= Error Color =================
if (theme.errorColor) {
fillColor(theme.errorColor, 'error');
}
// ================= Info Color ==================
if (theme.infoColor) {
fillColor(theme.infoColor, 'info');
}
// Convert to css variables
const cssList = Object.keys(variables).map(
key => `--${globalPrefixCls}-${key}: ${variables[key]};`,
);
updateCSS(
`
:root {
${cssList.join('\n')}
}
`,
`${dynamicStyleMark}-dynamic-theme`,
);
}

View File

@ -48,13 +48,20 @@ Some components use dynamic style to support wave effect. You can config `csp` p
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| autoInsertSpaceInButton | Set `false` to remove space between 2 chinese characters on Button | boolean | true | |
| componentSize | Config antd component size | `small` \| `middle` \| `large` | - | 3.0 |
| csp | Set [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) config | { nonce: string } | - | |
| direction | Set direction of layout. See [demo](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | 3.0 |
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | - | 3.0 |
| form | Set Form common props | { validateMessages?: [ValidateMessages](/components/form/#validateMessages), requiredMark?: boolean \| `optional` } | - | 3.0 |
| renderEmpty | set empty content of components. Ref [Empty](/components/empty/) | slot-scope \| Function(componentName: string): ReactNode | - | |
| getPopupContainer | to set the container of the popup element. The default is to create a `div` element in `body`. | Function(triggerNode, dialogContext) | `() => document.body` | |
| getTargetContainer | Config Affix, Anchor scroll target container | () => HTMLElement | () => window | 3.0 |
| input | Set Input common props | { autocomplete?: string } | - | 3.0 |
| locale | language package setting, you can find the packages in [ant-design-vue/es/locale](http://unpkg.com/ant-design-vue/es/locale/) | object | - | 1.5.0 |
| prefixCls | set prefix class | string | ant | |
| pageHeader | Unify the ghost of pageHeader ,Ref [pageHeader](<(/components/page-header)> | { ghost:boolean } | 'true' | 1.5.0 |
| transformCellText | Table data can be changed again before rendering. The default configuration of general user empty data. | Function({ text, column, record, index }) => any | - | 1.5.4 |
| space | Set Space `size`, ref [Space](/components/space) | { size: `small` \| `middle` \| `large` \| `number` } | - | 3.0 |
| virtual | Disable virtual scroll when set to false | boolean | true | 3.0 |
### ConfigProvider.config() `3.0.0+`

View File

@ -1,66 +1,24 @@
import type { PropType, ExtractPropTypes, UnwrapRef, App, Plugin, WatchStopHandle } from 'vue';
import { reactive, provide, defineComponent, watch, watchEffect } from 'vue';
import PropTypes from '../_util/vue-types';
import type { UnwrapRef, App, Plugin, WatchStopHandle } from 'vue';
import { computed, reactive, provide, defineComponent, watch, watchEffect } from 'vue';
import defaultRenderEmpty from './renderEmpty';
import type { RenderEmptyHandler } from './renderEmpty';
import type { Locale } from '../locale-provider';
import LocaleProvider, { ANT_MARK } from '../locale-provider';
import type { TransformCellTextProps } from '../table/interface';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import type { RequiredMark } from '../form/Form';
import type { MaybeRef } from '../_util/type';
import message from '../message';
import notification from '../notification';
import { registerTheme } from './cssVariables';
import defaultLocale from '../locale/default';
import type { ValidateMessages } from '../form/interface';
export type SizeType = 'small' | 'middle' | 'large' | undefined;
export interface CSPConfig {
nonce?: string;
}
export type { RenderEmptyHandler };
export type Direction = 'ltr' | 'rtl';
export interface ConfigConsumerProps {
getTargetContainer?: () => HTMLElement;
getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
rootPrefixCls?: string;
getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string;
renderEmpty: RenderEmptyHandler;
transformCellText?: (tableProps: TransformCellTextProps) => any;
csp?: CSPConfig;
autoInsertSpaceInButton?: boolean;
input?: {
autoComplete?: string;
};
locale?: Locale;
pageHeader?: {
ghost: boolean;
};
componentSize?: SizeType;
direction?: 'ltr' | 'rtl';
space?: {
size?: SizeType | number;
};
virtual?: boolean;
dropdownMatchSelectWidth?: boolean | number;
}
export const configConsumerProps = [
'getTargetContainer',
'getPopupContainer',
'rootPrefixCls',
'getPrefixCls',
'renderEmpty',
'csp',
'autoInsertSpaceInButton',
'locale',
'pageHeader',
];
import type { ConfigProviderProps, Theme } from './context';
import { configProviderProps, useProvideGlobalForm } from './context';
export type { ConfigProviderProps, Theme, SizeType, Direction } from './context';
export const defaultPrefixCls = 'ant';
function getGlobalPrefixCls() {
return globalConfigForApi.prefixCls || defaultPrefixCls;
}
@ -107,13 +65,16 @@ type GlobalConfigProviderProps = {
};
let stopWatchEffect: WatchStopHandle;
const setGlobalConfig = (params: GlobalConfigProviderProps) => {
const setGlobalConfig = (params: GlobalConfigProviderProps & { theme?: Theme }) => {
if (stopWatchEffect) {
stopWatchEffect();
}
stopWatchEffect = watchEffect(() => {
Object.assign(globalConfigBySet, reactive(params));
});
if (params.theme) {
registerTheme(getGlobalPrefixCls(), params.theme);
}
};
export const globalConfig = () => ({
@ -142,61 +103,10 @@ export const globalConfig = () => ({
},
});
export const configProviderProps = {
getTargetContainer: {
type: Function as PropType<() => HTMLElement>,
},
getPopupContainer: {
type: Function as PropType<(triggerNode: HTMLElement) => HTMLElement>,
},
prefixCls: String,
getPrefixCls: {
type: Function as PropType<(suffixCls?: string, customizePrefixCls?: string) => string>,
},
renderEmpty: {
type: Function as PropType<RenderEmptyHandler>,
},
transformCellText: {
type: Function as PropType<(tableProps: TransformCellTextProps) => any>,
},
csp: {
type: Object as PropType<CSPConfig>,
default: undefined as CSPConfig,
},
input: {
type: Object as PropType<{ autocomplete: string }>,
},
autoInsertSpaceInButton: PropTypes.looseBool,
locale: {
type: Object as PropType<Locale>,
},
pageHeader: {
type: Object as PropType<{ ghost: boolean }>,
},
componentSize: {
type: String as PropType<SizeType>,
},
direction: {
type: String as PropType<'ltr' | 'rtl'>,
},
space: {
type: Object as PropType<{ size: SizeType | number }>,
},
virtual: PropTypes.looseBool,
dropdownMatchSelectWidth: { type: [Number, Boolean], default: true },
form: {
type: Object as PropType<{ requiredMark?: RequiredMark }>,
},
// internal use
notUpdateGlobalConfig: Boolean,
};
export type ConfigProviderProps = Partial<ExtractPropTypes<typeof configProviderProps>>;
const ConfigProvider = defineComponent({
name: 'AConfigProvider',
inheritAttrs: false,
props: configProviderProps,
props: configProviderProps(),
setup(props, { slots }) {
const getPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => {
const { prefixCls = 'ant' } = props;
@ -240,7 +150,24 @@ const ConfigProvider = defineComponent({
Object.assign(globalConfigByCom, configProvider);
});
}
const validateMessagesRef = computed(() => {
// Additional Form provider
let validateMessages: ValidateMessages = {
...defaultLocale.Form?.defaultValidateMessages,
};
if (props.locale) {
validateMessages =
props.locale.Form?.defaultValidateMessages ||
defaultLocale.Form?.defaultValidateMessages ||
{};
}
if (props.form && props.form.validateMessages) {
validateMessages = { ...validateMessages, ...props.form.validateMessages };
}
return validateMessages;
});
useProvideGlobalForm({ validateMessages: validateMessagesRef });
provide('configProvider', configProvider);
const renderProvider = (legacyLocale: Locale) => {

View File

@ -49,13 +49,20 @@ ConfigProvider 使用 Vue 的 [provide / inject](https://vuejs.org/v2/api/#provi
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| autoInsertSpaceInButton | 设置为 `false` 时,移除按钮中 2 个汉字之间的空格 | boolean | true | |
| componentSize | 设置 antd 组件大小 | `small` \| `middle` \| `large` | - | 3.0 |
| csp | 设置 [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | |
| renderEmpty | 自定义组件空状态。参考 [空状态](/components/empty/) | slot-scope \| Function(componentName: string): VNode | - | |
| direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | 3.0 |
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。`false` 时会关闭虚拟滚动 | boolean \| number | - | |
| form | 设置 Form 组件的通用属性 | { validateMessages?: [ValidateMessages](/components/form/#validateMessages), requiredMark?: boolean \| `optional`, colon?: boolean} | - | 3.0 |
| renderEmpty | 自定义组件空状态。参考 [空状态](/components/empty/) | slot \| Function(componentName: string): VNode | - | |
| getPopupContainer | 弹出框Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | Function(triggerNode, dialogContext) | () => document.body | |
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 3.0 |
| input | 设置 Input 组件的通用属性 | { autocomplete?: string } | - | 3.0 |
| locale | 语言包配置,语言包可到 [ant-design-vue/es/locale](http://unpkg.com/ant-design-vue/es/locale/) 目录下寻找 | object | - | 1.5.0 |
| pageHeader | 统一设置 pageHeader 的 ghost参考 [pageHeader](<(/components/page-header)>) | { ghost: boolean } | 'true' | 1.5.0 |
| prefixCls | 设置统一样式前缀。注意:需要配合 `less` 变量 `@ant-prefix` 使用 | string | `ant` | |
| transformCellText | Table 数据渲染前可以再次改变,一般用户空数据的默认配置 | Function({ text, column, record, index }) => any | - | 1.5.4 |
| space | 设置 Space 的 `size`,参考 [Space](/components/space) | { size: `small` \| `middle` \| `large` \| `number` } | - | 3.0 |
| virtual | 设置 `false` 时关闭虚拟滚动 | boolean | - | 3.0 |
### ConfigProvider.config() `3.0.0+`

View File

@ -1,17 +1,14 @@
import { inject } from 'vue';
import Empty from '../empty';
import { defaultConfigProvider } from '.';
import type { VueNode } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject';
export interface RenderEmptyProps {
componentName?: string;
}
const RenderEmpty = (props: RenderEmptyProps) => {
const configProvider = inject('configProvider', defaultConfigProvider);
const { prefixCls } = useConfigInject('empty', props);
const renderHtml = (componentName?: string) => {
const { getPrefixCls } = configProvider;
const prefix = getPrefixCls('empty');
switch (componentName) {
case 'Table':
case 'List':
@ -22,7 +19,7 @@ const RenderEmpty = (props: RenderEmptyProps) => {
case 'Cascader':
case 'Transfer':
case 'Mentions':
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} class={`${prefix}-small`} />;
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} class={`${prefixCls.value}-small`} />;
default:
return <Empty />;

View File

@ -1,8 +1,8 @@
import { flattenChildren } from '../_util/props-util';
import type { ExtractPropTypes, PropType } from 'vue';
import { computed, defineComponent, inject } from 'vue';
import { defaultConfigProvider } from '../config-provider';
import { computed, defineComponent } from 'vue';
import { withInstall } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject';
export const dividerProps = {
prefixCls: String,
@ -29,8 +29,7 @@ const Divider = defineComponent({
name: 'ADivider',
props: dividerProps,
setup(props, { slots }) {
const configProvider = inject('configProvider', defaultConfigProvider);
const prefixClsRef = computed(() => configProvider.getPrefixCls('divider', props.prefixCls));
const { prefixCls: prefixClsRef, direction } = useConfigInject('divider', props);
const classString = computed(() => {
const { type, dashed, plain } = props;
@ -40,7 +39,7 @@ const Divider = defineComponent({
[`${prefixCls}-${type}`]: true,
[`${prefixCls}-dashed`]: !!dashed,
[`${prefixCls}-plain`]: !!plain,
[`${prefixCls}-rtl`]: configProvider.direction === 'rtl',
[`${prefixCls}-rtl`]: direction.value === 'rtl',
};
});

View File

@ -1,8 +1,7 @@
import { inject } from 'vue';
import { defaultConfigProvider } from '../config-provider';
import useConfigInject from '../_util/hooks/useConfigInject';
const Empty = () => {
const { getPrefixCls } = inject('configProvider', defaultConfigProvider);
const { getPrefixCls } = useConfigInject('empty', {});
const prefixCls = getPrefixCls('empty-img-default');
return (

View File

@ -1,7 +1,5 @@
import type { CSSProperties, FunctionalComponent } from 'vue';
import { inject } from 'vue';
import classNames from '../_util/classNames';
import { defaultConfigProvider } from '../config-provider';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import DefaultEmptyImg from './empty';
import SimpleEmptyImg from './simple';
@ -9,6 +7,7 @@ import { filterEmpty } from '../_util/props-util';
import PropTypes from '../_util/vue-types';
import type { VueNode } from '../_util/type';
import { withInstall } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject';
const defaultEmptyImg = <DefaultEmptyImg />;
const simpleEmptyImg = <SimpleEmptyImg />;
@ -33,10 +32,10 @@ interface EmptyType extends FunctionalComponent<EmptyProps> {
}
const Empty: EmptyType = (props, { slots = {}, attrs }) => {
const configProvider = inject('configProvider', defaultConfigProvider);
const { getPrefixCls, direction } = configProvider;
const { direction, prefixCls: prefixClsRef } = useConfigInject('empty', props);
const prefixCls = prefixClsRef.value;
const {
prefixCls: customizePrefixCls,
image = defaultEmptyImg,
description = slots.description?.() || undefined,
imageStyle,
@ -48,7 +47,6 @@ const Empty: EmptyType = (props, { slots = {}, attrs }) => {
<LocaleReceiver
componentName="Empty"
children={(locale: Locale) => {
const prefixCls = getPrefixCls('empty', customizePrefixCls);
const des = typeof description !== 'undefined' ? description : locale.description;
const alt = typeof des === 'string' ? des : 'empty';
let imageNode: EmptyProps['image'] = null;
@ -63,7 +61,7 @@ const Empty: EmptyType = (props, { slots = {}, attrs }) => {
<div
class={classNames(prefixCls, className, {
[`${prefixCls}-normal`]: image === simpleEmptyImg,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-rtl`]: direction.value === 'rtl',
})}
{...restProps}
>

View File

@ -1,8 +1,7 @@
import { inject } from 'vue';
import { defaultConfigProvider } from '../config-provider';
import useConfigInject from '../_util/hooks/useConfigInject';
const Simple = () => {
const { getPrefixCls } = inject('configProvider', defaultConfigProvider);
const { getPrefixCls } = useConfigInject('empty', {});
const prefixCls = getPrefixCls('empty-img-simple');
return (

View File

@ -1,17 +1,15 @@
import PreviewGroup from '../vc-image/src/PreviewGroup';
import { computed, defineComponent, inject } from 'vue';
import { defaultConfigProvider } from '../config-provider';
import { computed, defineComponent } from 'vue';
import PropTypes from '../_util/vue-types';
import useConfigInject from '../_util/hooks/useConfigInject';
const InternalPreviewGroup = defineComponent({
name: 'AImagePreviewGroup',
inheritAttrs: false,
props: { previewPrefixCls: PropTypes.string },
setup(props, { attrs, slots }) {
const configProvider = inject('configProvider', defaultConfigProvider);
const prefixCls = computed(() =>
configProvider.getPrefixCls('image-preview', props.previewPrefixCls),
);
const { getPrefixCls } = useConfigInject('image', props);
const prefixCls = computed(() => getPrefixCls('image-preview', props.previewPrefixCls));
return () => {
return (
<PreviewGroup

View File

@ -108,7 +108,6 @@ const TreeSelect = defineComponent({
const formItemContext = useInjectFormItemContext();
const {
configProvider,
prefixCls,
renderEmpty,
direction,
@ -125,12 +124,8 @@ const TreeSelect = defineComponent({
const choiceTransitionName = computed(() =>
getTransitionName(rootPrefixCls.value, '', props.choiceTransitionName),
);
const treePrefixCls = computed(() =>
configProvider.getPrefixCls('select-tree', props.prefixCls),
);
const treeSelectPrefixCls = computed(() =>
configProvider.getPrefixCls('tree-select', props.prefixCls),
);
const treePrefixCls = computed(() => getPrefixCls('select-tree', props.prefixCls));
const treeSelectPrefixCls = computed(() => getPrefixCls('tree-select', props.prefixCls));
const mergedDropdownClassName = computed(() =>
classNames(props.dropdownClassName, `${treeSelectPrefixCls.value}-dropdown`, {

View File

@ -0,0 +1,99 @@
import canUseDom from '../../_util/canUseDom';
const MARK_KEY = `vc-util-key` as any;
function getMark({ mark }: Options = {}) {
if (mark) {
return mark.startsWith('data-') ? mark : `data-${mark}`;
}
return MARK_KEY;
}
interface Options {
attachTo?: Element;
csp?: { nonce?: string };
prepend?: boolean;
mark?: string;
}
function getContainer(option: Options) {
if (option.attachTo) {
return option.attachTo;
}
const head = document.querySelector('head');
return head || document.body;
}
export function injectCSS(css: string, option: Options = {}) {
if (!canUseDom()) {
return null;
}
const styleNode = document.createElement('style');
if (option.csp?.nonce) {
styleNode.nonce = option.csp?.nonce;
}
styleNode.innerHTML = css;
const container = getContainer(option);
const { firstChild } = container;
if (option.prepend && container.prepend) {
// Use `prepend` first
container.prepend(styleNode);
} else if (option.prepend && firstChild) {
// Fallback to `insertBefore` like IE not support `prepend`
container.insertBefore(styleNode, firstChild);
} else {
container.appendChild(styleNode);
}
return styleNode;
}
const containerCache = new Map<Element, Node & ParentNode>();
function findExistNode(key: string, option: Options = {}) {
const container = getContainer(option);
return Array.from(containerCache.get(container).children).find(
node => node.tagName === 'STYLE' && node.getAttribute(getMark(option)) === key,
) as HTMLStyleElement;
}
export function removeCSS(key: string, option: Options = {}) {
const existNode = findExistNode(key, option);
existNode?.parentNode?.removeChild(existNode);
}
export function updateCSS(css: string, key: string, option: Options = {}) {
const container = getContainer(option);
// Get real parent
if (!containerCache.has(container)) {
const placeholderStyle = injectCSS('', option);
const { parentNode } = placeholderStyle;
containerCache.set(container, parentNode);
parentNode.removeChild(placeholderStyle);
}
const existNode = findExistNode(key, option);
if (existNode) {
if (option.csp?.nonce && existNode.nonce !== option.csp?.nonce) {
existNode.nonce = option.csp?.nonce;
}
if (existNode.innerHTML !== css) {
existNode.innerHTML = css;
}
return existNode;
}
const newNode = injectCSS(css, option);
newNode.setAttribute(getMark(option), key);
return newNode;
}