@base44/vite-plugin 0.2.25 → 0.2.26

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 (38) hide show
  1. package/dist/injections/layer-dropdown/consts.d.ts +19 -0
  2. package/dist/injections/layer-dropdown/consts.d.ts.map +1 -0
  3. package/dist/injections/layer-dropdown/consts.js +40 -0
  4. package/dist/injections/layer-dropdown/consts.js.map +1 -0
  5. package/dist/injections/layer-dropdown/controller.d.ts +4 -0
  6. package/dist/injections/layer-dropdown/controller.d.ts.map +1 -0
  7. package/dist/injections/layer-dropdown/controller.js +88 -0
  8. package/dist/injections/layer-dropdown/controller.js.map +1 -0
  9. package/dist/injections/layer-dropdown/dropdown-ui.d.ts +13 -0
  10. package/dist/injections/layer-dropdown/dropdown-ui.d.ts.map +1 -0
  11. package/dist/injections/layer-dropdown/dropdown-ui.js +176 -0
  12. package/dist/injections/layer-dropdown/dropdown-ui.js.map +1 -0
  13. package/dist/injections/layer-dropdown/types.d.ts +26 -0
  14. package/dist/injections/layer-dropdown/types.d.ts.map +1 -0
  15. package/dist/injections/layer-dropdown/types.js +3 -0
  16. package/dist/injections/layer-dropdown/types.js.map +1 -0
  17. package/dist/injections/layer-dropdown/utils.d.ts +25 -0
  18. package/dist/injections/layer-dropdown/utils.d.ts.map +1 -0
  19. package/dist/injections/layer-dropdown/utils.js +143 -0
  20. package/dist/injections/layer-dropdown/utils.js.map +1 -0
  21. package/dist/injections/utils.d.ts +4 -0
  22. package/dist/injections/utils.d.ts.map +1 -1
  23. package/dist/injections/utils.js +12 -0
  24. package/dist/injections/utils.js.map +1 -1
  25. package/dist/injections/visual-edit-agent.d.ts.map +1 -1
  26. package/dist/injections/visual-edit-agent.js +87 -72
  27. package/dist/injections/visual-edit-agent.js.map +1 -1
  28. package/dist/statics/index.mjs +1 -1
  29. package/dist/statics/index.mjs.map +1 -1
  30. package/package.json +1 -1
  31. package/src/injections/layer-dropdown/LAYERS.md +258 -0
  32. package/src/injections/layer-dropdown/consts.ts +49 -0
  33. package/src/injections/layer-dropdown/controller.ts +109 -0
  34. package/src/injections/layer-dropdown/dropdown-ui.ts +230 -0
  35. package/src/injections/layer-dropdown/types.ts +30 -0
  36. package/src/injections/layer-dropdown/utils.ts +175 -0
  37. package/src/injections/utils.ts +18 -0
  38. package/src/injections/visual-edit-agent.ts +98 -83
@@ -0,0 +1,175 @@
1
+ /** DOM utilities for the layer-dropdown module */
2
+
3
+ import { isInstrumentedElement, getElementSelectorId } from "../utils.js";
4
+ import { MAX_PARENT_DEPTH, MAX_CHILD_DEPTH } from "./consts.js";
5
+
6
+ import type { LayerInfo } from "./types.js";
7
+
8
+ /** Apply a style map to an element */
9
+ export function applyStyles(element: HTMLElement, styles: Record<string, string>): void {
10
+ for (const key of Object.keys(styles)) {
11
+ element.style.setProperty(
12
+ key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`),
13
+ styles[key]!
14
+ );
15
+ }
16
+ }
17
+
18
+ /** Display name for a layer — just the real tag name */
19
+ export function getLayerDisplayName(layer: LayerInfo): string {
20
+ return layer.tagName;
21
+ }
22
+
23
+ function toLayerInfo(element: Element, depth?: number): LayerInfo {
24
+ const info: LayerInfo = {
25
+ element,
26
+ tagName: element.tagName.toLowerCase(),
27
+ selectorId: getElementSelectorId(element),
28
+ };
29
+ if (depth !== undefined) info.depth = depth;
30
+ return info;
31
+ }
32
+
33
+ /**
34
+ * Collect instrumented descendants up to `maxDepth` instrumented nesting levels.
35
+ * Non-instrumented wrappers are walked through without counting toward depth.
36
+ * Results are in DOM order.
37
+ * When `startDepth` is provided, assigns `depth` to each item during collection.
38
+ */
39
+ export function getInstrumentedDescendants(
40
+ parent: Element,
41
+ maxDepth: number,
42
+ startDepth?: number
43
+ ): LayerInfo[] {
44
+ const result: LayerInfo[] = [];
45
+
46
+ function walk(el: Element, instrDepth: number): void {
47
+ if (instrDepth > maxDepth) return;
48
+ for (let i = 0; i < el.children.length; i++) {
49
+ const child = el.children[i]!;
50
+ if (isInstrumentedElement(child)) {
51
+ const info: LayerInfo = {
52
+ element: child,
53
+ tagName: child.tagName.toLowerCase(),
54
+ selectorId: getElementSelectorId(child),
55
+ };
56
+ if (startDepth !== undefined) {
57
+ info.depth = startDepth + instrDepth - 1;
58
+ }
59
+ result.push(info);
60
+ walk(child, instrDepth + 1);
61
+ } else {
62
+ walk(child, instrDepth);
63
+ }
64
+ }
65
+ }
66
+
67
+ walk(parent, 1);
68
+ return result;
69
+ }
70
+
71
+ /** Collect instrumented ancestors from selected element up to MAX_PARENT_DEPTH (outermost first). */
72
+ function collectInstrumentedParents(selectedElement: Element): LayerInfo[] {
73
+ const parents: LayerInfo[] = [];
74
+ let current = selectedElement.parentElement;
75
+ while (
76
+ current &&
77
+ current !== document.documentElement &&
78
+ current !== document.body &&
79
+ parents.length < MAX_PARENT_DEPTH
80
+ ) {
81
+ if (isInstrumentedElement(current)) {
82
+ parents.push(toLayerInfo(current));
83
+ }
84
+ current = current.parentElement;
85
+ }
86
+ parents.reverse();
87
+ return parents;
88
+ }
89
+
90
+ /** Add parents to chain with depth 0, 1, …; returns depth of selected (parents.length). */
91
+ function addParentsToChain(chain: LayerInfo[], parents: LayerInfo[]): number {
92
+ parents.forEach((p, i) => {
93
+ chain.push({ ...p, depth: i });
94
+ });
95
+ return parents.length;
96
+ }
97
+
98
+ /** Add selected element and its descendants at the given depth. */
99
+ function addSelfAndDescendantsToChain(
100
+ chain: LayerInfo[],
101
+ selectedElement: Element,
102
+ selfDepth: number
103
+ ): void {
104
+ chain.push(toLayerInfo(selectedElement, selfDepth));
105
+ const descendants = getInstrumentedDescendants(
106
+ selectedElement,
107
+ MAX_CHILD_DEPTH,
108
+ selfDepth + 1
109
+ );
110
+ chain.push(...descendants);
111
+ }
112
+
113
+ /** Get the innermost instrumented parent's DOM element, or null if none. */
114
+ function getImmediateInstrParent(parents: LayerInfo[]): Element | null {
115
+ return parents.at(-1)?.element ?? null;
116
+ }
117
+
118
+ /** Collect instrumented siblings of the selected element from its parent (DOM order). */
119
+ function collectSiblings(parent: Element, selectedElement: Element): LayerInfo[] {
120
+ const siblings = getInstrumentedDescendants(parent, 1);
121
+ if (!siblings.some((s) => s.element === selectedElement)) {
122
+ siblings.push(toLayerInfo(selectedElement));
123
+ }
124
+ return siblings;
125
+ }
126
+
127
+ /** Add siblings at selfDepth, expanding children only for the selected element. */
128
+ function appendSiblingsWithSelected(
129
+ chain: LayerInfo[],
130
+ siblings: LayerInfo[],
131
+ selectedElement: Element,
132
+ selfDepth: number
133
+ ): void {
134
+ const selectedSelectorId = getElementSelectorId(selectedElement);
135
+ const seen = new Set<string>();
136
+ for (const sibling of siblings) {
137
+ if (sibling.element === selectedElement) {
138
+ addSelfAndDescendantsToChain(chain, selectedElement, selfDepth);
139
+ if (selectedSelectorId) seen.add(selectedSelectorId);
140
+ } else {
141
+ const id = sibling.selectorId;
142
+ if (id != null) {
143
+ if (id === selectedSelectorId || seen.has(id)) continue;
144
+ seen.add(id);
145
+ }
146
+ chain.push({ ...sibling, depth: selfDepth });
147
+ }
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Build the layer chain for the dropdown:
153
+ *
154
+ * Parents – up to MAX_PARENT_DEPTH instrumented ancestors, outer → inner.
155
+ * Siblings – instrumented children of the immediate parent, at the same depth.
156
+ * Current – the selected element (highlighted), with children expanded.
157
+ * Children – instrumented descendants within MAX_CHILD_DEPTH levels, DOM order.
158
+ *
159
+ * Each item carries a `depth` for visual indentation.
160
+ */
161
+ export function buildLayerChain(selectedElement: Element): LayerInfo[] {
162
+ const parents = collectInstrumentedParents(selectedElement);
163
+ const chain: LayerInfo[] = [];
164
+ const selfDepth = addParentsToChain(chain, parents);
165
+
166
+ const instrParent = getImmediateInstrParent(parents);
167
+ if (instrParent) {
168
+ const siblings = collectSiblings(instrParent, selectedElement);
169
+ appendSiblingsWithSelected(chain, siblings, selectedElement, selfDepth);
170
+ } else {
171
+ addSelfAndDescendantsToChain(chain, selectedElement, selfDepth);
172
+ }
173
+
174
+ return chain;
175
+ }
@@ -1,3 +1,21 @@
1
+ /** Check if an element has instrumentation attributes */
2
+ export function isInstrumentedElement(element: Element): boolean {
3
+ const htmlEl = element as HTMLElement;
4
+ return !!(
5
+ htmlEl.dataset?.sourceLocation || htmlEl.dataset?.visualSelectorId
6
+ );
7
+ }
8
+
9
+ /** Get the selector ID from an element's data attributes (prefers source-location) */
10
+ export function getElementSelectorId(element: Element): string | null {
11
+ const htmlEl = element as HTMLElement;
12
+ return (
13
+ htmlEl.dataset?.sourceLocation ||
14
+ htmlEl.dataset?.visualSelectorId ||
15
+ null
16
+ );
17
+ }
18
+
1
19
  export const ALLOWED_ATTRIBUTES: string[] = ["src"];
2
20
 
3
21
  /** Find elements by ID - first try data-source-location, fallback to data-visual-selector-id */
@@ -1,4 +1,6 @@
1
- import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES } from "./utils.js";
1
+ import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId } from "./utils.js";
2
+ import { createLayerController } from "./layer-dropdown/controller.js";
3
+ import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
2
4
 
3
5
  export function setupVisualEditAgent() {
4
6
  // State variables (replacing React useState/useRef)
@@ -78,6 +80,76 @@ export function setupVisualEditAgent() {
78
80
  currentHighlightedElements = [];
79
81
  };
80
82
 
83
+ const clearSelectedOverlays = () => {
84
+ selectedOverlays.forEach((overlay) => {
85
+ if (overlay && overlay.parentNode) {
86
+ overlay.remove();
87
+ }
88
+ });
89
+ selectedOverlays = [];
90
+ };
91
+
92
+ const TEXT_TAGS = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'a', 'label'];
93
+
94
+ const notifyElementSelected = (element: Element) => {
95
+ const htmlElement = element as HTMLElement;
96
+ const rect = element.getBoundingClientRect();
97
+ const svgElement = element as SVGElement;
98
+ const isTextElement = TEXT_TAGS.includes(element.tagName?.toLowerCase());
99
+ window.parent.postMessage({
100
+ type: "element-selected",
101
+ tagName: element.tagName,
102
+ classes:
103
+ (svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
104
+ element.className ||
105
+ "",
106
+ visualSelectorId: getElementSelectorId(element),
107
+ content: isTextElement ? htmlElement.innerText : undefined,
108
+ dataSourceLocation: htmlElement.dataset.sourceLocation,
109
+ isDynamicContent: htmlElement.dataset.dynamicContent === "true",
110
+ linenumber: htmlElement.dataset.linenumber,
111
+ filename: htmlElement.dataset.filename,
112
+ position: {
113
+ top: rect.top,
114
+ left: rect.left,
115
+ right: rect.right,
116
+ bottom: rect.bottom,
117
+ width: rect.width,
118
+ height: rect.height,
119
+ centerX: rect.left + rect.width / 2,
120
+ centerY: rect.top + rect.height / 2,
121
+ },
122
+ attributes: collectAllowedAttributes(element, ALLOWED_ATTRIBUTES),
123
+ isTextElement,
124
+ }, "*");
125
+ };
126
+
127
+ // Select an element: create overlays, update state, notify parent
128
+ const selectElement = (element: Element): HTMLDivElement | undefined => {
129
+ const visualSelectorId = getElementSelectorId(element);
130
+
131
+ clearSelectedOverlays();
132
+
133
+ const elements = findElementsById(visualSelectorId || null);
134
+ elements.forEach((el) => {
135
+ const overlay = createOverlay(true);
136
+ document.body.appendChild(overlay);
137
+ selectedOverlays.push(overlay);
138
+ positionOverlay(overlay, el, true);
139
+ });
140
+
141
+ selectedElementId = visualSelectorId || null;
142
+ clearHoverOverlays();
143
+ notifyElementSelected(element);
144
+
145
+ return selectedOverlays[0];
146
+ };
147
+
148
+ const notifyDeselection = (): void => {
149
+ selectedElementId = null;
150
+ window.parent.postMessage({ type: "unselect-element" }, "*");
151
+ };
152
+
81
153
  // Handle mouse over event
82
154
  const handleMouseOver = (e: MouseEvent) => {
83
155
  if (!isVisualEditMode || isPopoverDragging) return;
@@ -146,6 +218,9 @@ export function setupVisualEditAgent() {
146
218
 
147
219
  const target = e.target as Element;
148
220
 
221
+ // Let layer dropdown clicks pass through without interference
222
+ if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
223
+
149
224
  // Close dropdowns when clicking anywhere in iframe if a dropdown is open
150
225
  if (isDropdownOpen) {
151
226
  e.preventDefault();
@@ -174,82 +249,13 @@ export function setupVisualEditAgent() {
174
249
  return;
175
250
  }
176
251
 
177
- const htmlElement = element as HTMLElement;
178
- const visualSelectorId =
179
- htmlElement.dataset.sourceLocation ||
180
- htmlElement.dataset.visualSelectorId;
181
-
182
- // Clear any existing selected overlays
183
- selectedOverlays.forEach((overlay) => {
184
- if (overlay && overlay.parentNode) {
185
- overlay.remove();
186
- }
187
- });
188
- selectedOverlays = [];
189
-
190
- // Find all elements with the same ID
191
- const elements = findElementsById(visualSelectorId || null);
192
-
193
- // Create selected overlays for all matching elements
194
- elements.forEach((el) => {
195
- const overlay = createOverlay(true);
196
- document.body.appendChild(overlay);
197
- selectedOverlays.push(overlay);
198
- positionOverlay(overlay, el, true);
199
- });
200
-
201
- selectedElementId = visualSelectorId || null;
202
-
203
- // Clear hover overlays
204
- clearHoverOverlays();
205
-
206
- // Calculate element position for popover positioning
207
- const rect = element.getBoundingClientRect();
208
- const elementPosition = {
209
- top: rect.top,
210
- left: rect.left,
211
- right: rect.right,
212
- bottom: rect.bottom,
213
- width: rect.width,
214
- height: rect.height,
215
- centerX: rect.left + rect.width / 2,
216
- centerY: rect.top + rect.height / 2,
217
- };
218
-
219
- // Collect allowed element attributes
220
- const attributes = collectAllowedAttributes(element, ALLOWED_ATTRIBUTES);
221
- const isTextElement = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'a', 'label'].includes(element.tagName?.toLowerCase());
222
-
223
- // Send message to parent window with element info including position
224
- const svgElement = element as SVGElement;
225
- const elementData = {
226
- type: "element-selected",
227
- tagName: element.tagName,
228
- classes:
229
- (svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
230
- element.className ||
231
- "",
232
- visualSelectorId: visualSelectorId,
233
- content: isTextElement ? (element as HTMLElement).innerText : undefined,
234
- dataSourceLocation: htmlElement.dataset.sourceLocation,
235
- isDynamicContent: htmlElement.dataset.dynamicContent === "true",
236
- linenumber: htmlElement.dataset.linenumber,
237
- filename: htmlElement.dataset.filename,
238
- position: elementPosition,
239
- attributes,
240
- isTextElement: isTextElement,
241
- };
242
- window.parent.postMessage(elementData, "*");
252
+ const selectedOverlay = selectElement(element);
253
+ layerController.attachToOverlay(selectedOverlay, element);
243
254
  };
244
255
 
245
- // Unselect the current element
246
- const unselectElement = () => {
247
- selectedOverlays.forEach((overlay) => {
248
- if (overlay && overlay.parentNode) {
249
- overlay.remove();
250
- }
251
- });
252
- selectedOverlays = [];
256
+ // Clear the current selection
257
+ const clearSelection = () => {
258
+ clearSelectedOverlays();
253
259
  selectedElementId = null;
254
260
  };
255
261
 
@@ -331,19 +337,28 @@ export function setupVisualEditAgent() {
331
337
  }, 50);
332
338
  };
333
339
 
340
+ // --- Layer dropdown controller ---
341
+ const layerController = createLayerController({
342
+ createPreviewOverlay: (element: Element) => {
343
+ const overlay = createOverlay(false);
344
+ overlay.style.zIndex = "9998";
345
+ document.body.appendChild(overlay);
346
+ positionOverlay(overlay, element);
347
+ return overlay;
348
+ },
349
+ getSelectedElementId: () => selectedElementId,
350
+ selectElement,
351
+ onDeselect: notifyDeselection,
352
+ });
353
+
334
354
  // Toggle visual edit mode
335
355
  const toggleVisualEditMode = (isEnabled: boolean) => {
336
356
  isVisualEditMode = isEnabled;
337
357
 
338
358
  if (!isEnabled) {
359
+ layerController.cleanup();
339
360
  clearHoverOverlays();
340
-
341
- selectedOverlays.forEach((overlay) => {
342
- if (overlay && overlay.parentNode) {
343
- overlay.remove();
344
- }
345
- });
346
- selectedOverlays = [];
361
+ clearSelectedOverlays();
347
362
 
348
363
  currentHighlightedElements = [];
349
364
  selectedElementId = null;
@@ -444,7 +459,7 @@ export function setupVisualEditAgent() {
444
459
  break;
445
460
 
446
461
  case "unselect-element":
447
- unselectElement();
462
+ clearSelection();
448
463
  break;
449
464
 
450
465
  case "refresh-page":