@pixerate/editor-react 0.69.0 → 0.71.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.
@@ -521,6 +521,10 @@ var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, clas
521
521
  // eslint-disable-next-line react-hooks/exhaustive-deps
522
522
  [controller, brushes, paper, thumbnailWidth, thumbnailHeight]
523
523
  );
524
+ const engine = (0, import_paint_editor4.brushEngineControls)(brush);
525
+ const mc = brush.behavior.multicolor;
526
+ const carried = (0, import_paint_editor4.brushLoadColor)(state.mixerLoad);
527
+ const setColors = (colors) => controller.setBrush({ behavior: { multicolor: { colors } } });
524
528
  const slug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "brush";
525
529
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `${panel} ${className ?? ""}`, "data-testid": "brush-panel", children: [
526
530
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "grid grid-cols-2 gap-1", role: "listbox", "aria-label": "Brushes", children: brushes.map((b) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -552,6 +556,68 @@ var BrushPanel = ({ controller, thumbnailWidth = 112, thumbnailHeight = 32, clas
552
556
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-2", children: [
553
557
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: brush.tip.pressureOpacity, onChange: (e) => controller.setBrush({ tip: { pressureOpacity: e.target.checked } }) }),
554
558
  " Pressure \u2192 opacity"
559
+ ] }),
560
+ brush.engine === "dab" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
561
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Blend" }),
562
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Brush blend", value: brush.tip.blend, onChange: (e) => controller.setBrush({ tip: { blend: e.target.value } }), className: "rounded bg-slate-800 px-1 py-0.5", children: import_paint_editor4.BLEND_MENU_GROUPS.map((g) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("optgroup", { label: g.label, children: g.modes.map((m) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: m.id, title: m.description, children: m.label }, m.id)) }, g.label)) })
563
+ ] })
564
+ ] }),
565
+ engine && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "brush-engine", children: [
566
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "font-medium text-slate-300", children: engine.title }),
567
+ engine.sliders.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor4.readPath)(brush, c.path), onChange: (v) => controller.setBrush((0, import_paint_editor4.patchPath)(brush, c.path, v)) }, c.path)),
568
+ engine.mixingPath && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
569
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Mixing" }),
570
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
571
+ "select",
572
+ {
573
+ "aria-label": "Brush mixing",
574
+ value: engine.mixingPath === "blender.mixing" ? brush.blender.mixing : engine.mixingPath === "smudge.mixing" ? brush.smudge.mixing : brush.behavior.mixing,
575
+ onChange: (e) => controller.setBrush((0, import_paint_editor4.patchBrushValue)(brush, engine.mixingPath, e.target.value)),
576
+ className: "rounded bg-slate-800 px-1 py-0.5",
577
+ children: [
578
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "pigment", children: "Pigment" }),
579
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "rgb", children: "RGB" })
580
+ ]
581
+ }
582
+ )
583
+ ] }),
584
+ engine.dirty && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-2", title: "Keep the colour the brush picked up for the next stroke (needs Pick-up above 0)", children: [
585
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", "aria-label": "Dirty brush", checked: brush.behavior.dirty, onChange: (e) => controller.setBrush({ behavior: { dirty: e.target.checked } }) }),
586
+ " Dirty brush"
587
+ ] }),
588
+ engine.multicolor && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-1.5", "data-testid": "multicolor", children: [
589
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
590
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Multi-colour" }),
591
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { "aria-label": "Multi-colour", value: mc.mode, onChange: (e) => controller.setBrush({ behavior: { multicolor: { mode: e.target.value } } }), className: "rounded bg-slate-800 px-1 py-0.5", children: [
592
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "off", children: "Off" }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "tip", children: "Across the tip" }),
594
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "dab", children: "Per dab" })
595
+ ] })
596
+ ] }),
597
+ mc.mode !== "off" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
598
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap items-center gap-1", "aria-label": "Multi-colour colours", children: [
599
+ mc.colors.map((col, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "flex items-center", children: [
600
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "color", "aria-label": `Multi-colour ${i + 1}`, value: col, onChange: (e) => setColors(mc.colors.map((x, j) => j === i ? e.target.value : x)), className: "h-5 w-6 rounded bg-transparent" }),
601
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": `Remove multi-colour ${i + 1}`, className: "px-0.5 text-slate-500 hover:text-white", onClick: () => setColors(mc.colors.filter((_, j) => j !== i)), children: "\xD7" })
602
+ ] }, i)),
603
+ mc.colors.length < 8 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: button, "aria-label": "Add multi-colour", onClick: () => setColors([...mc.colors.length ? mc.colors : [state.color, state.secondaryColor], state.color].slice(0, 8)), children: "+" }),
604
+ mc.colors.length < 2 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[10px] text-slate-500", children: "Uses the paint and second colours" })
605
+ ] }),
606
+ mc.mode === "dab" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center justify-between gap-2", children: [
607
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Order" }),
608
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { "aria-label": "Colour order", value: mc.order, onChange: (e) => controller.setBrush({ behavior: { multicolor: { order: e.target.value } } }), className: "rounded bg-slate-800 px-1 py-0.5", children: [
609
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "sequence", children: "In order" }),
610
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "random", children: "Random" })
611
+ ] })
612
+ ] }),
613
+ (0, import_paint_editor4.multiColorSliders)(mc.mode).map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PaintSlider, { control: c, value: (0, import_paint_editor4.readPath)(brush, c.path), onChange: (v) => controller.setBrush((0, import_paint_editor4.patchPath)(brush, c.path, v)) }, c.path))
614
+ ] })
615
+ ] }),
616
+ engine.load && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-1", children: [
617
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-slate-400", children: "Carries" }),
618
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-label": "Brush load", title: carried ?? "Clean", className: "h-4 w-4 rounded ring-1 ring-slate-700", style: { background: carried ?? "transparent" } }),
619
+ brush.engine === "mixer" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: button, onClick: () => controller.loadMixerBrush(), children: "Load" }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: button, onClick: () => controller.cleanMixerBrush(brush.engine === "mixer"), children: "Clean" })
555
621
  ] })
556
622
  ] }),
557
623
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
@@ -631,6 +697,9 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
631
697
  const [wheel, setWheel] = (0, import_react7.useState)("ryb");
632
698
  const [kind, setKind] = (0, import_react7.useState)("complementary");
633
699
  const [padTool, setPadTool] = (0, import_react7.useState)("paint");
700
+ const [sample, setSample] = (0, import_react7.useState)(3);
701
+ const [parts, setParts] = (0, import_react7.useState)([1, 1]);
702
+ const [padName, setPadName] = (0, import_react7.useState)("");
634
703
  const padPoints = (0, import_react7.useRef)(null);
635
704
  const ring = (0, import_react7.useMemo)(() => (0, import_paint_editor6.renderColorWheel)(wheelSize, wheel), [wheelSize, wheel]);
636
705
  const color = state.color;
@@ -742,7 +811,7 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
742
811
  onPointerDown: (e) => {
743
812
  const p = padPoint(e);
744
813
  if (padTool === "pick") {
745
- attempt(() => controller.mixingPadPick(p.x, p.y, { setColor: true }));
814
+ attempt(() => controller.mixingPadPick(p.x, p.y, { setColor: true, radius: (0, import_paint_editor6.padSampleRadius)(sample) }));
746
815
  return;
747
816
  }
748
817
  padPoints.current = [p];
@@ -756,7 +825,44 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
756
825
  },
757
826
  children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PixelCanvas, { pixels: pad, label: "Mixing pad pixels", className: "w-full rounded", style: { aspectRatio: `${import_paint_editor6.MIXING_PAD_WIDTH} / ${import_paint_editor6.MIXING_PAD_HEIGHT}` } })
758
827
  }
759
- )
828
+ ),
829
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex flex-wrap items-center gap-1", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1", children: [
830
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-400", children: "Sample" }),
831
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { "aria-label": "Pick sample size", value: sample, onChange: (e) => setSample(Number(e.target.value)), className: "rounded bg-slate-800 px-1 py-0.5", children: import_paint_editor6.PAD_SAMPLE_SIZES.map((n) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: n, children: n === 1 ? "1 px" : `${n}\xD7${n}` }, n)) })
832
+ ] }) }),
833
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
834
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "h-4 w-4 rounded ring-1 ring-slate-700", style: { background: color } }),
835
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "number", "aria-label": "Paint colour parts", min: 0, max: 20, value: parts[0], onChange: (e) => setParts([Number(e.target.value), parts[1]]), className: "w-10 rounded bg-slate-800 px-1 py-0.5" }),
836
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-slate-500", children: ":" }),
837
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "number", "aria-label": "Second colour parts", min: 0, max: 20, value: parts[1], onChange: (e) => setParts([parts[0], Number(e.target.value)]), className: "w-10 rounded bg-slate-800 px-1 py-0.5" }),
838
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "h-4 w-4 rounded ring-1 ring-slate-700", style: { background: state.secondaryColor } }),
839
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
840
+ "button",
841
+ {
842
+ type: "button",
843
+ className: button,
844
+ title: "Mix the paint and second colours in these parts on the pad, and use the mix",
845
+ onClick: () => attempt(
846
+ () => controller.mixOnPad(
847
+ [
848
+ { color, parts: parts[0] },
849
+ { color: state.secondaryColor, parts: parts[1] }
850
+ ],
851
+ { setColor: true }
852
+ )
853
+ ),
854
+ children: "Mix"
855
+ }
856
+ )
857
+ ] }),
858
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
859
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { value: padName, placeholder: "Pad name", "aria-label": "Pad name", onChange: (e) => setPadName(e.target.value), className: "w-24 rounded bg-slate-800 px-1.5 py-0.5" }),
860
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, onClick: () => attempt(() => (controller.saveMixingPad(padName || void 0), setPadName(""))), children: "Keep pad" })
861
+ ] }),
862
+ state.savedMixingPads.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "flex flex-col gap-0.5", "aria-label": "Kept pads", children: state.savedMixingPads.map((p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { className: "flex items-center gap-1", children: [
863
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: `${button} flex-1 text-left`, onClick: () => attempt(() => controller.loadMixingPad(p.id)), children: p.name }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: button, "aria-label": `Remove ${p.name}`, onClick: () => attempt(() => controller.removeMixingPad(p.id)), children: "\xD7" })
865
+ ] }, p.id)) })
760
866
  ] }),
761
867
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorLine, { error })
762
868
  ] });