sanity-plugin-hotspot-array 0.0.4 → 0.0.7

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 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
- ![2021-12-22 15 07 58](https://user-images.githubusercontent.com/9684022/147113578-d49d747a-4904-45b4-b4e0-409b33832226.gif)
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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Feedback.tsx"],"names":["Feedback","children"],"mappings":";;;;;;;AAAA;;AACA;;;;AAEe,SAASA,QAAT,OAA8B;AAAA,MAAXC,QAAW,QAAXA,QAAW;AAC3C,sBACE,6BAAC,QAAD;AAAM,IAAA,OAAO,EAAE,CAAf;AAAkB,IAAA,MAAM,EAAE,CAA1B;AAA6B,IAAA,MAAM,EAAE,CAArC;AAAwC,IAAA,IAAI,EAAC;AAA7C,kBACE,6BAAC,QAAD,QAAOA,QAAP,CADF,CADF;AAKD","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"],"file":"Feedback.js"}
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"}
@@ -9,67 +9,95 @@ var _client = _interopRequireDefault(require("part:@sanity/base/client"));
9
9
 
10
10
  var _formBuilder = require("part:@sanity/form-builder");
11
11
 
12
- var _react = _interopRequireDefault(require("react"));
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 _assetUtils = require("@sanity/asset-utils");
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 _Feedback = _interopRequireDefault(require("./Feedback"));
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 || 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); }
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); }
37
49
 
38
- var builder = (0, _imageUrl.default)(_client.default).dataset(_client.default.config().dataset);
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; }
39
51
 
40
- var urlFor = source => builder.image(source);
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; }
41
55
 
42
56
  var imageStyle = {
43
57
  width: "100%",
44
58
  height: "auto"
45
59
  };
60
+ var VALID_ROOT_PATHS = ['document', 'parent'];
46
61
 
47
62
  var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
48
- var _type$options, _type$options2;
63
+ var _type$options, _type$options2, _type$options3;
49
64
 
50
65
  var type = props.type,
51
66
  value = props.value,
52
67
  onChange = props.onChange,
53
- sanityDocument = props.document;
68
+ document = props.document;
54
69
 
55
70
  var _ref = type !== null && type !== void 0 ? type : {},
56
71
  options = _ref.options; // Attempt prevention of infinite loop in <FormBuilderInput />
57
72
  // Re-renders can still occur if this Component is used again in a nested field
58
73
 
59
74
 
60
- var typeWithoutInputComponent = (0, _useUnsetInputComponent.useUnsetInputComponent)(type, type === null || type === void 0 ? void 0 : type.inputComponent); // Finding the image from the document,
61
- // using the path from the hotspot's `options` field
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]);
62
88
 
63
89
  var displayImage = _react.default.useMemo(() => {
64
90
  var _hotspotImage$asset;
65
91
 
66
- var hotspotImage = (0, _get.default)(sanityDocument, options === null || options === void 0 ? void 0 : options.hotspotImagePath);
92
+ var builder = (0, _imageUrl.default)(_client.default).dataset(_client.default.config().dataset);
93
+
94
+ var urlFor = source => builder.image(source);
67
95
 
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 = 600;
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
- }, [sanityDocument, type]);
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: hotspotImageRef,
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 ", /*#__PURE__*/_react.default.createElement("code", null, "options.hotspotImagePath"))), /*#__PURE__*/_react.default.createElement(_FormBuilderInput.FormBuilderInput, _extends({}, props, {
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
  })));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/HotspotArray.tsx"],"names":["builder","sanityClient","dataset","config","urlFor","source","image","imageStyle","width","height","HotspotArray","React","forwardRef","props","ref","type","value","onChange","sanityDocument","document","options","typeWithoutInputComponent","inputComponent","displayImage","useMemo","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 builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)\nconst urlFor = (source) => builder.image(source)\nconst imageStyle = {width: `100%`, height: `auto`}\n\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 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":";;;;;;;AAGA;;AAEA;;AAEA;;AACA;;AACA;;AACA;;AACA;;AACA;;AACA;;AACA;;AAEA;;AACA;;AACA;;AACA;;;;;;;;;;;;;;;;;;;;;;AAEA,IAAMA,UAAU,GAAG;EAACC,KAAK,QAAN;EAAgBC,MAAM;AAAtB,CAAnB;AAEA,IAAMC,gBAAgB,GAAG,CAAC,UAAD,EAAa,QAAb,CAAzB;;AAWA,IAAMC,YAAY,gBAAGC,cAAA,CAAMC,UAAN,CAAiB,CAACC,KAAD,EAAaC,GAAb,KAAqB;EAAA;;EACzD,IAAOC,IAAP,GAA0CF,KAA1C,CAAOE,IAAP;EAAA,IAAaC,KAAb,GAA0CH,KAA1C,CAAaG,KAAb;EAAA,IAAoBC,QAApB,GAA0CJ,KAA1C,CAAoBI,QAApB;EAAA,IAA8BC,QAA9B,GAA0CL,KAA1C,CAA8BK,QAA9B;;EACA,WAAkBH,IAAlB,aAAkBA,IAAlB,cAAkBA,IAAlB,GAA0B,EAA1B;EAAA,IAAOI,OAAP,QAAOA,OAAP,CAFyD,CAIzD;EACA;;;EACA,IAAMC,yBAAyB,GAAG,IAAAC,8CAAA,EAAuBN,IAAvB,EAA6BA,IAA7B,aAA6BA,IAA7B,uBAA6BA,IAAI,CAAEO,cAAnC,CAAlC;EACA,IAAMC,oBAAoB,GAAGd,gBAAgB,CAACe,QAAjB,CAA0BL,OAA1B,aAA0BA,OAA1B,uBAA0BA,OAAO,CAAEI,oBAAnC,IACzBV,KAAK,CAACM,OAAO,CAACI,oBAAT,CADoB,GAEzBL,QAFJ;EAIA;AACF;AACA;AACA;AACA;AACA;AACA;;EACE,IAAMO,YAAY,GAAGd,cAAA,CAAMe,OAAN,CAAc,MAAM;IACvC,OAAO,IAAAC,YAAA,EAAIJ,oBAAJ,EAA0BJ,OAA1B,aAA0BA,OAA1B,uBAA0BA,OAAO,CAAES,gBAAnC,CAAP;EACD,CAFoB,EAElB,CAACL,oBAAD,CAFkB,CAArB;;EAIA,IAAMM,YAAY,GAAGlB,cAAA,CAAMe,OAAN,CAAc,MAAM;IAAA;;IACvC,IAAMI,OAAO,GAAG,IAAAC,iBAAA,EAAgBC,eAAhB,EAA8BC,OAA9B,CAAsCD,eAAA,CAAaE,MAAb,GAAsBD,OAA5D,CAAhB;;IACA,IAAME,MAAM,GAAIC,MAAD,IAAYN,OAAO,CAACO,KAAR,CAAcD,MAAd,CAA3B;;IAEA,IAAIX,YAAJ,aAAIA,YAAJ,sCAAIA,YAAY,CAAEa,KAAlB,gDAAI,oBAAqBC,IAAzB,EAA+B;MAC7B,0BAAsB,IAAAC,8BAAA,EAAmBf,YAAY,CAACa,KAAb,CAAmBC,IAAtC,CAAtB;MAAA,IAAOE,WAAP,uBAAOA,WAAP;;MACA,IAAMlC,KAAK,GAAG,IAAd;MACA,IAAMC,MAAM,GAAGkC,IAAI,CAACC,KAAL,CAAWpC,KAAK,GAAGkC,WAAnB,CAAf;MACA,IAAMG,GAAG,GAAGT,MAAM,CAACV,YAAD,CAAN,CAAqBlB,KAArB,CAA2BA,KAA3B,EAAkCqC,GAAlC,EAAZ;MAEA,OAAO;QAACrC,KAAD;QAAQC,MAAR;QAAgBoC;MAAhB,CAAP;IACD;;IAED,OAAO,IAAP;EACD,CAdoB,EAclB,CAACnB,YAAD,CAdkB,CAArB;;EAgBA,IAAMoB,uBAAuB,GAAGlC,cAAA,CAAMmC,WAAN,CAAmBC,KAAD,IAAW;IAC3D,IAAOC,WAAP,GAAsBD,KAAtB,CAAOC,WAAP,CAD2D,CAG3D;;IACA,IAAMC,CAAC,GAAGC,MAAM,CAAC,CAAEF,WAAW,CAACG,OAAZ,GAAsB,GAAvB,GAA8BH,WAAW,CAACI,UAAZ,CAAuB7C,KAAtD,EAA6D8C,OAA7D,CAAqE,CAArE,CAAD,CAAhB;IACA,IAAMC,CAAC,GAAGJ,MAAM,CAAC,CAAEF,WAAW,CAACO,OAAZ,GAAsB,GAAvB,GAA8BP,WAAW,CAACI,UAAZ,CAAuB5C,MAAtD,EAA8D6C,OAA9D,CAAsE,CAAtE,CAAD,CAAhB;IACA,IAAMG,WAAW,4BAAqBP,CAArB,iBAA6BK,CAA7B,MAAjB;IAEA,IAAMG,MAAa,GAAG;MACpBC,IAAI,EAAE,IAAAC,kBAAA,EAAU,EAAV,CADc;MAEpBC,KAAK,QAFe;MAGpBX,CAHoB;MAIpBK;IAJoB,CAAtB;;IAOA,IAAInC,OAAJ,aAAIA,OAAJ,eAAIA,OAAO,CAAE0C,sBAAb,EAAqC;MACnCJ,MAAM,CAACtC,OAAO,CAAC0C,sBAAT,CAAN,GAAyCL,WAAzC;IACD;;IAEDvC,QAAQ,CAAC6C,sBAAA,CAAWC,IAAX,CAAgB,IAAAC,wBAAA,EAAa,EAAb,CAAhB,EAAkC,IAAAC,kBAAA,EAAO,CAACR,MAAD,CAAP,EAAiB,OAAjB,EAA0B,CAAC,CAAC,CAAF,CAA1B,CAAlC,CAAD,CAAR;EACD,CApB+B,EAoB7B,EApB6B,CAAhC;;EAsBA,IAAMS,iBAAgC,GAAGvD,cAAA,CAAMmC,WAAN,CACvC,CAACqB,GAAD,EAAMlB,CAAN,EAASK,CAAT,KAAe;IACbrC,QAAQ,CACN6C,sBAAA,CAAWC,IAAX,EACE;IACA,IAAAK,eAAA,EAAInB,CAAJ,EAAO,CAAC;MAACS,IAAI,EAAES;IAAP,CAAD,EAAc,GAAd,CAAP,CAFF,EAGE;IACA,IAAAC,eAAA,EAAId,CAAJ,EAAO,CAAC;MAACI,IAAI,EAAES;IAAP,CAAD,EAAc,GAAd,CAAP,CAJF,CADM,CAAR;EAQD,CAVsC,EAWvC,CAACnD,KAAD,CAXuC,CAAzC;;EAcA,IAAMqD,eAAe,GAAG1D,cAAA,CAAM2D,MAAN,CAAsC,IAAtC,CAAxB;;EAEA,gBAAkC,IAAAC,eAAA,GAAlC;EAAA;EAAA,IAAOC,SAAP;EAAA,IAAkBC,YAAlB;;EACA,IAAMC,uBAAuB,GAAG,IAAAC,yBAAA,EAC5BC,CAAD,IAAOH,YAAY,CAACG,CAAC,CAACC,WAAH,CADU,EAE9B,CAACJ,YAAD,CAF8B,EAG9B,GAH8B,CAAhC;EAKA,IAAAK,sBAAA,EAAkBT,eAAlB,EAAmCK,uBAAnC;EAEA,oBACE,6BAAC,SAAD;IAAO,KAAK,EAAE,CAAC,CAAD,EAAI,CAAJ,EAAO,CAAP;EAAd,GACG7C,YAAY,SAAZ,IAAAA,YAAY,WAAZ,IAAAA,YAAY,CAAEe,GAAd,gBACC;IAAK,KAAK,EAAE;MAACmC,QAAQ;IAAT;EAAZ,GACGP,SAAS,IACR,CAAAxD,KAAK,SAAL,IAAAA,KAAK,WAAL,YAAAA,KAAK,CAAEgE,MAAP,IAAgB,CADjB,IAEChE,KAAK,CAACiE,GAAN,CAAU,CAACC,IAAD,EAAOC,KAAP,kBACR,6BAAC,aAAD;IACE,KAAK,EAAEA,KADT;IAEE,GAAG,EAAED,IAAI,CAACxB,IAFZ;IAGE,IAAI,EAAEwB,IAHR;IAIE,MAAM,EAAEV,SAJV;IAKE,MAAM,EAAEN,iBALV;IAME,sBAAsB,EAAE/C,OAAF,aAAEA,OAAF,uBAAEA,OAAO,CAAE0C,sBANnC;IAOE,OAAO,EAAE1C,OAAF,aAAEA,OAAF,uBAAEA,OAAO,CAAEiE;EAPpB,EADF,CAHJ,eAeE,6BAAC,QAAD;IAAM,oBAAoB,MAA1B;IAA2B,MAAM,EAAE;EAAnC,gBACE,6BAAC,QAAD;IAAM,KAAK,EAAC,QAAZ;IAAqB,OAAO,EAAC;EAA7B,gBACE;IACE,GAAG,EAAEf,eADP;IAEE,GAAG,EAAExC,YAAY,CAACe,GAFpB;IAGE,KAAK,EAAEf,YAAY,CAACtB,KAHtB;IAIE,MAAM,EAAEsB,YAAY,CAACrB,MAJvB;IAKE,GAAG,EAAC,EALN;IAME,KAAK,EAAEF,UANT;IAOE,OAAO,EAAEuC;EAPX,EADF,CADF,CAfF,CADD,gBA+BC,6BAAC,iBAAD,QACG9B,IAAI,SAAJ,IAAAA,IAAI,WAAJ,qBAAAA,IAAI,CAAEI,OAAN,wDAAeS,gBAAf,gBACC,4GACiC,2CAAOb,IAAP,aAAOA,IAAP,yCAAOA,IAAI,CAAEI,OAAb,mDAAO,eAAeS,gBAAtB,CADjC,CADD,gBAKC,wIAC2E,GAD3E,eAEE,sEAFF,CANJ,CAhCJ,EA6CG,CAAAb,IAAI,SAAJ,IAAAA,IAAI,WAAJ,8BAAAA,IAAI,CAAEI,OAAN,kEAAeI,oBAAf,KACC,CAACd,gBAAgB,CAACe,QAAjB,CAA0BT,IAAI,CAACI,OAAL,CAAaI,oBAAvC,CADF,iBAEG,6BAAC,iBAAD,gDACsCR,IAAI,CAACI,OAAL,CAAaI,oBADnD,4EAEqDd,gBAAgB,CAAC4E,IAAjB,CAAsB,IAAtB,CAFrD,QA/CN,eAoDE,6BAAC,kCAAD,eAAsBxE,KAAtB;IAA6B,IAAI,EAAEO,yBAAnC;IAA8D,GAAG,EAAEN;EAAnE,GApDF,CADF;AAwDD,CA5IoB,CAArB;;eA8Ie,IAAAwE,yBAAA,EAAa5E,YAAb,C"}
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,108 @@ 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: "1rem",
38
- height: "1rem",
39
- position: "absolute"
41
+ width: '1.4rem',
42
+ height: '1.4rem',
43
+ position: 'absolute',
44
+ top: 0,
45
+ left: 0,
46
+ margin: '-0.7rem 0 0 -0.7rem',
47
+ cursor: 'pointer',
48
+ display: 'flex',
49
+ justifyContent: 'center',
50
+ alignItems: 'center',
51
+ borderRadius: '50%',
52
+ background: '#000',
53
+ color: 'white'
40
54
  };
41
-
42
- var dotStyle = _objectSpread(_objectSpread({}, dragStyle), {}, {
43
- borderRadius: "50%",
55
+ var dragStyleWhileDrag = {
56
+ background: 'rgba(0, 0, 0, 0.1)',
57
+ border: '1px solid #fff',
58
+ cursor: 'none'
59
+ };
60
+ var dragStyleWhileHover = {
61
+ background: 'rgba(0, 0, 0, 0.1)',
62
+ border: '1px solid #fff'
63
+ };
64
+ var dotStyle = {
65
+ position: 'absolute',
66
+ left: '50%',
67
+ top: '50%',
68
+ height: '0.2rem',
69
+ width: '0.2rem',
70
+ margin: '-0.1rem 0 0 -0.1rem',
71
+ background: '#fff',
72
+ visibility: 'hidden',
73
+ borderRadius: '50%',
44
74
  // make sure pointer events only run on the parent
45
- pointerEvents: "none"
46
- });
75
+ pointerEvents: 'none'
76
+ };
77
+ var dotStyleWhileActive = {
78
+ visibility: 'visible'
79
+ };
80
+ var labelStyle = {
81
+ color: 'white',
82
+ fontSize: '0.7rem',
83
+ fontWeight: 600,
84
+ lineHeight: '1'
85
+ };
86
+ var labelStyleWhileActive = {
87
+ visibility: 'hidden'
88
+ };
47
89
 
48
90
  var round = num => Math.round(num * 100) / 100;
49
91
 
50
92
  function Spot(_ref) {
51
93
  var spot = _ref.spot,
52
- _ref$bounds = _ref.bounds,
53
- bounds = _ref$bounds === void 0 ? undefined : _ref$bounds,
94
+ bounds = _ref.bounds,
54
95
  update = _ref.update,
55
- _ref$hotspotDescripti = _ref.hotspotDescriptionPath,
56
- hotspotDescriptionPath = _ref$hotspotDescripti === void 0 ? "" : _ref$hotspotDescripti,
57
- tooltip = _ref.tooltip;
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;
96
+ hotspotDescriptionPath = _ref.hotspotDescriptionPath,
97
+ tooltip = _ref.tooltip,
98
+ index = _ref.index;
85
99
 
86
- var clientRect = bounds === null || bounds === void 0 ? void 0 : (_bounds$current = bounds.current) === null || _bounds$current === void 0 ? void 0 : _bounds$current.getBoundingClientRect();
87
-
88
- if (clientRect) {
89
- // So convert % to px once we know the height/width of the image
90
- setXY({
91
- x: round(clientRect.width * (spot.x / 100)),
92
- y: round(clientRect.height * (spot.y / 100))
93
- });
100
+ var _React$useState = _react.default.useState(false),
101
+ _React$useState2 = _slicedToArray(_React$useState, 2),
102
+ isDragging = _React$useState2[0],
103
+ setIsDragging = _React$useState2[1];
94
104
 
95
- if (!rect.width || !rect.height) {
96
- setRect(clientRect);
97
- }
98
- }
99
- }, [bounds, bounds.current]);
105
+ var _React$useState3 = _react.default.useState(false),
106
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
107
+ isHovering = _React$useState4[0],
108
+ setIsHovering = _React$useState4[1]; // x/y are stored as % but need to be converted to px
100
109
 
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
110
 
105
- var _event$srcElement$sty = event.srcElement.style.transform.split(" ").map(v => {
106
- return v ? v.match(/\(([^)]+)\)/).pop().replace("px", "") : null;
107
- }),
108
- _event$srcElement$sty2 = _slicedToArray(_event$srcElement$sty, 2),
109
- currentX = _event$srcElement$sty2[0],
110
- currentY = _event$srcElement$sty2[1];
111
+ var x = (0, _framerMotion.useMotionValue)(round(bounds.width * (spot.x / 100)));
112
+ var y = (0, _framerMotion.useMotionValue)(round(bounds.height * (spot.y / 100)));
113
+ /**
114
+ * update x/y if the bounds change when resizing the window
115
+ */
111
116
 
112
- if (!Number(currentX) || !Number(currentY)) {
113
- return console.warn("Missing or non-number X or Y", {
114
- currentX,
115
- currentY
116
- }, event.srcElement);
117
- }
117
+ (0, _react.useEffect)(() => {
118
+ x.set(round(bounds.width * (spot.x / 100)));
119
+ y.set(round(bounds.height * (spot.y / 100)));
120
+ }, [bounds]);
118
121
 
119
- if (!rect.width || !rect.height) {
120
- return console.warn("Rect width/height not yet set", {
121
- rect
122
- }, bounds === null || bounds === void 0 ? void 0 : bounds.current);
123
- } // Which we need to convert back to `%` to patch the document
122
+ var handleDragEnd = _react.default.useCallback(() => {
123
+ setIsDragging(false); // get current values for x/y in px
124
124
 
125
+ var currentX = x.get();
126
+ var currentY = y.get(); // Which we need to convert back to `%` to patch the document
125
127
 
126
- var newX = round(currentX * 100 / rect.width);
127
- var newY = round(currentY * 100 / rect.height); // Don't go below 0 or above 100
128
+ var newX = round(currentX * 100 / bounds.width);
129
+ var newY = round(currentY * 100 / bounds.height); // Don't go below 0 or above 100
128
130
 
129
131
  var safeX = Math.max(0, Math.min(100, newX));
130
132
  var safeY = Math.max(0, Math.min(100, newY));
131
133
  update(spot._key, safeX, safeY);
132
134
  }, [spot]);
133
135
 
136
+ var handleDragStart = _react.default.useCallback(() => setIsDragging(true), []);
137
+
138
+ var handleHoverStart = _react.default.useCallback(() => setIsHovering(true), []);
139
+
140
+ var handleHoverEnd = _react.default.useCallback(() => setIsHovering(false), []);
141
+
134
142
  if (!x || !y) {
135
143
  return null;
136
144
  }
@@ -138,7 +146,6 @@ function Spot(_ref) {
138
146
  return /*#__PURE__*/_react.default.createElement(_ui.Tooltip, {
139
147
  key: spot._key,
140
148
  disabled: isDragging,
141
- boundaryElement: bounds.current,
142
149
  portal: true,
143
150
  content: tooltip && typeof tooltip === 'function' ? /*#__PURE__*/_react.default.createElement(tooltip, {
144
151
  spot
@@ -154,18 +161,20 @@ function Spot(_ref) {
154
161
  }, /*#__PURE__*/_react.default.createElement(_framerMotion.motion.div, {
155
162
  drag: true,
156
163
  dragConstraints: bounds,
164
+ dragElastic: 0,
157
165
  dragMomentum: false,
158
- initial: {
166
+ onDragEnd: handleDragEnd,
167
+ onDragStart: handleDragStart,
168
+ onHoverStart: handleHoverStart,
169
+ onHoverEnd: handleHoverEnd,
170
+ style: _objectSpread(_objectSpread(_objectSpread({}, dragStyle), {}, {
159
171
  x,
160
172
  y
161
- },
162
- onDragEnd: handleDragEnd,
163
- onDragStart: () => setIsDragging(true),
164
- style: dragStyle
165
- }, /*#__PURE__*/_react.default.createElement(_ui.Card, {
166
- tone: "primary",
167
- shadow: 3,
168
- style: dotStyle
169
- })));
173
+ }, isDragging && _objectSpread({}, dragStyleWhileDrag)), isHovering && _objectSpread({}, dragStyleWhileHover))
174
+ }, /*#__PURE__*/_react.default.createElement(_ui.Box, {
175
+ style: _objectSpread(_objectSpread({}, dotStyle), (isDragging || isHovering) && _objectSpread({}, dotStyleWhileActive))
176
+ }), /*#__PURE__*/_react.default.createElement("div", {
177
+ style: _objectSpread(_objectSpread({}, labelStyle), (isDragging || isHovering) && _objectSpread({}, labelStyleWhileActive))
178
+ }, index + 1)));
170
179
  }
171
180
  //# sourceMappingURL=Spot.js.map
package/lib/Spot.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Spot.tsx"],"names":["dragStyle","width","height","position","dotStyle","borderRadius","pointerEvents","round","num","Math","Spot","spot","bounds","undefined","update","hotspotDescriptionPath","tooltip","React","useState","x","y","setXY","current","getBoundingClientRect","rect","setRect","isDragging","setIsDragging","useEffect","clientRect","handleDragEnd","useCallback","event","srcElement","style","transform","split","map","v","match","pop","replace","currentX","currentY","Number","console","warn","newX","newY","safeX","max","min","safeY","_key","createElement","maxWidth"],"mappings":"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","sourcesContent":["import React from 'react'\nimport {Box, Card, Text, Tooltip} from '@sanity/ui'\nimport {motion} from 'framer-motion'\nimport get from 'lodash/get'\n\nconst dragStyle = {\n width: `1rem`,\n height: `1rem`,\n position: `absolute`,\n}\n\nconst dotStyle = {\n ...dragStyle,\n borderRadius: `50%`,\n // make sure pointer events only run on the parent\n pointerEvents: `none`,\n}\n\nconst round = (num) => Math.round(num * 100) / 100\n\nexport default function Spot({spot, bounds = undefined, update, hotspotDescriptionPath = ``, tooltip}) {\n // x/y are stored as % but need to be displayed as px\n const [{x, y}, setXY] = React.useState({x: 0, y: 0})\n const [rect, setRect] = React.useState(bounds?.current ? bounds.current.getBoundingClientRect() : {width: 0, height:0})\n const [isDragging, setIsDragging] = React.useState(false)\n\n React.useEffect(() => {\n const clientRect = bounds?.current?.getBoundingClientRect()\n\n if (clientRect) {\n // So convert % to px once we know the height/width of the image\n setXY({\n x: round(clientRect.width * (spot.x / 100)),\n y: round(clientRect.height * (spot.y / 100)),\n })\n\n if (!rect.width || !rect.height) {\n setRect(clientRect)\n }\n }\n }, [bounds, bounds.current])\n\n const handleDragEnd = React.useCallback(\n (event) => {\n setIsDragging(false)\n\n // I don't know why, but framer-motion doesn't give you the actual transform values\n // So we have to regex the `px` values off the inline styles\n const [currentX, currentY] = event.srcElement.style.transform.split(` `).map((v) => {\n return v\n ? v\n .match(/\\(([^)]+)\\)/)\n .pop()\n .replace(`px`, ``)\n : null\n })\n\n if (!Number(currentX) || !Number(currentY)) {\n return console.warn(`Missing or non-number X or Y`, {currentX, currentY}, event.srcElement)\n }\n\n if (!rect.width || !rect.height) {\n return console.warn(`Rect width/height not yet set`, {rect}, bounds?.current)\n }\n\n // Which we need to convert back to `%` to patch the document\n const newX = round((currentX * 100) / rect.width)\n const newY = round((currentY * 100) / rect.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 },\n\n [spot]\n )\n\n if (!x || !y) {\n return null\n }\n\n return (\n <Tooltip\n key={spot._key}\n disabled={isDragging}\n boundaryElement={bounds.current}\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\">{hotspotDescriptionPath ? get(spot, hotspotDescriptionPath) : `${spot.x}% x ${spot.y}%`}</Text>\n </Box>\n )\n }\n >\n <motion.div\n drag\n dragConstraints={bounds}\n dragMomentum={false}\n initial={{x, y}}\n onDragEnd={handleDragEnd}\n onDragStart={() => setIsDragging(true)}\n style={dragStyle}\n >\n <Card tone=\"primary\" shadow={3} style={dotStyle} />\n </motion.div>\n </Tooltip>\n )\n}"],"file":"Spot.js"}
1
+ {"version":3,"file":"Spot.js","names":["dragStyle","width","height","position","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 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,"sources":["../src/useUnsetInputComponent.ts"],"names":["useUnsetInputComponent","type","component","React","useMemo","unsetInputComponent","t","inputComponent","undefined","typeOfType"],"mappings":";;;;;;;AAAA;;;;;;;;;;AAEO,SAASA,sBAAT,CAAgCC,IAAhC,EAAsCC,SAAtC,EAAiD;AACpD,SAAOC,eAAMC,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;AAC5C,MAAMI,CAAC,mCACFL,IADE;AAELM,IAAAA,cAAc,EAAEN,IAAI,CAACM,cAAL,KAAwBL,SAAxB,GAAoCM,SAApC,GAAgDP,IAAI,CAACM;AAFhE,IAAP;;AAIA,MAAME,UAAU,GAAGH,CAAC,CAACL,IAAF,GAASI,mBAAmB,CAACC,CAAC,CAACL,IAAH,EAASC,SAAT,CAA5B,GAAkDM,SAArE;AACA,yCACKF,CADL;AAEEL,IAAAA,IAAI,EAAEQ;AAFR;AAID","sourcesContent":["import React from 'react'\n\nexport function useUnsetInputComponent(type, component) {\n return React.useMemo(() => unsetInputComponent(type, component), [type, component])\n }\n \n function 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 }"],"file":"useUnsetInputComponent.js"}
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.4",
3
+ "version": "0.0.7",
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.23.2",
33
- "@sanity/form-builder": "^2.23.2",
34
- "@sanity/ui": "^0.36.17",
35
- "framer-motion": "^5.5.5",
36
- "husky": "^7.0.1"
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": "^7.32.0",
40
- "eslint-config-prettier": "^8.3.0",
41
- "eslint-config-sanity": "^5.1.0",
42
- "eslint-plugin-react": "^7.27.1",
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.5.1",
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
- "lodash": "4.17.21"
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"
@@ -5,40 +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 React from 'react'
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 {randomKey} from '@sanity/util/content'
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 {getImageDimensions} from '@sanity/asset-utils'
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 Spot from './Spot'
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
- const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)
22
- const urlFor = (source) => builder.image(source)
23
22
  const imageStyle = {width: `100%`, height: `auto`}
24
23
 
24
+ const VALID_ROOT_PATHS = ['document', 'parent']
25
+
26
+ export type FnHotspotMove = (key: string, x: number, y: number) => void
25
27
 
26
- const HotspotArray = React.forwardRef((props, ref) => {
27
- const {type, value, onChange, document: sanityDocument} = props
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
28
37
  const {options} = type ?? {}
29
38
 
30
39
  // Attempt prevention of infinite loop in <FormBuilderInput />
31
40
  // Re-renders can still occur if this Component is used again in a nested field
32
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])
33
56
 
34
- // Finding the image from the document,
35
- // using the path from the hotspot's `options` field
36
57
  const displayImage = React.useMemo(() => {
37
- const hotspotImage = get(sanityDocument, options?.hotspotImagePath)
58
+ const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)
59
+ const urlFor = (source) => builder.image(source)
38
60
 
39
61
  if (hotspotImage?.asset?._ref) {
40
62
  const {aspectRatio} = getImageDimensions(hotspotImage.asset._ref)
41
- const width = 600
63
+ const width = 1200
42
64
  const height = Math.round(width / aspectRatio)
43
65
  const url = urlFor(hotspotImage).width(width).url()
44
66
 
@@ -46,7 +68,7 @@ const HotspotArray = React.forwardRef((props, ref) => {
46
68
  }
47
69
 
48
70
  return null
49
- }, [sanityDocument, type])
71
+ }, [hotspotImage])
50
72
 
51
73
  const handleHotspotImageClick = React.useCallback((event) => {
52
74
  const {nativeEvent} = event
@@ -56,7 +78,7 @@ const HotspotArray = React.forwardRef((props, ref) => {
56
78
  const y = Number(((nativeEvent.offsetY * 100) / nativeEvent.srcElement.height).toFixed(2))
57
79
  const description = `New Hotspot at ${x}% x ${y}%`
58
80
 
59
- const newRow = {
81
+ const newRow: TSpot = {
60
82
  _key: randomKey(12),
61
83
  _type: `spot`,
62
84
  x,
@@ -70,13 +92,8 @@ const HotspotArray = React.forwardRef((props, ref) => {
70
92
  onChange(PatchEvent.from(setIfMissing([]), insert([newRow], 'after', [-1])))
71
93
  }, [])
72
94
 
73
- const handleHotspotMove = React.useCallback(
95
+ const handleHotspotMove: FnHotspotMove = React.useCallback(
74
96
  (key, x, y) => {
75
- if (!Number(x) || !Number(y)) {
76
- console.warn(`Missing or non-number X or Y`, {x, y})
77
- return
78
- }
79
-
80
97
  onChange(
81
98
  PatchEvent.from(
82
99
  // Set the `x` value of this array key item
@@ -89,18 +106,28 @@ const HotspotArray = React.forwardRef((props, ref) => {
89
106
  [value]
90
107
  )
91
108
 
92
- 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)
93
118
 
94
119
  return (
95
- <Stack space={[2,2,3]}>
120
+ <Stack space={[2, 2, 3]}>
96
121
  {displayImage?.url ? (
97
122
  <div style={{position: `relative`}}>
98
- {value?.length > 0 &&
99
- value.map((spot) => (
123
+ {imageRect &&
124
+ value?.length > 0 &&
125
+ value.map((spot, index) => (
100
126
  <Spot
127
+ index={index}
101
128
  key={spot._key}
102
129
  spot={spot}
103
- bounds={hotspotImageRef}
130
+ bounds={imageRect}
104
131
  update={handleHotspotMove}
105
132
  hotspotDescriptionPath={options?.hotspotDescriptionPath}
106
133
  tooltip={options?.hotspotTooltip}
@@ -117,18 +144,31 @@ const HotspotArray = React.forwardRef((props, ref) => {
117
144
  alt=""
118
145
  style={imageStyle}
119
146
  onClick={handleHotspotImageClick}
120
- />
147
+ />
121
148
  </Flex>
122
149
  </Card>
123
150
  </div>
124
151
  ) : (
125
152
  <Feedback>
126
- {type?.options?.hotspotImagePath
127
- ? <>No Hotspot image found at path <code>{type?.options?.hotspotImagePath}</code></>
128
- : <>Define a path in this field using to the image field in this document at <code>options.hotspotImagePath</code></>
129
- }
130
- </Feedback>
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>
131
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
+ )}
132
172
  <FormBuilderInput {...props} type={typeWithoutInputComponent} ref={ref} />
133
173
  </Stack>
134
174
  )
package/src/Spot.tsx CHANGED
@@ -1,81 +1,120 @@
1
- import React from 'react'
2
- import {Box, Card, Text, Tooltip} from '@sanity/ui'
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
+ top: 0,
12
+ left: 0,
13
+ margin: '-0.7rem 0 0 -0.7rem',
14
+ cursor: 'pointer',
15
+ display: 'flex',
16
+ justifyContent: 'center',
17
+ alignItems: 'center',
18
+ borderRadius: '50%',
19
+ background: '#000',
20
+ color: 'white',
21
+ }
22
+
23
+ const dragStyleWhileDrag: CSSProperties = {
24
+ background: 'rgba(0, 0, 0, 0.1)',
25
+ border: '1px solid #fff',
26
+ cursor: 'none',
27
+ }
5
28
 
6
- const dragStyle = {
7
- width: `1rem`,
8
- height: `1rem`,
9
- position: `absolute`,
29
+ const dragStyleWhileHover: CSSProperties = {
30
+ background: 'rgba(0, 0, 0, 0.1)',
31
+ border: '1px solid #fff',
10
32
  }
11
33
 
12
- const dotStyle = {
13
- ...dragStyle,
14
- borderRadius: `50%`,
34
+ const dotStyle: CSSProperties = {
35
+ position: 'absolute',
36
+ left: '50%',
37
+ top: '50%',
38
+ height: '0.2rem',
39
+ width: '0.2rem',
40
+ margin: '-0.1rem 0 0 -0.1rem',
41
+ background: '#fff',
42
+ visibility: 'hidden',
43
+ borderRadius: '50%',
15
44
  // make sure pointer events only run on the parent
16
- pointerEvents: `none`,
45
+ pointerEvents: 'none',
46
+ }
47
+
48
+ const dotStyleWhileActive: CSSProperties = {
49
+ visibility: 'visible',
50
+ }
51
+
52
+ const labelStyle: CSSProperties = {
53
+ color: 'white',
54
+ fontSize: '0.7rem',
55
+ fontWeight: 600,
56
+ lineHeight: '1',
57
+ }
58
+
59
+ const labelStyleWhileActive: CSSProperties = {
60
+ visibility: 'hidden',
17
61
  }
18
62
 
19
63
  const round = (num) => Math.round(num * 100) / 100
20
64
 
21
- export default function Spot({spot, bounds = undefined, update, hotspotDescriptionPath = ``, tooltip}) {
22
- // x/y are stored as % but need to be displayed as px
23
- const [{x, y}, setXY] = React.useState({x: 0, y: 0})
24
- const [rect, setRect] = React.useState(bounds?.current ? bounds.current.getBoundingClientRect() : {width: 0, height:0})
65
+ interface IHotspot {
66
+ spot: TSpot
67
+ bounds: DOMRectReadOnly
68
+ update: FnHotspotMove
69
+ hotspotDescriptionPath?: string
70
+ tooltip?: ReactElement
71
+ index: number
72
+ }
73
+
74
+ export default function Spot({
75
+ spot,
76
+ bounds,
77
+ update,
78
+ hotspotDescriptionPath,
79
+ tooltip,
80
+ index,
81
+ }: IHotspot) {
25
82
  const [isDragging, setIsDragging] = React.useState(false)
83
+ const [isHovering, setIsHovering] = React.useState(false)
26
84
 
27
- React.useEffect(() => {
28
- const clientRect = bounds?.current?.getBoundingClientRect()
85
+ // x/y are stored as % but need to be converted to px
86
+ const x = useMotionValue(round(bounds.width * (spot.x / 100)))
87
+ const y = useMotionValue(round(bounds.height * (spot.y / 100)))
29
88
 
30
- if (clientRect) {
31
- // So convert % to px once we know the height/width of the image
32
- setXY({
33
- x: round(clientRect.width * (spot.x / 100)),
34
- y: round(clientRect.height * (spot.y / 100)),
35
- })
89
+ /**
90
+ * update x/y if the bounds change when resizing the window
91
+ */
92
+ useEffect(() => {
93
+ x.set(round(bounds.width * (spot.x / 100)))
94
+ y.set(round(bounds.height * (spot.y / 100)))
95
+ }, [bounds])
36
96
 
37
- if (!rect.width || !rect.height) {
38
- setRect(clientRect)
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
- }
97
+ const handleDragEnd = React.useCallback(() => {
98
+ setIsDragging(false)
61
99
 
62
- if (!rect.width || !rect.height) {
63
- return console.warn(`Rect width/height not yet set`, {rect}, bounds?.current)
64
- }
100
+ // get current values for x/y in px
101
+ const currentX = x.get()
102
+ const currentY = y.get()
65
103
 
66
- // Which we need to convert back to `%` to patch the document
67
- const newX = round((currentX * 100) / rect.width)
68
- const newY = round((currentY * 100) / rect.height)
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))
104
+ // Which we need to convert back to `%` to patch the document
105
+ const newX = round((currentX * 100) / bounds.width)
106
+ const newY = round((currentY * 100) / bounds.height)
73
107
 
74
- update(spot._key, safeX, safeY)
75
- },
108
+ // Don't go below 0 or above 100
109
+ const safeX = Math.max(0, Math.min(100, newX))
110
+ const safeY = Math.max(0, Math.min(100, newY))
76
111
 
77
- [spot]
78
- )
112
+ update(spot._key, safeX, safeY)
113
+ }, [spot])
114
+ const handleDragStart = React.useCallback(() => setIsDragging(true), [])
115
+
116
+ const handleHoverStart = React.useCallback(() => setIsHovering(true), [])
117
+ const handleHoverEnd = React.useCallback(() => setIsHovering(false), [])
79
118
 
80
119
  if (!x || !y) {
81
120
  return null
@@ -85,14 +124,17 @@ export default function Spot({spot, bounds = undefined, update, hotspotDescripti
85
124
  <Tooltip
86
125
  key={spot._key}
87
126
  disabled={isDragging}
88
- boundaryElement={bounds.current}
89
127
  portal
90
128
  content={
91
129
  tooltip && typeof tooltip === 'function' ? (
92
130
  React.createElement(tooltip, {spot})
93
131
  ) : (
94
132
  <Box padding={2} style={{maxWidth: 200, pointerEvents: `none`}}>
95
- <Text textOverflow="ellipsis">{hotspotDescriptionPath ? get(spot, hotspotDescriptionPath) : `${spot.x}% x ${spot.y}%`}</Text>
133
+ <Text textOverflow="ellipsis">
134
+ {hotspotDescriptionPath
135
+ ? (get(spot, hotspotDescriptionPath) as string)
136
+ : `${spot.x}% x ${spot.y}%`}
137
+ </Text>
96
138
  </Box>
97
139
  )
98
140
  }
@@ -100,14 +142,37 @@ export default function Spot({spot, bounds = undefined, update, hotspotDescripti
100
142
  <motion.div
101
143
  drag
102
144
  dragConstraints={bounds}
145
+ dragElastic={0}
103
146
  dragMomentum={false}
104
- initial={{x, y}}
105
147
  onDragEnd={handleDragEnd}
106
- onDragStart={() => setIsDragging(true)}
107
- style={dragStyle}
148
+ onDragStart={handleDragStart}
149
+ onHoverStart={handleHoverStart}
150
+ onHoverEnd={handleHoverEnd}
151
+ style={{
152
+ ...dragStyle,
153
+ x,
154
+ y,
155
+ ...(isDragging && {...dragStyleWhileDrag}),
156
+ ...(isHovering && {...dragStyleWhileHover}),
157
+ }}
108
158
  >
109
- <Card tone="primary" shadow={3} style={dotStyle} />
159
+ {/* Dot */}
160
+ <Box
161
+ style={{
162
+ ...dotStyle,
163
+ ...((isDragging || isHovering) && {...dotStyleWhileActive}),
164
+ }}
165
+ />
166
+ {/* Label */}
167
+ <div
168
+ style={{
169
+ ...labelStyle,
170
+ ...((isDragging || isHovering) && {...labelStyleWhileActive}),
171
+ }}
172
+ >
173
+ {index + 1}
174
+ </div>
110
175
  </motion.div>
111
176
  </Tooltip>
112
177
  )
113
- }
178
+ }
@@ -1,17 +1,17 @@
1
1
  import React from 'react'
2
2
 
3
3
  export function useUnsetInputComponent(type, component) {
4
- return React.useMemo(() => unsetInputComponent(type, component), [type, component])
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
+ }