@octane-xplat/pager 0.14.0 → 0.16.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/package.json +13 -9
- package/src/Pager.parity.check.ts +19 -0
- package/src/Pager.parity.tsrx +19 -0
- package/src/Pager.tsrx +182 -130
- package/src/Pager.web.tsrx +7 -4
- package/src/layout-child.ts +71 -33
- package/src/style-merge.ts +59 -0
- package/dist/native/Pager.tsrx.js +0 -282
- package/dist/native/cx.mobile.js +0 -12
- package/dist/native/escape-props.mobile.js +0 -39
- package/dist/native/index.js +0 -2
- package/dist/native/layout-child.js +0 -38
- package/dist/native/octane-client-build.json +0 -8
- package/dist/web/Pager.web.tsrx.js +0 -334
- package/dist/web/index.js +0 -2
- package/dist/web/layout-child.web.js +0 -47
- package/dist/web/octane-client-build.json +0 -8
- package/dist/web/platformProps.web.js +0 -9
- package/src/Pager.web.test.tsrx +0 -83
- package/src/cx.mobile.ts +0 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/pager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Paged swipe container for Octane xplat — web scroll-snap row + @nativescript-community/ui-pager (ViewPager2 / paging UICollectionView)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
"directory": "packages/pager"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"dist",
|
|
28
27
|
"types",
|
|
29
|
-
"src"
|
|
28
|
+
"src",
|
|
29
|
+
"!src/**/*.test.*",
|
|
30
|
+
"!src/**/*.spec.*"
|
|
30
31
|
],
|
|
31
32
|
"type": "module",
|
|
32
33
|
"exports": {
|
|
@@ -36,9 +37,9 @@
|
|
|
36
37
|
"default": "./src/index.macos.ts"
|
|
37
38
|
},
|
|
38
39
|
"types": "./types/index.d.ts",
|
|
39
|
-
"web": "./
|
|
40
|
-
"native": "./
|
|
41
|
-
"default": "./
|
|
40
|
+
"web": "./src/index.web.ts",
|
|
41
|
+
"native": "./src/index.ts",
|
|
42
|
+
"default": "./src/index.ts"
|
|
42
43
|
}
|
|
43
44
|
},
|
|
44
45
|
"publishConfig": {
|
|
@@ -48,17 +49,20 @@
|
|
|
48
49
|
"@nativescript-community/ui-pager": "14.1.42"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
|
-
"@nativescript-community/octane": "0.2.
|
|
52
|
+
"@nativescript-community/octane": "0.2.5",
|
|
52
53
|
"@nativescript/core": "9.1.3",
|
|
54
|
+
"@octane-xplat/macos-renderer": "0.16.0",
|
|
55
|
+
"@octane-xplat/ui": "0.16.0",
|
|
53
56
|
"@octanejs/vite-plugin": "0.1.61",
|
|
54
57
|
"octane": "0.11.0",
|
|
55
|
-
"tsrx-typegen": "0.
|
|
58
|
+
"tsrx-typegen": "0.3.1",
|
|
56
59
|
"typescript": "5.9.3",
|
|
57
60
|
"vite": "8.3.0"
|
|
58
61
|
},
|
|
59
62
|
"peerDependencies": {
|
|
60
63
|
"@nativescript-community/octane": ">=0.2.1 <1",
|
|
61
64
|
"@nativescript/core": ">=9.1.0 <10",
|
|
65
|
+
"@octane-xplat/ui": ">=0.6.0 <1",
|
|
62
66
|
"octane": ">=0.6.3 <1"
|
|
63
67
|
},
|
|
64
68
|
"peerDependenciesMeta": {
|
|
@@ -70,7 +74,7 @@
|
|
|
70
74
|
}
|
|
71
75
|
},
|
|
72
76
|
"scripts": {
|
|
73
|
-
"
|
|
77
|
+
"test:native": "vitest run --config vitest.native.config.mts",
|
|
74
78
|
"typegen": "tsc -p tsconfig.types.json",
|
|
75
79
|
"test:packed": "node tests/packed-consumer.mjs",
|
|
76
80
|
"pack:check": "tsrx-typegen --pack-check"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ParityCheck } from '@octane-xplat/ui/parity/check-helpers'
|
|
2
|
+
import { dims } from '@octane-xplat/ui/parity/check-helpers'
|
|
3
|
+
|
|
4
|
+
export const CHECKS: ParityCheck[] = [
|
|
5
|
+
|
|
6
|
+
{
|
|
7
|
+
fixture: 'pager-two-pages',
|
|
8
|
+
targets: ['web', 'ios', 'android'],
|
|
9
|
+
elements: { pager: 'parity-pager-root', page: 'parity-pager-page' },
|
|
10
|
+
equal: [
|
|
11
|
+
'pager.box.w',
|
|
12
|
+
'pager.box.h',
|
|
13
|
+
'pager.style.backgroundColor',
|
|
14
|
+
'page.box.w',
|
|
15
|
+
'page.box.h',
|
|
16
|
+
],
|
|
17
|
+
check: (m) => [dims(m('pager'), 220, 96), dims(m('page'), 220, 96)],
|
|
18
|
+
}
|
|
19
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Measured parity fixtures — assertions live in Pager.parity.check.ts.
|
|
2
|
+
import { Pager } from './Pager'
|
|
3
|
+
import { View } from '@octane-xplat/ui'
|
|
4
|
+
import type { ParityFixture } from '@octane-xplat/ui/parity'
|
|
5
|
+
|
|
6
|
+
function PagerFixture() @{
|
|
7
|
+
<Pager
|
|
8
|
+
className="parity-pager-root"
|
|
9
|
+
style={{ width: 220, height: 96, backgroundColor: '#f5f5f5' }}
|
|
10
|
+
items={[{ key: 'first' }, { key: 'second' }]}
|
|
11
|
+
renderItem={() => (
|
|
12
|
+
<View className="parity-pager-page" style={{ width: 220, height: 96 }} />
|
|
13
|
+
)}
|
|
14
|
+
/>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const PARITY: ParityFixture[] = [
|
|
18
|
+
{ name: 'pager-two-pages', component: 'Pager', renderer: PagerFixture, props: {} },
|
|
19
|
+
]
|
package/src/Pager.tsrx
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
-
import { memo, useCallback, useEffect, useRef } from 'octane'
|
|
2
|
+
import { memo, useCallback, useEffect, useRef, useState } from 'octane'
|
|
3
|
+
import { registerElement } from '@nativescript-community/octane'
|
|
4
|
+
|
|
5
|
+
import type { Attributes } from '@nativescript-community/octane/intrinsics'
|
|
6
|
+
import {
|
|
7
|
+
createPortal as createUniversalPortal,
|
|
8
|
+
type UniversalRenderable,
|
|
9
|
+
} from 'octane/universal/native'
|
|
10
|
+
|
|
11
|
+
import { Pager as NSPager } from '@nativescript-community/ui-pager'
|
|
12
|
+
import { GridLayout } from '@nativescript/core'
|
|
13
|
+
import type { PagerProps } from './props'
|
|
3
14
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import type { Attributes } from '@nativescript-community/octane/intrinsics';
|
|
11
|
-
import type { UniversalRoot } from '@nativescript-community/octane';
|
|
12
|
-
import { Pager as NSPager } from '@nativescript-community/ui-pager';
|
|
13
|
-
import { GridLayout } from '@nativescript/core';
|
|
14
|
-
import type { PagerProps } from './props';
|
|
15
|
-
import { cx } from './cx.mobile';
|
|
16
|
-
import { applyEscapeProps, nativeAccessibilityRole, nativeAccessibilityState } from './escape-props.mobile';
|
|
17
|
-
import { layoutChildProps } from './layout-child';
|
|
15
|
+
applyEscapeProps,
|
|
16
|
+
nativeAccessibilityRole,
|
|
17
|
+
nativeAccessibilityState,
|
|
18
|
+
} from './escape-props.mobile'
|
|
19
|
+
|
|
20
|
+
import { layoutChildProps } from './layout-child'
|
|
18
21
|
|
|
19
22
|
declare module '@nativescript-community/octane/intrinsics' {
|
|
20
23
|
interface NativeScriptElements {
|
|
21
|
-
pager: Attributes<typeof NSPager
|
|
24
|
+
pager: Attributes<typeof NSPager>
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
|
|
@@ -26,166 +29,189 @@ declare module '@nativescript-community/octane/intrinsics' {
|
|
|
26
29
|
// UICollectionView on iOS. It is a required peer: this leaf sits in the
|
|
27
30
|
// shared native barrel, so every native app must declare the plugin for
|
|
28
31
|
// the /ns/m dev bridge to resolve it under the app root.
|
|
29
|
-
registerElement('pager', NSPager)
|
|
32
|
+
registerElement('pager', NSPager)
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* ui-pager fires the same `itemLoading`/`itemDisposing` pair. */
|
|
34
|
-
interface PagerCell {
|
|
35
|
-
host: any;
|
|
36
|
-
root: UniversalRoot;
|
|
37
|
-
pager: any;
|
|
38
|
-
renderItem: any;
|
|
39
|
-
item: any;
|
|
40
|
-
index: number;
|
|
34
|
+
function createPortal(children: unknown, target: unknown) {
|
|
35
|
+
return createUniversalPortal(children as UniversalRenderable, target)
|
|
41
36
|
}
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
/** One recycled page: the adapter-owned host plus the current render data.
|
|
39
|
+
* Its content is a portal from Pager's declaring tree. */
|
|
40
|
+
interface PagerCell {
|
|
41
|
+
host: any
|
|
42
|
+
pager: any
|
|
43
|
+
renderItem: any
|
|
44
|
+
item: any
|
|
45
|
+
index: number
|
|
46
|
+
}
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* gridlayout — page content must stay single-root. */
|
|
49
|
-
const PagerCell = defineUniversalComponent(NATIVESCRIPT_RENDERER_ID, (props: any) =>
|
|
50
|
-
props.renderItem(props.item, props.index),
|
|
51
|
-
);
|
|
48
|
+
const cellsByHost = new WeakMap<object, PagerCell>()
|
|
49
|
+
const cellsByPager = new WeakMap<object, Set<PagerCell>>()
|
|
52
50
|
|
|
53
51
|
function cellFor(host: any): PagerCell {
|
|
54
|
-
let cell = cellsByHost.get(host)
|
|
52
|
+
let cell = cellsByHost.get(host)
|
|
55
53
|
if (!cell) {
|
|
56
|
-
cell = { host,
|
|
57
|
-
cellsByHost.set(host, cell)
|
|
54
|
+
cell = { host, pager: null, renderItem: null, item: undefined, index: -1 }
|
|
55
|
+
cellsByHost.set(host, cell)
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
return cell
|
|
58
|
+
return cell
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
/** `createView` runs inside the plugin's adapter — no pager instance in
|
|
64
62
|
* scope, so the cell is only linked to its pager at `itemLoading` (where
|
|
65
63
|
* `args.object` is the owner). */
|
|
66
64
|
function createPageHost() {
|
|
67
|
-
const host = new GridLayout()
|
|
68
|
-
cellFor(host)
|
|
69
|
-
return host
|
|
65
|
+
const host = new GridLayout()
|
|
66
|
+
cellFor(host)
|
|
67
|
+
return host
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
const PAGER_TEMPLATES = [{ key: 'xplat-page', createView: createPageHost }]
|
|
70
|
+
const PAGER_TEMPLATES = [{ key: 'xplat-page', createView: createPageHost }]
|
|
73
71
|
// The built-in 'default' template stays first in the plugin's internal
|
|
74
72
|
// list, so the selector must name our key — a 'default' entry would match
|
|
75
73
|
// the built-in (an XML string parse, which yields nothing here).
|
|
76
|
-
const selectPageTemplate = () => 'xplat-page'
|
|
74
|
+
const selectPageTemplate = () => 'xplat-page'
|
|
77
75
|
|
|
78
76
|
function releasePagerCells(pager: any) {
|
|
79
|
-
const cells = cellsByPager.get(pager)
|
|
80
|
-
if (!cells)
|
|
77
|
+
const cells = cellsByPager.get(pager)
|
|
78
|
+
if (!cells) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
81
82
|
for (const cell of cells) {
|
|
82
|
-
cellsByHost.delete(cell.host)
|
|
83
|
-
cell.root.unmount();
|
|
83
|
+
cellsByHost.delete(cell.host)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
cellsByPager.delete(pager)
|
|
86
|
+
cellsByPager.delete(pager)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function PagerInner(props: PagerProps) @{
|
|
90
|
-
const host = useRef<any>(null)
|
|
91
|
-
|
|
90
|
+
const host = useRef<any>(null)
|
|
91
|
+
const [portalCells, setPortalCells] = useState<PagerCell[]>([])
|
|
92
|
+
// Track selectedIndex writes across synchronous and delayed echoes.
|
|
93
|
+
// The plugin raises
|
|
92
94
|
// 'selectedIndexChange' for script writes exactly as for swipes, and
|
|
93
95
|
// the driver's auto-mute only covers JSX prop writes, not ours.
|
|
94
|
-
const
|
|
95
|
-
const pageSeeded = useRef(false)
|
|
96
|
+
const current = useRef(props.page ?? props.defaultPage ?? 0)
|
|
97
|
+
const pageSeeded = useRef(false)
|
|
96
98
|
|
|
97
99
|
// Stable ref identity — an inline closure re-fires ref(null)/ref(v) per
|
|
98
100
|
// render, which would bounce host.current around the effects below.
|
|
99
101
|
const bindHost = useCallback((v: any) => {
|
|
100
|
-
host.current = v
|
|
101
|
-
}, [])
|
|
102
|
+
host.current = v
|
|
103
|
+
}, [])
|
|
102
104
|
|
|
103
105
|
const bindCell = (args: any) => {
|
|
104
|
-
const cell = cellFor(args.view)
|
|
105
|
-
cell.pager = args.object
|
|
106
|
-
let owned = cellsByPager.get(cell.pager)
|
|
106
|
+
const cell = cellFor(args.view)
|
|
107
|
+
cell.pager = args.object
|
|
108
|
+
let owned = cellsByPager.get(cell.pager)
|
|
107
109
|
if (!owned) {
|
|
108
|
-
owned = new Set()
|
|
109
|
-
cellsByPager.set(cell.pager, owned)
|
|
110
|
+
owned = new Set()
|
|
111
|
+
cellsByPager.set(cell.pager, owned)
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
owned.add(cell)
|
|
113
|
-
const item = args.bindingContext ?? props.items?.[args.index]
|
|
114
|
-
if (
|
|
115
|
-
|
|
114
|
+
owned.add(cell)
|
|
115
|
+
const item = args.bindingContext ?? props.items?.[args.index]
|
|
116
|
+
if (
|
|
117
|
+
cell.renderItem === props.renderItem &&
|
|
118
|
+
cell.index === args.index &&
|
|
119
|
+
Object.is(cell.item, item)
|
|
120
|
+
) {
|
|
121
|
+
return
|
|
116
122
|
}
|
|
117
123
|
|
|
118
|
-
cell.renderItem = props.renderItem
|
|
119
|
-
cell.item = item
|
|
120
|
-
cell.index = args.index
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
};
|
|
124
|
+
cell.renderItem = props.renderItem
|
|
125
|
+
cell.item = item
|
|
126
|
+
cell.index = args.index
|
|
127
|
+
setPortalCells((current) => {
|
|
128
|
+
const existing = current.some((entry) => entry === cell)
|
|
129
|
+
return existing ? [...current] : [...current, cell]
|
|
130
|
+
})
|
|
131
|
+
}
|
|
127
132
|
|
|
128
133
|
const disposeCell = (args: any) => {
|
|
129
|
-
const cell = args.view && cellsByHost.get(args.view)
|
|
134
|
+
const cell = args.view && cellsByHost.get(args.view)
|
|
130
135
|
if (cell) {
|
|
131
|
-
cellsByHost.delete(args.view)
|
|
132
|
-
if (cell.pager)
|
|
133
|
-
|
|
136
|
+
cellsByHost.delete(args.view)
|
|
137
|
+
if (cell.pager) {
|
|
138
|
+
cellsByPager.get(cell.pager)?.delete(cell)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setPortalCells((current) => current.filter((entry) => entry !== cell))
|
|
134
142
|
}
|
|
135
|
-
}
|
|
143
|
+
}
|
|
136
144
|
|
|
137
145
|
// One-time wiring (mount): the template set + selector are imperative —
|
|
138
146
|
// as JSX props they would be re-applied on every render. The cleanup
|
|
139
147
|
// captures the pager so cell roots release after the ref detaches.
|
|
140
148
|
useEffect(() => {
|
|
141
|
-
const pager = host.current
|
|
142
|
-
if (!pager)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
const pager = host.current
|
|
150
|
+
if (!pager) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
pager.itemTemplates = PAGER_TEMPLATES
|
|
155
|
+
pager.itemTemplateSelector = selectPageTemplate
|
|
156
|
+
return () => releasePagerCells(pager)
|
|
157
|
+
}, [])
|
|
147
158
|
|
|
148
159
|
// Items and the index are imperative writes, not JSX props: `items` must
|
|
149
160
|
// land before `selectedIndex` (the plugin coerces the index against the
|
|
150
161
|
// page count — a write ahead of items clamps to -1), and a re-assert
|
|
151
162
|
// after an items change repairs the plugin's own re-coercion.
|
|
152
163
|
useEffect(() => {
|
|
153
|
-
const pager = host.current
|
|
154
|
-
if (!pager)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
const pager = host.current
|
|
165
|
+
if (!pager) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
applyEscapeProps(pager, props)
|
|
170
|
+
if (pager.items !== props.items) {
|
|
171
|
+
pager.items = props.items
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const target = props.page ?? (pageSeeded.current ? undefined : props.defaultPage)
|
|
158
175
|
if (target !== undefined && pager.selectedIndex !== target) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
} finally {
|
|
163
|
-
suppressEcho.current = false;
|
|
164
|
-
}
|
|
176
|
+
// Track the coerced target before writing: Android may echo later.
|
|
177
|
+
current.current = Math.max(0, Math.min(props.items.length - 1, target))
|
|
178
|
+
pager.selectedIndex = target
|
|
165
179
|
}
|
|
166
180
|
|
|
167
|
-
pageSeeded.current = true
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
181
|
+
pageSeeded.current = true
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
<>
|
|
185
|
+
<pager
|
|
186
|
+
ref={bindHost}
|
|
187
|
+
id={props.id}
|
|
188
|
+
{...layoutChildProps(props)}
|
|
189
|
+
className={['vx-pager', props.className]}
|
|
190
|
+
onItemLoading={bindCell}
|
|
191
|
+
onItemDisposing={disposeCell}
|
|
192
|
+
onSelectedIndexChange={(args: any) => {
|
|
193
|
+
if (args.value !== current.current) {
|
|
194
|
+
current.current = args.value
|
|
195
|
+
props.onPageChange?.(args.value)
|
|
196
|
+
}
|
|
197
|
+
}}
|
|
198
|
+
accessibilityRole={nativeAccessibilityRole(props.accessibilityRole) as any}
|
|
199
|
+
accessibilityLabel={props.accessibilityLabel}
|
|
200
|
+
accessibilityHint={props.accessibilityHint}
|
|
201
|
+
accessibilityValue={props.accessibilityValue}
|
|
202
|
+
accessibilityState={nativeAccessibilityState(props.accessibilityState) as any}
|
|
203
|
+
accessible={props.accessible}
|
|
204
|
+
accessibilityLiveRegion={props.accessibilityLiveRegion as any}
|
|
205
|
+
/>
|
|
206
|
+
{portalCells.map((cell) =>
|
|
207
|
+
createPortal(
|
|
208
|
+
<masonview style={{ display: 'grid' }}>
|
|
209
|
+
{cell.renderItem?.(cell.item, cell.index)}
|
|
210
|
+
</masonview>,
|
|
211
|
+
cell.host,
|
|
212
|
+
),
|
|
213
|
+
)}
|
|
214
|
+
</>
|
|
189
215
|
}
|
|
190
216
|
|
|
191
217
|
// The pager subtree is memoized on `items` and configuration: the universal
|
|
@@ -194,28 +220,54 @@ function PagerInner(props: PagerProps) @{
|
|
|
194
220
|
// UITableView: `renderItem` should be identity-stable — a fresh closure per
|
|
195
221
|
// render is deliberately not part of the compare. (`PagerInner` must precede
|
|
196
222
|
// this — compiled components are consts, not hoisted functions.)
|
|
197
|
-
const MemoPager = memo(
|
|
198
|
-
|
|
199
|
-
a
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
223
|
+
const MemoPager = memo(
|
|
224
|
+
PagerInner,
|
|
225
|
+
(a, b) =>
|
|
226
|
+
a.items === b.items &&
|
|
227
|
+
a.className === b.className &&
|
|
228
|
+
a.style === b.style &&
|
|
229
|
+
a.id === b.id &&
|
|
230
|
+
a.page === b.page &&
|
|
231
|
+
a.onPageChange === b.onPageChange &&
|
|
232
|
+
a.ios === b.ios &&
|
|
233
|
+
a.android === b.android &&
|
|
234
|
+
a.row === b.row &&
|
|
235
|
+
a.col === b.col &&
|
|
236
|
+
a.rowSpan === b.rowSpan &&
|
|
237
|
+
a.colSpan === b.colSpan &&
|
|
238
|
+
a.dock === b.dock &&
|
|
239
|
+
a.left === b.left &&
|
|
240
|
+
a.top === b.top &&
|
|
241
|
+
a.flexGrow === b.flexGrow &&
|
|
242
|
+
a.flexShrink === b.flexShrink &&
|
|
243
|
+
a.alignSelf === b.alignSelf &&
|
|
244
|
+
a.order === b.order &&
|
|
245
|
+
a.accessible === b.accessible &&
|
|
246
|
+
a.accessibilityRole === b.accessibilityRole &&
|
|
247
|
+
a.accessibilityLabel === b.accessibilityLabel &&
|
|
248
|
+
a.accessibilityHint === b.accessibilityHint &&
|
|
249
|
+
a.accessibilityValue === b.accessibilityValue &&
|
|
250
|
+
a.accessibilityState === b.accessibilityState &&
|
|
251
|
+
a.accessibilityLiveRegion === b.accessibilityLiveRegion,
|
|
252
|
+
)
|
|
207
253
|
|
|
208
254
|
/** Pager — paged horizontal swiping via @nativescript-community/ui-pager
|
|
209
255
|
* (ViewPager2 / paging UICollectionView), chrome-reset: one full-size cell
|
|
210
256
|
* per item, no indicator. Pages recycle through `itemTemplates` +
|
|
211
|
-
* `itemLoading
|
|
212
|
-
*
|
|
257
|
+
* `itemLoading`; active cell hosts are portals from the Pager's declaring
|
|
258
|
+
* tree, so provider context follows each cell. */
|
|
213
259
|
export function Pager(props: PagerProps) @{
|
|
214
260
|
@if (props.items.length > 0) {
|
|
215
261
|
<MemoPager
|
|
262
|
+
accessible={props.accessible}
|
|
263
|
+
accessibilityRole={props.accessibilityRole}
|
|
264
|
+
accessibilityLabel={props.accessibilityLabel}
|
|
265
|
+
accessibilityHint={props.accessibilityHint}
|
|
266
|
+
accessibilityValue={props.accessibilityValue}
|
|
267
|
+
accessibilityState={props.accessibilityState}
|
|
268
|
+
accessibilityLiveRegion={props.accessibilityLiveRegion}
|
|
216
269
|
id={props.id}
|
|
217
270
|
className={props.className}
|
|
218
|
-
style={props.style}
|
|
219
271
|
items={props.items}
|
|
220
272
|
renderItem={props.renderItem}
|
|
221
273
|
page={props.page}
|
package/src/Pager.web.tsrx
CHANGED
|
@@ -17,6 +17,7 @@ export function Pager(props: PagerProps) @{
|
|
|
17
17
|
// updated by controlled writes and settled swipes. A scroll that lands
|
|
18
18
|
// on `current` is programmatic — the settle never reports it.
|
|
19
19
|
const current = useRef(props.page ?? props.defaultPage ?? 0);
|
|
20
|
+
const hasItems = props.items.length > 0;
|
|
20
21
|
const settle = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
21
22
|
|
|
22
23
|
useEffect(() => {
|
|
@@ -45,17 +46,19 @@ export function Pager(props: PagerProps) @{
|
|
|
45
46
|
return () => {
|
|
46
47
|
el.removeEventListener('scroll', onScroll);
|
|
47
48
|
if (settle.current !== null) clearTimeout(settle.current);
|
|
49
|
+
settle.current = null;
|
|
48
50
|
};
|
|
49
|
-
}, []);
|
|
51
|
+
}, [hasItems]);
|
|
50
52
|
|
|
51
|
-
//
|
|
53
|
+
// Each new host: jump (no animation) to the starting page — an initial position
|
|
52
54
|
// is not a scroll. Runs after the scroll listener attaches so the write
|
|
53
55
|
// still flows through the tracked-index path.
|
|
54
56
|
useEffect(() => {
|
|
55
57
|
const el = host.current;
|
|
56
|
-
if (!el
|
|
58
|
+
if (!el) return;
|
|
59
|
+
current.current = props.page ?? current.current;
|
|
57
60
|
el.scrollLeft = current.current * el.clientWidth;
|
|
58
|
-
}, []);
|
|
61
|
+
}, [hasItems]);
|
|
59
62
|
|
|
60
63
|
// Controlled writes scroll smoothly. Skipped when the page already
|
|
61
64
|
// matches `current` — a user swipe that the parent echoes back unchanged
|