@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.
- package/README.md +2 -1
- package/lib/commonjs/components/AuthedImage/AuthedImage.js +225 -0
- package/lib/commonjs/components/AuthedImage/authedImageCache.js +253 -0
- package/lib/commonjs/components/AuthedImage/index.js +45 -0
- package/lib/commonjs/components/BottomSheet/BottomSheet.js +10 -4
- package/lib/commonjs/components/Drawer/Drawer.js +6 -3
- package/lib/commonjs/components/ErrorBoundary/ErrorBoundary.js +136 -0
- package/lib/commonjs/components/ErrorBoundary/index.js +27 -0
- package/lib/commonjs/components/ErrorBoundary/useErrorHandler.js +24 -0
- package/lib/commonjs/components/ErrorBoundary/withErrorBoundary.js +28 -0
- package/lib/commonjs/components/FilePicker/FilePicker.js +1 -1
- package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +14 -1
- package/lib/commonjs/components/ImageGallery/ImageGallery.js +13 -0
- package/lib/commonjs/components/InlineSelect/InlineSelect.js +1 -1
- package/lib/commonjs/components/Input/Input.js +6 -0
- package/lib/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +62 -13
- package/lib/commonjs/components/LoaderOverlay/LoaderOverlay.js +203 -0
- package/lib/commonjs/components/LoaderOverlay/index.js +25 -0
- package/lib/commonjs/components/Markdown/Markdown.js +274 -0
- package/lib/commonjs/components/Markdown/index.js +13 -0
- package/lib/commonjs/components/Markdown/parse.js +229 -0
- package/lib/commonjs/components/OTPInput/OTPInput.js +5 -0
- package/lib/commonjs/components/OptionSheet/OptionSheet.js +3 -3
- package/lib/commonjs/components/PhoneNumberInput/PhoneNumberInput.js +1 -1
- package/lib/commonjs/components/Radio/Radio.js +9 -0
- package/lib/commonjs/components/Rating/Rating.js +5 -0
- package/lib/commonjs/components/RichTextEditor/RichTextEditor.js +311 -0
- package/lib/commonjs/components/RichTextEditor/index.js +20 -0
- package/lib/commonjs/components/RichTextEditor/markdownActions.js +213 -0
- package/lib/commonjs/components/SearchBar/SearchBar.js +7 -0
- package/lib/commonjs/components/Select/Select.js +1 -1
- package/lib/commonjs/components/Skeleton/Skeleton.js +17 -36
- package/lib/commonjs/components/Slider/Slider.js +21 -5
- package/lib/commonjs/components/Swipeable/Swipeable.js +5 -2
- package/lib/commonjs/components/TimePicker/TimePicker.js +11 -0
- package/lib/commonjs/components/index.js +230 -141
- package/lib/commonjs/form/FormContext.js +13 -1
- package/lib/commonjs/form/useField.js +13 -1
- package/lib/commonjs/theme/Gradient.js +44 -41
- package/lib/commonjs/theme/index.js +24 -2
- package/lib/commonjs/utils/hapticUtils.js +38 -18
- package/lib/module/components/AuthedImage/AuthedImage.js +221 -0
- package/lib/module/components/AuthedImage/authedImageCache.js +239 -0
- package/lib/module/components/AuthedImage/index.js +5 -0
- package/lib/module/components/BottomSheet/BottomSheet.js +10 -4
- package/lib/module/components/Drawer/Drawer.js +6 -3
- package/lib/module/components/ErrorBoundary/ErrorBoundary.js +130 -0
- package/lib/module/components/ErrorBoundary/index.js +6 -0
- package/lib/module/components/ErrorBoundary/useErrorHandler.js +20 -0
- package/lib/module/components/ErrorBoundary/withErrorBoundary.js +23 -0
- package/lib/module/components/FilePicker/FilePicker.js +1 -1
- package/lib/module/components/FloatingActionButton/FloatingActionButton.js +14 -1
- package/lib/module/components/ImageGallery/ImageGallery.js +13 -0
- package/lib/module/components/InlineSelect/InlineSelect.js +1 -1
- package/lib/module/components/Input/Input.js +6 -0
- package/lib/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +63 -14
- package/lib/module/components/LoaderOverlay/LoaderOverlay.js +196 -0
- package/lib/module/components/LoaderOverlay/index.js +4 -0
- package/lib/module/components/Markdown/Markdown.js +268 -0
- package/lib/module/components/Markdown/index.js +4 -0
- package/lib/module/components/Markdown/parse.js +223 -0
- package/lib/module/components/OTPInput/OTPInput.js +5 -0
- package/lib/module/components/OptionSheet/OptionSheet.js +3 -3
- package/lib/module/components/PhoneNumberInput/PhoneNumberInput.js +1 -1
- package/lib/module/components/Radio/Radio.js +9 -0
- package/lib/module/components/Rating/Rating.js +5 -0
- package/lib/module/components/RichTextEditor/RichTextEditor.js +306 -0
- package/lib/module/components/RichTextEditor/index.js +5 -0
- package/lib/module/components/RichTextEditor/markdownActions.js +207 -0
- package/lib/module/components/SearchBar/SearchBar.js +7 -0
- package/lib/module/components/Select/Select.js +1 -1
- package/lib/module/components/Skeleton/Skeleton.js +17 -36
- package/lib/module/components/Slider/Slider.js +21 -5
- package/lib/module/components/Swipeable/Swipeable.js +5 -2
- package/lib/module/components/TimePicker/TimePicker.js +11 -0
- package/lib/module/components/index.js +5 -0
- package/lib/module/form/FormContext.js +13 -1
- package/lib/module/form/useField.js +13 -1
- package/lib/module/theme/Gradient.js +41 -38
- package/lib/module/theme/index.js +17 -1
- package/lib/module/utils/hapticUtils.js +37 -18
- package/lib/typescript/commonjs/components/AuthedImage/AuthedImage.d.ts +22 -0
- package/lib/typescript/commonjs/components/AuthedImage/authedImageCache.d.ts +48 -0
- package/lib/typescript/commonjs/components/AuthedImage/index.d.ts +5 -0
- package/lib/typescript/commonjs/components/Button/Button.d.ts +1 -3
- package/lib/typescript/commonjs/components/Card/Card.d.ts +1 -2
- package/lib/typescript/commonjs/components/ErrorBoundary/ErrorBoundary.d.ts +38 -0
- package/lib/typescript/commonjs/components/ErrorBoundary/index.d.ts +5 -0
- package/lib/typescript/commonjs/components/ErrorBoundary/useErrorHandler.d.ts +9 -0
- package/lib/typescript/commonjs/components/ErrorBoundary/withErrorBoundary.d.ts +9 -0
- package/lib/typescript/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +15 -0
- package/lib/typescript/commonjs/components/LoaderOverlay/LoaderOverlay.d.ts +25 -0
- package/lib/typescript/commonjs/components/LoaderOverlay/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/Markdown/Markdown.d.ts +29 -0
- package/lib/typescript/commonjs/components/Markdown/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/Markdown/parse.d.ts +58 -0
- package/lib/typescript/commonjs/components/OptionSheet/OptionSheet.d.ts +8 -1
- package/lib/typescript/commonjs/components/RichTextEditor/RichTextEditor.d.ts +48 -0
- package/lib/typescript/commonjs/components/RichTextEditor/index.d.ts +5 -0
- package/lib/typescript/commonjs/components/RichTextEditor/markdownActions.d.ts +35 -0
- package/lib/typescript/commonjs/components/Skeleton/Skeleton.d.ts +1 -1
- package/lib/typescript/commonjs/components/index.d.ts +10 -0
- package/lib/typescript/commonjs/form/FormContext.d.ts +13 -3
- package/lib/typescript/commonjs/theme/Gradient.d.ts +18 -3
- package/lib/typescript/commonjs/theme/index.d.ts +16 -1
- package/lib/typescript/commonjs/theme/types.d.ts +2 -2
- package/lib/typescript/commonjs/utils/hapticUtils.d.ts +4 -0
- package/lib/typescript/module/components/AuthedImage/AuthedImage.d.ts +22 -0
- package/lib/typescript/module/components/AuthedImage/authedImageCache.d.ts +48 -0
- package/lib/typescript/module/components/AuthedImage/index.d.ts +5 -0
- package/lib/typescript/module/components/Button/Button.d.ts +1 -3
- package/lib/typescript/module/components/Card/Card.d.ts +1 -2
- package/lib/typescript/module/components/ErrorBoundary/ErrorBoundary.d.ts +38 -0
- package/lib/typescript/module/components/ErrorBoundary/index.d.ts +5 -0
- package/lib/typescript/module/components/ErrorBoundary/useErrorHandler.d.ts +9 -0
- package/lib/typescript/module/components/ErrorBoundary/withErrorBoundary.d.ts +9 -0
- package/lib/typescript/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +15 -0
- package/lib/typescript/module/components/LoaderOverlay/LoaderOverlay.d.ts +25 -0
- package/lib/typescript/module/components/LoaderOverlay/index.d.ts +3 -0
- package/lib/typescript/module/components/Markdown/Markdown.d.ts +29 -0
- package/lib/typescript/module/components/Markdown/index.d.ts +3 -0
- package/lib/typescript/module/components/Markdown/parse.d.ts +58 -0
- package/lib/typescript/module/components/OptionSheet/OptionSheet.d.ts +8 -1
- package/lib/typescript/module/components/RichTextEditor/RichTextEditor.d.ts +48 -0
- package/lib/typescript/module/components/RichTextEditor/index.d.ts +5 -0
- package/lib/typescript/module/components/RichTextEditor/markdownActions.d.ts +35 -0
- package/lib/typescript/module/components/Skeleton/Skeleton.d.ts +1 -1
- package/lib/typescript/module/components/index.d.ts +10 -0
- package/lib/typescript/module/form/FormContext.d.ts +13 -3
- package/lib/typescript/module/theme/Gradient.d.ts +18 -3
- package/lib/typescript/module/theme/index.d.ts +16 -1
- package/lib/typescript/module/theme/types.d.ts +2 -2
- package/lib/typescript/module/utils/hapticUtils.d.ts +4 -0
- 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
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|