@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,285 @@
|
|
|
1
|
+
//#region src/hash-image.ts
|
|
2
|
+
/** `blurhash:<hash>` / `thumbhash:<base64>` → PNG bytes as base64, or null. */
|
|
3
|
+
function hashToPngBase64(src) {
|
|
4
|
+
if (src.startsWith("blurhash:")) {
|
|
5
|
+
const rgba = decodeBlurHash(src.slice(9), 32, 32);
|
|
6
|
+
return rgba ? base64Encode(rgbaToPng(32, 32, rgba)) : null;
|
|
7
|
+
}
|
|
8
|
+
if (src.startsWith("thumbhash:")) {
|
|
9
|
+
const hash = base64Decode(src.slice(10));
|
|
10
|
+
const decoded = hash ? thumbHashToRGBA(hash) : null;
|
|
11
|
+
return decoded ? base64Encode(rgbaToPng(decoded.w, decoded.h, decoded.rgba)) : null;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
var BASE83 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
|
|
16
|
+
function decode83(str, start, end) {
|
|
17
|
+
let value = 0;
|
|
18
|
+
for (let i = start; i < end; i++) {
|
|
19
|
+
const digit = BASE83.indexOf(str[i]);
|
|
20
|
+
if (digit === -1) return -1;
|
|
21
|
+
value = value * 83 + digit;
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function srgbToLinear(value) {
|
|
26
|
+
const v = value / 255;
|
|
27
|
+
return v <= .04045 ? v / 12.92 : Math.pow((v + .055) / 1.055, 2.4);
|
|
28
|
+
}
|
|
29
|
+
function linearToSrgb(value) {
|
|
30
|
+
const v = Math.max(0, Math.min(1, value));
|
|
31
|
+
return v <= .0031308 ? Math.round(v * 12.92 * 255 + .5) : Math.round((1.055 * Math.pow(v, 1 / 2.4) - .055) * 255 + .5);
|
|
32
|
+
}
|
|
33
|
+
function signPow(value, exp) {
|
|
34
|
+
return Math.sign(value) * Math.pow(Math.abs(value), exp);
|
|
35
|
+
}
|
|
36
|
+
function decodeBlurHash(hash, width, height) {
|
|
37
|
+
if (hash.length < 6) return null;
|
|
38
|
+
const sizeFlag = decode83(hash, 0, 1);
|
|
39
|
+
const quantisedMax = decode83(hash, 1, 2);
|
|
40
|
+
const dcValue = decode83(hash, 2, 6);
|
|
41
|
+
if (sizeFlag < 0 || quantisedMax < 0 || dcValue < 0) return null;
|
|
42
|
+
const numY = Math.floor(sizeFlag / 9) + 1;
|
|
43
|
+
const numX = sizeFlag % 9 + 1;
|
|
44
|
+
if (hash.length !== 4 + 2 * numX * numY) return null;
|
|
45
|
+
const maximumValue = (quantisedMax + 1) / 166;
|
|
46
|
+
const colors = new Array(numX * numY);
|
|
47
|
+
colors[0] = [
|
|
48
|
+
srgbToLinear(dcValue >> 16),
|
|
49
|
+
srgbToLinear(dcValue >> 8 & 255),
|
|
50
|
+
srgbToLinear(dcValue & 255)
|
|
51
|
+
];
|
|
52
|
+
for (let i = 1; i < numX * numY; i++) {
|
|
53
|
+
const value = decode83(hash, 4 + i * 2, 6 + i * 2);
|
|
54
|
+
if (value < 0) return null;
|
|
55
|
+
const quantR = Math.floor(value / 361);
|
|
56
|
+
const quantG = Math.floor(value / 19) % 19;
|
|
57
|
+
const quantB = value % 19;
|
|
58
|
+
colors[i] = [
|
|
59
|
+
signPow((quantR - 9) / 9, 2) * maximumValue,
|
|
60
|
+
signPow((quantG - 9) / 9, 2) * maximumValue,
|
|
61
|
+
signPow((quantB - 9) / 9, 2) * maximumValue
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
const pixels = new Uint8ClampedArray(width * height * 4);
|
|
65
|
+
for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
|
|
66
|
+
let r = 0;
|
|
67
|
+
let g = 0;
|
|
68
|
+
let b = 0;
|
|
69
|
+
for (let j = 0; j < numY; j++) for (let i = 0; i < numX; i++) {
|
|
70
|
+
const basis = Math.cos(Math.PI * x * i / width) * Math.cos(Math.PI * y * j / height);
|
|
71
|
+
const color = colors[i + j * numX];
|
|
72
|
+
r += color[0] * basis;
|
|
73
|
+
g += color[1] * basis;
|
|
74
|
+
b += color[2] * basis;
|
|
75
|
+
}
|
|
76
|
+
const o = (x + y * width) * 4;
|
|
77
|
+
pixels[o] = linearToSrgb(r);
|
|
78
|
+
pixels[o + 1] = linearToSrgb(g);
|
|
79
|
+
pixels[o + 2] = linearToSrgb(b);
|
|
80
|
+
pixels[o + 3] = 255;
|
|
81
|
+
}
|
|
82
|
+
return pixels;
|
|
83
|
+
}
|
|
84
|
+
function thumbHashToApproximateAspectRatio(hash) {
|
|
85
|
+
const header = hash[3];
|
|
86
|
+
const hasAlpha = (hash[2] & 128) !== 0;
|
|
87
|
+
const isLandscape = (hash[4] & 128) !== 0;
|
|
88
|
+
return (isLandscape ? hasAlpha ? 5 : 7 : header & 7) / (isLandscape ? header & 7 : hasAlpha ? 5 : 7);
|
|
89
|
+
}
|
|
90
|
+
function thumbHashToRGBA(hash) {
|
|
91
|
+
if (hash.length < 5) return null;
|
|
92
|
+
const { PI, min, max, cos, round } = Math;
|
|
93
|
+
const header24 = hash[0] | hash[1] << 8 | hash[2] << 16;
|
|
94
|
+
const header16 = hash[3] | hash[4] << 8;
|
|
95
|
+
const l_dc = (header24 & 63) / 63;
|
|
96
|
+
const p_dc = (header24 >> 6 & 63) / 31.5 - 1;
|
|
97
|
+
const q_dc = (header24 >> 12 & 63) / 31.5 - 1;
|
|
98
|
+
const l_scale = (header24 >> 18 & 31) / 31;
|
|
99
|
+
const hasAlpha = header24 >> 23 !== 0;
|
|
100
|
+
const p_scale = (header16 >> 3 & 63) / 63;
|
|
101
|
+
const q_scale = (header16 >> 9 & 63) / 63;
|
|
102
|
+
const isLandscape = header16 >> 15 !== 0;
|
|
103
|
+
const lx = max(3, isLandscape ? hasAlpha ? 5 : 7 : header16 & 7);
|
|
104
|
+
const ly = max(3, isLandscape ? header16 & 7 : hasAlpha ? 5 : 7);
|
|
105
|
+
const a_dc = hasAlpha ? (hash[5] & 15) / 15 : 1;
|
|
106
|
+
const a_scale = (hash[5] >> 4) / 15;
|
|
107
|
+
const ac_start = hasAlpha ? 6 : 5;
|
|
108
|
+
let ac_index = 0;
|
|
109
|
+
const decodeChannel = (nx, ny, scale) => {
|
|
110
|
+
const ac = [];
|
|
111
|
+
for (let cy = 0; cy < ny; cy++) for (let cx = cy ? 0 : 1; cx * ny < nx * (ny - cy); cx++) {
|
|
112
|
+
const byte = hash[ac_start + (ac_index >> 1)];
|
|
113
|
+
if (byte === void 0) return null;
|
|
114
|
+
ac.push(((byte >> ((ac_index++ & 1) << 2) & 15) / 7.5 - 1) * scale);
|
|
115
|
+
}
|
|
116
|
+
return ac;
|
|
117
|
+
};
|
|
118
|
+
const l_ac = decodeChannel(lx, ly, l_scale);
|
|
119
|
+
const p_ac = decodeChannel(3, 3, p_scale * 1.25);
|
|
120
|
+
const q_ac = decodeChannel(3, 3, q_scale * 1.25);
|
|
121
|
+
const a_ac = hasAlpha ? decodeChannel(5, 5, a_scale) : null;
|
|
122
|
+
if (!l_ac || !p_ac || !q_ac || hasAlpha && !a_ac) return null;
|
|
123
|
+
const ratio = thumbHashToApproximateAspectRatio(hash);
|
|
124
|
+
const w = round(ratio > 1 ? 32 : 32 * ratio);
|
|
125
|
+
const h = round(ratio > 1 ? 32 / ratio : 32);
|
|
126
|
+
const rgba = new Uint8Array(w * h * 4);
|
|
127
|
+
const fx = [];
|
|
128
|
+
const fy = [];
|
|
129
|
+
for (let y = 0, i = 0; y < h; y++) for (let x = 0; x < w; x++, i += 4) {
|
|
130
|
+
let l = l_dc;
|
|
131
|
+
let p = p_dc;
|
|
132
|
+
let q = q_dc;
|
|
133
|
+
let a = a_dc;
|
|
134
|
+
for (let cx = 0, n = max(lx, hasAlpha ? 5 : 3); cx < n; cx++) fx[cx] = cos(PI / w * (x + .5) * cx);
|
|
135
|
+
for (let cy = 0, n = max(ly, hasAlpha ? 5 : 3); cy < n; cy++) fy[cy] = cos(PI / h * (y + .5) * cy);
|
|
136
|
+
for (let cy = 0, j = 0; cy < ly; cy++) for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx * ly < lx * (ly - cy); cx++, j++) l += l_ac[j] * fx[cx] * fy2;
|
|
137
|
+
for (let cy = 0, j = 0; cy < 3; cy++) for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 3 - cy; cx++, j++) {
|
|
138
|
+
const f = fx[cx] * fy2;
|
|
139
|
+
p += p_ac[j] * f;
|
|
140
|
+
q += q_ac[j] * f;
|
|
141
|
+
}
|
|
142
|
+
if (hasAlpha && a_ac) for (let cy = 0, j = 0; cy < 5; cy++) for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 5 - cy; cx++, j++) a += a_ac[j] * fx[cx] * fy2;
|
|
143
|
+
const b = l - 2 / 3 * p;
|
|
144
|
+
const r = (3 * l - b + q) / 2;
|
|
145
|
+
const g = r - q;
|
|
146
|
+
rgba[i] = max(0, 255 * min(1, r));
|
|
147
|
+
rgba[i + 1] = max(0, 255 * min(1, g));
|
|
148
|
+
rgba[i + 2] = max(0, 255 * min(1, b));
|
|
149
|
+
rgba[i + 3] = max(0, 255 * min(1, a));
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
w,
|
|
153
|
+
h,
|
|
154
|
+
rgba
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
var PNG_CRC_TABLE = [
|
|
158
|
+
0,
|
|
159
|
+
498536548,
|
|
160
|
+
997073096,
|
|
161
|
+
651767980,
|
|
162
|
+
1994146192,
|
|
163
|
+
1802195444,
|
|
164
|
+
1303535960,
|
|
165
|
+
1342533948,
|
|
166
|
+
-306674912,
|
|
167
|
+
-267414716,
|
|
168
|
+
-690576408,
|
|
169
|
+
-882789492,
|
|
170
|
+
-1687895376,
|
|
171
|
+
-2032938284,
|
|
172
|
+
-1609899400,
|
|
173
|
+
-1111625188
|
|
174
|
+
];
|
|
175
|
+
/** Uncompressed PNG (zlib store blocks) — correctness over size; inputs are
|
|
176
|
+
* ~32px placeholders so the payload stays small. */
|
|
177
|
+
function rgbaToPng(w, h, rgba) {
|
|
178
|
+
const row = w * 4 + 1;
|
|
179
|
+
const idat = 6 + h * (5 + row);
|
|
180
|
+
const bytes = [
|
|
181
|
+
137,
|
|
182
|
+
80,
|
|
183
|
+
78,
|
|
184
|
+
71,
|
|
185
|
+
13,
|
|
186
|
+
10,
|
|
187
|
+
26,
|
|
188
|
+
10,
|
|
189
|
+
0,
|
|
190
|
+
0,
|
|
191
|
+
0,
|
|
192
|
+
13,
|
|
193
|
+
73,
|
|
194
|
+
72,
|
|
195
|
+
68,
|
|
196
|
+
82,
|
|
197
|
+
0,
|
|
198
|
+
0,
|
|
199
|
+
w >> 8,
|
|
200
|
+
w & 255,
|
|
201
|
+
0,
|
|
202
|
+
0,
|
|
203
|
+
h >> 8,
|
|
204
|
+
h & 255,
|
|
205
|
+
8,
|
|
206
|
+
6,
|
|
207
|
+
0,
|
|
208
|
+
0,
|
|
209
|
+
0,
|
|
210
|
+
0,
|
|
211
|
+
0,
|
|
212
|
+
0,
|
|
213
|
+
0,
|
|
214
|
+
idat >>> 24,
|
|
215
|
+
idat >> 16 & 255,
|
|
216
|
+
idat >> 8 & 255,
|
|
217
|
+
idat & 255,
|
|
218
|
+
73,
|
|
219
|
+
68,
|
|
220
|
+
65,
|
|
221
|
+
84,
|
|
222
|
+
120,
|
|
223
|
+
1
|
|
224
|
+
];
|
|
225
|
+
let a = 1;
|
|
226
|
+
let b = 0;
|
|
227
|
+
for (let y = 0, i = 0, end = row - 1; y < h; y++, end += row - 1) {
|
|
228
|
+
bytes.push(y + 1 < h ? 0 : 1, row & 255, row >> 8, ~row & 255, row >> 8 ^ 255, 0);
|
|
229
|
+
for (b = (b + a) % 65521; i < end; i++) {
|
|
230
|
+
const u = rgba[i] & 255;
|
|
231
|
+
bytes.push(u);
|
|
232
|
+
a = (a + u) % 65521;
|
|
233
|
+
b = (b + a) % 65521;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
bytes.push(b >> 8, b & 255, a >> 8, a & 255, 0, 0, 0, 0, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130);
|
|
237
|
+
for (let [start, end] of [[12, 29], [37, 41 + idat]]) {
|
|
238
|
+
let c = -1;
|
|
239
|
+
for (let i = start; i < end; i++) {
|
|
240
|
+
c ^= bytes[i];
|
|
241
|
+
c = c >>> 4 ^ PNG_CRC_TABLE[c & 15];
|
|
242
|
+
c = c >>> 4 ^ PNG_CRC_TABLE[c & 15];
|
|
243
|
+
}
|
|
244
|
+
c = ~c;
|
|
245
|
+
bytes[end++] = c >>> 24;
|
|
246
|
+
bytes[end++] = c >> 16 & 255;
|
|
247
|
+
bytes[end++] = c >> 8 & 255;
|
|
248
|
+
bytes[end++] = c & 255;
|
|
249
|
+
}
|
|
250
|
+
return bytes;
|
|
251
|
+
}
|
|
252
|
+
var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
253
|
+
function base64Encode(bytes) {
|
|
254
|
+
let out = "";
|
|
255
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
256
|
+
const b0 = bytes[i];
|
|
257
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
258
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
259
|
+
out += BASE64_CHARS[b0 >> 2];
|
|
260
|
+
out += BASE64_CHARS[(b0 & 3) << 4 | b1 >> 4];
|
|
261
|
+
out += i + 1 < bytes.length ? BASE64_CHARS[(b1 & 15) << 2 | b2 >> 6] : "=";
|
|
262
|
+
out += i + 2 < bytes.length ? BASE64_CHARS[b2 & 63] : "=";
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
function base64Decode(text) {
|
|
267
|
+
const clean = text.replace(/-/g, "+").replace(/_/g, "/").replace(/[\s=]/g, "");
|
|
268
|
+
const bytes = new Uint8Array(Math.floor(clean.length * 3 / 4));
|
|
269
|
+
let out = 0;
|
|
270
|
+
let acc = 0;
|
|
271
|
+
let bits = 0;
|
|
272
|
+
for (const ch of clean) {
|
|
273
|
+
const digit = BASE64_CHARS.indexOf(ch);
|
|
274
|
+
if (digit === -1) return null;
|
|
275
|
+
acc = acc << 6 | digit;
|
|
276
|
+
bits += 6;
|
|
277
|
+
if (bits >= 8) {
|
|
278
|
+
bits -= 8;
|
|
279
|
+
if (out < bytes.length) bytes[out++] = acc >> bits & 255;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return bytes.subarray(0, out);
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
export { hashToPngBase64 };
|
|
@@ -0,0 +1,133 @@
|
|
|
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 ensureWebUnits(value) {
|
|
93
|
+
const text = String(value).trim();
|
|
94
|
+
return text.endsWith("%") ? text : `${text}px`;
|
|
95
|
+
}
|
|
96
|
+
/** Browser `object-position` for a resolved position — mirrors expo-image
|
|
97
|
+
* web's `getObjectPositionFromContentPositionObject`. */
|
|
98
|
+
function objectPositionCSSValue(position) {
|
|
99
|
+
const resolved = { ...position };
|
|
100
|
+
if (resolved.top == null && resolved.bottom == null) resolved.top = "50%";
|
|
101
|
+
if (resolved.left == null && resolved.right == null) resolved.left = "50%";
|
|
102
|
+
return [
|
|
103
|
+
"top",
|
|
104
|
+
"bottom",
|
|
105
|
+
"left",
|
|
106
|
+
"right"
|
|
107
|
+
].map((edge) => resolved[edge] != null ? `${edge} ${ensureWebUnits(resolved[edge])}` : "").filter((part) => part !== "").join(" ") || "50% 50%";
|
|
108
|
+
}
|
|
109
|
+
/** Browser `srcset` candidates for a source array: `w` descriptors (real
|
|
110
|
+
* file px = width·scale) when every source is sized, `x` descriptors when
|
|
111
|
+
* every source only carries density. `src` falls back to the largest
|
|
112
|
+
* declared pixel count — guaranteed coverage where srcset is ignored. */
|
|
113
|
+
function imageSrcSet(sources) {
|
|
114
|
+
if (sources.length === 0) return { src: "" };
|
|
115
|
+
const everyWidth = sources.every((source) => typeof source.width === "number" && source.width > 0);
|
|
116
|
+
const everyScale = !everyWidth && sources.every((source) => typeof source.scale === "number" && source.scale > 0);
|
|
117
|
+
const srcSet = everyWidth ? sources.map((source) => `${source.uri} ${Math.round(source.width * (source.scale ?? 1))}w`).join(", ") : everyScale ? sources.map((source) => `${source.uri} ${source.scale}x`).join(", ") : void 0;
|
|
118
|
+
let fallback = sources[0];
|
|
119
|
+
let bestCount = -Infinity;
|
|
120
|
+
for (const source of sources) {
|
|
121
|
+
const count = (source.width ?? 0) * (source.height ?? 0) * (source.scale ?? 1) ** 2;
|
|
122
|
+
if (count > bestCount) {
|
|
123
|
+
bestCount = count;
|
|
124
|
+
fallback = source;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
src: fallback.uri,
|
|
129
|
+
srcSet
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { DEFAULT_CONTENT_FIT, imageSrcSet, objectPositionCSSValue, resolveContentFit, resolveContentPosition };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Cross-platform UI for Octane xplat — shared primitives, stacks, routes and theming across web (DOM), iOS and Android (NativeScript)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"exports": {
|
|
33
33
|
".": {
|
|
34
34
|
"macos": {
|
|
35
|
-
"types": "./types/index.macos.d.ts",
|
|
35
|
+
"types": "./types/generated/index.macos.d.ts",
|
|
36
36
|
"default": "./src/index.macos.ts"
|
|
37
37
|
},
|
|
38
38
|
"web": {
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"@nativescript-community/ui-drawer": "0.1.32",
|
|
71
71
|
"@nativescript/core": "9.1.2",
|
|
72
72
|
"@nativescript/types": "9.0.0",
|
|
73
|
+
"@octane-xplat/macos-renderer": "0.11.0",
|
|
73
74
|
"@octanejs/vite-plugin": "0.1.61",
|
|
74
75
|
"@tsrx/typescript-plugin": "0.4.11",
|
|
75
76
|
"jsdom": "30.1.1",
|
package/src/Image.macos.tsrx
CHANGED
|
@@ -4,13 +4,22 @@ import { appKitImageSource } from './svg-image.macos'
|
|
|
4
4
|
import type { ImageProps } from './props'
|
|
5
5
|
|
|
6
6
|
export function Image(props: ImageProps) @{
|
|
7
|
-
const
|
|
7
|
+
const srcString = (Array.isArray(props.src) ? props.src[0]?.uri : props.src) ?? ''
|
|
8
|
+
const src = useMemo(() => appKitImageSource(srcString), [srcString])
|
|
9
|
+
// contentFit → style.objectFit (the renderer maps contain/scale-down/
|
|
10
|
+
// fill/none; 'cover' degrades to scale-down — AppKit has no cover
|
|
11
|
+
// scaling mode). contentPosition is unimplemented on this leaf.
|
|
12
|
+
// `decoding` is ignored — AppKit image loads are synchronous.
|
|
13
|
+
const style = {
|
|
14
|
+
objectFit: props.contentFit ?? 'cover',
|
|
15
|
+
...props.style,
|
|
16
|
+
}
|
|
8
17
|
|
|
9
18
|
<image
|
|
10
19
|
id={props.id}
|
|
11
20
|
src={src}
|
|
12
21
|
className={props.className}
|
|
13
|
-
|
|
22
|
+
{style}
|
|
14
23
|
accessibilityLabel={props.alt ?? ''}
|
|
15
24
|
/>
|
|
16
25
|
}
|