@jxsuite/studio 0.23.2 → 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.
Files changed (105) hide show
  1. package/dist/studio.js +2230 -714
  2. package/dist/studio.js.map +121 -104
  3. package/package.json +8 -7
  4. package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
  5. package/src/browse/{browse.js → browse.ts} +109 -84
  6. package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
  7. package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
  8. package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
  9. package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
  10. package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
  11. package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
  12. package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
  13. package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
  14. package/src/editor/{context-menu.js → context-menu.ts} +38 -36
  15. package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
  16. package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
  17. package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
  18. package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
  19. package/src/editor/{inline-format.js → inline-format.ts} +46 -51
  20. package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
  21. package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
  22. package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
  23. package/src/files/{components.js → components.ts} +12 -15
  24. package/src/files/{file-ops.js → file-ops.ts} +36 -29
  25. package/src/files/{files.js → files.ts} +102 -84
  26. package/src/github/{github-auth.js → github-auth.ts} +3 -3
  27. package/src/github/{github-publish.js → github-publish.ts} +20 -18
  28. package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
  29. package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
  30. package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
  31. package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
  32. package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
  33. package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
  34. package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
  35. package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
  36. package/src/panels/dnd.ts +417 -0
  37. package/src/panels/{editors.js → editors.ts} +43 -50
  38. package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
  39. package/src/panels/{events-panel.js → events-panel.ts} +33 -25
  40. package/src/panels/{git-panel.js → git-panel.ts} +97 -83
  41. package/src/panels/{head-panel.js → head-panel.ts} +117 -139
  42. package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
  43. package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
  44. package/src/panels/{left-panel.js → left-panel.ts} +56 -63
  45. package/src/panels/{overlays.js → overlays.ts} +28 -36
  46. package/src/panels/{panel-events.js → panel-events.ts} +53 -51
  47. package/src/panels/{preview-render.js → preview-render.ts} +20 -15
  48. package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
  49. package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
  50. package/src/panels/{quick-search.js → quick-search.ts} +16 -17
  51. package/src/panels/{right-panel.js → right-panel.ts} +41 -59
  52. package/src/panels/{shared.js → shared.ts} +7 -5
  53. package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
  54. package/src/panels/{statusbar.js → statusbar.ts} +14 -14
  55. package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
  56. package/src/panels/{style-panel.js → style-panel.ts} +235 -290
  57. package/src/panels/{style-utils.js → style-utils.ts} +32 -31
  58. package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
  59. package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
  60. package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
  61. package/src/panels/{toolbar.js → toolbar.ts} +68 -70
  62. package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
  63. package/src/{platform.js → platform.ts} +5 -3
  64. package/src/platforms/{devserver.js → devserver.ts} +53 -44
  65. package/src/{recent-projects.js → recent-projects.ts} +17 -12
  66. package/src/{resize-edges.js → resize-edges.ts} +18 -13
  67. package/src/services/{cem-export.js → cem-export.ts} +14 -7
  68. package/src/services/{code-services.js → code-services.ts} +29 -20
  69. package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
  70. package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
  71. package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
  72. package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
  73. package/src/settings/{general-settings.js → general-settings.ts} +29 -28
  74. package/src/settings/{head-editor.js → head-editor.ts} +37 -35
  75. package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
  76. package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
  77. package/src/{site-context.js → site-context.ts} +35 -32
  78. package/src/{state.js → state.ts} +95 -78
  79. package/src/{store.js → store.ts} +60 -63
  80. package/src/{studio.js → studio.ts} +117 -119
  81. package/src/tabs/tab.ts +196 -0
  82. package/src/tabs/{transact.js → transact.ts} +107 -60
  83. package/src/types.ts +185 -0
  84. package/src/ui/{button-group.js → button-group.ts} +21 -21
  85. package/src/ui/{color-selector.js → color-selector.ts} +44 -37
  86. package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
  87. package/src/ui/{field-row.js → field-row.ts} +19 -2
  88. package/src/ui/{icons.js → icons.ts} +2 -2
  89. package/src/ui/{layers.js → layers.ts} +33 -24
  90. package/src/ui/{media-picker.js → media-picker.ts} +13 -9
  91. package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
  92. package/src/ui/{spectrum.js → spectrum.ts} +4 -4
  93. package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
  94. package/src/ui/{value-selector.js → value-selector.ts} +28 -28
  95. package/src/ui/{widgets.js → widgets.ts} +62 -15
  96. package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
  97. package/src/utils/{edit-display.js → edit-display.ts} +23 -20
  98. package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
  99. package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
  100. package/src/utils/{studio-utils.js → studio-utils.ts} +35 -23
  101. package/src/{view.js → view.ts} +53 -55
  102. package/src/workspace/{workspace.js → workspace.ts} +44 -33
  103. package/src/panels/dnd.js +0 -426
  104. package/src/tabs/tab.js +0 -192
  105. /package/src/{reactivity.js → reactivity.ts} +0 -0
@@ -1,53 +1,50 @@
1
- import { reactive, computed } from "../reactivity.js";
2
- import { createTab, disposeTab } from "../tabs/tab.js";
1
+ import { reactive, computed } from "../reactivity";
2
+ import { createTab, disposeTab } from "../tabs/tab";
3
+ import type { Tab } from "../tabs/tab";
3
4
 
4
- /**
5
- * @typedef {import("../tabs/tab.js").Tab} Tab
6
- *
7
- * @typedef {import("../files/components.js").ComponentEntry} ComponentEntry
8
- *
9
- * @typedef {{ name: string; path: string; type: string }} FileEntry
10
- */
5
+ import type { ComponentEntry } from "../files/components";
6
+ import type { JxStyle, JxMutableNode } from "@jxsuite/schema/types";
7
+
8
+ interface FileEntry {
9
+ name: string;
10
+ path: string;
11
+ type: string;
12
+ }
11
13
 
12
14
  export const workspace = reactive({
13
15
  /** @type {string | null} */
14
- projectRoot: null,
16
+ projectRoot: null as string | null,
15
17
  /** @type {object | null} */
16
- projectConfig: null,
18
+ projectConfig: null as object | null,
17
19
  /** @type {ComponentEntry[]} */
18
- componentRegistry: [],
20
+ componentRegistry: [] as ComponentEntry[],
19
21
  /** @type {JxMutableNode | null} */
20
- clipboard: null,
22
+ clipboard: null as JxMutableNode | null,
21
23
  /** @type {JxStyle | null} */
22
- styleClipboard: null,
24
+ styleClipboard: null as JxStyle | null,
23
25
  fileTree: {
24
26
  /** @type {Map<string, FileEntry[]>} */
25
- dirs: new Map(),
27
+ dirs: new Map<string, FileEntry[]>(),
26
28
  /** @type {Set<string>} */
27
- expanded: new Set(),
29
+ expanded: new Set<string>(),
28
30
  /** @type {string | null} */
29
- selectedPath: null,
31
+ selectedPath: null as string | null,
30
32
  searchQuery: "",
31
33
  },
32
34
  /** @type {Map<string, Tab>} */
33
- tabs: new Map(),
35
+ tabs: new Map<string, Tab>(),
34
36
  /** @type {string[]} */
35
- tabOrder: [],
37
+ tabOrder: [] as string[],
36
38
  /** @type {string | null} */
37
- activeTabId: null,
39
+ activeTabId: null as string | null,
38
40
  ui: {
39
41
  activityBar: "files",
40
42
  },
41
43
  });
42
44
 
43
- /** @type {import("@vue/reactivity").ComputedRef<Tab>} */
44
- export const activeTab = /** @type {import("@vue/reactivity").ComputedRef<Tab>} */ (
45
- /** @type {unknown} */ (
46
- computed(() =>
47
- workspace.activeTabId ? (workspace.tabs.get(workspace.activeTabId) ?? null) : null,
48
- )
49
- )
50
- );
45
+ export const activeTab = computed(() =>
46
+ workspace.activeTabId ? (workspace.tabs.get(workspace.activeTabId) ?? null) : null,
47
+ ) as unknown as import("@vue/reactivity").ComputedRef<Tab | null>;
51
48
 
52
49
  /**
53
50
  * Open a new tab and make it active.
@@ -63,7 +60,15 @@ export const activeTab = /** @type {import("@vue/reactivity").ComputedRef<Tab>}
63
60
  * }} opts
64
61
  * @returns {Tab}
65
62
  */
66
- export function openTab(opts) {
63
+ export function openTab(opts: {
64
+ id: string;
65
+ documentPath?: string | null;
66
+ fileHandle?: FileSystemFileHandle | null;
67
+ document: Record<string, unknown>;
68
+ frontmatter?: Record<string, unknown>;
69
+ sourceFormat?: string | null;
70
+ capabilities?: { modes?: string[] };
71
+ }) {
67
72
  const tab = createTab(opts);
68
73
  workspace.tabs.set(tab.id, tab);
69
74
  workspace.tabOrder.push(tab.id);
@@ -76,7 +81,7 @@ export function openTab(opts) {
76
81
  *
77
82
  * @param {string} tabId
78
83
  */
79
- export function closeTab(tabId) {
84
+ export function closeTab(tabId: string) {
80
85
  const tab = workspace.tabs.get(tabId);
81
86
  if (!tab) return;
82
87
  disposeTab(tab);
@@ -111,7 +116,13 @@ export function closeAllTabs() {
111
116
  * }} newTabOpts
112
117
  * @returns {import("../tabs/tab.js").Tab}
113
118
  */
114
- export function replaceAllTabs(newTabOpts) {
119
+ export function replaceAllTabs(newTabOpts: {
120
+ id: string;
121
+ documentPath?: string | null;
122
+ document: Record<string, unknown>;
123
+ frontmatter?: Record<string, unknown>;
124
+ sourceFormat?: string | null;
125
+ }) {
115
126
  const oldIds = [...workspace.tabs.keys()];
116
127
  const oldTabs = [...workspace.tabs.values()];
117
128
 
@@ -136,7 +147,7 @@ export function replaceAllTabs(newTabOpts) {
136
147
  *
137
148
  * @param {string} tabId
138
149
  */
139
- export function activateTab(tabId) {
150
+ export function activateTab(tabId: string) {
140
151
  if (workspace.tabs.has(tabId)) workspace.activeTabId = tabId;
141
152
  }
142
153
 
@@ -148,7 +159,7 @@ export function activateTab(tabId) {
148
159
  * @param {string} newId
149
160
  * @param {string} newDocumentPath
150
161
  */
151
- export function renameTab(oldId, newId, newDocumentPath) {
162
+ export function renameTab(oldId: string, newId: string, newDocumentPath: string) {
152
163
  const tab = workspace.tabs.get(oldId);
153
164
  if (!tab) return;
154
165
  tab.id = newId;
package/src/panels/dnd.js DELETED
@@ -1,426 +0,0 @@
1
- /**
2
- * DnD registration functions — extracted from studio.js (Phase 4). Registers drag-and-drop behavior
3
- * on layer rows, component cards, and element cards.
4
- */
5
-
6
- /**
7
- * @typedef {{ element: HTMLElement; input: { clientX: number; clientY: number } }} DragCanDragArgs
8
- *
9
- * @typedef {{ source: { data: Record<string, unknown>; element: HTMLElement } }} DragDropSourceArgs
10
- *
11
- * @typedef {{ self: { data: Record<string, unknown> } }} DragSelfArgs
12
- *
13
- * @typedef {{
14
- * source: { data: Record<string, unknown>; element: HTMLElement };
15
- * location: { current: { dropTargets: { data: Record<string, unknown> }[] } };
16
- * }} DragMonitorDropArgs
17
- */
18
-
19
- import {
20
- draggable,
21
- dropTargetForElements,
22
- monitorForElements,
23
- } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
24
- import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
25
- import {
26
- attachInstruction,
27
- extractInstruction,
28
- } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
29
-
30
- import {
31
- leftPanel,
32
- getNodeAtPath,
33
- parentElementPath,
34
- childIndex,
35
- isAncestor,
36
- renderOnly,
37
- } from "../store.js";
38
- import { transact, transactDoc, mutateMoveNode, mutateInsertNode } from "../tabs/transact.js";
39
- import { activeTab } from "../workspace/workspace.js";
40
- import { view } from "../view.js";
41
- import { componentRegistry, computeRelativePath } from "../files/components.js";
42
- import { renderComponentPreview } from "./stylebook-panel.js";
43
- import { defaultDef, unsafeTags } from "./shared.js";
44
-
45
- /** Register DnD on layer rows — called from left-panel.js after render */
46
- export function registerLayersDnD() {
47
- requestAnimationFrame(() => {
48
- const container = /** @type {HTMLElement | null} */ (
49
- leftPanel?.querySelector(".layers-container")
50
- );
51
- if (!container) return;
52
-
53
- /** @type {NodeListOf<HTMLElement>} */ (container.querySelectorAll("[data-dnd-row]")).forEach(
54
- (row) => {
55
- const rowPath = /** @type {JxPath} */ (
56
- /** @type {string} */ (row.dataset.path)
57
- .split("/")
58
- .map((/** @type {string} */ s) => (/^\d+$/.test(s) ? parseInt(s) : s))
59
- );
60
- const rowDepth = parseInt(/** @type {string} */ (row.dataset.dndDepth)) || 0;
61
- const isVoid = row.hasAttribute("data-dnd-void");
62
- const isExpanded = row.hasAttribute("data-dnd-expanded");
63
-
64
- const cleanup = combine(
65
- draggable({
66
- element: row,
67
- canDrag(/** @type {DragCanDragArgs} */ { element: _el, input }) {
68
- const target = /** @type {HTMLElement} */ (
69
- document.elementFromPoint(input.clientX, input.clientY)
70
- );
71
- if (target?.closest(".layer-actions")) return false;
72
- return true;
73
- },
74
- getInitialData() {
75
- return { type: "tree-node", path: rowPath };
76
- },
77
- onDragStart() {
78
- row.classList.add("dragging");
79
- view.layerDragSourceHeight = row.offsetHeight;
80
- if (isExpanded) {
81
- hideDescendantRows(row, container);
82
- }
83
- },
84
- onDrop() {
85
- row.classList.remove("dragging");
86
- if (isExpanded) {
87
- renderOnly("leftPanel");
88
- }
89
- },
90
- }),
91
- dropTargetForElements({
92
- element: row,
93
- canDrop(/** @type {DragDropSourceArgs} */ { source }) {
94
- const srcPath = /** @type {JxPath | undefined} */ (source.data.path);
95
- if (srcPath && isAncestor(srcPath, rowPath)) return false;
96
- return true;
97
- },
98
- getData(/** @type {any} */ { input, element }) {
99
- return /** @type {Record<string | symbol, unknown>} */ (
100
- attachInstruction(
101
- { path: rowPath },
102
- {
103
- input,
104
- element,
105
- currentLevel: rowDepth,
106
- indentPerLevel: 16,
107
- mode: isExpanded ? "expanded" : "standard",
108
- block: isVoid ? ["make-child"] : [],
109
- },
110
- )
111
- );
112
- },
113
- onDragEnter(/** @type {DragSelfArgs} */ { self }) {
114
- showLayerDropGap(row, self.data, container);
115
- },
116
- onDrag(/** @type {DragSelfArgs} */ { self }) {
117
- showLayerDropGap(row, self.data, container);
118
- },
119
- onDragLeave() {
120
- clearLayerDropGap(container);
121
- },
122
- onDrop() {
123
- clearLayerDropGap(container);
124
- },
125
- }),
126
- );
127
- view.dndCleanups.push(cleanup);
128
- },
129
- );
130
-
131
- // Global monitor
132
- const monitorCleanup = monitorForElements({
133
- onDrop(/** @type {DragMonitorDropArgs} */ { source, location }) {
134
- clearLayerDropGap(container);
135
- const target = location.current.dropTargets[0];
136
- if (!target) return;
137
- const instruction = extractInstruction(target.data);
138
- if (!instruction || instruction.type === "instruction-blocked") return;
139
- const srcData = source.data;
140
- const targetPath = /** @type {JxPath} */ (target.data.path);
141
-
142
- // If the source had children, persist collapse at the new location
143
- const srcRow = srcData.type === "tree-node" && source.element;
144
- const wasExpanded = srcRow && srcRow.hasAttribute("data-dnd-expanded");
145
-
146
- applyDropInstruction(instruction, srcData, targetPath);
147
-
148
- if (wasExpanded) {
149
- const tab = activeTab.value;
150
- const newPath = tab?.session.selection;
151
- if (newPath) {
152
- const collapsed = view._layersCollapsed || (view._layersCollapsed = new Set());
153
- collapsed.add(newPath.join("/"));
154
- }
155
- }
156
- },
157
- });
158
- view.dndCleanups.push(monitorCleanup);
159
- });
160
- }
161
-
162
- /** Register DnD on component rows — called from renderLeftPanel when tab=components */
163
- export function registerComponentsDnD() {
164
- requestAnimationFrame(() => {
165
- const container = /** @type {HTMLElement | null} */ (
166
- leftPanel?.querySelector(".components-section")
167
- );
168
- if (!container) return;
169
-
170
- /** @type {NodeListOf<HTMLElement>} */ (
171
- container.querySelectorAll("[data-component-tag]")
172
- ).forEach((row) => {
173
- const tagName = row.dataset.componentTag;
174
- if (!tagName) return;
175
- const comp = componentRegistry.find(
176
- (/** @type {import("../files/components.js").ComponentEntry} */ c) => c.tagName === tagName,
177
- );
178
- if (!comp) return;
179
-
180
- // Fill preview with live rendered component
181
- const preview = row.querySelector(".element-card-preview");
182
- if (preview && !preview.querySelector(tagName)) {
183
- renderComponentPreview(comp).then((/** @type {HTMLElement} */ el) => {
184
- preview.textContent = "";
185
- preview.appendChild(el);
186
- });
187
- }
188
-
189
- const instanceDef = {
190
- tagName: comp.tagName,
191
- $props: comp.props
192
- ? Object.fromEntries(
193
- comp.props.map(
194
- (/** @type {{ name: string; default?: unknown; [k: string]: unknown }} */ p) => [
195
- p.name,
196
- p.default !== undefined ? p.default : "",
197
- ],
198
- ),
199
- )
200
- : {},
201
- };
202
- const cleanup = draggable({
203
- element: row,
204
- getInitialData() {
205
- return { type: "block", fragment: structuredClone(instanceDef) };
206
- },
207
- });
208
- view.dndCleanups.push(cleanup);
209
- });
210
- });
211
- }
212
-
213
- /** Register DnD on element (HTML block) rows */
214
- export function registerElementsDnD() {
215
- requestAnimationFrame(() => {
216
- const container = /** @type {HTMLElement | null} */ (leftPanel?.querySelector(".panel-body"));
217
- if (!container) return;
218
- /** @type {NodeListOf<HTMLElement>} */ (container.querySelectorAll("[data-block-tag]")).forEach(
219
- (row) => {
220
- const tag = /** @type {string} */ (row.dataset.blockTag);
221
- const preview = row.querySelector(".element-card-preview");
222
- if (preview && !preview.firstChild) {
223
- const el = document.createElement(unsafeTags.has(tag) ? "span" : tag);
224
- el.textContent = tag;
225
- preview.appendChild(el);
226
- }
227
- const def = defaultDef(tag);
228
- const cleanup = draggable({
229
- element: row,
230
- getInitialData() {
231
- return { type: "block", fragment: structuredClone(def) };
232
- },
233
- });
234
- view.dndCleanups.push(cleanup);
235
- },
236
- );
237
- });
238
- }
239
-
240
- /**
241
- * Hide descendant rows of the dragged item so it appears collapsed during drag.
242
- *
243
- * @param {HTMLElement} parentRow
244
- * @param {HTMLElement} container
245
- */
246
- function hideDescendantRows(parentRow, container) {
247
- const prefix = parentRow.dataset.path + "/";
248
- const rows = container.querySelectorAll(".layers-tree .layer-row");
249
- for (const r of rows) {
250
- if (/** @type {HTMLElement} */ (r).dataset.path?.startsWith(prefix)) {
251
- /** @type {HTMLElement} */ (r).style.display = "none";
252
- }
253
- }
254
- }
255
-
256
- /**
257
- * @param {HTMLElement} rowEl
258
- * @param {Record<string, unknown>} data
259
- * @param {HTMLElement} container
260
- */
261
- export function showLayerDropGap(rowEl, data, container) {
262
- const instruction = extractInstruction(data);
263
-
264
- // Clear previous drop-target highlight
265
- if (view._currentDropTargetRow && view._currentDropTargetRow !== rowEl) {
266
- view._currentDropTargetRow.classList.remove("drop-target");
267
- }
268
-
269
- if (!instruction || instruction.type === "instruction-blocked") {
270
- clearLayerDropGap(container);
271
- return;
272
- }
273
-
274
- if (instruction.type === "make-child") {
275
- clearLayerDropGap(container);
276
- rowEl.classList.add("drop-target");
277
- view._currentDropTargetRow = rowEl;
278
- return;
279
- }
280
-
281
- rowEl.classList.remove("drop-target");
282
- view._currentDropTargetRow = rowEl;
283
-
284
- // Shift rows to create gap
285
- const rows = Array.from(container.querySelectorAll(".layers-tree .layer-row"));
286
- const targetIdx = rows.indexOf(rowEl);
287
- const gap = view.layerDragSourceHeight;
288
-
289
- for (let i = 0; i < rows.length; i++) {
290
- if (/** @type {HTMLElement} */ (rows[i]).classList.contains("dragging")) continue;
291
- if (instruction.type === "reorder-above") {
292
- /** @type {HTMLElement} */ (rows[i]).style.transform =
293
- i >= targetIdx ? `translateY(${gap}px)` : "";
294
- } else {
295
- /** @type {HTMLElement} */ (rows[i]).style.transform =
296
- i > targetIdx ? `translateY(${gap}px)` : "";
297
- }
298
- }
299
- }
300
-
301
- /** @param {HTMLElement} container */
302
- export function clearLayerDropGap(container) {
303
- if (view._currentDropTargetRow) {
304
- view._currentDropTargetRow.classList.remove("drop-target");
305
- view._currentDropTargetRow = null;
306
- }
307
- const rows = container.querySelectorAll(".layers-tree .layer-row");
308
- for (const r of rows) /** @type {HTMLElement} */ (r).style.transform = "";
309
- }
310
-
311
- /**
312
- * Apply a DnD instruction to the state
313
- *
314
- * @param {{ type: string }} instruction
315
- * @param {Record<string, unknown>} srcData
316
- * @param {JxPath} targetPath
317
- */
318
- export function applyDropInstruction(instruction, srcData, targetPath) {
319
- const doc = /** @type {JxMutableNode} */ (activeTab.value?.doc.document);
320
- if (srcData.type === "tree-node") {
321
- const fromPath = /** @type {JxPath} */ (srcData.path);
322
- const targetParent = /** @type {JxPath} */ (parentElementPath(targetPath));
323
- const targetIdx = /** @type {number} */ (childIndex(targetPath));
324
-
325
- switch (instruction.type) {
326
- case "reorder-above":
327
- transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetParent, targetIdx));
328
- break;
329
- case "reorder-below":
330
- transactDoc(activeTab.value, (t) =>
331
- mutateMoveNode(t, fromPath, targetParent, targetIdx + 1),
332
- );
333
- break;
334
- case "make-child": {
335
- const target = getNodeAtPath(doc, targetPath);
336
- const len = target?.children?.length || 0;
337
- transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetPath, len));
338
- break;
339
- }
340
- }
341
- } else if (srcData.type === "block") {
342
- const targetParent = /** @type {JxPath} */ (parentElementPath(targetPath));
343
- const targetIdx = /** @type {number} */ (childIndex(targetPath));
344
-
345
- switch (instruction.type) {
346
- case "reorder-above":
347
- transactDoc(activeTab.value, (t) =>
348
- mutateInsertNode(
349
- t,
350
- targetParent,
351
- targetIdx,
352
- structuredClone(/** @type {JxMutableNode} */ (srcData.fragment)),
353
- ),
354
- );
355
- break;
356
- case "reorder-below":
357
- transactDoc(activeTab.value, (t) =>
358
- mutateInsertNode(
359
- t,
360
- targetParent,
361
- targetIdx + 1,
362
- structuredClone(/** @type {JxMutableNode} */ (srcData.fragment)),
363
- ),
364
- );
365
- break;
366
- case "make-child": {
367
- const target = getNodeAtPath(doc, targetPath);
368
- const len = target?.children?.length || 0;
369
- transactDoc(activeTab.value, (t) =>
370
- mutateInsertNode(
371
- t,
372
- targetPath,
373
- len,
374
- structuredClone(/** @type {JxMutableNode} */ (srcData.fragment)),
375
- ),
376
- );
377
- break;
378
- }
379
- }
380
-
381
- // Auto-import to $elements if the dropped block is a custom component
382
- const fragment = /** @type {JxMutableNode | undefined} */ (srcData.fragment);
383
- const tag = fragment?.tagName;
384
- if (tag && tag.includes("-")) {
385
- const comp = componentRegistry.find(
386
- (/** @type {import("../files/components.js").ComponentEntry} */ c) => c.tagName === tag,
387
- );
388
- if (comp) {
389
- const tab = activeTab.value;
390
- const elements = tab?.doc.document?.$elements || [];
391
- if (comp.source === "npm") {
392
- const specifier = comp.modulePath ? `${comp.package}/${comp.modulePath}` : comp.package;
393
- if (!specifier) return;
394
- const alreadyImported = elements.some(
395
- (/** @type {JxMutableNode | string | { $ref: string }} */ e) =>
396
- e === specifier || e === comp.package,
397
- );
398
- if (!alreadyImported) {
399
- transact(activeTab.value, (/** @type {JxMutableNode} */ doc) => {
400
- if (!doc.$elements) doc.$elements = [];
401
- doc.$elements.push(specifier);
402
- });
403
- }
404
- } else {
405
- const alreadyImported = elements.some(
406
- (/** @type {JxMutableNode | string | { $ref: string }} */ e) => {
407
- const ref = typeof e === "object" && e !== null ? e.$ref : undefined;
408
- return (
409
- ref &&
410
- (ref === `./${comp.path}` ||
411
- ref.endsWith(/** @type {string} */ (comp.path.split("/").pop())))
412
- );
413
- },
414
- );
415
- if (!alreadyImported) {
416
- const relPath = computeRelativePath(tab?.documentPath, comp.path);
417
- transact(activeTab.value, (/** @type {JxMutableNode} */ doc) => {
418
- if (!doc.$elements) doc.$elements = [];
419
- doc.$elements.push({ $ref: relPath });
420
- });
421
- }
422
- }
423
- }
424
- }
425
- }
426
- }