@octane-xplat/ui 0.7.3 → 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.
Files changed (195) hide show
  1. package/README.md +4 -3
  2. package/dist/native/Button.tsrx.js +1 -1
  3. package/dist/native/KeyboardAvoiding.mobile.tsrx.js +2 -2
  4. package/dist/native/Markdown.tsrx.js +233 -0
  5. package/dist/native/Overlay.tsrx.js +15 -26
  6. package/dist/native/PinInput.tsrx.js +25 -7
  7. package/dist/native/Popover.tsrx.js +17 -18
  8. package/dist/native/Pressable.tsrx.js +4 -1
  9. package/dist/native/SearchInput.tsrx.js +6 -3
  10. package/dist/native/Sheet.tsrx.js +22 -37
  11. package/dist/native/TextArea.tsrx.js +6 -3
  12. package/dist/native/TextInput.tsrx.js +6 -3
  13. package/dist/native/VirtualList.tsrx.js +305 -121
  14. package/dist/native/index.shared.js +7 -5
  15. package/dist/native/keyboard-inset.mobile.js +5 -5
  16. package/dist/native/navigation-request.js +26 -0
  17. package/dist/native/octane-client-build.json +1 -1
  18. package/dist/native/overlay-lifetime.mobile.js +65 -0
  19. package/dist/native/pan.tsrx.js +16 -2
  20. package/dist/native/route-table.js +90 -10
  21. package/dist/native/route.js +26 -16
  22. package/dist/native/shared.js +7 -5
  23. package/dist/native/sheet-service.js +1 -1
  24. package/dist/native/svg.mobile.js +1 -1
  25. package/dist/native/text-focus.js +10 -0
  26. package/dist/native/text-write.js +18 -11
  27. package/dist/native/toast-anchor.tsrx.js +1 -1
  28. package/dist/native/ui.js +7 -6
  29. package/dist/native/vendor/ui-svg/index.android.js +2 -2
  30. package/dist/native/vendor/ui-svg/index.ios.js +2 -2
  31. package/dist/native/vendor/ui-svg/{index.common.js → src/index.common.js} +1 -1
  32. package/dist/native/virtual-list-cell-pool.js +63 -0
  33. package/dist/web/Button.web.tsrx.js +1 -1
  34. package/dist/web/KeyboardAvoiding.web.tsrx.js +78 -0
  35. package/dist/web/Markdown.tsrx.js +98 -0
  36. package/dist/web/Overlay.web.tsrx.js +81 -32
  37. package/dist/web/PinInput.web.tsrx.js +24 -10
  38. package/dist/web/Pressable.web.tsrx.js +169 -53
  39. package/dist/web/SearchInput.web.tsrx.js +2 -2
  40. package/dist/web/Sheet.web.tsrx.js +20 -7
  41. package/dist/web/TextArea.web.tsrx.js +22 -17
  42. package/dist/web/TextInput.web.tsrx.js +2 -2
  43. package/dist/web/VirtualList.web.tsrx.js +220 -109
  44. package/dist/web/WebView.web.tsrx.js +3 -3
  45. package/dist/web/{index.shared.js → index.shared.web.js} +7 -5
  46. package/dist/web/modal-focus.web.js +95 -0
  47. package/dist/web/navigation-request.js +26 -0
  48. package/dist/web/octane-client-build.json +1 -1
  49. package/dist/web/pan.web.tsrx.js +27 -10
  50. package/dist/web/route-table.js +90 -10
  51. package/dist/web/route.web.js +88 -22
  52. package/dist/web/sheet-service.web.js +8 -0
  53. package/dist/web/theme/colorScheme.web.js +31 -4
  54. package/dist/web/ui.js +8 -6
  55. package/dist/web/virtual-list-cell-pool.js +63 -0
  56. package/package.json +21 -19
  57. package/platforms/android/native-api-usage.json +7 -7
  58. package/src/CommandPalette.tsrx +1 -0
  59. package/src/Grid.tsrx +2 -0
  60. package/src/Icon.macos.tsrx +1 -0
  61. package/src/KeyboardAvoiding.macos.tsrx +7 -1
  62. package/src/KeyboardAvoiding.mobile.tsrx +2 -2
  63. package/src/KeyboardAvoiding.tsrx +15 -0
  64. package/src/KeyboardAvoiding.web.tsrx +15 -0
  65. package/src/Markdown.tsrx +121 -0
  66. package/src/Meter.tsrx +1 -0
  67. package/src/Overlay.tsrx +15 -30
  68. package/src/Overlay.web.tsrx +12 -3
  69. package/src/PinInput.macos.tsrx +20 -6
  70. package/src/PinInput.mobile.test.ts +107 -0
  71. package/src/PinInput.tsrx +20 -6
  72. package/src/PinInput.web.test.tsrx +42 -0
  73. package/src/PinInput.web.tsrx +20 -6
  74. package/src/Popover.tsrx +17 -17
  75. package/src/Pressable.tsrx +1 -1
  76. package/src/Pressable.web.test.tsrx +36 -0
  77. package/src/Pressable.web.tsrx +37 -6
  78. package/src/RouteHost.macos.tsrx +1 -0
  79. package/src/SearchInput.tsrx +4 -3
  80. package/src/SearchInput.web.tsrx +1 -1
  81. package/src/Sheet.tsrx +20 -41
  82. package/src/Sheet.web.tsrx +9 -1
  83. package/src/Text.tsrx +2 -0
  84. package/src/TextArea.tsrx +6 -5
  85. package/src/TextArea.web.test.tsrx +39 -0
  86. package/src/TextArea.web.tsrx +7 -5
  87. package/src/TextInput.tsrx +6 -6
  88. package/src/TextInput.web.tsrx +1 -1
  89. package/src/VirtualList.macos.tsrx +93 -6
  90. package/src/VirtualList.mobile.test.tsrx +232 -0
  91. package/src/VirtualList.tsrx +285 -81
  92. package/src/VirtualList.web.tsrx +131 -25
  93. package/src/WebView.tsrx +1 -0
  94. package/src/WebView.web.tsrx +3 -0
  95. package/src/android/BottomNavigationView.android.tsrx +1 -0
  96. package/src/android/MaterialDialog.android.tsrx +1 -0
  97. package/src/anim.macos.tsrx +1 -0
  98. package/src/apply-native-modifiers.native.ts +4 -1
  99. package/src/child-array.ts +12 -7
  100. package/src/colorScheme.macos.ts +1 -0
  101. package/src/controls.macos.tsrx +4 -1
  102. package/src/data-driver.mobile.test.ts +120 -0
  103. package/src/data-lifecycle.mobile.test.ts +251 -0
  104. package/src/escape-props.ts +2 -0
  105. package/src/grid-auto-placement.ts +54 -12
  106. package/src/grid-placement.ts +1 -1
  107. package/src/hosted-unsupported.macos.tsrx +1 -0
  108. package/src/index.macos.ts +23 -3
  109. package/src/index.mobile.ts +0 -1
  110. package/src/index.shared.ts +22 -1
  111. package/src/index.shared.web.ts +291 -0
  112. package/src/index.web.ts +1 -1
  113. package/src/input-readiness.web.test.tsrx +82 -0
  114. package/src/ios/UITabBar.ios.tsrx +1 -0
  115. package/src/keyboard-inset.mobile.test.ts +62 -0
  116. package/src/keyboard-inset.mobile.ts +63 -43
  117. package/src/layout-shells.macos.tsrx +1 -5
  118. package/src/macos-extras.macos.tsrx +2 -0
  119. package/src/modal-focus.web.test.tsrx +63 -0
  120. package/src/modal-focus.web.ts +162 -0
  121. package/src/navigation-request.ts +37 -0
  122. package/src/overlay-lifecycle.mobile.test.ts +316 -0
  123. package/src/overlay-lifetime.mobile.ts +107 -0
  124. package/src/pan.mobile.test.tsrx +116 -0
  125. package/src/pan.tsrx +20 -2
  126. package/src/pan.web.tsrx +35 -7
  127. package/src/platformProps.ts +1 -0
  128. package/src/popover.mobile.test.ts +9 -1
  129. package/src/pressable-accessibility.mobile.test.ts +30 -0
  130. package/src/props.ts +163 -3
  131. package/src/refresh.ts +6 -1
  132. package/src/refresh.web.test.ts +77 -77
  133. package/src/route-android.test.ts +162 -0
  134. package/src/route-history.web.test.ts +80 -0
  135. package/src/route-navigation.web.test.ts +173 -0
  136. package/src/route-table.test.ts +188 -9
  137. package/src/route-table.ts +138 -10
  138. package/src/route.macos.ts +56 -13
  139. package/src/route.ts +54 -24
  140. package/src/route.web.ts +141 -24
  141. package/src/sheet-detents.ts +5 -3
  142. package/src/sheet-service.macos.ts +5 -1
  143. package/src/sheet-service.web.ts +8 -0
  144. package/src/sheet-snap.test.ts +23 -23
  145. package/src/sheet-snap.ts +6 -1
  146. package/src/svg.mobile.ts +9 -3
  147. package/src/tap-to-blur.ios.ts +48 -32
  148. package/src/tap-to-blur.mobile.ts +23 -15
  149. package/src/text-focus.ts +12 -0
  150. package/src/text-input.mobile.test.ts +89 -0
  151. package/src/text-write.ts +21 -11
  152. package/src/theme/colorScheme.linux.ts +2 -4
  153. package/src/theme/colorScheme.web.ts +51 -4
  154. package/src/theme/theme-scheme.macos.ts +8 -2
  155. package/src/theme/tokens.css +1736 -1730
  156. package/src/translate.macos.ts +4 -1
  157. package/src/virtual-list-cell-pool.test.ts +53 -0
  158. package/src/virtual-list-cell-pool.ts +81 -0
  159. package/src/virtual-list-layout.test.ts +11 -8
  160. package/src/virtual-list-layout.ts +4 -8
  161. package/src/windows.linux.ts +7 -2
  162. package/src/windows.macos.ts +1 -0
  163. package/types/generated/.tsrx-typegen-manifest.json +13 -3
  164. package/types/generated/KeyboardAvoiding.d.ts +3 -0
  165. package/types/generated/KeyboardAvoiding.web.d.ts +2 -0
  166. package/types/generated/Markdown.d.ts +59 -0
  167. package/types/generated/TextArea.d.ts +2 -2
  168. package/types/generated/TextArea.web.d.ts +1 -1
  169. package/types/generated/TextInput.d.ts +2 -3
  170. package/types/generated/VirtualList.d.ts +3 -3
  171. package/types/generated/index.mobile.d.ts +0 -1
  172. package/types/generated/index.shared.d.ts +6 -3
  173. package/types/generated/index.shared.web.d.ts +114 -0
  174. package/types/generated/index.web.d.ts +1 -1
  175. package/types/generated/modal-focus.web.d.ts +3 -0
  176. package/types/generated/navigation-request.d.ts +10 -0
  177. package/types/generated/overlay-lifetime.mobile.d.ts +10 -0
  178. package/types/generated/props.d.ts +111 -3
  179. package/types/generated/route-table.d.ts +14 -3
  180. package/types/generated/svg.mobile.d.ts +2 -2
  181. package/types/generated/text-focus.d.ts +3 -0
  182. package/types/generated/text-write.d.ts +3 -1
  183. package/types/generated/theme/colorScheme.web.d.ts +5 -0
  184. package/types/generated/virtual-list-cell-pool.d.ts +18 -0
  185. package/types/index.macos.d.ts +15 -3
  186. /package/src/vendor/ui-svg/{index.android.ts → src/index.android.ts} +0 -0
  187. /package/src/vendor/ui-svg/{index.common.ts → src/index.common.ts} +0 -0
  188. /package/src/vendor/ui-svg/{index.ios.ts → src/index.ios.ts} +0 -0
  189. /package/src/vendor/ui-svg/{index.ts → src/index.ts} +0 -0
  190. /package/src/vendor/ui-svg/{typings → src/typings}/SVGKit.d.ts +0 -0
  191. /package/src/vendor/ui-svg/{typings → src/typings}/android.d.ts +0 -0
  192. /package/src/vendor/ui-svg/{typings → src/typings}/ios.d.ts +0 -0
  193. /package/types/generated/vendor/ui-svg/{index.common.d.ts → src/index.common.d.ts} +0 -0
  194. /package/types/generated/vendor/ui-svg/{index.d.ts → src/index.d.ts} +0 -0
  195. /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
+ })
@@ -19,12 +19,14 @@ export function applyEscapeProps(
19
19
  if (!view) {
20
20
  return
21
21
  }
22
+
22
23
  rememberGridChildPlacement(view, props)
23
24
 
24
25
  const bag = isIOS ? props.ios : isAndroid ? props.android : undefined
25
26
  if (bag) {
26
27
  Object.assign(view, bag)
27
28
  }
29
+
28
30
  applyNativeModifiers(view, props.modifiers)
29
31
  }
30
32
 
@@ -12,7 +12,10 @@ export interface GridAutoPlacement {
12
12
  }
13
13
 
14
14
  function gridIndex(value: unknown): number | null {
15
- if (value == null || value === '') return null
15
+ if (value == null || value === '') {
16
+ return null
17
+ }
18
+
16
19
  const index = Number(value)
17
20
  return Number.isInteger(index) && index >= 0 ? index : null
18
21
  }
@@ -38,6 +41,7 @@ export function autoPlaceGridChildren(
38
41
  rowSpan: gridSpan(item.rowSpan),
39
42
  colSpan: gridSpan(item.colSpan),
40
43
  }))
44
+
41
45
  const columnCount = Math.max(
42
46
  1,
43
47
  trackCount(columnTracks),
@@ -46,18 +50,25 @@ export function autoPlaceGridChildren(
46
50
  .map((placement) => placement.col! + placement.colSpan),
47
51
  ...placements.map((placement) => placement.colSpan),
48
52
  )
53
+
49
54
  const occupied = new Set<string>()
50
55
  const isFree = (row: number, col: number, rowSpan: number, colSpan: number) => {
51
56
  for (let y = row; y < row + rowSpan; y++) {
52
57
  for (let x = col; x < col + colSpan; x++) {
53
- if (occupied.has(`${y},${x}`)) return false
58
+ if (occupied.has(`${y},${x}`)) {
59
+ return false
60
+ }
54
61
  }
55
62
  }
63
+
56
64
  return true
57
65
  }
66
+
58
67
  const occupy = (row: number, col: number, rowSpan: number, colSpan: number) => {
59
68
  for (let y = row; y < row + rowSpan; y++) {
60
- for (let x = col; x < col + colSpan; x++) occupied.add(`${y},${x}`)
69
+ for (let x = col; x < col + colSpan; x++) {
70
+ occupied.add(`${y},${x}`)
71
+ }
61
72
  }
62
73
  }
63
74
 
@@ -69,17 +80,29 @@ export function autoPlaceGridChildren(
69
80
  }
70
81
 
71
82
  for (const placement of placements) {
72
- if (placement.row == null || placement.col != null) continue
83
+ if (placement.row == null || placement.col != null) {
84
+ continue
85
+ }
86
+
73
87
  let col = 0
74
- while (!isFree(placement.row, col, placement.rowSpan, placement.colSpan)) col++
88
+ while (!isFree(placement.row, col, placement.rowSpan, placement.colSpan)) {
89
+ col++
90
+ }
91
+
75
92
  placement.col = col
76
93
  occupy(placement.row, col, placement.rowSpan, placement.colSpan)
77
94
  }
78
95
 
79
96
  for (const placement of placements) {
80
- if (placement.col == null || placement.row != null) continue
97
+ if (placement.col == null || placement.row != null) {
98
+ continue
99
+ }
100
+
81
101
  let row = 0
82
- while (!isFree(row, placement.col, placement.rowSpan, placement.colSpan)) row++
102
+ while (!isFree(row, placement.col, placement.rowSpan, placement.colSpan)) {
103
+ row++
104
+ }
105
+
83
106
  placement.row = row
84
107
  occupy(row, placement.col, placement.rowSpan, placement.colSpan)
85
108
  }
@@ -87,16 +110,24 @@ export function autoPlaceGridChildren(
87
110
  let cursorRow = 0
88
111
  let cursorCol = 0
89
112
  for (const placement of placements) {
90
- if (placement.row != null && placement.col != null) continue
113
+ if (placement.row != null && placement.col != null) {
114
+ continue
115
+ }
116
+
91
117
  while (true) {
92
118
  if (cursorCol + placement.colSpan > columnCount) {
93
119
  cursorRow++
94
120
  cursorCol = 0
95
121
  continue
96
122
  }
97
- if (isFree(cursorRow, cursorCol, placement.rowSpan, placement.colSpan)) break
123
+
124
+ if (isFree(cursorRow, cursorCol, placement.rowSpan, placement.colSpan)) {
125
+ break
126
+ }
127
+
98
128
  cursorCol++
99
129
  }
130
+
100
131
  placement.row = cursorRow
101
132
  placement.col = cursorCol
102
133
  occupy(cursorRow, cursorCol, placement.rowSpan, placement.colSpan)
@@ -109,13 +140,24 @@ export function autoPlaceGridChildren(
109
140
 
110
141
  return {
111
142
  cells: placements.map(({ row, col }) => ({ row: row!, col: col! })),
112
- rowCount: Math.max(trackCount(rowTracks), ...placements.map((item) => item.row! + item.rowSpan)),
143
+ rowCount: Math.max(
144
+ trackCount(rowTracks),
145
+ ...placements.map((item) => item.row! + item.rowSpan),
146
+ ),
113
147
  columnCount: Math.max(columnCount, ...placements.map((item) => item.col! + item.colSpan)),
114
148
  }
115
149
  }
116
150
 
117
151
  export function ensureGridTracks(spec: string | undefined, requiredCount: number): string {
118
- const tracks = spec?.split(',').map((track) => track.trim()).filter(Boolean) ?? []
119
- while (tracks.length < requiredCount) tracks.push('auto')
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
+
120
162
  return tracks.join(',')
121
163
  }
@@ -25,5 +25,5 @@ export function rememberGridChildPlacement(view: unknown, props: GridChildPlacem
25
25
  }
26
26
 
27
27
  export function gridChildPlacement(view: unknown): GridChildPlacement {
28
- return typeof view === 'object' && view !== null ? placements.get(view) ?? {} : {}
28
+ return typeof view === 'object' && view !== null ? (placements.get(view) ?? {}) : {}
29
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
  }
@@ -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 { Button, Checkbox, Switch, Slider, SegmentedControl, SearchInput, ActivityIndicator } from './controls.macos.tsrx'
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 { useThemeScheme, getThemeScheme, setThemePreference, getThemePreference, themeSchemeClasses, onThemeSchemeChange, applyThemeClasses } from './theme/theme-scheme'
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, KeyboardAvoiding } from './layout-shells.macos.tsrx'
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'
@@ -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'
@@ -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 { deriveRouteManifest, defineRoutes, mergeRouteManifests } from './route-table'
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,