@elementor/editor-canvas 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/.turbo/turbo-build.log +22 -0
- package/CHANGELOG.md +19 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +161 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +140 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +45 -0
- package/src/components/__tests__/elements-overlays.test.tsx +127 -0
- package/src/components/element-overlay.tsx +43 -0
- package/src/components/elements-overlays.tsx +29 -0
- package/src/hooks/use-bind-react-props-to-element.ts +49 -0
- package/src/hooks/use-floating-on-element.ts +48 -0
- package/src/index.ts +3 -0
- package/src/init.tsx +10 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
> @elementor/editor-canvas@0.1.0 build
|
|
3
|
+
> tsup --config=../../tsup.build.ts
|
|
4
|
+
|
|
5
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
+
[34mCLI[39m Using tsconfig: ../../../tsconfig.json
|
|
7
|
+
[34mCLI[39m tsup v8.3.5
|
|
8
|
+
[34mCLI[39m Using tsup config: /home/runner/work/elementor-packages/elementor-packages/tsup.build.ts
|
|
9
|
+
[34mCLI[39m Target: esnext
|
|
10
|
+
[34mCLI[39m Cleaning output folder
|
|
11
|
+
[34mESM[39m Build start
|
|
12
|
+
[34mCJS[39m Build start
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m4.77 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m8.61 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 48ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m6.13 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m8.63 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 49ms
|
|
19
|
+
[34mDTS[39m Build start
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 11882ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m13.00 B[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m13.00 B[39m
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @elementor/editor-canvas
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d21c5c3: Introduce editor canvas to render element overlays for atomic elements
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [e7f4706]
|
|
12
|
+
- Updated dependencies [7781969]
|
|
13
|
+
- Updated dependencies [e4c6e3b]
|
|
14
|
+
- Updated dependencies [d21c5c3]
|
|
15
|
+
- Updated dependencies [7781969]
|
|
16
|
+
- Updated dependencies [0c6bcb6]
|
|
17
|
+
- @elementor/editor-elements@0.3.0
|
|
18
|
+
- @elementor/editor@0.17.0
|
|
19
|
+
- @elementor/editor-v1-adapters@0.8.4
|
package/dist/index.d.mts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/init.tsx
|
|
26
|
+
var import_editor = require("@elementor/editor");
|
|
27
|
+
|
|
28
|
+
// src/components/elements-overlays.tsx
|
|
29
|
+
var React2 = __toESM(require("react"));
|
|
30
|
+
var import_editor_elements = require("@elementor/editor-elements");
|
|
31
|
+
var import_editor_v1_adapters = require("@elementor/editor-v1-adapters");
|
|
32
|
+
|
|
33
|
+
// src/components/element-overlay.tsx
|
|
34
|
+
var React = __toESM(require("react"));
|
|
35
|
+
var import_ui = require("@elementor/ui");
|
|
36
|
+
var import_react4 = require("@floating-ui/react");
|
|
37
|
+
|
|
38
|
+
// src/hooks/use-bind-react-props-to-element.ts
|
|
39
|
+
var import_react = require("react");
|
|
40
|
+
function useBindReactPropsToElement(element, getProps) {
|
|
41
|
+
(0, import_react.useEffect)(() => {
|
|
42
|
+
const el = element;
|
|
43
|
+
const { events, attrs } = groupProps(getProps());
|
|
44
|
+
events.forEach(([eventName, listener]) => el.addEventListener(eventName, listener));
|
|
45
|
+
attrs.forEach(([attrName, attrValue]) => el.setAttribute(attrName, attrValue));
|
|
46
|
+
return () => {
|
|
47
|
+
events.forEach(([eventName, listener]) => el.removeEventListener(eventName, listener));
|
|
48
|
+
attrs.forEach(([attrName]) => el.removeAttribute(attrName));
|
|
49
|
+
};
|
|
50
|
+
}, [getProps, element]);
|
|
51
|
+
}
|
|
52
|
+
function groupProps(props) {
|
|
53
|
+
const eventRegex = /^on(?=[A-Z])/;
|
|
54
|
+
return Object.entries(props).reduce(
|
|
55
|
+
(acc, [propName, propValue]) => {
|
|
56
|
+
if (!eventRegex.test(propName)) {
|
|
57
|
+
acc.attrs.push([propName, propValue]);
|
|
58
|
+
return acc;
|
|
59
|
+
}
|
|
60
|
+
const eventName = propName.replace(eventRegex, "").toLowerCase();
|
|
61
|
+
const listener = propValue;
|
|
62
|
+
acc.events.push([eventName, listener]);
|
|
63
|
+
return acc;
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
events: [],
|
|
67
|
+
attrs: []
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/hooks/use-floating-on-element.ts
|
|
73
|
+
var import_react2 = require("react");
|
|
74
|
+
var import_react3 = require("@floating-ui/react");
|
|
75
|
+
function useFloatingOnElement({ element, isSelected }) {
|
|
76
|
+
const [isOpen, setIsOpen] = (0, import_react2.useState)(false);
|
|
77
|
+
const { refs, floatingStyles, context } = (0, import_react3.useFloating)({
|
|
78
|
+
// Must be controlled for interactions (like hover) to work.
|
|
79
|
+
open: isOpen || isSelected,
|
|
80
|
+
onOpenChange: setIsOpen,
|
|
81
|
+
// Add an animation frame to support scroll events (without it the floating element will stay in the same position).
|
|
82
|
+
whileElementsMounted: (...args) => (0, import_react3.autoUpdate)(...args, { animationFrame: true }),
|
|
83
|
+
// The first element in the canvas is `display: contents` so we need to use the first child.
|
|
84
|
+
elements: { reference: element.firstElementChild },
|
|
85
|
+
middleware: [
|
|
86
|
+
// Match the floating element's size to the reference element.
|
|
87
|
+
(0, import_react3.size)({
|
|
88
|
+
apply({ elements, rects }) {
|
|
89
|
+
Object.assign(elements.floating.style, {
|
|
90
|
+
width: `${rects.reference.width}px`,
|
|
91
|
+
height: `${rects.reference.height}px`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
// Center the floating element on the reference element.
|
|
96
|
+
(0, import_react3.offset)(({ rects }) => -rects.reference.height / 2 - rects.floating.height / 2)
|
|
97
|
+
]
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
isVisible: isOpen || isSelected,
|
|
101
|
+
context,
|
|
102
|
+
floating: {
|
|
103
|
+
setRef: refs.setFloating,
|
|
104
|
+
ref: refs.floating,
|
|
105
|
+
styles: floatingStyles
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/components/element-overlay.tsx
|
|
111
|
+
var CANVAS_WRAPPER_ID = "elementor-preview-responsive-wrapper";
|
|
112
|
+
var OverlayBox = (0, import_ui.styled)(import_ui.Box, { shouldForwardProp: (prop) => prop !== "isSelected" })(({ theme, isSelected }) => ({
|
|
113
|
+
outline: `${isSelected ? "2px" : "1px"} solid ${theme.palette.primary.light}`,
|
|
114
|
+
outlineOffset: isSelected ? "-2px" : "-1px",
|
|
115
|
+
pointerEvents: "none"
|
|
116
|
+
}));
|
|
117
|
+
function ElementOverlay({ element, isSelected }) {
|
|
118
|
+
const { context, floating, isVisible } = useFloatingOnElement({ element, isSelected });
|
|
119
|
+
const { getFloatingProps, getReferenceProps } = (0, import_react4.useInteractions)([(0, import_react4.useHover)(context)]);
|
|
120
|
+
useBindReactPropsToElement(element, getReferenceProps);
|
|
121
|
+
return isVisible && /* @__PURE__ */ React.createElement(import_react4.FloatingPortal, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React.createElement(
|
|
122
|
+
OverlayBox,
|
|
123
|
+
{
|
|
124
|
+
ref: floating.setRef,
|
|
125
|
+
isSelected,
|
|
126
|
+
style: floating.styles,
|
|
127
|
+
"data-element-overlay": element.dataset.id,
|
|
128
|
+
role: "presentation",
|
|
129
|
+
...getFloatingProps()
|
|
130
|
+
}
|
|
131
|
+
));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/components/elements-overlays.tsx
|
|
135
|
+
function ElementsOverlays() {
|
|
136
|
+
const selected = (0, import_editor_elements.useSelectedElement)();
|
|
137
|
+
const domElements = (0, import_editor_elements.useElementsDomRef)();
|
|
138
|
+
const isPreviewMode = (0, import_editor_v1_adapters.__privateUseIsPreviewMode)();
|
|
139
|
+
const isKitRouteActive = (0, import_editor_v1_adapters.__privateUseIsRouteActive)("panel/global");
|
|
140
|
+
const isActive = !isPreviewMode && !isKitRouteActive;
|
|
141
|
+
return isActive && domElements.map((el) => /* @__PURE__ */ React2.createElement(
|
|
142
|
+
ElementOverlay,
|
|
143
|
+
{
|
|
144
|
+
element: el,
|
|
145
|
+
key: el.dataset.id,
|
|
146
|
+
isSelected: selected.element?.id === el.dataset.id
|
|
147
|
+
}
|
|
148
|
+
));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/init.tsx
|
|
152
|
+
function init() {
|
|
153
|
+
(0, import_editor.injectIntoTop)({
|
|
154
|
+
id: "elements-overlays",
|
|
155
|
+
component: ElementsOverlays
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/index.ts
|
|
160
|
+
init();
|
|
161
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init.tsx","../src/components/elements-overlays.tsx","../src/components/element-overlay.tsx","../src/hooks/use-bind-react-props-to-element.ts","../src/hooks/use-floating-on-element.ts","../src/index.ts"],"sourcesContent":["import { injectIntoTop } from '@elementor/editor';\n\nimport { ElementsOverlays } from './components/elements-overlays';\n\nexport function init() {\n\tinjectIntoTop( {\n\t\tid: 'elements-overlays',\n\t\tcomponent: ElementsOverlays,\n\t} );\n}\n","import * as React from 'react';\nimport { useElementsDomRef, useSelectedElement } from '@elementor/editor-elements';\nimport {\n\t__privateUseIsPreviewMode as useIsPreviewMode,\n\t__privateUseIsRouteActive as useIsRouteActive,\n} from '@elementor/editor-v1-adapters';\n\nimport { ElementOverlay } from './element-overlay';\n\nexport function ElementsOverlays() {\n\tconst selected = useSelectedElement();\n\tconst domElements = useElementsDomRef();\n\n\tconst isPreviewMode = useIsPreviewMode();\n\tconst isKitRouteActive = useIsRouteActive( 'panel/global' );\n\n\tconst isActive = ! isPreviewMode && ! isKitRouteActive;\n\n\treturn (\n\t\tisActive &&\n\t\tdomElements.map( ( el ) => (\n\t\t\t<ElementOverlay\n\t\t\t\telement={ el }\n\t\t\t\tkey={ el.dataset.id }\n\t\t\t\tisSelected={ selected.element?.id === el.dataset.id }\n\t\t\t/>\n\t\t) )\n\t);\n}\n","import * as React from 'react';\nimport { Box, styled } from '@elementor/ui';\nimport { FloatingPortal, useHover, useInteractions } from '@floating-ui/react';\n\nimport { useBindReactPropsToElement } from '../hooks/use-bind-react-props-to-element';\nimport { useFloatingOnElement } from '../hooks/use-floating-on-element';\n\nexport const CANVAS_WRAPPER_ID = 'elementor-preview-responsive-wrapper';\n\ntype Props = {\n\telement: HTMLElement;\n\tisSelected: boolean;\n};\n\nconst OverlayBox = styled( Box, { shouldForwardProp: ( prop ) => prop !== 'isSelected' } )<\n\tPick< Props, 'isSelected' >\n>( ( { theme, isSelected } ) => ( {\n\toutline: `${ isSelected ? '2px' : '1px' } solid ${ theme.palette.primary.light }`,\n\toutlineOffset: isSelected ? '-2px' : '-1px',\n\tpointerEvents: 'none',\n} ) );\n\nexport function ElementOverlay( { element, isSelected }: Props ) {\n\tconst { context, floating, isVisible } = useFloatingOnElement( { element, isSelected } );\n\tconst { getFloatingProps, getReferenceProps } = useInteractions( [ useHover( context ) ] );\n\n\tuseBindReactPropsToElement( element, getReferenceProps );\n\n\treturn (\n\t\tisVisible && (\n\t\t\t<FloatingPortal id={ CANVAS_WRAPPER_ID }>\n\t\t\t\t<OverlayBox\n\t\t\t\t\tref={ floating.setRef }\n\t\t\t\t\tisSelected={ isSelected }\n\t\t\t\t\tstyle={ floating.styles }\n\t\t\t\t\tdata-element-overlay={ element.dataset.id }\n\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t{ ...getFloatingProps() }\n\t\t\t\t/>\n\t\t\t</FloatingPortal>\n\t\t)\n\t);\n}\n","import { useEffect } from 'react';\n\ntype Props = Record< string, unknown >;\n\nexport function useBindReactPropsToElement( element: HTMLElement, getProps: () => Props ) {\n\tuseEffect( () => {\n\t\tconst el = element;\n\n\t\tconst { events, attrs } = groupProps( getProps() );\n\n\t\tevents.forEach( ( [ eventName, listener ] ) => el.addEventListener( eventName, listener ) );\n\t\tattrs.forEach( ( [ attrName, attrValue ] ) => el.setAttribute( attrName, attrValue ) );\n\n\t\treturn () => {\n\t\t\tevents.forEach( ( [ eventName, listener ] ) => el.removeEventListener( eventName, listener ) );\n\t\t\tattrs.forEach( ( [ attrName ] ) => el.removeAttribute( attrName ) );\n\t\t};\n\t}, [ getProps, element ] );\n}\n\ntype GroupedProps = {\n\tevents: Array< [ string, () => void ] >;\n\tattrs: Array< [ string, string ] >;\n};\n\nfunction groupProps( props: Props ) {\n\tconst eventRegex = /^on(?=[A-Z])/;\n\n\treturn Object.entries( props ).reduce< GroupedProps >(\n\t\t( acc, [ propName, propValue ] ) => {\n\t\t\tif ( ! eventRegex.test( propName ) ) {\n\t\t\t\tacc.attrs.push( [ propName, propValue as string ] );\n\n\t\t\t\treturn acc;\n\t\t\t}\n\n\t\t\tconst eventName = propName.replace( eventRegex, '' ).toLowerCase();\n\t\t\tconst listener = propValue as () => void;\n\n\t\t\tacc.events.push( [ eventName, listener ] );\n\n\t\t\treturn acc;\n\t\t},\n\t\t{\n\t\t\tevents: [],\n\t\t\tattrs: [],\n\t\t}\n\t);\n}\n","import { useState } from 'react';\nimport { autoUpdate, offset, size, useFloating } from '@floating-ui/react';\n\ntype Options = {\n\telement: HTMLElement;\n\tisSelected: boolean;\n};\n\nexport function useFloatingOnElement( { element, isSelected }: Options ) {\n\tconst [ isOpen, setIsOpen ] = useState( false );\n\n\tconst { refs, floatingStyles, context } = useFloating( {\n\t\t// Must be controlled for interactions (like hover) to work.\n\t\topen: isOpen || isSelected,\n\t\tonOpenChange: setIsOpen,\n\n\t\t// Add an animation frame to support scroll events (without it the floating element will stay in the same position).\n\t\twhileElementsMounted: ( ...args ) => autoUpdate( ...args, { animationFrame: true } ),\n\n\t\t// The first element in the canvas is `display: contents` so we need to use the first child.\n\t\telements: { reference: element.firstElementChild },\n\n\t\tmiddleware: [\n\t\t\t// Match the floating element's size to the reference element.\n\t\t\tsize( {\n\t\t\t\tapply( { elements, rects } ) {\n\t\t\t\t\tObject.assign( elements.floating.style, {\n\t\t\t\t\t\twidth: `${ rects.reference.width }px`,\n\t\t\t\t\t\theight: `${ rects.reference.height }px`,\n\t\t\t\t\t} );\n\t\t\t\t},\n\t\t\t} ),\n\n\t\t\t// Center the floating element on the reference element.\n\t\t\toffset( ( { rects } ) => -rects.reference.height / 2 - rects.floating.height / 2 ),\n\t\t],\n\t} );\n\n\treturn {\n\t\tisVisible: isOpen || isSelected,\n\t\tcontext,\n\t\tfloating: {\n\t\t\tsetRef: refs.setFloating,\n\t\t\tref: refs.floating,\n\t\t\tstyles: floatingStyles,\n\t\t},\n\t};\n}\n","import { init } from './init';\n\ninit();\n"],"mappings":"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","names":["React","import_react","import_react","useIsPreviewMode","useIsRouteActive"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/init.tsx
|
|
2
|
+
import { injectIntoTop } from "@elementor/editor";
|
|
3
|
+
|
|
4
|
+
// src/components/elements-overlays.tsx
|
|
5
|
+
import * as React2 from "react";
|
|
6
|
+
import { useElementsDomRef, useSelectedElement } from "@elementor/editor-elements";
|
|
7
|
+
import {
|
|
8
|
+
__privateUseIsPreviewMode as useIsPreviewMode,
|
|
9
|
+
__privateUseIsRouteActive as useIsRouteActive
|
|
10
|
+
} from "@elementor/editor-v1-adapters";
|
|
11
|
+
|
|
12
|
+
// src/components/element-overlay.tsx
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
import { Box, styled } from "@elementor/ui";
|
|
15
|
+
import { FloatingPortal, useHover, useInteractions } from "@floating-ui/react";
|
|
16
|
+
|
|
17
|
+
// src/hooks/use-bind-react-props-to-element.ts
|
|
18
|
+
import { useEffect } from "react";
|
|
19
|
+
function useBindReactPropsToElement(element, getProps) {
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const el = element;
|
|
22
|
+
const { events, attrs } = groupProps(getProps());
|
|
23
|
+
events.forEach(([eventName, listener]) => el.addEventListener(eventName, listener));
|
|
24
|
+
attrs.forEach(([attrName, attrValue]) => el.setAttribute(attrName, attrValue));
|
|
25
|
+
return () => {
|
|
26
|
+
events.forEach(([eventName, listener]) => el.removeEventListener(eventName, listener));
|
|
27
|
+
attrs.forEach(([attrName]) => el.removeAttribute(attrName));
|
|
28
|
+
};
|
|
29
|
+
}, [getProps, element]);
|
|
30
|
+
}
|
|
31
|
+
function groupProps(props) {
|
|
32
|
+
const eventRegex = /^on(?=[A-Z])/;
|
|
33
|
+
return Object.entries(props).reduce(
|
|
34
|
+
(acc, [propName, propValue]) => {
|
|
35
|
+
if (!eventRegex.test(propName)) {
|
|
36
|
+
acc.attrs.push([propName, propValue]);
|
|
37
|
+
return acc;
|
|
38
|
+
}
|
|
39
|
+
const eventName = propName.replace(eventRegex, "").toLowerCase();
|
|
40
|
+
const listener = propValue;
|
|
41
|
+
acc.events.push([eventName, listener]);
|
|
42
|
+
return acc;
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
events: [],
|
|
46
|
+
attrs: []
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/hooks/use-floating-on-element.ts
|
|
52
|
+
import { useState } from "react";
|
|
53
|
+
import { autoUpdate, offset, size, useFloating } from "@floating-ui/react";
|
|
54
|
+
function useFloatingOnElement({ element, isSelected }) {
|
|
55
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
56
|
+
const { refs, floatingStyles, context } = useFloating({
|
|
57
|
+
// Must be controlled for interactions (like hover) to work.
|
|
58
|
+
open: isOpen || isSelected,
|
|
59
|
+
onOpenChange: setIsOpen,
|
|
60
|
+
// Add an animation frame to support scroll events (without it the floating element will stay in the same position).
|
|
61
|
+
whileElementsMounted: (...args) => autoUpdate(...args, { animationFrame: true }),
|
|
62
|
+
// The first element in the canvas is `display: contents` so we need to use the first child.
|
|
63
|
+
elements: { reference: element.firstElementChild },
|
|
64
|
+
middleware: [
|
|
65
|
+
// Match the floating element's size to the reference element.
|
|
66
|
+
size({
|
|
67
|
+
apply({ elements, rects }) {
|
|
68
|
+
Object.assign(elements.floating.style, {
|
|
69
|
+
width: `${rects.reference.width}px`,
|
|
70
|
+
height: `${rects.reference.height}px`
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
// Center the floating element on the reference element.
|
|
75
|
+
offset(({ rects }) => -rects.reference.height / 2 - rects.floating.height / 2)
|
|
76
|
+
]
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
isVisible: isOpen || isSelected,
|
|
80
|
+
context,
|
|
81
|
+
floating: {
|
|
82
|
+
setRef: refs.setFloating,
|
|
83
|
+
ref: refs.floating,
|
|
84
|
+
styles: floatingStyles
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/components/element-overlay.tsx
|
|
90
|
+
var CANVAS_WRAPPER_ID = "elementor-preview-responsive-wrapper";
|
|
91
|
+
var OverlayBox = styled(Box, { shouldForwardProp: (prop) => prop !== "isSelected" })(({ theme, isSelected }) => ({
|
|
92
|
+
outline: `${isSelected ? "2px" : "1px"} solid ${theme.palette.primary.light}`,
|
|
93
|
+
outlineOffset: isSelected ? "-2px" : "-1px",
|
|
94
|
+
pointerEvents: "none"
|
|
95
|
+
}));
|
|
96
|
+
function ElementOverlay({ element, isSelected }) {
|
|
97
|
+
const { context, floating, isVisible } = useFloatingOnElement({ element, isSelected });
|
|
98
|
+
const { getFloatingProps, getReferenceProps } = useInteractions([useHover(context)]);
|
|
99
|
+
useBindReactPropsToElement(element, getReferenceProps);
|
|
100
|
+
return isVisible && /* @__PURE__ */ React.createElement(FloatingPortal, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React.createElement(
|
|
101
|
+
OverlayBox,
|
|
102
|
+
{
|
|
103
|
+
ref: floating.setRef,
|
|
104
|
+
isSelected,
|
|
105
|
+
style: floating.styles,
|
|
106
|
+
"data-element-overlay": element.dataset.id,
|
|
107
|
+
role: "presentation",
|
|
108
|
+
...getFloatingProps()
|
|
109
|
+
}
|
|
110
|
+
));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/components/elements-overlays.tsx
|
|
114
|
+
function ElementsOverlays() {
|
|
115
|
+
const selected = useSelectedElement();
|
|
116
|
+
const domElements = useElementsDomRef();
|
|
117
|
+
const isPreviewMode = useIsPreviewMode();
|
|
118
|
+
const isKitRouteActive = useIsRouteActive("panel/global");
|
|
119
|
+
const isActive = !isPreviewMode && !isKitRouteActive;
|
|
120
|
+
return isActive && domElements.map((el) => /* @__PURE__ */ React2.createElement(
|
|
121
|
+
ElementOverlay,
|
|
122
|
+
{
|
|
123
|
+
element: el,
|
|
124
|
+
key: el.dataset.id,
|
|
125
|
+
isSelected: selected.element?.id === el.dataset.id
|
|
126
|
+
}
|
|
127
|
+
));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/init.tsx
|
|
131
|
+
function init() {
|
|
132
|
+
injectIntoTop({
|
|
133
|
+
id: "elements-overlays",
|
|
134
|
+
component: ElementsOverlays
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/index.ts
|
|
139
|
+
init();
|
|
140
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init.tsx","../src/components/elements-overlays.tsx","../src/components/element-overlay.tsx","../src/hooks/use-bind-react-props-to-element.ts","../src/hooks/use-floating-on-element.ts","../src/index.ts"],"sourcesContent":["import { injectIntoTop } from '@elementor/editor';\n\nimport { ElementsOverlays } from './components/elements-overlays';\n\nexport function init() {\n\tinjectIntoTop( {\n\t\tid: 'elements-overlays',\n\t\tcomponent: ElementsOverlays,\n\t} );\n}\n","import * as React from 'react';\nimport { useElementsDomRef, useSelectedElement } from '@elementor/editor-elements';\nimport {\n\t__privateUseIsPreviewMode as useIsPreviewMode,\n\t__privateUseIsRouteActive as useIsRouteActive,\n} from '@elementor/editor-v1-adapters';\n\nimport { ElementOverlay } from './element-overlay';\n\nexport function ElementsOverlays() {\n\tconst selected = useSelectedElement();\n\tconst domElements = useElementsDomRef();\n\n\tconst isPreviewMode = useIsPreviewMode();\n\tconst isKitRouteActive = useIsRouteActive( 'panel/global' );\n\n\tconst isActive = ! isPreviewMode && ! isKitRouteActive;\n\n\treturn (\n\t\tisActive &&\n\t\tdomElements.map( ( el ) => (\n\t\t\t<ElementOverlay\n\t\t\t\telement={ el }\n\t\t\t\tkey={ el.dataset.id }\n\t\t\t\tisSelected={ selected.element?.id === el.dataset.id }\n\t\t\t/>\n\t\t) )\n\t);\n}\n","import * as React from 'react';\nimport { Box, styled } from '@elementor/ui';\nimport { FloatingPortal, useHover, useInteractions } from '@floating-ui/react';\n\nimport { useBindReactPropsToElement } from '../hooks/use-bind-react-props-to-element';\nimport { useFloatingOnElement } from '../hooks/use-floating-on-element';\n\nexport const CANVAS_WRAPPER_ID = 'elementor-preview-responsive-wrapper';\n\ntype Props = {\n\telement: HTMLElement;\n\tisSelected: boolean;\n};\n\nconst OverlayBox = styled( Box, { shouldForwardProp: ( prop ) => prop !== 'isSelected' } )<\n\tPick< Props, 'isSelected' >\n>( ( { theme, isSelected } ) => ( {\n\toutline: `${ isSelected ? '2px' : '1px' } solid ${ theme.palette.primary.light }`,\n\toutlineOffset: isSelected ? '-2px' : '-1px',\n\tpointerEvents: 'none',\n} ) );\n\nexport function ElementOverlay( { element, isSelected }: Props ) {\n\tconst { context, floating, isVisible } = useFloatingOnElement( { element, isSelected } );\n\tconst { getFloatingProps, getReferenceProps } = useInteractions( [ useHover( context ) ] );\n\n\tuseBindReactPropsToElement( element, getReferenceProps );\n\n\treturn (\n\t\tisVisible && (\n\t\t\t<FloatingPortal id={ CANVAS_WRAPPER_ID }>\n\t\t\t\t<OverlayBox\n\t\t\t\t\tref={ floating.setRef }\n\t\t\t\t\tisSelected={ isSelected }\n\t\t\t\t\tstyle={ floating.styles }\n\t\t\t\t\tdata-element-overlay={ element.dataset.id }\n\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t{ ...getFloatingProps() }\n\t\t\t\t/>\n\t\t\t</FloatingPortal>\n\t\t)\n\t);\n}\n","import { useEffect } from 'react';\n\ntype Props = Record< string, unknown >;\n\nexport function useBindReactPropsToElement( element: HTMLElement, getProps: () => Props ) {\n\tuseEffect( () => {\n\t\tconst el = element;\n\n\t\tconst { events, attrs } = groupProps( getProps() );\n\n\t\tevents.forEach( ( [ eventName, listener ] ) => el.addEventListener( eventName, listener ) );\n\t\tattrs.forEach( ( [ attrName, attrValue ] ) => el.setAttribute( attrName, attrValue ) );\n\n\t\treturn () => {\n\t\t\tevents.forEach( ( [ eventName, listener ] ) => el.removeEventListener( eventName, listener ) );\n\t\t\tattrs.forEach( ( [ attrName ] ) => el.removeAttribute( attrName ) );\n\t\t};\n\t}, [ getProps, element ] );\n}\n\ntype GroupedProps = {\n\tevents: Array< [ string, () => void ] >;\n\tattrs: Array< [ string, string ] >;\n};\n\nfunction groupProps( props: Props ) {\n\tconst eventRegex = /^on(?=[A-Z])/;\n\n\treturn Object.entries( props ).reduce< GroupedProps >(\n\t\t( acc, [ propName, propValue ] ) => {\n\t\t\tif ( ! eventRegex.test( propName ) ) {\n\t\t\t\tacc.attrs.push( [ propName, propValue as string ] );\n\n\t\t\t\treturn acc;\n\t\t\t}\n\n\t\t\tconst eventName = propName.replace( eventRegex, '' ).toLowerCase();\n\t\t\tconst listener = propValue as () => void;\n\n\t\t\tacc.events.push( [ eventName, listener ] );\n\n\t\t\treturn acc;\n\t\t},\n\t\t{\n\t\t\tevents: [],\n\t\t\tattrs: [],\n\t\t}\n\t);\n}\n","import { useState } from 'react';\nimport { autoUpdate, offset, size, useFloating } from '@floating-ui/react';\n\ntype Options = {\n\telement: HTMLElement;\n\tisSelected: boolean;\n};\n\nexport function useFloatingOnElement( { element, isSelected }: Options ) {\n\tconst [ isOpen, setIsOpen ] = useState( false );\n\n\tconst { refs, floatingStyles, context } = useFloating( {\n\t\t// Must be controlled for interactions (like hover) to work.\n\t\topen: isOpen || isSelected,\n\t\tonOpenChange: setIsOpen,\n\n\t\t// Add an animation frame to support scroll events (without it the floating element will stay in the same position).\n\t\twhileElementsMounted: ( ...args ) => autoUpdate( ...args, { animationFrame: true } ),\n\n\t\t// The first element in the canvas is `display: contents` so we need to use the first child.\n\t\telements: { reference: element.firstElementChild },\n\n\t\tmiddleware: [\n\t\t\t// Match the floating element's size to the reference element.\n\t\t\tsize( {\n\t\t\t\tapply( { elements, rects } ) {\n\t\t\t\t\tObject.assign( elements.floating.style, {\n\t\t\t\t\t\twidth: `${ rects.reference.width }px`,\n\t\t\t\t\t\theight: `${ rects.reference.height }px`,\n\t\t\t\t\t} );\n\t\t\t\t},\n\t\t\t} ),\n\n\t\t\t// Center the floating element on the reference element.\n\t\t\toffset( ( { rects } ) => -rects.reference.height / 2 - rects.floating.height / 2 ),\n\t\t],\n\t} );\n\n\treturn {\n\t\tisVisible: isOpen || isSelected,\n\t\tcontext,\n\t\tfloating: {\n\t\t\tsetRef: refs.setFloating,\n\t\t\tref: refs.floating,\n\t\t\tstyles: floatingStyles,\n\t\t},\n\t};\n}\n","import { init } from './init';\n\ninit();\n"],"mappings":"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","names":["React"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elementor/editor-canvas",
|
|
3
|
+
"description": "Elementor Editor Canvas",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"private": false,
|
|
6
|
+
"author": "Elementor Team",
|
|
7
|
+
"homepage": "https://elementor.com/",
|
|
8
|
+
"license": "GPL-3.0-or-later",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"module": "dist/index.mjs",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.mjs",
|
|
16
|
+
"require": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/elementor/elementor-packages.git",
|
|
23
|
+
"directory": "packages/core/editor-canvas"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/elementor/elementor-packages/issues"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup --config=../../tsup.build.ts",
|
|
33
|
+
"dev": "tsup --config=../../tsup.dev.ts"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": "^18.3.1"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@elementor/editor": "^0.17.0",
|
|
40
|
+
"@elementor/ui": "^1.21.9",
|
|
41
|
+
"@floating-ui/react": "^0.26.28",
|
|
42
|
+
"@elementor/editor-elements": "^0.3.0",
|
|
43
|
+
"@elementor/editor-v1-adapters": "^0.8.4"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createDOMElement, createMockElementType } from 'test-utils';
|
|
3
|
+
import { useElementsDomRef, useSelectedElement } from '@elementor/editor-elements';
|
|
4
|
+
import {
|
|
5
|
+
__privateUseIsPreviewMode as useIsPreviewMode,
|
|
6
|
+
__privateUseIsRouteActive as useIsRouteActive,
|
|
7
|
+
} from '@elementor/editor-v1-adapters';
|
|
8
|
+
import { render, screen } from '@testing-library/react';
|
|
9
|
+
|
|
10
|
+
import { CANVAS_WRAPPER_ID } from '../element-overlay';
|
|
11
|
+
import { ElementsOverlays } from '../elements-overlays';
|
|
12
|
+
|
|
13
|
+
jest.mock( '@elementor/editor-elements' );
|
|
14
|
+
jest.mock( '@elementor/editor-v1-adapters' );
|
|
15
|
+
|
|
16
|
+
describe( '<ElementsOverlays />', () => {
|
|
17
|
+
beforeEach( () => {
|
|
18
|
+
const atomicElements = [
|
|
19
|
+
createDOMElement( {
|
|
20
|
+
tag: 'div',
|
|
21
|
+
attrs: {
|
|
22
|
+
'data-id': '10',
|
|
23
|
+
'data-atomic': '',
|
|
24
|
+
},
|
|
25
|
+
} ),
|
|
26
|
+
createDOMElement( {
|
|
27
|
+
tag: 'span',
|
|
28
|
+
attrs: {
|
|
29
|
+
'data-id': '20',
|
|
30
|
+
'data-atomic': '',
|
|
31
|
+
},
|
|
32
|
+
} ),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
window.document.body.appendChild(
|
|
36
|
+
createDOMElement( {
|
|
37
|
+
tag: 'div',
|
|
38
|
+
attrs: {
|
|
39
|
+
'data-testid': CANVAS_WRAPPER_ID,
|
|
40
|
+
id: CANVAS_WRAPPER_ID,
|
|
41
|
+
},
|
|
42
|
+
children: [
|
|
43
|
+
createDOMElement( {
|
|
44
|
+
tag: 'iframe',
|
|
45
|
+
children: atomicElements,
|
|
46
|
+
} ),
|
|
47
|
+
],
|
|
48
|
+
} )
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
jest.mocked( useIsPreviewMode ).mockReturnValue( false );
|
|
52
|
+
jest.mocked( useIsRouteActive ).mockReturnValue( false );
|
|
53
|
+
|
|
54
|
+
jest.mocked( useElementsDomRef ).mockImplementation( () => atomicElements );
|
|
55
|
+
jest.mocked( useSelectedElement ).mockReturnValue( { element: null, elementType: null } );
|
|
56
|
+
} );
|
|
57
|
+
|
|
58
|
+
afterEach( () => {
|
|
59
|
+
window.document.body.innerHTML = '';
|
|
60
|
+
} );
|
|
61
|
+
|
|
62
|
+
it( 'should render an overlay on atomic element when the element is selected', () => {
|
|
63
|
+
// Arrange.
|
|
64
|
+
jest.mocked( useSelectedElement ).mockReturnValue( {
|
|
65
|
+
element: { id: '20', type: 'widget' },
|
|
66
|
+
elementType: createMockElementType(),
|
|
67
|
+
} );
|
|
68
|
+
|
|
69
|
+
// Act.
|
|
70
|
+
render( <ElementsOverlays /> );
|
|
71
|
+
|
|
72
|
+
// Assert.
|
|
73
|
+
const overlay = screen.getByRole( 'presentation' );
|
|
74
|
+
|
|
75
|
+
expect( overlay ).toHaveAttribute( 'data-element-overlay', '20' );
|
|
76
|
+
expect( screen.getByTestId( CANVAS_WRAPPER_ID ) ).toContainElement( overlay );
|
|
77
|
+
} );
|
|
78
|
+
|
|
79
|
+
it.each( [
|
|
80
|
+
{
|
|
81
|
+
message: 'preview mode is active',
|
|
82
|
+
payload: {
|
|
83
|
+
isPreviewMode: true,
|
|
84
|
+
isKitRouteActive: false,
|
|
85
|
+
selected: { id: '20', type: 'widget' },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
message: 'kit route is active',
|
|
90
|
+
payload: {
|
|
91
|
+
isPreviewMode: false,
|
|
92
|
+
isKitRouteActive: true,
|
|
93
|
+
selected: { id: '20', type: 'widget' },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
message: 'nothing selected or hovered',
|
|
98
|
+
payload: {
|
|
99
|
+
isPreviewMode: false,
|
|
100
|
+
isKitRouteActive: false,
|
|
101
|
+
selected: null,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
] )( 'should not render overlays if $message', ( { payload } ) => {
|
|
105
|
+
// Arrange.
|
|
106
|
+
jest.mocked( useIsPreviewMode ).mockReturnValue( payload.isPreviewMode );
|
|
107
|
+
jest.mocked( useIsRouteActive ).mockReturnValue( payload.isKitRouteActive );
|
|
108
|
+
|
|
109
|
+
jest.mocked( useSelectedElement ).mockReturnValue(
|
|
110
|
+
payload.selected
|
|
111
|
+
? {
|
|
112
|
+
element: payload.selected,
|
|
113
|
+
elementType: createMockElementType(),
|
|
114
|
+
}
|
|
115
|
+
: {
|
|
116
|
+
element: null,
|
|
117
|
+
elementType: null,
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Act.
|
|
122
|
+
render( <ElementsOverlays /> );
|
|
123
|
+
|
|
124
|
+
// Assert.
|
|
125
|
+
expect( screen.queryByRole( 'presentation' ) ).not.toBeInTheDocument();
|
|
126
|
+
} );
|
|
127
|
+
} );
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, styled } from '@elementor/ui';
|
|
3
|
+
import { FloatingPortal, useHover, useInteractions } from '@floating-ui/react';
|
|
4
|
+
|
|
5
|
+
import { useBindReactPropsToElement } from '../hooks/use-bind-react-props-to-element';
|
|
6
|
+
import { useFloatingOnElement } from '../hooks/use-floating-on-element';
|
|
7
|
+
|
|
8
|
+
export const CANVAS_WRAPPER_ID = 'elementor-preview-responsive-wrapper';
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
element: HTMLElement;
|
|
12
|
+
isSelected: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const OverlayBox = styled( Box, { shouldForwardProp: ( prop ) => prop !== 'isSelected' } )<
|
|
16
|
+
Pick< Props, 'isSelected' >
|
|
17
|
+
>( ( { theme, isSelected } ) => ( {
|
|
18
|
+
outline: `${ isSelected ? '2px' : '1px' } solid ${ theme.palette.primary.light }`,
|
|
19
|
+
outlineOffset: isSelected ? '-2px' : '-1px',
|
|
20
|
+
pointerEvents: 'none',
|
|
21
|
+
} ) );
|
|
22
|
+
|
|
23
|
+
export function ElementOverlay( { element, isSelected }: Props ) {
|
|
24
|
+
const { context, floating, isVisible } = useFloatingOnElement( { element, isSelected } );
|
|
25
|
+
const { getFloatingProps, getReferenceProps } = useInteractions( [ useHover( context ) ] );
|
|
26
|
+
|
|
27
|
+
useBindReactPropsToElement( element, getReferenceProps );
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
isVisible && (
|
|
31
|
+
<FloatingPortal id={ CANVAS_WRAPPER_ID }>
|
|
32
|
+
<OverlayBox
|
|
33
|
+
ref={ floating.setRef }
|
|
34
|
+
isSelected={ isSelected }
|
|
35
|
+
style={ floating.styles }
|
|
36
|
+
data-element-overlay={ element.dataset.id }
|
|
37
|
+
role="presentation"
|
|
38
|
+
{ ...getFloatingProps() }
|
|
39
|
+
/>
|
|
40
|
+
</FloatingPortal>
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useElementsDomRef, useSelectedElement } from '@elementor/editor-elements';
|
|
3
|
+
import {
|
|
4
|
+
__privateUseIsPreviewMode as useIsPreviewMode,
|
|
5
|
+
__privateUseIsRouteActive as useIsRouteActive,
|
|
6
|
+
} from '@elementor/editor-v1-adapters';
|
|
7
|
+
|
|
8
|
+
import { ElementOverlay } from './element-overlay';
|
|
9
|
+
|
|
10
|
+
export function ElementsOverlays() {
|
|
11
|
+
const selected = useSelectedElement();
|
|
12
|
+
const domElements = useElementsDomRef();
|
|
13
|
+
|
|
14
|
+
const isPreviewMode = useIsPreviewMode();
|
|
15
|
+
const isKitRouteActive = useIsRouteActive( 'panel/global' );
|
|
16
|
+
|
|
17
|
+
const isActive = ! isPreviewMode && ! isKitRouteActive;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
isActive &&
|
|
21
|
+
domElements.map( ( el ) => (
|
|
22
|
+
<ElementOverlay
|
|
23
|
+
element={ el }
|
|
24
|
+
key={ el.dataset.id }
|
|
25
|
+
isSelected={ selected.element?.id === el.dataset.id }
|
|
26
|
+
/>
|
|
27
|
+
) )
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
type Props = Record< string, unknown >;
|
|
4
|
+
|
|
5
|
+
export function useBindReactPropsToElement( element: HTMLElement, getProps: () => Props ) {
|
|
6
|
+
useEffect( () => {
|
|
7
|
+
const el = element;
|
|
8
|
+
|
|
9
|
+
const { events, attrs } = groupProps( getProps() );
|
|
10
|
+
|
|
11
|
+
events.forEach( ( [ eventName, listener ] ) => el.addEventListener( eventName, listener ) );
|
|
12
|
+
attrs.forEach( ( [ attrName, attrValue ] ) => el.setAttribute( attrName, attrValue ) );
|
|
13
|
+
|
|
14
|
+
return () => {
|
|
15
|
+
events.forEach( ( [ eventName, listener ] ) => el.removeEventListener( eventName, listener ) );
|
|
16
|
+
attrs.forEach( ( [ attrName ] ) => el.removeAttribute( attrName ) );
|
|
17
|
+
};
|
|
18
|
+
}, [ getProps, element ] );
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type GroupedProps = {
|
|
22
|
+
events: Array< [ string, () => void ] >;
|
|
23
|
+
attrs: Array< [ string, string ] >;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function groupProps( props: Props ) {
|
|
27
|
+
const eventRegex = /^on(?=[A-Z])/;
|
|
28
|
+
|
|
29
|
+
return Object.entries( props ).reduce< GroupedProps >(
|
|
30
|
+
( acc, [ propName, propValue ] ) => {
|
|
31
|
+
if ( ! eventRegex.test( propName ) ) {
|
|
32
|
+
acc.attrs.push( [ propName, propValue as string ] );
|
|
33
|
+
|
|
34
|
+
return acc;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const eventName = propName.replace( eventRegex, '' ).toLowerCase();
|
|
38
|
+
const listener = propValue as () => void;
|
|
39
|
+
|
|
40
|
+
acc.events.push( [ eventName, listener ] );
|
|
41
|
+
|
|
42
|
+
return acc;
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
events: [],
|
|
46
|
+
attrs: [],
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { autoUpdate, offset, size, useFloating } from '@floating-ui/react';
|
|
3
|
+
|
|
4
|
+
type Options = {
|
|
5
|
+
element: HTMLElement;
|
|
6
|
+
isSelected: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function useFloatingOnElement( { element, isSelected }: Options ) {
|
|
10
|
+
const [ isOpen, setIsOpen ] = useState( false );
|
|
11
|
+
|
|
12
|
+
const { refs, floatingStyles, context } = useFloating( {
|
|
13
|
+
// Must be controlled for interactions (like hover) to work.
|
|
14
|
+
open: isOpen || isSelected,
|
|
15
|
+
onOpenChange: setIsOpen,
|
|
16
|
+
|
|
17
|
+
// Add an animation frame to support scroll events (without it the floating element will stay in the same position).
|
|
18
|
+
whileElementsMounted: ( ...args ) => autoUpdate( ...args, { animationFrame: true } ),
|
|
19
|
+
|
|
20
|
+
// The first element in the canvas is `display: contents` so we need to use the first child.
|
|
21
|
+
elements: { reference: element.firstElementChild },
|
|
22
|
+
|
|
23
|
+
middleware: [
|
|
24
|
+
// Match the floating element's size to the reference element.
|
|
25
|
+
size( {
|
|
26
|
+
apply( { elements, rects } ) {
|
|
27
|
+
Object.assign( elements.floating.style, {
|
|
28
|
+
width: `${ rects.reference.width }px`,
|
|
29
|
+
height: `${ rects.reference.height }px`,
|
|
30
|
+
} );
|
|
31
|
+
},
|
|
32
|
+
} ),
|
|
33
|
+
|
|
34
|
+
// Center the floating element on the reference element.
|
|
35
|
+
offset( ( { rects } ) => -rects.reference.height / 2 - rects.floating.height / 2 ),
|
|
36
|
+
],
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
isVisible: isOpen || isSelected,
|
|
41
|
+
context,
|
|
42
|
+
floating: {
|
|
43
|
+
setRef: refs.setFloating,
|
|
44
|
+
ref: refs.floating,
|
|
45
|
+
styles: floatingStyles,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
package/src/index.ts
ADDED
package/src/init.tsx
ADDED