naive-ui
Version:
A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast
508 lines • 19.8 kB
JavaScript
import { changeColor } from 'seemly';
import { useMemo } from 'vooks';
import { computed, defineComponent, h, inject, ref, watchEffect } from 'vue';
import { NBaseLoading, NBaseWave, NFadeInExpandTransition, NIconSwitchTransition } from "../../_internal/index.mjs";
import { useConfig, useFormItem, useTheme, useThemeClass } from "../../_mixins/index.mjs";
import { useRtl } from "../../_mixins/use-rtl.mjs";
import { call, color2Class, createKey, isSlotEmpty, resolveWrappedSlot, warnOnce } from "../../_utils/index.mjs";
import { createHoverColor, createPressedColor } from "../../_utils/color/index.mjs";
import { isSafari } from "../../_utils/env/browser.mjs";
import { buttonGroupInjectionKey } from "../../button-group/src/context.mjs";
import { buttonLight } from "../styles/index.mjs";
import style from "./styles/index.cssr.mjs";
export const buttonProps = Object.assign(Object.assign({}, useTheme.props), {
color: String,
textColor: String,
text: Boolean,
block: Boolean,
loading: Boolean,
disabled: Boolean,
circle: Boolean,
size: String,
ghost: Boolean,
round: Boolean,
secondary: Boolean,
tertiary: Boolean,
quaternary: Boolean,
strong: Boolean,
focusable: {
type: Boolean,
default: true
},
keyboard: {
type: Boolean,
default: true
},
tag: {
type: String,
default: 'button'
},
type: {
type: String,
default: 'default'
},
dashed: Boolean,
renderIcon: Function,
iconPlacement: {
type: String,
default: 'left'
},
attrType: {
type: String,
default: 'button'
},
bordered: {
type: Boolean,
default: true
},
onClick: [Function, Array],
nativeFocusBehavior: {
type: Boolean,
default: !isSafari
}
});
const Button = defineComponent({
name: 'Button',
props: buttonProps,
slots: Object,
setup(props) {
if (process.env.NODE_ENV !== 'production') {
watchEffect(() => {
const {
dashed,
ghost,
text,
secondary,
tertiary,
quaternary
} = props;
if ((dashed || ghost || text) && (secondary || tertiary || quaternary)) {
warnOnce('button', '`dashed`, `ghost` and `text` props can\'t be used along with `secondary`, `tertiary` and `quaternary` props.');
}
});
}
const selfElRef = ref(null);
const waveElRef = ref(null);
const enterPressedRef = ref(false);
const showBorderRef = useMemo(() => {
return !props.quaternary && !props.tertiary && !props.secondary && !props.text && (!props.color || props.ghost || props.dashed) && props.bordered;
});
const NButtonGroup = inject(buttonGroupInjectionKey, {});
const {
mergedSizeRef
} = useFormItem({}, {
defaultSize: 'medium',
mergedSize: NFormItem => {
const {
size
} = props;
if (size) return size;
const {
size: buttonGroupSize
} = NButtonGroup;
if (buttonGroupSize) return buttonGroupSize;
const {
mergedSize: formItemSize
} = NFormItem || {};
if (formItemSize) {
return formItemSize.value;
}
return 'medium';
}
});
const mergedFocusableRef = computed(() => {
return props.focusable && !props.disabled;
});
const handleMousedown = e => {
var _a;
if (!mergedFocusableRef.value) {
e.preventDefault();
}
if (props.nativeFocusBehavior) {
return;
}
e.preventDefault();
// normally this won't be called if disabled (when tag is button)
// if not, we try to make it behave like a button
if (props.disabled) {
return;
}
if (mergedFocusableRef.value) {
(_a = selfElRef.value) === null || _a === void 0 ? void 0 : _a.focus({
preventScroll: true
});
}
};
const handleClick = e => {
var _a;
if (!props.disabled && !props.loading) {
const {
onClick
} = props;
if (onClick) call(onClick, e);
if (!props.text) {
(_a = waveElRef.value) === null || _a === void 0 ? void 0 : _a.play();
}
}
};
const handleKeyup = e => {
switch (e.key) {
case 'Enter':
if (!props.keyboard) {
return;
}
enterPressedRef.value = false;
}
};
const handleKeydown = e => {
switch (e.key) {
case 'Enter':
if (!props.keyboard || props.loading) {
e.preventDefault();
return;
}
enterPressedRef.value = true;
}
};
const handleBlur = () => {
enterPressedRef.value = false;
};
const {
inlineThemeDisabled,
mergedClsPrefixRef,
mergedRtlRef
} = useConfig(props);
const themeRef = useTheme('Button', '-button', style, buttonLight, props, mergedClsPrefixRef);
const rtlEnabledRef = useRtl('Button', mergedRtlRef, mergedClsPrefixRef);
const cssVarsRef = computed(() => {
const theme = themeRef.value;
const {
common: {
cubicBezierEaseInOut,
cubicBezierEaseOut
},
self
} = theme;
const {
rippleDuration,
opacityDisabled,
fontWeight,
fontWeightStrong
} = self;
const size = mergedSizeRef.value;
const {
dashed,
type,
ghost,
text,
color,
round,
circle,
textColor,
secondary,
tertiary,
quaternary,
strong
} = props;
// font
const fontProps = {
'--n-font-weight': strong ? fontWeightStrong : fontWeight
};
// color
let colorProps = {
'--n-color': 'initial',
'--n-color-hover': 'initial',
'--n-color-pressed': 'initial',
'--n-color-focus': 'initial',
'--n-color-disabled': 'initial',
'--n-ripple-color': 'initial',
'--n-text-color': 'initial',
'--n-text-color-hover': 'initial',
'--n-text-color-pressed': 'initial',
'--n-text-color-focus': 'initial',
'--n-text-color-disabled': 'initial'
};
const typeIsTertiary = type === 'tertiary';
const typeIsDefault = type === 'default';
const mergedType = typeIsTertiary ? 'default' : type;
if (text) {
const propTextColor = textColor || color;
const mergedTextColor = propTextColor || self[createKey('textColorText', mergedType)];
colorProps = {
'--n-color': '#0000',
'--n-color-hover': '#0000',
'--n-color-pressed': '#0000',
'--n-color-focus': '#0000',
'--n-color-disabled': '#0000',
'--n-ripple-color': '#0000',
'--n-text-color': mergedTextColor,
'--n-text-color-hover': propTextColor ? createHoverColor(propTextColor) : self[createKey('textColorTextHover', mergedType)],
'--n-text-color-pressed': propTextColor ? createPressedColor(propTextColor) : self[createKey('textColorTextPressed', mergedType)],
'--n-text-color-focus': propTextColor ? createHoverColor(propTextColor) : self[createKey('textColorTextHover', mergedType)],
'--n-text-color-disabled': propTextColor || self[createKey('textColorTextDisabled', mergedType)]
};
} else if (ghost || dashed) {
const mergedTextColor = textColor || color;
colorProps = {
'--n-color': '#0000',
'--n-color-hover': '#0000',
'--n-color-pressed': '#0000',
'--n-color-focus': '#0000',
'--n-color-disabled': '#0000',
'--n-ripple-color': color || self[createKey('rippleColor', mergedType)],
'--n-text-color': mergedTextColor || self[createKey('textColorGhost', mergedType)],
'--n-text-color-hover': mergedTextColor ? createHoverColor(mergedTextColor) : self[createKey('textColorGhostHover', mergedType)],
'--n-text-color-pressed': mergedTextColor ? createPressedColor(mergedTextColor) : self[createKey('textColorGhostPressed', mergedType)],
'--n-text-color-focus': mergedTextColor ? createHoverColor(mergedTextColor) : self[createKey('textColorGhostHover', mergedType)],
'--n-text-color-disabled': mergedTextColor || self[createKey('textColorGhostDisabled', mergedType)]
};
} else if (secondary) {
const typeTextColor = typeIsDefault ? self.textColor : typeIsTertiary ? self.textColorTertiary : self[createKey('color', mergedType)];
const mergedTextColor = color || typeTextColor;
const isColoredType = type !== 'default' && type !== 'tertiary';
colorProps = {
'--n-color': isColoredType ? changeColor(mergedTextColor, {
alpha: Number(self.colorOpacitySecondary)
}) : self.colorSecondary,
'--n-color-hover': isColoredType ? changeColor(mergedTextColor, {
alpha: Number(self.colorOpacitySecondaryHover)
}) : self.colorSecondaryHover,
'--n-color-pressed': isColoredType ? changeColor(mergedTextColor, {
alpha: Number(self.colorOpacitySecondaryPressed)
}) : self.colorSecondaryPressed,
'--n-color-focus': isColoredType ? changeColor(mergedTextColor, {
alpha: Number(self.colorOpacitySecondaryHover)
}) : self.colorSecondaryHover,
'--n-color-disabled': self.colorSecondary,
'--n-ripple-color': '#0000',
'--n-text-color': mergedTextColor,
'--n-text-color-hover': mergedTextColor,
'--n-text-color-pressed': mergedTextColor,
'--n-text-color-focus': mergedTextColor,
'--n-text-color-disabled': mergedTextColor
};
} else if (tertiary || quaternary) {
const typeColor = typeIsDefault ? self.textColor : typeIsTertiary ? self.textColorTertiary : self[createKey('color', mergedType)];
const mergedColor = color || typeColor;
if (tertiary) {
colorProps['--n-color'] = self.colorTertiary;
colorProps['--n-color-hover'] = self.colorTertiaryHover;
colorProps['--n-color-pressed'] = self.colorTertiaryPressed;
colorProps['--n-color-focus'] = self.colorSecondaryHover;
colorProps['--n-color-disabled'] = self.colorTertiary;
} else {
colorProps['--n-color'] = self.colorQuaternary;
colorProps['--n-color-hover'] = self.colorQuaternaryHover;
colorProps['--n-color-pressed'] = self.colorQuaternaryPressed;
colorProps['--n-color-focus'] = self.colorQuaternaryHover;
colorProps['--n-color-disabled'] = self.colorQuaternary;
}
colorProps['--n-ripple-color'] = '#0000';
colorProps['--n-text-color'] = mergedColor;
colorProps['--n-text-color-hover'] = mergedColor;
colorProps['--n-text-color-pressed'] = mergedColor;
colorProps['--n-text-color-focus'] = mergedColor;
colorProps['--n-text-color-disabled'] = mergedColor;
} else {
colorProps = {
'--n-color': color || self[createKey('color', mergedType)],
'--n-color-hover': color ? createHoverColor(color) : self[createKey('colorHover', mergedType)],
'--n-color-pressed': color ? createPressedColor(color) : self[createKey('colorPressed', mergedType)],
'--n-color-focus': color ? createHoverColor(color) : self[createKey('colorFocus', mergedType)],
'--n-color-disabled': color || self[createKey('colorDisabled', mergedType)],
'--n-ripple-color': color || self[createKey('rippleColor', mergedType)],
'--n-text-color': textColor || (color ? self.textColorPrimary : typeIsTertiary ? self.textColorTertiary : self[createKey('textColor', mergedType)]),
'--n-text-color-hover': textColor || (color ? self.textColorHoverPrimary : self[createKey('textColorHover', mergedType)]),
'--n-text-color-pressed': textColor || (color ? self.textColorPressedPrimary : self[createKey('textColorPressed', mergedType)]),
'--n-text-color-focus': textColor || (color ? self.textColorFocusPrimary : self[createKey('textColorFocus', mergedType)]),
'--n-text-color-disabled': textColor || (color ? self.textColorDisabledPrimary : self[createKey('textColorDisabled', mergedType)])
};
}
// border
let borderProps = {
'--n-border': 'initial',
'--n-border-hover': 'initial',
'--n-border-pressed': 'initial',
'--n-border-focus': 'initial',
'--n-border-disabled': 'initial'
};
if (text) {
borderProps = {
'--n-border': 'none',
'--n-border-hover': 'none',
'--n-border-pressed': 'none',
'--n-border-focus': 'none',
'--n-border-disabled': 'none'
};
} else {
borderProps = {
'--n-border': self[createKey('border', mergedType)],
'--n-border-hover': self[createKey('borderHover', mergedType)],
'--n-border-pressed': self[createKey('borderPressed', mergedType)],
'--n-border-focus': self[createKey('borderFocus', mergedType)],
'--n-border-disabled': self[createKey('borderDisabled', mergedType)]
};
}
// size
const {
[createKey('height', size)]: height,
[createKey('fontSize', size)]: fontSize,
[createKey('padding', size)]: padding,
[createKey('paddingRound', size)]: paddingRound,
[createKey('iconSize', size)]: iconSize,
[createKey('borderRadius', size)]: borderRadius,
[createKey('iconMargin', size)]: iconMargin,
waveOpacity
} = self;
const sizeProps = {
'--n-width': circle && !text ? height : 'initial',
'--n-height': text ? 'initial' : height,
'--n-font-size': fontSize,
'--n-padding': circle ? 'initial' : text ? 'initial' : round ? paddingRound : padding,
'--n-icon-size': iconSize,
'--n-icon-margin': iconMargin,
'--n-border-radius': text ? 'initial' : circle || round ? height : borderRadius
};
return Object.assign(Object.assign(Object.assign(Object.assign({
'--n-bezier': cubicBezierEaseInOut,
'--n-bezier-ease-out': cubicBezierEaseOut,
'--n-ripple-duration': rippleDuration,
'--n-opacity-disabled': opacityDisabled,
'--n-wave-opacity': waveOpacity
}, fontProps), colorProps), borderProps), sizeProps);
});
const themeClassHandle = inlineThemeDisabled ? useThemeClass('button', computed(() => {
let hash = '';
const {
dashed,
type,
ghost,
text,
color,
round,
circle,
textColor,
secondary,
tertiary,
quaternary,
strong
} = props;
if (dashed) hash += 'a';
if (ghost) hash += 'b';
if (text) hash += 'c';
if (round) hash += 'd';
if (circle) hash += 'e';
if (secondary) hash += 'f';
if (tertiary) hash += 'g';
if (quaternary) hash += 'h';
if (strong) hash += 'i';
if (color) hash += `j${color2Class(color)}`;
if (textColor) hash += `k${color2Class(textColor)}`;
const {
value: size
} = mergedSizeRef;
hash += `l${size[0]}`;
hash += `m${type[0]}`;
return hash;
}), cssVarsRef, props) : undefined;
return {
selfElRef,
waveElRef,
mergedClsPrefix: mergedClsPrefixRef,
mergedFocusable: mergedFocusableRef,
mergedSize: mergedSizeRef,
showBorder: showBorderRef,
enterPressed: enterPressedRef,
rtlEnabled: rtlEnabledRef,
handleMousedown,
handleKeydown,
handleBlur,
handleKeyup,
handleClick,
customColorCssVars: computed(() => {
const {
color
} = props;
if (!color) return null;
const hoverColor = createHoverColor(color);
return {
'--n-border-color': color,
'--n-border-color-hover': hoverColor,
'--n-border-color-pressed': createPressedColor(color),
'--n-border-color-focus': hoverColor,
'--n-border-color-disabled': color
};
}),
cssVars: inlineThemeDisabled ? undefined : cssVarsRef,
themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass,
onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender
};
},
render() {
const {
mergedClsPrefix,
tag: Component,
onRender
} = this;
onRender === null || onRender === void 0 ? void 0 : onRender();
const children = resolveWrappedSlot(this.$slots.default, children => children && h("span", {
class: `${mergedClsPrefix}-button__content`
}, children));
return h(Component, {
ref: "selfElRef",
class: [this.themeClass, `${mergedClsPrefix}-button`, `${mergedClsPrefix}-button--${this.type}-type`, `${mergedClsPrefix}-button--${this.mergedSize}-type`, this.rtlEnabled && `${mergedClsPrefix}-button--rtl`, this.disabled && `${mergedClsPrefix}-button--disabled`, this.block && `${mergedClsPrefix}-button--block`, this.enterPressed && `${mergedClsPrefix}-button--pressed`, !this.text && this.dashed && `${mergedClsPrefix}-button--dashed`, this.color && `${mergedClsPrefix}-button--color`, this.secondary && `${mergedClsPrefix}-button--secondary`, this.loading && `${mergedClsPrefix}-button--loading`, this.ghost && `${mergedClsPrefix}-button--ghost` // required for button group border collapse
],
tabindex: this.mergedFocusable ? 0 : -1,
type: this.attrType,
style: this.cssVars,
disabled: this.disabled,
onClick: this.handleClick,
onBlur: this.handleBlur,
onMousedown: this.handleMousedown,
onKeyup: this.handleKeyup,
onKeydown: this.handleKeydown
}, this.iconPlacement === 'right' && children, h(NFadeInExpandTransition, {
width: true
}, {
default: () => resolveWrappedSlot(this.$slots.icon, children => (this.loading || this.renderIcon || children) && h("span", {
class: `${mergedClsPrefix}-button__icon`,
style: {
margin: isSlotEmpty(this.$slots.default) ? '0' : ''
}
}, h(NIconSwitchTransition, null, {
default: () => this.loading ? h(NBaseLoading, {
clsPrefix: mergedClsPrefix,
key: "loading",
class: `${mergedClsPrefix}-icon-slot`,
strokeWidth: 20
}) : h("div", {
key: "icon",
class: `${mergedClsPrefix}-icon-slot`,
role: "none"
}, this.renderIcon ? this.renderIcon() : children)
})))
}), this.iconPlacement === 'left' && children, !this.text ? h(NBaseWave, {
ref: "waveElRef",
clsPrefix: mergedClsPrefix
}) : null, this.showBorder ? h("div", {
"aria-hidden": true,
class: `${mergedClsPrefix}-button__border`,
style: this.customColorCssVars
}) : null, this.showBorder ? h("div", {
"aria-hidden": true,
class: `${mergedClsPrefix}-button__state-border`,
style: this.customColorCssVars
}) : null);
}
});
export default Button;
// XButton is for tsx type checking
// It's not compatible with render function `h`
// Currently we don't expose it as public
// If there's any issue about this, we may expose it
// Since most people use template, the type checking phase doesn't work as tsx
export const XButton = Button;
// Also, we may make XButton a generic type which support `tag` prop
// but currently vue doesn't export IntrinsicElementAttributes from runtime-dom
// so we can't easily make an attr map by hand
// just leave it for later