@sanity/assist 1.0.0

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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/index.d.ts +52 -0
  4. package/dist/index.esm.js +2341 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.js +2341 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +98 -0
  9. package/sanity.json +8 -0
  10. package/src/_lib/connector/ConnectFromRegion.tsx +24 -0
  11. package/src/_lib/connector/ConnectToRegion.tsx +22 -0
  12. package/src/_lib/connector/ConnectorRegion.tsx +23 -0
  13. package/src/_lib/connector/ConnectorsProvider.tsx +19 -0
  14. package/src/_lib/connector/ConnectorsStore.ts +122 -0
  15. package/src/_lib/connector/ConnectorsStoreContext.ts +4 -0
  16. package/src/_lib/connector/helpers.ts +5 -0
  17. package/src/_lib/connector/index.ts +9 -0
  18. package/src/_lib/connector/mapConnectorToLine.ts +83 -0
  19. package/src/_lib/connector/types.ts +56 -0
  20. package/src/_lib/connector/useConnectorsStore.ts +13 -0
  21. package/src/_lib/connector/useRegionRects.ts +141 -0
  22. package/src/_lib/fixedListenQuery.ts +101 -0
  23. package/src/_lib/form/DocumentForm.tsx +197 -0
  24. package/src/_lib/form/helpers.ts +31 -0
  25. package/src/_lib/form/index.ts +1 -0
  26. package/src/_lib/randomKey.ts +29 -0
  27. package/src/_lib/useListeningQuery.ts +61 -0
  28. package/src/_lib/usePrevious.ts +9 -0
  29. package/src/assistConnectors/AssistConnectorsOverlay.tsx +132 -0
  30. package/src/assistConnectors/ConnectorPath.tsx +62 -0
  31. package/src/assistConnectors/draw/arrowPath.ts +9 -0
  32. package/src/assistConnectors/draw/connectorPath.ts +142 -0
  33. package/src/assistConnectors/index.ts +1 -0
  34. package/src/assistDocument/AssistDocumentContext.tsx +31 -0
  35. package/src/assistDocument/AssistDocumentContextProvider.tsx +17 -0
  36. package/src/assistDocument/AssistDocumentInput.tsx +46 -0
  37. package/src/assistDocument/RequestRunInstructionProvider.tsx +50 -0
  38. package/src/assistDocument/components/AssistDocumentForm.tsx +188 -0
  39. package/src/assistDocument/components/FieldRefPreview.tsx +27 -0
  40. package/src/assistDocument/components/InstructionsArrayField.tsx +8 -0
  41. package/src/assistDocument/components/InstructionsArrayInput.tsx +26 -0
  42. package/src/assistDocument/components/SelectedFieldContext.tsx +10 -0
  43. package/src/assistDocument/components/generic/HiddenFieldTitle.tsx +5 -0
  44. package/src/assistDocument/components/helpers.ts +21 -0
  45. package/src/assistDocument/components/instruction/BackToInstructionsLink.tsx +31 -0
  46. package/src/assistDocument/components/instruction/FieldRefInput.tsx +33 -0
  47. package/src/assistDocument/components/instruction/InstructionInput.tsx +87 -0
  48. package/src/assistDocument/components/instruction/PromptInput.tsx +52 -0
  49. package/src/assistDocument/components/instruction/appearance/IconInput.tsx +46 -0
  50. package/src/assistDocument/components/instruction/appearance/InstructionVisibility.tsx +37 -0
  51. package/src/assistDocument/hooks/useAssistDocumentContextValue.tsx +68 -0
  52. package/src/assistDocument/hooks/useDocumentState.ts +6 -0
  53. package/src/assistDocument/hooks/useInstructionToaster.tsx +74 -0
  54. package/src/assistDocument/hooks/useStudioAssistDocument.ts +119 -0
  55. package/src/assistDocument/index.ts +1 -0
  56. package/src/assistFormComponents/AssistField.tsx +51 -0
  57. package/src/assistFormComponents/AssistFormBlock.tsx +31 -0
  58. package/src/assistFormComponents/AssistInlineFormBlock.tsx +14 -0
  59. package/src/assistFormComponents/AssistItem.tsx +20 -0
  60. package/src/assistFormComponents/validation/listItem.tsx +63 -0
  61. package/src/assistFormComponents/validation/validationList.tsx +89 -0
  62. package/src/assistInspector/AssistInspector.tsx +379 -0
  63. package/src/assistInspector/FieldAutocomplete.tsx +119 -0
  64. package/src/assistInspector/InstructionTaskHistoryButton.tsx +261 -0
  65. package/src/assistInspector/constants.ts +1 -0
  66. package/src/assistInspector/helpers.ts +125 -0
  67. package/src/assistInspector/index.ts +26 -0
  68. package/src/assistLayout/AiAssistanceConfigContext.tsx +81 -0
  69. package/src/assistLayout/AlphaMigration.tsx +311 -0
  70. package/src/assistLayout/AssistLayout.tsx +38 -0
  71. package/src/assistLayout/RunInstructionProvider.tsx +222 -0
  72. package/src/components/AssistFeatureBadge.tsx +9 -0
  73. package/src/components/Delay.tsx +25 -0
  74. package/src/components/HideReferenceChangedBannerInput.tsx +25 -0
  75. package/src/components/SafeValueInput.tsx +73 -0
  76. package/src/components/TimeAgo.tsx +18 -0
  77. package/src/constants.ts +20 -0
  78. package/src/fieldActions/PrivateIcon.tsx +20 -0
  79. package/src/fieldActions/assistFieldActions.tsx +230 -0
  80. package/src/globals.d.ts +4 -0
  81. package/src/helpers/assistSupported.ts +44 -0
  82. package/src/helpers/ids.ts +19 -0
  83. package/src/helpers/misc.ts +16 -0
  84. package/src/helpers/typeUtils.ts +15 -0
  85. package/src/helpers/useAssistSupported.ts +10 -0
  86. package/src/index.ts +6 -0
  87. package/src/legacy-types.ts +72 -0
  88. package/src/onboarding/FieldActionsOnboarding.tsx +90 -0
  89. package/src/onboarding/FirstAssistedPathProvider.tsx +29 -0
  90. package/src/onboarding/InspectorOnboarding.tsx +46 -0
  91. package/src/onboarding/onboardingStore.ts +33 -0
  92. package/src/plugin.tsx +80 -0
  93. package/src/presence/AiFieldPresence.tsx +28 -0
  94. package/src/presence/AssistAvatar.tsx +96 -0
  95. package/src/presence/AssistDocumentPresence.tsx +58 -0
  96. package/src/presence/useAssistPresence.ts +61 -0
  97. package/src/schemas/assistDocumentSchema.tsx +450 -0
  98. package/src/schemas/contextDocumentSchema.tsx +56 -0
  99. package/src/schemas/index.ts +25 -0
  100. package/src/schemas/serialize/SchemTypeTool.tsx +102 -0
  101. package/src/schemas/serialize/schemaUtils.ts +37 -0
  102. package/src/schemas/serialize/serializeSchema.test.ts +382 -0
  103. package/src/schemas/serialize/serializeSchema.ts +162 -0
  104. package/src/schemas/serializedSchemaTypeSchema.ts +59 -0
  105. package/src/schemas/typeDefExtensions.ts +30 -0
  106. package/src/types.ts +167 -0
  107. package/src/useApiClient.ts +140 -0
  108. package/src/vite.config.ts +9 -0
  109. package/v2-incompatible.js +11 -0
package/package.json ADDED
@@ -0,0 +1,98 @@
1
+ {
2
+ "name": "@sanity/assist",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "keywords": [
6
+ "sanity",
7
+ "sanity-plugin",
8
+ "ai"
9
+ ],
10
+ "homepage": "https://github.com/sanity-io/assist#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/sanity-io/sanity/assist/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+ssh://git@github.com/sanity-io/assist.git"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Sanity <hello@sanity.io>",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "source": "./src/index.ts",
24
+ "require": "./dist/index.js",
25
+ "import": "./dist/index.esm.js",
26
+ "default": "./dist/index.esm.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "main": "./dist/index.js",
31
+ "module": "./dist/index.esm.js",
32
+ "source": "./src/index.ts",
33
+ "types": "./dist/index.d.ts",
34
+ "files": [
35
+ "dist",
36
+ "sanity.json",
37
+ "src",
38
+ "v2-incompatible.js"
39
+ ],
40
+ "scripts": {
41
+ "build": "run-s clean && plugin-kit verify-package --silent && pkg-utils build --strict && pkg-utils --strict",
42
+ "clean": "rimraf dist",
43
+ "format": "prettier --write --cache --ignore-unknown .",
44
+ "link-watch": "plugin-kit link-watch",
45
+ "lint": "eslint src/**/* --ext .tsx,.ts",
46
+ "prepublishOnly": "run-s build",
47
+ "watch": "pkg-utils watch --strict",
48
+ "compile": "tsc --noEmit",
49
+ "test": "vitest",
50
+ "release": "semantic-release"
51
+ },
52
+ "dependencies": {
53
+ "@sanity/icons": "^2.4.0",
54
+ "@sanity/incompatible-plugin": "^1.0.4",
55
+ "@sanity/ui": "^1.6.0",
56
+ "react-fast-compare": "^3.2.1",
57
+ "rxjs": "^7.8.0",
58
+ "rxjs-exhaustmap-with-trailing": "^2.1.1"
59
+ },
60
+ "devDependencies": {
61
+ "@commitlint/cli": "^17.6.6",
62
+ "@commitlint/config-conventional": "^17.6.6",
63
+ "@rollup/plugin-image": "^3.0.2",
64
+ "@sanity/pkg-utils": "^2.2.13",
65
+ "@sanity/plugin-kit": "^3.1.7",
66
+ "@sanity/semantic-release-preset": "^4.1.1",
67
+ "@types/react": "^18.0.28",
68
+ "@types/styled-components": "^5.1.26",
69
+ "@typescript-eslint/eslint-plugin": "^5.56.0",
70
+ "@typescript-eslint/parser": "^5.56.0",
71
+ "eslint": "^8.36.0",
72
+ "eslint-config-prettier": "^8.8.0",
73
+ "eslint-config-sanity": "^6.0.0",
74
+ "eslint-plugin-prettier": "^4.2.1",
75
+ "eslint-plugin-react": "^7.32.2",
76
+ "eslint-plugin-react-hooks": "^4.6.0",
77
+ "npm-run-all": "^4.1.5",
78
+ "react": "^18.2.0",
79
+ "react-dom": "^18.2.0",
80
+ "react-is": "^18.2.0",
81
+ "rimraf": "^4.4.0",
82
+ "sanity": "3.13.0",
83
+ "semantic-release": "^21.0.5",
84
+ "styled-components": "^5.3.9",
85
+ "typescript": "^5.1.3",
86
+ "vitest": "^0.32.2"
87
+ },
88
+ "peerDependencies": {
89
+ "react": "^18",
90
+ "sanity": "3.13.0"
91
+ },
92
+ "engines": {
93
+ "node": ">=14"
94
+ },
95
+ "publishConfig": {
96
+ "access": "public"
97
+ }
98
+ }
package/sanity.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "parts": [
3
+ {
4
+ "implements": "part:@sanity/base/sanity-root",
5
+ "path": "./v2-incompatible.js"
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,24 @@
1
+ import {HTMLProps, useEffect, useState} from 'react'
2
+ import {useConnectorsStore} from './useConnectorsStore'
3
+ import {ConnectorRegionRects} from './types'
4
+ import {ConnectorRegion} from './ConnectorRegion'
5
+
6
+ export function ConnectFromRegion(
7
+ props: {_key: string; zIndex: number} & HTMLProps<HTMLDivElement>
8
+ ) {
9
+ const {children, _key: key, zIndex, ...restProps} = props
10
+ const store = useConnectorsStore()
11
+ const [rects, setRects] = useState<ConnectorRegionRects | null>(null)
12
+
13
+ useEffect(() => store.from.subscribe(key, {zIndex}), [key, store, zIndex])
14
+
15
+ useEffect(() => {
16
+ if (rects) store.from.next(key, rects)
17
+ }, [key, rects, store])
18
+
19
+ return (
20
+ <ConnectorRegion {...restProps} onRectsChange={setRects}>
21
+ {children}
22
+ </ConnectorRegion>
23
+ )
24
+ }
@@ -0,0 +1,22 @@
1
+ import {HTMLProps, useEffect, useState} from 'react'
2
+ import {ConnectorRegion} from './ConnectorRegion'
3
+ import {ConnectorRegionRects} from './types'
4
+ import {useConnectorsStore} from './useConnectorsStore'
5
+
6
+ export function ConnectToRegion(props: {_key: string} & HTMLProps<HTMLDivElement>) {
7
+ const {children, _key: key, ...restProps} = props
8
+ const aiConnectors = useConnectorsStore()
9
+ const [rects, setRects] = useState<ConnectorRegionRects | null>(null)
10
+
11
+ useEffect(() => aiConnectors.to.subscribe(key), [aiConnectors, key])
12
+
13
+ useEffect(() => {
14
+ if (rects) aiConnectors.to.next(key, rects)
15
+ }, [aiConnectors, key, rects])
16
+
17
+ return (
18
+ <ConnectorRegion {...restProps} onRectsChange={setRects}>
19
+ {children}
20
+ </ConnectorRegion>
21
+ )
22
+ }
@@ -0,0 +1,23 @@
1
+ import {HTMLProps, useEffect} from 'react'
2
+ import {ConnectorRegionRects} from './types'
3
+ import {useRegionRects} from './useRegionRects'
4
+
5
+ export function ConnectorRegion(
6
+ props: {
7
+ onRectsChange?: (rects: ConnectorRegionRects | null) => void
8
+ } & HTMLProps<HTMLDivElement>
9
+ ) {
10
+ const {children, onRectsChange, ...restProps} = props
11
+
12
+ const {bounds, element, ref} = useRegionRects()
13
+
14
+ useEffect(() => {
15
+ onRectsChange?.(bounds && element ? {bounds, element} : null)
16
+ }, [bounds, element, onRectsChange])
17
+
18
+ return (
19
+ <div {...restProps} ref={ref}>
20
+ {children}
21
+ </div>
22
+ )
23
+ }
@@ -0,0 +1,19 @@
1
+ import {ReactNode, useEffect, useMemo} from 'react'
2
+ import {ConnectorsStore, createConnectorsStore} from './ConnectorsStore'
3
+ import {ConnectorsStoreContext} from './ConnectorsStoreContext'
4
+ import {Connector} from './types'
5
+
6
+ export function ConnectorsProvider(props: {
7
+ children?: ReactNode
8
+ onConnectorsChange?: (connectors: Connector[]) => void
9
+ }) {
10
+ const {children, onConnectorsChange} = props
11
+ const store: ConnectorsStore = useMemo(() => createConnectorsStore(), [])
12
+
13
+ useEffect(
14
+ () => onConnectorsChange && store.connectors.subscribe(onConnectorsChange),
15
+ [onConnectorsChange, store]
16
+ )
17
+
18
+ return <ConnectorsStoreContext.Provider value={store}>{children}</ConnectorsStoreContext.Provider>
19
+ }
@@ -0,0 +1,122 @@
1
+ import {Connector, ConnectorRegionRects} from './types'
2
+
3
+ export interface ConnectorsStore {
4
+ connectors: {
5
+ subscribe: (observer: (connectors: Connector[]) => void) => () => void
6
+ }
7
+
8
+ from: {
9
+ subscribe: (key: string, payload?: Record<string, unknown>) => () => void
10
+ next: (key: string, rects: ConnectorRegionRects) => void
11
+ }
12
+
13
+ to: {
14
+ subscribe: (key: string, payload?: Record<string, unknown>) => () => void
15
+ next: (key: string, rects: ConnectorRegionRects) => void
16
+ }
17
+ }
18
+
19
+ export function createConnectorsStore(): ConnectorsStore {
20
+ const configKeys: string[] = []
21
+ const fieldKeys: string[] = []
22
+
23
+ const channels = {
24
+ from: new Map<string, ConnectorRegionRects | null>(),
25
+ to: new Map<string, ConnectorRegionRects | null>(),
26
+ }
27
+
28
+ const payloads = {
29
+ from: new Map<string, Record<string, unknown> | undefined>(),
30
+ to: new Map<string, Record<string, unknown> | undefined>(),
31
+ }
32
+
33
+ const observers: ((connectors: Connector[]) => void)[] = []
34
+
35
+ function notifyObservers() {
36
+ const connectors: Connector[] = []
37
+
38
+ for (const key of configKeys) {
39
+ const toRects = channels.to.get(key)
40
+ const toPayload = payloads.from.get(key)
41
+
42
+ const fromRects = channels.from.get(key)
43
+ const fromPayload = payloads.from.get(key)
44
+
45
+ if (toRects && fromRects) {
46
+ connectors.push({
47
+ key,
48
+ from: {...fromRects, payload: fromPayload},
49
+ to: {...toRects, payload: toPayload},
50
+ })
51
+ }
52
+ }
53
+
54
+ for (const observer of observers) {
55
+ observer(connectors)
56
+ }
57
+ }
58
+
59
+ return {
60
+ to: {
61
+ subscribe(key, payload) {
62
+ channels.to.set(key, null)
63
+ payloads.to.set(key, payload)
64
+
65
+ configKeys.push(key)
66
+
67
+ return () => {
68
+ channels.to.delete(key)
69
+ payloads.to.delete(key)
70
+
71
+ const idx = configKeys.indexOf(key)
72
+
73
+ if (idx > -1) configKeys.splice(idx, 1)
74
+
75
+ notifyObservers()
76
+ }
77
+ },
78
+ next(key, rects) {
79
+ channels.to.set(key, rects)
80
+
81
+ if (fieldKeys.includes(key)) notifyObservers()
82
+ },
83
+ },
84
+
85
+ connectors: {
86
+ subscribe(observer) {
87
+ observers.push(observer)
88
+
89
+ return () => {
90
+ const idx = observers.indexOf(observer)
91
+
92
+ if (idx > -1) observers.splice(idx, 1)
93
+ }
94
+ },
95
+ },
96
+
97
+ from: {
98
+ subscribe(key, payload) {
99
+ channels.from.set(key, null)
100
+ payloads.from.set(key, payload)
101
+
102
+ fieldKeys.push(key)
103
+
104
+ return () => {
105
+ channels.from.delete(key)
106
+ payloads.from.delete(key)
107
+
108
+ const idx = fieldKeys.indexOf(key)
109
+
110
+ if (idx > -1) fieldKeys.splice(idx, 1)
111
+
112
+ notifyObservers()
113
+ }
114
+ },
115
+ next(key, rects) {
116
+ channels.from.set(key, rects)
117
+
118
+ if (configKeys.includes(key)) notifyObservers()
119
+ },
120
+ },
121
+ }
122
+ }
@@ -0,0 +1,4 @@
1
+ import {createContext} from 'react'
2
+ import {ConnectorsStore} from './ConnectorsStore'
3
+
4
+ export const ConnectorsStoreContext = createContext<ConnectorsStore | null>(null)
@@ -0,0 +1,5 @@
1
+ export function hasOverflowScroll(el: HTMLElement): boolean {
2
+ const overflow = getComputedStyle(el).overflow
3
+
4
+ return overflow.includes('auto') || overflow.includes('hidden') || overflow.includes('scroll')
5
+ }
@@ -0,0 +1,9 @@
1
+ export * from './ConnectFromRegion'
2
+ export * from './ConnectToRegion'
3
+ export * from './ConnectorRegion'
4
+ export * from './ConnectorsProvider'
5
+ export * from './ConnectorsStore'
6
+ export * from './ConnectorsStoreContext'
7
+ export * from './mapConnectorToLine'
8
+ export * from './types'
9
+ export * from './useConnectorsStore'
@@ -0,0 +1,83 @@
1
+ import {
2
+ ConnectorLine,
3
+ ConnectorLinePoint,
4
+ ConnectorOptions,
5
+ ConnectorRegionRects,
6
+ Rect,
7
+ } from './types'
8
+
9
+ function getConnectorLinePoint(
10
+ options: ConnectorOptions,
11
+ rect: Rect,
12
+ bounds: Rect
13
+ ): ConnectorLinePoint {
14
+ const centerY = rect.y + rect.h / 2
15
+ const isAbove = rect.y + rect.h < bounds.y + options.arrow.marginY
16
+ const isBelow = rect.y > bounds.y + bounds.h - options.arrow.marginY
17
+
18
+ return {
19
+ bounds,
20
+ x: rect.x,
21
+ y: centerY,
22
+ centerY,
23
+ startY: rect.y + options.path.marginY,
24
+ endY: rect.y + rect.h - options.path.marginY,
25
+ isAbove,
26
+ isBelow,
27
+ outOfBounds: isAbove || isBelow,
28
+ }
29
+ }
30
+
31
+ export function mapConnectorToLine(
32
+ options: ConnectorOptions,
33
+ connector: {from: ConnectorRegionRects; to: ConnectorRegionRects}
34
+ ): ConnectorLine {
35
+ const fromBounds: Rect = {
36
+ y: connector.from.bounds.y + options.arrow.threshold,
37
+ // bottom: connector.from.bounds.y + connector.from.bounds.h - options.arrow.threshold,
38
+ x: connector.from.bounds.x,
39
+ // right: connector.from.bounds.x + connector.from.bounds.w,
40
+ w: connector.from.bounds.w,
41
+ h: connector.from.bounds.h - options.arrow.threshold * 2,
42
+ }
43
+
44
+ const from = getConnectorLinePoint(options, connector.from.element, fromBounds)
45
+ from.x = connector.from.element.x + connector.from.element.w // + 1
46
+
47
+ const fromBottom = fromBounds.y + fromBounds.h
48
+
49
+ const toBounds: Rect = {
50
+ y: connector.to.bounds.y + options.arrow.threshold,
51
+ // bottom: connector.to.bounds.y + connector.to.bounds.h - options.arrow.threshold,
52
+ x: connector.to.bounds.x,
53
+ // right: connector.to.bounds.x + connector.to.bounds.w,
54
+ w: connector.to.bounds.w,
55
+ h: connector.to.bounds.h - options.arrow.threshold * 2,
56
+ }
57
+
58
+ const toBottom = toBounds.y + toBounds.h
59
+
60
+ const to = getConnectorLinePoint(options, connector.to.element, toBounds)
61
+
62
+ const maxStartY = Math.max(to.startY, from.startY)
63
+
64
+ // Align from <-> to vertically
65
+ from.y = Math.min(maxStartY, from.endY)
66
+ if (from.y < toBounds.y) {
67
+ from.y = Math.min(toBounds.y, from.endY)
68
+ } else if (from.y > toBottom) {
69
+ from.y = Math.max(toBottom, from.startY)
70
+ }
71
+ to.y = Math.min(maxStartY, to.endY)
72
+ if (to.y < fromBounds.y) {
73
+ to.y = Math.min(fromBounds.y, to.endY)
74
+ } else if (to.y > fromBottom) {
75
+ to.y = Math.max(fromBottom, to.startY)
76
+ }
77
+
78
+ // Keep within bounds
79
+ from.y = Math.min(Math.max(from.y, fromBounds.y), fromBottom)
80
+ to.y = Math.min(Math.max(to.y, toBounds.y), toBottom)
81
+
82
+ return {from, to}
83
+ }
@@ -0,0 +1,56 @@
1
+ export interface Rect {
2
+ x: number
3
+ y: number
4
+ w: number
5
+ h: number
6
+ }
7
+
8
+ export interface Scroll {
9
+ x: number
10
+ y: number
11
+ }
12
+
13
+ export interface ConnectorLinePoint {
14
+ bounds: Rect
15
+ x: number
16
+ y: number
17
+ startY: number
18
+ centerY: number
19
+ endY: number
20
+ isAbove: boolean
21
+ isBelow: boolean
22
+ outOfBounds: boolean
23
+ }
24
+
25
+ export interface ConnectorLine {
26
+ from: ConnectorLinePoint
27
+ to: ConnectorLinePoint
28
+ }
29
+
30
+ export interface ConnectorOptions {
31
+ arrow: {
32
+ marginX: number
33
+ marginY: number
34
+ size: number
35
+ threshold: number
36
+ }
37
+ divider: {
38
+ offsetX: number
39
+ }
40
+ path: {
41
+ cornerRadius: number
42
+ marginY: number
43
+ strokeWidth: number
44
+ }
45
+ }
46
+
47
+ export interface ConnectorRegionRects {
48
+ bounds: Rect
49
+ element: Rect
50
+ }
51
+
52
+ export interface Connector {
53
+ key: string
54
+ from: ConnectorRegionRects & {payload?: Record<string, unknown>}
55
+ to: ConnectorRegionRects & {payload?: Record<string, unknown>}
56
+ }
@@ -0,0 +1,13 @@
1
+ import {useContext} from 'react'
2
+ import {ConnectorsStoreContext} from './ConnectorsStoreContext'
3
+ import {ConnectorsStore} from './ConnectorsStore'
4
+
5
+ export function useConnectorsStore(): ConnectorsStore {
6
+ const store = useContext(ConnectorsStoreContext)
7
+
8
+ if (!store) {
9
+ throw new Error('Missing connectors store context')
10
+ }
11
+
12
+ return store
13
+ }
@@ -0,0 +1,141 @@
1
+ import {useEffect, useMemo, useRef, useState} from 'react'
2
+ import {Rect, Scroll} from './types'
3
+ import {hasOverflowScroll} from './helpers'
4
+
5
+ export function useRegionRects() {
6
+ const ref = useRef<HTMLDivElement>(null)
7
+
8
+ const [relativeBoundsRect, setRelativeBoundsRect] = useState<Rect | null>(null)
9
+ const [relativeElementRect, setRelativeElementRect] = useState<Rect | null>(null)
10
+ const [boundsScroll, setBoundsScroll] = useState<Scroll>({x: 0, y: 0})
11
+ const [scroll, setScroll] = useState<Scroll>({x: 0, y: 0})
12
+
13
+ const boundsScrollXRef = useRef(0)
14
+ const boundsScrollYRef = useRef(0)
15
+
16
+ const elementScrollXRef = useRef(0)
17
+ const elementScrollYRef = useRef(0)
18
+
19
+ useEffect(() => {
20
+ const el = ref.current
21
+
22
+ if (!el) return undefined
23
+
24
+ const scrollParents: HTMLElement[] = []
25
+ let parent = el.parentElement
26
+
27
+ while (parent && parent !== document.body) {
28
+ if (
29
+ hasOverflowScroll(parent)
30
+ // || parent.scrollHeight > parent.clientHeight
31
+ ) {
32
+ scrollParents.push(parent)
33
+ }
34
+
35
+ parent = parent.parentElement
36
+ }
37
+
38
+ function handleResize() {
39
+ const scrollParent = scrollParents[0]
40
+
41
+ const boundsRect = scrollParent?.getBoundingClientRect() || {
42
+ x: 0,
43
+ y: 0,
44
+ width: window.innerWidth,
45
+ height: window.innerHeight,
46
+ }
47
+
48
+ const domRect = el!.getBoundingClientRect()
49
+
50
+ setRelativeBoundsRect({
51
+ x: boundsRect.x + boundsScrollXRef.current,
52
+ y: boundsRect.y + boundsScrollYRef.current,
53
+ w: boundsRect.width,
54
+ h: boundsRect.height,
55
+ })
56
+
57
+ setRelativeElementRect({
58
+ x: domRect.x + elementScrollXRef.current,
59
+ y: domRect.y + elementScrollYRef.current,
60
+ w: domRect.width,
61
+ h: domRect.height,
62
+ })
63
+ }
64
+
65
+ function handleScroll() {
66
+ let scrollX = window.scrollX
67
+ let scrollY = window.scrollY
68
+
69
+ for (const scrollParent of scrollParents) {
70
+ scrollX += scrollParent.scrollLeft
71
+ scrollY += scrollParent.scrollTop
72
+ }
73
+
74
+ const scrollParent = scrollParents[0]
75
+
76
+ boundsScrollXRef.current = scrollX - (scrollParent?.scrollLeft || window.scrollX)
77
+
78
+ boundsScrollYRef.current = scrollY - (scrollParent?.scrollTop || window.scrollY)
79
+
80
+ setBoundsScroll({
81
+ x: boundsScrollXRef.current,
82
+ y: boundsScrollYRef.current,
83
+ })
84
+
85
+ elementScrollXRef.current = scrollX
86
+ elementScrollYRef.current = scrollY
87
+
88
+ setScroll({x: scrollX, y: scrollY})
89
+ }
90
+
91
+ window.addEventListener('scroll', handleScroll, {passive: true})
92
+
93
+ const ro = new ResizeObserver(handleResize)
94
+
95
+ ro.observe(el)
96
+
97
+ for (const scrollParent of scrollParents) {
98
+ scrollParent.addEventListener('scroll', handleScroll, {passive: true})
99
+ ro.observe(scrollParent)
100
+ }
101
+
102
+ handleScroll()
103
+
104
+ return () => {
105
+ ro.unobserve(el)
106
+
107
+ for (const scrollParent of scrollParents) {
108
+ ro.unobserve(scrollParent)
109
+ scrollParent.removeEventListener('scroll', handleScroll)
110
+ }
111
+
112
+ ro.disconnect()
113
+
114
+ window.removeEventListener('scroll', handleScroll)
115
+ }
116
+ }, [])
117
+
118
+ const bounds: Rect | null = useMemo(
119
+ () =>
120
+ relativeBoundsRect && {
121
+ x: relativeBoundsRect.x - boundsScroll.x,
122
+ y: relativeBoundsRect.y - boundsScroll.y,
123
+ w: relativeBoundsRect.w,
124
+ h: relativeBoundsRect.h,
125
+ },
126
+ [relativeBoundsRect, boundsScroll]
127
+ )
128
+
129
+ const element: Rect | null = useMemo(
130
+ () =>
131
+ relativeElementRect && {
132
+ x: relativeElementRect.x - scroll.x,
133
+ y: relativeElementRect.y - scroll.y,
134
+ w: relativeElementRect.w,
135
+ h: relativeElementRect.h,
136
+ },
137
+ [relativeElementRect, scroll]
138
+ )
139
+
140
+ return {bounds, element, ref}
141
+ }