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 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,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 _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); }
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
- sanityDocument = props.document;
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); // Finding the image from the document,
57
- // 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]);
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 = 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":["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: "1rem",
38
- height: "1rem",
39
- position: "absolute"
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
- var dotStyle = _objectSpread(_objectSpread({}, dragStyle), {}, {
43
- borderRadius: "50%",
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: "none"
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
- _ref$bounds = _ref.bounds,
53
- bounds = _ref$bounds === void 0 ? undefined : _ref$bounds,
95
+ bounds = _ref.bounds,
54
96
  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;
97
+ hotspotDescriptionPath = _ref.hotspotDescriptionPath,
98
+ tooltip = _ref.tooltip,
99
+ index = _ref.index;
85
100
 
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
- });
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
- if (!rect.width || !rect.height) {
96
- setRect(clientRect);
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
- 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];
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
- if (!Number(currentX) || !Number(currentY)) {
113
- return console.warn("Missing or non-number X or Y", {
114
- currentX,
115
- currentY
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
- 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
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 / rect.width);
127
- var newY = round(currentY * 100 / rect.height); // Don't go below 0 or above 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
- initial: {
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
- 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
- })));
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,"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":";;;;;;;AAAA;;AACA;;AACA;;AACA;;;;;;;;;;;;;;;;;;;;;;AAEA,IAAMA,SAAS,GAAG;AAChBC,EAAAA,KAAK,QADW;AAEhBC,EAAAA,MAAM,QAFU;AAGhBC,EAAAA,QAAQ;AAHQ,CAAlB;;AAMA,IAAMC,QAAQ,mCACTJ,SADS;AAEZK,EAAAA,YAAY,OAFA;AAGZ;AACAC,EAAAA,aAAa;AAJD,EAAd;;AAOA,IAAMC,KAAK,GAAIC,GAAD,IAASC,IAAI,CAACF,KAAL,CAAWC,GAAG,GAAG,GAAjB,IAAwB,GAA/C;;AAEe,SAASE,IAAT,OAAwF;AAAA,MAAzEC,IAAyE,QAAzEA,IAAyE;AAAA,yBAAnEC,MAAmE;AAAA,MAAnEA,MAAmE,4BAA1DC,SAA0D;AAAA,MAA/CC,MAA+C,QAA/CA,MAA+C;AAAA,mCAAvCC,sBAAuC;AAAA,MAAvCA,sBAAuC;AAAA,MAAVC,OAAU,QAAVA,OAAU;;AACrG;AACA,wBAAwBC,eAAMC,QAAN,CAAe;AAACC,IAAAA,CAAC,EAAE,CAAJ;AAAOC,IAAAA,CAAC,EAAE;AAAV,GAAf,CAAxB;AAAA;AAAA;AAAA,MAAQD,CAAR,qBAAQA,CAAR;AAAA,MAAWC,CAAX,qBAAWA,CAAX;AAAA,MAAeC,KAAf;;AACA,yBAAwBJ,eAAMC,QAAN,CAAeN,MAAM,SAAN,IAAAA,MAAM,WAAN,IAAAA,MAAM,CAAEU,OAAR,GAAkBV,MAAM,CAACU,OAAP,CAAeC,qBAAf,EAAlB,GAA2D;AAACtB,IAAAA,KAAK,EAAE,CAAR;AAAWC,IAAAA,MAAM,EAAC;AAAlB,GAA1E,CAAxB;AAAA;AAAA,MAAOsB,IAAP;AAAA,MAAaC,OAAb;;AACA,yBAAoCR,eAAMC,QAAN,CAAe,KAAf,CAApC;AAAA;AAAA,MAAOQ,UAAP;AAAA,MAAmBC,aAAnB;;AAEAV,iBAAMW,SAAN,CAAgB,MAAM;AAAA;;AACpB,QAAMC,UAAU,GAAGjB,MAAH,aAAGA,MAAH,0CAAGA,MAAM,CAAEU,OAAX,oDAAG,gBAAiBC,qBAAjB,EAAnB;;AAEA,QAAIM,UAAJ,EAAgB;AACd;AACAR,MAAAA,KAAK,CAAC;AACJF,QAAAA,CAAC,EAAEZ,KAAK,CAACsB,UAAU,CAAC5B,KAAX,IAAoBU,IAAI,CAACQ,CAAL,GAAS,GAA7B,CAAD,CADJ;AAEJC,QAAAA,CAAC,EAAEb,KAAK,CAACsB,UAAU,CAAC3B,MAAX,IAAqBS,IAAI,CAACS,CAAL,GAAS,GAA9B,CAAD;AAFJ,OAAD,CAAL;;AAKA,UAAI,CAACI,IAAI,CAACvB,KAAN,IAAe,CAACuB,IAAI,CAACtB,MAAzB,EAAiC;AAC/BuB,QAAAA,OAAO,CAACI,UAAD,CAAP;AACD;AACF;AACF,GAdD,EAcG,CAACjB,MAAD,EAASA,MAAM,CAACU,OAAhB,CAdH;;AAgBA,MAAMQ,aAAa,GAAGb,eAAMc,WAAN,CACnBC,KAAD,IAAW;AACTL,IAAAA,aAAa,CAAC,KAAD,CAAb,CADS,CAGT;AACA;;AACA,gCAA6BK,KAAK,CAACC,UAAN,CAAiBC,KAAjB,CAAuBC,SAAvB,CAAiCC,KAAjC,MAA4CC,GAA5C,CAAiDC,CAAD,IAAO;AAClF,aAAOA,CAAC,GACJA,CAAC,CACEC,KADH,CACS,aADT,EAEGC,GAFH,GAGGC,OAHH,UADI,GAKJ,IALJ;AAMD,KAP4B,CAA7B;AAAA;AAAA,QAAOC,QAAP;AAAA,QAAiBC,QAAjB;;AASA,QAAI,CAACC,MAAM,CAACF,QAAD,CAAP,IAAqB,CAACE,MAAM,CAACD,QAAD,CAAhC,EAA4C;AAC1C,aAAOE,OAAO,CAACC,IAAR,iCAA6C;AAACJ,QAAAA,QAAD;AAAWC,QAAAA;AAAX,OAA7C,EAAmEX,KAAK,CAACC,UAAzE,CAAP;AACD;;AAED,QAAI,CAACT,IAAI,CAACvB,KAAN,IAAe,CAACuB,IAAI,CAACtB,MAAzB,EAAiC;AAC/B,aAAO2C,OAAO,CAACC,IAAR,kCAA8C;AAACtB,QAAAA;AAAD,OAA9C,EAAsDZ,MAAtD,aAAsDA,MAAtD,uBAAsDA,MAAM,CAAEU,OAA9D,CAAP;AACD,KApBQ,CAsBT;;;AACA,QAAMyB,IAAI,GAAGxC,KAAK,CAAEmC,QAAQ,GAAG,GAAZ,GAAmBlB,IAAI,CAACvB,KAAzB,CAAlB;AACA,QAAM+C,IAAI,GAAGzC,KAAK,CAAEoC,QAAQ,GAAG,GAAZ,GAAmBnB,IAAI,CAACtB,MAAzB,CAAlB,CAxBS,CA0BT;;AACA,QAAM+C,KAAK,GAAGxC,IAAI,CAACyC,GAAL,CAAS,CAAT,EAAYzC,IAAI,CAAC0C,GAAL,CAAS,GAAT,EAAcJ,IAAd,CAAZ,CAAd;AACA,QAAMK,KAAK,GAAG3C,IAAI,CAACyC,GAAL,CAAS,CAAT,EAAYzC,IAAI,CAAC0C,GAAL,CAAS,GAAT,EAAcH,IAAd,CAAZ,CAAd;AAEAlC,IAAAA,MAAM,CAACH,IAAI,CAAC0C,IAAN,EAAYJ,KAAZ,EAAmBG,KAAnB,CAAN;AACD,GAhCmB,EAkCpB,CAACzC,IAAD,CAlCoB,CAAtB;;AAqCA,MAAI,CAACQ,CAAD,IAAM,CAACC,CAAX,EAAc;AACZ,WAAO,IAAP;AACD;;AAED,sBACE,6BAAC,WAAD;AACE,IAAA,GAAG,EAAET,IAAI,CAAC0C,IADZ;AAEE,IAAA,QAAQ,EAAE3B,UAFZ;AAGE,IAAA,eAAe,EAAEd,MAAM,CAACU,OAH1B;AAIE,IAAA,MAAM,MAJR;AAKE,IAAA,OAAO,EACLN,OAAO,IAAI,OAAOA,OAAP,KAAmB,UAA9B,gBACEC,eAAMqC,aAAN,CAAoBtC,OAApB,EAA6B;AAACL,MAAAA;AAAD,KAA7B,CADF,gBAGE,6BAAC,OAAD;AAAK,MAAA,OAAO,EAAE,CAAd;AAAiB,MAAA,KAAK,EAAE;AAAC4C,QAAAA,QAAQ,EAAE,GAAX;AAAgBjD,QAAAA,aAAa;AAA7B;AAAxB,oBACE,6BAAC,QAAD;AAAM,MAAA,YAAY,EAAC;AAAnB,OAA+BS,sBAAsB,GAAG,kBAAIJ,IAAJ,EAAUI,sBAAV,CAAH,aAA0CJ,IAAI,CAACQ,CAA/C,iBAAuDR,IAAI,CAACS,CAA5D,MAArD,CADF;AATN,kBAeE,6BAAC,oBAAD,CAAQ,GAAR;AACE,IAAA,IAAI,MADN;AAEE,IAAA,eAAe,EAAER,MAFnB;AAGE,IAAA,YAAY,EAAE,KAHhB;AAIE,IAAA,OAAO,EAAE;AAACO,MAAAA,CAAD;AAAIC,MAAAA;AAAJ,KAJX;AAKE,IAAA,SAAS,EAAEU,aALb;AAME,IAAA,WAAW,EAAE,MAAMH,aAAa,CAAC,IAAD,CANlC;AAOE,IAAA,KAAK,EAAE3B;AAPT,kBASE,6BAAC,QAAD;AAAM,IAAA,IAAI,EAAC,SAAX;AAAqB,IAAA,MAAM,EAAE,CAA7B;AAAgC,IAAA,KAAK,EAAEI;AAAvC,IATF,CAfF,CADF;AA6BD","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","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,"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.5",
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.27.0",
33
- "@sanity/form-builder": "^2.27.0",
34
- "@sanity/ui": "^0.37.5",
35
- "framer-motion": "^6.2.6",
36
- "husky": "^7.0.4"
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,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 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
22
  const imageStyle = {width: `100%`, height: `auto`}
22
23
 
23
- const HotspotArray = React.forwardRef((props, ref) => {
24
- const {type, value, onChange, document: sanityDocument} = props
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 = 600
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
- }, [sanityDocument, type])
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
- {value?.length > 0 &&
98
- value.map((spot) => (
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={hotspotImageRef}
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
- {type?.options?.hotspotImagePath
126
- ? <>No Hotspot image found at path <code>{type?.options?.hotspotImagePath}</code></>
127
- : <>Define a path in this field using to the image field in this document at <code>options.hotspotImagePath</code></>
128
- }
129
- </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>
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 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
+ 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 dragStyle = {
7
- width: `1rem`,
8
- height: `1rem`,
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
- ...dragStyle,
14
- borderRadius: `50%`,
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: `none`,
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
- 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})
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
- React.useEffect(() => {
28
- const clientRect = bounds?.current?.getBoundingClientRect()
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
- 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
- })
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
- 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
- }
98
+ const handleDragEnd = React.useCallback(() => {
99
+ setIsDragging(false)
61
100
 
62
- if (!rect.width || !rect.height) {
63
- return console.warn(`Rect width/height not yet set`, {rect}, bounds?.current)
64
- }
101
+ // get current values for x/y in px
102
+ const currentX = x.get()
103
+ const currentY = y.get()
65
104
 
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))
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
- update(spot._key, safeX, safeY)
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
- [spot]
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">{hotspotDescriptionPath ? get(spot, hotspotDescriptionPath) : `${spot.x}% x ${spot.y}%`}</Text>
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={() => setIsDragging(true)}
107
- style={dragStyle}
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
- <Card tone="primary" shadow={3} style={dotStyle} />
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
- 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
+ }