@upstart.gg/vite-plugins 0.0.139

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 (63) hide show
  1. package/dist/vite-plugin-upstart-attrs.d.ts +29 -0
  2. package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -0
  3. package/dist/vite-plugin-upstart-attrs.js +286 -0
  4. package/dist/vite-plugin-upstart-attrs.js.map +1 -0
  5. package/dist/vite-plugin-upstart-editor/plugin.d.ts +15 -0
  6. package/dist/vite-plugin-upstart-editor/plugin.d.ts.map +1 -0
  7. package/dist/vite-plugin-upstart-editor/plugin.js +55 -0
  8. package/dist/vite-plugin-upstart-editor/plugin.js.map +1 -0
  9. package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts +12 -0
  10. package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts.map +1 -0
  11. package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +57 -0
  12. package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -0
  13. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts +12 -0
  14. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts.map +1 -0
  15. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +91 -0
  16. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -0
  17. package/dist/vite-plugin-upstart-editor/runtime/index.d.ts +22 -0
  18. package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -0
  19. package/dist/vite-plugin-upstart-editor/runtime/index.js +62 -0
  20. package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -0
  21. package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts +15 -0
  22. package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -0
  23. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +292 -0
  24. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -0
  25. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +126 -0
  26. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -0
  27. package/dist/vite-plugin-upstart-editor/runtime/types.js +1 -0
  28. package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts +15 -0
  29. package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts.map +1 -0
  30. package/dist/vite-plugin-upstart-editor/runtime/utils.js +26 -0
  31. package/dist/vite-plugin-upstart-editor/runtime/utils.js.map +1 -0
  32. package/dist/vite-plugin-upstart-routes.d.ts +20 -0
  33. package/dist/vite-plugin-upstart-routes.d.ts.map +1 -0
  34. package/dist/vite-plugin-upstart-routes.js +143 -0
  35. package/dist/vite-plugin-upstart-routes.js.map +1 -0
  36. package/dist/vite-plugin-upstart-theme.d.ts +22 -0
  37. package/dist/vite-plugin-upstart-theme.d.ts.map +1 -0
  38. package/dist/vite-plugin-upstart-theme.js +180 -0
  39. package/dist/vite-plugin-upstart-theme.js.map +1 -0
  40. package/package.json +63 -0
  41. package/src/tests/fixtures/routes/default-layout.tsx +10 -0
  42. package/src/tests/fixtures/routes/dynamic-route.tsx +10 -0
  43. package/src/tests/fixtures/routes/missing-attributes.tsx +8 -0
  44. package/src/tests/fixtures/routes/missing-path.tsx +9 -0
  45. package/src/tests/fixtures/routes/valid-full.tsx +15 -0
  46. package/src/tests/fixtures/routes/valid-minimal.tsx +10 -0
  47. package/src/tests/fixtures/routes/with-comments.tsx +12 -0
  48. package/src/tests/fixtures/routes/with-nested-objects.tsx +15 -0
  49. package/src/tests/upstart-editor-api.test.ts +367 -0
  50. package/src/tests/vite-plugin-upstart-attrs.test.ts +957 -0
  51. package/src/tests/vite-plugin-upstart-editor.test.ts +81 -0
  52. package/src/tests/vite-plugin-upstart-routes.test.ts +220 -0
  53. package/src/upstart-editor-api.ts +204 -0
  54. package/src/vite-plugin-upstart-attrs.ts +616 -0
  55. package/src/vite-plugin-upstart-editor/PLAN.md +1391 -0
  56. package/src/vite-plugin-upstart-editor/plugin.ts +73 -0
  57. package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +80 -0
  58. package/src/vite-plugin-upstart-editor/runtime/hover-overlay.ts +135 -0
  59. package/src/vite-plugin-upstart-editor/runtime/index.ts +90 -0
  60. package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +401 -0
  61. package/src/vite-plugin-upstart-editor/runtime/types.ts +120 -0
  62. package/src/vite-plugin-upstart-editor/runtime/utils.ts +34 -0
  63. package/src/vite-plugin-upstart-theme.ts +321 -0
@@ -0,0 +1,29 @@
1
+ import * as unplugin2 from "unplugin";
2
+ import * as magic_string0 from "magic-string";
3
+
4
+ //#region src/vite-plugin-upstart-attrs.d.ts
5
+ interface Options {
6
+ enabled: boolean;
7
+ emitRegistry?: boolean;
8
+ }
9
+ interface EditableEntry {
10
+ file: string;
11
+ type: "text" | "className";
12
+ startOffset: number;
13
+ endOffset: number;
14
+ originalContent: string;
15
+ context: {
16
+ parentTag: string;
17
+ };
18
+ }
19
+ declare function getRegistry(): Record<string, EditableEntry>;
20
+ declare function clearRegistry(): void;
21
+ declare const upstartEditor: unplugin2.UnpluginInstance<Options, boolean>;
22
+ declare function transformWithOxc(code: string, filePath: string): {
23
+ code: string;
24
+ map: magic_string0.SourceMap;
25
+ } | null;
26
+ declare const _default: (options: Options) => unplugin2.VitePlugin<any> | unplugin2.VitePlugin<any>[];
27
+ //#endregion
28
+ export { EditableEntry, clearRegistry, _default as default, getRegistry, transformWithOxc, upstartEditor };
29
+ //# sourceMappingURL=vite-plugin-upstart-attrs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin-upstart-attrs.d.ts","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"sourcesContent":[],"mappings":";;;;UAgBU,OAAA;;;;AAAA,UA6BO,aAAA,CA7BA;EA6BA,IAAA,EAAA,MAAA;EAoBD,IAAA,EAAA,MAAA,GAAW,WAAmB;EAK9B,WAAA,EAAA,MAAa;EAWhB,SAAA,EAAA,MAkEX;EAEc,eAAA,EAAA,MAAgB;EAmL/B,OAAA,EAAA;;;;iBAvQe,WAAA,CAAA,GAAe,eAAe;iBAK9B,aAAA,CAAA;cAWH,eAAa,SAAA,CAAA,iBAAA;iBAoEV,gBAAA;;OAAgB,aAAA,CAAA;;cAmL/B"}
@@ -0,0 +1,286 @@
1
+ import { createUnplugin } from "unplugin";
2
+ import { parseSync } from "oxc-parser";
3
+ import { walk } from "zimmerframe";
4
+ import MagicString from "magic-string";
5
+ import path from "node:path";
6
+
7
+ //#region src/vite-plugin-upstart-attrs.ts
8
+ function hasRange(node) {
9
+ return node && typeof node.start === "number" && typeof node.end === "number";
10
+ }
11
+ function hashContent(content) {
12
+ let hash = 5381;
13
+ for (let i = 0; i < content.length; i++) hash = (hash << 5) + hash ^ content.charCodeAt(i);
14
+ return (hash >>> 0).toString(16);
15
+ }
16
+ const editableRegistry = /* @__PURE__ */ new Map();
17
+ function generateId(filePath, node) {
18
+ return `${filePath}:${node.start}`;
19
+ }
20
+ function getRegistry() {
21
+ return Object.fromEntries(editableRegistry);
22
+ }
23
+ function clearRegistry() {
24
+ editableRegistry.clear();
25
+ }
26
+ const upstartEditor = createUnplugin((options) => {
27
+ if (!options.enabled) return { name: "upstart-editor-disabled" };
28
+ const emitRegistry = options.emitRegistry ?? true;
29
+ let root = process.cwd();
30
+ return {
31
+ name: "upstart-editor",
32
+ enforce: "pre",
33
+ vite: {
34
+ configResolved(config) {
35
+ root = config.root;
36
+ },
37
+ generateBundle() {
38
+ if (!emitRegistry || editableRegistry.size === 0) return;
39
+ const registry = {
40
+ version: 1,
41
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
42
+ elements: Object.fromEntries(editableRegistry)
43
+ };
44
+ this.emitFile({
45
+ type: "asset",
46
+ fileName: "upstart-registry.json",
47
+ source: JSON.stringify(registry, null, 2)
48
+ });
49
+ editableRegistry.clear();
50
+ }
51
+ },
52
+ transformInclude(id) {
53
+ return /\.(tsx|jsx)$/.test(id) && !id.includes("node_modules");
54
+ },
55
+ transform(code, id) {
56
+ if (!code.includes("<")) return null;
57
+ try {
58
+ const result = transformWithOxc(code, path.relative(root, id));
59
+ if (!result) return null;
60
+ return {
61
+ code: result.code,
62
+ map: result.map
63
+ };
64
+ } catch (error) {
65
+ console.error(`Error transforming ${id}:`, error);
66
+ return null;
67
+ }
68
+ }
69
+ };
70
+ });
71
+ function transformWithOxc(code, filePath) {
72
+ const ast = parseSync(filePath, code, { sourceType: "module" });
73
+ if (!ast.program) return null;
74
+ const s = new MagicString(code);
75
+ const state = {
76
+ filePath,
77
+ code,
78
+ s,
79
+ loopStack: []
80
+ };
81
+ let modified = false;
82
+ walk(ast.program, state, { _(node, { state: state$1, next }) {
83
+ if (node.type === "CallExpression") {
84
+ const loopInfo = detectMapCall(node, state$1.code);
85
+ if (loopInfo) {
86
+ state$1.loopStack.push(loopInfo);
87
+ next();
88
+ state$1.loopStack.pop();
89
+ return;
90
+ }
91
+ }
92
+ if (node.type === "JSXElement") {
93
+ const jsxNode = node;
94
+ const opening = jsxNode.openingElement;
95
+ const tagName = getJSXElementName(opening);
96
+ const insertPos = getAttributeInsertPosition(opening, state$1.code);
97
+ const attributes = [];
98
+ if (hasRange(jsxNode)) {
99
+ const hash = hashContent(state$1.code.slice(jsxNode.start, jsxNode.end));
100
+ attributes.push(`data-upstart-hash="${hash}"`);
101
+ }
102
+ if (isTextLeafElement(jsxNode)) {
103
+ attributes.push("data-upstart-editable-text=\"true\"");
104
+ const textChild = jsxNode.children.find((c) => c.type === "JSXText" && c.value?.trim());
105
+ if (textChild && hasRange(textChild)) {
106
+ const id = generateId(state$1.filePath, textChild);
107
+ const textValue = textChild.value;
108
+ const trimmedStart = textChild.start + (textValue.length - textValue.trimStart().length);
109
+ const trimmedEnd = textChild.end - (textValue.length - textValue.trimEnd().length);
110
+ editableRegistry.set(id, {
111
+ file: state$1.filePath,
112
+ type: "text",
113
+ startOffset: trimmedStart,
114
+ endOffset: trimmedEnd,
115
+ originalContent: textValue.trim(),
116
+ context: { parentTag: tagName || "unknown" }
117
+ });
118
+ attributes.push(`data-upstart-id="${id}"`);
119
+ }
120
+ }
121
+ const classNameAttr = opening.attributes.find((attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === "className" && attr.value?.type === "Literal" && typeof attr.value.value === "string");
122
+ if (classNameAttr && classNameAttr.value && hasRange(classNameAttr.value)) {
123
+ const id = generateId(state$1.filePath, classNameAttr.value);
124
+ const classValue = classNameAttr.value.value;
125
+ editableRegistry.set(id, {
126
+ file: state$1.filePath,
127
+ type: "className",
128
+ startOffset: classNameAttr.value.start + 1,
129
+ endOffset: classNameAttr.value.end - 1,
130
+ originalContent: classValue,
131
+ context: { parentTag: tagName || "unknown" }
132
+ });
133
+ attributes.push(`data-upstart-classname-id="${id}"`);
134
+ }
135
+ if (tagName && /^[A-Z]/.test(tagName)) {
136
+ attributes.push(`data-upstart-file="${escapeProp(state$1.filePath)}"`);
137
+ attributes.push(`data-upstart-component="${escapeProp(tagName)}"`);
138
+ if (state$1.loopStack.length > 0) {
139
+ const loop = state$1.loopStack[state$1.loopStack.length - 1];
140
+ attributes.push(`data-upstart-loop-item="${escapeProp(loop.itemName)}"`);
141
+ if (loop.indexName) attributes.push(`data-upstart-loop-index={${loop.indexName.toLowerCase()}}`);
142
+ attributes.push(`data-upstart-loop-array="${escapeProp(loop.arrayExpr)}"`);
143
+ }
144
+ for (const attr of opening.attributes) {
145
+ if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") continue;
146
+ const propName = attr.name.name;
147
+ const binding = analyzeBinding(attr.value, state$1.code);
148
+ if (binding) {
149
+ attributes.push(`data-upstart-prop-${propName.toLowerCase()}="${escapeProp(binding.path)}"`);
150
+ if (binding.datasource) attributes.push(`data-upstart-datasource-${propName.toLowerCase()}="${escapeProp(binding.datasource)}"`);
151
+ if (binding.recordId) attributes.push(`data-upstart-record-id-${propName.toLowerCase()}={${binding.recordId}}`);
152
+ if (binding.isConditional) attributes.push(`data-upstart-conditional-${propName.toLowerCase()}="true"`);
153
+ }
154
+ }
155
+ }
156
+ if (insertPos !== -1 && attributes.length > 0) {
157
+ const attrString = " " + attributes.join(" ");
158
+ state$1.s.appendLeft(insertPos, attrString);
159
+ modified = true;
160
+ }
161
+ }
162
+ next();
163
+ } });
164
+ if (!modified) return null;
165
+ return {
166
+ code: s.toString(),
167
+ map: s.generateMap({ hires: true })
168
+ };
169
+ }
170
+ function getJSXElementName(opening) {
171
+ if (opening.name.type === "JSXIdentifier") return opening.name.name;
172
+ if (opening.name.type === "JSXMemberExpression") {
173
+ let current = opening.name;
174
+ while (current.property) {
175
+ if (current.property.type === "JSXIdentifier") return current.property.name;
176
+ if (current.type === "JSXMemberExpression") current = current.property;
177
+ else break;
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+ function getAttributeInsertPosition(opening, code) {
183
+ if (opening.attributes.length > 0) {
184
+ const firstAttr = opening.attributes[0];
185
+ if (hasRange(firstAttr)) return firstAttr.start;
186
+ }
187
+ if (opening.name.type === "JSXIdentifier" && hasRange(opening.name)) return opening.name.end;
188
+ if (opening.name.type === "JSXMemberExpression" && hasRange(opening.name)) return opening.name.end;
189
+ return -1;
190
+ }
191
+ function analyzeBinding(value, code) {
192
+ if (!value) return null;
193
+ if (value.type === "JSXExpressionContainer") {
194
+ const expr = value.expression;
195
+ if (expr.type === "JSXEmptyExpression") return null;
196
+ return analyzeExpression(expr, code);
197
+ }
198
+ return null;
199
+ }
200
+ function analyzeExpression(expr, code) {
201
+ if (expr.type === "MemberExpression") return {
202
+ path: exprToString(expr, code),
203
+ datasource: extractDatasource(expr),
204
+ recordId: extractRecordId(expr)
205
+ };
206
+ if (expr.type === "Identifier") return { path: expr.name };
207
+ if (expr.type === "ConditionalExpression") {
208
+ const consequent = analyzeExpression(expr.consequent, code);
209
+ if (consequent) return {
210
+ ...consequent,
211
+ isConditional: true,
212
+ path: exprToString(expr, code)
213
+ };
214
+ }
215
+ if (expr.type === "LogicalExpression") {
216
+ const right = analyzeExpression(expr.right, code);
217
+ if (right) return {
218
+ ...right,
219
+ isConditional: true,
220
+ path: exprToString(expr, code)
221
+ };
222
+ }
223
+ const exprWithRange = expr;
224
+ if (exprWithRange.start !== void 0 && exprWithRange.end !== void 0) return { path: code.slice(exprWithRange.start, exprWithRange.end) };
225
+ return null;
226
+ }
227
+ function exprToString(expr, code) {
228
+ const exprWithRange = expr;
229
+ if (exprWithRange.start !== void 0 && exprWithRange.end !== void 0) return code.slice(exprWithRange.start, exprWithRange.end);
230
+ if (expr.type === "Identifier") return expr.name;
231
+ if (expr.type === "MemberExpression") {
232
+ const obj = exprToString(expr.object, code);
233
+ const prop = expr.property.type === "Identifier" && !expr.computed ? expr.property.name : exprToString(expr.property, code);
234
+ return expr.computed ? `${obj}[${prop}]` : `${obj}.${prop}`;
235
+ }
236
+ if (expr.type === "ConditionalExpression") return `${exprToString(expr.test, code)} ? ${exprToString(expr.consequent, code)} : ${exprToString(expr.alternate, code)}`;
237
+ if (expr.type === "LogicalExpression") {
238
+ const left = exprToString(expr.left, code);
239
+ const right = exprToString(expr.right, code);
240
+ return `${left} ${expr.operator} ${right}`;
241
+ }
242
+ return "";
243
+ }
244
+ function extractDatasource(expr) {
245
+ let current = expr.object;
246
+ while (current.type === "MemberExpression") current = current.object;
247
+ if (current.type === "Identifier") return current.name;
248
+ }
249
+ function extractRecordId(expr) {
250
+ const obj = expr.object;
251
+ if (obj.type === "Identifier") return `${obj.name}.id`;
252
+ if (obj.type === "MemberExpression") {
253
+ const datasource = extractDatasource(obj);
254
+ if (datasource) return `${datasource}.id`;
255
+ }
256
+ }
257
+ function detectMapCall(node, code) {
258
+ if (node.callee.type !== "MemberExpression" || node.callee.property.type !== "Identifier" || node.callee.property.name !== "map") return null;
259
+ const callback = node.arguments[0];
260
+ if (!callback) return null;
261
+ if (callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression") return null;
262
+ const params = callback.params;
263
+ const itemParam = params[0];
264
+ const indexParam = params[1];
265
+ if (!itemParam) return null;
266
+ return {
267
+ itemName: itemParam.type === "Identifier" ? itemParam.name : "item",
268
+ indexName: indexParam?.type === "Identifier" ? indexParam.name : null,
269
+ arrayExpr: exprToString(node.callee.object, code)
270
+ };
271
+ }
272
+ function escapeProp(value) {
273
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
274
+ }
275
+ function isTextLeafElement(jsxElement) {
276
+ let hasText = false;
277
+ for (const child of jsxElement.children) if (child.type === "JSXText") {
278
+ if (child.value?.trim()) hasText = true;
279
+ } else if (child.type === "JSXElement" || child.type === "JSXFragment" || child.type === "JSXExpressionContainer" || child.type === "JSXSpreadChild") return false;
280
+ return hasText;
281
+ }
282
+ var vite_plugin_upstart_attrs_default = upstartEditor.vite;
283
+
284
+ //#endregion
285
+ export { clearRegistry, vite_plugin_upstart_attrs_default as default, getRegistry, transformWithOxc, upstartEditor };
286
+ //# sourceMappingURL=vite-plugin-upstart-attrs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin-upstart-attrs.js","names":["state: TransformState","state","attributes: string[]","current: Expression"],"sources":["../src/vite-plugin-upstart-attrs.ts"],"sourcesContent":["import { createUnplugin } from \"unplugin\";\nimport { parseSync } from \"oxc-parser\";\nimport { walk } from \"zimmerframe\";\nimport MagicString from \"magic-string\";\nimport path from \"path\";\nimport type {\n Program,\n Node,\n JSXElement,\n JSXOpeningElement,\n JSXAttribute,\n Expression,\n MemberExpression,\n CallExpression,\n} from \"estree-jsx\";\n\ninterface Options {\n enabled: boolean;\n emitRegistry?: boolean;\n}\n\ntype NodeWithRange = Node & { start: number; end: number };\n\nfunction hasRange(node: any): node is NodeWithRange {\n return node && typeof node.start === \"number\" && typeof node.end === \"number\";\n}\n\n// Fast, stable hash function (djb2 variant)\n// Produces a short hex string that's stable across rebuilds\nfunction hashContent(content: string): string {\n let hash = 5381;\n for (let i = 0; i < content.length; i++) {\n hash = ((hash << 5) + hash) ^ content.charCodeAt(i);\n }\n // Convert to unsigned 32-bit and then to hex\n return (hash >>> 0).toString(16);\n}\n\ninterface LoopContext {\n itemName: string;\n indexName: string | null;\n arrayExpr: string;\n}\n\n// Registry entry for editable elements (text and className)\nexport interface EditableEntry {\n file: string;\n type: \"text\" | \"className\";\n startOffset: number;\n endOffset: number;\n originalContent: string;\n context: { parentTag: string };\n}\n\n// Module-level registry (collected across all files during build)\nconst editableRegistry = new Map<string, EditableEntry>();\n\n// Generate stable ID for editable elements\n// Replace the counter-based ID generation with position-based\nfunction generateId(filePath: string, node: NodeWithRange): string {\n // Use file path + start position for a stable, deterministic ID\n return `${filePath}:${node.start}`;\n}\n\n// Export for testing - get a copy of the current registry\nexport function getRegistry(): Record<string, EditableEntry> {\n return Object.fromEntries(editableRegistry);\n}\n\n// Export for testing - clear registry and counters\nexport function clearRegistry(): void {\n editableRegistry.clear();\n}\n\ninterface TransformState {\n filePath: string;\n code: string;\n s: MagicString;\n loopStack: LoopContext[];\n}\n\nexport const upstartEditor = createUnplugin<Options>((options) => {\n if (!options.enabled) {\n return { name: \"upstart-editor-disabled\" };\n }\n\n const emitRegistry = options.emitRegistry ?? true;\n let root = process.cwd();\n\n return {\n name: \"upstart-editor\",\n enforce: \"pre\",\n\n vite: {\n configResolved(config) {\n root = config.root;\n },\n generateBundle() {\n if (!emitRegistry || editableRegistry.size === 0) {\n return;\n }\n\n const registry = {\n version: 1,\n generatedAt: new Date().toISOString(),\n elements: Object.fromEntries(editableRegistry),\n };\n\n this.emitFile({\n type: \"asset\",\n fileName: \"upstart-registry.json\",\n source: JSON.stringify(registry, null, 2),\n });\n\n // Clear for next build\n editableRegistry.clear();\n },\n },\n\n transformInclude(id) {\n return /\\.(tsx|jsx)$/.test(id) && !id.includes(\"node_modules\");\n },\n\n transform(code, id) {\n // Fast path: skip files without JSX\n if (!code.includes(\"<\")) {\n return null;\n }\n\n try {\n const relativePath = path.relative(root, id);\n const result = transformWithOxc(code, relativePath);\n\n if (!result) {\n return null;\n }\n\n return {\n code: result.code,\n map: result.map,\n };\n } catch (error) {\n console.error(`Error transforming ${id}:`, error);\n return null;\n }\n },\n };\n});\n\nexport function transformWithOxc(code: string, filePath: string) {\n // Parse with oxc (super fast!)\n const ast = parseSync(filePath, code, {\n sourceType: \"module\",\n });\n\n if (!ast.program) {\n return null;\n }\n\n const s = new MagicString(code);\n const state: TransformState = {\n filePath,\n code,\n s,\n loopStack: [],\n };\n\n let modified = false;\n\n // Use zimmerframe to walk and transform the AST\n walk(ast.program as Program, state, {\n _(node: Node, { state, next }: { state: TransformState; next: () => void }) {\n // Handle .map() calls to track loops (must be before JSXElement)\n if (node.type === \"CallExpression\") {\n const loopInfo = detectMapCall(node as CallExpression, state.code);\n\n if (loopInfo) {\n // Push loop context\n state.loopStack.push(loopInfo);\n\n // Continue walking into the callback\n next();\n\n // Pop loop context after traversing\n state.loopStack.pop();\n return;\n }\n }\n\n // Handle JSX elements\n if (node.type === \"JSXElement\") {\n const jsxNode = node as JSXElement;\n const opening = jsxNode.openingElement;\n const tagName = getJSXElementName(opening);\n const insertPos = getAttributeInsertPosition(opening, state.code);\n\n // Track attributes to inject\n const attributes: string[] = [];\n\n // Compute stable hash from the element's source code (includes all children)\n // This hash changes if ANY part of the element or its children change\n if (hasRange(jsxNode)) {\n const elementSource = state.code.slice(jsxNode.start, jsxNode.end);\n const hash = hashContent(elementSource);\n attributes.push(`data-upstart-hash=\"${hash}\"`);\n }\n\n // Check for text leaf elements (any element with only static text)\n if (isTextLeafElement(jsxNode)) {\n attributes.push('data-upstart-editable-text=\"true\"');\n\n // Find the actual JSXText node to track its location\n const textChild = jsxNode.children.find((c) => c.type === \"JSXText\" && (c as any).value?.trim());\n\n if (textChild && hasRange(textChild)) {\n const id = generateId(state.filePath, textChild);\n const textValue = (textChild as any).value as string;\n\n // Calculate trimmed offsets (exclude leading/trailing whitespace)\n const trimmedStart = textChild.start + (textValue.length - textValue.trimStart().length);\n const trimmedEnd = textChild.end - (textValue.length - textValue.trimEnd().length);\n\n editableRegistry.set(id, {\n file: state.filePath,\n type: \"text\",\n startOffset: trimmedStart,\n endOffset: trimmedEnd,\n originalContent: textValue.trim(),\n context: { parentTag: tagName || \"unknown\" },\n });\n\n attributes.push(`data-upstart-id=\"${id}\"`);\n }\n }\n\n // Track className attribute if it's a string literal\n const classNameAttr = opening.attributes.find(\n (attr): attr is JSXAttribute =>\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n attr.name.name === \"className\" &&\n attr.value?.type === \"Literal\" &&\n typeof (attr.value as any).value === \"string\",\n );\n\n if (classNameAttr && classNameAttr.value && hasRange(classNameAttr.value)) {\n const id = generateId(state.filePath, classNameAttr.value);\n const classValue = (classNameAttr.value as any).value as string;\n\n // +1 and -1 to exclude the quotes\n editableRegistry.set(id, {\n file: state.filePath,\n type: \"className\",\n startOffset: classNameAttr.value.start + 1,\n endOffset: classNameAttr.value.end - 1,\n originalContent: classValue,\n context: { parentTag: tagName || \"unknown\" },\n });\n\n attributes.push(`data-upstart-classname-id=\"${id}\"`);\n }\n\n // Process PascalCase components for additional tracking\n if (tagName && /^[A-Z]/.test(tagName)) {\n // File and component tracking\n attributes.push(`data-upstart-file=\"${escapeProp(state.filePath)}\"`);\n attributes.push(`data-upstart-component=\"${escapeProp(tagName)}\"`);\n\n // Loop context tracking\n if (state.loopStack.length > 0) {\n const loop = state.loopStack[state.loopStack.length - 1];\n attributes.push(`data-upstart-loop-item=\"${escapeProp(loop.itemName)}\"`);\n if (loop.indexName) {\n attributes.push(`data-upstart-loop-index={${loop.indexName.toLowerCase()}}`);\n }\n attributes.push(`data-upstart-loop-array=\"${escapeProp(loop.arrayExpr)}\"`);\n }\n\n // Analyze each prop for data bindings\n for (const attr of opening.attributes) {\n if (attr.type !== \"JSXAttribute\" || attr.name.type !== \"JSXIdentifier\") {\n continue;\n }\n\n const propName = attr.name.name;\n const binding = analyzeBinding(attr.value, state.code);\n\n if (binding) {\n attributes.push(`data-upstart-prop-${propName.toLowerCase()}=\"${escapeProp(binding.path)}\"`);\n\n if (binding.datasource) {\n attributes.push(\n `data-upstart-datasource-${propName.toLowerCase()}=\"${escapeProp(binding.datasource)}\"`,\n );\n }\n\n if (binding.recordId) {\n attributes.push(`data-upstart-record-id-${propName.toLowerCase()}={${binding.recordId}}`);\n }\n\n // Track conditional expressions\n if (binding.isConditional) {\n attributes.push(`data-upstart-conditional-${propName.toLowerCase()}=\"true\"`);\n }\n }\n }\n }\n\n // Inject attributes if any\n if (insertPos !== -1 && attributes.length > 0) {\n const attrString = \" \" + attributes.join(\" \");\n state.s.appendLeft(insertPos, attrString);\n modified = true;\n }\n }\n\n next();\n },\n });\n\n if (!modified) {\n return null;\n }\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: true }),\n };\n}\n\n// Helper: Get JSX element name\nfunction getJSXElementName(opening: JSXOpeningElement): string | null {\n if (opening.name.type === \"JSXIdentifier\") {\n return opening.name.name;\n }\n\n // Handle JSXMemberExpression like <Foo.Bar>\n if (opening.name.type === \"JSXMemberExpression\") {\n let current = opening.name;\n while (current.property) {\n if (current.property.type === \"JSXIdentifier\") {\n return current.property.name;\n }\n if (current.type === \"JSXMemberExpression\") {\n current = current.property as any;\n } else {\n break;\n }\n }\n }\n\n return null;\n}\n\n// Helper: Find where to insert attributes in JSX opening tag\nfunction getAttributeInsertPosition(opening: JSXOpeningElement, code: string): number {\n // If there are existing attributes, insert before the first one\n if (opening.attributes.length > 0) {\n const firstAttr = opening.attributes[0];\n if (hasRange(firstAttr)) {\n return firstAttr.start;\n }\n }\n\n // Otherwise, insert after the tag name\n if (opening.name.type === \"JSXIdentifier\" && hasRange(opening.name)) {\n return opening.name.end;\n }\n\n if (opening.name.type === \"JSXMemberExpression\" && hasRange(opening.name)) {\n return opening.name.end;\n }\n\n return -1;\n}\n\n// Helper: Analyze a prop value to extract data binding\nfunction analyzeBinding(\n value: JSXAttribute[\"value\"],\n code: string,\n): {\n path: string;\n datasource?: string;\n recordId?: string;\n isConditional?: boolean;\n} | null {\n if (!value) {\n return null;\n }\n\n if (value.type === \"JSXExpressionContainer\") {\n const expr = value.expression;\n\n if (expr.type === \"JSXEmptyExpression\") {\n return null;\n }\n\n return analyzeExpression(expr, code);\n }\n\n return null;\n}\n\n// Helper: Analyze any expression to extract binding info\nfunction analyzeExpression(\n expr: Expression,\n code: string,\n): {\n path: string;\n datasource?: string;\n recordId?: string;\n isConditional?: boolean;\n} | null {\n // Handle member expressions: user.name, product.price, etc.\n if (expr.type === \"MemberExpression\") {\n const path = exprToString(expr, code);\n const datasource = extractDatasource(expr);\n const recordId = extractRecordId(expr);\n\n return { path, datasource, recordId };\n }\n\n // Handle identifiers: userName, price, etc.\n if (expr.type === \"Identifier\") {\n return { path: expr.name };\n }\n\n // Handle conditional expressions: condition ? value1 : value2\n if (expr.type === \"ConditionalExpression\") {\n // Try to extract from the consequent\n const consequent = analyzeExpression(expr.consequent, code);\n if (consequent) {\n return {\n ...consequent,\n isConditional: true,\n path: exprToString(expr, code),\n };\n }\n }\n\n // Handle logical expressions: value1 && value2, value1 || value2\n if (expr.type === \"LogicalExpression\") {\n const right = analyzeExpression(expr.right, code);\n if (right) {\n return {\n ...right,\n isConditional: true,\n path: exprToString(expr, code),\n };\n }\n }\n\n // For other complex expressions, just return the path\n const exprWithRange = expr as any;\n if (exprWithRange.start !== undefined && exprWithRange.end !== undefined) {\n return {\n path: code.slice(exprWithRange.start, exprWithRange.end),\n };\n }\n\n return null;\n}\n\n// Helper: Convert expression AST to string\nfunction exprToString(expr: Expression, code: string): string {\n // Use the source range to get the actual code\n const exprWithRange = expr as any;\n if (exprWithRange.start !== undefined && exprWithRange.end !== undefined) {\n return code.slice(exprWithRange.start, exprWithRange.end);\n }\n\n // Fallback: reconstruct from AST\n if (expr.type === \"Identifier\") {\n return expr.name;\n }\n\n if (expr.type === \"MemberExpression\") {\n const obj = exprToString(expr.object as Expression, code);\n const prop =\n expr.property.type === \"Identifier\" && !expr.computed\n ? expr.property.name\n : exprToString(expr.property as Expression, code);\n return expr.computed ? `${obj}[${prop}]` : `${obj}.${prop}`;\n }\n\n if (expr.type === \"ConditionalExpression\") {\n const test = exprToString(expr.test, code);\n const consequent = exprToString(expr.consequent, code);\n const alternate = exprToString(expr.alternate, code);\n return `${test} ? ${consequent} : ${alternate}`;\n }\n\n if (expr.type === \"LogicalExpression\") {\n const left = exprToString(expr.left, code);\n const right = exprToString(expr.right, code);\n return `${left} ${expr.operator} ${right}`;\n }\n\n return \"\";\n}\n\n// Helper: Extract datasource name from member expression\nfunction extractDatasource(expr: MemberExpression): string | undefined {\n let current: Expression = expr.object as Expression;\n\n // Traverse to the root object\n while (current.type === \"MemberExpression\") {\n current = current.object as Expression;\n }\n\n if (current.type === \"Identifier\") {\n return current.name;\n }\n\n return undefined;\n}\n\n// Helper: Extract record ID reference\nfunction extractRecordId(expr: MemberExpression): string | undefined {\n const obj = expr.object;\n\n if (obj.type === \"Identifier\") {\n // Assume the ID field is `${object}.id`\n return `${obj.name}.id`;\n }\n\n if (obj.type === \"MemberExpression\") {\n // For nested objects like user.profile.id\n const datasource = extractDatasource(obj);\n if (datasource) {\n return `${datasource}.id`;\n }\n }\n\n return undefined;\n}\n\n// Helper: Detect .map() calls and extract loop context\nfunction detectMapCall(node: CallExpression, code: string): LoopContext | null {\n // Check if this is a .map() call\n if (\n node.callee.type !== \"MemberExpression\" ||\n node.callee.property.type !== \"Identifier\" ||\n node.callee.property.name !== \"map\"\n ) {\n return null;\n }\n\n const callback = node.arguments[0];\n\n if (!callback) {\n return null;\n }\n\n // Check if callback is an arrow function or function expression\n if (callback.type !== \"ArrowFunctionExpression\" && callback.type !== \"FunctionExpression\") {\n return null;\n }\n\n const params = callback.params;\n const itemParam = params[0];\n const indexParam = params[1];\n\n if (!itemParam) {\n return null;\n }\n\n // Extract parameter names\n const itemName = itemParam.type === \"Identifier\" ? itemParam.name : \"item\";\n const indexName = indexParam?.type === \"Identifier\" ? indexParam.name : null;\n\n // Get the array expression\n const arrayExpr = exprToString(node.callee.object as Expression, code);\n\n return {\n itemName,\n indexName,\n arrayExpr,\n };\n}\n\n// Helper: Escape prop values for JSX attributes\nfunction escapeProp(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\n// Helper: Check if element is a \"leaf\" with only static text (no nested elements or expressions)\nfunction isTextLeafElement(jsxElement: JSXElement): boolean {\n let hasText = false;\n\n for (const child of jsxElement.children) {\n if (child.type === \"JSXText\") {\n const textValue = (child as any).value;\n if (textValue?.trim()) {\n hasText = true;\n }\n } else if (\n child.type === \"JSXElement\" ||\n child.type === \"JSXFragment\" ||\n child.type === \"JSXExpressionContainer\" ||\n child.type === \"JSXSpreadChild\"\n ) {\n // Has non-text children, not a leaf element\n return false;\n }\n }\n\n return hasText;\n}\n\nexport default upstartEditor.vite;\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { UpstartEditorPluginOptions } from "./runtime/types.js";
2
+ import * as unplugin0 from "unplugin";
3
+
4
+ //#region src/vite-plugin-upstart-editor/plugin.d.ts
5
+
6
+ /**
7
+ * Upstart Visual Editor Vite plugin (build-time)
8
+ *
9
+ * Injects the editor runtime into the app entry during build.
10
+ */
11
+ declare const upstartEditor: unplugin0.UnpluginInstance<UpstartEditorPluginOptions, boolean>;
12
+ declare const _default: (options: UpstartEditorPluginOptions) => unplugin0.VitePlugin<any> | unplugin0.VitePlugin<any>[];
13
+ //#endregion
14
+ export { _default as default, upstartEditor };
15
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/vite-plugin-upstart-editor/plugin.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAeA;AAuDG;cAvDU,eAAa,SAAA,CAAA,iBAAA;cAuDvB"}
@@ -0,0 +1,55 @@
1
+ import { createUnplugin } from "unplugin";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ //#region src/vite-plugin-upstart-editor/plugin.ts
6
+ const DEFAULT_OPTIONS = {
7
+ enabled: false,
8
+ autoInject: true
9
+ };
10
+ /**
11
+ * Upstart Visual Editor Vite plugin (build-time)
12
+ *
13
+ * Injects the editor runtime into the app entry during build.
14
+ */
15
+ const upstartEditor = createUnplugin((options = {}) => {
16
+ const { enabled, autoInject } = {
17
+ ...DEFAULT_OPTIONS,
18
+ ...options
19
+ };
20
+ if (!enabled) return { name: "upstart-editor-disabled" };
21
+ const runtimePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "./runtime/index");
22
+ return {
23
+ name: "upstart-editor",
24
+ enforce: "pre",
25
+ transform(code, id) {
26
+ if (!autoInject) return null;
27
+ const [cleanId] = id.split("?");
28
+ if (!cleanId || cleanId.includes("node_modules")) return null;
29
+ if (!/\.(t|j)sx?$/.test(cleanId)) return null;
30
+ const filename = path.basename(cleanId);
31
+ if (!filename.startsWith("main.") && !filename.startsWith("index.")) return null;
32
+ if (code.includes("initUpstartEditor")) return null;
33
+ return {
34
+ code: `${[
35
+ `import { initUpstartEditor } from ${JSON.stringify(runtimePath)};`,
36
+ "",
37
+ "if (typeof window !== 'undefined') {",
38
+ " if (document.readyState === 'loading') {",
39
+ " document.addEventListener('DOMContentLoaded', () => initUpstartEditor());",
40
+ " } else {",
41
+ " initUpstartEditor();",
42
+ " }",
43
+ "}",
44
+ ""
45
+ ].join("\n")}${code}`,
46
+ map: null
47
+ };
48
+ }
49
+ };
50
+ });
51
+ var plugin_default = upstartEditor.vite;
52
+
53
+ //#endregion
54
+ export { plugin_default as default, upstartEditor };
55
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","names":["DEFAULT_OPTIONS: Required<UpstartEditorPluginOptions>"],"sources":["../../src/vite-plugin-upstart-editor/plugin.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createUnplugin } from \"unplugin\";\nimport type { UpstartEditorPluginOptions } from \"./runtime/types.js\";\n\nconst DEFAULT_OPTIONS: Required<UpstartEditorPluginOptions> = {\n enabled: false,\n autoInject: true,\n};\n\n/**\n * Upstart Visual Editor Vite plugin (build-time)\n *\n * Injects the editor runtime into the app entry during build.\n */\nexport const upstartEditor = createUnplugin<UpstartEditorPluginOptions>((options = {}) => {\n const { enabled, autoInject } = { ...DEFAULT_OPTIONS, ...options };\n\n if (!enabled) {\n return { name: \"upstart-editor-disabled\" };\n }\n\n const runtimePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), \"./runtime/index\");\n\n return {\n name: \"upstart-editor\",\n enforce: \"pre\",\n\n transform(code, id) {\n if (!autoInject) {\n return null;\n }\n\n const [cleanId] = id.split(\"?\");\n if (!cleanId || cleanId.includes(\"node_modules\")) {\n return null;\n }\n\n if (!/\\.(t|j)sx?$/.test(cleanId)) {\n return null;\n }\n\n const filename = path.basename(cleanId);\n if (!filename.startsWith(\"main.\") && !filename.startsWith(\"index.\")) {\n return null;\n }\n\n if (code.includes(\"initUpstartEditor\")) {\n return null;\n }\n\n const injection = [\n `import { initUpstartEditor } from ${JSON.stringify(runtimePath)};`,\n \"\",\n \"if (typeof window !== 'undefined') {\",\n \" if (document.readyState === 'loading') {\",\n \" document.addEventListener('DOMContentLoaded', () => initUpstartEditor());\",\n \" } else {\",\n \" initUpstartEditor();\",\n \" }\",\n \"}\",\n \"\",\n ].join(\"\\n\");\n\n return {\n code: `${injection}${code}`,\n map: null,\n };\n },\n };\n});\n\nexport default upstartEditor.vite;\n"],"mappings":";;;;;AAKA,MAAMA,kBAAwD;CAC5D,SAAS;CACT,YAAY;CACb;;;;;;AAOD,MAAa,gBAAgB,gBAA4C,UAAU,EAAE,KAAK;CACxF,MAAM,EAAE,SAAS,eAAe;EAAE,GAAG;EAAiB,GAAG;EAAS;AAElE,KAAI,CAAC,QACH,QAAO,EAAE,MAAM,2BAA2B;CAG5C,MAAM,cAAc,KAAK,QAAQ,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EAAE,kBAAkB;AAEjG,QAAO;EACL,MAAM;EACN,SAAS;EAET,UAAU,MAAM,IAAI;AAClB,OAAI,CAAC,WACH,QAAO;GAGT,MAAM,CAAC,WAAW,GAAG,MAAM,IAAI;AAC/B,OAAI,CAAC,WAAW,QAAQ,SAAS,eAAe,CAC9C,QAAO;AAGT,OAAI,CAAC,cAAc,KAAK,QAAQ,CAC9B,QAAO;GAGT,MAAM,WAAW,KAAK,SAAS,QAAQ;AACvC,OAAI,CAAC,SAAS,WAAW,QAAQ,IAAI,CAAC,SAAS,WAAW,SAAS,CACjE,QAAO;AAGT,OAAI,KAAK,SAAS,oBAAoB,CACpC,QAAO;AAgBT,UAAO;IACL,MAAM,GAdU;KAChB,qCAAqC,KAAK,UAAU,YAAY,CAAC;KACjE;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,KAAK,GAGW;IACrB,KAAK;IACN;;EAEJ;EACD;AAEF,qBAAe,cAAc"}
@@ -0,0 +1,12 @@
1
+ //#region src/vite-plugin-upstart-editor/runtime/click-handler.d.ts
2
+ /**
3
+ * Initialize click handler for className editing.
4
+ */
5
+ declare function initClickHandler(): void;
6
+ /**
7
+ * Cleanup click handler.
8
+ */
9
+ declare function cleanupClickHandler(): void;
10
+ //#endregion
11
+ export { cleanupClickHandler, initClickHandler };
12
+ //# sourceMappingURL=click-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"click-handler.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/click-handler.ts"],"sourcesContent":[],"mappings":";;AAQA;AAiBA;iBAjBgB,gBAAA,CAAA;;;;iBAiBA,mBAAA,CAAA"}
@@ -0,0 +1,57 @@
1
+ import { sendToParent } from "./utils.js";
2
+ import { getCurrentMode } from "./index.js";
3
+
4
+ //#region src/vite-plugin-upstart-editor/runtime/click-handler.ts
5
+ let isInitialized = false;
6
+ /**
7
+ * Initialize click handler for className editing.
8
+ */
9
+ function initClickHandler() {
10
+ if (typeof document === "undefined") return;
11
+ if (isInitialized) return;
12
+ console.log("[Upstart Editor] Initializing click handler...");
13
+ document.addEventListener("click", handleClick, true);
14
+ isInitialized = true;
15
+ }
16
+ /**
17
+ * Cleanup click handler.
18
+ */
19
+ function cleanupClickHandler() {
20
+ document.removeEventListener("click", handleClick, true);
21
+ isInitialized = false;
22
+ }
23
+ function handleClick(event) {
24
+ if (getCurrentMode() !== "edit") return;
25
+ const target = event.target;
26
+ if (!target) return;
27
+ if (target.closest("[contenteditable='true']")) return;
28
+ const component = target.closest("[data-upstart-component]");
29
+ if (!component) return;
30
+ event.preventDefault();
31
+ event.stopPropagation();
32
+ const hash = component.dataset.upstartHash;
33
+ const componentName = component.dataset.upstartComponent;
34
+ const filePath = component.dataset.upstartFile ?? "";
35
+ if (!hash || !componentName) return;
36
+ const rect = component.getBoundingClientRect();
37
+ sendToParent({
38
+ type: "element-clicked",
39
+ hash,
40
+ componentName,
41
+ filePath,
42
+ currentClassName: component.className,
43
+ bounds: {
44
+ top: rect.top,
45
+ left: rect.left,
46
+ width: rect.width,
47
+ height: rect.height,
48
+ right: rect.right,
49
+ bottom: rect.bottom
50
+ }
51
+ });
52
+ console.log("[Upstart Editor] Element clicked:", componentName, hash);
53
+ }
54
+
55
+ //#endregion
56
+ export { cleanupClickHandler, initClickHandler };
57
+ //# sourceMappingURL=click-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"click-handler.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/click-handler.ts"],"sourcesContent":["import { getCurrentMode } from \"./index.js\";\nimport { sendToParent } from \"./utils.js\";\n\nlet isInitialized = false;\n\n/**\n * Initialize click handler for className editing.\n */\nexport function initClickHandler(): void {\n if (typeof document === \"undefined\") {\n return;\n }\n\n if (isInitialized) {\n return;\n }\n\n console.log(\"[Upstart Editor] Initializing click handler...\");\n document.addEventListener(\"click\", handleClick, true);\n isInitialized = true;\n}\n\n/**\n * Cleanup click handler.\n */\nexport function cleanupClickHandler(): void {\n document.removeEventListener(\"click\", handleClick, true);\n isInitialized = false;\n}\n\nfunction handleClick(event: MouseEvent): void {\n if (getCurrentMode() !== \"edit\") {\n return;\n }\n\n const target = event.target as HTMLElement | null;\n if (!target) {\n return;\n }\n\n if (target.closest(\"[contenteditable='true']\")) {\n return;\n }\n\n const component = target.closest<HTMLElement>(\"[data-upstart-component]\");\n if (!component) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n const hash = component.dataset.upstartHash;\n const componentName = component.dataset.upstartComponent;\n const filePath = component.dataset.upstartFile ?? \"\";\n\n if (!hash || !componentName) {\n return;\n }\n\n const rect = component.getBoundingClientRect();\n\n sendToParent({\n type: \"element-clicked\",\n hash,\n componentName,\n filePath,\n currentClassName: component.className,\n bounds: {\n top: rect.top,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n right: rect.right,\n bottom: rect.bottom,\n },\n });\n\n console.log(\"[Upstart Editor] Element clicked:\", componentName, hash);\n}\n"],"mappings":";;;;AAGA,IAAI,gBAAgB;;;;AAKpB,SAAgB,mBAAyB;AACvC,KAAI,OAAO,aAAa,YACtB;AAGF,KAAI,cACF;AAGF,SAAQ,IAAI,iDAAiD;AAC7D,UAAS,iBAAiB,SAAS,aAAa,KAAK;AACrD,iBAAgB;;;;;AAMlB,SAAgB,sBAA4B;AAC1C,UAAS,oBAAoB,SAAS,aAAa,KAAK;AACxD,iBAAgB;;AAGlB,SAAS,YAAY,OAAyB;AAC5C,KAAI,gBAAgB,KAAK,OACvB;CAGF,MAAM,SAAS,MAAM;AACrB,KAAI,CAAC,OACH;AAGF,KAAI,OAAO,QAAQ,2BAA2B,CAC5C;CAGF,MAAM,YAAY,OAAO,QAAqB,2BAA2B;AACzE,KAAI,CAAC,UACH;AAGF,OAAM,gBAAgB;AACtB,OAAM,iBAAiB;CAEvB,MAAM,OAAO,UAAU,QAAQ;CAC/B,MAAM,gBAAgB,UAAU,QAAQ;CACxC,MAAM,WAAW,UAAU,QAAQ,eAAe;AAElD,KAAI,CAAC,QAAQ,CAAC,cACZ;CAGF,MAAM,OAAO,UAAU,uBAAuB;AAE9C,cAAa;EACX,MAAM;EACN;EACA;EACA;EACA,kBAAkB,UAAU;EAC5B,QAAQ;GACN,KAAK,KAAK;GACV,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,OAAO,KAAK;GACZ,QAAQ,KAAK;GACd;EACF,CAAC;AAEF,SAAQ,IAAI,qCAAqC,eAAe,KAAK"}
@@ -0,0 +1,12 @@
1
+ //#region src/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts
2
+ /**
3
+ * Initialize hover overlay.
4
+ */
5
+ declare function initHoverOverlay(): void;
6
+ /**
7
+ * Hide all overlays.
8
+ */
9
+ declare function hideOverlays(): void;
10
+ //#endregion
11
+ export { hideOverlays, initHoverOverlay };
12
+ //# sourceMappingURL=hover-overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hover-overlay.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/hover-overlay.ts"],"sourcesContent":[],"mappings":";;AAWA;AAsBA;iBAtBgB,gBAAA,CAAA;;;;iBAsBA,YAAA,CAAA"}
@@ -0,0 +1,91 @@
1
+ import { sendToParent } from "./utils.js";
2
+ import { getCurrentMode } from "./index.js";
3
+
4
+ //#region src/vite-plugin-upstart-editor/runtime/hover-overlay.ts
5
+ let overlay = null;
6
+ let currentTarget = null;
7
+ let isInitialized = false;
8
+ let rafId = null;
9
+ /**
10
+ * Initialize hover overlay.
11
+ */
12
+ function initHoverOverlay() {
13
+ if (typeof document === "undefined") return;
14
+ if (isInitialized) return;
15
+ console.log("[Upstart Editor] Initializing hover overlay...");
16
+ document.addEventListener("mouseover", handleMouseOver);
17
+ document.addEventListener("mouseout", handleMouseOut);
18
+ window.addEventListener("scroll", scheduleOverlayUpdate, { passive: true });
19
+ window.addEventListener("resize", scheduleOverlayUpdate, { passive: true });
20
+ isInitialized = true;
21
+ }
22
+ /**
23
+ * Hide all overlays.
24
+ */
25
+ function hideOverlays() {
26
+ if (overlay) {
27
+ overlay.style.display = "none";
28
+ currentTarget = null;
29
+ }
30
+ }
31
+ function handleMouseOver(event) {
32
+ if (getCurrentMode() !== "edit") return;
33
+ const target = event.target;
34
+ if (!target) return;
35
+ const component = target.closest("[data-upstart-component]");
36
+ if (!component) return;
37
+ if (!overlay) createOverlay();
38
+ currentTarget = component;
39
+ positionOverlay(component);
40
+ const hash = component.dataset.upstartHash;
41
+ if (hash) {
42
+ const rect = component.getBoundingClientRect();
43
+ sendToParent({
44
+ type: "element-hovered",
45
+ hash,
46
+ bounds: {
47
+ top: rect.top,
48
+ left: rect.left,
49
+ width: rect.width,
50
+ height: rect.height,
51
+ right: rect.right,
52
+ bottom: rect.bottom
53
+ }
54
+ });
55
+ }
56
+ }
57
+ function handleMouseOut(event) {
58
+ const target = event.target;
59
+ const relatedTarget = event.relatedTarget;
60
+ if (!target) return;
61
+ const component = target.closest("[data-upstart-component]");
62
+ if (!component) return;
63
+ if (relatedTarget && component.contains(relatedTarget)) return;
64
+ hideOverlays();
65
+ }
66
+ function createOverlay() {
67
+ overlay = document.createElement("div");
68
+ overlay.id = "upstart-hover-overlay";
69
+ overlay.style.cssText = "position: absolute; pointer-events: none; border: 2px solid #3b82f6; background: rgba(59, 130, 246, 0.05); border-radius: 4px; z-index: 9999; transition: all 0.1s ease; display: none;";
70
+ document.body.appendChild(overlay);
71
+ }
72
+ function positionOverlay(element) {
73
+ if (!overlay) return;
74
+ const rect = element.getBoundingClientRect();
75
+ overlay.style.top = `${rect.top + window.scrollY}px`;
76
+ overlay.style.left = `${rect.left + window.scrollX}px`;
77
+ overlay.style.width = `${rect.width}px`;
78
+ overlay.style.height = `${rect.height}px`;
79
+ overlay.style.display = "block";
80
+ }
81
+ function scheduleOverlayUpdate() {
82
+ if (rafId !== null) return;
83
+ rafId = requestAnimationFrame(() => {
84
+ rafId = null;
85
+ if (currentTarget && overlay && overlay.style.display === "block") positionOverlay(currentTarget);
86
+ });
87
+ }
88
+
89
+ //#endregion
90
+ export { hideOverlays, initHoverOverlay };
91
+ //# sourceMappingURL=hover-overlay.js.map