@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
|
@@ -13,11 +13,23 @@ const FormContext = /*#__PURE__*/(0, _react.createContext)(null);
|
|
|
13
13
|
/**
|
|
14
14
|
* Provides a {@link useForm} instance to descendants so `<FormField name>`,
|
|
15
15
|
* {@link useField} and {@link useFieldArray} can bind by name.
|
|
16
|
+
*
|
|
17
|
+
* Generic in `Values`, and it has to be. `FormApi` is invariant in `Values`
|
|
18
|
+
* because `setValues` accepts a `(prev: Values) => Values`, so a
|
|
19
|
+
* `FormApi<MyValues>` is not assignable to a `FormApi<FormValues>`. While the
|
|
20
|
+
* prop was declared at the base type, `useForm<MyValues>()` could not be handed
|
|
21
|
+
* to `<Form>` by ANY consumer, which is most of the reason to type a form at
|
|
22
|
+
* all. Untyped callers are unaffected: `Values` defaults to `FormValues`.
|
|
16
23
|
*/
|
|
17
24
|
const Form = ({
|
|
18
25
|
form,
|
|
19
26
|
children
|
|
20
|
-
}) =>
|
|
27
|
+
}) =>
|
|
28
|
+
/*#__PURE__*/
|
|
29
|
+
// Stored at the base type on purpose: descendants bind by NAME through
|
|
30
|
+
// useField and never observe Values, so widening here is safe and is what
|
|
31
|
+
// lets a typed form be provided at all.
|
|
32
|
+
(0, _jsxRuntime.jsx)(FormContext.Provider, {
|
|
21
33
|
value: form,
|
|
22
34
|
children: children
|
|
23
35
|
});
|
|
@@ -38,13 +38,25 @@ function useFieldArray(name) {
|
|
|
38
38
|
copy.splice(index, 0, item);
|
|
39
39
|
set(copy);
|
|
40
40
|
},
|
|
41
|
+
// `move` and `replace` index into the array directly, so an out-of-range
|
|
42
|
+
// index corrupts it rather than failing: splice(9, 1) on a 3-item array
|
|
43
|
+
// yields `undefined`, which then gets spliced back IN, and `copy[9] = item`
|
|
44
|
+
// grows the array with six holes. Either one puts nulls into the submitted
|
|
45
|
+
// payload of a repeating field. `insert` and `remove` need no guard —
|
|
46
|
+
// splice clamps and filter no-ops.
|
|
41
47
|
move: (from, to) => {
|
|
48
|
+
if (from < 0 || from >= fields.length) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
42
51
|
const copy = [...fields];
|
|
43
52
|
const [moved] = copy.splice(from, 1);
|
|
44
|
-
copy.splice(to, 0, moved);
|
|
53
|
+
copy.splice(Math.max(0, Math.min(to, copy.length)), 0, moved);
|
|
45
54
|
set(copy);
|
|
46
55
|
},
|
|
47
56
|
replace: (index, item) => {
|
|
57
|
+
if (index < 0 || index >= fields.length) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
48
60
|
const copy = [...fields];
|
|
49
61
|
copy[index] = item;
|
|
50
62
|
set(copy);
|
|
@@ -3,54 +3,57 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
7
|
-
var _react = _interopRequireDefault(require("react"));
|
|
6
|
+
exports.toBackgroundImage = exports.gradientAngle = exports.Gradient = void 0;
|
|
8
7
|
var _reactNative = require("react-native");
|
|
9
8
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
10
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
9
|
/**
|
|
12
|
-
* Render a linear gradient
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* If `<Gradient>` (or any component that accepts a `gradient` prop) is reached
|
|
16
|
-
* without the package present, we fall back to a flat View using the first
|
|
17
|
-
* colour and emit a one-time dev warning so the omission is loud but
|
|
18
|
-
* non-fatal.
|
|
10
|
+
* Render a linear gradient with React Native's own `experimental_backgroundImage`
|
|
11
|
+
* style prop. `GradientDefinition` keeps its vector-based `start`/`end` shape,
|
|
12
|
+
* which is translated to a CSS angle here.
|
|
19
13
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
|
|
15
|
+
const DEFAULT_START = {
|
|
16
|
+
x: 0.5,
|
|
17
|
+
y: 0
|
|
18
|
+
};
|
|
19
|
+
const DEFAULT_END = {
|
|
20
|
+
x: 0.5,
|
|
21
|
+
y: 1
|
|
22
|
+
};
|
|
23
|
+
const round = value => Math.round(value * 1000) / 1000;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* CSS gradient angles are measured clockwise from "to top", while the vectors
|
|
27
|
+
* live in a y-down 0..1 space, so the y delta is negated before the atan2.
|
|
28
|
+
*/
|
|
29
|
+
const gradientAngle = (start = DEFAULT_START, end = DEFAULT_END) => {
|
|
30
|
+
const degrees = Math.atan2(end.x - start.x, start.y - end.y) * 180 / Math.PI;
|
|
31
|
+
return round((degrees % 360 + 360) % 360);
|
|
32
|
+
};
|
|
33
|
+
exports.gradientAngle = gradientAngle;
|
|
34
|
+
const toBackgroundImage = gradient => ({
|
|
35
|
+
type: 'linear-gradient',
|
|
36
|
+
direction: `${gradientAngle(gradient.start, gradient.end)}deg`,
|
|
37
|
+
colorStops: gradient.colors.map((color, index) => {
|
|
38
|
+
const location = gradient.locations?.[index];
|
|
39
|
+
return location === undefined ? {
|
|
40
|
+
color
|
|
41
|
+
} : {
|
|
42
|
+
color,
|
|
43
|
+
positions: [`${round(location * 100)}%`]
|
|
44
|
+
};
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
exports.toBackgroundImage = toBackgroundImage;
|
|
27
48
|
const Gradient = ({
|
|
28
49
|
gradient,
|
|
29
50
|
style,
|
|
30
51
|
children
|
|
31
|
-
}) => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
end: gradient.end,
|
|
38
|
-
style: style,
|
|
39
|
-
children: children
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
if (__DEV__ && !warned) {
|
|
43
|
-
warned = true;
|
|
44
|
-
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.');
|
|
45
|
-
}
|
|
46
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
47
|
-
style: [{
|
|
48
|
-
backgroundColor: gradient.colors[0]
|
|
49
|
-
}, style],
|
|
50
|
-
children: children
|
|
51
|
-
});
|
|
52
|
-
};
|
|
52
|
+
}) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
53
|
+
style: [{
|
|
54
|
+
experimental_backgroundImage: [toBackgroundImage(gradient)]
|
|
55
|
+
}, style],
|
|
56
|
+
children: children
|
|
57
|
+
});
|
|
53
58
|
exports.Gradient = Gradient;
|
|
54
|
-
const isGradientAvailable = () => LinearGradientImpl != null;
|
|
55
|
-
exports.isGradientAvailable = isGradientAvailable;
|
|
56
59
|
//# sourceMappingURL=Gradient.js.map
|
|
@@ -34,10 +34,10 @@ Object.defineProperty(exports, "fontFor", {
|
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
exports.getTheme = exports.getColorMode = void 0;
|
|
37
|
-
Object.defineProperty(exports, "
|
|
37
|
+
Object.defineProperty(exports, "gradientAngle", {
|
|
38
38
|
enumerable: true,
|
|
39
39
|
get: function () {
|
|
40
|
-
return _Gradient.
|
|
40
|
+
return _Gradient.gradientAngle;
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
43
|
Object.defineProperty(exports, "lightTheme", {
|
|
@@ -60,6 +60,12 @@ Object.defineProperty(exports, "setNativeValue", {
|
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
62
|
exports.subscribeTheme = exports.setTheme = void 0;
|
|
63
|
+
Object.defineProperty(exports, "toBackgroundImage", {
|
|
64
|
+
enumerable: true,
|
|
65
|
+
get: function () {
|
|
66
|
+
return _Gradient.toBackgroundImage;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
63
69
|
Object.defineProperty(exports, "useTheme", {
|
|
64
70
|
enumerable: true,
|
|
65
71
|
get: function () {
|
|
@@ -89,6 +95,22 @@ const recompute = () => {
|
|
|
89
95
|
currentTheme = overrides ? (0, _merge.mergeTheme)(base, overrides) : base;
|
|
90
96
|
subscribers.forEach(cb => cb());
|
|
91
97
|
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Set the override object for a mode (defaults to the active one).
|
|
101
|
+
*
|
|
102
|
+
* REPLACES that mode's overrides; it does not accumulate them. Calling
|
|
103
|
+
* `setTheme({ colors })` and then `setTheme({ spacing })` leaves ONLY the
|
|
104
|
+
* spacing override live — the colours silently revert to the base theme. Pass
|
|
105
|
+
* one complete override object per mode, or merge them yourself before calling:
|
|
106
|
+
*
|
|
107
|
+
* setTheme({ ...myColors, ...mySpacing });
|
|
108
|
+
*
|
|
109
|
+
* This is deliberate (one override object per mode, last call wins) and is
|
|
110
|
+
* pinned by a test. Do not "fix" it to merge without treating that as the
|
|
111
|
+
* behaviour change it is: an app relying on replace to CLEAR a previous
|
|
112
|
+
* override would silently keep it.
|
|
113
|
+
*/
|
|
92
114
|
const setTheme = (overrides, mode) => {
|
|
93
115
|
if (mode) {
|
|
94
116
|
currentMode = mode;
|
|
@@ -4,25 +4,20 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.triggerHaptic = exports.setHapticImplementation = exports.resolveHaptic = void 0;
|
|
7
|
+
var Haptics = _interopRequireWildcard(require("expo-haptics"));
|
|
7
8
|
var _reactNative = require("react-native");
|
|
9
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
8
10
|
let hapticImpl = null;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
enableVibrateFallback: true
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
} catch {
|
|
23
|
-
// Ignored: not installed; fall back to Vibration on Android, no-op on iOS.
|
|
24
|
-
}
|
|
25
|
-
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Only what a test or the showcase injected. The default is expo-haptics, which
|
|
14
|
+
* this module imports directly: `expo` is a required peer, so the 48 components
|
|
15
|
+
* that fire haptics get them without any app wiring anything.
|
|
16
|
+
*
|
|
17
|
+
* The Vibration fallback below is now only the FAILURE path — a device whose
|
|
18
|
+
* haptic engine is disabled rejects, and Android still gets something.
|
|
19
|
+
*/
|
|
20
|
+
|
|
26
21
|
const fallback = type => {
|
|
27
22
|
if (_reactNative.Platform.OS === 'android') {
|
|
28
23
|
const map = {
|
|
@@ -37,8 +32,23 @@ const fallback = type => {
|
|
|
37
32
|
_reactNative.Vibration.vibrate(map[type] ?? 10);
|
|
38
33
|
}
|
|
39
34
|
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* expo-haptics, which splits what this module treats as one vocabulary across
|
|
38
|
+
* three functions. The promises are deliberately swallowed: the seam is
|
|
39
|
+
* synchronous, a device with the engine disabled rejects, and an unhandled
|
|
40
|
+
* rejection from a button press must never become a crash.
|
|
41
|
+
*/
|
|
42
|
+
const EXPO_HANDLERS = {
|
|
43
|
+
selection: () => Haptics.selectionAsync(),
|
|
44
|
+
impactLight: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light),
|
|
45
|
+
impactMedium: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium),
|
|
46
|
+
impactHeavy: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy),
|
|
47
|
+
notificationSuccess: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success),
|
|
48
|
+
notificationWarning: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning),
|
|
49
|
+
notificationError: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
|
|
50
|
+
};
|
|
40
51
|
const triggerHaptic = (type = 'selection') => {
|
|
41
|
-
tryLoadOptional();
|
|
42
52
|
if (hapticImpl) {
|
|
43
53
|
try {
|
|
44
54
|
hapticImpl(type);
|
|
@@ -47,8 +57,18 @@ const triggerHaptic = (type = 'selection') => {
|
|
|
47
57
|
/* ignore */
|
|
48
58
|
}
|
|
49
59
|
}
|
|
60
|
+
const handler = EXPO_HANDLERS[type];
|
|
61
|
+
if (handler) {
|
|
62
|
+
handler().catch(() => fallback(type));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
50
65
|
fallback(type);
|
|
51
66
|
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Replace the haptic backend. Intended for TESTS and for the showcase; an app
|
|
70
|
+
* gets expo-haptics without wiring anything. Pass `null` to restore it.
|
|
71
|
+
*/
|
|
52
72
|
exports.triggerHaptic = triggerHaptic;
|
|
53
73
|
const setHapticImplementation = impl => {
|
|
54
74
|
hapticImpl = impl;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { Image, StyleSheet, Text, View } from 'react-native';
|
|
5
|
+
import { useTheme } from "../../theme/index.js";
|
|
6
|
+
import { Spinner } from "../Spinner/index.js";
|
|
7
|
+
import { acquireFetch, DEFAULT_TTL_MS, deriveCacheKey, isFresh, readEntry, releaseFetch, touchEntry } from "./authedImageCache.js";
|
|
8
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
const toError = value => value instanceof Error ? value : new Error(String(value));
|
|
10
|
+
const AuthedImage = props => {
|
|
11
|
+
const {
|
|
12
|
+
uri,
|
|
13
|
+
headers,
|
|
14
|
+
authenticated = true,
|
|
15
|
+
ttlMs = DEFAULT_TTL_MS,
|
|
16
|
+
placeholder,
|
|
17
|
+
fallback,
|
|
18
|
+
onLoadStart,
|
|
19
|
+
onLoadEnd,
|
|
20
|
+
onError,
|
|
21
|
+
cacheKey,
|
|
22
|
+
testID,
|
|
23
|
+
accessibilityLabel,
|
|
24
|
+
style,
|
|
25
|
+
...imageProps
|
|
26
|
+
} = props;
|
|
27
|
+
const theme = useTheme();
|
|
28
|
+
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
29
|
+
const [state, setState] = useState({
|
|
30
|
+
status: 'loading'
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Callbacks live behind refs so a caller passing inline arrows does not
|
|
34
|
+
// restart the fetch on every render.
|
|
35
|
+
const callbacks = useRef({
|
|
36
|
+
onLoadStart,
|
|
37
|
+
onLoadEnd,
|
|
38
|
+
onError
|
|
39
|
+
});
|
|
40
|
+
callbacks.current = {
|
|
41
|
+
onLoadStart,
|
|
42
|
+
onLoadEnd,
|
|
43
|
+
onError
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Compared by value: a headers object literal is a new identity every render.
|
|
47
|
+
const headersKey = JSON.stringify(headers ?? {});
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
let cancelled = false;
|
|
50
|
+
let acquired = null;
|
|
51
|
+
const finish = next => {
|
|
52
|
+
if (cancelled) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
setState(next);
|
|
56
|
+
callbacks.current.onLoadEnd?.();
|
|
57
|
+
};
|
|
58
|
+
const run = async () => {
|
|
59
|
+
if (!uri) {
|
|
60
|
+
const error = new Error('[@webority-technologies/mobile-ui] AuthedImage was given an empty `uri`. Pass the ' + 'image URL, or render nothing while the URL is still unknown.');
|
|
61
|
+
if (!cancelled) {
|
|
62
|
+
setState({
|
|
63
|
+
status: 'error'
|
|
64
|
+
});
|
|
65
|
+
callbacks.current.onError?.(error);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
setState({
|
|
70
|
+
status: 'loading'
|
|
71
|
+
});
|
|
72
|
+
callbacks.current.onLoadStart?.();
|
|
73
|
+
const key = deriveCacheKey(uri, cacheKey);
|
|
74
|
+
const cached = await readEntry(key);
|
|
75
|
+
if (cancelled) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (cached && isFresh(cached.meta, ttlMs)) {
|
|
79
|
+
void touchEntry(key, cached.meta);
|
|
80
|
+
finish({
|
|
81
|
+
status: 'ready',
|
|
82
|
+
dataUri: cached.dataUri
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const entry = acquireFetch(key, {
|
|
87
|
+
uri,
|
|
88
|
+
headers: JSON.parse(headersKey),
|
|
89
|
+
authenticated
|
|
90
|
+
});
|
|
91
|
+
acquired = {
|
|
92
|
+
key,
|
|
93
|
+
entry
|
|
94
|
+
};
|
|
95
|
+
try {
|
|
96
|
+
const result = await entry.promise;
|
|
97
|
+
finish({
|
|
98
|
+
status: 'ready',
|
|
99
|
+
dataUri: result.dataUri
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (cancelled) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// A stale image beats an empty box: the entry is only expired, and the
|
|
106
|
+
// refetch that would have replaced it failed.
|
|
107
|
+
if (cached) {
|
|
108
|
+
finish({
|
|
109
|
+
status: 'ready',
|
|
110
|
+
dataUri: cached.dataUri
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
setState({
|
|
115
|
+
status: 'error'
|
|
116
|
+
});
|
|
117
|
+
callbacks.current.onError?.(toError(error));
|
|
118
|
+
callbacks.current.onLoadEnd?.();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
void run();
|
|
122
|
+
return () => {
|
|
123
|
+
cancelled = true;
|
|
124
|
+
if (acquired) {
|
|
125
|
+
releaseFetch(acquired.key, acquired.entry);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}, [uri, cacheKey, ttlMs, authenticated, headersKey]);
|
|
129
|
+
|
|
130
|
+
// The base `testID` sits on the root in EVERY state, with the state-specific
|
|
131
|
+
// suffixes nested inside it. A consumer that can only find the component while
|
|
132
|
+
// it happens to be loaded cannot wait for it to load.
|
|
133
|
+
if (state.status === 'loading') {
|
|
134
|
+
if (placeholder !== undefined) {
|
|
135
|
+
return /*#__PURE__*/_jsx(View, {
|
|
136
|
+
style: style,
|
|
137
|
+
testID: testID,
|
|
138
|
+
children: placeholder
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return /*#__PURE__*/_jsx(View, {
|
|
142
|
+
style: [styles.placeholder, style],
|
|
143
|
+
testID: testID,
|
|
144
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
145
|
+
style: styles.stateFill,
|
|
146
|
+
testID: testID ? `${testID}-placeholder` : undefined,
|
|
147
|
+
pointerEvents: "none",
|
|
148
|
+
children: /*#__PURE__*/_jsx(Spinner, {
|
|
149
|
+
size: "sm",
|
|
150
|
+
accessibilityLabel: accessibilityLabel ?? 'Loading image'
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (state.status === 'error') {
|
|
156
|
+
if (fallback !== undefined) {
|
|
157
|
+
return /*#__PURE__*/_jsx(View, {
|
|
158
|
+
style: style,
|
|
159
|
+
testID: testID,
|
|
160
|
+
children: fallback
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return /*#__PURE__*/_jsx(View, {
|
|
164
|
+
style: [styles.fallback, style],
|
|
165
|
+
testID: testID,
|
|
166
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
167
|
+
accessible: true,
|
|
168
|
+
accessibilityRole: "image",
|
|
169
|
+
accessibilityLabel: accessibilityLabel ? `${accessibilityLabel}, image unavailable` : 'Image unavailable',
|
|
170
|
+
testID: testID ? `${testID}-fallback` : undefined,
|
|
171
|
+
style: styles.stateFill,
|
|
172
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
173
|
+
style: styles.fallbackText,
|
|
174
|
+
children: "!"
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return /*#__PURE__*/_jsx(Image, {
|
|
180
|
+
...imageProps,
|
|
181
|
+
accessible: true,
|
|
182
|
+
accessibilityRole: "image",
|
|
183
|
+
accessibilityLabel: accessibilityLabel,
|
|
184
|
+
testID: testID,
|
|
185
|
+
style: style,
|
|
186
|
+
source: {
|
|
187
|
+
uri: state.dataUri
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
AuthedImage.displayName = 'AuthedImage';
|
|
192
|
+
const buildStyles = theme => StyleSheet.create({
|
|
193
|
+
placeholder: {
|
|
194
|
+
alignItems: 'center',
|
|
195
|
+
justifyContent: 'center',
|
|
196
|
+
backgroundColor: theme.colors.background.secondary,
|
|
197
|
+
borderRadius: theme.radius.md
|
|
198
|
+
},
|
|
199
|
+
fallback: {
|
|
200
|
+
alignItems: 'center',
|
|
201
|
+
justifyContent: 'center',
|
|
202
|
+
backgroundColor: theme.colors.background.secondary,
|
|
203
|
+
borderRadius: theme.radius.md,
|
|
204
|
+
borderWidth: theme.colors.border.width,
|
|
205
|
+
borderColor: theme.colors.border.secondary,
|
|
206
|
+
padding: theme.spacing.sm
|
|
207
|
+
},
|
|
208
|
+
stateFill: {
|
|
209
|
+
alignItems: 'center',
|
|
210
|
+
flex: 1,
|
|
211
|
+
justifyContent: 'center'
|
|
212
|
+
},
|
|
213
|
+
fallbackText: {
|
|
214
|
+
color: theme.colors.text.tertiary,
|
|
215
|
+
fontSize: theme.typography.fontSize.lg,
|
|
216
|
+
fontWeight: theme.typography.fontWeight.bold
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
export { AuthedImage };
|
|
220
|
+
export default AuthedImage;
|
|
221
|
+
//# sourceMappingURL=AuthedImage.js.map
|