@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,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
- valueOf: valueOf,
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
- * `LinearGradient` (transparent → highlight → transparent) translates
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. When the optional
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
- children: LinearGradientImpl ? /*#__PURE__*/_jsx(LinearGradientImpl, {
176
- colors: ['transparent', highlightColor, 'transparent'],
177
- locations: [0, 0.5, 1],
178
- start: {
179
- x: 0,
180
- y: 0.5
181
- },
182
- end: {
183
- x: 1,
184
- y: 0.5
185
- },
186
- style: StyleSheet.absoluteFill
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 nextLow = clamp(lowVal.value + delta, min, highVal.value);
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 next = clamp(lowVal.value + delta, min, max);
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
- const lowDisplayValue = isRange ? Math.min(...currentValue) : currentValue;
324
- const highDisplayValue = isRange ? Math.max(...currentValue) : currentValue;
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
- }) => /*#__PURE__*/_jsx(FormContext.Provider, {
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
- * Render a linear gradient using `react-native-linear-gradient` when available.
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
- let LinearGradientImpl = null;
16
- let warned = false;
17
- try {
18
- LinearGradientImpl = require('react-native-linear-gradient').default;
19
- } catch {
20
- // Optional peer; resolved lazily by consumers that need gradients.
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
- if (LinearGradientImpl) {
28
- return /*#__PURE__*/_jsx(LinearGradientImpl, {
29
- colors: gradient.colors,
30
- locations: gradient.locations,
31
- start: gradient.start,
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, isGradientAvailable } from "./Gradient.js";
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";