diff --git a/components/spin/Spin.tsx b/components/spin/Spin.tsx
index f0500f9738..379e3c427b 100644
--- a/components/spin/Spin.tsx
+++ b/components/spin/Spin.tsx
@@ -1,10 +1,11 @@
import type { VNode, ExtractPropTypes, PropType } from 'vue';
-import { inject, cloneVNode, isVNode, defineComponent, nextTick } from 'vue';
+import { cloneVNode, isVNode, defineComponent, shallowRef, watch } from 'vue';
import debounce from 'lodash-es/debounce';
import PropTypes from '../_util/vue-types';
-import { getComponent, getSlot } from '../_util/props-util';
+import { filterEmpty, getPropsSlot } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps';
-import { defaultConfigProvider, configProviderKey } from '../config-provider/context';
+import useStyle from './style';
+import useConfigInject from '../config-provider/hooks/useConfigInject';
export type SpinSize = 'small' | 'default' | 'large';
export const spinProps = () => ({
@@ -40,133 +41,103 @@ export default defineComponent({
spinning: true,
wrapperClassName: '',
}),
- setup() {
- return {
- originalUpdateSpinning: null,
- configProvider: inject(configProviderKey, defaultConfigProvider),
- };
- },
- data() {
- const { spinning, delay } = this;
- const shouldBeDelayed = shouldDelay(spinning, delay);
- return {
- sSpinning: spinning && !shouldBeDelayed,
- };
- },
- created() {
- this.originalUpdateSpinning = this.updateSpinning;
- this.debouncifyUpdateSpinning(this.$props);
- },
- mounted() {
- this.updateSpinning();
- },
- updated() {
- nextTick(() => {
- this.debouncifyUpdateSpinning();
- this.updateSpinning();
- });
- },
- beforeUnmount() {
- this.cancelExistingSpin();
- },
- methods: {
- debouncifyUpdateSpinning(props?: any) {
- const { delay } = props || this.$props;
- if (delay) {
- this.cancelExistingSpin();
- this.updateSpinning = debounce(this.originalUpdateSpinning, delay);
- }
- },
- updateSpinning() {
- const { spinning, sSpinning } = this;
- if (sSpinning !== spinning) {
- this.sSpinning = spinning;
- }
- },
- cancelExistingSpin() {
- const { updateSpinning } = this;
- if (updateSpinning && (updateSpinning as any).cancel) {
- (updateSpinning as any).cancel();
- }
- },
- renderIndicator(prefixCls: string) {
- const dotClassName = `${prefixCls}-dot`;
- let indicator = getComponent(this, 'indicator');
- // should not be render default indicator when indicator value is null
- if (indicator === null) {
- return null;
+ setup(props, { attrs, slots }) {
+ const { prefixCls, size, direction } = useConfigInject('spin', props);
+ const [wrapSSR, hashId] = useStyle(prefixCls);
+ const sSpinning = shallowRef(props.spinning && shouldDelay(props.spinning, props.delay));
+ let updateSpinning: any;
+ function originalUpdateSpinning() {
+ if (sSpinning.value !== props.spinning) {
+ sSpinning.value = props.spinning;
}
- if (Array.isArray(indicator)) {
- indicator = indicator.length === 1 ? indicator[0] : indicator;
- }
- if (isVNode(indicator)) {
- return cloneVNode(indicator, { class: dotClassName });
+ }
+ function cancelExistingSpin() {
+ if (updateSpinning && updateSpinning.cancel) {
+ updateSpinning.cancel();
}
-
- if (defaultIndicator && isVNode(defaultIndicator())) {
- return cloneVNode(defaultIndicator(), { class: dotClassName });
+ }
+ function debouncifyUpdateSpinning() {
+ const { delay } = props;
+ if (delay) {
+ cancelExistingSpin();
+ updateSpinning = debounce(originalUpdateSpinning, delay);
+ } else {
+ updateSpinning = originalUpdateSpinning;
}
-
- return (
-
-
-
-
-
-
- );
- },
- },
- render() {
- const {
- size,
- prefixCls: customizePrefixCls,
- tip = this.$slots.tip?.(),
- wrapperClassName,
- } = this.$props;
- const { class: cls, style, ...divProps } = this.$attrs;
- const { getPrefixCls, direction } = this.configProvider;
- const prefixCls = getPrefixCls('spin', customizePrefixCls);
-
- const { sSpinning } = this;
- const spinClassName = {
- [prefixCls]: true,
- [`${prefixCls}-sm`]: size === 'small',
- [`${prefixCls}-lg`]: size === 'large',
- [`${prefixCls}-spinning`]: sSpinning,
- [`${prefixCls}-show-text`]: !!tip,
- [`${prefixCls}-rtl`]: direction === 'rtl',
- [cls as string]: !!cls,
- };
-
- const spinElement = (
-
- {this.renderIndicator(prefixCls)}
- {tip ?
{tip}
: null}
-
+ }
+ watch(
+ () => [props.spinning, props.delay],
+ () => {
+ debouncifyUpdateSpinning();
+ updateSpinning?.();
+ },
+ {
+ immediate: true,
+ },
);
- const children = getSlot(this);
- if (children && children.length) {
- const containerClassName = {
- [`${prefixCls}-container`]: true,
- [`${prefixCls}-blur`]: sSpinning,
+ return () => {
+ const { class: cls, ...divProps } = attrs;
+ const { tip = slots.tip?.() } = props;
+ const children = slots.default?.();
+ const spinClassName = {
+ [hashId.value]: true,
+ [prefixCls.value]: true,
+ [`${prefixCls.value}-sm`]: size.value === 'small',
+ [`${prefixCls.value}-lg`]: size.value === 'large',
+ [`${prefixCls.value}-spinning`]: sSpinning.value,
+ [`${prefixCls.value}-show-text`]: !!tip,
+ [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
+ [cls as string]: !!cls,
};
- return (
-
- {sSpinning &&
{spinElement}
}
-
- {children}
-
+ function renderIndicator(prefixCls: string) {
+ const dotClassName = `${prefixCls}-dot`;
+ let indicator = getPropsSlot(slots, props, 'indicator');
+ // should not be render default indicator when indicator value is null
+ if (indicator === null) {
+ return null;
+ }
+ if (Array.isArray(indicator)) {
+ indicator = indicator.length === 1 ? indicator[0] : indicator;
+ }
+ if (isVNode(indicator)) {
+ return cloneVNode(indicator, { class: dotClassName });
+ }
+
+ if (defaultIndicator && isVNode(defaultIndicator())) {
+ return cloneVNode(defaultIndicator(), { class: dotClassName });
+ }
+
+ return (
+
+
+
+
+
+
+ );
+ }
+ const spinElement = (
+
+ {renderIndicator(prefixCls.value)}
+ {tip ?
{tip}
: null}
);
- }
- return spinElement;
+ if (children && filterEmpty(children).length) {
+ const containerClassName = {
+ [`${prefixCls.value}-container`]: true,
+ [`${prefixCls.value}-blur`]: sSpinning.value,
+ };
+ return wrapSSR(
+
+ {sSpinning.value &&
{spinElement}
}
+
+ {children}
+
+
,
+ );
+ }
+ return wrapSSR(spinElement);
+ };
},
});
diff --git a/components/spin/style/index.less b/components/spin/style/index.less
deleted file mode 100644
index 2dee02e28e..0000000000
--- a/components/spin/style/index.less
+++ /dev/null
@@ -1,218 +0,0 @@
-@import '../../style/themes/index';
-@import '../../style/mixins/index';
-
-@spin-prefix-cls: ~'@{ant-prefix}-spin';
-@spin-dot-default: @text-color-secondary;
-
-.@{spin-prefix-cls} {
- .reset-component();
-
- position: absolute;
- display: none;
- color: @primary-color;
- text-align: center;
- vertical-align: middle;
- opacity: 0;
- transition: transform 0.3s @ease-in-out-circ;
-
- &-spinning {
- position: static;
- display: inline-block;
- opacity: 1;
- }
-
- &-nested-loading {
- position: relative;
- > div > .@{spin-prefix-cls} {
- position: absolute;
- top: 0;
- left: 0;
- z-index: 4;
- display: block;
- width: 100%;
- height: 100%;
- max-height: 400px;
- .@{spin-prefix-cls}-dot {
- position: absolute;
- top: 50%;
- left: 50%;
- margin: -(@spin-dot-size / 2);
- }
- .@{spin-prefix-cls}-text {
- position: absolute;
- top: 50%;
- width: 100%;
- padding-top: ((@spin-dot-size - @font-size-base) / 2) + 2px;
- text-shadow: 0 1px 2px @shadow-color-inverse;
- }
- &.@{spin-prefix-cls}-show-text .@{spin-prefix-cls}-dot {
- margin-top: -(@spin-dot-size / 2) - 10px;
- }
- }
-
- > div > .@{spin-prefix-cls}-sm {
- .@{spin-prefix-cls}-dot {
- margin: -(@spin-dot-size-sm / 2);
- }
- .@{spin-prefix-cls}-text {
- padding-top: ((@spin-dot-size-sm - @font-size-base) / 2) + 2px;
- }
- &.@{spin-prefix-cls}-show-text .@{spin-prefix-cls}-dot {
- margin-top: -(@spin-dot-size-sm / 2) - 10px;
- }
- }
-
- > div > .@{spin-prefix-cls}-lg {
- .@{spin-prefix-cls}-dot {
- margin: -(@spin-dot-size-lg / 2);
- }
- .@{spin-prefix-cls}-text {
- padding-top: ((@spin-dot-size-lg - @font-size-base) / 2) + 2px;
- }
- &.@{spin-prefix-cls}-show-text .@{spin-prefix-cls}-dot {
- margin-top: -(@spin-dot-size-lg / 2) - 10px;
- }
- }
- }
-
- &-container {
- position: relative;
- transition: opacity 0.3s;
-
- &::after {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 10;
- display: ~'none \9';
- width: 100%;
- height: 100%;
- background: @component-background;
- opacity: 0;
- transition: all 0.3s;
- content: '';
- pointer-events: none;
- }
- }
-
- &-blur {
- clear: both;
- opacity: 0.5;
- user-select: none;
- pointer-events: none;
-
- &::after {
- opacity: 0.4;
- pointer-events: auto;
- }
- }
-
- // tip
- // ------------------------------
- &-tip {
- color: @spin-dot-default;
- }
-
- // dots
- // ------------------------------
-
- &-dot {
- position: relative;
- display: inline-block;
- font-size: @spin-dot-size;
-
- .square(1em);
-
- &-item {
- position: absolute;
- display: block;
- width: 9px;
- height: 9px;
- background-color: @primary-color;
- border-radius: 100%;
- transform: scale(0.75);
- transform-origin: 50% 50%;
- opacity: 0.3;
- animation: antSpinMove 1s infinite linear alternate;
-
- &:nth-child(1) {
- top: 0;
- left: 0;
- }
-
- &:nth-child(2) {
- top: 0;
- right: 0;
- animation-delay: 0.4s;
- }
-
- &:nth-child(3) {
- right: 0;
- bottom: 0;
- animation-delay: 0.8s;
- }
-
- &:nth-child(4) {
- bottom: 0;
- left: 0;
- animation-delay: 1.2s;
- }
- }
-
- &-spin {
- transform: rotate(0deg);
- animation: antRotate 1.2s infinite linear;
- }
- }
-
- // Sizes
- // ------------------------------
-
- // small
- &-sm &-dot {
- font-size: @spin-dot-size-sm;
-
- i {
- width: 6px;
- height: 6px;
- }
- }
-
- // large
- &-lg &-dot {
- font-size: @spin-dot-size-lg;
-
- i {
- width: 14px;
- height: 14px;
- }
- }
-
- &&-show-text &-text {
- display: block;
- }
-}
-
-@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
- /* IE10+ */
- .@{spin-prefix-cls}-blur {
- background: @component-background;
- opacity: 0.5;
- }
-}
-
-@keyframes antSpinMove {
- to {
- opacity: 1;
- }
-}
-
-@keyframes antRotate {
- to {
- transform: rotate(360deg);
- }
-}
-
-@import './rtl';
diff --git a/components/spin/style/index.ts b/components/spin/style/index.ts
new file mode 100644
index 0000000000..52187ca7d7
--- /dev/null
+++ b/components/spin/style/index.ts
@@ -0,0 +1,241 @@
+import type { CSSObject } from '../../_util/cssinjs';
+import { Keyframes } from '../../_util/cssinjs';
+import type { FullToken, GenerateStyle } from '../../theme/internal';
+import { genComponentStyleHook, mergeToken } from '../../theme/internal';
+import { resetComponent } from '../../_style';
+
+export interface ComponentToken {
+ contentHeight: number;
+}
+
+interface SpinToken extends FullToken<'Spin'> {
+ spinDotDefault: string;
+ spinDotSize: number;
+ spinDotSizeSM: number;
+ spinDotSizeLG: number;
+}
+
+const antSpinMove = new Keyframes('antSpinMove', {
+ to: { opacity: 1 },
+});
+
+const antRotate = new Keyframes('antRotate', {
+ to: { transform: 'rotate(405deg)' },
+});
+
+const genSpinStyle: GenerateStyle
= (token: SpinToken): CSSObject => ({
+ [`${token.componentCls}`]: {
+ ...resetComponent(token),
+ position: 'absolute',
+ display: 'none',
+ color: token.colorPrimary,
+ textAlign: 'center',
+ verticalAlign: 'middle',
+ opacity: 0,
+ transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOutCirc}`,
+
+ '&-spinning': {
+ position: 'static',
+ display: 'inline-block',
+ opacity: 1,
+ },
+
+ '&-nested-loading': {
+ position: 'relative',
+ [`> div > ${token.componentCls}`]: {
+ position: 'absolute',
+ top: 0,
+ insetInlineStart: 0,
+ zIndex: 4,
+ display: 'block',
+ width: '100%',
+ height: '100%',
+ maxHeight: token.contentHeight,
+
+ [`${token.componentCls}-dot`]: {
+ position: 'absolute',
+ top: '50%',
+ insetInlineStart: '50%',
+ margin: -token.spinDotSize / 2,
+ },
+
+ [`${token.componentCls}-text`]: {
+ position: 'absolute',
+ top: '50%',
+ width: '100%',
+ paddingTop: (token.spinDotSize - token.fontSize) / 2 + 2,
+ textShadow: `0 1px 2px ${token.colorBgContainer}`, // FIXME: shadow
+ },
+
+ [`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {
+ marginTop: -(token.spinDotSize / 2) - 10,
+ },
+
+ '&-sm': {
+ [`${token.componentCls}-dot`]: {
+ margin: -token.spinDotSizeSM / 2,
+ },
+ [`${token.componentCls}-text`]: {
+ paddingTop: (token.spinDotSizeSM - token.fontSize) / 2 + 2,
+ },
+ [`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {
+ marginTop: -(token.spinDotSizeSM / 2) - 10,
+ },
+ },
+
+ '&-lg': {
+ [`${token.componentCls}-dot`]: {
+ margin: -(token.spinDotSizeLG / 2),
+ },
+ [`${token.componentCls}-text`]: {
+ paddingTop: (token.spinDotSizeLG - token.fontSize) / 2 + 2,
+ },
+ [`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {
+ marginTop: -(token.spinDotSizeLG / 2) - 10,
+ },
+ },
+ },
+
+ [`${token.componentCls}-container`]: {
+ position: 'relative',
+ transition: `opacity ${token.motionDurationSlow}`,
+
+ '&::after': {
+ position: 'absolute',
+ top: 0,
+ insetInlineEnd: 0,
+ bottom: 0,
+ insetInlineStart: 0,
+ zIndex: 10,
+ width: '100%',
+ height: '100%',
+ background: token.colorBgContainer,
+ opacity: 0,
+ transition: `all ${token.motionDurationSlow}`,
+ content: '""',
+ pointerEvents: 'none',
+ },
+ },
+
+ [`${token.componentCls}-blur`]: {
+ clear: 'both',
+ opacity: 0.5,
+ userSelect: 'none',
+ pointerEvents: 'none',
+
+ [`&::after`]: {
+ opacity: 0.4,
+ pointerEvents: 'auto',
+ },
+ },
+ },
+
+ // tip
+ // ------------------------------
+ [`&-tip`]: {
+ color: token.spinDotDefault,
+ },
+
+ // dots
+ // ------------------------------
+ [`${token.componentCls}-dot`]: {
+ position: 'relative',
+ display: 'inline-block',
+ fontSize: token.spinDotSize,
+ width: '1em',
+ height: '1em',
+
+ '&-item': {
+ position: 'absolute',
+ display: 'block',
+ width: (token.spinDotSize - token.marginXXS / 2) / 2,
+ height: (token.spinDotSize - token.marginXXS / 2) / 2,
+ backgroundColor: token.colorPrimary,
+ borderRadius: '100%',
+ transform: 'scale(0.75)',
+ transformOrigin: '50% 50%',
+ opacity: 0.3,
+ animationName: antSpinMove,
+ animationDuration: '1s',
+ animationIterationCount: 'infinite',
+ animationTimingFunction: 'linear',
+ animationDirection: 'alternate',
+
+ '&:nth-child(1)': {
+ top: 0,
+ insetInlineStart: 0,
+ },
+
+ '&:nth-child(2)': {
+ top: 0,
+ insetInlineEnd: 0,
+ animationDelay: '0.4s',
+ },
+
+ '&:nth-child(3)': {
+ insetInlineEnd: 0,
+ bottom: 0,
+ animationDelay: '0.8s',
+ },
+
+ '&:nth-child(4)': {
+ bottom: 0,
+ insetInlineStart: 0,
+ animationDelay: '1.2s',
+ },
+ },
+
+ '&-spin': {
+ transform: 'rotate(45deg)',
+ animationName: antRotate,
+ animationDuration: '1.2s',
+ animationIterationCount: 'infinite',
+ animationTimingFunction: 'linear',
+ },
+ },
+
+ // Sizes
+ // ------------------------------
+
+ // small
+ [`&-sm ${token.componentCls}-dot`]: {
+ fontSize: token.spinDotSizeSM,
+
+ i: {
+ width: (token.spinDotSizeSM - token.marginXXS / 2) / 2,
+ height: (token.spinDotSizeSM - token.marginXXS / 2) / 2,
+ },
+ },
+
+ // large
+ [`&-lg ${token.componentCls}-dot`]: {
+ fontSize: token.spinDotSizeLG,
+
+ i: {
+ width: (token.spinDotSizeLG - token.marginXXS) / 2,
+ height: (token.spinDotSizeLG - token.marginXXS) / 2,
+ },
+ },
+
+ [`&${token.componentCls}-show-text ${token.componentCls}-text`]: {
+ display: 'block',
+ },
+ },
+});
+
+// ============================== Export ==============================
+export default genComponentStyleHook(
+ 'Spin',
+ token => {
+ const spinToken = mergeToken(token, {
+ spinDotDefault: token.colorTextDescription,
+ spinDotSize: token.controlHeightLG / 2,
+ spinDotSizeSM: token.controlHeightLG * 0.35,
+ spinDotSizeLG: token.controlHeight,
+ });
+ return [genSpinStyle(spinToken)];
+ },
+ {
+ contentHeight: 400,
+ },
+);
diff --git a/components/spin/style/index.tsx b/components/spin/style/index.tsx
deleted file mode 100644
index 3a3ab0de59..0000000000
--- a/components/spin/style/index.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-import '../../style/index.less';
-import './index.less';
diff --git a/components/spin/style/rtl.less b/components/spin/style/rtl.less
deleted file mode 100644
index 03fb9b257c..0000000000
--- a/components/spin/style/rtl.less
+++ /dev/null
@@ -1,20 +0,0 @@
-.@{spin-prefix-cls} {
- &-rtl {
- direction: rtl;
- }
-
- &-dot {
- &-spin {
- .@{spin-prefix-cls}-rtl & {
- transform: rotate(-45deg);
- animation-name: antRotateRtl;
- }
- }
- }
-}
-
-@keyframes antRotateRtl {
- to {
- transform: rotate(-405deg);
- }
-}
diff --git a/components/style.ts b/components/style.ts
index 6bfc80e3c5..606436b77a 100644
--- a/components/style.ts
+++ b/components/style.ts
@@ -22,7 +22,7 @@ import './collapse/style';
import './carousel/style';
// import './notification/style';
// import './message/style';
-import './spin/style';
+// import './spin/style';
import './select/style';
import './switch/style';
import './auto-complete/style';
diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts
index f285f04dbd..cb19bb3792 100644
--- a/components/theme/interface/components.ts
+++ b/components/theme/interface/components.ts
@@ -35,7 +35,7 @@ import type { ComponentToken as NotificationComponentToken } from '../../notific
// import type { ComponentToken as SkeletonComponentToken } from '../../skeleton/style';
// import type { ComponentToken as SliderComponentToken } from '../../slider/style';
// import type { ComponentToken as SpaceComponentToken } from '../../space/style';
-// import type { ComponentToken as SpinComponentToken } from '../../spin/style';
+import type { ComponentToken as SpinComponentToken } from '../../spin/style';
// import type { ComponentToken as StepsComponentToken } from '../../steps/style';
// import type { ComponentToken as TableComponentToken } from '../../table/style';
// import type { ComponentToken as TabsComponentToken } from '../../tabs/style';
@@ -90,7 +90,7 @@ export interface ComponentTokenMap {
// Select?: SelectComponentToken;
// Skeleton?: SkeletonComponentToken;
// Slider?: SliderComponentToken;
- // Spin?: SpinComponentToken;
+ Spin?: SpinComponentToken;
Statistic?: {};
Switch?: {};
// Tag?: TagComponentToken;