@jxsuite/studio 0.0.1

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/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@jxsuite/studio",
3
+ "version": "0.0.1",
4
+ "description": "Jx Studio — visual builder for Jx documents",
5
+ "license": "MIT",
6
+ "files": [
7
+ "*.js",
8
+ "src",
9
+ "dist"
10
+ ],
11
+ "type": "module",
12
+ "exports": {
13
+ ".": "./studio.js",
14
+ "./platform.js": "./platform.js"
15
+ },
16
+ "scripts": {
17
+ "build": "bun build ./src/studio.js --outdir dist --target browser --sourcemap=linked",
18
+ "gen:webdata": "bun run scripts/gen-webdata.js",
19
+ "upgrade": "bunx npm-check-updates -u && bun install",
20
+ "test": "bun test"
21
+ },
22
+ "dependencies": {
23
+ "@atlaskit/pragmatic-drag-and-drop": "^1.8.1",
24
+ "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
25
+ "@jxsuite/runtime": "workspace:*",
26
+ "@spectrum-web-components/accordion": "^1.11.2",
27
+ "@spectrum-web-components/action-bar": "1.11.2",
28
+ "@spectrum-web-components/action-button": "^1.11.2",
29
+ "@spectrum-web-components/action-group": "^1.11.2",
30
+ "@spectrum-web-components/checkbox": "^1.11.2",
31
+ "@spectrum-web-components/color-area": "^1.11.2",
32
+ "@spectrum-web-components/color-handle": "^1.11.2",
33
+ "@spectrum-web-components/color-slider": "^1.11.2",
34
+ "@spectrum-web-components/combobox": "^1.11.2",
35
+ "@spectrum-web-components/divider": "^1.11.2",
36
+ "@spectrum-web-components/icon": "^1.11.2",
37
+ "@spectrum-web-components/icons-workflow": "^1.11.2",
38
+ "@spectrum-web-components/menu": "^1.11.2",
39
+ "@spectrum-web-components/number-field": "^1.11.2",
40
+ "@spectrum-web-components/overlay": "^1.11.2",
41
+ "@spectrum-web-components/picker": "^1.11.2",
42
+ "@spectrum-web-components/picker-button": "^1.11.2",
43
+ "@spectrum-web-components/popover": "^1.11.2",
44
+ "@spectrum-web-components/search": "^1.11.2",
45
+ "@spectrum-web-components/swatch": "^1.11.2",
46
+ "@spectrum-web-components/switch": "^1.11.2",
47
+ "@spectrum-web-components/tabs": "^1.11.2",
48
+ "@spectrum-web-components/textfield": "^1.11.2",
49
+ "@spectrum-web-components/theme": "^1.11.2",
50
+ "@spectrum-web-components/tooltip": "^1.11.2",
51
+ "lit-html": "^3.3.2",
52
+ "monaco-editor": "^0.55.1",
53
+ "remark-directive": "^4.0.0",
54
+ "remark-frontmatter": "^5.0.0",
55
+ "remark-gfm": "^4.0.1",
56
+ "remark-parse": "^11.0.0",
57
+ "remark-stringify": "^11.0.0",
58
+ "unified": "^11.0.5",
59
+ "yaml": "^2.8.3"
60
+ },
61
+ "devDependencies": {
62
+ "@happy-dom/global-registrator": "^20.9.0",
63
+ "@webref/css": "^8.5.3",
64
+ "@webref/elements": "^2.7.1",
65
+ "@webref/idl": "^3.75.3"
66
+ }
67
+ }
@@ -0,0 +1,144 @@
1
+ // ─── Clipboard & Context Menu ─────────────────────────────────────────────────
2
+ import { html, render as litRender } from "lit-html";
3
+ import {
4
+ update,
5
+ selectNode,
6
+ insertNode,
7
+ removeNode,
8
+ duplicateNode,
9
+ getNodeAtPath,
10
+ parentElementPath,
11
+ childIndex,
12
+ } from "../store.js";
13
+ import { statusMessage } from "../panels/statusbar.js";
14
+
15
+ /** @type {any} */
16
+ let clipboard = null;
17
+
18
+ // ─── Clipboard ────────────────────────────────────────────────────────────────
19
+
20
+ /** @param {any} S */
21
+ export function copyNode(S) {
22
+ if (!S.selection) return;
23
+ const node = getNodeAtPath(S.document, S.selection);
24
+ if (!node) return;
25
+ clipboard = structuredClone(node);
26
+ statusMessage("Copied");
27
+ }
28
+
29
+ /** @param {any} S */
30
+ export function cutNode(S) {
31
+ if (!S.selection || S.selection.length < 2) return;
32
+ const node = getNodeAtPath(S.document, S.selection);
33
+ if (!node) return;
34
+ clipboard = structuredClone(node);
35
+ update(removeNode(S, S.selection));
36
+ statusMessage("Cut");
37
+ }
38
+
39
+ /** @param {any} S */
40
+ export function pasteNode(S) {
41
+ if (!clipboard) return;
42
+ const pPath = S.selection || [];
43
+ const parent = getNodeAtPath(S.document, pPath);
44
+ if (!parent) return;
45
+
46
+ if (S.selection && S.selection.length >= 2) {
47
+ // Paste as sibling after selection
48
+ const pp = /** @type {any} */ (parentElementPath(S.selection));
49
+ const idx = /** @type {number} */ (childIndex(S.selection));
50
+ update(insertNode(S, pp, idx + 1, structuredClone(clipboard)));
51
+ } else {
52
+ // Paste as last child of root/selected
53
+ const idx = parent.children ? parent.children.length : 0;
54
+ update(insertNode(S, pPath, idx, structuredClone(clipboard)));
55
+ }
56
+ statusMessage("Pasted");
57
+ }
58
+
59
+ // ─── Context menu ─────────────────────────────────────────────────────────────
60
+
61
+ const ctxMenu = document.createElement("sp-popover");
62
+ ctxMenu.style.position = "fixed";
63
+ ctxMenu.style.zIndex = "10000";
64
+ /** Append inside sp-theme so the popover inherits Spectrum styles */
65
+ (document.querySelector("sp-theme") || document.body).appendChild(ctxMenu);
66
+
67
+ document.addEventListener("click", () => {
68
+ ctxMenu.removeAttribute("open");
69
+ });
70
+
71
+ /**
72
+ * @param {any} e
73
+ * @param {any} path
74
+ * @param {any} S
75
+ */
76
+ export function showContextMenu(e, path, S) {
77
+ e.preventDefault();
78
+ ctxMenu.removeAttribute("open");
79
+
80
+ const node = getNodeAtPath(S.document, path);
81
+ if (!node) return;
82
+
83
+ // Select the node
84
+ update(selectNode(S, path));
85
+
86
+ /** @type {{ label: string; action?: () => void; danger?: boolean }[]} */
87
+ const items = [];
88
+
89
+ items.push({ label: "Copy", action: () => copyNode(S) });
90
+ if (path.length >= 2) {
91
+ items.push({ label: "Cut", action: () => cutNode(S) });
92
+ items.push({ label: "Duplicate", action: () => update(duplicateNode(S, S.selection)) });
93
+ items.push({ label: "—" }); // separator
94
+ items.push({ label: "Delete", action: () => update(removeNode(S, S.selection)), danger: true });
95
+ }
96
+ if (clipboard) {
97
+ items.push({ label: "—" });
98
+ items.push({
99
+ label: "Paste inside",
100
+ action: () => {
101
+ const idx = node.children ? node.children.length : 0;
102
+ update(insertNode(S, path, idx, structuredClone(clipboard)));
103
+ },
104
+ });
105
+ if (path.length >= 2) {
106
+ items.push({
107
+ label: "Paste after",
108
+ action: () => {
109
+ const pp = /** @type {any} */ (parentElementPath(path));
110
+ const idx = /** @type {number} */ (childIndex(path));
111
+ update(insertNode(S, pp, idx + 1, structuredClone(clipboard)));
112
+ },
113
+ });
114
+ }
115
+ }
116
+
117
+ litRender(
118
+ html`<sp-menu>
119
+ ${items.map((item) =>
120
+ item.label === "—"
121
+ ? html`<sp-menu-divider></sp-menu-divider>`
122
+ : html`<sp-menu-item
123
+ style=${item.danger ? "color: var(--danger)" : ""}
124
+ @click=${() => {
125
+ ctxMenu.removeAttribute("open");
126
+ item.action?.();
127
+ }}
128
+ >${item.label}</sp-menu-item
129
+ >`,
130
+ )}
131
+ </sp-menu>`,
132
+ ctxMenu,
133
+ );
134
+
135
+ // Position the menu
136
+ ctxMenu.setAttribute("open", "");
137
+ const menuRect = ctxMenu.getBoundingClientRect();
138
+ let x = e.clientX,
139
+ y = e.clientY;
140
+ if (x + menuRect.width > window.innerWidth) x = window.innerWidth - menuRect.width - 4;
141
+ if (y + menuRect.height > window.innerHeight) y = window.innerHeight - menuRect.height - 4;
142
+ ctxMenu.style.left = `${x}px`;
143
+ ctxMenu.style.top = `${y}px`;
144
+ }