1 | var __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 | };
|
7 | var __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 | };
|
12 | var _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 |
|
14 | import { adoptedStyle, customElement, attribute, globalemitter, boolattribute, } from '@mantou/gem/lib/decorators';
|
15 | import { GemElement, html } from '@mantou/gem/lib/element';
|
16 | import { createCSSSheet, css, styleMap, classMap } from '@mantou/gem/lib/utils';
|
17 | import { parseHexColor, rgbToHexColor, rgbToHslColor, rgbToRgbColor, hslToRgb, rgbToHsl, hslToHsv, hsvToHsl, isValidHexColor, } from '../lib/color';
|
18 | import { theme } from '../lib/theme';
|
19 | import { icons } from '../lib/icons';
|
20 | import { clamp, formatToPrecision } from '../lib/number';
|
21 | import './gesture';
|
22 | import './use';
|
23 | import './input';
|
24 | import './select';
|
25 | const 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 |
|
141 |
|
142 |
|
143 |
|
144 | let 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 |
|
154 | r: 0,
|
155 | g: 0,
|
156 | b: 0,
|
157 | h: 0,
|
158 | s: 0,
|
159 | l: 0,
|
160 | a: 0,
|
161 |
|
162 | sa: 0,
|
163 | v: 0,
|
164 |
|
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 |
|
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);
|
368 | DuoyunColorPanelElement = __decorate([
|
369 | customElement('dy-color-panel'),
|
370 | adoptedStyle(style)
|
371 | ], DuoyunColorPanelElement);
|
372 | export { DuoyunColorPanelElement };
|
373 |
|
\ | No newline at end of file |