@enigmax/primitives 0.24.0 → 0.26.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-4SZ5N7BA.js +121 -0
- package/dist/chunk-EYZ366LP.js +189 -0
- package/dist/chunk-ISXB5RUH.js +380 -0
- package/dist/chunk-JJVZYW5C.js +946 -0
- package/dist/chunk-KVLSOQTI.js +66 -0
- package/dist/chunk-QIIV2QSV.js +108 -0
- package/dist/{chunk-QJJ34E4G.js → chunk-RMNXVWQ4.js} +32 -15
- package/dist/chunk-RUEQ3TGP.js +155 -0
- package/dist/clipboard-menu-BvXJCSFN.d.ts +93 -0
- package/dist/{color-OPV3BJV6.js → color-T63FLJNH.js} +183 -120
- package/dist/context-BfjYGPnH.d.ts +54 -0
- package/dist/{clipboard-menu-B_ouitfS.d.ts → context-menu-D3FtTn7v.d.ts} +1 -91
- package/dist/{index-ZPvlf9vs.d.ts → index-qXOkYQCU.d.ts} +4 -0
- package/dist/index.d.ts +266 -2
- package/dist/index.js +4 -2
- package/dist/menu-MSQSE6U5.js +54 -0
- package/dist/menu-PNQRTRNV.js +33 -0
- package/dist/next/index.d.ts +7 -3
- package/dist/next/index.js +11 -7
- package/dist/react/context-menu.d.ts +8 -54
- package/dist/react/image.d.ts +109 -0
- package/dist/react/image.js +3 -0
- package/dist/react/index.d.ts +8 -4
- package/dist/react/index.js +11 -7
- package/dist/react/input.d.ts +1 -1
- package/dist/react/input.js +2 -2
- package/dist/react/video.d.ts +120 -0
- package/dist/react/video.js +3 -0
- package/dist/react-router/index.d.ts +7 -3
- package/dist/react-router/index.js +11 -7
- package/dist/viewer-73LSE6RR.js +3 -0
- package/package.json +15 -2
- package/recipes/color/styles.css +20 -0
- package/recipes/image/styles.css +182 -0
- package/recipes/video/styles.css +154 -0
- package/registry.json +477 -3
- package/src/core/image-viewer.ts +222 -0
- package/src/core/player.ts +303 -0
- package/src/index.ts +52 -0
- package/src/react/image/icons.tsx +55 -0
- package/src/react/image/index.tsx +185 -0
- package/src/react/image/menu.tsx +76 -0
- package/src/react/image/styles.ts +213 -0
- package/src/react/image/types.ts +113 -0
- package/src/react/image/viewer.tsx +571 -0
- package/src/react/index.ts +3 -0
- package/src/react/input/color-styles.ts +20 -0
- package/src/react/input/color-swatch.tsx +96 -0
- package/src/react/input/color.tsx +84 -23
- package/src/react/input/index.tsx +27 -4
- package/src/react/input/types.ts +4 -0
- package/src/react/video/icons.tsx +135 -0
- package/src/react/video/index.tsx +724 -0
- package/src/react/video/menu.tsx +66 -0
- package/src/react/video/rail.tsx +99 -0
- package/src/react/video/styles.ts +161 -0
- package/src/react/video/types.ts +135 -0
- package/dist/chunk-S7EE57YB.js +0 -9
- /package/dist/{chunk-MSOCCQGH.js → chunk-T7I6WJSN.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseColor, rgbToHsv, colorEquals, hsvToRgb, formatColor, toHex } from './chunk-S2FPN5ID.js';
|
|
2
|
-
import { writeValue } from './chunk-
|
|
2
|
+
import { writeValue, ColorSwatch } from './chunk-KVLSOQTI.js';
|
|
3
3
|
import { useLayoutEffect, useRef, useState, useMemo, useEffect, useCallback } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
// src/react/input/color-styles.ts
|
|
7
7
|
var COLOR_STYLES = `
|
|
@@ -133,6 +133,26 @@ var COLOR_STYLES = `
|
|
|
133
133
|
[data-enigma-color-eyedropper]:hover { color: var(--enigma-color-text); }
|
|
134
134
|
[data-enigma-color-eyedropper]:focus-visible { outline: 2px solid var(--enigma-color-focus); outline-offset: 2px; }
|
|
135
135
|
|
|
136
|
+
/* The value as text, with the notation beside it. Monospace on purpose: a hex whose digits
|
|
137
|
+
shift width as you drag is a value nobody can read at a glance. */
|
|
138
|
+
[data-enigma-color-value] { display: flex; align-items: center; gap: 0.375rem; }
|
|
139
|
+
[data-enigma-color-format] {
|
|
140
|
+
flex: none; padding: 0.3125rem 0.375rem;
|
|
141
|
+
font: inherit; font-size: 0.625rem; line-height: 1; letter-spacing: 0.06em;
|
|
142
|
+
color: var(--enigma-color-muted); background: none;
|
|
143
|
+
border: 1px solid var(--enigma-color-border); border-radius: 0.25rem; cursor: pointer;
|
|
144
|
+
}
|
|
145
|
+
[data-enigma-color-format]:hover { color: var(--enigma-color-text); }
|
|
146
|
+
[data-enigma-color-input] {
|
|
147
|
+
flex: 1; min-width: 0; padding: 0.25rem 0.375rem;
|
|
148
|
+
font: inherit; font-family: var(--enigma-color-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
149
|
+
font-size: 0.75rem; line-height: 1.4;
|
|
150
|
+
color: var(--enigma-color-text); background: none;
|
|
151
|
+
border: 1px solid var(--enigma-color-border); border-radius: 0.25rem;
|
|
152
|
+
}
|
|
153
|
+
[data-enigma-color-format]:focus-visible,
|
|
154
|
+
[data-enigma-color-input]:focus-visible { outline: 2px solid var(--enigma-color-focus); outline-offset: 1px; }
|
|
155
|
+
|
|
136
156
|
[data-enigma-color-swatches] { display: flex; flex-wrap: wrap; gap: 0.25rem; }
|
|
137
157
|
[data-enigma-color-preset] {
|
|
138
158
|
width: 1.125rem; height: 1.125rem; padding: 0;
|
|
@@ -144,6 +164,8 @@ var COLOR_STYLES = `
|
|
|
144
164
|
var INITIAL = { h: 0, s: 0, v: 0, a: 1 };
|
|
145
165
|
var STEP = { fine: 0.01, coarse: 0.1, hue: 1, hueCoarse: 10 };
|
|
146
166
|
var MARGIN = 12;
|
|
167
|
+
var FORMATS = ["hex", "rgb", "hsl"];
|
|
168
|
+
var FORMAT_LABELS = { hex: "HEX", rgb: "RGB", hsl: "HSL" };
|
|
147
169
|
var injected = false;
|
|
148
170
|
function injectStyles() {
|
|
149
171
|
if (injected || typeof document === "undefined") return;
|
|
@@ -167,6 +189,7 @@ function ColorExtras({
|
|
|
167
189
|
placement = "auto",
|
|
168
190
|
styles = true,
|
|
169
191
|
locked = false,
|
|
192
|
+
openOnMount = false,
|
|
170
193
|
labels
|
|
171
194
|
}) {
|
|
172
195
|
useLayoutEffect(() => {
|
|
@@ -176,7 +199,7 @@ function ColorExtras({
|
|
|
176
199
|
const panelRef = useRef(null);
|
|
177
200
|
const swatchRef = useRef(null);
|
|
178
201
|
const areaRef = useRef(null);
|
|
179
|
-
const [open, setOpen] = useState(
|
|
202
|
+
const [open, setOpen] = useState(openOnMount && !locked);
|
|
180
203
|
const [side, setSide] = useState(placement === "top" ? "top" : "bottom");
|
|
181
204
|
const parsed = useMemo(() => parseColor(value), [value]);
|
|
182
205
|
const [hsv, setHsv] = useState(() => parsed ? rgbToHsv(parsed) : INITIAL);
|
|
@@ -305,138 +328,178 @@ function ColorExtras({
|
|
|
305
328
|
} catch {
|
|
306
329
|
}
|
|
307
330
|
}, [commit, hsv]);
|
|
331
|
+
const [shown, setShown] = useState(format);
|
|
332
|
+
useEffect(() => setShown(format), [format]);
|
|
333
|
+
const [draft, setDraft] = useState(null);
|
|
334
|
+
const readout = useMemo(() => formatColor(rgb, shown, { alpha }), [rgb, shown, alpha]);
|
|
335
|
+
const nextFormat = FORMATS[(FORMATS.indexOf(shown) + 1) % FORMATS.length];
|
|
336
|
+
const typed = useCallback((next) => {
|
|
337
|
+
setDraft(next);
|
|
338
|
+
const colour = parseColor(next);
|
|
339
|
+
if (colour) commit(rgbToHsv(alpha ? colour : { ...colour, a: 1 }, hsv.h));
|
|
340
|
+
}, [commit, alpha, hsv.h]);
|
|
308
341
|
const css = useMemo(() => formatColor(rgb, "hex", { alpha: true }), [rgb]);
|
|
309
342
|
const opaque = useMemo(() => toHex({ ...rgb, a: 1 }), [rgb]);
|
|
310
343
|
const text = labels ?? {};
|
|
311
344
|
const valueText = `${text.color ?? "Colour"} ${css}`;
|
|
312
345
|
const panelStyle = { "--enigma-color-hue": Math.round(hsv.h), "--enigma-color-opaque": opaque };
|
|
313
|
-
return /* @__PURE__ */
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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",
|
|
346
|
+
return /* @__PURE__ */ jsx(
|
|
347
|
+
ColorSwatch,
|
|
348
|
+
{
|
|
349
|
+
anchorRef,
|
|
350
|
+
buttonRef: swatchRef,
|
|
351
|
+
value: css,
|
|
352
|
+
invalid: !parsed,
|
|
353
|
+
open,
|
|
354
|
+
locked,
|
|
355
|
+
label: text.open ?? "Pick a colour",
|
|
356
|
+
onPress: () => open ? close(true) : setOpenState(true),
|
|
357
|
+
children: open && !locked && /* @__PURE__ */ jsxs(
|
|
358
|
+
"div",
|
|
359
|
+
{
|
|
360
|
+
ref: panelRef,
|
|
361
|
+
"data-enigma-color-panel": "",
|
|
362
|
+
"data-side": side,
|
|
363
|
+
role: "dialog",
|
|
364
|
+
"aria-label": text.panel ?? "Colour picker",
|
|
365
|
+
style: panelStyle,
|
|
366
|
+
onKeyDown: (event) => {
|
|
367
|
+
if (event.key !== "Escape") return;
|
|
368
|
+
event.stopPropagation();
|
|
369
|
+
close(true);
|
|
370
|
+
},
|
|
371
|
+
children: [
|
|
372
|
+
/* @__PURE__ */ jsx(
|
|
373
|
+
"div",
|
|
367
374
|
{
|
|
368
|
-
|
|
369
|
-
"data-enigma-color-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
375
|
+
ref: areaRef,
|
|
376
|
+
"data-enigma-color-area": "",
|
|
377
|
+
role: "slider",
|
|
378
|
+
tabIndex: 0,
|
|
379
|
+
"aria-label": text.area ?? "Saturation and brightness",
|
|
380
|
+
"aria-valuemin": 0,
|
|
381
|
+
"aria-valuemax": 100,
|
|
382
|
+
"aria-valuenow": Math.round(hsv.s * 100),
|
|
383
|
+
"aria-valuetext": `${Math.round(hsv.s * 100)}% saturation, ${Math.round(hsv.v * 100)}% brightness, ${valueText}`,
|
|
384
|
+
onKeyDown: areaKeys,
|
|
385
|
+
onPointerDown: (event) => track(event, (x, y) => commit({ ...hsv, s: x, v: 1 - y })),
|
|
386
|
+
children: /* @__PURE__ */ jsx("span", { "data-enigma-color-thumb": "", style: { left: `${hsv.s * 100}%`, top: `${(1 - hsv.v) * 100}%` } })
|
|
376
387
|
}
|
|
377
388
|
),
|
|
378
|
-
/* @__PURE__ */ jsxs("div", { "data-enigma-color-
|
|
389
|
+
/* @__PURE__ */ jsxs("div", { "data-enigma-color-controls": "", children: [
|
|
390
|
+
/* @__PURE__ */ jsx("span", { "data-enigma-color-preview": "", "data-enigma-color-checkers": "", children: /* @__PURE__ */ jsx("span", { "data-enigma-color-fill": "", style: { background: css } }) }),
|
|
391
|
+
dropper && /* @__PURE__ */ jsx(
|
|
392
|
+
"button",
|
|
393
|
+
{
|
|
394
|
+
type: "button",
|
|
395
|
+
"data-enigma-color-eyedropper": "",
|
|
396
|
+
"aria-label": text.eyedropper ?? "Pick a colour from the screen",
|
|
397
|
+
title: text.eyedropper ?? "Pick a colour from the screen",
|
|
398
|
+
onClick: () => {
|
|
399
|
+
void pick();
|
|
400
|
+
},
|
|
401
|
+
children: /* @__PURE__ */ jsx(EyedropperIcon, {})
|
|
402
|
+
}
|
|
403
|
+
),
|
|
404
|
+
/* @__PURE__ */ jsxs("div", { "data-enigma-color-rails": "", children: [
|
|
405
|
+
/* @__PURE__ */ jsx(
|
|
406
|
+
"div",
|
|
407
|
+
{
|
|
408
|
+
"data-enigma-color-rail": "hue",
|
|
409
|
+
role: "slider",
|
|
410
|
+
tabIndex: 0,
|
|
411
|
+
"aria-label": text.hue ?? "Hue",
|
|
412
|
+
"aria-valuemin": 0,
|
|
413
|
+
"aria-valuemax": 360,
|
|
414
|
+
"aria-valuenow": Math.round(hsv.h),
|
|
415
|
+
"aria-valuetext": `${Math.round(hsv.h)} degrees`,
|
|
416
|
+
onKeyDown: (event) => railKeys(event, "hue"),
|
|
417
|
+
onPointerDown: (event) => track(event, (x) => commit({ ...hsv, h: x * 360 })),
|
|
418
|
+
children: /* @__PURE__ */ jsx("span", { "data-enigma-color-thumb": "", style: { left: `${hsv.h / 360 * 100}%` } })
|
|
419
|
+
}
|
|
420
|
+
),
|
|
421
|
+
alpha && /* @__PURE__ */ jsxs(
|
|
422
|
+
"div",
|
|
423
|
+
{
|
|
424
|
+
"data-enigma-color-rail": "alpha",
|
|
425
|
+
"data-enigma-color-checkers": "",
|
|
426
|
+
role: "slider",
|
|
427
|
+
tabIndex: 0,
|
|
428
|
+
"aria-label": text.alpha ?? "Opacity",
|
|
429
|
+
"aria-valuemin": 0,
|
|
430
|
+
"aria-valuemax": 100,
|
|
431
|
+
"aria-valuenow": Math.round(hsv.a * 100),
|
|
432
|
+
"aria-valuetext": `${Math.round(hsv.a * 100)}% opacity`,
|
|
433
|
+
onKeyDown: (event) => railKeys(event, "alpha"),
|
|
434
|
+
onPointerDown: (event) => track(event, (x) => commit({ ...hsv, a: x })),
|
|
435
|
+
children: [
|
|
436
|
+
/* @__PURE__ */ jsx("span", { "data-enigma-color-gradient": "" }),
|
|
437
|
+
/* @__PURE__ */ jsx("span", { "data-enigma-color-thumb": "", style: { left: `${hsv.a * 100}%` } })
|
|
438
|
+
]
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
] })
|
|
442
|
+
] }),
|
|
443
|
+
/* @__PURE__ */ jsxs("div", { "data-enigma-color-value": "", children: [
|
|
379
444
|
/* @__PURE__ */ jsx(
|
|
380
|
-
"
|
|
445
|
+
"button",
|
|
381
446
|
{
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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}%` } })
|
|
447
|
+
type: "button",
|
|
448
|
+
"data-enigma-color-format": "",
|
|
449
|
+
"aria-label": `${text.formatAs ?? "Show as"} ${FORMAT_LABELS[nextFormat]}`,
|
|
450
|
+
title: `${text.formatAs ?? "Show as"} ${FORMAT_LABELS[nextFormat]}`,
|
|
451
|
+
onClick: () => {
|
|
452
|
+
setShown(nextFormat);
|
|
453
|
+
setDraft(null);
|
|
454
|
+
},
|
|
455
|
+
children: FORMAT_LABELS[shown]
|
|
393
456
|
}
|
|
394
457
|
),
|
|
395
|
-
|
|
396
|
-
"
|
|
458
|
+
/* @__PURE__ */ jsx(
|
|
459
|
+
"input",
|
|
397
460
|
{
|
|
398
|
-
|
|
399
|
-
"data-enigma-color-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
"
|
|
404
|
-
"
|
|
405
|
-
"
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
461
|
+
type: "text",
|
|
462
|
+
"data-enigma-color-input": "",
|
|
463
|
+
value: draft ?? readout,
|
|
464
|
+
"aria-label": text.value ?? "Colour value",
|
|
465
|
+
spellCheck: false,
|
|
466
|
+
autoComplete: "off",
|
|
467
|
+
autoCorrect: "off",
|
|
468
|
+
autoCapitalize: "off",
|
|
469
|
+
onChange: (event) => typed(event.target.value),
|
|
470
|
+
onBlur: () => setDraft(null),
|
|
471
|
+
onKeyDown: (event) => {
|
|
472
|
+
if (event.key !== "Enter") return;
|
|
473
|
+
event.preventDefault();
|
|
474
|
+
setDraft(null);
|
|
475
|
+
}
|
|
413
476
|
}
|
|
414
477
|
)
|
|
415
|
-
] })
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
478
|
+
] }),
|
|
479
|
+
swatches && swatches.length > 0 && /* @__PURE__ */ jsx("div", { "data-enigma-color-swatches": "", role: "group", "aria-label": text.swatches ?? "Preset colours", children: swatches.map((preset) => {
|
|
480
|
+
const colour = parseColor(preset);
|
|
481
|
+
return /* @__PURE__ */ jsx(
|
|
482
|
+
"button",
|
|
483
|
+
{
|
|
484
|
+
type: "button",
|
|
485
|
+
"data-enigma-color-preset": "",
|
|
486
|
+
"data-enigma-color-checkers": "",
|
|
487
|
+
style: { background: preset },
|
|
488
|
+
"aria-label": preset,
|
|
489
|
+
title: preset,
|
|
490
|
+
"aria-pressed": colourEqualsPreset(colour, css),
|
|
491
|
+
onClick: () => {
|
|
492
|
+
if (colour) commit(rgbToHsv(alpha ? colour : { ...colour, a: 1 }, hsv.h));
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
preset
|
|
496
|
+
);
|
|
497
|
+
}) })
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
);
|
|
440
503
|
}
|
|
441
504
|
function colourEqualsPreset(preset, current) {
|
|
442
505
|
return colorEquals(preset, parseColor(current));
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ReactNode, RefObject } from 'react';
|
|
2
|
+
import { a as ContextMenuAction, b as ContextMenuEntry, c as ContextMenuInstance, j as ContextMenuState, h as ContextMenuPoint } from './context-menu-D3FtTn7v.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One row, as React draws it. The core carries `icon` as `unknown` because it cannot know
|
|
6
|
+
* what a node is; here it is one.
|
|
7
|
+
*/
|
|
8
|
+
interface ContextMenuItem extends Omit<ContextMenuAction, "icon" | "items"> {
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
items?: readonly ContextMenuNode[];
|
|
11
|
+
}
|
|
12
|
+
/** Anything that can appear in a menu, with React's icon type. */
|
|
13
|
+
type ContextMenuNode = ContextMenuItem | Exclude<ContextMenuEntry, ContextMenuAction>;
|
|
14
|
+
interface ContextMenuContextValue {
|
|
15
|
+
instance: ContextMenuInstance;
|
|
16
|
+
state: ContextMenuState;
|
|
17
|
+
/**
|
|
18
|
+
* Open at a point, over what the press landed on. False when there was nothing to show,
|
|
19
|
+
* so the press is left alone.
|
|
20
|
+
*
|
|
21
|
+
* `target` is what the clipboard rows are built from - the field or the selection under
|
|
22
|
+
* the pointer - and a caller that leaves it out gets a menu with none of them, which is
|
|
23
|
+
* the right answer for a menu opened from somewhere that is not a press.
|
|
24
|
+
*/
|
|
25
|
+
open: (point: ContextMenuPoint, target?: EventTarget | null) => boolean;
|
|
26
|
+
close: () => void;
|
|
27
|
+
/** ids the parts need to point at each other. */
|
|
28
|
+
ids: {
|
|
29
|
+
trigger: string;
|
|
30
|
+
};
|
|
31
|
+
panelId: (level: number) => string;
|
|
32
|
+
itemId: (level: number, index: number) => string;
|
|
33
|
+
triggerRef: RefObject<HTMLElement | null>;
|
|
34
|
+
/** Which panel the keyboard is in. Always the deepest open one. */
|
|
35
|
+
onMenuKeyDown: (event: React.KeyboardEvent) => void;
|
|
36
|
+
/** How long the pointer must rest on a row before its submenu opens, and before one closes. */
|
|
37
|
+
delays: {
|
|
38
|
+
open: number;
|
|
39
|
+
close: number;
|
|
40
|
+
};
|
|
41
|
+
/** Drop the pending close, wherever it was scheduled. Called wherever the pointer lands. */
|
|
42
|
+
cancelClose: () => void;
|
|
43
|
+
/** Close everything below `level`, after the beat - replacing any close already waiting. */
|
|
44
|
+
scheduleClose: (level: number) => void;
|
|
45
|
+
/** Draw a row instead of the default icon / label / shortcut. */
|
|
46
|
+
renderItem?: (item: ContextMenuItem, level: number, index: number) => ReactNode;
|
|
47
|
+
/** Shown while a fetched submenu has not answered. */
|
|
48
|
+
loadingLabel: ReactNode;
|
|
49
|
+
/** Shown when a filter matched nothing, or a fetched submenu came back with nothing. */
|
|
50
|
+
emptyLabel: ReactNode;
|
|
51
|
+
}
|
|
52
|
+
declare function useContextMenuContext(part: string): ContextMenuContextValue;
|
|
53
|
+
|
|
54
|
+
export { type ContextMenuContextValue as C, type ContextMenuItem as a, type ContextMenuNode as b, useContextMenuContext as u };
|
|
@@ -171,94 +171,4 @@ interface ContextMenuInstance {
|
|
|
171
171
|
type ContextMenuMoveKey = "ArrowDown" | "ArrowUp" | "Home" | "End" | "PageDown" | "PageUp";
|
|
172
172
|
declare function createContextMenu(options?: ContextMenuOptions): ContextMenuInstance;
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
* The rows every context menu on a desktop already has: Copy, Cut and Paste.
|
|
176
|
-
*
|
|
177
|
-
* A menu opened over a selection or over a field is expected to offer them - the browser's own
|
|
178
|
-
* does, and replacing that menu with a custom one takes them away without saying so. Which is
|
|
179
|
-
* why they are ON by default here and turned off with a prop, rather than the other way round:
|
|
180
|
-
* the defect is silent, so the default has to be the safe one.
|
|
181
|
-
*
|
|
182
|
-
* WHAT DECIDES THEY APPEAR is what was right-clicked, and it is read at the moment the menu
|
|
183
|
-
* opens, not from React state - by then the selection is settled (a `contextmenu` event fires
|
|
184
|
-
* after the press has adjusted it) and the caret is still where the visitor left it.
|
|
185
|
-
*
|
|
186
|
-
* - **Copy**, when there is selected text under the pointer.
|
|
187
|
-
* - **Cut**, when that selection is also in something writable.
|
|
188
|
-
* - **Paste**, in anything writable - and disabled when the clipboard is known to be empty.
|
|
189
|
-
*
|
|
190
|
-
* Not a React module: what a selection is, whether a field takes writes, and how text is put
|
|
191
|
-
* back into one are DOM questions, so a menu drawn by anything else gets the same rows.
|
|
192
|
-
*/
|
|
193
|
-
|
|
194
|
-
/** The ids these rows carry. Namespaced, so they can never collide with a caller's own. */
|
|
195
|
-
declare const CLIPBOARD_PREFIX = "enigma:clipboard:";
|
|
196
|
-
type ClipboardAction = "copy" | "cut" | "paste";
|
|
197
|
-
/** What the menu was opened over, as far as the clipboard is concerned. */
|
|
198
|
-
interface ClipboardTarget {
|
|
199
|
-
/** The field or contenteditable under the pointer, or null when it is neither. */
|
|
200
|
-
editable: HTMLElement | null;
|
|
201
|
-
/** Whether that element takes writes: not disabled, not read-only. */
|
|
202
|
-
writable: boolean;
|
|
203
|
-
/** The selected text, from the field's own selection or the document's. */
|
|
204
|
-
selection: string;
|
|
205
|
-
/**
|
|
206
|
-
* Whether that text may be put on the clipboard.
|
|
207
|
-
*
|
|
208
|
-
* False for a password field. The clipboard is shared with every other application on the
|
|
209
|
-
* machine and is not cleared, so a menu that copies a password out of a masked field
|
|
210
|
-
* leaks it somewhere the visitor cannot see - and the browser's own menu refuses too.
|
|
211
|
-
*/
|
|
212
|
-
copyable: boolean;
|
|
213
|
-
/** Where the selection was, so it can be put back after the menu has taken focus. */
|
|
214
|
-
range: {
|
|
215
|
-
start: number;
|
|
216
|
-
end: number;
|
|
217
|
-
} | null;
|
|
218
|
-
}
|
|
219
|
-
interface ClipboardMenuLabels {
|
|
220
|
-
copy?: string;
|
|
221
|
-
cut?: string;
|
|
222
|
-
paste?: string;
|
|
223
|
-
}
|
|
224
|
-
interface ClipboardMenuOptions {
|
|
225
|
-
copy?: boolean;
|
|
226
|
-
cut?: boolean;
|
|
227
|
-
paste?: boolean;
|
|
228
|
-
labels?: ClipboardMenuLabels;
|
|
229
|
-
/** Whatever the renderer draws icons with. `unknown`, because a core cannot know. */
|
|
230
|
-
icons?: {
|
|
231
|
-
copy?: unknown;
|
|
232
|
-
cut?: unknown;
|
|
233
|
-
paste?: unknown;
|
|
234
|
-
};
|
|
235
|
-
/** The clipboard is known to be empty, so Paste is listed and greyed rather than missing. */
|
|
236
|
-
clipboardEmpty?: boolean;
|
|
237
|
-
}
|
|
238
|
-
/** What was right-clicked, read the moment the menu opens. */
|
|
239
|
-
declare function inspectClipboardTarget(node: EventTarget | null): ClipboardTarget;
|
|
240
|
-
/**
|
|
241
|
-
* Whether the clipboard has text in it, or null when that cannot be known.
|
|
242
|
-
*
|
|
243
|
-
* Null is the common answer and not a failure: reading the clipboard needs permission, and
|
|
244
|
-
* asking for it puts a browser prompt on screen just to decide whether to grey out a row -
|
|
245
|
-
* which is a worse trade than showing an enabled Paste that turns out to do nothing. So the
|
|
246
|
-
* permission is only READ, never requested, and the clipboard is only opened where it has
|
|
247
|
-
* already been granted.
|
|
248
|
-
*/
|
|
249
|
-
declare function clipboardHasText(): Promise<boolean | null>;
|
|
250
|
-
/** The rows for this target, in the order every desktop menu puts them. */
|
|
251
|
-
declare function clipboardEntries(target: ClipboardTarget, options?: ClipboardMenuOptions): ContextMenuEntry[];
|
|
252
|
-
/** Which clipboard row an id belongs to, or null for anything that is not one of ours. */
|
|
253
|
-
declare function clipboardAction(id: string): ClipboardAction | null;
|
|
254
|
-
/**
|
|
255
|
-
* Do what the row says.
|
|
256
|
-
*
|
|
257
|
-
* Called straight from the press that chose it, so the browser still counts it as a user
|
|
258
|
-
* gesture - which is what the clipboard API requires and what makes a paste possible at all.
|
|
259
|
-
* Returns whether the action happened: a refused permission and an empty clipboard are both
|
|
260
|
-
* "no", and neither is worth an exception the caller has to catch.
|
|
261
|
-
*/
|
|
262
|
-
declare function performClipboardAction(action: ClipboardAction, target: ClipboardTarget): Promise<boolean>;
|
|
263
|
-
|
|
264
|
-
export { CLIPBOARD_PREFIX as C, CONTEXT_MENU_SEARCH_KEYS as a, type ClipboardAction as b, type ClipboardMenuLabels as c, type ClipboardMenuOptions as d, type ClipboardTarget as e, type ContextMenuAction as f, type ContextMenuEntry as g, type ContextMenuInstance as h, type ContextMenuLabel as i, type ContextMenuLevel as j, type ContextMenuMoveKey as k, type ContextMenuOptions as l, type ContextMenuPoint as m, type ContextMenuSeparator as n, type ContextMenuState as o, clipboardAction as p, clipboardEntries as q, clipboardHasText as r, createContextMenu as s, inspectClipboardTarget as t, isAction as u, performClipboardAction as v };
|
|
174
|
+
export { CONTEXT_MENU_SEARCH_KEYS as C, type ContextMenuAction as a, type ContextMenuEntry as b, type ContextMenuInstance as c, type ContextMenuLabel as d, type ContextMenuLevel as e, type ContextMenuMoveKey as f, type ContextMenuOptions as g, type ContextMenuPoint as h, type ContextMenuSeparator as i, type ContextMenuState as j, createContextMenu as k, isAction as l };
|
|
@@ -145,6 +145,10 @@ interface ColorLabels {
|
|
|
145
145
|
alpha?: string;
|
|
146
146
|
eyedropper?: string;
|
|
147
147
|
swatches?: string;
|
|
148
|
+
/** The editable readout under the rails. Default "Colour value". */
|
|
149
|
+
value?: string;
|
|
150
|
+
/** The button that cycles the readout between HEX, RGB and HSL. Default "Show as". */
|
|
151
|
+
formatAs?: string;
|
|
148
152
|
/** The word before the colour in what a screen reader announces. Default "Colour". */
|
|
149
153
|
color?: string;
|
|
150
154
|
}
|