@strapi/content-manager 0.0.0-experimental.e5d4b412da0d932b61b2fa5012d16513fda6de04 → 0.0.0-experimental.e65c671348c825470427c509f1273497b0b4b828

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 (83) hide show
  1. package/dist/admin/components/LeftMenu.js +13 -15
  2. package/dist/admin/components/LeftMenu.js.map +1 -1
  3. package/dist/admin/components/LeftMenu.mjs +14 -16
  4. package/dist/admin/components/LeftMenu.mjs.map +1 -1
  5. package/dist/admin/components/Widgets.js +4 -2
  6. package/dist/admin/components/Widgets.js.map +1 -1
  7. package/dist/admin/components/Widgets.mjs +4 -2
  8. package/dist/admin/components/Widgets.mjs.map +1 -1
  9. package/dist/admin/pages/EditView/EditViewPage.js +3 -11
  10. package/dist/admin/pages/EditView/EditViewPage.js.map +1 -1
  11. package/dist/admin/pages/EditView/EditViewPage.mjs +4 -12
  12. package/dist/admin/pages/EditView/EditViewPage.mjs.map +1 -1
  13. package/dist/admin/pages/EditView/components/Blocker.js +18 -0
  14. package/dist/admin/pages/EditView/components/Blocker.js.map +1 -0
  15. package/dist/admin/pages/EditView/components/Blocker.mjs +16 -0
  16. package/dist/admin/pages/EditView/components/Blocker.mjs.map +1 -0
  17. package/dist/admin/pages/EditView/components/InputRenderer.js +20 -7
  18. package/dist/admin/pages/EditView/components/InputRenderer.js.map +1 -1
  19. package/dist/admin/pages/EditView/components/InputRenderer.mjs +20 -7
  20. package/dist/admin/pages/EditView/components/InputRenderer.mjs.map +1 -1
  21. package/dist/admin/pages/EditView/utils/data.js +27 -8
  22. package/dist/admin/pages/EditView/utils/data.js.map +1 -1
  23. package/dist/admin/pages/EditView/utils/data.mjs +27 -8
  24. package/dist/admin/pages/EditView/utils/data.mjs.map +1 -1
  25. package/dist/admin/preview/components/InputPopover.js +189 -0
  26. package/dist/admin/preview/components/InputPopover.js.map +1 -0
  27. package/dist/admin/preview/components/InputPopover.mjs +167 -0
  28. package/dist/admin/preview/components/InputPopover.mjs.map +1 -0
  29. package/dist/admin/preview/components/PreviewHeader.js +0 -1
  30. package/dist/admin/preview/components/PreviewHeader.js.map +1 -1
  31. package/dist/admin/preview/components/PreviewHeader.mjs +0 -1
  32. package/dist/admin/preview/components/PreviewHeader.mjs.map +1 -1
  33. package/dist/admin/preview/hooks/usePreviewInputManager.js +77 -0
  34. package/dist/admin/preview/hooks/usePreviewInputManager.js.map +1 -0
  35. package/dist/admin/preview/hooks/usePreviewInputManager.mjs +56 -0
  36. package/dist/admin/preview/hooks/usePreviewInputManager.mjs.map +1 -0
  37. package/dist/admin/preview/pages/Preview.js +122 -119
  38. package/dist/admin/preview/pages/Preview.js.map +1 -1
  39. package/dist/admin/preview/pages/Preview.mjs +122 -119
  40. package/dist/admin/preview/pages/Preview.mjs.map +1 -1
  41. package/dist/admin/preview/utils/constants.js +36 -1
  42. package/dist/admin/preview/utils/constants.js.map +1 -1
  43. package/dist/admin/preview/utils/constants.mjs +35 -2
  44. package/dist/admin/preview/utils/constants.mjs.map +1 -1
  45. package/dist/admin/preview/utils/fieldUtils.js +107 -0
  46. package/dist/admin/preview/utils/fieldUtils.js.map +1 -0
  47. package/dist/admin/preview/utils/fieldUtils.mjs +102 -0
  48. package/dist/admin/preview/utils/fieldUtils.mjs.map +1 -0
  49. package/dist/admin/preview/utils/getSendMessage.js +22 -0
  50. package/dist/admin/preview/utils/getSendMessage.js.map +1 -0
  51. package/dist/admin/preview/utils/getSendMessage.mjs +20 -0
  52. package/dist/admin/preview/utils/getSendMessage.mjs.map +1 -0
  53. package/dist/admin/preview/utils/previewScript.js +423 -92
  54. package/dist/admin/preview/utils/previewScript.js.map +1 -1
  55. package/dist/admin/preview/utils/previewScript.mjs +423 -92
  56. package/dist/admin/preview/utils/previewScript.mjs.map +1 -1
  57. package/dist/admin/src/pages/EditView/components/Blocker.d.ts +5 -0
  58. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +1 -1
  59. package/dist/admin/src/preview/components/InputPopover.d.ts +6 -0
  60. package/dist/admin/src/preview/hooks/usePreviewInputManager.d.ts +5 -0
  61. package/dist/admin/src/preview/pages/Preview.d.ts +12 -0
  62. package/dist/admin/src/preview/utils/constants.d.ts +39 -1
  63. package/dist/admin/src/preview/utils/fieldUtils.d.ts +22 -0
  64. package/dist/admin/src/preview/utils/getSendMessage.d.ts +11 -0
  65. package/dist/admin/src/preview/utils/previewScript.d.ts +7 -1
  66. package/dist/admin/translations/en.json.js +6 -0
  67. package/dist/admin/translations/en.json.js.map +1 -1
  68. package/dist/admin/translations/en.json.mjs +6 -0
  69. package/dist/admin/translations/en.json.mjs.map +1 -1
  70. package/dist/admin/translations/es.json.js +1 -0
  71. package/dist/admin/translations/es.json.js.map +1 -1
  72. package/dist/admin/translations/es.json.mjs +1 -0
  73. package/dist/admin/translations/es.json.mjs.map +1 -1
  74. package/dist/admin/translations/fr.json.js +1 -0
  75. package/dist/admin/translations/fr.json.js.map +1 -1
  76. package/dist/admin/translations/fr.json.mjs +1 -0
  77. package/dist/admin/translations/fr.json.mjs.map +1 -1
  78. package/dist/server/homepage/services/homepage.js +1 -1
  79. package/dist/server/homepage/services/homepage.js.map +1 -1
  80. package/dist/server/homepage/services/homepage.mjs +1 -1
  81. package/dist/server/homepage/services/homepage.mjs.map +1 -1
  82. package/dist/server/src/homepage/services/homepage.d.ts.map +1 -1
  83. package/package.json +5 -5
@@ -9,10 +9,18 @@
9
9
  * Params
10
10
  * ---------------------------------------------------------------------------------------------*/ const HIGHLIGHT_PADDING = 2; // in pixels
11
11
  const HIGHLIGHT_HOVER_COLOR = window.STRAPI_HIGHLIGHT_HOVER_COLOR ?? '#4945ff'; // dark primary500
12
+ const HIGHLIGHT_ACTIVE_COLOR = window.STRAPI_HIGHLIGHT_ACTIVE_COLOR ?? '#7b79ff'; // dark primary600
13
+ const HIGHLIGHT_STYLES_ID = 'strapi-preview-highlight-styles';
14
+ const DOUBLE_CLICK_TIMEOUT = 300; // milliseconds to wait for potential double-click
15
+ const DISABLE_STEGA_DECODING = window.STRAPI_DISABLE_STEGA_DECODING ?? false;
12
16
  const SOURCE_ATTRIBUTE = 'data-strapi-source';
13
17
  const OVERLAY_ID = 'strapi-preview-overlay';
14
18
  const INTERNAL_EVENTS = {
15
- DUMMY_EVENT: 'dummyEvent'
19
+ STRAPI_FIELD_FOCUS: 'strapiFieldFocus',
20
+ STRAPI_FIELD_BLUR: 'strapiFieldBlur',
21
+ STRAPI_FIELD_CHANGE: 'strapiFieldChange',
22
+ STRAPI_FIELD_FOCUS_INTENT: 'strapiFieldFocusIntent',
23
+ STRAPI_FIELD_SINGLE_CLICK_HINT: 'strapiFieldSingleClickHint'
16
24
  };
17
25
  /**
18
26
  * Calling the function in no-run mode lets us retrieve the constants from other files and keep
@@ -24,8 +32,111 @@
24
32
  };
25
33
  }
26
34
  /* -----------------------------------------------------------------------------------------------
35
+ * Utils
36
+ * ---------------------------------------------------------------------------------------------*/ const sendMessage = (type, payload)=>{
37
+ window.parent.postMessage({
38
+ type,
39
+ payload
40
+ }, '*');
41
+ };
42
+ const getElementsByPath = (path)=>{
43
+ return document.querySelectorAll(`[${SOURCE_ATTRIBUTE}*="path=${path}"]`);
44
+ };
45
+ /* -----------------------------------------------------------------------------------------------
27
46
  * Functionality pieces
28
- * ---------------------------------------------------------------------------------------------*/ const createOverlaySystem = ()=>{
47
+ * ---------------------------------------------------------------------------------------------*/ const setupStegaDOMObserver = async ()=>{
48
+ if (DISABLE_STEGA_DECODING) {
49
+ return;
50
+ }
51
+ const { vercelStegaDecode: stegaDecode, vercelStegaClean: stegaClean } = await import(// @ts-expect-error it's not a local dependency
52
+ // eslint-disable-next-line import/no-unresolved
53
+ 'https://cdn.jsdelivr.net/npm/@vercel/stega@0.1.2/+esm');
54
+ const applyStegaToElement = (element)=>{
55
+ const directTextNodes = Array.from(element.childNodes).filter((node)=>node.nodeType === Node.TEXT_NODE);
56
+ const directTextContent = directTextNodes.map((node)=>node.textContent || '').join('');
57
+ if (directTextContent) {
58
+ try {
59
+ // TODO: check if we can call split instead of decode+clean
60
+ const result = stegaDecode(directTextContent);
61
+ if (result && 'strapiSource' in result) {
62
+ element.setAttribute(SOURCE_ATTRIBUTE, result.strapiSource);
63
+ // Remove encoded part from DOM text content (to avoid breaking links for example)
64
+ directTextNodes.forEach((node)=>{
65
+ if (node.textContent) {
66
+ const cleanedText = stegaClean(node.textContent);
67
+ if (cleanedText !== node.textContent) {
68
+ node.textContent = cleanedText;
69
+ }
70
+ }
71
+ });
72
+ }
73
+ } catch (error) {}
74
+ }
75
+ };
76
+ // Process all existing elements
77
+ const allElements = document.querySelectorAll('*');
78
+ Array.from(allElements).forEach(applyStegaToElement);
79
+ // Create observer for new elements and text changes
80
+ const observer = new MutationObserver((mutations)=>{
81
+ mutations.forEach((mutation)=>{
82
+ // Handle added nodes
83
+ if (mutation.type === 'childList') {
84
+ mutation.addedNodes.forEach((node)=>{
85
+ if (node.nodeType === Node.ELEMENT_NODE) {
86
+ const element = node;
87
+ // Process the added element
88
+ applyStegaToElement(element);
89
+ // Process all child elements
90
+ const childElements = element.querySelectorAll('*');
91
+ Array.from(childElements).forEach(applyStegaToElement);
92
+ }
93
+ });
94
+ }
95
+ // Handle text content changes
96
+ if (mutation.type === 'characterData' && mutation.target.parentElement) {
97
+ applyStegaToElement(mutation.target.parentElement);
98
+ }
99
+ });
100
+ });
101
+ observer.observe(document, {
102
+ childList: true,
103
+ subtree: true,
104
+ characterData: true
105
+ });
106
+ return observer;
107
+ };
108
+ const createHighlightStyles = ()=>{
109
+ const existingStyles = document.getElementById(HIGHLIGHT_STYLES_ID);
110
+ // Remove existing styles to avoid duplicates
111
+ if (existingStyles) {
112
+ existingStyles.remove();
113
+ }
114
+ const styleElement = document.createElement('style');
115
+ styleElement.id = HIGHLIGHT_STYLES_ID;
116
+ styleElement.textContent = `
117
+ .strapi-highlight {
118
+ position: absolute;
119
+ outline: 2px solid transparent;
120
+ pointer-events: auto;
121
+ border-radius: 2px;
122
+ background-color: transparent;
123
+ will-change: transform;
124
+ transition: outline-color 0.1s ease-in-out;
125
+ }
126
+
127
+ .strapi-highlight:hover {
128
+ outline-color: ${HIGHLIGHT_HOVER_COLOR} !important;
129
+ }
130
+
131
+ .strapi-highlight.strapi-highlight-focused {
132
+ outline-color: ${HIGHLIGHT_ACTIVE_COLOR} !important;
133
+ outline-width: 3px !important;
134
+ }
135
+ `;
136
+ document.head.appendChild(styleElement);
137
+ return styleElement;
138
+ };
139
+ const createOverlaySystem = ()=>{
29
140
  // Clean up before creating a new overlay so we can safely call previewScript multiple times
30
141
  window.__strapi_previewCleanup?.();
31
142
  document.getElementById(OVERLAY_ID)?.remove();
@@ -44,9 +155,11 @@
44
155
  return overlay;
45
156
  };
46
157
  const createHighlightManager = (overlay)=>{
47
- const elements = window.document.querySelectorAll(`[${SOURCE_ATTRIBUTE}]`);
48
- const highlights = [];
158
+ const elementsToHighlight = new Map();
49
159
  const eventListeners = [];
160
+ const focusedHighlights = [];
161
+ const pendingClicks = new Map(); // number is timeout id
162
+ let focusedField = null;
50
163
  const drawHighlight = (target, highlight)=>{
51
164
  if (!highlight) return;
52
165
  const rect = target.getBoundingClientRect();
@@ -55,90 +168,169 @@
55
168
  highlight.style.transform = `translate(${rect.left - HIGHLIGHT_PADDING}px, ${rect.top - HIGHLIGHT_PADDING}px)`;
56
169
  };
57
170
  const updateAllHighlights = ()=>{
58
- highlights.forEach((highlight, index)=>{
59
- const element = elements[index];
60
- if (element && highlight) {
61
- drawHighlight(element, highlight);
171
+ elementsToHighlight.forEach((highlight, element)=>{
172
+ drawHighlight(element, highlight);
173
+ });
174
+ };
175
+ const createHighlightForElement = (element)=>{
176
+ if (elementsToHighlight.has(element)) {
177
+ // Already has a highlight
178
+ return;
179
+ }
180
+ const highlight = document.createElement('div');
181
+ highlight.className = 'strapi-highlight';
182
+ const clickHandler = (event)=>{
183
+ // Skip if this is a re-dispatched event from our delayed handler to avoid infinite loops
184
+ if (event.__strapi_redispatched) {
185
+ return;
62
186
  }
187
+ // Prevent the immediate action for interactive elements
188
+ event.preventDefault();
189
+ event.stopPropagation();
190
+ // Clear any existing timeout for this element
191
+ const existingTimeout = pendingClicks.get(element);
192
+ if (existingTimeout) {
193
+ window.clearTimeout(existingTimeout);
194
+ pendingClicks.delete(element);
195
+ }
196
+ // Set up a delayed single-click handler
197
+ const timeout = window.setTimeout(()=>{
198
+ pendingClicks.delete(element);
199
+ // Send single-click hint notification
200
+ sendMessage(INTERNAL_EVENTS.STRAPI_FIELD_SINGLE_CLICK_HINT, null);
201
+ // Re-trigger the click on the underlying element after the double-click timeout
202
+ // Create a new event to dispatch with a marker to prevent re-handling
203
+ const newEvent = new MouseEvent('click', {
204
+ bubbles: true,
205
+ cancelable: true,
206
+ view: window,
207
+ detail: 1,
208
+ button: event.button,
209
+ buttons: event.buttons,
210
+ clientX: event.clientX,
211
+ clientY: event.clientY,
212
+ ctrlKey: event.ctrlKey,
213
+ altKey: event.altKey,
214
+ shiftKey: event.shiftKey,
215
+ metaKey: event.metaKey
216
+ });
217
+ newEvent.__strapi_redispatched = true;
218
+ element.dispatchEvent(newEvent);
219
+ }, DOUBLE_CLICK_TIMEOUT);
220
+ pendingClicks.set(element, timeout);
221
+ };
222
+ const doubleClickHandler = (event)=>{
223
+ // Prevent the default behavior on double-click
224
+ event.preventDefault();
225
+ event.stopPropagation();
226
+ // Clear any pending single-click action
227
+ const existingTimeout = pendingClicks.get(element);
228
+ if (existingTimeout) {
229
+ clearTimeout(existingTimeout);
230
+ pendingClicks.delete(element);
231
+ }
232
+ const sourceAttribute = element.getAttribute(SOURCE_ATTRIBUTE);
233
+ if (sourceAttribute) {
234
+ const rect = element.getBoundingClientRect();
235
+ sendMessage(INTERNAL_EVENTS.STRAPI_FIELD_FOCUS_INTENT, {
236
+ path: sourceAttribute,
237
+ position: {
238
+ top: rect.top,
239
+ left: rect.left,
240
+ right: rect.right,
241
+ bottom: rect.bottom,
242
+ width: rect.width,
243
+ height: rect.height
244
+ }
245
+ });
246
+ }
247
+ };
248
+ const mouseDownHandler = (event)=>{
249
+ // Prevent default multi click to select behavior
250
+ if (event.detail >= 2) {
251
+ event.preventDefault();
252
+ }
253
+ };
254
+ highlight.addEventListener('click', clickHandler);
255
+ highlight.addEventListener('dblclick', doubleClickHandler);
256
+ highlight.addEventListener('mousedown', mouseDownHandler);
257
+ // Store event listeners for cleanup
258
+ eventListeners.push({
259
+ element: highlight,
260
+ type: 'click',
261
+ handler: clickHandler
262
+ }, {
263
+ element: highlight,
264
+ type: 'dblclick',
265
+ handler: doubleClickHandler
266
+ }, {
267
+ element: highlight,
268
+ type: 'mousedown',
269
+ handler: mouseDownHandler
270
+ });
271
+ elementsToHighlight.set(element, highlight);
272
+ overlay.appendChild(highlight);
273
+ drawHighlight(element, highlight);
274
+ };
275
+ const removeHighlightForElement = (element)=>{
276
+ const highlight = elementsToHighlight.get(element);
277
+ if (!highlight) return;
278
+ // Clear any pending click timeout for this element
279
+ const pendingTimeout = pendingClicks.get(element);
280
+ if (pendingTimeout) {
281
+ window.clearTimeout(pendingTimeout);
282
+ pendingClicks.delete(element);
283
+ }
284
+ highlight.remove();
285
+ elementsToHighlight.delete(element);
286
+ // Remove event listeners for this highlight
287
+ const listenersToRemove = eventListeners.filter((listener)=>listener.element === highlight);
288
+ listenersToRemove.forEach(({ element, type, handler })=>{
289
+ element.removeEventListener(type, handler);
63
290
  });
291
+ // Mutate eventListeners to remove listeners for this highlight
292
+ eventListeners.splice(0, eventListeners.length, ...eventListeners.filter((listener)=>listener.element !== highlight));
64
293
  };
65
- elements.forEach((element)=>{
294
+ // Process all existing elements with source attributes
295
+ const initialElements = window.document.querySelectorAll(`[${SOURCE_ATTRIBUTE}]`);
296
+ Array.from(initialElements).forEach((element)=>{
66
297
  if (element instanceof HTMLElement) {
67
- const highlight = document.createElement('div');
68
- highlight.style.cssText = `
69
- position: absolute;
70
- outline: 2px solid transparent;
71
- pointer-events: none;
72
- border-radius: 2px;
73
- background-color: transparent;
74
- will-change: transform;
75
- transition: outline-color 0.1s ease-in-out;
76
- `;
77
- // Move hover detection to the underlying element
78
- const mouseEnterHandler = ()=>{
79
- highlight.style.outlineColor = HIGHLIGHT_HOVER_COLOR;
80
- };
81
- const mouseLeaveHandler = ()=>{
82
- highlight.style.outlineColor = 'transparent';
83
- };
84
- const doubleClickHandler = ()=>{
85
- // TODO: handle for real
86
- // eslint-disable-next-line no-console
87
- console.log('Double click on highlight', element);
88
- };
89
- const mouseDownHandler = (event)=>{
90
- // Prevent default multi click to select behavior
91
- if (event.detail >= 2) {
92
- event.preventDefault();
93
- }
94
- };
95
- element.addEventListener('mouseenter', mouseEnterHandler);
96
- element.addEventListener('mouseleave', mouseLeaveHandler);
97
- element.addEventListener('dblclick', doubleClickHandler);
98
- element.addEventListener('mousedown', mouseDownHandler);
99
- // Store event listeners for cleanup
100
- eventListeners.push({
101
- element,
102
- type: 'mouseenter',
103
- handler: mouseEnterHandler
104
- }, {
105
- element,
106
- type: 'mouseleave',
107
- handler: mouseLeaveHandler
108
- }, {
109
- element,
110
- type: 'dblclick',
111
- handler: doubleClickHandler
112
- }, {
113
- element,
114
- type: 'mousedown',
115
- handler: mouseDownHandler
116
- });
117
- highlights.push(highlight);
118
- overlay.appendChild(highlight);
119
- drawHighlight(element, highlight);
298
+ createHighlightForElement(element);
120
299
  }
121
300
  });
122
301
  return {
123
- elements,
302
+ get elements () {
303
+ return Array.from(elementsToHighlight.keys());
304
+ },
305
+ get highlights () {
306
+ return Array.from(elementsToHighlight.values());
307
+ },
124
308
  updateAllHighlights,
125
- eventListeners
309
+ eventListeners,
310
+ focusedHighlights,
311
+ createHighlightForElement,
312
+ removeHighlightForElement,
313
+ setFocusedField: (field)=>{
314
+ focusedField = field;
315
+ },
316
+ getFocusedField: ()=>focusedField,
317
+ clearAllPendingClicks: ()=>{
318
+ pendingClicks.forEach((timeout)=>clearTimeout(timeout));
319
+ pendingClicks.clear();
320
+ }
126
321
  };
127
322
  };
128
- const setupObservers = (highlightManager)=>{
129
- const resizeObserver = new ResizeObserver(()=>{
130
- highlightManager.updateAllHighlights();
131
- });
132
- highlightManager.elements.forEach((element)=>{
133
- resizeObserver.observe(element);
134
- });
135
- resizeObserver.observe(document.documentElement);
323
+ /**
324
+ * We need to track scroll in all the element parents in order to keep the highlight position
325
+ * in sync with the element position. Listening to window scroll is not enough because the
326
+ * element can be inside one or more scrollable containers.
327
+ */ const setupScrollManagement = (highlightManager)=>{
136
328
  const updateOnScroll = ()=>{
137
329
  highlightManager.updateAllHighlights();
138
330
  };
139
331
  const scrollableElements = new Set();
140
332
  scrollableElements.add(window);
141
- // Find all scrollable ancestors for all tracked elements
333
+ // Find all scrollable ancestors for all tracked elements and set up scroll listeners
142
334
  highlightManager.elements.forEach((element)=>{
143
335
  let parent = element.parentElement;
144
336
  while(parent){
@@ -159,42 +351,181 @@
159
351
  element.addEventListener('scroll', updateOnScroll);
160
352
  }
161
353
  });
354
+ const cleanup = ()=>{
355
+ scrollableElements.forEach((element)=>{
356
+ if (element === window) {
357
+ window.removeEventListener('scroll', updateOnScroll);
358
+ window.removeEventListener('resize', updateOnScroll);
359
+ } else {
360
+ element.removeEventListener('scroll', updateOnScroll);
361
+ }
362
+ });
363
+ };
364
+ return {
365
+ cleanup
366
+ };
367
+ };
368
+ const setupObservers = (highlightManager, stegaObserver)=>{
369
+ const resizeObserver = new ResizeObserver(()=>{
370
+ highlightManager.updateAllHighlights();
371
+ });
372
+ const observeElementForResize = (element)=>{
373
+ resizeObserver.observe(element);
374
+ };
375
+ // Observe existing elements
376
+ highlightManager.elements.forEach(observeElementForResize);
377
+ resizeObserver.observe(document.documentElement);
378
+ // Create highlight observer to watch for new elements with source attributes
379
+ const highlightObserver = new MutationObserver((mutations)=>{
380
+ mutations.forEach((mutation)=>{
381
+ if (mutation.type === 'attributes' && mutation.attributeName === SOURCE_ATTRIBUTE) {
382
+ const target = mutation.target;
383
+ if (target.hasAttribute(SOURCE_ATTRIBUTE)) {
384
+ highlightManager.createHighlightForElement(target);
385
+ observeElementForResize(target);
386
+ } else {
387
+ highlightManager.removeHighlightForElement(target);
388
+ }
389
+ }
390
+ if (mutation.type === 'childList') {
391
+ mutation.addedNodes.forEach((node)=>{
392
+ if (node.nodeType === Node.ELEMENT_NODE) {
393
+ const element = node;
394
+ // Check if the added element has source attribute
395
+ if (element.hasAttribute(SOURCE_ATTRIBUTE) && element instanceof HTMLElement) {
396
+ highlightManager.createHighlightForElement(element);
397
+ observeElementForResize(element);
398
+ }
399
+ // Check all child elements for source attributes
400
+ const elementsWithSource = element.querySelectorAll(`[${SOURCE_ATTRIBUTE}]`);
401
+ Array.from(elementsWithSource).forEach((childElement)=>{
402
+ if (childElement instanceof HTMLElement) {
403
+ highlightManager.createHighlightForElement(childElement);
404
+ observeElementForResize(childElement);
405
+ }
406
+ });
407
+ }
408
+ });
409
+ mutation.removedNodes.forEach((node)=>{
410
+ if (node.nodeType === Node.ELEMENT_NODE) {
411
+ const element = node;
412
+ highlightManager.removeHighlightForElement(element);
413
+ }
414
+ });
415
+ }
416
+ });
417
+ });
418
+ highlightObserver.observe(document, {
419
+ childList: true,
420
+ subtree: true,
421
+ attributes: true,
422
+ attributeFilter: [
423
+ SOURCE_ATTRIBUTE
424
+ ]
425
+ });
162
426
  return {
163
427
  resizeObserver,
164
- updateOnScroll,
165
- scrollableElements
428
+ highlightObserver,
429
+ stegaObserver
166
430
  };
167
431
  };
168
432
  const setupEventHandlers = (highlightManager)=>{
169
- // TODO: The listeners for postMessage events will go here
170
- return highlightManager.eventListeners;
433
+ const handleMessage = (event)=>{
434
+ if (!event.data?.type) return;
435
+ // The user typed in an input, reflect the change in the preview
436
+ if (event.data.type === INTERNAL_EVENTS.STRAPI_FIELD_CHANGE) {
437
+ const { field, value } = event.data.payload;
438
+ if (!field) return;
439
+ getElementsByPath(field).forEach((element)=>{
440
+ if (element instanceof HTMLElement) {
441
+ element.textContent = value || '';
442
+ }
443
+ });
444
+ // Update highlight dimensions since the new text content may affect them
445
+ highlightManager.updateAllHighlights();
446
+ return;
447
+ }
448
+ // The user focused a new input, update the highlights in the preview
449
+ if (event.data.type === INTERNAL_EVENTS.STRAPI_FIELD_FOCUS) {
450
+ const { field } = event.data.payload;
451
+ if (!field) return;
452
+ // Clear existing focused highlights
453
+ highlightManager.focusedHighlights.forEach((highlight)=>{
454
+ highlight.classList.remove('strapi-highlight-focused');
455
+ });
456
+ highlightManager.focusedHighlights.length = 0;
457
+ // Set new focused field and highlight matching elements
458
+ highlightManager.setFocusedField(field);
459
+ getElementsByPath(field).forEach((element, index)=>{
460
+ if (index === 0) {
461
+ element.scrollIntoView({
462
+ behavior: 'smooth',
463
+ block: 'center'
464
+ });
465
+ }
466
+ const highlight = highlightManager.highlights[Array.from(highlightManager.elements).indexOf(element)];
467
+ if (highlight) {
468
+ highlight.classList.add('strapi-highlight-focused');
469
+ highlightManager.focusedHighlights.push(highlight);
470
+ }
471
+ });
472
+ return;
473
+ }
474
+ // The user is no longer focusing an input, remove the highlights
475
+ if (event.data.type === INTERNAL_EVENTS.STRAPI_FIELD_BLUR) {
476
+ const { field } = event.data.payload;
477
+ if (field !== highlightManager.getFocusedField()) return;
478
+ highlightManager.focusedHighlights.forEach((highlight)=>{
479
+ highlight.classList.remove('strapi-highlight-focused');
480
+ });
481
+ highlightManager.focusedHighlights.length = 0;
482
+ highlightManager.setFocusedField(null);
483
+ }
484
+ };
485
+ window.addEventListener('message', handleMessage);
486
+ // Add the message handler to the cleanup list
487
+ const messageEventListener = {
488
+ element: window,
489
+ type: 'message',
490
+ handler: handleMessage
491
+ };
492
+ return [
493
+ ...highlightManager.eventListeners,
494
+ messageEventListener
495
+ ];
171
496
  };
172
- const createCleanupSystem = (overlay, observers, eventHandlers)=>{
497
+ const createCleanupSystem = (overlay, observers, scrollManager, eventHandlers, highlightManager)=>{
173
498
  window.__strapi_previewCleanup = ()=>{
174
499
  observers.resizeObserver.disconnect();
175
- // Remove all scroll listeners
176
- observers.scrollableElements.forEach((element)=>{
177
- if (element === window) {
178
- window.removeEventListener('scroll', observers.updateOnScroll);
179
- window.removeEventListener('resize', observers.updateOnScroll);
180
- } else {
181
- element.removeEventListener('scroll', observers.updateOnScroll);
182
- }
183
- });
500
+ observers.highlightObserver.disconnect();
501
+ observers.stegaObserver?.disconnect();
502
+ // Clean up scroll listeners
503
+ scrollManager.cleanup();
504
+ // Clear all pending click timeouts
505
+ highlightManager.clearAllPendingClicks();
184
506
  // Remove highlight event listeners
185
507
  eventHandlers.forEach(({ element, type, handler })=>{
186
508
  element.removeEventListener(type, handler);
187
509
  });
510
+ // Clean up CSS styles
511
+ const existingStyles = document.getElementById(HIGHLIGHT_STYLES_ID);
512
+ if (existingStyles) {
513
+ existingStyles.remove();
514
+ }
188
515
  overlay.remove();
189
516
  };
190
517
  };
191
518
  /* -----------------------------------------------------------------------------------------------
192
519
  * Orchestration
193
- * ---------------------------------------------------------------------------------------------*/ const overlay = createOverlaySystem();
194
- const highlightManager = createHighlightManager(overlay);
195
- const observers = setupObservers(highlightManager);
196
- const eventHandlers = setupEventHandlers(highlightManager);
197
- createCleanupSystem(overlay, observers, eventHandlers);
520
+ * ---------------------------------------------------------------------------------------------*/ setupStegaDOMObserver().then((stegaObserver)=>{
521
+ createHighlightStyles();
522
+ const overlay = createOverlaySystem();
523
+ const highlightManager = createHighlightManager(overlay);
524
+ const observers = setupObservers(highlightManager, stegaObserver);
525
+ const scrollManager = setupScrollManagement(highlightManager);
526
+ const eventHandlers = setupEventHandlers(highlightManager);
527
+ createCleanupSystem(overlay, observers, scrollManager, eventHandlers, highlightManager);
528
+ });
198
529
  };
199
530
 
200
531
  export { previewScript };