sanity-plugin-hotspot-array 0.0.1 → 0.0.2

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
@@ -27,16 +27,18 @@ export default {
27
27
  type: `array`,
28
28
  inputComponent: HotspotArray,
29
29
  of: [
30
- // see `Spot object` setup below
30
+ // see `Spot object` setup below
31
31
  ],
32
32
  options: {
33
- // see `Image and description path` setup below
34
- hotspotImagePath: `featureImage`
35
- hotspotDescriptionPath: `details`
36
- }
37
- }
33
+ // see `Image and description path` setup below
34
+ hotspotImagePath: `featureImage`,
35
+ hotspotDescriptionPath: `details`,
36
+ // see `Custom tooltip` setup below
37
+ hotspotTooltip: undefined,
38
+ },
39
+ },
38
40
  // ...all your other fields
39
- ]
41
+ ],
40
42
  }
41
43
  ```
42
44
 
@@ -97,7 +99,7 @@ Here's an example object schema complete with initial values, validation, fields
97
99
  ],
98
100
  preview: {
99
101
  select: {
100
- title: 'description',
102
+ title: 'details',
101
103
  x: 'x',
102
104
  y: 'y',
103
105
  },
@@ -111,9 +113,25 @@ Here's an example object schema complete with initial values, validation, fields
111
113
  }
112
114
  ```
113
115
 
114
- ## Future cool ideas I just haven't added (yet?)
116
+ ## Custom tooltip
115
117
 
116
- - An `options` key to define a custom preview component for the Spot's `<Tooltip />`
118
+ You can customise the Tooltip to display any Component, it will accept a single prop `spot` which contains the values of the object. Example:
119
+
120
+ ```js
121
+ import HotspotArray from 'sanity-plugin-hotspot-array'
122
+ import ProductPreview from '../../components/ProductPreview'
123
+
124
+ // Setup a custom tooltip component
125
+ function ProductPreview({spot}) {
126
+ return <div>{JSON.stringify(spot)}</div>
127
+ }
128
+
129
+ // ...then add to your schema definition
130
+ options: {
131
+ hotspotImagePath: `hotspotImage`,
132
+ hotspotTooltip: ProductPreview,
133
+ },
134
+ ```
117
135
 
118
136
  ## License
119
137
 
@@ -45,21 +45,25 @@ var imageStyle = {
45
45
  };
46
46
 
47
47
  var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
48
- var _type$options4, _type$options5;
48
+ var _type$options, _type$options2;
49
49
 
50
50
  var type = props.type,
51
51
  value = props.value,
52
52
  onChange = props.onChange,
53
- sanityDocument = props.document; // Attempt prevention of infinite loop in <FormBuilderInput />
53
+ sanityDocument = props.document;
54
+
55
+ var _ref = type !== null && type !== void 0 ? type : {},
56
+ options = _ref.options; // Attempt prevention of infinite loop in <FormBuilderInput />
54
57
  // Re-renders can still occur if this Component is used again in a nested field
55
58
 
59
+
56
60
  var typeWithoutInputComponent = (0, _useUnsetInputComponent.useUnsetInputComponent)(type, type === null || type === void 0 ? void 0 : type.inputComponent); // Finding the image from the document,
57
61
  // using the path from the hotspot's `options` field
58
62
 
59
63
  var displayImage = _react.default.useMemo(() => {
60
- var _type$options, _hotspotImage$asset;
64
+ var _hotspotImage$asset;
61
65
 
62
- var hotspotImage = (0, _get.default)(sanityDocument, type === null || type === void 0 ? void 0 : (_type$options = type.options) === null || _type$options === void 0 ? void 0 : _type$options.hotspotImagePath);
66
+ var hotspotImage = (0, _get.default)(sanityDocument, options === null || options === void 0 ? void 0 : options.hotspotImagePath);
63
67
 
64
68
  if (hotspotImage !== null && hotspotImage !== void 0 && (_hotspotImage$asset = hotspotImage.asset) !== null && _hotspotImage$asset !== void 0 && _hotspotImage$asset._ref) {
65
69
  var _getImageDimensions = (0, _assetUtils.getImageDimensions)(hotspotImage.asset._ref),
@@ -79,8 +83,6 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
79
83
  }, [sanityDocument, type]);
80
84
 
81
85
  var handleHotspotImageClick = _react.default.useCallback(event => {
82
- var _type$options2;
83
-
84
86
  var nativeEvent = event.nativeEvent; // Calculate the x/y percentage of the click position
85
87
 
86
88
  var x = Number((nativeEvent.offsetX * 100 / nativeEvent.srcElement.width).toFixed(2));
@@ -93,9 +95,8 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
93
95
  y
94
96
  };
95
97
 
96
- if (type !== null && type !== void 0 && (_type$options2 = type.options) !== null && _type$options2 !== void 0 && _type$options2.hotspotDescriptionPath) {
97
- console.log(type.options.hotspotDescriptionPath);
98
- newRow[type.options.hotspotDescriptionPath] = description;
98
+ if (options !== null && options !== void 0 && options.hotspotDescriptionPath) {
99
+ newRow[options.hotspotDescriptionPath] = description;
99
100
  }
100
101
 
101
102
  onChange(_PatchEvent.PatchEvent.from((0, _PatchEvent.setIfMissing)([]), (0, _PatchEvent.insert)([newRow], 'after', [-1])));
@@ -127,17 +128,14 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
127
128
  style: {
128
129
  position: "relative"
129
130
  }
130
- }, (value === null || value === void 0 ? void 0 : value.length) > 0 && value.map(spot => {
131
- var _type$options3;
132
-
133
- return /*#__PURE__*/_react.default.createElement(_Spot.default, {
134
- key: spot._key,
135
- spot: spot,
136
- bounds: hotspotImageRef,
137
- update: handleHotspotMove,
138
- hotspotDescriptionPath: type === null || type === void 0 ? void 0 : (_type$options3 = type.options) === null || _type$options3 === void 0 ? void 0 : _type$options3.hotspotDescriptionPath
139
- });
140
- }), /*#__PURE__*/_react.default.createElement(_ui.Card, {
131
+ }, (value === null || value === void 0 ? void 0 : value.length) > 0 && value.map(spot => /*#__PURE__*/_react.default.createElement(_Spot.default, {
132
+ key: spot._key,
133
+ spot: spot,
134
+ bounds: hotspotImageRef,
135
+ update: handleHotspotMove,
136
+ hotspotDescriptionPath: options === null || options === void 0 ? void 0 : options.hotspotDescriptionPath,
137
+ tooltip: options === null || options === void 0 ? void 0 : options.hotspotTooltip
138
+ })), /*#__PURE__*/_react.default.createElement(_ui.Card, {
141
139
  __unstable_checkered: true,
142
140
  shadow: 1
143
141
  }, /*#__PURE__*/_react.default.createElement(_ui.Flex, {
@@ -151,7 +149,7 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
151
149
  alt: "",
152
150
  style: imageStyle,
153
151
  onClick: handleHotspotImageClick
154
- })))) : /*#__PURE__*/_react.default.createElement(_Feedback.default, null, type !== null && type !== void 0 && (_type$options4 = type.options) !== null && _type$options4 !== void 0 && _type$options4.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$options5 = type.options) === null || _type$options5 === void 0 ? void 0 : _type$options5.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, {
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, {
155
153
  type: typeWithoutInputComponent,
156
154
  ref: ref
157
155
  })));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/HotspotArray.tsx"],"names":["builder","sanityClient","urlFor","source","image","imageStyle","width","height","HotspotArray","React","forwardRef","props","ref","type","value","onChange","sanityDocument","document","typeWithoutInputComponent","inputComponent","displayImage","useMemo","hotspotImage","options","hotspotImagePath","asset","_ref","aspectRatio","Math","round","url","handleHotspotImageClick","useCallback","event","nativeEvent","x","Number","offsetX","srcElement","toFixed","y","offsetY","description","newRow","_key","_type","hotspotDescriptionPath","console","log","PatchEvent","from","handleHotspotMove","key","warn","hotspotImageRef","useRef","position","length","map","spot"],"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)\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\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, type?.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 (type?.options?.hotspotDescriptionPath) {\n console.log(type.options.hotspotDescriptionPath);\n newRow[type.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={type?.options?.hotspotDescriptionPath}\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,"sources":["../src/HotspotArray.tsx"],"names":["builder","sanityClient","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)\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"}
package/lib/Spot.js CHANGED
@@ -53,7 +53,8 @@ function Spot(_ref) {
53
53
  bounds = _ref$bounds === void 0 ? undefined : _ref$bounds,
54
54
  update = _ref.update,
55
55
  _ref$hotspotDescripti = _ref.hotspotDescriptionPath,
56
- hotspotDescriptionPath = _ref$hotspotDescripti === void 0 ? "" : _ref$hotspotDescripti;
56
+ hotspotDescriptionPath = _ref$hotspotDescripti === void 0 ? "" : _ref$hotspotDescripti,
57
+ tooltip = _ref.tooltip;
57
58
 
58
59
  // x/y are stored as % but need to be displayed as px
59
60
  var _React$useState = _react.default.useState({
@@ -130,7 +131,9 @@ function Spot(_ref) {
130
131
  disabled: isDragging,
131
132
  boundaryElement: bounds.current,
132
133
  portal: true,
133
- content: /*#__PURE__*/_react.default.createElement(_ui.Box, {
134
+ content: tooltip && typeof tooltip === 'function' ? /*#__PURE__*/_react.default.createElement(tooltip, {
135
+ spot
136
+ }) : /*#__PURE__*/_react.default.createElement(_ui.Box, {
134
137
  padding: 2,
135
138
  style: {
136
139
  maxWidth: 200,
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","React","useState","x","y","setXY","rect","setRect","isDragging","setIsDragging","useEffect","clientRect","current","getBoundingClientRect","handleDragEnd","useCallback","event","srcElement","style","transform","split","map","v","match","pop","replace","currentX","currentY","Number","console","warn","newX","newY","safeX","max","min","safeY","_key","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 = ``}) {\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({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 setRect(clientRect)\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 // 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 <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 <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,"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","rect","setRect","isDragging","setIsDragging","useEffect","clientRect","current","getBoundingClientRect","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({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 setRect(clientRect)\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 // 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"}
package/package.json CHANGED
@@ -1,13 +1,21 @@
1
1
  {
2
2
  "name": "sanity-plugin-hotspot-array",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
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": {
7
- "lint": "eslint .",
8
7
  "build": "sanipack build",
9
8
  "watch": "sanipack build --watch",
10
- "prepublishOnly": "sanipack build && sanipack verify"
9
+ "_postinstall": "husky install",
10
+ "prepublishOnly": "pinst --disable && sanipack build && sanipack verify",
11
+ "postpublish": "pinst --enable",
12
+ "lint": "eslint .",
13
+ "lint:fix": "eslint . --fix"
14
+ },
15
+ "husky": {
16
+ "hooks": {
17
+ "pre-commit": "npm run lint:fix"
18
+ }
11
19
  },
12
20
  "repository": {
13
21
  "type": "git",
@@ -25,13 +33,14 @@
25
33
  "@sanity/form-builder": "^2.23.2",
26
34
  "@sanity/ui": "^0.36.17",
27
35
  "framer-motion": "^5.5.5",
28
- "prop-types": "^15.7.2"
36
+ "husky": "^7.0.1"
29
37
  },
30
38
  "devDependencies": {
31
39
  "eslint": "^7.32.0",
32
40
  "eslint-config-prettier": "^8.3.0",
33
41
  "eslint-config-sanity": "^5.1.0",
34
42
  "eslint-plugin-react": "^7.27.1",
43
+ "pinst": "^2.1.6",
35
44
  "prettier": "^2.5.1",
36
45
  "sanipack": "^2.1.0"
37
46
  },
package/sanity.json CHANGED
@@ -4,4 +4,4 @@
4
4
  "compiled": "./lib"
5
5
  },
6
6
  "parts": []
7
- }
7
+ }
@@ -25,6 +25,7 @@ const imageStyle = {width: `100%`, height: `auto`}
25
25
 
26
26
  const HotspotArray = React.forwardRef((props, ref) => {
27
27
  const {type, value, onChange, document: sanityDocument} = props
28
+ const {options} = type ?? {}
28
29
 
29
30
  // Attempt prevention of infinite loop in <FormBuilderInput />
30
31
  // Re-renders can still occur if this Component is used again in a nested field
@@ -33,7 +34,7 @@ const HotspotArray = React.forwardRef((props, ref) => {
33
34
  // Finding the image from the document,
34
35
  // using the path from the hotspot's `options` field
35
36
  const displayImage = React.useMemo(() => {
36
- const hotspotImage = get(sanityDocument, type?.options?.hotspotImagePath)
37
+ const hotspotImage = get(sanityDocument, options?.hotspotImagePath)
37
38
 
38
39
  if (hotspotImage?.asset?._ref) {
39
40
  const {aspectRatio} = getImageDimensions(hotspotImage.asset._ref)
@@ -62,9 +63,8 @@ const HotspotArray = React.forwardRef((props, ref) => {
62
63
  y,
63
64
  }
64
65
 
65
- if (type?.options?.hotspotDescriptionPath) {
66
- console.log(type.options.hotspotDescriptionPath);
67
- newRow[type.options.hotspotDescriptionPath] = description
66
+ if (options?.hotspotDescriptionPath) {
67
+ newRow[options.hotspotDescriptionPath] = description
68
68
  }
69
69
 
70
70
  onChange(PatchEvent.from(setIfMissing([]), insert([newRow], 'after', [-1])))
@@ -102,7 +102,8 @@ const HotspotArray = React.forwardRef((props, ref) => {
102
102
  spot={spot}
103
103
  bounds={hotspotImageRef}
104
104
  update={handleHotspotMove}
105
- hotspotDescriptionPath={type?.options?.hotspotDescriptionPath}
105
+ hotspotDescriptionPath={options?.hotspotDescriptionPath}
106
+ tooltip={options?.hotspotTooltip}
106
107
  />
107
108
  ))}
108
109
 
package/src/Spot.tsx CHANGED
@@ -18,7 +18,7 @@ const dotStyle = {
18
18
 
19
19
  const round = (num) => Math.round(num * 100) / 100
20
20
 
21
- export default function Spot({spot, bounds = undefined, update, hotspotDescriptionPath = ``}) {
21
+ export default function Spot({spot, bounds = undefined, update, hotspotDescriptionPath = ``, tooltip}) {
22
22
  // x/y are stored as % but need to be displayed as px
23
23
  const [{x, y}, setXY] = React.useState({x: 0, y: 0})
24
24
  const [rect, setRect] = React.useState({width: 0, height: 0})
@@ -82,9 +82,13 @@ export default function Spot({spot, bounds = undefined, update, hotspotDescripti
82
82
  boundaryElement={bounds.current}
83
83
  portal
84
84
  content={
85
- <Box padding={2} style={{maxWidth: 200, pointerEvents: `none`}}>
86
- <Text textOverflow="ellipsis">{hotspotDescriptionPath ? get(spot, hotspotDescriptionPath) : `${spot.x}% x ${spot.y}%`}</Text>
87
- </Box>
85
+ tooltip && typeof tooltip === 'function' ? (
86
+ React.createElement(tooltip, {spot})
87
+ ) : (
88
+ <Box padding={2} style={{maxWidth: 200, pointerEvents: `none`}}>
89
+ <Text textOverflow="ellipsis">{hotspotDescriptionPath ? get(spot, hotspotDescriptionPath) : `${spot.x}% x ${spot.y}%`}</Text>
90
+ </Box>
91
+ )
88
92
  }
89
93
  >
90
94
  <motion.div