@multiplatform.one/rich-text 7.11.0 → 7.15.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 (187) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/RichTextEditor/index.native.js.map +1 -1
  3. package/dist/cjs/constants.native.js.map +1 -1
  4. package/dist/cjs/context.native.js.map +1 -1
  5. package/dist/cjs/editor/BlockDragHandle.native.js.map +1 -1
  6. package/dist/cjs/editor/MarkdownPaste.native.js.map +1 -1
  7. package/dist/cjs/editor/SlashCommands.native.js.map +1 -1
  8. package/dist/cjs/editor/TentapEditor.native.js +47 -14
  9. package/dist/cjs/editor/TentapEditor.native.js.map +1 -1
  10. package/dist/cjs/editor/TiptapEditor.native.js.map +1 -1
  11. package/dist/cjs/editor/blockAnchor.native.js.map +1 -1
  12. package/dist/cjs/editor/capabilities.native.js.map +1 -1
  13. package/dist/cjs/editor/codeBlockPreview.native.js.map +1 -1
  14. package/dist/cjs/editor/editorStyles.native.js.map +1 -1
  15. package/dist/cjs/editor/emoji.native.js.map +1 -1
  16. package/dist/cjs/editor/extensions.native.js.map +1 -1
  17. package/dist/cjs/editor/index.native.js.map +1 -1
  18. package/dist/cjs/editor/lazyKatex.native.js.map +1 -1
  19. package/dist/cjs/editor/markdown.native.js.map +1 -1
  20. package/dist/cjs/editor/mathematics.native.js.map +1 -1
  21. package/dist/cjs/editor/media.native.js.map +1 -1
  22. package/dist/cjs/editor/mentions.native.js.map +1 -1
  23. package/dist/cjs/editor/nativeEditorCSS.native.js.map +1 -1
  24. package/dist/cjs/editor/pasteCleanup.native.js.map +1 -1
  25. package/dist/cjs/editor/suggestionPopup.native.js.map +1 -1
  26. package/dist/cjs/editor/types.native.js.map +1 -1
  27. package/dist/cjs/editor/useEditorState.native.js.map +1 -1
  28. package/dist/cjs/index.native.js +1 -1
  29. package/dist/cjs/index.native.js.map +1 -1
  30. package/dist/cjs/preview.native.js.map +1 -1
  31. package/dist/cjs/shared/t.native.js.map +1 -1
  32. package/dist/cjs/shared/url.native.js.map +1 -1
  33. package/dist/cjs/styled.native.js.map +1 -1
  34. package/dist/cjs/toolbar/BubbleToolbar.native.js.map +1 -1
  35. package/dist/cjs/toolbar/CodeBlockBubble.native.js.map +1 -1
  36. package/dist/cjs/toolbar/ColorPicker.cjs +2 -2
  37. package/dist/cjs/toolbar/HeadingPicker.native.js.map +1 -1
  38. package/dist/cjs/toolbar/ImageBubble.native.js.map +1 -1
  39. package/dist/cjs/toolbar/ImageDialog.cjs +1 -1
  40. package/dist/cjs/toolbar/LinkBubble.native.js.map +1 -1
  41. package/dist/cjs/toolbar/LinkDialog.cjs +1 -1
  42. package/dist/cjs/toolbar/ListPicker.native.js.map +1 -1
  43. package/dist/cjs/toolbar/MathBubble.native.js.map +1 -1
  44. package/dist/cjs/toolbar/MoreDropdown.cjs +1 -1
  45. package/dist/cjs/toolbar/MoreDropdown.native.js +1 -1
  46. package/dist/cjs/toolbar/MoreDropdown.native.js.map +1 -1
  47. package/dist/cjs/toolbar/ResizeHandle.native.js.map +1 -1
  48. package/dist/cjs/toolbar/StatusBar.native.js.map +1 -1
  49. package/dist/cjs/toolbar/TableBubble.cjs +1 -1
  50. package/dist/cjs/toolbar/TableBubble.native.js +1 -1
  51. package/dist/cjs/toolbar/TableBubble.native.js.map +1 -1
  52. package/dist/cjs/toolbar/ToolbarGroup.native.js.map +1 -1
  53. package/dist/cjs/toolbar/shortcuts.native.js.map +1 -1
  54. package/dist/cjs/toolbar/useHorizontalScroll.cjs +2 -3
  55. package/dist/cjs/toolbar/useHorizontalScroll.native.js +2 -3
  56. package/dist/cjs/toolbar/useHorizontalScroll.native.js.map +1 -1
  57. package/dist/cjs/toolbar/useRovingFocus.native.js.map +1 -1
  58. package/dist/cjs/types.native.js.map +1 -1
  59. package/dist/esm/RichTextEditor/index.native.js.map +1 -1
  60. package/dist/esm/constants.native.js.map +1 -1
  61. package/dist/esm/context.native.js.map +1 -1
  62. package/dist/esm/editor/BlockDragHandle.native.js.map +1 -1
  63. package/dist/esm/editor/MarkdownPaste.native.js.map +1 -1
  64. package/dist/esm/editor/SlashCommands.native.js.map +1 -1
  65. package/dist/esm/editor/TentapEditor.native.js +48 -15
  66. package/dist/esm/editor/TentapEditor.native.js.map +1 -1
  67. package/dist/esm/editor/TiptapEditor.native.js.map +1 -1
  68. package/dist/esm/editor/blockAnchor.native.js.map +1 -1
  69. package/dist/esm/editor/capabilities.native.js.map +1 -1
  70. package/dist/esm/editor/codeBlockPreview.native.js.map +1 -1
  71. package/dist/esm/editor/editorStyles.native.js.map +1 -1
  72. package/dist/esm/editor/emoji.native.js.map +1 -1
  73. package/dist/esm/editor/extensions.native.js.map +1 -1
  74. package/dist/esm/editor/index.native.js.map +1 -1
  75. package/dist/esm/editor/lazyKatex.native.js.map +1 -1
  76. package/dist/esm/editor/markdown.native.js.map +1 -1
  77. package/dist/esm/editor/mathematics.native.js.map +1 -1
  78. package/dist/esm/editor/media.native.js.map +1 -1
  79. package/dist/esm/editor/mentions.native.js.map +1 -1
  80. package/dist/esm/editor/nativeEditorCSS.native.js.map +1 -1
  81. package/dist/esm/editor/pasteCleanup.native.js.map +1 -1
  82. package/dist/esm/editor/suggestionPopup.native.js.map +1 -1
  83. package/dist/esm/editor/types.native.js.map +1 -1
  84. package/dist/esm/editor/useEditorState.native.js.map +1 -1
  85. package/dist/esm/index.native.js.map +1 -1
  86. package/dist/esm/preview.native.js.map +1 -1
  87. package/dist/esm/shared/t.native.js.map +1 -1
  88. package/dist/esm/shared/url.native.js.map +1 -1
  89. package/dist/esm/styled.native.js.map +1 -1
  90. package/dist/esm/toolbar/BubbleToolbar.native.js.map +1 -1
  91. package/dist/esm/toolbar/CodeBlockBubble.native.js.map +1 -1
  92. package/dist/esm/toolbar/ColorPicker.mjs +2 -2
  93. package/dist/esm/toolbar/HeadingPicker.native.js.map +1 -1
  94. package/dist/esm/toolbar/ImageBubble.native.js.map +1 -1
  95. package/dist/esm/toolbar/ImageDialog.mjs +1 -1
  96. package/dist/esm/toolbar/LinkBubble.native.js.map +1 -1
  97. package/dist/esm/toolbar/LinkDialog.mjs +1 -1
  98. package/dist/esm/toolbar/ListPicker.native.js.map +1 -1
  99. package/dist/esm/toolbar/MathBubble.native.js.map +1 -1
  100. package/dist/esm/toolbar/MoreDropdown.mjs +1 -1
  101. package/dist/esm/toolbar/MoreDropdown.native.js +1 -1
  102. package/dist/esm/toolbar/MoreDropdown.native.js.map +1 -1
  103. package/dist/esm/toolbar/ResizeHandle.native.js.map +1 -1
  104. package/dist/esm/toolbar/StatusBar.native.js.map +1 -1
  105. package/dist/esm/toolbar/TableBubble.mjs +2 -2
  106. package/dist/esm/toolbar/TableBubble.mjs.map +1 -1
  107. package/dist/esm/toolbar/TableBubble.native.js +2 -2
  108. package/dist/esm/toolbar/TableBubble.native.js.map +1 -1
  109. package/dist/esm/toolbar/ToolbarGroup.native.js.map +1 -1
  110. package/dist/esm/toolbar/shortcuts.native.js.map +1 -1
  111. package/dist/esm/toolbar/useHorizontalScroll.mjs +2 -3
  112. package/dist/esm/toolbar/useHorizontalScroll.mjs.map +1 -1
  113. package/dist/esm/toolbar/useHorizontalScroll.native.js +2 -3
  114. package/dist/esm/toolbar/useHorizontalScroll.native.js.map +1 -1
  115. package/dist/esm/toolbar/useRovingFocus.native.js.map +1 -1
  116. package/dist/esm/types.native.js.map +1 -1
  117. package/dist/jsx/RichTextEditor/index.native.js.map +1 -1
  118. package/dist/jsx/constants.native.js.map +1 -1
  119. package/dist/jsx/context.native.js.map +1 -1
  120. package/dist/jsx/editor/BlockDragHandle.native.js.map +1 -1
  121. package/dist/jsx/editor/MarkdownPaste.native.js.map +1 -1
  122. package/dist/jsx/editor/SlashCommands.native.js.map +1 -1
  123. package/dist/jsx/editor/TentapEditor.native.js +47 -14
  124. package/dist/jsx/editor/TentapEditor.native.js.map +1 -1
  125. package/dist/jsx/editor/TiptapEditor.native.js.map +1 -1
  126. package/dist/jsx/editor/blockAnchor.native.js.map +1 -1
  127. package/dist/jsx/editor/capabilities.native.js.map +1 -1
  128. package/dist/jsx/editor/codeBlockPreview.native.js.map +1 -1
  129. package/dist/jsx/editor/editorStyles.native.js.map +1 -1
  130. package/dist/jsx/editor/emoji.native.js.map +1 -1
  131. package/dist/jsx/editor/extensions.native.js.map +1 -1
  132. package/dist/jsx/editor/index.native.js.map +1 -1
  133. package/dist/jsx/editor/lazyKatex.native.js.map +1 -1
  134. package/dist/jsx/editor/markdown.native.js.map +1 -1
  135. package/dist/jsx/editor/mathematics.native.js.map +1 -1
  136. package/dist/jsx/editor/media.native.js.map +1 -1
  137. package/dist/jsx/editor/mentions.native.js.map +1 -1
  138. package/dist/jsx/editor/nativeEditorCSS.native.js.map +1 -1
  139. package/dist/jsx/editor/pasteCleanup.native.js.map +1 -1
  140. package/dist/jsx/editor/suggestionPopup.native.js.map +1 -1
  141. package/dist/jsx/editor/types.native.js.map +1 -1
  142. package/dist/jsx/editor/useEditorState.native.js.map +1 -1
  143. package/dist/jsx/index.native.js +1 -1
  144. package/dist/jsx/preview.native.js.map +1 -1
  145. package/dist/jsx/shared/t.native.js.map +1 -1
  146. package/dist/jsx/shared/url.native.js.map +1 -1
  147. package/dist/jsx/styled.native.js.map +1 -1
  148. package/dist/jsx/toolbar/BubbleToolbar.native.js.map +1 -1
  149. package/dist/jsx/toolbar/CodeBlockBubble.native.js.map +1 -1
  150. package/dist/jsx/toolbar/ColorPicker.mjs +2 -2
  151. package/dist/jsx/toolbar/HeadingPicker.native.js.map +1 -1
  152. package/dist/jsx/toolbar/ImageBubble.native.js.map +1 -1
  153. package/dist/jsx/toolbar/ImageDialog.mjs +1 -1
  154. package/dist/jsx/toolbar/LinkBubble.native.js.map +1 -1
  155. package/dist/jsx/toolbar/LinkDialog.mjs +1 -1
  156. package/dist/jsx/toolbar/ListPicker.native.js.map +1 -1
  157. package/dist/jsx/toolbar/MathBubble.native.js.map +1 -1
  158. package/dist/jsx/toolbar/MoreDropdown.mjs +1 -1
  159. package/dist/jsx/toolbar/MoreDropdown.native.js +1 -1
  160. package/dist/jsx/toolbar/MoreDropdown.native.js.map +1 -1
  161. package/dist/jsx/toolbar/ResizeHandle.native.js.map +1 -1
  162. package/dist/jsx/toolbar/StatusBar.native.js.map +1 -1
  163. package/dist/jsx/toolbar/TableBubble.mjs +2 -2
  164. package/dist/jsx/toolbar/TableBubble.mjs.map +1 -1
  165. package/dist/jsx/toolbar/TableBubble.native.js +1 -1
  166. package/dist/jsx/toolbar/TableBubble.native.js.map +1 -1
  167. package/dist/jsx/toolbar/ToolbarGroup.native.js.map +1 -1
  168. package/dist/jsx/toolbar/shortcuts.native.js.map +1 -1
  169. package/dist/jsx/toolbar/useHorizontalScroll.mjs +2 -3
  170. package/dist/jsx/toolbar/useHorizontalScroll.mjs.map +1 -1
  171. package/dist/jsx/toolbar/useHorizontalScroll.native.js +2 -3
  172. package/dist/jsx/toolbar/useHorizontalScroll.native.js.map +1 -1
  173. package/dist/jsx/toolbar/useRovingFocus.native.js.map +1 -1
  174. package/dist/jsx/types.native.js.map +1 -1
  175. package/package.json +10 -10
  176. package/src/editor/TentapEditor.native.spec.tsx +39 -1
  177. package/src/editor/TentapEditor.native.tsx +72 -15
  178. package/src/toolbar/ColorPicker.tsx +2 -2
  179. package/src/toolbar/ImageDialog.tsx +1 -1
  180. package/src/toolbar/LinkDialog.tsx +1 -1
  181. package/src/toolbar/MoreDropdown.tsx +1 -1
  182. package/src/toolbar/TableBubble.spec.tsx +6 -0
  183. package/src/toolbar/TableBubble.tsx +2 -2
  184. package/src/toolbar/toolbarMenuAnchoring.spec.ts +32 -0
  185. package/src/toolbar/useHorizontalScroll.tsx +2 -1
  186. package/types/editor/TentapEditor.native.d.ts.map +1 -1
  187. package/types/toolbar/useHorizontalScroll.d.ts.map +1 -1
@@ -36,7 +36,7 @@ import {
36
36
  darkEditorCss,
37
37
  darkEditorTheme,
38
38
  } from "@10play/tentap-editor";
39
- import { View, useTheme, useThemeName } from "tamagui";
39
+ import { View, getConfig, getVariableValue, useTheme, useThemeName } from "tamagui";
40
40
  import type { RichEditorProps, RichEditorRef, EditorCommand } from "../types";
41
41
  import { RichEditorContext } from "../context";
42
42
  import { PALETTE_THEME_TOKENS } from "../constants";
@@ -46,13 +46,56 @@ import { getEditorCSS } from "./editorStyles";
46
46
  import { escapeNativeEditorCSS } from "./nativeEditorCSS";
47
47
  import { htmlToMarkdown, markdownToHtml } from "./markdown";
48
48
 
49
- function useNativeEditorDarkMode(): boolean {
50
- const themeName = useThemeName();
49
+ type EditorScheme = "light" | "dark";
50
+
51
+ function useNativeEditorScheme() {
52
+ const themeName = String(useThemeName() || "");
51
53
  const osScheme = useColorScheme();
52
- if (themeName) {
53
- return String(themeName).startsWith("dark");
54
- }
55
- return osScheme === "dark";
54
+ const scheme: EditorScheme = themeName
55
+ ? themeName.startsWith("dark")
56
+ ? "dark"
57
+ : "light"
58
+ : osScheme === "dark"
59
+ ? "dark"
60
+ : "light";
61
+ return { themeName: themeName || scheme, scheme, followsAppearance: osScheme === scheme };
62
+ }
63
+
64
+ function paletteDecls(read: (token: string) => unknown): string {
65
+ return PALETTE_THEME_TOKENS.flatMap((token) => {
66
+ const value = read(token);
67
+ return value ? [`--${token}: ${value};`] : [];
68
+ }).join(" ");
69
+ }
70
+
71
+ /**
72
+ * A theme that follows the appearance gets both palettes behind
73
+ * prefers-color-scheme, so the WebView repaints on a scheme flip JS never
74
+ * hears about: a fullScreen modal detaches the root view, and RN's Appearance
75
+ * stays silent until it closes (MPO-187).
76
+ */
77
+ function schemeStyles(
78
+ themeName: string,
79
+ scheme: EditorScheme,
80
+ followsAppearance: boolean,
81
+ current: string,
82
+ ): { dark: string; palette: string } {
83
+ const staticStyles = {
84
+ dark: scheme === "dark" ? darkEditorCss : "",
85
+ palette: `:root { ${current} }`,
86
+ };
87
+ if (!followsAppearance) return staticStyles;
88
+ const other: EditorScheme = scheme === "dark" ? "light" : "dark";
89
+ const otherTheme = (getConfig().themes as Record<string, Record<string, unknown>>)[
90
+ themeName.replace(/^(light|dark)/, other)
91
+ ];
92
+ if (!otherTheme) return staticStyles;
93
+ const opposite = paletteDecls((token) => getVariableValue(otherTheme[token]));
94
+ const byScheme = { [scheme]: current, [other]: opposite } as Record<EditorScheme, string>;
95
+ return {
96
+ dark: `@media (prefers-color-scheme: dark) { ${darkEditorCss} }`,
97
+ palette: `@media (prefers-color-scheme: light) { :root { ${byScheme.light} } } @media (prefers-color-scheme: dark) { :root { ${byScheme.dark} } }`,
98
+ };
56
99
  }
57
100
 
58
101
  // ===========================================================================
@@ -91,7 +134,8 @@ export const TentapEditor = forwardRef(
91
134
  ) => {
92
135
  const ctx = RichEditorContext.useStyledContext();
93
136
  const placeholder = placeholderProp ?? t("Start writing...");
94
- const isDarkMode = useNativeEditorDarkMode();
137
+ const { themeName, scheme, followsAppearance } = useNativeEditorScheme();
138
+ const isDarkMode = scheme === "dark";
95
139
  const cachedHtml = useRef<string>("");
96
140
  const cachedJson = useRef<any>(null);
97
141
 
@@ -133,14 +177,16 @@ export const TentapEditor = forwardRef(
133
177
  const inject = (css: string, tag: string) => {
134
178
  editor.injectCSS(escapeNativeEditorCSS(css), tag);
135
179
  };
136
- inject(isDarkMode ? darkEditorCss : "", "multiplatform-tentap-dark");
137
180
  // The WebView has no Tamagui stylesheet. Resolve the same palette as
138
181
  // 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");
182
+ const styles = schemeStyles(
183
+ themeName,
184
+ scheme,
185
+ followsAppearance,
186
+ paletteDecls((token) => (theme as any)[token]?.val),
187
+ );
188
+ inject(styles.dark, "multiplatform-tentap-dark");
189
+ inject(styles.palette, "multiplatform-tentap-palette");
144
190
  inject(
145
191
  getEditorCSS({
146
192
  minHeight,
@@ -152,7 +198,18 @@ export const TentapEditor = forwardRef(
152
198
  }),
153
199
  "multiplatform-tentap-content",
154
200
  );
155
- }, [editor, isDarkMode, theme, minHeight, maxHeight, bodyFontFamily, headingFontFamily, fill]);
201
+ }, [
202
+ editor,
203
+ themeName,
204
+ scheme,
205
+ followsAppearance,
206
+ theme,
207
+ minHeight,
208
+ maxHeight,
209
+ bodyFontFamily,
210
+ headingFontFamily,
211
+ fill,
212
+ ]);
156
213
 
157
214
  // TenTap invokes this after its internal onLoad handling, including its
158
215
  // first-load iOS reload. Apply on every load so the replacement document
@@ -80,7 +80,7 @@ export function TextColorPicker({ editorRef, state, disabled }: ColorPickerProps
80
80
  );
81
81
 
82
82
  return (
83
- <Popover open={open} onOpenChange={setOpen} placement="bottom">
83
+ <Popover open={open} onOpenChange={setOpen} placement="bottom-start">
84
84
  <Popover.Trigger asChild>
85
85
  <ToolbarButton
86
86
  active={!!state.textColor}
@@ -123,7 +123,7 @@ export function HighlightColorPicker({ editorRef, state, disabled }: ColorPicker
123
123
  );
124
124
 
125
125
  return (
126
- <Popover open={open} onOpenChange={setOpen} placement="bottom">
126
+ <Popover open={open} onOpenChange={setOpen} placement="bottom-start">
127
127
  <Popover.Trigger asChild>
128
128
  <ToolbarButton
129
129
  active={!!state.highlightColor}
@@ -43,7 +43,7 @@ export function ImageDialog({ editorRef, disabled }: ImageDialogProps) {
43
43
  }, [imageUrl, editorRef]);
44
44
 
45
45
  return (
46
- <Popover open={open} onOpenChange={setOpen} placement="bottom">
46
+ <Popover open={open} onOpenChange={setOpen} placement="bottom-start">
47
47
  <Popover.Trigger asChild>
48
48
  <ToolbarButton
49
49
  disabled={disabled}
@@ -69,7 +69,7 @@ export function LinkDialog({
69
69
  }, [editorRef]);
70
70
 
71
71
  return (
72
- <Popover open={open} onOpenChange={setOpen} placement="bottom">
72
+ <Popover open={open} onOpenChange={setOpen} placement="bottom-start">
73
73
  <Popover.Trigger asChild>
74
74
  <ToolbarButton
75
75
  active={state.isLink}
@@ -159,7 +159,7 @@ export function MoreDropdown({
159
159
  );
160
160
 
161
161
  return (
162
- <DropdownMenu open={open} onOpenChange={setOpen} placement="bottom-end">
162
+ <DropdownMenu open={open} onOpenChange={setOpen} placement="bottom-start">
163
163
  <DropdownMenu.Trigger asChild>
164
164
  <ToolbarButton
165
165
  disabled={disabled}
@@ -117,6 +117,12 @@ describe("TableBubble", () => {
117
117
  expect(bubble.props?.options).toMatchObject({ placement: "top-start" });
118
118
  });
119
119
 
120
+ it("attaches flush to the table at the overlay family gap, not a private 6px (MPO-35)", () => {
121
+ const { editor } = makeEditor();
122
+ render(editor);
123
+ expect(bubble.props?.options).toMatchObject({ offset: 0 });
124
+ });
125
+
120
126
  // LC-52: GFM pipe tables are plain grids, so merge and split have no
121
127
  // markdown form. The markdown capability must drop exactly those two.
122
128
  it("markdown capability removes exactly mergeCells and splitCell", () => {
@@ -7,7 +7,7 @@ import { useState } from "react";
7
7
  import type { Editor } from "@tiptap/react";
8
8
  import { BubbleMenu } from "@tiptap/react/menus";
9
9
  import { PopoverContent } from "@multiplatform.one/components";
10
- import { useResolvedKnobs } from "@multiplatform.one/theme";
10
+ import { OVERLAY_ATTACH_GAP, useResolvedKnobs } from "@multiplatform.one/theme";
11
11
  import { ColumnsPlusRightIcon, RowsPlusBottomIcon, TrashIcon } from "@phosphor-icons/react";
12
12
  import { Popover, Text, XStack, YStack } from "tamagui";
13
13
  import { DropdownMenuItemFrame, ToolbarSeparatorFrame } from "../styled";
@@ -111,7 +111,7 @@ export function TableBubble({ editor, capabilities = "full" }: TableBubbleProps)
111
111
  // the first row and holds still while typing in cells (LC-06 spirit:
112
112
  // chrome must not sit on top of the content being edited).
113
113
  getReferencedVirtualElement={() => getBlockVirtualElement(editor, "table")}
114
- options={{ placement: "top-start", offset: 6 }}
114
+ options={{ placement: "top-start", offset: OVERLAY_ATTACH_GAP }}
115
115
  >
116
116
  <XStack
117
117
  backgroundColor="$background"
@@ -0,0 +1,32 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ // MPO-35 ruling B (Clay, 2026-09-26): toolbar menus are input-anchored. Each
7
+ // one drops flush below its trigger and starts at the trigger's left edge, so
8
+ // a menu never covers the toolbar being worked in and never centres itself
9
+ // off to one side. The selection and block bubbles are their own lineage and
10
+ // are not held to this.
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ const toolbarMenus = {
13
+ "ColorPicker.tsx": 2,
14
+ "HeadingPicker.tsx": 1,
15
+ "ImageDialog.tsx": 1,
16
+ "LinkDialog.tsx": 1,
17
+ "ListPicker.tsx": 1,
18
+ "MoreDropdown.tsx": 1,
19
+ } as const;
20
+
21
+ describe("rich-text toolbar menus open input-anchored (MPO-35)", () => {
22
+ for (const [file, menus] of Object.entries(toolbarMenus)) {
23
+ it(`${file}: every menu opens bottom-start`, () => {
24
+ const src = readFileSync(join(here, file), "utf8");
25
+ const placements = [
26
+ ...src.matchAll(/<(?:Popover|DropdownMenu)\b[^>]*placement="([^"]+)"/g),
27
+ ].map((match) => match[1]);
28
+ expect(placements).toHaveLength(menus);
29
+ expect(placements.every((placement) => placement === "bottom-start")).toBe(true);
30
+ });
31
+ }
32
+ });
@@ -9,6 +9,7 @@ import { View } from "tamagui";
9
9
  import { LinearGradient } from "@tamagui/linear-gradient";
10
10
  import { CaretLeftIcon, CaretRightIcon } from "@phosphor-icons/react";
11
11
  import { useDirection } from "@multiplatform.one/components";
12
+ import { transitionProps } from "@multiplatform.one/theme";
12
13
 
13
14
  const scrollArrowThreshold = 8;
14
15
 
@@ -276,7 +277,7 @@ export function HorizontalScrollArrow({
276
277
  opacity={visible ? 1 : 0}
277
278
  // `animation` is the tamagui 1.x prop — silently dropped on 2.x, which
278
279
  // left this opacity fade popping.
279
- {...(transition ? { transition } : undefined)}
280
+ {...transitionProps(transition)}
280
281
  onPointerEnter={visible ? handlePointerEnter : undefined}
281
282
  onPointerLeave={handlePointerLeave}
282
283
  data-testid={`toolbar-scroll-arrow-${direction}`}
@@ -1 +1 @@
1
- {"version":3,"file":"TentapEditor.native.d.ts","sourceRoot":"","sources":["../../src/editor/TentapEditor.native.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAsB9E,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,6EAA6E;IAC7E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAID,eAAO,MAAM,YAAY,6GA6QxB,CAAC"}
1
+ {"version":3,"file":"TentapEditor.native.d.ts","sourceRoot":"","sources":["../../src/editor/TentapEditor.native.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAiE9E,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,6EAA6E;IAC7E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAID,eAAO,MAAM,YAAY,6GA2RxB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useHorizontalScroll.d.ts","sourceRoot":"","sources":["../../src/toolbar/useHorizontalScroll.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,GAAG,aAAa,GAAG,aAAa,CAAC,EACxE,SAAS,EAAE,KAAK,GAAG,KAAK,GACvB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAOjD;AAMD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,QAsE9E;AAMD;;GAEG;AACH,wBAAgB,yBAAyB;;;;4BAO3B,WAAW;EAWxB;AAMD,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,SAAS,EACT,OAAO,EACP,iBAAiB,EACjB,UAAU,GACX,EAAE,0BAA0B,2CAwI5B"}
1
+ {"version":3,"file":"useHorizontalScroll.d.ts","sourceRoot":"","sources":["../../src/toolbar/useHorizontalScroll.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,GAAG,aAAa,GAAG,aAAa,CAAC,EACxE,SAAS,EAAE,KAAK,GAAG,KAAK,GACvB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAOjD;AAMD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,QAsE9E;AAMD;;GAEG;AACH,wBAAgB,yBAAyB;;;;4BAO3B,WAAW;EAWxB;AAMD,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,SAAS,EACT,OAAO,EACP,iBAAiB,EACjB,UAAU,GACX,EAAE,0BAA0B,2CAwI5B"}