UNPKG

naive-ui

Version:

A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast

616 lines 19.4 kB
import { getPreciseEventTarget, hsl2hsv, hsl2rgb, hsla, hsv2hsl, hsv2rgb, hsva, rgb2hsl, rgb2hsv, rgba, toHexaString, toHexString, toHslaString, toHslString, toHsvaString, toHsvString, toRgbaString, toRgbString } from 'seemly'; import { clickoutside } from 'vdirs'; import { useIsMounted, useMergedState } from 'vooks'; import { computed, defineComponent, h, nextTick, provide, ref, toRef, Transition, watch, watchEffect, withDirectives } from 'vue'; import { VBinder, VFollower, VTarget } from 'vueuc'; import { useConfig, useFormItem, useLocale, useTheme, useThemeClass } from "../../_mixins/index.mjs"; import { call, createKey, useAdjustedTo } from "../../_utils/index.mjs"; import { NButton } from "../../button/index.mjs"; import { colorPickerLight } from "../styles/index.mjs"; import AlphaSlider from "./AlphaSlider.mjs"; import ColorInput from "./ColorInput.mjs"; import ColorPickerSwatches from "./ColorPickerSwatches.mjs"; import ColorPickerTrigger from "./ColorPickerTrigger.mjs"; import ColorPreview from "./ColorPreview.mjs"; import { colorPickerInjectionKey } from "./context.mjs"; import HueSlider from "./HueSlider.mjs"; import Pallete from "./Pallete.mjs"; import style from "./styles/index.cssr.mjs"; import { deriveDefaultValue, getModeFromValue } from "./utils.mjs"; export const colorPickerProps = Object.assign(Object.assign({}, useTheme.props), { value: String, show: { type: Boolean, default: undefined }, defaultShow: Boolean, defaultValue: String, modes: { type: Array, // no hsva by default since browser doesn't support it default: () => ['rgb', 'hex', 'hsl'] }, placement: { type: String, default: 'bottom-start' }, to: useAdjustedTo.propTo, showAlpha: { type: Boolean, default: true }, showPreview: Boolean, swatches: Array, disabled: { type: Boolean, default: undefined }, actions: { type: Array, default: null }, internalActions: Array, size: String, renderLabel: Function, onComplete: Function, onConfirm: Function, onClear: Function, 'onUpdate:show': [Function, Array], onUpdateShow: [Function, Array], 'onUpdate:value': [Function, Array], onUpdateValue: [Function, Array] }); export default defineComponent({ name: 'ColorPicker', props: colorPickerProps, slots: Object, setup(props, { slots }) { const selfRef = ref(null); let upcomingValue = null; const formItem = useFormItem(props); const { mergedSizeRef, mergedDisabledRef } = formItem; const { localeRef } = useLocale('global'); const { mergedClsPrefixRef, namespaceRef, inlineThemeDisabled } = useConfig(props); const themeRef = useTheme('ColorPicker', '-color-picker', style, colorPickerLight, props, mergedClsPrefixRef); provide(colorPickerInjectionKey, { themeRef, renderLabelRef: toRef(props, 'renderLabel'), colorPickerSlots: slots }); const uncontrolledShowRef = ref(props.defaultShow); const mergedShowRef = useMergedState(toRef(props, 'show'), uncontrolledShowRef); function doUpdateShow(value) { const { onUpdateShow, 'onUpdate:show': _onUpdateShow } = props; if (onUpdateShow) call(onUpdateShow, value); if (_onUpdateShow) call(_onUpdateShow, value); uncontrolledShowRef.value = value; } const { defaultValue } = props; const uncontrolledValueRef = ref(defaultValue === undefined ? deriveDefaultValue(props.modes, props.showAlpha) : defaultValue); const mergedValueRef = useMergedState(toRef(props, 'value'), uncontrolledValueRef); const undoStackRef = ref([mergedValueRef.value]); const valueIndexRef = ref(0); const valueModeRef = computed(() => getModeFromValue(mergedValueRef.value)); const { modes } = props; const displayedModeRef = ref(getModeFromValue(mergedValueRef.value) || modes[0] || 'rgb'); function handleUpdateDisplayedMode() { const { modes } = props; const { value: displayedMode } = displayedModeRef; const currentModeIndex = modes.findIndex(mode => mode === displayedMode); if (~currentModeIndex) { displayedModeRef.value = modes[(currentModeIndex + 1) % modes.length]; } else { displayedModeRef.value = 'rgb'; } } let _h, // avoid conflict with render function's h s, l, v, r, g, b, a; const hsvaRef = computed(() => { const { value: mergedValue } = mergedValueRef; if (!mergedValue) return null; switch (valueModeRef.value) { case 'hsv': return hsva(mergedValue); case 'hsl': ; [_h, s, l, a] = hsla(mergedValue); return [...hsl2hsv(_h, s, l), a]; case 'rgb': case 'hex': ; [r, g, b, a] = rgba(mergedValue); return [...rgb2hsv(r, g, b), a]; } }); const rgbaRef = computed(() => { const { value: mergedValue } = mergedValueRef; if (!mergedValue) return null; switch (valueModeRef.value) { case 'rgb': case 'hex': return rgba(mergedValue); case 'hsv': ; [_h, s, v, a] = hsva(mergedValue); return [...hsv2rgb(_h, s, v), a]; case 'hsl': ; [_h, s, l, a] = hsla(mergedValue); return [...hsl2rgb(_h, s, l), a]; } }); const hslaRef = computed(() => { const { value: mergedValue } = mergedValueRef; if (!mergedValue) return null; switch (valueModeRef.value) { case 'hsl': return hsla(mergedValue); case 'hsv': ; [_h, s, v, a] = hsva(mergedValue); return [...hsv2hsl(_h, s, v), a]; case 'rgb': case 'hex': ; [r, g, b, a] = rgba(mergedValue); return [...rgb2hsl(r, g, b), a]; } }); const mergedValueArrRef = computed(() => { switch (displayedModeRef.value) { case 'rgb': case 'hex': return rgbaRef.value; case 'hsv': return hsvaRef.value; case 'hsl': return hslaRef.value; } }); const displayedHueRef = ref(0); const displayedAlphaRef = ref(1); const displayedSvRef = ref([0, 0]); function handleUpdateSv(s, v) { const { value: hsvaArr } = hsvaRef; const hue = displayedHueRef.value; const alpha = hsvaArr ? hsvaArr[3] : 1; displayedSvRef.value = [s, v]; const { showAlpha } = props; switch (displayedModeRef.value) { case 'hsv': doUpdateValue((showAlpha ? toHsvaString : toHsvString)([hue, s, v, alpha]), 'cursor'); break; case 'hsl': doUpdateValue((showAlpha ? toHslaString : toHslString)([...hsv2hsl(hue, s, v), alpha]), 'cursor'); break; case 'rgb': doUpdateValue((showAlpha ? toRgbaString : toRgbString)([...hsv2rgb(hue, s, v), alpha]), 'cursor'); break; case 'hex': doUpdateValue((showAlpha ? toHexaString : toHexString)([...hsv2rgb(hue, s, v), alpha]), 'cursor'); break; } } function handleUpdateHue(hue) { displayedHueRef.value = hue; const { value: hsvaArr } = hsvaRef; if (!hsvaArr) { return; } const [, s, v, a] = hsvaArr; const { showAlpha } = props; switch (displayedModeRef.value) { case 'hsv': doUpdateValue((showAlpha ? toHsvaString : toHsvString)([hue, s, v, a]), 'cursor'); break; case 'rgb': doUpdateValue((showAlpha ? toRgbaString : toRgbString)([...hsv2rgb(hue, s, v), a]), 'cursor'); break; case 'hex': doUpdateValue((showAlpha ? toHexaString : toHexString)([...hsv2rgb(hue, s, v), a]), 'cursor'); break; case 'hsl': doUpdateValue((showAlpha ? toHslaString : toHslString)([...hsv2hsl(hue, s, v), a]), 'cursor'); break; } } function handleUpdateAlpha(alpha) { switch (displayedModeRef.value) { case 'hsv': ; [_h, s, v] = hsvaRef.value; doUpdateValue(toHsvaString([_h, s, v, alpha]), 'cursor'); break; case 'rgb': ; [r, g, b] = rgbaRef.value; doUpdateValue(toRgbaString([r, g, b, alpha]), 'cursor'); break; case 'hex': ; [r, g, b] = rgbaRef.value; doUpdateValue(toHexaString([r, g, b, alpha]), 'cursor'); break; case 'hsl': ; [_h, s, l] = hslaRef.value; doUpdateValue(toHslaString([_h, s, l, alpha]), 'cursor'); break; } displayedAlphaRef.value = alpha; } function doUpdateValue(value, updateSource) { if (updateSource === 'cursor') { upcomingValue = value; } else { upcomingValue = null; } const { nTriggerFormChange, nTriggerFormInput } = formItem; const { onUpdateValue, 'onUpdate:value': _onUpdateValue } = props; if (onUpdateValue) call(onUpdateValue, value); if (_onUpdateValue) call(_onUpdateValue, value); nTriggerFormChange(); nTriggerFormInput(); uncontrolledValueRef.value = value; } function handleInputUpdateValue(value) { doUpdateValue(value, 'input'); void nextTick(handleComplete); } function handleComplete(pushStack = true) { const { value } = mergedValueRef; // no value & only hue changes will complete with no value if (value) { const { nTriggerFormChange, nTriggerFormInput } = formItem; const { onComplete } = props; if (onComplete) { ; onComplete(value); } const { value: undoStack } = undoStackRef; const { value: valueIndex } = valueIndexRef; if (pushStack) { undoStack.splice(valueIndex + 1, undoStack.length, value); valueIndexRef.value = valueIndex + 1; } nTriggerFormChange(); nTriggerFormInput(); } } function undo() { const { value: valueIndex } = valueIndexRef; if (valueIndex - 1 < 0) return; doUpdateValue(undoStackRef.value[valueIndex - 1], 'input'); handleComplete(false); valueIndexRef.value = valueIndex - 1; } function redo() { const { value: valueIndex } = valueIndexRef; if (valueIndex < 0 || valueIndex + 1 >= undoStackRef.value.length) return; doUpdateValue(undoStackRef.value[valueIndex + 1], 'input'); handleComplete(false); valueIndexRef.value = valueIndex + 1; } function handleClear() { doUpdateValue(null, 'input'); const { onClear } = props; if (onClear) { onClear(); } doUpdateShow(false); } function handleConfirm() { const { value } = mergedValueRef; const { onConfirm } = props; if (onConfirm) { ; onConfirm(value); } doUpdateShow(false); } const undoableRef = computed(() => valueIndexRef.value >= 1); const redoableRef = computed(() => { const { value: undoStack } = undoStackRef; return undoStack.length > 1 && valueIndexRef.value < undoStack.length - 1; }); watch(mergedShowRef, value => { if (!value) { undoStackRef.value = [mergedValueRef.value]; valueIndexRef.value = 0; } }); watchEffect(() => { if (upcomingValue && upcomingValue === mergedValueRef.value) { // let it works in uncontrolled mode } else { const { value } = hsvaRef; if (value) { displayedHueRef.value = value[0]; displayedAlphaRef.value = value[3]; displayedSvRef.value = [value[1], value[2]]; } } upcomingValue = null; }); const cssVarsRef = computed(() => { const { value: mergedSize } = mergedSizeRef; const { common: { cubicBezierEaseInOut }, self: { textColor, color, panelFontSize, boxShadow, border, borderRadius, dividerColor, [createKey('height', mergedSize)]: height, [createKey('fontSize', mergedSize)]: fontSize } } = themeRef.value; return { '--n-bezier': cubicBezierEaseInOut, '--n-text-color': textColor, '--n-color': color, '--n-panel-font-size': panelFontSize, '--n-font-size': fontSize, '--n-box-shadow': boxShadow, '--n-border': border, '--n-border-radius': borderRadius, '--n-height': height, '--n-divider-color': dividerColor }; }); const themeClassHandle = inlineThemeDisabled ? useThemeClass('color-picker', computed(() => { return mergedSizeRef.value[0]; }), cssVarsRef, props) : undefined; function renderPanel() { var _a; const { value: rgba } = rgbaRef; const { value: displayedHue } = displayedHueRef; const { internalActions, modes, actions } = props; const { value: mergedTheme } = themeRef; const { value: mergedClsPrefix } = mergedClsPrefixRef; return h("div", { class: [`${mergedClsPrefix}-color-picker-panel`, themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass.value], onDragstart: e => { e.preventDefault(); }, style: inlineThemeDisabled ? undefined : cssVarsRef.value }, h("div", { class: `${mergedClsPrefix}-color-picker-control` }, h(Pallete, { clsPrefix: mergedClsPrefix, rgba: rgba, displayedHue: displayedHue, displayedSv: displayedSvRef.value, onUpdateSV: handleUpdateSv, onComplete: handleComplete }), h("div", { class: `${mergedClsPrefix}-color-picker-preview` }, h("div", { class: `${mergedClsPrefix}-color-picker-preview__sliders` }, h(HueSlider, { clsPrefix: mergedClsPrefix, hue: displayedHue, onUpdateHue: handleUpdateHue, onComplete: handleComplete }), props.showAlpha ? h(AlphaSlider, { clsPrefix: mergedClsPrefix, rgba: rgba, alpha: displayedAlphaRef.value, onUpdateAlpha: handleUpdateAlpha, onComplete: handleComplete }) : null), props.showPreview ? h(ColorPreview, { clsPrefix: mergedClsPrefix, mode: displayedModeRef.value, color: rgbaRef.value && toHexString(rgbaRef.value), onUpdateColor: color => { doUpdateValue(color, 'input'); } }) : null), h(ColorInput, { clsPrefix: mergedClsPrefix, showAlpha: props.showAlpha, mode: displayedModeRef.value, modes: modes, onUpdateMode: handleUpdateDisplayedMode, value: mergedValueRef.value, valueArr: mergedValueArrRef.value, onUpdateValue: handleInputUpdateValue }), ((_a = props.swatches) === null || _a === void 0 ? void 0 : _a.length) && h(ColorPickerSwatches, { clsPrefix: mergedClsPrefix, mode: displayedModeRef.value, swatches: props.swatches, onUpdateColor: color => { doUpdateValue(color, 'input'); } })), (actions === null || actions === void 0 ? void 0 : actions.length) ? h("div", { class: `${mergedClsPrefix}-color-picker-action` }, actions.includes('confirm') && h(NButton, { size: "small", onClick: handleConfirm, theme: mergedTheme.peers.Button, themeOverrides: mergedTheme.peerOverrides.Button }, { default: () => localeRef.value.confirm }), actions.includes('clear') && h(NButton, { size: "small", onClick: handleClear, disabled: !mergedValueRef.value, theme: mergedTheme.peers.Button, themeOverrides: mergedTheme.peerOverrides.Button }, { default: () => localeRef.value.clear })) : null, slots.action ? h("div", { class: `${mergedClsPrefix}-color-picker-action` }, { default: slots.action }) : internalActions ? h("div", { class: `${mergedClsPrefix}-color-picker-action` }, internalActions.includes('undo') && h(NButton, { size: "small", onClick: undo, disabled: !undoableRef.value, theme: mergedTheme.peers.Button, themeOverrides: mergedTheme.peerOverrides.Button }, { default: () => localeRef.value.undo }), internalActions.includes('redo') && h(NButton, { size: "small", onClick: redo, disabled: !redoableRef.value, theme: mergedTheme.peers.Button, themeOverrides: mergedTheme.peerOverrides.Button }, { default: () => localeRef.value.redo })) : null); } return { mergedClsPrefix: mergedClsPrefixRef, namespace: namespaceRef, selfRef, hsla: hslaRef, rgba: rgbaRef, mergedShow: mergedShowRef, mergedDisabled: mergedDisabledRef, isMounted: useIsMounted(), adjustedTo: useAdjustedTo(props), mergedValue: mergedValueRef, handleTriggerClick() { doUpdateShow(true); }, handleClickOutside(e) { var _a; if ((_a = selfRef.value) === null || _a === void 0 ? void 0 : _a.contains(getPreciseEventTarget(e))) { return; } doUpdateShow(false); }, renderPanel, 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, onRender } = this; onRender === null || onRender === void 0 ? void 0 : onRender(); return h("div", { class: [this.themeClass, `${mergedClsPrefix}-color-picker`], ref: "selfRef", style: this.cssVars }, h(VBinder, null, { default: () => [h(VTarget, null, { default: () => h(ColorPickerTrigger, { clsPrefix: mergedClsPrefix, value: this.mergedValue, hsla: this.hsla, disabled: this.mergedDisabled, onClick: this.handleTriggerClick }) }), h(VFollower, { placement: this.placement, show: this.mergedShow, containerClass: this.namespace, teleportDisabled: this.adjustedTo === useAdjustedTo.tdkey, to: this.adjustedTo }, { default: () => h(Transition, { name: "fade-in-scale-up-transition", appear: this.isMounted }, { default: () => this.mergedShow ? withDirectives(this.renderPanel(), [[clickoutside, this.handleClickOutside, undefined, { capture: true }]]) : null }) })] })); } });