@webority-technologies/mobile-ui 0.0.4 → 0.0.5

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.
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.ZoomableImage = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _reactNativeGestureHandler = require("react-native-gesture-handler");
10
+ var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
11
+ var _index = require("../../utils/index.js");
12
+ var _jsxRuntime = require("react/jsx-runtime");
13
+ 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); }
14
+ /**
15
+ * ZoomableImage — a standalone pinch-to-zoom / pan / double-tap-to-zoom
16
+ * wrapper for a single piece of content (typically one `<Image>`).
17
+ *
18
+ * Reuses the same Reanimated 3 gesture composition `ImageGallery`'s internal
19
+ * lightbox slide uses (pinch clamped and rubber-banded at its limits, pan
20
+ * gated to only engage once zoomed in, double-tap toggling between baseline
21
+ * and `doubleTapScale`), stripped of the carousel-slide concerns that
22
+ * component needs and this one does not — there is no `index`/`total`/
23
+ * `active` because a standalone image is always the one being viewed.
24
+ *
25
+ * USAGE:
26
+ *
27
+ * <ZoomableImage>
28
+ * <Image source={{ uri }} style={styles.image} resizeMode="contain" />
29
+ * </ZoomableImage>
30
+ *
31
+ * Takes `children` rather than a `source`/`uri` prop: every real consumer of
32
+ * this pattern in the fleet wraps an already-authenticated image component
33
+ * (headers, caching, its own loading state) rather than a bare `<Image>`, so
34
+ * a `source` prop would force re-implementing that per app. Wrapping keeps
35
+ * this component ignorant of how the image is fetched.
36
+ */
37
+
38
+ const DEFAULT_MAX_SCALE = 4;
39
+ const DEFAULT_MIN_SCALE = 1;
40
+ const DEFAULT_DOUBLE_TAP_SCALE = 2;
41
+ const SPRING_CONFIG = {
42
+ damping: 18,
43
+ stiffness: 220,
44
+ mass: 1
45
+ };
46
+ const ZoomableImage = ({
47
+ children,
48
+ maxScale = DEFAULT_MAX_SCALE,
49
+ minScale = DEFAULT_MIN_SCALE,
50
+ doubleTapScale = DEFAULT_DOUBLE_TAP_SCALE,
51
+ disabled = false,
52
+ haptic,
53
+ style,
54
+ accessibilityLabel,
55
+ testID
56
+ }) => {
57
+ const scale = (0, _reactNativeReanimated.useSharedValue)(1);
58
+ const savedScale = (0, _reactNativeReanimated.useSharedValue)(1);
59
+ const translateX = (0, _reactNativeReanimated.useSharedValue)(0);
60
+ const translateY = (0, _reactNativeReanimated.useSharedValue)(0);
61
+ const savedTranslateX = (0, _reactNativeReanimated.useSharedValue)(0);
62
+ const savedTranslateY = (0, _reactNativeReanimated.useSharedValue)(0);
63
+ const resetTransform = (0, _react.useCallback)(() => {
64
+ scale.value = (0, _reactNativeReanimated.withSpring)(1, SPRING_CONFIG);
65
+ savedScale.value = 1;
66
+ translateX.value = (0, _reactNativeReanimated.withSpring)(0, SPRING_CONFIG);
67
+ translateY.value = (0, _reactNativeReanimated.withSpring)(0, SPRING_CONFIG);
68
+ savedTranslateX.value = 0;
69
+ savedTranslateY.value = 0;
70
+ }, [scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY]);
71
+
72
+ // Disabling mid-zoom must not leave the content stranded scaled/panned —
73
+ // snap it back the same way ImageGallery's slide resets on losing focus.
74
+ (0, _react.useEffect)(() => {
75
+ if (disabled) {
76
+ resetTransform();
77
+ }
78
+ }, [disabled, resetTransform]);
79
+ const triggerImpact = (0, _react.useCallback)(() => {
80
+ const h = (0, _index.resolveHaptic)(haptic, 'impactLight');
81
+ if (h) {
82
+ (0, _index.triggerHaptic)(h);
83
+ }
84
+ }, [haptic]);
85
+ const pinch = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Pinch().enabled(!disabled).onStart(() => {
86
+ 'worklet';
87
+
88
+ savedScale.value = scale.value;
89
+ }).onUpdate(e => {
90
+ 'worklet';
91
+
92
+ const next = savedScale.value * e.scale;
93
+ // Clamp minScale..maxScale on the UI thread so pinch feels rubbery at limits.
94
+ if (next < minScale) {
95
+ scale.value = minScale + (next - minScale) * 0.3;
96
+ } else if (next > maxScale) {
97
+ scale.value = maxScale + (next - maxScale) * 0.2;
98
+ } else {
99
+ scale.value = next;
100
+ }
101
+ }).onEnd(() => {
102
+ 'worklet';
103
+
104
+ if (scale.value < minScale) {
105
+ scale.value = (0, _reactNativeReanimated.withSpring)(minScale, SPRING_CONFIG);
106
+ translateX.value = (0, _reactNativeReanimated.withSpring)(0, SPRING_CONFIG);
107
+ translateY.value = (0, _reactNativeReanimated.withSpring)(0, SPRING_CONFIG);
108
+ savedScale.value = minScale;
109
+ savedTranslateX.value = 0;
110
+ savedTranslateY.value = 0;
111
+ return;
112
+ }
113
+ if (scale.value > maxScale) {
114
+ scale.value = (0, _reactNativeReanimated.withSpring)(maxScale, SPRING_CONFIG);
115
+ savedScale.value = maxScale;
116
+ return;
117
+ }
118
+ savedScale.value = scale.value;
119
+ }), [disabled, scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY, minScale, maxScale]);
120
+
121
+ // Pan only engages when zoomed in — otherwise a scroll view or carousel this
122
+ // is nested inside keeps owning the gesture.
123
+ const pan = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Pan().enabled(!disabled).minPointers(1).maxPointers(1).onStart(() => {
124
+ 'worklet';
125
+
126
+ savedTranslateX.value = translateX.value;
127
+ savedTranslateY.value = translateY.value;
128
+ }).onUpdate(e => {
129
+ 'worklet';
130
+
131
+ if (scale.value <= 1) {
132
+ return;
133
+ }
134
+ translateX.value = savedTranslateX.value + e.translationX;
135
+ translateY.value = savedTranslateY.value + e.translationY;
136
+ }).onEnd(() => {
137
+ 'worklet';
138
+
139
+ savedTranslateX.value = translateX.value;
140
+ savedTranslateY.value = translateY.value;
141
+ }), [disabled, scale, translateX, translateY, savedTranslateX, savedTranslateY]);
142
+ const doubleTap = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Tap().enabled(!disabled).numberOfTaps(2).onEnd(() => {
143
+ 'worklet';
144
+
145
+ (0, _reactNativeReanimated.runOnJS)(triggerImpact)();
146
+ if (scale.value > 1) {
147
+ scale.value = (0, _reactNativeReanimated.withSpring)(1, SPRING_CONFIG);
148
+ translateX.value = (0, _reactNativeReanimated.withSpring)(0, SPRING_CONFIG);
149
+ translateY.value = (0, _reactNativeReanimated.withSpring)(0, SPRING_CONFIG);
150
+ savedScale.value = 1;
151
+ savedTranslateX.value = 0;
152
+ savedTranslateY.value = 0;
153
+ } else {
154
+ scale.value = (0, _reactNativeReanimated.withSpring)(doubleTapScale, SPRING_CONFIG);
155
+ savedScale.value = doubleTapScale;
156
+ }
157
+ }), [disabled, scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY, triggerImpact, doubleTapScale]);
158
+
159
+ // Race the gestures so they coordinate cleanly: a tap won't be misread as a
160
+ // tiny pan, and pan won't fire when the user actually starts a pinch.
161
+ const composed = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Race(pinch, pan, doubleTap), [pinch, pan, doubleTap]);
162
+ const animatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
163
+ transform: [{
164
+ translateX: translateX.value
165
+ }, {
166
+ translateY: translateY.value
167
+ }, {
168
+ scale: scale.value
169
+ }]
170
+ }));
171
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
172
+ gesture: composed,
173
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
174
+ style: [styles.container, style],
175
+ testID: testID,
176
+ accessibilityLabel: accessibilityLabel,
177
+ accessibilityState: {
178
+ disabled
179
+ },
180
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
181
+ style: [styles.content, animatedStyle],
182
+ children: children
183
+ })
184
+ })
185
+ });
186
+ };
187
+ exports.ZoomableImage = ZoomableImage;
188
+ ZoomableImage.displayName = 'ZoomableImage';
189
+ const styles = _reactNative.StyleSheet.create({
190
+ container: {
191
+ flex: 1,
192
+ alignItems: 'center',
193
+ justifyContent: 'center',
194
+ overflow: 'hidden'
195
+ },
196
+ content: {
197
+ width: '100%',
198
+ height: '100%',
199
+ alignItems: 'center',
200
+ justifyContent: 'center'
201
+ }
202
+ });
203
+ var _default = exports.default = ZoomableImage;
204
+ //# sourceMappingURL=ZoomableImage.js.map
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "ZoomableImage", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _ZoomableImage.ZoomableImage;
10
+ }
11
+ });
12
+ var _ZoomableImage = require("./ZoomableImage.js");
13
+ //# sourceMappingURL=index.js.map
@@ -513,6 +513,12 @@ Object.defineProperty(exports, "Tooltip", {
513
513
  return _index65.Tooltip;
514
514
  }
515
515
  });
516
+ Object.defineProperty(exports, "ZoomableImage", {
517
+ enumerable: true,
518
+ get: function () {
519
+ return _index66.ZoomableImage;
520
+ }
521
+ });
516
522
  Object.defineProperty(exports, "applyMarkdownAction", {
517
523
  enumerable: true,
518
524
  get: function () {
@@ -682,4 +688,5 @@ var _index62 = require("./Timeline/index.js");
682
688
  var _index63 = require("./TimePicker/index.js");
683
689
  var _index64 = require("./Toast/index.js");
684
690
  var _index65 = require("./Tooltip/index.js");
691
+ var _index66 = require("./ZoomableImage/index.js");
685
692
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * ZoomableImage — a standalone pinch-to-zoom / pan / double-tap-to-zoom
5
+ * wrapper for a single piece of content (typically one `<Image>`).
6
+ *
7
+ * Reuses the same Reanimated 3 gesture composition `ImageGallery`'s internal
8
+ * lightbox slide uses (pinch clamped and rubber-banded at its limits, pan
9
+ * gated to only engage once zoomed in, double-tap toggling between baseline
10
+ * and `doubleTapScale`), stripped of the carousel-slide concerns that
11
+ * component needs and this one does not — there is no `index`/`total`/
12
+ * `active` because a standalone image is always the one being viewed.
13
+ *
14
+ * USAGE:
15
+ *
16
+ * <ZoomableImage>
17
+ * <Image source={{ uri }} style={styles.image} resizeMode="contain" />
18
+ * </ZoomableImage>
19
+ *
20
+ * Takes `children` rather than a `source`/`uri` prop: every real consumer of
21
+ * this pattern in the fleet wraps an already-authenticated image component
22
+ * (headers, caching, its own loading state) rather than a bare `<Image>`, so
23
+ * a `source` prop would force re-implementing that per app. Wrapping keeps
24
+ * this component ignorant of how the image is fetched.
25
+ */
26
+
27
+ import React, { useCallback, useEffect, useMemo } from 'react';
28
+ import { StyleSheet } from 'react-native';
29
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
30
+ import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
31
+ import { resolveHaptic, triggerHaptic } from "../../utils/index.js";
32
+ import { jsx as _jsx } from "react/jsx-runtime";
33
+ const DEFAULT_MAX_SCALE = 4;
34
+ const DEFAULT_MIN_SCALE = 1;
35
+ const DEFAULT_DOUBLE_TAP_SCALE = 2;
36
+ const SPRING_CONFIG = {
37
+ damping: 18,
38
+ stiffness: 220,
39
+ mass: 1
40
+ };
41
+ export const ZoomableImage = ({
42
+ children,
43
+ maxScale = DEFAULT_MAX_SCALE,
44
+ minScale = DEFAULT_MIN_SCALE,
45
+ doubleTapScale = DEFAULT_DOUBLE_TAP_SCALE,
46
+ disabled = false,
47
+ haptic,
48
+ style,
49
+ accessibilityLabel,
50
+ testID
51
+ }) => {
52
+ const scale = useSharedValue(1);
53
+ const savedScale = useSharedValue(1);
54
+ const translateX = useSharedValue(0);
55
+ const translateY = useSharedValue(0);
56
+ const savedTranslateX = useSharedValue(0);
57
+ const savedTranslateY = useSharedValue(0);
58
+ const resetTransform = useCallback(() => {
59
+ scale.value = withSpring(1, SPRING_CONFIG);
60
+ savedScale.value = 1;
61
+ translateX.value = withSpring(0, SPRING_CONFIG);
62
+ translateY.value = withSpring(0, SPRING_CONFIG);
63
+ savedTranslateX.value = 0;
64
+ savedTranslateY.value = 0;
65
+ }, [scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY]);
66
+
67
+ // Disabling mid-zoom must not leave the content stranded scaled/panned —
68
+ // snap it back the same way ImageGallery's slide resets on losing focus.
69
+ useEffect(() => {
70
+ if (disabled) {
71
+ resetTransform();
72
+ }
73
+ }, [disabled, resetTransform]);
74
+ const triggerImpact = useCallback(() => {
75
+ const h = resolveHaptic(haptic, 'impactLight');
76
+ if (h) {
77
+ triggerHaptic(h);
78
+ }
79
+ }, [haptic]);
80
+ const pinch = useMemo(() => Gesture.Pinch().enabled(!disabled).onStart(() => {
81
+ 'worklet';
82
+
83
+ savedScale.value = scale.value;
84
+ }).onUpdate(e => {
85
+ 'worklet';
86
+
87
+ const next = savedScale.value * e.scale;
88
+ // Clamp minScale..maxScale on the UI thread so pinch feels rubbery at limits.
89
+ if (next < minScale) {
90
+ scale.value = minScale + (next - minScale) * 0.3;
91
+ } else if (next > maxScale) {
92
+ scale.value = maxScale + (next - maxScale) * 0.2;
93
+ } else {
94
+ scale.value = next;
95
+ }
96
+ }).onEnd(() => {
97
+ 'worklet';
98
+
99
+ if (scale.value < minScale) {
100
+ scale.value = withSpring(minScale, SPRING_CONFIG);
101
+ translateX.value = withSpring(0, SPRING_CONFIG);
102
+ translateY.value = withSpring(0, SPRING_CONFIG);
103
+ savedScale.value = minScale;
104
+ savedTranslateX.value = 0;
105
+ savedTranslateY.value = 0;
106
+ return;
107
+ }
108
+ if (scale.value > maxScale) {
109
+ scale.value = withSpring(maxScale, SPRING_CONFIG);
110
+ savedScale.value = maxScale;
111
+ return;
112
+ }
113
+ savedScale.value = scale.value;
114
+ }), [disabled, scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY, minScale, maxScale]);
115
+
116
+ // Pan only engages when zoomed in — otherwise a scroll view or carousel this
117
+ // is nested inside keeps owning the gesture.
118
+ const pan = useMemo(() => Gesture.Pan().enabled(!disabled).minPointers(1).maxPointers(1).onStart(() => {
119
+ 'worklet';
120
+
121
+ savedTranslateX.value = translateX.value;
122
+ savedTranslateY.value = translateY.value;
123
+ }).onUpdate(e => {
124
+ 'worklet';
125
+
126
+ if (scale.value <= 1) {
127
+ return;
128
+ }
129
+ translateX.value = savedTranslateX.value + e.translationX;
130
+ translateY.value = savedTranslateY.value + e.translationY;
131
+ }).onEnd(() => {
132
+ 'worklet';
133
+
134
+ savedTranslateX.value = translateX.value;
135
+ savedTranslateY.value = translateY.value;
136
+ }), [disabled, scale, translateX, translateY, savedTranslateX, savedTranslateY]);
137
+ const doubleTap = useMemo(() => Gesture.Tap().enabled(!disabled).numberOfTaps(2).onEnd(() => {
138
+ 'worklet';
139
+
140
+ runOnJS(triggerImpact)();
141
+ if (scale.value > 1) {
142
+ scale.value = withSpring(1, SPRING_CONFIG);
143
+ translateX.value = withSpring(0, SPRING_CONFIG);
144
+ translateY.value = withSpring(0, SPRING_CONFIG);
145
+ savedScale.value = 1;
146
+ savedTranslateX.value = 0;
147
+ savedTranslateY.value = 0;
148
+ } else {
149
+ scale.value = withSpring(doubleTapScale, SPRING_CONFIG);
150
+ savedScale.value = doubleTapScale;
151
+ }
152
+ }), [disabled, scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY, triggerImpact, doubleTapScale]);
153
+
154
+ // Race the gestures so they coordinate cleanly: a tap won't be misread as a
155
+ // tiny pan, and pan won't fire when the user actually starts a pinch.
156
+ const composed = useMemo(() => Gesture.Race(pinch, pan, doubleTap), [pinch, pan, doubleTap]);
157
+ const animatedStyle = useAnimatedStyle(() => ({
158
+ transform: [{
159
+ translateX: translateX.value
160
+ }, {
161
+ translateY: translateY.value
162
+ }, {
163
+ scale: scale.value
164
+ }]
165
+ }));
166
+ return /*#__PURE__*/_jsx(GestureDetector, {
167
+ gesture: composed,
168
+ children: /*#__PURE__*/_jsx(Animated.View, {
169
+ style: [styles.container, style],
170
+ testID: testID,
171
+ accessibilityLabel: accessibilityLabel,
172
+ accessibilityState: {
173
+ disabled
174
+ },
175
+ children: /*#__PURE__*/_jsx(Animated.View, {
176
+ style: [styles.content, animatedStyle],
177
+ children: children
178
+ })
179
+ })
180
+ });
181
+ };
182
+ ZoomableImage.displayName = 'ZoomableImage';
183
+ const styles = StyleSheet.create({
184
+ container: {
185
+ flex: 1,
186
+ alignItems: 'center',
187
+ justifyContent: 'center',
188
+ overflow: 'hidden'
189
+ },
190
+ content: {
191
+ width: '100%',
192
+ height: '100%',
193
+ alignItems: 'center',
194
+ justifyContent: 'center'
195
+ }
196
+ });
197
+ export default ZoomableImage;
198
+ //# sourceMappingURL=ZoomableImage.js.map
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export { ZoomableImage } from "./ZoomableImage.js";
4
+ //# sourceMappingURL=index.js.map
@@ -67,4 +67,5 @@ export { Timeline } from "./Timeline/index.js";
67
67
  export { TimePicker } from "./TimePicker/index.js";
68
68
  export { Toast, ToastProvider, toast, useToast } from "./Toast/index.js";
69
69
  export { Tooltip } from "./Tooltip/index.js";
70
+ export { ZoomableImage } from "./ZoomableImage/index.js";
70
71
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * ZoomableImage — a standalone pinch-to-zoom / pan / double-tap-to-zoom
3
+ * wrapper for a single piece of content (typically one `<Image>`).
4
+ *
5
+ * Reuses the same Reanimated 3 gesture composition `ImageGallery`'s internal
6
+ * lightbox slide uses (pinch clamped and rubber-banded at its limits, pan
7
+ * gated to only engage once zoomed in, double-tap toggling between baseline
8
+ * and `doubleTapScale`), stripped of the carousel-slide concerns that
9
+ * component needs and this one does not — there is no `index`/`total`/
10
+ * `active` because a standalone image is always the one being viewed.
11
+ *
12
+ * USAGE:
13
+ *
14
+ * <ZoomableImage>
15
+ * <Image source={{ uri }} style={styles.image} resizeMode="contain" />
16
+ * </ZoomableImage>
17
+ *
18
+ * Takes `children` rather than a `source`/`uri` prop: every real consumer of
19
+ * this pattern in the fleet wraps an already-authenticated image component
20
+ * (headers, caching, its own loading state) rather than a bare `<Image>`, so
21
+ * a `source` prop would force re-implementing that per app. Wrapping keeps
22
+ * this component ignorant of how the image is fetched.
23
+ */
24
+ import React from 'react';
25
+ import type { StyleProp, ViewStyle } from 'react-native';
26
+ import type { HapticType } from '../../utils';
27
+ export interface ZoomableImageProps {
28
+ /** The content to pinch-zoom, pan, and double-tap-zoom — typically a single `<Image>`. */
29
+ children: React.ReactNode;
30
+ /** Upper bound on pinch and double-tap zoom. Default 4. */
31
+ maxScale?: number;
32
+ /** Lower bound on pinch zoom — the resting scale a pinch springs back to. Default 1. */
33
+ minScale?: number;
34
+ /** Scale a double tap zooms to; a second double tap returns to baseline. Default 2. */
35
+ doubleTapScale?: number;
36
+ /** Blocks pinch/pan/double-tap and smoothly resets any active zoom back to baseline. Default false. */
37
+ disabled?: boolean;
38
+ /** Haptic feedback on double-tap-to-zoom. Pass false to disable; pass a HapticType to override. */
39
+ haptic?: HapticType | false;
40
+ /** Style for the outer wrapping view. */
41
+ style?: StyleProp<ViewStyle>;
42
+ accessibilityLabel?: string;
43
+ testID?: string;
44
+ }
45
+ export declare const ZoomableImage: React.FC<ZoomableImageProps>;
46
+ export default ZoomableImage;
47
+ //# sourceMappingURL=ZoomableImage.d.ts.map
@@ -0,0 +1,3 @@
1
+ export type { ZoomableImageProps } from './ZoomableImage';
2
+ export { ZoomableImage } from './ZoomableImage';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -132,4 +132,6 @@ export type { ToastApi, ToastOptions, ToastPosition, ToastProps, ToastProviderPr
132
132
  export { Toast, ToastProvider, toast, useToast } from './Toast';
133
133
  export type { TooltipPlacement, TooltipProps, TooltipTrigger } from './Tooltip';
134
134
  export { Tooltip } from './Tooltip';
135
+ export type { ZoomableImageProps } from './ZoomableImage';
136
+ export { ZoomableImage } from './ZoomableImage';
135
137
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * ZoomableImage — a standalone pinch-to-zoom / pan / double-tap-to-zoom
3
+ * wrapper for a single piece of content (typically one `<Image>`).
4
+ *
5
+ * Reuses the same Reanimated 3 gesture composition `ImageGallery`'s internal
6
+ * lightbox slide uses (pinch clamped and rubber-banded at its limits, pan
7
+ * gated to only engage once zoomed in, double-tap toggling between baseline
8
+ * and `doubleTapScale`), stripped of the carousel-slide concerns that
9
+ * component needs and this one does not — there is no `index`/`total`/
10
+ * `active` because a standalone image is always the one being viewed.
11
+ *
12
+ * USAGE:
13
+ *
14
+ * <ZoomableImage>
15
+ * <Image source={{ uri }} style={styles.image} resizeMode="contain" />
16
+ * </ZoomableImage>
17
+ *
18
+ * Takes `children` rather than a `source`/`uri` prop: every real consumer of
19
+ * this pattern in the fleet wraps an already-authenticated image component
20
+ * (headers, caching, its own loading state) rather than a bare `<Image>`, so
21
+ * a `source` prop would force re-implementing that per app. Wrapping keeps
22
+ * this component ignorant of how the image is fetched.
23
+ */
24
+ import React from 'react';
25
+ import type { StyleProp, ViewStyle } from 'react-native';
26
+ import type { HapticType } from '../../utils/index.js';
27
+ export interface ZoomableImageProps {
28
+ /** The content to pinch-zoom, pan, and double-tap-zoom — typically a single `<Image>`. */
29
+ children: React.ReactNode;
30
+ /** Upper bound on pinch and double-tap zoom. Default 4. */
31
+ maxScale?: number;
32
+ /** Lower bound on pinch zoom — the resting scale a pinch springs back to. Default 1. */
33
+ minScale?: number;
34
+ /** Scale a double tap zooms to; a second double tap returns to baseline. Default 2. */
35
+ doubleTapScale?: number;
36
+ /** Blocks pinch/pan/double-tap and smoothly resets any active zoom back to baseline. Default false. */
37
+ disabled?: boolean;
38
+ /** Haptic feedback on double-tap-to-zoom. Pass false to disable; pass a HapticType to override. */
39
+ haptic?: HapticType | false;
40
+ /** Style for the outer wrapping view. */
41
+ style?: StyleProp<ViewStyle>;
42
+ accessibilityLabel?: string;
43
+ testID?: string;
44
+ }
45
+ export declare const ZoomableImage: React.FC<ZoomableImageProps>;
46
+ export default ZoomableImage;
47
+ //# sourceMappingURL=ZoomableImage.d.ts.map
@@ -0,0 +1,3 @@
1
+ export type { ZoomableImageProps } from './ZoomableImage.js';
2
+ export { ZoomableImage } from './ZoomableImage.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -132,4 +132,6 @@ export type { ToastApi, ToastOptions, ToastPosition, ToastProps, ToastProviderPr
132
132
  export { Toast, ToastProvider, toast, useToast } from './Toast/index.js';
133
133
  export type { TooltipPlacement, TooltipProps, TooltipTrigger } from './Tooltip/index.js';
134
134
  export { Tooltip } from './Tooltip/index.js';
135
+ export type { ZoomableImageProps } from './ZoomableImage/index.js';
136
+ export { ZoomableImage } from './ZoomableImage/index.js';
135
137
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority-technologies/mobile-ui",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Beautiful, animated, accessible React Native components, theme and form engine for Webority projects.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -61,7 +61,7 @@
61
61
  "typecheck": "tsc --noEmit"
62
62
  },
63
63
  "dependencies": {
64
- "@webority-technologies/mobile-core": "^0.0.4"
64
+ "@webority-technologies/mobile-core": "^0.0.5"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "expo": ">=57.0.0",