@sanity/sdk-react 3.0.0-rc.2 → 3.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 (38) hide show
  1. package/dist/_exports/dashboard.d.ts +209 -0
  2. package/dist/_exports/dashboard.d.ts.map +1 -0
  3. package/dist/_exports/dashboard.js +278 -0
  4. package/dist/_exports/dashboard.js.map +1 -0
  5. package/dist/index.d.ts +86 -261
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +134 -532
  8. package/dist/index.js.map +1 -1
  9. package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js +177 -0
  10. package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js.map +1 -0
  11. package/package.json +9 -8
  12. package/src/_exports/dashboard.ts +11 -0
  13. package/src/_exports/sdk-react.ts +2 -12
  14. package/src/components/SDKProvider.test.tsx +5 -5
  15. package/src/components/auth/AuthBoundary.tsx +5 -5
  16. package/src/context/{WorkbenchTokenRefresh.test.tsx → DashboardTokenRefresh.test.tsx} +26 -26
  17. package/src/context/{WorkbenchTokenRefresh.tsx → DashboardTokenRefresh.tsx} +14 -14
  18. package/src/context/OrganizationResourcesProvider.test.tsx +9 -9
  19. package/src/context/OrganizationResourcesProvider.tsx +2 -2
  20. package/src/context/{workbenchToken.ts → dashboardToken.ts} +13 -13
  21. package/src/hooks/comlink/useWindowConnection.ts +1 -1
  22. package/src/hooks/{agent → dashboard}/useAgentResourceContext.ts +1 -1
  23. package/src/hooks/dashboard/useFavorite.test.tsx +101 -0
  24. package/src/hooks/dashboard/useFavorite.ts +34 -0
  25. package/src/hooks/dashboard/useFavoriteContext.ts +61 -0
  26. package/src/hooks/dashboard/{useDashboardNavigate.test.ts → useNavigate.test.ts} +3 -3
  27. package/src/hooks/dashboard/{useDashboardNavigate.ts → useNavigate.ts} +5 -5
  28. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -1
  29. package/src/hooks/{auth/useDashboardOrganizationId.test.tsx → dashboard/useOrganizationId.test.tsx} +4 -4
  30. package/src/hooks/{auth/useDashboardOrganizationId.tsx → dashboard/useOrganizationId.tsx} +2 -2
  31. package/src/hooks/dashboard/useUpdateFavorite.test.tsx +146 -0
  32. package/src/hooks/dashboard/useUpdateFavorite.ts +74 -0
  33. package/src/hooks/dashboard/useWindowTitle.ts +1 -1
  34. package/src/hooks/datasets/useDatasets.test.tsx +29 -22
  35. package/src/hooks/datasets/useDatasets.ts +31 -53
  36. package/src/hooks/dashboard/useManageFavorite.test.tsx +0 -379
  37. package/src/hooks/dashboard/useManageFavorite.ts +0 -173
  38. /package/src/hooks/{agent → dashboard}/useAgentResourceContext.test.tsx +0 -0
@@ -0,0 +1,209 @@
1
+ import { DocumentHandle } from "@sanity/sdk";
2
+ import { PathChangeMessage } from "@sanity/message-protocol";
3
+ /**
4
+ * @public
5
+ */
6
+ interface AgentResourceContextOptions {
7
+ /**
8
+ * The project ID of the current context
9
+ */
10
+ projectId: string;
11
+ /**
12
+ * The dataset of the current context
13
+ */
14
+ dataset: string;
15
+ /**
16
+ * Optional document ID if the user is viewing/editing a specific document
17
+ */
18
+ documentId?: string;
19
+ }
20
+ /**
21
+ * @public
22
+ * Hook for emitting agent resource context updates to the Dashboard.
23
+ * This allows the Agent to understand what resource the user is currently
24
+ * interacting with (e.g., which document they're editing).
25
+ *
26
+ * The hook will automatically emit the context when it changes, and also
27
+ * emit the initial context when the hook is first mounted.
28
+ *
29
+ * @category Agent
30
+ * @param options - The resource context options containing projectId, dataset, and optional documentId
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * import {useAgentResourceContext} from '@sanity/sdk-react/dashboard'
35
+ *
36
+ * function MyComponent() {
37
+ * const documentId = 'my-document-id'
38
+ *
39
+ * // Automatically updates the Agent's context whenever the document changes
40
+ * useAgentResourceContext({
41
+ * projectId: 'my-project',
42
+ * dataset: 'production',
43
+ * documentId,
44
+ * })
45
+ *
46
+ * return <div>Editing document: {documentId}</div>
47
+ * }
48
+ * ```
49
+ */
50
+ declare function useAgentResourceContext(options: AgentResourceContextOptions): void;
51
+ /**
52
+ * @public
53
+ *
54
+ * A helper hook designed to be injected into routing components for apps within the Dashboard.
55
+ * While the Dashboard can usually handle navigation, there are special cases when you
56
+ * are already within a target app, and need to navigate to another route inside of that app.
57
+ *
58
+ * For example, your user might "favorite" a document inside of your application.
59
+ * If they click on the Dashboard favorites item in the sidebar, and are already within your application,
60
+ * there needs to be some way for the dashboard to signal to your application to reroute to where that document was favorited.
61
+ *
62
+ * This hook is intended to receive those messages, and takes a function to route to the correct path.
63
+ *
64
+ * @param navigateFn - Function to handle navigation; should accept:
65
+ * - `path`: a string, which will be a relative path (for example, 'my-route')
66
+ * - `type`: 'push', 'replace', or 'pop', which will be the type of navigation to perform
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * import {useNavigate} from '@sanity/sdk-react/dashboard'
71
+ * import {BrowserRouter, useNavigate as useRouterNavigate} from 'react-router'
72
+ * import {Suspense} from 'react'
73
+ *
74
+ * function DashboardNavigationHandler() {
75
+ * const navigate = useRouterNavigate()
76
+ * useNavigate(({path, type}) => {
77
+ * navigate(path, {replace: type === 'replace'})
78
+ * })
79
+ * return null
80
+ * }
81
+ *
82
+ * // Wrap the component with Suspense since the hook may suspend
83
+ * function MyApp() {
84
+ * return (
85
+ * <BrowserRouter>
86
+ * <Suspense>
87
+ * <DashboardNavigationHandler />
88
+ * </Suspense>
89
+ * </BrowserRouter>
90
+ * )
91
+ * }
92
+ * ```
93
+ */
94
+ declare function useNavigate(navigateFn: (options: PathChangeMessage['data']) => void): void;
95
+ /**
96
+ * @public
97
+ * @category Types
98
+ */
99
+ interface NavigateToStudioResult {
100
+ navigateToStudioDocument: () => void;
101
+ }
102
+ /**
103
+ * @public
104
+ *
105
+ * Hook that provides a function to navigate to a given document in its parent Studio.
106
+ *
107
+ * Uses the `projectId` and `dataset` properties of the {@link DocumentHandle} you provide to resolve the correct Studio.
108
+ * This will only work if you have deployed a studio with a workspace with this `projectId` / `dataset` combination.
109
+ *
110
+ * @remarks If you write your own Document Handle to pass to this hook (as opposed to a Document Handle generated by another hook),
111
+ * it must include values for `documentId`, `documentType`, `projectId`, and `dataset`.
112
+ *
113
+ * @category Documents
114
+ * @param documentHandle - The document handle for the document to navigate to
115
+ * @param preferredStudioUrl - The preferred studio url to navigate to if you have multiple
116
+ * studios with the same projectId and dataset
117
+ * @returns An object containing:
118
+ * - `navigateToStudioDocument` - Function that when called will navigate to the studio document
119
+ *
120
+ * @example
121
+ * ```tsx
122
+ * import {type DocumentHandle} from '@sanity/sdk-react'
123
+ * import {useNavigateToStudioDocument} from '@sanity/sdk-react/dashboard'
124
+ * import {Button} from '@sanity/ui'
125
+ * import {Suspense} from 'react'
126
+ *
127
+ * function NavigateButton({documentHandle}: {documentHandle: DocumentHandle}) {
128
+ * const {navigateToStudioDocument} = useNavigateToStudioDocument(documentHandle)
129
+ * return (
130
+ * <Button
131
+ * onClick={navigateToStudioDocument}
132
+ * text="Navigate to Studio Document"
133
+ * />
134
+ * )
135
+ * }
136
+ *
137
+ * // Wrap the component with Suspense since the hook may suspend
138
+ * function MyDocumentAction({documentHandle}: {documentHandle: DocumentHandle}) {
139
+ * return (
140
+ * <Suspense fallback={<Button text="Loading..." disabled />}>
141
+ * <NavigateButton documentHandle={documentHandle} />
142
+ * </Suspense>
143
+ * )
144
+ * }
145
+ * ```
146
+ */
147
+ declare function useNavigateToStudioDocument(documentHandle: DocumentHandle, preferredStudioUrl?: string): NavigateToStudioResult;
148
+ /**
149
+ * @public
150
+ *
151
+ * A React hook that retrieves the dashboard organization ID that is currently selected in the Sanity Dashboard.
152
+ *
153
+ * @example
154
+ * ```tsx
155
+ * function DashboardComponent() {
156
+ * const orgId = useOrganizationId()
157
+ *
158
+ * if (!orgId) return null
159
+ *
160
+ * return <div>Organization ID: {String(orgId)}</div>
161
+ * }
162
+ * ```
163
+ *
164
+ * @category Dashboard
165
+ * @returns The dashboard organization ID (string | undefined)
166
+ */
167
+ declare function useOrganizationId(): string | undefined;
168
+ /**
169
+ * Sets the browser's document title, automatically including the app's name
170
+ * from the manifest.
171
+ *
172
+ * This follows the same convention as Sanity Studio workspaces, where the
173
+ * workspace name is always present in the title:
174
+ *
175
+ * - With a view title: `<viewTitle> | <appTitle>`
176
+ * - Without a view title: `<appTitle>`
177
+ *
178
+ * The Sanity dashboard appends `| Sanity` to produce the final browser tab title.
179
+ *
180
+ * @param viewTitle - An optional view-specific title to prepend to the app title.
181
+ *
182
+ * @example
183
+ * ```tsx
184
+ * import {useWindowTitle} from '@sanity/sdk-react/dashboard'
185
+ *
186
+ * function MoviesList() {
187
+ * useWindowTitle('Movies')
188
+ * return <div>...</div>
189
+ * }
190
+ *
191
+ * // Browser tab: "Movies | My App | Sanity"
192
+ * ```
193
+ *
194
+ * @example
195
+ * ```tsx
196
+ * // Call without arguments to show just the app title
197
+ * function AppRoot() {
198
+ * useWindowTitle()
199
+ * return <Outlet />
200
+ * }
201
+ *
202
+ * // Browser tab: "My App | Sanity"
203
+ * ```
204
+ *
205
+ * @public
206
+ */
207
+ declare function useWindowTitle(viewTitle?: string): void;
208
+ export { type AgentResourceContextOptions, type NavigateToStudioResult, useAgentResourceContext, useNavigate, useNavigateToStudioDocument, useOrganizationId, useWindowTitle };
209
+ //# sourceMappingURL=dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.d.ts","names":[],"sources":["../../src/hooks/dashboard/useAgentResourceContext.ts","../../src/hooks/dashboard/useNavigate.ts","../../src/hooks/dashboard/useNavigateToStudioDocument.ts","../../src/hooks/dashboard/useOrganizationId.tsx","../../src/hooks/dashboard/useWindowTitle.ts"],"mappings":";;;;;UASiB;;;;EAIf;;;;EAIA;;;;EAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCc,wBAAwB,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCPjC,YACd,aAAa,SAAS;;;;;UClCP;EACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgDc,4BACd,gBAAgB,gBAChB,8BACC;;;;;;;;;;;;;;;;;;;;iBC1Ca;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBC2CA,eAAe"}
@@ -0,0 +1,278 @@
1
+ import { n as useOrganizationId, r as useWindowConnection, t as useStudioWorkspacesByProjectIdDataset } from "../useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js";
2
+ import { c } from "react-compiler-runtime";
3
+ import "@sanity/sdk";
4
+ import { useEffect, useRef, useState } from "react";
5
+ import { SDK_CHANNEL_NAME, SDK_NODE_NAME } from "@sanity/message-protocol";
6
+ import "@sanity/sdk/comlink";
7
+ /**
8
+ * @public
9
+ * Hook for emitting agent resource context updates to the Dashboard.
10
+ * This allows the Agent to understand what resource the user is currently
11
+ * interacting with (e.g., which document they're editing).
12
+ *
13
+ * The hook will automatically emit the context when it changes, and also
14
+ * emit the initial context when the hook is first mounted.
15
+ *
16
+ * @category Agent
17
+ * @param options - The resource context options containing projectId, dataset, and optional documentId
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * import {useAgentResourceContext} from '@sanity/sdk-react/dashboard'
22
+ *
23
+ * function MyComponent() {
24
+ * const documentId = 'my-document-id'
25
+ *
26
+ * // Automatically updates the Agent's context whenever the document changes
27
+ * useAgentResourceContext({
28
+ * projectId: 'my-project',
29
+ * dataset: 'production',
30
+ * documentId,
31
+ * })
32
+ *
33
+ * return <div>Editing document: {documentId}</div>
34
+ * }
35
+ * ```
36
+ */
37
+ function useAgentResourceContext(options) {
38
+ let $ = c(9), { projectId, dataset, documentId } = options, t0;
39
+ $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = {
40
+ name: SDK_NODE_NAME,
41
+ connectTo: SDK_CHANNEL_NAME
42
+ }, $[0] = t0) : t0 = $[0];
43
+ let { sendMessage } = useWindowConnection(t0), lastContextRef = useRef(null), t1;
44
+ $[1] !== dataset || $[2] !== documentId || $[3] !== projectId || $[4] !== sendMessage ? (t1 = () => {
45
+ if (!projectId || !dataset) {
46
+ console.warn("[useAgentResourceContext] projectId and dataset are required", {
47
+ projectId,
48
+ dataset
49
+ });
50
+ return;
51
+ }
52
+ let contextKey = `${projectId}:${dataset}:${documentId || ""}`;
53
+ if (lastContextRef.current !== contextKey) try {
54
+ let message = {
55
+ type: "dashboard/v1/events/agent/resource/update",
56
+ data: {
57
+ projectId,
58
+ dataset,
59
+ documentId
60
+ }
61
+ };
62
+ sendMessage(message.type, message.data), lastContextRef.current = contextKey;
63
+ } catch (t2) {
64
+ console.error("[useAgentResourceContext] Failed to update context:", t2);
65
+ }
66
+ }, $[1] = dataset, $[2] = documentId, $[3] = projectId, $[4] = sendMessage, $[5] = t1) : t1 = $[5];
67
+ let updateContext = t1, t2, t3;
68
+ $[6] === updateContext ? (t2 = $[7], t3 = $[8]) : (t2 = () => {
69
+ updateContext();
70
+ }, t3 = [updateContext], $[6] = updateContext, $[7] = t2, $[8] = t3), useEffect(t2, t3);
71
+ }
72
+ /**
73
+ * @public
74
+ *
75
+ * A helper hook designed to be injected into routing components for apps within the Dashboard.
76
+ * While the Dashboard can usually handle navigation, there are special cases when you
77
+ * are already within a target app, and need to navigate to another route inside of that app.
78
+ *
79
+ * For example, your user might "favorite" a document inside of your application.
80
+ * If they click on the Dashboard favorites item in the sidebar, and are already within your application,
81
+ * there needs to be some way for the dashboard to signal to your application to reroute to where that document was favorited.
82
+ *
83
+ * This hook is intended to receive those messages, and takes a function to route to the correct path.
84
+ *
85
+ * @param navigateFn - Function to handle navigation; should accept:
86
+ * - `path`: a string, which will be a relative path (for example, 'my-route')
87
+ * - `type`: 'push', 'replace', or 'pop', which will be the type of navigation to perform
88
+ *
89
+ * @example
90
+ * ```tsx
91
+ * import {useNavigate} from '@sanity/sdk-react/dashboard'
92
+ * import {BrowserRouter, useNavigate as useRouterNavigate} from 'react-router'
93
+ * import {Suspense} from 'react'
94
+ *
95
+ * function DashboardNavigationHandler() {
96
+ * const navigate = useRouterNavigate()
97
+ * useNavigate(({path, type}) => {
98
+ * navigate(path, {replace: type === 'replace'})
99
+ * })
100
+ * return null
101
+ * }
102
+ *
103
+ * // Wrap the component with Suspense since the hook may suspend
104
+ * function MyApp() {
105
+ * return (
106
+ * <BrowserRouter>
107
+ * <Suspense>
108
+ * <DashboardNavigationHandler />
109
+ * </Suspense>
110
+ * </BrowserRouter>
111
+ * )
112
+ * }
113
+ * ```
114
+ */
115
+ function useNavigate(navigateFn) {
116
+ let $ = c(2), t0;
117
+ $[0] === navigateFn ? t0 = $[1] : (t0 = {
118
+ name: SDK_NODE_NAME,
119
+ connectTo: SDK_CHANNEL_NAME,
120
+ onMessage: { "dashboard/v1/history/change-path": (data) => {
121
+ navigateFn(data);
122
+ } }
123
+ }, $[0] = navigateFn, $[1] = t0), useWindowConnection(t0);
124
+ }
125
+ /**
126
+ * @public
127
+ *
128
+ * Hook that provides a function to navigate to a given document in its parent Studio.
129
+ *
130
+ * Uses the `projectId` and `dataset` properties of the {@link DocumentHandle} you provide to resolve the correct Studio.
131
+ * This will only work if you have deployed a studio with a workspace with this `projectId` / `dataset` combination.
132
+ *
133
+ * @remarks If you write your own Document Handle to pass to this hook (as opposed to a Document Handle generated by another hook),
134
+ * it must include values for `documentId`, `documentType`, `projectId`, and `dataset`.
135
+ *
136
+ * @category Documents
137
+ * @param documentHandle - The document handle for the document to navigate to
138
+ * @param preferredStudioUrl - The preferred studio url to navigate to if you have multiple
139
+ * studios with the same projectId and dataset
140
+ * @returns An object containing:
141
+ * - `navigateToStudioDocument` - Function that when called will navigate to the studio document
142
+ *
143
+ * @example
144
+ * ```tsx
145
+ * import {type DocumentHandle} from '@sanity/sdk-react'
146
+ * import {useNavigateToStudioDocument} from '@sanity/sdk-react/dashboard'
147
+ * import {Button} from '@sanity/ui'
148
+ * import {Suspense} from 'react'
149
+ *
150
+ * function NavigateButton({documentHandle}: {documentHandle: DocumentHandle}) {
151
+ * const {navigateToStudioDocument} = useNavigateToStudioDocument(documentHandle)
152
+ * return (
153
+ * <Button
154
+ * onClick={navigateToStudioDocument}
155
+ * text="Navigate to Studio Document"
156
+ * />
157
+ * )
158
+ * }
159
+ *
160
+ * // Wrap the component with Suspense since the hook may suspend
161
+ * function MyDocumentAction({documentHandle}: {documentHandle: DocumentHandle}) {
162
+ * return (
163
+ * <Suspense fallback={<Button text="Loading..." disabled />}>
164
+ * <NavigateButton documentHandle={documentHandle} />
165
+ * </Suspense>
166
+ * )
167
+ * }
168
+ * ```
169
+ */
170
+ function useNavigateToStudioDocument(documentHandle, preferredStudioUrl) {
171
+ let $ = c(8), { workspacesByProjectIdAndDataset } = useStudioWorkspacesByProjectIdDataset(), t0;
172
+ $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = {
173
+ name: SDK_NODE_NAME,
174
+ connectTo: SDK_CHANNEL_NAME
175
+ }, $[0] = t0) : t0 = $[0];
176
+ let { sendMessage } = useWindowConnection(t0), t1;
177
+ $[1] !== documentHandle || $[2] !== preferredStudioUrl || $[3] !== sendMessage || $[4] !== workspacesByProjectIdAndDataset ? (t1 = () => {
178
+ let { projectId, dataset } = documentHandle;
179
+ if (!projectId || !dataset) {
180
+ console.warn("Project ID and dataset are required to navigate to a studio document");
181
+ return;
182
+ }
183
+ let workspace;
184
+ if (preferredStudioUrl) workspace = [...workspacesByProjectIdAndDataset[`${projectId}:${dataset}`] || [], ...workspacesByProjectIdAndDataset["NO_PROJECT_ID:NO_DATASET"] || []].find((w) => w.url === preferredStudioUrl);
185
+ else {
186
+ let workspaces = workspacesByProjectIdAndDataset[`${projectId}:${dataset}`];
187
+ workspaces?.length > 1 && (console.warn("Multiple workspaces found for document and no preferred studio url", documentHandle), console.warn("Using the first one", workspaces[0])), workspace = workspaces?.[0];
188
+ }
189
+ if (!workspace) {
190
+ console.warn(`No workspace found for document with projectId: ${projectId} and dataset: ${dataset}${preferredStudioUrl ? ` or with preferred studio url: ${preferredStudioUrl}` : ""}`);
191
+ return;
192
+ }
193
+ let message = {
194
+ type: "dashboard/v1/bridge/navigate-to-resource",
195
+ data: {
196
+ resourceId: workspace.id,
197
+ resourceType: "studio",
198
+ path: `/intent/edit/id=${documentHandle.documentId};type=${documentHandle.documentType}`
199
+ }
200
+ };
201
+ sendMessage(message.type, message.data);
202
+ }, $[1] = documentHandle, $[2] = preferredStudioUrl, $[3] = sendMessage, $[4] = workspacesByProjectIdAndDataset, $[5] = t1) : t1 = $[5];
203
+ let navigateToStudioDocument = t1, t2;
204
+ return $[6] === navigateToStudioDocument ? t2 = $[7] : (t2 = { navigateToStudioDocument }, $[6] = navigateToStudioDocument, $[7] = t2), t2;
205
+ }
206
+ function resolveAppTitle(resource) {
207
+ return resource.manifest?.title || resource.activeDeployment?.manifest?.title || resource.title;
208
+ }
209
+ /**
210
+ * Sets the browser's document title, automatically including the app's name
211
+ * from the manifest.
212
+ *
213
+ * This follows the same convention as Sanity Studio workspaces, where the
214
+ * workspace name is always present in the title:
215
+ *
216
+ * - With a view title: `<viewTitle> | <appTitle>`
217
+ * - Without a view title: `<appTitle>`
218
+ *
219
+ * The Sanity dashboard appends `| Sanity` to produce the final browser tab title.
220
+ *
221
+ * @param viewTitle - An optional view-specific title to prepend to the app title.
222
+ *
223
+ * @example
224
+ * ```tsx
225
+ * import {useWindowTitle} from '@sanity/sdk-react/dashboard'
226
+ *
227
+ * function MoviesList() {
228
+ * useWindowTitle('Movies')
229
+ * return <div>...</div>
230
+ * }
231
+ *
232
+ * // Browser tab: "Movies | My App | Sanity"
233
+ * ```
234
+ *
235
+ * @example
236
+ * ```tsx
237
+ * // Call without arguments to show just the app title
238
+ * function AppRoot() {
239
+ * useWindowTitle()
240
+ * return <Outlet />
241
+ * }
242
+ *
243
+ * // Browser tab: "My App | Sanity"
244
+ * ```
245
+ *
246
+ * @public
247
+ */
248
+ function useWindowTitle(viewTitle) {
249
+ let [appTitle, setAppTitle] = useState(null), { fetch } = useWindowConnection({
250
+ name: SDK_NODE_NAME,
251
+ connectTo: SDK_CHANNEL_NAME
252
+ });
253
+ useEffect(() => {
254
+ if (!fetch) return;
255
+ let controller = new AbortController();
256
+ async function fetchAppTitle(signal) {
257
+ try {
258
+ let title = resolveAppTitle((await fetch("dashboard/v1/context", void 0, { signal })).context.resource) || document.title;
259
+ title && setAppTitle(title);
260
+ } catch (err) {
261
+ if (err instanceof Error && err.name === "AbortError") return;
262
+ console.error("Failed to fetch app title from dashboard context:", err);
263
+ }
264
+ }
265
+ return fetchAppTitle(controller.signal), () => {
266
+ controller.abort();
267
+ };
268
+ }, [fetch]), useEffect(() => {
269
+ if (!appTitle) return;
270
+ let previous = document.title;
271
+ return document.title = viewTitle ? `${viewTitle} | ${appTitle}` : appTitle, () => {
272
+ document.title = previous;
273
+ };
274
+ }, [viewTitle, appTitle]);
275
+ }
276
+ export { useAgentResourceContext, useNavigate, useNavigateToStudioDocument, useOrganizationId, useWindowTitle };
277
+
278
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","names":["Events","SDK_CHANNEL_NAME","SDK_NODE_NAME","FrameMessage","useCallback","useEffect","useRef","useWindowConnection","AgentResourceContextOptions","projectId","dataset","documentId","useAgentResourceContext","options","$","_c","t0","Symbol","for","name","connectTo","sendMessage","lastContextRef","t1","console","warn","contextKey","current","message","type","data","t2","error","updateContext","t3","PathChangeMessage","SDK_CHANNEL_NAME","SDK_NODE_NAME","useWindowConnection","useNavigate","navigateFn","$","_c","t0","name","connectTo","onMessage","data","Bridge","SDK_CHANNEL_NAME","SDK_NODE_NAME","DocumentHandle","useCallback","useWindowConnection","DashboardResource","useStudioWorkspacesByProjectIdDataset","NavigateToStudioResult","navigateToStudioDocument","useNavigateToStudioDocument","documentHandle","preferredStudioUrl","$","_c","workspacesByProjectIdAndDataset","t0","Symbol","for","name","connectTo","sendMessage","t1","projectId","dataset","console","warn","workspace","allWorkspaces","find","w","url","workspaces","length","message","type","data","resourceId","id","resourceType","path","documentId","documentType","t2","SDK_CHANNEL_NAME","SDK_NODE_NAME","useEffect","useState","useWindowConnection","ContextResource","type","title","manifest","activeDeployment","ContextResponse","context","resource","resolveAppTitle","useWindowTitle","viewTitle","appTitle","setAppTitle","fetch","name","connectTo","controller","AbortController","fetchAppTitle","signal","AbortSignal","data","undefined","document","err","Error","console","error","abort","previous"],"sources":["../../src/hooks/dashboard/useAgentResourceContext.ts","../../src/hooks/dashboard/useNavigate.ts","../../src/hooks/dashboard/useNavigateToStudioDocument.ts","../../src/hooks/dashboard/useWindowTitle.ts"],"sourcesContent":["import {type Events, SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'\nimport {type FrameMessage} from '@sanity/sdk/comlink'\nimport {useCallback, useEffect, useRef} from 'react'\n\nimport {useWindowConnection} from '../comlink/useWindowConnection'\n\n/**\n * @public\n */\nexport interface AgentResourceContextOptions {\n /**\n * The project ID of the current context\n */\n projectId: string\n /**\n * The dataset of the current context\n */\n dataset: string\n /**\n * Optional document ID if the user is viewing/editing a specific document\n */\n documentId?: string\n}\n\n/**\n * @public\n * Hook for emitting agent resource context updates to the Dashboard.\n * This allows the Agent to understand what resource the user is currently\n * interacting with (e.g., which document they're editing).\n *\n * The hook will automatically emit the context when it changes, and also\n * emit the initial context when the hook is first mounted.\n *\n * @category Agent\n * @param options - The resource context options containing projectId, dataset, and optional documentId\n *\n * @example\n * ```tsx\n * import {useAgentResourceContext} from '@sanity/sdk-react/dashboard'\n *\n * function MyComponent() {\n * const documentId = 'my-document-id'\n *\n * // Automatically updates the Agent's context whenever the document changes\n * useAgentResourceContext({\n * projectId: 'my-project',\n * dataset: 'production',\n * documentId,\n * })\n *\n * return <div>Editing document: {documentId}</div>\n * }\n * ```\n */\nexport function useAgentResourceContext(options: AgentResourceContextOptions): void {\n const {projectId, dataset, documentId} = options\n const {sendMessage} = useWindowConnection<Events.AgentResourceUpdateMessage, FrameMessage>({\n name: SDK_NODE_NAME,\n connectTo: SDK_CHANNEL_NAME,\n })\n\n // Track the last sent context to avoid duplicate updates\n const lastContextRef = useRef<string | null>(null)\n\n const updateContext = useCallback(() => {\n // Validate required fields\n if (!projectId || !dataset) {\n // eslint-disable-next-line no-console\n console.warn('[useAgentResourceContext] projectId and dataset are required', {\n projectId,\n dataset,\n })\n return\n }\n\n // Create a stable key for the current context\n const contextKey = `${projectId}:${dataset}:${documentId || ''}`\n\n // Skip if context hasn't changed\n if (lastContextRef.current === contextKey) {\n return\n }\n\n try {\n const message: Events.AgentResourceUpdateMessage = {\n type: 'dashboard/v1/events/agent/resource/update',\n data: {\n projectId,\n dataset,\n documentId,\n },\n }\n\n sendMessage(message.type, message.data)\n lastContextRef.current = contextKey\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('[useAgentResourceContext] Failed to update context:', error)\n }\n }, [projectId, dataset, documentId, sendMessage])\n\n // Update context whenever it changes\n useEffect(() => {\n updateContext()\n }, [updateContext])\n}\n","import {type PathChangeMessage, SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'\n\nimport {useWindowConnection} from '../comlink/useWindowConnection'\n\n/**\n * @public\n *\n * A helper hook designed to be injected into routing components for apps within the Dashboard.\n * While the Dashboard can usually handle navigation, there are special cases when you\n * are already within a target app, and need to navigate to another route inside of that app.\n *\n * For example, your user might \"favorite\" a document inside of your application.\n * If they click on the Dashboard favorites item in the sidebar, and are already within your application,\n * there needs to be some way for the dashboard to signal to your application to reroute to where that document was favorited.\n *\n * This hook is intended to receive those messages, and takes a function to route to the correct path.\n *\n * @param navigateFn - Function to handle navigation; should accept:\n * - `path`: a string, which will be a relative path (for example, 'my-route')\n * - `type`: 'push', 'replace', or 'pop', which will be the type of navigation to perform\n *\n * @example\n * ```tsx\n * import {useNavigate} from '@sanity/sdk-react/dashboard'\n * import {BrowserRouter, useNavigate as useRouterNavigate} from 'react-router'\n * import {Suspense} from 'react'\n *\n * function DashboardNavigationHandler() {\n * const navigate = useRouterNavigate()\n * useNavigate(({path, type}) => {\n * navigate(path, {replace: type === 'replace'})\n * })\n * return null\n * }\n *\n * // Wrap the component with Suspense since the hook may suspend\n * function MyApp() {\n * return (\n * <BrowserRouter>\n * <Suspense>\n * <DashboardNavigationHandler />\n * </Suspense>\n * </BrowserRouter>\n * )\n * }\n * ```\n */\nexport function useNavigate(\n navigateFn: (options: PathChangeMessage['data']) => void,\n): void {\n useWindowConnection<PathChangeMessage, never>({\n name: SDK_NODE_NAME,\n connectTo: SDK_CHANNEL_NAME,\n onMessage: {\n 'dashboard/v1/history/change-path': (data: PathChangeMessage['data']) => {\n navigateFn(data)\n },\n },\n })\n}\n","import {type Bridge, SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'\nimport {type DocumentHandle} from '@sanity/sdk'\nimport {useCallback} from 'react'\n\nimport {useWindowConnection} from '../comlink/useWindowConnection'\nimport {\n type DashboardResource,\n useStudioWorkspacesByProjectIdDataset,\n} from './useStudioWorkspacesByProjectIdDataset'\n\n/**\n * @public\n * @category Types\n */\nexport interface NavigateToStudioResult {\n navigateToStudioDocument: () => void\n}\n\n/**\n * @public\n *\n * Hook that provides a function to navigate to a given document in its parent Studio.\n *\n * Uses the `projectId` and `dataset` properties of the {@link DocumentHandle} you provide to resolve the correct Studio.\n * This will only work if you have deployed a studio with a workspace with this `projectId` / `dataset` combination.\n *\n * @remarks If you write your own Document Handle to pass to this hook (as opposed to a Document Handle generated by another hook),\n * it must include values for `documentId`, `documentType`, `projectId`, and `dataset`.\n *\n * @category Documents\n * @param documentHandle - The document handle for the document to navigate to\n * @param preferredStudioUrl - The preferred studio url to navigate to if you have multiple\n * studios with the same projectId and dataset\n * @returns An object containing:\n * - `navigateToStudioDocument` - Function that when called will navigate to the studio document\n *\n * @example\n * ```tsx\n * import {type DocumentHandle} from '@sanity/sdk-react'\n * import {useNavigateToStudioDocument} from '@sanity/sdk-react/dashboard'\n * import {Button} from '@sanity/ui'\n * import {Suspense} from 'react'\n *\n * function NavigateButton({documentHandle}: {documentHandle: DocumentHandle}) {\n * const {navigateToStudioDocument} = useNavigateToStudioDocument(documentHandle)\n * return (\n * <Button\n * onClick={navigateToStudioDocument}\n * text=\"Navigate to Studio Document\"\n * />\n * )\n * }\n *\n * // Wrap the component with Suspense since the hook may suspend\n * function MyDocumentAction({documentHandle}: {documentHandle: DocumentHandle}) {\n * return (\n * <Suspense fallback={<Button text=\"Loading...\" disabled />}>\n * <NavigateButton documentHandle={documentHandle} />\n * </Suspense>\n * )\n * }\n * ```\n */\nexport function useNavigateToStudioDocument(\n documentHandle: DocumentHandle,\n preferredStudioUrl?: string,\n): NavigateToStudioResult {\n const {workspacesByProjectIdAndDataset} = useStudioWorkspacesByProjectIdDataset()\n const {sendMessage} = useWindowConnection<Bridge.Navigation.NavigateToResourceMessage, never>({\n name: SDK_NODE_NAME,\n connectTo: SDK_CHANNEL_NAME,\n })\n\n const navigateToStudioDocument = useCallback(() => {\n const {projectId, dataset} = documentHandle\n\n if (!projectId || !dataset) {\n // eslint-disable-next-line no-console\n console.warn('Project ID and dataset are required to navigate to a studio document')\n return\n }\n\n let workspace: DashboardResource | undefined\n\n if (preferredStudioUrl) {\n // Get workspaces matching the projectId:dataset and any workspaces without projectId/dataset,\n // in case there hasn't been a manifest loaded yet\n const allWorkspaces = [\n ...(workspacesByProjectIdAndDataset[`${projectId}:${dataset}`] || []),\n ...(workspacesByProjectIdAndDataset['NO_PROJECT_ID:NO_DATASET'] || []),\n ]\n workspace = allWorkspaces.find((w) => w.url === preferredStudioUrl)\n } else {\n const workspaces = workspacesByProjectIdAndDataset[`${projectId}:${dataset}`]\n if (workspaces?.length > 1) {\n // eslint-disable-next-line no-console\n console.warn(\n 'Multiple workspaces found for document and no preferred studio url',\n documentHandle,\n )\n // eslint-disable-next-line no-console\n console.warn('Using the first one', workspaces[0])\n }\n\n workspace = workspaces?.[0]\n }\n\n if (!workspace) {\n // eslint-disable-next-line no-console\n console.warn(\n `No workspace found for document with projectId: ${projectId} and dataset: ${dataset}${preferredStudioUrl ? ` or with preferred studio url: ${preferredStudioUrl}` : ''}`,\n )\n return\n }\n\n const message: Bridge.Navigation.NavigateToResourceMessage = {\n type: 'dashboard/v1/bridge/navigate-to-resource',\n data: {\n resourceId: workspace.id,\n resourceType: 'studio',\n path: `/intent/edit/id=${documentHandle.documentId};type=${documentHandle.documentType}`,\n },\n }\n\n sendMessage(message.type, message.data)\n }, [documentHandle, workspacesByProjectIdAndDataset, sendMessage, preferredStudioUrl])\n\n return {\n navigateToStudioDocument,\n }\n}\n","import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'\nimport {useEffect, useState} from 'react'\n\nimport {useWindowConnection} from '../comlink/useWindowConnection'\n\ninterface ContextResource {\n type: string\n title?: string\n manifest?: {\n title?: string\n } | null\n activeDeployment?: {\n manifest?: {\n title?: string\n } | null\n } | null\n}\n\ninterface ContextResponse {\n context: {\n resource: ContextResource\n }\n}\n\nfunction resolveAppTitle(resource: ContextResource): string | undefined {\n return resource.manifest?.title || resource.activeDeployment?.manifest?.title || resource.title\n}\n\n/**\n * Sets the browser's document title, automatically including the app's name\n * from the manifest.\n *\n * This follows the same convention as Sanity Studio workspaces, where the\n * workspace name is always present in the title:\n *\n * - With a view title: `<viewTitle> | <appTitle>`\n * - Without a view title: `<appTitle>`\n *\n * The Sanity dashboard appends `| Sanity` to produce the final browser tab title.\n *\n * @param viewTitle - An optional view-specific title to prepend to the app title.\n *\n * @example\n * ```tsx\n * import {useWindowTitle} from '@sanity/sdk-react/dashboard'\n *\n * function MoviesList() {\n * useWindowTitle('Movies')\n * return <div>...</div>\n * }\n *\n * // Browser tab: \"Movies | My App | Sanity\"\n * ```\n *\n * @example\n * ```tsx\n * // Call without arguments to show just the app title\n * function AppRoot() {\n * useWindowTitle()\n * return <Outlet />\n * }\n *\n * // Browser tab: \"My App | Sanity\"\n * ```\n *\n * @public\n */\nexport function useWindowTitle(viewTitle?: string): void {\n const [appTitle, setAppTitle] = useState<string | null>(null)\n\n const {fetch} = useWindowConnection({\n name: SDK_NODE_NAME,\n connectTo: SDK_CHANNEL_NAME,\n })\n\n useEffect(() => {\n if (!fetch) return\n\n const controller = new AbortController()\n\n async function fetchAppTitle(signal: AbortSignal) {\n try {\n const data = await fetch<ContextResponse>('dashboard/v1/context', undefined, {signal})\n // Local development resources do not have a registered title or deployment manifest.\n // In that case, use the title rendered into the iframe HTML by the Sanity CLI.\n const title = resolveAppTitle(data.context.resource) || document.title\n if (title) {\n setAppTitle(title)\n }\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') return\n // eslint-disable-next-line no-console\n console.error('Failed to fetch app title from dashboard context:', err)\n }\n }\n\n fetchAppTitle(controller.signal)\n\n return () => {\n controller.abort()\n }\n }, [fetch])\n\n useEffect(() => {\n if (!appTitle) return\n\n const previous = document.title\n document.title = viewTitle ? `${viewTitle} | ${appTitle}` : appTitle\n\n return () => {\n document.title = previous\n }\n }, [viewTitle, appTitle])\n}\n"],"mappings":"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"}