@salesforce/storefront-next-runtime 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.
- package/LICENSE.txt +181 -0
- package/README.md +158 -0
- package/dist/ComponentContext.js +14 -0
- package/dist/ComponentContext.js.map +1 -0
- package/dist/DesignContext.js +769 -0
- package/dist/DesignContext.js.map +1 -0
- package/dist/DesignContext2.js +6 -0
- package/dist/PageDesignerProvider.js +53 -0
- package/dist/PageDesignerProvider.js.map +1 -0
- package/dist/PageRegistration.js +29 -0
- package/dist/PageRegistration.js.map +1 -0
- package/dist/PreviewContext.js +18 -0
- package/dist/PreviewContext.js.map +1 -0
- package/dist/design-messaging.d.ts +3 -0
- package/dist/design-messaging.js +3 -0
- package/dist/design-mode.d.ts +40 -0
- package/dist/design-mode.d.ts.map +1 -0
- package/dist/design-mode.js +3 -0
- package/dist/design-react-core.d.ts +69 -0
- package/dist/design-react-core.d.ts.map +1 -0
- package/dist/design-react-core.js +23 -0
- package/dist/design-react-core.js.map +1 -0
- package/dist/design-react.d.ts +130 -0
- package/dist/design-react.d.ts.map +1 -0
- package/dist/design-react.js +488 -0
- package/dist/design-react.js.map +1 -0
- package/dist/design-styles.css +232 -0
- package/dist/design.d.ts +2 -0
- package/dist/design.js +219 -0
- package/dist/design.js.map +1 -0
- package/dist/events.d.ts +208 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +104 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +215 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index2.d.ts +1171 -0
- package/dist/index2.d.ts.map +1 -0
- package/dist/messaging-api.js +340 -0
- package/dist/messaging-api.js.map +1 -0
- package/dist/modeDetection.js +50 -0
- package/dist/modeDetection.js.map +1 -0
- package/dist/scapi.d.ts +29278 -0
- package/dist/scapi.d.ts.map +1 -0
- package/dist/scapi.js +2 -0
- package/dist/scapi.js.map +1 -0
- package/dist/types.d.ts +13293 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +108 -0
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
import { n as createClientApi } from "./messaging-api.js";
|
|
2
|
+
import { n as usePageDesignerMode } from "./PageDesignerProvider.js";
|
|
3
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/design/react/hooks/useInteraction.ts
|
|
7
|
+
/**
|
|
8
|
+
* Base hook that provides common interaction patterns for design-time functionality.
|
|
9
|
+
* Reduces boilerplate by handling state management, event listeners, and cleanup.
|
|
10
|
+
*
|
|
11
|
+
* @param config - Configuration object defining the interaction behavior
|
|
12
|
+
* @returns Object containing state and action methods
|
|
13
|
+
*/
|
|
14
|
+
function useInteraction(config) {
|
|
15
|
+
const [state, setState] = useState(config.initialState);
|
|
16
|
+
const { isDesignMode, clientApi } = useDesignContext();
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!isDesignMode || !clientApi) return () => {};
|
|
19
|
+
const unsubscribeFunctions = Object.entries(config.eventHandlers ?? {}).map(([eventName, entry]) => clientApi.on(eventName, (event) => entry.handler(event, setState)));
|
|
20
|
+
return () => {
|
|
21
|
+
unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
|
|
22
|
+
};
|
|
23
|
+
}, [
|
|
24
|
+
isDesignMode,
|
|
25
|
+
clientApi,
|
|
26
|
+
config.eventHandlers
|
|
27
|
+
]);
|
|
28
|
+
return {
|
|
29
|
+
state,
|
|
30
|
+
...config.actions?.(state, setState, clientApi ?? null) ?? {}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/design/react/hooks/useSelectInteraction.ts
|
|
36
|
+
/**
|
|
37
|
+
* Custom hook that manages component selection state and handles
|
|
38
|
+
* client-host communication for selection events.
|
|
39
|
+
*
|
|
40
|
+
* @param isDesignMode - Whether design mode is active
|
|
41
|
+
* @param clientApi - Client API for host communication
|
|
42
|
+
* @returns Selection state and interaction methods
|
|
43
|
+
*/
|
|
44
|
+
function useSelectInteraction() {
|
|
45
|
+
const { state: selectedComponentId, setSelectedComponent } = useInteraction({
|
|
46
|
+
initialState: "",
|
|
47
|
+
eventHandlers: {
|
|
48
|
+
ComponentSelected: { handler: (event, setState) => {
|
|
49
|
+
setState(event.componentId);
|
|
50
|
+
} },
|
|
51
|
+
ComponentDeselected: { handler: (_, setState) => {
|
|
52
|
+
setState("");
|
|
53
|
+
} }
|
|
54
|
+
},
|
|
55
|
+
actions: (_state, setState, clientApi) => ({ setSelectedComponent: (componentId) => {
|
|
56
|
+
setState(componentId);
|
|
57
|
+
clientApi?.selectComponent({ componentId });
|
|
58
|
+
} })
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
selectedComponentId,
|
|
62
|
+
setSelectedComponent
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/design/react/hooks/useHoverInteraction.ts
|
|
68
|
+
/**
|
|
69
|
+
* Custom hook that manages component hover state and handles
|
|
70
|
+
* client-host communication for hover events.
|
|
71
|
+
*
|
|
72
|
+
* @returns Hover state and interaction methods
|
|
73
|
+
*/
|
|
74
|
+
function useHoverInteraction() {
|
|
75
|
+
const { state: hoveredComponentId, setHoveredComponent } = useInteraction({
|
|
76
|
+
initialState: null,
|
|
77
|
+
eventHandlers: {
|
|
78
|
+
ComponentHoveredIn: { handler: (event, setState) => setState(event.componentId) },
|
|
79
|
+
ComponentHoveredOut: { handler: (_, setState) => setState(null) }
|
|
80
|
+
},
|
|
81
|
+
actions: (state, setState, clientApi) => ({ setHoveredComponent: (componentId) => {
|
|
82
|
+
if (state && componentId !== state) clientApi?.hoverOutOfComponent({ componentId: state });
|
|
83
|
+
if (componentId && componentId !== state) clientApi?.hoverInToComponent({ componentId });
|
|
84
|
+
setState(componentId);
|
|
85
|
+
} })
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
hoveredComponentId,
|
|
89
|
+
setHoveredComponent
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/design/react/hooks/useDeleteInteraction.ts
|
|
95
|
+
function useDeleteInteraction({ selectedComponentId, setSelectedComponent }) {
|
|
96
|
+
const { deleteComponent } = useInteraction({
|
|
97
|
+
initialState: null,
|
|
98
|
+
eventHandlers: {},
|
|
99
|
+
actions: (_state, _setState, clientApi) => ({ deleteComponent: (event) => {
|
|
100
|
+
clientApi?.deleteComponent(event);
|
|
101
|
+
if (selectedComponentId === event.componentId) setSelectedComponent("");
|
|
102
|
+
} })
|
|
103
|
+
});
|
|
104
|
+
return { deleteComponent };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/design/react/hooks/useFocusInteraction.ts
|
|
109
|
+
function useFocusInteraction({ setSelectedComponent }) {
|
|
110
|
+
const { state: focusedComponentId, focusComponent } = useInteraction({
|
|
111
|
+
initialState: null,
|
|
112
|
+
eventHandlers: { ComponentFocused: { handler: (event, setState) => {
|
|
113
|
+
setSelectedComponent("");
|
|
114
|
+
setState(event.componentId);
|
|
115
|
+
} } },
|
|
116
|
+
actions: (_state, setState) => ({ focusComponent: (node) => {
|
|
117
|
+
node.scrollIntoView();
|
|
118
|
+
setState(null);
|
|
119
|
+
} })
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
focusedComponentId,
|
|
123
|
+
focusComponent
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/design/react/hooks/useScrollInteraction.ts
|
|
129
|
+
/**
|
|
130
|
+
* Custom hook that manages component hover state and handles
|
|
131
|
+
* client-host communication for hover events.
|
|
132
|
+
*
|
|
133
|
+
* @returns Hover state and interaction methods
|
|
134
|
+
*/
|
|
135
|
+
function useScrollInteraction() {
|
|
136
|
+
const { notifyWindowScrollChange } = useInteraction({
|
|
137
|
+
initialState: null,
|
|
138
|
+
eventHandlers: { WindowScrollChanged: { handler: (event) => {
|
|
139
|
+
if (event.scrollY != null) window.scrollTo({
|
|
140
|
+
behavior: "instant",
|
|
141
|
+
top: event.scrollY
|
|
142
|
+
});
|
|
143
|
+
} } },
|
|
144
|
+
actions: (_state, _setState, clientApi) => ({ notifyWindowScrollChange: (x, y) => {
|
|
145
|
+
clientApi?.notifyWindowScrollChanged({
|
|
146
|
+
scrollX: x,
|
|
147
|
+
scrollY: y
|
|
148
|
+
});
|
|
149
|
+
} })
|
|
150
|
+
});
|
|
151
|
+
return { notifyWindowScrollChange };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/design/react/hooks/useComponentDiscovery.ts
|
|
156
|
+
/**
|
|
157
|
+
* Returns a utility for discovering components and regions at a given
|
|
158
|
+
* x, y coordinates.
|
|
159
|
+
* @param nodeToTargetMap - The map of nodes to target entries.
|
|
160
|
+
*/
|
|
161
|
+
function useComponentDiscovery({ nodeToTargetMap }) {
|
|
162
|
+
return useCallback(({ x, y, filter = () => true }) => {
|
|
163
|
+
const nodeStack = document.elementsFromPoint(x, y);
|
|
164
|
+
const results = [];
|
|
165
|
+
for (let i = 0; i < nodeStack.length; i += 1) {
|
|
166
|
+
const node = nodeStack[i];
|
|
167
|
+
const entry = nodeToTargetMap.get(node);
|
|
168
|
+
if (entry && filter(entry)) results.push({
|
|
169
|
+
...entry,
|
|
170
|
+
node
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return results;
|
|
174
|
+
}, [nodeToTargetMap]);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/design/react/utils/regionUtils.ts
|
|
179
|
+
/**
|
|
180
|
+
* Copyright 2026 Salesforce, Inc.
|
|
181
|
+
*
|
|
182
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
183
|
+
* you may not use this file except in compliance with the License.
|
|
184
|
+
* You may obtain a copy of the License at
|
|
185
|
+
*
|
|
186
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
187
|
+
*
|
|
188
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
189
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
190
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
191
|
+
* See the License for the specific language governing permissions and
|
|
192
|
+
* limitations under the License.
|
|
193
|
+
*/
|
|
194
|
+
/**
|
|
195
|
+
* Checks if a component type is allowed in a region based on inclusion and exclusion rules.
|
|
196
|
+
*
|
|
197
|
+
* @param componentType - The type of component being checked
|
|
198
|
+
* @param componentTypeInclusions - Array of allowed component types (if empty, all types are allowed by default)
|
|
199
|
+
* @param componentTypeExclusions - Array of forbidden component types
|
|
200
|
+
* @returns true if the component type is allowed, false otherwise
|
|
201
|
+
*/
|
|
202
|
+
function isComponentTypeAllowedInRegion(componentType, componentTypeInclusions, componentTypeExclusions) {
|
|
203
|
+
if (!componentType) return false;
|
|
204
|
+
if (componentTypeExclusions?.includes(componentType)) return false;
|
|
205
|
+
if (componentTypeInclusions?.length > 0) return componentTypeInclusions.includes(componentType);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/design/react/hooks/useDragInteraction.ts
|
|
211
|
+
const SCROLL_BUFFER_HEIGHT_PERCENTAGE = 15;
|
|
212
|
+
const SCROLL_BUFFER_MIN_HEIGHT_IN_PIXELS = 50;
|
|
213
|
+
const SCROLL_INTERVAL_IN_MS = 1e3 / 60;
|
|
214
|
+
const SCROLL_BASE_AMOUNT_IN_PIXELS = 50;
|
|
215
|
+
function getInsertionType({ cache, node, x, y }) {
|
|
216
|
+
if (!cache.has(node)) {
|
|
217
|
+
const rect$1 = node.getBoundingClientRect();
|
|
218
|
+
const screenLeft = rect$1.left - window.scrollX;
|
|
219
|
+
const screenTop = rect$1.top + window.scrollY;
|
|
220
|
+
cache.set(node, new DOMRect(screenLeft, screenTop, rect$1.width, rect$1.height));
|
|
221
|
+
}
|
|
222
|
+
const rect = cache.get(node);
|
|
223
|
+
const screenX = x + window.scrollX;
|
|
224
|
+
const screenY = y + window.scrollY;
|
|
225
|
+
const midX = rect.left + rect.width / 2;
|
|
226
|
+
const midY = rect.top + rect.height / 2;
|
|
227
|
+
const deltaX = screenX - midX;
|
|
228
|
+
const deltaY = screenY - midY;
|
|
229
|
+
const relativeDeltaX = deltaX / (rect.width / 2);
|
|
230
|
+
const relativeDeltaY = deltaY / (rect.height / 2);
|
|
231
|
+
if (Math.abs(relativeDeltaX) > Math.abs(relativeDeltaY)) return {
|
|
232
|
+
axis: "x",
|
|
233
|
+
type: relativeDeltaX < 0 ? "before" : "after"
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
axis: "y",
|
|
237
|
+
type: relativeDeltaY < 0 ? "before" : "after"
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function isOnSelfDropTarget({ sourceComponentId, beforeComponentId, afterComponentId, insertType, componentId }) {
|
|
241
|
+
const isOnSource = sourceComponentId && componentId === sourceComponentId;
|
|
242
|
+
const isOnSameRegionBefore = sourceComponentId && insertType.type === "before" && beforeComponentId === sourceComponentId;
|
|
243
|
+
const isOnSameRegionAfter = sourceComponentId && insertType.type === "after" && afterComponentId === sourceComponentId;
|
|
244
|
+
return isOnSource || isOnSameRegionBefore || isOnSameRegionAfter;
|
|
245
|
+
}
|
|
246
|
+
function useDragInteraction({ nodeToTargetMap }) {
|
|
247
|
+
const discoverComponents = useComponentDiscovery({ nodeToTargetMap });
|
|
248
|
+
const getNearestComponentAndRegion = useCallback((x, y) => {
|
|
249
|
+
const stack = discoverComponents({
|
|
250
|
+
x,
|
|
251
|
+
y
|
|
252
|
+
});
|
|
253
|
+
let component = null;
|
|
254
|
+
let region = null;
|
|
255
|
+
for (let i = 0; i < stack.length; i += 1) {
|
|
256
|
+
const entry = stack[i];
|
|
257
|
+
if (entry.regionId) {
|
|
258
|
+
if (entry.type === "component") component = entry;
|
|
259
|
+
else if (entry.type === "region") {
|
|
260
|
+
region = entry;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
component,
|
|
267
|
+
region
|
|
268
|
+
};
|
|
269
|
+
}, [discoverComponents]);
|
|
270
|
+
const getInsertionComponentIds = (componentId, region) => {
|
|
271
|
+
const componentIndex = region.componentIds.indexOf(componentId);
|
|
272
|
+
return [region.componentIds[componentIndex - 1], region.componentIds[componentIndex + 1]];
|
|
273
|
+
};
|
|
274
|
+
const getCurrentDropTarget = useCallback(({ x, y, rectCache, componentType }) => {
|
|
275
|
+
const { component, region } = getNearestComponentAndRegion(x, y);
|
|
276
|
+
if (region) {
|
|
277
|
+
if (!isComponentTypeAllowedInRegion(componentType, region.componentTypeInclusions || [], region.componentTypeExclusions || [])) return null;
|
|
278
|
+
const insertType = component ? getInsertionType({
|
|
279
|
+
cache: rectCache,
|
|
280
|
+
node: component.node,
|
|
281
|
+
x,
|
|
282
|
+
y
|
|
283
|
+
}) : {
|
|
284
|
+
axis: "y",
|
|
285
|
+
type: "after"
|
|
286
|
+
};
|
|
287
|
+
const [beforeComponentId, afterComponentId] = component ? getInsertionComponentIds(component.componentId, region) : [];
|
|
288
|
+
return {
|
|
289
|
+
type: component ? "component" : "region",
|
|
290
|
+
regionId: region.regionId,
|
|
291
|
+
componentIds: region.componentIds,
|
|
292
|
+
componentId: component?.componentId ?? "",
|
|
293
|
+
parentId: region.parentId,
|
|
294
|
+
beforeComponentId,
|
|
295
|
+
afterComponentId,
|
|
296
|
+
insertComponentId: component?.componentId,
|
|
297
|
+
insertType,
|
|
298
|
+
componentTypeInclusions: region.componentTypeInclusions,
|
|
299
|
+
componentTypeExclusions: region.componentTypeExclusions
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}, [getNearestComponentAndRegion]);
|
|
304
|
+
const computeScrollFactor = ({ y, windowHeight }) => {
|
|
305
|
+
const bufferHeight = Math.max(windowHeight * (SCROLL_BUFFER_HEIGHT_PERCENTAGE / 100), SCROLL_BUFFER_MIN_HEIGHT_IN_PIXELS);
|
|
306
|
+
const bottomBufferStart = windowHeight - bufferHeight;
|
|
307
|
+
if (y > bottomBufferStart) return (y - bottomBufferStart) / bufferHeight;
|
|
308
|
+
if (y < bufferHeight) return (y - bufferHeight) / bufferHeight;
|
|
309
|
+
return 0;
|
|
310
|
+
};
|
|
311
|
+
const computeScrollDirection = (factor) => {
|
|
312
|
+
if (factor > 0) return 1;
|
|
313
|
+
if (factor < 0) return -1;
|
|
314
|
+
return 0;
|
|
315
|
+
};
|
|
316
|
+
const scrollFactorRef = useRef(0);
|
|
317
|
+
const { state: dragState, commitCurrentDropTarget, updateComponentMove, startComponentMove, dropComponent, cancelDrag, setPendingComponentDragId } = useInteraction({
|
|
318
|
+
initialState: {
|
|
319
|
+
isDragging: false,
|
|
320
|
+
componentType: "",
|
|
321
|
+
sourceComponentId: void 0,
|
|
322
|
+
sourceRegionId: void 0,
|
|
323
|
+
x: 0,
|
|
324
|
+
y: 0,
|
|
325
|
+
currentDropTarget: null,
|
|
326
|
+
pendingTargetCommit: false,
|
|
327
|
+
rectCache: /* @__PURE__ */ new WeakMap(),
|
|
328
|
+
pendingComponentDragId: null
|
|
329
|
+
},
|
|
330
|
+
eventHandlers: {
|
|
331
|
+
ComponentDragStarted: { handler: (event, setState) => {
|
|
332
|
+
scrollFactorRef.current = 0;
|
|
333
|
+
setState((prevState) => ({
|
|
334
|
+
...prevState,
|
|
335
|
+
componentType: event.componentType,
|
|
336
|
+
sourceComponentId: void 0,
|
|
337
|
+
sourceRegionId: void 0,
|
|
338
|
+
x: 0,
|
|
339
|
+
y: 0,
|
|
340
|
+
isDragging: true,
|
|
341
|
+
currentDropTarget: null,
|
|
342
|
+
pendingTargetCommit: false,
|
|
343
|
+
scrollDirection: 0,
|
|
344
|
+
rectCache: /* @__PURE__ */ new WeakMap()
|
|
345
|
+
}));
|
|
346
|
+
} },
|
|
347
|
+
ClientWindowDragExited: { handler: (_, setState) => {
|
|
348
|
+
scrollFactorRef.current = 0;
|
|
349
|
+
setState((prevState) => ({
|
|
350
|
+
...prevState,
|
|
351
|
+
componentType: "",
|
|
352
|
+
x: 0,
|
|
353
|
+
y: 0,
|
|
354
|
+
isDragging: false,
|
|
355
|
+
currentDropTarget: null,
|
|
356
|
+
scrollDirection: 0,
|
|
357
|
+
pendingTargetCommit: false
|
|
358
|
+
}));
|
|
359
|
+
} },
|
|
360
|
+
ClientWindowDragMoved: { handler: (event, setState) => {
|
|
361
|
+
scrollFactorRef.current = computeScrollFactor({
|
|
362
|
+
y: event.y,
|
|
363
|
+
windowHeight: window.innerHeight
|
|
364
|
+
});
|
|
365
|
+
setState((prevState) => ({
|
|
366
|
+
...prevState,
|
|
367
|
+
x: event.x,
|
|
368
|
+
y: event.y,
|
|
369
|
+
isDragging: true,
|
|
370
|
+
scrollDirection: computeScrollDirection(scrollFactorRef.current),
|
|
371
|
+
currentDropTarget: getCurrentDropTarget({
|
|
372
|
+
x: event.x,
|
|
373
|
+
y: event.y,
|
|
374
|
+
rectCache: dragState.rectCache,
|
|
375
|
+
componentType: prevState.componentType
|
|
376
|
+
})
|
|
377
|
+
}));
|
|
378
|
+
} },
|
|
379
|
+
ClientWindowDragDropped: { handler: (_, setState) => {
|
|
380
|
+
setState((prevState) => ({
|
|
381
|
+
...prevState,
|
|
382
|
+
isDragging: false,
|
|
383
|
+
pendingTargetCommit: true
|
|
384
|
+
}));
|
|
385
|
+
} }
|
|
386
|
+
},
|
|
387
|
+
actions: (state, setState, clientApi) => ({
|
|
388
|
+
cancelDrag: () => {
|
|
389
|
+
scrollFactorRef.current = 0;
|
|
390
|
+
setState((prevState) => ({
|
|
391
|
+
...prevState,
|
|
392
|
+
x: 0,
|
|
393
|
+
y: 0,
|
|
394
|
+
scrollDirection: 0,
|
|
395
|
+
isDragging: false,
|
|
396
|
+
pendingComponentDragId: null
|
|
397
|
+
}));
|
|
398
|
+
},
|
|
399
|
+
updateComponentMove: ({ x, y }) => {
|
|
400
|
+
scrollFactorRef.current = computeScrollFactor({
|
|
401
|
+
y,
|
|
402
|
+
windowHeight: window.innerHeight
|
|
403
|
+
});
|
|
404
|
+
setState((prevState) => ({
|
|
405
|
+
...prevState,
|
|
406
|
+
x,
|
|
407
|
+
y,
|
|
408
|
+
scrollDirection: computeScrollDirection(scrollFactorRef.current),
|
|
409
|
+
currentDropTarget: getCurrentDropTarget({
|
|
410
|
+
x,
|
|
411
|
+
y,
|
|
412
|
+
rectCache: state.rectCache,
|
|
413
|
+
componentType: state.componentType
|
|
414
|
+
})
|
|
415
|
+
}));
|
|
416
|
+
},
|
|
417
|
+
setPendingComponentDragId: (componentId) => {
|
|
418
|
+
setState((prevState) => ({
|
|
419
|
+
...prevState,
|
|
420
|
+
pendingComponentDragId: componentId
|
|
421
|
+
}));
|
|
422
|
+
},
|
|
423
|
+
dropComponent: () => {
|
|
424
|
+
setState((prevState) => ({
|
|
425
|
+
...prevState,
|
|
426
|
+
isDragging: false,
|
|
427
|
+
pendingTargetCommit: true
|
|
428
|
+
}));
|
|
429
|
+
},
|
|
430
|
+
startComponentMove: (componentId, regionId, componentType) => {
|
|
431
|
+
scrollFactorRef.current = 0;
|
|
432
|
+
setState((prevState) => ({
|
|
433
|
+
...prevState,
|
|
434
|
+
x: 0,
|
|
435
|
+
y: 0,
|
|
436
|
+
componentType,
|
|
437
|
+
sourceComponentId: componentId,
|
|
438
|
+
sourceRegionId: regionId,
|
|
439
|
+
isDragging: true,
|
|
440
|
+
scrollDirection: 0,
|
|
441
|
+
rectCache: /* @__PURE__ */ new WeakMap()
|
|
442
|
+
}));
|
|
443
|
+
},
|
|
444
|
+
commitCurrentDropTarget: () => {
|
|
445
|
+
if (state.currentDropTarget) {
|
|
446
|
+
if (state.sourceComponentId) {
|
|
447
|
+
if (!isOnSelfDropTarget({
|
|
448
|
+
sourceComponentId: state.sourceComponentId,
|
|
449
|
+
beforeComponentId: state.currentDropTarget.beforeComponentId,
|
|
450
|
+
afterComponentId: state.currentDropTarget.afterComponentId,
|
|
451
|
+
insertType: state.currentDropTarget.insertType,
|
|
452
|
+
componentId: state.currentDropTarget.componentId
|
|
453
|
+
})) clientApi?.moveComponentToRegion({
|
|
454
|
+
componentId: state.sourceComponentId,
|
|
455
|
+
sourceRegionId: state.sourceRegionId ?? "",
|
|
456
|
+
insertType: state.currentDropTarget.insertType?.type,
|
|
457
|
+
insertComponentId: state.currentDropTarget.insertComponentId,
|
|
458
|
+
beforeComponentId: state.currentDropTarget.beforeComponentId,
|
|
459
|
+
afterComponentId: state.currentDropTarget.afterComponentId,
|
|
460
|
+
targetRegionId: state.currentDropTarget.regionId,
|
|
461
|
+
targetComponentId: state.currentDropTarget.parentId ?? ""
|
|
462
|
+
});
|
|
463
|
+
} else if (state.componentType) clientApi?.addComponentToRegion({
|
|
464
|
+
insertType: state.currentDropTarget.insertType?.type,
|
|
465
|
+
insertComponentId: state.currentDropTarget.insertComponentId,
|
|
466
|
+
componentProperties: {},
|
|
467
|
+
componentType: state.componentType,
|
|
468
|
+
targetComponentId: state.currentDropTarget.parentId ?? "",
|
|
469
|
+
beforeComponentId: state.currentDropTarget.beforeComponentId,
|
|
470
|
+
afterComponentId: state.currentDropTarget.afterComponentId,
|
|
471
|
+
targetRegionId: state.currentDropTarget.regionId
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
scrollFactorRef.current = 0;
|
|
475
|
+
setState((prevState) => ({
|
|
476
|
+
...prevState,
|
|
477
|
+
x: 0,
|
|
478
|
+
y: 0,
|
|
479
|
+
componentType: "",
|
|
480
|
+
scrollDirection: 0,
|
|
481
|
+
sourceComponentId: void 0,
|
|
482
|
+
sourceRegionId: void 0,
|
|
483
|
+
pendingComponentDragId: null,
|
|
484
|
+
currentDropTarget: null,
|
|
485
|
+
pendingTargetCommit: false
|
|
486
|
+
}));
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
});
|
|
490
|
+
useEffect(() => {
|
|
491
|
+
if (dragState.pendingTargetCommit) commitCurrentDropTarget();
|
|
492
|
+
}, [dragState.pendingTargetCommit, commitCurrentDropTarget]);
|
|
493
|
+
useEffect(() => {
|
|
494
|
+
if (dragState.scrollDirection !== 0) {
|
|
495
|
+
const interval = setInterval(() => {
|
|
496
|
+
window.scrollBy(0, scrollFactorRef.current * SCROLL_BASE_AMOUNT_IN_PIXELS);
|
|
497
|
+
}, SCROLL_INTERVAL_IN_MS);
|
|
498
|
+
return () => clearInterval(interval);
|
|
499
|
+
}
|
|
500
|
+
return () => {};
|
|
501
|
+
}, [dragState.scrollDirection, scrollFactorRef]);
|
|
502
|
+
return {
|
|
503
|
+
dragState,
|
|
504
|
+
setPendingComponentDragId,
|
|
505
|
+
commitCurrentDropTarget,
|
|
506
|
+
startComponentMove,
|
|
507
|
+
updateComponentMove,
|
|
508
|
+
dropComponent,
|
|
509
|
+
cancelDrag
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/design/react/context/DesignStateContext.tsx
|
|
515
|
+
const DesignStateContext = React.createContext(null);
|
|
516
|
+
const DesignStateProvider = ({ children }) => {
|
|
517
|
+
const selectInteraction = useSelectInteraction();
|
|
518
|
+
const hoverInteraction = useHoverInteraction();
|
|
519
|
+
const deleteInteraction = useDeleteInteraction({
|
|
520
|
+
selectedComponentId: selectInteraction.selectedComponentId,
|
|
521
|
+
setSelectedComponent: selectInteraction.setSelectedComponent
|
|
522
|
+
});
|
|
523
|
+
const focusInteraction = useFocusInteraction({ setSelectedComponent: selectInteraction.setSelectedComponent });
|
|
524
|
+
const scrollInteraction = useScrollInteraction();
|
|
525
|
+
const nodeToTargetMap = React.useMemo(() => /* @__PURE__ */ new WeakMap(), []);
|
|
526
|
+
const dragInteraction = useDragInteraction({ nodeToTargetMap });
|
|
527
|
+
const state = React.useMemo(() => ({
|
|
528
|
+
...deleteInteraction,
|
|
529
|
+
...selectInteraction,
|
|
530
|
+
...hoverInteraction,
|
|
531
|
+
...focusInteraction,
|
|
532
|
+
...dragInteraction,
|
|
533
|
+
...scrollInteraction,
|
|
534
|
+
nodeToTargetMap
|
|
535
|
+
}), [
|
|
536
|
+
deleteInteraction,
|
|
537
|
+
selectInteraction,
|
|
538
|
+
hoverInteraction,
|
|
539
|
+
focusInteraction,
|
|
540
|
+
dragInteraction,
|
|
541
|
+
nodeToTargetMap,
|
|
542
|
+
scrollInteraction
|
|
543
|
+
]);
|
|
544
|
+
return /* @__PURE__ */ jsx(DesignStateContext.Provider, {
|
|
545
|
+
value: state,
|
|
546
|
+
children
|
|
547
|
+
});
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region src/design/react/hooks/useDesignState.ts
|
|
552
|
+
/**
|
|
553
|
+
* Custom hook that manages design-time component state by composing
|
|
554
|
+
* individual interaction hooks for better maintainability and testability.
|
|
555
|
+
*
|
|
556
|
+
* @returns Combined design state from all interactions
|
|
557
|
+
*/
|
|
558
|
+
const useDesignState = () => {
|
|
559
|
+
const context = React.useContext(DesignStateContext);
|
|
560
|
+
if (!context) throw new Error("useDesignState must be used within a DesignStateProvider");
|
|
561
|
+
return context;
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
//#endregion
|
|
565
|
+
//#region src/design/react/hooks/useThrottledCallback.ts
|
|
566
|
+
function useThrottledCallback(callback, interval, deps = []) {
|
|
567
|
+
const lastCallTime = useRef(0);
|
|
568
|
+
return useCallback((...args) => {
|
|
569
|
+
const now = Date.now();
|
|
570
|
+
if (now >= lastCallTime.current + interval) {
|
|
571
|
+
lastCallTime.current = now;
|
|
572
|
+
callback(...args);
|
|
573
|
+
}
|
|
574
|
+
}, [
|
|
575
|
+
callback,
|
|
576
|
+
interval,
|
|
577
|
+
...deps
|
|
578
|
+
]);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/design/react/hooks/useDebouncedCallback.ts
|
|
583
|
+
function useDebouncedCallback(callback, interval, deps = []) {
|
|
584
|
+
const timeoutRef = useRef(null);
|
|
585
|
+
return useCallback((...args) => {
|
|
586
|
+
if (timeoutRef.current) {
|
|
587
|
+
clearTimeout(timeoutRef.current);
|
|
588
|
+
timeoutRef.current = null;
|
|
589
|
+
}
|
|
590
|
+
timeoutRef.current = setTimeout(() => {
|
|
591
|
+
callback(...args);
|
|
592
|
+
timeoutRef.current = null;
|
|
593
|
+
}, interval);
|
|
594
|
+
}, [
|
|
595
|
+
callback,
|
|
596
|
+
interval,
|
|
597
|
+
...deps
|
|
598
|
+
]);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region src/design/react/hooks/useGlobalListeners.ts
|
|
603
|
+
const FPS_60 = 1e3 / 60;
|
|
604
|
+
function useGlobalListeners() {
|
|
605
|
+
const { dropComponent, updateComponentMove, cancelDrag, notifyWindowScrollChange } = useDesignState();
|
|
606
|
+
const dragListener = useThrottledCallback((event) => updateComponentMove({
|
|
607
|
+
x: event.clientX,
|
|
608
|
+
y: event.clientY
|
|
609
|
+
}), FPS_60, [updateComponentMove]);
|
|
610
|
+
const scrollListener = useDebouncedCallback(() => notifyWindowScrollChange(window.scrollX, window.scrollY), 100, [notifyWindowScrollChange]);
|
|
611
|
+
useEffect(() => {
|
|
612
|
+
const dragEndListener = () => dropComponent();
|
|
613
|
+
const mouseUpListener = () => cancelDrag();
|
|
614
|
+
window.addEventListener("dragover", dragListener);
|
|
615
|
+
window.addEventListener("dragend", dragEndListener);
|
|
616
|
+
window.addEventListener("scroll", scrollListener);
|
|
617
|
+
window.addEventListener("mouseup", mouseUpListener);
|
|
618
|
+
return () => {
|
|
619
|
+
window.removeEventListener("dragover", dragListener);
|
|
620
|
+
window.removeEventListener("dragend", dragEndListener);
|
|
621
|
+
window.removeEventListener("mouseup", mouseUpListener);
|
|
622
|
+
window.removeEventListener("scroll", scrollListener);
|
|
623
|
+
};
|
|
624
|
+
}, [
|
|
625
|
+
dropComponent,
|
|
626
|
+
cancelDrag,
|
|
627
|
+
dragListener,
|
|
628
|
+
scrollListener
|
|
629
|
+
]);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region src/design/react/hooks/useGlobalAnchorBlock.ts
|
|
634
|
+
/**
|
|
635
|
+
* React hook that prevents all <a> (anchor) navigation by default in the document,
|
|
636
|
+
* unless the anchor has the attribute `data-pd-allow-link`.
|
|
637
|
+
*/
|
|
638
|
+
function useGlobalAnchorBlock() {
|
|
639
|
+
useEffect(() => {
|
|
640
|
+
function preventAnchorClicks(event) {
|
|
641
|
+
const anchor = event.target.closest("a");
|
|
642
|
+
if (anchor && !anchor.hasAttribute("data-pd-allow-link")) event.preventDefault();
|
|
643
|
+
}
|
|
644
|
+
document.addEventListener("click", preventAnchorClicks);
|
|
645
|
+
return () => document.removeEventListener("click", preventAnchorClicks);
|
|
646
|
+
}, []);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/design/react/components/DesignApp.tsx
|
|
651
|
+
/**
|
|
652
|
+
* Containes any global setup logic for the design layer.
|
|
653
|
+
*/
|
|
654
|
+
const DesignApp = ({ children }) => {
|
|
655
|
+
useGlobalListeners();
|
|
656
|
+
useGlobalAnchorBlock();
|
|
657
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region src/design/react/context/DesignContext.tsx
|
|
662
|
+
const noop = () => {};
|
|
663
|
+
const DesignContext = React.createContext({
|
|
664
|
+
isDesignMode: false,
|
|
665
|
+
isConnected: false,
|
|
666
|
+
pageDesignerConfig: null,
|
|
667
|
+
clientPage: null,
|
|
668
|
+
setClientPage: noop
|
|
669
|
+
});
|
|
670
|
+
/**
|
|
671
|
+
* Provider component that enables design-time functionality for child components.
|
|
672
|
+
* Sets up client-host communication and manages component selection state.
|
|
673
|
+
*
|
|
674
|
+
* @param children - Child components to wrap with design functionality
|
|
675
|
+
* @param targetOrigin - Target origin for postMessage communication
|
|
676
|
+
* @param clientId - Id for the client API
|
|
677
|
+
* @returns JSX element wrapping children with design context
|
|
678
|
+
*/
|
|
679
|
+
const DesignProvider = ({ children, targetOrigin, clientId, usid, clientConnectionTimeout, clientConnectionInterval, clientLogger = noop }) => {
|
|
680
|
+
const { isDesignMode } = usePageDesignerMode();
|
|
681
|
+
const [isConnected, setIsConnected] = React.useState(false);
|
|
682
|
+
const [pageDesignerConfig, setPageDesignerConfig] = React.useState(null);
|
|
683
|
+
const [clientPage, setClientPage] = React.useState(null);
|
|
684
|
+
const clientPageRef = React.useRef(null);
|
|
685
|
+
const clientApi = React.useMemo(() => createClientApi({
|
|
686
|
+
logger: clientLogger,
|
|
687
|
+
emitter: {
|
|
688
|
+
postMessage: (message) => window.parent.postMessage(message, targetOrigin),
|
|
689
|
+
addEventListener: (handler) => {
|
|
690
|
+
const listener = (event) => handler(event.data);
|
|
691
|
+
window.addEventListener("message", listener);
|
|
692
|
+
return () => window.removeEventListener("message", listener);
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
id: clientId
|
|
696
|
+
}), [
|
|
697
|
+
targetOrigin,
|
|
698
|
+
clientId,
|
|
699
|
+
clientLogger
|
|
700
|
+
]);
|
|
701
|
+
React.useEffect(() => {
|
|
702
|
+
clientApi.connect({
|
|
703
|
+
timeout: clientConnectionTimeout,
|
|
704
|
+
interval: clientConnectionInterval,
|
|
705
|
+
onHostConnected: (event) => {
|
|
706
|
+
setPageDesignerConfig(event);
|
|
707
|
+
setIsConnected(true);
|
|
708
|
+
},
|
|
709
|
+
onHostDisconnected: (reconnect) => {
|
|
710
|
+
setPageDesignerConfig(null);
|
|
711
|
+
setIsConnected(false);
|
|
712
|
+
reconnect();
|
|
713
|
+
},
|
|
714
|
+
onError: () => {},
|
|
715
|
+
usid
|
|
716
|
+
});
|
|
717
|
+
return () => {
|
|
718
|
+
clientApi.disconnect();
|
|
719
|
+
setPageDesignerConfig(null);
|
|
720
|
+
setIsConnected(false);
|
|
721
|
+
};
|
|
722
|
+
}, [
|
|
723
|
+
clientApi,
|
|
724
|
+
clientConnectionTimeout,
|
|
725
|
+
clientConnectionInterval,
|
|
726
|
+
usid
|
|
727
|
+
]);
|
|
728
|
+
const contextValue = React.useMemo(() => ({
|
|
729
|
+
isDesignMode,
|
|
730
|
+
clientApi,
|
|
731
|
+
isConnected,
|
|
732
|
+
pageDesignerConfig,
|
|
733
|
+
clientPage,
|
|
734
|
+
setClientPage: (page) => {
|
|
735
|
+
if (page !== clientPageRef.current) {
|
|
736
|
+
clientPageRef.current = page;
|
|
737
|
+
setClientPage(page);
|
|
738
|
+
clientApi?.notifyClientPageChanged({ page });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}), [
|
|
742
|
+
isDesignMode,
|
|
743
|
+
clientApi,
|
|
744
|
+
isConnected,
|
|
745
|
+
pageDesignerConfig,
|
|
746
|
+
clientPage,
|
|
747
|
+
setClientPage
|
|
748
|
+
]);
|
|
749
|
+
return /* @__PURE__ */ jsx(DesignContext.Provider, {
|
|
750
|
+
value: contextValue,
|
|
751
|
+
children: /* @__PURE__ */ jsx(DesignStateProvider, { children: /* @__PURE__ */ jsx(DesignApp, { children }) })
|
|
752
|
+
});
|
|
753
|
+
};
|
|
754
|
+
DesignProvider.defaultProps = {
|
|
755
|
+
clientLogger: noop,
|
|
756
|
+
clientConnectionTimeout: 6e4,
|
|
757
|
+
clientConnectionInterval: 1e3
|
|
758
|
+
};
|
|
759
|
+
/**
|
|
760
|
+
* Custom hook to access the design context
|
|
761
|
+
* Provides access to design mode state and component selection functionality
|
|
762
|
+
*
|
|
763
|
+
* @returns The current design context
|
|
764
|
+
*/
|
|
765
|
+
const useDesignContext = () => React.useContext(DesignContext);
|
|
766
|
+
|
|
767
|
+
//#endregion
|
|
768
|
+
export { isComponentTypeAllowedInRegion as a, useDesignState as i, DesignProvider as n, useComponentDiscovery as o, useDesignContext as r, DesignContext as t };
|
|
769
|
+
//# sourceMappingURL=DesignContext.js.map
|