@fluidframework/react 2.90.0-378676 → 2.90.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 (96) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +2 -0
  3. package/lib/index.d.ts +2 -0
  4. package/lib/index.d.ts.map +1 -1
  5. package/lib/index.js +2 -0
  6. package/lib/index.js.map +1 -1
  7. package/lib/propNode.js.map +1 -1
  8. package/lib/test/mochaHooks.js +13 -0
  9. package/lib/test/mochaHooks.js.map +1 -0
  10. package/lib/test/text/plainUtils.test.js +75 -0
  11. package/lib/test/text/plainUtils.test.js.map +1 -0
  12. package/lib/test/text/textEditor.test.js +704 -0
  13. package/lib/test/text/textEditor.test.js.map +1 -0
  14. package/lib/test/undoRedo.test.js +62 -0
  15. package/lib/test/undoRedo.test.js.map +1 -0
  16. package/lib/test/useObservation.spec.js +0 -1
  17. package/lib/test/useObservation.spec.js.map +1 -1
  18. package/lib/test/useTree.spec.js +0 -1
  19. package/lib/test/useTree.spec.js.map +1 -1
  20. package/lib/text/formatted/index.d.ts +6 -0
  21. package/lib/text/formatted/index.d.ts.map +1 -0
  22. package/lib/text/formatted/index.js +6 -0
  23. package/lib/text/formatted/index.js.map +1 -0
  24. package/lib/text/formatted/quillFormattedView.d.ts +54 -0
  25. package/lib/text/formatted/quillFormattedView.d.ts.map +1 -0
  26. package/lib/text/formatted/quillFormattedView.js +426 -0
  27. package/lib/text/formatted/quillFormattedView.js.map +1 -0
  28. package/lib/text/index.d.ts +7 -0
  29. package/lib/text/index.d.ts.map +1 -0
  30. package/lib/text/index.js +7 -0
  31. package/lib/text/index.js.map +1 -0
  32. package/lib/text/plain/index.d.ts +7 -0
  33. package/lib/text/plain/index.d.ts.map +1 -0
  34. package/lib/text/plain/index.js +7 -0
  35. package/lib/text/plain/index.js.map +1 -0
  36. package/lib/text/plain/plainTextView.d.ts +14 -0
  37. package/lib/text/plain/plainTextView.d.ts.map +1 -0
  38. package/lib/text/plain/plainTextView.js +75 -0
  39. package/lib/text/plain/plainTextView.js.map +1 -0
  40. package/lib/text/plain/plainUtils.d.ts +23 -0
  41. package/lib/text/plain/plainUtils.d.ts.map +1 -0
  42. package/lib/text/plain/plainUtils.js +51 -0
  43. package/lib/text/plain/plainUtils.js.map +1 -0
  44. package/lib/text/plain/quillView.d.ts +22 -0
  45. package/lib/text/plain/quillView.d.ts.map +1 -0
  46. package/lib/text/plain/quillView.js +112 -0
  47. package/lib/text/plain/quillView.js.map +1 -0
  48. package/lib/undoRedo.d.ts +51 -0
  49. package/lib/undoRedo.d.ts.map +1 -0
  50. package/lib/undoRedo.js +76 -0
  51. package/lib/undoRedo.js.map +1 -0
  52. package/package.json +26 -45
  53. package/react.test-files.tar +0 -0
  54. package/src/index.ts +10 -0
  55. package/src/propNode.ts +1 -1
  56. package/src/text/formatted/index.ts +11 -0
  57. package/src/text/formatted/quillFormattedView.tsx +509 -0
  58. package/src/text/index.ts +15 -0
  59. package/src/text/plain/index.ts +7 -0
  60. package/src/text/plain/plainTextView.tsx +110 -0
  61. package/src/text/plain/plainUtils.ts +68 -0
  62. package/src/text/plain/quillView.tsx +149 -0
  63. package/src/undoRedo.ts +117 -0
  64. package/tsconfig.json +6 -0
  65. package/api-extractor/api-extractor-lint-alpha.cjs.json +0 -5
  66. package/api-extractor/api-extractor-lint-beta.cjs.json +0 -5
  67. package/api-extractor/api-extractor-lint-public.cjs.json +0 -5
  68. package/dist/alpha.d.ts +0 -45
  69. package/dist/beta.d.ts +0 -15
  70. package/dist/index.d.ts +0 -16
  71. package/dist/index.d.ts.map +0 -1
  72. package/dist/index.js +0 -26
  73. package/dist/index.js.map +0 -1
  74. package/dist/package.json +0 -4
  75. package/dist/propNode.d.ts +0 -114
  76. package/dist/propNode.d.ts.map +0 -1
  77. package/dist/propNode.js +0 -43
  78. package/dist/propNode.js.map +0 -1
  79. package/dist/public.d.ts +0 -15
  80. package/dist/reactSharedTreeView.d.ts +0 -119
  81. package/dist/reactSharedTreeView.d.ts.map +0 -1
  82. package/dist/reactSharedTreeView.js +0 -206
  83. package/dist/reactSharedTreeView.js.map +0 -1
  84. package/dist/simpleIdentifier.d.ts +0 -19
  85. package/dist/simpleIdentifier.d.ts.map +0 -1
  86. package/dist/simpleIdentifier.js +0 -33
  87. package/dist/simpleIdentifier.js.map +0 -1
  88. package/dist/useObservation.d.ts +0 -83
  89. package/dist/useObservation.d.ts.map +0 -1
  90. package/dist/useObservation.js +0 -295
  91. package/dist/useObservation.js.map +0 -1
  92. package/dist/useTree.d.ts +0 -80
  93. package/dist/useTree.d.ts.map +0 -1
  94. package/dist/useTree.js +0 -137
  95. package/dist/useTree.js.map +0 -1
  96. package/tsconfig.cjs.json +0 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plainTextView.js","sourceRoot":"","sources":["../../../src/text/plain/plainTextView.tsx"],"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 type { TextAsTree } from \"@fluidframework/tree/internal\";\nimport * as React from \"react\";\n\nimport { withMemoizedTreeObservations } from \"../../useTree.js\";\n\nimport { syncTextToTree } from \"./plainUtils.js\";\nimport type { MainViewProps } from \"./quillView.js\";\n\n/**\n * A React component for plain text editing.\n * @remarks\n * Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and an HTML textarea for the UI.\n * @internal\n */\nexport const MainView: React.FC<MainViewProps> = ({ root }) => {\n\treturn <PlainTextEditorView root={root} />;\n};\n\n/**\n * A plain text editor view component using a native HTML textarea.\n * Uses TextAsTree for collaborative plain text storage.\n *\n * @remarks\n * This uses withMemoizedTreeObservations to automatically re-render\n * when the tree changes.\n */\nconst PlainTextEditorView = withMemoizedTreeObservations(\n\t({ root }: { root: TextAsTree.Tree }) => {\n\t\t// Reference to the textarea element\n\t\tconst textareaRef = React.useRef<HTMLTextAreaElement>(null);\n\t\t// Guards against update loops between textarea and the tree\n\t\tconst isUpdatingRef = React.useRef<boolean>(false);\n\n\t\t// Access tree content during render to establish observation.\n\t\t// The HOC will automatically re-render when this content changes.\n\t\tconst currentText = root.fullString();\n\n\t\t// Handle textarea changes - sync textarea → tree\n\t\tconst handleChange = React.useCallback(\n\t\t\t(event: React.ChangeEvent<HTMLTextAreaElement>) => {\n\t\t\t\tif (isUpdatingRef.current) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tisUpdatingRef.current = true;\n\n\t\t\t\tconst newText = event.target.value;\n\t\t\t\tsyncTextToTree(root, newText);\n\n\t\t\t\tisUpdatingRef.current = false;\n\t\t\t},\n\t\t\t[root],\n\t\t);\n\n\t\t// Sync textarea when tree changes externally.\n\t\t// We skip this if isUpdatingRef is true, meaning we caused the tree change ourselves\n\t\t// via the handleChange above - in that case textarea already has the correct content.\n\t\tif (textareaRef.current && !isUpdatingRef.current) {\n\t\t\tconst textareaValue = textareaRef.current.value;\n\n\t\t\t// Only update if content actually differs (avoids cursor jump on local edits)\n\t\t\tif (textareaValue !== currentText) {\n\t\t\t\tisUpdatingRef.current = true;\n\n\t\t\t\t// Preserve cursor position\n\t\t\t\tconst selectionStart = textareaRef.current.selectionStart;\n\t\t\t\tconst selectionEnd = textareaRef.current.selectionEnd;\n\n\t\t\t\ttextareaRef.current.value = currentText;\n\n\t\t\t\t// Restore cursor position, clamped to new text length\n\t\t\t\tconst newPosition = Math.min(selectionStart, currentText.length);\n\t\t\t\tconst newEnd = Math.min(selectionEnd, currentText.length);\n\t\t\t\ttextareaRef.current.setSelectionRange(newPosition, newEnd);\n\n\t\t\t\tisUpdatingRef.current = false;\n\t\t\t}\n\t\t}\n\n\t\treturn (\n\t\t\t<div\n\t\t\t\tclassName=\"text-editor-container\"\n\t\t\t\tstyle={{ height: \"100%\", display: \"flex\", flexDirection: \"column\" }}\n\t\t\t>\n\t\t\t\t<h2 style={{ margin: \"10px 0\" }}>Collaborative Text Editor</h2>\n\t\t\t\t<textarea\n\t\t\t\t\tref={textareaRef}\n\t\t\t\t\tdefaultValue={currentText}\n\t\t\t\t\tonChange={handleChange}\n\t\t\t\t\tplaceholder=\"Start typing...\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tflex: 1,\n\t\t\t\t\t\tminHeight: \"300px\",\n\t\t\t\t\t\tborder: \"1px solid #ccc\",\n\t\t\t\t\t\tborderRadius: \"4px\",\n\t\t\t\t\t\tpadding: \"8px\",\n\t\t\t\t\t\tfontSize: \"14px\",\n\t\t\t\t\t\tfontFamily: \"inherit\",\n\t\t\t\t\t\tresize: \"vertical\",\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t);\n\t},\n);\n"]}
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import type { TextAsTree } from "@fluidframework/tree/internal";
6
+ /**
7
+ * Sync `newText` into the provided `root` tree.
8
+ */
9
+ export declare function syncTextToTree(root: TextAsTree.Tree, newText: string): void;
10
+ /**
11
+ * Sync `newText` into the provided `root` tree.
12
+ */
13
+ export declare function computeSync<T>(existing: readonly T[], final: readonly T[]): {
14
+ remove?: {
15
+ start: number;
16
+ end: number;
17
+ };
18
+ insert?: {
19
+ location: number;
20
+ slice: T[];
21
+ };
22
+ };
23
+ //# sourceMappingURL=plainUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plainUtils.d.ts","sourceRoot":"","sources":["../../../src/text/plain/plainUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAS3E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC5B,QAAQ,EAAE,SAAS,CAAC,EAAE,EACtB,KAAK,EAAE,SAAS,CAAC,EAAE,GACjB;IAAE,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,EAAE,CAAA;KAAE,CAAA;CAAE,CAwCxF"}
@@ -0,0 +1,51 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /**
6
+ * Sync `newText` into the provided `root` tree.
7
+ */
8
+ export function syncTextToTree(root, newText) {
9
+ const sync = computeSync(root.charactersCopy(), [...newText]);
10
+ if (sync.remove) {
11
+ root.removeRange(sync.remove.start, sync.remove.end);
12
+ }
13
+ if (sync.insert) {
14
+ root.insertAt(sync.insert.location, sync.insert.slice.join(""));
15
+ }
16
+ }
17
+ /**
18
+ * Sync `newText` into the provided `root` tree.
19
+ */
20
+ export function computeSync(existing, final) {
21
+ // Find common prefix and suffix to minimize changes
22
+ let prefixLength = 0;
23
+ while (prefixLength < existing.length &&
24
+ prefixLength < final.length &&
25
+ existing[prefixLength] === final[prefixLength]) {
26
+ prefixLength++;
27
+ }
28
+ let suffixLength = 0;
29
+ while (suffixLength + prefixLength < existing.length &&
30
+ suffixLength + prefixLength < final.length &&
31
+ existing[existing.length - 1 - suffixLength] === final[final.length - 1 - suffixLength]) {
32
+ suffixLength++;
33
+ }
34
+ // Locate middle replaced range in existing and final
35
+ const existingMiddleStart = prefixLength;
36
+ const existingMiddleEnd = existing.length - suffixLength;
37
+ const newMiddleStart = prefixLength;
38
+ const newMiddleEnd = final.length - suffixLength;
39
+ return {
40
+ remove: existingMiddleStart < existingMiddleEnd
41
+ ? { start: existingMiddleStart, end: existingMiddleEnd }
42
+ : undefined,
43
+ insert: newMiddleStart < newMiddleEnd
44
+ ? {
45
+ location: existingMiddleStart,
46
+ slice: final.slice(newMiddleStart, newMiddleEnd),
47
+ }
48
+ : undefined,
49
+ };
50
+ }
51
+ //# sourceMappingURL=plainUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plainUtils.js","sourceRoot":"","sources":["../../../src/text/plain/plainUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAqB,EAAE,OAAe;IACpE,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAE9D,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACjE,CAAC;AACF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAC1B,QAAsB,EACtB,KAAmB;IAEnB,oDAAoD;IAEpD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,OACC,YAAY,GAAG,QAAQ,CAAC,MAAM;QAC9B,YAAY,GAAG,KAAK,CAAC,MAAM;QAC3B,QAAQ,CAAC,YAAY,CAAC,KAAK,KAAK,CAAC,YAAY,CAAC,EAC7C,CAAC;QACF,YAAY,EAAE,CAAC;IAChB,CAAC;IAED,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,OACC,YAAY,GAAG,YAAY,GAAG,QAAQ,CAAC,MAAM;QAC7C,YAAY,GAAG,YAAY,GAAG,KAAK,CAAC,MAAM;QAC1C,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,YAAY,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,YAAY,CAAC,EACtF,CAAC;QACF,YAAY,EAAE,CAAC;IAChB,CAAC;IAED,qDAAqD;IACrD,MAAM,mBAAmB,GAAG,YAAY,CAAC;IACzC,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,GAAG,YAAY,CAAC;IACzD,MAAM,cAAc,GAAG,YAAY,CAAC;IACpC,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC;IAEjD,OAAO;QACN,MAAM,EACL,mBAAmB,GAAG,iBAAiB;YACtC,CAAC,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,GAAG,EAAE,iBAAiB,EAAE;YACxD,CAAC,CAAC,SAAS;QACb,MAAM,EACL,cAAc,GAAG,YAAY;YAC5B,CAAC,CAAC;gBACA,QAAQ,EAAE,mBAAmB;gBAC7B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,YAAY,CAAC;aAChD;YACF,CAAC,CAAC,SAAS;KACb,CAAC;AACH,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { TextAsTree } from \"@fluidframework/tree/internal\";\n\n/**\n * Sync `newText` into the provided `root` tree.\n */\nexport function syncTextToTree(root: TextAsTree.Tree, newText: string): void {\n\tconst sync = computeSync(root.charactersCopy(), [...newText]);\n\n\tif (sync.remove) {\n\t\troot.removeRange(sync.remove.start, sync.remove.end);\n\t}\n\tif (sync.insert) {\n\t\troot.insertAt(sync.insert.location, sync.insert.slice.join(\"\"));\n\t}\n}\n\n/**\n * Sync `newText` into the provided `root` tree.\n */\nexport function computeSync<T>(\n\texisting: readonly T[],\n\tfinal: readonly T[],\n): { remove?: { start: number; end: number }; insert?: { location: number; slice: T[] } } {\n\t// Find common prefix and suffix to minimize changes\n\n\tlet prefixLength = 0;\n\twhile (\n\t\tprefixLength < existing.length &&\n\t\tprefixLength < final.length &&\n\t\texisting[prefixLength] === final[prefixLength]\n\t) {\n\t\tprefixLength++;\n\t}\n\n\tlet suffixLength = 0;\n\twhile (\n\t\tsuffixLength + prefixLength < existing.length &&\n\t\tsuffixLength + prefixLength < final.length &&\n\t\texisting[existing.length - 1 - suffixLength] === final[final.length - 1 - suffixLength]\n\t) {\n\t\tsuffixLength++;\n\t}\n\n\t// Locate middle replaced range in existing and final\n\tconst existingMiddleStart = prefixLength;\n\tconst existingMiddleEnd = existing.length - suffixLength;\n\tconst newMiddleStart = prefixLength;\n\tconst newMiddleEnd = final.length - suffixLength;\n\n\treturn {\n\t\tremove:\n\t\t\texistingMiddleStart < existingMiddleEnd\n\t\t\t\t? { start: existingMiddleStart, end: existingMiddleEnd }\n\t\t\t\t: undefined,\n\t\tinsert:\n\t\t\tnewMiddleStart < newMiddleEnd\n\t\t\t\t? {\n\t\t\t\t\t\tlocation: existingMiddleStart,\n\t\t\t\t\t\tslice: final.slice(newMiddleStart, newMiddleEnd),\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t};\n}\n"]}
@@ -0,0 +1,22 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import type { TextAsTree } from "@fluidframework/tree/internal";
6
+ import * as React from "react";
7
+ import type { PropTreeNode } from "../../propNode.js";
8
+ /**
9
+ * Props for the MainView component.
10
+ * @input @internal
11
+ */
12
+ export interface MainViewProps {
13
+ root: PropTreeNode<TextAsTree.Tree>;
14
+ }
15
+ /**
16
+ * A React component for plain text editing.
17
+ * @remarks
18
+ * Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and Quill for the UI.
19
+ * @internal
20
+ */
21
+ export declare const MainView: React.FC<MainViewProps>;
22
+ //# sourceMappingURL=quillView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quillView.d.ts","sourceRoot":"","sources":["../../../src/text/plain/quillView.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAKtD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;CACpC;AAED;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAE5C,CAAC"}
@@ -0,0 +1,112 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import Quill from "quill";
6
+ import * as React from "react";
7
+ import { withMemoizedTreeObservations } from "../../useTree.js";
8
+ import { syncTextToTree } from "./plainUtils.js";
9
+ /**
10
+ * A React component for plain text editing.
11
+ * @remarks
12
+ * Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and Quill for the UI.
13
+ * @internal
14
+ */
15
+ export const MainView = ({ root }) => {
16
+ return React.createElement(TextEditorView, { root: root });
17
+ };
18
+ /**
19
+ * The text editor view component with Quill integration.
20
+ * Uses TextAsTree for collaborative plain text storage.
21
+ *
22
+ * @remarks
23
+ * This uses withMemoizedTreeObservations to automatically re-render
24
+ * when the tree changes.
25
+ */
26
+ const TextEditorView = withMemoizedTreeObservations(({ root }) => {
27
+ // DOM element where Quill will mount its editor
28
+ const editorRef = React.useRef(null);
29
+ // Quill instance, persisted across renders to avoid re-initialization
30
+ const quillRef = React.useRef(null);
31
+ // Guards against update loops between Quill and the tree
32
+ const isUpdatingRef = React.useRef(false);
33
+ // Access tree content during render to establish observation.
34
+ // The HOC will automatically re-render when this content changes.
35
+ const currentText = root.fullString();
36
+ // Initialize Quill editor
37
+ React.useEffect(() => {
38
+ if (editorRef.current && !quillRef.current) {
39
+ const quill = new Quill(editorRef.current, {
40
+ placeholder: "Start typing...",
41
+ });
42
+ // Set initial content from tree (add trailing newline to match Quill's convention)
43
+ const initialText = root.fullString();
44
+ if (initialText.length > 0) {
45
+ const textWithNewline = initialText.endsWith("\n") ? initialText : `${initialText}\n`;
46
+ quill.setText(textWithNewline);
47
+ }
48
+ // Listen to local Quill changes
49
+ quill.on("text-change", (_delta, _oldDelta, source) => {
50
+ if (source === "user" && !isUpdatingRef.current) {
51
+ isUpdatingRef.current = true;
52
+ // Get plain text from Quill and preserve trailing newline
53
+ const newText = quill.getText();
54
+ // TODO: Consider using delta from Quill to compute a more minimal update,
55
+ // and maybe add a debugAssert that the delta actually gets the strings synchronized.
56
+ syncTextToTree(root, newText);
57
+ isUpdatingRef.current = false;
58
+ }
59
+ });
60
+ quillRef.current = quill;
61
+ }
62
+ // In React strict mode, effects run twice. The `!quillRef.current` check above
63
+ // makes the second call a no-op, preventing double-initialization of Quill.
64
+ // eslint-disable-next-line react-hooks/exhaustive-deps
65
+ }, []);
66
+ // Sync Quill when tree changes externally.
67
+ // We skip this if isUpdatingRef is true, meaning we caused the tree change ourselves
68
+ // via the text-change handler above - in that case Quill already has the correct content.
69
+ // No update is lost because isUpdatingRef is only true synchronously during our own
70
+ // handler execution, so Quill already reflects the change.
71
+ if (quillRef.current && !isUpdatingRef.current) {
72
+ const quillText = quillRef.current.getText();
73
+ // Normalize tree text to match Quill's trailing newline convention
74
+ const treeTextWithNewline = currentText.endsWith("\n") ? currentText : `${currentText}\n`;
75
+ // Only update if content actually differs (avoids cursor jump on local edits)
76
+ if (quillText !== treeTextWithNewline) {
77
+ isUpdatingRef.current = true;
78
+ const selection = quillRef.current.getSelection();
79
+ quillRef.current.setText(treeTextWithNewline);
80
+ if (selection) {
81
+ const length = quillRef.current.getLength();
82
+ const newPosition = Math.min(selection.index, length - 1);
83
+ quillRef.current.setSelection(newPosition, 0);
84
+ }
85
+ isUpdatingRef.current = false;
86
+ }
87
+ }
88
+ return (React.createElement("div", { className: "text-editor-container", style: { height: "100%", display: "flex", flexDirection: "column" } },
89
+ React.createElement("style", null, `
90
+ .ql-container {
91
+ height: 100%;
92
+ font-size: 14px;
93
+ }
94
+ .ql-editor {
95
+ height: 100%;
96
+ outline: none;
97
+ }
98
+ .ql-editor.ql-blank::before {
99
+ color: #999;
100
+ font-style: italic;
101
+ }
102
+ `),
103
+ React.createElement("h2", { style: { margin: "10px 0" } }, "Collaborative Text Editor"),
104
+ React.createElement("div", { ref: editorRef, style: {
105
+ flex: 1,
106
+ minHeight: "300px",
107
+ border: "1px solid #ccc",
108
+ borderRadius: "4px",
109
+ padding: "8px",
110
+ } })));
111
+ });
112
+ //# sourceMappingURL=quillView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quillView.js","sourceRoot":"","sources":["../../../src/text/plain/quillView.tsx"],"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 type { TextAsTree } from \"@fluidframework/tree/internal\";\nimport Quill from \"quill\";\nimport * as React from \"react\";\n\nimport type { PropTreeNode } from \"../../propNode.js\";\nimport { withMemoizedTreeObservations } from \"../../useTree.js\";\n\nimport { syncTextToTree } from \"./plainUtils.js\";\n\n/**\n * Props for the MainView component.\n * @input @internal\n */\nexport interface MainViewProps {\n\troot: PropTreeNode<TextAsTree.Tree>;\n}\n\n/**\n * A React component for plain text editing.\n * @remarks\n * Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and Quill for the UI.\n * @internal\n */\nexport const MainView: React.FC<MainViewProps> = ({ root }) => {\n\treturn <TextEditorView root={root} />;\n};\n\n/**\n * The text editor view component with Quill integration.\n * Uses TextAsTree for collaborative plain text storage.\n *\n * @remarks\n * This uses withMemoizedTreeObservations to automatically re-render\n * when the tree changes.\n */\nconst TextEditorView = withMemoizedTreeObservations(({ root }: { root: TextAsTree.Tree }) => {\n\t// DOM element where Quill will mount its editor\n\tconst editorRef = React.useRef<HTMLDivElement>(null);\n\t// Quill instance, persisted across renders to avoid re-initialization\n\tconst quillRef = React.useRef<Quill | null>(null);\n\t// Guards against update loops between Quill and the tree\n\tconst isUpdatingRef = React.useRef<boolean>(false);\n\n\t// Access tree content during render to establish observation.\n\t// The HOC will automatically re-render when this content changes.\n\tconst currentText = root.fullString();\n\n\t// Initialize Quill editor\n\tReact.useEffect(() => {\n\t\tif (editorRef.current && !quillRef.current) {\n\t\t\tconst quill = new Quill(editorRef.current, {\n\t\t\t\tplaceholder: \"Start typing...\",\n\t\t\t});\n\n\t\t\t// Set initial content from tree (add trailing newline to match Quill's convention)\n\t\t\tconst initialText = root.fullString();\n\t\t\tif (initialText.length > 0) {\n\t\t\t\tconst textWithNewline = initialText.endsWith(\"\\n\") ? initialText : `${initialText}\\n`;\n\t\t\t\tquill.setText(textWithNewline);\n\t\t\t}\n\n\t\t\t// Listen to local Quill changes\n\t\t\tquill.on(\"text-change\", (_delta, _oldDelta, source) => {\n\t\t\t\tif (source === \"user\" && !isUpdatingRef.current) {\n\t\t\t\t\tisUpdatingRef.current = true;\n\n\t\t\t\t\t// Get plain text from Quill and preserve trailing newline\n\t\t\t\t\tconst newText = quill.getText();\n\t\t\t\t\t// TODO: Consider using delta from Quill to compute a more minimal update,\n\t\t\t\t\t// and maybe add a debugAssert that the delta actually gets the strings synchronized.\n\t\t\t\t\tsyncTextToTree(root, newText);\n\n\t\t\t\t\tisUpdatingRef.current = false;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tquillRef.current = quill;\n\t\t}\n\t\t// In React strict mode, effects run twice. The `!quillRef.current` check above\n\t\t// makes the second call a no-op, preventing double-initialization of Quill.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, []);\n\n\t// Sync Quill when tree changes externally.\n\t// We skip this if isUpdatingRef is true, meaning we caused the tree change ourselves\n\t// via the text-change handler above - in that case Quill already has the correct content.\n\t// No update is lost because isUpdatingRef is only true synchronously during our own\n\t// handler execution, so Quill already reflects the change.\n\tif (quillRef.current && !isUpdatingRef.current) {\n\t\tconst quillText = quillRef.current.getText();\n\t\t// Normalize tree text to match Quill's trailing newline convention\n\t\tconst treeTextWithNewline = currentText.endsWith(\"\\n\") ? currentText : `${currentText}\\n`;\n\n\t\t// Only update if content actually differs (avoids cursor jump on local edits)\n\t\tif (quillText !== treeTextWithNewline) {\n\t\t\tisUpdatingRef.current = true;\n\n\t\t\tconst selection = quillRef.current.getSelection();\n\t\t\tquillRef.current.setText(treeTextWithNewline);\n\t\t\tif (selection) {\n\t\t\t\tconst length = quillRef.current.getLength();\n\t\t\t\tconst newPosition = Math.min(selection.index, length - 1);\n\t\t\t\tquillRef.current.setSelection(newPosition, 0);\n\t\t\t}\n\n\t\t\tisUpdatingRef.current = false;\n\t\t}\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"text-editor-container\"\n\t\t\tstyle={{ height: \"100%\", display: \"flex\", flexDirection: \"column\" }}\n\t\t>\n\t\t\t<style>\n\t\t\t\t{`\n\t\t\t\t\t.ql-container {\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t}\n\t\t\t\t\t.ql-editor {\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\toutline: none;\n\t\t\t\t\t}\n\t\t\t\t\t.ql-editor.ql-blank::before {\n\t\t\t\t\t\tcolor: #999;\n\t\t\t\t\t\tfont-style: italic;\n\t\t\t\t\t}\n\t\t\t\t`}\n\t\t\t</style>\n\t\t\t<h2 style={{ margin: \"10px 0\" }}>Collaborative Text Editor</h2>\n\t\t\t<div\n\t\t\t\tref={editorRef}\n\t\t\t\tstyle={{\n\t\t\t\t\tflex: 1,\n\t\t\t\t\tminHeight: \"300px\",\n\t\t\t\t\tborder: \"1px solid #ccc\",\n\t\t\t\t\tborderRadius: \"4px\",\n\t\t\t\t\tpadding: \"8px\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>\n\t);\n});\n"]}
@@ -0,0 +1,51 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import type { Listenable } from "@fluidframework/core-interfaces";
6
+ import { type TreeViewEvents } from "@fluidframework/tree";
7
+ /**
8
+ * Interface for undo/redo stack operations.
9
+ * @internal
10
+ */
11
+ export interface UndoRedo {
12
+ /**
13
+ * Reverts the most recent change. Only valid to call when {@link UndoRedo.canUndo} returns true.
14
+ * @throws Error if there is nothing to undo.
15
+ */
16
+ undo(): void;
17
+ /**
18
+ * Reapplies the most recently undone change. Only valid to call when {@link UndoRedo.canRedo} returns true.
19
+ * @throws Error if there is nothing to redo.
20
+ */
21
+ redo(): void;
22
+ dispose(): void;
23
+ canUndo(): boolean;
24
+ canRedo(): boolean;
25
+ /**
26
+ * Subscribe to state changes (when canUndo/canRedo may have changed).
27
+ * @param callback - Called when the undo/redo stack state changes
28
+ * @returns Unsubscribe function
29
+ */
30
+ onStateChange(callback: () => void): () => void;
31
+ }
32
+ /**
33
+ * Manages undo and redo stacks for a TreeView.
34
+ * Listens to commitApplied events and manages Revertible objects.
35
+ * @sealed @internal
36
+ */
37
+ export declare class UndoRedoStacks implements UndoRedo {
38
+ private readonly undoStack;
39
+ private readonly redoStack;
40
+ private readonly listeners;
41
+ private readonly unsubscribe;
42
+ constructor(events: Listenable<TreeViewEvents>);
43
+ undo(): void;
44
+ redo(): void;
45
+ dispose(): void;
46
+ canUndo(): boolean;
47
+ canRedo(): boolean;
48
+ onStateChange(callback: () => void): () => void;
49
+ private notifyListeners;
50
+ }
51
+ //# sourceMappingURL=undoRedo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"undoRedo.d.ts","sourceRoot":"","sources":["../src/undoRedo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAKN,KAAK,cAAc,EACnB,MAAM,sBAAsB,CAAC;AAE9B;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACxB;;;OAGG;IACH,IAAI,IAAI,IAAI,CAAC;IACb;;;OAGG;IACH,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,IAAI,CAAC;IAChB,OAAO,IAAI,OAAO,CAAC;IACnB,OAAO,IAAI,OAAO,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,qBAAa,cAAe,YAAW,QAAQ;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;gBAEtB,MAAM,EAAE,UAAU,CAAC,cAAc,CAAC;IAoB9C,IAAI,IAAI,IAAI;IASZ,IAAI,IAAI,IAAI;IASZ,OAAO,IAAI,IAAI;IASf,OAAO,IAAI,OAAO;IAIlB,OAAO,IAAI,OAAO;IAIlB,aAAa,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKtD,OAAO,CAAC,eAAe;CAKvB"}
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/react",
3
- "version": "2.90.0-378676",
3
+ "version": "2.90.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": "dist/index.js",
58
- "types": "./dist/public.d.ts",
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.90.0-378676",
83
- "@fluidframework/core-interfaces": "2.90.0-378676",
84
- "@fluidframework/datastore-definitions": "2.90.0-378676",
85
- "@fluidframework/fluid-static": "2.90.0-378676",
86
- "@fluidframework/runtime-definitions": "2.90.0-378676",
87
- "@fluidframework/shared-object-base": "2.90.0-378676",
88
- "@fluidframework/tree": "2.90.0-378676",
89
- "react": "^18.3.1"
66
+ "@fluidframework/aqueduct": "~2.90.0",
67
+ "@fluidframework/core-interfaces": "~2.90.0",
68
+ "@fluidframework/core-utils": "~2.90.0",
69
+ "@fluidframework/datastore-definitions": "~2.90.0",
70
+ "@fluidframework/fluid-static": "~2.90.0",
71
+ "@fluidframework/runtime-definitions": "~2.90.0",
72
+ "@fluidframework/shared-object-base": "~2.90.0",
73
+ "@fluidframework/tree": "~2.90.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
81
  "@biomejs/biome": "~1.9.3",
94
- "@fluid-internal/mocha-test-setup": "2.90.0-378676",
82
+ "@fluid-internal/mocha-test-setup": "~2.90.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.90.0-378676",
99
- "@fluidframework/tinylicious-client": "2.90.0-378676",
86
+ "@fluidframework/eslint-config-fluid": "~2.90.0",
87
+ "@fluidframework/tinylicious-client": "~2.90.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,9 @@
110
99
  "eslint-config-prettier": "~10.1.8",
111
100
  "global-jsdom": "^26.0.0",
112
101
  "jiti": "^2.6.1",
113
- "mocha": "^10.8.2",
102
+ "mocha": "^11.7.5",
114
103
  "mocha-multi-reporters": "^1.5.1",
115
- "rimraf": "^6.1.2",
104
+ "rimraf": "^6.1.3",
116
105
  "typescript": "~5.4.5"
117
106
  },
118
107
  "typeValidation": {
@@ -120,23 +109,17 @@
120
109
  },
121
110
  "scripts": {
122
111
  "api": "fluid-build . --task api",
123
- "api-extractor:commonjs": "flub generate entrypoints --outDir ./dist",
124
112
  "api-extractor:esnext": "flub generate entrypoints --outDir ./lib",
125
113
  "build": "fluid-build . --task build",
126
- "build:commonjs": "fluid-build . --task commonjs",
127
114
  "build:compile": "fluid-build . --task compile",
128
115
  "build:docs": "api-extractor run --local",
129
116
  "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 && npm run build:test:cjs",
131
- "build:test:cjs": "fluid-tsc commonjs --project ./src/test/tsconfig.cjs.json",
117
+ "build:test": "npm run build:test:esm",
132
118
  "build:test:esm": "tsc --project ./src/test/tsconfig.json",
133
- "check:are-the-types-wrong": "attw --pack . --entrypoints .",
119
+ "check:are-the-types-wrong": "attw --pack . --entrypoints . --profile esm-only",
134
120
  "check:biome": "biome check .",
135
121
  "check:exports": "concurrently \"npm:check:exports:*\"",
136
122
  "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
123
  "check:exports:esm:alpha": "api-extractor run --config api-extractor/api-extractor-lint-alpha.esm.json",
141
124
  "check:exports:esm:beta": "api-extractor run --config api-extractor/api-extractor-lint-beta.esm.json",
142
125
  "check:exports:esm:public": "api-extractor run --config api-extractor/api-extractor-lint-public.esm.json",
@@ -149,13 +132,11 @@
149
132
  "format:biome": "biome check . --write",
150
133
  "lint": "fluid-build . --task lint",
151
134
  "lint:fix": "fluid-build . --task eslint:fix --task format",
152
- "pack:tests": "tar -cf ./react.test-files.tar ./src/test ./dist/test ./lib/test",
135
+ "pack:tests": "tar -cf ./react.test-files.tar ./src/test ./lib/test",
153
136
  "test": "npm run test:mocha",
154
137
  "test:coverage": "c8 npm test",
155
- "test:mocha": "npm run test:mocha:esm && echo skipping cjs to avoid overhead - npm run test:mocha:cjs",
156
- "test:mocha:cjs": "cross-env FLUID_TEST_MODULE_SYSTEM=CJS mocha",
138
+ "test:mocha": "npm run test:mocha:esm",
157
139
  "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"
140
+ "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha"
160
141
  }
161
142
  }
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-interface, @typescript-eslint/no-empty-object-type
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
  /**
@@ -0,0 +1,11 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ export {
7
+ FormattedTextAsTree,
8
+ FormattedMainView,
9
+ type FormattedMainViewProps,
10
+ type FormattedEditorHandle,
11
+ } from "./quillFormattedView.js";