vue-accessible-color-picker
Version:
Color picker component for Vue.js
484 lines (483 loc) • 15.3 kB
JavaScript
import { defineComponent as se, useTemplateRef as K, ref as Y, computed as S, watch as ue, onMounted as ie, onBeforeUnmount as he, toRaw as me, createElementBlock as b, openBlock as g, normalizeStyle as M, unref as pe, createElementVNode as a, createCommentVNode as U, renderSlot as x, createTextVNode as X, Fragment as fe, renderList as de, toDisplayString as ve } from "vue";
import y from "colorjs.io";
function v(n, s, i) {
return Math.max(s, Math.min(n, i));
}
function I(n, { format: s = "srgb", alpha: i = !0 } = { format: "srgb", alpha: !0 }) {
const r = {
alpha: i
};
s === "srgb" ? r.format = {
name: "rgb",
coords: ["<number>[0, 255]", "<number>[0, 255]", "<number>[0, 255]"]
} : s === "hex" && (r.format = "hex", r.collapse = !0);
const p = s === "hex" ? "srgb" : s;
return n.to(p).toString(r).replace(/none/g, "0");
}
function q(n, s, i) {
const r = n.getBoundingClientRect(), p = s - r.left, d = i - r.top;
return {
x: r.width === 0 ? 0 : v(p / r.width * 100, 0, 100),
y: r.height === 0 ? 0 : v((1 - d / r.height) * 100, 0, 100)
};
}
const be = {
deg: 1,
grad: 0.9,
rad: 180 / Math.PI,
turn: 360
};
function ge() {
const n = f(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, 1);
return {
from(s) {
const i = s.match(/deg|g?rad|turn$/);
if (i === null)
return n.from(s);
const r = i[0];
return n.from(s.slice(0, -r.length)) * be[r];
}
};
}
function f(n, s, i) {
return {
from(r) {
if (r.endsWith("%")) {
const p = r.slice(0, -1);
return p.endsWith(".") ? NaN : v(Number(p), 0, 100) * i / 100;
}
return r.endsWith(".") ? NaN : v(Number(r), n, s);
}
};
}
const j = 5, ye = f(0, 1, 1), A = ge(), w = f(0, 100, 100), J = f(-125, 125, 1), we = f(0, 150, 1), Q = f(0, 1, 1), Z = f(-0.4, 0.4, 1), Te = f(0, 0.4, 1), V = f(0, 255, 255), H = {
channel: "alpha",
label: "Alpha",
preferredType: "<number>",
from: ye.from
}, _ = {
hsl: [
{
channel: "h",
label: "H",
preferredType: "<number>",
from: A.from
},
{
channel: "s",
label: "S",
preferredType: "<percentage>",
from: w.from
},
{
channel: "l",
label: "L",
preferredType: "<percentage>",
from: w.from
}
],
hwb: [
{
channel: "h",
label: "H",
preferredType: "<number>",
from: A.from
},
{
channel: "w",
label: "W",
preferredType: "<percentage>",
from: w.from
},
{
channel: "b",
label: "B",
preferredType: "<percentage>",
from: w.from
}
],
lab: [
{
channel: "l",
label: "L",
preferredType: "<percentage>",
from: w.from
},
{
channel: "a",
label: "a",
preferredType: "<number>",
from: J.from
},
{
channel: "b",
label: "b",
preferredType: "<number>",
from: J.from
}
],
lch: [
{
channel: "l",
label: "L",
preferredType: "<percentage>",
from: w.from
},
{
channel: "c",
label: "C",
preferredType: "<number>",
from: we.from
},
{
channel: "h",
label: "H",
preferredType: "<number>",
from: A.from
}
],
oklab: [
{
channel: "l",
label: "L",
preferredType: "<percentage>",
from: Q.from
},
{
channel: "a",
label: "a",
preferredType: "<number>",
from: Z.from
},
{
channel: "b",
label: "b",
preferredType: "<number>",
from: Z.from
}
],
oklch: [
{
channel: "l",
label: "L",
preferredType: "<percentage>",
from: Q.from
},
{
channel: "c",
label: "C",
preferredType: "<number>",
from: Te.from
},
{
channel: "h",
label: "H",
preferredType: "<number>",
from: A.from
}
],
srgb: [
{
channel: "r",
label: "R",
preferredType: "<number>",
from: V.from
},
{
channel: "g",
label: "G",
preferredType: "<number>",
from: V.from
},
{
channel: "b",
label: "B",
preferredType: "<number>",
from: V.from
}
]
}, Ce = { class: "vacp-range-input-group" }, xe = ["for"], Ne = { class: "vacp-range-input-label-text vacp-range-input-label-text--hue" }, $e = ["id", "value"], ke = ["for"], Ie = { class: "vacp-range-input-label-text vacp-range-input-label-text--alpha" }, Ae = ["id", "value"], Fe = { class: "vacp-actions" }, Le = { class: "vacp-color-inputs" }, Ee = { class: "vacp-color-input-group" }, Pe = ["for"], Se = ["id", "value"], Me = ["id", "for"], Ve = { class: "vacp-color-input-label-text" }, He = ["id", "value"], De = /* @__PURE__ */ se({
__name: "ColorPicker",
props: {
color: { default: "#ffffffff" },
copy: { type: Function },
id: { default: "color-picker" },
visibleFormats: { default: () => ["hex", "hsl", "hwb", "srgb"] },
defaultFormat: { default: "hsl" },
alphaChannel: { default: "show" }
},
emits: ["color-change", "color-copy"],
setup(n, { expose: s, emit: i }) {
const r = i;
s({
copyColor: z,
switchFormat: D
});
const p = K("colorPicker"), d = K("colorSpaceRef");
let T = !1;
const h = Y(n.visibleFormats.includes(n.defaultFormat) ? n.defaultFormat : n.visibleFormats[0]), l = Y(new y("srgb", [1, 1, 1], 1)), ee = S(function() {
const e = h.value, t = l.value.space.getFormat("default").coords;
return _[e].map(({ channel: o, label: c, preferredType: u }, m) => {
const C = l.value.to(e);
C.toGamut();
const k = t[m];
return { value: (k.find(({ type: ce }) => ce === u) ?? k.at(0)).serialize(C.coords[m] ?? 0, j), channel: o, label: c };
}).concat(n.alphaChannel === "show" ? [{
value: l.value.alpha.toPrecision(j).replace(/\.?0+$/, ""),
channel: H.channel,
label: H.label
}] : []);
}), te = S(function() {
return I(l.value, { format: "hex", alpha: n.alphaChannel === "show" });
}), F = S(function() {
const e = l.value.to("hsv");
return e.toGamut(), e;
});
ue(() => n.color, R, { immediate: !0 }), ie(function() {
document.addEventListener("pointermove", L, { passive: !1 }), document.addEventListener("touchmove", E, { passive: !1 }), document.addEventListener("pointerup", N), document.addEventListener("touchend", N);
}), he(function() {
document.removeEventListener("pointermove", L), document.removeEventListener("touchmove", E), document.removeEventListener("pointerup", N), document.removeEventListener("touchend", N);
});
function D() {
const t = (n.visibleFormats.findIndex((o) => o === h.value) + 1) % n.visibleFormats.length;
h.value = n.visibleFormats[t];
}
function ne(e) {
T = !0, L(e);
}
function oe(e) {
T = !0, E(e);
}
function N() {
T = !1;
}
function L(e) {
e.buttons !== 1 || !T || !(d.value instanceof HTMLElement) || P(q(d.value, e.clientX, e.clientY));
}
function E(e) {
if (!T || !(d.value instanceof HTMLElement))
return;
e.preventDefault();
const t = e.touches[0];
P(q(d.value, t.clientX, t.clientY));
}
function P({ x: e, y: t }) {
const o = l.value.to("hsv");
o.toGamut(), $(new y("hsv", [o.h ?? 0, e, t], l.value.alpha));
}
function re(e) {
if (!["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"].includes(e.key))
return;
e.preventDefault();
const t = ["ArrowLeft", "ArrowDown"].includes(e.key) ? -1 : 1, o = ["ArrowLeft", "ArrowRight"].includes(e.key) ? "s" : "v", c = e.shiftKey ? 10 : 1, u = l.value.to("hsv");
u.toGamut();
const m = u.s ?? 0, C = u.v ?? 0, k = o === "s" ? v(m + t * c, 0, 100) : m, O = o === "v" ? v(C + t * c, 0, 100) : C;
P({ x: k, y: O });
}
function R(e) {
let t;
try {
t = e instanceof y ? e : new y(e);
} catch (o) {
if (o instanceof TypeError)
return;
throw o;
}
$(t);
}
function W(e, t) {
const o = e.currentTarget, c = t === "alpha" ? Number(o.value) : l.value.alpha, u = l.value.to("hsl");
u.toGamut();
const m = t === "h" ? Number(o.value) : u.h ?? 0;
$(new y("hsl", [m, u.s ?? 0, u.l ?? 0], c));
}
function ae(e) {
const t = e.target;
/^#(([A-F0-9]{2}){3,4}|[A-F0-9]{3,4})$/i.test(t.value) && R(t.value);
}
function le() {
const e = h.value, t = _[e].concat(n.alphaChannel === "show" ? [H] : []).map(({ channel: c, from: u }) => {
const m = p.value.querySelector(`input[id="${n.id}-color-${e}-${c}"]`);
return u(m.value);
});
if (t.some((c) => Number.isNaN(c)))
return;
const o = t.slice(0, 3);
$(new y(e, o, t[3]));
}
function $(e) {
Math.abs(l.value.distance(e)) < Number.EPSILON && l.value.alpha === e.alpha || (l.value = e, r("color-change", B()));
}
async function z() {
const e = I(l.value, { format: h.value, alpha: n.alphaChannel === "show" });
await (n.copy ? n.copy : (o) => window.navigator.clipboard.writeText(o))(e), r("color-copy", B());
}
function B() {
return {
color: me(l.value),
cssColor: I(l.value, { format: h.value, alpha: n.alphaChannel === "show" })
};
}
function G(e) {
if (!["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"].includes(e.key) || !e.shiftKey)
return;
const t = e.currentTarget, o = Number(t.step), c = ["ArrowLeft", "ArrowDown"].includes(e.key) ? -1 : 1, u = Number(t.value) + c * o * 10, m = v(u, Number(t.min), Number(t.max));
t.value = String(m - c * o);
}
return (e, t) => (g(), b("div", {
ref_key: "colorPicker",
ref: p,
class: "vacp-color-picker",
style: M(`--vacp-color: ${pe(I)(l.value, { format: "hsl", alpha: !1 })}`)
}, [
a("div", {
ref: "colorSpaceRef",
class: "vacp-color-space",
style: M(`position: relative; background: linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent); background-color: hsl(${F.value.h} 100% 50%)`),
onPointerdown: ne,
onTouchstart: oe
}, [
a("div", {
ref: "thumb",
class: "vacp-color-space-thumb",
tabindex: "0",
"aria-label": "Color space thumb",
style: M(`box-sizing: border-box; position: absolute; left: ${F.value.s}%; bottom: ${F.value.v}%;`),
onKeydown: re
}, null, 36)
], 36),
a("div", Ce, [
a("label", {
class: "vacp-range-input-label vacp-range-input-label--hue",
for: `${n.id}-hue-slider`
}, [
a("span", Ne, [
x(e.$slots, "hue-range-input-label", {}, () => [
t[2] || (t[2] = X("Hue", -1))
])
]),
a("input", {
id: `${n.id}-hue-slider`,
class: "vacp-range-input vacp-range-input--hue",
value: l.value.to("hsl").toGamut().h ?? 0,
type: "range",
min: "0",
max: "360",
step: "1",
onKeydown: G,
onInput: t[0] || (t[0] = (o) => W(o, "h"))
}, null, 40, $e)
], 8, xe),
n.alphaChannel === "show" ? (g(), b("label", {
key: 0,
class: "vacp-range-input-label vacp-range-input-label--alpha",
for: `${n.id}-alpha-slider`
}, [
a("span", Ie, [
x(e.$slots, "alpha-range-input-label", {}, () => [
t[3] || (t[3] = X("Alpha", -1))
])
]),
a("input", {
id: `${n.id}-alpha-slider`,
class: "vacp-range-input vacp-range-input--alpha",
value: l.value.alpha,
type: "range",
min: "0",
max: "1",
step: "0.01",
onKeydown: G,
onInput: t[1] || (t[1] = (o) => W(o, "alpha"))
}, null, 40, Ae)
], 8, ke)) : U("", !0)
]),
a("div", Fe, [
a("button", {
class: "vacp-copy-button",
type: "button",
onClick: z
}, [
x(e.$slots, "copy-button", {}, () => [
t[4] || (t[4] = a("span", { class: "vacp-visually-hidden" }, "Copy color", -1)),
t[5] || (t[5] = a("svg", {
class: "vacp-icon",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": "true",
width: "24",
height: "24",
viewBox: "0 0 32 32"
}, [
a("path", {
d: "M25.313 28v-18.688h-14.625v18.688h14.625zM25.313 6.688c1.438 0 2.688 1.188 2.688 2.625v18.688c0 1.438-1.25 2.688-2.688 2.688h-14.625c-1.438 0-2.688-1.25-2.688-2.688v-18.688c0-1.438 1.25-2.625 2.688-2.625h14.625zM21.313 1.313v2.688h-16v18.688h-2.625v-18.688c0-1.438 1.188-2.688 2.625-2.688h16z",
fill: "currentColor"
})
], -1))
])
]),
x(e.$slots, "actions")
]),
a("div", Le, [
a("div", Ee, [
h.value === "hex" ? (g(), b("label", {
key: 0,
class: "vacp-color-input-label",
for: `${n.id}-color-hex`
}, [
t[6] || (t[6] = a("span", { class: "vacp-color-input-label-text" }, " Hex ", -1)),
a("input", {
id: `${n.id}-color-hex`,
class: "vacp-color-input",
type: "text",
value: te.value,
onChange: ae
}, null, 40, Se)
], 8, Pe)) : (g(!0), b(fe, { key: 1 }, de(ee.value, ({ value: o, channel: c, label: u }) => (g(), b("label", {
id: `${n.id}-color-${h.value}-${c}-label`,
key: `${n.id}-color-${h.value}-${c}-label`,
class: "vacp-color-input-label",
for: `${n.id}-color-${h.value}-${c}`
}, [
a("span", Ve, ve(u), 1),
a("input", {
id: `${n.id}-color-${h.value}-${c}`,
class: "vacp-color-input",
type: "text",
value: o,
onChange: le
}, null, 40, He)
], 8, Me))), 128))
]),
n.visibleFormats.length > 1 ? (g(), b("button", {
key: 0,
class: "vacp-format-switch-button",
type: "button",
onClick: D
}, [
x(e.$slots, "format-switch-button", {}, () => [
t[7] || (t[7] = a("span", { class: "vacp-visually-hidden" }, "Switch format", -1)),
t[8] || (t[8] = a("svg", {
class: "vacp-icon",
"aria-hidden": "true",
xmlns: "http://www.w3.org/2000/svg",
width: "16",
height: "15"
}, [
a("path", {
d: "M8 15l5-5-1-1-4 2-4-2-1 1zm4-9l1-1-5-5-5 5 1 1 4-2z",
fill: "currentColor"
})
], -1))
])
])) : U("", !0)
])
], 4));
}
}), Be = {
install(n) {
n.component("ColorPicker", De);
}
};
export {
De as ColorPicker,
Be as default
};