@sanity/sdk-react 0.0.0-rc.0 → 0.0.0-rc.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/README.md +1 -1
- package/dist/index.d.ts +398 -228
- package/dist/index.js +275 -166
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/_exports/index.ts +20 -12
- package/src/components/Login/LoginLinks.test.tsx +2 -2
- package/src/components/Login/LoginLinks.tsx +2 -2
- package/src/components/auth/AuthBoundary.test.tsx +2 -2
- package/src/components/auth/LoginCallback.test.tsx +3 -3
- package/src/components/auth/LoginCallback.tsx +4 -4
- package/src/hooks/auth/useCurrentUser.tsx +1 -0
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +42 -0
- package/src/hooks/auth/useDashboardOrganizationId.tsx +29 -0
- package/src/hooks/auth/useHandleAuthCallback.test.tsx +16 -0
- package/src/hooks/auth/{useHandleCallback.tsx → useHandleAuthCallback.tsx} +6 -6
- package/src/hooks/auth/useLogOut.test.tsx +2 -2
- package/src/hooks/client/useClient.ts +1 -0
- package/src/hooks/comlink/useFrameConnection.test.tsx +20 -10
- package/src/hooks/comlink/useFrameConnection.ts +33 -56
- package/src/hooks/comlink/useManageFavorite.test.ts +9 -4
- package/src/hooks/comlink/useManageFavorite.ts +46 -14
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +7 -3
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.ts +45 -14
- package/src/hooks/comlink/useWindowConnection.test.ts +20 -10
- package/src/hooks/comlink/useWindowConnection.ts +69 -41
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +178 -0
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +123 -0
- package/src/hooks/dashboard/useStudioWorkspacesByResourceId.test.tsx +278 -0
- package/src/hooks/dashboard/useStudioWorkspacesByResourceId.ts +92 -0
- package/src/hooks/datasets/useDatasets.ts +6 -3
- package/src/hooks/document/useApplyDocumentActions.test.ts +25 -0
- package/src/hooks/document/{useApplyActions.ts → useApplyDocumentActions.ts} +13 -12
- package/src/hooks/document/{usePermissions.ts → useDocumentPermissions.ts} +12 -10
- package/src/hooks/document/useDocumentSyncStatus.ts +4 -1
- package/src/hooks/document/useEditDocument.test.ts +8 -8
- package/src/hooks/document/useEditDocument.ts +2 -2
- package/src/hooks/{infiniteList/useInfiniteList.test.tsx → documents/useDocuments.test.tsx} +9 -9
- package/src/hooks/{infiniteList/useInfiniteList.ts → documents/useDocuments.ts} +10 -10
- package/src/hooks/{paginatedList/usePaginatedList.test.tsx → paginatedDocuments/usePaginatedDocuments.test.tsx} +14 -14
- package/src/hooks/{paginatedList/usePaginatedList.ts → paginatedDocuments/usePaginatedDocuments.ts} +7 -7
- package/src/hooks/preview/usePreview.test.tsx +6 -6
- package/src/hooks/preview/usePreview.tsx +5 -5
- package/src/hooks/projection/useProjection.test.tsx +9 -9
- package/src/hooks/projection/useProjection.ts +27 -15
- package/src/hooks/projects/useProject.ts +4 -1
- package/src/hooks/projects/useProjects.ts +7 -3
- package/src/hooks/auth/useHandleCallback.test.tsx +0 -16
- package/src/hooks/document/useApplyActions.test.ts +0 -25
|
@@ -12,7 +12,7 @@ import {renderHook} from '@testing-library/react'
|
|
|
12
12
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
13
13
|
|
|
14
14
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
15
|
-
import {
|
|
15
|
+
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
16
16
|
import {useEditDocument} from './useEditDocument'
|
|
17
17
|
|
|
18
18
|
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
@@ -29,8 +29,8 @@ vi.mock('../context/useSanityInstance', () => ({
|
|
|
29
29
|
useSanityInstance: vi.fn(),
|
|
30
30
|
}))
|
|
31
31
|
|
|
32
|
-
vi.mock('./
|
|
33
|
-
|
|
32
|
+
vi.mock('./useApplyDocumentActions', () => ({
|
|
33
|
+
useApplyDocumentActions: vi.fn(),
|
|
34
34
|
}))
|
|
35
35
|
|
|
36
36
|
// Create a fake instance to be returned by useSanityInstance.
|
|
@@ -66,7 +66,7 @@ describe('useEditDocument hook', () => {
|
|
|
66
66
|
} as unknown as StateSource<SanityDocument>)
|
|
67
67
|
|
|
68
68
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx1'})
|
|
69
|
-
vi.mocked(
|
|
69
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
70
70
|
|
|
71
71
|
const {result} = renderHook(() => useEditDocument(docHandle, 'foo'))
|
|
72
72
|
const promise = result.current('newValue')
|
|
@@ -87,7 +87,7 @@ describe('useEditDocument hook', () => {
|
|
|
87
87
|
} as unknown as StateSource<SanityDocument>)
|
|
88
88
|
|
|
89
89
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx2'})
|
|
90
|
-
vi.mocked(
|
|
90
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
91
91
|
|
|
92
92
|
const {result} = renderHook(() => useEditDocument(docHandle))
|
|
93
93
|
const promise = result.current({...doc, foo: 'baz', extra: 'old', _id: 'doc1'})
|
|
@@ -105,7 +105,7 @@ describe('useEditDocument hook', () => {
|
|
|
105
105
|
} as unknown as StateSource<SanityDocument>)
|
|
106
106
|
|
|
107
107
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx3'})
|
|
108
|
-
vi.mocked(
|
|
108
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
109
109
|
|
|
110
110
|
const {result} = renderHook(() => useEditDocument(docHandle, 'foo'))
|
|
111
111
|
const promise = result.current((prev: unknown) => `${prev}Updated`) // 'bar' becomes 'barUpdated'
|
|
@@ -125,7 +125,7 @@ describe('useEditDocument hook', () => {
|
|
|
125
125
|
} as unknown as StateSource<SanityDocument>)
|
|
126
126
|
|
|
127
127
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx4'})
|
|
128
|
-
vi.mocked(
|
|
128
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
129
129
|
|
|
130
130
|
const {result} = renderHook(() => useEditDocument(docHandle))
|
|
131
131
|
const promise = result.current((prevDoc) => ({...prevDoc, foo: 'baz'}))
|
|
@@ -143,7 +143,7 @@ describe('useEditDocument hook', () => {
|
|
|
143
143
|
} as unknown as StateSource<SanityDocument>)
|
|
144
144
|
|
|
145
145
|
const fakeApply = vi.fn()
|
|
146
|
-
vi.mocked(
|
|
146
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(fakeApply)
|
|
147
147
|
|
|
148
148
|
const {result} = renderHook(() => useEditDocument(docHandle))
|
|
149
149
|
expect(() => result.current('notAnObject' as unknown as SanityDocument)).toThrowError(
|
|
@@ -12,7 +12,7 @@ import {type SanityDocument} from '@sanity/types'
|
|
|
12
12
|
import {useCallback} from 'react'
|
|
13
13
|
|
|
14
14
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
15
|
-
import {
|
|
15
|
+
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
16
16
|
|
|
17
17
|
const ignoredKeys = ['_id', '_type', '_createdAt', '_updatedAt', '_rev']
|
|
18
18
|
|
|
@@ -154,7 +154,7 @@ export function useEditDocument(
|
|
|
154
154
|
const resourceId = getResourceId(doc.resourceId)!
|
|
155
155
|
const documentId = doc._id
|
|
156
156
|
const instance = useSanityInstance(resourceId)
|
|
157
|
-
const apply =
|
|
157
|
+
const apply = useApplyDocumentActions(resourceId)
|
|
158
158
|
const isDocumentReady = useCallback(
|
|
159
159
|
() => getDocumentState(instance, documentId).getCurrent() !== undefined,
|
|
160
160
|
[instance, documentId],
|
|
@@ -3,11 +3,11 @@ import {describe, vi} from 'vitest'
|
|
|
3
3
|
|
|
4
4
|
import {evaluateSync, parse} from '../_synchronous-groq-js.mjs'
|
|
5
5
|
import {useQuery} from '../query/useQuery'
|
|
6
|
-
import {
|
|
6
|
+
import {useDocuments} from './useDocuments'
|
|
7
7
|
|
|
8
8
|
vi.mock('../query/useQuery')
|
|
9
9
|
|
|
10
|
-
describe('
|
|
10
|
+
describe('useDocuments', () => {
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
const dataset = [
|
|
13
13
|
{
|
|
@@ -76,13 +76,13 @@ describe('useInfiniteList', () => {
|
|
|
76
76
|
|
|
77
77
|
it('should respect custom page size', () => {
|
|
78
78
|
const customBatchSize = 2
|
|
79
|
-
const {result} = renderHook(() =>
|
|
79
|
+
const {result} = renderHook(() => useDocuments({batchSize: customBatchSize}))
|
|
80
80
|
|
|
81
81
|
expect(result.current.data.length).toBe(customBatchSize)
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
it('should filter by document type', () => {
|
|
85
|
-
const {result} = renderHook(() =>
|
|
85
|
+
const {result} = renderHook(() => useDocuments({filter: '_type == "movie"'}))
|
|
86
86
|
|
|
87
87
|
expect(result.current.data.every((doc) => doc._type === 'movie')).toBe(true)
|
|
88
88
|
expect(result.current.count).toBe(5) // 5 movies in the dataset
|
|
@@ -90,7 +90,7 @@ describe('useInfiniteList', () => {
|
|
|
90
90
|
|
|
91
91
|
// groq-js doesn't support search filters yet
|
|
92
92
|
it.skip('should apply search filter', () => {
|
|
93
|
-
const {result} = renderHook(() =>
|
|
93
|
+
const {result} = renderHook(() => useDocuments({search: 'inter'}))
|
|
94
94
|
|
|
95
95
|
// Should match "Interstellar"
|
|
96
96
|
expect(result.current.data.some((doc) => doc._id === 'movie3')).toBe(true)
|
|
@@ -98,7 +98,7 @@ describe('useInfiniteList', () => {
|
|
|
98
98
|
|
|
99
99
|
it('should apply ordering', () => {
|
|
100
100
|
const {result} = renderHook(() =>
|
|
101
|
-
|
|
101
|
+
useDocuments({
|
|
102
102
|
filter: '_type == "movie"',
|
|
103
103
|
orderings: [{field: 'releaseYear', direction: 'desc'}],
|
|
104
104
|
}),
|
|
@@ -110,7 +110,7 @@ describe('useInfiniteList', () => {
|
|
|
110
110
|
|
|
111
111
|
it('should load more data when loadMore is called', () => {
|
|
112
112
|
const batchSize = 2
|
|
113
|
-
const {result} = renderHook(() =>
|
|
113
|
+
const {result} = renderHook(() => useDocuments({batchSize: batchSize}))
|
|
114
114
|
|
|
115
115
|
expect(result.current.data.length).toBe(batchSize)
|
|
116
116
|
|
|
@@ -122,7 +122,7 @@ describe('useInfiniteList', () => {
|
|
|
122
122
|
})
|
|
123
123
|
|
|
124
124
|
it('should indicate when there is more data to load', () => {
|
|
125
|
-
const {result} = renderHook(() =>
|
|
125
|
+
const {result} = renderHook(() => useDocuments({batchSize: 3}))
|
|
126
126
|
expect(result.current.hasMore).toBe(true)
|
|
127
127
|
// Load all remaining data
|
|
128
128
|
act(() => {
|
|
@@ -133,7 +133,7 @@ describe('useInfiniteList', () => {
|
|
|
133
133
|
|
|
134
134
|
// New test case for resetting limit when filter changes
|
|
135
135
|
it('should reset limit when filter changes', () => {
|
|
136
|
-
const {result, rerender} = renderHook((props) =>
|
|
136
|
+
const {result, rerender} = renderHook((props) => useDocuments(props), {
|
|
137
137
|
initialProps: {batchSize: 2, filter: ''},
|
|
138
138
|
})
|
|
139
139
|
// Initially, data length equals pageSize (2)
|
|
@@ -11,18 +11,18 @@ const DEFAULT_PERSPECTIVE = 'drafts'
|
|
|
11
11
|
* Result structure returned from the infinite list query
|
|
12
12
|
* @internal
|
|
13
13
|
*/
|
|
14
|
-
interface
|
|
14
|
+
interface UseDocumentsQueryResult {
|
|
15
15
|
count: number
|
|
16
16
|
data: DocumentHandle[]
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Configuration options for the
|
|
20
|
+
* Configuration options for the useDocuments hook
|
|
21
21
|
*
|
|
22
22
|
* @beta
|
|
23
23
|
* @category Types
|
|
24
24
|
*/
|
|
25
|
-
export interface
|
|
25
|
+
export interface DocumentsOptions extends QueryOptions {
|
|
26
26
|
/**
|
|
27
27
|
* GROQ filter expression to apply to the query
|
|
28
28
|
*/
|
|
@@ -42,12 +42,12 @@ export interface InfiniteListOptions extends QueryOptions {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Return value from the
|
|
45
|
+
* Return value from the useDocuments hook
|
|
46
46
|
*
|
|
47
47
|
* @beta
|
|
48
48
|
* @category Types
|
|
49
49
|
*/
|
|
50
|
-
export interface
|
|
50
|
+
export interface DocumentsResponse {
|
|
51
51
|
/**
|
|
52
52
|
* Array of document handles for the current batch
|
|
53
53
|
*/
|
|
@@ -78,10 +78,10 @@ export interface InfiniteList {
|
|
|
78
78
|
* @beta
|
|
79
79
|
* @category Documents
|
|
80
80
|
* @param options - Configuration options for the infinite list
|
|
81
|
-
* @returns An object containing the list of document handles, the loading state, the total count of
|
|
81
|
+
* @returns An object containing the list of document handles, the loading state, the total count of retrieved document handles, and a function to load more
|
|
82
82
|
* @example
|
|
83
83
|
* ```tsx
|
|
84
|
-
* const {data, hasMore, isPending, loadMore} =
|
|
84
|
+
* const {data, hasMore, isPending, loadMore} = useDocuments({
|
|
85
85
|
* filter: '_type == "post"',
|
|
86
86
|
* search: searchTerm,
|
|
87
87
|
* batchSize: 10,
|
|
@@ -104,14 +104,14 @@ export interface InfiniteList {
|
|
|
104
104
|
* ```
|
|
105
105
|
*
|
|
106
106
|
*/
|
|
107
|
-
export function
|
|
107
|
+
export function useDocuments({
|
|
108
108
|
batchSize = DEFAULT_BATCH_SIZE,
|
|
109
109
|
params,
|
|
110
110
|
search,
|
|
111
111
|
filter,
|
|
112
112
|
orderings,
|
|
113
113
|
...options
|
|
114
|
-
}:
|
|
114
|
+
}: DocumentsOptions): DocumentsResponse {
|
|
115
115
|
const perspective = options.perspective ?? DEFAULT_PERSPECTIVE
|
|
116
116
|
const [limit, setLimit] = useState(batchSize)
|
|
117
117
|
|
|
@@ -155,7 +155,7 @@ export function useInfiniteList({
|
|
|
155
155
|
const {
|
|
156
156
|
data: {count, data},
|
|
157
157
|
isPending,
|
|
158
|
-
} = useQuery<
|
|
158
|
+
} = useQuery<UseDocumentsQueryResult>(`{"count":${countQuery},"data":${dataQuery}}`, {
|
|
159
159
|
...options,
|
|
160
160
|
params,
|
|
161
161
|
perspective,
|
|
@@ -3,11 +3,11 @@ import {describe, vi} from 'vitest'
|
|
|
3
3
|
|
|
4
4
|
import {evaluateSync, parse} from '../_synchronous-groq-js.mjs'
|
|
5
5
|
import {useQuery} from '../query/useQuery'
|
|
6
|
-
import {
|
|
6
|
+
import {usePaginatedDocuments} from './usePaginatedDocuments'
|
|
7
7
|
|
|
8
8
|
vi.mock('../query/useQuery')
|
|
9
9
|
|
|
10
|
-
describe('
|
|
10
|
+
describe('usePaginatedDocuments', () => {
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
const dataset = [
|
|
13
13
|
{
|
|
@@ -76,14 +76,14 @@ describe('usePaginatedList', () => {
|
|
|
76
76
|
|
|
77
77
|
it('should respect custom page size', () => {
|
|
78
78
|
const customPageSize = 2
|
|
79
|
-
const {result} = renderHook(() =>
|
|
79
|
+
const {result} = renderHook(() => usePaginatedDocuments({pageSize: customPageSize}))
|
|
80
80
|
|
|
81
81
|
expect(result.current.pageSize).toBe(customPageSize)
|
|
82
82
|
expect(result.current.data.length).toBeLessThanOrEqual(customPageSize)
|
|
83
83
|
})
|
|
84
84
|
|
|
85
85
|
it('should filter by document type', () => {
|
|
86
|
-
const {result} = renderHook(() =>
|
|
86
|
+
const {result} = renderHook(() => usePaginatedDocuments({filter: '_type == "movie"'}))
|
|
87
87
|
|
|
88
88
|
expect(result.current.data.every((doc) => doc._type === 'movie')).toBe(true)
|
|
89
89
|
expect(result.current.count).toBe(5) // 5 movies in the dataset
|
|
@@ -91,7 +91,7 @@ describe('usePaginatedList', () => {
|
|
|
91
91
|
|
|
92
92
|
// groq-js doesn't support search filters yet
|
|
93
93
|
it.skip('should apply search filter', () => {
|
|
94
|
-
const {result} = renderHook(() =>
|
|
94
|
+
const {result} = renderHook(() => usePaginatedDocuments({search: 'inter'}))
|
|
95
95
|
|
|
96
96
|
// Should match "Interstellar"
|
|
97
97
|
expect(result.current.data.some((doc) => doc._id === 'movie3')).toBe(true)
|
|
@@ -99,7 +99,7 @@ describe('usePaginatedList', () => {
|
|
|
99
99
|
|
|
100
100
|
it('should apply ordering', () => {
|
|
101
101
|
const {result} = renderHook(() =>
|
|
102
|
-
|
|
102
|
+
usePaginatedDocuments({
|
|
103
103
|
filter: '_type == "movie"',
|
|
104
104
|
orderings: [{field: 'releaseYear', direction: 'desc'}],
|
|
105
105
|
}),
|
|
@@ -111,7 +111,7 @@ describe('usePaginatedList', () => {
|
|
|
111
111
|
|
|
112
112
|
it('should calculate pagination values correctly', () => {
|
|
113
113
|
const pageSize = 2
|
|
114
|
-
const {result} = renderHook(() =>
|
|
114
|
+
const {result} = renderHook(() => usePaginatedDocuments({pageSize}))
|
|
115
115
|
|
|
116
116
|
expect(result.current.currentPage).toBe(1)
|
|
117
117
|
expect(result.current.totalPages).toBe(3) // 6 items with page size 2
|
|
@@ -122,7 +122,7 @@ describe('usePaginatedList', () => {
|
|
|
122
122
|
|
|
123
123
|
it('should navigate to next page', () => {
|
|
124
124
|
const pageSize = 2
|
|
125
|
-
const {result} = renderHook(() =>
|
|
125
|
+
const {result} = renderHook(() => usePaginatedDocuments({pageSize}))
|
|
126
126
|
|
|
127
127
|
expect(result.current.currentPage).toBe(1)
|
|
128
128
|
expect(result.current.data.length).toBe(pageSize)
|
|
@@ -138,7 +138,7 @@ describe('usePaginatedList', () => {
|
|
|
138
138
|
|
|
139
139
|
it('should navigate to previous page', () => {
|
|
140
140
|
const pageSize = 2
|
|
141
|
-
const {result} = renderHook(() =>
|
|
141
|
+
const {result} = renderHook(() => usePaginatedDocuments({pageSize}))
|
|
142
142
|
|
|
143
143
|
// Go to page 2 first
|
|
144
144
|
act(() => {
|
|
@@ -158,7 +158,7 @@ describe('usePaginatedList', () => {
|
|
|
158
158
|
|
|
159
159
|
it('should navigate to first page', () => {
|
|
160
160
|
const pageSize = 2
|
|
161
|
-
const {result} = renderHook(() =>
|
|
161
|
+
const {result} = renderHook(() => usePaginatedDocuments({pageSize}))
|
|
162
162
|
|
|
163
163
|
// Go to last page first
|
|
164
164
|
act(() => {
|
|
@@ -178,7 +178,7 @@ describe('usePaginatedList', () => {
|
|
|
178
178
|
|
|
179
179
|
it('should navigate to last page', () => {
|
|
180
180
|
const pageSize = 2
|
|
181
|
-
const {result} = renderHook(() =>
|
|
181
|
+
const {result} = renderHook(() => usePaginatedDocuments({pageSize}))
|
|
182
182
|
|
|
183
183
|
act(() => {
|
|
184
184
|
result.current.lastPage()
|
|
@@ -190,7 +190,7 @@ describe('usePaginatedList', () => {
|
|
|
190
190
|
|
|
191
191
|
it('should navigate to specific page', () => {
|
|
192
192
|
const pageSize = 2
|
|
193
|
-
const {result} = renderHook(() =>
|
|
193
|
+
const {result} = renderHook(() => usePaginatedDocuments({pageSize}))
|
|
194
194
|
|
|
195
195
|
act(() => {
|
|
196
196
|
result.current.goToPage(2) // Go to page 2
|
|
@@ -215,7 +215,7 @@ describe('usePaginatedList', () => {
|
|
|
215
215
|
|
|
216
216
|
it('should set page availability flags correctly', () => {
|
|
217
217
|
const pageSize = 2
|
|
218
|
-
const {result} = renderHook(() =>
|
|
218
|
+
const {result} = renderHook(() => usePaginatedDocuments({pageSize}))
|
|
219
219
|
// On first page
|
|
220
220
|
expect(result.current.hasFirstPage).toBe(false)
|
|
221
221
|
expect(result.current.hasPreviousPage).toBe(false)
|
|
@@ -241,7 +241,7 @@ describe('usePaginatedList', () => {
|
|
|
241
241
|
|
|
242
242
|
// New test case for resetting the current page when filter changes
|
|
243
243
|
it('should reset current page when filter changes', () => {
|
|
244
|
-
const {result, rerender} = renderHook((props) =>
|
|
244
|
+
const {result, rerender} = renderHook((props) => usePaginatedDocuments(props), {
|
|
245
245
|
initialProps: {pageSize: 2, filter: ''},
|
|
246
246
|
})
|
|
247
247
|
// Initially, current page should be 1
|
package/src/hooks/{paginatedList/usePaginatedList.ts → paginatedDocuments/usePaginatedDocuments.ts}
RENAMED
|
@@ -7,12 +7,12 @@ import {useQuery} from '../query/useQuery'
|
|
|
7
7
|
const DEFAULT_PERSPECTIVE = 'drafts'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Configuration options for the
|
|
10
|
+
* Configuration options for the usePaginatedDocuments hook
|
|
11
11
|
*
|
|
12
12
|
* @beta
|
|
13
13
|
* @category Types
|
|
14
14
|
*/
|
|
15
|
-
export interface
|
|
15
|
+
export interface PaginatedDocumentsOptions extends QueryOptions {
|
|
16
16
|
/**
|
|
17
17
|
* GROQ filter expression to apply to the query
|
|
18
18
|
*/
|
|
@@ -32,12 +32,12 @@ export interface PaginatedListOptions extends QueryOptions {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Return value from the
|
|
35
|
+
* Return value from the usePaginatedDocuments hook
|
|
36
36
|
*
|
|
37
37
|
* @beta
|
|
38
38
|
* @category Types
|
|
39
39
|
*/
|
|
40
|
-
export interface
|
|
40
|
+
export interface PaginatedDocumentsResponse {
|
|
41
41
|
/**
|
|
42
42
|
* Array of document handles for the current page
|
|
43
43
|
*/
|
|
@@ -136,7 +136,7 @@ export interface PaginatedList {
|
|
|
136
136
|
* previousPage,
|
|
137
137
|
* hasNextPage,
|
|
138
138
|
* hasPreviousPage
|
|
139
|
-
* } =
|
|
139
|
+
* } = usePaginatedDocuments({
|
|
140
140
|
* filter: '_type == "post"',
|
|
141
141
|
* search: searchTerm,
|
|
142
142
|
* pageSize: 10,
|
|
@@ -160,14 +160,14 @@ export interface PaginatedList {
|
|
|
160
160
|
* ```
|
|
161
161
|
*
|
|
162
162
|
*/
|
|
163
|
-
export function
|
|
163
|
+
export function usePaginatedDocuments({
|
|
164
164
|
filter = '',
|
|
165
165
|
pageSize = 25,
|
|
166
166
|
params = {},
|
|
167
167
|
orderings,
|
|
168
168
|
search,
|
|
169
169
|
...options
|
|
170
|
-
}:
|
|
170
|
+
}: PaginatedDocumentsOptions = {}): PaginatedDocumentsResponse {
|
|
171
171
|
const [pageIndex, setPageIndex] = useState(0)
|
|
172
172
|
const key = JSON.stringify({filter, search, params, orderings, pageSize})
|
|
173
173
|
// Reset the pageIndex to 0 whenever any query parameters (filter, search,
|
|
@@ -45,12 +45,12 @@ const mockDocument: DocumentHandle = {
|
|
|
45
45
|
|
|
46
46
|
function TestComponent({document}: {document: DocumentHandle}) {
|
|
47
47
|
const ref = useRef(null)
|
|
48
|
-
const {
|
|
48
|
+
const {data, isPending} = usePreview({document, ref})
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
51
|
<div ref={ref}>
|
|
52
|
-
<h1>{
|
|
53
|
-
<p>{
|
|
52
|
+
<h1>{data?.title}</h1>
|
|
53
|
+
<p>{data?.subtitle}</p>
|
|
54
54
|
{isPending && <div>Pending...</div>}
|
|
55
55
|
</div>
|
|
56
56
|
)
|
|
@@ -75,7 +75,7 @@ describe('usePreview', () => {
|
|
|
75
75
|
test('it only subscribes when element is visible', async () => {
|
|
76
76
|
// Setup initial state
|
|
77
77
|
getCurrent.mockReturnValue({
|
|
78
|
-
|
|
78
|
+
data: {title: 'Initial Title', subtitle: 'Initial Subtitle'},
|
|
79
79
|
isPending: false,
|
|
80
80
|
})
|
|
81
81
|
const eventsUnsubscribe = vi.fn()
|
|
@@ -139,7 +139,7 @@ describe('usePreview', () => {
|
|
|
139
139
|
intersectionObserverCallback([{isIntersecting: true} as IntersectionObserverEntry])
|
|
140
140
|
await resolvePromise
|
|
141
141
|
getCurrent.mockReturnValue({
|
|
142
|
-
|
|
142
|
+
data: {title: 'Resolved Title', subtitle: 'Resolved Subtitle'},
|
|
143
143
|
isPending: false,
|
|
144
144
|
})
|
|
145
145
|
subscriber?.()
|
|
@@ -156,7 +156,7 @@ describe('usePreview', () => {
|
|
|
156
156
|
delete window.IntersectionObserver
|
|
157
157
|
|
|
158
158
|
getCurrent.mockReturnValue({
|
|
159
|
-
|
|
159
|
+
data: {title: 'Fallback Title', subtitle: 'Fallback Subtitle'},
|
|
160
160
|
isPending: false,
|
|
161
161
|
})
|
|
162
162
|
subscribe.mockImplementation(() => vi.fn())
|
|
@@ -19,7 +19,7 @@ export interface UsePreviewOptions {
|
|
|
19
19
|
*/
|
|
20
20
|
export interface UsePreviewResults {
|
|
21
21
|
/** The results of resolving the document’s preview values */
|
|
22
|
-
|
|
22
|
+
data: PreviewValue
|
|
23
23
|
/** True when preview values are being refreshed */
|
|
24
24
|
isPending: boolean
|
|
25
25
|
}
|
|
@@ -40,7 +40,7 @@ export interface UsePreviewResults {
|
|
|
40
40
|
* ```
|
|
41
41
|
* // PreviewComponent.jsx
|
|
42
42
|
* export default function PreviewComponent({ document }) {
|
|
43
|
-
* const {
|
|
43
|
+
* const { data: { title, subtitle, media }, isPending } = usePreview({ document })
|
|
44
44
|
* return (
|
|
45
45
|
* <article style={{ opacity: isPending ? 0.5 : 1}}>
|
|
46
46
|
* {media?.type === 'image-asset' ? <img src={media.url} alt='' /> : ''}
|
|
@@ -51,12 +51,12 @@ export interface UsePreviewResults {
|
|
|
51
51
|
* }
|
|
52
52
|
*
|
|
53
53
|
* // DocumentList.jsx
|
|
54
|
-
* const {
|
|
54
|
+
* const { data } = useDocuments({ filter: '_type == "movie"' })
|
|
55
55
|
* return (
|
|
56
56
|
* <div>
|
|
57
57
|
* <h1>Movies</h1>
|
|
58
58
|
* <ul>
|
|
59
|
-
* {
|
|
59
|
+
* {data.map(movie => (
|
|
60
60
|
* <li key={movie._id}>
|
|
61
61
|
* <Suspense fallback='Loading…'>
|
|
62
62
|
* <PreviewComponent document={movie} />
|
|
@@ -115,7 +115,7 @@ export function usePreview({document: {_id, _type}, ref}: UsePreviewOptions): Us
|
|
|
115
115
|
// Create getSnapshot function to return current state
|
|
116
116
|
const getSnapshot = useCallback(() => {
|
|
117
117
|
const currentState = stateSource.getCurrent()
|
|
118
|
-
if (currentState.
|
|
118
|
+
if (currentState.data === null) throw resolvePreview(instance, {document: {_id, _type}})
|
|
119
119
|
return currentState as UsePreviewResults
|
|
120
120
|
}, [_id, _type, instance, stateSource])
|
|
121
121
|
|
|
@@ -61,12 +61,12 @@ function TestComponent({
|
|
|
61
61
|
projection: ValidProjection
|
|
62
62
|
}) {
|
|
63
63
|
const ref = useRef(null)
|
|
64
|
-
const {
|
|
64
|
+
const {data, isPending} = useProjection<ProjectionResult>({document, projection, ref})
|
|
65
65
|
|
|
66
66
|
return (
|
|
67
67
|
<div ref={ref}>
|
|
68
|
-
<h1>{
|
|
69
|
-
<p>{
|
|
68
|
+
<h1>{data.title}</h1>
|
|
69
|
+
<p>{data.description}</p>
|
|
70
70
|
{isPending && <div>Pending...</div>}
|
|
71
71
|
</div>
|
|
72
72
|
)
|
|
@@ -91,7 +91,7 @@ describe('useProjection', () => {
|
|
|
91
91
|
test('it only subscribes when element is visible', async () => {
|
|
92
92
|
// Setup initial state
|
|
93
93
|
getCurrent.mockReturnValue({
|
|
94
|
-
|
|
94
|
+
data: {title: 'Initial Title', description: 'Initial Description'},
|
|
95
95
|
isPending: false,
|
|
96
96
|
})
|
|
97
97
|
const eventsUnsubscribe = vi.fn()
|
|
@@ -129,12 +129,12 @@ describe('useProjection', () => {
|
|
|
129
129
|
test('it suspends and resolves data when element becomes visible', async () => {
|
|
130
130
|
// Mock the initial state to trigger suspense
|
|
131
131
|
getCurrent.mockReturnValueOnce({
|
|
132
|
-
|
|
132
|
+
data: null,
|
|
133
133
|
isPending: true,
|
|
134
134
|
})
|
|
135
135
|
|
|
136
136
|
const resolvedData = {
|
|
137
|
-
|
|
137
|
+
data: {title: 'Resolved Title', description: 'Resolved Description'},
|
|
138
138
|
isPending: false,
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -169,7 +169,7 @@ describe('useProjection', () => {
|
|
|
169
169
|
delete window.IntersectionObserver
|
|
170
170
|
|
|
171
171
|
getCurrent.mockReturnValue({
|
|
172
|
-
|
|
172
|
+
data: {title: 'Fallback Title', description: 'Fallback Description'},
|
|
173
173
|
isPending: false,
|
|
174
174
|
})
|
|
175
175
|
subscribe.mockImplementation(() => vi.fn())
|
|
@@ -188,7 +188,7 @@ describe('useProjection', () => {
|
|
|
188
188
|
|
|
189
189
|
test('it updates when projection changes', async () => {
|
|
190
190
|
getCurrent.mockReturnValue({
|
|
191
|
-
|
|
191
|
+
data: {title: 'Initial Title'},
|
|
192
192
|
isPending: false,
|
|
193
193
|
})
|
|
194
194
|
const eventsUnsubscribe = vi.fn()
|
|
@@ -202,7 +202,7 @@ describe('useProjection', () => {
|
|
|
202
202
|
|
|
203
203
|
// Change projection
|
|
204
204
|
getCurrent.mockReturnValue({
|
|
205
|
-
|
|
205
|
+
data: {title: 'Updated Title', description: 'Added Description'},
|
|
206
206
|
isPending: false,
|
|
207
207
|
})
|
|
208
208
|
|
|
@@ -9,14 +9,22 @@ import {distinctUntilChanged, EMPTY, Observable, startWith, switchMap} from 'rxj
|
|
|
9
9
|
|
|
10
10
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @public
|
|
14
|
+
* @category Types
|
|
15
|
+
*/
|
|
16
|
+
export interface UseProjectionOptions {
|
|
13
17
|
document: DocumentHandle
|
|
14
18
|
projection: ValidProjection
|
|
15
19
|
ref?: React.RefObject<unknown>
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @public
|
|
24
|
+
* @category Types
|
|
25
|
+
*/
|
|
26
|
+
export interface UseProjectionResults<TResult extends object> {
|
|
27
|
+
data: TResult
|
|
20
28
|
isPending: boolean
|
|
21
29
|
}
|
|
22
30
|
|
|
@@ -32,40 +40,44 @@ interface UseProjectionResults<TResult extends object> {
|
|
|
32
40
|
* @param options - The document handle for the document you want to project values from, the projection string, and an optional ref
|
|
33
41
|
* @returns The projection values for the given document and a boolean to indicate whether the resolution is pending
|
|
34
42
|
*
|
|
35
|
-
* @example Using a projection to
|
|
43
|
+
* @example Using a projection to render a preview of document
|
|
36
44
|
* ```
|
|
37
45
|
* // ProjectionComponent.jsx
|
|
38
46
|
* export default function ProjectionComponent({ document }) {
|
|
39
47
|
* const ref = useRef(null)
|
|
40
|
-
* const { results: { title,
|
|
48
|
+
* const { results: { title, coverImage, authors }, isPending } = useProjection({
|
|
41
49
|
* document,
|
|
42
|
-
*
|
|
43
|
-
*
|
|
50
|
+
* ref,
|
|
51
|
+
* projection: `{
|
|
52
|
+
* title,
|
|
53
|
+
* 'coverImage': cover.asset->url,
|
|
54
|
+
* 'authors': array::join(authors[]->{'name': firstName + ' ' + lastName + ' '}.name, ', ')
|
|
55
|
+
* }`,
|
|
44
56
|
* })
|
|
45
57
|
*
|
|
46
58
|
* return (
|
|
47
59
|
* <article ref={ref} style={{ opacity: isPending ? 0.5 : 1}}>
|
|
48
60
|
* <h2>{title}</h2>
|
|
49
|
-
* <
|
|
61
|
+
* <img src={coverImage} alt={title} />
|
|
50
62
|
* <p>{authors}</p>
|
|
51
63
|
* </article>
|
|
52
64
|
* )
|
|
53
65
|
* }
|
|
54
66
|
* ```
|
|
55
67
|
*
|
|
56
|
-
* @example Combining with
|
|
68
|
+
* @example Combining with useDocuments to render a collection with specific fields
|
|
57
69
|
* ```
|
|
58
70
|
* // DocumentList.jsx
|
|
59
|
-
* const { data } =
|
|
71
|
+
* const { data } = useDocuments({ filter: '_type == "article"' })
|
|
60
72
|
* return (
|
|
61
73
|
* <div>
|
|
62
|
-
* <h1>
|
|
74
|
+
* <h1>Books</h1>
|
|
63
75
|
* <ul>
|
|
64
|
-
* {data.map(
|
|
65
|
-
* <li key={
|
|
76
|
+
* {data.map(book => (
|
|
77
|
+
* <li key={book._id}>
|
|
66
78
|
* <Suspense fallback='Loading…'>
|
|
67
79
|
* <ProjectionComponent
|
|
68
|
-
* document={
|
|
80
|
+
* document={book}
|
|
69
81
|
* />
|
|
70
82
|
* </Suspense>
|
|
71
83
|
* </li>
|
|
@@ -126,7 +138,7 @@ export function useProjection<TResult extends object>({
|
|
|
126
138
|
// Create getSnapshot function to return current state
|
|
127
139
|
const getSnapshot = useCallback(() => {
|
|
128
140
|
const currentState = stateSource.getCurrent()
|
|
129
|
-
if (currentState.
|
|
141
|
+
if (currentState.data === null)
|
|
130
142
|
throw resolveProjection(instance, {document: {_id, _type}, projection})
|
|
131
143
|
return currentState as UseProjectionResults<TResult>
|
|
132
144
|
}, [_id, _type, projection, instance, stateSource])
|
|
@@ -32,7 +32,10 @@ type UseProject = {
|
|
|
32
32
|
(projectId: string): SanityProject
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* @public
|
|
37
|
+
* @function
|
|
38
|
+
*/
|
|
36
39
|
export const useProject: UseProject = createStateSourceHook({
|
|
37
40
|
// remove `undefined` since we're suspending when that is the case
|
|
38
41
|
getState: getProjectState as (
|