1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! `whisker-image` — networked image component.
//!
//! **API shape — 1 (pure Component).** See
//! [`docs/module-api-design.md`](https://github.com/whiskerrs/whisker/blob/main/docs/module-api-design.md)
//! §"Shape 1". All state is captured by props; no imperative
//! handle. Mounts a `whisker-image:Image` element backed by:
//!
//! - **iOS**: `UIImageView` + [Kingfisher](https://github.com/onevcat/Kingfisher)
//! for URL fetching, in-memory `NSCache`, and on-disk cache.
//! - **Android**: `ImageView` + [Coil](https://coil-kt.github.io/coil/)
//! for URL fetching, `LruCache`, and disk cache.
//!
//! ## Why a separate module instead of Lynx's `<image>`?
//!
//! Lynx ships a `LynxServiceImageProtocol` interface that's expected
//! to be implemented + registered by the host app (Lynx's own
//! `LynxImageService` uses SDWebImage on iOS / Fresco on Android,
//! but it's a separate subspec that consumers wire themselves). The
//! Whisker iOS / Android distribution doesn't include any
//! implementation, so a bare `<image src="…">` mounts a `UIImageView`
//! whose `image` property never gets assigned. `whisker-image` skips
//! the Lynx image stack entirely and drives the URL load from the
//! native module directly — same idea as `whisker-video` for media
//! playback.
//!
//! ## Usage
//!
//! ```ignore
//! use whisker::prelude::*;
//! use whisker_image::{Image, ImageMode, ImageProps};
//!
//! #[whisker::main]
//! fn app() -> Element {
//! render! {
//! Image(
//! src: "https://example.com/cover.jpg",
//! mode: ImageMode::AspectFill,
//! style: "width: 240px; height: 240px; border-radius: 8px;",
//! )
//! }
//! }
//! ```
//!
//! ## Props
//!
//! - `src` — image URL (HTTPS recommended; `http://` works if the
//! host app's network security config allows cleartext).
//! - `mode` — content fit. Takes the typed [`ImageMode`] enum;
//! defaults to [`ImageMode::AspectFill`].
//! - `style` — standard Whisker style string. Width / height must be
//! set on the element (or via flex sizing) — Kingfisher / Coil
//! target-size the fetched bitmap against the rendered size, so an
//! element with `width: 0; height: 0;` would never paint.
//!
//! ## Native source
//!
//! Contributors: the matching platform module lives at
//!
//! - iOS: `packages/whisker-image/ios/Sources/WhiskerImage/ImageModule.swift`
//! (view: `ImageView.swift`)
//! - Android: `packages/whisker-image/android/src/main/kotlin/rs/whisker/elements/image/ImageModule.kt`
//! (view: `WhiskerImageView.kt`)
use Signal;
/// Content-fit mode for an [`Image`]. The variant names mirror the
/// camelCase wire strings the iOS and Android image-view modules
/// dispatch on (`packages/whisker-image/ios/Sources/WhiskerImage/`,
/// `packages/whisker-image/android/src/main/kotlin/.../WhiskerImageView.kt`).
///
/// `#[non_exhaustive]` so a future fit mode (cover, contain, …) can
/// be added without breaking exhaustive matches downstream.
/// `whisker-image:Image` element. All props are reactive — the
/// platform-side setters re-apply whenever the bound signals change,
/// so a `src` swap re-fetches and a `mode` swap re-lays-out without
/// remount. Corners follow the standard CSS `border-radius` in the
/// `style:` cascade (iOS clips via `UIView.layer.cornerRadius` +
/// `clipsToBounds`; Android extracts the parsed radius from Lynx's
/// `onBorderRadiusUpdated` callback and feeds it to Coil's
/// `RoundedCornersTransformation`).