UNPKG

14.8 kBJavaScriptView Raw
1var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4 else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5 return c > 3 && r && Object.defineProperty(target, key, r), r;
6};
7var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8 if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9 if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10 return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11};
12var _DuoyunColorPanelElement_instances, _DuoyunColorPanelElement_color_get, _DuoyunColorPanelElement_typeOptions, _DuoyunColorPanelElement_getPosition, _DuoyunColorPanelElement_onPanHue, _DuoyunColorPanelElement_onPanSV, _DuoyunColorPanelElement_onPanA, _DuoyunColorPanelElement_onPanEnd, _DuoyunColorPanelElement_onChangeType, _DuoyunColorPanelElement_onChangeValue, _DuoyunColorPanelElement_onChangeA, _DuoyunColorPanelElement_openEyeDropper;
13// https://spectrum.adobe.com/page/color-area/
14import { adoptedStyle, customElement, attribute, globalemitter, boolattribute, } from '@mantou/gem/lib/decorators';
15import { GemElement, html } from '@mantou/gem/lib/element';
16import { createCSSSheet, css, styleMap, classMap } from '@mantou/gem/lib/utils';
17import { parseHexColor, rgbToHexColor, rgbToHslColor, rgbToRgbColor, hslToRgb, rgbToHsl, hslToHsv, hsvToHsl, isValidHexColor, } from '../lib/color';
18import { theme } from '../lib/theme';
19import { icons } from '../lib/icons';
20import { clamp, formatToPrecision } from '../lib/number';
21import './gesture';
22import './use';
23import './input';
24import './select';
25const style = createCSSSheet(css `
26 :host(:where(:not([hidden]))) {
27 display: flex;
28 flex-direction: column;
29 gap: 1em;
30 width: 20em;
31 font-size: 0.875em;
32 --hsl: hsl(var(--h), var(--s), var(--l));
33 --hue: hsl(var(--h), 100%, 50%);
34 --alpha: linear-gradient(var(--hue), transparent),
35 conic-gradient(transparent 0.25turn, #d3cfcf 0.25turn 0.5turn, transparent 0.5turn 0.75turn, #d3cfcf 0.75turn) top
36 left / 1.2em 1.2em repeat;
37 }
38 .color {
39 display: flex;
40 align-items: stretch;
41 gap: 1em;
42 }
43 .area {
44 flex-grow: 1;
45 aspect-ratio: 1 / 1;
46 background-color: var(--hue);
47 background-image: linear-gradient(transparent, #000), linear-gradient(90deg, #fff, transparent);
48 }
49 .hue-bar,
50 .alpha-bar {
51 width: 1.8em;
52 }
53 .hue-bar {
54 background-image: linear-gradient(
55 ${Array.from({ length: 15 }, (_, i) => `hsl(${360 - (360 / (15 - 1)) * i}, 100%, 50%)`).join(',')}
56 );
57 }
58 .alpha-bar {
59 background: var(--alpha);
60 }
61 .area,
62 .hue-bar,
63 .alpha-bar {
64 position: relative;
65 border-radius: ${theme.normalRound};
66 box-shadow: inset 0 0 0 1px #0002;
67 }
68 .current {
69 transition: transform 0.1s;
70 background: radial-gradient(transparent 50%, white 50%);
71 }
72 .current.grabbing {
73 transform: translate(-50%, -50%) scale(1.3);
74 }
75 .current,
76 .current span {
77 position: absolute;
78 width: 1.2em;
79 aspect-ratio: 1;
80 border-radius: 10em;
81 border: 1px solid #0008;
82 transform: translate(-50%, -50%);
83 }
84 .current span {
85 left: 50%;
86 top: 50%;
87 width: 66%;
88 background-clip: content-box;
89 }
90 .area .current span {
91 background-color: var(--hsl);
92 }
93 .hue-bar .current span {
94 background-color: var(--hue);
95 }
96 .alpha-bar .current span {
97 background-color: transparent;
98 }
99 .input {
100 display: flex;
101 align-items: center;
102 gap: 0.5em;
103 }
104 .type {
105 width: 5em;
106 flex-shrink: 0;
107 }
108 .value,
109 .alpha {
110 border-radius: 0;
111 border-top-color: transparent;
112 border-inline: none;
113 }
114 :where(.value, .alpha)::part(input) {
115 padding-inline: 0;
116 }
117 .value {
118 width: auto;
119 flex-shrink: 1;
120 }
121 .alpha {
122 width: 3em;
123 margin-inline-end: 2em;
124 }
125 .hidden {
126 pointer-events: none;
127 opacity: 0;
128 }
129 .colorize {
130 padding: 0.15em;
131 width: 1.5em;
132 flex-shrink: 0;
133 border-radius: ${theme.normalRound};
134 }
135 .colorize:hover {
136 background: ${theme.lightBackgroundColor};
137 }
138`);
139/**
140 * @customElement dy-color-panel
141 * @attr value
142 * @attr alpha
143 */
144let DuoyunColorPanelElement = class DuoyunColorPanelElement extends GemElement {
145 constructor() {
146 super();
147 _DuoyunColorPanelElement_instances.add(this);
148 this.state = {
149 mode: 'Hex',
150 grabbingHue: false,
151 grabbingA: false,
152 grabbingSV: false,
153 // stringify
154 r: 0,
155 g: 0,
156 b: 0,
157 h: 0,
158 s: 0,
159 l: 0,
160 a: 0,
161 // hsv
162 sa: 0,
163 v: 0,
164 // without alpha
165 str: '',
166 };
167 _DuoyunColorPanelElement_typeOptions.set(this, [
168 {
169 label: 'Hex',
170 },
171 {
172 label: 'RGB',
173 },
174 {
175 label: 'HSL',
176 },
177 ]);
178 _DuoyunColorPanelElement_getPosition.set(this, (target, { clientX, clientY }) => {
179 const { left, top, width, height } = target.getBoundingClientRect();
180 return {
181 left: clamp(0, clientX - left, width) / width,
182 top: clamp(0, clientY - top, height) / height,
183 };
184 });
185 _DuoyunColorPanelElement_onPanHue.set(this, ({ detail, target }) => {
186 this.setState({ grabbingHue: true });
187 const { a, s, l } = this.state;
188 const { top } = __classPrivateFieldGet(this, _DuoyunColorPanelElement_getPosition, "f").call(this, target, detail);
189 const h = 1 - top;
190 const [r, g, b] = hslToRgb([h, s, l]);
191 const color = rgbToHexColor([r, g, b, a]);
192 this.setState({ commitValue: { h, str: color } });
193 this.change(color);
194 if (color === this.value) {
195 this.setState({ h });
196 }
197 });
198 _DuoyunColorPanelElement_onPanSV.set(this, ({ detail, target }) => {
199 this.setState({ grabbingSV: true });
200 const { h, a, str } = this.state;
201 const { left, top } = __classPrivateFieldGet(this, _DuoyunColorPanelElement_getPosition, "f").call(this, target, detail);
202 const v = 1 - top;
203 const sa = left;
204 const [_, s, l] = hsvToHsl([h, sa, v]);
205 const [r, g, b] = hslToRgb([h, s, l]);
206 const color = rgbToHexColor([r, g, b, a]);
207 this.setState({
208 sa: color === str ? sa : this.state.sa,
209 commitValue: { str: color, v, sa, h },
210 });
211 this.change(color);
212 });
213 _DuoyunColorPanelElement_onPanA.set(this, ({ detail, target }) => {
214 this.setState({ grabbingA: true });
215 const { h, s, l, sa } = this.state;
216 const [r, g, b] = hslToRgb([h, s, l]);
217 const { top } = __classPrivateFieldGet(this, _DuoyunColorPanelElement_getPosition, "f").call(this, target, detail);
218 const a = 1 - top;
219 const color = rgbToHexColor([r, g, b, a]);
220 this.setState({ commitValue: { str: color, h, a, sa } });
221 this.change(color);
222 });
223 _DuoyunColorPanelElement_onPanEnd.set(this, () => {
224 this.setState({ grabbingHue: false, grabbingSV: false, grabbingA: false });
225 });
226 _DuoyunColorPanelElement_onChangeType.set(this, (evt) => {
227 evt.stopPropagation();
228 this.setState({ mode: evt.detail });
229 });
230 _DuoyunColorPanelElement_onChangeValue.set(this, (evt) => {
231 evt.stopPropagation();
232 const { mode, a } = this.state;
233 if (mode !== 'Hex') {
234 this.setState({ mode: 'Hex' });
235 return;
236 }
237 let str = '#' + evt.detail.trim().replace('#', '').replace(/[g-z]/gi, '').toLowerCase();
238 // valid color emit event
239 if (isValidHexColor(str)) {
240 const aStr = Math.round(a * 255)
241 .toString(16)
242 .padStart(2, '0');
243 const isShortHex = str.length === 4 && aStr[0] === aStr[1];
244 str = str.slice(0, isShortHex ? 4 : 7);
245 this.change((this.alpha && a !== 1 ? str + (isShortHex ? aStr[0] : aStr) : str));
246 }
247 this.setState({ str });
248 });
249 _DuoyunColorPanelElement_onChangeA.set(this, (evt) => {
250 evt.stopPropagation();
251 const { r, g, b } = this.state;
252 this.change(rgbToHexColor([r, g, b, clamp(0, Number(evt.detail) || 0, 1)]));
253 });
254 _DuoyunColorPanelElement_openEyeDropper.set(this, async () => {
255 const result = await new window.EyeDropper().open();
256 this.change(result.sRGBHex);
257 });
258 this.willMount = () => {
259 this.memo(() => {
260 var _a;
261 const value = this.value || '#fff';
262 const [r, g, b, a] = parseHexColor(value);
263 const [h, s, l] = rgbToHsl([r, g, b]);
264 const [_, sa, v] = hslToHsv([h, s, l]);
265 const str = value.length === 5 ? value.slice(0, 4) : value.length === 9 ? value.slice(0, 7) : value;
266 const parseState = { r, g, b, a, h, s, l, sa, v, str };
267 if (this.value === ((_a = this.state.commitValue) === null || _a === void 0 ? void 0 : _a.str)) {
268 this.setState({ ...parseState, ...this.state.commitValue });
269 return;
270 }
271 else {
272 this.setState({ ...parseState });
273 }
274 }, () => [this.value]);
275 };
276 this.render = () => {
277 const { mode, grabbingHue, grabbingSV, grabbingA, h, s, l, a, sa, v, str } = this.state;
278 return html `
279 <style>
280 :host {
281 --h: ${h * 360};
282 --s: ${s * 100}%;
283 --l: ${l * 100}%;
284 --a: ${a};
285 }
286 </style>
287 <div class="color">
288 <dy-gesture class="area" @pan=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_onPanSV, "f")} @end=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_onPanEnd, "f")}>
289 <div
290 class=${classMap({ current: true, grabbing: grabbingSV })}
291 style=${styleMap({ left: `${sa * 100}%`, top: `${(1 - v) * 100}%` })}
292 >
293 <span></span>
294 </div>
295 </dy-gesture>
296 <dy-gesture class="hue-bar" @pan=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_onPanHue, "f")} @end=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_onPanEnd, "f")}>
297 <div
298 class=${classMap({ current: true, grabbing: grabbingHue })}
299 style=${styleMap({ top: `${(1 - h) * 100}%`, left: '50%' })}
300 >
301 <span></span>
302 </div>
303 </dy-gesture>
304 ${this.alpha
305 ? html `
306 <dy-gesture class="alpha-bar" @pan=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_onPanA, "f")} @end=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_onPanEnd, "f")}>
307 <div
308 class=${classMap({ current: true, grabbing: grabbingA })}
309 style=${styleMap({ top: `${(1 - a) * 100}%`, left: '50%' })}
310 >
311 <span></span>
312 </div>
313 </dy-gesture>
314 `
315 : ''}
316 </div>
317 <div class="input">
318 <dy-select
319 class="type"
320 ?borderless=${true}
321 .options=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_typeOptions, "f")}
322 .value=${mode}
323 .dropdownStyle=${{ fontSize: '0.75em' }}
324 @change=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_onChangeType, "f")}
325 ></dy-select>
326 <dy-input class="value" value=${mode === 'Hex' ? str : __classPrivateFieldGet(this, _DuoyunColorPanelElement_instances, "a", _DuoyunColorPanelElement_color_get)} @change=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_onChangeValue, "f")}></dy-input>
327 <dy-input
328 class=${classMap({ alpha: true, hidden: !this.alpha })}
329 aria-hidden=${!this.alpha}
330 type="number"
331 value=${String(formatToPrecision(a))}
332 step=${0.1}
333 @change=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_onChangeA, "f")}
334 ></dy-input>
335 <dy-use
336 role="button"
337 aria-hidden=${!('EyeDropper' in window)}
338 class=${classMap({ colorize: true, hidden: !('EyeDropper' in window) })}
339 .element=${icons.colorize}
340 @click=${__classPrivateFieldGet(this, _DuoyunColorPanelElement_openEyeDropper, "f")}
341 ></dy-use>
342 </div>
343 `;
344 };
345 this.internals.role = 'widget';
346 }
347};
348_DuoyunColorPanelElement_typeOptions = new WeakMap(), _DuoyunColorPanelElement_getPosition = new WeakMap(), _DuoyunColorPanelElement_onPanHue = new WeakMap(), _DuoyunColorPanelElement_onPanSV = new WeakMap(), _DuoyunColorPanelElement_onPanA = new WeakMap(), _DuoyunColorPanelElement_onPanEnd = new WeakMap(), _DuoyunColorPanelElement_onChangeType = new WeakMap(), _DuoyunColorPanelElement_onChangeValue = new WeakMap(), _DuoyunColorPanelElement_onChangeA = new WeakMap(), _DuoyunColorPanelElement_openEyeDropper = new WeakMap(), _DuoyunColorPanelElement_instances = new WeakSet(), _DuoyunColorPanelElement_color_get = function _DuoyunColorPanelElement_color_get() {
349 const { r, g, b, mode } = this.state;
350 switch (mode) {
351 case 'Hex':
352 return rgbToHexColor([r, g, b]);
353 case 'RGB':
354 return rgbToRgbColor([r, g, b]);
355 case 'HSL':
356 return rgbToHslColor([r, g, b]);
357 }
358};
359__decorate([
360 attribute
361], DuoyunColorPanelElement.prototype, "value", void 0);
362__decorate([
363 boolattribute
364], DuoyunColorPanelElement.prototype, "alpha", void 0);
365__decorate([
366 globalemitter
367], DuoyunColorPanelElement.prototype, "change", void 0);
368DuoyunColorPanelElement = __decorate([
369 customElement('dy-color-panel'),
370 adoptedStyle(style)
371], DuoyunColorPanelElement);
372export { DuoyunColorPanelElement };
373//# sourceMappingURL=color-panel.js.map
\No newline at end of file