@octane-xplat/ui 0.10.0 → 0.11.0
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/dist/native/Image.tsrx.js +151 -16
- package/dist/native/hash-image.js +285 -0
- package/dist/native/image-content-binding.js +158 -0
- package/dist/native/image-content.js +206 -0
- package/dist/native/image-sizing.js +102 -0
- package/dist/native/octane-client-build.json +1 -1
- package/dist/native/vendor/ui-svg/index.android.js +1 -1
- package/dist/web/Image.web.tsrx.js +112 -19
- package/dist/web/hash-image.js +285 -0
- package/dist/web/image-content.js +133 -0
- package/dist/web/octane-client-build.json +1 -1
- package/package.json +3 -2
- package/src/Image.macos.tsrx +11 -2
- package/src/Image.tsrx +203 -17
- package/src/Image.web.tsrx +91 -5
- package/src/Tabs.macos.tsrx +2 -0
- package/src/Timestamp.macos.tsrx +1 -1
- package/src/hash-image.test.ts +72 -0
- package/src/hash-image.ts +310 -0
- package/src/hosted-unsupported.macos.tsrx +1 -2
- package/src/image-content-binding.mobile.test.ts +174 -0
- package/src/image-content-binding.ts +231 -0
- package/src/image-content.mobile.test.tsrx +84 -0
- package/src/image-content.test.ts +219 -0
- package/src/image-content.ts +309 -0
- package/src/image-decoding.web.test.tsrx +26 -0
- package/src/image-placeholder.mobile.test.tsrx +113 -0
- package/src/image-placeholder.web.test.tsrx +48 -0
- package/src/image-recycling.mobile.test.ts +61 -0
- package/src/image-sizing.mobile.test.ts +158 -0
- package/src/image-sizing.ts +117 -0
- package/src/image.mobile.test.tsrx +81 -0
- package/src/index.macos.ts +1 -0
- package/src/index.shared.ts +7 -0
- package/src/index.shared.web.ts +7 -0
- package/src/list-viewability-observer.ios.ts +1 -0
- package/src/list-viewability.mobile.test.ts +1 -0
- package/src/props.ts +100 -1
- package/src/safeAreaInsets.macos.tsrx +6 -1
- package/src/styled.macos.tsrx +1 -1
- package/src/vendor/ui-svg/src/index.android.ts +5 -1
- package/types/generated/.tsrx-typegen-manifest.json +170 -0
- package/types/generated/Accordion.macos.d.ts +5 -0
- package/types/generated/Alert.macos.d.ts +3 -0
- package/types/generated/AlertDialog.macos.d.ts +4 -0
- package/types/generated/AppShell.macos.d.ts +7 -0
- package/types/generated/AspectRatio.macos.d.ts +5 -0
- package/types/generated/Avatar.macos.d.ts +6 -0
- package/types/generated/AvatarGroup.macos.d.ts +6 -0
- package/types/generated/Badge.macos.d.ts +5 -0
- package/types/generated/Banner.macos.d.ts +4 -0
- package/types/generated/BaseTypeahead.macos.d.ts +7 -0
- package/types/generated/Blockquote.macos.d.ts +6 -0
- package/types/generated/BottomSheet.macos.d.ts +3 -0
- package/types/generated/Breadcrumb.macos.d.ts +4 -0
- package/types/generated/ButtonGroup.macos.d.ts +3 -0
- package/types/generated/Calendar.macos.d.ts +5 -0
- package/types/generated/Card.macos.d.ts +4 -0
- package/types/generated/Carousel.macos.d.ts +6 -0
- package/types/generated/Center.macos.d.ts +4 -0
- package/types/generated/CheckIndicator.macos.d.ts +4 -0
- package/types/generated/CheckboxGroup.macos.d.ts +3 -0
- package/types/generated/CheckboxIndicator.macos.d.ts +4 -0
- package/types/generated/Chip.macos.d.ts +5 -0
- package/types/generated/Citation.macos.d.ts +4 -0
- package/types/generated/ClickableCard.macos.d.ts +3 -0
- package/types/generated/Code.macos.d.ts +5 -0
- package/types/generated/CodeBlock.macos.d.ts +6 -0
- package/types/generated/Collapsible.macos.d.ts +5 -0
- package/types/generated/CommandPalette.macos.d.ts +3 -0
- package/types/generated/ComplexSelector.macos.d.ts +3 -0
- package/types/generated/ContextMenu.macos.d.ts +5 -0
- package/types/generated/DateInput.macos.d.ts +6 -0
- package/types/generated/DateRangeInput.macos.d.ts +5 -0
- package/types/generated/DateTimeInput.macos.d.ts +5 -0
- package/types/generated/Dialog.macos.d.ts +3 -0
- package/types/generated/Drawer.macos.d.ts +7 -0
- package/types/generated/DropdownMenu.macos.d.ts +5 -0
- package/types/generated/Empty.macos.d.ts +4 -0
- package/types/generated/FieldGroup.macos.d.ts +4 -0
- package/types/generated/FormField.macos.d.ts +4 -0
- package/types/generated/FormLayout.macos.d.ts +6 -0
- package/types/generated/HStack.macos.d.ts +4 -0
- package/types/generated/Heading.macos.d.ts +3 -0
- package/types/generated/Icon.macos.d.ts +3 -0
- package/types/generated/IconButton.macos.d.ts +3 -0
- package/types/generated/Image.d.ts +0 -5
- package/types/generated/Image.macos.d.ts +2 -0
- package/types/generated/Image.web.d.ts +12 -1
- package/types/generated/InputGroup.macos.d.ts +5 -0
- package/types/generated/InputNumber.macos.d.ts +5 -0
- package/types/generated/InputRating.macos.d.ts +4 -0
- package/types/generated/Item.macos.d.ts +3 -0
- package/types/generated/Kbd.macos.d.ts +4 -0
- package/types/generated/KeyboardAvoiding.macos.d.ts +3 -0
- package/types/generated/Lightbox.macos.d.ts +2 -0
- package/types/generated/Link.macos.d.ts +3 -0
- package/types/generated/List.macos.d.ts +4 -0
- package/types/generated/ListItem.macos.d.ts +5 -0
- package/types/generated/MenuParts.macos.d.ts +9 -0
- package/types/generated/MenuSurface.macos.d.ts +4 -0
- package/types/generated/MetadataList.macos.d.ts +4 -0
- package/types/generated/MetadataListItem.macos.d.ts +3 -0
- package/types/generated/Meter.macos.d.ts +3 -0
- package/types/generated/MobileNav.macos.d.ts +15 -0
- package/types/generated/MoreMenu.macos.d.ts +2 -0
- package/types/generated/NavIcon.macos.d.ts +4 -0
- package/types/generated/NavLink.macos.d.ts +3 -0
- package/types/generated/NavMenu.macos.d.ts +11 -0
- package/types/generated/NavigationMenu.macos.d.ts +4 -0
- package/types/generated/Outline.macos.d.ts +5 -0
- package/types/generated/OverflowList.macos.d.ts +6 -0
- package/types/generated/Overlay.macos.d.ts +3 -0
- package/types/generated/Pagination.macos.d.ts +4 -0
- package/types/generated/PinInput.macos.d.ts +4 -0
- package/types/generated/Popover.macos.d.ts +3 -0
- package/types/generated/PowerSearch.macos.d.ts +7 -0
- package/types/generated/PowerSearchFilterEditor.macos.d.ts +12 -0
- package/types/generated/PowerSearchToken.macos.d.ts +7 -0
- package/types/generated/Pressable.macos.d.ts +2 -0
- package/types/generated/ProgressBar.macos.d.ts +6 -0
- package/types/generated/ProgressGroup.macos.d.ts +3 -0
- package/types/generated/RadioGroup.macos.d.ts +5 -0
- package/types/generated/RadioIndicator.macos.d.ts +3 -0
- package/types/generated/ResizeHandle.macos.d.ts +7 -0
- package/types/generated/RouteBoundary.macos.d.ts +8 -0
- package/types/generated/RouteHost.macos.d.ts +13 -0
- package/types/generated/Row.macos.d.ts +2 -0
- package/types/generated/SafeArea.macos.d.ts +3 -0
- package/types/generated/Screen.macos.d.ts +3 -0
- package/types/generated/ScrollableArea.macos.d.ts +7 -0
- package/types/generated/Section.macos.d.ts +5 -0
- package/types/generated/Select.macos.d.ts +3 -0
- package/types/generated/SelectableCard.macos.d.ts +3 -0
- package/types/generated/Separator.macos.d.ts +5 -0
- package/types/generated/SideNav.macos.d.ts +19 -0
- package/types/generated/Skeleton.macos.d.ts +5 -0
- package/types/generated/StatusDot.macos.d.ts +5 -0
- package/types/generated/Stepper.macos.d.ts +4 -0
- package/types/generated/TabList.macos.d.ts +18 -0
- package/types/generated/Table.macos.d.ts +4 -0
- package/types/generated/Tabs.macos.d.ts +3 -0
- package/types/generated/Text.macos.d.ts +5 -0
- package/types/generated/Thumbnail.macos.d.ts +7 -0
- package/types/generated/TimeInput.macos.d.ts +6 -0
- package/types/generated/TimeWheelPanel.macos.d.ts +16 -0
- package/types/generated/Timeline.macos.d.ts +3 -0
- package/types/generated/Timer.macos.d.ts +3 -0
- package/types/generated/Timestamp.macos.d.ts +5 -0
- package/types/generated/Toast.macos.d.ts +3 -0
- package/types/generated/ToggleButton.macos.d.ts +3 -0
- package/types/generated/Token.macos.d.ts +5 -0
- package/types/generated/Tokenizer.macos.d.ts +6 -0
- package/types/generated/Toolbar.macos.d.ts +6 -0
- package/types/generated/TopNav.macos.d.ts +25 -0
- package/types/generated/Tree.macos.d.ts +4 -0
- package/types/generated/Typeahead.macos.d.ts +8 -0
- package/types/generated/TypeaheadItem.macos.d.ts +5 -0
- package/types/generated/User.macos.d.ts +5 -0
- package/types/generated/VStack.macos.d.ts +4 -0
- package/types/generated/View.macos.d.ts +3 -0
- package/types/generated/VirtualList.macos.d.ts +6 -0
- package/types/generated/VisuallyHidden.macos.d.ts +5 -0
- package/types/generated/WebView.macos.d.ts +2 -0
- package/types/generated/aliases.macos.d.ts +3 -0
- package/types/generated/anim.macos.d.ts +3 -0
- package/types/generated/animation-host.macos.d.ts +3 -0
- package/types/generated/bottom-sheet-panel.macos.d.ts +7 -0
- package/types/generated/bottom-sheet-service.macos.d.ts +5 -0
- package/types/generated/clipboard-write.macos.d.ts +3 -0
- package/types/generated/colorScheme.macos.d.ts +4 -0
- package/types/generated/command-palette-keys.macos.d.ts +2 -0
- package/types/generated/command-palette-scroll.macos.d.ts +2 -0
- package/types/generated/controls.macos.d.ts +8 -0
- package/types/generated/date-composition.macos.d.ts +5 -0
- package/types/generated/escape-props.macos.d.ts +5 -0
- package/types/generated/hash-image.d.ts +13 -0
- package/types/generated/hosted-unsupported.macos.d.ts +1 -0
- package/types/generated/image-content-binding.d.ts +64 -0
- package/types/generated/image-content.d.ts +56 -0
- package/types/generated/image-sizing.d.ts +37 -0
- package/types/generated/index.macos.d.ts +196 -0
- package/types/generated/index.shared.d.ts +1 -1
- package/types/generated/index.shared.web.d.ts +1 -1
- package/types/generated/layout-shells.macos.d.ts +3 -0
- package/types/generated/layout.macos.d.ts +9 -0
- package/types/generated/macos-extras.macos.d.ts +4 -0
- package/types/generated/measure-bounds.macos.d.ts +3 -0
- package/types/generated/nav-href.macos.d.ts +4 -0
- package/types/generated/outline-dom.macos.d.ts +2 -0
- package/types/generated/pan.macos.d.ts +3 -0
- package/types/generated/platform.macos.d.ts +1 -0
- package/types/generated/presentation.macos.d.ts +15 -0
- package/types/generated/props.d.ts +76 -1
- package/types/generated/query-storage.macos.d.ts +8 -0
- package/types/generated/resize-basis.macos.d.ts +4 -0
- package/types/generated/resize-persist.macos.d.ts +8 -0
- package/types/generated/route.macos.d.ts +28 -0
- package/types/generated/safeAreaInsets.macos.d.ts +10 -0
- package/types/generated/stacks.macos.d.ts +3 -0
- package/types/generated/styled.macos.d.ts +9 -0
- package/types/generated/surfaces.macos.d.ts +4 -0
- package/types/generated/svg-image.macos.d.ts +3 -0
- package/types/generated/text-controls.macos.d.ts +4 -0
- package/types/generated/theme/theme-scheme.macos.d.ts +8 -0
- package/types/generated/toast-service.macos.d.ts +3 -0
- package/types/generated/toast-viewport.macos.d.ts +5 -0
- package/types/generated/translate.macos.d.ts +1 -0
- package/types/generated/unsupported.macos.d.ts +1 -0
- package/types/generated/use-imperative-alert-dialog.macos.d.ts +2 -0
- package/types/generated/use-imperative-dialog.macos.d.ts +2 -0
- package/types/generated/use-lightbox.macos.d.ts +2 -0
- package/types/generated/use-scrollable-area.macos.d.ts +6 -0
- package/types/generated/useMeasure.macos.d.ts +3 -0
- package/types/generated/useResizable.macos.d.ts +8 -0
- package/types/generated/useStore.macos.d.ts +2 -0
- package/types/generated/windows.macos.d.ts +2 -0
- package/src/CameraView.macos.tsrx +0 -6
- package/types/index.macos.d.ts +0 -448
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
//#region src/image-content.ts
|
|
2
|
+
/** Shared `contentFit`/`contentPosition` + multi-source math for `Image` —
|
|
3
|
+
* pure functions used by every leaf (docs/notes/expo-image-study.md L7/L8).
|
|
4
|
+
* Semantics mirror CSS `object-fit`/`object-position`, which is also
|
|
5
|
+
* expo-image's contract: the leaves compute the drawn-content rect
|
|
6
|
+
* themselves instead of delegating to platform scaleType/contentMode,
|
|
7
|
+
* so all platforms get the same answer.
|
|
8
|
+
*
|
|
9
|
+
* The default fit is 'cover' everywhere (the expo/RN default). Today's
|
|
10
|
+
* platform defaults disagree — web <img> is 'fill' and NS stretch is
|
|
11
|
+
* 'aspectFit' — so an un-propped Image was silently divergent. */
|
|
12
|
+
var DEFAULT_CONTENT_FIT = "cover";
|
|
13
|
+
function resolveContentFit(fit) {
|
|
14
|
+
return fit ?? "cover";
|
|
15
|
+
}
|
|
16
|
+
/** expo-image's keyword shorthands; the object form positions relative to
|
|
17
|
+
* any corner pair ({top,left}, {top,right}, {bottom,left}, {bottom,right}). */
|
|
18
|
+
var POSITION_KEYWORDS = {
|
|
19
|
+
center: {
|
|
20
|
+
top: "50%",
|
|
21
|
+
left: "50%"
|
|
22
|
+
},
|
|
23
|
+
top: {
|
|
24
|
+
top: 0,
|
|
25
|
+
left: "50%"
|
|
26
|
+
},
|
|
27
|
+
right: {
|
|
28
|
+
top: "50%",
|
|
29
|
+
right: 0
|
|
30
|
+
},
|
|
31
|
+
bottom: {
|
|
32
|
+
bottom: 0,
|
|
33
|
+
left: "50%"
|
|
34
|
+
},
|
|
35
|
+
left: {
|
|
36
|
+
top: "50%",
|
|
37
|
+
left: 0
|
|
38
|
+
},
|
|
39
|
+
"top center": {
|
|
40
|
+
top: 0,
|
|
41
|
+
left: "50%"
|
|
42
|
+
},
|
|
43
|
+
"top right": {
|
|
44
|
+
top: 0,
|
|
45
|
+
right: 0
|
|
46
|
+
},
|
|
47
|
+
"top left": {
|
|
48
|
+
top: 0,
|
|
49
|
+
left: 0
|
|
50
|
+
},
|
|
51
|
+
"right center": {
|
|
52
|
+
top: "50%",
|
|
53
|
+
right: 0
|
|
54
|
+
},
|
|
55
|
+
"right top": {
|
|
56
|
+
top: 0,
|
|
57
|
+
right: 0
|
|
58
|
+
},
|
|
59
|
+
"right bottom": {
|
|
60
|
+
bottom: 0,
|
|
61
|
+
right: 0
|
|
62
|
+
},
|
|
63
|
+
"bottom center": {
|
|
64
|
+
bottom: 0,
|
|
65
|
+
left: "50%"
|
|
66
|
+
},
|
|
67
|
+
"bottom right": {
|
|
68
|
+
bottom: 0,
|
|
69
|
+
right: 0
|
|
70
|
+
},
|
|
71
|
+
"bottom left": {
|
|
72
|
+
bottom: 0,
|
|
73
|
+
left: 0
|
|
74
|
+
},
|
|
75
|
+
"left center": {
|
|
76
|
+
top: "50%",
|
|
77
|
+
left: 0
|
|
78
|
+
},
|
|
79
|
+
"left top": {
|
|
80
|
+
top: 0,
|
|
81
|
+
left: 0
|
|
82
|
+
},
|
|
83
|
+
"left bottom": {
|
|
84
|
+
bottom: 0,
|
|
85
|
+
left: 0
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
function resolveContentPosition(position) {
|
|
89
|
+
if (typeof position === "string") return POSITION_KEYWORDS[position] ?? POSITION_KEYWORDS.center;
|
|
90
|
+
return position ?? POSITION_KEYWORDS.center;
|
|
91
|
+
}
|
|
92
|
+
function parseAxisValue(value) {
|
|
93
|
+
if (value == null) return null;
|
|
94
|
+
if (typeof value === "number") return { dips: value };
|
|
95
|
+
const text = String(value).trim();
|
|
96
|
+
if (text === "center") return { percent: 50 };
|
|
97
|
+
if (text.endsWith("%")) {
|
|
98
|
+
const parsed = Number.parseFloat(text);
|
|
99
|
+
return Number.isNaN(parsed) ? null : { percent: parsed };
|
|
100
|
+
}
|
|
101
|
+
const parsed = Number.parseFloat(text);
|
|
102
|
+
return Number.isNaN(parsed) ? null : { dips: parsed };
|
|
103
|
+
}
|
|
104
|
+
/** Offset of the drawn content's start edge inside the view along one axis.
|
|
105
|
+
* expo-image `calcTranslation`: percentages run over the free space
|
|
106
|
+
* (view − content), absolute dips measure from the named edge, and the
|
|
107
|
+
* reverse edge (right/bottom) counts back from the far side. */
|
|
108
|
+
function contentAxisOffset(viewSize, contentSize, start, end) {
|
|
109
|
+
const from = parseAxisValue(start);
|
|
110
|
+
if (from) return "percent" in from ? from.percent / 100 * (viewSize - contentSize) : from.dips;
|
|
111
|
+
const to = parseAxisValue(end);
|
|
112
|
+
if (to) return "percent" in to ? (1 - to.percent / 100) * (viewSize - contentSize) : viewSize - contentSize - to.dips;
|
|
113
|
+
return (viewSize - contentSize) / 2;
|
|
114
|
+
}
|
|
115
|
+
function centeredAxis(start, end) {
|
|
116
|
+
const from = parseAxisValue(start);
|
|
117
|
+
if (from) return "percent" in from && from.percent === 50;
|
|
118
|
+
const to = parseAxisValue(end);
|
|
119
|
+
if (to) return "percent" in to && to.percent === 50;
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
function isCenteredContentPosition(position) {
|
|
123
|
+
return centeredAxis(position.left, position.right) && centeredAxis(position.top, position.bottom);
|
|
124
|
+
}
|
|
125
|
+
/** The rect the bitmap occupies inside the view box, in the same units the
|
|
126
|
+
* caller measures with (native: dips; web: CSS px via object-fit anyway —
|
|
127
|
+
* only the native leaf calls this). Every length unit is dips except the
|
|
128
|
+
* intrinsic size, which the leaf must supply in dips too. */
|
|
129
|
+
function contentRect(fit, position, viewWidth, viewHeight, imageWidth, imageHeight) {
|
|
130
|
+
let width;
|
|
131
|
+
let height;
|
|
132
|
+
switch (fit) {
|
|
133
|
+
case "fill":
|
|
134
|
+
width = viewWidth;
|
|
135
|
+
height = viewHeight;
|
|
136
|
+
break;
|
|
137
|
+
case "cover": {
|
|
138
|
+
const scale = Math.max(viewWidth / imageWidth, viewHeight / imageHeight);
|
|
139
|
+
width = imageWidth * scale;
|
|
140
|
+
height = imageHeight * scale;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "scale-down": {
|
|
144
|
+
const scale = Math.min(1, Math.min(viewWidth / imageWidth, viewHeight / imageHeight));
|
|
145
|
+
width = imageWidth * scale;
|
|
146
|
+
height = imageHeight * scale;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "none":
|
|
150
|
+
width = imageWidth;
|
|
151
|
+
height = imageHeight;
|
|
152
|
+
break;
|
|
153
|
+
default: {
|
|
154
|
+
const scale = Math.min(viewWidth / imageWidth, viewHeight / imageHeight);
|
|
155
|
+
width = imageWidth * scale;
|
|
156
|
+
height = imageHeight * scale;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
x: contentAxisOffset(viewWidth, width, position.left, position.right),
|
|
161
|
+
y: contentAxisOffset(viewHeight, height, position.top, position.bottom),
|
|
162
|
+
width,
|
|
163
|
+
height
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** True when no platform stretch/scaleType can express the request and the
|
|
167
|
+
* leaf must measure + lay out the image itself: 'none' (iOS maps NS
|
|
168
|
+
* stretch='none' to TopLeft, not CSS centered), 'scale-down' (needs the
|
|
169
|
+
* intrinsic-vs-view compare), or any non-center position. 'fill' is
|
|
170
|
+
* exempt — its rect is always the whole box, so position can't matter. */
|
|
171
|
+
function needsContentRect(fit, position) {
|
|
172
|
+
if (fit === "none" || fit === "scale-down") return true;
|
|
173
|
+
return fit !== "fill" && !isCenteredContentPosition(position);
|
|
174
|
+
}
|
|
175
|
+
/** NS `stretch` for the fast path — only reached for fits the platform
|
|
176
|
+
* vocabulary expresses identically (contain/cover/fill centered). */
|
|
177
|
+
function contentFitToStretch(fit) {
|
|
178
|
+
switch (fit) {
|
|
179
|
+
case "contain": return "aspectFit";
|
|
180
|
+
case "fill": return "fill";
|
|
181
|
+
default: return "aspectFill";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** L8 — expo `getBestSource`: the source whose declared pixel count
|
|
185
|
+
* (`width·height·scale²`, dips × density) is closest to the view's pixel
|
|
186
|
+
* count. Needs a real measured size; one source or no size picks nothing
|
|
187
|
+
* to decide. Unsized sources score worst and are skipped when any sized
|
|
188
|
+
* candidate exists. */
|
|
189
|
+
function bestImageSource(sources, viewWidth, viewHeight, screenScale) {
|
|
190
|
+
if (sources.length === 0 || viewWidth <= 0 || viewHeight <= 0) return null;
|
|
191
|
+
if (sources.length === 1) return sources[0];
|
|
192
|
+
const target = viewWidth * viewHeight * screenScale * screenScale;
|
|
193
|
+
let best = null;
|
|
194
|
+
let bestFit = Infinity;
|
|
195
|
+
for (const source of sources) {
|
|
196
|
+
const pixelCount = (source.width ?? 0) * (source.height ?? 0) * (source.scale ?? 1) ** 2;
|
|
197
|
+
const fit = Math.abs(1 - pixelCount / target);
|
|
198
|
+
if (fit < bestFit) {
|
|
199
|
+
best = source;
|
|
200
|
+
bestFit = fit;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return best;
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
export { DEFAULT_CONTENT_FIT, bestImageSource, contentAxisOffset, contentFitToStretch, contentRect, isCenteredContentPosition, needsContentRect, resolveContentFit, resolveContentPosition };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//#region src/image-sizing.ts
|
|
2
|
+
/** View-sized decode for the native <image> leaf.
|
|
3
|
+
*
|
|
4
|
+
* NS Android reads decodeWidth/decodeHeight only when `src` is written —
|
|
5
|
+
* `srcProperty.setNative` → `_createImageSourceFromSrc` →
|
|
6
|
+
* `imageView.setUri(value, decodeWidth, decodeHeight, …)` — and the
|
|
7
|
+
* properties default to 0 → `Fetcher.calculateInSampleSize` maps
|
|
8
|
+
* reqWidth ≤ 0 to the source dims → inSampleSize 1 → a full-resolution
|
|
9
|
+
* decode (~48 MB ARGB_8888 per 12 MP photo; docs/notes/expo-image-study.md
|
|
10
|
+
* L1). So the leaf holds `src` until the view's first real layout, writes
|
|
11
|
+
* the decode dims in device pixels, then assigns src. A later resize
|
|
12
|
+
* re-issues at the new size — the same reload expo-image runs from
|
|
13
|
+
* onSizeChanged — under the same gate: 'fill'/'none' stretches keep the
|
|
14
|
+
* existing decode because a resize can't change the ideal sample there
|
|
15
|
+
* ('none' additionally never downsamples — its contract is source
|
|
16
|
+
* pixels). iOS ignores the decode dims entirely; the deferred assignment
|
|
17
|
+
* is behavior-identical there, so one path serves both platforms.
|
|
18
|
+
*
|
|
19
|
+
* The ref callback re-fires on every render (identity change), so
|
|
20
|
+
* `update` must be idempotent: same view + same src + same size is a
|
|
21
|
+
* no-op. Null ref calls on detach are ignored by the leaf — re-attaching
|
|
22
|
+
* the same view must not lose `applied`, or each render would re-issue
|
|
23
|
+
* setUri. The dead-view listener left behind on real unmount is a
|
|
24
|
+
* self-contained cycle the GC collects with the component record.
|
|
25
|
+
*
|
|
26
|
+
* `afterIssue` runs right after every `src` write — including a same-src
|
|
27
|
+
* resize re-issue mid-load, which fires no `isLoadingChange` (isLoading is
|
|
28
|
+
* already true). The Image placeholder hooks this to re-apply its bitmap
|
|
29
|
+
* after the write has nulled `imageSource` (L5, decision #103). */
|
|
30
|
+
function createSizedImageBinding(options = {}) {
|
|
31
|
+
let view = null;
|
|
32
|
+
let listening = false;
|
|
33
|
+
let pendingSrc;
|
|
34
|
+
let pendingStretch;
|
|
35
|
+
let appliedSrc;
|
|
36
|
+
let applied = false;
|
|
37
|
+
let width = 0;
|
|
38
|
+
let height = 0;
|
|
39
|
+
const issue = (next) => {
|
|
40
|
+
const downscale = (pendingStretch ?? view.stretch) !== "none";
|
|
41
|
+
view.decodeWidth = {
|
|
42
|
+
value: downscale ? width : 0,
|
|
43
|
+
unit: "px"
|
|
44
|
+
};
|
|
45
|
+
view.decodeHeight = {
|
|
46
|
+
value: downscale ? height : 0,
|
|
47
|
+
unit: "px"
|
|
48
|
+
};
|
|
49
|
+
view.src = next;
|
|
50
|
+
appliedSrc = next;
|
|
51
|
+
applied = true;
|
|
52
|
+
options.afterIssue?.(view);
|
|
53
|
+
};
|
|
54
|
+
const evaluate = () => {
|
|
55
|
+
if (!view) return;
|
|
56
|
+
const measuredWidth = view.getMeasuredWidth?.() ?? 0;
|
|
57
|
+
const measuredHeight = view.getMeasuredHeight?.() ?? 0;
|
|
58
|
+
if (measuredWidth <= 0 || measuredHeight <= 0) return;
|
|
59
|
+
if (!applied || pendingSrc !== appliedSrc) {
|
|
60
|
+
width = measuredWidth;
|
|
61
|
+
height = measuredHeight;
|
|
62
|
+
issue(pendingSrc);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (measuredWidth === width && measuredHeight === height) return;
|
|
66
|
+
const stretch = pendingStretch ?? view.stretch;
|
|
67
|
+
if (stretch === "fill" || stretch === "none") return;
|
|
68
|
+
width = measuredWidth;
|
|
69
|
+
height = measuredHeight;
|
|
70
|
+
issue(appliedSrc);
|
|
71
|
+
};
|
|
72
|
+
const onLayout = () => evaluate();
|
|
73
|
+
return {
|
|
74
|
+
/** (Re)bind source → view; issues once the view reports a real layout.
|
|
75
|
+
* `stretchForDecode` overrides view.stretch for the decode/reload
|
|
76
|
+
* gates — see `issue`. */
|
|
77
|
+
update(next, source, stretchForDecode) {
|
|
78
|
+
if (next !== view) {
|
|
79
|
+
view?.off?.("layoutChanged", onLayout);
|
|
80
|
+
view = next;
|
|
81
|
+
listening = false;
|
|
82
|
+
applied = false;
|
|
83
|
+
appliedSrc = void 0;
|
|
84
|
+
}
|
|
85
|
+
pendingSrc = source;
|
|
86
|
+
pendingStretch = stretchForDecode;
|
|
87
|
+
if (view && !listening && typeof view.on === "function") {
|
|
88
|
+
view.on("layoutChanged", onLayout);
|
|
89
|
+
listening = true;
|
|
90
|
+
}
|
|
91
|
+
evaluate();
|
|
92
|
+
},
|
|
93
|
+
dispose() {
|
|
94
|
+
view?.off?.("layoutChanged", onLayout);
|
|
95
|
+
view = null;
|
|
96
|
+
listening = false;
|
|
97
|
+
applied = false;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { createSizedImageBinding };
|
|
@@ -30,7 +30,7 @@ function getSVG(src) {
|
|
|
30
30
|
var SVGExternalFileResolver = class extends com.caverock.androidsvg.SVGExternalFileResolver {
|
|
31
31
|
resolveFont(fontFamily, fontWeight, fontStyle) {
|
|
32
32
|
if (fontFamily) fontFamily = fontFamily.replace(/\\\//, "/");
|
|
33
|
-
return
|
|
33
|
+
return Font.default.withFontFamily(fontFamily).withFontStyle(fontStyle.toLowerCase()).withFontWeight(fontWeight + "").getAndroidTypeface();
|
|
34
34
|
}
|
|
35
35
|
resolveImage(filename) {
|
|
36
36
|
let bitmap = null;
|
|
@@ -1,28 +1,76 @@
|
|
|
1
1
|
import { applyEscapeProps } from "./escape-props.web.js";
|
|
2
|
-
import { layoutChildProps } from "./layout-child.web.js";
|
|
3
|
-
import {
|
|
2
|
+
import { layoutChildProps, normStyle } from "./layout-child.web.js";
|
|
3
|
+
import { imageSrcSet, objectPositionCSSValue, resolveContentFit, resolveContentPosition } from "./image-content.js";
|
|
4
|
+
import { hashToPngBase64 } from "./hash-image.js";
|
|
5
|
+
import { __s, bag11, clone, hookSlots, snapshotSpread, template, useLayoutEffect, useRef } from "octane";
|
|
4
6
|
import { bindSignalHostPropSources, enableSignalBindings } from "octane/internal/client";
|
|
5
7
|
//#region src/Image.web.tsrx
|
|
6
8
|
enableSignalBindings(1, true);
|
|
7
9
|
var _t$0 = /* @__PURE__ */ template("<img/>");
|
|
10
|
+
var _hs$ = /* @__PURE__ */ hookSlots(2);
|
|
8
11
|
var _rm$0 = [
|
|
9
12
|
"s",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
13
|
+
"k",
|
|
14
|
+
"j"
|
|
12
15
|
];
|
|
16
|
+
function placeholderUrl(placeholder) {
|
|
17
|
+
const png = hashToPngBase64(placeholder);
|
|
18
|
+
return png ? `data:image/png;base64,${png}` : placeholder;
|
|
19
|
+
}
|
|
13
20
|
var Image = /* @__PURE__ */ __s(function Image(props, __s, __extra) {
|
|
21
|
+
const imgRef = useRef(null, _hs$ + 0);
|
|
22
|
+
const sources = Array.isArray(props.src) ? props.src : null;
|
|
23
|
+
const selected = sources ? imageSrcSet(sources) : {
|
|
24
|
+
src: props.src,
|
|
25
|
+
srcSet: void 0
|
|
26
|
+
};
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
const img = imgRef.current;
|
|
29
|
+
if (!img || !props.placeholder || img.complete) return;
|
|
30
|
+
const placeholder = placeholderUrl(props.placeholder);
|
|
31
|
+
const { src: final, srcSet: finalSrcSet } = selected;
|
|
32
|
+
const warm = document.createElement("img");
|
|
33
|
+
warm.src = final;
|
|
34
|
+
if (finalSrcSet) warm.srcset = finalSrcSet;
|
|
35
|
+
const swap = () => {
|
|
36
|
+
img.src = final;
|
|
37
|
+
if (finalSrcSet) img.srcset = finalSrcSet;
|
|
38
|
+
};
|
|
39
|
+
img.addEventListener("load", swap, { once: true });
|
|
40
|
+
img.addEventListener("error", swap, { once: true });
|
|
41
|
+
if (finalSrcSet) img.srcset = "";
|
|
42
|
+
img.src = placeholder;
|
|
43
|
+
return () => {
|
|
44
|
+
img.removeEventListener("load", swap);
|
|
45
|
+
img.removeEventListener("error", swap);
|
|
46
|
+
};
|
|
47
|
+
}, [props.src, props.placeholder], _hs$ + 1);
|
|
48
|
+
const style = {
|
|
49
|
+
objectFit: "cover",
|
|
50
|
+
objectPosition: "50% 50%",
|
|
51
|
+
...normStyle(props.style),
|
|
52
|
+
...props.contentFit !== void 0 ? { objectFit: resolveContentFit(props.contentFit) } : null,
|
|
53
|
+
...props.contentPosition !== void 0 ? { objectPosition: objectPositionCSSValue(resolveContentPosition(props.contentPosition)) } : null
|
|
54
|
+
};
|
|
55
|
+
if (__s.block.pending && !__s.block.crossRenderUpdate) return;
|
|
14
56
|
let _b = __s.slots[0];
|
|
15
57
|
if (_b === void 0) {
|
|
16
|
-
let _m0, _m1, _m2, _m3, _m4, _m5, _m6, _m7;
|
|
58
|
+
let _m0, _m1, _m2, _m3, _m4, _m5, _m6, _m7, _m8, _m9, _m10;
|
|
17
59
|
const _root = clone(_t$0);
|
|
18
|
-
_m0 = (e) =>
|
|
60
|
+
_m0 = (e) => {
|
|
61
|
+
imgRef.current = e;
|
|
62
|
+
applyEscapeProps(e, props);
|
|
63
|
+
};
|
|
19
64
|
_m1 = props.id;
|
|
20
65
|
_m2 = props.className;
|
|
21
66
|
_m3 = snapshotSpread(layoutChildProps(props));
|
|
22
|
-
_m4 =
|
|
23
|
-
_m5 =
|
|
24
|
-
_m6 =
|
|
25
|
-
_m7 =
|
|
67
|
+
_m4 = selected.src;
|
|
68
|
+
_m5 = selected.srcSet;
|
|
69
|
+
_m6 = props.decoding ?? "async";
|
|
70
|
+
_m7 = props.alt;
|
|
71
|
+
_m8 = style;
|
|
72
|
+
_m9 = _root;
|
|
73
|
+
_m10 = bindSignalHostPropSources(__s, void 0, _root, [
|
|
26
74
|
[
|
|
27
75
|
false,
|
|
28
76
|
"ref",
|
|
@@ -46,15 +94,33 @@ var Image = /* @__PURE__ */ __s(function Image(props, __s, __extra) {
|
|
|
46
94
|
],
|
|
47
95
|
[
|
|
48
96
|
false,
|
|
49
|
-
"
|
|
97
|
+
"srcSet",
|
|
50
98
|
_m5
|
|
99
|
+
],
|
|
100
|
+
[
|
|
101
|
+
false,
|
|
102
|
+
"decoding",
|
|
103
|
+
_m6
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
false,
|
|
107
|
+
"alt",
|
|
108
|
+
_m7
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
false,
|
|
112
|
+
"style",
|
|
113
|
+
_m8
|
|
51
114
|
]
|
|
52
|
-
], "b:
|
|
53
|
-
_b =
|
|
115
|
+
], "b:2396612c", false);
|
|
116
|
+
_b = bag11(__s, _root, _m0, _m1, _m2, _m3, _m4, _m5, _m6, _m7, _m8, _m9, _m10);
|
|
54
117
|
__s.refFields = _rm$0;
|
|
55
118
|
} else {
|
|
56
119
|
{
|
|
57
|
-
const _v = (e) =>
|
|
120
|
+
const _v = (e) => {
|
|
121
|
+
imgRef.current = e;
|
|
122
|
+
applyEscapeProps(e, props);
|
|
123
|
+
};
|
|
58
124
|
if (_b.a !== _v) _b.a = _v;
|
|
59
125
|
}
|
|
60
126
|
{
|
|
@@ -67,14 +133,26 @@ var Image = /* @__PURE__ */ __s(function Image(props, __s, __extra) {
|
|
|
67
133
|
}
|
|
68
134
|
_b.d = snapshotSpread(layoutChildProps(props));
|
|
69
135
|
{
|
|
70
|
-
const _v =
|
|
136
|
+
const _v = selected.src;
|
|
71
137
|
if (_b.e !== _v) _b.e = _v;
|
|
72
138
|
}
|
|
73
139
|
{
|
|
74
|
-
const _v =
|
|
140
|
+
const _v = selected.srcSet;
|
|
75
141
|
if (_b.f !== _v) _b.f = _v;
|
|
76
142
|
}
|
|
77
|
-
|
|
143
|
+
{
|
|
144
|
+
const _v = props.decoding ?? "async";
|
|
145
|
+
if (_b.g !== _v) _b.g = _v;
|
|
146
|
+
}
|
|
147
|
+
{
|
|
148
|
+
const _v = props.alt;
|
|
149
|
+
if (_b.h !== _v) _b.h = _v;
|
|
150
|
+
}
|
|
151
|
+
{
|
|
152
|
+
const _v = style;
|
|
153
|
+
if (_b.i !== _v) _b.i = _v;
|
|
154
|
+
}
|
|
155
|
+
_b.k = bindSignalHostPropSources(__s, _b.k, _b.j, [
|
|
78
156
|
[
|
|
79
157
|
false,
|
|
80
158
|
"ref",
|
|
@@ -98,10 +176,25 @@ var Image = /* @__PURE__ */ __s(function Image(props, __s, __extra) {
|
|
|
98
176
|
],
|
|
99
177
|
[
|
|
100
178
|
false,
|
|
101
|
-
"
|
|
179
|
+
"srcSet",
|
|
102
180
|
_b.f
|
|
181
|
+
],
|
|
182
|
+
[
|
|
183
|
+
false,
|
|
184
|
+
"decoding",
|
|
185
|
+
_b.g
|
|
186
|
+
],
|
|
187
|
+
[
|
|
188
|
+
false,
|
|
189
|
+
"alt",
|
|
190
|
+
_b.h
|
|
191
|
+
],
|
|
192
|
+
[
|
|
193
|
+
false,
|
|
194
|
+
"style",
|
|
195
|
+
_b.i
|
|
103
196
|
]
|
|
104
|
-
], "b:
|
|
197
|
+
], "b:2396612c", false);
|
|
105
198
|
}
|
|
106
199
|
});
|
|
107
200
|
//#endregion
|