@webority-technologies/mobile-ui 0.0.1 → 0.0.3
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,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The text transforms behind the editor's toolbar, kept out of the component so
|
|
5
|
+
* the behaviour people actually notice — toggling bold OFF again, and where the
|
|
6
|
+
* caret lands when nothing is selected — can be tested as data, with no
|
|
7
|
+
* rendering involved. Same split as Markdown/parse.ts.
|
|
8
|
+
*
|
|
9
|
+
* The output targets our own `<Markdown>` renderer, so the vocabulary is the
|
|
10
|
+
* subset that parser reads: `**`, `*`, `~~`, backtick, `[](…)`, `#`, `##`,
|
|
11
|
+
* `- `, `1. `, `> `. Nothing here emits anything that renderer would show as
|
|
12
|
+
* literal text.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const INLINE_MARKERS = {
|
|
16
|
+
bold: '**',
|
|
17
|
+
italic: '*',
|
|
18
|
+
strikethrough: '~~',
|
|
19
|
+
code: '`'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** A whole link, and only a whole link — used to decide "is this already one?". */
|
|
23
|
+
const WHOLE_LINK = /^\[([^\]]*)\]\(([^)\s]*)\)$/;
|
|
24
|
+
const LINK_URL_PLACEHOLDER = 'url';
|
|
25
|
+
const BLOCK_RULES = {
|
|
26
|
+
heading1: {
|
|
27
|
+
has: l => /^#\s/.test(l),
|
|
28
|
+
prefix: () => '# '
|
|
29
|
+
},
|
|
30
|
+
heading2: {
|
|
31
|
+
has: l => /^##\s/.test(l),
|
|
32
|
+
prefix: () => '## '
|
|
33
|
+
},
|
|
34
|
+
bulletList: {
|
|
35
|
+
has: l => /^[-*+]\s/.test(l),
|
|
36
|
+
prefix: () => '- '
|
|
37
|
+
},
|
|
38
|
+
numberedList: {
|
|
39
|
+
has: l => /^\d+[.)]\s/.test(l),
|
|
40
|
+
prefix: index => `${index + 1}. `
|
|
41
|
+
},
|
|
42
|
+
quote: {
|
|
43
|
+
has: l => /^>\s?/.test(l),
|
|
44
|
+
prefix: () => '> '
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Every block marker this editor writes, so switching kinds replaces rather than stacks. */
|
|
49
|
+
const ANY_BLOCK_PREFIX = /^(?:#{1,6}\s+|[-*+]\s+|\d+[.)]\s+|>\s?)/;
|
|
50
|
+
const isBlockAction = action => action in BLOCK_RULES;
|
|
51
|
+
const clamp = (value, max) => {
|
|
52
|
+
// React Native reports -1 for "no selection yet" on some Android builds.
|
|
53
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
return Math.min(Math.trunc(value), max);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Selection as given can be reversed, unset, or past the end after an edit. */
|
|
60
|
+
export const normalizeSelection = (selection, length) => {
|
|
61
|
+
const a = clamp(selection.start, length);
|
|
62
|
+
const b = clamp(selection.end, length);
|
|
63
|
+
return {
|
|
64
|
+
start: Math.min(a, b),
|
|
65
|
+
end: Math.max(a, b)
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const caret = at => ({
|
|
69
|
+
start: at,
|
|
70
|
+
end: at
|
|
71
|
+
});
|
|
72
|
+
const applyInlineMarker = (text, selection, marker) => {
|
|
73
|
+
const {
|
|
74
|
+
start,
|
|
75
|
+
end
|
|
76
|
+
} = selection;
|
|
77
|
+
const selected = text.slice(start, end);
|
|
78
|
+
const size = marker.length;
|
|
79
|
+
|
|
80
|
+
// `*` is a prefix of `**`, so italic must never mistake bold for itself:
|
|
81
|
+
// unwrapping one star per side would silently downgrade bold to italic.
|
|
82
|
+
const boldCollision = marker === '*';
|
|
83
|
+
const wrappedInside = selected.length >= size * 2 && selected.startsWith(marker) && selected.endsWith(marker);
|
|
84
|
+
if (wrappedInside && !(boldCollision && selected.startsWith('**') && selected.endsWith('**'))) {
|
|
85
|
+
const inner = selected.slice(size, selected.length - size);
|
|
86
|
+
return {
|
|
87
|
+
text: text.slice(0, start) + inner + text.slice(end),
|
|
88
|
+
selection: {
|
|
89
|
+
start,
|
|
90
|
+
end: start + inner.length
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const before = text.slice(Math.max(0, start - size), start);
|
|
95
|
+
const after = text.slice(end, end + size);
|
|
96
|
+
const wrappedOutside = start >= size && before === marker && after === marker && !(boldCollision && text.slice(Math.max(0, start - 2), start) === '**');
|
|
97
|
+
if (wrappedOutside) {
|
|
98
|
+
return {
|
|
99
|
+
text: text.slice(0, start - size) + selected + text.slice(end + size),
|
|
100
|
+
selection: {
|
|
101
|
+
start: start - size,
|
|
102
|
+
end: end - size
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const next = text.slice(0, start) + marker + selected + marker + text.slice(end);
|
|
107
|
+
return {
|
|
108
|
+
text: next,
|
|
109
|
+
selection: start === end ? caret(start + size) : {
|
|
110
|
+
start: start + size,
|
|
111
|
+
end: end + size
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
const applyLink = (text, selection) => {
|
|
116
|
+
const {
|
|
117
|
+
start,
|
|
118
|
+
end
|
|
119
|
+
} = selection;
|
|
120
|
+
const selected = text.slice(start, end);
|
|
121
|
+
const existing = WHOLE_LINK.exec(selected);
|
|
122
|
+
if (existing) {
|
|
123
|
+
const label = existing[1];
|
|
124
|
+
return {
|
|
125
|
+
text: text.slice(0, start) + label + text.slice(end),
|
|
126
|
+
selection: {
|
|
127
|
+
start,
|
|
128
|
+
end: start + label.length
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const inserted = `[${selected}](${LINK_URL_PLACEHOLDER})`;
|
|
133
|
+
const next = text.slice(0, start) + inserted + text.slice(end);
|
|
134
|
+
if (start === end) {
|
|
135
|
+
// Nothing selected: the label is what the user types first, so the caret
|
|
136
|
+
// goes between the brackets rather than onto the URL placeholder.
|
|
137
|
+
return {
|
|
138
|
+
text: next,
|
|
139
|
+
selection: caret(start + 1)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const urlStart = start + selected.length + 3;
|
|
143
|
+
return {
|
|
144
|
+
text: next,
|
|
145
|
+
selection: {
|
|
146
|
+
start: urlStart,
|
|
147
|
+
end: urlStart + LINK_URL_PLACEHOLDER.length
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
const applyBlockPrefix = (text, selection, action) => {
|
|
152
|
+
const rule = BLOCK_RULES[action];
|
|
153
|
+
const lines = text.split('\n');
|
|
154
|
+
const offsets = [];
|
|
155
|
+
let cursor = 0;
|
|
156
|
+
for (const line of lines) {
|
|
157
|
+
offsets.push(cursor);
|
|
158
|
+
cursor += line.length + 1;
|
|
159
|
+
}
|
|
160
|
+
const lineIndexAt = position => {
|
|
161
|
+
let index = 0;
|
|
162
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
163
|
+
if (offsets[i] <= position) {
|
|
164
|
+
index = i;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return index;
|
|
168
|
+
};
|
|
169
|
+
const first = lineIndexAt(selection.start);
|
|
170
|
+
const last = lineIndexAt(selection.end);
|
|
171
|
+
const affected = lines.slice(first, last + 1);
|
|
172
|
+
const allHave = affected.every(line => rule.has(line));
|
|
173
|
+
const rewritten = affected.map((line, index) => allHave ? line.replace(ANY_BLOCK_PREFIX, '') : rule.prefix(index) + line.replace(ANY_BLOCK_PREFIX, ''));
|
|
174
|
+
const firstDelta = rewritten[0].length - affected[0].length;
|
|
175
|
+
const totalDelta = rewritten.reduce((sum, line, index) => sum + (line.length - affected[index].length), 0);
|
|
176
|
+
const nextLines = [...lines.slice(0, first), ...rewritten, ...lines.slice(last + 1)];
|
|
177
|
+
const nextText = nextLines.join('\n');
|
|
178
|
+
return {
|
|
179
|
+
text: nextText,
|
|
180
|
+
selection: normalizeSelection({
|
|
181
|
+
start: Math.max(offsets[first], selection.start + firstDelta),
|
|
182
|
+
end: selection.end + totalDelta
|
|
183
|
+
}, nextText.length)
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Applies one toolbar action to the current text + selection and returns the
|
|
189
|
+
* next pair. Pure: the caller owns the state.
|
|
190
|
+
*
|
|
191
|
+
* Wrapping applies to the selection; with nothing selected the markers are
|
|
192
|
+
* inserted and the caret lands between them. Applying an action to text that
|
|
193
|
+
* already carries it removes it instead of stacking a second copy — which is
|
|
194
|
+
* the half people notice immediately when it is missing.
|
|
195
|
+
*/
|
|
196
|
+
export const applyMarkdownAction = (state, action) => {
|
|
197
|
+
const text = state.text ?? '';
|
|
198
|
+
const selection = normalizeSelection(state.selection, text.length);
|
|
199
|
+
if (isBlockAction(action)) {
|
|
200
|
+
return applyBlockPrefix(text, selection, action);
|
|
201
|
+
}
|
|
202
|
+
if (action === 'link') {
|
|
203
|
+
return applyLink(text, selection);
|
|
204
|
+
}
|
|
205
|
+
return applyInlineMarker(text, selection, INLINE_MARKERS[action]);
|
|
206
|
+
};
|
|
207
|
+
//# sourceMappingURL=markdownActions.js.map
|
|
@@ -63,6 +63,13 @@ const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
63
63
|
const debouncedValue = useDebounce(internalValue, debounceMs ?? 0);
|
|
64
64
|
const cancelAnim = useRef(createAnimatedValue(0)).current;
|
|
65
65
|
|
|
66
|
+
// Equivalent-mutant note: removing the `debounceMs === undefined` guard
|
|
67
|
+
// below cannot change observable behaviour. `internalValue` is only ever
|
|
68
|
+
// read (via `displayValue`) when `debounceMs !== undefined`; without the
|
|
69
|
+
// guard, a non-debounced instance would keep resyncing `internalValue`
|
|
70
|
+
// from `value` on every prop change, but that resynced value is never
|
|
71
|
+
// displayed or sent anywhere while non-debounced, so no test can observe
|
|
72
|
+
// the difference.
|
|
66
73
|
// biome-ignore lint/correctness/useExhaustiveDependencies: syncs the internal draft FROM the `value` prop; adding `internalValue` would revert every keystroke while the debounce is still pending
|
|
67
74
|
useEffect(() => {
|
|
68
75
|
if (debounceMs === undefined) {
|
|
@@ -226,7 +226,7 @@ const Select = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
226
226
|
accessibilityLabel: label ?? placeholder ?? 'Options',
|
|
227
227
|
options: options,
|
|
228
228
|
labelOf: labelOf,
|
|
229
|
-
|
|
229
|
+
getValue: valueOf,
|
|
230
230
|
disabledOf: disabledOf,
|
|
231
231
|
descriptionOf: descriptionOf,
|
|
232
232
|
iconOf: iconOf,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - A View with the base color and `overflow: hidden` + radius gives the
|
|
13
13
|
* placeholder its shape.
|
|
14
14
|
* - For `animation: 'shimmer'`, an Animated.View carrying a 3-stop
|
|
15
|
-
*
|
|
15
|
+
* linear gradient (transparent → highlight → transparent) translates
|
|
16
16
|
* across the box. The radius clips it to the right outline automatically.
|
|
17
17
|
* - For `animation: 'pulse'`, a full-fill Animated.View fades its opacity
|
|
18
18
|
* between 0 and 0.6. Cheap and useful for tiny shapes where a sweep
|
|
@@ -28,17 +28,7 @@
|
|
|
28
28
|
import React, { useMemo, useState } from 'react';
|
|
29
29
|
import { Animated, StyleSheet, View } from 'react-native';
|
|
30
30
|
import { useTheme } from "../../theme/index.js";
|
|
31
|
-
|
|
32
|
-
// `react-native-linear-gradient` is an OPTIONAL peer dep. When it's installed
|
|
33
|
-
// we sweep a real gradient band across the shimmer. When it isn't, we fall
|
|
34
|
-
// back to a solid-color band — still works, just less polished. This keeps
|
|
35
|
-
// the library deployable in apps that don't need gradients elsewhere.
|
|
36
|
-
let LinearGradientImpl = null;
|
|
37
|
-
try {
|
|
38
|
-
LinearGradientImpl = require('react-native-linear-gradient').default;
|
|
39
|
-
} catch {
|
|
40
|
-
// Optional peer; resolved lazily by consumers that need shimmer polish.
|
|
41
|
-
}
|
|
31
|
+
import { toBackgroundImage } from "../../theme/Gradient.js";
|
|
42
32
|
import { Responsive } from "../../utils/index.js";
|
|
43
33
|
import { useSkeletonClock } from "./SkeletonClock.js";
|
|
44
34
|
import { useSkeletonDefaults } from "./SkeletonProvider.js";
|
|
@@ -160,36 +150,27 @@ const Skeleton = ({
|
|
|
160
150
|
}
|
|
161
151
|
|
|
162
152
|
// 3-stop gradient: transparent → highlight → transparent. Feathered
|
|
163
|
-
// edges feel premium; a solid band looks like a bug.
|
|
164
|
-
// gradient package isn't installed we render the band as a solid
|
|
165
|
-
// highlight rectangle — visibly different but still recognizably a
|
|
166
|
-
// shimmer effect.
|
|
153
|
+
// edges feel premium; a solid band looks like a bug.
|
|
167
154
|
return /*#__PURE__*/_jsx(Animated.View, {
|
|
168
155
|
pointerEvents: "none",
|
|
169
156
|
style: [styles.shimmerBand, {
|
|
170
157
|
width: bandWidth,
|
|
171
158
|
transform: [{
|
|
172
159
|
translateX
|
|
173
|
-
}]
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
}) : /*#__PURE__*/_jsx(View, {
|
|
188
|
-
style: [StyleSheet.absoluteFill, {
|
|
189
|
-
backgroundColor: highlightColor,
|
|
190
|
-
opacity: 0.55
|
|
191
|
-
}]
|
|
192
|
-
})
|
|
160
|
+
}],
|
|
161
|
+
experimental_backgroundImage: [toBackgroundImage({
|
|
162
|
+
colors: ['transparent', highlightColor, 'transparent'],
|
|
163
|
+
locations: [0, 0.5, 1],
|
|
164
|
+
start: {
|
|
165
|
+
x: 0,
|
|
166
|
+
y: 0.5
|
|
167
|
+
},
|
|
168
|
+
end: {
|
|
169
|
+
x: 1,
|
|
170
|
+
y: 0.5
|
|
171
|
+
}
|
|
172
|
+
})]
|
|
173
|
+
}]
|
|
193
174
|
});
|
|
194
175
|
}, [effectiveVariant, highlightColor, clock.pulse, containerWidth, styles.shimmerBand, bandWidth, translateX]);
|
|
195
176
|
return /*#__PURE__*/_jsx(View, {
|
|
@@ -270,8 +270,13 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
270
270
|
return;
|
|
271
271
|
}
|
|
272
272
|
const delta = event.nativeEvent.actionName === 'increment' ? step : -step;
|
|
273
|
+
// Round through `precision` (same guard the pan gesture uses) so repeated
|
|
274
|
+
// increments of a fractional step (e.g. 0.1) don't drift off the exact
|
|
275
|
+
// decimal via binary float addition (0.1+0.1+0.1 = 0.30000000000000004).
|
|
276
|
+
const roundedDelta = step > 0 ? Math.round(delta * precision) / precision : delta;
|
|
273
277
|
if (isRange) {
|
|
274
|
-
const
|
|
278
|
+
const rawLow = Math.round((lowVal.value + roundedDelta) * precision) / precision;
|
|
279
|
+
const nextLow = clamp(rawLow, min, highVal.value);
|
|
275
280
|
if (nextLow !== lowVal.value) {
|
|
276
281
|
lowVal.value = nextLow;
|
|
277
282
|
lowX.value = valueToPx(nextLow, trackWidth);
|
|
@@ -279,7 +284,8 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
279
284
|
fireChange(nextLow, highVal.value);
|
|
280
285
|
}
|
|
281
286
|
} else {
|
|
282
|
-
const
|
|
287
|
+
const rawNext = Math.round((lowVal.value + roundedDelta) * precision) / precision;
|
|
288
|
+
const next = clamp(rawNext, min, max);
|
|
283
289
|
if (next !== lowVal.value) {
|
|
284
290
|
lowVal.value = next;
|
|
285
291
|
lowX.value = valueToPx(next, trackWidth);
|
|
@@ -287,7 +293,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
287
293
|
fireChange(next, next);
|
|
288
294
|
}
|
|
289
295
|
}
|
|
290
|
-
}, [disabled, step, isRange, min, max, valueToPx, trackWidth, lowX, lowVal, highVal, fireChange, fireHaptic]);
|
|
296
|
+
}, [disabled, step, precision, isRange, min, max, valueToPx, trackWidth, lowX, lowVal, highVal, fireChange, fireHaptic]);
|
|
291
297
|
const thumbRadius = sizeCfg.thumbDiameter / 2;
|
|
292
298
|
|
|
293
299
|
// ───────── Animated styles ─────────
|
|
@@ -320,8 +326,18 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
320
326
|
}));
|
|
321
327
|
|
|
322
328
|
// Display values (from committed state) for labels + a11y.
|
|
323
|
-
|
|
324
|
-
|
|
329
|
+
//
|
|
330
|
+
// Clamped to [min, max]. A caller-supplied value outside the range is not
|
|
331
|
+
// rewritten — `onChange` is never fired from here and the caller's own state
|
|
332
|
+
// is untouched — but it must not be RENDERED as-is: valueToPx does not clamp,
|
|
333
|
+
// so value=150 on a 0..100 slider puts the thumb off the end of the track, and
|
|
334
|
+
// accessibilityValue would announce `now: 150` against `max: 100`, which is an
|
|
335
|
+
// invalid state for a screen reader. Any interaction re-clamps through
|
|
336
|
+
// clamp()/snapToStep() anyway, so this only aligns the first render with what
|
|
337
|
+
// every subsequent one already does.
|
|
338
|
+
const clampDisplay = v => clamp(v, min, max);
|
|
339
|
+
const lowDisplayValue = clampDisplay(isRange ? Math.min(...currentValue) : currentValue);
|
|
340
|
+
const highDisplayValue = clampDisplay(isRange ? Math.max(...currentValue) : currentValue);
|
|
325
341
|
return /*#__PURE__*/_jsx(View, {
|
|
326
342
|
ref: ref,
|
|
327
343
|
style: [styles.container, containerStyle, style],
|
|
@@ -118,6 +118,9 @@ const Swipeable = props => {
|
|
|
118
118
|
const fireFirstAction = useCallback(side => {
|
|
119
119
|
const list = side === 'left' ? leftActions : rightActions;
|
|
120
120
|
const first = list?.[0];
|
|
121
|
+
// Structurally unreachable, not a bug guard: both call sites below are
|
|
122
|
+
// gated on allowLeft/allowRight (== hasLeft/hasRight), which is exactly
|
|
123
|
+
// "list has at least one element". Kept for type narrowing on `first`.
|
|
121
124
|
if (!first) {
|
|
122
125
|
return;
|
|
123
126
|
}
|
|
@@ -154,7 +157,7 @@ const Swipeable = props => {
|
|
|
154
157
|
const openThreshold = threshold;
|
|
155
158
|
const allowLeft = hasLeft;
|
|
156
159
|
const allowRight = hasRight;
|
|
157
|
-
return Gesture.Pan().enabled(!disabled).activeOffsetX([-dragActivationOffset, dragActivationOffset]).failOffsetY([-failOffsetY, failOffsetY]).onStart(() => {
|
|
160
|
+
return Gesture.Pan().enabled(!disabled).withTestId(testID ? `${testID}-gesture` : 'swipeable-gesture').activeOffsetX([-dragActivationOffset, dragActivationOffset]).failOffsetY([-failOffsetY, failOffsetY]).onStart(() => {
|
|
158
161
|
'worklet';
|
|
159
162
|
|
|
160
163
|
dragStartX.value = translateX.value;
|
|
@@ -212,7 +215,7 @@ const Swipeable = props => {
|
|
|
212
215
|
translateX.value = withSpring(0, springConfig);
|
|
213
216
|
runOnJS(notifyClose)();
|
|
214
217
|
});
|
|
215
|
-
}, [disabled, dragStartX, fireFirstAction, fullSwipeThreshold, hasLeft, hasRight, leftRevealWidth, notifyClose, notifyOpen, rightRevealWidth, screenWidth, threshold, translateX, dragActivationOffset, failOffsetY, rubberBandFactor, springConfig]);
|
|
218
|
+
}, [disabled, dragStartX, fireFirstAction, fullSwipeThreshold, hasLeft, hasRight, leftRevealWidth, notifyClose, notifyOpen, rightRevealWidth, screenWidth, threshold, translateX, dragActivationOffset, failOffsetY, rubberBandFactor, springConfig, testID]);
|
|
216
219
|
|
|
217
220
|
// ───────── Animated styles ─────────
|
|
218
221
|
const contentStyle = useAnimatedStyle(() => ({
|
|
@@ -331,6 +331,17 @@ const TimePicker = props => {
|
|
|
331
331
|
const [periodIndex, setPeriodIndex] = useState(() => initial.period === 'PM' ? 1 : 0);
|
|
332
332
|
|
|
333
333
|
// Re-sync on open toggle / value change / format / step.
|
|
334
|
+
//
|
|
335
|
+
// The `!open` guard is a RENDER OPTIMISATION, not a correctness guard, and it
|
|
336
|
+
// is worth knowing which. `open` is itself a dependency, so the effect re-runs
|
|
337
|
+
// when the picker opens and reads a fresh `new Date()` either way — the state
|
|
338
|
+
// a user actually sees is identical with or without it. What it saves is three
|
|
339
|
+
// setState calls (and the extra commit they schedule) on every prop change
|
|
340
|
+
// while the picker is closed. Correctness while closed is handled by RN's
|
|
341
|
+
// Modal, which renders nothing at visible={false}.
|
|
342
|
+
//
|
|
343
|
+
// That is why its test counts render passes through React.Profiler rather than
|
|
344
|
+
// asserting on the tree: no DOM assertion can distinguish the two paths.
|
|
334
345
|
useEffect(() => {
|
|
335
346
|
if (!open) {
|
|
336
347
|
return;
|
|
@@ -4,6 +4,7 @@ export { Accordion, AccordionGroup } from "./Accordion/index.js";
|
|
|
4
4
|
export { AnimatePresence } from "./AnimatePresence/index.js";
|
|
5
5
|
export { AppBar } from "./AppBar/index.js";
|
|
6
6
|
export { AsyncBlock } from "./AsyncBlock/index.js";
|
|
7
|
+
export { AuthedImage, authedImageCacheStats, clearAuthedImageCache, getAuthedImageCacheLimit, setAuthedImageCacheLimit } from "./AuthedImage/index.js";
|
|
7
8
|
export { Autocomplete } from "./Autocomplete/index.js";
|
|
8
9
|
export { Avatar, AvatarGroup } from "./Avatar/index.js";
|
|
9
10
|
export { Badge } from "./Badge/index.js";
|
|
@@ -25,6 +26,7 @@ export { Dialog } from "./Dialog/index.js";
|
|
|
25
26
|
export { Divider } from "./Divider/index.js";
|
|
26
27
|
export { Drawer } from "./Drawer/index.js";
|
|
27
28
|
export { EmptyState } from "./EmptyState/index.js";
|
|
29
|
+
export { ErrorBoundary, useErrorHandler, withErrorBoundary } from "./ErrorBoundary/index.js";
|
|
28
30
|
export { FilePicker, formatFileSize } from "./FilePicker/index.js";
|
|
29
31
|
export { FloatingActionButton, FloatingActionButtonGroup } from "./FloatingActionButton/index.js";
|
|
30
32
|
export { ForceUpdateDialog } from "./ForceUpdateDialog/index.js";
|
|
@@ -36,6 +38,8 @@ export { Input } from "./Input/index.js";
|
|
|
36
38
|
export { KeyboardAwareScrollView } from "./KeyboardAwareScrollView/index.js";
|
|
37
39
|
export { KeyboardToolbar } from "./KeyboardToolbar/index.js";
|
|
38
40
|
export { ListItem } from "./ListItem/index.js";
|
|
41
|
+
export { LoaderOverlay, loader, useLoader } from "./LoaderOverlay/index.js";
|
|
42
|
+
export { Markdown } from "./Markdown/index.js";
|
|
39
43
|
export { MODAL_DISMISS_GRACE_MS, Modal } from "./Modal/index.js";
|
|
40
44
|
export { NumberInput } from "./NumberInput/index.js";
|
|
41
45
|
export { OTPInput } from "./OTPInput/index.js";
|
|
@@ -45,6 +49,7 @@ export { ProgressBar } from "./ProgressBar/index.js";
|
|
|
45
49
|
export { Radio, RadioDot, RadioGroup } from "./Radio/index.js";
|
|
46
50
|
export { Rating } from "./Rating/index.js";
|
|
47
51
|
export { Repeater } from "./Repeater/index.js";
|
|
52
|
+
export { applyMarkdownAction, RichTextEditor } from "./RichTextEditor/index.js";
|
|
48
53
|
export { SearchBar } from "./SearchBar/index.js";
|
|
49
54
|
export { SegmentedControl } from "./SegmentedControl/index.js";
|
|
50
55
|
export { Select } from "./Select/index.js";
|
|
@@ -6,11 +6,23 @@ const FormContext = /*#__PURE__*/createContext(null);
|
|
|
6
6
|
/**
|
|
7
7
|
* Provides a {@link useForm} instance to descendants so `<FormField name>`,
|
|
8
8
|
* {@link useField} and {@link useFieldArray} can bind by name.
|
|
9
|
+
*
|
|
10
|
+
* Generic in `Values`, and it has to be. `FormApi` is invariant in `Values`
|
|
11
|
+
* because `setValues` accepts a `(prev: Values) => Values`, so a
|
|
12
|
+
* `FormApi<MyValues>` is not assignable to a `FormApi<FormValues>`. While the
|
|
13
|
+
* prop was declared at the base type, `useForm<MyValues>()` could not be handed
|
|
14
|
+
* to `<Form>` by ANY consumer, which is most of the reason to type a form at
|
|
15
|
+
* all. Untyped callers are unaffected: `Values` defaults to `FormValues`.
|
|
9
16
|
*/
|
|
10
17
|
export const Form = ({
|
|
11
18
|
form,
|
|
12
19
|
children
|
|
13
|
-
}) =>
|
|
20
|
+
}) =>
|
|
21
|
+
/*#__PURE__*/
|
|
22
|
+
// Stored at the base type on purpose: descendants bind by NAME through
|
|
23
|
+
// useField and never observe Values, so widening here is safe and is what
|
|
24
|
+
// lets a typed form be provided at all.
|
|
25
|
+
_jsx(FormContext.Provider, {
|
|
14
26
|
value: form,
|
|
15
27
|
children: children
|
|
16
28
|
});
|
|
@@ -33,13 +33,25 @@ export function useFieldArray(name) {
|
|
|
33
33
|
copy.splice(index, 0, item);
|
|
34
34
|
set(copy);
|
|
35
35
|
},
|
|
36
|
+
// `move` and `replace` index into the array directly, so an out-of-range
|
|
37
|
+
// index corrupts it rather than failing: splice(9, 1) on a 3-item array
|
|
38
|
+
// yields `undefined`, which then gets spliced back IN, and `copy[9] = item`
|
|
39
|
+
// grows the array with six holes. Either one puts nulls into the submitted
|
|
40
|
+
// payload of a repeating field. `insert` and `remove` need no guard —
|
|
41
|
+
// splice clamps and filter no-ops.
|
|
36
42
|
move: (from, to) => {
|
|
43
|
+
if (from < 0 || from >= fields.length) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
37
46
|
const copy = [...fields];
|
|
38
47
|
const [moved] = copy.splice(from, 1);
|
|
39
|
-
copy.splice(to, 0, moved);
|
|
48
|
+
copy.splice(Math.max(0, Math.min(to, copy.length)), 0, moved);
|
|
40
49
|
set(copy);
|
|
41
50
|
},
|
|
42
51
|
replace: (index, item) => {
|
|
52
|
+
if (index < 0 || index >= fields.length) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
43
55
|
const copy = [...fields];
|
|
44
56
|
copy[index] = item;
|
|
45
57
|
set(copy);
|
|
@@ -1,49 +1,52 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
4
3
|
import { View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Render a linear gradient with React Native's own `experimental_backgroundImage`
|
|
7
|
+
* style prop. `GradientDefinition` keeps its vector-based `start`/`end` shape,
|
|
8
|
+
* which is translated to a CSS angle here.
|
|
9
|
+
*/
|
|
5
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
const DEFAULT_START = {
|
|
12
|
+
x: 0.5,
|
|
13
|
+
y: 0
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT_END = {
|
|
16
|
+
x: 0.5,
|
|
17
|
+
y: 1
|
|
18
|
+
};
|
|
19
|
+
const round = value => Math.round(value * 1000) / 1000;
|
|
20
|
+
|
|
6
21
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* The dep is OPTIONAL — apps that never use gradients don't need it installed.
|
|
10
|
-
* If `<Gradient>` (or any component that accepts a `gradient` prop) is reached
|
|
11
|
-
* without the package present, we fall back to a flat View using the first
|
|
12
|
-
* colour and emit a one-time dev warning so the omission is loud but
|
|
13
|
-
* non-fatal.
|
|
22
|
+
* CSS gradient angles are measured clockwise from "to top", while the vectors
|
|
23
|
+
* live in a y-down 0..1 space, so the y delta is negated before the atan2.
|
|
14
24
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
25
|
+
export const gradientAngle = (start = DEFAULT_START, end = DEFAULT_END) => {
|
|
26
|
+
const degrees = Math.atan2(end.x - start.x, start.y - end.y) * 180 / Math.PI;
|
|
27
|
+
return round((degrees % 360 + 360) % 360);
|
|
28
|
+
};
|
|
29
|
+
export const toBackgroundImage = gradient => ({
|
|
30
|
+
type: 'linear-gradient',
|
|
31
|
+
direction: `${gradientAngle(gradient.start, gradient.end)}deg`,
|
|
32
|
+
colorStops: gradient.colors.map((color, index) => {
|
|
33
|
+
const location = gradient.locations?.[index];
|
|
34
|
+
return location === undefined ? {
|
|
35
|
+
color
|
|
36
|
+
} : {
|
|
37
|
+
color,
|
|
38
|
+
positions: [`${round(location * 100)}%`]
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
});
|
|
22
42
|
export const Gradient = ({
|
|
23
43
|
gradient,
|
|
24
44
|
style,
|
|
25
45
|
children
|
|
26
|
-
}) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end: gradient.end,
|
|
33
|
-
style: style,
|
|
34
|
-
children: children
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
if (__DEV__ && !warned) {
|
|
38
|
-
warned = true;
|
|
39
|
-
console.warn('[@webority-technologies/mobile-ui] gradient prop set but react-native-linear-gradient is not installed. ' + 'Run `npm install react-native-linear-gradient` to enable.');
|
|
40
|
-
}
|
|
41
|
-
return /*#__PURE__*/_jsx(View, {
|
|
42
|
-
style: [{
|
|
43
|
-
backgroundColor: gradient.colors[0]
|
|
44
|
-
}, style],
|
|
45
|
-
children: children
|
|
46
|
-
});
|
|
47
|
-
};
|
|
48
|
-
export const isGradientAvailable = () => LinearGradientImpl != null;
|
|
46
|
+
}) => /*#__PURE__*/_jsx(View, {
|
|
47
|
+
style: [{
|
|
48
|
+
experimental_backgroundImage: [toBackgroundImage(gradient)]
|
|
49
|
+
}, style],
|
|
50
|
+
children: children
|
|
51
|
+
});
|
|
49
52
|
//# sourceMappingURL=Gradient.js.map
|
|
@@ -13,6 +13,22 @@ const recompute = () => {
|
|
|
13
13
|
currentTheme = overrides ? mergeTheme(base, overrides) : base;
|
|
14
14
|
subscribers.forEach(cb => cb());
|
|
15
15
|
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Set the override object for a mode (defaults to the active one).
|
|
19
|
+
*
|
|
20
|
+
* REPLACES that mode's overrides; it does not accumulate them. Calling
|
|
21
|
+
* `setTheme({ colors })` and then `setTheme({ spacing })` leaves ONLY the
|
|
22
|
+
* spacing override live — the colours silently revert to the base theme. Pass
|
|
23
|
+
* one complete override object per mode, or merge them yourself before calling:
|
|
24
|
+
*
|
|
25
|
+
* setTheme({ ...myColors, ...mySpacing });
|
|
26
|
+
*
|
|
27
|
+
* This is deliberate (one override object per mode, last call wins) and is
|
|
28
|
+
* pinned by a test. Do not "fix" it to merge without treating that as the
|
|
29
|
+
* behaviour change it is: an app relying on replace to CLEAR a previous
|
|
30
|
+
* override would silently keep it.
|
|
31
|
+
*/
|
|
16
32
|
export const setTheme = (overrides, mode) => {
|
|
17
33
|
if (mode) {
|
|
18
34
|
currentMode = mode;
|
|
@@ -43,7 +59,7 @@ export const subscribeTheme = callback => {
|
|
|
43
59
|
};
|
|
44
60
|
};
|
|
45
61
|
export { createAnimatedValue, setNativeValue } from "./animatedValue.js";
|
|
46
|
-
export { Gradient,
|
|
62
|
+
export { Gradient, gradientAngle, toBackgroundImage } from "./Gradient.js";
|
|
47
63
|
export { mergeTheme } from "./merge.js";
|
|
48
64
|
export { ThemeProvider, useTheme, useThemeMode } from "./ThemeContext.js";
|
|
49
65
|
export { fontFor } from "./textStyle.js";
|