@fluidframework/react 2.90.0-378676 → 2.91.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/CHANGELOG.md +13 -0
- package/README.md +2 -0
- package/api-report/react.alpha.api.md +8 -8
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/propNode.js.map +1 -1
- package/lib/reactSharedTreeView.d.ts +6 -6
- package/lib/reactSharedTreeView.d.ts.map +1 -1
- package/lib/reactSharedTreeView.js +16 -18
- package/lib/reactSharedTreeView.js.map +1 -1
- package/lib/test/mochaHooks.js +13 -0
- package/lib/test/mochaHooks.js.map +1 -0
- package/lib/test/reactSharedTreeView.spec.js +3 -3
- package/lib/test/reactSharedTreeView.spec.js.map +1 -1
- package/lib/test/text/plainUtils.test.js +75 -0
- package/lib/test/text/plainUtils.test.js.map +1 -0
- package/lib/test/text/textEditor.test.js +760 -0
- package/lib/test/text/textEditor.test.js.map +1 -0
- package/lib/test/undoRedo.test.js +62 -0
- package/lib/test/undoRedo.test.js.map +1 -0
- package/lib/test/useObservation.spec.js +8 -9
- package/lib/test/useObservation.spec.js.map +1 -1
- package/lib/test/useTree.spec.js +15 -16
- package/lib/test/useTree.spec.js.map +1 -1
- package/lib/text/formatted/index.d.ts +6 -0
- package/lib/text/formatted/index.d.ts.map +1 -0
- package/lib/text/formatted/index.js +6 -0
- package/lib/text/formatted/index.js.map +1 -0
- package/lib/text/formatted/quillFormattedView.d.ts +66 -0
- package/lib/text/formatted/quillFormattedView.d.ts.map +1 -0
- package/lib/text/formatted/quillFormattedView.js +520 -0
- package/lib/text/formatted/quillFormattedView.js.map +1 -0
- package/lib/text/index.d.ts +7 -0
- package/lib/text/index.d.ts.map +1 -0
- package/lib/text/index.js +7 -0
- package/lib/text/index.js.map +1 -0
- package/lib/text/plain/index.d.ts +7 -0
- package/lib/text/plain/index.d.ts.map +1 -0
- package/lib/text/plain/index.js +7 -0
- package/lib/text/plain/index.js.map +1 -0
- package/lib/text/plain/plainTextView.d.ts +14 -0
- package/lib/text/plain/plainTextView.d.ts.map +1 -0
- package/lib/text/plain/plainTextView.js +70 -0
- package/lib/text/plain/plainTextView.js.map +1 -0
- package/lib/text/plain/plainUtils.d.ts +23 -0
- package/lib/text/plain/plainUtils.d.ts.map +1 -0
- package/lib/text/plain/plainUtils.js +51 -0
- package/lib/text/plain/plainUtils.js.map +1 -0
- package/lib/text/plain/quillView.d.ts +22 -0
- package/lib/text/plain/quillView.d.ts.map +1 -0
- package/lib/text/plain/quillView.js +106 -0
- package/lib/text/plain/quillView.js.map +1 -0
- package/lib/undoRedo.d.ts +51 -0
- package/lib/undoRedo.d.ts.map +1 -0
- package/lib/undoRedo.js +76 -0
- package/lib/undoRedo.js.map +1 -0
- package/lib/useObservation.js +6 -6
- package/lib/useObservation.js.map +1 -1
- package/lib/useTree.d.ts +7 -7
- package/lib/useTree.d.ts.map +1 -1
- package/lib/useTree.js +6 -6
- package/lib/useTree.js.map +1 -1
- package/package.json +28 -46
- package/react.test-files.tar +0 -0
- package/src/index.ts +10 -0
- package/src/propNode.ts +1 -1
- package/src/reactSharedTreeView.tsx +11 -13
- package/src/text/formatted/index.ts +11 -0
- package/src/text/formatted/quillFormattedView.tsx +627 -0
- package/src/text/index.ts +15 -0
- package/src/text/plain/index.ts +7 -0
- package/src/text/plain/plainTextView.tsx +110 -0
- package/src/text/plain/plainUtils.ts +68 -0
- package/src/text/plain/quillView.tsx +149 -0
- package/src/undoRedo.ts +117 -0
- package/src/useObservation.ts +6 -6
- package/src/useTree.ts +19 -12
- package/tsconfig.json +6 -0
- package/api-extractor/api-extractor-lint-alpha.cjs.json +0 -5
- package/api-extractor/api-extractor-lint-beta.cjs.json +0 -5
- package/api-extractor/api-extractor-lint-public.cjs.json +0 -5
- package/dist/alpha.d.ts +0 -45
- package/dist/beta.d.ts +0 -15
- package/dist/index.d.ts +0 -16
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -26
- package/dist/index.js.map +0 -1
- package/dist/package.json +0 -4
- package/dist/propNode.d.ts +0 -114
- package/dist/propNode.d.ts.map +0 -1
- package/dist/propNode.js +0 -43
- package/dist/propNode.js.map +0 -1
- package/dist/public.d.ts +0 -15
- package/dist/reactSharedTreeView.d.ts +0 -119
- package/dist/reactSharedTreeView.d.ts.map +0 -1
- package/dist/reactSharedTreeView.js +0 -206
- package/dist/reactSharedTreeView.js.map +0 -1
- package/dist/simpleIdentifier.d.ts +0 -19
- package/dist/simpleIdentifier.d.ts.map +0 -1
- package/dist/simpleIdentifier.js +0 -33
- package/dist/simpleIdentifier.js.map +0 -1
- package/dist/useObservation.d.ts +0 -83
- package/dist/useObservation.d.ts.map +0 -1
- package/dist/useObservation.js +0 -295
- package/dist/useObservation.js.map +0 -1
- package/dist/useTree.d.ts +0 -80
- package/dist/useTree.d.ts.map +0 -1
- package/dist/useTree.js +0 -137
- package/dist/useTree.js.map +0 -1
- package/tsconfig.cjs.json +0 -7
package/lib/undoRedo.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { CommitKind, } from "@fluidframework/tree";
|
|
6
|
+
/**
|
|
7
|
+
* Manages undo and redo stacks for a TreeView.
|
|
8
|
+
* Listens to commitApplied events and manages Revertible objects.
|
|
9
|
+
* @sealed @internal
|
|
10
|
+
*/
|
|
11
|
+
export class UndoRedoStacks {
|
|
12
|
+
constructor(events) {
|
|
13
|
+
this.undoStack = [];
|
|
14
|
+
this.redoStack = [];
|
|
15
|
+
this.listeners = new Set();
|
|
16
|
+
this.unsubscribe = events.on("commitApplied", (commit, getRevertible) => {
|
|
17
|
+
if (getRevertible === undefined)
|
|
18
|
+
return;
|
|
19
|
+
const revertible = getRevertible();
|
|
20
|
+
if (commit.kind === CommitKind.Undo) {
|
|
21
|
+
this.redoStack.push(revertible);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
if (commit.kind === CommitKind.Default) {
|
|
25
|
+
for (const r of this.redoStack)
|
|
26
|
+
r.dispose();
|
|
27
|
+
this.redoStack.length = 0;
|
|
28
|
+
}
|
|
29
|
+
this.undoStack.push(revertible);
|
|
30
|
+
}
|
|
31
|
+
this.notifyListeners();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
undo() {
|
|
35
|
+
const revertible = this.undoStack.pop();
|
|
36
|
+
if (revertible === undefined) {
|
|
37
|
+
throw new Error("Cannot undo: undo stack is empty.");
|
|
38
|
+
}
|
|
39
|
+
revertible.revert();
|
|
40
|
+
this.notifyListeners();
|
|
41
|
+
}
|
|
42
|
+
redo() {
|
|
43
|
+
const revertible = this.redoStack.pop();
|
|
44
|
+
if (revertible === undefined) {
|
|
45
|
+
throw new Error("Cannot redo: redo stack is empty.");
|
|
46
|
+
}
|
|
47
|
+
revertible.revert();
|
|
48
|
+
this.notifyListeners();
|
|
49
|
+
}
|
|
50
|
+
dispose() {
|
|
51
|
+
this.unsubscribe();
|
|
52
|
+
this.listeners.clear();
|
|
53
|
+
for (const r of this.undoStack)
|
|
54
|
+
r.dispose();
|
|
55
|
+
for (const r of this.redoStack)
|
|
56
|
+
r.dispose();
|
|
57
|
+
this.undoStack.length = 0;
|
|
58
|
+
this.redoStack.length = 0;
|
|
59
|
+
}
|
|
60
|
+
canUndo() {
|
|
61
|
+
return this.undoStack.length > 0;
|
|
62
|
+
}
|
|
63
|
+
canRedo() {
|
|
64
|
+
return this.redoStack.length > 0;
|
|
65
|
+
}
|
|
66
|
+
onStateChange(callback) {
|
|
67
|
+
this.listeners.add(callback);
|
|
68
|
+
return () => this.listeners.delete(callback);
|
|
69
|
+
}
|
|
70
|
+
notifyListeners() {
|
|
71
|
+
for (const listener of this.listeners) {
|
|
72
|
+
listener();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=undoRedo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"undoRedo.js","sourceRoot":"","sources":["../src/undoRedo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACN,UAAU,GAKV,MAAM,sBAAsB,CAAC;AA4B9B;;;;GAIG;AACH,MAAM,OAAO,cAAc;IAM1B,YAAmB,MAAkC;QALpC,cAAS,GAAiB,EAAE,CAAC;QAC7B,cAAS,GAAiB,EAAE,CAAC;QAC7B,cAAS,GAAG,IAAI,GAAG,EAAc,CAAC;QAIlD,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,EAAE,CAC3B,eAAe,EACf,CAAC,MAAsB,EAAE,aAAiC,EAAE,EAAE;YAC7D,IAAI,aAAa,KAAK,SAAS;gBAAE,OAAO;YACxC,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;YACnC,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,EAAE,CAAC;gBACrC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACP,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC;oBACxC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS;wBAAE,CAAC,CAAC,OAAO,EAAE,CAAC;oBAC5C,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;gBAC3B,CAAC;gBACD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACjC,CAAC;YACD,IAAI,CAAC,eAAe,EAAE,CAAC;QACxB,CAAC,CACD,CAAC;IACH,CAAC;IAEM,IAAI;QACV,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;QACxC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACtD,CAAC;QACD,UAAU,CAAC,MAAM,EAAE,CAAC;QACpB,IAAI,CAAC,eAAe,EAAE,CAAC;IACxB,CAAC;IAEM,IAAI;QACV,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;QACxC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACtD,CAAC;QACD,UAAU,CAAC,MAAM,EAAE,CAAC;QACpB,IAAI,CAAC,eAAe,EAAE,CAAC;IACxB,CAAC;IAEM,OAAO;QACb,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS;YAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC5C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS;YAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC5C,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3B,CAAC;IAEM,OAAO;QACb,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAClC,CAAC;IAEM,OAAO;QACb,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAClC,CAAC;IAEM,aAAa,CAAC,QAAoB;QACxC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAEO,eAAe;QACtB,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACvC,QAAQ,EAAE,CAAC;QACZ,CAAC;IACF,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { Listenable } from \"@fluidframework/core-interfaces\";\nimport {\n\tCommitKind,\n\ttype CommitMetadata,\n\ttype Revertible,\n\ttype RevertibleFactory,\n\ttype TreeViewEvents,\n} from \"@fluidframework/tree\";\n\n/**\n * Interface for undo/redo stack operations.\n * @internal\n */\nexport interface UndoRedo {\n\t/**\n\t * Reverts the most recent change. Only valid to call when {@link UndoRedo.canUndo} returns true.\n\t * @throws Error if there is nothing to undo.\n\t */\n\tundo(): void;\n\t/**\n\t * Reapplies the most recently undone change. Only valid to call when {@link UndoRedo.canRedo} returns true.\n\t * @throws Error if there is nothing to redo.\n\t */\n\tredo(): void;\n\tdispose(): void;\n\tcanUndo(): boolean;\n\tcanRedo(): boolean;\n\t/**\n\t * Subscribe to state changes (when canUndo/canRedo may have changed).\n\t * @param callback - Called when the undo/redo stack state changes\n\t * @returns Unsubscribe function\n\t */\n\tonStateChange(callback: () => void): () => void;\n}\n\n/**\n * Manages undo and redo stacks for a TreeView.\n * Listens to commitApplied events and manages Revertible objects.\n * @sealed @internal\n */\nexport class UndoRedoStacks implements UndoRedo {\n\tprivate readonly undoStack: Revertible[] = [];\n\tprivate readonly redoStack: Revertible[] = [];\n\tprivate readonly listeners = new Set<() => void>();\n\tprivate readonly unsubscribe: () => void;\n\n\tpublic constructor(events: Listenable<TreeViewEvents>) {\n\t\tthis.unsubscribe = events.on(\n\t\t\t\"commitApplied\",\n\t\t\t(commit: CommitMetadata, getRevertible?: RevertibleFactory) => {\n\t\t\t\tif (getRevertible === undefined) return;\n\t\t\t\tconst revertible = getRevertible();\n\t\t\t\tif (commit.kind === CommitKind.Undo) {\n\t\t\t\t\tthis.redoStack.push(revertible);\n\t\t\t\t} else {\n\t\t\t\t\tif (commit.kind === CommitKind.Default) {\n\t\t\t\t\t\tfor (const r of this.redoStack) r.dispose();\n\t\t\t\t\t\tthis.redoStack.length = 0;\n\t\t\t\t\t}\n\t\t\t\t\tthis.undoStack.push(revertible);\n\t\t\t\t}\n\t\t\t\tthis.notifyListeners();\n\t\t\t},\n\t\t);\n\t}\n\n\tpublic undo(): void {\n\t\tconst revertible = this.undoStack.pop();\n\t\tif (revertible === undefined) {\n\t\t\tthrow new Error(\"Cannot undo: undo stack is empty.\");\n\t\t}\n\t\trevertible.revert();\n\t\tthis.notifyListeners();\n\t}\n\n\tpublic redo(): void {\n\t\tconst revertible = this.redoStack.pop();\n\t\tif (revertible === undefined) {\n\t\t\tthrow new Error(\"Cannot redo: redo stack is empty.\");\n\t\t}\n\t\trevertible.revert();\n\t\tthis.notifyListeners();\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.unsubscribe();\n\t\tthis.listeners.clear();\n\t\tfor (const r of this.undoStack) r.dispose();\n\t\tfor (const r of this.redoStack) r.dispose();\n\t\tthis.undoStack.length = 0;\n\t\tthis.redoStack.length = 0;\n\t}\n\n\tpublic canUndo(): boolean {\n\t\treturn this.undoStack.length > 0;\n\t}\n\n\tpublic canRedo(): boolean {\n\t\treturn this.redoStack.length > 0;\n\t}\n\n\tpublic onStateChange(callback: () => void): () => void {\n\t\tthis.listeners.add(callback);\n\t\treturn () => this.listeners.delete(callback);\n\t}\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\tlistener();\n\t\t}\n\t}\n}\n"]}
|
package/lib/useObservation.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
6
|
/**
|
|
7
7
|
* Wrapper around subscriptions to give it an object identity which can be used with FinalizationRegistry.
|
|
8
8
|
* @remarks
|
|
@@ -38,7 +38,7 @@ class SubscriptionsWrapper {
|
|
|
38
38
|
*/
|
|
39
39
|
export function useObservation(trackDuring, options) {
|
|
40
40
|
// Use a React state hook to invalidate this component something tracked by `trackDuring` changes.
|
|
41
|
-
const [subscriptions, setSubscriptions] =
|
|
41
|
+
const [subscriptions, setSubscriptions] = useState(new SubscriptionsWrapper());
|
|
42
42
|
// Because `subscriptions` is used in `finalizationRegistry` for cleanup, it is important that nothing save a reference to it which is retained by the invalidation callback.
|
|
43
43
|
// TO help with this, pull out `inner` so it can be closed over without retaining `subscriptions`.
|
|
44
44
|
const inner = subscriptions.inner;
|
|
@@ -75,7 +75,7 @@ export function useObservation(trackDuring, options) {
|
|
|
75
75
|
// Suppressing that invalidation bug with an extra call to setSubscriptions could work, but would produce incorrect warnings about leaks,
|
|
76
76
|
// and might cause infinite rerender depending on how StrictMode works.
|
|
77
77
|
// Such an Effect would look like this:
|
|
78
|
-
//
|
|
78
|
+
// useEffect(
|
|
79
79
|
// () => () => {
|
|
80
80
|
// subscriptions.unsubscribe?.();
|
|
81
81
|
// subscriptions.unsubscribe = undefined;
|
|
@@ -116,9 +116,9 @@ const finalizationRegistry = new FinalizationRegistry((subscriptions) => {
|
|
|
116
116
|
*/
|
|
117
117
|
function useObservationPure(trackDuring, options) {
|
|
118
118
|
// Dummy state used to trigger invalidations.
|
|
119
|
-
const [_subscriptions, setSubscriptions] =
|
|
119
|
+
const [_subscriptions, setSubscriptions] = useState(0);
|
|
120
120
|
const { result, subscribe } = trackDuring();
|
|
121
|
-
|
|
121
|
+
useEffect(() => {
|
|
122
122
|
// Subscribe to events from the latest render
|
|
123
123
|
const invalidate = () => {
|
|
124
124
|
setSubscriptions((n) => n + 1);
|
|
@@ -257,7 +257,7 @@ function observationAdapter(trackDuring, options) {
|
|
|
257
257
|
export function useObservationStrict(trackDuring, options) {
|
|
258
258
|
// Used to unsubscribe from the previous render's subscriptions.
|
|
259
259
|
// See `useObservation` for a more documented explanation of this pattern.
|
|
260
|
-
const [subscriptions] =
|
|
260
|
+
const [subscriptions] = useState({ previousTracker: undefined });
|
|
261
261
|
const pureResult = useObservationPure(observationAdapter(trackDuring, options), options);
|
|
262
262
|
subscriptions.previousTracker?.dispose();
|
|
263
263
|
subscriptions.previousTracker = pureResult.tracker;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useObservation.js","sourceRoot":"","sources":["../src/useObservation.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport * as React from \"react\";\n\n/**\n * Tracks and subscriptions from the latests render of a given instance of the {@link useObservation} hook.\n */\ninterface Subscriptions {\n\t/**\n\t * If defined, still needs to be called at some point.\n\t * @remarks\n\t * Clear when called.\n\t */\n\tunsubscribe?: () => void;\n}\n\n/**\n * Wrapper around subscriptions to give it an object identity which can be used with FinalizationRegistry.\n * @remarks\n * This indirection is needed so inner can be provided to finalizationRegistry as the heldValue and avoid having that cause a leak.\n * @privateRemarks\n * This is a named class to make looking for leaks of it in heap snapshots easier.\n */\nclass SubscriptionsWrapper {\n\tpublic readonly inner: Subscriptions = {};\n}\n\n/**\n * Options for {@link useTreeObservations}.\n * @input\n * @alpha\n */\nexport interface ObservationOptions {\n\t/**\n\t * Called when the tracked observations are invalidated.\n\t * @remarks\n\t * This is not expected to have production use cases, but is useful for testing and debugging.\n\t */\n\tonInvalidation?: () => void;\n}\n\n/**\n * Custom hook which invalidates a React Component based on changes to what was observed during `trackDuring`.\n *\n * @param trackDuring - Called synchronously: can make event subscriptions which call the provided `invalidate` function.\n * Any such subscriptions should be cleaned up via the returned `unsubscribe` function which will only be invoked if `invalidate` is not called.\n * If `invalidate` is called, the code calling it should remove any subscriptions before calling it.\n * @remarks\n * React strongly discourages \"render\" from having side-effects other than idempotent lazy initialization.\n *\n * Tracking observations made during render to subscribe to events for automatic invalidation is a side-effect.\n * This makes the behavior of this hook somewhat unusual from a React perspective, and also rather poorly supported by React.\n *\n * That said, the alternatives more aligned with how React expects things to work have much less friendly APIs, or have gaps where they risk invalidation bugs.\n *\n * For example, this hook could record which observations were made during render, then pass them into a `useEffect` hook to do the subscription.\n * This would be more aligned with React's expectations, but would have a number of issues:\n * - The effect would run after render, so if the observed content changed between render and the effect running, there could be an invalidation bug.\n * - It would require changes to `TreeAlpha.trackObservationsOnce` to support a two phase approach (first track, then subscribe) which would have the same risk of missed invalidation.\n * - It would have slightly higher cost due to the extra effect.\n * Such an approach is implemented in {@link useObservationPure}.\n */\nexport function useObservation<TResult>(\n\ttrackDuring: (invalidate: () => void) => { result: TResult; unsubscribe: () => void },\n\toptions?: ObservationOptions,\n): TResult {\n\t// Use a React state hook to invalidate this component something tracked by `trackDuring` changes.\n\tconst [subscriptions, setSubscriptions] = React.useState<SubscriptionsWrapper>(\n\t\tnew SubscriptionsWrapper(),\n\t);\n\n\t// Because `subscriptions` is used in `finalizationRegistry` for cleanup, it is important that nothing save a reference to it which is retained by the invalidation callback.\n\t// TO help with this, pull out `inner` so it can be closed over without retaining `subscriptions`.\n\tconst inner = subscriptions.inner;\n\n\tconst invalidate = (): void => {\n\t\t// Since below uses trackObservationsOnce, the un-subscription is done before calling this callback,\n\t\t// and therefore this must ensure that no further un-subscriptions occur, as well as that the render is invalidated.\n\t\t//\n\t\t// Note referencing `setSubscriptions` risks transitively holding onto a reference to `subscriptions` depending on how React implements `useState`.\n\t\t// If such a transitive reference does exist, it would cause a leak (by preventing finalizationRegistry from running and thus preventing un-subscription after unmount).\n\t\t// Experimentally this has been observed not to be the case, and is validated by the \"unsubscribe on unmount\" tests.\n\t\tsetSubscriptions(new SubscriptionsWrapper());\n\n\t\t// This cannot do `registry.unregister(subscriptions);` as that would cause a leak by holding onto `subscriptions`\n\t\t// since this closure is held onto by the subscribed events.\n\t\t// Skipping such an un-registration is fine so long as we ensure the registry does not redundantly unsubscribe.\n\t\t// Since trackObservationsOnce already unsubscribed, just clear out the unsubscribe function to ensure it is not called again by the finalizer.\n\t\tinner.unsubscribe = undefined;\n\n\t\toptions?.onInvalidation?.();\n\t};\n\n\t// If there was a previous rendering of this instance of this hook in the current component, unsubscribe from it.\n\t// This avoids a memory leak (of the event subscriptions) in the case where a components is rerendered.\n\tinner.unsubscribe?.();\n\tinner.unsubscribe = undefined;\n\n\t// This is logically pure other than the side effect of registering for invalidation if the observed content changes.\n\t// This is safe from a React perspective since when the observed content changes, that is reflected in the `useState` above.\n\t// What is more problematic is avoiding of leaking the event registrations since React does not provide an easy way to do that for code run outside of a hook.\n\t// That leak is avoided via two separate approaches: the un-subscription for events from previous renders above,\n\t// and the use of finalizationRegistry below to handle the component unmount case.\n\tconst out = trackDuring(invalidate);\n\n\tinner.unsubscribe = out.unsubscribe;\n\n\t// There is still the issue of unsubscribing when the component unmounts.\n\t// This can almost be done using a React effect hook with an empty dependency list.\n\t// Unfortunately that would have a hard time getting the correct subscriptions to unsubscribe,\n\t// and if run before unmount, like in StrictMode, it would cause an invalidation bug.\n\t// Suppressing that invalidation bug with an extra call to setSubscriptions could work, but would produce incorrect warnings about leaks,\n\t// and might cause infinite rerender depending on how StrictMode works.\n\t// Such an Effect would look like this:\n\t// React.useEffect(\n\t// \t() => () => {\n\t// \t\tsubscriptions.unsubscribe?.();\n\t// \t\tsubscriptions.unsubscribe = undefined;\n\t// \t\tsetSubscriptions({});\n\t// \t},\n\t// \t[],\n\t// );\n\t// Instead of that, use a FinalizationRegistry to clean up when the subscriptions.\n\t// As this only needs to run sometime after the component is unmounted, triggering it based on React no longer holding onto the subscriptions state object is sufficient.\n\t// This should be safe (not unsubscribe too early) as React will hold onto the state object for as long as the component is mounted since if the component rerenders, it will be required.\n\t// If React decided it would never reuse the component instance (recreate it instead of rerender) but kept it mounted, then it would be possible for this to unsubscribe too early.\n\t// Currently however, it does not seem like React does or will do that.\n\t// If such an issue does ever occur, it could be fixed by stuffing a reference to the `subscriptions` object in the DOM: for now such a mitigation appears unnecessary and would add overhead.\n\tfinalizationRegistry.register(subscriptions, inner);\n\n\treturn out.result;\n}\n\n/**\n * Handles unsubscribing from events when the {@link SubscriptionsWrapper} is garbage collected.\n * See comments in {@link useTreeObservations} for details.\n */\nconst finalizationRegistry = new FinalizationRegistry((subscriptions: Subscriptions) => {\n\tsubscriptions.unsubscribe?.();\n\t// Clear out the unsubscribe function to ensure it is not called again.\n\t// This should not be needed, but maintains the invariant that unsubscribe should be removed after being called.\n\tsubscriptions.unsubscribe = undefined;\n});\n\n//\n// Below here are some alternative approaches.\n// Should issues arise with the above, one of these could be used instead.\n// These alternatives have user facing downsides (mainly performance and/or gaps where they could miss invalidations)\n// so are not being used as long as the above setup seems to be working well enough.\n//\n\n/**\n * Options for {@link useTreeObservations}.\n * @input\n */\nexport interface ObservationPureOptions {\n\tonSubscribe?: () => void;\n\tonUnsubscribe?: () => void;\n\tonPureInvalidation?: () => void;\n}\n\n/**\n * Variant of {@link useObservation} where render behaves in a more pure functional way.\n * @remarks\n * Subscriptions are only created in effects, which leaves a gap between when the observations are tracked and the subscriptions are created.\n * @privateRemarks\n * If impureness of the other approaches becomes a problem, this could be used directly instead.\n * Doing so would require changing `TreeAlpha.trackObservationsOnce` return a function to subscribe to the tracked observations instead of subscribing directly.\n * This would be less robust (edits could be missed between render and the effect running) but would avoid the impure aspects of the other approaches.\n * This would remove the need for a finalizationRegistry, and would avoid relying on React not doing something unexpected like rendering a component twice and throwing away the second render instead of the first.\n *\n * If using this directly, ensure it has tests other than via the other hooks which use it.\n */\nfunction useObservationPure<TResult>(\n\ttrackDuring: () => { result: TResult; subscribe: (invalidate: () => void) => () => void },\n\toptions?: ObservationPureOptions,\n): TResult {\n\t// Dummy state used to trigger invalidations.\n\tconst [_subscriptions, setSubscriptions] = React.useState(0);\n\n\tconst { result, subscribe } = trackDuring();\n\n\tReact.useEffect(() => {\n\t\t// Subscribe to events from the latest render\n\n\t\tconst invalidate = (): void => {\n\t\t\tsetSubscriptions((n) => n + 1);\n\t\t\tinner.unsubscribe = undefined;\n\t\t\toptions?.onPureInvalidation?.();\n\t\t};\n\n\t\toptions?.onSubscribe?.();\n\t\tconst inner: Subscriptions = { unsubscribe: subscribe(invalidate) };\n\n\t\treturn () => {\n\t\t\tinner.unsubscribe?.();\n\t\t\tinner.unsubscribe = undefined;\n\t\t\toptions?.onUnsubscribe?.();\n\t\t};\n\t});\n\treturn result;\n}\n\n/**\n * Manages subscription to a one-shot invalidation event (unsubscribes when sent) event where multiple parties may want to subscribe to the event.\n * @remarks\n * When the event occurs, all subscribers are called.\n * Any subscribers added after the event has occurred are immediately called.\n *\n * Since new subscriptions can be added any any time, this can not unsubscribe from the source after the last destination has unsubscribed.\n *\n * Instead the finalizationRegistry is used.\n * @privateRemarks\n * This is a named class to make looking for leaks of it in heap snapshots easier.\n */\nclass SubscriptionTracker {\n\t/**\n\t * Subscriptions to underlying events.\n\t */\n\tprivate readonly inner: Subscriptions;\n\t/**\n\t * Hook subscriptions to be trigger by `inner`.\n\t */\n\tprivate readonly toInvalidate = new Set<() => void>();\n\n\tprivate disposed: boolean = false;\n\n\tprivate constructor(unsubscribe: () => void) {\n\t\tthis.inner = { unsubscribe };\n\t}\n\n\tprivate assertNotDisposed(): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"Already disposed\");\n\t\t}\n\t}\n\n\tpublic readonly invalidate = (): void => {\n\t\tthis.assertNotDisposed();\n\t\tif (this.inner.unsubscribe === undefined) {\n\t\t\tthrow new Error(\"Already invalidated\");\n\t\t}\n\n\t\tthis.inner.unsubscribe = undefined;\n\n\t\tfor (const invalidate of this.toInvalidate) {\n\t\t\tinvalidate();\n\t\t}\n\t\tthis.toInvalidate.clear();\n\t};\n\n\tpublic static create(unsubscribe: () => void): SubscriptionTracker {\n\t\tconst tracker = new SubscriptionTracker(unsubscribe);\n\t\tfinalizationRegistry.register(tracker, tracker.inner);\n\t\treturn tracker;\n\t}\n\n\tpublic subscribe(callback: () => void): () => void {\n\t\tthis.assertNotDisposed();\n\t\tif (this.toInvalidate.has(callback)) {\n\t\t\tthrow new Error(\"Already subscribed\");\n\t\t}\n\n\t\tif (this.inner.unsubscribe === undefined) {\n\t\t\t// Already invalidated, so immediately call back.\n\t\t\tcallback();\n\t\t\treturn () => {};\n\t\t}\n\n\t\tthis.toInvalidate.add(callback);\n\n\t\treturn () => {\n\t\t\tthis.assertNotDisposed();\n\t\t\tif (!this.toInvalidate.has(callback)) {\n\t\t\t\tthrow new Error(\"Not subscribed\");\n\t\t\t}\n\t\t\tthis.toInvalidate.delete(callback);\n\t\t};\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.assertNotDisposed();\n\t\tthis.disposed = true;\n\t\tthis.inner.unsubscribe?.();\n\t\tthis.inner.unsubscribe = undefined;\n\n\t\tif (this.toInvalidate.size > 0) {\n\t\t\tthrow new Error(\"Invalid disposal before unsubscribing all listeners\");\n\t\t}\n\n\t\tfinalizationRegistry.unregister(this.inner);\n\t}\n}\n\n/**\n * {@link useObservation} but more aligned with React expectations.\n * @remarks\n * This is more expensive than {@link useObservation}, and also leaks subscriptions longer.\n * When rendering a component, relies on a finalizer to clean up subscriptions from the previous render.\n *\n * Unlike {@link useObservation}, this behave correctly even if React does something unexpected, like Rendering a component twice, and throwing away the second render instead of the first.\n * {@link useObservation} relies on React not doing such things, assuming that when re-rendering a component, it will be the older render which is discarded.\n *\n * This should also avoid calling `setState` after unmount, which can avoid a React warning.\n *\n * This does not however avoid the finalizer based cleanup: it actually relies on it much more (for rerender and unmount, not just unmount).\n * This simply adds a layer of indirection to the invalidation through useEffect.\n */\nexport function useObservationWithEffects<TResult>(\n\ttrackDuring: (invalidate: () => void) => { result: TResult; unsubscribe: () => void },\n\toptions?: ObservationOptions & ObservationPureOptions,\n): TResult {\n\tconst pureResult = useObservationPure(observationAdapter(trackDuring, options), options);\n\treturn pureResult.innerResult;\n}\n\n/**\n * An adapter wrapping `trackDuring` to help implement the {@link useObservation} using {@link useObservationPure}.\n */\nfunction observationAdapter<TResult>(\n\ttrackDuring: (invalidate: () => void) => { result: TResult; unsubscribe: () => void },\n\toptions?: ObservationOptions & ObservationPureOptions,\n): () => {\n\tresult: {\n\t\ttracker: SubscriptionTracker;\n\t\tinnerResult: TResult;\n\t};\n\tsubscribe: (invalidate: () => void) => () => void;\n} {\n\treturn () => {\n\t\t// The main invalidation function, which only runs once, and is used to create the SubscriptionTracker.\n\t\tconst invalidateMain = (): void => {\n\t\t\ttracker.invalidate();\n\t\t\toptions?.onInvalidation?.();\n\t\t};\n\t\tconst result2 = trackDuring(invalidateMain);\n\t\tconst tracker = SubscriptionTracker.create(result2.unsubscribe);\n\n\t\treturn {\n\t\t\tresult: { tracker, innerResult: result2.result },\n\t\t\tsubscribe: (invalidate) => {\n\t\t\t\treturn tracker.subscribe(invalidate);\n\t\t\t},\n\t\t};\n\t};\n}\n\n/**\n * {@link useObservation} but more strict with its behavior.\n * @remarks\n * This has the eager cleanup on re-render of {@link useObservation}, but has the effect based subscriptions and cleanup on unmount of {@link useObservationWithEffects}.\n *\n * If React behaves in a way which breaks the assumptions of {@link useObservation} (and thus would require the leakier {@link useObservationWithEffects}), this will throw an error.\n * @privateRemarks\n * This is just a {@link useObservationPure}, except with the eager cleanup on re-render from {@link useObservation}.\n */\nexport function useObservationStrict<TResult>(\n\ttrackDuring: (invalidate: () => void) => { result: TResult; unsubscribe: () => void },\n\toptions?: ObservationOptions & ObservationPureOptions,\n): TResult {\n\t// Used to unsubscribe from the previous render's subscriptions.\n\t// See `useObservation` for a more documented explanation of this pattern.\n\tconst [subscriptions] = React.useState<{\n\t\tpreviousTracker: SubscriptionTracker | undefined;\n\t}>({ previousTracker: undefined });\n\n\tconst pureResult = useObservationPure(observationAdapter(trackDuring, options), options);\n\n\tsubscriptions.previousTracker?.dispose();\n\tsubscriptions.previousTracker = pureResult.tracker;\n\n\treturn pureResult.innerResult;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useObservation.js","sourceRoot":"","sources":["../src/useObservation.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { useEffect, useState } from \"react\";\n\n/**\n * Tracks and subscriptions from the latests render of a given instance of the {@link useObservation} hook.\n */\ninterface Subscriptions {\n\t/**\n\t * If defined, still needs to be called at some point.\n\t * @remarks\n\t * Clear when called.\n\t */\n\tunsubscribe?: () => void;\n}\n\n/**\n * Wrapper around subscriptions to give it an object identity which can be used with FinalizationRegistry.\n * @remarks\n * This indirection is needed so inner can be provided to finalizationRegistry as the heldValue and avoid having that cause a leak.\n * @privateRemarks\n * This is a named class to make looking for leaks of it in heap snapshots easier.\n */\nclass SubscriptionsWrapper {\n\tpublic readonly inner: Subscriptions = {};\n}\n\n/**\n * Options for {@link useTreeObservations}.\n * @input\n * @alpha\n */\nexport interface ObservationOptions {\n\t/**\n\t * Called when the tracked observations are invalidated.\n\t * @remarks\n\t * This is not expected to have production use cases, but is useful for testing and debugging.\n\t */\n\tonInvalidation?: () => void;\n}\n\n/**\n * Custom hook which invalidates a React Component based on changes to what was observed during `trackDuring`.\n *\n * @param trackDuring - Called synchronously: can make event subscriptions which call the provided `invalidate` function.\n * Any such subscriptions should be cleaned up via the returned `unsubscribe` function which will only be invoked if `invalidate` is not called.\n * If `invalidate` is called, the code calling it should remove any subscriptions before calling it.\n * @remarks\n * React strongly discourages \"render\" from having side-effects other than idempotent lazy initialization.\n *\n * Tracking observations made during render to subscribe to events for automatic invalidation is a side-effect.\n * This makes the behavior of this hook somewhat unusual from a React perspective, and also rather poorly supported by React.\n *\n * That said, the alternatives more aligned with how React expects things to work have much less friendly APIs, or have gaps where they risk invalidation bugs.\n *\n * For example, this hook could record which observations were made during render, then pass them into a `useEffect` hook to do the subscription.\n * This would be more aligned with React's expectations, but would have a number of issues:\n * - The effect would run after render, so if the observed content changed between render and the effect running, there could be an invalidation bug.\n * - It would require changes to `TreeAlpha.trackObservationsOnce` to support a two phase approach (first track, then subscribe) which would have the same risk of missed invalidation.\n * - It would have slightly higher cost due to the extra effect.\n * Such an approach is implemented in {@link useObservationPure}.\n */\nexport function useObservation<TResult>(\n\ttrackDuring: (invalidate: () => void) => { result: TResult; unsubscribe: () => void },\n\toptions?: ObservationOptions,\n): TResult {\n\t// Use a React state hook to invalidate this component something tracked by `trackDuring` changes.\n\tconst [subscriptions, setSubscriptions] = useState<SubscriptionsWrapper>(\n\t\tnew SubscriptionsWrapper(),\n\t);\n\n\t// Because `subscriptions` is used in `finalizationRegistry` for cleanup, it is important that nothing save a reference to it which is retained by the invalidation callback.\n\t// TO help with this, pull out `inner` so it can be closed over without retaining `subscriptions`.\n\tconst inner = subscriptions.inner;\n\n\tconst invalidate = (): void => {\n\t\t// Since below uses trackObservationsOnce, the un-subscription is done before calling this callback,\n\t\t// and therefore this must ensure that no further un-subscriptions occur, as well as that the render is invalidated.\n\t\t//\n\t\t// Note referencing `setSubscriptions` risks transitively holding onto a reference to `subscriptions` depending on how React implements `useState`.\n\t\t// If such a transitive reference does exist, it would cause a leak (by preventing finalizationRegistry from running and thus preventing un-subscription after unmount).\n\t\t// Experimentally this has been observed not to be the case, and is validated by the \"unsubscribe on unmount\" tests.\n\t\tsetSubscriptions(new SubscriptionsWrapper());\n\n\t\t// This cannot do `registry.unregister(subscriptions);` as that would cause a leak by holding onto `subscriptions`\n\t\t// since this closure is held onto by the subscribed events.\n\t\t// Skipping such an un-registration is fine so long as we ensure the registry does not redundantly unsubscribe.\n\t\t// Since trackObservationsOnce already unsubscribed, just clear out the unsubscribe function to ensure it is not called again by the finalizer.\n\t\tinner.unsubscribe = undefined;\n\n\t\toptions?.onInvalidation?.();\n\t};\n\n\t// If there was a previous rendering of this instance of this hook in the current component, unsubscribe from it.\n\t// This avoids a memory leak (of the event subscriptions) in the case where a components is rerendered.\n\tinner.unsubscribe?.();\n\tinner.unsubscribe = undefined;\n\n\t// This is logically pure other than the side effect of registering for invalidation if the observed content changes.\n\t// This is safe from a React perspective since when the observed content changes, that is reflected in the `useState` above.\n\t// What is more problematic is avoiding of leaking the event registrations since React does not provide an easy way to do that for code run outside of a hook.\n\t// That leak is avoided via two separate approaches: the un-subscription for events from previous renders above,\n\t// and the use of finalizationRegistry below to handle the component unmount case.\n\tconst out = trackDuring(invalidate);\n\n\tinner.unsubscribe = out.unsubscribe;\n\n\t// There is still the issue of unsubscribing when the component unmounts.\n\t// This can almost be done using a React effect hook with an empty dependency list.\n\t// Unfortunately that would have a hard time getting the correct subscriptions to unsubscribe,\n\t// and if run before unmount, like in StrictMode, it would cause an invalidation bug.\n\t// Suppressing that invalidation bug with an extra call to setSubscriptions could work, but would produce incorrect warnings about leaks,\n\t// and might cause infinite rerender depending on how StrictMode works.\n\t// Such an Effect would look like this:\n\t// useEffect(\n\t// \t() => () => {\n\t// \t\tsubscriptions.unsubscribe?.();\n\t// \t\tsubscriptions.unsubscribe = undefined;\n\t// \t\tsetSubscriptions({});\n\t// \t},\n\t// \t[],\n\t// );\n\t// Instead of that, use a FinalizationRegistry to clean up when the subscriptions.\n\t// As this only needs to run sometime after the component is unmounted, triggering it based on React no longer holding onto the subscriptions state object is sufficient.\n\t// This should be safe (not unsubscribe too early) as React will hold onto the state object for as long as the component is mounted since if the component rerenders, it will be required.\n\t// If React decided it would never reuse the component instance (recreate it instead of rerender) but kept it mounted, then it would be possible for this to unsubscribe too early.\n\t// Currently however, it does not seem like React does or will do that.\n\t// If such an issue does ever occur, it could be fixed by stuffing a reference to the `subscriptions` object in the DOM: for now such a mitigation appears unnecessary and would add overhead.\n\tfinalizationRegistry.register(subscriptions, inner);\n\n\treturn out.result;\n}\n\n/**\n * Handles unsubscribing from events when the {@link SubscriptionsWrapper} is garbage collected.\n * See comments in {@link useTreeObservations} for details.\n */\nconst finalizationRegistry = new FinalizationRegistry((subscriptions: Subscriptions) => {\n\tsubscriptions.unsubscribe?.();\n\t// Clear out the unsubscribe function to ensure it is not called again.\n\t// This should not be needed, but maintains the invariant that unsubscribe should be removed after being called.\n\tsubscriptions.unsubscribe = undefined;\n});\n\n//\n// Below here are some alternative approaches.\n// Should issues arise with the above, one of these could be used instead.\n// These alternatives have user facing downsides (mainly performance and/or gaps where they could miss invalidations)\n// so are not being used as long as the above setup seems to be working well enough.\n//\n\n/**\n * Options for {@link useTreeObservations}.\n * @input\n */\nexport interface ObservationPureOptions {\n\tonSubscribe?: () => void;\n\tonUnsubscribe?: () => void;\n\tonPureInvalidation?: () => void;\n}\n\n/**\n * Variant of {@link useObservation} where render behaves in a more pure functional way.\n * @remarks\n * Subscriptions are only created in effects, which leaves a gap between when the observations are tracked and the subscriptions are created.\n * @privateRemarks\n * If impureness of the other approaches becomes a problem, this could be used directly instead.\n * Doing so would require changing `TreeAlpha.trackObservationsOnce` return a function to subscribe to the tracked observations instead of subscribing directly.\n * This would be less robust (edits could be missed between render and the effect running) but would avoid the impure aspects of the other approaches.\n * This would remove the need for a finalizationRegistry, and would avoid relying on React not doing something unexpected like rendering a component twice and throwing away the second render instead of the first.\n *\n * If using this directly, ensure it has tests other than via the other hooks which use it.\n */\nfunction useObservationPure<TResult>(\n\ttrackDuring: () => { result: TResult; subscribe: (invalidate: () => void) => () => void },\n\toptions?: ObservationPureOptions,\n): TResult {\n\t// Dummy state used to trigger invalidations.\n\tconst [_subscriptions, setSubscriptions] = useState(0);\n\n\tconst { result, subscribe } = trackDuring();\n\n\tuseEffect(() => {\n\t\t// Subscribe to events from the latest render\n\n\t\tconst invalidate = (): void => {\n\t\t\tsetSubscriptions((n) => n + 1);\n\t\t\tinner.unsubscribe = undefined;\n\t\t\toptions?.onPureInvalidation?.();\n\t\t};\n\n\t\toptions?.onSubscribe?.();\n\t\tconst inner: Subscriptions = { unsubscribe: subscribe(invalidate) };\n\n\t\treturn () => {\n\t\t\tinner.unsubscribe?.();\n\t\t\tinner.unsubscribe = undefined;\n\t\t\toptions?.onUnsubscribe?.();\n\t\t};\n\t});\n\treturn result;\n}\n\n/**\n * Manages subscription to a one-shot invalidation event (unsubscribes when sent) event where multiple parties may want to subscribe to the event.\n * @remarks\n * When the event occurs, all subscribers are called.\n * Any subscribers added after the event has occurred are immediately called.\n *\n * Since new subscriptions can be added any any time, this can not unsubscribe from the source after the last destination has unsubscribed.\n *\n * Instead the finalizationRegistry is used.\n * @privateRemarks\n * This is a named class to make looking for leaks of it in heap snapshots easier.\n */\nclass SubscriptionTracker {\n\t/**\n\t * Subscriptions to underlying events.\n\t */\n\tprivate readonly inner: Subscriptions;\n\t/**\n\t * Hook subscriptions to be trigger by `inner`.\n\t */\n\tprivate readonly toInvalidate = new Set<() => void>();\n\n\tprivate disposed: boolean = false;\n\n\tprivate constructor(unsubscribe: () => void) {\n\t\tthis.inner = { unsubscribe };\n\t}\n\n\tprivate assertNotDisposed(): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"Already disposed\");\n\t\t}\n\t}\n\n\tpublic readonly invalidate = (): void => {\n\t\tthis.assertNotDisposed();\n\t\tif (this.inner.unsubscribe === undefined) {\n\t\t\tthrow new Error(\"Already invalidated\");\n\t\t}\n\n\t\tthis.inner.unsubscribe = undefined;\n\n\t\tfor (const invalidate of this.toInvalidate) {\n\t\t\tinvalidate();\n\t\t}\n\t\tthis.toInvalidate.clear();\n\t};\n\n\tpublic static create(unsubscribe: () => void): SubscriptionTracker {\n\t\tconst tracker = new SubscriptionTracker(unsubscribe);\n\t\tfinalizationRegistry.register(tracker, tracker.inner);\n\t\treturn tracker;\n\t}\n\n\tpublic subscribe(callback: () => void): () => void {\n\t\tthis.assertNotDisposed();\n\t\tif (this.toInvalidate.has(callback)) {\n\t\t\tthrow new Error(\"Already subscribed\");\n\t\t}\n\n\t\tif (this.inner.unsubscribe === undefined) {\n\t\t\t// Already invalidated, so immediately call back.\n\t\t\tcallback();\n\t\t\treturn () => {};\n\t\t}\n\n\t\tthis.toInvalidate.add(callback);\n\n\t\treturn () => {\n\t\t\tthis.assertNotDisposed();\n\t\t\tif (!this.toInvalidate.has(callback)) {\n\t\t\t\tthrow new Error(\"Not subscribed\");\n\t\t\t}\n\t\t\tthis.toInvalidate.delete(callback);\n\t\t};\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.assertNotDisposed();\n\t\tthis.disposed = true;\n\t\tthis.inner.unsubscribe?.();\n\t\tthis.inner.unsubscribe = undefined;\n\n\t\tif (this.toInvalidate.size > 0) {\n\t\t\tthrow new Error(\"Invalid disposal before unsubscribing all listeners\");\n\t\t}\n\n\t\tfinalizationRegistry.unregister(this.inner);\n\t}\n}\n\n/**\n * {@link useObservation} but more aligned with React expectations.\n * @remarks\n * This is more expensive than {@link useObservation}, and also leaks subscriptions longer.\n * When rendering a component, relies on a finalizer to clean up subscriptions from the previous render.\n *\n * Unlike {@link useObservation}, this behave correctly even if React does something unexpected, like Rendering a component twice, and throwing away the second render instead of the first.\n * {@link useObservation} relies on React not doing such things, assuming that when re-rendering a component, it will be the older render which is discarded.\n *\n * This should also avoid calling `setState` after unmount, which can avoid a React warning.\n *\n * This does not however avoid the finalizer based cleanup: it actually relies on it much more (for rerender and unmount, not just unmount).\n * This simply adds a layer of indirection to the invalidation through useEffect.\n */\nexport function useObservationWithEffects<TResult>(\n\ttrackDuring: (invalidate: () => void) => { result: TResult; unsubscribe: () => void },\n\toptions?: ObservationOptions & ObservationPureOptions,\n): TResult {\n\tconst pureResult = useObservationPure(observationAdapter(trackDuring, options), options);\n\treturn pureResult.innerResult;\n}\n\n/**\n * An adapter wrapping `trackDuring` to help implement the {@link useObservation} using {@link useObservationPure}.\n */\nfunction observationAdapter<TResult>(\n\ttrackDuring: (invalidate: () => void) => { result: TResult; unsubscribe: () => void },\n\toptions?: ObservationOptions & ObservationPureOptions,\n): () => {\n\tresult: {\n\t\ttracker: SubscriptionTracker;\n\t\tinnerResult: TResult;\n\t};\n\tsubscribe: (invalidate: () => void) => () => void;\n} {\n\treturn () => {\n\t\t// The main invalidation function, which only runs once, and is used to create the SubscriptionTracker.\n\t\tconst invalidateMain = (): void => {\n\t\t\ttracker.invalidate();\n\t\t\toptions?.onInvalidation?.();\n\t\t};\n\t\tconst result2 = trackDuring(invalidateMain);\n\t\tconst tracker = SubscriptionTracker.create(result2.unsubscribe);\n\n\t\treturn {\n\t\t\tresult: { tracker, innerResult: result2.result },\n\t\t\tsubscribe: (invalidate) => {\n\t\t\t\treturn tracker.subscribe(invalidate);\n\t\t\t},\n\t\t};\n\t};\n}\n\n/**\n * {@link useObservation} but more strict with its behavior.\n * @remarks\n * This has the eager cleanup on re-render of {@link useObservation}, but has the effect based subscriptions and cleanup on unmount of {@link useObservationWithEffects}.\n *\n * If React behaves in a way which breaks the assumptions of {@link useObservation} (and thus would require the leakier {@link useObservationWithEffects}), this will throw an error.\n * @privateRemarks\n * This is just a {@link useObservationPure}, except with the eager cleanup on re-render from {@link useObservation}.\n */\nexport function useObservationStrict<TResult>(\n\ttrackDuring: (invalidate: () => void) => { result: TResult; unsubscribe: () => void },\n\toptions?: ObservationOptions & ObservationPureOptions,\n): TResult {\n\t// Used to unsubscribe from the previous render's subscriptions.\n\t// See `useObservation` for a more documented explanation of this pattern.\n\tconst [subscriptions] = useState<{\n\t\tpreviousTracker: SubscriptionTracker | undefined;\n\t}>({ previousTracker: undefined });\n\n\tconst pureResult = useObservationPure(observationAdapter(trackDuring, options), options);\n\n\tsubscriptions.previousTracker?.dispose();\n\tsubscriptions.previousTracker = pureResult.tracker;\n\n\treturn pureResult.innerResult;\n}\n"]}
|
package/lib/useTree.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
import type { TreeLeafValue, TreeNode } from "@fluidframework/tree";
|
|
6
|
-
import
|
|
6
|
+
import { type FC, memo, type MemoExoticComponent } from "react";
|
|
7
7
|
import { type PropTreeNodeRecord, type PropTreeValue, type UnwrapPropTreeNodeRecord, type WrapNodes } from "./propNode.js";
|
|
8
8
|
import { type ObservationOptions } from "./useObservation.js";
|
|
9
9
|
/**
|
|
@@ -25,20 +25,20 @@ export declare function useTree(subtreeRoot: TreeNode): number;
|
|
|
25
25
|
* It is recommended that sub-components which take in TreeNodes, if not defined using this higher order components, take the nodes in as {@link PropTreeNode}s.
|
|
26
26
|
* Components defined using this higher order component can take in either raw TreeNodes or {@link PropTreeNode}s: the latter will be automatically unwrapped.
|
|
27
27
|
* @privateRemarks
|
|
28
|
-
* `
|
|
28
|
+
* `FC` does not seem to be covariant over its input type, so to make use of this more ergonomic,
|
|
29
29
|
* the return type intersects the various ways this could be used (with or without PropTreeNode wrapping).
|
|
30
30
|
* @alpha
|
|
31
31
|
*/
|
|
32
|
-
export declare function withTreeObservations<TIn>(component:
|
|
32
|
+
export declare function withTreeObservations<TIn>(component: FC<TIn>, options?: ObservationOptions): FC<TIn> & FC<WrapNodes<TIn>> & FC<TIn | WrapNodes<TIn>>;
|
|
33
33
|
/**
|
|
34
|
-
* {@link withTreeObservations} wrapped with
|
|
34
|
+
* {@link withTreeObservations} wrapped with memo.
|
|
35
35
|
* @remarks
|
|
36
36
|
* There is no special logic here, just a convenience wrapper.
|
|
37
37
|
* @alpha
|
|
38
38
|
*/
|
|
39
|
-
export declare function withMemoizedTreeObservations<TIn>(component:
|
|
40
|
-
readonly propsAreEqual?: Parameters<typeof
|
|
41
|
-
}):
|
|
39
|
+
export declare function withMemoizedTreeObservations<TIn>(component: FC<TIn>, options?: ObservationOptions & {
|
|
40
|
+
readonly propsAreEqual?: Parameters<typeof memo>[1];
|
|
41
|
+
}): MemoExoticComponent<ReturnType<typeof withTreeObservations<TIn>>>;
|
|
42
42
|
/**
|
|
43
43
|
* Custom hook which invalidates a React Component when there is a change in tree content observed during `trackDuring`.
|
|
44
44
|
*
|
package/lib/useTree.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTree.d.ts","sourceRoot":"","sources":["../src/useTree.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGpE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useTree.d.ts","sourceRoot":"","sources":["../src/useTree.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGpE,OAAO,EACN,KAAK,EAAE,EACP,IAAI,EACJ,KAAK,mBAAmB,EAIxB,MAAM,OAAO,CAAC;AAEf,OAAO,EAGN,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAC7B,KAAK,SAAS,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9E;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,WAAW,EAAE,QAAQ,GAAG,MAAM,CAcrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EACvC,SAAS,EAAE,EAAE,CAAC,GAAG,CAAC,EAClB,OAAO,CAAC,EAAE,kBAAkB,GAC1B,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAGzD;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAC/C,SAAS,EAAE,EAAE,CAAC,GAAG,CAAC,EAClB,OAAO,CAAC,EAAE,kBAAkB,GAAG;IAC9B,QAAQ,CAAC,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;CACpD,GACC,mBAAmB,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,CAEnE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAC1C,WAAW,EAAE,MAAM,OAAO,EAC1B,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAKT;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,QAAQ,GAAG,aAAa,EAAE,OAAO,EAC1E,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,EAC9B,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,GAC/B,SAAS,CAAC,OAAO,CAAC,CAMpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,CAAC,SAAS,kBAAkB,EAAE,OAAO,EAC5E,KAAK,EAAE,CAAC,EACR,CAAC,EAAE,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,OAAO,GAC/C,SAAS,CAAC,OAAO,CAAC,CAMpB"}
|
package/lib/useTree.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Tree } from "@fluidframework/tree";
|
|
6
6
|
import { TreeAlpha } from "@fluidframework/tree/internal";
|
|
7
|
-
import
|
|
7
|
+
import { memo, useEffect, useState, } from "react";
|
|
8
8
|
import { unwrapPropTreeNode, unwrapPropTreeRecord, } from "./propNode.js";
|
|
9
9
|
import { useObservation } from "./useObservation.js";
|
|
10
10
|
/**
|
|
@@ -18,9 +18,9 @@ import { useObservation } from "./useObservation.js";
|
|
|
18
18
|
export function useTree(subtreeRoot) {
|
|
19
19
|
// Use a React effect hook to invalidate this component when the subtreeRoot changes.
|
|
20
20
|
// We do this by incrementing a counter, which is passed as a dependency to the effect hook.
|
|
21
|
-
const [invalidations, setInvalidations] =
|
|
21
|
+
const [invalidations, setInvalidations] = useState(0);
|
|
22
22
|
// React effect hook that increments the 'invalidation' counter whenever subtreeRoot or any of its children change.
|
|
23
|
-
|
|
23
|
+
useEffect(() => {
|
|
24
24
|
// Returns the cleanup function to be invoked when the component unmounts.
|
|
25
25
|
return Tree.on(subtreeRoot, "treeChanged", () => {
|
|
26
26
|
setInvalidations((i) => i + 1);
|
|
@@ -38,7 +38,7 @@ export function useTree(subtreeRoot) {
|
|
|
38
38
|
* It is recommended that sub-components which take in TreeNodes, if not defined using this higher order components, take the nodes in as {@link PropTreeNode}s.
|
|
39
39
|
* Components defined using this higher order component can take in either raw TreeNodes or {@link PropTreeNode}s: the latter will be automatically unwrapped.
|
|
40
40
|
* @privateRemarks
|
|
41
|
-
* `
|
|
41
|
+
* `FC` does not seem to be covariant over its input type, so to make use of this more ergonomic,
|
|
42
42
|
* the return type intersects the various ways this could be used (with or without PropTreeNode wrapping).
|
|
43
43
|
* @alpha
|
|
44
44
|
*/
|
|
@@ -46,13 +46,13 @@ export function withTreeObservations(component, options) {
|
|
|
46
46
|
return (props) => useTreeObservations(() => component(props), options);
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* {@link withTreeObservations} wrapped with
|
|
49
|
+
* {@link withTreeObservations} wrapped with memo.
|
|
50
50
|
* @remarks
|
|
51
51
|
* There is no special logic here, just a convenience wrapper.
|
|
52
52
|
* @alpha
|
|
53
53
|
*/
|
|
54
54
|
export function withMemoizedTreeObservations(component, options) {
|
|
55
|
-
return
|
|
55
|
+
return memo(withTreeObservations(component, options), options?.propsAreEqual);
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* Custom hook which invalidates a React Component when there is a change in tree content observed during `trackDuring`.
|
package/lib/useTree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTree.js","sourceRoot":"","sources":["../src/useTree.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,
|
|
1
|
+
{"version":3,"file":"useTree.js","sourceRoot":"","sources":["../src/useTree.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAEN,IAAI,EAGJ,SAAS,EACT,QAAQ,GACR,MAAM,OAAO,CAAC;AAEf,OAAO,EACN,kBAAkB,EAClB,oBAAoB,GAKpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAA2B,MAAM,qBAAqB,CAAC;AAE9E;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,WAAqB;IAC5C,qFAAqF;IACrF,4FAA4F;IAC5F,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEtD,mHAAmH;IACnH,SAAS,CAAC,GAAG,EAAE;QACd,0EAA0E;QAC1E,OAAO,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,GAAG,EAAE;YAC/C,gBAAgB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC;IAEjC,OAAO,aAAa,CAAC;AACtB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CACnC,SAAkB,EAClB,OAA4B;IAE5B,OAAO,CAAC,KAA2B,EAAa,EAAE,CACjD,mBAAmB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,KAAY,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B,CAC3C,SAAkB,EAClB,OAEC;IAED,OAAO,IAAI,CAAC,oBAAoB,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAClC,WAA0B,EAC1B,OAA4B;IAE5B,OAAO,cAAc,CACpB,CAAC,UAAU,EAAE,EAAE,CAAC,SAAS,CAAC,qBAAqB,CAAC,UAAU,EAAE,WAAW,CAAC,EACxE,OAAO,CACP,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAC9B,QAA8B,EAC9B,WAAiC;IAEjC,MAAM,IAAI,GAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAE7C,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IAE5D,OAAO,MAA4B,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAChC,KAAQ,EACR,CAAiD;IAEjD,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAEpD,OAAO,MAA4B,CAAC;AACrC,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { TreeLeafValue, TreeNode } from \"@fluidframework/tree\";\nimport { Tree } from \"@fluidframework/tree\";\nimport { TreeAlpha } from \"@fluidframework/tree/internal\";\nimport {\n\ttype FC,\n\tmemo,\n\ttype MemoExoticComponent,\n\ttype ReactNode,\n\tuseEffect,\n\tuseState,\n} from \"react\";\n\nimport {\n\tunwrapPropTreeNode,\n\tunwrapPropTreeRecord,\n\ttype PropTreeNodeRecord,\n\ttype PropTreeValue,\n\ttype UnwrapPropTreeNodeRecord,\n\ttype WrapNodes,\n} from \"./propNode.js\";\nimport { useObservation, type ObservationOptions } from \"./useObservation.js\";\n\n/**\n * Custom hook which invalidates a React Component when there is a change in the subtree defined by `subtreeRoot`.\n * This includes changes to the tree's content, but not changes to its parentage.\n * See {@link @fluidframework/tree#TreeChangeEvents.treeChanged} for details.\n * @remarks\n * Consider using {@link useTreeObservations} instead which tracks what was observed and only invalidates if it changes.\n * @alpha\n */\nexport function useTree(subtreeRoot: TreeNode): number {\n\t// Use a React effect hook to invalidate this component when the subtreeRoot changes.\n\t// We do this by incrementing a counter, which is passed as a dependency to the effect hook.\n\tconst [invalidations, setInvalidations] = useState(0);\n\n\t// React effect hook that increments the 'invalidation' counter whenever subtreeRoot or any of its children change.\n\tuseEffect(() => {\n\t\t// Returns the cleanup function to be invoked when the component unmounts.\n\t\treturn Tree.on(subtreeRoot, \"treeChanged\", () => {\n\t\t\tsetInvalidations((i) => i + 1);\n\t\t});\n\t}, [invalidations, subtreeRoot]);\n\n\treturn invalidations;\n}\n\n/**\n * Higher order component which wraps a component to use {@link useTreeObservations}.\n *\n * @remarks\n * When passing TreeNodes in props, care must be taken to not observe their content outside of a context which does observation tracking (or manual invalidation).\n * This wraps a component in such tracking.\n *\n * It is recommended that sub-components which take in TreeNodes, if not defined using this higher order components, take the nodes in as {@link PropTreeNode}s.\n * Components defined using this higher order component can take in either raw TreeNodes or {@link PropTreeNode}s: the latter will be automatically unwrapped.\n * @privateRemarks\n * `FC` does not seem to be covariant over its input type, so to make use of this more ergonomic,\n * the return type intersects the various ways this could be used (with or without PropTreeNode wrapping).\n * @alpha\n */\nexport function withTreeObservations<TIn>(\n\tcomponent: FC<TIn>,\n\toptions?: ObservationOptions,\n): FC<TIn> & FC<WrapNodes<TIn>> & FC<TIn | WrapNodes<TIn>> {\n\treturn (props: TIn | WrapNodes<TIn>): ReactNode =>\n\t\tuseTreeObservations(() => component(props as TIn), options);\n}\n\n/**\n * {@link withTreeObservations} wrapped with memo.\n * @remarks\n * There is no special logic here, just a convenience wrapper.\n * @alpha\n */\nexport function withMemoizedTreeObservations<TIn>(\n\tcomponent: FC<TIn>,\n\toptions?: ObservationOptions & {\n\t\treadonly propsAreEqual?: Parameters<typeof memo>[1];\n\t},\n): MemoExoticComponent<ReturnType<typeof withTreeObservations<TIn>>> {\n\treturn memo(withTreeObservations(component, options), options?.propsAreEqual);\n}\n\n/**\n * Custom hook which invalidates a React Component when there is a change in tree content observed during `trackDuring`.\n *\n * @param trackDuring - Called synchronously, and will have its tree observations tracked.\n *\n * @remarks\n * This includes changes to the tree's content.\n * Currently this will throw if observing a node's parentage to be undefined,\n * and node status changes will not cause invalidation.\n *\n * For additional type safety to help avoid observing TreeNode content outside of this hook, see {@link PropTreeNode}.\n * @alpha\n */\nexport function useTreeObservations<TResult>(\n\ttrackDuring: () => TResult,\n\toptions?: ObservationOptions,\n): TResult {\n\treturn useObservation(\n\t\t(invalidate) => TreeAlpha.trackObservationsOnce(invalidate, trackDuring),\n\t\toptions,\n\t);\n}\n\n/**\n * Custom hook for using a prop tree node.\n *\n * @param propNode - Input, automatically unwrapped TreeNode from a {@link PropTreeNode} if needed.\n * @param trackDuring - Callback which reads from the node and returns a result.\n * If the result is a TreeNode or {@link NodeRecord} it will be wrapped as a {@link PropTreeNode} or {@link PropTreeNodeRecord}, see {@link WrapNodes}.\n *\n * It is recommended that when returning data containing TreeNodes,\n * use a format supported by {@link WrapNodes} or wrap the nodes manually using {@link toPropTreeNode}.\n * This improves the type safety, reducing the risk of invalidation bugs due to untracked access of tree content contained in the return value.\n *\n * Note that is is fine to observe any node inside the callback, not just the provided node: all accesses will be tracked.\n * The input node is just provided as a way to automatically unwrap the {@link PropTreeNode}\n *\n * @remarks\n * Reads content using {@link useTreeObservations} to track dependencies.\n * @alpha\n */\nexport function usePropTreeNode<T extends TreeNode | TreeLeafValue, TResult>(\n\tpropNode: PropTreeValue<T> | T,\n\ttrackDuring: (node: T) => TResult,\n): WrapNodes<TResult> {\n\tconst node: T = unwrapPropTreeNode(propNode);\n\n\tconst result = useTreeObservations(() => trackDuring(node));\n\n\treturn result as WrapNodes<TResult>;\n}\n\n/**\n * {@link usePropTreeNode} but takes in a {@link PropTreeNodeRecord}.\n * @alpha\n */\nexport function usePropTreeRecord<const T extends PropTreeNodeRecord, TResult>(\n\tprops: T,\n\tf: (node: UnwrapPropTreeNodeRecord<T>) => TResult,\n): WrapNodes<TResult> {\n\tconst record = unwrapPropTreeRecord(props);\n\n\tconst result = useTreeObservations(() => f(record));\n\n\treturn result as WrapNodes<TResult>;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.91.0",
|
|
4
4
|
"description": "Utilities for integrating content powered by the Fluid Framework into React applications",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -17,45 +17,29 @@
|
|
|
17
17
|
"import": {
|
|
18
18
|
"types": "./lib/public.d.ts",
|
|
19
19
|
"default": "./lib/index.js"
|
|
20
|
-
},
|
|
21
|
-
"require": {
|
|
22
|
-
"types": "./dist/public.d.ts",
|
|
23
|
-
"default": "./dist/index.js"
|
|
24
20
|
}
|
|
25
21
|
},
|
|
26
22
|
"./alpha": {
|
|
27
23
|
"import": {
|
|
28
24
|
"types": "./lib/alpha.d.ts",
|
|
29
25
|
"default": "./lib/index.js"
|
|
30
|
-
},
|
|
31
|
-
"require": {
|
|
32
|
-
"types": "./dist/alpha.d.ts",
|
|
33
|
-
"default": "./dist/index.js"
|
|
34
26
|
}
|
|
35
27
|
},
|
|
36
28
|
"./beta": {
|
|
37
29
|
"import": {
|
|
38
30
|
"types": "./lib/beta.d.ts",
|
|
39
31
|
"default": "./lib/index.js"
|
|
40
|
-
},
|
|
41
|
-
"require": {
|
|
42
|
-
"types": "./dist/beta.d.ts",
|
|
43
|
-
"default": "./dist/index.js"
|
|
44
32
|
}
|
|
45
33
|
},
|
|
46
34
|
"./internal": {
|
|
47
35
|
"import": {
|
|
48
36
|
"types": "./lib/index.d.ts",
|
|
49
37
|
"default": "./lib/index.js"
|
|
50
|
-
},
|
|
51
|
-
"require": {
|
|
52
|
-
"types": "./dist/index.d.ts",
|
|
53
|
-
"default": "./dist/index.js"
|
|
54
38
|
}
|
|
55
39
|
}
|
|
56
40
|
},
|
|
57
|
-
"main": "
|
|
58
|
-
"types": "./
|
|
41
|
+
"main": "lib/index.js",
|
|
42
|
+
"types": "./lib/public.d.ts",
|
|
59
43
|
"c8": {
|
|
60
44
|
"all": true,
|
|
61
45
|
"cache-dir": "nyc/.cache",
|
|
@@ -79,29 +63,34 @@
|
|
|
79
63
|
"temp-directory": "nyc/.nyc_output"
|
|
80
64
|
},
|
|
81
65
|
"dependencies": {
|
|
82
|
-
"@fluidframework/aqueduct": "2.
|
|
83
|
-
"@fluidframework/core-interfaces": "2.
|
|
84
|
-
"@fluidframework/
|
|
85
|
-
"@fluidframework/
|
|
86
|
-
"@fluidframework/
|
|
87
|
-
"@fluidframework/
|
|
88
|
-
"@fluidframework/
|
|
89
|
-
"
|
|
66
|
+
"@fluidframework/aqueduct": "~2.91.0",
|
|
67
|
+
"@fluidframework/core-interfaces": "~2.91.0",
|
|
68
|
+
"@fluidframework/core-utils": "~2.91.0",
|
|
69
|
+
"@fluidframework/datastore-definitions": "~2.91.0",
|
|
70
|
+
"@fluidframework/fluid-static": "~2.91.0",
|
|
71
|
+
"@fluidframework/runtime-definitions": "~2.91.0",
|
|
72
|
+
"@fluidframework/shared-object-base": "~2.91.0",
|
|
73
|
+
"@fluidframework/tree": "~2.91.0",
|
|
74
|
+
"quill": "^2.0.3",
|
|
75
|
+
"quill-delta": "^5.1.0",
|
|
76
|
+
"react": "^18.3.1",
|
|
77
|
+
"react-dom": "^18.3.1"
|
|
90
78
|
},
|
|
91
79
|
"devDependencies": {
|
|
92
80
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
93
|
-
"@biomejs/biome": "~
|
|
94
|
-
"@fluid-internal/mocha-test-setup": "2.
|
|
81
|
+
"@biomejs/biome": "~2.4.5",
|
|
82
|
+
"@fluid-internal/mocha-test-setup": "~2.91.0",
|
|
95
83
|
"@fluid-tools/build-cli": "^0.63.0",
|
|
96
84
|
"@fluidframework/build-common": "^2.0.3",
|
|
97
85
|
"@fluidframework/build-tools": "^0.63.0",
|
|
98
|
-
"@fluidframework/eslint-config-fluid": "2.
|
|
99
|
-
"@fluidframework/tinylicious-client": "2.
|
|
86
|
+
"@fluidframework/eslint-config-fluid": "~2.91.0",
|
|
87
|
+
"@fluidframework/tinylicious-client": "~2.91.0",
|
|
100
88
|
"@microsoft/api-extractor": "7.52.11",
|
|
101
89
|
"@testing-library/react": "^16.3.0",
|
|
102
90
|
"@types/mocha": "^10.0.10",
|
|
103
91
|
"@types/node": "~20.19.30",
|
|
104
92
|
"@types/react": "^18.3.11",
|
|
93
|
+
"@types/react-dom": "^18.3.1",
|
|
105
94
|
"c8": "^10.1.3",
|
|
106
95
|
"concurrently": "^9.2.1",
|
|
107
96
|
"copyfiles": "^2.4.1",
|
|
@@ -110,9 +99,10 @@
|
|
|
110
99
|
"eslint-config-prettier": "~10.1.8",
|
|
111
100
|
"global-jsdom": "^26.0.0",
|
|
112
101
|
"jiti": "^2.6.1",
|
|
113
|
-
"
|
|
102
|
+
"jsdom": "^26.1.0",
|
|
103
|
+
"mocha": "^11.7.5",
|
|
114
104
|
"mocha-multi-reporters": "^1.5.1",
|
|
115
|
-
"rimraf": "^6.1.
|
|
105
|
+
"rimraf": "^6.1.3",
|
|
116
106
|
"typescript": "~5.4.5"
|
|
117
107
|
},
|
|
118
108
|
"typeValidation": {
|
|
@@ -120,23 +110,17 @@
|
|
|
120
110
|
},
|
|
121
111
|
"scripts": {
|
|
122
112
|
"api": "fluid-build . --task api",
|
|
123
|
-
"api-extractor:commonjs": "flub generate entrypoints --outDir ./dist",
|
|
124
113
|
"api-extractor:esnext": "flub generate entrypoints --outDir ./lib",
|
|
125
114
|
"build": "fluid-build . --task build",
|
|
126
|
-
"build:commonjs": "fluid-build . --task commonjs",
|
|
127
115
|
"build:compile": "fluid-build . --task compile",
|
|
128
116
|
"build:docs": "api-extractor run --local",
|
|
129
117
|
"build:esnext": "tsc --project ./tsconfig.json && copyfiles -f ../../../common/build/build-common/src/esm/package.json ./lib",
|
|
130
|
-
"build:test": "npm run build:test:esm
|
|
131
|
-
"build:test:cjs": "fluid-tsc commonjs --project ./src/test/tsconfig.cjs.json",
|
|
118
|
+
"build:test": "npm run build:test:esm",
|
|
132
119
|
"build:test:esm": "tsc --project ./src/test/tsconfig.json",
|
|
133
|
-
"check:are-the-types-wrong": "attw --pack . --entrypoints .",
|
|
120
|
+
"check:are-the-types-wrong": "attw --pack . --entrypoints . --profile esm-only",
|
|
134
121
|
"check:biome": "biome check .",
|
|
135
122
|
"check:exports": "concurrently \"npm:check:exports:*\"",
|
|
136
123
|
"check:exports:bundle-release-tags": "api-extractor run --config api-extractor/api-extractor-lint-bundle.json",
|
|
137
|
-
"check:exports:cjs:alpha": "api-extractor run --config api-extractor/api-extractor-lint-alpha.cjs.json",
|
|
138
|
-
"check:exports:cjs:beta": "api-extractor run --config api-extractor/api-extractor-lint-beta.cjs.json",
|
|
139
|
-
"check:exports:cjs:public": "api-extractor run --config api-extractor/api-extractor-lint-public.cjs.json",
|
|
140
124
|
"check:exports:esm:alpha": "api-extractor run --config api-extractor/api-extractor-lint-alpha.esm.json",
|
|
141
125
|
"check:exports:esm:beta": "api-extractor run --config api-extractor/api-extractor-lint-beta.esm.json",
|
|
142
126
|
"check:exports:esm:public": "api-extractor run --config api-extractor/api-extractor-lint-public.esm.json",
|
|
@@ -149,13 +133,11 @@
|
|
|
149
133
|
"format:biome": "biome check . --write",
|
|
150
134
|
"lint": "fluid-build . --task lint",
|
|
151
135
|
"lint:fix": "fluid-build . --task eslint:fix --task format",
|
|
152
|
-
"pack:tests": "tar -cf ./react.test-files.tar ./src/test ./
|
|
136
|
+
"pack:tests": "tar -cf ./react.test-files.tar ./src/test ./lib/test",
|
|
153
137
|
"test": "npm run test:mocha",
|
|
154
138
|
"test:coverage": "c8 npm test",
|
|
155
|
-
"test:mocha": "npm run test:mocha:esm
|
|
156
|
-
"test:mocha:cjs": "cross-env FLUID_TEST_MODULE_SYSTEM=CJS mocha",
|
|
139
|
+
"test:mocha": "npm run test:mocha:esm",
|
|
157
140
|
"test:mocha:esm": "mocha",
|
|
158
|
-
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha"
|
|
159
|
-
"tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ../../../common/build/build-common/src/cjs/package.json ./dist"
|
|
141
|
+
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha"
|
|
160
142
|
}
|
|
161
143
|
}
|
package/react.test-files.tar
CHANGED
|
Binary file
|
package/src/index.ts
CHANGED
|
@@ -45,3 +45,13 @@ export {
|
|
|
45
45
|
withMemoizedTreeObservations,
|
|
46
46
|
} from "./useTree.js";
|
|
47
47
|
export { objectIdNumber } from "./simpleIdentifier.js";
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
FormattedMainView,
|
|
51
|
+
PlainTextMainView,
|
|
52
|
+
PlainQuillView,
|
|
53
|
+
type FormattedMainViewProps,
|
|
54
|
+
type PlainMainViewProps,
|
|
55
|
+
type FormattedEditorHandle,
|
|
56
|
+
} from "./text/index.js";
|
|
57
|
+
export { UndoRedoStacks, type UndoRedo } from "./undoRedo.js";
|
package/src/propNode.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { TreeNode, TreeLeafValue } from "@fluidframework/tree";
|
|
|
16
16
|
* To convert a TreeNode to this type use {@link toPropTreeNode} or {@link toPropTreeRecord}.
|
|
17
17
|
* @alpha
|
|
18
18
|
*/
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
20
20
|
export interface PropTreeNode<T extends TreeNode> extends ErasedType<[T, "PropTreeNode"]> {}
|
|
21
21
|
|
|
22
22
|
/**
|