@particle-academy/fancy-cms-ui 0.0.1-dev.2 → 0.0.1-dev.4
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-7YYLYBLI.js +47 -0
- package/dist/chunk-7YYLYBLI.js.map +1 -0
- package/dist/editor.cjs +232 -82
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +19 -9
- package/dist/editor.d.ts +19 -9
- package/dist/editor.js +222 -82
- package/dist/editor.js.map +1 -1
- package/dist/index.js +1 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/document/fractional.ts
|
|
2
|
+
var BASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
3
|
+
var RADIX = BASE.length;
|
|
4
|
+
var MID = Math.floor(RADIX / 2);
|
|
5
|
+
function val(c) {
|
|
6
|
+
const i = BASE.indexOf(c);
|
|
7
|
+
return i < 0 ? 0 : i;
|
|
8
|
+
}
|
|
9
|
+
function keyBetween(a, b) {
|
|
10
|
+
if (a === null) a = "";
|
|
11
|
+
if (b === null) {
|
|
12
|
+
return a + BASE[MID];
|
|
13
|
+
}
|
|
14
|
+
if (a >= b) {
|
|
15
|
+
throw new Error(`keyBetween: lower bound "${a}" is not below upper bound "${b}"`);
|
|
16
|
+
}
|
|
17
|
+
let result = "";
|
|
18
|
+
let i = 0;
|
|
19
|
+
for (; ; ) {
|
|
20
|
+
const ca = i < a.length ? val(a[i]) : 0;
|
|
21
|
+
const cb = i < b.length ? val(b[i]) : RADIX;
|
|
22
|
+
if (cb - ca > 1) {
|
|
23
|
+
result += BASE[ca + Math.floor((cb - ca) / 2)];
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
if (ca === cb) {
|
|
27
|
+
result += BASE[ca];
|
|
28
|
+
i++;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
result += BASE[ca];
|
|
32
|
+
i++;
|
|
33
|
+
for (; ; ) {
|
|
34
|
+
const da = i < a.length ? val(a[i]) : 0;
|
|
35
|
+
if (RADIX - da > 1) {
|
|
36
|
+
result += BASE[da + Math.floor((RADIX - da) / 2)];
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
result += BASE[da];
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { keyBetween };
|
|
46
|
+
//# sourceMappingURL=chunk-7YYLYBLI.js.map
|
|
47
|
+
//# sourceMappingURL=chunk-7YYLYBLI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/document/fractional.ts"],"names":[],"mappings":";AAaA,IAAM,IAAA,GAAO,gEAAA;AACb,IAAM,QAAQ,IAAA,CAAK,MAAA;AACnB,IAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,CAAC,CAAA;AAEhC,SAAS,IAAI,CAAA,EAAmB;AAC9B,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,CAAC,CAAA;AACxB,EAAA,OAAO,CAAA,GAAI,IAAI,CAAA,GAAI,CAAA;AACrB;AAOO,SAAS,UAAA,CAAW,GAAkB,CAAA,EAA0B;AACrE,EAAA,IAAI,CAAA,KAAM,MAAM,CAAA,GAAI,EAAA;AACpB,EAAA,IAAI,MAAM,IAAA,EAAM;AAEd,IAAA,OAAO,CAAA,GAAI,KAAK,GAAG,CAAA;AAAA,EACrB;AACA,EAAA,IAAI,KAAK,CAAA,EAAG;AACV,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,CAAC,CAAA,4BAAA,EAA+B,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,EAClF;AAEA,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,IAAI,CAAA,GAAI,CAAA;AAER,EAAA,WAAS;AACP,IAAA,MAAM,EAAA,GAAK,IAAI,CAAA,CAAE,MAAA,GAAS,IAAI,CAAA,CAAE,CAAC,CAAW,CAAA,GAAI,CAAA;AAChD,IAAA,MAAM,EAAA,GAAK,IAAI,CAAA,CAAE,MAAA,GAAS,IAAI,CAAA,CAAE,CAAC,CAAW,CAAA,GAAI,KAAA;AAChD,IAAA,IAAI,EAAA,GAAK,KAAK,CAAA,EAAG;AACf,MAAA,MAAA,IAAU,KAAK,EAAA,GAAK,IAAA,CAAK,OAAO,EAAA,GAAK,EAAA,IAAM,CAAC,CAAC,CAAA;AAC7C,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,IAAI,OAAO,EAAA,EAAI;AACb,MAAA,MAAA,IAAU,KAAK,EAAE,CAAA;AACjB,MAAA,CAAA,EAAA;AACA,MAAA;AAAA,IACF;AAGA,IAAA,MAAA,IAAU,KAAK,EAAE,CAAA;AACjB,IAAA,CAAA,EAAA;AACA,IAAA,WAAS;AACP,MAAA,MAAM,EAAA,GAAK,IAAI,CAAA,CAAE,MAAA,GAAS,IAAI,CAAA,CAAE,CAAC,CAAW,CAAA,GAAI,CAAA;AAChD,MAAA,IAAI,KAAA,GAAQ,KAAK,CAAA,EAAG;AAClB,QAAA,MAAA,IAAU,KAAK,EAAA,GAAK,IAAA,CAAK,OAAO,KAAA,GAAQ,EAAA,IAAM,CAAC,CAAC,CAAA;AAChD,QAAA,OAAO,MAAA;AAAA,MACT;AACA,MAAA,MAAA,IAAU,KAAK,EAAE,CAAA;AACjB,MAAA,CAAA,EAAA;AAAA,IACF;AAAA,EACF;AACF","file":"chunk-7YYLYBLI.js","sourcesContent":["/**\n * Minimal collab-safe fractional ordering keys.\n *\n * Sibling `order` is a string over a base-62 alphabet interpreted as a fraction\n * in (0, 1). `keyBetween(a, b)` returns a key that sorts strictly between `a`\n * and `b` (lexicographically), extending precision when the bounds are\n * adjacent. Concurrent inserts at the same spot produce different keys (vary by\n * actor), so reorders never collide.\n *\n * This is intentionally compact for Phase 0. Before live collaboration ships it\n * should be swapped for a hardened implementation with jitter + bucket bounds.\n */\n\nconst BASE = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\nconst RADIX = BASE.length; // 62\nconst MID = Math.floor(RADIX / 2);\n\nfunction val(c: string): number {\n const i = BASE.indexOf(c);\n return i < 0 ? 0 : i;\n}\n\n/**\n * Return a key strictly between `a` and `b`. Pass `null` for an open bound\n * (`keyBetween(null, null)` for the first key, `keyBetween(last, null)` to\n * append, `keyBetween(null, first)` to prepend).\n */\nexport function keyBetween(a: string | null, b: string | null): string {\n if (a === null) a = \"\";\n if (b === null) {\n // Append: a key greater than `a`. Appending a mid digit always sorts after.\n return a + BASE[MID];\n }\n if (a >= b) {\n throw new Error(`keyBetween: lower bound \"${a}\" is not below upper bound \"${b}\"`);\n }\n\n let result = \"\";\n let i = 0;\n // Walk the common prefix, then split at the first differing digit.\n for (;;) {\n const ca = i < a.length ? val(a[i] as string) : 0;\n const cb = i < b.length ? val(b[i] as string) : RADIX;\n if (cb - ca > 1) {\n result += BASE[ca + Math.floor((cb - ca) / 2)];\n return result;\n }\n if (ca === cb) {\n result += BASE[ca];\n i++;\n continue;\n }\n // Adjacent digits (cb - ca === 1): take the lower path, then descend into\n // `a`'s remainder with RADIX as the upper bound until a gap opens.\n result += BASE[ca];\n i++;\n for (;;) {\n const da = i < a.length ? val(a[i] as string) : 0;\n if (RADIX - da > 1) {\n result += BASE[da + Math.floor((RADIX - da) / 2)];\n return result;\n }\n result += BASE[da];\n i++;\n }\n }\n}\n"]}
|
package/dist/editor.cjs
CHANGED
|
@@ -697,138 +697,288 @@ function Toolbar({ ed }) {
|
|
|
697
697
|
}
|
|
698
698
|
);
|
|
699
699
|
}
|
|
700
|
+
|
|
701
|
+
// src/document/fractional.ts
|
|
702
|
+
var BASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
703
|
+
var RADIX = BASE.length;
|
|
704
|
+
var MID = Math.floor(RADIX / 2);
|
|
705
|
+
function keyBetween(a, b) {
|
|
706
|
+
if (a === null) a = "";
|
|
707
|
+
{
|
|
708
|
+
return a + BASE[MID];
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
var Z = 2147483e3;
|
|
700
712
|
function EditablePage({
|
|
701
713
|
doc,
|
|
702
714
|
registry = defaultRegistry,
|
|
703
|
-
|
|
704
|
-
|
|
715
|
+
timelineDock,
|
|
716
|
+
transforms,
|
|
717
|
+
onNodeTransform,
|
|
718
|
+
onSelect
|
|
705
719
|
}) {
|
|
706
720
|
const ed = useEditor(doc);
|
|
707
|
-
const [editing, setEditing] = react.useState(
|
|
721
|
+
const [editing, setEditing] = react.useState(false);
|
|
722
|
+
const [revealed, setRevealed] = react.useState(false);
|
|
708
723
|
const pageRef = react.useRef(null);
|
|
709
724
|
const [box, setBox] = react.useState(null);
|
|
710
725
|
const { selection } = ed.state;
|
|
726
|
+
const selNode = selection ? ed.state.doc.nodes[selection] : null;
|
|
727
|
+
react.useEffect(() => onSelect?.(selection), [selection, onSelect]);
|
|
728
|
+
react.useEffect(() => {
|
|
729
|
+
const onKey = (e) => setRevealed(e.ctrlKey && e.shiftKey);
|
|
730
|
+
const onBlur = () => setRevealed(false);
|
|
731
|
+
window.addEventListener("keydown", onKey);
|
|
732
|
+
window.addEventListener("keyup", onKey);
|
|
733
|
+
window.addEventListener("blur", onBlur);
|
|
734
|
+
return () => {
|
|
735
|
+
window.removeEventListener("keydown", onKey);
|
|
736
|
+
window.removeEventListener("keyup", onKey);
|
|
737
|
+
window.removeEventListener("blur", onBlur);
|
|
738
|
+
};
|
|
739
|
+
}, []);
|
|
740
|
+
react.useEffect(() => {
|
|
741
|
+
const root = pageRef.current;
|
|
742
|
+
if (!root) return;
|
|
743
|
+
root.querySelectorAll("[data-cms]").forEach((el) => {
|
|
744
|
+
const id = el.dataset.cms;
|
|
745
|
+
const t = id ? transforms?.[id] : void 0;
|
|
746
|
+
if (t) {
|
|
747
|
+
el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
|
|
748
|
+
el.style.opacity = t.opacity != null ? String(t.opacity) : "";
|
|
749
|
+
} else {
|
|
750
|
+
el.style.transform = "";
|
|
751
|
+
el.style.opacity = "";
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
}, [transforms, ed.state.doc]);
|
|
755
|
+
const measureBox = react.useCallback(() => {
|
|
756
|
+
if (!editing || !selection || !pageRef.current) return setBox(null);
|
|
757
|
+
const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
|
|
758
|
+
if (!el) return setBox(null);
|
|
759
|
+
const r = el.getBoundingClientRect();
|
|
760
|
+
setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
|
|
761
|
+
}, [editing, selection]);
|
|
711
762
|
react.useLayoutEffect(() => {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (!el) return setBox(null);
|
|
719
|
-
const r = el.getBoundingClientRect();
|
|
720
|
-
setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
|
|
763
|
+
measureBox();
|
|
764
|
+
window.addEventListener("scroll", measureBox, { passive: true });
|
|
765
|
+
window.addEventListener("resize", measureBox);
|
|
766
|
+
return () => {
|
|
767
|
+
window.removeEventListener("scroll", measureBox);
|
|
768
|
+
window.removeEventListener("resize", measureBox);
|
|
721
769
|
};
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
770
|
+
}, [measureBox, ed.state.doc, transforms]);
|
|
771
|
+
react.useEffect(() => {
|
|
772
|
+
if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
|
|
773
|
+
const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
|
|
774
|
+
if (!el) return;
|
|
775
|
+
el.setAttribute("contenteditable", "true");
|
|
776
|
+
el.style.outline = "none";
|
|
777
|
+
el.focus();
|
|
778
|
+
const commit = () => ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
|
|
779
|
+
el.addEventListener("blur", commit);
|
|
725
780
|
return () => {
|
|
726
|
-
|
|
727
|
-
|
|
781
|
+
el.removeEventListener("blur", commit);
|
|
782
|
+
el.removeAttribute("contenteditable");
|
|
728
783
|
};
|
|
729
|
-
}, [editing, selection, ed
|
|
784
|
+
}, [editing, selection, selNode?.type, ed]);
|
|
730
785
|
const onClickCapture = (e) => {
|
|
731
786
|
if (!editing) return;
|
|
732
787
|
const el = e.target.closest("[data-cms]");
|
|
733
788
|
if (el) {
|
|
789
|
+
if (el.getAttribute("contenteditable") === "true") return;
|
|
734
790
|
e.preventDefault();
|
|
735
791
|
e.stopPropagation();
|
|
736
792
|
ed.select(el.getAttribute("data-cms"));
|
|
737
793
|
}
|
|
738
794
|
};
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
zIndex: 2147483e3
|
|
749
|
-
} : null;
|
|
795
|
+
const addText = react.useCallback(() => {
|
|
796
|
+
const parent = selNode && selNode.type !== "text" ? selNode.id : ed.state.doc.sections[0] ?? null;
|
|
797
|
+
const siblings = childrenOf(ed.state.doc, parent);
|
|
798
|
+
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null);
|
|
799
|
+
const id = `n${ed.state.doc.seq + 1}-${Math.floor(performance.now())}`;
|
|
800
|
+
ed.apply({ t: "insert_node", node: { id, type: "text", parent, order, props: { content: "New text" }, style: { base: { color: "inherit" } } } });
|
|
801
|
+
ed.select(id);
|
|
802
|
+
}, [ed, selNode]);
|
|
803
|
+
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
750
804
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
751
805
|
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: pageRef, onClickCapture, children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry }) }),
|
|
752
|
-
editing &&
|
|
753
|
-
|
|
806
|
+
editing && box && selection ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
807
|
+
SelectionOverlay,
|
|
808
|
+
{
|
|
809
|
+
box,
|
|
810
|
+
base: transforms?.[selection] ?? {},
|
|
811
|
+
movable: Boolean(onNodeTransform),
|
|
812
|
+
getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape2(selection)}"]`) ?? null,
|
|
813
|
+
onTransform: (t) => onNodeTransform?.(selection, t),
|
|
814
|
+
onLive: measureBox
|
|
815
|
+
}
|
|
816
|
+
) : null,
|
|
817
|
+
editing && box && selNode ? /* @__PURE__ */ jsxRuntime.jsx(FloatingToolbar, { box, isText: selNode.type === "text", base: selNode.style.base, setStyle }) : null,
|
|
818
|
+
revealed || editing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
754
819
|
EditBar,
|
|
755
820
|
{
|
|
756
821
|
editing,
|
|
757
|
-
onToggle: () =>
|
|
822
|
+
onToggle: () => {
|
|
823
|
+
setEditing((v) => !v);
|
|
824
|
+
ed.select(null);
|
|
825
|
+
},
|
|
758
826
|
canUndo: ed.canUndo,
|
|
759
827
|
canRedo: ed.canRedo,
|
|
760
828
|
onUndo: ed.undo,
|
|
761
829
|
onRedo: ed.redo,
|
|
762
|
-
|
|
830
|
+
onAddText: addText
|
|
763
831
|
}
|
|
764
|
-
),
|
|
765
|
-
editing &&
|
|
766
|
-
editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: dockSlot, children: timelineDock }) : null
|
|
832
|
+
) : null,
|
|
833
|
+
editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z }, children: timelineDock }) : null
|
|
767
834
|
] });
|
|
768
835
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
border: "1px solid #e2e8f0",
|
|
777
|
-
borderRadius: 12,
|
|
778
|
-
boxShadow: "0 10px 30px -10px rgba(0,0,0,0.25)",
|
|
779
|
-
overflow: "auto",
|
|
780
|
-
zIndex: 2147483001
|
|
781
|
-
};
|
|
782
|
-
var dockSlot = {
|
|
783
|
-
position: "fixed",
|
|
784
|
-
left: 0,
|
|
785
|
-
right: 0,
|
|
786
|
-
bottom: 0,
|
|
787
|
-
zIndex: 2147483e3
|
|
788
|
-
};
|
|
789
|
-
function EditBar({
|
|
790
|
-
editing,
|
|
791
|
-
onToggle,
|
|
792
|
-
canUndo,
|
|
793
|
-
canRedo,
|
|
794
|
-
onUndo,
|
|
795
|
-
onRedo,
|
|
796
|
-
hasDock
|
|
836
|
+
function SelectionOverlay({
|
|
837
|
+
box,
|
|
838
|
+
base,
|
|
839
|
+
movable,
|
|
840
|
+
getEl,
|
|
841
|
+
onTransform,
|
|
842
|
+
onLive
|
|
797
843
|
}) {
|
|
798
|
-
const
|
|
844
|
+
const drag = react.useRef(null);
|
|
845
|
+
const apply = (t) => {
|
|
846
|
+
const el = getEl();
|
|
847
|
+
if (el) {
|
|
848
|
+
el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
|
|
849
|
+
el.style.opacity = t.opacity != null ? String(t.opacity) : "";
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
const start = (kind) => (e) => {
|
|
853
|
+
e.stopPropagation();
|
|
854
|
+
e.preventDefault();
|
|
855
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
856
|
+
drag.current = { kind, x: e.clientX, y: e.clientY, w: box.w, live: { ...base } };
|
|
857
|
+
};
|
|
858
|
+
const move = (e) => {
|
|
859
|
+
const d = drag.current;
|
|
860
|
+
if (!d) return;
|
|
861
|
+
if (d.kind === "move") {
|
|
862
|
+
d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
|
|
863
|
+
} else {
|
|
864
|
+
const factor = Math.max(0.1, (d.w + (e.clientX - d.x)) / d.w);
|
|
865
|
+
d.live = { ...base, scale: Number(((base.scale ?? 1) * factor).toFixed(3)) };
|
|
866
|
+
}
|
|
867
|
+
apply(d.live);
|
|
868
|
+
onLive();
|
|
869
|
+
};
|
|
870
|
+
const end = () => {
|
|
871
|
+
const d = drag.current;
|
|
872
|
+
if (d) onTransform(d.live);
|
|
873
|
+
drag.current = null;
|
|
874
|
+
};
|
|
875
|
+
const outline = {
|
|
876
|
+
position: "fixed",
|
|
877
|
+
left: box.x - 1,
|
|
878
|
+
top: box.y - 1,
|
|
879
|
+
width: box.w + 2,
|
|
880
|
+
height: box.h + 2,
|
|
881
|
+
outline: "2px solid #8b5cf6",
|
|
882
|
+
borderRadius: 4,
|
|
883
|
+
pointerEvents: "none",
|
|
884
|
+
zIndex: Z
|
|
885
|
+
};
|
|
886
|
+
const handle = {
|
|
887
|
+
position: "fixed",
|
|
888
|
+
width: 16,
|
|
889
|
+
height: 16,
|
|
890
|
+
background: "#8b5cf6",
|
|
891
|
+
border: "2px solid #fff",
|
|
892
|
+
borderRadius: 5,
|
|
893
|
+
zIndex: Z + 1,
|
|
894
|
+
touchAction: "none"
|
|
895
|
+
};
|
|
896
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
897
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: outline }),
|
|
898
|
+
movable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
899
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
900
|
+
"div",
|
|
901
|
+
{
|
|
902
|
+
title: "Drag to move",
|
|
903
|
+
onPointerDown: start("move"),
|
|
904
|
+
onPointerMove: move,
|
|
905
|
+
onPointerUp: end,
|
|
906
|
+
style: { ...handle, left: box.x - 9, top: box.y - 9, cursor: "move", borderRadius: 9 }
|
|
907
|
+
}
|
|
908
|
+
),
|
|
909
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
910
|
+
"div",
|
|
911
|
+
{
|
|
912
|
+
title: "Drag to resize",
|
|
913
|
+
onPointerDown: start("resize"),
|
|
914
|
+
onPointerMove: move,
|
|
915
|
+
onPointerUp: end,
|
|
916
|
+
style: { ...handle, left: box.x + box.w - 7, top: box.y + box.h - 7, cursor: "nwse-resize" }
|
|
917
|
+
}
|
|
918
|
+
)
|
|
919
|
+
] }) : null
|
|
920
|
+
] });
|
|
921
|
+
}
|
|
922
|
+
function FloatingToolbar({
|
|
923
|
+
box,
|
|
924
|
+
isText,
|
|
925
|
+
base,
|
|
926
|
+
setStyle
|
|
927
|
+
}) {
|
|
928
|
+
const wrap = {
|
|
799
929
|
position: "fixed",
|
|
800
|
-
|
|
801
|
-
|
|
930
|
+
left: box.x,
|
|
931
|
+
top: Math.max(8, box.y - 46),
|
|
802
932
|
display: "flex",
|
|
803
|
-
gap: 6,
|
|
804
933
|
alignItems: "center",
|
|
805
|
-
|
|
934
|
+
gap: 8,
|
|
935
|
+
padding: "6px 10px",
|
|
806
936
|
background: "#0f172a",
|
|
807
937
|
color: "#fff",
|
|
938
|
+
border: "1px solid rgba(255,255,255,0.12)",
|
|
808
939
|
borderRadius: 999,
|
|
809
|
-
boxShadow: "0 8px 24px -8px rgba(0,0,0,0.
|
|
810
|
-
zIndex:
|
|
940
|
+
boxShadow: "0 8px 24px -8px rgba(0,0,0,0.5)",
|
|
941
|
+
zIndex: Z + 2,
|
|
811
942
|
fontFamily: "system-ui, sans-serif",
|
|
812
943
|
fontSize: 12
|
|
813
944
|
};
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
945
|
+
const swatch = { width: 22, height: 22, border: "none", background: "transparent", padding: 0, cursor: "pointer" };
|
|
946
|
+
const num = { width: 52, background: "#1e293b", color: "#fff", border: "1px solid #334155", borderRadius: 6, padding: "3px 6px", font: "inherit" };
|
|
947
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: wrap, onMouseDown: (e) => e.preventDefault(), children: [
|
|
948
|
+
isText ? /* @__PURE__ */ jsxRuntime.jsxs("label", { title: "Text color", style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
|
|
949
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.7 }, children: "A" }),
|
|
950
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "color", style: swatch, value: base.color ?? "#0f172a", onChange: (e) => setStyle({ color: e.target.value }) })
|
|
951
|
+
] }) : null,
|
|
952
|
+
isText ? /* @__PURE__ */ jsxRuntime.jsx("input", { type: "number", title: "Font size (px)", style: num, placeholder: "size", value: base.fontSize?.value ?? "", onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } }) }) : null,
|
|
953
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { title: "Background", style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
|
|
954
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.7 }, children: "BG" }),
|
|
955
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "color", style: swatch, value: normalizeColor(base.background), onChange: (e) => setStyle({ background: e.target.value }) })
|
|
956
|
+
] })
|
|
957
|
+
] });
|
|
958
|
+
}
|
|
959
|
+
function EditBar({
|
|
960
|
+
editing,
|
|
961
|
+
onToggle,
|
|
962
|
+
canUndo,
|
|
963
|
+
canRedo,
|
|
964
|
+
onUndo,
|
|
965
|
+
onRedo,
|
|
966
|
+
onAddText
|
|
967
|
+
}) {
|
|
968
|
+
const bar = { position: "fixed", top: 12, right: 12, display: "flex", gap: 6, alignItems: "center", padding: 6, background: "#0f172a", color: "#fff", borderRadius: 999, boxShadow: "0 8px 24px -8px rgba(0,0,0,0.4)", zIndex: Z + 3, fontFamily: "system-ui, sans-serif", fontSize: 12 };
|
|
969
|
+
const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
|
|
823
970
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bar, children: [
|
|
824
971
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
|
|
825
972
|
editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
973
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: btn, onClick: onAddText, title: "Add a text element", children: "\uFF0B Text" }),
|
|
826
974
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canUndo ? 1 : 0.4 }, disabled: !canUndo, onClick: onUndo, children: "\u21B6" }),
|
|
827
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canRedo ? 1 : 0.4 }, disabled: !canRedo, onClick: onRedo, children: "\u21B7" })
|
|
828
|
-
!hasDock ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.5, paddingRight: 6 }, children: "timeline: fancy-motion \u2192" }) : null
|
|
975
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canRedo ? 1 : 0.4 }, disabled: !canRedo, onClick: onRedo, children: "\u21B7" })
|
|
829
976
|
] }) : null
|
|
830
977
|
] });
|
|
831
978
|
}
|
|
979
|
+
function normalizeColor(v) {
|
|
980
|
+
return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
|
|
981
|
+
}
|
|
832
982
|
function cssEscape2(value) {
|
|
833
983
|
return value.replace(/["\\]/g, "\\$&");
|
|
834
984
|
}
|