@octane-xplat/ui 0.7.2 → 0.9.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/README.md +4 -3
- package/dist/native/Button.tsrx.js +1 -1
- package/dist/native/CommandPalette.tsrx.js +7 -1
- package/dist/native/Grid.tsrx.js +31 -2
- package/dist/native/KeyboardAvoiding.mobile.tsrx.js +2 -2
- package/dist/native/Markdown.tsrx.js +233 -0
- package/dist/native/Overlay.tsrx.js +15 -26
- package/dist/native/Pagination.tsrx.js +15 -12
- package/dist/native/PinInput.tsrx.js +25 -7
- package/dist/native/Popover.tsrx.js +33 -23
- package/dist/native/Pressable.tsrx.js +4 -1
- package/dist/native/Screen.tsrx.js +1 -1
- package/dist/native/SearchInput.tsrx.js +6 -3
- package/dist/native/Sheet.tsrx.js +23 -38
- package/dist/native/Slider.tsrx.js +23 -14
- package/dist/native/Text.tsrx.js +16 -0
- package/dist/native/TextArea.tsrx.js +6 -3
- package/dist/native/TextInput.tsrx.js +6 -3
- package/dist/native/VirtualList.tsrx.js +305 -121
- package/dist/native/WebView.tsrx.js +17 -2
- package/dist/native/_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js +9 -0
- package/dist/native/escape-props.js +2 -0
- package/dist/native/grid-auto-placement.js +83 -0
- package/dist/native/grid-placement.js +20 -0
- package/dist/native/index.shared.js +7 -5
- package/dist/native/keyboard-inset.mobile.js +5 -5
- package/dist/native/navigation-request.js +26 -0
- package/dist/native/octane-client-build.json +1 -1
- package/dist/native/overlay-lifetime.mobile.js +65 -0
- package/dist/native/pan.tsrx.js +16 -2
- package/dist/native/route-table.js +90 -10
- package/dist/native/route.js +26 -16
- package/dist/native/shared.js +7 -5
- package/dist/native/sheet-service.js +2 -2
- package/dist/native/svg.mobile.js +1 -1
- package/dist/native/{tap-to-blur.mobile.js → tap-to-blur.ios.js} +20 -32
- package/dist/native/text-focus.js +10 -0
- package/dist/native/text-write.js +18 -11
- package/dist/native/toast-anchor.tsrx.js +1 -1
- package/dist/native/ui.js +7 -6
- package/dist/native/useMeasure.tsrx.js +17 -18
- package/dist/native/vendor/ui-svg/index.android.js +2 -2
- package/dist/native/vendor/ui-svg/index.ios.js +2 -2
- package/dist/native/vendor/ui-svg/{index.common.js → src/index.common.js} +1 -1
- package/dist/native/virtual-list-cell-pool.js +63 -0
- package/dist/web/Button.web.tsrx.js +1 -1
- package/dist/web/KeyboardAvoiding.web.tsrx.js +78 -0
- package/dist/web/Markdown.tsrx.js +98 -0
- package/dist/web/Overlay.web.tsrx.js +81 -32
- package/dist/web/PinInput.web.tsrx.js +24 -10
- package/dist/web/Pressable.web.tsrx.js +169 -53
- package/dist/web/SearchInput.web.tsrx.js +2 -2
- package/dist/web/Sheet.web.tsrx.js +20 -7
- package/dist/web/TextArea.web.tsrx.js +22 -17
- package/dist/web/TextInput.web.tsrx.js +2 -2
- package/dist/web/VirtualList.web.tsrx.js +220 -109
- package/dist/web/WebView.web.tsrx.js +3 -3
- package/dist/web/{index.shared.js → index.shared.web.js} +7 -5
- package/dist/web/modal-focus.web.js +95 -0
- package/dist/web/navigation-request.js +26 -0
- package/dist/web/octane-client-build.json +1 -1
- package/dist/web/pan.web.tsrx.js +27 -10
- package/dist/web/route-table.js +90 -10
- package/dist/web/route.web.js +88 -22
- package/dist/web/sheet-service.web.js +8 -0
- package/dist/web/theme/colorScheme.web.js +31 -4
- package/dist/web/ui.js +8 -6
- package/dist/web/virtual-list-cell-pool.js +63 -0
- package/package.json +21 -19
- package/platforms/android/native-api-usage.json +7 -7
- package/src/CommandPalette.tsrx +8 -1
- package/src/Grid.tsrx +32 -1
- package/src/Icon.macos.tsrx +1 -0
- package/src/KeyboardAvoiding.macos.tsrx +7 -1
- package/src/KeyboardAvoiding.mobile.tsrx +2 -2
- package/src/KeyboardAvoiding.tsrx +15 -0
- package/src/KeyboardAvoiding.web.tsrx +15 -0
- package/src/Markdown.tsrx +121 -0
- package/src/Meter.tsrx +1 -0
- package/src/Overlay.tsrx +15 -30
- package/src/Overlay.web.tsrx +12 -3
- package/src/Pagination.tsrx +6 -4
- package/src/PinInput.macos.tsrx +20 -6
- package/src/PinInput.mobile.test.ts +107 -0
- package/src/PinInput.tsrx +20 -6
- package/src/PinInput.web.test.tsrx +42 -0
- package/src/PinInput.web.tsrx +20 -6
- package/src/Popover.tsrx +36 -22
- package/src/Pressable.tsrx +1 -1
- package/src/Pressable.web.test.tsrx +36 -0
- package/src/Pressable.web.tsrx +37 -6
- package/src/RouteHost.macos.tsrx +1 -0
- package/src/Screen.tsrx +1 -1
- package/src/SearchInput.tsrx +4 -3
- package/src/SearchInput.web.tsrx +1 -1
- package/src/Sheet.tsrx +21 -42
- package/src/Sheet.web.tsrx +9 -1
- package/src/Slider.tsrx +13 -11
- package/src/Text.tsrx +36 -0
- package/src/TextArea.tsrx +6 -5
- package/src/TextArea.web.test.tsrx +39 -0
- package/src/TextArea.web.tsrx +7 -5
- package/src/TextInput.tsrx +6 -6
- package/src/TextInput.web.tsrx +1 -1
- package/src/VirtualList.macos.tsrx +93 -6
- package/src/VirtualList.mobile.test.tsrx +232 -0
- package/src/VirtualList.tsrx +285 -81
- package/src/VirtualList.web.tsrx +131 -25
- package/src/WebView.tsrx +20 -10
- package/src/WebView.web.tsrx +3 -0
- package/src/android/BottomNavigationView.android.tsrx +1 -0
- package/src/android/MaterialDialog.android.tsrx +1 -0
- package/src/anim.macos.tsrx +1 -0
- package/src/apply-native-modifiers.native.ts +4 -1
- package/src/child-array.ts +12 -7
- package/src/colorScheme.macos.ts +1 -0
- package/src/controls.macos.tsrx +4 -1
- package/src/data-driver.mobile.test.ts +120 -0
- package/src/data-lifecycle.mobile.test.ts +251 -0
- package/src/escape-props.ts +13 -1
- package/src/grid-auto-placement.ts +163 -0
- package/src/grid-placement.ts +29 -0
- package/src/hosted-unsupported.macos.tsrx +1 -0
- package/src/index.macos.ts +23 -3
- package/src/index.mobile.ts +0 -1
- package/src/index.shared.ts +22 -1
- package/src/index.shared.web.ts +291 -0
- package/src/index.web.ts +1 -1
- package/src/input-readiness.web.test.tsrx +82 -0
- package/src/ios/UITabBar.ios.tsrx +1 -0
- package/src/keyboard-inset.mobile.test.ts +62 -0
- package/src/keyboard-inset.mobile.ts +63 -43
- package/src/layout-shells.macos.tsrx +1 -5
- package/src/macos-extras.macos.tsrx +2 -0
- package/src/modal-focus.web.test.tsrx +63 -0
- package/src/modal-focus.web.ts +162 -0
- package/src/navigation-request.ts +37 -0
- package/src/overlay-lifecycle.mobile.test.ts +316 -0
- package/src/overlay-lifetime.mobile.ts +107 -0
- package/src/pan.mobile.test.tsrx +116 -0
- package/src/pan.tsrx +20 -2
- package/src/pan.web.tsrx +35 -7
- package/src/platformProps.ts +1 -0
- package/src/popover.mobile.test.ts +9 -1
- package/src/pressable-accessibility.mobile.test.ts +30 -0
- package/src/props.ts +163 -3
- package/src/refresh.ts +6 -1
- package/src/refresh.web.test.ts +77 -77
- package/src/route-android.test.ts +162 -0
- package/src/route-history.web.test.ts +80 -0
- package/src/route-navigation.web.test.ts +173 -0
- package/src/route-table.test.ts +188 -9
- package/src/route-table.ts +138 -10
- package/src/route.macos.ts +56 -13
- package/src/route.ts +54 -24
- package/src/route.web.ts +141 -24
- package/src/sheet-detents.ts +5 -3
- package/src/sheet-service.macos.ts +5 -1
- package/src/sheet-service.ts +1 -1
- package/src/sheet-service.web.ts +8 -0
- package/src/sheet-snap.test.ts +23 -23
- package/src/sheet-snap.ts +6 -1
- package/src/svg.mobile.ts +9 -3
- package/src/tap-to-blur.ios.ts +106 -0
- package/src/tap-to-blur.mobile.ts +21 -95
- package/src/text-focus.ts +12 -0
- package/src/text-input.mobile.test.ts +89 -0
- package/src/text-write.ts +21 -11
- package/src/theme/colorScheme.linux.ts +2 -4
- package/src/theme/colorScheme.web.ts +51 -4
- package/src/theme/theme-scheme.macos.ts +8 -2
- package/src/theme/tokens.css +1740 -1718
- package/src/translate.macos.ts +4 -1
- package/src/useMeasure.tsrx +20 -21
- package/src/virtual-list-cell-pool.test.ts +53 -0
- package/src/virtual-list-cell-pool.ts +81 -0
- package/src/virtual-list-layout.test.ts +11 -8
- package/src/virtual-list-layout.ts +4 -8
- package/src/windows.linux.ts +7 -2
- package/src/windows.macos.ts +1 -0
- package/types/generated/.tsrx-typegen-manifest.json +16 -3
- package/types/generated/Grid.d.ts +0 -1
- package/types/generated/KeyboardAvoiding.d.ts +3 -0
- package/types/generated/KeyboardAvoiding.web.d.ts +2 -0
- package/types/generated/Markdown.d.ts +59 -0
- package/types/generated/TextArea.d.ts +2 -2
- package/types/generated/TextArea.web.d.ts +1 -1
- package/types/generated/TextInput.d.ts +2 -3
- package/types/generated/VirtualList.d.ts +3 -3
- package/types/generated/escape-props.d.ts +4 -0
- package/types/generated/grid-auto-placement.d.ts +13 -0
- package/types/generated/grid-placement.d.ts +5 -0
- package/types/generated/index.mobile.d.ts +0 -1
- package/types/generated/index.shared.d.ts +6 -3
- package/types/generated/index.shared.web.d.ts +114 -0
- package/types/generated/index.web.d.ts +1 -1
- package/types/generated/modal-focus.web.d.ts +3 -0
- package/types/generated/navigation-request.d.ts +10 -0
- package/types/generated/overlay-lifetime.mobile.d.ts +10 -0
- package/types/generated/props.d.ts +111 -3
- package/types/generated/route-table.d.ts +14 -3
- package/types/generated/svg.mobile.d.ts +2 -2
- package/types/generated/tap-to-blur.ios.d.ts +13 -0
- package/types/generated/tap-to-blur.mobile.d.ts +1 -15
- package/types/generated/text-focus.d.ts +3 -0
- package/types/generated/text-write.d.ts +3 -1
- package/types/generated/theme/colorScheme.web.d.ts +5 -0
- package/types/generated/virtual-list-cell-pool.d.ts +18 -0
- package/types/index.macos.d.ts +15 -3
- /package/src/vendor/ui-svg/{index.android.ts → src/index.android.ts} +0 -0
- /package/src/vendor/ui-svg/{index.common.ts → src/index.common.ts} +0 -0
- /package/src/vendor/ui-svg/{index.ios.ts → src/index.ios.ts} +0 -0
- /package/src/vendor/ui-svg/{index.ts → src/index.ts} +0 -0
- /package/src/vendor/ui-svg/{typings → src/typings}/SVGKit.d.ts +0 -0
- /package/src/vendor/ui-svg/{typings → src/typings}/android.d.ts +0 -0
- /package/src/vendor/ui-svg/{typings → src/typings}/ios.d.ts +0 -0
- /package/types/generated/vendor/ui-svg/{index.common.d.ts → src/index.common.d.ts} +0 -0
- /package/types/generated/vendor/ui-svg/{index.d.ts → src/index.d.ts} +0 -0
- /package/types/generated/vendor/ui-svg/{index.ios.d.ts → src/index.ios.d.ts} +0 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Profile,
|
|
3
|
+
SuspenseProfile,
|
|
4
|
+
NestedProfile,
|
|
5
|
+
Retained,
|
|
6
|
+
Reader,
|
|
7
|
+
sharedSelection$,
|
|
8
|
+
sharedCalls,
|
|
9
|
+
} from '../tests/data-lifecycle.fixture.mobile.tsrx'
|
|
10
|
+
|
|
11
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
12
|
+
import {
|
|
13
|
+
createObjectContainer,
|
|
14
|
+
createObjectDriver,
|
|
15
|
+
createUniversalRoot,
|
|
16
|
+
flushUniversalSync,
|
|
17
|
+
} from 'octane/universal/native'
|
|
18
|
+
|
|
19
|
+
import { type QueryContext } from 'octane/signals'
|
|
20
|
+
|
|
21
|
+
type Request = {
|
|
22
|
+
id: string
|
|
23
|
+
signal: AbortSignal
|
|
24
|
+
resolve: (value: string) => void
|
|
25
|
+
reject: (error: Error) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function requests() {
|
|
29
|
+
const calls: Request[] = []
|
|
30
|
+
const load = (id: string, { signal }: QueryContext<string>) =>
|
|
31
|
+
new Promise<string>((resolve, reject) => {
|
|
32
|
+
calls.push({ id, signal, resolve, reject })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return { calls, load }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const roots: ReturnType<typeof createUniversalRoot>[] = []
|
|
39
|
+
function mount(Component: any, props: any) {
|
|
40
|
+
const container = createObjectContainer('nativescript')
|
|
41
|
+
const root = createUniversalRoot(container, createObjectDriver('nativescript'))
|
|
42
|
+
roots.push(root)
|
|
43
|
+
root.render(Component, props)
|
|
44
|
+
return {
|
|
45
|
+
root,
|
|
46
|
+
container,
|
|
47
|
+
text: () =>
|
|
48
|
+
container.children
|
|
49
|
+
.filter((node: any) => node.type === 'label')
|
|
50
|
+
.map((node: any) => node.props.text),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function settle() {
|
|
55
|
+
for (let i = 0; i < 12; i++) {
|
|
56
|
+
await Promise.resolve()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
flushUniversalSync(() => {})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
for (const root of roots.splice(0)) {
|
|
64
|
+
root.unmount()
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('compiler-owned query lifecycle on universal roots', () => {
|
|
69
|
+
it('suspends on first data, catches a query error, and retries the boundary', async () => {
|
|
70
|
+
const { calls, load } = requests()
|
|
71
|
+
const view = mount(SuspenseProfile, { load })
|
|
72
|
+
expect(view.text()).toEqual(['pending'])
|
|
73
|
+
calls[0].reject(new Error('offline'))
|
|
74
|
+
await settle()
|
|
75
|
+
expect(view.container.children[0].props.text).toBe('offline')
|
|
76
|
+
flushUniversalSync(() => view.container.dispatchEvent(view.container.children[0], 'tap', {}))
|
|
77
|
+
await settle()
|
|
78
|
+
expect(view.text()).toEqual(['pending'])
|
|
79
|
+
expect(calls).toHaveLength(2)
|
|
80
|
+
calls[1].resolve('recovered')
|
|
81
|
+
await settle()
|
|
82
|
+
expect(view.text()).toEqual(['recovered'])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('shares the declaring component query with a child reader', async () => {
|
|
86
|
+
const { calls, load } = requests()
|
|
87
|
+
const view = mount(NestedProfile, { load })
|
|
88
|
+
expect(calls).toHaveLength(1)
|
|
89
|
+
calls[0].resolve('nested result')
|
|
90
|
+
await settle()
|
|
91
|
+
expect(view.text()).toEqual(['ready', 'nested result'])
|
|
92
|
+
view.root.unmount()
|
|
93
|
+
expect(view.container.children).toEqual([])
|
|
94
|
+
const pendingView = mount(NestedProfile, { load })
|
|
95
|
+
expect(calls).toHaveLength(2)
|
|
96
|
+
pendingView.root.unmount()
|
|
97
|
+
expect(calls[1].signal.aborted).toBe(true)
|
|
98
|
+
calls[1].resolve('after child reader unmounted')
|
|
99
|
+
await settle()
|
|
100
|
+
expect(pendingView.container.children).toEqual([])
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('isolates stacked instances, retires requests, and ignores late responses', async () => {
|
|
104
|
+
const { calls, load } = requests()
|
|
105
|
+
const first = mount(Profile, { id: 'first', load })
|
|
106
|
+
const second = mount(Profile, { id: 'second', load })
|
|
107
|
+
expect(calls.map((call) => call.id)).toEqual(['first', 'second'])
|
|
108
|
+
calls[0].resolve('one')
|
|
109
|
+
calls[1].resolve('two')
|
|
110
|
+
await settle()
|
|
111
|
+
expect(first.text()).toEqual(['first:ready:one:false'])
|
|
112
|
+
expect(second.text()).toEqual(['second:ready:two:false'])
|
|
113
|
+
second.root.render(Profile, { id: 'second', nextId: 'third', load })
|
|
114
|
+
flushUniversalSync(() =>
|
|
115
|
+
second.container.dispatchEvent(second.container.children[4], 'tap', {}),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
await settle()
|
|
119
|
+
expect(calls.map((call) => call.id)).toEqual(['first', 'second', 'third'])
|
|
120
|
+
second.root.render(Profile, { id: 'second', nextId: 'fourth', load })
|
|
121
|
+
flushUniversalSync(() =>
|
|
122
|
+
second.container.dispatchEvent(second.container.children[4], 'tap', {}),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
await settle()
|
|
126
|
+
expect(calls[2].signal.aborted).toBe(true)
|
|
127
|
+
calls[3].resolve('four')
|
|
128
|
+
await settle()
|
|
129
|
+
calls[2].resolve('late third')
|
|
130
|
+
await settle()
|
|
131
|
+
expect(second.text()).toEqual(['fourth:ready:four:false'])
|
|
132
|
+
expect(first.text()).toEqual(['first:ready:one:false'])
|
|
133
|
+
second.root.render(Profile, { id: 'second', nextId: 'fifth', load })
|
|
134
|
+
flushUniversalSync(() =>
|
|
135
|
+
second.container.dispatchEvent(second.container.children[4], 'tap', {}),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
await settle()
|
|
139
|
+
second.root.unmount()
|
|
140
|
+
expect(calls[4].signal.aborted).toBe(true)
|
|
141
|
+
calls[4].resolve('after unmount')
|
|
142
|
+
await settle()
|
|
143
|
+
expect(second.container.children).toEqual([])
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('reports pending, error, retry, refetch, and reset separately', async () => {
|
|
147
|
+
const { calls, load } = requests()
|
|
148
|
+
const view = mount(Profile, { id: 'a', load })
|
|
149
|
+
expect(view.text()).toEqual(['a:pending:none:false'])
|
|
150
|
+
calls[0].reject(new Error('offline'))
|
|
151
|
+
await settle()
|
|
152
|
+
expect(view.text()).toEqual(['a:error:none:false'])
|
|
153
|
+
flushUniversalSync(() => view.container.dispatchEvent(view.container.children[1], 'tap', {}))
|
|
154
|
+
await settle()
|
|
155
|
+
expect(view.text()).toEqual(['a:pending:none:false'])
|
|
156
|
+
calls[1].resolve('one')
|
|
157
|
+
await settle()
|
|
158
|
+
flushUniversalSync(() => view.container.dispatchEvent(view.container.children[2], 'tap', {}))
|
|
159
|
+
await settle()
|
|
160
|
+
expect(view.text()).toEqual(['a:ready:one:true'])
|
|
161
|
+
calls[2].resolve('two')
|
|
162
|
+
await settle()
|
|
163
|
+
flushUniversalSync(() => view.container.dispatchEvent(view.container.children[3], 'tap', {}))
|
|
164
|
+
await settle()
|
|
165
|
+
expect(view.text()).toEqual(['a:pending:two:false'])
|
|
166
|
+
calls[3].resolve('three')
|
|
167
|
+
await settle()
|
|
168
|
+
expect(view.text()).toEqual(['a:ready:three:false'])
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('updates a module signal across roots and disconnects unmounted readers', async () => {
|
|
172
|
+
const selection$ = sharedSelection$
|
|
173
|
+
const calls = sharedCalls
|
|
174
|
+
selection$.set('a')
|
|
175
|
+
let renders = 0
|
|
176
|
+
const props = {
|
|
177
|
+
onRender: () => {
|
|
178
|
+
renders++
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const page = mount(Reader, props)
|
|
183
|
+
const sheet = mount(Reader, props)
|
|
184
|
+
const modal = mount(Reader, props)
|
|
185
|
+
expect(calls).toHaveLength(1)
|
|
186
|
+
calls[0].resolve('one')
|
|
187
|
+
await settle()
|
|
188
|
+
for (const view of [page, sheet, modal]) {
|
|
189
|
+
expect(view.text()).toEqual(['a:ready:one'])
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
flushUniversalSync(() => selection$.set('b'))
|
|
193
|
+
await settle()
|
|
194
|
+
expect(calls).toHaveLength(2)
|
|
195
|
+
calls[1].resolve('two')
|
|
196
|
+
await settle()
|
|
197
|
+
for (const view of [page, sheet, modal]) {
|
|
198
|
+
expect(view.text()).toEqual(['b:ready:two'])
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const view of [page, sheet, modal]) {
|
|
202
|
+
view.root.unmount()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const before = renders
|
|
206
|
+
flushUniversalSync(() => selection$.set(''))
|
|
207
|
+
await settle()
|
|
208
|
+
expect(renders).toBe(before)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('delivers a tap that re-suspends, hides retained hosts, and reports handler errors', async () => {
|
|
212
|
+
let resolve!: (value: string) => void
|
|
213
|
+
const pending = new Promise<string>((done) => {
|
|
214
|
+
resolve = done
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const error = new Error('handler failure')
|
|
218
|
+
const fail = vi.fn(() => {
|
|
219
|
+
throw error
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
const uncaught = vi.fn()
|
|
223
|
+
const container = createObjectContainer('nativescript')
|
|
224
|
+
const root = createUniversalRoot(container, createObjectDriver('nativescript'), {
|
|
225
|
+
onUncaughtError: uncaught,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
roots.push(root)
|
|
229
|
+
root.render(Retained, { pending, fail })
|
|
230
|
+
const body = container.children[0]
|
|
231
|
+
expect(body.props.text).toBe('ready')
|
|
232
|
+
flushUniversalSync(() => container.dispatchEvent(body, 'tap', {}))
|
|
233
|
+
await settle()
|
|
234
|
+
expect(container.children[0]).toBe(body)
|
|
235
|
+
expect(body.visible).toBe(false)
|
|
236
|
+
expect(container.children[1]).toMatchObject({ props: { text: 'pending' }, visible: true })
|
|
237
|
+
expect(container.commits.flatMap((batch: any) => batch.commands ?? batch)).toEqual(
|
|
238
|
+
expect.arrayContaining([expect.objectContaining({ op: 'visibility', state: 'hidden' })]),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
resolve('resolved')
|
|
242
|
+
await settle()
|
|
243
|
+
expect(container.children[0]).toBe(body)
|
|
244
|
+
expect(body.visible).toBe(true)
|
|
245
|
+
expect(body.props.text).toBe('resolved')
|
|
246
|
+
container.dispatchEvent(container.children[1], 'tap', {})
|
|
247
|
+
await settle()
|
|
248
|
+
expect(fail).toHaveBeenCalledOnce()
|
|
249
|
+
expect(uncaught).toHaveBeenCalledWith(error)
|
|
250
|
+
})
|
|
251
|
+
})
|
package/src/escape-props.ts
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
import { isAndroid, isIOS } from '@nativescript/core'
|
|
2
2
|
import type { NativeModifier, Role } from './props'
|
|
3
3
|
import { applyNativeModifiers } from './apply-native-modifiers.native'
|
|
4
|
+
import { rememberGridChildPlacement } from './grid-placement'
|
|
4
5
|
|
|
5
6
|
/** Apply only the active device's escape bag after the primitive's own props. */
|
|
6
7
|
export function applyEscapeProps(
|
|
7
8
|
view: any,
|
|
8
|
-
props: {
|
|
9
|
+
props: {
|
|
10
|
+
ios?: any
|
|
11
|
+
android?: any
|
|
12
|
+
modifiers?: readonly NativeModifier[]
|
|
13
|
+
row?: number
|
|
14
|
+
col?: number
|
|
15
|
+
rowSpan?: number
|
|
16
|
+
colSpan?: number
|
|
17
|
+
},
|
|
9
18
|
): void {
|
|
10
19
|
if (!view) {
|
|
11
20
|
return
|
|
12
21
|
}
|
|
13
22
|
|
|
23
|
+
rememberGridChildPlacement(view, props)
|
|
24
|
+
|
|
14
25
|
const bag = isIOS ? props.ios : isAndroid ? props.android : undefined
|
|
15
26
|
if (bag) {
|
|
16
27
|
Object.assign(view, bag)
|
|
17
28
|
}
|
|
29
|
+
|
|
18
30
|
applyNativeModifiers(view, props.modifiers)
|
|
19
31
|
}
|
|
20
32
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { GridChildPlacement } from './grid-placement'
|
|
2
|
+
|
|
3
|
+
export interface GridCell {
|
|
4
|
+
row: number
|
|
5
|
+
col: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface GridAutoPlacement {
|
|
9
|
+
cells: GridCell[]
|
|
10
|
+
rowCount: number
|
|
11
|
+
columnCount: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function gridIndex(value: unknown): number | null {
|
|
15
|
+
if (value == null || value === '') {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const index = Number(value)
|
|
20
|
+
return Number.isInteger(index) && index >= 0 ? index : null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function gridSpan(value: unknown): number {
|
|
24
|
+
const span = Number(value)
|
|
25
|
+
return Number.isInteger(span) && span > 0 ? span : 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function trackCount(spec?: string): number {
|
|
29
|
+
return spec?.split(',').filter((track) => track.trim().length > 0).length ?? 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Match CSS Grid's row-flow placement for NativeScript GridLayout children. */
|
|
33
|
+
export function autoPlaceGridChildren(
|
|
34
|
+
items: readonly GridChildPlacement[],
|
|
35
|
+
rowTracks?: string,
|
|
36
|
+
columnTracks?: string,
|
|
37
|
+
): GridAutoPlacement {
|
|
38
|
+
const placements = items.map((item) => ({
|
|
39
|
+
row: gridIndex(item.row),
|
|
40
|
+
col: gridIndex(item.col),
|
|
41
|
+
rowSpan: gridSpan(item.rowSpan),
|
|
42
|
+
colSpan: gridSpan(item.colSpan),
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
const columnCount = Math.max(
|
|
46
|
+
1,
|
|
47
|
+
trackCount(columnTracks),
|
|
48
|
+
...placements
|
|
49
|
+
.filter((placement) => placement.col != null)
|
|
50
|
+
.map((placement) => placement.col! + placement.colSpan),
|
|
51
|
+
...placements.map((placement) => placement.colSpan),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const occupied = new Set<string>()
|
|
55
|
+
const isFree = (row: number, col: number, rowSpan: number, colSpan: number) => {
|
|
56
|
+
for (let y = row; y < row + rowSpan; y++) {
|
|
57
|
+
for (let x = col; x < col + colSpan; x++) {
|
|
58
|
+
if (occupied.has(`${y},${x}`)) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const occupy = (row: number, col: number, rowSpan: number, colSpan: number) => {
|
|
68
|
+
for (let y = row; y < row + rowSpan; y++) {
|
|
69
|
+
for (let x = col; x < col + colSpan; x++) {
|
|
70
|
+
occupied.add(`${y},${x}`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Definite placements reserve their cells before automatic items flow.
|
|
76
|
+
for (const placement of placements) {
|
|
77
|
+
if (placement.row != null && placement.col != null) {
|
|
78
|
+
occupy(placement.row, placement.col, placement.rowSpan, placement.colSpan)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const placement of placements) {
|
|
83
|
+
if (placement.row == null || placement.col != null) {
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let col = 0
|
|
88
|
+
while (!isFree(placement.row, col, placement.rowSpan, placement.colSpan)) {
|
|
89
|
+
col++
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
placement.col = col
|
|
93
|
+
occupy(placement.row, col, placement.rowSpan, placement.colSpan)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const placement of placements) {
|
|
97
|
+
if (placement.col == null || placement.row != null) {
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let row = 0
|
|
102
|
+
while (!isFree(row, placement.col, placement.rowSpan, placement.colSpan)) {
|
|
103
|
+
row++
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
placement.row = row
|
|
107
|
+
occupy(row, placement.col, placement.rowSpan, placement.colSpan)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let cursorRow = 0
|
|
111
|
+
let cursorCol = 0
|
|
112
|
+
for (const placement of placements) {
|
|
113
|
+
if (placement.row != null && placement.col != null) {
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
while (true) {
|
|
118
|
+
if (cursorCol + placement.colSpan > columnCount) {
|
|
119
|
+
cursorRow++
|
|
120
|
+
cursorCol = 0
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (isFree(cursorRow, cursorCol, placement.rowSpan, placement.colSpan)) {
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
cursorCol++
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
placement.row = cursorRow
|
|
132
|
+
placement.col = cursorCol
|
|
133
|
+
occupy(cursorRow, cursorCol, placement.rowSpan, placement.colSpan)
|
|
134
|
+
cursorCol += placement.colSpan
|
|
135
|
+
if (cursorCol >= columnCount) {
|
|
136
|
+
cursorRow++
|
|
137
|
+
cursorCol = 0
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
cells: placements.map(({ row, col }) => ({ row: row!, col: col! })),
|
|
143
|
+
rowCount: Math.max(
|
|
144
|
+
trackCount(rowTracks),
|
|
145
|
+
...placements.map((item) => item.row! + item.rowSpan),
|
|
146
|
+
),
|
|
147
|
+
columnCount: Math.max(columnCount, ...placements.map((item) => item.col! + item.colSpan)),
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function ensureGridTracks(spec: string | undefined, requiredCount: number): string {
|
|
152
|
+
const tracks =
|
|
153
|
+
spec
|
|
154
|
+
?.split(',')
|
|
155
|
+
.map((track) => track.trim())
|
|
156
|
+
.filter(Boolean) ?? []
|
|
157
|
+
|
|
158
|
+
while (tracks.length < requiredCount) {
|
|
159
|
+
tracks.push('auto')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return tracks.join(',')
|
|
163
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { LayoutChildProps } from './props'
|
|
2
|
+
|
|
3
|
+
export type GridChildPlacement = Pick<LayoutChildProps, 'row' | 'col' | 'rowSpan' | 'colSpan'>
|
|
4
|
+
|
|
5
|
+
const placements = new WeakMap<object, GridChildPlacement>()
|
|
6
|
+
|
|
7
|
+
/** Keep the authored Grid placement next to its mounted NativeScript view. */
|
|
8
|
+
export function rememberGridChildPlacement(view: unknown, props: GridChildPlacement): void {
|
|
9
|
+
if ((typeof view !== 'object' || view === null) && typeof view !== 'function') {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const placement: GridChildPlacement = {}
|
|
14
|
+
for (const key of ['row', 'col', 'rowSpan', 'colSpan'] as const) {
|
|
15
|
+
if (props[key] !== undefined) {
|
|
16
|
+
placement[key] = props[key]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Object.keys(placement).length) {
|
|
21
|
+
placements.set(view as object, placement)
|
|
22
|
+
} else {
|
|
23
|
+
placements.delete(view as object)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function gridChildPlacement(view: unknown): GridChildPlacement {
|
|
28
|
+
return typeof view === 'object' && view !== null ? (placements.get(view) ?? {}) : {}
|
|
29
|
+
}
|
|
@@ -4,6 +4,7 @@ import type { CameraViewProps, WebViewProps } from './props'
|
|
|
4
4
|
export function WebView(_props: WebViewProps) @{
|
|
5
5
|
<label className="vx-unsupported">WebView is unsupported by the AppKit host</label>
|
|
6
6
|
}
|
|
7
|
+
|
|
7
8
|
export function CameraView(_props: CameraViewProps) @{
|
|
8
9
|
<label className="vx-unsupported">Camera capture is unsupported by the AppKit host</label>
|
|
9
10
|
}
|
package/src/index.macos.ts
CHANGED
|
@@ -8,7 +8,15 @@ export type { PanEvent, SwipeEvent, SetTranslate } from './props'
|
|
|
8
8
|
export { setTranslate } from './translate.macos'
|
|
9
9
|
export { Text, RichText, RichTextSpan } from './Text.macos.tsrx'
|
|
10
10
|
export { Pressable } from './Pressable.macos.tsrx'
|
|
11
|
-
export {
|
|
11
|
+
export {
|
|
12
|
+
Button,
|
|
13
|
+
Checkbox,
|
|
14
|
+
Switch,
|
|
15
|
+
Slider,
|
|
16
|
+
SegmentedControl,
|
|
17
|
+
SearchInput,
|
|
18
|
+
ActivityIndicator,
|
|
19
|
+
} from './controls.macos.tsrx'
|
|
12
20
|
|
|
13
21
|
export { Collapsible } from './Collapsible.macos.tsrx'
|
|
14
22
|
export { Accordion } from './Accordion.macos.tsrx'
|
|
@@ -50,6 +58,7 @@ export { Drawer } from './Drawer.macos.tsrx'
|
|
|
50
58
|
|
|
51
59
|
export { Hoverable } from './Hoverable.tsrx'
|
|
52
60
|
export { Tooltip } from './Tooltip.tsrx'
|
|
61
|
+
export { Markdown, MarkdownScreen } from './Markdown.tsrx'
|
|
53
62
|
export { Link } from './Link.macos.tsrx'
|
|
54
63
|
export { NavLink } from './NavLink.macos.tsrx'
|
|
55
64
|
export { TextInput, TextArea } from './text-controls.macos.tsrx'
|
|
@@ -60,14 +69,25 @@ export { WebView, CameraView } from './hosted-unsupported.macos.tsrx'
|
|
|
60
69
|
export { Overlay, Popover, Sheet } from './surfaces.macos.tsrx'
|
|
61
70
|
export { showToast } from './toast-anchor.macos.tsrx'
|
|
62
71
|
export { useAnimation } from './anim.macos.tsrx'
|
|
63
|
-
export {
|
|
72
|
+
export {
|
|
73
|
+
useThemeScheme,
|
|
74
|
+
getThemeScheme,
|
|
75
|
+
setThemePreference,
|
|
76
|
+
getThemePreference,
|
|
77
|
+
themeSchemeClasses,
|
|
78
|
+
onThemeSchemeChange,
|
|
79
|
+
applyThemeClasses,
|
|
80
|
+
} from './theme/theme-scheme'
|
|
81
|
+
|
|
64
82
|
export { useColorScheme, getColorScheme } from './colorScheme.macos'
|
|
65
83
|
export { styled } from './styled.macos.tsrx'
|
|
66
84
|
export { openWindow } from './windows.macos'
|
|
67
85
|
export { Screen } from './Screen.macos.tsrx'
|
|
68
86
|
export { Tabs } from './Tabs.macos.tsrx'
|
|
69
87
|
export type { TabSpec } from './props'
|
|
70
|
-
export { SafeArea
|
|
88
|
+
export { SafeArea } from './layout-shells.macos.tsrx'
|
|
89
|
+
export { KeyboardAvoiding } from './KeyboardAvoiding.macos.tsrx'
|
|
90
|
+
export type { KeyboardAvoidingProps } from './props'
|
|
71
91
|
export { useSafeAreaInsets } from './safeAreaInsets.macos.tsrx'
|
|
72
92
|
export { useMeasure } from './useMeasure.macos.tsrx'
|
|
73
93
|
export { Meter, Heading } from './macos-extras.macos.tsrx'
|
package/src/index.mobile.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from './index.shared'
|
|
2
2
|
export { KeyboardAvoiding } from './KeyboardAvoiding.mobile.tsrx'
|
|
3
|
-
export type { KeyboardAvoidingProps } from './props'
|
|
4
3
|
|
|
5
4
|
// Keep native status-bar icon appearance synced with the effective theme scheme.
|
|
6
5
|
import './theme/status-bar-scheme.mobile'
|
package/src/index.shared.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type { PanEvent, SwipeEvent, SetTranslate } from './props'
|
|
|
9
9
|
export { setTranslate } from './translate'
|
|
10
10
|
export { Spacer } from './Spacer'
|
|
11
11
|
export { Text } from './Text'
|
|
12
|
+
export { Markdown, MarkdownScreen } from './Markdown'
|
|
13
|
+
export type { MdDoc, MdNode, MdInline } from './Markdown'
|
|
12
14
|
export { RichText, RichTextSpan } from './RichText'
|
|
13
15
|
export { Pressable } from './Pressable'
|
|
14
16
|
export { Button } from './Button'
|
|
@@ -169,6 +171,7 @@ export { Tabs } from './Tabs'
|
|
|
169
171
|
export type { TabSpec } from './Tabs'
|
|
170
172
|
export { Switch } from './Switch'
|
|
171
173
|
export { SafeArea } from './SafeArea'
|
|
174
|
+
export { KeyboardAvoiding } from './KeyboardAvoiding'
|
|
172
175
|
export { Drawer } from './Drawer'
|
|
173
176
|
export { useSafeAreaInsets } from './safeAreaInsets'
|
|
174
177
|
export type { SafeAreaInsets } from './safeAreaInsets'
|
|
@@ -218,7 +221,13 @@ export {
|
|
|
218
221
|
|
|
219
222
|
export { useBackInterceptor } from './use-back'
|
|
220
223
|
|
|
221
|
-
export {
|
|
224
|
+
export {
|
|
225
|
+
deriveRouteManifest,
|
|
226
|
+
defineRoutes,
|
|
227
|
+
mergeRouteManifests,
|
|
228
|
+
manifestToJson,
|
|
229
|
+
} from './route-table'
|
|
230
|
+
|
|
222
231
|
export type {
|
|
223
232
|
BeforeLoad,
|
|
224
233
|
BeforeLoadArgs,
|
|
@@ -230,8 +239,19 @@ export type {
|
|
|
230
239
|
RouteHeadExport,
|
|
231
240
|
RouteMeta,
|
|
232
241
|
RouteManifest,
|
|
242
|
+
RouteManifestJson,
|
|
243
|
+
RouteJson,
|
|
244
|
+
RouteDataMode,
|
|
245
|
+
RouteNameOfPath,
|
|
246
|
+
RouteParamsFromSpecs,
|
|
247
|
+
RoutePathParams,
|
|
248
|
+
RoutePresentationsFromSpecs,
|
|
233
249
|
RouteSpec,
|
|
234
250
|
RouteSpecSet,
|
|
251
|
+
ManifestRouteNames,
|
|
252
|
+
ManifestRouteParams,
|
|
253
|
+
ManifestRoutePresentations,
|
|
254
|
+
SpecRouteInfo,
|
|
235
255
|
ScreenTable,
|
|
236
256
|
ModalOpenResult,
|
|
237
257
|
OpenWindowOptions,
|
|
@@ -249,6 +269,7 @@ export type {
|
|
|
249
269
|
DrawerProps,
|
|
250
270
|
GridProps,
|
|
251
271
|
ImageProps,
|
|
272
|
+
KeyboardAvoidingProps,
|
|
252
273
|
LayoutChildProps,
|
|
253
274
|
PressableProps,
|
|
254
275
|
RichTextProps,
|