diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 32718a94d1..00c687e3b4 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,4 +1,4 @@
-blank_issues_enabled: false
+blank_issues_enabled: true
contact_links:
- name: Create new issue
url: https://vuecomponent.github.io/issue-helper/
diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md
index e955042384..84d4ffa90c 100644
--- a/CHANGELOG.en-US.md
+++ b/CHANGELOG.en-US.md
@@ -10,6 +10,27 @@
---
+## 4.2.6
+
+- 🐞 Fix Modal component aria-hidden error problem under chrome [#7823](https://github.com/vueComponent/ant-design-vue/issues/7823)
+- 🐞 Fix the problem that the built-in input method of Safari automatically fills in the decimal point when inputting Chinese [#7918](https://github.com/vueComponent/ant-design-vue/issues/7918)
+- 🐞 Fix InputNumber component disabled style problem [#7776](https://github.com/vueComponent/ant-design-vue/issues/7776)
+- 🐞 Fix Select cannot lose focus problem [#7819](https://github.com/vueComponent/ant-design-vue/issues/7819)
+
+## 4.2.5
+
+- 🐞 Fix Empty component memory leak problem
+- 🐞 Fix Image width & height property not working problem
+
+## 4.2.4
+
+- 🐞 Fix Wave memory leak problem
+
+## 4.2.3
+
+- 🌟 TourStep custom Button, support function children [#7628](https://github.com/vueComponent/ant-design-vue/pull/7628)
+- 🐞 Fix the problem that the input value is hidden in Select and Cascader search multi-select mode [#7640](https://github.com/vueComponent/ant-design-vue/issues/7640)
+
## 4.2.2
- 🐞 Fix TreeSelect placeholder slot invalid [#7545](https://github.com/vueComponent/ant-design-vue/issues/7545)
diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md
index f4f51e14b9..c9b50d9631 100644
--- a/CHANGELOG.zh-CN.md
+++ b/CHANGELOG.zh-CN.md
@@ -10,6 +10,27 @@
---
+## 4.2.6
+
+- 🐞 修复 Modal 组件在 chrome 下,aria-hidden 报错问题 [#7823](https://github.com/vueComponent/ant-design-vue/issues/7823)
+- 🐞 修复 Safari 下自带输入法 input 组件输入中文时,自动填写小数点问题 [#7918](https://github.com/vueComponent/ant-design-vue/issues/7918)
+- 🐞 修复 InputNumber 组件 disabled 样式问题 [#7776](https://github.com/vueComponent/ant-design-vue/issues/7776)
+- 🐞 修复 Select 无法失焦问题 [#7819](https://github.com/vueComponent/ant-design-vue/issues/7819)
+
+## 4.2.5
+
+- 🐞 修复 Empty 组件内存泄漏问题
+- 🐞 修复 Image width & height 属性不生效问题
+
+## 4.2.4
+
+- 🐞 修复 Wave 内存泄漏问题
+
+## 4.2.3
+
+- 🌟 TourStep 自定义 Button,支持函数 children [#7628](https://github.com/vueComponent/ant-design-vue/pull/7628)
+- 🐞 修复 Select 和 Cascader 搜索多选模式下,输入值被隐藏问题 [#7640](https://github.com/vueComponent/ant-design-vue/issues/7640)
+
## 4.2.2
- 🐞 修复 TreeSelect placeholder 插槽无效 [#7545](https://github.com/vueComponent/ant-design-vue/issues/7545)
diff --git a/README-zh_CN.md b/README-zh_CN.md
index c672231073..05a8f70302 100644
--- a/README-zh_CN.md
+++ b/README-zh_CN.md
@@ -73,6 +73,7 @@ $ yarn add ant-design-vue
| [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | 在 DOM 模板中,您可以使用 ant-design-vue 组件的自定义事件(camelCase) |
| [@formily/antdv](https://github.com/formilyjs/antdv) | 这是一个结合了 Formily 和 ant-design-vue 的组件库 |
| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | ant-design-vue 的 nuxt 模块扩展 |
+| [ant-design-x-vue](https://github.com/wzc520pyfm/ant-design-x-vue) | 基于 Ant Design X 设计规范的 Vue AI 界面解决方案 |
## 问答
diff --git a/README.md b/README.md
index 853c16791b..3bc3fb0fc3 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,7 @@ If you are in a bad network environment, you can try other registries and tools
| [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | The library function, implemented in the DOM template, can use the custom event of the ant-design-vue component (camelCase) |
| [@formily/antdv](https://github.com/formilyjs/antdv) | The Library with Formily and ant-design-vue |
| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | A nuxt module for ant-design-vue |
+| [ant-design-x-vue](https://github.com/wzc520pyfm/ant-design-x-vue) | A Vue AI interface solutions base on the Ant Design X design specification |
## Donation
diff --git a/components/_util/transition.tsx b/components/_util/transition.tsx
index fd6ebc4904..f4b69f31ed 100644
--- a/components/_util/transition.tsx
+++ b/components/_util/transition.tsx
@@ -5,7 +5,7 @@ import type {
TransitionGroupProps,
TransitionProps,
} from 'vue';
-import { nextTick, Transition, TransitionGroup } from 'vue';
+import { nextTick } from 'vue';
import { tuple } from './type';
const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight');
@@ -126,6 +126,4 @@ const getTransitionName = (rootPrefixCls: string, motion: string, transitionName
return `${rootPrefixCls}-${motion}`;
};
-export { Transition, TransitionGroup, collapseMotion, getTransitionName, getTransitionDirection };
-
-export default Transition;
+export { collapseMotion, getTransitionName, getTransitionDirection };
diff --git a/components/_util/wave/WaveEffect.tsx b/components/_util/wave/WaveEffect.tsx
index 1f3ce23d17..b59c8ff66a 100644
--- a/components/_util/wave/WaveEffect.tsx
+++ b/components/_util/wave/WaveEffect.tsx
@@ -159,6 +159,12 @@ function showWaveEffect(node: HTMLElement, className: string) {
node?.insertBefore(holder, node?.firstChild);
render(, holder);
+ return () => {
+ render(null, holder);
+ if (holder.parentElement) {
+ holder.parentElement.removeChild(holder);
+ }
+ };
}
export default showWaveEffect;
diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx
index 26ac1aff5e..26dab40f9c 100644
--- a/components/_util/wave/index.tsx
+++ b/components/_util/wave/index.tsx
@@ -33,13 +33,12 @@ export default defineComponent({
// =============================== Wave ===============================
const showWave = useWave(
- instance,
computed(() => classNames(prefixCls.value, hashId.value)),
wave,
);
let onClick: (e: MouseEvent) => void;
const clear = () => {
- const node = findDOMNode(instance);
+ const node = findDOMNode(instance) as HTMLElement;
node.removeEventListener('click', onClick, true);
};
onMounted(() => {
diff --git a/components/_util/wave/useWave.ts b/components/_util/wave/useWave.ts
index 84e2a6effb..f88c361688 100644
--- a/components/_util/wave/useWave.ts
+++ b/components/_util/wave/useWave.ts
@@ -1,21 +1,25 @@
-import type { ComponentInternalInstance, ComputedRef, Ref } from 'vue';
+import type { ComputedRef, Ref } from 'vue';
+import { onBeforeUnmount, getCurrentInstance } from 'vue';
import { findDOMNode } from '../props-util';
import showWaveEffect from './WaveEffect';
export default function useWave(
- instance: ComponentInternalInstance | null,
className: Ref,
wave?: ComputedRef<{ disabled?: boolean }>,
): VoidFunction {
+ const instance = getCurrentInstance();
+ let stopWave: () => void;
function showWave() {
const node = findDOMNode(instance);
-
+ stopWave?.();
if (wave?.value?.disabled || !node) {
return;
}
-
- showWaveEffect(node, className.value);
+ stopWave = showWaveEffect(node, className.value);
}
+ onBeforeUnmount(() => {
+ stopWave?.();
+ });
return showWave;
}
diff --git a/components/alert/index.tsx b/components/alert/index.tsx
index 63a61aa6e0..f3dead2ba5 100644
--- a/components/alert/index.tsx
+++ b/components/alert/index.tsx
@@ -1,5 +1,5 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
-import { computed, defineComponent, shallowRef } from 'vue';
+import { computed, defineComponent, shallowRef, Transition } 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';
@@ -11,7 +11,7 @@ import InfoCircleFilled from '@ant-design/icons-vue/InfoCircleFilled';
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import classNames from '../_util/classNames';
import PropTypes from '../_util/vue-types';
-import { getTransitionProps, Transition } from '../_util/transition';
+import { getTransitionProps } from '../_util/transition';
import { isValidElement } from '../_util/props-util';
import { tuple, withInstall } from '../_util/type';
import { cloneElement } from '../_util/vnode';
diff --git a/components/badge/Badge.tsx b/components/badge/Badge.tsx
index 30195d049b..a0363732f4 100644
--- a/components/badge/Badge.tsx
+++ b/components/badge/Badge.tsx
@@ -3,9 +3,9 @@ import ScrollNumber from './ScrollNumber';
import classNames from '../_util/classNames';
import { getPropsSlot, flattenChildren } from '../_util/props-util';
import { cloneElement } from '../_util/vnode';
-import { getTransitionProps, Transition } from '../_util/transition';
+import { getTransitionProps } from '../_util/transition';
import type { ExtractPropTypes, CSSProperties, PropType } from 'vue';
-import { defineComponent, computed, ref, watch } from 'vue';
+import { defineComponent, computed, ref, watch, Transition } from 'vue';
import Ribbon from './Ribbon';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import isNumeric from '../_util/isNumeric';
diff --git a/components/button/LoadingIcon.tsx b/components/button/LoadingIcon.tsx
index 391527fd45..f865e09078 100644
--- a/components/button/LoadingIcon.tsx
+++ b/components/button/LoadingIcon.tsx
@@ -1,6 +1,5 @@
-import { defineComponent, nextTick } from 'vue';
+import { defineComponent, nextTick, Transition } from 'vue';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
-import Transition from '../_util/transition';
const getCollapsedWidth = (node: HTMLSpanElement) => {
if (node) {
node.style.width = '0px';
diff --git a/components/calendar/index.zh-CN.md b/components/calendar/index.zh-CN.md
index 3d0f68d1fd..725f2d5731 100644
--- a/components/calendar/index.zh-CN.md
+++ b/components/calendar/index.zh-CN.md
@@ -28,16 +28,16 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
-| dateCellRender | 作用域插槽,用来自定义渲染日期单元格,返回内容会被追加到单元格, | v-slot:dateCellRender="{current: dayjs}" | 无 | |
-| dateFullCellRender | 作用域插槽,自定义渲染日期单元格,返回内容覆盖单元格 | v-slot:dateFullCellRender="{current: dayjs}" | 无 | |
-| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | 无 | |
+| dateCellRender | 作用域插槽,用来自定义渲染日期单元格,返回内容会被追加到单元格, | v-slot:dateCellRender="{current: dayjs}" | - | |
+| dateFullCellRender | 作用域插槽,自定义渲染日期单元格,返回内容覆盖单元格 | v-slot:dateFullCellRender="{current: dayjs}" | - | |
+| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | |
| fullscreen | 是否全屏显示 | boolean | true | |
| headerRender | 自定义头部内容 | v-slot:headerRender="{value: dayjs, type: string, onChange: f(), onTypeChange: f()}" | - | |
| locale | 国际化配置 | object | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | |
| mode | 初始模式,`month/year` | string | month | |
-| monthCellRender | 作用域插槽,自定义渲染月单元格,返回内容会被追加到单元格 | v-slot:monthCellRender="{current: dayjs}" | 无 | |
-| monthFullCellRender | 作用域插槽,自定义渲染月单元格,返回内容覆盖单元格 | v-slot:monthFullCellRender="{current: dayjs}" | 无 | |
-| validRange | 设置可以显示的日期 | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | 无 | |
+| monthCellRender | 作用域插槽,自定义渲染月单元格,返回内容会被追加到单元格 | v-slot:monthCellRender="{current: dayjs}" | - | |
+| monthFullCellRender | 作用域插槽,自定义渲染月单元格,返回内容覆盖单元格 | v-slot:monthFullCellRender="{current: dayjs}" | - | |
+| validRange | 设置可以显示的日期 | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | - | |
| value(v-model) | 展示日期 | [dayjs](https://day.js.org/) | 当前日期 | |
| valueFormat | 可选,绑定值的格式,对 value、defaultValue 起作用。不指定则绑定值为 dayjs 对象 | string,[具体格式](https://day.js.org/docs/zh-CN/display/format) | - | |
@@ -45,8 +45,8 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA
| 事件名称 | 说明 | 回调参数 | |
| --- | --- | --- | --- | --- |
-| change | 日期变化时的回调, 面板变化有可能导致日期变化 | function(date: dayjs \| string) | 无 |
-| panelChange | 日期面板变化回调 | function(date: dayjs \| string, mode: string) | 无 |
+| change | 日期变化时的回调, 面板变化有可能导致日期变化 | function(date: dayjs \| string) | - |
+| panelChange | 日期面板变化回调 | function(date: dayjs \| string, mode: string) | - |
| select | 选择日期回调,包含来源信息 | function(date: Dayjs, info: { source: 'year' \| 'month' \| 'date' \| 'customize' }) | - | |
### 如何仅获取来自面板点击的日期?
diff --git a/components/collapse/CollapsePanel.tsx b/components/collapse/CollapsePanel.tsx
index 4ff984dd74..95af0ec645 100644
--- a/components/collapse/CollapsePanel.tsx
+++ b/components/collapse/CollapsePanel.tsx
@@ -2,8 +2,7 @@ import PanelContent from './PanelContent';
import { initDefaultProps } from '../_util/props-util';
import { collapsePanelProps } from './commonProps';
import type { ExtractPropTypes } from 'vue';
-import { defineComponent } from 'vue';
-import Transition from '../_util/transition';
+import { defineComponent, Transition } from 'vue';
import classNames from '../_util/classNames';
import devWarning from '../vc-util/devWarning';
import useConfigInject from '../config-provider/hooks/useConfigInject';
diff --git a/components/collapse/index.zh-CN.md b/components/collapse/index.zh-CN.md
index e9e1c042d7..ddae33295a 100644
--- a/components/collapse/index.zh-CN.md
+++ b/components/collapse/index.zh-CN.md
@@ -42,6 +42,6 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAA
| collapsible | 是否可折叠或指定可折叠触发区域 | `header` \| `disabled` | - | 3.0 |
| extra | 自定义渲染每个面板右上角的内容 | VNode \| slot | - | 1.5.0 |
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false | |
-| header | 面板头内容 | string\|slot | 无 | |
-| key | 对应 activeKey | string \| number | 无 | |
+| header | 面板头内容 | string\|slot | - | |
+| key | 对应 activeKey | string \| number | - | |
| showArrow | 是否展示当前面板上的箭头 | boolean | `true` | |
diff --git a/components/dropdown/index.zh-CN.md b/components/dropdown/index.zh-CN.md
index b66ae40489..1149da0a23 100644
--- a/components/dropdown/index.zh-CN.md
+++ b/components/dropdown/index.zh-CN.md
@@ -22,7 +22,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5qm4S4Zgh2QAAA
| 参数 | 说明 | 类型 | 默认值 | |
| --- | --- | --- | --- | --- |
-| align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | 无 | |
+| align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | - | |
| arrow | 下拉框箭头是否显示 | boolean \| { pointAtCenter: boolean } | false | 3.3.0 |
| destroyPopupOnHide | 关闭后是否销毁 Dropdown | boolean | false | 3.0 |
| disabled | 菜单是否禁用 | boolean | - | |
diff --git a/components/empty/index.tsx b/components/empty/index.tsx
index 5d73982bce..ab778c9e65 100644
--- a/components/empty/index.tsx
+++ b/components/empty/index.tsx
@@ -1,4 +1,4 @@
-import { defineComponent } from 'vue';
+import { defineComponent, h } from 'vue';
import type { CSSProperties, ExtractPropTypes } from 'vue';
import classNames from '../_util/classNames';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
@@ -11,9 +11,6 @@ import useConfigInject from '../config-provider/hooks/useConfigInject';
import useStyle from './style';
-const defaultEmptyImg = ;
-const simpleEmptyImg = ;
-
interface Locale {
description?: string;
}
@@ -40,13 +37,16 @@ const Empty = defineComponent({
return () => {
const prefixCls = prefixClsRef.value;
const {
- image = slots.image?.() || defaultEmptyImg,
+ image: mergedImage = slots.image?.() || h(DefaultEmptyImg),
description = slots.description?.() || undefined,
imageStyle,
class: className = '',
...restProps
} = { ...props, ...attrs };
-
+ const image =
+ typeof mergedImage === 'function' ? (mergedImage as () => VueNode)() : mergedImage;
+ const isNormal =
+ typeof image === 'object' && 'type' in image && (image.type as any).PRESENTED_IMAGE_SIMPLE;
return wrapSSR(
h(DefaultEmptyImg);
+Empty.PRESENTED_IMAGE_SIMPLE = () => h(SimpleEmptyImg);
export default withInstall(Empty);
diff --git a/components/float-button/BackTop.tsx b/components/float-button/BackTop.tsx
index 6baa6daad4..7229059752 100644
--- a/components/float-button/BackTop.tsx
+++ b/components/float-button/BackTop.tsx
@@ -1,5 +1,5 @@
import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined';
-import { getTransitionProps, Transition } from '../_util/transition';
+import { getTransitionProps } from '../_util/transition';
import {
defineComponent,
nextTick,
@@ -10,6 +10,7 @@ import {
ref,
watch,
onDeactivated,
+ Transition,
} from 'vue';
import FloatButton, { floatButtonPrefixCls } from './FloatButton';
import useConfigInject from '../config-provider/hooks/useConfigInject';
diff --git a/components/float-button/FloatButtonGroup.tsx b/components/float-button/FloatButtonGroup.tsx
index 743d60e5ad..611646299d 100644
--- a/components/float-button/FloatButtonGroup.tsx
+++ b/components/float-button/FloatButtonGroup.tsx
@@ -1,8 +1,8 @@
-import { defineComponent, ref, computed, watch, onBeforeUnmount } from 'vue';
+import { defineComponent, ref, computed, watch, onBeforeUnmount, Transition } from 'vue';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import FileTextOutlined from '@ant-design/icons-vue/FileTextOutlined';
import classNames from '../_util/classNames';
-import { getTransitionProps, Transition } from '../_util/transition';
+import { getTransitionProps } from '../_util/transition';
import FloatButton, { floatButtonPrefixCls } from './FloatButton';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import { useProvideFloatButtonGroupContext } from './context';
diff --git a/components/float-button/index.zh-CN.md b/components/float-button/index.zh-CN.md
index 589c658653..284cd6842b 100644
--- a/components/float-button/index.zh-CN.md
+++ b/components/float-button/index.zh-CN.md
@@ -33,11 +33,11 @@ tag: New
| target | 相当于 a 标签的 target 属性,href 存在时生效 | string | - | |
| badge | 带徽标数字的悬浮按钮(不支持 status 以及相关属性) | [BadgeProps](/components/badge-cn#api) | - | |
-### common events
+### 共同的事件
-| 事件名称 | 说明 | 回调参数 | 版本 |
-| -------- | --------------------------------------- | ----------------- | ---- |
-| click | Set the handler to handle `click` event | `(event) => void` | - |
+| 事件名称 | 说明 | 回调参数 | 版本 |
+| -------- | ----------------------------- | ----------------- | ---- |
+| click | 设置处理 `click` 事件的处理器 | `(event) => void` | - |
### FloatButton.Group
@@ -47,7 +47,7 @@ tag: New
| trigger | 触发方式(有触发方式为菜单模式) | `click` \| `hover` | - | |
| open(v-model) | 受控展开 | boolean | - | |
-### FloatButton.Group Events
+### FloatButton.Group 事件
| 事件名称 | 说明 | 回调参数 | 版本 |
| ---------- | ---------------- | ----------------------- | ---- |
diff --git a/components/icon/index.en-US.md b/components/icon/index.en-US.md
index 758950f77b..7d753e2c33 100644
--- a/components/icon/index.en-US.md
+++ b/components/icon/index.en-US.md
@@ -122,6 +122,8 @@ See [iconfont.cn documents](http://iconfont.cn/help/detail?spm=a313x.7781069.199
### Custom SVG Icon
+#### vue cli 3
+
You can import SVG icon as an vue component by using `vue cli 3` and [`vue-svg-loader`](https://www.npmjs.com/package/vue-svg-loader). `vue-svg-loader`'s `options` [reference](https://github.com/visualfanatic/vue-svg-loader).
```js
@@ -149,6 +151,84 @@ export default defineComponent({
});
```
+#### Rsbuild
+
+Rsbuild is a new generation of build tool, official website https://rsbuild.dev/
+Create your own `vue-svg-loader.js` file, which allows you to customize and beautify SVG, and then configure it in `rsbuild.config.ts`
+
+```js
+// vue-svg-loader.js
+/* eslint-disable */
+const { optimize } = require('svgo');
+const { version } = require('vue');
+const semverMajor = require('semver/functions/major');
+
+module.exports = async function (svg) {
+ const callback = this.async();
+
+ try {
+ ({ data: svg } = await optimize(svg, {
+ path: this.resourcePath,
+ js2svg: {
+ indent: 2,
+ pretty: true,
+ },
+ plugins: [
+ 'convertStyleToAttrs',
+ 'removeDoctype',
+ 'removeXMLProcInst',
+ 'removeComments',
+ 'removeMetadata',
+ 'removeTitle',
+ 'removeDesc',
+ 'removeStyleElement',
+ 'removeXMLNS',
+ 'removeXMLProcInst',
+ ],
+ }));
+ } catch (error) {
+ callback(error);
+ return;
+ }
+
+ if (semverMajor(version) === 2) {
+ svg = svg.replace('