@enigmax/primitives 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-QQFNAKMY.js → chunk-45UHLZYT.js} +1 -1
- package/dist/chunk-4VUHQFAT.js +130 -0
- package/dist/{chunk-D5A2ZMAG.js → chunk-4ZPMP47J.js} +6 -1
- package/dist/chunk-DZMHY3SU.js +777 -0
- package/dist/{chunk-FWVWX67R.js → chunk-FGBIZDV2.js} +3 -3
- package/dist/chunk-N6PDHMAX.js +213 -0
- package/dist/chunk-NONGREXC.js +248 -0
- package/dist/{chunk-R4ZAEE7V.js → chunk-QJJ34E4G.js} +34 -10
- package/dist/chunk-S2FPN5ID.js +115 -0
- package/dist/chunk-S7EE57YB.js +9 -0
- package/dist/chunk-VKL3DEIQ.js +804 -0
- package/dist/chunk-WSQC3PCC.js +466 -0
- package/dist/chunk-Z3VDE7OA.js +532 -0
- package/dist/clipboard-menu-B_ouitfS.d.ts +264 -0
- package/dist/color-D_rZ83Oc.d.ts +152 -0
- package/dist/color-OPV3BJV6.js +452 -0
- package/dist/{index-BHpOZncw.d.ts → index-ZPvlf9vs.d.ts} +53 -5
- package/dist/index.d.ts +6 -2
- package/dist/index.js +9 -4
- package/dist/keys-D2zJs1uB.d.ts +100 -0
- package/dist/next/index.d.ts +10 -3
- package/dist/next/index.js +22 -13
- package/dist/react/context-menu.d.ts +219 -0
- package/dist/react/context-menu.js +6 -0
- package/dist/react/index.d.ts +12 -5
- package/dist/react/index.js +21 -12
- package/dist/react/input.d.ts +3 -3
- package/dist/react/input.js +2 -1
- package/dist/react/palette.d.ts +1 -1
- package/dist/react/palette.js +3 -3
- package/dist/react/search.d.ts +1 -1
- package/dist/react/search.js +2 -2
- package/dist/react/select.d.ts +217 -0
- package/dist/react/select.js +7 -0
- package/dist/react/selection.d.ts +104 -0
- package/dist/react/selection.js +4 -0
- package/dist/react-router/index.d.ts +10 -3
- package/dist/react-router/index.js +22 -13
- package/dist/search/index.d.ts +2 -2
- package/dist/search/index.js +1 -1
- package/dist/{search-BD9-5O5U.d.ts → search-DYgqRp37.d.ts} +13 -1
- package/dist/{search-UQEXAPQB.js → search-PBZORZ7P.js} +1 -1
- package/dist/select-ClSy-J1f.d.ts +100 -0
- package/dist/selection-B_pmzHpy.d.ts +150 -0
- package/package.json +23 -2
- package/recipes/color/styles.css +143 -0
- package/recipes/context-menu/styles.css +185 -0
- package/recipes/input/styles.css +9 -0
- package/recipes/palette/styles.css +3 -0
- package/recipes/search/tailwind.tsx +3 -2
- package/recipes/select/styles.css +230 -0
- package/recipes/toast/styles.css +1 -1
- package/registry.json +432 -5
- package/src/core/clipboard-menu.ts +270 -0
- package/src/core/color.ts +248 -0
- package/src/core/context-menu.ts +694 -0
- package/src/core/keys.ts +264 -0
- package/src/core/search.ts +19 -0
- package/src/core/select.ts +404 -0
- package/src/core/selection.ts +648 -0
- package/src/index.ts +92 -1
- package/src/react/context-menu/context.ts +64 -0
- package/src/react/context-menu/index.tsx +108 -0
- package/src/react/context-menu/root.tsx +970 -0
- package/src/react/context-menu/styles.ts +194 -0
- package/src/react/index.ts +100 -1
- package/src/react/input/color-styles.ts +156 -0
- package/src/react/input/color.tsx +466 -0
- package/src/react/input/index.tsx +54 -6
- package/src/react/input/types.ts +59 -3
- package/src/react/palette/root.tsx +2 -2
- package/src/react/select/context.ts +56 -0
- package/src/react/select/index.tsx +96 -0
- package/src/react/select/root.tsx +839 -0
- package/src/react/select/styles.ts +238 -0
- package/src/react/selection/index.tsx +115 -0
- package/src/react/selection/use-selection.ts +260 -0
- package/dist/password-C8lG4Zm9.d.ts +0 -71
- /package/dist/{chunk-U3V4EHOB.js → chunk-MSOCCQGH.js} +0 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { parseColor, rgbToHsv, colorEquals, hsvToRgb, formatColor, toHex } from './chunk-S2FPN5ID.js';
|
|
2
|
+
import { writeValue } from './chunk-S7EE57YB.js';
|
|
3
|
+
import { useLayoutEffect, useRef, useState, useMemo, useEffect, useCallback } from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/react/input/color-styles.ts
|
|
7
|
+
var COLOR_STYLES = `
|
|
8
|
+
:root {
|
|
9
|
+
--enigma-color-panel-bg: #1c1c1c;
|
|
10
|
+
--enigma-color-panel-border: #333333;
|
|
11
|
+
--enigma-color-panel-radius: 0.625rem;
|
|
12
|
+
--enigma-color-panel-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
|
|
13
|
+
--enigma-color-panel-width: 13.5rem;
|
|
14
|
+
--enigma-color-text: #f5f5f5;
|
|
15
|
+
--enigma-color-muted: #a3a3a3;
|
|
16
|
+
--enigma-color-border: #404040;
|
|
17
|
+
--enigma-color-focus: #a3a3a3;
|
|
18
|
+
--enigma-color-area-height: 8rem;
|
|
19
|
+
--enigma-color-rail-height: 0.625rem;
|
|
20
|
+
--enigma-color-thumb: #ffffff;
|
|
21
|
+
--enigma-color-thumb-ring: rgba(0, 0, 0, 0.55);
|
|
22
|
+
/* The light square of the transparency chequerboard. The dark one is the surface it sits
|
|
23
|
+
on, which is why only one colour is a property. */
|
|
24
|
+
--enigma-color-checker: rgba(255, 255, 255, 0.22);
|
|
25
|
+
--enigma-color-checker-size: 0.5rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@media (prefers-color-scheme: light) {
|
|
29
|
+
:root {
|
|
30
|
+
--enigma-color-panel-bg: #ffffff;
|
|
31
|
+
--enigma-color-panel-border: #e5e5e5;
|
|
32
|
+
--enigma-color-panel-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
|
|
33
|
+
--enigma-color-text: #171717;
|
|
34
|
+
--enigma-color-muted: #737373;
|
|
35
|
+
--enigma-color-border: #d4d4d4;
|
|
36
|
+
--enigma-color-focus: #737373;
|
|
37
|
+
--enigma-color-thumb-ring: rgba(0, 0, 0, 0.35);
|
|
38
|
+
--enigma-color-checker: rgba(0, 0, 0, 0.14);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* The anchor the panel hangs from. Inline so it sits in the field's own row, and the
|
|
43
|
+
positioning context so the panel needs no portal and no scroll listener: it moves with
|
|
44
|
+
the field because it is INSIDE it. */
|
|
45
|
+
[data-enigma-color] { position: relative; display: inline-flex; flex: none; }
|
|
46
|
+
|
|
47
|
+
[data-enigma-color-swatch] {
|
|
48
|
+
display: block; width: 1.25rem; height: 1.25rem; padding: 0;
|
|
49
|
+
background: none; border: 1px solid var(--enigma-color-border); border-radius: 0.3125rem;
|
|
50
|
+
cursor: pointer; overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
[data-enigma-color-swatch]:focus-visible { outline: 2px solid var(--enigma-color-focus); outline-offset: 2px; }
|
|
53
|
+
[data-enigma-color-swatch]:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
54
|
+
|
|
55
|
+
/* The chequerboard, so a colour with alpha reads as transparent rather than as a lighter
|
|
56
|
+
colour - which is the whole difference between #ff000080 and #ff8080. */
|
|
57
|
+
[data-enigma-color-checkers] {
|
|
58
|
+
background-image:
|
|
59
|
+
conic-gradient(var(--enigma-color-checker) 0 25%, transparent 0 50%, var(--enigma-color-checker) 0 75%, transparent 0);
|
|
60
|
+
background-size: var(--enigma-color-checker-size) var(--enigma-color-checker-size);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
[data-enigma-color-fill] { display: block; width: 100%; height: 100%; }
|
|
64
|
+
/* An unparseable field has no colour to show, so the swatch shows the chequerboard alone
|
|
65
|
+
rather than the last good colour: a stale swatch says the value is fine when it is not. */
|
|
66
|
+
[data-enigma-color-swatch][data-invalid] [data-enigma-color-fill] { background: none; }
|
|
67
|
+
|
|
68
|
+
[data-enigma-color-panel] {
|
|
69
|
+
position: absolute; z-index: 60; top: calc(100% + 0.375rem); left: 0;
|
|
70
|
+
display: grid; gap: 0.625rem;
|
|
71
|
+
box-sizing: border-box; width: var(--enigma-color-panel-width); padding: 0.625rem;
|
|
72
|
+
color: var(--enigma-color-text); background: var(--enigma-color-panel-bg);
|
|
73
|
+
border: 1px solid var(--enigma-color-panel-border);
|
|
74
|
+
border-radius: var(--enigma-color-panel-radius);
|
|
75
|
+
box-shadow: var(--enigma-color-panel-shadow);
|
|
76
|
+
}
|
|
77
|
+
/* Flipped when the field sits near the bottom of the window. Without it the panel opens off
|
|
78
|
+
the screen and the controls are unreachable, which is the same defect the select fixes. */
|
|
79
|
+
[data-enigma-color-panel][data-side="top"] { top: auto; bottom: calc(100% + 0.375rem); }
|
|
80
|
+
|
|
81
|
+
[data-enigma-color-area] {
|
|
82
|
+
position: relative; height: var(--enigma-color-area-height);
|
|
83
|
+
border-radius: 0.375rem; cursor: crosshair;
|
|
84
|
+
/* The drag IS the control here, so the page must not scroll under it. The marquee's
|
|
85
|
+
warning about touch-action is the opposite case: a full-width row still has to let the
|
|
86
|
+
page move. */
|
|
87
|
+
touch-action: none;
|
|
88
|
+
background:
|
|
89
|
+
linear-gradient(to top, #000000, rgba(0, 0, 0, 0)),
|
|
90
|
+
linear-gradient(to right, #ffffff, rgba(255, 255, 255, 0)),
|
|
91
|
+
hsl(var(--enigma-color-hue, 0), 100%, 50%);
|
|
92
|
+
}
|
|
93
|
+
[data-enigma-color-area]:focus-visible,
|
|
94
|
+
[data-enigma-color-rail]:focus-visible { outline: 2px solid var(--enigma-color-focus); outline-offset: 2px; }
|
|
95
|
+
|
|
96
|
+
[data-enigma-color-controls] { display: flex; align-items: center; gap: 0.5rem; }
|
|
97
|
+
[data-enigma-color-rails] { display: grid; gap: 0.5rem; flex: 1; min-width: 0; }
|
|
98
|
+
|
|
99
|
+
[data-enigma-color-rail] {
|
|
100
|
+
position: relative; height: var(--enigma-color-rail-height);
|
|
101
|
+
border-radius: 999px; cursor: pointer; touch-action: none;
|
|
102
|
+
}
|
|
103
|
+
[data-enigma-color-rail="hue"] {
|
|
104
|
+
background: linear-gradient(to right, #ff0000, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000);
|
|
105
|
+
}
|
|
106
|
+
[data-enigma-color-rail="alpha"] { background-image: none; }
|
|
107
|
+
[data-enigma-color-rail="alpha"] [data-enigma-color-gradient] {
|
|
108
|
+
position: absolute; inset: 0; border-radius: inherit;
|
|
109
|
+
background: linear-gradient(to right, transparent, var(--enigma-color-opaque, #000000));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* One handle for the square and the rails. It must never take the pointer: a press that
|
|
113
|
+
lands on the handle instead of the surface under it would start no drag at all. */
|
|
114
|
+
[data-enigma-color-thumb] {
|
|
115
|
+
position: absolute; pointer-events: none;
|
|
116
|
+
width: 0.75rem; height: 0.75rem; margin: -0.375rem 0 0 -0.375rem;
|
|
117
|
+
border: 2px solid var(--enigma-color-thumb); border-radius: 999px;
|
|
118
|
+
box-shadow: 0 0 0 1px var(--enigma-color-thumb-ring), inset 0 0 0 1px var(--enigma-color-thumb-ring);
|
|
119
|
+
}
|
|
120
|
+
[data-enigma-color-rail] [data-enigma-color-thumb] { top: 50%; }
|
|
121
|
+
|
|
122
|
+
[data-enigma-color-preview] {
|
|
123
|
+
position: relative; flex: none; width: 1.75rem; height: 1.75rem;
|
|
124
|
+
border: 1px solid var(--enigma-color-border); border-radius: 999px; overflow: hidden;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
[data-enigma-color-eyedropper] {
|
|
128
|
+
display: grid; place-items: center; flex: none;
|
|
129
|
+
width: 1.75rem; height: 1.75rem; padding: 0;
|
|
130
|
+
color: var(--enigma-color-muted); background: none;
|
|
131
|
+
border: 1px solid var(--enigma-color-border); border-radius: 0.375rem; cursor: pointer;
|
|
132
|
+
}
|
|
133
|
+
[data-enigma-color-eyedropper]:hover { color: var(--enigma-color-text); }
|
|
134
|
+
[data-enigma-color-eyedropper]:focus-visible { outline: 2px solid var(--enigma-color-focus); outline-offset: 2px; }
|
|
135
|
+
|
|
136
|
+
[data-enigma-color-swatches] { display: flex; flex-wrap: wrap; gap: 0.25rem; }
|
|
137
|
+
[data-enigma-color-preset] {
|
|
138
|
+
width: 1.125rem; height: 1.125rem; padding: 0;
|
|
139
|
+
border: 1px solid var(--enigma-color-border); border-radius: 0.25rem; cursor: pointer;
|
|
140
|
+
}
|
|
141
|
+
[data-enigma-color-preset]:focus-visible { outline: 2px solid var(--enigma-color-focus); outline-offset: 2px; }
|
|
142
|
+
[data-enigma-color-preset][aria-pressed="true"] { box-shadow: 0 0 0 2px var(--enigma-color-focus); }
|
|
143
|
+
`;
|
|
144
|
+
var INITIAL = { h: 0, s: 0, v: 0, a: 1 };
|
|
145
|
+
var STEP = { fine: 0.01, coarse: 0.1, hue: 1, hueCoarse: 10 };
|
|
146
|
+
var MARGIN = 12;
|
|
147
|
+
var injected = false;
|
|
148
|
+
function injectStyles() {
|
|
149
|
+
if (injected || typeof document === "undefined") return;
|
|
150
|
+
injected = true;
|
|
151
|
+
if (document.querySelector("[data-enigma-color-styles]")) return;
|
|
152
|
+
const element = document.createElement("style");
|
|
153
|
+
element.setAttribute("data-enigma-color-styles", "");
|
|
154
|
+
element.textContent = COLOR_STYLES;
|
|
155
|
+
document.head.prepend(element);
|
|
156
|
+
}
|
|
157
|
+
function clamp01(value) {
|
|
158
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
159
|
+
}
|
|
160
|
+
function ColorExtras({
|
|
161
|
+
input,
|
|
162
|
+
value,
|
|
163
|
+
format = "hex",
|
|
164
|
+
alpha = false,
|
|
165
|
+
swatches,
|
|
166
|
+
eyedropper = true,
|
|
167
|
+
placement = "auto",
|
|
168
|
+
styles = true,
|
|
169
|
+
locked = false,
|
|
170
|
+
labels
|
|
171
|
+
}) {
|
|
172
|
+
useLayoutEffect(() => {
|
|
173
|
+
if (styles) injectStyles();
|
|
174
|
+
}, [styles]);
|
|
175
|
+
const anchorRef = useRef(null);
|
|
176
|
+
const panelRef = useRef(null);
|
|
177
|
+
const swatchRef = useRef(null);
|
|
178
|
+
const areaRef = useRef(null);
|
|
179
|
+
const [open, setOpen] = useState(false);
|
|
180
|
+
const [side, setSide] = useState(placement === "top" ? "top" : "bottom");
|
|
181
|
+
const parsed = useMemo(() => parseColor(value), [value]);
|
|
182
|
+
const [hsv, setHsv] = useState(() => parsed ? rgbToHsv(parsed) : INITIAL);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
if (!parsed) return;
|
|
185
|
+
setHsv((current) => colorEquals(hsvToRgb(current), parsed) ? current : rgbToHsv(parsed, current.h));
|
|
186
|
+
}, [parsed]);
|
|
187
|
+
const rgb = useMemo(() => hsvToRgb(hsv), [hsv]);
|
|
188
|
+
const commit = useCallback((next) => {
|
|
189
|
+
setHsv(next);
|
|
190
|
+
if (input) writeValue(input, formatColor(hsvToRgb(next), format, { alpha }));
|
|
191
|
+
}, [input, format, alpha]);
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (!input) return;
|
|
194
|
+
const onBlur = () => {
|
|
195
|
+
const colour = parseColor(input.value);
|
|
196
|
+
if (!colour) return;
|
|
197
|
+
const canonical = formatColor(colour, format, { alpha });
|
|
198
|
+
if (canonical !== input.value) writeValue(input, canonical);
|
|
199
|
+
};
|
|
200
|
+
input.addEventListener("blur", onBlur);
|
|
201
|
+
return () => input.removeEventListener("blur", onBlur);
|
|
202
|
+
}, [input, format, alpha]);
|
|
203
|
+
const setOpenState = useCallback((next) => {
|
|
204
|
+
setOpen((current) => current === next ? current : next);
|
|
205
|
+
}, []);
|
|
206
|
+
const close = useCallback((focusSwatch) => {
|
|
207
|
+
setOpenState(false);
|
|
208
|
+
if (focusSwatch) swatchRef.current?.focus();
|
|
209
|
+
}, [setOpenState]);
|
|
210
|
+
const place = useCallback(() => {
|
|
211
|
+
if (placement !== "auto") return setSide(placement);
|
|
212
|
+
const anchor = anchorRef.current?.getBoundingClientRect();
|
|
213
|
+
const panel = panelRef.current?.getBoundingClientRect();
|
|
214
|
+
if (!anchor || !panel) return;
|
|
215
|
+
const below = window.innerHeight - anchor.bottom;
|
|
216
|
+
setSide(below < panel.height + MARGIN && anchor.top > below ? "top" : "bottom");
|
|
217
|
+
}, [placement]);
|
|
218
|
+
useLayoutEffect(() => {
|
|
219
|
+
if (!open) return;
|
|
220
|
+
place();
|
|
221
|
+
}, [open, place]);
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
if (!open) return;
|
|
224
|
+
areaRef.current?.focus();
|
|
225
|
+
const inside = (target) => Boolean(anchorRef.current?.contains(target));
|
|
226
|
+
const onPointerDown = (event) => {
|
|
227
|
+
if (!inside(event.target)) setOpenState(false);
|
|
228
|
+
};
|
|
229
|
+
const onFocusIn = (event) => {
|
|
230
|
+
if (event.target === document.body || inside(event.target)) return;
|
|
231
|
+
setOpenState(false);
|
|
232
|
+
};
|
|
233
|
+
const onResize = () => place();
|
|
234
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
235
|
+
document.addEventListener("focusin", onFocusIn, true);
|
|
236
|
+
window.addEventListener("resize", onResize);
|
|
237
|
+
return () => {
|
|
238
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
239
|
+
document.removeEventListener("focusin", onFocusIn, true);
|
|
240
|
+
window.removeEventListener("resize", onResize);
|
|
241
|
+
};
|
|
242
|
+
}, [open, place, setOpenState]);
|
|
243
|
+
const track = useCallback((event, onMove) => {
|
|
244
|
+
if (event.button !== 0) return;
|
|
245
|
+
const element = event.currentTarget;
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
element.focus();
|
|
248
|
+
const apply = (clientX, clientY) => {
|
|
249
|
+
const rect = element.getBoundingClientRect();
|
|
250
|
+
onMove(
|
|
251
|
+
rect.width ? clamp01((clientX - rect.left) / rect.width) : 0,
|
|
252
|
+
rect.height ? clamp01((clientY - rect.top) / rect.height) : 0
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
const move = (moved) => apply(moved.clientX, moved.clientY);
|
|
256
|
+
const stop = () => {
|
|
257
|
+
element.removeEventListener("pointermove", move);
|
|
258
|
+
element.removeEventListener("pointerup", stop);
|
|
259
|
+
element.removeEventListener("pointercancel", stop);
|
|
260
|
+
};
|
|
261
|
+
element.addEventListener("pointermove", move);
|
|
262
|
+
element.addEventListener("pointerup", stop);
|
|
263
|
+
element.addEventListener("pointercancel", stop);
|
|
264
|
+
try {
|
|
265
|
+
element.setPointerCapture(event.pointerId);
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
apply(event.clientX, event.clientY);
|
|
269
|
+
}, []);
|
|
270
|
+
const areaKeys = useCallback((event) => {
|
|
271
|
+
const step = event.shiftKey ? STEP.coarse : STEP.fine;
|
|
272
|
+
const moves = {
|
|
273
|
+
ArrowLeft: [-step, 0],
|
|
274
|
+
ArrowRight: [step, 0],
|
|
275
|
+
ArrowUp: [0, step],
|
|
276
|
+
ArrowDown: [0, -step]
|
|
277
|
+
};
|
|
278
|
+
const move = moves[event.key];
|
|
279
|
+
if (!move) return;
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
commit({ ...hsv, s: clamp01(hsv.s + move[0]), v: clamp01(hsv.v + move[1]) });
|
|
282
|
+
}, [commit, hsv]);
|
|
283
|
+
const railKeys = useCallback((event, kind) => {
|
|
284
|
+
const step = kind === "hue" ? event.shiftKey ? STEP.hueCoarse : STEP.hue : event.shiftKey ? STEP.coarse : STEP.fine;
|
|
285
|
+
const max = kind === "hue" ? 360 : 1;
|
|
286
|
+
const at = kind === "hue" ? hsv.h : hsv.a;
|
|
287
|
+
let next = at;
|
|
288
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowDown") next = at - step;
|
|
289
|
+
else if (event.key === "ArrowRight" || event.key === "ArrowUp") next = at + step;
|
|
290
|
+
else if (event.key === "Home") next = 0;
|
|
291
|
+
else if (event.key === "End") next = max;
|
|
292
|
+
else return;
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
const value2 = kind === "hue" ? (next % 360 + 360) % 360 : clamp01(next);
|
|
295
|
+
commit(kind === "hue" ? { ...hsv, h: value2 } : { ...hsv, a: value2 });
|
|
296
|
+
}, [commit, hsv]);
|
|
297
|
+
const dropper = eyedropper && typeof window !== "undefined" && "EyeDropper" in window;
|
|
298
|
+
const pick = useCallback(async () => {
|
|
299
|
+
const api = window.EyeDropper;
|
|
300
|
+
if (!api) return;
|
|
301
|
+
try {
|
|
302
|
+
const result = await new api().open();
|
|
303
|
+
const colour = parseColor(result.sRGBHex);
|
|
304
|
+
if (colour) commit(rgbToHsv({ ...colour, a: hsv.a }, hsv.h));
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
}, [commit, hsv]);
|
|
308
|
+
const css = useMemo(() => formatColor(rgb, "hex", { alpha: true }), [rgb]);
|
|
309
|
+
const opaque = useMemo(() => toHex({ ...rgb, a: 1 }), [rgb]);
|
|
310
|
+
const text = labels ?? {};
|
|
311
|
+
const valueText = `${text.color ?? "Colour"} ${css}`;
|
|
312
|
+
const panelStyle = { "--enigma-color-hue": Math.round(hsv.h), "--enigma-color-opaque": opaque };
|
|
313
|
+
return /* @__PURE__ */ jsxs("span", { ref: anchorRef, "data-enigma-color": "", "data-open": open ? "" : void 0, children: [
|
|
314
|
+
/* @__PURE__ */ jsx(
|
|
315
|
+
"button",
|
|
316
|
+
{
|
|
317
|
+
ref: swatchRef,
|
|
318
|
+
type: "button",
|
|
319
|
+
"data-enigma-color-swatch": "",
|
|
320
|
+
"data-enigma-color-checkers": "",
|
|
321
|
+
"data-invalid": parsed ? void 0 : "",
|
|
322
|
+
"aria-haspopup": "dialog",
|
|
323
|
+
"aria-expanded": open,
|
|
324
|
+
"aria-label": text.open ?? "Pick a colour",
|
|
325
|
+
title: text.open ?? "Pick a colour",
|
|
326
|
+
disabled: locked,
|
|
327
|
+
onClick: () => open ? close(true) : setOpenState(true),
|
|
328
|
+
children: /* @__PURE__ */ jsx("span", { "data-enigma-color-fill": "", style: { background: parsed ? css : void 0 } })
|
|
329
|
+
}
|
|
330
|
+
),
|
|
331
|
+
open && !locked && /* @__PURE__ */ jsxs(
|
|
332
|
+
"div",
|
|
333
|
+
{
|
|
334
|
+
ref: panelRef,
|
|
335
|
+
"data-enigma-color-panel": "",
|
|
336
|
+
"data-side": side,
|
|
337
|
+
role: "dialog",
|
|
338
|
+
"aria-label": text.panel ?? "Colour picker",
|
|
339
|
+
style: panelStyle,
|
|
340
|
+
onKeyDown: (event) => {
|
|
341
|
+
if (event.key !== "Escape") return;
|
|
342
|
+
event.stopPropagation();
|
|
343
|
+
close(true);
|
|
344
|
+
},
|
|
345
|
+
children: [
|
|
346
|
+
/* @__PURE__ */ jsx(
|
|
347
|
+
"div",
|
|
348
|
+
{
|
|
349
|
+
ref: areaRef,
|
|
350
|
+
"data-enigma-color-area": "",
|
|
351
|
+
role: "slider",
|
|
352
|
+
tabIndex: 0,
|
|
353
|
+
"aria-label": text.area ?? "Saturation and brightness",
|
|
354
|
+
"aria-valuemin": 0,
|
|
355
|
+
"aria-valuemax": 100,
|
|
356
|
+
"aria-valuenow": Math.round(hsv.s * 100),
|
|
357
|
+
"aria-valuetext": `${Math.round(hsv.s * 100)}% saturation, ${Math.round(hsv.v * 100)}% brightness, ${valueText}`,
|
|
358
|
+
onKeyDown: areaKeys,
|
|
359
|
+
onPointerDown: (event) => track(event, (x, y) => commit({ ...hsv, s: x, v: 1 - y })),
|
|
360
|
+
children: /* @__PURE__ */ jsx("span", { "data-enigma-color-thumb": "", style: { left: `${hsv.s * 100}%`, top: `${(1 - hsv.v) * 100}%` } })
|
|
361
|
+
}
|
|
362
|
+
),
|
|
363
|
+
/* @__PURE__ */ jsxs("div", { "data-enigma-color-controls": "", children: [
|
|
364
|
+
/* @__PURE__ */ jsx("span", { "data-enigma-color-preview": "", "data-enigma-color-checkers": "", children: /* @__PURE__ */ jsx("span", { "data-enigma-color-fill": "", style: { background: css } }) }),
|
|
365
|
+
dropper && /* @__PURE__ */ jsx(
|
|
366
|
+
"button",
|
|
367
|
+
{
|
|
368
|
+
type: "button",
|
|
369
|
+
"data-enigma-color-eyedropper": "",
|
|
370
|
+
"aria-label": text.eyedropper ?? "Pick a colour from the screen",
|
|
371
|
+
title: text.eyedropper ?? "Pick a colour from the screen",
|
|
372
|
+
onClick: () => {
|
|
373
|
+
void pick();
|
|
374
|
+
},
|
|
375
|
+
children: /* @__PURE__ */ jsx(EyedropperIcon, {})
|
|
376
|
+
}
|
|
377
|
+
),
|
|
378
|
+
/* @__PURE__ */ jsxs("div", { "data-enigma-color-rails": "", children: [
|
|
379
|
+
/* @__PURE__ */ jsx(
|
|
380
|
+
"div",
|
|
381
|
+
{
|
|
382
|
+
"data-enigma-color-rail": "hue",
|
|
383
|
+
role: "slider",
|
|
384
|
+
tabIndex: 0,
|
|
385
|
+
"aria-label": text.hue ?? "Hue",
|
|
386
|
+
"aria-valuemin": 0,
|
|
387
|
+
"aria-valuemax": 360,
|
|
388
|
+
"aria-valuenow": Math.round(hsv.h),
|
|
389
|
+
"aria-valuetext": `${Math.round(hsv.h)} degrees`,
|
|
390
|
+
onKeyDown: (event) => railKeys(event, "hue"),
|
|
391
|
+
onPointerDown: (event) => track(event, (x) => commit({ ...hsv, h: x * 360 })),
|
|
392
|
+
children: /* @__PURE__ */ jsx("span", { "data-enigma-color-thumb": "", style: { left: `${hsv.h / 360 * 100}%` } })
|
|
393
|
+
}
|
|
394
|
+
),
|
|
395
|
+
alpha && /* @__PURE__ */ jsxs(
|
|
396
|
+
"div",
|
|
397
|
+
{
|
|
398
|
+
"data-enigma-color-rail": "alpha",
|
|
399
|
+
"data-enigma-color-checkers": "",
|
|
400
|
+
role: "slider",
|
|
401
|
+
tabIndex: 0,
|
|
402
|
+
"aria-label": text.alpha ?? "Opacity",
|
|
403
|
+
"aria-valuemin": 0,
|
|
404
|
+
"aria-valuemax": 100,
|
|
405
|
+
"aria-valuenow": Math.round(hsv.a * 100),
|
|
406
|
+
"aria-valuetext": `${Math.round(hsv.a * 100)}% opacity`,
|
|
407
|
+
onKeyDown: (event) => railKeys(event, "alpha"),
|
|
408
|
+
onPointerDown: (event) => track(event, (x) => commit({ ...hsv, a: x })),
|
|
409
|
+
children: [
|
|
410
|
+
/* @__PURE__ */ jsx("span", { "data-enigma-color-gradient": "" }),
|
|
411
|
+
/* @__PURE__ */ jsx("span", { "data-enigma-color-thumb": "", style: { left: `${hsv.a * 100}%` } })
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
)
|
|
415
|
+
] })
|
|
416
|
+
] }),
|
|
417
|
+
swatches && swatches.length > 0 && /* @__PURE__ */ jsx("div", { "data-enigma-color-swatches": "", role: "group", "aria-label": text.swatches ?? "Preset colours", children: swatches.map((preset) => {
|
|
418
|
+
const colour = parseColor(preset);
|
|
419
|
+
return /* @__PURE__ */ jsx(
|
|
420
|
+
"button",
|
|
421
|
+
{
|
|
422
|
+
type: "button",
|
|
423
|
+
"data-enigma-color-preset": "",
|
|
424
|
+
"data-enigma-color-checkers": "",
|
|
425
|
+
style: { background: preset },
|
|
426
|
+
"aria-label": preset,
|
|
427
|
+
title: preset,
|
|
428
|
+
"aria-pressed": colourEqualsPreset(colour, css),
|
|
429
|
+
onClick: () => {
|
|
430
|
+
if (colour) commit(rgbToHsv(alpha ? colour : { ...colour, a: 1 }, hsv.h));
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
preset
|
|
434
|
+
);
|
|
435
|
+
}) })
|
|
436
|
+
]
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
] });
|
|
440
|
+
}
|
|
441
|
+
function colourEqualsPreset(preset, current) {
|
|
442
|
+
return colorEquals(preset, parseColor(current));
|
|
443
|
+
}
|
|
444
|
+
function EyedropperIcon() {
|
|
445
|
+
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
446
|
+
/* @__PURE__ */ jsx("path", { d: "m2 22 1-1h3l9-9" }),
|
|
447
|
+
/* @__PURE__ */ jsx("path", { d: "M3 21v-3l9-9" }),
|
|
448
|
+
/* @__PURE__ */ jsx("path", { d: "m15 6 3-3a2.83 2.83 0 0 1 4 4l-3 3-1-1-4 4-3-3 4-4Z" })
|
|
449
|
+
] });
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export { ColorExtras };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { C as ColorFormat, G as GeneratePasswordOptions, E as EstimateOptions, c as PasswordStrengthReport } from './color-D_rZ83Oc.js';
|
|
1
2
|
import { ReactNode, ComponentPropsWithoutRef } from 'react';
|
|
2
|
-
import { c as SearchOptions, F as FuseConstructor, b as SearchMatch } from './search-
|
|
3
|
-
import { G as GeneratePasswordOptions, E as EstimateOptions, b as PasswordStrengthReport } from './password-C8lG4Zm9.js';
|
|
3
|
+
import { c as SearchOptions, F as FuseConstructor, b as SearchMatch } from './search-DYgqRp37.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The prop shapes for `<Input>`, split from the component so the per-type chunks can import
|
|
@@ -131,14 +131,62 @@ interface SearchOnlyProps<Item = unknown> {
|
|
|
131
131
|
clearable?: boolean;
|
|
132
132
|
clearLabel?: string;
|
|
133
133
|
}
|
|
134
|
+
/** Which side of the field the picker's panel opens on. `auto` measures the room there is. */
|
|
135
|
+
type ColorPanelPlacement = "auto" | "top" | "bottom";
|
|
136
|
+
/** The picker's accessible names, for a UI that is not in English. */
|
|
137
|
+
interface ColorLabels {
|
|
138
|
+
/** The swatch that opens the panel. */
|
|
139
|
+
open?: string;
|
|
140
|
+
/** The panel itself. */
|
|
141
|
+
panel?: string;
|
|
142
|
+
/** The saturation and brightness square. */
|
|
143
|
+
area?: string;
|
|
144
|
+
hue?: string;
|
|
145
|
+
alpha?: string;
|
|
146
|
+
eyedropper?: string;
|
|
147
|
+
swatches?: string;
|
|
148
|
+
/** The word before the colour in what a screen reader announces. Default "Colour". */
|
|
149
|
+
color?: string;
|
|
150
|
+
}
|
|
151
|
+
/** The half only a colour field has. */
|
|
152
|
+
interface ColorOnlyProps {
|
|
153
|
+
type: "color";
|
|
154
|
+
/**
|
|
155
|
+
* How the picker writes the value back: `#3b82f6`, `rgb(59, 130, 246)` or an `hsl()`.
|
|
156
|
+
* Default hex. Whatever the format, every one of them is READ - a pasted `rgb()` in a hex
|
|
157
|
+
* field is understood and normalized on blur rather than rejected.
|
|
158
|
+
*/
|
|
159
|
+
format?: ColorFormat;
|
|
160
|
+
/**
|
|
161
|
+
* The opacity rail, and the alpha channel in the value. OFF by default: it turns
|
|
162
|
+
* `#3b82f6` into `#3b82f680`, which is not what a column typed as a 7-character hex, or a
|
|
163
|
+
* `<input type="color">` on the server side, is expecting.
|
|
164
|
+
*/
|
|
165
|
+
alpha?: boolean;
|
|
166
|
+
/** Preset colours under the rails - a brand palette, or the last few that were used. */
|
|
167
|
+
swatches?: readonly string[];
|
|
168
|
+
/**
|
|
169
|
+
* The screen eyedropper, where the browser has one (Chromium today). Default true, and
|
|
170
|
+
* feature-detected: the button is not rendered at all where the API is missing.
|
|
171
|
+
*/
|
|
172
|
+
eyedropper?: boolean;
|
|
173
|
+
/** Which side the panel opens on. Default `auto`, which flips near the bottom of the window. */
|
|
174
|
+
placement?: ColorPanelPlacement;
|
|
175
|
+
/**
|
|
176
|
+
* The panel's baseline stylesheet, injected once. Default true, and the one place this
|
|
177
|
+
* package ships a look - see `color-styles.ts` for why a picker cannot be unstyled.
|
|
178
|
+
*/
|
|
179
|
+
styles?: boolean;
|
|
180
|
+
colorLabels?: ColorLabels;
|
|
181
|
+
}
|
|
134
182
|
/** Everything else: one `<input>`, its native props, and no extras to bundle. */
|
|
135
183
|
interface PlainOnlyProps {
|
|
136
|
-
type?: Exclude<InputType, "password" | "search">;
|
|
184
|
+
type?: Exclude<InputType, "password" | "search" | "color">;
|
|
137
185
|
}
|
|
138
|
-
type InputProps<Item = unknown> = InputBaseProps & (PasswordOnlyProps | SearchOnlyProps<Item> | PlainOnlyProps);
|
|
186
|
+
type InputProps<Item = unknown> = InputBaseProps & (PasswordOnlyProps | SearchOnlyProps<Item> | ColorOnlyProps | PlainOnlyProps);
|
|
139
187
|
|
|
140
188
|
declare const Input: <Item = unknown>(props: InputProps<Item> & {
|
|
141
189
|
ref?: React.Ref<HTMLInputElement>;
|
|
142
190
|
}) => ReactNode;
|
|
143
191
|
|
|
144
|
-
export { type BreachChecker as B, type FieldAction as F, Input as I, type PasswordOnlyProps as P, type SearchOnlyProps as S, type BreachState as a, type BreachStatus as b, type
|
|
192
|
+
export { type BreachChecker as B, type ColorLabels as C, type FieldAction as F, Input as I, type PasswordOnlyProps as P, type SearchOnlyProps as S, type BreachState as a, type BreachStatus as b, type ColorOnlyProps as c, type ColorPanelPlacement as d, type InputBaseProps as e, type InputProps as f, type InputType as g, type PasswordStrengthClassNames as h, type PlainOnlyProps as i };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
export { M as MarqueeHover, a as MarqueeInstance, b as MarqueeOptions, c as createMarquee } from './marquee-CJ3Uwy3E.js';
|
|
2
2
|
export { I as InputAction, a as InputActionState, b as InputIcon, c as InputInstance, d as InputOptions, e as createInput } from './input-BwXjFenq.js';
|
|
3
|
-
export { F as FuseConstructor, a as FuseLike, S as SearchInstance, b as SearchMatch, c as SearchOptions, d as createSearch, s as subsequenceMatcher } from './search-
|
|
3
|
+
export { F as FuseConstructor, a as FuseLike, S as SearchInstance, b as SearchMatch, c as SearchOptions, d as createSearch, s as shortenQuery, e as subsequenceMatcher } from './search-DYgqRp37.js';
|
|
4
4
|
export { B as ButtonCooldown, a as ButtonElement, b as ButtonInstance, c as ButtonOptions, d as ButtonState, e as createButton } from './button-CaXaqG_K.js';
|
|
5
|
-
export { E as EstimateOptions, G as GeneratePasswordOptions, P as PasswordAlphabet,
|
|
5
|
+
export { C as ColorFormat, E as EstimateOptions, F as FormatColorOptions, G as GeneratePasswordOptions, H as Hsl, a as Hsv, P as PasswordAlphabet, b as PasswordScore, c as PasswordStrengthReport, R as Rgb, d as colorEquals, e as estimatePasswordStrength, f as formatColor, g as generatePassword, h as hslToRgb, i as hsvToRgb, p as parseColor, r as rgbToHsl, j as rgbToHsv, t as toHex } from './color-D_rZ83Oc.js';
|
|
6
6
|
export { N as Notification, a as NotificationAction, b as NotificationInput, c as NotificationTone, d as Notifications, e as NotificationsOptions, P as PromiseMessages, f as createNotifications } from './notifications-BpVV6sel.js';
|
|
7
7
|
export { N as NetworkMonitor, a as NetworkState, S as SERVER_NETWORK_STATE, c as createNetworkMonitor } from './network-D2LsBG_k.js';
|
|
8
8
|
export { R as RelativeTimeFormat, a as RelativeTimeOptions, b as RelativeTimePrecision, c as RelativeTimeStyle, d as RelativeTimeTense, e as RelativeTimeView, f as ensureZone, n as normalizeDate, p as parseDuration, r as relativeTimeAttributes, g as relativeTimeView } from './relative-time-YpRTG7YH.js';
|
|
9
9
|
export { F as FLAG_CDN, a as FLAG_RATIO, b as FlagConfig, c as FlagFormat, d as FlagOptions, e as FlagShape, f as FlagSource, g as FlagView, h as configureFlags, i as flagAttributes, j as flagConfig, k as flagName, l as flagSrc, m as flagView, n as normalizeFlagCode, r as resetFlagConfig } from './flags-BQU1V9rE.js';
|
|
10
10
|
export { P as PaletteKey, a as PositionedRow, R as RecentEntry, b as RecentStore, c as RecentStoreOptions, d as RowGroup, e as createRecentStore, g as groupRows, i as isPaletteShortcut, m as moveActive, r as recentKey, s as shortcutLabel } from './palette-D7iuQh_T.js';
|
|
11
|
+
export { S as SELECT_SEARCH_KEYS, a as SelectInstance, b as SelectMoveKey, c as SelectOption, d as SelectOptions, e as SelectState, f as createSelect } from './select-ClSy-J1f.js';
|
|
12
|
+
export { C as CLIPBOARD_PREFIX, a as CONTEXT_MENU_SEARCH_KEYS, b as ClipboardAction, c as ClipboardMenuLabels, d as ClipboardMenuOptions, e as ClipboardTarget, f as ContextMenuAction, g as ContextMenuEntry, h as ContextMenuInstance, i as ContextMenuLabel, j as ContextMenuLevel, k as ContextMenuMoveKey, l as ContextMenuOptions, m as ContextMenuPoint, n as ContextMenuSeparator, o as ContextMenuState, p as clipboardAction, q as clipboardEntries, r as clipboardHasText, s as createContextMenu, t as inspectClipboardTarget, u as isAction, v as performClipboardAction } from './clipboard-menu-B_ouitfS.js';
|
|
13
|
+
export { D as DEFAULT_SELECTION_SHORTCUTS, S as SelectionClickModifiers, a as SelectionCommand, b as SelectionCommandEvent, c as SelectionCommandName, d as SelectionInstance, e as SelectionOptions, f as SelectionShortcuts, g as SelectionState, h as createSelection } from './selection-B_pmzHpy.js';
|
|
14
|
+
export { S as Shortcut, a as ShortcutEvent, b as ShortcutSpec, T as TYPEAHEAD_MS, c as TypeaheadState, d as TypeaheadStep, i as isApplePlatform, m as matchesShortcut, p as parseShortcut, s as shortcutList, e as shortcutText, f as shortcutTokens, t as typeaheadStep } from './keys-D2zJs1uB.js';
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* The icon shapes both renderers draw.
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
export { createInput } from './chunk-KJINGUQN.js';
|
|
2
|
+
export { ensureZone, normalizeDate, parseDuration, relativeTimeAttributes, relativeTimeView } from './chunk-IXVMRVD4.js';
|
|
2
3
|
export { estimatePasswordStrength, generatePassword } from './chunk-HC2ME5PU.js';
|
|
4
|
+
export { colorEquals, formatColor, hslToRgb, hsvToRgb, parseColor, rgbToHsl, rgbToHsv, toHex } from './chunk-S2FPN5ID.js';
|
|
5
|
+
export { CLIPBOARD_PREFIX, CONTEXT_MENU_SEARCH_KEYS, clipboardAction, clipboardEntries, clipboardHasText, createContextMenu, inspectClipboardTarget, isAction, performClipboardAction } from './chunk-Z3VDE7OA.js';
|
|
6
|
+
export { DEFAULT_SELECTION_SHORTCUTS, createSelection } from './chunk-WSQC3PCC.js';
|
|
7
|
+
export { createButton } from './chunk-MMQPZGSU.js';
|
|
3
8
|
export { FLAG_CDN, FLAG_RATIO, configureFlags, flagAttributes, flagConfig, flagName, flagSrc, flagView, normalizeFlagCode, resetFlagConfig } from './chunk-LMZJBOZG.js';
|
|
9
|
+
export { createNotifications } from './chunk-SNYUBXWQ.js';
|
|
4
10
|
export { createMarquee } from './chunk-UOSSNUSC.js';
|
|
5
11
|
export { SERVER_NETWORK_STATE, createNetworkMonitor } from './chunk-2QFTRNAZ.js';
|
|
6
|
-
export { createNotifications } from './chunk-SNYUBXWQ.js';
|
|
7
|
-
export { ensureZone, normalizeDate, parseDuration, relativeTimeAttributes, relativeTimeView } from './chunk-IXVMRVD4.js';
|
|
8
12
|
export { INPUT_ICON_PATHS, iconMarkup } from './chunk-F25CGNQC.js';
|
|
13
|
+
export { SELECT_SEARCH_KEYS, createSelect } from './chunk-NONGREXC.js';
|
|
14
|
+
export { TYPEAHEAD_MS, isApplePlatform, matchesShortcut, parseShortcut, shortcutList, shortcutText, shortcutTokens, typeaheadStep } from './chunk-4VUHQFAT.js';
|
|
9
15
|
export { createRecentStore, groupRows, isPaletteShortcut, moveActive, recentKey, shortcutLabel } from './chunk-DTWZDONY.js';
|
|
10
|
-
export { createSearch, subsequenceMatcher } from './chunk-
|
|
11
|
-
export { createButton } from './chunk-MMQPZGSU.js';
|
|
16
|
+
export { createSearch, shortenQuery, subsequenceMatcher } from './chunk-4ZPMP47J.js';
|