sanity-plugin-hotspot-array 0.1.0 → 1.0.1
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 +128 -55
- package/lib/index.esm.js +2 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/src/index.d.ts +55 -0
- package/package.json +57 -46
- package/sanity.json +6 -5
- package/src/Feedback.tsx +2 -2
- package/src/ImageHotspotArray.tsx +188 -0
- package/src/Spot.tsx +31 -19
- package/src/index.ts +4 -0
- package/src/plugin.tsx +39 -0
- package/v2-incompatible.js +11 -0
- package/.babelrc +0 -3
- package/.releaserc.json +0 -4
- package/.semantic-release/sanity-plugin-hotspot-array-0.1.0.tgz +0 -0
- package/CHANGELOG.md +0 -16
- package/lib/Feedback.js +0 -19
- package/lib/Feedback.js.map +0 -1
- package/lib/HotspotArray.js +0 -150
- package/lib/HotspotArray.js.map +0 -1
- package/lib/NestedFormBuilder.js +0 -53
- package/lib/NestedFormBuilder.js.map +0 -1
- package/lib/Spot.js +0 -157
- package/lib/Spot.js.map +0 -1
- package/lib/useUnsetInputComponent.js +0 -24
- package/lib/useUnsetInputComponent.js.map +0 -1
- package/src/HotspotArray.tsx +0 -178
- package/src/NestedFormBuilder.tsx +0 -44
- package/src/useUnsetInputComponent.ts +0 -17
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# sanity-plugin-hotspot-array
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> For the
|
|
3
|
+
>This is a **Sanity Studio v3** plugin.
|
|
4
|
+
> For the v2 version, please refer to the [v2-branch](https://github.com/sanity-io/sanity-plugin-hotspot-array/tree/studio-v2).
|
|
5
|
+
|
|
6
|
+
## What is it?
|
|
5
7
|
|
|
6
8
|
A configurable Custom Input for Arrays that will add and update items by clicking on an Image
|
|
7
9
|
|
|
@@ -9,85 +11,106 @@ A configurable Custom Input for Arrays that will add and update items by clickin
|
|
|
9
11
|
|
|
10
12
|
## Installation
|
|
11
13
|
|
|
12
|
-
```sh
|
|
13
|
-
yarn add sanity-plugin-hotspot-array@studio-v2
|
|
14
14
|
```
|
|
15
|
+
npm install --save sanity-plugin-hotspot-array
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
or
|
|
15
19
|
|
|
16
|
-
Next, add `"hotspot-array"` to `sanity.json` plugins array:
|
|
17
|
-
```json
|
|
18
|
-
"plugins": [
|
|
19
|
-
"hotspot-array"
|
|
20
|
-
]
|
|
21
20
|
```
|
|
21
|
+
yarn add sanity-plugin-hotspot-array
|
|
22
|
+
```
|
|
23
|
+
|
|
22
24
|
|
|
23
|
-
##
|
|
25
|
+
## Usage
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
Add it as a plugin in sanity.config.ts (or .js):
|
|
26
28
|
|
|
27
29
|
```js
|
|
28
|
-
import
|
|
30
|
+
import { imageHotspotArray } from "sanity-plugin-hotspot-array";
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
// ...
|
|
34
|
+
plugins: [
|
|
35
|
+
imageHotspotArray(),
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Now you will have `imageHotspot` available as an options on `array` fields:
|
|
29
41
|
|
|
30
|
-
|
|
42
|
+
```js
|
|
43
|
+
import {defineType, defineField} from 'sanity'
|
|
44
|
+
|
|
45
|
+
export const productSchema = defineType({
|
|
31
46
|
name: `product`,
|
|
32
47
|
title: `Product`,
|
|
33
48
|
type: `document`,
|
|
34
49
|
fields: [
|
|
35
|
-
{
|
|
50
|
+
defineField({
|
|
36
51
|
name: `hotspots`,
|
|
37
52
|
type: `array`,
|
|
38
|
-
inputComponent: HotspotArray,
|
|
39
53
|
of: [
|
|
40
54
|
// see `Spot object` setup below
|
|
41
55
|
],
|
|
42
56
|
options: {
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
// plugin adds support for this option
|
|
58
|
+
imageHotspot: {
|
|
59
|
+
// see `Image and description path` setup below
|
|
60
|
+
imagePath: `featureImage`,
|
|
61
|
+
descriptionPath: `details`,
|
|
62
|
+
// see `Custom tooltip` setup below
|
|
63
|
+
tooltip: undefined,
|
|
64
|
+
}
|
|
49
65
|
},
|
|
50
|
-
},
|
|
66
|
+
}),
|
|
51
67
|
// ...all your other fields
|
|
68
|
+
// ...of which one should be featureImage in this example
|
|
52
69
|
],
|
|
53
|
-
}
|
|
70
|
+
})
|
|
54
71
|
```
|
|
72
|
+
There is no need to provide an explicit input component, as that is handled by the plugin.
|
|
55
73
|
|
|
56
74
|
The plugin makes a number of assumptions to add and update data in the array. Including:
|
|
57
75
|
|
|
58
|
-
- The `array` field is an array of objects
|
|
76
|
+
- The `array` field is an array of objects, with a single object type
|
|
59
77
|
- The object contains two number fields named `x` and `y`
|
|
60
78
|
- You'll want to save those values as % from the top left of the image
|
|
61
79
|
- The same document contains the image you want to add hotspots to
|
|
62
80
|
|
|
63
|
-
### Image
|
|
81
|
+
### Image path
|
|
64
82
|
|
|
65
|
-
The custom input has the current values of all fields in the document
|
|
83
|
+
The custom input has the current values of all fields in the document, and so can "pick" the image out of the document by its path.
|
|
66
84
|
|
|
67
|
-
For example, if you want to add hotspots to an image, and that image is uploaded to the
|
|
85
|
+
For example, if you want to add hotspots to an image, and that image is uploaded to the field `featuredImage`, your fields `options` would look like:
|
|
68
86
|
|
|
69
87
|
```js
|
|
70
88
|
options: {
|
|
71
|
-
|
|
89
|
+
imageHotspot: {
|
|
90
|
+
imagePath: `featureImage`
|
|
91
|
+
}
|
|
72
92
|
}
|
|
73
93
|
```
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
In this case, if the image is uploaded to the parent object field of `featuredImage`, your fields `options` would look like:
|
|
78
|
-
|
|
95
|
+
To pick the image out of the hotspot-array parent object, use
|
|
79
96
|
```js
|
|
80
97
|
options: {
|
|
81
|
-
|
|
82
|
-
|
|
98
|
+
imageHotspot: {
|
|
99
|
+
pathRoot: 'parent'
|
|
100
|
+
}
|
|
83
101
|
}
|
|
84
102
|
```
|
|
85
103
|
|
|
86
|
-
|
|
104
|
+
### Description path
|
|
105
|
+
|
|
106
|
+
The custom input can also pre-fill a string or text field with a description of the position of the spot to make them easier to identify.
|
|
107
|
+
Add a path **relative to the spot object** for this field.
|
|
87
108
|
|
|
88
109
|
```js
|
|
89
110
|
options: {
|
|
90
|
-
|
|
111
|
+
imageHotspot: {
|
|
112
|
+
descriptionPath: `details`
|
|
113
|
+
}
|
|
91
114
|
}
|
|
92
115
|
```
|
|
93
116
|
|
|
@@ -96,7 +119,7 @@ options: {
|
|
|
96
119
|
Here's an example object schema complete with initial values, validation, fieldsets and a styled preview.
|
|
97
120
|
|
|
98
121
|
```js
|
|
99
|
-
{
|
|
122
|
+
defineField({
|
|
100
123
|
name: 'spot',
|
|
101
124
|
type: 'object',
|
|
102
125
|
fieldsets: [{name: 'position', options: {columns: 2}}],
|
|
@@ -132,28 +155,66 @@ Here's an example object schema complete with initial values, validation, fields
|
|
|
132
155
|
}
|
|
133
156
|
},
|
|
134
157
|
},
|
|
135
|
-
}
|
|
158
|
+
})
|
|
136
159
|
```
|
|
137
160
|
|
|
138
161
|
## Custom tooltip
|
|
139
162
|
|
|
140
|
-
You can customise the Tooltip to display any Component,
|
|
163
|
+
You can customise the Tooltip to display any Component, which will receive `value` (the hotspot value with x and y),
|
|
164
|
+
`schemaType` (schemaType of the hotspot value), and `renderPreview` (callback for rendering Studio preview).
|
|
165
|
+
|
|
166
|
+
### Example 1 - use default hotspot preview
|
|
141
167
|
|
|
142
|
-
|
|
168
|
+
```tsx
|
|
169
|
+
import { Box } from "@sanity/ui";
|
|
170
|
+
import { HotspotTooltipProps } from "sanity-plugin-hotspot-array";
|
|
171
|
+
|
|
172
|
+
export function HotspotPreview({
|
|
173
|
+
value,
|
|
174
|
+
schemaType,
|
|
175
|
+
renderPreview,
|
|
176
|
+
}: HotspotTooltipProps) {
|
|
177
|
+
return (
|
|
178
|
+
<Box padding={2} style={{ minWidth: 200 }}>
|
|
179
|
+
{renderPreview({
|
|
180
|
+
value,
|
|
181
|
+
schemaType,
|
|
182
|
+
layout: "default",
|
|
183
|
+
})}
|
|
184
|
+
</Box>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Then back in your schema definition
|
|
143
190
|
|
|
144
191
|
```js
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
192
|
+
options: {
|
|
193
|
+
imageHotspot: {
|
|
194
|
+
tooltip: HotspotPreview
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Example 2 - reference value in hotspot
|
|
200
|
+
In this example our `value` object has a `reference` field to the `product` schema type, and will show a document preview.
|
|
201
|
+
|
|
202
|
+
```jsx
|
|
203
|
+
import {useSchema }from 'sanity'
|
|
148
204
|
import {Box} from '@sanity/ui'
|
|
149
205
|
|
|
150
|
-
export
|
|
206
|
+
export function ProductPreview({value, renderPreview}) {
|
|
207
|
+
const productSchemaType = useSchema().get('product')
|
|
151
208
|
return (
|
|
152
209
|
<Box padding={2} style={{minWidth: 200}}>
|
|
153
|
-
{
|
|
154
|
-
|
|
210
|
+
{value?.product?._ref ? (
|
|
211
|
+
renderPreview({
|
|
212
|
+
value,
|
|
213
|
+
schemaType: productSchemaType,
|
|
214
|
+
layout: "default"
|
|
215
|
+
})
|
|
155
216
|
) : (
|
|
156
|
-
`No
|
|
217
|
+
`No reference selected`
|
|
157
218
|
)}
|
|
158
219
|
</Box>
|
|
159
220
|
)
|
|
@@ -163,16 +224,28 @@ export default function ProductPreview({spot}) {
|
|
|
163
224
|
Then back in your schema definition
|
|
164
225
|
|
|
165
226
|
```js
|
|
166
|
-
import HotspotArray from 'sanity-plugin-hotspot-array'
|
|
167
|
-
import ProductPreview from '../../components/ProductPreview'
|
|
168
|
-
|
|
169
227
|
options: {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
228
|
+
imageHotspot: {
|
|
229
|
+
tooltip: ProductPreview
|
|
230
|
+
}
|
|
231
|
+
}
|
|
173
232
|
```
|
|
174
233
|
|
|
175
234
|
## License
|
|
176
235
|
|
|
177
|
-
MIT
|
|
178
|
-
|
|
236
|
+
MIT-licensed. See LICENSE.
|
|
237
|
+
|
|
238
|
+
## Develop & test
|
|
239
|
+
|
|
240
|
+
This plugin uses [@sanity/plugin-kit](https://github.com/sanity-io/plugin-kit)
|
|
241
|
+
with default configuration for build & watch scripts.
|
|
242
|
+
|
|
243
|
+
See [Testing a plugin in Sanity Studio](https://github.com/sanity-io/plugin-kit#testing-a-plugin-in-sanity-studio)
|
|
244
|
+
on how to run this plugin with hotreload in the studio.
|
|
245
|
+
|
|
246
|
+
### Release new version
|
|
247
|
+
|
|
248
|
+
Run ["CI & Release" workflow](https://github.com/sanity-io/sanity-plugin-hotspot-array/actions/workflows/main.yml).
|
|
249
|
+
Make sure to select the main branch and check "Release new version".
|
|
250
|
+
|
|
251
|
+
Semantic release will only release on configured branches, so it is safe to run release on any branch.
|
package/lib/index.esm.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),o.push.apply(o,i)}return o}function t(t){for(var i=1;i<arguments.length;i++){var r=null!=arguments[i]?arguments[i]:{};i%2?e(Object(r),!0).forEach((function(e){o(t,e,r[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):e(Object(r)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(r,e))}))}return t}function o(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}import{jsx as i,jsxs as r,Fragment as n}from"react/jsx-runtime";import{getImageDimensions as a}from"@sanity/asset-utils";import{useClient as l,useFormValue as s,PatchEvent as c,setIfMissing as d,insert as h,set as u,definePlugin as p}from"sanity";import m from"@sanity/image-url";import{Card as f,Text as g,Tooltip as y,Box as b,Stack as v,Flex as w}from"@sanity/ui";import{randomKey as P}from"@sanity/util/content";import x from"lodash/get";import O,{useEffect as k,useMemo as j,useCallback as _,useRef as D,useState as E}from"react";import{useDebouncedCallback as R,useResizeObserver as H}from"@react-hookz/web";import{useMotionValue as T,motion as C}from"framer-motion";function S(e){let{children:t}=e;return i(f,{padding:4,radius:2,shadow:1,tone:"caution",children:i(g,{children:t})})}const M={width:"1.4rem",height:"1.4rem",position:"absolute",boxSizing:"border-box",top:0,left:0,margin:"-0.7rem 0 0 -0.7rem",cursor:"pointer",display:"flex",justifyContent:"center",alignItems:"center",borderRadius:"50%",background:"#000",color:"white"},N={background:"rgba(0, 0, 0, 0.1)",border:"1px solid #fff",cursor:"none"},z={background:"rgba(0, 0, 0, 0.1)",border:"1px solid #fff"},F={position:"absolute",left:"50%",top:"50%",height:"0.2rem",width:"0.2rem",margin:"-0.1rem 0 0 -0.1rem",background:"#fff",visibility:"hidden",borderRadius:"50%",pointerEvents:"none"},I={visibility:"visible"},W={color:"white",fontSize:"0.7rem",fontWeight:600,lineHeight:"1"},A={visibility:"hidden"},V=e=>Math.round(100*e)/100;function X(e){let{value:o,bounds:n,update:a,hotspotDescriptionPath:l,tooltip:s,index:c,schemaType:d,renderPreview:h}=e;const[u,p]=O.useState(!1),[m,f]=O.useState(!1),v=T(V(n.width*(o.x/100))),w=T(V(n.height*(o.y/100)));k((()=>{v.set(V(n.width*(o.x/100))),w.set(V(n.height*(o.y/100)))}),[v,w,o,n]);const P=O.useCallback((()=>{p(!1);const e=v.get(),t=w.get(),i=V(100*e/n.width),r=V(100*t/n.height),l=Math.max(0,Math.min(100,i)),s=Math.max(0,Math.min(100,r));a(o._key,l,s)}),[v,w,o,a,n]),j=O.useCallback((()=>p(!0)),[]),_=O.useCallback((()=>f(!0)),[]),D=O.useCallback((()=>f(!1)),[]);return v&&w?i(y,{disabled:u,portal:!0,content:s&&"function"==typeof s?O.createElement(s,{value:o,renderPreview:h,schemaType:d}):i(b,{padding:2,style:{maxWidth:200,pointerEvents:"none"},children:i(g,{textOverflow:"ellipsis",children:l?x(o,l):"".concat(o.x,"% x ").concat(o.y,"%")})}),children:r(C.div,{drag:!0,dragConstraints:n,dragElastic:0,dragMomentum:!1,onDragEnd:P,onDragStart:j,onHoverStart:_,onHoverEnd:D,style:t(t(t({},M),{},{x:v,y:w},u&&t({},N)),m&&t({},z)),children:[i(b,{style:t(t({},F),(u||m)&&t({},I))}),i("div",{style:t(t({},W),(u||m)&&t({},A)),children:c+1})]})},o._key):null}const Y={width:"100%",height:"auto"},q=["document","parent"];function B(e){var t;const{value:o,onChange:p,imageHotspotOptions:g,schemaType:y,renderPreview:b}=e,O=l({apiVersion:"2022-01-01"}),k=j((()=>{var t;return"document"===(q.includes(null!=(t=g.pathRoot)?t:"")?g.pathRoot:"document")?[]:e.path.slice(0,-1)}),[g.pathRoot,e.path]),T=s(k),C=j((()=>x(T,g.imagePath)),[T,g.imagePath]),M=j((()=>{var e,t;const o=m(O).dataset(null!=(e=O.config().dataset)?e:"");if(null==(t=null==C?void 0:C.asset)?void 0:t._ref){const{aspectRatio:e}=a(C.asset._ref),t=1200;return{width:t,height:Math.round(t/e),url:(i=C,o.image(i)).width(t).url()}}var i;return null}),[C,O]),N=_((e=>{const{nativeEvent:t}=e,o=Number((100*t.offsetX/t.srcElement.width).toFixed(2)),i=Number((100*t.offsetY/t.srcElement.height).toFixed(2)),r="New Hotspot at ".concat(o,"% x ").concat(i,"%"),n={_key:P(12),_type:y.of[0].name,x:o,y:i};(null==g?void 0:g.descriptionPath)&&(n[g.descriptionPath]=r),p(c.from([d([]),h([n],"after",[-1])]))}),[g,p,y]),z=_(((e,t,o)=>{p(c.from([u(t,[{_key:e},"x"]),u(o,[{_key:e},"y"])]))}),[p]),F=D(null),[I,W]=E(),A=R((e=>W(e.contentRect)),[W],200);return H(F,A),r(v,{space:[2,2,3],children:[(null==M?void 0:M.url)?r("div",{style:{position:"relative"},children:[I&&(null!=(t=null==o?void 0:o.length)?t:0)>0&&(null==o?void 0:o.map(((e,t)=>i(X,{index:t,value:e,bounds:I,update:z,hotspotDescriptionPath:null==g?void 0:g.descriptionPath,tooltip:null==g?void 0:g.tooltip,renderPreview:b,schemaType:y.of[0]},e._key)))),i(f,{__unstable_checkered:!0,shadow:1,children:i(w,{align:"center",justify:"center",children:i("img",{ref:F,src:M.url,width:M.width,height:M.height,alt:"",style:Y,onClick:N})})})]}):i(S,{children:g.imagePath?r(n,{children:["No Hotspot image found at path ",i("code",{children:g.imagePath})]}):r(n,{children:["Define a path in this field using to the image field in this document at"," ",i("code",{children:"options.hotspotImagePath"})]})}),g.pathRoot&&!q.includes(g.pathRoot)&&r(S,{children:['The supplied imageHotspotPathRoot "',g.pathRoot,'" is not valid, falling back to "document". Available values are "',q.join(", "),'".']}),e.renderDefault(e)]})}const G=p({name:"image-hotspot-array",form:{components:{input:e=>{var o,r;if("array"===e.schemaType.jsonType&&(null==(o=e.schemaType.options)?void 0:o.imageHotspot)){const o=null==(r=e.schemaType.options)?void 0:r.imageHotspot;if(o)return i(B,t(t({},e),{},{imageHotspotOptions:o}))}return e.renderDefault(e)}}}});export{B as ImageHotspotArray,G as imageHotspotArrayPlugin};
|
|
2
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/Feedback.tsx","../src/Spot.tsx","../src/ImageHotspotArray.tsx","../src/plugin.tsx"],"sourcesContent":["import React, {ReactNode} from 'react'\nimport {Card, Text} from '@sanity/ui'\n\nexport default function Feedback({children}: {children: ReactNode}) {\n return (\n <Card padding={4} radius={2} shadow={1} tone=\"caution\">\n <Text>{children}</Text>\n </Card>\n )\n}\n","/* eslint-disable */\nimport {Box, Text, Tooltip} from '@sanity/ui'\nimport {motion, useMotionValue} from 'framer-motion'\nimport get from 'lodash/get'\nimport React, {ComponentType, CSSProperties, ReactElement, useEffect} from 'react'\nimport {FnHotspotMove, HotspotItem} from './ImageHotspotArray'\nimport {ObjectSchemaType, RenderPreviewCallback} from 'sanity'\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: number) => Math.round(num * 100) / 100\n\nexport interface HotspotTooltipProps<HotspotFields = {[key: string]: unknown}> {\n value: HotspotItem<HotspotFields>\n schemaType: ObjectSchemaType\n renderPreview: RenderPreviewCallback\n}\n\ninterface HotspotProps<HotspotFields = {[key: string]: unknown}> {\n value: HotspotItem\n bounds: DOMRectReadOnly\n update: FnHotspotMove\n hotspotDescriptionPath?: string\n tooltip?: ComponentType<HotspotTooltipProps<HotspotFields>>\n index: number\n schemaType: ObjectSchemaType\n renderPreview: RenderPreviewCallback\n}\n\nexport default function Spot({\n value,\n bounds,\n update,\n hotspotDescriptionPath,\n tooltip,\n index,\n schemaType,\n renderPreview,\n}: HotspotProps) {\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 * (value.x / 100)))\n const y = useMotionValue(round(bounds.height * (value.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 * (value.x / 100)))\n y.set(round(bounds.height * (value.y / 100)))\n }, [x, y, value, 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(value._key, safeX, safeY)\n }, [x, y, value, update, bounds])\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={value._key}\n disabled={isDragging}\n portal\n content={\n tooltip && typeof tooltip === 'function' ? (\n React.createElement(tooltip, {value, renderPreview, schemaType})\n ) : (\n <Box padding={2} style={{maxWidth: 200, pointerEvents: `none`}}>\n <Text textOverflow=\"ellipsis\">\n {hotspotDescriptionPath\n ? (get(value, hotspotDescriptionPath) as string)\n : `${value.x}% x ${value.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","/* eslint-disable react/display-name */\n\nimport {getImageDimensions} from '@sanity/asset-utils'\nimport {\n ArrayOfObjectsInputProps,\n ImageValue,\n insert,\n ObjectSchemaType,\n PatchEvent,\n set,\n setIfMissing,\n useClient,\n useFormValue,\n} from 'sanity'\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, {useCallback, useMemo, useRef, useState} from 'react'\n\nimport {IUseResizeObserverCallback, useDebouncedCallback, useResizeObserver} from '@react-hookz/web'\nimport Feedback from './Feedback'\nimport Spot from './Spot'\nimport {ImageHotspotOptions} from './plugin'\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 HotspotItem<HotspotFields = {[key: string]: unknown}> = {\n _key: string\n _type: string\n x: number\n y: number\n} & HotspotFields\n\nexport function ImageHotspotArray(\n props: ArrayOfObjectsInputProps<HotspotItem> & {imageHotspotOptions: ImageHotspotOptions}\n) {\n const {value, onChange, imageHotspotOptions, schemaType, renderPreview} = props\n\n const sanityClient = useClient({apiVersion: '2022-01-01'})\n\n const imageHotspotPathRoot = useMemo(() => {\n const pathRoot = VALID_ROOT_PATHS.includes(imageHotspotOptions.pathRoot ?? '')\n ? imageHotspotOptions.pathRoot\n : 'document'\n return pathRoot === 'document' ? [] : props.path.slice(0, -1)\n }, [imageHotspotOptions.pathRoot, props.path])\n\n const rootObject = useFormValue(imageHotspotPathRoot)\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 = useMemo(() => {\n return get(rootObject, imageHotspotOptions.imagePath) as ImageValue | undefined\n }, [rootObject, imageHotspotOptions.imagePath])\n\n const displayImage = useMemo(() => {\n const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset ?? '')\n const urlFor = (source: ImageValue) => 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, sanityClient])\n\n const handleHotspotImageClick = useCallback(\n (event: any) => {\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: HotspotItem = {\n _key: randomKey(12),\n _type: schemaType.of[0].name,\n x,\n y,\n }\n\n if (imageHotspotOptions?.descriptionPath) {\n newRow[imageHotspotOptions.descriptionPath] = description\n }\n\n onChange(PatchEvent.from([setIfMissing([]), insert([newRow], 'after', [-1])]))\n },\n [imageHotspotOptions, onChange, schemaType]\n )\n\n const handleHotspotMove: FnHotspotMove = 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 [onChange]\n )\n\n const hotspotImageRef = 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) > 0 &&\n value?.map((spot, index) => (\n <Spot\n index={index}\n key={spot._key}\n value={spot}\n bounds={imageRect}\n update={handleHotspotMove}\n hotspotDescriptionPath={imageHotspotOptions?.descriptionPath}\n tooltip={imageHotspotOptions?.tooltip}\n renderPreview={renderPreview}\n schemaType={schemaType.of[0] as ObjectSchemaType}\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 {imageHotspotOptions.imagePath ? (\n <>\n No Hotspot image found at path <code>{imageHotspotOptions.imagePath}</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 {imageHotspotOptions.pathRoot && !VALID_ROOT_PATHS.includes(imageHotspotOptions.pathRoot) && (\n <Feedback>\n The supplied imageHotspotPathRoot \"{imageHotspotOptions.pathRoot}\" is not valid, falling\n back to \"document\". Available values are \"{VALID_ROOT_PATHS.join(', ')}\".\n </Feedback>\n )}\n {props.renderDefault(props as unknown as ArrayOfObjectsInputProps)}\n </Stack>\n )\n}\n","import {ImageHotspotArray, type HotspotItem} from './ImageHotspotArray'\nimport React, {ComponentType} from 'react'\nimport {ArrayOfObjectsInputProps, definePlugin} from 'sanity'\nimport {type HotspotTooltipProps} from './Spot'\n\nexport interface ImageHotspotOptions<HotspotFields = {[key: string]: unknown}> {\n pathRoot?: 'document' | 'parent'\n imagePath: string\n descriptionPath?: string\n tooltip?: ComponentType<HotspotTooltipProps<HotspotFields>>\n}\n\ndeclare module '@sanity/types' {\n export interface ArrayOptions {\n imageHotspot?: ImageHotspotOptions\n }\n}\n\nexport const imageHotspotArrayPlugin = definePlugin({\n name: 'image-hotspot-array',\n form: {\n components: {\n input: (props) => {\n if (props.schemaType.jsonType === 'array' && props.schemaType.options?.imageHotspot) {\n const imageHotspotOptions = props.schemaType.options?.imageHotspot\n if (imageHotspotOptions) {\n return (\n <ImageHotspotArray\n {...(props as unknown as ArrayOfObjectsInputProps<HotspotItem>)}\n imageHotspotOptions={imageHotspotOptions}\n />\n )\n }\n }\n return props.renderDefault(props)\n },\n },\n },\n})\n"],"names":["Feedback","_ref","children","jsx","Card","padding","radius","shadow","tone","Text","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","_ref2","value","bounds","update","hotspotDescriptionPath","tooltip","index","schemaType","renderPreview","isDragging","setIsDragging","React","useState","isHovering","setIsHovering","x","useMotionValue","y","useEffect","set","handleDragEnd","useCallback","currentX","get","currentY","newX","newY","safeX","max","min","safeY","_key","handleDragStart","handleHoverStart","handleHoverEnd","Tooltip","disabled","portal","content","createElement","Box","style","maxWidth","textOverflow","jsxs","motion","div","drag","dragConstraints","dragElastic","dragMomentum","onDragEnd","onDragStart","onHoverStart","onHoverEnd","_objectSpread","imageStyle","VALID_ROOT_PATHS","ImageHotspotArray","props","_a","onChange","imageHotspotOptions","sanityClient","useClient","apiVersion","imageHotspotPathRoot","useMemo","includes","pathRoot","path","slice","rootObject","useFormValue","hotspotImage","imagePath","displayImage","_b","builder","imageUrlBuilder","dataset","config","asset","aspectRatio","getImageDimensions","url","source","image","handleHotspotImageClick","event","nativeEvent","Number","offsetX","srcElement","toFixed","offsetY","description","concat","newRow","randomKey","_type","of","name","descriptionPath","PatchEvent","from","setIfMissing","insert","handleHotspotMove","key","hotspotImageRef","useRef","imageRect","setImageRect","updateImageRectCallback","useDebouncedCallback","e","contentRect","useResizeObserver","Stack","space","length","map","spot","__unstable_checkered","Flex","align","justify","ref","src","alt","onClick","Fragment","join","renderDefault","imageHotspotArrayPlugin","definePlugin","form","components","input","jsonType","options","imageHotspot"],"mappings":"w1CAGwB,SAAAA,EAA4CC,GAAA,IAAnCC,SAACA,GAAkCD,EAClE,OACGE,EAAAC,EAAA,CAAKC,QAAS,EAAGC,OAAQ,EAAGC,OAAQ,EAAGC,KAAK,UAC3CN,SAACC,EAAAM,EAAA,CAAMP,cAGb,CCDA,MAAMQ,EAA2B,CAC/BC,MAAO,SACPC,OAAQ,SACRC,SAAU,WACVC,UAAW,aACXC,IAAK,EACLC,KAAM,EACNC,OAAQ,sBACRC,OAAQ,UACRC,QAAS,OACTC,eAAgB,SAChBC,WAAY,SACZC,aAAc,MACdC,WAAY,OACZC,MAAO,SAGHC,EAAoC,CACxCF,WAAY,qBACZG,OAAQ,iBACRR,OAAQ,QAGJS,EAAqC,CACzCJ,WAAY,qBACZG,OAAQ,kBAGJE,EAA0B,CAC9Bf,SAAU,WACVG,KAAM,MACND,IAAK,MACLH,OAAQ,SACRD,MAAO,SACPM,OAAQ,sBACRM,WAAY,OACZM,WAAY,SACZP,aAAc,MAEdQ,cAAe,QAGXC,EAAqC,CACzCF,WAAY,WAGRG,EAA4B,CAChCR,MAAO,QACPS,SAAU,SACVC,WAAY,IACZC,WAAY,KAGRC,EAAuC,CAC3CP,WAAY,UAGRQ,EAASC,GAAgBC,KAAKF,MAAY,IAANC,GAAa,IAmBvD,SAAwBE,EASPC,GAAA,IATYC,MAC3BA,EAAAC,OACAA,EAAAC,OACAA,EAAAC,uBACAA,EAAAC,QACAA,EAAAC,MACAA,EAAAC,WACAA,EAAAC,cACAA,GACeR,EACf,MAAOS,EAAYC,GAAiBC,EAAMC,UAAS,IAC5CC,EAAYC,GAAiBH,EAAMC,UAAS,GAG7CG,EAAIC,EAAepB,EAAMM,EAAOhC,OAAS+B,EAAMc,EAAI,OACnDE,EAAID,EAAepB,EAAMM,EAAO/B,QAAU8B,EAAMgB,EAAI,OAK1DC,GAAU,KACRH,EAAEI,IAAIvB,EAAMM,EAAOhC,OAAS+B,EAAMc,EAAI,OACtCE,EAAEE,IAAIvB,EAAMM,EAAO/B,QAAU8B,EAAMgB,EAAI,MAAK,GAC3C,CAACF,EAAGE,EAAGhB,EAAOC,IAEX,MAAAkB,EAAgBT,EAAMU,aAAY,KACtCX,GAAc,GAGR,MAAAY,EAAWP,EAAEQ,MACbC,EAAWP,EAAEM,MAGbE,EAAO7B,EAAkB,IAAX0B,EAAkBpB,EAAOhC,OACvCwD,EAAO9B,EAAkB,IAAX4B,EAAkBtB,EAAO/B,QAGvCwD,EAAQ7B,KAAK8B,IAAI,EAAG9B,KAAK+B,IAAI,IAAKJ,IAClCK,EAAQhC,KAAK8B,IAAI,EAAG9B,KAAK+B,IAAI,IAAKH,IAEjCvB,EAAAF,EAAM8B,KAAMJ,EAAOG,EAAK,GAC9B,CAACf,EAAGE,EAAGhB,EAAOE,EAAQD,IACnB8B,EAAkBrB,EAAMU,aAAY,IAAMX,GAAc,IAAO,IAE/DuB,EAAmBtB,EAAMU,aAAY,IAAMP,GAAc,IAAO,IAChEoB,EAAiBvB,EAAMU,aAAY,IAAMP,GAAc,IAAQ,IAEjE,OAACC,GAAME,EAKRvD,EAAAyE,EAAA,CAECC,SAAU3B,EACV4B,QAAM,EACNC,QACEjC,GAA8B,mBAAZA,EAChBM,EAAM4B,cAAclC,EAAS,CAACJ,QAAOO,gBAAeD,eAEnD7C,EAAA8E,EAAA,CAAI5E,QAAS,EAAG6E,MAAO,CAACC,SAAU,IAAKrD,sBACtC5B,SAACC,EAAAM,EAAA,CAAK2E,aAAa,WAChBlF,SAAA2C,EACImB,EAAItB,EAAOG,aACTH,EAAMc,EAAQd,QAAAA,OAAAA,EAAMgB,EAAA,SAMnCxD,SAAAmF,EAACC,EAAOC,IAAP,CACCC,MAAI,EACJC,gBAAiB9C,EACjB+C,YAAa,EACbC,cAAc,EACdC,UAAW/B,EACXgC,YAAapB,EACbqB,aAAcpB,EACdqB,WAAYpB,EACZO,eACKxE,GAAA,CAAA,EAAA,CACH8C,IACAE,KACIR,QAAkBzB,IAClB6B,GAAA0C,EAAA,CAAA,EAAkBrE,IAIxBzB,SAAA,CAACC,EAAA8E,EAAA,CACCC,MAAOc,EAAAA,EAAA,CAAA,EACFpE,IACEsB,GAAcI,IAAmBvB,EAAAA,CAAAA,EAAAA,MAIzC5B,EAAA,MAAA,CACC+E,MAAOc,EAAAA,EAAA,CAAA,EACFhE,IACEkB,GAAcI,IAAe0C,EAAA,CAAA,EAAI5D,IAGvClC,SAAQ6C,EAAA,QAhDRL,EAAM8B,MALN,IA0DX,CCrKA,MAAMyB,EAAa,CAACtF,MAAO,OAAQC,eAE7BsF,EAAmB,CAAC,WAAY,UAW/B,SAASC,EACdC,GAvCF,IAAAC,EAyCE,MAAM3D,MAACA,EAAO4D,SAAAA,EAAAC,oBAAUA,EAAqBvD,WAAAA,EAAAC,cAAYA,GAAiBmD,EAEpEI,EAAeC,EAAU,CAACC,WAAY,eAEtCC,EAAuBC,GAAQ,KA7CvCP,IAAAA,EAiDW,MAAa,cAHHH,EAAiBW,SAAS,OAAAR,EAAAE,EAAoBO,UAApBT,EAAgC,IACvEE,EAAoBO,SACpB,YAC6B,GAAKV,EAAMW,KAAKC,MAAM,GAAK,EAAA,GAC3D,CAACT,EAAoBO,SAAUV,EAAMW,OAElCE,EAAaC,EAAaP,GAS1BQ,EAAeP,GAAQ,IACpB5C,EAAIiD,EAAYV,EAAoBa,YAC1C,CAACH,EAAYV,EAAoBa,YAE9BC,EAAeT,GAAQ,KAjE/B,IAAAP,EAAAiB,EAkEI,MAAMC,EAAUC,EAAgBhB,GAAciB,QAAQ,OAAApB,EAAAG,EAAakB,SAASD,SAAtBpB,EAAiC,IAGnF,GAAA,OAAAiB,EAAA,MAAAH,OAAA,EAAAA,EAAcQ,YAAd,EAAAL,EAAqBrH,KAAM,CAC7B,MAAM2H,YAACA,GAAeC,EAAmBV,EAAaQ,MAAM1H,MACtDU,EAAQ,KAIP,MAAA,CAACA,QAAOC,OAHA2B,KAAKF,MAAM1B,EAAQiH,GAGXE,KARTC,EAMKZ,EANkBI,EAAQS,MAAMD,IAMlBpH,MAAMA,GAAOmH,MAGhD,CATgBC,MAWT,OAAA,IAAA,GACN,CAACZ,EAAcX,IAEZyB,EAA0BnE,GAC7BoE,IACO,MAAAC,YAACA,GAAeD,EAGhB1E,EAAI4E,QAA+B,IAAtBD,EAAYE,QAAiBF,EAAYG,WAAW3H,OAAO4H,QAAQ,IAChF7E,EAAI0E,QAA+B,IAAtBD,EAAYK,QAAiBL,EAAYG,WAAW1H,QAAQ2H,QAAQ,IACjFE,EAAA,kBAAAC,OAAgClF,EAAQ,QAAAkF,OAAAhF,EAAA,KAExCiF,EAAsB,CAC1BnE,KAAMoE,EAAU,IAChBC,MAAO7F,EAAW8F,GAAG,GAAGC,KACxBvF,IACAE,YAGE6C,WAAqByC,mBACvBL,EAAOpC,EAAoByC,iBAAmBP,GAGhDnC,EAAS2C,EAAWC,KAAK,CAACC,EAAa,IAAKC,EAAO,CAACT,GAAS,QAAS,QAAO,GAE/E,CAACpC,EAAqBD,EAAUtD,IAG5BqG,EAAmCvF,GACvC,CAACwF,EAAK9F,EAAGE,KACP4C,EACE2C,EAAWC,KAAK,CAEdtF,EAAIJ,EAAG,CAAC,CAACgB,KAAM8E,GAAM,MAErB1F,EAAIF,EAAG,CAAC,CAACc,KAAM8E,GAAM,QAEzB,GAEF,CAAChD,IAGGiD,EAAkBC,EAAgC,OAEjDC,EAAWC,GAAgBrG,IAC5BsG,EAA0BC,GAC5BC,GAAMH,EAAaG,EAAEC,cACvB,CAACJ,GACD,KAIF,OAFAK,EAAkBR,EAAiBI,GAGhCtE,EAAA2E,EAAA,CAAMC,MAAO,CAAC,EAAG,EAAG,GAClB/J,SAAA,EAAA,MAAAmH,OAAA,EAAAA,EAAcS,KACZzC,EAAA,MAAA,CAAIH,MAAO,CAACrE,SAAA,YACVX,SAAA,CACEuJ,IAAA,OAAApD,EAAA,MAAA3D,OAAA,EAAAA,EAAOwH,QAAP7D,EAAiB,GAAK,UACvB3D,WAAOyH,KAAI,CAACC,EAAMrH,IACf5C,EAAAqC,EAAA,CACCO,QAEAL,MAAO0H,EACPzH,OAAQ8G,EACR7G,OAAQyG,EACRxG,uBAA6C,MAArB0D,OAAqB,EAAAA,EAAAyC,gBAC7ClG,QAA8B,MAArByD,OAAqB,EAAAA,EAAAzD,QAC9BG,gBACAD,WAAYA,EAAW8F,GAAG,IAPrBsB,EAAK5F,SAWfrE,EAAAC,EAAA,CAAKiK,sBAAoB,EAAC9J,OAAQ,EACjCL,SAACC,EAAAmK,EAAA,CAAKC,MAAM,SAASC,QAAQ,SAC3BtK,SAACC,EAAA,MAAA,CACCsK,IAAKlB,EACLmB,IAAKrD,EAAaS,IAClBnH,MAAO0G,EAAa1G,MACpBC,OAAQyG,EAAazG,OACrB+J,IAAI,GACJzF,MAAOe,EACP2E,QAAS3C,WAMhB9H,EAAAH,EAAA,CACEE,WAAoBkH,UACnB/B,EAAAwF,EAAA,CAAE3K,SAAA,CAAA,kCACgCC,EAAA,OAAA,CAAMD,SAAoBqG,EAAAa,eAG5D/B,EAAAwF,EAAA,CAAE3K,SAAA,CAAA,2EACyE,IACxEC,EAAA,OAAA,CAAKD,SAAA,kCAKbqG,EAAoBO,WAAaZ,EAAiBW,SAASN,EAAoBO,WAC7EzB,EAAArF,EAAA,CAASE,SAAA,CAAA,sCAC4BqG,EAAoBO,SAAS,qEACtBZ,EAAiB4E,KAAK,MAAM,QAG1E1E,EAAM2E,cAAc3E,KAG3B,CCzKO,MAAM4E,EAA0BC,EAAa,CAClDlC,KAAM,sBACNmC,KAAM,CACJC,WAAY,CACVC,MAAQhF,IAtBd,IAAAC,EAAAiB,EAuBY,GAA8B,UAA9BlB,EAAMpD,WAAWqI,WAAwB,OAAAhF,IAAMrD,WAAWsI,kBAASC,cAAc,CACnF,MAAMhF,EAAsB,OAAAe,EAAAlB,EAAMpD,WAAWsI,cAAS,EAAAhE,EAAAiE,aACtD,GAAIhF,EACF,OACGpG,EAAAgG,SACMC,GAAA,CAAA,EAAA,CACLG,wBAIR,CACO,OAAAH,EAAM2E,cAAc3E,EAAK"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";function e(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,r)}return i}function t(t){for(var r=1;r<arguments.length;r++){var n=null!=arguments[r]?arguments[r]:{};r%2?e(Object(n),!0).forEach((function(e){i(t,e,n[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(n)):e(Object(n)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(n,e))}))}return t}function i(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}Object.defineProperty(exports,"__esModule",{value:!0});var r=require("react/jsx-runtime"),n=require("@sanity/asset-utils"),o=require("sanity"),a=require("@sanity/image-url"),s=require("@sanity/ui"),l=require("@sanity/util/content"),u=require("lodash/get"),c=require("react"),d=require("@react-hookz/web"),h=require("framer-motion");function p(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var f=p(a),m=p(u),g=p(c);function y(e){let{children:t}=e;return r.jsx(s.Card,{padding:4,radius:2,shadow:1,tone:"caution",children:r.jsx(s.Text,{children:t})})}const b={width:"1.4rem",height:"1.4rem",position:"absolute",boxSizing:"border-box",top:0,left:0,margin:"-0.7rem 0 0 -0.7rem",cursor:"pointer",display:"flex",justifyContent:"center",alignItems:"center",borderRadius:"50%",background:"#000",color:"white"},v={background:"rgba(0, 0, 0, 0.1)",border:"1px solid #fff",cursor:"none"},x={background:"rgba(0, 0, 0, 0.1)",border:"1px solid #fff"},j={position:"absolute",left:"50%",top:"50%",height:"0.2rem",width:"0.2rem",margin:"-0.1rem 0 0 -0.1rem",background:"#fff",visibility:"hidden",borderRadius:"50%",pointerEvents:"none"},w={visibility:"visible"},P={color:"white",fontSize:"0.7rem",fontWeight:600,lineHeight:"1"},k={visibility:"hidden"},O=e=>Math.round(100*e)/100;function C(e){let{value:i,bounds:n,update:o,hotspotDescriptionPath:a,tooltip:l,index:u,schemaType:d,renderPreview:p}=e;const[f,y]=g.default.useState(!1),[C,E]=g.default.useState(!1),M=h.useMotionValue(O(n.width*(i.x/100))),_=h.useMotionValue(O(n.height*(i.y/100)));c.useEffect((()=>{M.set(O(n.width*(i.x/100))),_.set(O(n.height*(i.y/100)))}),[M,_,i,n]);const D=g.default.useCallback((()=>{y(!1);const e=M.get(),t=_.get(),r=O(100*e/n.width),a=O(100*t/n.height),s=Math.max(0,Math.min(100,r)),l=Math.max(0,Math.min(100,a));o(i._key,s,l)}),[M,_,i,o,n]),R=g.default.useCallback((()=>y(!0)),[]),H=g.default.useCallback((()=>E(!0)),[]),T=g.default.useCallback((()=>E(!1)),[]);return M&&_?r.jsx(s.Tooltip,{disabled:f,portal:!0,content:l&&"function"==typeof l?g.default.createElement(l,{value:i,renderPreview:p,schemaType:d}):r.jsx(s.Box,{padding:2,style:{maxWidth:200,pointerEvents:"none"},children:r.jsx(s.Text,{textOverflow:"ellipsis",children:a?m.default(i,a):"".concat(i.x,"% x ").concat(i.y,"%")})}),children:r.jsxs(h.motion.div,{drag:!0,dragConstraints:n,dragElastic:0,dragMomentum:!1,onDragEnd:D,onDragStart:R,onHoverStart:H,onHoverEnd:T,style:t(t(t({},b),{},{x:M,y:_},f&&t({},v)),C&&t({},x)),children:[r.jsx(s.Box,{style:t(t({},j),(f||C)&&t({},w))}),r.jsx("div",{style:t(t({},P),(f||C)&&t({},k)),children:u+1})]})},i._key):null}const E={width:"100%",height:"auto"},M=["document","parent"];function _(e){var t;const{value:i,onChange:a,imageHotspotOptions:u,schemaType:h,renderPreview:p}=e,g=o.useClient({apiVersion:"2022-01-01"}),b=c.useMemo((()=>{var t;return"document"===(M.includes(null!=(t=u.pathRoot)?t:"")?u.pathRoot:"document")?[]:e.path.slice(0,-1)}),[u.pathRoot,e.path]),v=o.useFormValue(b),x=c.useMemo((()=>m.default(v,u.imagePath)),[v,u.imagePath]),j=c.useMemo((()=>{var e,t;const i=f.default(g).dataset(null!=(e=g.config().dataset)?e:"");if(null==(t=null==x?void 0:x.asset)?void 0:t._ref){const{aspectRatio:e}=n.getImageDimensions(x.asset._ref),t=1200;return{width:t,height:Math.round(t/e),url:(r=x,i.image(r)).width(t).url()}}var r;return null}),[x,g]),w=c.useCallback((e=>{const{nativeEvent:t}=e,i=Number((100*t.offsetX/t.srcElement.width).toFixed(2)),r=Number((100*t.offsetY/t.srcElement.height).toFixed(2)),n="New Hotspot at ".concat(i,"% x ").concat(r,"%"),s={_key:l.randomKey(12),_type:h.of[0].name,x:i,y:r};(null==u?void 0:u.descriptionPath)&&(s[u.descriptionPath]=n),a(o.PatchEvent.from([o.setIfMissing([]),o.insert([s],"after",[-1])]))}),[u,a,h]),P=c.useCallback(((e,t,i)=>{a(o.PatchEvent.from([o.set(t,[{_key:e},"x"]),o.set(i,[{_key:e},"y"])]))}),[a]),k=c.useRef(null),[O,_]=c.useState(),D=d.useDebouncedCallback((e=>_(e.contentRect)),[_],200);return d.useResizeObserver(k,D),r.jsxs(s.Stack,{space:[2,2,3],children:[(null==j?void 0:j.url)?r.jsxs("div",{style:{position:"relative"},children:[O&&(null!=(t=null==i?void 0:i.length)?t:0)>0&&(null==i?void 0:i.map(((e,t)=>r.jsx(C,{index:t,value:e,bounds:O,update:P,hotspotDescriptionPath:null==u?void 0:u.descriptionPath,tooltip:null==u?void 0:u.tooltip,renderPreview:p,schemaType:h.of[0]},e._key)))),r.jsx(s.Card,{__unstable_checkered:!0,shadow:1,children:r.jsx(s.Flex,{align:"center",justify:"center",children:r.jsx("img",{ref:k,src:j.url,width:j.width,height:j.height,alt:"",style:E,onClick:w})})})]}):r.jsx(y,{children:u.imagePath?r.jsxs(r.Fragment,{children:["No Hotspot image found at path ",r.jsx("code",{children:u.imagePath})]}):r.jsxs(r.Fragment,{children:["Define a path in this field using to the image field in this document at"," ",r.jsx("code",{children:"options.hotspotImagePath"})]})}),u.pathRoot&&!M.includes(u.pathRoot)&&r.jsxs(y,{children:['The supplied imageHotspotPathRoot "',u.pathRoot,'" is not valid, falling back to "document". Available values are "',M.join(", "),'".']}),e.renderDefault(e)]})}const D=o.definePlugin({name:"image-hotspot-array",form:{components:{input:e=>{var i,n;if("array"===e.schemaType.jsonType&&(null==(i=e.schemaType.options)?void 0:i.imageHotspot)){const i=null==(n=e.schemaType.options)?void 0:n.imageHotspot;if(i)return r.jsx(_,t(t({},e),{},{imageHotspotOptions:i}))}return e.renderDefault(e)}}}});exports.ImageHotspotArray=_,exports.imageHotspotArrayPlugin=D;
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/Feedback.tsx","../src/Spot.tsx","../src/ImageHotspotArray.tsx","../src/plugin.tsx"],"sourcesContent":["import React, {ReactNode} from 'react'\nimport {Card, Text} from '@sanity/ui'\n\nexport default function Feedback({children}: {children: ReactNode}) {\n return (\n <Card padding={4} radius={2} shadow={1} tone=\"caution\">\n <Text>{children}</Text>\n </Card>\n )\n}\n","/* eslint-disable */\nimport {Box, Text, Tooltip} from '@sanity/ui'\nimport {motion, useMotionValue} from 'framer-motion'\nimport get from 'lodash/get'\nimport React, {ComponentType, CSSProperties, ReactElement, useEffect} from 'react'\nimport {FnHotspotMove, HotspotItem} from './ImageHotspotArray'\nimport {ObjectSchemaType, RenderPreviewCallback} from 'sanity'\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: number) => Math.round(num * 100) / 100\n\nexport interface HotspotTooltipProps<HotspotFields = {[key: string]: unknown}> {\n value: HotspotItem<HotspotFields>\n schemaType: ObjectSchemaType\n renderPreview: RenderPreviewCallback\n}\n\ninterface HotspotProps<HotspotFields = {[key: string]: unknown}> {\n value: HotspotItem\n bounds: DOMRectReadOnly\n update: FnHotspotMove\n hotspotDescriptionPath?: string\n tooltip?: ComponentType<HotspotTooltipProps<HotspotFields>>\n index: number\n schemaType: ObjectSchemaType\n renderPreview: RenderPreviewCallback\n}\n\nexport default function Spot({\n value,\n bounds,\n update,\n hotspotDescriptionPath,\n tooltip,\n index,\n schemaType,\n renderPreview,\n}: HotspotProps) {\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 * (value.x / 100)))\n const y = useMotionValue(round(bounds.height * (value.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 * (value.x / 100)))\n y.set(round(bounds.height * (value.y / 100)))\n }, [x, y, value, 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(value._key, safeX, safeY)\n }, [x, y, value, update, bounds])\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={value._key}\n disabled={isDragging}\n portal\n content={\n tooltip && typeof tooltip === 'function' ? (\n React.createElement(tooltip, {value, renderPreview, schemaType})\n ) : (\n <Box padding={2} style={{maxWidth: 200, pointerEvents: `none`}}>\n <Text textOverflow=\"ellipsis\">\n {hotspotDescriptionPath\n ? (get(value, hotspotDescriptionPath) as string)\n : `${value.x}% x ${value.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","/* eslint-disable react/display-name */\n\nimport {getImageDimensions} from '@sanity/asset-utils'\nimport {\n ArrayOfObjectsInputProps,\n ImageValue,\n insert,\n ObjectSchemaType,\n PatchEvent,\n set,\n setIfMissing,\n useClient,\n useFormValue,\n} from 'sanity'\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, {useCallback, useMemo, useRef, useState} from 'react'\n\nimport {IUseResizeObserverCallback, useDebouncedCallback, useResizeObserver} from '@react-hookz/web'\nimport Feedback from './Feedback'\nimport Spot from './Spot'\nimport {ImageHotspotOptions} from './plugin'\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 HotspotItem<HotspotFields = {[key: string]: unknown}> = {\n _key: string\n _type: string\n x: number\n y: number\n} & HotspotFields\n\nexport function ImageHotspotArray(\n props: ArrayOfObjectsInputProps<HotspotItem> & {imageHotspotOptions: ImageHotspotOptions}\n) {\n const {value, onChange, imageHotspotOptions, schemaType, renderPreview} = props\n\n const sanityClient = useClient({apiVersion: '2022-01-01'})\n\n const imageHotspotPathRoot = useMemo(() => {\n const pathRoot = VALID_ROOT_PATHS.includes(imageHotspotOptions.pathRoot ?? '')\n ? imageHotspotOptions.pathRoot\n : 'document'\n return pathRoot === 'document' ? [] : props.path.slice(0, -1)\n }, [imageHotspotOptions.pathRoot, props.path])\n\n const rootObject = useFormValue(imageHotspotPathRoot)\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 = useMemo(() => {\n return get(rootObject, imageHotspotOptions.imagePath) as ImageValue | undefined\n }, [rootObject, imageHotspotOptions.imagePath])\n\n const displayImage = useMemo(() => {\n const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset ?? '')\n const urlFor = (source: ImageValue) => 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, sanityClient])\n\n const handleHotspotImageClick = useCallback(\n (event: any) => {\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: HotspotItem = {\n _key: randomKey(12),\n _type: schemaType.of[0].name,\n x,\n y,\n }\n\n if (imageHotspotOptions?.descriptionPath) {\n newRow[imageHotspotOptions.descriptionPath] = description\n }\n\n onChange(PatchEvent.from([setIfMissing([]), insert([newRow], 'after', [-1])]))\n },\n [imageHotspotOptions, onChange, schemaType]\n )\n\n const handleHotspotMove: FnHotspotMove = 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 [onChange]\n )\n\n const hotspotImageRef = 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) > 0 &&\n value?.map((spot, index) => (\n <Spot\n index={index}\n key={spot._key}\n value={spot}\n bounds={imageRect}\n update={handleHotspotMove}\n hotspotDescriptionPath={imageHotspotOptions?.descriptionPath}\n tooltip={imageHotspotOptions?.tooltip}\n renderPreview={renderPreview}\n schemaType={schemaType.of[0] as ObjectSchemaType}\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 {imageHotspotOptions.imagePath ? (\n <>\n No Hotspot image found at path <code>{imageHotspotOptions.imagePath}</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 {imageHotspotOptions.pathRoot && !VALID_ROOT_PATHS.includes(imageHotspotOptions.pathRoot) && (\n <Feedback>\n The supplied imageHotspotPathRoot \"{imageHotspotOptions.pathRoot}\" is not valid, falling\n back to \"document\". Available values are \"{VALID_ROOT_PATHS.join(', ')}\".\n </Feedback>\n )}\n {props.renderDefault(props as unknown as ArrayOfObjectsInputProps)}\n </Stack>\n )\n}\n","import {ImageHotspotArray, type HotspotItem} from './ImageHotspotArray'\nimport React, {ComponentType} from 'react'\nimport {ArrayOfObjectsInputProps, definePlugin} from 'sanity'\nimport {type HotspotTooltipProps} from './Spot'\n\nexport interface ImageHotspotOptions<HotspotFields = {[key: string]: unknown}> {\n pathRoot?: 'document' | 'parent'\n imagePath: string\n descriptionPath?: string\n tooltip?: ComponentType<HotspotTooltipProps<HotspotFields>>\n}\n\ndeclare module '@sanity/types' {\n export interface ArrayOptions {\n imageHotspot?: ImageHotspotOptions\n }\n}\n\nexport const imageHotspotArrayPlugin = definePlugin({\n name: 'image-hotspot-array',\n form: {\n components: {\n input: (props) => {\n if (props.schemaType.jsonType === 'array' && props.schemaType.options?.imageHotspot) {\n const imageHotspotOptions = props.schemaType.options?.imageHotspot\n if (imageHotspotOptions) {\n return (\n <ImageHotspotArray\n {...(props as unknown as ArrayOfObjectsInputProps<HotspotItem>)}\n imageHotspotOptions={imageHotspotOptions}\n />\n )\n }\n }\n return props.renderDefault(props)\n },\n },\n },\n})\n"],"names":["Feedback","_ref","children","jsx","Card","padding","radius","shadow","tone","Text","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","_ref2","value","bounds","update","hotspotDescriptionPath","tooltip","index","schemaType","renderPreview","isDragging","setIsDragging","React","useState","isHovering","setIsHovering","x","useMotionValue","y","useEffect","set","handleDragEnd","useCallback","currentX","get","currentY","newX","newY","safeX","max","min","safeY","_key","handleDragStart","handleHoverStart","handleHoverEnd","Tooltip","disabled","portal","content","createElement","Box","style","maxWidth","textOverflow","jsxs","motion","div","drag","dragConstraints","dragElastic","dragMomentum","onDragEnd","onDragStart","onHoverStart","onHoverEnd","_objectSpread","imageStyle","VALID_ROOT_PATHS","ImageHotspotArray","props","_a","onChange","imageHotspotOptions","sanityClient","useClient","apiVersion","imageHotspotPathRoot","useMemo","includes","pathRoot","path","slice","rootObject","useFormValue","hotspotImage","imagePath","displayImage","_b","builder","imageUrlBuilder","dataset","config","asset","aspectRatio","getImageDimensions","url","source","image","handleHotspotImageClick","event","nativeEvent","Number","offsetX","srcElement","toFixed","offsetY","description","concat","newRow","randomKey","_type","of","name","descriptionPath","PatchEvent","from","setIfMissing","insert","handleHotspotMove","key","hotspotImageRef","useRef","imageRect","setImageRect","updateImageRectCallback","useDebouncedCallback","e","contentRect","useResizeObserver","Stack","space","length","map","spot","__unstable_checkered","Flex","align","justify","ref","src","alt","onClick","Fragment","join","renderDefault","imageHotspotArrayPlugin","definePlugin","form","components","input","jsonType","options","imageHotspot"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
|
|
3
|
+
import {ArrayOfObjectsInputProps} from 'sanity'
|
|
4
|
+
import {ComponentType} from 'react'
|
|
5
|
+
import {ObjectSchemaType} from 'sanity'
|
|
6
|
+
import {Plugin as Plugin_2} from 'sanity'
|
|
7
|
+
import {RenderPreviewCallback} from 'sanity'
|
|
8
|
+
|
|
9
|
+
export declare type HotspotItem<
|
|
10
|
+
HotspotFields = {
|
|
11
|
+
[key: string]: unknown
|
|
12
|
+
}
|
|
13
|
+
> = {
|
|
14
|
+
_key: string
|
|
15
|
+
_type: string
|
|
16
|
+
x: number
|
|
17
|
+
y: number
|
|
18
|
+
} & HotspotFields
|
|
19
|
+
|
|
20
|
+
export declare interface HotspotTooltipProps<
|
|
21
|
+
HotspotFields = {
|
|
22
|
+
[key: string]: unknown
|
|
23
|
+
}
|
|
24
|
+
> {
|
|
25
|
+
value: HotspotItem<HotspotFields>
|
|
26
|
+
schemaType: ObjectSchemaType
|
|
27
|
+
renderPreview: RenderPreviewCallback
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export declare function ImageHotspotArray(
|
|
31
|
+
props: ArrayOfObjectsInputProps<HotspotItem> & {
|
|
32
|
+
imageHotspotOptions: ImageHotspotOptions
|
|
33
|
+
}
|
|
34
|
+
): JSX.Element
|
|
35
|
+
|
|
36
|
+
export declare const imageHotspotArrayPlugin: Plugin_2<void>
|
|
37
|
+
|
|
38
|
+
export declare interface ImageHotspotOptions<
|
|
39
|
+
HotspotFields = {
|
|
40
|
+
[key: string]: unknown
|
|
41
|
+
}
|
|
42
|
+
> {
|
|
43
|
+
pathRoot?: 'document' | 'parent'
|
|
44
|
+
imagePath: string
|
|
45
|
+
descriptionPath?: string
|
|
46
|
+
tooltip?: ComponentType<HotspotTooltipProps<HotspotFields>>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export {}
|
|
50
|
+
|
|
51
|
+
declare module '@sanity/types' {
|
|
52
|
+
interface ArrayOptions {
|
|
53
|
+
imageHotspot?: ImageHotspotOptions;
|
|
54
|
+
}
|
|
55
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-hotspot-array",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A configurable Custom Input for Arrays that will add and update items by clicking on an Image",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -12,69 +12,80 @@
|
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "git
|
|
15
|
+
"url": "git@github.com:sanity-io/sanity-plugin-hotspot-array.git"
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"author": "Sanity.io <hello@sanity.io>",
|
|
19
|
-
"
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./lib/src/index.d.ts",
|
|
22
|
+
"source": "./src/index.ts",
|
|
23
|
+
"import": "./lib/index.esm.js",
|
|
24
|
+
"require": "./lib/index.js",
|
|
25
|
+
"default": "./lib/index.esm.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"main": "./lib/index.js",
|
|
30
|
+
"module": "./lib/index.esm.js",
|
|
31
|
+
"source": "./src/index.ts",
|
|
32
|
+
"types": "./lib/src/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"src",
|
|
35
|
+
"lib",
|
|
36
|
+
"v2-incompatible.js",
|
|
37
|
+
"sanity.json"
|
|
38
|
+
],
|
|
20
39
|
"scripts": {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
40
|
+
"prebuild": "npm run clean && plugin-kit verify-package --silent && pkg-utils",
|
|
41
|
+
"build": "pkg-utils build --strict",
|
|
42
|
+
"clean": "rimraf lib",
|
|
43
|
+
"link-watch": "plugin-kit link-watch",
|
|
44
|
+
"lint": "eslint .",
|
|
25
45
|
"lint:fix": "eslint . --fix",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"watch": "
|
|
29
|
-
},
|
|
30
|
-
"husky": {
|
|
31
|
-
"hooks": {
|
|
32
|
-
"pre-commit": "npm run lint"
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
"prettier": {
|
|
36
|
-
"bracketSpacing": false,
|
|
37
|
-
"printWidth": 100,
|
|
38
|
-
"semi": false,
|
|
39
|
-
"singleQuote": true
|
|
40
|
-
},
|
|
41
|
-
"eslintConfig": {
|
|
42
|
-
"parser": "sanipack/babel/eslint-parser",
|
|
43
|
-
"extends": [
|
|
44
|
-
"sanity",
|
|
45
|
-
"sanity/react",
|
|
46
|
-
"prettier"
|
|
47
|
-
],
|
|
48
|
-
"ignorePatterns": [
|
|
49
|
-
"lib/**/"
|
|
50
|
-
]
|
|
46
|
+
"prepare": "husky install",
|
|
47
|
+
"prepublishOnly": "npm run build",
|
|
48
|
+
"watch": "pkg-utils watch"
|
|
51
49
|
},
|
|
52
50
|
"dependencies": {
|
|
53
51
|
"@react-hookz/web": "^14.2.2",
|
|
54
|
-
"@sanity/asset-utils": "^1.3
|
|
55
|
-
"@sanity/
|
|
56
|
-
"@sanity/
|
|
57
|
-
"@sanity/ui": "^0.
|
|
52
|
+
"@sanity/asset-utils": "^1.2.3",
|
|
53
|
+
"@sanity/image-url": "^1.0.1",
|
|
54
|
+
"@sanity/incompatible-plugin": "^1.0.4",
|
|
55
|
+
"@sanity/ui": "^1.0.0",
|
|
56
|
+
"@sanity/util": "^3.0.0",
|
|
58
57
|
"framer-motion": "^6.3.11",
|
|
59
|
-
"
|
|
58
|
+
"lodash": "^4.17.21"
|
|
60
59
|
},
|
|
61
60
|
"devDependencies": {
|
|
62
61
|
"@commitlint/cli": "^17.2.0",
|
|
63
62
|
"@commitlint/config-conventional": "^17.2.0",
|
|
63
|
+
"@sanity/pkg-utils": "^1.17.2",
|
|
64
|
+
"@sanity/plugin-kit": "^2.1.5",
|
|
64
65
|
"@sanity/semantic-release-preset": "^2.0.2",
|
|
65
|
-
"eslint": "^
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
|
67
|
+
"@typescript-eslint/parser": "^5.42.0",
|
|
68
|
+
"eslint": "^8.26.0",
|
|
66
69
|
"eslint-config-prettier": "^8.5.0",
|
|
67
70
|
"eslint-config-sanity": "^6.0.0",
|
|
68
|
-
"eslint-plugin-
|
|
71
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
72
|
+
"eslint-plugin-react": "^7.31.10",
|
|
73
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
74
|
+
"husky": "^8.0.1",
|
|
75
|
+
"lint-staged": "^13.0.3",
|
|
69
76
|
"pinst": "^2.1.6",
|
|
70
|
-
"prettier": "^2.7.
|
|
71
|
-
"
|
|
77
|
+
"prettier": "^2.7.1",
|
|
78
|
+
"prettier-plugin-packagejson": "^2.3.0",
|
|
79
|
+
"react": "^18",
|
|
80
|
+
"rimraf": "^3.0.2",
|
|
81
|
+
"sanity": "^3.0.0",
|
|
82
|
+
"typescript": "^4.8.4"
|
|
72
83
|
},
|
|
73
84
|
"peerDependencies": {
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
85
|
+
"react": "^18",
|
|
86
|
+
"sanity": "^3.0.0"
|
|
87
|
+
},
|
|
88
|
+
"engines": {
|
|
89
|
+
"node": ">=14"
|
|
79
90
|
}
|
|
80
91
|
}
|
package/sanity.json
CHANGED
package/src/Feedback.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, {ReactNode} from 'react'
|
|
2
2
|
import {Card, Text} from '@sanity/ui'
|
|
3
3
|
|
|
4
|
-
export default function Feedback({children}) {
|
|
4
|
+
export default function Feedback({children}: {children: ReactNode}) {
|
|
5
5
|
return (
|
|
6
6
|
<Card padding={4} radius={2} shadow={1} tone="caution">
|
|
7
7
|
<Text>{children}</Text>
|