sanity-plugin-hotspot-array 0.0.5 → 0.0.8
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/README.md +2 -2
- package/lib/Feedback.js.map +1 -1
- package/lib/HotspotArray.js +55 -27
- package/lib/HotspotArray.js.map +1 -1
- package/lib/Spot.js +104 -94
- package/lib/Spot.js.map +1 -1
- package/lib/useUnsetInputComponent.js.map +1 -1
- package/package.json +15 -14
- package/src/HotspotArray.tsx +73 -32
- package/src/Spot.tsx +134 -68
- package/src/useUnsetInputComponent.ts +13 -13
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
A configurable Custom Input for Arrays that will add and update items by clicking on an Image
|
|
4
4
|
|
|
5
|
+
<img src="https://user-images.githubusercontent.com/209129/174171697-57319ebc-03a7-4d82-a73e-b6effcb0b3ba.gif" width="600" />
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```
|
|
8
10
|
sanity install hotspot-array
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-

|
|
12
|
-
|
|
13
13
|
## Setup
|
|
14
14
|
|
|
15
15
|
Import the `HotspotArray` component from this package, into your schema. And insert it as the `inputComponent` of an `array` field.
|
package/lib/Feedback.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"Feedback.js","names":["Feedback","children"],"sources":["../src/Feedback.tsx"],"sourcesContent":["import React from 'react'\nimport {Card, Text} from '@sanity/ui'\n\nexport default function Feedback({children}) {\n return (\n <Card padding={4} radius={2} shadow={1} tone=\"caution\">\n <Text>{children}</Text>\n </Card>\n )\n}\n"],"mappings":";;;;;;;AAAA;;AACA;;;;AAEe,SAASA,QAAT,OAA8B;EAAA,IAAXC,QAAW,QAAXA,QAAW;EAC3C,oBACE,6BAAC,QAAD;IAAM,OAAO,EAAE,CAAf;IAAkB,MAAM,EAAE,CAA1B;IAA6B,MAAM,EAAE,CAArC;IAAwC,IAAI,EAAC;EAA7C,gBACE,6BAAC,QAAD,QAAOA,QAAP,CADF,CADF;AAKD"}
|
package/lib/HotspotArray.js
CHANGED
|
@@ -9,52 +9,82 @@ var _client = _interopRequireDefault(require("part:@sanity/base/client"));
|
|
|
9
9
|
|
|
10
10
|
var _formBuilder = require("part:@sanity/form-builder");
|
|
11
11
|
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
var _ui = require("@sanity/ui");
|
|
12
|
+
var _assetUtils = require("@sanity/asset-utils");
|
|
15
13
|
|
|
16
14
|
var _FormBuilderInput = require("@sanity/form-builder/lib/FormBuilderInput");
|
|
17
15
|
|
|
18
|
-
var _content = require("@sanity/util/content");
|
|
19
|
-
|
|
20
16
|
var _PatchEvent = require("@sanity/form-builder/PatchEvent");
|
|
21
17
|
|
|
22
18
|
var _imageUrl = _interopRequireDefault(require("@sanity/image-url"));
|
|
23
19
|
|
|
24
|
-
var
|
|
20
|
+
var _ui = require("@sanity/ui");
|
|
21
|
+
|
|
22
|
+
var _content = require("@sanity/util/content");
|
|
25
23
|
|
|
26
24
|
var _get = _interopRequireDefault(require("lodash/get"));
|
|
27
25
|
|
|
26
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
27
|
+
|
|
28
|
+
var _web = require("@react-hookz/web");
|
|
29
|
+
|
|
30
|
+
var _Feedback = _interopRequireDefault(require("./Feedback"));
|
|
31
|
+
|
|
28
32
|
var _Spot = _interopRequireDefault(require("./Spot"));
|
|
29
33
|
|
|
30
34
|
var _useUnsetInputComponent = require("./useUnsetInputComponent");
|
|
31
35
|
|
|
32
|
-
var
|
|
36
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
37
|
+
|
|
38
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
33
39
|
|
|
34
40
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
35
41
|
|
|
36
|
-
function _extends() { _extends = Object.assign
|
|
42
|
+
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
43
|
+
|
|
44
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
45
|
+
|
|
46
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
47
|
+
|
|
48
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
49
|
+
|
|
50
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
51
|
+
|
|
52
|
+
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
|
|
53
|
+
|
|
54
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
37
55
|
|
|
38
56
|
var imageStyle = {
|
|
39
57
|
width: "100%",
|
|
40
58
|
height: "auto"
|
|
41
59
|
};
|
|
60
|
+
var VALID_ROOT_PATHS = ['document', 'parent'];
|
|
42
61
|
|
|
43
62
|
var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
44
|
-
var _type$options, _type$options2;
|
|
63
|
+
var _type$options, _type$options2, _type$options3;
|
|
45
64
|
|
|
46
65
|
var type = props.type,
|
|
47
66
|
value = props.value,
|
|
48
67
|
onChange = props.onChange,
|
|
49
|
-
|
|
68
|
+
document = props.document;
|
|
50
69
|
|
|
51
70
|
var _ref = type !== null && type !== void 0 ? type : {},
|
|
52
71
|
options = _ref.options; // Attempt prevention of infinite loop in <FormBuilderInput />
|
|
53
72
|
// Re-renders can still occur if this Component is used again in a nested field
|
|
54
73
|
|
|
55
74
|
|
|
56
|
-
var typeWithoutInputComponent = (0, _useUnsetInputComponent.useUnsetInputComponent)(type, type === null || type === void 0 ? void 0 : type.inputComponent);
|
|
57
|
-
|
|
75
|
+
var typeWithoutInputComponent = (0, _useUnsetInputComponent.useUnsetInputComponent)(type, type === null || type === void 0 ? void 0 : type.inputComponent);
|
|
76
|
+
var imageHotspotPathRoot = VALID_ROOT_PATHS.includes(options === null || options === void 0 ? void 0 : options.imageHotspotPathRoot) ? props[options.imageHotspotPathRoot] : document;
|
|
77
|
+
/**
|
|
78
|
+
* Finding the image from the imageHotspotPathRoot (defaults to document),
|
|
79
|
+
* using the path from the hotspot's `options` field
|
|
80
|
+
*
|
|
81
|
+
* when changes in imageHotspotPathRoot (e.g. document) occur,
|
|
82
|
+
* check if there are any changes to the hotspotImage and update the reference
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
var hotspotImage = _react.default.useMemo(() => {
|
|
86
|
+
return (0, _get.default)(imageHotspotPathRoot, options === null || options === void 0 ? void 0 : options.hotspotImagePath);
|
|
87
|
+
}, [imageHotspotPathRoot]);
|
|
58
88
|
|
|
59
89
|
var displayImage = _react.default.useMemo(() => {
|
|
60
90
|
var _hotspotImage$asset;
|
|
@@ -63,13 +93,11 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
|
63
93
|
|
|
64
94
|
var urlFor = source => builder.image(source);
|
|
65
95
|
|
|
66
|
-
var hotspotImage = (0, _get.default)(sanityDocument, options === null || options === void 0 ? void 0 : options.hotspotImagePath);
|
|
67
|
-
|
|
68
96
|
if (hotspotImage !== null && hotspotImage !== void 0 && (_hotspotImage$asset = hotspotImage.asset) !== null && _hotspotImage$asset !== void 0 && _hotspotImage$asset._ref) {
|
|
69
97
|
var _getImageDimensions = (0, _assetUtils.getImageDimensions)(hotspotImage.asset._ref),
|
|
70
98
|
aspectRatio = _getImageDimensions.aspectRatio;
|
|
71
99
|
|
|
72
|
-
var width =
|
|
100
|
+
var width = 1200;
|
|
73
101
|
var height = Math.round(width / aspectRatio);
|
|
74
102
|
var url = urlFor(hotspotImage).width(width).url();
|
|
75
103
|
return {
|
|
@@ -80,7 +108,7 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
|
80
108
|
}
|
|
81
109
|
|
|
82
110
|
return null;
|
|
83
|
-
}, [
|
|
111
|
+
}, [hotspotImage]);
|
|
84
112
|
|
|
85
113
|
var handleHotspotImageClick = _react.default.useCallback(event => {
|
|
86
114
|
var nativeEvent = event.nativeEvent; // Calculate the x/y percentage of the click position
|
|
@@ -103,14 +131,6 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
|
103
131
|
}, []);
|
|
104
132
|
|
|
105
133
|
var handleHotspotMove = _react.default.useCallback((key, x, y) => {
|
|
106
|
-
if (!Number(x) || !Number(y)) {
|
|
107
|
-
console.warn("Missing or non-number X or Y", {
|
|
108
|
-
x,
|
|
109
|
-
y
|
|
110
|
-
});
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
134
|
onChange(_PatchEvent.PatchEvent.from( // Set the `x` value of this array key item
|
|
115
135
|
(0, _PatchEvent.set)(x, [{
|
|
116
136
|
_key: key
|
|
@@ -122,16 +142,24 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
|
122
142
|
|
|
123
143
|
var hotspotImageRef = _react.default.useRef(null);
|
|
124
144
|
|
|
145
|
+
var _useState = (0, _react.useState)(),
|
|
146
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
147
|
+
imageRect = _useState2[0],
|
|
148
|
+
setImageRect = _useState2[1];
|
|
149
|
+
|
|
150
|
+
var updateImageRectCallback = (0, _web.useDebouncedCallback)(e => setImageRect(e.contentRect), [setImageRect], 200);
|
|
151
|
+
(0, _web.useResizeObserver)(hotspotImageRef, updateImageRectCallback);
|
|
125
152
|
return /*#__PURE__*/_react.default.createElement(_ui.Stack, {
|
|
126
153
|
space: [2, 2, 3]
|
|
127
154
|
}, displayImage !== null && displayImage !== void 0 && displayImage.url ? /*#__PURE__*/_react.default.createElement("div", {
|
|
128
155
|
style: {
|
|
129
156
|
position: "relative"
|
|
130
157
|
}
|
|
131
|
-
}, (value === null || value === void 0 ? void 0 : value.length) > 0 && value.map(spot => /*#__PURE__*/_react.default.createElement(_Spot.default, {
|
|
158
|
+
}, imageRect && (value === null || value === void 0 ? void 0 : value.length) > 0 && value.map((spot, index) => /*#__PURE__*/_react.default.createElement(_Spot.default, {
|
|
159
|
+
index: index,
|
|
132
160
|
key: spot._key,
|
|
133
161
|
spot: spot,
|
|
134
|
-
bounds:
|
|
162
|
+
bounds: imageRect,
|
|
135
163
|
update: handleHotspotMove,
|
|
136
164
|
hotspotDescriptionPath: options === null || options === void 0 ? void 0 : options.hotspotDescriptionPath,
|
|
137
165
|
tooltip: options === null || options === void 0 ? void 0 : options.hotspotTooltip
|
|
@@ -149,7 +177,7 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
|
149
177
|
alt: "",
|
|
150
178
|
style: imageStyle,
|
|
151
179
|
onClick: handleHotspotImageClick
|
|
152
|
-
})))) : /*#__PURE__*/_react.default.createElement(_Feedback.default, null, type !== null && type !== void 0 && (_type$options = type.options) !== null && _type$options !== void 0 && _type$options.hotspotImagePath ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "No Hotspot image found at path ", /*#__PURE__*/_react.default.createElement("code", null, type === null || type === void 0 ? void 0 : (_type$options2 = type.options) === null || _type$options2 === void 0 ? void 0 : _type$options2.hotspotImagePath)) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "Define a path in this field using to the image field in this document at
|
|
180
|
+
})))) : /*#__PURE__*/_react.default.createElement(_Feedback.default, null, type !== null && type !== void 0 && (_type$options = type.options) !== null && _type$options !== void 0 && _type$options.hotspotImagePath ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "No Hotspot image found at path ", /*#__PURE__*/_react.default.createElement("code", null, type === null || type === void 0 ? void 0 : (_type$options2 = type.options) === null || _type$options2 === void 0 ? void 0 : _type$options2.hotspotImagePath)) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "Define a path in this field using to the image field in this document at", ' ', /*#__PURE__*/_react.default.createElement("code", null, "options.hotspotImagePath"))), (type === null || type === void 0 ? void 0 : (_type$options3 = type.options) === null || _type$options3 === void 0 ? void 0 : _type$options3.imageHotspotPathRoot) && !VALID_ROOT_PATHS.includes(type.options.imageHotspotPathRoot) && /*#__PURE__*/_react.default.createElement(_Feedback.default, null, "The supplied imageHotspotPathRoot \"", type.options.imageHotspotPathRoot, "\" is not valid, falling back to \"document\". Available values are \"", VALID_ROOT_PATHS.join(', '), "\"."), /*#__PURE__*/_react.default.createElement(_FormBuilderInput.FormBuilderInput, _extends({}, props, {
|
|
153
181
|
type: typeWithoutInputComponent,
|
|
154
182
|
ref: ref
|
|
155
183
|
})));
|
package/lib/HotspotArray.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/HotspotArray.tsx"],"names":["imageStyle","width","height","HotspotArray","React","forwardRef","props","ref","type","value","onChange","sanityDocument","document","options","typeWithoutInputComponent","inputComponent","displayImage","useMemo","builder","sanityClient","dataset","config","urlFor","source","image","hotspotImage","hotspotImagePath","asset","_ref","aspectRatio","Math","round","url","handleHotspotImageClick","useCallback","event","nativeEvent","x","Number","offsetX","srcElement","toFixed","y","offsetY","description","newRow","_key","_type","hotspotDescriptionPath","PatchEvent","from","handleHotspotMove","key","console","warn","hotspotImageRef","useRef","position","length","map","spot","hotspotTooltip"],"mappings":"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","sourcesContent":["/* eslint-disable react/display-name */\n\n// @ts-ignore\nimport sanityClient from 'part:@sanity/base/client'\n// @ts-ignore\nimport {withDocument} from 'part:@sanity/form-builder'\n\nimport React from 'react'\nimport {Card, Flex, Stack, Text} from '@sanity/ui'\nimport {FormBuilderInput} from '@sanity/form-builder/lib/FormBuilderInput'\nimport {randomKey} from '@sanity/util/content'\nimport {PatchEvent, setIfMissing, set, insert} from '@sanity/form-builder/PatchEvent'\nimport imageUrlBuilder from '@sanity/image-url'\nimport {getImageDimensions} from '@sanity/asset-utils'\nimport get from 'lodash/get'\n\nimport Spot from './Spot'\nimport { useUnsetInputComponent } from './useUnsetInputComponent'\nimport Feedback from './Feedback'\n\nconst imageStyle = {width: `100%`, height: `auto`}\n\nconst HotspotArray = React.forwardRef((props, ref) => {\n const {type, value, onChange, document: sanityDocument} = props\n const {options} = type ?? {}\n\n // Attempt prevention of infinite loop in <FormBuilderInput />\n // Re-renders can still occur if this Component is used again in a nested field\n const typeWithoutInputComponent = useUnsetInputComponent(type, type?.inputComponent)\n\n // Finding the image from the document,\n // using the path from the hotspot's `options` field\n const displayImage = React.useMemo(() => {\n const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)\n const urlFor = (source) => builder.image(source)\n const hotspotImage = get(sanityDocument, options?.hotspotImagePath)\n\n if (hotspotImage?.asset?._ref) {\n const {aspectRatio} = getImageDimensions(hotspotImage.asset._ref)\n const width = 600\n const height = Math.round(width / aspectRatio)\n const url = urlFor(hotspotImage).width(width).url()\n\n return {width, height, url}\n }\n\n return null\n }, [sanityDocument, type])\n\n const handleHotspotImageClick = React.useCallback((event) => {\n const {nativeEvent} = event\n\n // Calculate the x/y percentage of the click position\n const x = Number(((nativeEvent.offsetX * 100) / nativeEvent.srcElement.width).toFixed(2))\n const y = Number(((nativeEvent.offsetY * 100) / nativeEvent.srcElement.height).toFixed(2))\n const description = `New Hotspot at ${x}% x ${y}%`\n\n const newRow = {\n _key: randomKey(12),\n _type: `spot`,\n x,\n y,\n }\n\n if (options?.hotspotDescriptionPath) {\n newRow[options.hotspotDescriptionPath] = description\n }\n\n onChange(PatchEvent.from(setIfMissing([]), insert([newRow], 'after', [-1])))\n }, [])\n\n const handleHotspotMove = React.useCallback(\n (key, x, y) => {\n if (!Number(x) || !Number(y)) {\n console.warn(`Missing or non-number X or Y`, {x, y})\n return\n }\n\n onChange(\n PatchEvent.from(\n // Set the `x` value of this array key item\n set(x, [{_key: key}, 'x']),\n // Set the `y` value of this array key item\n set(y, [{_key: key}, 'y'])\n )\n )\n },\n [value]\n )\n\n const hotspotImageRef = React.useRef(null)\n\n return (\n <Stack space={[2,2,3]}>\n {displayImage?.url ? (\n <div style={{position: `relative`}}>\n {value?.length > 0 &&\n value.map((spot) => (\n <Spot\n key={spot._key}\n spot={spot}\n bounds={hotspotImageRef}\n update={handleHotspotMove}\n hotspotDescriptionPath={options?.hotspotDescriptionPath}\n tooltip={options?.hotspotTooltip}\n />\n ))}\n\n <Card __unstable_checkered shadow={1}>\n <Flex align=\"center\" justify=\"center\">\n <img\n ref={hotspotImageRef}\n src={displayImage.url}\n width={displayImage.width}\n height={displayImage.height}\n alt=\"\"\n style={imageStyle}\n onClick={handleHotspotImageClick}\n />\n </Flex>\n </Card>\n </div>\n ) : (\n <Feedback>\n {type?.options?.hotspotImagePath \n ? <>No Hotspot image found at path <code>{type?.options?.hotspotImagePath}</code></> \n : <>Define a path in this field using to the image field in this document at <code>options.hotspotImagePath</code></>\n }\n </Feedback>\n )}\n <FormBuilderInput {...props} type={typeWithoutInputComponent} ref={ref} />\n </Stack>\n )\n})\n\nexport default withDocument(HotspotArray)\n"],"file":"HotspotArray.js"}
|
|
1
|
+
{"version":3,"file":"HotspotArray.js","names":["imageStyle","width","height","VALID_ROOT_PATHS","HotspotArray","React","forwardRef","props","ref","type","value","onChange","document","options","typeWithoutInputComponent","useUnsetInputComponent","inputComponent","imageHotspotPathRoot","includes","hotspotImage","useMemo","get","hotspotImagePath","displayImage","builder","imageUrlBuilder","sanityClient","dataset","config","urlFor","source","image","asset","_ref","getImageDimensions","aspectRatio","Math","round","url","handleHotspotImageClick","useCallback","event","nativeEvent","x","Number","offsetX","srcElement","toFixed","y","offsetY","description","newRow","_key","randomKey","_type","hotspotDescriptionPath","PatchEvent","from","setIfMissing","insert","handleHotspotMove","key","set","hotspotImageRef","useRef","useState","imageRect","setImageRect","updateImageRectCallback","useDebouncedCallback","e","contentRect","useResizeObserver","position","length","map","spot","index","hotspotTooltip","join","withDocument"],"sources":["../src/HotspotArray.tsx"],"sourcesContent":["/* eslint-disable react/display-name */\n\n// @ts-ignore\nimport sanityClient from 'part:@sanity/base/client'\n// @ts-ignore\nimport {withDocument} from 'part:@sanity/form-builder'\n\nimport {getImageDimensions} from '@sanity/asset-utils'\nimport {FormBuilderInput} from '@sanity/form-builder/lib/FormBuilderInput'\nimport {insert, PatchEvent, set, setIfMissing} from '@sanity/form-builder/PatchEvent'\nimport imageUrlBuilder from '@sanity/image-url'\nimport {Card, Flex, Stack} from '@sanity/ui'\nimport {randomKey} from '@sanity/util/content'\nimport get from 'lodash/get'\nimport React, {useState} from 'react'\n\nimport {IUseResizeObserverCallback, useDebouncedCallback, useResizeObserver} from '@react-hookz/web'\nimport Feedback from './Feedback'\nimport Spot from './Spot'\nimport {useUnsetInputComponent} from './useUnsetInputComponent'\n\nconst imageStyle = {width: `100%`, height: `auto`}\n\nconst VALID_ROOT_PATHS = ['document', 'parent']\n\nexport type FnHotspotMove = (key: string, x: number, y: number) => void\n\nexport type TSpot = {\n _key: string\n _type: `spot`\n x: number\n y: number\n} & {[key: string]: unknown}\n\nconst HotspotArray = React.forwardRef((props: any, ref) => {\n const {type, value, onChange, document} = props\n const {options} = type ?? {}\n\n // Attempt prevention of infinite loop in <FormBuilderInput />\n // Re-renders can still occur if this Component is used again in a nested field\n const typeWithoutInputComponent = useUnsetInputComponent(type, type?.inputComponent)\n const imageHotspotPathRoot = VALID_ROOT_PATHS.includes(options?.imageHotspotPathRoot)\n ? props[options.imageHotspotPathRoot]\n : document\n\n /**\n * Finding the image from the imageHotspotPathRoot (defaults to document),\n * using the path from the hotspot's `options` field\n *\n * when changes in imageHotspotPathRoot (e.g. document) occur,\n * check if there are any changes to the hotspotImage and update the reference\n */\n const hotspotImage = React.useMemo(() => {\n return get(imageHotspotPathRoot, options?.hotspotImagePath)\n }, [imageHotspotPathRoot])\n\n const displayImage = React.useMemo(() => {\n const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)\n const urlFor = (source) => builder.image(source)\n\n if (hotspotImage?.asset?._ref) {\n const {aspectRatio} = getImageDimensions(hotspotImage.asset._ref)\n const width = 1200\n const height = Math.round(width / aspectRatio)\n const url = urlFor(hotspotImage).width(width).url()\n\n return {width, height, url}\n }\n\n return null\n }, [hotspotImage])\n\n const handleHotspotImageClick = React.useCallback((event) => {\n const {nativeEvent} = event\n\n // Calculate the x/y percentage of the click position\n const x = Number(((nativeEvent.offsetX * 100) / nativeEvent.srcElement.width).toFixed(2))\n const y = Number(((nativeEvent.offsetY * 100) / nativeEvent.srcElement.height).toFixed(2))\n const description = `New Hotspot at ${x}% x ${y}%`\n\n const newRow: TSpot = {\n _key: randomKey(12),\n _type: `spot`,\n x,\n y,\n }\n\n if (options?.hotspotDescriptionPath) {\n newRow[options.hotspotDescriptionPath] = description\n }\n\n onChange(PatchEvent.from(setIfMissing([]), insert([newRow], 'after', [-1])))\n }, [])\n\n const handleHotspotMove: FnHotspotMove = React.useCallback(\n (key, x, y) => {\n onChange(\n PatchEvent.from(\n // Set the `x` value of this array key item\n set(x, [{_key: key}, 'x']),\n // Set the `y` value of this array key item\n set(y, [{_key: key}, 'y'])\n )\n )\n },\n [value]\n )\n\n const hotspotImageRef = React.useRef<HTMLImageElement | null>(null)\n\n const [imageRect, setImageRect] = useState<DOMRectReadOnly>()\n const updateImageRectCallback = useDebouncedCallback(\n ((e) => setImageRect(e.contentRect)) as IUseResizeObserverCallback,\n [setImageRect],\n 200\n )\n useResizeObserver(hotspotImageRef, updateImageRectCallback)\n\n return (\n <Stack space={[2, 2, 3]}>\n {displayImage?.url ? (\n <div style={{position: `relative`}}>\n {imageRect &&\n value?.length > 0 &&\n value.map((spot, index) => (\n <Spot\n index={index}\n key={spot._key}\n spot={spot}\n bounds={imageRect}\n update={handleHotspotMove}\n hotspotDescriptionPath={options?.hotspotDescriptionPath}\n tooltip={options?.hotspotTooltip}\n />\n ))}\n\n <Card __unstable_checkered shadow={1}>\n <Flex align=\"center\" justify=\"center\">\n <img\n ref={hotspotImageRef}\n src={displayImage.url}\n width={displayImage.width}\n height={displayImage.height}\n alt=\"\"\n style={imageStyle}\n onClick={handleHotspotImageClick}\n />\n </Flex>\n </Card>\n </div>\n ) : (\n <Feedback>\n {type?.options?.hotspotImagePath ? (\n <>\n No Hotspot image found at path <code>{type?.options?.hotspotImagePath}</code>\n </>\n ) : (\n <>\n Define a path in this field using to the image field in this document at{' '}\n <code>options.hotspotImagePath</code>\n </>\n )}\n </Feedback>\n )}\n {type?.options?.imageHotspotPathRoot &&\n !VALID_ROOT_PATHS.includes(type.options.imageHotspotPathRoot) && (\n <Feedback>\n The supplied imageHotspotPathRoot \"{type.options.imageHotspotPathRoot}\" is not valid,\n falling back to \"document\". Available values are \"{VALID_ROOT_PATHS.join(', ')}\".\n </Feedback>\n )}\n <FormBuilderInput {...props} type={typeWithoutInputComponent} ref={ref} />\n </Stack>\n )\n})\n\nexport default withDocument(HotspotArray)\n"],"mappings":"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"}
|
package/lib/Spot.js
CHANGED
|
@@ -5,16 +5,26 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = Spot;
|
|
7
7
|
|
|
8
|
-
var _react = _interopRequireDefault(require("react"));
|
|
9
|
-
|
|
10
8
|
var _ui = require("@sanity/ui");
|
|
11
9
|
|
|
12
10
|
var _framerMotion = require("framer-motion");
|
|
13
11
|
|
|
14
12
|
var _get = _interopRequireDefault(require("lodash/get"));
|
|
15
13
|
|
|
14
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
15
|
+
|
|
16
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
17
|
+
|
|
18
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
19
|
+
|
|
16
20
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
21
|
|
|
22
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
23
|
+
|
|
24
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
25
|
+
|
|
26
|
+
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
27
|
+
|
|
18
28
|
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
19
29
|
|
|
20
30
|
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
@@ -27,110 +37,109 @@ function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Sy
|
|
|
27
37
|
|
|
28
38
|
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
29
39
|
|
|
30
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
31
|
-
|
|
32
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
33
|
-
|
|
34
|
-
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
35
|
-
|
|
36
40
|
var dragStyle = {
|
|
37
|
-
width:
|
|
38
|
-
height:
|
|
39
|
-
position:
|
|
41
|
+
width: '1.4rem',
|
|
42
|
+
height: '1.4rem',
|
|
43
|
+
position: 'absolute',
|
|
44
|
+
boxSizing: 'border-box',
|
|
45
|
+
top: 0,
|
|
46
|
+
left: 0,
|
|
47
|
+
margin: '-0.7rem 0 0 -0.7rem',
|
|
48
|
+
cursor: 'pointer',
|
|
49
|
+
display: 'flex',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
borderRadius: '50%',
|
|
53
|
+
background: '#000',
|
|
54
|
+
color: 'white'
|
|
40
55
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
var dragStyleWhileDrag = {
|
|
57
|
+
background: 'rgba(0, 0, 0, 0.1)',
|
|
58
|
+
border: '1px solid #fff',
|
|
59
|
+
cursor: 'none'
|
|
60
|
+
};
|
|
61
|
+
var dragStyleWhileHover = {
|
|
62
|
+
background: 'rgba(0, 0, 0, 0.1)',
|
|
63
|
+
border: '1px solid #fff'
|
|
64
|
+
};
|
|
65
|
+
var dotStyle = {
|
|
66
|
+
position: 'absolute',
|
|
67
|
+
left: '50%',
|
|
68
|
+
top: '50%',
|
|
69
|
+
height: '0.2rem',
|
|
70
|
+
width: '0.2rem',
|
|
71
|
+
margin: '-0.1rem 0 0 -0.1rem',
|
|
72
|
+
background: '#fff',
|
|
73
|
+
visibility: 'hidden',
|
|
74
|
+
borderRadius: '50%',
|
|
44
75
|
// make sure pointer events only run on the parent
|
|
45
|
-
pointerEvents:
|
|
46
|
-
}
|
|
76
|
+
pointerEvents: 'none'
|
|
77
|
+
};
|
|
78
|
+
var dotStyleWhileActive = {
|
|
79
|
+
visibility: 'visible'
|
|
80
|
+
};
|
|
81
|
+
var labelStyle = {
|
|
82
|
+
color: 'white',
|
|
83
|
+
fontSize: '0.7rem',
|
|
84
|
+
fontWeight: 600,
|
|
85
|
+
lineHeight: '1'
|
|
86
|
+
};
|
|
87
|
+
var labelStyleWhileActive = {
|
|
88
|
+
visibility: 'hidden'
|
|
89
|
+
};
|
|
47
90
|
|
|
48
91
|
var round = num => Math.round(num * 100) / 100;
|
|
49
92
|
|
|
50
93
|
function Spot(_ref) {
|
|
51
94
|
var spot = _ref.spot,
|
|
52
|
-
|
|
53
|
-
bounds = _ref$bounds === void 0 ? undefined : _ref$bounds,
|
|
95
|
+
bounds = _ref.bounds,
|
|
54
96
|
update = _ref.update,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// x/y are stored as % but need to be displayed as px
|
|
60
|
-
var _React$useState = _react.default.useState({
|
|
61
|
-
x: 0,
|
|
62
|
-
y: 0
|
|
63
|
-
}),
|
|
64
|
-
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
65
|
-
_React$useState2$ = _React$useState2[0],
|
|
66
|
-
x = _React$useState2$.x,
|
|
67
|
-
y = _React$useState2$.y,
|
|
68
|
-
setXY = _React$useState2[1];
|
|
69
|
-
|
|
70
|
-
var _React$useState3 = _react.default.useState(bounds !== null && bounds !== void 0 && bounds.current ? bounds.current.getBoundingClientRect() : {
|
|
71
|
-
width: 0,
|
|
72
|
-
height: 0
|
|
73
|
-
}),
|
|
74
|
-
_React$useState4 = _slicedToArray(_React$useState3, 2),
|
|
75
|
-
rect = _React$useState4[0],
|
|
76
|
-
setRect = _React$useState4[1];
|
|
77
|
-
|
|
78
|
-
var _React$useState5 = _react.default.useState(false),
|
|
79
|
-
_React$useState6 = _slicedToArray(_React$useState5, 2),
|
|
80
|
-
isDragging = _React$useState6[0],
|
|
81
|
-
setIsDragging = _React$useState6[1];
|
|
82
|
-
|
|
83
|
-
_react.default.useEffect(() => {
|
|
84
|
-
var _bounds$current;
|
|
97
|
+
hotspotDescriptionPath = _ref.hotspotDescriptionPath,
|
|
98
|
+
tooltip = _ref.tooltip,
|
|
99
|
+
index = _ref.index;
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
setXY({
|
|
91
|
-
x: round(clientRect.width * (spot.x / 100)),
|
|
92
|
-
y: round(clientRect.height * (spot.y / 100))
|
|
93
|
-
});
|
|
101
|
+
var _React$useState = _react.default.useState(false),
|
|
102
|
+
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
103
|
+
isDragging = _React$useState2[0],
|
|
104
|
+
setIsDragging = _React$useState2[1];
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}, [bounds, bounds.current]);
|
|
106
|
+
var _React$useState3 = _react.default.useState(false),
|
|
107
|
+
_React$useState4 = _slicedToArray(_React$useState3, 2),
|
|
108
|
+
isHovering = _React$useState4[0],
|
|
109
|
+
setIsHovering = _React$useState4[1]; // x/y are stored as % but need to be converted to px
|
|
100
110
|
|
|
101
|
-
var handleDragEnd = _react.default.useCallback(event => {
|
|
102
|
-
setIsDragging(false); // I don't know why, but framer-motion doesn't give you the actual transform values
|
|
103
|
-
// So we have to regex the `px` values off the inline styles
|
|
104
111
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
currentY = _event$srcElement$sty2[1];
|
|
112
|
+
var x = (0, _framerMotion.useMotionValue)(round(bounds.width * (spot.x / 100)));
|
|
113
|
+
var y = (0, _framerMotion.useMotionValue)(round(bounds.height * (spot.y / 100)));
|
|
114
|
+
/**
|
|
115
|
+
* update x/y if the bounds change when resizing the window
|
|
116
|
+
*/
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}, event.srcElement);
|
|
117
|
-
}
|
|
118
|
+
(0, _react.useEffect)(() => {
|
|
119
|
+
x.set(round(bounds.width * (spot.x / 100)));
|
|
120
|
+
y.set(round(bounds.height * (spot.y / 100)));
|
|
121
|
+
}, [bounds]);
|
|
118
122
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
rect
|
|
122
|
-
}, bounds === null || bounds === void 0 ? void 0 : bounds.current);
|
|
123
|
-
} // Which we need to convert back to `%` to patch the document
|
|
123
|
+
var handleDragEnd = _react.default.useCallback(() => {
|
|
124
|
+
setIsDragging(false); // get current values for x/y in px
|
|
124
125
|
|
|
126
|
+
var currentX = x.get();
|
|
127
|
+
var currentY = y.get(); // Which we need to convert back to `%` to patch the document
|
|
125
128
|
|
|
126
|
-
var newX = round(currentX * 100 /
|
|
127
|
-
var newY = round(currentY * 100 /
|
|
129
|
+
var newX = round(currentX * 100 / bounds.width);
|
|
130
|
+
var newY = round(currentY * 100 / bounds.height); // Don't go below 0 or above 100
|
|
128
131
|
|
|
129
132
|
var safeX = Math.max(0, Math.min(100, newX));
|
|
130
133
|
var safeY = Math.max(0, Math.min(100, newY));
|
|
131
134
|
update(spot._key, safeX, safeY);
|
|
132
135
|
}, [spot]);
|
|
133
136
|
|
|
137
|
+
var handleDragStart = _react.default.useCallback(() => setIsDragging(true), []);
|
|
138
|
+
|
|
139
|
+
var handleHoverStart = _react.default.useCallback(() => setIsHovering(true), []);
|
|
140
|
+
|
|
141
|
+
var handleHoverEnd = _react.default.useCallback(() => setIsHovering(false), []);
|
|
142
|
+
|
|
134
143
|
if (!x || !y) {
|
|
135
144
|
return null;
|
|
136
145
|
}
|
|
@@ -138,7 +147,6 @@ function Spot(_ref) {
|
|
|
138
147
|
return /*#__PURE__*/_react.default.createElement(_ui.Tooltip, {
|
|
139
148
|
key: spot._key,
|
|
140
149
|
disabled: isDragging,
|
|
141
|
-
boundaryElement: bounds.current,
|
|
142
150
|
portal: true,
|
|
143
151
|
content: tooltip && typeof tooltip === 'function' ? /*#__PURE__*/_react.default.createElement(tooltip, {
|
|
144
152
|
spot
|
|
@@ -154,18 +162,20 @@ function Spot(_ref) {
|
|
|
154
162
|
}, /*#__PURE__*/_react.default.createElement(_framerMotion.motion.div, {
|
|
155
163
|
drag: true,
|
|
156
164
|
dragConstraints: bounds,
|
|
165
|
+
dragElastic: 0,
|
|
157
166
|
dragMomentum: false,
|
|
158
|
-
|
|
167
|
+
onDragEnd: handleDragEnd,
|
|
168
|
+
onDragStart: handleDragStart,
|
|
169
|
+
onHoverStart: handleHoverStart,
|
|
170
|
+
onHoverEnd: handleHoverEnd,
|
|
171
|
+
style: _objectSpread(_objectSpread(_objectSpread({}, dragStyle), {}, {
|
|
159
172
|
x,
|
|
160
173
|
y
|
|
161
|
-
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
shadow: 3,
|
|
168
|
-
style: dotStyle
|
|
169
|
-
})));
|
|
174
|
+
}, isDragging && _objectSpread({}, dragStyleWhileDrag)), isHovering && _objectSpread({}, dragStyleWhileHover))
|
|
175
|
+
}, /*#__PURE__*/_react.default.createElement(_ui.Box, {
|
|
176
|
+
style: _objectSpread(_objectSpread({}, dotStyle), (isDragging || isHovering) && _objectSpread({}, dotStyleWhileActive))
|
|
177
|
+
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
178
|
+
style: _objectSpread(_objectSpread({}, labelStyle), (isDragging || isHovering) && _objectSpread({}, labelStyleWhileActive))
|
|
179
|
+
}, index + 1)));
|
|
170
180
|
}
|
|
171
181
|
//# sourceMappingURL=Spot.js.map
|
package/lib/Spot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"Spot.js","names":["dragStyle","width","height","position","boxSizing","top","left","margin","cursor","display","justifyContent","alignItems","borderRadius","background","color","dragStyleWhileDrag","border","dragStyleWhileHover","dotStyle","visibility","pointerEvents","dotStyleWhileActive","labelStyle","fontSize","fontWeight","lineHeight","labelStyleWhileActive","round","num","Math","Spot","spot","bounds","update","hotspotDescriptionPath","tooltip","index","React","useState","isDragging","setIsDragging","isHovering","setIsHovering","x","useMotionValue","y","useEffect","set","handleDragEnd","useCallback","currentX","get","currentY","newX","newY","safeX","max","min","safeY","_key","handleDragStart","handleHoverStart","handleHoverEnd","createElement","maxWidth"],"sources":["../src/Spot.tsx"],"sourcesContent":["import {Box, Text, Tooltip} from '@sanity/ui'\nimport {motion, useMotionValue} from 'framer-motion'\nimport get from 'lodash/get'\nimport React, {CSSProperties, ReactElement, useEffect} from 'react'\nimport {FnHotspotMove, TSpot} from './HotspotArray'\n\nconst dragStyle: CSSProperties = {\n width: '1.4rem',\n height: '1.4rem',\n position: 'absolute',\n boxSizing: 'border-box',\n top: 0,\n left: 0,\n margin: '-0.7rem 0 0 -0.7rem',\n cursor: 'pointer',\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: '50%',\n background: '#000',\n color: 'white',\n}\n\nconst dragStyleWhileDrag: CSSProperties = {\n background: 'rgba(0, 0, 0, 0.1)',\n border: '1px solid #fff',\n cursor: 'none',\n}\n\nconst dragStyleWhileHover: CSSProperties = {\n background: 'rgba(0, 0, 0, 0.1)',\n border: '1px solid #fff',\n}\n\nconst dotStyle: CSSProperties = {\n position: 'absolute',\n left: '50%',\n top: '50%',\n height: '0.2rem',\n width: '0.2rem',\n margin: '-0.1rem 0 0 -0.1rem',\n background: '#fff',\n visibility: 'hidden',\n borderRadius: '50%',\n // make sure pointer events only run on the parent\n pointerEvents: 'none',\n}\n\nconst dotStyleWhileActive: CSSProperties = {\n visibility: 'visible',\n}\n\nconst labelStyle: CSSProperties = {\n color: 'white',\n fontSize: '0.7rem',\n fontWeight: 600,\n lineHeight: '1',\n}\n\nconst labelStyleWhileActive: CSSProperties = {\n visibility: 'hidden',\n}\n\nconst round = (num) => Math.round(num * 100) / 100\n\ninterface IHotspot {\n spot: TSpot\n bounds: DOMRectReadOnly\n update: FnHotspotMove\n hotspotDescriptionPath?: string\n tooltip?: ReactElement\n index: number\n}\n\nexport default function Spot({\n spot,\n bounds,\n update,\n hotspotDescriptionPath,\n tooltip,\n index,\n}: IHotspot) {\n const [isDragging, setIsDragging] = React.useState(false)\n const [isHovering, setIsHovering] = React.useState(false)\n\n // x/y are stored as % but need to be converted to px\n const x = useMotionValue(round(bounds.width * (spot.x / 100)))\n const y = useMotionValue(round(bounds.height * (spot.y / 100)))\n\n /**\n * update x/y if the bounds change when resizing the window\n */\n useEffect(() => {\n x.set(round(bounds.width * (spot.x / 100)))\n y.set(round(bounds.height * (spot.y / 100)))\n }, [bounds])\n\n const handleDragEnd = React.useCallback(() => {\n setIsDragging(false)\n\n // get current values for x/y in px\n const currentX = x.get()\n const currentY = y.get()\n\n // Which we need to convert back to `%` to patch the document\n const newX = round((currentX * 100) / bounds.width)\n const newY = round((currentY * 100) / bounds.height)\n\n // Don't go below 0 or above 100\n const safeX = Math.max(0, Math.min(100, newX))\n const safeY = Math.max(0, Math.min(100, newY))\n\n update(spot._key, safeX, safeY)\n }, [spot])\n const handleDragStart = React.useCallback(() => setIsDragging(true), [])\n\n const handleHoverStart = React.useCallback(() => setIsHovering(true), [])\n const handleHoverEnd = React.useCallback(() => setIsHovering(false), [])\n\n if (!x || !y) {\n return null\n }\n\n return (\n <Tooltip\n key={spot._key}\n disabled={isDragging}\n portal\n content={\n tooltip && typeof tooltip === 'function' ? (\n React.createElement(tooltip, {spot})\n ) : (\n <Box padding={2} style={{maxWidth: 200, pointerEvents: `none`}}>\n <Text textOverflow=\"ellipsis\">\n {hotspotDescriptionPath\n ? (get(spot, hotspotDescriptionPath) as string)\n : `${spot.x}% x ${spot.y}%`}\n </Text>\n </Box>\n )\n }\n >\n <motion.div\n drag\n dragConstraints={bounds}\n dragElastic={0}\n dragMomentum={false}\n onDragEnd={handleDragEnd}\n onDragStart={handleDragStart}\n onHoverStart={handleHoverStart}\n onHoverEnd={handleHoverEnd}\n style={{\n ...dragStyle,\n x,\n y,\n ...(isDragging && {...dragStyleWhileDrag}),\n ...(isHovering && {...dragStyleWhileHover}),\n }}\n >\n {/* Dot */}\n <Box\n style={{\n ...dotStyle,\n ...((isDragging || isHovering) && {...dotStyleWhileActive}),\n }}\n />\n {/* Label */}\n <div\n style={{\n ...labelStyle,\n ...((isDragging || isHovering) && {...labelStyleWhileActive}),\n }}\n >\n {index + 1}\n </div>\n </motion.div>\n </Tooltip>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"useUnsetInputComponent.js","names":["useUnsetInputComponent","type","component","React","useMemo","unsetInputComponent","t","inputComponent","undefined","typeOfType"],"sources":["../src/useUnsetInputComponent.ts"],"sourcesContent":["import React from 'react'\n\nexport function useUnsetInputComponent(type, component) {\n return React.useMemo(() => unsetInputComponent(type, component), [type, component])\n}\n\nfunction unsetInputComponent(type, component) {\n const t = {\n ...type,\n inputComponent: type.inputComponent === component ? undefined : type.inputComponent,\n }\n const typeOfType = t.type ? unsetInputComponent(t.type, component) : undefined\n return {\n ...t,\n type: typeOfType,\n }\n}\n"],"mappings":";;;;;;;AAAA;;;;;;;;;;AAEO,SAASA,sBAAT,CAAgCC,IAAhC,EAAsCC,SAAtC,EAAiD;EACtD,OAAOC,cAAA,CAAMC,OAAN,CAAc,MAAMC,mBAAmB,CAACJ,IAAD,EAAOC,SAAP,CAAvC,EAA0D,CAACD,IAAD,EAAOC,SAAP,CAA1D,CAAP;AACD;;AAED,SAASG,mBAAT,CAA6BJ,IAA7B,EAAmCC,SAAnC,EAA8C;EAC5C,IAAMI,CAAC,mCACFL,IADE;IAELM,cAAc,EAAEN,IAAI,CAACM,cAAL,KAAwBL,SAAxB,GAAoCM,SAApC,GAAgDP,IAAI,CAACM;EAFhE,EAAP;;EAIA,IAAME,UAAU,GAAGH,CAAC,CAACL,IAAF,GAASI,mBAAmB,CAACC,CAAC,CAACL,IAAH,EAASC,SAAT,CAA5B,GAAkDM,SAArE;EACA,uCACKF,CADL;IAEEL,IAAI,EAAEQ;EAFR;AAID"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-hotspot-array",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "A configurable Custom Input for Arrays that will add and update items by clicking on an Image",
|
|
5
5
|
"main": "lib/HotspotArray.js",
|
|
6
6
|
"scripts": {
|
|
@@ -28,27 +28,28 @@
|
|
|
28
28
|
"author": "Simeon Griggs <simeon@sanity.io>",
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@react-hookz/web": "^14.2.2",
|
|
31
32
|
"@sanity/asset-utils": "^1.2.3",
|
|
32
|
-
"@sanity/base": "^2.
|
|
33
|
-
"@sanity/form-builder": "^2.
|
|
34
|
-
"@sanity/ui": "^0.37.
|
|
35
|
-
"framer-motion": "^6.
|
|
36
|
-
"husky": "^
|
|
33
|
+
"@sanity/base": "^2.30.1",
|
|
34
|
+
"@sanity/form-builder": "^2.30.1",
|
|
35
|
+
"@sanity/ui": "^0.37.12",
|
|
36
|
+
"framer-motion": "^6.3.11",
|
|
37
|
+
"husky": "^8.0.1"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"eslint": "^
|
|
40
|
-
"eslint-config-prettier": "^8.
|
|
41
|
-
"eslint-config-sanity": "^
|
|
42
|
-
"eslint-plugin-react": "^7.
|
|
40
|
+
"eslint": "^8.17.0",
|
|
41
|
+
"eslint-config-prettier": "^8.5.0",
|
|
42
|
+
"eslint-config-sanity": "^6.0.0",
|
|
43
|
+
"eslint-plugin-react": "^7.30.0",
|
|
43
44
|
"pinst": "^2.1.6",
|
|
44
|
-
"prettier": "^2.
|
|
45
|
+
"prettier": "^2.7.0",
|
|
45
46
|
"sanipack": "^2.1.0"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
|
48
|
-
"react": "^17.0.0",
|
|
49
|
-
"@sanity/util": "2.23.2",
|
|
50
49
|
"@sanity/image-url": "1.0.1",
|
|
51
|
-
"
|
|
50
|
+
"@sanity/util": "2.29.5",
|
|
51
|
+
"lodash": "4.17.21",
|
|
52
|
+
"react": "^17.0.2"
|
|
52
53
|
},
|
|
53
54
|
"bugs": {
|
|
54
55
|
"url": "https://github.com/SimeonGriggs/sanity-plugin-hotspot-array/issues"
|
package/src/HotspotArray.tsx
CHANGED
|
@@ -5,39 +5,62 @@ import sanityClient from 'part:@sanity/base/client'
|
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
import {withDocument} from 'part:@sanity/form-builder'
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import {Card, Flex, Stack, Text} from '@sanity/ui'
|
|
8
|
+
import {getImageDimensions} from '@sanity/asset-utils'
|
|
10
9
|
import {FormBuilderInput} from '@sanity/form-builder/lib/FormBuilderInput'
|
|
11
|
-
import {
|
|
12
|
-
import {PatchEvent, setIfMissing, set, insert} from '@sanity/form-builder/PatchEvent'
|
|
10
|
+
import {insert, PatchEvent, set, setIfMissing} from '@sanity/form-builder/PatchEvent'
|
|
13
11
|
import imageUrlBuilder from '@sanity/image-url'
|
|
14
|
-
import {
|
|
12
|
+
import {Card, Flex, Stack} from '@sanity/ui'
|
|
13
|
+
import {randomKey} from '@sanity/util/content'
|
|
15
14
|
import get from 'lodash/get'
|
|
15
|
+
import React, {useState} from 'react'
|
|
16
16
|
|
|
17
|
-
import
|
|
18
|
-
import { useUnsetInputComponent } from './useUnsetInputComponent'
|
|
17
|
+
import {IUseResizeObserverCallback, useDebouncedCallback, useResizeObserver} from '@react-hookz/web'
|
|
19
18
|
import Feedback from './Feedback'
|
|
19
|
+
import Spot from './Spot'
|
|
20
|
+
import {useUnsetInputComponent} from './useUnsetInputComponent'
|
|
20
21
|
|
|
21
22
|
const imageStyle = {width: `100%`, height: `auto`}
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
-
|
|
24
|
+
const VALID_ROOT_PATHS = ['document', 'parent']
|
|
25
|
+
|
|
26
|
+
export type FnHotspotMove = (key: string, x: number, y: number) => void
|
|
27
|
+
|
|
28
|
+
export type TSpot = {
|
|
29
|
+
_key: string
|
|
30
|
+
_type: `spot`
|
|
31
|
+
x: number
|
|
32
|
+
y: number
|
|
33
|
+
} & {[key: string]: unknown}
|
|
34
|
+
|
|
35
|
+
const HotspotArray = React.forwardRef((props: any, ref) => {
|
|
36
|
+
const {type, value, onChange, document} = props
|
|
25
37
|
const {options} = type ?? {}
|
|
26
38
|
|
|
27
39
|
// Attempt prevention of infinite loop in <FormBuilderInput />
|
|
28
40
|
// Re-renders can still occur if this Component is used again in a nested field
|
|
29
41
|
const typeWithoutInputComponent = useUnsetInputComponent(type, type?.inputComponent)
|
|
42
|
+
const imageHotspotPathRoot = VALID_ROOT_PATHS.includes(options?.imageHotspotPathRoot)
|
|
43
|
+
? props[options.imageHotspotPathRoot]
|
|
44
|
+
: document
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Finding the image from the imageHotspotPathRoot (defaults to document),
|
|
48
|
+
* using the path from the hotspot's `options` field
|
|
49
|
+
*
|
|
50
|
+
* when changes in imageHotspotPathRoot (e.g. document) occur,
|
|
51
|
+
* check if there are any changes to the hotspotImage and update the reference
|
|
52
|
+
*/
|
|
53
|
+
const hotspotImage = React.useMemo(() => {
|
|
54
|
+
return get(imageHotspotPathRoot, options?.hotspotImagePath)
|
|
55
|
+
}, [imageHotspotPathRoot])
|
|
30
56
|
|
|
31
|
-
// Finding the image from the document,
|
|
32
|
-
// using the path from the hotspot's `options` field
|
|
33
57
|
const displayImage = React.useMemo(() => {
|
|
34
58
|
const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)
|
|
35
59
|
const urlFor = (source) => builder.image(source)
|
|
36
|
-
const hotspotImage = get(sanityDocument, options?.hotspotImagePath)
|
|
37
60
|
|
|
38
61
|
if (hotspotImage?.asset?._ref) {
|
|
39
62
|
const {aspectRatio} = getImageDimensions(hotspotImage.asset._ref)
|
|
40
|
-
const width =
|
|
63
|
+
const width = 1200
|
|
41
64
|
const height = Math.round(width / aspectRatio)
|
|
42
65
|
const url = urlFor(hotspotImage).width(width).url()
|
|
43
66
|
|
|
@@ -45,7 +68,7 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
45
68
|
}
|
|
46
69
|
|
|
47
70
|
return null
|
|
48
|
-
}, [
|
|
71
|
+
}, [hotspotImage])
|
|
49
72
|
|
|
50
73
|
const handleHotspotImageClick = React.useCallback((event) => {
|
|
51
74
|
const {nativeEvent} = event
|
|
@@ -55,7 +78,7 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
55
78
|
const y = Number(((nativeEvent.offsetY * 100) / nativeEvent.srcElement.height).toFixed(2))
|
|
56
79
|
const description = `New Hotspot at ${x}% x ${y}%`
|
|
57
80
|
|
|
58
|
-
const newRow = {
|
|
81
|
+
const newRow: TSpot = {
|
|
59
82
|
_key: randomKey(12),
|
|
60
83
|
_type: `spot`,
|
|
61
84
|
x,
|
|
@@ -69,13 +92,8 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
69
92
|
onChange(PatchEvent.from(setIfMissing([]), insert([newRow], 'after', [-1])))
|
|
70
93
|
}, [])
|
|
71
94
|
|
|
72
|
-
const handleHotspotMove = React.useCallback(
|
|
95
|
+
const handleHotspotMove: FnHotspotMove = React.useCallback(
|
|
73
96
|
(key, x, y) => {
|
|
74
|
-
if (!Number(x) || !Number(y)) {
|
|
75
|
-
console.warn(`Missing or non-number X or Y`, {x, y})
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
|
|
79
97
|
onChange(
|
|
80
98
|
PatchEvent.from(
|
|
81
99
|
// Set the `x` value of this array key item
|
|
@@ -88,18 +106,28 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
88
106
|
[value]
|
|
89
107
|
)
|
|
90
108
|
|
|
91
|
-
const hotspotImageRef = React.useRef(null)
|
|
109
|
+
const hotspotImageRef = React.useRef<HTMLImageElement | null>(null)
|
|
110
|
+
|
|
111
|
+
const [imageRect, setImageRect] = useState<DOMRectReadOnly>()
|
|
112
|
+
const updateImageRectCallback = useDebouncedCallback(
|
|
113
|
+
((e) => setImageRect(e.contentRect)) as IUseResizeObserverCallback,
|
|
114
|
+
[setImageRect],
|
|
115
|
+
200
|
|
116
|
+
)
|
|
117
|
+
useResizeObserver(hotspotImageRef, updateImageRectCallback)
|
|
92
118
|
|
|
93
119
|
return (
|
|
94
|
-
<Stack space={[2,2,3]}>
|
|
120
|
+
<Stack space={[2, 2, 3]}>
|
|
95
121
|
{displayImage?.url ? (
|
|
96
122
|
<div style={{position: `relative`}}>
|
|
97
|
-
{
|
|
98
|
-
value
|
|
123
|
+
{imageRect &&
|
|
124
|
+
value?.length > 0 &&
|
|
125
|
+
value.map((spot, index) => (
|
|
99
126
|
<Spot
|
|
127
|
+
index={index}
|
|
100
128
|
key={spot._key}
|
|
101
129
|
spot={spot}
|
|
102
|
-
bounds={
|
|
130
|
+
bounds={imageRect}
|
|
103
131
|
update={handleHotspotMove}
|
|
104
132
|
hotspotDescriptionPath={options?.hotspotDescriptionPath}
|
|
105
133
|
tooltip={options?.hotspotTooltip}
|
|
@@ -116,18 +144,31 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
116
144
|
alt=""
|
|
117
145
|
style={imageStyle}
|
|
118
146
|
onClick={handleHotspotImageClick}
|
|
119
|
-
|
|
147
|
+
/>
|
|
120
148
|
</Flex>
|
|
121
149
|
</Card>
|
|
122
150
|
</div>
|
|
123
151
|
) : (
|
|
124
152
|
<Feedback>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
153
|
+
{type?.options?.hotspotImagePath ? (
|
|
154
|
+
<>
|
|
155
|
+
No Hotspot image found at path <code>{type?.options?.hotspotImagePath}</code>
|
|
156
|
+
</>
|
|
157
|
+
) : (
|
|
158
|
+
<>
|
|
159
|
+
Define a path in this field using to the image field in this document at{' '}
|
|
160
|
+
<code>options.hotspotImagePath</code>
|
|
161
|
+
</>
|
|
162
|
+
)}
|
|
163
|
+
</Feedback>
|
|
130
164
|
)}
|
|
165
|
+
{type?.options?.imageHotspotPathRoot &&
|
|
166
|
+
!VALID_ROOT_PATHS.includes(type.options.imageHotspotPathRoot) && (
|
|
167
|
+
<Feedback>
|
|
168
|
+
The supplied imageHotspotPathRoot "{type.options.imageHotspotPathRoot}" is not valid,
|
|
169
|
+
falling back to "document". Available values are "{VALID_ROOT_PATHS.join(', ')}".
|
|
170
|
+
</Feedback>
|
|
171
|
+
)}
|
|
131
172
|
<FormBuilderInput {...props} type={typeWithoutInputComponent} ref={ref} />
|
|
132
173
|
</Stack>
|
|
133
174
|
)
|
package/src/Spot.tsx
CHANGED
|
@@ -1,81 +1,121 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {motion} from 'framer-motion'
|
|
1
|
+
import {Box, Text, Tooltip} from '@sanity/ui'
|
|
2
|
+
import {motion, useMotionValue} from 'framer-motion'
|
|
4
3
|
import get from 'lodash/get'
|
|
4
|
+
import React, {CSSProperties, ReactElement, useEffect} from 'react'
|
|
5
|
+
import {FnHotspotMove, TSpot} from './HotspotArray'
|
|
6
|
+
|
|
7
|
+
const dragStyle: CSSProperties = {
|
|
8
|
+
width: '1.4rem',
|
|
9
|
+
height: '1.4rem',
|
|
10
|
+
position: 'absolute',
|
|
11
|
+
boxSizing: 'border-box',
|
|
12
|
+
top: 0,
|
|
13
|
+
left: 0,
|
|
14
|
+
margin: '-0.7rem 0 0 -0.7rem',
|
|
15
|
+
cursor: 'pointer',
|
|
16
|
+
display: 'flex',
|
|
17
|
+
justifyContent: 'center',
|
|
18
|
+
alignItems: 'center',
|
|
19
|
+
borderRadius: '50%',
|
|
20
|
+
background: '#000',
|
|
21
|
+
color: 'white',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const dragStyleWhileDrag: CSSProperties = {
|
|
25
|
+
background: 'rgba(0, 0, 0, 0.1)',
|
|
26
|
+
border: '1px solid #fff',
|
|
27
|
+
cursor: 'none',
|
|
28
|
+
}
|
|
5
29
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
position: `absolute`,
|
|
30
|
+
const dragStyleWhileHover: CSSProperties = {
|
|
31
|
+
background: 'rgba(0, 0, 0, 0.1)',
|
|
32
|
+
border: '1px solid #fff',
|
|
10
33
|
}
|
|
11
34
|
|
|
12
|
-
const dotStyle = {
|
|
13
|
-
|
|
14
|
-
|
|
35
|
+
const dotStyle: CSSProperties = {
|
|
36
|
+
position: 'absolute',
|
|
37
|
+
left: '50%',
|
|
38
|
+
top: '50%',
|
|
39
|
+
height: '0.2rem',
|
|
40
|
+
width: '0.2rem',
|
|
41
|
+
margin: '-0.1rem 0 0 -0.1rem',
|
|
42
|
+
background: '#fff',
|
|
43
|
+
visibility: 'hidden',
|
|
44
|
+
borderRadius: '50%',
|
|
15
45
|
// make sure pointer events only run on the parent
|
|
16
|
-
pointerEvents:
|
|
46
|
+
pointerEvents: 'none',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const dotStyleWhileActive: CSSProperties = {
|
|
50
|
+
visibility: 'visible',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const labelStyle: CSSProperties = {
|
|
54
|
+
color: 'white',
|
|
55
|
+
fontSize: '0.7rem',
|
|
56
|
+
fontWeight: 600,
|
|
57
|
+
lineHeight: '1',
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const labelStyleWhileActive: CSSProperties = {
|
|
61
|
+
visibility: 'hidden',
|
|
17
62
|
}
|
|
18
63
|
|
|
19
64
|
const round = (num) => Math.round(num * 100) / 100
|
|
20
65
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
66
|
+
interface IHotspot {
|
|
67
|
+
spot: TSpot
|
|
68
|
+
bounds: DOMRectReadOnly
|
|
69
|
+
update: FnHotspotMove
|
|
70
|
+
hotspotDescriptionPath?: string
|
|
71
|
+
tooltip?: ReactElement
|
|
72
|
+
index: number
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default function Spot({
|
|
76
|
+
spot,
|
|
77
|
+
bounds,
|
|
78
|
+
update,
|
|
79
|
+
hotspotDescriptionPath,
|
|
80
|
+
tooltip,
|
|
81
|
+
index,
|
|
82
|
+
}: IHotspot) {
|
|
25
83
|
const [isDragging, setIsDragging] = React.useState(false)
|
|
84
|
+
const [isHovering, setIsHovering] = React.useState(false)
|
|
26
85
|
|
|
27
|
-
|
|
28
|
-
|
|
86
|
+
// x/y are stored as % but need to be converted to px
|
|
87
|
+
const x = useMotionValue(round(bounds.width * (spot.x / 100)))
|
|
88
|
+
const y = useMotionValue(round(bounds.height * (spot.y / 100)))
|
|
29
89
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
90
|
+
/**
|
|
91
|
+
* update x/y if the bounds change when resizing the window
|
|
92
|
+
*/
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
x.set(round(bounds.width * (spot.x / 100)))
|
|
95
|
+
y.set(round(bounds.height * (spot.y / 100)))
|
|
96
|
+
}, [bounds])
|
|
36
97
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}, [bounds, bounds.current])
|
|
42
|
-
|
|
43
|
-
const handleDragEnd = React.useCallback(
|
|
44
|
-
(event) => {
|
|
45
|
-
setIsDragging(false)
|
|
46
|
-
|
|
47
|
-
// I don't know why, but framer-motion doesn't give you the actual transform values
|
|
48
|
-
// So we have to regex the `px` values off the inline styles
|
|
49
|
-
const [currentX, currentY] = event.srcElement.style.transform.split(` `).map((v) => {
|
|
50
|
-
return v
|
|
51
|
-
? v
|
|
52
|
-
.match(/\(([^)]+)\)/)
|
|
53
|
-
.pop()
|
|
54
|
-
.replace(`px`, ``)
|
|
55
|
-
: null
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
if (!Number(currentX) || !Number(currentY)) {
|
|
59
|
-
return console.warn(`Missing or non-number X or Y`, {currentX, currentY}, event.srcElement)
|
|
60
|
-
}
|
|
98
|
+
const handleDragEnd = React.useCallback(() => {
|
|
99
|
+
setIsDragging(false)
|
|
61
100
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
101
|
+
// get current values for x/y in px
|
|
102
|
+
const currentX = x.get()
|
|
103
|
+
const currentY = y.get()
|
|
65
104
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// Don't go below 0 or above 100
|
|
71
|
-
const safeX = Math.max(0, Math.min(100, newX))
|
|
72
|
-
const safeY = Math.max(0, Math.min(100, newY))
|
|
105
|
+
// Which we need to convert back to `%` to patch the document
|
|
106
|
+
const newX = round((currentX * 100) / bounds.width)
|
|
107
|
+
const newY = round((currentY * 100) / bounds.height)
|
|
73
108
|
|
|
74
|
-
|
|
75
|
-
|
|
109
|
+
// Don't go below 0 or above 100
|
|
110
|
+
const safeX = Math.max(0, Math.min(100, newX))
|
|
111
|
+
const safeY = Math.max(0, Math.min(100, newY))
|
|
76
112
|
|
|
77
|
-
|
|
78
|
-
)
|
|
113
|
+
update(spot._key, safeX, safeY)
|
|
114
|
+
}, [spot])
|
|
115
|
+
const handleDragStart = React.useCallback(() => setIsDragging(true), [])
|
|
116
|
+
|
|
117
|
+
const handleHoverStart = React.useCallback(() => setIsHovering(true), [])
|
|
118
|
+
const handleHoverEnd = React.useCallback(() => setIsHovering(false), [])
|
|
79
119
|
|
|
80
120
|
if (!x || !y) {
|
|
81
121
|
return null
|
|
@@ -85,14 +125,17 @@ export default function Spot({spot, bounds = undefined, update, hotspotDescripti
|
|
|
85
125
|
<Tooltip
|
|
86
126
|
key={spot._key}
|
|
87
127
|
disabled={isDragging}
|
|
88
|
-
boundaryElement={bounds.current}
|
|
89
128
|
portal
|
|
90
129
|
content={
|
|
91
130
|
tooltip && typeof tooltip === 'function' ? (
|
|
92
131
|
React.createElement(tooltip, {spot})
|
|
93
132
|
) : (
|
|
94
133
|
<Box padding={2} style={{maxWidth: 200, pointerEvents: `none`}}>
|
|
95
|
-
<Text textOverflow="ellipsis">
|
|
134
|
+
<Text textOverflow="ellipsis">
|
|
135
|
+
{hotspotDescriptionPath
|
|
136
|
+
? (get(spot, hotspotDescriptionPath) as string)
|
|
137
|
+
: `${spot.x}% x ${spot.y}%`}
|
|
138
|
+
</Text>
|
|
96
139
|
</Box>
|
|
97
140
|
)
|
|
98
141
|
}
|
|
@@ -100,14 +143,37 @@ export default function Spot({spot, bounds = undefined, update, hotspotDescripti
|
|
|
100
143
|
<motion.div
|
|
101
144
|
drag
|
|
102
145
|
dragConstraints={bounds}
|
|
146
|
+
dragElastic={0}
|
|
103
147
|
dragMomentum={false}
|
|
104
|
-
initial={{x, y}}
|
|
105
148
|
onDragEnd={handleDragEnd}
|
|
106
|
-
onDragStart={
|
|
107
|
-
|
|
149
|
+
onDragStart={handleDragStart}
|
|
150
|
+
onHoverStart={handleHoverStart}
|
|
151
|
+
onHoverEnd={handleHoverEnd}
|
|
152
|
+
style={{
|
|
153
|
+
...dragStyle,
|
|
154
|
+
x,
|
|
155
|
+
y,
|
|
156
|
+
...(isDragging && {...dragStyleWhileDrag}),
|
|
157
|
+
...(isHovering && {...dragStyleWhileHover}),
|
|
158
|
+
}}
|
|
108
159
|
>
|
|
109
|
-
|
|
160
|
+
{/* Dot */}
|
|
161
|
+
<Box
|
|
162
|
+
style={{
|
|
163
|
+
...dotStyle,
|
|
164
|
+
...((isDragging || isHovering) && {...dotStyleWhileActive}),
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
{/* Label */}
|
|
168
|
+
<div
|
|
169
|
+
style={{
|
|
170
|
+
...labelStyle,
|
|
171
|
+
...((isDragging || isHovering) && {...labelStyleWhileActive}),
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
{index + 1}
|
|
175
|
+
</div>
|
|
110
176
|
</motion.div>
|
|
111
177
|
</Tooltip>
|
|
112
178
|
)
|
|
113
|
-
}
|
|
179
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
3
|
export function useUnsetInputComponent(type, component) {
|
|
4
|
-
|
|
4
|
+
return React.useMemo(() => unsetInputComponent(type, component), [type, component])
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function unsetInputComponent(type, component) {
|
|
8
|
+
const t = {
|
|
9
|
+
...type,
|
|
10
|
+
inputComponent: type.inputComponent === component ? undefined : type.inputComponent,
|
|
11
|
+
}
|
|
12
|
+
const typeOfType = t.type ? unsetInputComponent(t.type, component) : undefined
|
|
13
|
+
return {
|
|
14
|
+
...t,
|
|
15
|
+
type: typeOfType,
|
|
5
16
|
}
|
|
6
|
-
|
|
7
|
-
function unsetInputComponent(type, component) {
|
|
8
|
-
const t = {
|
|
9
|
-
...type,
|
|
10
|
-
inputComponent: type.inputComponent === component ? undefined : type.inputComponent,
|
|
11
|
-
}
|
|
12
|
-
const typeOfType = t.type ? unsetInputComponent(t.type, component) : undefined
|
|
13
|
-
return {
|
|
14
|
-
...t,
|
|
15
|
-
type: typeOfType,
|
|
16
|
-
}
|
|
17
|
-
}
|
|
17
|
+
}
|