@octane-xplat/ui 0.10.0 → 0.11.1
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 +5 -4
- 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/colorScheme.macos.ts +18 -5
- 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/text-controls.macos.tsrx +3 -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,158 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { createSizedImageBinding } from './image-sizing'
|
|
3
|
+
|
|
4
|
+
function mockView(width = 0, height = 0) {
|
|
5
|
+
const handlers = new Map<string, Set<() => void>>()
|
|
6
|
+
const srcWrites: any[] = []
|
|
7
|
+
const view: any = {
|
|
8
|
+
measured: { w: width, h: height },
|
|
9
|
+
stretch: 'aspectFit',
|
|
10
|
+
decodeWidth: undefined as any,
|
|
11
|
+
decodeHeight: undefined as any,
|
|
12
|
+
srcWrites,
|
|
13
|
+
on: (event: string, callback: () => void) => {
|
|
14
|
+
const set = handlers.get(event) ?? new Set()
|
|
15
|
+
set.add(callback)
|
|
16
|
+
handlers.set(event, set)
|
|
17
|
+
},
|
|
18
|
+
off: (event: string, callback: () => void) => handlers.get(event)?.delete(callback),
|
|
19
|
+
getMeasuredWidth() {
|
|
20
|
+
return view.measured.w
|
|
21
|
+
},
|
|
22
|
+
getMeasuredHeight() {
|
|
23
|
+
return view.measured.h
|
|
24
|
+
},
|
|
25
|
+
emit(event: string) {
|
|
26
|
+
for (const callback of handlers.get(event) ?? []) callback()
|
|
27
|
+
},
|
|
28
|
+
handlerCount(event: string) {
|
|
29
|
+
return handlers.get(event)?.size ?? 0
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
let src: any
|
|
33
|
+
Object.defineProperty(view, 'src', {
|
|
34
|
+
get: () => src,
|
|
35
|
+
set: (value) => {
|
|
36
|
+
src = value
|
|
37
|
+
srcWrites.push(value)
|
|
38
|
+
},
|
|
39
|
+
configurable: true,
|
|
40
|
+
})
|
|
41
|
+
return view
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('sized image decode binding', () => {
|
|
45
|
+
it('holds src until the first real layout, then issues decode dims in device px', () => {
|
|
46
|
+
const view = mockView(0, 0)
|
|
47
|
+
const binding = createSizedImageBinding()
|
|
48
|
+
|
|
49
|
+
binding.update(view, 'res://feed/photo.jpg')
|
|
50
|
+
expect(view.src).toBeUndefined()
|
|
51
|
+
expect(view.decodeWidth).toBeUndefined()
|
|
52
|
+
expect(view.handlerCount('layoutChanged')).toBe(1)
|
|
53
|
+
|
|
54
|
+
view.measured = { w: 300, h: 200 }
|
|
55
|
+
view.emit('layoutChanged')
|
|
56
|
+
expect(view.src).toBe('res://feed/photo.jpg')
|
|
57
|
+
expect(view.decodeWidth).toEqual({ value: 300, unit: 'px' })
|
|
58
|
+
expect(view.decodeHeight).toEqual({ value: 200, unit: 'px' })
|
|
59
|
+
expect(view.srcWrites).toHaveLength(1)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('issues immediately when the view is already measured, and repeats are no-ops', () => {
|
|
63
|
+
const view = mockView(300, 200)
|
|
64
|
+
const binding = createSizedImageBinding()
|
|
65
|
+
|
|
66
|
+
binding.update(view, 'https://img.example/x.png')
|
|
67
|
+
expect(view.src).toBe('https://img.example/x.png')
|
|
68
|
+
expect(view.srcWrites).toHaveLength(1)
|
|
69
|
+
|
|
70
|
+
binding.update(view, 'https://img.example/x.png')
|
|
71
|
+
view.emit('layoutChanged')
|
|
72
|
+
expect(view.srcWrites).toHaveLength(1)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('re-issues on a src change even when the size is unchanged', () => {
|
|
76
|
+
const view = mockView(300, 200)
|
|
77
|
+
const binding = createSizedImageBinding()
|
|
78
|
+
|
|
79
|
+
binding.update(view, 'res://a.jpg')
|
|
80
|
+
binding.update(view, 'res://b.jpg')
|
|
81
|
+
expect(view.src).toBe('res://b.jpg')
|
|
82
|
+
expect(view.srcWrites).toEqual(['res://a.jpg', 'res://b.jpg'])
|
|
83
|
+
expect(view.decodeWidth).toEqual({ value: 300, unit: 'px' })
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('re-issues at the new size on resize for aspect-fit stretches', () => {
|
|
87
|
+
const view = mockView(300, 200)
|
|
88
|
+
const binding = createSizedImageBinding()
|
|
89
|
+
|
|
90
|
+
binding.update(view, 'res://a.jpg')
|
|
91
|
+
view.measured = { w: 600, h: 400 }
|
|
92
|
+
view.emit('layoutChanged')
|
|
93
|
+
expect(view.srcWrites).toHaveLength(2)
|
|
94
|
+
expect(view.decodeWidth).toEqual({ value: 600, unit: 'px' })
|
|
95
|
+
expect(view.decodeHeight).toEqual({ value: 400, unit: 'px' })
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('does not reload on resize for fill or none — the expo-image gate', () => {
|
|
99
|
+
for (const stretch of ['fill', 'none']) {
|
|
100
|
+
const view = mockView(300, 200)
|
|
101
|
+
view.stretch = stretch
|
|
102
|
+
const binding = createSizedImageBinding()
|
|
103
|
+
|
|
104
|
+
binding.update(view, 'res://a.jpg')
|
|
105
|
+
view.measured = { w: 600, h: 400 }
|
|
106
|
+
view.emit('layoutChanged')
|
|
107
|
+
expect(view.srcWrites).toHaveLength(1)
|
|
108
|
+
expect(view.decodeWidth).toEqual({
|
|
109
|
+
value: stretch === 'fill' ? 300 : 0,
|
|
110
|
+
unit: 'px',
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('never downsamples for stretch none — source pixels are the contract', () => {
|
|
116
|
+
const view = mockView(300, 200)
|
|
117
|
+
view.stretch = 'none'
|
|
118
|
+
const binding = createSizedImageBinding()
|
|
119
|
+
|
|
120
|
+
binding.update(view, 'res://a.jpg')
|
|
121
|
+
expect(view.src).toBe('res://a.jpg')
|
|
122
|
+
expect(view.decodeWidth).toEqual({ value: 0, unit: 'px' })
|
|
123
|
+
expect(view.decodeHeight).toEqual({ value: 0, unit: 'px' })
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('moves the listener to a recreated view and re-issues at its size', () => {
|
|
127
|
+
const first = mockView(300, 200)
|
|
128
|
+
const second = mockView(0, 0)
|
|
129
|
+
const binding = createSizedImageBinding()
|
|
130
|
+
|
|
131
|
+
binding.update(first, 'res://a.jpg')
|
|
132
|
+
expect(first.src).toBe('res://a.jpg')
|
|
133
|
+
|
|
134
|
+
binding.update(second, 'res://a.jpg')
|
|
135
|
+
expect(first.handlerCount('layoutChanged')).toBe(0)
|
|
136
|
+
expect(second.handlerCount('layoutChanged')).toBe(1)
|
|
137
|
+
expect(second.src).toBeUndefined()
|
|
138
|
+
|
|
139
|
+
second.measured = { w: 100, h: 50 }
|
|
140
|
+
second.emit('layoutChanged')
|
|
141
|
+
expect(second.src).toBe('res://a.jpg')
|
|
142
|
+
expect(second.decodeWidth).toEqual({ value: 100, unit: 'px' })
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('keeps a held src pending until layout even across re-renders', () => {
|
|
146
|
+
const view = mockView(0, 0)
|
|
147
|
+
const binding = createSizedImageBinding()
|
|
148
|
+
|
|
149
|
+
binding.update(view, 'res://a.jpg')
|
|
150
|
+
binding.update(view, 'res://b.jpg')
|
|
151
|
+
expect(view.src).toBeUndefined()
|
|
152
|
+
|
|
153
|
+
view.measured = { w: 50, h: 50 }
|
|
154
|
+
view.emit('layoutChanged')
|
|
155
|
+
expect(view.src).toBe('res://b.jpg')
|
|
156
|
+
expect(view.srcWrites).toEqual(['res://b.jpg'])
|
|
157
|
+
})
|
|
158
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/** View-sized decode for the native <image> leaf.
|
|
2
|
+
*
|
|
3
|
+
* NS Android reads decodeWidth/decodeHeight only when `src` is written —
|
|
4
|
+
* `srcProperty.setNative` → `_createImageSourceFromSrc` →
|
|
5
|
+
* `imageView.setUri(value, decodeWidth, decodeHeight, …)` — and the
|
|
6
|
+
* properties default to 0 → `Fetcher.calculateInSampleSize` maps
|
|
7
|
+
* reqWidth ≤ 0 to the source dims → inSampleSize 1 → a full-resolution
|
|
8
|
+
* decode (~48 MB ARGB_8888 per 12 MP photo; docs/notes/expo-image-study.md
|
|
9
|
+
* L1). So the leaf holds `src` until the view's first real layout, writes
|
|
10
|
+
* the decode dims in device pixels, then assigns src. A later resize
|
|
11
|
+
* re-issues at the new size — the same reload expo-image runs from
|
|
12
|
+
* onSizeChanged — under the same gate: 'fill'/'none' stretches keep the
|
|
13
|
+
* existing decode because a resize can't change the ideal sample there
|
|
14
|
+
* ('none' additionally never downsamples — its contract is source
|
|
15
|
+
* pixels). iOS ignores the decode dims entirely; the deferred assignment
|
|
16
|
+
* is behavior-identical there, so one path serves both platforms.
|
|
17
|
+
*
|
|
18
|
+
* The ref callback re-fires on every render (identity change), so
|
|
19
|
+
* `update` must be idempotent: same view + same src + same size is a
|
|
20
|
+
* no-op. Null ref calls on detach are ignored by the leaf — re-attaching
|
|
21
|
+
* the same view must not lose `applied`, or each render would re-issue
|
|
22
|
+
* setUri. The dead-view listener left behind on real unmount is a
|
|
23
|
+
* self-contained cycle the GC collects with the component record.
|
|
24
|
+
*
|
|
25
|
+
* `afterIssue` runs right after every `src` write — including a same-src
|
|
26
|
+
* resize re-issue mid-load, which fires no `isLoadingChange` (isLoading is
|
|
27
|
+
* already true). The Image placeholder hooks this to re-apply its bitmap
|
|
28
|
+
* after the write has nulled `imageSource` (L5, decision #103). */
|
|
29
|
+
export function createSizedImageBinding(options: { afterIssue?: (view: any) => void } = {}) {
|
|
30
|
+
let view: any = null;
|
|
31
|
+
let listening = false;
|
|
32
|
+
let pendingSrc: any;
|
|
33
|
+
let pendingStretch: string | undefined;
|
|
34
|
+
let appliedSrc: any;
|
|
35
|
+
let applied = false;
|
|
36
|
+
let width = 0;
|
|
37
|
+
let height = 0;
|
|
38
|
+
|
|
39
|
+
const issue = (next: any) => {
|
|
40
|
+
// `pendingStretch` overrides view.stretch for the decode gates when the
|
|
41
|
+
// content-rect path draws stretch='fill' under a different semantic fit
|
|
42
|
+
// ('none' needs the full-resolution source to find the natural size).
|
|
43
|
+
const downscale = (pendingStretch ?? view.stretch) !== 'none';
|
|
44
|
+
view.decodeWidth = { value: downscale ? width : 0, unit: 'px' };
|
|
45
|
+
view.decodeHeight = { value: downscale ? height : 0, unit: 'px' };
|
|
46
|
+
view.src = next;
|
|
47
|
+
appliedSrc = next;
|
|
48
|
+
applied = true;
|
|
49
|
+
options.afterIssue?.(view);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const evaluate = () => {
|
|
53
|
+
if (!view) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const measuredWidth = view.getMeasuredWidth?.() ?? 0;
|
|
58
|
+
const measuredHeight = view.getMeasuredHeight?.() ?? 0;
|
|
59
|
+
// Hold the request until a real layout — writing src sooner decodes
|
|
60
|
+
// at the default 0 dims (full source resolution).
|
|
61
|
+
if (measuredWidth <= 0 || measuredHeight <= 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!applied || pendingSrc !== appliedSrc) {
|
|
66
|
+
width = measuredWidth;
|
|
67
|
+
height = measuredHeight;
|
|
68
|
+
issue(pendingSrc);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (measuredWidth === width && measuredHeight === height) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const stretch = pendingStretch ?? view.stretch;
|
|
77
|
+
if (stretch === 'fill' || stretch === 'none') {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
width = measuredWidth;
|
|
82
|
+
height = measuredHeight;
|
|
83
|
+
issue(appliedSrc);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const onLayout = () => evaluate();
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
/** (Re)bind source → view; issues once the view reports a real layout.
|
|
90
|
+
* `stretchForDecode` overrides view.stretch for the decode/reload
|
|
91
|
+
* gates — see `issue`. */
|
|
92
|
+
update(next: any, source: any, stretchForDecode?: string): void {
|
|
93
|
+
if (next !== view) {
|
|
94
|
+
view?.off?.('layoutChanged', onLayout);
|
|
95
|
+
view = next;
|
|
96
|
+
listening = false;
|
|
97
|
+
applied = false;
|
|
98
|
+
appliedSrc = undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pendingSrc = source;
|
|
102
|
+
pendingStretch = stretchForDecode;
|
|
103
|
+
if (view && !listening && typeof view.on === 'function') {
|
|
104
|
+
view.on('layoutChanged', onLayout);
|
|
105
|
+
listening = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
evaluate();
|
|
109
|
+
},
|
|
110
|
+
dispose(): void {
|
|
111
|
+
view?.off?.('layoutChanged', onLayout);
|
|
112
|
+
view = null;
|
|
113
|
+
listening = false;
|
|
114
|
+
applied = false;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { createObjectContainer, createObjectDriver, createUniversalRoot } from 'octane/universal/native'
|
|
4
|
+
|
|
5
|
+
vi.mock('@nativescript/core', () => ({
|
|
6
|
+
isIOS: false,
|
|
7
|
+
isAndroid: true,
|
|
8
|
+
ScrollView: class {},
|
|
9
|
+
ImageSource: class {
|
|
10
|
+
static fromBase64Sync() {
|
|
11
|
+
return { mocked: true }
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
// The raster path is under test; ./svg resolves to svg.mobile.ts, which
|
|
17
|
+
// registers SVGView from the vendored submodule (needs real NS core).
|
|
18
|
+
vi.mock('./svg', () => ({
|
|
19
|
+
isSvgSrc: () => false,
|
|
20
|
+
svgSource: (s: string) => s,
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
import { Image } from './Image.tsrx'
|
|
24
|
+
|
|
25
|
+
describe('native Image sized decode', () => {
|
|
26
|
+
it('defers src until first layout, issues decode dims in device px, and re-issues per the stretch gate', () => {
|
|
27
|
+
const container = createObjectContainer('nativescript')
|
|
28
|
+
const root = createUniversalRoot(container, createObjectDriver('nativescript'))
|
|
29
|
+
root.render(Image as any, { src: 'res://feed/photo.jpg' })
|
|
30
|
+
const host = container.children[0] as any
|
|
31
|
+
expect(host.type).toBe('image')
|
|
32
|
+
// The leaf owns src imperatively — the driver never saw it.
|
|
33
|
+
expect(host.props.src).toBeUndefined()
|
|
34
|
+
expect(host.src).toBeUndefined()
|
|
35
|
+
|
|
36
|
+
let layoutCb: (() => void) | null = null
|
|
37
|
+
host.on = (event: string, cb: () => void) => {
|
|
38
|
+
if (event === 'layoutChanged') layoutCb = cb
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
host.off = () => {}
|
|
42
|
+
host.getMeasuredWidth = () => 300
|
|
43
|
+
host.getMeasuredHeight = () => 200
|
|
44
|
+
host.stretch = 'aspectFit'
|
|
45
|
+
|
|
46
|
+
root.render(Image as any, { src: 'res://feed/photo.jpg', id: 'rebind' })
|
|
47
|
+
expect(host.src).toBe('res://feed/photo.jpg')
|
|
48
|
+
expect(host.decodeWidth).toEqual({ value: 300, unit: 'px' })
|
|
49
|
+
expect(host.decodeHeight).toEqual({ value: 200, unit: 'px' })
|
|
50
|
+
|
|
51
|
+
host.getMeasuredWidth = () => 600
|
|
52
|
+
layoutCb!()
|
|
53
|
+
expect(host.decodeWidth).toEqual({ value: 600, unit: 'px' })
|
|
54
|
+
expect(host.src).toBe('res://feed/photo.jpg')
|
|
55
|
+
|
|
56
|
+
host.stretch = 'fill'
|
|
57
|
+
host.getMeasuredWidth = () => 900
|
|
58
|
+
layoutCb!()
|
|
59
|
+
expect(host.decodeWidth).toEqual({ value: 600, unit: 'px' })
|
|
60
|
+
|
|
61
|
+
root.render(Image as any, { src: 'res://feed/other.jpg' })
|
|
62
|
+
expect(host.src).toBe('res://feed/other.jpg')
|
|
63
|
+
expect(host.decodeWidth).toEqual({ value: 900, unit: 'px' })
|
|
64
|
+
root.unmount()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('maps decoding to NS loadMode — async by default, sync when requested', () => {
|
|
68
|
+
const container = createObjectContainer('nativescript')
|
|
69
|
+
const root = createUniversalRoot(container, createObjectDriver('nativescript'))
|
|
70
|
+
|
|
71
|
+
root.render(Image as any, { src: 'res://feed/photo.jpg' })
|
|
72
|
+
expect((container.children[0] as any).props.loadMode).toBe('async')
|
|
73
|
+
|
|
74
|
+
root.render(Image as any, { src: 'res://feed/photo.jpg', decoding: 'sync' })
|
|
75
|
+
expect((container.children[0] as any).props.loadMode).toBe('sync')
|
|
76
|
+
|
|
77
|
+
root.render(Image as any, { src: 'res://feed/photo.jpg', decoding: 'async' })
|
|
78
|
+
expect((container.children[0] as any).props.loadMode).toBe('async')
|
|
79
|
+
root.unmount()
|
|
80
|
+
})
|
|
81
|
+
})
|
package/src/index.macos.ts
CHANGED
|
@@ -272,6 +272,7 @@ export { SafeArea } from './layout-shells.macos.tsrx'
|
|
|
272
272
|
export { KeyboardAvoiding } from './KeyboardAvoiding.macos.tsrx'
|
|
273
273
|
export type { KeyboardAvoidingProps } from './props'
|
|
274
274
|
export { useSafeAreaInsets } from './safeAreaInsets.macos.tsrx'
|
|
275
|
+
export type { SafeAreaInsets } from './safeAreaInsets.macos.tsrx'
|
|
275
276
|
export { useMeasure } from './useMeasure.macos.tsrx'
|
|
276
277
|
export { Meter, Heading } from './macos-extras.macos.tsrx'
|
|
277
278
|
export { Icon } from './Icon.macos.tsrx'
|
package/src/index.shared.ts
CHANGED
|
@@ -837,6 +837,13 @@ export type {
|
|
|
837
837
|
DrawerProps,
|
|
838
838
|
GridProps,
|
|
839
839
|
ImageProps,
|
|
840
|
+
ImageContentFit,
|
|
841
|
+
ImageContentPosition,
|
|
842
|
+
ImageContentPositionObject,
|
|
843
|
+
ImageContentPositionString,
|
|
844
|
+
ImageContentPositionValue,
|
|
845
|
+
ImageDecoding,
|
|
846
|
+
ImageSourceLike,
|
|
840
847
|
KeyboardAvoidingProps,
|
|
841
848
|
LayoutChildProps,
|
|
842
849
|
PressableProps,
|
package/src/index.shared.web.ts
CHANGED
|
@@ -838,6 +838,13 @@ export type {
|
|
|
838
838
|
DrawerProps,
|
|
839
839
|
GridProps,
|
|
840
840
|
ImageProps,
|
|
841
|
+
ImageContentFit,
|
|
842
|
+
ImageContentPosition,
|
|
843
|
+
ImageContentPositionObject,
|
|
844
|
+
ImageContentPositionString,
|
|
845
|
+
ImageContentPositionValue,
|
|
846
|
+
ImageDecoding,
|
|
847
|
+
ImageSourceLike,
|
|
841
848
|
KeyboardAvoidingProps,
|
|
842
849
|
LayoutChildProps,
|
|
843
850
|
PressableProps,
|
|
@@ -42,6 +42,7 @@ export function observeListViewability(
|
|
|
42
42
|
const top = offset + Number(insets?.top ?? 0)
|
|
43
43
|
const bottom =
|
|
44
44
|
offset + Number(table.bounds?.size?.height ?? 0) - Number(insets?.bottom ?? 0)
|
|
45
|
+
|
|
45
46
|
const rows: VisibleListRow[] = []
|
|
46
47
|
const paths = Array.from(table.indexPathsForVisibleRows ?? []) as NSIndexPath[]
|
|
47
48
|
for (const path of paths) {
|
package/src/props.ts
CHANGED
|
@@ -390,6 +390,8 @@ export interface RichTextProps extends LayoutChildProps {
|
|
|
390
390
|
style?: any
|
|
391
391
|
children?: any
|
|
392
392
|
id?: string
|
|
393
|
+
/** Optional explicit accessible name override for the text block. */
|
|
394
|
+
accessibilityLabel?: string
|
|
393
395
|
/** Platform-specific properties are applied after shared props. */
|
|
394
396
|
ios?: any
|
|
395
397
|
android?: any
|
|
@@ -1196,12 +1198,105 @@ export interface WebViewProps extends LayoutChildProps, AccessibilityProps {
|
|
|
1196
1198
|
web?: any
|
|
1197
1199
|
}
|
|
1198
1200
|
|
|
1201
|
+
/** A density/size-qualified entry in a multi-resolution `Image.src` array —
|
|
1202
|
+
* the native `srcset` analog. `uri` takes the same grammar as a string
|
|
1203
|
+
* `src`. `width`/`height` declare the source's display size (dips) and
|
|
1204
|
+
* `scale` its density multiplier, so file pixels are `width·scale`: the
|
|
1205
|
+
* selector compares `width·height·scale²` against the view's pixel count
|
|
1206
|
+
* and loads the closest match once a real layout exists. */
|
|
1207
|
+
export interface ImageSourceLike {
|
|
1208
|
+
uri: string
|
|
1209
|
+
width?: number
|
|
1210
|
+
height?: number
|
|
1211
|
+
scale?: number
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/** How the bitmap is sized inside the element's box — the CSS `object-fit`
|
|
1215
|
+
* vocabulary (same contract as expo-image `contentFit`). Default 'cover'. */
|
|
1216
|
+
export type ImageContentFit = 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
|
|
1217
|
+
|
|
1218
|
+
/** One edge value in a `contentPosition`: dips from the named edge, a
|
|
1219
|
+
* percentage of the free space along that axis, or 'center' (= '50%'). */
|
|
1220
|
+
export type ImageContentPositionValue = number | `${number}%` | 'center'
|
|
1221
|
+
|
|
1222
|
+
export interface ImageContentPositionObject {
|
|
1223
|
+
top?: ImageContentPositionValue
|
|
1224
|
+
right?: ImageContentPositionValue
|
|
1225
|
+
bottom?: ImageContentPositionValue
|
|
1226
|
+
left?: ImageContentPositionValue
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
export type ImageContentPositionString =
|
|
1230
|
+
| 'center'
|
|
1231
|
+
| 'top'
|
|
1232
|
+
| 'right'
|
|
1233
|
+
| 'bottom'
|
|
1234
|
+
| 'left'
|
|
1235
|
+
| 'top center'
|
|
1236
|
+
| 'top right'
|
|
1237
|
+
| 'top left'
|
|
1238
|
+
| 'right center'
|
|
1239
|
+
| 'right top'
|
|
1240
|
+
| 'right bottom'
|
|
1241
|
+
| 'bottom center'
|
|
1242
|
+
| 'bottom right'
|
|
1243
|
+
| 'bottom left'
|
|
1244
|
+
| 'left center'
|
|
1245
|
+
| 'left top'
|
|
1246
|
+
| 'left bottom'
|
|
1247
|
+
|
|
1248
|
+
/** Where the drawn bitmap sits inside the element's box — CSS
|
|
1249
|
+
* `object-position`. A keyword string, or an object naming one horizontal
|
|
1250
|
+
* and one vertical edge. Default centered on every platform. */
|
|
1251
|
+
export type ImageContentPosition = ImageContentPositionObject | ImageContentPositionString
|
|
1252
|
+
|
|
1253
|
+
/** When the bitmap is decoded — the HTML `decoding` attribute's vocabulary.
|
|
1254
|
+
* 'async' decodes off the UI thread, 'sync' decodes during the `src`
|
|
1255
|
+
* assignment. */
|
|
1256
|
+
export type ImageDecoding = 'async' | 'sync'
|
|
1257
|
+
|
|
1199
1258
|
export interface ImageProps extends LayoutChildProps {
|
|
1200
1259
|
className?: any
|
|
1201
1260
|
style?: any
|
|
1202
1261
|
id?: string
|
|
1203
|
-
|
|
1262
|
+
/** A single source — `res://name`, `~/bundle-path`, file path, remote
|
|
1263
|
+
* URL, or data:/inline-SVG URI — or a resolution-qualified array
|
|
1264
|
+
* (closest pixel count wins; the `srcset` model). */
|
|
1265
|
+
src: string | ImageSourceLike[]
|
|
1204
1266
|
alt?: string
|
|
1267
|
+
/** Placeholder shown while `src` loads — painted inside the same image
|
|
1268
|
+
* view so the swap can't blank or mismatch fit (expo-image study L5).
|
|
1269
|
+
* Accepts `blurhash:<hash>` / `thumbhash:<base64>` (decoded to a small
|
|
1270
|
+
* PNG in JS) plus any synchronously resolvable `src` grammar (`data:`
|
|
1271
|
+
* base64, `res://name`, `~/`, file path). Remote URLs are ignored on
|
|
1272
|
+
* native — a placeholder that must fetch first is pointless. On web any
|
|
1273
|
+
* `img.src` URL works (e.g. a low-res image). No-op on macOS, where
|
|
1274
|
+
* image loads are synchronous. Ignored for SVG srcs. */
|
|
1275
|
+
placeholder?: string
|
|
1276
|
+
/** Item identity for recycled platform-list cells (RecyclerView/UITableView
|
|
1277
|
+
* rebind the same view). When the key changes between binds, the previous
|
|
1278
|
+
* bitmap is cleared before the new `src` load starts, so the cell never
|
|
1279
|
+
* flashes the prior item's image (expo-image's `recyclingKey`). Web and
|
|
1280
|
+
* macOS leaves ignore it: their lists remount keyed rows rather than
|
|
1281
|
+
* rebinding a reused host. */
|
|
1282
|
+
recyclingKey?: string
|
|
1283
|
+
/** Bitmap sizing inside the box — CSS `object-fit`. Default 'cover' on
|
|
1284
|
+
* every platform (the platform-native defaults disagree: web `img` is
|
|
1285
|
+
* `fill`, NS stretch is `aspectFit`). */
|
|
1286
|
+
contentFit?: ImageContentFit
|
|
1287
|
+
/** Bitmap alignment inside the box — CSS `object-position`. Native
|
|
1288
|
+
* honors it by laying out the image itself (measured path), so
|
|
1289
|
+
* arbitrary positions match web pixel-for-pixel. */
|
|
1290
|
+
contentPosition?: ImageContentPosition
|
|
1291
|
+
/** Decode scheduling hint — the HTML `decoding` attribute. 'async'
|
|
1292
|
+
* (default): the bitmap decodes off the UI thread and may paint a frame
|
|
1293
|
+
* late. 'sync': decoded during the `src` assignment, blocking the UI
|
|
1294
|
+
* thread — for small bitmaps that must appear without a flicker.
|
|
1295
|
+
* Native maps to NS `loadMode` for file/resource/data srcs; remote URLs
|
|
1296
|
+
* always decode async and font-icon srcs are always sync regardless.
|
|
1297
|
+
* Web writes the `decoding` attribute. macOS ignores it — AppKit image
|
|
1298
|
+
* loads are synchronous. */
|
|
1299
|
+
decoding?: ImageDecoding
|
|
1205
1300
|
/** Platform-specific properties are applied after shared props. */
|
|
1206
1301
|
ios?: any
|
|
1207
1302
|
android?: any
|
|
@@ -1262,6 +1357,8 @@ export interface SwitchProps extends FieldControlProps {
|
|
|
1262
1357
|
className?: any
|
|
1263
1358
|
style?: any
|
|
1264
1359
|
id?: string
|
|
1360
|
+
/** Optional explicit accessible name override for the switch. */
|
|
1361
|
+
accessibilityLabel?: string
|
|
1265
1362
|
checked?: boolean
|
|
1266
1363
|
onCheckedChange?: (checked: boolean) => void
|
|
1267
1364
|
/** Platform-specific properties are applied after shared props. */
|
|
@@ -2289,6 +2386,7 @@ export interface LinkProps {
|
|
|
2289
2386
|
href: string
|
|
2290
2387
|
target?: string
|
|
2291
2388
|
className?: any
|
|
2389
|
+
style?: any
|
|
2292
2390
|
children?: any
|
|
2293
2391
|
}
|
|
2294
2392
|
|
|
@@ -2296,6 +2394,7 @@ export interface NavLinkProps {
|
|
|
2296
2394
|
route: Route
|
|
2297
2395
|
activeClassName?: string
|
|
2298
2396
|
className?: any
|
|
2397
|
+
style?: any
|
|
2299
2398
|
children?: any
|
|
2300
2399
|
}
|
|
2301
2400
|
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
export interface SafeAreaInsets {
|
|
2
|
+
top: number
|
|
3
|
+
right: number
|
|
4
|
+
bottom: number
|
|
5
|
+
left: number
|
|
6
|
+
}
|
|
2
7
|
|
|
3
8
|
/** The AppKit root view begins below the title bar; there are no additional
|
|
4
9
|
* safe-area insets in this window-hosted renderer. */
|
package/src/styled.macos.tsrx
CHANGED
|
@@ -5,7 +5,7 @@ import { cx } from './cx'
|
|
|
5
5
|
export function styled<P extends { className?: any }, V extends Record<string, any>>(
|
|
6
6
|
Base: (props: P) => any,
|
|
7
7
|
def: { base?: any; variants?: V },
|
|
8
|
-
) {
|
|
8
|
+
): (props: P & { [K in keyof V]?: boolean }, context: any) => any {
|
|
9
9
|
function Styled(props: P & { [K in keyof V]?: boolean }) {
|
|
10
10
|
const classes: any[] = [def.base]
|
|
11
11
|
const rest: any = { ...props }
|
|
@@ -18,7 +18,9 @@ export function TextInput(inputProps: TextInputProps) @{
|
|
|
18
18
|
style={props.style}
|
|
19
19
|
isEnabled={!props.isDisabled}
|
|
20
20
|
editable={!isBlocked}
|
|
21
|
+
secure={props.secure}
|
|
21
22
|
onTextChange={props.onChange}
|
|
23
|
+
onSubmit={props.onSubmit}
|
|
22
24
|
accessibilityLabel={props.accessibilityLabel ?? props.ariaLabel ?? props.fieldLabel ?? props.label}
|
|
23
25
|
accessibilityHint={props.accessibilityHint}
|
|
24
26
|
accessibilityState={{ disabled: props.isDisabled, readonly: props.isReadOnly, busy: props.isLoading, required: props.isRequired && !props.isOptional, invalid: props.status?.type === 'error' }}
|
|
@@ -33,5 +35,5 @@ export function TextArea(inputProps: TextAreaProps) @{
|
|
|
33
35
|
const props = useFieldControlProps(inputProps)
|
|
34
36
|
const isBlocked = props.isDisabled || props.isReadOnly
|
|
35
37
|
|
|
36
|
-
<textview id={props.id} value={props.value} placeholder={props.placeholder} rows={props.autoGrow ? undefined : props.rows ?? 3} className={cx('vx-textarea', props.isDisabled && 'vx-textarea--disabled', props.isReadOnly && 'vx-textarea--readonly', props.isLoading && 'vx-textarea--loading', props.status && `vx-textarea--status-${props.status.type}`, props.size && `vx-textarea--size-${props.size}`, props.className)} style={props.style} isEnabled={!props.isDisabled} editable={!isBlocked} onTextChange={props.onChange} accessibilityLabel={props.accessibilityLabel ?? props.ariaLabel ?? props.fieldLabel ?? props.label} accessibilityHint={props.accessibilityHint} accessibilityState={{ disabled: props.isDisabled, readonly: props.isReadOnly, busy: props.isLoading, required: props.isRequired && !props.isOptional, invalid: props.status?.type === 'error' }} />
|
|
38
|
+
<textview id={props.id} value={props.value} placeholder={props.placeholder} rows={props.autoGrow ? undefined : props.rows ?? 3} className={cx('vx-textarea', props.isDisabled && 'vx-textarea--disabled', props.isReadOnly && 'vx-textarea--readonly', props.isLoading && 'vx-textarea--loading', props.status && `vx-textarea--status-${props.status.type}`, props.size && `vx-textarea--size-${props.size}`, props.className)} style={props.style} isEnabled={!props.isDisabled} editable={!isBlocked} secure={props.secure} onTextChange={props.onChange} onSubmit={props.onSubmit} accessibilityLabel={props.accessibilityLabel ?? props.ariaLabel ?? props.fieldLabel ?? props.label} accessibilityHint={props.accessibilityHint} accessibilityState={{ disabled: props.isDisabled, readonly: props.isReadOnly, busy: props.isLoading, required: props.isRequired && !props.isOptional, invalid: props.status?.type === 'error' }} />
|
|
37
39
|
}
|
|
@@ -55,7 +55,11 @@ class SVGExternalFileResolver extends com.caverock.androidsvg.SVGExternalFileRes
|
|
|
55
55
|
if (fontFamily) {
|
|
56
56
|
fontFamily = fontFamily.replace(/\\\//, '/')
|
|
57
57
|
}
|
|
58
|
-
return
|
|
58
|
+
return Font.default
|
|
59
|
+
.withFontFamily(fontFamily)
|
|
60
|
+
.withFontStyle(fontStyle.toLowerCase() as any)
|
|
61
|
+
.withFontWeight((fontWeight + '') as any)
|
|
62
|
+
.getAndroidTypeface()
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
override resolveImage(filename: string): globalAndroid.graphics.Bitmap {
|