@salesforce/storefront-next-runtime 0.4.0-alpha.2 → 0.4.0-alpha.3
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/dist/ComponentContext.js.map +1 -1
- package/dist/DesignComponent.js +46 -31
- package/dist/DesignComponent.js.map +1 -1
- package/dist/DesignContext.js +91 -65
- package/dist/DesignContext.js.map +1 -1
- package/dist/DesignFrame.js +8 -4
- package/dist/DesignFrame.js.map +1 -1
- package/dist/DesignRegion.js +6 -8
- package/dist/DesignRegion.js.map +1 -1
- package/dist/component.types.d.ts +2 -2
- package/dist/custom-global-preferences.d.ts +3 -3
- package/dist/data-store.d.ts +3 -3
- package/dist/data-store.d.ts.map +1 -1
- package/dist/design-data.d.ts.map +1 -1
- package/dist/design-react-core.d.ts +4 -3
- package/dist/design-react-core.d.ts.map +1 -1
- package/dist/gcp-preferences.d.ts +3 -3
- package/dist/index.d.ts +25 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/scapi.d.ts.map +1 -1
- package/dist/site-context.d.ts +7 -7
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentContext.js","names":[],"sources":["../src/design/react/core/RegionContext.tsx","../src/design/react/core/ComponentContext.tsx"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\n\nexport interface RegionContextType {\n regionId: string;\n
|
|
1
|
+
{"version":3,"file":"ComponentContext.js","names":[],"sources":["../src/design/react/core/RegionContext.tsx","../src/design/react/core/ComponentContext.tsx"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\n\nexport interface RegionContextType {\n regionId: string;\n contentLinkUuids: string[];\n}\n\nexport const RegionContext = React.createContext<RegionContextType | null>(null);\n\nexport const useRegionContext = (): RegionContextType | null => React.useContext(RegionContext);\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\n\nexport interface ComponentContextType {\n componentId: string;\n name?: string;\n contentLinkUuid?: string;\n}\n\nexport const ComponentContext = React.createContext<ComponentContextType | null>(null);\n\nexport const useComponentContext = (): ComponentContextType | null => React.useContext(ComponentContext);\n"],"mappings":";;;AAsBA,MAAa,gBAAgB,MAAM,cAAwC,KAAK;AAEhF,MAAa,yBAAmD,MAAM,WAAW,cAAc;;;;ACD/F,MAAa,mBAAmB,MAAM,cAA2C,KAAK;AAEtF,MAAa,4BAAyD,MAAM,WAAW,iBAAiB"}
|
package/dist/DesignComponent.js
CHANGED
|
@@ -8,13 +8,13 @@ import React, { useCallback, useRef } from "react";
|
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
|
|
10
10
|
//#region src/design/react/hooks/useComponentDecoratorClasses.ts
|
|
11
|
-
function useComponentDecoratorClasses({
|
|
12
|
-
const {
|
|
13
|
-
const isSelected =
|
|
14
|
-
const isHovered = !dragState.isDragging &&
|
|
11
|
+
function useComponentDecoratorClasses({ contentLinkUuid, isFragment, isLocalized }) {
|
|
12
|
+
const { selectedContentLinkUuid, hoveredContentLinkUuid, dragState } = useDesignState();
|
|
13
|
+
const isSelected = selectedContentLinkUuid === contentLinkUuid;
|
|
14
|
+
const isHovered = !dragState.isDragging && hoveredContentLinkUuid === contentLinkUuid;
|
|
15
15
|
const showFrame = (isSelected || isHovered) && !dragState.isDragging;
|
|
16
|
-
const isMoving = dragState.isDragging && dragState.
|
|
17
|
-
const isDropTarget = dragState.currentDropTarget?.
|
|
16
|
+
const isMoving = dragState.isDragging && dragState.sourceContentLinkUuid === contentLinkUuid;
|
|
17
|
+
const isDropTarget = dragState.currentDropTarget?.contentLinkUuid === contentLinkUuid;
|
|
18
18
|
const dropTargetInsertType = dragState.currentDropTarget?.insertType;
|
|
19
19
|
const dropTargetAxis = dropTargetInsertType?.axis;
|
|
20
20
|
return [
|
|
@@ -32,17 +32,17 @@ function useComponentDecoratorClasses({ componentId, isFragment, isLocalized })
|
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region src/design/react/hooks/useFocusedComponentHandler.ts
|
|
34
34
|
/**
|
|
35
|
-
* Focuses a component when the focused component id matches the
|
|
36
|
-
* @param
|
|
35
|
+
* Focuses a component when the focused component id matches the content link UUID.
|
|
36
|
+
* @param contentLinkUuid - The content link UUID of the component.
|
|
37
37
|
* @param nodeRef - The ref object to the node to focus.
|
|
38
38
|
*/
|
|
39
|
-
function useFocusedComponentHandler(
|
|
40
|
-
const {
|
|
39
|
+
function useFocusedComponentHandler(contentLinkUuid, nodeRef) {
|
|
40
|
+
const { focusedContentLinkUuid, focusComponent } = useDesignState();
|
|
41
41
|
React.useEffect(() => {
|
|
42
|
-
if (
|
|
42
|
+
if (focusedContentLinkUuid === contentLinkUuid && nodeRef.current) focusComponent(nodeRef.current);
|
|
43
43
|
}, [
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
focusedContentLinkUuid,
|
|
45
|
+
contentLinkUuid,
|
|
46
46
|
focusComponent,
|
|
47
47
|
nodeRef
|
|
48
48
|
]);
|
|
@@ -73,7 +73,7 @@ function useComponentInfo(componentId) {
|
|
|
73
73
|
//#region src/design/react/components/DesignComponent.tsx
|
|
74
74
|
function DesignComponent(props) {
|
|
75
75
|
const { designMetadata, children } = props;
|
|
76
|
-
const { id = "", name, isFragment = false, isVisible = true, isLocalized = false } = designMetadata ?? {};
|
|
76
|
+
const { id = "", contentLinkUuid = "", name, isFragment = false, isVisible = true, isLocalized = false } = designMetadata ?? {};
|
|
77
77
|
const componentId = id;
|
|
78
78
|
const componentType = useComponentType(componentId);
|
|
79
79
|
const componentInfo = useComponentInfo(componentId);
|
|
@@ -82,23 +82,31 @@ function DesignComponent(props) {
|
|
|
82
82
|
const dragRef = useRef(null);
|
|
83
83
|
const { regionId } = useRegionContext() ?? {};
|
|
84
84
|
const { componentId: parentComponentId } = useComponentContext() ?? {};
|
|
85
|
-
const {
|
|
86
|
-
|
|
85
|
+
const { selectedContentLinkUuid, hoveredContentLinkUuid, setSelectedComponent, setHoveredComponent, startComponentMove, setPendingDragContentLinkUuid, dragState: { pendingDragContentLinkUuid, isDragging, sourceContentLinkUuid: draggingSourceContentLinkUuid }, registerContentLink } = useDesignState();
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
if (contentLinkUuid && componentId) registerContentLink(contentLinkUuid, componentId);
|
|
88
|
+
}, [
|
|
89
|
+
componentId,
|
|
90
|
+
contentLinkUuid,
|
|
91
|
+
registerContentLink
|
|
92
|
+
]);
|
|
93
|
+
useFocusedComponentHandler(contentLinkUuid, dragRef);
|
|
87
94
|
useNodeToTargetStore({
|
|
88
95
|
type: "component",
|
|
89
96
|
nodeRef: dragRef,
|
|
90
97
|
parentId: parentComponentId,
|
|
91
98
|
regionId,
|
|
92
|
-
componentId
|
|
99
|
+
componentId,
|
|
100
|
+
contentLinkUuid
|
|
93
101
|
});
|
|
94
102
|
const discoverComponents = useComponentDiscovery({ nodeToTargetMap });
|
|
95
|
-
const isPendingDrag =
|
|
103
|
+
const isPendingDrag = pendingDragContentLinkUuid === contentLinkUuid;
|
|
96
104
|
const findAndSetHoveredComponent = useCallback((x, y) => {
|
|
97
105
|
setHoveredComponent(discoverComponents({
|
|
98
106
|
x,
|
|
99
107
|
y,
|
|
100
108
|
filter: (entry) => entry.type === "component"
|
|
101
|
-
})[0]?.
|
|
109
|
+
})[0]?.contentLinkUuid ?? null);
|
|
102
110
|
}, [setHoveredComponent, discoverComponents]);
|
|
103
111
|
const handleMouseMove = useThrottledCallback((event) => {
|
|
104
112
|
event.stopPropagation();
|
|
@@ -110,35 +118,41 @@ function DesignComponent(props) {
|
|
|
110
118
|
}, [findAndSetHoveredComponent]);
|
|
111
119
|
const handleClick = useCallback((e) => {
|
|
112
120
|
e.stopPropagation();
|
|
113
|
-
setSelectedComponent(
|
|
114
|
-
}, [setSelectedComponent,
|
|
115
|
-
const showFrame = [
|
|
121
|
+
setSelectedComponent(contentLinkUuid ?? "");
|
|
122
|
+
}, [setSelectedComponent, contentLinkUuid]);
|
|
123
|
+
const showFrame = [selectedContentLinkUuid, hoveredContentLinkUuid].includes(contentLinkUuid ?? "") && !isDragging;
|
|
116
124
|
const isDraggable = Boolean(componentId && regionId && componentType?.id);
|
|
117
125
|
const classes = useComponentDecoratorClasses({
|
|
118
|
-
|
|
126
|
+
contentLinkUuid,
|
|
119
127
|
isLocalized,
|
|
120
128
|
isFragment: Boolean(isFragment)
|
|
121
129
|
});
|
|
122
130
|
const context = React.useMemo(() => ({
|
|
123
131
|
componentId: id,
|
|
124
|
-
name
|
|
125
|
-
|
|
132
|
+
name,
|
|
133
|
+
contentLinkUuid
|
|
134
|
+
}), [
|
|
135
|
+
id,
|
|
136
|
+
name,
|
|
137
|
+
contentLinkUuid
|
|
138
|
+
]);
|
|
126
139
|
const handleDragOver = React.useCallback((event) => {
|
|
127
|
-
if (
|
|
128
|
-
}, [
|
|
140
|
+
if (draggingSourceContentLinkUuid !== contentLinkUuid) event.preventDefault();
|
|
141
|
+
}, [draggingSourceContentLinkUuid, contentLinkUuid]);
|
|
129
142
|
const handleMouseDown = React.useCallback((event) => {
|
|
130
|
-
if (
|
|
143
|
+
if (contentLinkUuid) {
|
|
131
144
|
event.stopPropagation();
|
|
132
|
-
|
|
145
|
+
setPendingDragContentLinkUuid(contentLinkUuid);
|
|
133
146
|
}
|
|
134
|
-
}, [
|
|
147
|
+
}, [contentLinkUuid, setPendingDragContentLinkUuid]);
|
|
135
148
|
const handleDragStart = React.useCallback((event) => {
|
|
136
149
|
event.stopPropagation();
|
|
137
|
-
if (componentId && regionId && componentType?.id) startComponentMove(componentId, regionId, componentType.id);
|
|
150
|
+
if (componentId && regionId && componentType?.id) startComponentMove(componentId, regionId, componentType.id, contentLinkUuid);
|
|
138
151
|
}, [
|
|
139
152
|
componentId,
|
|
140
153
|
regionId,
|
|
141
154
|
componentType?.id,
|
|
155
|
+
contentLinkUuid,
|
|
142
156
|
startComponentMove
|
|
143
157
|
]);
|
|
144
158
|
if (!isVisible) return /* @__PURE__ */ jsx(Fragment, {});
|
|
@@ -157,6 +171,7 @@ function DesignComponent(props) {
|
|
|
157
171
|
children: [/* @__PURE__ */ jsx("div", { className: "pd-design__component__drop-target" }), /* @__PURE__ */ jsx(DesignFrame, {
|
|
158
172
|
showFrame,
|
|
159
173
|
componentId,
|
|
174
|
+
contentLinkUuid,
|
|
160
175
|
localized: isLocalized,
|
|
161
176
|
name: componentName,
|
|
162
177
|
parentId: parentComponentId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DesignComponent.js","names":[],"sources":["../src/design/react/hooks/useComponentDecoratorClasses.ts","../src/design/react/hooks/useFocusedComponentHandler.ts","../src/design/react/hooks/useComponentInfo.ts","../src/design/react/components/DesignComponent.tsx"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useDesignState } from './useDesignState';\n\nexport function useComponentDecoratorClasses({\n componentId,\n isFragment,\n isLocalized,\n}: {\n componentId: string;\n isFragment: boolean;\n isLocalized: boolean;\n}): string {\n const { selectedComponentId, hoveredComponentId, dragState } = useDesignState();\n\n const isSelected = selectedComponentId === componentId;\n const isHovered = !dragState.isDragging && hoveredComponentId === componentId;\n const showFrame = (isSelected || isHovered) && !dragState.isDragging;\n const isMoving = dragState.isDragging && dragState.sourceComponentId === componentId;\n const isDropTarget = dragState.currentDropTarget?.componentId === componentId;\n const dropTargetInsertType = dragState.currentDropTarget?.insertType;\n const dropTargetAxis = dropTargetInsertType?.axis;\n\n return [\n 'pd-design__decorator',\n isFragment ? 'pd-design__fragment' : 'pd-design__component',\n showFrame && 'pd-design__frame--visible',\n isSelected && 'pd-design__decorator--selected',\n isHovered && 'pd-design__decorator--hovered',\n isMoving && 'pd-design__decorator--moving',\n !isLocalized && 'pd-design__component--unlocalized',\n isDropTarget &&\n dropTargetAxis &&\n dropTargetInsertType &&\n `pd-design__drop-target__${dropTargetAxis}-${dropTargetInsertType.type}`,\n ]\n .filter(Boolean)\n .join(' ');\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\nimport { useDesignState } from './useDesignState';\n\n/**\n * Focuses a component when the focused component id matches the component id.\n * @param componentId - The id of the component to focus.\n * @param nodeRef - The ref object to the node to focus.\n */\nexport function useFocusedComponentHandler(componentId: string, nodeRef: React.RefObject<Element | null>): void {\n const { focusedComponentId, focusComponent } = useDesignState();\n\n React.useEffect(() => {\n if (focusedComponentId === componentId && nodeRef.current) {\n focusComponent(nodeRef.current);\n }\n }, [focusedComponentId, componentId, focusComponent, nodeRef]);\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ComponentInfo } from '../../messaging-api';\nimport { useDesignContext } from '../context/DesignContext';\nimport { useDesignState } from './useDesignState';\n\n/**\n * Hook that returns the current ComponentInfo for a given component ID,\n * merging the base config with any runtime updates.\n *\n * @param componentId - The ID of the component to get info for\n * @returns The merged ComponentInfo or null if the component doesn't exist\n */\nexport function useComponentInfo(componentId: string): ComponentInfo | null {\n const { pageDesignerConfig } = useDesignContext();\n const { componentUpdates } = useDesignState();\n const baseComponentInfo = pageDesignerConfig?.components?.[componentId];\n const updates = componentUpdates?.[componentId] ?? {};\n\n if (!baseComponentInfo) {\n return null;\n }\n\n return { ...baseComponentInfo, ...updates };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React, { useRef, useCallback } from 'react';\nimport type { ComponentDecoratorProps } from '../core/component.types';\nimport { useComponentDecoratorClasses } from '../hooks/useComponentDecoratorClasses';\nimport { useDesignState } from '../hooks/useDesignState';\nimport { useFocusedComponentHandler } from '../hooks/useFocusedComponentHandler';\nimport { useNodeToTargetStore } from '../hooks/useNodeToTargetStore';\nimport { DesignFrame } from './DesignFrame';\nimport { useRegionContext } from '../core/RegionContext';\nimport { ComponentContext, useComponentContext, type ComponentContextType } from '../core/ComponentContext';\nimport { useComponentDiscovery } from '../hooks/useComponentDiscovery';\nimport { useComponentType } from '../hooks/useComponentType';\nimport { useThrottledCallback } from '../hooks/useThrottledCallback';\nimport { useComponentInfo } from '../hooks/useComponentInfo';\n\nexport function DesignComponent(props: ComponentDecoratorProps<unknown>): React.JSX.Element {\n const { designMetadata, children } = props;\n const { id = '', name, isFragment = false, isVisible = true, isLocalized = false } = designMetadata ?? {};\n const componentId = id;\n const componentType = useComponentType(componentId);\n const componentInfo = useComponentInfo(componentId);\n const { nodeToTargetMap } = useDesignState();\n\n const componentName = componentInfo?.name || componentType?.label || name || 'Component';\n const dragRef = useRef<HTMLDivElement>(null);\n const { regionId } = useRegionContext() ?? {};\n const { componentId: parentComponentId } = useComponentContext() ?? {};\n\n const {\n selectedComponentId,\n hoveredComponentId,\n setSelectedComponent,\n setHoveredComponent,\n startComponentMove,\n setPendingComponentDragId,\n dragState: { pendingComponentDragId, isDragging, sourceComponentId: draggingSourceComponentId },\n } = useDesignState();\n\n useFocusedComponentHandler(componentId, dragRef);\n useNodeToTargetStore({\n type: 'component',\n nodeRef: dragRef,\n parentId: parentComponentId,\n regionId,\n componentId,\n });\n\n const discoverComponents = useComponentDiscovery({\n nodeToTargetMap,\n });\n\n const isPendingDrag = pendingComponentDragId === componentId;\n const findAndSetHoveredComponent = useCallback(\n (x: number, y: number) => {\n // If we hover off a component, we could still be hovering over a parent component\n // that contains that child. In this instance, the mouse enter doesn't fire and that parent\n // would not be highlighted. Everytime we leave a component, we check\n // if we are hovering over a component at those coordinates. If we are,\n // we set the hovered component to that component.\n const components = discoverComponents({\n x,\n y,\n filter: (entry) => entry.type === 'component',\n });\n\n setHoveredComponent(components[0]?.componentId ?? null);\n },\n [setHoveredComponent, discoverComponents]\n );\n\n const handleMouseMove = useThrottledCallback(\n (event: React.MouseEvent) => {\n event.stopPropagation();\n findAndSetHoveredComponent(event.clientX, event.clientY);\n },\n 1000 / 60, // 60 FPS\n [findAndSetHoveredComponent]\n );\n\n const handleMouseLeave = useCallback(\n (event: React.MouseEvent) => {\n event.stopPropagation();\n findAndSetHoveredComponent(event.clientX, event.clientY);\n },\n [findAndSetHoveredComponent]\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n setSelectedComponent(componentId);\n },\n [setSelectedComponent, componentId]\n );\n\n const showFrame = [selectedComponentId, hoveredComponentId].includes(componentId) && !isDragging;\n const isDraggable = Boolean(componentId && regionId && componentType?.id);\n\n const classes = useComponentDecoratorClasses({\n componentId,\n isLocalized,\n isFragment: Boolean(isFragment),\n });\n\n const context = React.useMemo<ComponentContextType>(() => ({ componentId: id, name }), [id, name]);\n\n // Makes the component a drop target.\n const handleDragOver = React.useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n // Don't prevent propagation here.\n // We depend on the global listener to handle the drag over event.\n // If we are moving a component, don't let it be droppable on itself.\n if (draggingSourceComponentId !== componentId) {\n event.preventDefault();\n }\n },\n [draggingSourceComponentId, componentId]\n );\n\n // When dragging, we don't consider the component as dragging until the drag start event\n // is triggered. However, we need to mark the component as draggable on mouse down so that\n // it can even be dragged via the native drag and drop API.\n //\n // If we were to mark the components as dragging on mouse down instead, a selection of a component\n // would first remove the frame because this thinks we are dragging the component instead of selecting it.\n // This is why it is split up into two events.\n const handleMouseDown = React.useCallback(\n (event: React.MouseEvent) => {\n if (componentId) {\n event.stopPropagation();\n setPendingComponentDragId(componentId);\n }\n },\n [componentId, setPendingComponentDragId]\n );\n\n const handleDragStart = React.useCallback(\n (event: React.DragEvent) => {\n event.stopPropagation();\n\n if (componentId && regionId && componentType?.id) {\n startComponentMove(componentId, regionId, componentType.id);\n }\n },\n [componentId, regionId, componentType?.id, startComponentMove]\n );\n\n // Don't render anything if the components is hidden via visibility rules.\n // We still want the component to be reactive in case the use changes the\n // visibility rules or the render context.\n if (!isVisible) {\n return <></>;\n }\n\n return (\n <div\n ref={dragRef}\n className={classes}\n draggable={isPendingDrag && isDraggable}\n onClick={handleClick}\n onDragOver={handleDragOver}\n onDragStart={handleDragStart}\n onMouseMove={handleMouseMove}\n onMouseLeave={handleMouseLeave}\n onMouseDown={handleMouseDown}\n data-component-type={componentType?.id}\n data-testid={`design-component-${componentId}`}>\n <div className=\"pd-design__component__drop-target\" />\n <DesignFrame\n showFrame={showFrame}\n componentId={componentId}\n localized={isLocalized}\n name={componentName}\n parentId={parentComponentId}\n isMoveable={isDraggable}\n regionId={regionId}>\n <ComponentContext.Provider value={context}>{children}</ComponentContext.Provider>\n </DesignFrame>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DesignComponent.js","names":[],"sources":["../src/design/react/hooks/useComponentDecoratorClasses.ts","../src/design/react/hooks/useFocusedComponentHandler.ts","../src/design/react/hooks/useComponentInfo.ts","../src/design/react/components/DesignComponent.tsx"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useDesignState } from './useDesignState';\n\nexport function useComponentDecoratorClasses({\n contentLinkUuid,\n isFragment,\n isLocalized,\n}: {\n contentLinkUuid: string;\n isFragment: boolean;\n isLocalized: boolean;\n}): string {\n const { selectedContentLinkUuid, hoveredContentLinkUuid, dragState } = useDesignState();\n\n const isSelected = selectedContentLinkUuid === contentLinkUuid;\n const isHovered = !dragState.isDragging && hoveredContentLinkUuid === contentLinkUuid;\n const showFrame = (isSelected || isHovered) && !dragState.isDragging;\n const isMoving = dragState.isDragging && dragState.sourceContentLinkUuid === contentLinkUuid;\n const isDropTarget = dragState.currentDropTarget?.contentLinkUuid === contentLinkUuid;\n const dropTargetInsertType = dragState.currentDropTarget?.insertType;\n const dropTargetAxis = dropTargetInsertType?.axis;\n\n return [\n 'pd-design__decorator',\n isFragment ? 'pd-design__fragment' : 'pd-design__component',\n showFrame && 'pd-design__frame--visible',\n isSelected && 'pd-design__decorator--selected',\n isHovered && 'pd-design__decorator--hovered',\n isMoving && 'pd-design__decorator--moving',\n !isLocalized && 'pd-design__component--unlocalized',\n isDropTarget &&\n dropTargetAxis &&\n dropTargetInsertType &&\n `pd-design__drop-target__${dropTargetAxis}-${dropTargetInsertType.type}`,\n ]\n .filter(Boolean)\n .join(' ');\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\nimport { useDesignState } from './useDesignState';\n\n/**\n * Focuses a component when the focused component id matches the content link UUID.\n * @param contentLinkUuid - The content link UUID of the component.\n * @param nodeRef - The ref object to the node to focus.\n */\nexport function useFocusedComponentHandler(contentLinkUuid: string, nodeRef: React.RefObject<Element | null>): void {\n const { focusedContentLinkUuid, focusComponent } = useDesignState();\n\n React.useEffect(() => {\n if (focusedContentLinkUuid === contentLinkUuid && nodeRef.current) {\n focusComponent(nodeRef.current);\n }\n }, [focusedContentLinkUuid, contentLinkUuid, focusComponent, nodeRef]);\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ComponentInfo } from '../../messaging-api';\nimport { useDesignContext } from '../context/DesignContext';\nimport { useDesignState } from './useDesignState';\n\n/**\n * Hook that returns the current ComponentInfo for a given component ID,\n * merging the base config with any runtime updates.\n *\n * @param componentId - The ID of the component to get info for\n * @returns The merged ComponentInfo or null if the component doesn't exist\n */\nexport function useComponentInfo(componentId: string): ComponentInfo | null {\n const { pageDesignerConfig } = useDesignContext();\n const { componentUpdates } = useDesignState();\n const baseComponentInfo = pageDesignerConfig?.components?.[componentId];\n const updates = componentUpdates?.[componentId] ?? {};\n\n if (!baseComponentInfo) {\n return null;\n }\n\n return { ...baseComponentInfo, ...updates };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React, { useRef, useCallback } from 'react';\nimport type { ComponentDecoratorProps } from '../core/component.types';\nimport { useComponentDecoratorClasses } from '../hooks/useComponentDecoratorClasses';\nimport { useDesignState } from '../hooks/useDesignState';\nimport { useFocusedComponentHandler } from '../hooks/useFocusedComponentHandler';\nimport { useNodeToTargetStore } from '../hooks/useNodeToTargetStore';\nimport { DesignFrame } from './DesignFrame';\nimport { useRegionContext } from '../core/RegionContext';\nimport { ComponentContext, useComponentContext, type ComponentContextType } from '../core/ComponentContext';\nimport { useComponentDiscovery } from '../hooks/useComponentDiscovery';\nimport { useComponentType } from '../hooks/useComponentType';\nimport { useThrottledCallback } from '../hooks/useThrottledCallback';\nimport { useComponentInfo } from '../hooks/useComponentInfo';\n\nexport function DesignComponent(props: ComponentDecoratorProps<unknown>): React.JSX.Element {\n const { designMetadata, children } = props;\n const {\n id = '',\n contentLinkUuid = '',\n name,\n isFragment = false,\n isVisible = true,\n isLocalized = false,\n } = designMetadata ?? {};\n const componentId = id;\n const componentType = useComponentType(componentId);\n const componentInfo = useComponentInfo(componentId);\n const { nodeToTargetMap } = useDesignState();\n\n const componentName = componentInfo?.name || componentType?.label || name || 'Component';\n const dragRef = useRef<HTMLDivElement>(null);\n const { regionId } = useRegionContext() ?? {};\n const { componentId: parentComponentId } = useComponentContext() ?? {};\n\n const {\n selectedContentLinkUuid,\n hoveredContentLinkUuid,\n setSelectedComponent,\n setHoveredComponent,\n startComponentMove,\n setPendingDragContentLinkUuid,\n dragState: { pendingDragContentLinkUuid, isDragging, sourceContentLinkUuid: draggingSourceContentLinkUuid },\n registerContentLink,\n } = useDesignState();\n\n React.useEffect(() => {\n if (contentLinkUuid && componentId) {\n registerContentLink(contentLinkUuid, componentId);\n }\n }, [componentId, contentLinkUuid, registerContentLink]);\n\n useFocusedComponentHandler(contentLinkUuid, dragRef);\n useNodeToTargetStore({\n type: 'component',\n nodeRef: dragRef,\n parentId: parentComponentId,\n regionId,\n componentId,\n contentLinkUuid,\n });\n\n const discoverComponents = useComponentDiscovery({\n nodeToTargetMap,\n });\n\n const isPendingDrag = pendingDragContentLinkUuid === contentLinkUuid;\n const findAndSetHoveredComponent = useCallback(\n (x: number, y: number) => {\n // If we hover off a component, we could still be hovering over a parent component\n // that contains that child. In this instance, the mouse enter doesn't fire and that parent\n // would not be highlighted. Everytime we leave a component, we check\n // if we are hovering over a component at those coordinates. If we are,\n // we set the hovered component to that component.\n const components = discoverComponents({\n x,\n y,\n filter: (entry) => entry.type === 'component',\n });\n\n setHoveredComponent(components[0]?.contentLinkUuid ?? null);\n },\n [setHoveredComponent, discoverComponents]\n );\n\n const handleMouseMove = useThrottledCallback(\n (event: React.MouseEvent) => {\n event.stopPropagation();\n findAndSetHoveredComponent(event.clientX, event.clientY);\n },\n 1000 / 60, // 60 FPS\n [findAndSetHoveredComponent]\n );\n\n const handleMouseLeave = useCallback(\n (event: React.MouseEvent) => {\n event.stopPropagation();\n findAndSetHoveredComponent(event.clientX, event.clientY);\n },\n [findAndSetHoveredComponent]\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n setSelectedComponent(contentLinkUuid ?? '');\n },\n [setSelectedComponent, contentLinkUuid]\n );\n\n const showFrame = [selectedContentLinkUuid, hoveredContentLinkUuid].includes(contentLinkUuid ?? '') && !isDragging;\n const isDraggable = Boolean(componentId && regionId && componentType?.id);\n\n const classes = useComponentDecoratorClasses({\n contentLinkUuid,\n isLocalized,\n isFragment: Boolean(isFragment),\n });\n\n const context = React.useMemo<ComponentContextType>(\n () => ({ componentId: id, name, contentLinkUuid }),\n [id, name, contentLinkUuid]\n );\n\n // Makes the component a drop target.\n const handleDragOver = React.useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n // Don't prevent propagation here.\n // We depend on the global listener to handle the drag over event.\n // If we are moving a component, don't let it be droppable on itself.\n // Compare by contentLinkUuid to handle duplicate components correctly.\n if (draggingSourceContentLinkUuid !== contentLinkUuid) {\n event.preventDefault();\n }\n },\n [draggingSourceContentLinkUuid, contentLinkUuid]\n );\n\n // When dragging, we don't consider the component as dragging until the drag start event\n // is triggered. However, we need to mark the component as draggable on mouse down so that\n // it can even be dragged via the native drag and drop API.\n //\n // If we were to mark the components as dragging on mouse down instead, a selection of a component\n // would first remove the frame because this thinks we are dragging the component instead of selecting it.\n // This is why it is split up into two events.\n const handleMouseDown = React.useCallback(\n (event: React.MouseEvent) => {\n if (contentLinkUuid) {\n event.stopPropagation();\n setPendingDragContentLinkUuid(contentLinkUuid);\n }\n },\n [contentLinkUuid, setPendingDragContentLinkUuid]\n );\n\n const handleDragStart = React.useCallback(\n (event: React.DragEvent) => {\n event.stopPropagation();\n\n if (componentId && regionId && componentType?.id) {\n startComponentMove(componentId, regionId, componentType.id, contentLinkUuid);\n }\n },\n [componentId, regionId, componentType?.id, contentLinkUuid, startComponentMove]\n );\n\n // Don't render anything if the components is hidden via visibility rules.\n // We still want the component to be reactive in case the use changes the\n // visibility rules or the render context.\n if (!isVisible) {\n return <></>;\n }\n\n return (\n <div\n ref={dragRef}\n className={classes}\n draggable={isPendingDrag && isDraggable}\n onClick={handleClick}\n onDragOver={handleDragOver}\n onDragStart={handleDragStart}\n onMouseMove={handleMouseMove}\n onMouseLeave={handleMouseLeave}\n onMouseDown={handleMouseDown}\n data-component-type={componentType?.id}\n data-testid={`design-component-${componentId}`}>\n <div className=\"pd-design__component__drop-target\" />\n <DesignFrame\n showFrame={showFrame}\n componentId={componentId}\n contentLinkUuid={contentLinkUuid}\n localized={isLocalized}\n name={componentName}\n parentId={parentComponentId}\n isMoveable={isDraggable}\n regionId={regionId}>\n <ComponentContext.Provider value={context}>{children}</ComponentContext.Provider>\n </DesignFrame>\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist/DesignContext.js
CHANGED
|
@@ -37,28 +37,29 @@ function useInteraction(config) {
|
|
|
37
37
|
* Custom hook that manages component selection state and handles
|
|
38
38
|
* client-host communication for selection events.
|
|
39
39
|
*
|
|
40
|
-
* @param isDesignMode - Whether design mode is active
|
|
41
|
-
* @param clientApi - Client API for host communication
|
|
42
40
|
* @returns Selection state and interaction methods
|
|
43
41
|
*/
|
|
44
|
-
function useSelectInteraction() {
|
|
45
|
-
const { state:
|
|
42
|
+
function useSelectInteraction({ contentLinkMap }) {
|
|
43
|
+
const { state: selectedContentLinkUuid, setSelectedComponent } = useInteraction({
|
|
46
44
|
initialState: "",
|
|
47
45
|
eventHandlers: {
|
|
48
46
|
ComponentSelected: { handler: (event, setState) => {
|
|
49
|
-
setState(event.
|
|
47
|
+
setState(event.contentLinkUuid);
|
|
50
48
|
} },
|
|
51
49
|
ComponentDeselected: { handler: (_, setState) => {
|
|
52
50
|
setState("");
|
|
53
51
|
} }
|
|
54
52
|
},
|
|
55
|
-
actions: (_state, setState, clientApi) => ({ setSelectedComponent: (
|
|
56
|
-
setState(
|
|
57
|
-
clientApi?.selectComponent({
|
|
53
|
+
actions: (_state, setState, clientApi) => ({ setSelectedComponent: (contentLinkUuid) => {
|
|
54
|
+
setState(contentLinkUuid);
|
|
55
|
+
clientApi?.selectComponent({
|
|
56
|
+
componentId: contentLinkMap[contentLinkUuid] ?? "",
|
|
57
|
+
contentLinkUuid
|
|
58
|
+
});
|
|
58
59
|
} })
|
|
59
60
|
});
|
|
60
61
|
return {
|
|
61
|
-
|
|
62
|
+
selectedContentLinkUuid,
|
|
62
63
|
setSelectedComponent
|
|
63
64
|
};
|
|
64
65
|
}
|
|
@@ -71,34 +72,40 @@ function useSelectInteraction() {
|
|
|
71
72
|
*
|
|
72
73
|
* @returns Hover state and interaction methods
|
|
73
74
|
*/
|
|
74
|
-
function useHoverInteraction() {
|
|
75
|
-
const { state:
|
|
75
|
+
function useHoverInteraction({ contentLinkMap }) {
|
|
76
|
+
const { state: hoveredContentLinkUuid, setHoveredComponent } = useInteraction({
|
|
76
77
|
initialState: null,
|
|
77
78
|
eventHandlers: {
|
|
78
|
-
ComponentHoveredIn: { handler: (event, setState) => setState(event.
|
|
79
|
+
ComponentHoveredIn: { handler: (event, setState) => setState(event.contentLinkUuid) },
|
|
79
80
|
ComponentHoveredOut: { handler: (_, setState) => setState(null) }
|
|
80
81
|
},
|
|
81
|
-
actions: (state, setState, clientApi) => ({ setHoveredComponent: (
|
|
82
|
-
if (state &&
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
actions: (state, setState, clientApi) => ({ setHoveredComponent: (componentUuid) => {
|
|
83
|
+
if (state && componentUuid !== state) clientApi?.hoverOutOfComponent({
|
|
84
|
+
componentId: contentLinkMap[state] ?? state,
|
|
85
|
+
contentLinkUuid: state
|
|
86
|
+
});
|
|
87
|
+
if (componentUuid && componentUuid !== state) clientApi?.hoverInToComponent({
|
|
88
|
+
componentId: contentLinkMap[componentUuid] ?? null,
|
|
89
|
+
contentLinkUuid: componentUuid
|
|
90
|
+
});
|
|
91
|
+
setState(componentUuid);
|
|
85
92
|
} })
|
|
86
93
|
});
|
|
87
94
|
return {
|
|
88
|
-
|
|
95
|
+
hoveredContentLinkUuid,
|
|
89
96
|
setHoveredComponent
|
|
90
97
|
};
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
//#endregion
|
|
94
101
|
//#region src/design/react/hooks/useDeleteInteraction.ts
|
|
95
|
-
function useDeleteInteraction({
|
|
102
|
+
function useDeleteInteraction({ selectedContentLinkUuid, setSelectedComponent }) {
|
|
96
103
|
const { deleteComponent } = useInteraction({
|
|
97
104
|
initialState: null,
|
|
98
105
|
eventHandlers: {},
|
|
99
106
|
actions: (_state, _setState, clientApi) => ({ deleteComponent: (event) => {
|
|
100
107
|
clientApi?.deleteComponent(event);
|
|
101
|
-
if (
|
|
108
|
+
if (selectedContentLinkUuid === event.contentLinkUuid) setSelectedComponent("");
|
|
102
109
|
} })
|
|
103
110
|
});
|
|
104
111
|
return { deleteComponent };
|
|
@@ -107,11 +114,11 @@ function useDeleteInteraction({ selectedComponentId, setSelectedComponent }) {
|
|
|
107
114
|
//#endregion
|
|
108
115
|
//#region src/design/react/hooks/useFocusInteraction.ts
|
|
109
116
|
function useFocusInteraction({ setSelectedComponent }) {
|
|
110
|
-
const { state:
|
|
117
|
+
const { state: focusedContentLinkUuid, focusComponent } = useInteraction({
|
|
111
118
|
initialState: null,
|
|
112
119
|
eventHandlers: { ComponentFocused: { handler: (event, setState) => {
|
|
113
120
|
setSelectedComponent("");
|
|
114
|
-
setState(event.
|
|
121
|
+
setState(event.contentLinkUuid);
|
|
115
122
|
} } },
|
|
116
123
|
actions: (_state, setState) => ({ focusComponent: (node) => {
|
|
117
124
|
node.scrollIntoView();
|
|
@@ -119,7 +126,7 @@ function useFocusInteraction({ setSelectedComponent }) {
|
|
|
119
126
|
} })
|
|
120
127
|
});
|
|
121
128
|
return {
|
|
122
|
-
|
|
129
|
+
focusedContentLinkUuid,
|
|
123
130
|
focusComponent
|
|
124
131
|
};
|
|
125
132
|
}
|
|
@@ -237,10 +244,10 @@ function getInsertionType({ cache, node, x, y }) {
|
|
|
237
244
|
type: relativeDeltaY < 0 ? "before" : "after"
|
|
238
245
|
};
|
|
239
246
|
}
|
|
240
|
-
function isOnSelfDropTarget({
|
|
241
|
-
const isOnSource =
|
|
242
|
-
const isOnSameRegionBefore =
|
|
243
|
-
const isOnSameRegionAfter =
|
|
247
|
+
function isOnSelfDropTarget({ sourceContentLinkUuid, beforeContentLinkUuid, afterContentLinkUuid, insertType, contentLinkUuid }) {
|
|
248
|
+
const isOnSource = sourceContentLinkUuid && contentLinkUuid === sourceContentLinkUuid;
|
|
249
|
+
const isOnSameRegionBefore = sourceContentLinkUuid && insertType.type === "before" && beforeContentLinkUuid === sourceContentLinkUuid;
|
|
250
|
+
const isOnSameRegionAfter = sourceContentLinkUuid && insertType.type === "after" && afterContentLinkUuid === sourceContentLinkUuid;
|
|
244
251
|
return isOnSource || isOnSameRegionBefore || isOnSameRegionAfter;
|
|
245
252
|
}
|
|
246
253
|
function useDragInteraction({ nodeToTargetMap }) {
|
|
@@ -267,9 +274,9 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
267
274
|
region
|
|
268
275
|
};
|
|
269
276
|
}, [discoverComponents]);
|
|
270
|
-
const
|
|
271
|
-
const componentIndex = region.
|
|
272
|
-
return [region.
|
|
277
|
+
const getInsertionComponentUuids = (contentLinkUuid, region) => {
|
|
278
|
+
const componentIndex = region.contentLinkUuids.indexOf(contentLinkUuid);
|
|
279
|
+
return [region.contentLinkUuids[componentIndex - 1], region.contentLinkUuids[componentIndex + 1]];
|
|
273
280
|
};
|
|
274
281
|
const getCurrentDropTarget = useCallback(({ x, y, rectCache, componentType }) => {
|
|
275
282
|
const { component, region } = getNearestComponentAndRegion(x, y);
|
|
@@ -284,16 +291,17 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
284
291
|
axis: "y",
|
|
285
292
|
type: "after"
|
|
286
293
|
};
|
|
287
|
-
const
|
|
294
|
+
const componentContentLinkUuid = component?.contentLinkUuid ?? "";
|
|
295
|
+
const [beforeContentLinkUuid, afterContentLinkUuid] = component ? getInsertionComponentUuids(componentContentLinkUuid, region) : [];
|
|
288
296
|
return {
|
|
289
297
|
type: component ? "component" : "region",
|
|
290
298
|
regionId: region.regionId,
|
|
291
|
-
componentIds: region.componentIds,
|
|
292
299
|
componentId: component?.componentId ?? "",
|
|
300
|
+
contentLinkUuid: componentContentLinkUuid,
|
|
293
301
|
parentId: region.parentId,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
302
|
+
beforeContentLinkUuid,
|
|
303
|
+
afterContentLinkUuid,
|
|
304
|
+
insertContentLinkUuid: componentContentLinkUuid,
|
|
297
305
|
insertType,
|
|
298
306
|
componentTypeInclusions: region.componentTypeInclusions,
|
|
299
307
|
componentTypeExclusions: region.componentTypeExclusions
|
|
@@ -314,18 +322,19 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
314
322
|
return 0;
|
|
315
323
|
};
|
|
316
324
|
const scrollFactorRef = useRef(0);
|
|
317
|
-
const { state: dragState, commitCurrentDropTarget, updateComponentMove, startComponentMove, dropComponent, cancelDrag,
|
|
325
|
+
const { state: dragState, commitCurrentDropTarget, updateComponentMove, startComponentMove, dropComponent, cancelDrag, setPendingDragContentLinkUuid } = useInteraction({
|
|
318
326
|
initialState: {
|
|
319
327
|
isDragging: false,
|
|
320
328
|
componentType: "",
|
|
321
|
-
|
|
329
|
+
fragmentId: void 0,
|
|
330
|
+
sourceContentLinkUuid: void 0,
|
|
322
331
|
sourceRegionId: void 0,
|
|
323
332
|
x: 0,
|
|
324
333
|
y: 0,
|
|
325
334
|
currentDropTarget: null,
|
|
326
335
|
pendingTargetCommit: false,
|
|
327
336
|
rectCache: /* @__PURE__ */ new WeakMap(),
|
|
328
|
-
|
|
337
|
+
pendingDragContentLinkUuid: null
|
|
329
338
|
},
|
|
330
339
|
eventHandlers: {
|
|
331
340
|
ComponentDragStarted: { handler: (event, setState) => {
|
|
@@ -333,7 +342,8 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
333
342
|
setState((prevState) => ({
|
|
334
343
|
...prevState,
|
|
335
344
|
componentType: event.componentType,
|
|
336
|
-
|
|
345
|
+
fragmentId: event.fragmentId,
|
|
346
|
+
sourceContentLinkUuid: void 0,
|
|
337
347
|
sourceRegionId: void 0,
|
|
338
348
|
x: 0,
|
|
339
349
|
y: 0,
|
|
@@ -393,7 +403,7 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
393
403
|
y: 0,
|
|
394
404
|
scrollDirection: 0,
|
|
395
405
|
isDragging: false,
|
|
396
|
-
|
|
406
|
+
pendingDragContentLinkUuid: null
|
|
397
407
|
}));
|
|
398
408
|
},
|
|
399
409
|
updateComponentMove: ({ x, y }) => {
|
|
@@ -414,10 +424,10 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
414
424
|
})
|
|
415
425
|
}));
|
|
416
426
|
},
|
|
417
|
-
|
|
427
|
+
setPendingDragContentLinkUuid: (contentLinkUuid) => {
|
|
418
428
|
setState((prevState) => ({
|
|
419
429
|
...prevState,
|
|
420
|
-
|
|
430
|
+
pendingDragContentLinkUuid: contentLinkUuid
|
|
421
431
|
}));
|
|
422
432
|
},
|
|
423
433
|
dropComponent: () => {
|
|
@@ -427,14 +437,14 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
427
437
|
pendingTargetCommit: true
|
|
428
438
|
}));
|
|
429
439
|
},
|
|
430
|
-
startComponentMove: (componentId, regionId, componentType) => {
|
|
440
|
+
startComponentMove: (componentId, regionId, componentType, contentLinkUuid) => {
|
|
431
441
|
scrollFactorRef.current = 0;
|
|
432
442
|
setState((prevState) => ({
|
|
433
443
|
...prevState,
|
|
434
444
|
x: 0,
|
|
435
445
|
y: 0,
|
|
436
446
|
componentType,
|
|
437
|
-
|
|
447
|
+
sourceContentLinkUuid: contentLinkUuid,
|
|
438
448
|
sourceRegionId: regionId,
|
|
439
449
|
isDragging: true,
|
|
440
450
|
scrollDirection: 0,
|
|
@@ -443,31 +453,33 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
443
453
|
},
|
|
444
454
|
commitCurrentDropTarget: () => {
|
|
445
455
|
if (state.currentDropTarget) {
|
|
446
|
-
if (state.
|
|
456
|
+
if (state.sourceContentLinkUuid) {
|
|
447
457
|
if (!isOnSelfDropTarget({
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
458
|
+
sourceContentLinkUuid: state.sourceContentLinkUuid,
|
|
459
|
+
beforeContentLinkUuid: state.currentDropTarget.beforeContentLinkUuid,
|
|
460
|
+
afterContentLinkUuid: state.currentDropTarget.afterContentLinkUuid,
|
|
451
461
|
insertType: state.currentDropTarget.insertType,
|
|
452
|
-
|
|
462
|
+
contentLinkUuid: state.currentDropTarget.contentLinkUuid ?? ""
|
|
453
463
|
})) clientApi?.moveComponentToRegion({
|
|
454
|
-
componentId: state.
|
|
464
|
+
componentId: state.currentDropTarget.componentId ?? "",
|
|
465
|
+
contentLinkUuid: state.sourceContentLinkUuid,
|
|
455
466
|
sourceRegionId: state.sourceRegionId ?? "",
|
|
456
467
|
insertType: state.currentDropTarget.insertType?.type,
|
|
457
|
-
insertComponentId: state.currentDropTarget.
|
|
458
|
-
beforeComponentId: state.currentDropTarget.
|
|
459
|
-
afterComponentId: state.currentDropTarget.
|
|
468
|
+
insertComponentId: state.currentDropTarget.insertContentLinkUuid,
|
|
469
|
+
beforeComponentId: state.currentDropTarget.beforeContentLinkUuid,
|
|
470
|
+
afterComponentId: state.currentDropTarget.afterContentLinkUuid,
|
|
460
471
|
targetRegionId: state.currentDropTarget.regionId,
|
|
461
472
|
targetComponentId: state.currentDropTarget.parentId ?? ""
|
|
462
473
|
});
|
|
463
|
-
} else if (state.componentType) clientApi?.addComponentToRegion({
|
|
474
|
+
} else if (state.componentType || state.fragmentId) clientApi?.addComponentToRegion({
|
|
464
475
|
insertType: state.currentDropTarget.insertType?.type,
|
|
465
|
-
insertComponentId: state.currentDropTarget.
|
|
476
|
+
insertComponentId: state.currentDropTarget.insertContentLinkUuid,
|
|
477
|
+
beforeComponentId: state.currentDropTarget.beforeContentLinkUuid,
|
|
466
478
|
componentProperties: {},
|
|
467
|
-
componentType: state.componentType,
|
|
479
|
+
componentType: state.fragmentId ? "" : state.componentType ?? "",
|
|
480
|
+
fragmentId: state.fragmentId,
|
|
468
481
|
targetComponentId: state.currentDropTarget.parentId ?? "",
|
|
469
|
-
|
|
470
|
-
afterComponentId: state.currentDropTarget.afterComponentId,
|
|
482
|
+
afterComponentId: state.currentDropTarget.afterContentLinkUuid,
|
|
471
483
|
targetRegionId: state.currentDropTarget.regionId
|
|
472
484
|
});
|
|
473
485
|
}
|
|
@@ -478,9 +490,9 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
478
490
|
y: 0,
|
|
479
491
|
componentType: "",
|
|
480
492
|
scrollDirection: 0,
|
|
481
|
-
|
|
493
|
+
sourceContentLinkUuid: void 0,
|
|
482
494
|
sourceRegionId: void 0,
|
|
483
|
-
|
|
495
|
+
pendingDragContentLinkUuid: null,
|
|
484
496
|
currentDropTarget: null,
|
|
485
497
|
pendingTargetCommit: false
|
|
486
498
|
}));
|
|
@@ -501,7 +513,7 @@ function useDragInteraction({ nodeToTargetMap }) {
|
|
|
501
513
|
}, [dragState.scrollDirection, scrollFactorRef]);
|
|
502
514
|
return {
|
|
503
515
|
dragState,
|
|
504
|
-
|
|
516
|
+
setPendingDragContentLinkUuid,
|
|
505
517
|
commitCurrentDropTarget,
|
|
506
518
|
startComponentMove,
|
|
507
519
|
updateComponentMove,
|
|
@@ -556,10 +568,20 @@ function useComponentUpdateInteraction() {
|
|
|
556
568
|
//#region src/design/react/context/DesignStateContext.tsx
|
|
557
569
|
const DesignStateContext = React.createContext(null);
|
|
558
570
|
const DesignStateProvider = ({ children }) => {
|
|
559
|
-
const
|
|
560
|
-
const
|
|
571
|
+
const [contentLinkMap, setContentLinkMap] = React.useState({});
|
|
572
|
+
const registerContentLink = React.useCallback((contentLinkUuid, componentId) => {
|
|
573
|
+
setContentLinkMap((prev) => {
|
|
574
|
+
if (prev[contentLinkUuid] === componentId) return prev;
|
|
575
|
+
return {
|
|
576
|
+
...prev,
|
|
577
|
+
[contentLinkUuid]: componentId
|
|
578
|
+
};
|
|
579
|
+
});
|
|
580
|
+
}, []);
|
|
581
|
+
const selectInteraction = useSelectInteraction({ contentLinkMap });
|
|
582
|
+
const hoverInteraction = useHoverInteraction({ contentLinkMap });
|
|
561
583
|
const deleteInteraction = useDeleteInteraction({
|
|
562
|
-
|
|
584
|
+
selectedContentLinkUuid: selectInteraction.selectedContentLinkUuid,
|
|
563
585
|
setSelectedComponent: selectInteraction.setSelectedComponent
|
|
564
586
|
});
|
|
565
587
|
const focusInteraction = useFocusInteraction({ setSelectedComponent: selectInteraction.setSelectedComponent });
|
|
@@ -575,7 +597,9 @@ const DesignStateProvider = ({ children }) => {
|
|
|
575
597
|
...dragInteraction,
|
|
576
598
|
...scrollInteraction,
|
|
577
599
|
...componentUpdateInteraction,
|
|
578
|
-
nodeToTargetMap
|
|
600
|
+
nodeToTargetMap,
|
|
601
|
+
contentLinkMap,
|
|
602
|
+
registerContentLink
|
|
579
603
|
}), [
|
|
580
604
|
deleteInteraction,
|
|
581
605
|
selectInteraction,
|
|
@@ -584,7 +608,9 @@ const DesignStateProvider = ({ children }) => {
|
|
|
584
608
|
dragInteraction,
|
|
585
609
|
nodeToTargetMap,
|
|
586
610
|
scrollInteraction,
|
|
587
|
-
componentUpdateInteraction
|
|
611
|
+
componentUpdateInteraction,
|
|
612
|
+
contentLinkMap,
|
|
613
|
+
registerContentLink
|
|
588
614
|
]);
|
|
589
615
|
return /* @__PURE__ */ jsx(DesignStateContext.Provider, {
|
|
590
616
|
value: state,
|