@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.
Files changed (219) hide show
  1. package/dist/native/Image.tsrx.js +151 -16
  2. package/dist/native/hash-image.js +285 -0
  3. package/dist/native/image-content-binding.js +158 -0
  4. package/dist/native/image-content.js +206 -0
  5. package/dist/native/image-sizing.js +102 -0
  6. package/dist/native/octane-client-build.json +1 -1
  7. package/dist/native/vendor/ui-svg/index.android.js +1 -1
  8. package/dist/web/Image.web.tsrx.js +112 -19
  9. package/dist/web/hash-image.js +285 -0
  10. package/dist/web/image-content.js +133 -0
  11. package/dist/web/octane-client-build.json +1 -1
  12. package/package.json +3 -2
  13. package/src/Image.macos.tsrx +11 -2
  14. package/src/Image.tsrx +203 -17
  15. package/src/Image.web.tsrx +91 -5
  16. package/src/Tabs.macos.tsrx +2 -0
  17. package/src/Timestamp.macos.tsrx +1 -1
  18. package/src/hash-image.test.ts +72 -0
  19. package/src/hash-image.ts +310 -0
  20. package/src/hosted-unsupported.macos.tsrx +1 -2
  21. package/src/image-content-binding.mobile.test.ts +174 -0
  22. package/src/image-content-binding.ts +231 -0
  23. package/src/image-content.mobile.test.tsrx +84 -0
  24. package/src/image-content.test.ts +219 -0
  25. package/src/image-content.ts +309 -0
  26. package/src/image-decoding.web.test.tsrx +26 -0
  27. package/src/image-placeholder.mobile.test.tsrx +113 -0
  28. package/src/image-placeholder.web.test.tsrx +48 -0
  29. package/src/image-recycling.mobile.test.ts +61 -0
  30. package/src/image-sizing.mobile.test.ts +158 -0
  31. package/src/image-sizing.ts +117 -0
  32. package/src/image.mobile.test.tsrx +81 -0
  33. package/src/index.macos.ts +1 -0
  34. package/src/index.shared.ts +7 -0
  35. package/src/index.shared.web.ts +7 -0
  36. package/src/list-viewability-observer.ios.ts +1 -0
  37. package/src/list-viewability.mobile.test.ts +1 -0
  38. package/src/props.ts +100 -1
  39. package/src/safeAreaInsets.macos.tsrx +6 -1
  40. package/src/styled.macos.tsrx +1 -1
  41. package/src/vendor/ui-svg/src/index.android.ts +5 -1
  42. package/types/generated/.tsrx-typegen-manifest.json +170 -0
  43. package/types/generated/Accordion.macos.d.ts +5 -0
  44. package/types/generated/Alert.macos.d.ts +3 -0
  45. package/types/generated/AlertDialog.macos.d.ts +4 -0
  46. package/types/generated/AppShell.macos.d.ts +7 -0
  47. package/types/generated/AspectRatio.macos.d.ts +5 -0
  48. package/types/generated/Avatar.macos.d.ts +6 -0
  49. package/types/generated/AvatarGroup.macos.d.ts +6 -0
  50. package/types/generated/Badge.macos.d.ts +5 -0
  51. package/types/generated/Banner.macos.d.ts +4 -0
  52. package/types/generated/BaseTypeahead.macos.d.ts +7 -0
  53. package/types/generated/Blockquote.macos.d.ts +6 -0
  54. package/types/generated/BottomSheet.macos.d.ts +3 -0
  55. package/types/generated/Breadcrumb.macos.d.ts +4 -0
  56. package/types/generated/ButtonGroup.macos.d.ts +3 -0
  57. package/types/generated/Calendar.macos.d.ts +5 -0
  58. package/types/generated/Card.macos.d.ts +4 -0
  59. package/types/generated/Carousel.macos.d.ts +6 -0
  60. package/types/generated/Center.macos.d.ts +4 -0
  61. package/types/generated/CheckIndicator.macos.d.ts +4 -0
  62. package/types/generated/CheckboxGroup.macos.d.ts +3 -0
  63. package/types/generated/CheckboxIndicator.macos.d.ts +4 -0
  64. package/types/generated/Chip.macos.d.ts +5 -0
  65. package/types/generated/Citation.macos.d.ts +4 -0
  66. package/types/generated/ClickableCard.macos.d.ts +3 -0
  67. package/types/generated/Code.macos.d.ts +5 -0
  68. package/types/generated/CodeBlock.macos.d.ts +6 -0
  69. package/types/generated/Collapsible.macos.d.ts +5 -0
  70. package/types/generated/CommandPalette.macos.d.ts +3 -0
  71. package/types/generated/ComplexSelector.macos.d.ts +3 -0
  72. package/types/generated/ContextMenu.macos.d.ts +5 -0
  73. package/types/generated/DateInput.macos.d.ts +6 -0
  74. package/types/generated/DateRangeInput.macos.d.ts +5 -0
  75. package/types/generated/DateTimeInput.macos.d.ts +5 -0
  76. package/types/generated/Dialog.macos.d.ts +3 -0
  77. package/types/generated/Drawer.macos.d.ts +7 -0
  78. package/types/generated/DropdownMenu.macos.d.ts +5 -0
  79. package/types/generated/Empty.macos.d.ts +4 -0
  80. package/types/generated/FieldGroup.macos.d.ts +4 -0
  81. package/types/generated/FormField.macos.d.ts +4 -0
  82. package/types/generated/FormLayout.macos.d.ts +6 -0
  83. package/types/generated/HStack.macos.d.ts +4 -0
  84. package/types/generated/Heading.macos.d.ts +3 -0
  85. package/types/generated/Icon.macos.d.ts +3 -0
  86. package/types/generated/IconButton.macos.d.ts +3 -0
  87. package/types/generated/Image.d.ts +0 -5
  88. package/types/generated/Image.macos.d.ts +2 -0
  89. package/types/generated/Image.web.d.ts +12 -1
  90. package/types/generated/InputGroup.macos.d.ts +5 -0
  91. package/types/generated/InputNumber.macos.d.ts +5 -0
  92. package/types/generated/InputRating.macos.d.ts +4 -0
  93. package/types/generated/Item.macos.d.ts +3 -0
  94. package/types/generated/Kbd.macos.d.ts +4 -0
  95. package/types/generated/KeyboardAvoiding.macos.d.ts +3 -0
  96. package/types/generated/Lightbox.macos.d.ts +2 -0
  97. package/types/generated/Link.macos.d.ts +3 -0
  98. package/types/generated/List.macos.d.ts +4 -0
  99. package/types/generated/ListItem.macos.d.ts +5 -0
  100. package/types/generated/MenuParts.macos.d.ts +9 -0
  101. package/types/generated/MenuSurface.macos.d.ts +4 -0
  102. package/types/generated/MetadataList.macos.d.ts +4 -0
  103. package/types/generated/MetadataListItem.macos.d.ts +3 -0
  104. package/types/generated/Meter.macos.d.ts +3 -0
  105. package/types/generated/MobileNav.macos.d.ts +15 -0
  106. package/types/generated/MoreMenu.macos.d.ts +2 -0
  107. package/types/generated/NavIcon.macos.d.ts +4 -0
  108. package/types/generated/NavLink.macos.d.ts +3 -0
  109. package/types/generated/NavMenu.macos.d.ts +11 -0
  110. package/types/generated/NavigationMenu.macos.d.ts +4 -0
  111. package/types/generated/Outline.macos.d.ts +5 -0
  112. package/types/generated/OverflowList.macos.d.ts +6 -0
  113. package/types/generated/Overlay.macos.d.ts +3 -0
  114. package/types/generated/Pagination.macos.d.ts +4 -0
  115. package/types/generated/PinInput.macos.d.ts +4 -0
  116. package/types/generated/Popover.macos.d.ts +3 -0
  117. package/types/generated/PowerSearch.macos.d.ts +7 -0
  118. package/types/generated/PowerSearchFilterEditor.macos.d.ts +12 -0
  119. package/types/generated/PowerSearchToken.macos.d.ts +7 -0
  120. package/types/generated/Pressable.macos.d.ts +2 -0
  121. package/types/generated/ProgressBar.macos.d.ts +6 -0
  122. package/types/generated/ProgressGroup.macos.d.ts +3 -0
  123. package/types/generated/RadioGroup.macos.d.ts +5 -0
  124. package/types/generated/RadioIndicator.macos.d.ts +3 -0
  125. package/types/generated/ResizeHandle.macos.d.ts +7 -0
  126. package/types/generated/RouteBoundary.macos.d.ts +8 -0
  127. package/types/generated/RouteHost.macos.d.ts +13 -0
  128. package/types/generated/Row.macos.d.ts +2 -0
  129. package/types/generated/SafeArea.macos.d.ts +3 -0
  130. package/types/generated/Screen.macos.d.ts +3 -0
  131. package/types/generated/ScrollableArea.macos.d.ts +7 -0
  132. package/types/generated/Section.macos.d.ts +5 -0
  133. package/types/generated/Select.macos.d.ts +3 -0
  134. package/types/generated/SelectableCard.macos.d.ts +3 -0
  135. package/types/generated/Separator.macos.d.ts +5 -0
  136. package/types/generated/SideNav.macos.d.ts +19 -0
  137. package/types/generated/Skeleton.macos.d.ts +5 -0
  138. package/types/generated/StatusDot.macos.d.ts +5 -0
  139. package/types/generated/Stepper.macos.d.ts +4 -0
  140. package/types/generated/TabList.macos.d.ts +18 -0
  141. package/types/generated/Table.macos.d.ts +4 -0
  142. package/types/generated/Tabs.macos.d.ts +3 -0
  143. package/types/generated/Text.macos.d.ts +5 -0
  144. package/types/generated/Thumbnail.macos.d.ts +7 -0
  145. package/types/generated/TimeInput.macos.d.ts +6 -0
  146. package/types/generated/TimeWheelPanel.macos.d.ts +16 -0
  147. package/types/generated/Timeline.macos.d.ts +3 -0
  148. package/types/generated/Timer.macos.d.ts +3 -0
  149. package/types/generated/Timestamp.macos.d.ts +5 -0
  150. package/types/generated/Toast.macos.d.ts +3 -0
  151. package/types/generated/ToggleButton.macos.d.ts +3 -0
  152. package/types/generated/Token.macos.d.ts +5 -0
  153. package/types/generated/Tokenizer.macos.d.ts +6 -0
  154. package/types/generated/Toolbar.macos.d.ts +6 -0
  155. package/types/generated/TopNav.macos.d.ts +25 -0
  156. package/types/generated/Tree.macos.d.ts +4 -0
  157. package/types/generated/Typeahead.macos.d.ts +8 -0
  158. package/types/generated/TypeaheadItem.macos.d.ts +5 -0
  159. package/types/generated/User.macos.d.ts +5 -0
  160. package/types/generated/VStack.macos.d.ts +4 -0
  161. package/types/generated/View.macos.d.ts +3 -0
  162. package/types/generated/VirtualList.macos.d.ts +6 -0
  163. package/types/generated/VisuallyHidden.macos.d.ts +5 -0
  164. package/types/generated/WebView.macos.d.ts +2 -0
  165. package/types/generated/aliases.macos.d.ts +3 -0
  166. package/types/generated/anim.macos.d.ts +3 -0
  167. package/types/generated/animation-host.macos.d.ts +3 -0
  168. package/types/generated/bottom-sheet-panel.macos.d.ts +7 -0
  169. package/types/generated/bottom-sheet-service.macos.d.ts +5 -0
  170. package/types/generated/clipboard-write.macos.d.ts +3 -0
  171. package/types/generated/colorScheme.macos.d.ts +4 -0
  172. package/types/generated/command-palette-keys.macos.d.ts +2 -0
  173. package/types/generated/command-palette-scroll.macos.d.ts +2 -0
  174. package/types/generated/controls.macos.d.ts +8 -0
  175. package/types/generated/date-composition.macos.d.ts +5 -0
  176. package/types/generated/escape-props.macos.d.ts +5 -0
  177. package/types/generated/hash-image.d.ts +13 -0
  178. package/types/generated/hosted-unsupported.macos.d.ts +1 -0
  179. package/types/generated/image-content-binding.d.ts +64 -0
  180. package/types/generated/image-content.d.ts +56 -0
  181. package/types/generated/image-sizing.d.ts +37 -0
  182. package/types/generated/index.macos.d.ts +196 -0
  183. package/types/generated/index.shared.d.ts +1 -1
  184. package/types/generated/index.shared.web.d.ts +1 -1
  185. package/types/generated/layout-shells.macos.d.ts +3 -0
  186. package/types/generated/layout.macos.d.ts +9 -0
  187. package/types/generated/macos-extras.macos.d.ts +4 -0
  188. package/types/generated/measure-bounds.macos.d.ts +3 -0
  189. package/types/generated/nav-href.macos.d.ts +4 -0
  190. package/types/generated/outline-dom.macos.d.ts +2 -0
  191. package/types/generated/pan.macos.d.ts +3 -0
  192. package/types/generated/platform.macos.d.ts +1 -0
  193. package/types/generated/presentation.macos.d.ts +15 -0
  194. package/types/generated/props.d.ts +76 -1
  195. package/types/generated/query-storage.macos.d.ts +8 -0
  196. package/types/generated/resize-basis.macos.d.ts +4 -0
  197. package/types/generated/resize-persist.macos.d.ts +8 -0
  198. package/types/generated/route.macos.d.ts +28 -0
  199. package/types/generated/safeAreaInsets.macos.d.ts +10 -0
  200. package/types/generated/stacks.macos.d.ts +3 -0
  201. package/types/generated/styled.macos.d.ts +9 -0
  202. package/types/generated/surfaces.macos.d.ts +4 -0
  203. package/types/generated/svg-image.macos.d.ts +3 -0
  204. package/types/generated/text-controls.macos.d.ts +4 -0
  205. package/types/generated/theme/theme-scheme.macos.d.ts +8 -0
  206. package/types/generated/toast-service.macos.d.ts +3 -0
  207. package/types/generated/toast-viewport.macos.d.ts +5 -0
  208. package/types/generated/translate.macos.d.ts +1 -0
  209. package/types/generated/unsupported.macos.d.ts +1 -0
  210. package/types/generated/use-imperative-alert-dialog.macos.d.ts +2 -0
  211. package/types/generated/use-imperative-dialog.macos.d.ts +2 -0
  212. package/types/generated/use-lightbox.macos.d.ts +2 -0
  213. package/types/generated/use-scrollable-area.macos.d.ts +6 -0
  214. package/types/generated/useMeasure.macos.d.ts +3 -0
  215. package/types/generated/useResizable.macos.d.ts +8 -0
  216. package/types/generated/useStore.macos.d.ts +2 -0
  217. package/types/generated/windows.macos.d.ts +2 -0
  218. package/src/CameraView.macos.tsrx +0 -6
  219. package/types/index.macos.d.ts +0 -448
@@ -1,36 +1,125 @@
1
1
  import { cx } from "./cx.js";
2
+ import { rememberGridChildPlacement } from "./grid-placement.js";
2
3
  import { applyEscapeProps } from "./escape-props.js";
3
4
  import { layoutChildProps } from "./layout-child.js";
4
5
  import { isSvgSrc, svgSource } from "./svg-source.js";
5
6
  import "./svg.mobile.js";
6
- import { defineUniversalComponent, hookSlots, universalIf, universalPlan, universalProps, universalValue, useMemo } from "@nativescript-community/octane";
7
- import { ImageSource } from "@nativescript/core";
7
+ import { createSizedImageBinding } from "./image-sizing.js";
8
+ import { contentFitToStretch, needsContentRect, resolveContentFit, resolveContentPosition } from "./image-content.js";
9
+ import { createImageContentBinding } from "./image-content-binding.js";
10
+ import { hashToPngBase64 } from "./hash-image.js";
11
+ import { defineUniversalComponent, hookSlots, universalIf, universalPlan, universalProps, universalValue, useLayoutEffect, useMemo, useRef } from "@nativescript-community/octane";
12
+ import { ImageSource, Screen, isAndroid, isIOS, knownFolders } from "@nativescript/core";
8
13
  //#region src/Image.tsrx
9
- var _hs$ = /* @__PURE__ */ hookSlots(2);
14
+ var _hs$ = /* @__PURE__ */ hookSlots(8);
10
15
  var _h$0 = /* @__PURE__ */ Symbol(_hs$);
11
16
  var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
17
+ var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
18
+ var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
19
+ var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
20
+ var _h$5 = /* @__PURE__ */ Symbol(_hs$ + 5);
21
+ var _h$6 = /* @__PURE__ */ Symbol(_hs$ + 6);
22
+ var _h$7 = /* @__PURE__ */ Symbol(_hs$ + 7);
12
23
  var __octaneUniversalPlan0 = universalPlan("nativescript", {
13
24
  "kind": "host",
14
25
  "type": "svgview",
15
26
  "propsSlot": 0
16
27
  });
17
28
  var __octaneUniversalPlan1 = universalPlan("nativescript", {
29
+ "kind": "host",
30
+ "type": "absolutelayout",
31
+ "propsSlot": 0,
32
+ "children": [{
33
+ "kind": "host",
34
+ "type": "image",
35
+ "props": { "stretch": "fill" },
36
+ "bindings": [
37
+ ["ref", 1],
38
+ ["loadMode", 2],
39
+ ["accessibilityLabel", 3]
40
+ ]
41
+ }]
42
+ });
43
+ var __octaneUniversalPlan2 = universalPlan("nativescript", {
18
44
  "kind": "host",
19
45
  "type": "image",
20
46
  "propsSlot": 0
21
47
  });
22
- var __octaneUniversalPlan2 = universalPlan("nativescript", {
48
+ var __octaneUniversalPlan3 = universalPlan("nativescript", {
23
49
  "kind": "slot",
24
50
  "slot": 0
25
51
  });
52
+ function placeholderSource(placeholder) {
53
+ const png = hashToPngBase64(placeholder);
54
+ if (png) return ImageSource.fromBase64Sync(png);
55
+ const comma = placeholder.indexOf(",");
56
+ if (placeholder.startsWith("data:") && comma > 0 && placeholder.slice(0, comma).endsWith(";base64")) return ImageSource.fromBase64Sync(placeholder.slice(comma + 1));
57
+ if (placeholder.startsWith("res://")) return ImageSource.fromResourceSync(placeholder.slice(6));
58
+ if (placeholder.startsWith("~/")) return ImageSource.fromFileSync(knownFolders.currentApp().path + "/" + placeholder.slice(2));
59
+ if (!/^https?:\/\//.test(placeholder)) return ImageSource.fromFileSync(placeholder);
60
+ return null;
61
+ }
62
+ function applyPlaceholder(view, placeholder) {
63
+ if (!placeholder || !(view.isLoading || !view.imageSource)) return;
64
+ const ph = placeholderSource(placeholder);
65
+ if (ph) view.imageSource = ph;
66
+ }
67
+ function clipImageHost(host) {
68
+ host?.on?.("loaded", () => {
69
+ if (isIOS && host.ios) host.ios.clipsToBounds = true;
70
+ });
71
+ }
26
72
  var Image = defineUniversalComponent("nativescript", function Image(props) {
27
- const svg = useMemo(() => isSvgSrc(props.src) ? svgSource(props.src) : null, [props.src], _h$0);
73
+ const sources = Array.isArray(props.src) ? props.src : null;
74
+ const srcString = sources ? "" : props.src;
75
+ const fit = resolveContentFit(props.contentFit);
76
+ const position = resolveContentPosition(props.contentPosition);
77
+ const computed = needsContentRect(fit, position);
78
+ const imageRef = useRef(null, _h$0);
79
+ const placeholderRef = useRef(props.placeholder, _h$1);
80
+ placeholderRef.current = props.placeholder;
81
+ const sizing = useMemo(() => createSizedImageBinding({ afterIssue: (v) => applyPlaceholder(v, placeholderRef.current) }), [], _h$2);
82
+ const binding = useMemo(() => createImageContentBinding({
83
+ isAndroid,
84
+ screenScale: () => Screen.mainScreen?.scale ?? 1,
85
+ issue: (view, src, gate) => sizing.update(view, src, gate)
86
+ }), [], _h$3);
87
+ const boundRef = useRef({
88
+ key: props.recyclingKey,
89
+ src: props.src
90
+ }, _h$4);
91
+ const bound = boundRef.current;
92
+ boundRef.current = {
93
+ key: props.recyclingKey,
94
+ src: props.src
95
+ };
96
+ if (bound.key != null && props.recyclingKey != null && bound.key !== props.recyclingKey && bound.src !== props.src && imageRef.current) imageRef.current.imageSource = null;
97
+ const svg = useMemo(() => srcString && isSvgSrc(srcString) ? svgSource(srcString) : null, [srcString], _h$5);
28
98
  const source = useMemo(() => {
29
- const comma = props.src.indexOf(",");
30
- if (props.src.startsWith("data:") && comma > 0 && props.src.slice(0, comma).endsWith(";base64")) return ImageSource.fromBase64Sync(props.src.slice(comma + 1));
31
- return props.src;
32
- }, [props.src], _h$1);
33
- return universalValue(__octaneUniversalPlan2, [universalIf(svg != null, () => {
99
+ const comma = srcString.indexOf(",");
100
+ if (srcString.startsWith("data:") && comma > 0 && srcString.slice(0, comma).endsWith(";base64")) return ImageSource.fromBase64Sync(srcString.slice(comma + 1));
101
+ return srcString;
102
+ }, [srcString], _h$6);
103
+ const update = {
104
+ source: sources ? null : source,
105
+ sources,
106
+ fit,
107
+ position,
108
+ computed
109
+ };
110
+ useLayoutEffect(() => {
111
+ const view = imageRef.current;
112
+ if (!view || !props.placeholder || svg != null) return;
113
+ const show = () => applyPlaceholder(view, props.placeholder);
114
+ show();
115
+ view.on?.("isLoadingChange", show);
116
+ return () => view.off?.("isLoadingChange", show);
117
+ }, [
118
+ props.src,
119
+ props.placeholder,
120
+ svg
121
+ ], _h$7);
122
+ return universalValue(__octaneUniversalPlan3, [universalIf(svg != null, () => {
34
123
  return universalValue(__octaneUniversalPlan0, [universalProps([
35
124
  [
36
125
  "set",
@@ -61,7 +150,7 @@ var Image = defineUniversalComponent("nativescript", function Image(props) {
61
150
  [
62
151
  "set",
63
152
  "stretch",
64
- "aspectFit"
153
+ fit === "none" ? "none" : contentFitToStretch(fit === "scale-down" ? "contain" : fit)
65
154
  ],
66
155
  [
67
156
  "set",
@@ -69,12 +158,53 @@ var Image = defineUniversalComponent("nativescript", function Image(props) {
69
158
  props.alt
70
159
  ]
71
160
  ])]);
161
+ }, () => universalIf(computed, () => {
162
+ return universalValue(__octaneUniversalPlan1, [
163
+ universalProps([
164
+ [
165
+ "set",
166
+ "ref",
167
+ (v) => {
168
+ clipImageHost(v);
169
+ rememberGridChildPlacement(v, props);
170
+ if (v) binding.update(v, null, update);
171
+ }
172
+ ],
173
+ [
174
+ "set",
175
+ "id",
176
+ props.id
177
+ ],
178
+ ["spread", layoutChildProps(props)],
179
+ [
180
+ "set",
181
+ "className",
182
+ cx(props.className)
183
+ ],
184
+ [
185
+ "set",
186
+ "style",
187
+ props.style
188
+ ]
189
+ ]),
190
+ (v) => {
191
+ imageRef.current = v;
192
+ applyEscapeProps(v, props);
193
+ if (v) binding.update(null, v, update);
194
+ },
195
+ props.decoding ?? "async",
196
+ props.alt
197
+ ]);
72
198
  }, () => {
73
- return universalValue(__octaneUniversalPlan1, [universalProps([
199
+ return universalValue(__octaneUniversalPlan2, [universalProps([
74
200
  [
75
201
  "set",
76
202
  "ref",
77
- (v) => applyEscapeProps(v, props)
203
+ (v) => {
204
+ imageRef.current = v;
205
+ applyEscapeProps(v, props);
206
+ if (v) binding.update(v, v, update);
207
+ }
78
208
  ],
79
209
  [
80
210
  "set",
@@ -94,8 +224,13 @@ var Image = defineUniversalComponent("nativescript", function Image(props) {
94
224
  ],
95
225
  [
96
226
  "set",
97
- "src",
98
- source
227
+ "stretch",
228
+ contentFitToStretch(fit)
229
+ ],
230
+ [
231
+ "set",
232
+ "loadMode",
233
+ props.decoding ?? "async"
99
234
  ],
100
235
  [
101
236
  "set",
@@ -103,7 +238,7 @@ var Image = defineUniversalComponent("nativescript", function Image(props) {
103
238
  props.alt
104
239
  ]
105
240
  ])]);
106
- })]);
241
+ }))]);
107
242
  }, { "module": "@nativescript-community/octane" });
108
243
  //#endregion
109
244
  export { Image };
@@ -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,158 @@
1
+ import { bestImageSource, contentRect } from "./image-content.js";
2
+ //#region src/image-content-binding.ts
3
+ /** Intrinsic size in dips. iOS `UIImage.size` is already points; Android
4
+ * `Bitmap` reports raw pixels — density-bucketed resource bitmaps convert
5
+ * through their bucket (a 300px xxxhdpi asset = 75 dips), while decoded
6
+ * remote/file bitmaps carry no density and treat px as dips, which is the
7
+ * CSS 'none' natural size on every platform. */
8
+ function intrinsicDipSize(imageSource, android) {
9
+ const width = imageSource?.width;
10
+ const height = imageSource?.height;
11
+ if (!width || !height) return null;
12
+ if (android) {
13
+ const density = imageSource.android?.getDensity?.() ?? 0;
14
+ if (density > 0) return {
15
+ w: width * 160 / density,
16
+ h: height * 160 / density
17
+ };
18
+ }
19
+ return {
20
+ w: width,
21
+ h: height
22
+ };
23
+ }
24
+ /** Measure → compute → apply for Image's content contract (study L7).
25
+ * NS's stock widget owns its draw path — the widgets ImageView computes
26
+ * its own matrix in onDraw and honors only the four stretch scaleTypes —
27
+ * so the drawable-level matrix expo-image uses is engine internals. What
28
+ * the leaf can own is the view-level equivalent: measure the box
29
+ * (`layoutChanged` → `getActualSize`, dips), compute the content rect
30
+ * with the same math as object-fit/object-position, and lay out the
31
+ * inner <image stretch="fill"> to that rect inside a clipped host. One
32
+ * code path produces identical rects on iOS and Android.
33
+ *
34
+ * This binding never writes `src` itself — it resolves WHICH source the
35
+ * view should load and hands it to `env.issue`, the sized-decode binding
36
+ * (study L1). That seam composes both contracts: the L8 array pick waits
37
+ * on the same first real layout the decode gate needs, and the computed
38
+ * path passes the semantic fit as the decode gate so 'none' still
39
+ * decodes at source resolution while 'fill' would have been
40
+ * view-sampled.
41
+ *
42
+ * Before the bitmap's intrinsic size is known the computed path sizes
43
+ * the inner image to the host box: a real rect is what lets the decode
44
+ * gate's first-layout check pass, and the placeholder is the CSS 'fill'
45
+ * answer until imageSourceChange refines it.
46
+ *
47
+ * `update` re-runs on every render (ref identity churn); all writes are
48
+ * idempotent. Null refs on detach are ignored — a dead listener is a
49
+ * self-contained cycle GC collects with the view. */
50
+ function createImageContentBinding(env) {
51
+ let sizeView = null;
52
+ let imageView = null;
53
+ let update = {
54
+ source: null,
55
+ sources: null,
56
+ fit: "cover",
57
+ position: {},
58
+ computed: false
59
+ };
60
+ let appliedW = 0;
61
+ let appliedH = 0;
62
+ let appliedX = 0;
63
+ let appliedY = 0;
64
+ let listening = false;
65
+ let sourceListening = false;
66
+ const screenScale = () => {
67
+ try {
68
+ return env.screenScale();
69
+ } catch {
70
+ return 1;
71
+ }
72
+ };
73
+ const decodeGate = () => update.fit === "none" ? "none" : "aspectFit";
74
+ const evaluate = () => {
75
+ if (!sizeView) return;
76
+ const needsViewSize = update.computed || update.sources != null;
77
+ let viewWidth = 0;
78
+ let viewHeight = 0;
79
+ if (needsViewSize) {
80
+ const actual = sizeView.getActualSize?.();
81
+ viewWidth = actual?.width ?? 0;
82
+ viewHeight = actual?.height ?? 0;
83
+ if (viewWidth <= 0 || viewHeight <= 0) return;
84
+ }
85
+ if (imageView) {
86
+ let resolved = update.source;
87
+ if (update.sources?.length) resolved = bestImageSource(update.sources, viewWidth, viewHeight, screenScale())?.uri;
88
+ if (resolved != null && resolved !== "") env.issue(imageView, resolved, update.computed ? decodeGate() : void 0);
89
+ }
90
+ if (!update.computed || !imageView) return;
91
+ const intrinsic = intrinsicDipSize(imageView.imageSource, env.isAndroid);
92
+ const rect = intrinsic ? contentRect(update.fit, update.position, viewWidth, viewHeight, intrinsic.w, intrinsic.h) : {
93
+ x: 0,
94
+ y: 0,
95
+ width: viewWidth,
96
+ height: viewHeight
97
+ };
98
+ if (rect.width !== appliedW || rect.height !== appliedH || rect.x !== appliedX || rect.y !== appliedY) {
99
+ appliedW = rect.width;
100
+ appliedH = rect.height;
101
+ appliedX = rect.x;
102
+ appliedY = rect.y;
103
+ imageView.width = rect.width;
104
+ imageView.height = rect.height;
105
+ imageView.left = rect.x;
106
+ imageView.top = rect.y;
107
+ }
108
+ };
109
+ const onLayout = () => evaluate();
110
+ const onImageSource = () => evaluate();
111
+ const listen = () => {
112
+ const wantsMeasure = update.computed || update.sources != null;
113
+ if (wantsMeasure && !listening && typeof sizeView?.on === "function") {
114
+ sizeView.on("layoutChanged", onLayout);
115
+ listening = true;
116
+ } else if (!wantsMeasure && listening) {
117
+ sizeView?.off?.("layoutChanged", onLayout);
118
+ listening = false;
119
+ }
120
+ if (update.computed && !sourceListening && typeof imageView?.on === "function") {
121
+ imageView.on("imageSourceChange", onImageSource);
122
+ sourceListening = true;
123
+ } else if (!update.computed && sourceListening) {
124
+ imageView?.off?.("imageSourceChange", onImageSource);
125
+ sourceListening = false;
126
+ }
127
+ };
128
+ return {
129
+ /** (Re)bind views + intent. `sizeView` is the measured box (the image
130
+ * itself on the stretch path, the clip host on the computed path);
131
+ * `imageView` is the <image> that draws. */
132
+ update(nextSizeView, nextImageView, next) {
133
+ if (nextSizeView && nextSizeView !== sizeView) {
134
+ sizeView?.off?.("layoutChanged", onLayout);
135
+ sizeView = nextSizeView;
136
+ listening = false;
137
+ }
138
+ if (nextImageView && nextImageView !== imageView) {
139
+ imageView?.off?.("imageSourceChange", onImageSource);
140
+ imageView = nextImageView;
141
+ sourceListening = false;
142
+ }
143
+ update = next;
144
+ listen();
145
+ evaluate();
146
+ },
147
+ dispose() {
148
+ sizeView?.off?.("layoutChanged", onLayout);
149
+ imageView?.off?.("imageSourceChange", onImageSource);
150
+ sizeView = null;
151
+ imageView = null;
152
+ listening = false;
153
+ sourceListening = false;
154
+ }
155
+ };
156
+ }
157
+ //#endregion
158
+ export { createImageContentBinding };