UNPKG

8.96 kBJavaScriptView Raw
1/*
2Copyright 2019 Adobe. All rights reserved.
3This file is licensed to you under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License. You may obtain a copy
5of the License at http://www.apache.org/licenses/LICENSE-2.0
6
7Unless required by applicable law or agreed to in writing, software distributed under
8the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9OF ANY KIND, either express or implied. See the License for the specific language
10governing permissions and limitations under the License.
11*/
12
13const chroma = require('chroma-js');
14
15const {
16 colorSpaces,
17 convertColorValue,
18 multiplyRatios,
19 ratioName,
20 round,
21 searchColors,
22} = require('./utils');
23
24const { BackgroundColor } = require('./backgroundcolor');
25
26class Theme {
27 constructor({ colors, backgroundColor, lightness, contrast = 1, saturation = 100, output = 'HEX' }) {
28 this._output = output;
29 this._colors = colors;
30 this._lightness = lightness;
31 this._saturatoin = saturation;
32
33 this._setBackgroundColor(backgroundColor);
34 this._setBackgroundColorValue();
35
36 this._contrast = contrast;
37 if (!this._colors) {
38 throw new Error('No colors are defined');
39 }
40 if (!this._backgroundColor) {
41 throw new Error('Background color is undefined');
42 }
43 colors.forEach((color) => {
44 if (!color.ratios) throw new Error(`Color ${color.name}'s ratios are undefined`);
45 });
46 if (!colorSpaces[this._output]) {
47 throw new Error(`Output “${output}” not supported`);
48 }
49
50 this._findContrastColors();
51 this._findContrastColorPairs();
52 this._findContrastColorValues();
53 }
54
55 set contrast(contrast) {
56 this._contrast = contrast;
57 this._findContrastColors();
58 }
59
60 get contrast() {
61 return this._contrast;
62 }
63
64 set lightness(lightness) {
65 this._lightness = lightness;
66 this._setBackgroundColor(this._backgroundColor);
67 this._findContrastColors();
68 }
69
70 get lightness() {
71 return this._lightness;
72 }
73
74 set saturation(saturation) {
75 this._saturation = saturation;
76 // Update all colors key colors
77 this._updateColorSaturation(saturation);
78 this._findContrastColors();
79 }
80
81 get saturation() {
82 return this._saturation;
83 }
84
85 set backgroundColor(backgroundColor) {
86 this._setBackgroundColor(backgroundColor);
87 this._findContrastColors();
88 }
89
90 get backgroundColorValue() {
91 return this._backgroundColorValue;
92 }
93
94 get backgroundColor() {
95 return this._backgroundColor;
96 }
97
98 // Add a getter and setter for colors
99 set colors(colors) {
100 this._colors = colors;
101 this._findContrastColors();
102 }
103
104 get colors() {
105 return this._colors;
106 }
107
108 // add individual new colors
109 set addColor(color) {
110 this._colors.push(color);
111 this._findContrastColors();
112 }
113 // remove individual colors
114 set removeColor(color) {
115 const filteredColors = this._colors.filter(entry => {return entry.name !== color.name});
116 this._colors = filteredColors;
117 this._findContrastColors();
118 }
119 // modify individual colors
120 set updateColor(param) {
121 // pass arguments in the format _updateColorParameter(color: 'ColorToChange', [propertyToChange]: 'newValue')
122 // eg, changing the name of a color: _updateColorParameter(color: 'blue', name: 'cerulean')
123 let currentColor = this._colors.filter(entry => {return entry.name === param.color});
124 currentColor = currentColor[0];
125 const filteredColors = this._colors.filter(entry => {return entry.name !== param.color});
126 if(param.name) currentColor.name = param.name;
127 if(param.colorKeys) currentColor.colorKeys = param.colorKeys;
128 if(param.ratios) currentColor.ratios = param.ratios;
129 if(param.colorspace) currentColor.colorspace = param.colorspace;
130 if(param.smooth) currentColor.smooth = param.smooth;
131
132 filteredColors.push(currentColor);
133 this._colors = filteredColors;
134
135 this._findContrastColors();
136 }
137
138 set output(output) {
139 this._output = output;
140 this._colors.forEach((element) => {
141 element.output = this._output;
142 });
143 this._backgroundColor.output = this._output;
144
145 this._findContrastColors();
146 }
147
148 get output() {
149 return this._output;
150 }
151
152 get contrastColors() {
153 return this._contrastColors;
154 }
155
156 get contrastColorPairs() {
157 return this._contrastColorPairs;
158 }
159
160 get contrastColorValues() {
161 return this._contrastColorValues;
162 }
163
164 _setBackgroundColor(backgroundColor) {
165 if (typeof backgroundColor === 'string') {
166 // If it's a string, convert to Color object and assign lightness.
167 const newBackgroundColor = new BackgroundColor({ name: 'background', colorKeys: [backgroundColor], output: 'RGB' });
168 const calcLightness = round(chroma(String(backgroundColor)).hsluv()[2]);
169
170 this._backgroundColor = newBackgroundColor;
171 this._lightness = calcLightness;
172 this._backgroundColorValue = newBackgroundColor[this._lightness];
173 // console.log(`String background color of ${backgroundColor} converted to ${newBackgroundColor}`)
174 } else {
175 // console.log(`NOT a string for background, instead it is ${JSON.stringify(backgroundColor)}`)
176 backgroundColor.output = 'RGB';
177 const calcBackgroundColorValue = backgroundColor.backgroundColorScale[this._lightness];
178
179 // console.log(`Object background \nLightness: ${this._lightness} \nBackground scale: ${backgroundColor.backgroundColorScale}\nCalculated background value of ${calcBackgroundColorValue}`)
180 this._backgroundColor = backgroundColor;
181 this._backgroundColorValue = calcBackgroundColorValue;
182 }
183 }
184
185 _setBackgroundColorValue() {
186 this._backgroundColorValue = this._backgroundColor.backgroundColorScale[this._lightness];
187 }
188
189 _updateColorSaturation(saturation) {
190 this._colors.map((color) => {
191 const colorKeys = color.colorKeys;
192 let newColorKeys = [];
193 colorKeys.forEach(key => {
194 let currentHsluv = chroma(`${key}`).hsluv();
195 let currentSaturation = currentHsluv[1];
196 let newSaturation = currentSaturation * (saturation / 100);
197 let newHsluv = chroma.hsluv(currentHsluv[0], newSaturation, currentHsluv[2]);
198 let newColor = chroma.rgb(newHsluv).hex();
199 newColorKeys.push(newColor);
200 });
201 // set each colors color keys with new modified array
202 color.colorKeys = newColorKeys;
203 })
204 }
205
206 _findContrastColors() {
207 const bgRgbArray = chroma(String(this._backgroundColorValue)).rgb();
208 const baseV = this._lightness / 100;
209 const convertedBackgroundColorValue = convertColorValue(this._backgroundColorValue, this._output);
210 const baseObj = { background: convertedBackgroundColorValue };
211
212 const returnColors = []; // Array to be populated with JSON objects for each color, including names & contrast values
213 const returnColorValues = []; // Array to be populated with flat list of all color values
214 const returnColorPairs = {...baseObj}; // Objext to be populated with flat list of all color values as named key-value pairs
215 returnColors.push(baseObj);
216
217 this._colors.map((color) => {
218 if (color.ratios !== undefined) {
219 let swatchNames;
220 const newArr = [];
221 const colorObj = {
222 name: color.name,
223 values: newArr,
224 };
225
226 let ratioValues;
227
228 if (Array.isArray(color.ratios)) {
229 ratioValues = color.ratios;
230 } else if (!Array.isArray(color.ratios)) {
231 swatchNames = Object.keys(color.ratios);
232 ratioValues = Object.values(color.ratios);
233 }
234
235 // modify target ratio based on contrast multiplier
236 ratioValues = ratioValues.map((ratio) => multiplyRatios(+ratio, this._contrast));
237
238 const contrastColors = searchColors(color, bgRgbArray, baseV, ratioValues).map((clr) => convertColorValue(clr, this._output));
239
240 for (let i = 0; i < contrastColors.length; i++) {
241 let n;
242 if (!swatchNames) {
243 const rVal = ratioName(color.ratios)[i];
244 n = color.name.concat(rVal);
245 } else {
246 n = swatchNames[i];
247 }
248
249 const obj = {
250 name: n,
251 contrast: ratioValues[i],
252 value: contrastColors[i],
253 };
254 newArr.push(obj);
255 // Push the same values to the returnColorPairs object
256 returnColorPairs[n] = contrastColors[i];
257 // Push the same value to the returnColorValues array
258 returnColorValues.push(contrastColors[i]);
259 }
260 returnColors.push(colorObj);
261 }
262 return null;
263 });
264 this._contrastColorValues = returnColorValues;
265 this._contrastColorPairs = returnColorPairs;
266 this._contrastColors = returnColors;
267 return this._contrastColors;
268 }
269
270 _findContrastColorPairs() {
271 return this._contrastColorPairs;
272 }
273
274 _findContrastColorValues() {
275 return this._contrastColorValues;
276 }
277}
278
279module.exports = { Theme };