@multiplatform.one/rich-text 7.7.6 → 7.8.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 (121) hide show
  1. package/dist/cjs/RichEditor.native.js +18 -7
  2. package/dist/cjs/RichEditor.native.js.map +1 -1
  3. package/dist/cjs/RichTextEditor/index.cjs +2 -0
  4. package/dist/cjs/RichTextEditor/index.native.js +2 -0
  5. package/dist/cjs/RichTextEditor/index.native.js.map +1 -1
  6. package/dist/cjs/editor/TentapEditor.native.js +29 -39
  7. package/dist/cjs/editor/TentapEditor.native.js.map +1 -1
  8. package/dist/cjs/editor/TiptapEditor.cjs +6 -2
  9. package/dist/cjs/editor/TiptapEditor.native.js +8 -4
  10. package/dist/cjs/editor/TiptapEditor.native.js.map +1 -1
  11. package/dist/cjs/editor/blockAnchor.cjs +3 -2
  12. package/dist/cjs/editor/blockAnchor.native.js +4 -2
  13. package/dist/cjs/editor/blockAnchor.native.js.map +1 -1
  14. package/dist/cjs/editor/nativeEditorCSS.cjs +30 -0
  15. package/dist/cjs/editor/nativeEditorCSS.native.js +33 -0
  16. package/dist/cjs/editor/nativeEditorCSS.native.js.map +1 -0
  17. package/dist/cjs/toolbar/HeadingPicker.cjs +7 -0
  18. package/dist/cjs/toolbar/HeadingPicker.native.js +13 -17
  19. package/dist/cjs/toolbar/HeadingPicker.native.js.map +1 -1
  20. package/dist/cjs/toolbar/ListPicker.cjs +7 -0
  21. package/dist/cjs/toolbar/ListPicker.native.js +12 -11
  22. package/dist/cjs/toolbar/ListPicker.native.js.map +1 -1
  23. package/dist/cjs/toolbar/MoreDropdown.cjs +7 -0
  24. package/dist/cjs/toolbar/MoreDropdown.native.js +12 -11
  25. package/dist/cjs/toolbar/MoreDropdown.native.js.map +1 -1
  26. package/dist/esm/RichEditor.native.js +19 -8
  27. package/dist/esm/RichEditor.native.js.map +1 -1
  28. package/dist/esm/RichTextEditor/index.mjs +2 -0
  29. package/dist/esm/RichTextEditor/index.mjs.map +1 -1
  30. package/dist/esm/RichTextEditor/index.native.js +2 -0
  31. package/dist/esm/RichTextEditor/index.native.js.map +1 -1
  32. package/dist/esm/editor/TentapEditor.native.js +29 -39
  33. package/dist/esm/editor/TentapEditor.native.js.map +1 -1
  34. package/dist/esm/editor/TiptapEditor.mjs +6 -2
  35. package/dist/esm/editor/TiptapEditor.mjs.map +1 -1
  36. package/dist/esm/editor/TiptapEditor.native.js +8 -4
  37. package/dist/esm/editor/TiptapEditor.native.js.map +1 -1
  38. package/dist/esm/editor/blockAnchor.mjs +3 -2
  39. package/dist/esm/editor/blockAnchor.mjs.map +1 -1
  40. package/dist/esm/editor/blockAnchor.native.js +4 -2
  41. package/dist/esm/editor/blockAnchor.native.js.map +1 -1
  42. package/dist/esm/editor/nativeEditorCSS.mjs +5 -0
  43. package/dist/esm/editor/nativeEditorCSS.mjs.map +1 -0
  44. package/dist/esm/editor/nativeEditorCSS.native.js +5 -0
  45. package/dist/esm/editor/nativeEditorCSS.native.js.map +1 -0
  46. package/dist/esm/toolbar/HeadingPicker.mjs +7 -0
  47. package/dist/esm/toolbar/HeadingPicker.mjs.map +1 -1
  48. package/dist/esm/toolbar/HeadingPicker.native.js +13 -17
  49. package/dist/esm/toolbar/HeadingPicker.native.js.map +1 -1
  50. package/dist/esm/toolbar/ListPicker.mjs +7 -0
  51. package/dist/esm/toolbar/ListPicker.mjs.map +1 -1
  52. package/dist/esm/toolbar/ListPicker.native.js +12 -11
  53. package/dist/esm/toolbar/ListPicker.native.js.map +1 -1
  54. package/dist/esm/toolbar/MoreDropdown.mjs +7 -0
  55. package/dist/esm/toolbar/MoreDropdown.mjs.map +1 -1
  56. package/dist/esm/toolbar/MoreDropdown.native.js +12 -11
  57. package/dist/esm/toolbar/MoreDropdown.native.js.map +1 -1
  58. package/dist/jsx/RichEditor.native.js +18 -7
  59. package/dist/jsx/RichEditor.native.js.map +1 -1
  60. package/dist/jsx/RichTextEditor/index.mjs +2 -0
  61. package/dist/jsx/RichTextEditor/index.mjs.map +1 -1
  62. package/dist/jsx/RichTextEditor/index.native.js +2 -0
  63. package/dist/jsx/RichTextEditor/index.native.js.map +1 -1
  64. package/dist/jsx/editor/TentapEditor.native.js +29 -39
  65. package/dist/jsx/editor/TentapEditor.native.js.map +1 -1
  66. package/dist/jsx/editor/TiptapEditor.mjs +6 -2
  67. package/dist/jsx/editor/TiptapEditor.mjs.map +1 -1
  68. package/dist/jsx/editor/TiptapEditor.native.js +8 -4
  69. package/dist/jsx/editor/TiptapEditor.native.js.map +1 -1
  70. package/dist/jsx/editor/blockAnchor.mjs +3 -2
  71. package/dist/jsx/editor/blockAnchor.mjs.map +1 -1
  72. package/dist/jsx/editor/blockAnchor.native.js +4 -2
  73. package/dist/jsx/editor/blockAnchor.native.js.map +1 -1
  74. package/dist/jsx/editor/nativeEditorCSS.mjs +5 -0
  75. package/dist/jsx/editor/nativeEditorCSS.mjs.map +1 -0
  76. package/dist/jsx/editor/nativeEditorCSS.native.js +33 -0
  77. package/dist/jsx/editor/nativeEditorCSS.native.js.map +1 -0
  78. package/dist/jsx/toolbar/HeadingPicker.mjs +7 -0
  79. package/dist/jsx/toolbar/HeadingPicker.mjs.map +1 -1
  80. package/dist/jsx/toolbar/HeadingPicker.native.js +13 -17
  81. package/dist/jsx/toolbar/HeadingPicker.native.js.map +1 -1
  82. package/dist/jsx/toolbar/ListPicker.mjs +7 -0
  83. package/dist/jsx/toolbar/ListPicker.mjs.map +1 -1
  84. package/dist/jsx/toolbar/ListPicker.native.js +12 -11
  85. package/dist/jsx/toolbar/ListPicker.native.js.map +1 -1
  86. package/dist/jsx/toolbar/MoreDropdown.mjs +7 -0
  87. package/dist/jsx/toolbar/MoreDropdown.mjs.map +1 -1
  88. package/dist/jsx/toolbar/MoreDropdown.native.js +12 -11
  89. package/dist/jsx/toolbar/MoreDropdown.native.js.map +1 -1
  90. package/package.json +10 -8
  91. package/src/RichEditor.native.spec.tsx +158 -0
  92. package/src/RichEditor.native.tsx +28 -7
  93. package/src/RichTextEditor/index.tsx +4 -2
  94. package/src/RichTextEditor/richTextEditor.spec.tsx +40 -6
  95. package/src/editor/SlashCommands.spec.tsx +3 -1
  96. package/src/editor/TentapEditor.native.spec.tsx +120 -0
  97. package/src/editor/TentapEditor.native.tsx +40 -54
  98. package/src/editor/TiptapEditor.tsx +2 -2
  99. package/src/editor/blockAnchor.ts +3 -2
  100. package/src/editor/nativeEditorCSS.spec.ts +75 -0
  101. package/src/editor/nativeEditorCSS.ts +4 -0
  102. package/src/integration.spec.tsx +0 -23
  103. package/src/richEditor.spec.tsx +0 -23
  104. package/src/toolbar/HeadingPicker.tsx +14 -15
  105. package/src/toolbar/ListPicker.tsx +13 -9
  106. package/src/toolbar/MoreDropdown.tsx +13 -9
  107. package/src/toolbar/bubbleToolbar.spec.tsx +2 -1
  108. package/src/toolbar/fixedToolbarStories.spec.tsx +4 -2
  109. package/src/toolbar/statusBarStories.spec.tsx +4 -2
  110. package/src/toolbar/toolbarButtonStories.spec.tsx +4 -2
  111. package/src/toolbar/toolbarGroupStories.spec.tsx +4 -2
  112. package/types/RichEditor.native.d.ts.map +1 -1
  113. package/types/RichTextEditor/index.d.ts +1 -1
  114. package/types/RichTextEditor/index.d.ts.map +1 -1
  115. package/types/editor/TentapEditor.native.d.ts.map +1 -1
  116. package/types/editor/blockAnchor.d.ts.map +1 -1
  117. package/types/editor/nativeEditorCSS.d.ts +3 -0
  118. package/types/editor/nativeEditorCSS.d.ts.map +1 -0
  119. package/types/toolbar/HeadingPicker.d.ts.map +1 -1
  120. package/types/toolbar/ListPicker.d.ts.map +1 -1
  121. package/types/toolbar/MoreDropdown.d.ts.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","MoreDropdown_exports","MoreDropdown","module","exports","import_jsx_runtime","require","import_react","import_react2","import_components","import_capabilities","import_ToolbarButton","import_icons","import_shortcuts","import_t","INLINE_ITEMS","label","SHORTCUTS","subscript","command","icon","SubscriptIcon","isActive","s","isSubscript","label2","superscript","SuperscriptIcon","isActive2","isSuperscript","ALIGN_ITEMS","label3","alignLeft","AlignLeftIcon","isActive3","textAlign","label4","alignCenter","AlignCenterIcon","isActive4","label5","alignRight","AlignRightIcon","isActive5","label6","alignJustify","AlignJustifyIcon","isActive6","BLOCK_ITEMS","label7","horizontalRule","HorizontalRuleIcon","label8","clearFormatting","ClearFormattingIcon","MoreIcon","jsx","DotsThreeIcon","size","weight","ItemSection","param","items","state","onSelect","Fragment","children","map","item","_item_isActive","Icon","label9","DropdownMenu","Item","closeOnSelect","backgroundColor","onSelect1","payload"],"sources":["../../../src/toolbar/MoreDropdown.tsx"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","MoreDropdown_exports","MoreDropdown","module","exports","import_jsx_runtime","require","import_react","import_react2","import_components","import_capabilities","import_ToolbarButton","import_icons","import_shortcuts","import_t","INLINE_ITEMS","label","SHORTCUTS","subscript","command","icon","SubscriptIcon","isActive","s","isSubscript","label2","superscript","SuperscriptIcon","isActive2","isSuperscript","ALIGN_ITEMS","label3","alignLeft","AlignLeftIcon","isActive3","textAlign","label4","alignCenter","AlignCenterIcon","isActive4","label5","alignRight","AlignRightIcon","isActive5","label6","alignJustify","AlignJustifyIcon","isActive6","BLOCK_ITEMS","label7","horizontalRule","HorizontalRuleIcon","label8","clearFormatting","ClearFormattingIcon","MoreIcon","jsx","DotsThreeIcon","size","weight","ItemSection","param","items","state","onSelect","Fragment","children","map","item","_item_isActive","Icon","label9","DropdownMenu","Item","closeOnSelect","backgroundColor","onSelect1","payload","editorRef","capabilities","disabled","open","setOpen","useState","selectedRef","useRef","handleSelect","handleSelect2","_editorRef_current","current","executeCommand","inlineItems","filter"],"sources":["../../../src/toolbar/MoreDropdown.tsx"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/rich-text",
3
- "version": "7.7.6",
3
+ "version": "7.8.0",
4
4
  "description": "Cross-platform rich text editor with markdown support and syntax highlighting",
5
5
  "keywords": [
6
6
  "cross-platform",
@@ -60,6 +60,7 @@
60
60
  "@phosphor-icons/react": "^2.1.10",
61
61
  "@tamagui/linear-gradient": "2.7.6",
62
62
  "@tanstack/react-form": "^1.29.1",
63
+ "@tiptap/core": "^3.30.0",
63
64
  "@tiptap/extension-character-count": "^3.30.0",
64
65
  "@tiptap/extension-code-block-lowlight": "^3.30.0",
65
66
  "@tiptap/extension-color": "^3.30.0",
@@ -93,13 +94,14 @@
93
94
  "marked": "^17.0.6",
94
95
  "tamagui": "2.7.6",
95
96
  "turndown": "^7.2.4",
96
- "@multiplatform.one/components": "7.7.6",
97
- "@multiplatform.one/forms": "7.7.6",
98
- "@multiplatform.one/markdown": "7.7.6",
99
- "@multiplatform.one/theme": "7.7.6"
97
+ "@multiplatform.one/components": "7.8.0",
98
+ "@multiplatform.one/theme": "7.8.0",
99
+ "@multiplatform.one/forms": "7.8.0",
100
+ "@multiplatform.one/markdown": "7.8.0"
100
101
  },
101
102
  "devDependencies": {
102
103
  "@tamagui/build": "2.7.6",
104
+ "@testing-library/jest-dom": "^6.9.1",
103
105
  "@testing-library/react": "^16.3.2",
104
106
  "@types/katex": "^0.16.7",
105
107
  "@vitejs/plugin-react": "^6.0.1",
@@ -108,9 +110,9 @@
108
110
  "react": "19.2.5",
109
111
  "react-dom": "19.2.5",
110
112
  "vitest": "^4.1.5",
111
- "@multiplatform.one/config": "7.7.6",
112
- "@multiplatform.one/storybook": "7.7.6",
113
- "@multiplatform.one/test-utils": "7.7.6"
113
+ "@multiplatform.one/test-utils": "7.8.0",
114
+ "@multiplatform.one/storybook": "7.8.0",
115
+ "@multiplatform.one/config": "7.8.0"
114
116
  },
115
117
  "peerDependencies": {
116
118
  "i18next": "^25.0.0",
@@ -0,0 +1,158 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { runInNewContext } from "node:vm";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import * as React from "react";
6
+ import * as jsxRuntime from "react/jsx-runtime";
7
+ import { cleanup, fireEvent, render, screen } from "@testing-library/react";
8
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
9
+ import { fontCategoryStacks } from "@multiplatform.one/theme";
10
+ import { JsxEmit, ModuleKind, transpileModule } from "typescript";
11
+
12
+ let bodyFamily: string | undefined = "Inter";
13
+ let headingFamily: string | undefined = "Inter";
14
+ let bodyToken = "$body";
15
+ let headingToken = "$heading";
16
+ const Container = ({ children }: any) => <div>{children}</div>;
17
+ const Pressable = ({ children, onPress, accessibilityLabel }: any) => (
18
+ <button aria-label={accessibilityLabel} onClick={onPress}>
19
+ {children}
20
+ </button>
21
+ );
22
+ const Tentap = React.forwardRef((_props: any, _ref) => (
23
+ <div
24
+ data-testid="tentap"
25
+ data-body={_props.bodyFontFamily}
26
+ data-heading={_props.headingFontFamily}
27
+ />
28
+ ));
29
+
30
+ // RichEditor deliberately uses a guarded native require for its optional
31
+ // WebView backend. Execute the real wrapper with that boundary substituted;
32
+ // importing through the web bundler would turn it into the unavailable branch.
33
+ const nativeExports: Record<string, any> = {};
34
+ const modules: Record<string, any> = {
35
+ react: React,
36
+ "react/jsx-runtime": jsxRuntime,
37
+ "react-native": {
38
+ KeyboardAvoidingView: Container,
39
+ SafeAreaView: Container,
40
+ Pressable,
41
+ Modal: ({ visible, children }: any) => (visible ? <div>{children}</div> : null),
42
+ Platform: { OS: "ios" },
43
+ StyleSheet: { create: (styles: any) => styles },
44
+ },
45
+ tamagui: {
46
+ Text: Container,
47
+ View: Container,
48
+ XStack: Container,
49
+ YStack: Container,
50
+ getConfig: () => ({
51
+ fonts: {
52
+ [bodyToken.slice(1)]: { family: bodyFamily },
53
+ [headingToken.slice(1)]: { family: headingFamily },
54
+ },
55
+ }),
56
+ withStaticProperties: Object.assign,
57
+ },
58
+ "@multiplatform.one/theme": {
59
+ fontCategoryStacks,
60
+ useGlyphColor: () => "black",
61
+ sizeRecipeForToken: () => ({ iconSize: 18 }),
62
+ useResolvedKnobs: () => ({
63
+ knobProps: {
64
+ body: { fontFamily: bodyToken },
65
+ heading: { fontFamily: headingToken },
66
+ borderRadius: {},
67
+ panelPadding: { padding: 12 },
68
+ },
69
+ }),
70
+ },
71
+ "@multiplatform.one/components": { Pencil: () => null },
72
+ "@multiplatform.one/forms": { Button: Pressable, clampRadiusForLargeComponent: () => 4 },
73
+ "./types": { defaultEditorState: {} },
74
+ "./context": { RichEditorContext: { Provider: Container } },
75
+ "./styled": { EditorFrame: Container },
76
+ "./toolbar/FixedToolbar": { FixedToolbar: () => null },
77
+ "./preview": { parseHtmlPreview: () => ({ blocks: [], truncated: false }) },
78
+ "./editor/markdown": { htmlToMarkdown: (v: string) => v, markdownToHtml: (v: string) => v },
79
+ "./shared/t": { t: (v: string) => v },
80
+ "./editor/TentapEditor.native": { TentapEditor: Tentap },
81
+ };
82
+ runInNewContext(
83
+ transpileModule(
84
+ readFileSync(
85
+ path.join(path.dirname(fileURLToPath(import.meta.url)), "RichEditor.native.tsx"),
86
+ "utf8",
87
+ ),
88
+ {
89
+ compilerOptions: { module: ModuleKind.CommonJS, jsx: JsxEmit.ReactJSX },
90
+ },
91
+ ).outputText,
92
+ {
93
+ exports: nativeExports,
94
+ require: (name: string) => {
95
+ if (!(name in modules)) throw new Error(`Unexpected native wrapper import ${name}`);
96
+ return modules[name];
97
+ },
98
+ },
99
+ );
100
+ const RichEditor = nativeExports.RichEditor;
101
+ afterEach(cleanup);
102
+ beforeEach(() => {
103
+ bodyFamily = "Inter";
104
+ headingFamily = "Inter";
105
+ bodyToken = "$body";
106
+ headingToken = "$heading";
107
+ });
108
+
109
+ describe("native RichEditor font propagation", () => {
110
+ it.each([false, true])(
111
+ "passes resolved families to the editing document, inline=%s",
112
+ (inlineEditing) => {
113
+ render(<RichEditor inlineEditing={inlineEditing} initialContent="<p>Saved note</p>" />);
114
+ if (!inlineEditing) fireEvent.click(screen.getByRole("button", { name: "Edit rich text" }));
115
+ const editor = screen.getByTestId("tentap");
116
+ expect(editor.getAttribute("data-body")).toBe(
117
+ `${bodyFamily}, ${fontCategoryStacks["sans-serif"]}`,
118
+ );
119
+ expect(editor.getAttribute("data-heading")).toBe(
120
+ `${headingFamily}, ${fontCategoryStacks["sans-serif"]}`,
121
+ );
122
+ },
123
+ );
124
+ });
125
+
126
+ describe("native editor CSS font fallbacks", () => {
127
+ it("keeps configured families first and uses the selected category fallback", () => {
128
+ bodyFamily = "Custom Serif";
129
+ headingFamily = "Custom Mono";
130
+ bodyToken = "$serif";
131
+ headingToken = "$mono";
132
+ render(<RichEditor defaultFullscreen initialContent="<p>Saved note</p>" />);
133
+ const editor = screen.getByTestId("tentap");
134
+ expect(editor.getAttribute("data-body")).toBe(`${bodyFamily}, ${fontCategoryStacks.serif}`);
135
+ expect(editor.getAttribute("data-heading")).toBe(
136
+ `${headingFamily}, ${fontCategoryStacks.mono}`,
137
+ );
138
+ });
139
+
140
+ it("preserves explicit CSS stacks and intentional generic families", () => {
141
+ bodyFamily = "Georgia, serif";
142
+ headingFamily = "system-ui";
143
+ render(<RichEditor defaultFullscreen />);
144
+ const editor = screen.getByTestId("tentap");
145
+ expect(editor.getAttribute("data-body")).toBe(bodyFamily);
146
+ expect(editor.getAttribute("data-heading")).toBe(headingFamily);
147
+ });
148
+
149
+ it("uses the canonical fallback when no configured font exists", () => {
150
+ bodyFamily = undefined;
151
+ headingFamily = undefined;
152
+ headingToken = "$mono";
153
+ render(<RichEditor defaultFullscreen />);
154
+ const editor = screen.getByTestId("tentap");
155
+ expect(editor.getAttribute("data-body")).toBe(fontCategoryStacks["sans-serif"]);
156
+ expect(editor.getAttribute("data-heading")).toBe(fontCategoryStacks.mono);
157
+ });
158
+ });
@@ -34,7 +34,13 @@ import {
34
34
  } from "react-native";
35
35
  import type { SizeTokens } from "tamagui";
36
36
  import { Text, View, XStack, YStack, getConfig, withStaticProperties } from "tamagui";
37
- import { useResolvedKnobs } from "@multiplatform.one/theme";
37
+ import {
38
+ fontCategoryStacks,
39
+ sizeRecipeForToken,
40
+ useGlyphColor,
41
+ useResolvedKnobs,
42
+ } from "@multiplatform.one/theme";
43
+ import { Pencil } from "@multiplatform.one/components";
38
44
  import { Button, clampRadiusForLargeComponent } from "@multiplatform.one/forms";
39
45
  import type { EditorState, RichEditorProps, RichEditorRef, ToolbarSection } from "./types";
40
46
  import { defaultEditorState } from "./types";
@@ -265,6 +271,7 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
265
271
  size,
266
272
  compact,
267
273
  });
274
+ const editGlyphColor = useGlyphColor("$color11");
268
275
 
269
276
  // Same resolver as the web RichEditor: the knob names a registered font
270
277
  // token ($body / $heading); the WebView needs its concrete CSS family
@@ -272,9 +279,22 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
272
279
  const { bodyFontFamily, headingFontFamily } = useMemo(() => {
273
280
  const fonts = getConfig()?.fonts;
274
281
  const resolve = (token?: string) => {
275
- if (!token) return undefined;
276
- const key = token.startsWith("$") ? token.slice(1) : token;
277
- return fonts?.[key]?.family as string | undefined;
282
+ const key = token?.replace(/^\$/, "") ?? "body";
283
+ const configured = fonts?.[key]?.family;
284
+ const family = typeof configured === "string" ? configured.trim() : undefined;
285
+ // RN's bare Inter name is not a font installed inside WKWebView.
286
+ // Preserve explicit CSS stacks; otherwise use the shared category fallback.
287
+ if (
288
+ family &&
289
+ /(?:^|,)\s*(?:serif|sans-serif|monospace|cursive|fantasy|system-ui|ui-serif|ui-sans-serif|ui-monospace|ui-rounded)\s*$/i.test(
290
+ family,
291
+ )
292
+ )
293
+ return family;
294
+ const fallback = Object.hasOwn(fontCategoryStacks, key)
295
+ ? fontCategoryStacks[key as keyof typeof fontCategoryStacks]
296
+ : fontCategoryStacks["sans-serif"];
297
+ return family ? `${family}, ${fallback}` : fallback;
278
298
  };
279
299
  return {
280
300
  bodyFontFamily: resolve(knobProps.body?.fontFamily),
@@ -381,6 +401,7 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
381
401
  const cappedRadius = clampRadiusForLargeComponent(knobProps.borderRadius.borderRadius, 4);
382
402
  const disabled = !editable;
383
403
  const sizeToken = (knobProps.sizeToken ?? "$true") as any;
404
+ const editGlyphSize = sizeRecipeForToken(sizeToken, { touch: true }).iconSize;
384
405
 
385
406
  const preview = useMemo(() => parseHtmlPreview(committed.html, 6), [committed.html]);
386
407
  const previewEmpty = preview.blocks.length === 0;
@@ -484,9 +505,7 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
484
505
  </YStack>
485
506
  {!disabled && (
486
507
  <View position="absolute" top="$2.5" right="$2.5">
487
- <Text color="$color10" fontSize={15}>
488
- {"✎"}
489
- </Text>
508
+ <Pencil color={editGlyphColor} size={editGlyphSize} accessible={false} />
490
509
  </View>
491
510
  )}
492
511
  </EditorFrame>
@@ -538,6 +557,8 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
538
557
  {TentapEditor ? (
539
558
  <TentapEditor
540
559
  ref={internalRef}
560
+ bodyFontFamily={bodyFontFamily}
561
+ headingFontFamily={headingFontFamily}
541
562
  {...editorProps}
542
563
  initialContent={committed.html}
543
564
  initialJSON={committed.json ?? undefined}
@@ -56,9 +56,11 @@ function applyExternalContent(
56
56
  ) {
57
57
  if (!editor) return;
58
58
  if (capabilities === "markdown") {
59
+ if (isWeb && editor.getMarkdown() === next) return;
59
60
  editor.setMarkdown(next);
60
61
  return;
61
62
  }
63
+ if (isWeb && editor.getHTML() === next) return;
62
64
  editor.setHTML(next);
63
65
  }
64
66
 
@@ -135,12 +137,12 @@ export type RichTextEditorProps<
135
137
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
136
138
  > = Omit<
137
139
  FormFieldProps<TParentData, TName, TFieldValidator, TFormValidator, TData>,
138
- "children" | "field"
140
+ "children" | "field" | "onChange"
139
141
  > &
140
142
  Partial<
141
143
  Omit<
142
144
  FieldComponentProps<TParentData, TName, TFieldValidator, TFormValidator, TData>,
143
- "children"
145
+ "children" | "onChange"
144
146
  >
145
147
  > & {
146
148
  value?: string;
@@ -1,4 +1,4 @@
1
- import { forwardRef, useImperativeHandle } from "react";
1
+ import { forwardRef, useImperativeHandle, useRef, useState } from "react";
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
 
4
4
  const editorSpies = vi.hoisted(() => ({
@@ -92,14 +92,21 @@ vi.mock("@multiplatform.one/forms", async () => {
92
92
 
93
93
  vi.mock("../RichEditor", () => ({
94
94
  RichEditor: forwardRef((props: any, ref) => {
95
+ const content = useRef(props.initialContent ?? "");
95
96
  useImperativeHandle(ref, () => ({
96
- setHTML: editorSpies.setHTML,
97
- setMarkdown: editorSpies.setMarkdown,
97
+ setHTML: (next: string) => {
98
+ content.current = next;
99
+ editorSpies.setHTML(next);
100
+ },
101
+ setMarkdown: (next: string) => {
102
+ content.current = next;
103
+ editorSpies.setMarkdown(next);
104
+ },
98
105
  focus: editorSpies.focus,
99
106
  blur: vi.fn(),
100
- getHTML: () => props.initialContent ?? "",
107
+ getHTML: () => content.current,
101
108
  getJSON: () => null,
102
- getMarkdown: () => "",
109
+ getMarkdown: () => content.current,
103
110
  getState: () => ({}),
104
111
  executeCommand: vi.fn(),
105
112
  }));
@@ -111,7 +118,18 @@ vi.mock("../RichEditor", () => ({
111
118
  data-initial={props.initialContent}
112
119
  data-bubble={props.showBubbleToolbar ? "true" : undefined}
113
120
  data-sections={props.sections ? JSON.stringify(props.sections) : undefined}
114
- />
121
+ >
122
+ <button
123
+ type="button"
124
+ data-testid="simulate-edit"
125
+ onClick={() => {
126
+ content.current = props.capabilities === "markdown" ? "edited" : "<p>edited</p>";
127
+ props.onChange(content.current);
128
+ }}
129
+ >
130
+ edit
131
+ </button>
132
+ </div>
115
133
  );
116
134
  }),
117
135
  }));
@@ -198,6 +216,22 @@ describe("RichTextEditor form-integrated field", () => {
198
216
  expect(editorSpies.setMarkdown).toHaveBeenCalledWith("world");
199
217
  });
200
218
 
219
+ it.each(["full", "markdown"] as const)(
220
+ "does not replace a %s document when its user edit is echoed",
221
+ async (capabilities) => {
222
+ editorSpies.setHTML.mockClear();
223
+ editorSpies.setMarkdown.mockClear();
224
+ function Controlled() {
225
+ const [value, setValue] = useState("");
226
+ return <RichTextEditor value={value} onChange={setValue} capabilities={capabilities} />;
227
+ }
228
+ const { getByTestId } = renderWithProviders(<Controlled />);
229
+ await act(async () => getByTestId("simulate-edit").click());
230
+ expect(editorSpies.setHTML).not.toHaveBeenCalled();
231
+ expect(editorSpies.setMarkdown).not.toHaveBeenCalled();
232
+ },
233
+ );
234
+
201
235
  it("clears the editor when the form field value is reset", async () => {
202
236
  editorSpies.setHTML.mockClear();
203
237
  const { getByTestId } = renderWithProviders(
@@ -1,6 +1,8 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
- const suggestion = vi.hoisted(() => ({ fn: vi.fn(() => ({ props: {} })) }));
3
+ const suggestion = vi.hoisted(() => ({
4
+ fn: vi.fn((_options: Record<string, unknown>) => ({ props: {} })),
5
+ }));
4
6
 
5
7
  vi.mock("@tiptap/suggestion", () => ({ default: suggestion.fn }));
6
8
 
@@ -0,0 +1,120 @@
1
+ import { act, cleanup, render } from "@testing-library/react";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { TentapEditor } from "./TentapEditor.native";
4
+
5
+ const state = vi.hoisted(() => ({
6
+ props: {} as any,
7
+ scheme: "light",
8
+ theme: { color12: { val: "black" } },
9
+ editor: {
10
+ injectCSS: vi.fn(),
11
+ setContent: vi.fn(),
12
+ _subscribeToEditorStateUpdate: vi.fn(() => vi.fn()),
13
+ getEditorState: vi.fn(() => ({})),
14
+ },
15
+ }));
16
+
17
+ vi.mock("react-native", () => ({
18
+ StyleSheet: { create: (styles: any) => styles },
19
+ useColorScheme: () => "light",
20
+ }));
21
+ vi.mock("tamagui", () => ({
22
+ View: ({ children }: any) => <div>{children}</div>,
23
+ useTheme: () => state.theme,
24
+ useThemeName: () => state.scheme,
25
+ }));
26
+ vi.mock("../context", () => ({
27
+ RichEditorContext: { useStyledContext: () => ({ disabled: false }) },
28
+ }));
29
+ vi.mock("../shared/t", () => ({ t: (value: string) => value }));
30
+ vi.mock("@10play/tentap-editor", () => ({
31
+ RichText: (props: any) => {
32
+ state.props = props;
33
+ return <div data-testid="native-webview" />;
34
+ },
35
+ useEditorBridge: () => state.editor,
36
+ TenTapStartKit: [],
37
+ UnderlineBridge: {},
38
+ LinkBridge: {},
39
+ ColorBridge: {},
40
+ HighlightBridge: {},
41
+ ImageBridge: {},
42
+ TaskListBridge: {},
43
+ PlaceholderBridge: { configureExtension: () => ({}) },
44
+ darkEditorCss: "body { background: black; color: white; }",
45
+ darkEditorTheme: {},
46
+ }));
47
+
48
+ beforeEach(() => {
49
+ vi.useFakeTimers();
50
+ vi.clearAllMocks();
51
+ state.scheme = "light";
52
+ state.theme = { color12: { val: "black" } };
53
+ });
54
+ afterEach(() => {
55
+ cleanup();
56
+ vi.useRealTimers();
57
+ });
58
+
59
+ describe("Tentap native stylesheet lifecycle", () => {
60
+ it("waits for load, reapplies after the iOS replacement document, and updates live styling", () => {
61
+ const view = render(
62
+ <TentapEditor initialContent="<p>Saved note</p>" bodyFontFamily="system-ui" />,
63
+ );
64
+ act(() => {
65
+ vi.advanceTimersByTime(1000);
66
+ });
67
+ expect(state.editor.injectCSS).not.toHaveBeenCalled();
68
+
69
+ // Installed RichText calls its supplied props.onLoad after its own loaded
70
+ // state and first-iOS-load key change. That replacement fires onLoad again.
71
+ const originalLoad = state.props.onLoad;
72
+ act(() => originalLoad({ nativeEvent: { url: "about:blank" } }));
73
+ expect(state.editor.injectCSS).toHaveBeenCalledTimes(3);
74
+ act(() => state.props.onLoad({ nativeEvent: { url: "about:blank" } }));
75
+ expect(state.editor.injectCSS).toHaveBeenCalledTimes(6);
76
+ expect(state.editor.injectCSS).toHaveBeenLastCalledWith(
77
+ expect.stringContaining("font-family: system-ui"),
78
+ "multiplatform-tentap-content",
79
+ );
80
+
81
+ state.scheme = "dark";
82
+ state.theme = { color12: { val: "white" } };
83
+ view.rerender(
84
+ <TentapEditor initialContent="<p>Saved note</p>" bodyFontFamily="Georgia, serif" />,
85
+ );
86
+ expect(state.editor.injectCSS).toHaveBeenCalledWith(
87
+ expect.stringContaining("background: black"),
88
+ "multiplatform-tentap-dark",
89
+ );
90
+ expect(state.editor.injectCSS).toHaveBeenCalledWith(
91
+ expect.stringContaining("--color12: white"),
92
+ "multiplatform-tentap-palette",
93
+ );
94
+ expect(state.editor.injectCSS).toHaveBeenLastCalledWith(
95
+ expect.stringContaining("font-family: Georgia, serif"),
96
+ "multiplatform-tentap-content",
97
+ );
98
+ expect(state.editor.setContent).not.toHaveBeenCalled();
99
+ const calls = state.editor.injectCSS.mock.calls.length;
100
+ view.unmount();
101
+ act(() => {
102
+ vi.advanceTimersByTime(1000);
103
+ });
104
+ expect(state.editor.injectCSS).toHaveBeenCalledTimes(calls);
105
+ });
106
+
107
+ it("uses the latest scheme when styling changes before the WebView loads", () => {
108
+ const view = render(<TentapEditor />);
109
+ state.scheme = "dark";
110
+ state.theme = { color12: { val: "white" } };
111
+ view.rerender(<TentapEditor />);
112
+ expect(state.editor.injectCSS).not.toHaveBeenCalled();
113
+ act(() => state.props.onLoad({ nativeEvent: { url: "about:blank" } }));
114
+ expect(state.editor.injectCSS).toHaveBeenCalledTimes(3);
115
+ expect(state.editor.injectCSS).toHaveBeenCalledWith(
116
+ expect.stringContaining("--color12: white"),
117
+ "multiplatform-tentap-palette",
118
+ );
119
+ });
120
+ });
@@ -43,6 +43,7 @@ import { PALETTE_THEME_TOKENS } from "../constants";
43
43
  import { t } from "../shared/t";
44
44
  import { getEditorStateFromBridge } from "./useEditorState";
45
45
  import { getEditorCSS } from "./editorStyles";
46
+ import { escapeNativeEditorCSS } from "./nativeEditorCSS";
46
47
  import { htmlToMarkdown, markdownToHtml } from "./markdown";
47
48
 
48
49
  function useNativeEditorDarkMode(): boolean {
@@ -126,62 +127,43 @@ export const TentapEditor = forwardRef(
126
127
  },
127
128
  });
128
129
 
129
- // 10Tap defaults the WebView to a white shell; darkEditorTheme fixes the RN WebView,
130
- // and darkEditorCss aligns the inner ProseMirror document with dark mode.
131
- useEffect(() => {
132
- const id = setTimeout(() => {
133
- if (isDarkMode) {
134
- editor.injectCSS(darkEditorCss, "multiplatform-tentap-dark");
135
- } else {
136
- editor.injectCSS("", "multiplatform-tentap-dark");
137
- }
138
- }, 150);
139
- return () => clearTimeout(id);
140
- }, [editor, isDarkMode]);
141
-
142
- // Web-authored color/highlight marks store theme custom properties
143
- // (`var(--red11)`); the 10Tap webview has no tamagui stylesheet, so
144
- // define the palette vars there from the active theme (Axiom 5: same
145
- // tokens, resolved once per scheme).
146
130
  const theme = useTheme();
147
- useEffect(() => {
148
- const id = setTimeout(() => {
149
- const decls = PALETTE_THEME_TOKENS.map((token) => {
150
- const value = (theme as any)[token]?.val;
151
- return value ? `--${token}: ${value};` : "";
152
- }).join(" ");
153
- editor.injectCSS(`:root { ${decls} }`, "multiplatform-tentap-palette");
154
- }, 150);
155
- return () => clearTimeout(id);
156
- }, [editor, theme]);
131
+ const webviewLoaded = useRef(false);
132
+ const applyEditorStyles = useCallback(() => {
133
+ const inject = (css: string, tag: string) => {
134
+ editor.injectCSS(escapeNativeEditorCSS(css), tag);
135
+ };
136
+ inject(isDarkMode ? darkEditorCss : "", "multiplatform-tentap-dark");
137
+ // The WebView has no Tamagui stylesheet. Resolve the same palette as
138
+ // web-authored color/highlight marks before applying content rules.
139
+ const decls = PALETTE_THEME_TOKENS.map((token) => {
140
+ const value = (theme as any)[token]?.val;
141
+ return value ? `--${token}: ${value};` : "";
142
+ }).join(" ");
143
+ inject(`:root { ${decls} }`, "multiplatform-tentap-palette");
144
+ inject(
145
+ getEditorCSS({
146
+ minHeight,
147
+ maxHeight,
148
+ bodyFontFamily,
149
+ headingFontFamily,
150
+ fullscreen: fill,
151
+ motionMs: 0,
152
+ }),
153
+ "multiplatform-tentap-content",
154
+ );
155
+ }, [editor, isDarkMode, theme, minHeight, maxHeight, bodyFontFamily, headingFontFamily, fill]);
157
156
 
158
- // The content rules: padding, the $body / $heading families, block flow
159
- // for lists and quotes, code and image chrome. Web gets them from
160
- // getEditorCSS() through a <style> tag; the 10Tap WebView is a separate
161
- // document with no Tamagui stylesheet, so with nothing injected it renders
162
- // WebKit's defaults, which is zero padding and the UA serif (MPO-187, Clay's
163
- // screenshot: "Hello world" at x=0 in Times while the toolbar is the system
164
- // sans). One source: the same function the web editor uses, so a fix on
165
- // either side lands on both. motionMs 0 because there is no bubble menu on
166
- // native, and the palette block above already defines every var(--colorN)
167
- // this CSS reads. `.ProseMirror` outranks darkEditorCss's `* { color }`, so
168
- // the foreground also moves onto the theme's --color12 in both schemes.
157
+ // TenTap invokes this after its internal onLoad handling, including its
158
+ // first-load iOS reload. Apply on every load so the replacement document
159
+ // receives the styles too; a mount-time timer can target the old one.
160
+ const handleEditorLoad = useCallback(() => {
161
+ webviewLoaded.current = true;
162
+ applyEditorStyles();
163
+ }, [applyEditorStyles]);
169
164
  useEffect(() => {
170
- const id = setTimeout(() => {
171
- editor.injectCSS(
172
- getEditorCSS({
173
- minHeight,
174
- maxHeight,
175
- bodyFontFamily,
176
- headingFontFamily,
177
- fullscreen: fill,
178
- motionMs: 0,
179
- }),
180
- "multiplatform-tentap-content",
181
- );
182
- }, 150);
183
- return () => clearTimeout(id);
184
- }, [editor, minHeight, maxHeight, bodyFontFamily, headingFontFamily, fill]);
165
+ if (webviewLoaded.current) applyEditorStyles();
166
+ }, [applyEditorStyles]);
185
167
 
186
168
  // Subscribe to editor state updates
187
169
  useEffect(() => {
@@ -349,7 +331,11 @@ export const TentapEditor = forwardRef(
349
331
  overflow="hidden"
350
332
  opacity={ctx.disabled ? 0.5 : 1}
351
333
  >
352
- <RichText editor={editor} style={fill ? styles.editorFill : styles.editor} />
334
+ <RichText
335
+ editor={editor}
336
+ style={fill ? styles.editorFill : styles.editor}
337
+ onLoad={handleEditorLoad}
338
+ />
353
339
  </View>
354
340
  );
355
341
  },