@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.
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.esm.js +2341 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2341 -0
- package/dist/index.js.map +1 -0
- package/package.json +98 -0
- package/sanity.json +8 -0
- package/src/_lib/connector/ConnectFromRegion.tsx +24 -0
- package/src/_lib/connector/ConnectToRegion.tsx +22 -0
- package/src/_lib/connector/ConnectorRegion.tsx +23 -0
- package/src/_lib/connector/ConnectorsProvider.tsx +19 -0
- package/src/_lib/connector/ConnectorsStore.ts +122 -0
- package/src/_lib/connector/ConnectorsStoreContext.ts +4 -0
- package/src/_lib/connector/helpers.ts +5 -0
- package/src/_lib/connector/index.ts +9 -0
- package/src/_lib/connector/mapConnectorToLine.ts +83 -0
- package/src/_lib/connector/types.ts +56 -0
- package/src/_lib/connector/useConnectorsStore.ts +13 -0
- package/src/_lib/connector/useRegionRects.ts +141 -0
- package/src/_lib/fixedListenQuery.ts +101 -0
- package/src/_lib/form/DocumentForm.tsx +197 -0
- package/src/_lib/form/helpers.ts +31 -0
- package/src/_lib/form/index.ts +1 -0
- package/src/_lib/randomKey.ts +29 -0
- package/src/_lib/useListeningQuery.ts +61 -0
- package/src/_lib/usePrevious.ts +9 -0
- package/src/assistConnectors/AssistConnectorsOverlay.tsx +132 -0
- package/src/assistConnectors/ConnectorPath.tsx +62 -0
- package/src/assistConnectors/draw/arrowPath.ts +9 -0
- package/src/assistConnectors/draw/connectorPath.ts +142 -0
- package/src/assistConnectors/index.ts +1 -0
- package/src/assistDocument/AssistDocumentContext.tsx +31 -0
- package/src/assistDocument/AssistDocumentContextProvider.tsx +17 -0
- package/src/assistDocument/AssistDocumentInput.tsx +46 -0
- package/src/assistDocument/RequestRunInstructionProvider.tsx +50 -0
- package/src/assistDocument/components/AssistDocumentForm.tsx +188 -0
- package/src/assistDocument/components/FieldRefPreview.tsx +27 -0
- package/src/assistDocument/components/InstructionsArrayField.tsx +8 -0
- package/src/assistDocument/components/InstructionsArrayInput.tsx +26 -0
- package/src/assistDocument/components/SelectedFieldContext.tsx +10 -0
- package/src/assistDocument/components/generic/HiddenFieldTitle.tsx +5 -0
- package/src/assistDocument/components/helpers.ts +21 -0
- package/src/assistDocument/components/instruction/BackToInstructionsLink.tsx +31 -0
- package/src/assistDocument/components/instruction/FieldRefInput.tsx +33 -0
- package/src/assistDocument/components/instruction/InstructionInput.tsx +87 -0
- package/src/assistDocument/components/instruction/PromptInput.tsx +52 -0
- package/src/assistDocument/components/instruction/appearance/IconInput.tsx +46 -0
- package/src/assistDocument/components/instruction/appearance/InstructionVisibility.tsx +37 -0
- package/src/assistDocument/hooks/useAssistDocumentContextValue.tsx +68 -0
- package/src/assistDocument/hooks/useDocumentState.ts +6 -0
- package/src/assistDocument/hooks/useInstructionToaster.tsx +74 -0
- package/src/assistDocument/hooks/useStudioAssistDocument.ts +119 -0
- package/src/assistDocument/index.ts +1 -0
- package/src/assistFormComponents/AssistField.tsx +51 -0
- package/src/assistFormComponents/AssistFormBlock.tsx +31 -0
- package/src/assistFormComponents/AssistInlineFormBlock.tsx +14 -0
- package/src/assistFormComponents/AssistItem.tsx +20 -0
- package/src/assistFormComponents/validation/listItem.tsx +63 -0
- package/src/assistFormComponents/validation/validationList.tsx +89 -0
- package/src/assistInspector/AssistInspector.tsx +379 -0
- package/src/assistInspector/FieldAutocomplete.tsx +119 -0
- package/src/assistInspector/InstructionTaskHistoryButton.tsx +261 -0
- package/src/assistInspector/constants.ts +1 -0
- package/src/assistInspector/helpers.ts +125 -0
- package/src/assistInspector/index.ts +26 -0
- package/src/assistLayout/AiAssistanceConfigContext.tsx +81 -0
- package/src/assistLayout/AlphaMigration.tsx +311 -0
- package/src/assistLayout/AssistLayout.tsx +38 -0
- package/src/assistLayout/RunInstructionProvider.tsx +222 -0
- package/src/components/AssistFeatureBadge.tsx +9 -0
- package/src/components/Delay.tsx +25 -0
- package/src/components/HideReferenceChangedBannerInput.tsx +25 -0
- package/src/components/SafeValueInput.tsx +73 -0
- package/src/components/TimeAgo.tsx +18 -0
- package/src/constants.ts +20 -0
- package/src/fieldActions/PrivateIcon.tsx +20 -0
- package/src/fieldActions/assistFieldActions.tsx +230 -0
- package/src/globals.d.ts +4 -0
- package/src/helpers/assistSupported.ts +44 -0
- package/src/helpers/ids.ts +19 -0
- package/src/helpers/misc.ts +16 -0
- package/src/helpers/typeUtils.ts +15 -0
- package/src/helpers/useAssistSupported.ts +10 -0
- package/src/index.ts +6 -0
- package/src/legacy-types.ts +72 -0
- package/src/onboarding/FieldActionsOnboarding.tsx +90 -0
- package/src/onboarding/FirstAssistedPathProvider.tsx +29 -0
- package/src/onboarding/InspectorOnboarding.tsx +46 -0
- package/src/onboarding/onboardingStore.ts +33 -0
- package/src/plugin.tsx +80 -0
- package/src/presence/AiFieldPresence.tsx +28 -0
- package/src/presence/AssistAvatar.tsx +96 -0
- package/src/presence/AssistDocumentPresence.tsx +58 -0
- package/src/presence/useAssistPresence.ts +61 -0
- package/src/schemas/assistDocumentSchema.tsx +450 -0
- package/src/schemas/contextDocumentSchema.tsx +56 -0
- package/src/schemas/index.ts +25 -0
- package/src/schemas/serialize/SchemTypeTool.tsx +102 -0
- package/src/schemas/serialize/schemaUtils.ts +37 -0
- package/src/schemas/serialize/serializeSchema.test.ts +382 -0
- package/src/schemas/serialize/serializeSchema.ts +162 -0
- package/src/schemas/serializedSchemaTypeSchema.ts +59 -0
- package/src/schemas/typeDefExtensions.ts +30 -0
- package/src/types.ts +167 -0
- package/src/useApiClient.ts +140 -0
- package/src/vite.config.ts +9 -0
- 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,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,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
|
+
}
|