@contember/bindx-react 0.1.44 → 0.1.46
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { GraphQlClient } from '@contember/graphql-client';
|
|
3
2
|
import { SnapshotStore, UndoManager, type SchemaDefinition, type SchemaNames, type UndoManagerConfig, type UpdateMode } from '@contember/bindx';
|
|
3
|
+
import { type BindxGraphQlClient } from './BackendAdapterContext.js';
|
|
4
4
|
/**
|
|
5
5
|
* Converts SchemaNames (Contember format) to SchemaDefinition (bindx format)
|
|
6
6
|
* so that a SchemaRegistry can be created for standalone hooks.
|
|
@@ -16,7 +16,7 @@ export interface ContemberBindxProviderProps {
|
|
|
16
16
|
store?: SnapshotStore;
|
|
17
17
|
/** Children */
|
|
18
18
|
children: ReactNode;
|
|
19
|
-
client:
|
|
19
|
+
client: BindxGraphQlClient;
|
|
20
20
|
/** Enable undo/redo functionality. Pass true to auto-create an UndoManager, or pass an UndoManager instance. */
|
|
21
21
|
undoManager?: UndoManager | boolean;
|
|
22
22
|
/** Configuration for auto-created undo manager (only used when undoManager={true}) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContemberBindxProvider.d.ts","sourceRoot":"","sources":["../../src/hooks/ContemberBindxProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"ContemberBindxProvider.d.ts","sourceRoot":"","sources":["../../src/hooks/ContemberBindxProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAErD,OAAO,EAAoB,aAAa,EAAuF,WAAW,EAAqC,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAiB,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AACxS,OAAO,EAAwC,KAAK,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAG1G;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAwBnG;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC3C,gDAAgD;IAChD,MAAM,EAAE,WAAW,CAAA;IACnB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,eAAe;IACf,QAAQ,EAAE,SAAS,CAAA;IAEnB,MAAM,EAAE,kBAAkB,CAAA;IAE1B,gHAAgH;IAChH,WAAW,CAAC,EAAE,WAAW,GAAG,OAAO,CAAA;IACnC,sFAAsF;IACtF,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,UAAU,CAAA;IAC9B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;GAEG;AACH,eAAO,MAAM,sBAAsB,mEAkEjC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Entity.d.ts","sourceRoot":"","sources":["../../../src/jsx/components/Entity.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"Entity.d.ts","sourceRoot":"","sources":["../../../src/jsx/components/Entity.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAkG,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AAKhJ,OAAO,KAAK,EAAkB,SAAS,EAAiB,MAAM,aAAa,CAAA;AAC3E,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAiB,KAAK,UAAU,EAAgF,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAI1M;;;GAGG;AACH,UAAU,eAAe,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAChE,kCAAkC;IAClC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAA;IAC3B,+EAA+E;IAC/E,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;IACxE,qBAAqB;IACrB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,KAAK,CAAC,SAAS,CAAA;CAC9C;AAED;;GAEG;AACH,UAAU,aAAa,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAE,SAAQ,eAAe,CAAC,QAAQ,CAAC;IACjG,sFAAsF;IACtF,EAAE,EAAE,iBAAiB,CAAA;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,KAAK,CAAA;IACd,WAAW,CAAC,EAAE,KAAK,CAAA;IACnB,uBAAuB;IACvB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED;;GAEG;AACH,UAAU,iBAAiB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAE,SAAQ,eAAe,CAAC,QAAQ,CAAC;IACrG,EAAE,CAAC,EAAE,KAAK,CAAA;IACV,8DAA8D;IAC9D,MAAM,EAAE,IAAI,CAAA;IACZ,wEAAwE;IACxE,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,OAAO,CAAC,EAAE,KAAK,CAAA;IACf,QAAQ,CAAC,EAAE,KAAK,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IACrF,aAAa,CAAC,QAAQ,CAAC,GACvB,iBAAiB,CAAC,QAAQ,CAAC,CAAA;AAmT9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,iBAAS,UAAU,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1D,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,GAC1B,YAAY,GAAG,IAAI,CA2BrB;AAGD,eAAO,MAAM,MAAM,EAAkC,OAAO,UAAU,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contember/bindx-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.46",
|
|
4
4
|
"description": "Type-safe React data binding framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"react": ">=18.0.0"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@contember/bindx": "0.1.
|
|
19
|
-
"@contember/bindx-client": "0.1.
|
|
18
|
+
"@contember/bindx": "0.1.46",
|
|
19
|
+
"@contember/bindx-client": "0.1.46",
|
|
20
20
|
"@contember/graphql-client": "^2.1.0-beta.1",
|
|
21
21
|
"@radix-ui/primitive": "^1.1.3",
|
|
22
22
|
"@radix-ui/react-slot": "^1.2.4"
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { memo, useMemo, type ReactNode } from 'react'
|
|
2
|
-
import { GraphQlClient } from '@contember/graphql-client'
|
|
3
2
|
import { ContentClient } from '@contember/bindx-client'
|
|
4
3
|
import { ContemberAdapter, SnapshotStore, ActionDispatcher, BatchPersister, MutationCollector, ContemberSchemaMutationAdapter, UndoManager, SchemaRegistry, NotificationStore, type SchemaDefinition, type SchemaNames, type FieldDef, type UndoManagerConfig, type UpdateMode } from '@contember/bindx'
|
|
5
|
-
import { BindxContext, type BindxContextValue } from './BackendAdapterContext.js'
|
|
4
|
+
import { BindxContext, type BindxContextValue, type BindxGraphQlClient } from './BackendAdapterContext.js'
|
|
6
5
|
import { QueryBatcher } from '../batching/QueryBatcher.js'
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -46,7 +45,7 @@ export interface ContemberBindxProviderProps {
|
|
|
46
45
|
/** Children */
|
|
47
46
|
children: ReactNode
|
|
48
47
|
|
|
49
|
-
client:
|
|
48
|
+
client: BindxGraphQlClient
|
|
50
49
|
|
|
51
50
|
/** Enable undo/redo functionality. Pass true to auto-create an UndoManager, or pass an UndoManager instance. */
|
|
52
51
|
undoManager?: UndoManager | boolean
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { memo, useCallback, useEffect, useMemo, useRef, useSyncExternalStore, type ReactElement } from 'react'
|
|
1
|
+
import React, { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, type ReactElement } from 'react'
|
|
2
2
|
import { useBindxContext, useSchemaRegistry } from '../../hooks/BackendAdapterContext.js'
|
|
3
3
|
import { annotateElement } from '../devAnnotations.js'
|
|
4
4
|
import { useEntity } from '../../hooks/useEntity.js'
|
|
@@ -163,49 +163,83 @@ interface CreateModeSnapshot {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
|
-
* Internal component for create mode (creating a new entity)
|
|
166
|
+
* Internal component for create mode (creating a new entity).
|
|
167
|
+
*
|
|
168
|
+
* The draft entity is minted in a layout effect, NEVER during render. A
|
|
169
|
+
* render-phase store mutation is unsafe two ways:
|
|
170
|
+
* - it leaks the draft whenever the render is discarded — StrictMode
|
|
171
|
+
* double-invoke, an aborted concurrent render, a thrown render, Suspense —
|
|
172
|
+
* because no effect cleanup runs for a render that never commits;
|
|
173
|
+
* - it synchronously notifies the store's global subscribers, force-updating an
|
|
174
|
+
* already-mounted aggregate subscriber (e.g. a `usePersist` consumer)
|
|
175
|
+
* mid-render. React rejects that ("Cannot update a component while rendering a
|
|
176
|
+
* different component") and the create form renders blank.
|
|
177
|
+
* A layout effect runs before paint, so the draft exists before the user sees
|
|
178
|
+
* anything and its creation notifies subscribers normally (no stale dirty state).
|
|
167
179
|
*/
|
|
168
180
|
function EntityCreateMode({
|
|
169
181
|
entityType,
|
|
170
182
|
children,
|
|
171
|
-
error: errorFallback,
|
|
172
183
|
onPersisted,
|
|
173
184
|
}: EntityCreateModeProps): ReactElement {
|
|
174
|
-
const { store
|
|
175
|
-
const schemaRegistry = useSchemaRegistry()
|
|
185
|
+
const { store } = useBindxContext()
|
|
176
186
|
const tempIdRef = useRef<string | null>(null)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (tempIdRef.current) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// Lifecycle of the draft entity:
|
|
189
|
-
// - Re-seed it under the SAME temp id if a previous unmount's cleanup removed
|
|
190
|
-
// it. This keeps the form bound across a React StrictMode mount→cleanup→mount
|
|
191
|
-
// cycle (effects run twice in dev), where the memoized id survives but the
|
|
192
|
-
// snapshot would otherwise be gone. No-op on first mount and after the entity
|
|
193
|
-
// was persisted (rekeyed to a server id).
|
|
194
|
-
// - On unmount, discard a never-persisted draft. Drop its root registration and
|
|
195
|
-
// let the reachability-aware sweep reclaim it ONLY if nothing else references
|
|
196
|
-
// it — a draft connected into another live parent stays reachable and survives
|
|
197
|
-
// (the diamond / shared-create case). A persisted entity is left untouched.
|
|
198
|
-
useEffect(() => {
|
|
199
|
-
if (!store.getPersistedId(entityType, tempId) && !store.hasEntity(entityType, tempId)) {
|
|
200
|
-
store.createEntity(entityType, { id: tempId })
|
|
187
|
+
const [tempId, setTempId] = useState<string | null>(null)
|
|
188
|
+
|
|
189
|
+
useLayoutEffect(() => {
|
|
190
|
+
if (!tempIdRef.current) {
|
|
191
|
+
tempIdRef.current = store.createEntity(entityType)
|
|
192
|
+
setTempId(tempIdRef.current)
|
|
193
|
+
} else if (!store.getPersistedId(entityType, tempIdRef.current) && !store.hasEntity(entityType, tempIdRef.current)) {
|
|
194
|
+
// Re-seed under the SAME temp id if a previous cleanup removed it (a React
|
|
195
|
+
// StrictMode mount→cleanup→mount cycle), so the form stays bound across it.
|
|
196
|
+
store.createEntity(entityType, { id: tempIdRef.current })
|
|
201
197
|
}
|
|
202
198
|
return () => {
|
|
203
|
-
|
|
204
|
-
|
|
199
|
+
const id = tempIdRef.current
|
|
200
|
+
// On unmount, discard a never-persisted draft: drop its root registration
|
|
201
|
+
// and let the reachability-aware sweep reclaim it ONLY if nothing else
|
|
202
|
+
// references it — a draft connected into another live parent stays reachable
|
|
203
|
+
// and survives (the diamond / shared-create case). A persisted entity is
|
|
204
|
+
// left untouched.
|
|
205
|
+
if (id && !store.getPersistedId(entityType, id)) {
|
|
206
|
+
store.unregisterRootEntity(entityType, id)
|
|
205
207
|
store.sweepUnreachableCreated()
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
|
-
}, [store, entityType
|
|
210
|
+
}, [store, entityType])
|
|
211
|
+
|
|
212
|
+
// First render, before the layout effect mints the draft. The draft is created
|
|
213
|
+
// before paint, so this empty frame is never visible.
|
|
214
|
+
if (tempId === null) {
|
|
215
|
+
return <></>
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<EntityCreateModeInner entityType={entityType} tempId={tempId} onPersisted={onPersisted}>
|
|
220
|
+
{children}
|
|
221
|
+
</EntityCreateModeInner>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Renders the create-mode draft once it exists. Split out of EntityCreateMode so
|
|
227
|
+
* its store subscriptions and selection collection always run against a real
|
|
228
|
+
* tempId (never null) — the hooks stay unconditional.
|
|
229
|
+
*/
|
|
230
|
+
function EntityCreateModeInner({
|
|
231
|
+
entityType,
|
|
232
|
+
tempId,
|
|
233
|
+
children,
|
|
234
|
+
onPersisted,
|
|
235
|
+
}: {
|
|
236
|
+
entityType: string
|
|
237
|
+
tempId: string
|
|
238
|
+
children: (entity: EntityAccessor<unknown>) => React.ReactNode
|
|
239
|
+
onPersisted?: (id: string) => void
|
|
240
|
+
}): ReactElement {
|
|
241
|
+
const { store, dispatcher } = useBindxContext()
|
|
242
|
+
const schemaRegistry = useSchemaRegistry()
|
|
209
243
|
|
|
210
244
|
// Subscribe to store changes for this entity
|
|
211
245
|
const subscribe = useCallback(
|