@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.
Files changed (112) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +2 -0
  3. package/api-report/react.alpha.api.md +8 -8
  4. package/lib/index.d.ts +2 -0
  5. package/lib/index.d.ts.map +1 -1
  6. package/lib/index.js +2 -0
  7. package/lib/index.js.map +1 -1
  8. package/lib/propNode.js.map +1 -1
  9. package/lib/reactSharedTreeView.d.ts +6 -6
  10. package/lib/reactSharedTreeView.d.ts.map +1 -1
  11. package/lib/reactSharedTreeView.js +16 -18
  12. package/lib/reactSharedTreeView.js.map +1 -1
  13. package/lib/test/mochaHooks.js +13 -0
  14. package/lib/test/mochaHooks.js.map +1 -0
  15. package/lib/test/reactSharedTreeView.spec.js +3 -3
  16. package/lib/test/reactSharedTreeView.spec.js.map +1 -1
  17. package/lib/test/text/plainUtils.test.js +75 -0
  18. package/lib/test/text/plainUtils.test.js.map +1 -0
  19. package/lib/test/text/textEditor.test.js +760 -0
  20. package/lib/test/text/textEditor.test.js.map +1 -0
  21. package/lib/test/undoRedo.test.js +62 -0
  22. package/lib/test/undoRedo.test.js.map +1 -0
  23. package/lib/test/useObservation.spec.js +8 -9
  24. package/lib/test/useObservation.spec.js.map +1 -1
  25. package/lib/test/useTree.spec.js +15 -16
  26. package/lib/test/useTree.spec.js.map +1 -1
  27. package/lib/text/formatted/index.d.ts +6 -0
  28. package/lib/text/formatted/index.d.ts.map +1 -0
  29. package/lib/text/formatted/index.js +6 -0
  30. package/lib/text/formatted/index.js.map +1 -0
  31. package/lib/text/formatted/quillFormattedView.d.ts +66 -0
  32. package/lib/text/formatted/quillFormattedView.d.ts.map +1 -0
  33. package/lib/text/formatted/quillFormattedView.js +520 -0
  34. package/lib/text/formatted/quillFormattedView.js.map +1 -0
  35. package/lib/text/index.d.ts +7 -0
  36. package/lib/text/index.d.ts.map +1 -0
  37. package/lib/text/index.js +7 -0
  38. package/lib/text/index.js.map +1 -0
  39. package/lib/text/plain/index.d.ts +7 -0
  40. package/lib/text/plain/index.d.ts.map +1 -0
  41. package/lib/text/plain/index.js +7 -0
  42. package/lib/text/plain/index.js.map +1 -0
  43. package/lib/text/plain/plainTextView.d.ts +14 -0
  44. package/lib/text/plain/plainTextView.d.ts.map +1 -0
  45. package/lib/text/plain/plainTextView.js +70 -0
  46. package/lib/text/plain/plainTextView.js.map +1 -0
  47. package/lib/text/plain/plainUtils.d.ts +23 -0
  48. package/lib/text/plain/plainUtils.d.ts.map +1 -0
  49. package/lib/text/plain/plainUtils.js +51 -0
  50. package/lib/text/plain/plainUtils.js.map +1 -0
  51. package/lib/text/plain/quillView.d.ts +22 -0
  52. package/lib/text/plain/quillView.d.ts.map +1 -0
  53. package/lib/text/plain/quillView.js +106 -0
  54. package/lib/text/plain/quillView.js.map +1 -0
  55. package/lib/undoRedo.d.ts +51 -0
  56. package/lib/undoRedo.d.ts.map +1 -0
  57. package/lib/undoRedo.js +76 -0
  58. package/lib/undoRedo.js.map +1 -0
  59. package/lib/useObservation.js +6 -6
  60. package/lib/useObservation.js.map +1 -1
  61. package/lib/useTree.d.ts +7 -7
  62. package/lib/useTree.d.ts.map +1 -1
  63. package/lib/useTree.js +6 -6
  64. package/lib/useTree.js.map +1 -1
  65. package/package.json +28 -46
  66. package/react.test-files.tar +0 -0
  67. package/src/index.ts +10 -0
  68. package/src/propNode.ts +1 -1
  69. package/src/reactSharedTreeView.tsx +11 -13
  70. package/src/text/formatted/index.ts +11 -0
  71. package/src/text/formatted/quillFormattedView.tsx +627 -0
  72. package/src/text/index.ts +15 -0
  73. package/src/text/plain/index.ts +7 -0
  74. package/src/text/plain/plainTextView.tsx +110 -0
  75. package/src/text/plain/plainUtils.ts +68 -0
  76. package/src/text/plain/quillView.tsx +149 -0
  77. package/src/undoRedo.ts +117 -0
  78. package/src/useObservation.ts +6 -6
  79. package/src/useTree.ts +19 -12
  80. package/tsconfig.json +6 -0
  81. package/api-extractor/api-extractor-lint-alpha.cjs.json +0 -5
  82. package/api-extractor/api-extractor-lint-beta.cjs.json +0 -5
  83. package/api-extractor/api-extractor-lint-public.cjs.json +0 -5
  84. package/dist/alpha.d.ts +0 -45
  85. package/dist/beta.d.ts +0 -15
  86. package/dist/index.d.ts +0 -16
  87. package/dist/index.d.ts.map +0 -1
  88. package/dist/index.js +0 -26
  89. package/dist/index.js.map +0 -1
  90. package/dist/package.json +0 -4
  91. package/dist/propNode.d.ts +0 -114
  92. package/dist/propNode.d.ts.map +0 -1
  93. package/dist/propNode.js +0 -43
  94. package/dist/propNode.js.map +0 -1
  95. package/dist/public.d.ts +0 -15
  96. package/dist/reactSharedTreeView.d.ts +0 -119
  97. package/dist/reactSharedTreeView.d.ts.map +0 -1
  98. package/dist/reactSharedTreeView.js +0 -206
  99. package/dist/reactSharedTreeView.js.map +0 -1
  100. package/dist/simpleIdentifier.d.ts +0 -19
  101. package/dist/simpleIdentifier.d.ts.map +0 -1
  102. package/dist/simpleIdentifier.js +0 -33
  103. package/dist/simpleIdentifier.js.map +0 -1
  104. package/dist/useObservation.d.ts +0 -83
  105. package/dist/useObservation.d.ts.map +0 -1
  106. package/dist/useObservation.js +0 -295
  107. package/dist/useObservation.js.map +0 -1
  108. package/dist/useTree.d.ts +0 -80
  109. package/dist/useTree.d.ts.map +0 -1
  110. package/dist/useTree.js +0 -137
  111. package/dist/useTree.js.map +0 -1
  112. package/tsconfig.cjs.json +0 -7
@@ -1,33 +0,0 @@
1
- "use strict";
2
- /*!
3
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
- * Licensed under the MIT License.
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.objectIdNumber = void 0;
8
- let counter = 0;
9
- const idMap = new WeakMap();
10
- /**
11
- * Associates a unique number with an object.
12
- * @remarks
13
- * The ID number is tied to the object identity, not the object's contents; modifying the object will not cause it to get a different ID.
14
- *
15
- * This can be handy for generating {@link https://react.dev/learn/rendering-lists#where-to-get-your-key | keys for React lists} from TreeNodes.
16
- *
17
- * Most cases which could use this function should just use the objects themselves instead of getting IDs from them, since the objects will have the same equality as the IDs.
18
- * For example, if storing data associated with the objects in a map, using the object as the key is more efficient than getting an ID from it and using that.
19
- * This functions exists to deal with the edge case where you would like to use object identity, but you can't.
20
- * React keys are an examples of such a case, since React does not allow objects as keys.
21
- * @alpha
22
- */
23
- function objectIdNumber(object) {
24
- const id = idMap.get(object);
25
- if (id !== undefined) {
26
- return id;
27
- }
28
- counter++;
29
- idMap.set(object, counter);
30
- return counter;
31
- }
32
- exports.objectIdNumber = objectIdNumber;
33
- //# sourceMappingURL=simpleIdentifier.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"simpleIdentifier.js","sourceRoot":"","sources":["../src/simpleIdentifier.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,IAAI,OAAO,GAAG,CAAC,CAAC;AAEhB,MAAM,KAAK,GAAG,IAAI,OAAO,EAAkB,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,SAAgB,cAAc,CAAC,MAAc;IAC5C,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACX,CAAC;IACD,OAAO,EAAE,CAAC;IACV,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC;AAChB,CAAC;AARD,wCAQC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nlet counter = 0;\n\nconst idMap = new WeakMap<object, number>();\n\n/**\n * Associates a unique number with an object.\n * @remarks\n * The ID number is tied to the object identity, not the object's contents; modifying the object will not cause it to get a different ID.\n *\n * This can be handy for generating {@link https://react.dev/learn/rendering-lists#where-to-get-your-key | keys for React lists} from TreeNodes.\n *\n * Most cases which could use this function should just use the objects themselves instead of getting IDs from them, since the objects will have the same equality as the IDs.\n * For example, if storing data associated with the objects in a map, using the object as the key is more efficient than getting an ID from it and using that.\n * This functions exists to deal with the edge case where you would like to use object identity, but you can't.\n * React keys are an examples of such a case, since React does not allow objects as keys.\n * @alpha\n */\nexport function objectIdNumber(object: object): number {\n\tconst id = idMap.get(object);\n\tif (id !== undefined) {\n\t\treturn id;\n\t}\n\tcounter++;\n\tidMap.set(object, counter);\n\treturn counter;\n}\n"]}
@@ -1,83 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- /**
6
- * Options for {@link useTreeObservations}.
7
- * @input
8
- * @alpha
9
- */
10
- export interface ObservationOptions {
11
- /**
12
- * Called when the tracked observations are invalidated.
13
- * @remarks
14
- * This is not expected to have production use cases, but is useful for testing and debugging.
15
- */
16
- onInvalidation?: () => void;
17
- }
18
- /**
19
- * Custom hook which invalidates a React Component based on changes to what was observed during `trackDuring`.
20
- *
21
- * @param trackDuring - Called synchronously: can make event subscriptions which call the provided `invalidate` function.
22
- * Any such subscriptions should be cleaned up via the returned `unsubscribe` function which will only be invoked if `invalidate` is not called.
23
- * If `invalidate` is called, the code calling it should remove any subscriptions before calling it.
24
- * @remarks
25
- * React strongly discourages "render" from having side-effects other than idempotent lazy initialization.
26
- *
27
- * Tracking observations made during render to subscribe to events for automatic invalidation is a side-effect.
28
- * This makes the behavior of this hook somewhat unusual from a React perspective, and also rather poorly supported by React.
29
- *
30
- * 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.
31
- *
32
- * For example, this hook could record which observations were made during render, then pass them into a `useEffect` hook to do the subscription.
33
- * This would be more aligned with React's expectations, but would have a number of issues:
34
- * - The effect would run after render, so if the observed content changed between render and the effect running, there could be an invalidation bug.
35
- * - 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.
36
- * - It would have slightly higher cost due to the extra effect.
37
- * Such an approach is implemented in {@link useObservationPure}.
38
- */
39
- export declare function useObservation<TResult>(trackDuring: (invalidate: () => void) => {
40
- result: TResult;
41
- unsubscribe: () => void;
42
- }, options?: ObservationOptions): TResult;
43
- /**
44
- * Options for {@link useTreeObservations}.
45
- * @input
46
- */
47
- export interface ObservationPureOptions {
48
- onSubscribe?: () => void;
49
- onUnsubscribe?: () => void;
50
- onPureInvalidation?: () => void;
51
- }
52
- /**
53
- * {@link useObservation} but more aligned with React expectations.
54
- * @remarks
55
- * This is more expensive than {@link useObservation}, and also leaks subscriptions longer.
56
- * When rendering a component, relies on a finalizer to clean up subscriptions from the previous render.
57
- *
58
- * 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.
59
- * {@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.
60
- *
61
- * This should also avoid calling `setState` after unmount, which can avoid a React warning.
62
- *
63
- * This does not however avoid the finalizer based cleanup: it actually relies on it much more (for rerender and unmount, not just unmount).
64
- * This simply adds a layer of indirection to the invalidation through useEffect.
65
- */
66
- export declare function useObservationWithEffects<TResult>(trackDuring: (invalidate: () => void) => {
67
- result: TResult;
68
- unsubscribe: () => void;
69
- }, options?: ObservationOptions & ObservationPureOptions): TResult;
70
- /**
71
- * {@link useObservation} but more strict with its behavior.
72
- * @remarks
73
- * This has the eager cleanup on re-render of {@link useObservation}, but has the effect based subscriptions and cleanup on unmount of {@link useObservationWithEffects}.
74
- *
75
- * 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.
76
- * @privateRemarks
77
- * This is just a {@link useObservationPure}, except with the eager cleanup on re-render from {@link useObservation}.
78
- */
79
- export declare function useObservationStrict<TResult>(trackDuring: (invalidate: () => void) => {
80
- result: TResult;
81
- unsubscribe: () => void;
82
- }, options?: ObservationOptions & ObservationPureOptions): TResult;
83
- //# sourceMappingURL=useObservation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useObservation.d.ts","sourceRoot":"","sources":["../src/useObservation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2BH;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EACrC,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,IAAI,KAAK;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,IAAI,CAAA;CAAE,EACrF,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAkET;AAoBD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACtC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAuID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAChD,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,IAAI,KAAK;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,IAAI,CAAA;CAAE,EACrF,OAAO,CAAC,EAAE,kBAAkB,GAAG,sBAAsB,GACnD,OAAO,CAGT;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAC3C,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,IAAI,KAAK;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,IAAI,CAAA;CAAE,EACrF,OAAO,CAAC,EAAE,kBAAkB,GAAG,sBAAsB,GACnD,OAAO,CAaT"}
@@ -1,295 +0,0 @@
1
- "use strict";
2
- /*!
3
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
- * Licensed under the MIT License.
5
- */
6
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
- if (k2 === undefined) k2 = k;
8
- var desc = Object.getOwnPropertyDescriptor(m, k);
9
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
- desc = { enumerable: true, get: function() { return m[k]; } };
11
- }
12
- Object.defineProperty(o, k2, desc);
13
- }) : (function(o, m, k, k2) {
14
- if (k2 === undefined) k2 = k;
15
- o[k2] = m[k];
16
- }));
17
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
- Object.defineProperty(o, "default", { enumerable: true, value: v });
19
- }) : function(o, v) {
20
- o["default"] = v;
21
- });
22
- var __importStar = (this && this.__importStar) || function (mod) {
23
- if (mod && mod.__esModule) return mod;
24
- var result = {};
25
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
26
- __setModuleDefault(result, mod);
27
- return result;
28
- };
29
- Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.useObservationStrict = exports.useObservationWithEffects = exports.useObservation = void 0;
31
- const React = __importStar(require("react"));
32
- /**
33
- * Wrapper around subscriptions to give it an object identity which can be used with FinalizationRegistry.
34
- * @remarks
35
- * This indirection is needed so inner can be provided to finalizationRegistry as the heldValue and avoid having that cause a leak.
36
- * @privateRemarks
37
- * This is a named class to make looking for leaks of it in heap snapshots easier.
38
- */
39
- class SubscriptionsWrapper {
40
- constructor() {
41
- this.inner = {};
42
- }
43
- }
44
- /**
45
- * Custom hook which invalidates a React Component based on changes to what was observed during `trackDuring`.
46
- *
47
- * @param trackDuring - Called synchronously: can make event subscriptions which call the provided `invalidate` function.
48
- * Any such subscriptions should be cleaned up via the returned `unsubscribe` function which will only be invoked if `invalidate` is not called.
49
- * If `invalidate` is called, the code calling it should remove any subscriptions before calling it.
50
- * @remarks
51
- * React strongly discourages "render" from having side-effects other than idempotent lazy initialization.
52
- *
53
- * Tracking observations made during render to subscribe to events for automatic invalidation is a side-effect.
54
- * This makes the behavior of this hook somewhat unusual from a React perspective, and also rather poorly supported by React.
55
- *
56
- * 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.
57
- *
58
- * For example, this hook could record which observations were made during render, then pass them into a `useEffect` hook to do the subscription.
59
- * This would be more aligned with React's expectations, but would have a number of issues:
60
- * - The effect would run after render, so if the observed content changed between render and the effect running, there could be an invalidation bug.
61
- * - 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.
62
- * - It would have slightly higher cost due to the extra effect.
63
- * Such an approach is implemented in {@link useObservationPure}.
64
- */
65
- function useObservation(trackDuring, options) {
66
- // Use a React state hook to invalidate this component something tracked by `trackDuring` changes.
67
- const [subscriptions, setSubscriptions] = React.useState(new SubscriptionsWrapper());
68
- // 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.
69
- // TO help with this, pull out `inner` so it can be closed over without retaining `subscriptions`.
70
- const inner = subscriptions.inner;
71
- const invalidate = () => {
72
- // Since below uses trackObservationsOnce, the un-subscription is done before calling this callback,
73
- // and therefore this must ensure that no further un-subscriptions occur, as well as that the render is invalidated.
74
- //
75
- // Note referencing `setSubscriptions` risks transitively holding onto a reference to `subscriptions` depending on how React implements `useState`.
76
- // If such a transitive reference does exist, it would cause a leak (by preventing finalizationRegistry from running and thus preventing un-subscription after unmount).
77
- // Experimentally this has been observed not to be the case, and is validated by the "unsubscribe on unmount" tests.
78
- setSubscriptions(new SubscriptionsWrapper());
79
- // This cannot do `registry.unregister(subscriptions);` as that would cause a leak by holding onto `subscriptions`
80
- // since this closure is held onto by the subscribed events.
81
- // Skipping such an un-registration is fine so long as we ensure the registry does not redundantly unsubscribe.
82
- // Since trackObservationsOnce already unsubscribed, just clear out the unsubscribe function to ensure it is not called again by the finalizer.
83
- inner.unsubscribe = undefined;
84
- options?.onInvalidation?.();
85
- };
86
- // If there was a previous rendering of this instance of this hook in the current component, unsubscribe from it.
87
- // This avoids a memory leak (of the event subscriptions) in the case where a components is rerendered.
88
- inner.unsubscribe?.();
89
- inner.unsubscribe = undefined;
90
- // This is logically pure other than the side effect of registering for invalidation if the observed content changes.
91
- // This is safe from a React perspective since when the observed content changes, that is reflected in the `useState` above.
92
- // 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.
93
- // That leak is avoided via two separate approaches: the un-subscription for events from previous renders above,
94
- // and the use of finalizationRegistry below to handle the component unmount case.
95
- const out = trackDuring(invalidate);
96
- inner.unsubscribe = out.unsubscribe;
97
- // There is still the issue of unsubscribing when the component unmounts.
98
- // This can almost be done using a React effect hook with an empty dependency list.
99
- // Unfortunately that would have a hard time getting the correct subscriptions to unsubscribe,
100
- // and if run before unmount, like in StrictMode, it would cause an invalidation bug.
101
- // Suppressing that invalidation bug with an extra call to setSubscriptions could work, but would produce incorrect warnings about leaks,
102
- // and might cause infinite rerender depending on how StrictMode works.
103
- // Such an Effect would look like this:
104
- // React.useEffect(
105
- // () => () => {
106
- // subscriptions.unsubscribe?.();
107
- // subscriptions.unsubscribe = undefined;
108
- // setSubscriptions({});
109
- // },
110
- // [],
111
- // );
112
- // Instead of that, use a FinalizationRegistry to clean up when the subscriptions.
113
- // 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.
114
- // 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.
115
- // 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.
116
- // Currently however, it does not seem like React does or will do that.
117
- // 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.
118
- finalizationRegistry.register(subscriptions, inner);
119
- return out.result;
120
- }
121
- exports.useObservation = useObservation;
122
- /**
123
- * Handles unsubscribing from events when the {@link SubscriptionsWrapper} is garbage collected.
124
- * See comments in {@link useTreeObservations} for details.
125
- */
126
- const finalizationRegistry = new FinalizationRegistry((subscriptions) => {
127
- subscriptions.unsubscribe?.();
128
- // Clear out the unsubscribe function to ensure it is not called again.
129
- // This should not be needed, but maintains the invariant that unsubscribe should be removed after being called.
130
- subscriptions.unsubscribe = undefined;
131
- });
132
- /**
133
- * Variant of {@link useObservation} where render behaves in a more pure functional way.
134
- * @remarks
135
- * Subscriptions are only created in effects, which leaves a gap between when the observations are tracked and the subscriptions are created.
136
- * @privateRemarks
137
- * If impureness of the other approaches becomes a problem, this could be used directly instead.
138
- * Doing so would require changing `TreeAlpha.trackObservationsOnce` return a function to subscribe to the tracked observations instead of subscribing directly.
139
- * 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.
140
- * 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.
141
- *
142
- * If using this directly, ensure it has tests other than via the other hooks which use it.
143
- */
144
- function useObservationPure(trackDuring, options) {
145
- // Dummy state used to trigger invalidations.
146
- const [_subscriptions, setSubscriptions] = React.useState(0);
147
- const { result, subscribe } = trackDuring();
148
- React.useEffect(() => {
149
- // Subscribe to events from the latest render
150
- const invalidate = () => {
151
- setSubscriptions((n) => n + 1);
152
- inner.unsubscribe = undefined;
153
- options?.onPureInvalidation?.();
154
- };
155
- options?.onSubscribe?.();
156
- const inner = { unsubscribe: subscribe(invalidate) };
157
- return () => {
158
- inner.unsubscribe?.();
159
- inner.unsubscribe = undefined;
160
- options?.onUnsubscribe?.();
161
- };
162
- });
163
- return result;
164
- }
165
- /**
166
- * Manages subscription to a one-shot invalidation event (unsubscribes when sent) event where multiple parties may want to subscribe to the event.
167
- * @remarks
168
- * When the event occurs, all subscribers are called.
169
- * Any subscribers added after the event has occurred are immediately called.
170
- *
171
- * Since new subscriptions can be added any any time, this can not unsubscribe from the source after the last destination has unsubscribed.
172
- *
173
- * Instead the finalizationRegistry is used.
174
- * @privateRemarks
175
- * This is a named class to make looking for leaks of it in heap snapshots easier.
176
- */
177
- class SubscriptionTracker {
178
- constructor(unsubscribe) {
179
- /**
180
- * Hook subscriptions to be trigger by `inner`.
181
- */
182
- this.toInvalidate = new Set();
183
- this.disposed = false;
184
- this.invalidate = () => {
185
- this.assertNotDisposed();
186
- if (this.inner.unsubscribe === undefined) {
187
- throw new Error("Already invalidated");
188
- }
189
- this.inner.unsubscribe = undefined;
190
- for (const invalidate of this.toInvalidate) {
191
- invalidate();
192
- }
193
- this.toInvalidate.clear();
194
- };
195
- this.inner = { unsubscribe };
196
- }
197
- assertNotDisposed() {
198
- if (this.disposed) {
199
- throw new Error("Already disposed");
200
- }
201
- }
202
- static create(unsubscribe) {
203
- const tracker = new SubscriptionTracker(unsubscribe);
204
- finalizationRegistry.register(tracker, tracker.inner);
205
- return tracker;
206
- }
207
- subscribe(callback) {
208
- this.assertNotDisposed();
209
- if (this.toInvalidate.has(callback)) {
210
- throw new Error("Already subscribed");
211
- }
212
- if (this.inner.unsubscribe === undefined) {
213
- // Already invalidated, so immediately call back.
214
- callback();
215
- return () => { };
216
- }
217
- this.toInvalidate.add(callback);
218
- return () => {
219
- this.assertNotDisposed();
220
- if (!this.toInvalidate.has(callback)) {
221
- throw new Error("Not subscribed");
222
- }
223
- this.toInvalidate.delete(callback);
224
- };
225
- }
226
- dispose() {
227
- this.assertNotDisposed();
228
- this.disposed = true;
229
- this.inner.unsubscribe?.();
230
- this.inner.unsubscribe = undefined;
231
- if (this.toInvalidate.size > 0) {
232
- throw new Error("Invalid disposal before unsubscribing all listeners");
233
- }
234
- finalizationRegistry.unregister(this.inner);
235
- }
236
- }
237
- /**
238
- * {@link useObservation} but more aligned with React expectations.
239
- * @remarks
240
- * This is more expensive than {@link useObservation}, and also leaks subscriptions longer.
241
- * When rendering a component, relies on a finalizer to clean up subscriptions from the previous render.
242
- *
243
- * 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.
244
- * {@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.
245
- *
246
- * This should also avoid calling `setState` after unmount, which can avoid a React warning.
247
- *
248
- * This does not however avoid the finalizer based cleanup: it actually relies on it much more (for rerender and unmount, not just unmount).
249
- * This simply adds a layer of indirection to the invalidation through useEffect.
250
- */
251
- function useObservationWithEffects(trackDuring, options) {
252
- const pureResult = useObservationPure(observationAdapter(trackDuring, options), options);
253
- return pureResult.innerResult;
254
- }
255
- exports.useObservationWithEffects = useObservationWithEffects;
256
- /**
257
- * An adapter wrapping `trackDuring` to help implement the {@link useObservation} using {@link useObservationPure}.
258
- */
259
- function observationAdapter(trackDuring, options) {
260
- return () => {
261
- // The main invalidation function, which only runs once, and is used to create the SubscriptionTracker.
262
- const invalidateMain = () => {
263
- tracker.invalidate();
264
- options?.onInvalidation?.();
265
- };
266
- const result2 = trackDuring(invalidateMain);
267
- const tracker = SubscriptionTracker.create(result2.unsubscribe);
268
- return {
269
- result: { tracker, innerResult: result2.result },
270
- subscribe: (invalidate) => {
271
- return tracker.subscribe(invalidate);
272
- },
273
- };
274
- };
275
- }
276
- /**
277
- * {@link useObservation} but more strict with its behavior.
278
- * @remarks
279
- * This has the eager cleanup on re-render of {@link useObservation}, but has the effect based subscriptions and cleanup on unmount of {@link useObservationWithEffects}.
280
- *
281
- * 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.
282
- * @privateRemarks
283
- * This is just a {@link useObservationPure}, except with the eager cleanup on re-render from {@link useObservation}.
284
- */
285
- function useObservationStrict(trackDuring, options) {
286
- // Used to unsubscribe from the previous render's subscriptions.
287
- // See `useObservation` for a more documented explanation of this pattern.
288
- const [subscriptions] = React.useState({ previousTracker: undefined });
289
- const pureResult = useObservationPure(observationAdapter(trackDuring, options), options);
290
- subscriptions.previousTracker?.dispose();
291
- subscriptions.previousTracker = pureResult.tracker;
292
- return pureResult.innerResult;
293
- }
294
- exports.useObservationStrict = useObservationStrict;
295
- //# sourceMappingURL=useObservation.js.map
@@ -1 +0,0 @@
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"]}
package/dist/useTree.d.ts DELETED
@@ -1,80 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import type { TreeLeafValue, TreeNode } from "@fluidframework/tree";
6
- import * as React from "react";
7
- import { type PropTreeNodeRecord, type PropTreeValue, type UnwrapPropTreeNodeRecord, type WrapNodes } from "./propNode.js";
8
- import { type ObservationOptions } from "./useObservation.js";
9
- /**
10
- * Custom hook which invalidates a React Component when there is a change in the subtree defined by `subtreeRoot`.
11
- * This includes changes to the tree's content, but not changes to its parentage.
12
- * See {@link @fluidframework/tree#TreeChangeEvents.treeChanged} for details.
13
- * @remarks
14
- * Consider using {@link useTreeObservations} instead which tracks what was observed and only invalidates if it changes.
15
- * @alpha
16
- */
17
- export declare function useTree(subtreeRoot: TreeNode): number;
18
- /**
19
- * Higher order component which wraps a component to use {@link useTreeObservations}.
20
- *
21
- * @remarks
22
- * 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).
23
- * This wraps a component in such tracking.
24
- *
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
- * Components defined using this higher order component can take in either raw TreeNodes or {@link PropTreeNode}s: the latter will be automatically unwrapped.
27
- * @privateRemarks
28
- * `React.FC` does not seem to be covariant over its input type, so to make use of this more ergonomic,
29
- * the return type intersects the various ways this could be used (with or without PropTreeNode wrapping).
30
- * @alpha
31
- */
32
- export declare function withTreeObservations<TIn>(component: React.FC<TIn>, options?: ObservationOptions): React.FC<TIn> & React.FC<WrapNodes<TIn>> & React.FC<TIn | WrapNodes<TIn>>;
33
- /**
34
- * {@link withTreeObservations} wrapped with React.memo.
35
- * @remarks
36
- * There is no special logic here, just a convenience wrapper.
37
- * @alpha
38
- */
39
- export declare function withMemoizedTreeObservations<TIn>(component: React.FC<TIn>, options?: ObservationOptions & {
40
- readonly propsAreEqual?: Parameters<typeof React.memo>[1];
41
- }): React.MemoExoticComponent<ReturnType<typeof withTreeObservations<TIn>>>;
42
- /**
43
- * Custom hook which invalidates a React Component when there is a change in tree content observed during `trackDuring`.
44
- *
45
- * @param trackDuring - Called synchronously, and will have its tree observations tracked.
46
- *
47
- * @remarks
48
- * This includes changes to the tree's content.
49
- * Currently this will throw if observing a node's parentage to be undefined,
50
- * and node status changes will not cause invalidation.
51
- *
52
- * For additional type safety to help avoid observing TreeNode content outside of this hook, see {@link PropTreeNode}.
53
- * @alpha
54
- */
55
- export declare function useTreeObservations<TResult>(trackDuring: () => TResult, options?: ObservationOptions): TResult;
56
- /**
57
- * Custom hook for using a prop tree node.
58
- *
59
- * @param propNode - Input, automatically unwrapped TreeNode from a {@link PropTreeNode} if needed.
60
- * @param trackDuring - Callback which reads from the node and returns a result.
61
- * If the result is a TreeNode or {@link NodeRecord} it will be wrapped as a {@link PropTreeNode} or {@link PropTreeNodeRecord}, see {@link WrapNodes}.
62
- *
63
- * It is recommended that when returning data containing TreeNodes,
64
- * use a format supported by {@link WrapNodes} or wrap the nodes manually using {@link toPropTreeNode}.
65
- * This improves the type safety, reducing the risk of invalidation bugs due to untracked access of tree content contained in the return value.
66
- *
67
- * Note that is is fine to observe any node inside the callback, not just the provided node: all accesses will be tracked.
68
- * The input node is just provided as a way to automatically unwrap the {@link PropTreeNode}
69
- *
70
- * @remarks
71
- * Reads content using {@link useTreeObservations} to track dependencies.
72
- * @alpha
73
- */
74
- export declare function usePropTreeNode<T extends TreeNode | TreeLeafValue, TResult>(propNode: PropTreeValue<T> | T, trackDuring: (node: T) => TResult): WrapNodes<TResult>;
75
- /**
76
- * {@link usePropTreeNode} but takes in a {@link PropTreeNodeRecord}.
77
- * @alpha
78
- */
79
- export declare function usePropTreeRecord<const T extends PropTreeNodeRecord, TResult>(props: T, f: (node: UnwrapPropTreeNodeRecord<T>) => TResult): WrapNodes<TResult>;
80
- //# sourceMappingURL=useTree.d.ts.map
@@ -1 +0,0 @@
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;AAE/B,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,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,EACxB,OAAO,CAAC,EAAE,kBAAkB,GAC1B,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAG3E;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAC/C,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,EACxB,OAAO,CAAC,EAAE,kBAAkB,GAAG;IAC9B,QAAQ,CAAC,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;CAC1D,GACC,KAAK,CAAC,mBAAmB,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,CAEzE;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"}