@embeddables/cli 0.1.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 (173) hide show
  1. package/README.md +116 -0
  2. package/bin/embeddables.mjs +2 -0
  3. package/dist/auth/index.d.ts +43 -0
  4. package/dist/auth/index.d.ts.map +1 -0
  5. package/dist/auth/index.js +100 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +75 -0
  9. package/dist/commands/build-workbench.d.ts +5 -0
  10. package/dist/commands/build-workbench.d.ts.map +1 -0
  11. package/dist/commands/build-workbench.js +122 -0
  12. package/dist/commands/build.d.ts +7 -0
  13. package/dist/commands/build.d.ts.map +1 -0
  14. package/dist/commands/build.js +22 -0
  15. package/dist/commands/dev.d.ts +11 -0
  16. package/dist/commands/dev.d.ts.map +1 -0
  17. package/dist/commands/dev.js +153 -0
  18. package/dist/commands/login.d.ts +2 -0
  19. package/dist/commands/login.d.ts.map +1 -0
  20. package/dist/commands/login.js +112 -0
  21. package/dist/commands/logout.d.ts +2 -0
  22. package/dist/commands/logout.d.ts.map +1 -0
  23. package/dist/commands/logout.js +18 -0
  24. package/dist/commands/pull.d.ts +7 -0
  25. package/dist/commands/pull.d.ts.map +1 -0
  26. package/dist/commands/pull.js +97 -0
  27. package/dist/compiler/errors.d.ts +20 -0
  28. package/dist/compiler/errors.d.ts.map +1 -0
  29. package/dist/compiler/errors.js +35 -0
  30. package/dist/compiler/evalStatic.d.ts +3 -0
  31. package/dist/compiler/evalStatic.d.ts.map +1 -0
  32. package/dist/compiler/evalStatic.js +57 -0
  33. package/dist/compiler/flatten.js +1 -0
  34. package/dist/compiler/helpers/duplicateIds.d.ts +9 -0
  35. package/dist/compiler/helpers/duplicateIds.d.ts.map +1 -0
  36. package/dist/compiler/helpers/duplicateIds.js +71 -0
  37. package/dist/compiler/index.d.ts +16 -0
  38. package/dist/compiler/index.d.ts.map +1 -0
  39. package/dist/compiler/index.js +934 -0
  40. package/dist/compiler/parsePage.d.ts +15 -0
  41. package/dist/compiler/parsePage.d.ts.map +1 -0
  42. package/dist/compiler/parsePage.js +562 -0
  43. package/dist/compiler/registry.d.ts +4 -0
  44. package/dist/compiler/registry.d.ts.map +1 -0
  45. package/dist/compiler/registry.js +44 -0
  46. package/dist/compiler/reverse.d.ts +17 -0
  47. package/dist/compiler/reverse.d.ts.map +1 -0
  48. package/dist/compiler/reverse.js +1632 -0
  49. package/dist/compiler/types.d.ts +21 -0
  50. package/dist/compiler/types.d.ts.map +1 -0
  51. package/dist/compiler/types.js +1 -0
  52. package/dist/components/index.d.ts +21 -0
  53. package/dist/components/index.d.ts.map +1 -0
  54. package/dist/components/index.js +21 -0
  55. package/dist/components/primitives/BaseComponent.d.ts +32 -0
  56. package/dist/components/primitives/BaseComponent.d.ts.map +1 -0
  57. package/dist/components/primitives/BaseComponent.js +26 -0
  58. package/dist/components/primitives/BookMeeting.d.ts +18 -0
  59. package/dist/components/primitives/BookMeeting.d.ts.map +1 -0
  60. package/dist/components/primitives/BookMeeting.js +5 -0
  61. package/dist/components/primitives/Chart.d.ts +41 -0
  62. package/dist/components/primitives/Chart.d.ts.map +1 -0
  63. package/dist/components/primitives/Chart.js +5 -0
  64. package/dist/components/primitives/Container.d.ts +8 -0
  65. package/dist/components/primitives/Container.d.ts.map +1 -0
  66. package/dist/components/primitives/Container.js +5 -0
  67. package/dist/components/primitives/CustomButton.d.ts +37 -0
  68. package/dist/components/primitives/CustomButton.d.ts.map +1 -0
  69. package/dist/components/primitives/CustomButton.js +10 -0
  70. package/dist/components/primitives/CustomHTML.d.ts +8 -0
  71. package/dist/components/primitives/CustomHTML.d.ts.map +1 -0
  72. package/dist/components/primitives/CustomHTML.js +5 -0
  73. package/dist/components/primitives/FileUpload.d.ts +18 -0
  74. package/dist/components/primitives/FileUpload.d.ts.map +1 -0
  75. package/dist/components/primitives/FileUpload.js +16 -0
  76. package/dist/components/primitives/InputBox.d.ts +34 -0
  77. package/dist/components/primitives/InputBox.d.ts.map +1 -0
  78. package/dist/components/primitives/InputBox.js +25 -0
  79. package/dist/components/primitives/Lottie.d.ts +11 -0
  80. package/dist/components/primitives/Lottie.d.ts.map +1 -0
  81. package/dist/components/primitives/Lottie.js +5 -0
  82. package/dist/components/primitives/MediaEmbed.d.ts +13 -0
  83. package/dist/components/primitives/MediaEmbed.d.ts.map +1 -0
  84. package/dist/components/primitives/MediaEmbed.js +6 -0
  85. package/dist/components/primitives/MediaImage.d.ts +8 -0
  86. package/dist/components/primitives/MediaImage.d.ts.map +1 -0
  87. package/dist/components/primitives/MediaImage.js +5 -0
  88. package/dist/components/primitives/OptionSelector.d.ts +35 -0
  89. package/dist/components/primitives/OptionSelector.d.ts.map +1 -0
  90. package/dist/components/primitives/OptionSelector.js +8 -0
  91. package/dist/components/primitives/PaypalCheckout.d.ts +25 -0
  92. package/dist/components/primitives/PaypalCheckout.d.ts.map +1 -0
  93. package/dist/components/primitives/PaypalCheckout.js +5 -0
  94. package/dist/components/primitives/PlainText.d.ts +6 -0
  95. package/dist/components/primitives/PlainText.d.ts.map +1 -0
  96. package/dist/components/primitives/PlainText.js +5 -0
  97. package/dist/components/primitives/ProgressBar.d.ts +15 -0
  98. package/dist/components/primitives/ProgressBar.d.ts.map +1 -0
  99. package/dist/components/primitives/ProgressBar.js +5 -0
  100. package/dist/components/primitives/RichText.d.ts +6 -0
  101. package/dist/components/primitives/RichText.d.ts.map +1 -0
  102. package/dist/components/primitives/RichText.js +5 -0
  103. package/dist/components/primitives/RichTextMarkdown.d.ts +6 -0
  104. package/dist/components/primitives/RichTextMarkdown.d.ts.map +1 -0
  105. package/dist/components/primitives/RichTextMarkdown.js +5 -0
  106. package/dist/components/primitives/Rive.d.ts +16 -0
  107. package/dist/components/primitives/Rive.d.ts.map +1 -0
  108. package/dist/components/primitives/Rive.js +8 -0
  109. package/dist/components/primitives/StripeCheckout.d.ts +52 -0
  110. package/dist/components/primitives/StripeCheckout.d.ts.map +1 -0
  111. package/dist/components/primitives/StripeCheckout.js +5 -0
  112. package/dist/components/primitives/StripeCheckout2.d.ts +30 -0
  113. package/dist/components/primitives/StripeCheckout2.d.ts.map +1 -0
  114. package/dist/components/primitives/StripeCheckout2.js +7 -0
  115. package/dist/proxy/injectApiInterceptor.d.ts +6 -0
  116. package/dist/proxy/injectApiInterceptor.d.ts.map +1 -0
  117. package/dist/proxy/injectApiInterceptor.js +66 -0
  118. package/dist/proxy/injectReload.d.ts +2 -0
  119. package/dist/proxy/injectReload.d.ts.map +1 -0
  120. package/dist/proxy/injectReload.js +14 -0
  121. package/dist/proxy/injectWorkbench.d.ts +4 -0
  122. package/dist/proxy/injectWorkbench.d.ts.map +1 -0
  123. package/dist/proxy/injectWorkbench.js +16 -0
  124. package/dist/proxy/server.d.ts +11 -0
  125. package/dist/proxy/server.d.ts.map +1 -0
  126. package/dist/proxy/server.js +246 -0
  127. package/dist/proxy/sse.d.ts +5 -0
  128. package/dist/proxy/sse.d.ts.map +1 -0
  129. package/dist/proxy/sse.js +17 -0
  130. package/dist/types-builder.d.ts +800 -0
  131. package/dist/types-builder.d.ts.map +1 -0
  132. package/dist/types-builder.js +20 -0
  133. package/dist/workbench/ActionsPanel.d.ts +6 -0
  134. package/dist/workbench/ActionsPanel.d.ts.map +1 -0
  135. package/dist/workbench/ActionsPanel.js +47 -0
  136. package/dist/workbench/AutofillPanel.d.ts +6 -0
  137. package/dist/workbench/AutofillPanel.d.ts.map +1 -0
  138. package/dist/workbench/AutofillPanel.js +543 -0
  139. package/dist/workbench/ComputedFieldsPanel.d.ts +6 -0
  140. package/dist/workbench/ComputedFieldsPanel.d.ts.map +1 -0
  141. package/dist/workbench/ComputedFieldsPanel.js +31 -0
  142. package/dist/workbench/ExperimentsPanel.d.ts +6 -0
  143. package/dist/workbench/ExperimentsPanel.d.ts.map +1 -0
  144. package/dist/workbench/ExperimentsPanel.js +182 -0
  145. package/dist/workbench/FieldEditorPanel.d.ts +9 -0
  146. package/dist/workbench/FieldEditorPanel.d.ts.map +1 -0
  147. package/dist/workbench/FieldEditorPanel.js +650 -0
  148. package/dist/workbench/InspectorPanel.d.ts +6 -0
  149. package/dist/workbench/InspectorPanel.d.ts.map +1 -0
  150. package/dist/workbench/InspectorPanel.js +341 -0
  151. package/dist/workbench/PageNavigator.d.ts +6 -0
  152. package/dist/workbench/PageNavigator.d.ts.map +1 -0
  153. package/dist/workbench/PageNavigator.js +123 -0
  154. package/dist/workbench/SchemaPanel.d.ts +6 -0
  155. package/dist/workbench/SchemaPanel.d.ts.map +1 -0
  156. package/dist/workbench/SchemaPanel.js +222 -0
  157. package/dist/workbench/UserDataPanel.d.ts +6 -0
  158. package/dist/workbench/UserDataPanel.d.ts.map +1 -0
  159. package/dist/workbench/UserDataPanel.js +350 -0
  160. package/dist/workbench/WorkbenchApp.d.ts +6 -0
  161. package/dist/workbench/WorkbenchApp.d.ts.map +1 -0
  162. package/dist/workbench/WorkbenchApp.js +193 -0
  163. package/dist/workbench/cloudflare-worker/README.md +31 -0
  164. package/dist/workbench/cloudflare-worker/public/workbench.css +1614 -0
  165. package/dist/workbench/cloudflare-worker/public/workbench.js +77 -0
  166. package/dist/workbench/cloudflare-worker/worker.js +40 -0
  167. package/dist/workbench/cloudflare-worker/wrangler.toml +10 -0
  168. package/dist/workbench/index.d.ts +9 -0
  169. package/dist/workbench/index.d.ts.map +1 -0
  170. package/dist/workbench/index.js +44 -0
  171. package/dist/workbench/workbench.css +1614 -0
  172. package/dist/workbench/workbench.js +77 -0
  173. package/package.json +79 -0
@@ -0,0 +1,6 @@
1
+ type InspectorPanelProps = {
2
+ embeddableId: string;
3
+ };
4
+ export declare function InspectorPanel({ embeddableId }: InspectorPanelProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=InspectorPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InspectorPanel.d.ts","sourceRoot":"","sources":["../../src/workbench/InspectorPanel.tsx"],"names":[],"mappings":"AAGA,KAAK,mBAAmB,GAAG;IACzB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAmPD,wBAAgB,cAAc,CAAC,EAAE,YAAY,EAAE,EAAE,mBAAmB,2CAianE"}
@@ -0,0 +1,341 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ function getComponentIdFromElement(el) {
4
+ for (const cls of el.classList) {
5
+ if (cls.startsWith('cid-')) {
6
+ return cls.slice(4); // Remove 'cid-' prefix
7
+ }
8
+ }
9
+ return null;
10
+ }
11
+ function findFlowComponentAncestor(el) {
12
+ let current = el;
13
+ while (current) {
14
+ if (current.classList.contains('Flow-Component')) {
15
+ return current;
16
+ }
17
+ current = current.parentElement;
18
+ }
19
+ return null;
20
+ }
21
+ function findComponentById(flowJson, componentId) {
22
+ // Check global components
23
+ for (const comp of flowJson.components ?? []) {
24
+ if (comp.id === componentId)
25
+ return comp;
26
+ }
27
+ // Check page components
28
+ for (const page of flowJson.pages ?? []) {
29
+ for (const comp of page.components ?? []) {
30
+ if (comp.id === componentId)
31
+ return comp;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ function findPageById(flowJson, pageId) {
37
+ for (const page of flowJson.pages ?? []) {
38
+ if (page.id === pageId)
39
+ return page;
40
+ }
41
+ return null;
42
+ }
43
+ /**
44
+ * Filter styles to those related to a component and/or page.
45
+ * Returns an object with selectors grouped by their relevance.
46
+ */
47
+ function filterRelatedStyles(styles, component, page) {
48
+ const componentStyles = {};
49
+ const pageStyles = {};
50
+ if (!styles)
51
+ return { componentStyles, pageStyles };
52
+ // Build matchers for the component
53
+ const componentMatchers = [];
54
+ if (component) {
55
+ // Match component tags (e.g., .ComponentTag-page_wrapper)
56
+ for (const tag of component.tags ?? []) {
57
+ componentMatchers.push(`ComponentTag-${tag}`);
58
+ }
59
+ // Match component type (e.g., .ElementType-OptionSelector, .ElementType-CustomButton)
60
+ if (component.type) {
61
+ componentMatchers.push(`ElementType-${component.type}`);
62
+ }
63
+ // Match component ID (e.g., .cid-abc123)
64
+ if (component.id) {
65
+ componentMatchers.push(`cid-${component.id}`);
66
+ }
67
+ }
68
+ // Build matchers for the page
69
+ const pageMatchers = [];
70
+ if (page) {
71
+ // Match page tags (e.g., .PageTag-standard_page)
72
+ for (const tag of page.tags ?? []) {
73
+ pageMatchers.push(`PageTag-${tag}`);
74
+ }
75
+ // Match page key/id in selectors
76
+ if (page.key) {
77
+ pageMatchers.push(`PageKey-${page.key}`);
78
+ }
79
+ }
80
+ // Always include flow-level page selectors
81
+ pageMatchers.push('Flow-Page');
82
+ pageMatchers.push('Flow-EntireFlow');
83
+ pageMatchers.push('ElementType-PageContents');
84
+ for (const [selector, properties] of Object.entries(styles)) {
85
+ // Check if selector matches any component matchers
86
+ const matchesComponent = componentMatchers.some((matcher) => selector.includes(matcher));
87
+ // Check if selector matches any page matchers
88
+ const matchesPage = pageMatchers.some((matcher) => selector.includes(matcher));
89
+ if (matchesComponent) {
90
+ componentStyles[selector] = properties;
91
+ }
92
+ else if (matchesPage) {
93
+ pageStyles[selector] = properties;
94
+ }
95
+ }
96
+ return { componentStyles, pageStyles };
97
+ }
98
+ /**
99
+ * Convert a FlowStyles object to CSS text for display
100
+ */
101
+ function stylesToCss(styles) {
102
+ const lines = [];
103
+ for (const [selector, properties] of Object.entries(styles)) {
104
+ lines.push(`${selector} {`);
105
+ for (const [prop, value] of Object.entries(properties)) {
106
+ lines.push(` ${prop}: ${value};`);
107
+ }
108
+ lines.push('}');
109
+ lines.push('');
110
+ }
111
+ return lines.join('\n').trim();
112
+ }
113
+ function filterJsonByKey(obj, filterText) {
114
+ if (!filterText.trim())
115
+ return obj;
116
+ if (typeof obj !== 'object' || obj === null)
117
+ return obj;
118
+ const lowerFilter = filterText.toLowerCase();
119
+ // For arrays, filter each item and only keep non-empty results
120
+ if (Array.isArray(obj)) {
121
+ const filtered = obj
122
+ .map((item) => filterJsonByKey(item, filterText))
123
+ .filter((item) => {
124
+ if (typeof item !== 'object' || item === null)
125
+ return false;
126
+ if (Array.isArray(item))
127
+ return item.length > 0;
128
+ return Object.keys(item).length > 0;
129
+ });
130
+ return filtered;
131
+ }
132
+ const result = {};
133
+ for (const [key, value] of Object.entries(obj)) {
134
+ if (key.toLowerCase().includes(lowerFilter)) {
135
+ // Key matches - include the entire value unfiltered
136
+ result[key] = value;
137
+ }
138
+ else if (typeof value === 'object' && value !== null) {
139
+ // Key doesn't match - recurse to find nested matches
140
+ const filtered = filterJsonByKey(value, filterText);
141
+ if (Array.isArray(filtered)) {
142
+ if (filtered.length > 0)
143
+ result[key] = filtered;
144
+ }
145
+ else if (filtered !== null && Object.keys(filtered).length > 0) {
146
+ result[key] = filtered;
147
+ }
148
+ }
149
+ }
150
+ return result;
151
+ }
152
+ const USERDATA_UPDATED_EVENT = 'embeddables:userdata_updated';
153
+ function RelatedStylesPanel({ styles, component, page }) {
154
+ const { componentStyles, pageStyles } = filterRelatedStyles(styles, component, page);
155
+ const componentCss = stylesToCss(componentStyles);
156
+ const pageCss = stylesToCss(pageStyles);
157
+ const hasComponentStyles = Object.keys(componentStyles).length > 0;
158
+ const hasPageStyles = Object.keys(pageStyles).length > 0;
159
+ const hasAnyStyles = hasComponentStyles || hasPageStyles;
160
+ return (_jsxs("div", { className: "flex min-h-0 w-80 shrink-0 flex-col", children: [_jsx("div", { className: "mb-2 text-[11px] font-semibold uppercase tracking-wider text-slate-400", children: "Related Styles" }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto rounded-xl bg-slate-950/60 p-3 ring-1 ring-inset ring-white/10", children: !hasAnyStyles ? (_jsx("div", { className: "text-[11px] text-slate-500 italic", children: "No related styles found for this component or page." })) : (_jsxs("div", { className: "flex flex-col gap-4", children: [hasComponentStyles && (_jsxs("div", { children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-teal-400", children: "Component" }), _jsxs("span", { className: "text-[10px] text-slate-500", children: ["(", Object.keys(componentStyles).length, " rules)"] })] }), _jsx("pre", { className: "font-mono text-xs leading-5 text-slate-100 whitespace-pre-wrap break-all", children: componentCss })] })), hasPageStyles && (_jsxs("div", { children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-sky-400", children: "Page" }), _jsxs("span", { className: "text-[10px] text-slate-500", children: ["(", Object.keys(pageStyles).length, " rules)"] })] }), _jsx("pre", { className: "font-mono text-xs leading-5 text-slate-100 whitespace-pre-wrap break-all", children: pageCss })] }))] })) })] }));
161
+ }
162
+ export function InspectorPanel({ embeddableId }) {
163
+ const [state, setState] = useState('idle');
164
+ const [selectedComponent, setSelectedComponent] = useState(null);
165
+ const [selectedComponentId, setSelectedComponentId] = useState(null);
166
+ const [error, setError] = useState(null);
167
+ const [keyFilter, setKeyFilter] = useState('');
168
+ const [currentPage, setCurrentPage] = useState(null);
169
+ const [flowStyles, setFlowStyles] = useState(undefined);
170
+ const overlayRef = useRef(null);
171
+ const hoveredElementRef = useRef(null);
172
+ const savvy = window.Savvy;
173
+ // Refresh page and styles info
174
+ const refreshPageAndStyles = useCallback(() => {
175
+ if (!savvy?.getFlowJson || !savvy?.getUserData)
176
+ return;
177
+ try {
178
+ const flowJson = savvy.getFlowJson(embeddableId);
179
+ if (flowJson?.styles) {
180
+ setFlowStyles(flowJson.styles);
181
+ }
182
+ const userData = savvy.getUserData(embeddableId);
183
+ if (userData && flowJson) {
184
+ const page = findPageById(flowJson, userData.current_page_id);
185
+ setCurrentPage(page);
186
+ }
187
+ }
188
+ catch {
189
+ // ignore errors
190
+ }
191
+ }, [embeddableId, savvy]);
192
+ // Refresh on mount and when user data changes
193
+ useEffect(() => {
194
+ const timer = setTimeout(refreshPageAndStyles, 100);
195
+ return () => clearTimeout(timer);
196
+ }, [refreshPageAndStyles]);
197
+ useEffect(() => {
198
+ const handler = (event) => {
199
+ const detail = event?.detail;
200
+ const eventEmbeddableId = detail?.embeddableId;
201
+ if (typeof eventEmbeddableId === 'string' &&
202
+ eventEmbeddableId.length > 0 &&
203
+ eventEmbeddableId !== embeddableId) {
204
+ return;
205
+ }
206
+ refreshPageAndStyles();
207
+ };
208
+ window.addEventListener(USERDATA_UPDATED_EVENT, handler);
209
+ return () => window.removeEventListener(USERDATA_UPDATED_EVENT, handler);
210
+ }, [embeddableId, refreshPageAndStyles]);
211
+ // Create or get the highlight overlay element
212
+ const getOverlay = useCallback(() => {
213
+ if (!overlayRef.current) {
214
+ const overlay = document.createElement('div');
215
+ overlay.id = 'embeddables-inspector-overlay';
216
+ overlay.style.cssText = `
217
+ position: fixed;
218
+ pointer-events: none;
219
+ background: rgba(59, 130, 246, 0.15);
220
+ border: 2px solid rgba(59, 130, 246, 0.8);
221
+ border-radius: 4px;
222
+ z-index: 2147483646;
223
+ transition: all 0.1s ease-out;
224
+ display: none;
225
+ `;
226
+ document.body.appendChild(overlay);
227
+ overlayRef.current = overlay;
228
+ }
229
+ return overlayRef.current;
230
+ }, []);
231
+ // Position the overlay over an element
232
+ const positionOverlay = useCallback((el) => {
233
+ const overlay = getOverlay();
234
+ if (!el) {
235
+ overlay.style.display = 'none';
236
+ return;
237
+ }
238
+ const rect = el.getBoundingClientRect();
239
+ overlay.style.display = 'block';
240
+ overlay.style.top = `${rect.top}px`;
241
+ overlay.style.left = `${rect.left}px`;
242
+ overlay.style.width = `${rect.width}px`;
243
+ overlay.style.height = `${rect.height}px`;
244
+ }, [getOverlay]);
245
+ // Handle pointer movement during inspection
246
+ const handlePointerOver = useCallback((e) => {
247
+ const target = e.target;
248
+ const flowComponent = findFlowComponentAncestor(target);
249
+ if (flowComponent && flowComponent !== hoveredElementRef.current) {
250
+ hoveredElementRef.current = flowComponent;
251
+ positionOverlay(flowComponent);
252
+ }
253
+ else if (!flowComponent && hoveredElementRef.current) {
254
+ hoveredElementRef.current = null;
255
+ positionOverlay(null);
256
+ }
257
+ }, [positionOverlay]);
258
+ // Handle click during inspection
259
+ const handleClick = useCallback((e) => {
260
+ const target = e.target;
261
+ const flowComponent = findFlowComponentAncestor(target);
262
+ if (flowComponent) {
263
+ e.preventDefault();
264
+ e.stopPropagation();
265
+ const componentId = getComponentIdFromElement(flowComponent);
266
+ if (!componentId) {
267
+ setError('Could not find component ID (cid-*) class on element.');
268
+ return;
269
+ }
270
+ if (!savvy?.getFlowJson) {
271
+ setError('window.Savvy.getFlowJson is not available.');
272
+ return;
273
+ }
274
+ const flowJson = savvy.getFlowJson(embeddableId);
275
+ if (!flowJson) {
276
+ setError('Could not retrieve flow JSON.');
277
+ return;
278
+ }
279
+ const component = findComponentById(flowJson, componentId);
280
+ if (!component) {
281
+ setError(`Component with ID "${componentId}" not found in flow JSON.`);
282
+ return;
283
+ }
284
+ setSelectedComponentId(componentId);
285
+ setSelectedComponent(component);
286
+ setError(null);
287
+ setState('selected');
288
+ // Hide overlay after selection
289
+ positionOverlay(null);
290
+ hoveredElementRef.current = null;
291
+ }
292
+ }, [embeddableId, positionOverlay, savvy]);
293
+ // Start inspection mode
294
+ const startInspecting = useCallback(() => {
295
+ setError(null);
296
+ setState('inspecting');
297
+ }, []);
298
+ // Stop inspection mode
299
+ const stopInspecting = useCallback(() => {
300
+ setState('idle');
301
+ positionOverlay(null);
302
+ hoveredElementRef.current = null;
303
+ }, [positionOverlay]);
304
+ // Clear selection and go back to idle
305
+ const clearSelection = useCallback(() => {
306
+ setSelectedComponent(null);
307
+ setSelectedComponentId(null);
308
+ setState('idle');
309
+ }, []);
310
+ // Inspect another component (go back to inspecting mode)
311
+ const inspectAnother = useCallback(() => {
312
+ setError(null);
313
+ setState('inspecting');
314
+ }, []);
315
+ // Set up and tear down event listeners
316
+ useEffect(() => {
317
+ if (state === 'inspecting') {
318
+ document.addEventListener('pointerover', handlePointerOver, true);
319
+ document.addEventListener('click', handleClick, true);
320
+ return () => {
321
+ document.removeEventListener('pointerover', handlePointerOver, true);
322
+ document.removeEventListener('click', handleClick, true);
323
+ positionOverlay(null);
324
+ };
325
+ }
326
+ }, [state, handlePointerOver, handleClick, positionOverlay]);
327
+ // Cleanup overlay on unmount
328
+ useEffect(() => {
329
+ return () => {
330
+ if (overlayRef.current) {
331
+ overlayRef.current.remove();
332
+ overlayRef.current = null;
333
+ }
334
+ };
335
+ }, []);
336
+ return (_jsxs("div", { className: "mx-auto flex h-full w-full max-w-6xl flex-col", children: [_jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-xs font-semibold tracking-wide text-slate-100", children: "Inspect Components" }), _jsx("div", { className: "mt-0.5 text-[11px] text-slate-400", children: state === 'inspecting'
337
+ ? 'Hover over a component and click to inspect its JSON.'
338
+ : 'Click a component on the page to view its full JSON configuration.' })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-start gap-2 sm:justify-end", children: [state === 'idle' && (_jsxs("button", { type: "button", onClick: startInspecting, className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-sky-600 px-2.5 py-1.5 text-xs font-semibold text-white ring-1 ring-inset ring-sky-500 hover:bg-sky-500", children: [_jsx("span", { className: "text-sm mb-0.5", children: "\u2316" }), "Start Inspecting"] })), state === 'inspecting' && (_jsx("button", { type: "button", onClick: stopInspecting, className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-rose-600 px-2.5 py-1.5 text-xs font-semibold text-white ring-1 ring-inset ring-rose-500 hover:bg-rose-500", children: "Stop Inspecting" })), state === 'selected' && (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: inspectAnother, className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-sky-600 px-2.5 py-1.5 text-xs font-semibold text-white ring-1 ring-inset ring-sky-500 hover:bg-sky-500", children: [_jsx("span", { className: "text-sm", children: "\u2316" }), "Inspect Another"] }), _jsx("button", { type: "button", onClick: clearSelection, className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-slate-700 px-2.5 py-1.5 text-xs font-semibold text-slate-200 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-white", children: "Clear" })] }))] })] }), error && (_jsx("div", { className: "mt-3 rounded-xl bg-rose-500/10 px-3 py-2 text-xs text-rose-200 ring-1 ring-inset ring-rose-500/20", children: error })), state === 'inspecting' && (_jsxs("div", { className: "mt-4 flex items-center gap-2 rounded-xl bg-sky-500/10 px-3 py-2 text-xs text-sky-200 ring-1 ring-inset ring-sky-500/20", children: [_jsx("span", { className: "inline-block h-2 w-2 animate-pulse rounded-full bg-sky-400" }), "Inspection mode active. Click any component with the", ' ', _jsx("code", { className: "rounded bg-white/10 px-1 py-0.5 font-mono", children: "Flow-Component" }), " class."] })), state === 'selected' && selectedComponent && (_jsxs("div", { className: "mt-4 flex min-h-0 flex-1 flex-col gap-3 overflow-hidden", children: [_jsx("div", { className: "rounded-xl bg-white/5 p-3 ring-1 ring-inset ring-white/10", children: _jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] font-medium uppercase tracking-wider text-slate-300 -mb-0.5", children: "Type" }), _jsx("span", { className: "text-xs font-semibold text-teal-300", children: selectedComponent.type })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] font-medium uppercase tracking-wider text-slate-300 -mb-0.5", children: "ID" }), _jsx("code", { className: "rounded bg-white/10 px-1.5 py-0.5 font-mono text-xs text-slate-200", children: selectedComponentId })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] font-medium uppercase tracking-wider text-slate-300 -mb-0.5", children: "Key" }), _jsx("code", { className: "rounded bg-white/10 px-1.5 py-0.5 font-mono text-xs text-slate-200", children: selectedComponent.key })] }), selectedComponent.tags && selectedComponent.tags.length > 0 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] font-medium uppercase tracking-wider text-slate-300 -mb-0.5", children: "Tags" }), _jsx("div", { className: "flex flex-wrap gap-1", children: selectedComponent.tags.map((tag) => (_jsx("code", { className: "rounded bg-amber-500/20 px-1.5 py-0.5 font-mono text-xs text-amber-200", children: tag }, tag))) })] }))] }) }), _jsxs("div", { className: "flex min-h-0 flex-1 gap-3", children: [_jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [_jsx("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-slate-400", children: "Component JSON" }), _jsx("input", { type: "text", value: keyFilter, onChange: (e) => setKeyFilter(e.target.value), placeholder: "Filter by key...", className: "w-32 rounded-lg bg-slate-800 px-2 py-1.5 text-xs text-slate-100 ring-1 ring-inset ring-slate-600 placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-sky-500/60" })] }), _jsx("pre", { className: "min-h-0 flex-1 overflow-auto rounded-xl bg-slate-950/60 p-3 font-mono text-xs leading-5 text-slate-100 ring-1 ring-inset ring-white/10", children: JSON.stringify(keyFilter.trim()
339
+ ? filterJsonByKey(selectedComponent, keyFilter)
340
+ : selectedComponent, null, 2) })] }), _jsx(RelatedStylesPanel, { styles: flowStyles, component: selectedComponent, page: currentPage })] })] })), state === 'idle' && !error && (_jsxs("div", { className: "mt-4 flex min-h-0 flex-1 flex-col gap-3 overflow-hidden", children: [_jsx("div", { className: "text-[11px] text-slate-400", children: "Click \"Start Inspecting\" to begin selecting components." }), currentPage && (_jsxs("div", { className: "flex min-h-0 flex-1 gap-3", children: [_jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [_jsx("div", { className: "mb-2 text-[11px] font-semibold uppercase tracking-wider text-slate-400", children: "Current Page" }), _jsx("div", { className: "rounded-xl bg-white/5 p-3 ring-1 ring-inset ring-white/10", children: _jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] font-medium uppercase tracking-wider text-slate-300 -mb-0.5", children: "Key" }), _jsx("code", { className: "rounded bg-white/10 px-1.5 py-0.5 font-mono text-xs text-slate-200", children: currentPage.key })] }), currentPage.tags && currentPage.tags.length > 0 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] font-medium uppercase tracking-wider text-slate-300 -mb-0.5", children: "Tags" }), _jsx("div", { className: "flex flex-wrap gap-1", children: currentPage.tags.map((tag) => (_jsx("code", { className: "rounded bg-sky-500/20 px-1.5 py-0.5 font-mono text-xs text-sky-200", children: tag }, tag))) })] }))] }) })] }), _jsx(RelatedStylesPanel, { styles: flowStyles, component: null, page: currentPage })] }))] }))] }));
341
+ }
@@ -0,0 +1,6 @@
1
+ type PageNavigatorProps = {
2
+ embeddableId: string;
3
+ };
4
+ export declare function PageNavigator({ embeddableId }: PageNavigatorProps): import("react/jsx-runtime").JSX.Element | null;
5
+ export {};
6
+ //# sourceMappingURL=PageNavigator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageNavigator.d.ts","sourceRoot":"","sources":["../../src/workbench/PageNavigator.tsx"],"names":[],"mappings":"AAGA,KAAK,kBAAkB,GAAG;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AA2BD,wBAAgB,aAAa,CAAC,EAAE,YAAY,EAAE,EAAE,kBAAkB,kDAkMjE"}
@@ -0,0 +1,123 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState, useRef } from 'react';
3
+ const USERDATA_UPDATED_EVENT = 'embeddables:userdata_updated';
4
+ export function PageNavigator({ embeddableId }) {
5
+ const [pages, setPages] = useState([]);
6
+ const [currentPageKey, setCurrentPageKey] = useState('');
7
+ const [currentPageIndex, setCurrentPageIndex] = useState(0);
8
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
9
+ const dropdownRef = useRef(null);
10
+ const dropdownListRef = useRef(null);
11
+ const savvy = window.Savvy;
12
+ const refresh = useCallback(() => {
13
+ if (!savvy?.getFlowJson || !savvy?.getUserData)
14
+ return;
15
+ try {
16
+ const flowJson = savvy.getFlowJson(embeddableId);
17
+ if (flowJson?.pages) {
18
+ setPages(flowJson.pages.map((p) => ({ id: p.id, key: p.key })));
19
+ }
20
+ const userData = savvy.getUserData(embeddableId);
21
+ if (userData) {
22
+ setCurrentPageKey(userData.current_page_key || '');
23
+ setCurrentPageIndex(userData.current_page_index ?? 0);
24
+ }
25
+ }
26
+ catch {
27
+ // ignore errors
28
+ }
29
+ }, [embeddableId, savvy]);
30
+ useEffect(() => {
31
+ const timer = setTimeout(refresh, 100);
32
+ return () => clearTimeout(timer);
33
+ }, [refresh]);
34
+ useEffect(() => {
35
+ const handler = (event) => {
36
+ const detail = event?.detail;
37
+ const eventEmbeddableId = detail?.embeddableId;
38
+ if (typeof eventEmbeddableId === 'string' &&
39
+ eventEmbeddableId.length > 0 &&
40
+ eventEmbeddableId !== embeddableId) {
41
+ return;
42
+ }
43
+ refresh();
44
+ };
45
+ window.addEventListener(USERDATA_UPDATED_EVENT, handler);
46
+ return () => window.removeEventListener(USERDATA_UPDATED_EVENT, handler);
47
+ }, [embeddableId, refresh]);
48
+ // Close dropdown when clicking outside
49
+ useEffect(() => {
50
+ const handleClickOutside = (event) => {
51
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
52
+ setIsDropdownOpen(false);
53
+ }
54
+ };
55
+ if (isDropdownOpen) {
56
+ document.addEventListener('mousedown', handleClickOutside);
57
+ return () => document.removeEventListener('mousedown', handleClickOutside);
58
+ }
59
+ }, [isDropdownOpen]);
60
+ // Scroll to current page when dropdown opens
61
+ useEffect(() => {
62
+ if (isDropdownOpen && dropdownListRef.current) {
63
+ const currentIndex = pages.findIndex((p) => p.key === currentPageKey);
64
+ if (currentIndex >= 0) {
65
+ const listEl = dropdownListRef.current;
66
+ const items = listEl.querySelectorAll('button');
67
+ const currentItem = items[currentIndex];
68
+ if (currentItem) {
69
+ const listHeight = listEl.clientHeight;
70
+ const itemTop = currentItem.offsetTop;
71
+ const itemHeight = currentItem.offsetHeight;
72
+ // Center the item in the list
73
+ const scrollTop = itemTop - listHeight / 2 + itemHeight / 2;
74
+ listEl.scrollTop = Math.max(0, scrollTop);
75
+ }
76
+ }
77
+ }
78
+ }, [isDropdownOpen, pages, currentPageKey]);
79
+ const handlePrevPage = (e) => {
80
+ e.stopPropagation();
81
+ // Cmd (Mac) or Ctrl (Windows) + click: jump directly by page ID
82
+ if ((e.metaKey || e.ctrlKey) && savvy?.goToPage) {
83
+ const prevPage = pages[currentPageIndex - 1];
84
+ if (prevPage) {
85
+ savvy.goToPage(embeddableId, prevPage.id);
86
+ }
87
+ }
88
+ else if (savvy?.goToPrevPage) {
89
+ savvy.goToPrevPage(embeddableId);
90
+ }
91
+ };
92
+ const handleNextPage = (e) => {
93
+ e.stopPropagation();
94
+ // Cmd (Mac) or Ctrl (Windows) + click: jump directly by page ID
95
+ if ((e.metaKey || e.ctrlKey) && savvy?.goToPage) {
96
+ const nextPage = pages[currentPageIndex + 1];
97
+ if (nextPage) {
98
+ savvy.goToPage(embeddableId, nextPage.id);
99
+ }
100
+ }
101
+ else if (savvy?.goToNextPage) {
102
+ savvy.goToNextPage(embeddableId);
103
+ }
104
+ };
105
+ const handleSelectPage = (pageId) => {
106
+ if (savvy?.goToPage) {
107
+ savvy.goToPage(embeddableId, pageId);
108
+ }
109
+ setIsDropdownOpen(false);
110
+ };
111
+ const toggleDropdown = (e) => {
112
+ e.stopPropagation();
113
+ setIsDropdownOpen(!isDropdownOpen);
114
+ };
115
+ if (pages.length === 0)
116
+ return null;
117
+ const isFirstPage = currentPageIndex === 0;
118
+ const isLastPage = currentPageIndex >= pages.length - 1;
119
+ const absolutePageIndex = pages.findIndex((p) => p.key === currentPageKey);
120
+ return (_jsxs("div", { className: "flex items-center gap-1", ref: dropdownRef, children: [_jsx("button", { type: "button", onClick: handlePrevPage, disabled: isFirstPage, title: "Previous page (Cmd/Ctrl+click to jump by ID)", className: "grid h-6 w-6 cursor-pointer place-items-center rounded-md bg-white/5 text-slate-300 ring-1 ring-inset ring-white/10 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-white/5 disabled:hover:text-slate-300", children: _jsx("span", { className: "text-[10px] leading-none", children: "\u25C0" }) }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: toggleDropdown, title: currentPageKey, className: "flex h-6 cursor-pointer items-center gap-1 rounded-md bg-white/5 px-2 text-[11px] text-slate-300 ring-1 ring-inset ring-white/10 transition-colors hover:bg-white/10 hover:text-white", children: [_jsxs("span", { className: "text-slate-400", children: [absolutePageIndex + 1, "."] }), _jsx("span", { className: "max-w-[120px] truncate", children: currentPageKey || 'Loading...' }), _jsx("span", { className: "text-[8px] leading-none", children: isDropdownOpen ? '▲' : '▼' })] }), isDropdownOpen && (_jsx("div", { ref: dropdownListRef, className: "absolute right-0 bottom-full z-50 mb-1 max-h-64 min-w-[180px] overflow-auto rounded-lg bg-slate-900 py-1 shadow-xl ring-1 ring-white/10", children: pages.map((page, index) => (_jsxs("button", { type: "button", onClick: () => handleSelectPage(page.id), className: `w-full cursor-pointer px-3 py-1.5 whitespace-nowrap text-left text-[11px] transition-colors ${page.key === currentPageKey
121
+ ? 'bg-white/30 text-white'
122
+ : 'text-slate-300 hover:bg-white/5 hover:text-white'}`, children: [_jsx("span", { className: "mr-2 inline-block w-4 text-right text-[10px] text-slate-400", children: index + 1 }), _jsx("span", { className: "truncate", children: page.key })] }, page.id))) }))] }), _jsx("button", { type: "button", onClick: handleNextPage, disabled: isLastPage, title: "Next page (Cmd/Ctrl+click to jump by ID)", className: "grid h-6 w-6 cursor-pointer place-items-center rounded-md bg-white/5 text-slate-300 ring-1 ring-inset ring-white/10 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-white/5 disabled:hover:text-slate-300", children: _jsx("span", { className: "text-[10px] leading-none", children: "\u25B6" }) })] }));
123
+ }
@@ -0,0 +1,6 @@
1
+ type SchemaPanelProps = {
2
+ embeddableId: string;
3
+ };
4
+ export declare function SchemaPanel({ embeddableId }: SchemaPanelProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=SchemaPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SchemaPanel.d.ts","sourceRoot":"","sources":["../../src/workbench/SchemaPanel.tsx"],"names":[],"mappings":"AAGA,KAAK,gBAAgB,GAAG;IACtB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AA4DD,wBAAgB,WAAW,CAAC,EAAE,YAAY,EAAE,EAAE,gBAAgB,2CA6O7D"}