sanity-plugin-hotspot-array 0.0.8 → 0.1.0-v3-studio.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Simeon Griggs
3
+ Copyright (c) 2022 Simeon Griggs
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # sanity-plugin-hotspot-array
2
2
 
3
+ > > **NOTE**
4
+ >
5
+ > This is the **Sanity Studio v3 version** of sanity-plugin-hotspot-array.
6
+ >
7
+ > For the v2 version, please refer to the [v2-branch](https://github.com/sanity-io/sanity-plugin-hotspot-array).
8
+
9
+ ## What is it?
10
+
3
11
  A configurable Custom Input for Arrays that will add and update items by clicking on an Image
4
12
 
5
13
  <img src="https://user-images.githubusercontent.com/209129/174171697-57319ebc-03a7-4d82-a73e-b6effcb0b3ba.gif" width="600" />
@@ -7,49 +15,73 @@ A configurable Custom Input for Arrays that will add and update items by clickin
7
15
  ## Installation
8
16
 
9
17
  ```
10
- sanity install hotspot-array
18
+ npm install --save sanity-plugin-hotspot-array@studio-v3
19
+ ```
20
+
21
+ or
22
+
23
+ ```
24
+ yarn add sanity-plugin-hotspot-array@studio-v3
11
25
  ```
12
26
 
13
- ## Setup
14
27
 
15
- Import the `HotspotArray` component from this package, into your schema. And insert it as the `inputComponent` of an `array` field.
28
+ ## Usage
29
+
30
+ Add it as a plugin in sanity.config.ts (or .js):
31
+
32
+ ```js
33
+ import { imageHotspotArray } from "sanity-plugin-hotspot-array";
34
+
35
+ export default defineConfig({
36
+ // ...
37
+ plugins: [
38
+ imageHotspotArray(),
39
+ ]
40
+ })
41
+ ```
42
+
43
+ Now you will have `imageHotspot` available as an options on `array` fields:
16
44
 
17
45
  ```js
18
- import HotspotArray from 'sanity-plugin-hotspot-array'
46
+ import {defineType, defineField} from 'sanity'
19
47
 
20
- export default {
48
+ export const productSchema = defineType({
21
49
  name: `product`,
22
50
  title: `Product`,
23
51
  type: `document`,
24
52
  fields: [
25
- {
53
+ defineField({
26
54
  name: `hotspots`,
27
55
  type: `array`,
28
- inputComponent: HotspotArray,
29
56
  of: [
30
57
  // see `Spot object` setup below
31
58
  ],
32
59
  options: {
33
- // see `Image and description path` setup below
34
- hotspotImagePath: `featureImage`,
35
- hotspotDescriptionPath: `details`,
36
- // see `Custom tooltip` setup below
37
- hotspotTooltip: undefined,
60
+ // plugin adds support for this option
61
+ imageHotspot: {
62
+ // see `Image and description path` setup below
63
+ imagePath: `featureImage`,
64
+ descriptionPath: `details`,
65
+ // see `Custom tooltip` setup below
66
+ tooltip: undefined,
67
+ }
38
68
  },
39
- },
69
+ }),
40
70
  // ...all your other fields
71
+ // ...of which one should be featureImage in this example
41
72
  ],
42
- }
73
+ })
43
74
  ```
75
+ There is no need to provide an explicit input component, as that is handled by the plugin.
44
76
 
45
77
  The plugin makes a number of assumptions to add and update data in the array. Including:
46
78
 
47
- - The `array` field is an array of objects
79
+ - The `array` field is an array of objects, with a single object type
48
80
  - The object contains two number fields named `x` and `y`
49
81
  - You'll want to save those values as % from the top left of the image
50
82
  - The same document contains the image you want to add hotspots to
51
83
 
52
- ### Image and description paths
84
+ ### Image path
53
85
 
54
86
  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.
55
87
 
@@ -57,15 +89,31 @@ For example, if you want to add hotspots to an image, and that image is uploaded
57
89
 
58
90
  ```js
59
91
  options: {
60
- hotspotImagePath: `featureImage`
92
+ imageHotspot: {
93
+ imagePath: `featureImage`
94
+ }
95
+ }
96
+ ```
97
+
98
+ To pick the image out of the hotspot-array parent object, use
99
+ ```js
100
+ options: {
101
+ imageHotspot: {
102
+ pathRoot: 'parent'
103
+ }
61
104
  }
62
105
  ```
63
106
 
64
- 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. Add a path **relative to the spot object** for this field.
107
+ ### Description path
108
+
109
+ 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.
110
+ Add a path **relative to the spot object** for this field.
65
111
 
66
112
  ```js
67
113
  options: {
68
- hotspotDescriptionPath: `details`
114
+ imageHotspot: {
115
+ descriptionPath: `details`
116
+ }
69
117
  }
70
118
  ```
71
119
 
@@ -74,7 +122,7 @@ options: {
74
122
  Here's an example object schema complete with initial values, validation, fieldsets and a styled preview.
75
123
 
76
124
  ```js
77
- {
125
+ defineField({
78
126
  name: 'spot',
79
127
  type: 'object',
80
128
  fieldsets: [{name: 'position', options: {columns: 2}}],
@@ -110,28 +158,66 @@ Here's an example object schema complete with initial values, validation, fields
110
158
  }
111
159
  },
112
160
  },
113
- }
161
+ })
114
162
  ```
115
163
 
116
164
  ## Custom tooltip
117
165
 
118
- You can customise the Tooltip to display any Component, it will accept a single prop `spot` which contains the values of the object.
166
+ You can customise the Tooltip to display any Component, which will receive `value` (the hotspot value with x and y),
167
+ `schemaType` (schemaType of the hotspot value), and `renderPreview` (callback for rendering Studio preview).
119
168
 
120
- In this example our `spot` object has a `reference` field to the `product` schema type, and will show a Document Preview.
169
+ ### Example 1 - use default hotspot preview
170
+
171
+ ```tsx
172
+ import { Box } from "@sanity/ui";
173
+ import { HotspotTooltipProps } from "sanity-plugin-hotspot-array";
174
+
175
+ export function HotspotPreview({
176
+ value,
177
+ schemaType,
178
+ renderPreview,
179
+ }: HotspotTooltipProps) {
180
+ return (
181
+ <Box padding={2} style={{ minWidth: 200 }}>
182
+ {renderPreview({
183
+ value,
184
+ schemaType,
185
+ layout: "default",
186
+ })}
187
+ </Box>
188
+ );
189
+ }
190
+ ```
191
+
192
+ Then back in your schema definition
121
193
 
122
194
  ```js
123
- // Setup a custom tooltip component
124
- import Preview from 'part:@sanity/base/preview'
125
- import schema from 'part:@sanity/base/schema'
195
+ options: {
196
+ imageHotspot: {
197
+ tooltip: HotspotPreview
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### Example 2 - reference value in hotspot
203
+ In this example our `value` object has a `reference` field to the `product` schema type, and will show a document preview.
204
+
205
+ ```jsx
206
+ import {useSchema }from 'sanity'
126
207
  import {Box} from '@sanity/ui'
127
208
 
128
- export default function ProductPreview({spot}) {
209
+ export function ProductPreview({value, renderPreview}) {
210
+ const productSchemaType = useSchema().get('product')
129
211
  return (
130
212
  <Box padding={2} style={{minWidth: 200}}>
131
- {spot?.product?._ref ? (
132
- <Preview value={{_id: spot.product._ref}} type={schema.get(`product`)} />
213
+ {value?.product?._ref ? (
214
+ renderPreview({
215
+ value,
216
+ schemaType: productSchemaType,
217
+ layout: "default"
218
+ })
133
219
  ) : (
134
- `No Reference Selected`
220
+ `No reference selected`
135
221
  )}
136
222
  </Box>
137
223
  )
@@ -141,16 +227,28 @@ export default function ProductPreview({spot}) {
141
227
  Then back in your schema definition
142
228
 
143
229
  ```js
144
- import HotspotArray from 'sanity-plugin-hotspot-array'
145
- import ProductPreview from '../../components/ProductPreview'
146
-
147
230
  options: {
148
- hotspotImagePath: `hotspotImage`,
149
- hotspotTooltip: ProductPreview,
150
- },
231
+ imageHotspot: {
232
+ tooltip: ProductPreview
233
+ }
234
+ }
151
235
  ```
152
236
 
153
237
  ## License
154
238
 
155
- MIT © Simeon Griggs
156
- See LICENSE
239
+ MIT-licensed. See LICENSE.
240
+
241
+ ## Develop & test
242
+
243
+ This plugin uses [@sanity/plugin-kit](https://github.com/sanity-io/plugin-kit)
244
+ with default configuration for build & watch scripts.
245
+
246
+ See [Testing a plugin in Sanity Studio](https://github.com/sanity-io/plugin-kit#testing-a-plugin-in-sanity-studio)
247
+ on how to run this plugin with hotreload in the studio.
248
+
249
+ ### Release new version
250
+
251
+ Run ["CI & Release" workflow](https://github.com/sanity-io/sanity-plugin-hotspot-array/actions/workflows/main.yml).
252
+ Make sure to select the main branch and check "Release new version".
253
+
254
+ Semantic release will only release on configured branches, so it is safe to run release on any branch.
@@ -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
@@ -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,75 +1,89 @@
1
1
  {
2
2
  "name": "sanity-plugin-hotspot-array",
3
- "version": "0.0.8",
3
+ "version": "0.1.0-v3-studio.2",
4
4
  "description": "A configurable Custom Input for Arrays that will add and update items by clicking on an Image",
5
- "main": "lib/HotspotArray.js",
5
+ "author": "Simeon Griggs <simeon@sanity.io>",
6
+ "license": "MIT",
7
+ "main": "./lib/index.js",
8
+ "source": "./src/index.ts",
9
+ "module": "./lib/index.esm.js",
10
+ "types": "./lib/src/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/src/index.d.ts",
14
+ "source": "./src/index.ts",
15
+ "import": "./lib/index.esm.js",
16
+ "require": "./lib/index.js",
17
+ "default": "./lib/index.esm.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "src",
22
+ "lib",
23
+ "v2-incompatible.js",
24
+ "sanity.json"
25
+ ],
26
+ "engines": {
27
+ "node": ">=14.0.0"
28
+ },
6
29
  "scripts": {
7
- "build": "sanipack build",
8
- "watch": "sanipack build --watch",
9
- "_postinstall": "husky install",
10
- "prepublishOnly": "pinst --disable && sanipack build && sanipack verify",
11
- "postpublish": "pinst --enable",
30
+ "build": "pkg-utils build",
31
+ "watch": "pkg-utils watch",
32
+ "prepublishOnly": "npm run build",
12
33
  "lint": "eslint .",
13
- "lint:fix": "eslint . --fix"
14
- },
15
- "husky": {
16
- "hooks": {
17
- "pre-commit": "npm run lint:fix"
18
- }
34
+ "lint:fix": "eslint . --fix",
35
+ "clean": "rimraf lib",
36
+ "prebuild": "npm run clean && plugin-kit verify-package --silent && pkg-utils",
37
+ "link-watch": "plugin-kit link-watch",
38
+ "prepare": "husky install"
19
39
  },
20
40
  "repository": {
21
41
  "type": "git",
22
- "url": "git+ssh://git@github.com/SimeonGriggs/sanity-plugin-hotspot-array.git"
42
+ "url": "git@github.com:sanity-io/sanity-plugin-hotspot-array.git"
23
43
  },
24
44
  "keywords": [
25
45
  "sanity",
26
46
  "sanity-plugin"
27
47
  ],
28
- "author": "Simeon Griggs <simeon@sanity.io>",
29
- "license": "MIT",
30
48
  "dependencies": {
31
49
  "@react-hookz/web": "^14.2.2",
32
50
  "@sanity/asset-utils": "^1.2.3",
33
- "@sanity/base": "^2.30.1",
34
- "@sanity/form-builder": "^2.30.1",
35
- "@sanity/ui": "^0.37.12",
36
- "framer-motion": "^6.3.11",
37
- "husky": "^8.0.1"
51
+ "@sanity/incompatible-plugin": "^1.0.4",
52
+ "@sanity/ui": "^1.0.0-beta.31",
53
+ "@sanity/util": "3.0.0-rc.0",
54
+ "framer-motion": "^6.3.11"
38
55
  },
39
56
  "devDependencies": {
40
- "eslint": "^8.17.0",
57
+ "@commitlint/cli": "^17.2.0",
58
+ "@commitlint/config-conventional": "^17.2.0",
59
+ "@sanity/pkg-utils": "^1.16.2",
60
+ "@sanity/plugin-kit": "^2.0.7",
61
+ "@sanity/semantic-release-preset": "^2.0.2",
62
+ "@typescript-eslint/eslint-plugin": "^5.42.0",
63
+ "@typescript-eslint/parser": "^5.42.0",
64
+ "eslint": "^8.26.0",
41
65
  "eslint-config-prettier": "^8.5.0",
42
66
  "eslint-config-sanity": "^6.0.0",
43
- "eslint-plugin-react": "^7.30.0",
67
+ "eslint-plugin-prettier": "^4.2.1",
68
+ "eslint-plugin-react": "^7.31.10",
69
+ "eslint-plugin-react-hooks": "^4.6.0",
70
+ "husky": "^8.0.1",
71
+ "lint-staged": "^13.0.3",
44
72
  "pinst": "^2.1.6",
45
- "prettier": "^2.7.0",
46
- "sanipack": "^2.1.0"
73
+ "prettier": "^2.7.1",
74
+ "react": "^18.0.0",
75
+ "rimraf": "^3.0.2",
76
+ "sanity": "dev-preview || 3.0.0-rc.0",
77
+ "typescript": "^4.8.4"
47
78
  },
48
79
  "peerDependencies": {
49
80
  "@sanity/image-url": "1.0.1",
50
- "@sanity/util": "2.29.5",
51
81
  "lodash": "4.17.21",
52
- "react": "^17.0.2"
82
+ "react": "^18.0.0",
83
+ "sanity": "dev-preview || 3.0.0-rc.0"
53
84
  },
54
85
  "bugs": {
55
- "url": "https://github.com/SimeonGriggs/sanity-plugin-hotspot-array/issues"
56
- },
57
- "homepage": "https://github.com/SimeonGriggs/sanity-plugin-hotspot-array#readme",
58
- "prettier": {
59
- "semi": false,
60
- "printWidth": 100,
61
- "bracketSpacing": false,
62
- "singleQuote": true
86
+ "url": "https://github.com/sanity-io/sanity-plugin-hotspot-array/issues"
63
87
  },
64
- "eslintConfig": {
65
- "parser": "sanipack/babel/eslint-parser",
66
- "extends": [
67
- "sanity",
68
- "sanity/react",
69
- "prettier"
70
- ],
71
- "ignorePatterns": [
72
- "lib/**/"
73
- ]
74
- }
88
+ "homepage": "https://github.com/sanity-io/sanity-plugin-hotspot-array#readme"
75
89
  }
package/sanity.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
- "paths": {
3
- "source": "./src",
4
- "compiled": "./lib"
5
- },
6
- "parts": []
2
+ "parts": [
3
+ {
4
+ "implements": "part:@sanity/base/sanity-root",
5
+ "path": "./v2-incompatible.js"
6
+ }
7
+ ]
7
8
  }
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>