@webority-technologies/mobile-ui 0.0.1 → 0.0.2

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 (134) hide show
  1. package/README.md +2 -1
  2. package/lib/commonjs/components/AuthedImage/AuthedImage.js +225 -0
  3. package/lib/commonjs/components/AuthedImage/authedImageCache.js +253 -0
  4. package/lib/commonjs/components/AuthedImage/index.js +45 -0
  5. package/lib/commonjs/components/BottomSheet/BottomSheet.js +10 -4
  6. package/lib/commonjs/components/Drawer/Drawer.js +6 -3
  7. package/lib/commonjs/components/ErrorBoundary/ErrorBoundary.js +136 -0
  8. package/lib/commonjs/components/ErrorBoundary/index.js +27 -0
  9. package/lib/commonjs/components/ErrorBoundary/useErrorHandler.js +24 -0
  10. package/lib/commonjs/components/ErrorBoundary/withErrorBoundary.js +28 -0
  11. package/lib/commonjs/components/FilePicker/FilePicker.js +1 -1
  12. package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +14 -1
  13. package/lib/commonjs/components/ImageGallery/ImageGallery.js +13 -0
  14. package/lib/commonjs/components/InlineSelect/InlineSelect.js +1 -1
  15. package/lib/commonjs/components/Input/Input.js +6 -0
  16. package/lib/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +62 -13
  17. package/lib/commonjs/components/LoaderOverlay/LoaderOverlay.js +203 -0
  18. package/lib/commonjs/components/LoaderOverlay/index.js +25 -0
  19. package/lib/commonjs/components/Markdown/Markdown.js +274 -0
  20. package/lib/commonjs/components/Markdown/index.js +13 -0
  21. package/lib/commonjs/components/Markdown/parse.js +229 -0
  22. package/lib/commonjs/components/OTPInput/OTPInput.js +5 -0
  23. package/lib/commonjs/components/OptionSheet/OptionSheet.js +3 -3
  24. package/lib/commonjs/components/PhoneNumberInput/PhoneNumberInput.js +1 -1
  25. package/lib/commonjs/components/Radio/Radio.js +9 -0
  26. package/lib/commonjs/components/Rating/Rating.js +5 -0
  27. package/lib/commonjs/components/RichTextEditor/RichTextEditor.js +311 -0
  28. package/lib/commonjs/components/RichTextEditor/index.js +20 -0
  29. package/lib/commonjs/components/RichTextEditor/markdownActions.js +213 -0
  30. package/lib/commonjs/components/SearchBar/SearchBar.js +7 -0
  31. package/lib/commonjs/components/Select/Select.js +1 -1
  32. package/lib/commonjs/components/Skeleton/Skeleton.js +17 -36
  33. package/lib/commonjs/components/Slider/Slider.js +21 -5
  34. package/lib/commonjs/components/Swipeable/Swipeable.js +5 -2
  35. package/lib/commonjs/components/TimePicker/TimePicker.js +11 -0
  36. package/lib/commonjs/components/index.js +230 -141
  37. package/lib/commonjs/form/FormContext.js +13 -1
  38. package/lib/commonjs/form/useField.js +13 -1
  39. package/lib/commonjs/theme/Gradient.js +44 -41
  40. package/lib/commonjs/theme/index.js +24 -2
  41. package/lib/commonjs/utils/hapticUtils.js +38 -18
  42. package/lib/module/components/AuthedImage/AuthedImage.js +221 -0
  43. package/lib/module/components/AuthedImage/authedImageCache.js +239 -0
  44. package/lib/module/components/AuthedImage/index.js +5 -0
  45. package/lib/module/components/BottomSheet/BottomSheet.js +10 -4
  46. package/lib/module/components/Drawer/Drawer.js +6 -3
  47. package/lib/module/components/ErrorBoundary/ErrorBoundary.js +130 -0
  48. package/lib/module/components/ErrorBoundary/index.js +6 -0
  49. package/lib/module/components/ErrorBoundary/useErrorHandler.js +20 -0
  50. package/lib/module/components/ErrorBoundary/withErrorBoundary.js +23 -0
  51. package/lib/module/components/FilePicker/FilePicker.js +1 -1
  52. package/lib/module/components/FloatingActionButton/FloatingActionButton.js +14 -1
  53. package/lib/module/components/ImageGallery/ImageGallery.js +13 -0
  54. package/lib/module/components/InlineSelect/InlineSelect.js +1 -1
  55. package/lib/module/components/Input/Input.js +6 -0
  56. package/lib/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +63 -14
  57. package/lib/module/components/LoaderOverlay/LoaderOverlay.js +196 -0
  58. package/lib/module/components/LoaderOverlay/index.js +4 -0
  59. package/lib/module/components/Markdown/Markdown.js +268 -0
  60. package/lib/module/components/Markdown/index.js +4 -0
  61. package/lib/module/components/Markdown/parse.js +223 -0
  62. package/lib/module/components/OTPInput/OTPInput.js +5 -0
  63. package/lib/module/components/OptionSheet/OptionSheet.js +3 -3
  64. package/lib/module/components/PhoneNumberInput/PhoneNumberInput.js +1 -1
  65. package/lib/module/components/Radio/Radio.js +9 -0
  66. package/lib/module/components/Rating/Rating.js +5 -0
  67. package/lib/module/components/RichTextEditor/RichTextEditor.js +306 -0
  68. package/lib/module/components/RichTextEditor/index.js +5 -0
  69. package/lib/module/components/RichTextEditor/markdownActions.js +207 -0
  70. package/lib/module/components/SearchBar/SearchBar.js +7 -0
  71. package/lib/module/components/Select/Select.js +1 -1
  72. package/lib/module/components/Skeleton/Skeleton.js +17 -36
  73. package/lib/module/components/Slider/Slider.js +21 -5
  74. package/lib/module/components/Swipeable/Swipeable.js +5 -2
  75. package/lib/module/components/TimePicker/TimePicker.js +11 -0
  76. package/lib/module/components/index.js +5 -0
  77. package/lib/module/form/FormContext.js +13 -1
  78. package/lib/module/form/useField.js +13 -1
  79. package/lib/module/theme/Gradient.js +41 -38
  80. package/lib/module/theme/index.js +17 -1
  81. package/lib/module/utils/hapticUtils.js +37 -18
  82. package/lib/typescript/commonjs/components/AuthedImage/AuthedImage.d.ts +22 -0
  83. package/lib/typescript/commonjs/components/AuthedImage/authedImageCache.d.ts +48 -0
  84. package/lib/typescript/commonjs/components/AuthedImage/index.d.ts +5 -0
  85. package/lib/typescript/commonjs/components/Button/Button.d.ts +1 -3
  86. package/lib/typescript/commonjs/components/Card/Card.d.ts +1 -2
  87. package/lib/typescript/commonjs/components/ErrorBoundary/ErrorBoundary.d.ts +38 -0
  88. package/lib/typescript/commonjs/components/ErrorBoundary/index.d.ts +5 -0
  89. package/lib/typescript/commonjs/components/ErrorBoundary/useErrorHandler.d.ts +9 -0
  90. package/lib/typescript/commonjs/components/ErrorBoundary/withErrorBoundary.d.ts +9 -0
  91. package/lib/typescript/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +15 -0
  92. package/lib/typescript/commonjs/components/LoaderOverlay/LoaderOverlay.d.ts +25 -0
  93. package/lib/typescript/commonjs/components/LoaderOverlay/index.d.ts +3 -0
  94. package/lib/typescript/commonjs/components/Markdown/Markdown.d.ts +29 -0
  95. package/lib/typescript/commonjs/components/Markdown/index.d.ts +3 -0
  96. package/lib/typescript/commonjs/components/Markdown/parse.d.ts +58 -0
  97. package/lib/typescript/commonjs/components/OptionSheet/OptionSheet.d.ts +8 -1
  98. package/lib/typescript/commonjs/components/RichTextEditor/RichTextEditor.d.ts +48 -0
  99. package/lib/typescript/commonjs/components/RichTextEditor/index.d.ts +5 -0
  100. package/lib/typescript/commonjs/components/RichTextEditor/markdownActions.d.ts +35 -0
  101. package/lib/typescript/commonjs/components/Skeleton/Skeleton.d.ts +1 -1
  102. package/lib/typescript/commonjs/components/index.d.ts +10 -0
  103. package/lib/typescript/commonjs/form/FormContext.d.ts +13 -3
  104. package/lib/typescript/commonjs/theme/Gradient.d.ts +18 -3
  105. package/lib/typescript/commonjs/theme/index.d.ts +16 -1
  106. package/lib/typescript/commonjs/theme/types.d.ts +2 -2
  107. package/lib/typescript/commonjs/utils/hapticUtils.d.ts +4 -0
  108. package/lib/typescript/module/components/AuthedImage/AuthedImage.d.ts +22 -0
  109. package/lib/typescript/module/components/AuthedImage/authedImageCache.d.ts +48 -0
  110. package/lib/typescript/module/components/AuthedImage/index.d.ts +5 -0
  111. package/lib/typescript/module/components/Button/Button.d.ts +1 -3
  112. package/lib/typescript/module/components/Card/Card.d.ts +1 -2
  113. package/lib/typescript/module/components/ErrorBoundary/ErrorBoundary.d.ts +38 -0
  114. package/lib/typescript/module/components/ErrorBoundary/index.d.ts +5 -0
  115. package/lib/typescript/module/components/ErrorBoundary/useErrorHandler.d.ts +9 -0
  116. package/lib/typescript/module/components/ErrorBoundary/withErrorBoundary.d.ts +9 -0
  117. package/lib/typescript/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +15 -0
  118. package/lib/typescript/module/components/LoaderOverlay/LoaderOverlay.d.ts +25 -0
  119. package/lib/typescript/module/components/LoaderOverlay/index.d.ts +3 -0
  120. package/lib/typescript/module/components/Markdown/Markdown.d.ts +29 -0
  121. package/lib/typescript/module/components/Markdown/index.d.ts +3 -0
  122. package/lib/typescript/module/components/Markdown/parse.d.ts +58 -0
  123. package/lib/typescript/module/components/OptionSheet/OptionSheet.d.ts +8 -1
  124. package/lib/typescript/module/components/RichTextEditor/RichTextEditor.d.ts +48 -0
  125. package/lib/typescript/module/components/RichTextEditor/index.d.ts +5 -0
  126. package/lib/typescript/module/components/RichTextEditor/markdownActions.d.ts +35 -0
  127. package/lib/typescript/module/components/Skeleton/Skeleton.d.ts +1 -1
  128. package/lib/typescript/module/components/index.d.ts +10 -0
  129. package/lib/typescript/module/form/FormContext.d.ts +13 -3
  130. package/lib/typescript/module/theme/Gradient.d.ts +18 -3
  131. package/lib/typescript/module/theme/index.d.ts +16 -1
  132. package/lib/typescript/module/theme/types.d.ts +2 -2
  133. package/lib/typescript/module/utils/hapticUtils.d.ts +4 -0
  134. package/package.json +20 -13
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * A deliberately small CommonMark subset, parsed without a dependency.
5
+ *
6
+ * The scope is what our own content actually contains — release notes, help
7
+ * text, descriptions written by people in a web editor — not a spec-complete
8
+ * renderer. Nested lists, tables, footnotes, HTML blocks and reference links
9
+ * are out of scope on purpose: supporting them means a parser nobody here can
10
+ * maintain, and content that needs them is a sign it should not be markdown.
11
+ *
12
+ * Kept separate from the component so the grammar can be tested as data, with
13
+ * no rendering involved.
14
+ */
15
+
16
+ /**
17
+ * Inline parsing is a single left-to-right scan rather than nested regex
18
+ * replacement: replacement cannot express "a `code` span suppresses every other
19
+ * marker inside it", which is the one rule that makes documentation readable.
20
+ */
21
+ const INLINE_PATTERNS = [
22
+ // Code first: everything inside a span is literal.
23
+ {
24
+ re: /^`([^`]+)`/,
25
+ build: m => ({
26
+ type: 'code',
27
+ value: m[1]
28
+ })
29
+ }, {
30
+ re: /^\[([^\]]*)\]\(([^)\s]+)\)/,
31
+ build: (m, parse) => ({
32
+ type: 'link',
33
+ href: m[2],
34
+ children: parse(m[1])
35
+ })
36
+ },
37
+ // Non-greedy across ANY character, not "everything except the delimiter":
38
+ // the excluding form cannot match `**bold with *italic* inside**`, because
39
+ // the content legitimately contains the single-character marker. `**` is
40
+ // listed before `*` so the longer delimiter always wins.
41
+ {
42
+ re: /^\*\*([\s\S]+?)\*\*/,
43
+ build: (m, parse) => ({
44
+ type: 'strong',
45
+ children: parse(m[1])
46
+ })
47
+ }, {
48
+ re: /^__([\s\S]+?)__/,
49
+ build: (m, parse) => ({
50
+ type: 'strong',
51
+ children: parse(m[1])
52
+ })
53
+ }, {
54
+ re: /^~~([\s\S]+?)~~/,
55
+ build: (m, parse) => ({
56
+ type: 'strike',
57
+ children: parse(m[1])
58
+ })
59
+ }, {
60
+ re: /^\*([^*\n]+)\*/,
61
+ build: (m, parse) => ({
62
+ type: 'em',
63
+ children: parse(m[1])
64
+ })
65
+ }, {
66
+ re: /^_([^_\n]+)_/,
67
+ build: (m, parse) => ({
68
+ type: 'em',
69
+ children: parse(m[1])
70
+ })
71
+ }];
72
+ export const parseInline = input => {
73
+ const out = [];
74
+ let buffer = '';
75
+ let rest = input;
76
+ const flush = () => {
77
+ if (buffer) {
78
+ out.push({
79
+ type: 'text',
80
+ value: buffer
81
+ });
82
+ buffer = '';
83
+ }
84
+ };
85
+ while (rest.length > 0) {
86
+ // A backslash escapes the next character, so a literal asterisk is writable.
87
+ if (rest.startsWith('\\') && rest.length > 1) {
88
+ buffer += rest[1];
89
+ rest = rest.slice(2);
90
+ continue;
91
+ }
92
+ let matched = false;
93
+ for (const {
94
+ re,
95
+ build
96
+ } of INLINE_PATTERNS) {
97
+ const m = re.exec(rest);
98
+ if (m) {
99
+ flush();
100
+ out.push(build(m, parseInline));
101
+ rest = rest.slice(m[0].length);
102
+ matched = true;
103
+ break;
104
+ }
105
+ }
106
+ if (!matched) {
107
+ buffer += rest[0];
108
+ rest = rest.slice(1);
109
+ }
110
+ }
111
+ flush();
112
+ return out;
113
+ };
114
+
115
+ /**
116
+ * Indent to nesting depth. Two spaces per level is the CommonMark-ish
117
+ * convention people actually type; a tab counts as one level. Depth is capped
118
+ * because the renderer indents by depth and a runaway indent would push the
119
+ * text off screen rather than fail visibly.
120
+ */
121
+ const MAX_LIST_DEPTH = 4;
122
+ const depthOf = indent => {
123
+ const spaces = indent.replace(/\t/g, ' ').length;
124
+ return Math.min(Math.floor(spaces / 2), MAX_LIST_DEPTH);
125
+ };
126
+ const HEADING = /^(#{1,6})\s+(.*)$/;
127
+ const UNORDERED = /^(\s*)[-*+]\s+(.*)$/;
128
+ const ORDERED = /^(\s*)(\d+)[.)]\s+(.*)$/;
129
+ const QUOTE = /^\s*>\s?(.*)$/;
130
+ const RULE = /^\s*([-*_])\s*(?:\1\s*){2,}$/;
131
+ const FENCE = /^\s*```\s*(\S+)?\s*$/;
132
+ export const parseMarkdown = source => {
133
+ const blocks = [];
134
+ const lines = (source ?? '').replace(/\r\n?/g, '\n').split('\n');
135
+ let paragraph = [];
136
+ const flushParagraph = () => {
137
+ if (paragraph.length > 0) {
138
+ blocks.push({
139
+ type: 'paragraph',
140
+ children: parseInline(paragraph.join(' ').trim())
141
+ });
142
+ paragraph = [];
143
+ }
144
+ };
145
+ for (let i = 0; i < lines.length; i++) {
146
+ const line = lines[i];
147
+ const fence = FENCE.exec(line);
148
+ if (fence) {
149
+ flushParagraph();
150
+ const body = [];
151
+ i++;
152
+ while (i < lines.length && !FENCE.test(lines[i])) {
153
+ body.push(lines[i]);
154
+ i++;
155
+ }
156
+ blocks.push({
157
+ type: 'codeBlock',
158
+ value: body.join('\n'),
159
+ language: fence[1]
160
+ });
161
+ continue;
162
+ }
163
+ if (line.trim() === '') {
164
+ flushParagraph();
165
+ continue;
166
+ }
167
+ // Checked before the list patterns: `---` is also a valid bullet to them.
168
+ if (RULE.test(line)) {
169
+ flushParagraph();
170
+ blocks.push({
171
+ type: 'rule'
172
+ });
173
+ continue;
174
+ }
175
+ const heading = HEADING.exec(line);
176
+ if (heading) {
177
+ flushParagraph();
178
+ blocks.push({
179
+ type: 'heading',
180
+ level: heading[1].length,
181
+ children: parseInline(heading[2])
182
+ });
183
+ continue;
184
+ }
185
+ const ordered = ORDERED.exec(line);
186
+ if (ordered) {
187
+ flushParagraph();
188
+ blocks.push({
189
+ type: 'listItem',
190
+ ordered: true,
191
+ index: Number(ordered[2]),
192
+ depth: depthOf(ordered[1]),
193
+ children: parseInline(ordered[3])
194
+ });
195
+ continue;
196
+ }
197
+ const unordered = UNORDERED.exec(line);
198
+ if (unordered) {
199
+ flushParagraph();
200
+ blocks.push({
201
+ type: 'listItem',
202
+ ordered: false,
203
+ index: 0,
204
+ depth: depthOf(unordered[1]),
205
+ children: parseInline(unordered[2])
206
+ });
207
+ continue;
208
+ }
209
+ const quote = QUOTE.exec(line);
210
+ if (quote) {
211
+ flushParagraph();
212
+ blocks.push({
213
+ type: 'quote',
214
+ children: parseInline(quote[1])
215
+ });
216
+ continue;
217
+ }
218
+ paragraph.push(line.trim());
219
+ }
220
+ flushParagraph();
221
+ return blocks;
222
+ };
223
+ //# sourceMappingURL=parse.js.map
@@ -144,6 +144,11 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
144
144
  if (previous === focusedIndex) {
145
145
  return;
146
146
  }
147
+ // Equivalent-mutant note: this branch is unreachable, so removing it
148
+ // cannot change behaviour. Reaching here already proves
149
+ // `previous !== focusedIndex` (the check above returned otherwise), so
150
+ // `previous === -1 && focusedIndex === -1` — which requires them equal —
151
+ // can never be true.
147
152
  if (previous === -1 && focusedIndex === -1) {
148
153
  return;
149
154
  }
@@ -31,7 +31,7 @@ export const OptionSheet = props => {
31
31
  title,
32
32
  options,
33
33
  labelOf,
34
- valueOf,
34
+ getValue,
35
35
  disabledOf,
36
36
  descriptionOf,
37
37
  iconOf,
@@ -173,7 +173,7 @@ export const OptionSheet = props => {
173
173
  item
174
174
  }) => {
175
175
  const itemLabel = labelOf(item);
176
- const itemValue = valueOf(item);
176
+ const itemValue = getValue(item);
177
177
  const itemDescription = descriptionOf(item);
178
178
  const itemIcon = iconOf(item);
179
179
  const selected = isSelected(itemValue);
@@ -311,7 +311,7 @@ export const OptionSheet = props => {
311
311
  })
312
312
  }) : null, /*#__PURE__*/_jsx(FlatList, {
313
313
  data: filteredOptions,
314
- keyExtractor: item => String(valueOf(item)),
314
+ keyExtractor: item => String(getValue(item)),
315
315
  renderItem: renderOption,
316
316
  keyboardShouldPersistTaps: "handled",
317
317
  showsVerticalScrollIndicator: true,
@@ -203,7 +203,7 @@ export const PhoneNumberInput = /*#__PURE__*/forwardRef((props, ref) => {
203
203
  title: countrySheetTitle,
204
204
  options: countries,
205
205
  labelOf: o => `${o.label} (+${o.code})`,
206
- valueOf: o => o.iso,
206
+ getValue: o => o.iso,
207
207
  disabledOf: () => false,
208
208
  descriptionOf: () => undefined,
209
209
  iconOf: () => null,
@@ -43,6 +43,15 @@ const Radio = /*#__PURE__*/forwardRef((props, ref) => {
43
43
  const theme = useTheme();
44
44
  const styles = useMemo(() => buildStyles(theme), [theme]);
45
45
  const ctx = useRadioGroup();
46
+
47
+ // `value` is required by the props, but a JavaScript consumer can still omit
48
+ // it, and the failure is silent rather than loud: inside a group,
49
+ // `selectedValue === undefined` makes every unselected radio look selected the
50
+ // moment nothing is chosen, and pressing one reports `undefined` as the
51
+ // choice. A conformance test omitted it for months without anything noticing.
52
+ if (__DEV__ && value === undefined) {
53
+ console.warn('[@webority-technologies/mobile-ui] Radio was rendered without a `value`. ' + 'Selection compares against it, so the group cannot track which option is chosen.');
54
+ }
46
55
  const selected = ctx ? ctx.selectedValue === value : !!selectedProp;
47
56
  const disabled = ctx ? ctx.disabled || disabledProp : disabledProp;
48
57
  const fillColor = toneColor(theme, tone);
@@ -121,6 +121,11 @@ const Rating = /*#__PURE__*/forwardRef((props, ref) => {
121
121
  const pulseScale = theme.components.rating?.pulseScale ?? 1.2;
122
122
  const pulse = useCallback(idx => {
123
123
  const v = scaleRefs[idx];
124
+ // Equivalent mutant, not an untested guard: `pulse` is only ever invoked from
125
+ // `commit` with an `animatedIndex` sourced from the star-loop below, which
126
+ // only pushes indices already confirmed present in `scaleRefs` (same render,
127
+ // same `max`). `v` is therefore always defined here — no reachable input
128
+ // makes this `return` fire, so no test can kill its removal.
124
129
  if (!v) {
125
130
  return;
126
131
  }
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+
3
+ import React, { forwardRef, useCallback, useMemo, useState } from 'react';
4
+ import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
5
+ import { fontFor, useTheme } from "../../theme/index.js";
6
+ import { resolveHaptic, triggerHaptic } from "../../utils/index.js";
7
+ import { Skeleton } from "../Skeleton/index.js";
8
+ import { applyMarkdownAction, normalizeSelection } from "./markdownActions.js";
9
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ /** Glyph + spoken name per action. The glyph IS the markdown marker wherever one reads as itself. */
11
+ const ACTION_META = {
12
+ bold: {
13
+ glyph: 'B',
14
+ label: 'Bold'
15
+ },
16
+ italic: {
17
+ glyph: 'I',
18
+ label: 'Italic'
19
+ },
20
+ strikethrough: {
21
+ glyph: 'S',
22
+ label: 'Strikethrough'
23
+ },
24
+ code: {
25
+ glyph: '</>',
26
+ label: 'Inline code'
27
+ },
28
+ link: {
29
+ glyph: 'link',
30
+ label: 'Link'
31
+ },
32
+ heading1: {
33
+ glyph: 'H1',
34
+ label: 'Heading 1'
35
+ },
36
+ heading2: {
37
+ glyph: 'H2',
38
+ label: 'Heading 2'
39
+ },
40
+ bulletList: {
41
+ glyph: '•',
42
+ label: 'Bulleted list'
43
+ },
44
+ numberedList: {
45
+ glyph: '1.',
46
+ label: 'Numbered list'
47
+ },
48
+ quote: {
49
+ glyph: '>',
50
+ label: 'Quote'
51
+ }
52
+ };
53
+ const DEFAULT_ACTIONS = ['bold', 'italic', 'strikethrough', 'code', 'link', 'heading1', 'heading2', 'bulletList', 'numberedList', 'quote'];
54
+ const DEFAULT_MIN_HEIGHT = 160;
55
+
56
+ /**
57
+ * A markdown editor: a themed multiline field plus a toolbar that wraps the
58
+ * current selection in the markers our own `<Markdown>` renderer reads back.
59
+ *
60
+ * Deliberately NOT a WYSIWYG surface. Rich-text-on-RN means a WebView, which is
61
+ * exactly what replacing the stale `react-native-pell-rich-editor` was meant to
62
+ * escape: markdown in, markdown out, one plain TextInput, no bridge.
63
+ */
64
+ export const RichTextEditor = /*#__PURE__*/forwardRef((props, ref) => {
65
+ const {
66
+ value,
67
+ onChangeText,
68
+ placeholder,
69
+ actions = DEFAULT_ACTIONS,
70
+ disabled = false,
71
+ loading = false,
72
+ minHeight = DEFAULT_MIN_HEIGHT,
73
+ autoFocus = false,
74
+ accessibilityLabel,
75
+ haptic,
76
+ style,
77
+ toolbarStyle,
78
+ inputStyle,
79
+ testID
80
+ } = props;
81
+ const theme = useTheme();
82
+ const styles = useMemo(() => buildStyles(theme), [theme]);
83
+ const text = value ?? '';
84
+ const [selection, setSelection] = useState({
85
+ start: 0,
86
+ end: 0
87
+ });
88
+ const [focused, setFocused] = useState(false);
89
+ /**
90
+ * Only set for the render immediately after a toolbar action. Holding the
91
+ * TextInput's `selection` permanently fights the platform keyboard on
92
+ * Android, so it is released as soon as the field reports the caret moved.
93
+ */
94
+ const [forcedSelection, setForcedSelection] = useState(null);
95
+ const handleSelectionChange = useCallback(event => {
96
+ setSelection(event.nativeEvent.selection);
97
+ setForcedSelection(null);
98
+ }, []);
99
+ const handleChangeText = useCallback(next => {
100
+ setForcedSelection(null);
101
+ onChangeText?.(next);
102
+ }, [onChangeText]);
103
+ const runAction = useCallback(action => {
104
+ if (disabled) {
105
+ return;
106
+ }
107
+ const h = resolveHaptic(haptic, 'selection');
108
+ if (h) {
109
+ triggerHaptic(h);
110
+ }
111
+ const result = applyMarkdownAction({
112
+ text,
113
+ selection: normalizeSelection(selection, text.length)
114
+ }, action);
115
+ setSelection(result.selection);
116
+ setForcedSelection(result.selection);
117
+ onChangeText?.(result.text);
118
+ }, [disabled, haptic, onChangeText, selection, text]);
119
+ if (loading) {
120
+ return /*#__PURE__*/_jsx(RichTextEditorSkeleton, {
121
+ actionCount: actions.length,
122
+ minHeight: minHeight,
123
+ style: style,
124
+ testID: testID
125
+ });
126
+ }
127
+ return /*#__PURE__*/_jsxs(View, {
128
+ testID: testID,
129
+ style: [styles.container, {
130
+ borderColor: focused ? theme.colors.border.focus : theme.colors.border.primary,
131
+ opacity: disabled ? 0.55 : 1
132
+ }, style],
133
+ children: [/*#__PURE__*/_jsx(View, {
134
+ style: [styles.toolbar, toolbarStyle],
135
+ children: actions.map(action => /*#__PURE__*/_jsx(ToolbarButton, {
136
+ action: action,
137
+ disabled: disabled,
138
+ onPress: runAction,
139
+ styles: styles,
140
+ theme: theme,
141
+ testID: testID ? `${testID}-${action}` : undefined
142
+ }, action))
143
+ }), /*#__PURE__*/_jsx(TextInput, {
144
+ ref: ref,
145
+ value: text,
146
+ onChangeText: handleChangeText,
147
+ onSelectionChange: handleSelectionChange,
148
+ onFocus: () => setFocused(true),
149
+ onBlur: () => setFocused(false),
150
+ selection: forcedSelection ?? undefined,
151
+ editable: !disabled,
152
+ autoFocus: autoFocus,
153
+ multiline: true,
154
+ textAlignVertical: "top",
155
+ placeholder: placeholder,
156
+ placeholderTextColor: theme.colors.text.tertiary,
157
+ accessibilityLabel: accessibilityLabel,
158
+ style: [styles.input, {
159
+ minHeight
160
+ }, inputStyle],
161
+ testID: testID ? `${testID}-input` : undefined
162
+ })]
163
+ });
164
+ });
165
+ RichTextEditor.displayName = 'RichTextEditor';
166
+ const ToolbarButton = ({
167
+ action,
168
+ disabled,
169
+ onPress,
170
+ styles,
171
+ theme,
172
+ testID
173
+ }) => {
174
+ const meta = ACTION_META[action];
175
+ // Each glyph carries its own formatting, so the button reads as what it does.
176
+ // Built as one object rather than an array entry per rule: a `fontWeight:
177
+ // undefined` in a style array wipes the family weight `fontFor` just set.
178
+ const glyphStyle = {
179
+ color: disabled ? theme.colors.text.disabled : theme.colors.text.primary
180
+ };
181
+ if (action === 'bold') {
182
+ glyphStyle.fontWeight = theme.typography.fontWeight.bold;
183
+ }
184
+ if (action === 'italic') {
185
+ glyphStyle.fontStyle = 'italic';
186
+ }
187
+ if (action === 'strikethrough') {
188
+ glyphStyle.textDecorationLine = 'line-through';
189
+ }
190
+ return /*#__PURE__*/_jsx(Pressable, {
191
+ onPress: () => onPress(action),
192
+ disabled: disabled,
193
+ accessibilityRole: "button",
194
+ accessibilityLabel: meta.label,
195
+ accessibilityState: {
196
+ disabled
197
+ },
198
+ hitSlop: 4,
199
+ testID: testID,
200
+ style: ({
201
+ pressed
202
+ }) => [styles.button, {
203
+ backgroundColor: pressed ? theme.colors.surface.pressed : theme.colors.background.secondary
204
+ }],
205
+ children: /*#__PURE__*/_jsx(Text, {
206
+ allowFontScaling: false,
207
+ style: [styles.buttonLabel, glyphStyle],
208
+ children: meta.glyph
209
+ })
210
+ });
211
+ };
212
+ ToolbarButton.displayName = 'RichTextEditorToolbarButton';
213
+
214
+ /**
215
+ * Placeholder shape for `<RichTextEditor>`: the same bordered box, a row of
216
+ * button-sized blocks where the toolbar sits, and text lines filling the body,
217
+ * so nothing reflows when the draft arrives.
218
+ */
219
+ const RichTextEditorSkeleton = ({
220
+ actionCount,
221
+ minHeight,
222
+ style,
223
+ testID
224
+ }) => {
225
+ const theme = useTheme();
226
+ const styles = useMemo(() => buildStyles(theme), [theme]);
227
+ const buttons = Array.from({
228
+ length: Math.max(1, actionCount)
229
+ }, (_, index) => index);
230
+ return /*#__PURE__*/_jsxs(View, {
231
+ testID: testID,
232
+ style: [styles.container, style],
233
+ children: [/*#__PURE__*/_jsx(View, {
234
+ style: styles.toolbar,
235
+ children: buttons.map(index => /*#__PURE__*/_jsx(Skeleton, {
236
+ width: 34,
237
+ height: 30,
238
+ radius: "sm"
239
+ }, index))
240
+ }), /*#__PURE__*/_jsxs(View, {
241
+ style: [styles.skeletonBody, {
242
+ minHeight
243
+ }],
244
+ children: [/*#__PURE__*/_jsx(Skeleton, {
245
+ shape: "text",
246
+ width: "94%",
247
+ height: 14
248
+ }), /*#__PURE__*/_jsx(Skeleton, {
249
+ shape: "text",
250
+ width: "100%",
251
+ height: 14
252
+ }), /*#__PURE__*/_jsx(Skeleton, {
253
+ shape: "text",
254
+ width: "72%",
255
+ height: 14
256
+ })]
257
+ })]
258
+ });
259
+ };
260
+ RichTextEditorSkeleton.displayName = 'RichTextEditorSkeleton';
261
+ const buildStyles = theme => StyleSheet.create({
262
+ container: {
263
+ borderWidth: theme.colors.border.width,
264
+ borderColor: theme.colors.border.primary,
265
+ borderRadius: theme.radius.md,
266
+ backgroundColor: theme.colors.background.primary,
267
+ overflow: 'hidden'
268
+ },
269
+ toolbar: {
270
+ flexDirection: 'row',
271
+ flexWrap: 'wrap',
272
+ alignItems: 'center',
273
+ gap: theme.spacing.xs,
274
+ padding: theme.spacing.xs,
275
+ borderBottomWidth: StyleSheet.hairlineWidth,
276
+ borderBottomColor: theme.colors.border.secondary,
277
+ backgroundColor: theme.colors.background.secondary
278
+ },
279
+ button: {
280
+ minWidth: 34,
281
+ height: 30,
282
+ paddingHorizontal: theme.spacing.xs,
283
+ borderRadius: theme.radius.sm,
284
+ alignItems: 'center',
285
+ justifyContent: 'center'
286
+ },
287
+ buttonLabel: {
288
+ ...fontFor(theme, 'medium'),
289
+ fontSize: theme.typography.fontSize.sm
290
+ },
291
+ input: {
292
+ ...fontFor(theme, 'normal'),
293
+ color: theme.colors.text.primary,
294
+ fontSize: theme.typography.fontSize.base,
295
+ lineHeight: theme.typography.fontSize.base * 1.5,
296
+ paddingHorizontal: theme.spacing.md,
297
+ paddingVertical: theme.spacing.sm
298
+ },
299
+ skeletonBody: {
300
+ paddingHorizontal: theme.spacing.md,
301
+ paddingVertical: theme.spacing.sm,
302
+ gap: theme.spacing.sm
303
+ }
304
+ });
305
+ export default RichTextEditor;
306
+ //# sourceMappingURL=RichTextEditor.js.map
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ export { applyMarkdownAction } from "./markdownActions.js";
4
+ export { RichTextEditor } from "./RichTextEditor.js";
5
+ //# sourceMappingURL=index.js.map