@medway-ui/native 1.9.1 → 1.10.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 (298) hide show
  1. package/README.md +2 -2
  2. package/lib/commonjs/components/Alert.js +36 -25
  3. package/lib/commonjs/components/Alert.js.map +1 -1
  4. package/lib/commonjs/components/AlertDialog.js +22 -23
  5. package/lib/commonjs/components/AlertDialog.js.map +1 -1
  6. package/lib/commonjs/components/Avatar.js +4 -4
  7. package/lib/commonjs/components/Badge.js +22 -15
  8. package/lib/commonjs/components/Badge.js.map +1 -1
  9. package/lib/commonjs/components/Button.js +56 -42
  10. package/lib/commonjs/components/Button.js.map +1 -1
  11. package/lib/commonjs/components/CardNews.js +18 -13
  12. package/lib/commonjs/components/CardNews.js.map +1 -1
  13. package/lib/commonjs/components/Carousel.js +12 -8
  14. package/lib/commonjs/components/Carousel.js.map +1 -1
  15. package/lib/commonjs/components/Checkbox.js +5 -5
  16. package/lib/commonjs/components/Checkbox.js.map +1 -1
  17. package/lib/commonjs/components/Chip.js +52 -23
  18. package/lib/commonjs/components/Chip.js.map +1 -1
  19. package/lib/commonjs/components/Choice.js +26 -12
  20. package/lib/commonjs/components/Choice.js.map +1 -1
  21. package/lib/commonjs/components/Dialog.js +16 -8
  22. package/lib/commonjs/components/Dialog.js.map +1 -1
  23. package/lib/commonjs/components/DropdownMenu.js +35 -14
  24. package/lib/commonjs/components/DropdownMenu.js.map +1 -1
  25. package/lib/commonjs/components/EmptyState.js +5 -5
  26. package/lib/commonjs/components/EmptyState.js.map +1 -1
  27. package/lib/commonjs/components/FallbackImage.js +42 -35
  28. package/lib/commonjs/components/FallbackImage.js.map +1 -1
  29. package/lib/commonjs/components/Feed.js +6 -5
  30. package/lib/commonjs/components/Feed.js.map +1 -1
  31. package/lib/commonjs/components/FeedNotice.js +3 -3
  32. package/lib/commonjs/components/Feedback.js +2 -4
  33. package/lib/commonjs/components/Feedback.js.map +1 -1
  34. package/lib/commonjs/components/Filter.js +6 -9
  35. package/lib/commonjs/components/Filter.js.map +1 -1
  36. package/lib/commonjs/components/IndexLevel.js +4 -4
  37. package/lib/commonjs/components/Input.js +25 -20
  38. package/lib/commonjs/components/Input.js.map +1 -1
  39. package/lib/commonjs/components/InvitePopover.js +19 -8
  40. package/lib/commonjs/components/InvitePopover.js.map +1 -1
  41. package/lib/commonjs/components/Label.js +2 -2
  42. package/lib/commonjs/components/LeaderboardCard.js +3 -2
  43. package/lib/commonjs/components/LeaderboardCard.js.map +1 -1
  44. package/lib/commonjs/components/Loading.js +18 -15
  45. package/lib/commonjs/components/Loading.js.map +1 -1
  46. package/lib/commonjs/components/LoadingOverlay.js +36 -43
  47. package/lib/commonjs/components/LoadingOverlay.js.map +1 -1
  48. package/lib/commonjs/components/MemberRow.js +7 -6
  49. package/lib/commonjs/components/MemberRow.js.map +1 -1
  50. package/lib/commonjs/components/PageHeader.js +3 -2
  51. package/lib/commonjs/components/PageHeader.js.map +1 -1
  52. package/lib/commonjs/components/Popover.js +1 -1
  53. package/lib/commonjs/components/PostForm.js +38 -24
  54. package/lib/commonjs/components/PostForm.js.map +1 -1
  55. package/lib/commonjs/components/PrizeCollection.js +5 -5
  56. package/lib/commonjs/components/PrizeCollection.js.map +1 -1
  57. package/lib/commonjs/components/RadioGroup.js +65 -15
  58. package/lib/commonjs/components/RadioGroup.js.map +1 -1
  59. package/lib/commonjs/components/SectionHeader.js +3 -2
  60. package/lib/commonjs/components/SectionHeader.js.map +1 -1
  61. package/lib/commonjs/components/SegmentControl.js +4 -4
  62. package/lib/commonjs/components/Select.js +7 -6
  63. package/lib/commonjs/components/Select.js.map +1 -1
  64. package/lib/commonjs/components/Separator.js +2 -2
  65. package/lib/commonjs/components/Separator.js.map +1 -1
  66. package/lib/commonjs/components/Sheet.js +17 -11
  67. package/lib/commonjs/components/Sheet.js.map +1 -1
  68. package/lib/commonjs/components/Skeleton.js +2 -2
  69. package/lib/commonjs/components/Skeleton.js.map +1 -1
  70. package/lib/commonjs/components/Switch.js +3 -3
  71. package/lib/commonjs/components/Tabs.js +3 -2
  72. package/lib/commonjs/components/Tabs.js.map +1 -1
  73. package/lib/commonjs/components/Tag.js +52 -32
  74. package/lib/commonjs/components/Tag.js.map +1 -1
  75. package/lib/commonjs/components/Text.js +1 -1
  76. package/lib/commonjs/components/ThemedIcon.js +1 -1
  77. package/lib/commonjs/components/Toast.js +12 -12
  78. package/lib/commonjs/components/Toast.js.map +1 -1
  79. package/lib/commonjs/components/Tooltip.js +2 -2
  80. package/lib/commonjs/editor/NoteEditor.js +190 -67
  81. package/lib/commonjs/editor/NoteEditor.js.map +1 -1
  82. package/lib/commonjs/editor/editorHtml.generated.js +1 -1
  83. package/lib/commonjs/editor/editorHtml.generated.js.map +1 -1
  84. package/lib/commonjs/editor/index.js.map +1 -1
  85. package/lib/commonjs/editor/protocol.js +1 -2
  86. package/lib/commonjs/editor/protocol.js.map +1 -1
  87. package/lib/commonjs/hooks/useThemeColor.js +41 -19
  88. package/lib/commonjs/hooks/useThemeColor.js.map +1 -1
  89. package/lib/commonjs/index.js +46 -46
  90. package/lib/commonjs/lib/backdrop.js +18 -0
  91. package/lib/commonjs/lib/backdrop.js.map +1 -0
  92. package/lib/commonjs/lib/overlay-host.js +5 -3
  93. package/lib/commonjs/lib/overlay-host.js.map +1 -1
  94. package/lib/commonjs/lib/raised-surface.js +1 -1
  95. package/lib/module/components/Alert.js +36 -25
  96. package/lib/module/components/Alert.js.map +1 -1
  97. package/lib/module/components/AlertDialog.js +21 -22
  98. package/lib/module/components/AlertDialog.js.map +1 -1
  99. package/lib/module/components/Avatar.js +4 -4
  100. package/lib/module/components/Badge.js +22 -15
  101. package/lib/module/components/Badge.js.map +1 -1
  102. package/lib/module/components/Button.js +56 -42
  103. package/lib/module/components/Button.js.map +1 -1
  104. package/lib/module/components/CardNews.js +18 -13
  105. package/lib/module/components/CardNews.js.map +1 -1
  106. package/lib/module/components/Carousel.js +12 -8
  107. package/lib/module/components/Carousel.js.map +1 -1
  108. package/lib/module/components/Checkbox.js +5 -5
  109. package/lib/module/components/Checkbox.js.map +1 -1
  110. package/lib/module/components/Chip.js +52 -23
  111. package/lib/module/components/Chip.js.map +1 -1
  112. package/lib/module/components/Choice.js +26 -12
  113. package/lib/module/components/Choice.js.map +1 -1
  114. package/lib/module/components/Dialog.js +16 -8
  115. package/lib/module/components/Dialog.js.map +1 -1
  116. package/lib/module/components/DropdownMenu.js +36 -15
  117. package/lib/module/components/DropdownMenu.js.map +1 -1
  118. package/lib/module/components/EmptyState.js +5 -5
  119. package/lib/module/components/EmptyState.js.map +1 -1
  120. package/lib/module/components/FallbackImage.js +42 -35
  121. package/lib/module/components/FallbackImage.js.map +1 -1
  122. package/lib/module/components/Feed.js +6 -5
  123. package/lib/module/components/Feed.js.map +1 -1
  124. package/lib/module/components/FeedNotice.js +3 -3
  125. package/lib/module/components/Feedback.js +2 -4
  126. package/lib/module/components/Feedback.js.map +1 -1
  127. package/lib/module/components/Filter.js +6 -9
  128. package/lib/module/components/Filter.js.map +1 -1
  129. package/lib/module/components/IndexLevel.js +4 -4
  130. package/lib/module/components/Input.js +24 -18
  131. package/lib/module/components/Input.js.map +1 -1
  132. package/lib/module/components/InvitePopover.js +20 -9
  133. package/lib/module/components/InvitePopover.js.map +1 -1
  134. package/lib/module/components/Label.js +2 -2
  135. package/lib/module/components/LeaderboardCard.js +3 -2
  136. package/lib/module/components/LeaderboardCard.js.map +1 -1
  137. package/lib/module/components/Loading.js +18 -15
  138. package/lib/module/components/Loading.js.map +1 -1
  139. package/lib/module/components/LoadingOverlay.js +37 -44
  140. package/lib/module/components/LoadingOverlay.js.map +1 -1
  141. package/lib/module/components/MemberRow.js +7 -6
  142. package/lib/module/components/MemberRow.js.map +1 -1
  143. package/lib/module/components/PageHeader.js +3 -2
  144. package/lib/module/components/PageHeader.js.map +1 -1
  145. package/lib/module/components/Popover.js +1 -1
  146. package/lib/module/components/PostForm.js +38 -24
  147. package/lib/module/components/PostForm.js.map +1 -1
  148. package/lib/module/components/PrizeCollection.js +5 -5
  149. package/lib/module/components/PrizeCollection.js.map +1 -1
  150. package/lib/module/components/RadioGroup.js +65 -15
  151. package/lib/module/components/RadioGroup.js.map +1 -1
  152. package/lib/module/components/SectionHeader.js +3 -2
  153. package/lib/module/components/SectionHeader.js.map +1 -1
  154. package/lib/module/components/SegmentControl.js +4 -4
  155. package/lib/module/components/Select.js +6 -5
  156. package/lib/module/components/Select.js.map +1 -1
  157. package/lib/module/components/Separator.js +2 -2
  158. package/lib/module/components/Separator.js.map +1 -1
  159. package/lib/module/components/Sheet.js +17 -11
  160. package/lib/module/components/Sheet.js.map +1 -1
  161. package/lib/module/components/Skeleton.js +2 -2
  162. package/lib/module/components/Skeleton.js.map +1 -1
  163. package/lib/module/components/Switch.js +3 -3
  164. package/lib/module/components/Tabs.js +3 -2
  165. package/lib/module/components/Tabs.js.map +1 -1
  166. package/lib/module/components/Tag.js +52 -32
  167. package/lib/module/components/Tag.js.map +1 -1
  168. package/lib/module/components/Text.js +1 -1
  169. package/lib/module/components/ThemedIcon.js +1 -1
  170. package/lib/module/components/Toast.js +12 -12
  171. package/lib/module/components/Toast.js.map +1 -1
  172. package/lib/module/components/Tooltip.js +2 -2
  173. package/lib/module/editor/NoteEditor.js +193 -69
  174. package/lib/module/editor/NoteEditor.js.map +1 -1
  175. package/lib/module/editor/editorHtml.generated.js +1 -1
  176. package/lib/module/editor/editorHtml.generated.js.map +1 -1
  177. package/lib/module/editor/index.js.map +1 -1
  178. package/lib/module/editor/protocol.js +1 -2
  179. package/lib/module/editor/protocol.js.map +1 -1
  180. package/lib/module/hooks/useThemeColor.js +41 -19
  181. package/lib/module/hooks/useThemeColor.js.map +1 -1
  182. package/lib/module/index.js +46 -46
  183. package/lib/module/lib/backdrop.js +15 -0
  184. package/lib/module/lib/backdrop.js.map +1 -0
  185. package/lib/module/lib/overlay-host.js +6 -4
  186. package/lib/module/lib/overlay-host.js.map +1 -1
  187. package/lib/module/lib/raised-surface.js +1 -1
  188. package/lib/typescript/components/Alert.d.ts +1 -1
  189. package/lib/typescript/components/Alert.d.ts.map +1 -1
  190. package/lib/typescript/components/AlertDialog.d.ts +1 -1
  191. package/lib/typescript/components/AlertDialog.d.ts.map +1 -1
  192. package/lib/typescript/components/Badge.d.ts.map +1 -1
  193. package/lib/typescript/components/Button.d.ts +1 -0
  194. package/lib/typescript/components/Button.d.ts.map +1 -1
  195. package/lib/typescript/components/CardNews.d.ts +7 -2
  196. package/lib/typescript/components/CardNews.d.ts.map +1 -1
  197. package/lib/typescript/components/Checkbox.d.ts +3 -1
  198. package/lib/typescript/components/Checkbox.d.ts.map +1 -1
  199. package/lib/typescript/components/Chip.d.ts.map +1 -1
  200. package/lib/typescript/components/Choice.d.ts +4 -1
  201. package/lib/typescript/components/Choice.d.ts.map +1 -1
  202. package/lib/typescript/components/Dialog.d.ts +2 -1
  203. package/lib/typescript/components/Dialog.d.ts.map +1 -1
  204. package/lib/typescript/components/DropdownMenu.d.ts +3 -2
  205. package/lib/typescript/components/DropdownMenu.d.ts.map +1 -1
  206. package/lib/typescript/components/FallbackImage.d.ts.map +1 -1
  207. package/lib/typescript/components/Feed.d.ts.map +1 -1
  208. package/lib/typescript/components/Feedback.d.ts.map +1 -1
  209. package/lib/typescript/components/Filter.d.ts.map +1 -1
  210. package/lib/typescript/components/IndexLevel.d.ts +1 -1
  211. package/lib/typescript/components/Input.d.ts +5 -5
  212. package/lib/typescript/components/Input.d.ts.map +1 -1
  213. package/lib/typescript/components/InvitePopover.d.ts +1 -1
  214. package/lib/typescript/components/InvitePopover.d.ts.map +1 -1
  215. package/lib/typescript/components/LeaderboardCard.d.ts.map +1 -1
  216. package/lib/typescript/components/Loading.d.ts +1 -1
  217. package/lib/typescript/components/Loading.d.ts.map +1 -1
  218. package/lib/typescript/components/LoadingOverlay.d.ts +9 -9
  219. package/lib/typescript/components/LoadingOverlay.d.ts.map +1 -1
  220. package/lib/typescript/components/PageHeader.d.ts.map +1 -1
  221. package/lib/typescript/components/PostForm.d.ts.map +1 -1
  222. package/lib/typescript/components/RadioGroup.d.ts +4 -2
  223. package/lib/typescript/components/RadioGroup.d.ts.map +1 -1
  224. package/lib/typescript/components/SectionHeader.d.ts.map +1 -1
  225. package/lib/typescript/components/SegmentControl.d.ts +1 -1
  226. package/lib/typescript/components/Select.d.ts +2 -1
  227. package/lib/typescript/components/Select.d.ts.map +1 -1
  228. package/lib/typescript/components/Separator.d.ts.map +1 -1
  229. package/lib/typescript/components/Sheet.d.ts +2 -1
  230. package/lib/typescript/components/Sheet.d.ts.map +1 -1
  231. package/lib/typescript/components/Tabs.d.ts +4 -3
  232. package/lib/typescript/components/Tabs.d.ts.map +1 -1
  233. package/lib/typescript/components/Tag.d.ts +3 -1
  234. package/lib/typescript/components/Tag.d.ts.map +1 -1
  235. package/lib/typescript/components/ThemedIcon.d.ts +1 -1
  236. package/lib/typescript/components/Toast.d.ts +1 -1
  237. package/lib/typescript/components/Toast.d.ts.map +1 -1
  238. package/lib/typescript/editor/NoteEditor.d.ts +28 -8
  239. package/lib/typescript/editor/NoteEditor.d.ts.map +1 -1
  240. package/lib/typescript/editor/editorHtml.generated.d.ts.map +1 -1
  241. package/lib/typescript/editor/index.d.ts +1 -1
  242. package/lib/typescript/editor/index.d.ts.map +1 -1
  243. package/lib/typescript/editor/protocol.d.ts +4 -14
  244. package/lib/typescript/editor/protocol.d.ts.map +1 -1
  245. package/lib/typescript/hooks/use-toast.d.ts +1 -1
  246. package/lib/typescript/hooks/useThemeColor.d.ts.map +1 -1
  247. package/lib/typescript/index.d.ts +91 -91
  248. package/lib/typescript/index.d.ts.map +1 -1
  249. package/lib/typescript/lib/backdrop.d.ts +4 -0
  250. package/lib/typescript/lib/backdrop.d.ts.map +1 -0
  251. package/lib/typescript/lib/overlay-host.d.ts +1 -0
  252. package/lib/typescript/lib/overlay-host.d.ts.map +1 -1
  253. package/package.json +9 -14
  254. package/setup/metro.js +26 -73
  255. package/setup/tailwind.js +16 -8
  256. package/setup/tokens.native.css +51 -20
  257. package/src/components/Alert.tsx +37 -20
  258. package/src/components/AlertDialog.tsx +18 -19
  259. package/src/components/Badge.tsx +16 -15
  260. package/src/components/Button.tsx +64 -31
  261. package/src/components/CardNews.tsx +17 -6
  262. package/src/components/Carousel.tsx +5 -5
  263. package/src/components/Checkbox.tsx +17 -7
  264. package/src/components/Chip.tsx +49 -21
  265. package/src/components/Choice.tsx +34 -14
  266. package/src/components/Dialog.tsx +15 -5
  267. package/src/components/DropdownMenu.tsx +62 -14
  268. package/src/components/EmptyState.tsx +3 -3
  269. package/src/components/FallbackImage.tsx +32 -23
  270. package/src/components/Feed.tsx +2 -1
  271. package/src/components/Feedback.tsx +0 -1
  272. package/src/components/Filter.tsx +2 -3
  273. package/src/components/Input.tsx +83 -64
  274. package/src/components/InvitePopover.tsx +11 -3
  275. package/src/components/LeaderboardCard.tsx +4 -1
  276. package/src/components/Loading.tsx +17 -14
  277. package/src/components/LoadingOverlay.tsx +33 -50
  278. package/src/components/MemberRow.tsx +3 -3
  279. package/src/components/PageHeader.tsx +4 -1
  280. package/src/components/PostForm.tsx +28 -16
  281. package/src/components/PrizeCollection.tsx +3 -3
  282. package/src/components/RadioGroup.tsx +69 -12
  283. package/src/components/SectionHeader.tsx +4 -1
  284. package/src/components/Select.tsx +4 -3
  285. package/src/components/Separator.tsx +2 -2
  286. package/src/components/Sheet.tsx +15 -8
  287. package/src/components/Skeleton.tsx +1 -1
  288. package/src/components/Tabs.tsx +5 -4
  289. package/src/components/Tag.tsx +56 -35
  290. package/src/components/Toast.tsx +10 -6
  291. package/src/editor/NoteEditor.tsx +246 -72
  292. package/src/editor/editorHtml.generated.ts +1 -1
  293. package/src/editor/index.ts +8 -4
  294. package/src/editor/protocol.ts +4 -12
  295. package/src/hooks/useThemeColor.ts +41 -19
  296. package/src/index.ts +1 -1
  297. package/src/lib/backdrop.ts +16 -0
  298. package/src/lib/overlay-host.tsx +5 -5
@@ -8,14 +8,16 @@ var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _reactNativeWebview = _interopRequireDefault(require("react-native-webview"));
10
10
  var _nativewind = require("nativewind");
11
- var _editorHtmlGenerated = require("./editorHtml.generated.js");
12
11
  var _notesCore = require("@medway-ui/notes-core");
13
12
  var _native = require("@medway-ui/icons/native");
14
- var _Button = require("@/components/Button");
15
- var _Input = require("@/components/Input");
16
- var _Dialog = require("@/components/Dialog");
17
- var _useThemeColor = require("@/hooks/useThemeColor");
13
+ var _Button = require("../components/Button.js");
14
+ var _Input = require("../components/Input.js");
15
+ var _Text = require("../components/Text.js");
16
+ var _Dialog = require("../components/Dialog.js");
17
+ var _editorHtml = require("./editorHtml.generated.js");
18
18
  var _protocol = require("./protocol.js");
19
+ var _useThemeColor = require("../hooks/useThemeColor.js");
20
+ var _touchTarget = require("../lib/touch-target.js");
19
21
  var _jsxRuntime = require("react/jsx-runtime");
20
22
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
21
23
  function useEditorThemeColor(token, fallback) {
@@ -49,7 +51,35 @@ const WebView = _reactNativeWebview.default;
49
51
 
50
52
  /** The title is single-line; newlines can still arrive via paste. */
51
53
  const sanitizeTitle = value => value.replace(/\r?\n/g, " ");
52
- const DEFAULT_IMAGE_DELETE_GRACE_MS = 4000;
54
+
55
+ /**
56
+ * Enter in the multiline title still commits a "\n" (`onKeyPress` cannot
57
+ * cancel it): one newline added to the previous title is that key, dropped
58
+ * instead of becoming a trailing space. Pasted newlines still become spaces.
59
+ */
60
+ const nextTitle = (previous, value) => value.replace(/\r?\n/, "") === previous ? previous : sanitizeTitle(value);
61
+ const TOOLBAR_BUTTON_SIZE = 32;
62
+ const SLASH_MENU_WIDTH = 256;
63
+ const SLASH_MENU_GAP = 6;
64
+
65
+ /** Starts at the caret, pulled left so the menu never crosses the editor's right edge. */
66
+ const getSlashMenuLeft = (caretX, editorWidth) => editorWidth > 0 ? Math.max(SLASH_MENU_GAP, Math.min(caretX, editorWidth - SLASH_MENU_WIDTH - SLASH_MENU_GAP)) : caretX;
67
+ const TOOLBAR_BUTTON_HIT_SLOP = (0, _touchTarget.getMinTouchHitSlop)(TOOLBAR_BUTTON_SIZE);
68
+ const MAX_WEBVIEW_REMOUNTS = 3;
69
+ const WEBVIEW_REMOUNT_WINDOW_MS = 60_000;
70
+ const collectImageSrcs = doc => {
71
+ const srcs = new Set();
72
+ const walk = nodes => {
73
+ nodes.forEach(node => {
74
+ if (node.type === "image" && typeof node.attrs?.src === "string") {
75
+ srcs.add(node.attrs.src);
76
+ }
77
+ if (node.content) walk(node.content);
78
+ });
79
+ };
80
+ walk(doc.content);
81
+ return srcs;
82
+ };
53
83
  let requestCounter = 0;
54
84
  /** Correlates a `getDocument` request with the `document` response that answers it. */
55
85
  const nextRequestId = () => {
@@ -68,10 +98,11 @@ const useNoteEditorTheme = () => {
68
98
  } = (0, _nativewind.useColorScheme)();
69
99
  const background = (0, _useThemeColor.useThemeColor)("surface-card");
70
100
  const text = (0, _useThemeColor.useThemeColor)("content-paragraph");
71
- const heading = (0, _useThemeColor.useThemeColor)("content-title");
101
+ const heading = (0, _useThemeColor.useThemeColor)("gray-foreground");
72
102
  const muted = (0, _useThemeColor.useThemeColor)("gray-foreground-muted");
73
103
  const border = (0, _useThemeColor.useThemeColor)("gray-stroke-muted");
74
104
  const accent = (0, _useThemeColor.useThemeColor)("primary-foreground");
105
+ const link = (0, _useThemeColor.useThemeColor)("content-link");
75
106
  const codeBackground = useEditorThemeColor("gray-background-soft", "#F0F1F5");
76
107
  return (0, _react.useMemo)(() => ({
77
108
  colorScheme: colorScheme === "dark" ? "dark" : "light",
@@ -82,6 +113,7 @@ const useNoteEditorTheme = () => {
82
113
  placeholder: muted,
83
114
  border,
84
115
  accent,
116
+ link,
85
117
  caret: accent,
86
118
  selection: border,
87
119
  codeBackground,
@@ -92,7 +124,7 @@ const useNoteEditorTheme = () => {
92
124
  paddingHorizontal: 16,
93
125
  paddingTop: 12,
94
126
  paddingBottom: 32
95
- }), [colorScheme, background, text, heading, muted, border, accent, codeBackground]);
127
+ }), [colorScheme, background, text, heading, muted, border, accent, link, codeBackground]);
96
128
  };
97
129
 
98
130
  /** Every icon exported from `@medway-ui/icons/native` shares this component shape. */
@@ -225,14 +257,13 @@ const visibleSlashCommandIds = (commandIds, canUploadImage) => canUploadImage ?
225
257
  const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
226
258
  document: initialDocument,
227
259
  initialTitle,
228
- titlePlaceholder = "Title",
260
+ titlePlaceholder = "Título",
229
261
  onChange,
230
262
  editable = true,
231
263
  autofocus = false,
232
264
  toolbar = false,
233
265
  onImageUpload,
234
266
  onImageRemoved,
235
- imageDeleteGraceMs = DEFAULT_IMAGE_DELETE_GRACE_MS,
236
267
  onLinkPress,
237
268
  height,
238
269
  backgroundColor,
@@ -249,11 +280,9 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
249
280
  const [title, setTitle] = (0, _react.useState)(() => sanitizeTitle(initialTitle ?? ""));
250
281
  const titleInputRef = (0, _react.useRef)(null);
251
282
  const [titleSelection, setTitleSelection] = (0, _react.useState)(undefined);
252
- const autofocusTitleRef = (0, _react.useRef)(autofocus === true && title.length === 0);
283
+ const autofocusTitleRef = (0, _react.useRef)(Boolean(autofocus) && title.length === 0);
253
284
  (0, _react.useEffect)(() => {
254
285
  if (autofocusTitleRef.current) titleInputRef.current?.focus();
255
- // Mount-only: the one-time "new note" autofocus decision.
256
- // eslint-disable-next-line react-hooks/exhaustive-deps
257
286
  }, []);
258
287
  const focusTitleAtEnd = (0, _react.useCallback)(() => {
259
288
  titleInputRef.current?.focus();
@@ -279,11 +308,41 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
279
308
  setIsReady(false);
280
309
  setSlashState(_notesCore.EMPTY_SLASH_STATE);
281
310
  }, []);
311
+ const [webViewKey, setWebViewKey] = (0, _react.useState)(0);
312
+ const recentCrashesRef = (0, _react.useRef)([]);
313
+
314
+ // A WebView whose content/render process died can't be reused (Android
315
+ // tears it down after `onRenderProcessGone`), so a fresh one is mounted.
316
+ // Capped, so a document that crashes it on every load doesn't loop.
317
+ const handleWebViewCrash = (0, _react.useCallback)(() => {
318
+ handleWebViewReset();
319
+ const now = Date.now();
320
+ const recent = recentCrashesRef.current.filter(crashedAt => now - crashedAt < WEBVIEW_REMOUNT_WINDOW_MS);
321
+ if (recent.length >= MAX_WEBVIEW_REMOUNTS) {
322
+ recentCrashesRef.current = recent;
323
+ console.error(`@medway-ui/native NoteEditor: the editor WebView crashed ${MAX_WEBVIEW_REMOUNTS + 1} times within ${WEBVIEW_REMOUNT_WINDOW_MS / 1000}s; not remounting it again.`);
324
+ return;
325
+ }
326
+ recentCrashesRef.current = [...recent, now];
327
+ setWebViewKey(key => key + 1);
328
+ }, [handleWebViewReset]);
329
+
330
+ // What `setContent` sends on every `ready`, so a remount after a crash comes
331
+ // back with the latest content the host has seen rather than `initialDocument`.
332
+ const lastKnownContentRef = (0, _react.useRef)(initialDocument ?? _notesCore.EMPTY_DOCUMENT);
333
+ const uploadedThisSessionRef = (0, _react.useRef)(new Set());
334
+ const isMountedRef = (0, _react.useRef)(true);
335
+ const hasLoadedContentRef = (0, _react.useRef)(false);
336
+ // Bumped by `setDocument`: a `document` answering a request made before it
337
+ // describes the replaced note.
338
+ const documentGenerationRef = (0, _react.useRef)(0);
282
339
  const pendingDocumentRequests = (0, _react.useRef)(new Map());
283
340
  const requestDocument = (0, _react.useCallback)(() => {
284
341
  const requestId = nextRequestId();
342
+ const generation = documentGenerationRef.current;
285
343
  return new Promise((resolve, reject) => {
286
344
  pendingDocumentRequests.current.set(requestId, {
345
+ generation,
287
346
  resolve,
288
347
  reject
289
348
  });
@@ -314,23 +373,35 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
314
373
  (0, _react.useEffect)(() => {
315
374
  onLinkPressRef.current = onLinkPress;
316
375
  }, [onLinkPress]);
376
+ (0, _react.useEffect)(() => {
377
+ isMountedRef.current = true;
378
+ return () => {
379
+ isMountedRef.current = false;
380
+ };
381
+ }, []);
317
382
  const insertImage = (0, _react.useCallback)(async () => {
318
383
  const upload = onImageUploadRef.current;
319
384
  if (!upload || !editableRef.current) return;
320
385
  try {
321
386
  const result = await upload();
322
387
  if (!result) return;
323
- if (!editableRef.current) {
388
+ if (!isMountedRef.current || !editableRef.current) {
389
+ onImageRemovedRef.current?.(result.src);
390
+ return;
391
+ }
392
+ const src = (0, _notesCore.sanitizeImageSrc)(result.src);
393
+ if (!src) {
394
+ console.error("@medway-ui/native NoteEditor: onImageUpload resolved with an unsupported URL (only absolute http(s): or data:)", result.src);
324
395
  onImageRemovedRef.current?.(result.src);
325
396
  return;
326
397
  }
398
+ uploadedThisSessionRef.current.add(src);
327
399
  sendMutation({
328
400
  type: "insertImage",
329
- src: result.src,
401
+ src,
330
402
  alt: result.alt ?? null
331
403
  });
332
404
  } catch (error) {
333
- // eslint-disable-next-line no-console
334
405
  console.error("@medway-ui/native NoteEditor: onImageUpload failed", error);
335
406
  }
336
407
  }, [sendMutation]);
@@ -349,7 +420,6 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
349
420
  // Refused loudly rather than letting commands quietly not match
350
421
  // what the bundle understands.
351
422
  if (editorMessage.protocol !== _protocol.EDITOR_PROTOCOL_VERSION) {
352
- // eslint-disable-next-line no-console
353
423
  console.error(`@medway-ui/native NoteEditor: editor bundle reports protocol ${editorMessage.protocol}, expected ${_protocol.EDITOR_PROTOCOL_VERSION}. Rebuild editor-bundle (its generated HTML is stale).`);
354
424
  return;
355
425
  }
@@ -366,7 +436,11 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
366
436
  // round trip happens only when a host asked for `onChange`, which
367
437
  // keeps serialization per-settled-keystroke, not per-keystroke.
368
438
  if (onChangeRef.current) {
369
- requestDocument().then(doc => onChangeRef.current?.(doc)).catch(() => {});
439
+ const generation = documentGenerationRef.current;
440
+ requestDocument().then(doc => {
441
+ if (generation !== documentGenerationRef.current) return;
442
+ onChangeRef.current?.(doc);
443
+ }).catch(() => {});
370
444
  }
371
445
  return;
372
446
  case "document":
@@ -378,7 +452,13 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
378
452
  // whitelist/depth pass is what makes it an `EditorDocument`, and it
379
453
  // runs on the way out too, not just on `setContent` — the WebView
380
454
  // renders student content, so what it hands back is untrusted.
381
- pending.resolve((0, _notesCore.normalizeDocument)(editorMessage.doc));
455
+ const doc = (0, _notesCore.normalizeDocument)(editorMessage.doc);
456
+ // Still the answer to what was asked, but no longer the content
457
+ // to restore after a crash.
458
+ if (pending.generation === documentGenerationRef.current) {
459
+ lastKnownContentRef.current = doc;
460
+ }
461
+ pending.resolve(doc);
382
462
  return;
383
463
  }
384
464
  case "resize":
@@ -390,11 +470,7 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
390
470
  case "backspaceAtDocStart":
391
471
  focusTitleAtEnd();
392
472
  return;
393
- case "imageRemoved":
394
- onImageRemovedRef.current?.(editorMessage.src);
395
- return;
396
473
  case "error":
397
- // eslint-disable-next-line no-console
398
474
  console.error("@medway-ui/native NoteEditor:", editorMessage.message);
399
475
  return;
400
476
  default:
@@ -402,25 +478,24 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
402
478
  }
403
479
  }, [focusTitleAtEnd, isEmbedded, requestDocument]);
404
480
 
405
- // Fires once, when the bundle reports `ready`: the initial document, layout
406
- // mode and image-delete grace period are all read-once.
481
+ // Fires on every `ready`: the first load, and the remount after a crash.
482
+ // `editable`/`autofocus` stay out of deps on purpose: they seed the editor
483
+ // the bundle builds on load; `editable` is kept in sync by the next effect.
407
484
  (0, _react.useEffect)(() => {
408
485
  if (!isReady) return;
486
+ const isFirstLoad = !hasLoadedContentRef.current;
487
+ hasLoadedContentRef.current = true;
409
488
  send({
410
489
  type: "setContent",
411
- doc: initialDocument ?? _notesCore.EMPTY_DOCUMENT,
490
+ doc: lastKnownContentRef.current,
412
491
  editable,
413
- focus: autofocusTitleRef.current ? false : Boolean(autofocus),
414
- focusPosition: typeof autofocus === "string" ? autofocus : undefined,
415
- imageDeleteGraceMs
492
+ focus: isFirstLoad && !autofocusTitleRef.current && Boolean(autofocus),
493
+ focusPosition: typeof autofocus === "string" ? autofocus : undefined
416
494
  });
417
495
  if (isEmbedded) send({
418
496
  type: "setLayout",
419
497
  embedded: true
420
498
  });
421
- // `editable`/`autofocus`/`imageDeleteGraceMs` are excluded from deps on
422
- // purpose: they seed the *initial* editor, like `document`.
423
- // eslint-disable-next-line react-hooks/exhaustive-deps
424
499
  }, [isReady, send]);
425
500
 
426
501
  // `editable` is the one prop that stays reactive after mount.
@@ -448,37 +523,72 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
448
523
  type: "blur"
449
524
  }),
450
525
  getDocument: () => requestDocument(),
451
- getSaveData: async () => ({
452
- title,
453
- json: await requestDocument()
454
- }),
455
- setDocument: (doc, options) => send({
456
- type: "setContent",
457
- doc,
458
- editable,
459
- focus: Boolean(options?.focus),
460
- reset: false
461
- })
526
+ getSaveData: async () => {
527
+ const generation = documentGenerationRef.current;
528
+ const json = await requestDocument();
529
+ // After a `setDocument` the session uploads belong to the new note.
530
+ if (generation !== documentGenerationRef.current) {
531
+ return {
532
+ title,
533
+ json,
534
+ removedImages: []
535
+ };
536
+ }
537
+ const kept = collectImageSrcs(json);
538
+ const removedImages = [...uploadedThisSessionRef.current].filter(src => !kept.has(src));
539
+ return {
540
+ title,
541
+ json,
542
+ removedImages
543
+ };
544
+ },
545
+ setDocument: (doc, options) => {
546
+ documentGenerationRef.current += 1;
547
+ lastKnownContentRef.current = doc;
548
+ uploadedThisSessionRef.current.clear();
549
+ send({
550
+ type: "setContent",
551
+ doc,
552
+ editable,
553
+ focus: Boolean(options?.focus),
554
+ reset: false
555
+ });
556
+ }
462
557
  }), [title, editable, requestDocument, send]);
463
558
  const [linkDialogOpen, setLinkDialogOpen] = (0, _react.useState)(false);
464
559
  const [linkValue, setLinkValue] = (0, _react.useState)("");
560
+ const [linkInvalid, setLinkInvalid] = (0, _react.useState)(false);
465
561
  const openLinkDialog = (0, _react.useCallback)(() => {
466
562
  setLinkValue(selectionState.linkHref ?? "");
563
+ setLinkInvalid(false);
467
564
  setLinkDialogOpen(true);
468
565
  }, [selectionState.linkHref]);
566
+ const handleLinkValueChange = (0, _react.useCallback)(value => {
567
+ setLinkValue(value);
568
+ setLinkInvalid(false);
569
+ }, []);
469
570
  const confirmLink = (0, _react.useCallback)(() => {
470
- const href = linkValue.trim();
471
- sendMutation(href ? {
571
+ if (!linkValue.trim()) {
572
+ sendMutation({
573
+ type: "command",
574
+ command: {
575
+ name: "unsetLink"
576
+ }
577
+ });
578
+ setLinkDialogOpen(false);
579
+ return;
580
+ }
581
+ const href = (0, _notesCore.normalizeLinkInput)(linkValue);
582
+ if (!href) {
583
+ setLinkInvalid(true);
584
+ return;
585
+ }
586
+ sendMutation({
472
587
  type: "command",
473
588
  command: {
474
589
  name: "setLink",
475
590
  href
476
591
  }
477
- } : {
478
- type: "command",
479
- command: {
480
- name: "unsetLink"
481
- }
482
592
  });
483
593
  setLinkDialogOpen(false);
484
594
  }, [linkValue, sendMutation]);
@@ -496,6 +606,10 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
496
606
  if (id === "image") insertImage();
497
607
  }, [editable, send, insertImage]);
498
608
  const containerHeight = isEmbedded ? contentHeight : undefined;
609
+ const [editorWidth, setEditorWidth] = (0, _react.useState)(0);
610
+ const handleEditorLayout = (0, _react.useCallback)(event => {
611
+ setEditorWidth(event.nativeEvent.layout.width);
612
+ }, []);
499
613
  const resolvedBackground = backgroundColor ?? theme.background;
500
614
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
501
615
  style: [isEmbedded ? styles.containerEmbedded : styles.container, {
@@ -511,11 +625,10 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
511
625
  ref: titleInputRef,
512
626
  multiline: true,
513
627
  value: title,
514
- onChangeText: value => setTitle(sanitizeTitle(value)),
628
+ onChangeText: value => setTitle(previous => nextTitle(previous, value)),
515
629
  onKeyPress: event => {
516
- // Enter moves focus into the content's first block, never inserts
517
- // a line break; `sanitizeTitle` is the backstop if the platform
518
- // commits the `\n` before this handler runs.
630
+ // Enter moves focus into the content's first block; the `\n` it
631
+ // still commits is dropped by `nextTitle`.
519
632
  if (event.nativeEvent.key !== "Enter") return;
520
633
  send({
521
634
  type: "focus",
@@ -538,6 +651,7 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
538
651
  style: {
539
652
  flex: isEmbedded ? undefined : 1
540
653
  },
654
+ onLayout: handleEditorLayout,
541
655
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(WebView, {
542
656
  ref: webViewRef,
543
657
  testID: testID,
@@ -546,7 +660,7 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
546
660
  height: containerHeight
547
661
  } : undefined,
548
662
  source: {
549
- html: _editorHtmlGenerated.EDITOR_HTML
663
+ html: _editorHtml.EDITOR_HTML
550
664
  }
551
665
  // Anchored: `react-native-webview` treats each entry as an
552
666
  // unanchored pattern, so a bare "about:blank" also admits
@@ -555,8 +669,8 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
555
669
  originWhitelist: ["^about:blank$"],
556
670
  onMessage: handleMessage,
557
671
  onLoadStart: handleWebViewReset,
558
- onContentProcessDidTerminate: handleWebViewReset,
559
- onRenderProcessGone: handleWebViewReset
672
+ onContentProcessDidTerminate: handleWebViewCrash,
673
+ onRenderProcessGone: handleWebViewCrash
560
674
  // Links already leave the editor via the `linkPress` message, so
561
675
  // nothing rendered here needs to navigate. Allowing only the
562
676
  // initial `about:blank` load closes off `data:`/script-driven
@@ -592,8 +706,9 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
592
706
  } : null, {
593
707
  backgroundColor: resolvedBackground
594
708
  }]
595
- }), slashState.active && slashState.caret ? /*#__PURE__*/(0, _jsxRuntime.jsx)(NoteEditorSlashMenu, {
709
+ }, webViewKey), slashState.active && slashState.caret ? /*#__PURE__*/(0, _jsxRuntime.jsx)(NoteEditorSlashMenu, {
596
710
  slash: slashState,
711
+ editorWidth: editorWidth,
597
712
  canUploadImage: Boolean(onImageUpload),
598
713
  onSelect: handleSlashSelect
599
714
  }) : null]
@@ -605,16 +720,21 @@ const NoteEditor = /*#__PURE__*/(0, _react.forwardRef)(({
605
720
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Dialog.DialogTitle, {
606
721
  children: "Inserir link"
607
722
  })
608
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Dialog.DialogContent, {
609
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Input.Input, {
723
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_Dialog.DialogContent, {
724
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Input.Input, {
610
725
  value: linkValue,
611
- onChangeText: setLinkValue,
726
+ onChangeText: handleLinkValueChange,
727
+ invalid: linkInvalid,
612
728
  placeholder: "https://",
613
729
  label: "URL",
614
730
  autoCapitalize: "none",
615
731
  autoCorrect: false,
616
732
  keyboardType: "url"
617
- })
733
+ }), linkInvalid ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Text.Text, {
734
+ accessibilityRole: "alert",
735
+ className: "mt-1 text-xs text-feedback-error-foreground",
736
+ children: "Link inv\xE1lido. Use um endere\xE7o como medway.com.br"
737
+ }) : null]
618
738
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Dialog.DialogFooter, {
619
739
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.Button, {
620
740
  onPress: confirmLink,
@@ -662,6 +782,7 @@ function NoteEditorToolbar({
662
782
  selected: active
663
783
  },
664
784
  disabled: disabled,
785
+ hitSlop: TOOLBAR_BUTTON_HIT_SLOP,
665
786
  onPress: item.onPress ?? (() => item.command && onCommand(item.command)),
666
787
  style: buttonStyle,
667
788
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Icon, {
@@ -684,6 +805,7 @@ function NoteEditorToolbar({
684
805
  disabled: allDisabled
685
806
  },
686
807
  disabled: allDisabled,
808
+ hitSlop: TOOLBAR_BUTTON_HIT_SLOP,
687
809
  onPress: onImagePress,
688
810
  style: [styles.toolbarButton, allDisabled ? styles.toolbarButtonDisabled : null],
689
811
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_native.Image, {
@@ -705,6 +827,7 @@ function NoteEditorToolbar({
705
827
  */
706
828
  function NoteEditorSlashMenu({
707
829
  slash,
830
+ editorWidth,
708
831
  canUploadImage,
709
832
  onSelect
710
833
  }) {
@@ -717,8 +840,8 @@ function NoteEditorSlashMenu({
717
840
  const commandIds = visibleSlashCommandIds(slash.commandIds, canUploadImage);
718
841
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
719
842
  style: [styles.slashMenuShadow, {
720
- left: slash.caret.x,
721
- top: slash.caret.y + slash.caret.height + 6
843
+ left: getSlashMenuLeft(slash.caret.x, editorWidth),
844
+ top: slash.caret.y + slash.caret.height + SLASH_MENU_GAP
722
845
  }],
723
846
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
724
847
  style: [styles.slashMenu, {
@@ -804,8 +927,8 @@ const styles = _reactNative.StyleSheet.create({
804
927
  marginHorizontal: 4
805
928
  },
806
929
  toolbarButton: {
807
- width: 32,
808
- height: 32,
930
+ width: TOOLBAR_BUTTON_SIZE,
931
+ height: TOOLBAR_BUTTON_SIZE,
809
932
  alignItems: "center",
810
933
  justifyContent: "center",
811
934
  borderRadius: 6
@@ -816,7 +939,7 @@ const styles = _reactNative.StyleSheet.create({
816
939
  slashMenuShadow: {
817
940
  position: "absolute",
818
941
  zIndex: 50,
819
- width: 256,
942
+ width: SLASH_MENU_WIDTH,
820
943
  shadowColor: "#000",
821
944
  shadowOffset: {
822
945
  width: 0,