@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
package/src/translate.macos.ts
CHANGED
package/src/useMeasure.tsrx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
-
import { useCallback, useEffect, useState } from 'octane';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'octane';
|
|
3
3
|
import type { MeasureBounds, MeasureResult, UseMeasureOptions } from './props';
|
|
4
4
|
|
|
5
5
|
function readBounds(view: any): MeasureBounds | null {
|
|
@@ -11,14 +11,12 @@ function readBounds(view: any): MeasureBounds | null {
|
|
|
11
11
|
|
|
12
12
|
/** Binds to a NativeScript view and observes screen-relative DIP bounds by default. */
|
|
13
13
|
export function useMeasure(options: UseMeasureOptions = {}): MeasureResult {
|
|
14
|
-
const [view, setView] = useState<any>(null);
|
|
15
14
|
const [bounds, setBounds] = useState<MeasureBounds | null>(null);
|
|
15
|
+
const detach = useRef<(() => void) | null>(null);
|
|
16
16
|
const observe = options.observe ?? true;
|
|
17
|
-
const bind = useCallback((
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
17
|
+
const bind = useCallback((view: any) => {
|
|
18
|
+
detach.current?.();
|
|
19
|
+
detach.current = null;
|
|
22
20
|
if (!view) {
|
|
23
21
|
setBounds(null);
|
|
24
22
|
return;
|
|
@@ -28,39 +26,40 @@ export function useMeasure(options: UseMeasureOptions = {}): MeasureResult {
|
|
|
28
26
|
const next = readBounds(view);
|
|
29
27
|
// Fresh object each read — skip the setState when nothing moved
|
|
30
28
|
// or the re-render → layoutChanged → update loop never ends.
|
|
31
|
-
setBounds((prev: MeasureBounds | null) =>
|
|
32
|
-
prev && next &&
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
setBounds((prev: MeasureBounds | null) => {
|
|
30
|
+
return prev && next &&
|
|
31
|
+
prev.x === next.x && prev.y === next.y &&
|
|
32
|
+
prev.width === next.width && prev.height === next.height
|
|
35
33
|
? prev
|
|
36
|
-
: next
|
|
37
|
-
);
|
|
34
|
+
: next;
|
|
35
|
+
});
|
|
38
36
|
};
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
view.on?.('loaded', onLoaded);
|
|
38
|
+
view.on?.('loaded', update);
|
|
42
39
|
update();
|
|
40
|
+
const scrollParents: any[] = [];
|
|
43
41
|
if (observe) {
|
|
44
42
|
view.on?.('layoutChanged', update);
|
|
45
43
|
let parent = view.parent;
|
|
46
44
|
while (parent) {
|
|
47
45
|
parent.on?.('scroll', update);
|
|
46
|
+
scrollParents.push(parent);
|
|
48
47
|
parent = parent.parent;
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
view.off?.('loaded',
|
|
51
|
+
detach.current = () => {
|
|
52
|
+
view.off?.('loaded', update);
|
|
54
53
|
if (observe) {
|
|
55
54
|
view.off?.('layoutChanged', update);
|
|
56
|
-
|
|
57
|
-
while (parent) {
|
|
55
|
+
for (const parent of scrollParents) {
|
|
58
56
|
parent.off?.('scroll', update);
|
|
59
|
-
parent = parent.parent;
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
};
|
|
63
|
-
}, [
|
|
60
|
+
}, [observe]);
|
|
61
|
+
|
|
62
|
+
useEffect(() => () => detach.current?.(), []);
|
|
64
63
|
|
|
65
64
|
return { bind, bounds };
|
|
66
65
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { VirtualListCellPool } from './virtual-list-cell-pool'
|
|
3
|
+
import { createVirtualListEntries } from './virtual-list-layout'
|
|
4
|
+
|
|
5
|
+
const item = (id: string, type = 'row') => ({ id, type })
|
|
6
|
+
const entriesFor = (items: ReturnType<typeof item>[]) =>
|
|
7
|
+
createVirtualListEntries(
|
|
8
|
+
items,
|
|
9
|
+
(row) => row.id,
|
|
10
|
+
(row) => row.type,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
describe('VirtualList physical cell ownership', () => {
|
|
14
|
+
it('retains keyed hosts across prepend and recycles only compatible free hosts', () => {
|
|
15
|
+
const items = [item('a'), item('b'), item('c', 'other')]
|
|
16
|
+
const pool = new VirtualListCellPool<ReturnType<typeof item>>()
|
|
17
|
+
const initial = pool.update(entriesFor(items))
|
|
18
|
+
const a = initial.find((cell) => cell.entry?.key === 'a')!
|
|
19
|
+
const b = initial.find((cell) => cell.entry?.key === 'b')!
|
|
20
|
+
const c = initial.find((cell) => cell.entry?.key === 'c')!
|
|
21
|
+
const next = pool.update(entriesFor([item('new'), items[1], items[2]]))
|
|
22
|
+
expect(next.find((cell) => cell.entry?.key === 'new')!.id).toBe(a.id)
|
|
23
|
+
expect(next.find((cell) => cell.entry?.key === 'b')!.id).toBe(b.id)
|
|
24
|
+
expect(next.find((cell) => cell.entry?.key === 'c')!.id).toBe(c.id)
|
|
25
|
+
expect(pool.isCurrent(a.id, a.entry!.rowKey, a.generation)).toBe(false)
|
|
26
|
+
const prepended = pool.update(entriesFor([item('first'), item('new'), items[1], items[2]]))
|
|
27
|
+
expect(prepended.find((cell) => cell.entry?.key === 'b')!.id).toBe(b.id)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('does not recycle a host across types and bounds free hosts after repeated changes', () => {
|
|
31
|
+
const pool = new VirtualListCellPool<ReturnType<typeof item>>()
|
|
32
|
+
const original = pool.update(entriesFor([item('first', 'type0')]))[0]
|
|
33
|
+
const different = pool.update(entriesFor([item('next', 'type1')])).find((cell) => cell.entry)!
|
|
34
|
+
expect(different.id).not.toBe(original.id)
|
|
35
|
+
for (let index = 2; index < 1000; index++) {
|
|
36
|
+
const cells = pool.update(entriesFor([item(String(index), 'type' + index)]))
|
|
37
|
+
expect(cells.length).toBeLessThanOrEqual(9)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('invalidates work on item replacement, emptying, restoration and disposal', () => {
|
|
42
|
+
const pool = new VirtualListCellPool<ReturnType<typeof item>>()
|
|
43
|
+
const first = pool.update(entriesFor([item('same')]))[0]
|
|
44
|
+
const replacement = pool.update(entriesFor([item('same')]))[0]
|
|
45
|
+
expect(replacement.id).toBe(first.id)
|
|
46
|
+
expect(pool.isCurrent(first.id, first.entry!.rowKey, first.generation)).toBe(false)
|
|
47
|
+
expect(pool.update([])).toEqual([])
|
|
48
|
+
const restored = pool.update(entriesFor([item('same')]))[0]
|
|
49
|
+
expect(restored.id).not.toBe(first.id)
|
|
50
|
+
pool.clear()
|
|
51
|
+
expect(pool.isCurrent(restored.id, restored.entry!.rowKey, restored.generation)).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { VirtualListEntry } from './virtual-list-layout'
|
|
2
|
+
|
|
3
|
+
export interface VirtualListCell<T> {
|
|
4
|
+
id: number
|
|
5
|
+
typeKey: string
|
|
6
|
+
generation: number
|
|
7
|
+
entry: VirtualListEntry<T> | null
|
|
8
|
+
rows: readonly VirtualListEntry<T>[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Reuse physical hosts without retaining an off-window logical row subtree. */
|
|
12
|
+
export class VirtualListCellPool<T> {
|
|
13
|
+
private cells: VirtualListCell<T>[] = []
|
|
14
|
+
private byId = new Map<number, VirtualListCell<T>>()
|
|
15
|
+
private nextId = 0
|
|
16
|
+
|
|
17
|
+
update(entries: readonly VirtualListEntry<T>[]): readonly VirtualListCell<T>[] {
|
|
18
|
+
if (entries.length === 0) {
|
|
19
|
+
this.clear()
|
|
20
|
+
return this.cells
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const wanted = new Set(entries.map((entry) => entry.rowKey))
|
|
24
|
+
let cells = this.cells.map((cell) =>
|
|
25
|
+
cell.entry && !wanted.has(cell.entry.rowKey)
|
|
26
|
+
? { ...cell, entry: null, rows: [], generation: cell.generation + 1 }
|
|
27
|
+
: cell,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const retained = new Map(
|
|
31
|
+
cells.filter((cell) => cell.entry).map((cell) => [cell.entry!.rowKey, cell.id]),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const retainedId = retained.get(entry.rowKey)
|
|
36
|
+
let index =
|
|
37
|
+
retainedId === undefined
|
|
38
|
+
? cells.findIndex((cell) => !cell.entry && cell.typeKey === entry.typeKey)
|
|
39
|
+
: cells.findIndex((cell) => cell.id === retainedId)
|
|
40
|
+
|
|
41
|
+
if (index === -1) {
|
|
42
|
+
index = cells.length
|
|
43
|
+
cells.push({
|
|
44
|
+
id: this.nextId++,
|
|
45
|
+
typeKey: entry.typeKey,
|
|
46
|
+
generation: 0,
|
|
47
|
+
entry: null,
|
|
48
|
+
rows: [],
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const cell = cells[index]
|
|
53
|
+
if (cell.entry !== entry) {
|
|
54
|
+
// Immutable snapshots let renderer caches see assignment/index changes.
|
|
55
|
+
cells[index] = { ...cell, entry, rows: [entry], generation: cell.generation + 1 }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let spare = 0
|
|
60
|
+
cells = cells.filter((cell) => cell.entry || spare++ < 8)
|
|
61
|
+
cells.sort((a, b) => (a.entry?.index ?? Infinity) - (b.entry?.index ?? Infinity) || a.id - b.id)
|
|
62
|
+
this.cells = cells
|
|
63
|
+
this.byId = new Map(cells.map((cell) => [cell.id, cell]))
|
|
64
|
+
return cells
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get(id: number): VirtualListCell<T> | undefined {
|
|
68
|
+
return this.byId.get(id)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
isCurrent(id: number, rowKey: string, generation: number): boolean {
|
|
72
|
+
const cell = this.byId.get(id)
|
|
73
|
+
return cell?.entry?.rowKey === rowKey && cell.generation === generation
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
clear() {
|
|
77
|
+
this.cells = []
|
|
78
|
+
this.byId.clear()
|
|
79
|
+
// IDs remain monotonic across empty/restore so old work cannot match.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -48,10 +48,9 @@ describe('VirtualList size index', () => {
|
|
|
48
48
|
})
|
|
49
49
|
|
|
50
50
|
it('rejects duplicate item keys', () => {
|
|
51
|
-
expect(() =>
|
|
52
|
-
[{ id: 'same' }, { id: 'same' }],
|
|
53
|
-
|
|
54
|
-
)).toThrow('duplicate key')
|
|
51
|
+
expect(() =>
|
|
52
|
+
createVirtualListEntries([{ id: 'same' }, { id: 'same' }], (item) => item.id),
|
|
53
|
+
).toThrow('duplicate key')
|
|
55
54
|
})
|
|
56
55
|
|
|
57
56
|
it('uses a type estimate only after four rows are measured', () => {
|
|
@@ -61,10 +60,14 @@ describe('VirtualList size index', () => {
|
|
|
61
60
|
() => 'row',
|
|
62
61
|
)
|
|
63
62
|
|
|
64
|
-
const measurements = new Map(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const measurements = new Map(
|
|
64
|
+
entries
|
|
65
|
+
.slice(0, 3)
|
|
66
|
+
.map((entry, index) => [
|
|
67
|
+
virtualListMeasurementKey(entry.rowKey, false),
|
|
68
|
+
{ width: 100, height: 36 + index * 12 },
|
|
69
|
+
]),
|
|
70
|
+
)
|
|
68
71
|
|
|
69
72
|
expect(estimateVirtualListSizes(entries, false, 100, measurements)[3]).toBe(
|
|
70
73
|
VIRTUAL_LIST_ESTIMATED_ROW_SIZE,
|
|
@@ -38,7 +38,9 @@ export function createVirtualListEntries<T>(
|
|
|
38
38
|
|
|
39
39
|
const keyToken = JSON.stringify([typeof key, key])
|
|
40
40
|
if (seen.has(keyToken)) {
|
|
41
|
-
throw new Error(
|
|
41
|
+
throw new Error(
|
|
42
|
+
`VirtualList keyExtractor returned a duplicate key at index ${index}: ${String(key)}`,
|
|
43
|
+
)
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
seen.add(keyToken)
|
|
@@ -89,12 +91,7 @@ export function estimateVirtualListSizes<T>(
|
|
|
89
91
|
)
|
|
90
92
|
|
|
91
93
|
const cached = measurements.get(cacheKey)
|
|
92
|
-
if (
|
|
93
|
-
cached &&
|
|
94
|
-
width > 0 &&
|
|
95
|
-
cached.width > 0 &&
|
|
96
|
-
Math.abs(cached.width - width) < 0.5
|
|
97
|
-
) {
|
|
94
|
+
if (cached && width > 0 && cached.width > 0 && Math.abs(cached.width - width) < 0.5) {
|
|
98
95
|
return cached.height
|
|
99
96
|
}
|
|
100
97
|
|
|
@@ -130,7 +127,6 @@ export class VirtualListSizeIndex {
|
|
|
130
127
|
this.tree[parent] += this.tree[index]
|
|
131
128
|
}
|
|
132
129
|
}
|
|
133
|
-
|
|
134
130
|
}
|
|
135
131
|
|
|
136
132
|
get(index: number): number {
|
package/src/windows.linux.ts
CHANGED
|
@@ -32,6 +32,7 @@ export function openWindow(
|
|
|
32
32
|
const closed = new Promise<void>((resolve) => {
|
|
33
33
|
resolveClosed = resolve
|
|
34
34
|
})
|
|
35
|
+
|
|
35
36
|
const off = bridge().on('windows', 'closed', (wid: unknown) => {
|
|
36
37
|
if (wid === id) {
|
|
37
38
|
off()
|
|
@@ -50,10 +51,14 @@ export function openWindow(
|
|
|
50
51
|
id,
|
|
51
52
|
closed,
|
|
52
53
|
close() {
|
|
53
|
-
void bridge()
|
|
54
|
+
void bridge()
|
|
55
|
+
.call('windows', 'close', [id])
|
|
56
|
+
.catch(() => {})
|
|
54
57
|
},
|
|
55
58
|
setTitle(title: string) {
|
|
56
|
-
void bridge()
|
|
59
|
+
void bridge()
|
|
60
|
+
.call('windows', 'setTitle', [id, title])
|
|
61
|
+
.catch(() => {})
|
|
57
62
|
},
|
|
58
63
|
}
|
|
59
64
|
}
|
package/src/windows.macos.ts
CHANGED
|
@@ -64,11 +64,14 @@
|
|
|
64
64
|
"InputTags.web.d.ts",
|
|
65
65
|
"Kbd.d.ts",
|
|
66
66
|
"Kbd.web.d.ts",
|
|
67
|
+
"KeyboardAvoiding.d.ts",
|
|
67
68
|
"KeyboardAvoiding.mobile.d.ts",
|
|
69
|
+
"KeyboardAvoiding.web.d.ts",
|
|
68
70
|
"Link.d.ts",
|
|
69
71
|
"Link.web.d.ts",
|
|
70
72
|
"ListItem.d.ts",
|
|
71
73
|
"ListItem.web.d.ts",
|
|
74
|
+
"Markdown.d.ts",
|
|
72
75
|
"Meter.d.ts",
|
|
73
76
|
"Meter.web.d.ts",
|
|
74
77
|
"NavLink.d.ts",
|
|
@@ -169,11 +172,14 @@
|
|
|
169
172
|
"cx.d.ts",
|
|
170
173
|
"escape-props.d.ts",
|
|
171
174
|
"escape-props.web.d.ts",
|
|
175
|
+
"grid-auto-placement.d.ts",
|
|
176
|
+
"grid-placement.d.ts",
|
|
172
177
|
"icons.d.ts",
|
|
173
178
|
"index.d.ts",
|
|
174
179
|
"index.linux.d.ts",
|
|
175
180
|
"index.mobile.d.ts",
|
|
176
181
|
"index.shared.d.ts",
|
|
182
|
+
"index.shared.web.d.ts",
|
|
177
183
|
"index.web.d.ts",
|
|
178
184
|
"ios/LiquidGlass.ios.d.ts",
|
|
179
185
|
"ios/LiquidGlassContainer.ios.d.ts",
|
|
@@ -191,9 +197,12 @@
|
|
|
191
197
|
"layout-child.d.ts",
|
|
192
198
|
"layout-child.web.d.ts",
|
|
193
199
|
"meter-math.d.ts",
|
|
200
|
+
"modal-focus.web.d.ts",
|
|
194
201
|
"modal-presenter.mobile.d.ts",
|
|
195
202
|
"modifier-factories.d.ts",
|
|
196
203
|
"native/index.d.ts",
|
|
204
|
+
"navigation-request.d.ts",
|
|
205
|
+
"overlay-lifetime.mobile.d.ts",
|
|
197
206
|
"pan.d.ts",
|
|
198
207
|
"pan.web.d.ts",
|
|
199
208
|
"platform.d.ts",
|
|
@@ -223,11 +232,14 @@
|
|
|
223
232
|
"styled.d.ts",
|
|
224
233
|
"styled.web.d.ts",
|
|
225
234
|
"svg.mobile.d.ts",
|
|
235
|
+
"tap-to-blur.ios.d.ts",
|
|
226
236
|
"tap-to-blur.mobile.d.ts",
|
|
237
|
+
"text-focus.d.ts",
|
|
227
238
|
"text-write.d.ts",
|
|
228
239
|
"text-write.web.d.ts",
|
|
229
240
|
"theme/colorScheme.d.ts",
|
|
230
241
|
"theme/colorScheme.linux.d.ts",
|
|
242
|
+
"theme/colorScheme.web.d.ts",
|
|
231
243
|
"theme/status-bar-scheme.mobile.d.ts",
|
|
232
244
|
"theme/theme-scheme.d.ts",
|
|
233
245
|
"toast-anchor.d.ts",
|
|
@@ -243,9 +255,10 @@
|
|
|
243
255
|
"use-store.web.d.ts",
|
|
244
256
|
"useMeasure.d.ts",
|
|
245
257
|
"useMeasure.web.d.ts",
|
|
246
|
-
"vendor/ui-svg/index.common.d.ts",
|
|
247
|
-
"vendor/ui-svg/index.d.ts",
|
|
248
|
-
"vendor/ui-svg/index.ios.d.ts",
|
|
258
|
+
"vendor/ui-svg/src/index.common.d.ts",
|
|
259
|
+
"vendor/ui-svg/src/index.d.ts",
|
|
260
|
+
"vendor/ui-svg/src/index.ios.d.ts",
|
|
261
|
+
"virtual-list-cell-pool.d.ts",
|
|
249
262
|
"virtual-list-layout.d.ts",
|
|
250
263
|
"web/index.d.ts",
|
|
251
264
|
"windows.d.ts",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Markdown AST — the serialized shape `xplat routes` bakes for `.md`
|
|
2
|
+
* routes (parse happens at codegen, not runtime). `MdNode` covers block
|
|
3
|
+
* level; `MdInline` covers text runs inside paragraphs/lists/quotes. */
|
|
4
|
+
export type MdInline = {
|
|
5
|
+
t: 'text';
|
|
6
|
+
text: string;
|
|
7
|
+
} | {
|
|
8
|
+
t: 'code';
|
|
9
|
+
text: string;
|
|
10
|
+
} | {
|
|
11
|
+
t: 'bold';
|
|
12
|
+
children: MdInline[];
|
|
13
|
+
} | {
|
|
14
|
+
t: 'em';
|
|
15
|
+
children: MdInline[];
|
|
16
|
+
} | {
|
|
17
|
+
t: 'link';
|
|
18
|
+
text: string;
|
|
19
|
+
href: string;
|
|
20
|
+
};
|
|
21
|
+
export type MdNode = {
|
|
22
|
+
t: 'h';
|
|
23
|
+
depth: 1 | 2 | 3 | 4 | 5 | 6;
|
|
24
|
+
children: MdInline[];
|
|
25
|
+
} | {
|
|
26
|
+
t: 'p';
|
|
27
|
+
children: MdInline[];
|
|
28
|
+
} | {
|
|
29
|
+
t: 'code';
|
|
30
|
+
lang?: string;
|
|
31
|
+
text: string;
|
|
32
|
+
} | {
|
|
33
|
+
t: 'list';
|
|
34
|
+
ordered: boolean;
|
|
35
|
+
items: MdInline[][];
|
|
36
|
+
} | {
|
|
37
|
+
t: 'quote';
|
|
38
|
+
children: MdInline[];
|
|
39
|
+
} | {
|
|
40
|
+
t: 'hr';
|
|
41
|
+
};
|
|
42
|
+
export interface MdDoc {
|
|
43
|
+
kind: 'octane-xplat/md';
|
|
44
|
+
v: 1;
|
|
45
|
+
children: MdNode[];
|
|
46
|
+
}
|
|
47
|
+
/** Renders a baked markdown doc with the shared vocabulary — Text
|
|
48
|
+
* blocks on every platform, no parser at runtime. `data` is the AST the
|
|
49
|
+
* codegen baked into routes.gen.data (or a doc built programmatically). */
|
|
50
|
+
export declare function Markdown(props: {
|
|
51
|
+
data?: MdDoc;
|
|
52
|
+
}): unknown;
|
|
53
|
+
/** Route screen for `.md` files — the codegen emits specs with this as
|
|
54
|
+
* `screen`, so a markdown file in the route dir is a route end-to-end.
|
|
55
|
+
* Receives baked data like any route (`props.data`). */
|
|
56
|
+
export declare function MarkdownScreen(props: {
|
|
57
|
+
data?: MdDoc;
|
|
58
|
+
error?: unknown;
|
|
59
|
+
}): unknown;
|
|
@@ -3,6 +3,6 @@ import type { TextAreaProps } from "./props.js";
|
|
|
3
3
|
* `autoGrow` is its default; `rows`/`maxRows` have no NS equivalent, so the
|
|
4
4
|
* leaf converts them to min/maxHeight in dips using the widget's own line
|
|
5
5
|
* height (iOS `font.lineHeight`, Android `getLineHeight()` px→dip — measured
|
|
6
|
-
* on `loaded`, re-fit every commit for prop/style changes).
|
|
7
|
-
*
|
|
6
|
+
* on `loaded`, re-fit every commit for prop/style changes). Imperative
|
|
7
|
+
* writes suppress their synchronous textChange echo through text-write. */
|
|
8
8
|
export declare function TextArea(props: TextAreaProps): unknown;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TextAreaProps } from "./props.js";
|
|
2
2
|
/** Multiline input — <textarea>. `autoGrow` re-fits height to content after
|
|
3
|
-
*
|
|
3
|
+
* input and value updates via scrollHeight, capped by `maxRows` translated to a real
|
|
4
4
|
* max-height from the element's computed line-height; past the cap the
|
|
5
5
|
* field scrolls. `useLayoutEffect` so the measure lands pre-paint in the
|
|
6
6
|
* commit that wrote `value`. Deps are explicit — tsrx infers them from the
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { TextInputProps } from "./props.js";
|
|
2
|
-
/** Controlled single-line input.
|
|
3
|
-
*
|
|
4
|
-
* guard needed. `editable` defaults true at the leaf too: an undefined
|
|
2
|
+
/** Controlled single-line input. Imperative writes suppress their synchronous
|
|
3
|
+
* textChange echo through text-write. `editable` defaults true: an undefined
|
|
5
4
|
* write coerces ios.userInteractionEnabled to NO (dead field) — the
|
|
6
5
|
* driver's skip-undefined patch covers it, the ?? stays as documentation.
|
|
7
6
|
* `text` isn't a bound prop: the leaf writes it imperatively so the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VirtualListProps } from './props.js';
|
|
2
|
-
/** Shared vertical virtualization
|
|
3
|
-
* rows unmount; measured
|
|
4
|
-
* the
|
|
2
|
+
/** Shared vertical virtualization with pooled outer hosts and keyed logical
|
|
3
|
+
* rows. Off-window item subtrees unmount; measured heights position cells and
|
|
4
|
+
* preserve the visible keyed anchor after layout. */
|
|
5
5
|
export declare function VirtualList<T = any>(props: VirtualListProps<T>): unknown;
|
|
@@ -4,6 +4,10 @@ export declare function applyEscapeProps(view: any, props: {
|
|
|
4
4
|
ios?: any;
|
|
5
5
|
android?: any;
|
|
6
6
|
modifiers?: readonly NativeModifier[];
|
|
7
|
+
row?: number;
|
|
8
|
+
col?: number;
|
|
9
|
+
rowSpan?: number;
|
|
10
|
+
colSpan?: number;
|
|
7
11
|
}): void;
|
|
8
12
|
/** Translate the shared/ARIA spelling to NativeScript's narrower role enum. */
|
|
9
13
|
export declare function nativeAccessibilityRole(role?: Role): string | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GridChildPlacement } from './grid-placement.js';
|
|
2
|
+
export interface GridCell {
|
|
3
|
+
row: number;
|
|
4
|
+
col: number;
|
|
5
|
+
}
|
|
6
|
+
export interface GridAutoPlacement {
|
|
7
|
+
cells: GridCell[];
|
|
8
|
+
rowCount: number;
|
|
9
|
+
columnCount: number;
|
|
10
|
+
}
|
|
11
|
+
/** Match CSS Grid's row-flow placement for NativeScript GridLayout children. */
|
|
12
|
+
export declare function autoPlaceGridChildren(items: readonly GridChildPlacement[], rowTracks?: string, columnTracks?: string): GridAutoPlacement;
|
|
13
|
+
export declare function ensureGridTracks(spec: string | undefined, requiredCount: number): string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LayoutChildProps } from './props.js';
|
|
2
|
+
export type GridChildPlacement = Pick<LayoutChildProps, 'row' | 'col' | 'rowSpan' | 'colSpan'>;
|
|
3
|
+
/** Keep the authored Grid placement next to its mounted NativeScript view. */
|
|
4
|
+
export declare function rememberGridChildPlacement(view: unknown, props: GridChildPlacement): void;
|
|
5
|
+
export declare function gridChildPlacement(view: unknown): GridChildPlacement;
|
|
@@ -8,6 +8,8 @@ export type { PanEvent, SwipeEvent, SetTranslate } from './props.js';
|
|
|
8
8
|
export { setTranslate } from './translate.js';
|
|
9
9
|
export { Spacer } from './Spacer.js';
|
|
10
10
|
export { Text } from './Text.js';
|
|
11
|
+
export { Markdown, MarkdownScreen } from './Markdown.js';
|
|
12
|
+
export type { MdDoc, MdNode, MdInline } from './Markdown.js';
|
|
11
13
|
export { RichText, RichTextSpan } from './RichText.js';
|
|
12
14
|
export { Pressable } from './Pressable.js';
|
|
13
15
|
export { Button } from './Button.js';
|
|
@@ -83,6 +85,7 @@ export { Tabs } from './Tabs.js';
|
|
|
83
85
|
export type { TabSpec } from './Tabs.js';
|
|
84
86
|
export { Switch } from './Switch.js';
|
|
85
87
|
export { SafeArea } from './SafeArea.js';
|
|
88
|
+
export { KeyboardAvoiding } from './KeyboardAvoiding.js';
|
|
86
89
|
export { Drawer } from './Drawer.js';
|
|
87
90
|
export { useSafeAreaInsets } from './safeAreaInsets.js';
|
|
88
91
|
export type { SafeAreaInsets } from './safeAreaInsets.js';
|
|
@@ -100,12 +103,12 @@ export { isNative } from './platform.js';
|
|
|
100
103
|
export { registerStack, getStack, stackEntries } from './stacks.js';
|
|
101
104
|
export { pushRoute, popRoute, routeStacks, pushDeepLink, routeFor, currentRoute, currentModalRoute, canGoBack, useCanGoBack, useRoute, useModalRoute, redirect, addBackInterceptor, registerScreens, registerRoutes, addRoutes, screenFor, hrefFor, layoutsForRoute, } from './route.js';
|
|
102
105
|
export { useBackInterceptor } from './use-back.js';
|
|
103
|
-
export { deriveRouteManifest, defineRoutes, mergeRouteManifests } from './route-table.js';
|
|
104
|
-
export type { BeforeLoad, BeforeLoadArgs, LinkProps, NavLinkProps, Route, RouteContext, RouteHead, RouteHeadExport, RouteMeta, RouteManifest, RouteSpec, RouteSpecSet, ScreenTable, ModalOpenResult, OpenWindowOptions, } from './props.js';
|
|
106
|
+
export { deriveRouteManifest, defineRoutes, mergeRouteManifests, manifestToJson, } from './route-table.js';
|
|
107
|
+
export type { BeforeLoad, BeforeLoadArgs, LinkProps, NavLinkProps, Route, RouteContext, RouteHead, RouteHeadExport, RouteMeta, RouteManifest, RouteManifestJson, RouteJson, RouteDataMode, RouteNameOfPath, RouteParamsFromSpecs, RoutePathParams, RoutePresentationsFromSpecs, RouteSpec, RouteSpecSet, ManifestRouteNames, ManifestRouteParams, ManifestRoutePresentations, SpecRouteInfo, ScreenTable, ModalOpenResult, OpenWindowOptions, } from './props.js';
|
|
105
108
|
export { createStore } from './store.js';
|
|
106
109
|
export type { Store, ReadableStore } from './store.js';
|
|
107
110
|
export { useStore } from './use-store.js';
|
|
108
111
|
export { Sheet } from './Sheet.js';
|
|
109
112
|
export { openSheet, closeSheet } from './sheet-service.js';
|
|
110
113
|
export type { SheetProps, SheetOpenOptions, OpenSheet } from './props.js';
|
|
111
|
-
export type { AbsoluteProps, DrawerProps, GridProps, ImageProps, LayoutChildProps, PressableProps, RichTextProps, RichTextSpanProps, RowProps, ScreenProps, SafeAreaProps, ScrollBoxProps, ScrollViewProps, SpacerProps, StackProps, SwitchProps, TabsProps, TextAreaProps, TextInputProps, TextProps, ViewProps, } from './props.js';
|
|
114
|
+
export type { AbsoluteProps, DrawerProps, GridProps, ImageProps, KeyboardAvoidingProps, LayoutChildProps, PressableProps, RichTextProps, RichTextSpanProps, RowProps, ScreenProps, SafeAreaProps, ScrollBoxProps, ScrollViewProps, SpacerProps, StackProps, SwitchProps, TabsProps, TextAreaProps, TextInputProps, TextProps, ViewProps, } from './props.js';
|