@xnetjs/react 0.12.0 → 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/dist/{chunk-SEYIZDHG.js → chunk-MEIYFD6R.js} +6 -2
- package/dist/experimental.js +1 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +193 -54
- package/package.json +10 -10
|
@@ -2478,7 +2478,9 @@ function useCan(nodeId) {
|
|
|
2478
2478
|
try {
|
|
2479
2479
|
const [read, write, del, share] = await Promise.all([
|
|
2480
2480
|
store.auth.can({ action: "read", nodeId }),
|
|
2481
|
-
|
|
2481
|
+
// 'update' is the precise verb for mutating an existing node; it
|
|
2482
|
+
// falls back to the schema's write expression (0304).
|
|
2483
|
+
store.auth.can({ action: "update", nodeId }),
|
|
2482
2484
|
store.auth.can({ action: "delete", nodeId }),
|
|
2483
2485
|
store.auth.can({ action: "share", nodeId })
|
|
2484
2486
|
]);
|
|
@@ -2553,7 +2555,9 @@ function useCanEdit(nodeId) {
|
|
|
2553
2555
|
try {
|
|
2554
2556
|
const [read, write] = await Promise.all([
|
|
2555
2557
|
store.auth.can({ action: "read", nodeId }),
|
|
2556
|
-
|
|
2558
|
+
// 'update' is the precise verb for mutating an existing node; it
|
|
2559
|
+
// falls back to the schema's write expression (0304).
|
|
2560
|
+
store.auth.can({ action: "update", nodeId })
|
|
2557
2561
|
]);
|
|
2558
2562
|
if (requestRef.current !== requestId) return;
|
|
2559
2563
|
const roleSet = /* @__PURE__ */ new Set([...read.roles, ...write.roles]);
|
package/dist/experimental.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -457,6 +457,45 @@ declare function createSavedViewVisualCanvasProjectionRequest(input: {
|
|
|
457
457
|
}): SavedViewVisualCanvasProjectionRequest;
|
|
458
458
|
declare function SavedViewResultTable({ query, columns, expandedRowId, onToggleRow, loadingLabel, emptyLabel, formatValue }: SavedViewResultTableProps): JSX.Element;
|
|
459
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Minimal Awareness surface (compatible with y-protocols/awareness).
|
|
462
|
+
* Duck-typed so tests and non-Yjs transports can provide their own.
|
|
463
|
+
*/
|
|
464
|
+
interface PresenceAwareness {
|
|
465
|
+
clientID: number;
|
|
466
|
+
getLocalState(): Record<string, unknown> | null;
|
|
467
|
+
setLocalState(state: Record<string, unknown> | null): void;
|
|
468
|
+
getStates(): Map<number, Record<string, unknown>>;
|
|
469
|
+
on(event: 'change', handler: () => void): void;
|
|
470
|
+
off(event: 'change', handler: () => void): void;
|
|
471
|
+
}
|
|
472
|
+
interface PresencePeer<T> {
|
|
473
|
+
/** Awareness client id (one per connected tab/device). */
|
|
474
|
+
clientId: number;
|
|
475
|
+
/** That peer's presence state. */
|
|
476
|
+
state: T;
|
|
477
|
+
}
|
|
478
|
+
interface UsePresenceOptions {
|
|
479
|
+
/**
|
|
480
|
+
* Minimum ms between broadcasts (default 33 ≈ 30fps). The hub rate-limits
|
|
481
|
+
* at 100 messages/sec/connection and closes the socket on repeated
|
|
482
|
+
* breaches — do not go below ~16ms.
|
|
483
|
+
*/
|
|
484
|
+
throttleMs?: number;
|
|
485
|
+
}
|
|
486
|
+
interface UsePresenceResult<T> {
|
|
487
|
+
/** Remote peers only (local client excluded). Evicted on disconnect. */
|
|
488
|
+
peers: Array<PresencePeer<T>>;
|
|
489
|
+
/**
|
|
490
|
+
* Merge a partial patch into local presence. Patches within one throttle
|
|
491
|
+
* window coalesce into a single broadcast (leading + trailing edge).
|
|
492
|
+
*/
|
|
493
|
+
setState: (patch: Partial<T>) => void;
|
|
494
|
+
/** Local awareness client id, or null before awareness attaches. */
|
|
495
|
+
clientId: number | null;
|
|
496
|
+
}
|
|
497
|
+
declare function usePresence<T extends Record<string, unknown>>(awareness: PresenceAwareness | null | undefined, initialState: T, options?: UsePresenceOptions): UsePresenceResult<T>;
|
|
498
|
+
|
|
460
499
|
/**
|
|
461
500
|
* useCanvasTaskSync - Reconcile canvas checklist items with Task nodes.
|
|
462
501
|
*
|
|
@@ -630,6 +669,31 @@ interface UseGlobalUndoResult {
|
|
|
630
669
|
}
|
|
631
670
|
declare function useGlobalUndo(): UseGlobalUndoResult;
|
|
632
671
|
|
|
672
|
+
/**
|
|
673
|
+
* useCanCreate - Check whether the current user may create a node of a schema
|
|
674
|
+
* (exploration 0304).
|
|
675
|
+
*
|
|
676
|
+
* The check runs against a *draft* node built from `schemaId` + `properties`,
|
|
677
|
+
* so container relations in the draft (e.g. `space`, `channel`) resolve
|
|
678
|
+
* membership roles — pass the relation the composer is about to write into:
|
|
679
|
+
*
|
|
680
|
+
* ```tsx
|
|
681
|
+
* const { canCreate } = useCanCreate(CHAT_MESSAGE_SCHEMA_IRI, { channel: channelId })
|
|
682
|
+
* <SendButton disabled={!canCreate} />
|
|
683
|
+
* ```
|
|
684
|
+
*
|
|
685
|
+
* Schemas that declare no `create` expression fall back to their `write`
|
|
686
|
+
* policy, which includes the creator role in every preset — those schemas
|
|
687
|
+
* resolve to `canCreate: true`.
|
|
688
|
+
*/
|
|
689
|
+
|
|
690
|
+
type UseCanCreateResult = {
|
|
691
|
+
canCreate: boolean;
|
|
692
|
+
loading: boolean;
|
|
693
|
+
error: Error | null;
|
|
694
|
+
};
|
|
695
|
+
declare function useCanCreate(schemaId: SchemaIRI, properties?: Record<string, unknown>): UseCanCreateResult;
|
|
696
|
+
|
|
633
697
|
/**
|
|
634
698
|
* useBilling — reactive billing state + checkout, the same shape as `useIdentity`.
|
|
635
699
|
*
|
|
@@ -693,4 +757,4 @@ declare function flattenTaskTree(items: TaskTreeItem[], depth?: number): Rendera
|
|
|
693
757
|
declare function formatTaskDueDate(timestamp: number | undefined): string | null;
|
|
694
758
|
declare function isTaskOverdue(timestamp: number | undefined, completed: boolean): boolean;
|
|
695
759
|
|
|
696
|
-
export { type AddRowOptions, type CanvasTaskInput, type CheckoutOptions, FlatNode, type GridFieldModel, type GridOptionModel, type GridRowModel, type GridViewModel, QueryFilter, type RenderableTaskRow, type SavedViewCanvasProjectionNode, type SavedViewDateBrushSelection, type SavedViewDateBucketFieldSummary, type SavedViewDateBucketInterval, type SavedViewDateBucketSummary, type SavedViewFacetSelection, type SavedViewFacetSummary, type SavedViewFacetValueSummary, type SavedViewFeedEnrichmentAdapter, type SavedViewFeedEnrichmentEntry, type SavedViewInspectorItem, type SavedViewInspectorItemKind, type SavedViewLensDraft, type SavedViewPresentationMode, type SavedViewPrivacyChip, type SavedViewPrivacyChipTone, type SavedViewPrivacySummary, type SavedViewQueryOverride, type SavedViewQueryResult, SavedViewResultTable, type SavedViewResultTableProps, type SavedViewRowInspectorModel, SavedViewRunner, type SavedViewRunnerProps, type SavedViewSchemaRegistry, type SavedViewSortDirection, type SavedViewVisualCanvasProjectionRequest, SavedViewVisualFeed, type SavedViewVisualLayoutId, type SavedViewVisualLayoutOption, type SavedViewVisualPreviewCreator, type SavedViewVisualPreviewKind, type SavedViewVisualPreviewModel, type SavedViewVisualPreviewPrivacy, type SavedViewVisualPreviewRelationship, type SavedViewVisualTimelineBucket, type SavedViewVisualWorkspaceLayout, TaskProjectionInput, TaskTreeItem, type UseBillingResult, type UseCanvasTaskSyncOptions, type UseCanvasTaskSyncResult, type UseEffectiveSchemaResult, type UseGlobalUndoResult, type UseGridDatabaseOptions, type UseGridDatabaseResult, type UseSavedViewOptions, type UseSavedViewResult, UseTaskProjectionSyncResult, createSavedViewCanvasProjectionNodes, createSavedViewLensDraft, createSavedViewVisualCanvasProjectionRequest, createSavedViewVisualPreviewFingerprint, deriveCachedSavedViewVisualPreviews, deriveSavedViewColumns, deriveSavedViewDateBucketSummaries, deriveSavedViewFacetSummaries, deriveSavedViewPrivacyChips, deriveSavedViewRowInspector, deriveSavedViewTimelineBuckets, deriveSavedViewVisualPreview, deriveSavedViewVisualPreviews, filterSavedViewRowsByDateBrush, filterSavedViewRowsByFacets, flattenTaskTree, formatSavedViewCellValue, formatTaskDueDate, getSavedViewSensitiveResultWarning, hasSavedViewVisualPreviewSensitiveData, isSavedViewVisualPreviewEmbeddable, isTaskOverdue, mergeSavedViewFeedEnrichment, savedViewVisualPreviewIsSelfActor, useBilling, useCanvasTaskSync, useEffectiveSchema, useGlobalUndo, useGridDatabase, useSavedView };
|
|
760
|
+
export { type AddRowOptions, type CanvasTaskInput, type CheckoutOptions, FlatNode, type GridFieldModel, type GridOptionModel, type GridRowModel, type GridViewModel, type PresenceAwareness, type PresencePeer, QueryFilter, type RenderableTaskRow, type SavedViewCanvasProjectionNode, type SavedViewDateBrushSelection, type SavedViewDateBucketFieldSummary, type SavedViewDateBucketInterval, type SavedViewDateBucketSummary, type SavedViewFacetSelection, type SavedViewFacetSummary, type SavedViewFacetValueSummary, type SavedViewFeedEnrichmentAdapter, type SavedViewFeedEnrichmentEntry, type SavedViewInspectorItem, type SavedViewInspectorItemKind, type SavedViewLensDraft, type SavedViewPresentationMode, type SavedViewPrivacyChip, type SavedViewPrivacyChipTone, type SavedViewPrivacySummary, type SavedViewQueryOverride, type SavedViewQueryResult, SavedViewResultTable, type SavedViewResultTableProps, type SavedViewRowInspectorModel, SavedViewRunner, type SavedViewRunnerProps, type SavedViewSchemaRegistry, type SavedViewSortDirection, type SavedViewVisualCanvasProjectionRequest, SavedViewVisualFeed, type SavedViewVisualLayoutId, type SavedViewVisualLayoutOption, type SavedViewVisualPreviewCreator, type SavedViewVisualPreviewKind, type SavedViewVisualPreviewModel, type SavedViewVisualPreviewPrivacy, type SavedViewVisualPreviewRelationship, type SavedViewVisualTimelineBucket, type SavedViewVisualWorkspaceLayout, TaskProjectionInput, TaskTreeItem, type UseBillingResult, type UseCanCreateResult, type UseCanvasTaskSyncOptions, type UseCanvasTaskSyncResult, type UseEffectiveSchemaResult, type UseGlobalUndoResult, type UseGridDatabaseOptions, type UseGridDatabaseResult, type UsePresenceOptions, type UsePresenceResult, type UseSavedViewOptions, type UseSavedViewResult, UseTaskProjectionSyncResult, createSavedViewCanvasProjectionNodes, createSavedViewLensDraft, createSavedViewVisualCanvasProjectionRequest, createSavedViewVisualPreviewFingerprint, deriveCachedSavedViewVisualPreviews, deriveSavedViewColumns, deriveSavedViewDateBucketSummaries, deriveSavedViewFacetSummaries, deriveSavedViewPrivacyChips, deriveSavedViewRowInspector, deriveSavedViewTimelineBuckets, deriveSavedViewVisualPreview, deriveSavedViewVisualPreviews, filterSavedViewRowsByDateBrush, filterSavedViewRowsByFacets, flattenTaskTree, formatSavedViewCellValue, formatTaskDueDate, getSavedViewSensitiveResultWarning, hasSavedViewVisualPreviewSensitiveData, isSavedViewVisualPreviewEmbeddable, isTaskOverdue, mergeSavedViewFeedEnrichment, savedViewVisualPreviewIsSelfActor, useBilling, useCanCreate, useCanvasTaskSync, useEffectiveSchema, useGlobalUndo, useGridDatabase, usePresence, useSavedView };
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ import {
|
|
|
94
94
|
useUndo,
|
|
95
95
|
useVerification,
|
|
96
96
|
useVisibleComments
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-MEIYFD6R.js";
|
|
98
98
|
import {
|
|
99
99
|
EMPTY_PAGE_INFO,
|
|
100
100
|
computeFallbackPageInfo,
|
|
@@ -4205,6 +4205,92 @@ function formatSavedViewInspectorValue(key, value) {
|
|
|
4205
4205
|
return formatSavedViewCellValue(key, value);
|
|
4206
4206
|
}
|
|
4207
4207
|
|
|
4208
|
+
// src/hooks/usePresence.ts
|
|
4209
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef3, useState as useState5 } from "react";
|
|
4210
|
+
function readPeers(awareness, ownKeys) {
|
|
4211
|
+
const peers = [];
|
|
4212
|
+
awareness.getStates().forEach((state, clientId) => {
|
|
4213
|
+
if (clientId === awareness.clientID || !state) return;
|
|
4214
|
+
for (const key of ownKeys) {
|
|
4215
|
+
if (key in state) {
|
|
4216
|
+
peers.push({ clientId, state });
|
|
4217
|
+
return;
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
});
|
|
4221
|
+
return peers;
|
|
4222
|
+
}
|
|
4223
|
+
function usePresence(awareness, initialState, options = {}) {
|
|
4224
|
+
const { throttleMs = 33 } = options;
|
|
4225
|
+
const [peers, setPeers] = useState5([]);
|
|
4226
|
+
const [clientId, setClientId] = useState5(null);
|
|
4227
|
+
const ownKeysRef = useRef3(new Set(Object.keys(initialState)));
|
|
4228
|
+
const initialStateRef = useRef3(initialState);
|
|
4229
|
+
initialStateRef.current = initialState;
|
|
4230
|
+
const awarenessRef = useRef3(null);
|
|
4231
|
+
const pendingRef = useRef3({});
|
|
4232
|
+
const lastBroadcastRef = useRef3(0);
|
|
4233
|
+
const timerRef = useRef3(null);
|
|
4234
|
+
const flush = useCallback2(() => {
|
|
4235
|
+
const aw = awarenessRef.current;
|
|
4236
|
+
if (!aw) return;
|
|
4237
|
+
lastBroadcastRef.current = Date.now();
|
|
4238
|
+
const patch = pendingRef.current;
|
|
4239
|
+
pendingRef.current = {};
|
|
4240
|
+
aw.setLocalState({ ...aw.getLocalState() ?? {}, ...patch });
|
|
4241
|
+
}, []);
|
|
4242
|
+
const setState = useCallback2(
|
|
4243
|
+
(patch) => {
|
|
4244
|
+
for (const key of Object.keys(patch)) ownKeysRef.current.add(key);
|
|
4245
|
+
pendingRef.current = { ...pendingRef.current, ...patch };
|
|
4246
|
+
const elapsed = Date.now() - lastBroadcastRef.current;
|
|
4247
|
+
if (elapsed >= throttleMs) {
|
|
4248
|
+
flush();
|
|
4249
|
+
} else if (timerRef.current === null) {
|
|
4250
|
+
timerRef.current = setTimeout(() => {
|
|
4251
|
+
timerRef.current = null;
|
|
4252
|
+
flush();
|
|
4253
|
+
}, throttleMs - elapsed);
|
|
4254
|
+
}
|
|
4255
|
+
},
|
|
4256
|
+
[flush, throttleMs]
|
|
4257
|
+
);
|
|
4258
|
+
useEffect5(() => {
|
|
4259
|
+
if (!awareness) {
|
|
4260
|
+
awarenessRef.current = null;
|
|
4261
|
+
setClientId(null);
|
|
4262
|
+
setPeers([]);
|
|
4263
|
+
return;
|
|
4264
|
+
}
|
|
4265
|
+
awarenessRef.current = awareness;
|
|
4266
|
+
setClientId(awareness.clientID);
|
|
4267
|
+
const ownKeys = ownKeysRef.current;
|
|
4268
|
+
awareness.setLocalState({
|
|
4269
|
+
...awareness.getLocalState() ?? {},
|
|
4270
|
+
...initialStateRef.current
|
|
4271
|
+
});
|
|
4272
|
+
lastBroadcastRef.current = Date.now();
|
|
4273
|
+
const handleChange = () => {
|
|
4274
|
+
setPeers(readPeers(awareness, ownKeysRef.current));
|
|
4275
|
+
};
|
|
4276
|
+
handleChange();
|
|
4277
|
+
awareness.on("change", handleChange);
|
|
4278
|
+
return () => {
|
|
4279
|
+
awareness.off("change", handleChange);
|
|
4280
|
+
if (timerRef.current !== null) {
|
|
4281
|
+
clearTimeout(timerRef.current);
|
|
4282
|
+
timerRef.current = null;
|
|
4283
|
+
}
|
|
4284
|
+
pendingRef.current = {};
|
|
4285
|
+
const remaining = { ...awareness.getLocalState() ?? {} };
|
|
4286
|
+
for (const key of ownKeys) delete remaining[key];
|
|
4287
|
+
awareness.setLocalState(remaining);
|
|
4288
|
+
if (awarenessRef.current === awareness) awarenessRef.current = null;
|
|
4289
|
+
};
|
|
4290
|
+
}, [awareness]);
|
|
4291
|
+
return { peers, setState, clientId };
|
|
4292
|
+
}
|
|
4293
|
+
|
|
4208
4294
|
// src/hooks/useCanvasTaskSync.ts
|
|
4209
4295
|
function useCanvasTaskSync({
|
|
4210
4296
|
canvasId,
|
|
@@ -4237,7 +4323,7 @@ import {
|
|
|
4237
4323
|
convertCellValue,
|
|
4238
4324
|
FormulaService
|
|
4239
4325
|
} from "@xnetjs/data";
|
|
4240
|
-
import { useCallback as
|
|
4326
|
+
import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef4, useState as useState6 } from "react";
|
|
4241
4327
|
function toFieldModel(node) {
|
|
4242
4328
|
return {
|
|
4243
4329
|
id: node.id,
|
|
@@ -4358,8 +4444,8 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4358
4444
|
return effective.map((e) => e.field);
|
|
4359
4445
|
}, [fields, activeView]);
|
|
4360
4446
|
const { store } = useNodeStore();
|
|
4361
|
-
const [rollupValues, setRollupValues] =
|
|
4362
|
-
|
|
4447
|
+
const [rollupValues, setRollupValues] = useState6(/* @__PURE__ */ new Map());
|
|
4448
|
+
useEffect6(() => {
|
|
4363
4449
|
const rollupFields = fields.filter((f) => f.type === "rollup");
|
|
4364
4450
|
if (rollupFields.length === 0 || !store) {
|
|
4365
4451
|
setRollupValues((prev) => prev.size > 0 ? /* @__PURE__ */ new Map() : prev);
|
|
@@ -4459,11 +4545,11 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4459
4545
|
return sortRows(filtered, columns, activeView.sorts);
|
|
4460
4546
|
}, [rowNodes, activeView, fields, databaseId, rollupValues]);
|
|
4461
4547
|
const loading = dbStatus === "loading" || fieldStatus === "loading" || viewStatus === "loading" || rowStatus === "loading";
|
|
4462
|
-
const rowTailRef =
|
|
4463
|
-
const fieldTailRef =
|
|
4464
|
-
const viewTailRef =
|
|
4465
|
-
const optionTailRef =
|
|
4466
|
-
|
|
4548
|
+
const rowTailRef = useRef4(void 0);
|
|
4549
|
+
const fieldTailRef = useRef4(void 0);
|
|
4550
|
+
const viewTailRef = useRef4(void 0);
|
|
4551
|
+
const optionTailRef = useRef4(/* @__PURE__ */ new Map());
|
|
4552
|
+
useEffect6(() => {
|
|
4467
4553
|
const dataTail = rows.reduce(
|
|
4468
4554
|
(max, r) => !max || compareSortKeys(r.sortKey, max) > 0 ? r.sortKey : max,
|
|
4469
4555
|
void 0
|
|
@@ -4472,19 +4558,19 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4472
4558
|
rowTailRef.current = dataTail;
|
|
4473
4559
|
}
|
|
4474
4560
|
}, [rows]);
|
|
4475
|
-
|
|
4561
|
+
useEffect6(() => {
|
|
4476
4562
|
const dataTail = fields[fields.length - 1]?.sortKey;
|
|
4477
4563
|
if (dataTail && (!fieldTailRef.current || compareSortKeys(dataTail, fieldTailRef.current) > 0)) {
|
|
4478
4564
|
fieldTailRef.current = dataTail;
|
|
4479
4565
|
}
|
|
4480
4566
|
}, [fields]);
|
|
4481
|
-
|
|
4567
|
+
useEffect6(() => {
|
|
4482
4568
|
const dataTail = views[views.length - 1]?.sortKey;
|
|
4483
4569
|
if (dataTail && (!viewTailRef.current || compareSortKeys(dataTail, viewTailRef.current) > 0)) {
|
|
4484
4570
|
viewTailRef.current = dataTail;
|
|
4485
4571
|
}
|
|
4486
4572
|
}, [views]);
|
|
4487
|
-
|
|
4573
|
+
useEffect6(() => {
|
|
4488
4574
|
for (const [fieldId, list] of optionsByField) {
|
|
4489
4575
|
const dataTail = list[list.length - 1]?.sortKey;
|
|
4490
4576
|
const current = optionTailRef.current.get(fieldId);
|
|
@@ -4493,7 +4579,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4493
4579
|
}
|
|
4494
4580
|
}
|
|
4495
4581
|
}, [optionsByField]);
|
|
4496
|
-
const nextAppendKey =
|
|
4582
|
+
const nextAppendKey = useCallback3((tailRef) => {
|
|
4497
4583
|
const key = generateSortKey(tailRef.current, void 0);
|
|
4498
4584
|
tailRef.current = key;
|
|
4499
4585
|
return key;
|
|
@@ -4511,19 +4597,19 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4511
4597
|
localDID: did ?? null,
|
|
4512
4598
|
options: { localOnly: true }
|
|
4513
4599
|
});
|
|
4514
|
-
const updateRowProps =
|
|
4600
|
+
const updateRowProps = useCallback3(
|
|
4515
4601
|
async (rowId, properties) => {
|
|
4516
4602
|
await mutate.update(DatabaseRowSchema, rowId, properties);
|
|
4517
4603
|
},
|
|
4518
4604
|
[mutate]
|
|
4519
4605
|
);
|
|
4520
|
-
const updateCell =
|
|
4606
|
+
const updateCell = useCallback3(
|
|
4521
4607
|
async (rowId, fieldId, value) => {
|
|
4522
4608
|
await updateRowProps(rowId, { [cellKey(fieldId)]: value });
|
|
4523
4609
|
},
|
|
4524
4610
|
[updateRowProps]
|
|
4525
4611
|
);
|
|
4526
|
-
const clearCells =
|
|
4612
|
+
const clearCells = useCallback3(
|
|
4527
4613
|
async (cells) => {
|
|
4528
4614
|
const byRow = /* @__PURE__ */ new Map();
|
|
4529
4615
|
for (const { rowId, fieldId } of cells) {
|
|
@@ -4535,7 +4621,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4535
4621
|
},
|
|
4536
4622
|
[updateRowProps]
|
|
4537
4623
|
);
|
|
4538
|
-
const addRow =
|
|
4624
|
+
const addRow = useCallback3(
|
|
4539
4625
|
async (afterRowId, cells, opts) => {
|
|
4540
4626
|
let sortKey;
|
|
4541
4627
|
if (afterRowId) {
|
|
@@ -4570,13 +4656,13 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4570
4656
|
},
|
|
4571
4657
|
[mutate, databaseId, rows, nextAppendKey]
|
|
4572
4658
|
);
|
|
4573
|
-
const deleteRows =
|
|
4659
|
+
const deleteRows = useCallback3(
|
|
4574
4660
|
async (rowIds) => {
|
|
4575
4661
|
await Promise.all(rowIds.map((id) => mutate.remove(id)));
|
|
4576
4662
|
},
|
|
4577
4663
|
[mutate]
|
|
4578
4664
|
);
|
|
4579
|
-
const moveRowToIndex =
|
|
4665
|
+
const moveRowToIndex = useCallback3(
|
|
4580
4666
|
async (rowId, targetIndex) => {
|
|
4581
4667
|
if (activeView && activeView.sorts.length > 0) return;
|
|
4582
4668
|
const ordered = rows.map((r) => ({ id: r.id, key: r.sortKey }));
|
|
@@ -4585,7 +4671,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4585
4671
|
},
|
|
4586
4672
|
[rows, activeView, updateRowProps]
|
|
4587
4673
|
);
|
|
4588
|
-
const createOption =
|
|
4674
|
+
const createOption = useCallback3(
|
|
4589
4675
|
async (fieldId, name) => {
|
|
4590
4676
|
const existing = optionsByField.get(fieldId) ?? [];
|
|
4591
4677
|
const match = existing.find((o) => o.name.toLowerCase() === name.toLowerCase());
|
|
@@ -4604,7 +4690,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4604
4690
|
},
|
|
4605
4691
|
[mutate, databaseId, optionsByField]
|
|
4606
4692
|
);
|
|
4607
|
-
const addField =
|
|
4693
|
+
const addField = useCallback3(
|
|
4608
4694
|
async (name, type, config, opts) => {
|
|
4609
4695
|
const node = await mutate.create(DatabaseFieldSchema, {
|
|
4610
4696
|
database: databaseId,
|
|
@@ -4619,13 +4705,13 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4619
4705
|
},
|
|
4620
4706
|
[mutate, databaseId, nextAppendKey]
|
|
4621
4707
|
);
|
|
4622
|
-
const renameField =
|
|
4708
|
+
const renameField = useCallback3(
|
|
4623
4709
|
async (fieldId, name) => {
|
|
4624
4710
|
await mutate.update(DatabaseFieldSchema, fieldId, { name });
|
|
4625
4711
|
},
|
|
4626
4712
|
[mutate]
|
|
4627
4713
|
);
|
|
4628
|
-
const updateFieldConfig =
|
|
4714
|
+
const updateFieldConfig = useCallback3(
|
|
4629
4715
|
async (fieldId, config) => {
|
|
4630
4716
|
await mutate.update(DatabaseFieldSchema, fieldId, {
|
|
4631
4717
|
config
|
|
@@ -4633,7 +4719,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4633
4719
|
},
|
|
4634
4720
|
[mutate]
|
|
4635
4721
|
);
|
|
4636
|
-
const changeFieldType =
|
|
4722
|
+
const changeFieldType = useCallback3(
|
|
4637
4723
|
async (fieldId, type) => {
|
|
4638
4724
|
const field = fields.find((f) => f.id === fieldId);
|
|
4639
4725
|
const sourceType = field?.type ?? "text";
|
|
@@ -4670,7 +4756,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4670
4756
|
},
|
|
4671
4757
|
[mutate, fields, rows, createOption, updateRowProps]
|
|
4672
4758
|
);
|
|
4673
|
-
const removeField =
|
|
4759
|
+
const removeField = useCallback3(
|
|
4674
4760
|
async (fieldId) => {
|
|
4675
4761
|
const orphanedOptions = optionsByField.get(fieldId) ?? [];
|
|
4676
4762
|
await Promise.all(orphanedOptions.map((o) => mutate.remove(o.id)));
|
|
@@ -4678,7 +4764,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4678
4764
|
},
|
|
4679
4765
|
[mutate, optionsByField]
|
|
4680
4766
|
);
|
|
4681
|
-
const moveFieldToIndex =
|
|
4767
|
+
const moveFieldToIndex = useCallback3(
|
|
4682
4768
|
async (fieldId, targetIndex) => {
|
|
4683
4769
|
if (!activeView) return;
|
|
4684
4770
|
const ordered = visibleFields.map((f) => ({
|
|
@@ -4692,7 +4778,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4692
4778
|
},
|
|
4693
4779
|
[mutate, activeView, visibleFields, fields]
|
|
4694
4780
|
);
|
|
4695
|
-
const resizeField =
|
|
4781
|
+
const resizeField = useCallback3(
|
|
4696
4782
|
async (fieldId, width) => {
|
|
4697
4783
|
if (!activeView) return;
|
|
4698
4784
|
await mutate.update(DatabaseViewSchema, activeView.id, {
|
|
@@ -4701,7 +4787,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4701
4787
|
},
|
|
4702
4788
|
[mutate, activeView]
|
|
4703
4789
|
);
|
|
4704
|
-
const setFieldHiddenInView =
|
|
4790
|
+
const setFieldHiddenInView = useCallback3(
|
|
4705
4791
|
async (fieldId, hidden) => {
|
|
4706
4792
|
if (!activeView) return;
|
|
4707
4793
|
const set = new Set(activeView.hiddenFields);
|
|
@@ -4711,7 +4797,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4711
4797
|
},
|
|
4712
4798
|
[mutate, activeView]
|
|
4713
4799
|
);
|
|
4714
|
-
const toggleSort =
|
|
4800
|
+
const toggleSort = useCallback3(
|
|
4715
4801
|
async (fieldId) => {
|
|
4716
4802
|
if (!activeView) return;
|
|
4717
4803
|
const current = activeView.sorts.find((s) => s.columnId === fieldId);
|
|
@@ -4727,28 +4813,28 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4727
4813
|
},
|
|
4728
4814
|
[mutate, activeView]
|
|
4729
4815
|
);
|
|
4730
|
-
const setFilters =
|
|
4816
|
+
const setFilters = useCallback3(
|
|
4731
4817
|
async (filters) => {
|
|
4732
4818
|
if (!activeView) return;
|
|
4733
4819
|
await mutate.update(DatabaseViewSchema, activeView.id, { filters });
|
|
4734
4820
|
},
|
|
4735
4821
|
[mutate, activeView]
|
|
4736
4822
|
);
|
|
4737
|
-
const setGroupBy =
|
|
4823
|
+
const setGroupBy = useCallback3(
|
|
4738
4824
|
async (fieldId) => {
|
|
4739
4825
|
if (!activeView) return;
|
|
4740
4826
|
await mutate.update(DatabaseViewSchema, activeView.id, { groupBy: fieldId ?? void 0 });
|
|
4741
4827
|
},
|
|
4742
4828
|
[mutate, activeView]
|
|
4743
4829
|
);
|
|
4744
|
-
const setRowHeight =
|
|
4830
|
+
const setRowHeight = useCallback3(
|
|
4745
4831
|
async (rowHeight) => {
|
|
4746
4832
|
if (!activeView) return;
|
|
4747
4833
|
await mutate.update(DatabaseViewSchema, activeView.id, { rowHeight });
|
|
4748
4834
|
},
|
|
4749
4835
|
[mutate, activeView]
|
|
4750
4836
|
);
|
|
4751
|
-
const setColumnSummary =
|
|
4837
|
+
const setColumnSummary = useCallback3(
|
|
4752
4838
|
async (fieldId, fn) => {
|
|
4753
4839
|
if (!activeView) return;
|
|
4754
4840
|
const next = { ...activeView.columnSummaries };
|
|
@@ -4758,28 +4844,28 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4758
4844
|
},
|
|
4759
4845
|
[mutate, activeView]
|
|
4760
4846
|
);
|
|
4761
|
-
const setFormConfig =
|
|
4847
|
+
const setFormConfig = useCallback3(
|
|
4762
4848
|
async (config) => {
|
|
4763
4849
|
if (!activeView) return;
|
|
4764
4850
|
await mutate.update(DatabaseViewSchema, activeView.id, { formConfig: config });
|
|
4765
4851
|
},
|
|
4766
4852
|
[mutate, activeView]
|
|
4767
4853
|
);
|
|
4768
|
-
const setFormRules =
|
|
4854
|
+
const setFormRules = useCallback3(
|
|
4769
4855
|
async (rules) => {
|
|
4770
4856
|
if (!activeView) return;
|
|
4771
4857
|
await mutate.update(DatabaseViewSchema, activeView.id, { formRules: rules });
|
|
4772
4858
|
},
|
|
4773
4859
|
[mutate, activeView]
|
|
4774
4860
|
);
|
|
4775
|
-
const setFormAccepting =
|
|
4861
|
+
const setFormAccepting = useCallback3(
|
|
4776
4862
|
async (accepting) => {
|
|
4777
4863
|
if (!activeView) return;
|
|
4778
4864
|
await mutate.update(DatabaseViewSchema, activeView.id, { formAccepting: accepting });
|
|
4779
4865
|
},
|
|
4780
4866
|
[mutate, activeView]
|
|
4781
4867
|
);
|
|
4782
|
-
const addView =
|
|
4868
|
+
const addView = useCallback3(
|
|
4783
4869
|
async (name, type) => {
|
|
4784
4870
|
const node = await mutate.create(DatabaseViewSchema, {
|
|
4785
4871
|
database: databaseId,
|
|
@@ -4791,13 +4877,13 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4791
4877
|
},
|
|
4792
4878
|
[mutate, databaseId, nextAppendKey]
|
|
4793
4879
|
);
|
|
4794
|
-
const renameView =
|
|
4880
|
+
const renameView = useCallback3(
|
|
4795
4881
|
async (id, name) => {
|
|
4796
4882
|
await mutate.update(DatabaseViewSchema, id, { name });
|
|
4797
4883
|
},
|
|
4798
4884
|
[mutate]
|
|
4799
4885
|
);
|
|
4800
|
-
const removeView =
|
|
4886
|
+
const removeView = useCallback3(
|
|
4801
4887
|
async (id) => {
|
|
4802
4888
|
await mutate.remove(id);
|
|
4803
4889
|
},
|
|
@@ -4844,7 +4930,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4844
4930
|
}
|
|
4845
4931
|
|
|
4846
4932
|
// src/hooks/useGlobalUndo.ts
|
|
4847
|
-
import { useCallback as
|
|
4933
|
+
import { useCallback as useCallback4, useContext, useEffect as useEffect7, useReducer } from "react";
|
|
4848
4934
|
function readUndoContext(ctx) {
|
|
4849
4935
|
if (!ctx) return { undoManager: null, nodeStore: null };
|
|
4850
4936
|
return { undoManager: ctx.undoManager, nodeStore: ctx.nodeStore };
|
|
@@ -4853,17 +4939,17 @@ function useGlobalUndo() {
|
|
|
4853
4939
|
const ctx = useContext(XNetContext);
|
|
4854
4940
|
const { undoManager, nodeStore } = readUndoContext(ctx);
|
|
4855
4941
|
const [, bump] = useReducer((n) => n + 1, 0);
|
|
4856
|
-
|
|
4942
|
+
useEffect7(() => {
|
|
4857
4943
|
if (!nodeStore) return;
|
|
4858
4944
|
return nodeStore.subscribe(() => bump());
|
|
4859
4945
|
}, [nodeStore]);
|
|
4860
|
-
const undo =
|
|
4946
|
+
const undo = useCallback4(async () => {
|
|
4861
4947
|
if (!undoManager) return false;
|
|
4862
4948
|
const ok = await undoManager.undoLatest();
|
|
4863
4949
|
bump();
|
|
4864
4950
|
return ok;
|
|
4865
4951
|
}, [undoManager]);
|
|
4866
|
-
const redo =
|
|
4952
|
+
const redo = useCallback4(async () => {
|
|
4867
4953
|
if (!undoManager) return false;
|
|
4868
4954
|
const ok = await undoManager.redoLatest();
|
|
4869
4955
|
bump();
|
|
@@ -4877,6 +4963,57 @@ function useGlobalUndo() {
|
|
|
4877
4963
|
};
|
|
4878
4964
|
}
|
|
4879
4965
|
|
|
4966
|
+
// src/hooks/useCanCreate.ts
|
|
4967
|
+
import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef5, useState as useState7 } from "react";
|
|
4968
|
+
var INITIAL_STATE = {
|
|
4969
|
+
canCreate: false,
|
|
4970
|
+
loading: true,
|
|
4971
|
+
error: null
|
|
4972
|
+
};
|
|
4973
|
+
function useCanCreate(schemaId, properties) {
|
|
4974
|
+
const { store, isReady } = useNodeStore();
|
|
4975
|
+
const [state, setState] = useState7(INITIAL_STATE);
|
|
4976
|
+
const requestRef = useRef5(0);
|
|
4977
|
+
const propertiesKey = useMemo6(() => JSON.stringify(properties ?? {}), [properties]);
|
|
4978
|
+
useEffect8(() => {
|
|
4979
|
+
if (!store || !isReady) {
|
|
4980
|
+
setState((prev) => ({ ...prev, loading: true }));
|
|
4981
|
+
return;
|
|
4982
|
+
}
|
|
4983
|
+
if (!store.auth) {
|
|
4984
|
+
setState({
|
|
4985
|
+
...INITIAL_STATE,
|
|
4986
|
+
loading: false,
|
|
4987
|
+
error: new Error("Authorization API is not configured on this NodeStore")
|
|
4988
|
+
});
|
|
4989
|
+
return;
|
|
4990
|
+
}
|
|
4991
|
+
const run = async () => {
|
|
4992
|
+
const requestId = ++requestRef.current;
|
|
4993
|
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
4994
|
+
try {
|
|
4995
|
+
const decision = await store.auth.can({
|
|
4996
|
+
action: "create",
|
|
4997
|
+
// A synthetic id: the draft node must not collide with a stored one.
|
|
4998
|
+
nodeId: `draft:${schemaId}`,
|
|
4999
|
+
node: {
|
|
5000
|
+
schemaId,
|
|
5001
|
+
properties: JSON.parse(propertiesKey)
|
|
5002
|
+
}
|
|
5003
|
+
});
|
|
5004
|
+
if (requestRef.current !== requestId) return;
|
|
5005
|
+
setState({ canCreate: decision.allowed, loading: false, error: null });
|
|
5006
|
+
} catch (error) {
|
|
5007
|
+
if (requestRef.current !== requestId) return;
|
|
5008
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
5009
|
+
setState((prev) => ({ ...prev, loading: false, error: normalized }));
|
|
5010
|
+
}
|
|
5011
|
+
};
|
|
5012
|
+
void run();
|
|
5013
|
+
}, [isReady, propertiesKey, schemaId, store]);
|
|
5014
|
+
return state;
|
|
5015
|
+
}
|
|
5016
|
+
|
|
4880
5017
|
// src/index.ts
|
|
4881
5018
|
import { WebSocketSyncProvider } from "@xnetjs/runtime";
|
|
4882
5019
|
import {
|
|
@@ -4907,7 +5044,7 @@ import {
|
|
|
4907
5044
|
} from "@xnetjs/runtime";
|
|
4908
5045
|
|
|
4909
5046
|
// src/hooks/useBilling.ts
|
|
4910
|
-
import { useCallback as
|
|
5047
|
+
import { useCallback as useCallback5, useContext as useContext2, useEffect as useEffect9, useMemo as useMemo7, useState as useState8 } from "react";
|
|
4911
5048
|
var toHttpUrl = (hubUrl) => {
|
|
4912
5049
|
try {
|
|
4913
5050
|
const url = new URL(hubUrl);
|
|
@@ -4944,20 +5081,20 @@ function deriveBilling(state) {
|
|
|
4944
5081
|
}
|
|
4945
5082
|
function useBilling() {
|
|
4946
5083
|
const context = useContext2(XNetContext);
|
|
4947
|
-
const [state, setState] =
|
|
4948
|
-
const [loading, setLoading] =
|
|
5084
|
+
const [state, setState] = useState8(null);
|
|
5085
|
+
const [loading, setLoading] = useState8(
|
|
4949
5086
|
Boolean(context?.hubUrl || context?.billing?.apiBase)
|
|
4950
5087
|
);
|
|
4951
|
-
const [error, setError] =
|
|
4952
|
-
const apiBase =
|
|
5088
|
+
const [error, setError] = useState8(null);
|
|
5089
|
+
const apiBase = useMemo7(() => {
|
|
4953
5090
|
if (context?.billing?.apiBase) return context.billing.apiBase.replace(/\/$/, "");
|
|
4954
5091
|
return context?.hubUrl ? toHttpUrl(context.hubUrl) : null;
|
|
4955
5092
|
}, [context?.billing?.apiBase, context?.hubUrl]);
|
|
4956
|
-
const authHeaders =
|
|
5093
|
+
const authHeaders = useCallback5(async () => {
|
|
4957
5094
|
const token = context?.getHubAuthToken ? await context.getHubAuthToken() : "";
|
|
4958
5095
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
4959
5096
|
}, [context]);
|
|
4960
|
-
const reload =
|
|
5097
|
+
const reload = useCallback5(async () => {
|
|
4961
5098
|
if (!apiBase) {
|
|
4962
5099
|
setState(null);
|
|
4963
5100
|
setLoading(false);
|
|
@@ -4975,10 +5112,10 @@ function useBilling() {
|
|
|
4975
5112
|
setLoading(false);
|
|
4976
5113
|
}
|
|
4977
5114
|
}, [apiBase, authHeaders]);
|
|
4978
|
-
|
|
5115
|
+
useEffect9(() => {
|
|
4979
5116
|
void reload();
|
|
4980
5117
|
}, [reload]);
|
|
4981
|
-
const redirectVia =
|
|
5118
|
+
const redirectVia = useCallback5(
|
|
4982
5119
|
async (path, body) => {
|
|
4983
5120
|
if (!apiBase) throw new Error("Hub URL not configured");
|
|
4984
5121
|
const res = await fetch(`${apiBase}${path}`, {
|
|
@@ -4992,11 +5129,11 @@ function useBilling() {
|
|
|
4992
5129
|
},
|
|
4993
5130
|
[apiBase, authHeaders]
|
|
4994
5131
|
);
|
|
4995
|
-
const openCheckout =
|
|
5132
|
+
const openCheckout = useCallback5(
|
|
4996
5133
|
(priceRef, options = {}) => redirectVia("/billing/checkout", buildCheckoutBody(priceRef, options)),
|
|
4997
5134
|
[redirectVia]
|
|
4998
5135
|
);
|
|
4999
|
-
const openPortal =
|
|
5136
|
+
const openPortal = useCallback5(
|
|
5000
5137
|
(returnUrl) => redirectVia("/billing/portal", returnUrl ? { returnUrl } : {}),
|
|
5001
5138
|
[redirectVia]
|
|
5002
5139
|
);
|
|
@@ -5120,6 +5257,7 @@ export {
|
|
|
5120
5257
|
useBilling,
|
|
5121
5258
|
useBlame,
|
|
5122
5259
|
useCan,
|
|
5260
|
+
useCanCreate,
|
|
5123
5261
|
useCanEdit,
|
|
5124
5262
|
useCanvasTaskSync,
|
|
5125
5263
|
useCell,
|
|
@@ -5164,6 +5302,7 @@ export {
|
|
|
5164
5302
|
usePluginRegistryOptional,
|
|
5165
5303
|
usePlugins,
|
|
5166
5304
|
usePolicyFilteredReactionCounters,
|
|
5305
|
+
usePresence,
|
|
5167
5306
|
useQuery,
|
|
5168
5307
|
useRelatedRows,
|
|
5169
5308
|
useRemoteSchema,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xnetjs/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,15 +46,15 @@
|
|
|
46
46
|
"y-protocols": "^1.0.6",
|
|
47
47
|
"yjs": "^13.6.24",
|
|
48
48
|
"@xnetjs/billing": "0.0.2",
|
|
49
|
-
"@xnetjs/core": "0.
|
|
50
|
-
"@xnetjs/crypto": "0.
|
|
51
|
-
"@xnetjs/data": "0.
|
|
52
|
-
"@xnetjs/data-bridge": "0.
|
|
53
|
-
"@xnetjs/history": "0.
|
|
54
|
-
"@xnetjs/identity": "0.
|
|
55
|
-
"@xnetjs/plugins": "0.
|
|
56
|
-
"@xnetjs/runtime": "0.
|
|
57
|
-
"@xnetjs/sync": "0.
|
|
49
|
+
"@xnetjs/core": "1.0.0",
|
|
50
|
+
"@xnetjs/crypto": "1.0.0",
|
|
51
|
+
"@xnetjs/data": "1.0.0",
|
|
52
|
+
"@xnetjs/data-bridge": "1.0.0",
|
|
53
|
+
"@xnetjs/history": "1.0.0",
|
|
54
|
+
"@xnetjs/identity": "1.0.0",
|
|
55
|
+
"@xnetjs/plugins": "1.0.0",
|
|
56
|
+
"@xnetjs/runtime": "0.4.0",
|
|
57
|
+
"@xnetjs/sync": "1.0.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"react": "^18.0.0 || ^19.0.0"
|