Skip to content

Commit e247806

Browse files
committed
refactor: The output value should follow the same format as the input value. (fix: #185)
1 parent e5d2e36 commit e247806

File tree

5 files changed

+125
-26
lines changed

5 files changed

+125
-26
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import { reactive } from 'vue'
6161
import { ChromePicker } from 'vue-color'
6262
6363
const color = defineModel({
64-
default: () => reactive({r: 0, g: 0, b: 255, a: 1})
64+
default: '#68CCCA'
6565
});
6666
</script>
6767
```

demo/App.vue

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ const tinyColor = defineModel('tinycolor', {
1616
default: '#F5F7FA'
1717
});
1818
19-
const colorRGBA = defineModel({
19+
const color = defineModel({
2020
default: () => reactive({r: 0, g: 0, b: 255, a: 1})
2121
});
2222
2323
const scope = effectScope();
2424
2525
scope.run(() => {
26-
watch(tinyColor, () => console.log(tinyColor.value));
26+
watch(color, () => console.log('color changed ==>', color.value));
2727
});
2828
2929
function invertColor(rgb: { r: number; g: number; b: number }): string {
@@ -81,56 +81,56 @@ const textColor = computed(() => {
8181
<div class="col">
8282
<div class="roboto current-color" :style="{color: textColor, opacity: 0.5}" :aria-hidden="true">
8383
{{ hex }}<br />
84-
{{ colorRGBA }}<br />
84+
{{ color }}<br />
8585
{{ hsva }}
8686
</div>
8787
<div class="picker-container">
88-
<Chrome v-model:tinyColor="tinyColor" v-model="colorRGBA" />
88+
<Chrome v-model:tinyColor="tinyColor" v-model="color" />
8989
<div class="picker-title roboto" :style="{color: textColor, opacity: 0.5}">&lt;ChromePicker /&gt;</div>
9090
</div>
9191
</div>
9292

9393
<div class="picker-container">
94-
<div><Sketch v-model:tinyColor="tinyColor" v-model="colorRGBA" /></div>
94+
<div><Sketch v-model:tinyColor="tinyColor" v-model="color" /></div>
9595
<div class="picker-title roboto" :style="{color: textColor, opacity: 0.5}">&lt;SketchPicker /&gt;</div>
9696
</div>
9797

9898
<div class="picker-container">
99-
<div><Photoshop v-model:tinyColor="tinyColor" v-model="colorRGBA" :hasResetButton="true" /></div>
99+
<div><Photoshop v-model:tinyColor="tinyColor" v-model="color" :hasResetButton="true" /></div>
100100
<div class="picker-title roboto" :style="{color: textColor, opacity: 0.5}">&lt;PhotoshopPicker /&gt;</div>
101101
</div>
102102
</div>
103103
<div class="row" :style="{marginTop: '5%'}">
104104
<div class="col">
105105
<div class="picker-container">
106-
<div><Compact v-model:tinyColor="tinyColor" v-model="colorRGBA" /></div>
106+
<div><Compact v-model:tinyColor="tinyColor" v-model="color" /></div>
107107
<div class="picker-title roboto" :style="{color: textColor, opacity: 0.5}">&lt;CompactPicker /&gt;</div>
108108
</div>
109109
<div class="picker-container">
110-
<div><Grayscale v-model:tinyColor="tinyColor" v-model="colorRGBA" /></div>
110+
<div><Grayscale v-model:tinyColor="tinyColor" v-model="color" /></div>
111111
<div class="picker-title roboto" :style="{color: textColor, opacity: 0.5}">&lt;GrayscalePicker /&gt;</div>
112112
</div>
113113
<div class="picker-container">
114-
<div><Material v-model:tinyColor="tinyColor" v-model="colorRGBA" /></div>
114+
<div><Material v-model:tinyColor="tinyColor" v-model="color" /></div>
115115
<div class="picker-title roboto" :style="{color: textColor, opacity: 0.5}">&lt;MaterialPicker /&gt;</div>
116116
</div>
117117
</div>
118118

119119
<div class="col">
120120
<div class="picker-container">
121-
<div><Slider v-model:tinyColor="tinyColor" v-model="colorRGBA" /></div>
121+
<div><Slider v-model:tinyColor="tinyColor" v-model="color" /></div>
122122
<div class="picker-title roboto" :style="{color: textColor, opacity: 0.5}">&lt;SliderPicker /&gt;</div>
123123
</div>
124124

125125
<div class="picker-container">
126-
<div><Twitter v-model:tinyColor="tinyColor" v-model="colorRGBA" /></div>
126+
<div><Twitter v-model:tinyColor="tinyColor" v-model="color" /></div>
127127
<div class="picker-title roboto" :style="{color: textColor, opacity: 0.5}">&lt;TwitterPicker /&gt;</div>
128128
</div>
129129
</div>
130130

131131
<div class="col">
132132
<div class="picker-container">
133-
<div><Swatches v-model:tinyColor="tinyColor" v-model="colorRGBA" /></div>
133+
<div><Swatches v-model:tinyColor="tinyColor" v-model="color" /></div>
134134
<div class="picker-title roboto" :style="{color: textColor, opacity: 0.5}">&lt;SwatchesPicker /&gt;</div>
135135
</div>
136136
</div>

src/composable/vmodel.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { computed, type EmitFn } from 'vue';
22
import tinycolor from 'tinycolor2';
33

4-
const transformToOriginalInputFormat = (color: tinycolor.Instance, isObjectOriginally = false, originalFormat: string) => {
4+
type TinyColorFormat = 'name' | 'hex8' | 'hex' | 'prgb' | 'rgb' | 'hsv' | 'hsl';
5+
6+
const transformToOriginalInputFormat = (color: tinycolor.Instance, originalFormat?: TinyColorFormat, isObjectOriginally = false) => {
57
if (isObjectOriginally) {
68
switch (originalFormat) {
79
case 'rgb': {
@@ -22,7 +24,7 @@ const transformToOriginalInputFormat = (color: tinycolor.Instance, isObjectOrigi
2224
}
2325
} else {
2426
// transform back to the original format
25-
let newValue = color.toString();
27+
let newValue = color.toString(originalFormat);
2628
try {
2729
newValue = JSON.parse(newValue);
2830
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -46,17 +48,21 @@ export const EmitEventNames = ['update:tinyColor', 'update:modelValue'];
4648
*/
4749
export function useTinyColorModel(props: useTinyColorModelProps, emit: EmitFn) {
4850

49-
let isObjectOriginally = false;
50-
let originalFormat = '';
51+
let isObjectOriginally: boolean;
52+
let originalFormat: TinyColorFormat;
5153

5254
const tinyColorRef = computed({
5355
get: () => {
54-
if (typeof props.modelValue === 'object') {
55-
isObjectOriginally = true;
56-
}
5756
const colorInput = props.modelValue ?? props.tinyColor;
5857
const value = tinycolor(colorInput);
59-
originalFormat = value.getFormat();
58+
if (typeof originalFormat === 'undefined') {
59+
originalFormat = value.getFormat() as TinyColorFormat;
60+
}
61+
if (typeof isObjectOriginally === 'undefined') {
62+
if (typeof props.modelValue === 'object') {
63+
isObjectOriginally = true;
64+
}
65+
}
6066
return value;
6167
},
6268
set: (newValue: tinycolor.Instance) => {
@@ -70,7 +76,7 @@ export function useTinyColorModel(props: useTinyColorModelProps, emit: EmitFn) {
7076
emit('update:tinyColor', newValue.clone());
7177
}
7278
if (props.modelValue) {
73-
emit('update:modelValue', transformToOriginalInputFormat(newValue, isObjectOriginally, originalFormat));
79+
emit('update:modelValue', transformToOriginalInputFormat(newValue, originalFormat, isObjectOriginally));
7480
}
7581
}
7682

tests/components/CompactPicker.spec.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, test } from 'vitest';
1+
import { expect, test, describe } from 'vitest';
22
import { render } from 'vitest-browser-vue';
33
import CompactPicker from '../../src/components/CompactPicker.vue';
44

@@ -31,4 +31,98 @@ test('click one of the color in the palette', async () => {
3131
// press Space bar
3232
options.nth(4).element().dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }));
3333
expect((emitted()['update:modelValue'][1] as [string])[0]).toBe('#FE9200'.toLowerCase());
34+
});
35+
36+
describe('The output value should follow the same format as the input value', async () => {
37+
const cases = [
38+
{
39+
format: 'hex8',
40+
input: '#ffffff00',
41+
expectFunc: (toBeChecked: string) => {
42+
expect(toBeChecked).toBe('#68cccaff');
43+
}
44+
},
45+
{
46+
format: 'hex',
47+
input: '#ffffff',
48+
expectFunc: (toBeChecked: string) => {
49+
expect(toBeChecked).toBe('#68ccca');
50+
}
51+
},
52+
{
53+
format: 'prgb',
54+
input: { r: '50%', g: '50%', b: '50%' },
55+
expectFunc: (toBeChecked: { r: string, g: string, b: string}) => {
56+
expect(Number(toBeChecked.r.replace('%', ''))).toBeCloseTo(41);
57+
expect(Number(toBeChecked.g.replace('%', ''))).toBeCloseTo(80);
58+
expect(Number(toBeChecked.b.replace('%', ''))).toBeCloseTo(79);
59+
},
60+
},
61+
{
62+
format: 'prgb(string)',
63+
input: 'rgb(1%, 1%, 1%)',
64+
expectFunc: (toBeChecked: string) => {
65+
expect(toBeChecked).toBe('rgb(41%, 80%, 79%)');
66+
}
67+
},
68+
{
69+
format: 'rgb',
70+
input: { r: 10, g: 10, b: 10 },
71+
expectFunc: (toBeChecked: { r: number, g: number, b: number}) => {
72+
expect(toBeChecked.r).toBeCloseTo(104);
73+
expect(toBeChecked.g).toBeCloseTo(204);
74+
expect(toBeChecked.b).toBeCloseTo(202);
75+
},
76+
},
77+
{
78+
format: 'rgb(string)',
79+
input: 'rgb(1, 1, 1)',
80+
expectFunc: (toBeChecked: string) => {
81+
expect(toBeChecked).toBe('rgb(104, 204, 202)');
82+
},
83+
},
84+
{
85+
format: 'hsv',
86+
input: { h: 0, s: 0, v: 0 },
87+
expectFunc: (toBeChecked: { h: number, s: number, v: number}) => {
88+
expect(toBeChecked.h).toBeCloseTo(179, 0);
89+
expect(toBeChecked.s).toBeCloseTo(0.49);
90+
expect(toBeChecked.v).toBeCloseTo(0.8);
91+
},
92+
},
93+
{
94+
format: 'hsl',
95+
input: { h: 0, s: 0, l: 0 },
96+
expectFunc: (toBeChecked: { h: number, s: number, l: number}) => {
97+
expect(toBeChecked.h).toBeCloseTo(179, 0);
98+
expect(toBeChecked.s).toBeCloseTo(0.5);
99+
expect(toBeChecked.l).toBeCloseTo(0.6);
100+
},
101+
},
102+
{
103+
format: 'hsv(string)',
104+
input: 'hsva(1, 1%, 1%, 1)',
105+
expectFunc: (toBeChecked: string) => {
106+
expect(toBeChecked).toBe('hsv(179, 49%, 80%)');
107+
},
108+
},
109+
{
110+
format: 'hsl(string)',
111+
input: 'hsl(1, 1%, 1%)',
112+
expectFunc: (toBeChecked: string) => {
113+
expect(toBeChecked).toBe('hsl(179, 50%, 60%)');
114+
},
115+
},
116+
];
117+
test.each(cases)('$format', async ({ input, expectFunc }) => {
118+
const { getByRole, emitted } = render(CompactPicker, {
119+
props: {
120+
modelValue: input
121+
}
122+
});
123+
const presetColors = getByRole('option');
124+
await presetColors.nth(8).click();
125+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
126+
expectFunc((emitted()['update:modelValue'][0] as [string])[0] as any);
127+
})
34128
});

tests/components/SketchPicker.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,11 @@ test('change color by clicking preset color', async () => {
134134
expect((emitted()['update:modelValue'][0] as [string])[0]).toBe('#417505');
135135

136136
await presetColors.last().click();
137-
expect((emitted()['update:modelValue'][1] as [string])[0]).toBe('rgba(0, 0, 0, 0)');
137+
expect((emitted()['update:modelValue'][1] as [string])[0]).toBe('#000000');
138138

139139
presetColors.nth(10).element().dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }));
140140
expect((emitted()['update:modelValue'][2] as [string])[0]).toBe('#b8e986');
141141

142142
presetColors.last().element().dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }));
143-
expect((emitted()['update:modelValue'][3] as [string])[0]).toBe('rgba(0, 0, 0, 0)');
144-
143+
expect((emitted()['update:modelValue'][3] as [string])[0]).toBe('#000000');
145144
});

0 commit comments

Comments
 (0)